d1326e81bc
Although the LED might be RGB-only, the LED data is packed in a 32-bit long variable and, then, this is the default size of a LED pixel to define the 'WS2812_RW_PIXEL_SIZE' macro. Please note that the lower-half driver will deal with the case of the addressable LED being 3 or 4-pixel sized.
852 lines
24 KiB
C
852 lines
24 KiB
C
/****************************************************************************
|
|
* drivers/leds/ws2812.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/mutex.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/leds/ws2812.h>
|
|
|
|
#ifndef CONFIG_WS2812_NON_SPI_DRIVER
|
|
#include <nuttx/spi/spi.h>
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
#ifdef CONFIG_WS2812
|
|
|
|
/****************************************************************************
|
|
* ######## ATTENTION #######
|
|
* This file contains code that supports two separate ws2812 upper-half
|
|
* models:
|
|
*
|
|
* If CONFIG_WS2812_NON_SPI_DRIVER is NOT defined the older upper-half
|
|
* code that uses SPI to send the serial data will be built.
|
|
*
|
|
* If WS2812_NEW_MODEL_DRIVER is defined the upper-half code that does
|
|
* not relay on SPI will be built.
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
/* In order to meet the signaling timing requirements, the waveforms required
|
|
* to represent a 0/1 symbol are created by specific SPI bytes defined here.
|
|
*
|
|
* Only two target frequencies: 4 MHz and 8 MHz. However, given the tolerance
|
|
* allowed in the WS2812 timing specs, two ranges around those target
|
|
* frequencies can be used for better flexibility. Extreme frequencies
|
|
* rounded to the nearest multiple of 100 kHz which meets the specs.
|
|
* Try to avoid using the extreme frequencies.
|
|
*
|
|
* If using an LED different to the WS2812 (e.g. WS2812B) check its timing
|
|
* specs, which may vary slightly, to decide which frequency is safe to use.
|
|
*
|
|
* WS2812 specs:
|
|
* T0H range: 200ns - 500ns
|
|
* T1H range: 550ns - 850ns
|
|
* Reset: low signal >50us
|
|
*/
|
|
|
|
#if CONFIG_WS2812_FREQUENCY >= 3600000 && CONFIG_WS2812_FREQUENCY <= 5000000
|
|
# define WS2812_ZERO_BYTE 0b01000000 /* 200ns at 5 MHz, 278ns at 3.6 MHz */
|
|
# define WS2812_ONE_BYTE 0b01110000 /* 600ns at 5 MHz, 833ns at 3.6 MHz */
|
|
#elif CONFIG_WS2812_FREQUENCY >= 5900000 && CONFIG_WS2812_FREQUENCY <= 9000000
|
|
# define WS2812_ZERO_BYTE 0b01100000 /* 222ns at 9 MHz, 339ns at 5.9 MHz */
|
|
# define WS2812_ONE_BYTE 0b01111100 /* 556ns at 9 MHz, 847ns at 5.9 MHz */
|
|
#else
|
|
# error "Unsupported SPI Frequency"
|
|
#endif
|
|
|
|
/* Reset bytes
|
|
* Number of empty bytes to create the reset low pulse
|
|
* Aiming for 60 us, safely above the 50us required.
|
|
*/
|
|
|
|
#define WS2812_RST_CYCLES (CONFIG_WS2812_FREQUENCY * 60 / 1000000 / 8)
|
|
|
|
#define WS2812_BYTES_PER_LED (8 * 3)
|
|
|
|
/* Transmit buffer looks like:
|
|
* [<----N reset bytes---->|<-RGBn->...<-RGB0->|<----1 reset byte---->]
|
|
*
|
|
* It is important that this is shipped as close to one chunk as possible
|
|
* in order to meet timing requirements and to keep MOSI from going high
|
|
* between transactions. Some chips will leave MOSI at the state of the
|
|
* MSB of the last byte for this reason it is recommended to shift the
|
|
* bits that represents the zero or one waveform so that the MSB is 0.
|
|
* The reset byte after the RGB data will pad the shortened low at the end.
|
|
*/
|
|
|
|
#define TXBUFF_SIZE(n) (WS2812_RST_CYCLES + n * WS2812_BYTES_PER_LED + 1)
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
struct ws2812_dev_s
|
|
{
|
|
FAR struct spi_dev_s *spi; /* SPI interface */
|
|
uint16_t nleds; /* Number of addressable LEDs */
|
|
FAR uint8_t *tx_buf; /* Buffer for write transaction and state */
|
|
mutex_t lock; /* Assures exclusive access to the driver */
|
|
};
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
static inline void ws2812_configspi(FAR struct spi_dev_s *spi);
|
|
static void ws2812_pack(FAR uint8_t *buf, uint32_t rgb);
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/* Character driver methods */
|
|
|
|
#ifdef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
static ssize_t ws2812_open(FAR struct file *filep);
|
|
|
|
static ssize_t ws2812_close(FAR struct file *filep);
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
static ssize_t ws2812_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t ws2812_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static off_t ws2812_seek(FAR struct file *filep, off_t offset, int whence);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_ws2812fops =
|
|
{
|
|
#ifdef CONFIG_WS2812_NON_SPI_DRIVER
|
|
ws2812_open, /* open */
|
|
ws2812_close, /* close */
|
|
#else /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
NULL, /* open */
|
|
NULL, /* close */
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
ws2812_read, /* read */
|
|
ws2812_write, /* write */
|
|
ws2812_seek, /* seek */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* #### TODO ####
|
|
*
|
|
* Consider supporting mmap by returning memory buffer using file_operations'
|
|
* mmap
|
|
* Code using this would be non-portable across architectures as the format
|
|
* of the buffer can be different.
|
|
*
|
|
* Consider supporting rectangular arrays of ws2812s as a video output
|
|
* device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Table of Gamma Correction Values
|
|
*
|
|
* This table is based on:
|
|
* y = 255 * (x / 255)^2.6
|
|
****************************************************************************/
|
|
|
|
static const uint8_t ws2812_gamma[256] =
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,
|
|
3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
|
|
6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
|
|
11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17,
|
|
17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
|
|
25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35,
|
|
36, 37, 38, 38, 39, 40, 41, 42, 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, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81,
|
|
82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102,
|
|
103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125,
|
|
127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152,
|
|
154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182,
|
|
184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
|
|
218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
|
|
255
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Table for HSV to RGB conversion
|
|
****************************************************************************/
|
|
|
|
static const uint8_t hsv_rgb[43] =
|
|
{
|
|
0, 6, 12, 18, 24, 30, 36, 43, 49, 55,
|
|
61, 67, 73, 79, 85, 91, 97, 103, 109, 115,
|
|
121, 128, 134, 140, 146, 152, 158, 164, 170, 176,
|
|
182, 188, 194, 200, 206, 213, 219, 225, 231, 237,
|
|
243, 249, 255
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_open
|
|
*
|
|
* Description:
|
|
* Prepare the ws2812 for use. This method just calls the lower-half
|
|
* open routine if one exists.
|
|
*
|
|
* Input Parameters:
|
|
* filep - Pointer system file data
|
|
*
|
|
* Returned Value:
|
|
* A pointer to an internal structure used by rp2040_ws2812
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t ws2812_open(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
int res;
|
|
|
|
res = priv->open(filep);
|
|
|
|
return res;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_close
|
|
*
|
|
* Description:
|
|
* Cleanup after use. This method just calls the lower-half
|
|
* open routine if one exists.
|
|
*
|
|
* Input Parameters:
|
|
* filep - Pointer system file data
|
|
*
|
|
* Returned Value:
|
|
* OK if successful, or an error code on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int ws2812_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
int res = OK;
|
|
|
|
if (priv != NULL && priv->close != NULL)
|
|
{
|
|
res = priv->close(filep);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_write
|
|
* Description:
|
|
* Updates the data buffer with the supplied data any then sends the data
|
|
* to the LEDs. A write length of zero does not update data but will
|
|
* re-send the data to the leds.
|
|
*
|
|
* Input Parameter:
|
|
* filep - Pointer system file data
|
|
* data - Data to send.
|
|
* len - Length of data in bytes.
|
|
*
|
|
* Returned Value:
|
|
* number of bytes written on success, ERROR if write fails.
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t ws2812_write(FAR struct file *filep,
|
|
FAR const char *data,
|
|
size_t len)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
ssize_t res;
|
|
|
|
if ((len % WS2812_RW_PIXEL_SIZE) != 0)
|
|
{
|
|
lederr("ERROR: LED values must be 24bit packed in 32bit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = priv->write(filep, data, len);
|
|
|
|
return res;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_read
|
|
* Description:
|
|
* Fetches data from the pixel buffer.
|
|
*
|
|
* Input Parameter:
|
|
* filep - Pointer system file data
|
|
* data - pointer to receive buffer.
|
|
* len - Length to read in bytes.
|
|
*
|
|
* Returned Value:
|
|
* number of bytes read on success, ERROR if read fails.
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t ws2812_read(FAR struct file *filep,
|
|
FAR char *data,
|
|
size_t len)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
ssize_t res;
|
|
|
|
if (priv == NULL || priv->read == NULL)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
if ((len % WS2812_RW_PIXEL_SIZE) != 0)
|
|
{
|
|
lederr("ERROR: LED values must be packed in 32bit words.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = priv->read(filep, data, len);
|
|
|
|
return res;
|
|
}
|
|
|
|
#else /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_configspi
|
|
*
|
|
* Description:
|
|
* Set the SPI bus configuration
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline void ws2812_configspi(FAR struct spi_dev_s *spi)
|
|
{
|
|
/* Configure SPI for the WS2812
|
|
* There is no CS on this device we just use MOSI and it is exclusive
|
|
*/
|
|
|
|
SPI_LOCK(spi, true); /* Exclusive use of the bus */
|
|
SPI_SETMODE(spi, SPIDEV_MODE3);
|
|
SPI_SETBITS(spi, 8);
|
|
SPI_HWFEATURES(spi, 0);
|
|
SPI_SETFREQUENCY(spi, CONFIG_WS2812_FREQUENCY);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_pack
|
|
*
|
|
* Description:
|
|
* This writes the expanded SPI transaction to the transaction buffer
|
|
* for a given 24bit RGB value.
|
|
*
|
|
* Input Parameters:
|
|
* buf - The location in the transmit buffer to write.
|
|
* rgb - A 24bit RGB color 8bit red, 8-bit green, 8-bit blue
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void ws2812_pack(FAR uint8_t *buf, uint32_t rgb)
|
|
{
|
|
uint8_t bit_idx;
|
|
uint8_t byte_idx;
|
|
uint8_t offset = 0;
|
|
uint8_t color;
|
|
uint32_t grb;
|
|
|
|
grb = (rgb & 0x00ff00) << 8;
|
|
grb |= (rgb & 0xff0000) >> 8;
|
|
grb |= rgb & 0x0000ff;
|
|
|
|
for (byte_idx = 0; byte_idx < 3; byte_idx++)
|
|
{
|
|
color = (uint8_t)(grb >> (8 * (2 - byte_idx)));
|
|
for (bit_idx = 0; bit_idx < 8; bit_idx++)
|
|
{
|
|
if (color & (1 << (7 - bit_idx)))
|
|
{
|
|
buf[offset] = WS2812_ONE_BYTE;
|
|
}
|
|
else
|
|
{
|
|
buf[offset] = WS2812_ZERO_BYTE;
|
|
}
|
|
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t ws2812_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_write
|
|
*
|
|
* Description:
|
|
* This routine is called when writing to the WS2812 device. Data buffer
|
|
* should be an array of 32bit values holding 24bits of color information
|
|
* in host byte ordering 0x**rrggbb.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static ssize_t ws2812_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
FAR uint8_t *tx_pixel;
|
|
FAR uint32_t *pixel_buf = (FAR uint32_t *)buffer;
|
|
size_t cur_led;
|
|
size_t start_led;
|
|
size_t end_led;
|
|
size_t written = 0;
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
lederr("ERROR: Buffer is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* We need at least one LED, so 1 byte */
|
|
|
|
if (buflen < 1)
|
|
{
|
|
lederr("ERROR: You need to control at least 1 LED!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((buflen % WS2812_RW_PIXEL_SIZE) != 0)
|
|
{
|
|
lederr("ERROR: LED values must be 24bit packed in 32bit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
nxmutex_lock(&priv->lock);
|
|
|
|
start_led = filep->f_pos / WS2812_RW_PIXEL_SIZE;
|
|
tx_pixel = priv->tx_buf + WS2812_RST_CYCLES + \
|
|
start_led * WS2812_BYTES_PER_LED;
|
|
|
|
end_led = start_led + (buflen / WS2812_RW_PIXEL_SIZE) - 1;
|
|
ledinfo("Start: %d End: %d\n", start_led, end_led);
|
|
|
|
if (end_led > (priv->nleds -1))
|
|
{
|
|
end_led = priv->nleds - 1;
|
|
}
|
|
|
|
for (cur_led = start_led; cur_led <= end_led; cur_led++)
|
|
{
|
|
ws2812_pack(tx_pixel, *pixel_buf & 0xffffff);
|
|
pixel_buf++;
|
|
tx_pixel += WS2812_BYTES_PER_LED;
|
|
written += WS2812_RW_PIXEL_SIZE;
|
|
}
|
|
|
|
SPI_SNDBLOCK(priv->spi, priv->tx_buf, TXBUFF_SIZE(priv->nleds));
|
|
|
|
/* Update LED position and handle case were we wrote the last LED */
|
|
|
|
filep->f_pos += written;
|
|
if (end_led == (priv->nleds - 1))
|
|
{
|
|
filep->f_pos -= WS2812_RW_PIXEL_SIZE;
|
|
}
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return written;
|
|
}
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_seek
|
|
*
|
|
* Description:
|
|
* This routine is called when seeking the WS2812 device. This can be used
|
|
* to address the starting LED to write. This should be done on a full
|
|
* color boundary which is 32bits. e.g. LED0 - offset 0, LED 8 - offset 32
|
|
*
|
|
****************************************************************************/
|
|
|
|
static off_t ws2812_seek(FAR struct file *filep, off_t offset, int whence)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct ws2812_dev_s *priv = inode->i_private;
|
|
|
|
off_t maxpos;
|
|
off_t pos;
|
|
|
|
if ((offset % WS2812_RW_PIXEL_SIZE) != 0)
|
|
{
|
|
return (off_t)-EINVAL;
|
|
}
|
|
|
|
nxmutex_lock(&priv->lock);
|
|
|
|
maxpos = (priv->nleds - 1) * WS2812_RW_PIXEL_SIZE;
|
|
pos = filep->f_pos;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_CUR:
|
|
pos += offset;
|
|
filep->f_pos = pos;
|
|
break;
|
|
|
|
case SEEK_SET:
|
|
pos = offset;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
pos = maxpos + offset + 4;
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Return EINVAL if the whence argument is invalid */
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return (off_t)-EINVAL;
|
|
}
|
|
|
|
if (pos > maxpos)
|
|
{
|
|
pos = maxpos;
|
|
}
|
|
else if (pos < 0)
|
|
{
|
|
pos = 0;
|
|
}
|
|
|
|
filep->f_pos = pos;
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return pos;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_WS2812_NON_SPI_DRIVER
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_register
|
|
*
|
|
* Description:
|
|
* Initialize a ws2812 device as a LEDs interface.
|
|
*
|
|
* Input Parameters:
|
|
* dev_path - The full path to the driver to register. E.g., "/dev/leds0"
|
|
* count - The number of ws2812s in the chain
|
|
* has_white - Set true if the ws2812s in the chain have while LEDs
|
|
* slow_leds - Set true to support older 400 kHz leds.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ws2812_register(FAR const char *dev_path,
|
|
FAR struct ws2812_dev_s *dev_data)
|
|
{
|
|
/* Register the character driver */
|
|
|
|
int ret = register_driver(dev_path, &g_ws2812fops, 0666, dev_data);
|
|
if (ret < 0)
|
|
{
|
|
lederr("ERROR: Failed to register ws2812 driver: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_leds_register
|
|
*
|
|
* Description:
|
|
* Register the WS2812 character device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/leds0"
|
|
* spi - An instance of the SPI interface to use to communicate with
|
|
* WS2812
|
|
* nleds - Number of addressable LEDs
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int ws2812_leds_register(FAR const char *devpath, FAR struct spi_dev_s *spi,
|
|
uint16_t nleds)
|
|
{
|
|
FAR struct ws2812_dev_s *priv;
|
|
int ret;
|
|
int led;
|
|
|
|
/* Initialize the WS2812 device structure */
|
|
|
|
priv = kmm_malloc(sizeof(struct ws2812_dev_s));
|
|
if (!priv)
|
|
{
|
|
lederr("ERROR: Failed to allocate instance\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->nleds = nleds;
|
|
priv->tx_buf = kmm_zalloc(TXBUFF_SIZE(priv->nleds));
|
|
if (!priv->tx_buf)
|
|
{
|
|
lederr("ERROR: Failed to allocate tx buffer\n");
|
|
kmm_free(priv);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Mark LED section of TX buffer as off */
|
|
|
|
for (led = 0; led < priv->nleds; led++)
|
|
{
|
|
ws2812_pack(
|
|
priv->tx_buf + WS2812_RST_CYCLES + led * WS2812_BYTES_PER_LED,
|
|
0);
|
|
}
|
|
|
|
priv->spi = spi;
|
|
ws2812_configspi(priv->spi);
|
|
|
|
nxmutex_init(&priv->lock);
|
|
|
|
SPI_SNDBLOCK(priv->spi, priv->tx_buf, TXBUFF_SIZE(priv->nleds));
|
|
|
|
/* Register the character driver */
|
|
|
|
ret = register_driver(devpath, &g_ws2812fops, 0666, priv);
|
|
if (ret < 0)
|
|
{
|
|
lederr("ERROR: Failed to register driver: %d\n", ret);
|
|
nxmutex_destroy(&priv->lock);
|
|
kmm_free(priv->tx_buf);
|
|
kmm_free(priv);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_WS2812_NON_SPI_DRIVER */
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_hsv_to_rgb
|
|
*
|
|
* Description:
|
|
* Convert a set of hue, saturation and value numbers to an RGB pixel.
|
|
*
|
|
* Representative "hue" values:
|
|
* Red 0
|
|
* Yellow 42
|
|
* Green 86
|
|
* Cyan 128
|
|
* Blue 170
|
|
* Magenta 212
|
|
*
|
|
* "Saturation" values run from 0 (gray) to 255 (pure color)
|
|
*
|
|
* "Value" values run from 0 (black) to 255 (full brightness)
|
|
*
|
|
* Input Parameters:
|
|
* hue in range (0-255) (red -> 0, green -> 85, blue -> 170)
|
|
* saturation in range (0-255)
|
|
* value in range (0-255)
|
|
*
|
|
* Returned Value:
|
|
* A 32-bit pixel in 0x00RRGGBB format.
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t ws2812_hsv_to_rgb(uint8_t hue,
|
|
uint8_t saturation,
|
|
uint8_t value)
|
|
{
|
|
uint32_t val = value + 1; /* move value to range 1...256 */
|
|
uint32_t sat = saturation + 1; /* move value to range 1...256 */
|
|
uint16_t r;
|
|
uint16_t g;
|
|
uint16_t b;
|
|
|
|
/* ===== Compute full saturation R,G,B based on hue =====
|
|
*
|
|
* These computed values are inverted from the normal
|
|
* sense. (0 -> full color 255 -> black) in preparation
|
|
* for the saturation adjustment.
|
|
*/
|
|
|
|
if (hue < 86)
|
|
{
|
|
/* Color between Red and Green */
|
|
|
|
b = 255;
|
|
if (hue < 43)
|
|
{
|
|
/* 0 - 42 Color between Red and Yellow */
|
|
|
|
r = 0;
|
|
g = 255 - hsv_rgb[hue];
|
|
}
|
|
else
|
|
{
|
|
/* 43 - 85 Color between Yellow and Green */
|
|
|
|
r = hsv_rgb[hue - 43];
|
|
g = 0;
|
|
}
|
|
}
|
|
else if (hue < 171)
|
|
{
|
|
/* Color between Green and Blue */
|
|
|
|
r = 255;
|
|
if (hue < 128)
|
|
{
|
|
/* 86 - 127 Color between Green and Cyan */
|
|
|
|
g = 0;
|
|
b = 255 - hsv_rgb[hue - 86];
|
|
}
|
|
else
|
|
{
|
|
/* 128 - 170 Color between Cyan and Blue */
|
|
|
|
g = hsv_rgb[hue - 128];
|
|
b = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Color between Blue and Red */
|
|
|
|
g = 255;
|
|
if (hue < 214)
|
|
{
|
|
/* 171 - 213 Color between Blue and Magenta */
|
|
|
|
b = 0;
|
|
r = 255 - hsv_rgb[hue - 171];
|
|
}
|
|
else
|
|
{
|
|
/* 214 - 255 Color between Magenta and Red */
|
|
|
|
b = hsv_rgb[hue - 214];
|
|
r = 0;
|
|
}
|
|
}
|
|
|
|
/* This step scales the color for saturation and inverts
|
|
* back to 255 -> bright and 0 -> black.
|
|
*/
|
|
|
|
r = 255 - ((r * sat) >> 8);
|
|
g = 255 - ((g * sat) >> 8);
|
|
b = 255 - ((b * sat) >> 8);
|
|
|
|
/* compute the return value using the r, g, and b values scaled
|
|
* by the value parameter
|
|
*/
|
|
|
|
return (((r * val) << 8) & 0xff0000)
|
|
| (((g * val) << 0) & 0x00ff00)
|
|
| (((b * val) >> 8) & 0x0000ff);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: ws2812_gamma_correct
|
|
*
|
|
* Description:
|
|
* Applies a gamma correction to the supplied pixel.
|
|
*
|
|
* Input Parameters:
|
|
* a 32-bit pixel with 8-bit color components.
|
|
*
|
|
* Returned Value:
|
|
* A 32-bit gamma corrected pixel.
|
|
*
|
|
****************************************************************************/
|
|
|
|
uint32_t ws2812_gamma_correct(uint32_t pixel)
|
|
{
|
|
uint32_t res;
|
|
FAR uint8_t *in = (FAR uint8_t *)&pixel;
|
|
FAR uint8_t *out = (FAR uint8_t *)&res;
|
|
|
|
*out++ = ws2812_gamma[*in++];
|
|
*out++ = ws2812_gamma[*in++];
|
|
*out++ = ws2812_gamma[*in++];
|
|
*out = ws2812_gamma[*in];
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif /* CONFIG_WS2812 */
|