Skip to content

Commit

Permalink
WS2812 Overhaul (#210)
Browse files Browse the repository at this point in the history
* ARM - ws2812 bitbang (qmk#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

* Support RGBLIGHT_SLEEP when ChibiOS boards suspend (qmk#7280)

Copypasta from the AVR suspend implementation with a Teensy-specific
hack removed

* Unify RGB and RGBW commands (qmk#7297)

* Fix unicode in comments

Co-Authored-By: fauxpark <[email protected]>

* Remove separate RGBW implementation for a unified function

* Set White to 0 in RGBW LEDs

This is just to get this working, later, proper brightness can be handled elsewhere.

* Use us instead of nanoseconds(?) since it renders correctly on web

* Remove RGBW function from arm/ws2812.h

* Remove RGBW function from arm/ws2812.c

* Formatting changes

* Add doc info

* Remove force of debug on within rgblight - causes lockups waiting for hid_listen (qmk#7330)

* Move Ergodox EZ RGB Light code to custom driver  (qmk#7309)

* Move Ergodox EZ RGB code to custom driver

Also implements full addressing of Ergodox EZ's LED Strip, as written by seebs
Co-authored-by: Seebs <[email protected]>

* Make Clipping range accessible for custom drivers

* Remove RGBW_BB_TWI from driver and docs

* Revert changes to clipping range support

* Use just rgblight_set instead of full custom driver

* Convert to i2c_master commands

* Rename rgblight driver and clean up includes

* Use White channel on RGBW LEDs

* SPI DMA based RGB Underglow for STM32 (qmk#7674)

* Initial stash of ws2812 spi driver

* Update comment, add sync backup plan

* Add testing notes to spi ws2812 driver

* Align RGBW error messages

Co-authored-by: Joel Challis <[email protected]>
Co-authored-by: Jonathan Rascher <[email protected]>
Co-authored-by: Florian Didron <[email protected]>
  • Loading branch information
4 people committed Jan 8, 2020
1 parent 81126b6 commit df91396
Show file tree
Hide file tree
Showing 17 changed files with 426 additions and 164 deletions.
96 changes: 95 additions & 1 deletion drivers/arm/ws2812.c
Original file line number Diff line number Diff line change
@@ -1 +1,95 @@
#error("NOT SUPPORTED")
#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);
#ifdef RGBW
sendByte(ledarray[i].w);
#endif
}

wait_ns(RES);

chSysUnlock();
}
16 changes: 16 additions & 0 deletions drivers/arm/ws2812.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#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 50us to reset the LEDs
*/
void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds);
91 changes: 90 additions & 1 deletion drivers/arm/ws2812_spi.c
Original file line number Diff line number Diff line change
@@ -1 +1,90 @@
#error("NOT SUPPORTED")
#include "quantum.h"
#include "ws2812.h"

/* Adapted from https://github.com/gamazeps/ws2812b-chibios-SPIDMA/ */

#ifdef RGBW
# error "RGBW not supported"
#endif

// Define the spi your LEDs are plugged to here
#ifndef WS2812_SPI
# define WS2812_SPI SPID1
#endif

#ifndef WS2812_SPI_MOSI_PAL_MODE
# define WS2812_SPI_MOSI_PAL_MODE 5
#endif

#define BYTES_FOR_LED_BYTE 4
#define NB_COLORS 3
#define BYTES_FOR_LED (BYTES_FOR_LED_BYTE * NB_COLORS)
#define DATA_SIZE (BYTES_FOR_LED * RGBLED_NUM)
#define RESET_SIZE 200
#define PREAMBLE_SIZE 4

static uint8_t txbuf[PREAMBLE_SIZE + DATA_SIZE + RESET_SIZE] = {0};

/*
* As the trick here is to use the SPI to send a huge pattern of 0 and 1 to
* the ws2812b protocol, we use this helper function to translate bytes into
* 0s and 1s for the LED (with the appropriate timing).
*/
static uint8_t get_protocol_eq(uint8_t data, int pos) {
uint8_t eq = 0;
if (data & (1 << (2 * (3 - pos))))
eq = 0b1110;
else
eq = 0b1000;
if (data & (2 << (2 * (3 - pos))))
eq += 0b11100000;
else
eq += 0b10000000;
return eq;
}

static void set_led_color_rgb(LED_TYPE color, int pos) {
uint8_t* tx_start = &txbuf[PREAMBLE_SIZE];

for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + j] = get_protocol_eq(color.g, j);
for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE + j] = get_protocol_eq(color.r, j);
for (int j = 0; j < 4; j++) tx_start[BYTES_FOR_LED * pos + BYTES_FOR_LED_BYTE * 2 + j] = get_protocol_eq(color.b, j);
}

void ws2812_init(void) {
#if defined(USE_GPIOV1)
palSetLineMode(RGB_DI_PIN, PAL_MODE_STM32_ALTERNATE_PUSHPULL);
#else
palSetLineMode(RGB_DI_PIN, PAL_MODE_ALTERNATE(WS2812_SPI_MOSI_PAL_MODE) | PAL_STM32_OTYPE_PUSHPULL);
#endif

// TODO: more dynamic baudrate
static const SPIConfig spicfg = {
NULL, PAL_PORT(RGB_DI_PIN), PAL_PAD(RGB_DI_PIN),
SPI_CR1_BR_1 | SPI_CR1_BR_0 // baudrate : fpclk / 8 => 1tick is 0.32us (2.25 MHz)
};

spiAcquireBus(&WS2812_SPI); /* Acquire ownership of the bus. */
spiStart(&WS2812_SPI, &spicfg); /* Setup transfer parameters. */
spiSelect(&WS2812_SPI); /* Slave Select assertion. */
}

void ws2812_setleds(LED_TYPE* ledarray, uint16_t leds) {
static bool s_init = false;
if (!s_init) {
ws2812_init();
s_init = true;
}

for (uint8_t i = 0; i < leds; i++) {
set_led_color_rgb(ledarray[i], i);
}

// Send async - each led takes ~0.03ms, 50 leds ~1.5ms, animations flushing faster than send will cause issues.
// Instead spiSend can be used to send synchronously (or the thread logic can be added back).
#ifdef WS2812_SPI_SYNC
spiSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
#else
spiStartSend(&WS2812_SPI, sizeof(txbuf) / sizeof(txbuf[0]), txbuf);
#endif
}
141 changes: 4 additions & 137 deletions drivers/avr/ws2812.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,155 +36,22 @@
void ws2812_sendarray(uint8_t *array, uint16_t length);
void ws2812_sendarray_mask(uint8_t *array, uint16_t length, uint8_t pinmask);


#ifdef RGBW_BB_TWI

// Port for the I2C
# define I2C_DDR DDRD
# define I2C_PIN PIND
# define I2C_PORT PORTD

// Pins to be used in the bit banging
# define I2C_CLK 0
# define I2C_DAT 1

# define I2C_DATA_HI() \
I2C_DDR &= ~(1 << I2C_DAT); \
I2C_PORT |= (1 << I2C_DAT);
# define I2C_DATA_LO() \
I2C_DDR |= (1 << I2C_DAT); \
I2C_PORT &= ~(1 << I2C_DAT);

# define I2C_CLOCK_HI() \
I2C_DDR &= ~(1 << I2C_CLK); \
I2C_PORT |= (1 << I2C_CLK);
# define I2C_CLOCK_LO() \
I2C_DDR |= (1 << I2C_CLK); \
I2C_PORT &= ~(1 << I2C_CLK);

# define I2C_DELAY 1

void I2C_WriteBit(unsigned char c) {
if (c > 0) {
I2C_DATA_HI();
} else {
I2C_DATA_LO();
}

I2C_CLOCK_HI();
_delay_us(I2C_DELAY);

I2C_CLOCK_LO();
_delay_us(I2C_DELAY);

if (c > 0) {
I2C_DATA_LO();
}

_delay_us(I2C_DELAY);
}

// Inits bitbanging port, must be called before using the functions below
//
void I2C_Init(void) {
I2C_PORT &= ~((1 << I2C_DAT) | (1 << I2C_CLK));

I2C_CLOCK_HI();
I2C_DATA_HI();

_delay_us(I2C_DELAY);
}

// Send a START Condition
//
void I2C_Start(void) {
// set both to high at the same time
I2C_DDR &= ~((1 << I2C_DAT) | (1 << I2C_CLK));
_delay_us(I2C_DELAY);

I2C_DATA_LO();
_delay_us(I2C_DELAY);

I2C_CLOCK_LO();
_delay_us(I2C_DELAY);
}

// Send a STOP Condition
//
void I2C_Stop(void) {
I2C_CLOCK_HI();
_delay_us(I2C_DELAY);

I2C_DATA_HI();
_delay_us(I2C_DELAY);
}

// write a byte to the I2C slave device
//
unsigned char I2C_Write(unsigned char c) {
for (char i = 0; i < 8; i++) {
I2C_WriteBit(c & 128);

c <<= 1;
}

I2C_WriteBit(0);
_delay_us(I2C_DELAY);
_delay_us(I2C_DELAY);

// _delay_us(I2C_DELAY);
// return I2C_ReadBit();
return 0;
}

#endif

// Setleds for standard RGB
void inline ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) {
// ws2812_setleds_pin(ledarray,leds, _BV(ws2812_pin));
ws2812_setleds_pin(ledarray, leds, _BV(RGB_DI_PIN & 0xF));
}

void inline ws2812_setleds_pin(LED_TYPE *ledarray, uint16_t leds, uint8_t pinmask) {
// ws2812_DDRREG |= pinmask; // Enable DDR
// new universal format (DDR)
_SFR_IO8((RGB_DI_PIN >> 4) + 1) |= pinmask;

ws2812_sendarray_mask((uint8_t *)ledarray, leds + leds + leds, pinmask);
_delay_us(50);
}

// Setleds for SK6812RGBW
void inline ws2812_setleds_rgbw(LED_TYPE *ledarray, uint16_t leds) {
#ifdef RGBW_BB_TWI
uint8_t sreg_prev, twcr_prev;
sreg_prev = SREG;
twcr_prev = TWCR;
cli();
TWCR &= ~(1 << TWEN);
I2C_Init();
I2C_Start();
I2C_Write(0x84);
uint16_t datlen = leds << 2;
uint8_t curbyte;
uint8_t *data = (uint8_t *)ledarray;
while (datlen--) {
curbyte = *data++;
I2C_Write(curbyte);
}
I2C_Stop();
SREG = sreg_prev;
TWCR = twcr_prev;
#endif

// ws2812_DDRREG |= _BV(ws2812_pin); // Enable DDR
// new universal format (DDR)
_SFR_IO8((RGB_DI_PIN >> 4) + 1) |= _BV(RGB_DI_PIN & 0xF);

ws2812_sendarray_mask((uint8_t *)ledarray, leds << 2, _BV(RGB_DI_PIN & 0xF));
ws2812_sendarray_mask((uint8_t *)ledarray, leds * sizeof(LED_TYPE), pinmask);

#ifndef RGBW_BB_TWI
#ifdef RGBW
_delay_us(80);
#else
_delay_us(50);
#endif
}

Expand Down
3 changes: 1 addition & 2 deletions drivers/avr/ws2812.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@
* 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
* - Wait 50us to reset the LEDs
*/
void ws2812_setleds(LED_TYPE *ledarray, uint16_t number_of_leds);
void ws2812_setleds_pin(LED_TYPE *ledarray, uint16_t number_of_leds, uint8_t pinmask);
void ws2812_setleds_rgbw(LED_TYPE *ledarray, uint16_t number_of_leds);
Loading

0 comments on commit df91396

Please sign in to comment.