modbus是一个非常好的串口协议(当然也能用在网口上),它简洁、规范、强大。可以满足大部分的工业、嵌入式需求。我写的这个四个寄存器都可以用(输入寄存器、保持寄存器、线圈寄存器、离散寄存器)。不像别的代码,就只能用输入寄存器、保持寄存器。

这里详细说下如何将freemodbus移植到stm32平台。我下载的版本是1.6,我会在我的下载链接里面加上1.6的freemodbus压缩包以及我已经移植好的stm32 for freemodbus版本。我上传的移植好的绝对没问题,经过多个板子测试。这里编译器使用的是keil5.28,单片机用的是STM32F103RCT6。

移植到VET6以及ZET6的话只需要改一下选择的芯片就好,改法:点击魔法棒 - >Device,然后找到自己用的芯片就好了,因为这三款都是大容量的,所以只需要改这里:小容量的比如stm32f103c8t6啥的请看这个教程改芯片,教程连接,单击即可跳转,我也测试编译了,但是由于手上没单片机,就没实测。

这里1.6的freemodbus压缩包也可以在官网下载,官网下载链接:freeModbus官网从机下载链接
我的压缩包里面也有,在文章的最后面可以下载。

1. 下载好之后,解压得到如下内容:


我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。

2. 添加文件到工程

准备一个STM32的工程文件夹,带嵌入式系统ucos、freeRTOS等的工程也是一样的,在工程文件夹下新建一个文件夹:FreeModbus。将第一步获取的两个文件夹放到里面。

打开工程,添加两个Add Group,名字分别为modbus和modbus_port。将这两个文件夹下的C文件都添加进来,tcp相关的除外。


添加文件包含路径,添加这几个文件夹里面头文件的位置:

添加后如下图

添加好了之后编译有两个错误,如下图,先不管他,我们先完善文件

3. 完善portserial.c文件

该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择usart1,波特率9600.

第一次打开这个文件,内容如下:

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/
}BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{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. */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.*/return TRUE;
}

认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节等等。

完善后代码如下,也可以直接复制整个内容替换文件内容:


/** FreeModbus Libary: BARE Port* Copyright (C) 2006 Christian Walter <wolti@sil.at>** 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 $*/#include "port.h"
#include "stm32f10x.h"
#include "bsp_usart1.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.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.*///STM32串口接收中断使能if(xRxEnable == TRUE){//UART中断使能USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);}else{//禁止接收和接收中断USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);}//STM32串口发送中断使能if(xTxEnable == TRUE){//使能发送中断USART_ITConfig(USART1, USART_IT_TC, ENABLE);}else{//禁止发送中断USART_ITConfig(USART1, USART_IT_TC, DISABLE);}
}BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{//串口初始化USART1_Config((uint16_t)ulBaudRate);  USART_NVIC();return TRUE;
}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. *///串口发送函数USART_SendData(USART1, 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 = USART_ReceiveData(USART1); 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  This function handles usart1 Handler.* @param  None* @retval None*/
//串口中断函数
void USART1_IRQHandler(void)
{//发生接收中断if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){prvvUARTRxISR(); //串口接收中断调用函数//清除中断标志位    USART_ClearITPendingBit(USART1, USART_IT_RXNE);   }if(USART_GetITStatus(USART1, USART_IT_ORE) == SET){  USART_ClearITPendingBit(USART1, USART_IT_ORE);prvvUARTRxISR();   //串口发送中断调用函数}//发生完成中断if(USART_GetITStatus(USART1, USART_IT_TC) == SET){prvvUARTTxReadyISR();//清除中断标志USART_ClearITPendingBit(USART1, USART_IT_TC);}
}

其中USART1_Config((uint16_t)ulBaudRate);和 USART_NVIC();是串口初始化的代码,我专门弄了一个.c和.h文件来放置这个文件,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,具体代码如下:

bsp_usart1.c


