历史上的今天
今天是: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 定时器中断,定时进入判断是否接收超时或完毕
上一篇:STM32中的看门狗
史海拾趣
|
目录: 第一篇 模拟电路 实验一 常用电子仪器的使用 实验二 单级低频放大器的设计、安装与调试 实验三 集成运算放大器及其应用 实验四 OTL低频功率放大器 实验五 音频功率放大器的设计、安装与调试 实验六 集成稳压电源 实验七 LC振荡器 ...… 查看全部问答> |
|
谁知道嵌入式精简TCP/IP协议有多大要用多大的flash/ram来存储,是不是单片机上有这个协议就可以实现网络通信了啊? 如果不行还需要什么啊?… 查看全部问答> |
|
本帖最后由 jameswangsynnex 于 2015-3-3 20:04 编辑 当今绚丽多彩的家电市场,产品最受关注的卖点莫过于“智能”、“人性化”、“全自动”等。从用户的角度来看,能配上如此美名的产品,应该是用户只需按触一个“START”开关,不论具体应用的对象 ...… 查看全部问答> |
|
芯片MSP430F47197 SD16_A进行7路采样,32768晶振,OSR256,采样频率fs=32768*32/256=4096/s 采样的交流电压频率为50HZ,我的理解是每秒可采集4096个数据,而交流电每秒有50个周期, 理论上我每个周期的交流电可采集到4096/50=82个数据左右。 &n ...… 查看全部问答> |
|
Push- Pull输出就是一般所说的推挽输出,在CMOS电路里面应该较CMOS输出更合适,因为在CMOS里面的push-pull输出能力不可能做得双极那么大。输出能力看IC内部输出极N管P管的面积。和开漏输出相比,push-pull的高低电平由IC的电源低定,不能简单的做 ...… 查看全部问答> |




