aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrice Figureau <brice@daysofwonder.com>2019-04-22 11:34:13 -0400
committerMechMerlin <30334081+mechmerlin@users.noreply.github.com>2019-04-22 08:34:13 -0700
commitb61baf4281bde34bfe28aaa1109bd5d5c6471116 (patch)
treefa60f457b48bbb4d635b32f5b48b03a7dd0dccf7
parentc28a4321123131b6ff1e6c6b302fba764255623b (diff)
downloadqmk_firmware-b61baf4281bde34bfe28aaa1109bd5d5c6471116.tar.gz
qmk_firmware-b61baf4281bde34bfe28aaa1109bd5d5c6471116.zip
Fix #3566 use an hardware timer for software PWM stability (#3615)
With my XD60, I noticed that when typing the backlight was flickering. The XD60 doesn't have the backlight wired to a hardware PWM pin. I assumed it was a timing issue in the matrix scan that made the PWM lit the LED a bit too longer. I verified it because the more keys that were pressed, the more lighting I observed. This patch makes the software PWM be called during CPU interruptions. It works almost like the hardware PWM, except instead of using the CPU waveform generation, the CPU will fire interruption when the LEDs need be turned on or off. Using the same timer system as for hardware PWM, when the counter will reach OCRxx (the current backlight level), an Output Compare match interrupt will be fired and we'll turn the LEDs off. When the counter reaches its maximum value, an overflow interrupt will be triggered in which we turn the LEDs on. This way we replicate the hardware backlight PWM duty cycle. This gives a better time stability of the PWM computation than pure software PWM, leading to a flicker free backlight. Since this is reusing the hardware PWM code, software PWM also supports backlight breathing. Note that if timer1 is used for audio, backlight will use timer3, and if timer3 is used for audio backlight will use timer1. If both timers are used for audio, then this feature is disabled and we revert to the matrix scan based PWM computation. Signed-off-by: Brice Figureau <brice@daysofwonder.com>
-rw-r--r--docs/feature_backlight.md52
-rw-r--r--quantum/quantum.c256
-rw-r--r--quantum/quantum.h4
3 files changed, 267 insertions, 45 deletions
diff --git a/docs/feature_backlight.md b/docs/feature_backlight.md
index c7a1f131e..048d75390 100644
--- a/docs/feature_backlight.md
+++ b/docs/feature_backlight.md
@@ -30,7 +30,31 @@ You should then be able to use the keycodes below to change the backlight level.
30 30
31This feature is distinct from both the [RGB underglow](feature_rgblight.md) and [RGB matrix](feature_rgb_matrix.md) features as it usually allows for only a single colour per switch, though you can obviously use multiple different coloured LEDs on a keyboard. 31This feature is distinct from both the [RGB underglow](feature_rgblight.md) and [RGB matrix](feature_rgb_matrix.md) features as it usually allows for only a single colour per switch, though you can obviously use multiple different coloured LEDs on a keyboard.
32 32
33Hardware PWM is only supported on certain pins of the MCU, so if the backlighting is not connected to one of them, a software implementation will be used, and backlight breathing will not be available. Currently the supported pins are `B5`, `B6`, `B7`, and `C6`. 33Hardware PWM is only supported on certain pins of the MCU, so if the backlighting is not connected to one of them, a software PWM implementation triggered by hardware timer interrupts will be used.
34
35Hardware PWM is supported according to the following table:
36
37| Backlight Pin | Hardware timer |
38|---------------|----------------|
39|`B5` | Timer 1 |
40|`B6` | Timer 1 |
41|`B7` | Timer 1 |
42|`C6` | Timer 3 |
43| other | Software PWM |
44
45The [audio feature](feature_audio.md) also uses hardware timers. Please refer to the following table to know what hardware timer the software PWM will use depending on the audio configuration:
46
47| Audio Pin(s) | Audio Timer | Software PWM Timer |
48|--------------|-------------|--------------------|
49| `C4` | Timer 3 | Timer 1 |
50| `C5` | Timer 3 | Timer 1 |
51| `C6` | Timer 3 | Timer 1 |
52| `B5` | Timer 1 | Timer 3 |
53| `B6` | Timer 1 | Timer 3 |
54| `B7` | Timer 1 | Timer 3 |
55| `Bx` & `Cx` | Timer 1 & 3 | None |
56
57When all timers are in use for [audio](feature_audio.md), the backlight software PWM will not use a hardware timer, but instead will be triggered during the matrix scan. In this case the backlight doesn't support breathing and might show lighting artifacts (for instance flickering), because the PWM computation might not be called with enough timing precision.
34 58
35## Configuration 59## Configuration
36 60
@@ -39,11 +63,26 @@ To change the behaviour of the backlighting, `#define` these in your `config.h`:
39|Define |Default |Description | 63|Define |Default |Description |
40|---------------------|-------------|-------------------------------------------------------------------------------------------------------------| 64|---------------------|-------------|-------------------------------------------------------------------------------------------------------------|
41|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this| 65|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this|
66|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information|
42|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 15 excluding off) | 67|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 15 excluding off) |
43|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | 68|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) |
44|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if hardware PWM is used | 69|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if supported |
45|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | 70|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
46 71
72## Multiple backlight pins
73
74Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin).
75In software PWM, it is possible to define multiple backlight pins. All those pins will be turned on and off at the same time during the PWM duty cycle.
76This feature allows to set for instance the Caps Lock LED (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped LCTRL in place of Caps Lock and you need the Caps Lock LED to be part of the backlight instead of being activated when Caps Lock is on.
77
78To activate multiple backlight pins, you need to add something like this to your user `config.h`:
79
80~~~c
81#define BACKLIGHT_LED_COUNT 2
82#undef BACKLIGHT_PIN
83#define BACKLIGHT_PINS { F5, B2 }
84~~~
85
47## Hardware PWM Implementation 86## Hardware PWM Implementation
48 87
49When using the supported pins for backlighting, QMK will use a hardware timer configured to output a PWM signal. This timer will count up to `ICRx` (by default `0xFFFF`) before resetting to 0. 88When using the supported pins for backlighting, QMK will use a hardware timer configured to output a PWM signal. This timer will count up to `ICRx` (by default `0xFFFF`) before resetting to 0.
@@ -53,6 +92,15 @@ In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus th
53The breathing effect is achieved by registering an interrupt handler for `TIMER1_OVF_vect` that is called whenever the counter resets, roughly 244 times per second. 92The breathing effect is achieved by registering an interrupt handler for `TIMER1_OVF_vect` that is called whenever the counter resets, roughly 244 times per second.
54In this handler, the value of an incrementing counter is mapped onto a precomputed brightness curve. To turn off breathing, the interrupt handler is simply disabled, and the brightness reset to the level stored in EEPROM. 93In this handler, the value of an incrementing counter is mapped onto a precomputed brightness curve. To turn off breathing, the interrupt handler is simply disabled, and the brightness reset to the level stored in EEPROM.
55 94
95## Software PWM Implementation
96
97When `BACKLIGHT_PIN` is not set to a hardware backlight pin, QMK will use a hardware timer configured to trigger software interrupts. This time will count up to `ICRx` (by default `0xFFFF`) before resetting to 0.
98When resetting to 0, the CPU will fire an OVF (overflow) interrupt that will turn the LEDs on, starting the duty cycle.
99The desired brightness is calculated and stored in the `OCRxx` register. When the counter reaches this value, the CPU will fire a Compare Output match interrupt, which will turn the LEDs off.
100In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus the brightness, where `0x0000` is completely off and `0xFFFF` is completely on.
101
102The breathing effect is the same as in the hardware PWM implementation.
103
56## Backlight Functions 104## Backlight Functions
57 105
58|Function |Description | 106|Function |Description |
diff --git a/quantum/quantum.c b/quantum/quantum.c
index 9aa498dad..0fb798a74 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -1138,30 +1138,38 @@ void matrix_scan_quantum() {
1138 1138
1139 matrix_scan_kb(); 1139 matrix_scan_kb();
1140} 1140}
1141#if defined(BACKLIGHT_ENABLE) && defined(BACKLIGHT_PIN) 1141#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS))
1142 1142
1143static const uint8_t backlight_pin = BACKLIGHT_PIN; 1143// The logic is a bit complex, we support 3 setups:
1144// 1. hardware PWM when backlight is wired to a PWM pin
1145// depending on this pin, we use a different output compare unit
1146// 2. software PWM with hardware timers, but the used timer depends
1147// on the audio setup (audio wins other backlight)
1148// 3. full software PWM
1144 1149
1145// depending on the pin, we use a different output compare unit
1146#if BACKLIGHT_PIN == B7 1150#if BACKLIGHT_PIN == B7
1151# define HARDWARE_PWM
1147# define TCCRxA TCCR1A 1152# define TCCRxA TCCR1A
1148# define TCCRxB TCCR1B 1153# define TCCRxB TCCR1B
1149# define COMxx1 COM1C1 1154# define COMxx1 COM1C1
1150# define OCRxx OCR1C 1155# define OCRxx OCR1C
1151# define ICRx ICR1 1156# define ICRx ICR1
1152#elif BACKLIGHT_PIN == B6 1157#elif BACKLIGHT_PIN == B6
1158# define HARDWARE_PWM
1153# define TCCRxA TCCR1A 1159# define TCCRxA TCCR1A
1154# define TCCRxB TCCR1B 1160# define TCCRxB TCCR1B
1155# define COMxx1 COM1B1 1161# define COMxx1 COM1B1
1156# define OCRxx OCR1B 1162# define OCRxx OCR1B
1157# define ICRx ICR1 1163# define ICRx ICR1
1158#elif BACKLIGHT_PIN == B5 1164#elif BACKLIGHT_PIN == B5
1165# define HARDWARE_PWM
1159# define TCCRxA TCCR1A 1166# define TCCRxA TCCR1A
1160# define TCCRxB TCCR1B 1167# define TCCRxB TCCR1B
1161# define COMxx1 COM1A1 1168# define COMxx1 COM1A1
1162# define OCRxx OCR1A 1169# define OCRxx OCR1A
1163# define ICRx ICR1 1170# define ICRx ICR1
1164#elif BACKLIGHT_PIN == C6 1171#elif BACKLIGHT_PIN == C6
1172# define HARDWARE_PWM
1165# define TCCRxA TCCR3A 1173# define TCCRxA TCCR3A
1166# define TCCRxB TCCR3B 1174# define TCCRxB TCCR3B
1167# define COMxx1 COM1A1 1175# define COMxx1 COM1A1
@@ -1175,28 +1183,115 @@ static const uint8_t backlight_pin = BACKLIGHT_PIN;
1175# define ICRx ICR1 1183# define ICRx ICR1
1176# define TIMSK1 TIMSK 1184# define TIMSK1 TIMSK
1177#else 1185#else
1178# define NO_HARDWARE_PWM 1186# if !defined(BACKLIGHT_CUSTOM_DRIVER)
1187# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO)
1188 // timer 1 is not used by audio , backlight can use it
1189#pragma message "Using hardware timer 1 with software PWM"
1190# define HARDWARE_PWM
1191# define BACKLIGHT_PWM_TIMER
1192# define TCCRxA TCCR1A
1193# define TCCRxB TCCR1B
1194# define OCRxx OCR1A
1195# define OCRxAH OCR1AH
1196# define OCRxAL OCR1AL
1197# define TIMERx_COMPA_vect TIMER1_COMPA_vect
1198# define TIMERx_OVF_vect TIMER1_OVF_vect
1199# define OCIExA OCIE1A
1200# define TOIEx TOIE1
1201# define ICRx ICR1
1202# ifndef TIMSK
1203# define TIMSK TIMSK1
1204# endif
1205# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO)
1206#pragma message "Using hardware timer 3 with software PWM"
1207// timer 3 is not used by audio, backlight can use it
1208# define HARDWARE_PWM
1209# define BACKLIGHT_PWM_TIMER
1210# define TCCRxA TCCR3A
1211# define TCCRxB TCCR3B
1212# define OCRxx OCR3A
1213# define OCRxAH OCR3AH
1214# define OCRxAL OCR3AL
1215# define TIMERx_COMPA_vect TIMER3_COMPA_vect
1216# define TIMERx_OVF_vect TIMER3_OVF_vect
1217# define OCIExA OCIE3A
1218# define TOIEx TOIE3
1219# define ICRx ICR1
1220# ifndef TIMSK
1221# define TIMSK TIMSK3
1222# endif
1223# else
1224#pragma message "Audio in use - using pure software PWM"
1225#define NO_HARDWARE_PWM
1226# endif
1227# else
1228#pragma message "Custom driver defined - using pure software PWM"
1229#define NO_HARDWARE_PWM
1230# endif
1179#endif 1231#endif
1180 1232
1181#ifndef BACKLIGHT_ON_STATE 1233#ifndef BACKLIGHT_ON_STATE
1182#define BACKLIGHT_ON_STATE 0 1234#define BACKLIGHT_ON_STATE 0
1183#endif 1235#endif
1184 1236
1185#ifdef NO_HARDWARE_PWM // pwm through software 1237void backlight_on(uint8_t backlight_pin) {
1238#if BACKLIGHT_ON_STATE == 0
1239 writePinLow(backlight_pin);
1240#else
1241 writePinHigh(backlight_pin);
1242#endif
1243}
1186 1244
1187__attribute__ ((weak)) 1245void backlight_off(uint8_t backlight_pin) {
1246#if BACKLIGHT_ON_STATE == 0
1247 writePinHigh(backlight_pin);
1248#else
1249 writePinLow(backlight_pin);
1250#endif
1251}
1252
1253
1254#if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software
1255
1256// we support multiple backlight pins
1257#ifndef BACKLIGHT_LED_COUNT
1258#define BACKLIGHT_LED_COUNT 1
1259#endif
1260
1261#if BACKLIGHT_LED_COUNT == 1
1262#define BACKLIGHT_PIN_INIT { BACKLIGHT_PIN }
1263#else
1264#define BACKLIGHT_PIN_INIT BACKLIGHT_PINS
1265#endif
1266
1267#define FOR_EACH_LED(x) \
1268 for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) \
1269 { \
1270 uint8_t backlight_pin = backlight_pins[i]; \
1271 { \
1272 x \
1273 } \
1274 }
1275
1276static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT;
1277
1278#else // full hardware PWM
1279
1280// we support only one backlight pin
1281static const uint8_t backlight_pin = BACKLIGHT_PIN;
1282#define FOR_EACH_LED(x) x
1283
1284#endif
1285
1286#ifdef NO_HARDWARE_PWM
1287__attribute__((weak))
1188void backlight_init_ports(void) 1288void backlight_init_ports(void)
1189{ 1289{
1190 // Setup backlight pin as output and output to on state. 1290 // Setup backlight pin as output and output to on state.
1191 // DDRx |= n 1291 FOR_EACH_LED(
1192 _SFR_IO8((backlight_pin >> 4) + 1) |= _BV(backlight_pin & 0xF); 1292 setPinOutput(backlight_pin);
1193 #if BACKLIGHT_ON_STATE == 0 1293 backlight_on(backlight_pin);
1194 // PORTx &= ~n 1294 )
1195 _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
1196 #else
1197 // PORTx |= n
1198 _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
1199 #endif
1200} 1295}
1201 1296
1202__attribute__ ((weak)) 1297__attribute__ ((weak))
@@ -1207,21 +1302,14 @@ uint8_t backlight_tick = 0;
1207#ifndef BACKLIGHT_CUSTOM_DRIVER 1302#ifndef BACKLIGHT_CUSTOM_DRIVER
1208void backlight_task(void) { 1303void backlight_task(void) {
1209 if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) { 1304 if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) {
1210 #if BACKLIGHT_ON_STATE == 0 1305 FOR_EACH_LED(
1211 // PORTx &= ~n 1306 backlight_on(backlight_pin);
1212 _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF); 1307 )
1213 #else 1308 }
1214 // PORTx |= n 1309 else {
1215 _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF); 1310 FOR_EACH_LED(
1216 #endif 1311 backlight_off(backlight_pin);
1217 } else { 1312 )
1218 #if BACKLIGHT_ON_STATE == 0
1219 // PORTx |= n
1220 _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
1221 #else
1222 // PORTx &= ~n
1223 _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
1224 #endif
1225 } 1313 }
1226 backlight_tick = (backlight_tick + 1) % 16; 1314 backlight_tick = (backlight_tick + 1) % 16;
1227} 1315}
@@ -1233,7 +1321,52 @@ void backlight_task(void) {
1233 #endif 1321 #endif
1234#endif 1322#endif
1235 1323
1236#else // pwm through timer 1324#else // hardware pwm through timer
1325
1326#ifdef BACKLIGHT_PWM_TIMER
1327
1328// The idea of software PWM assisted by hardware timers is the following
1329// we use the hardware timer in fast PWM mode like for hardware PWM, but
1330// instead of letting the Output Match Comparator control the led pin
1331// (which is not possible since the backlight is not wired to PWM pins on the
1332// CPU), we do the LED on/off by oursleves.
1333// The timer is setup to count up to 0xFFFF, and we set the Output Compare
1334// register to the current 16bits backlight level (after CIE correction).
1335// This means the CPU will trigger a compare match interrupt when the counter
1336// reaches the backlight level, where we turn off the LEDs,
1337// but also an overflow interrupt when the counter rolls back to 0,
1338// in which we're going to turn on the LEDs.
1339// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz.
1340
1341// Triggered when the counter reaches the OCRx value
1342ISR(TIMERx_COMPA_vect) {
1343 FOR_EACH_LED(
1344 backlight_off(backlight_pin);
1345 )
1346}
1347
1348// Triggered when the counter reaches the TOP value
1349// this one triggers at F_CPU/65536 =~ 244 Hz
1350ISR(TIMERx_OVF_vect) {
1351#ifdef BACKLIGHT_BREATHING
1352 breathing_task();
1353#endif
1354 // for very small values of OCRxx (or backlight level)
1355 // we can't guarantee this whole code won't execute
1356 // at the same time as the compare match interrupt
1357 // which means that we might turn on the leds while
1358 // trying to turn them off, leading to flickering
1359 // artifacts (especially while breathing, because breathing_task
1360 // takes many computation cycles).
1361 // so better not turn them on while the counter TOP is very low.
1362 if (OCRxx > 256) {
1363 FOR_EACH_LED(
1364 backlight_on(backlight_pin);
1365 )
1366 }
1367}
1368
1369#endif
1237 1370
1238#define TIMER_TOP 0xFFFFU 1371#define TIMER_TOP 0xFFFFU
1239 1372
@@ -1265,11 +1398,28 @@ void backlight_set(uint8_t level) {
1265 level = BACKLIGHT_LEVELS; 1398 level = BACKLIGHT_LEVELS;
1266 1399
1267 if (level == 0) { 1400 if (level == 0) {
1401 #ifdef BACKLIGHT_PWM_TIMER
1402 if (OCRxx) {
1403 TIMSK &= ~(_BV(OCIExA));
1404 TIMSK &= ~(_BV(TOIEx));
1405 FOR_EACH_LED(
1406 backlight_off(backlight_pin);
1407 )
1408 }
1409 #else
1268 // Turn off PWM control on backlight pin 1410 // Turn off PWM control on backlight pin
1269 TCCRxA &= ~(_BV(COMxx1)); 1411 TCCRxA &= ~(_BV(COMxx1));
1412 #endif
1270 } else { 1413 } else {
1414 #ifdef BACKLIGHT_PWM_TIMER
1415 if (!OCRxx) {
1416 TIMSK |= _BV(OCIExA);
1417 TIMSK |= _BV(TOIEx);
1418 }
1419 #else
1271 // Turn on PWM control of backlight pin 1420 // Turn on PWM control of backlight pin
1272 TCCRxA |= _BV(COMxx1); 1421 TCCRxA |= _BV(COMxx1);
1422 #endif
1273 } 1423 }
1274 // Set the brightness 1424 // Set the brightness
1275 set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS)); 1425 set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS));
@@ -1289,12 +1439,25 @@ static uint8_t breathing_period = BREATHING_PERIOD;
1289static uint8_t breathing_halt = BREATHING_NO_HALT; 1439static uint8_t breathing_halt = BREATHING_NO_HALT;
1290static uint16_t breathing_counter = 0; 1440static uint16_t breathing_counter = 0;
1291 1441
1442#ifdef BACKLIGHT_PWM_TIMER
1443static bool breathing = false;
1444
1445bool is_breathing(void) {
1446 return breathing;
1447}
1448
1449#define breathing_interrupt_enable() do { breathing = true; } while (0)
1450#define breathing_interrupt_disable() do { breathing = false; } while (0)
1451#else
1452
1292bool is_breathing(void) { 1453bool is_breathing(void) {
1293 return !!(TIMSK1 & _BV(TOIE1)); 1454 return !!(TIMSK1 & _BV(TOIE1));
1294} 1455}
1295 1456
1296#define breathing_interrupt_enable() do {TIMSK1 |= _BV(TOIE1);} while (0) 1457#define breathing_interrupt_enable() do {TIMSK1 |= _BV(TOIE1);} while (0)
1297#define breathing_interrupt_disable() do {TIMSK1 &= ~_BV(TOIE1);} while (0) 1458#define breathing_interrupt_disable() do {TIMSK1 &= ~_BV(TOIE1);} while (0)
1459#endif
1460
1298#define breathing_min() do {breathing_counter = 0;} while (0) 1461#define breathing_min() do {breathing_counter = 0;} while (0)
1299#define breathing_max() do {breathing_counter = breathing_period * 244 / 2;} while (0) 1462#define breathing_max() do {breathing_counter = breathing_period * 244 / 2;} while (0)
1300 1463
@@ -1368,10 +1531,14 @@ static inline uint16_t scale_backlight(uint16_t v) {
1368 return v / BACKLIGHT_LEVELS * get_backlight_level(); 1531 return v / BACKLIGHT_LEVELS * get_backlight_level();
1369} 1532}
1370 1533
1534#ifdef BACKLIGHT_PWM_TIMER
1535void breathing_task(void)
1536#else
1371/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run 1537/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run
1372 * about 244 times per second. 1538 * about 244 times per second.
1373 */ 1539 */
1374ISR(TIMER1_OVF_vect) 1540ISR(TIMER1_OVF_vect)
1541#endif
1375{ 1542{
1376 uint16_t interval = (uint16_t) breathing_period * 244 / BREATHING_STEPS; 1543 uint16_t interval = (uint16_t) breathing_period * 244 / BREATHING_STEPS;
1377 // resetting after one period to prevent ugly reset at overflow. 1544 // resetting after one period to prevent ugly reset at overflow.
@@ -1393,19 +1560,21 @@ __attribute__ ((weak))
1393void backlight_init_ports(void) 1560void backlight_init_ports(void)
1394{ 1561{
1395 // Setup backlight pin as output and output to on state. 1562 // Setup backlight pin as output and output to on state.
1396 // DDRx |= n 1563 FOR_EACH_LED(
1397 _SFR_IO8((backlight_pin >> 4) + 1) |= _BV(backlight_pin & 0xF); 1564 setPinOutput(backlight_pin);
1398 #if BACKLIGHT_ON_STATE == 0 1565 backlight_on(backlight_pin);
1399 // PORTx &= ~n 1566 )
1400 _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF); 1567
1401 #else
1402 // PORTx |= n
1403 _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
1404 #endif
1405 // I could write a wall of text here to explain... but TL;DW 1568 // I could write a wall of text here to explain... but TL;DW
1406 // Go read the ATmega32u4 datasheet. 1569 // Go read the ATmega32u4 datasheet.
1407 // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on 1570 // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
1408 1571
1572#ifdef BACKLIGHT_PWM_TIMER
1573 // TimerX setup, Fast PWM mode count to TOP set in ICRx
1574 TCCRxA = _BV(WGM11); // = 0b00000010;
1575 // clock select clk/1
1576 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
1577#else // hardware PWM
1409 // Pin PB7 = OCR1C (Timer 1, Channel C) 1578 // Pin PB7 = OCR1C (Timer 1, Channel C)
1410 // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0 1579 // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
1411 // (i.e. start high, go low when counter matches.) 1580 // (i.e. start high, go low when counter matches.)
@@ -1417,8 +1586,9 @@ void backlight_init_ports(void)
1417 "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 [..]." 1586 "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 [..]."
1418 "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)." 1587 "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)."
1419 */ 1588 */
1420 TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010; 1589 TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010;
1421 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; 1590 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
1591#endif
1422 // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0. 1592 // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0.
1423 ICRx = TIMER_TOP; 1593 ICRx = TIMER_TOP;
1424 1594
@@ -1428,9 +1598,9 @@ void backlight_init_ports(void)
1428 #endif 1598 #endif
1429} 1599}
1430 1600
1431#endif // NO_HARDWARE_PWM 1601#endif // hardware backlight
1432 1602
1433#else // backlight 1603#else // no backlight
1434 1604
1435__attribute__ ((weak)) 1605__attribute__ ((weak))
1436void backlight_init_ports(void) {} 1606void backlight_init_ports(void) {}
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 987516ded..17cb90274 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -260,8 +260,12 @@ void tap_code16(uint16_t code);
260#ifdef BACKLIGHT_ENABLE 260#ifdef BACKLIGHT_ENABLE
261void backlight_init_ports(void); 261void backlight_init_ports(void);
262void backlight_task(void); 262void backlight_task(void);
263void backlight_task_internal(void);
264void backlight_on(uint8_t backlight_pin);
265void backlight_off(uint8_t backlight_pin);
263 266
264#ifdef BACKLIGHT_BREATHING 267#ifdef BACKLIGHT_BREATHING
268void breathing_task(void);
265void breathing_enable(void); 269void breathing_enable(void);
266void breathing_pulse(void); 270void breathing_pulse(void);
267void breathing_disable(void); 271void breathing_disable(void);