#include "bsp_usart1.h"uint8_t SendBuff[SENDBUFF_SIZE];/*** @brief  USART1 GPIO 配置,工作模式配置。9600 8-N-1* @param  无* @retval 无*/
void USART1_Config(uint16_t buad)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;/* config USART1 clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);/* USART1 GPIO config *//* Configure USART1 Tx (PA.09) as alternate function push-pull */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);    /* Configure USART1 Rx (PA.10) as input floating */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);/* USART1 mode config */USART_InitStructure.USART_BaudRate = buad;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_InitStructure.USART_Parity = USART_Parity_No ;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE);
}/*** @brief  USART1 中断 配置* @param  无* @retval 无*/
void USART_NVIC(void)
{NVIC_InitTypeDef NVIC_InitStructure;/* Configure one bit for preemption priority *///NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);/* 配置中断源 */NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
}/// 重定向c库函数printf到USART1
int fputc(int ch, FILE *f)
{/* 发送一个字节数据到USART1 */USART_SendData(USART1, (uint8_t) ch);/* 等待发送完毕 */while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);       return (ch);
}/// 重定向c库函数scanf到USART1
int fgetc(FILE *f)
{/* 等待串口1输入数据 */while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(USART1);
}
/*********************************************END OF FILE**********************/

bsp_usart1.h

#ifndef __USART1_H
#define __USART1_H#include "stm32f10x.h"
#include <stdio.h>#define USART1_DR_Base  0x40013804      // 0x40013800 + 0x04 = 0x40013804
#define SENDBUFF_SIZE   5000void USART1_Config(uint16_t buad);
void USART1_DMA_Config(void);
void USART_NVIC(void);#endif /* __USART1_H */

4. 完善porttimer.c文件

modbus工作时需要一个定时器,所以这里配置一个定时器。定时器时基是50us,周期做为参数输入。这里注意我们这里面的inline void vMBPortTimersEnable( )以及inline void vMBPortTimersDisable( )函数需要去掉前面的inline,具体改好的代码如下:

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "bsp_timer2.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR( void );/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{timer2_init(usTim1Timerout50us);timer2_nvic();return TRUE;
}void vMBPortTimersEnable(  )
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */TIM_ClearITPendingBit(TIM2, TIM_IT_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);TIM_SetCounter(TIM2,0x0000); TIM_Cmd(TIM2, ENABLE);
}void vMBPortTimersDisable(  )
{/* Disable any pending timers. */TIM_ClearITPendingBit(TIM2, TIM_IT_Update);TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);TIM_SetCounter(TIM2,0x0000); TIM_Cmd(TIM2, DISABLE);
}/* 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(  );
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){prvvTIMERExpiredISR();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

其中 timer2_init(usTim1Timerout50us) 和 timer2_nvic() 是timer2初始化函数,我也同样建立了一个.c和一个.h文件保存,这两个文件在我的工程里面的HARDWARE文件夹下,也可以直接去拷贝,内容如下:

bsp_timer2.c


#include "bsp_timer2.h"void timer2_init(uint16_t period)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_DeInit(TIM2);TIM_TimeBaseStructure.TIM_Period = period;TIM_TimeBaseStructure.TIM_Prescaler = (1800 - 1);   TIM_TimeBaseStructure.TIM_ClockDivision = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);TIM_Cmd(TIM2, ENABLE);}void timer2_nvic(void)
{NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  NVIC_Init(&NVIC_InitStructure);
}

bsp_timer2.h

#ifndef __TIMER2_H
#define __TIMER2_H#include "stm32f10x.h"void timer2_init(uint16_t period);
void timer2_nvic(void);#endif /* __TIMER2_H */

这样,两个文件就补充好了,接下来需要我们将bsp_usart1.h和bsp_timer2.h的文件路径加入我们的工程,如下图:


此时编译会提示我们没有加入官方库函数,我们现在导入

我的提示官方标准库里面的一些关于tim函数没找到,代表我们没加入的stm32f10x_tim.c文件,加入就好了,你们的还可能提示关于usart的文件没找到,加入stm32f10x_usart.c就好,这个我就不写教程了,不会的话建议重新学stm32,加入后如图:

