本文详细介绍基于STM32的刷卡指纹智能门禁系统。
获取资料/指导答疑/技术交流/选题/帮助,请点链接:
https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt
1 系统功能
1.1 功能概述
本系统由STM32硬件端(下位机)和QT管理平台(上位机)构成,两者通过WiFi无线连接,采用TCP协议通信。
**硬件端:**STM32及外设模块(RC522刷卡、AS608指纹、OLED显示、ESP8266 WiFi、舵机开门、蜂鸣器、LED);
**管理平台:**用QT实现并在电脑运行。
**主要功能:**系统登录、建立连接及通信、录入用户、查看用户、删除用户、刷卡开门、指纹开门、查看开门记录、管理员开门、退出登录等。
系统的管理员界面及STM32硬件端实物图如下:
1.2 主要功能
1.2.1 系统登陆
运行管理平台需要先登录,登录界面如下:

登录后进入管理员系统,界面如下:

1.2.2 连接通信
上位机与下位机通信需要先建立连接,首先电脑要连接STM32硬件端WiFi模块发射的WiFi(wifi: hello-esp8266,密码:6个8),然后在管理员界面输入IP并点击连接,连接成功后在OLED屏上会显示在线状态,如下:

1.2.3 添加用户
在管理员界面点击"添加用户",再输入用户信息,点击确定,然录入新卡,接着采集指纹录入(需采集2次并且一致),以上步骤无误后提示录入成功,如下:



1.2.4 查看用户
在管理员界面点击"用户列表"可查看用户,如下:

1.2.5 删除用户
在用户列表上选中某个用户,再点击"删除用户",即可将该用户删除,如下:

1.2.6 刷卡开门
用已录入的卡去刷卡,能够开门并在OLED屏显示开门成功,如下:

1.2.7 指纹开门
用已录入的指纹去刷指纹,能够开门并在OLED屏显示开门成功,如下:

1.2.8 管理员开门
在管理员界面点击"开门",可开门并在OLED屏显示开门成功,如下:

1.2.9 查看开门记录
在管理员界面点击"历史记录",可查看开门记录,如下:

1.2.10 退出登陆
在管理员界面点击"退出",可退出管理员系统,回到登陆界面,如下:

2 系统硬件
2.1 原理图

2.2 PCB

