这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界» 论坛首页» 高校专区» 坤创E-Geek/天科大新电社» 【原创】手把手教你移植FreeModbus到STM32(二)

共14条 1/2 1 2 跳转至

【原创】手把手教你移植FreeModbus到STM32(二)

高工
2020-04-21 12:01:20 打赏

------------------紧接上一帖----------------

3.01移植freemodbusstm32平台

移植之前需要准备: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,其实两个代码几乎一致

//该函数实现STM32串口发送中断和接收中断使能 void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { //STM32串口 接收中断使能 if(xRxEnable==TRUE) { //使能接收和接收中断 USART_ITConfig(MODBUS_USART, USART_IT_RXNE, ENABLE); } else if(xRxEnable == FALSE) { //禁止接收和接收中断 USART_ITConfig(MODBUS_USART, USART_IT_RXNE, DISABLE); } //STM32串口 发送中断使能 if(xTxEnable==TRUE) { //使能发送完成中断 USART_ITConfig(MODBUS_USART, USART_IT_TXE, ENABLE); } else if(xTxEnable == FALSE) { //禁止发送完成中断 USART_ITConfig(MODBUS_USART, USART_IT_TXE, DISABLE); } } else if(xTxEnable == FALSE) { MODBUS_RECIEVE(); USART_ITConfig(MODBUS_USART, USART_IT_TC, DISABLE); } } /*******************************************************************/ //对串口进行初始化由eMBRTUInt函数进行调用 BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; (void)ucPORT; //不修改串口 (void)ucDataBits; //不修改数据长度 (void)eParity; //不许改效验格式 /***引脚初始化*************************************/ //时钟使能 RCC_APB2PeriphClockCmd(MODBUS_USART_GPIO_CLK,ENABLE); RCC_APB1PeriphClockCmd(MODBUS_USART_CLK,ENABLE); //TX GPIO_InitStructure.GPIO_Pin = MODBUS_USART_TX_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(MODBUS_USART_TX_PORT, &GPIO_InitStructure); //RX GPIO_InitStructure.GPIO_Pin = MODBUS_USART_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入 GPIO_Init(MODBUS_USART_RX_PORT, &GPIO_InitStructure); /***************串口初始化********************/ USART_InitStructure.USART_BaudRate = ulBaudRate; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(MODBUS_USART, &USART_InitStructure); USART_Cmd(MODBUS_USART, ENABLE); /*****************************中断初始化*************************************/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = MODBUS_USART_IRQ ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 return TRUE; } /*******************************************************************/


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

//串口发送 BOOL xMBPortSerialPutByte( CHAR ucByte ) { /* Put a byte in the UARTs transmit buffer. This function is called * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been * called. */ USART_SendData(MODBUS_USART, ucByte); //?????? while (USART_GetFlagStatus(MODBUS_USART, USART_FLAG_TC) == RESET){}; return TRUE; } /*******************************************************************/ //串口接收 BOOL xMBPortSerialGetByte( CHAR * pucByte ) { /* Return the byte in the UARTs receive buffer. This function is called * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. */ *pucByte = USART_ReceiveData(MODBUS_USART); return TRUE; } /*******************************************************************/ //串口中断处理函数 void MODBUS_USART_IRQHandler(void) { if(USART_GetITStatus(MODBUS_USART, USART_IT_TXE) == SET) { prvvUARTTxReadyISR(); USART_ClearITPendingBit(MODBUS_USART, USART_IT_TXE); } if(USART_GetITStatus(MODBUS_USART, USART_IT_RXNE) == SET) { prvvUARTRxISR(); USART_ClearITPendingBit(MODBUS_USART, USART_IT_RXNE); } }


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

