1 Star 0 Fork 194

tela / modbus

forked from ECBM工作室 / modbus 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 18.68 KB
一键复制 编辑 原始数据 按行查看 历史
奈特 提交于 2021-12-22 05:44 . update README.md.

modbus

介绍

全新的、可裁剪的modbus。一定是你没见过的版本。易移植易上手,大量注释爱不释手。

modbus协议是工控设备的标准通信协议,详细说明可见百科说明。简单来说,modbus协议就是为了在主机设备上能拿到从机里的数据的协议。本仓库主要是收录了基础型的modbus框架,因此支持线圈(1位)和寄存器(16位)这两种数据类型(有的modbus支持浮点型或者32位型)。支持的功能码有:01读线圈、02读离散量输入、03读保持寄存器、04读输入寄存器、05写单个线圈、06写单个寄存器和10写多个寄存器。

软件架构

本modbus基于状态机框架实现,因此具有极快的相应速度。不过状态机没有自己的缓存可以保存信息,因此需要额外的变量来存放对应的数据,所以该框架占用的内存会比缓存对比框架要多。

安装教程

  1. 先准备好一个工程,可以是新建的空的工程也可以是旧的工程。这里以新工程来举例。双击“新建工程.bat”,名字可以任意,只要是英文就行。这里随便写个modbus。 新建工程

  2. 复制modbus.h和modbus.c到工程文件夹。 复制文件到工程

  3. 打开工程,在任意工程文件夹里双击,比如双击DEVICE文件夹,在弹出的选择框中双击modbus.c将其添加到工程中。 加入文件

  4. 打开main.c,加载modbus的头文件。如下第二句:

    #include "ecbm_core.h"	//加载库函数的头文件。
    #include "modbus.h"     //加载modbus的头文件。
    void main(){			//main函数,必须的。
    	system_init();		//系统初始化函数,也是必须的。
    	while(1){
    		
    	}
    }

    至此modbus组件已完整的添加到工程中。但只是添加到工程中还不能使用modbus。modbus是基于串口的协议,因此我们还需要初始化串口。

  5. 好在ECBM默认就是打开串口的,先到ecbm_core.h去设置当前使用单片机型号。当然设置的时候别忘了使用ECBM强大的图形化配置界面,只需要点击窗口左下角的【Configuration Wizard】标签就行。实例的单片机我用的是STC8F2K32S2,于是按下图的步骤设置。确保单片机时钟设置是【内部高速时钟HSI(标准)】,这样设置的话ECBM库会自己识别出你在stc-isp工具上设置的时钟频率。有了这个自动识别,你就不会因为时钟没调好、波特率不对而收不到数据啦。然后确保【自动下载功能】是打开的,一方面自动下载会让你的调试更加方便,另一方面该功能只要开启就会自动初始化串口,省事。 设置工程

  6. 打开uart.h,进入到图形化配置界面。将波特率修改成实际使用的波特率,比如115200。使能接收并打开串口1的接收回调函数。 设置串口

  7. 在main.c中定义串口1回调函数,函数名为uart1_receive_callback。然后将modbus的接收函数放到串口1回调函数中。接着定义modbus读写串口的两个函数ecbm_modbus_rtu_set_data和ecbm_modbus_rtu_get_data。

    #include "ecbm_core.h"	//加载库函数的头文件。
    #include "modbus.h"     //加载modbus的头文件。
    void main(){			//main函数,必须的。
    	system_init();		//系统初始化函数,也是必须的。
    	while(1){
    		
    	}
    }
    void uart1_receive_callback(void){//接收处理部分。
        ecbm_modbus_rtu_receive();
    }
    void ecbm_modbus_rtu_set_data(emu8 dat){//发送数据部分。
        uart_char(1,dat);//ECBM库的发送函数。
    }
    emu8 ecbm_modbus_rtu_get_data(void){//获取串口值部分。
        return SBUF;//串口1的寄存器
    }
  8. modbus还有接收超时的设定,因此还需要定时器的帮助。打开timer.h,进入图形化设置界面,任选一个定时器,比如定时器0。设置成定时器模式,然后定时时间为1mS。因为在实验平台上单片机工作在24MHz,所以定时1mS的初值是24000。 设置定时器

  9. 回到main.c,添加初始化定时器和运行定时器的代码。最后把modbus的运行函数写到循环中就行了。此时modbus已安装完毕,可以使用了。

    #include "ecbm_core.h"	//加载库函数的头文件。
    #include "modbus.h"     //加载modbus的头文件。
    void main(){			//main函数,必须的。
    	system_init();		//系统初始化函数,也是必须的。
        timer_init();        //初始化定时器。
    	timer_start(0);      //开启定时器0。
    	while(1){
    		ecbm_modbus_rtu_run();//运行modbus函数。
    	}
    }
    void uart1_receive_callback(void){//接收处理部分。
        ecbm_modbus_rtu_receive();
    }
    void ecbm_modbus_rtu_set_data(emu8 dat){//发送数据部分。
        uart_char(1,dat);//ECBM库的发送函数。
    }
    emu8 ecbm_modbus_rtu_get_data(void){//获取串口值部分。
        return SBUF;//串口1的寄存器
    }
    void tim0_fun(void)TIMER0_IT_NUM {//定时器处理部分
        ECBM_MODBUS_RTU_TIMEOUT_RUN();
    }

