qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

apa102.c (5885B)


      1 /* Copyright 2020 Aldehir Rojas
      2  * Copyright 2017 Mikkel (Duckle29)
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 2 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     16  */
     17 
     18 #include "apa102.h"
     19 #include "gpio.h"
     20 
     21 #ifndef APA102_NOPS
     22 #    if defined(__AVR__)
     23 #        define APA102_NOPS 0 // AVR at 16 MHz already spends 62.5 ns per clock, so no extra delay is needed
     24 #    elif defined(PROTOCOL_CHIBIOS)
     25 #        include "hal.h"
     26 #        include "chibios_config.h"
     27 #        if defined(STM32F0XX) || defined(STM32F1XX) || defined(STM32F3XX) || defined(STM32F4XX) || defined(STM32L0XX) || defined(AT32F415) || defined(GD32VF103) || defined(MCU_RP)
     28 #            define APA102_NOPS (100 / (1000000000L / (CPU_CLOCK / 4))) // This calculates how many loops of 4 nops to run to delay 100 ns
     29 #        else
     30 #            error APA102_NOPS configuration required
     31 #            define APA102_NOPS 0 // this just pleases the compile so the above error is easier to spot
     32 #        endif
     33 #    endif
     34 #endif
     35 
     36 #define io_wait                                 \
     37     do {                                        \
     38         for (int i = 0; i < APA102_NOPS; i++) { \
     39             __asm__ volatile("nop\n\t"          \
     40                              "nop\n\t"          \
     41                              "nop\n\t"          \
     42                              "nop\n\t");        \
     43         }                                       \
     44     } while (0)
     45 
     46 #define APA102_SEND_BIT(byte, bit)                        \
     47     do {                                                  \
     48         gpio_write_pin(APA102_DI_PIN, (byte >> bit) & 1); \
     49         io_wait;                                          \
     50         gpio_write_pin_high(APA102_CI_PIN);               \
     51         io_wait;                                          \
     52         gpio_write_pin_low(APA102_CI_PIN);                \
     53         io_wait;                                          \
     54     } while (0)
     55 
     56 rgb_t   apa102_leds[APA102_LED_COUNT];
     57 uint8_t apa102_led_brightness = APA102_DEFAULT_BRIGHTNESS;
     58 
     59 static void apa102_send_byte(uint8_t byte) {
     60     APA102_SEND_BIT(byte, 7);
     61     APA102_SEND_BIT(byte, 6);
     62     APA102_SEND_BIT(byte, 5);
     63     APA102_SEND_BIT(byte, 4);
     64     APA102_SEND_BIT(byte, 3);
     65     APA102_SEND_BIT(byte, 2);
     66     APA102_SEND_BIT(byte, 1);
     67     APA102_SEND_BIT(byte, 0);
     68 }
     69 
     70 static void apa102_start_frame(void) {
     71     gpio_write_pin_low(APA102_DI_PIN);
     72     gpio_write_pin_low(APA102_CI_PIN);
     73 
     74     for (uint16_t i = 0; i < 4; i++) {
     75         apa102_send_byte(0);
     76     }
     77 }
     78 
     79 static void apa102_end_frame(uint16_t num_leds) {
     80     // This function has been taken from: https://github.com/pololu/apa102-arduino/blob/master/APA102.h
     81     // and adapted. The code is MIT licensed. I think thats compatible?
     82     //
     83     // The data stream seen by the last LED in the chain will be delayed by
     84     // (count - 1) clock edges, because each LED before it inverts the clock
     85     // line and delays the data by one clock edge.  Therefore, to make sure
     86     // the last LED actually receives the data we wrote, the number of extra
     87     // edges we send at the end of the frame must be at least (count - 1).
     88     //
     89     // Assuming we only want to send these edges in groups of size K, the
     90     // C/C++ expression for the minimum number of groups to send is:
     91     //
     92     //   ((count - 1) + (K - 1)) / K
     93     //
     94     // The C/C++ expression above is just (count - 1) divided by K,
     95     // rounded up to the nearest whole number if there is a remainder.
     96     //
     97     // We set K to 16 and use the formula above as the number of frame-end
     98     // bytes to transfer.  Each byte has 16 clock edges.
     99     //
    100     // We are ignoring the specification for the end frame in the APA102
    101     // datasheet, which says to send 0xFF four times, because it does not work
    102     // when you have 66 LEDs or more, and also it results in unwanted white
    103     // pixels if you try to update fewer LEDs than are on your LED strip.
    104     uint16_t iterations = (num_leds + 14) / 16;
    105     for (uint16_t i = 0; i < iterations; i++) {
    106         apa102_send_byte(0);
    107     }
    108 
    109     gpio_write_pin_low(APA102_DI_PIN);
    110     gpio_write_pin_low(APA102_CI_PIN);
    111 }
    112 
    113 static void apa102_send_frame(uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness) {
    114     apa102_send_byte(0b11100000 | brightness);
    115     apa102_send_byte(blue);
    116     apa102_send_byte(green);
    117     apa102_send_byte(red);
    118 }
    119 
    120 void apa102_init(void) {
    121     gpio_set_pin_output(APA102_DI_PIN);
    122     gpio_set_pin_output(APA102_CI_PIN);
    123 }
    124 
    125 void apa102_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) {
    126     apa102_leds[index].r = red;
    127     apa102_leds[index].g = green;
    128     apa102_leds[index].b = blue;
    129 }
    130 
    131 void apa102_set_color_all(uint8_t red, uint8_t green, uint8_t blue) {
    132     for (uint16_t i = 0; i < APA102_LED_COUNT; i++) {
    133         apa102_set_color(i, red, green, blue);
    134     }
    135 }
    136 
    137 void apa102_flush(void) {
    138     apa102_start_frame();
    139     for (uint8_t i = 0; i < APA102_LED_COUNT; i++) {
    140         apa102_send_frame(apa102_leds[i].r, apa102_leds[i].g, apa102_leds[i].b, apa102_led_brightness);
    141     }
    142     apa102_end_frame(APA102_LED_COUNT);
    143 }
    144 
    145 void apa102_set_brightness(uint8_t brightness) {
    146     if (brightness > APA102_MAX_BRIGHTNESS) {
    147         apa102_led_brightness = APA102_MAX_BRIGHTNESS;
    148     } else if (brightness < 0) {
    149         apa102_led_brightness = 0;
    150     } else {
    151         apa102_led_brightness = brightness;
    152     }
    153 }