3 软件实现
3.1 STM32软件
3.1.1 主函数main
在main函数中,主要完成系统及外设模块的初始化,还有一个重要的模块:time service的初始化以及注册3个不同间隔执行的函数,分别是0间隔(相当于while(1))、100ms间隔、1000ms的间隔执行,主体程序均可在这3个不同间隔执行的函数中运行。
Main程序如下:
cpp
int main(void)
{
// 配置系统时钟72M,使用外部晶振
system_clock_config();
delay_init();
// 调试打印串口初始化
usart_init(115200);
printf("\n hello stm32.\r\n");
/* 硬件模块驱动初始化 */
led_init(); //LED初始化
beep_init(); //蜂鸣器初始化
oled_init(); // OLED初始化
rc522_init(); // RC522 rfid模块初始化
// 指纹模块AS608初始化:usart3用于AS608
usart3_init(AS608_UART_BAUDRATE, as608_data_recv);
as608_init(usart3_send, as608_finger_event_cb);
// wifi通信初始化
wifi_proto_init();
/* time service需要timer提供时钟 */
timsrv_init();
TIM2_init(72-1, 1000-1, timer_timeout_cb); // 1ms 定时
TIM2_start();
// 注册任务,执行间隔以ms为单位
timsrv_register_task(0, task_while);
timsrv_register_task(100, task_100ms);
timsrv_register_task(1000, task_1000ms);
//舵机初始化:PWM3为舵机提供pwm
pwm3_init(72-1, 20000-1); //舵机要求:频率1MHz,周期20ms
pwm3_start();
servo_init(20000, pwm3_set_pulse);
// 显示主界面
ui_show_destop();
ui_show_online(false);
memset(&g_sys_info, 0, sizeof(system_info_t));
g_sys_info.work_mode = WORK_MODE_NORMAL;
while(1)
{
// 主任务:根据时间间隔执行注册的任务
timsrv_tasks_running();
}
}
3.1.2 事件处理process
事务处理主要是上述time service注册的3个不同间隔执行的函数,如下:
cpp
/* 0间隔执行,相当于while(1)执行 */
void task_while(void)
{
//wifi接收数据处理
wifi_data_handle();
// 服务器协议处理
server_proto_handle();
}
/* 每间隔100ms执行一次 */
void task_100ms(void)
{
static uint8_t card_wait = 0;
uint8_t card_id[8];
int ret, i;
//扫描读卡,每次读到卡间隔2S
if(card_wait == 0)
{
ret = rc522_read_card(card_id);
if(ret == 0)
{
beep_on();
timsrv_set_delay_work(100, beep_off);
proto_0x11_sendCardnum(card_id);
card_wait = 1;
printf("get card: ");
for(i=0; i<8; i++)
printf("%02X ", card_id[i]);
printf("\n");
}
}
else
{
if(card_wait ++ >= 20)
card_wait = 0;
}
//指纹模块运行处理
as608_run_process();
}
/* 每间隔1000ms执行一次 */
void task_1000ms(void)
{
static int num = 0;
printf("%s: %d\r\n", __FUNCTION__, num);
num ++;
}
3.1.3 外设事件处理
外设像WiFi、指纹模块AS608等有事件回调的处理,如下:
cpp
//wifi模块事件回调处理函数
int wifi_event_cb(wifi_event_e event, int param)
{
printf("wifi event: %d, param: %d\n", event, param);
switch(event)
{
case WIFI_EVE_TCP_CONNECT: //TCP连接
g_sys_info.online = true;
led_on();
ui_show_online(true);
break;
case WIFI_EVE_TCP_DISCONNECT: //TCP断开
g_sys_info.online = false;
led_off();
ui_show_online(false);
break;
default:
break;
}
return 0;
}
//指纹模块事件回调处理函数
int as608_finger_event_cb(as608_event_e event, int param)
{
printf("as608 event: %d, param: %d\n", event, param);
switch(event)
{
case AS608_EVE_GET_FINGER: //检测到指纹按下
beep_on();
timsrv_set_delay_work(100, beep_off);
break;
case AS608_EVE_SEARCH_FINGER: //识别到识别
proto_0x14_recognFingerId(param);
break;
case AS608_EVE_ADD_WAIT_FINGER: //添加指纹:等待指纹
break;
case AS608_EVE_ADD_1ST_FINGER: //添加指纹:采集第一个指纹
beep_on();
timsrv_set_delay_work(100, beep_off);
proto_0x13_addFingerResult(1);
break;
case AS608_EVE_ADD_2ND_FINGER: //添加指纹:采集第二个指纹
beep_on();
timsrv_set_delay_work(100, beep_off);
proto_0x13_addFingerResult(2);
break;
case AS608_EVE_ADD_RESULT: //添加指纹结果
if(param == 0)
proto_0x13_addFingerResult(0);
else
proto_0x13_addFingerResult(4);
break;
case AS608_EVE_ADD_TIMEOUT: //添加指纹超时
proto_0x13_addFingerResult(3);
break;
default:
break;
}
return 0;
}
3.1.3 通信协议处理
STM32需要跟上位机QT交互,因此需要通信协议,以解析通信的内容及作相关处理。例如STM32端读到卡片需发送给QT端以判断是否开门、若要开门QT端还要发送开门指纹给STM32让其执行开门动作。
STM32主动发送协议的部分代码:
cpp
int proto_0x11_sendCardnum(unsigned char *cardnum) //发送卡号
{
unsigned char data_buf[64];
int data_len = 0;
int pack_len = 0;
data_len += proto_add_param(data_buf, ID_CARD_NUM, 8, cardnum);
proto_makeup_packet(0x11, data_buf, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &pack_len);
server.send(proto_tmp_buf, pack_len);
return 0;
}
int proto_0x13_addFingerResult(unsigned char result) //发送添加指纹结果
{
unsigned char data_buf[64];
int data_len = 0;
int pack_len = 0;
printf("add finger result %d\n", result);
data_len += proto_add_param(data_buf, ID_ADD_FINGER_RES, 1, &result);
proto_makeup_packet(0x13, data_buf, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &pack_len);
server.send(proto_tmp_buf, pack_len);
return 0;
}
int proto_0x14_recognFingerId(int id) //发送识别到的指纹
{
unsigned char data_buf[64];
int data_len = 0;
int pack_len = 0;
data_len += proto_add_param(data_buf, ID_FINGER_ID, 4, (unsigned char *)&id);
proto_makeup_packet(0x14, data_buf, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &pack_len);
server.send(proto_tmp_buf, pack_len);
return 0;
}
STM32接收到协议的处理:
cpp
static int server_proto_dispatch(uint8_t *pack, int len)
{
uint8_t cmd = 0;
uint8_t *data = NULL;
int data_len = 0;
int ack_len = 0;
int ret;
ret = proto_packet_analy(pack, len, &cmd, &data_len, &data);
if(ret != 0)
return -1;
printf("ptoto cmd: 0x%02x, pack_len: %d, data_len: %d\n", cmd, len, data_len);
switch(cmd)
{
case 0x03:
ret = server_0x03_heartbeat(data, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &ack_len);
break;
case 0x10:
ret = server_0x10_opendoor(data, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &ack_len);
break;
case 0x12:
ret = server_0x12_toWorkmode(data, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &ack_len);
break;
case 0x15:
ret = server_0x15_delete_finger(data, data_len, proto_tmp_buf, sizeof(proto_tmp_buf), &ack_len);
break;
default:
printf("error: cmd 0x%02x not found!\r\n", cmd);
break;
}
/* send ack data */
if(ret==0 && ack_len>0)
{
proto_makeup_packet(cmd, proto_tmp_buf, ack_len, proto_recv_buf, sizeof(proto_recv_buf), &data_len);
server.send(proto_recv_buf, data_len);
}
return 0;
}
3.2 QT软件
3.2.1 UI界面布局
在Qt creator中布局UI界面,如下:

