| /**************************************************************************** |
| * 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 */ |