FreeModbus是Modbus的一个被广泛移植的实现。其源码在github,最新版是1.6。

FreeModbus支持Modbus功能码里的0x01~0x06,0x0F~0x11和0x17,对一些功能比如异常诊断和读事件计数等功能码并没有提供支持。这些没有实现的功能码其实都可以用输入寄存器或保持寄存器来实现,所以并不影响Modbus的使用。

另外,FreeModbus并没有提供主机的实现,github上有一些对主机模式的移植。

移植的文件主要在两个文件夹里,一个在/modbus/,一个在/demo/BARE/。前一个文件夹是协议的上层功能,包括协议的几个应用函数,基本不用修改。第二个文件夹有一个demo.c,是一个运行示例,也不需要修改。需要修改的是/demo/BARE/port/里的内容,这几个文件的功能是完成硬件的配置。主要修改的部分包括串口和定时器的初始化,以及中断函数。

对于STM32,源码并没有给出移植好的示例,不过网上的相关移植已经有很多了,很容易就能找到,所以只需要复制/demo/BARE/port里的内容,然后稍微修改一下就好了。

一个个文件来说。

第一个是port.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函数即可。注意到STM32F103的manual中讲到,TC不需要软清零,只需要读一下TC位,下次写TDR寄存器时就会自动清零;如果手动清零了TC位,那么下次要发送时,会因为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的运行情况,配置如下:

移植Modbus到STM32F103(2):移植FreeModbus到usart3并运行示例代码-LMLPHP

移植Modbus到STM32F103(2):移植FreeModbus到usart3并运行示例代码-LMLPHP

结果应该能看到地址为00999的寄存器数字在变化,其他两位都是0,说明已经移植成功了。

02-22 03:57