8. RS485#
目前硬件上支持RS485的有以下板卡:
LubanCat-3金手指/BTB底板(含有RS485x2)
8.1. 硬件标识和引脚定义#
8.1.1. 硬件标识#
LubanCat-3金手指/BTB正面图硬件标识图:
8.1.2. 引脚定义#
LubanCat-3金手指/BTB底板
485引脚 |
对应串口 |
流控引脚 |
RS485-1 |
UART3-M1 |
GPIO0_PC3 |
RS485-2 |
UART5-M0 |
GPIO0_PC5 |
8.2. 软件配置#
重要
野火镜像已支持RS485驱动层换流,不需要再在应用层手动换流,使用方法和普通串口一样,十分简单,因此建议使用驱动层换流。
8.2.1. 使用驱动层换流#
这里以开启RS485-1为例,开启和关闭,rs485对应的 rs485插件 ,支持RS485的板卡操作相同,只是设备树插件名字稍有差异。
8.2.1.1. 修改配置文件#
1#修改配置文件
2sudo vi /boot/uEnv/uEnv.txt
8.2.1.1.1. 开启设备树插件方法#
这里以LubanCat-3金手指/BTB底板 开启RS485-1 为例
把
#dtoverlay=/dtb/overlay/rk3576-lubancat-3io-rs485-1-overlay.dtbo前面的#删除,如下图
8.2.1.1.2. 关闭设备树插件方法#
这里以LubanCat-3金手指/BTB底板 关闭RS485-1 为例
在
dtoverlay=/dtb/overlay/rk3576-lubancat-3io-rs485-1-overlay.dtbo前面添加#,如下图
修改完配置文件后需要重启生效
1#重启
2sudo reboot
8.2.2. 使用应用层换流#
这里以开启LubanCat-3金手指/BTB底板RS485-1为例,开启和关闭rs485对应的 串口插件 ,支持RS485的板卡操作相同,只是设备树插件名字稍有差异,需要根据实际对应的串口进行修改。
8.2.2.1. 修改配置文件#
1#修改配置文件
2sudo vi /boot/uEnv/uEnv.txt
8.2.2.1.1. 开启设备树插件方法#
这里以LubanCat-3金手指/BTB底板为例
把
#dtoverlay=/dtb/overlay/rk3576-lubancat-uart3-m1-overlay.dtbo前面的#删除,如下图
8.2.2.1.2. 关闭设备树插件方法#
这里以LubanCat-3金手指/BTB底板为例
在
dtoverlay=/dtb/overlay/rk3576-lubancat-uart3-m1-overlay.dtbo前面添加#,如下图
修改完配置文件后需要重启生效
1#重启
2sudo reboot
8.2.3. 使用方法#
重要
使用驱动层换流不需要再在应用层手动换流,使用应用层换流则需要在应用层手动控制流控引脚电平实现换流。
这里以LubanCat-3金手指/BTB底板RS485-1为例,该底板RS485-1对应串口是UART3-M1。
LubanCat-3金手指/BTB底板RS485-1对应的设备树插件使能后,在/dev目录下生成ttyS3设备文件,用stty工具查询其通信参数
1#在板卡的终端执行如下命令
2sudo stty -F /dev/ttyS3
3
4#信息打印如下
5speed 9600 baud; line = 0;
6-brkint -imaxbel
8.2.3.1. 修改串口波特率#
1#设置通讯速率,其中ispeed为输入速率,ospeed为输出速率
2sudo stty -F /dev/ttyS3 ispeed 115200 ospeed 115200
3
4#再次查看通讯速率
5sudo stty -F /dev/ttyS3
6
7#信息打印如下
8speed 115200 baud; line = 0;
9-brkint -imaxbel
8.2.3.2. 关闭回显#
默认串口是开启回显的 可以使用以下命令关闭回显
1#关闭回显
2sudo stty -F /dev/ttyS3 -echo
如需恢复回显:
1#恢复回显
2sudo stty -F /dev/ttyS3 echo
8.2.3.3. 使用驱动层换流的通讯方法#
1#需要在root用户下运行,切换root
2sudo -s
3
4#操作和普通串口一样,直接收发即可
5#发送消息
6echo "I'm lubancat" > /dev/ttyS3
7
8#接收消息
9cat /dev/ttyS3
8.2.3.4. 使用应用层换流的通讯方法#
使用应用层换流需要在应用层手动控制流控引脚电平实现换流,如果想要发送信息,需要把发送引脚拉高,如果想要接收信息,需要把发送引脚拉低,具体流控引脚根据实际板卡而定。
板卡 |
RS485-1流控引脚 |
RS485-2流控引脚 |
LubanCat-3金手指/BTB底板 |
GPIO0_PC3 |
GPIO0_PC5 |
GPIO的操作方法
引脚编号此处不作过多说明,参考 GPIO控制章节
1、使用sysfs
以LubanCat-3金手指/BTB底板RS485-1的流控引脚为例,RS485-2的引脚的操作类似
1#需要在root用户下运行,切换root
2sudo -s
3
4#使能引脚
5echo 19 > /sys/class/gpio/export
6
7#设置引脚为输出模式
8echo out > /sys/class/gpio/gpio19/direction
9
10#设置引脚为低电平
11echo 0 > /sys/class/gpio/gpio19/value
12#流控引脚为低时,数据接收
13cat /dev/ttyS3
14
15#设置引脚为高电平
16echo 1 > /sys/class/gpio/gpio19/value
17#流控引脚为高时,数据发送
18echo "I'm lubancat" > /dev/ttyS3
2、使用gpiod
1#需要在root用户下运行,切换root
2sudo -s
3
4#设置RS485-1的引脚为高电平
5gpioset 0 19=1
6#流控引脚为高时,数据发送
7echo "I'm lubancat" > /dev/ttyS3
8
9#设置RS485-1的引脚为低电平
10gpioset 0 19=0
11#流控引脚为低时,数据接收
12cat /dev/ttyS3
8.3. 自检测试#
该测试需要把两个RS485引脚用线相连接。
RS485-1A |
连接 |
RS485-2A |
RS485-1B |
连接 |
RS485-2B |
然后需要同时打开RS485-1和RS485-2对应的设备树插件。
8.3.1. 使用驱动层换流自检测试#
根据前面介绍的使能RS485对应的 RS485设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行自检测试。
以LubanCat-3金手指/BTB底板为例:
1#需要在root用户下运行,切换root
2sudo -s
3
4#RS485-2后台接收数据
5cat /dev/ttyS5 &
6
7#RS485-1发送消息
8echo "1234567890" > /dev/ttyS3
9
10#终端打印信息
111234567890
发送完成就可以看到有接收到消息了。
提示
驱动层换流只支持中断模式,不支持DMA,以上提示DMA失败使用中断模式是正常的。UART使用DMA传输模式只有在数据量很大时才会产生较为明显的减轻CPU负载的效果。一般情况下,和使用中断传输模式相比,UART使用DMA传输模式并不一定能提高数据传输速度。一方面,CPU性能都很高,传输瓶颈在外设。另一方面,启动DMA需要消耗额外的资源,并且由于UART数据存在长度不确定的特性,会使DMA传输效率下降。
如果想结束后台运行的 cat /dev/ttyS5 &,参照以下操作
1pkill cat
RS485-1接收&RS485-2发送的方法与上述方法类似,只是将串口编号进行对调,可以自行修改操作。
8.3.2. 使用应用层换流自检测试#
根据前面介绍的使能RS485对应的 串口设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行自检测试。
以LubanCat-3金手指/BTB底板为例:
1#需要在root用户下运行,切换root
2sudo -s
3
4#RS485-2流控脚拉低,数据接收
5gpioset 0 21=0
6#RS485-2后台接收
7cat /dev/ttyS5 &
8
9#RS485-1流控脚拉高,数据发送
10gpioset 0 19=1
11#RS485-1发送消息
12echo "1234567890" > /dev/ttyS3
13
14#终端打印信息
151234567890
发送完成就可以看到有接收到消息了。
如果想结束后台运行的 cat /dev/ttyS5 &,参照以下操作
1pkill cat
RS485-1接收&RS485-2发送的方法与上述方法类似,只是将串口编号进行对调以及修改流控引脚电平,可以自行修改操作
8.4. C程序收发示例#
8.4.1. 使用驱动层换流C程序收发示例#
使用驱动层换流收发比较简单,和使用普通串口无异。
参考应用开发手册的 串口通讯实验(系统调用) 章节,将串口改为RS485实际对应的串口即可。
8.4.2. 使用应用层换流C程序收发示例#
此处示例在前面自检测试基础上编译C程序进行收发测试,如需实现其他功能可自行参考修改。
此处示例驱动层换流的也可以参考,只需将流控部分代码去掉即可。
根据前面介绍的使能RS485对应的设备树插件并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
以LubanCat-3金手指/BTB底板为例:
1#include <sys/types.h>
2#include <sys/stat.h>
3#include <fcntl.h>
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <stdint.h>
9
10#include <unistd.h>
11#include <termios.h>
12
13#define SERVER_GPIO_INDEX "19" //流控引脚对应编号
14const char default_path[] = "/dev/ttyS3"; //rs485-1对应的串口
15
16/*
17*串口配置成功返回0,失败返回-1;
18*/
19int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop);
20
21static int _server_ioctl_init(void)
22{
23 int fd;
24 //index config
25 fd = open("/sys/class/gpio/export", O_WRONLY);
26 if(fd < 0)
27 return 1;
28
29 write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));
30 close(fd);
31
32 //direction config
33 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);
34 if(fd < 0)
35 return 2;
36
37 write(fd, "out", strlen("out"));
38 close(fd);
39
40 return 0;
41}
42
43static int _server_ioctl_on(void)
44{
45 int fd;
46
47 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
48 if(fd < 0){
49 printf("_server_ioctl_open error\n");
50 return 1;
51 }
52 write(fd, "1", 1);
53 close(fd);
54 return 0;
55}
56
57static int _server_ioctl_off(void)
58{
59 int fd;
60
61 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
62 if(fd < 0)
63 return 1;
64
65 write(fd, "0", 1);
66 close(fd);
67 return 0;
68}
69
70static void _modbus_rtu_server_ioctl(int on)
71{
72 if (on) {
73 _server_ioctl_on();
74 } else {
75 _server_ioctl_off();
76 }
77}
78
79static int _server_ioctl_exit(void)
80{
81 int fd;
82 fd = open("/sys/class/gpio/uexport", O_WRONLY);
83 if(fd < 0)
84 return 1;
85
86 write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));
87 close(fd);
88
89}
90
91int main(int argc,char *argv[])
92{
93 int fd;
94 int res;
95 char *path;
96 //char buf[1024] = "Embedfire 485 send test.\n";
97 char buf[1024] = "1111111\n";
98 char buf1[1024];
99 int i;
100
101 if(argc > 1)
102 {
103 path = argv[1];
104 }
105 else
106 {
107 path = (char *)default_path;
108 }
109
110 fd = open(path,O_RDWR);
111 if(fd < 0)
112 {
113 perror(path);
114 exit(-1);
115 }
116
117 if( set_uart(fd,115200,8,'n',1) )
118 {
119 printf("set uart error\n");
120 }
121
122 _server_ioctl_init();
123
124 for(i=0;i<100;i++){
125 //485-1发送
126 _modbus_rtu_server_ioctl(1);
127 write(fd, buf, strlen(buf));
128 printf("485-1 Send data, res = %d bytes data: %s\r\n",strlen(buf), buf);
129 usleep(10000); // 10毫秒
130 //sleep(1);
131
132 //485-1读取
133 _modbus_rtu_server_ioctl(0);
134 memset(buf1,0,1024);
135 res = read(fd, buf1, 1024);
136 if(res > 0)
137 {
138 printf("485-1 Read data, res = %d bytes data: %s\r\n",res, buf1);
139 }
140
141 printf("cycle index is %d\r\n",i);
142 usleep(10000); // 10毫秒
143 //sleep(1);
144 }
145
146 _server_ioctl_exit();
147 close(fd);
148
149 return 0;
150}
151
152int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop)
153{
154
155 struct termios opt;
156
157 //清空串口接收缓冲区
158 tcflush(fd, TCIOFLUSH);
159
160 //获取串口配置参数
161 tcgetattr(fd, &opt);
162
163 opt.c_cflag &= (~CBAUD); //清除数据位设置
164 opt.c_cflag &= (~PARENB); //清除校验位设置
165
166 //opt.c_iflag |= IGNCR; //忽略接收数据中的'\r'字符,在windows中换行为'\r\n'
167 opt.c_iflag &= (~ICRNL); //不将'\r'转换为'\n'
168
169 opt.c_lflag &= (~ECHO); //不使用回显
170
171 //设置波特率
172 switch(nSpeed)
173 {
174 case 2400:
175 cfsetspeed(&opt,B2400);
176 break;
177
178 case 4800:
179 cfsetspeed(&opt,B4800);
180 break;
181
182 case 9600:
183 cfsetspeed(&opt,B9600);
184 break;
185
186 case 38400:
187 cfsetspeed(&opt,B38400);
188 break;
189
190 case 115200:
191 cfsetspeed(&opt,B115200);
192 break;
193
194 default:
195 return -1;
196 }
197
198 //设置数据位
199 switch(nBits)
200 {
201 case 7:
202 opt.c_cflag |= CS7;
203 break;
204
205 case 8:
206 opt.c_cflag |= CS8;
207 break;
208
209 default:
210 return -1;
211 }
212
213 //设置校验位
214 switch(nEvent)
215 {
216 //无奇偶校验
217 case 'n':
218 case 'N':
219 opt.c_cflag &= (~PARENB);
220 break;
221
222 //奇校验
223 case 'o':
224 case 'O':
225 opt.c_cflag |= PARODD;
226 break;
227
228 //偶校验
229 case 'e':
230 case 'E':
231 opt.c_cflag |= PARENB;
232 opt.c_cflag &= (~PARODD);
233 break;
234
235 default:
236 return -1;
237 }
238
239 //设置停止位
240 switch(nStop)
241 {
242 case 1:
243 opt.c_cflag &= ~CSTOPB;
244 break;
245 case 2:
246 opt.c_cflag |= CSTOPB;
247 break;
248 default:
249 return -1;
250 }
251 //设置串口
252 tcsetattr(fd,TCSANOW,&opt);
253
254 return 0;
255}
1#include <sys/types.h>
2#include <sys/stat.h>
3#include <fcntl.h>
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <stdint.h>
9
10#include <unistd.h>
11#include <termios.h>
12
13#define SERVER_GPIO_INDEX "21" //流控引脚的对应编号
14const char default_path[] = "/dev/ttyS5"; //rs485-2对应的串口
15
16/*
17*串口配置成功返回0,失败返回-1;
18*/
19int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop);
20
21static int _server_ioctl_init(void)
22{
23 int fd;
24 //index config
25 fd = open("/sys/class/gpio/export", O_WRONLY);
26 if(fd < 0)
27 return 1;
28
29 write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));
30 close(fd);
31
32 //direction config
33 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);
34 if(fd < 0)
35 return 2;
36
37 write(fd, "out", strlen("out"));
38 close(fd);
39
40 return 0;
41}
42
43static int _server_ioctl_on(void)
44{
45 int fd;
46
47 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
48 if(fd < 0)
49 return 1;
50
51 write(fd, "1", 1);
52 close(fd);
53 return 0;
54}
55
56static int _server_ioctl_off(void)
57{
58 int fd;
59
60 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
61 if(fd < 0)
62 return 1;
63
64 write(fd, "0", 1);
65 close(fd);
66 return 0;
67}
68
69static void _modbus_rtu_server_ioctl(int on)
70{
71 if (on) {
72 _server_ioctl_on();
73 } else {
74 _server_ioctl_off();
75 }
76}
77
78static int _server_ioctl_exit(void)
79{
80 int fd;
81 fd = open("/sys/class/gpio/uexport", O_WRONLY);
82 if(fd < 0)
83 return 1;
84
85 write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));
86 close(fd);
87
88}
89
90int main(int argc,char *argv[])
91{
92 int fd;
93 int res;
94 char *path;
95 char buf[1024] = "0000000\n";;
96 char buf1[1024];
97 int i;
98
99 if(argc > 1)
100 {
101 path = argv[1];
102 }
103 else
104 {
105 path = (char *)default_path;
106 }
107
108 fd = open(path,O_RDWR);
109 if(fd < 0)
110 {
111 perror(path);
112 exit(-1);
113 }
114
115 if( set_uart(fd,115200,8,'n',1) )
116 {
117 printf("set uart error\n");
118 }
119
120 _server_ioctl_init();
121
122 for(i=0;i<100;i++){
123
124 //485-2读取
125 _modbus_rtu_server_ioctl(0);
126 memset(buf1,0,1024);
127 res = read(fd, buf1, 1024);
128 if(res > 0)
129 {
130 printf("485-2 Read data, res = %d bytes data: %s\r\n",res, buf1);
131 }
132 usleep(10000); // 10毫秒
133 //sleep(1);
134
135 //485-2发送
136 _modbus_rtu_server_ioctl(1);
137 write(fd, buf, strlen(buf));
138 printf("485-2 Send data, res = %d bytes data: %s\r\n",strlen(buf), buf);
139
140 printf("cycle index is %d\r\n",i);
141 usleep(10000); // 10毫秒
142 //sleep(1);
143 }
144
145 _server_ioctl_exit();
146 close(fd);
147
148 return 0;
149}
150
151int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop)
152{
153
154 struct termios opt;
155
156 //清空串口接收缓冲区
157 tcflush(fd, TCIOFLUSH);
158
159 //获取串口配置参数
160 tcgetattr(fd, &opt);
161
162 opt.c_cflag &= (~CBAUD); //清除数据位设置
163 opt.c_cflag &= (~PARENB); //清除校验位设置
164
165 //opt.c_iflag |= IGNCR; //忽略接收数据中的'\r'字符,在windows中换行为'\r\n'
166 opt.c_iflag &= (~ICRNL); //不将'\r'转换为'\n'
167
168 opt.c_lflag &= (~ECHO); //不使用回显
169
170 //设置波特率
171 switch(nSpeed)
172 {
173 case 2400:
174 cfsetspeed(&opt,B2400);
175 break;
176
177 case 4800:
178 cfsetspeed(&opt,B4800);
179 break;
180
181 case 9600:
182 cfsetspeed(&opt,B9600);
183 break;
184
185 case 38400:
186 cfsetspeed(&opt,B38400);
187 break;
188
189 case 115200:
190 cfsetspeed(&opt,B115200);
191 break;
192
193 default:
194 return -1;
195 }
196
197 //设置数据位
198 switch(nBits)
199 {
200 case 7:
201 opt.c_cflag |= CS7;
202 break;
203
204 case 8:
205 opt.c_cflag |= CS8;
206 break;
207
208 default:
209 return -1;
210 }
211
212 //设置校验位
213 switch(nEvent)
214 {
215 //无奇偶校验
216 case 'n':
217 case 'N':
218 opt.c_cflag &= (~PARENB);
219 break;
220
221 //奇校验
222 case 'o':
223 case 'O':
224 opt.c_cflag |= PARODD;
225 break;
226
227 //偶校验
228 case 'e':
229 case 'E':
230 opt.c_cflag |= PARENB;
231 opt.c_cflag &= (~PARODD);
232 break;
233
234 default:
235 return -1;
236 }
237
238 //设置停止位
239 switch(nStop)
240 {
241 case 1:
242 opt.c_cflag &= ~CSTOPB;
243 break;
244 case 2:
245 opt.c_cflag |= CSTOPB;
246 break;
247 default:
248 return -1;
249 }
250 //设置串口
251 tcsetattr(fd,TCSANOW,&opt);
252
253 return 0;
254}
8.4.2.1. 编译程序#
1gcc rs485_1_test_client.c -o rs485_1_test_client
2gcc rs485_2_test_server.c -o rs485_2_test_server
8.4.2.2. 运行测试#
打开两个终端,分别运行两测试程序。终端可以是一个串口终端一个ssh终端,或者都使用ssh。
在终端1执行:
1sudo ./rs485_2_test_server
在终端2执行:
1sudo ./rs485_1_test_client
效果如下图所示,485-1和485-2相互收发100次,如果需要发送其他数据,可能需要调整发送和接收数据间的间隔时间,自行测试。
8.5. libmodbus简介#
libmodbus是一个与使用modbus协议的设备进行数据发送/接收的库,它包含各种后端(backends)通过不同网络进行通信。 (例如,RTU模式下的串口、485总线或TCP / IPv6中的以太网)。 libmodbus还提供了较低通信层的抽象,并在所有支持的平台上提供相同的API。
libmodbus是开源的,它遵循 LGPL v2.1 开源协议,这个协议没有GPL协议那么严格, 简单来说,只要你不修改libmodbus库里面的东西(只调用、链接该库),你是可以闭源你的代码的,也可以用于商业用途,这一点非常好的。
官方代码仓库位于:https://github.com/stephane/libmodbus
提示
如果使用驱动层换流只需对官方代码进行小修改,将串口改为实际串口即可,如果使用应用层换流需要配置rts引脚,野火也提供了示例测试,可进行参考。
8.5.1. 使用驱动层换流#
8.5.1.1. 前期准备#
根据前面介绍的使能RS485对应的 RS485设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
开发准备,在开发板系统上安装libmodbus-dev和一些编译工具。
1sudo apt update
2sudo apt install gcc make git autoconf libtool automake
1、拉取libmodbus源码并修改
1git clone https://github.com/stephane/libmodbus.git
拉取下来后看到本地有libmodbus文件夹,我们进入 libmodbus/tests目录下,该目录下存放了很多测试程序,此处我们使用unit-test-server.c、unit-test-client.c进行测试。
修改为使用使用的接口,以鲁班猫3金手指/btb底板为例:
打开libmodbus/tests/unit-test-server.c,将代码第57行和第76行的/dev/ttyUSB0修改为实际使用的串口,鲁班猫3金手指/btb底板rs485-1对应/dev/ttyS3
1#将libmodbus/tests/unit-test-server.c的第57行和第76行定义的接口修改为实际的接口
2...
3printf("Eg. tcp 127.0.0.1 or rtu /dev/ttyS3\n\n");
4...
5
6...
7ip_or_device = "/dev/ttyS3";
8...
打开libmodbus/tests/unit-test-client.c,将代码第93行和第112行的/dev/ttyUSB1修改为实际使用的串口,鲁班猫3金手指/btb底板rs485-2对应/dev/ttyS5
1#将libmodbus/tests/unit-test-client.c的第93行和第112行定义的接口修改为实际的接口
2...
3printf("Eg. tcp 127.0.0.1 or rtu /dev/ttyS5\n\n");
4...
5
6...
7ip_or_device = "/dev/ttyS5";
8...
2、编译程序
进入 libmodbus最上层目录 下,运行它提供的脚本,它主要是自动生成一些用于配置的文件。
1./autogen.sh
打印消息如下
1configure.ac:33: installing 'build-aux/compile'
2configure.ac:56: installing 'build-aux/config.guess'
3configure.ac:56: installing 'build-aux/config.sub'
4configure.ac:32: installing 'build-aux/install-sh'
5configure.ac:32: installing 'build-aux/missing'
6src/Makefile.am: installing 'build-aux/depcomp'
7parallel-tests: installing 'build-aux/test-driver'
8
9------------------------------------------------------
10Initialized build system. You can now run ./configure
11------------------------------------------------------
运行完毕后,接着运行 configure 去配置编译相关的信息。
1./configure
最后打印信息如下
1config.status: executing depfiles commands
2config.status: executing libtool commands
3
4 libmodbus 3.1.10
5 ===============
6
7 prefix: /usr/local
8 sysconfdir: ${prefix}/etc
9 libdir: ${exec_prefix}/lib
10 includedir: ${prefix}/include
11
12 compiler: gcc
13 cflags: -g -O2
14 ldflags:
15
16 tests: yes
运行上一步之后,在当前目录下将产生 Makefile 文件,使用 make 命令编译即可:
1make
在编译完成后,在 tests 目录下你会发现有很多可执行的文件,比如我们稍后要运行的程序 unit-test-server、unit-test-client:
1root@lubancat:~/libmodbus/tests# ls
2LICENSE bandwidth-server-one unit-test-client.c
3Makefile bandwidth-server-one.c unit-test-client.o
4Makefile.am bandwidth-server-one.o unit-test-server
5Makefile.in random-test-client unit-test-server.c
6README.md random-test-client.c unit-test-server.o
7bandwidth-client random-test-client.o unit-test.h
8bandwidth-client.c random-test-server unit-test.h.in
9bandwidth-client.o random-test-server.c unit-tests.sh
10bandwidth-server-many-up random-test-server.o version
11bandwidth-server-many-up.c stamp-h2 version.c
12bandwidth-server-many-up.o unit-test-client version.o
3、运行程序
打开两个终端,一个用于运行服务端一个用于运行客户端。如下所示
在终端1中执行:
1./unit-test-server rtu
在终端2中执行:
1./unit-test-client rtu
在 client 终端中最终打印
1[11][0F][01][60][00][00][00][02][2B][00][01][00][64][59][63]
2* try function 0xF: write 0 values: Waiting for a confirmation...
3<11><8F><03><05><F4>
4OK
5[11][0F][01][60][07][B0][06][02][2B][00][01][00][64][12][27]
6* try function 0xF: write 1968 values: Waiting for a confirmation...
7<11><8F><03><05><F4>
8OK
9[11][42][00][00][00][00][7B][55]
10Waiting for a confirmation...
11<11><C2><01><B1><65>
12Return an exception on unknown function code: OK
13
14TEST INVALID INITIALIZATION:
15The device string is empty
16OK
17The baud rate value must not be zero
18OK
19
20ALL TESTS PASS WITH SUCCESS.
以上只是测试使用,关于具体用法请读者参考官方说明自行研究。
8.5.2. 使用应用层层换流#
8.5.2.1. 前期准备#
根据前面介绍的使能RS485对应的 串口设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
开发准备,在开发板系统上安装libmodbus-dev和一些编译工具。
1sudo apt update
2sudo apt install gcc git libmodbus-dev pkg-config
8.5.2.2. 程序#
以鲁班猫3金手指/btb底板为例,其他底板需要自行修改流控引脚编号以及实际的串口接口。
1.服务端程序
1#include <stdio.h>
2#include <unistd.h>
3#include <string.h>
4#include <stdlib.h>
5#include <errno.h>
6#include <modbus.h>
7#include <sys/stat.h>
8#include <fcntl.h>
9
10#define SERVER_GPIO_INDEX "19" //485-1的换流引脚
11#define SERVER_ID 17
12const uint16_t UT_REGISTERS_TAB[] = { 0x0A, 0x0E, 0x0A, 0x1B,0x0A};
13
14static int _server_ioctl_init(void)
15{
16 int fd;
17 //index config
18 fd = open("/sys/class/gpio/export", O_WRONLY);
19 if(fd < 0)
20 return 1;
21
22 write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));
23 close(fd);
24
25 //direction config
26 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);
27 if(fd < 0)
28 return 2;
29
30 write(fd, "out", strlen("out"));
31 close(fd);
32
33 return 0;
34}
35
36static int _server_ioctl_on(void)
37{
38 int fd;
39
40 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
41 if(fd < 0)
42 return 1;
43
44 write(fd, "1", 1);
45 close(fd);
46 return 0;
47}
48
49static int _server_ioctl_off(void)
50{
51 int fd;
52
53 fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);
54 if(fd < 0)
55 return 1;
56
57 write(fd, "0", 1);
58 close(fd);
59 return 0;
60}
61
62static void _modbus_rtu_server_ioctl(modbus_t *ctx, int on)
63{
64 if (on) {
65 _server_ioctl_on();
66 } else {
67 _server_ioctl_off();
68 }
69}
70
71int main(int argc, char*argv[])
72{
73 modbus_t *ctx;
74 modbus_mapping_t *mb_mapping;
75 int rc;
76 int i;
77 uint8_t *query;
78 /*设置串口信息*/
79 ctx = modbus_new_rtu("/dev/ttyS3", 9600, 'N', 8, 1);
80 _server_ioctl_init();
81
82 /*设置从机地址,设置485模式*/
83 modbus_set_slave(ctx, SERVER_ID);
84 modbus_rtu_set_custom_rts(ctx, _modbus_rtu_server_ioctl);
85 modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP);
86 modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
87
88
89 query = malloc(MODBUS_RTU_MAX_ADU_LENGTH);
90 /*开启调试*/
91 modbus_set_debug(ctx, TRUE);
92
93 mb_mapping = modbus_mapping_new_start_address(0,0,0,0,0,5,0,0);
94 if (mb_mapping == NULL) {
95 fprintf(stderr, "Failed to allocate the mapping: %s\n",
96 modbus_strerror(errno));
97 modbus_free(ctx);
98 return -1;
99 }
100
101 /* 初始化值 */
102 for (i=0; i < 5; i++) {
103 mb_mapping->tab_registers[i] = UT_REGISTERS_TAB[i];
104 }
105
106 rc = modbus_connect(ctx);
107 if (rc == -1) {
108 fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
109 modbus_free(ctx);
110 return -1;
111 }
112 modbus_flush(ctx);
113
114 for (;;) {
115 do {
116 rc = modbus_receive(ctx, query);
117 } while (rc == 0);
118
119 rc = modbus_reply(ctx, query, rc, mb_mapping);
120 if (rc == -1) {
121 break;
122 }
123 }
124
125 modbus_mapping_free(mb_mapping);
126 free(query);
127
128 modbus_close(ctx);
129 modbus_free(ctx);
130 return 0;
131}
2.客户端程序
1#include <stdio.h>
2#include <unistd.h>
3#include <string.h>
4#include <stdlib.h>
5#include <errno.h>
6#include <modbus.h>
7#include <sys/stat.h>
8#include <fcntl.h>
9
10#define CLIENT_GPIO_INDEX "21" //485-2的换流引脚
11#define SERVER_ID 17
12
13static int _client_ioctl_init(void)
14{
15 int fd;
16 //index config
17 fd = open("/sys/class/gpio/export", O_WRONLY);
18 if(fd < 0)
19 return 1 ;
20
21 write(fd, CLIENT_GPIO_INDEX, strlen(CLIENT_GPIO_INDEX));
22 close(fd);
23
24 //direction config
25 fd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/direction", O_WRONLY);
26 if(fd < 0)
27 return 2;
28
29 write(fd, "out", strlen("out"));
30 close(fd);
31
32 return 0;
33}
34
35static int _client_ioctl_on(void)
36{
37 int fd;
38
39 fd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/value", O_WRONLY);
40 if(fd < 0)
41 return 1;
42
43 write(fd, "0", 1);
44 close(fd);
45
46 return 0;
47}
48
49static int _client_ioctl_off(void)
50{
51 int fd;
52
53 fd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/value", O_WRONLY);
54 if(fd < 0)
55 return 1;
56
57 write(fd, "1", 1);
58 close(fd);
59
60 return 0;
61}
62
63static void _modbus_rtu_client_ioctl(modbus_t *ctx, int on)
64{
65 if (on) {
66 _client_ioctl_on();
67 } else {
68 _client_ioctl_off();
69 }
70}
71
72int main(int argc, char *argv[])
73{
74 modbus_t *ctx = NULL;
75 int i,rc;
76 uint16_t tab_rp_registers[5] = {0}; //定义存放数据的数组
77
78 /*创建一个RTU类型的变量*/
79 /*设置要打开的串口设备 波特率 奇偶校验 数据位 停止位*/
80 ctx = modbus_new_rtu("/dev/ttyS5", 9600, 'N', 8, 1);
81 if (ctx == NULL) {
82 fprintf(stderr, "Unable to allocate libmodbus context\n");
83 return -1;
84 }
85
86 /*设置485模式*/
87 _client_ioctl_init();
88 modbus_rtu_set_custom_rts(ctx, _modbus_rtu_client_ioctl);
89 modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_DOWN);
90 modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
91
92 /*设置debug模式*/
93 modbus_set_debug(ctx, TRUE);
94
95 /*设置从机地址*/
96 modbus_set_slave(ctx, SERVER_ID);
97
98 /*RTU模式 打开串口*/
99 if (modbus_connect(ctx) == -1) {
100 fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
101 modbus_free(ctx);
102 return -1;
103 }
104
105 //读取多个连续寄存器
106 rc = modbus_read_registers(ctx, 0, 5, tab_rp_registers);
107 if (rc == -1)
108 {
109 fprintf(stderr,"%s\n", modbus_strerror(errno));
110 return -1;
111 }
112 for (i=0; i<5; i++)
113 {
114 //打印读取的数据
115 printf("reg[%d] = %d(0x%x)\n", i, tab_rp_registers[i], tab_rp_registers[i]);
116 }
117
118 modbus_close(ctx);
119 modbus_free(ctx);
120 return 0;
121}
8.5.2.3. 编译#
将两个程序的源文件传输到开发板系统,然后使用下面命令编译:
1gcc test-server.c -o test-server `pkg-config --cflags --libs libmodbus`
2gcc test-client.c -o test-client `pkg-config --cflags --libs libmodbus`
打开两个终端,一个用于运行服务端一个用于运行客户端(ssh终端和串口终端)。如下所示:
1root@lubancat:~$./test-server
1 root@lubancat:~$./test-client
在客户终端中最终打印
1Opening /dev/ttyS5 at 9600 bauds (N, 8, 1)
2[0A][03][00][00][00][05][84][B2]
3Sending request using RTS signal
4Waiting for a confirmation...
5<0A><03><0A><00><0A><00><0E><00><0A><00><1B><00><0A><C7><5C>
6reg[0] = 10(0xa)
7reg[1] = 14(0xe)
8reg[2] = 10(0xa)
9reg[3] = 27(0x1b)
10reg[4] = 10(0xa)
在服务终端中最终打印
1 Opening /dev/ttyS3 at 9600 bauds (N, 8, 1)
2 Bytes flushed (0)
3 Waiting for a indication...
4 <0A><03><00><00><00><05><84><B2>
5 [0A][03][0A][00][0A][00][0E][00][0A][00][1B][00][0A][C7][5C]
6 Sending request using RTS signal
7 Waiting for a indication...
以上代码均做测试用,关于具体用法请读者自行研究。 参考资料: https://github.com/stephane/libmodbus