接下来在编译,会发现就只报4个错误了,提示4个文件未定义,如下图:

跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。

5、在main.c文件中定义各个模拟寄存器的地址和大小。

将下面的宏定义放到我们main.c 声明头文件结束之后

/* ----------------------- Defines ------------------------------------------*/
//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16/* Private variables ---------------------------------------------------------*/
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};

6.补全输入寄存器操作函数、保持寄存器操作函数、线圈操作函数、离散寄存器函数

在main.c文件里面更改int main()函数对modbus功能进行初始化,设置地址和波特率。这部分内容可以参考官方资料里的例程,也可以直接复制别人写好的。这是我写好的代码:

/*** @brief  主函数* @param  无* @retval 无*/
int main(void)
{//初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验    eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);eMBEnable();   for(;;){(void)eMBPoll();}
}/*********************************************END OF FILE**********************//****************************************************************************
* 名   称:eMBRegInputCB
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNRegs: 要读取的寄存器个数
* 出口参数:
* 注   意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*                               +StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*                               +LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*                               +CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*                           3 区
****************************************************************************/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if( ( usAddress >= REG_INPUT_START )&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ){iRegIndex = ( int )( usAddress - usRegInputStart );while( usNRegs > 0 ){*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}/****************************************************************************
* 名   称:eMBRegHoldingCB
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister
*                                                   16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*                                                   03 读保持寄存器 eMBFuncReadHoldingRegister
*                                                   23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNRegs: 要读写的寄存器个数
*                       eMode: 功能码
* 出口参数:
* 注   意:4 区
****************************************************************************/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);switch(eMode){                                       case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++;usNRegs--;                 }                            break;case MB_REG_WRITE://写 MB_REG_WRITE = 0while(usNRegs > 0){         usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}              }}else//错误{eStatus = MB_ENOREG;}   return eStatus;
}extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );
/****************************************************************************
* 名   称:eMBRegCoilsCB
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*                                                   05 写线圈 eMBFuncWriteCoil
*                                                   15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 线圈地址
*                       usNCoils: 要读写的线圈个数
*                       eMode: 功能码
* 出口参数:
* 注   意:如继电器
*                       0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//寄存器个数int16_t iNCoils = ( int16_t )usNCoils;//寄存器偏移量int16_t usBitOffset;//检查寄存器是否在指定范围内if( ( (int16_t)usAddress >= REG_COILS_START ) &&( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) ){//计算寄存器偏移量usBitOffset = ( int16_t )( usAddress - REG_COILS_START );switch ( eMode ){//读操作case MB_REG_READ:while( iNCoils > 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );iNCoils -= 8;usBitOffset += 8;}break;//写操作case MB_REG_WRITE:while( iNCoils > 0 ){xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),*pucRegBuffer++ );iNCoils -= 8;}break;}}else{eStatus = MB_ENOREG;}return eStatus;
}/****************************************************************************
* 名   称:eMBRegDiscreteCB
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注   意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//操作寄存器个数int16_t iNDiscrete = ( int16_t )usNDiscrete;//偏移量uint16_t usBitOffset;//判断寄存器时候再制定范围内if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ){//获得偏移量usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );while( iNDiscrete > 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );iNDiscrete -= 8;usBitOffset += 8;}}else{eStatus = MB_ENOREG;}return eStatus;
}

同时这里需要在main.c文件里面加入如下两个头文件

#include "mb.h"
#include "mbutils.h"

完整的main.c文件内容如下:

#include "led.h"
#include "delay.h"
#include "sys.h"
#include "mb.h"
#include "mbutils.h"/* ----------------------- Defines ------------------------------------------*/
//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16/* Private variables ---------------------------------------------------------*/
//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};/*** @brief  主函数* @param  无* @retval 无*/
int main(void)
{//初始化 RTU模式 参数二:不用管,参数3:从机地址为1 参数4:9600 参数5:无效验    eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);eMBEnable();   for(;;){(void)eMBPoll();}
}/*********************************************END OF FILE**********************//****************************************************************************
* 名   称:eMBRegInputCB
* 功    能:读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNRegs: 要读取的寄存器个数
* 出口参数:
* 注   意:上位机发来的 帧格式是: SlaveAddr(1 Byte)+FuncCode(1 Byte)
*                               +StartAddrHiByte(1 Byte)+StartAddrLoByte(1 Byte)
*                               +LenAddrHiByte(1 Byte)+LenAddrLoByte(1 Byte)+
*                               +CRCAddrHiByte(1 Byte)+CRCAddrLoByte(1 Byte)
*                           3 区
****************************************************************************/
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if( ( usAddress >= REG_INPUT_START )&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ){iRegIndex = ( int )( usAddress - usRegInputStart );while( usNRegs > 0 ){*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}/****************************************************************************
* 名   称:eMBRegHoldingCB
* 功    能:对应功能码有:06 写保持寄存器 eMBFuncWriteHoldingRegister
*                                                   16 写多个保持寄存器 eMBFuncWriteMultipleHoldingRegister
*                                                   03 读保持寄存器 eMBFuncReadHoldingRegister
*                                                   23 读写多个保持寄存器 eMBFuncReadWriteMultipleHoldingRegister
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNRegs: 要读写的寄存器个数
*                       eMode: 功能码
* 出口参数:
* 注   意:4 区
****************************************************************************/
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);switch(eMode){                                       case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++;usNRegs--;                 }                            break;case MB_REG_WRITE://写 MB_REG_WRITE = 0while(usNRegs > 0){         usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}              }}else//错误{eStatus = MB_ENOREG;}   return eStatus;
}extern void xMBUtilSetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits,UCHAR ucValue );
extern UCHAR xMBUtilGetBits( UCHAR * ucByteBuf, USHORT usBitOffset, UCHAR ucNBits );
/****************************************************************************
* 名   称:eMBRegCoilsCB
* 功    能:对应功能码有:01 读线圈 eMBFuncReadCoils
*                                                   05 写线圈 eMBFuncWriteCoil
*                                                   15 写多个线圈 eMBFuncWriteMultipleCoils
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 线圈地址
*                       usNCoils: 要读写的线圈个数
*                       eMode: 功能码
* 出口参数:
* 注   意:如继电器
*                       0 区
****************************************************************************/
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//寄存器个数int16_t iNCoils = ( int16_t )usNCoils;//寄存器偏移量int16_t usBitOffset;//检查寄存器是否在指定范围内if( ( (int16_t)usAddress >= REG_COILS_START ) &&( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) ){//计算寄存器偏移量usBitOffset = ( int16_t )( usAddress - REG_COILS_START );switch ( eMode ){//读操作case MB_REG_READ:while( iNCoils > 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );iNCoils -= 8;usBitOffset += 8;}break;//写操作case MB_REG_WRITE:while( iNCoils > 0 ){xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),*pucRegBuffer++ );iNCoils -= 8;}break;}}else{eStatus = MB_ENOREG;}return eStatus;
}/****************************************************************************
* 名   称:eMBRegDiscreteCB
* 功    能:读取离散寄存器,对应功能码有:02 读离散寄存器 eMBFuncReadDiscreteInputs
* 入口参数:pucRegBuffer: 数据缓存区,用于响应主机
*                       usAddress: 寄存器地址
*                       usNDiscrete: 要读取的寄存器个数
* 出口参数:
* 注   意:1 区
****************************************************************************/
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//操作寄存器个数int16_t iNDiscrete = ( int16_t )usNDiscrete;//偏移量uint16_t usBitOffset;//判断寄存器时候再制定范围内if( ( (int16_t)usAddress >= REG_DISCRETE_START ) &&( usAddress + usNDiscrete <= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ){//获得偏移量usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );while( iNDiscrete > 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,( uint8_t)( iNDiscrete > 8 ? 8 : iNDiscrete ) );iNDiscrete -= 8;usBitOffset += 8;}}else{eStatus = MB_ENOREG;}return eStatus;
}

