Skip to content

Commit 8a07907

Browse files
committed
samples: drivers: uart: add RS485 echo bot sample
Add a sample application demonstrating RS485 transceiver control using the SAM0 UART driver. The sample shows how to: - Configure UART in RS485 mode at runtime - Use de-gpios and re-gpios for transceiver control - Implement echo bot with interrupt-driven RX and polling TX The sample includes complete documentation and overlay for SAME54 Xplained Pro board using SERCOM0 with DE/RE control on PA22. Hardware tested: - SAME54 Xplained Pro board - SparkFun RS-485 Transceiver Breakout - DTECH USB-to-RS485 converter Signed-off-by: Ricardo Levano <rlevano77@gmail.com>
1 parent 13d4016 commit 8a07907

File tree

6 files changed

+322
-0
lines changed

6 files changed

+322
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(uart_echo_bot)
7+
8+
target_sources(app PRIVATE src/main.c)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
.. zephyr:code-sample:: uart-rs485
2+
:name: UART RS485 echo bot
3+
:relevant-api: uart_interface
4+
5+
Read data from RS485 bus and echo it back.
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates how to use the UART serial driver with RS485 transceiver
11+
control. It reads data from the RS485 bus and echoes the characters back after
12+
an end of line (return key) is received.
13+
14+
The sample automatically configures the UART in RS485 mode and controls the
15+
Driver Enable (DE) and Receiver Enable (RE) signals to manage bus direction.
16+
17+
The polling API is used for sending data and the interrupt-driven API
18+
for receiving, so that in theory the thread could do something else
19+
while waiting for incoming data.
20+
21+
Hardware Requirements
22+
*********************
23+
24+
This sample requires a UART peripheral with RS485 transceiver control support.
25+
The hardware must have:
26+
27+
* A UART peripheral (e.g., SERCOM on SAM devices)
28+
* GPIO pins for DE (Driver Enable) and RE (Receiver Enable) control
29+
* An RS485 transceiver chip connected to the UART
30+
31+
Test Hardware
32+
=============
33+
34+
This sample was tested with the following hardware:
35+
36+
* **Development Board**: SAME54 Xplained Pro
37+
* **RS485 Transceiver**: `SparkFun Transceiver Breakout - RS-485 <https://www.sparkfun.com/sparkfun-transceiver-breakout-rs-485.html>`_
38+
* **USB-to-RS485 Adapter**: `DTECH USB to RS422/RS485 Converter Cable <https://www.dtechelectronics.com/high-quality-usb-2-0-to-rs422-rs485-converter-cable-1-2m-gold-plated-db9-male-usb-to-rs422-rs485-adapter-cable_p368.html>`_
39+
40+
SAME54 Xplained Pro Configuration
41+
**********************************
42+
43+
For the SAME54 Xplained Pro board, the sample is configured to use:
44+
45+
* **SERCOM0** as UART
46+
* **PA04** - TX (SERCOM0 PAD0)
47+
* **PA05** - RX (SERCOM0 PAD1)
48+
* **PA22** - DE/RE (Driver/Receiver Enable, shared pin)
49+
* **Baud rate**: 115200
50+
51+
Connect your RS485 transceiver as follows:
52+
53+
1. Connect SERCOM0 TX (PA04) to transceiver DI (Driver Input)
54+
2. Connect SERCOM0 RX (PA05) to transceiver RO (Receiver Output)
55+
3. Connect PA22 to both DE and ~RE pins on the transceiver
56+
4. Connect A and B lines to your RS485 bus
57+
5. Add 120Ω termination resistor if at bus end
58+
59+
Building and Running
60+
********************
61+
62+
Build and flash the sample for SAME54 Xplained Pro:
63+
64+
.. zephyr-app-commands::
65+
:zephyr-app: samples/drivers/uart/echo_bot_rs485
66+
:board: same54_xpro
67+
:goals: build flash
68+
:compact:
69+
70+
Sample Output
71+
=============
72+
73+
.. code-block:: console
74+
75+
*** Booting Zephyr OS build v4.3.0-rc3 ***
76+
RS485 mode enabled
77+
Hello! I'm your echo bot.
78+
Tell me something and press enter:
79+
# Type e.g. "Hi there!" and hit enter!
80+
Echo: Hi there!
81+
82+
Testing
83+
*******
84+
85+
To test the RS485 functionality:
86+
87+
1. Connect another RS485 device to the bus
88+
2. Configure it with 115200 baud, 8N1
89+
3. Send text data followed by newline
90+
4. The echo bot will respond with "Echo: <your message>"
91+
92+
The DE/RE GPIO will automatically:
93+
94+
* Switch to transmit mode (DE=HIGH, RE=LOW) when sending
95+
* Switch to receive mode (DE=LOW, RE=HIGH) when receiving
96+
* Transition happens automatically in the driver
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2025 Altronix
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/*
7+
* RS485 Echo Bot configuration for SAME54 Xplained Pro
8+
* Using SERCOM0 with:
9+
* - TX on PA04 (SERCOM0 PAD0)
10+
* - RX on PA05 (SERCOM0 PAD1)
11+
* - DE/RE on PA22 (Driver Enable and Receiver Enable tied together)
12+
*/
13+
14+
#include <dt-bindings/pinctrl/same54p-pinctrl.h>
15+
16+
/* Add SERCOM0 pinctrl configuration */
17+
&pinctrl {
18+
sercom0_uart_rs485: sercom0_uart_rs485 {
19+
group1 {
20+
pinmux = <PA4D_SERCOM0_PAD0>, /* TX */
21+
<PA5D_SERCOM0_PAD1>; /* RX */
22+
};
23+
};
24+
};
25+
26+
/* Configure SERCOM0 as UART with RS485 support */
27+
&sercom0 {
28+
status = "okay";
29+
compatible = "atmel,sam0-uart";
30+
current-speed = <115200>;
31+
rxpo = <1>; /* RX on PAD1 */
32+
txpo = <0>; /* TX on PAD0 */
33+
34+
/*
35+
* RS485 transceiver control
36+
*
37+
* Option 1: DE and RE tied together (RECOMMENDED - most common)
38+
* ---------------------------------------------------------------
39+
* If your RS485 transceiver has DE and RE pins connected together,
40+
* only specify de-gpios:
41+
*
42+
* de-gpios = <&porta 22 GPIO_ACTIVE_HIGH>;
43+
*
44+
* The driver will:
45+
* - Set GPIO HIGH during transmit (enables driver)
46+
* - Set GPIO LOW during receive (enables receiver)
47+
*
48+
* Option 2: Separate DE and RE control
49+
* -------------------------------------
50+
* If your transceiver has separate DE and RE pins, specify both:
51+
*
52+
* de-gpios = <&porta 22 GPIO_ACTIVE_HIGH>;
53+
* re-gpios = <&porta 23 GPIO_ACTIVE_LOW>;
54+
*
55+
* Note: RE is typically active-low (inverted), so when the driver
56+
* "enables RX", it sets the GPIO to 1, which pulls RE low.
57+
*/
58+
de-gpios = <&porta 22 GPIO_ACTIVE_HIGH>;
59+
60+
pinctrl-0 = <&sercom0_uart_rs485>;
61+
pinctrl-names = "default";
62+
};
63+
64+
/* Override the chosen UART for this sample */
65+
/ {
66+
chosen {
67+
zephyr,shell-uart = &sercom0;
68+
};
69+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CONFIG_SERIAL=y
2+
CONFIG_UART_INTERRUPT_DRIVEN=y
3+
4+
# Enable RS485 support
5+
CONFIG_UART_USE_RUNTIME_CONFIGURE=y
6+
7+
# Enable GPIO for DE/RE control
8+
CONFIG_GPIO=y
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
sample:
2+
name: UART RS485 Echo Bot
3+
description: RS485 echo bot sample using UART with transceiver control
4+
tests:
5+
sample.drivers.uart.rs485:
6+
platform_allow:
7+
- same54_xpro
8+
integration_platforms:
9+
- same54_xpro
10+
tags:
11+
- serial
12+
- uart
13+
- rs485
14+
filter: CONFIG_SERIAL and
15+
CONFIG_UART_INTERRUPT_DRIVEN and
16+
CONFIG_GPIO and
17+
dt_chosen_enabled("zephyr,shell-uart")
18+
harness: keyboard
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2022 Libre Solar Technologies GmbH
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/device.h>
9+
#include <zephyr/drivers/uart.h>
10+
11+
#include <string.h>
12+
13+
/* change this to any other UART peripheral if desired */
14+
#define UART_DEVICE_NODE DT_CHOSEN(zephyr_shell_uart)
15+
16+
#define MSG_SIZE 32
17+
18+
/* queue to store up to 10 messages (aligned to 4-byte boundary) */
19+
K_MSGQ_DEFINE(uart_msgq, MSG_SIZE, 10, 4);
20+
21+
static const struct device *const uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE);
22+
23+
/* receive buffer used in UART ISR callback */
24+
static char rx_buf[MSG_SIZE];
25+
static int rx_buf_pos;
26+
27+
/*
28+
* Read characters from UART until line end is detected. Afterwards push the
29+
* data to the message queue.
30+
*/
31+
void serial_cb(const struct device *dev, void *user_data)
32+
{
33+
uint8_t c;
34+
35+
if (!uart_irq_update(uart_dev)) {
36+
return;
37+
}
38+
39+
if (!uart_irq_rx_ready(uart_dev)) {
40+
return;
41+
}
42+
43+
/* read until FIFO empty */
44+
while (uart_fifo_read(uart_dev, &c, 1) == 1) {
45+
if ((c == '\n' || c == '\r') && rx_buf_pos > 0) {
46+
/* terminate string */
47+
rx_buf[rx_buf_pos] = '\0';
48+
49+
/* if queue is full, message is silently dropped */
50+
k_msgq_put(&uart_msgq, &rx_buf, K_NO_WAIT);
51+
52+
/* reset the buffer (it was copied to the msgq) */
53+
rx_buf_pos = 0;
54+
} else if (rx_buf_pos < (sizeof(rx_buf) - 1)) {
55+
rx_buf[rx_buf_pos++] = c;
56+
}
57+
/* else: characters beyond buffer size are dropped */
58+
}
59+
}
60+
61+
/*
62+
* Print a null-terminated string character by character to the UART interface
63+
*/
64+
void print_uart(char *buf)
65+
{
66+
int msg_len = strlen(buf);
67+
68+
for (int i = 0; i < msg_len; i++) {
69+
uart_poll_out(uart_dev, buf[i]);
70+
}
71+
}
72+
73+
int main(void)
74+
{
75+
char tx_buf[MSG_SIZE];
76+
77+
if (!device_is_ready(uart_dev)) {
78+
printk("UART device not found!");
79+
return 0;
80+
}
81+
82+
/* Configure UART for RS485 mode */
83+
struct uart_config uart_cfg;
84+
int ret = uart_config_get(uart_dev, &uart_cfg);
85+
86+
if (ret == 0) {
87+
uart_cfg.flow_ctrl = UART_CFG_FLOW_CTRL_RS485;
88+
ret = uart_configure(uart_dev, &uart_cfg);
89+
if (ret < 0) {
90+
printk("Failed to configure RS485 mode: %d\n", ret);
91+
return 0;
92+
}
93+
printk("RS485 mode enabled\n");
94+
} else {
95+
printk("Failed to get UART config: %d\n", ret);
96+
}
97+
98+
/* configure interrupt and callback to receive data */
99+
ret = uart_irq_callback_user_data_set(uart_dev, serial_cb, NULL);
100+
101+
if (ret < 0) {
102+
if (ret == -ENOTSUP) {
103+
printk("Interrupt-driven UART API support not enabled\n");
104+
} else if (ret == -ENOSYS) {
105+
printk("UART device does not support interrupt-driven API\n");
106+
} else {
107+
printk("Error setting UART callback: %d\n", ret);
108+
}
109+
return 0;
110+
}
111+
uart_irq_rx_enable(uart_dev);
112+
113+
print_uart("Hello! I'm your echo bot.\r\n");
114+
print_uart("Tell me something and press enter:\r\n");
115+
116+
/* indefinitely wait for input from the user */
117+
while (k_msgq_get(&uart_msgq, &tx_buf, K_FOREVER) == 0) {
118+
print_uart("Echo: ");
119+
print_uart(tx_buf);
120+
print_uart("\r\n");
121+
}
122+
return 0;
123+
}

0 commit comments

Comments
 (0)