什么是485通讯

RS485通讯采用的是差分信号负逻辑控制模式,即2~6V表示“0”,-6~-2V表示“1”。

RS485通讯有两线制和四线制2种接线方式。

四线制只能实现点对点的通信方式,现已很少采用。

两线制接线方式是目前采用较多的为总线式拓朴结构,属于半双工传输方式,也就是说收发是不同步的。

总线端口防护

在恶劣的环境下,RS485通讯端口通常都做好静电防护、雷击浪涌防护等额外的防护,甚至还需要做好防80V市电接入的方案,以避免智能仪表、工控主机的损坏。

如图为常见的3种RS485总线端口防护方案。

什么是TVS器件

‌TVS器件‌,全称为瞬态电压抑制二极管(Transient Voltage Suppressor),是一种用于保护电子设备免受瞬间高电压冲击的半导体器件。

它能够在极短的时间内(通常为皮秒到纳秒级别)响应电路中的异常过电压,通过迅速将高阻态转变为低阻态,将异常过电压限制在一个安全的范围内,从而保护后级电路不受损害‌

当电路处于正常工作状态时,TVS 呈现高阻态,如同一个开路元件,对电路的正常工作电流几乎没有影响,就像一座关闭的“阀门”,不干扰电子设备的正常运行。

然而,一旦电路中出现瞬间的高电压脉冲,例如静电放电(ESD)或者电快速瞬变脉冲群(EFT)等情况,TVS 会迅速做出反应。它在极短的时间内(通常为皮秒到纳秒级别)从高阻态转变为低阻态,瞬间变成一个低阻抗的通道,就像打开了“泄洪闸”,让瞬间的高电流能够快速通过它流入地端,从而将被保护电路两端的电压钳制在一个安全的范围内,避免过高的电压对电子元件造成损害,如芯片被击穿、电容被击穿等严重故障。

第一种为AB端口分别并联TVS器件到保护地,AB端口之间并联TVS器件、AB端口分别串联热敏电阻、并接气体放电管到保护地形成三级保护的方案; Image 第二种为AB分别并联TVS到地、串联热敏电阻,AB之间并联压敏电阻的三级防护方案; Image

第三种为AB分别接上下拉电阻到电源与地,AB之间接TVS,A或B某一端口接热敏电阻的方案。

Image

二、RS485组网方式

1、总线式组网

Image

如图显示了典型网络应用电路。这些器件也能用作电缆长于4000英尺的线性转发器,为减小反射,应当在传输线两端以其特性阻抗进行终端匹配,主干线以外的分支连线长度应尽可能短。

在通信过程中有两种信号因导致信号反射:阻抗不连续和阻抗不匹配。

阻抗不连续信号在传输线末端突然遇到电缆阻抗很小甚至没有信号在这个地方就会引起反射,消除这种反射的方法就必须在电缆的末端跨接一个与电缆的特性阻抗同样大小的终端电阻使电缆的阻抗连续。由于信号在电缆上的传输是双向的因此在通讯电缆的另一端可跨接一个同样大小的终端电阻,一般的终端匹配都采用终端电阻方法。

终端电阻在RS485网络中取120Ω,相当于电缆特性阻抗的电阻,因为大多数双绞线电缆特性阻抗大约在100~120Ω。

这种匹配方法简单有效,但有一个缺点,匹配电阻要消耗较大功率,对于功耗限制比较严格的系统不太适合。

RS485通讯在一般场合下的连接,采用普通的双绞线即可。要求比较高的环境下,采用带屏蔽层的同轴电缆连接。在使用RS485接口时,对于特定的传输线路,从RS485接口到

负载,其数据信号传输所允许的最大电缆长度与信号传输的波频率成反比。

该长度数据主要是受信号失真及噪声等影响。在传输过程中可以采用增加中继的方法对信号进行放大,一般最多可以加9个中继,也就是说理论上RS485的最大传输距离可以达到9.6km。

