你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 HAL库移植FreeModbus详细步骤

[复制链接]
STMCU小助手 发布时间:2022-4-10 22:53
modbus命令码表:

b8032deb4d154657acd2f7f2942369d4.jpg

FreeModbus文件说明

      解压freemodbus文件后打开,我们需要demo目录下的BARE,该目录下的代码是空的,STM32移植工作基本就是修改:portserial.c、porttimer.c、port.h这三个文件。
20200313143257958.png

      mobus文件夹就是完整的源码,包含rtu、ascii、tcp:

ZM%DLXPRB8$%[NN2%4Z1N.png

      我为了移植时在keil添加源文件和头文件方便,就把modbus所有的头文件和源文件放到了一个文件夹下,并创建了一个port.c文件,用于编写modbus所必需的回调处理函数:

20200313144036579.png

STM32CUBEMX配置

时钟配置,设置主频工作在72MHz下:


JU1~N33M}I7K1(6K@JFN5~R.png

配置串口1,这里随便配置就行,在modbus移植过程中还会对串口重新初始化:


U25OQ_FHYD5WEXLFMI4V@RU.png

配置定时器4,用于3.5个字符的定时检测,这里随便配置就行,在modbus移植过程中还会对定时器重新初始化:


H6{)WT`**CIHDAM02558GK.png

中断配置,这里注意,串口的优先级是要比定时器优先高的:


G(RP`74TR)NPJFUK``Z%I2L.png
取消掉自动生成中断服务程序,在移植过程中我们要自己编写串口和定时器的中断服务程序:

ALHMK5Y615K6(`M{0[]YJ.png

移植代码修改

生成代码后将modbus放到工程目录下:

5%(_LMGIM_8_YGL`]JGAB67.png

打开keil工程添加modbus源码:


F`8_YFACMNOAP28~2VHD{CO.png

添加包含头文件:


`7BMVXA`R9SQBTGT`B@{]X7.png

修改modbus定时器初始化源代码porttimer.c文件
       定时器的修改比较容易,将定时器设置为每50us的时长记一个数,传入的usTim1Timerout50us变量给自动装载即可,prvvTIMERExpiredISR函数需要在定时器中断服务函数中调用,它的作用是用于通知modbus协议栈3.5个字符的等待时间已经到达;由于我们在STM32CUBEMX中取消掉了定时器和串口的中断服务函数程序,所以我们在该文件中添加定时器的中断服务程序,修改后的代码如下:

  1. BOOL
  2. xMBPortTimersInit( USHORT usTim1Timerout50us )
  3. {
  4.     TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  5.     TIM_MasterConfigTypeDef sMasterConfig = {0};

  6.     htim4.Instance = TIM4;
  7.     htim4.Init.Prescaler = 3599;                                                                // 50us记一次数
  8.     htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
  9.     htim4.Init.Period = usTim1Timerout50us - 1;                                        // usTim1Timerout50us * 50即为定时器溢出时间
  10.     htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  11.     htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  12.     if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
  13.     {
  14.         return FALSE;
  15.     }
  16.     sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  17.     if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
  18.     {
  19.         return FALSE;
  20.     }
  21.     sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  22.     sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  23.     if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
  24.     {
  25.         return FALSE;
  26.     }

  27.     __HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE);                                        // 使能定时器更新中断
  28.     return TRUE;
  29. }

  30. inline void
  31. vMBPortTimersEnable(  )
  32. {
  33.     __HAL_TIM_SET_COUNTER(&htim4, 0);                // 清空计数器
  34.     __HAL_TIM_ENABLE(&htim4);                                // 使能定时器
  35. }

  36. inline void
  37. vMBPortTimersDisable(  )
  38. {
  39.     __HAL_TIM_DISABLE(&htim4);                                // 禁能定时器
  40. }

  41. /* Create an ISR which is called whenever the timer has expired. This function
  42. * must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
  43. * the timer has expired.
  44. */
  45. static void prvvTIMERExpiredISR( void )
  46. {
  47.     ( void )pxMBPortCBTimerExpired(  );
  48. }

  49. /// 定时器4中断服务程序
  50. void TIM4_IRQHandler(void)
  51. {
  52.     if(__HAL_TIM_GET_FLAG(&htim4, TIM_FLAG_UPDATE))                        // 更新中断标记被置位
  53.     {
  54.         __HAL_TIM_CLEAR_FLAG(&htim4, TIM_FLAG_UPDATE);                // 清除中断标记
  55.         prvvTIMERExpiredISR();                                                                // 通知modbus3.5个字符等待时间到
  56.     }
  57. }