如果你没有这些头文件:

#include "led.h"
#include "delay.h"
#include "sys.h"

就将其换成

#include "stm32f10x.h"

7. 修改mbrtu.c文件

否则modbus从机收到命令后,只会返回一次数据。在函数“eMBRTUSend”中,函数在mbrtu.c文件里面。修改后的代码如下:

eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{eMBErrorCode    eStatus = MB_ENOERR;USHORT          usCRC16;ENTER_CRITICAL_SECTION(  );/* Check if the receiver is still in idle state. If not we where to* slow with processing the received frame and the master sent another* frame on the network. We have to abort sending the frame.*/if( eRcvState == STATE_RX_IDLE ){/* First byte before the Modbus-PDU is the slave address. */pucSndBufferCur = ( UCHAR * ) pucFrame - 1;usSndBufferCount = 1;/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;usSndBufferCount += usLength;/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );/* Activate the transmitter. */eSndState = STATE_TX_XMIT;//启动第一次发送,这样才可以进入发送完成中断xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );pucSndBufferCur++;  /* next byte in sendbuffer. */usSndBufferCount--;//修改了从这里往下//使能发送状态,禁止接收状态vMBPortSerialEnable( FALSE, TRUE );}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION(  );return eStatus;
}

8. 修改mbconfig.h文件

