[应用相关] STM32 | 移植FreeModbus详细过程

[复制链接]
2353|46
 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:32 | 显示全部楼层 |阅读模式
modbus是一个非常好的串口协议(当然也能用在网口上),它简洁、规范、强大。可以满足大部分的工业、嵌入式需求。
这里详细说下如何将freemodbus移植到stm32平台。我之前下载的版本是1.5,当前官网最新的版本是1.6。两者差别不大,这里以1.5版本做演示。


 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:33 | 显示全部楼层
下载


下载好之后,解压得到如下内容:
2494562e249c593fd9.png
我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。

 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:34 | 显示全部楼层
2
准备一个STM32的工程文件夹


在工程文件夹下新建一个文件夹:FreeModbus。将第一步获取的两个文件夹放到里面。

打开工程,添加两个group,名字分别为modbus和port。将这两个文件夹下的C文件都添加进来,tcp相关的除外。
202762e249feb53f5.png
文件包含路径,也添加这几个文件夹的位置:
9792262e24a0c667e4.png

 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:35 | 显示全部楼层
3完善portserial.c文件

该文件就是modbus通信中用到的串口的初始化配置文件。我这里选择usart1,波特率9600.

第一次打开这个文件,内容如下:

  1. void
  2. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  3. {
  4.    
  5. }

  6. BOOL
  7. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  8. {
  9.     return FALSE;
  10. }

  11. BOOL
  12. xMBPortSerialPutByte( CHAR ucByte )
  13. {
  14.    
  15.     return TRUE;
  16. }

认真看一下函数名字,你会发现这些函数分别是:串口使能、串口初始化、发送一个字节、接收一个字节等等。

完善后代码如下:

  1. void
  2. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  3. {
  4.    
  5.   if(xRxEnable == TRUE)
  6.   {
  7.     USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
  8.   }
  9.   else
  10.   {
  11.     USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
  12.   }
  13.   
  14.   if(xTxEnable == TRUE)
  15.   {
  16.     USART_ITConfig(USART1, USART_IT_TC, ENABLE);
  17.   }
  18.   else
  19.   {
  20.     USART_ITConfig(USART1, USART_IT_TC, DISABLE);
  21.   }
  22. }

  23. BOOL
  24. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  25. {
  26.   USART1_Config((uint16_t)ulBaudRate);  
  27.   USART_NVIC();
  28.   return TRUE;
  29. }

  30. BOOL
  31. xMBPortSerialPutByte( CHAR ucByte )
  32. {
  33.    
  34.   
  35.     USART_SendData(USART1, ucByte);
  36.     return TRUE;
  37. }

  38. BOOL
  39. xMBPortSerialGetByte( CHAR * pucByte )
  40. {
  41.    
  42.   *pucByte = USART_ReceiveData(USART1);
  43.   return TRUE;
  44. }


  45. static void prvvUARTTxReadyISR( void )
  46. {
  47.     pxMBFrameCBTransmitterEmpty(  );
  48. }


  49. static void prvvUARTRxISR( void )
  50. {
  51.     pxMBFrameCBByteReceived(  );
  52. }


  53. void USART1_IRQHandler(void)
  54. {
  55.   
  56.   if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
  57.   {
  58.     prvvUARTRxISR();
  59.    
  60.     USART_ClearITPendingBit(USART1, USART_IT_RXNE);   
  61.   }
  62.   
  63.   if(USART_GetITStatus(USART1, USART_IT_ORE) == SET)
  64.   {  
  65.     USART_ClearITPendingBit(USART1, USART_IT_ORE);
  66.     prvvUARTRxISR();   
  67.   }
  68.   
  69.   
  70.   if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
  71.   {
  72.     prvvUARTTxReadyISR();
  73.    
  74.     USART_ClearITPendingBit(USART1, USART_IT_TC);
  75.   }
  76. }

其中USART1_Config((uint16_t)ulBaudRate);和    USART_NVIC();是串口初始化的代码,如下:


  1. void USART1_Config(uint16_t buad)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStructure;
  4.     USART_InitTypeDef USART_InitStructure;
  5.    
  6.    
  7.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
  8.    
  9.    
  10.    
  11.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  12.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  13.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  14.     GPIO_Init(GPIOA, &GPIO_InitStructure);   
  15.    
  16.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  17.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  18.     GPIO_Init(GPIOA, &GPIO_InitStructure);
  19.       
  20.    
  21.     USART_InitStructure.USART_BaudRate = buad;
  22.     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  23.     USART_InitStructure.USART_StopBits = USART_StopBits_1;
  24.     USART_InitStructure.USART_Parity = USART_Parity_No ;
  25.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  26.     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  27.    
  28.     USART_Init(USART1, &USART_InitStructure);
  29.     USART_Cmd(USART1, ENABLE);
  30. }


  31. void USART_NVIC(void)
  32. {
  33.   NVIC_InitTypeDef NVIC_InitStructure;
  34.   
  35.   
  36.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
  37.   
  38.   
  39.   NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  40.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  41.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  42.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  43.   NVIC_Init(&NVIC_InitStructure);
  44. }
 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:36 | 显示全部楼层