3.2.2 按钮槽函数处理
按钮的槽函数是通过信号与槽机制实现的。当用户点击按钮时,按钮会发出一个 clicked() 信号,开发者可以将其连接到一个自定义的槽函数上,以处理按钮点击事件。
cpp
void MainWindow::on_connectBtn_clicked() //点击连接
{
if(ui->connectBtn->isChecked())
{
QString svr_ip;
svr_ip = ui->serverIpEdit->text();
qDebug() << "server ip" << svr_ip;
client->connectToHost(svr_ip, DEFAULT_SERVER_PORT);
}
else
{
client->disconnectFromHost();
}
}
void MainWindow::on_addUserBtn_clicked() //添加用户
{
if(ui->addUserBtn->isChecked())
{
ui->userIdEdit->clear();
ui->userNameEdit->clear();
ui->phoneEdit->clear();
ui->addUserWidget->setHidden(false);
g_sys_info.work_mode = WORK_MODE_ADD_USER;
g_sys_info.add_step = ADD_STEP_USERINFO;
proto_0x12_toWorkmode(1, 1, 0);
}
else
{
to_destop_window();
g_sys_info.work_mode = WORK_MODE_NORMAL;
proto_0x12_toWorkmode(0, 0, 0);
}
}
void MainWindow::on_userListBtn_clicked() //用户列表
{
if(ui->userListBtn->isChecked())
{
set_table_view(SQL_TABLE_USER);
ui->tableView->setHidden(false);
}
else
{
ui->tableView->setHidden(true);
}
}
void MainWindow::on_delUserBtn_clicked() //删除用户
{
user_info_t user;
QString tips;
int id;
if(!ui->userListBtn->isChecked())
return;
QModelIndex cur_index = ui->tableView->currentIndex();
if(cur_index.row() == -1)
return;
id = sqlmodel->data(sqlmodel->index(cur_index.row(), 0)).toInt();
memset(&user, 0, sizeof(user_info_t));
sql_user_read(id, &user);
tips = QString("是否删除用户<%1>?").arg(user.name);
if(QMessageBox::No == QMessageBox::question(this, "注意", tips, QMessageBox::Yes|QMessageBox::No, QMessageBox::No))
return;
proto_0x15_delete_finger(user.finger);
sql_user_del(id);
sqlmodel->select();
sqlmodel->submitAll();
}
void MainWindow::on_historyBtn_clicked() //历史记录
{
if(ui->historyBtn->isChecked())
{
set_table_view(SQL_TABLE_HISTORY);
ui->tableView->setHidden(false);
}
else
{
ui->tableView->setHidden(true);
}
}
void MainWindow::on_openDoorBtn_clicked() //开门
{
if(!mainwindow->tcp_connect)
return;
proto_0x10_opendoor(1);
history_info_t history;
memset(&history, 0, sizeof(history_info_t));
QDateTime dtime = QDateTime::currentDateTime();
QString str_dtime = dtime.toString("yyyy-MM-dd hh:mm:ss");
history.time = dtime.toTime_t();
strncpy(history.time_str, str_dtime.toLatin1().data(), TIME_STR_LEN);
strncpy(history.name, "管理员", USER_NAME_LEN);
sql_history_write(&history);
}
void MainWindow::on_exitBtn_clicked() //退出
{
loginDialog_init();
delete this;
}
3.2.3 SQL数据库
用户数据是用SQLite数据库存储的,建立2个数据表:用户表(用户信息)、记录表(开门历史);数据库的操作就是插入添加、删除、查询等动作。
创建数据表:
cpp
static int sql_create_user_tbl(void)
{
QString sql_cmd;
int ret = 0;
sql_mutex.lock();
sql_cmd = QString("create table if not exists %1("
"%2 int primary key not null,"
"%3 char(32),"
"%4 char(16), "
"%5 char(32), "
"%6 int);")
.arg(SQL_TABLE_USER)
.arg(SQL_COL_ID)
.arg(SQL_COL_NAME)
.arg(SQL_COL_PHONE)
.arg(SQL_COL_CARD)
.arg(SQL_COL_FINGER);
qDebug() << sql_cmd;
if(!sqlquery->exec(sql_cmd))
{
qDebug() << "sql exec failed!" ;
ret = -1;
}
sql_mutex.unlock();
return ret;
}
int sql_create_history_tbl(void)
{
QString sql_cmd;
int ret = 0;
sql_mutex.lock();
sql_cmd = QString("create table if not exists %1("
"%2 int primary key not null,"
"%3 char(32),"
"%4 int, "
"%5 char(32), "
"%6 char(32), "
"%7 int);")
.arg(SQL_TABLE_HISTORY)
.arg(SQL_COL_TIME)
.arg(SQL_COL_TIME_STR)
.arg(SQL_COL_ID)
.arg(SQL_COL_NAME)
.arg(SQL_COL_CARD)
.arg(SQL_COL_FINGER);
qDebug() << sql_cmd;
if(!sqlquery->exec(sql_cmd))
{
qDebug() << "sql exec failed!" ;
ret = -1;
}
sql_mutex.unlock();
return ret;
}
添加、删除、读取:
int sql_user_add(user_info_t *user)
{
QString sql_cmd;
int ret = 0;
sql_mutex.lock();
sql_cmd = QString("insert into %1(%2,%3,%4,%5,%6) "
"values(%7,'%8','%9','%10',%11);")
.arg(SQL_TABLE_USER)
.arg(SQL_COL_ID)
.arg(SQL_COL_NAME)
.arg(SQL_COL_PHONE)
.arg(SQL_COL_CARD)
.arg(SQL_COL_FINGER)
.arg(user->id)
.arg(user->name)
.arg(user->phone)
.arg(user->card)
.arg(user->finger);
qDebug() << sql_cmd;
if(!sqlquery->exec(sql_cmd))
{
qDebug() << "sql exec failed!" ;
ret = -1;
}
sql_mutex.unlock();
return ret;
}
int sql_user_del(int id)
{
QString sql_cmd;
int ret = 0;
sql_mutex.lock();
sql_cmd = QString("delete from %1 where %2=%3;")
.arg(SQL_TABLE_USER)
.arg(SQL_COL_ID)
.arg(id);
qDebug() << sql_cmd;
if(!sqlquery->exec(sql_cmd))
{
qDebug() << "sql exec failed!" ;
ret = -1;
}
sql_mutex.unlock();
return ret;
}
int sql_user_read(int id, user_info_t *user)
{
QString sql_cmd;
int ret = -1;
sql_mutex.lock();
sql_cmd = QString("select * from %1 where %2=%3;")
.arg(SQL_TABLE_USER)
.arg(SQL_COL_ID)
.arg(id);
qDebug() << sql_cmd;
if(!sqlquery->exec(sql_cmd))
{
qDebug() << "sql exec failed!" ;
sql_mutex.unlock();
return -1;
}
if(sqlquery->next())
{
user->id = sqlquery->value(0).toInt();
strcpy(user->name, (char *)sqlquery->value(1).toString().toLocal8Bit().data());
strcpy(user->phone, (char *)sqlquery->value(2).toString().toLocal8Bit().data());
strcpy(user->card, (char *)sqlquery->value(3).toString().toLocal8Bit().data());
user->finger = sqlquery->value(4).toInt();
ret = 0;
}
sql_mutex.unlock();
return ret;
}
获取资料/指导答疑/技术交流/选题/帮助,请点链接:
https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt
如有任何问题,请联系作者,谢谢!
-
-
- 曾哥,专注嵌入式。
-