历史上的今天
返回首页

历史上的今天

今天是:2025年03月30日(星期日)

正在发生

2020年03月30日 | STM32 FreeModbus RTU从机移植以及UART配置

2020-03-30 来源:eefocus

FreeModbus的具体介绍就不提了。至于为什么要移植,大概就是因为移植比较快,而且比较稳定,可以减少因为自己编写出现的漏洞。 

但是FreeModbus 1.5版本是没有主机的,因此移植的时候只可以做从机。网上有几个关于Modbus主机的源代码,回头等我弄好了再更新。


==================================


理论上来说,此处我移植了全部,但是只调试了RTU部分,因此其他部分不做赘述。


移植过程: 

1.将modbus目录下所有文件拷贝加入工程。 

2.对modbus中的include下的mbconfig.h进行编辑,裁剪其中需要的模块。(此处我没有进行裁剪,因此选项都是默认) 

3.将demo中的合适的port文件夹下的文件加入工程。 

4.修改port文件夹下的代码,移植UART驱动。 

5.使用modbus poll调试。


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


port.c


#include "stm32f10x.h"


void EnterCriticalSection(  )

{

    __set_PRIMASK(1);

}


void ExitCriticalSection(  )

{

    __set_PRIMASK(0);

}

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


port.h


#ifndef _PORT_H

#define _PORT_H


#include "stm32f10x.h"

#include

#include


#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

port.h只要是针对一些类型的跨平台支持。


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;

}

portevent.c没有改动,作用是起到一个简单的事件队列。


接下来两个是重点移植的文件 

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);

}

2).vMBPortClose 在Demo的源代码中没有编写,我也没有移植。


3).xMBPortSerialInit 是串口初始化的函数,其中只要把对应需要的串口移植添加进来就可以了。


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;

}

串口初始化程序很简单。设置一下数据位和校验还有波特率就可以了。此处我只针对了UART2移植,因此没有加入其他的串口初始化。


4).xMBPortSerialPutByte 的作用是将字节数据送到串口。把正常用的串口发送拿过来就可以了


推荐:STM32的UART DMA传输总结


使用DMA传输可以连续获取或发送一段信息而不占用中断或延时,在通信频繁或有大段信息要传输时非常有用。 由上表可知,要使用USART1TX/RX我们选择通道4和5 1、 DM


BOOL xMBPortSerialPutByte( UCHAR ucByte )

{

    USART_SendData(USART2, ucByte);  

    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET){};


    return TRUE; 

}

5).xMBPortSerialGetByte 的作用是从串口接收数据并且把数据传给指定内存。


BOOL

xMBPortSerialGetByte( UCHAR * pucByte )

{

    *pucByte = (UCHAR)UARTRecvBuffer;

    return TRUE;

}

此处的UARTRecvBuffer是一个接受缓冲区。我这样做是为了防止在串口接收的时候数据来不及接收被冲毁,是自己因为在仿真测试的时候发现数据经常丢字节而做的一个措施,具体正确性要等待上板测试。


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();

    }

}

7).prvvUARTTxReadyISR 这个函数其实就是发送的时候会调用的函数。


static void prvvUARTTxReadyISR( void )

{

    DE1 = 1;

    delay_ms(1);

    pxMBFrameCBTransmitterEmpty(  );

    delay_ms(1);

    DE1 = 0;

}

DE1是485的控制器,为1的时候就是发送状态,为0是接收状态。


8).prvvUARTRxISR是串口接收函数,调用pxMBFrameCBByteReceived();


static void prvvUARTRxISR( void )

{

    pxMBFrameCBByteReceived(  );

}

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;

}

2).vMBPortTimersEnable TIM2使能函数 

在此使能时钟的时候,要把时钟重置。


void vMBPortTimersEnable( )

{

    TIM_Cmd(TIM2, DISABLE);

    TIM_Cmd(TIM2, ENABLE);

}

3).vMBPortTimersDisable TIM2除能函数


voidvMBPortTimersDisable( )

{

    TIM_Cmd(TIM2, DISABLE);

}

4).TIM2_IRQHandler 定时器中断,定时进入判断是否接收超时或完毕

推荐阅读

史海拾趣

德索五金(dosinconn)公司的发展小趣事

发展历程:2008年,公司从工程部门中分出研发部门,并设立研发实验室。连接器定制研发从此成为公司的核心业务,创新意识也深入到了企业文化之中。

成果与影响:研发部门的成立和技术创新的推动使德索五金电子在连接器制造领域保持了领先地位,也为公司的长远发展提供了源源不断的动力。

ADMOS公司的发展小趣事

随着技术的不断进步,ADMOS公司开始积极拓展国内外市场。通过与各大电子设备制造商建立紧密的合作关系,ADMOS的产品逐渐渗透到智能手机、平板电脑、笔记本电脑等消费电子产品中。同时,公司还积极开拓汽车电子、工业控制等新兴市场,为公司的持续成长注入了新的动力。

