ws2812_bitbang.c (4344B)
1 #include "ws2812.h" 2 3 #include "gpio.h" 4 #include "chibios_config.h" 5 6 // DEPRECATED - DO NOT USE 7 #if defined(NOP_FUDGE) 8 # define WS2812_BITBANG_NOP_FUDGE NOP_FUDGE 9 #endif 10 11 /* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */ 12 13 #ifndef WS2812_BITBANG_NOP_FUDGE 14 # if defined(STM32F0XX) || defined(STM32F1XX) || defined(GD32VF103) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(WB32F3G71xx) || defined(WB32FQ95xx) || defined(AT32F415) 15 # define WS2812_BITBANG_NOP_FUDGE 0.4 16 # else 17 # if defined(RP2040) 18 # error "Please use `vendor` WS2812 driver for RP2040" 19 # else 20 # error "WS2812_BITBANG_NOP_FUDGE configuration required" 21 # endif 22 # define WS2812_BITBANG_NOP_FUDGE 1 // this just pleases the compile so the above error is easier to spot 23 # endif 24 #endif 25 26 // Push Pull or Open Drain Configuration 27 // Default Push Pull 28 #ifndef WS2812_EXTERNAL_PULLUP 29 # define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_PUSHPULL 30 #else 31 # define WS2812_OUTPUT_MODE PAL_MODE_OUTPUT_OPENDRAIN 32 #endif 33 34 // The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased 35 // to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. 36 #ifndef WS2812_RES 37 # define WS2812_RES (1000 * WS2812_TRST_US) // Width of the low gap between bits to cause a frame to latch 38 #endif 39 40 #define NUMBER_NOPS 6 41 #define CYCLES_PER_SEC (CPU_CLOCK / NUMBER_NOPS * WS2812_BITBANG_NOP_FUDGE) 42 #define NS_PER_SEC (1000000000L) // Note that this has to be SIGNED since we want to be able to check for negative values of derivatives 43 #define NS_PER_CYCLE (NS_PER_SEC / CYCLES_PER_SEC) 44 #define NS_TO_CYCLES(n) ((n) / NS_PER_CYCLE) 45 46 #define wait_ns(x) \ 47 do { \ 48 for (int i = 0; i < NS_TO_CYCLES(x); i++) { \ 49 __asm__ volatile("nop\n\t" \ 50 "nop\n\t" \ 51 "nop\n\t" \ 52 "nop\n\t" \ 53 "nop\n\t" \ 54 "nop\n\t"); \ 55 } \ 56 } while (0) 57 58 void sendByte(uint8_t byte) { 59 // WS2812 protocol wants most significant bits first 60 for (unsigned char bit = 0; bit < 8; bit++) { 61 bool is_one = byte & (1 << (7 - bit)); 62 // using something like wait_ns(is_one ? T1L : T0L) here throws off timings 63 if (is_one) { 64 // 1 65 gpio_write_pin_high(WS2812_DI_PIN); 66 wait_ns(WS2812_T1H); 67 gpio_write_pin_low(WS2812_DI_PIN); 68 wait_ns(WS2812_T1L); 69 } else { 70 // 0 71 gpio_write_pin_high(WS2812_DI_PIN); 72 wait_ns(WS2812_T0H); 73 gpio_write_pin_low(WS2812_DI_PIN); 74 wait_ns(WS2812_T0L); 75 } 76 } 77 } 78 79 ws2812_led_t ws2812_leds[WS2812_LED_COUNT]; 80 81 void ws2812_init(void) { 82 palSetLineMode(WS2812_DI_PIN, WS2812_OUTPUT_MODE); 83 } 84 85 void ws2812_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { 86 ws2812_leds[index].r = red; 87 ws2812_leds[index].g = green; 88 ws2812_leds[index].b = blue; 89 #if defined(WS2812_RGBW) 90 ws2812_rgb_to_rgbw(&ws2812_leds[index]); 91 #endif 92 } 93 94 void ws2812_set_color_all(uint8_t red, uint8_t green, uint8_t blue) { 95 for (int i = 0; i < WS2812_LED_COUNT; i++) { 96 ws2812_set_color(i, red, green, blue); 97 } 98 } 99 100 void ws2812_flush(void) { 101 // this code is very time dependent, so we need to disable interrupts 102 chSysLock(); 103 104 for (int i = 0; i < WS2812_LED_COUNT; i++) { 105 // WS2812 protocol dictates grb order 106 #if (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_GRB) 107 sendByte(ws2812_leds[i].g); 108 sendByte(ws2812_leds[i].r); 109 sendByte(ws2812_leds[i].b); 110 #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_RGB) 111 sendByte(ws2812_leds[i].r); 112 sendByte(ws2812_leds[i].g); 113 sendByte(ws2812_leds[i].b); 114 #elif (WS2812_BYTE_ORDER == WS2812_BYTE_ORDER_BGR) 115 sendByte(ws2812_leds[i].b); 116 sendByte(ws2812_leds[i].g); 117 sendByte(ws2812_leds[i].r); 118 #endif 119 120 #ifdef WS2812_RGBW 121 sendByte(ws2812_leds[i].w); 122 #endif 123 } 124 125 wait_ns(WS2812_RES); 126 127 chSysUnlock(); 128 }