使用说明

modbus是基于串口的通信协议,用于电脑访问设备的寄存器来完成设置或者执行某些动作。其固定的数据格式为:【设备地址】+【功能码】+【起始地址】+【功能码相关】+【CRC校验】。本库目前支持01,02,03,04,05,06,10共7个功能码。

功能码详解

【01】读线圈

设备地址 功能码 起始地址 线圈数量 CRC
1~247 01 0x0000~0xFFFF 1~2000 先低位后高位

举例:主机发送【01 01 00 00 00 01 FD CA】。意思是读取地址为01的设备中0000号线圈的值。

【02】读离散量输入

设备地址 功能码 起始地址 线圈数量 CRC
1~247 02 0x0000~0xFFFF 1~2000 先低位后高位

举例:主机发送【01 02 00 00 00 03 38 0B】。意思是读取地址为01的设备中0000~0002号3个离散量的值。

【03】读保持寄存器

设备地址 功能码 起始地址 寄存器数量 CRC
1~247 03 0x0000~0xFFFF 1~125 先低位后高位

举例:主机发送【01 03 00 0A 00 03 25 C9 】。意思是读取地址为01的设备中000A~000C号3个寄存器的值。

【04】读输入寄存器

设备地址 功能码 起始地址 寄存器数量 CRC
1~247 04 0x0000~0xFFFF 1~125 先低位后高位

举例:主机发送【01 04 00 00 00 01 31 CA 】。意思是读取地址为01的设备中0000号输入寄存器的值。

【05】写单个线圈

设备地址 功能码 起始地址 线圈数量 CRC
1~247 05 0x0000~0xFFFF 0x0000或0xFF00 先低位后高位

举例:主机发送【01 05 00 0A FF 00 AC 38 】。意思是将地址为01的设备中000A号线圈的值设置为1。

【06】写单个寄存器

设备地址 功能码 起始地址 寄存器值 CRC
1~247 06 0x0000~0xFFFF 0x0000~0xFFFF 先低位后高位

举例:主机发送【01 06 00 01 12 34 D5 7D 】。意思是将地址为01的设备中0001号线圈的值设置为0x1234。

【10】写多个寄存器

设备地址 功能码 起始地址 寄存器数量 字节计数 寄存器值 CRC
1~247 10 0x0000~0xFFFF 1~78 寄存器数量*2 0x0000~0xFFFF 先低位后高位

举例:主机发送【01 10 00 0A 00 04 08 11 11 22 22 33 33 44 44 5D 5E 】。意思是将地址为01的设备中000A~000D号4个寄存器的值分别设置为0x1111、0x2222、0x3333、0x4444。

如何自定义modbus寄存器

为了方便大家使用,本库默认包含了两个数组作为modbus通信中的寄存器。其中:

  • ecbm_modbus_rtu_bit_buf,用于存放线圈操作。也就是功能码01会从这个数组里读取数据,功能码05会往这个数组写数据。
  • ecbm_modbus_rtu_reg_buf,用于存放寄存器操作。也就是功能码03能从这个数据里读取数据,功能码06会往这个数组里写数据。而功能码10可以一次性写多个数据到数组里。

但是如果需要本库来对接旧项目,或者您的项目更加复杂,不是单纯的存取寄存器,那么可以取消自带的数组,然后定义新的读写函数即可。

比如不需要库自带的线圈缓存,第一步,关闭线圈缓存使能。

第二步,重建线圈读写函数。因为原来的读写函数都是操作自带缓存的,取消缓存之后函数也会失效。重建很简单,定义一个ecbm_modbus_cmd_write_bit函数和ecbm_modbus_cmd_read_bit函数就行。下面举个例子