当需要长距离传输时,可以采用光纤为传播介质,收发两端各加一个光电转换器。

多模光纤的传输距离能达5~10km,若采用单模光纤传输,可达50km的传输距离。

2、手拉手式组网

Image

又称菊花链拓扑结构,是RS485总线布线的标准及规范,是TIA等组织推荐使用的RS485总线拓扑结构。

其布线方式就是主控设备与多个从控设备形成手拉手连接方式,如图所示,不留分支才是手拉手的方式。

这种布线方式,具有信号反射小,通讯成功率高等优点。

目前很多应用基本上采用这种组网方式。

硬件

YD3082EESA 15kV ESD保护、失效保护 RS-485/RS-422收发器 Image

概述

用于RS-485/RS-422通信的YD3082EESA是一款半双工通信的高速收发器,其包含一路驱动器和一 路接收器。具有±15kV人体模式ESD保护以及失效保护电路,当接收器输入开路或短路时,确保接收器 输出逻辑高电平。如果挂接在终端匹配总线上的所有发送器都禁用(高阻),接收器将输出逻辑高电平。 YD3082EESA具有低摆率驱动器,能够减小EMI和由于不恰当的终端匹配电缆所引起的反射,并实现高 达1Mbps的无差错数据传输。此外,YD3082EESA其接收器具有1/8单位负载输入阻抗,总线上可以挂 接多达256个收发器。

特性

● 提供低电流关断模式 ● 提供工业标准的8引脚SOP封装 ● 总线上允许挂接多达256个收发器 ● 真正的失效保护接收器兼容于EIA/TIA-485 ● 强大的摆率控制功能有助于实现无差错数据传输 ● 为RS-485/RS-422 A/B引脚提供增强型ESD保护

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111

#include <rtthread.h>
#include <rtdevice.h>
 
/* 串口设备句柄 */
static rt_device_t serial;
 
/* 485控制引脚 */
static rt_base_t rs485_ctrl_pin = -1;
 
/* timeout receive */
static int serial_read_frame(rt_device_t dev, uint8_t *buf, int max_len, uint32_t idle_ms, int timeout_ms)
{
    int rx_len = 0, rc;
    uint32_t idle_time, timeout_time, cur_tick, last_tick;
 
    timeout_time = rt_tick_from_millisecond(timeout_ms);
    idle_time = rt_tick_from_millisecond(idle_ms);
    cur_tick = rt_tick_get();
 
    while((rt_tick_get()-last_tick<idle_time && rx_len<max_len) || rx_len<=0){
        rc = rt_device_read(dev, rx_len, buf, max_len-rx_len);
        if(rc>0){
            rx_len += rc;
            last_tick = rt_tick_get();
        }else{
            rt_thread_mdelay(1);
        }
        if(rt_tick_get()-cur_tick>timeout_time && rx_len<=0)
            break;
    }
    return rx_len;
}
/* transmit with auto 485 pin ctrl */
static void serial_write_frame_rs485(rt_device_t dev, uint8_t *buf, int len, int bitrate, int ctrl_pin)
{
    int ms = len * 10 *1000 / bitrate + 2;
 
    rt_pin_write(rs485_ctrl_pin,1);
    rt_hw_us_delay(10);
    rt_device_write(dev, 0, buf, len);
    rt_thread_mdelay(ms);
    rt_pin_write(rs485_ctrl_pin,0);
}
static void serial_thread_entry(void *parameter)
{
    rt_uint32_t rx_len;
    static unsigned char rx_buf[256];
 
 
    while(1){
        rx_len = serial_read_frame(serial, rx_buf, 255, 10, 1000);
        if(rx_len<=0)
            continue;
        serial_write_frame_rs485(serial, rx_buf, rx_len, 115200, rs485_ctrl_pin);
        /* 打印数据 */
        rx_buf[rx_len] = '\0';
        rt_kprintf("rx_len = %d\n",rx_len);
    }
}
 
