【STM32】基于标准库和HAL库分别实现RS485通信方式

一、简介

发展历程:

  • UART 的产生:UART(通用异步收发器)的发展与串行通信的发展密切相关。早期的串行通讯主要用于电信和无线电设备,采用莫尔斯电码和调幅(AM),这些系统通常是定制化的,缺乏标准化的硬件模块。随着串行通信的普及,20 世纪 60 年代末,UART 应运而生。
  • RS-232 的出现:UART 产生后,需要有相应的标准来规范其硬件的电气特性和协议,使串行通信更加规范化。1962 年,EIA 发布了 RS-232 标准,命名为 EIA-232-E,作为工业标准,以保证不同厂家产品之间的兼容。RS-232 采用单端信号传送,存在共地噪声和不能抑制共模干扰等问题,一般用于 20 米以内的通信,最大位速率为 20Kb/s。
  • RS-422 的发展:由于 RS-232 存在通信距离短、速率低的缺点,为了改进这些不足,EIA 在 RS-232 的基础上发展出了 RS-422 标准。RS-422 定义了一种平衡通信接口,将传输速率提高到 10Mbps,传输距离延长到 1200 米(速率低于 100Kbps 时),并允许在一条平衡总线上连接最多 10 个接收器。它是一种单机发送、多机接收的单向、平衡传输规范,被命名为 TIA/EIA-422-A 标准。
  • RS-485 的诞生:为了进一步扩展应用范围,EIA 于 1983 年在 RS-422 基础上制定了 RS-485 标准。RS-485 增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为 TIA/EIA-485-A 标准。RS-485 为半双工通信,用于多站互连时可节省信号线,便于高速、远距离传送,允许最多并联 32 台驱动器和 32 台接收器。

       需要注意的是,按照技术发展的先后顺序,应该是 UART->RS-232->RS-422->RS-485。

       RS485和RS422电路原理基本相同,都是以差动方式发送和接受,不需要数字地线。差动工作是同速率条件下传输距离远的根本原因,这正是二者与RS232的根本区别,因为RS232是单端输入输出,双工工作时至少需要数字地线、发送线和接受线三条线(异步传输),还可以加其它控制线完成同步等功能。RS422通过两对双绞线可以全双工工作收发互不影响,而RS485只能半双工工作,发收不能同时进行,但它只需要一对双绞线。

       RS-232、RS-422与RS-485标准只对接口的电气特性(电压,阻抗)做出规定,而不涉及接插件、电缆或协议,在此基础上用户可以建立自己的高层通信协议。