取消对ASCII的支持。

#define MB_ASCII_ENABLED                        (  0 )#define MB_ASCII_TIMEOUT_SEC                    (  0 )

这里再编译就会发现没有错误了,有4个警告,四个警告都是说与0比较无意义,因为我们四个寄存器的起始地址都为0x0000,所以这里不用管它,等哪天公司要求改地址啥的方好直接改宏定义就好了。

跟我一样的情况就继续往下,不是一样的就检查检查,或者再来一遍。一样的话建议先保存一个副本,防止下面的操作出问题,然后又得重新开始。有副本的话可以继续从这里开始。

9. 修改一些细节,让我们读取写入的时候地址不会自动加一,使读写的地址准确:

需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c

直接去对应的文件搜索:usRegAddress++;
然后屏蔽掉。
mbfunccoils.c里面有三处
mbfuncdisc.c里面有一处
mbfuncholding.c里面有三处
mbfuncinput.c里面有一处
修改后就可以使读写地址不会自动加1了,如果疑问心比较强的同学也可以不屏蔽测测。

10. modbus通信格式以及寄存器功能码。

10.1 通信格式:

这个长度是根据我的代码来的。

10.1.1 写操作

通信地址 功能号 寄存器地址 数据 CRC效验
8bit 8bit 16bit 16bit 16bit

例如:写单个线圈

01 05 00 00 00 01 0C 0A

写返回(跟写的数据一模一样表示写成功):

通信地址 功能号 寄存器地址 数据 CRC效验
8bit 8bit 16bit 16bit 16bit

例如:

01 05 00 00 00 01 0C 0A

10.1.2 读操作

通信地址 功能号 寄存器地址 数据长度 CRC效验
8bit 8bit 16bit 16bit 16bit

例如:读单个线圈

01 01 00 00 00 01 FD CA

读返回:

通信地址 功能号 数据长度 数据 CRC效验
8bit 8bit 16bit 16bit 16bit

例如:

01 01 00 02 00 00 FD CA

10.2 功能码详解

modbus完整支持很多功能码,但是实际在应用的时候常用的也就那么几个。具体如下:

0x01: 读线圈寄存器0x02: 读离散输入寄存器0x03: 读保持寄存器0x04: 读输入寄存器0x05: 写单个线圈寄存器0x06: 写单个保持寄存器0x0f:  写多个线圈寄存器0x10: 写多个保持寄存器

如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器。这名字也不知道谁起的,让人看了一点不通俗易懂,搞得晕晕乎乎。实际上你要是看清他的本质就很简单了。下面分别解释一下:

线圈寄存器,实际上就可以类比为开关量,没一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f

离散输入寄存器,如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02

保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10

输入寄存器,只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04

11. 保存,编译,下载。使用专用的modbus工具测试

