├── component.mk ├── CMakeLists.txt ├── .gitignore.txt ├── neoled.cpp ├── include └── neoled.h └── README.md /component.mk: -------------------------------------------------------------------------------- 1 | # Use defaults 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set the component to use C++ instead of C 2 | set(COMPONENT_ADD_INCLUDEDIRS include) 3 | set(COMPONENT_SRCS "neoled.cpp") 4 | 5 | # Specify C++ as the language and set C++11 standard 6 | set(COMPONENT_PRIV_REQUIRES driver freertos) 7 | set(CMAKE_CXX_STANDARD 11) 8 | 9 | idf_component_register(SRCS ${COMPONENT_SRCS} INCLUDE_DIRS ${COMPONENT_ADD_INCLUDEDIRS}) 10 | 11 | -------------------------------------------------------------------------------- /.gitignore.txt: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /neoled.cpp: -------------------------------------------------------------------------------- 1 | /** MIT licence 2 | 3 | Copyright (C) 2019 by Vu Nam https://github.com/vunam https://studiokoda.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: The above copyright notice and this 11 | permission notice shall be included in all copies or substantial portions of 12 | the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 17 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 18 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "freertos/FreeRTOS.h" 28 | #include "freertos/task.h" 29 | #include "esp_system.h" 30 | #include "driver/i2s.h" 31 | #include "driver/gpio.h" 32 | #include "neoled.h" 33 | 34 | namespace NeoLED { 35 | 36 | static uint8_t out_buffer[LED_NUMBER * PIXEL_SIZE] = {0}; 37 | static uint8_t off_buffer[ZERO_BUFFER] = {0}; 38 | static uint16_t size_buffer; 39 | 40 | static const uint16_t bitpatterns[4] = {0x88, 0x8e, 0xe8, 0xee}; 41 | 42 | i2s_config_t i2s_config = { 43 | .mode = static_cast(I2S_MODE_MASTER | I2S_MODE_TX), 44 | .sample_rate = SAMPLE_RATE, 45 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 46 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 47 | .communication_format = static_cast ( I2S_COMM_FORMAT_STAND_I2S | I2S_COMM_FORMAT_STAND_MSB), 48 | .intr_alloc_flags = 0, 49 | .dma_buf_count = 4, 50 | .dma_buf_len = LED_NUMBER * PIXEL_SIZE, 51 | .use_apll = false, 52 | .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT 53 | }; 54 | 55 | i2s_pin_config_t pin_config = { 56 | .mck_io_num = static_cast(1), 57 | .bck_io_num = static_cast(-1), 58 | .ws_io_num = static_cast(-1), 59 | .data_out_num = static_cast(I2S_DO_IO), 60 | .data_in_num = static_cast(-1) 61 | 62 | }; 63 | 64 | void init() { 65 | size_buffer = LED_NUMBER * PIXEL_SIZE; 66 | i2s_driver_install(static_cast(I2S_NUM), &i2s_config, 0, nullptr); 67 | i2s_set_pin(static_cast(I2S_NUM), &pin_config); 68 | } 69 | 70 | void destroy() { 71 | i2s_driver_uninstall(static_cast(I2S_NUM)); 72 | gpio_reset_pin(static_cast(I2S_DO_IO)); 73 | } 74 | 75 | void update(Pixel* pixels) { 76 | size_t bytes_written = 0; 77 | 78 | for (uint16_t i = 0; i < LED_NUMBER; i++) { 79 | int loc = i * PIXEL_SIZE; 80 | 81 | out_buffer[loc] = bitpatterns[pixels[i].green >> 6 & 0x03]; 82 | out_buffer[loc + 1] = bitpatterns[pixels[i].green >> 4 & 0x03]; 83 | out_buffer[loc + 2] = bitpatterns[pixels[i].green >> 2 & 0x03]; 84 | out_buffer[loc + 3] = bitpatterns[pixels[i].green & 0x03]; 85 | 86 | out_buffer[loc + 4] = bitpatterns[pixels[i].red >> 6 & 0x03]; 87 | out_buffer[loc + 5] = bitpatterns[pixels[i].red >> 4 & 0x03]; 88 | out_buffer[loc + 6] = bitpatterns[pixels[i].red >> 2 & 0x03]; 89 | out_buffer[loc + 7] = bitpatterns[pixels[i].red & 0x03]; 90 | 91 | out_buffer[loc + 8] = bitpatterns[pixels[i].blue >> 6 & 0x03]; 92 | out_buffer[loc + 9] = bitpatterns[pixels[i].blue >> 4 & 0x03]; 93 | out_buffer[loc + 10] = bitpatterns[pixels[i].blue >> 2 & 0x03]; 94 | out_buffer[loc + 11] = bitpatterns[pixels[i].blue & 0x03]; 95 | } 96 | 97 | i2s_write(static_cast(I2S_NUM), out_buffer, size_buffer, &bytes_written, portMAX_DELAY); 98 | i2s_write(static_cast(I2S_NUM), off_buffer, ZERO_BUFFER, &bytes_written, portMAX_DELAY); 99 | vTaskDelay(pdMS_TO_TICKS(10)); 100 | i2s_zero_dma_buffer(static_cast(I2S_NUM)); 101 | } 102 | 103 | } // namespace NeoLED 104 | -------------------------------------------------------------------------------- /include/neoled.h: -------------------------------------------------------------------------------- 1 | /** MIT licence 2 | 3 | Copyright (C) 2019 by Vu Nam https://github.com/vunam https://studiokoda.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: The above copyright notice and this 11 | permission notice shall be included in all copies or substantial portions of 12 | the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 17 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 18 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 19 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | 22 | */ 23 | #ifndef NEOLED_H 24 | #define NEOLED_H 25 | 26 | #include 27 | 28 | namespace NeoLED { 29 | 30 | #define LED_NUMBER 1 31 | #define PIXEL_SIZE 12 // each color takes 4 bytes 32 | #define SAMPLE_RATE (93750) 33 | #define ZERO_BUFFER 48 34 | #define I2S_NUM (0) 35 | #define I2S_DO_IO (21) 36 | 37 | 38 | #define COLOR_RED (Pixel){0, 255, 0} 39 | #define COLOR_ORANGE (Pixel){64, 255, 0} 40 | #define COLOR_YELLOW (Pixel){128, 255, 0} 41 | #define COLOR_LIME (Pixel){255, 255, 0} 42 | #define COLOR_GREEN (Pixel){255, 0, 0} 43 | #define COLOR_TURQUOISE (Pixel){255, 0, 128} 44 | #define COLOR_CYAN (Pixel){255, 0, 255} 45 | #define COLOR_AQUA (Pixel){128, 0, 255} 46 | #define COLOR_BLUE (Pixel){0, 0, 255} 47 | #define COLOR_PURPLE (Pixel){0, 128, 255} 48 | #define COLOR_MAGENTA (Pixel){0, 255, 255} 49 | #define COLOR_ROSE (Pixel){0, 255, 128} 50 | #define COLOR_WHITE (Pixel){255, 255, 255} 51 | #define COLOR_OFF (Pixel){0, 0, 0} 52 | 53 | 54 | #define HUE_RED 0 // Red 55 | #define HUE_ORANGE 32 // Orange 56 | #define HUE_YELLOW 64 // Yellow 57 | #define HUE_LIME 80 // Lime Green 58 | #define HUE_GREEN 96 // Green 59 | #define HUE_TURQUOISE 112 // Turquoise 60 | #define HUE_CYAN 128 // Cyan 61 | #define HUE_AQUA 144 // Aqua Blue 62 | #define HUE_BLUE 160 // Blue 63 | #define HUE_PURPLE 176 // Purple 64 | #define HUE_MAGENTA 192 // Magenta (Pink) 65 | #define HUE_ROSE 224 // Rose Pink 66 | #define HUE_WHITE 0 // Use full brightness for white (no hue shift) 67 | #define HUE_OFF 0 // Turn off LED (set RGB values to zero) 68 | 69 | typedef struct 70 | { 71 | uint8_t green; 72 | uint8_t red; 73 | uint8_t blue; 74 | } Pixel; 75 | 76 | void init(); 77 | void update(Pixel* pixels); 78 | void destroy(); 79 | 80 | inline Pixel makePixel(uint8_t r, uint8_t g, uint8_t b) 81 | { 82 | Pixel pixel; 83 | pixel.red = r; 84 | pixel.green = g; 85 | pixel.blue = b; 86 | return pixel; 87 | } 88 | inline Pixel colorWheel(uint8_t hue) 89 | { 90 | Pixel pixel; 91 | 92 | if (hue < 85) 93 | { 94 | pixel.red = hue * 3; 95 | pixel.green = 255 - hue * 3; 96 | pixel.blue = 0; 97 | } 98 | else if (hue < 170) 99 | { 100 | hue -= 85; 101 | pixel.red = 255 - hue * 3; 102 | pixel.green = 0; 103 | pixel.blue = hue * 3; 104 | } 105 | else 106 | { 107 | hue -= 170; 108 | pixel.red = 0; 109 | pixel.green = hue * 3; 110 | pixel.blue = 255 - hue * 3; 111 | } 112 | 113 | return pixel; 114 | } 115 | inline uint8_t hueValue(Pixel pixel) 116 | { 117 | if (pixel.red > 0 && pixel.blue == 0) { 118 | return pixel.red / 3; 119 | } 120 | if (pixel.red > 0 && pixel.green == 0) { 121 | return 85 + (255 - pixel.red) / 3; 122 | } 123 | if (pixel.green > 0 && pixel.red == 0) { 124 | return 170 + pixel.green / 3; 125 | } 126 | return 0; 127 | } 128 | inline uint32_t hexValue(Pixel pixel) 129 | { 130 | return ((uint32_t)pixel.red << 16) | ((uint32_t)pixel.green << 8) | (uint32_t)pixel.blue; 131 | } 132 | inline Pixel RGBValue(uint32_t hexValue) 133 | { 134 | Pixel pixel; 135 | pixel.red = (hexValue >> 16) & 0xFF; 136 | pixel.green = (hexValue >> 8) & 0xFF; 137 | pixel.blue = hexValue & 0xFF; 138 | return pixel; 139 | } 140 | } // namespace NeoLED 141 | 142 | #endif // NEOLED_H 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeoLED - ESP32 Component for WS2812 LEDs Using I2S 2 | 3 | ## Introduction 4 | 5 | NeoLED is an ESP32 component library designed specifically for controlling WS2812 NeoPixel LEDs using the I2S peripheral of the ESP-IDF SDK. This library was created to fill the gap when a suitable existing library was not found, particularly for use in the M5Stack Cardputer, where GPIO control via I2S is essential for reliable LED performance. 6 | 7 | ### Why Use I2S for NeoPixels? 8 | 9 | The WS2812 LEDs typically rely on precise timing signals, which can be challenging to achieve with regular GPIO operations, especially on the ESP32 when running other tasks concurrently. By leveraging the I2S peripheral, NeoLED can generate the necessary timing signals more accurately, reducing flicker and glitches even under heavy CPU load. 10 | 11 | ## Features 12 | 13 | - **Reliable I2S Control**: Ensures stable operation of WS2812 LEDs by using the I2S peripheral for precise timing. 14 | - **Customizable GPIO**: Defaults to GPIO 21, but can be configured via `neoled.h`. 15 | - **Simple API**: Easy-to-use API for initializing, setting pixel colors, and updating the LED strip. 16 | - **Hue Support**: Includes a color wheel function for smooth hue transitions. 17 | 18 | ## Installation 19 | 20 | 1. Copy the `NeoLED` component folder into your ESP-IDF project under the `components` directory. 21 | 2. Add `NeoLED` to your project's `CMakeLists.txt`: 22 | 23 | ```cmake 24 | set(COMPONENT_SRCS "ws2812.cpp") 25 | set(COMPONENT_ADD_INCLUDEDIRS "include") 26 | register_component() 27 | ``` 28 | 29 | 3. Include the library in your source files: 30 | 31 | ```cpp 32 | #include "neoled.h" 33 | ``` 34 | 35 | ## Configuration 36 | 37 | You can change the default settings in `neoled.h`: 38 | 39 | ```cpp 40 | #define LED_NUMBER 1 // Number of LEDs in your strip 41 | #define I2S_DO_IO GPIO_NUM_21 // Default GPIO pin for data output 42 | ``` 43 | 44 | ## Usage 45 | 46 | Below is an example of how to use NeoLED in your ESP-IDF project: 47 | 48 | ```cpp 49 | #include "neoled.h" 50 | 51 | extern "C" void app_main() { 52 | // Initialize NeoLED 53 | NeoLED::init(); 54 | 55 | // Create a green pixel 56 | NeoLED::Pixel green_pixel = NeoLED::makePixel(0, 255, 0); 57 | 58 | // Update the LED with the green pixel 59 | NeoLED::update(&green_pixel); 60 | 61 | // Example with hue color wheel 62 | for (int hue = 0; hue < 256; hue++) { 63 | NeoLED::Pixel pixel = NeoLED::colorWheel(hue); 64 | NeoLED::update(&pixel); 65 | vTaskDelay(pdMS_TO_TICKS(50)); // Delay for smooth transition 66 | } 67 | } 68 | ``` 69 | 70 | ### Explanation: 71 | 72 | - **`NeoLED::init()`**: Initializes the I2S peripheral for controlling the LEDs. 73 | - **`NeoLED::makePixel(r, g, b)`**: Creates a pixel with specified RGB values. 74 | - **`NeoLED::update(pixel)`**: Updates the LED strip with the pixel data. 75 | - **`NeoLED::colorWheel(hue)`**: Generates a pixel color based on a hue value (0-255). 76 | 77 | ### Advanced Usage 78 | 79 | You can customize the LED configuration directly in `neoled.h`, or implement your own memory allocation for dynamically sized LED strips. 80 | 81 | ## Known Issues 82 | 83 | - **Limited GPIO Compatibility**: The library defaults to GPIO 21, which is suitable for M5Stack Cardputer. If using other hardware, ensure the chosen GPIO pin supports I2S output. 84 | - **Single LED Support**: Currently designed for up to one LED. Future updates will include support for longer LED strips and RGBW LEDs. 85 | 86 | ## Planned Improvements 87 | 88 | - **Support for RGBW LEDs**: Add functionality to handle RGBW NeoPixel strips. 89 | - **Dynamic Configuration**: Implement initialization functions to allow dynamic configuration of LED numbers and GPIO pins without modifying the header file. 90 | - **Memory Management**: Add custom memory allocation options for better control over resource usage. 91 | 92 | ## Debugging Tips 93 | 94 | - **LED Not Lighting Up**: Ensure that the data pin (`I2S_DO_IO`) is correctly configured and connected to the input of the LED strip. 95 | - **Flickering LEDs**: This may be due to incorrect power supply or timing issues. Verify that the power supply can handle the current draw of the LEDs. 96 | - **Incorrect Colors**: Check the RGB order. Some NeoPixel strips use different color orders (e.g., GRB instead of RGB). Modify `makePixel()` accordingly. 97 | 98 | ## License 99 | 100 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 101 | 102 | ## Contributions 103 | 104 | Contributions are welcome! Please feel free to submit pull requests or open issues for any bugs or feature requests. If you create a new app or feature for the M5Stack Cardputer using this library, consider sharing it with the community! 105 | 106 | ## Acknowledgments 107 | 108 | Special thanks to [Vu Nam](https://github.com/vunam) for the original inspiration and implementation of a WS2812 I2S driver for the ESP32. This project builds on those efforts and aims to provide a robust solution for the M5Stack Cardputer. 109 | 110 | --- 111 | 112 | --------------------------------------------------------------------------------