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 }