其他分享
首页 > 其他分享> > 手把手教你移植FreeModbus到STM32(一)

手把手教你移植FreeModbus到STM32(一)

作者:互联网

转载自:http://forum.eepw.com.cn/thread/334805/1   作者:zhuzhaokun1987

 

0. 为什么要移植free modbus

大家好,近期由于一个小项目的需要,要用到Modbus协议进行通信。相信各位工作的小伙伴们,或多或少都要跟Modbus打交道吧。那么,Modbus协议的重要性我自不必多言,相信大家都心知肚明。

现如今,我们在很多的工控设备上都会看到一个叫Modbus的协议,甚至你要读一个温度、电压、电流都要使用Modbus协议。因此在设备中移植Modbus协议变成了一个不得要做的事,因此使用Modbus协议已经成为了一种不可缺少的工作技能。

幸运的是,现在有很多的开源的modbus代码,可以方便我们快速的应用Modbus协议进行进一步的工作。例如,FreeModbus、LibModbus等。但移植Modbus也不是一件简单的事情,本系列就以FreeModbus为例进行移植,将其移植到STM32中使用。FreeModbus是一个优秀的应用层协议,它很简洁也相对完善,对于还没有接触过modbus的朋友来说也能很快上手,对于以前已经熟悉过Modbus的人来说,那简直更是轻而易举了。

简单科普一下,FreeModbus是一个奥地利人写的Modbus协议。它是一个针对嵌入式应用的一个免费(自由)的通用MODBUS协议的移植(摘自百度百科)。好了言归正传,我们一起来移植Freemodbus。

 

1. free modbus library 源码的获取

如果您下决心要研究一下Free Modbus的话,可以访问他们的官网:https://www.embedded-solutions.at/en/freemodbus/在freemodbus DownLoads界面中,可以免费下载freemodbus V1.6,以及freemodbus的源码,如有需要,还请大家点击上面的链接,动动发财手亲自下载,这里就暂且略过。 

 

图片1.png

 

2. free modbus library 介绍

下载完毕后,不要心急,一看解压后一大堆文件,更不要怕,分类很重重要!free modbus -V1.6,主要包括demo  modbus doc tools 四个文件夹,具体如下图所示:

 

图片2.png

 

Demo 文件夹中主要free modbus官方为我们新建好的各种平台的测试例程,加快我们的开发进度,其中包括 Win32平台、Linux平台、ARM平台等。我们需要移植到STM32单片机也属于ARM平台的范畴,因此,在我们移植的过程中可以参考ARM平台已经新建好的测试工程。Demo 文件夹下具体测试平台工程如下所示:

 

图片3.png

Modbus文件夹下,主要放一些关于Modbus自身协议的源码,其中包括Modbus-Rtu、Modbus-Ascii、Modbus-Tcp等,具体如下图所示,移植的过程中,可根据实际情况的需要对该文件夹进行适当的裁剪。

图片4.png

