【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();
        }
    }
}

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

相关推荐
SundayBear1 小时前
零基础入门MQTT协议
c语言·单片机
嗯嗯=2 小时前
STM32单片机学习篇9
stm32·单片机·学习
小范馆6 小时前
ESP各模组的引脚图-小智接线图
stm32
松涛和鸣6 小时前
DAY63 IMX6ULL ADC Driver Development
linux·运维·arm开发·单片机·嵌入式硬件·ubuntu
想放学的刺客9 小时前
单片机嵌入式试题(第23期)嵌入式系统电源管理策略设计、嵌入式系统通信协议栈实现要点两个全新主题。
c语言·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆10 小时前
【Linux 驱动开发】五. 设备树
linux·arm开发·驱动开发·stm32·嵌入式硬件·mcu·硬件工程
YouEmbedded10 小时前
解码内部集成电路(IIC)与OLED屏
stm32·0.96寸oled·硬件iic·软件模拟iic·图片取模·汉字取模
jghhh0111 小时前
基于上海钜泉科技HT7017单相计量芯片的参考例程实现
科技·单片机·嵌入式硬件
恶魔泡泡糖11 小时前
51单片机外部中断
c语言·单片机·嵌入式硬件·51单片机
意法半导体STM3212 小时前
【官方原创】如何基于DevelopPackage开启安全启动(MP15x) LAT6036
javascript·stm32·单片机·嵌入式硬件·mcu·安全·stm32开发