zephyr/drivers/led_strip/apa102.c
Cyril Fougeray 89594e3ada apa102: fix end frame
end frame is used to supply clock pulses so that data goes to last
LED in the chain. Thus, it depends on the number of LEDs in the chain.
Previously, the number of ones sent into the end frame was
hard-coded and limited the usage of the driver to 64 LEDs in the
strip.

Signed-off-by: Cyril Fougeray <cyril.fougeray@toolsforhumanity.com>
2025-01-14 20:52:08 +01:00

135 lines
3.3 KiB
C

/*
* Copyright (c) 2018 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT apa_apa102
#include <errno.h>
#include <zephyr/drivers/led_strip.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
struct apa102_config {
struct spi_dt_spec bus;
size_t length;
uint8_t *const end_frame;
const size_t end_frame_size;
};
static int apa102_update(const struct device *dev, void *buf, size_t size)
{
const struct apa102_config *config = dev->config;
static const uint8_t zeros[] = { 0, 0, 0, 0 };
const struct spi_buf tx_bufs[] = {
{
/* Start frame: at least 32 zeros */
.buf = (uint8_t *)zeros,
.len = sizeof(zeros),
},
{
/* LED data itself */
.buf = buf,
.len = size,
},
{
/* End frame: at least 32 ones to clock the
* remaining bits to the LEDs at the end of
* the strip.
*/
.buf = (uint8_t *)config->end_frame,
.len = config->end_frame_size,
},
};
const struct spi_buf_set tx = {
.buffers = tx_bufs,
.count = ARRAY_SIZE(tx_bufs)
};
return spi_write_dt(&config->bus, &tx);
}
static int apa102_update_rgb(const struct device *dev, struct led_rgb *pixels,
size_t count)
{
uint8_t *p = (uint8_t *)pixels;
size_t i;
/* SOF (3 bits) followed by the 0 to 31 global dimming level */
uint8_t prefix = 0xE0 | 31;
/* Rewrite to the on-wire format */
for (i = 0; i < count; i++) {
uint8_t r = pixels[i].r;
uint8_t g = pixels[i].g;
uint8_t b = pixels[i].b;
*p++ = prefix;
*p++ = b;
*p++ = g;
*p++ = r;
}
BUILD_ASSERT(sizeof(struct led_rgb) == 4);
return apa102_update(dev, pixels, sizeof(struct led_rgb) * count);
}
static size_t apa102_length(const struct device *dev)
{
const struct apa102_config *config = dev->config;
return config->length;
}
static int apa102_init(const struct device *dev)
{
const struct apa102_config *config = dev->config;
if (!spi_is_ready_dt(&config->bus)) {
return -ENODEV;
}
memset(config->end_frame, 0xFF, config->end_frame_size);
return 0;
}
static DEVICE_API(led_strip, apa102_api) = {
.update_rgb = apa102_update_rgb,
.length = apa102_length,
};
/*
* The "End frame" is statically allocated, as a sequence of 0xFF bytes
* The only function of the “End frame” is to supply more clock pulses
* to the string until the data has permeated to the last LED. The
* number of clock pulses required is exactly half the total number
* of LEDs in the string. See below `end_frame`.
*/
#define APA102_DEVICE(idx) \
static uint8_t apa102_end_frame_##idx \
[(DT_INST_PROP(idx, chain_length) / \
sizeof(struct led_rgb) / 2) + 1]; \
static const struct apa102_config apa102_##idx##_config = { \
.bus = SPI_DT_SPEC_INST_GET( \
idx, \
SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), \
0), \
.length = DT_INST_PROP(idx, chain_length), \
.end_frame = apa102_end_frame_##idx, \
.end_frame_size = (DT_INST_PROP(idx, chain_length) / \
sizeof(struct led_rgb) / 2) + 1, \
}; \
\
DEVICE_DT_INST_DEFINE(idx, \
apa102_init, \
NULL, \
NULL, \
&apa102_##idx##_config, \
POST_KERNEL, \
CONFIG_LED_STRIP_INIT_PRIORITY, \
&apa102_api);
DT_INST_FOREACH_STATUS_OKAY(APA102_DEVICE)