diff --git a/Documentation/pycardium/utime.rst b/Documentation/pycardium/utime.rst index 3fdbf4eff26f8e55f739d6039d4a09d8a5732f20..7b62db053c187880bd0a25451d6ec90a52d6b4ca 100644 --- a/Documentation/pycardium/utime.rst +++ b/Documentation/pycardium/utime.rst @@ -25,16 +25,18 @@ alarm. .. py:function:: time() - Return the current timestamp in seconds since 2000-01-01 00:00. + Return the current timestamp in seconds since 2000-01-01 00:00 in + the local timezone. .. py:function:: set_time(secs) - Sets the time to ``secs`` seconds since 2000-01-01 00:00. + Sets the time to ``secs`` seconds since 2000-01-01 00:00 in the local + timezone. .. py:function:: set_unix_time(secs) Sets the time to ``secs`` seconds since 1970-01-01 00:00 UTC. - This corresponds a regular Unix timestamp which can be obtained + This corresponds to a regular Unix timestamp which can be obtained by running ``date +%s`` in a command line or ``int(time.time())`` in Python. diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h index 87b24e8975dff763119aa4ffe6ce336e1cb75212..69899aaeb9b7e52a848b61b3e9451dd75f14708e 100644 --- a/epicardium/epicardium.h +++ b/epicardium/epicardium.h @@ -32,6 +32,7 @@ typedef _Bool bool; #define API_SYSTEM_EXIT 0x1 #define API_SYSTEM_EXEC 0x2 #define API_SYSTEM_RESET 0x3 +#define API_BATTERY_VOLTAGE 0x4 #define API_INTERRUPT_ENABLE 0xA #define API_INTERRUPT_DISABLE 0xB @@ -210,6 +211,16 @@ API(API_SYSTEM_EXEC, int __epic_exec(char *name)); */ API(API_SYSTEM_RESET, void epic_system_reset(void)); +/** + * Battery Voltage + * =============== + */ + +/** + * Read the current battery voltage. + */ +API(API_BATTERY_VOLTAGE, int epic_read_battery_voltage(float *result)); + /** * UART/Serial Interface * ===================== diff --git a/epicardium/modules/modules.h b/epicardium/modules/modules.h index 3555ab52e7c8ed66707459f2b0b4c90bf123dadd..484e951f5c9b9c1e98b181717d4bf8740e1767b5 100644 --- a/epicardium/modules/modules.h +++ b/epicardium/modules/modules.h @@ -32,11 +32,32 @@ void vLedTask(void *pvParameters); int personal_state_enabled(); /* ---------- PMIC --------------------------------------------------------- */ -/* In 1/10s */ -#define PMIC_PRESS_SLEEP 20 -#define PMIC_PRESS_POWEROFF 40 void vPmicTask(void *pvParameters); +/* Critical battery voltage */ +#define BATTERY_CRITICAL 3.40f + +enum pmic_amux_signal { + PMIC_AMUX_CHGIN_U = 0x1, + PMIC_AMUX_CHGIN_I = 0x2, + PMIC_AMUX_BATT_U = 0x3, + PMIC_AMUX_BATT_CHG_I = 0x4, + PMIC_AMUX_BATT_DIS_I = 0x5, + PMIC_AMUX_BATT_NULL_I = 0x6, + PMIC_AMUX_THM_U = 0x7, + PMIC_AMUX_TBIAS_U = 0x8, + PMIC_AMUX_AGND_U = 0x9, + PMIC_AMUX_SYS_U = 0xA, + _PMIC_AMUX_MAX, +}; + +/* + * Read a value from the PMIC's AMUX. The result is already converted into its + * proper unit. See the MAX77650 datasheet for details. + */ +int pmic_read_amux(enum pmic_amux_signal sig, float *result); + + /* ---------- BLE ---------------------------------------------------------- */ void vBleTask(void *pvParameters); bool ble_shall_start(void); diff --git a/epicardium/modules/pmic.c b/epicardium/modules/pmic.c index c02691dc25350723b04bd0059183bcf0522396ae..48a311c983cedd2ff2b1b71a4b4d4ddd9583336c 100644 --- a/epicardium/modules/pmic.c +++ b/epicardium/modules/pmic.c @@ -1,41 +1,263 @@ -#include <stdio.h> +#include "epicardium.h" +#include "modules/modules.h" +#include "modules/log.h" -#include "max32665.h" -#include "gcr_regs.h" +#include "card10.h" #include "pmic.h" #include "MAX77650-Arduino-Library.h" -#include "card10.h" + +#include "max32665.h" +#include "mxc_sys.h" +#include "mxc_pins.h" +#include "adc.h" #include "FreeRTOS.h" #include "task.h" +#include "timers.h" -#include "epicardium.h" -#include "modules.h" -#include "modules/log.h" +#include <stdio.h> /* Task ID for the pmic handler */ static TaskHandle_t pmic_task_id = NULL; +enum { + /* An irq was received, probably the power button */ + PMIC_NOTIFY_IRQ = 1, + /* The timer has ticked and we should check the battery voltage again */ + PMIC_NOTIFY_MONITOR = 2, +}; + void pmic_interrupt_callback(void *_) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (pmic_task_id != NULL) { - vTaskNotifyGiveFromISR(pmic_task_id, &xHigherPriorityTaskWoken); + xTaskNotifyFromISR( + pmic_task_id, + PMIC_NOTIFY_IRQ, + eSetBits, + &xHigherPriorityTaskWoken + ); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } +int pmic_read_amux(enum pmic_amux_signal sig, float *result) +{ + int ret = 0; + + if (sig > _PMIC_AMUX_MAX) { + return -EINVAL; + } + + ret = hwlock_acquire(HWLOCK_ADC, pdMS_TO_TICKS(100)); + if (ret < 0) { + return ret; + } + ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100)); + if (ret < 0) { + return ret; + } + + /* Select the correct channel for this measurement. */ + MAX77650_setMUX_SEL(sig); + + /* + * According to the datasheet, the voltage will stabilize within 0.3us. + * Just to be sure, we'll wait a little longer. In the meantime, + * release the I2C mutex. + */ + hwlock_release(HWLOCK_I2C); + vTaskDelay(pdMS_TO_TICKS(5)); + ret = hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(100)); + if (ret < 0) { + return ret; + } + + uint16_t adc_data; + ADC_StartConvert(ADC_CH_0, 0, 0); + ADC_GetData(&adc_data); + + /* Turn MUX back to neutral so it does not waste power. */ + MAX77650_setMUX_SEL(sig); + + /* Convert ADC measurement to SI Volts */ + float adc_voltage = (float)adc_data / 1023.0f * 1.22f; + + /* + * Convert value according to PMIC formulas (Table 7) + */ + switch (sig) { + case PMIC_AMUX_CHGIN_U: + *result = adc_voltage / 0.167f; + break; + case PMIC_AMUX_CHGIN_I: + *result = adc_voltage / 2.632f; + break; + case PMIC_AMUX_BATT_U: + *result = adc_voltage / 0.272f; + break; + case PMIC_AMUX_BATT_CHG_I: + *result = adc_voltage / 1.25f; + break; + case PMIC_AMUX_BATT_NULL_I: + case PMIC_AMUX_THM_U: + case PMIC_AMUX_TBIAS_U: + case PMIC_AMUX_AGND_U: + *result = adc_voltage; + break; + case PMIC_AMUX_SYS_U: + *result = adc_voltage / 0.26f; + break; + default: + ret = -EINVAL; + } + + hwlock_release(HWLOCK_I2C); + hwlock_release(HWLOCK_ADC); + return ret; +} + +/* + * Read the interrupt flag register and handle all interrupts which the PMIC has + * sent. In most cases this will be the buttons. + */ +static void +pmic_poll_interrupts(TickType_t *button_start_tick, TickType_t duration) +{ + while (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(500)) < 0) { + LOG_WARN("pmic", "Failed to acquire I2C. Retrying ..."); + xTaskNotify(pmic_task_id, PMIC_NOTIFY_IRQ, eSetBits); + return; + } + + uint8_t int_flag = MAX77650_getINT_GLBL(); + hwlock_release(HWLOCK_I2C); + + if (int_flag & MAX77650_INT_nEN_F) { + /* Button was pressed */ + *button_start_tick = xTaskGetTickCount(); + } + if (int_flag & MAX77650_INT_nEN_R) { + /* Button was released */ + *button_start_tick = 0; + if (duration < pdMS_TO_TICKS(400)) { + return_to_menu(); + } else { + LOG_WARN("pmic", "Resetting ..."); + card10_reset(); + } + } + + /* TODO: Remove when all interrupts are handled */ + if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) { + LOG_WARN("pmic", "Unhandled PMIC Interrupt: %x", int_flag); + } +} + +__attribute__((noreturn)) static void pmic_die(float u_batt) +{ + /* Stop core 1 */ + core1_stop(); + + /* Grab the screen */ + disp_forcelock(); + + /* Draw an error screen */ + epic_disp_clear(0x0000); + + epic_disp_print(0, 0, " Battery", 0xffff, 0x0000); + epic_disp_print(0, 20, " critical", 0xffff, 0x0000); + epic_disp_print(0, 40, " !!!!", 0xffff, 0x0000); + epic_disp_update(); + + /* Vibrate violently */ + epic_vibra_set(true); + + /* Wait a bit */ + for (int i = 0; i < 50000000; i++) + __NOP(); + + LOG_WARN("pmic", "Poweroff"); + MAX77650_setSFT_RST(0x2); + + while (1) + __WFI(); +} + +/* + * Check the battery voltage. If it drops too low, turn card10 off. + */ +static void pmic_check_battery() +{ + float u_batt; + int res; + + res = pmic_read_amux(PMIC_AMUX_BATT_U, &u_batt); + if (res < 0) { + LOG_ERR("pmic", "Failed reading battery voltage: %d", res); + return; + } + + LOG_DEBUG( + "pmic", + "Battery is at %d.%03d V", + (int)u_batt, + (int)(u_batt * 1000.0) % 1000 + ); + + if (u_batt < BATTERY_CRITICAL) { + pmic_die(u_batt); + } +} + +/* + * API-call for battery voltage + */ +int epic_read_battery_voltage(float *result) +{ + return pmic_read_amux(PMIC_AMUX_BATT_U, result); +} + +static StaticTimer_t pmic_timer_data; +static void vPmicTimerCb(TimerHandle_t xTimer) +{ + /* + * Tell the PMIC task to check the battery again. + */ + xTaskNotify(pmic_task_id, PMIC_NOTIFY_MONITOR, eSetBits); +} + void vPmicTask(void *pvParameters) { pmic_task_id = xTaskGetCurrentTaskHandle(); + ADC_Init(0x9, NULL); + GPIO_Config(&gpio_cfg_adc0); + TickType_t button_start_tick = 0; + pmic_check_battery(); + + TimerHandle_t pmic_timer = xTimerCreateStatic( + "PMIC Timer", + pdMS_TO_TICKS(60 * 1000), + pdTRUE, + NULL, + vPmicTimerCb, + &pmic_timer_data + ); + if (pmic_timer == NULL) { + LOG_CRIT("pmic", "Could not create timer."); + vTaskDelay(portMAX_DELAY); + } + xTimerStart(pmic_timer, 0); + while (1) { + uint32_t reason; if (button_start_tick == 0) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + reason = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } else { - ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)); + reason = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100)); } TickType_t duration = xTaskGetTickCount() - button_start_tick; @@ -45,36 +267,12 @@ void vPmicTask(void *pvParameters) MAX77650_setSFT_RST(0x2); } - while (hwlock_acquire(HWLOCK_I2C, pdMS_TO_TICKS(500)) < 0) { - LOG_WARN("pmic", "Failed to acquire I2C. Retrying ..."); - vTaskDelay(pdMS_TO_TICKS(500)); - } - - uint8_t int_flag = MAX77650_getINT_GLBL(); - hwlock_release(HWLOCK_I2C); - - if (int_flag & MAX77650_INT_nEN_F) { - /* Button was pressed */ - button_start_tick = xTaskGetTickCount(); - } - if (int_flag & MAX77650_INT_nEN_R) { - /* Button was released */ - button_start_tick = 0; - if (duration < pdMS_TO_TICKS(400)) { - return_to_menu(); - } else { - LOG_WARN("pmic", "Resetting ..."); - card10_reset(); - } + if (reason & PMIC_NOTIFY_IRQ) { + pmic_poll_interrupts(&button_start_tick, duration); } - /* TODO: Remove when all interrupts are handled */ - if (int_flag & ~(MAX77650_INT_nEN_F | MAX77650_INT_nEN_R)) { - LOG_WARN( - "pmic", - "Unhandled PMIC Interrupt: %x", - int_flag - ); + if (reason & PMIC_NOTIFY_MONITOR) { + pmic_check_battery(); } } } diff --git a/pycardium/modules/os.c b/pycardium/modules/os.c index 385cc9de659270c1d21764f3c956a2f3da9ec16a..c017e417e75a0dea115af340bb1b5a2b85c672e9 100644 --- a/pycardium/modules/os.c +++ b/pycardium/modules/os.c @@ -123,6 +123,18 @@ static mp_obj_t mp_os_rename(mp_obj_t py_oldp, mp_obj_t py_newp) } static MP_DEFINE_CONST_FUN_OBJ_2(rename_obj, mp_os_rename); +static mp_obj_t mp_os_read_battery() +{ + float result; + int res = epic_read_battery_voltage(&result); + if (res < 0) { + mp_raise_OSError(-res); + } + + return mp_obj_new_float(result); +} +static MP_DEFINE_CONST_FUN_OBJ_0(read_battery_obj, mp_os_read_battery); + static const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_os) }, { MP_ROM_QSTR(MP_QSTR_exit), MP_ROM_PTR(&exit_obj) }, @@ -132,6 +144,7 @@ static const mp_rom_map_elem_t os_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&unlink_obj) }, { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&mkdir_obj) }, { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&rename_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_battery), MP_ROM_PTR(&read_battery_obj) }, }; static MP_DEFINE_CONST_DICT(os_module_globals, os_module_globals_table); diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h index acf598f1b82c81e31722934c881d7d1a3217d9b3..f78afd25ed5aba9b38ef3b29d845ef5d573e47a6 100644 --- a/pycardium/modules/qstrdefs.h +++ b/pycardium/modules/qstrdefs.h @@ -105,6 +105,7 @@ Q(listdir) Q(unlink) Q(mkdir) Q(rename) +Q(read_battery) /* gpio */ Q(gpio) diff --git a/pycardium/modules/utime.c b/pycardium/modules/utime.c index a3a1173f67ed8f8f5b291be96f333dad0aa5d796..ef9b3da65d2af7a55799ec879857469117859291 100644 --- a/pycardium/modules/utime.c +++ b/pycardium/modules/utime.c @@ -19,8 +19,8 @@ static mp_obj_t time_set_time(mp_obj_t secs) { - uint64_t timestamp = - mp_obj_get_int(secs) * 1000ULL + EPOCH_OFFSET * 1000ULL; + uint64_t timestamp = mp_obj_get_int(secs) * 1000ULL + + EPOCH_OFFSET * 1000ULL + TZONE_OFFSET; epic_rtc_set_milliseconds(timestamp); return mp_const_none; }