//Modbus定时器初始化函数 BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; uint16_t PrescalerValue = 0; //使能定时器4的时钟 RCC_APB1PeriphClockCmd(MODBUS_TIM_CLK, ENABLE); //时钟使能 //定时器4时间配置说明 //HCLK为72MHz,APB1经2分频为36MHz //TIM4时钟倍频后为72MHz(硬件自动倍频,达到最大) //TIM4的分频系数为3599,时间基频率为:72 / (1 + Prescaler) = 20KHz,基准为50us //TIM最大计数值为:usTim1Timerout50u PrescalerValue = (uint16_t) (SystemCoreClock / 20000) - 1; //定时器TIM4初始化 TIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us; TIM_TimeBaseStructure.TIM_Prescaler =PrescalerValue; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(MODBUS_TIM, &TIM_TimeBaseStructure); //使能预装载 TIM_ARRPreloadConfig(MODBUS_TIM, ENABLE); //中断优先级NVIC设置 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = MODBUS_TIM_IRQ ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); //清除溢出中断标志位 TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update); //定时器溢出中断关闭 TIM_ITConfig(MODBUS_TIM,TIM_IT_Update,DISABLE); //失能定时器 TIM_Cmd(MODBUS_TIM, DISABLE); return TRUE; } /*******************************************************************/ //Modbus定时器使能函数 void vMBPortTimersEnable() { /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update); TIM_ITConfig(MODBUS_TIM, TIM_IT_Update, ENABLE); //设置定时器的初值 TIM_SetCounter(MODBUS_TIM,0x0000); TIM_Cmd(MODBUS_TIM, ENABLE); } /*******************************************************************/ //Modbus定时器失能函数 void vMBPortTimersDisable() { /* Disable any pending timers. */ TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update); TIM_ITConfig(MODBUS_TIM, TIM_IT_Update, DISABLE); TIM_SetCounter(MODBUS_TIM,0x0000); //关闭定时器 TIM_Cmd(MODBUS_TIM, DISABLE); } /*******************************************************************/ //Modbus 定时器中断函数 void MODBUS_TIM_IRQHandler( void ) { if(TIM_GetITStatus(MODBUS_TIM, TIM_IT_Update) == SET) { TIM_ClearITPendingBit(MODBUS_TIM, TIM_IT_Update); prvvTIMERExpiredISR(); } }


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

#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1); //关闭中断 #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0); //开启中断 //Modbus串口 #define MODBUS_USART USART2 #define MODBUS_USART_CLK RCC_APB1Periph_USART2 #define MODBUS_USART_GPIO_CLK RCC_APB2Periph_GPIOA #define MODBUS_USART_TX_PORT GPIOA #define MODBUS_USART_RX_PORT GPIOA #define MODBUS_USART_TX_PIN GPIO_Pin_2 #define MODBUS_USART_RX_PIN GPIO_Pin_3 #define MODBUS_USART_IRQ USART2_IRQn #define MODBUS_USART_IRQHandler USART2_IRQHandler //Modbus定时器 #define MODBUS_TIM TIM4 #define MODBUS_TIM_CLK RCC_APB1Periph_TIM4 #define MODBUS_TIM_IRQ TIM4_IRQn #define MODBUS_TIM_IRQHandler TIM4_IRQHandler


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

图片8.png

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

图片9.png

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

#ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } } #else void __aeabi_assert(const char * x1, const char * x2, int x3) { } #endif


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

----未完待续---- 等你来哦





关键词: FreeModbus 移植 STM32 Modbu

管理员
2020-04-21 15:50:31 打赏
2楼

祝老师,代码可以用下面这个文本框试试

按钮在工具栏的【代码语言】

比如,截取一部分

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { //STM32串口 接收中断使能 if(xRxEnable==TRUE) { //使能接收和接收中断 USART_ITConfig(MODBUS_USART, USART_IT_RXNE, ENABLE); } else if(xRxEnable == FALSE) { //禁止接收和接收中断 USART_ITConfig(MODBUS_USART, USART_IT_RXNE, DISABLE); } ......

院士
2020-04-22 10:22:44 打赏
3楼

我觉得 祝老师这个代码框挺好看的


专家
2020-04-26 21:52:50 打赏
4楼

文字写的轻松又有内涵,赞



菜鸟
2020-10-23 21:10:27 打赏
5楼

这里第一个代码区。大括号多了一个。

  1. //该函数实现STM32串口发送中断和接收中断使能

  2. voidvMBPortSerialEnable(BOOLxRxEnable,BOOLxTxEnable )

  3. {

  4. //STM32串口 接收中断使能

  5. if(xRxEnable==TRUE)

  6. {

  7. //使能接收和接收中断

  8. USART_ITConfig(MODBUS_USART, USART_IT_RXNE, ENABLE);

  9. }

  10. elseif(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. elseif(xTxEnable == FALSE)

  22. {

  23. //禁止发送完成中断

  24. USART_ITConfig(MODBUS_USART, USART_IT_TXE, DISABLE);

  25. }

  26. }elseif(xTxEnable == FALSE)

  27. {

  28. MODBUS_RECIEVE();

  29. USART_ITConfig(MODBUS_USART, USART_IT_TC, DISABLE);

  30. }

  31. }



菜鸟
2021-01-23 15:22:56 打赏
6楼

真不错!


菜鸟
2022-01-20 21:44:13 打赏
7楼

哈喽!文章很不错,能发一下这个工程么,谢谢了


专家
2022-01-20 21:46:43 打赏
8楼

看看


菜鸟
2022-05-17 07:55:37 打赏
9楼

请教老师:

MODBUS_RECIEVE()

这个函数的实现在哪里?


助工
2022-05-17 08:28:55 打赏
10楼

谢谢分享


共14条 1/2 1 2 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册]