FreeModbus是Modbus的一個被普遍移植的實現。其源碼在github,最新版是1.6。git
FreeModbus支持Modbus功能碼裏的0x01~0x06,0x0F~0x11和0x17,對其餘功能碼好比異常診斷和事件計數等並無提供支持,但並不影響Modbus的使用。github
另外,FreeModbus僅提供了服務器(從機)的實現,客戶端(主機)的實現能夠在github上找到一些。服務器
FreeModbus的文件主要在兩個文件夾裏,一個在/modbus/,一個在/demo/BARE/。前一個文件夾是協議的上層功能,包括協議的幾個應用函數,基本不用修改。第二個文件夾有一個demo.c,是一個運行示例,也不須要修改。須要修改的是/demo/BARE/port/裏的內容,這幾個文件的功能完成了硬件的配置,包括串口和定時器的初始化,以及中斷函數等。函數
對於STM32,FreeModbus的源碼並無給出示例,不過網上的相關移植已經有不少了,很容易就能找到,因此只須要複製/demo/BARE/port裏的內容,而後稍微修改一下就行了。測試
一個個文件來講。3d
第一個是port.h,只須要包含庫文件"stm32f10x.h",並增長臨界區的進入和離開指令便可。我這裏使用的是__set_PRIMASK函數,關閉除了NMI和硬fault之外的中斷。code
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1); //disable interrupts #define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0); //enable interrupts
第二個是portserial.c,須要填滿幾個串口的相關函數。blog
使能或失能串口發送完成中斷和串口接收中斷:事件
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。爲了方便,這裏就無論串口號、數據位、校驗位了,只有波特率能夠起做用:ip
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,說明協議已經移植成功了。