43 Star 456 Fork 191

ECBM工作室 / modbus

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

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。如果是移植到旧工程或者想自定义读写函数,就不使能这个选项,然后自己定义读写函数就好了。

寄存器缓存总数

这个参数是用于定义缓存数组,请根据实际情况填写。由于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.4(2023-01-29)

  1. 放宽了地址的限制,在不适用内部数组缓存的情况下,将不会对协议地址的范围进行二次限定。

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库)的示例工程。
MIT License Copyright (c) 2023 ÄÎÌØ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

全新的、可裁剪的modbus。一定是你没见过的版本。易移植易上手,大量注释爱不释手。 展开 收起
C 等 4 种语言
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
C
1
https://gitee.com/ecbm/modbus.git
git@gitee.com:ecbm/modbus.git
ecbm
modbus
modbus
master

搜索帮助

14c37bed 8189591 565d56ea 8189591