智能门锁:第四讲

语音配置

语音播放器路径:C:\Users\21906\Desktop\01may嵌赛智能门锁江西省单片机仿真\嵌入式大赛\嵌赛沁恒赛道\项目\乡村智居系统\模块资料\百为电子语音模块\深圳百为电子最新资料_2021\2-语音模块\BY8301-16P

语音合成路径:C:\Users\21906\Desktop\01may嵌赛智能门锁江西省单片机仿真\嵌入式大赛\嵌赛沁恒赛道\项目\乡村智居系统\模块资料\百为电子语音模块\深圳百为电子最新资料_2021\9-合成软件\语音合成工具1

新建文件夹后,点击浏览添加路径,设置文件名后,在文本框中输入文字,即可在新建文件夹中生成音频

通过串口数据包来控制语音

在Driver文件夹中新建串口的底层代码,将原先的串口3移植进来

USART_ClearFlag(USART2,USART_FLAG_TC);:特别用于清空串口3的发送标志位,不然发送数据包的第一个数据会丢失

在主函数中完成相应的移植配置

在Driver文件夹中创建audio.c和audio.h文件,audio.c特别要引用串口头文件

void audio_init():用于引用串口来收发数据

void audio_play(u8 num):将音频播放功能独立封装起来,直接可在主函数调用

u8 string[]={0x7e,0x05,0x41,0x00,num,0x05^0x41^0x00^num,0xef};:依据上述协议命令来写

while( USART_GetFlagStatus(USART3, USART_FLAG_TC)==0 );:串口发送频率和系统频率不同,当标志位为0时,系统等待串口发送完数据再进行下一步操作

配置audio.h文件

在主函数调用头文件,并调用初始化函数与音频函数,每次上电,播放语音

RFID卡

手册路径:C:\Users\21906\Desktop\01may嵌赛智能门锁江西省单片机仿真\嵌入式大赛\嵌赛沁恒赛道\项目\乡村智居系统\模块资料\RFID卡模块\RFID读写器配套资料与视频讲解B V1.3.2\RFID读写器二次开发资料

串口发送数据写入协议

语音模块和RFID卡波特率不同,要分开用两个串口

:page_facing_up: 主程序 (main.c)

全局变量说明

头文件引用区

 #include "debug.h"  //必要系统头文件
 #include "lcd.h"    //屏幕头文件
 #include "pic.h"    //图片头文件
 #include "timer.h"  //定时器头文件
 #include "key.h"    //矩阵按键头文件
 #include "uart.h"   //串口头文件
 #include "audio.h"  //语音模块头文件
 #include "string.h" //数组函数相关头文件

系统时间与任务调度

 unsigned long int uwtick;  // 系统滴答计数器 (1ms 中断累加)
按键相关变量
 u8 key_val;       // 当前按键值 (实时读取)
 u8 key_old;       // 上一次按键值 (用于边缘检测)
 u8 key_down;      // 按键下降沿检测 (按下瞬间)
 u8 key_up;        // 按键上升沿检测 (松开瞬间)
 u8 key_temp[7];   // 按键输入缓冲区 (存储 6 位密码 + 1 位防溢出)
 u8 key_index;     // 当前已输入的按键数量
 u8 key_index_old; // 上一次的按键数量 (用于触发界面更新)
密码与权限管理
 u8 password[6] = {1,2,3,4,5,6};       // 用户密码 (默认: 123456)
 u8 password_cmd[6] = {2,7,7,5,1,6};   // 管理员密码 (默认: 277516)
 u8 password_error;                    // 密码错误次数计数器
 u16 time15s = 15;                     // 密码错误锁定倒计时 (15 秒)
 u16 time1000ms;                       // 1 秒定时计数器
门锁状态控制
 u8 lock_flag = 1;     // 门锁状态: 1=锁定, 0=解锁
 u16 time5000ms;       // 自动上锁延时计数 (5 秒)
显示与模式控制
 u8 show_flag;         // 当前显示内容标志
 u8 show_flag_old;     // 上一次显示内容 (用于触发界面更新)
 u8 mode;              // 系统工作模式 (0-4)
