reform

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

board_reform2.c (40447B)


      1 #include "projectconfig.h"
      2 
      3 #ifdef CFG_BRD_REFORM2
      4 
      5 #include <string.h> /* strlen */
      6 
      7 #include "boards/board.h"
      8 #include "core/gpio/gpio.h"
      9 #include "core/delay/delay.h"
     10 #include "core/eeprom/eeprom.h"
     11 #include "core/pmu/pmu.h"
     12 #include "core/i2c/i2c.h"
     13 #include "core/ssp0/ssp0.h"
     14 #include "core/ssp1/ssp1.h"
     15 #include "core/uart/uart.h"
     16 
     17 #ifdef CFG_USB
     18   #include "core/usb/usbd.h"
     19   #ifdef CFG_USB_CDC
     20     #include "core/usb/usb_cdc.h"
     21   #endif
     22 #endif
     23 
     24 #ifdef CFG_INTERFACE
     25   #include "cli/cli.h"
     26 #endif
     27 
     28 #ifdef CFG_PROTOCOL
     29   #include "protocol/protocol.h"
     30 #endif
     31 
     32 #define REFORM_MBREV_D2 2
     33 #define REFORM_MBREV_D3 3
     34 #define REFORM_MBREV_D4 4
     35 #define REFORM_MBREV_R1 11
     36 #define REFORM_MBREV_R2 12 // stock R2
     37 #define REFORM_MBREV_20_R3 13 // R2 with "NTC instead of RNG/SS" fix
     38 #define REFORM_MBREV_25_R2 25 // motherboard 2.5
     39 
     40 // don't forget to set this to the correct rev for your motherboard!
     41 // if you have a motherboard made in 2023 (2.5 R-2):
     42 //#define REFORM_MOTHERBOARD_REV REFORM_MBREV_25_R2
     43 // if you have a motherboard made before 2023 (2.0 R-3):
     44 //#define REFORM_MOTHERBOARD_REV REFORM_MBREV_20_R3
     45 
     46 #ifndef REFORM_MOTHERBOARD_REV
     47 #  error You must have REFORM_MOTHERBOARD_REV set to the firmware you intend to build
     48 #endif
     49 
     50 //#define REF2_DEBUG 1
     51 #define FW_STRING1 "MREF2LPC"
     52 #if REFORM_MOTHERBOARD_REV == 2
     53 #  define FW_STRING2 "D2"
     54 #elif REFORM_MOTHERBOARD_REV == 3
     55 #  define FW_STRING2 "D3"
     56 #elif REFORM_MOTHERBOARD_REV == 4
     57 #  define FW_STRING2 "D4"
     58 #elif REFORM_MOTHERBOARD_REV == 11
     59 #  define FW_STRING2 "R1"
     60 #elif REFORM_MOTHERBOARD_REV == 12
     61 #  define FW_STRING2 "R2"
     62 #elif REFORM_MOTHERBOARD_REV == 13
     63 #  define FW_STRING2 "R3"
     64 #elif REFORM_MOTHERBOARD_REV == 25
     65 #  define FW_STRING2 "25_R2"
     66 #endif
     67 #ifndef FW_STRING3
     68 #  define FW_STRING3 "20231124"
     69 #endif
     70 #define FW_REV FW_STRING1 FW_STRING2 FW_STRING3
     71 
     72 #define POWERSAVE_SLEEP_SECONDS 1
     73 #define POWERSAVE_HOLDOFF_CYCLES (60*15)
     74 
     75 #define INA260_ADDRESS 0x4e
     76 #define LTC4162F_ADDRESS 0x68
     77 #define I2C_READ 1
     78 
     79 #define ST_EXPECT_DIGIT_0 0
     80 #define ST_EXPECT_DIGIT_1 1
     81 #define ST_EXPECT_DIGIT_2 2
     82 #define ST_EXPECT_DIGIT_3 3
     83 #define ST_EXPECT_CMD     4
     84 #define ST_SYNTAX_ERROR   5
     85 #define ST_EXPECT_RETURN  6
     86 #define ST_EXPECT_MAGIC   7
     87 
     88 extern volatile uint8_t   i2c_write_buf[I2C_BUFSIZE];
     89 extern volatile uint8_t   i2c_read_buf[I2C_BUFSIZE];
     90 extern volatile uint32_t  i2c_read_len, i2c_write_len;
     91 
     92 err_t i2c_write8(uint8_t i2c_addr, uint8_t reg, uint8_t value)
     93 {
     94   i2c_write_len = 3;
     95   i2c_read_len = 0;
     96   i2c_write_buf[0] = i2c_addr << 1;
     97   i2c_write_buf[1] = reg;
     98   i2c_write_buf[2] = value;
     99   i2cEngine();
    100 
    101   return ERROR_NONE;
    102 }
    103 
    104 int16_t i2c_read16_be(uint8_t i2c_addr, uint8_t reg)
    105 {
    106   i2c_write_len = 2;
    107   i2c_read_len = 2;
    108   i2c_write_buf[0] = i2c_addr << 1;
    109   i2c_write_buf[1] = reg;
    110   i2c_write_buf[2] = (i2c_addr << 1) | I2C_READ;
    111   i2cEngine();
    112 
    113   int16_t value = (i2c_read_buf[0] << 8) | i2c_read_buf[1];
    114   return value;
    115 }
    116 
    117 err_t i2c_write16_be(uint8_t i2c_addr, uint8_t reg, uint16_t value)
    118 {
    119   i2c_write_len = 4;
    120   i2c_read_len = 0;
    121   i2c_write_buf[0] = i2c_addr << 1;
    122   i2c_write_buf[1] = reg;
    123   i2c_write_buf[2] = value>>8;
    124   i2c_write_buf[3] = value&0xff;
    125   i2cEngine();
    126 
    127   return ERROR_NONE;
    128 }
    129 
    130 int16_t i2c_read16_le(uint8_t i2c_addr, uint8_t reg)
    131 {
    132   i2c_write_len = 2;
    133   i2c_read_len = 2;
    134   i2c_write_buf[0] = i2c_addr << 1;
    135   i2c_write_buf[1] = reg;
    136   i2c_write_buf[2] = (i2c_addr << 1) | I2C_READ;
    137   i2cEngine();
    138 
    139   int16_t value = (i2c_read_buf[1] << 8) | i2c_read_buf[0];
    140   return value;
    141 }
    142 
    143 err_t i2c_write16_le(uint8_t i2c_addr, uint8_t reg, uint16_t value)
    144 {
    145   i2c_write_len = 4;
    146   i2c_read_len = 0;
    147   i2c_write_buf[0] = i2c_addr << 1;
    148   i2c_write_buf[1] = reg;
    149   i2c_write_buf[2] = value&0xff;
    150   i2c_write_buf[3] = value>>8;
    151   i2cEngine();
    152 
    153   return ERROR_NONE;
    154 }
    155 
    156 // see https://www.analog.com/media/en/technical-documentation/data-sheets/680324fa.pdf
    157 // default = 0x41
    158 uint8_t calc_pec(uint8_t d, uint8_t pec) {
    159   //uint8_t pec = 0x41; // 0100 0001
    160 
    161   for (int i=0; i<8; i++) {
    162     uint8_t bit = (d>>7)&1; // pop off upper bit
    163     d = d<<1;
    164     uint8_t in0 = bit ^ (pec>>7);
    165     uint8_t in1 = (pec & 1) ^ in0;
    166     uint8_t in2 = ((pec & 2)>>1) ^ in0;
    167     pec = (pec<<1)&0xf8;
    168     pec |= in2<<2;
    169     pec |= in1<<1;
    170     pec |= in0;
    171   }
    172 
    173   return pec;
    174 }
    175 
    176 // main charger state machine
    177 enum state_t {
    178               ST_CHARGE,
    179               ST_OVERVOLTED,
    180               ST_COOLDOWN,
    181               ST_UNDERVOLTED,
    182               ST_MISSING,
    183               ST_FULLY_CHARGED,
    184               ST_POWERSAVE
    185 };
    186 
    187 // charging state machine
    188 int state = ST_CHARGE;
    189 int cycles_in_state = 0;
    190 int cycles_uptime = 0;
    191 int charge_current = 1;
    192 uint32_t cur_second = 0;
    193 uint32_t last_second = 0;
    194 int powersave_holdoff_cycles = POWERSAVE_HOLDOFF_CYCLES;
    195 
    196 // 1.8A x 3600 seconds/hour
    197 #define MAX_CAPACITY (1.8)*3600.0
    198 float capacity_max_ampsecs =  MAX_CAPACITY;
    199 float capacity_accu_ampsecs = MAX_CAPACITY;
    200 float capacity_min_ampsecs = 430*3.6; // TODO save this in flash after learning
    201 int capacity_percentage = 0;
    202 float volts = 0;
    203 float current = 0;
    204 unsigned long lastTime = 0;
    205 char uartBuffer[255] = {0};
    206 float cells_v[8] = {0,0,0,0,0,0,0,0};
    207 int num_undervolted_cells = 0;
    208 int num_undervolted_critical_cells = 0;
    209 int num_fully_charged_cells = 0;
    210 int num_overvolted_cells = 0;
    211 int num_missing_cells = 0;
    212 uint8_t reached_full_charge = 0;
    213 uint16_t discharge_bits = 0;
    214 uint16_t overvoltage_bits = 0;
    215 uint16_t undervoltage_bits = 0;
    216 uint16_t missing_bits = 0;
    217 uint16_t missing_reason = 0;
    218 uint8_t spir[64];
    219 bool som_is_powered = false;
    220 bool imx_uart_enabled = false;
    221 
    222 #if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_20_R3)
    223   #define OVERVOLTAGE_START_VALUE 3.8
    224   #define OVERVOLTAGE_STOP_VALUE 3.6
    225 #else
    226   #define OVERVOLTAGE_START_VALUE 3.61
    227   #define OVERVOLTAGE_STOP_VALUE 3.5
    228 #endif
    229 #define UNDERVOLTAGE_VALUE 2.45
    230 #define UNDERVOLTAGE_CRITICAL_VALUE 2.3
    231 #define MISSING_VALUE_HI 4.5
    232 #define MISSING_VALUE_LO 0.4
    233 #if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_20_R3)
    234   #define FULLY_CHARGED_VOLTAGE 3.4
    235   #define FULLY_CHARGED_CURRENT -0.3
    236 #else
    237   #define FULLY_CHARGED_VOLTAGE 3.5
    238   #define FULLY_CHARGED_CURRENT -0.6
    239 #endif
    240 #define WALLPOWER_DETECT_VOLTAGE 24
    241 
    242 void set_discharge_bits(uint16_t bits) {
    243   // 0x10: WRCFG, write config
    244   spir[0] = 0x01;
    245   spir[1] = 0xc7;
    246 
    247   spir[2] = 0xe1; // set cdc to 2 = continuous measurement
    248   spir[3] = bits&0xff; // first 8 bits of discharge switches
    249   spir[4] = (bits&0xf00)>>8; // last 4 bits of discharge switches
    250 
    251   spir[5] = 0x0;
    252   spir[6] = 0x0;
    253   spir[7] = 0x0;
    254 
    255   uint8_t pec = 0x41;
    256   for (int i=2; i<=7; i++) {
    257     pec = calc_pec(spir[i], pec);
    258   }
    259   spir[8] = pec;
    260 
    261   LPC_GPIO->CLR[1] = (1 << 23);
    262   ssp1Send(spir, 9);
    263   LPC_GPIO->SET[1] = (1 << 23);
    264 }
    265 
    266 void disable_charge_current(void) {
    267   if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_R1) {
    268     LPC_GPIO->SET[1] |= (1 << 25);
    269   } else {
    270     LPC_GPIO->CLR[1] |= (1 << 25);
    271   }
    272 }
    273 
    274 void enable_charge_current(void) {
    275   if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_R1) {
    276     LPC_GPIO->CLR[1] |= (1 << 25);
    277   } else {
    278     LPC_GPIO->SET[1] |= (1 << 25);
    279   }
    280 }
    281 
    282 void measure_cell_voltages_and_control_discharge() {
    283   // first, turn off any discharging
    284   set_discharge_bits(0);
    285 
    286   // defensive: make sure not to charge with any discharge bits
    287   // turned on, regardless of state
    288   if (state == ST_CHARGE && discharge_bits == 0) {
    289     // we're in normal charge mode, so remove charge current limit
    290     enable_charge_current();
    291   } else {
    292     // we're discharging (balancing), or full charged,
    293     // so limit charge current
    294     // also don't charge if we have missing cells
    295     disable_charge_current();
    296     set_discharge_bits(discharge_bits);
    297   }
    298 
    299   // 0x10: STCVDC, start adc
    300   spir[0] = 0x60;
    301   spir[1] = 0xe7;
    302   LPC_GPIO->CLR[1] = (1 << 23);
    303   ssp1Send(spir, 2);
    304   LPC_GPIO->SET[1] = (1 << 23);
    305 
    306   // delay is measured in "ticks", which are basically ms?
    307   delay(50); // FIXME tunable
    308 
    309   // 0x4: RDCV, read all cell voltages
    310   spir[0] = 0x4;
    311   spir[1] = 0xdc;
    312 
    313   LPC_GPIO->CLR[1] = (1 << 23);
    314   ssp1Send(spir, 2);
    315   memset(spir, 0, 32);
    316   ssp1Receive(spir, 19);
    317   LPC_GPIO->SET[1] = (1 << 23);
    318 
    319   int j=0;
    320   for (int i=0; i<8; i+=2) {
    321     cells_v[i]   = ((float)((spir[j]|((spir[j+1]&0xf)<<8))-512)) * 1.5 / 1000.0;
    322     cells_v[i+1] = ((float)((spir[j+1]&0xf0)>>4|(spir[j+2]<<4))-512) * 1.5 / 1000.0;
    323     j+=3;
    324 
    325 #ifdef REF2_DEBUG
    326     //sprintf(uartBuffer,"cell %d: %fV cell %d: %fV\r\n",
    327     //                        i, cells_v[i], i+1, cells_v[i+1]);
    328     //uartSend((uint8_t *)uartBuffer, strlen(uartBuffer));
    329 #endif
    330   }
    331 
    332   num_missing_cells = 0;
    333   num_undervolted_cells = 0;
    334   num_fully_charged_cells = 0;
    335   num_overvolted_cells = 0;
    336   num_undervolted_critical_cells = 0;
    337 
    338   missing_bits = 0;
    339   undervoltage_bits = 0;
    340   overvoltage_bits = 0;
    341   for (int i=0; i<8; i++) {
    342     if (cells_v[i] >= MISSING_VALUE_HI || cells_v[i] <= MISSING_VALUE_LO) {
    343       missing_bits |= (1<<i);
    344       num_missing_cells++;
    345     }
    346     else if ((state == ST_OVERVOLTED && cells_v[i] >= OVERVOLTAGE_STOP_VALUE) ||
    347              (state != ST_OVERVOLTED && cells_v[i] >= OVERVOLTAGE_START_VALUE)) {
    348       overvoltage_bits |= (1<<i);
    349       num_overvolted_cells++;
    350       // we assume that overvoltage also means fully charged
    351       num_fully_charged_cells++;
    352     }
    353     else if (cells_v[i] >= FULLY_CHARGED_VOLTAGE) {
    354       num_fully_charged_cells++;
    355     }
    356     else if (cells_v[i] < UNDERVOLTAGE_VALUE) {
    357       undervoltage_bits |= (1<<i);
    358       num_undervolted_cells++;
    359 
    360       if (cells_v[i] < UNDERVOLTAGE_CRITICAL_VALUE) {
    361         num_undervolted_critical_cells++;
    362       }
    363     }
    364   }
    365 
    366   // 0x2: RDCFG, read config registers
    367   /*spir[0] = 0x2;
    368   spir[1] = 0xce;
    369 
    370   LPC_GPIO->CLR[1] = (1 << 23);
    371   delay(2);
    372   ssp1Send(spir, 2);
    373   memset(spir, 0, 32);
    374   ssp1Receive(spir, 7);
    375   delay(2);
    376   LPC_GPIO->SET[1] = (1 << 23);
    377 
    378   sprintf(uartBuffer,"CFG00 %02x %02x %02x %02x %02x %02x %02x %02x\r\n",
    379           spir[0], spir[1], spir[2], spir[3], spir[4], spir[5], spir[6], spir[7]);
    380           uartSend((uint8_t *)uartBuffer, strlen(uartBuffer));*/
    381 }
    382 
    383 void discharge_overvolted_cells() {
    384   discharge_bits = overvoltage_bits;
    385 }
    386 
    387 void reset_discharge_bits() {
    388   discharge_bits = 0;
    389 }
    390 
    391 // using INA260 current monitor, count amp-secs going in and out of battery
    392 // (battery gauge)
    393 void measure_and_accumulate_current() {
    394   float raw_volts   = (float)i2c_read16_be(INA260_ADDRESS, 0x2);
    395   float raw_current = (float)i2c_read16_be(INA260_ADDRESS, 0x1);
    396 
    397   volts   = raw_volts * 0.00125;
    398   current = raw_current * 0.001;
    399 
    400   if (current>-0.02 && current<0.02) current = 0; // clamp to zero
    401 
    402   unsigned long thisTime = delayGetSecondsActive();
    403   if (lastTime>0 && thisTime>lastTime) {
    404     unsigned long secondsPassed = thisTime - lastTime;
    405 
    406     if (secondsPassed >= 1) {
    407       lastTime = thisTime;
    408 
    409       // decrease estimated battery capacity
    410       capacity_accu_ampsecs -= current*(secondsPassed);
    411     }
    412   } else {
    413     // timer uninitialized or timer wrap
    414     lastTime = thisTime;
    415   }
    416 
    417 #ifdef REF2_DEBUG
    418   //sprintf(uartBuffer,"\033[H\033[2JINA Ah: %f V: %f A: %f\r\n",capacity_accu_ampsecs/3600,volts,current);
    419   //uartSend((uint8_t *)uartBuffer, strlen(uartBuffer));
    420 #endif
    421 }
    422 
    423 #define INA233_ADDRESS 0x40
    424 
    425 void ina233_calibrate() {
    426   /*
    427     current_lsb = i_max / (pow(2,15));      // 0.00030517578125 (i_max = 10 amperes)
    428     power_lsb = 25 * current_lsb;           // 0.00762939453125
    429     cal = 0.00512/(r_shunt * current_lsb);  // 838.8608 (r_shunt = 0.02 ohms)
    430 
    431     m_c = 1/current_lsb // 3276.8
    432     m_b = 1/power_lsb   // 131.072
    433 
    434     see also: https://github.com/scottvr/pi-ina233/blob/master/ina233.py
    435   */
    436 
    437   uint16_t cal = 838;
    438 
    439   i2c_write16_le(INA233_ADDRESS, 0xd4, cal);
    440 }
    441 
    442 void measure_and_accumulate_current_v25() {
    443   float raw_volts   = (float)i2c_read16_le(INA233_ADDRESS, 0x88);
    444   float raw_current = (float)i2c_read16_le(INA233_ADDRESS, 0x89);
    445 
    446   volts   = raw_volts * 0.00125; // TODO adjust for 20mOhms
    447   current = raw_current * 0.00030517578125;
    448 
    449   if (current>-0.02 && current<0.02) current = 0; // clamp to zero
    450 
    451   unsigned long thisTime = delayGetSecondsActive();
    452   if (lastTime>0 && thisTime>lastTime) {
    453     unsigned long secondsPassed = thisTime - lastTime;
    454 
    455     if (secondsPassed >= 1) {
    456       lastTime = thisTime;
    457 
    458       // decrease estimated battery capacity
    459       capacity_accu_ampsecs -= current * secondsPassed;
    460     }
    461   } else {
    462     // timer uninitialized or timer wrap
    463     lastTime = thisTime;
    464   }
    465 }
    466 
    467 void turn_som_power_on(void) {
    468   LPC_GPIO->CLR[1] = (1 << 28); // hold in reset
    469 
    470   LPC_GPIO->CLR[0] = (1 << 20); // PCIe off
    471   LPC_GPIO->CLR[1] = (1 << 19); // 1v2 off
    472   LPC_GPIO->CLR[1] = (1 << 31); // USB 5v off (R1+)
    473   LPC_GPIO->CLR[0] = (1 << 7);  // AUX 3v3 off (R1+)
    474 
    475   if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_R1 && REFORM_MOTHERBOARD_REV < REFORM_MBREV_25_R2) {
    476     LPC_GPIO->CLR[1] = (1 << 16); // 3v3 on
    477     LPC_GPIO->CLR[1] = (1 << 15); // 5v on
    478   } else {
    479     LPC_GPIO->SET[1] = (1 << 16); // 3v3 on
    480     LPC_GPIO->SET[1] = (1 << 15); // 5v on
    481   }
    482 
    483   LPC_GPIO->SET[0] = (1 << 20); // PCIe on
    484   LPC_GPIO->SET[1] = (1 << 19); // 1v2 on
    485   LPC_GPIO->SET[1] = (1 << 31); // USB 5v on (R1+)
    486   LPC_GPIO->SET[0] = (1 << 7);  // AUX 3v3 on (R1+)
    487 
    488   LPC_GPIO->SET[1] = (1 << 28); // release reset
    489 
    490   som_is_powered = true;
    491 }
    492 
    493 void turn_som_power_off(void) {
    494   LPC_GPIO->CLR[1] = (1 << 28); // hold in reset
    495 
    496   if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_R1 && REFORM_MOTHERBOARD_REV < REFORM_MBREV_25_R2) {
    497     LPC_GPIO->SET[1] = (1 << 16); // 3v3 off
    498     LPC_GPIO->SET[1] = (1 << 15); // 5v off
    499   } else {
    500     LPC_GPIO->CLR[1] = (1 << 16); // 3v3 off
    501     LPC_GPIO->CLR[1] = (1 << 15); // 5v off
    502   }
    503 
    504   LPC_GPIO->CLR[0] = (1 << 20); // PCIe off
    505   LPC_GPIO->CLR[1] = (1 << 19); // 1v2 off
    506   LPC_GPIO->CLR[1] = (1 << 31); // USB 5v off (R1+)
    507   LPC_GPIO->CLR[0] = (1 << 7);  // AUX 3v3 off (R1+)
    508 
    509   som_is_powered = false;
    510 }
    511 
    512 // just a reset pulse to IMX, no power toggling
    513 void reset_som(void) {
    514   LPC_GPIO->CLR[1] = (1 << 28); // hold reset
    515   delay(1);
    516   LPC_GPIO->SET[1] = (1 << 28); // release reset
    517 }
    518 
    519 void turn_aux_power_on(void) {
    520   LPC_GPIO->SET[0] = (1 << 20); // PCIe on
    521   //LPC_GPIO->SET[1] = (1 << 19); // 1v2 on
    522   //LPC_GPIO->SET[1] = (1 << 31); // USB 5v on (R1+)
    523   //LPC_GPIO->SET[0] = (1 << 7);  // AUX 3v3 on (R1+)
    524 }
    525 
    526 void turn_aux_power_off(void) {
    527   LPC_GPIO->CLR[0] = (1 << 20); // PCIe off
    528   //LPC_GPIO->CLR[1] = (1 << 19); // 1v2 off
    529   //LPC_GPIO->CLR[1] = (1 << 31); // USB 5v off (R1+)
    530   //LPC_GPIO->CLR[0] = (1 << 7);  // AUX 3v3 off (R1+)
    531 }
    532 
    533 void brownout_enable(void) {
    534   // Set brownout threshold to 1.46-1.63V (lowest level)
    535   // and enable brownout reset (BODRSTENA)
    536   LPC_SYSCON->BODCTRL = 0x0 | (1<<4);
    537 }
    538 
    539 void brownout_disable(void) {
    540   // disable brownout reset (BODRSTENA)
    541   LPC_SYSCON->BODCTRL = 0x0;
    542 }
    543 
    544 void watchdog_feed(void) {
    545   __disable_irq();
    546   LPC_WWDT->FEED = 0xAA;
    547   LPC_WWDT->FEED = 0x55;
    548   __enable_irq();
    549 }
    550 
    551 #define WWDT_WDMOD_WDEN             ((uint32_t) (1 << 0))
    552 #define WWDT_WDMOD_WDRESET          ((uint32_t) (1 << 1))
    553 
    554 void boardInit(void)
    555 {
    556   brownout_enable();
    557 
    558   SystemCoreClockUpdate();
    559   GPIOInit();
    560   delayInit();
    561 
    562   // Set up GPIO directions
    563 
    564   // 5V regulator (U7) on/off
    565   LPC_GPIO->DIR[1] |= (1 << 15);
    566   // 3V3 regulator (U12) on/off
    567   LPC_GPIO->DIR[1] |= (1 << 16);
    568   // 1V2 regulator (U13) on/off
    569   LPC_GPIO->DIR[1] |= (1 << 19);
    570   // PCIe 1 power supply transistor (1V5 regulator U19 and 3V3 load switch)
    571   LPC_GPIO->DIR[0] |= (1 << 20);
    572   // USB 5V rail on/off (U24) (board revision R-1+)
    573   LPC_GPIO->DIR[1] |= (1 << 31);
    574   // AUX 3V3 power rail on/off (U27), and downstream 1V8 (U17) (board revision R-1+)
    575   LPC_GPIO->DIR[0] |= (1 << 7);
    576 
    577   // IMX Wake
    578   LPC_GPIO->DIR[1] |= (1 << 24);
    579   // IMX Reset
    580   LPC_GPIO->DIR[1] |= (1 << 28);
    581 
    582   // RNG/SS pin of LTC4020: control/limit charge current
    583   LPC_GPIO->DIR[1] |= (1 << 25);
    584 
    585   // initially turn the system off
    586   turn_som_power_off();
    587 
    588   // start with low charge current
    589   disable_charge_current();
    590 
    591   uartInit(CFG_UART_BAUDRATE);
    592   i2cInit(I2CMASTER);
    593 
    594   // SPI1 connected to battery monitor (we're controller)
    595   ssp1Init();
    596   ssp1ClockSlow();
    597 
    598   // SPI0 connected to the main SOM (they're controller)
    599   ssp0Init();
    600   ssp0ClockFast();
    601 
    602   // SPI chip select
    603   LPC_GPIO->DIR[1] |= (1 << 23);
    604   LPC_GPIO->SET[1] =  (1 << 23); // active low
    605 
    606   // UART connected to i.MX8M ttymxc2
    607   /* Set 0.14 UART RXD */
    608   // this disrupts keyboard communication when main power turned off
    609   //LPC_IOCON->PIO1_14 &= ~0x07;
    610   //LPC_IOCON->PIO1_14 |= 0x03;
    611 
    612   // only send to reform, don't receive from it
    613   /* Set 0.13 UART TXD */
    614   LPC_IOCON->PIO1_13 &= ~0x07;
    615 
    616   // enable debug UART on expansion header
    617   LPC_IOCON->PIO0_19 = 0x01; // TXD
    618   imx_uart_enabled = true;
    619 
    620   // motherboard 2.5 introduces INA233 instead of INA260
    621   if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_25_R2) {
    622     ina233_calibrate();
    623   }
    624 
    625 #ifdef REF2_DEBUG
    626   sprintf(uartBuffer, "\r\nMNT Reform 2.0 MCU initialized.\r\n");
    627   uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    628 #endif
    629 }
    630 
    631 char remote_cmd = 0;
    632 uint8_t remote_arg = 0;
    633 unsigned char cmd_state = ST_EXPECT_DIGIT_0;
    634 unsigned int cmd_number = 0;
    635 int cmd_echo = 0;
    636 int force_sleep = 0;
    637 
    638 void handle_commands() {
    639   if (!uartRxBufferDataPending()) return;
    640 
    641   // reset sleep counter on any interaction
    642   powersave_holdoff_cycles = POWERSAVE_HOLDOFF_CYCLES;
    643 
    644   char chr = uartRxBufferRead();
    645 
    646   if (cmd_echo) {
    647     sprintf(uartBuffer, "%c", chr);
    648     uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    649   }
    650 
    651   // states:
    652   // 0-3 digits of optional command argument
    653   // 4   command letter expected
    654   // 5   syntax error (unexpected character)
    655   // 6   command letter entered
    656 
    657   if (cmd_state>=ST_EXPECT_DIGIT_0 && cmd_state<=ST_EXPECT_DIGIT_3) {
    658     // read number or command
    659     if (chr >= '0' && chr <= '9') {
    660       cmd_number*=10;
    661       cmd_number+=(chr-'0');
    662       cmd_state++;
    663     } else if ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')) {
    664       // command entered instead of digit
    665       remote_cmd = chr;
    666       cmd_state = ST_EXPECT_RETURN;
    667     } else if (chr == '\n' || chr == ' ') {
    668       // ignore newlines or spaces
    669     } else if (chr == '\r') {
    670       sprintf(uartBuffer, "error:syntax\r\n");
    671       uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    672       cmd_state = ST_EXPECT_DIGIT_0;
    673       cmd_number = 0;
    674     } else {
    675       // syntax error
    676       cmd_state = ST_SYNTAX_ERROR;
    677     }
    678   }
    679   else if (cmd_state == ST_EXPECT_CMD) {
    680     // read command
    681     if ((chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z')) {
    682       remote_cmd = chr;
    683       cmd_state = ST_EXPECT_RETURN;
    684     } else {
    685       cmd_state = ST_SYNTAX_ERROR;
    686     }
    687   }
    688   else if (cmd_state == ST_SYNTAX_ERROR) {
    689     // syntax error
    690     if (chr == '\r') {
    691       sprintf(uartBuffer, "error:syntax\r\n");
    692       uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    693       cmd_state = ST_EXPECT_DIGIT_0;
    694       cmd_number = 0;
    695     }
    696   }
    697   else if (cmd_state == ST_EXPECT_RETURN) {
    698     if (chr == '\n' || chr == ' ') {
    699       // ignore newlines or spaces
    700     }
    701     else if (chr == '\r') {
    702       if (cmd_echo) {
    703         // FIXME
    704         sprintf(uartBuffer,"\n");
    705         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    706       }
    707 
    708       // execute
    709       if (remote_cmd == 'p') {
    710         // toggle system power and/or reset imx
    711         if (cmd_number == 0) {
    712           turn_som_power_off();
    713           sprintf(uartBuffer,"system: off\r\n");
    714           uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    715         } else if (cmd_number == 2) {
    716           reset_som();
    717           sprintf(uartBuffer,"system: reset\r\n");
    718           uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    719         } else if (cmd_number == 3) {
    720           turn_aux_power_off();
    721           sprintf(uartBuffer,"system: auxpwr off\r\n");
    722           uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    723         } else if (cmd_number == 4) {
    724           turn_aux_power_on();
    725           sprintf(uartBuffer,"system: auxpwr on\r\n");
    726           uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    727         } else {
    728           if (som_is_powered) {
    729             sprintf(uartBuffer,"system: already on\r\n");
    730             uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    731           } else {
    732             turn_som_power_on();
    733             sprintf(uartBuffer,"system: on\r\n");
    734             uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    735           }
    736         }
    737       }
    738       else if (remote_cmd == 'x') {
    739         // test sleep
    740         force_sleep = cmd_number;
    741         sprintf(uartBuffer,"sleep: %d\r\n", force_sleep);
    742         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    743       }
    744       else if (remote_cmd == 'a') {
    745         // get system current (mA)
    746         sprintf(uartBuffer,"%d\r\n",(int)(current*1000.0));
    747         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    748       }
    749       else if (remote_cmd == 'v' && cmd_number>=0 && cmd_number<=7) {
    750         // get cell voltage
    751         sprintf(uartBuffer,"%d\r\n",(int)(cells_v[cmd_number]*1000.0));
    752         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    753       }
    754       else if (remote_cmd == 'V') {
    755         // get system voltage
    756         sprintf(uartBuffer,"%d\r\n",(int)(volts*1000.0));
    757         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    758       }
    759       else if (remote_cmd == 's') {
    760         int min_mah = (int)(capacity_min_ampsecs/3.6);
    761         int acc_mah = (int)(capacity_accu_ampsecs/3.6);
    762         int max_mah = (int)(capacity_max_ampsecs/3.6);
    763 
    764         // get charger system state
    765         if (state == ST_CHARGE) {
    766           sprintf(uartBuffer,"norm:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    767         } else if (state == ST_OVERVOLTED) {
    768           sprintf(uartBuffer,"baln:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    769         } else if (state == ST_COOLDOWN) {
    770           sprintf(uartBuffer,"cool:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    771         } else if (state == ST_UNDERVOLTED) {
    772           sprintf(uartBuffer,"uvol:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    773         } else if (state == ST_MISSING) {
    774           sprintf(uartBuffer,"miss:%d:%d mAh:%d:%d:%d up:%d "FW_REV"\r",missing_reason,cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    775         } else if (state == ST_FULLY_CHARGED) {
    776           sprintf(uartBuffer,"full:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    777         } else if (state == ST_POWERSAVE) {
    778           sprintf(uartBuffer,"psav:%d mAh:%d:%d:%d up:%d "FW_REV"\r",cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    779         } else {
    780           sprintf(uartBuffer,"unkn:%d:%d mAh:%d:%d:%d up:%d "FW_REV"\r",state,cycles_in_state,min_mah,acc_mah,max_mah,cycles_uptime);
    781         }
    782 
    783         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    784       }
    785       else if (remote_cmd == 'f') {
    786         // print the firmware string
    787         if (cmd_number == 1) {
    788             sprintf(uartBuffer,FW_STRING1"\r\n");
    789         } else if (cmd_number == 2) {
    790             sprintf(uartBuffer,FW_STRING2"\r\n");
    791         } else if (cmd_number == 3) {
    792             sprintf(uartBuffer,FW_STRING3"\r\n");
    793         } else {
    794             // if cmd_number is 0, print all of them concatenated as it is
    795             // done for "s"
    796             sprintf(uartBuffer,FW_REV"\r\n");
    797         }
    798         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    799       }
    800       else if (remote_cmd == 'u') {
    801         // turn reporting to i.MX on or off
    802         if (cmd_number>0) {
    803           // turn i.MX UART output on
    804           imx_uart_enabled = true;
    805           LPC_IOCON->PIO1_13 |= 0x3;
    806         } else {
    807           // turn i.MX UART output off
    808           imx_uart_enabled = false;
    809           LPC_IOCON->PIO1_13 &= ~0x07;
    810         }
    811       }
    812       else if (remote_cmd == 'w') {
    813         // wake i.MX
    814         if (cmd_number>0) {
    815           // send a string via UART
    816           LPC_IOCON->PIO1_13 |= 0x3;
    817           sprintf(uartBuffer,"wake!\r");
    818           uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    819           LPC_IOCON->PIO1_13 &= ~0x07;
    820         } else {
    821           // pulse wake GPIO
    822           LPC_GPIO->SET[1] = (1 << 24);
    823           delay(100);
    824           LPC_GPIO->CLR[1] = (1 << 24);
    825         }
    826       }
    827       else if (remote_cmd == 'c') {
    828         // get status of cells, current, voltage, fuel gauge
    829         char gauge[5] = {0,0,0,0,0};
    830 
    831         // get fuel gauge (percent)
    832         if (reached_full_charge > 0) {
    833           sprintf(gauge,"%3d%%", capacity_percentage);
    834         } else {
    835           // if we never reached full charge,
    836           // we don't really know where we are.
    837           sprintf(gauge,"???%%");
    838         }
    839 
    840         int mA = (int)(current*1000.0);
    841         char mA_sign = ' ';
    842         if (mA<0) {
    843           mA = -mA;
    844           mA_sign = '-';
    845         }
    846         int mV = (int)(volts*1000.0);
    847 
    848         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",
    849                 (int)(cells_v[0]*10),
    850                 (discharge_bits    &(1<<0))?'!':' ',
    851                 (int)(cells_v[1]*10),
    852                 (discharge_bits    &(1<<1))?'!':' ',
    853                 (int)(cells_v[2]*10),
    854                 (discharge_bits    &(1<<2))?'!':' ',
    855                 (int)(cells_v[3]*10),
    856                 (discharge_bits    &(1<<3))?'!':' ',
    857                 (int)(cells_v[4]*10),
    858                 (discharge_bits    &(1<<4))?'!':' ',
    859                 (int)(cells_v[5]*10),
    860                 (discharge_bits    &(1<<5))?'!':' ',
    861                 (int)(cells_v[6]*10),
    862                 (discharge_bits    &(1<<6))?'!':' ',
    863                 (int)(cells_v[7]*10),
    864                 (discharge_bits    &(1<<7))?'!':' ',
    865                 mA_sign,
    866                 mA,
    867                 mV,
    868                 gauge,
    869                 som_is_powered);
    870         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    871       }
    872       else if (remote_cmd == 'U') {
    873         // get uptime
    874         sprintf(uartBuffer, "%d\r\n", cycles_uptime);
    875         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    876       }
    877       else if (remote_cmd == 'S') {
    878         // get charger system cycles in current state
    879         sprintf(uartBuffer, "%d\r\n", cycles_in_state);
    880         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    881       }
    882       else if (remote_cmd == 'C') {
    883         // get battery capacity (mAh)
    884         sprintf(uartBuffer,"%d/%d/%d\r\n",
    885                 (int)(capacity_accu_ampsecs/3.6),
    886                 (int)(capacity_min_ampsecs/3.6),
    887                 (int)(capacity_max_ampsecs/3.6));
    888         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    889       }
    890       else if (remote_cmd == 'e') {
    891         // toggle serial echo
    892         cmd_echo = cmd_number?1:0;
    893       }
    894       else {
    895         sprintf(uartBuffer, "error:command\r\n");
    896         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    897       }
    898 
    899       cmd_state = ST_EXPECT_DIGIT_0;
    900       cmd_number = 0;
    901     } else {
    902       cmd_state = ST_SYNTAX_ERROR;
    903     }
    904   }
    905 }
    906 
    907 unsigned char spi_cmd_state = ST_EXPECT_MAGIC;
    908 unsigned char spi_command = '\0';
    909 
    910 uint8_t spi_arg1 = 0;
    911 /**
    912  * @brief SPI command from imx poll function
    913  * Attempts to handle spi communication asynchronously and in a non-blocking way.
    914  *
    915  */
    916 void handle_spi_commands() {
    917   STATIC_ASSERT(SSP0_FIFOSIZE >= 8);
    918   uint8_t spi_buf[SSP0_FIFOSIZE];
    919   uint8_t len = SSP0_FIFOSIZE;
    920 
    921   int all_zeroes = 1;
    922 
    923   // non blocking read
    924   // read until requested buffer full or receive buffer empty
    925   for (uint8_t i = 0; i < len; i++) {
    926     // No more data in receive buffer
    927     if ((LPC_SSP0->SR & SSP0_SR_RNE_MASK) == SSP0_SR_RNE_EMPTY) {
    928       len = i;
    929       break;
    930     }
    931     spi_buf[i] = LPC_SSP0->DR;
    932     if (spi_buf[i] != 0) all_zeroes = 0;
    933   }
    934 
    935   // states:
    936   // 0   arg1 byte expected
    937   // 4   command byte expected
    938   // 6   execute command
    939   // 7   magic byte expected
    940   for (uint8_t s = 0; s < len; s++) {
    941     if (spi_cmd_state == ST_EXPECT_MAGIC) {
    942       // magic byte found, prevents garbage data
    943       // in the bus from triggering a command
    944       if (spi_buf[s] == 0xB5) {
    945         spi_cmd_state = ST_EXPECT_CMD;
    946       }
    947     }
    948     else if (spi_cmd_state == ST_EXPECT_CMD) {
    949       // read command
    950       spi_command = spi_buf[s];
    951       spi_cmd_state = ST_EXPECT_DIGIT_0;
    952     }
    953     else if (spi_cmd_state == ST_EXPECT_DIGIT_0) {
    954       // read arg1 byte
    955       spi_arg1 = spi_buf[s];
    956       spi_cmd_state = ST_EXPECT_RETURN;
    957     }
    958   }
    959 
    960   if (spi_cmd_state == ST_EXPECT_MAGIC && !all_zeroes) {
    961     // reset SPI0 block
    962     // this is a workaround for confusion with
    963     // software spi from BPI-CM4 where we get
    964     // bit-shifted bytes
    965 
    966     // dump the buffer to serial
    967     if (imx_uart_enabled) {
    968       for (int i = 0; i < len; i++) {
    969         sprintf(uartBuffer, "S%d:%X\r\n", i, spi_buf[i]);
    970         uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    971       }
    972     }
    973 
    974     ssp0Init();
    975   }
    976 
    977   if (spi_cmd_state != ST_EXPECT_RETURN) {
    978     // waiting for more data
    979     return;
    980   }
    981 
    982   /*if (imx_uart_enabled) {
    983     sprintf(uartBuffer, "spi:exec:%X,%X\r\n", spi_command, spi_arg1);
    984     uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
    985   }*/
    986 
    987   // clear receive buffer, reuse as send buffer
    988   memset(spi_buf, 0, 8);
    989 
    990   // execute power state command
    991   if (spi_command == 'p') {
    992     // toggle system power and/or reset imx
    993     if (spi_arg1 == 1) {
    994       turn_som_power_off();
    995     }
    996     if (spi_arg1 == 2) {
    997       turn_som_power_on();
    998     }
    999     if (spi_arg1 == 3) {
   1000       reset_som();
   1001     }
   1002     if (spi_arg1 == 4) {
   1003       turn_aux_power_off();
   1004     }
   1005     if (spi_arg1 == 5) {
   1006       turn_aux_power_on();
   1007     }
   1008 
   1009     spi_buf[0] = som_is_powered;
   1010   }
   1011   // return firmware version and api info
   1012   else if (spi_command == 'f') {
   1013     if(spi_arg1 == 0) {
   1014       memcpy(spi_buf, FW_STRING1, 8);
   1015     }
   1016     else if(spi_arg1 == 1) {
   1017       memcpy(spi_buf, FW_STRING2, 8);
   1018     }
   1019     else {
   1020       memcpy(spi_buf, FW_STRING3, 8);
   1021     }
   1022   }
   1023   // execute status query command
   1024   else if (spi_command == 'q') {
   1025     uint8_t percentage = (uint8_t)capacity_percentage;
   1026     // if (!reached_full_charge) {
   1027     //   percentage = 255;
   1028     // }
   1029     uint16_t voltsInt = (uint16_t)(volts*1000.0);
   1030     uint16_t currentInt = (uint16_t)(current*1000.0);
   1031 
   1032     spi_buf[0] = (uint8_t)voltsInt;
   1033     spi_buf[1] = (uint8_t)(voltsInt >> 8);
   1034     spi_buf[2] = (uint8_t)currentInt;
   1035     spi_buf[3] = (uint8_t)(currentInt >> 8);
   1036     spi_buf[4] = (uint8_t)percentage;
   1037     spi_buf[5] = (uint8_t)state;
   1038     //spi_buf[6] = bitfield of power power rails
   1039   }
   1040   // get cell voltage
   1041   else if (spi_command == 'v') {
   1042     uint16_t volts = 0;
   1043     uint8_t cell1 = 0;
   1044 
   1045     if (spi_arg1 == 1) {
   1046       cell1 = 4;
   1047     }
   1048 
   1049     for(uint8_t c = 0; c < 4; c++) {
   1050       volts = cells_v[c + cell1]*1000.0;
   1051       spi_buf[c*2] = (uint8_t)volts;
   1052       spi_buf[(c*2)+1] = (uint8_t)(volts >> 8);
   1053     }
   1054   }
   1055   // get calculated capacity
   1056   else if (spi_command == 'c') {
   1057     uint16_t cap_accu = (uint16_t) capacity_accu_ampsecs / 3.6;
   1058     uint16_t cap_min = (uint16_t) capacity_min_ampsecs / 3.6;
   1059     uint16_t cap_max = (uint16_t) capacity_max_ampsecs / 3.6;
   1060 
   1061     spi_buf[0] = (uint8_t) cap_accu;
   1062     spi_buf[1] = (uint8_t) (cap_accu >> 8);
   1063     spi_buf[2] = (uint8_t) cap_min;
   1064     spi_buf[3] = (uint8_t) (cap_min >> 8);
   1065     spi_buf[4] = (uint8_t) cap_max;
   1066     spi_buf[5] = (uint8_t) (cap_max >> 8);
   1067   }
   1068   // turn reporting to i.MX on or off
   1069   else if (spi_command == 'u') {
   1070     if (spi_arg1 == 1) {
   1071       // turn i.MX UART output on
   1072       imx_uart_enabled = true;
   1073       LPC_IOCON->PIO1_13 |= 0x3;
   1074     } else {
   1075       // turn i.MX UART output off
   1076       imx_uart_enabled = false;
   1077       LPC_IOCON->PIO1_13 &= ~0x07;
   1078     }
   1079   }
   1080 
   1081   /*if (imx_uart_enabled) {
   1082     sprintf(uartBuffer, "spi:res: %X %X %X %X %X %X %X %X\r\n",
   1083             spi_buf[0], spi_buf[1], spi_buf[2], spi_buf[3],
   1084             spi_buf[4], spi_buf[5], spi_buf[6], spi_buf[7]);
   1085     uartSend((uint8_t*)uartBuffer, strlen(uartBuffer));
   1086   }*/
   1087 
   1088   // Host must wait while the LPC prepares response buffer
   1089   // If host does not read 8 bytes the previous response buffer will be stuck in here.
   1090   for (uint8_t i = 0; i < SSP0_FIFOSIZE; i++)
   1091     LPC_SSP0->DR = spi_buf[i];
   1092 
   1093   // Clear RX FIFO
   1094   for (uint8_t i = 0; i < SSP0_FIFOSIZE; i++)
   1095     spi_buf[i] = LPC_SSP0->DR;
   1096 
   1097   spi_cmd_state = ST_EXPECT_MAGIC;
   1098   spi_command = 0;
   1099   spi_arg1 = 0;
   1100 
   1101   return;
   1102 }
   1103 
   1104 void calculate_capacity_percentage() {
   1105   if (capacity_accu_ampsecs <= capacity_min_ampsecs) {
   1106     capacity_percentage = 0;
   1107   } else if (capacity_accu_ampsecs >= capacity_max_ampsecs) {
   1108     capacity_percentage = 100;
   1109   } else {
   1110     capacity_percentage = (int)(100.0*((float)(capacity_accu_ampsecs - capacity_min_ampsecs)) / (float)(capacity_max_ampsecs - capacity_min_ampsecs));
   1111   }
   1112 }
   1113 
   1114 void WDT_IRQHandler(void) {
   1115   // Disable WDT interrupt
   1116   NVIC_DisableIRQ(WDT_IRQn);
   1117   NVIC_ClearPendingIRQ(WDT_IRQn);
   1118 }
   1119 
   1120 // WARNING: take care not to overflow TC (11786 * secs)
   1121 void deep_sleep_seconds(int secs) {
   1122   // brownout reset seems to interfere with deep sleep
   1123   brownout_disable();
   1124 
   1125   // make WWDTINT wake the LPC up from sleep
   1126   // STARTERP1 WWDTINT bit 12
   1127   LPC_SYSCON->STARTERP1 |= (1 << 12);
   1128 
   1129   NVIC_DisableIRQ(WDT_IRQn);
   1130 
   1131   // Configure watchdog timer to wake us up
   1132   LPC_SYSCON->SYSAHBCLKCTRL |= (1<<15); // WWDT enable
   1133 
   1134   // 9375Hz??
   1135   LPC_SYSCON->WDTOSCCTRL =
   1136     (1<<5) | // FREQSEL 0.6MHz
   1137     31; // DIVSEL 64 (31+1)*2
   1138 
   1139   // Power configuration register
   1140   LPC_SYSCON->PDRUNCFG &= ~(1<<6); // WDTOSC_PD disable (power down disable)
   1141   LPC_SYSCON->PDSLEEPCFG &= ~(1<<6); // WDTOSC_PD disable (power down disable)
   1142   LPC_SYSCON->PDAWAKECFG = LPC_SYSCON->PDRUNCFG; // when waking up, power up the default blocks
   1143 
   1144   LPC_WWDT->CLKSEL = 1; // WDOSC
   1145 
   1146   //LPC_WWDT->TC = 0xffff/5; // timeout counter, ~5 seconds
   1147   // FIXME isn't that 1.39s? (0xffff/5.0937/5.0)
   1148   // no, apparently it is 5.56s (x4)
   1149 
   1150   LPC_WWDT->TC = 11786 * secs; // timeout counter, ~1 second
   1151 
   1152   LPC_WWDT->MOD = 0;
   1153   //LPC_WWDT->MOD |= WWDT_WDMOD_WDRESET; // WDRESET (watchdog resets system)
   1154   LPC_WWDT->MOD |= WWDT_WDMOD_WDEN; // watchdog enable
   1155 
   1156   // counter value that triggers interrupt
   1157   LPC_WWDT->WARNINT = 0;
   1158 
   1159   // need to feed WD once to apply WDMOD values
   1160   watchdog_feed();
   1161 
   1162   // NVIC exception 25 (WWDT) interrupt source:
   1163   // ISER SETENA bit 25 -> 0xE000E100
   1164   NVIC_ClearPendingIRQ(WDT_IRQn);
   1165   NVIC_EnableIRQ(WDT_IRQn);
   1166 
   1167   // Go to deep sleep mode
   1168   LPC_PMU->PCON = 1<<11; // clear DPDFLAG
   1169   LPC_PMU->PCON = 1; // select deep power down mode
   1170   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
   1171   __WFI();
   1172 
   1173   NVIC_DisableIRQ(WDT_IRQn);
   1174   LPC_WWDT->MOD = 0;
   1175 
   1176   brownout_enable();
   1177 }
   1178 
   1179 int main(void) {
   1180   boardInit();
   1181   reset_discharge_bits();
   1182 
   1183   state = ST_CHARGE;
   1184   cycles_in_state = 0;
   1185   cycles_uptime = 0;
   1186   powersave_holdoff_cycles = POWERSAVE_HOLDOFF_CYCLES;
   1187 
   1188   last_second = delayGetSecondsActive();
   1189 
   1190   int next_state = state;
   1191 
   1192   while (1) {
   1193     // algorithm idea:
   1194     // - check all cell voltages
   1195     // - charging state: if a cell voltage is higher than nominal, do not charge, enter balancing state
   1196     //   - option 1: suspend_charger (CONFIG_BITS_REG, 0x14, bit [5])
   1197     //   - option 2: charge_current_setting (0x1A) to a low level
   1198     // - charging state: if cell voltages are on average lower than nominal, enter charging state
   1199     // - balancing state: discharge the cell with highest voltage (enable discharge switch)
   1200     // - idle state: if one cell voltages is below undervoltage threshold, enter alert state
   1201     // - alert state: constantly signal undervoltage alert to host. after timeout, switch off 5v rail and 3v3 rail
   1202 
   1203     // charge current 2: ~0.2A
   1204 
   1205     if (REFORM_MOTHERBOARD_REV >= REFORM_MBREV_25_R2) {
   1206       measure_and_accumulate_current_v25();
   1207     } else {
   1208       measure_and_accumulate_current();
   1209     }
   1210     measure_cell_voltages_and_control_discharge();
   1211     calculate_capacity_percentage();
   1212 
   1213     if (state == ST_CHARGE) {
   1214       reset_discharge_bits();
   1215 
   1216       if (force_sleep) {
   1217         // debug sleeping
   1218         if (powersave_holdoff_cycles <= 0) {
   1219           next_state = ST_POWERSAVE;
   1220           cycles_in_state = 0;
   1221         }
   1222       } else if (num_missing_cells >= 1) {
   1223         if (cycles_in_state > 5) {
   1224           missing_reason = num_missing_cells;
   1225           // if cells were unplugged, we don't know the capacity anymore.
   1226           reached_full_charge = 0;
   1227           next_state = ST_MISSING;
   1228           cycles_in_state = 0;
   1229         }
   1230       }
   1231       else if (num_missing_cells == 0 && current >= 0 && num_undervolted_cells > 0) {
   1232         // when transitioning to undervoltage, we assume we reached the bottom
   1233         // of usable capacity, so record it
   1234         // but only if we reached top charge once, or our counter will
   1235         // be off.
   1236         if (cycles_in_state > 5) {
   1237           if (reached_full_charge > 0) {
   1238             capacity_min_ampsecs = capacity_accu_ampsecs;
   1239           }
   1240           next_state = ST_UNDERVOLTED;
   1241           cycles_in_state = 0;
   1242         }
   1243       }
   1244       else if (current < 0.01 && current > FULLY_CHARGED_CURRENT && num_fully_charged_cells >= 8) {
   1245         if (cycles_in_state > 5) {
   1246           // when transitioning to fully charged, we assume that we're at max capacity
   1247           capacity_accu_ampsecs = capacity_max_ampsecs;
   1248           next_state = ST_FULLY_CHARGED;
   1249           reached_full_charge = 1;
   1250           cycles_in_state = 0;
   1251         }
   1252       }
   1253       else if (num_overvolted_cells > 0) {
   1254         if (cycles_in_state > 2) {
   1255           // some cool-off time
   1256           next_state = ST_OVERVOLTED;
   1257           cycles_in_state = 0;
   1258         }
   1259       }
   1260       else if (current < 0.05 && current >= 0 && !som_is_powered) {
   1261         // if not charging and the system is off, we can sleep regularly to save power
   1262         if (powersave_holdoff_cycles <= 0) {
   1263           next_state = ST_POWERSAVE;
   1264           cycles_in_state = 0;
   1265         }
   1266       }
   1267     }
   1268     else if (state == ST_UNDERVOLTED) {
   1269       reset_discharge_bits();
   1270       deep_sleep_seconds(POWERSAVE_SLEEP_SECONDS);
   1271 
   1272       // TODO: find safe heuristic. here we turn off if half
   1273       // of the cells are undervolted and there's no wall power.
   1274       if (volts < WALLPOWER_DETECT_VOLTAGE && (num_undervolted_critical_cells >= 1 || num_undervolted_cells >= 4)) {
   1275         turn_som_power_off();
   1276       }
   1277 
   1278       next_state = ST_CHARGE;
   1279       cycles_in_state = 0;
   1280     }
   1281     else if (state == ST_OVERVOLTED) {
   1282       discharge_overvolted_cells();
   1283 
   1284       // discharge
   1285       if (cycles_in_state > 3 && (num_overvolted_cells==0 || num_undervolted_cells>0)) {
   1286         reset_discharge_bits();
   1287 
   1288         next_state = ST_COOLDOWN;
   1289         cycles_in_state = 0;
   1290       } else if (cycles_in_state > 5) {
   1291         // don't discharge for more than 5 cycles
   1292         next_state = ST_COOLDOWN;
   1293         cycles_in_state = 0;
   1294       }
   1295     }
   1296     else if (state == ST_COOLDOWN) {
   1297       // avoid overheating the resistors
   1298       reset_discharge_bits();
   1299 
   1300       if (cycles_in_state > 3) {
   1301         next_state = ST_CHARGE;
   1302         cycles_in_state = 0;
   1303       }
   1304     }
   1305     else if (state == ST_MISSING) {
   1306       reset_discharge_bits();
   1307 
   1308       if (cycles_in_state > 5) {
   1309         if (num_missing_cells == 0) {
   1310           next_state = ST_CHARGE;
   1311           cycles_in_state = 0;
   1312         }
   1313       }
   1314     }
   1315     else if (state == ST_FULLY_CHARGED) {
   1316       reset_discharge_bits();
   1317 
   1318       if (cycles_in_state > 5) {
   1319         // if none of the cells are fully charged anymore, allow charging again
   1320         if (num_fully_charged_cells < 1) {
   1321           next_state = ST_CHARGE;
   1322           cycles_in_state = 0;
   1323         }
   1324         else if (num_overvolted_cells > 0) {
   1325           next_state = ST_OVERVOLTED;
   1326           cycles_in_state = 0;
   1327         }
   1328       }
   1329     }
   1330     else if (state == ST_POWERSAVE) {
   1331       deep_sleep_seconds(POWERSAVE_SLEEP_SECONDS);
   1332       next_state = ST_CHARGE;
   1333       cycles_in_state = 0;
   1334     }
   1335 
   1336     // handle keyboard commands
   1337     // this also resets powersave holdoff counter
   1338     handle_commands();
   1339 
   1340     //TODO: if chip select high
   1341     handle_spi_commands();
   1342 
   1343     if (state == ST_POWERSAVE || state == ST_UNDERVOLTED) {
   1344       cur_second += POWERSAVE_SLEEP_SECONDS;
   1345     } else {
   1346       cur_second = delayGetSecondsActive();
   1347     }
   1348 
   1349     if (last_second != cur_second) {
   1350       if (cur_second-last_second<10) {
   1351         // prevent rollovers
   1352         cycles_in_state += cur_second-last_second;
   1353         cycles_uptime += cur_second-last_second;
   1354         if (cycles_in_state < 0) cycles_in_state = 0;
   1355         if (cycles_uptime < 0) cycles_uptime = 0;
   1356       }
   1357       last_second = cur_second;
   1358 
   1359       if (powersave_holdoff_cycles > 0) {
   1360         powersave_holdoff_cycles--;
   1361       }
   1362     }
   1363 
   1364     state = next_state;
   1365   }
   1366 }
   1367 
   1368 #endif