Skip to content

Commit 120532d

Browse files
committed
DTR/RI virtual UART implementation
This is very much work in progress. Draft created, so that we can discuss the approach. Terms: Data Terminal Equipment (DTE), AKA. host. Data Communication Equipment (DCE), AKA. SLM. Data Terminal Ready (DTR) Ring Indicate (RI) DTR operation: When DTE is ready to communicate with DCE, it resumes it's UART, enables RX, sets DTR low and waits for DTC to activate before sending possibly pending TX data. When DCE notices DTR down, it resumes UART, enables RX and sends any possibly pending TX data. When DTE wants to stop communicating with DCE, it sets DTR UP, aborts possible TX, disables RX and suspends UART. When DCE notices DTR up, it aborts possible TX, disables RX and suspends UART. RI operation: When DCE observes DTR up and it needs to send data, it sets RI up to notify DTE. When DTE notices RI up, it initializes communication with DCE. Application integration: uart_rx_enable is used as indicator that application is ready. It can also be used by DTE, together with uart_rx_disable, to manage the state of the UART's. UART_RX_BUF_REQUEST is sent by virtual UART to application to request a buffer. This means that we want to communicate, so DCE received DTR down or DTE received RI. uart_tx causes DCE to send RI. For DTE, it starts communication with DCE.
1 parent 44fe5ba commit 120532d

File tree

15 files changed

+1320
-256
lines changed

15 files changed

+1320
-256
lines changed

applications/serial_lte_modem/boards/nrf9151dk_nrf9151_ns.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# set here will take precedence if they are present in both files.
1010

1111
# When working with PC terminal, unmask the following config.
12-
CONFIG_SLM_POWER_PIN=8
12+
CONFIG_SLM_POWER_PIN=-1
1313
CONFIG_SLM_INDICATE_PIN=0
1414

1515
# When working with external MCU, unmask the following config.

applications/serial_lte_modem/boards/nrf9151dk_nrf9151_ns.overlay

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,51 @@
66

77
/ {
88
chosen {
9-
ncs,slm-uart = &uart0;
9+
ncs,slm-uart = &dtr_uart;
1010
};
1111
};
1212

13+
/*
14+
/ {
15+
aliases {
16+
/delete-property/ sw0;
17+
/delete-property/ mcuboot-button0;
18+
};
19+
};
20+
/delete-node/ &button0;
21+
*/
22+
23+
/* DTR with DK */
24+
/*
1325
&uart0 {
1426
status = "okay";
1527
hw-flow-control;
28+
29+
lpuart: nrf-sw-lpuart {
30+
compatible = "nordic,nrf-sw-lpuart";
31+
dtr-pin = <8>;
32+
dtr-pin-toggle;
33+
dtr-pin-debounce = <50>;
34+
ri-pin = <0>;
35+
status = "okay";
36+
};
37+
};
38+
*/
39+
40+
/* DTR with external MCU */
41+
&uart1 {
42+
status = "okay";
43+
hw-flow-control;
44+
45+
dtr_uart: nrf-sw-dtr-uart {
46+
compatible = "nordic,nrf-sw-dtr-uart";
47+
dtr-pin = <31>;
48+
ri-pin = <30>;
49+
status = "okay";
50+
};
51+
pinctrl-0 = <&uart2_default_alt>;
52+
pinctrl-1 = <&uart2_sleep_alt>;
53+
pinctrl-names = "default", "sleep";
1654
};
1755