void ecbm_modbus_cmd_write_bit(emu16 addr,emu8 dat){
    if(addr==101){//比如地址为101的线圈是管理者LED的亮灭状态的。
		if(dat==0){//如果往101写入了0,
			LED_OFF;//就关闭LED。
		}else{//否则
			LED_ON;//就打开LED。
		}
	}
	if(addr==0){//比如地址0,对应了板子上DCDC的使能。
		dc_dc_en=dat;//将写入数据赋予使能。
	}
}
void ecbm_modbus_cmd_read_bit(emu16 addr,emu8 * dat){
    if(addr==101){//比如地址为101的线圈是管理者LED的亮灭状态的。
		if(LED_PIN==0){//如果该引脚是低电平,说明LED是亮的。
			*dat=1;//注意,这里返回1的原因是通常LED用低电平点亮。
		}else{//否则
			*dat=0;//返回0。
		}
	}
	if(addr==0){//比如地址0,对应了板子上DCDC的使能。
		*dat=dc_dc_en;//将使能状态返回去。
	}
}

同理,如果不需要自带的寄存器缓存。也是分两步就OK。第一步,关闭寄存器缓存使能。317-8

第二步,重建寄存器读写函数,理由同上。分别是ecbm_modbus_cmd_write_reg和ecbm_modbus_cmd_read_reg。举个例子:

void ecbm_modbus_cmd_write_reg(emu16 addr,emu16 dat){
    if(addr<512){//可以把512以下的地址作为OLED的缓存(128*64个点需要128x64/16=512个16位寄存器)。
		OLED_BUF[addr]=dat;
	}else{//512以上的地址作为另一组。
		MCU_SETTING[addr-512]=dat;
		if(addr==512){//当地址为512的寄存器的D0位
			if(dat&0x0001){//被写1的时候,定义为用户要更新OLED。
				OLED_SHOW();//刷新OLED显示。
			}
		}
	}
}
void ecbm_modbus_cmd_read_reg(emu16 addr,emu16 * dat){
    if(addr<512){//可以把512以下的地址作为OLED的缓存(128*64个点需要128x64/16=512个16位寄存器)。
		*dat=OLED_BUF[addr];
	}else{//512以上的地址作为另一组。
		*dat=MCU_SETTING[addr-512];
	}
}

由此可见,自定义的modbus寄存器不仅仅可以完成普通的储存功能,还能具备一些触发功能,可以通过下发指令来让单片机执行某些动作。这将极大的扩展modbus的使用场景。

图形化配置界面说明

众所周知,图形化配置界面是ECBM系列的特点,自然在本modbus库也不例外的。317-9

首先用keil打开modbus.h,在左下角找到Configuration Wizard标签。点击即可进入图形化配置界面。这个界面是keil的功能,因此用IAR和GCC是没有办法享受到的喔。317-10

下面我来一一说明:

本机地址/ID

顾名思义,在总线中为了区分不同设备而设立的一个唯一的地址。在使用中要确保该地址是唯一的,否则在总线中就会冲突。由于图形化配置界面设置的信息在编译后就不能修改了,所以要实现动态ID地址可以通过修改变量ecbm_modbus_rtu_id来实现。

超时时间

在串口通信中,可能会遇到通信中断的情况。由于modbus-rtu在传输上使用了原始数据传输,也就是说0x00到0xFF都可以用来表示数据,那么就没有哪个值是可以专门用来当帧头帧尾的了。在这一点上,modbus-ascii就做得很好,它只用字符0到9和A到F表示数据,用:号表示帧头,这样就可以通过帧头来判断一个数据帧的开始和结束。由于modbus-rtu不能这样操作,那么为了能使modbus从机在通信中断后可以恢复到待接收状态,需要设定一个时间,这个时间就是超时时间。在图形化配置界面设置中,设置值代表ECBM_MODBUS_RTU_TIMEOUT_RUN执行的次数。比如图中设置为5,如果ECBM_MODBUS_RTU_TIMEOUT_RUN每10mS运行一次,那么只要超过5*10=50mS没收到串口数据,就认为是通信中断,然后modbus恢复待接收态。

线圈读写功能设置

线圈这个概念来源于以前的工业设备,一个线圈就是指一个继电器,继电器通常是单刀双掷可以输出高低电平,所以说白了线圈就是一个位寄存器可以储存一个比特的信息。

线圈缓存

