STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)

实验目的

用stm32最小核心板+AHT20模块,完成一个 modbus接口的温湿度Slave设备,能够让上位机PC通过modbus协议获取温湿度。主程序采用多任务框架,比如RT-thread Nano

实验原理

这里的具体 Modbus 原理可以看我前面的博客:

  1. STM32 —— Modbus 协议

这里我们可以直接移植已有的 Modbus 协议 FreeModbus,这里我们会把下载地址放在文末,关于 FreeModbus 的详细介绍可以看 FreeMosbus 官网:FreeMODBUS

官方的 FreeModbus 只到 v1.5,且只支持从机免费,做主机的功能要收费

后来有网友基于这个开发了自己的 v1.6,主从机都免费,这里会在文末给出下载链接

关于读取温度传感器 AHT20 和 RT-Thread 移植可以看我的另外两篇博客:

  1. STM32 —— IIC 读取 ATH20(DTH20)温度传感器

  2. STM32 —— RT-Thread Nano 移植

FreeModbus 协议简介

FreeMODBUS 是一个奥地利人写的 Modbus 协议,它是一个针对嵌入式应用的一个免费(自由)的通用 MODBUS 协议的移植。Modbus 是一个工业制造环境中应用的一个通用协议。Modbus 通信协议栈包括两层:Modbus 应用层协议,该层定义了数据模式和功能;另外一层是网络层

FreeMODBUS 提供了 RTU/ASCII 传输模式及 TCP 协议支持。FreeModbus 遵循 BSD 许可证,这意味着用户可以将 FreeModbus 应用于商业环境中。下面给出命令表:

指令代码 描述 是否支持 备注
Master 主机
Slave 从机
MB_RTU RTU模式
MB_ASCII ASCII模式
MB_TCP TCP模式
0x01 读线圈
0x02 读离散输入
0x03 读保持寄存器
0x04 读输入寄存器
0x05 写单个线圈
0x06 写单个寄存器
0x07 读异常状态
0x08 诊断
0x0B 获取事件计数器
0x0C 获取事件记录
0x0F 写多个线圈
0x10 写多个寄存器
0x11 报告从机ID 协议与文档不一致
0x14 读文件记录
0x15 写文件记录
0x16 屏蔽写寄存器
0x17 读/写多个寄存器
0x18 写FIFO
0x2B 封装接口传输
0x2B/0x0D CANopen参考请求于应答
0x2B/0x0E 读设备身份表示

HAL 库方法

FreeModbus 驱动移植准备阶段

首先要下载 FreeModbus 协议,我们这里要安装的是从机程序,下载连接会放在文末,然后将我们下载好的压缩包解压出来,解压后文件夹内容如下:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第1张图片

Demo 中主要放了不同平台下的移植文件,以 bare 文件夹为例:

BARE
	| - port 
	|    | - portevent.c
	|    | - portserial.c
	|    | - porttimer.c
	| - demo.c
	| - Makefile

这些就是移植层需要我们适配的接口了。其中 event基本不改。主要就是适配串口接口 和 定时器 接口。

然后选择demo.c 里面的

tools:工具

modbus:主要的协议栈实现,实际上不怎么需要修改

doc:文档

然后我们进入到 demo 目录下,新建一个 STM32F103C8T6_Mosbus 用于存储适用于我们的最小系统开发板的 FreeMosbus 驱动文件

将 demo/BARE 目录下的所有文件复制到我们刚才新建的文件夹下,然后再将 demo 目录下的 modbus 文件加复制到我们刚创建的目录下,这样我们就已经准备好了 Modbus 驱动环境:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第2张图片

CubeMX 项目配置

RCC 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第3张图片

USART1 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第4张图片

TIM3 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第5张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第6张图片

I2C 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第7张图片

SYS 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第8张图片

RT-Thread 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第9张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第10张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第11张图片

NVIC 配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第12张图片

时钟配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第13张图片

引脚配置

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第14张图片

