基本的处理思路如下:
1. UART接收的处理方法
打开UART的接收中断,每收到一个字节就放到接收缓冲区,同时更新接收指针。当连续100ms没有收到接收字符,则认为本次帧接收完毕,置位帧接收完成标志,由主程序进行处理。
2. UART发送的处理方法
将需要发送的数据放到发送缓冲区,设置发送长度。然后发送第一个字节,并打开发送中断。在发送中断中判断是否已经发送了指定长度的数据。如果没有发送完成,则继续发送;发送完成,则关闭发送中断。
以上方法,说起来比较简单,主要是容错的处理,以及细节的考虑。以下我以STM32单片机为例进行说明。
1. 定义需要的变量
uint8_t gcRXDBuffer[50], gcRXDPointer, gcRXDLength; //接收的缓冲区、接收指针、接收的帧长度 uint8_t gcTXDBuffer[50], gcTXDPointer, gcTXDLength; //发送的缓冲区,发送指针,发送的长度 uint8_t gcInRXDMode, gcInTXDMode; //是否处于接收或发送的状态标志,在需要切入低功耗模式,或关闭其他功能时需要 |
2. 初始化UART寄存器,以LL库为例,HAL库也可以。其实这部分功能使用STM32CUBEMX自己生成就行,不用给自己编写。
/* USART2 init function */ static void MX_USART2_UART_Init(void) { LL_USART_InitTypeDef USART_InitStruct; LL_GPIO_InitTypeDef GPIO_InitStruct; /* Peripheral clock enable */ LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2); /**USART2 GPIO Configuration PA9 ------> USART2_TX PA10 ------> USART2_RX */ GPIO_InitStruct.Pin = LL_GPIO_PIN_9; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Alternate = LL_GPIO_AF_4; LL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = LL_GPIO_PIN_10; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Alternate = LL_GPIO_AF_4; LL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART2 interrupt Init */ NVIC_SetPriority(USART2_IRQn, 0); NVIC_EnableIRQ(USART2_IRQn); USART_InitStruct.BaudRate = 9600; USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; USART_InitStruct.StopBits = LL_USART_STOPBITS_1; USART_InitStruct.Parity = LL_USART_PARITY_NONE; USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16; LL_USART_Init(USART2, &USART_InitStruct); LL_USART_DisableOverrunDetect(USART2); LL_USART_ConfigAsyncMode(USART2); LL_USART_Enable(USART2); } |
/************************************ *不带流控的USART2函数**************** */ void uart2Init(void) { gcRXDPointer=0; gcRXDLength=0; gcTXDPointer=0; gcTXDLength=0; gcInRXDMode=0; gcInTXDMode=0; LL_USART_EnableIT_RXNE(USART2); //LL_USART_EnableIT_TXE(USART2); } |
4. 以上的初始化就完成了,下面看中断处理函数的编程方法。
/** * @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26. */ void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ uint8_t ucTemp; /* USER CODE END USART2_IRQn 0 */ /* USER CODE BEGIN USART2_IRQn 1 */ if(LL_USART_IsActiveFlag_RXNE(USART2)) { //如果是接收中断 gcUartCounter=0; //全局变量,每次收到一个自己就清零,到100ms没有更新认为接收完成。 ucTemp=LL_USART_ReceiveData8(USART2); gcRXDBuffer[gcRXDPointer++]=ucTemp; //接收到的数据放入缓冲区 gcInRXDMode=1; //当前在接收模式 } else if(LL_USART_IsActiveFlag_TXE(USART2)) { //如果是发送中断 gcTXDPointer++; if(gcTXDPointer<gcTXDLength) { //还没有发送完成,继续发送 LL_USART_TransmitData8(USART2, gcTXDBuffer[gcTXDPointer]); } else { //发送完成了,复位发送的变量 gcTXDLength=0; gcTXDPointer=0; LL_USART_DisableIT_TXE(USART2); //关闭发送中断 gcInTXDMode=0; //退出发送模式 delayms(1); //有流控时需要延时关闭流控,比如RTS,或者485中断的发送引脚。 RTS_HIGH(); } } /* USER CODE END USART2_IRQn 1 */ } |
5. 在系统的1ms SysTick中断中,判断是否100ms没有收到数据了。
gcUartCounter++; //这个变量在接收中断中不断清零 if(gcUartCounter>=100) { //100ms没有收到数据了,如果有数据,则打包帧 gcUartCounter=100; gcInRXDMode=0; //退出接收模式 if(gcRXDPointer>=3) { //根据协议长度,3是可以改动的。 gcRXDLength=gcRXDPointer; //将长度放入gcRXDLength,由主程序处理 gcRXDPointer=0; } } |
6. 以上程序中,接收数据部分就完成了。在主程序,或主业务中,判断gcRXDLength就知道是否有数据需要处理。
7. 在需要发送数据的时候:
/************************************ 清空发送缓冲区的函数,需要重新组织发送时调用。*/ void TxdClearBuff(void) { gcTXDPointer=0; gcTXDLength=0; } /************************************ 如果需要多次组织数据,就一次次调用Push函数,将发送数据送入发送缓冲区。*/ void TxdPushToBuff(uint8_t *buffer, unsigned int length) { memcpy(gcTXDBuffer+gcTXDLength, buffer, length); gcTXDLength+=length; } /************************************ 组织完数据后,调用TxdSend,进行发送。*/ void TxdSend(void) { LL_USART_TransmitData8(USART2, gcTXDBuffer[0]); gcTXDPointer=0; LL_USART_EnableIT_TXE(USART2); //打开发送中断。 gcInTXDMode=1; //进入发送模式。 } |
以上发送需要使用3个函数,有些复杂。如果你一次就能将数据组织完成,就可以写简单点。
在对实时处理要求更严格的时候,会在接收中断中直接处理帧头的判断(是否是正确的帧头,不是则接收指针直接清零),并根据帧长度字节,判断接收是否完成,然后直接调用通讯处理函数。这样的处理方法最快速,但封装不好,不易维护。不是必须的时候,不建议这么使用。
原文地址:http://blog.51cto.com/13719208/2112723