1856
&uart2 {

applications/serial_lte_modem/prj.conf

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ CONFIG_REBOOT=y
1515
CONFIG_EVENTFD=y
1616

1717
# Segger RTT
18-
CONFIG_USE_SEGGER_RTT=y
18+
CONFIG_USE_SEGGER_RTT=n
1919
# Where console messages (printk) are output.
2020
# By itself, SLM does not output any.
21-
CONFIG_RTT_CONSOLE=y
22-
CONFIG_UART_CONSOLE=n
21+
CONFIG_RTT_CONSOLE=n
22+
CONFIG_UART_CONSOLE=y
2323
# Where SLM logs are output.
24-
CONFIG_LOG_BACKEND_RTT=y
25-
CONFIG_LOG_BACKEND_UART=n
24+
CONFIG_LOG_BACKEND_RTT=n
25+
CONFIG_LOG_BACKEND_UART=y
2626
# Increase the buffer so that all SLM boot logs fit into the buffer.
2727
# Because J-Link RTT Viewer has to be connected after the bootup,
2828
# only logs that fit into the buffer are shown. This is especially useful in showing
@@ -133,7 +133,11 @@ CONFIG_SLM_EXTERNAL_XTAL=n
133133
#CONFIG_SLM_LOG_LEVEL_DBG=y
134134
#CONFIG_LOG_PRINTK=n
135135
#CONFIG_LOG_MODE_IMMEDIATE=y
136+
CONFIG_DEBUG_OPTIMIZATIONS=y
136137

137138
# For using external GNSS antenna
138139
#CONFIG_MODEM_ANTENNA=y
139140
#CONFIG_MODEM_ANTENNA_GNSS_EXTERNAL=y
141+
142+
CONFIG_NRF_SW_DTR_UART=y
143+
CONFIG_NRF_SW_DTR_UART_LOG_LEVEL_DBG=y

applications/serial_lte_modem/src/slm_at_commands.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ static int handle_at_shutdown(enum at_parser_cmd_type cmd_type, struct at_parser
199199

200200
FUNC_NORETURN void slm_reset(void)
201201
{
202-
slm_at_host_uninit();
203202
slm_power_off_modem();
204203
LOG_PANIC();
205204
sys_reboot(SYS_REBOOT_COLD);
@@ -212,6 +211,7 @@ static int handle_at_reset(enum at_parser_cmd_type cmd_type, struct at_parser *,
212211
return -EINVAL;
213212
}
214213

214+
slm_at_host_uninit();
215215
final_call(slm_reset);
216216
return 0;
217217
}

applications/serial_lte_modem/src/slm_at_host.c

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ static void format_final_result(char *buf, size_t buf_len, size_t buf_max_len)
446446
}
447447

448448
static int slm_at_send_indicate(const uint8_t *data, size_t len,
449-
bool print_full_debug, bool indicate)
449+
bool print_full_debug, bool)
450450
{
451451
int ret;
452452

@@ -455,15 +455,6 @@ static int slm_at_send_indicate(const uint8_t *data, size_t len,
455455
return -EINTR;
456456
}
457457

458-
if (indicate) {
459-
enum pm_device_state state = PM_DEVICE_STATE_OFF;
460-
461-
pm_device_state_get(slm_uart_dev, &state);
462-
if (state != PM_DEVICE_STATE_ACTIVE) {
463-
slm_ctrl_pin_indicate();
464-
}
465-
}
466-
467458
ret = slm_tx_write(data, len);
468459
if (!ret) {
469460
LOG_HEXDUMP_DBG(data, print_full_debug ? len : MIN(HEXDUMP_LIMIT, len), "TX");
@@ -1019,7 +1010,7 @@ void slm_at_host_uninit(void)
10191010

10201011
slm_at_uninit();
10211012

1022-
at_host_power_off(true);
1013+
// at_host_power_off(true); MARKUS TODO
10231014

10241015
LOG_DBG("at_host uninit done");
10251016
}

applications/serial_lte_modem/src/slm_ppp.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ static void ppp_data_passing_thread(void*, void*, void*)
705705
{
706706
const size_t mtu = net_if_get_mtu(ppp_iface);
707707
struct zsock_pollfd fds[PPP_FDS_COUNT];
708-
enum pm_device_state state = PM_DEVICE_STATE_OFF;
708+
// enum pm_device_state state = PM_DEVICE_STATE_OFF;
709709

710710
for (size_t i = 0; i != ARRAY_SIZE(fds); ++i) {
711711
fds[i].fd = ppp_fds[i];
@@ -747,11 +747,11 @@ static void ppp_data_passing_thread(void*, void*, void*)
747747

748748
/* When DL data is received from the network, check if UART is suspended */
749749
if (src == MODEM_FD_IDX) {
750-
pm_device_state_get(ppp_uart_dev, &state);
751-
if (state != PM_DEVICE_STATE_ACTIVE) {
752-
LOG_DBG("PPP data received but UART not active");
753-
slm_ctrl_pin_indicate();
754-
}
750+
// pm_device_state_get(ppp_uart_dev, &state);
751+
// if (state != PM_DEVICE_STATE_ACTIVE) {
752+
// LOG_DBG("PPP data received but UART not active");
753+
// // slm_ctrl_pin_indicate();
754+
// }
755755
}
756756
const ssize_t len =
757757
zsock_recv(fds[src].fd, ppp_data_buf, mtu, ZSOCK_MSG_DONTWAIT);

applications/serial_lte_modem/src/slm_uart_handler.c

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
#include <zephyr/drivers/uart.h>
1010
#include <hal/nrf_uarte.h>
1111
#include <hal/nrf_gpio.h>
12+
#include <assert.h>
1213
#include <zephyr/sys/ring_buffer.h>
1314
#include <zephyr/pm/device.h>
1415
#include <zephyr/modem/pipe.h>
1516
#include "slm_uart_handler.h"
1617
#include "slm_at_host.h"
18+
#include <hal/nrf_regulators.h>
19+
#include <zephyr/sys/reboot.h>
1720

1821
#include <zephyr/logging/log.h>
1922
LOG_MODULE_REGISTER(slm_uart_handler, CONFIG_SLM_LOG_LEVEL);
@@ -27,6 +30,7 @@ const struct device *const slm_uart_dev = DEVICE_DT_GET(DT_CHOSEN(ncs_slm_uart))
2730
uint32_t slm_uart_baudrate;
2831

2932
static struct k_work_delayable rx_process_work;
33+
static struct k_work sleep_work;
3034

3135
struct rx_buf_t {
3236
atomic_t ref_counter;
@@ -279,16 +283,12 @@ static int tx_start(void)
279283
uint8_t *buf;
280284
size_t len;
281285
int err;
282-
enum pm_device_state state = PM_DEVICE_STATE_OFF;
286+
// enum pm_device_state state = PM_DEVICE_STATE_OFF;
283287

284-
if (!atomic_test_bit(&uart_state, SLM_TX_ENABLED_BIT)) {
285-
return -EAGAIN;
286-
}
287-
288-
pm_device_state_get(slm_uart_dev, &state);
289-
if (state != PM_DEVICE_STATE_ACTIVE) {
290-
return 1;
291-
}
288+
// pm_device_state_get(slm_uart_dev, &state);
289+
// if (state != PM_DEVICE_STATE_ACTIVE) {
290+
// return 1;
291+
// }
292292

293293
len = ring_buf_get_claim(&tx_buf, &buf, ring_buf_capacity_get(&tx_buf));
294294
err = uart_tx(slm_uart_dev, buf, len, SYS_FOREVER_US);
@@ -328,6 +328,7 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void
328328
struct rx_buf_t *buf;
329329
struct rx_event_t rx_event;
330330
int err;
331+
static bool hwfc_active;
331332

332333
ARG_UNUSED(dev);
333334
ARG_UNUSED(user_data);
@@ -360,17 +361,20 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void
360361
(void)k_work_submit((struct k_work *)&rx_process_work);
361362
break;
362363
case UART_RX_BUF_REQUEST:
364+
hwfc_active = false;
363365
if (k_msgq_num_free_get(&rx_event_queue) < UART_RX_EVENT_COUNT_FOR_BUF) {
364366
LOG_WRN("Disabling UART RX: No event space.");
365367
break;
366368
}
367369
buf = rx_buf_alloc();
368370
if (!buf) {
371+
hwfc_active = true;
369372
LOG_WRN("Disabling UART RX: No free buffers.");
370373
break;
371374
}
372375
err = uart_rx_buf_rsp(slm_uart_dev, buf->buf, sizeof(buf->buf));
373376
if (err) {
377+
hwfc_active = true;
374378
LOG_WRN("Disabling UART RX: %d", err);
375379
rx_buf_unref(buf);
376380
}
@@ -381,8 +385,15 @@ static void uart_callback(const struct device *dev, struct uart_event *evt, void
381385
}
382386
break;
383387
case UART_RX_DISABLED:
388+
LOG_INF("UART RX disabled");
384389
atomic_clear_bit(&uart_state, SLM_RX_ENABLED_BIT);
385-
k_work_submit((struct k_work *)&rx_process_work);
390+
if (hwfc_active) {
391+
LOG_INF("UART RX disabled due to flow control");
392+
k_work_submit((struct k_work *)&rx_process_work);
393+
}
394+
// MARKUS TODO: Whether we shutdown or go to sleep should
395+
// be configurable with AT-commands.
396+
// (void)k_work_submit((struct k_work *)&sleep_work);
386397
break;
387398
default:
388399
break;
@@ -445,6 +456,35 @@ static int slm_uart_tx_write(const uint8_t *data, size_t len)
445456
return 0;
446457
}
447458

459+
static void sleep_work_fn(struct k_work *work)
460+
{
461+
ARG_UNUSED(work);
462+
463+
// MARKUS TODO: Some of the cleanup code sends UART responses. Those should be handled properly.
464+
// This is a problem with having the virtual uart driver. The API between that and the SLM is
465+
// the default UART API, which does not allow for us to react to DTR before closing the UART.
466+
//
467+
// It is possible to remove the UART responses in cleanup code. But it would be better to have them.
468+
469+
slm_at_host_uninit();
470+
471+
/* Only power off the modem if it has not been put
472+
* in flight mode to allow reducing NVM wear.
473+
*/
474+
// if (!slm_is_modem_functional_mode(LTE_LC_FUNC_MODE_OFFLINE)) {
475+
// slm_power_off_modem();
476+
// }
477+
478+
LOG_INF("Entering sleep.");
479+
// LOG_PANIC();
480+
// nrf_gpio_cfg_sense_set(CONFIG_SLM_POWER_PIN, NRF_GPIO_PIN_SENSE_LOW);
481+
482+
k_sleep(K_MSEC(100));
483+
484+
nrf_regulators_system_off(NRF_REGULATORS_NS);
485+
assert(false);
486+
}
487+
448488
int slm_tx_write(const uint8_t *data, size_t len)
449489
{
450490
#if SLM_PIPE
@@ -505,6 +545,7 @@ int slm_uart_handler_enable(void)
505545
}
506546

507547
k_work_init_delayable(&rx_process_work, rx_process);
548+
k_work_init(&sleep_work, sleep_work_fn);
508549

509550

510551
/* Flush possibly pending data in case SLM was idle. */

drivers/serial/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
zephyr_library_amend()
88
zephyr_library_sources_ifdef(CONFIG_NRF_SW_LPUART uart_nrf_sw_lpuart.c)
99
zephyr_library_sources_ifdef(CONFIG_IPC_UART uart_ipc.c)
10+
zephyr_library_sources_ifdef(CONFIG_NRF_SW_DTR_UART uart_nrf_sw_dtr_uart.c)

drivers/serial/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66

77
rsource "Kconfig.nrf_sw_lpuart"
88
rsource "Kconfig.ipc"
9+
rsource "Kconfig.nrf_sw_dtr_uart"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# UART configuration
2+
3+
# Copyright (c) 2025, Nordic Semiconductor ASA
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
6+
config NRF_SW_DTR_UART
7+
bool "Low Power UART using REQ/RDY lines"
8+
select GPIO
9+
select UART_ASYNC_API
10+
select RING_BUFFER
11+
select PM_DEVICE
12+
help
13+
Low power UART implements UART API and extends standard UART
14+
communication with 2 pins protocol for receiver wake up and flow control.
15+
16+
if NRF_SW_DTR_UART
17+
18+
config NRF_SW_DTR_UART_DTE_IDLE
19+
bool "Enable DTE idle detection"
20+
help
21+
When DTE has not sent or received data for NRF_SW_DTR_UART_DTE_IDLE_TIMEOUT_MS period,
22+
it will pull up DTR to force DCE to disable UART and then disables own UART.
23+
24+
config NRF_SW_DTR_UART_DTE_IDLE_TIMEOUT_MS
25+
int "DTE UART Idle Timeout (ms)"
26+
default 1000
27+
range 100 600000
28+
help
29+
Idle detection timeout in milliseconds.
30+
31+
config NRF_SW_DTR_UART_INIT_PRIORITY
32+
int "Initialization priority"
33+
default 45
34+
help
35+
Initialization priority within POST_KERNEL level.
36+
37+
38+
module = NRF_SW_DTR_UART
39+
module-str = dtr uart
40+
source "subsys/logging/Kconfig.template.log_config"
41+
42+
endif

0 commit comments

Comments
 (0)