工作主要是传感器相关,常与之打交道的协议,莫过于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工具来测试,测试结果如下:
文章来源:https://www.toymoban.com/news/detail-760965.html
至此,freemodbus的移植以及测试已经完成;文章来源地址https://www.toymoban.com/news/detail-760965.html
到了这里,关于【freeModbus】STM32之HAL库移植笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!