STM32与RS-485通讯——好记性不如烂鼠标

 本文是学习野火的指南针开发板过程的学习笔记,可能有误,详细请看B站野火官方配套视频教程

(这个教程真的讲的很详细,给官方三连吧)

注意概念混淆:串口通信通常指通用的串行总线接口,它可以支持多种协议,包括RS-485。当串口设备按照RS-485协议工作时,就是在串口通信的基础上应用了RS-485的具体标准规则。可见,RS-485本质只是串口通信的一种应用场景、是一种电气标准,它规范了如何在两线或多线上实现全双工的串行通信,特别适合于长距离、多节点的数据传输。

01 RS-485通讯协议简介

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

RS-485与RS-232通讯协议的特性对比: 

差分信号线具有很强的干扰能力,特别适合应用于电磁环境复杂的工业控制环境中,RS-485协议主要是把RS-232的信号改进成差分信号,从而大大提高了抗干扰特性

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

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


02 RS-485—通讯实验

以霸道开发板的MAX485为例

两个开发板之间的a接a,b接b;PC2为收发控制引脚

代码结构:

使用串口1打印输出到电脑

485通信使用串口2(头文件改为自己的文件名)

bsp_485.c
/**
  ******************************************************************************
  * @file    bsp_485.c
  * @author  fire
  * @version V1.0
  * @date    2015-xx-xx
  * @brief   485驱动
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-霸道 STM32  开发板  
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */ 
#include "./485/bsp_485.h"
#include <stdarg.h>


static void Delay(__IO u32 nCount); 


/// 配置USART接收中断优先级
static void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    /* Configure the NVIC Preemption Priority Bits */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

    /* Enable the USARTy Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = RS485_INT_IRQ; 
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
/*
 * 函数名:RS485_Config
 * 描述  :USART GPIO 配置,工作模式配置
 * 输入  :无
 * 输出  : 无
 * 调用  :外部调用
 */
void RS485_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	/* config USART clock */
	RCC_APB2PeriphClockCmd(RS485_USART_RX_GPIO_CLK|RS485_USART_TX_GPIO_CLK|RS485_RE_GPIO_CLK, ENABLE);
	RCC_APB1PeriphClockCmd(RS485_USART_CLK, ENABLE); 	
	
	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = RS485_USART_TX_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(RS485_USART_TX_GPIO_PORT, &GPIO_InitStructure);
  	
	// 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = RS485_USART_RX_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(RS485_USART_RX_GPIO_PORT, &GPIO_InitStructure);	
  
  /* 485收发控制管脚 */
	GPIO_InitStructure.GPIO_Pin = RS485_RE_PIN;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 	
  GPIO_Init(RS485_RE_GPIO_PORT, &GPIO_InitStructure);

	/* USART 模式配置*/
	USART_InitStructure.USART_BaudRate = RS485_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(RS485_USART, &USART_InitStructure); 
	/*使能USART*/
  USART_Cmd(RS485_USART, ENABLE);
	
	/*配置中断优先级*/
	NVIC_Configuration();
	/* 使能串口接收中断 */
	USART_ITConfig(RS485_USART, USART_IT_RXNE, ENABLE);
	
	/*控制485芯片进入接收模式*/
	GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);
}



/***************** 发送一个字符  **********************/
//使用单字节数据发送前要使能发送引脚,发送后要使能接收引脚。
void RS485_SendByte(  uint8_t ch )
{
	/* 发送一个字节数据到USART1 */
	USART_SendData(RS485_USART,ch);
		
	/* 等待发送完毕 */
	while (USART_GetFlagStatus(RS485_USART, USART_FLAG_TXE) == RESET);	
	
}
/*****************  发送指定长度的字符串 **********************/
void RS485_SendStr_length( uint8_t *str,uint32_t strlen )
{
	unsigned int k=0;

	RS485_TX_EN()	;//	使能发送数据	
    do 
    {
        RS485_SendByte( *(str + k) );
        k++;
    } while(k < strlen);
		
	/*加短暂延时,保证485发送数据完毕*/
	Delay(0xFFF);
		
	RS485_RX_EN()	;//	使能接收数据
}


