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

   前面的文章分析了串口的一些基本知识,在工业应用中,串口通信比较常用的协议就是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!\n", 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软件,通信界面如下:

嵌入式Linux 串口编程系列4——EasyARM287开发板通过freemodbus实现Modbus通信_第1张图片

由于没有对 输入和 保持寄存器赋值,所以都是0,不过测试通信正常。

代码git地址如下:

https://github.com/YorkJia/modbusRTU-Linux.git

你可能感兴趣的:(Linux)