①MCU为STM32L431,使用串口2。
②Liteos采用接管中断的方式。
STM32CubeMX配置生成串口代码:
串口DMA接收和发送配置区别是接收采用循环模式,发送为正常模式。
将生成的代码移植到liteos工程中,由于使用的接管中断的方式,所以使能中断的时候需要先注册中断。
HAL_NVIC_SetPriority(USART2_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel6_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);
改为:
uvIntSave = LOS_IntLock();
LOS_HwiCreate(USART2_IRQn,1,(HWI_MODE_T)0,(HWI_PROC_FUNC)USART2_IRQHandler,(HWI_ARG_T)0);
LOS_HwiCreate(DMA1_Channel6_IRQn,1,(HWI_MODE_T)0,(HWI_PROC_FUNC)DMA1_Channel6_IRQHandler,(HWI_ARG_T)0);
LOS_HwiCreate(DMA1_Channel7_IRQn,2,(HWI_MODE_T)0,(HWI_PROC_FUNC)DMA1_Channel7_IRQHandler,(HWI_ARG_T)0);
LOS_IntRestore(uvIntSave);
创建一个事件标志组,并用其中的一位来表示串口接收到数据。
#define ZigbeeUartIdle (0x01<<0)
#define ZigbeeUartRecv (0x01<<1)
UINT32 uwRet = LOS_OK;
uwRet = LOS_EventInit(&SysEventGroup);
if(uwRet != LOS_OK)
{
PRINT_ERR("Event Error Code:0x%X\n",uwRet);
return uwRet;
}
串口初始化函数中使能串口接收:
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, (uint8_t*)Usart2RxBuf, USART2_RX_BUFFER_LEN);
不要使用HAL_UART_Receive_DMA函数,直接使用HAL_UARTEx_ReceiveToIdle_DMA函数,该函数会以DMA方式接收一定数量的数据,直到收到预期数量的数据或者发生IDLE事件时会产生中断,这样的话在中断服务函数中可以直接调用回调函数,去处理DMA接收的数据。
串口的中断服务函数:
void ClearError(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->ISR);
uint32_t errorflags;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
if (errorflags != 0U)
{
__HAL_UART_CLEAR_FLAG(&huart2, UART_CLEAR_OREF | UART_CLEAR_NEF | UART_CLEAR_PEF | UART_CLEAR_FEF);
}
}
void USART2_IRQHandler(void)
{
ClearError(&huart2);
HAL_UART_IRQHandler(&huart2);
}
在中断服务函数中调用ClearError是为了防止因为上溢错误或者帧错误导致一直进入中断服务函数中,所以在处理中断事件时先把错误清除。
如果在初始化串口的时候注册了DMA通道的中断的话,当使用DMA发送数据时半发送或者发送完成时,都会进入DMA通道中断,同样的在接收数据的时候当收到定义的缓冲区一半或者接收满时也会进入DMA通道中断。
void DMA1_Channel6_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_usart2_rx);
}
void DMA1_Channel7_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma_usart2_tx);
}
使用DMA发送数据完成会调用HAL_UART_TxCpltCallback回调函数,可以在这里表示串口空闲。
/**
* @brief Tx Transfer completed callback
* @param UartHandle: UART handle.
* @note This example shows a simple way to report end of DMA Tx transfer, and
* you can add your own implementation.
* @retval None
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
/* Set transmission flag: transfer complete*/
LOS_EventWrite(&SysEventGroup,ZigbeeUartIdle);
}
当发生空闲中断的时候会调用HAL_UARTEx_RxEventCallback函数来处理,函数参数Size表示接收到的字节数,如果使用的循环模式,那么传进来的Size会自动循环。比如定义的接收缓冲区共有56个字节,第一次产生空闲中断时接收20字节,Size大小为20,第二次产生空闲中断时接收20字节,Size大小为40,第三次产生空闲中断时接收20字节,Size大小为4。
Usart2RxLen为一个全局变量,用来表示任务在每次处理数据时需要处理的数据长度,比如第一次收到20字节,任务立刻处理,那么会在任务中把Usart2RxLen清零;如果第一次收到20字节,任务没有处理,第二次又收到20字节,Usart2RxLen会累加,那么这个时候任务需要从接收缓存区中读取的数据长度就是40。RecvOnce是一个静态的全局变量,用来记录上次执行HAL_UARTEx_RxEventCallback函数时传入的Size值,以此来区分本次空闲中断产生时,接收的数据有没有从头开始,从而正确的计算出本次接收到数据的长度。
volatile uint16_t Usart2RxLen = 0;
static uint16_t RecvOnce = 0;
/**
* @brief Reception Event Callback (Rx event notification called after use of advanced reception service).
* @param huart UART handle
* @param Size Number of data available in application reception buffer (indicates a position in
* reception buffer until which, data are available)
* @retval None
*/
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART2)
{
LOS_EventWrite(&SysEventGroup,ZigbeeUartRecv);//通知任务接收到数据
if(Size > RecvOnce)
{
PRINT_INFO("size = %d\r\n",Size-RecvOnce);
Usart2RxLen = (Usart2RxLen + Size - RecvOnce) % USART2_RX_BUFFER_LEN;
}
else
{
PRINT_INFO("size = %d\r\n",(USART2_RX_BUFFER_LEN - RecvOnce + Size));
Usart2RxLen = (Usart2RxLen+(USART2_RX_BUFFER_LEN - RecvOnce + Size)) % USART2_RX_BUFFER_LEN;
}
RecvOnce = Size;
}
}
任务中读取数据:
void Process_Task(void)
{
UINT32 uwRet = LOS_OK;
UINT16 msglen = 0;
UINT8 msg[USART2_RX_BUFFER_LEN];
while(1)
{
uwRet = LOS_EventRead(&SysEventGroup,ZigbeeUartRecv,LOS_WAITMODE_AND | LOS_WAITMODE_CLR,0);
if(uwRet & ZigbeeUartRecv)
{
msglen = Usart2RxLen;
Usart2RxLen = 0;//待处理数据长度清零
memset(msg,0x00,USART2_RX_BUFFER_LEN);
if((msglen + DealAddr) <= USART2_RX_BUFFER_LEN)
{
memcpy(msg,&Usart2RxBuf[DealAddr],msglen);
}
else
{
memcpy(msg,&Usart2RxBuf[DealAddr],(USART2_RX_BUFFER_LEN - DealAddr));
memcpy(&msg[USART2_RX_BUFFER_LEN - DealAddr],Usart2RxBuf,(msglen+DealAddr-USART2_RX_BUFFER_LEN));
}
DealAddr = (DealAddr+msglen) % USART2_RX_BUFFER_LEN;
HAL_UART_Transmit_DMA(&huart2,msg,msglen);
}
LOS_TaskDelay(LOS_MS2Tick(50));//50ms轮询一次
}
}
任务处理串口数据的周期是50ms,使用LOS_EventRead读取事件标志的时候超时时间设置为0,作用相当于轮询状态后直接返回,处理数据的时候使用DealAddr变量来记录上次处理之后的位置,通过(msglen + DealAddr)的值和接收缓冲区的长度比较,来确定读取数据的方式,并使用HAL_UART_Transmit_DMA函数来完成数据回环测试。
接收缓冲区的长度是56,可以看到在第三次和第六次接收的时候,一帧数据调用了两次HAL_UARTEx_RxEventCallback函数,这是因为DMA通道产生了半接收(28)和接收完成中断(56),但是对一帧数据的接收没有影响。
如果不想有这种情况,可以不使能串口RX绑定的DMA通道中断。
模拟串口收到一帧数据,但是任务没有处理数据,又来一帧时一起处理。