第一次接触STM32密码锁项目时,我完全是个嵌入式小白。手头的开发板是STM32F103ZET6,这块板子性价比高,资源丰富,特别适合入门级项目。搭配的OLED显示屏我选了正点原子的0.96寸款,分辨率128x64,SPI接口通信,显示效果清晰而且接线简单。
矩阵键盘用的是4x4薄膜按键模块,这种键盘防尘防水,价格不到10块钱。电磁锁模块要注意驱动电压,我选的12V常闭型,搭配一个5V继电器控制。这里有个坑要注意:STM32的GPIO输出是3.3V电平,直接驱动5V继电器可能不稳定。我的解决方案是用S8050三极管搭建放大电路,成本不到1块钱。
提示:所有模块建议先用杜邦线连接测试,确认功能正常后再考虑焊接固定。
先给开发板接上ST-Link下载器,我用的是SWD模式,只需要连接四根线:
OLED的接线要注意方向,我遇到过反接烧坏模块的情况:
4x4矩阵键盘有8个引脚,我这样分配GPIO:
这里有个优化点:同一组GPIO口的连续引脚可以简化编程。PF0-PF7正好是一个完整的8位端口,扫描时可以直接操作ODR和IDR寄存器。
继电器控制电路我这样设计:
记得在继电器线圈两端并联续流二极管,我用的是1N4148,防止反电动势损坏三极管。
新建工程时芯片选STM32F103ZE,我用的是标准外设库3.5版本。关键配置:
调试配置里,我习惯把Reset and Run勾选上,这样下载后自动运行程序,不用手动复位。
除了常见的ST-Link驱动,有几个驱动容易被忽略:
建议在设备管理器里确认所有设备都正常识别,没有黄色感叹号。
main.c的主体结构我这样组织:
c复制int main(void)
{
// 硬件初始化
delay_init();
NVIC_Configuration();
OLED_Init();
Matrix_Key_Init();
Flash_Init();
// 显示欢迎界面
Show_Welcome();
// 主循环
while(1) {
key = Matrix_Key_Scan();
Process_Input(key);
Change_Password(key);
}
}
原始代码的按键扫描有改进空间,我优化后的版本:
c复制uint8_t Matrix_Key_Scan(void)
{
static uint8_t key_state = 0;
uint8_t key_val = KEY_NONE;
// 扫描行线
for(int i=0; i<4; i++) {
GPIO_ResetBits(ROW_PORT, ROW_PINS[i]);
delay_us(10); // 消抖
// 检测列线
for(int j=0; j<4; j++) {
if(GPIO_ReadInputDataBit(COL_PORT, COL_PINS[j]) == 0) {
if(key_state == 0) {
key_val = KEY_MAP[i][j];
key_state = 1;
}
break;
}
}
GPIO_SetBits(ROW_PORT, ROW_PINS[i]);
}
if(GPIO_ReadInputDataBit(COL_PORT, COL_PINS[0]) &&
GPIO_ReadInputDataBit(COL_PORT, COL_PINS[1]) &&
GPIO_ReadInputDataBit(COL_PORT, COL_PINS[2]) &&
GPIO_ReadInputDataBit(COL_PORT, COL_PINS[3])) {
key_state = 0;
}
return key_val;
}
掉电保存我用的是内部Flash模拟EEPROM,关键操作:
c复制FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
c复制FLASH_ErasePage(FLASH_SAVE_ADDR);
c复制for(int i=0; i<PWD_LEN; i++) {
FLASH_ProgramHalfWord(addr, pwd[i]);
addr += 2;
}
c复制FLASH_Lock();
遇到花屏问题时,按这个顺序检查:
我的调试经验:
遇到过几种典型情况:
在密码验证部分添加计数器:
c复制static uint8_t error_count = 0;
if(密码错误) {
error_count++;
if(error_count >= 3) {
锁定系统();
}
}
可以用PWM驱动蜂鸣器:
c复制void Beep(uint16_t freq, uint16_t duration)
{
TIM_OCInitTypeDef oc;
TIM_TimeBaseInitTypeDef tb;
// 配置定时器
tb.TIM_Period = 1000000/freq - 1;
tb.TIM_Prescaler = 72 - 1;
TIM_TimeBaseInit(TIM3, &tb);
// 配置PWM
oc.TIM_OCMode = TIM_OCMode_PWM1;
oc.TIM_OutputState = TIM_OutputState_Enable;
oc.TIM_Pulse = tb.TIM_Period/2;
TIM_OC2Init(TIM3, &oc);
TIM_Cmd(TIM3, ENABLE);
delay_ms(duration);
TIM_Cmd(TIM3, DISABLE);
}
可以设计管理员密码和用户密码:
c复制#define ADMIN_PWD 123456
#define USER_PWD 654321
void Check_Password(uint8_t *input)
{
if(匹配管理员密码) {
进入管理模式();
}
else if(匹配用户密码) {
正常开锁();
}
else {
提示错误();
}
}
几个省电技巧:
建议的文件结构:
虽然是个练习项目,但可以加入:
最开始做这个项目时,我连GPIO初始化都写不对。调试矩阵键盘花了两天时间,后来发现是上拉电阻没配置好。OLED显示乱码的问题更棘手,最终发现是SPI时钟相位设反了。
最让我头疼的是Flash写入问题,总是写不进去。后来在论坛看到要先擦除整页,而且地址必须对齐。解决这个问题后,我又尝试优化写入速度,发现批量写入比单字节快很多。
电磁锁的驱动电路烧过两次三极管,第一次是因为没加续流二极管,第二次是负载电流太大。后来改用MOSFET驱动就稳定多了。这些小教训让我明白,硬件设计必须考虑余量和保护。