引入驱动代码文件并添加头文件路径

首先我们需要将对应的所有驱动文件复制到我们的项目目录下:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第15张图片

这里具体的引入 RT-Thread 和 AHT20 可以看我前面的博客,这里不详细介绍,直接给出引入后的截图:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第16张图片

这里 Modbus 中添加我们 STM32F103C8T6_Mosbus/modbus 中的所有文件,Modbus_Port 中添加我们 STM32F103C8T6_Mosbus/port 中的所有文件

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第17张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第18张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第19张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第20张图片

注意:这里同样需要将我们前面创建好的 STM32F103C8T6_Mosbus 文件夹一同添加到我们的项目中

最后添加头文件目录:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第21张图片

修改 FreeModbus 代码

文末会直接给出修改后的 FreeModbus 用于 STM32F103C8T6 最小系统板上的驱动程序

首先,我们要修改 RT-Thread 和 AHT20 的相关代码,直接看我前面的博客有详细内容,这里就直接使用修改后的代码,不再单独介绍

修改 portserial.c 的代码

修改的代码文件位置:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第22张图片

引入相关头文件

#include "usart.h"

修改 vMBPortSerialEnable 函数,该函数用于串口的接收和发送功能的打开或关闭

修改后代码如下:

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(&huart1,UART_IT_RXNE);	//我用的是串口1,故为&huart1
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
	}
	if (xTxEnable)
	{
		__HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
	}
}

修改 xMBPortSerialInit 函数,该函数对 UART 串口进行初始化,由 eMBRTUInit 函数进行调用,这里改为默认返回 TRUE

修改后代码如下:

BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    return TRUE;
}

修改 xMBPortSerialPutByte 和 xMBPortSerialGetByte 函数,串口发送和接收函数,将STM32串口发送函数进行封装,供协议栈使用

修改后代码如下:

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(&huart1,(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 (&huart1 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )//添加接收一位代码
		return FALSE ;
	else {
		return TRUE;
	}
}

修改 prvvUARTTxReadyISR 和 prvvUARTRxISR 函数,总线状态改为发送后,会在发送缓冲时,自动调用 prvvUARTTxReadyISR() 中断服务程序。prvvUARTTxReadyISR() 只调用了一个函数,就是 pxMBFrameCBTransmitterEmpty ()

数据链路层是最基本的打包部分,将数据打包成帧,送到应用层。在数据链路层协议中,使用中断方式来接受。那么每次接收到字符就自动调用接收字符的 ISR 程序。按照规定,应该将中断服务程序安装给 prvvUARTRxISR(void) 函数。实际上这个函数只调用了一个函数:

pxMBFrameCBByteReceived(),这个指针调用了 xMBRTUReceiveFSM 函数。

将两个函数前的 static 去掉即可,修改后代码如下:

void prvvUARTTxReadyISR( void )
{
    pxMBFrameCBTransmitterEmpty(  );
}

void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

注意:这里需要在声明函数的时候也将对应函数前的 static 去掉

完整 portserial.c 代码如下:

点击查看完整代码
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "usart.h"

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
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(&huart1,UART_IT_RXNE);	//我用的是串口1,故为&huart1
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
	}
	if (xTxEnable)
	{
		__HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);
	}
	else
	{
		__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);
	}
}

BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    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. */
	if(HAL_UART_Transmit(&huart1,(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 (&huart1 ,(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.
 */
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.
 */
void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}

修改 porttimer.c 的代码

引入头文件:

#include "stm32f1xx_hal.h"
#include "tim.h"

修改 xMBPortTimersInit 函数,该函数用于定时器初始化这里改为默认返回 TRUE

修改后代码如下:

BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    return TRUE;
}

修改 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_SET_COUNTER(&htim3,0);
	__HAL_TIM_ENABLE(&htim3);
}

修改 vMBPortTimersDisable 函数,这里会停止定时器,因为好长时间没人发送数据过来

修改后代码如下:

inline void vMBPortTimersDisable(  )	//取消定时器中断
{
    /* Disable any pending timers. */
	__HAL_TIM_DISABLE(&htim3);
	__HAL_TIM_SET_COUNTER(&htim3,0);
	__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);
	__HAL_TIM_CLEAR_IT(&htim3,TIM_IT_UPDATE);
}

最后由于我们需要在 stm32f1xx_it.c 中调用 prvvTIMERExpiredISR 函数,所以要将这里函数和函数定义前的 static 删掉即可

完整 porttimer.c 代码如下:

点击查看完整代码
/*
 * FreeModbus Libary: BARE Port
 * Copyright (C) 2006 Christian Walter 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

#include "stm32f1xx_hal.h"
#include "tim.h"

/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    return TRUE;
}


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_SET_COUNTER(&htim3,0);
	__HAL_TIM_ENABLE(&htim3);
}

inline void vMBPortTimersDisable(  )	//取消定时器中断
{
    /* Disable any pending timers. */
	__HAL_TIM_DISABLE(&htim3);
	__HAL_TIM_SET_COUNTER(&htim3,0);
	__HAL_TIM_DISABLE_IT(&htim3,TIM_IT_UPDATE);
	__HAL_TIM_CLEAR_IT(&htim3,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.
 */
void prvvTIMERExpiredISR( void )
{
    ( void )pxMBPortCBTimerExpired(  );
}

修改 port.h 的代码

引用头文件

#include "stm32f1xx_hal.h"

修改如下位置代码:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第23张图片

修改后代码如下:

#define ENTER_CRITICAL_SECTION( )		__set_PRIMASK(1)	// 关闭总中断
#define EXIT_CRITICAL_SECTION( )		__set_PRIMASK(0)	// 开启总中断

修改 stm32f1xx_it.c 的代码

调用如下全局函数:

// 用于 FreeModbus
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );
用于 DMA 与 I2C 中断设置
extern DMA_HandleTypeDef hdma_i2c1_rx;
extern DMA_HandleTypeDef hdma_i2c1_tx;
extern I2C_HandleTypeDef hi2c1;

修改 USART1_IRQHandler 函数,用于 USART 串口 1 处理中断

修改后代码如下:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)!= RESET) 
	{
		prvvUARTRxISR();//接收中断
	}

	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)!= RESET) 
	{
		prvvUARTTxReadyISR();//发送中断
	}
	
  HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

修改 HardFault_Handler 函数,这个函数用于设置设备中断,这里如果不修改则会直接报错重复定义,所以,这里我们直接修改为 I2C 的中断函数即可

修改后代码如下:

/**
  * @brief This function handles I2C1 event interrupt.
  */
void I2C1_EV_IRQHandler(void)
{
  /* USER CODE BEGIN I2C1_EV_IRQn 0 */

  /* USER CODE END I2C1_EV_IRQn 0 */
  HAL_I2C_EV_IRQHandler(&hi2c1);
  /* USER CODE BEGIN I2C1_EV_IRQn 1 */

  /* USER CODE END I2C1_EV_IRQn 1 */
}

增加 HAL_TIM_PeriodElapsedCallback 函数,用于定时器中断回调函数

函数代码如下:

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( );
}

新增 DMA 与 I2C 中断设置函数

函数代码如下:

/**
  * @brief This function handles DMA1 channel6 global interrupt.
  */
void DMA1_Channel6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel6_IRQn 0 */

  /* USER CODE END DMA1_Channel6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_i2c1_tx);
  /* USER CODE BEGIN DMA1_Channel6_IRQn 1 */

  /* USER CODE END DMA1_Channel6_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel7 global interrupt.
  */
void DMA1_Channel7_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel7_IRQn 0 */

  /* USER CODE END DMA1_Channel7_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_i2c1_rx);
  /* USER CODE BEGIN DMA1_Channel7_IRQn 1 */

  /* USER CODE END DMA1_Channel7_IRQn 1 */
}

