写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
官网:https://www.embedded-solutions.at/zh-hans/freemodbus/
下载:https://sourceforge.net/projects/freemodbus.berlios/files/
当然,现在也有 v1.6版本:
https://www.embedded-experts.at/en/freemodbus-downloads/
v1.6相对的,因为 FreeModbus是一款开源的 Modbus协议栈,但是只有从机开源,主机源码是需要收费的。同时网上也没有发现比较好的开源的 Modbus主机协议栈,所以才开发这款支持主机模式的 FreeModbus协议栈。同时把 FreeModbus版本号更改为 v1.6,特性如下:
v1.6的可以看下面的几个链接:
https://www.embedded-solutions.at/files/freemodbus-v1.6-apidoc/
https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32
https://www.zybuluo.com/armink/note/7278
此处使用的是 v1.5的版本,当下载解压 v1.5版本的 FreeModbus文件夹后,可以看到如下文件:
文件提取:
实际用到的只有两个文件,…\freemodbus-v1.5.0\demo\BARE以及 …\freemodbus-v1.5.0\modbus,然后我们移植一下得到下面的列表(其实相当于把 …\demo\BARE的文件夹移到上层目录,其他的官方例子都不要了)
源文件 | 描述 |
---|---|
…\modbus\mb.c | 给应用层提供Modbus从机设置及轮询相关接口 |
…\modbus\ascii\mbascii.c | ASCII模式设置及其状态机 |
…\modbus\functions\mbfunccoils.c | 从机线圈相关功能 |
…\modbus\functions\mbfuncdisc.c | 从机离散输入相关功能 |
…\modbus\functions\mbfuncholding.c | 从机保持寄存器相关功能 |
…\modbus\functions\mbfuncinput.c | 从机输入寄存器相关功能 |
…\modbus\functions\mbfuncother.c | 其余Modbus功能 |
…\modbus\functions\mbutils.c | 一些协议栈中需要用到的小工具 |
…\modbus\rtu\mbcrc.c | CRC校验功能 |
…\modbus\rtu\mbrtu.c | 从机RTU模式设置及其状态机 |
…\modbus\tcp\mbtcp.c | TCP模式设置及其状态机 |
…\port\portevent.c | 实现从机事件移植接口 |
…\port\portserial.c | 从机串口移植 |
…\port\porttimer.c | 从机定时器移植 |
当完成上面的文件提取后,那么我们就需要进行工程文件并入了,添加构建成如下:
由于我们一般只使用 RTU协议,所以你可以不添加 ASCII以及 TCP功能的源文件进来,又或者像我这样添加但把它忽略编译,对应的文件就是那两个有特殊符号(即不进行对其编译)的文件
其实都很好认,我们主要配置的是上面的两个部分,第一张的选择使能 Modbus的对应协议,ASCII跟 TCP我们不需要,可以设置为 0;第二张则是打开相应的函数操作
在Port.h头文件中定义有ENTER_CRITICAL_SECTION()和EXIT_CRITICAL_SECTION( )用于临界段屏蔽中段。
例:
#define ENTER_CRITICAL_SECTION( ) __disable_irq() //关总中断
#define EXIT_CRITICAL_SECTION( ) __enable_irq() //开总中断
接口调用及接口移植都在 …\freemodbus-v1.5.0\demo\BARE\port文件夹内,该文件夹内主要有以下需要用户移植的接口:
所属文件 | 接口 | 功能描述 |
---|---|---|
portevent.c | xMBPortEventInit | 从机事件初始化 |
portevent.c | xMBPortEventPost | 从机发送事件 |
portevent.c | xMBPortEventGet | 从机获取事件 |
portserial.c | vMBPortSerialEnable | 从机串口中断使能 |
portserial.c | xMBPortSerialInit | 从机串口初始化 |
portserial.c | xMBPortSerialPutByte | 从机发送一个字节 |
portserial.c | xMBPortSerialGetByte | 从机接收一个字节 |
portserial.c | prvvUARTTxReadyISR | 从机数据发送为空中断调用函数 |
portserial.c | prvvUARTRxISR | 从机数据接收中断调用函数 |
porttimer.c | xMBPortTimersInit | 从机定时器初始化 |
porttimer.c | vMBPortTimersEnable | 从机计数使能 |
porttimer.c | vMBPortTimersDisable | 从机计数失能 |
porttimer.c | prvvTIMERExpiredISR | 从机定时中断调用函数 |
这里我们不需要修改什么,保持默认就好了,因为只是一些状态反馈而已
无论是 Modbus ASCII还是RTU模式,都以串口通讯做为载体,故有:
FreeModbus通过串口中断的方式接收和发送数据,采用这种做法可以节省程序等待的时间,并且也充分使用CPU的资源:
涉及到串口的移植文件都位于 …\port\portserial.c文件中,修改如下:
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* File: $Id: portserial.c,v 1.1 2006/08/22 21:35:13 wolti Exp $
*/
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- User add includes ----------------------------------*/
#undef TRUE
#undef FALSE
#include "Nano100Series.h"
#include "bsp_uart.h"
/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );
/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(TRUE == xRxEnable)
{
/* 使能接收 FIFO阀值中断,和接收超时中断 */
UART_ENABLE_INT(UART1, (UART_IER_RDA_IE_Msk | UART_IER_RTO_IE_Msk));
}
else
{
UART_DISABLE_INT(UART1, (UART_IER_RDA_IE_Msk | UART_IER_RTO_IE_Msk));
}
if(TRUE == xTxEnable)
{
UART_ENABLE_INT(UART1, UART_IER_THRE_IE_Msk);
}
else
{
UART_DISABLE_INT(UART1, UART_IER_THRE_IE_Msk);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
uint32_t parity;
if(USER_UART_COM1 == ucPORT) {
// if(8 == ucDataBits)
ucDataBits = UART_WORD_LEN_8;
switch(eParity) {
case MB_PAR_NONE: {
parity = UART_PARITY_NONE;
break;
}
case MB_PAR_ODD: {
parity = UART_PARITY_ODD;
break;
}
case MB_PAR_EVEN: {
parity = UART_PARITY_EVEN;
break;
}
}
/* Enable IP clock */
CLK_EnableModuleClock(UART1_MODULE);
/* Select IP clock source */
CLK_SetModuleClock(UART1_MODULE,CLK_CLKSEL1_UART_S_HIRC,CLK_UART_CLK_DIVIDER(1));
/* Set PB multi-function pins for UART1 RXD and TXD */
SYS->PB_L_MFP &= ~( SYS_PB_L_MFP_PB4_MFP_Msk | SYS_PB_L_MFP_PB5_MFP_Msk);
SYS->PB_L_MFP |= (SYS_PB_L_MFP_PB4_MFP_UART1_RX | SYS_PB_L_MFP_PB5_MFP_UART1_TX );
SYS_ResetModule(UART1_RST);
/* Configure UART1 */
UART_SetLine_Config(UART1, ulBaudRate, ucDataBits, parity, UART_STOP_BIT_1);
/* 设置接收超时时间 time = Baudrate clk */
UART_SetTimeoutCnt(UART1, 0x10);
/* 优先权 0最高,3最低 */
NVIC_SetPriority(UART1_IRQn, 0);
NVIC_EnableIRQ(UART1_IRQn);
return TRUE;
} else
return FALSE;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
/* Put a byte in the UARTs transmit buffer. This function is called
* by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
* called. */
UART_WRITE(UART1, ucByte);
return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
/* Return the byte in the UARTs receive buffer. This function is called
* by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
*/
*pucByte = UART_READ(UART1);
return TRUE;
}
/* Create an interrupt handler for the transmit buffer empty interrupt
* (or an equivalent) for your target processor. This function should then
* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
* a new character can be sent. The protocol stack will then call
* xMBPortSerialPutByte( ) to send the character.
*/
static void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
/* Create an interrupt handler for the receive interrupt for your target
* processor. This function should then call pxMBFrameCBByteReceived( ). The
* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
* character.
*/
static void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
/**
* @brief UART1 IRQHandler.
* @param None.
* @return None.
*/
void UART1_IRQHandler(void)
{
__IO uint32_t u32IntSts = UART1->ISR;
__IO uint32_t u32IntIER = UART1->IER;
#if 1
if(u32IntSts & (UART_ISR_RDA_IS_Msk | UART_ISR_RTO_IS_Msk)) {
while(UART_GET_RX_EMPTY(UART1) == 0) {
prvvUARTRxISR();
}
}
if(u32IntSts & UART_ISR_THRE_IS_Msk
&& u32IntIER & UART_IER_THRE_IE_Msk) {
prvvUARTTxReadyISR();
}
#else
/* 发生接收阀值中断或者接收超时中断 */
if(u32IntSts & (UART_ISR_RDA_IS_Msk | UART_ISR_RTO_IS_Msk)) {
/* 读走接收 FIFO中所有的数据,直到接收 FIFO为空 */
while(UART_GET_RX_EMPTY(UART1) == 0) {
/* 从接收 FIFO中读一个数据 */
Usart0.RxBuffer[Usart0.RxCounter++] = UART_READ(UART1);
if(Usart0.RxCounter >= RxBUFFER_SIZE) {
Usart0.RxCounter = 0;
}
}
Usart0.Receiving_Time = 2;
}
if(u32IntSts & UART_ISR_THRE_IS_Msk
&& u32IntIER & UART_IER_THRE_IE_Msk) {
/* 发送寄存器空中断,当使能 THRE后,
只要 uart->THR空了,就会产生中断。
所以,发送完字符串后必须关掉,
否则会导致重复进入中断。
*/
}
#endif
}
这里值得注意的是 Nano130是自带串口数据超时功能的,所以在接收处理上要加上 UART_IER_RTO_IE_Msk这个中断
( 上面所述的接收超时并不是我们一般用的超时接收处理,而是 Nano130在接收器中有一个 16 字节的 FIFO (每个字节加 3 个 bit的纠错位) 缓存来减少向 CPU 申请中断的次数,通过配置下图的寄存器位进行选择:
这里最多只能一次接收 14个 byte才出发一次中断,相对于 16 字节的 FIFO缓存有预留;如果 RX FIFO中断触发级别设为 14,UART接收 14个字节才会发生 RDA(接收数据可得)中断,这样可以降低 CPU的负载;上面的情况,如果 RX只接收到 10个字节怎么办呢?这时候就要用到接收超时中断。当 RX FIFO中收到 1个字节以后,定时器就开始计数,如果定时器超时都没有再收到下一个字节就会发生接收超时中断(RTO) )
第二个值得注意的是,发送中断处理是使用发送寄存器空中断;如果是 STM32的话,还有个发送完成中断,这里需要关注一下,所幸在 Nano130中并没有发送完成中断
Modbus RTU协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内,没有接收到新的字符数据,那么就认为收到了新的帧。接下来就可以处理数据了。RTU通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。
涉及到定时器的移植文件都位于 …\port\porttimer.c文件中,修改如下:
/*
* FreeModbus Libary: BARE Port
* Copyright (C) 2006 Christian Walter
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* File: $Id: porttimer.c,v 1.1 2006/08/22 21:35:13 wolti Exp $
*/
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- User add includes ----------------------------------*/
#undef TRUE
#undef FALSE
#include "Nano100Series.h"
#include "bsp_time.h"
/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
/*
T3.5个字符时间区分不同的帧,即接收到的两个字符之间时间间隔小于3.5个字符
时间时认为是同一个帧的,如果间隔大于3.5个字符时间则认为是不同帧的
在一般的串口通信中,发送 1个字符需要:1位起始位,8位数据位,1位校验位(可无),
1位停止位,总共 1+8+1+1 = 11位,3.5个字符时间就是 3.5 * 11 = 38.5位,
假如波特率是 9600,那么传输 1位的时间是 1/9600 = 0.10416667(ms) ,
这样,3.5个字符时间就大约是 38.5 * 0.10416667 = 4 ms ,即定时器需要的中断时间
*/
uint32_t PrescalerValue = 0;
/* Enable IP clock */
CLK_EnableModuleClock(TMR1_MODULE);
/* Timer1选择 HXT做时钟源 */
CLK_SetModuleClock(TMR1_MODULE, CLK_CLKSEL1_TMR1_S_HXT, NULL);
/* 初始化 Timer1;周期模式;n tick(非真正除频) */
TIMER_Open(TIMER1, TIMER_PERIODIC_MODE, 50*usTim1Timerout50us);
/* 分频为 100kHz,单位为 10us,最后计数基准为 50us */
PrescalerValue = (uint32_t)(TIMER_GetModuleClock(TIMER1) / 100000) - 1;
TIMER_SET_PRESCALE_VALUE(TIMER1, PrescalerValue); // 修改预分频的值( PRESCALE_CNT + 1 ) < 256
TIMER_SET_CMP_VALUE(TIMER1, 5 * usTim1Timerout50us); // 修改比较寄存器的值
/* 优先权 0最高,3最低 */
NVIC_SetPriority(TMR1_IRQn, 1);
/* 使能 Timer1中断 */
TIMER_EnableInt(TIMER1);
NVIC_EnableIRQ(TMR1_IRQn);
return TRUE;
}
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
// The user also needs to add time count cleanup function here
/* 由于该系列没有清计数值功能,所以只能用重设比较值来清零 */
TIMER_SET_CMP_VALUE(TIMER1, 400); // 400 tick *10us = 4ms
TIMER_Start(TIMER1);
}
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
TIMER_Stop(TIMER1);
}
/* Create an ISR which is called whenever the timer has expired. This function
* must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
* the timer has expired.
*/
static void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
/**
* @brief TMR1 IRQHandler.
* @param None.
* @return None.
*/
void TMR1_IRQHandler(void)
{
TIMER_ClearIntFlag(TIMER1);
prvvTIMERExpiredISR();
}
在这里值得注意的是,定时器基准一定要做成 50us,不然有可能出现读写过慢或者读写多个寄存器时无效出错,还有就是在 vMBPortTimersEnable();函数内,在调用启动计数时,先清一下计数值;在 vMBPortTimersDisable();函数中,关闭计数后,可以不清;但不建议把计数清零功能只放在 vMBPortTimersDisable();关闭函数而不放在 vMBPortTimersEnable();函数内
在 mb.h头文件中,大概 268行左右,是写有 Callback Function的声明及解释的,它的描述如下:
/*! \defgroup modbus_registers Modbus Registers
* \code #include "mb.h" \endcode
* The protocol stack does not internally allocate any memory for the
* registers. This makes the protocol stack very small and also usable on
* low end targets. In addition the values don't have to be in the memory
* and could for example be stored in a flash.
* Whenever the protocol stack requires a value it calls one of the callback
* function with the register address and the number of registers to read
* as an argument. The application should then read the actual register values
* (for example the ADC voltage) and should store the result in the supplied
* buffer.
* If the protocol stack wants to update a register value because a write
* register function was received a buffer with the new register values is
* passed to the callback function. The function should then use these values
* to update the application register values.
*/
后面的就是各个回调函数了
然后对应的返回状态代码:
/*! \ingroup modbus
* \brief Errorcodes used by all function in the protocol stack.
*/
typedef enum
{
MB_ENOERR, /*!< no error. */
MB_ENOREG, /*!< illegal register address. */
MB_EINVAL, /*!< illegal argument. */
MB_EPORTERR, /*!< porting layer error. */
MB_ENORES, /*!< insufficient resources. */
MB_EIO, /*!< I/O error. */
MB_EILLSTATE, /*!< protocol stack in illegal state. */
MB_ETIMEDOUT /*!< timeout error occurred. */
} eMBErrorCode;
/**
*****************************************************************************
* @Name : 读输入寄存器
* @Brief : 对应功能码
* 0x04 -> 读单个或多个输入寄存器eMBFuncReadInputRegister
* @Input : pucRegBuffer 数据缓冲区
* usAddress: 寄存器地址
* usNRegs: 寄存器数量
* @Output : none
* @Return : Modbus状态信息
* @Notice : 3 区
* 对象类型:16位
*****************************************************************************
**/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer,
USHORT usAddress,
USHORT usNRegs )
{
#if RTU_REG_INPUT
eMBErrorCode eStatus = MB_ENOERR;
USHORT iRegIndex;
/* it already plus one in modbus function method. */
// usAddress--;
/* 请求地址大于起始地址 && 地址长度小于设定长度 */
if ((usAddress >= REG_INPUT_START)
&& (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS))
{
iRegIndex = (USHORT)(usAddress - usRegInputStart);
while ( usNRegs > 0 )
{
*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (UCHAR)(usRegInputBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
#else
eMBErrorCode eStatus = MB_ENOREG;
( void )pucRegBuffer;
( void )usAddress;
( void )usNRegs;
#endif
return eStatus;
}
/**
*****************************************************************************
* @Name : 保持寄存器
* @Brief : 对应功能码
* 0x03 -> 读单个或多个寄存器eMBFuncReadHoldingRegister
* 0x06 -> 写单个保持寄存器eMBFuncWriteHoldingRegister
* 0x10 -> 写多个保持寄存器eMBFuncWriteMultipleHoldingRegister
* @Input : pucRegBuffer: 数据缓冲区
* usAddress: 寄存器地址
* usNRegs: 寄存器数量
* eMode: 功能码
* @Output : none
* @Return : Modbus状态信息
* @Notice : 4 区
* 对象类型:16位
*****************************************************************************
**/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer,
USHORT usAddress,
USHORT usNRegs,
eMBRegisterMode eMode )
{
#if RTU_REG_HOLDING
eMBErrorCode eStatus = MB_ENOERR;
USHORT iRegIndex;
/* it already plus one in modbus function method. */
// usAddress--;
if((usAddress >= REG_HOLDING_START)
&& (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS))
{
iRegIndex = (USHORT)(usAddress - usRegHoldingStart);
switch (eMode)
{
/* read current register values from the protocol stack. */
case MB_REG_READ:
while (usNRegs > 0)
{
*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
/* write current register values with new values from the protocol stack. */
case MB_REG_WRITE:
while (usNRegs > 0)
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
}
else {
eStatus = MB_ENOREG;
}
#else
eMBErrorCode eStatus = MB_ENOREG;
( void )pucRegBuffer;
( void )usAddress;
( void )usNRegs;
( void )eMode;
#endif
return eStatus;
}
/**
*****************************************************************************
* @Name : 线圈寄存器
* @Brief : 对应功能码
* 0x01 -> 读单个或多个线圈eMBFuncReadCoils
* 0x05 -> 写单个线圈eMBFuncWriteCoil
* 0x0F -> 写多个线圈eMBFuncWriteMultipleCoils
* @Input : pucRegBuffer: 数据缓冲区
* usAddress: 寄存器地址
* usNRegs: 寄存器数量
* eMode: 功能码
* @Output : none
* @Return : Modbus状态信息
* @Notice : 0 区
* 对象类型:单个位
*****************************************************************************
**/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer,
USHORT usAddress,
USHORT usNCoils,
eMBRegisterMode eMode )
{
#if RTU_REG_COILS
eMBErrorCode eStatus = MB_ENOERR;
USHORT iRegIndex, iRegBitIndex, iNReg;
iNReg = usNCoils / 8 + 1;
/* it already plus one in modbus function method. */
// usAddress--;
if ((usAddress >= REG_COIL_START)
&& (usAddress + usNCoils <= REG_COIL_START + REG_COIL_NREGS))
{
iRegIndex = (USHORT)(usAddress - usRegCoilStart) / 8;
iRegBitIndex = (USHORT)(usAddress - usRegCoilStart) % 8;
switch (eMode)
{
/* read current coil values from the protocol stack. */
case MB_REG_READ:
while (iNReg > 0)
{
*pucRegBuffer++ = xMBUtilGetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, 8);
iNReg--;
}
pucRegBuffer--;
/* last coils */
usNCoils = usNCoils % 8;
/* filling zero to high bit */
*pucRegBuffer = *pucRegBuffer << (8 - usNCoils);
*pucRegBuffer = *pucRegBuffer >> (8 - usNCoils);
break;
/* write current coil values with new values from the protocol stack. */
case MB_REG_WRITE:
while (iNReg > 1)
{
xMBUtilSetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, 8, *pucRegBuffer++);
iNReg--;
}
/* last coils */
usNCoils = usNCoils % 8;
/* xMBUtilSetBits has bug when ucNBits is zero */
if (usNCoils != 0)
{
xMBUtilSetBits(&usRegCoilBuf[iRegIndex++], iRegBitIndex, usNCoils, *pucRegBuffer++);
}
break;
}
}
else
{
eStatus = MB_ENOREG;
}
#else
eMBErrorCode eStatus = MB_ENOREG;
( void )pucRegBuffer;
( void )usAddress;
( void )usNCoils;
( void )eMode;
#endif
return eStatus;
}
/**
*****************************************************************************
* @Name : 读离散输入寄存器
* @Brief : 对应功能码
* 0x02 -> 读单个或多个离散输入寄存器eMBFuncReadDiscreteInputs
* @Input : pucRegBuffer: 数据缓冲区
* usAddress: 寄存器地址
* usNRegs: 寄存器数量
* @Output : none
* @Return : Modbus状态信息
* @Notice : 1 区
* 对象类型:单个位
*****************************************************************************
**/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer,
USHORT usAddress,
USHORT usNDiscrete )
{
#if RTU_REG_DISCRETE
eMBErrorCode eStatus = MB_ENOERR;
USHORT iRegIndex, iRegBitIndex, iNReg;
iNReg = usNDiscrete / 8 + 1;
#if 1 // 测试
uint16_t i;
const char buf[] = {0x01,0x02,0x03,0x04,0x05,0x07,0x08,0x09,0x0A};
for(i = 0;i < REG_DISCRETE_NREGS / 8;i++){
usRegDiscreteBuf[i] = *(buf + i);
}
#endif
/* it already plus one in modbus function method. */
// usAddress--;
if ((usAddress >= REG_DISCRETE_START)
&& (usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_NREGS))
{
iRegIndex = (USHORT)(usAddress - usRegDiscreteStart) / 8;
iRegBitIndex = (USHORT)(usAddress - usRegDiscreteStart) % 8;
while (iNReg > 0)
{
*pucRegBuffer++ = xMBUtilGetBits(&usRegDiscreteBuf[iRegIndex++],
iRegBitIndex, 8);
iNReg--;
}
pucRegBuffer--;
/* last discrete */
usNDiscrete = usNDiscrete % 8;
/* filling zero to high bit */
*pucRegBuffer = *pucRegBuffer << (8 - usNDiscrete);
*pucRegBuffer = *pucRegBuffer >> (8 - usNDiscrete);
}
else
{
eStatus = MB_ENOREG;
}
#else
eMBErrorCode eStatus = MB_ENOREG;
( void )pucRegBuffer;
( void )usAddress;
( void )usNDiscrete;
#endif
return eStatus;
}
最后,为了利于调试,我们可以接出官方设置的断言:
/* Modbus 断言调试调用函数 */
void __aeabi_assert(const char *x1,const char *x2,int x3)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
while(1);
}
1、调用 eMBInit();初始化 Modbus从机协议栈,涉及到的一些底层硬件就在这个时候做了初始化
2、调用 eMBEnable();方法启动 Modbus协议栈
3、通过在线程或者定时器轮询调用 eMBPoll();方法进行处理,轮询周期决定了命令的响应时间。
/**
* @brief Main routine.
* @param None.
* @return None.
*/
int main(void)
{
eMBErrorCode eStatus = MB_ENOERR;
System_Start();
/* 初始化 modbus为 RTU方式,波特率 9600,无校验 */
eStatus = eMBInit(MB_RTU, MB_DEVEICE_ADDR, USER_UART_COM1, 9600, MB_PAR_NONE);
if( MB_ENOERR == eStatus)
eMBEnable(); // 使能 modbus协议栈
while(1)
{
(void)eMBPoll(); // 轮训查询
}
}