reform

MNT Reform: Open Source Portable Computer
Log (Feed) | Files | Refs (Tags) | README

commit 6642b62ddd58ad39c168b02a9375148c1344b0d5
parent 35e1ace1151de86a0f2836c849398d05c8fd1bb5
Author: mntmn <lukas@mntre.com>
Date:   Mon, 27 Sep 2021 20:48:17 +0000

Merge branch 'wip-lpc-powersave' into 'master'

Deep Sleep/Powersave modes for Keyboard/Motherboard LPC Tandem

See merge request reform/reform!20
Diffstat:
Mreform2-keyboard-fw/Keyboard.c | 355+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mreform2-keyboard-fw/Keyboard.h | 3++-
Mreform2-lpc-fw/src/boards/reform2/board_reform2.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
3 files changed, 354 insertions(+), 194 deletions(-)

diff --git a/reform2-keyboard-fw/Keyboard.c b/reform2-keyboard-fw/Keyboard.c @@ -41,11 +41,28 @@ #include <stdlib.h> #include <avr/sleep.h> -#define KBD_FW_REV "R1 20210815" +#define KBD_FW_REV "R1 20210927" //#define KBD_VARIANT_STANDALONE #define KBD_VARIANT_QWERTY_US //#define KBD_VARIANT_NEO2 +#define COLS 14 +#define ROWS 6 + +uint8_t matrix[COLS*6+2] = { + KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, HID_KEYBOARD_SC_EXSEL, + + KEY_GRAVE_ACCENT_AND_TILDE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS_AND_UNDERSCORE, KEY_EQUAL_AND_PLUS, KEY_BACKSPACE, + + KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_OPENING_BRACKET_AND_OPENING_BRACE, KEY_CLOSING_BRACKET_AND_CLOSING_BRACE, KEY_BACKSLASH_AND_PIPE, + + HID_KEYBOARD_SC_LEFT_CONTROL, HID_KEYBOARD_SC_APPLICATION, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON_AND_COLON, KEY_APOSTROPHE_AND_QUOTE, KEY_ENTER, + + HID_KEYBOARD_SC_LEFT_SHIFT, HID_KEYBOARD_SC_NON_US_BACKSLASH_AND_PIPE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, HID_KEYBOARD_SC_COMMA_AND_LESS_THAN_SIGN, HID_KEYBOARD_SC_DOT_AND_GREATER_THAN_SIGN, KEY_SLASH_AND_QUESTION_MARK, HID_KEYBOARD_SC_UP_ARROW, HID_KEYBOARD_SC_RIGHT_SHIFT, + + HID_KEYBOARD_SC_RIGHT_GUI, HID_KEYBOARD_SC_LEFT_GUI, HID_KEYBOARD_SC_RIGHT_CONTROL, KEY_SPACE, HID_KEYBOARD_SC_LEFT_ALT, HID_KEYBOARD_SC_RIGHT_ALT, KEY_SPACE, HID_KEYBOARD_SC_PAGE_UP, HID_KEYBOARD_SC_PAGE_DOWN, HID_KEYBOARD_SC_LEFT_ARROW, HID_KEYBOARD_SC_DOWN_ARROW, HID_KEYBOARD_SC_RIGHT_ARROW, 0xfe,0xed,0xca,0xfe +}; + /** Buffer to hold the previously generated Keyboard HID report, for comparison purposes inside the HID class driver. */ static uint8_t PrevKeyboardHIDReportBuffer[sizeof(USB_KeyboardReport_Data_t)]; @@ -75,22 +92,9 @@ USB_ClassInfo_HID_Device_t Keyboard_HID_Interface = #define set_input(portdir,pin) portdir &= ~(1<<pin) #define set_output(portdir,pin) portdir |= (1<<pin) -uint8_t matrix[15*6] = { - KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, HID_KEYBOARD_SC_EXSEL, HID_KEYBOARD_SC_EXSEL, - - KEY_GRAVE_ACCENT_AND_TILDE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS_AND_UNDERSCORE, KEY_EQUAL_AND_PLUS, KEY_BACKSPACE, 0, - - KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_OPENING_BRACKET_AND_OPENING_BRACE, KEY_CLOSING_BRACKET_AND_CLOSING_BRACE, KEY_BACKSLASH_AND_PIPE, 0, - - HID_KEYBOARD_SC_LEFT_CONTROL, HID_KEYBOARD_SC_APPLICATION, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON_AND_COLON, KEY_APOSTROPHE_AND_QUOTE, KEY_ENTER, 0, - - HID_KEYBOARD_SC_LEFT_SHIFT, HID_KEYBOARD_SC_NON_US_BACKSLASH_AND_PIPE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, HID_KEYBOARD_SC_COMMA_AND_LESS_THAN_SIGN, HID_KEYBOARD_SC_DOT_AND_GREATER_THAN_SIGN, KEY_SLASH_AND_QUESTION_MARK, HID_KEYBOARD_SC_UP_ARROW, HID_KEYBOARD_SC_RIGHT_SHIFT, 0, - - HID_KEYBOARD_SC_RIGHT_GUI, HID_KEYBOARD_SC_LEFT_GUI, HID_KEYBOARD_SC_RIGHT_CONTROL, KEY_SPACE, HID_KEYBOARD_SC_LEFT_ALT, HID_KEYBOARD_SC_RIGHT_ALT, KEY_SPACE, HID_KEYBOARD_SC_PAGE_UP, HID_KEYBOARD_SC_PAGE_DOWN, HID_KEYBOARD_SC_LEFT_ARROW, HID_KEYBOARD_SC_DOWN_ARROW, HID_KEYBOARD_SC_RIGHT_ARROW, 0,0,0 -}; - -uint8_t matrix_debounce[15*6]; -uint8_t matrix_state[15*6]; +uint8_t matrix_debounce[COLS*6]; +uint8_t matrix_state[COLS*6]; +uint8_t remote_som_power_expected_state = 0; // f8 = sleep // 49 = mute @@ -220,9 +224,68 @@ void insert_bat_icon(char* str, int x, float v) { str[x+1] = 4*32+icon+1; } -void remote_get_voltages(void) { +void remote_try_wakeup(void) { + char buf[64]; + + for (int i=0; i<1000; i++) { + if (i%10 == 0) { + gfx_clear(); + sprintf(buf, "Waking up LPC... %d%%", i/4); + gfx_poke_str(0, 0, buf); + gfx_flush(); + } + + Serial_SendByte('a'); + Serial_SendByte('\r'); + + if (Serial_ReceiveByte()>0) { + remote_receive_string(0); + break; + } + + Delay_MS(25); + } + Serial_SendByte('\r'); + Delay_MS(10); + while (remote_receive_string(0)) { + Delay_MS(25); + } +} + +int remote_try_command(char* cmd, int print_response) { + int ok = 0; + empty_serial(); + for (int tries=0; tries<2; tries++) { + for (int i=0; i<strlen(cmd); i++) { + Serial_SendByte(cmd[i]); + } + Serial_SendByte('\r'); + Delay_MS(1); + if (print_response) { + term_x = 0; + term_y = 0; + } + ok = remote_receive_string(print_response); + + if (!ok && tries == 0) { + remote_try_wakeup(); + empty_serial(); + } + if (ok) break; + } + if (!ok) { + gfx_clear(); + gfx_poke_str(0, 0, "No response from LPC."); + gfx_flush(); + } + + empty_serial(); + return ok; +} + +void remote_get_voltages(void) { term_x = 0; term_y = 0; @@ -230,15 +293,13 @@ void remote_get_voltages(void) { float bat_amps = 0; char bat_gauge[5] = {0,0,0,0,0}; - Serial_SendByte('c'); - Serial_SendByte('\r'); - Delay_MS(1); - remote_receive_string(0); + int ok = remote_try_command("c", 0); + if (!ok) return; - // lpc format: 32 32 32 32 32 32 32 32 mA 0256mV26143 ???% - // | | | | | | | | | | | | - // 0 3 6 9 12 15 18 21 24| | | - // 26 33 39 + // lpc format: 32 32 32 32 32 32 32 32 mA 0256mV26143 ???% P1 + // | | | | | | | | | | | | | + // 0 3 6 9 12 15 18 21 24| | | | + // 26 33 39 44 // | // `- can be a minus float sum_volts = 0; @@ -260,21 +321,34 @@ void remote_get_voltages(void) { int gauge_offset = volts_offset+5+1; strncpy(bat_gauge, &response[gauge_offset], 4); + char* power_str = " "; + int syspower_offset = gauge_offset+5; + char power_digit = response[syspower_offset+1]; + if (power_digit == '1') { + power_str = " On"; + } else if (power_digit == '0') { + power_str = "Off"; + } + // plot gfx_clear(); char str[32]; - sprintf(str,"[] %.1f [] %.1f %s",voltages[0],voltages[4],bat_gauge); + sprintf(str,"[] %.1f [] %.1f %s",voltages[0],voltages[4],bat_gauge); insert_bat_icon(str,0,voltages[0]); insert_bat_icon(str,8,voltages[4]); gfx_poke_str(0,0,str); - sprintf(str,"[] %.1f [] %.1f ",voltages[1],voltages[5]); + sprintf(str,"[] %.1f [] %.1f %s",voltages[1],voltages[5],power_str); insert_bat_icon(str,0,voltages[1]); insert_bat_icon(str,8,voltages[5]); gfx_poke_str(0,1,str); - sprintf(str,"[] %.1f [] %.1f %2.2fA",voltages[2],voltages[6],bat_amps); + if (bat_amps>=0) { + sprintf(str,"[] %.1f [] %.1f %2.3fA",voltages[2],voltages[6],bat_amps); + } else { + sprintf(str,"[] %.1f [] %.1f %2.2fA",voltages[2],voltages[6],bat_amps); + } insert_bat_icon(str,0,voltages[2]); insert_bat_icon(str,8,voltages[6]); gfx_poke_str(0,2,str); @@ -297,9 +371,11 @@ void remote_check_for_low_battery(void) { Serial_SendByte('c'); Serial_SendByte('\r'); Delay_MS(1); - remote_receive_string(0); + int ok = remote_receive_string(0); + if (!ok) return; for (int i=0; i<8; i++) { + // TODO: only accept digits voltages[i] = ((float)((response[i*3]-'0')*10 + (response[i*3+1]-'0')))/10.0; if (voltages[i]<0) voltages[i]=0; if (voltages[i]>=10) voltages[i]=9.9; @@ -319,6 +395,25 @@ void remote_check_for_low_battery(void) { low_battery_alert = 1; } } + + int syspower_offset = gauge_offset+5; + if (response[syspower_offset] == 'P') { + char digit = response[syspower_offset+1]; + if (digit == '0' || digit == '1') { + int is_computer_on = (digit == '1'); + if (!is_computer_on && remote_som_power_expected_state == 1) { + // LPC says the computer is off, but we didn't expect it to be. + // the only way this happens is if LPC turned off the system + // due to a low battery condition. + // + // The keyboard will then go to sleep accordingly. + + EnterPowerOff(); + reset_keyboard_state(); + } + remote_som_power_expected_state = is_computer_on; + } + } } void remote_get_status(void) { @@ -331,12 +426,8 @@ void remote_get_status(void) { gfx_flush(); #ifndef KBD_VARIANT_STANDALONE - term_x = 0; - term_y = 0; - Serial_SendByte('s'); - Serial_SendByte('\r'); - Delay_MS(1); - remote_receive_string(1); + int ok = remote_try_command("s", 1); + if (!ok) return; #endif } @@ -378,7 +469,7 @@ void kbd_brightness_dec(void) { } void kbd_brightness_set(int brite) { - pwmval=brite; + pwmval = brite; if (pwmval<0) pwmval = 0; if (pwmval>=10) pwmval = 10; OCR0A = pwmval; @@ -386,113 +477,60 @@ void kbd_brightness_set(int brite) { void remote_turn_on_som(void) { gfx_clear(); - empty_serial(); - term_x = 0; - term_y = 0; + int ok = remote_try_command("1p", 0); + if (!ok) return; - Serial_SendByte('1'); - Serial_SendByte('p'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); anim_hello(); kbd_brightness_init(); + + remote_som_power_expected_state = 1; } void remote_turn_off_som(void) { anim_goodbye(); - empty_serial(); - term_x = 0; - term_y = 0; + int ok = remote_try_command("0p", 0); + if (!ok) return; - Serial_SendByte('0'); - Serial_SendByte('p'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + remote_som_power_expected_state = 0; } void remote_reset_som(void) { - empty_serial(); - - term_x = 0; - term_y = 0; - - Serial_SendByte('2'); - Serial_SendByte('p'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("2p", 0); + if (!ok) return; } void remote_wake_som(void) { - empty_serial(); - - term_x = 0; - term_y = 0; - - Serial_SendByte('1'); - Serial_SendByte('w'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); - Serial_SendByte('0'); - Serial_SendByte('w'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("1w", 0); + if (!ok) return; + ok = remote_try_command("0w", 0); + if (!ok) return; } void remote_turn_off_aux(void) { - empty_serial(); - - Serial_SendByte('3'); - Serial_SendByte('p'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("3p", 0); + if (!ok) return; } void remote_turn_on_aux(void) { - empty_serial(); - - Serial_SendByte('4'); - Serial_SendByte('p'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("4p", 0); + if (!ok) return; } void remote_report_voltages(void) { - empty_serial(); - - Serial_SendByte('0'); - Serial_SendByte('c'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("0c", 0); + if (!ok) return; } void remote_enable_som_uart(void) { - empty_serial(); - - Serial_SendByte('1'); - Serial_SendByte('u'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("1u", 0); + if (!ok) return; } void remote_disable_som_uart(void) { - empty_serial(); - - Serial_SendByte('0'); - Serial_SendByte('u'); - Serial_SendByte('\r'); - Delay_MS(1); - empty_serial(); + int ok = remote_try_command("0u", 0); + if (!ok) return; } typedef struct MenuItem { @@ -509,7 +547,7 @@ const MenuItem menu_items[] = { { "System Status s", KEY_S } }; #else -#define MENU_NUM_ITEMS 10 +#define MENU_NUM_ITEMS 9 const MenuItem menu_items[] = { { "Exit Menu ESC", KEY_ESCAPE }, { "Power On 1", KEY_1 }, @@ -520,7 +558,11 @@ const MenuItem menu_items[] = { { "Key Backlight+ F2", KEY_F2 }, { "Wake SPC", KEY_SPACE }, { "System Status s", KEY_S }, - { "KBD Power-Off p", KEY_P } + + // Only needed for debugging. + // The keyboard will go to sleep when turning off + // main system power. + { "KBD Power-Off p", KEY_P }, }; #endif @@ -553,6 +595,8 @@ int execute_meta_function(int keycode) { // TODO: are you sure? remote_turn_off_som(); EnterPowerOff(); + // Directly enter menu again + return 2; } else if (keycode == KEY_1) { remote_turn_on_som(); @@ -565,10 +609,7 @@ int execute_meta_function(int keycode) { else if (keycode == KEY_SPACE) { remote_wake_som(); } - /*else if (keycode == KEY_X) { - remote_turn_on_aux(); - } - else if (keycode == KEY_V) { + /*else if (keycode == KEY_V) { remote_turn_off_aux(); }*/ else if (keycode == KEY_B) { @@ -611,6 +652,8 @@ int execute_meta_function(int keycode) { } else if (keycode == KEY_P) { EnterPowerOff(); + // Directly enter menu again + return 2; } gfx_clear(); @@ -621,14 +664,30 @@ int execute_meta_function(int keycode) { uint8_t last_meta_key = 0; +// enter the menu +void enter_meta_mode(void) { + current_scroll_y = 0; + current_menu_y = 0; + active_meta_mode = 1; + // render menu + render_menu(current_scroll_y); +} + +void reset_keyboard_state(void) { + for (int i=0; i<COLS*ROWS; i++) { + matrix_debounce[i] = 0; + matrix_state[i] = 0; + } + last_meta_key = 0; +} + void process_keyboard(char usb_report_mode, USB_KeyboardReport_Data_t* KeyboardReport) { // how many keys are pressed this round uint8_t total_pressed = 0; uint8_t used_key_codes = 0; // pull ROWs low one after the other - for (int y=0; y<6; y++) { - + for (int y=0; y<ROWS; y++) { switch (y) { case 0: output_low(PORTB, 6); break; case 1: output_low(PORTB, 5); break; @@ -642,9 +701,9 @@ void process_keyboard(char usb_report_mode, USB_KeyboardReport_Data_t* KeyboardR // TODO maybe not necessary _delay_us(10); - // check input COLs + // check input COLs for (int x=0; x<14; x++) { - uint16_t loc = y*15+x; + uint16_t loc = y*COLS+x; uint16_t keycode = matrix[loc]; uint8_t pressed = 0; uint8_t debounced_pressed = 0; @@ -684,16 +743,12 @@ void process_keyboard(char usb_report_mode, USB_KeyboardReport_Data_t* KeyboardR // circle key? if (keycode == HID_KEYBOARD_SC_EXSEL) { if (!active_meta_mode && !last_meta_key) { - current_scroll_y = 0; - current_menu_y = 0; - active_meta_mode = 1; - // render menu - render_menu(current_scroll_y); + enter_meta_mode(); } } else { if (active_meta_mode) { // not holding the same key? - if (last_meta_key!=keycode) { + if (last_meta_key != keycode) { // hyper/circle/menu functions int stay_meta = execute_meta_function(keycode); // don't repeat action while key is held down @@ -703,6 +758,13 @@ void process_keyboard(char usb_report_mode, USB_KeyboardReport_Data_t* KeyboardR if (!stay_meta) { active_meta_mode = 0; } + + // after wake-up from sleep mode, skip further keymap processing + if (stay_meta == 2) { + reset_keyboard_state(); + enter_meta_mode(); + return; + } } } else if (!last_meta_key) { // not meta mode, regular key: report keypress via USB @@ -749,16 +811,17 @@ void process_alerts(void) { int main(void) { #ifdef KBD_VARIANT_QWERTY_US - matrix[15*4+1]=KEY_DELETE; + matrix[COLS*4+1]=KEY_DELETE; #endif #ifdef KBD_VARIANT_NEO2 - matrix[15*3+0]=HID_KEYBOARD_SC_CAPS_LOCK; // M3 - matrix[15*2+13]=KEY_ENTER; - matrix[15*3+13]=KEY_BACKSLASH_AND_PIPE; // M3 + matrix[COLS*3+0]=HID_KEYBOARD_SC_CAPS_LOCK; // M3 + matrix[COLS*2+13]=KEY_ENTER; + matrix[COLS*3+13]=KEY_BACKSLASH_AND_PIPE; // M3 #endif SetupHardware(); GlobalInterruptEnable(); + anim_hello(); int counter = 0; @@ -769,7 +832,7 @@ int main(void) USB_USBTask(); counter++; #ifndef KBD_VARIANT_STANDALONE - if (counter>=30000) { + if (counter>=100000) { remote_check_for_low_battery(); counter = 0; } @@ -780,7 +843,7 @@ int main(void) } } -void SetupHardware() +void SetupHardware(void) { // Disable watchdog if enabled by bootloader/fuses MCUSR &= ~(1 << WDRF); @@ -810,8 +873,6 @@ void SetupHardware() kbd_brightness_init(); gfx_init(false); - anim_hello(); - Serial_Init(57600, false); USB_Init(); } @@ -828,26 +889,28 @@ void SetupHardware() void EnterPowerOff(void) { USB_Disable(); // Stop USB stack so it doesn't wake us up - - kbd_brightness_set(0); + + // turn off backlight, but don't overwrite setting + OCR0A = 0; + // Turn off OLED to save power gfx_clear_screen(); gfx_off(); // Disable ADC to save even more power ADCSRA=0; - cli(); // No interrupts + cli(); // No interrupts // Set all ports not floating if possible, leaving pullups alone PORTB=0x3F; // Leave pull-up on all the columns on PB0-3, drive rows 2-3 high, 1-low - PORTC=0xC0; + PORTC=0xC0; PORTD=0xF0; // Keep pullup on PD5 like setup did, drive rows 4,5,6 high PORTE=0x40; // Pullup on PE6 PORTF=0xFF; // Pullups on PF (columns) // ROW1 is the only row driven low and left low, thus is always ready to be read out // We just need to check COL14 (PC6) if it is low (pressed) or high - // Unfortunatly the circle key is on COL14(PC6) which doesn't have pin change interrupt + // Unfortunately the circle key is on COL14(PC6) which doesn't have pin change interrupt // capabilities, so we need to wake up every so often to check if it is pressed, and // if so bring us out of power-off // We can use the Watchdog timer to do this. @@ -855,7 +918,7 @@ void EnterPowerOff(void) do { wdt_reset(); WDTCSR = (1<<WDCE) | (1<<WDE); // Enable writes to watchdog - WDTCSR = (1<<WDIE) | (1<<WDE) | (1<<WDP2) | (1<<WDP1); // Interrupt mode, 1s timeout + WDTCSR = (1<<WDIE) | (1<<WDE) | (0<<WDP3) | (1<<WDP2) | (1<<WDP1) | (0<<WDP0); // Interrupt mode, 1s timeout // Enter Power-save mode set_sleep_mode(SLEEP_MODE_PWR_DOWN); @@ -864,11 +927,11 @@ void EnterPowerOff(void) sleep_cpu(); // Actually go to sleep // Zzzzzz sleep_disable(); // We've woken up - sei(); + sei(); // Check if circle key has been pressed (active-low) // If not reset the watchdog and try again } while(PINC&(1<<6)); - + // Resume and reinitialize hardware SetupHardware(); } @@ -978,6 +1041,8 @@ void CALLBACK_HID_Device_ProcessHIDReport(USB_ClassInfo_HID_Device_t* const HIDI else if (data[0]=='P' && data[1]=='W' && data[2]=='R' && data[3]=='0') { // PWR0: shutdown (turn off power rails) remote_turn_off_som(); + EnterPowerOff(); + reset_keyboard_state(); } else if (data[0]=='P' && data[1]=='W' && data[2]=='R' && data[3]=='3') { // PWR3: aux power off diff --git a/reform2-keyboard-fw/Keyboard.h b/reform2-keyboard-fw/Keyboard.h @@ -67,7 +67,8 @@ /* Function Prototypes: */ void SetupHardware(void); - void EnterPowerOff(void); + void EnterPowerOff(void); + void reset_keyboard_state(void); void EVENT_USB_Device_Connect(void); void EVENT_USB_Device_Disconnect(void); diff --git a/reform2-lpc-fw/src/boards/reform2/board_reform2.c b/reform2-lpc-fw/src/boards/reform2/board_reform2.c @@ -37,9 +37,12 @@ #define REFORM_MBREV_R3 13 // R2 with "NTC instead of RNG/SS" fix // don't forget to set this to the correct rev for your motherboard! -#define REFORM_MOTHERBOARD_REV REFORM_MBREV_R2 +#define REFORM_MOTHERBOARD_REV REFORM_MBREV_R3 //#define REF2_DEBUG 1 -#define FW_REV "MREF2LPC R2 20210419" +#define FW_REV "MREF2LPC R3 20210925" + +#define POWERSAVE_SLEEP_SECONDS 1 +#define POWERSAVE_HOLDOFF_CYCLES (60*15) #define INA260_ADDRESS 0x4e #define LTC4162F_ADDRESS 0x68 @@ -134,7 +137,8 @@ enum state_t { ST_OVERVOLTED, ST_UNDERVOLTED, ST_MISSING, - ST_FULLY_CHARGED + ST_FULLY_CHARGED, + ST_POWERSAVE }; // charging state machine @@ -143,6 +147,7 @@ int cycles_in_state = 0; int charge_current = 1; uint32_t cur_second = 0; uint32_t last_second = 0; +int powersave_holdoff_cycles = POWERSAVE_HOLDOFF_CYCLES; // 1.8A x 3600 seconds/hour #define MAX_CAPACITY (1.8)*3600.0 @@ -377,6 +382,7 @@ uint16_t charger_alerts; uint16_t status_alerts; float chg_vin; float chg_vbat; +int som_is_powered = 0; void turn_som_power_on(void) { LPC_GPIO->CLR[1] = (1 << 28); // hold in reset @@ -400,6 +406,8 @@ void turn_som_power_on(void) { LPC_GPIO->SET[0] = (1 << 7); // AUX 3v3 on (R1+) LPC_GPIO->SET[1] = (1 << 28); // release reset + + som_is_powered = 1; } void turn_som_power_off(void) { @@ -417,6 +425,8 @@ void turn_som_power_off(void) { LPC_GPIO->CLR[1] = (1 << 19); // 1v2 off LPC_GPIO->CLR[1] = (1 << 31); // USB 5v off (R1+) LPC_GPIO->CLR[0] = (1 << 7); // AUX 3v3 off (R1+) + + som_is_powered = 0; } // just a reset pulse to IMX, no power toggling @@ -447,33 +457,15 @@ void brownout_setup(void) { } void watchdog_feed(void) { + __disable_irq(); LPC_WWDT->FEED = 0xAA; LPC_WWDT->FEED = 0x55; + __enable_irq(); } #define WWDT_WDMOD_WDEN ((uint32_t) (1 << 0)) #define WWDT_WDMOD_WDRESET ((uint32_t) (1 << 1)) -void watchdog_setup(void) { - LPC_SYSCON->SYSAHBCLKCTRL |= (1<<15); // WWDT enable - - LPC_SYSCON->WDTOSCCTRL = - (1<<5) | // FREQSEL 0.6MHz - 31; // DIVSEL 64 (31+1)*2 - - LPC_SYSCON->PDRUNCFG &= ~(1<<6); // WDTOSC_PD disable - - LPC_WWDT->CLKSEL = 1; // WDOSC - - LPC_WWDT->TC = 0xffff/5; // timeout counter, ~5 seconds - - LPC_WWDT->MOD = 0; - LPC_WWDT->MOD |= WWDT_WDMOD_WDRESET; // enable WDRESET (watchdog resets system) - LPC_WWDT->MOD |= WWDT_WDMOD_WDEN; // watchdog enable - - watchdog_feed(); -} - void boardInit(void) { SystemCoreClockUpdate(); @@ -545,10 +537,14 @@ uint8_t remote_arg = 0; unsigned char cmd_state = ST_EXPECT_DIGIT_0; unsigned int cmd_number = 0; int cmd_echo = 0; +int force_sleep = 0; void handle_commands() { if (!uartRxBufferDataPending()) return; + // reset sleep counter on any interaction + powersave_holdoff_cycles = POWERSAVE_HOLDOFF_CYCLES; + char chr = uartRxBufferRead(); if (cmd_echo) { @@ -638,6 +634,12 @@ void handle_commands() { uartSend((uint8_t*)uartBuffer, strlen(uartBuffer)); } } + else if (remote_cmd == 'x') { + // test sleep + force_sleep = cmd_number; + sprintf(uartBuffer,"sleep: %d\r\n", force_sleep); + uartSend((uint8_t*)uartBuffer, strlen(uartBuffer)); + } else if (remote_cmd == 'a') { // get system current (mA) sprintf(uartBuffer,"%d\r\n",(int)(current*1000.0)); @@ -668,9 +670,12 @@ void handle_commands() { sprintf(uartBuffer,FW_REV"cell missing,%d,%d,%d\r",cycles_in_state,min_mah,acc_mah); } else if (state == ST_FULLY_CHARGED) { sprintf(uartBuffer,FW_REV"full charge,%d,%d,%d\r",cycles_in_state,min_mah,acc_mah); + } else if (state == ST_POWERSAVE) { + sprintf(uartBuffer,FW_REV"powersave,%d,%d,%d\r",cycles_in_state,min_mah,acc_mah); } else { sprintf(uartBuffer,FW_REV"unknown:%d,%d,%d,%d\r",state,cycles_in_state,min_mah,acc_mah); } + uartSend((uint8_t*)uartBuffer, strlen(uartBuffer)); } else if (remote_cmd == 'u') { @@ -719,7 +724,7 @@ void handle_commands() { } int mV = (int)(volts*1000.0); - sprintf(uartBuffer,"%02d%c%02d%c%02d%c%02d%c%02d%c%02d%c%02d%c%02d%cmA%c%04dmV%05d %s\r\n", + sprintf(uartBuffer,"%02d%c%02d%c%02d%c%02d%c%02d%c%02d%c%02d%c%02d%cmA%c%04dmV%05d %s P%d\r\n", (int)(cells_v[0]*10), (discharge_bits &(1<<0))?'!':' ', (int)(cells_v[1]*10), @@ -739,7 +744,8 @@ void handle_commands() { mA_sign, mA, mV, - gauge); + gauge, + som_is_powered); uartSend((uint8_t*)uartBuffer, strlen(uartBuffer)); } else if (remote_cmd == 'S') { @@ -798,6 +804,67 @@ void report_to_spi(void) ssp0Send((uint8_t*)report, strlen(report)); } +void WDT_IRQHandler(void) +{ + // Disable WDT interrupt + NVIC_DisableIRQ(WDT_IRQn); + NVIC_ClearPendingIRQ(WDT_IRQn); +} + +// WARNING: take care not to overflow TC (11786 * secs) +void deep_sleep_seconds(int secs) { + // make WWDTINT wake the LPC up from sleep + // STARTERP1 WWDTINT bit 12 + LPC_SYSCON->STARTERP1 |= (1 << 12); + + NVIC_DisableIRQ(WDT_IRQn); + + // Configure watchdog timer to wake us up + LPC_SYSCON->SYSAHBCLKCTRL |= (1<<15); // WWDT enable + + // 9375Hz?? + LPC_SYSCON->WDTOSCCTRL = + (1<<5) | // FREQSEL 0.6MHz + 31; // DIVSEL 64 (31+1)*2 + + // Power configuration register + LPC_SYSCON->PDRUNCFG &= ~(1<<6); // WDTOSC_PD disable (power down disable) + LPC_SYSCON->PDSLEEPCFG &= ~(1<<6); // WDTOSC_PD disable (power down disable) + LPC_SYSCON->PDAWAKECFG = LPC_SYSCON->PDRUNCFG; // when waking up, power up the default blocks + + LPC_WWDT->CLKSEL = 1; // WDOSC + + //LPC_WWDT->TC = 0xffff/5; // timeout counter, ~5 seconds + // FIXME isn't that 1.39s? (0xffff/5.0937/5.0) + // no, apparently it is 5.56s (x4) + + LPC_WWDT->TC = 11786 * secs; // timeout counter, ~1 second + + LPC_WWDT->MOD = 0; + //LPC_WWDT->MOD |= WWDT_WDMOD_WDRESET; // WDRESET (watchdog resets system) + LPC_WWDT->MOD |= WWDT_WDMOD_WDEN; // watchdog enable + + // counter value that triggers interrupt + LPC_WWDT->WARNINT = 0; + + // need to feed WD once to apply WDMOD values + watchdog_feed(); + + // NVIC exception 25 (WWDT) interrupt source: + // ISER SETENA bit 25 -> 0xE000E100 + NVIC_ClearPendingIRQ(WDT_IRQn); + NVIC_EnableIRQ(WDT_IRQn); + + // Go to deep sleep mode + LPC_PMU->PCON = 1<<11; // clear DPDFLAG + LPC_PMU->PCON = 1; // select deep power down mode + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + __WFI(); + + NVIC_DisableIRQ(WDT_IRQn); + LPC_WWDT->MOD = 0; +} + int main(void) { boardInit(); @@ -808,10 +875,7 @@ int main(void) last_second = delayGetSecondsActive(); - // WIP, not yet tested - //watchdog_setup(); - //sprintf(uartBuffer, "\r\nwatchdog_setup() completed.\r\n"); - //uartSend((uint8_t*)uartBuffer, strlen(uartBuffer)); + int next_state = state; while (1) { @@ -827,8 +891,6 @@ int main(void) // charge current 2: ~0.2A - //watchdog_feed(); - measure_and_accumulate_current(); measure_cell_voltages_and_control_discharge(); calculate_capacity_percentage(); @@ -836,23 +898,29 @@ int main(void) if (state == ST_CHARGE) { reset_discharge_bits(); - if (num_missing_cells > 0) { + if (force_sleep) { + // debug sleeping + if (powersave_holdoff_cycles <= 0) { + next_state = ST_POWERSAVE; + cycles_in_state = 0; + } + } else if ((num_missing_cells >= 1) && (num_missing_cells <= 7)) { missing_reason = missing_bits; // if cells were unplugged, we don't know the capacity anymore. reached_full_charge = 0; - state = ST_MISSING; + next_state = ST_MISSING; cycles_in_state = 0; } - else if (num_undervolted_cells > 0) { + else if (current >= 0 && num_undervolted_cells > 0) { // when transitioning to undervoltage, we assume we reached the bottom // of usable capacity, so record it // but only if we reached top charge once, or our counter will // be off. - if (cycles_in_state > 5) { + if (cycles_in_state > 2) { if (reached_full_charge > 0) { capacity_min_ampsecs = capacity_accu_ampsecs; } - state = ST_UNDERVOLTED; + next_state = ST_UNDERVOLTED; cycles_in_state = 0; } } @@ -860,7 +928,7 @@ int main(void) if (cycles_in_state > 5) { // when transitioning to fully charged, we assume that we're at max capacity capacity_accu_ampsecs = capacity_max_ampsecs; - state = ST_FULLY_CHARGED; + next_state = ST_FULLY_CHARGED; reached_full_charge = 1; cycles_in_state = 0; } @@ -868,14 +936,21 @@ int main(void) else if (num_overvolted_cells > 0) { if (cycles_in_state > 5) { // some cool-off time - state = ST_OVERVOLTED; + next_state = ST_OVERVOLTED; + cycles_in_state = 0; + } + } + else if (current < 0.05 && current >= 0 && !som_is_powered) { + // if not charging and the system is off, we can sleep regularly to save power + if (powersave_holdoff_cycles <= 0) { + next_state = ST_POWERSAVE; cycles_in_state = 0; } } } else if (state == ST_UNDERVOLTED) { - // TODO: issue alert -- switch off system if critical reset_discharge_bits(); + deep_sleep_seconds(POWERSAVE_SLEEP_SECONDS); if (cycles_in_state > 1) { // TODO: find safe heuristic. here we turn off if half @@ -884,7 +959,7 @@ int main(void) turn_som_power_off(); } - state = ST_CHARGE; + next_state = ST_CHARGE; cycles_in_state = 0; } } @@ -893,7 +968,7 @@ int main(void) missing_reason = missing_bits; // if cells were unplugged, we don't know the capacity anymore. reached_full_charge = 0; - state = ST_MISSING; + next_state = ST_MISSING; cycles_in_state = 0; } else { discharge_overvolted_cells(); @@ -902,17 +977,18 @@ int main(void) if (cycles_in_state > 1 && (num_overvolted_cells==0 || num_undervolted_cells>0)) { reset_discharge_bits(); - state = ST_CHARGE; + next_state = ST_CHARGE; cycles_in_state = 0; } } } else if (state == ST_MISSING) { reset_discharge_bits(); + deep_sleep_seconds(POWERSAVE_SLEEP_SECONDS); - if (cycles_in_state > 5) { - if (num_missing_cells < 1) { - state = ST_CHARGE; + if (cycles_in_state > 1) { + if (num_missing_cells == 0 || num_missing_cells == 8) { + next_state = ST_CHARGE; cycles_in_state = 0; } } @@ -923,19 +999,30 @@ int main(void) if (cycles_in_state > 5) { // if none of the cells are fully charged anymore, allow charging again if (num_fully_charged_cells < 1) { - state = ST_CHARGE; + next_state = ST_CHARGE; cycles_in_state = 0; } else if (num_overvolted_cells > 0) { - state = ST_OVERVOLTED; + next_state = ST_OVERVOLTED; cycles_in_state = 0; } } } + else if (state == ST_POWERSAVE) { + deep_sleep_seconds(POWERSAVE_SLEEP_SECONDS); + next_state = ST_CHARGE; + cycles_in_state = 0; + } // handle keyboard commands + // this also resets powersave holdoff counter handle_commands(); - cur_second = delayGetSecondsActive(); + + if (state == ST_POWERSAVE || state == ST_MISSING || state == ST_UNDERVOLTED) { + cur_second += POWERSAVE_SLEEP_SECONDS; + } else { + cur_second = delayGetSecondsActive(); + } if (last_second != cur_second) { if (cur_second-last_second<10) { @@ -947,7 +1034,14 @@ int main(void) // report_to_spi(); } last_second = cur_second; + + if (powersave_holdoff_cycles > 0) { + powersave_holdoff_cycles--; + } } + + state = next_state; + } }