【STM32】智能门锁项目

智能门锁

1. 功能

思路:

正确输入密码,开锁

#确定输入,*修改密码

密码保存在W25Q128里

OLED屏幕显示信息

接线:

|---------|---------|------|
| STM32引脚 | 连接模块 | 模块引脚 |
| PB0 | 矩阵键盘 | R1 |
| PB1 | 矩阵键盘 | R2 |
| PB2 | 矩阵键盘 | R3 |
| PB10 | 矩阵键盘 | R4 |
| PB11 | 矩阵键盘 | C1 |
| PB12 | 矩阵键盘 | C2 |
| PB13 | 矩阵键盘 | C3 |
| PB8 | OLED屏幕 | SCL |
| PB9 | OLED屏幕 | SDA |
| PC13 | 蜂鸣器 | I/O |
| PA4 | W25Q128 | CS |
| PA5 | W25Q128 | CLK |
| PA6 | W25Q128 | DO |
| PA7 | W25Q128 | DI |
| PB7 | 继电器 | I/O |
| 5V | | VCC |
| 3.3V | | VCC |
| GND | | GND |

输出:OLED、蜂鸣器、继电器

输入:矩阵键盘

2. 键盘功能实现(keyboard)

STM32 HAL 库实现的4 行 3 列矩阵键盘驱动 ,采用「行中断触发 + 列电平检测」的方式实现按键识别,核心优势是非轮询(中断驱动),相比纯轮询更节省 CPU 资源。核心逻辑:

  1. 行引脚(PB0/PB1/PB2/PB10)配置为下降沿中断 + 上拉输入,作为 "按键触发检测端";

  2. 列引脚(PB11/PB12/PB13)配置为下拉输入,作为 "按键位置识别端";

  3. 按下按键时,对应行引脚电平下降触发中断,在中断回调中检测列电平,通过 "行 + 列" 编码确定按键值;

  4. 封装keyboard_get_value接口,返回标准化按键字符(如 '1'/'0'/'*' 等)。

  5. 矩阵键盘硬件映射(先明确接线)

代码对应的 4 行 3 列键盘硬件接线:

  1. 键盘初始化(keyboard_init)

行引脚上拉: 未按键时为高电平,按下按键后,行引脚通过按键与列引脚(下拉)连通,电平被拉低 → 触发下降沿中断;
**列引脚下拉:**未按键时为低电平,按下按键后,列引脚通过按键与行引脚(上拉)连通,电平被拉高 → 可检测到高电平。

cpp 复制代码
void keyboard_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;

    __HAL_RCC_GPIOB_CLK_ENABLE();                           
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_10;          
    gpio_initstruct.Mode = GPIO_MODE_IT_FALLING;             
    gpio_initstruct.Pull = GPIO_PULLUP;                     
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
    
    gpio_initstruct.Pin = GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13;          
    gpio_initstruct.Mode = GPIO_MODE_INPUT;             
    gpio_initstruct.Pull = GPIO_PULLDOWN;                     
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
    
    HAL_NVIC_SetPriority(EXTI0_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    
    HAL_NVIC_SetPriority(EXTI1_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(EXTI1_IRQn);
    
    HAL_NVIC_SetPriority(EXTI2_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(EXTI2_IRQn);
    
    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
  1. 中断服务函数(EXTI 中断入口)
cpp 复制代码
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

void EXTI1_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}

void EXTI2_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}

void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
}

STM32 的 GPIO 中断按引脚号分组(如 EXTI0 对应所有 GPIOx_PIN_0),需为每个行引脚编写中断入口;

内部调用HAL_GPIO_EXTI_IRQHandler:HAL 库通用中断处理函数,会自动清除中断标志位,并调用回调函数HAL_GPIO_EXTI_Callback。

  1. 中断回调函数(核心按键识别)

当按键按下时,行线产生下降沿中断👈初始化中配置了 PB0 为GPIO_MODE_IT_FALLING(下降沿触发)

在中断中扫描所有列线,看哪一列为高电平

将行列值组合成唯一键值