stm32f1xx_it.c 完整代码如下:

点击查看完整代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    stm32f1xx_it.c
  * @brief   Interrupt Service Routines.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2022 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);
extern void prvvTIMERExpiredISR( void );
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_i2c1_rx;
extern DMA_HandleTypeDef hdma_i2c1_tx;
extern I2C_HandleTypeDef hi2c1;
extern TIM_HandleTypeDef htim3;
extern UART_HandleTypeDef huart1;
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M3 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
  while (1)
  {
  }
  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles I2C1 event interrupt.
  */
void I2C1_EV_IRQHandler(void)
{
  /* USER CODE BEGIN I2C1_EV_IRQn 0 */

  /* USER CODE END I2C1_EV_IRQn 0 */
  HAL_I2C_EV_IRQHandler(&hi2c1);
  /* USER CODE BEGIN I2C1_EV_IRQn 1 */

  /* USER CODE END I2C1_EV_IRQn 1 */
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Prefetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f1xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles DMA1 channel6 global interrupt.
  */
void DMA1_Channel6_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel6_IRQn 0 */

  /* USER CODE END DMA1_Channel6_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_i2c1_tx);
  /* USER CODE BEGIN DMA1_Channel6_IRQn 1 */

  /* USER CODE END DMA1_Channel6_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel7 global interrupt.
  */
void DMA1_Channel7_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel7_IRQn 0 */

  /* USER CODE END DMA1_Channel7_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_i2c1_rx);
  /* USER CODE BEGIN DMA1_Channel7_IRQn 1 */

  /* USER CODE END DMA1_Channel7_IRQn 1 */
}


/**
  * @brief This function handles TIM3 global interrupt.
  */
void TIM3_IRQHandler(void)
{
  /* USER CODE BEGIN TIM3_IRQn 0 */

  /* USER CODE END TIM3_IRQn 0 */
  HAL_TIM_IRQHandler(&htim3);
  /* USER CODE BEGIN TIM3_IRQn 1 */

  /* USER CODE END TIM3_IRQn 1 */
}

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE)!= RESET) 
	{
		prvvUARTRxISR();//接收中断
	}

	if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TXE)!= RESET) 
	{
		prvvUARTTxReadyISR();//发送中断
	}
	
  HAL_NVIC_ClearPendingIRQ(USART1_IRQn);
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

/* 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( );
}


/* USER CODE END 1 */

修改 demo.c 文件

首先我们需要注释掉,demo.c 中的 main 函数,不然在编译的时候会报错重复定义

修改如下代码:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第24张图片

修改后的代码如下:

static USHORT   usRegInputStart = REG_INPUT_START;
//static USHORT   usRegInputBuf[REG_INPUT_NREGS];

uint16_t   usRegInputBuf[REG_INPUT_NREGS];
uint16_t InputBuff[5];
//

修改 eMBRegInputCB 函数,用于读取输入寄存器,对应功能码是 04 eMBFuncReadInputRegister

修改后代码如下:

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>8;
			pucRegBuffer++;
			*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
			pucRegBuffer++;
		}
	}
	else
	{
		eStatus = MB_ENOREG;
	}

	return eStatus;
}

完整 demo.c 代码如下:

点击查看完整代码
/*
 * FreeModbus Libary: BARE Demo Application
 * Copyright (C) 2006 Christian Walter 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * File: $Id$
 */

/* ----------------------- 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 USHORT   usRegInputBuf[REG_INPUT_NREGS];

uint16_t   usRegInputBuf[REG_INPUT_NREGS];
uint16_t InputBuff[5];


/* ----------------------- Start implementation -----------------------------*/
/*int main( void )
{
    eMBErrorCode    eStatus;

    eStatus = eMBInit( MB_RTU, 0x0A, 0, 38400, MB_PAR_EVEN );

    eStatus = eMBEnable(  );

    for( ;; )
    {
        ( void )eMBPoll(  );

        usRegInputBuf[0]++;
    }
}*/

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>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;
}

