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串口编程接收到的数据不完整问题
- 本文章将会展示至 里程碑专区 ,您也可以在 专区 内查看其他创作者的纪念日文章