[STM32F0] 基于STM32的MODBUS-RS485

[复制链接]
1004|13
 楼主| 回复就哭哭 发表于 2022-11-30 19:18 | 显示全部楼层 |阅读模式
这里为什么说RS485是MODBUS的实现载体呢,因为RS485仅仅只是一种硬件电平标准,我的理解就是在串口的基础上加了电平转换芯片比如MAX485,将串口的TTL电平转换成了RS485差分电平。

贴上代码段

 楼主| 回复就哭哭 发表于 2022-11-30 19:22 | 显示全部楼层
.RS485——串口2初始化
  1. void RS485_USART2_Init(void)
  2. {
  3.         GPIO_InitTypeDef  GPIO_InitStructure;
  4.         USART_InitTypeDef  USART_InitStruct;
  5.         NVIC_InitTypeDef  NVIC_InitStruct;
  6.        
  7.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
  8.         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOG,ENABLE);
  9.        
  10.         GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2);
  11.         GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2);
  12.          //GPIOA2,A3初始化设置
  13.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
  14.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用模式
  15.         GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  16.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  17.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  18.         GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化

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



  26. //串口初始化配置
  27. USART_InitStruct.USART_BaudRate=9600;
  28. USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
  29. USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
  30. USART_InitStruct.USART_Parity=USART_Parity_No;
  31. USART_InitStruct.USART_StopBits=USART_StopBits_1;
  32. USART_InitStruct.USART_WordLength=USART_WordLength_8b;
  33. USART_Init(USART2,&USART_InitStruct);
  34. USART_Cmd(USART2,ENABLE);

  35. USART_ClearFlag(USART2, USART_FLAG_TC);
  36. USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);



  37. NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;
  38. NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
  39. NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
  40. NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
  41. NVIC_Init(&NVIC_InitStruct);
  42. GPIO_ResetBits(GPIOG,GPIO_Pin_8);//初始化为接收状态
  43. }
 楼主| 回复就哭哭 发表于 2022-11-30 19:23 | 显示全部楼层
因为需要控制芯片引脚收发,所以要利用PG8来控制接收或者发送使能,具体的硬件原理图于正点原子F407的相同,大家可以参考那个。
 楼主| 回复就哭哭 发表于 2022-11-30 19:24 | 显示全部楼层
定时器初始化
  1. void Timer2_Init(void)//84M
  2. {
  3.         TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStruc;
  4.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
  5.        
  6.         TIM_TimeBaseInitStruc.TIM_Period=1000-1;//1ms
  7.         TIM_TimeBaseInitStruc.TIM_Prescaler=84-1;//84M/84=1MHZ->1us
  8.         TIM_TimeBaseInitStruc.TIM_ClockDivision=TIM_CKD_DIV1;
  9.         TIM_TimeBaseInitStruc.TIM_CounterMode=TIM_CounterMode_Up;       
  10.         TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruc);
  11.         TIM_ClearITPendingBit(TIM2,TIM_IT_Update);       
  12.         TIM_Cmd(TIM2,ENABLE);
  13.         TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
  14.        
  15. }
  16. void Timer2_NVIC_Configuration(void)
  17. {
  18.         NVIC_InitTypeDef  NVIC_InitStruct;
  19.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
  20.        
  21.         NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
  22.         NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
  23.         NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
  24.         NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
  25.         NVIC_Init(&NVIC_InitStruct);
  26. }
 楼主| 回复就哭哭 发表于 2022-11-30 19:24 | 显示全部楼层
发送字节与发送多个字节函数
  1. void RS485_SendByte(u8 bbyte)
  2. {
  3.         RS_485_TX;
  4.         USART_SendData(USART2, bbyte);
  5.         while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
  6.         RS_485_RX;
  7. }
  8. void RS485_SendData(u8 *Sdbuf,u8 len)
  9. {
  10.         u8 i;
  11.         RS_485_TX;
  12.         for(i=0;i<len;i++)
  13.         {
  14.                 USART_SendData(USART2,Sdbuf[i]);
  15.                 while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
  16.         }
  17.         while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
  18.         RS_485_RX;       
  19. }
 楼主| 回复就哭哭 发表于 2022-11-30 19:25 | 显示全部楼层
我认为这些都是基础知识,大家应该首先根据正点原子把能够利用485收发全部实现,然后再这个基础上加入485协议。
 楼主| 回复就哭哭 发表于 2022-11-30 19:25 | 显示全部楼层