4
完善porttimer.c文件


modbus工作时需要一个定时器,所以这里配置一个定时器。定时器时基是50us,周期做为参数输入。代码如下:

  1. BOOL
  2. xMBPortTimersInit( USHORT usTim1Timerout50us )
  3. {
  4.   timer2_init(usTim1Timerout50us);
  5.   timer2_nvic();
  6.   return TRUE;
  7. }


  8. void
  9. vMBPortTimersEnable(  )
  10. {
  11.    
  12.   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  13.   TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  14.   TIM_SetCounter(TIM2,0x0000);
  15.   TIM_Cmd(TIM2, ENABLE);
  16. }

  17. void
  18. vMBPortTimersDisable(  )
  19. {
  20.    
  21.   TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  22.   TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);
  23.   TIM_SetCounter(TIM2,0x0000);
  24.   TIM_Cmd(TIM2, DISABLE);
  25. }


  26. static void prvvTIMERExpiredISR( void )
  27. {
  28.     ( void )pxMBPortCBTimerExpired(  );
  29. }

  30. void TIM2_IRQHandler(void)
  31. {
  32.   if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
  33.   {
  34.     prvvTIMERExpiredISR();
  35.     TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
  36.   }
  37. }

其中 timer2_init(usTim1Timerout50us) 和 timer2_nvic() 是timer2初始化函数,内容如下:

  1. void timer2_init(uint16_t period)
  2. {
  3.   TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  4.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  5.         TIM_DeInit(TIM2);
  6.   TIM_TimeBaseStructure.TIM_Period = period;
  7.         TIM_TimeBaseStructure.TIM_Prescaler = (1800 - 1);  
  8.   TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  9.   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  10.   TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  11.   TIM_Cmd(TIM2, ENABLE);

  12. }

  13. void timer2_nvic(void)
  14. {
  15.   NVIC_InitTypeDef NVIC_InitStructure;
  16.   NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
  17.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  18.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
  19.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
  20.   NVIC_Init(&NVIC_InitStructure);  
  21. }

在main.c文件中,定义各个模拟寄存器的地址和大小。

  1. #define REG_INPUT_START 0x0000

  2. #define REG_INPUT_NREGS 8

  3. #define REG_HOLDING_START 0x0000

  4. #define REG_HOLDING_NREGS 8

  5. #define REG_COILS_START 0x0000

  6. #define REG_COILS_SIZE 16

  7. #define REG_DISCRETE_START 0x0000

  8. #define REG_DISCRETE_SIZE 16
 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:37 | 显示全部楼层
