From 1ea0cac998cab60c8b2aa64ba4bd995acbece4b4 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 7 Nov 2019 13:10:29 +0000 Subject: [PATCH] ARM - ws2812 bitbang (#7173) * Initial ARM bitbang ws2812 driver * Unify chibios platform to run rgblight_task * Remove 'avr only' comments from ws2812 docs * Remove 'avr only' comments from ws2812 docs * Unify chibios platform to run rgblight_task - review comments * Remove debug flags from keymap * Add comments from review * Add defines for STM32L0XX * Attempt to get arm ws2812 working on multiple gcc versions --- docs/feature_rgb_matrix.md | 2 +- docs/feature_rgblight.md | 2 +- docs/hardware_drivers.md | 2 +- docs/ws2812_driver.md | 13 ++- drivers/arm/ws2812.c | 101 +++++++++++++++++- drivers/arm/ws2812.h | 17 +++ keyboards/handwired/onekey/bluepill/config.h | 2 + .../handwired/onekey/keymaps/rgb/config.h | 4 + .../handwired/onekey/keymaps/rgb/keymap.c | 11 ++ .../handwired/onekey/keymaps/rgb/rules.mk | 1 + keyboards/handwired/onekey/proton_c/config.h | 2 + .../handwired/onekey/stm32f0_disco/config.h | 2 + quantum/stm32/proton_c.mk | 2 +- tmk_core/protocol/chibios/main.c | 8 ++ 14 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 drivers/arm/ws2812.h create mode 100644 keyboards/handwired/onekey/keymaps/rgb/config.h create mode 100644 keyboards/handwired/onekey/keymaps/rgb/keymap.c create mode 100644 keyboards/handwired/onekey/keymaps/rgb/rules.mk diff --git a/docs/feature_rgb_matrix.md b/docs/feature_rgb_matrix.md index b2850c3cff..3e69361fbd 100644 --- a/docs/feature_rgb_matrix.md +++ b/docs/feature_rgb_matrix.md @@ -107,7 +107,7 @@ Where `X_Y` is the location of the LED in the matrix defined by [the datasheet]( --- -### WS2812 (AVR only) +### WS2812 There is basic support for addressable RGB matrix lighting with a WS2811/WS2812{a,b,c} addressable LED strand. To enable it, add this to your `rules.mk`: diff --git a/docs/feature_rgblight.md b/docs/feature_rgblight.md index be4ddfa729..397dde587d 100644 --- a/docs/feature_rgblight.md +++ b/docs/feature_rgblight.md @@ -6,7 +6,7 @@ QMK has the ability to control RGB LEDs attached to your keyboard. This is commo Some keyboards come with RGB LEDs preinstalled. Others must have them installed after the fact. See the [Hardware Modification](#hardware-modification) section for information on adding RGB lighting to your keyboard. -Currently QMK supports the following addressable LEDs on AVR microcontrollers (however, the white LED in RGBW variants is not supported): +Currently QMK supports the following addressable LEDs (however, the white LED in RGBW variants is not supported): * WS2811, WS2812, WS2812B, WS2812C, etc. * SK6812, SK6812MINI, SK6805 diff --git a/docs/hardware_drivers.md b/docs/hardware_drivers.md index 321ba2a4bc..1651877957 100644 --- a/docs/hardware_drivers.md +++ b/docs/hardware_drivers.md @@ -22,7 +22,7 @@ Support for SSD1306 based OLED displays. For more information see the [OLED Driv You can make use of uGFX within QMK to drive character and graphic LCDs, LED arrays, OLED, TFT, and other display technologies. This needs to be better documented, if you are trying to do this and reading the code doesn't help please [open an issue](https://github.com/qmk/qmk_firmware/issues/new) and we can help you through the process. -## WS2812 (AVR Only) +## WS2812 Support for WS2811/WS2812{a,b,c} LED's. For more information see the [RGB Light](feature_rgblight.md) page. diff --git a/docs/ws2812_driver.md b/docs/ws2812_driver.md index 6fa5d324cf..67481c458d 100644 --- a/docs/ws2812_driver.md +++ b/docs/ws2812_driver.md @@ -1,13 +1,22 @@ # WS2812 Driver This driver powers the [RGB Lighting](feature_rgblight.md) and [RGB Matrix](feature_rgb_matrix.md) features. -Currently QMK supports the following addressable LEDs on AVR microcontrollers (however, the white LED in RGBW variants is not supported): +Currently QMK supports the following addressable LEDs (however, the white LED in RGBW variants is not supported): WS2811, WS2812, WS2812B, WS2812C, etc. SK6812, SK6812MINI, SK6805 These LEDs are called "addressable" because instead of using a wire per color, each LED contains a small microchip that understands a special protocol sent over a single wire. The chip passes on the remaining data to the next LED, allowing them to be chained together. In this way, you can easily control the color of the individual LEDs. +## Supported Driver Types + +| | AVR | ARM | +|----------|--------------------|--------------------| +| bit bang | :heavy_check_mark: | :heavy_check_mark: | +| I2C | :heavy_check_mark: | | +| SPI | | Soon™ | +| PWM | | Soon™ | + ## Driver configuration ### Bitbang @@ -17,7 +26,7 @@ Default driver, the absence of configuration assumes this driver. To configure i WS2812_DRIVER = bitbang ``` -!> ARM does not yet support WS2182. Progress is being made, but we are not quite there, yet. +!> This driver is not hardware accelerated and may not be performant on heavily loaded systems. ### I2C Targeting boards where WS2812 support is offloaded to a 2nd MCU. Currently the driver is limited to AVR given the known consumers are ps2avrGB/BMC. To configure it, add this to your rules.mk: diff --git a/drivers/arm/ws2812.c b/drivers/arm/ws2812.c index 2094e50098..b076eff330 100644 --- a/drivers/arm/ws2812.c +++ b/drivers/arm/ws2812.c @@ -1 +1,100 @@ -#error("NOT SUPPORTED") \ No newline at end of file +#include "quantum.h" +#include "ws2812.h" +#include "ch.h" +#include "hal.h" + +/* Adapted from https://github.com/bigjosh/SimpleNeoPixelDemo/ */ + +#ifndef NOP_FUDGE +# if defined(STM32F1XX) || defined(STM32F1xx) || defined(STM32F0XX) || defined(STM32F0xx) || defined(STM32F3XX) || defined(STM32F3xx) || defined(STM32L0XX) || defined(STM32L0xx) +# define NOP_FUDGE 0.4 +# else +# error("NOP_FUDGE configuration required") +# define NOP_FUDGE 1 // this just pleases the compile so the above error is easier to spot +# endif +#endif + +#define NUMBER_NOPS 6 +#define CYCLES_PER_SEC (STM32_SYSCLK / NUMBER_NOPS * NOP_FUDGE) +#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 +#define NS_PER_CYCLE (NS_PER_SEC / CYCLES_PER_SEC) +#define NS_TO_CYCLES(n) ((n) / NS_PER_CYCLE) + +#define wait_ns(x) \ + do { \ + for (int i = 0; i < NS_TO_CYCLES(x); i++) { \ + __asm__ volatile("nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t" \ + "nop\n\t"); \ + } \ + } while (0) + +// These are the timing constraints taken mostly from the WS2812 datasheets +// These are chosen to be conservative and avoid problems rather than for maximum throughput + +#define T1H 900 // Width of a 1 bit in ns +#define T1L (1250 - T1H) // Width of a 1 bit in ns + +#define T0H 350 // Width of a 0 bit in ns +#define T0L (1250 - T0H) // Width of a 0 bit in ns + +// The reset gap can be 6000 ns, but depending on the LED strip it may have to be increased +// to values like 600000 ns. If it is too small, the pixels will show nothing most of the time. +#define RES 10000 // Width of the low gap between bits to cause a frame to latch + +void sendByte(uint8_t byte) { + // WS2812 protocol wants most significant bits first + for (unsigned char bit = 0; bit < 8; bit++) { + bool is_one = byte & (1 << (7 - bit)); + // using something like wait_ns(is_one ? T1L : T0L) here throws off timings + if (is_one) { + // 1 + writePinHigh(RGB_DI_PIN); + wait_ns(T1H); + writePinLow(RGB_DI_PIN); + wait_ns(T1L); + } else { + // 0 + writePinHigh(RGB_DI_PIN); + wait_ns(T0H); + writePinLow(RGB_DI_PIN); + wait_ns(T0L); + } + } +} + +void ws2812_init(void) { setPinOutput(RGB_DI_PIN); } + +// Setleds for standard RGB +void ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) { + static bool s_init = false; + if (!s_init) { + ws2812_init(); + s_init = true; + } + + // this code is very time dependent, so we need to disable interrupts + chSysLock(); + + for (uint8_t i = 0; i < leds; i++) { + // WS2812 protocol dictates grb order + sendByte(ledarray[i].g); + sendByte(ledarray[i].r); + sendByte(ledarray[i].b); + } + + wait_ns(RES); + + chSysUnlock(); +} + +// Setleds for SK6812RGBW +void ws2812_setleds_rgbw(LED_TYPE *ledarray, uint16_t leds) { +// not supported - for now error out if its enabled +#ifdef RGBW +# error "RGBW not supported" +#endif +} diff --git a/drivers/arm/ws2812.h b/drivers/arm/ws2812.h new file mode 100644 index 0000000000..bf5c9fd0f1 --- /dev/null +++ b/drivers/arm/ws2812.h @@ -0,0 +1,17 @@ +#pragma once + +#include "quantum/color.h" + +/* User Interface + * + * Input: + * ledarray: An array of GRB data describing the LED colors + * number_of_leds: The number of LEDs to write + * + * The functions will perform the following actions: + * - Set the data-out pin as output + * - Send out the LED data + * - Wait 50�s to reset the LEDs + */ +void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds); +void ws2812_setleds_rgbw(LED_TYPE *ledarray, uint16_t number_of_leds); diff --git a/keyboards/handwired/onekey/bluepill/config.h b/keyboards/handwired/onekey/bluepill/config.h index 81282ae1ff..01555e4315 100644 --- a/keyboards/handwired/onekey/bluepill/config.h +++ b/keyboards/handwired/onekey/bluepill/config.h @@ -25,3 +25,5 @@ #define BACKLIGHT_PIN A0 #define BACKLIGHT_PWM_DRIVER PWMD2 #define BACKLIGHT_PWM_CHANNEL 1 + +#define RGB_DI_PIN A1 diff --git a/keyboards/handwired/onekey/keymaps/rgb/config.h b/keyboards/handwired/onekey/keymaps/rgb/config.h new file mode 100644 index 0000000000..89e76326b4 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/rgb/config.h @@ -0,0 +1,4 @@ +#pragma once + +#define RGBLED_NUM 9 +#define RGBLIGHT_ANIMATIONS diff --git a/keyboards/handwired/onekey/keymaps/rgb/keymap.c b/keyboards/handwired/onekey/keymaps/rgb/keymap.c new file mode 100644 index 0000000000..a96c6f3863 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/rgb/keymap.c @@ -0,0 +1,11 @@ +#include QMK_KEYBOARD_H + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + LAYOUT( RGB_MOD ) +}; + +void keyboard_post_init_user(void) { + rgblight_enable_noeeprom(); + rgblight_sethsv_noeeprom_cyan(); + rgblight_mode_noeeprom(RGBLIGHT_MODE_RAINBOW_SWIRL); +} diff --git a/keyboards/handwired/onekey/keymaps/rgb/rules.mk b/keyboards/handwired/onekey/keymaps/rgb/rules.mk new file mode 100644 index 0000000000..1e3cebb145 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/rgb/rules.mk @@ -0,0 +1 @@ +RGBLIGHT_ENABLE = yes diff --git a/keyboards/handwired/onekey/proton_c/config.h b/keyboards/handwired/onekey/proton_c/config.h index d4fb9c8299..fe34f94add 100644 --- a/keyboards/handwired/onekey/proton_c/config.h +++ b/keyboards/handwired/onekey/proton_c/config.h @@ -26,3 +26,5 @@ #define BACKLIGHT_PWM_DRIVER PWMD4 #define BACKLIGHT_PWM_CHANNEL 3 #define BACKLIGHT_PAL_MODE 2 + +#define RGB_DI_PIN A1 diff --git a/keyboards/handwired/onekey/stm32f0_disco/config.h b/keyboards/handwired/onekey/stm32f0_disco/config.h index 4024ee1caa..637ed65d3f 100644 --- a/keyboards/handwired/onekey/stm32f0_disco/config.h +++ b/keyboards/handwired/onekey/stm32f0_disco/config.h @@ -26,3 +26,5 @@ #define BACKLIGHT_PWM_DRIVER PWMD3 #define BACKLIGHT_PWM_CHANNEL 3 #define BACKLIGHT_PAL_MODE 0 + +#define RGB_DI_PIN B15 diff --git a/quantum/stm32/proton_c.mk b/quantum/stm32/proton_c.mk index 193e09ca1e..ff28a4cb5d 100644 --- a/quantum/stm32/proton_c.mk +++ b/quantum/stm32/proton_c.mk @@ -2,7 +2,7 @@ # These are defaults based on what has been implemented for ARM boards AUDIO_ENABLE = yes -RGBLIGHT_ENABLE = no +WS2812_DRIVER = bitbang # Force task driven PWM until ARM can provide automatic configuration ifneq ($(strip $(BACKLIGHT_ENABLE)), no) diff --git a/tmk_core/protocol/chibios/main.c b/tmk_core/protocol/chibios/main.c index de2b493b84..c304f4d795 100644 --- a/tmk_core/protocol/chibios/main.c +++ b/tmk_core/protocol/chibios/main.c @@ -32,6 +32,11 @@ #include "sendchar.h" #include "debug.h" #include "printf.h" +#include "rgblight_reconfig.h" + +#if (defined(RGB_MIDI) || defined(RGBLIGHT_ANIMATIONS)) && defined(RGBLIGHT_ENABLE) +# include "rgblight.h" +#endif #ifdef SLEEP_LED_ENABLE # include "sleep_led.h" #endif @@ -214,6 +219,9 @@ int main(void) { #endif #ifdef RAW_ENABLE raw_hid_task(); +#endif +#if defined(RGBLIGHT_ANIMATIONS) && defined(RGBLIGHT_ENABLE) + rgblight_task(); #endif } }