正点原子RS485学习

发布于:2024-10-13 ⋅ 阅读:(11) ⋅ 点赞:(0)

        485通信基于串口实验

串口,Rs232,RS422和RS485关系,串行通信接口标准,逻辑1和逻辑0的表示不同

CMOS电平逻辑1通常为VCC的70%以上,逻辑0通常为VCC的30%以下

TTL电平逻辑1通常为2.0V以上,标准TTL的高电平通常为2.4V以上

TTL电平低电平通常为0.8V以下,标准TTL的低电平通常为0.4V以下

差分信号V+和V-是根据一个参考电压反相的,要在输出端得到实际信号,则利用(V+-V-)/2就是实际信号电压

RS485使用差分信号传输,抗干扰性能强,常用于工控领域

串口协议:仅指封装了基本数据包格式的协议(基于数据位)

MODBUS协议:使用基本数据包组合成通讯帧格式的高层应用协议

RS485是在RS232版本改进电压,不易损失芯片,传输速率高,传输距离远,支持节点多

RS485总线是A线连A线,B线连B线,半双工通信,485芯片将将485电平转换为TTL电平,或者相反,双绞线也是为了抗干扰,匹配电阻也是为了抑制噪声

485收发器是电平转换芯片如SP3485,TP8485E

旗舰版电路,当配置为接收器输出使能时,stm32的串口RX作为接收端,两口跳线帽将位号为P7的P3 USART2 RX和RS485 TX连接在一起

A相当于+,B相当于-在图中,RS485是半双工的,得到的差分信号(A-B)/2得到实际信号,但是这里是逻辑电平1,0所以只管谁大就能区别发送的逻辑电平

485通信波形图,RO是接收器输出端,连接的是stm32的RX,  A和B是电平转换芯片将TTL电平转换为485电平输出或输入口

硬件连接,A连A,B连B

跳线帽将485的TX和串口的RX连接,将接收的485电平经过电平转换芯片TP8485E转换为单片机的TTL电平

将串口的TX和485的RX相连,然后经过电平转换芯片将TTL电平输出为485电平

RS485相关HAL库驱动

HAL_UART_Receive()和HAL_UART_Transmit()都是读取或写入USART_DR数据寄存器

引脚  单片机的RS485_RE的引脚连接芯片的RE和DE,DE高电平有效

PA2作为USART2的发送,PA3作为USART的接收

接收模式要通过USART接收,所以使能接收中断

其中RS485_UX_IRQHandler是通过宏定义把函数名

中断处理函数就是串口2的中断处理函数,通过宏定义转换了,非0即1

定义最大接收字节数量为64,大于了不管了这里的处理是

rs485.c学习

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

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


UART_HandleTypeDef g_rs458_handler;     /* RS485控制句柄(串口) */

#ifdef RS485_EN_RX /* 如果使能了接收 */

uint8_t g_RS485_rx_buf[RS485_REC_LEN];  /* 接收缓冲, 最大 RS485_REC_LEN 个字节. */
uint8_t g_RS485_rx_cnt = 0;             /* 接收到的数据长度 ,接收计数器*/

