aboutsummaryrefslogtreecommitdiff
path: root/quantum/quantum.c
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 /quantum/quantum.c
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>
Diffstat (limited to 'quantum/quantum.c')
-rw-r--r--quantum/quantum.c256
1 files changed, 213 insertions, 43 deletions
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) {}