工具在我上传的资源文件里面,自行下载。
工具配置如下:

modbus指令格式如下:

咱们这里设置如下:01 04 00 00 00 01,功能码04,起始地址0,数据长度1.校验码没有写怎么办?

这就是这个工具的便利之处!我们不用管,它会自动计算!直接点击发送即可。得到结果如下:

可以看到下面的框里,绿色的是我们发送的内容,最后两位是工具自动补上的。蓝色内容是单片机(也就是modbus从机)返回给我们的。读取的内容是输入寄存器04里面的00 00地址的数据,数据内容为10 00

12. 广播地址回复设置

这里我们发送广播地址是没有回复的,想要有回复的话在mb.c文件里面的函数eMBPoll( void )里面屏蔽掉一个if条件,下面是我屏蔽好的。这个根据自己的需求来

eMBErrorCode
eMBPoll( void )
{static UCHAR   *ucMBFrame;static UCHAR    ucRcvAddress;static UCHAR    ucFunctionCode;static USHORT   usLength;static eMBException eException;int             i;eMBErrorCode    eStatus = MB_ENOERR;eMBEventType    eEvent;/* Check if the protocol stack is ready. */if( eMBState != STATE_ENABLED ){return MB_EILLSTATE;}/* Check if there is a event available. If not return control to caller.* Otherwise we will handle the event. */if( xMBPortEventGet( &eEvent ) == TRUE ){switch ( eEvent ){case EV_READY:break;case EV_FRAME_RECEIVED:eStatus = peMBFrameReceiveCur( &ucRcvAddress, &ucMBFrame, &usLength );if( eStatus == MB_ENOERR ){/* Check if the frame is for us. If not ignore the frame. */if( ( ucRcvAddress == ucMBAddress ) || ( ucRcvAddress == MB_ADDRESS_BROADCAST ) ){( void )xMBPortEventPost( EV_EXECUTE );}}break;case EV_EXECUTE:ucFunctionCode = ucMBFrame[MB_PDU_FUNC_OFF];eException = MB_EX_ILLEGAL_FUNCTION;for( i = 0; i < MB_FUNC_HANDLERS_MAX; i++ ){/* No more function handlers registered. Abort. */if( xFuncHandlers[i].ucFunctionCode == 0 ){break;}else if( xFuncHandlers[i].ucFunctionCode == ucFunctionCode ){eException = xFuncHandlers[i].pxHandler( ucMBFrame, &usLength );break;}}/*这里的代码含义,如果请求没有发送到广播地址我们返回一个回复。*/ /*我屏蔽了,发送广播地址也要求回复*/
//            if( ucRcvAddress != MB_ADDRESS_BROADCAST )
//            {if( eException != MB_EX_NONE ){/* An exception occured. Build an error frame. */usLength = 0;ucMBFrame[usLength++] = ( UCHAR )( ucFunctionCode | MB_FUNC_ERROR );ucMBFrame[usLength++] = eException;}if( ( eMBCurrentMode == MB_ASCII ) && MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ){vMBPortTimersDelay( MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS );}                eStatus = peMBFrameSendCur( ucMBAddress, ucMBFrame, usLength );
//            }break;case EV_FRAME_SENT:break;}}return MB_ENOERR;
}

我写好的程序以及测试工具以及modbus1.6文件下载连接:https://download.csdn.net/download/qq_40305944/12642118

STM32 移植FreeModbus详细过程相关推荐

  1. TFT-LCD移植LVGL详细过程记录

    TFT-LCD移植LVGL LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用. LVGL更多介绍:http ...

  2. 2440移植Mplayer详细过程(最简便的方法)以及报错解决

    移植环境(红色粗字体字为修改后内容,蓝色粗体字为特别注意内容) 1,开发板:韦东山JZ2440 2,linux 版本:linux-3.4.2 3,系统版本:Ubuntu9.10 4,交叉编译环境:ar ...

  3. 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇

    具体Freemodbus移植到STM32步骤参考: STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤 基于STM32HAL库移植FreeModbus FreeModbu ...

  4. 移植FreeModbus

    freeemodbus百度百科(介绍比较详细) https://baike.baidu.com/item/freemodbus/7566841?fr=aladdin STMC2CubeMX | STM ...

  5. UCOS2_STM32移植详细过程(汇总)

    Ⅰ.概述 笔者发现一个问题,很多初学者,甚至很多工作一两年的人,他们有一种依赖的思想,就是希望从别处获取的软件代码不做任何修改,直接可以运行或者使用.笔者想说,实践才是检验真理的关键,实践才是掌握知识 ...

  6. STM32移植LVGL8.0.2超详细的保姆级教程附移植好的工程文件

    文章目录 前言 一.什么是LVGL? 二.先看效果 三.移植前准备工作 1.准备原有工程 2.下载LVGL源码 四.开始移植 1.把源码搬运到工程文件夹里 2.把搬运好的代码添加到keil工程 3.动 ...

  7. 手把手教你移植FreeModbus到STM32【看评论区引导,领取全套资料包】

    为什么要移植freemodbus 大家好,近期由于一个小项目的需要,要用到Modbus协议进行通信.相信各位工作的小伙伴们,或多或少都要跟Modbus打交道吧.那么,Modbus协议的重要性我自不必多 ...

  8. 【STM32】Freemodbus流程解析函数解析详细

     此篇博客将按照Freemodbus的运行流程,对各个函数进行解析(无主机RTU模式), 我将尽我所能解释的尽量清楚,如有错误或者语义模糊处还请在评论区指出,谢谢. 运行流程:freemodbus流程 ...

  9. android的wifi网卡移植详细过程已经通用驱动的问题

    这里有一篇详细的教程,看完还有一个问题 就是android的wifi驱动移植,如果有wifi网卡的驱动代码,是一定需要对android系统本身的代码修改重写编译吗?就是说,有无可能不改变android ...

最新文章

  1. 如果不写Order By子句,会怎么样
  2. Java黑皮书课后题第1章:*1.11(人口估算)编写一个程序,显示未来5年的每年人口数。假设当前的人口是312 032 486,每年有365天
  3. php正则相对地址,php – 正则表达式将相对URL更改为绝对值
  4. vue-beauty 的v-data-table数据单元不换行
  5. pat 乙级 1021 个位数统计(C++)
  6. Flutter Listener 监听手指的滑动方向、监听手指上下滑动
  7. linux 窗口管理器_您最喜欢的Linux窗口管理器是什么?
  8. 安卓应用市场分类排名算法一般是怎样的?
  9. 详解Spring Security进阶身份认证之UserDetailsService(附源码)
  10. 如何确定线程池核心数的最佳值?
  11. Xcode真机调试不了,提示 “Please reconnect the device”
  12. python的ols_Python Statsmodels 统计包之 OLS 回归
  13. react 实现图片正在加载中 加载完成 加载失败三个阶段的
  14. Java枚举类与常用方法
  15. 本科毕设不通过是什么原因,哪个少年不曾为如何能够顺利优秀毕业而愁眉苦战
  16. 使用react制作点赞组件
  17. 微信小程序之js 字符串转换成数字的三种方法, 取float型小数点后两位数的方法
  18. 基于STM32的游戏平台,其二TETRIS
  19. 2022前端面试需要掌握的面试题
  20. 《出师表 》-英文版 苟全性命于乱世,不求闻达于诸侯

热门文章

  1. 【原创】如何做一张原创8BIT音乐的NES音乐卡片
  2. 1053 Path of Equal Weight (30分)
  3. 一款好用的内存清理工具
  4. MVC用AuthorizeAttribute登录验证
  5. [Mysql] primary key 简明
  6. 如何让百度快照更新不断
  7. 联想台式机ideacentre 510A-15IKL安装Win7系统
  8. 赛门铁克警告Switch模拟器下载链接实为垃圾站点
  9. RF(四则运算及 Evaluate 用法)
  10. NFC读写芯片15693协议CLRC663国产替代DP1363F兼容详细对比资料