8. RS485#

目前硬件上支持RS485的有以下板卡:

  1. LubanCat-3金手指/BTB底板(含有RS485x2)

8.1. 硬件标识和引脚定义#

8.1.1. 硬件标识#

LubanCat-3金手指/BTB正面图硬件标识图:

../../_images/rs485_0.jpg

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

发送完成就可以看到有接收到消息了。

../../_images/485-dri-send.png

提示

驱动层换流只支持中断模式,不支持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底板为例:

lubancat_rk_code_storage/quick_start/rs485/rs485_1_test_client.c#
  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}
lubancat_rk_code_storage/quick_start/rs485/rs485_2_test_server.c#
  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.服务端程序

lubancat_rk_code_storage/quick_start/rs485/test-server.c#
  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.客户端程序

lubancat_rk_code_storage/quick_start/rs485/test-client.c#
  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