接下来就是串口的接收以及定时器中断函数,modbus的复杂主要就麻烦在如何将串口的接收中断与定时器中断配合好,达到完整的接收一帧数据,并且不影响下一帧接受的效果。
 楼主| 回复就哭哭 发表于 2022-11-30 19:27 | 显示全部楼层
  1. void USART2_IRQHandler()
  2. {
  3.         u8 res;
  4.         if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)
  5.         {
  6.                 res =USART_ReceiveData(USART2);
  7.                 modbus.ReceiveBuff[modbus.ReceiveCount]=res;
  8.                 modbus.ReceiveCount++;
  9.                 if(modbus.ReceiveCount==1)
  10.                 {
  11.                         modbus.timerun=1;
  12.                 }
  13.                 modbus.timecount=0;
  14.         }
  15. }
  16. void TIM2_IRQHandler()
  17. {
  18.         if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
  19.         {
  20.                 if(modbus.timerun==1)
  21.                 {
  22.                         modbus.timecount++;
  23.                 }
  24.                 if(modbus.timecount>=5)
  25.                 {
  26.                         modbus.timerun=0;
  27.                         modbus.timecount=0;
  28.                         modbus.ReceiveComplete=1;
  29.                 }
  30.         }
  31.         TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
  32. }
 楼主| 回复就哭哭 发表于 2022-11-30 19:29 | 显示全部楼层
在这里为了方便,定义了一个modbus的结构体
  1. typedef struct
  2. {
  3.         u8 Slave_ID;                        //从机ID
  4.         u8 ReceiveBuff[20];                //接收缓存数组
  5.         u8 ReceiveCount;                //计算接收到的数据有多少字节
  6.         u8 timecount;                        //有多久没有接收到字节,数据断续的时间
  7.         u8 timerun;                                //断续时间是否开始累加
  8.         u8 ReceiveComplete;                //一帧数据接收完成标志       
  9. }MODBUS;
 楼主| 回复就哭哭 发表于 2022-11-30 19:30 | 显示全部楼层
modbus协议主要就是,在接收时有3.5个字符间隔,那么就认为这一帧数据接收结束,可以进行处理并且接收下一帧数据。

在这里定义了一个接收缓存数组,用于将串口2接收到的数据放到缓存数组中。
 楼主| 回复就哭哭 发表于 2022-11-30 19:31 | 显示全部楼层
大概逻辑是这样的,首先主机发送命令后,从机返回一帧数据,当接收到一帧数据的第一个字符时,计时器开始计时,timrun启动,timcount++,如果在3.5个字符内又收到了下一个字节数据,那么timcount就会置0,使他无法到达3.5个字符间隔,如果接收到了,就将数据继续放入到数组中。直到接收完毕,定时器到达3,5个字符间隔,此时接收完成ReceiveComplete=1;然后这个时候为了防止下一帧数据进来把数据覆盖掉,需要把接收缓存数组ReceiveBuff中的数据copy出去,
 楼主| 回复就哭哭 发表于 2022-11-30 19:32 | 显示全部楼层
copy数据的函数
  1. void RS485_Receive_Data(u8 *buf,u8 *len)
  2. {
  3.         u8 i;
  4.         u8 Temp_len;
  5.         Temp_len=modbus.ReceiveCount;
  6.         if(modbus.ReceiveComplete==1)
  7.         {
  8.                 for(i=0;i<Temp_len;i++)
  9.                 {
  10.                         buf[i]=modbus.ReceiveBuff[i];
  11.                 }
  12.                 *len=modbus.ReceiveCount;
  13.                 modbus.ReceiveCount=0;       
  14.         }
  15.         modbus.ReceiveComplete=0;
  16.        
  17. }
 楼主| 回复就哭哭 发表于 2022-11-30 19:33 | 显示全部楼层
copy完后主程序处理数据,中断继续接收。
公羊子丹 发表于 2024-11-8 07:17 | 显示全部楼层

防雷电路的输出残压值必须比被防护电路自身能够耐受的过电压峰值低,并有一定裕量
Uriah 发表于 2024-11-8 09:23 | 显示全部楼层

在完成测试后,需要分析测试结果并进行评估
帛灿灿 发表于 2024-11-8 11:19 | 显示全部楼层

构成各种滤波器对EMI进行滤波
Bblythe 发表于 2024-11-8 12:22 | 显示全部楼层

在动态测试之前,首先需要设定测试时间和测试频率
周半梅 发表于 2024-11-8 14:18 | 显示全部楼层

测试负载测试是在特定的测试条件下进行的
Pulitzer 发表于 2024-11-8 15:21 | 显示全部楼层

在测试中,可以改变负载电流,得到最大输出电流和输出电压
童雨竹 发表于 2024-11-8 17:17 | 显示全部楼层

信号防雷电路应满足相应接口信号传输速率及带宽的需求,且接口与被保护设备兼容。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

436

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部