freemodbus官网
FreeMODBUS 是针对通用的Modbus协议栈在嵌入式系统中应用的一个实现。Modbus协议是一个在工业制造领域中得到广泛应用的一个网络协议。
一个Modbus通信协议栈包括两层:定义了数据结构和功能Modbus应用协议和网络层。
在FreeMODBUS的当前版本中,提供了Modbus Application Protocol v1.1a 的实现并且支持在Modbus over serial line specification 1.0中定义的RTU/ASCII传输模式。从0.7版本开始,FreeModbus也支持在TCP defined in Modbus Messaging on TCP/IP Implementation Guide v1.0a中定义的TCP传输。Freemodbus遵循BSD ,这意味着本协议栈的实现代码可以应用于商业用途。
目前版本的FreeModbus支持如下的功能码:
freemobus的API说明
freemobus github
源码下载页面
下载freemodbus 源码
在上面的链接下,可以下载1.6版本的源代码,此版本包括Modbus RTU / ASCII,Modbus TCP,输入/保持寄存器访问,离散寄存器等。此外,它还被移植到以下平台:FreeRTOS / Cortex M3 SAM3S,FreeRTOS / ARM STR71X,FreeRTOS / ARM AT91SAM7X,lwIP / PPP / STR71X,飞思卡尔MCF5235,lwIP / MCF5235,Atmel AVR ATMega168,TI-MSP430,Win32和Linux操作系统。(居然没有STM32平台?)
移植需要的文件再demo和modbus文件夹中
demo文件夹中的内容☟
我们需要BARE文件夹里的文件
之后是modbus文件夹,里面有modbus功能的源代码
新建stm32F4的工程
因为我使用的是正点原子阿波罗的开发板,我用cubeMX软件进行初始化配置,芯片选择stm32f429IGT6
时钟,GPIO,调试端口这些按需要进行配置,这里freemodbus需要的外设有1个TIMER和1个UART。(可以看出来,freeModbus需要的外设资源就是这么少)串口是用来承载modbus协议的物理层端口,定时器是用来产生T3.5的中断的,freemodbus是根据这个超时中断来判断一帧数据接收完成的。
TIMER6的配置,这里预分频器和自动装载寄存器的值可以先随便填一个,因为后续我们在freemodbus的移植文件里还要重新对TIMER进行初始化。
uart1的配置。这里也是可以先随便填(停止位要配置好),后续在移植文件里还要重新对UART1进行初始化。
之后记得使能两个外设的中断,这里优先级的设置要提一下,查阅了很多文档,都没有说明这两个外设(串口和定时器)优先级的配置,我在开始配置的时候认为定时器的优先权要高一些,也许freemodbus要使用这个定时器定时查询什么状态。但是在之后移植完成后,我仔细阅读了源代码,认为应该把串口的优先级调高,优先保证将数据从串口中取出,定时器中断的作用就是post一个信号量,使得eMBPoll() 可以 get这个信号量,这个post的动作适当推迟一些应该也没什么问题,所以定时器的中断优先级就可以比中断优先级低。(图中是我一开始配置的优先级,之后我在程序里将这两个外设的优先级对调了一下?)
时钟配置按需配置就行,记得TIMER6用的PCLK频率是多少,这个需要我们来计算分频系数的。
配置完成就可以generate code了,我用的最新的F4的HAL库(V1.24.1)
添加*.c *.h文件
上面是keil的工程目录结构
sys.c和usart.c是我从正点原子代码工程里拷贝来的,里面有些比较好用的宏定义和函数API
注意sys.h中需要修改1个宏
#define SYSTEM_SUPPORT_OS 0 //系统文件夹是否支持OS,1:支持 0:不支持
然后就是把usart.c中和其他文件中冲突的代码都注释掉,因为这个文件里我们只用如下功能
//重定义fput()
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送
USART1->DR = (u8) ch;
return ch;
}
#define ENTER_CRITICAL_SECTION( ) __disable_irq()
#define EXIT_CRITICAL_SECTION( ) __enable_irq()
补充完整这两个宏定义
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "sys.h"
extern UART_HandleTypeDef huart1;
/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );
添加sys.h头文件,去掉*prvvUARTTxReadyISR( void )*和 prvvUARTRxISR( void ) 的static属性,这是方便在ISR中调用这两个函数。
添加huart1声明,目的是在这个文件中使用这个句柄。
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(xRxEnable == TRUE)
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
else
__HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);
if(xTxEnable == TRUE)
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
else
__HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
}
完成串口使能函数体,接收时使用RXNE中断,发送时使用TXE中断
这两个中断是在RM0090中USART状态寄存器 (USART_SR)定义的
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
HAL_UART_DeInit(&huart1);//DEINIT cubeMX中的初始化配置
(void)ucPORT;
huart1.Instance = USART1;
huart1.Init.BaudRate = ulBaudRate;
huart1.Init.StopBits = UART_STOPBITS_1;
switch (eParity)//使用校验位,就需要将uart的数据位配置为9位
{
case MB_PAR_ODD:
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.Parity = UART_PARITY_ODD;
break;
case MB_PAR_EVEN:
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.Parity = UART_PARITY_EVEN;
break;
default:
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.Parity = UART_PARITY_NONE;
break;
}
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
重新对串口1进行初始化,重新配置波特率和校验位,至于数据位,freemodbus中固定为8位,停止位按需配置,端口号暂时我没有用到。
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,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
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,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
发送和接收1个字节,调用的HAL库函数。
/* 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( );
}
这两个函数在串口1ISR中会调用,在这里只需要删掉函数前的static即可
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- static functions ---------------------------------*/
void prvvTIMERExpiredISR( void );
extern TIM_HandleTypeDef htim6;
/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
HAL_TIM_Base_DeInit(&htim6);
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM6_Init 1 */
/* USER CODE END TIM6_Init 1 */
htim6.Instance = TIM6;
htim6.Init.Prescaler = 4499;//50us分频,这里使用的timer PCLK频率是90Mhz
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = usTim1Timerout50us-1;//modbus 规定的TIMEOUT时间
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
return FALSE;
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
return FALSE;
}
return TRUE;
}
定时器的初始化。这里是被eMBInit进行调用,调用时usTim1Timerout50us参数是用来计算50us的倍数的,这个值在eMBRtuInit()中根据波特率进行计算。
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_CLEAR_IT(&htim6,TIM_IT_UPDATE);
__HAL_TIM_SetCounter(&htim6,0);//这里一定要清零计数器
HAL_TIM_Base_Start_IT(&htim6);
}
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
HAL_TIM_Base_Stop_IT(&htim6);
__HAL_TIM_SetCounter(&htim6,0);
__HAL_TIM_CLEAR_IT(&htim6,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( );
}
会在TIMER6的ISR中调用,去掉前面static即可
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
uint8_t tmp;
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_PE))//奇偶校验位判断
{
HAL_UART_Receive(&huart1,&tmp,1,1);
}
else if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_RXNE))
{
prvvUARTRxISR();
}
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)&&__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_TXE))
{
prvvUARTTxReadyISR();
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
*/
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
prvvTIMERExpiredISR();
/* USER CODE END TIM6_DAC_IRQn 1 */
}
修改两个外设的ISR,在UART的ISR中加入了奇偶校验的判断
#include "mb.h"
#include "sys.h"
//添加头文件
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 0x0001U //寻址地址是从1开始的
#define REG_INPUT_NREGS 4
#define REG_HOLDING_START ( 1 )
#define REG_HOLDING_NREGS ( 32 )
/* ----------------------- Static variables ---------------------------------*/
static uint16_t usRegInputStart = REG_INPUT_START;
static uint16_t usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//为了验证使用的初始化值
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
添加输入寄存器和保持寄存器的起始地址,定义寄存器大小,定义数组
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM6_Init();
__HAL_TIM_CLEAR_FLAG(&htim6,TIM_FLAG_UPDATE);//是HAL库的一个BUG,初始化完成后会置位UIF
eMBInit(MB_RTU, 0x01, 1, 9600, MB_PAR_ODD);//Modbus初始化
eMBEnable();//使能协议栈
/* Infinite loop */
while (1)
{
eMBPoll();
PBout(0) = !PBout(0);//这是我点亮LED的语句
PBout(1) = !PBout(1);
HAL_Delay(100);//轮询时间为100ms
}
}
void __aeabi_assert(const char * x1, const char * x2, int x3)
{
}
freemodbus中使用了断言函数,这里添加函数体解决断言函数找不到的问题
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++ =
( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
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:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
在这里实现了输入寄存器和保持寄存器的相关功能函数。