RS485调试记录
RS485实现相对简单,本次调试主要记录过程中遇到的一些串口问题(自己写出来大无语的bug)和改进办法。
目录
- RS485调试记录
- 前言
- 一、RS485
- 二、程序
- 1.初始化串口2(RS485)
- 2.中断服务函数(F767)和发送函数
- 三、总结
前言
开发板:
需要两块板做测试,分别是野火的F767开发板和正点原子的F103
提示:两块板都有不一样的问题,下面会提及。
提示:以下是本篇文章正文内容,下面案例可供参考
一、RS485
- RS485
这里不着重介绍,主要是半双工通道,通过485芯片转换将串口接入接发通道。 - 串口部分:
1、使用的UART2,波特率统一调成115200。
2、开接收中断和按键发送数据。 - 环境:
因为模拟产品环境,F767使用的Freertos测试,F103用测试板自带例程。
以下是野火F767板子 芯片贴图:
以下是正点原子F103板子 芯片贴图:
- 这里板子有错误需要纠正:
野火F767的485芯片上,RE脚是由PB8接入的,而案例程序是PD11 !! 非常坑…注意修改成PB8
正点原子的F103板上的TX和RX是指对象的TX和RX而并非串口的TX/RX,用示波器检测的时候注意测量点不要错。
二、程序
1.初始化串口2(RS485)
初始化如下(示例):
/**
* @brief RS485 UART2初始化函数.
* @param u32 bound -波特率
* @retval None
*/
void RS485_USART_Init(uint32_t bound)
{
//UART 初始化设置
UART2_Handler.Instance = USART2; //USART2
UART2_Handler.Init.BaudRate = bound; //波特率
UART2_Handler.Init.WordLength = UART_WORDLENGTH_8B; //字长为8位数据格式
UART2_Handler.Init.StopBits = UART_STOPBITS_1; //一个停止位
UART2_Handler.Init.Parity = UART_PARITY_NONE; //无奇偶校验位
UART2_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; //无硬件流控
UART2_Handler.Init.Mode = UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART2_Handler); //HAL_UART_Init()会使能UART2
}
MSP初始化如下(示例):
/*************************** BSP_USART2 板级定义**************************************/
#define RS485_USARTx USART2
#define RS485_USART_CLK_ENABLE() __USART2_CLK_ENABLE()
/* usart2_TX PD5 复用 */
#define RS485_USART_TX_GPIO_CLK() __HAL_RCC_GPIOD_CLK_ENABLE()
#define RS485_USART_TX_PORT GPIOD
#define RS485_USART_TX_PIN GPIO_PIN_5
#define RS485_USART_TX_AF GPIO_AF7_USART2 //复用
/* usart2_RX PD6 复用 */
#define RS485_USART_RX_GPIO_CLK() __HAL_RCC_GPIOD_CLK_ENABLE()
#define RS485_USART_RX_PORT GPIOD
#define RS485_USART_RX_PIN GPIO_PIN_6
#define RS485_USART_RX_AF GPIO_AF7_USART2 //复用
/* RE */
#define RS485_USART_RE_GPIO_CLK() __HAL_RCC_GPIOB_CLK_ENABLE()
#define RS485_USART_RE_PORT GPIOB
#define RS485_USART_RE_PIN GPIO_PIN_8
#define RS485_USART_IRQ USART2_IRQn
/**
* @brief UART
* 底层初始化,时钟使能,引脚配置,中断配置。此函数会被HAL_UART_Init()调用
* @param huart - 串口句柄
* @retval None
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_Initure;
if(huart->Instance == RS485_USARTx)
{
RS485_USART_CLK_ENABLE();
RS485_USART_TX_GPIO_CLK();
RS485_USART_RX_GPIO_CLK();
RS485_USART_RE_GPIO_CLK();
GPIO_Initure.Pin = RS485_USART_TX_PIN;
GPIO_Initure.Mode = GPIO_MODE_AF_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed= GPIO_SPEED_FAST;
GPIO_Initure.Alternate = RS485_USART_TX_AF;
HAL_GPIO_Init(RS485_USART_TX_PORT,&GPIO_Initure);
GPIO_Initure.Pin = RS485_USART_RX_PIN;
GPIO_Initure.Alternate = RS485_USART_RX_AF;
HAL_GPIO_Init(RS485_USART_RX_PORT,&GPIO_Initure);
GPIO_Initure.Mode = GPIO_MODE_OUTPUT_PP; //输出
GPIO_Initure.Pin = RS485_USART_RE_PIN;
GPIO_Initure.Pull = GPIO_PULLUP; //上拉
HAL_GPIO_Init(RS485_USART_RE_PORT,&GPIO_Initure);
HAL_NVIC_EnableIRQ(RS485_USART_IRQ); //使能USART1中断通道
__HAL_UART_ENABLE_IT(&UART2_Handler,UART_IT_RXNE);
__HAL_USART_ENABLE_IT(&UART2_Handler,USART_IT_ERR); /*开启故障中断,目的是为了故障后进入中断清楚,防止程序卡死*/
__HAL_USART_ENABLE_IT(&UART2_Handler,USART_IT_IDLE);
HAL_NVIC_SetPriority(RS485_USART_IRQ,5,5); //抢占优先级5,子优先级5
}
}
这里中断开的有三个,分别是
1、UART_IT_RXNE (必开)
2、USART_IT_ERR(选用,后面会提及作用)
3、USART_IT_IDLE(选用,后面会提及作用)
2.中断服务函数(F767)和发送函数
F103的发送代码代码如下(示例):
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)//KEY0按下,发送一次数据
{
for(i=0;i<5;i++)
{
rs485buf[i]=cnt+i;//填充发送缓冲区
LCD_ShowxNum(30+i*32,190,rs485buf[i],3,16,0X80); //显示数据
}
RS485_Send_Data(rs485buf,5);//发送5个字节
}
}
F767的串口中断代码如下(示例):
/**
* @brief USART2中断服务函数.
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
uint8_t ch;
if(__HAL_UART_GET_IT (&UART2_Handler, UART_IT_RXNE ) != RESET) //接收中断
{
ch = USART2->RDR;
printf("CH = %d\r\n",ch);
}
}
注意:该中断程序为第一次测试时用,并非最完善版本
上述的程序测试过程中,发现F767的所有进程都没有响应,疑似程序跑飞。
后来发现是因为串口中断不断地触发,导致RTOS的其他进程无法响应。虽然串口进入中断但并未进入RXNE中,说明有其他的中断触发源。百度发现有其他网友也出现这种情况,原因是消息故障导致。在开启RXNE的时候就会同时开启故障中断,如是增加4个中断检测,如下:
UART_FLAG_ORE //故障
UART_FLAG_PE
UART_FLAG_FE
UART_FLAG_NE
在中断源经过测试后发现确实有检测到该4位标志位 置位的现象。经查标志位信息可大概清除故障原因:
罪魁祸首就是 UART_FLAG_FE 标志位 其次就是 UART_FLAG_NE 标志位
UART_FLAG_FE:文档说明
FE: 帧错误 (Framing error)
数据寄存器 (USART_DR) 位1
当检测到同步错位,过多的噪声或者检测到断开符,该位被硬件置位。由软件序列将其清零(先 读USART_SR,再读USART_DR)。
0:没有检测到帧错误;
1:检测到帧错误或者break符。
注意:该位不会产生中断,因为它和RXNE一起出现,硬件会在设置RXNE标志时产生中断。如果当前传输的数据既产生了帧错误,又产生了过载错误,硬件还是会继续该数据的传输,并 且只设置ORE标志位。
在多缓冲区通信模式下,如果设置了EIE位,则设置FE标志时会产生中断
UART_FLAG_NE:文档说明
NE: 噪声错误标志 (Noise error flag)
数据寄存器 (USART_DR) 位2 在接收到的帧检测到噪音时,由硬件对该位置位。由软件序列对其清玲(先读USART_SR,再读USART_DR)。
0:没有检测到噪声;
1:检测到噪声。
注意:该位不会产生中断,因为它和RXNE一起出现,硬件会在设置RXNE标志时产生中断。
在多缓冲区通信模式下,如果设置了EIE位,则设置NE标志时会产生中断。
原文链接:
测试发现:发送的5个字节数据,只有1个字节能被有效接收,之后马上进入过载错误中断中。
- 结论:罪魁祸首是在中断模式中使用了Printf():
因为数据在连续接收的过程中,在中断中使用了打印函数,导致第1个字节后面的数据无法被及时读取导致fifo过载,进入错误中断。
函数正确修改后如下:
uint8_t arry1[10];
/**
* @brief USART2中断服务函数.
* @param None
* @retval None
*/
void USART2_IRQHandler(void)
{
uint8_t ch;
static uint8_t n =0;
if(__HAL_UART_GET_FLAG(&UART2_Handler, USART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&UART2_Handler);
/*此处利用总线空闲,来做打印测试,在正式的项目中,切勿在串口中使用打印函数*/
for(int x =0 ;x<10;x++)
{
printf("%d ",arry1[x]);
}
n =0;
}
if(__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_IT(&UART2_Handler,UART_CLEAR_OREF);
ch = USART2->RDR;
}
if(__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_PE) != RESET)
{
__HAL_UART_CLEAR_IT(&UART2_Handler,UART_CLEAR_PEF);
ch = USART2->RDR;
}
if(__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_FE) != RESET)
{
__HAL_UART_CLEAR_IT(&UART2_Handler,UART_CLEAR_FEF);
ch = USART2->RDR;
}
if(__HAL_UART_GET_FLAG(&UART2_Handler, UART_FLAG_NE) != RESET)
{
__HAL_UART_CLEAR_IT(&UART2_Handler,UART_CLEAR_NEF);
ch = USART2->RDR;
}
if(__HAL_UART_GET_IT (&UART2_Handler, UART_IT_RXNE ) != RESET) //接收中断
{
arry1[n] = USART2->RDR;
n++;
}
}
此处利用总线空闲,来做打印测试,在正式的项目中,切勿在串口中使用打印函数
三、总结
1、在使用串口时,切勿在串口中断中使用打印函数,如测试时方便调试,使用必须要注意不能影响接收数据的响应速度,可在总线空闲时打印,或在其他主函数任务中打印数据。
2、可以开启故障标志位读取来对中断错误进行复位,否则程序会在中断中无限循环,导致跑飞。