
軟件架構(gòu)
本modbus基于狀態(tài)機(jī)框架實(shí)現(xiàn),因此具有極快的響應(yīng)速度。不過(guò)狀態(tài)機(jī)沒(méi)有自己的緩存可以保存信息,因此需要額外的變量來(lái)存放對(duì)應(yīng)的數(shù)據(jù),所以該框架占用的內(nèi)存會(huì)比緩存對(duì)比框架要多。
安裝教程
1.先準(zhǔn)備好一個(gè)工程,可以是新建的空的工程也可以是舊的工程,這里以新工程來(lái)舉例。雙擊“新建工程.bat”,名字可以任意,只要是英文就行。這里隨便寫個(gè)modbus。

2.復(fù)制modbus.h和modbus.c到工程文件夾。

3.打開工程,在任意工程文件夾里雙擊,比如雙擊DEVICE文件夾,在彈出的選擇框中雙擊modbus.c將其添加到工程中。

void main(){ //main函數(shù),必須的。system_init(); //系統(tǒng)初始化函數(shù),也是必須的。while(1){}}


#include "ecbm_core.h"//加載庫(kù)函數(shù)的頭文件。#include "modbus.h" //加載modbus的頭文件。void main(){ //main函數(shù),必須的。system_init(); //系統(tǒng)初始化函數(shù),也是必須的。while(1){}}void uart1_receive_callback(void){//接收處理部分。ecbm_modbus_rtu_receive();}void ecbm_modbus_rtu_set_data(emu8 dat){//發(fā)送數(shù)據(jù)部分。uart_char(1,dat);//ECBM庫(kù)的發(fā)送函數(shù)。}emu8 ecbm_modbus_rtu_get_data(void){//獲取串口值部分。return SBUF;//串口1的寄存器}

void main(){ //main函數(shù),必須的。system_init(); //系統(tǒng)初始化函數(shù),也是必須的。timer_init(); //初始化定時(shí)器。timer_start(0); //開啟定時(shí)器0。while(1){ecbm_modbus_rtu_run();//運(yùn)行modbus函數(shù)。}}void uart1_receive_callback(void){//接收處理部分。ecbm_modbus_rtu_receive();}void ecbm_modbus_rtu_set_data(emu8 dat){//發(fā)送數(shù)據(jù)部分。uart_char(1,dat);//ECBM庫(kù)的發(fā)送函數(shù)。}emu8 ecbm_modbus_rtu_get_data(void){//獲取串口值部分。return SBUF;//串口1的寄存器}void tim0_fun(void)TIMER0_IT_NUM {//定時(shí)器處理部分ECBM_MODBUS_RTU_TIMEOUT_RUN();}
使用說(shuō)明
modbus是基于串口的通信協(xié)議,用于電腦訪問(wèn)設(shè)備的寄存器來(lái)完成設(shè)置或者執(zhí)行某些動(dòng)作。
其固定的數(shù)據(jù)格式為:
【設(shè)備地址】+【功能碼】+【起始地址】+【功能碼相關(guān)】+【CRC校驗(yàn)】。
本庫(kù)目前支持01,02,03,04,05,06,10共7個(gè)功能碼。
功能碼詳解
【01】讀線圈

舉例:主機(jī)發(fā)送【01 01 00 00 00 01 FD CA】。意思是讀取地址為01的設(shè)備中0000號(hào)線圈的值。

舉例:主機(jī)發(fā)送【01 02 00 00 00 03 38 0B】。意思是讀取地址為01的設(shè)備中0000~0002號(hào)3個(gè)離散量的值。
【03】讀保持寄存器

舉例:主機(jī)發(fā)送【01 03 00 0A 00 03 25 C9 】。意思是讀取地址為01的設(shè)備中000A~000C號(hào)3個(gè)寄存器的值。
【04】讀輸入寄存器

舉例:主機(jī)發(fā)送【01 04 00 00 00 01 31 CA 】。意思是讀取地址為01的設(shè)備中0000號(hào)輸入寄存器的值。
【05】寫單個(gè)線圈

舉例:主機(jī)發(fā)送【01 05 00 0A FF 00 AC 38 】。意思是將地址為01的設(shè)備中000A號(hào)線圈的值設(shè)置為1。
【06】寫單個(gè)寄存器

【10】寫多個(gè)寄存器

舉例:主機(jī)發(fā)送【01 10 00 0A 00 04 08 11 11 22 22 33 33 44 44 5D 5E 】。意思是將地址為01的設(shè)備中000A~000D號(hào)4個(gè)寄存器的值分別設(shè)置為0x1111、0x2222、0x3333、0x4444。
如何自定義modbus寄存器
為了方便大家使用,本庫(kù)默認(rèn)包含了兩個(gè)數(shù)組作為modbus通信中的寄存器。其中:
ecbm_modbus_rtu_bit_buf,用
于存放線圈操作。也就是功能碼01會(huì)從這個(gè)數(shù)組里讀取數(shù)據(jù),功能碼05會(huì)往這個(gè)數(shù)組寫數(shù)據(jù)。
ecbm_modbus_rtu_reg_buf,用于存放寄存器操作。也就是功能碼03能從這個(gè)數(shù)據(jù)里讀取數(shù)據(jù),功能碼06會(huì)往這個(gè)數(shù)組里寫數(shù)據(jù)。而功能碼10可以一次性寫多個(gè)數(shù)據(jù)到數(shù)組里。
但是如果需要本庫(kù)來(lái)對(duì)接舊項(xiàng)目,或者您的項(xiàng)目更加復(fù)雜,不是單純的存取寄存器,那么可以取消自帶的數(shù)組,然后定義新的讀寫函數(shù)即可。
一、不需要庫(kù)自帶的線圈緩存:
第一步,關(guān)閉線圈緩存使能。

第二步:重建線圈讀寫函數(shù)。因?yàn)樵瓉?lái)的讀寫函數(shù)都是操作自帶緩存的,取消緩存之后函數(shù)也會(huì)失效。
重建很簡(jiǎn)單,定義一個(gè)ecbm_modbus_cmd_write_bit函數(shù)和ecbm_modbus_cmd_read_bit函數(shù)就行。下面舉個(gè)例子:
void ecbm_modbus_cmd_write_bit(emu16 addr,emu8 dat){if(addr==101){//比如地址為101的線圈是管理者LED的亮滅狀態(tài)的。if(dat==0){//如果往101寫入了0,LED_OFF;//就關(guān)閉LED。}else{//否則LED_ON;//就打開LED。}}if(addr==0){//比如地址0,對(duì)應(yīng)了板子上DCDC的使能。dc_dc_en=dat;//將寫入數(shù)據(jù)賦予使能。}}void ecbm_modbus_cmd_read_bit(emu16 addr,emu8 * dat){if(addr==101){//比如地址為101的線圈是管理者LED的亮滅狀態(tài)的。if(LED_PIN==0){//如果該引腳是低電平,說(shuō)明LED是亮的。*dat=1;//注意,這里返回1的原因是通常LED用低電平點(diǎn)亮。}else{//否則*dat=0;//返回0。}}if(addr==0){//比如地址0,對(duì)應(yīng)了板子上DCDC的使能。*dat=dc_dc_en;//將使能狀態(tài)返回去。}}
二、如果不需要自帶的寄存器緩存。
也是分兩步就OK。
第一步,關(guān)閉寄存器緩存使能。

第二步,重建寄存器讀寫函數(shù),理由同上。分別是ecbm_modbus_cmd_write_reg和ecbm_modbus_cmd_read_reg。
舉個(gè)例子:
void ecbm_modbus_cmd_write_reg(emu16 addr,emu16 dat){if(addr<512){//可以把512以下的地址作為OLED的緩存(128*64個(gè)點(diǎn)需要128x64/16=512個(gè)16位寄存器)。OLED_BUF[addr]=dat;}else{//512以上的地址作為另一組。MCU_SETTING[addr-512]=dat;if(addr==512){//當(dāng)?shù)刂窞?12的寄存器的D0位if(dat&0x0001){//被寫1的時(shí)候,定義為用戶要更新OLED。OLED_SHOW();//刷新OLED顯示。}}}}void ecbm_modbus_cmd_read_reg(emu16 addr,emu16 * dat){if(addr<512){//可以把512以下的地址作為OLED的緩存(128*64個(gè)點(diǎn)需要128x64/16=512個(gè)16位寄存器)。*dat=OLED_BUF[addr];}else{//512以上的地址作為另一組。*dat=MCU_SETTING[addr-512];}}
由此可見(jiàn),自定義的modbus寄存器不僅僅可以完成普通的儲(chǔ)存功能,還能具備一些觸發(fā)功能,可以通過(guò)下發(fā)指令來(lái)讓單片機(jī)執(zhí)行某些動(dòng)作。
這將極大的擴(kuò)展modbus的使用場(chǎng)景。
圖形化配置界面說(shuō)明
眾所周知,圖形化配置界面是ECBM系列的特點(diǎn),自然在本modbus庫(kù)也不例外的。

首先用keil打開modbus.h,在左下角找到Configuration Wizard標(biāo)簽。點(diǎn)擊即可進(jìn)入圖形化配置界面。
這個(gè)界面是keil的功能,因此用IAR和GCC是沒(méi)有辦法享受到的喔。

下面我來(lái)一一說(shuō)明:
本機(jī)地址/ID
顧名思義,在總線中為了區(qū)分不同設(shè)備而設(shè)立的一個(gè)唯一的地址。在使用中要確保該地址是唯一的,否則在總線中就會(huì)沖突。
由于圖形化配置界面設(shè)置的信息在編譯后就不能修改了,所以要實(shí)現(xiàn)動(dòng)態(tài)ID地址可以通過(guò)修改變量ecbm_modbus_rtu_id來(lái)實(shí)現(xiàn)。
超時(shí)時(shí)間
在串口通信中,可能會(huì)遇到通信中斷的情況。
由于modbus-rtu在傳輸上使用了原始數(shù)據(jù)傳輸,也就是說(shuō)0x00到0xFF都可以用來(lái)表示數(shù)據(jù),那么就沒(méi)有哪個(gè)值是可以專門用來(lái)當(dāng)幀頭幀尾的了。
在這一點(diǎn)上,modbus-ascii就做得很好,它只用字符0到9和A到F表示數(shù)據(jù),用:號(hào)表示幀頭,這樣就可以通過(guò)幀頭來(lái)判斷一個(gè)數(shù)據(jù)幀的開始和結(jié)束。
由于modbus-rtu不能這樣操作,那么為了能使modbus從機(jī)在通信中斷后可以恢復(fù)到待接收狀態(tài),需要設(shè)定一個(gè)時(shí)間,這個(gè)時(shí)間就是超時(shí)時(shí)間。
在圖形化配置界面設(shè)置中,設(shè)置值代表ECBM_MODBUS_RTU_TIMEOUT_RUN執(zhí)行的次數(shù)。
比如圖中設(shè)置為5,如果ECBM_MODBUS_RTU_TIMEOUT_RUN每10mS運(yùn)行一次,那么只要超過(guò)5*10=50mS沒(méi)收到串口數(shù)據(jù),就認(rèn)為是通信中斷,然后modbus恢復(fù)待接收態(tài)。
線圈讀寫功能設(shè)置
線圈這個(gè)概念來(lái)源于以前的工業(yè)設(shè)備,一個(gè)線圈就是指一個(gè)繼電器,繼電器通常是單刀雙擲可以輸出高低電平,所以說(shuō)白了線圈就是一個(gè)位寄存器可以儲(chǔ)存一個(gè)比特的信息。
線圈緩存
這個(gè)緩存其實(shí)也就是一個(gè)u8型數(shù)組,由于線圈就是一個(gè)比特的數(shù)據(jù),用一個(gè)字節(jié)來(lái)存一個(gè)比特太浪費(fèi)空間了。
為了方便大家往u8型數(shù)組里存比特?cái)?shù)據(jù),本庫(kù)已經(jīng)把這個(gè)算法寫好了。
使能這個(gè)選項(xiàng)時(shí),本庫(kù)會(huì)定義一個(gè)數(shù)組ecbm_modbus_rtu_bit_buf,同時(shí)會(huì)將對(duì)應(yīng)的讀寫函數(shù)ecbm_modbus_cmd_write_bit,ecbm_modbus_cmd_read_bit定義好,通過(guò)調(diào)用讀寫函數(shù)就可以讀寫對(duì)應(yīng)的線圈。
如果是移植本庫(kù)到以前的工程中,也就是說(shuō)在原來(lái)的工程中已經(jīng)有了緩存了,那么可以不使能本選項(xiàng)。
不使能的話,數(shù)組ecbm_modbus_rtu_bit_buf就不會(huì)定義,同時(shí)讀寫函數(shù)也需要你自己去寫了。畢竟作者也無(wú)法預(yù)料你的舊工程用的是什么緩存嘛。
線圈緩存總數(shù)
這個(gè)設(shè)置涉及modbus通信的地址和數(shù)據(jù)總數(shù)判斷,所以一定要根據(jù)實(shí)際情況填寫。
數(shù)值的單位是字節(jié),如果需要10個(gè)線圈,要兩個(gè)字節(jié)可以放得下,那么就應(yīng)該填上2。
線圈起始地址
這個(gè)是應(yīng)對(duì)特殊要求的,對(duì)于大部分用戶而言,保持為0就可以了。
線圈指令使能
主要涉及兩個(gè)指令:一個(gè)用于讀線圈的01指令,一個(gè)用于寫線圈的05指令。使能對(duì)應(yīng)指令,可以讓本庫(kù)編譯相應(yīng)的指令解析代碼。所以有需求就可以使能,沒(méi)有需求就不使能以節(jié)省程序空間。
寄存器讀寫功能設(shè)置
寄存器這個(gè)概念比線圈好理解,就和單片機(jī)的寄存器差不多一個(gè)意思,然后modbus的寄存器是16位的。
寄存器緩存
和上面的線圈一樣,為了方便大家,讀寫的算法已經(jīng)寫好。
使能緩存就會(huì)定義u16型數(shù)組ecbm_modbus_rtu_reg_buf還有讀寫函數(shù)ecbm_modbus_cmd_write_reg和ecbm_modbus_cmd_read_reg。
如果是移植到舊工程或者想自定義讀寫函數(shù),就不使能這個(gè)選項(xiàng),然后自己定義讀寫函數(shù)就好了。
寄存器緩存總數(shù)
這個(gè)參數(shù)是用于定義緩存數(shù)組,請(qǐng)根據(jù)實(shí)際情況填寫。由于u16型是可以直接定義的,所以本選項(xiàng)的單位是字(1字節(jié)=8比特、一字=16比特)。用多少就填多少。
寄存器起始地址
這個(gè)是應(yīng)對(duì)特殊要求的,對(duì)于大部分用戶而言,保持為0就可以了。
寄存器指令使能
主要涉及三個(gè)指令:一個(gè)用于讀寄存器的03指令,兩個(gè)用于寫寄存器的06指令和10指令。
其中06指令一次只能寫一個(gè)寄存器。
10指令可以一次寫多個(gè)寄存器。
使能對(duì)應(yīng)指令,可以讓本庫(kù)編譯相應(yīng)的指令解析代碼。所以有需求就可以使能,沒(méi)有需求就不使能以節(jié)省程序空間。
另外在10指令那里,還有個(gè)選項(xiàng)是寫入緩存總數(shù)的,這是因?yàn)閙odbus通信都是需要計(jì)算CRC的。
如果CRC錯(cuò)誤則本次傳輸?shù)男畔⑷孔鲝U。因此在CRC結(jié)果出來(lái)之前,傳入的數(shù)據(jù)都會(huì)放在寫入緩存中。
而這個(gè)選項(xiàng)就是定義這個(gè)緩存的大小,單位也是字(16比特)。
如果寫入緩存的定義為10,實(shí)際通信傳輸了15,那么就會(huì)因?yàn)閿?shù)據(jù)溢出導(dǎo)致程序出問(wèn)題,所以還是注意要符合實(shí)際。
IO系統(tǒng)指令使能
這里包含了兩個(gè)指令,一個(gè)是02指令讀離散量輸入,另一個(gè)是04指令讀輸入寄存器。
由于外界輸入千變?nèi)f化,所以這兩個(gè)指令對(duì)應(yīng)函數(shù)沒(méi)有定義。如果有需要就先使能,然后定義相應(yīng)的讀函數(shù)。
02指令對(duì)應(yīng)的是ecbm_modbus_cmd_read_io_bit函數(shù)。
04指令對(duì)應(yīng)的是ecbm_modbus_cmd_read_io_reg函數(shù)。
這個(gè)項(xiàng)目的倉(cāng)庫(kù)地址為:
https://gitee.com/ecbm/modbus
關(guān)注【一起學(xué)嵌入式】,回復(fù)“加群”進(jìn)技術(shù)交流群。
覺(jué)得文章不錯(cuò),點(diǎn)擊“分享”、“贊”、“在看” 唄!