cpp 复制代码
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    uint8_t row = 0, column = 0;
    
    if(key_value != 0) return;
    //确认行
    if(GPIO_Pin == GPIO_PIN_0)
        row = 0x10;
    else if(GPIO_Pin == GPIO_PIN_1)
        row = 0x20;
    else if(GPIO_Pin == GPIO_PIN_2)
        row = 0x30;
    else if(GPIO_Pin == GPIO_PIN_10)
        row = 0x40;
    
    //确定列
    if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11) == GPIO_PIN_SET)
    {
        delay_ms(10);
        while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11))
            column = 0x01;
    }
    else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_SET)
    {
        delay_ms(10);
        while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12))
            column = 0x02;
    }
    else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13) == GPIO_PIN_SET)
    {
        delay_ms(10);
        while(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_13))
            column = 0x03;
    }
    
    if(row != 0 && column != 0)
        key_value = row | column;
}

key_value = row | column;是什么意思?

// 行值(高4位)

row = 0x10; // 第0行 → 0001 0000

row = 0x20; // 第1行 → 0010 0000

row = 0x30; // 第2行 → 0011 0000

row = 0x40; // 第3行 → 0100 0000

// 列值(低4位)

column = 0x01; // 第0列 → 0000 0001

column = 0x02; // 第1列 → 0000 0010

column = 0x03; // 第2列 → 0000 0011

// 假设按下第1行第2列的按键(应该是'5')

row = 0x20; // 0010 0000

column = 0x02; // 0000 0010

key_value = row | column; // 0010 0010 = 0x22

// 按下第3行第1列的按键 (应该是'*')

row = 0x40; // 0100 0000

column = 0x01; // 0000 0001

key_value = row | column; // 0100 0001 = 0x41

  1. 按键值读取接口(keyboard_get_value)

将内部键值转换为实际字符

添加去抖动延迟

清除键值标志

cpp 复制代码
uint8_t keyboard_get_value(void)
{
    uint8_t ch = 0;
    if(key_value != 0)
    {
        if(key_value == 0x11) ch = '1';
        else if(key_value == 0x12) ch = '2';
        else if(key_value == 0x13) ch = '3';
        else if(key_value == 0x21) ch = '4';
        else if(key_value == 0x22) ch = '5';
        else if(key_value == 0x23) ch = '6';
        else if(key_value == 0x31) ch = '7';
        else if(key_value == 0x32) ch = '8';
        else if(key_value == 0x33) ch = '9';
        else if(key_value == 0x41) ch = '*';
        else if(key_value == 0x42) ch = '0';
        else if(key_value == 0x43) ch = '#';
        delay_ms(400);
        key_value = 0x00;
    }
    return ch;
}

将中断中生成的编码(如 0x11)转换为直观的字符(如 '1'),方便上层程序使用;

delay_ms(400):防连击,避免快速按多次或长按导致返回多个字符;

读取后清空key_value,保证下次按键能被正常识别。

3. 蜂鸣器(beep)和门锁(lock)

蜂鸣器只是更改了引脚

cpp 复制代码
#include "beep.h"
#include "sys.h"

//初始化GPIO函数
void beep_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    //打开时钟
    __HAL_RCC_GPIOC_CLK_ENABLE();                           // 使能GPIOB时钟
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_13;                       // 蜂鸣器对应的引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;             // 推挽输出
    gpio_initstruct.Pull = GPIO_PULLUP;                     // 上拉
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           // 高速
    HAL_GPIO_Init(GPIOC, &gpio_initstruct);
    //关闭蜂鸣器
    beep_off();
}

//打开蜂鸣器的函数
void beep_on(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);   // 拉低蜂鸣器引脚,打开蜂鸣器
}

//关闭蜂鸣器的函数
void beep_off(void)
{
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);     // 拉高蜂鸣器引脚,关闭蜂鸣器
}

门锁通过继电器实现

cpp 复制代码
#include "lock.h"
#include "sys.h"

//初始化GPIO函数
void lock_init(void)
{
    GPIO_InitTypeDef gpio_initstruct;
    //打开时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();                           // 使能GPIOB时钟
    
    //调用GPIO初始化函数
    gpio_initstruct.Pin = GPIO_PIN_7;                       // 继电器对应的引脚
    gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;             // 推挽输出
    gpio_initstruct.Pull = GPIO_PULLUP;                     // 上拉
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;           // 高速
    HAL_GPIO_Init(GPIOB, &gpio_initstruct);
    //关闭LED
    lock_off();
}

