[应用相关]

STM32 FreeModbus RTU从机移植

[复制链接]
2285|19
手机看帖
扫描二维码
随时随地手机跟帖
gaoke231|  楼主 | 2018-8-19 23:20 | 显示全部楼层 |阅读模式
FreeModbus的具体介绍就不提了。至于为什么要移植,大概就是因为移植比较快,而且比较稳定,可以减少因为自己编写出现的漏洞。
但是FreeModbus 1.5版本是没有主机的,因此移植的时候只可以做从机。
gaoke231|  楼主 | 2018-8-19 23:20 | 显示全部楼层
移植过程:
1.将modbus目录下所有文件拷贝加入工程。
2.对modbus中的include下的mbconfig.h进行编辑,裁剪其中需要的模块。(此处我没有进行裁剪,因此选项都是默认)
3.将demo中的合适的port文件夹下的文件加入工程。
4.修改port文件夹下的代码,移植UART驱动。
5.使用modbus poll调试。

使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:22 | 显示全部楼层

因为没有配置mbconfig.h,因此直接从port的移植开始说。

port.c文件如下,port.c中的两个函数作用是开关总中断并保存。

#include "stm32f10x.h"

void EnterCriticalSection(  )
{
    __set_PRIMASK(1);
}

void ExitCriticalSection(  )
{
    __set_PRIMASK(0);
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:26 | 显示全部楼层
port.h只要是针对一些类型的跨平台支持。
#ifndef _PORT_H
#define _PORT_H

#include "stm32f10x.h"
#include <assert.h>
#include <inttypes.h>

#define INLINE
#define PR_BEGIN_EXTERN_C           extern "C" {
#define PR_END_EXTERN_C             }

#define ENTER_CRITICAL_SECTION( )       EnterCriticalSection( )
#define EXIT_CRITICAL_SECTION( )    ExitCriticalSection( )

void            EnterCriticalSection( void );
void            ExitCriticalSection( void );


#ifndef TRUE
#define TRUE            1
#endif

#ifndef FALSE
#define FALSE           0
#endif

typedef u8 UCHAR;
typedef u16 USHORT;
typedef u8 BOOL;
typedef u32 ULONG;
typedef char CHAR;
typedef long LONG;
typedef short SHORT;
typedef int INT;

#endif


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:27 | 显示全部楼层
portevent.c没有改动,作用是起到一个简单的事件队列。
#include "mb.h"                  
#include "mbport.h"

/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL     xEventInQueue;

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
    xEventInQueue = FALSE;
    return TRUE;
}

BOOL
xMBPortEventPost( eMBEventType eEvent )
{
    xEventInQueue = TRUE;
    eQueuedEvent = eEvent;
    return TRUE;
}

BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
    BOOL            xEventHappened = FALSE;

    if( xEventInQueue )
    {
        *eEvent = eQueuedEvent;
        xEventInQueue = FALSE;
        xEventHappened = TRUE;
    }
    return xEventHappened;
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:28 | 显示全部楼层

portserial.c

1).vMBPortSerialEnable 函数的作用是单独禁止或开启发送或接受中断。在此处有人推荐使用发送完成中断而并非发送缓存为0中断。想想是有道理的,这里还在测试阶段,因此还没有去修改。

void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
    if(xRxEnable)
    {
        USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
        DE1 = 0;
    }
    else
    {
        USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);
        DE1 = 1;
    }   
    if(xTxEnable)
    {
        USART_ITConfig(USART2, USART_IT_TXE, ENABLE);
        prvvUARTTxReadyISR();
    }
    else
        USART_ITConfig(USART2, USART_IT_TXE, DISABLE);
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:29 | 显示全部楼层
(2).vMBPortClose 在Demo的源代码中没有编写,我也没有移植。

使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:29 | 显示全部楼层
3).xMBPortSerialInit 是串口初始化的函数,其中只要把对应需要的串口移植添加进来就可以了。串口初始化程序很简单。设置一下数据位和校验还有波特率就可以了。此处我只针对了UART2移植,因此没有加入其他的串口初始化。
BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
    BOOL bInitialized = TRUE;
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef  USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    (void)ucPORT;
    (void)ucDataBits;
    (void)eParity;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);//使能GPIO外设时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;             //RX2
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;     
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;           //TX2
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;           //DE
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

/*************************************************************************************/

    USART_InitStructure.USART_BaudRate = ulBaudRate;     

    USART_InitStructure.USART_StopBits = USART_StopBits_1;   
//  USART_InitStructure.USART_Parity = USART_Parity_No; //设置奇校验时,通信出现错误

    switch(eParity)
    {
    case MB_PAR_NONE:USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_WordLength = USART_WordLength_8b; break;
    case MB_PAR_ODD:USART_InitStructure.USART_Parity = USART_Parity_Odd;USART_InitStructure.USART_WordLength = USART_WordLength_9b; break;
    case MB_PAR_EVEN:USART_InitStructure.USART_Parity = USART_Parity_Even;USART_InitStructure.USART_WordLength = USART_WordLength_9b; break;
    default:break;
    }


    USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART2, &USART_InitStructure);
    USART_Cmd(USART2,ENABLE);

    vMBPortSerialEnable(FALSE,FALSE);
    USART_ClearFlag(USART2,USART_FLAG_TC);