代码设计

这里需要按照我们前面几篇博客介绍的方法,对 RT-Thread 和 ATH20 的驱动代码进行修改,这里不详细介绍,直接进行 Modbus 的代码设计

由于我们是要通过 Modbus 协议获取温度并发送给上位机,所以这里我们将温度获取函数写在 Modbus 的 demo.c 中

引入头文件并设置获取温度所需要的参数:

//下面的定义的数据用于获取温湿度
#include “AHT20.h”
uint32_t CT_data[2]={0,0};
volatile int  c1,t1,c2,t2;

eMBRegInputCB 函数就是用于输出的函数,所以我们直接在这个函数中获取温度并输出,InputBuff[] 数组中存放的就是我们要输出的内容,所以我们直接将我们获取到的温度数据存储在这个数组中即可

修改后的函数如下:

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
	eMBErrorCode eStatus = MB_ENOERR;
	int iRegIndex;
	int i;
	
	HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);	// 使用 PC13 引脚上的板载小灯泡进行测试——小灯泡亮
	AHT20_Read_CTdata_crc(CT_data);       //经过CRC校验,读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
	c1 = CT_data[0]*1000/1024/1024;  //计算得到湿度值c1(放大了10倍)
	t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)
	t2 = t1/10 + (t1/10)%10;	//温度的整数部分
	t3 = t1%10;	//温度的小数部分
	c2 = c2/10 + (c1/10)%10;	// 湿度的整数部分
	c3 = c3%10;	//湿度的小数部分
	
	InputBuff[0] = t2;
	InputBuff[1] = t3;
	InputBuff[2] = c2;
	InputBuff[3] = c3;
	
	if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
	{
		iRegIndex = ( int )( usAddress - usRegInputStart );
		for(i=0;i>8;
			pucRegBuffer++;
			*pucRegBuffer=InputBuff[i+usAddress-1]&0xff;
			pucRegBuffer++;
		}
	}
	else
	{
		eStatus = MB_ENOREG;
	}

	return eStatus;
}

然后我们在 RT-Thread 中进行任务创建,这里我们创建两个任务,一个是点亮板载灯泡,用来测试程序是否正常运行,另一个就是获取温湿度,我们将获取温湿度作为主进程,app_rt_thread.c 代码如下:

#include "rtthread.h"
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#include "AHT20.h"
#include "mb.h"
#include "mbport.h"

struct rt_thread led1_thread;
rt_uint8_t rt_led1_thread_stack[128];
void led1_task_entry(void *parameter);

//初始化线程函数
void MX_RT_Thread_Init(void)
{
	//初始化LED1线程
	rt_thread_init(&led1_thread,"led1",led1_task_entry,RT_NULL,&rt_led1_thread_stack[0],sizeof(rt_led1_thread_stack),3,20);
	//开启线程调度
	rt_thread_startup(&led1_thread);
}
 
//主任务
void MX_RT_Thread_Process(void)
{
	(void)eMBPoll();	//启动modbus监听
}
 
//LED1任务
void led1_task_entry(void *parameter)
{
	while(1)
	{
		HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_RESET);
		rt_thread_delay(500);
		HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13, GPIO_PIN_SET);
		rt_thread_delay(500);
	}
}

注意:这里一定要增大我们的 IRAM,并且需要重载为 STM32F103C8 的 MAP 才能够保证程序的栈空间充足,确保程序正常运行

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第25张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第26张图片

在 main.c 源文件中引入以下头文件和全局函数:

#include "rtthread.h"	// RT-Thread 头文件
#include "mb.h"	// Modbus 头文件
#include "mbport.h"	// Modbus 端口头文件
#include "AHT20.h"	// AHT20传感器驱动头文件
extern void MX_RT_Thread_Init(void);	// RT-Thread 初始化函数,初始化并执行各种进程
extern void MX_RT_Thread_Process(void);	// RT-Thread 主进程

