> 技术文档 > 快速了解rs485协议(以arm-linux开发板和stm32为例)

快速了解rs485协议(以arm-linux开发板和stm32为例)

目录

 

1.什么是rs485协议

2.rs485的特点

3.rs485的工作原理

4.rs485的优缺点

5.stm32与i.mx6ull通信

6.实际效果​


 

1.什么是rs485协议

        rs485协议是一种广泛用于工业通信的串行通信标准,具有抗干扰强,传输距离远,多节点传输等优点。它采用差分信号传输方式,通过一对双绞线进行传输,常用于长距离,多节点的工业通信。

2.rs485的特点

        1.电气特性:rs485的逻辑1表示两线间的电压差为+2v到+6v,逻辑0表示两线间的电压差为-2v到-6v。相比rs232,rs485的信号电平比较低,不易损坏接口电路。
        2.传输效率:rs485的数据传输速率最高可达10Mbps,传输距离可达1200米。在较低速率下,传数距离可以更远。
        3.抗干扰能力强:rs485采用平衡驱动器和差分接受器的组合,具有良好的抗干扰能力。
        4.多节点连接:rs485接口允许在同一总线上连接多达32个节点,适用于多设备联网。
rs485推荐的一主多从连接方式如下图所示:
快速了解rs485协议(以arm-linux开发板和stm32为例)

3.rs485的工作原理

        RS485的工作原理基于差分信号传输。发送端通过一对双绞线发送数据,接收端通过检测两线间的电压差来判断逻辑状态。具体来说,当发送端输出高电平时,A线电压高于B线电压;当输出低电平时,A线电压低于B线电压。
        TP8485E/SP3485可作为rs485的接受器,该芯片支持3.3v-5.5v供电,最大传输速度可达250kbps,支持256个节点,并支持输出短路保护。该芯片框图如下图所示: 

                                        快速了解rs485协议(以arm-linux开发板和stm32为例)
        图中A、B总线接口,用于连接485总线。RO是接受输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)。

4.rs485的优缺点

优点

  • 抗干扰能力强,适用于恶劣环境。

  • 传输距离远,最高可达1200米。

  • 支持多节点连接,最多可连接32个设备。

  • 数据传输速率高,最高可达10Mbps12。

缺点

  • 不支持全双工通信,只能实现半双工通信。

  • 布线复杂,维护成本高。

5.stm32与i.mx6ull通信

在协议的配置中,RS485的配置通常与UART的配置相同,只不过需要注意的是RS485需要方向控制,切换接收和发送模式。

i.mx6ull:

#ifndef RS485_H#define RS485_H#include #include #include #include #include #include #include #include #include #include #include #include extern int rs485_fd;int rs485_init();void async_io_init(void);void rs485_send(char *buf);#endif // RS485_H
#include \"rs485.h\"#define device \"/dev/ttymxc2\"// 在 rs485.c 中定义int rs485_fd = -1;struct termios old_cfg = {0}; /* 保存的旧的配置参数,便于将终端恢复成原来的状态,因为只是简单演示,实际并没有用到 */int rs485_init(){ /* 1.打开设备文件 */ rs485_fd = open(device, O_RDWR | O_NOCTTY); //打开串口设备,使用O_NOCTTY用于告知系统/dev/ttymxc2他不会成为进程的控制终端 if (rs485_fd  cfsetspeed(&cfg, B115200)) { fprintf(stderr, \"cfsetspeed error: %s\\n\", strerror(errno)); return -1; } /* 设置数据位大小 */ cfg.c_cflag &= ~CSIZE; cfg.c_cflag |= CS8; /* 设置为8数据位 */ /* 设置奇偶校验 */ /** * 奇校验使能 * new_cfg.c_cflag |= (PARODD | PARENB); * new_cfg.c_iflag |= INPCK; * * 偶校验使能 * new_cfg.c_cflag |= PARENB; * new_cfg.c_cflag &= ~PARODD; 清除PARODD标志,配置为偶校验 * new_cfg.c_iflag |= INPCK; **/ cfg.c_cflag &= ~PARENB; /* 无校验 */ cfg.c_iflag &= ~INPCK; /* 设置停止位 */ cfg.c_cflag &= ~CSTOPB; /* 将停止为设置为一个bit */ /* 设置MIN和TIME的值 */ cfg.c_cc[VTIME] = 0; cfg.c_cc[VMIN] = 0; //在对接收字符和等待时间没有特别要求的情况下,可以将MIN和TIME设置为0,这样则在任何情况下read()调用都会立即返回 /* 清空缓冲区 */ if (0 > tcflush(rs485_fd, TCIOFLUSH)) { fprintf(stderr, \"tcflush error: %s\\n\", strerror(errno)); return -1; } // RS485硬件控制配置 struct serial_rs485 rs485_conf = {0}; rs485_conf.flags |= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND; if (ioctl(rs485_fd, TIOCSRS485, &rs485_conf) < 0) { perror(\"ioctl TIOCSRS485 failed\"); close(rs485_fd); return -1; } if(tcsetattr(rs485_fd, TCSANOW, &cfg) si_code) { ret = read(rs485_fd, buf, 8); printf(\"[\"); for (n = 0; n < ret; n++) printf(\"0x%hhx \",buf[n]); printf(\"]\\n\"); }}void async_io_init(void){ struct sigaction sigatn; int flag; /* 使能异步I/O */ flag = fcntl(rs485_fd, F_GETFL); //使能串口的异步I/O功能 flag |= O_ASYNC; fcntl(rs485_fd, F_SETFL, flag); /* 设置异步I/O的所有者 */ fcntl(rs485_fd, F_SETOWN, getpid()); /* 指定实时信号SIGRTMIN作为异步I/O通知信号 */ fcntl(rs485_fd, F_SETSIG, SIGRTMIN); /* 为实时信号SIGRTMIN注册信号处理函数 */ sigatn.sa_sigaction = io_handler; //当串口有数据可读时,会跳转到io_handler函数 sigatn.sa_flags = SA_SIGINFO; sigemptyset(&sigatn.sa_mask); sigaction(SIGRTMIN, &sigatn, NULL);}void rs485_send(char *buf, int len){ int ret = write(rs485_fd, buf, len); //一次写八个字节 if(ret < 0) { perror(\"rs485_send error\"); exit(EXIT_FAILURE); }}
#include \"rs485.h\"void *receive_thread(void *arg) { //创建接受线程 while(1);}int main(){ int ret = rs485_init(); pthread_t tid; pthread_create(&tid, NULL, receive_thread, NULL); async_io_init(); if(ret != 0) { perror(\"init error\"); return -1; } unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; //通过串口发送出去的数据 rs485_send(w_buf); pthread_join(tid,NULL); close(rs485_fd); return 0;}

