RS485和RS232 通信配置
RS232
目前硬件上支持RS232的有以下板卡:
-
LubanCat-5IO底板(含有RS232x2)
7.1. 引脚定义
具体的引脚定义可以参考背面的丝印
-
LubanCat-5IO底板
引脚定义图
7.2. 跳帽配置
-
LubanCat-5IO底板
鲁班买5IO底板上的RS485和RS232是共用同一组UART信号的,使用跳帽切换RS485或者RS232
7.3. 软件配置
232引脚 | 对应串口 |
RS232-1 | UART4_M2 |
RS232-2 | UART7_M2 |
这里以开启RS232-1为例,开启RS232-1即开启UART4_M2设备树插件,相应的开启RS232-2即开启UART7_M2设备树插件
-
修改配置文件
1 2 | #修改配置文件 sudo vi /boot/uEnv/uEnv.txt |
7.3.1. 开启设备树插件
这里以打开RS232-1为例
-
把
#dtoverlay=/dtb/overlay/rk3588-lubancat-uart4-m2-overlay.dtbo
前面的#删除,如下图
7.3.2. 关闭设备树插件
这里以关闭RS232-1为例
-
在
dtoverlay=/dtb/overlay/rk3588-lubancat-uart4-m2-overlay.dtbo
前面添加#,如下图
修改完配置文件后需要重启生效
1 2 | #重启 sudo reboot |
7.4. 使用方法
这里以RS232-1为例
7.4.1. 查询串口4的通信参数
串口4外设使能后,在/dev目录下生成ttyS4设备文件,用stty工具查询其通信参数
1 2 3 4 | #需要在root用户下运行#在板卡的终端执行如下命令 stty -F /dev/ttyS4 |
如下图:
7.4.2. 修改串口波特率
1 2 3 4 | #需要在root用户下运行#设置通讯速率,其中ispeed为输入速率,ospeed为输出速率 stty -F /dev/ttyS4 ispeed 115200 ospeed 115200 |
如下图:
7.4.3. 关闭回显
默认串口是开启回显的 可以使用以下命令关闭回显
1 2 3 | #需要在root用户下运行stty -F /dev/ttyS4 -echo |
7.4.4. 通讯
1 2 3 4 5 6 7 | #需要在root用户下运行#发送消息 echo "I'm lubancat" > /dev/ttyS4#接收消息 cat /dev/ttyS4 |
除了这种方法外,还可以使用minicom,具体方法可以参考下列文章 串口章节
7.5. 自检测试
该测试需要把RS232引脚的相连接
TX1 | 连接 | RX2 |
RX1 | 连接 | TX2 |
然后需要同时打开RS232-1和RS232-2的设备树插件。
1 2 3 4 5 6 7 8 | #需要在root用户下运行#RS232-2后台接收 cat /dev/ttyS7 &#RS232-1发送消息 echo "1234567890" > /dev/ttyS4 echo "abcdefghijklmn" > /dev/ttyS4 |
发送完成就可以看到有接收到消息了,如下图所示
如果想结束后台运行的 cat /dev/ttyS7 &
,参照以下操作
1 | pkill cat |
RS232-1接收&RS232-2发送的方法与上述方法类似,可以自行修改操作。
RS485
目前硬件上支持RS485的有以下板卡:
-
LubanCat-5IO底板(含有RS485x2)
8.1. 引脚定义
具体的引脚定义可以参考背面的丝印
-
LubanCat-5IO底板
引脚定义图
8.2. 跳帽配置
-
LubanCat-5IO底板
鲁班买5IO底板上的RS485和RS232是共用同一组UART信号的,使用跳帽切换RS485或者RS232
8.3. 对应串口
LubanCat-5IO底板RS485对应UART接口如下:
485引脚 | 对应串口 |
RS485-1 | UART4_M2 |
RS485-2 | UART7-M2 |
8.3.1. 软件配置
重要
野火2024年9月12日支持RS485驱动层换流,不需要再在应用层手动换流,使用方法和普通串口一样,十分简单,因此建议使用新镜像或更新内核,使用驱动层换流。如果比2024年9月12日旧的镜像可以更新内核或者直接使用新镜像。
8.4. 使用驱动层换流
这里以开启RS485-1为例,开启和关闭,rs485对应的 rs485插件 ,支持RS485的板卡操作相同,只是设备树插件名字稍有差异。
1 2 | #修改配置文件 sudo vi /boot/uEnv/uEnv.txt |
这里以LubanCat-5IO底板 开启RS485-1 为例
-
把
#dtoverlay=/dtb/overlay/rk3588-lubancat-5io-rs485-1-overlay.dtbo
前面的#删除,如下图
这里以LubanCat-5IO底板 关闭RS485-1 为例
-
在
#dtoverlay=/dtb/overlay/rk3588-lubancat-5io-rs485-1-overlay.dtbo
前面添加#,如下图
修改完配置文件后需要重启生效
1 2 | #重启 sudo reboot |
8.5. 使用应用层换流
这里以开启LubanCat-5IO底板RS485-1为例,开启和关闭rs485对应的 串口插件 ,支持RS485的板卡操作相同,只是设备树插件名字稍有差异,需要根据实际对应的串口进行修改。
1 2 | #修改配置文件 sudo vi /boot/uEnv/uEnv.txt |
这里以LubanCat-5IO底板为例
-
把
#dtoverlay=/dtb/overlay/rk3588-lubancat-uart4-m2-overlay.dtbo
前面的#删除,如下图
这里以LubanCat-5IO底板为例
-
在
#dtoverlay=/dtb/overlay/rk3588-lubancat-uart4-m2-overlay.dtbo
前面添加#,如下图
修改完配置文件后需要重启生效
1 2 | #重启 sudo reboot |
8.6. 使用方法
重要
使用驱动层换流不需要再在应用层手动换流,使用应用层换流则需要在应用层手动控制流控引脚电平实现换流。
这里以LubanCat-5IO底板RS485-1为例,该底板RS485-1对应串口是UART4-M2。
LubanCat-5IO底板RS485-1对应的设备树插件使能后,在/dev目录下生成ttyS4设备文件,用stty工具查询其通信参数
1 2 3 4 5 6 7 8 | #需要在root用户下运行#在板卡的终端执行如下命令 stty -F /dev/ttyS4#信息输出如下 speed 9600 baud; line = 0; -brkint -imaxbel |
1 2 3 4 | #需要在root用户下运行#设置通讯速率,其中ispeed为输入速率,ospeed为输出速率 stty -F /dev/ttyS4 ispeed 115200 ospeed 115200 |
默认串口是开启回显的 可以使用以下命令关闭回显
1 2 3 | #需要在root用户下运行stty -F /dev/ttyS4 -echo |
1 2 3 4 5 6 7 8 | #需要在root用户下运行#操作和普通串口一样,直接收发即可 #发送消息 echo "I'm lubancat" > /dev/ttyS4#接收消息 cat /dev/ttyS4 |
除了这种方法外,还可以使用minicom,具体方法可以参考下列文章 串口章节
使用应用层换流需要在应用层手动控制流控引脚电平实现换流,如果想要发送信息,需要把发送引脚拉高,如果想要接收信息,需要把发送引脚拉低,具体流控引脚根据实际板卡而定。
板卡 | RS485-1流控引脚 | RS485-2流控引脚 |
LubanCat-5IO底板 | GPIO3_PC7 | GPIO3_PD0 |
GPIO的操作方法
引脚编号此处不作过多说明,参考 GPIO控制章节
1、使用sysfs
以LubanCat-5IO底板RS485-1的流控引脚为例,RS485-2的引脚的操作类似
123456789 10 11 12 13 14 15 16 17 | #需要在root用户下运行#使能引脚 echo 119 > /sys/class/gpio/export#设置引脚为输出模式 echo out > /sys/class/gpio/gpio119/direction#设置引脚为低电平 echo 0 > /sys/class/gpio/gpio119/value #流控引脚为低时,数据接收 cat /dev/ttyS4#设置引脚为高电平 echo 1 > /sys/class/gpio/gpio119/value #流控引脚为高时,数据发送 echo "I'm lubancat" > /dev/ttyS4 |
2、使用gpiod
123456789 10 11 | #需要在root用户下运行#设置RS485-1的引脚为高电平 gpioset 3 23=1 #流控引脚为高时,数据发送 echo "I'm lubancat" > /dev/ttyS4#设置RS485-1的引脚为低电平 gpioset 3 23=0 #流控引脚为低时,数据接收 cat /dev/ttyS4 |
8.6.1. 自检测试
该测试需要把两个RS485引脚用线相连接。
RS485-1A | 连接 | RS485-2A |
RS485-1B | 连接 | RS485-2B |
然后需要同时打开RS485-1和RS485-2对应的设备树插件。
8.7. 使用驱动层换流自检测试
根据前面介绍的使能RS485对应的 RS485设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行自检测试。
以LubanCat-5IO底板为例:
1 2 3 4 5 6 7 8 | #需要在root用户下运行#RS485-2后台接收数据 cat /dev/ttyS7 &#RS485-1发送消息 echo "12345678119" > /dev/ttyS4 echo "abcdefghijklmn" > /dev/ttyS4 |
发送完成就可以看到有接收到消息了,如下图所示
提示
驱动层换流只支持中断模式,不支持DMA,以上提示DMA失败使用中断模式是正常的。UART使用DMA传输模式只有在数据量很大时才会产生较为明显的减轻CPU负载的效果。一般情况下,和使用中断传输模式相比,UART使用DMA传输模式并不一定能提高数据传输速度。一方面,CPU性能都很高,传输瓶颈在外设。另一方面,启动DMA需要消耗额外的资源,并且由于UART数据存在长度不确定的特性,会使DMA传输效率下降。
如果想结束后台运行的 cat /dev/ttyS7 &
,参照以下操作
1 | pkill cat |
RS485-1接收&RS485-2发送的方法与上述方法类似,只是将串口编号进行对调,可以自行修改操作。
8.8. 使用应用层换流自检测试
根据前面介绍的使能RS485对应的 串口设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行自检测试。
以LubanCat-5IO底板为例:
123456789 10 11 | #需要在root用户下运行#RS485-2流控脚拉低,数据接收 gpioset 3 24=0 #RS485-2后台接收 cat /dev/ttyS7 &#RS485-1流控脚拉高,数据发送 gpioset 3 23=1 #RS485-1发送消息 echo "12345678119" > /dev/ttyS4 |
发送完成就可以看到有接收到消息了,如下图所示
如果想结束后台运行的 cat /dev/ttyS7 &
,参照以下操作
1 | pkill cat |
RS485-1接收&RS485-2发送的方法与上述方法类似,只是将串口编号进行对调以及修改流控引脚电平,可以自行修改操作
8.8.1. C程序收发示例
8.9. 使用驱动层换流C程序收发示例
使用驱动层换流收发比较简单,和使用普通串口无异。
参考应用开发手册的 串口通讯实验(系统调用) 章节,将串口改为RS485实际对应的串口即可。
8.10. 使用应用层换流C程序收发示例
此处示例在前面自检测试基础上编译C程序进行收发测试,如需实现其他功能可自行参考修改。
此处示例驱动层换流的也可以参考,只需将流控部分代码去掉即可。
根据前面介绍的使能RS485对应的设备树插件并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
以LubanCat-5IO底板为例:
lubancat_rk_code_storage/quick_start/rs485/rs485_1_test_client.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 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 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h>#include <unistd.h> #include <termios.h>#define SERVER_GPIO_INDEX "119" //流控引脚对应编号 const char default_path[] = "/dev/ttyS4"; //rs485-1对应的串口/* *串口配置成功返回0,失败返回-1; */ int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop);static int _server_ioctl_init(void) {int fd;//index configfd = open("/sys/class/gpio/export", O_WRONLY);if(fd < 0)return 1;write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));close(fd);//direction configfd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);if(fd < 0)return 2;write(fd, "out", strlen("out"));close(fd);return 0; }static int _server_ioctl_on(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0){printf("_server_ioctl_open error\n");return 1;}write(fd, "1", 1);close(fd);return 0; }static int _server_ioctl_off(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "0", 1);close(fd);return 0; }static void _modbus_rtu_server_ioctl(int on) {if (on) {_server_ioctl_on();} else {_server_ioctl_off();} }static int _server_ioctl_exit(void) {int fd;fd = open("/sys/class/gpio/uexport", O_WRONLY);if(fd < 0)return 1;write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));close(fd);}int main(int argc,char *argv[]) {int fd;int res;char *path;//char buf[1024] = "Embedfire 485 send test.\n";char buf[1024] = "1111111\n";char buf1[1024];int i;if(argc > 1){path = argv[1];}else{path = (char *)default_path;}fd = open(path,O_RDWR);if(fd < 0){perror(path);exit(-1);}if( set_uart(fd,115200,8,'n',1) ){printf("set uart error\n");}_server_ioctl_init();for(i=0;i<100;i++){//485-1发送_modbus_rtu_server_ioctl(1);write(fd, buf, strlen(buf));printf("485-1 Send data, res = %d bytes data: %s\r\n",strlen(buf), buf);usleep(10000); // 10毫秒//sleep(1);//485-1读取_modbus_rtu_server_ioctl(0);memset(buf1,0,1024);res = read(fd, buf1, 1024);if(res > 0){printf("485-1 Read data, res = %d bytes data: %s\r\n",res, buf1);}printf("cycle index is %d\r\n",i);usleep(10000); // 10毫秒//sleep(1);}_server_ioctl_exit();close(fd);return 0; }int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop) {struct termios opt;//清空串口接收缓冲区tcflush(fd, TCIOFLUSH);//获取串口配置参数tcgetattr(fd, &opt);opt.c_cflag &= (~CBAUD); //清除数据位设置opt.c_cflag &= (~PARENB); //清除校验位设置//opt.c_iflag |= IGNCR; //忽略接收数据中的'\r'字符,在windows中换行为'\r\n'opt.c_iflag &= (~ICRNL); //不将'\r'转换为'\n'opt.c_lflag &= (~ECHO); //不使用回显//设置波特率switch(nSpeed){case 2400:cfsetspeed(&opt,B2400);break;case 4800:cfsetspeed(&opt,B4800);break;case 9600:cfsetspeed(&opt,B9600);break;case 38400:cfsetspeed(&opt,B38400);break;case 115200:cfsetspeed(&opt,B115200);break;default:return -1;}//设置数据位switch(nBits){case 7:opt.c_cflag |= CS7;break;case 8:opt.c_cflag |= CS8;break;default:return -1;}//设置校验位switch(nEvent){//无奇偶校验case 'n':case 'N':opt.c_cflag &= (~PARENB);break;//奇校验case 'o':case 'O':opt.c_cflag |= PARODD;break;//偶校验case 'e':case 'E':opt.c_cflag |= PARENB;opt.c_cflag &= (~PARODD);break;default:return -1;}//设置停止位switch(nStop){case 1:opt.c_cflag &= ~CSTOPB;break;case 2:opt.c_cflag |= CSTOPB;break;default:return -1;}//设置串口tcsetattr(fd,TCSANOW,&opt);return 0; } |
lubancat_rk_code_storage/quick_start/rs485/rs485_2_test_server.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 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 | #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h>#include <unistd.h> #include <termios.h>#define SERVER_GPIO_INDEX "120" //流控引脚的对应编号 const char default_path[] = "/dev/ttyS7"; //rs485-2对应的串口/* *串口配置成功返回0,失败返回-1; */ int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop);static int _server_ioctl_init(void) {int fd;//index configfd = open("/sys/class/gpio/export", O_WRONLY);if(fd < 0)return 1;write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));close(fd);//direction configfd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);if(fd < 0)return 2;write(fd, "out", strlen("out"));close(fd);return 0; }static int _server_ioctl_on(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "1", 1);close(fd);return 0; }static int _server_ioctl_off(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "0", 1);close(fd);return 0; }static void _modbus_rtu_server_ioctl(int on) {if (on) {_server_ioctl_on();} else {_server_ioctl_off();} }static int _server_ioctl_exit(void) {int fd;fd = open("/sys/class/gpio/uexport", O_WRONLY);if(fd < 0)return 1;write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));close(fd);}int main(int argc,char *argv[]) {int fd;int res;char *path;char buf[1024] = "0000000\n";;char buf1[1024];int i;if(argc > 1){path = argv[1];}else{path = (char *)default_path;}fd = open(path,O_RDWR);if(fd < 0){perror(path);exit(-1);}if( set_uart(fd,115200,8,'n',1) ){printf("set uart error\n");}_server_ioctl_init();for(i=0;i<100;i++){//485-2读取_modbus_rtu_server_ioctl(0);memset(buf1,0,1024);res = read(fd, buf1, 1024);if(res > 0){printf("485-2 Read data, res = %d bytes data: %s\r\n",res, buf1);}usleep(10000); // 10毫秒//sleep(1);//485-2发送_modbus_rtu_server_ioctl(1);write(fd, buf, strlen(buf));printf("485-2 Send data, res = %d bytes data: %s\r\n",strlen(buf), buf);printf("cycle index is %d\r\n",i);usleep(10000); // 10毫秒//sleep(1);}_server_ioctl_exit();close(fd);return 0; }int set_uart(int fd,int nSpeed, int nBits, char nEvent, int nStop) {struct termios opt;//清空串口接收缓冲区tcflush(fd, TCIOFLUSH);//获取串口配置参数tcgetattr(fd, &opt);opt.c_cflag &= (~CBAUD); //清除数据位设置opt.c_cflag &= (~PARENB); //清除校验位设置//opt.c_iflag |= IGNCR; //忽略接收数据中的'\r'字符,在windows中换行为'\r\n'opt.c_iflag &= (~ICRNL); //不将'\r'转换为'\n'opt.c_lflag &= (~ECHO); //不使用回显//设置波特率switch(nSpeed){case 2400:cfsetspeed(&opt,B2400);break;case 4800:cfsetspeed(&opt,B4800);break;case 9600:cfsetspeed(&opt,B9600);break;case 38400:cfsetspeed(&opt,B38400);break;case 115200:cfsetspeed(&opt,B115200);break;default:return -1;}//设置数据位switch(nBits){case 7:opt.c_cflag |= CS7;break;case 8:opt.c_cflag |= CS8;break;default:return -1;}//设置校验位switch(nEvent){//无奇偶校验case 'n':case 'N':opt.c_cflag &= (~PARENB);break;//奇校验case 'o':case 'O':opt.c_cflag |= PARODD;break;//偶校验case 'e':case 'E':opt.c_cflag |= PARENB;opt.c_cflag &= (~PARODD);break;default:return -1;}//设置停止位switch(nStop){case 1:opt.c_cflag &= ~CSTOPB;break;case 2:opt.c_cflag |= CSTOPB;break;default:return -1;}//设置串口tcsetattr(fd,TCSANOW,&opt);return 0; } |
1 2 | gcc rs485_1_test_client.c -o rs485_1_test_client gcc rs485_2_test_server.c -o rs485_2_test_server |
打开两个终端,分别运行两测试程序。终端可以是一个串口终端一个ssh终端,或者都使用ssh。
在终端1执行:
1 | sudo ./rs485_2_test_server |
在终端2执行:
1 | sudo ./rs485_1_test_client |
效果如下图所示,485-1和485-2相互收发100次,如果需要发送其他数据,可能需要调整发送和接收数据间的间隔时间,自行测试。
8.10.1. libmodbus简介
libmodbus是一个与使用modbus协议的设备进行数据发送/接收的库,它包含各种后端(backends)通过不同网络进行通信。 (例如,RTU模式下的串口、485总线或TCP / IPv6中的以太网)。 libmodbus还提供了较低通信层的抽象,并在所有支持的平台上提供相同的API。
libmodbus是开源的,它遵循 LGPL v2.1 开源协议,这个协议没有GPL协议那么严格, 简单来说,只要你不修改libmodbus库里面的东西(只调用、链接该库),你是可以闭源你的代码的,也可以用于商业用途,这一点非常好的。
官方代码仓库位于:GitHub - stephane/libmodbus: A Modbus library for Linux, Mac OS, FreeBSD and Windows
提示
如果使用驱动层换流只需对官方代码进行小修改,将串口改为实际串口即可,如果使用应用层换流需要配置rts引脚,野火也提供了示例测试,可进行参考。
8.11. 使用驱动层换流
根据前面介绍的使能RS485对应的 RS485设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
开发准备,在开发板系统上安装libmodbus-dev和一些编译工具。
1 2 | sudo apt update sudo apt install gcc make git autoconf libtool automake |
1、拉取libmodbus源码并修改
1 | git clone https://github.com/stephane/libmodbus.git |
拉取下来后看到本地有libmodbus文件夹,我们进入 libmodbus/tests目录下,该目录下存放了很多测试程序,此处我们使用unit-test-server.c、unit-test-client.c进行测试。
修改为使用使用的接口,以鲁班猫2金手指/btb底板为例:
打开libmodbus/tests/unit-test-server.c,将代码第57行和第76行的/dev/ttyUSB0修改为实际使用的串口,鲁班猫2金手指/btb底板rs485-1对应/dev/ttyS4
1 2 3 4 5 6 7 8 | #将libmodbus/tests/unit-test-server.c的第57行和第76行定义的接口修改为实际的接口 ... printf("Eg. tcp 127.0.0.1 or rtu /dev/ttyS4\n\n"); ...... ip_or_device = "/dev/ttyS4"; ... |
打开libmodbus/tests/unit-test-client.c,将代码第93行和第112行的/dev/ttyUSB1修改为实际使用的串口,鲁班猫2金手指/btb底板rs485-2对应/dev/ttyS7
1 2 3 4 5 6 7 8 | #将libmodbus/tests/unit-test-client.c的第93行和第112行定义的接口修改为实际的接口 ... printf("Eg. tcp 127.0.0.1 or rtu /dev/ttyS7\n\n"); ...... ip_or_device = "/dev/ttyS7"; ... |
2、编译程序
进入 libmodbus最上层目录 下,运行它提供的脚本,它主要是自动生成一些用于配置的文件。
1 | ./autogen.sh |
打印消息如下
123456789 10 11 | configure.ac:33: installing 'build-aux/compile' configure.ac:56: installing 'build-aux/config.guess' configure.ac:56: installing 'build-aux/config.sub' configure.ac:32: installing 'build-aux/install-sh' configure.ac:32: installing 'build-aux/missing' src/Makefile.am: installing 'build-aux/depcomp' parallel-tests: installing 'build-aux/test-driver'------------------------------------------------------ Initialized build system. You can now run ./configure ------------------------------------------------------ |
运行完毕后,接着运行 configure 去配置编译相关的信息。
1 | ./configure |
最后打印信息如下
123456789 10 11 12 13 14 15 16 | config.status: executing depfiles commands config.status: executing libtool commandslibmodbus 3.1.10===============prefix: /usr/localsysconfdir: ${prefix}/etclibdir: ${exec_prefix}/libincludedir: ${prefix}/includecompiler: gcccflags: -g -O2ldflags:tests: yes |
运行上一步之后,在当前目录下将产生 Makefile 文件,使用 make 命令编译即可:
1 | make |
在编译完成后,在 tests 目录下你会发现有很多可执行的文件,比如我们稍后要运行的程序 unit-test-server、unit-test-client:
123456789 10 11 12 | root@lubancat:~/libmodbus/tests# ls LICENSE bandwidth-server-one unit-test-client.c Makefile bandwidth-server-one.c unit-test-client.o Makefile.am bandwidth-server-one.o unit-test-server Makefile.in random-test-client unit-test-server.c README.md random-test-client.c unit-test-server.o bandwidth-client random-test-client.o unit-test.h bandwidth-client.c random-test-server unit-test.h.in bandwidth-client.o random-test-server.c unit-tests.sh bandwidth-server-many-up random-test-server.o version bandwidth-server-many-up.c stamp-h2 version.c bandwidth-server-many-up.o unit-test-client version.o |
3、运行程序
打开两个终端,一个用于运行服务端一个用于运行客户端。如下所示
在终端1中执行:
1 | ./unit-test-server rtu |
在终端2中执行:
1 | ./unit-test-client rtu |
在 client 终端中最终打印
123456789 10 11 12 13 14 15 16 17 18 19 20 | [11][0F][01][60][00][00][00][02][2B][00][01][00][64][59][63] * try function 0xF: write 0 values: Waiting for a confirmation... <11><8F><03><05><F4> OK [11][0F][01][60][07][B0][06][02][2B][00][01][00][64][12][27] * try function 0xF: write 1968 values: Waiting for a confirmation... <11><8F><03><05><F4> OK [11][42][00][00][00][00][7B][55] Waiting for a confirmation... <11><C2><01><B1><65> Return an exception on unknown function code: OKTEST INVALID INITIALIZATION: The device string is empty OK The baud rate value must not be zero OKALL TESTS PASS WITH SUCCESS. |
以上只是测试使用,关于具体用法请读者参考官方说明自行研究。
8.12. 使用应用层层换流
根据前面介绍的使能RS485对应的 串口设备树插件 并且正确将RS485-1和RS485-2相连接后,可进行后续步骤。
开发准备,在开发板系统上安装libmodbus-dev和一些编译工具。
1 2 | sudo apt update sudo apt install gcc git libmodbus-dev pkg-config |
以鲁班猫2金手指/btb底板为例,其他底板需要自行修改流控引脚编号以及实际的串口接口。
1.服务端程序
lubancat_rk_code_storage/quick_start/rs485/test-server.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 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 | #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <modbus.h> #include <sys/stat.h> #include <fcntl.h>#define SERVER_GPIO_INDEX "119" //485-1的换流引脚 #define SERVER_ID 17 const uint16_t UT_REGISTERS_TAB[] = { 0x0A, 0x0E, 0x0A, 0x1B,0x0A};static int _server_ioctl_init(void) {int fd;//index configfd = open("/sys/class/gpio/export", O_WRONLY);if(fd < 0)return 1;write(fd, SERVER_GPIO_INDEX, strlen(SERVER_GPIO_INDEX));close(fd);//direction configfd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/direction", O_WRONLY);if(fd < 0)return 2;write(fd, "out", strlen("out"));close(fd);return 0; }static int _server_ioctl_on(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "1", 1);close(fd);return 0; }static int _server_ioctl_off(void) {int fd;fd = open("/sys/class/gpio/gpio" SERVER_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "0", 1);close(fd);return 0; }static void _modbus_rtu_server_ioctl(modbus_t *ctx, int on) {if (on) {_server_ioctl_on();} else {_server_ioctl_off();} }int main(int argc, char*argv[]) {modbus_t *ctx;modbus_mapping_t *mb_mapping;int rc;int i;uint8_t *query;/*设置串口信息*/ctx = modbus_new_rtu("/dev/ttyS4", 9600, 'N', 8, 1);_server_ioctl_init();/*设置从机地址,设置485模式*/modbus_set_slave(ctx, SERVER_ID);modbus_rtu_set_custom_rts(ctx, _modbus_rtu_server_ioctl);modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP);modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);query = malloc(MODBUS_RTU_MAX_ADU_LENGTH);/*开启调试*/modbus_set_debug(ctx, TRUE);mb_mapping = modbus_mapping_new_start_address(0,0,0,0,0,5,0,0);if (mb_mapping == NULL) {fprintf(stderr, "Failed to allocate the mapping: %s\n",modbus_strerror(errno));modbus_free(ctx);return -1;}/* 初始化值 */for (i=0; i < 5; i++) {mb_mapping->tab_registers[i] = UT_REGISTERS_TAB[i];}rc = modbus_connect(ctx);if (rc == -1) {fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));modbus_free(ctx);return -1;}modbus_flush(ctx);for (;;) {do {rc = modbus_receive(ctx, query);} while (rc == 0);rc = modbus_reply(ctx, query, rc, mb_mapping);if (rc == -1) {break;}}modbus_mapping_free(mb_mapping);free(query);modbus_close(ctx);modbus_free(ctx);return 0; } |
2.客户端程序
lubancat_rk_code_storage/quick_start/rs485/test-client.c
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <modbus.h> #include <sys/stat.h> #include <fcntl.h>#define CLIENT_GPIO_INDEX "91" //485-2的换流引脚 #define SERVER_ID 17static int _client_ioctl_init(void) {int fd;//index configfd = open("/sys/class/gpio/export", O_WRONLY);if(fd < 0)return 1 ;write(fd, CLIENT_GPIO_INDEX, strlen(CLIENT_GPIO_INDEX));close(fd);//direction configfd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/direction", O_WRONLY);if(fd < 0)return 2;write(fd, "out", strlen("out"));close(fd);return 0; }static int _client_ioctl_on(void) {int fd;fd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "0", 1);close(fd);return 0; }static int _client_ioctl_off(void) {int fd;fd = open("/sys/class/gpio/gpio" CLIENT_GPIO_INDEX "/value", O_WRONLY);if(fd < 0)return 1;write(fd, "1", 1);close(fd);return 0; }static void _modbus_rtu_client_ioctl(modbus_t *ctx, int on) {if (on) {_client_ioctl_on();} else {_client_ioctl_off();} }int main(int argc, char *argv[]) {modbus_t *ctx = NULL;int i,rc;uint16_t tab_rp_registers[5] = {0}; //定义存放数据的数组/*创建一个RTU类型的变量*//*设置要打开的串口设备 波特率 奇偶校验 数据位 停止位*/ctx = modbus_new_rtu("/dev/ttyS7", 9600, 'N', 8, 1);if (ctx == NULL) {fprintf(stderr, "Unable to allocate libmodbus context\n");return -1;}/*设置485模式*/_client_ioctl_init();modbus_rtu_set_custom_rts(ctx, _modbus_rtu_client_ioctl);modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_DOWN);modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);/*设置debug模式*/modbus_set_debug(ctx, TRUE);/*设置从机地址*/modbus_set_slave(ctx, SERVER_ID);/*RTU模式 打开串口*/if (modbus_connect(ctx) == -1) {fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));modbus_free(ctx);return -1;}//读取多个连续寄存器rc = modbus_read_registers(ctx, 0, 5, tab_rp_registers);if (rc == -1){fprintf(stderr,"%s\n", modbus_strerror(errno));return -1;}for (i=0; i<5; i++){//打印读取的数据printf("reg[%d] = %d(0x%x)\n", i, tab_rp_registers[i], tab_rp_registers[i]);}modbus_close(ctx);modbus_free(ctx);return 0; } |
将两个程序的源文件传输到开发板系统,然后使用下面命令编译:
1 2 | gcc test-server.c -o test-server `pkg-config --cflags --libs libmodbus` gcc test-client.c -o test-client `pkg-config --cflags --libs libmodbus` |
打开两个终端,一个用于运行服务端一个用于运行客户端(ssh终端和串口终端)。如下所示:
服务终端
1 | root@lubancat:~$./test-server |
客户终端
1 | root@lubancat:~$./test-client |
在客户终端中最终打印
客户终端
123456789 10 | Opening /dev/ttyS7 at 9600 bauds (N, 8, 1) [0A][03][00][00][00][05][84][B2] Sending request using RTS signal Waiting for a confirmation... <0A><03><0A><00><0A><00><0E><00><0A><00><1B><00><0A><C7><5C> reg[0] = 10(0xa) reg[1] = 14(0xe) reg[2] = 10(0xa) reg[3] = 27(0x1b) reg[4] = 10(0xa) |
在服务终端中最终打印
服务终端
1 2 3 4 5 6 7 | Opening /dev/ttyS4 at 9600 bauds (N, 8, 1)Bytes flushed (0)Waiting for a indication...<0A><03><00><00><00><05><84><B2>[0A][03][0A][00][0A][00][0E][00][0A][00][1B][00][0A][C7][5C]Sending request using RTS signalWaiting for a indication... |
以上代码均做测试用,关于具体用法请读者自行研究。 参考资料: GitHub - stephane/libmodbus: A Modbus library for Linux, Mac OS, FreeBSD and Windows
相关文章:

