STM32F103不定长串口DMA通信AS608按两次指纹登录一个模板的HAL库实现调试模板

 一、背景

        假期想做一个锁控系统的项目,其中使用F103ZET6为主控,与AS608模块通信。由于AS608模块的指令发送和部分接收的字长都不相等,所以索性就学习了以空闲中断+DMA方式串口模式,分享一下自己踩的小坑,帮助后来者学习。

二、准备

        我使用的HAL库,用CubeMX快速配置模式,开启UART1作为串口调试显示接收到电脑,UART3作为与AS608的通信串口。

  NVIC什么的乱配一通,

         串口DMA采用字节的方式接收和发送,普通模式

三、AS608模块按两次指纹登录一个模板存于flash指纹库代码

1、宏定义

        首先我让deepseek给我根据文档生成.h文件所需要的模块宏定义,当然部分还需要自己微调(有了它是真的方便)。


#ifndef __AS60X_CMD_H__
#define __AS60X_CMD_H__

#include "stm32f1xx_hal.h"
#include "usart.h"
#include "led.h"
#include "delay/delay.h"
#include <stdint.h>

//------------------------- 基础定义 -------------------------
#pragma pack(push, 1) // 1字节对齐

// 包头定义
#define AS60X_PACKET_HEADER    __REV16(0xEF01)

// 包标识
#define PKG_TYPE_CMD           0x01    // 命令包
#define PKG_TYPE_DATA          0x02    // 数据包
#define PKG_TYPE_END           0x08    // 结束包
#define PKG_TYPE_RESPONSE      0x07    // 应答包

// 缓冲区编号
#define BUFFER_ID_1            0x01    // CharBuffer1
#define BUFFER_ID_2            0x02    // CharBuffer2

// 默认芯片地址
#define DEFAULT_CHIP_ADDR      0xFFFFFFFF

//发送包的最大长度
#define Packet_MAXLEN		   30


//------------------------- 指令代码 -------------------------
typedef enum {
    CMD_GetImage          = 0x01,   // 录入图像
    CMD_GenChar           = 0x02,   // 生成特征
    CMD_Match             = 0x03,   // 精确比对
    CMD_Search            = 0x04,   // 搜索指纹
    CMD_RegModel          = 0x05,   // 合并特征生成模板
    CMD_StoreChar         = 0x06,   // 存储模板
    CMD_LoadChar          = 0x07,   // 读取模板
    CMD_UpChar            = 0x08,   // 上传特征
    CMD_DownChar          = 0x09,   // 下载特征
    CMD_UpImage           = 0x0A,   // 上传图像
    CMD_DownImage         = 0x0B,   // 下载图像
    CMD_DeletChar         = 0x0C,   // 删除模板
    CMD_Empty             = 0x0D,   // 清空指纹库
    CMD_WriteReg          = 0x0E,   // 写寄存器
    CMD_ReadSysPara       = 0x0F,   // 读系统参数
    CMD_Enroll            = 0x10,   // 自动注册模板
    CMD_Identify          = 0x11,   // 自动验证指纹
    CMD_SetPwd            = 0x12,   // 设置口令
    CMD_VfyPwd            = 0x13,   // 验证口令
    CMD_GetRandomCode     = 0x14,   // 采样随机数
    CMD_SetChipAddr       = 0x15,   // 设置芯片地址
    CMD_ReadINFpage       = 0x16,   // 读FLASH信息页
    CMD_PortControl       = 0x17,   // 端口控制
    CMD_WriteNotepad      = 0x18,   // 写记事本
    CMD_ReadNotepad       = 0x19,   // 读记事本
    CMD_BurnCode          = 0x1A,   // 烧写FLASH
    CMD_HighSpeedSearch   = 0x1B,   // 高速搜索
    CMD_GenBinImage       = 0x1C,   // 生成二值化图像
    CMD_ValidTempleteNum  = 0x1D,   // 读有效模板数
    CMD_UserGPIOCommand   = 0x1E,   // GPIO控制
    CMD_ReadIndexTable    = 0x1F,   // 读索引表
} AS60x_CommandCode;

