首先说明 : FreeModbus 有很多个库!!!! 不同库的实现方法是略有不同的!!!
本次 FreeModbus RTU 移植 主要依据 这个网友分享的工程他人移植的库
你可能会在csdn看到他的文章, 但是完全跟着那个文章走很混乱 而且跟库的文件不一样. 故而 我重新整理了工程, 并写了一个详细的移植教程
1. 下载 FreeModbus RTU 库
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) 关闭再开启 然后就可以使用了