代码改变世界

详细介绍:嵌入式项目:STM32刷卡RFID指纹识别考勤系统

2025-12-26 15:59  tlnshuju  阅读(0)  评论(0)    收藏  举报

 本文详细介绍基于STM32的刷卡rfid和指纹识别考勤系统。

 获取资料/指导答疑/技术交流/选题/帮助,请点链接:
https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt

1、系统介绍

1.1项目概述

项目概述:由stm32和qt实现的刷卡rfid+指纹的考勤系统,管理员可在上位机:添加/删除/查看用户、设置考勤时间、查看考勤记录等,普通用户:可用指纹或刷卡来打卡考勤。

主要功能:系统登录、建立连接及通信、设置考勤时间、录入用户、查看用户、删除用户、刷卡签到、指纹签到、查看考勤记录等。

系统框架:STM32硬件部分(下位机)+ QT管理平台(上位机),通过WiFi无线连接,采用TCP协议通信。

硬件组成:STM32单片机、RC522刷卡、AS608指纹、OLED显示、ESP8266 WiFi、蜂鸣器、LED等。

软件说明:stm32 使用 keil5 IDE 开发,采用 C 语言;管理平台即上位机,使用 Qt creator IDE 开发,采用 C/C++,数据库用 SQLite。

1.2实物图

系统的管理员界面及STM32硬件端实物图如下:

1.3主要功能

1.3.1系统登陆

运行管理平台需要先登录,登录界面如下:

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

1.3.2连接通信

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

1.3.3添加用户

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

1.3.4查看用户

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

1.3.5删除用户

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

1.3.6签到

签到可用刷卡或指纹的方式,二选一即可。

用已录入的指纹去刷指纹,能够成功签到并在OLED屏显示打卡成功,如下:

在签到时间后,用已录入的卡去刷卡,能够成功打卡但在OLED屏显示签到迟到,如下:

1.3.7签退

签退亦可用刷卡或指纹的方式,二选一即可。

用已录入的卡去刷卡,在签退时间后,能够成功签退并在OLED屏显示签退成功,如下:

1.3.8查看考勤记录

在管理员界面点击“考勤记录”,可查看签到与签退记录,如下:

1.3.9退出登陆

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

    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(DEBUG_UART_BAUDRATE);
    	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();
        memset(&g_sys_info, 0, sizeof(system_info_t));
        g_sys_info.work_mode = WORK_MODE_NORMAL;
    	// 注册任务,执行间隔以ms为单位
    	timsrv_register_task(0, task_while);
    	timsrv_register_task(100, task_100ms);
    	timsrv_register_task(1000, task_1000ms);
    	// 显示主界面
    	ui_show_destop();
    	ui_show_online(false);
    	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;
    	//扫描读卡,每次读到卡间隔2S
    	if(card_wait == 0)
    	{
    		ret = rc522_read_card(card_id);
    		if(ret == 0)
    		{
    			beep_on();
    			timsrv_set_delay_work(BEEP_ON_TIME, 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);
    			else
    				proto_0x13_addFingerResult(4);
    			break;
    		case AS608_EVE_ADD_TIMEOUT:		//添加指纹超时
    			proto_0x13_addFingerResult(3);
    			break;
    		default:
    			break;
    	}
    	return 0;
    }

    3.1.4通信协议处理

    STM32需要跟上位机QT交互,因此需要通信协议,以解析通信的内容及作相关处理。例如STM32端读到卡片需发送给QT端以判断签到是否有效、QT端还要发送签到结果给STM32让其在OLED屏上显示。

    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 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;
    		case 0x20:
    			ret = server_0x20_set_attend(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_attendListBtn_clicked() //考勤记录
    {
        if(ui->attendListBtn->isChecked())
        {
            set_table_view(SQL_TABLE_ATTEND);
            ui->tableView->setHidden(false);
        }
        else
        {
            ui->tableView->setHidden(true);
        }
    }
    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

    如有任何问题,请联系作者,谢谢!
    - - - 曾哥,专注嵌入式。