//------------------------- 应答确认码 -------------------------
typedef enum {
    ACK_OK                = 0x00,   // 成功
    ACK_PACKET_ERROR      = 0x01,   // 收包错误
    ACK_NO_FINGER         = 0x02,   // 无手指
    ACK_IMAGE_FAIL        = 0x03,   // 录入失败
    ACK_IMAGE_DRY         = 0x04,   // 图像太干
    ACK_IMAGE_WET         = 0x05,   // 图像太湿
    // ... 其他确认码参考文档定义
    ACK_PWD_ERROR         = 0x13,   // 口令错误
} AS60x_AckCode;

//------------------------- 数据结构 -------------------------
// 通用指令包结构(示例:CMD_GetImage)
typedef struct {
    uint16_t header;        // 包头 0xEF01
    uint32_t chip_addr;     // 芯片地址
    uint8_t  pkg_type;      // 包标识 (0x01)
    uint16_t pkg_len;       // 包长度(不包含自身)
    uint8_t  cmd_code;      // 指令代码
    uint16_t checksum;      // 校验和(包标识到校验和的和)
} AS60x_GetImage;

typedef struct {
    uint16_t header;        // 包头 0xEF01
    uint32_t chip_addr;     // 芯片地址
    uint8_t  pkg_type;      // 包标识 (0x01)
    uint16_t pkg_len;       // 包长度(不包含自身)
    uint8_t  cmd_code;      // 指令代码
	uint8_t  bufferID;		// 缓冲区
    uint16_t checksum;      // 校验和(包标识到校验和的和)
} AS60x_GenChar;

typedef struct {
    uint16_t header;        // 包头 0xEF01
    uint32_t chip_addr;     // 芯片地址
    uint8_t  pkg_type;      // 包标识 (0x01)
    uint16_t pkg_len;       // 包长度(不包含自身)
    uint8_t  cmd_code;      // 指令代码
	uint8_t  bufferID;		// 缓冲区
	uint16_t  pageID;		//位置号
    uint16_t checksum;      // 校验和(包标识到校验和的和)
} AS60x_StoreChar;

// 通用应答包结构
typedef struct {
    uint16_t header;        // 包头 0xEF01
    uint32_t chip_addr;     // 芯片地址
    uint8_t  pkg_type;      // 包标识 (0x07)
    uint16_t pkg_len;       // 包长度
    uint8_t  ack_code;      // 确认码
    uint16_t checksum;      // 校验和
} AS60x_ResponsePacket;

//------------------------- 寄存器定义 -------------------------
typedef enum {
    REG_BAUD_RATE      = 0x04,   // 波特率控制寄存器
    REG_SECURITY_LEVEL = 0x05,   // 安全等级寄存器
    REG_PACKET_SIZE    = 0x06,   // 数据包大小寄存器
} AS60x_RegAddr;

extern uint8_t AS608_Buffer[Packet_MAXLEN];
extern uint8_t Flag;


void SendCMD_GetImage(void);
void SendCMD_GenChar(uint8_t buffer_ID);
void SendCMD_RegModel(void);
void SendCMD_StoreChar(uint8_t buffer_ID , uint16_t page_ID);

//BSP
uint8_t Log_AS60x(uint16_t page_ID);

#pragma pack(pop)

#endif // __AS60X_CMD_H__

2、.c执行文件

        这里我使用串口传递结构体的方式(这也是我从学弟那里得知的,以前都是裤裤传递uint8数组),感觉这样才更方便阅读和管理。将结构体以二进制数据性进行传递,将结构体的内存布局视为一个连续的字节数组,以便通过逐字节的方式访问或传输数据。在调用类似HAL_UART_Transmit_DMA的函数时,需要传递一个uint8_t* 类型的缓冲区指针。通过强制转换,可以直接发送结构体的原始内存。

        由于STM32是以小端存储数据,内存的读写永远从低地址开始读/写,比如存储0x0006,在其内部就是显示就是0x0600,串口传输的也是0x0600,而AS608模块要接收这位数据0x0006才有响应,所以就要把STM内部存储的那几位数据进行按字节反转,使用__REV16()函数,反转两个字节。在其他函数中我直接写入手动反转😂,懒得挨个写入函数了。

        在编写Log_AS60x()时,一定要注意两个指令之间的时间间隔,我这里设置的都是3s,如果过短的时间间隔会导致取指失败。根据AS608模块的用户手册写业务逻辑,代码全文如下:

#include "AS608.h"

uint8_t AS608_Buffer[Packet_MAXLEN];
AS60x_GetImage  GetImage_CommandPacket;
AS60x_GenChar	GenChar_CommandPacket;
AS60x_GetImage  RegModel_CommandPacket;
AS60x_StoreChar StoreChar_CommandPacket;

uint8_t Flag = 0;

void SendCMD_GetImage(void)
{
	GetImage_CommandPacket.header = AS60X_PACKET_HEADER;
	GetImage_CommandPacket.chip_addr = DEFAULT_CHIP_ADDR;
	GetImage_CommandPacket.pkg_type = 0x01;
	GetImage_CommandPacket.pkg_len  = 0x0300;
	GetImage_CommandPacket.cmd_code = CMD_GetImage;
	GetImage_CommandPacket.checksum = 0x0500;
	
	if(HAL_UART_GetState(&huart3) != HAL_UART_STATE_BUSY_TX){
		HAL_UART_Transmit_DMA(&huart3,(uint8_t*)&GetImage_CommandPacket,sizeof(AS60x_GetImage));
//		printf("1\r\n");
	}
}

void SendCMD_GenChar(uint8_t buffer_ID)
{
	GenChar_CommandPacket.header = AS60X_PACKET_HEADER;
	GenChar_CommandPacket.chip_addr = DEFAULT_CHIP_ADDR;
	GenChar_CommandPacket.pkg_type = 0x01;
	GenChar_CommandPacket.pkg_len  = 0x0400;
	GenChar_CommandPacket.cmd_code = CMD_GenChar;
	GenChar_CommandPacket.bufferID = buffer_ID;
	if(buffer_ID == 0x01){
		GenChar_CommandPacket.checksum = 0x0800;
	}else if(buffer_ID == 0x02){
		GenChar_CommandPacket.checksum = 0x0900;
	}
	
	if(HAL_UART_GetState(&huart3) != HAL_UART_STATE_BUSY_TX){
		HAL_UART_Transmit_DMA(&huart3,(uint8_t*)&GenChar_CommandPacket,sizeof(AS60x_GenChar));
//		printf("2\r\n");
	}
}

void SendCMD_RegModel(void)
{
	RegModel_CommandPacket.header = AS60X_PACKET_HEADER;
	RegModel_CommandPacket.chip_addr = DEFAULT_CHIP_ADDR;
	RegModel_CommandPacket.pkg_type = 0x01;
	RegModel_CommandPacket.pkg_len = 0x0300;
	RegModel_CommandPacket.cmd_code = CMD_RegModel;
	RegModel_CommandPacket.checksum = 0x0900;

	HAL_UART_Transmit_DMA(&huart3,(uint8_t*)&RegModel_CommandPacket,sizeof(AS60x_GetImage));
}

void SendCMD_StoreChar(uint8_t buffer_ID , uint16_t page_ID)
{
	StoreChar_CommandPacket.header =  AS60X_PACKET_HEADER;
	StoreChar_CommandPacket.chip_addr = DEFAULT_CHIP_ADDR;
	StoreChar_CommandPacket.pkg_type = 0x01;
	StoreChar_CommandPacket.pkg_len = __REV16(0x06); //反转
	StoreChar_CommandPacket.cmd_code = 0x06;
	StoreChar_CommandPacket.bufferID = buffer_ID;
	StoreChar_CommandPacket.pageID = __REV16(page_ID);
	StoreChar_CommandPacket.checksum = __REV16(StoreChar_CommandPacket.pkg_type + __REV16(StoreChar_CommandPacket.pkg_len)
	+ StoreChar_CommandPacket.cmd_code + StoreChar_CommandPacket.bufferID + __REV16(StoreChar_CommandPacket.pageID));
	
	HAL_UART_Transmit_DMA(&huart3,(uint8_t*)&StoreChar_CommandPacket,sizeof(AS60x_StoreChar));
}