RFID 刷卡相关
 u8 rfid_index;                    // RFID 数据接收状态机索引
 u8 rfid_temp[4];                  // RFID 临时数据缓冲区 (存储 4 字节卡号)
 u8 rfid[4][4];                    // RFID 卡片存储数组 (最多 4 张卡)
 u8 rfid_password_index;           // 已录入卡片数量

核心函数详解

1. 按键处理函数 key_proc()
 void key_proc()
 {
     /*=== 按键扫描与边缘检测 ===*/
     key_val = key_read();                         // 读取当前按键值
     key_down = key_val & (key_val ^ key_old);     // 检测按下瞬间 (0→1 跳变)
     key_up = ~key_val & (key_val ^ key_old);      // 检测松开瞬间 (1→0 跳变)
     key_old = key_val;                            // 更新历史值
 ​
     if(password_error == 3) return;               // 密码错误 3 次时禁用按键输入
 ​
     /*=== 按键音效反馈 ===*/
     if(key_down) audio_play(1);                   // 按下任意键播放按键音效
 ​
     /*=== 按键功能分发 ===*/
     switch(key_down)
     {
         // 数字键 1-9, 0 的处理
         case 1:  case 2:  case 3:
         case 5:  case 6:  case 7:
         case 9:  case 10: case 11:
         case 14:
             key_temp[key_index] = 对应的数字值;  // 存储到缓冲区
             key_index++;                           // 索引递增
             break;
 ​
         // 功能键 4: 进入修改密码模式
         case 4:
             if(mode == 0) {
                 mode = 1;                          // 切换到模式 1 (等待管理员验证)
                 audio_play(6);                     // 播放提示音
                 password_error = 0;                // 重置错误计数
                 key_clear();                       // 清空输入缓冲
             }
             break;
 ​
         // 功能键 12: 进入录入卡片模式
         case 12:
             if(mode == 0) {
                 mode = 3;                          // 切换到模式 3 (等待管理员验证)
                 audio_play(13);                    // 播放提示音
                 key_clear();
             }
             break;
 ​
         // 功能键 13: 清除输入
         case 13:
             key_clear();                           // 清空缓冲区和索引
             break;
 ​
         // 功能键 15: 退格删除
         case 15:
             if(key_index > 0) {
                 key_index--;                       // 索引回退
                 key_temp[key_index] = 10;          // 清除该位 (10 表示空)
             }
             break;
 ​
         // 功能键 16: 确认键 (核心逻辑)
         case 16:
             switch(mode)
             {
                 case 0:  // 主页模式 - 验证用户密码
                     if(string_chek(key_temp, password, 6)) {  // 密码正确
                         lock_flag = 0;             // 解锁
                         show_flag = 1;             // 显示"门已打开"
                         audio_play(3);             // 播放成功音效
                         key_clear();
                         password_error = 0;        // 重置错误计数
                     }
                     else {                         // 密码错误
                         audio_play(4);             // 播放错误音效
                         key_clear();
                         if(++password_error == 3) {  // 累计 3 次错误
                             audio_play(5);         // 播放锁定提示音
                             show_flag = 2;         // 显示锁定倒计时界面
                         }
                     }
                     break;
 ​
                 case 1:  // 修改密码模式 - 验证管理员密码
                     if(string_chek(key_temp, password_cmd, 6)) {
                         mode = 2;                  // 进入密码修改状态
                         audio_play(7);
                         key_clear();
                     }
                     else {
                         audio_play(9);             // 播放管理员密码错误提示
                         if(++password_error == 3) {
                             audio_play(10);
                             show_flag = 2;
                         }
                     }
                     break;
 ​
                 case 2:  // 修改密码模式 - 写入新密码
                     string_copy(key_temp, password, 6);  // 将输入内容复制到密码数组
                     audio_play(8);                 // 播放设置成功提示
                     mode = 0;                      // 返回主页
                     key_clear();
                     break;
 ​
                 case 3:  // 录入卡片模式 - 验证管理员密码
                     if(string_chek(key_temp, password_cmd, 6)) {
                         mode = 4;                  // 进入卡片录入状态
                         audio_play(14);
                         key_clear();
                     }
                     else {
                         audio_play(9);
                         if(++password_error == 3) {
                             audio_play(10);
                             show_flag = 2;
                         }
                     }
                     break;
             }
             break;
     }
 }