RS485和RS232 通信配置
RS232 目前硬件上支持RS232的有以下板卡: LubanCat-5IO底板(含有RS232x2) 7.1. 引脚定义 具体的引脚定义可以参考背面的丝印 LubanCat-5IO底板 引脚定义图 7.2. 跳帽配置 LubanCat-5IO底板 鲁班买5IO底板上的RS485和RS232是共用同一组…...

zst-2001 历年真题 设计模式
设计模式 - 第1题 a 设计模式 - 第2题 一个产品可以产生多个就是抽象,一个就是工厂 比如这样 第二题a是意图 bc: d 设计模式 - 第3题 b 设计模式 - 第4题 类图里全是builder,疯狂暗示 设计模式 - 第5题 aa 设计模式 - 第6题 只有工厂方法是创…...
微软系统 红帽系统 网络故障排查:ping、traceroute、netstat
在微软(Windows)和红帽(Red Hat Enterprise Linux,RHEL)等系统中,网络故障排查是确保系统正常运行的重要环节。 ping、traceroute(在Windows中为tracert)和netstat是三个常用的网络…...
11.多用组合和少继承
11.多用组合和少继承 目录介绍 01.前言思考的介绍 1.1 组合优于继承思考1.2 实际中的案例1.3 思考继承的争议 02.绘图应用程序案例 2.1 案例的背景2.2 用继承实现多形状2.3 用组合实现多形状 03.不同鸟类程序案例 3.1 案例的背景3.2 用继承实现多鸟类3.3 继承的问题暴露3.4 用…...
【AWS+Wordpress】将本地 WordPress 网站部署到AWS
前言 自学笔记,解决问题为主,亲测有效,欢迎补充。 本地开发机:macOS(Sequoia 15.0.1) 服务器:AWS EC2(Amazon Linux 2023) 目标:从本地迁移 WordPress 到云…...

