嵌入式Linux 串口编程系列4——EasyARM287开发板通过freemodbus实现Modbu

2019-07-13 09:13发布

   前面的文章分析了串口的一些基本知识,在工业应用中,串口通信比较常用的协议就是Modbus RTU,freemodbus是一款微型modbus协议栈,之前对各种单片机、小型处理器支持的比较好,从V1.6版本开始,对Linux也支持了,下面先简单的分析总结下freemodbus的工作流程:    我们知道Modbus通信的重点一方面是数据解析,另一方面就是串口的 不定长 数据接收,因为modbus的命令还是有很多种的,不同的命令,长度都是不同的,尤其是“写”多个寄存器命令,直接跟要写的寄存器数量成正比,这就要求串口能够接收不定长数据。之前的文章介绍过各种方式,既要保证实时性,又要保证数据准确性,最好的效果就是,一旦串口接收到最后一个字符后,很快就触发modbus处理。    freemodbus的接收处理方式采用的是 定时器计时,当串口有数据接收时,触发定时器开始计时,每接收一个字节,重置定时器,这样能保证在接收数据的过程中,定时器是不会溢出的,这一点与 前面文章讲的 termios结构体中的 VTIME是一样的道理。等到串口接收全部数据后,由于没有对定时器进行重置,所以定时器就会按照设定的溢出时间(一般是3.5个字符时间),溢出,在串口溢出中断服务程序中,将要更新 状态机FSM,而eMBPoll函数则会进行modbus协议的解析。    在非Linux的系统中,上面说的定时器是通过 实际的 定时器外围来实现的,当然串口的 收发都是采用 指定外围的发送和接收中断来实现的。而在Linux中,由于多级分层的编程思想,应用程序的程序员基本上是可以不接触具体的硬件的,尤其是linux下的timeval 结构体,本身就能实现 微秒 级的 时间获取,所以这里就不再需要 再使用一个  定时器 外围了,而是虚拟了一个 计时  函数,也能实现定时器的效果。而串口的发送,自然是使用 write函数。     在freemodbus  LINUX中,串口的接收,作者也是采用定时器 计时实现的,termios中的VMIN和VTIME都是设置为0,这就意味 着串口只要有数据,read就会改变 阻塞状态,同时配合select机制,实现串口的接收,freemodbus是  典型使用状态机FSM的案例,把串口 数据流细分成 n多个状态,这样做的优点就是 程序代码非常稳健,当然缺点就是 代码量 有点大,上手稍微优点难。 不过结合上面的 接收原理理解起来不难。 下面说一下移值步骤: 1.修改 配置地址 串口参数 等, 位置:demo.c L112 原: else if( eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN ) != MB_ENOERR ) 修改为 else if( eMBInit( MB_RTU, 0x01, 0, 9600, MB_PAR_NONE ) != MB_ENOERR ) 之所以这么改的原因是,modbus通信,不适合波特率过高,为了传输质量。 2、删掉 设备信息配置,不需要, 位置:demo L117... else if( eMBSetSlaveID( 0x34, TRUE, ucSlaveID, 3 ) != MB_ENOERR ) { fprintf( stderr, "%s: can't set slave id! ", PROG ); iExitCode = EXIT_FAILURE; } 3、修改 串口 初始化中的 打开串口的 “路径”字符串 位置:port/portserial.c L101 原: snprintf( szDevice, 16, "/dev/ttyS%d", ucPort ); 改: snprintf( szDevice, 16, "/dev/ttySP%d", ucPort ); 3、修改 定时器 超时统计函数,这里应该是个bug, 位置:port/porttimer.c L73-74 原: ulDeltaMS = ( xTimeCur.tv_sec - xTimeLast.tv_sec ) * 1000L + ( xTimeCur.tv_usec - xTimeLast.tv_usec ) * 1000L; 改: ulDeltaMS = ( xTimeCur.tv_sec - xTimeLast.tv_sec ) * 1000L + ( xTimeCur.tv_usec - xTimeLast.tv_usec ) / 1000L; 4、修改寄存器起始地址及 数量 位置:demo.c L38-41 原: #define REG_INPUT_START 1000 #define REG_INPUT_NREGS 4 #define REG_HOLDING_START 2000 #define REG_HOLDING_NREGS 130 该: #define REG_INPUT_START 1 #define REG_INPUT_NREGS 200 #define REG_HOLDING_START 1 #define REG_HOLDING_NREGS 200 即输入和保持寄存器起始地址都为1,数量为200个。 重新编写Makefile如下 # 交叉环境变量 CROSS = arm-fsl-linux-gnueabi- # gcc 变量 CC = $(CROSS)gcc # lib LIBS = -pthread # strip函数-优化代码 STRIP = $(CROSS)strip # CFLAGS-系统变量,编译器参数 CFLAGS = -g -O2 -Wall # 头文件路径 INC = -I. -Iport -I../../modbus/rtu -I../../modbus/ascii -I../../modbus/include # 目标 TARGET = modbusrtu # 源文件 SRC = demo.c port/portevent.c port/portother.c port/portserial.c port/porttimer.c ../../modbus/mb.c ../../modbus/rtu/mbrtu.c ../../modbus/rtu/mbcrc.c ../../modbus/ascii/mbascii.c ../../modbus/functions/mbfunccoils.c ../../modbus/functions/mbfuncdiag.c ../../modbus/functions/mbfuncdisc.c ../../modbus/functions/mbfuncholding.c ../../modbus/functions/mbfuncinput.c ../../modbus/functions/mbfuncother.c ../../modbus/functions/mbutils.c # 目标文件 OBJS = $(SRC:.c=.o) # 链接为可执行文件 $(TARGET): $(OBJS) $(CC) $^ -o $@ $(LIBS) .PHONY: clean clean: rm -f $(OBJS) install : $(TARGET) clean @echo compile.... cp $(TARGET) /var/tftpboot @echo end.. %.o : %.c $(CC) $(CFLAGS) $(INC) -o $@ -c $<   执行make install 会生成 modbusrtu 执行文件, 将其copy到 EasyARM287开发板下,修改文件权限为 可执行,然后执行,才是开发板就是Modbus从设备,通信地址为 1,串口参数为 9600-8-N-1,在Windows下运行 Modbus Poll软件,通信界面如下: 由于没有对 输入和 保持寄存器赋值,所以都是0,不过测试通信正常。 代码git地址如下: https://github.com/YorkJia/modbusRTU-Linux.git