------------------紧接上一帖----------------
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文件内的具体内容如下:
02.使用MDK打开基础工程,向工程中添加Modbus分组,将Modbus文件夹下对应的文件都添加到Modbus分组中,主要包括:ascii文件夹的mbascii.c文件;function文件夹下的所有.c文件;rtu和tcp文件夹下的.c文件;mb.c文件。添加后的效果如下:
03.添加对应的头文件,工程需要添加以下文件夹:ascii文件夹、BARE/port文件夹、include文件夹、rtu文件夹、tcp文件夹;总之有.h的文件夹统统包含,具体如下:
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 字样,咦,奇怪的东西消失了呢。
09.当然困难不止一个,就像当前的新冠病毒,一波未平一波又起。再次编译,出现关于assert的错误...........,不要挠头皮,继续想法搞定吧!于是打开搜索引擎,开始找对应的解决办法的过程。
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是否可以正常通讯。(验证方式和全部代码下载,请见下一帖)
----未完待续---- 等你来哦