鸿蒙NEXT开发动画案例4
1.创建空白项目 2.Page文件夹下面新建Spin.ets文件,代码如下: /*** TODO SpinKit动画组件 - 双粒子旋转缩放动画* author: CSDN-鸿蒙布道师* since: 2025/05/08*/ ComponentV2 export struct SpinFour {// 参数定义Require Param spinSize: number 36…...
Java(day136):Arrays.asList() 方法:如何在 Java 中高效地将数组转化为列表!
前言 在 Java 编程中,数组和集合是两种常用的数据结构。数组是固定大小的容器,而集合则是动态扩展的容器。随着 Java 集合框架的引入,开发者通常更倾向于使用集合类来存储和操作数据,因为集合类提供了更多的功能和灵活性。 然而&a…...

XML语言
XML语言 在开始介绍Mybatis之前,先介绍一下XML语言,XML语言发明最初是用于数据的存储和传输,它是由一个一个的标签嵌套而成 <?xml version"1.0" encoding"UTF-8" ?> <outer> <name>阿伟</name&…...

基于SpringBoot的小区停车位管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...

VR博物馆,足不出户云逛展
VR博物馆概念与特点 定义与由来 VR博物馆,即虚拟现实(Virtual Reality)博物馆,是利用计算机技术、互联网和虚拟现实技术,将实体博物馆及其藏品数字化,实现在虚拟空间中的展示和体验的新型博物馆形式。概念起源于20世纪90年代末&…...

uniapp|实现多终端聊天对话组件、表情选择、消息发送
基于UniApp框架,实现跨平台多终端适配的聊天对话组件开发、表情选择交互设计及消息发送,支持文本与表情混合渲染。 目录 聊天界面静态组件实现消息列表布局消息气泡双向布局辅助元素定位与样式静态数据模拟与扩展性设计表情选择器静态模块浮层实现符号网格排列多端样式适配方…...

73页最佳实践PPT《DeepSeek自学手册-从理论模型训练到实践模型应用》
这份文档是一份关于 DeepSeek 自学手册的详细指南,涵盖了 DeepSeek V3 和 R1 模型的架构、训练方法、性能表现以及使用技巧等内容。它介绍了 DeepSeek V3 作为强大的 MoE 语言模型在数学、代码等任务上的出色表现以及其训练过程中的创新架构如多头潜在注意力和多 To…...

stm32 WDG看门狗
目录 stm32 WDG看门狗一、WDG基础知识1)WDG(Watchdog)看门狗简介 二、IWDG独立看门狗1)IWDG键寄存器2)IWDG超时时间 三、WWDG窗口看门狗1)WWDG框图2)WWDG工作特性3)WWDG超时时间4&am…...

