智能门锁
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 资源。核心逻辑:
-
行引脚(PB0/PB1/PB2/PB10)配置为下降沿中断 + 上拉输入,作为 "按键触发检测端";
-
列引脚(PB11/PB12/PB13)配置为下拉输入,作为 "按键位置识别端";
-
按下按键时,对应行引脚电平下降触发中断,在中断回调中检测列电平,通过 "行 + 列" 编码确定按键值;
-
封装
keyboard_get_value接口,返回标准化按键字符(如 '1'/'0'/'*' 等)。 -
矩阵键盘硬件映射(先明确接线)
代码对应的 4 行 3 列键盘硬件接线:

- 键盘初始化(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);
}
- 中断服务函数(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。


- 中断回调函数(核心按键识别)
当按键按下时,行线产生下降沿中断👈初始化中配置了 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
- 按键值读取接口(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();
}
}
}
烧录,验证,就是文章最开始的视频中效果