stm32:
stm32用的是正点原子stm32f103zet6,驱动代码也来字正点原子rs485驱动代码。使用时需将rs485引脚与串口2用跳线帽连接。

#ifndef __RS485_H#define __RS485_H#include \"./SYSTEM/sys/sys.h\"/******************************************************************************************//* RS485 引脚 和 串口 定义 */#define RS485_RE_GPIO_PORT  GPIOD#define RS485_RE_GPIO_PIN  GPIO_PIN_7#define RS485_RE_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOD_CLK_ENABLE(); }while(0) /* PD口时钟使能 */#define RS485_TX_GPIO_PORT  GPIOA#define RS485_TX_GPIO_PIN  GPIO_PIN_2#define RS485_TX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */#define RS485_RX_GPIO_PORT  GPIOA#define RS485_RX_GPIO_PIN  GPIO_PIN_3#define RS485_RX_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */#define RS485_UX USART2#define RS485_UX_IRQn USART2_IRQn#define RS485_UX_IRQHandler  USART2_IRQHandler#define RS485_UX_CLK_ENABLE()  do{ __HAL_RCC_USART2_CLK_ENABLE(); }while(0) /* USART2 时钟使能 *//******************************************************************************************//* 控制RS485_RE脚, 控制RS485发送/接收状态 * RS485_RE = 0, 进入接收模式 * RS485_RE = 1, 进入发送模式 */ #define RS485_RE(x) do{ x ? \\ HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_SET) : \\ HAL_GPIO_WritePin(RS485_RE_GPIO_PORT, RS485_RE_GPIO_PIN, GPIO_PIN_RESET); \\}while(0)#define RS485_REC_LEN  64 /* 定义最大接收字节数 64 */#define RS485_EN_RX  1  /* 使能(1)/禁止(0)RS485接收 */extern uint8_t g_RS485_rx_buf[RS485_REC_LEN]; /* 接收缓冲,最大RS485_REC_LEN个字节 */extern uint8_t g_RS485_rx_cnt;  /* 接收数据长度 */void rs485_init( uint32_t baudrate); /* RS485初始化 */void rs485_send_data(uint8_t *buf, uint8_t len); /* RS485发送数据 */void rs485_receive_data(uint8_t *buf, uint8_t *len);/* RS485接收数据 */#endif
#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; HAL_GPIO_Init(RS485_TX_GPIO_PORT, &gpio_initure); /* 串口TX 脚 模式设置 */ gpio_initure.Pin = RS485_RX_GPIO_PIN; gpio_initure.Mode = GPIO_MODE_AF_INPUT; 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);/* HAL_UART_Init()会使能UART2 */#if RS485_EN_RX /* 如果使能了接收 */ /* 使能接收中断 */ __HAL_UART_ENABLE_IT(&g_rs458_handler, UART_IT_RXNE); /* 开启接收中断 */ HAL_NVIC_EnableIRQ(RS485_UX_IRQn);  /* 使能USART2中断 */ 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; 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 (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; /* 清零 */ }}
#include \"./SYSTEM/sys/sys.h\"#include \"./SYSTEM/usart/usart.h\"#include \"./SYSTEM/delay/delay.h\"#include \"./USMART/usmart.h\"#include \"./BSP/LED/led.h\"#include \"./BSP/LCD/lcd.h\"#include \"./BSP/KEY/key.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(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ usmart_dev.init(72); /* 初始化USMART */ led_init();  /* 初始化LED */ lcd_init();  /* 初始化LCD */ key_init();  /* 初始化按键 */ rs485_init(115200); /* 初始化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, 130, 200, 16, 16, \"Count:\", RED); /* 显示当前计数值 */ lcd_show_string(30, 150, 200, 16, 16, \"Send Data:\", RED); /* 提示发送的数据 */ lcd_show_string(30, 190, 200, 16, 16, \"Receive Data:\", RED);/* 提示接收到的数据 */ while (1) { key = key_scan(0); if (key == KEY0_PRES) /* KEY0按下,发送一次数据 */ { for (i = 0; i  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) { LED0_TOGGLE(); /* LED0闪烁, 提示系统正在运行 */ t = 0; cnt++; lcd_show_xnum(30 + 48, 130, cnt, 3, 16, 0X80, BLUE); /* 显示数据 */ } }}

6.实际效果
快速了解rs485协议(以arm-linux开发板和stm32为例)

 

十字绣门户网