语音配置
语音播放器路径: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卡波特率不同,要分开用两个串口
主程序 (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.c 的 lock() 函数中,通过 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(); // 执行任务调度
}
}
初始化流程要点:
-
硬件抽象层初始化: 时钟、中断、外设
-
视觉反馈: 启动动画增强用户体验
-
无限循环调度: 所有功能由调度器统一管理
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; // 重置索引
}
功能流程图
密码解锁流程
用户操作 系统响应
│
├─ 输入数字 → 显示星号 + 按键音效
│
├─ 按下确认键 → 密码验证
│ ├─ 正确 → 解锁 + 语音提示 + 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
│
└─ 错误 → 错误计数