6
补全输入寄存器操作函数、保持寄存器操作函数
modbus功能进行初始化,设置地址和波特率。这部分内容可以参考官方资料里的例程,也可以直接复制别人写好的。我这里放别人写好的代码:


  1. int main(void)
  2. {
  3.     usRegInputBuf[0] = 'I';
  4.     usRegInputBuf[1] = ' ';
  5.     usRegInputBuf[2] = 'a';
  6.     usRegInputBuf[3] = 'm';
  7.     usRegInputBuf[4] = ' ';
  8.     usRegInputBuf[5] = 'I';
  9.   

  10.     RCC_Config();
  11.     eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
  12.     eMBEnable();   
  13.     for(;;)
  14.     {
  15.       (void)eMBPoll();
  16.     }
  17. }


  18. eMBErrorCode
  19. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  20. {
  21.     eMBErrorCode    eStatus = MB_ENOERR;
  22.     int             iRegIndex;

  23.     if( ( usAddress >= REG_INPUT_START )
  24.         && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
  25.     {
  26.         iRegIndex = ( int )( usAddress - usRegInputStart );
  27.         while( usNRegs > 0 )
  28.         {
  29.             *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );
  30.             *pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );
  31.             iRegIndex++;
  32.             usNRegs--;
  33.         }
  34.     }
  35.     else
  36.     {
  37.         eStatus = MB_ENOREG;
  38.     }

  39.     return eStatus;
  40. }


  41. eMBErrorCode
  42. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
  43. {
  44.   eMBErrorCode    eStatus = MB_ENOERR;
  45.   int             iRegIndex;


  46.   if((usAddress >= REG_HOLDING_START)&&\
  47.     ((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
  48.   {
  49.     iRegIndex = (int)(usAddress - usRegHoldingStart);
  50.     switch(eMode)
  51.     {                                       
  52.       case MB_REG_READ:
  53.         while(usNRegs > 0)
  54.         {
  55.           *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            
  56.           *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF);
  57.           iRegIndex++;
  58.           usNRegs--;         
  59.         }                           
  60.         break;
  61.       case MB_REG_WRITE:
  62.         while(usNRegs > 0)
  63.         {         
  64.           usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
  65.           usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
  66.           iRegIndex++;
  67.           usNRegs--;
  68.         }        
  69.       }
  70.   }
  71.   else
  72.   {
  73.     eStatus = MB_ENOREG;
  74.   }  
  75.   
  76.   return eStatus;
  77. }


  78. eMBErrorCode
  79. eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
  80. {
  81.   eMBErrorCode    eStatus = MB_ENOERR;
  82.   int             iRegIndex;


  83.   if((usAddress >= REG_HOLDING_START)&&\
  84.     ((usAddress+usNCoils) <= (REG_HOLDING_START + REG_HOLDING_NREGS)))
  85.   {
  86.     iRegIndex = (int)(usAddress - usRegHoldingStart);
  87.     switch(eMode)
  88.     {                                       
  89.       case MB_REG_READ:
  90.         while(usNCoils > 0)
  91.         {


  92.           iRegIndex++;
  93.           usNCoils--;         
  94.         }                           
  95.         break;
  96.       case MB_REG_WRITE:
  97.         while(usNCoils > 0)
  98.         {         


  99.           iRegIndex++;
  100.           usNCoils--;
  101.         }        
  102.       }
  103.   }
  104.   else
  105.   {
  106.     eStatus = MB_ENOREG;
  107.   }  
  108.   
  109.   return eStatus;
  110. }

  111. eMBErrorCode
  112. eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  113. {
  114.     ( void )pucRegBuffer;
  115.     ( void )usAddress;
  116.     ( void )usNDiscrete;
  117.     return MB_ENOREG;
  118. }
 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:38 | 显示全部楼层
7
修改mbrtu.c文件


否则modbus从机收到命令后,只会返回一次数据。在函数“eMBRTUSend”中。


  1. eMBErrorCode
  2. eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
  3. {
  4.     eMBErrorCode    eStatus = MB_ENOERR;
  5.     USHORT          usCRC16;

  6.     ENTER_CRITICAL_SECTION(  );

  7.    
  8.     if( eRcvState == STATE_RX_IDLE )
  9.     {
  10.         
  11.         pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
  12.         usSndBufferCount = 1;

  13.         
  14.         pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
  15.         usSndBufferCount += usLength;

  16.         
  17.         usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
  18.         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
  19.         ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 >> 8 );

  20.         
  21.         
  22.         eSndState = STATE_TX_XMIT;
  23.         
  24.         
  25.         xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
  26.         pucSndBufferCur++;  
  27.         usSndBufferCount--;
  28.         
  29.         
  30.         vMBPortSerialEnable( FALSE, TRUE );
  31.     }
  32.     else
  33.     {
  34.         eStatus = MB_EIO;
  35.     }
  36.     EXIT_CRITICAL_SECTION(  );
  37.     return eStatus;
  38. }
 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:39 | 显示全部楼层
8
修改mbconfig.h文件
取消对ASCII的支持。

  1. #define MB_ASCII_ENABLED                        (  0 )

 楼主| 可怜的小弗朗士 发表于 2022-7-28 16:40 | 显示全部楼层
保存,编译,下载。使用专用的modbus工具测试
工具配置如下:
6349162e24b80063cf.png
modbus指令格式如下:
7469462e24b8945f28.png
咱们这里设置如下:01 04 00 00 00 02,功能码04,起始地址0,数据长度2.校验码没有写怎么办?

这就是这个工具的便利之处!我们不用管,它会自动计算!直接点击发送即可。得到结果如下:
7474062e24b92cbce7.png
可以看到下面的框里,绿色的是我们发送的内容,最后两位是工具自动补上的。蓝色内容是单片机(也就是modbus从机)返回给我们的。

没有问题,打完收工!
稳稳の幸福 发表于 2022-7-29 11:46 | 显示全部楼层
谢谢分享
Uriah 发表于 2022-10-4 11:30 | 显示全部楼层

待向GPIO(通用I/O端口)的输入从0变为1时,程序可以一定的间隔来检查GPIO的状态
Bblythe 发表于 2022-10-4 14:29 | 显示全部楼层

CPLD解密,DSP解密都习惯称为单片机解密
bestwell 发表于 2022-10-12 09:53 | 显示全部楼层
STM32 HAL库移植FreeModbus  
yszong 发表于 2022-10-13 22:42 | 显示全部楼层
单片机的外部都连接有象电池等电源部分
zerorobert 发表于 2022-10-17 21:08 | 显示全部楼层
STM32移植FreeModbus RTU从机吗           
1988020566 发表于 2022-10-17 21:28 | 显示全部楼层
移植Modbus也不是一件简单的事情        
uytyu 发表于 2022-10-17 22:17 | 显示全部楼层
以FreeModbus为例进行移植,将其移植到STM32中使用。
wukye 发表于 2022-10-18 11:37 | 显示全部楼层
mark一下
1988020566 发表于 2022-10-18 11:43 | 显示全部楼层
stm32裸机移植FreeModbus        
kkzz 发表于 2022-10-18 12:30 | 显示全部楼层
要的是modbus这个文件夹,和demo->BARE下的port文件夹。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

101

主题

763

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部