?本文詳細介紹基于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程序如下:
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用于AS608usart3_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為舵機提供pwmpwm3_init(72-1, 20000-1); //舵機要求:頻率1MHz,周期20mspwm3_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個不同間隔執行的函數,如下:
/* 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;//掃描讀卡,每次讀到卡間隔2Sif(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等有事件回調的處理,如下:
//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);elseproto_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主動發送協議的部分代碼:
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接收到協議的處理:
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()?信號,開發者可以將其連接到一個自定義的槽函數上,以處理按鈕點擊事件。
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個數據表:用戶表(用戶信息)、記錄表(開門歷史);數據庫的操作就是插入添加、刪除、查詢等動作。
創建數據表:
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
如有任何問題,請聯系作者,謝謝!
- - - 曾哥,專注嵌入式。