复制代码

修改modbus串口初始化源代码portserial.c文件
         ~~~~~~~~        在该文件中实现串口1的中断服务程序,prvvUARTTxReadyISR和prvvUARTRxISR函数需要填写进中断服务程序,前者得到作用为通知modbus协议栈串口已经空闲可以发送数据了,后者的作用为通知modbus串口1有数据到达,修改后的代码如下:

  1. /*
  2. * FreeModbus Libary: BARE Port
  3. * Copyright (C) 2006 Christian Walter <<a href="mailto:wolti@sil.at">wolti@sil.at</a>>
  4. *
  5. * This library is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU Lesser General Public
  7. * License as published by the Free Software Foundation; either
  8. * version 2.1 of the License, or (at your option) any later version.
  9. *
  10. * This library is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. * Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public
  16. * License along with this library; if not, write to the Free Software
  17. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18. *
  19. * File: $Id$
  20. */

  21. #include "port.h"
  22. #include "usart.h"

  23. /* ----------------------- Modbus includes ----------------------------------*/
  24. #include "mb.h"
  25. #include "mbport.h"

  26. /* ----------------------- static functions ---------------------------------*/
  27. static void prvvUARTTxReadyISR( void );
  28. static void prvvUARTRxISR( void );

  29. /* ----------------------- Start implementation -----------------------------*/
  30. void
  31. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  32. {
  33.     if(xRxEnable)
  34.     {
  35.         __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);                // 使能接收非空中断
  36.     }
  37.     else
  38.     {
  39.         __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);                // 禁能接收非空中断
  40.     }

  41.     if(xTxEnable)
  42.     {
  43.         __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);                        // 使能发送为空中断
  44.     }
  45.     else
  46.     {
  47.         __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);                // 禁能发送为空中断
  48.     }
  49. }

  50. BOOL
  51. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  52. {
  53.     huart1.Instance = USART1;
  54.     huart1.Init.BaudRate = ulBaudRate;
  55.     huart1.Init.StopBits = UART_STOPBITS_1;
  56.     huart1.Init.Mode = UART_MODE_TX_RX;
  57.     huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  58.     huart1.Init.OverSampling = UART_OVERSAMPLING_16;

  59.     switch(eParity)
  60.     {
  61.         // 奇校验
  62.     case MB_PAR_ODD:
  63.         huart1.Init.Parity = UART_PARITY_ODD;
  64.         huart1.Init.WordLength = UART_WORDLENGTH_9B;                        // 带奇偶校验数据位为9bits
  65.         break;
  66.         
  67.         // 偶校验
  68.     case MB_PAR_EVEN:
  69.         huart1.Init.Parity = UART_PARITY_EVEN;
  70.         huart1.Init.WordLength = UART_WORDLENGTH_9B;                        // 带奇偶校验数据位为9bits
  71.         break;
  72.         
  73.         // 无校验
  74.     default:
  75.         huart1.Init.Parity = UART_PARITY_NONE;
  76.         huart1.Init.WordLength = UART_WORDLENGTH_8B;                        // 无奇偶校验数据位为8bits
  77.         break;
  78.     }
  79.     return HAL_UART_Init(&huart1) == HAL_OK ? TRUE : FALSE;
  80. }

  81. BOOL
  82. xMBPortSerialPutByte( CHAR ucByte )
  83. {
  84.     USART1->DR = ucByte;
  85.     return TRUE;
  86. }

  87. BOOL
  88. xMBPortSerialGetByte( CHAR * pucByte )
  89. {
  90.     *pucByte = (USART1->DR & (uint16_t)0x00FF);
  91.         return TRUE;
  92. }

  93. /* Create an interrupt handler for the transmit buffer empty interrupt
  94. * (or an equivalent) for your target processor. This function should then
  95. * call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
  96. * a new character can be sent. The protocol stack will then call
  97. * xMBPortSerialPutByte( ) to send the character.
  98. */
  99. static void prvvUARTTxReadyISR( void )
  100. {
  101.     pxMBFrameCBTransmitterEmpty(  );
  102. }

  103. /* Create an interrupt handler for the receive interrupt for your target
  104. * processor. This function should then call pxMBFrameCBByteReceived( ). The
  105. * protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
  106. * character.
  107. */
  108. static void prvvUARTRxISR( void )
  109. {
  110.     pxMBFrameCBByteReceived(  );
  111. }

  112. void USART1_IRQHandler(void)
  113. {
  114.     if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))                        // 接收非空中断标记被置位
  115.     {
  116.         __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);                        // 清除中断标记
  117.         prvvUARTRxISR();                                                                                // 通知modbus有数据到达
  118.     }

  119.     if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))                                // 发送为空中断标记被置位
  120.     {
  121.         __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TXE);                        // 清除中断标记
  122.         prvvUARTTxReadyISR();                                                                        // 通知modbus数据可以发松
  123.     }
  124. }