/*****************  发送字符串 **********************/
void RS485_SendString(  uint8_t *str)
{
	unsigned int k=0;
	
	RS485_TX_EN()	;//	使能发送数据
	
    do 
    {
        RS485_SendByte(  *(str + k) );
        k++;
    } while(*(str + k)!='\0');
	
	/*加短暂延时,保证485发送数据完毕*/
	Delay(0xFFF);
		
	RS485_RX_EN()	;//	使能接收数据
}









//中断缓存串口数据
#define UART_BUFF_SIZE      1024
volatile    uint16_t uart_p = 0;
uint8_t     uart_buff[UART_BUFF_SIZE];

void bsp_RS485_IRQHandler(void)
{
    if(uart_p<UART_BUFF_SIZE)
    {
        if(USART_GetITStatus(RS485_USART, USART_IT_RXNE) != RESET)
        {
            uart_buff[uart_p] = USART_ReceiveData(RS485_USART);
            uart_p++;
						
						USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
        }
    }
		else
		{
			USART_ClearITPendingBit(RS485_USART, USART_IT_RXNE);
//			clean_rebuff();       
		}
}



//获取接收到的数据和长度
char *get_rebuff(uint16_t *len) 
{
    *len = uart_p;
    return (char *)&uart_buff;
}

//清空缓冲区
void clean_rebuff(void) 
{

    uint16_t i=UART_BUFF_SIZE+1;
    uart_p = 0;
	while(i)
		uart_buff[--i]=0;

}




static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

bsp_485.h 

使用485通信(头文件改为自己的文件名) 

#ifndef _RS485_H
#define	_RS485_H

#include "stm32f10x.h"
#include <stdio.h>


/*USART号、时钟、波特率*/
#define RS485_USART                             USART2
#define RS485_USART_CLK                         RCC_APB1Periph_USART2
#define RS485_USART_BAUDRATE                    115200

/*RX引脚*/
#define RS485_USART_RX_GPIO_PORT                GPIOA
#define RS485_USART_RX_GPIO_CLK                 RCC_APB2Periph_GPIOA
#define RS485_USART_RX_PIN                      GPIO_Pin_3

/*TX引脚*/
#define RS485_USART_TX_GPIO_PORT                GPIOA
#define RS485_USART_TX_GPIO_CLK                 RCC_APB2Periph_GPIOA
#define RS485_USART_TX_PIN                      GPIO_Pin_2

/*485收发控制引脚*/
#define RS485_RE_GPIO_PORT						GPIOC
#define RS485_RE_GPIO_CLK						RCC_APB2Periph_GPIOC
#define RS485_RE_PIN							GPIO_Pin_2

/*中断相关*/
#define RS485_INT_IRQ                 			USART2_IRQn
#define RS485_IRQHandler                        USART2_IRQHandler


	/// 不精确的延时
static void RS485_delay(__IO u32 nCount)
{
	for(; nCount != 0; nCount--);
} 


/*控制收发引脚*/
//进入接收模式,必须要有延时等待485处理完数据
#define RS485_RX_EN()			RS485_delay(1000); GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);  RS485_delay(1000);
//进入发送模式,必须要有延时等待485处理完数据
#define RS485_TX_EN()			RS485_delay(1000); GPIO_SetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);  RS485_delay(1000);



/*debug*/

#define RS485_DEBUG_ON          1
#define RS485_DEBUG_ARRAY_ON   1
#define RS485_DEBUG_FUNC_ON    1
   
   
// Log define
#define RS485_INFO(fmt,arg...)           printf("<<-RS485-INFO->> "fmt"\n",##arg)
#define RS485_ERROR(fmt,arg...)          printf("<<-RS485-ERROR->> "fmt"\n",##arg)
#define RS485_DEBUG(fmt,arg...)          do{\
																					 if(RS485_DEBUG_ON)\
																					 printf("<<-RS485-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
																				 }while(0)