至于doc和tools文件夹就不再赘述,doc主要放一些帮助和说明文件,tools就是放置一些需要的工具。Free modbus library 就介绍这么多,下面开始从0到1完成在stm32平台上的移植。(---未完待续哦---

 

 

3.从0到1移植freemodbus到stm32平台

 

移植之前需要准备:STM32基础工程(标准库、HAL库均可);FreeModbus Library V1.6;IDE(这里选择常用的MDK);如果需要添加操作系统的,可将STM32的基础工程改为带有操作系统的基础工程,比如常用的FreeRTOS、RT-Thread等。

 

01.复制正点原子战舰V3库函基础工程,在此工程上添加FreeModbus,先在工程中新建一个Modbus文件,并将freemodbus v1.6下的modbus文件夹下的文件全部复制过来,再将Demo文件夹下的BARE文件夹复制过来,Modbus文件内的具体内容如下:

 

图片5.png

 

02.使用MDK打开基础工程,向工程中添加Modbus分组,将Modbus文件夹下对应的文件都添加到Modbus分组中,主要包括:ascii文件夹的mbascii.c文件;function文件夹下的所有.c文件;rtu和tcp文件夹下的.c文件;mb.c文件。添加后的效果如下:

 

图片6.png

 

03.添加对应的头文件,工程需要添加以下文件夹:ascii文件夹、BARE/port文件夹、include文件夹、rtu文件夹、tcp文件夹;总之有.h的文件夹统统包含,具体如下:

 

图片7.png

 

04.需要对添加过来部分文件进行补充,以完成Modbus对串口和定时器的需要,第一个需要补充的是portserial.c文件,补充Modbus串口发送中断和接收中断使能函数、Modbus串口初始化函数xMBPortSerialInit,具体代码如下:(这里只实现了232功能,没有加485,其实两个代码几乎一致)

 

 

 

view plaincopy to clipboardprint?
  1. //该函数实现STM32串口发送中断和接收中断使能  
  2. void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )  
  3. {  
  4. //STM32串口 接收中断使能  
  5. if(xRxEnable==TRUE)   
  6. {   
  7.  //使能接收和接收中断  
  8.      USART_ITConfig(MODBUS_USART, USART_IT_RXNE, ENABLE);  
  9.   }   
  10. else if(xRxEnable == FALSE)  
  11. {   
  12.      //禁止接收和接收中断    
  13.      USART_ITConfig(MODBUS_USART, USART_IT_RXNE, DISABLE);  
  14.   }  
  15.   //STM32串口 发送中断使能  
  16.    if(xTxEnable==TRUE)   
  17. {  
  18.      //使能发送完成中断  
  19.      USART_ITConfig(MODBUS_USART, USART_IT_TXE, ENABLE);  
  20.   }   
  21. else if(xTxEnable == FALSE)   
  22. {  
  23.      //禁止发送完成中断  
  24.      USART_ITConfig(MODBUS_USART, USART_IT_TXE, DISABLE);  
  25.   }  
  26. } else if(xTxEnable == FALSE)   
  27. {  
  28.      MODBUS_RECIEVE();  
  29.      USART_ITConfig(MODBUS_USART, USART_IT_TC, DISABLE);  
  30.   }  
  31. }  
  32. /*******************************************************************/  
  33. //对串口进行初始化由eMBRTUInt函数进行调用  
  34. BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )  
  35. {  
  36.  GPIO_InitTypeDef  GPIO_InitStructure;  
  37.  USART_InitTypeDef USART_InitStructure;  
  38.  NVIC_InitTypeDef  NVIC_InitStructure;  
  39. (void)ucPORT; //不修改串口  
  40.    (void)ucDataBits; //不修改数据长度  
  41.    (void)eParity; //不许改效验格式  
  42.    /***引脚初始化*************************************/  
  43.    
  44.    //时钟使能  
  45.  RCC_APB2PeriphClockCmd(MODBUS_USART_GPIO_CLK,ENABLE);  
  46.  RCC_APB1PeriphClockCmd(MODBUS_USART_CLK,ENABLE);  
  47.    //TX  
  48.    GPIO_InitStructure.GPIO_Pin = MODBUS_USART_TX_PIN;  
  49.  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
  50.    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出  
  51.    GPIO_Init(MODBUS_USART_TX_PORT, &GPIO_InitStructure);  
  52.    //RX  
  53.    GPIO_InitStructure.GPIO_Pin = MODBUS_USART_RX_PIN;  
  54.  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入  
  55.    GPIO_Init(MODBUS_USART_RX_PORT, &GPIO_InitStructure);  
  56.    
  57.   /***************串口初始化********************/  
  58.    USART_InitStructure.USART_BaudRate    = ulBaudRate;  
  59.    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  
  60.    USART_InitStructure.USART_Mode        = USART_Mode_Rx|USART_Mode_Tx;  
  61.    USART_InitStructure.USART_Parity      = USART_Parity_No;  
  62.    USART_InitStructure.USART_StopBits    = USART_StopBits_1;  
  63.    USART_InitStructure.USART_WordLength  = USART_WordLength_8b;  
  64. USART_Init(MODBUS_USART, &USART_InitStructure);  
  65.    USART_Cmd(MODBUS_USART, ENABLE);  
  66.      
  67.    /*****************************中断初始化*************************************/  
  68.  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);  
  69.  NVIC_InitStructure.NVIC_IRQChannel = MODBUS_USART_IRQ ;  
  70.  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0  
  71.  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //子优先级0  
  72.  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能  
  73.  NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器  
  74.    
  75.  return TRUE;  
  76. }  
  77. /*******************************************************************/  

 

 

