最近有用到modbus主机部分,网上搜索了一圈,没找到好用的现成的开源代码。之前用过freemodbus,只有从机的源代码是免费的,其他的都需要商业授权。既然这样,那就自己动手,丰衣足食……自己编写个modbus的主机代码,并且开源出来。
modbus就不多介绍了,是工业上常用的通信协议。在物理层, Modbus 串行链路系统可以使用不同的物理接口(RS485、 RS232)。最常用的是RS485 两线制接口。
modbus又是一个主从协议:同一时刻,在总线上只能有一个主机,其他作为从机。并且从机不会主动发数据,都是主机给从机发一条,从机才会回复一条。所以主机的发送和接收也是不同步进行的。如下流程:
主机发送——》等待从机应答——》接收到从机应答——》应答处理——》回到初始状态
以上的主要流程请记好,也是编写modbus主机代码的指导方针。下面再看下modbus是如何区分一帧一帧的数据的(这里只是介绍RTU模式的,ASSCII暂不做讨论)。如下图所示,帧1与帧2之间要至少有3.5个字符的时间,我们把它简称为3.5T。意思也就是在收到数据直到总线上空闲超出了3.5t,就意味着这一帧数据接收完成了。我们就可以把刚才收到的一帧进行CRC计算解析看一帧数据接收是否正确。
实际上除了如上规定的帧与帧之间必须间隔3.5T外,在一帧内字符字符之间要小于1.5T。如下图所示:
但是在实际运行测试中,这种字符之间超1.5T的情况概率很小,即便出现也能在CRC校验时把错误的帧过滤掉。所以我编写的modbus主机并没有进行字符之间是否超过1.5T的校验。而只做了3.5T的帧区分。至此基本概念介绍完了,先看下我编写的modbus主机的主要状态流程图:
如上图所示,对应代码中定义的如下状态,状态的切换也是modbus主机的主要核心机制:
typedef enum
{
MBH_STATE_IDLE=0X00,
MBH_STATE_TX,
MBH_STATE_TX_END,
MBH_STATE_RX,
MBH_STATE_RX_CHECK,
MBH_STATE_EXEC,
MBH_STATE_REC_ERR, //接收错误状态
MBH_STATE_TIMES_ERR, //传输
}mb_host_state;
这篇文章主要针对代码的核心思想进行分析,处理细节在这里不做陈述,请自己参考源码,下面就针对核心的一个个状态进行讨论:
这个很好理解,在一上电后就会进入到空闲状态。当对接收数据处理完或者读写从机错误次数达到最大值得时候也会回到这个初始状态。只有状态处于IDLE时才能发起新的一轮对从机的读写。
从状态切换图可以看得出来,当主机需要对从机进行读写的时候就会首先进入TX状态,该状态下回打开串口TX中断,然后通过发送中断把一帧数据发送完成。
当一帧数据发送完成时就会进入到TX_END状态。这时候主机发给从机的命令已经完成了,就等待从机的应答了。打开串口接收中断切换到下一个状态去
进入到RX状态下,会完成对从机回应的一帧数据的接收,都是在串口接收中断中处理。如果3.5T超时了就证明接收一帧完成,切换到下一个状态进行校验处理
在rx check状态下主要完成对接收一帧数据的合法性和CRC进行计算,看接收是否正确。如果接收正确了,就进入到回调处理。如果校验错误就进入到接收错误状态。
不管是对主机的读还是写,从机都会回复。在该状态下根据从机回复的不同功能码就会执行不同的回调函数。执行完回调再次回到初始的IDLE状态,这样主机对从机的一次读或者一次写就完成了。
当接收校验错误的时候就会进入到该状态,在该状态下会对错误的次数进行计数。如果连续错误次数超过了最大值,就进入到超过错误次数状态。如果还没达到最大错误次数,就重新回到tx状态,重头再执行一遍对从机的读写动作。
经过几次对从机的读写操作都失败以后就进入到该状态,在该状态下执行超错误次数回调,之后回到最初的IDLE状态等待下一次的读写操作。
最后是对源码文件目录进行一个大概的说明,整个modbus主机代码如下构成:
- mb_crc.c/h 这个没啥解释的,主要放CRC计算部分的代码
- mb_hook.c/h 该文件中存放回调函数接口,所有用户的回调处理都在这里添加。这个需要用户自己添加
- mb_port.c/h 这个也是需要用户自己修改的文件,针对使用的不同的MCU平台进行移植,主要移植工作就是串口初始化、串口tx、rx中断开启,定时器初始化、定时器开始停止接口
- mb_host.c/h 核心处理部分代码,不建议用户修改,上面所述的状态机切换处理全部在该文件中完成。用户层最终调用的API接口也在该文件中定义。如下:
void mbh_init(uint32_t baud,uint8_t parity);
int8_t mbh_send(uint8_t add,uint8_t cmd,uint8_t *data,uint8_t data_len);
void mbh_poll(void);
void mbh_timer3T5Isr(void);
void mbh_uartRxIsr(void);
void mbh_uartTxIsr(void);
最后就是源码地址:https://github.com/Derrick45/modbus-host
mb_crc.c
#include "mb_include.h"
static const uint8_t aucCRCHi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40
};
static const uint8_t aucCRCLo[] = {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
0x41, 0x81, 0x80, 0x40
};
uint16_t mb_crc16( uint8_t * pFrame, uint16_t len )
{
uint8_t ucCRCHi = 0xFF;
uint8_t ucCRCLo = 0xFF;
int iIndex;
while( len-- )
{
iIndex = ucCRCLo ^ *( pFrame++ );
ucCRCLo = ( uint8_t )( ucCRCHi ^ aucCRCHi[iIndex] );
ucCRCHi = aucCRCLo[iIndex];
}
return ( uint16_t )( ucCRCHi << 8 | ucCRCLo );
}
mb_crc.h
#ifndef __MB_CRC16_H
#define __MB_CRC16_H
uint16_t mb_crc16( uint8_t * pFrame, uint16_t len );
#endif
mb_hook.c
/**
******************************************************************************
* @file mb_hook.c
* @author Derrick Wang
* @brief modebus回调函数接口
******************************************************************************
* @note
* 针对modbus的回调处理,请再该文件中添加
******************************************************************************
*/
#include "mb_include.h"
void mbh_hook_rec01(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec02(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec03(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec04(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec05(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec06(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec15(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_rec16(uint8_t add,uint8_t *data,uint8_t datalen)
{
}
void mbh_hook_timesErr(uint8_t add,uint8_t cmd)
{
}
mb_hook.h
/**
******************************************************************************
* @file mb_hook.c
* @author Derrick Wang
* @brief modebus回调函数头文件
******************************************************************************
* @note
*
******************************************************************************
*/
#ifndef __MB_HOOK_H
#define __MB_HOOK_H
/**
* @brief MODBUS主机模式下接收到从机回复不同功能码的回调处理
* @param add:从机的地址
* @param data:接收到的从机发来的数据指针
* @param datalen:接收到的从机发来的数据长度
* @return NONE
* @note rec01\02\03……等数字代表功能码
*/
void mbh_hook_rec01(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec02(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec03(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec04(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec05(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec06(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec15(uint8_t add,uint8_t *data,uint8_t datalen);
void mbh_hook_rec16(uint8_t add,uint8_t *data,uint8_t datalen);
/**
* @brief MODBUS主机读写从机超过最大错误次数回调
* @param add:从机的地址
* @param cmd:功能码
* @return NONE
* @note
*/
void mbh_hook_timesErr(uint8_t add,uint8_t cmd);
#endif
mb_host.c
/**
******************************************************************************
* @file mb_host.c
* @author Derrick Wang
* @brief modebus主机实现代码
******************************************************************************
* @note
* 该文件无需修改
******************************************************************************
*/
#include "mb_include.h"
#include "string.h"
struct
{
uint8_t state; //modbus host状态
uint8_t errTimes; //失败次数计数
uint8_t txLen; //需要发送的帧长度
uint8_t txCounter; //已发送bytes计数
uint8_t txBuf[MBH_RTU_MAX_SIZE]; //发送缓冲区
uint8_t rxCounter; //接收计数
uint8_t rxBuf[MBH_RTU_MAX_SIZE]; //接收缓冲区
uint8_t rxTimeOut; //接收时的超时计数
}mbHost;
//modbus初始化
void mbh_init(uint32_t baud,uint8_t parity)
{
mb_port_uartInit(baud,parity);
mb_port_timerInit(baud);
}
uint8_t mbh_getState()
{
return mbHost.state;
}
//发送一帧命令
int8_t mbh_send(uint8_t add,uint8_t cmd,uint8_t *data,uint8_t data_len)
{
uint16_t crc;
if(mbHost.state!=MBH_STATE_IDLE)return -1; //busy state
mbHost.txCounter=0;
mbHost.rxCounter=0;
mbHost.txBuf[0]=add;
mbHost.txBuf[1]=cmd;
memcpy((mbHost.txBuf+2),data,data_len);
mbHost.txLen=data_len+2; //data(n)+add(1)+cmd(1)
crc=mb_crc16(mbHost.txBuf,mbHost.txLen);
mbHost.txBuf[mbHost.txLen++]=(uint8_t)(crc&0xff);
mbHost.txBuf[mbHost.txLen++]=(uint8_t)(crc>>8);
mbHost.state=MBH_STATE_TX;
mb_port_uartEnable(1,0); //enable tx,disable rx
/*当打开TXE中断以后,立马就会触发一次,所以这里不用先发送一个byte*/
//mb_port_putchar(mbHost.txBuf[mbHost.txCounter++]); //send first char,then enter tx isr
return 0;
}
//接收正确,进行解析处理
void mbh_exec(uint8_t *pframe,uint8_t len)
{
uint8_t datalen=len-2;
switch(pframe[1])//cmd
{
case 1:
mbh_hook_rec01(pframe[0],(pframe+2),datalen);
break;
case 2:
mbh_hook_rec02(pframe[0],(pframe+2),datalen);
break;
case 3:
mbh_hook_rec03(pframe[0],(pframe+2),datalen);
break;
case 4:
mbh_hook_rec04(pframe[0],(pframe+2),datalen);
break;
case 5:
mbh_hook_rec05(pframe[0],(pframe+2),datalen);
break;
case 6:
mbh_hook_rec06(pframe[0],(pframe+2),datalen);
break;
case 15:
mbh_hook_rec15(pframe[0],(pframe+2),datalen);
break;
case 16:
mbh_hook_rec16(pframe[0],(pframe+2),datalen);
break;
}
}
void mbh_poll()
{
switch(mbHost.state)
{
/*接收完一帧数据,开始进行校验*/
case MBH_STATE_RX_CHECK: //接收完成,对一帧数据进行检查
if((mbHost.rxCounter>=MBH_RTU_MIN_SIZE)&&(mb_crc16(mbHost.rxBuf,mbHost.rxCounter)==0)) //接收的一帧数据正确
{
if((mbHost.txBuf[0]==mbHost.rxBuf[0])&&(mbHost.txBuf[1]==mbHost.rxBuf[1])) //发送帧数据和接收到的帧数据地址和功能码一样
{
mbHost.state=MBH_STATE_EXEC;
}
else mbHost.state=MBH_STATE_REC_ERR;
}
else mbHost.state=MBH_STATE_REC_ERR;
break;
/*接收一帧数据出错*/
case MBH_STATE_REC_ERR:
mbHost.errTimes++;
if(mbHost.errTimes>=MBH_ERR_MAX_TIMES)
{
mbHost.state=MBH_STATE_TIMES_ERR;
}
else //重新再启动一次传输
{
mbHost.txCounter=0;
mbHost.rxCounter=0;
mbHost.state=MBH_STATE_TX;
mb_port_uartEnable(1,0); //enable tx,disable rx
}
break;
/*超过最大错误传输次数*/
case MBH_STATE_TIMES_ERR:
mbh_hook_timesErr(mbHost.txBuf[0],mbHost.txBuf[1]);
mbHost.txCounter=0;
mbHost.rxCounter=0;
break;
/*确定接收正确执行回调*/
case MBH_STATE_EXEC: //主机发送接收完成,执行回调
mbh_exec(mbHost.rxBuf,mbHost.rxCounter);
mbHost.state=MBH_STATE_IDLE;
break;
}
}
void mbh_timer3T5Isr()
{
switch(mbHost.state)
{
/*发送完但没有接收到数据*/
case MBH_STATE_TX_END:
mbHost.rxTimeOut++;
if(mbHost.rxTimeOut>=MBH_REC_TIMEOUT) //接收超时
{
mbHost.rxTimeOut=0;
mbHost.state=MBH_STATE_REC_ERR;
mb_port_timerDisable(); //关闭定时器
mb_port_uartEnable(0,0); //串口tx、rx都关闭
}
break;
case MBH_STATE_RX: //3.5T到,接收一帧完成
mbHost.state=MBH_STATE_RX_CHECK;
mb_port_timerDisable(); //关闭定时器
mb_port_uartEnable(0,0); //串口tx、rx都关闭
break;
}
}
void mbh_uartRxIsr()
{
uint8_t ch;
mb_port_getchar(&ch);
switch(mbHost.state)
{
case MBH_STATE_TX_END:
mbHost.rxCounter=0;
mbHost.rxBuf[mbHost.rxCounter++]=ch;
mbHost.state=MBH_STATE_RX;
mb_port_timerEnable();
break;
case MBH_STATE_RX:
if(mbHost.rxCounter
mb_host.h
/**
******************************************************************************
* @file mb_host.h
* @author Derrick Wang
* @brief modebus主机核心代码头文件
******************************************************************************
* @note
* 该文件无需修改
******************************************************************************
*/
#ifndef __MB_HOST_H
#define __MB_HOST_H
#define MBH_RTU_MIN_SIZE 4
#define MBH_RTU_MAX_SIZE 255 //最大不超过255
#define MBH_ERR_MAX_TIMES 3
#define MBH_REC_TIMEOUT 100 //单位3.5T
typedef enum
{
MBH_STATE_IDLE=0X00,
MBH_STATE_TX,
MBH_STATE_TX_END,
MBH_STATE_RX,
MBH_STATE_RX_CHECK,
MBH_STATE_EXEC,
MBH_STATE_REC_ERR, //接收错误状态
MBH_STATE_TIMES_ERR, //传输
}mb_host_state;
/**
* @brief MODBUS主机初始化
* @param baud:串口波特率
* @param parity:奇偶校验位设置
* @return NONE
* @note 使用modbus之前先调用该函数进行初始化
*/
void mbh_init(uint32_t baud,uint8_t parity);
/**
* @brief MODBUS主机给从机发送一条命令
* @param add:从机地址
* @param cmd:功能码
* @param data:要发送的数据
* @param len:发送的数据长度
* @return -1:发送失败 0:发送成功
* @note 该函数为非阻塞式,调用后立即返回
*/
int8_t mbh_send(uint8_t add,uint8_t cmd,uint8_t *data,uint8_t data_len);
/**
* @brief 获取MODBUS主机运行状态
* @return mb_host_state中状态
* @note
*/
uint8_t mbh_getState(void);
/**
* @brief MODBUS状态轮训
* @return none
* @note 该函数必须不断轮询,用来底层核心状态进行切换
* 可在操作系统任务中运行,但应该尽可能短的延时间隔
*/
void mbh_poll(void);
/**
* @brief modbus主机定时器中断处理
* @return none
* @note 放在mcu的中断服务中调用
*
*/
void mbh_timer3T5Isr(void);
/**
* @brief modbus主机串口接收中断处理
* @return none
* @note 放在mcu的rx中断中调用
*
*/
void mbh_uartRxIsr(void);
/**
* @brief modbus主机串口接收中断处理
* @return none
* @note 放在mcu的tx中断中调用
*
*/
void mbh_uartTxIsr(void);
#endif
mb_include.h
#ifndef __MB_INCLUDE_H
#define __MB_INCLUDE_H
#include "main.h"
#include "mb_crc.h"
#include "mb_host.h"
#include "mb_port.h"
#include "mb_hook.h"
#endif
mb_port.c
/**
******************************************************************************
* @file mb_port.c
* @author Derrick Wang
* @brief modebus移植接口
******************************************************************************
* @note
* 该文件为modbus移植接口的实现,根据不同的MCU平台进行移植
******************************************************************************
*/
#include "mb_include.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
void mb_port_uartInit(uint32_t baud,uint8_t parity)
{
/*串口部分初始化*/
huart1.Instance = USART1;
huart1.Init.BaudRate = baud;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
if(parity==MB_PARITY_ODD)
{
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_ODD;
}
else if(parity==MB_PARITY_EVEN)
{
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_EVEN;
}
else
{
huart1.Init.StopBits = UART_STOPBITS_2;
huart1.Init.Parity = UART_PARITY_NONE;
}
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
void mb_port_uartEnable(uint8_t txen,uint8_t rxen)
{
if(txen)
{
__HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
}
if(rxen)
{
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
}
}
void mb_port_putchar(uint8_t ch)
{
huart1.Instance->DR = ch; //直接操作寄存器比HAL封装的更高效
}
void mb_port_getchar(uint8_t *ch)
{
*ch= (uint8_t)(huart1.Instance->DR & (uint8_t)0x00FF);
}
void mb_port_timerInit(uint32_t baud)
{
/*定时器部分初始化*/
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
if(baud>19200) //波特率大于19200固定使用1800作为3.5T
{
htim3.Init.Period = 1800;
}
else //其他波特率的需要根据计算
{
/* us=1s/(baud/11)*1000000*3.5
* =(11*1000000*3.5)/baud
* =38500000/baud
*/
htim3.Init.Period = (uint32_t)38500000/baud;
}
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
}
void mb_port_timerEnable()
{
__HAL_TIM_DISABLE(&htim3);
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE); //清除中断位
__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE); //使能中断位
__HAL_TIM_SET_COUNTER(&htim3,0); //设置定时器计数为0
__HAL_TIM_ENABLE(&htim3); //使能定时器
}
void mb_port_timerDisable()
{
__HAL_TIM_DISABLE(&htim3);
__HAL_TIM_SET_COUNTER(&htim3,0);
__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
}
//串口中断服务函数
void USART1_IRQHandler()
{
HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET))
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
mbh_uartRxIsr();
}
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)!=RESET))
{
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_TXE);
mbh_uartTxIsr();
}
}
//定时器中断服务函数
void TIM3_IRQHandler()
{
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
mbh_timer3T5Isr();
}
mb_port.h
#ifndef __MB_PORT_H
#define __MB_PORT_H
/**
******************************************************************************
* @file mb_port.h
* @author Derrick Wang
* @brief modebus移植接口头文件
******************************************************************************
* @note
* 该文件为modbus移植接口的实现,根据不同的MCU平台进行移植
******************************************************************************
*/
typedef enum
{
MB_PARITY_NONE=0X00, //无奇偶校验,两个停止位
MB_PARITY_ODD, //奇校验
MB_PARITY_EVEN //偶校验
}mbParity;
/**
* @brief MODBUS串口初始化接口
* @param baud:串口波特率
* @param parity:奇偶校验位设置
* @return NONE
* @note 需要根据使用MCU进行移植
*/
void mb_port_uartInit(uint32_t baud,uint8_t parity);
/**
* @brief 串口TX\RX使能接口
* @param txen:0-关闭tx中断 1-打开tx中断
* @param rxen:0-关闭rx中断 1-打开rx中断
* @return NONE
* @note 需要根据使用MCU进行移植
*/
void mb_port_uartEnable(uint8_t txen,uint8_t rxen);
/**
* @brief 串口发送一个byte
* @param ch:要发送的byte
* @return NONE
* @note 需要根据使用MCU进行移植
*/
void mb_port_putchar(uint8_t ch);
/**
* @brief 串口读取一个byte
* @param ch:存放读取一个byte的指针
* @return NONE
* @note 需要根据使用MCU进行移植
*/
void mb_port_getchar(uint8_t *ch);
/**
* @brief 定时器初始化接口
* @param baud:串口波特率,根据波特率生成3.5T的定时
* @return NONE
* @note 需要根据使用MCU进行移植
*/
void mb_port_timerInit(uint32_t baud);
/**
* @brief 定时器使能
* @return NONE
* @note 定时器要清0重新计数
*/
void mb_port_timerEnable(void);
/**
* @brief 定时器关闭
* @return NONE
* @note 定时器要清0重新计数
*/
void mb_port_timerDisable(void);
#endif
https://www.eemaker.com/modbus-host.html