#define RS485_DEBUG_ARRAY(array, num)    do{\
                                         int32_t i;\
                                         uint8_t* a = array;\
                                         if(RS485_DEBUG_ARRAY_ON)\
                                         {\
                                            printf("<<-RS485-DEBUG-ARRAY->>\n");\
                                            for (i = 0; i < (num); i++)\
                                            {\
                                                printf("%02x   ", (a)[i]);\
                                                if ((i + 1 ) %10 == 0)\
                                                {\
                                                    printf("\n");\
                                                }\
                                            }\
                                            printf("\n");\
                                        }\
                                       }while(0)

#define RS485_DEBUG_FUNC()               do{\
                                         if(RS485_DEBUG_FUNC_ON)\
                                         printf("<<-RS485-FUNC->> Func:%s@Line:%d\n",__func__,__LINE__);\
                                       }while(0)


void RS485_Config(void);
void RS485_SendByte(  uint8_t ch );
void RS485_SendStr_length( uint8_t *str,uint32_t strlen );
void RS485_SendString(  uint8_t *str);



void bsp_RS485_IRQHandler(void);
char *get_rebuff(uint16_t *len);
void clean_rebuff(void);
#endif /* _RS485_H */

 其中

/*控制收发引脚*/
//进入接收模式,必须要有延时等待485处理完数据
#define RS485_RX_EN()			RS485_delay(1000); GPIO_ResetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);  RS485_delay(1000);
//进入发送模式,必须要有延时等待485处理完数据
#define RS485_TX_EN()			RS485_delay(1000); GPIO_SetBits(RS485_RE_GPIO_PORT,RS485_RE_PIN);  RS485_delay(1000);

注意因为485的反应没有STM32快,所以我们采取一定延时,并且定义在宏中

main.c和中断服务函数
void RS485_IRQHandler(void)
{
    bsp_RS485_IRQHandler();

}
/**
  ******************************************************************************
  * @file    main.c
  * @author  fire
  * @version V1.0
  * @date    2016-xx-xx
  * @brief   485通讯例程
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火 F103-霸道 STM32 开发板 
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */ 

#include "stm32f10x.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"   
#include "./key/bsp_key.h"  
#include "./485/bsp_485.h"


