STM32串口接收不定长数据
1、使用定时器中断的方式接收不定长数据
在这篇博客中有关于USART的一些基础知识,以及使用定时器中断的方式接收不定长数据的方法
2、使用IDLE空闲中断和DMA的方式接收不定长数据
什么是串口空闲中断IDLE呢?
当我们通过电脑向串口发送消息的时候,会形成一连串连续的数据帧,此时串口不是空闲的,当数据发送完成的时候,则会检测到串口空闲,形成一个空闲帧,此时则会产生一个空闲中断,那么就可以在空闲中断中,处理当前接收到的数据。
使用空闲中断的方法接受不定长数据相较于使用定时器的方法提升了太多太多,首先使用定时器的方法会占用一个定时器的资源,并且对于不同的应用情况,对定时的时间也有要求,即根据接收数据量的大小来调节定时时间。而我们通过串口空闲中断的方法,可以节省一个定时器资源,并且对于任意数据量,不用考虑当前数据量的大小,只要当前的数据发送完成则会产生一个空闲中断,否则表示数据正在传输,这样判断一条数据是否发送完成就只需要考虑是否产生了空闲中断。
变量以及一些宏定义:
uint8_t RxBuffer[20];
#define countof(a) (sizeof(a) / sizeof(*(a)))
#define RxBufferSize (countof(RxBuffer) - 1)
相关的时钟配置:
/**
* @brief Configures the different system clocks.
* @param None
* @retval None
*/
void UART_DMA_RCC_Configuration(void)
{
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
/* Enable USART2 Clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
串口2GPIO配置:
/**
* @brief Configures the different GPIO ports.
* @param None
* @retval None
*/
void UART_DMA_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART2 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
串口2中断向量配置:
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval None
*/
void UART_DMA_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the USARTz Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
DMA的相关配置:
DMA配置为外设到存储器的方式,所以在这里DMA的方向应该选择为外设作为源地址的方式即代码中体现的DMA_DIR_PeripheralSRC。
关于DMA的通道选择需要遵循下表:我们这里使用到的是USART2_RX,所以应该使用DMA1_Channel6
/**
* @brief Configures the DMA.
* @param None
* @retval None
*/
void UART_DMA_DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* USART2_Rx_DMA_Channel (triggered by USART2 Rx event) Config */
DMA_DeInit(DMA1_Channel6);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)RxBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = RxBufferSize;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
}
串口的初始化:
void UART_DMA_INIT(void)
{
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
UART_DMA_RCC_Configuration();
/* NVIC configuration */
UART_DMA_NVIC_Configuration();
/* Configure the GPIO ports */
UART_DMA_GPIO_Configuration();
/* Configure the DMA */
UART_DMA_DMA_Configuration();
/* USART2 configuration -------------------------------------------*/
/* USART2 configured as follow:
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
/* Enable USART2 DMA TX request */
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
/* Enable the USART2 Receive Interrupt */
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
/* Enable USART2 */
USART_Cmd(USART2, ENABLE);
/* Enable USART2 DMA RX Channel */
DMA_Cmd(DMA1_Channel6, ENABLE);
}
重定向printf:
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET)
{}
return ch;
}
中断处理:
/**
* @brief This function handles USART2 global interrupt request.
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
uint8_t Usart2_Rec_Cnt = 0;
if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
USART_ReceiveData(USART2);//读取数据注意:这句必须要,否则不能够清除中断标志位。
Usart2_Rec_Cnt =RxBufferSize-DMA_GetCurrDataCounter(DMA1_Channel6); //算出接本帧数据长度
//***********帧数据处理函数************//
printf ("Thelenght:%d\r\n",Usart2_Rec_Cnt);
printf ("The data:%s\r\n",RxBuffer);
printf ("Over! \r\n");
memset(RxBuffer,0,sizeof(RxBuffer));
//*************************************//
USART_ClearITPendingBit(USART2,USART_IT_IDLE); //清除中断标志
DMA_Cmd(DMA1_Channel6, DISABLE ); //关闭USART2 RX DMA1所指示的通道
DMA_SetCurrDataCounter(DMA1_Channel6,RxBufferSize); //DMA通道的DMA缓存的大小
DMA_Cmd(DMA1_Channel6, ENABLE); //打开USART2 RX DMA1所指示的通道
}
}
3、使用IDLE空闲中断和RXNE中断的方式接收不定长数据
这个例子和上一个例子完成的功能是一样的,但相较于上一个例子的区别是,而是使用RXNE中断来处理数据,这个例子没有使用到DMA,配置更加简便。假如我们发送数据"hello world",那么将产生11次RXNE中断(因为数据的长度为11),然后接着一次IDLE中断。
#include "stdio.h"
#include "string.h"
uint8_t RxBuffer[20];
_Bool uart2_idle_flag = 0;
uint8_t RxCnt = 0;
#define countof(a) (sizeof(a) / sizeof(*(a)))
#define RxBufferSize (countof(RxBuffer) - 1)
/**
* @brief Configures the different system clocks.
* @param None
* @retval None
*/
void UART_IDLE_RXNE_RCC_Configuration(void)
{
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
/* Enable USART2 Clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
}
/**
* @brief Configures the different GPIO ports.
* @param None
* @retval None
*/
void UART_IDLE_RXNE_GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure USART2 Rx as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART2 Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval None
*/
void UART_IDLE_RXNE_NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Enable the USART2 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void UART_IDLE_RXNE_INIT(void)
{
USART_InitTypeDef USART_InitStructure;
/* System Clocks Configuration */
UART_IDLE_RXNE_RCC_Configuration();
/* NVIC configuration */
UART_IDLE_RXNE_NVIC_Configuration();
/* Configure the GPIO ports */
UART_IDLE_RXNE_GPIO_Configuration();
/* USART2 configuration -------------------------------------------*/
/* USART2 configured as follow:
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
/* Configure USART2 */
USART_Init(USART2, &USART_InitStructure);
/* Enable the USART2 Receive Interrupt */
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
/* Enable USART2 */
USART_Cmd(USART2, ENABLE);
}
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(USART2, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET)
{}
return ch;
}
/**
* @brief This function handles USART2 global interrupt request.
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
RxBuffer[RxCnt++] = USART_ReceiveData(USART2);
// USART_SendData(USART2, (uint8_t) RxBuffer[RxCnt - 1]);
// /* Loop until the end of transmission */
// while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET)
// {}
}
else if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
USART_ReceiveData(USART2);// 读取数据注意:这句必须要,否则不能够清除中断标志位。
//***********帧数据处理函数************//
printf ("Thelenght:%d\r\n",RxCnt);
printf ("The data:%s\r\n",RxBuffer);
printf ("Over! \r\n");
memset(RxBuffer,0,sizeof(RxBuffer));
RxCnt = 0;
USART_ClearITPendingBit(USART2,USART_IT_IDLE); //清除中断标志
}
}