超级版主
 
- 积分
- 5323
- 金钱
- 5323
- 注册时间
- 2019-5-8
- 在线时间
- 1366 小时
|
|
第三十五章 RS485实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章我们将向大家介绍如何使用ESP32-P4的串口实现485通信(半双工)。在本章中,我们将使用ESP32-P4的串口1来实现两块开发板之间的485通信,并将结果显示在LCD屏上。
本章分为如下几个小节:
35.1 485介绍
35.2 硬件设计
35.3 程序设计
35.4 下载验证
35.1 485介绍
485(一般称作 RS485/EIA-485)隶属于OSI模型物理层,是串行通讯的一种。电气特性规定为2线,半双工,多点通信的类型。它的电气特性和RS-232大不一样。用缆线两端的电压差值来表示传递信号。RS485仅仅规定了接受端和发送端的电气特性。它没有规定或推荐任何数据协议。
RS485的特点包括:
① 接口电平低,不易损坏芯片。RS485的电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS232降低了,不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL电路连接。
② 传输速率高。10米时,RS485的数据最高传输速率可达35Mbps,在1200m时,传输速度可达100Kbps。
③ 抗干扰能力强。RS485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。
④ 传输距离远,支持节点多。RS485总线最长可以传输1200m左右,更远的距离则需要中继传输设备支持但这时(速率≤100Kbps)才能稳定传输,一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。
RS485推荐使用在点对点网络中,比如:线型,总线型网络等,而不能是星型,环型网络。理想情况下RS485需要2个终端匹配电阻,其阻值要求等于传输电缆的特性阻抗(一般为120Ω)。没有特性阻抗的话,当所有的设备都静止或者没有能量的时候就会产生噪声,而且线移需要双端的电压差。没有终接电阻的话,会使得较快速的发送端产生多个数据信号的边缘,导致数据传输出错。485推荐的一主多从连接方式如图35.1.1所示:
图35.1.1 RS485连接
在上面的连接中,如果需要添加匹配电阻,我们一般在总线的起止端加入,也就是主机和设备4上面各加一个120Ω的匹配电阻。
由于RS485具有传输距离远、传输速度快、支持节点多和抗干扰能力更强等特点,所以RS485有很广泛的应用。
ESP32-P4开发板采用TPT3485作为收发器,该芯片支持3.3V供电,最大传输速度可达10Mbps,支持多达32个节点,并且支持输出短路保护。该芯片的框图如图35.1.2所示:
图35.1.2 TPT3485框图
图中A、B总线接口,用于连接485总线。RO是接收输出端,DI是发送数据收入端,RE是接收使能信号(低电平有效),DE是发送使能信号(高电平有效)
35.2 硬件设计
35.2.1 例程功能
经过前面的学习我们知道实际的RS485仍是串行通讯的一种电平传输方式,那么我们实际通讯时可以使用串口进行实际数据的收发处理,使用485转换芯片将串口信号转换为485的电平信号进行传输,本章,我们只需要配置好串口1,就可以实现正常的485通信了,串口1的配置和串口0是一样的。
本章将实现这样的功能:通过连接两个ESP32-P4的RS485接口,然后由BOOT控制发送,当按下一个开发板的BOOT的时候,就发送5个数据给另外一个开发板,并在两个开发板上分别显示发送的值和接收到的值。
35.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)RS485电路
RS485_TX - IO27(U1RXD)(跳线帽连接)
RS485_RX - IO26(U1TXD)(跳线帽连接)
RS485_RE – IO0_4(X L9555)
35.2.3 原理图
RS485电路相关原理图,如下图所示。
图35.2.3.1 RS485电路原理图
从上图可以看出:开发板的串口1和TPT8485上的引脚连接到P5端上的端子,但不直接相连。注意:RS485_RE信号是连接在XL9555的IO0_4引脚上,并没有直接连接到MCU,需要通过IIC总线控制XL9555,从而实现对RS485_RE的控制。RS485_RE控制TPT8485的收发,当RS485_RE=0的时候,为接收模式;当RS485_RE=1的时候,为发送模式。
图中的R11和R14是两个偏置电阻,用来保证总线空闲时,A、B之间的电压差都会大于200mV(逻辑1),从而避免总线空闲时因A、B压差不稳定,可能出现的乱码。
最后,我们用2根导线将两个开发板RS485端子的A和A,B和B连接起来。这里注意不要接反了,接反了会导致通讯异常!!。
35.3 程序设计
35.3.1 RS485的IDF驱动
由于485实际上是串口通讯,我们参照串口实验使用类似的IDF驱动即可。
35.3.2 程序流程图
图35.3.1.1 RS485实验程序流程图
35.3.3 程序解析
在26_rs485例程中,作者在26_rs485 \components\BSP路径下新增了1个文件夹RS485,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. RS485驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RS485驱动源码包括两个文件:rs485.c和rs485.h。
下面先解析rs485.h的程序。对RS485相关引脚和串口编号做了相关宏定义。
- <font size="3">/* RS485通信引脚 和 串口定义 */</font>
- <font size="3">#define RS485_UART_PORT UART_NUM_1</font>
- <font size="3">#define RS485_TX_GPIO_PIN GPIO_NUM_26</font>
- <font size="3">#define RS485_RX_GPIO_PIN GPIO_NUM_27</font>
- <font size="3">/* 定义RS485接收大小及接收使能配置 */</font>
- <font size="3">#define RS485_REC_LEN 1024 /* 定义最大接收字节数1024 */</font>
复制代码 RS485用到的是串口1,而串口1的发送引脚用到的是IO26,串口1的接收引脚用到的是IO27。
下面我们再解析rs485.c的程序,首先先来看一下初始化函数rs485_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief RS485初始化</font>
- <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url] 该函数主要是初始化串口</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] baudrate:波特率,根据自己需要设置波特率值</font>
- <font size="3"> * @retval ESP_OK:表示初始化成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t rs485_init(uint32_t baudrate)</font>
- <font size="3">{</font>
- <font size="3"> uart_config_t rs485_config = {0};</font>
- <font size="3"> rs485_config.baud_rate = baudrate; /* 设置波特率 */</font>
- <font size="3"> rs485_config.data_bits = UART_DATA_8_BITS; /* 数据位 */</font>
- <font size="3"> rs485_config.parity = UART_PARITY_DISABLE; /* 无奇偶校验位 */</font>
- <font size="3"> rs485_config.stop_bits = UART_STOP_BITS_1; /* 一位停止位 */</font>
- <font size="3"> rs485_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; /* 无硬件流控 */</font>
- <font size="3">rs485_config.source_clk = UART_SCLK_DEFAULT; /* 选择时钟源 */</font>
- <font size="3"> /* RS485的UART配置 */</font>
- <font size="3"> ESP_ERROR_CHECK(uart_param_config(RS485_UART_PORT, &rs485_config)); </font>
- <font size="3"> /* 设置管脚 */ </font>
- <font size="3">ESP_ERROR_CHECK(uart_set_pin(RS485_UART_PORT, RS485_TX_GPIO_PIN, </font>
- <font size="3">RS485_RX_GPIO_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));</font>
- <font size="3"> /* 安装串口驱动 */</font>
- <font size="3">ESP_ERROR_CHECK(uart_driver_install(RS485_UART_PORT, RS485_REC_LEN, </font>
- <font size="3">RS485_REC_LEN, 0, NULL, 0)); </font>
- <font size="3"> /* 设置超时时间 3.5T * 8 = 28 ticks, TOUT=3 -> ~24..33 ticks */</font>
- <font size="3"> ESP_ERROR_CHECK(uart_set_rx_timeout(RS485_UART_PORT, 3));</font>
- <font size="3"> rs485_tx_set(0); /* 进入接收模式 */</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 可以看到代码基本跟串口的配置一样,只是多了收发控制引脚的配置而已。
切换发送和接收状态,定义了rs485_tx_set函数,代码如下。
- <font size="3">/**</font>
- <font size="3"> * @brief RS485模式控制.</font>
- <font size="3"> * @param en:0,接收;1,发送</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void rs485_tx_set(uint8_t en)</font>
- <font size="3">{</font>
- <font size="3"> xl9555_pin_write(RS485_RE_IO, en);</font>
- <font size="3">}</font>
复制代码 若想485电路进入到接收状态,调用rs485_tx_set(0)即可;若想485电路进入到发送状态,调用rs485_tx_set(1)即可。
继续看一下RS485发送函数,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief RS485发送len个字节</font>
- <font size="3"> * @param buf:发送区首地址</font>
- <font size="3"> * @param len:发送字节数(为了和本代码的接收匹配,这里建议不要超过RS485_REC_LEN字节)</font>
- <font size="3"> * @retval ESP_OK:表示发送成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t rs485_send_data(uint8_t *buf, uint8_t len)</font>
- <font size="3">{</font>
- <font size="3"> rs485_tx_set(1); /* 进入发送模式 */</font>
- <font size="3"> uart_write_bytes(RS485_UART_PORT, buf, len); </font>
- <font size="3"> ESP_ERROR_CHECK(uart_wait_tx_done(RS485_UART_PORT, 10));</font>
- <font size="3"> rs485_tx_set(0); /* 退出发送模式 */</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 发送函数用于输出485信号到485总线上,默认的485方式一般空闲时为接收状态,只有发送数据时,我们才控制485芯片进入发送状态,发送完成后马上回到空闲接收状态,这样子可以保证操作过程中485的数据丢失最小。
最后看一下RS485接收函数,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief RS485查询接收到的数据</font>
- <font size="3"> * @param buf:接收缓冲区首地址</font>
- <font size="3"> * @param len:接收到的数据长度</font>
- <font size="3"> * @arg 0, 表示没有接收到任何数据; 其他, 表示接收到的数据长度</font>
- <font size="3"> * @retval ESP_OK:表示接收成功</font>
- <font size="3"> */</font>
- <font size="3">esp_err_t rs485_receive_data(uint8_t *buf, uint8_t *len)</font>
- <font size="3">{</font>
- <font size="3"> *len = 0;</font>
- <font size="3"> ESP_ERROR_CHECK(uart_get_buffered_data_len(RS485_UART_PORT, (size_t*)len));</font>
- <font size="3"> </font>
- <font size="3"> if (*len > 0)</font>
- <font size="3"> {</font>
- <font size="3"> uart_read_bytes(RS485_UART_PORT, buf, *len, 100);</font>
- <font size="3"> }</font>
- <font size="3"> return ESP_OK;</font>
- <font size="3">}</font>
复制代码 该函数用于查询485总线上接收到的数据,主要调用uart_get_buffered_data_len函数去获取缓冲区数据长度,然后再通过uart_read_bytes函数去获取缓冲区数据。
2. CMakeLists.txt文件
本例程的功能实现主要依靠RTC驱动。要在main函数中,成功调用RTC文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY</font>
- <font size="3"> LCD</font>
- <font size="3"> XL9555</font>
- <font size="3"> RS485)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> KEY</font>
- <font size="3"> LCD</font>
- <font size="3"> XL9555</font>
- <font size="3"> RS485)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver</font>
- <font size="3"> esp_lcd</font>
- <font size="3"> esp_common</font>
- <font size="3"> newlib)</font>
- <font size="3">idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码 3. main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> uint8_t key;</font>
- <font size="3"> uint8_t i = 0, t = 0;</font>
- <font size="3"> uint8_t cnt = 0;</font>
- <font size="3"> uint8_t rs485buf[5];</font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3"> if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* LED初始化 */</font>
- <font size="3"> key_init(); /* KEY初始化 */</font>
- <font size="3"> myiic_init(); /* MYIIC初始化 */</font>
- <font size="3"> xl9555_init(); /* XL9555初始化 */</font>
- <font size="3"> lcd_init(); /* LCD屏初始化 */</font>
- <font size="3"> rs485_init(9600); /* 初始化RS485 */</font>
- <font size="3"> lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);</font>
- <font size="3"> lcd_show_string(30, 70, 200, 16, 16, "RS485 TEST", RED);</font>
- <font size="3"> lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);</font>
- <font size="3"> </font>
- <font size="3"> lcd_show_string(30, 110, 200, 16, 16, "BOOT:Send", RED); </font>
- <font size="3"> lcd_show_string(30, 130, 200, 16, 16, "Count:", RED); </font>
- <font size="3"> lcd_show_string(30, 150, 200, 16, 16, "Send Data:", RED); </font>
- <font size="3"> lcd_show_string(30, 190, 200, 16, 16, "Receive Data:", RED); </font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> key = key_scan(0);</font>
- <font size="3"> if (key == BOOT_PRES) /* BOOT按下,发送一次数据 */</font>
- <font size="3"> {</font>
- <font size="3"> for (i = 0; i < 5; i++)</font>
- <font size="3"> {</font>
- <font size="3"> rs485buf</font><span style="font-size: medium;"> = cnt + i; /* 填充发送缓冲区 */
- <span style="font-style: italic;"><span style="font-style: normal;"> lcd_show_xnum(30 + i * 32, 170, rs485buf, 3, 16, 0X80, BLUE);
- }
- 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_xnum(30 + i * 32, 210, rs485buf</span><span style="font-style: normal;">, 3, 16, 0X80, BLUE);
- }
- }
- t++;
- vTaskDelay(pdMS_TO_TICKS(10));
- if (t == 20)
- {
- LED0_TOGGLE();
- t = 0;
- cnt++;
- lcd_show_xnum(30 + 48, 130, cnt, 3, 16, 0X80, BLUE);
- }
- }
- }</span></span></span>
复制代码 在app_main函数中,通过rs485_init函数对RS485进行初始化,在while循环中,会检测BOOT按键是否被按下进而决定是否向485总线发送数据,同时一直会查询485总线上是否有数据接收到,若有接收到便通过LCD进行显示。LED0闪烁,以提示程序正在运行。
35.4 下载验证
将程序下载到开发板后(注意两个开发板都要下载这个代码),可以看到LED0不停闪烁,提示程序已经在运行了,可看到LCD显示的内容如图35.4.1所示:
图35.4.1 RS485实验程序运行效果图
我们按下BOOT按键便可以在另外一个开发板上收到这个开发板发送的数据了。如图35.4.2和图35.4.3所示。
图35.4.2 发送RS485数据的开发板页面
图35.4.3 接收RS485数据的开发板页面
图35.4.2来自开发板A,发送了5个数据,图35.4.3来自开发板B,接收到了来自开发板A的5个数据。 |
|