BUUCTF——Cookie is so stable
BUUCTF——Cookie is so stable 进入靶场 页面有点熟悉 跟之前做过的靶场有点像 先简单看一看靶场信息 有几个功能点 flag.php 随便输了个admin 根据题目提示 应该与cookie有关 抓包看看 构造payload Cookie: PHPSESSIDef0623af2c1a6d2012d57f3529427d52; user{{7*7}}有…...

用go从零构建写一个RPC(仿gRPC,tRPC)--- 版本1(Client端)
这里我们来实现这个RPC的client端 为了实现RPC的效果,我们调用的Hello方法,即server端的方法,应该是由代理来调用,让proxy里面封装网络请求,消息的发送和接受处理。而上一篇文章提到的服务端的代理已经在.rpc.go文件中…...

一文读懂 AI
2022年11月30日,OpenAI发布了ChatGPT,2023年3月15日,GPT-4引发全球轰动,让世界上很多人认识了ai这个词。如今已过去快两年半,AI产品层出不穷,如GPT-4、DeepSeek、Cursor、自动驾驶等,但很多人仍…...
第三天 车联网云架构
一、车联网技术演进与行业变革 1.1 从传统Telematics到智能网联汽车 当我们驾驶着搭载智能网联系统的汽车时,车辆每秒会产生超过1GB的数据流量。这些数据包括: 高精度地图的实时更新ADAS传感器采集的环境信息车载娱乐系统交互数据车辆状态监控信息传统基于2G/3G的Telematic…...