复制代码

注意一点,一般如果使用了485芯片的话,那么同一时刻只能接收或者发送,可以将函数vMBPortSerialEnable修改成这样:

  1. void
  2. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  3. {
  4.     if(xRxEnable)
  5.     {
  6.             //
  7.         // 在此处将485芯片设置为接收模式
  8.         //
  9.                 /* do something */
  10.         __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);                // 使能接收非空中断
  11.     }
  12.     else
  13.     {
  14.         __HAL_UART_DISABLE_IT(&huart1, UART_IT_RXNE);                // 禁能接收非空中断
  15.     }

  16.     if(xTxEnable)
  17.     {
  18.             //
  19.         // 在此处将485芯片设置为发送模式
  20.         //
  21.         /* do something */
  22.         __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);                        // 使能发送为空中断
  23.     }
  24.     else
  25.     {
  26.         __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);                // 禁能发送为空中断
  27.     }
  28. }
复制代码

编写modbus命令处理回调函数port.c文件
本例程只实现了读取输入寄存器和保持寄存器的功能,详细代码如下:

  1. #include "mb.h"
  2. #include "mbport.h"


  3. // 十路输入寄存器
  4. #define REG_INPUT_SIZE  10
  5. uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];


  6. // 十路保持寄存器
  7. #define REG_HOLD_SIZE   10
  8. uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];


  9. // 十路线圈
  10. #define REG_COILS_SIZE 10
  11. uint8_t REG_COILS_BUF[REG_COILS_SIZE];


  12. // 十路离散量
  13. #define REG_DISC_SIZE  10
  14. uint8_t REG_DISC_BUF[10];


  15. /// CMD4
  16. eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  17. {
  18.     USHORT usRegIndex = usAddress - 1;

  19.     // 非法检测
  20.     if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
  21.     {
  22.         return MB_ENOREG;
  23.     }

  24.     // 循环读取
  25.     while( usNRegs > 0 )
  26.     {
  27.         *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
  28.         *pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
  29.         usRegIndex++;
  30.         usNRegs--;
  31.     }

  32.     // 模拟输入寄存器被改变
  33.     for(usRegIndex = 0; usRegIndex < REG_INPUT_SIZE; usRegIndex++)
  34.     {
  35.         REG_INPUT_BUF[usRegIndex]++;
  36.     }

  37.     return MB_ENOERR;
  38. }

  39. /// CMD6、3、16
  40. eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
  41. {
  42.     USHORT usRegIndex = usAddress - 1;  

  43.     // 非法检测
  44.     if((usRegIndex + usNRegs) > REG_HOLD_SIZE)
  45.     {
  46.         return MB_ENOREG;
  47.     }

  48.         // 写寄存器
  49.     if(eMode == MB_REG_WRITE)
  50.     {
  51.         while( usNRegs > 0 )
  52.         {
  53.                         REG_HOLD_BUF[usRegIndex] = (pucRegBuffer[0] << 8) | pucRegBuffer[1];
  54.                         pucRegBuffer += 2;
  55.             usRegIndex++;
  56.             usNRegs--;
  57.         }
  58.     }
  59.         
  60.         // 读寄存器
  61.     else
  62.     {
  63.         while( usNRegs > 0 )
  64.         {
  65.             *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
  66.             *pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
  67.             usRegIndex++;
  68.             usNRegs--;
  69.         }
  70.     }

  71.     return MB_ENOERR;
  72. }

  73. /// CMD1、5、15
  74. eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
  75. {
  76.     USHORT usRegIndex   = usAddress - 1;
  77.     USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
  78.     UCHAR  ucStatus     = 0;
  79.     UCHAR  ucBits       = 0;
  80.     UCHAR  ucDisp       = 0;

  81.     // 非法检测
  82.     if((usRegIndex + usNCoils) > REG_COILS_SIZE)
  83.     {
  84.         return MB_ENOREG;
  85.     }

  86.     // 写线圈
  87.     if(eMode == MB_REG_WRITE)
  88.     {
  89.         while(usCoilGroups--)
  90.         {
  91.             ucStatus = *pucRegBuffer++;
  92.             ucBits   = 8;
  93.             while((usNCoils--) != 0 && (ucBits--) != 0)
  94.             {
  95.                 REG_COILS_BUF[usRegIndex++] = ucStatus & 0X01;
  96.                 ucStatus >>= 1;
  97.             }
  98.         }
  99.     }

  100.     // 读线圈
  101.     else
  102.     {
  103.         while(usCoilGroups--)
  104.         {
  105.             ucDisp = 0;
  106.             ucBits = 8;
  107.             while((usNCoils--) != 0 && (ucBits--) != 0)
  108.             {
  109.                 ucStatus |= (REG_COILS_BUF[usRegIndex++] << (ucDisp++));
  110.             }
  111.             *pucRegBuffer++ = ucStatus;
  112.         }
  113.     }
  114.     return MB_ENOERR;
  115. }


  116. /// CMD4
  117. eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  118. {
  119.     USHORT usRegIndex   = usAddress - 1;
  120.     USHORT usCoilGroups = ((usNDiscrete - 1) / 8 + 1);
  121.     UCHAR  ucStatus     = 0;
  122.     UCHAR  ucBits       = 0;
  123.     UCHAR  ucDisp       = 0;

  124.     // 非法检测
  125.     if((usRegIndex + usNDiscrete) > REG_DISC_SIZE)
  126.     {
  127.         return MB_ENOREG;
  128.     }

  129.         // 读离散输入
  130.         while(usCoilGroups--)
  131.         {
  132.                 ucDisp = 0;
  133.                 ucBits = 8;
  134.                 while((usNDiscrete--) != 0 && (ucBits--) != 0)
  135.                 {
  136.                         if(REG_DISC_BUF[usRegIndex])
  137.                         {
  138.                                 ucStatus |= (1 << ucDisp);
  139.                         }
  140.                         ucDisp++;
  141.                 }
  142.                 *pucRegBuffer++ = ucStatus;
  143.         }

  144.     // 模拟改变
  145.     for(usRegIndex = 0; usRegIndex < REG_DISC_SIZE; usRegIndex++)
  146.     {
  147.         REG_DISC_BUF[usRegIndex] = !REG_DISC_BUF[usRegIndex];
  148.     }

  149.     return MB_ENOERR;
  150. }
复制代码

主函数

  1. int main(void)
  2. {
  3.     HAL_Init();
  4.     SystemClock_Config();
  5.     MX_GPIO_Init();
  6.     eMBInit(MB_RTU, 0x01, 0, 9600, MB_PAR_ODD);                // 初始化modbus为RTU方式,波特率9600,奇校验
  7.     eMBEnable();                                                                        // 使能modbus协议栈

  8.     for( ;; )
  9.     {
  10.         eMBPoll();                                                                        // 轮训查询
  11.     }
  12. }
复制代码

移植测试

20200313154857173.gif

ends…



收藏 评论1 发布时间:2022-4-10 22:53

举报

1个回答
zzzff 回答时间:2024-10-26 10:31:57

我的是stm32g071系列,移植上去不行怎么办

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版