首先说明 : FreeModbus 有很多个库!!!! 不同库的实现方法是略有不同的!!!
本次 FreeModbus RTU 移植 主要依据 这个网友分享的工程他人移植的库
你可能会在csdn看到他的文章, 但是完全跟着那个文章走很混乱 而且跟库的文件不一样. 故而 我重新整理了工程, 并写了一个详细的移植教程
1. 下载 FreeModbus RTU 库
2. STM32CubeMX 配置流程
我假设你已经学会使用stm32cubeMX点灯了;
2.1下载模式配置
2.2 开启外部时钟
2.3 定时器配置
2.4 串口配置
2.5 中断配置
2.6 配置中断函数(关闭自动生成)
2.7 配置时钟
3.库文件导入
3.1 .c文件汇总
3.2 .h文件汇总
3.3 demo文件选择
4. 移植流程
ok 完成上述步骤后, 你就可以开始正式的移植工作了:
主要需要移植的地方为: portserial.c && porttimer.c && demo.c
4.1 portserial.c
vMBPortSerialEnable() 函数
这个函数要根据传入的参数进行串口接收和关闭中断使能
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if(xRxEnable)
{
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
}
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() 函数
这个函数主要初始化串口,因为我们已经使用stm32cubeMX配置好串口,所以直接调用就好了, 这样子可以实现配置统一,方便阅读和修改
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
MX_USART1_UART_Init();
return TRUE;
}
xMBPortSerialPutByte () 函数
这个函数主要实现串口发送一个字节, 有点神奇的是我直接调用hal库的函数传输会有bug,通信失败. 具体原因我还没排查到.这里贴那个老哥是实现方法;
xMBPortSerialPutByte( CHAR ucByte )
{
USART1->DR = ucByte;
return TRUE;
}
xMBPortSerialGetByte() 函数
这个函数主要实现串口接收一个字节, 有点神奇的是我直接调用hal库的函数传输会有bug,通信失败. 具体原因我还没排查到.这里贴那个老哥的实现方法;
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = (USART1->DR & (uint16_t)0x00FF);
return TRUE;
}
手搓串口1中断函数USART1_IRQHandler()
这个中断函数要在portserial.c中手动添加, 主要是因为它调用了static void prvvUARTTxReadyISR( void ) 和 static void prvvUARTRxISR( void ); 再其他文件调用不了. 所以才要关闭hal库自动生成中断函数在这里手动添加;
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
prvvUARTRxISR();
}
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))
{
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);
prvvUARTTxReadyISR();
}
}
4.2 porttimer.c
xMBPortTimersInit() 函数
这个函数主要初始化定时器, 因为我使用的是stm32cubeMX配置好定时器, 所以直接调用就好了, 这样子可以实现配置统一,方便阅读和修改
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
MX_TIM4_Init();
__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);
return TRUE;
}
vMBPortTimersEnable() 函数
使能定时器 和情况计数器数值
vMBPortTimersEnable( )
{
__HAL_TIM_SET_COUNTER(&htim4, 0);
__HAL_TIM_ENABLE(&htim4);
}
vMBPortTimersDisable() 函数
关闭定时器
vMBPortTimersDisable( )
{
__HAL_TIM_DISABLE(&htim4);
}
手搓定时器4中断函数TIM4_IRQHandler()
这个中断函数要在porttimer.c中手动添加, 主要是因为它调用了static void prvvTIMERExpiredISR( void ); 再其他文件调用不了. 所以才要关闭hal库自动生成中断函数在这里手动添加;
void TIM4_IRQHandler(void)
{
if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))
{
__HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);
prvvTIMERExpiredISR();
}
}
4.3. demo.c
这个文件就是FreeModbus移植的关键位置了, 其通讯都是在调用这些信息;
以下直接贴验证的好代码, 想要自定义就修改前面那四个寄存器就可以;
#include "mb.h"
#include "mbport.h"
// 十路输入寄存器
#define REG_INPUT_SIZE 10
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];
// 十路保持寄存器
#define REG_HOLD_SIZE 10
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];
// 十路线圈
#define REG_COILS_SIZE 10
uint8_t REG_COILS_BUF[REG_COILS_SIZE] = {1, 1, 1, 1, 0, 0, 0, 0, 1, 1};
// 十路离散量
#define REG_DISC_SIZE 10
uint8_t REG_DISC_BUF[REG_DISC_SIZE] = {1,1,1,1,0,0,0,0,1,1};
/// CMD4命令处理回调函数
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
{
return MB_ENOREG;
}
// 循环读取
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
// 模拟输入寄存器被改变
for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
{
REG_INPUT_BUF[usRegIndex]++;
}
return MB_ENOERR;
}
/// CMD6、3、16命令处理回调函数
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
// 非法检测
if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
{
return MB_ENOREG;
}
// 写寄存器
if(eMode == MB_REG_WRITE)
{
while( usNRegs > 0 )
{
REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
pucRegBuffer += 2;
usRegIndex++;
usNRegs--;
}
}
// 读寄存器
else
{
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
}
return MB_ENOERR;
}
/// CMD1、5、15命令处理回调函数
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if((usRegIndex + usNCoils) > REG_COILS_SIZE)
{
return MB_ENOREG;
}
if(eMode == MB_REG_WRITE)
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = *pucRegBuffer++;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
REG_COILS_BUF[usRegIndex++] = (ucState >> ucBits) & 0X01;
usNCoils--;
ucBits++;
}
ucLoops--;
}
}
else
{
ucLoops = (usNCoils - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNCoils != 0 && ucBits < 8)
{
if(REG_COILS_BUF[usRegIndex])
{
ucState |= (1 << ucBits);
}
usNCoils--;
usRegIndex++;
ucBits++;
}
*pucRegBuffer++ = ucState;
ucLoops--;
}
}
return MB_ENOERR;
}
/// CMD2命令处理回调函数
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
USHORT usRegIndex = usAddress - 1;
UCHAR ucBits = 0;
UCHAR ucState = 0;
UCHAR ucLoops = 0;
// 非法检测
if((usRegIndex + usNDiscrete) > REG_DISC_SIZE)
{
return MB_ENOREG;
}
ucLoops = (usNDiscrete - 1) / 8 + 1;
while(ucLoops != 0)
{
ucState = 0;
ucBits = 0;
while(usNDiscrete != 0 && ucBits < 8)
{
if(REG_DISC_BUF[usRegIndex])
{
ucState |= (1 << ucBits);
}
usNDiscrete--;
usRegIndex++;
ucBits++;
}
*pucRegBuffer++ = ucState;
ucLoops--;
}
// 模拟离散量输入被改变
for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++)
{
REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
}
return MB_ENOERR;
}
5.移植最后一步 main.c 编写
ok 至此你已经完成90%的移植工作了;
现在只需要调用 对应初始化的函数 还有那四个数组就可以了;
贴一个简单版的main.c关键部分
#include "mb.h"
#include "mbport.h"
void SystemClock_Config(void);
extern uint16_t REG_HOLD_BUF[10];
int main(void)
{
HAL_Init();
eMBInit(MB_RTU, 0x01, 0, 115200, MB_PAR_NONE);
eMBEnable();
SystemClock_Config();
REG_HOLD_BUF[0] = 0Xff00;
MX_GPIO_Init();
MX_TIM4_Init();
MX_USART1_UART_Init();
while (1)
{
eMBPoll();
}
}
6.剩下10%的坑
如果你操作完上面的步骤发现还没能使用, 恭喜你 还有几个坑得改一改:
- 使用 MicroLIB 库 且 编译优化等级选择 Level 3(-O3) 重编译;
如下:
- 把你的modbus助手(如 QModBus) 关闭再开启 然后就可以使用了