核心逻辑要点:

  • 采用边缘检测机制,防止按键长按重复触发

  • 使用状态机模式处理不同工作模式下的确认键逻辑

  • 实现错误计数与锁定机制,3 次密码错误后锁定 15 秒

  • 缓冲区索引动态管理,支持退格删除功能


2. 门锁控制函数 lock_proc()
 void lock_proc()
 {
     lock(lock_flag);  // 根据全局标志控制舵机状态
 }

说明: 这是一个简单的执行层函数,实际控制逻辑封装在 timer.clock() 函数中,通过 PWM 调节舵机角度实现开锁/上锁。


3. LCD 显示处理函数 lcd_proc()
 void lcd_proc()
 {
     /*=== 密码输入星号显示 ===*/
     if(key_index != key_index_old)   // 检测到输入长度变化
     {
         u8 i = key_index;
         LCD_Fill(16, 45, 112, 66, YELLOW);  // 清空输入区域背景 (黄色)
 ​
         if(key_index == 7) key_index = 6;   // 防止溢出 (最多 6 位)
 ​
         while(i--) {  // 循环显示星号
             if(i < 6)
                 LCD_ShowChar(20 + 16*i, 45, '*', RED, YELLOW, 16, 0);
         }
 ​
         key_index_old = key_index;  // 更新比较基准
     }
 ​
     /*=== 顶部提示信息显示 ===*/
     if(show_flag != show_flag_old)
     {
         LCD_Fill(0, 0, 128, 32, WHITE);  // 清空顶部区域 (白色)
         show_flag_old = show_flag;
     }
 ​
     switch(show_flag)
     {
         case 0:  // 正常待机状态
             lcd_show_chinese(0, 0, "请输入密码后按确认键", RED, WHITE, 16, 0);
             break;
 ​
         case 1:  // 解锁成功状态
             lcd_show_chinese(0, 0, "门已打开,欢迎回家", RED, WHITE, 16, 0);
             break;
 ​
         case 2:  // 密码错误锁定状态
             LCD_ShowIntNum(50, 16, time15s, 2, RED, WHITE, 16);  // 显示倒计时
             break;
     }
 }

显示逻辑要点:

  • 增量更新机制: 只在数据变化时重绘,避免闪烁

  • 分区显示: 顶部提示区 + 中部输入区 + 底部功能区

  • 星号遮挡: 密码输入时显示 * 保护隐私


4. 系统定时器中断 TIM3_IRQHandler()
 void TIM3_IRQHandler(void)  // 1ms 周期中断
 {
     if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
     {
         uwtick++;  // 系统滴答计数器累加
 ​
         /*=== 自动上锁计时 ===*/
         if(lock_flag == 0)  // 门锁处于解锁状态
         {
             if(++time5000ms == 5000)  // 计时 5 秒
             {
                 time5000ms = 0;
                 lock_flag = 1;         // 自动上锁
                 show_flag = 0;         // 恢复主界面
                 audio_play(2);         // 播放上锁提示音
             }
         }
 ​
         /*=== 错误锁定倒计时 ===*/
         if(password_error == 3)  // 密码错误 3 次
         {
             if(++time1000ms == 1000)  // 每秒递减
             {
                 time1000ms = 0;
                 if(--time15s == 0)  // 倒计时归零
                 {
                     time15s = 15;           // 重置倒计时
                     password_error = 0;     // 解除锁定
                     show_flag = 0;          // 恢复主界面
                 }
             }
         }
     }
     TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  // 清除中断标志
 }

时序控制要点:

  • 1ms 基准时钟: 所有定时任务的时间基准

  • 非阻塞计时: 通过计数器实现,不影响主循环

  • 自动安全机制: 5 秒自动上锁 + 15 秒错误锁定


