環境
硬體
NUCLEO-F030R8,芯片為 STM32F030R8,該板子 RAM 為 8KB,FLASH 為 64KB,主頻最高為48MHz,
軟體
IAR EWARM 8.22.1 + Stm32CubeMX 6.3 + HAL 1.11.3 + freeModbus 最新版
前言
本來是想用 libModbus 3.1.6,因為我的主站用的是這個,但是嘗試移植到裸奔系統的時候才發現,libModbus 3.1.6 是在 Linux 下使用的,所以只好放棄,重新回到 freeModbus,
freeModbus 移植需求
freeModbus 是一個很小開源的 Modbus 協議堆疊,支持 ascii,rtu 和 tcp 模式,移植 freeModbus 需要硬體支持包括如下:
1、一個串口,
2、一個定時器,用于產生 3.5T 時間中斷,
3、Modbus 相關的回呼函式,
使用 CubeMX 生產基本代碼
時鐘
NUCLEO-F030R8 板子沒有晶振,最高的時鐘頻率為
48
48
48MHz,我這里配置為
40
40
40MHz,其實這個頻率可以任意配置,沒有什么特別的要求,

串口
我是用了 usart1,波特率什么都可以隨便設定,因為在一直 freeModbus 的時候都會重新配置,只需要硬體使能串口,并打開中斷即可,下面是 CubeMX 的配置截圖,

下圖為串口中斷配置,

主要目的是讓 CubeMX 自動生成對應的代碼,注意串口中斷的優先級設定為最高,
定時器
任意選擇一個 Timer 都可以,這里我選擇了 TIM6,沒有什么特別原因,就是它簡單,配置如圖,

也是隨便配置一下,同樣,在移植 freeModbus 的時候,會將 TIM6 重新配置的,
下圖是 TIM6 中斷配置,

同樣注意 TIM6 的優先級設定為
2
2
2,比 USART1 的優先級低,
生成代碼
這樣配置完成后,生成代碼即可,
freeModbus 移植
拷貝代碼
我遵守 CubeMX 的方式,也就是增加了 Middlewares 目錄,然后將 freeModbus-master 壓縮包中的 modbus 目錄拷貝過來即可,對應的目錄結構和檔案如下圖,

