mirror of
https://xff.cz/git/pinephone-keyboard/
synced 2024-11-09 22:15:42 +01:00
Implement polled mode and key bitmap change signalling via external interrupt
- this firmware consumes about 4-5 mA (can be probably made lower)
This commit is contained in:
parent
a9bb6a3691
commit
bca8c47f46
1 changed files with 122 additions and 22 deletions
142
firmware/main.c
142
firmware/main.c
|
@ -18,23 +18,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
#include <em85f684a.h>
|
#include <em85f684a.h>
|
||||||
|
|
||||||
|
// configuration (we can make this runtime configurable via i2c)
|
||||||
|
// polled input mode is necessary if some rows are always on
|
||||||
|
#define POLL_INPUT 1
|
||||||
|
|
||||||
#define BIT(n) (1u << (n))
|
#define BIT(n) (1u << (n))
|
||||||
|
|
||||||
// we just use this interrupt for wakeup from sleep on input change
|
// timers clock is 2 MHz so we need to wait for 2000 ticks to get delay of 1ms
|
||||||
void pinchange_interupt(void) __interrupt(IRQ_PINCHANGE)
|
|
||||||
{
|
|
||||||
// disable all input change interrupts
|
|
||||||
P0_ICEN = BIT(5);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define T0_SET_TIMEOUT(n) { \
|
#define T0_SET_TIMEOUT(n) { \
|
||||||
TL0 = 0x00; \
|
TL0 = 0x00; \
|
||||||
TH0 = (0x10000u - n) >> 8; \
|
TH0 = (0x10000u - n) >> 8; \
|
||||||
TL0 = (0x10000u - n) & 0xff; \
|
TL0 = (0x10000u - n) & 0xff; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define T1_SET_TIMEOUT(n) { \
|
||||||
|
TL1 = 0x00; \
|
||||||
|
TH1 = (0x10000u - n) >> 8; \
|
||||||
|
TL1 = (0x10000u - n) & 0xff; \
|
||||||
|
}
|
||||||
|
|
||||||
#define delay_us(n) { \
|
#define delay_us(n) { \
|
||||||
TL0 = 0x00; \
|
TL0 = 0x00; \
|
||||||
TF0 = 0; \
|
TF0 = 0; \
|
||||||
|
@ -43,6 +48,38 @@ void pinchange_interupt(void) __interrupt(IRQ_PINCHANGE)
|
||||||
while (!TF0); \
|
while (!TF0); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static __sbit p6_changed = 0;
|
||||||
|
static __sbit run_tasks = 0;
|
||||||
|
|
||||||
|
// we use this interrupt for wakeup from sleep on input change
|
||||||
|
|
||||||
|
void pinchange_interupt(void) __interrupt(IRQ_PINCHANGE)
|
||||||
|
{
|
||||||
|
uint8_t saved_page = PAGESW;
|
||||||
|
|
||||||
|
PAGESW = 0;
|
||||||
|
|
||||||
|
if (P0_ICEN & BIT(1))
|
||||||
|
p6_changed = 1;
|
||||||
|
|
||||||
|
// clear input change flags
|
||||||
|
P0_ICEN = BIT(5);
|
||||||
|
|
||||||
|
PAGESW = saved_page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use this interrupt as a scheduling tick (wakeup from sleep)
|
||||||
|
|
||||||
|
void timer1_interupt(void) __interrupt(IRQ_TIMER1)
|
||||||
|
{
|
||||||
|
run_tasks = 1;
|
||||||
|
|
||||||
|
// 20 ms
|
||||||
|
T1_SET_TIMEOUT(40000);
|
||||||
|
|
||||||
|
TF1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Keyboard has 12 columns and 6 rows directly connected to GPIOs.
|
// Keyboard has 12 columns and 6 rows directly connected to GPIOs.
|
||||||
//
|
//
|
||||||
// C1 P95
|
// C1 P95
|
||||||
|
@ -111,14 +148,29 @@ void keyscan_idle(void)
|
||||||
P8 &= 0xfe;
|
P8 &= 0xfe;
|
||||||
P9 &= 0x1f;
|
P9 &= 0x1f;
|
||||||
|
|
||||||
|
#if POLL_INPUT
|
||||||
|
// make all columns an input, hi-Z (saves power)
|
||||||
|
P0_P5M0 = ~0x00u;
|
||||||
|
P0_P8M0 |= ~0xfeu;
|
||||||
|
PAGESW = 1;
|
||||||
|
P1_P9M0 |= ~0x1fu;
|
||||||
|
ICIE = 0;
|
||||||
|
p6_changed = 0;
|
||||||
|
#else
|
||||||
P0_P5M0 = 0x00;
|
P0_P5M0 = 0x00;
|
||||||
P0_P8M0 &= 0xfe;
|
P0_P8M0 &= 0xfe;
|
||||||
PAGESW = 1;
|
PAGESW = 1;
|
||||||
P1_P9M0 &= 0x1f;
|
P1_P9M0 &= 0x1f;
|
||||||
|
|
||||||
// enable input change interrupt on port6 and clear the flag
|
// enable input change interrupt on port6 and clear the interrupt flag after
|
||||||
|
// things stabilize
|
||||||
|
delay_us(10);
|
||||||
|
|
||||||
|
PAGESW = 0;
|
||||||
|
p6_changed = 0;
|
||||||
P0_ICEN = BIT(5);
|
P0_ICEN = BIT(5);
|
||||||
ICIE = 1;
|
ICIE = 1;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t keyscan_idle_is_pressed(void)
|
uint8_t keyscan_idle_is_pressed(void)
|
||||||
|
@ -144,13 +196,16 @@ void keyscan_active(void)
|
||||||
P8 &= 0xfe;
|
P8 &= 0xfe;
|
||||||
P9 &= 0x1f;
|
P9 &= 0x1f;
|
||||||
|
|
||||||
|
// make all columns an input (hi-Z) in preparation for individual
|
||||||
|
// column scanning
|
||||||
P0_P5M0 = ~0x00u;
|
P0_P5M0 = ~0x00u;
|
||||||
P0_P8M0 |= ~0xfeu;
|
P0_P8M0 |= ~0xfeu;
|
||||||
PAGESW = 1;
|
PAGESW = 1;
|
||||||
P1_P9M0 |= ~0x1fu;
|
P1_P9M0 |= ~0x1fu;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: we can debounce in the scan function too (3us?)
|
// XXX: do we need to debounce in the scan function?
|
||||||
|
// XXX: it looks like that there should be no bouncing going on mechanically
|
||||||
|
|
||||||
// 12 byte storage required
|
// 12 byte storage required
|
||||||
uint8_t keyscan_scan(uint8_t* res)
|
uint8_t keyscan_scan(uint8_t* res)
|
||||||
|
@ -166,7 +221,7 @@ uint8_t keyscan_scan(uint8_t* res)
|
||||||
PAGESW = 1;
|
PAGESW = 1;
|
||||||
for (pin = 5; pin <= 7; pin++) {
|
for (pin = 5; pin <= 7; pin++) {
|
||||||
P1_P9M0 &= ~BIT(pin);
|
P1_P9M0 &= ~BIT(pin);
|
||||||
delay_us(10);
|
delay_us(3);
|
||||||
row = ~P6 & 0x3f;
|
row = ~P6 & 0x3f;
|
||||||
mask |= row;
|
mask |= row;
|
||||||
*res++ = row;
|
*res++ = row;
|
||||||
|
@ -176,7 +231,7 @@ uint8_t keyscan_scan(uint8_t* res)
|
||||||
PAGESW = 0;
|
PAGESW = 0;
|
||||||
for (pin = 0; pin <= 7; pin++) {
|
for (pin = 0; pin <= 7; pin++) {
|
||||||
P0_P5M0 &= ~BIT(pin);
|
P0_P5M0 &= ~BIT(pin);
|
||||||
delay_us(10);
|
delay_us(3);
|
||||||
row = ~P6 & 0x3f;
|
row = ~P6 & 0x3f;
|
||||||
mask |= row;
|
mask |= row;
|
||||||
*res++ = row;
|
*res++ = row;
|
||||||
|
@ -184,7 +239,7 @@ uint8_t keyscan_scan(uint8_t* res)
|
||||||
}
|
}
|
||||||
|
|
||||||
P0_P8M0 &= ~BIT(0);
|
P0_P8M0 &= ~BIT(0);
|
||||||
delay_us(10);
|
delay_us(3);
|
||||||
row = ~P6 & 0x3f;
|
row = ~P6 & 0x3f;
|
||||||
mask |= row;
|
mask |= row;
|
||||||
*res++ = row;
|
*res++ = row;
|
||||||
|
@ -308,18 +363,15 @@ void main(void)
|
||||||
// set both timers to 16-bit counter modes
|
// set both timers to 16-bit counter modes
|
||||||
TMOD = 0x11;
|
TMOD = 0x11;
|
||||||
|
|
||||||
// timers clock is 2 MHz so we need to wait for 2000 ticks to get delay of 1ms
|
|
||||||
//T0_SET_TIMEOUT(2000);
|
|
||||||
|
|
||||||
// enable both timers
|
// enable both timers
|
||||||
TCON = 0x50;
|
TCON = 0x50;
|
||||||
|
|
||||||
// setup watchdog (timer base is 8ms, prescaler sets up timeout /128 = ~1s)
|
// setup watchdog (timer base is 8ms, prescaler sets up timeout /128 = ~1s)
|
||||||
P0_WDTCR = 0x87; // enable watchdog ~1s
|
// P0_WDTCR = 0x87; // enable watchdog ~1s
|
||||||
P0_WDTKEY = 0x4e; // reset watchdog
|
// P0_WDTKEY = 0x4e; // reset watchdog
|
||||||
|
|
||||||
// P0_WDTCR = 0x07; // disable watchdog ~1s
|
P0_WDTCR = 0x07; // disable watchdog ~1s
|
||||||
// P0_WDTKEY = 0xb1; // disable watchdog
|
P0_WDTKEY = 0xb1; // disable watchdog
|
||||||
|
|
||||||
// power down unused peripherlas
|
// power down unused peripherlas
|
||||||
P0_DEVPD1 |= BIT(6) | BIT(5) | BIT(3) | BIT(1); // PWM A, timer 3, SPI, LVD
|
P0_DEVPD1 |= BIT(6) | BIT(5) | BIT(3) | BIT(1); // PWM A, timer 3, SPI, LVD
|
||||||
|
@ -342,13 +394,59 @@ void main(void)
|
||||||
|
|
||||||
i2c_slave_init();
|
i2c_slave_init();
|
||||||
|
|
||||||
// enable interrupts
|
T1_SET_TIMEOUT(40000);
|
||||||
EA = 1;
|
|
||||||
|
|
||||||
|
// enable interrupts
|
||||||
|
ET1 = 1;
|
||||||
|
EA = 1;
|
||||||
ext_int_deassert();
|
ext_int_deassert();
|
||||||
|
|
||||||
|
#if POLL_INPUT
|
||||||
|
keyscan_active();
|
||||||
|
#else
|
||||||
keyscan_idle();
|
keyscan_idle();
|
||||||
|
#endif
|
||||||
|
uint8_t asserted = 0;
|
||||||
|
//uint16_t ticks = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
|
if (!run_tasks) {
|
||||||
|
// power down
|
||||||
|
//PCON |= BIT(1);
|
||||||
|
|
||||||
|
// go to idle CPU mode when there's nothing to do
|
||||||
|
//PCON |= BIT(0);
|
||||||
|
__asm__("nop");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ticks++;
|
||||||
|
run_tasks = 0;
|
||||||
|
|
||||||
|
#if POLL_INPUT
|
||||||
|
// every 10ms we will scan the keyboard keys state and check for changes
|
||||||
|
uint8_t keys[12];
|
||||||
|
uint8_t active_rows = keyscan_scan(keys);
|
||||||
|
if (active_rows) {
|
||||||
|
// pressing FN+PINE+F switches to flashing mode (keys 1:2 3:5 5:2, electrically)
|
||||||
|
if (keys[0] & BIT(2) && keys[2] & BIT(5) && keys[4] & BIT(2)) {
|
||||||
|
EA = 0;
|
||||||
|
__asm__("mov r6,#0x5a");
|
||||||
|
__asm__("mov r7,#0xe7");
|
||||||
|
__asm__("ljmp 0x0118");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for changes
|
||||||
|
if (!memcmp(i2c_regs + 4, keys, 12))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// signal interrupt
|
||||||
|
memcpy(i2c_regs + 4, keys, 12);
|
||||||
|
ext_int_assert();
|
||||||
|
delay_us(100);
|
||||||
|
ext_int_deassert();
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
if (scan_active) {
|
if (scan_active) {
|
||||||
uint8_t active_rows = keyscan_scan(i2c_regs + 4);
|
uint8_t active_rows = keyscan_scan(i2c_regs + 4);
|
||||||
if (!active_rows) {
|
if (!active_rows) {
|
||||||
|
@ -360,6 +458,7 @@ void main(void)
|
||||||
//__asm__("nop");
|
//__asm__("nop");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pressing FN+PINE+F switches to flashing mode (keys 1:2 3:5 5:2, electrically)
|
||||||
if (i2c_regs[4 + 0] & BIT(2) && i2c_regs[4 + 2] & BIT(5) && i2c_regs[4 + 4] & BIT(2)) {
|
if (i2c_regs[4 + 0] & BIT(2) && i2c_regs[4 + 2] & BIT(5) && i2c_regs[4 + 4] & BIT(2)) {
|
||||||
EA = 0;
|
EA = 0;
|
||||||
__asm__("mov r6,#0x5a");
|
__asm__("mov r6,#0x5a");
|
||||||
|
@ -374,5 +473,6 @@ void main(void)
|
||||||
scan_active = 1;
|
scan_active = 1;
|
||||||
keyscan_active();
|
keyscan_active();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue