RS485调试记录

RS485实现相对简单,本次调试主要记录过程中遇到的一些串口问题(自己写出来大无语的bug)和改进办法。



目录

  • RS485调试记录
  • 前言
  • 一、RS485
  • 二、程序
  • 1.初始化串口2(RS485)
  • 2.中断服务函数(F767)和发送函数
  • 三、总结



前言

开发板:

需要两块板做测试,分别是野火的F767开发板和正点原子的F103

提示:两块板都有不一样的问题,下面会提及。


提示:以下是本篇文章正文内容,下面案例可供参考

一、RS485

  • RS485
    这里不着重介绍,主要是半双工通道,通过485芯片转换将串口接入接发通道。
  • 串口部分:
    1、使用的UART2,波特率统一调成115200。
    2、开接收中断和按键发送数据。
  • 环境:
    因为模拟产品环境,F767使用的Freertos测试,F103用测试板自带例程。

以下是野火F767板子 芯片贴图:

Android 调用RS485串口 rs485串口调试_串口通信


以下是正点原子F103板子 芯片贴图:

Android 调用RS485串口 rs485串口调试_单片机_02

  • 这里板子有错误需要纠正:
    野火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、可以开启故障标志位读取来对中断错误进行复位,否则程序会在中断中无限循环,导致跑飞。