static int uart_485_sample(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX] = "uart4";
 
    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
 
    rt_kprintf("uart_name = %s\n",uart_name);
    if(rt_strcmp(uart_name,"uart3")==0){
        rs485_ctrl_pin = rt_pin_get("PE.15");
        rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
        rt_pin_write(rs485_ctrl_pin,0 );
    }else if(rt_strcmp(uart_name,"uart4")==0){
        rs485_ctrl_pin = rt_pin_get("PA.15");
        rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
        rt_pin_write(rs485_ctrl_pin,0);
    }else{
        return RT_ERROR;
    }
 
    /* 查找串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }
 
    /* 以 DMA 接收及轮询发送方式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_RX_NON_BLOCKING | RT_DEVICE_FLAG_TX_NON_BLOCKING);
 
    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }
 
    return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_485_sample, uart device rs485 sample);

接收函数,返回收到的字节数:

int serial_read_frame(rt_device_t dev, uint8_t *buf, int max_len, uint32_t idle_ms, int timeout_ms)

idle_ms : 收到最后一个字节数据后空闲的毫秒数

timeout_ms : 如果在这个时间内没有收到数据,则返回0;-1代表一直等待直到收到数据。

发送函数

void serial_write_frame_rs485(rt_device_t dev, uint8_t *buf, int len, int bitrate, int ctrl_pin)

bitrate : 波特率,用于计算实际需要发送的时间

ctrl_pin :485收发控制引脚号

Modbus从机实现

实现了一个极简Modbus从机,支持Modbus功能码01,02,03,04,05,06,15,16

底层函数实现:

1
2
3
4
5
6
rt_weak rt_err_t mb_write_coil_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak rt_err_t mb_write_holding_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak unsigned short mb_read_coil_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_di_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_holding_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_input_cb(unsigned short addr){return 0;}

mb_core.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#include <rtthread.h>
 
static unsigned int MB_COIL_NUM    = 16;
static unsigned int MB_DI_NUM      = 16;
static unsigned int MB_HOLDING_NUM = 32;
static unsigned int MB_INPUT_NUM   = 32;
 
static unsigned char DEV_ADDR = 1;
 
rt_weak rt_err_t mb_write_coil_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak rt_err_t mb_write_holding_cb(unsigned short addr, unsigned short val){return RT_EOK;}
rt_weak unsigned short mb_read_coil_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_di_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_holding_cb(unsigned short addr){return 0;}
rt_weak unsigned short mb_read_input_cb(unsigned short addr){return 0;}
 
 
/* 高位字节的 CRC 值 */
static unsigned char auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
        0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
        0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
        0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 };
/* 低位字节的 CRC 值 */
static char auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
        0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8,
        0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5,
        0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33,
        0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E,
        0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
        0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3,
        0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5,
        0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8,
        0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4,
        0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
        0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F,
        0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A,
        0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82,
        0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 };
 
static unsigned short CRC16_ModBus(unsigned char *puchMsg, unsigned char usDataLen)
{
    unsigned char uchCRCHi = 0xFF; /* CRC 的高字节初始化 */
    unsigned char uchCRCLo = 0xFF; /* CRC 的低字节初始化 */
    unsigned char uIndex; /* CRC 查询表索引 */
    while (usDataLen--) /* 完成整个报文缓冲区 */
    {
        uIndex = uchCRCLo ^ *puchMsg++; /* 计算 CRC */
        uchCRCLo = uchCRCHi ^ auchCRCHi[uIndex];
        uchCRCHi = auchCRCLo[uIndex];
    }
    return (uchCRCHi << 8 | uchCRCLo);
}
 
