本文主要为自己复习485通信的实现使用。
**RS-485** 是一种广泛应用于工业自动化、楼宇自动化、数据采集等领域的串行通信标准。它以差分信号传输为基础,具有抗干扰能力强、传输距离远、支持多点通信等特点。
1. **差分信号传输**
- 使用两根信号线(A 和 B)进行差分传输。
- 差分信号可以有效抵抗电磁干扰,适合长距离通信。
2. **多点通信**
- 支持多点通信(Multi-Drop),最多可连接 32 个设备(在标准模式下,扩展模式下可达 128 或更多)。
- 每个设备通过地址区分,实现主从通信或多主机通信。
3. **长距离和高传输速率**
- 最大传输距离:约 1200 米(低速率时)。
- 最大传输速率:通常为 10 Mbps(短距离时)。
4. **半双工与全双工**
- 半双工:使用一对差分信号线(A 和 B),需要切换发送和接收方向。
- 全双工:使用两对差分信号线(A/B 和 Y/Z),可以同时发送和接收数据。
5. **电气特性**
- 差分电压范围:±200mV 至 ±6V。
- 接口芯片:常用芯片有 MAX485、SN75176 等。
RS-485 本身只定义了物理层的标准,不包括通信协议。因此,开发者需要根据应用需求设计或选择合适的通信协议。以下是常见的通信协议:
1. **Modbus RTU**
- 基于 RS-485 的最常见协议之一。
- 数据帧格式:
```
地址 + 功能码 + 数据 + CRC校验
```
- 特点:简单易用,支持主从通信。
2. **自定义协议**
- 根据项目需求设计简单的通信协议。
- 示例帧格式:
```
起始标志 + 地址 + 命令 + 数据长度 + 数据 + 校验
```
相关函数简介:
485通信本质也是串口通信,我这边是通过串口2来实现的485通信(需要开发板支持)。
串口初始化、GPIO复用配置、串口接收中断及NVIC配置
借助串口发送函数实现485发送一字节函数
crc16校验,这个函数直接网上移植的,使用即可
解析接受到主机发送的数据,对其进行解析,数据的接收是配合接收中断和空闲中断实现的
对主机功能码1的请求进行回复的函数
#include "main.h"
/*******************************************************
* @function : rs485_usart2_init
* @param : uint32_t baud
* @retval : void
* @brief : rs485初始化
RE(Receiver Enable)--active low
DE(Driver Enable) --active high
PD7
********************************************************/
UART_HandleTypeDef huart2;
void rs485_usart2_init(uint32_t baud)
{
//usart1
__HAL_RCC_USART2_CLK_ENABLE();
huart2.Instance = USART2;
huart2.Init.BaudRate = baud;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
HAL_UART_Init(&huart2);
//gpio ---后初始化GPIO,否则第一次会有乱码
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init={0};
GPIO_Init.Mode = GPIO_MODE_AF_PP;
GPIO_Init.Pin = GPIO_PIN_2 ;
GPIO_Init.Pull = GPIO_NOPULL;
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_AF_INPUT;
GPIO_Init.Pin = GPIO_PIN_3 ;
HAL_GPIO_Init(GPIOA,&GPIO_Init);
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init.Pin = GPIO_PIN_7 ;
GPIO_Init.Pull = GPIO_NOPULL;
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD,&GPIO_Init);
REC_ENABLE;//默认接收状态
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
__HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
HAL_NVIC_SetPriority(USART2_IRQn,2,0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
/*******************************************************
* @function : rs485_send_datas
* @param : uint8_t *data,uint16_t len
* @retval : void
* @brief : 485发送一字节数据
********************************************************/
void rs485_send_datas(uint8_t *data,uint16_t len)
{
if(HAL_UART_Transmit(&huart2,(const uint8_t *)data,len,50)!=HAL_OK)
{
REC_ENABLE;
return ;
};
}
/*******************************************************
* @function : Modbus_CRC16
* @param : uint8_t *data, uint16_t length
* @retval : void
* @brief : CRC16校验
********************************************************/
uint16_t Modbus_CRC16(uint8_t *data, uint16_t length)
{
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < length; i++)
{
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++)
{
if (crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/*******************************************************
* @function : Modbus_Service
* @param : void
* @retval : void
* @brief : 解析主机请求
********************************************************/
uint16_t startRegAddr;
uint16_t RegNum;
uint16_t calCRC;
void Modbus_Service(void)
{
uint16_t recCRC;
if(g_usart2_rs485.flag==1)//接收完成标志位
{
SEND_ENABLE;
// printf("\r\nmodbus_rec:");
// for(int i=0;i>8);// 先发送高字节--在发送低字节
modbus_tx_buff[4+i*2]=(Modbus_register[startRegAddr+i]);
}
calCRC=Modbus_CRC16(modbus_tx_buff,RegNum*2+3);
modbus_tx_buff[RegNum*2+3]=(calCRC)&0xFF;
modbus_tx_buff[RegNum*2+4]=(calCRC>>8)&0xFF;
rs485_send_datas(modbus_tx_buff,RegNum*2+5);
}
else//寄存器地址+数量超出范围
{
modbus_tx_buff[0]=g_usart2_rs485.buff[0];
modbus_tx_buff[1]=g_usart2_rs485.buff[1];
modbus_tx_buff[2]=0x02; //异常码
rs485_send_datas(modbus_tx_buff,3);
}
}
/*******************************************************
* @function : Modbus_03_Solve
* @param : void
* @retval : void
* @brief : 功能码3
********************************************************/
void Modbus_03_Solve(void)
{
uint8_t i;
static uint8_t num=0;
if(num==12)
{
num=0;
}
num++;
Modbus_register[0x00] = 0x01<>8);// 先发送高字节--在发送低字节
modbus_tx_buff[4+i*2]=(Modbus_register[startRegAddr+i]);
}
calCRC=Modbus_CRC16(modbus_tx_buff,RegNum*2+3);
modbus_tx_buff[RegNum*2+3]=(calCRC)&0xFF;
modbus_tx_buff[RegNum*2+4]=(calCRC>>8)&0xFF;
rs485_send_datas(modbus_tx_buff,RegNum*2+5);
}
else//寄存器地址+数量超出范围
{
modbus_tx_buff[0]=g_usart2_rs485.buff[0];
modbus_tx_buff[1]=g_usart2_rs485.buff[1];
modbus_tx_buff[2]=0x02; //异常码
rs485_send_datas(modbus_tx_buff,3);
}
}
/*******************************************************
* @function : Modbus_03_Solve
* @param : void
* @retval : void
* @brief : 功能码3
********************************************************/
void Modbus_16_Solve(void)
{
uint8_t i;
RegNum= (((uint16_t)g_usart2_rs485.buff[4])<<8)|((g_usart2_rs485.buff[5]));//获取寄存器数量
if((startRegAddr+RegNum)>8)&0xFF;
rs485_send_datas(modbus_tx_buff,8);
}
else//寄存器地址+数量超出范围
{
modbus_tx_buff[0]=g_usart2_rs485.buff[0];
modbus_tx_buff[1]=g_usart2_rs485.buff[1]|0x80;
modbus_tx_buff[2]=0x02; //异常码
rs485_send_datas(modbus_tx_buff,3);
}
}
#ifndef _MODBUS_RTU_H
#define _MODBUS_RTU_H
/*include*/
#include "main.h"
/*define*/
#define SEND_ENABLE HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7,GPIO_PIN_SET)
#define REC_ENABLE HAL_GPIO_WritePin(GPIOD,GPIO_PIN_7,GPIO_PIN_RESET)
#define Modbus_Addr 1
/*function*/
void rs485_usart2_init(uint32_t baud);
void Modbus_Service(void);
void Modbus_01_Solve(void);
void Modbus_03_Solve(void);
void Modbus_16_Solve(void);
/*extern*/
extern UART_HandleTypeDef huart2;
#endif
/*******************************************************
* @function : USART2_IRQHandler
* @param : void
* @retval : void
* @brief : USART2--MODBUS RS485中断服务函数
********************************************************/
void USART2_IRQHandler(void)
{
//receive IT
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE))
{
__HAL_UART_CLEAR_FLAG(&huart2,UART_FLAG_RXNE);
g_usart2_rs485.buff[g_usart2_rs485.len++]=USART2->DR;
}
//IDLE IT
if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE))
{
USART2->SR;
USART2->DR;
g_usart2_rs485.buff[g_usart2_rs485.len]=0;
// g_usart2_rs485.len=0;
g_usart2_rs485.flag=1;
// printf("rec:%s\r\n",g_usart2_rs485.buff);
}
}
相关全局变量
任意.h文件,建议和下面.C文件保持一致
typedef struct usart
{
uint8_t flag;
uint8_t buff[1024];
uint16_t len;
}g_strcut_usart;
#define MODBUS_REGISTER_NUM 0XFF
任意.c文件,可以在485初始配置文件添加
g_strcut_usart g_usart2_rs485; //串口2MODBUS RS485 结构体信息
uint8_t modbus_tx_buff[1024]; //modbus rtu发送
uint16_t Modbus_register[MODBUS_REGISTER_NUM]; //MODBUS寄存器
寄存器对应位:
维纶屏配置:
也可以使用modbus poll 软件,或者串口软件sscom.
注:本文只是初步实现了通信和简单的设备数据读取。