【LeetCode Hot100 | 每日刷题】二叉树的层序遍历
题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例 2&a…...
Unity垃圾回收(GC)
1.GC的作用:定期释放不再使用的内存空间。 注:C不支持GC,需要手动管理内存,使用new()申请内存空间,使用完后通过delete()释放掉,但可能出现忘记释放或者指针…...

SpringBoot3集成Oauth2——1(/oauth2/token方法的升级踩坑)
备注:本文适用于你在SpringBoot2.7以前集成过oauth2,并且项目已经正式投入使用的情况,否则,我建议你直接学习或者找资料学习最新的oauth2集成,就不要纠结于老版本的oauth2。 原因:Spring Security 5.x和Sp…...
MySQL 索引与事务详解
目录 一、索引(Index) 二、事务(Transaction) 三、总结 一、索引(Index) 索引的本质:一种数据结构(如 BTree、Hash),用于快速定位数据,避免全…...

基于Qt开发的多线程TCP服务端
目录 一、Qt TCP服务端开发环境准备1. 项目配置2. 核心类说明 二、服务端搭建步骤详解步骤1:初始化服务端对象步骤2:启动端口监听步骤3:处理客户端连接 三、数据通信与状态管理1. 数据收发实现2. 客户端状态监控 四、进阶功能扩展1. 多客户端…...
【Debian】关于LubanCat-RK3588s开发板安装Debian的一些事
琐碎的事问题不少,甚至一度让我以为核心坏了 按照指引烧录完Debian11-gnome镜像后启动,此时输出的分辨率不一定匹配显示器,进而导致黑屏,此时需要使用MobaXterm的串口终端以运行一些指令,下载链接用xrandr指令查看显示…...
Python爬虫实战:研究网站动态滑块验证
1. 引言 1.1 研究背景与意义 在当今信息时代,Web 数据的价值日益凸显。通过爬虫技术获取公开数据并进行分析,能够为企业决策、学术研究等提供有力支持。然而,为了防止数据被恶意爬取,许多网站采用了各种反爬机制,其中动态滑块验证是一种常见且有效的方式。动态滑块验证通…...