05.补充串口发送函数和接收函数、中断处理函数,将STM32串口发送函数和接收函数进行封装,供协议栈使用。代码如下:

 

 

 

view plaincopy to clipboardprint?
  1. //串口发送  
  2. BOOL xMBPortSerialPutByte( CHAR ucByte )  
  3. {  
  4.     /* Put a byte in the UARTs transmit buffer. This function is called 
  5.      * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been 
  6.      * called. */  
  7.   USART_SendData(MODBUS_USART, ucByte);  //??????  
  8.   while (USART_GetFlagStatus(MODBUS_USART, USART_FLAG_TC) == RESET){};   
  9.     return TRUE;  
  10. }  
  11. /*******************************************************************/  
  12. //串口接收  
  13. BOOL xMBPortSerialGetByte( CHAR * pucByte )  
  14. {  
  15.     /* Return the byte in the UARTs receive buffer. This function is called 
  16.      * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. 
  17.      */  
  18.  *pucByte = USART_ReceiveData(MODBUS_USART);    
  19.     return TRUE;  
  20. }  
  21. /*******************************************************************/  
  22. //串口中断处理函数  
  23. void MODBUS_USART_IRQHandler(void)  
  24. {  
  25.    if(USART_GetITStatus(MODBUS_USART, USART_IT_TXE) == SET)   
  26.  {  
  27.      prvvUARTTxReadyISR();  
  28.      USART_ClearITPendingBit(MODBUS_USART, USART_IT_TXE);  
  29.    }  
  30.    
  31.    if(USART_GetITStatus(MODBUS_USART, USART_IT_RXNE) == SET)   
  32.  {  
  33.      prvvUARTRxISR();  
  34.      USART_ClearITPendingBit(MODBUS_USART, USART_IT_RXNE);  
  35.    }  
  36. }  

 

 

06.第二个需要补充的是porttimerl.c文件,需要补充的就是Modbus定时器初始化函数、Modbus定时器使能和失能函数,以及Modbus 定时器中断函数。这个对于熟悉stm32编程的就是分分中的事。具体代码如下:

 

 

 

view plaincopy to clipboardprint?
  1. //Modbus定时器初始化函数  
  2. BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )  
  3. {  
  4.   TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;  
  5. NVIC_InitTypeDef NVIC_InitStructure;  
  6.    uint16_t PrescalerValue = 0;  
  7. //使能定时器4的时钟  
  8. RCC_APB1PeriphClockCmd(MODBUS_TIM_CLK, ENABLE); //时钟使能  
  9. //定时器4时间配置说明  
  10. //HCLK为72MHz,APB1经2分频为36MHz  
  11.   //TIM4时钟倍频后为72MHz(硬件自动倍频,达到最大)  
  12.   //TIM4的分频系数为3599,时间基频率为:72 / (1 + Prescaler) = 20KHz,基准为50us  
  13.   //TIM最大计数值为:usTim1Timerout50u  
  14. PrescalerValue = (uint16_t) (SystemCoreClock / 20000) - 1;   
  15. //定时器TIM4初始化  
  16. TIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us;   
  17. TIM_TimeBaseStructure.TIM_Prescaler =PrescalerValue;  
  18.   TIM_TimeBaseStructure.TIM_ClockDivision = 0;   
  19. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;    
  20. TIM_TimeBaseInit(MODBUS_TIM, &TIM_TimeBaseStructure);   
  21. //使能预装载  
  22.   TIM_ARRPreloadConfig(MODBUS_TIM, ENABLE);  
  23.    
  24. //中断优先级NVIC设置  
  25. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);  
  26. NVIC_InitStructure.NVIC_IRQChannel = MODBUS_TIM_IRQ ;    
  27. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    
  28. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;    
  29. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   
  30. NVIC_Init(&NVIC_InitStructure);   
  31. //清除溢出中断标志位  
  32. TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update);  
  33. //定时器溢出中断关闭  
  34.   TIM_ITConfig(MODBUS_TIM,TIM_IT_Update,DISABLE);  
  35. //失能定时器  
  36. TIM_Cmd(MODBUS_TIM, DISABLE);    
  37. return TRUE;  
  38. }  
  39. /*******************************************************************/  
  40. //Modbus定时器使能函数  
  41. void vMBPortTimersEnable()  
  42. {  
  43.     /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */  
  44.  TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update);  
  45.    TIM_ITConfig(MODBUS_TIM, TIM_IT_Update, ENABLE);  
  46.  //设置定时器的初值  
  47.  TIM_SetCounter(MODBUS_TIM,0x0000);  
  48.  TIM_Cmd(MODBUS_TIM, ENABLE);  
  49. }  
  50. /*******************************************************************/  
  51. //Modbus定时器失能函数  
  52. void vMBPortTimersDisable()  
  53. {  
  54.   /* Disable any pending timers. */  
  55. TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update);  
  56.   TIM_ITConfig(MODBUS_TIM, TIM_IT_Update, DISABLE);  
  57.   TIM_SetCounter(MODBUS_TIM,0x0000);   
  58.   //关闭定时器  
  59.   TIM_Cmd(MODBUS_TIM, DISABLE);  
  60. }  
  61. /*******************************************************************/  
  62. //Modbus 定时器中断函数  
  63. void MODBUS_TIM_IRQHandler( void )  
  64. {  
  65.   if(TIM_GetITStatus(MODBUS_TIM, TIM_IT_Update) == SET)   
  66. {  
  67.      TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update);  
  68.      prvvTIMERExpiredISR();  
  69.    }  
  70. }  

 

 

