1. 前言
本例中的485驱动,基于标准库编写,不是HAL库,请大家注意。
最近搞嵌入式程序,踩了不少坑,这里统一记录一下。
2. 收获
1.串口通信,数据是一个字节一个字节的发送,对方收到的数据是放在了寄存器中(这个是由硬件控制的),软件在接收数据时,中断处理函数中再把寄存器中的数据取出来,一般是放在一个缓冲区里。因为写入的数据量 和写入时间不确定性 ,会导致软件在读取串口的时候读取的数据不完整 或者一次读取"一条半"这样的问题…
这时候软件上就要解决一个问题,如何保证收到的数据是完整的。
一般有下面几种方式:
1. 发送的数据长度是知道的,那么当接收到相应长度的数据时,认为接收完成了。
2. 数据本身有格式,比如数据头+数据+校验信息。那么每当收到下一个数据头时,认为上一个数据收完了。
3. 接收中断处理函数中,添加定时器,当超过一段时间,还没收到数据,认为一段数据收完了。具体设置多长时间,需要根据通信双方的需要,来进行设置。(本例中,使用的此方法)
数据发送到数据接收的切换,中间要加时延。今天测试程序时,发现一个问题:RS485串口发送完数据后,RS485串口接收,中间要加一定时延,才能保证接收的数据是完整的。但有一个问题:为啥要加这个时延?怎么量化计算?
3.程序
3.1 USART GPIO配置,工作模式配置
/** 函数名:_485_Config* 描述:USART GPIO配置,工作模式配置*/
void _485_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;/* config USART clock */RCC_AHB1PeriphClockCmd(_485_USART_RX_GPIO_CLK|_485_USART_TX_GPIO_CLK|_485_RE_GPIO_CLK, ENABLE);RCC_APB2PeriphClockCmd(_485_USART_CLK, ENABLE); // USART1,USART6属于APB2 RCC_APB2PeriphClockCmd, USART2,USART3属于APB1 RCC_APB1PeriphClockCmd// RCC_APB2PeriphClockCmd(_485_USART_CLK,ENABLE);
// GPIO_PinRemapConfig(_485_USART_CLK,ENABLE);/* Connect PXx to USARTx_Tx*/GPIO_PinAFConfig(_485_USART_RX_GPIO_PORT,_485_USART_RX_SOURCE, _485_USART_RX_AF);/* Connect PXx to USARTx_Rx*/GPIO_PinAFConfig(_485_USART_TX_GPIO_PORT,_485_USART_TX_SOURCE,_485_USART_TX_AF);/* USART GPIO config *//* Configure USART Tx as alternate function push-pull */GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Pin = _485_USART_TX_PIN ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(_485_USART_TX_GPIO_PORT, &GPIO_InitStructure);/* Configure USART Rx as alternate function */GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Pin = _485_USART_RX_PIN;GPIO_Init(_485_USART_RX_GPIO_PORT, &GPIO_InitStructure);/* 485收发控制管教 */GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_Pin = _485_RE_PIN ;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(_485_RE_GPIO_PORT, &GPIO_InitStructure);/* USART mode config */USART_InitStructure.USART_BaudRate = _485_USART_BAUDRATE;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;USART_Init(_485_USART, &USART_InitStructure); USART_Cmd(_485_USART, ENABLE);NVIC_Configuration();/* 使能串口接收中断 */USART_ITConfig(_485_USART, USART_IT_RXNE, ENABLE);GPIO_ResetBits(_485_RE_GPIO_PORT,_485_RE_PIN); //默认进入接收模式
}
3.2 GPIO宏定义
// 串口1 定义:PA9 Tx,PA10 Rx,PA8 DE
#define _485_USART USART1
#define _485_USART_CLK RCC_APB2Periph_USART1
#define _485_USART_BAUDRATE 115200#define _485_USART_RX_GPIO_PORT GPIOA
#define _485_USART_RX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define _485_USART_RX_PIN GPIO_Pin_10
#define _485_USART_RX_AF GPIO_AF_USART1
#define _485_USART_RX_SOURCE GPIO_PinSource10#define _485_USART_TX_GPIO_PORT GPIOA
#define _485_USART_TX_GPIO_CLK RCC_AHB1Periph_GPIOA
#define _485_USART_TX_PIN GPIO_Pin_9
#define _485_USART_TX_AF GPIO_AF_USART1
#define _485_USART_TX_SOURCE GPIO_PinSource9#define _485_RE_GPIO_PORT GPIOA
#define _485_RE_GPIO_CLK RCC_AHB1Periph_GPIOA
#define _485_RE_PIN GPIO_Pin_8#define _485_INT_IRQ USART1_IRQn
#define _485_IRQHandler USART1_IRQHandler
每一个外设,都有对应的地址。通过对这个地址操作,操作这些外设。
3.3 接收中断处理函数
定义了一个缓冲区来专门存放接收到的数据。
//串口中断处理函数---接收
#define UART_BUFF_SIZE 1024
volatile uint16_t uart_p = 0;
uint8_t uart_buff[UART_BUFF_SIZE];
extern uint8_t g_recvMotorData;void bsp_485_IRQHandler(void)
{// printf("come here1");//g_recvMotorData = 1;uint8_t clear; //Delay(115200/8); if(uart_p<UART_BUFF_SIZE){while(USART_GetITStatus(_485_USART, USART_IT_RXNE) != RESET){uart_buff[uart_p] = USART_ReceiveData(_485_USART);uart_p++;TIM_Cmd(BASIC_TIM, ENABLE); USART_ClearITPendingBit(_485_USART, USART_IT_RXNE);}}else{ //接收的数据长度超过UART_BUFF_SIZE 时,这次数据全部丢弃USART_ClearITPendingBit(_485_USART, USART_IT_RXNE);//clean_rebuff(); }//printf("uart_p = %u",uart_p);if (uart_p == 1) // 用来判断是否收到一帧数据 https://blog.csdn.net/ASKLW/article/details/79246786 uart_p == 1 USART_GetITStatus(_485_USART, USART_IT_IDLE) != RESET{
// clear = _485_USART->SR; //先读SR,再读DR才能完成idle中断的清零,否则一直进入中断
// clear = _485_USART->DR; uint16_t len = 0;
// char str[1024] ={0};
// sprintf(str, "%d", ii);
// Usart_SendByte(USART6, len);//Usart_SendString(USART6, get_rebuff(&len));
// printf("come here");g_recvMotorData = 1;}
}
这个中断处理函数,是与启动文件中的中断号对应的。当硬件感知到来了一个中断时,调用对应的中断处理函数。
3.4 发送和接收函数
***************** 发送一个字符 **********************/
// 使用单字节数据发送前要使能发送引脚,发送后要使能接收引脚
void _485_SendByte( uint8_t ch )
{/* 发送一个字节数据到USART1 */USART_SendData(_485_USART,ch);/* 等待发送完成 */while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET); }
/***************** 发送指定长度的字符串 **********************/
void _485_SendStr_length( uint8_t *str,uint32_t strlen )
{unsigned int k=0;_485_TX_EN(); // 使能发送数据 do {_485_SendByte( *(str + k) );k++;} while(k < strlen);/*加短暂延时,保证485发送数据完毕*/Delay(0xFFF);_485_RX_EN() ;// 使能接收数据
}/***************** 发送字符串 **********************/
void _485_SendString( uint8_t *str) //字符发的什么样,收的就是什么样子。
{unsigned int k=0;_485_TX_EN() ;// 使能发送数据do {_485_SendByte( *(str + k) );k++;} while(*(str + k)!='\0');/*加短暂延时,保证485发送数据完毕*/Delay_us(100);// 延时1ms_485_RX_EN() ;// 使能接收数据
}void _485_SendArray(uint8_t *arr, uint8_t len)
{_485_TX_EN();// 切换到发送模式if (len < 1){return;}for (uint8_t i = 0; i < len; i++){_485_SendByte(arr[i]);}_485_RX_EN();// 切换到接收模式
}/***************** 发送一个16位数,此计算结果低位在前高位在后 **********************/
void _485_SendHalfWord(uint16_t ch)
{uint8_t temp_h, temp_l;/* 取出高八位 */temp_h = (ch&0XFF00)>>8;/* 取出低八位 */temp_l = ch&0XFF;/* 发送低八位 */_485_SendByte(temp_l);while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET); /* 发送高八位 */_485_SendByte(temp_h);while (USART_GetFlagStatus(_485_USART, USART_FLAG_TXE) == RESET);
}
3.4 主函数
_485_SendArray(cmd_6064, 8); Delay_us(100);// 加1ms时延;RS485串口发送完数据后,为了保证接收的数据完整性,中间需要加一定的时延。if (g_timer) {g_timer = 0;_485_RX_EN();uint16_t len =0;char * pbuf = get_rebuff(&len);for (int i = 0; i < len; i++){printf("%c", pbuf[i]);}clean_rebuff();// 用完之后,清空缓冲区}
里面定时器相关内容,略写了。
参考
- modbus串口编程接收到的数据不完整问题
- 本文章将会展示至 里程碑专区 ,您也可以在 专区 内查看其他创作者的纪念日文章