Skip to content

Commit f1c8199

Browse files
committed
drivers: display: add HUB12 LED matrix driver
Add driver for HUB12 interface monochrome LED matrix displays. Features: - 32x16 pixel resolution, 1-bit monochrome (PIXEL_FORMAT_MONO01) - SPI-based data transfer with shift registers - Configurable brightness control - Thread-safe framebuffer access with semaphore The driver implements the standard Zephyr display API Signed-off-by: Siratul Islam <[email protected]>
1 parent 1bb0c9e commit f1c8199

File tree

4 files changed

+339
-0
lines changed

4 files changed

+339
-0
lines changed

drivers/display/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_ST7796S display_st7796s.c)
3434
zephyr_library_sources_ifdef(CONFIG_STM32_LTDC display_stm32_ltdc.c)
3535
zephyr_library_sources_ifdef(CONFIG_RM68200 display_rm68200.c)
3636
zephyr_library_sources_ifdef(CONFIG_RM67162 display_rm67162.c)
37+
zephyr_library_sources_ifdef(CONFIG_HUB12 display_hub12.c)
3738
zephyr_library_sources_ifdef(CONFIG_HX8379C display_hx8379c.c)
3839
zephyr_library_sources_ifdef(CONFIG_HX8394 display_hx8394.c)
3940
zephyr_library_sources_ifdef(CONFIG_GC9X01X display_gc9x01x.c)

drivers/display/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ source "drivers/display/Kconfig.max7219"
5151
source "drivers/display/Kconfig.intel_multibootfb"
5252
source "drivers/display/Kconfig.mcux_dcnano_lcdif"
5353
source "drivers/display/Kconfig.otm8009a"
54+
source "drivers/display/Kconfig.hub12"
5455
source "drivers/display/Kconfig.hx8379c"
5556
source "drivers/display/Kconfig.hx8394"
5657
source "drivers/display/Kconfig.gc9x01x"

drivers/display/Kconfig.hub12

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) 2025 Siratul Islam <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config HUB12
5+
bool "HUB12 LED Panel Display Driver"
6+
default y
7+
depends on DT_HAS_HUB12_ENABLED
8+
depends on SPI
9+
depends on GPIO
10+
help
11+
HUB12 LED panel driver for 32x16 monochrome displays