07.第三个需要修改的文件是port.h文件,这里文件我放置了一些需要定义的宏,方便后面修改,万一哪一天Modbus、串口2、定时器4打起来了,那我有的忙了,一个伟大程序员总是在考虑谁会打起来,不希望出现劝架的那一天。port.h文件中主要补充进入临界区和退出临界区的宏定义、portserial.c 文件中用到的宏、porttimer.c 文件中用到的宏。(注:是补充哦!不是全部的port.h文件内容,不要傻傻的去全部替换!)

 

 

 

view plaincopy to clipboardprint?
  1. #define ENTER_CRITICAL_SECTION( )    __set_PRIMASK(1); //关闭中断  
  2. #define EXIT_CRITICAL_SECTION( )     __set_PRIMASK(0); //开启中断  
  3. //Modbus串口   
  4. #define  MODBUS_USART            USART2  
  5. #define  MODBUS_USART_CLK        RCC_APB1Periph_USART2  
  6. #define  MODBUS_USART_GPIO_CLK   RCC_APB2Periph_GPIOA  
  7. #define  MODBUS_USART_TX_PORT    GPIOA  
  8. #define  MODBUS_USART_RX_PORT    GPIOA  
  9. #define  MODBUS_USART_TX_PIN     GPIO_Pin_2  
  10. #define  MODBUS_USART_RX_PIN     GPIO_Pin_3  
  11. #define  MODBUS_USART_IRQ           USART2_IRQn  
  12. #define  MODBUS_USART_IRQHandler    USART2_IRQHandler  
  13. //Modbus定时器  
  14. #define  MODBUS_TIM              TIM4        
  15. #define  MODBUS_TIM_CLK          RCC_APB1Periph_TIM4  
  16. #define  MODBUS_TIM_IRQ          TIM4_IRQn  
  17. #define  MODBUS_TIM_IRQHandler   TIM4_IRQHandler  

 

 

08.编译之后,果然不出所料,有错误。学会修改错误,是一个程序猿的基本修养。如果连这点基本修养都没有,那只能说,路漫漫其修远兮,还得继续上下左右之求索。在这里我们尝试着去删除porttimer.c 中定时器使能和失能函数前的inline 字样,咦,奇怪的东西消失了呢。

 

图片8.png

 

09.当然困难不止一个,就像当前的新冠病毒,一波未平一波又起。再次编译,出现关于assert的错误...........,不要挠头皮,继续想法搞定吧!于是打开搜索引擎,开始找对应的解决办法的过程。

 

图片9.png

 

10. 搜索并思考了很久,偶然在CSDN上看到一位博主是这么解决的:在主函数下面添加以下代码(代码在下方),即可解决以上问题,硬着头皮先试一试(因为暂时不相信也没有其它办法),复制过来,直接编译,居然轻松解决,至于原因嘛,就留给各位读者自己思索咯。

 

 

 

