Skip to content

Commit 5be38c6

Browse files
xingrzkartben
authored andcommitted
drivers: auxdisplay: Add driver for common 7-segment display
This commit introduces a new driver for a common GPIO-driven 7-segment display. supporting both common anode and common cathode configurations. Signed-off-by: Chen Xingyu <hi@xingrz.me>
1 parent 81ee157 commit 5be38c6

File tree

5 files changed

+364
-0
lines changed

5 files changed

+364
-0
lines changed

drivers/auxdisplay/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/auxdisplay.h)
44

55
zephyr_library()
66
zephyr_library_sources_ifdef(CONFIG_USERSPACE auxdisplay_handlers.c)
7+
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_GPIO_7SEG auxdisplay_gpio_7seg.c)
78
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_HD44780 auxdisplay_hd44780.c)
89
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_ITRON auxdisplay_itron.c)
910
zephyr_library_sources_ifdef(CONFIG_AUXDISPLAY_JHD1313 auxdisplay_jhd1313.c)

drivers/auxdisplay/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module = AUXDISPLAY
2020
module-str = auxdisplay
2121
source "subsys/logging/Kconfig.template.log_config"
2222

23+
source "drivers/auxdisplay/Kconfig.gpio"
2324
source "drivers/auxdisplay/Kconfig.hd44780"
2425
source "drivers/auxdisplay/Kconfig.itron"
2526
source "drivers/auxdisplay/Kconfig.jhd1313"