//闭合继电器的函数
void lock_on(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);   // 拉低LED1引脚,点亮LED1
}

//松开继电器的函数
void lock_off(void)
{
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);     // 拉高LED1引脚,熄灭LED1
}

//获取继电器状态的函数
uint8_t lock_status_get(void)
{
    return (uint8_t)HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7);
}

4. 屏幕显示(oled)

在font.h中依次增加新字

font.h中依次补充

chinese_enter_password[] // "请输入密码"

chinese_password_right[] // "密码正确"

chinese_password_error[] // "密码错误"

chinese_enter_old_password[] // "请输入旧密码"

chinese_enter_new_password[] // "请输入新密码"

chinese_password_modified[] // "密码修改成功"

chinese_password_setting[] // "请设定密码"

cpp 复制代码
//16号汉字:请 输 入 密 码
const unsigned char chinese_enter_password[][32] =
{  
    0x40,0x42,0xCC,0x00,0x00,0x44,0x54,0x54,0x54,0x7F,0x54,0x54,0x54,0x44,0x40,0x00,
    0x00,0x00,0x7F,0x20,0x10,0x00,0xFF,0x15,0x15,0x15,0x55,0x95,0x7F,0x00,0x00,0x00,/*"请",0*/

    0x88,0x68,0x1F,0xC8,0x08,0x10,0xC8,0x54,0x52,0xD1,0x12,0x94,0x08,0xD0,0x10,0x00,
    0x09,0x19,0x09,0xFF,0x05,0x00,0xFF,0x12,0x92,0xFF,0x00,0x5F,0x80,0x7F,0x00,0x00,/*"输",1*/

    0x00,0x00,0x00,0x00,0x00,0x01,0xE2,0x1C,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x03,0x0C,0x30,0x40,0x80,0x80,0x00,/*"入",2*/

    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",3*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",4*/

    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/*":",5*/
};

//16号汉字: 密 码 正 确
const unsigned char chinese_password_right[][32] =
{  
    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",0*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",1*/

    0x00,0x02,0x02,0xC2,0x02,0x02,0x02,0xFE,0x82,0x82,0x82,0x82,0x82,0x02,0x00,0x00,
    0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x7F,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,/*"正",2*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x20,0x10,0xE8,0x27,0x24,0xE4,0x34,0x2C,0xE0,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x80,0x60,0x1F,0x09,0x09,0x3F,0x49,0x89,0x7F,0x00,/*"确",3*/
};

//16号汉字: 密 码 错 误
const unsigned char chinese_password_error[][32] =
{  
    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",0*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",1*/

    0x40,0x30,0xEF,0x24,0x64,0x48,0x48,0x7F,0x48,0x48,0x48,0x7F,0x48,0x48,0x40,0x00,
    0x01,0x01,0x7F,0x21,0x11,0x00,0xFF,0x49,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,0x00,/*"错",2*/

    0x40,0x42,0xCC,0x00,0x00,0x80,0x9E,0x92,0x92,0x92,0x92,0x92,0x9E,0x80,0x00,0x00,
    0x00,0x00,0x7F,0x20,0x94,0x84,0x44,0x24,0x14,0x0F,0x14,0x24,0x44,0x84,0x84,0x00,/*"误",3*/
};

//16号汉字: 请 输 入 旧 密 码
const unsigned char chinese_enter_old_password[][32] =
{  
    0x40,0x42,0xCC,0x00,0x00,0x44,0x54,0x54,0x54,0x7F,0x54,0x54,0x54,0x44,0x40,0x00,
    0x00,0x00,0x7F,0x20,0x10,0x00,0xFF,0x15,0x15,0x15,0x55,0x95,0x7F,0x00,0x00,0x00,/*"请",0*/

    0x88,0x68,0x1F,0xC8,0x08,0x10,0xC8,0x54,0x52,0xD1,0x12,0x94,0x08,0xD0,0x10,0x00,
    0x09,0x19,0x09,0xFF,0x05,0x00,0xFF,0x12,0x92,0xFF,0x00,0x5F,0x80,0x7F,0x00,0x00,/*"输",1*/

    0x00,0x00,0x00,0x00,0x00,0x01,0xE2,0x1C,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x03,0x0C,0x30,0x40,0x80,0x80,0x00,/*"入",2*/

    0x00,0x00,0xFF,0x00,0x00,0x00,0xFE,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x00,0x00,
    0x00,0x00,0xFF,0x00,0x00,0x00,0x7F,0x20,0x20,0x20,0x20,0x20,0x20,0x7F,0x00,0x00,/*"旧",3*/

    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",4*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",5*/
};