RS485:

       与CAN类似,RS-485是一种工业控制环境中常用的通讯协议,它具有抗干扰能力强、传输距离远的特点。RS-485通讯协议由RS-232协议改进而来, 协议层不变,只是改进了物理层,因而保留了串口通讯协议应用简单的特点。(参考链接

RS485通信网络示意图

       对比CAN通讯网络,可发现它们的网络结构组成是类似的,每个节点都是由一个通讯控制器和一个收发器组成,在RS-485通讯网络中, 节点中的串口控制器使用RX与TX信号线连接到收发器上,而收发器通过差分线连接到网络总线,串口控制器与收发器之间一般使用TTL信号传输, 收发器与总线则使用差分信号来传输。发送数据时,串口控制器的TX信号经过收发器转换成差分信号传输到总线上,而接收数据时, 收发器把总线上的差分信号转化成TTL信号通过RX引脚传输到串口控制器中。

       RS-485通讯网络的最大传输距离可达1200米,总线上可挂载128个通讯节点,而由于RS-485网络只有一对差分信号线,它使用差分信号来表达逻辑, 当AB两线间的电压差为-6V~-2V时表示逻辑1,当电压差为+2V~+6V表示逻辑0,在同一时刻只能表达一个信号,所以它的通讯是半双工形式的, 它与RS-232通讯协议的特性对比见图。

       RS-485与RS-232的差异只体现在物理层上,它们的协议层是相同的,也是使用串口数据包的形式传输数据。而由于RS-485具有强大的组网功能, 人们在基础协议之上还制定了MODBUS协议,被广泛应用在工业控制网络中。此处说的基础协议是指前面串口章节中讲解的, 仅封装了基本数据包格式的协议(基于数据位),而MODBUS协议是使用基本数据包组合成通讯帧格式的高层应用协议(基于数据包或字节)。 感兴趣的读者可查找MODBUS协议的相关资料了解。

       由于RS-485与RS-232的协议层没有区别,进行通讯时,同样是使用STM32的USART外设作为通讯节点中的串口控制器, 再外接一个RS-485收发器芯片把USART外设的TTL电平信号转化成RS-485的差分信号即可。

实际的RS485收发器的硬件原理图:

SP3485 是一款高性能、低功耗的 RS-485/RS-422 收发器。其引脚定义如下:

  1. RO(接收输出):将从 RS485 中接收到的差分信号转换为数字信号,并输出到 USART 的 RX 引脚。(RO接MCU的RX)
  2. RE(接收使能):低电平有效,即当该引脚为低电平时,使能接收功能,高电平则禁用接收功能。(MCU对RE输出低电平就激活收发器的接收功能,反之禁用接收)
  3. DE(发送使能):高电平有效,当该引脚为高电平时,使能发送功能,低电平则禁用发送功能。(MCU对DE输出高电平就激活收发器的发送功能,反之禁用发送)
  4. DI(发送输入):将 USART 输入的数字信号发送到 RS485,将其转换为差分信号后输出到总线。(DI接MCU的TX)
  5. GND(接地):连接到电源地。
  6. A(发送器输出 / 接收器输入正端):用于 RS-485 差分信号传输的正极引脚。(半双工)
  7. B(发送器输出 / 接收器输入负端):用于 RS-485 差分信号传输的负极引脚。(半双工)
  8. VCC(电源):芯片供电引脚,通常接 + 3.3V 电源。

       单片机通信一般是TTL电平,而外接设备如果是485设备,通信的电平就是485电平,电平是不一样的,所以两者不能直接相接。中间需要电平转换的芯片来转换一下,所以就有了我们的485芯片。因为485通信是半双工的,所以又把485芯片叫做半双工收发器。而SP3485芯片就是一款非常经典的低功耗半双工收发器的芯片,满足RS485串行协议要求。

RS485 通信采用差分信号传输,通常情况下只需要两根信号线就可以进行正常的通信。
在差分信号中,逻辑0逻辑1是用两根信号线(A+和B-)的电压差来表示。

  • 逻辑 1:两根信号线(A+和B-)的电压差在 +2V~+6V 之间。
  • 逻辑 0:两根信号线(A+和B-)的电压差在 -2V~-6V 之间。

串行接口RS485简介-视频链接

二、工程创建

1.基于STM32CubeMX的HAL库实现RS485通讯

(1)打开STM32CubeMX进行工程创建,首先,开启外部高速时钟

(2)开启SWD

(3)开启串口1,作为MCU链接RS485的串口接口,并且开启全局中断

(4)开启一个GPIO-PA7作为RS485的控制引脚

(5)PB8/9 作为OLED驱动引脚,PB1/11作为按键来控制发送部分

(6)配置时钟树

(7)工程配置

(8)代码编写

接收部分:

bsp_RS485.h

#ifndef __BSP_485_H
#define __BSP_485_H

#include "stm32f1xx_hal.h"

#define USART1_RX_Byte_Size 1
#define USART1_RxBuffer_Size 256

#define RS485DIR_TX_MODE HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);//定义我们的控制引脚为发送状态
#define RS485DIR_RX_MODE HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);//定义我们的控制引脚为接收状态

extern uint16_t USART1_RX_Flag;
extern uint8_t USART1_RX_Byte[USART1_RX_Byte_Size];
extern uint8_t USART1_RxBuffer[USART1_RxBuffer_Size];

void RS485_Init(void);

#endif //__BSP_485_H

bsp_RS485.c

#include "bsp_RS485.h"

#include "usart.h"

uint16_t USART1_RX_Flag = 0;

uint8_t USART1_RX_Byte[USART1_RX_Byte_Size]; //字节接受
uint8_t USART1_RxBuffer[USART1_RxBuffer_Size]; //缓冲区


uint8_t RX_index = 0;

void RS485_Init(void)
{
	//RS485DIR_TX_MODE;
	RS485DIR_RX_MODE;
	HAL_UART_Receive_IT(&huart1, (unsigned char *)USART1_RX_Byte, sizeof(USART1_RX_Byte));//初始化接收中断使能
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	
	if(huart->Instance==USART1)//如果是串口1
	{
//		if((USART1_RX_Flag&0x8000)==0)//接收未完成
//		{
//			if(USART1_RX_Flag&0x4000)
//			{
//				if(USART1_RX_Byte[0]!=0x0a)USART1_RX_Flag=0;//接收错误,重新开始
//				else USART1_RX_Flag|=0x8000;	//收到0x0a 接收完成了 
//			}
//			else 
//			{	
//				//判断是否收到0X0D
//				if(USART1_RX_Byte[0]==0x0d)USART1_RX_Flag|=0x4000; //收到0X0D USART1_RX_Flag为0x4000
//				else //没有收到0X0D
//				{
//					USART1_RxBuffer[USART1_RX_Flag&0X3FFF]=USART1_RX_Byte[0] ;
//					USART1_RX_Flag++;
//					if(USART1_RX_Flag>(USART1_RxBuffer_Size-1))USART1_RX_Flag=0;//接收数据错误,重新开始接收	  
//				}		 
//			}
//		}
		
		USART1_RX_Flag = 1;
		USART1_RxBuffer[RX_index++] = USART1_RX_Byte[0];
		USART1_RX_Byte[0] = 0;
		
		
		HAL_UART_Receive_IT(&huart1, (unsigned char *)USART1_RX_Byte, sizeof(USART1_RX_Byte));
		
	}
}

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_key.h"
#include "bsp_oled.h"
#include "bsp_RS485.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
	uint8_t len;
	uint8_t message[] = "tip";
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	OLED_Init();
	RS485_Init();//RX
	
	OLED_Clear();
	OLED_ShowString(1, 1, "TX:");
	OLED_ShowString(3, 1, "RX:");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	HAL_Delay(100);
	HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
	HAL_Delay(100);
	  
	  
	  //USART1_RX_Flag&0x8000
	if(USART1_RX_Flag == 1)
		{					   
			//len=USART1_RX_Flag&0x3fff;//得到此次接收到的数据长度
			
//			RS485DIR_TX_MODE;//拉高PB5,更改RS485模式为发送
//			HAL_UART_Transmit(&huart1,(uint8_t*)message,sizeof(message),1000);	//发送提示信息
//			while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
//			HAL_UART_Transmit(&huart1,(uint8_t*)USART1_RxBuffer,len,1000);	//发送接收到的数据
//			while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET);		//等待发送结束
			
			
			OLED_ShowNum(3, 5, USART1_RxBuffer[0], 2);
			
			OLED_ShowNum(3, 9, USART1_RxBuffer[1], 2);
			
			
			
			
			USART1_RxBuffer[0] = 0;
			USART1_RxBuffer[1] = 0;
//			USART1_RxBuffer[2] = 0;
//			USART1_RxBuffer[3] = 0;
			
			RX_index=0;
			//RS485DIR_RX_MODE;//拉低PB5,更改RS485模式为接收
			len = 0;
			USART1_RX_Flag=0;
//			if(USART1_RxBuffer[0] == '1') 
//				HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_1);
		}
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

发送部分:

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp_key.h"
#include "bsp_oled.h"
#include "bsp_RS485.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
	uint8_t TX_Buffer[2] = {1,2};
	uint8_t Key_Num = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	OLED_Init();
	RS485_Init();//TX
	
	OLED_Clear();
	OLED_ShowString(1, 1, "TX:");
	OLED_ShowString(3, 1, "RX:");
	
	uint8_t new[2] = "\r\n";
	
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	//HAL_Delay(100);
	
	
	Key_Num = KEY_GetNum();
	if(Key_Num == 1)
	{
		HAL_UART_Transmit(&huart1, TX_Buffer, 2, 1000);
		//HAL_UART_Transmit(&huart1, new, 2, 1000);
		OLED_ShowNum(1, 5, TX_Buffer[0], 2);
		OLED_ShowNum(1, 9, TX_Buffer[1], 2);
		
		TX_Buffer[0]++;
		TX_Buffer[1]++;
	}
	  
	  
	HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
	HAL_Delay(500);
	  
	  
	  
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

实验效果:

总结:

RS485 是在串口(UART)的串行通信原理基础上,通过以下方式实现升级的通信技术:

  1. 物理层增强:采用差分信号传输(通过 RS485 收发器实现电平转换和驱动),提升抗干扰能力和传输距离,支持多节点组网。
  2. 上层协议补充:RS485 本身仅定义物理层标准,需搭配 Modbus 等上层协议规定数据帧格式、通信规则,以实现有效通信。
posted @ 2025-07-07 17:02  Molesidy  阅读(19)  评论(0)    收藏  举报  来源
//雪花飘落效果