5. 任务调度器
 /*=== 任务结构体定义 ===*/
 typedef struct
 {
     void (*task_func)(void);        // 任务函数指针
     unsigned long int rate_ms;      // 执行周期 (毫秒)
     unsigned long int last_run;     // 上次执行时间戳
 } task_t;
 ​
 /*=== 任务列表配置 ===*/
 task_t scheduler_task[] = {
     {lcd_proc,  100, 0},  // LCD 刷新任务, 100ms 周期
     {key_proc,   10, 0},  // 按键扫描任务, 10ms 周期
     {lock_proc,  30, 0},  // 门锁控制任务, 30ms 周期
 };
 ​
 /*=== 调度器初始化 ===*/
 void scheduler_init()
 {
     task_num = sizeof(scheduler_task) / sizeof(task_t);  // 计算任务数量
 }
 ​
 /*=== 调度器运行 ===*/
 void scheduler_run()
 {
     unsigned char i;
     for(i = 0; i < task_num; i++)
     {
         unsigned long int now_time = uwtick;  // 获取当前系统时间
 ​
         // 检查是否到达执行周期
         if(now_time > (scheduler_task[i].last_run + scheduler_task[i].rate_ms))
         {
             scheduler_task[i].last_run = now_time;   // 记录执行时间
             scheduler_task[i].task_func();           // 执行任务函数
         }
     }
 }

调度器设计优势:

  • 时间片轮询: 无需操作系统即可实现多任务

  • 精确周期控制: 每个任务可独立配置执行频率

  • 低 CPU 占用: 基于时间戳检测,避免空转


6. RFID 刷卡中断 USART2_IRQHandler()
 void USART2_IRQHandler(void)
 {
     u8 temp;
     if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
     {
         temp = USART_ReceiveData(USART2);  // 接收一个字节
 ​
         /*=== RFID 协议解析状态机 ===*/
         switch(rfid_index)
         {
             case 0:  if(temp == 0x04) rfid_index++; break;  // 命令类型:0x04
             case 1:  if(temp == 0x0c) rfid_index++; else rfid_index=0; break;  // 包长度:0x0c
             case 2:  if(temp == 0x02) rfid_index++; else rfid_index=0; break;  // 工作模式:0x02
             case 3:  if(temp == 0x30) rfid_index++; else rfid_index=0; break;  // 地址:0x30
             case 4:  if(temp == 0x00) rfid_index++; else rfid_index=0; break;  // 状态:0x00
             case 5:  if(temp == 0x04) rfid_index++; else rfid_index=0; break;  // 卡的类型
             case 6:  if(temp == 0x00) rfid_index++; else rfid_index=0; break;  // 卡的类型
 ​
             // 接收卡号 4 字节
             case 7:  rfid_temp[0] = temp; rfid_index++; break;
             case 8:  rfid_temp[1] = temp; rfid_index++; break;
             case 9:  rfid_temp[2] = temp; rfid_index++; break;
             case 10:
                 rfid_temp[3] = temp;
                 rfid_index = 0;  // 状态机复位
 ​
                 /*=== 模式判断 ===*/
                 if(mode == 0)  // 主页模式 - 验证刷卡
                 {
                     if(rfid_chek()) {  // 卡号匹配
                         audio_play(11);  // 播放刷卡成功提示
                         lock_flag = 0;   // 解锁
                     }
                     else {
                         audio_play(12);  // 播放刷卡失败提示
                     }
                 }
 ​
                 if(mode == 4)  // 录入模式 - 保存新卡
                 {
                     string_copy(rfid_temp, rfid[rfid_password_index], 4);  // 存储卡号
                     audio_play(15);         // 播放录入成功提示
                     mode = 0;               // 返回主页
                     rfid_password_index++;  // 卡片数量+1
                 }
                 break;
         }
     }
     USART_ClearITPendingBit(USART2, USART_IT_RXNE);
 }

RFID 协议说明:

  • 帧格式: 04 0C 02 30 00 卡的类型2字节[04] [00] 卡号4字节[][][][]

  • 状态机解析: 严格按序验证每个字节,容错性强

  • 双模式处理: 验证模式 + 录入模式