//16号汉字: 请 输 入 新 密 码
const unsigned char chinese_enter_new_password[][32] =
{  
    0x40,0x42,0xCC,0x00,0x00,0x44,0x54,0x54,0x54,0x7F,0x54,0x54,0x54,0x44,0x40,0x00,
    0x00,0x00,0x7F,0x20,0x10,0x00,0xFF,0x15,0x15,0x15,0x55,0x95,0x7F,0x00,0x00,0x00,/*"请",0*/

    0x88,0x68,0x1F,0xC8,0x08,0x10,0xC8,0x54,0x52,0xD1,0x12,0x94,0x08,0xD0,0x10,0x00,
    0x09,0x19,0x09,0xFF,0x05,0x00,0xFF,0x12,0x92,0xFF,0x00,0x5F,0x80,0x7F,0x00,0x00,/*"输",1*/

    0x00,0x00,0x00,0x00,0x00,0x01,0xE2,0x1C,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x03,0x0C,0x30,0x40,0x80,0x80,0x00,/*"入",2*/

    0x40,0x44,0x54,0x65,0xC6,0x64,0x54,0x44,0x00,0xFC,0x44,0x44,0xC4,0x42,0x40,0x00,
    0x20,0x12,0x4A,0x82,0x7F,0x02,0x0A,0x92,0x60,0x1F,0x00,0x00,0xFF,0x00,0x00,0x00,/*"新",3*/

    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",4*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",5*/
};

//16号汉字: 密 码 修 改 成 功
const unsigned char chinese_password_modified[][32] =
{  
    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",0*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",1*/

    0x40,0x20,0xF8,0x07,0xF0,0xA0,0x90,0x4C,0x57,0x24,0xA4,0x54,0x4C,0x80,0x80,0x00,
    0x00,0x00,0xFF,0x00,0x1F,0x80,0x92,0x52,0x49,0x29,0x24,0x12,0x08,0x00,0x00,0x00,/*"修",2*/

    0x04,0x84,0x84,0x84,0x84,0xFC,0x40,0x30,0xCC,0x0B,0x08,0x08,0xF8,0x08,0x08,0x00,
    0x00,0x7F,0x20,0x10,0x10,0x08,0x80,0x40,0x21,0x16,0x08,0x16,0x21,0x40,0x80,0x00,/*"改",3*/

    0x00,0x00,0xF8,0x88,0x88,0x88,0x88,0x08,0x08,0xFF,0x08,0x09,0x0A,0xC8,0x08,0x00,
    0x80,0x60,0x1F,0x00,0x10,0x20,0x1F,0x80,0x40,0x21,0x16,0x18,0x26,0x41,0xF8,0x00,/*"成",4*/

    0x08,0x08,0x08,0xF8,0x08,0x08,0x08,0x10,0x10,0xFF,0x10,0x10,0x10,0xF0,0x00,0x00,
    0x10,0x30,0x10,0x1F,0x08,0x88,0x48,0x30,0x0E,0x01,0x40,0x80,0x40,0x3F,0x00,0x00,/*"功",5*/
};

