如果单片机不支持串口空闲中断和DMA,可以参考之前写的,串口只用接收中断,完成不定长的分包。
这里以stm32L4的单片机举例,思路可拓展到GD32等支持DMA和串口空闲中断的单片机。
串口DMA接收,就是当串口有数据接收的时候,不会触发串口中断,等串口空闲后,(一帧数据包接收完成,硬件会自己保证)会促发一次串口中断,此时可以对一个完整的数据包进行处理。
只开串口接收中断是每个字节都会促发一次串口中断服务函数,这样会让CPU多次执行,入栈出栈等多余操作,也不能保证,每次都正确分包接收到的数据,毕竟是软件时间来分的包。
先初始化单片机的DMA和GPIO复用成串口。stm32单片机,可以用cubemx生成代码。下面是stm32l431的串口初始化。
#include <stdio.h>
#include "stm32l4xx.h"
#include "stm32l4xx_hal.h"
#define UART1_PORT GPIOB
#define UART1_TX_PIN GPIO_PIN_6
#define UART1_RX_PIN GPIO_PIN_7
UART_HandleTypeDef UART1_Handler; //UART1 Handler
DMA_HandleTypeDef hdma_usart1_rx; //UART1 dma Handler
//USART1 RX SIZE
#define USART1_RX_SIZE 256
//USART1 RX buff
uint8_t USART1_rxBuffer[USART1_RX_SIZE];
/**
* @brief UART1 initialize
* @param None
* @retval None
*/
void BSP_UART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//Enable GPIO and USART clock
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
//UART TX GPIO pin configuration
GPIO_InitStruct.Pin = UART1_TX_PIN | UART1_RX_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(UART1_PORT, &GPIO_InitStruct);
UART1_Handler.Instance = USART1;
UART1_Handler.Init.BaudRate = 115200;
UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B;
UART1_Handler.Init.StopBits = UART_STOPBITS_1;
UART1_Handler.Init.Parity = UART_PARITY_NONE;
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UART1_Handler.Init.Mode = UART_MODE_TX_RX;
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UART1_Handler.Init.OverSampling = UART_OVERSAMPLING_16;
UART1_Handler.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
UART1_Handler.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&UART1_Handler);
/* USART1 DMA Init */
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_rx.Init.Request = DMA_REQUEST_2;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_NORMAL;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
HAL_DMA_Init(&hdma_usart1_rx);
__HAL_LINKDMA(&UART1_Handler,hdmarx,hdma_usart1_rx);
//开启DMA接收,收完USART1_RX_SIZE字节后,会停止接收,因为没有用循环模式。使用建议,串口收的一包数据不大于USART1_RX_SIZE。串口空闲的时候,可以再次重新设置接收长度,这样DMA传输不会停止,不用循环模式,也能避免数据覆盖。
HAL_UART_Receive_DMA(&UART1_Handler,USART1_rxBuffer,USART1_RX_SIZE);
//只开串口空闲中断
__HAL_UART_ENABLE_IT(&UART1_Handler, UART_IT_IDLE);
HAL_NVIC_SetPriority(USART1_IRQn,1,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
//prinft输出重定义到串口发送,串口发送也可以用DMA,需要封装一下DBG打印,感觉不是很必要,CPU累一点也没啥。
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&UART1_Handler, (uint8_t *)&ch, 1, 0xff);
return ch;
}
驱动完成后,是应用。可以在串口空闲中断来的时候,置一个标志,完成对这包数据的处理后再开启DMA串口接收。还可以,把数据拷贝到fifo存着,直接开启DMA串口接收,下面用自己写fifo存储方式应用,(这个存储方式,不知道速度上能不能再优化了,欢迎大佬指正)。
在中断服务函数中,把数据拷贝到fifo里面。
/**
* @brief UART1 IRQ Handler
* @param None
* @retval None
*/
void USART1_IRQHandler(void)
{
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_IDLE)!=RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&UART1_Handler);
HAL_UART_DMAStop(&UART1_Handler);
uint16_t rx_len;
//__HAL_DMA_GET_COUNTER(&hdma_usart1_rx) 这个寄存器里面初值是USART1_RX_SIZE大小,每收到一个字节会减一,减到0的话,DMA不再从外设把数据搬到定义的数组内存。下面的计算,是算出这包数据帧的长度。
rx_len = USART1_RX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
//长度有效,写入fifo
if(rx_len)
Write_RingBuff(USART1_rxBuffer,rx_len);
//再次促发DMA搬运USART1_RX_SIZE个字节到USART1_rxBuffer
HAL_UART_Receive_DMA(&UART1_Handler,USART1_rxBuffer,USART1_RX_SIZE);
}
HAL_UART_IRQHandler(&UART1_Handler);
}
下面是fifo的实现,用到产品一段时间,目前没发现bug。思想就是两个循环fifo,一个数据存储的fifo,一个是记录包长度的fifo。最大存10包,buff长度最大100字节,根据实际使用调正。
#define RINGBUFF_LEN 100
#define PACKET_LEN 10
typedef struct
{
uint16_t Head;
uint16_t Tail;
uint8_t Ring_Buff[RINGBUFF_LEN];
}RingBuff_t;
//ringbuff structure
RingBuff_t ringBuff = {0};
//read index
uint8_t r_index = 0;
//write index
uint8_t w_index = 0;
//packet count
uint8_t count = 0;
//Record packet length
uint16_t Packet_len[PACKET_LEN];
/**
* @brief Write_RingBuff
* @param Which of the buff
* @param date len
* @retval none
*/
void Write_RingBuff(uint8_t *data,uint16_t len)
{
if(count >= 10)
{
printf("the packet is full!\r\n");
return ;
}
uint8_t *p = data;
Packet_len[w_index] = len;
if(++w_index >= PACKET_LEN) w_index = 0;
uint16_t i;
if(ringBuff.Tail + len < RINGBUFF_LEN)
{
for(i = 0; i < len; ++i)
{
ringBuff.Ring_Buff[ringBuff.Tail] = *p;
++ringBuff.Tail;
++p;
}
}
else
{
uint16_t size = RINGBUFF_LEN - ringBuff.Tail;
for(i = 0; i < size; ++i)
{
ringBuff.Ring_Buff[ringBuff.Tail] = *p;
++ringBuff.Tail;
++p;
}
ringBuff.Tail = 0;
for(i = size; i < len; ++i)
{
ringBuff.Ring_Buff[ringBuff.Tail] = *p;
++ringBuff.Tail;
++p;
}
}
++count;
}
/**
* @brief read_RingBuff
* @param Which of the buff
* @param date len
* @retval -1 :No package 0:have package
*/
int8_t Read_RingBuff(uint8_t *rData,uint16_t *len)
{
if(count == 0)
{
return -1;
}
uint8_t *p = rData;
*len = Packet_len[r_index];
if(++r_index >= PACKET_LEN) r_index = 0;
uint16_t i;
if(ringBuff.Head + *len < RINGBUFF_LEN)
{
for(i = 0; i < *len; ++i)
{
*p = ringBuff.Ring_Buff[ringBuff.Head];
++ringBuff.Head;
++p;
}
}
else
{
uint16_t size = RINGBUFF_LEN - ringBuff.Head;
for(i = 0; i < size; ++i)
{
*p = ringBuff.Ring_Buff[ringBuff.Head];
++ringBuff.Head;
++p;
}
ringBuff.Head = 0;
for(i = size; i < *len; ++i)
{
*p = ringBuff.Ring_Buff[ringBuff.Head];
++ringBuff.Head;
++p;
}
}
--count;
return 0;
}
使用fifo里面包的应用如下。
uint8_t uart1_rx_buff[300];
//uart1 rx len
uint16_t rx_len;
if(0==Read_RingBuff(uart1_rx_buff,&rx_len))
{
printf("uart_recv len = %d , date= %.*s\r\n",rx_len,rx_len,uart1_rx_buff);
....
if((bool)strstr((char*)uart1_rx_buff,"CMD:xxxx"))
{
}
//处理数据后,清理一下buff,不清理,strstr函数是可能找到这个字符的,引发bug。
memset(uart1_rx_buff,0,sizeof(uart1_rx_buff));
}
插入代码片