基于STM32的MODBUS-RTU框架的實(shí)現(xiàn)
---------------------------------------------------------------------------------------手動(dòng)分割線--------------------------------------------------------------------------------
---------------------------------------------------------------------------------------文章開始--------------------------------------------------------------------------------
一、協(xié)議簡介
Modbus是一種串行通信協(xié)議,是Modicon公司(現(xiàn)在的施耐德電氣Schneider Electric)于1979年為使用可編程邏輯控制器(PLC)通信而發(fā)表。Modbus已經(jīng)成為工業(yè)領(lǐng)域通信協(xié)議的業(yè)界標(biāo)準(zhǔn)(De facto),并且現(xiàn)在是工業(yè)電子設(shè)備之間常用的連接方式。Modbus比其他通信協(xié)議使用的更廣泛的主要原因有:
- 公開發(fā)表并且無版權(quán)要求
- 易于部署和維護(hù)
- 對(duì)供應(yīng)商來說,修改移動(dòng)本地的比特或字節(jié)沒有很多限制
Modbus允許多個(gè) (大約240個(gè)) 設(shè)備連接在同一個(gè)網(wǎng)絡(luò)上進(jìn)行通信,舉個(gè)例子,一個(gè)測(cè)量溫度和濕度的裝置,并且將結(jié)果發(fā)送給計(jì)算機(jī)。在數(shù)據(jù)采集與監(jiān)視控制系統(tǒng)(SCADA)中,Modbus通常用來連接監(jiān)控計(jì)算機(jī)和遠(yuǎn)程終端控制系統(tǒng)(RTU)。
Modbus協(xié)議目前存在用于串口、以太網(wǎng)以及其他支持互聯(lián)網(wǎng)協(xié)議的網(wǎng)絡(luò)的版本。對(duì)于串行連接,存在兩個(gè)變種,它們?cè)跀?shù)值數(shù)據(jù)表示不同和協(xié)議細(xì)節(jié)上略有不同。Modbus RTU是一種緊湊的,采用二進(jìn)制表示數(shù)據(jù)的方式,Modbus ASCII是一種人類可讀的,冗長的表示方式。
本文介紹的為MODBUS-RTU協(xié)議在STM32單片機(jī)上的實(shí)現(xiàn)。
二、協(xié)議框架
MODBUS的幀(報(bào)告)形式:RTU幀??蚣艿囊话阈问饺缦聢D所示:
MODBUS的幀根據(jù)主從方式分為兩種:詢問幀和應(yīng)答幀。
下圖為RTU幀的詢問幀:
下圖為RTU幀的一般應(yīng)答幀:
下圖為一般潛在長度的幀的響應(yīng)格式:
三、與標(biāo)準(zhǔn)的RTU幀的差異
標(biāo)準(zhǔn)RTU幀:
使用RTU模式,消息發(fā)送至少要以3.5個(gè)字符時(shí)間的停頓間隔開始。在網(wǎng)絡(luò)波特率下多樣的字符時(shí)間,這是最容易實(shí)現(xiàn)的。
傳輸?shù)牡谝粋€(gè)域是設(shè)備地址??梢允褂玫膫鬏斪址鞘M(jìn)制的0…9,A…F。網(wǎng)絡(luò)設(shè)備不斷偵測(cè)網(wǎng)絡(luò)總線,包括停頓間隔時(shí)間內(nèi)。
當(dāng)?shù)谝粋€(gè)域(地址域)接收到,每個(gè)設(shè)備都進(jìn)行解碼以判斷是否發(fā)往自己的。
在最后一個(gè)傳輸字符之后,一個(gè)至少3.5個(gè)字符時(shí)間的停頓標(biāo)定了消息的結(jié)束。一個(gè)新的消息可在此停頓后開始。
整個(gè)消息幀必須作為一連續(xù)的流轉(zhuǎn)輸。如果在幀完成之前有超過1.5個(gè)字符時(shí)間的停頓時(shí)間,接收設(shè)備將刷新不完整的消息并假定下一字節(jié)是一個(gè)新消息的地址域。
同樣地,如果一個(gè)新消息在小于3.5個(gè)字符時(shí)間內(nèi)接著前個(gè)消息開始,接收的設(shè)備將認(rèn)為它是前一消息的延續(xù)。
這將導(dǎo)致一個(gè)錯(cuò)誤,因?yàn)樵谧詈蟮腃RC域的值不可能是正確的。
STM32的處理方式:
采用標(biāo)準(zhǔn)的RTU幀實(shí)現(xiàn)每一幀的數(shù)據(jù)分割有點(diǎn)麻煩,需要使用單獨(dú)的定時(shí)器來進(jìn)行接收字符時(shí)間判斷。
在STM32上,采用串口空閑接收中斷實(shí)現(xiàn)對(duì)每幀數(shù)據(jù)的分割,從而簡化STM32上的MODBUS的RTU協(xié)議,實(shí)現(xiàn)快速實(shí)現(xiàn)。
四、串口空閑接收中斷
關(guān)于串口空閑中斷網(wǎng)上有很多教程,我這里就直接提供代碼,后續(xù)有需要再單獨(dú)出個(gè)帖子。
//空閑中斷初始化函數(shù)
XL_StatusTypeDef XL_UartIdle_DMA_Init(UART_HandleTypeDef *huart,uint8_t *pData)
{
__HAL_UART_CLEAR_IDLEFLAG(huart); //清除空閑中斷標(biāo)志
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);//啟動(dòng)串口空閑中斷
return (XL_StatusTypeDef)HAL_UARTEx_ReceiveToIdle_DMA(huart,pData,XL_UartRx_Len);//開啟DMA接收
}
//空閑中斷回調(diào)函數(shù)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
void XL_Uart_Idle(UART_HandleTypeDef *huart, uint8_t *pData,uint16_t Size);
if(huart == &hlpuart1)
XL_Uart_Idle(huart,LPU1_RxBuff.pData,Size);
if(huart == &huart3)
XL_Uart_Idle(huart,U3_RxBuff.pData,Size);
}
五、RTU協(xié)議框架
//申明函數(shù)
ModbusState BackCheckFunction(uint8_t subcode);
ModbusState ReadEventFunction(void);
//modbus函數(shù)框架
ModbusState XL_Modbus_RTU_Frame(uint8_t Addr,uint8_t *pData,uint16_t len)
{
//判斷地址是否正確、及數(shù)據(jù)長度
if(len<3 || Addr != pData[0] )
return MB_NULL;
//通信為低位在前
uint16_t crc = crc16tablefast(pData,len-2);
//判斷CRC
if(pData[len-2] != (uint8_t )crc||pData[len-1] != (uint8_t )(crc>>8) )
return MB_NULL;
//對(duì)應(yīng)功能碼的函數(shù)檢查
ModbusState State = LenCheck((FunCode)pData[1],pData,len);
if(State != MB_OK)
{
//返回錯(cuò)誤功能碼
ErrorSend((FunCode)pData[1],State);
return MB_ERROR;
}
//接收處理計(jì)數(shù)++
EventCountNum++;
switch(pData[1])
{
case ReadReg: //讀取多個(gè)寄存器
ReadRegFunction(pData,len);
break;
case ReadInputReg: //讀取多個(gè)輸入寄存器
ReadRegFunction(pData,len);
break;
case WriteSingleReg: //寫入單個(gè)寄存器
WriteRegFunction(pData,len);
break;
case ReadEventCount: //讀取事件計(jì)數(shù)
ReadEventFunction();
break;
case WriteReg: //寫入多個(gè)寄存器
WriteRegFunction(pData,len);
break;
default: //異?;貜?fù),不存在的功能碼
ErrorSend((FunCode)pData[1],UNFUNCODE);
break;
}
return MB_OK;
}
//寄存器映射--MARK()
ModbusState RegMap(uint16_t regaddr,uint16_t *reg,ModbusState code )
{
//先進(jìn)行地址偏移
/*這部分代碼需要自己實(shí)現(xiàn)*/
return MB_OK;
}
//讀取多個(gè)寄存器
ModbusState ReadRegFunction(uint8_t *pData,uint16_t len)
{
//定義發(fā)送數(shù)組
uint8_t bData[310] = {pData[0],pData[1],2*((pData[4]<<8)+pData[5])};
//定義返回?cái)?shù)據(jù)長度
uint16_t blen = 2*((pData[4]<<8)+pData[5])+5;
//寄存器開始地址
uint16_t addr = (pData[2]<<8)+pData[3];
//讀取寄存器數(shù)
uint16_t allnum = (pData[4]<<8)+pData[5];
//遍歷寄存器
for(int i = 0 ;i<allnum;i++)
{
uint16_t reg;
RegMap(addr+i,®,MB_READ);
bData[3+i*2] = reg>>8;
bData[3+i*2+1] = reg;
}
//發(fā)送數(shù)據(jù)出去
MBSendCRC(bData,blen);
return MB_OK;
}
//寫入寄存器
ModbusState WriteRegFunction(uint8_t *pData,uint16_t len)
{
//寄存器開始地址
uint16_t addr = (pData[2]<<8)+pData[3];
//定義發(fā)送數(shù)組
uint8_t bData[8] = {pData[0],pData[1],pData[2],pData[3]};
//單個(gè)寄存器
if(pData[1] == WriteSingleReg)
{
//填充長度
bData[4] = 0;
bData[5] = 1;
uint16_t preg = (pData[4]<<8)+pData[5];
RegMap(addr,&preg,MB_WRITE);
}
//多個(gè)寄存器
if(pData[1] == WriteReg)
{
//填充長度
bData[4] = pData[4];
bData[5] = pData[5];
//讀取字節(jié)數(shù)
uint16_t allnum = pData[6]/2;
//遍歷寄存器
for(int i = 0 ;i<allnum;i++)
{
uint16_t preg = (pData[7+i*2]<<8)+pData[7+i*2+1];
RegMap(addr+i,&preg,MB_WRITE);
}
}
//發(fā)送數(shù)據(jù)出去
MBSendCRC(bData,8);
return MB_OK;
}
//modbus的通信狀態(tài)校驗(yàn)的函數(shù)
ModbusState BackCheckFunction(uint8_t subcode)
{
switch(subcode)
{
case 0x00: //返回詢問數(shù)據(jù)
{
uint8_t data[8] = {SlaveAddr,BackCheck,0x00,0x00,0x00,0x01};
MBSendCRC(data,8);
break;
}
default:
ErrorSend(BackCheck,UNFUNCODE);
break;
}
return MB_OK;
}
//modbus的讀取事件計(jì)數(shù)的函數(shù)
ModbusState ReadEventFunction(void)
{
uint8_t data[8] = {SlaveAddr,ReadEventCount,0x00,0x00,0x00,EventCountNum};
MBSendCRC(data,8);
return MB_OK;
}
//ModbusRTU的長度檢測(cè)函數(shù)
ModbusState LenCheck(FunCode code,uint8_t *pData,uint16_t len)
{
switch(pData[1])
{
case ReadReg: //讀取多個(gè)寄存器
if(pData[4]!= 0|| pData[5] > Max_RegNum) //判斷讀取的最大長度是否超過125寄存器
return LENERROR;
if(len != 8) //數(shù)據(jù)主機(jī)發(fā)送的不徹底或字節(jié)的數(shù)量是錯(cuò)誤的
return LENERROR;
return MB_OK; //通過篩選
case ReadInputReg: //讀取多個(gè)輸入寄存器
if(pData[4]!= 0|| pData[5] > Max_RegNum) //判斷讀取的最大長度是否超過125寄存器
return LENERROR;
if(len != 8) //數(shù)據(jù)主機(jī)發(fā)送的不徹底或字節(jié)的數(shù)量是錯(cuò)誤的
return LENERROR;
return MB_OK; //通過篩選
case WriteSingleReg: //寫入單個(gè)寄存器
if(len != 8) //數(shù)據(jù)主機(jī)發(fā)送的不徹底或字節(jié)的數(shù)量是錯(cuò)誤的
return LENERROR;
return MB_OK; //通過篩選
case BackCheck: //回送診斷校驗(yàn),等待與陳銘討論
if(len < 8 || len>310)
return LENERROR;
return MB_OK;
case ReadEventCount: //讀取事件計(jì)數(shù)
if(len < 4 || len>310)
return LENERROR;
return MB_OK;
case WriteReg: //寫入多個(gè)寄存器
if(pData[4]!= 0|| pData[5] > Max_RegNum || len != (pData[6]+9)||(2*((pData[4]<<8)+pData[5]))!=pData[6]) //判斷讀取的最大長度是否超過125寄存器
return LENERROR;
return MB_OK;
default: //異?;貜?fù)
return UNFUNCODE;
}
}
/*-----crc校驗(yàn)查表----------------
輔助完成CRC校驗(yàn),是CRC校驗(yàn)的快速查表法
----------------------------------*/
const uint16_t crctalbeabs[] = {
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400
};
/*-------CRC校驗(yàn)函數(shù)----------------
輸入為字符指針(ptr)、字符指針長度(len)
//ptr為校驗(yàn)數(shù)組的指針,len為校驗(yàn)數(shù)組的元素個(gè)數(shù)
返回CRC校驗(yàn)結(jié)果,為16位
根據(jù)實(shí)際需求進(jìn)行CRC校驗(yàn)碼
----------------------------------*/
uint16_t crc16tablefast(uint8_t *ptr, uint16_t len)
{
uint16_t crc = 0xffff;
uint16_t i;
uint8_t ch;
for (i = 0; i < len; i++)
{
ch = *ptr++;
crc = crctalbeabs[(ch ^ crc) & 15] ^ (crc >> 4);
crc = crctalbeabs[((ch >> 4) ^ crc) & 15] ^ (crc >> 4);
}
return crc;
}
//modbus 發(fā)送函數(shù)
void MBSend(uint8_t *pData,uint16_t len)
{
//需要自己修改串口發(fā)送驅(qū)動(dòng)函數(shù)
//XL_Transmit(&hlpuart1,pData,len,Max_SendUart_Time);
//XL_Transmit(&huart3,pData,len,Max_SendUart_Time);
}
//modbus 發(fā)送函數(shù)
void MBSendCRC(uint8_t *pData,uint16_t len)
{
uint16_t crc = crc16tablefast(pData,len-2);
pData[len-2] = (uint8_t )crc;
pData[len-1] = (uint8_t )(crc>>8);
//需要自己修改串口發(fā)送驅(qū)動(dòng)函數(shù)
//XL_Transmit(&hlpuart1,pData,len,Max_SendUart_Time);
//XL_Transmit(&huart3,pData,len,Max_SendUart_Time);
}
//異常、錯(cuò)誤代碼發(fā)送
void ErrorSend(FunCode code,ModbusState state)
{
uint8_t data[5] = {SlaveAddr,code+0x80,state};
MBSendCRC(data,5);
}
六、總結(jié)
工程文件(不需要積分):
https://download.csdn.net/download/qq_40824852/85022712?spm=1001.2014.3001.5503
目前采用STM32實(shí)現(xiàn)了部分MODBUS的簡易框架,能實(shí)現(xiàn)較為簡單的協(xié)議通信,后續(xù)有需求會(huì)更新。
----------------------------------------------------------------------------------到這里就結(jié)束了-------------------------------------------------------------------------------
時(shí)間流逝、年齡增長,是自己的磨煉、對(duì)知識(shí)技術(shù)的應(yīng)用,還有那不變的一顆對(duì)嵌入式熱愛的心!文章來源:http://www.zghlxwxcb.cn/news/detail-429440.html
到這里就結(jié)束了,希望大家點(diǎn)贊o( ̄▽ ̄)d、關(guān)注(o)/~、評(píng)論(▽)!文章來源地址http://www.zghlxwxcb.cn/news/detail-429440.html
到了這里,關(guān)于基于STM32的MODBUS-RTU框架的實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!