//16号汉字: 请 设 定 密 码
const unsigned char chinese_password_setting[][32] =
{  
    0x40,0x42,0xCC,0x00,0x00,0x44,0x54,0x54,0x54,0x7F,0x54,0x54,0x54,0x44,0x40,0x00,
    0x00,0x00,0x7F,0x20,0x10,0x00,0xFF,0x15,0x15,0x15,0x55,0x95,0x7F,0x00,0x00,0x00,/*"请",0*/

    0x40,0x40,0x42,0xCC,0x00,0x40,0xA0,0x9E,0x82,0x82,0x82,0x9E,0xA0,0x20,0x20,0x00,
    0x00,0x00,0x00,0x3F,0x90,0x88,0x40,0x43,0x2C,0x10,0x28,0x46,0x41,0x80,0x80,0x00,/*"设",1*/

    0x10,0x0C,0x44,0x44,0x44,0x44,0x45,0xC6,0x44,0x44,0x44,0x44,0x44,0x14,0x0C,0x00,
    0x80,0x40,0x20,0x1E,0x20,0x40,0x40,0x7F,0x44,0x44,0x44,0x44,0x44,0x40,0x40,0x00,/*"定",2*/

    0x10,0x8C,0x44,0x04,0xE4,0x04,0x95,0xA6,0x44,0x24,0x14,0x84,0x44,0x94,0x0C,0x00,
    0x02,0x02,0x7A,0x41,0x41,0x43,0x42,0x7E,0x42,0x42,0x42,0x43,0xF8,0x00,0x00,0x00,/*"密",3*/

    0x04,0x84,0xE4,0x5C,0x44,0xC4,0x00,0x02,0xF2,0x82,0x82,0x82,0xFE,0x80,0x80,0x00,
    0x02,0x01,0x7F,0x10,0x10,0x3F,0x00,0x08,0x08,0x08,0x08,0x48,0x88,0x40,0x3F,0x00,/*"码",4*/
};

oled.c中更改oled_show_chinese函数

// x,y: 显示起始坐标

// N: 汉字在字符串中的索引位置

// message_type: 要显示的消息类型

每个汉字由16x16点阵组成(32字节数据)

分上下两部分显示:上半部(0-15字节)和下半部(16-31字节)

通过双重循环遍历所有像素点

坐标(x,y) → ████████████████ ← 上半部(16字节数据)

坐标(x,y+1) → ████████████████ ← 下半部(16字节数据)

cpp 复制代码
void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N, uint8_t message_type)
{
	uint16_t i,j;
	for(j=0;j<2;j++)
	{
		oled_set_cursor(x,y+j);
		for(i=16*j;i<16*(j+1);i++)
		{
		switch(message_type)
		{
			case SHOW_INPUT_PWD:
				oled_write_data(chinese_enter_password[N][i]);
				break;
			case SHOW_PWD_RIGHT:
				oled_write_data(chinese_password_right[N][i]);
				break;
			case SHOW_PWD_WRONG:
				oled_write_data(chinese_password_error[N][i]);
				break;
			case SHOW_INPUT_OLD_PWD:
				oled_write_data(chinese_enter_old_password[N][i]);
				break;
			case SHOW_INPUT_NEW_PWD:
				oled_write_data(chinese_enter_new_password[N][i]);
				break;
			case SHOW_PWD_CHANGED:
				oled_write_data(chinese_password_modified[N][i]);
				break;
			case SHOW_SET_PWD:
				oled_write_data(chinese_password_setting[N][i]);
				break;
      default:
        break;		
			}
		}
	}
}

显示对应字符串

cpp 复制代码
//请输入密码
void oled_show_input(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_INPUT_PWD);
	oled_show_chinese(45,1,0,SHOW_INPUT_PWD);
	oled_show_chinese(70,1,0,SHOW_INPUT_PWD);
	oled_show_chinese(95,1,0,SHOW_INPUT_PWD);

}
//密码正确
void oled_show_right(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_PWD_RIGHT);
	oled_show_chinese(45,1,0,SHOW_PWD_RIGHT);
	oled_show_chinese(70,1,0,SHOW_PWD_RIGHT);
	oled_show_chinese(95,1,0,SHOW_PWD_RIGHT);

}
//密码错误
void oled_show_wrong(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_PWD_WRONG);
	oled_show_chinese(45,1,0,SHOW_PWD_WRONG);
	oled_show_chinese(70,1,0,SHOW_PWD_WRONG);
	oled_show_chinese(95,1,0,SHOW_PWD_WRONG);

}
//请输入旧密码
void oled_old_pwd_input(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_INPUT_OLD_PWD);
	oled_show_chinese(45,1,0,SHOW_INPUT_OLD_PWD);
	oled_show_chinese(70,1,0,SHOW_INPUT_OLD_PWD);
	oled_show_chinese(95,1,0,SHOW_INPUT_OLD_PWD);

}
//请输入新密码
void oled_new_pwd_input(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_INPUT_NEW_PWD);
	oled_show_chinese(45,1,0,SHOW_INPUT_NEW_PWD);
	oled_show_chinese(70,1,0,SHOW_INPUT_NEW_PWD);
	oled_show_chinese(95,1,0,SHOW_INPUT_NEW_PWD);

}
//密码修改成功
void oled_pwd_changed(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_PWD_CHANGED);
	oled_show_chinese(45,1,0,SHOW_PWD_CHANGED);
	oled_show_chinese(70,1,0,SHOW_PWD_CHANGED);
	oled_show_chinese(95,1,0,SHOW_PWD_CHANGED);

}
//请设定密码
void oled_set_pwd(void)
{
	oled_fill(0x00);
	oled_show_chinese(20,1,0,SHOW_SET_PWD);
	oled_show_chinese(45,1,0,SHOW_SET_PWD);
	oled_show_chinese(70,1,0,SHOW_SET_PWD);
	oled_show_chinese(95,1,0,SHOW_SET_PWD);

}

