移植Modbus到STM32F103(2):移植FreeModbus到usart3並運行示例代碼

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,說明協議已經移植成功了。

相關文章
相關標籤/搜索