Clion开发STM32——移植FreeModbus
STM32型号 :STM32H743VIT6
FreeModbus版本 :1.6
使用工具:stm32cubeMX,Clion
使用STM32作从机,模式:RTU
网上用keil的比较多,用Clion的比较少,如果你也用Clion,那么希望本文可以给你提供些许参考。
1 下载官网源码
官网地址:https://www.embedded-experts.at/en/freemodbus/about/
demo是移植例程,但是里面没有stm32的。
modbus是源码.
看一下modbus文件,我这里使用的模式是RTU模式,所以ascii和tcp文件夹就不需要了,如果你也只用RTU模式,那么只需要标红的文件夹。
在freemodbus-v1.6\demo\BARE\port这个文件夹中的文件全部需要,这些是接口文件。
把上面的文件放在一个文件夹里。
在自己的工程目录下新建FreeModbus(自己起名就好),并添加上面的文件,如下:
2 修改CMakeLists_template
可以参考这篇文章Clion开发STM32——添加自己文件
增加头文件路径
增加编译文件
只关注蓝色框框起来的即可,其他文件可以忽略,那是我这个工程的其他文件。
3 cubeMX配置串口和定时器
3.1串口
3.2 定时器
3.3 NVIC
中断回调函数比较繁琐,所以我这里取消掉了,自己写中断内的内容。
4 修改接口 port
4.1 port.h
首先是port.h文件
#ifndef _PORT_H
#define _PORT_H
#include <assert.h>
#include <inttypes.h>
/* ----------------------- Platform includes --------------------------------*/
#include "stm32h7xx_hal.h"
#include "main.h"
/* ----------------------- Defines ---------*/
#defineINLINE inline
#define PR_BEGIN_EXTERN_C extern "C" {
#definePR_END_EXTERN_C }
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1)//禁止中断
#define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0)//允许中断
typedef uint8_t BOOL;
typedef unsigned char UCHAR;
typedef char CHAR;
typedef uint16_t USHORT;
typedef int16_t SHORT;
typedef uint32_t ULONG;
typedef int32_t LONG;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#endif
包含平台,定义进出临界区
4.2 portserial.c
这个文件是留给用户写串口函数接口的。
使能串口中断(串口接收中断,串口发送中断)
这里串口发送中断,可以选择两种
1 发送寄存器空中断 UART_IT_TXE
2 发送完成中断 UART_IT_TC
两种都可以 ,只是UART_IT_TC不会主动触发中断,必须得发送完成,发送完成是只数据被写入到发送寄存器,然后串口会把数据送到移位寄存器,等到移位寄存器发送完成,才会置发送完成中断。所以后面发送函数里,得收到加一个发送函数来触发,才能进中断。
而UART_IT_TXE使能后就可以进入中断,因为发送寄存器本就空的。往发送寄存器里写数据之后,数据也会被送到移位寄存器。
我这里使用的事发生寄存器空中断UART_IT_TXE。
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );//被UART发送空中断调用的函数,注意是空中断,通知modbus数据可以发送
void prvvUARTRxISR( void );//被UART接收中断调用的函数,通知modbus有数据到来
/* ----------------------- Start implementation -----------------------------*/
/*使能串口中断*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable) {
/*设置485为接收模式*/
__HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);//使能接收中断
} else {
__HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);
}
if (xTxEnable) {
/*设置485为发送模式*/
__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 )
{
/*MX_USART2_UART_Init*/
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. */
huart2.Instance->TDR = ucByte;
//HAL_UART_Transmit(&huart2, (uint8_t*)&ucByte, 1, 0);
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 = (uint8_t)(huart2.Instance->RDR & (uint8_t)0x00FF);
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( );
}
这两个函数需要取消static定义,这两个函数要在中断里被调用,而static就限制了它只能在本文件中使用,所以取消掉。(当然你把中断函数写在这个文件也可以,我个人习惯把中断都放在stm32h7xx_it.c文件中)
4.3 portimer.c
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );//被定时器溢出中断调用的函数,通知modbus3.5个字符等待时间到
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim7.Instance = TIM7;
htim7.Init.Prescaler = 11999;
htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
htim7.Init.Period = usTim1Timerout50us - 1;
htim7.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim7) != HAL_OK)
{
return FALSE;
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
/*使能定时器*/
inline void
vMBPortTimersEnable( )
{
__HAL_TIM_CLEAR_IT(&htim7,TIM_IT_UPDATE);
__HAL_TIM_SET_COUNTER(&htim7,0);
HAL_TIM_Base_Start_IT(&htim7);
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
}
/*禁止定时器*/
inline void
vMBPortTimersDisable( )
{
HAL_TIM_Base_Stop_IT(&htim7);
__HAL_TIM_SET_COUNTER(&htim7,0);
__HAL_TIM_CLEAR_IT(&htim7,TIM_IT_UPDATE);
/* Disable any pending timers. */
}
/* 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( );
}
这个函数同样取消static,它被定时器溢出中断调用
关于定时器参数为什么要这么设置,可以看一下FreeModbus学习——eMBInit初始化
4.4 添加port.c文件
这个文件中存放的都是功能码处理函数调用的回调函数,当然你也可以放在别的文件,比如main.c里都可以。
这个文件中的函数怎么调用的,可以看一下FreeModbus学习——读输入寄存器eMBFuncReadInputRegister
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
//输入寄存器
#define REG_INPUT_START 3000
#define REG_INPUT_NREGS 4
//保持寄存器
#define REG_HOLD_START 4000
#define REG_HOLD_NREGS 10
//线圈
#define REG_COILS_START 0
#define REG_COILS_NREGS 4
//开关寄存器
#define REG_DISCRETE_START 1000
#define REG_DISCRETE_NREGS 4
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS] = {0x01, 0x02, 0x03, 0x04};
static USHORT usRegHoldStart = REG_HOLD_START;
static USHORT usRegHoldBuf[REG_HOLD_NREGS];
static USHORT usRegCoilsStart = REG_COILS_START;
static uint8_t usRegCoilsBuf[REG_COILS_NREGS];
static USHORT usRegDiscreteStart = REG_DISCRETE_START;
static uint8_t usRegDiscreteBuf[REG_DISCRETE_NREGS];
/****************************************************************************
* 名 称: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;//寄存器数组索引
usAddress = usAddress - 1;//传进来的地址+1了,这里要减1
//判断地址是否在输入寄存器范围内
if( ( usAddress >= REG_INPUT_START ) && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );//地址 - 开始地址 = 索引
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );//寄存器值高位
*pucRegBuffer++ = ( unsigned char )( 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;
usAddress = usAddress - 1;
if((usAddress >= REG_HOLD_START) && ((usAddress+usNRegs) <= (REG_HOLD_START + REG_HOLD_NREGS)))
{
iRegIndex = (int)(usAddress - usRegHoldStart);
switch(eMode)
{
case MB_REG_READ://读寄存器
while(usNRegs > 0)
{
*pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] >> 8);
*pucRegBuffer++ = (uint8_t)(usRegHoldBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE://写寄存器
while(usNRegs > 0)
{
usRegHoldBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else//错误
{
eStatus = MB_ENOREG;
}
return eStatus;
}
/****************************************************************************
* 名 称: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;
USHORT iRegIndex;
USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
UCHAR ucStatus = 0;
UCHAR ucBits = 0;
UCHAR ucDisp = 0;
usAddress = usAddress - 1;
if((usAddress >= REG_COILS_START) &&((usAddress + usNCoils) <= (REG_COILS_START + REG_COILS_NREGS)))
{
iRegIndex = (int)(usAddress - usRegCoilsStart);
switch(eMode)
{
case MB_REG_READ://读线圈
while(usCoilGroups--)
{
ucDisp = 0;
ucBits = 8;
while((usNCoils--) != 0 && (ucBits--) != 0)
{
ucStatus |= (usRegCoilsBuf[iRegIndex++] << (ucDisp++));
}
*pucRegBuffer++ = ucStatus;
}
break;
case MB_REG_WRITE://写线圈
while(usCoilGroups--)
{
ucStatus = *pucRegBuffer++;
ucBits = 8;
while((usNCoils--) != 0 && (ucBits--) != 0)
{
usRegCoilsBuf[iRegIndex++] = ucStatus & 0X01;
ucStatus >>= 1;
}
}
}
}
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;
USHORT iRegIndex;
USHORT usDiscreteGroups = ((usNDiscrete - 1) / 8 + 1);
UCHAR ucStatus = 0;
UCHAR ucBits = 0;
UCHAR ucDisp = 0;
usAddress = usAddress - 1;
if((usAddress >= REG_DISCRETE_START) &&((usAddress + usNDiscrete) <= (REG_DISCRETE_START + REG_DISCRETE_NREGS)))
{
iRegIndex = (int)(usAddress - usRegDiscreteStart);
while(usDiscreteGroups--)
{
ucDisp = 0;
ucBits = 8;
while((usNDiscrete--) != 0 && (ucBits--) != 0)
{
if(usRegDiscreteBuf[iRegIndex])
{
ucStatus |= (1 << ucDisp);
}
ucDisp++;
}
*pucRegBuffer++ = ucStatus;
}
}
else//错误
{
eStatus = MB_ENOREG;
}
return eStatus;
}
4.5 写中断服务函数
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
if((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE) != RESET)
&& (__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE)!= RESET)) {
__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_RXNE);
prvvUARTRxISR();
return;
}
if ((__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE)!= RESET)
&& (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE)!= RESET)){
__HAL_UART_CLEAR_FLAG(&huart2, UART_FLAG_TXE);
prvvUARTTxReadyISR();
return;
}
}
void TIM7_IRQHandler(void)
{
/* USER CODE BEGIN TIM7_IRQn 0 */
if(__HAL_TIM_GET_FLAG(&htim7, TIM_FLAG_UPDATE) != RESET
&& __HAL_TIM_GET_IT_SOURCE(&htim7, TIM_IT_UPDATE) !=RESET) {
__HAL_TIM_CLEAR_FLAG(&htim7, TIM_FLAG_UPDATE);
prvvTIMERExpiredISR();
}
}
5 调用协议栈
依次调用
eMBInit( MB_RTU, 1, 3, 9600, MB_PAR_NONE );
eMBEnable();
然后把eMBPoll();放在循环里
6 测试
以读输入寄存器为例
功能码04
使用软件Modbus Poll
软件配置:
效果:
OK !
移植结束。
想深入了解FreeModbus源码,可以看我写的其笔记FreeModbus,写的不好,对源码理解上以及写的时候,难免有瑕疵纰漏,如有错误还请大佬指出。您原谅着瞧,原谅着看。
原文地址:https://blog.csdn.net/rerrick_rose/article/details/140543937
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!