显示图片

cpp 复制代码
void oled_show_image(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t *bmp)
{
    uint8_t i, j;
    for(j = 0; j < height; j++)
    {
        oled_set_cursor(x, y + j);
        for(i = 0; i < width; i++)
            oled_write_data(bmp[width * j + i]);
    }
}

5. 闪存(w25q18)

W25Q18模块https://blog.csdn.net/2301_76153977/article/details/154997131?spm=1011.2415.3001.5331

cpp 复制代码
#include "w25q128.h"

SPI_HandleTypeDef spi_handle = {0};
void w25q128_spi_init(void)
{
    spi_handle.Instance = SPI1;
    spi_handle.Init.Mode = SPI_MODE_MASTER;
    spi_handle.Init.Direction = SPI_DIRECTION_2LINES;
    spi_handle.Init.DataSize = SPI_DATASIZE_8BIT;
    spi_handle.Init.CLKPolarity = SPI_POLARITY_LOW;              /* CPOL = 0 */
    spi_handle.Init.CLKPhase = SPI_PHASE_1EDGE;                  /* CPHA = 0 */
    spi_handle.Init.NSS = SPI_NSS_SOFT;
    spi_handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
    spi_handle.Init.FirstBit = SPI_FIRSTBIT_MSB;
    spi_handle.Init.TIMode = SPI_TIMODE_DISABLE;
    spi_handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    spi_handle.Init.CRCPolynomial = 7;
    HAL_SPI_Init(&spi_handle);
}

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
    if(hspi->Instance == SPI1)
    {
        GPIO_InitTypeDef gpio_initstruct;
        //打开时钟
        __HAL_RCC_GPIOA_CLK_ENABLE();                           // 使能GPIOB时钟
        __HAL_RCC_SPI1_CLK_ENABLE();
        
        //调用GPIO初始化函数
        gpio_initstruct.Pin = GPIO_PIN_4;          
        gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP;           
        gpio_initstruct.Pull = GPIO_PULLUP;                    
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;          
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_5 | GPIO_PIN_7;          
        gpio_initstruct.Mode = GPIO_MODE_AF_PP;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
        
        gpio_initstruct.Pin = GPIO_PIN_6;          
        gpio_initstruct.Mode = GPIO_MODE_INPUT;           
        HAL_GPIO_Init(GPIOA, &gpio_initstruct);
    }
}

uint8_t w25q128_spi_swap_byte(uint8_t data)
{
    uint8_t recv_data = 0;
    HAL_SPI_TransmitReceive(&spi_handle, &data, &recv_data, 1, 1000);
    return recv_data;
}

void w25q128_init(void)
{
    w25q128_spi_init();
}

uint16_t w25q128_read_id(void)
{
    uint16_t device_id = 0;
    W25Q128_CS(0);
    
    w25q128_spi_swap_byte(FLASH_ManufactDeviceID);
    w25q128_spi_swap_byte(0x00);
    w25q128_spi_swap_byte(0x00);
    w25q128_spi_swap_byte(0x00);
    device_id = w25q128_spi_swap_byte(FLASH_DummyByte) << 8;
    device_id |= w25q128_spi_swap_byte(FLASH_DummyByte);
    
    W25Q128_CS(1);
    return device_id;
}

void w25q128_writ_enable(void)
{
    W25Q128_CS(0);
    w25q128_spi_swap_byte(FLASH_WriteEnable);
    W25Q128_CS(1);
}

uint8_t w25q128_read_sr1(void)
{
    uint8_t recv_data = 0;
    
    W25Q128_CS(0);
    w25q128_spi_swap_byte(FLASH_ReadStatusReg1);
    recv_data = w25q128_spi_swap_byte(FLASH_DummyByte);
    W25Q128_CS(1);
    
    return recv_data;
}

void w25q128_wait_busy(void)
{
    while((w25q128_read_sr1() & 0x01) == 0x01);
}

void w25q128_send_address(uint32_t address)
{
    w25q128_spi_swap_byte(address >> 16);
    w25q128_spi_swap_byte(address >> 8);
    w25q128_spi_swap_byte(address);
}

void w25q128_read_data(uint32_t address, uint8_t *data, uint32_t size)
{
    uint32_t i = 0;
    W25Q128_CS(0);
    w25q128_spi_swap_byte(FLASH_ReadData);
    w25q128_send_address(address);
    
    for(i = 0; i < size; i++)
        data[i] = w25q128_spi_swap_byte(FLASH_DummyByte);
    W25Q128_CS(1);
}

void w25q128_write_page(uint32_t address, uint8_t *data, uint16_t size)
{
    uint16_t i = 0;
    w25q128_writ_enable();
    
    W25Q128_CS(0);
    w25q128_spi_swap_byte(FLASH_PageProgram);
    w25q128_send_address(address);
    
    for(i = 0; i < size; i++)
        w25q128_spi_swap_byte(data[i]);
    
    W25Q128_CS(1);
    //等待空闲
    w25q128_wait_busy();
}

void w25q128_erase_sector(uint32_t address)
{
    //写使能
    w25q128_writ_enable();
    //等待空闲
    w25q128_wait_busy();
    //拉低片选
    W25Q128_CS(0);
    //发送扇区擦除指令
    w25q128_spi_swap_byte(FLASH_SectorErase);
    //发送地址
    w25q128_send_address(address);
    //拉高片选
    W25Q128_CS(1);
    //等待空闲
    w25q128_wait_busy();
}

用来保存密码,在这里只写驱动,密码功能实现专门写在password部分

6. 密码(password)

把前面的模块整合到了一块,实现密码的增加、删除功能,实现键盘和密码协同合作的功能等

cpp 复制代码
#include "password.h"
#include "oled.h"
#include "w25q128.h"
#include "keyboard.h"
#include "string.h"
#include "stdio.h"
#include "beep.h"
#include "lock.h"
#include "delay.h"
#define PASSWORD_SIZE	10
uint8_t pwd_input[PASSWORD_SIZE] = {0};
uint8_t pwd_read[PASSWORD_SIZE] = {0};
uint8_t i = 0, key_value = 0, try_times = 0;

//初始化函数
void password_init(void)
{
	w25q128_init();
}

//清空输入缓存
void password_input_clear(void)
{
	memset(pwd_input, 0, PASSWORD_SIZE);
    i = 0;
}

//保存密码
void password_save(void)
{
    w25q128_erase_sector(0x000000);
	w25q128_write_page(0x000000, pwd_input, PASSWORD_SIZE);
    oled_show_changed();
}

//获取键盘输入
uint8_t password_get_input(void)
{
    password_input_clear();
    while(1)
    {
        key_value = keyboard_get_value();
        if(key_value == POUND_KEY)
        {
            printf("按下了#号键,input: %s\r\n", pwd_input);
            return POUND_KEY;
        }
        else if(key_value == STAR_KEY)
        {
            printf("按下了*号键\r\n");
            return STAR_KEY;
        }
        else if(key_value != 0)
        {
            printf("按下了:%c\r\n", key_value);
            oled_show_char(20 + i * 10, 4, key_value, 16);
            pwd_input[i++] = key_value;
        }
    }
}

