ENG | Zephyr RTOS: Data logger skeleton with shell interface
Building a Multi-threaded Data Logger with Shell Interface
When learning embedded systems development, one of the most valuable skills is understanding how to coordinate multiple tasks while maintaining interactive control. This tutorial demonstrates how to build a foundation for a data logger using Zephyr RTOS, showcasing essential concepts like multithreading, atomic variables, and shell commands.
System Architecture
The application uses two main threads: the logger thread handles periodic operations (blinking the red LED when enabled), while Zephyr’s shell subsystem runs in its own background thread for command processing. Communication between threads uses atomic variables to prevent race conditions.
Hardware control is abstracted through Zephyr’s device tree system, with LED definitions coming from aliases in the overlay file. This makes the code portable across different hardware platforms.
Operational Flow
On startup, the blue LED indicates the 60-second interactive window. Users can connect via serial terminal to issue commands:
led on|off
- Controls the green LEDlogger on|off
- Enables or disables loggingtimers
- Shows system timer values
After timeout, the blue LED turns off and the logger thread begins operation. When enabled, the red LED blinks every 50ms to indicate logging activity.
Key Implementation Details
The logger thread sleeps for the full timeout period before starting its main loop, creating a clear separation between interactive and logging modes. The atomic_t logger_enabled flag
provides thread-safe control over logging state.
Shell commands demonstrate typical embedded system patterns: parameter validation, hardware control, and user feedback. The shell runs continuously but doesn’t interfere with the logger thread’s operations.
Extension Points
This foundation supports various enhancements: filesystem integration for data persistence, sensor drivers for actual data collection, power management features, or network connectivity for remote access. The threading model and atomic communication patterns scale well for more complex applications.
The dual-mode approach provides both operational functionality and diagnostic capabilities, making it suitable for development and deployment scenarios where interactive control is needed alongside autonomous operation.
Source code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/shell/shell_uart.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/printk.h>
#include <string.h>
// GPIO definitions
static const struct gpio_dt_spec green_led = GPIO_DT_SPEC_GET(DT_ALIAS(led_green), gpios);
static const struct gpio_dt_spec red_led = GPIO_DT_SPEC_GET(DT_ALIAS(led_red), gpios);
static const struct gpio_dt_spec blue_led = GPIO_DT_SPEC_GET(DT_ALIAS(led_blue), gpios);
// Logger enable flag
static atomic_t logger_enabled = ATOMIC_INIT(1);
// Timeout for console (logging starts after TIMEOUT seconds if not disabled)
#define TIMEOUT 60
// Shell command: control green LED
static int cmd_led(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: led <on|off>");
return -EINVAL;
}
if (strcmp(argv[1], "on") == 0) {
gpio_pin_set_dt(&green_led, 1);
shell_print(shell, "Green LED ON");
} else if (strcmp(argv[1], "off") == 0) {
gpio_pin_set_dt(&green_led, 0);
shell_print(shell, "Green LED OFF");
} else {
shell_error(shell, "Invalid argument: %s", argv[1]);
return -EINVAL;
}
return 0;
}
// Shell command: control logger
static int cmd_logger(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: logger <on|off>");
return -EINVAL;
}
if (strcmp(argv[1], "on") == 0) {
atomic_set(&logger_enabled, 1);
shell_print(shell, "Logger ON");
} else if (strcmp(argv[1], "off") == 0) {
atomic_set(&logger_enabled, 0);
shell_print(shell, "Logger OFF");
} else {
shell_error(shell, "Invalid argument: %s", argv[1]);
return -EINVAL;
}
return 0;
}
static int cmd_timers(const struct shell *shell, size_t argc, char **argv)
{
int64_t uptime_ms = k_uptime_get();
shell_print(shell, "k_uptime_get() returned %lld [ms]", uptime_ms);
int64_t uptime_ticks = k_uptime_ticks();
shell_print(shell, "k_uptime_ticks() returned %lld", uptime_ticks);
uint64_t uptime_us = k_ticks_to_us_floor64(uptime_ticks);
shell_print(shell, "k_ticks_to_us_floor64() returned %llu [us]", uptime_us);
return 0;
}
SHELL_CMD_REGISTER(led, NULL, "Toggle green LED: led <on|off>", cmd_led);
SHELL_CMD_REGISTER(logger, NULL, "Toggle logger: logger <on|off>", cmd_logger);
SHELL_CMD_REGISTER(timers, NULL, "Show timers", cmd_timers);
// Logger thread: blinks red LED every 50 ms when enabled
void logger_thread(void) {
k_sleep(K_MSEC(1000*TIMEOUT));
gpio_pin_set_dt(&blue_led, 0);
while(1) {
k_sleep(K_MSEC(50));
if (atomic_get(&logger_enabled)) {
gpio_pin_toggle_dt(&red_led);
}
}
}
K_THREAD_DEFINE(logger_thread_id, 1024, logger_thread, NULL, NULL, NULL, 5, 0, 0);
int main(void)
{
// Initialize LEDs
if (!device_is_ready(green_led.port) || !device_is_ready(red_led.port)) {
printk("LED devices not ready\n");
return -1;
}
gpio_pin_configure_dt(&green_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&red_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&blue_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_set_dt(&blue_led, 1);
printk("Type logger off in 60 seconds to disable logger\n");
// NOTE: here we have shell thread and logger thread running,
// program won't exit
return 0;
}
1
2
3
4
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(shell-test)
target_sources(app PRIVATE src/main.c)
1
2
3
4
5
6
7
# Shell
CONFIG_SERIAL=y
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL=y
# LED
CONFIG_GPIO=y
1
2
3
4
5
6
7
/ {
aliases {
led-red = &led0;
led-green = &led1;
led-blue = &led2;
};
};
Usage example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\shell-01-led> west build -b xiao_ble/nrf52840
[7/7] Linking C executable zephyr\zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 61276 B 788 KB 7.59%
RAM: 20352 B 256 KB 7.76%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from C:/dev-zephyr/zephyrproject/my_projects/shell-01-led/build/zephyr/zephyr.elf for board: xiao_ble
Converted to uf2, output size: 122880, start address: 0x27000
Wrote 122880 bytes to zephyr.uf2
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\shell-01-led> cp .\build\zephyr\zephyr.uf2 d:
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\shell-01-led> mpremote connect COM11
Connected to MicroPython at COM11
Use Ctrl-] or Ctrl-x to exit this shell
uart:~$ help
Please press the <Tab> button to see all available commands.
You can also use the <Tab> button to prompt or auto-complete all commands or its subcommands.
You can try to call commands with <-h> or <--help> parameter for more information.
Shell supports following meta-keys:
Ctrl + (a key from: abcdefklnpuw)
Alt + (a key from: bf)
Please refer to shell documentation for more details.
Available commands:
clear : Clear screen.
device : Device commands
devmem : Read/write physical memory
Usage:
Read memory at address with optional width:
devmem <address> [<width>]
Write memory at address with mandatory width and value:
devmem <address> <width> <value>
help : Prints the help message.
history : Command history.
kernel : Kernel commands
led : Toggle green LED: led <on|off>
logger : Toggle logger: logger <on|off>
rem : Ignore lines beginning with 'rem '
resize : Console gets terminal screen size or assumes default in case the
readout fails. It must be executed after each terminal width change
to ensure correct text display.
retval : Print return value of most recent command
shell : Useful, not Unix-like shell commands.
timers : Show timers
uart:~$ led on
Green LED ON
uart:~$ logger off
Logger OFF
uart:~$ timers
k_uptime_get() returned 43942 [ms]
k_uptime_ticks() returned 1439984
k_ticks_to_us_floor64() returned 43944824 [us]
uart:~$