然后在主函数中我们要对 AHT20 模块进行初始化,并对 Modbus 协议进行使能,并初始化 RT-Thread 进程,main 函数完整代码如下:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2C1_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	AHT20_Init();	//初始化温度传感器并进行延时确保数据准确性
	HAL_Delay(2000);
	eMBInit( MB_RTU, 0x01, 1, 115200, MB_PAR_NONE);//初始化modbus,走modbusRTU,从站地址为0x01,端口为1。
	eMBEnable();//使能modbus
	MX_RT_Thread_Init();	//初始化 RT-Thread 进程
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		
		//( void )eMBPoll(  );//启动modbus侦听,由于在 RT-Thread 主进程中进行了监听启动,所以这里直接调用 RT-Thread 主进程
		
		MX_RT_Thread_Process();	//调用主进程
		
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

到此我们的程序设计全部完成

运行测试

接线示例

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第27张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第28张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第29张图片

运行结果

这里使用 ModbusPoll 进行接收数据

打开 modbuspoll,点击 setup 第一个读写定义,并设置参数如下:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第30张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第31张图片

点击 connection 的 connect ,进入连接设置,选项如图,我使用的是 usb 转 ttl,用的是 com 端口,我连接的口是 com3,所以选择的是 port3:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第32张图片
STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第33张图片

最后成功获取到参数:

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第34张图片

结果分析

STM32 —— FreeModbus 从机移植,基于 Modbus 协议读取从机温度(AHT20)_第35张图片

这里已经成功获取到指定参数:

数据 0 是温度参数的整数部分

数据 1 是温度参数的小数部分

数据 0 是湿度参数的整数部分

数据 1 是湿度参数的小数部分

这里温度为 26.6,湿度为 30.1

错误解决方法

报错如下:

如果我们添加了驱动代码文件之后,没有修改代码就直接编译会出现很多报错

解决方案:

这并没有什么问题,不要急,只需要按照上面讲述的步骤进行修改代码即可解决所有问题

报错如下:

Modbus_CubeMX\Modbus_CubeMX.axf: Error: L6200E: Symbol HardFault_Handler multiply defined (by context_rvds.o and stm32f1xx_it.o).

解决方案:

这里是说函数 HardFault_Handler 重复定义,前面我们已经介绍了这里的修改方法,参考前面的方法修改即可

参考文档

  1. FreeModbus开源协议栈的移植和详解(二)- modbus主流程分析

  2. FreeModbus开源协议栈的移植和详解(三)- RTU协议代码分析

  3. FreeModbus开源协议栈的移植和详解(四)- FreeModbus在STM32上的移植

  4. FreeModbus开源协议栈的(五)野火指南者+Keil+FreeModbus 的Modbus RTU从站

  5. FreeModbus开源协议栈的(六)FreeModbus状态机和事件总结

  6. FreeModbus在STM32上移植

  7. FreeModbus移植总结

  8. 【stm32】FreeModbus 介绍 + 移植stm32f103 HAl库

  9. Modbus RTU协议各知识点入门 + 实例

10 FreeModbus源码分析

文件下载

  1. FreeModbus 下载地址

官方 Github 仓库:FreeModbus v1.5 Github仓库

如果上述 Github 仓库地址无法打开,可以尝试以下连接:FreeModbus v1.5

  1. 网友自制 FreeModbus

Github仓库下载地址:FreeModbus v1.5

如果上述 Github 仓库地址无法打开,可以尝试以下连接:FreeModbus v1.5

  1. 修改后完全适用于 STM32F103C8T6 最小系统板的 FreeModbus 协议代码:

Github仓库下载地址:ppqppl-FreeModbus v1.5

如果上述 Github 仓库地址无法打开,可以尝试以下连接:ppqppl-FreeModbus v1.5

  1. Modbus 测试工具

ModbusPoll7.0.1/ModbusSlave6.1.3

你可能感兴趣的:(stm32,单片机,嵌入式硬件)