static int frame_available(unsigned char *buf, int len)
{
    int data_len = 0, num;
    if (len < 5)
        return 0;
 
    if (buf[1] == 1 || buf[1] == 2 || buf[1] == 3 || buf[1] == 4 || buf[1] == 5 || buf[1] == 6)
    {
        data_len = 4;
    }
    else if (buf[1] == 15)
    {
        num = ((buf[4] << 8) | buf[5]);
        data_len = 4 + 1 + (num + 7) / 8;
    }
    else if (buf[1] == 16)
    {
        num = ((buf[4] << 8) | buf[5]);
        data_len = 4 + 1 + num * 2;
    }
    else if (buf[1] & 0x80)
    {
        data_len = 1;
    }
    else
    {
        return -1;
    }
 
    if (len < data_len + 4)
        return 0;
 
    unsigned short crc = CRC16_ModBus(buf, 2 + data_len);
    if (crc == (buf[2 + data_len] | ((buf[2 + data_len + 1]) << 8)))
    {
        return 1;
    }
 
    return -1;
}
 
void mb_slave_init(unsigned char slave_addr, int coil_num, int di_num, int holding_num, int input_num)
{
    MB_COIL_NUM    = coil_num;
    MB_DI_NUM      = di_num;
    MB_HOLDING_NUM = holding_num;
    MB_INPUT_NUM   = input_num;
 
    DEV_ADDR = slave_addr;
}
 
