这里写目录标题
- 前言
- 串口接收流程
- 串口中断函数
- ReadITCallBack1中断接收函数
- 补充
- 结果展示
前言
Hi3061M给了很多相关的串口案例,但大多数是定长的,指定长度进行接收读取,而实际需求往往需要用到不定长的接收。
串口接收流程
首先介绍下Hi3061M串口接收流程,本篇主要介绍中断实现方式。
中断的方式接收数据在案例中的实现步骤首先是对串口进行初始化配置,然后注册中断函数****HAL_UART_IrqHandler,里面就是中断接收的主要实现代码。
HAL_UART_Init(&g_uart);
HAL_UART_RegisterCallBack(&g_uart, UART_TRNS_IT_ERROR, (UART_CallbackType)UART0InterruptErrorCallback);
HAL_UART_RegisterCallBack(&g_uart, UART_WRITE_IT_FINISH, (UART_CallbackType)WriteCallBack);
HAL_UART_RegisterCallBack(&g_uart, UART_READ_IT_FINISH, (UART_CallbackType)ReadCallBack);
IRQ_Register(IRQ_UART0, HAL_UART_IrqHandler, &g_uart);
IRQ_SetPriority(IRQ_UART0, 1); /* 1 is priority value */
IRQ_EnableN(IRQ_UART0);
其次在主函数中调用HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);将对应的存储接收数据的数组地址和接收数据固定长度传入,最后赋值给g_uart串口句柄对应的结构体变量。
uartHandle->rxbuff = saveData;
uartHandle->rxBuffSize = dataLength;
if (uartHandle->fifoMode == true) {
uartHandle->baseAddress->UART_IMSC.reg |= 0x7D0; /* Enable rx interrupt and rx timeout interrupt */
} else {
uartHandle->baseAddress->UART_IMSC.reg |= 0x20780; /* Enable rx not empty interrupt */
}
然后根据是否开启了FIFO模式(一般开启,本篇主要介绍开启该模式下的串口),设置开启对应的中断,即接收中断和接收超时中断。
开启中断后,如果接收到数据就会触发中断,从而进入中断函数,然后进行对应的数据接收处理。
串口中断函数
中断函数的内容主要就是根据中断屏蔽后状态寄存器的各个参数来触发,至于为什么是屏蔽后状态寄存器,而不少中断原状态寄存器来判断,我暂时没明白,但是试过了使用原中断状态寄存器的值判断同样也可以。
在介绍ReadITCallBack1中断接收函数函数之前,先介绍初始化中一个重要的参数设置。
g_uart.fifoTxThr = UART_FIFODEPTH_SIZE8;
g_uart.fifoRxThr = UART_FIFODEPTH_SIZE8;
UART 支持16 x 8bit的发送FIFO和16 x 12bit的接收FIFO,接收FIFO阈值和发送FIFO 阈值,决定触发UART中断触发点。例如:接收FIFO≥UART_FIFODEPTH_SIZE4 ,当接 收FIFO存放数据增长到大于等于4个字节数据时,触发接收中断;发送 FIFO≤UART_FIFODEPTH_SIZE4 ,当发生FIFO存放数据减少到小于等于4个数据时, 触发发送中断。UART的接收FIFO阈值点和发送FIFO阈值点一共可以配置多种,具体的阈值枚举定uart/uart_v1/inc/uart_ip.h。
这个参数的意义在于一次性可读取的FIFO大小,字节数,设置为UART_FIFODEPTH_SIZE8,即增长到8时会触发中断,然后对FIFO进行读取,也就是说中断函数中本次接收最多为8字节。注意这非常重要,这也意味着中断接收数据存在两种情况。
第一种情况,数据少于设定阈值,小于UART_FIFODEPTH_SIZE8,比如我设置为8字节,发送给MCU6字节数据,那么串口进入中断接收一次即可完成数据接收和存储。
第二种情况,数据多于设定阈值,比如设置阈值为8字节,发送给MCU15字节数据,而FIFO在8字节时,会触发接收中断,意味着会先将前8字节数据在中断函数中进行接收处理,后续剩余字节则下次再次进入中断函数中进行处理。也就是会进入两次中断,但是如果设置为UART_FIFODEPTH_SIZE15,最大为15,如果是这样的话,就进入一次中断函数就可以将接收完毕。
注意以上是本人调试的经验总结而出,与具体设计可能存在偏颇,如有问题,望指正,但是从测试结果来看确实是如此。
ReadITCallBack1中断接收函数
接下来详细介绍ReadITCallBack1中断接收函数。这是改写的不定长的数据接收函数,以回车换行符号作为结束标记,可以参考实现。
在
unsigned char USART_RX_STA=0; //接收状态标记 0表示开始接收,1表示接收到0d, 2表示接收完毕
static void ReadITCallBack1(UART_Handle *uartHandle)
{
UART_ASSERT_PARAM(uartHandle != NULL);
UART_ASSERT_PARAM(uartHandle->rxbuff != NULL);
UART_ASSERT_PARAM(IsUARTInstance(uartHandle->baseAddress));
if (uartHandle->rxState == UART_STATE_BUSY_RX) {
unsigned int tmp;
while (USART_RX_STA!=2) {
if (uartHandle->baseAddress->UART_FR.BIT.rxfe == 0x01) { /* True when the RX FIFO is empty */
break;
}
tmp = uartHandle->baseAddress->UART_DR.reg;
if(USART_RX_STA==1)//接收到了0x0d
{
if((tmp & 0xFF)!=0x0a)USART_RX_STA =0;//接收错误,重新开始
else USART_RX_STA = 2; //接收完成了
}
else //还没收到0X0D
{
if((tmp & 0xFF)==0x0d)USART_RX_STA = 1;
else
{
*(uartHandle->rxbuff)=(tmp & 0xFF);
uartHandle->rxbuff++;
}
}
}
if (USART_RX_STA!=2) {
uartHandle->baseAddress->UART_IMSC.reg &= 0xFFFDFFAF; /* Disable rxim ,rtim and rxfneim */
uartHandle->rxState = UART_STATE_READY;
}
uartHandle->baseAddress->UART_ICR.reg |= 0x20050; /* Clear rxic, rtic and rxfneic */
if (uartHandle->userCallBack.ReadItFinishCallBack != NULL && USART_RX_STA!=2) {
USART_RX_STA = 0;
uartHandle->userCallBack.ReadItFinishCallBack(uartHandle);
}
}
return;
}
通过设置一个接受标志USART_RX_STA,0表示接受,1表示接收到了0x0d,就是对应的回车符号,2表示接收完0x0d后成功接收到0x0a,也就是完整的接收到了结束标志,表示接收完成。整个函数的逻辑就是先循环判断是否接收完成,如果没有就进行数据接收,接收到0x0d,就修改标志,并马上确定接下来的数据是否为0x0d,如果为0x0d则表示接收完成,如果不是则接收错误结束标识,需重新接收。
后面就是接收完成后,对中断寄存器进行清零,并屏蔽接收中断(因为main函数中接收中断是通过函数主动开启主动接收的,其实这也可以优化,无需main主动触发,设置为中断接收,有数据来就触发中断并接收数据,这需要另外设置接收数组。),最后还要清除接收标志,此外调用配置的回调函数。
另外接收的长度会受限制与数组大小,这个由用户自定义设置,可以对接收长度进行计数并判断是否超出长度,可以充分利用USART_RX_STA的8个位进行计数,3位做标志,5位做计数,也可以扩展,优化空间还有很多。
另外注意的一个点就是
if (uartHandle->baseAddress->UART_FR.BIT.rxfe == 0x01) {
/* True when the RX FIFO is empty */
break;
}
这一句非常重要,这是判断fifo是否为空, 如果为空,两个情况(上面提到的),一个是串口接收完成,也就是USART_RX_STA=2了,完成一次接收了;另一个情况是,串口未读取到结束标志,但是单次接收完成了,也就是设置为UART_FIFODEPTH_SIZE8后,接收完8字节了但是未读取到结束标志,说明后面还有数据。
此时中断会跳出接收循环,但是不会运行屏蔽中断使能(因为USART_RX_STA!=2),同样不会运行标志清零和回调函数,因为此次接收还不算完成,但是会清除中断标志,等待下次中断触发,接下来的数据进入fifo同样会触发中断,从而继续进行数据接收,直到接收到结束标志为止。
这样就实现了以回车换作为结束标志的中断不定长接收实现,注意我个人感觉只有这种设置结束标志的方式是唯一的不定长接收方式,另外的方法应该是不太行,判空是不太行的因为涉及到了前面说的第二种情况,需要多次进入中断接收的情况,除非笃定一次的数据会少于设定的阈值UART_FIFODEPTH_SIZEX。
补充
至于main函数中,几乎不需要进行修改,这是中断接收后写的例程中的main函数
static volatile unsigned char g_str[20] = {0};
void UART_InterruptTxAfterRx(void)
{
SystemInit();
DBG_PRINTF("UART Init finish, please enter characters:\r\n");
unsigned int rxDataLength = 0; /* The receive length is 10 */
g_flag = false;
HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);
while (1) {
if (g_flag == true) {
g_flag = false;
unsigned int txDataLength = CountString(g_str); /* string length of the data to be sent after receiving */
HAL_UART_WriteIT(&g_uart, g_str, txDataLength);
BASE_FUNC_DELAY_MS(5);
ClearString(g_str);
HAL_UART_ReadIT(&g_uart, g_str, rxDataLength);
}
}
return;
}
其中rxDataLength 可以不用管,设置为0即可,因为我们没有用到这个参数,不需要根据这个进行判断。
此外这个例程中需要加入延迟BASE_FUNC_DELAY_MS(5);具体延迟时间需要大于2MS应该就可以,不然写数据会出现问题,因为ClearString(g_str);函数可能会清除掉接收的数据导致发送异常。g_str接收数组的大小可以自行设置。
结果展示
有什么问题欢迎一起沟通交流~
觉得还不错就点个小赞吧,谢谢