//密码比对
uint8_t password_compare(void)
{
    uint8_t i = 0;
    w25q128_read_data(0x000000, pwd_read, PASSWORD_SIZE);
    
    if(strlen((char *)pwd_input) != strlen((char *)pwd_read))
        return FALSE;
    
    for(i = 0; i < strlen((char *)pwd_read); i++)
    {
        if(pwd_input[i] != pwd_read[i])
            return FALSE;
    }
    
    return TRUE;
}

//密码输入正确的操作
void password_input_right_action(void)
{
    oled_show_right();
    lock_on();
    beep_on();
    delay_ms(300);
    beep_off();
    delay_ms(1000);
    lock_off();
    try_times = 0;
}

//密码输入错误的操作
void password_input_wrong_action(void)
{
    oled_show_wrong();
    try_times++;
    if(try_times >= 3)
    {
        beep_on();
        delay_ms(1000);
        beep_off();
        try_times = 0;
    }
    delay_ms(1000);
}

//旧密码输入正确的操作
void password_old_right_action(void)
{
    oled_show_new();
    password_get_input();
    password_save();
    
    beep_on();
    delay_ms(300);
    beep_off();
    delay_ms(500);
}

//旧密码输入错误的操作
void password_old_wrong_action(void)
{
    oled_show_wrong();
    delay_ms(1000);
}

//检查密码是否存在
void password_check(void)
{
    w25q128_read_data(0x000000, pwd_read, PASSWORD_SIZE);
    printf("请出密码:%s\r\n", pwd_read);
    if(pwd_read[0] == '\0' || pwd_read[0] == 0xFF)
    {
        oled_show_set();
        password_get_input();
        password_save();
    }
}

7. 验证

在main函数补充输入密码时最后的#和*的功能

密码验证开锁:通过矩阵键盘输入密码,按#确认,密码正确则开锁,错误则报警;

密码修改:按*进入改密模式,先验证旧密码,正确后可设置新密码(改密逻辑封装在password_old_right_action中);

cpp 复制代码
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "beep.h"
#include "keyboard.h"
#include "lock.h"
#include "oled.h"
#include "w25q128.h"
#include "password.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    uart1_init(115200);
    beep_init();
    keyboard_init();
    lock_init();
    oled_init();
    password_init();
//    w25q128_init();
    printf("hello world!\r\n");
//    oled_show_input();
    password_check();

    uint8_t key_last = 0;
    while(1)
    { 
        oled_show_input();
        key_last = password_get_input();
        if(key_last == POUND_KEY)
        {
            if(password_compare() == TRUE)
                password_input_right_action();
            else
                password_input_wrong_action();
        }
        else if(key_last == STAR_KEY)
        {
            oled_show_old();
            password_get_input();
            if(password_compare() == TRUE)
                password_old_right_action();
            else
                password_old_wrong_action();
        }
    }
}

烧录,验证,就是文章最开始的视频中效果

相关推荐
金色光环7 小时前
裸机stm32移植双串口modbus从机(附源码)
stm32·单片机·嵌入式硬件
一路往蓝-Anbo7 小时前
C语言从句柄到对象 (五) —— 虚函数表 (V-Table) 与 RAM 的救赎
c语言·开发语言·stm32·单片机·物联网
古译汉书7 小时前
keil编译错误:Error: Flash Download failed
开发语言·数据结构·stm32·单片机·嵌入式硬件
不吃鱼的羊8 小时前
达芬奇PWM模块
单片机·嵌入式硬件·fpga开发
creeper_boom8 小时前
32单片机软件程序启动流程
单片机
d111111111d9 小时前
使用STM32 HAL库配置ADC单次转换模式详解
笔记·stm32·单片机·嵌入式硬件·学习
boneStudent9 小时前
STM32智能温湿度监测系统完整代码
stm32·单片机·嵌入式硬件
炸膛坦客9 小时前
Cortex-M3-STM32F1 开发:(三十八)DMA详细介绍(2):软件触发、数据传输流向、宏观流程图
stm32·单片机·嵌入式硬件·dma
polarislove02149 小时前
9.3 输出比较-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
chen_mangoo9 小时前
Android10低电量无法打开相机
android·linux·驱动开发·嵌入式硬件