view plaincopy to clipboardprint?
  1. #ifdef  USE_FULL_ASSERT  
  2. /**  
  3.   * @brief  Reports the name of the source file and the source line number  
  4.   *         where the assert_param error has occurred.  
  5.   * @param  file: pointer to the source file name  
  6.   * @param  line: assert_param error line source number  
  7.   * @retval None  
  8.   */    
  9. void assert_failed(uint8_t* file, uint32_t line)    
  10. {    
  11.   /* User can add his own implementation to report the file name and line number,  
  12.      ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */    
  13.      
  14.   /* Infinite loop */    
  15.   while (1)    
  16.   {    
  17.   }    
  18. }  
  19. #else  
  20. void __aeabi_assert(const char * x1, const char * x2, int x3)  
  21. {  
  22. }  
  23. #endif  

 

好了各位小伙伴,到这里FreeModbus已经移植到STM32平台上了,下一步就是测试验证移植的FreeModbus是否可以正常通讯。(验证方式和全部代码下载,请见下一帖)

 

 

4.freemodbus在stm32平台测试与调试

 

一个不会打仗的士兵不是好士兵,整天在兵营里吹牛x,偶尔还放空枪,这样的还放下狠话,要灭人家一个师,就没见过这么厚脸皮的。我们要学习独立团团长李云龙,别扯那多没有用的,有真本事“亮剑”见识一下。So,我们今天就来试一试移植的效果,一起调一调主函数。

 

先来分析一下,想要能请得动FreeModbus这位大佬,要想让它为我们服务,首先需要给它伺候舒服了,也就是需要先对FreeModbus进行初始化。初始化的步骤其实就是调用eMBInit函数和eMBEnable函数,温馨补充一下照顾一下腼腆的孩子们:eMBInit函数函数是设置硬件的,有五个参数,分别是:

 

参数1.Modbus的模式:这里可以选择RTU、ASCII、TCP,这里我们选择RTU模式(MB_RTU);

 

参数2.设备的地址:这里可以直接写死(0x01),也可以通过拨码开关来设置;

 

参数3.Modbus选择的串口号:这里默认是串口2(0x02);

 

参数4.波特率:默认写115200,我们这里配置为9600;

 

参数5.校验位:默认不效验(MB_PAR_NONE)。

 

然后在主循环中调用eMBPoll函数不断查询。int main()函数具体代码如下:

 

 

 

view plaincopy to clipboardprint?
  1. int main(void)  
  2. {   
  3.   delay_init();     //延时函数初始化       
  4.   //初始化 RTU模式 从机地址为1 USART2 9600 无效验  
  5.   eMBInit(MB_RTU, 0x01, 0x02, 9600, MB_PAR_NONE);  
  6.   //启动FreeModbus   
  7.   eMBEnable();  
  8.   while(1)  
  9.   {    
  10.    //FreeMODBUS不断查询  
  11.    eMBPoll();   
  12.   }  
  13. }  

 

 

 

 

01.当然,仅仅拿个main函数出来,是不能够证明FreeModbus就跑起来了,我们再来添加点测试部分代码,以观其效吧!测试代码部分,主要添加输入寄存器、保持寄存器、线圈的起始地址和定义的内容,以及输入寄存器、保持寄存器、线圈的处理函数,具体如下:

 

 

 