int mb_slave_process(unsigned char *rx_buf, int rx_len, unsigned char *tx_buf)
{
    unsigned char error_code = 0;
    unsigned short num, addr, val, ind = 0, crc16, bytes;
    int ret = frame_available(rx_buf, rx_len);
 
    //frame error
    if (ret <= 0)
        return ret;
 
    //valid addr
    if (rx_buf[0] != DEV_ADDR)
        return 0;
 
    //process rtu cmd
    switch (rx_buf[1])
    {
    case 1: //read coil
    case 2: //read di
        addr = (rx_buf[2] << 8) | rx_buf[3];
        num = (rx_buf[4] << 8) | rx_buf[5];
        if (num < 1 || num > 0x7d0)
        {
            error_code = 3;
            break;
        }
        if (addr + num > (rx_buf[1] == 1 ? MB_COIL_NUM : MB_DI_NUM))
        {
            error_code = 2;
            break;
        }
        tx_buf[ind++] = DEV_ADDR;
        tx_buf[ind++] = rx_buf[1];
        bytes = (num + 7) / 8;
        tx_buf[ind++] = bytes;
        for (int i = 0; i < bytes; i++)
        {
            tx_buf[ind + i] = 0;
        }
        for (int i = 0; i < num; i++)
        {
            if (rx_buf[1] == 1)//coil
            {
                val = mb_read_coil_cb(i + addr);
                if (val)
                    tx_buf[ind + i / 8] |= 1 << (i % 8);
            }
            else//di
            {
                val = mb_read_di_cb(i + addr);
                if (val)
                    tx_buf[ind + i / 8] |= 1 << (i % 8);
            }
        }
        ind += bytes;
        crc16 = CRC16_ModBus(tx_buf, ind);
        tx_buf[ind++] = crc16;
        tx_buf[ind++] = crc16 >> 8;
        break;
 
    case 3: //read holding
    case 4: //read input
        addr = (rx_buf[2] << 8) | rx_buf[3];
        num = (rx_buf[4] << 8) | rx_buf[5];
        if (num < 1 || num > 125)
        {
            error_code = 3;
            break;
        }
        if (addr + num > (rx_buf[1] == 3 ? MB_HOLDING_NUM : MB_INPUT_NUM))
        {
            error_code = 2;
            break;
        }
        tx_buf[ind++] = DEV_ADDR;
        tx_buf[ind++] = rx_buf[1];
        tx_buf[ind++] = num * 2;
        for (int i = 0; i < num; i++)
        {
            if (rx_buf[1] == 3)//holding
            {
                val = mb_read_holding_cb(i + addr);
                tx_buf[ind++] = val >> 8;
                tx_buf[ind++] = val;
            }
            else//input
            {
                val = mb_read_input_cb(i + addr);
                tx_buf[ind++] = val >> 8;
                tx_buf[ind++] = val;
            }
        }
        crc16 = CRC16_ModBus(tx_buf, ind);
        tx_buf[ind++] = crc16;
        tx_buf[ind++] = crc16 >> 8;
        break;
 
    case 5: //write single coil
    case 6: //write single reg
        addr = (rx_buf[2] << 8) | rx_buf[3];
        val = (rx_buf[4] << 8) | rx_buf[5];
        if (val != 0x0 && val != 0xff00 && rx_buf[1] == 5)
        {
            error_code = 3;
            break;
        }
        if (addr> (rx_buf[1] == 5 ? MB_COIL_NUM : MB_HOLDING_NUM))
        {
            error_code = 2;
            break;
        }
        for (int i = 0; i < 8; i++)
        {
            tx_buf[i] = rx_buf[i];
        }
        if (rx_buf[1] == 5)
        {
            if (0 != mb_write_coil_cb(addr, val))
                error_code = 4;
        }
        else
        {
            if (0 != mb_write_holding_cb(addr, val))
                error_code = 4;
        }
        ind = 8;
        break;
 
    case 15: //wrtie multi coils
        addr = (rx_buf[2] << 8) | rx_buf[3];
        num = (rx_buf[4] << 8) | rx_buf[5];
        if (num < 1 || num > 0x7b || rx_buf[6] != (num + 7) / 8)
        {
            error_code = 3;
            break;
        }
        if (addr + num > MB_COIL_NUM)
        {
            error_code = 2;
            break;
        }
        for (ind = 0; ind < 6; ind++)
        {
            tx_buf[ind] = rx_buf[ind];
        }
        for (int i = 0; i < num; i++)
        {
            val = rx_buf[7 + i / 8] & (1 << (i % 8));
            if (mb_write_coil_cb(addr + i, val) != 0)
                error_code = 4;
        }
        crc16 = CRC16_ModBus(tx_buf, ind);
        tx_buf[ind++] = crc16;
        tx_buf[ind++] = crc16 >> 8;
        break;
 
    case 16: //write multi regs
        addr = (rx_buf[2] << 8) | rx_buf[3];
        num = (rx_buf[4] << 8) | rx_buf[5];
        if (num < 1 || num > 0x7b || rx_buf[6] != num * 2)
        {
            error_code = 3;
            break;
        }
        if (addr + num > MB_HOLDING_NUM)
        {
            error_code = 2;
            break;
        }
        for (ind = 0; ind < 6; ind++)
        {
            tx_buf[ind] = rx_buf[ind];
        }
        for (int i = 0; i < num; i++)
        {
            val = (rx_buf[7 + 2 * i] << 8) | rx_buf[7 + 2 * i + 1];
            if (0 != mb_write_holding_cb(addr + i, val))
                error_code = 4;
        }
        crc16 = CRC16_ModBus(tx_buf, ind);
        tx_buf[ind++] = crc16;
        tx_buf[ind++] = crc16 >> 8;
        break;
    default:
        if (rx_buf[1] & 0x80)
            return 0;
        else
            error_code = 1;
        break;
    }
    if (error_code != 0)
    {
        ind = 0;
        tx_buf[ind++] = DEV_ADDR;
        tx_buf[ind++] = rx_buf[1] | 0x80;
        tx_buf[ind++] = error_code;
        crc16 = CRC16_ModBus(tx_buf, ind);
        tx_buf[ind++] = crc16;
        tx_buf[ind++] = crc16 >> 8;
    }
    return ind;
}

测试代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include <rtthread.h>
#include <rtdevice.h>
 
/* 串口设备句柄 */
static rt_device_t serial;
 
/* 485控制引脚 */
static rt_base_t rs485_ctrl_pin = -1;
 
