裸机 · 2024年 3月 23日 0

485通信

485接口原理

串口

串口是一种接口标准,它规定了接口的电气标准,简单说只是物理层的一个标准。没有规定接口插件电缆以及使用的协议。所以只要我们使用的接口插件电缆符合串口标准就可以摘时间中灵活使用,在串口接口标准上使用各种协议通信及设备控制。

==典型的串行通信标准是RS232和RS485,它们定义了电压,阻抗等,但不对软件协议给予定义==

RS232接口缺陷

  1. 接口的型号电平值较高(+/- 12V),易损坏接口电路的芯片。
  2. 传输速率较低,在异步传输时,波特率为20Kbps
  3. 接口使用一根信号线和一根信号返回线而构成共地的传输形式,这种共地传输容易产生共模干扰,所以抗噪声干扰性弱。
  4. 传输距离有限,最大传输距离标准值为50英尺,实际上也只能用在50米左右。

485接口

485(一般称为RS485/EIA-485)是隶属于OSI模型物理层的电气特性规定为2线,半双工,多点通信的标准。

它的电气特性和RS-232大不一样。用缆线两端的电压差值来表示传递信号。

RS485仅仅规定了接收端和发送端的电气特性。它没有规定或推荐任何数据协议。

RS485的特点包括:

  1. 接口电平低,不易损坏芯片。RS485的电气特性:逻辑“1”以两线间的电压差为+(2 ~ 6)V表示;逻辑“0”以两线间的电压差为 -(2 ~ 6)V表示。接口信号电平比RS232降低了,不易损坏接口电路的芯片。
  2. 传输速率高。10米时,RS485的数据最高传输速率可达35Mbps,在1200m时,传输速度可达100kbps。
  3. 抗干扰能力强。RS485节课是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
  4. 传输距离远,支持节点多。RS485总线最长可以传输1200m以上(速率≤100Kbps)一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。

RS485推荐使用在点对点网络中,线型,总线型,不能是星型,环型网络。理想情况下RS485需要2个匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。485推荐的连接方式:

image-20240219172126013

==在上面的连接中,如果需要添加匹配电阻,我们一般在总线的起止端加入,也就是主机和设备4上面各加一个120Ω的匹配电阻。==

收发器SP3485

image-20240219172411425
  1. 图中A、B总线接口,用于连接485总线。
  2. RO是接收数据输出端
  3. DI是发送数据输入端
  4. RE是接收使能端(低电平有效)
  5. DE是发送使能信号(高电平有效)

硬件连接

image-20240219172841063

注意:

  1. R32和R34是两个偏置电阻,用于保证总线空闲时,AB之间的电压差都会大于200mV,避免总线空闲时压差不定逻辑混乱。
  2. 两个485接口连接,A连接A,B连接B。

485接口原理

image-20240219183953455
image-20240219184015375

程序讲解

rs485.c

/**
 ****************************************************************************************************
 * @file        rs485.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2022-4-20
 * @brief       RS485 驱动代码
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 阿波罗 F429开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 * 修改说明
 * V1.0 20220420
 * 第一次发布
 *
 ****************************************************************************************************
 */

#include "./BSP/RS485/rs485.h"
#include "./BSP/PCF8574/pcf8574.h"
#include "./SYSTEM/delay/delay.h"


UART_HandleTypeDef g_rs458_handle;          /* USART2句柄(用于RS485) */

#if EN_USART2_RX                            /* 如果使能了接收 */

uint8_t g_rs485_rx_buf[RS485_REC_LEN];      /* 接收缓冲,最大64个字节. */
uint8_t g_rs485_rx_cnt = 0;                 /* 接收到的数据长度 */

void USART2_IRQHandler(void)
{
    uint8_t res;

    if ((__HAL_UART_GET_FLAG(&g_rs458_handle, UART_FLAG_RXNE) != RESET))    /* 接收中断 */
    {
        HAL_UART_Receive(&g_rs458_handle, &res, 1, 1000);
        if (g_rs485_rx_cnt < RS485_REC_LEN)
        {
            g_rs485_rx_buf[g_rs485_rx_cnt] = res;                           /* 记录接收到的值 */
            g_rs485_rx_cnt++;                                               /* 接收数据增加1  */
        } 
    } 
}
#endif

/**
 * @brief       RS485初始化函数
 * @note        该函数主要是初始化串口
 * @param       bound   : 波特率, 根据自己需要设置波特率值
 * @retval      无
 */
void rs485_init(uint32_t bound)
{
    GPIO_InitTypeDef gpio_init_struct;
    RS485_TX_GPIO_CLK_ENABLE();                                     /* 使能GPIOA时钟 */
    RS485_UX_CLK_ENABLE();                                          /* 使能USART2时钟 */

    pcf8574_init();                                                 /* 初始化PCF8574,用于控制RE脚 */

    gpio_init_struct.Pin = RS485_TX_GPIO_PIN | RS485_RX_GPIO_PIN; 
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;                        /* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                            /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_HIGH;                       /* 高速 */
    gpio_init_struct.Alternate = GPIO_AF7_USART2;                   /* 复用为USART2 */
    HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_init_struct);           /* 初始化PA2,3 */

    /* USART 初始化设置 */
    g_rs458_handle.Instance = RS485_UX;                             /* USART2 */
    g_rs458_handle.Init.BaudRate = bound;                           /* 波特率 */
    g_rs458_handle.Init.WordLength = UART_WORDLENGTH_8B;            /* 字长为8位数据格式 */
    g_rs458_handle.Init.StopBits = UART_STOPBITS_1;                 /* 一个停止位 */
    g_rs458_handle.Init.Parity = UART_PARITY_NONE;                  /* 无奇偶校验位 */
    g_rs458_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;            /* 无硬件流控 */
    g_rs458_handle.Init.Mode = UART_MODE_TX_RX;                     /* 收发模式 */
    HAL_UART_Init(&g_rs458_handle);                                 /* HAL_UART_Init()会使能USART2 */

    __HAL_UART_DISABLE_IT(&g_rs458_handle, UART_IT_TC);