Centos离线安装mysql、redis、nginx等工具缺乏层层依赖的解决方案
Centos离线安装mysql、redis、nginx等工具缺乏层层依赖的解决方案 引困境yum-utils破局 引 前段时间,有个项目有边缘部署的需求,一台没有的外网的Centos系统服务器,需要先安装jdk,node,mysql,reids…...

从零开始开发纯血鸿蒙应用之XML解析
从零开始开发纯血鸿蒙应用 〇、前言一、鸿蒙SDK中的 XML API1、ohos.xml2、ohos.convertxml 三、XML 解析实践1、源数据结构2、定义映射关系3、定义接收对象4、获取文章信息 四、总结 〇、前言 在前后端的数据传输方面,论格式化形式,JSON格式自然是首选…...

10.王道_HTTP
1. 互联网时代的诞生 2. HTTP的基本特点 2.1客户端-服务端模型 2.2 无状态协议 2.3 可靠性 2.4 文本协议 3. HTML,CSS和JS 4. HTTP的各个组件 4.1 客户端 4.2 服务端 4.3 代理 5. URI和URL 6. HTTP报文 HTTP报文分为两种——请求报文和响应报文。 6.1 GET请求示例 注意&#…...

解决stm32HAL库使用vscode打开,识别不到头文件及uint8_t等问题
解决stm32HAL库使用vscode打开,识别不到头文件及uint8_t等问题 结论,问题有2问题1问题2解决办法将Keil Assistant自动生成的.vscode目录复制到MDK-ARM上层目录将Keil Assistant自动生成的.vscode目录复制到MDK-ARM上层目录将Keil Assistant自动生成的.vs…...
Docker Compose 完全指南:从入门到生产实践
Docker Compose 完全指南:从入门到生产实践 1. Docker Compose 简介与核心价值 Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个 YAML 文件来配置应用的服务,只需简单命令就能创建和启动所有服务。 核心优势:…...
Android Framework 记录之一
1、下载源码,目录如下: 2、Android系统的层次如下: 3、项目目录简单分析如下: 4、telphony目录 文件描述CellIdentityCdma...