打印
[STM32F0]

基于STM32的MODBUS-RS485

[复制链接]
671|13
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
回复就哭哭|  楼主 | 2022-11-30 19:18 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
这里为什么说RS485是MODBUS的实现载体呢,因为RS485仅仅只是一种硬件电平标准,我的理解就是在串口的基础上加了电平转换芯片比如MAX485,将串口的TTL电平转换成了RS485差分电平。

贴上代码段

使用特权

评论回复
沙发
回复就哭哭|  楼主 | 2022-11-30 19:22 | 只看该作者
.RS485——串口2初始化
void RS485_USART2_Init(void)
{
        GPIO_InitTypeDef  GPIO_InitStructure;
        USART_InitTypeDef  USART_InitStruct;
        NVIC_InitTypeDef  NVIC_InitStruct;
       
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
       
        GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
        GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
         //GPIOA2,A3初始化设置
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用模式
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
        GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化

        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//复用模式
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
        GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化
       



//串口初始化配置
USART_InitStruct.USART_BaudRate=9600;
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
USART_InitStruct.USART_Parity=USART_Parity_No;
USART_InitStruct.USART_StopBits=USART_StopBits_1;
USART_InitStruct.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStruct);
USART_Cmd(USART2,ENABLE);

USART_ClearFlag(USART2, USART_FLAG_TC);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);



NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
GPIO_ResetBits(GPIOG,GPIO_Pin_8);//初始化为接收状态
}

使用特权

评论回复
板凳
回复就哭哭|  楼主 | 2022-11-30 19:23 | 只看该作者
因为需要控制芯片引脚收发,所以要利用PG8来控制接收或者发送使能,具体的硬件原理图于正点原子F407的相同,大家可以参考那个。

使用特权

评论回复
地板
回复就哭哭|  楼主 | 2022-11-30 19:24 | 只看该作者
定时器初始化
void Timer2_Init(void)//84M
{
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruc;
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
       
        TIM_TimeBaseInitStruc.TIM_Period=1000-1;//1ms
        TIM_TimeBaseInitStruc.TIM_Prescaler=84-1;//84M/84=1MHZ->1us
        TIM_TimeBaseInitStruc.TIM_ClockDivision=TIM_CKD_DIV1;
        TIM_TimeBaseInitStruc.TIM_CounterMode=TIM_CounterMode_Up;       
        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruc);
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);       
        TIM_Cmd(TIM2,ENABLE);
        TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
       
}
void Timer2_NVIC_Configuration(void)
{
        NVIC_InitTypeDef  NVIC_InitStruct;
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
       
        NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
        NVIC_Init(&NVIC_InitStruct);
}

使用特权

评论回复
5
回复就哭哭|  楼主 | 2022-11-30 19:24 | 只看该作者
发送字节与发送多个字节函数
void RS485_SendByte(u8 bbyte)
{
        RS_485_TX;
        USART_SendData(USART2, bbyte);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
        RS_485_RX;
}
void RS485_SendData(u8 *Sdbuf,u8 len)
{
        u8 i;
        RS_485_TX;
        for(i=0;i<len;i++)
        {
                USART_SendData(USART2,Sdbuf[i]);
                while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
        }
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
        RS_485_RX;       
}

使用特权

评论回复
6
回复就哭哭|  楼主 | 2022-11-30 19:25 | 只看该作者
我认为这些都是基础知识,大家应该首先根据正点原子把能够利用485收发全部实现,然后再这个基础上加入485协议。

使用特权

评论回复
7
回复就哭哭|  楼主 | 2022-11-30 19:25 | 只看该作者
接下来就是串口的接收以及定时器中断函数,modbus的复杂主要就麻烦在如何将串口的接收中断与定时器中断配合好,达到完整的接收一帧数据,并且不影响下一帧接受的效果。

使用特权

