适用于所有STM32单片机的串口不定长接收

STM32 串口 不定长接收 DMA
donatello
发布时间: 2018-05-08
阅读: 3355

在使用STM32的时候,使用串口外设的场合是非常常见的,串口发送我想大家都非常熟悉了,直接重写fputc函数就可以了:

int fputc(int ch,FILE *f)
{
while(!(USART3->SR&UART_FLAG_TXE));//1
USART3->DR=ch;//2
return ch;
}

其中语句1和语句2是由单片机型号决定的,这两行语句必须是该单片机发送一个字符的函数,除了上述的寄存器法发送以外,还有一个HAL库发送法:
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart3,&ch,1,100);
return ch;
}
效果完全一样。

    串口发送函数的简单介绍就到这,接下来介绍重点:串口不定长接收。不定长接收大家想必都听说过了,实现方法也多种多样,如用软件方法实现的队列+结束码法,这里我介绍一个适用于所有STM32单片机的方法,用到了STM32单片机的两个重要硬件外设:串口空闲中断和DMA。这个方法实现的原理是这样:每当STM32从串口处收到一帧数据即一次数据时,会触发一次空闲中断(前提是提前使能空闲中断了),这一帧数据长度可以是任意的,也不需要识别任何结束码。如果我们提前开启了串口DMA,那么串口的数据就会放在DMA缓冲区里面,缓冲区总长度s减去缓冲区中没有数据部分的长度b就是串口不定长接收数据的长度a。其中s的长度可以由用户自由设置,一般是65535,b的长度可以通过访问寄存器获得,a就只能通过s-b获得了。串口在中断接收中的作用,不是为了减少CPU负担这种如此浅显的目的,而是充当串口接收数据的一个容器,这个容器,我们既知道它容量多少(总容量),也知道它还有多少空间是没有水的(缓存区后部没用到的部分),那么自然就知道它到底装了多少水(串口接收到多少数据)。
1.jpg
    这个方法适用于所有型号的STM32单片机,但是我总不可能每个型号都试一次,那么,我就挑三个最常用的系列来试:F103/F4/L4,为了写这篇文章,我拿出三个开发板来作为测试用例,分别是F103C8T6、L476RGT6、F469NIH6,并且,对应的软件库是HAL库:
100.jpg
    要启用串口接收DMA,就必须打开CubeMX查看每个系列的串口接收DMA:
2.jpg3.jpg4.jpg
可以看出,STM32每个系列的串口接收DMA通道都不同(F103/L476/F469),其中L476/L486/L496的通道是一样的,串口2的接收通道都是通道6,F103系列则是通道5,而F4系列则比较奇葩,F4在DMA中引入了流(Stream)的概念,CubeMX里面配置的窗口也是只显示DMA的流,具体是什么通道,就只能通过生成的工程里面详细看了。

首先看看F103C8T6的串口接收DMA初始化函数:



static void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart1);

__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_NVIC_SetPriority(USART1_IRQn,0,0);
  HAL_NVIC_EnableIRQ(USART1_IRQn);

}

static void MX_DMA_Init(void) 
{
  __HAL_RCC_DMA1_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn,0,0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);

}

static void MX_GPIO_Init(void)
{

  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

}


void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{

  GPIO_InitTypeDef GPIO_InitStruct;
    __HAL_RCC_USART1_CLK_ENABLE();
  
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    hdma_usart1_rx.Instance = DMA1_Channel5;
    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_LOW;
    HAL_DMA_Init(&hdma_usart1_rx);

    __HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
}


这里最关键的地方,要使能串口空闲中断以及配置串口中断优先级:


__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
HAL_NVIC_SetPriority(USART1_IRQn,0,0);
  HAL_NVIC_EnableIRQ(USART1_IRQn);

然后编写串口1的中断服务函数:



void USART1_IRQHandler()
{
	uint32_t temp;
	if(USART1==huart1.Instance)
	{	
		if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
		{
			__HAL_UART_CLEAR_IDLEFLAG(&huart1);
			HAL_UART_DMAStop(&huart1);
			temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
			rx_len=BUFFERSIZE-temp; 
			recv_end_flag=1;
		}
	}
}


最后是中断查询函数,并在主程序中调用:



#define BUFFERSIZE 255 uint8_t rx_buf[BUFFERSIZE]; uint8_t recv_end_flag=0,rx_len;