view plaincopy to clipboardprint?
  1. /* ----------------------- Defines ------------------------------------------*/  
  2. //添加输入寄存器、保持寄存器、线圈的起始地址和定义的内容  
  3. //保持寄存器起始地址  
  4. #define REG_HOLDING_START 0x0001  
  5. //保持寄存器数量  
  6. #define REG_HOLDING_NREGS 8  
  7. //保持寄存器内容  
  8. uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ={0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};  
  9.    
  10. //输入寄存器起始地址  
  11. #define REG_INPUT_START 0x0001  
  12. //输入寄存器数量  
  13. #define REG_INPUT_NREGS 8  
  14. //输入寄存器内容  
  15. uint16_t usRegInputBuf[REG_INPUT_NREGS] ={0x1111,0x2222,0x3333,0x4444,0x5555,0x6666,0x7777,0x8888};  
  16.    
  17. //线圈寄存器起始地址  
  18. #define REG_COILS_START 0x0001  
  19. //线圈寄存器数量  
  20. #define REG_COILS_SIZE 16  
  21. //线圈寄存器内容  
  22. uint8_t ucRegCoilsBuf[REG_COILS_SIZE/8] = {0x00,0xFF};  
  23. /***********************************************************************/  
  24. 输入寄存器、保持寄存器、线圈的处理函数  
  25. // 保持寄存器的读写函数 支持的命令为读 0x03 和写0x06 可读可写    
  26. eMBErrorCode   
  27. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,eMBRegisterMode eMode )  
  28. {  
  29.   //错误状态  
  30.   eMBErrorCode eStatus = MB_ENOERR;  
  31.   //偏移量  
  32.   int16_t iRegIndex;  
  33.     
  34. //判断寄存器是不是在范围内  
  35.   if( ( (int16_t)usAddress >= REG_HOLDING_START )&& ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )  
  36.   {  
  37.     //计算偏移量  
  38.     iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);  
  39.     switch ( eMode )  
  40.    {  
  41.      //读处理函数  
  42.       case MB_REG_READ:  
  43. while( usNRegs > 0 )  
  44. {  
  45. *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );  
  46. *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );  
  47. iRegIndex++;  
  48. usNRegs--;  
  49. }  
  50. break;  
  51.    
  52.     //写处理函数  
  53.      case MB_REG_WRITE:  
  54. while( usNRegs > 0 )  
  55. {  
  56. usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;  
  57. usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;  
  58. iRegIndex++;  
  59. usNRegs--;  
  60. }  
  61.  break;  
  62.   }  
  63. }  
  64. else  
  65. {  
  66. //返回错误状态  
  67. eStatus = MB_ENOREG;  
  68. }  
  69.    
  70. return eStatus;  
  71. }  
  72.    
  73. //读输入寄存器函数 支持的命令为读 0x04   
  74. eMBErrorCode  
  75. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )  
  76. {  
  77.     eMBErrorCode    eStatus = MB_ENOERR;  
  78.     int             iRegIndex;  
  79.    
  80.     if( ( usAddress >= REG_INPUT_START )  
  81.         && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )  
  82.     {  
  83.         iRegIndex = ( int )( usAddress - REG_INPUT_START );  
  84.         while( usNRegs > 0 )  
  85.         {  
  86.             *pucRegBuffer++ =  
  87.                 ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );  
  88.             *pucRegBuffer++ =  
  89.                 ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );  
  90.             iRegIndex++;  
  91.             usNRegs--;  
  92.         }  
  93.     }  
  94.     else  
  95.     {  
  96.         eStatus = MB_ENOREG;  
  97.     }  
  98.    
  99.     return eStatus;  
  100. }  
  101.    
  102. //线圈处理函数 可读可写 支持的命令为读 0x01 0x05  
  103. eMBErrorCode  
  104. eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,  
  105.                eMBRegisterMode eMode )  
  106. {  
  107.     //错误状态  
  108.     eMBErrorCode eStatus = MB_ENOERR;  
  109.     //寄存器个数  
  110.     int16_t iNCoils = ( int16_t )usNCoils;  
  111.     //寄存器偏移量  
  112.     int16_t usBitOffset;  
  113.    
  114.    //判断寄存器是不是在范围内  
  115.    if( ( (int16_t)usAddress >= REG_COILS_START ) &&( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )  
  116.    {  
  117.      //计算寄存器偏移量  
  118.      usBitOffset = ( int16_t )( usAddress - REG_COILS_START );  
  119.      switch ( eMode )  
  120.      {  
  121. //读操作  
  122. case MB_REG_READ:  
  123. while( iNCoils > 0 )  
  124. {  
  125. *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,  
  126. ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );  
  127. iNCoils -= 8;  
  128. usBitOffset += 8;  
  129. }  
  130.   break;  
  131.    
  132. //写操作  
  133. case MB_REG_WRITE:  
  134. while( iNCoils > 0 )  
  135. {  
  136. xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,  
  137. ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),  
  138. *pucRegBuffer++ );  
  139. iNCoils -= 8;  
  140. }  
  141. break;  
  142.     }  
  143.   }  
  144. else  
  145. {  
  146.    eStatus = MB_ENOREG;  
  147. }  
  148. return eStatus;   
  149.  return MB_ENOREG;  
  150. }  

 

 

 

 