port 目錄
根據 freeModbus 官方檔案定義,移植相關代碼建議放在 port 目錄下,建議參考 freeModbus-master/demo/bare 目錄,
在該目錄下有:檔案 demo.c,該檔案告訴你 main() 函式應該寫什么,modbus 對應的回呼函式應該如何實作,子目錄 port 下有
4
4
4 個檔案:
port.h
portevent.c 事件相關的移植檔案
portserial.c 串口相關的移植檔案
porttimer.c 定時器相關的移植檔案
我就是將 bare 目錄的 port 直接拷貝過來,具體的目錄結構參考上圖,
port.h
該檔案不需要什么改動,只需要增加對應的板子頭檔案即可,我增加了如下代碼:
#include "stm32f0xx_hal.h"
portevent.c
這個檔案我沒有做任何修改,
portserial.c
vMBPortSerialEnable
該函式為串口使能函式,根據自己的板子對串口中斷進行使能即可,
我的代碼如下:
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);
}
xMBPortSerialInit
該函式為串口初始化函式,根據自己的板子對串口初始化即可,
我的代碼如下:
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、我是用的是 USART1,所以對應為 huart1,參考 usart.c,
2、由于 CubeMX 會對串口進行初始化,所以要先 DeInit,
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. */
if(HAL_UART_Transmit(&huart1,(uint8_t*)&ucByte,1,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
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.
*/
if(HAL_UART_Receive(&huart1,(uint8_t*)pucByte,1,1) == HAL_OK)
return TRUE;
else
return FALSE;
}
prvvUARTTxReadyISR
Tx Ready ISR,
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
注意:原來這個函式是一個靜態函式,需要將 static 洗掉,我在 USART1 中斷中呼叫了本函式,
prvvUARTRxISR
Rx ISR,
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
注意:原來這個函式是一個靜態函式,需要將 static 洗掉,我在 USART1 中斷中呼叫了本函式,
porttimmer.c
xMBPortTimersInit
定時器初始化函式,
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頻率是40Mhz
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;
}
感覺這個定時器不需要特別的準確,隨便給一個時間就可以了,
vMBPortTimersEnable
使能定時器中斷,
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);
}
vMBPortTimersDisable
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);
}
prvvTIMERExpiredISR
定時器結束中斷,
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
注意:原來這個函式是一個靜態函式,需要將 static 洗掉,我在 TIM 中斷中呼叫了本函式,
中斷函式
道歉這個部分忘記加了,節后補上,
回呼函式
由于 freeModbus 采用了回呼函式的方法來實作具體的功能,所以我們必須實作對應的協議回呼函式,
這部分代碼的實作,可以參考 freeModbus-master/demo 目錄中任意一個實作,
頭檔案
必須包含 mb.h,
#include "mb.h"
初始化
主要是定義開始地址,資料快取區之類,
#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];
eMBRegInputCB
Read Input Register,回應功能碼 0x04,
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;
}
eMBRegHoldingCB
Write Holding Register,回應功能碼 0x06,
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;
}
eMBRegCoilsCB
Read Coils,回應功能碼 0x01,
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
也就是說我暫時沒有實作,
eMBRegDiscreteCB
Read Discrete Inputs,回應功能碼 0x02,
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
其他相關功能碼
其參考 mb.c,代碼如下:
static xMBFunctionHandler xFuncHandlers[MB_FUNC_HANDLERS_MAX] = {
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED > 0
{MB_FUNC_OTHER_REPORT_SLAVEID, eMBFuncReportSlaveID},
#endif
#if MB_FUNC_READ_INPUT_ENABLED > 0
{MB_FUNC_READ_INPUT_REGISTER, eMBFuncReadInputRegister},
#endif
#if MB_FUNC_READ_HOLDING_ENABLED > 0
{MB_FUNC_READ_HOLDING_REGISTER, eMBFuncReadHoldingRegister},
#endif
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_REGISTERS, eMBFuncWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_WRITE_HOLDING_ENABLED > 0
{MB_FUNC_WRITE_REGISTER, eMBFuncWriteHoldingRegister},
#endif
#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0
{MB_FUNC_READWRITE_MULTIPLE_REGISTERS, eMBFuncReadWriteMultipleHoldingRegister},
#endif
#if MB_FUNC_READ_COILS_ENABLED > 0
{MB_FUNC_READ_COILS, eMBFuncReadCoils},
#endif
#if MB_FUNC_WRITE_COIL_ENABLED > 0
{MB_FUNC_WRITE_SINGLE_COIL, eMBFuncWriteCoil},
#endif
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0
{MB_FUNC_WRITE_MULTIPLE_COILS, eMBFuncWriteMultipleCoils},
#endif
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED > 0
{MB_FUNC_READ_DISCRETE_INPUTS, eMBFuncReadDiscreteInputs},
#endif
};
freeModbus 支持的功能碼
參考 mbproto.h,支持的功能碼如下:
#define MB_FUNC_READ_COILS ( 1 )
#define MB_FUNC_READ_DISCRETE_INPUTS ( 2 )
#define MB_FUNC_WRITE_SINGLE_COIL ( 5 )
#define MB_FUNC_WRITE_MULTIPLE_COILS ( 15 )
#define MB_FUNC_READ_HOLDING_REGISTER ( 3 )
#define MB_FUNC_READ_INPUT_REGISTER ( 4 )
#define MB_FUNC_WRITE_REGISTER ( 6 )
#define MB_FUNC_WRITE_MULTIPLE_REGISTERS ( 16 )
#define MB_FUNC_READWRITE_MULTIPLE_REGISTERS ( 23 )
#define MB_FUNC_DIAG_READ_EXCEPTION ( 7 )
#define MB_FUNC_DIAG_DIAGNOSTIC ( 8 )
#define MB_FUNC_DIAG_GET_COM_EVENT_CNT ( 11 )
#define MB_FUNC_DIAG_GET_COM_EVENT_LOG ( 12 )
#define MB_FUNC_OTHER_REPORT_SLAVEID ( 17 )
代碼運行效果
04 命令測驗

注意上面代碼中
static uint16_t usRegInputBuf[REG_INPUT_NREGS]={0x01,0x02,0x03,0x04};//為了驗證使用的初始化值
06 命令測驗

03 命令測驗
其實代碼收到 03 命令就直接回傳了,但是從協議的角度,報文時正常的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301603.html
標籤:其他