void UART1_DMA_Get()
{
	if(recv_end_flag==1)
	{
		recv_end_flag=0;
		printf("帧长度:%d 帧内容:%s\n",rx_len,rx_buf);
	}
	HAL_UART_Receive_DMA(&huart1,(uint8_t*)rx_buf,BUFFERSIZE);
}


可以看到,整形数temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx),也就是取DMA容器(缓存区)中没有用到的后部的长度,而BUFFERSIZE则是我们自己定义的缓冲区总长,两者相减长度就是串口接收帧长度rx_len,数据流就是通过DMA传输的数组rx_buf。

看看效果:


5.jpg

看看L476的代码,与F103差异不大:


static void MX_USART2_UART_Init(void)
{

  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
	HAL_UART_Init(&huart2);
	
	__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
	HAL_NVIC_SetPriority(USART2_IRQn,0,0);
  HAL_NVIC_EnableIRQ(USART2_IRQn);

}

static void MX_DMA_Init(void) 
{
  __HAL_RCC_DMA1_CLK_ENABLE();

  HAL_NVIC_SetPriority(DMA1_Channel6_IRQn,0,0);
  HAL_NVIC_EnableIRQ(DMA1_Channel6_IRQn);

}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
}

void USART2_IRQHandler()
{
	uint32_t temp;
	if(USART2==huart2.Instance)
	{	
		if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE))
		{
			__HAL_UART_CLEAR_IDLEFLAG(&huart2);
			HAL_UART_DMAStop(&huart2);
			temp=__HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
			rx_len=BUFFERSIZE-temp; 
			recv_end_flag=1;
		}
	}
}


void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{

  GPIO_InitTypeDef GPIO_InitStruct;
    __HAL_RCC_USART2_CLK_ENABLE();
  
    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    hdma_usart2_rx.Instance = DMA1_Channel6;
    hdma_usart2_rx.Init.Request = DMA_REQUEST_2;
    hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_rx.Init.Mode = DMA_NORMAL;
    hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
    HAL_DMA_Init(&hdma_usart2_rx);

    __HAL_LINKDMA(huart,hdmarx,hdma_usart2_rx);


}


看看效果:


6.jpg

看看F469的代码,大同小异:


static void MX_USART3_UART_Init(void)
{

  huart3.Instance = USART3;
  huart3.Init.BaudRate = 115200;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart3);

__HAL_UART_ENABLE_IT(&huart3,UART_IT_IDLE);
HAL_NVIC_SetPriority(USART3_IRQn,0,0);
  HAL_NVIC_EnableIRQ(USART3_IRQn);

}


static void MX_DMA_Init(void) 
{
  __HAL_RCC_DMA1_CLK_ENABLE();

  HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);

}

static void MX_GPIO_Init(void)
{
  __HAL_RCC_GPIOB_CLK_ENABLE();

}

void USART3_IRQHandler()
{
uint32_t temp;
if(USART3==huart3.Instance)
{
if(__HAL_UART_GET_FLAG(&huart3,UART_FLAG_IDLE))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart3);
HAL_UART_DMAStop(&huart3);
temp=__HAL_DMA_GET_COUNTER(&hdma_usart3_rx);
rx_len=BUFFERSIZE-temp; 
recv_end_flag=1;
}
}
}

void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{

  GPIO_InitTypeDef GPIO_InitStruct;
    __HAL_RCC_USART3_CLK_ENABLE();
  
    GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART3;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);


    hdma_usart3_rx.Instance = DMA1_Stream1;
    hdma_usart3_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart3_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart3_rx.Init.Mode = DMA_NORMAL;
    hdma_usart3_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_usart3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    HAL_DMA_Init(&hdma_usart3_rx);

    __HAL_LINKDMA(huart,hdmarx,hdma_usart3_rx);

}


看看效果:

7.jpg

总结:实现这个串口不定长接收,最大的功劳要给DMA外设,因为所有工作都是围绕DMA缓存区进行的,但是,目前此方法的实现还是需要依赖HAL库,后续我会继续探索,争取使用效率更高的LL库实现。


原创作品,未经权利人授权禁止转载。详情见转载须知 举报文章

点赞 (0)
donatello
评论(0)

登录后可评论,请 登录注册

相关文章推荐
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回

我要举报该内容理由

×
请输入您举报的理由(50字以内)