Rewrite APA102 support (#10894)

* Rewrite APA102 support

The APA102 source was broken by commit 16a15c1cfc as it did not include the
quantum header. This commit addresses that, as well as other issues with
transferring bytes over the SPI interface, i.e. it was not setting the
clock pin back to low after sending a bit.

The deviation when sending the end frame is kept, but updated to the
latest from the referenced project.

Finally, these changes expose the global LED brightness parameter
of the APA102. Brightness values are configurable through
`APA102_DEFAULT_BRIGHTNESS` and `APA102_MAX_BRIGHTNESS`.

* Fix typo in led brightness extern

* Move driver out of AVR directory and add delay for ARM

* Experimental APA102 support on AVR and ARM

Co-authored-by: Alde Rojas <hello@alde.io>

* Refactor apa102_send_byte() calls to a loop

* Implement io_wait function for ARM

* Move APA102 drivers to own directory, fix copyright notice

* Add APA102 keymap to handwired/onekey

* Simplify RGBLIGHT_ENABLE/DRIVER option handling

Co-authored-by: Mikkel Jeppesen <2756925+Duckle29@users.noreply.github.com>
master
Aldehir Rojas 2020-12-29 18:28:49 -06:00 committed by GitHub
parent 7dd99f2b22
commit 4f2f21dc05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 259 additions and 123 deletions

View File

@ -154,18 +154,38 @@ else
endif endif
endif endif
RGBLIGHT_ENABLE ?= no
VALID_RGBLIGHT_TYPES := WS2812 APA102 custom
ifeq ($(strip $(RGBLIGHT_CUSTOM_DRIVER)), yes)
RGBLIGHT_DRIVER ?= custom
endif
ifeq ($(strip $(RGBLIGHT_ENABLE)), yes) ifeq ($(strip $(RGBLIGHT_ENABLE)), yes)
POST_CONFIG_H += $(QUANTUM_DIR)/rgblight_post_config.h RGBLIGHT_DRIVER ?= WS2812
OPT_DEFS += -DRGBLIGHT_ENABLE
SRC += $(QUANTUM_DIR)/color.c ifeq ($(filter $(RGBLIGHT_DRIVER),$(VALID_RGBLIGHT_TYPES)),)
SRC += $(QUANTUM_DIR)/rgblight.c $(error RGBLIGHT_DRIVER="$(RGBLIGHT_DRIVER)" is not a valid RGB type)
CIE1931_CURVE := yes
RGB_KEYCODES_ENABLE := yes
ifeq ($(strip $(RGBLIGHT_CUSTOM_DRIVER)), yes)
OPT_DEFS += -DRGBLIGHT_CUSTOM_DRIVER
else else
POST_CONFIG_H += $(QUANTUM_DIR)/rgblight_post_config.h
OPT_DEFS += -DRGBLIGHT_ENABLE
SRC += $(QUANTUM_DIR)/color.c
SRC += $(QUANTUM_DIR)/rgblight.c
CIE1931_CURVE := yes
RGB_KEYCODES_ENABLE := yes
endif
ifeq ($(strip $(RGBLIGHT_DRIVER)), WS2812)
WS2812_DRIVER_REQUIRED := yes WS2812_DRIVER_REQUIRED := yes
endif endif
ifeq ($(strip $(RGBLIGHT_DRIVER)), APA102)
APA102_DRIVER_REQUIRED := yes
endif
ifeq ($(strip $(RGBLIGHT_DRIVER)), custom)
OPT_DEFS += -DRGBLIGHT_CUSTOM_DRIVER
endif
endif endif
@ -243,6 +263,11 @@ endif
WS2812_DRIVER_REQUIRED := yes WS2812_DRIVER_REQUIRED := yes
endif endif
ifeq ($(strip $(RGB_MATRIX_DRIVER)), APA102)
OPT_DEFS += -DAPA102
APA102_DRIVER_REQUIRED := yes
endif
ifeq ($(strip $(RGB_MATRIX_CUSTOM_KB)), yes) ifeq ($(strip $(RGB_MATRIX_CUSTOM_KB)), yes)
OPT_DEFS += -DRGB_MATRIX_CUSTOM_KB OPT_DEFS += -DRGB_MATRIX_CUSTOM_KB
endif endif
@ -345,6 +370,11 @@ ifeq ($(strip $(WS2812_DRIVER_REQUIRED)), yes)
endif endif
endif endif
ifeq ($(strip $(APA102_DRIVER_REQUIRED)), yes)
COMMON_VPATH += $(DRIVER_PATH)/apa102
SRC += apa102.c
endif
ifeq ($(strip $(VISUALIZER_ENABLE)), yes) ifeq ($(strip $(VISUALIZER_ENABLE)), yes)
CIE1931_CURVE := yes CIE1931_CURVE := yes
endif endif

View File

@ -129,6 +129,28 @@ Configure the hardware via your `config.h`:
--- ---
### APA102 :id=apa102
There is basic support for APA102 based addressable LED strands. To enable it, add this to your `rules.mk`:
```makefile
RGB_MATRIX_ENABLE = yes
RGB_MATRIX_DRIVER = APA102
```
Configure the hardware via your `config.h`:
```c
// The pin connected to the data pin of the LEDs
#define RGB_DI_PIN D7
// The pin connected to the clock pin of the LEDs
#define RGB_CI_PIN D6
// The number of LEDs connected
#define DRIVER_LED_TOTAL 70
```
---
From this point forward the configuration is the same for all the drivers. The `led_config_t` struct provides a key electrical matrix to led index lookup table, what the physical position of each LED is on the board, and what type of key or usage the LED if the LED represents. Here is a brief example: From this point forward the configuration is the same for all the drivers. The `led_config_t` struct provides a key electrical matrix to led index lookup table, what the physical position of each LED is on the board, and what type of key or usage the LED if the LED represents. Here is a brief example:
```c ```c

View File

@ -10,6 +10,7 @@ Currently QMK supports the following addressable LEDs (however, the white LED in
* WS2811, WS2812, WS2812B, WS2812C, etc. * WS2811, WS2812, WS2812B, WS2812C, etc.
* SK6812, SK6812MINI, SK6805 * SK6812, SK6812MINI, SK6805
* APA102
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. 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.
@ -21,11 +22,19 @@ On keyboards with onboard RGB LEDs, it is usually enabled by default. If it is n
RGBLIGHT_ENABLE = yes RGBLIGHT_ENABLE = yes
``` ```
At minimum you must define the data pin your LED strip is connected to, and the number of LEDs in the strip, in your `config.h`. If your keyboard has onboard RGB LEDs, and you are simply creating a keymap, you usually won't need to modify these. For APA102 LEDs, add the following to your `rules.mk`:
```make
RGBLIGHT_ENABLE = yes
RGBLIGHT_DRIVER = APA102
```
At minimum you must define the data pin your LED strip is connected to, and the number of LEDs in the strip, in your `config.h`. For APA102 LEDs, you must also define the clock pin. If your keyboard has onboard RGB LEDs, and you are simply creating a keymap, you usually won't need to modify these.
|Define |Description | |Define |Description |
|---------------|---------------------------------------------------------------------------------------------------------| |---------------|---------------------------------------------------------------------------------------------------------|
|`RGB_DI_PIN` |The pin connected to the data pin of the LEDs | |`RGB_DI_PIN` |The pin connected to the data pin of the LEDs |
|`RGB_CI_PIN` |The pin connected to the clock pin of the LEDs (APA102 only) |
|`RGBLED_NUM` |The number of LEDs connected | |`RGBLED_NUM` |The number of LEDs connected |
|`RGBLED_SPLIT` |(Optional) For split keyboards, the number of LEDs connected on each half directly wired to `RGB_DI_PIN` | |`RGBLED_SPLIT` |(Optional) For split keyboards, the number of LEDs connected on each half directly wired to `RGB_DI_PIN` |

View File

@ -0,0 +1,151 @@
/* Copyright 2020 Aldehir Rojas
* Copyright 2017 Mikkel (Duckle29)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "apa102.h"
#include "quantum.h"
#ifndef APA102_NOPS
# if defined(__AVR__)
# define APA102_NOPS 0 // AVR at 16 MHz already spends 62.5 ns per clock, so no extra delay is needed
# elif defined(PROTOCOL_CHIBIOS)
# include "hal.h"
# if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX)
# define APA102_NOPS (100 / (1000000000L / (STM32_SYSCLK / 4))) // This calculates how many loops of 4 nops to run to delay 100 ns
# else
# error("APA102_NOPS configuration required")
# define APA102_NOPS 0 // this just pleases the compile so the above error is easier to spot
# endif
# endif
#endif
#define io_wait \
do { \
for (int i = 0; i < APA102_NOPS; i++) { \
__asm__ volatile("nop\n\t" \
"nop\n\t" \
"nop\n\t" \
"nop\n\t"); \
} \
} while (0)
#define APA102_SEND_BIT(byte, bit) \
do { \
writePin(RGB_DI_PIN, (byte >> bit) & 1); \
io_wait; \
writePinHigh(RGB_CI_PIN); \
io_wait; \
writePinLow(RGB_CI_PIN); \
io_wait; \
} while (0)
uint8_t apa102_led_brightness = APA102_DEFAULT_BRIGHTNESS;
void static apa102_start_frame(void);
void static apa102_end_frame(uint16_t num_leds);
void static apa102_send_frame(uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness);
void static apa102_send_byte(uint8_t byte);
void apa102_setleds(LED_TYPE *start_led, uint16_t num_leds) {
LED_TYPE *end = start_led + num_leds;
apa102_start_frame();
for (LED_TYPE *led = start_led; led < end; led++) {
apa102_send_frame(led->r, led->g, led->b, apa102_led_brightness);
}
apa102_end_frame(num_leds);
}
// Overwrite the default rgblight_call_driver to use apa102 driver
void rgblight_call_driver(LED_TYPE *start_led, uint8_t num_leds) { apa102_setleds(start_led, num_leds); }
void static apa102_init(void) {
setPinOutput(RGB_DI_PIN);
setPinOutput(RGB_CI_PIN);
writePinLow(RGB_DI_PIN);
writePinLow(RGB_CI_PIN);
}
void apa102_set_brightness(uint8_t brightness) {
if (brightness > APA102_MAX_BRIGHTNESS) {
apa102_led_brightness = APA102_MAX_BRIGHTNESS;
} else if (brightness < 0) {
apa102_led_brightness = 0;
} else {
apa102_led_brightness = brightness;
}
}
void static apa102_send_frame(uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness) {
apa102_send_byte(0b11100000 | brightness);
apa102_send_byte(blue);
apa102_send_byte(green);
apa102_send_byte(red);
}
void static apa102_start_frame(void) {
apa102_init();
for (uint16_t i = 0; i < 4; i++) {
apa102_send_byte(0);
}
}
void static apa102_end_frame(uint16_t num_leds) {
// This function has been taken from: https://github.com/pololu/apa102-arduino/blob/master/APA102.h
// and adapted. The code is MIT licensed. I think thats compatible?
//
// The data stream seen by the last LED in the chain will be delayed by
// (count - 1) clock edges, because each LED before it inverts the clock
// line and delays the data by one clock edge. Therefore, to make sure
// the last LED actually receives the data we wrote, the number of extra
// edges we send at the end of the frame must be at least (count - 1).
//
// Assuming we only want to send these edges in groups of size K, the
// C/C++ expression for the minimum number of groups to send is:
//
// ((count - 1) + (K - 1)) / K
//
// The C/C++ expression above is just (count - 1) divided by K,
// rounded up to the nearest whole number if there is a remainder.
//
// We set K to 16 and use the formula above as the number of frame-end
// bytes to transfer. Each byte has 16 clock edges.
//
// We are ignoring the specification for the end frame in the APA102
// datasheet, which says to send 0xFF four times, because it does not work
// when you have 66 LEDs or more, and also it results in unwanted white
// pixels if you try to update fewer LEDs than are on your LED strip.
uint16_t iterations = (num_leds + 14) / 16;
for (uint16_t i = 0; i < iterations; i++) {
apa102_send_byte(0);
}
apa102_init();
}
void static apa102_send_byte(uint8_t byte) {
APA102_SEND_BIT(byte, 7);
APA102_SEND_BIT(byte, 6);
APA102_SEND_BIT(byte, 5);
APA102_SEND_BIT(byte, 4);
APA102_SEND_BIT(byte, 3);
APA102_SEND_BIT(byte, 2);
APA102_SEND_BIT(byte, 1);
APA102_SEND_BIT(byte, 0);
}

View File

@ -1,10 +1,5 @@
/* /* Copyright 2020 Aldehir Rojas
* light weight WS2812 lib include * Copyright 2017 Mikkel (Duckle29)
*
* Version 2.3 - Nev 29th 2015
* Author: Tim (cpldcpu@gmail.com)
*
* Please do not change this file! All configuration is handled in "ws2812_config.h"
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,24 +17,25 @@
#pragma once #pragma once
#include <avr/io.h>
#include <avr/interrupt.h>
#include "color.h" #include "color.h"
#ifndef APA102_DEFAULT_BRIGHTNESS
# define APA102_DEFAULT_BRIGHTNESS 31
#endif
#define APA102_MAX_BRIGHTNESS 31
extern uint8_t apa102_led_brightness;
/* User Interface /* User Interface
* *
* Input: * Input:
* ledarray: An array of GRB data describing the LED colors * start_led: An array of GRB data describing the LED colors
* number_of_leds: The number of LEDs to write * num_leds: The number of LEDs to write
* pinmask (optional): Bitmask describing the output bin. e.g. _BV(PB0)
* *
* The functions will perform the following actions: * The functions will perform the following actions:
* - Set the data-out pin as output * - Set the data-out pin as output
* - Send out the LED data * - Send out the LED data
* - Wait 50<EFBFBD>s to reset the LEDs
*/ */
void apa102_setleds(LED_TYPE *start_led, uint16_t num_leds);
void apa102_setleds(LED_TYPE *ledarray, uint16_t number_of_leds); void apa102_set_brightness(uint8_t brightness);
void apa102_setleds_pin(LED_TYPE *ledarray, uint16_t number_of_leds, uint8_t pinmask);
void apa102_setleds_rgbw(LED_TYPE *ledarray, uint16_t number_of_leds);

View File

@ -1,96 +0,0 @@
/*
* APA102 lib V1.0a
*
* Controls APA102 RGB-LEDs
* Author: Mikkel (Duckle29 on GitHub)
*
* Dec 22th, 2017 v1.0a Initial Version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "apa102.h"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>
#include "debug.h"
// Setleds for standard RGB
void inline apa102_setleds(LED_TYPE *ledarray, uint16_t leds) { apa102_setleds_pin(ledarray, leds, _BV(RGB_DI_PIN & 0xF), _BV(RGB_CLK_PIN & 0xF)); }
void static inline apa102_setleds_pin(LED_TYPE *ledarray, uint16_t leds, uint8_t pinmask_DI, uint8_t pinmask_CLK) {
setPinOutput(RGB_DI_PIN);
setPinOutput(RGB_CLK_PIN);
apa102_send_array((uint8_t *)ledarray, leds)
}
void apa102_send_array(uint8_t *data, uint16_t leds) { // Data is struct of 3 bytes. RGB - leds is number of leds in data
apa102_start_frame();
while (leds--) {
apa102_send_frame(0xFF000000 | (data->b << 16) | (data->g << 8) | data->r);
data++;
}
apa102_end_frame(leds);
}
void apa102_send_frame(uint32_t frame) {
for (uint32_t i = 0xFF; i > 0;) {
apa102_send_byte(frame & i);
i = i << 8;
}
}
void apa102_start_frame() { apa102_send_frame(0); }
void apa102_end_frame(uint16_t leds) {
// This function has been taken from: https://github.com/pololu/apa102-arduino/blob/master/APA102.h
// and adapted. The code is MIT licensed. I think thats compatible?
// We need to send some more bytes to ensure that all the LEDs in the
// chain see their new color and start displaying it.
//
// The data stream seen by the last LED in the chain will be delayed by
// (count - 1) clock edges, because each LED before it inverts the clock
// line and delays the data by one clock edge. Therefore, to make sure
// the last LED actually receives the data we wrote, the number of extra
// edges we send at the end of the frame must be at least (count - 1).
// For the APA102C, that is sufficient.
//
// The SK9822 only updates after it sees 32 zero bits followed by one more
// rising edge. To avoid having the update time depend on the color of
// the last LED, we send a dummy 0xFF byte. (Unfortunately, this means
// that partial updates of the beginning of an LED strip are not possible;
// the LED after the last one you are trying to update will be black.)
// After that, to ensure that the last LED in the chain sees 32 zero bits
// and a rising edge, we need to send at least 65 + (count - 1) edges. It
// is sufficent and simpler to just send (5 + count/16) bytes of zeros.
//
// We are ignoring the specification for the end frame in the APA102/SK9822
// datasheets because it does not actually ensure that all the LEDs will
// start displaying their new colors right away.
apa102_send_byte(0xFF);
for (uint16_t i = 0; i < 5 + leds / 16; i++) {
apa102_send_byte(0);
}
}
void apa102_send_byte(uint8_t byte) {
uint8_t i;
for (i = 0; i < 8; i++) {
writePin(RGB_DI_PIN, !!(byte & (1 << (7 - i))));
writePinHigh(RGB_CLK_PIN);
}
}

View File

@ -25,5 +25,6 @@
#define BACKLIGHT_PIN B6 #define BACKLIGHT_PIN B6
#define RGB_DI_PIN F6 #define RGB_DI_PIN F6
#define RGB_CI_PIN B1
#define ADC_PIN F6 #define ADC_PIN F6

View File

@ -0,0 +1,5 @@
#pragma once
#define RGBLED_NUM 40
#define APA102_DEFAULT_BRIGHTNESS 5
#define RGBLIGHT_ANIMATIONS

View File

@ -0,0 +1,14 @@
#include QMK_KEYBOARD_H
#include "apa102.h" // Only needed if you want to use the global brightness function
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
LAYOUT_ortho_1x1(RGB_MOD)
};
void keyboard_post_init_user(void) {
apa102_set_brightness(5);
rgblight_enable_noeeprom();
rgblight_sethsv_noeeprom_cyan();
rgblight_mode_noeeprom(RGBLIGHT_MODE_RAINBOW_SWIRL);
}

View File

@ -0,0 +1,2 @@
RGBLIGHT_ENABLE = yes
RGBLIGHT_DRIVER = APA102

View File

@ -25,5 +25,6 @@
#define BACKLIGHT_PIN B6 #define BACKLIGHT_PIN B6
#define RGB_DI_PIN F6 #define RGB_DI_PIN F6
#define RGB_CI_PIN B1
#define ADC_PIN F6 #define ADC_PIN F6

View File

@ -28,5 +28,6 @@
#define BACKLIGHT_PAL_MODE 2 #define BACKLIGHT_PAL_MODE 2
#define RGB_DI_PIN A0 #define RGB_DI_PIN A0
#define RGB_CI_PIN B13
#define ADC_PIN A0 #define ADC_PIN A0