7. 主函数 main()
 int main(void)
 {
     /*=== 系统初始化 ===*/
     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 中断优先级分组
     SystemCoreClockUpdate();                         // 更新系统时钟
     USART_Printf_Init(115200);                       // 调试串口初始化
     Delay_Init();                                    // 延时函数初始化
 ​
     /*=== 硬件模块初始化 ===*/
     TIM2_PWM_Init();   // 舵机 PWM 初始化
     lock(1);           // 默认上锁状态
     LCD_Init();        // LCD 屏幕初始化
 ​
     /*=== 启动动画 ===*/
     LCD_Fill(0, 0, 127, 127, WHITE);                 // 清屏
     lcd_show_chinese(20, 50, "系统启动", RED, WHITE, 16, 0);
 ​
     // 进度条动画
     unsigned char i = 0;
     while(i < 128) {
         LCD_DrawLine(i, 100, i, 128, RED);
         Delay_Ms(10);
         i++;
     }
 ​
     lcd_show_chinese(20, 50, "启动成功", RED, WHITE, 16, 0);
     Delay_Ms(500);
 ​
     /*=== 显示 LOGO 和主界面 ===*/
     LCD_ShowPicture(0, 0, 128, 128, gImage_1);       // 显示 LOGO
     Delay_Ms(1000);
     LCD_ShowPicture(0, 0, 128, 128, gImage_2);       // 显示主页背景
     lcd_show_chinese(0, 0, "请输入密码后按确认键", RED, WHITE, 16, 0);
     LCD_Fill(16, 45, 112, 66, YELLOW);               // 绘制输入框背景
 ​
     /*=== 功能模块启动 ===*/
     key_init();                 // 矩阵键盘初始化
     Tim3_Init(1000, 96-1);      // 系统定时器初始化 (1ms 周期)
     scheduler_init();           // 任务调度器初始化
     audio_init();               // 语音模块初始化
     audio_play(2);              // 播放欢迎提示音
     Usart2_Init();              // RFID 串口初始化
 ​
     /*=== 主循环 ===*/
     while(1)
     {
         scheduler_run();  // 执行任务调度
     }
 }

初始化流程要点:

  1. 硬件抽象层初始化: 时钟、中断、外设

  2. 视觉反馈: 启动动画增强用户体验

  3. 无限循环调度: 所有功能由调度器统一管理


8. 辅助工具函数(自己写的)
 /*=== 字符串比较函数 ===*/
 u8 string_chek(u8* string1, u8* string2, u8 len)
 {
     while(len--) {
         if(string1[len] != string2[len]) return 0;  // 任意位不匹配返回失败
     }
     return 1;  // 全部匹配返回成功
 }
 ​
 /*=== 字符串复制函数 ===*/
 void string_copy(u8* string1, u8* string2, u8 len)
 {
     u8 i;
     for(i = 0; i < len; i++) {
         string2[i] = string1[i];  // 逐字节复制
     }
 }
 ​
 /*=== RFID 卡号验证函数 ===*/
 u8 rfid_chek()
 {
     u8 i;
     for(i = 0; i < rfid_password_index; i++) {
         if(string_chek(rfid_temp, rfid[i], 4)) return 1;  // 匹配成功
     }
     return 0;  // 所有已录入卡片都不匹配
 }
 ​
 /*=== 按键缓冲区清除函数 ===*/
 void key_clear()
 {
     memset(key_temp, 10, 6);  // 填充无效值 10
     key_index = 0;            // 重置索引
 }

:counterclockwise_arrows_button: 功能流程图

密码解锁流程

 用户操作              系统响应
    │
    ├─ 输入数字      → 显示星号 + 按键音效
    │
    ├─ 按下确认键    → 密码验证
    │                  ├─ 正确 → 解锁 + 语音提示 + 5秒后自动上锁
    │                  └─ 错误 → 错误计数+1
    │                            ├─ <3次 → 清空输入 + 重新输入
    │                            └─ =3次 → 锁定15秒 + 倒计时显示
    │
    └─ 刷卡         → RFID验证
                       ├─ 匹配 → 解锁 + 语音提示
                       └─ 不匹配 → 语音提示 "刷卡失败"

修改密码流程

 1. 主页按下功能键4 → mode=1, 提示"请输入管理员密码"
 2. 输入管理员密码 → 验证
    ├─ 正确 → mode=2, 提示"请输入新密码"
    │         └─ 输入6位新密码 → 保存到password数组 → mode=0
    │
    └─ 错误 → 错误计数
              └─ 3次后锁定15秒

录入卡片流程

 1. 主页按下功能键12 → mode=3, 提示"请输入管理员密码"
 2. 输入管理员密码 → 验证
    ├─ 正确 → mode=4, 提示"请刷卡"
    │         └─ 刷卡 → 保存到rfid数组 → mode=0
    │
    └─ 错误 → 错误计数
2 个赞

好细啊,我一般都是用Gemini3先预习一遍再听课,效果挺好的