基于STM32CubeMX移植freeModbus RTU(从站)-避坑篇
- (重点)Chapter0 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇
- (1)Freemodbus移植到TTL的USART1可行,但改为485的USART2不行
- (2)485-Modbus 通讯 Timeout,数据发送不全或者结尾总是0xFF
- (重点)Chapter1 基于STM32CubeMX移植freeModbus RTU(从站)
- 1.需要的材料
- 2.操作步骤
- 1.串口设置问题
- 2.定时器设置问题
- 具体操作:
- 1.选择MCU型号
- 2.使能时钟源RCC为外部时钟
- 3.配置时钟树,记录APB1频率
- 4.使能定时器4,预分频系数为3600-1,对应的分频频率为20KHz,不懂的回到上面去看定时器设置解析,自动重载值设置为35,得到超时时间1750us。
- 5.使能定时器中断
- 6.配置串口2,选择异步通信后参数设置为115200,8,NONE,1
- 7.使能串口中断
- 8.配置中断优先级,定时器中断优先级低于串口中断即可
- 9.配置项目参数并分离头文件和c文件后生成代码。
- 10.打开freeModbus代码包的demo文件夹,新建一个名为STM32MB的文件夹,之后将BARE文件夹内所有内容复制到STM32MB文件夹下,复制完成如图
- 11.回到freeModbus代码包,复制整个modbus文件夹也粘贴到STM32MB文件夹内,完成效果如图
- 12.将STM32MB文件夹移动到stm32cubeMX生成的工程目录下,如图
- 13.打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,
- 14.修改demo.c文件夹的main函数名为host,编译不报错即可开始修改,如图所示
- 15.修改MB_Port下的portserial.c文件(串口设置)
- 16.修改MB_Port下的porttimer.c文件(定时器设置)
- 17.修改完Modbus与stm32的接口文件之后要在port.h文件内定义总中断
- 18.串口及定时器中断修改
- 19.modbus功能处理
- 20.modbus启动
- 21.modbus测试
- Chapter2 基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植
- Chapter3 基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植
- Chapter4 STM32CubeMX | STM32 HAL库移植FreeModbus详细步骤
- Chapter5 STM32 移植FreeModbus详细过程
- 3. 完善portserial.c文件
- 4. 完善porttimer.c文件
- 5、在main.c文件中定义各个模拟寄存器的地址和大小。
- 6.补全输入寄存器操作函数、保持寄存器操作函数、线圈操作函数、离散寄存器函数
- 7. 修改mbrtu.c文件
- 8. 修改mbconfig.h文件
- 9. 修改一些细节,让我们读取写入的时候地址不会自动加一,使读写的地址准确:
- 10. modbus通信格式以及寄存器功能码。
- 12. 广播地址回复设置
- (重点)Chapter6 【freeModbus】STM32之HAL库移植笔记(避坑主要内容之一)
- 一、下载压缩包
- 二、移植准备
- 三、新建工程
- 重点!重点!!重点!!!
- 不要开启选择的定时器以及串口中断啊,纯纯的都是血泪史,之前就是看教程没有这一步,浪费了好多时间;
- 四、文件添加
- 五、内容修改
- 1.1 【vMBPortSerialEnable(BOOL , BOOL)】
- 1.2【xMBPortSerialInit】
- 1.3【xMBPortSerialPutByte】
- 1.4【xMBPortSerialGetByte】
- 1.5 接收/发送中断处理函数
- 1.6 中断调用
- 2.接下来我们需要配置的是定时器,即【porttimer.c】文件
- 2.1定时器初始化【xMBPortTimersInit】
- 2.2定时器使能/失能【vMBPortTimersEnable】
- 2.3超时处理函数【prvvTIMERExpiredISR】
- 2.4中断调用
- 3.从机地址修改
- 4.Modbus ASCII
- 5.发送中断修复
- 6.点开魔术棒,勾选Use MicroLIB,很重要的一步!!!
- 7.[demo.c]
- 8.[main.h]
- 六、测试
(重点)Chapter0 移植Freemodbus到STM32(基于CubeMX,HAL库)-避坑篇
原文链接:https://blog.csdn.net/xq1998/article/details/123707792
具体Freemodbus移植到STM32步骤参考:
STMC2CubeMX | STM32 HAL库移植FreeModbus详细步骤
基于STM32HAL库移植FreeModbus
FreeModbus+STM32 +HAL库 无操作系统移植 (已在正点原子阿波罗F429开发板上移植成功)
移植过程各路网站上都比较多,具体可参考以上链接内容。把思路和步骤记录一下:
①下载复制Freemodbus库;
② 使用CubeMX生成工程,需要注意配置串口、timer,NVIC优先级串口高于timer,需要用到中断,但不需要生成IRQHandle(移植中有手动添加)
③添加Freemodbus中相关.c.h文件到工程,设置中配置c++ include目录
④移植的核心在于port.c,portserial.c,porttimer.c,portevent.c 四个文件的修改与配置;其中配置serial与time是关键,需修改portserial.c与porttimer.c,操作时,只需要修改为需要的串口和timer即可,其他内容不需要动。
⑤如果用到485收发芯片,需要添加收发控制端代码(具体可以参考下边问题1种内容)
⑥根据需要配置port.c文件,如果初步测试,不用修改
⑦编译下载,然后就可以测试了
整个移植过程其实不是太复杂(站在前人基础上),但还是容易出现一些问题,在此仅个人移植存在的问题,踩过的坑在这里记录一下
(1)Freemodbus移植到TTL的USART1可行,但改为485的USART2不行
具体和端口没关系,主要问题是端口使用到485芯片,需要有收发控制端的配置,并在适当时候打开即可;
这个问题具体操作是修改portserial.c文件中 vMBPortSerialEnable函数内容,添加打开接收或者打开发送控制端,具体需要根据芯片及接法有关。
/* ------------- Start implementation -------------------------*/
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if(xRxEnable)
{
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);// 使能接收非空中断
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET); //MAX485操作 低电平为接收模式
}
else
{
__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);// 禁能接收非空中断
}
if(xTxEnable)
{
HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET); //MAX485操作 高电平为发送模式
__HAL_UART_ENABLE_IT(&huart2, UART_IT_TXE); // 使能发送为空中断
}
else
{
__HAL_UART_DISABLE_IT(&huart2, UART_IT_TXE); // 禁能发送为空中断
}
}
(2)485-Modbus 通讯 Timeout,数据发送不全或者结尾总是0xFF
这个问题的在不使用485芯片上一般不出现,在使用到485芯片的时候就比较明显,根源在于收发过程切换。仔细调试发现,MCU已经将数据传输出去,TTL口数据已经正常,但到485芯片后就出错,主要是MCU从发送到接收的转换太快,数据还没发送完全就将485的收发端口从发送转换为接收,造成最后一个字节数据没有从485芯片传输出去,造成出错。修改也比较简单,在切换之前进行适当延时,确保数据发送完成后在进行切换收发使能端口状态,即可解决。通过多方测试,感觉加到 cBOOL xMBRTUTransmitFSM( void )比较合适:增加语句【while(i<RS485_swtict_delay) i++; 】即可,具体位置看代码吧,其实加到相应的串口中断中也可以,不过可能影响效率。
关于RS485_swtict_delay 设置多大合适呢
经测试的波特率115200时,1000左右即可;波特率9600时,13000左右;这个延时时间与芯片延时有关系,具体以测试为准。
BOOL xMBRTUTransmitFSM( void )
{
BOOL xNeedPoll = FALSE;
uint16_t i = 0;
assert( eRcvState == STATE_RX_IDLE );
switch ( eSndState )
{
/* We should not get a transmitter event if the transmitter is in
* idle state. */
case STATE_TX_IDLE:
/* enable receiver/disable transmitter. */
vMBPortSerialEnable( TRUE, FALSE );
break;
case STATE_TX_XMIT:
/* check if we are finished. */
if( usSndBufferCount != 0 )
{
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++; /* next byte in sendbuffer. */
usSndBufferCount--;
}
else
{
xNeedPoll = xMBPortEventPost( EV_FRAME_SENT );
/* Disable transmitter. This prevents another transmit buffer
* empty interrupt. */
*
// ********看这里***** ↓ 边是重点
while(i<RS485_swtict_delay) i++; //补充延时
// ********看这里***** ↑ 边是重点
vMBPortSerialEnable( TRUE, FALSE );
eSndState = STATE_TX_IDLE;
}
break;
}
return xNeedPoll;
}
(重点)Chapter1 基于STM32CubeMX移植freeModbus RTU(从站)
原文链接:https://blog.csdn.net/ASWaterbenben/article/details/105549750
困惑了将近一年多的ModbusRTU在我昨天穷极无聊给自己定目标的情况下搞出来了,以前移植不出来主要原因就是基本功不扎实,没有进一步理解串口和定时器配置的原理,一通操作,移植完之后就Timeout,接下来就分享一下我是怎么从0开始移植这个协议的。
项目已上传码云,文章底部有链接!
1.需要的材料
STM32开发板一块,不限型号
freeModbus包可进入后方链接下载(Modbus官方源码包)
STM32CubeMX
2.操作步骤
操作之前先讲两个主要问题
1.串口设置问题
MoubusRTU移植到stm32平台通信是通过串口进行通信,主要是需要串口进行收发,所以发送中断时必须的,在波特率设置问题上是和定时器相关联,在mbrtu.c文件的eMBRTUInit函数里具体说明了串口波特率和定时器设置的关系
eMBErrorCode
eMBRTUInit( UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity )
{
eMBErrorCode eStatus = MB_ENOERR;
ULONG usTimerT35_50us;
( void )ucSlaveAddress;
ENTER_CRITICAL_SECTION( );
/* Modbus RTU uses 8 Databits. */
if( xMBPortSerialInit( ucPort, ulBaudRate, 8, eParity ) != TRUE )
{
eStatus = MB_EPORTERR;
}
else
{
/* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
if( ulBaudRate > 19200 )
{
usTimerT35_50us = 35; /* 1750us. */
}
else
{
/* The timer reload value for a character is given by:
*
* ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
* = 11 * Ticks_per_1s / Baudrate
* = 220000 / Baudrate
* The reload for t3.5 is 1.5 times this value and similary
* for t3.5.
*/
usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
}
if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
{
eStatus = MB_EPORTERR;
}
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
从上面代码的注释中可以看出,当波特率大于19200时,超时时间固定位为1750us,当波特率小于19200时,超时时间为3.5个字符时间,具体计算公式在代码注释里已经有了,这里我就不多赘述。本人波特率使用115200,所以按照1750us来。
2.定时器设置问题
ModbusRTU是通过定时器和串口配合来实现Modbus通信的,所以定时器是决定有没有超时的一大关键问题,由串口设置部分可知,定时器设置是要配合串口设置的波特率食用比较香,所以根据我使用的115200波特率可以得到我定时器设置。首先是APB1的主频率获取到,modbus要求通过预分配后得到的周期为50us,对应频率为20KHz。根据rtu初始化代码得到自动重载值设置为35。
具体操作:
熟悉stm32cubemx的老司机可以直接从15步看起
1.选择MCU型号
2.使能时钟源RCC为外部时钟
3.配置时钟树,记录APB1频率
4.使能定时器4,预分频系数为3600-1,对应的分频频率为20KHz,不懂的回到上面去看定时器设置解析,自动重载值设置为35,得到超时时间1750us。
5.使能定时器中断
6.配置串口2,选择异步通信后参数设置为115200,8,NONE,1
7.使能串口中断
8.配置中断优先级,定时器中断优先级低于串口中断即可
9.配置项目参数并分离头文件和c文件后生成代码。
10.打开freeModbus代码包的demo文件夹,新建一个名为STM32MB的文件夹,之后将BARE文件夹内所有内容复制到STM32MB文件夹下,复制完成如图
11.回到freeModbus代码包,复制整个modbus文件夹也粘贴到STM32MB文件夹内,完成效果如图
12.将STM32MB文件夹移动到stm32cubeMX生成的工程目录下,如图
13.打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,
打开工程,引入STM32MB内的所有头文件,并新建名为MB和MB_Port的组,MB内添加STM32MB文件夹下modbus文件夹内所有c文件以及根目录的demo.c文件,MB_Port内添加STM32MB文件夹下port文件夹内所有c文件,如图所示
14.修改demo.c文件夹的main函数名为host,编译不报错即可开始修改,如图所示
以下为正式修改Modbus代码,上面比较繁琐,熟悉stm32cubemx的老司机可以直接从15步看起
15.修改MB_Port下的portserial.c文件(串口设置)
我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注
#include "port.h"
#include "stm32f7xx_hal.h"
#include "usart.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.
*/
if (xRxEnable) //将串口收发中断和modbus联系起来,下面的串口改为自己使能的串口
{
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE); //我用的是串口2,故为&huart2
}
else
{
__HAL_UART_DISABLE_IT(&huart2,UART_IT_RXNE);
}
if (xTxEnable)
{
__HAL_UART_ENABLE_IT(&huart2,UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart2,UART_IT_TXE);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
return TRUE; //改为TURE,串口初始化在usart.c定义,mian函数已完成
}
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. */
if(HAL_UART_Transmit (&huart2 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK ) //添加发送一位代码
return FALSE ;
else
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.
*/
if(HAL_UART_Receive (&huart2 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )//添加接收一位代码
return FALSE ;
else
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 ) //删去前面的static,方便在串口中断使用
{
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 ) //删去前面的static,方便在串口中断使用
{
pxMBFrameCBByteReceived( );
}
16.修改MB_Port下的porttimer.c文件(定时器设置)
我直接贴代码,自己对比我的代码和源码差距,关键地方我会在后边标注
#include "port.h"
#include "stm32f7xx_hal.h"
#include "tim.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
//static void prvvTIMERExpiredISR( void );
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us ) //定时器初始化直接返回TRUE,已经在mian函数初始化过
{
return TRUE;
}
inline void
vMBPortTimersEnable( ) //使能定时器中断,我用的是定时器4,所以为&htim4
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim4,TIM_IT_UPDATE);
__HAL_TIM_SET_COUNTER(&htim4,0);
__HAL_TIM_ENABLE(&htim4);
}
inline void
vMBPortTimersDisable( ) //取消定时器中断
{
/* Disable any pending timers. */
__HAL_TIM_DISABLE(&htim4);
__HAL_TIM_SET_COUNTER(&htim4,0);
__HAL_TIM_DISABLE_IT(&htim4,TIM_IT_UPDATE);
__HAL_TIM_CLEAR_IT(&htim4,TIM_IT_UPDATE);
}
/* 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 ) //modbus定时器动作,需要在中断内使用
{
( void )pxMBPortCBTimerExpired( );
}
17.修改完Modbus与stm32的接口文件之后要在port.h文件内定义总中断
位置在port.h文件的32行和33行,修改为如下所示,并在port.h前包含上stm32的hal库,如图所示
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1) //关总中断
#define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0) //开总中断
#include "stm32f7xx_hal.h"
modbus端口函数到此修改完成,接下来是中断函数
18.串口及定时器中断修改
打开工程内的中断文件,是在Application/User–>stm32f7xx_it.c
根据板子不同而不同,区别是stm32f后面的数字。知道是中断管理文件就行
在/* USER CODE BEGIN PFP */后添加以下代码,用于和modbus的串口和定时器功能代码联系
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );
找到自己设置的串口中断处理函数,添加如下代码,用于将串口收到的内容移动到modbus功能函数进行处理
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET)
{
prvvUARTRxISR();//接收中断
}
if(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET)
{
prvvUARTTxReadyISR();//发送中断
}
HAL_NVIC_ClearPendingIRQ(USART2_IRQn);
HAL_UART_IRQHandler(&huart2);
/* USER CODE END USART2_IRQn 1 */
}
在Application/User–>stm32f7xx_it.c末尾的/* USER CODE BEGIN 1 */添加定时器中断回调函数如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器中断回调函数,用于连接porttimer.c文件的函数
{
/* NOTE : This function Should not be modified, when the callback is needed,
the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file
*/
prvvTIMERExpiredISR( );
}
到此,串口和定时器的问题已经处理完毕,接下来是modbus的配置
19.modbus功能处理
硬件接口方面结束之后就可以开始写功能了,在MB–>demo.c中有功能示例,我们根据功能示例来修改对应的功能并使能modbus,这里我只说输入寄存器功能,其它的一次类推,就不多赘述。
这里也是直接贴代码,大概说一下,就是自己设置一个数组,将数据放到数组内,并在被读取时根据数据位置将数据返回去
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 5
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
//static
uint16_t usRegInputBuf[REG_INPUT_NREGS];
uint16_t InputBuff[5];
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
int i;
InputBuff[0] = 0x11;
InputBuff[1] = 0x22;
InputBuff[2] = 0x33;
InputBuff[3] = 0x44;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );
for(i=0;i<usNRegs;i++)
{
*pucRegBuffer=InputBuff[i+usAddress-1]>>8;
pucRegBuffer++;
*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
pucRegBuffer++;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
20.modbus启动
启动modbus需要在main函数进行初始化、开启侦听操作,需要添加以下代码,对应位置可在mian函数找到
/* USER CODE BEGIN Includes */
#include "mb.h"
#include "mbport.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。
eMBEnable( );//使能modbus
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
( void )eMBPoll( );//启动modbus侦听
}
/* USER CODE END 3 */
至此修改完毕,编译下载之后即可使用modbus poll进行连接测试。
21.modbus测试
将上述代码编译下载到板子,用TTL转USB接入PC,找到在PC的对应端口即可打开ModbusPoll进行通信测试
代码下载成功后打开ModbusPoll,打开读写定义并设置为从站地址1,功能04读输入寄存器,起始地址0,长度为4,如图所示
按F3进行连接,连接设置如图,串口所在位置会显示TTL转串口的芯片型号,按照如下设定后确定。
即可得到下图,由于我们输入寄存器存放的是16进制数,所以要将ModbusPoll显示模式改为16进制才能显示相同内容
最终效果如下图,ModbusPoll读取的值与STM32内寄存器内的值一致,读取成功!
至此,freeModbusRTU移植成功!,具体代码已上传,详见我的码云F7_ModbusRTU
Chapter2 基于STM32CubeMX+STM32F407ZGT6+FreeRTOS+freeMODBUS_RTU的移植
原文链接:https://blog.csdn.net/qq_41252980/article/details/116709983
Chapter3 基于CubeMX+STM32F405RGT6+freeMODBUS_RTU的移植
原文链接:https://blog.csdn.net/yiyimufeng/article/details/115966004
Chapter4 STM32CubeMX | STM32 HAL库移植FreeModbus详细步骤
原文链接:https://blog.csdn.net/qq153471503/article/details/104840279
Chapter5 STM32 移植FreeModbus详细过程
原文链接:https://blog.csdn.net/qq_40305944/article/details/107447042
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 5000
void 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 = 0
while(usNRegs > 0)
{
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE://写 MB_REG_WRITE = 0
while(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 = 0
while(usNRegs > 0)
{
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE://写 MB_REG_WRITE = 0
while(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通信格式以及寄存器功能码。
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;
}
(重点)Chapter6 【freeModbus】STM32之HAL库移植笔记(避坑主要内容之一)
原文链接:https://blog.csdn.net/peng19106/article/details/132052766
工作主要是传感器相关,常与之打交道的协议,莫过于MODBUS了。之前一直都是手撸相关功能码,所以也就没了解过类似freeModbus之类的,现在需要使用HAL库开发,且配置Modbus从机协议为全栈,最近趁着空余时间,学习一番。
(网上说好的移植简单快捷,结果照着各种教程配置,磕磕碰碰了小一周才搞定,在此记录下详细教程)
一、下载压缩包
官网下载地址:About - Embedded Experts (embedded-experts.at)
注:下拉页面,然后点击右下角的Downloads,然后点击红框选中,下载;
二、移植准备
解压后,我们会看到几个文件夹,但是对我们当前移植来说,有用的是modbus,以及demo下的BARE,我们新建一个工程文件夹,并在该工程目录下新建文件夹freemodbus,将上面所说的两个文件夹复制到这里,如下图:
图2 freemodbus解压缩后目录
图3 新建工程内的freemodbus文件内容
注:modbus与port文件没有进行内容增删,usModbus文件夹放置的是BARE文件夹下的demo.c,将其改名,并新增usModbus.h文件(为了后期其他项目方便移植使用);
三、新建工程
Modbus协议,我们需要知道的一个关键点是:超时时间;所以,我们需要为其配置一个定时器TIM,以及一个串口USART
定时器配置:
定时器的配置是比较关键的,freeModbus中,计时步长为50us,且当波特率大于19200时,固定超时时间为1750us(35*50us),19200及其以下的波特率则还按照波特率计算3.5字节的超时时间,我使用的定时器的时钟为84M,串口波特率为115200,所以配置如上;
图 超时时间设置来源
串口配置:
串口配置无须多言,选择Asynchronous后,因为使用115200波特率,所以其他的使用默认即可,不需要多余配置;
重点!重点!!重点!!!
不要开启选择的定时器以及串口中断啊,纯纯的都是血泪史,之前就是看教程没有这一步,浪费了好多时间;
到此为此,我们已经完成了工程配置,然后生成工程即可;
四、文件添加
(1)点击魔术棒右边的品字图形,打开Manage Project items,然后添加三个组,FreeModbus,modbus_port,usModbus,三个组下的文件分别来源于工程文件下freemodbus内的modbus,port,usmodbus
添加文件如上,其中usmodbus.c是由demo.c改名而来;
(2)点击魔术棒,然后在C/C++中,将包含了.h文件的路径都包含进去;
五、内容修改
战前准备:【main.h】
方便我们后面修改内容时调用;
1.首先我们修改的是串口配置文件[portserial.c]
这其中只有六个函数,如下:
/* 串口中断使能函数 */
void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable );
/* 串口初始化函数 */
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity );
/* 发送函数:将一个数据推至串口发送缓存 */
BOOL
xMBPortSerialPutByte( CHAR ucByte );
/* 接收函数 */
BOOL
xMBPortSerialGetByte( CHAR * pucByte );
/* 串口中断发送处理函数 */
void prvvUARTTxReadyISR( void );
/* 串口中断接收函数 */
void prvvUARTRxISR( void );
我们首先在开头添加头文件[main.h]
#include "mb.h"
#include "mbport.h"
#include "main.h" /* 添加,方便调用usart函数以及句柄 */
1.1 【vMBPortSerialEnable(BOOL , BOOL)】
这个函数主要是用于使能接收/发送中断,根据形参BOOL可知,仅由TRUE与FALSE两值;
在这里有个关键点,在于发送标志用的是UART_IT_TXE还是UART_IT_TC,特别是Modbus协议是通过RS485去通讯的;
UART_IT_TC:发送数据完成;
UART_IT_TXE:发送寄存器空,但是不代表已经将数据发送出去,所以将RS485的发送/接收使能引脚跳变为接收前,需要添加一个小延迟,否则会导致个别数据包丢失最后一个字节;
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(xRxEnable)
{
RS485_Receive_ENABLE();
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
}
if(xTxEnable)
{
RS485_Transmit_ENABLE();
__HAL_UART_ENABLE_IT(&huart1,UART_IT_TC);
}
else
{
__HAL_UART_DISABLE_IT(&huart1,UART_IT_TC);
}
}
1.2【xMBPortSerialInit】
串口初始化函数,因为我们在cubeMX中配置好工程后,已经自动进行了串口引脚及参数的配置,所以在这里我们不需要多余配置,只需要把NVIC打开即可(很重要);
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
/**
* \note 我们在配置时关闭了中断,是因为我们需要手动的去开始关闭
***/
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
__HAL_UART_DISABLE_IT(&huart1,UART_IT_TC);
return TRUE;
}
1.3【xMBPortSerialPutByte】
将一个字节推至串口发送缓冲中
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. */
USART1->DR = ucByte;
return TRUE;
}
1.4【xMBPortSerialGetByte】
从串口接收缓冲中读出一个字节
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 = (USART1->DR & (uint16_t)0x00ff);
return TRUE;
}
1.5 接收/发送中断处理函数
prvvUARTTxReadyISR(),prvvUARTRxISR()两个函数不需要改动,只需要将[static]关键字屏蔽即可;
/* 函数声明:在文件的开头,记得将static关键字屏蔽 */
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
然后将prvvUARTTxReadyISR(),prvvUARTRxISR()两个函数添加至main.h中;
1.6 中断调用
freeModbus的接收和发送都是在中断中完成的,所以当我们配置好串口的各个函数后,也需要去完成串口中断函数,如下:
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE) != RESET)
{
prvvUARTRxISR();
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
}
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC) != RESET)
{
prvvUARTTxReadyISR();
__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_TC);
}
}
2.接下来我们需要配置的是定时器,即【porttimer.c】文件
熟悉的环节,我们需要了解这个文件中包含了哪些函数
/* 定时器初始化 */
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us );
/* 定时器使能 */
void vMBPortTimersEnable( ); //inline关键字屏蔽
/* 定时器失能 */
void vMBPortTimersDisable( ); //inline关键字屏蔽
/* 超时处理函数 */
void prvvTIMERExpiredISR( void );
然后,在开头将[main.h]头文件包含进去
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "main.h"
2.1定时器初始化【xMBPortTimersInit】
定时器的初始化不需要多余的配置,只需要将中断打开即可,并且返回true。
注:串口和定时器初始化中最重要的,就是配置并打开中断,否则程序将无法正常运行;
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
HAL_NVIC_SetPriority(TIM3_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
return TRUE;
}
2.2定时器使能/失能【vMBPortTimersEnable】
接下来是配置定时器的使能/失能函数,配置完后,记得将inline关键字屏蔽;
/* 定时器使能 */
void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim3,TIM_IT_UPDATE);
__HAL_TIM_SetCounter(&htim3,0);
__HAL_TIM_ENABLE(&htim3);
}
/* 定时器失能 */
void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
__HAL_TIM_ENABLE(&htim3);
__HAL_TIM_SetCounter(&htim3,0);
__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);
}
2.3超时处理函数【prvvTIMERExpiredISR】
超时处理函数是放置在定时器中断函数中,这个函数中我们不需要增删内容,只需要将static关键字屏蔽即可,方便我们后面在中断函数中调用,然后将该函数放置于main.h函数中;
/* 函数声明,放置在程序牵头 */
void prvvTIMERExpiredISR( void );
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
至此,porttimer.c的修改就完成了,接下来就是定时器中断函数配置;
2.4中断调用
在cubemx配置时,已经将定时器配置为1750us(35*50us)了,所以在定时器中断中,我们不需要再去计算时间,当TIM_FLAG_UPDATE标志置位时,我们直接调用prvvTIMERExpiredISR()即可;
void TIM3_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim3,TIM_FLAG_UPDATE) != RESET)
{
prvvTIMERExpiredISR();
__HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);
}
}
3.从机地址修改
freeModbus的函数中,对从机地址进行了一次usRegAddress++;在这里,我们不需要这个机制,所以,crtl+F,在find in files中搜索[usRegAddress++],然后全部屏蔽;
4.Modbus ASCII
这次我们只是需要移植RTU功能,不需要用到Modbus ascii功能,所以直接关闭即可,随便找一个文件,将头文件包含进去,即#include “mbconfig.h”,然后鼠标右键跳转至该文件中,将line 49的MB_ASCII_ENABLED设置为0,
5.发送中断修复
打开[mbrtu.c]文件,然后下拉到line 213,即eMBRTUSend()函数之中,然后插入图中红框内容,插入内容的作用是将一个带发送字节的数据推入串口数据寄存器中,触发串口的发送中断,实现从机对主机指令的响应;
6.点开魔术棒,勾选Use MicroLIB,很重要的一步!!!
7.[demo.c]
打开demo.c文件,删除该文件中的main函数,保留其他函数。小小修改以下方便测试,
起始地址为0x01;
寄存器数量为0x0004;
寄存器保存值为0x01,0x02,0x03,0x04
8.[main.h]
main.h中新增一个头文件
#include "mb.h"
六、测试
至此,我们已经完成了freemodbus移植文件的所有修改,剩下的,即是初始化以及调用了;
我们进入main()函数,在while(1)之前调用freemodbus的初始化函数eMBInit()与使能函数eMBEnable();
/**
* 第一个参数:(eMode)MB_RTU,表示freeModbus初始化模式为Modbus RTU
* 第二个参数:(ucSlaveAddress)0x01,表示从机地址为0x01;
* 第三个参数:(ucPort)0x01,使用串口1;
* 第四个参数:(ulBaudRate)115200,表示波特率为115200;
* 第五个参数:(eParity)MB_PAR_NONE,表示无校验码;
**/
eMBInit(MB_RTU,0X01,0X01,115200,MB_PAR_NONE);
eMBEnable(); /* freeModbus 使能 */
在while(1)中,然后使用eMBPoll()函数轮询;
使用Modbus Poll工具来测试,测试结果如下:
至此,freemodbus的移植以及测试已经完成;