• 嵌入式开发--RS-485通讯的问题


    RS-485说明

    RS-485一般简称485总线,是最常用的工业总线之一,一般采用2线的半双工模式,用差分方式收发信息。最高速度可达10M BPS。

    接口芯片

    单片机使用485总线时,是用UART或USART接口,通过RS-485收发器完成信号的输入和输出。常用的芯片有MAX485,MAX3485,SP3485等等。

    硬件连接

    在这里插入图片描述
    硬件很简单,RO和DI连接到单片机的UART接口,是数据的收发引脚,RE和DE连通,接到单片机的GPIO,以控制数据流的方向,是输入还是输出。

    CubeMX设置

    CubeMX教程见这里:嵌入式开发–CubeMX使用入门教程
    在这里插入图片描述
    连接到UART2,具体设置如下:
    在这里插入图片描述
    这些参数需要根据你的设置要求进行,异步模式,波特率,位数,奇偶校验,停止位,其他默认即可。开启中断,以方便接收数据。
    在这里插入图片描述
    UART对波特率要求并不高,误差5%以内即可,所以晶振可以用片内的RC振荡器,当然更建议用外部石英晶振,频率更准,更重要的是可靠性高。

    代码编写

    引脚定义

    #define MAX485_OUT()      HAL_GPIO_WritePin(CTL_485_PORT,CTL_485_PIN, GPIO_PIN_SET)
    #define MAX485_IN()       HAL_GPIO_WritePin(CTL_485_PORT,CTL_485_PIN, GPIO_PIN_RESET)
    
    • 1
    • 2

    使能串口

      HAL_UART_Receive_IT(&huart2, uart2_state_typedef.data, 1);	//开启串口,接收到的数据放到uart2_state_typedef.data,每次接收1个字节
      __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);	//启动RXNE中断
      __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);	//启动IDLE中断
    
    • 1
    • 2
    • 3

    RXNE中断是用来接收数据, 每次接收1个字节,并且在中断中再次开启这个中断
    IDLE是用来做帧结束判断的,485每帧8个字节,总线空闲后会产生一个IDLE中断,进了这个中断,就表示一帧结束。

    中断函数

    void USART2_IRQHandler(void)
    {
      /* USER CODE BEGIN USART2_IRQn 0 */
    
      /* USER CODE END USART2_IRQn 0 */
      HAL_UART_IRQHandler(&huart2);
      /* USER CODE BEGIN USART2_IRQn 1 */
      //RS485接口
      //收到1个字节的数据
      if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE))
      {
        __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);	//启动RXNE中断
        uart2_state_typedef.data[uart2_state_typedef.len] = huart2.Instance->RDR;
        uart2_state_typedef.len++;
      }
      
      //总线空闲时,会发生一次IDLE中断,此时意味着数据接收完成
      //不同的内核,清除IDLEIE的方式不同,请查阅手册
      if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE))
      {
        huart2.Instance->ICR |= USART_ICR_IDLECF;	 //向USART_CR1的IDLECF位写1,以清除IDLEIF标志,否则会一直进IDLEIE中断
      }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    发送数据

    void modbus_send(void)  //发送数据到串口,数据需要事先在modbus_send_array中准备好
     {
      MAX485_OUT();		//转换为输出模式
      delay_us(100); 	//延时,以等待接口芯片切换完成
      HAL_UART_Transmit(MODBUS_PORT, modbus_send_array,8, 100);   //从串口2发送数组命令
      MAX485_IN();  	//转换为输入模式
      delay_us(10);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接收数据

    u8 modbus_receive(u16 timeout)        //发送指令后,读取伺服回传的数据,超时单位为ms
    {
      u8 ret = 255;
      u8 i = 0;
    
      while(1)
      { 
        HAL_Delay(1);
        i++;
        
        if(i>timeout)
          break;
        if(uart2_state_typedef.state == UART_RECEIVE_OK)
        {
          uart2_state_typedef.state = 0;
          ret = 0;
          huart2.Instance->ICR |= (USART_ICR_EOBCF|USART_ICR_TCCF|USART_ICR_FECF|USART_ICR_PECF);//eobf   txe  tc  fe  pe
          return ret;
          break;
        }
      }
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    如此便可以发送和接收了。

    有一个问题,多收了一个数

    于是在串口中断内,数据接收,和IDLE处,分别触发了一个电平信号,以便观察。如下图:

    数据线上的波形

    第1行的波形是发送引脚
    第2行的波形是接收引脚
    第3行的波形是方向控制引脚
    第4行是接收中断,进一次就有一个脉冲
    第5行是IDLE中断,进一次就有一个脉冲。
    在这里插入图片描述
    下图是放大的,一帧数据的波形
    在这里插入图片描述
    注意红圈的这个脉冲,数据还没有开始接收,却已经进中断开始接收了一次数据,这也就是额外多出来的一个接收字符。
    在这里插入图片描述

    问题分析

    这个脉冲是发生的位置,是在数据发送完成,并且485的方向切换到接收之后5ms处,显然是在那时,又进了一次RXNE中断,进中断的原因也很简单,在因为下图这个低电平跳变。
    在这里插入图片描述
    也就是说,当485的数据方向从接收变为发送以后,接收端口会检测到一个低电平,这被认为是串口接收数据的起始位,但是后续没有一个高电平的结束位,所以收到的这个数据肯定是错误的,UART2的ISR寄存器FE位也指出了这一点,如下图
    在这里插入图片描述

    问题解决

    知道了问题,也就知道了如何解决,将发送函数略做修改,发送完成后,清一下ISR寄存器的RXNE标识问题解决

    void modbus_send(void)  //发送读寄存器的指令到串口
    {
      u32 i=0;
      
      
      HAL_UART_AbortReceive_IT(&huart2);
      __HAL_UART_DISABLE_IT(&huart2, UART_IT_RXNE);	//禁用RXNE中断
    
      MAX485_OUT();
      delay_us(100);
      HAL_UART_Transmit(MODBUS_PORT, modbus_send_array,8, 100);   //从串口2发送数组命令
      huart2.Instance->RQR |= USART_RQR_RXFRQ;	//清除485方向切换导致的RXNE标识
      huart2.Instance->ICR |= USART_ICR_IDLECF;	//清除空闲标识
      huart2.Instance->ICR |= USART_ICR_TCCF;	//清除发送完成标识
    
      __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);	//启动RXNE中断
      
      MAX485_IN();
      uart2_state_typedef.len = 0;
      uart2_state_typedef.state = UART_READY;
      delay_us(10);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    在这里插入图片描述
    进了8次接收中断,接收8个字符,进一次IDLE中断,表示这一帧结束,工作正常。

  • 相关阅读:
    Istio服务网格详解
    42. 接雨水[动态规划+双指针]
    为什么 JavaScript 模块中的默认导出很糟糕
    解决Selenium中无法点击元素,Selemium使用JS代码 driver.execute_script点击元素
    Java笔记(七)
    【大数据】【Spark】Spark概述
    【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表)
    VsCode import自己写的包各种报错解决方案2022.9
    MyBatisPlus快速入门
    flutter web 优化和flutter_admin_template
  • 原文地址:https://blog.csdn.net/13011803189/article/details/128206077