评论回复
8
回复就哭哭|  楼主 | 2022-11-30 19:27 | 只看该作者
void USART2_IRQHandler()
{
        u8 res;
        if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
        {
                res =USART_ReceiveData(USART2);
                modbus.ReceiveBuff[modbus.ReceiveCount]=res;
                modbus.ReceiveCount++;
                if(modbus.ReceiveCount==1)
                {
                        modbus.timerun=1;
                }
                modbus.timecount=0;
        }
}
void TIM2_IRQHandler()
{
        if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
        {
                if(modbus.timerun==1)
                {
                        modbus.timecount++;
                }
                if(modbus.timecount>=5)
                {
                        modbus.timerun=0;
                        modbus.timecount=0;
                        modbus.ReceiveComplete=1;
                }
        }
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}

使用特权

评论回复
9
回复就哭哭|  楼主 | 2022-11-30 19:29 | 只看该作者
在这里为了方便,定义了一个modbus的结构体
typedef struct
{
        u8 Slave_ID;                        //从机ID
        u8 ReceiveBuff[20];                //接收缓存数组
        u8 ReceiveCount;                //计算接收到的数据有多少字节
        u8 timecount;                        //有多久没有接收到字节,数据断续的时间
        u8 timerun;                                //断续时间是否开始累加
        u8 ReceiveComplete;                //一帧数据接收完成标志       
}MODBUS;

使用特权

评论回复
10
回复就哭哭|  楼主 | 2022-11-30 19:30 | 只看该作者
modbus协议主要就是,在接收时有3.5个字符间隔,那么就认为这一帧数据接收结束,可以进行处理并且接收下一帧数据。

在这里定义了一个接收缓存数组,用于将串口2接收到的数据放到缓存数组中。

使用特权

评论回复
11
回复就哭哭|  楼主 | 2022-11-30 19:31 | 只看该作者
大概逻辑是这样的,首先主机发送命令后,从机返回一帧数据,当接收到一帧数据的第一个字符时,计时器开始计时,timrun启动,timcount++,如果在3.5个字符内又收到了下一个字节数据,那么timcount就会置0,使他无法到达3.5个字符间隔,如果接收到了,就将数据继续放入到数组中。直到接收完毕,定时器到达3,5个字符间隔,此时接收完成ReceiveComplete=1;然后这个时候为了防止下一帧数据进来把数据覆盖掉,需要把接收缓存数组ReceiveBuff中的数据copy出去,

使用特权

评论回复
12
回复就哭哭|  楼主 | 2022-11-30 19:32 | 只看该作者
copy数据的函数
void RS485_Receive_Data(u8 *buf,u8 *len)
{
        u8 i;
        u8 Temp_len;
        Temp_len=modbus.ReceiveCount;
        if(modbus.ReceiveComplete==1)
        {
                for(i=0;i<Temp_len;i++)
                {
                        buf[i]=modbus.ReceiveBuff[i];
                }
                *len=modbus.ReceiveCount;
                modbus.ReceiveCount=0;       
        }
        modbus.ReceiveComplete=0;
       
}

使用特权

评论回复
13
回复就哭哭|  楼主 | 2022-11-30 19:33 | 只看该作者
copy完后主程序处理数据,中断继续接收。

使用特权

评论回复
14
公羊子丹| | 2024-11-8 07:17 | 只看该作者

防雷电路的输出残压值必须比被防护电路自身能够耐受的过电压峰值低,并有一定裕量

使用特权

评论回复
15
Uriah| | 2024-11-8 09:23 | 只看该作者

在完成测试后,需要分析测试结果并进行评估

使用特权

评论回复
16
帛灿灿| | 2024-11-8 11:19 | 只看该作者

构成各种滤波器对EMI进行滤波

使用特权

评论回复
17
Bblythe| | 2024-11-8 12:22 | 只看该作者

在动态测试之前,首先需要设定测试时间和测试频率

使用特权

评论回复
18
周半梅| | 2024-11-8 14:18 | 只看该作者

测试负载测试是在特定的测试条件下进行的

使用特权

评论回复
19
Pulitzer| | 2024-11-8 15:21 | 只看该作者

在测试中,可以改变负载电流,得到最大输出电流和输出电压

使用特权

评论回复
20
童雨竹| | 2024-11-8 17:17 | 只看该作者

信号防雷电路应满足相应接口信号传输速率及带宽的需求,且接口与被保护设备兼容。

使用特权

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

本版积分规则

24

主题

358

帖子

0

粉丝