Corporation Soneet公司的发展小趣事

随着Soneet在技术领域取得的成功,公司开始积极拓展市场。Soneet与多家知名电子设备制造商建立了战略合作关系,共同开发新产品。通过与这些合作伙伴的紧密合作,Soneet的产品得以迅速进入全球市场,进一步提升了公司的知名度和影响力。

B&B Electronics Manufacturing Company公司的发展小趣事

在国内市场取得一定成绩后,B&B公司开始将目光投向国际市场。通过参加国际电子展会、建立海外销售网络等方式,公司逐渐打开了国际市场的大门。其产品以高品质和可靠性赢得了海外客户的信赖,为公司的进一步发展奠定了坚实的基础。

EOZ S.A.S公司的发展小趣事

EOZ S.A.S公司非常重视企业文化和团队建设。公司倡导“创新、协作、共赢”的价值观,鼓励员工积极参与创新活动并分享创新成果。同时,公司还注重员工的培训和发展,为员工提供丰富的培训资源和晋升机会。这些举措不仅激发了员工的积极性和创造力,也为企业的发展提供了有力的人才保障。在EOZ S.A.S公司的发展历程中,优秀的企业文化和团队成为公司最宝贵的财富之一。

ABOV(现代单片机)公司的发展小趣事

EOZ S.A.S公司非常重视企业文化和团队建设。公司倡导“创新、协作、共赢”的价值观,鼓励员工积极参与创新活动并分享创新成果。同时,公司还注重员工的培训和发展,为员工提供丰富的培训资源和晋升机会。这些举措不仅激发了员工的积极性和创造力,也为企业的发展提供了有力的人才保障。在EOZ S.A.S公司的发展历程中,优秀的企业文化和团队成为公司最宝贵的财富之一。

问答坊 | AI 解惑

某个强人的电子竞赛经验介绍

本帖最后由 paulhyde 于 2014-9-15 09:42 编辑 很值得学习的大赛经验介绍!让你少走弯路!!  …

查看全部问答>

【藏书阁】电子线路实验 方建中

目录: 第一篇 模拟电路 实验一 常用电子仪器的使用 实验二 单级低频放大器的设计、安装与调试 实验三 集成运算放大器及其应用 实验四 OTL低频功率放大器 实验五 音频功率放大器的设计、安装与调试 实验六 集成稳压电源 实验七 LC振荡器 ...…

查看全部问答>

全国电子设计大赛的优秀作品集锦???

哪本书或者那个网站有最近几年全国电子设计大赛的优秀作品集锦???…

查看全部问答>

帮帮忙急求:谁知道嵌入式精简TCP/IP

    谁知道嵌入式精简TCP/IP协议有多大要用多大的flash/ram来存储,是不是单片机上有这个协议就可以实现网络通信了啊? 如果不行还需要什么啊?…

查看全部问答>

计算机组成原理 和 计算机体系结构 有什么不同?

已经学了计算机组成原理,下学期有计算机体系结构这门课选,不知道两者有多大的不同?大家给点意见。…

查看全部问答>

也谈家电产品之模糊控制技术(上)

本帖最后由 jameswangsynnex 于 2015-3-3 20:04 编辑 当今绚丽多彩的家电市场,产品最受关注的卖点莫过于“智能”、“人性化”、“全自动”等。从用户的角度来看,能配上如此美名的产品,应该是用户只需按触一个“START”开关,不论具体应用的对象 ...…

查看全部问答>

求解430中断程序处理方法!!急啊

芯片MSP430F47197 SD16_A进行7路采样,32768晶振,OSR256,采样频率fs=32768*32/256=4096/s 采样的交流电压频率为50HZ,我的理解是每秒可采集4096个数据,而交流电每秒有50个周期, 理论上我每个周期的交流电可采集到4096/50=82个数据左右。 &n ...…

查看全部问答>

求430控制十字路口交通灯程序 急急急

求430控制十字路口交通灯程序 急急急  …

查看全部问答>

电路图的画法规则

电路图的画法规则,为了使看图者能正确方便理解电路图的全部内容,绘制电路图时,,,,... 资源中心下载地址:https://download.eeworld.com.cn/detail/qwqwqw2088/281462 直接下载:电路图的画法规则…

查看全部问答>

【转】推挽输出与开漏输出的区别

Push- Pull输出就是一般所说的推挽输出,在CMOS电路里面应该较CMOS输出更合适,因为在CMOS里面的push-pull输出能力不可能做得双极那么大。输出能力看IC内部输出极N管P管的面积。和开漏输出相比,push-pull的高低电平由IC的电源低定,不能简单的做 ...…

查看全部问答>