STM32 HAL库移植笔记【freeModbus】

        工作主要是传感器相关,常与之打交道的协议,莫过于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的移植以及测试已经完成;

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库移植笔记【freeModbus】

发表评论