移植Modbus到STM32F103(2):移植FreeModbus到usart3并运行示例代码
时间: 2019-02-22来源:OSCHINA
前景提要
FreeModbus是Modbus的一个被广泛移植的实现。其源码在 github ,最新版是1.6。
FreeModbus支持Modbus功能码里的0x01~0x06,0x0F~0x11和0x17,对其他功能码比如异常诊断和事件计数等并没有提供支持,但并不影响Modbus的使用。
另外,FreeModbus仅提供了服务器(从机)的实现,客户端(主机)的实现可以在github上找到一些。
FreeModbus的文件主要在两个文件夹里,一个在/modbus/,一个在/demo/BARE/。前一个文件夹是协议的上层功能,包括协议的几个应用函数,基本不用修改。第二个文件夹有一个demo.c,是一个运行示例,也不需要修改。需要修改的是/demo/BARE/port/里的内容,这几个文件的功能完成了硬件的配置,包括串口和定时器的初始化,以及中断函数等。
对于STM32,FreeModbus的源码并没有给出示例,不过网上的相关移植已经有很多了,很容易就能找到,所以只需要复制/demo/BARE/port里的内容,然后稍微修改一下就好了。
一个个文件来说。
第一个是port.h,只需要包含库文件"stm32f10x.h",并增加临界区的进入和离开指令即可。我这里使用的是__set_PRIMASK函数,关闭除了NMI和硬fault以外的中断。 #define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1); //disable interrupts #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0); //enable interrupts
第二个是portserial.c,需要填满几个串口的相关函数。
使能或失能串口发送完成中断和串口接收中断: void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable ) { /* If xRXEnable enable serial receive interrupts. If xTxENable enable * transmitter empty interrupts. */ if (xRxEnable == TRUE) { USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); } else if (xRxEnable == FALSE) { USART_ITConfig(USART3, USART_IT_RXNE, DISABLE); } if (xTxEnable == TRUE) { USART_ITConfig(USART3, USART_IT_TC, ENABLE); } else if (xTxEnable == FALSE) { USART_ITConfig(USART3, USART_IT_TC, DISABLE); } }
初始化串口3和相应引脚,初始化NVIC。为了方便,这里就不管串口号、数据位、校验位了,只有波特率可以起作用: BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStructure); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate = ulBaudRate; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART3, &USART_InitStructure); USART_Cmd(USART3, ENABLE); vMBPortSerialEnable(FALSE, FALSE); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_Init(&NVIC_InitStructure); return TRUE; }
串口发送和接收: 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(USART3, ucByte); 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(USART3); return TRUE; }
串口中断函数,只需要判断中断类型,并调用FreeModbus已经实现好的prvvUARTTxReadyISR和prvvUARTRxISR函数即可。注意到发送状态机在发送完毕后会关闭串口中断,所以不需要清零TC位;STM32F103的manual中讲到,TC不需要软清零,只需要读一下TC位,下次写TDR寄存器时就会自动清零;如果手动清零了TC位,那么下次要发送时,即使使能了TCIE,会因为TC=0无法进入中断。所以这里只读了一下TC寄存器,并不清零。另外因为STM32F103使能RXNE后,ORE也会触发中断,所以需要做相应的清除操作,否则就会卡在中断里: void USART3_IRQHandler( void ) { if(USART_GetITStatus(USART3, USART_IT_TC) == SET) { prvvUARTTxReadyISR(); } if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET) { prvvUARTRxISR(); } else { USART_ClearITPendingBit(USART3, USART_IT_ORE); } }
第三个是porttimer.c,也有几个函数需要填空。
初始化定时器和NVIC。这里用的是定时器2,挂在APB1上,我的APB1总线时钟是36MHz,所以预分频器设为36MHz/20kHz=1800: BOOL xMBPortTimersInit( USHORT usTim1Timerout50us ) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us - 1; TIM_TimeBaseStructure.TIM_Prescaler = 1800 - 1; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); vMBPortTimersDisable(); NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_Init(&NVIC_InitStructure); return TRUE; }
使能定时器(注意到这两个函数定义成了内嵌函数,可以让定时器启动和关闭更快): inline void vMBPortTimersEnable( ) { /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */ vMBPortTimersDisable(); TIM_Cmd(TIM2, DISABLE); TIM_SetCounter(TIM2, 0); TIM_ClearFlag(TIM2, TIM_FLAG_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); }
失能定时器: inline void vMBPortTimersDisable( ) { /* Disable any pending timers. */ TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE); TIM_Cmd(TIM2, DISABLE); }
定时器中断函数: void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) { TIM_ClearFlag(TIM2, TIM_FLAG_Update); prvvTIMERExpiredISR(); } }
使用Modbus Poll来测试demo.c的运行情况,配置如下:


结果应该能看到地址为999的寄存器数据在变化,其他两个寄存器值都是0,说明协议已经移植成功了。

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

热门排行