这个缓存其实也就是一个u8型数组,由于线圈就是一个比特的数据,用一个字节来存一个比特太浪费空间了。为了方便大家往u8型数组里存比特数据,本库已经把这个算法写好了。使能这个选项时,本库会定义一个数组ecbm_modbus_rtu_bit_buf,同时会将对应的读写函数ecbm_modbus_cmd_write_bit,ecbm_modbus_cmd_read_bit定义好,通过调用读写函数就可以读写对应的线圈。如果是移植本库到以前的工程中,也就是说在原来的工程中已经有了缓存了,那么可以不使能本选项。不使能的话,数组ecbm_modbus_rtu_bit_buf就不会定义,同时读写函数也需要你自己去写了。毕竟作者也无法预料你的旧工程用的是什么缓存嘛。

线圈缓存总数

这个设置涉及modbus通信的地址和数据总数判断,所以无论上面的【线圈缓存】使能与否,都不能随意填写本选项,一定要根据实际情况填写。数值的单位是字节,如果需要10个线圈,要两个字节可以放得下,那么就应该填上2。

线圈起始地址

这个是应对特殊要求的,对于大部分用户而言,保持为0就可以了。

线圈指令使能

主要涉及两个指令:一个用于读线圈的01指令,一个用于写线圈的05指令。使能对应指令,可以让本库编译相应的指令解析代码。所以有需求就可以使能,没有需求就不使能以节省程序空间。

寄存器读写功能设置

寄存器这个概念比线圈好理解,就和单片机的寄存器差不多一个意思,然后modbus的寄存器是16位的。

寄存器缓存

和上面的线圈一样,为了方便大家,读写的算法已经写好。使能缓存就会定义u16型数组ecbm_modbus_rtu_reg_buf还有读写函数ecbm_modbus_cmd_write_reg和ecbm_modbus_cmd_read_reg。如果是移植到旧工程或者想自定义读写函数,就不使能这个选项,然后自己定义读写函数就好了。

寄存器缓存总数

这个参数不仅仅是用于定义缓存数组,还涉及modbus通信的地址和传输总数判断,所以无论寄存器缓存的使能与否都要根据实际情况填写。由于u16型是可以直接定义的,所以本选项的单位是字(1字节=8比特、一字=16比特)。用多少就填多少。

寄存器起始地址

这个是应对特殊要求的,对于大部分用户而言,保持为0就可以了。

寄存器指令使能

主要涉及三个指令:一个用于读寄存器的03指令,两个用于写寄存器的06指令和10指令。其中06指令一次只能写一个寄存器。10指令可以一次写多个寄存器。使能对应指令,可以让本库编译相应的指令解析代码。所以有需求就可以使能,没有需求就不使能以节省程序空间。另外在10指令那里,还有个选项是写入缓存总数的,这是因为modbus通信都是需要计算CRC的。如果CRC错误则本次传输的信息全部作废。因此在CRC结果出来之前,传入的数据都会放在写入缓存中。而这个选项就是定义这个缓存的大小,单位也是字(16比特)。如果写入缓存的定义为10,实际通信传输了15,那么就会因为数据溢出导致程序出问题,所以还是注意要符合实际。

IO系统指令使能

这里包含了两个指令,一个是02指令读离散量输入,另一个是04指令读输入寄存器。由于外界输入千变万化,所以这两个指令对应函数没有定义。如果有需要就先使能,然后定义相应的读函数。02指令对应的是ecbm_modbus_cmd_read_io_bit函数。04指令对应的是ecbm_modbus_cmd_read_io_reg函数。

更新历史

V1.0.3(2021-12-22)

  1. 续写了modbus的使用手册(添加了图形化配置界面的说明)。

V1.0.3(2021-10-23)

  1. 剔除了stream库,因为stream库已经独立。
  2. 丰富了modbus库的使用手册(添加自定义寄存器说明和操作说明)。

V1.0.2(2021-8-16)

  1. 修改了例程中uint16_t和uint8_t类型未定义的问题。

V1.0.2(2021-03-18)

  1. 修改u8、u16、u32数据类型的名字,解决了重定义错误的出现。
  2. 新增安装说明。
  3. 更新图片。

V1.0.1(2021-03-16)

  1. 优化了图形界面的逻辑。
  2. 扩大了宏定义的作用范围,现在支持自定义读写函数和共用逻辑块。
  3. 验证了01、03、05、06功能码。
  4. 新增了0xFF的广播地址。
  5. modbus自组头文件,成为独立的库。
  6. 02和04指令暂时移出,需要用户自己填写。同时他们的异常码04无法使用。

V1.0.0(2021-03-15)

  1. 更新了modbus第一版,支持01、02、03、04、05、06、16功能码。
  2. 增加了基于硬件(STC8F2K32S2)和软件(ECBM V3库)的示例工程。
C
1
https://gitee.com/aemedc/modbus.git
git@gitee.com:aemedc/modbus.git
aemedc
modbus
modbus
master

搜索帮助