qmk_firmware

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

backlight_pwm.c (11588B)


      1 #include "backlight.h"
      2 #include "gpio.h"
      3 #include "progmem.h"
      4 #include <avr/io.h>
      5 #include <avr/interrupt.h>
      6 
      7 // Maximum duty cycle limit
      8 #ifndef BACKLIGHT_LIMIT_VAL
      9 #    define BACKLIGHT_LIMIT_VAL 255
     10 #endif
     11 
     12 #if (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == B5 || BACKLIGHT_PIN == B6 || BACKLIGHT_PIN == B7)
     13 #    define ICRx ICR1
     14 #    define TCCRxA TCCR1A
     15 #    define TCCRxB TCCR1B
     16 #    define TIMERx_OVF_vect TIMER1_OVF_vect
     17 #    define TIMSKx TIMSK1
     18 #    define TOIEx TOIE1
     19 
     20 #    if BACKLIGHT_PIN == B5
     21 #        define COMxx0 COM1A0
     22 #        define COMxx1 COM1A1
     23 #        define OCRxx OCR1A
     24 #    elif BACKLIGHT_PIN == B6
     25 #        define COMxx0 COM1B0
     26 #        define COMxx1 COM1B1
     27 #        define OCRxx OCR1B
     28 #    elif BACKLIGHT_PIN == B7
     29 #        define COMxx0 COM1C0
     30 #        define COMxx1 COM1C1
     31 #        define OCRxx OCR1C
     32 #    endif
     33 #elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == C4 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6)
     34 #    define ICRx ICR3
     35 #    define TCCRxA TCCR3A
     36 #    define TCCRxB TCCR3B
     37 #    define TIMERx_OVF_vect TIMER3_OVF_vect
     38 #    define TIMSKx TIMSK3
     39 #    define TOIEx TOIE3
     40 
     41 #    if BACKLIGHT_PIN == C4
     42 #        if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
     43 #            error This MCU has no C4 pin!
     44 #        else
     45 #            define COMxx0 COM3C0
     46 #            define COMxx1 COM3C1
     47 #            define OCRxx OCR3C
     48 #        endif
     49 #    elif BACKLIGHT_PIN == C5
     50 #        if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
     51 #            error This MCU has no C5 pin!
     52 #        else
     53 #            define COMxx0 COM3B0
     54 #            define COMxx1 COM3B1
     55 #            define OCRxx OCR3B
     56 #        endif
     57 #    elif BACKLIGHT_PIN == C6
     58 #        define COMxx0 COM3A0
     59 #        define COMxx1 COM3A1
     60 #        define OCRxx OCR3A
     61 #    endif
     62 #elif (defined(__AVR_AT90USB162__) || defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6)
     63 #    define ICRx ICR1
     64 #    define TCCRxA TCCR1A
     65 #    define TCCRxB TCCR1B
     66 #    define TIMERx_OVF_vect TIMER1_OVF_vect
     67 #    define TIMSKx TIMSK1
     68 #    define TOIEx TOIE1
     69 
     70 #    if BACKLIGHT_PIN == B7
     71 #        define COMxx0 COM1C0
     72 #        define COMxx1 COM1C1
     73 #        define OCRxx OCR1C
     74 #    elif BACKLIGHT_PIN == C5
     75 #        define COMxx0 COM1B0
     76 #        define COMxx1 COM1B1
     77 #        define OCRxx OCR1B
     78 #    elif BACKLIGHT_PIN == C6
     79 #        define COMxx0 COM1A0
     80 #        define COMxx1 COM1A1
     81 #        define OCRxx OCR1A
     82 #    endif
     83 #elif defined(__AVR_ATmega32A__) && (BACKLIGHT_PIN == D4 || BACKLIGHT_PIN == D5)
     84 #    define ICRx ICR1
     85 #    define TCCRxA TCCR1A
     86 #    define TCCRxB TCCR1B
     87 #    define TIMERx_OVF_vect TIMER1_OVF_vect
     88 #    define TIMSKx TIMSK
     89 #    define TOIEx TOIE1
     90 
     91 #    if BACKLIGHT_PIN == D4
     92 #        define COMxx0 COM1B0
     93 #        define COMxx1 COM1B1
     94 #        define OCRxx OCR1B
     95 #    elif BACKLIGHT_PIN == D5
     96 #        define COMxx0 COM1A0
     97 #        define COMxx1 COM1A1
     98 #        define OCRxx OCR1A
     99 #    endif
    100 #elif (defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)) && (BACKLIGHT_PIN == B1 || BACKLIGHT_PIN == B2)
    101 #    define ICRx ICR1
    102 #    define TCCRxA TCCR1A
    103 #    define TCCRxB TCCR1B
    104 #    define TIMERx_OVF_vect TIMER1_OVF_vect
    105 #    define TIMSKx TIMSK1
    106 #    define TOIEx TOIE1
    107 
    108 #    if BACKLIGHT_PIN == B1
    109 #        define COMxx0 COM1A0
    110 #        define COMxx1 COM1A1
    111 #        define OCRxx OCR1A
    112 #    elif BACKLIGHT_PIN == B2
    113 #        define COMxx0 COM1B0
    114 #        define COMxx1 COM1B1
    115 #        define OCRxx OCR1B
    116 #    endif
    117 #endif
    118 
    119 #ifndef BACKLIGHT_RESOLUTION
    120 #    define BACKLIGHT_RESOLUTION 0xFFFFU
    121 #endif
    122 
    123 #if (BACKLIGHT_RESOLUTION > 0xFFFF || BACKLIGHT_RESOLUTION < 0x00FF)
    124 #    error "Backlight resolution must be between 0x00FF and 0xFFFF"
    125 #endif
    126 
    127 #define BREATHING_SCALE_FACTOR F_CPU / BACKLIGHT_RESOLUTION / 120
    128 
    129 static inline void enable_pwm(void) {
    130 #if BACKLIGHT_ON_STATE == 1
    131     TCCRxA |= _BV(COMxx1);
    132 #else
    133     TCCRxA |= _BV(COMxx1) | _BV(COMxx0);
    134 #endif
    135 }
    136 
    137 static inline void disable_pwm(void) {
    138 #if BACKLIGHT_ON_STATE == 1
    139     TCCRxA &= ~(_BV(COMxx1));
    140 #else
    141     TCCRxA &= ~(_BV(COMxx1) | _BV(COMxx0));
    142 #endif
    143 }
    144 
    145 // See http://jared.geek.nz/2013/feb/linear-led-pwm
    146 static uint16_t cie_lightness(uint16_t v) {
    147     if (v <= (uint32_t)ICRx / 12) // If the value is less than or equal to ~8% of max
    148     {
    149         return v / 9; // Same as dividing by 900%
    150     } else {
    151         // In the next two lines values are bit-shifted. This is to avoid loosing decimals in integer math.
    152         uint32_t y   = (((uint32_t)v + (uint32_t)ICRx / 6) << 5) / ((uint32_t)ICRx / 6 + ICRx); // If above 8%, add ~16% of max, and normalize with (max + ~16% max)
    153         uint32_t out = (y * y * y * ICRx) >> 15;                                                // Cube it and undo the bit-shifting. (which is now three times as much due to the cubing)
    154 
    155         if (out > ICRx) // Avoid overflows
    156         {
    157             out = ICRx;
    158         }
    159         return (uint16_t)out;
    160     }
    161 }
    162 
    163 // rescale the supplied backlight value to be in terms of the value limit	// range for val is [0..ICRx]. PWM pin is high while the timer count is below val.
    164 static uint32_t rescale_limit_val(uint32_t val) {
    165     return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256;
    166 }
    167 
    168 // range for val is [0..ICRx]. PWM pin is high while the timer count is below val.
    169 static inline void set_pwm(uint16_t val) {
    170     OCRxx = val;
    171 }
    172 
    173 void backlight_set(uint8_t level) {
    174     if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS;
    175 
    176     if (level == 0) {
    177         // Turn off PWM control on backlight pin
    178         disable_pwm();
    179     } else {
    180         // Turn on PWM control of backlight pin
    181         enable_pwm();
    182     }
    183     // Set the brightness
    184     set_pwm(cie_lightness(rescale_limit_val(ICRx * (uint32_t)level / BACKLIGHT_LEVELS)));
    185 }
    186 
    187 void backlight_task(void) {}
    188 
    189 #ifdef BACKLIGHT_BREATHING
    190 #    define BREATHING_NO_HALT 0
    191 #    define BREATHING_HALT_OFF 1
    192 #    define BREATHING_HALT_ON 2
    193 #    define BREATHING_STEPS 128
    194 
    195 static uint8_t  breathing_halt    = BREATHING_NO_HALT;
    196 static uint16_t breathing_counter = 0;
    197 
    198 static uint8_t breath_scale_counter = 1;
    199 /* Run the breathing loop at ~120Hz*/
    200 const uint8_t breathing_ISR_frequency = 120;
    201 
    202 bool is_breathing(void) {
    203     return !!(TIMSKx & _BV(TOIEx));
    204 }
    205 
    206 #    define breathing_interrupt_enable() \
    207         do {                             \
    208             TIMSKx |= _BV(TOIEx);        \
    209         } while (0)
    210 #    define breathing_interrupt_disable() \
    211         do {                              \
    212             TIMSKx &= ~_BV(TOIEx);        \
    213         } while (0)
    214 
    215 #    define breathing_min()        \
    216         do {                       \
    217             breathing_counter = 0; \
    218         } while (0)
    219 #    define breathing_max()                                                           \
    220         do {                                                                          \
    221             breathing_counter = get_breathing_period() * breathing_ISR_frequency / 2; \
    222         } while (0)
    223 
    224 void breathing_enable(void) {
    225     breathing_counter = 0;
    226     breathing_halt    = BREATHING_NO_HALT;
    227     breathing_interrupt_enable();
    228 }
    229 
    230 void breathing_pulse(void) {
    231     if (get_backlight_level() == 0)
    232         breathing_min();
    233     else
    234         breathing_max();
    235     breathing_halt = BREATHING_HALT_ON;
    236     breathing_interrupt_enable();
    237 }
    238 
    239 void breathing_disable(void) {
    240     breathing_interrupt_disable();
    241     // Restore backlight level
    242     backlight_set(get_backlight_level());
    243 }
    244 
    245 void breathing_self_disable(void) {
    246     if (get_backlight_level() == 0)
    247         breathing_halt = BREATHING_HALT_OFF;
    248     else
    249         breathing_halt = BREATHING_HALT_ON;
    250 }
    251 
    252 /* To generate breathing curve in python:
    253  * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)]
    254  */
    255 static const uint8_t breathing_table[BREATHING_STEPS] PROGMEM = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 17, 20, 24, 28, 32, 36, 41, 46, 51, 57, 63, 70, 76, 83, 91, 98, 106, 113, 121, 129, 138, 146, 154, 162, 170, 178, 185, 193, 200, 207, 213, 220, 225, 231, 235, 240, 244, 247, 250, 252, 253, 254, 255, 254, 253, 252, 250, 247, 244, 240, 235, 231, 225, 220, 213, 207, 200, 193, 185, 178, 170, 162, 154, 146, 138, 129, 121, 113, 106, 98, 91, 83, 76, 70, 63, 57, 51, 46, 41, 36, 32, 28, 24, 20, 17, 15, 12, 10, 8, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    256 
    257 // Use this before the cie_lightness function.
    258 static inline uint16_t scale_backlight(uint16_t v) {
    259     return v / BACKLIGHT_LEVELS * get_backlight_level();
    260 }
    261 
    262 /* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run
    263  * about 244 times per second.
    264  *
    265  * The following ISR runs at F_CPU/ISRx. With a 16MHz clock and default pwm resolution, that means 244Hz
    266  */
    267 ISR(TIMERx_OVF_vect) {
    268     // Only run this ISR at ~120 Hz
    269     if (breath_scale_counter++ == BREATHING_SCALE_FACTOR) {
    270         breath_scale_counter = 1;
    271     } else {
    272         return;
    273     }
    274     uint16_t interval = (uint16_t)get_breathing_period() * breathing_ISR_frequency / BREATHING_STEPS;
    275     // resetting after one period to prevent ugly reset at overflow.
    276     breathing_counter = (breathing_counter + 1) % (get_breathing_period() * breathing_ISR_frequency);
    277     uint8_t index     = breathing_counter / interval;
    278     // limit index to max step value
    279     if (index >= BREATHING_STEPS) {
    280         index = BREATHING_STEPS - 1;
    281     }
    282 
    283     if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) {
    284         breathing_interrupt_disable();
    285     }
    286 
    287     // Set PWM to a brightnessvalue scaled to the configured resolution
    288     set_pwm(cie_lightness(rescale_limit_val(scale_backlight((uint32_t)pgm_read_byte(&breathing_table[index]) * ICRx / 255))));
    289 }
    290 
    291 #endif // BACKLIGHT_BREATHING
    292 
    293 void backlight_init_ports(void) {
    294     gpio_set_pin_output(BACKLIGHT_PIN);
    295 #if BACKLIGHT_ON_STATE == 1
    296     gpio_write_pin_low(BACKLIGHT_PIN);
    297 #else
    298     gpio_write_pin_high(BACKLIGHT_PIN);
    299 #endif
    300 
    301     // I could write a wall of text here to explain... but TL;DW
    302     // Go read the ATmega32u4 datasheet.
    303     // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
    304 
    305     // Pin PB7 = OCR1C (Timer 1, Channel C)
    306     // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
    307     // (i.e. start high, go low when counter matches.)
    308     // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0
    309     // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1
    310 
    311     /*
    312     14.8.3:
    313     "In fast PWM mode, the compare units allow generation of PWM waveforms on the OCnx pins. Setting the COMnx1:0 bits to two will produce a non-inverted PWM [..]."
    314     "In fast PWM mode the counter is incremented until the counter value matches either one of the fixed values 0x00FF, 0x01FF, or 0x03FF (WGMn3:0 = 5, 6, or 7), the value in ICRn (WGMn3:0 = 14), or the value in OCRnA (WGMn3:0 = 15)."
    315     */
    316     TCCRxA = _BV(COMxx1) | _BV(WGM11);            // = 0b00001010;
    317     TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
    318     ICRx   = BACKLIGHT_RESOLUTION;
    319 
    320     backlight_init();
    321 
    322 #ifdef BACKLIGHT_BREATHING
    323     if (is_backlight_breathing()) {
    324         breathing_enable();
    325     }
    326 #endif
    327 }