drivers/display/display_hub12.c

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
/*
2+
* Copyright (c) 2025 Siratul Islam <[email protected]>
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Driver for 32x16 monochrome LED panels with HUB12 interface.
6+
*/
7+
8+
#include <zephyr/kernel.h>
9+
#include <zephyr/device.h>
10+
#include <zephyr/drivers/display.h>
11+
#include <zephyr/drivers/gpio.h>
12+
#include <zephyr/drivers/spi.h>
13+
#include <zephyr/logging/log.h>
14+
#include <zephyr/sys/util.h>
15+
#include <string.h>
16+
17+
LOG_MODULE_REGISTER(hub12, CONFIG_DISPLAY_LOG_LEVEL);
18+
19+
#define DT_DRV_COMPAT hub12
20+
21+
/* Display dimensions and layout */
22+
#define HUB12_WIDTH 32
23+
#define HUB12_HEIGHT 16
24+
#define HUB12_ROWS 4
25+
#define HUB12_BYTES_PER_ROW 16
26+
#define HUB12_GROUP_SIZE 4
27+
#define HUB12_NUM_GROUPS 4
28+
29+
/* Brightness control parameters */
30+
#define HUB12_PWM_FREQ 1000
31+
#define HUB12_DEFAULT_BRIGHTNESS 5
32+
#define HUB12_MIN_BRIGHTNESS 1
33+
#define HUB12_MAX_BRIGHTNESS 50
34+
35+
struct hub12_config {
36+
struct gpio_dt_spec pa;
37+
struct gpio_dt_spec pb;
38+
struct gpio_dt_spec pe;
39+
struct gpio_dt_spec plat;
40+
struct spi_dt_spec spi;
41+
};
42+
43+
struct hub12_data {
44+
uint8_t framebuffer[HUB12_WIDTH * HUB12_HEIGHT / 8];
45+
uint8_t cache[HUB12_ROWS][HUB12_BYTES_PER_ROW];
46+
uint8_t current_row;
47+
struct k_timer scan_timer;
48+
struct k_work scan_work;
49+
struct k_sem lock;
50+
const struct device *dev;
51+
uint8_t brightness_us;
52+
};
53+
54+
static void hub12_update_cache(struct hub12_data *data, uint8_t row)
55+
{
56+
const uint8_t *fb = data->framebuffer;
57+
58+
for (int i = 0; i < HUB12_BYTES_PER_ROW; i++) {
59+
int group = i / HUB12_GROUP_SIZE;
60+
int offset = i % HUB12_GROUP_SIZE;
61+
int reverse_offset = (HUB12_GROUP_SIZE - 1) - offset;
62+
int fb_idx = reverse_offset * HUB12_NUM_GROUPS * HUB12_ROWS +
63+
row * HUB12_NUM_GROUPS + group;
64+
65+
data->cache[row][i] = ~fb[fb_idx];
66+
}
67+
}
68+
69+
static void hub12_scan_row(struct hub12_data *data, const struct hub12_config *config)
70+
{
71+
uint8_t row = data->current_row;
72+
int ret;
73+
74+
struct spi_buf tx_buf = {.buf = data->cache[row], .len = HUB12_BYTES_PER_ROW};
75+
struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
76+
77+
ret = spi_write_dt(&config->spi, &tx);
78+
if (ret < 0) {
79+
LOG_ERR("SPI write failed: %d", ret);
80+
return;
81+
}
82+
83+
gpio_pin_set_dt(&config->pe, 0);
84+
85+
gpio_pin_set_dt(&config->plat, 1);
86+
k_busy_wait(1);
87+
gpio_pin_set_dt(&config->plat, 0);
88+
89+
gpio_pin_set_dt(&config->pa, (row & BIT(0)) ? 1 : 0);
90+
gpio_pin_set_dt(&config->pb, (row & BIT(1)) ? 1 : 0);
91+
92+
if (data->brightness_us > 0) {
93+
gpio_pin_set_dt(&config->pe, 1);
94+
k_busy_wait(data->brightness_us);
95+
gpio_pin_set_dt(&config->pe, 0);
96+
}
97+
98+
data->current_row = (data->current_row + 1) % HUB12_ROWS;
99+
100+
hub12_update_cache(data, data->current_row);
101+
}
102+
103+
static void hub12_scan_work_handler(struct k_work *work)
104+
{
105+
struct hub12_data *data = CONTAINER_OF(work, struct hub12_data, scan_work);
106+
const struct hub12_config *config = data->dev->config;
107+
108+
hub12_scan_row(data, config);
109+
}
110+
111+
static void hub12_scan_timer_handler(struct k_timer *timer)
112+
{
113+
struct hub12_data *data = CONTAINER_OF(timer, struct hub12_data, scan_timer);
114+
115+
k_work_submit(&data->scan_work);
116+
}
117+
118+
static int hub12_write(const struct device *dev, const uint16_t x, const uint16_t y,
119+
const struct display_buffer_descriptor *desc, const void *buf)
120+
{
121+
struct hub12_data *data = dev->data;
122+
const uint8_t *src = buf;
123+
size_t fb_size = HUB12_WIDTH * HUB12_HEIGHT / 8;
124+
125+
if (x >= HUB12_WIDTH || y >= HUB12_HEIGHT) {
126+
return -EINVAL;
127+
}
128+
129+
if ((x + desc->width) > HUB12_WIDTH || (y + desc->height) > HUB12_HEIGHT) {
130+
return -EINVAL;
131+
}
132+
133+
if (desc->pitch != desc->width) {
134+
LOG_ERR("Unsupported pitch");
135+
return -ENOTSUP;
136+
}
137+
138+
if (desc->buf_size < (desc->width * desc->height / 8)) {
139+
LOG_ERR("Buffer too small");
140+
return -EINVAL;
141+
}
142+
143+
k_sem_take(&data->lock, K_FOREVER);
144+
145+
if (x == 0 && y == 0 && desc->width == HUB12_WIDTH && desc->height == HUB12_HEIGHT) {
146+
memcpy(data->framebuffer, src, fb_size);
147+
} else {
148+
LOG_WRN("Partial updates not optimized");
149+
memcpy(data->framebuffer, src, fb_size);
150+
}
151+
152+
for (int i = 0; i < HUB12_ROWS; i++) {
153+
hub12_update_cache(data, i);
154+
}
155+
156+
k_sem_give(&data->lock);
157+
158+
return 0;
159+
}
160+
161+
static int hub12_read(const struct device *dev, const uint16_t x, const uint16_t y,
162+
const struct display_buffer_descriptor *desc, void *buf)
163+
{
164+
return -ENOTSUP;
165+
}
166+
167+
static void *hub12_get_framebuffer(const struct device *dev)
168+
{
169+
struct hub12_data *data = dev->data;
170+
171+
return data->framebuffer;
172+
}
173+
174+
static int hub12_blanking_off(const struct device *dev)
175+
{
176+
return 0;
177+
}
178+
179+
static int hub12_blanking_on(const struct device *dev)
180+
{
181+
return 0;
182+
}
183+
184+
static int hub12_set_brightness(const struct device *dev, const uint8_t brightness)
185+
{
186+
struct hub12_data *data = dev->data;
187+
188+
if (brightness == 0) {
189+
data->brightness_us = 0;
190+
} else {
191+
uint32_t range = HUB12_MAX_BRIGHTNESS - HUB12_MIN_BRIGHTNESS;
192+
193+
data->brightness_us = HUB12_MIN_BRIGHTNESS + (uint8_t)((brightness * range) / 255U);
194+
}
195+
196+
LOG_INF("Brightness set to %u us", data->brightness_us);
197+
198+
return 0;
199+
}
200+
201+
static int hub12_set_contrast(const struct device *dev, const uint8_t contrast)
202+
{
203+
return -ENOTSUP;
204+
}
205+
206+
static void hub12_get_capabilities(const struct device *dev, struct display_capabilities *caps)
207+
{
208+
memset(caps, 0, sizeof(*caps));
209+
caps->x_resolution = HUB12_WIDTH;
210+
caps->y_resolution = HUB12_HEIGHT;
211+
caps->supported_pixel_formats = PIXEL_FORMAT_MONO01;
212+
caps->current_pixel_format = PIXEL_FORMAT_MONO01;
213+
caps->screen_info = SCREEN_INFO_MONO_MSB_FIRST;
214+
}
215+
216+
static int hub12_set_pixel_format(const struct device *dev, const enum display_pixel_format pf)
217+
{
218+
if (pf == PIXEL_FORMAT_MONO01) {
219+
return 0;
220+
}
221+
222+
return -ENOTSUP;
223+
}
224+
225+
static int hub12_set_orientation(const struct device *dev,
226+
const enum display_orientation orientation)
227+
{
228+
if (orientation == DISPLAY_ORIENTATION_NORMAL) {
229+
return 0;
230+
}
231+
232+
return -ENOTSUP;
233+
}
234+
235+
static const struct display_driver_api hub12_api = {
236+
.blanking_on = hub12_blanking_on,
237+
.blanking_off = hub12_blanking_off,
238+
.write = hub12_write,
239+
.read = hub12_read,
240+
.get_framebuffer = hub12_get_framebuffer,
241+
.set_brightness = hub12_set_brightness,
242+
.set_contrast = hub12_set_contrast,
243+
.get_capabilities = hub12_get_capabilities,
244+
.set_pixel_format = hub12_set_pixel_format,
245+
.set_orientation = hub12_set_orientation,
246+
};
247+
248+
static int hub12_init(const struct device *dev)
249+
{
250+
struct hub12_data *data = dev->data;
251+
const struct hub12_config *config = dev->config;
252+
int ret;
253+
254+
data->dev = dev;
255+
256+
if (!gpio_is_ready_dt(&config->pa) || !gpio_is_ready_dt(&config->pb) ||
257+
!gpio_is_ready_dt(&config->pe) || !gpio_is_ready_dt(&config->plat)) {
258+
LOG_ERR("GPIO devices not ready");
259+
return -ENODEV;
260+
}
261+
262+
ret = gpio_pin_configure_dt(&config->pa, GPIO_OUTPUT_INACTIVE);
263+
if (ret < 0) {
264+
return ret;
265+
}
266+
267+
ret = gpio_pin_configure_dt(&config->pb, GPIO_OUTPUT_INACTIVE);
268+
if (ret < 0) {
269+
return ret;
270+
}
271+
272+
ret = gpio_pin_configure_dt(&config->pe, GPIO_OUTPUT_INACTIVE);
273+
if (ret < 0) {
274+
return ret;
275+
}
276+
277+
ret = gpio_pin_configure_dt(&config->plat, GPIO_OUTPUT_INACTIVE);
278+
if (ret < 0) {
279+
return ret;
280+
}
281+
282+
if (!spi_is_ready_dt(&config->spi)) {
283+
LOG_ERR("SPI device not ready");
284+
return -ENODEV;
285+
}
286+
287+
memset(data->framebuffer, 0, sizeof(data->framebuffer));
288+
memset(data->cache, 0, sizeof(data->cache));
289+
data->current_row = 0;
290+
data->brightness_us = HUB12_DEFAULT_BRIGHTNESS;
291+
292+
ret = k_sem_init(&data->lock, 1, 1);
293+
if (ret < 0) {
294+
LOG_ERR("Failed to initialize semaphore");
295+
return ret;
296+
}
297+
298+
for (int i = 0; i < HUB12_ROWS; i++) {
299+
hub12_update_cache(data, i);
300+
}
301+
302+
k_work_init(&data->scan_work, hub12_scan_work_handler);
303+
k_timer_init(&data->scan_timer, hub12_scan_timer_handler, NULL);
304+
k_timer_start(&data->scan_timer, K_MSEC(1), K_MSEC(1));
305+
306+
LOG_INF("HUB12 display initialized: %dx%d", HUB12_WIDTH, HUB12_HEIGHT);
307+
308+
return 0;
309+
}
310+
311+
#define HUB12_INIT(inst) \
312+
static struct hub12_data hub12_data_##inst; \
313+
\
314+
static const struct hub12_config hub12_config_##inst = { \
315+
.pa = GPIO_DT_SPEC_INST_GET(inst, pa_gpios), \
316+
.pb = GPIO_DT_SPEC_INST_GET(inst, pb_gpios), \
317+
.pe = GPIO_DT_SPEC_INST_GET(inst, pe_gpios), \
318+
.plat = GPIO_DT_SPEC_INST_GET(inst, plat_gpios), \
319+
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | \
320+
SPI_TRANSFER_LSB), \
321+
}; \
322+
\
323+
DEVICE_DT_INST_DEFINE(inst, hub12_init, NULL, &hub12_data_##inst, &hub12_config_##inst, \
324+
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &hub12_api);
325+
326+
DT_INST_FOREACH_STATUS_OKAY(HUB12_INIT)

0 commit comments

Comments
 (0)