/*************************************************************************************/

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //通道设置为串口2中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0;//抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //中断响应优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //打开中断
    NVIC_Init(&NVIC_InitStructure);   //初始化

    return bInitialized;
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:30 | 显示全部楼层
4).xMBPortSerialPutByte 的作用是将字节数据送到串口。把正常用的串口发送拿过来就可以了
BOOL xMBPortSerialPutByte( UCHAR ucByte )
{
    USART_SendData(USART2, ucByte);  
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET){};

    return TRUE;
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:53 | 显示全部楼层
5).xMBPortSerialGetByte 的作用是从串口接收数据并且把数据传给指定内存。此处的UARTRecvBuffer是一个接受缓冲区。我这样做是为了防止在串口接收的时候数据来不及接收被冲毁,是自己因为在仿真测试的时候发现数据经常丢字节而做的一个措施,具体正确性要等待上板测试。
BOOL
xMBPortSerialGetByte( UCHAR * pucByte )
{
    *pucByte = (UCHAR)UARTRecvBuffer;
    return TRUE;
}






使用特权

评论回复
gaoke231|  楼主 | 2018-8-19 23:53 | 显示全部楼层
6).USART2_IRQHandler 串口2中断此处中断有两个。发送和接受。
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)
    {
        USART_ClearITPendingBit(USART2,USART_IT_RXNE);
        UARTRecvBuffer = USART_ReceiveData(USART2);
        prvvUARTRxISR();
    }
    if(USART_GetITStatus(USART2,USART_IT_TXE)!=RESET)
    {
        USART_ClearITPendingBit(USART2,USART_IT_TXE);
        prvvUARTTxReadyISR();
    }
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:05 | 显示全部楼层
7).prvvUARTTxReadyISR 这个函数其实就是发送的时候会调用的函数。
static void prvvUARTTxReadyISR( void )
{
    DE1 = 1;
    delay_ms(1);
    pxMBFrameCBTransmitterEmpty(  );
    delay_ms(1);
    DE1 = 0;
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:06 | 显示全部楼层
8).prvvUARTRxISR是串口接收函数,调用pxMBFrameCBByteReceived();
static void prvvUARTRxISR( void )
{
    pxMBFrameCBByteReceived(  );
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:08 | 显示全部楼层

porttimer.c

1).xMBPortTimersInit 是用来初始化定时器的,modbus-rtu需要有一个定时器来计算每个字节之间的间隔时间,若间隔时间超过3.5T,则是认为此帧已经接收完毕。
此处的
timerBaseInit.TIM_Period = usTim1Timerout50us - 1;
timerBaseInit.TIM_Prescaler = 1799;// 50us
周期是重复次数,是在定时器中断为50us的前提下的重复次数,一般在波特率≤19200的情况下,公式为:
usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
在大于19200的情况下,固定为35.
TIM_Period 和TIM_Prescaler 传入的时候 都要减一才准确。此处我的时钟是TIM2,所以挂载APB1上,APB1我设置为最大的3600MHz。因此当分频为1800的时候,刚刚好是50us中断一次,所以分频设置为1799.

BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
{
    TIM_TimeBaseInitTypeDef timerBaseInit;
    NVIC_InitTypeDef NVIC_InitStructure;

    TIM_DeInit(TIM2); //重新将Timer设置为缺省值
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);// 使能Timer2外设时钟
    //TIM_InternalClockConfig(TIM2); //采用内部时钟给TIM2提供时钟源

    // TIM2
    timerBaseInit.TIM_Period = usTim1Timerout50us - 1;
    timerBaseInit.TIM_Prescaler = 1799;// 50us
    timerBaseInit.TIM_CounterMode = TIM_CounterMode_Up;

    timerBaseInit.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &timerBaseInit);

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清标志
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);     //中断使能
    TIM_Cmd(TIM2, DISABLE);


    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;//响应优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//允许中断
    NVIC_Init(&NVIC_InitStructure);
    return TRUE;
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:09 | 显示全部楼层
2).vMBPortTimersEnable TIM2使能函数 在此使能时钟的时候,要把时钟重置。
void vMBPortTimersEnable(  )
{
    TIM_Cmd(TIM2, DISABLE);
    TIM_Cmd(TIM2, ENABLE);
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:09 | 显示全部楼层
3).vMBPortTimersDisable TIM2除能函数
voidvMBPortTimersDisable(  )
{
    TIM_Cmd(TIM2, DISABLE);
}


使用特权

评论回复
gaoke231|  楼主 | 2018-8-20 00:10 | 显示全部楼层
4).TIM2_IRQHandler 定时器中断,定时进入判断是否接收超时或完毕,至此FreeModbus从机部分移植完毕。接下来就是调试了。
void TIM2_IRQHandler(void)
{
   u8 i;
   (void)i;
   if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){
      TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
      ( void )pxMBPortCBTimerExpired();

   }
}


使用特权

评论回复
yediezeus| | 2018-8-20 09:13 | 显示全部楼层

使用特权

评论回复
磨砂| | 2018-8-20 10:29 | 显示全部楼层
非常改写楼主分享

使用特权

评论回复
yfgww| | 2020-6-26 18:50 | 显示全部楼层

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

54

主题

1310

帖子

5

粉丝