drivers/auxdisplay/Kconfig.gpio

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2024-2025 Chen Xingyu <hi@xingrz.me>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config AUXDISPLAY_GPIO_7SEG
5+
bool "Common GPIO-driven 7-Segment Display"
6+
default y
7+
depends on DT_HAS_GPIO_7_SEGMENT_ENABLED
8+
select GPIO
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/*
2+
* Copyright (c) 2024-2025 Chen Xingyu <hi@xingrz.me>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#define DT_DRV_COMPAT gpio_7_segment
7+
8+
#include <zephyr/device.h>
9+
#include <zephyr/kernel.h>
10+
#include <zephyr/drivers/auxdisplay.h>
11+
#include <zephyr/drivers/gpio.h>
12+
13+
#include <zephyr/logging/log.h>
14+
LOG_MODULE_REGISTER(auxdisplay_gpio_7seg, CONFIG_AUXDISPLAY_LOG_LEVEL);
15+
16+
static const uint8_t DIGITS[] = {
17+
[0] = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5),
18+
[1] = BIT(1) | BIT(2),
19+
[2] = BIT(0) | BIT(1) | BIT(3) | BIT(4) | BIT(6),
20+
[3] = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(6),
21+
[4] = BIT(1) | BIT(2) | BIT(5) | BIT(6),
22+
[5] = BIT(0) | BIT(2) | BIT(3) | BIT(5) | BIT(6),
23+
[6] = BIT(0) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6),
24+
[7] = BIT(0) | BIT(1) | BIT(2),
25+
[8] = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6),
26+
[9] = BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(5) | BIT(6),
27+
};
28+
29+
#define DP (BIT(7))
30+
#define BLANK (0x00)
31+
32+
struct auxdisplay_gpio_7seg_data {
33+
struct k_timer timer;
34+
uint32_t refresh_pos;
35+
int16_t cursor_x;
36+
int16_t cursor_y;
37+
};
38+
39+
struct auxdisplay_gpio_7seg_config {
40+
struct auxdisplay_capabilities capabilities;
41+
const struct gpio_dt_spec *segment_gpios;
42+
const struct gpio_dt_spec *digit_gpios;
43+
uint32_t segment_count;
44+
uint32_t digit_count;
45+
uint32_t refresh_period_ms;
46+
uint8_t *buffer;
47+
};
48+
49+
static int auxdisplay_gpio_7seg_display_on(const struct device *dev)
50+
{
51+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
52+
struct auxdisplay_gpio_7seg_data *data = dev->data;
53+
54+
data->refresh_pos = 0;
55+
k_timer_start(&data->timer, K_NO_WAIT, K_MSEC(cfg->refresh_period_ms));
56+
57+
return 0;
58+
}
59+
60+
static int auxdisplay_gpio_7seg_display_off(const struct device *dev)
61+
{
62+
struct auxdisplay_gpio_7seg_data *data = dev->data;
63+
64+
k_timer_stop(&data->timer);
65+
66+
return 0;
67+
}
68+
69+
static int auxdisplay_gpio_7seg_cursor_position_set(const struct device *dev,
70+
enum auxdisplay_position type, int16_t x,
71+
int16_t y)
72+
{
73+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
74+
struct auxdisplay_gpio_7seg_data *data = dev->data;
75+
76+
if (type == AUXDISPLAY_POSITION_RELATIVE) {
77+
x += data->cursor_x;
78+
y += data->cursor_y;
79+
} else if (type == AUXDISPLAY_POSITION_RELATIVE_DIRECTION) {
80+
return -EINVAL;
81+
}
82+
83+
if (x < 0 || y < 0) {
84+
return -EINVAL;
85+
} else if (x >= cfg->capabilities.columns || y >= cfg->capabilities.rows) {
86+
return -EINVAL;
87+
}
88+
89+
data->cursor_x = x;
90+
data->cursor_y = y;
91+
92+
return 0;
93+
}
94+
95+
static int auxdisplay_gpio_7seg_cursor_position_get(const struct device *dev, int16_t *x,
96+
int16_t *y)
97+
{
98+
struct auxdisplay_gpio_7seg_data *data = dev->data;
99+
100+
*x = data->cursor_x;
101+
*y = data->cursor_y;
102+
103+
return 0;
104+
}
105+
106+
static int auxdisplay_gpio_7seg_capabilities_get(const struct device *dev,
107+
struct auxdisplay_capabilities *capabilities)
108+
{
109+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
110+
111+
memcpy(capabilities, &cfg->capabilities, sizeof(struct auxdisplay_capabilities));
112+
113+
return 0;
114+
}
115+
116+
static int auxdisplay_gpio_7seg_clear(const struct device *dev)
117+
{
118+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
119+
struct auxdisplay_gpio_7seg_data *data = dev->data;
120+
121+
memset(cfg->buffer, 0, cfg->digit_count);
122+
data->refresh_pos = 0;
123+
data->cursor_x = 0;
124+
data->cursor_y = 0;
125+
126+
return 0;
127+
}
128+
129+
static int auxdisplay_gpio_7seg_write(const struct device *dev, const uint8_t *ch, uint16_t len)
130+
{
131+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
132+
struct auxdisplay_gpio_7seg_data *data = dev->data;
133+
uint32_t i, cursor;
134+
135+
for (i = 0; i < len; i++) {
136+
cursor = data->cursor_y * cfg->capabilities.columns + data->cursor_x;
137+
138+
/*
139+
* Special case where the decimal point should be added to the
140+
* previous digit
141+
*/
142+
if (ch[i] == '.' && cursor > 0) {
143+
cfg->buffer[cursor - 1] |= DP;
144+
continue;
145+
}
146+
147+
if (cursor >= cfg->digit_count) {
148+
break;
149+
}
150+
151+
if (ch[i] >= '0' && ch[i] <= '9') {
152+
cfg->buffer[cursor] = DIGITS[ch[i] - '0'];
153+
} else if (ch[i] == '.') {
154+
/* Leading dot, leave the digit blank */
155+
cfg->buffer[cursor] = DP;
156+
} else {
157+
cfg->buffer[cursor] = BLANK;
158+
}
159+
160+
/* Move the cursor */
161+
if (data->cursor_x < cfg->capabilities.columns - 1) {
162+
data->cursor_x++;
163+
} else if (data->cursor_y < cfg->capabilities.rows - 1) {
164+
data->cursor_x = 0;
165+
data->cursor_y++;
166+
}
167+
}
168+
169+
/* Reset the refresh position */
170+
data->refresh_pos = 0;
171+
172+
return 0;
173+
}
174+
175+
static void auxdisplay_gpio_7seg_timer_expiry_fn(struct k_timer *timer)
176+
{
177+
const struct device *dev = k_timer_user_data_get(timer);
178+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
179+
struct auxdisplay_gpio_7seg_data *data = dev->data;
180+
181+
/* Turn off the current digit and move to the next one */
182+
gpio_pin_set_dt(&cfg->digit_gpios[data->refresh_pos], 0);
183+
data->refresh_pos = (data->refresh_pos + 1) % cfg->digit_count;
184+
185+
/* Set the segments for the new digit */
186+
for (uint32_t i = 0; i < cfg->segment_count; i++) {
187+
gpio_pin_set_dt(&cfg->segment_gpios[i], cfg->buffer[data->refresh_pos] & BIT(i));
188+
}
189+
190+
/* Turn on the new digit */
191+
gpio_pin_set_dt(&cfg->digit_gpios[data->refresh_pos], 1);
192+
}
193+
194+
static void auxdisplay_gpio_7seg_timer_stop_fn(struct k_timer *timer)
195+
{
196+
const struct device *dev = k_timer_user_data_get(timer);
197+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
198+
199+
/* Turn off all digits */
200+
for (uint32_t i = 0; i < cfg->digit_count; i++) {
201+
gpio_pin_set_dt(&cfg->digit_gpios[i], 0);
202+
}
203+
}
204+
205+
static int auxdisplay_gpio_7seg_init(const struct device *dev)
206+
{
207+
const struct auxdisplay_gpio_7seg_config *cfg = dev->config;
208+
struct auxdisplay_gpio_7seg_data *data = dev->data;
209+
210+
for (uint32_t i = 0; i < cfg->segment_count; i++) {
211+
gpio_pin_configure_dt(&cfg->segment_gpios[i], GPIO_OUTPUT_INACTIVE);
212+
}
213+
214+
for (uint32_t i = 0; i < cfg->digit_count; i++) {
215+
gpio_pin_configure_dt(&cfg->digit_gpios[i], GPIO_OUTPUT_INACTIVE);
216+
}
217+
218+
k_timer_init(&data->timer, auxdisplay_gpio_7seg_timer_expiry_fn,
219+
auxdisplay_gpio_7seg_timer_stop_fn);
220+
k_timer_user_data_set(&data->timer, (void *)dev);
221+
222+
auxdisplay_gpio_7seg_display_on(dev);
223+
224+
return 0;
225+
}
226+
227+
static DEVICE_API(auxdisplay, auxdisplay_gpio_7seg_api) = {
228+
.display_on = auxdisplay_gpio_7seg_display_on,
229+
.display_off = auxdisplay_gpio_7seg_display_off,
230+
.cursor_position_set = auxdisplay_gpio_7seg_cursor_position_set,
231+
.cursor_position_get = auxdisplay_gpio_7seg_cursor_position_get,
232+
.capabilities_get = auxdisplay_gpio_7seg_capabilities_get,
233+
.clear = auxdisplay_gpio_7seg_clear,
234+
.write = auxdisplay_gpio_7seg_write,
235+
};
236+
237+
#define AUXDISPLAY_GPIO_7SEG_INST(n) \
238+
static struct auxdisplay_gpio_7seg_data auxdisplay_gpio_7seg_data_##n; \
239+
\
240+
static const struct gpio_dt_spec auxdisplay_gpio_7seg_segment_gpios_##n[] = { \
241+
DT_INST_FOREACH_PROP_ELEM_SEP(n, segment_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,))}; \
242+
\
243+
static const struct gpio_dt_spec auxdisplay_gpio_7seg_digit_gpios_##n[] = { \
244+
DT_INST_FOREACH_PROP_ELEM_SEP(n, digit_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,))}; \
245+
\
246+
static uint8_t auxdisplay_gpio_7seg_buffer_##n[DT_INST_PROP_LEN(n, digit_gpios)]; \
247+
\
248+
static const struct auxdisplay_gpio_7seg_config auxdisplay_gpio_7seg_config_##n = { \
249+
.capabilities = \
250+
{ \
251+
.columns = DT_INST_PROP(n, columns), \
252+
.rows = DT_INST_PROP(n, rows), \
253+
}, \
254+
.segment_gpios = auxdisplay_gpio_7seg_segment_gpios_##n, \
255+
.segment_count = DT_INST_PROP_LEN(n, segment_gpios), \
256+
.digit_gpios = auxdisplay_gpio_7seg_digit_gpios_##n, \
257+
.digit_count = DT_INST_PROP_LEN(n, digit_gpios), \
258+
.refresh_period_ms = DT_INST_PROP(n, refresh_period_ms), \
259+
.buffer = auxdisplay_gpio_7seg_buffer_##n, \
260+
}; \
261+
\
262+
DEVICE_DT_INST_DEFINE(n, auxdisplay_gpio_7seg_init, NULL, &auxdisplay_gpio_7seg_data_##n, \
263+
&auxdisplay_gpio_7seg_config_##n, POST_KERNEL, \
264+
CONFIG_AUXDISPLAY_INIT_PRIORITY, &auxdisplay_gpio_7seg_api);
265+
266+
DT_INST_FOREACH_STATUS_OKAY(AUXDISPLAY_GPIO_7SEG_INST)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Copyright (c) 2024-2025 Chen Xingyu <hi@xingrz.me>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: |
5+
Common GPIO-driven 7-Segment Display
6+
7+
A 7-segment display is a form of electronic display device for displaying
8+
decimal numerals that is an alternative to the more complex dot matrix
9+
displays. Seven-segment displays are widely used in digital clocks, electronic
10+
meters, basic calculators, and other electronic devices that display numerical
11+
information.
12+
13+
This driver supports 7-segment displays driven by GPIOs, in a widely used
14+
circuit design either common anode or common cathode.
15+
16+
In common anode design, digit-gpios and segment-gpios are configured as active
17+
high and active low respectively, meaning that the current flows from
18+
digit-gpios to segment-gpios. Vice versa for common cathode.
19+
20+
Example:
21+
22+
#include <zephyr/dt-bindings/gpio/gpio.h>
23+
24+
/ {
25+
auxdisplay_0: digi-display {
26+
compatible = "gpio-7-segment";
27+
columns = <3>;
28+
rows = <1>;
29+
segment-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>, /* A */
30+
<&gpio0 1 GPIO_ACTIVE_LOW>, /* B */
31+
<&gpio0 2 GPIO_ACTIVE_LOW>, /* C */
32+
<&gpio0 3 GPIO_ACTIVE_LOW>, /* D */
33+
<&gpio0 4 GPIO_ACTIVE_LOW>, /* E */
34+
<&gpio0 5 GPIO_ACTIVE_LOW>, /* F */
35+
<&gpio0 6 GPIO_ACTIVE_LOW>, /* G */
36+
<&gpio0 7 GPIO_ACTIVE_LOW>; /* DP */
37+
digit-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>, /* DIG1 */
38+
<&gpio1 1 GPIO_ACTIVE_HIGH>, /* DIG2 */
39+
<&gpio1 2 GPIO_ACTIVE_HIGH>; /* DIG3 */
40+
refresh-period-ms = <1>;
41+
};
42+
};
43+
44+
compatible: "gpio-7-segment"
45+
46+
include: auxdisplay-device.yaml
47+
48+
properties:
49+
segment-gpios:
50+
type: phandle-array
51+
required: true
52+
description: |
53+
GPIOs used to control the segments.
54+
55+
Segments are usually labeled A to G, and DP for the decimal point. The
56+
GPIOs must be listed in the order A, B, C, D, E, F, G and an optional DP.
57+
The following diagram shows the segment layout:
58+
59+
-- A --
60+
| |
61+
F B
62+
| |
63+
-- G --
64+
| |
65+
E C
66+
| |
67+
-- D -- (DP)
68+
69+
The number of GPIOs must be 7 for a 7-segment display, or 8 for a 7-segment
70+
display with a decimal point.
71+
72+
digit-gpios:
73+
type: phandle-array
74+
required: true
75+
description: |
76+
GPIOs used to control the digits (the common anodes or cathodes).
77+
78+
The number of GPIOs must match the number of digits.
79+
80+
refresh-period-ms:
81+
type: int
82+
required: true
83+
description: |
84+
The refresh period in milliseconds.
85+
86+
This is the time between the display of each digit. The refresh period
87+
must be long enough to allow the segments to be driven and the digit to
88+
be selected.

0 commit comments

Comments
 (0)