void RS485_UX_IRQHandler(void)
{
    uint8_t res;

    if ((__HAL_UART_GET_FLAG(&g_rs458_handler, UART_FLAG_RXNE) != RESET)) /* 接收到数据 */
    {
        HAL_UART_Receive(&g_rs458_handler, &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       baudrate: 波特率, 根据自己需要设置波特率值
 * @retval      无
 */
void rs485_init(uint32_t baudrate)
{
    /* IO 及 时钟配置 */
    RS485_RE_GPIO_CLK_ENABLE(); /* 使能 RS485_RE 脚时钟 */
    RS485_TX_GPIO_CLK_ENABLE(); /* 使能 串口TX脚 时钟 */
    RS485_RX_GPIO_CLK_ENABLE(); /* 使能 串口RX脚 时钟 */
    RS485_UX_CLK_ENABLE();      /* 使能 串口 时钟 */

    GPIO_InitTypeDef gpio_initure;
    gpio_initure.Pin = RS485_TX_GPIO_PIN;
    gpio_initure.Mode = GPIO_MODE_AF_PP;    //串口发送引脚使用到的是复用推挽输出
    gpio_initure.Pull = GPIO_PULLUP;
    gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_initure.Alternate = GPIO_AF7_USART2;               /* 复用为串口2 */
    HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_initure);       /* 串口TX 脚 模式设置 */

    gpio_initure.Pin = RS485_RX_GPIO_PIN;     //串口接收引脚用到的是输入
    HAL_GPIO_Init(RS485_RX_GPIO_PORT, &gpio_initure);       /* 串口RX 脚 必须设置成输入模式 */

    gpio_initure.Pin = RS485_RE_GPIO_PIN;
    gpio_initure.Mode = GPIO_MODE_OUTPUT_PP;   //模式配置引脚设置为推挽输出
    gpio_initure.Pull = GPIO_PULLUP;
    gpio_initure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(RS485_RE_GPIO_PORT, &gpio_initure);       /* RS485_RE 脚 模式设置 */

    /* USART 初始化设置 */
    g_rs458_handler.Instance = RS485_UX;                    /* 选择485对应的串口 */
    g_rs458_handler.Init.BaudRate = baudrate;               /* 波特率 */
    g_rs458_handler.Init.WordLength = UART_WORDLENGTH_8B;   /* 字长为8位数据格式 */
    g_rs458_handler.Init.StopBits = UART_STOPBITS_1;        /* 一个停止位 */
    g_rs458_handler.Init.Parity = UART_PARITY_NONE;         /* 无奇偶校验位 */
    g_rs458_handler.Init.HwFlowCtl = UART_HWCONTROL_NONE;   /* 无硬件流控 */
    g_rs458_handler.Init.Mode = UART_MODE_TX_RX;            /* 收发模式 */
    HAL_UART_Init(&g_rs458_handler);                        /* 使能对应的串口, 但会调用MSp */
    __HAL_UART_DISABLE_IT(&g_rs458_handler, UART_IT_TC);

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

    RS485_RE(0); /* 默认为接收模式 */
}

/**
 * @brief       RS485发送len个字节
 * @param       buf     : 发送区首地址
 * @param       len     : 发送的字节数(为了和本代码的接收匹配,这里建议不要超过 RS485_REC_LEN 个字节)
 * @retval      无
 */
void rs485_send_data(uint8_t *buf, uint8_t len)
{
    RS485_RE(1);                                            /* 进入发送模式 */
    HAL_UART_Transmit(&g_rs458_handler, buf, len, 1000);    /* 串口2发送数据 */
    g_RS485_rx_cnt = 0;   //接收计数器赋值为0
    RS485_RE(0); /* 进入接收模式 */
}

/**
 * @brief       RS485查询接收到的数据
 * @param       buf     : 接收缓冲区首地址
 * @param       len     : 接收到的数据长度
 *   @arg               0   , 表示没有接收到任何数据
 *   @arg               其他, 表示接收到的数据长度
 * @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相当于判断接收到了数并完成接收了,因为连续10ms没有收到则默认接收结束
    if (rxlen == g_RS485_rx_cnt && rxlen) /* 接收到了数据,且接收完成了,这个判断是先等于再并rxlen!=0 */
    {
        for (i = 0; i < rxlen; i++)
        {
            buf[i] = g_RS485_rx_buf[i];   //把接收缓冲区的数据存到buf中
        }

        *len = g_RS485_rx_cnt; /* 记录本次数据长度 */
        g_RS485_rx_cnt = 0;    /* 存入完成后,清零接收缓冲区 */
    }
}

下面这个查询接收到的数据函数非常巧妙

main.c里面的部分,也很巧妙

 while (1)
    {
        key = key_scan(0);  //按键扫描,0为不支持连按,1为支持

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

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

        rs485_receive_data(rs485buf, &key);   //轮询查询接收到的数据,在接收数据缓冲区没有数据就返回长度0

        if (key)                            /* 接收到有数据 */
        {
            if (key > 5)key = 5;            /* 最大是5个数据. */

            for (i = 0; i < key; i++)
            {
                lcd_show_xnum(30 + i * 32, 210, rs485buf[i], 3, 16, 0X80, BLUE);    /* 显示数据 */
            }
        }

        t++;
        delay_ms(10);

        if (t == 20)   //结合上面延时,没0.2s进入一次
        {
            LED0_TOGGLE();  /* LED0切换输出引脚,非0即1, 提示系统正在运行 */
            t = 0;
            cnt++;
            lcd_show_xnum(30 + 48, 130, cnt, 3, 16, 0X80, BLUE);                    /* 显示数据 */
        }
    }
}

RS485总结,这些都是串行通信接口协议,就是逻辑0和1表示不同

在RS485基础上,还制定了MODBUS协议

RS485总线连接需要匹配电阻确保总线稳定性

相关HAL库驱动函数配置步骤