static void Delay(__IO u32 nCount); 

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{ 

	char *pbuf;
	uint16_t len;
	
	LED_GPIO_Config();	
	
    /*初始化USART1*/
    Debug_USART_Config();
	
	/*初始化485使用的串口,使用中断模式接收*/
	RS485_Config();
	
	LED_BLUE;
	
	Key_GPIO_Config();
	
	printf("\r\n 欢迎使用野火F103-霸道  STM32  开发板。\r\n");
    printf("\r\n 野火F103-霸道 485通讯实验例程\r\n");
	
	printf("\r\n 实验步骤:\r\n");

	printf("\r\n 1.使用导线连接好两个485通讯设备\r\n");
	printf("\r\n 2.使用跳线帽连接好:霸道V1底板:5V <---> C/4-5V  485-D <-----> PA2 485-R <-----> PA3  \r\n");
 	printf("\r\n                   霸道V2底板:3V3 <---> CAN/485_3V3 485TX <-----> PA2 485RX <-----> PA3 \r\n");   
	printf("\r\n 3.若使用两个野火开发板进行实验,给两个开发板都下载本程序即可。\r\n");
	printf("\r\n 4.准备好后,按下其中一个开发板的KEY1键,会使用485向外发送0-255的数字 \r\n");
	printf("\r\n 5.若开发板的485接收到256个字节数据,会把数据以16进制形式打印出来。 \r\n");
	
  while(1)
  {
		/*按一次按键发送一次数据*/
		if(	Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
		{
			uint16_t i;
			
			LED_BLUE;
			
			RS485_TX_EN();
			
			for(i=0;i<=0xff;i++)
			{
			RS485_SendByte(i);	 //发送数据
			}
			
			/*加短暂延时,保证485发送数据完毕*/
			Delay(0xFFF);
			RS485_RX_EN();
			
			LED_GREEN;
			
			printf("\r\n发送数据成功!\r\n"); //使用调试串口打印调试信息到终端

		}
		else
		{		
			LED_BLUE;
			
			pbuf = get_rebuff(&len);
			if(len>=256)
			{
				LED_GREEN;
				printf("\r\n接收到长度为%d的数据\r\n",len);	
				RS485_DEBUG_ARRAY((uint8_t*)pbuf,len);
				clean_rebuff();
			}
		}
  }

}

static void Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

/*********************************************END OF FILE**********************/



完整工程链接

### 实现 STM32 RS485 一主多从通信的关键要素 #### 1. **硬件配置** 在实现 RS485 的一主多从通信时,需要确保硬件连接正确。STM32 使用 UART 接口来驱动 RS485 收发器芯片(如 MAX485 或类似的器件)。为了支持半双工通信,通常会通过 GPIO 控制 DE 和 RE 引脚以切换发送/接收状态。 - 主设备和从设备都需要连接到同一条总线上。 - 每个从设备应具有唯一的地址以便于区分不同节点[^3]。 #### 2. **软件设计** ##### (a) 初始化串口 利用 STM32CubeMX 工具可以简化初始化过程,在 Pinout & Configuration 中选择合适的 USART 外设并设置其工作参数: ```c // 配置USART波特率、停止位等基本属性 void MX_USART3_UART_Init(void) { huart3.Instance = USART3; huart3.Init.BaudRate = 9600; // 设置波特率为9600bps huart3.Init.WordLength = UART_WORDLENGTH_8B;// 字符长度为8bit huart3.Init.StopBits = UART_STOPBITS_1; // 停止位数为1 huart3.Init.Parity = UART_PARITY_NONE; // 不启用校验功能 HAL_UART_Init(&huart3); // 调用HAL库函数完成初始化 } ``` ##### (b) 半双工控制逻辑 由于 RS485 是一种半双工协议,因此必须手动管理收发方向的切换。这可以通过编程控制 DE/RE 引脚的状态来达成: ```c #define RS485_DE_PIN GPIO_PIN_x // 定义DE引脚编号 #define RS485_PORT GPIOA // 定义端口号 void RS485_SendEnable(void){ HAL_GPIO_WritePin(RS485_PORT, RS485_DE_PIN, GPIO_PIN_SET); } void RS485_ReceiveEnable(void){ HAL_GPIO_WritePin(RS485_PORT, RS485_DE_PIN, GPIO_PIN_RESET); } ``` 每次准备传输前调用 `RS485_SendEnable()` 方法激活发送模式;而在等待响应或者监听其他节点消息期间,则需执行 `RS485_ReceiveEnable()` 来进入接收状态。 ##### (c) 数据帧结构定义 考虑到实际应用需求,建议采用 Modbus RTU 这样的标准化工业通讯规约作为基础框架构建数据包格式。它不仅规定了清晰明了的数据交换方式,还提供了错误检测机制CRC-16保障链路可靠性。 一个典型的Modbus请求可能如下所示: | 地址 | 功能码 | 寄存器起始位置高字节 | ... | |------|--------|-----------------------|-----| | 0x01 | 0x03 | 0x00 | ... | 其中,“地址”字段用来指定目标从站的身份标识号。“功能码”指示具体的操作类型比如读输入寄存器还是写单个线圈等等。 #### 3. **主控制器行为描述** 当主控单元想要获取某个特定传感器的信息时,按照上述提到的标准封装好查询命令之后发出即可。随后依据预期回应时间长短设定超时期限,并持续监测是否有合法回复到来直到时限结束为止。 如果接收到有效反馈则解析相应负载部分提取所需数值;反之如果没有得到任何答复或是收到了不符合预想形式的内容,则重新尝试一定次数后再放弃当前轮次交互流程继续下一个任务处理环节。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值