/* timeout receive */
static int serial_read_frame(rt_device_t dev, uint8_t *buf, int max_len, uint32_t idle_ms, int timeout_ms)
{
    int rx_len = 0, rc;
    uint32_t idle_time, timeout_time, cur_tick, last_tick;
 
    timeout_time = rt_tick_from_millisecond(timeout_ms);
    idle_time = rt_tick_from_millisecond(idle_ms);
    cur_tick = rt_tick_get();
 
    while ((rt_tick_get() - last_tick < idle_time && rx_len < max_len) || rx_len <= 0)
    {
        rc = rt_device_read(dev, rx_len, buf, max_len - rx_len);
        if (rc > 0)
        {
            rx_len += rc;
            last_tick = rt_tick_get();
        }
        else
        {
            rt_thread_mdelay(1);
        }
        if (rt_tick_get() - cur_tick > timeout_time && rx_len <= 0)
            break;
    }
    return rx_len;
}
/* transmit with auto 485 pin ctrl */
static void serial_write_frame_rs485(rt_device_t dev, uint8_t *buf, int len, int bitrate, int ctrl_pin)
{
    int ms = len * 10 * 1000 / bitrate + 2;
 
    rt_pin_write(rs485_ctrl_pin, 1);
    rt_hw_us_delay(10);
    rt_device_write(dev, 0, buf, len);
    rt_thread_mdelay(ms);
    rt_pin_write(rs485_ctrl_pin, 0);
}
 
static void serial_thread_entry(void *parameter)
{
    extern void mb_slave_init(unsigned char slave_addr, int coil_num, int di_num, int holding_num, int input_num);
    extern int mb_slave_process(unsigned char *rx_buf, int rx_len, unsigned char *tx_buf);
 
    rt_uint32_t rx_len, tx_len;
    static unsigned char rx_buf[260];
    static unsigned char tx_buf[260];
 
    mb_slave_init(1,16,16,32,32);
    while (1)
    {
        rx_len = serial_read_frame(serial, rx_buf, 260, 10, 1000);
        if (rx_len <= 0)
            continue;
        tx_len = mb_slave_process(rx_buf, rx_len, tx_buf);
        if(tx_len>0)
            serial_write_frame_rs485(serial, rx_buf, rx_len, 115200, rs485_ctrl_pin);
        /* 打印数据 */
        rx_buf[rx_len] = '\0';
        rt_kprintf("rx_len = %d\n", rx_len);
    }
}
 
static int uart_485_sample(int argc, char *argv[])
{
    rt_err_t ret = RT_EOK;
    char uart_name[RT_NAME_MAX] = "uart4";
 
    if (argc == 2)
    {
        rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
    }
 
    rt_kprintf("uart_name = %s\n", uart_name);
    if (rt_strcmp(uart_name,"uart3") == 0)
    {
        rs485_ctrl_pin = rt_pin_get("PE.15");
        rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
        rt_pin_write(rs485_ctrl_pin, 0);
    }
    else if (rt_strcmp(uart_name,"uart4") == 0)
    {
        rs485_ctrl_pin = rt_pin_get("PA.15");
        rt_pin_mode(rs485_ctrl_pin, PIN_MODE_OUTPUT);
        rt_pin_write(rs485_ctrl_pin, 0);
    }
    else
    {
        return RT_ERROR;
    }
 
    /* 查找串口设备 */
    serial = rt_device_find(uart_name);
    if (!serial)
    {
        rt_kprintf("find %s failed!\n", uart_name);
        return RT_ERROR;
    }
 
    /* 以 DMA 接收及轮询发送方式打开串口设备 */
    rt_device_open(serial, RT_DEVICE_FLAG_RX_NON_BLOCKING | RT_DEVICE_FLAG_TX_NON_BLOCKING);
 
    /* 创建 serial 线程 */
    rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
    /* 创建成功则启动线程 */
    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        ret = RT_ERROR;
    }
 
    return ret;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(uart_485_sample, uart device rs485 sample);