uint8_t Log_AS60x(uint16_t page_ID)
{
	SendCMD_GetImage();
	delay_ms(3000);
	
	if(AS608_Buffer[9] == 0x00){
		SendCMD_GenChar(0x01);
		delay_ms(3000);
	}else	
		return 0;
	
	SendCMD_GetImage();
	delay_ms(3000);
	
	if(AS608_Buffer[9] == 0x00){
		SendCMD_GenChar(0x02);
		delay_ms(3000);
	}else
		return 0;
	
	SendCMD_RegModel();
	delay_ms(3000);
	SendCMD_StoreChar(0x02,page_ID);
	delay_ms(3000);
	
	return 1;
}

3.中断回调函数文件

        我将中断回调放入了一个自己创建的文件里, 看起来更简洁些(也许)。重写HAL_UARTEx_RxEventCallback函数,HAL_UART_TxCpltCallback函数。其中HAL_UARTEx_ReceiveToIdle_DMA()函数结合DMA和空闲中断。无需依赖固定长度或结束符,通过总线空闲自动判断帧结束,而空闲时间由UART波特率决定(例如:9600bps时,1字节传输时间为1.04ms,空闲时间至少为1字节时间)。

        开启这个函数会同时开启两个中断 DMA_IT_HT(DMA半传输中断位)和  DMA_IT_TC( DMA传输完成中断)。

        由于开起了HAL_UARTEx_ReceiveToIdle_DMA()后直接使用的话,就会传输半个长度的数据便检测,然后全检测,几乎同一时间就会检测两遍,实际效果如下:

        为避免半传输中断的产生,我就在其后面加入__HAL_DMA_DISABLE_IT(&hdma_usart3_rx,DMA_IT_HT);

        在HAL_UART_TxCpltCallback()函数中使用HAL_UARTEx_ReceiveToIdle_DMA()回调HAL_UARTEx_RxEventCallback()函数。把内容发送给UART1串口验证

        手指头按上时的验证结果如下:

        不按上时的验证结果如下:

        手指第一次取指纹放上去第二次不放上去结果如下:

        回调函数代码全文如下:

#include "CpltCallback/CpltCallback.h"


void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if(huart->Instance == USART1){
		
	}
	
	if(huart->Instance == USART3){
		
		HAL_UART_Transmit_DMA(&huart1,AS608_Buffer,Size);
		
		if(AS608_Buffer[9] == 0x00){
			printf("\r\n");
		}else{
			printf("\r\n");		
		}
		
		HAL_UARTEx_ReceiveToIdle_DMA(huart,AS608_Buffer,Size);
		__HAL_DMA_DISABLE_IT(&hdma_usart3_rx,DMA_IT_HT);
		
	}
} 

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1){
		
	}
	if(huart->Instance == USART3){
		
		HAL_UARTEx_ReceiveToIdle_DMA(huart,AS608_Buffer,30);
		__HAL_DMA_DISABLE_IT(&hdma_usart3_rx,DMA_IT_HT);
		
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3){
		if(AS608_Buffer[9] == 0x00){
			printf("success\r\n");
		}else{
			printf("fail\r\n");
		}
		while(HAL_UART_Receive_DMA(huart,AS608_Buffer,12) != HAL_OK);
	}
}

四、结语

        AS608模块中有很多的指令,可玩性还是很高的,并且可以作为练习串口传输的工具,了解串口的各种模式。

        

        今天考研公布分数,今年的国家线比去年下降了十几分,但我没有过国家线,还是那段时间没有真正脚踏实地的学习。很难过虽然考完的当天就感觉没戏,觉得这半年时间里是浪费掉的。开学后就要找实习工作去,希望在工作这方面不要浮于表面,踏实专研。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值