#if EN_USART2_RX
    __HAL_UART_ENABLE_IT(&g_rs458_handle, UART_IT_RXNE);            /* 开启接收中断 */
    HAL_NVIC_EnableIRQ(RS485_UX_IRQn);                              /* 使能USART1中断 */
    HAL_NVIC_SetPriority(RS485_UX_IRQn, 3, 3);                      /* 抢占优先级3,子优先级3 */
#endif

    rs485_tx_set(0);                                                /* 设置为接收模式 */
}

/**
 * @brief       RS485发送len个字节
 * @param       buf      : 发送区首地址
 * @param       len      : 发送的字节数(为了和本代码的接收匹配,这里建议不要超过64个字节)
 * @retval      无
 */
void rs485_send_data(uint8_t *buf, uint8_t len)
{
    rs485_tx_set(1);                                                /* 设置为发送模式 */
    HAL_UART_Transmit(&g_rs458_handle, buf, len, 1000);             /* 串口2发送数据 */
    g_rs485_rx_cnt = 0;
    rs485_tx_set(0);                                                /* 设置为接收模式 */
}

/**
 * @brief       RS485查询接收到的数据
 * @param       buf  : 接收缓存首地址
 * @param       len  : 读到的数据长度
 * @retval      无
 */
void rs485_receive_data(uint8_t *buf, uint8_t *len)
{
    uint8_t rxlen = g_rs485_rx_cnt;
    uint8_t i = 0;
    *len = 0;                               /* 默认为0 */
    delay_ms(10);                           /* 等待10ms,连续超过10ms没有接收到一个数据,则认为接收结束 */

    if (rxlen == g_rs485_rx_cnt && rxlen)   /* 接收到了数据,且接收完成了 */
    {
        for (i = 0; i < rxlen; i++)
        {
            buf[i] = g_rs485_rx_buf[i];
        }

        *len = g_rs485_rx_cnt;              /* 记录本次数据长度 */
        g_rs485_rx_cnt = 0;                 /* 清零 */
    }
}

/**
 * @brief       RS485模式控制.
 * @param       en   : 0,接收;1,发送
 * @retval      无
 */
void rs485_tx_set(uint8_t en)
{
    pcf8574_write_bit(RS485_RE_IO, en);
}

main.c

/**
 ****************************************************************************************************
 * @file        main.c
 * @author      正点原子团队(ALIENTEK)
 * @version     V1.0
 * @date        2022-4-20
 * @brief       RS485 实验
 * @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
 ****************************************************************************************************
 * @attention
 *
 * 实验平台:正点原子 阿波罗 F429开发板
 * 在线视频:www.yuanzige.com
 * 技术论坛:www.openedv.com
 * 公司网址:www.alientek.com
 * 购买地址:openedv.taobao.com
 *
 ****************************************************************************************************
 */

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/SDRAM/sdram.h"
#include "./USMART/usmart.h"
#include "./BSP/RS485/rs485.h"


int main(void)
{
    uint8_t key;
    uint8_t i = 0, t = 0;
    uint8_t cnt = 0;
    uint8_t rs485buf[5]; 

    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);     /* 设置时钟,180Mhz */
    delay_init(180);                         /* 延时初始化 */
    usart_init(115200);                      /* 初始化USART */
    usmart_dev.init(90);                     /* 初始化USMART */
    led_init();                              /* 初始化LED */
    sdram_init();                            /* 初始化SDRAM */
    lcd_init();                              /* 初始化LCD */
    key_init();                              /* 初始化按键 */
    rs485_init(9600);                        /* 初始化RS485 */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "RS485 TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:Send", RED);

    lcd_show_string(30, 150, 200, 16, 16, "Count:", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "Send Data:", BLUE);
    lcd_show_string(30, 210, 200, 16, 16, "Receive Data:", BLUE);

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES)                                             /* KEY0按下,发送一次数据 */
        {
            for (i = 0; i < 5; i++)
            {
                rs485buf[i] = cnt + i;                                    /* 填充发送缓冲区 */
                lcd_show_num(30 + i * 32, 190, rs485buf[i], 3, 16, 0X80); /* 显示数据 */
            }

            rs485_send_data(rs485buf, 5);                                 /* 发送5个字节 */
        }

        rs485_receive_data(rs485buf, &key);

        if (key)                                                          /* 接收到有数据 */
        {
            if (key > 5)
            {
                key = 5;                                                  /* 最大是5个数据. */
            }
            for (i = 0; i < key; i++)
            {
                lcd_show_num(30 + i * 32, 230, rs485buf[i], 3, 16, 0X80); /* 显示数据 */
            }
        }

        t++; 
        delay_ms(10);

        if (t == 20)
        {
            LED0_TOGGLE();                                                /* 提示系统正在运行 */
            t = 0; 
            cnt++;
            lcd_show_num(30 + 48, 150, cnt, 3, 16, 0X80);                 /* 显示数据 */
        }
    }
}