|
@@ -1138,30 +1138,38 @@ void matrix_scan_quantum() {
|
|
|
|
|
|
matrix_scan_kb();
|
|
|
}
|
|
|
-#if defined(BACKLIGHT_ENABLE) && defined(BACKLIGHT_PIN)
|
|
|
+#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS))
|
|
|
|
|
|
-static const uint8_t backlight_pin = BACKLIGHT_PIN;
|
|
|
+// The logic is a bit complex, we support 3 setups:
|
|
|
+// 1. hardware PWM when backlight is wired to a PWM pin
|
|
|
+// depending on this pin, we use a different output compare unit
|
|
|
+// 2. software PWM with hardware timers, but the used timer depends
|
|
|
+// on the audio setup (audio wins other backlight)
|
|
|
+// 3. full software PWM
|
|
|
|
|
|
-// depending on the pin, we use a different output compare unit
|
|
|
#if BACKLIGHT_PIN == B7
|
|
|
+# define HARDWARE_PWM
|
|
|
# define TCCRxA TCCR1A
|
|
|
# define TCCRxB TCCR1B
|
|
|
# define COMxx1 COM1C1
|
|
|
# define OCRxx OCR1C
|
|
|
# define ICRx ICR1
|
|
|
#elif BACKLIGHT_PIN == B6
|
|
|
+# define HARDWARE_PWM
|
|
|
# define TCCRxA TCCR1A
|
|
|
# define TCCRxB TCCR1B
|
|
|
# define COMxx1 COM1B1
|
|
|
# define OCRxx OCR1B
|
|
|
# define ICRx ICR1
|
|
|
#elif BACKLIGHT_PIN == B5
|
|
|
+# define HARDWARE_PWM
|
|
|
# define TCCRxA TCCR1A
|
|
|
# define TCCRxB TCCR1B
|
|
|
# define COMxx1 COM1A1
|
|
|
# define OCRxx OCR1A
|
|
|
# define ICRx ICR1
|
|
|
#elif BACKLIGHT_PIN == C6
|
|
|
+# define HARDWARE_PWM
|
|
|
# define TCCRxA TCCR3A
|
|
|
# define TCCRxB TCCR3B
|
|
|
# define COMxx1 COM1A1
|
|
@@ -1175,28 +1183,115 @@ static const uint8_t backlight_pin = BACKLIGHT_PIN;
|
|
|
# define ICRx ICR1
|
|
|
# define TIMSK1 TIMSK
|
|
|
#else
|
|
|
-# define NO_HARDWARE_PWM
|
|
|
+# if !defined(BACKLIGHT_CUSTOM_DRIVER)
|
|
|
+# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO)
|
|
|
+ // timer 1 is not used by audio , backlight can use it
|
|
|
+#pragma message "Using hardware timer 1 with software PWM"
|
|
|
+# define HARDWARE_PWM
|
|
|
+# define BACKLIGHT_PWM_TIMER
|
|
|
+# define TCCRxA TCCR1A
|
|
|
+# define TCCRxB TCCR1B
|
|
|
+# define OCRxx OCR1A
|
|
|
+# define OCRxAH OCR1AH
|
|
|
+# define OCRxAL OCR1AL
|
|
|
+# define TIMERx_COMPA_vect TIMER1_COMPA_vect
|
|
|
+# define TIMERx_OVF_vect TIMER1_OVF_vect
|
|
|
+# define OCIExA OCIE1A
|
|
|
+# define TOIEx TOIE1
|
|
|
+# define ICRx ICR1
|
|
|
+# ifndef TIMSK
|
|
|
+# define TIMSK TIMSK1
|
|
|
+# endif
|
|
|
+# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO)
|
|
|
+#pragma message "Using hardware timer 3 with software PWM"
|
|
|
+// timer 3 is not used by audio, backlight can use it
|
|
|
+# define HARDWARE_PWM
|
|
|
+# define BACKLIGHT_PWM_TIMER
|
|
|
+# define TCCRxA TCCR3A
|
|
|
+# define TCCRxB TCCR3B
|
|
|
+# define OCRxx OCR3A
|
|
|
+# define OCRxAH OCR3AH
|
|
|
+# define OCRxAL OCR3AL
|
|
|
+# define TIMERx_COMPA_vect TIMER3_COMPA_vect
|
|
|
+# define TIMERx_OVF_vect TIMER3_OVF_vect
|
|
|
+# define OCIExA OCIE3A
|
|
|
+# define TOIEx TOIE3
|
|
|
+# define ICRx ICR1
|
|
|
+# ifndef TIMSK
|
|
|
+# define TIMSK TIMSK3
|
|
|
+# endif
|
|
|
+# else
|
|
|
+#pragma message "Audio in use - using pure software PWM"
|
|
|
+#define NO_HARDWARE_PWM
|
|
|
+# endif
|
|
|
+# else
|
|
|
+#pragma message "Custom driver defined - using pure software PWM"
|
|
|
+#define NO_HARDWARE_PWM
|
|
|
+# endif
|
|
|
#endif
|
|
|
|
|
|
#ifndef BACKLIGHT_ON_STATE
|
|
|
#define BACKLIGHT_ON_STATE 0
|
|
|
#endif
|
|
|
|
|
|
-#ifdef NO_HARDWARE_PWM // pwm through software
|
|
|
+void backlight_on(uint8_t backlight_pin) {
|
|
|
+#if BACKLIGHT_ON_STATE == 0
|
|
|
+ writePinLow(backlight_pin);
|
|
|
+#else
|
|
|
+ writePinHigh(backlight_pin);
|
|
|
+#endif
|
|
|
+}
|
|
|
|
|
|
-__attribute__ ((weak))
|
|
|
+void backlight_off(uint8_t backlight_pin) {
|
|
|
+#if BACKLIGHT_ON_STATE == 0
|
|
|
+ writePinHigh(backlight_pin);
|
|
|
+#else
|
|
|
+ writePinLow(backlight_pin);
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software
|
|
|
+
|
|
|
+// we support multiple backlight pins
|
|
|
+#ifndef BACKLIGHT_LED_COUNT
|
|
|
+#define BACKLIGHT_LED_COUNT 1
|
|
|
+#endif
|
|
|
+
|
|
|
+#if BACKLIGHT_LED_COUNT == 1
|
|
|
+#define BACKLIGHT_PIN_INIT { BACKLIGHT_PIN }
|
|
|
+#else
|
|
|
+#define BACKLIGHT_PIN_INIT BACKLIGHT_PINS
|
|
|
+#endif
|
|
|
+
|
|
|
+#define FOR_EACH_LED(x) \
|
|
|
+ for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) \
|
|
|
+ { \
|
|
|
+ uint8_t backlight_pin = backlight_pins[i]; \
|
|
|
+ { \
|
|
|
+ x \
|
|
|
+ } \
|
|
|
+ }
|
|
|
+
|
|
|
+static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT;
|
|
|
+
|
|
|
+#else // full hardware PWM
|
|
|
+
|
|
|
+// we support only one backlight pin
|
|
|
+static const uint8_t backlight_pin = BACKLIGHT_PIN;
|
|
|
+#define FOR_EACH_LED(x) x
|
|
|
+
|
|
|
+#endif
|
|
|
+
|
|
|
+#ifdef NO_HARDWARE_PWM
|
|
|
+__attribute__((weak))
|
|
|
void backlight_init_ports(void)
|
|
|
{
|
|
|
// Setup backlight pin as output and output to on state.
|
|
|
- // DDRx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 1) |= _BV(backlight_pin & 0xF);
|
|
|
- #if BACKLIGHT_ON_STATE == 0
|
|
|
- // PORTx &= ~n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
|
|
|
- #else
|
|
|
- // PORTx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
|
|
|
- #endif
|
|
|
+ FOR_EACH_LED(
|
|
|
+ setPinOutput(backlight_pin);
|
|
|
+ backlight_on(backlight_pin);
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
__attribute__ ((weak))
|
|
@@ -1207,21 +1302,14 @@ uint8_t backlight_tick = 0;
|
|
|
#ifndef BACKLIGHT_CUSTOM_DRIVER
|
|
|
void backlight_task(void) {
|
|
|
if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) {
|
|
|
- #if BACKLIGHT_ON_STATE == 0
|
|
|
- // PORTx &= ~n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
|
|
|
- #else
|
|
|
- // PORTx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
|
|
|
- #endif
|
|
|
- } else {
|
|
|
- #if BACKLIGHT_ON_STATE == 0
|
|
|
- // PORTx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
|
|
|
- #else
|
|
|
- // PORTx &= ~n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
|
|
|
- #endif
|
|
|
+ FOR_EACH_LED(
|
|
|
+ backlight_on(backlight_pin);
|
|
|
+ )
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ FOR_EACH_LED(
|
|
|
+ backlight_off(backlight_pin);
|
|
|
+ )
|
|
|
}
|
|
|
backlight_tick = (backlight_tick + 1) % 16;
|
|
|
}
|
|
@@ -1233,7 +1321,52 @@ void backlight_task(void) {
|
|
|
#endif
|
|
|
#endif
|
|
|
|
|
|
-#else // pwm through timer
|
|
|
+#else // hardware pwm through timer
|
|
|
+
|
|
|
+#ifdef BACKLIGHT_PWM_TIMER
|
|
|
+
|
|
|
+// The idea of software PWM assisted by hardware timers is the following
|
|
|
+// we use the hardware timer in fast PWM mode like for hardware PWM, but
|
|
|
+// instead of letting the Output Match Comparator control the led pin
|
|
|
+// (which is not possible since the backlight is not wired to PWM pins on the
|
|
|
+// CPU), we do the LED on/off by oursleves.
|
|
|
+// The timer is setup to count up to 0xFFFF, and we set the Output Compare
|
|
|
+// register to the current 16bits backlight level (after CIE correction).
|
|
|
+// This means the CPU will trigger a compare match interrupt when the counter
|
|
|
+// reaches the backlight level, where we turn off the LEDs,
|
|
|
+// but also an overflow interrupt when the counter rolls back to 0,
|
|
|
+// in which we're going to turn on the LEDs.
|
|
|
+// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz.
|
|
|
+
|
|
|
+// Triggered when the counter reaches the OCRx value
|
|
|
+ISR(TIMERx_COMPA_vect) {
|
|
|
+ FOR_EACH_LED(
|
|
|
+ backlight_off(backlight_pin);
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// Triggered when the counter reaches the TOP value
|
|
|
+// this one triggers at F_CPU/65536 =~ 244 Hz
|
|
|
+ISR(TIMERx_OVF_vect) {
|
|
|
+#ifdef BACKLIGHT_BREATHING
|
|
|
+ breathing_task();
|
|
|
+#endif
|
|
|
+ // for very small values of OCRxx (or backlight level)
|
|
|
+ // we can't guarantee this whole code won't execute
|
|
|
+ // at the same time as the compare match interrupt
|
|
|
+ // which means that we might turn on the leds while
|
|
|
+ // trying to turn them off, leading to flickering
|
|
|
+ // artifacts (especially while breathing, because breathing_task
|
|
|
+ // takes many computation cycles).
|
|
|
+ // so better not turn them on while the counter TOP is very low.
|
|
|
+ if (OCRxx > 256) {
|
|
|
+ FOR_EACH_LED(
|
|
|
+ backlight_on(backlight_pin);
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|
|
|
|
|
|
#define TIMER_TOP 0xFFFFU
|
|
|
|
|
@@ -1265,11 +1398,28 @@ void backlight_set(uint8_t level) {
|
|
|
level = BACKLIGHT_LEVELS;
|
|
|
|
|
|
if (level == 0) {
|
|
|
+ #ifdef BACKLIGHT_PWM_TIMER
|
|
|
+ if (OCRxx) {
|
|
|
+ TIMSK &= ~(_BV(OCIExA));
|
|
|
+ TIMSK &= ~(_BV(TOIEx));
|
|
|
+ FOR_EACH_LED(
|
|
|
+ backlight_off(backlight_pin);
|
|
|
+ )
|
|
|
+ }
|
|
|
+ #else
|
|
|
// Turn off PWM control on backlight pin
|
|
|
TCCRxA &= ~(_BV(COMxx1));
|
|
|
+ #endif
|
|
|
} else {
|
|
|
+ #ifdef BACKLIGHT_PWM_TIMER
|
|
|
+ if (!OCRxx) {
|
|
|
+ TIMSK |= _BV(OCIExA);
|
|
|
+ TIMSK |= _BV(TOIEx);
|
|
|
+ }
|
|
|
+ #else
|
|
|
// Turn on PWM control of backlight pin
|
|
|
TCCRxA |= _BV(COMxx1);
|
|
|
+ #endif
|
|
|
}
|
|
|
// Set the brightness
|
|
|
set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS));
|
|
@@ -1289,12 +1439,25 @@ static uint8_t breathing_period = BREATHING_PERIOD;
|
|
|
static uint8_t breathing_halt = BREATHING_NO_HALT;
|
|
|
static uint16_t breathing_counter = 0;
|
|
|
|
|
|
+#ifdef BACKLIGHT_PWM_TIMER
|
|
|
+static bool breathing = false;
|
|
|
+
|
|
|
+bool is_breathing(void) {
|
|
|
+ return breathing;
|
|
|
+}
|
|
|
+
|
|
|
+#define breathing_interrupt_enable() do { breathing = true; } while (0)
|
|
|
+#define breathing_interrupt_disable() do { breathing = false; } while (0)
|
|
|
+#else
|
|
|
+
|
|
|
bool is_breathing(void) {
|
|
|
return !!(TIMSK1 & _BV(TOIE1));
|
|
|
}
|
|
|
|
|
|
#define breathing_interrupt_enable() do {TIMSK1 |= _BV(TOIE1);} while (0)
|
|
|
#define breathing_interrupt_disable() do {TIMSK1 &= ~_BV(TOIE1);} while (0)
|
|
|
+#endif
|
|
|
+
|
|
|
#define breathing_min() do {breathing_counter = 0;} while (0)
|
|
|
#define breathing_max() do {breathing_counter = breathing_period * 244 / 2;} while (0)
|
|
|
|
|
@@ -1368,10 +1531,14 @@ static inline uint16_t scale_backlight(uint16_t v) {
|
|
|
return v / BACKLIGHT_LEVELS * get_backlight_level();
|
|
|
}
|
|
|
|
|
|
+#ifdef BACKLIGHT_PWM_TIMER
|
|
|
+void breathing_task(void)
|
|
|
+#else
|
|
|
/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run
|
|
|
* about 244 times per second.
|
|
|
*/
|
|
|
ISR(TIMER1_OVF_vect)
|
|
|
+#endif
|
|
|
{
|
|
|
uint16_t interval = (uint16_t) breathing_period * 244 / BREATHING_STEPS;
|
|
|
// resetting after one period to prevent ugly reset at overflow.
|
|
@@ -1393,19 +1560,21 @@ __attribute__ ((weak))
|
|
|
void backlight_init_ports(void)
|
|
|
{
|
|
|
// Setup backlight pin as output and output to on state.
|
|
|
- // DDRx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 1) |= _BV(backlight_pin & 0xF);
|
|
|
- #if BACKLIGHT_ON_STATE == 0
|
|
|
- // PORTx &= ~n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) &= ~_BV(backlight_pin & 0xF);
|
|
|
- #else
|
|
|
- // PORTx |= n
|
|
|
- _SFR_IO8((backlight_pin >> 4) + 2) |= _BV(backlight_pin & 0xF);
|
|
|
- #endif
|
|
|
+ FOR_EACH_LED(
|
|
|
+ setPinOutput(backlight_pin);
|
|
|
+ backlight_on(backlight_pin);
|
|
|
+ )
|
|
|
+
|
|
|
// I could write a wall of text here to explain... but TL;DW
|
|
|
// Go read the ATmega32u4 datasheet.
|
|
|
// And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
|
|
|
|
|
|
+#ifdef BACKLIGHT_PWM_TIMER
|
|
|
+ // TimerX setup, Fast PWM mode count to TOP set in ICRx
|
|
|
+ TCCRxA = _BV(WGM11); // = 0b00000010;
|
|
|
+ // clock select clk/1
|
|
|
+ TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
|
|
|
+#else // hardware PWM
|
|
|
// Pin PB7 = OCR1C (Timer 1, Channel C)
|
|
|
// Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
|
|
|
// (i.e. start high, go low when counter matches.)
|
|
@@ -1417,8 +1586,9 @@ void backlight_init_ports(void)
|
|
|
"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 [..]."
|
|
|
"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)."
|
|
|
*/
|
|
|
- TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010;
|
|
|
+ TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010;
|
|
|
TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
|
|
|
+#endif
|
|
|
// Use full 16-bit resolution. Counter counts to ICR1 before reset to 0.
|
|
|
ICRx = TIMER_TOP;
|
|
|
|
|
@@ -1428,9 +1598,9 @@ void backlight_init_ports(void)
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
-#endif // NO_HARDWARE_PWM
|
|
|
+#endif // hardware backlight
|
|
|
|
|
|
-#else // backlight
|
|
|
+#else // no backlight
|
|
|
|
|
|
__attribute__ ((weak))
|
|
|
void backlight_init_ports(void) {}
|