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库驱动函数配置步骤