02.好了,测试代码准备就绪,下面就是见证奇迹的时刻?我们使用ModbusPoll软件测试版来完成对移植FreeModbus rtu的测试,如下图,该代码能够成功的读出输入寄存器、保持寄存器、线圈的数据。

 

图片10.png

 

03. 通信正常,则可以看到USB转串口工具的TX和RX指示灯不停闪烁着。想象一下突然有一天,人类的眼睛可以分辨出100MHz以上的闪烁频率时,我们的世界将会如何?emmmm....这个脑洞可能开的有点大,留给大家去发挥想象力吧。如果我们能够分辨更高的频率,那么我们的电脑手机显示器的144Hz的刷新率,就再也不香了吧,哈哈~!

 

图片11.png

 

 

 

综上可以看出,本次对FreeModbus的移植工作已经完成了,该平台虽然是基于STM32F1xx,但是使用其他STM32的芯片也非常的方便,过程大致相同,简单改一下就好了。从通过ModbusPoll工具进行测试的结果看来,整个工程基本能够达到预期效果,当然本人技术有限,还请各位大佬们不吝赐教。最后贴上main测试完整代码供大家参考。

 

 

 

view plaincopy to clipboardprint?
  1. #include "sys.h"  
  2. #include "delay.h"  
  3. #include "usart.h"  
  4. #include "led.h"  
  5.    
  6. #include "mb.h"  
  7. #include "mbport.h"  
  8.    
  9. /* ----------------------- Defines ------------------------------------------*/  
  10. //保持寄存器起始地址  
  11. #define REG_HOLDING_START 0x0001  
  12. //保持寄存器数量  
  13. #define REG_HOLDING_NREGS 8  
  14. //保持寄存器内容  
  15. uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] ={0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};  
  16.    
  17. //输入寄存器起始地址  
  18. #define REG_INPUT_START 0x0001  
  19. //输入寄存器数量  
  20. #define REG_INPUT_NREGS 8  
  21. //输入寄存器内容  
  22. uint16_t usRegInputBuf[REG_INPUT_NREGS] ={0x1111,0x2222,0x3333,0x4444,0x5555,0x6666,0x7777,0x8888};  
  23.    
  24. //线圈寄存器起始地址  
  25. #define REG_COILS_START 0x0001  
  26. //线圈寄存器数量  
  27. #define REG_COILS_SIZE 16  
  28. //线圈寄存器内容  
  29. uint8_t ucRegCoilsBuf[REG_COILS_SIZE/8] = {0x00,0xFF};  
  30.    
  31.  int main(void)  
  32.  {   
  33. delay_init();     //延时函数初始化      
  34. //初始化 RTU模式 从机地址为1 USART2 9600 无效验  
  35. eMBInit(MB_RTU, 0x01, 0x02, 9600, MB_PAR_NONE);  
  36. //启动FreeModbus   
  37. eMBEnable();  
  38. while(1)  
  39. {    
  40.  //FreeMODBUS不断查询  
  41.     eMBPoll();   
  42. }  
  43. }  
  44.    
  45. // 保持寄存器的读写函数 支持的命令为读 0x03 和写0x06 可读可写    
  46. eMBErrorCode   
  47. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,eMBRegisterMode eMode )  
  48. {  
  49.   //错误状态  
  50.   eMBErrorCode eStatus = MB_ENOERR;  
  51.   //偏移量  
  52.   int16_t iRegIndex;  
  53.     
  54. //判断寄存器是不是在范围内  
  55.   if( ( (int16_t)usAddress >= REG_HOLDING_START )&& ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )  
  56.   {  
  57.     //计算偏移量  
  58.     iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);  
  59.     switch ( eMode )  
  60.    {  
  61.      //读处理函数  
  62.       case MB_REG_READ:  
  63. while( usNRegs > 0 )  
  64. {  
  65. *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] >> 8 );  
  66. *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );  
  67. iRegIndex++;  
  68. usNRegs--;  
  69. }  
  70. break;  
  71.    
  72.     //写处理函数  
  73.      case MB_REG_WRITE:  
  74. while( usNRegs > 0 )  
  75. {  
  76. usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;  
  77. usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;  
  78. iRegIndex++;  
  79. usNRegs--;  
  80. }  
  81.  break;  
  82.   }  
  83. }  
  84. else  
  85. {  
  86. //返回错误状态  
  87. eStatus = MB_ENOREG;  
  88. }  
  89.    
  90. return eStatus;  
  91. }  
  92.    
  93. //读输入寄存器函数 支持的命令为读 0x04   
  94. eMBErrorCode  
  95. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )  
  96. {  
  97.     eMBErrorCode    eStatus = MB_ENOERR;  
  98.     int             iRegIndex;  
  99.    
  100.     if( ( usAddress >= REG_INPUT_START )  
  101.         && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )  
  102.     {  
  103.         iRegIndex = ( int )( usAddress - REG_INPUT_START );  
  104.         while( usNRegs > 0 )  
  105.         {  
  106.             *pucRegBuffer++ =  
  107.                 ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );  
  108.             *pucRegBuffer++ =  
  109.                 ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );  
  110.             iRegIndex++;  
  111.             usNRegs--;  
  112.         }  
  113.     }  
  114.     else  
  115.     {  
  116.         eStatus = MB_ENOREG;  
  117.     }  
  118.    
  119.     return eStatus;  
  120. }  
  121.    
  122. //线圈处理函数 可读可写 支持的命令为读 0x01 0x05  
  123. eMBErrorCode  
  124. eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,  
  125.                eMBRegisterMode eMode )  
  126. {  
  127.     //错误状态  
  128.     eMBErrorCode eStatus = MB_ENOERR;  
  129.     //寄存器个数  
  130.     int16_t iNCoils = ( int16_t )usNCoils;  
  131.     //寄存器偏移量  
  132.     int16_t usBitOffset;  
  133.    
  134.    //判断寄存器是不是在范围内  
  135.    if( ( (int16_t)usAddress >= REG_COILS_START ) &&( usAddress + usNCoils <= REG_COILS_START + REG_COILS_SIZE ) )  
  136.    {  
  137.      //计算寄存器偏移量  
  138.      usBitOffset = ( int16_t )( usAddress - REG_COILS_START );  
  139.      switch ( eMode )  
  140.      {  
  141. //读操作  
  142. case MB_REG_READ:  
  143. while( iNCoils > 0 )  
  144. {  
  145. *pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,  
  146. ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ) );  
  147. iNCoils -= 8;  
  148. usBitOffset += 8;  
  149. }  
  150.   break;  
  151.    
  152. //写操作  
  153. case MB_REG_WRITE:  
  154. while( iNCoils > 0 )  
  155. {  
  156. xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,  
  157. ( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),  
  158. *pucRegBuffer++ );  
  159. iNCoils -= 8;  
  160. }  
  161. break;  
  162.     }  
  163.   }  
  164. else  
  165. {  
  166.    eStatus = MB_ENOREG;  
  167. }  
  168. return eStatus;   
  169.  return MB_ENOREG;  
  170. }  
  171.    
  172.    
  173. //开关输入寄存器处理函数 可读 支持的命令为读 0x02  
  174. eMBErrorCode  
  175. eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )  
  176. {  
  177.     return MB_ENOREG;  
  178. }  
  179.    
  180. //针对 assert错误添加的函数  
  181. #ifdef  USE_FULL_ASSERT  
  182. /**  
  183.   * @brief  Reports the name of the source file and the source line number  
  184.   *         where the assert_param error has occurred.  
  185.   * @param  file: pointer to the source file name  
  186.   * @param  line: assert_param error line source number  
  187.   * @retval None  
  188.   */    
  189. void assert_failed(uint8_t* file, uint32_t line)    
  190. {    
  191.   /* User can add his own implementation to report the file name and line number,  
  192.      ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */    
  193.      
  194.   /* Infinite loop */    
  195.   while (1)    
  196.   {    
  197.   }    
  198. }  
  199. #else  
  200. void __aeabi_assert(const char * x1, const char * x2, int x3)  
  201. {  
  202. }  
  203. #endif  

 

 

 

本系列到此就全部结束了,大家好,我是大Z,感谢大家支持~

 

标签:MODBUS,USART,Modbus,手把手,STM32,TIM,FreeModbus,寄存器,REG
来源: https://www.cnblogs.com/mangorange/p/14228119.html