diff --git a/platform/platform_capabilities.py b/platform/platform_capabilities.py index 23b86d1fe..2f5744c5f 100644 --- a/platform/platform_capabilities.py +++ b/platform/platform_capabilities.py @@ -184,7 +184,7 @@ { 'HAS_ACCESSORY_CONNECTOR', 'HAS_APP_GLANCES', - # 'HAS_BUILTIN_HRM', -- TODO: disabled because driver was removed + # 'HAS_BUILTIN_HRM', -- disabled by default 'HAS_CORE_NAVIGATION4', 'HAS_HEALTH_TRACKING', 'HAS_JAVASCRIPT', @@ -209,7 +209,7 @@ 'COMPOSITOR_USES_DMA', 'HAS_ACCESSORY_CONNECTOR', 'HAS_APP_GLANCES', - # 'HAS_BUILTIN_HRM', -- TODO: disabled because driver was removed + # 'HAS_BUILTIN_HRM', -- disabled by default 'HAS_CORE_NAVIGATION4', 'HAS_GLYPH_BITMAP_CACHING', 'HAS_HEALTH_TRACKING', @@ -323,6 +323,10 @@ def get_capability_dict(ctx, board): if ctx.env.NOJS: capabilities_of_board.discard('HAS_JAVASCRIPT') + if ctx.env.SILK_HR: + # Enable builtin HRM for Silk + capabilities_of_board.add('HAS_BUILTIN_HRM') + # End overrides section false_capabilities = master_capability_set - capabilities_of_board diff --git a/platform/wscript b/platform/wscript index 1b8dc66d2..ff800cff6 100644 --- a/platform/wscript +++ b/platform/wscript @@ -149,7 +149,12 @@ def has_touch(ctx): @conf def get_hrm(ctx): - return None + if is_robert(ctx): + return "AS7000" + elif is_silk(ctx): + return "AS7000" + else: + return None @conf diff --git a/src/fw/apps/system_apps/hrm_demo.c b/src/fw/apps/system_apps/hrm_demo.c new file mode 100644 index 000000000..cb05fc1e9 --- /dev/null +++ b/src/fw/apps/system_apps/hrm_demo.c @@ -0,0 +1,403 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "applib/app.h" +#include "applib/app_message/app_message.h" +#include "applib/ui/app_window_stack.h" +#include "applib/ui/text_layer.h" +#include "applib/ui/window.h" +#include "apps/system_app_ids.h" +#include "drivers/hrm/as7000.h" +#include "kernel/pbl_malloc.h" +#include "mfg/mfg_info.h" +#include "mfg/mfg_serials.h" +#include "process_state/app_state/app_state.h" +#include "services/common/hrm/hrm_manager.h" +#include "system/passert.h" + +#define BPM_STRING_LEN 10 + +typedef enum { + AppMessageKey_Status = 1, + + AppMessageKey_HeartRate = 10, + AppMessageKey_Confidence = 11, + AppMessageKey_Current = 12, + AppMessageKey_TIA = 13, + AppMessageKey_PPG = 14, + AppMessageKey_AccelData = 15, + AppMessageKey_SerialNumber = 16, + AppMessageKey_Model = 17, + AppMessageKey_HRMProtocolVersionMajor = 18, + AppMessageKey_HRMProtocolVersionMinor = 19, + AppMessageKey_HRMSoftwareVersionMajor = 20, + AppMessageKey_HRMSoftwareVersionMinor = 21, + AppMessageKey_HRMApplicationID = 22, + AppMessageKey_HRMHardwareRevision = 23, +} AppMessageKey; + +typedef enum { + AppStatus_Stopped = 0, + AppStatus_Enabled_1HZ = 1, +} AppStatus; + +typedef struct { + HRMSessionRef session; + EventServiceInfo hrm_event_info; + + Window window; + TextLayer bpm_text_layer; + TextLayer quality_text_layer; + + char bpm_string[BPM_STRING_LEN]; + + bool ready_to_send; + DictionaryIterator *out_iter; +} AppData; + +static char *prv_get_quality_string(HRMQuality quality) { + switch (quality) { + case HRMQuality_NoAccel: + return "No Accel Data"; + case HRMQuality_OffWrist: + return "Off Wrist"; + case HRMQuality_NoSignal: + return "No Signal"; + case HRMQuality_Worst: + return "Worst"; + case HRMQuality_Poor: + return "Poor"; + case HRMQuality_Acceptable: + return "Acceptable"; + case HRMQuality_Good: + return "Good"; + case HRMQuality_Excellent: + return "Excellent"; + } + WTF; +} + +static char *prv_translate_error(AppMessageResult result) { + switch (result) { + case APP_MSG_OK: + return "APP_MSG_OK"; + case APP_MSG_SEND_TIMEOUT: + return "APP_MSG_SEND_TIMEOUT"; + case APP_MSG_SEND_REJECTED: + return "APP_MSG_SEND_REJECTED"; + case APP_MSG_NOT_CONNECTED: + return "APP_MSG_NOT_CONNECTED"; + case APP_MSG_APP_NOT_RUNNING: + return "APP_MSG_APP_NOT_RUNNING"; + case APP_MSG_INVALID_ARGS: + return "APP_MSG_INVALID_ARGS"; + case APP_MSG_BUSY: + return "APP_MSG_BUSY"; + case APP_MSG_BUFFER_OVERFLOW: + return "APP_MSG_BUFFER_OVERFLOW"; + case APP_MSG_ALREADY_RELEASED: + return "APP_MSG_ALREADY_RELEASED"; + case APP_MSG_CALLBACK_ALREADY_REGISTERED: + return "APP_MSG_CALLBACK_ALREADY_REGISTERED"; + case APP_MSG_CALLBACK_NOT_REGISTERED: + return "APP_MSG_CALLBACK_NOT_REGISTERED"; + case APP_MSG_OUT_OF_MEMORY: + return "APP_MSG_OUT_OF_MEMORY"; + case APP_MSG_CLOSED: + return "APP_MSG_CLOSED"; + case APP_MSG_INTERNAL_ERROR: + return "APP_MSG_INTERNAL_ERROR"; + default: + return "UNKNOWN ERROR"; + } +} + +static void prv_send_msg(void) { + AppData *app_data = app_state_get_user_data(); + + AppMessageResult result = app_message_outbox_send(); + if (result == APP_MSG_OK) { + app_data->ready_to_send = false; + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Error sending message: %s", prv_translate_error(result)); + } +} + +static void prv_send_status_and_version(void) { + AppData *app_data = app_state_get_user_data(); + PBL_LOG(LOG_LEVEL_DEBUG, "Sending status and version to mobile app"); + + AppMessageResult result = app_message_outbox_begin(&app_data->out_iter); + if (result != APP_MSG_OK) { + PBL_LOG(LOG_LEVEL_DEBUG, "Failed to begin outbox - reason %i %s", result, + prv_translate_error(result)); + return; + } + + dict_write_uint8(app_data->out_iter, AppMessageKey_Status, AppStatus_Enabled_1HZ); + +#if CAPABILITY_HAS_BUILTIN_HRM + if (mfg_info_is_hrm_present()) { + AS7000InfoRecord hrm_info = {}; + as7000_get_version_info(HRM, &hrm_info); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMProtocolVersionMajor, + hrm_info.protocol_version_major); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMProtocolVersionMinor, + hrm_info.protocol_version_minor); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMSoftwareVersionMajor, + hrm_info.sw_version_major); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMSoftwareVersionMinor, + hrm_info.sw_version_minor); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMApplicationID, hrm_info.application_id); + dict_write_uint8(app_data->out_iter, AppMessageKey_HRMHardwareRevision, hrm_info.hw_revision); + } +#endif + + char serial_number_buffer[MFG_SERIAL_NUMBER_SIZE + 1]; + mfg_info_get_serialnumber(serial_number_buffer, sizeof(serial_number_buffer)); + dict_write_data(app_data->out_iter, AppMessageKey_SerialNumber, (uint8_t *)serial_number_buffer, + sizeof(serial_number_buffer)); + +#if IS_BIGBOARD + WatchInfoColor watch_color = WATCH_INFO_MODEL_UNKNOWN; +#else + WatchInfoColor watch_color = mfg_info_get_watch_color(); +#endif // IS_BIGBOARD + dict_write_uint32(app_data->out_iter, AppMessageKey_Model, watch_color); + + prv_send_msg(); +} + +static void prv_handle_hrm_data(PebbleEvent *e, void *context) { + AppData *app_data = app_state_get_user_data(); + + if (e->type == PEBBLE_HRM_EVENT) { + PebbleHRMEvent *hrm = &e->hrm; + + // Save HRMEventBPM data and send when we get the current into. + static uint8_t bpm = 0; + static uint8_t bpm_quality = 0; + static uint16_t led_current = 0; + + if (hrm->event_type == HRMEvent_BPM) { + snprintf(app_data->bpm_string, sizeof(app_data->bpm_string), "%" PRIu8 " BPM", hrm->bpm.bpm); + text_layer_set_text(&app_data->quality_text_layer, prv_get_quality_string(hrm->bpm.quality)); + layer_mark_dirty(&app_data->window.layer); + + bpm = hrm->bpm.bpm; + bpm_quality = hrm->bpm.quality; + } else if (hrm->event_type == HRMEvent_LEDCurrent) { + led_current = hrm->led.current_ua; + } else if (hrm->event_type == HRMEvent_Diagnostics) { + if (!app_data->ready_to_send) { + return; + } + + AppMessageResult result = app_message_outbox_begin(&app_data->out_iter); + PBL_ASSERTN(result == APP_MSG_OK); + + if (bpm) { + dict_write_uint8(app_data->out_iter, AppMessageKey_HeartRate, bpm); + dict_write_uint8(app_data->out_iter, AppMessageKey_Confidence, bpm_quality); + } + + if (led_current) { + dict_write_uint16(app_data->out_iter, AppMessageKey_Current, led_current); + } + + if (hrm->debug->ppg_data.num_samples) { + HRMPPGData *d = &hrm->debug->ppg_data; + dict_write_data(app_data->out_iter, AppMessageKey_TIA, (uint8_t *)d->tia, + d->num_samples * sizeof(d->tia[0])); + dict_write_data(app_data->out_iter, AppMessageKey_PPG, (uint8_t *)d->ppg, + d->num_samples * sizeof(d->ppg[0])); + } + + if (hrm->debug->ppg_data.tia[hrm->debug->ppg_data.num_samples - 1] == 0) { + PBL_LOG_COLOR(LOG_LEVEL_DEBUG, LOG_COLOR_CYAN, "last PPG TIA sample is 0!"); + } + + if (hrm->debug->ppg_data.num_samples != 20) { + PBL_LOG_COLOR(LOG_LEVEL_DEBUG, LOG_COLOR_CYAN, "Only got %" PRIu16 " samples!", + hrm->debug->ppg_data.num_samples); + } + + if (hrm->debug->accel_data.num_samples) { + HRMAccelData *d = &hrm->debug->accel_data; + dict_write_data(app_data->out_iter, AppMessageKey_AccelData, (uint8_t *)d->data, + d->num_samples * sizeof(d->data[0])); + } + + PBL_LOG(LOG_LEVEL_DEBUG, + "Sending message - bpm:%u quality:%u current:%u " + "ppg_readings:%u accel_readings %" PRIu32, + bpm, bpm_quality, led_current, hrm->debug->ppg_data.num_samples, + hrm->debug->accel_data.num_samples); + + led_current = bpm = bpm_quality = 0; + + prv_send_msg(); + } else if (hrm->event_type == HRMEvent_SubscriptionExpiring) { + PBL_LOG(LOG_LEVEL_INFO, "Got subscription expiring event"); + // Subscribe again if our subscription is expiring + const uint32_t update_time_s = 1; + app_data->session = sys_hrm_manager_app_subscribe(APP_ID_HRM_DEMO, update_time_s, + SECONDS_PER_HOUR, HRMFeature_BPM); + } + } +} + +static void prv_enable_hrm(void) { + AppData *app_data = app_state_get_user_data(); + + app_data->hrm_event_info = (EventServiceInfo){ + .type = PEBBLE_HRM_EVENT, + .handler = prv_handle_hrm_data, + }; + event_service_client_subscribe(&app_data->hrm_event_info); + + // TODO: Let the mobile app control this? + const uint32_t update_time_s = 1; + app_data->session = sys_hrm_manager_app_subscribe( + APP_ID_HRM_DEMO, update_time_s, SECONDS_PER_HOUR, + HRMFeature_BPM | HRMFeature_LEDCurrent | HRMFeature_Diagnostics); +} + +static void prv_disable_hrm(void) { + AppData *app_data = app_state_get_user_data(); + + event_service_client_unsubscribe(&app_data->hrm_event_info); + sys_hrm_manager_unsubscribe(app_data->session); +} + +static void prv_handle_mobile_status_request(AppStatus status) { + AppData *app_data = app_state_get_user_data(); + + if (status == AppStatus_Stopped) { + text_layer_set_text(&app_data->bpm_text_layer, "Paused"); + text_layer_set_text(&app_data->quality_text_layer, "Paused by mobile"); + prv_disable_hrm(); + } else { + app_data->bpm_string[0] = '\0'; + text_layer_set_text(&app_data->bpm_text_layer, app_data->bpm_string); + text_layer_set_text(&app_data->quality_text_layer, "Loading..."); + prv_enable_hrm(); + } +} + +static void prv_message_received_cb(DictionaryIterator *iterator, void *context) { + Tuple *status_tuple = dict_find(iterator, AppMessageKey_Status); + + if (status_tuple) { + prv_handle_mobile_status_request(status_tuple->value->uint8); + } +} + +static void prv_message_sent_cb(DictionaryIterator *iterator, void *context) { + AppData *app_data = app_state_get_user_data(); + + app_data->ready_to_send = true; +} + +static void prv_message_failed_cb(DictionaryIterator *iterator, AppMessageResult reason, + void *context) { + PBL_LOG(LOG_LEVEL_DEBUG, "Out message send failed - reason %i %s", reason, + prv_translate_error(reason)); + AppData *app_data = app_state_get_user_data(); + app_data->ready_to_send = true; +} + +static void prv_remote_notify_timer_cb(void *data) { prv_send_status_and_version(); } + +static void prv_init(void) { + AppData *app_data = app_malloc_check(sizeof(*app_data)); + *app_data = (AppData){ + .session = (HRMSessionRef)app_data, // Use app data as session ref + .ready_to_send = false, + }; + app_state_set_user_data(app_data); + + Window *window = &app_data->window; + window_init(window, ""); + window_set_fullscreen(window, true); + + GRect bounds = window->layer.bounds; + + bounds.origin.y += 40; + TextLayer *bpm_tl = &app_data->bpm_text_layer; + text_layer_init(bpm_tl, &bounds); + text_layer_set_font(bpm_tl, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); + text_layer_set_text_alignment(bpm_tl, GTextAlignmentCenter); + text_layer_set_text(bpm_tl, app_data->bpm_string); + layer_add_child(&window->layer, &bpm_tl->layer); + + bounds.origin.y += 35; + TextLayer *quality_tl = &app_data->quality_text_layer; + text_layer_init(quality_tl, &bounds); + text_layer_set_font(quality_tl, fonts_get_system_font(FONT_KEY_GOTHIC_18)); + text_layer_set_text_alignment(quality_tl, GTextAlignmentCenter); + text_layer_set_text(quality_tl, "Loading..."); + layer_add_child(&window->layer, &quality_tl->layer); + + const uint32_t inbox_size = 64; + const uint32_t outbox_size = 256; + AppMessageResult result = app_message_open(inbox_size, outbox_size); + if (result != APP_MSG_OK) { + PBL_LOG(LOG_LEVEL_ERROR, "Unable to open app message! %i %s", result, + prv_translate_error(result)); + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "Successfully opened app message"); + } + + if (!sys_hrm_manager_is_hrm_present()) { + text_layer_set_text(quality_tl, "No HRM Present"); + } else { + text_layer_set_text(quality_tl, "Loading..."); + prv_enable_hrm(); + } + + app_message_register_inbox_received(prv_message_received_cb); + app_message_register_outbox_sent(prv_message_sent_cb); + app_message_register_outbox_failed(prv_message_failed_cb); + + app_timer_register(1000, prv_remote_notify_timer_cb, NULL); + + app_window_stack_push(window, true); +} + +static void prv_deinit(void) { + AppData *app_data = app_state_get_user_data(); + sys_hrm_manager_unsubscribe(app_data->session); +} + +static void prv_main(void) { + prv_init(); + app_event_loop(); + prv_deinit(); +} + +const PebbleProcessMd *hrm_demo_get_app_info(void) { + static const PebbleProcessMdSystem s_hrm_demo_app_info = { + .name = "HRM Demo", + .common.uuid = {0xf8, 0x1b, 0x2a, 0xf8, 0x13, 0x0a, 0x11, 0xe6, 0x86, 0x9f, 0xa4, 0x5e, 0x60, + 0xb9, 0x77, 0x3d}, + .common.main_func = &prv_main, + }; + // Only show in launcher if HRM is present + return (sys_hrm_manager_is_hrm_present()) ? (const PebbleProcessMd *)&s_hrm_demo_app_info : NULL; +} diff --git a/src/fw/apps/system_apps/settings/settings_certifications.h b/src/fw/apps/system_apps/settings/settings_certifications.h index 418991d75..1c5231bc2 100644 --- a/src/fw/apps/system_apps/settings/settings_certifications.h +++ b/src/fw/apps/system_apps/settings/settings_certifications.h @@ -16,36 +16,35 @@ #pragma once -#include "mfg/mfg_info.h" - #include +#include "mfg/mfg_info.h" //! Which regulatory marks and/or IDs a given product should display. typedef struct RegulatoryFlags { -//! Australia Regulatory Compliance Mark - bool has_australia_rcm:1; -//! Canada IC ID - bool has_canada_ic:1; -//! China CMIIT ID - bool has_china_cmiit:1; -//! EU CE Mark - bool has_eu_ce:1; -//! EU WEEE Mark (wastebin with X) - bool has_eu_weee:1; -//! Japan TELEC (Telecom Engineering Center) [R] mark and ID -//! (Radio equipment conformity) - bool has_japan_telec_r:1; -//! TELEC mark [T] mark and ID (Terminal equipment conformity) - bool has_japan_telec_t:1; -//! Korea -//! - KCC mark -//! - Details window with KCC mark and KCC ID - bool has_korea_kcc:1; -//! Mexico NOM NYCE mark - bool has_mexico_nom_nyce:1; -//! USA FCC Mark and FCC ID - bool has_usa_fcc:1; + //! Australia Regulatory Compliance Mark + bool has_australia_rcm : 1; + //! Canada IC ID + bool has_canada_ic : 1; + //! China CMIIT ID + bool has_china_cmiit : 1; + //! EU CE Mark + bool has_eu_ce : 1; + //! EU WEEE Mark (wastebin with X) + bool has_eu_weee : 1; + //! Japan TELEC (Telecom Engineering Center) [R] mark and ID + //! (Radio equipment conformity) + bool has_japan_telec_r : 1; + //! TELEC mark [T] mark and ID (Terminal equipment conformity) + bool has_japan_telec_t : 1; + //! Korea + //! - KCC mark + //! - Details window with KCC mark and KCC ID + bool has_korea_kcc : 1; + //! Mexico NOM NYCE mark + bool has_mexico_nom_nyce : 1; + //! USA FCC Mark and FCC ID + bool has_usa_fcc : 1; } RegulatoryFlags; typedef struct CertificationIds { @@ -58,92 +57,84 @@ typedef struct CertificationIds { const char *usa_fcc_id; } CertificationIds; - -static const RegulatoryFlags s_regulatory_flags_fallback = { -}; +static const RegulatoryFlags s_regulatory_flags_fallback = {}; // Certifiation ID strings used for bigboards and such. static const CertificationIds s_certification_ids_fallback = { - .canada_ic_id = "XXXXXX-YYY", - .china_cmiit_id = "ABCDEFGHIJ", - .japan_telec_r_id = "XXX-YYYYYY", - .japan_telec_t_id = "D XX YYYY ZZZ", - .korea_kcc_id = "WWWW-XXX-YYY-ZZZ", - .mexico_ifetel_id = "RCPPEXXXX-YYYY", - .usa_fcc_id = "XXX-YYY", + .canada_ic_id = "XXXXXX-YYY", + .china_cmiit_id = "ABCDEFGHIJ", + .japan_telec_r_id = "XXX-YYYYYY", + .japan_telec_t_id = "D XX YYYY ZZZ", + .korea_kcc_id = "WWWW-XXX-YYY-ZZZ", + .mexico_ifetel_id = "RCPPEXXXX-YYYY", + .usa_fcc_id = "XXX-YYY", }; - static const RegulatoryFlags s_regulatory_flags_snowy = { - .has_canada_ic = true, - .has_china_cmiit = true, - .has_eu_ce = true, - .has_eu_weee = true, - .has_japan_telec_r = true, - .has_japan_telec_t = true, - .has_korea_kcc = true, - .has_usa_fcc = true, + .has_canada_ic = true, + .has_china_cmiit = true, + .has_eu_ce = true, + .has_eu_weee = true, + .has_japan_telec_r = true, + .has_japan_telec_t = true, + .has_korea_kcc = true, + .has_usa_fcc = true, }; static const CertificationIds s_certification_ids_snowy = { - .canada_ic_id = "10805A-501", - .china_cmiit_id = "2015DJ1504", - .japan_telec_r_id = "201-150104", - .japan_telec_t_id = "D 15 0015 201", - .korea_kcc_id = "MSIP-CRM-PEB-WQ3", - .usa_fcc_id = "RGQ-501", + .canada_ic_id = "10805A-501", + .china_cmiit_id = "2015DJ1504", + .japan_telec_r_id = "201-150104", + .japan_telec_t_id = "D 15 0015 201", + .korea_kcc_id = "MSIP-CRM-PEB-WQ3", + .usa_fcc_id = "RGQ-501", }; static const CertificationIds s_certification_ids_bobby = { - .canada_ic_id = "10805A-511", - .china_cmiit_id = "2015DJ3458", - .japan_telec_r_id = "201-150257", - .japan_telec_t_id = "D 15 0065 201", - .korea_kcc_id = "MSIP-CRM-PEB-WQ3", - .usa_fcc_id = "RGQ-511", + .canada_ic_id = "10805A-511", + .china_cmiit_id = "2015DJ3458", + .japan_telec_r_id = "201-150257", + .japan_telec_t_id = "D 15 0065 201", + .korea_kcc_id = "MSIP-CRM-PEB-WQ3", + .usa_fcc_id = "RGQ-511", }; static const RegulatoryFlags s_regulatory_flags_spalding = { - .has_canada_ic = true, - .has_eu_ce = true, - .has_eu_weee = true, - .has_usa_fcc = true, + .has_canada_ic = true, + .has_eu_ce = true, + .has_eu_weee = true, + .has_usa_fcc = true, }; static const CertificationIds s_certification_ids_spalding = { - .canada_ic_id = "10805A-601", - .usa_fcc_id = "RGQ-601", + .canada_ic_id = "10805A-601", + .usa_fcc_id = "RGQ-601", }; static const RegulatoryFlags s_regulatory_flags_silk = { - .has_australia_rcm = true, - .has_canada_ic = true, - .has_china_cmiit = true, - .has_eu_ce = true, - .has_eu_weee = true, - .has_japan_telec_r = true, - .has_mexico_nom_nyce = true, - .has_usa_fcc = true, + .has_australia_rcm = true, + .has_canada_ic = true, + .has_china_cmiit = true, + .has_eu_ce = true, + .has_eu_weee = true, + .has_japan_telec_r = true, + .has_mexico_nom_nyce = true, + .has_usa_fcc = true, }; -static const CertificationIds s_certification_ids_silk = { - .canada_ic_id = "10805A-1001", - .china_cmiit_id = "2016DJ4469", - .usa_fcc_id = "RGQ-1001", - .japan_telec_r_id = "201-160535", - .mexico_ifetel_id = "RCPPE1016-1161" -}; - -static const CertificationIds s_certification_ids_silk_hr = { - .canada_ic_id = "10805A-1002", - .china_cmiit_id = "2016DJ4931", - .usa_fcc_id = "RGQ-1002", - .japan_telec_r_id = "201-160558", - .mexico_ifetel_id = "RCPPE1016-1238" -}; +static const CertificationIds s_certification_ids_silk = {.canada_ic_id = "10805A-1001", + .china_cmiit_id = "2016DJ4469", + .usa_fcc_id = "RGQ-1001", + .japan_telec_r_id = "201-160535", + .mexico_ifetel_id = "RCPPE1016-1161"}; +static const CertificationIds s_certification_ids_silk_hr = {.canada_ic_id = "10805A-1002", + .china_cmiit_id = "2016DJ4931", + .usa_fcc_id = "RGQ-1002", + .japan_telec_r_id = "201-160558", + .mexico_ifetel_id = "RCPPE1016-1238"}; -static const RegulatoryFlags * prv_get_regulatory_flags(void) { +static const RegulatoryFlags *prv_get_regulatory_flags(void) { #if PLATFORM_SNOWY return &s_regulatory_flags_snowy; #elif PLATFORM_SPALDING @@ -156,30 +147,28 @@ static const RegulatoryFlags * prv_get_regulatory_flags(void) { } //! Don't call this function directly. Use the prv_get_*_id functions instead. -static const CertificationIds * prv_get_certification_ids(void) { +static const CertificationIds *prv_get_certification_ids(void) { #if defined(BOARD_SNOWY_S3) return &s_certification_ids_bobby; -#elif defined(BOARD_SNOWY_EVT) || defined(BOARD_SNOWY_EVT2) || \ - defined(BOARD_SNOWY_DVT) +#elif defined(BOARD_SNOWY_EVT) || defined(BOARD_SNOWY_EVT2) || defined(BOARD_SNOWY_DVT) return &s_certification_ids_snowy; #elif defined(BOARD_SPALDING) || defined(BOARD_SPALDING_EVT) return &s_certification_ids_spalding; + #elif PLATFORM_SILK && !defined(IS_BIGBOARD) && !defined(BOARD_ASTERIX_EVT1) -// TODO: remove force-false -// if (mfg_info_is_hrm_present()) { -// return &s_certification_ids_silk_hr; -// } else { + if (mfg_info_is_hrm_present()) { + return &s_certification_ids_silk_hr; + } else { return &s_certification_ids_silk; -// } + } #else return &s_certification_ids_fallback; #endif } -#define ID_GETTER(ID_KIND) \ - static const char * prv_get_##ID_KIND(void) { \ - return prv_get_certification_ids()->ID_KIND ?: \ - s_certification_ids_fallback.ID_KIND; \ +#define ID_GETTER(ID_KIND) \ + static const char *prv_get_##ID_KIND(void) { \ + return prv_get_certification_ids()->ID_KIND ?: s_certification_ids_fallback.ID_KIND; \ } ID_GETTER(canada_ic_id) diff --git a/src/fw/board/boards/board_robert.c b/src/fw/board/boards/board_robert.c index ce265e075..9912f92d2 100644 --- a/src/fw/board/boards/board_robert.c +++ b/src/fw/board/boards/board_robert.c @@ -15,10 +15,10 @@ */ #include "board/board.h" - #include "drivers/display/ice40lp/ice40lp_definitions.h" #include "drivers/exti.h" #include "drivers/flash/qspi_flash_definitions.h" +#include "drivers/hrm/as7000.h" #include "drivers/i2c_definitions.h" #include "drivers/pmic.h" #include "drivers/qspi_definitions.h" @@ -41,28 +41,28 @@ _Static_assert(DIALOG_SPI_DMA_PRIORITY < EXTI_PRIORITY, "Dialog SPI DMA priority static DMAControllerState s_dma1_state; static DMAController DMA1_DEVICE = { - .state = &s_dma1_state, - .periph = DMA1, - .rcc_bit = RCC_AHB1Periph_DMA1, + .state = &s_dma1_state, + .periph = DMA1, + .rcc_bit = RCC_AHB1Periph_DMA1, }; static DMAControllerState s_dma2_state; static DMAController DMA2_DEVICE = { - .state = &s_dma2_state, - .periph = DMA2, - .rcc_bit = RCC_AHB1Periph_DMA2, + .state = &s_dma2_state, + .periph = DMA2, + .rcc_bit = RCC_AHB1Periph_DMA2, }; // DMA Streams -CREATE_DMA_STREAM(1, 1); // DMA1_STREAM1_DEVICE - Debug UART RX -CREATE_DMA_STREAM(1, 2); // DMA1_STREAM2_DEVICE - Accessory UART RX -CREATE_DMA_STREAM(2, 0); // DMA2_STREAM0_DEVICE - Dialog SPI RX -CREATE_DMA_STREAM(2, 1); // DMA2_STREAM1_DEVICE - Dialog SPI TX -CREATE_DMA_STREAM(2, 2); // DMA2_STREAM2_DEVICE - Compositor DMA -CREATE_DMA_STREAM(2, 4); // DMA2_STREAM4_DEVICE - DFSDM -CREATE_DMA_STREAM(2, 5); // DMA2_STREAM5_DEVICE - ICE40LP TX -CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI +CREATE_DMA_STREAM(1, 1); // DMA1_STREAM1_DEVICE - Debug UART RX +CREATE_DMA_STREAM(1, 2); // DMA1_STREAM2_DEVICE - Accessory UART RX +CREATE_DMA_STREAM(2, 0); // DMA2_STREAM0_DEVICE - Dialog SPI RX +CREATE_DMA_STREAM(2, 1); // DMA2_STREAM1_DEVICE - Dialog SPI TX +CREATE_DMA_STREAM(2, 2); // DMA2_STREAM2_DEVICE - Compositor DMA +CREATE_DMA_STREAM(2, 4); // DMA2_STREAM4_DEVICE - DFSDM +CREATE_DMA_STREAM(2, 5); // DMA2_STREAM5_DEVICE - ICE40LP TX +CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI // DMA Requests // - On DMA1 we have "Debug UART RX" and "Accessory UART RX". The former is never used in a sealed @@ -77,182 +77,173 @@ CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI static DMARequestState s_dbg_uart_dma_request_state; static DMARequest DBG_UART_RX_DMA_REQUEST = { - .state = &s_dbg_uart_dma_request_state, - .stream = &DMA1_STREAM1_DEVICE, - .channel = 4, - .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts - .priority = DMARequestPriority_High, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_dbg_uart_dma_request_state, + .stream = &DMA1_STREAM1_DEVICE, + .channel = 4, + .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts + .priority = DMARequestPriority_High, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_accessory_uart_dma_request_state; static DMARequest ACCESSORY_UART_RX_DMA_REQUEST = { - .state = &s_accessory_uart_dma_request_state, - .stream = &DMA1_STREAM2_DEVICE, - .channel = 4, - .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts - .priority = DMARequestPriority_High, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_accessory_uart_dma_request_state, + .stream = &DMA1_STREAM2_DEVICE, + .channel = 4, + .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts + .priority = DMARequestPriority_High, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dialog_spi_rx_dma_request_state; static DMARequest DIALOG_SPI_RX_DMA_REQUEST = { - .state = &s_dialog_spi_rx_dma_request_state, - .stream = &DMA2_STREAM0_DEVICE, - .channel = 4, - .irq_priority = DIALOG_SPI_DMA_PRIORITY, - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_dialog_spi_rx_dma_request_state, + .stream = &DMA2_STREAM0_DEVICE, + .channel = 4, + .irq_priority = DIALOG_SPI_DMA_PRIORITY, + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dialog_spi_tx_dma_request_state; static DMARequest DIALOG_SPI_TX_DMA_REQUEST = { - .state = &s_dialog_spi_tx_dma_request_state, - .stream = &DMA2_STREAM1_DEVICE, - .channel = 4, - .irq_priority = DIALOG_SPI_DMA_PRIORITY, - .priority = DMARequestPriority_High, - .type = DMARequestType_MemoryToPeripheral, - .data_size = DMARequestDataSize_Byte, + .state = &s_dialog_spi_tx_dma_request_state, + .stream = &DMA2_STREAM1_DEVICE, + .channel = 4, + .irq_priority = DIALOG_SPI_DMA_PRIORITY, + .priority = DMARequestPriority_High, + .type = DMARequestType_MemoryToPeripheral, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_compositor_dma_request_state; static DMARequest COMPOSITOR_DMA_REQUEST = { - .state = &s_compositor_dma_request_state, - .stream = &DMA2_STREAM2_DEVICE, - .channel = 0, - .irq_priority = 11, - .priority = DMARequestPriority_Medium, - .type = DMARequestType_MemoryToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_compositor_dma_request_state, + .stream = &DMA2_STREAM2_DEVICE, + .channel = 0, + .irq_priority = 11, + .priority = DMARequestPriority_Medium, + .type = DMARequestType_MemoryToMemory, + .data_size = DMARequestDataSize_Byte, }; -DMARequest * const COMPOSITOR_DMA = &COMPOSITOR_DMA_REQUEST; +DMARequest *const COMPOSITOR_DMA = &COMPOSITOR_DMA_REQUEST; static DMARequestState s_dfsdm_dma_request_state; static DMARequest DFSDM_DMA_REQUEST = { - .state = &s_dfsdm_dma_request_state, - .stream = &DMA2_STREAM4_DEVICE, - .channel = 8, - .irq_priority = 0x0f, - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Word, + .state = &s_dfsdm_dma_request_state, + .stream = &DMA2_STREAM4_DEVICE, + .channel = 8, + .irq_priority = 0x0f, + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Word, }; static DMARequestState s_ice40lp_spi_tx_dma_request_state; static DMARequest ICE40LP_SPI_TX_DMA_REQUEST = { - .state = &s_ice40lp_spi_tx_dma_request_state, - .stream = &DMA2_STREAM5_DEVICE, - .channel = 1, - // Use the same priority as the EXTI handlers so that the DMA-complete - // handler doesn't preempt the display BUSY (INTn) handler. - .irq_priority = 0x0e, - .priority = DMARequestPriority_Medium, - .type = DMARequestType_MemoryToPeripheral, - .data_size = DMARequestDataSize_Byte, + .state = &s_ice40lp_spi_tx_dma_request_state, + .stream = &DMA2_STREAM5_DEVICE, + .channel = 1, + // Use the same priority as the EXTI handlers so that the DMA-complete + // handler doesn't preempt the display BUSY (INTn) handler. + .irq_priority = 0x0e, + .priority = DMARequestPriority_Medium, + .type = DMARequestType_MemoryToPeripheral, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_qspi_dma_request_state; static DMARequest QSPI_DMA_REQUEST = { - .state = &s_qspi_dma_request_state, - .stream = &DMA2_STREAM7_DEVICE, - .channel = 3, - .irq_priority = 0x0f, - .priority = DMARequestPriority_High, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Word, + .state = &s_qspi_dma_request_state, + .stream = &DMA2_STREAM7_DEVICE, + .channel = 3, + .irq_priority = 0x0f, + .priority = DMARequestPriority_High, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Word, }; - // UART DEVICES #if TARGET_QEMU static UARTDeviceState s_qemu_uart_state; static UARTDevice QEMU_UART_DEVICE = { - .state = &s_qemu_uart_state, - // GPIO? Where we're going, we don't need GPIO. (connected to QEMU) - .periph = USART2, - .irq_channel = USART2_IRQn, - .irq_priority = 13, - .rcc_apb_periph = RCC_APB1Periph_USART2 -}; -UARTDevice * const QEMU_UART = &QEMU_UART_DEVICE; + .state = &s_qemu_uart_state, + // GPIO? Where we're going, we don't need GPIO. (connected to QEMU) + .periph = USART2, + .irq_channel = USART2_IRQn, + .irq_priority = 13, + .rcc_apb_periph = RCC_APB1Periph_USART2}; +UARTDevice *const QEMU_UART = &QEMU_UART_DEVICE; IRQ_MAP(USART2, uart_irq_handler, QEMU_UART); #endif static UARTDeviceState s_dbg_uart_state; -static UARTDevice DBG_UART_DEVICE = { - .state = &s_dbg_uart_state, - .tx_gpio = { - .gpio = GPIOD, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF7_USART3 - }, - .rx_gpio = { - .gpio = GPIOD, - .gpio_pin = GPIO_Pin_9, - .gpio_pin_source = GPIO_PinSource9, - .gpio_af = GPIO_AF7_USART3 - }, - .periph = USART3, - .irq_channel = USART3_IRQn, - .irq_priority = 13, - .rcc_apb_periph = RCC_APB1Periph_USART3, - .rx_dma = &DBG_UART_RX_DMA_REQUEST -}; -UARTDevice * const DBG_UART = &DBG_UART_DEVICE; +static UARTDevice DBG_UART_DEVICE = {.state = &s_dbg_uart_state, + .tx_gpio = {.gpio = GPIOD, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF7_USART3}, + .rx_gpio = {.gpio = GPIOD, + .gpio_pin = GPIO_Pin_9, + .gpio_pin_source = GPIO_PinSource9, + .gpio_af = GPIO_AF7_USART3}, + .periph = USART3, + .irq_channel = USART3_IRQn, + .irq_priority = 13, + .rcc_apb_periph = RCC_APB1Periph_USART3, + .rx_dma = &DBG_UART_RX_DMA_REQUEST}; +UARTDevice *const DBG_UART = &DBG_UART_DEVICE; IRQ_MAP(USART3, uart_irq_handler, DBG_UART); static UARTDeviceState s_accessory_uart_state; -static UARTDevice ACCESSORY_UART_DEVICE = { - .state = &s_accessory_uart_state, - .half_duplex = true, - .tx_gpio = { +static UARTDevice ACCESSORY_UART_DEVICE = {.state = &s_accessory_uart_state, + .half_duplex = true, + .tx_gpio = + { #if BOARD_ROBERT_BB || BOARD_CUTTS_BB - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_12, - .gpio_pin_source = GPIO_PinSource12, - .gpio_af = GPIO_AF6_UART4 + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_12, + .gpio_pin_source = GPIO_PinSource12, + .gpio_af = GPIO_AF6_UART4 #elif BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .gpio = GPIOD, - .gpio_pin = GPIO_Pin_1, - .gpio_pin_source = GPIO_PinSource1, - .gpio_af = GPIO_AF8_UART4 + .gpio = GPIOD, + .gpio_pin = GPIO_Pin_1, + .gpio_pin_source = GPIO_PinSource1, + .gpio_af = GPIO_AF8_UART4 #else #error "Unknown board" #endif - }, - .periph = UART4, - .irq_channel = UART4_IRQn, - .irq_priority = 0xb, - .rcc_apb_periph = RCC_APB1Periph_UART4, - .rx_dma = &ACCESSORY_UART_RX_DMA_REQUEST -}; -UARTDevice * const ACCESSORY_UART = &ACCESSORY_UART_DEVICE; + }, + .periph = UART4, + .irq_channel = UART4_IRQn, + .irq_priority = 0xb, + .rcc_apb_periph = RCC_APB1Periph_UART4, + .rx_dma = &ACCESSORY_UART_RX_DMA_REQUEST}; +UARTDevice *const ACCESSORY_UART = &ACCESSORY_UART_DEVICE; IRQ_MAP(UART4, uart_irq_handler, ACCESSORY_UART); static UARTDeviceState s_bt_bootrom_uart_state; static UARTDevice BT_BOOTROM_UART_DEVICE = { - .state = &s_bt_bootrom_uart_state, + .state = &s_bt_bootrom_uart_state, #if BOARD_ROBERT_BB || BOARD_CUTTS_BB - .do_swap_rx_tx = true, + .do_swap_rx_tx = true, #elif BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .do_swap_rx_tx = false, + .do_swap_rx_tx = false, #else #error "Unknown board" #endif - .rx_gpio = { GPIOE, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF8_UART8 }, - .tx_gpio = { GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF8_UART8 }, - .rcc_apb_periph = RCC_APB1Periph_UART8, - .periph = UART8, + .rx_gpio = {GPIOE, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF8_UART8}, + .tx_gpio = {GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF8_UART8}, + .rcc_apb_periph = RCC_APB1Periph_UART8, + .periph = UART8, }; -UARTDevice * const BT_TX_BOOTROM_UART = &BT_BOOTROM_UART_DEVICE; -UARTDevice * const BT_RX_BOOTROM_UART = &BT_BOOTROM_UART_DEVICE; +UARTDevice *const BT_TX_BOOTROM_UART = &BT_BOOTROM_UART_DEVICE; +UARTDevice *const BT_RX_BOOTROM_UART = &BT_BOOTROM_UART_DEVICE; // I2C DEVICES @@ -260,136 +251,153 @@ UARTDevice * const BT_RX_BOOTROM_UART = &BT_BOOTROM_UART_DEVICE; static I2CBusState I2C_TOUCH_ALS_BUS_STATE = {}; static const I2CBusHal I2C_TOUCH_ALS_BUS_HAL = { - .i2c = I2C1, - .clock_ctrl = RCC_APB1Periph_I2C1, - .bus_mode = I2CBusMode_FastMode, - .clock_speed = 400000, - // TODO: These need to be measured. Just using PMIC_MAG values for now. - .rise_time_ns = 150, - .fall_time_ns = 6, - .ev_irq_channel = I2C1_EV_IRQn, - .er_irq_channel = I2C1_ER_IRQn, -}; + .i2c = I2C1, + .clock_ctrl = RCC_APB1Periph_I2C1, + .bus_mode = I2CBusMode_FastMode, + .clock_speed = 400000, + // TODO: These need to be measured. Just using PMIC_MAG values for now. + .rise_time_ns = 150, + .fall_time_ns = 6, + .ev_irq_channel = I2C1_EV_IRQn, + .er_irq_channel = I2C1_ER_IRQn, +}; + +static const I2CBus I2C_TOUCH_ALS_BUS = {.state = &I2C_TOUCH_ALS_BUS_STATE, + .hal = &I2C_TOUCH_ALS_BUS_HAL, + .scl_gpio = {.gpio = GPIOB, + .gpio_pin = GPIO_Pin_6, + .gpio_pin_source = GPIO_PinSource6, + .gpio_af = GPIO_AF4_I2C1}, + .sda_gpio = {.gpio = GPIOB, + .gpio_pin = GPIO_Pin_9, + .gpio_pin_source = GPIO_PinSource9, + .gpio_af = GPIO_AF4_I2C1}, + .stop_mode_inhibitor = InhibitorI2C1, + .name = "I2C_TOUCH_ALS"}; +#endif -static const I2CBus I2C_TOUCH_ALS_BUS = { - .state = &I2C_TOUCH_ALS_BUS_STATE, - .hal = &I2C_TOUCH_ALS_BUS_HAL, - .scl_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_6, - .gpio_pin_source = GPIO_PinSource6, - .gpio_af = GPIO_AF4_I2C1 - }, - .sda_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_9, - .gpio_pin_source = GPIO_PinSource9, - .gpio_af = GPIO_AF4_I2C1 - }, - .stop_mode_inhibitor = InhibitorI2C1, - .name = "I2C_TOUCH_ALS" -}; +static I2CBusState I2C_HRM_BUS_STATE = {}; + +static const I2CBusHal I2C_HRM_BUS_HAL = { + .i2c = I2C2, + .clock_ctrl = RCC_APB1Periph_I2C2, + .bus_mode = I2CBusMode_FastMode, + .clock_speed = 400000, +#if BOARD_ROBERT_BB || BOARD_CUTTS_BB + // TODO: These need to be measured. Just using PMIC_MAG values for now. + .rise_time_ns = 150, + .fall_time_ns = 6, +#elif BOARD_ROBERT_BB2 + // TODO: These need to be measured. Just using PMIC_MAG values for now. + .rise_time_ns = 150, + .fall_time_ns = 6, +#elif BOARD_ROBERT_EVT + // TODO: These need to be measured. Just using PMIC_MAG values for now. + .rise_time_ns = 70, + .fall_time_ns = 5, +#else +#error "Unknown board" #endif + .ev_irq_channel = I2C2_EV_IRQn, + .er_irq_channel = I2C2_ER_IRQn, +}; + +static const I2CBus I2C_HRM_BUS = {.state = &I2C_HRM_BUS_STATE, + .hal = &I2C_HRM_BUS_HAL, + .scl_gpio = {.gpio = GPIOF, + .gpio_pin = GPIO_Pin_1, + .gpio_pin_source = GPIO_PinSource1, + .gpio_af = GPIO_AF4_I2C2}, + .sda_gpio = {.gpio = GPIOF, + .gpio_pin = GPIO_Pin_0, + .gpio_pin_source = GPIO_PinSource0, + .gpio_af = GPIO_AF4_I2C2}, + .stop_mode_inhibitor = InhibitorI2C2, + .name = "I2C_HRM"}; #if BOARD_CUTTS_BB static I2CBusState I2C_NFC_BUS_STATE = {}; static const I2CBusHal I2C_NFC_BUS_HAL = { - .i2c = I2C3, - .clock_ctrl = RCC_APB1Periph_I2C3, - .bus_mode = I2CBusMode_FastMode, - .clock_speed = 400000, - // TODO: These need to be measured. Just using PMIC_MAG values for now. - .rise_time_ns = 150, - .fall_time_ns = 6, - .ev_irq_channel = I2C3_EV_IRQn, - .er_irq_channel = I2C3_ER_IRQn, -}; - -static const I2CBus I2C_NFC_BUS = { - .state = &I2C_NFC_BUS_STATE, - .hal = &I2C_NFC_BUS_HAL, - .scl_gpio = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF4_I2C3 - }, - .sda_gpio = { - .gpio = GPIOH, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF4_I2C3 - }, - .stop_mode_inhibitor = InhibitorI2C3, - .name = "I2C_NFC" -}; + .i2c = I2C3, + .clock_ctrl = RCC_APB1Periph_I2C3, + .bus_mode = I2CBusMode_FastMode, + .clock_speed = 400000, + // TODO: These need to be measured. Just using PMIC_MAG values for now. + .rise_time_ns = 150, + .fall_time_ns = 6, + .ev_irq_channel = I2C3_EV_IRQn, + .er_irq_channel = I2C3_ER_IRQn, +}; + +static const I2CBus I2C_NFC_BUS = {.state = &I2C_NFC_BUS_STATE, + .hal = &I2C_NFC_BUS_HAL, + .scl_gpio = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF4_I2C3}, + .sda_gpio = {.gpio = GPIOH, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF4_I2C3}, + .stop_mode_inhibitor = InhibitorI2C3, + .name = "I2C_NFC"}; #endif static I2CBusState I2C_PMIC_MAG_BUS_STATE = {}; static const I2CBusHal I2C_PMIC_MAG_BUS_HAL = { - .i2c = I2C4, - .clock_ctrl = RCC_APB1Periph_I2C4, - .bus_mode = I2CBusMode_FastMode, - .clock_speed = 400000, + .i2c = I2C4, + .clock_ctrl = RCC_APB1Periph_I2C4, + .bus_mode = I2CBusMode_FastMode, + .clock_speed = 400000, #if BOARD_ROBERT_BB || BOARD_CUTTS_BB - // These rise and fall times were measured. - .rise_time_ns = 150, - .fall_time_ns = 6, + // These rise and fall times were measured. + .rise_time_ns = 150, + .fall_time_ns = 6, #elif BOARD_ROBERT_BB2 - // TODO: These rise and fall times are based on robert_bb and should be measured - .rise_time_ns = 150, - .fall_time_ns = 6, + // TODO: These rise and fall times are based on robert_bb and should be measured + .rise_time_ns = 150, + .fall_time_ns = 6, #elif BOARD_ROBERT_EVT - // TODO: These are calculated and could potentially be measured. - .rise_time_ns = 70, - .fall_time_ns = 5, + // TODO: These are calculated and could potentially be measured. + .rise_time_ns = 70, + .fall_time_ns = 5, #else #error "Unknown board" #endif - .ev_irq_channel = I2C4_EV_IRQn, - .er_irq_channel = I2C4_ER_IRQn, -}; - -static const I2CBus I2C_PMIC_MAG_BUS = { - .state = &I2C_PMIC_MAG_BUS_STATE, - .hal = &I2C_PMIC_MAG_BUS_HAL, - .scl_gpio = { - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_14, - .gpio_pin_source = GPIO_PinSource14, - .gpio_af = GPIO_AF4_I2C4 - }, - .sda_gpio = { - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_15, - .gpio_pin_source = GPIO_PinSource15, - .gpio_af = GPIO_AF4_I2C4 - }, - .stop_mode_inhibitor = InhibitorI2C4, - .name = "I2C_PMIC_MAG" -}; + .ev_irq_channel = I2C4_EV_IRQn, + .er_irq_channel = I2C4_ER_IRQn, +}; + +static const I2CBus I2C_PMIC_MAG_BUS = {.state = &I2C_PMIC_MAG_BUS_STATE, + .hal = &I2C_PMIC_MAG_BUS_HAL, + .scl_gpio = {.gpio = GPIOF, + .gpio_pin = GPIO_Pin_14, + .gpio_pin_source = GPIO_PinSource14, + .gpio_af = GPIO_AF4_I2C4}, + .sda_gpio = {.gpio = GPIOF, + .gpio_pin = GPIO_Pin_15, + .gpio_pin_source = GPIO_PinSource15, + .gpio_af = GPIO_AF4_I2C4}, + .stop_mode_inhibitor = InhibitorI2C4, + .name = "I2C_PMIC_MAG"}; #if BOARD_CUTTS_BB -static const I2CSlavePort I2C_SLAVE_EWD1000 = { - .bus = &I2C_TOUCH_ALS_BUS, - .address = 0x2A -}; +static const I2CSlavePort I2C_SLAVE_EWD1000 = {.bus = &I2C_TOUCH_ALS_BUS, .address = 0x2A}; #endif -static const I2CSlavePort I2C_SLAVE_MAX14690 = { - .bus = &I2C_PMIC_MAG_BUS, - .address = 0x50 -}; +static const I2CSlavePort I2C_SLAVE_MAX14690 = {.bus = &I2C_PMIC_MAG_BUS, .address = 0x50}; -static const I2CSlavePort I2C_SLAVE_MAG3110 = { - .bus = &I2C_PMIC_MAG_BUS, - .address = 0x0e << 1 -}; +static const I2CSlavePort I2C_SLAVE_MAG3110 = {.bus = &I2C_PMIC_MAG_BUS, .address = 0x0e << 1}; + +static const I2CSlavePort I2C_SLAVE_AS7000 = {.bus = &I2C_HRM_BUS, .address = 0x60}; -I2CSlavePort * const I2C_MAX14690 = &I2C_SLAVE_MAX14690; -I2CSlavePort * const I2C_MAG3110 = &I2C_SLAVE_MAG3110; +I2CSlavePort *const I2C_MAX14690 = &I2C_SLAVE_MAX14690; +I2CSlavePort *const I2C_MAG3110 = &I2C_SLAVE_MAG3110; +I2CSlavePort *const I2C_AS7000 = &I2C_SLAVE_AS7000; +IRQ_MAP(I2C2_EV, i2c_hal_event_irq_handler, &I2C_HRM_BUS); +IRQ_MAP(I2C2_ER, i2c_hal_error_irq_handler, &I2C_HRM_BUS); IRQ_MAP(I2C4_EV, i2c_hal_event_irq_handler, &I2C_PMIC_MAG_BUS); IRQ_MAP(I2C4_ER, i2c_hal_error_irq_handler, &I2C_PMIC_MAG_BUS); #if BOARD_CUTTS_BB @@ -397,142 +405,143 @@ IRQ_MAP(I2C1_EV, i2c_hal_event_irq_handler, &I2C_TOUCH_ALS_BUS); IRQ_MAP(I2C1_ER, i2c_hal_error_irq_handler, &I2C_TOUCH_ALS_BUS); #endif +// HRM DEVICE +static HRMDeviceState s_hrm_state; +static HRMDevice HRM_DEVICE = { + .state = &s_hrm_state, + .handshake_int = {EXTI_PortSourceGPIOI, 10}, + .int_gpio = {.gpio = GPIOI, .gpio_pin = GPIO_Pin_10}, + .en_gpio = + { + .gpio = GPIOF, + .gpio_pin = GPIO_Pin_3, + .active_high = false, + }, + .i2c_slave = &I2C_SLAVE_AS7000, +}; +HRMDevice *const HRM = &HRM_DEVICE; #if BOARD_CUTTS_BB static const TouchSensor EWD1000_DEVICE = { - .i2c = &I2C_SLAVE_EWD1000, - .int_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_7, - }, - .int_exti = { - .exti_port_source = EXTI_PortSourceGPIOB, - .exti_line = 7, - }, - .reset_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_5, - .active_high = true, - }, -}; - -TouchSensor * const EWD1000 = &EWD1000_DEVICE; + .i2c = &I2C_SLAVE_EWD1000, + .int_gpio = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_7, + }, + .int_exti = + { + .exti_port_source = EXTI_PortSourceGPIOB, + .exti_line = 7, + }, + .reset_gpio = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_5, + .active_high = true, + }, +}; + +TouchSensor *const EWD1000 = &EWD1000_DEVICE; #endif // VOLTAGE MONITOR DEVICES static const VoltageMonitorDevice VOLTAGE_MONITOR_ALS_DEVICE = { - .adc = ADC3, - .adc_channel = ADC_Channel_14, - .clock_ctrl = RCC_APB2Periph_ADC3, - .input = { - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_4, - }, + .adc = ADC3, + .adc_channel = ADC_Channel_14, + .clock_ctrl = RCC_APB2Periph_ADC3, + .input = + { + .gpio = GPIOF, + .gpio_pin = GPIO_Pin_4, + }, }; static const VoltageMonitorDevice VOLTAGE_MONITOR_BATTERY_DEVICE = { - .adc = ADC1, - .adc_channel = ADC_Channel_9, - .clock_ctrl = RCC_APB2Periph_ADC1, - .input = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_1, - } -}; + .adc = ADC1, + .adc_channel = ADC_Channel_9, + .clock_ctrl = RCC_APB2Periph_ADC1, + .input = { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_1, + }}; static const VoltageMonitorDevice VOLTAGE_MONITOR_TEMPERATURE_DEVICE = { - .adc = ADC1, - .adc_channel = ADC_Channel_TempSensor, - .clock_ctrl = RCC_APB2Periph_ADC1, - // .input not applicable + .adc = ADC1, .adc_channel = ADC_Channel_TempSensor, .clock_ctrl = RCC_APB2Periph_ADC1, + // .input not applicable }; -VoltageMonitorDevice * const VOLTAGE_MONITOR_ALS = &VOLTAGE_MONITOR_ALS_DEVICE; -VoltageMonitorDevice * const VOLTAGE_MONITOR_BATTERY = &VOLTAGE_MONITOR_BATTERY_DEVICE; -VoltageMonitorDevice * const VOLTAGE_MONITOR_TEMPERATURE = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE; - +VoltageMonitorDevice *const VOLTAGE_MONITOR_ALS = &VOLTAGE_MONITOR_ALS_DEVICE; +VoltageMonitorDevice *const VOLTAGE_MONITOR_BATTERY = &VOLTAGE_MONITOR_BATTERY_DEVICE; +VoltageMonitorDevice *const VOLTAGE_MONITOR_TEMPERATURE = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE; // Temperature sensor const TemperatureSensor TEMPERATURE_SENSOR_DEVICE = { - .voltage_monitor = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE, - .millivolts_ref = 760, - .millidegrees_ref = 25000, - .slope_numerator = 5, - .slope_denominator = 2000, + .voltage_monitor = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE, + .millivolts_ref = 760, + .millidegrees_ref = 25000, + .slope_numerator = 5, + .slope_denominator = 2000, }; -const TemperatureSensor * const TEMPERATURE_SENSOR = &TEMPERATURE_SENSOR_DEVICE; - +const TemperatureSensor *const TEMPERATURE_SENSOR = &TEMPERATURE_SENSOR_DEVICE; // // SPI Bus configuration // -static SPIBusState DIALOG_SPI_BUS_STATE = { }; +static SPIBusState DIALOG_SPI_BUS_STATE = {}; static const SPIBus DIALOG_SPI_BUS = { - .state = &DIALOG_SPI_BUS_STATE, - .spi = SPI4, - .spi_sclk = { GPIOE, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF5_SPI5 }, - .spi_miso = { GPIOE, GPIO_Pin_13, GPIO_PinSource13, GPIO_AF5_SPI5 }, - .spi_mosi = { GPIOE, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF5_SPI5 }, - .spi_sclk_speed = GPIO_Speed_50MHz, - // DA14680_FS v1.4 page 89: - // "In slave mode the internal SPI clock must be more than four times the SPIx_CLK" - // The system clock is 16MHz, so don't use more than 4MHz. - .spi_clock_speed_hz = MHZ_TO_HZ(4) -}; + .state = &DIALOG_SPI_BUS_STATE, + .spi = SPI4, + .spi_sclk = {GPIOE, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF5_SPI5}, + .spi_miso = {GPIOE, GPIO_Pin_13, GPIO_PinSource13, GPIO_AF5_SPI5}, + .spi_mosi = {GPIOE, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF5_SPI5}, + .spi_sclk_speed = GPIO_Speed_50MHz, + // DA14680_FS v1.4 page 89: + // "In slave mode the internal SPI clock must be more than four times the SPIx_CLK" + // The system clock is 16MHz, so don't use more than 4MHz. + .spi_clock_speed_hz = MHZ_TO_HZ(4)}; static SPIBusState BMI160_SPI_BUS_STATE = {}; static const SPIBus BMI160_SPI_BUS = { - .state = &BMI160_SPI_BUS_STATE, - .spi = SPI2, - .spi_sclk = { - .gpio = GPIOI, - .gpio_pin = GPIO_Pin_1, - .gpio_pin_source = GPIO_PinSource1, - .gpio_af = GPIO_AF5_SPI2 - }, - .spi_miso = { - .gpio = GPIOI, - .gpio_pin = GPIO_Pin_2, - .gpio_pin_source = GPIO_PinSource2, - .gpio_af = GPIO_AF5_SPI2 - }, - .spi_mosi = { - .gpio = GPIOI, - .gpio_pin = GPIO_Pin_3, - .gpio_pin_source = GPIO_PinSource3, - .gpio_af = GPIO_AF5_SPI2 - }, - .spi_sclk_speed = GPIO_Speed_25MHz, - .spi_clock_speed_hz = MHZ_TO_HZ(5), + .state = &BMI160_SPI_BUS_STATE, + .spi = SPI2, + .spi_sclk = {.gpio = GPIOI, + .gpio_pin = GPIO_Pin_1, + .gpio_pin_source = GPIO_PinSource1, + .gpio_af = GPIO_AF5_SPI2}, + .spi_miso = {.gpio = GPIOI, + .gpio_pin = GPIO_Pin_2, + .gpio_pin_source = GPIO_PinSource2, + .gpio_af = GPIO_AF5_SPI2}, + .spi_mosi = {.gpio = GPIOI, + .gpio_pin = GPIO_Pin_3, + .gpio_pin_source = GPIO_PinSource3, + .gpio_af = GPIO_AF5_SPI2}, + .spi_sclk_speed = GPIO_Speed_25MHz, + .spi_clock_speed_hz = MHZ_TO_HZ(5), }; static SPIBusState ICE40LP_SPI_BUS_STATE = {}; static const SPIBus ICE40LP_SPI_BUS = { - .state = &ICE40LP_SPI_BUS_STATE, - .spi = SPI6, - .spi_sclk = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_5, - .gpio_pin_source = GPIO_PinSource5, - .gpio_af = GPIO_AF8_SPI6 - }, - .spi_miso = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_6, - .gpio_pin_source = GPIO_PinSource6, - .gpio_af = GPIO_AF8_SPI6 - }, - .spi_mosi = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_7, - .gpio_pin_source = GPIO_PinSource7, - .gpio_af = GPIO_AF8_SPI6 - }, - .spi_sclk_speed = GPIO_Speed_25MHz, - .spi_clock_speed_hz = MHZ_TO_HZ(16), + .state = &ICE40LP_SPI_BUS_STATE, + .spi = SPI6, + .spi_sclk = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_5, + .gpio_pin_source = GPIO_PinSource5, + .gpio_af = GPIO_AF8_SPI6}, + .spi_miso = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_6, + .gpio_pin_source = GPIO_PinSource6, + .gpio_af = GPIO_AF8_SPI6}, + .spi_mosi = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_7, + .gpio_pin_source = GPIO_PinSource7, + .gpio_af = GPIO_AF8_SPI6}, + .spi_sclk_speed = GPIO_Speed_25MHz, + .spi_clock_speed_hz = MHZ_TO_HZ(16), }; // @@ -541,181 +550,174 @@ static const SPIBus ICE40LP_SPI_BUS = { static SPISlavePortState BMI160_SPI_SLAVE_PORT_STATE = {}; static SPISlavePort BMI160_SPI_SLAVE_PORT = { - .slave_state = &BMI160_SPI_SLAVE_PORT_STATE, - .spi_bus = &BMI160_SPI_BUS, - .spi_scs = { - .gpio = GPIOI, - .gpio_pin = GPIO_Pin_0, - .active_high = false - }, - .spi_direction = SpiDirection_2LinesFullDuplex, - .spi_cpol = SpiCPol_Low, - .spi_cpha = SpiCPha_1Edge, - .spi_first_bit = SpiFirstBit_MSB, + .slave_state = &BMI160_SPI_SLAVE_PORT_STATE, + .spi_bus = &BMI160_SPI_BUS, + .spi_scs = {.gpio = GPIOI, .gpio_pin = GPIO_Pin_0, .active_high = false}, + .spi_direction = SpiDirection_2LinesFullDuplex, + .spi_cpol = SpiCPol_Low, + .spi_cpha = SpiCPha_1Edge, + .spi_first_bit = SpiFirstBit_MSB, }; -SPISlavePort * const BMI160_SPI = &BMI160_SPI_SLAVE_PORT; +SPISlavePort *const BMI160_SPI = &BMI160_SPI_SLAVE_PORT; static SPISlavePortState ICE40LP_SPI_SLAVE_PORT_STATE = {}; static SPISlavePort ICE40LP_SPI_SLAVE_PORT = { - .slave_state = &ICE40LP_SPI_SLAVE_PORT_STATE, - .spi_bus = &ICE40LP_SPI_BUS, - .spi_scs = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_4, - .active_high = false - }, - .spi_direction = SpiDirection_1LineTx, - .spi_cpol = SpiCPol_High, - .spi_cpha = SpiCPha_2Edge, - .spi_first_bit = SpiFirstBit_MSB, - .tx_dma = &ICE40LP_SPI_TX_DMA_REQUEST -}; + .slave_state = &ICE40LP_SPI_SLAVE_PORT_STATE, + .spi_bus = &ICE40LP_SPI_BUS, + .spi_scs = {.gpio = GPIOA, .gpio_pin = GPIO_Pin_4, .active_high = false}, + .spi_direction = SpiDirection_1LineTx, + .spi_cpol = SpiCPol_High, + .spi_cpha = SpiCPha_2Edge, + .spi_first_bit = SpiFirstBit_MSB, + .tx_dma = &ICE40LP_SPI_TX_DMA_REQUEST}; static SPISlavePortState DIALOG_SPI_SLAVE_PORT_STATE = {}; -static SPISlavePort DIALOG_SPI_SLAVE_PORT = { - .slave_state = &DIALOG_SPI_SLAVE_PORT_STATE, - .spi_bus = &DIALOG_SPI_BUS, - .spi_scs = { GPIOE, GPIO_Pin_11, false }, - .spi_direction = SpiDirection_2LinesFullDuplex, - .spi_cpol = SpiCPol_Low, - .spi_cpha = SpiCPha_1Edge, - .spi_first_bit = SpiFirstBit_MSB, - .rx_dma = &DIALOG_SPI_RX_DMA_REQUEST, - .tx_dma = &DIALOG_SPI_TX_DMA_REQUEST -}; -SPISlavePort * const DIALOG_SPI = &DIALOG_SPI_SLAVE_PORT; - - +static SPISlavePort DIALOG_SPI_SLAVE_PORT = {.slave_state = &DIALOG_SPI_SLAVE_PORT_STATE, + .spi_bus = &DIALOG_SPI_BUS, + .spi_scs = {GPIOE, GPIO_Pin_11, false}, + .spi_direction = SpiDirection_2LinesFullDuplex, + .spi_cpol = SpiCPol_Low, + .spi_cpha = SpiCPha_1Edge, + .spi_first_bit = SpiFirstBit_MSB, + .rx_dma = &DIALOG_SPI_RX_DMA_REQUEST, + .tx_dma = &DIALOG_SPI_TX_DMA_REQUEST}; +SPISlavePort *const DIALOG_SPI = &DIALOG_SPI_SLAVE_PORT; // // iCE40LP configuration // static ICE40LPDeviceState s_ice40lp_state; static ICE40LPDevice ICE40LP_DEVICE = { - .state = &s_ice40lp_state, - - .spi_port = &ICE40LP_SPI_SLAVE_PORT, - .base_spi_frequency = MHZ_TO_HZ(16), - .fast_spi_frequency = MHZ_TO_HZ(32), - .creset = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_3, - .active_high = true, - }, - .cdone = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_2, - }, - .busy = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_0, - }, - .cdone_exti = { - .exti_port_source = EXTI_PortSourceGPIOB, - .exti_line = 2, - }, - .busy_exti = { - .exti_port_source = EXTI_PortSourceGPIOB, - .exti_line = 0, - }, + .state = &s_ice40lp_state, + + .spi_port = &ICE40LP_SPI_SLAVE_PORT, + .base_spi_frequency = MHZ_TO_HZ(16), + .fast_spi_frequency = MHZ_TO_HZ(32), + .creset = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_3, + .active_high = true, + }, + .cdone = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_2, + }, + .busy = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_0, + }, + .cdone_exti = + { + .exti_port_source = EXTI_PortSourceGPIOB, + .exti_line = 2, + }, + .busy_exti = + { + .exti_port_source = EXTI_PortSourceGPIOB, + .exti_line = 0, + }, #if BOARD_CUTTS_BB - .use_6v6_rail = true, + .use_6v6_rail = true, #elif BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .use_6v6_rail = false, + .use_6v6_rail = false, #else #error "Unknown board" #endif }; -ICE40LPDevice * const ICE40LP = &ICE40LP_DEVICE; - +ICE40LPDevice *const ICE40LP = &ICE40LP_DEVICE; // QSPI static QSPIPortState s_qspi_port_state; static QSPIPort QSPI_PORT = { - .state = &s_qspi_port_state, - .clock_speed_hz = MHZ_TO_HZ(72), - .auto_polling_interval = 16, - .clock_ctrl = RCC_AHB3Periph_QSPI, - .cs_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_10, - .gpio_pin_source = GPIO_PinSource10, - .gpio_af = GPIO_AF9_QUADSPI, - }, - .clk_gpio = { - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_10, - .gpio_pin_source = GPIO_PinSource10, - .gpio_af = GPIO_AF9_QUADSPI, - }, - .data_gpio = { - { - .gpio = GPIOD, - .gpio_pin = GPIO_Pin_11, - .gpio_pin_source = GPIO_PinSource11, - .gpio_af = GPIO_AF9_QUADSPI, - }, - { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_10, - .gpio_pin_source = GPIO_PinSource10, - .gpio_af = GPIO_AF9_QUADSPI, - }, - { + .state = &s_qspi_port_state, + .clock_speed_hz = MHZ_TO_HZ(72), + .auto_polling_interval = 16, + .clock_ctrl = RCC_AHB3Periph_QSPI, + .cs_gpio = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_10, + .gpio_pin_source = GPIO_PinSource10, + .gpio_af = GPIO_AF9_QUADSPI, + }, + .clk_gpio = + { + .gpio = GPIOF, + .gpio_pin = GPIO_Pin_10, + .gpio_pin_source = GPIO_PinSource10, + .gpio_af = GPIO_AF9_QUADSPI, + }, + .data_gpio = + { + { + .gpio = GPIOD, + .gpio_pin = GPIO_Pin_11, + .gpio_pin_source = GPIO_PinSource11, + .gpio_af = GPIO_AF9_QUADSPI, + }, + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_10, + .gpio_pin_source = GPIO_PinSource10, + .gpio_af = GPIO_AF9_QUADSPI, + }, + { #if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_7, - .gpio_pin_source = GPIO_PinSource7, - .gpio_af = GPIO_AF9_QUADSPI, + .gpio = GPIOF, + .gpio_pin = GPIO_Pin_7, + .gpio_pin_source = GPIO_PinSource7, + .gpio_af = GPIO_AF9_QUADSPI, #elif BOARD_ROBERT_EVT - .gpio = GPIOE, - .gpio_pin = GPIO_Pin_2, - .gpio_pin_source = GPIO_PinSource2, - .gpio_af = GPIO_AF9_QUADSPI, + .gpio = GPIOE, + .gpio_pin = GPIO_Pin_2, + .gpio_pin_source = GPIO_PinSource2, + .gpio_af = GPIO_AF9_QUADSPI, #else #error "Unknown board" #endif - }, - { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_1, - .gpio_pin_source = GPIO_PinSource1, - .gpio_af = GPIO_AF9_QUADSPI, - }, - }, - .dma = &QSPI_DMA_REQUEST, -}; -QSPIPort * const QSPI = &QSPI_PORT; + }, + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_1, + .gpio_pin_source = GPIO_PinSource1, + .gpio_af = GPIO_AF9_QUADSPI, + }, + }, + .dma = &QSPI_DMA_REQUEST, +}; +QSPIPort *const QSPI = &QSPI_PORT; static QSPIFlashState s_qspi_flash_state; static QSPIFlash QSPI_FLASH_DEVICE = { - .state = &s_qspi_flash_state, - .qspi = &QSPI_PORT, - .default_fast_read_ddr_enabled = true, + .state = &s_qspi_flash_state, + .qspi = &QSPI_PORT, + .default_fast_read_ddr_enabled = true, #if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB - .reset_gpio = { GPIO_Port_NULL }, + .reset_gpio = {GPIO_Port_NULL}, #elif BOARD_ROBERT_EVT - .reset_gpio = { - .gpio = GPIOE, - .gpio_pin = GPIO_Pin_15, - .active_high = false, - }, + .reset_gpio = + { + .gpio = GPIOE, + .gpio_pin = GPIO_Pin_15, + .active_high = false, + }, #else #error "Unknown error" #endif }; -QSPIFlash * const QSPI_FLASH = &QSPI_FLASH_DEVICE; +QSPIFlash *const QSPI_FLASH = &QSPI_FLASH_DEVICE; - -void board_early_init(void) { - spi_slave_port_init(ICE40LP->spi_port); -} +void board_early_init(void) { spi_slave_port_init(ICE40LP->spi_port); } void board_init(void) { #if BOARD_CUTTS_BB i2c_init(&I2C_TOUCH_ALS_BUS); i2c_init(&I2C_NFC_BUS); #endif + i2c_init(&I2C_HRM_BUS); i2c_init(&I2C_PMIC_MAG_BUS); spi_slave_port_init(BMI160_SPI); diff --git a/src/fw/board/boards/board_robert.h b/src/fw/board/boards/board_robert.h index 6a055a14d..0880796b8 100644 --- a/src/fw/board/boards/board_robert.h +++ b/src/fw/board/boards/board_robert.h @@ -28,283 +28,297 @@ #define BOARD_LSE_MODE RCC_LSE_Bypass static const BoardConfig BOARD_CONFIG = { - .ambient_light_dark_threshold = 3220, - .ambient_k_delta_threshold = 96, - .photo_en = { GPIOF, GPIO_Pin_5, true }, - .als_always_on = true, + .ambient_light_dark_threshold = 3220, + .ambient_k_delta_threshold = 96, + .photo_en = {GPIOF, GPIO_Pin_5, true}, + .als_always_on = true, - .dbgserial_int = { EXTI_PortSourceGPIOH, 9 }, - .dbgserial_int_gpio = { GPIOH, GPIO_Pin_9 }, + .dbgserial_int = {EXTI_PortSourceGPIOH, 9}, + .dbgserial_int_gpio = {GPIOH, GPIO_Pin_9}, - // Only used with Sharp displays - .lcd_com = { 0 }, + // Only used with Sharp displays + .lcd_com = {0}, - .power_5v0_options = OptionNotPresent, - .power_ctl_5v0 = { 0 }, + .power_5v0_options = OptionNotPresent, + .power_ctl_5v0 = {0}, - .backlight_on_percent = 45, - .backlight_max_duty_cycle_percent = 100, + .backlight_on_percent = 45, + .backlight_max_duty_cycle_percent = 100, - .num_avail_gpios = 140, + .num_avail_gpios = 140, - .has_mic = true, + .has_mic = true, }; static const BoardConfigButton BOARD_CONFIG_BUTTON = { - .buttons = { + .buttons = + { #if BOARD_ROBERT_BB || BOARD_CUTTS_BB || BOARD_ROBERT_BB2 - [BUTTON_ID_BACK] = - { "Back", GPIOG, GPIO_Pin_6, { EXTI_PortSourceGPIOG, 6 }, GPIO_PuPd_UP }, - [BUTTON_ID_UP] = - { "Up", GPIOG, GPIO_Pin_3, { EXTI_PortSourceGPIOG, 3 }, GPIO_PuPd_NOPULL }, - [BUTTON_ID_DOWN] = - { "Down", GPIOG, GPIO_Pin_4, { EXTI_PortSourceGPIOG, 4 }, GPIO_PuPd_UP }, + [BUTTON_ID_BACK] = {"Back", GPIOG, GPIO_Pin_6, {EXTI_PortSourceGPIOG, 6}, GPIO_PuPd_UP}, + [BUTTON_ID_UP] = {"Up", GPIOG, GPIO_Pin_3, {EXTI_PortSourceGPIOG, 3}, GPIO_PuPd_NOPULL}, + [BUTTON_ID_DOWN] = {"Down", GPIOG, GPIO_Pin_4, {EXTI_PortSourceGPIOG, 4}, GPIO_PuPd_UP}, #elif BOARD_ROBERT_EVT - [BUTTON_ID_BACK] = - { "Back", GPIOG, GPIO_Pin_3, { EXTI_PortSourceGPIOG, 3 }, GPIO_PuPd_NOPULL }, - [BUTTON_ID_UP] = - { "Up", GPIOG, GPIO_Pin_4, { EXTI_PortSourceGPIOG, 4 }, GPIO_PuPd_UP }, - [BUTTON_ID_DOWN] = - { "Down", GPIOG, GPIO_Pin_6, { EXTI_PortSourceGPIOG, 6 }, GPIO_PuPd_UP }, + [BUTTON_ID_BACK] = + {"Back", GPIOG, GPIO_Pin_3, {EXTI_PortSourceGPIOG, 3}, GPIO_PuPd_NOPULL}, + [BUTTON_ID_UP] = {"Up", GPIOG, GPIO_Pin_4, {EXTI_PortSourceGPIOG, 4}, GPIO_PuPd_UP}, + [BUTTON_ID_DOWN] = {"Down", GPIOG, GPIO_Pin_6, {EXTI_PortSourceGPIOG, 6}, GPIO_PuPd_UP}, #else #error "Unknown board" #endif - [BUTTON_ID_SELECT] = - { "Select", GPIOG, GPIO_Pin_5, { EXTI_PortSourceGPIOG, 5 }, GPIO_PuPd_UP }, - }, + [BUTTON_ID_SELECT] = + {"Select", GPIOG, GPIO_Pin_5, {EXTI_PortSourceGPIOG, 5}, GPIO_PuPd_UP}, + }, - .button_com = { 0 }, - .active_high = false, + .button_com = {0}, + .active_high = false, }; static const BoardConfigPower BOARD_CONFIG_POWER = { - .pmic_int = { EXTI_PortSourceGPIOF, 12 }, - .pmic_int_gpio = { - .gpio = GPIOF, - .gpio_pin = GPIO_Pin_12, - }, - - .rail_4V5_ctrl = { - .gpio = GPIOH, - .gpio_pin = GPIO_Pin_5, - .active_high = true, - }, + .pmic_int = {EXTI_PortSourceGPIOF, 12}, + .pmic_int_gpio = + { + .gpio = GPIOF, + .gpio_pin = GPIO_Pin_12, + }, + + .rail_4V5_ctrl = + { + .gpio = GPIOH, + .gpio_pin = GPIO_Pin_5, + .active_high = true, + }, #if BOARD_CUTTS_BB - .rail_6V6_ctrl = { - .gpio = GPIOH, - .gpio_pin = GPIO_Pin_3, - .active_high = true, - }, - .rail_6V6_ctrl_otype = GPIO_OType_PP, + .rail_6V6_ctrl = + { + .gpio = GPIOH, + .gpio_pin = GPIO_Pin_3, + .active_high = true, + }, + .rail_6V6_ctrl_otype = GPIO_OType_PP, #elif BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .rail_6V6_ctrl = { GPIO_Port_NULL }, + .rail_6V6_ctrl = {GPIO_Port_NULL}, #else #error "Unknown board" #endif - .battery_vmon_scale = { - // The PMIC divides the battery voltage by a ratio of 3:1 in order to move it down to a voltage - // our ADC is capable of reading. The battery voltage varies around 4V~ and we're only capable - // of reading up to 1.8V in the ADC. - .numerator = 3, - .denominator = 1, - }, - - .vusb_stat = { .gpio = GPIO_Port_NULL, }, - .chg_stat = { GPIO_Port_NULL }, - .chg_fast = { GPIO_Port_NULL }, - .chg_en = { GPIO_Port_NULL }, - .has_vusb_interrupt = false, - - .wake_on_usb_power = false, + .battery_vmon_scale = + { + // The PMIC divides the battery voltage by a ratio of 3:1 in order to move it down to a + // voltage + // our ADC is capable of reading. The battery voltage varies around 4V~ and we're only + // capable + // of reading up to 1.8V in the ADC. + .numerator = 3, + .denominator = 1, + }, + + .vusb_stat = + { + .gpio = GPIO_Port_NULL, + }, + .chg_stat = {GPIO_Port_NULL}, + .chg_fast = {GPIO_Port_NULL}, + .chg_en = {GPIO_Port_NULL}, + .has_vusb_interrupt = false, + + .wake_on_usb_power = false, #if defined(IS_BIGBOARD) && !defined(BATTERY_DEBUG) - .charging_cutoff_voltage = 4200, + .charging_cutoff_voltage = 4200, #else - .charging_cutoff_voltage = 4300, + .charging_cutoff_voltage = 4300, #endif - .charging_status_led_voltage_compensation = 0, + .charging_status_led_voltage_compensation = 0, - .low_power_threshold = 2, - .battery_capacity_hours = 204, + .low_power_threshold = 2, + .battery_capacity_hours = 204, }; static const BoardConfigAccel BOARD_CONFIG_ACCEL = { - .accel_config = { - .axes_offsets[AXIS_X] = 0, - .axes_offsets[AXIS_Y] = 1, - .axes_offsets[AXIS_Z] = 2, + .accel_config = + { + .axes_offsets[AXIS_X] = 0, + .axes_offsets[AXIS_Y] = 1, + .axes_offsets[AXIS_Z] = 2, #if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB - .axes_inverts[AXIS_X] = true, - .axes_inverts[AXIS_Y] = false, - .axes_inverts[AXIS_Z] = true, + .axes_inverts[AXIS_X] = true, + .axes_inverts[AXIS_Y] = false, + .axes_inverts[AXIS_Z] = true, #elif BOARD_ROBERT_EVT - .axes_inverts[AXIS_X] = false, - .axes_inverts[AXIS_Y] = true, - .axes_inverts[AXIS_Z] = false, + .axes_inverts[AXIS_X] = false, + .axes_inverts[AXIS_Y] = true, + .axes_inverts[AXIS_Z] = false, #else #error "Unknown board" #endif - .shake_thresholds[AccelThresholdHigh] = 0x64, - .shake_thresholds[AccelThresholdLow] = 0xf, - .double_tap_threshold = 12500, - }, - .accel_int_gpios = { - [0] = { GPIOH, GPIO_Pin_15 }, - [1] = { GPIOH, GPIO_Pin_14 }, - }, - .accel_ints = { - [0] = { EXTI_PortSourceGPIOH, 15 }, - [1] = { EXTI_PortSourceGPIOH, 14 } - }, + .shake_thresholds[AccelThresholdHigh] = 0x64, + .shake_thresholds[AccelThresholdLow] = 0xf, + .double_tap_threshold = 12500, + }, + .accel_int_gpios = + { + [0] = {GPIOH, GPIO_Pin_15}, + [1] = {GPIOH, GPIO_Pin_14}, + }, + .accel_ints = {[0] = {EXTI_PortSourceGPIOH, 15}, [1] = {EXTI_PortSourceGPIOH, 14}}, }; static const BoardConfigMag BOARD_CONFIG_MAG = { - .mag_config = { - .axes_offsets[AXIS_X] = 1, - .axes_offsets[AXIS_Y] = 0, - .axes_offsets[AXIS_Z] = 2, + .mag_config = + { + .axes_offsets[AXIS_X] = 1, + .axes_offsets[AXIS_Y] = 0, + .axes_offsets[AXIS_Z] = 2, #if BOARD_ROBERT_BB || BOARD_ROBERT_BB2 || BOARD_CUTTS_BB - .axes_inverts[AXIS_X] = false, - .axes_inverts[AXIS_Y] = true, - .axes_inverts[AXIS_Z] = true, + .axes_inverts[AXIS_X] = false, + .axes_inverts[AXIS_Y] = true, + .axes_inverts[AXIS_Z] = true, #elif BOARD_ROBERT_EVT - .axes_inverts[AXIS_X] = true, - .axes_inverts[AXIS_Y] = true, - .axes_inverts[AXIS_Z] = false, + .axes_inverts[AXIS_X] = true, + .axes_inverts[AXIS_Y] = true, + .axes_inverts[AXIS_Z] = false, #else #error "Unknown board" #endif - }, - .mag_int_gpio = { GPIOF, GPIO_Pin_11 }, - .mag_int = { EXTI_PortSourceGPIOF, 11 }, + }, + .mag_int_gpio = {GPIOF, GPIO_Pin_11}, + .mag_int = {EXTI_PortSourceGPIOF, 11}, }; static const BoardConfigActuator BOARD_CONFIG_VIBE = { - .options = ActuatorOptions_Ctl | ActuatorOptions_Pwm | ActuatorOptions_HBridge, + .options = ActuatorOptions_Ctl | ActuatorOptions_Pwm | ActuatorOptions_HBridge, #if BOARD_ROBERT_BB || BOARD_CUTTS_BB - .ctl = { GPIOD, GPIO_Pin_14, true }, - .pwm = { - .output = { GPIOD, GPIO_Pin_12, true }, - .timer = { - .peripheral = TIM4, - .config_clock = RCC_APB1Periph_TIM4, - .init = TIM_OC1Init, - .preload = TIM_OC1PreloadConfig - }, - .afcfg = { GPIOD, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF2_TIM4 }, - }, + .ctl = {GPIOD, GPIO_Pin_14, true}, + .pwm = + { + .output = {GPIOD, GPIO_Pin_12, true}, + .timer = {.peripheral = TIM4, + .config_clock = RCC_APB1Periph_TIM4, + .init = TIM_OC1Init, + .preload = TIM_OC1PreloadConfig}, + .afcfg = {GPIOD, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF2_TIM4}, + }, #elif BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .ctl = { GPIOA, GPIO_Pin_12, true }, - .pwm = { - .output = { GPIOB, GPIO_Pin_14, true }, - .timer = { - .peripheral = TIM12, - .config_clock = RCC_APB1Periph_TIM12, - .init = TIM_OC1Init, - .preload = TIM_OC1PreloadConfig - }, - .afcfg = { GPIOB, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF9_TIM12 }, - }, + .ctl = {GPIOA, GPIO_Pin_12, true}, + .pwm = + { + .output = {GPIOB, GPIO_Pin_14, true}, + .timer = {.peripheral = TIM12, + .config_clock = RCC_APB1Periph_TIM12, + .init = TIM_OC1Init, + .preload = TIM_OC1PreloadConfig}, + .afcfg = {GPIOB, GPIO_Pin_14, GPIO_PinSource14, GPIO_AF9_TIM12}, + }, #else #error "Unknown board" #endif }; static const BoardConfigActuator BOARD_CONFIG_BACKLIGHT = { - .options = ActuatorOptions_Pwm, - .ctl = {0}, - .pwm = { - .output = { GPIOG, GPIO_Pin_13, true }, - .timer = { - .lp_peripheral = LPTIM1, - .config_clock = RCC_APB1Periph_LPTIM1, - }, - .afcfg = { GPIOG, GPIO_Pin_13, GPIO_PinSource13, GPIO_AF3_LPTIM1 }, - }, + .options = ActuatorOptions_Pwm, + .ctl = {0}, + .pwm = + { + .output = {GPIOG, GPIO_Pin_13, true}, + .timer = + { + .lp_peripheral = LPTIM1, + .config_clock = RCC_APB1Periph_LPTIM1, + }, + .afcfg = {GPIOG, GPIO_Pin_13, GPIO_PinSource13, GPIO_AF3_LPTIM1}, + }, }; static const BoardConfigAccessory BOARD_CONFIG_ACCESSORY = { #if BOARD_ROBERT_BB || BOARD_CUTTS_BB - .power_en = { GPIOA, GPIO_Pin_11, true }, + .power_en = {GPIOA, GPIO_Pin_11, true}, #elif BOARD_ROBERT_BB2 || BOARD_ROBERT_EVT - .power_en = { GPIOD, GPIO_Pin_2, true }, + .power_en = {GPIOD, GPIO_Pin_2, true}, #else #error "Unknown board" #endif - .int_gpio = { GPIOH, GPIO_Pin_13 }, - .exti = { EXTI_PortSourceGPIOH, 13 }, + .int_gpio = {GPIOH, GPIO_Pin_13}, + .exti = {EXTI_PortSourceGPIOH, 13}, }; static const BoardConfigBTCommon BOARD_CONFIG_BT_COMMON = { - .controller = DA14681, - .reset = { GPIOG, GPIO_Pin_0, true }, - .wakeup = { - .int_gpio = { GPIOG, GPIO_Pin_1 }, - .int_exti = { EXTI_PortSourceGPIOG, 1 }, - }, + .controller = DA14681, + .reset = {GPIOG, GPIO_Pin_0, true}, + .wakeup = + { + .int_gpio = {GPIOG, GPIO_Pin_1}, + .int_exti = {EXTI_PortSourceGPIOG, 1}, + }, }; static const BoardConfigBTUART BOARD_CONFIG_BT_UART = { - .rx_af_cfg = { GPIOE, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF8_UART8 }, - .tx_af_cfg = { GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF8_UART8 }, - .rx_clk_control = RCC_APB1Periph_UART8, - .tx_clk_control = RCC_APB1Periph_UART8, - .rx_uart = UART8, - .tx_uart = UART8, + .rx_af_cfg = {GPIOE, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF8_UART8}, + .tx_af_cfg = {GPIOE, GPIO_Pin_1, GPIO_PinSource1, GPIO_AF8_UART8}, + .rx_clk_control = RCC_APB1Periph_UART8, + .tx_clk_control = RCC_APB1Periph_UART8, + .rx_uart = UART8, + .tx_uart = UART8, }; static const BoardConfigBTSPI BOARD_CONFIG_BT_SPI = { - .cs = { GPIOE, GPIO_Pin_11, false }, + .cs = {GPIOE, GPIO_Pin_11, false}, }; static const BoardConfigMCO1 BOARD_CONFIG_MCO1 = { - .output_enabled = true, - .af_cfg = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF0_MCO, - }, - .an_cfg = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - }, + .output_enabled = true, + .af_cfg = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF0_MCO, + }, + .an_cfg = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + }, }; #define DIALOG_TIMER_IRQ_HANDLER TIM6_DAC_IRQHandler static const TimerIrqConfig BOARD_BT_WATCHDOG_TIMER = { - .timer = { - .peripheral = TIM6, - .config_clock = RCC_APB1Periph_TIM6, - }, - .irq_channel = TIM6_DAC_IRQn, + .timer = + { + .peripheral = TIM6, + .config_clock = RCC_APB1Periph_TIM6, + }, + .irq_channel = TIM6_DAC_IRQn, }; -extern DMARequest * const COMPOSITOR_DMA; +extern DMARequest *const COMPOSITOR_DMA; + +extern UARTDevice *const QEMU_UART; +extern UARTDevice *const DBG_UART; +extern UARTDevice *const ACCESSORY_UART; +extern UARTDevice *const BT_TX_BOOTROM_UART; +extern UARTDevice *const BT_RX_BOOTROM_UART; + +extern SPISlavePort *const BMI160_SPI; -extern UARTDevice * const QEMU_UART; -extern UARTDevice * const DBG_UART; -extern UARTDevice * const ACCESSORY_UART; -extern UARTDevice * const BT_TX_BOOTROM_UART; -extern UARTDevice * const BT_RX_BOOTROM_UART; +extern I2CSlavePort *const I2C_MAX14690; +extern I2CSlavePort *const I2C_MAG3110; +extern I2CSlavePort *const I2C_AS7000; -extern SPISlavePort * const BMI160_SPI; +extern VoltageMonitorDevice *const VOLTAGE_MONITOR_ALS; +extern VoltageMonitorDevice *const VOLTAGE_MONITOR_BATTERY; -extern I2CSlavePort * const I2C_MAX14690; -extern I2CSlavePort * const I2C_MAG3110; +extern TemperatureSensor *const TEMPERATURE_SENSOR; -extern VoltageMonitorDevice * const VOLTAGE_MONITOR_ALS; -extern VoltageMonitorDevice * const VOLTAGE_MONITOR_BATTERY; +extern QSPIPort *const QSPI; +extern QSPIFlash *const QSPI_FLASH; -extern TemperatureSensor * const TEMPERATURE_SENSOR; +extern ICE40LPDevice *const ICE40LP; +extern SPISlavePort *const DIALOG_SPI; -extern QSPIPort * const QSPI; -extern QSPIFlash * const QSPI_FLASH; +extern MicDevice *const MIC; -extern ICE40LPDevice * const ICE40LP; -extern SPISlavePort * const DIALOG_SPI; +extern HRMDevice *const HRM; #if BOARD_CUTTS_BB -extern TouchSensor * const EWD1000; +extern TouchSensor *const EWD1000; #endif diff --git a/src/fw/board/boards/board_silk.c b/src/fw/board/boards/board_silk.c index ad9296c5a..279a4a571 100644 --- a/src/fw/board/boards/board_silk.c +++ b/src/fw/board/boards/board_silk.c @@ -15,9 +15,9 @@ */ #include "board/board.h" - #include "drivers/exti.h" #include "drivers/flash/qspi_flash_definitions.h" +#include "drivers/hrm/as7000.h" #include "drivers/i2c_definitions.h" #include "drivers/qspi_definitions.h" #include "drivers/stm32f2/dma_definitions.h" @@ -38,27 +38,27 @@ _Static_assert(DIALOG_SPI_DMA_PRIORITY < EXTI_PRIORITY, "Dialog SPI DMA priority static DMAControllerState s_dma1_state; static DMAController DMA1_DEVICE = { - .state = &s_dma1_state, - .periph = DMA1, - .rcc_bit = RCC_AHB1Periph_DMA1, + .state = &s_dma1_state, + .periph = DMA1, + .rcc_bit = RCC_AHB1Periph_DMA1, }; static DMAControllerState s_dma2_state; static DMAController DMA2_DEVICE = { - .state = &s_dma2_state, - .periph = DMA2, - .rcc_bit = RCC_AHB1Periph_DMA2, + .state = &s_dma2_state, + .periph = DMA2, + .rcc_bit = RCC_AHB1Periph_DMA2, }; // DMA Streams -CREATE_DMA_STREAM(1, 4); // DMA2_STREAM2_DEVICE - Sharp SPI TX -CREATE_DMA_STREAM(2, 1); // DMA1_STREAM2_DEVICE - Accessory UART RX -CREATE_DMA_STREAM(2, 2); // DMA1_STREAM1_DEVICE - Debug UART RX -CREATE_DMA_STREAM(2, 3); // DMA2_STREAM0_DEVICE - Dialog SPI RX -CREATE_DMA_STREAM(2, 5); // DMA2_STREAM1_DEVICE - Dialog SPI TX -CREATE_DMA_STREAM(2, 6); // DMA2_STREAM4_DEVICE - DFSDM -CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI +CREATE_DMA_STREAM(1, 4); // DMA2_STREAM2_DEVICE - Sharp SPI TX +CREATE_DMA_STREAM(2, 1); // DMA1_STREAM2_DEVICE - Accessory UART RX +CREATE_DMA_STREAM(2, 2); // DMA1_STREAM1_DEVICE - Debug UART RX +CREATE_DMA_STREAM(2, 3); // DMA2_STREAM0_DEVICE - Dialog SPI RX +CREATE_DMA_STREAM(2, 5); // DMA2_STREAM1_DEVICE - Dialog SPI TX +CREATE_DMA_STREAM(2, 6); // DMA2_STREAM4_DEVICE - DFSDM +CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI // DMA Requests // - On DMA1 we just have have "Sharp SPI TX" so just set its priority to "High" since it doesn't @@ -71,351 +71,341 @@ CREATE_DMA_STREAM(2, 7); // DMA2_STREAM7_DEVICE - QSPI static DMARequestState s_sharp_spi_tx_dma_request_state; static DMARequest SHARP_SPI_TX_DMA_REQUEST = { - .state = &s_sharp_spi_tx_dma_request_state, - .stream = &DMA1_STREAM4_DEVICE, - .channel = 0, - .irq_priority = 0x0f, - .priority = DMARequestPriority_High, - .type = DMARequestType_MemoryToPeripheral, - .data_size = DMARequestDataSize_Byte, + .state = &s_sharp_spi_tx_dma_request_state, + .stream = &DMA1_STREAM4_DEVICE, + .channel = 0, + .irq_priority = 0x0f, + .priority = DMARequestPriority_High, + .type = DMARequestType_MemoryToPeripheral, + .data_size = DMARequestDataSize_Byte, }; -DMARequest * const SHARP_SPI_TX_DMA = &SHARP_SPI_TX_DMA_REQUEST; +DMARequest *const SHARP_SPI_TX_DMA = &SHARP_SPI_TX_DMA_REQUEST; static DMARequestState s_accessory_uart_dma_request_state; static DMARequest ACCESSORY_UART_RX_DMA_REQUEST = { - .state = &s_accessory_uart_dma_request_state, - .stream = &DMA2_STREAM1_DEVICE, - .channel = 5, - .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_accessory_uart_dma_request_state, + .stream = &DMA2_STREAM1_DEVICE, + .channel = 5, + .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dbg_uart_dma_request_state; static DMARequest DBG_UART_RX_DMA_REQUEST = { - .state = &s_dbg_uart_dma_request_state, - .stream = &DMA2_STREAM2_DEVICE, - .channel = 4, - .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_dbg_uart_dma_request_state, + .stream = &DMA2_STREAM2_DEVICE, + .channel = 4, + .irq_priority = IRQ_PRIORITY_INVALID, // no interrupts + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dialog_spi_rx_dma_request_state; static DMARequest DIALOG_SPI_RX_DMA_REQUEST = { - .state = &s_dialog_spi_rx_dma_request_state, - .stream = &DMA2_STREAM3_DEVICE, - .channel = 2, - .irq_priority = DIALOG_SPI_DMA_PRIORITY, - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Byte, + .state = &s_dialog_spi_rx_dma_request_state, + .stream = &DMA2_STREAM3_DEVICE, + .channel = 2, + .irq_priority = DIALOG_SPI_DMA_PRIORITY, + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dialog_spi_tx_dma_request_state; static DMARequest DIALOG_SPI_TX_DMA_REQUEST = { - .state = &s_dialog_spi_tx_dma_request_state, - .stream = &DMA2_STREAM5_DEVICE, - .channel = 5, - .irq_priority = DIALOG_SPI_DMA_PRIORITY, - .priority = DMARequestPriority_High, - .type = DMARequestType_MemoryToPeripheral, - .data_size = DMARequestDataSize_Byte, + .state = &s_dialog_spi_tx_dma_request_state, + .stream = &DMA2_STREAM5_DEVICE, + .channel = 5, + .irq_priority = DIALOG_SPI_DMA_PRIORITY, + .priority = DMARequestPriority_High, + .type = DMARequestType_MemoryToPeripheral, + .data_size = DMARequestDataSize_Byte, }; static DMARequestState s_dfsdm_dma_request_state; static DMARequest DFSDM_DMA_REQUEST = { - .state = &s_dfsdm_dma_request_state, - .stream = &DMA2_STREAM6_DEVICE, - .channel = 3, - .irq_priority = 0x0f, - .priority = DMARequestPriority_VeryHigh, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Word, + .state = &s_dfsdm_dma_request_state, + .stream = &DMA2_STREAM6_DEVICE, + .channel = 3, + .irq_priority = 0x0f, + .priority = DMARequestPriority_VeryHigh, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Word, }; static DMARequestState s_qspi_dma_request_state; static DMARequest QSPI_DMA_REQUEST = { - .state = &s_qspi_dma_request_state, - .stream = &DMA2_STREAM7_DEVICE, - .channel = 3, - .irq_priority = 0x0f, - .priority = DMARequestPriority_High, - .type = DMARequestType_PeripheralToMemory, - .data_size = DMARequestDataSize_Word, + .state = &s_qspi_dma_request_state, + .stream = &DMA2_STREAM7_DEVICE, + .channel = 3, + .irq_priority = 0x0f, + .priority = DMARequestPriority_High, + .type = DMARequestType_PeripheralToMemory, + .data_size = DMARequestDataSize_Word, }; - // UART DEVICES static UARTDeviceState s_bt_bootrom_rx_uart_state; static UARTDevice BT_RX_BOOTROM_UART_DEVICE = { - .state = &s_bt_bootrom_rx_uart_state, - .periph = USART6, - .rx_gpio = { GPIOA, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF_USART6 }, - .rcc_apb_periph = RCC_APB2Periph_USART6, - .tx_gpio = { 0 } -}; + .state = &s_bt_bootrom_rx_uart_state, + .periph = USART6, + .rx_gpio = {GPIOA, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF_USART6}, + .rcc_apb_periph = RCC_APB2Periph_USART6, + .tx_gpio = {0}}; static UARTDeviceState s_bt_bootrom_tx_uart_state; static UARTDevice BT_TX_BOOTROM_UART_DEVICE = { - .state = &s_bt_bootrom_tx_uart_state, - .periph = USART2, - .tx_gpio = { GPIOA, GPIO_Pin_2, GPIO_PinSource2, GPIO_AF_USART2 }, - .rcc_apb_periph = RCC_APB1Periph_USART2, - .rx_gpio = { 0 } -}; + .state = &s_bt_bootrom_tx_uart_state, + .periph = USART2, + .tx_gpio = {GPIOA, GPIO_Pin_2, GPIO_PinSource2, GPIO_AF_USART2}, + .rcc_apb_periph = RCC_APB1Periph_USART2, + .rx_gpio = {0}}; -UARTDevice * const BT_TX_BOOTROM_UART = &BT_TX_BOOTROM_UART_DEVICE; -UARTDevice * const BT_RX_BOOTROM_UART = &BT_RX_BOOTROM_UART_DEVICE; +UARTDevice *const BT_TX_BOOTROM_UART = &BT_TX_BOOTROM_UART_DEVICE; +UARTDevice *const BT_RX_BOOTROM_UART = &BT_RX_BOOTROM_UART_DEVICE; #if TARGET_QEMU static UARTDeviceState s_qemu_uart_state; static UARTDevice QEMU_UART_DEVICE = { - .state = &s_qemu_uart_state, - // GPIO? Where we're going, we don't need GPIO. (connected to QEMU) - .periph = USART2, - .irq_channel = USART2_IRQn, - .irq_priority = 13, - .rcc_apb_periph = RCC_APB1Periph_USART2 -}; -UARTDevice * const QEMU_UART = &QEMU_UART_DEVICE; + .state = &s_qemu_uart_state, + // GPIO? Where we're going, we don't need GPIO. (connected to QEMU) + .periph = USART2, + .irq_channel = USART2_IRQn, + .irq_priority = 13, + .rcc_apb_periph = RCC_APB1Periph_USART2}; +UARTDevice *const QEMU_UART = &QEMU_UART_DEVICE; IRQ_MAP(USART2, uart_irq_handler, QEMU_UART); #endif static UARTDeviceState s_dbg_uart_state; -static UARTDevice DBG_UART_DEVICE = { - .state = &s_dbg_uart_state, - .tx_gpio = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_9, - .gpio_pin_source = GPIO_PinSource9, - .gpio_af = GPIO_AF_USART1 - }, - .rx_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_7, - .gpio_pin_source = GPIO_PinSource7, - .gpio_af = GPIO_AF_USART1 - }, - .periph = USART1, - .irq_channel = USART1_IRQn, - .irq_priority = 13, - .rcc_apb_periph = RCC_APB2Periph_USART1, - .rx_dma = &DBG_UART_RX_DMA_REQUEST -}; -UARTDevice * const DBG_UART = &DBG_UART_DEVICE; +static UARTDevice DBG_UART_DEVICE = {.state = &s_dbg_uart_state, + .tx_gpio = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_9, + .gpio_pin_source = GPIO_PinSource9, + .gpio_af = GPIO_AF_USART1}, + .rx_gpio = {.gpio = GPIOB, + .gpio_pin = GPIO_Pin_7, + .gpio_pin_source = GPIO_PinSource7, + .gpio_af = GPIO_AF_USART1}, + .periph = USART1, + .irq_channel = USART1_IRQn, + .irq_priority = 13, + .rcc_apb_periph = RCC_APB2Periph_USART1, + .rx_dma = &DBG_UART_RX_DMA_REQUEST}; +UARTDevice *const DBG_UART = &DBG_UART_DEVICE; IRQ_MAP(USART1, uart_irq_handler, DBG_UART); static UARTDeviceState s_accessory_uart_state; -static UARTDevice ACCESSORY_UART_DEVICE = { - .state = &s_accessory_uart_state, - .half_duplex = true, - .tx_gpio = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_11, - .gpio_pin_source = GPIO_PinSource11, - .gpio_af = GPIO_AF_USART6 - }, - .periph = USART6, - .irq_channel = USART6_IRQn, - .irq_priority = 0xb, - .rcc_apb_periph = RCC_APB2Periph_USART6, - .rx_dma = &ACCESSORY_UART_RX_DMA_REQUEST -}; -UARTDevice * const ACCESSORY_UART = &ACCESSORY_UART_DEVICE; +static UARTDevice ACCESSORY_UART_DEVICE = {.state = &s_accessory_uart_state, + .half_duplex = true, + .tx_gpio = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_11, + .gpio_pin_source = GPIO_PinSource11, + .gpio_af = GPIO_AF_USART6}, + .periph = USART6, + .irq_channel = USART6_IRQn, + .irq_priority = 0xb, + .rcc_apb_periph = RCC_APB2Periph_USART6, + .rx_dma = &ACCESSORY_UART_RX_DMA_REQUEST}; +UARTDevice *const ACCESSORY_UART = &ACCESSORY_UART_DEVICE; IRQ_MAP(USART6, uart_irq_handler, ACCESSORY_UART); - // I2C DEVICES static I2CBusState I2C_PMIC_HRM_BUS_STATE = {}; static const I2CBusHal I2C_PMIC_HRM_BUS_HAL = { - .i2c = I2C3, - .clock_ctrl = RCC_APB1Periph_I2C3, - .clock_speed = 400000, - .duty_cycle = I2CDutyCycle_2, - .ev_irq_channel = I2C3_EV_IRQn, - .er_irq_channel = I2C3_ER_IRQn, + .i2c = I2C3, + .clock_ctrl = RCC_APB1Periph_I2C3, + .clock_speed = 400000, + .duty_cycle = I2CDutyCycle_2, + .ev_irq_channel = I2C3_EV_IRQn, + .er_irq_channel = I2C3_ER_IRQn, }; -static const I2CBus I2C_PMIC_HRM_BUS = { - .state = &I2C_PMIC_HRM_BUS_STATE, - .hal = &I2C_PMIC_HRM_BUS_HAL, - .scl_gpio = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF_I2C3 - }, - .sda_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF9_I2C3 - }, - .stop_mode_inhibitor = InhibitorI2C3, - .name = "I2C_PMIC" -}; +static const I2CBus I2C_PMIC_HRM_BUS = {.state = &I2C_PMIC_HRM_BUS_STATE, + .hal = &I2C_PMIC_HRM_BUS_HAL, + .scl_gpio = {.gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF_I2C3}, + .sda_gpio = {.gpio = GPIOB, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF9_I2C3}, + .stop_mode_inhibitor = InhibitorI2C3, + .name = "I2C_PMIC"}; -static const I2CSlavePort I2C_SLAVE_AS3701B = { - .bus = &I2C_PMIC_HRM_BUS, - .address = 0x80 -}; +static const I2CSlavePort I2C_SLAVE_AS3701B = {.bus = &I2C_PMIC_HRM_BUS, .address = 0x80}; + +static const I2CSlavePort I2C_SLAVE_AS7000 = {.bus = &I2C_PMIC_HRM_BUS, .address = 0x60}; -I2CSlavePort * const I2C_AS3701B = &I2C_SLAVE_AS3701B; +I2CSlavePort *const I2C_AS3701B = &I2C_SLAVE_AS3701B; +I2CSlavePort *const I2C_AS7000 = &I2C_SLAVE_AS7000; IRQ_MAP(I2C3_EV, i2c_hal_event_irq_handler, &I2C_PMIC_HRM_BUS); IRQ_MAP(I2C3_ER, i2c_hal_error_irq_handler, &I2C_PMIC_HRM_BUS); - // VOLTAGE MONITOR DEVICES static const VoltageMonitorDevice VOLTAGE_MONITOR_ALS_DEVICE = { - .adc = ADC1, - .adc_channel = ADC_Channel_13, - .clock_ctrl = RCC_APB2Periph_ADC1, - .input = { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_3, - }, + .adc = ADC1, + .adc_channel = ADC_Channel_13, + .clock_ctrl = RCC_APB2Periph_ADC1, + .input = + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_3, + }, }; static const VoltageMonitorDevice VOLTAGE_MONITOR_BATTERY_DEVICE = { - .adc = ADC1, - .adc_channel = ADC_Channel_5, - .clock_ctrl = RCC_APB2Periph_ADC1, - .input = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_5, - }, + .adc = ADC1, + .adc_channel = ADC_Channel_5, + .clock_ctrl = RCC_APB2Periph_ADC1, + .input = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_5, + }, }; static const VoltageMonitorDevice VOLTAGE_MONITOR_TEMPERATURE_DEVICE = { - .adc = ADC1, - .adc_channel = ADC_Channel_TempSensor, - .clock_ctrl = RCC_APB2Periph_ADC1, - // .input not applicable + .adc = ADC1, .adc_channel = ADC_Channel_TempSensor, .clock_ctrl = RCC_APB2Periph_ADC1, + // .input not applicable }; -const VoltageMonitorDevice * VOLTAGE_MONITOR_ALS = &VOLTAGE_MONITOR_ALS_DEVICE; -const VoltageMonitorDevice * VOLTAGE_MONITOR_BATTERY = &VOLTAGE_MONITOR_BATTERY_DEVICE; -const VoltageMonitorDevice * VOLTAGE_MONITOR_TEMPERATURE = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE; +const VoltageMonitorDevice *VOLTAGE_MONITOR_ALS = &VOLTAGE_MONITOR_ALS_DEVICE; +const VoltageMonitorDevice *VOLTAGE_MONITOR_BATTERY = &VOLTAGE_MONITOR_BATTERY_DEVICE; +const VoltageMonitorDevice *VOLTAGE_MONITOR_TEMPERATURE = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE; // Temperature sensor // STM32F412 datasheet rev 2 // Section 6.3.21 TemperatureSensor const TEMPERATURE_SENSOR_DEVICE = { - .voltage_monitor = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE, - .millivolts_ref = 760, - .millidegrees_ref = 25000, - .slope_numerator = 5, - .slope_denominator = 2000, + .voltage_monitor = &VOLTAGE_MONITOR_TEMPERATURE_DEVICE, + .millivolts_ref = 760, + .millidegrees_ref = 25000, + .slope_numerator = 5, + .slope_denominator = 2000, }; -TemperatureSensor * const TEMPERATURE_SENSOR = &TEMPERATURE_SENSOR_DEVICE; - +TemperatureSensor *const TEMPERATURE_SENSOR = &TEMPERATURE_SENSOR_DEVICE; // // SPI Bus configuration // -static SPIBusState DIALOG_SPI_BUS_STATE = { }; +static SPIBusState DIALOG_SPI_BUS_STATE = {}; static const SPIBus DIALOG_SPI_BUS = { - .state = &DIALOG_SPI_BUS_STATE, - .spi = SPI5, - .spi_sclk = { GPIOB, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF6_SPI5 }, - .spi_miso = { GPIOA, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF6_SPI5 }, - .spi_mosi = { GPIOA, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF6_SPI5 }, - .spi_sclk_speed = GPIO_Speed_50MHz, - // DA14680_FS v1.4 page 89: - // "In slave mode the internal SPI clock must be more than four times the SPIx_CLK" - // The system clock is 16MHz, so don't use more than 4MHz. - .spi_clock_speed_hz = MHZ_TO_HZ(4) -}; + .state = &DIALOG_SPI_BUS_STATE, + .spi = SPI5, + .spi_sclk = {GPIOB, GPIO_Pin_0, GPIO_PinSource0, GPIO_AF6_SPI5}, + .spi_miso = {GPIOA, GPIO_Pin_12, GPIO_PinSource12, GPIO_AF6_SPI5}, + .spi_mosi = {GPIOA, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF6_SPI5}, + .spi_sclk_speed = GPIO_Speed_50MHz, + // DA14680_FS v1.4 page 89: + // "In slave mode the internal SPI clock must be more than four times the SPIx_CLK" + // The system clock is 16MHz, so don't use more than 4MHz. + .spi_clock_speed_hz = MHZ_TO_HZ(4)}; // // SPI Slave port configuration // static SPISlavePortState DIALOG_SPI_SLAVE_PORT_STATE = {}; -static SPISlavePort DIALOG_SPI_SLAVE_PORT = { - .slave_state = &DIALOG_SPI_SLAVE_PORT_STATE, - .spi_bus = &DIALOG_SPI_BUS, - .spi_scs = { GPIOB, GPIO_Pin_1, false }, - .spi_direction = SpiDirection_2LinesFullDuplex, - .spi_cpol = SpiCPol_Low, - .spi_cpha = SpiCPha_1Edge, - .spi_first_bit = SpiFirstBit_MSB, - .rx_dma = &DIALOG_SPI_RX_DMA_REQUEST, - .tx_dma = &DIALOG_SPI_TX_DMA_REQUEST +static SPISlavePort DIALOG_SPI_SLAVE_PORT = {.slave_state = &DIALOG_SPI_SLAVE_PORT_STATE, + .spi_bus = &DIALOG_SPI_BUS, + .spi_scs = {GPIOB, GPIO_Pin_1, false}, + .spi_direction = SpiDirection_2LinesFullDuplex, + .spi_cpol = SpiCPol_Low, + .spi_cpha = SpiCPha_1Edge, + .spi_first_bit = SpiFirstBit_MSB, + .rx_dma = &DIALOG_SPI_RX_DMA_REQUEST, + .tx_dma = &DIALOG_SPI_TX_DMA_REQUEST}; +SPISlavePort *const DIALOG_SPI = &DIALOG_SPI_SLAVE_PORT; + +// HRM DEVICE +static HRMDeviceState s_hrm_state; +static HRMDevice HRM_DEVICE = { + .state = &s_hrm_state, + .handshake_int = {EXTI_PortSourceGPIOA, 15}, + .int_gpio = {.gpio = GPIOA, .gpio_pin = GPIO_Pin_15}, + .en_gpio = + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_1, + .active_high = false, + }, + .i2c_slave = &I2C_SLAVE_AS7000, }; -SPISlavePort * const DIALOG_SPI = &DIALOG_SPI_SLAVE_PORT; - +HRMDevice *const HRM = &HRM_DEVICE; // QSPI static QSPIPortState s_qspi_port_state; static QSPIPort QSPI_PORT = { - .state = &s_qspi_port_state, - .clock_speed_hz = MHZ_TO_HZ(50), - .auto_polling_interval = 16, - .clock_ctrl = RCC_AHB3Periph_QSPI, - .cs_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_6, - .gpio_pin_source = GPIO_PinSource6, - .gpio_af = GPIO_AF10_QUADSPI, - }, - .clk_gpio = { - .gpio = GPIOB, - .gpio_pin = GPIO_Pin_2, - .gpio_pin_source = GPIO_PinSource2, - .gpio_af = GPIO_AF9_QUADSPI, - }, - .data_gpio = { - { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_9, - .gpio_pin_source = GPIO_PinSource9, - .gpio_af = GPIO_AF9_QUADSPI, - }, - { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_10, - .gpio_pin_source = GPIO_PinSource10, - .gpio_af = GPIO_AF9_QUADSPI, - }, - { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF9_QUADSPI, - }, - { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_1, - .gpio_pin_source = GPIO_PinSource1, - .gpio_af = GPIO_AF9_QUADSPI, - }, - }, - .dma = &QSPI_DMA_REQUEST, + .state = &s_qspi_port_state, + .clock_speed_hz = MHZ_TO_HZ(50), + .auto_polling_interval = 16, + .clock_ctrl = RCC_AHB3Periph_QSPI, + .cs_gpio = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_6, + .gpio_pin_source = GPIO_PinSource6, + .gpio_af = GPIO_AF10_QUADSPI, + }, + .clk_gpio = + { + .gpio = GPIOB, + .gpio_pin = GPIO_Pin_2, + .gpio_pin_source = GPIO_PinSource2, + .gpio_af = GPIO_AF9_QUADSPI, + }, + .data_gpio = + { + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_9, + .gpio_pin_source = GPIO_PinSource9, + .gpio_af = GPIO_AF9_QUADSPI, + }, + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_10, + .gpio_pin_source = GPIO_PinSource10, + .gpio_af = GPIO_AF9_QUADSPI, + }, + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF9_QUADSPI, + }, + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_1, + .gpio_pin_source = GPIO_PinSource1, + .gpio_af = GPIO_AF9_QUADSPI, + }, + }, + .dma = &QSPI_DMA_REQUEST, }; -QSPIPort * const QSPI = &QSPI_PORT; +QSPIPort *const QSPI = &QSPI_PORT; static QSPIFlashState s_qspi_flash_state; static QSPIFlash QSPI_FLASH_DEVICE = { - .state = &s_qspi_flash_state, - .qspi = &QSPI_PORT, - .default_fast_read_ddr_enabled = false, - .reset_gpio = { GPIO_Port_NULL }, + .state = &s_qspi_flash_state, + .qspi = &QSPI_PORT, + .default_fast_read_ddr_enabled = false, + .reset_gpio = {GPIO_Port_NULL}, }; -QSPIFlash * const QSPI_FLASH = &QSPI_FLASH_DEVICE; +QSPIFlash *const QSPI_FLASH = &QSPI_FLASH_DEVICE; - -void board_early_init(void) { -} +void board_early_init(void) {} void board_init(void) { i2c_init(&I2C_PMIC_HRM_BUS); diff --git a/src/fw/board/boards/board_silk.h b/src/fw/board/boards/board_silk.h index 00e644af9..9cbfc73a1 100644 --- a/src/fw/board/boards/board_silk.h +++ b/src/fw/board/boards/board_silk.h @@ -22,220 +22,229 @@ #define BOARD_LSE_MODE RCC_LSE_Bypass static const BoardConfig BOARD_CONFIG = { - .ambient_light_dark_threshold = 150, - .ambient_k_delta_threshold = 50, - .photo_en = { GPIOC, GPIO_Pin_0, true }, - .als_always_on = true, + .ambient_light_dark_threshold = 150, + .ambient_k_delta_threshold = 50, + .photo_en = {GPIOC, GPIO_Pin_0, true}, + .als_always_on = true, - .dbgserial_int = { EXTI_PortSourceGPIOB, 5 }, + .dbgserial_int = {EXTI_PortSourceGPIOB, 5}, - // new sharp display requires 30/60Hz so we feed it directly from PMIC - .lcd_com = { 0 }, + // new sharp display requires 30/60Hz so we feed it directly from PMIC + .lcd_com = {0}, - .backlight_on_percent = 25, - .backlight_max_duty_cycle_percent = 67, + .backlight_on_percent = 25, + .backlight_max_duty_cycle_percent = 67, - .power_5v0_options = OptionNotPresent, - .power_ctl_5v0 = { 0 }, + .power_5v0_options = OptionNotPresent, + .power_ctl_5v0 = {0}, - .has_mic = true, + .has_mic = true, }; static const BoardConfigButton BOARD_CONFIG_BUTTON = { - .buttons = { - [BUTTON_ID_BACK] = - { "Back", GPIOC, GPIO_Pin_13, { EXTI_PortSourceGPIOC, 13 }, GPIO_PuPd_NOPULL }, - [BUTTON_ID_UP] = - { "Up", GPIOD, GPIO_Pin_2, { EXTI_PortSourceGPIOD, 2 }, GPIO_PuPd_DOWN }, - [BUTTON_ID_SELECT] = - { "Select", GPIOH, GPIO_Pin_0, { EXTI_PortSourceGPIOH, 0 }, GPIO_PuPd_DOWN }, - [BUTTON_ID_DOWN] = - { "Down", GPIOH, GPIO_Pin_1, { EXTI_PortSourceGPIOH, 1 }, GPIO_PuPd_DOWN }, - }, - .button_com = { 0 }, - .active_high = true, + .buttons = + { + [BUTTON_ID_BACK] = + {"Back", GPIOC, GPIO_Pin_13, {EXTI_PortSourceGPIOC, 13}, GPIO_PuPd_NOPULL}, + [BUTTON_ID_UP] = {"Up", GPIOD, GPIO_Pin_2, {EXTI_PortSourceGPIOD, 2}, GPIO_PuPd_DOWN}, + [BUTTON_ID_SELECT] = + {"Select", GPIOH, GPIO_Pin_0, {EXTI_PortSourceGPIOH, 0}, GPIO_PuPd_DOWN}, + [BUTTON_ID_DOWN] = + {"Down", GPIOH, GPIO_Pin_1, {EXTI_PortSourceGPIOH, 1}, GPIO_PuPd_DOWN}, + }, + .button_com = {0}, + .active_high = true, }; static const BoardConfigPower BOARD_CONFIG_POWER = { - .pmic_int = { EXTI_PortSourceGPIOC, 7 }, - .pmic_int_gpio = { - .gpio = GPIOC, - .gpio_pin = GPIO_Pin_7, - }, - - .battery_vmon_scale = { - // Battery voltage is scaled down by a pair of resistors: - // - R13 on the top @ 47k - // - R15 on the bottom @ 30.1k - // (R13 + R15) / R15 = 77.1 / 30.1 - .numerator = 771, - .denominator = 301, - }, - - .vusb_stat = { .gpio = GPIO_Port_NULL, }, - .chg_stat = { GPIO_Port_NULL }, - .chg_fast = { GPIO_Port_NULL }, - .chg_en = { GPIO_Port_NULL }, - .has_vusb_interrupt = false, - - .wake_on_usb_power = false, - - .charging_status_led_voltage_compensation = 0, + .pmic_int = {EXTI_PortSourceGPIOC, 7}, + .pmic_int_gpio = + { + .gpio = GPIOC, + .gpio_pin = GPIO_Pin_7, + }, + + .battery_vmon_scale = + { + // Battery voltage is scaled down by a pair of resistors: + // - R13 on the top @ 47k + // - R15 on the bottom @ 30.1k + // (R13 + R15) / R15 = 77.1 / 30.1 + .numerator = 771, + .denominator = 301, + }, + + .vusb_stat = + { + .gpio = GPIO_Port_NULL, + }, + .chg_stat = {GPIO_Port_NULL}, + .chg_fast = {GPIO_Port_NULL}, + .chg_en = {GPIO_Port_NULL}, + .has_vusb_interrupt = false, + + .wake_on_usb_power = false, + + .charging_status_led_voltage_compensation = 0, #if defined(IS_BIGBOARD) && !defined(BATTERY_DEBUG) - // We don't use the same batteries on all bigboards, so set a safe cutoff voltage of 4.2V. - // Please do not change this! - .charging_cutoff_voltage = 4200, + // We don't use the same batteries on all bigboards, so set a safe cutoff voltage of 4.2V. + // Please do not change this! + .charging_cutoff_voltage = 4200, #else - .charging_cutoff_voltage = 4300, + .charging_cutoff_voltage = 4300, #endif - .low_power_threshold = 5, + .low_power_threshold = 5, - // Based on measurements from v4.0-beta16. - // Typical Connected Current at VBAT without HRM ~520uA - // Added draw with HRM on : ~1.5mA ==> Average impact (5% per hour + 1 hour continuous / day) - // (.05 * 23/24 + 1.0 * 1/24) * 1.5mA = ~134uA - // Assume ~150uA or so for notifications & user interaction - // Total Hours = 125 mA * hr / (.520 + .134 + 150)mA = 155 hours - .battery_capacity_hours = 155, + // Based on measurements from v4.0-beta16. + // Typical Connected Current at VBAT without HRM ~520uA + // Added draw with HRM on : ~1.5mA ==> Average impact (5% per hour + 1 hour continuous / day) + // (.05 * 23/24 + 1.0 * 1/24) * 1.5mA = ~134uA + // Assume ~150uA or so for notifications & user interaction + // Total Hours = 125 mA * hr / (.520 + .134 + 150)mA = 155 hours + .battery_capacity_hours = 155, }; static const BoardConfigAccel BOARD_CONFIG_ACCEL = { - .accel_config = { - .axes_offsets[AXIS_X] = 0, - .axes_offsets[AXIS_Y] = 1, - .axes_offsets[AXIS_Z] = 2, + .accel_config = + { + .axes_offsets[AXIS_X] = 0, + .axes_offsets[AXIS_Y] = 1, + .axes_offsets[AXIS_Z] = 2, #if IS_BIGBOARD - .axes_inverts[AXIS_X] = false, - .axes_inverts[AXIS_Y] = false, - .axes_inverts[AXIS_Z] = false, + .axes_inverts[AXIS_X] = false, + .axes_inverts[AXIS_Y] = false, + .axes_inverts[AXIS_Z] = false, #else - .axes_inverts[AXIS_X] = true, - .axes_inverts[AXIS_Y] = true, - .axes_inverts[AXIS_Z] = true, + .axes_inverts[AXIS_X] = true, + .axes_inverts[AXIS_Y] = true, + .axes_inverts[AXIS_Z] = true, #endif - // This is affected by the acceleromter's configured ODR, so this value - // will need to be tuned again once we stop locking the BMA255 to an ODR of - // 125 Hz. - .shake_thresholds[AccelThresholdHigh] = 64, - .shake_thresholds[AccelThresholdLow] = 0xf, - .double_tap_threshold = 12500, - }, - .accel_int_gpios = { - [0] = { GPIOA, GPIO_Pin_6 }, - [1] = { GPIOA, GPIO_Pin_3 }, - }, - .accel_ints = { - [0] = { EXTI_PortSourceGPIOA, 6 }, - [1] = { EXTI_PortSourceGPIOA, 3 } - }, + // This is affected by the acceleromter's configured ODR, so this value + // will need to be tuned again once we stop locking the BMA255 to an ODR of + // 125 Hz. + .shake_thresholds[AccelThresholdHigh] = 64, + .shake_thresholds[AccelThresholdLow] = 0xf, + .double_tap_threshold = 12500, + }, + .accel_int_gpios = + { + [0] = {GPIOA, GPIO_Pin_6}, + [1] = {GPIOA, GPIO_Pin_3}, + }, + .accel_ints = {[0] = {EXTI_PortSourceGPIOA, 6}, [1] = {EXTI_PortSourceGPIOA, 3}}, }; static const BoardConfigActuator BOARD_CONFIG_VIBE = { - .options = ActuatorOptions_Pwm, - .ctl = { 0 }, - .pwm = { - .output = { GPIOA, GPIO_Pin_7, true }, - .timer = { - .peripheral = TIM14, - .config_clock = RCC_APB1Periph_TIM14, - .init = TIM_OC1Init, - .preload = TIM_OC1PreloadConfig - }, - .afcfg = { GPIOA, GPIO_Pin_7, GPIO_PinSource7, GPIO_AF_TIM14 }, - }, - .vsys_scale = 3300, + .options = ActuatorOptions_Pwm, + .ctl = {0}, + .pwm = + { + .output = {GPIOA, GPIO_Pin_7, true}, + .timer = {.peripheral = TIM14, + .config_clock = RCC_APB1Periph_TIM14, + .init = TIM_OC1Init, + .preload = TIM_OC1PreloadConfig}, + .afcfg = {GPIOA, GPIO_Pin_7, GPIO_PinSource7, GPIO_AF_TIM14}, + }, + .vsys_scale = 3300, }; static const BoardConfigActuator BOARD_CONFIG_BACKLIGHT = { - .options = ActuatorOptions_Pwm | ActuatorOptions_Ctl, - .ctl = { GPIOB, GPIO_Pin_13, true }, - .pwm = { - .output = { GPIOC, GPIO_Pin_6, true }, - .timer = { - .peripheral = TIM3, - .config_clock = RCC_APB1Periph_TIM3, - .init = TIM_OC1Init, - .preload = TIM_OC1PreloadConfig - }, - .afcfg = { GPIOC, GPIO_Pin_6, GPIO_PinSource6, GPIO_AF_TIM3 }, - }, + .options = ActuatorOptions_Pwm | ActuatorOptions_Ctl, + .ctl = {GPIOB, GPIO_Pin_13, true}, + .pwm = + { + .output = {GPIOC, GPIO_Pin_6, true}, + .timer = {.peripheral = TIM3, + .config_clock = RCC_APB1Periph_TIM3, + .init = TIM_OC1Init, + .preload = TIM_OC1PreloadConfig}, + .afcfg = {GPIOC, GPIO_Pin_6, GPIO_PinSource6, GPIO_AF_TIM3}, + }, }; #define ACCESSORY_UART_IS_SHARED_WITH_BT 1 static const BoardConfigAccessory BOARD_CONFIG_ACCESSORY = { - .exti = { EXTI_PortSourceGPIOA, 11 }, + .exti = {EXTI_PortSourceGPIOA, 11}, }; static const BoardConfigBTCommon BOARD_CONFIG_BT_COMMON = { - .controller = DA14681, - .reset = { GPIOC, GPIO_Pin_5, true }, - .wakeup = { - .int_gpio = { GPIOC, GPIO_Pin_4 }, - .int_exti = { EXTI_PortSourceGPIOC, 4 }, - }, + .controller = DA14681, + .reset = {GPIOC, GPIO_Pin_5, true}, + .wakeup = + { + .int_gpio = {GPIOC, GPIO_Pin_4}, + .int_exti = {EXTI_PortSourceGPIOC, 4}, + }, }; static const BoardConfigBTSPI BOARD_CONFIG_BT_SPI = { - .cs = { GPIOB, GPIO_Pin_1, false }, + .cs = {GPIOB, GPIO_Pin_1, false}, }; static const BoardConfigMCO1 BOARD_CONFIG_MCO1 = { - .output_enabled = true, - .af_cfg = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - .gpio_pin_source = GPIO_PinSource8, - .gpio_af = GPIO_AF_MCO, - }, - .an_cfg = { - .gpio = GPIOA, - .gpio_pin = GPIO_Pin_8, - }, + .output_enabled = true, + .af_cfg = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + .gpio_pin_source = GPIO_PinSource8, + .gpio_af = GPIO_AF_MCO, + }, + .an_cfg = + { + .gpio = GPIOA, + .gpio_pin = GPIO_Pin_8, + }, }; static const BoardConfigSharpDisplay BOARD_CONFIG_DISPLAY = { - .spi = SPI2, - .spi_gpio = GPIOB, - .spi_clk = RCC_APB1Periph_SPI2, - .spi_clk_periph = SpiPeriphClockAPB1, + .spi = SPI2, + .spi_gpio = GPIOB, + .spi_clk = RCC_APB1Periph_SPI2, + .spi_clk_periph = SpiPeriphClockAPB1, - .clk = { GPIOB, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF_SPI2 }, - .mosi = { GPIOB, GPIO_Pin_15, GPIO_PinSource15, GPIO_AF_SPI2 }, - .cs = { GPIOB, GPIO_Pin_9, true }, + .clk = {GPIOB, GPIO_Pin_10, GPIO_PinSource10, GPIO_AF_SPI2}, + .mosi = {GPIOB, GPIO_Pin_15, GPIO_PinSource15, GPIO_AF_SPI2}, + .cs = {GPIOB, GPIO_Pin_9, true}, - .on_ctrl = { GPIOA, GPIO_Pin_0, true }, - .on_ctrl_otype = GPIO_OType_PP, + .on_ctrl = {GPIOA, GPIO_Pin_0, true}, + .on_ctrl_otype = GPIO_OType_PP, }; #define DIALOG_TIMER_IRQ_HANDLER TIM6_IRQHandler static const TimerIrqConfig BOARD_BT_WATCHDOG_TIMER = { - .timer = { - .peripheral = TIM6, - .config_clock = RCC_APB1Periph_TIM6, - }, - .irq_channel = TIM6_IRQn, + .timer = + { + .peripheral = TIM6, + .config_clock = RCC_APB1Periph_TIM6, + }, + .irq_channel = TIM6_IRQn, }; -extern DMARequest * const COMPOSITOR_DMA; -extern DMARequest * const SHARP_SPI_TX_DMA; +extern DMARequest *const COMPOSITOR_DMA; +extern DMARequest *const SHARP_SPI_TX_DMA; -extern UARTDevice * const QEMU_UART; -extern UARTDevice * const DBG_UART; -extern UARTDevice * const ACCESSORY_UART; +extern UARTDevice *const QEMU_UART; +extern UARTDevice *const DBG_UART; +extern UARTDevice *const ACCESSORY_UART; -extern UARTDevice * const BT_TX_BOOTROM_UART; -extern UARTDevice * const BT_RX_BOOTROM_UART; +extern UARTDevice *const BT_TX_BOOTROM_UART; +extern UARTDevice *const BT_RX_BOOTROM_UART; -extern I2CSlavePort * const I2C_AS3701B; +extern I2CSlavePort *const I2C_AS3701B; +extern I2CSlavePort *const I2C_AS7000; -extern const VoltageMonitorDevice * VOLTAGE_MONITOR_ALS; -extern const VoltageMonitorDevice * VOLTAGE_MONITOR_BATTERY; +extern const VoltageMonitorDevice *VOLTAGE_MONITOR_ALS; +extern const VoltageMonitorDevice *VOLTAGE_MONITOR_BATTERY; -extern const TemperatureSensor * const TEMPERATURE_SENSOR; +extern const TemperatureSensor *const TEMPERATURE_SENSOR; -extern QSPIPort * const QSPI; -extern QSPIFlash * const QSPI_FLASH; +extern HRMDevice *const HRM; -extern SPISlavePort * const DIALOG_SPI; +extern QSPIPort *const QSPI; +extern QSPIFlash *const QSPI_FLASH; + +extern SPISlavePort *const DIALOG_SPI; diff --git a/src/fw/console/prompt_commands.h b/src/fw/console/prompt_commands.h index be31b8c4f..023f2aff0 100644 --- a/src/fw/console/prompt_commands.h +++ b/src/fw/console/prompt_commands.h @@ -24,24 +24,24 @@ extern void command_help(void); -extern void command_log_level_set(const char*); +extern void command_log_level_set(const char *); extern void command_log_level_get(void); extern void command_log_dump_current(void); extern void command_log_dump_last(void); extern void command_log_dump_spam(void); -extern void command_log_dump_generation(const char*); +extern void command_log_dump_generation(const char *); -extern void command_put_raw_button_event(const char*, const char*); -extern void command_put_button_event(const char*, const char*); -extern void command_button_press(const char*, const char*); +extern void command_put_raw_button_event(const char *, const char *); +extern void command_put_button_event(const char *, const char *); +extern void command_button_press(const char *, const char *); extern void command_button_press_multiple(const char *, const char *, const char *, const char *); -extern void command_button_press_short(const char*); +extern void command_button_press_short(const char *); extern void command_stats_dump_now(void); extern void command_stats_dump_current(void); extern void command_stats_dump_last(void); -extern void command_stats_dump_generation(const char*); +extern void command_stats_dump_generation(const char *); extern void command_crash(void); extern void command_hard_crash(void); @@ -62,14 +62,14 @@ extern void command_dump_malloc_app(void); extern void command_dump_malloc_worker(void); extern void command_dump_malloc_bt(void); -extern void command_read_word(const char*); +extern void command_read_word(const char *); -extern void command_power_2v5(const char*); +extern void command_power_2v5(const char *); -extern void command_backlight_ctl(const char*); -extern void command_rgb_set_color(const char*); +extern void command_backlight_ctl(const char *); +extern void command_rgb_set_color(const char *); -extern void command_battery_charge_option(const char*); +extern void command_battery_charge_option(const char *); extern void command_print_battery_status(void); extern void command_compass_peek(void); @@ -79,21 +79,21 @@ extern void command_accel_status(void); extern void command_accel_selftest(void); extern void command_accel_softreset(void); -extern void command_dump_flash(const char*, const char*); -extern void command_crc_flash(const char*, const char*); +extern void command_dump_flash(const char *, const char *); +extern void command_crc_flash(const char *, const char *); extern void command_format_flash(void); -extern void command_erase_flash(const char*, const char*); -extern void command_flash_read(const char*, const char*); -extern void command_flash_switch_mode(const char*); -extern void command_flash_fill(const char*, const char*, const char*); -extern void command_flash_test(const char* test_case_num_str, const char* iterations_str); +extern void command_erase_flash(const char *, const char *); +extern void command_flash_read(const char *, const char *); +extern void command_flash_switch_mode(const char *); +extern void command_flash_fill(const char *, const char *, const char *); +extern void command_flash_test(const char *test_case_num_str, const char *iterations_str); extern void command_flash_test_locked_sectors(void); extern void command_flash_stress(void); extern void command_flash_validate(void); extern void command_flash_apicheck(const char *len); extern void command_flash_unprotect(void); -//extern void command_flash_signal_test_init(void); -//extern void command_flash_signal_test_run(void); +// extern void command_flash_signal_test_init(void); +// extern void command_flash_signal_test_run(void); extern void command_flash_show_erased_sectors(const char *arg); extern void command_get_time(void); @@ -105,7 +105,7 @@ extern void command_vibe_ctl(const char *arg); // extern void command_print_task_list(void); extern void command_timers(void); -extern void command_bt_airplane_mode(const char*); +extern void command_bt_airplane_mode(const char *); extern void command_bt_prefs_wipe(void); extern void command_bt_print_mac(void); extern void command_bt_set_addr(const char *bd_addr); @@ -122,13 +122,13 @@ extern void command_bt_status(void); extern void command_get_active_app_metadata(void); extern void command_app_list(void); -extern void command_app_launch(const char* app_num_str); -extern void command_app_remove(const char* app_num_str); +extern void command_app_launch(const char *app_num_str); +extern void command_app_remove(const char *app_num_str); -extern void command_worker_launch(const char* app_num_str); +extern void command_worker_launch(const char *app_num_str); extern void command_worker_kill(void); -extern void command_boot_bit_set(const char* bit, const char* value); +extern void command_boot_bit_set(const char *bit, const char *value); extern void command_boot_bits_get(void); extern void command_window_stack_info(void); @@ -136,7 +136,7 @@ extern void command_modal_stack_info(void); extern void command_animations_info(void); extern void command_legacy2_animations_info(void); -extern void command_sim_panic(const char*); +extern void command_sim_panic(const char *); extern void command_alarm(void); @@ -154,7 +154,7 @@ extern void command_selftest(void); extern void command_enter_mfg(void); extern void command_enter_standby(void); extern void command_enter_consumer_mode(void); -extern void command_power_5v(const char*); +extern void command_power_5v(const char *); extern void command_accessory_imaging_start(void); @@ -166,14 +166,14 @@ extern void command_disp_offset_read(void); extern void command_rtcfreq_read(void); extern void command_model_read(void); -extern void command_serial_write(const char*); -extern void command_hwver_write(const char*); -extern void command_pcba_serial_write(const char*); -extern void command_color_write(const char*); -extern void command_disp_offset_write(const char*); -extern void command_rtcfreq_write(const char*); -extern void command_model_write(const char*); -extern void command_bootloader_test(const char*); +extern void command_serial_write(const char *); +extern void command_hwver_write(const char *); +extern void command_pcba_serial_write(const char *); +extern void command_color_write(const char *); +extern void command_disp_offset_write(const char *); +extern void command_rtcfreq_write(const char *); +extern void command_model_write(const char *); +extern void command_bootloader_test(const char *); extern void command_version_info(void); @@ -188,7 +188,7 @@ extern void command_layer_nudge(const char *address); extern void command_scheduler_force_active(void); extern void command_scheduler_resume_normal(void); -extern void command_button_read(const char*); +extern void command_button_read(const char *); extern void memory_layout_dump_mpu_regions_to_dbgserial(void); @@ -234,8 +234,8 @@ extern void command_set_runlevel(const char *runlevel); extern void command_litter_filesystem(void); typedef struct Command { - char* cmd_str; - void* func; + char *cmd_str; + void *func; unsigned int num_params; } Command; @@ -268,22 +268,30 @@ extern void command_low_power_debug(char *enable_arg); extern void command_audit_delay_us(void); extern void command_enter_stop(void); +extern void dialog_test_cmds(void); + extern void command_dump_notif_pref_db(void); -extern void command_bt_conn_param_set( - char *interval_min_ms, char *interval_max_ms, char *slave_latency, char *timeout_ms); +extern void command_bt_conn_param_set(char *interval_min_ms, char *interval_max_ms, + char *slave_latency, char *timeout_ms); extern void command_bt_disc_start(char *start_handle, char *end_handle); extern void command_bt_disc_stop(void); // Commands to support MFG Bluetooth LE-only testing -extern void command_btle_test_le_tx_start( - char *tx_channel, char *tx_packet_length, char *packet_payload_type); +extern void command_btle_test_le_tx_start(char *tx_channel, char *tx_packet_length, + char *packet_payload_type); extern void command_btle_test_rx_start(char *rx_channel); extern void command_btle_test_end(void); extern void command_btle_pa_set(char *option); extern void command_btle_unmod_tx_start(char *tx_channel); extern void command_btle_unmod_tx_stop(void); +#if CAPABILITY_HAS_BUILTIN_HRM +extern void command_hrm_read(void); +extern void command_hrm_wipe(void); +extern void command_hrm_freeze(void); +#endif + #if MFG_INFO_RECORDS_TEST_RESULTS extern void command_mfg_info_test_results(void); #endif @@ -295,7 +303,6 @@ extern void command_perftest_text_all(void); extern void command_bt_sleep_check(const char *iters); - #if PLATFORM_TINTIN && !TARGET_QEMU // We don't have space for anything that's not absolutely required for firmware development // (imaging resources over PULSE). Rip it all out. Note that this breaks test automation on tintin, @@ -308,299 +315,300 @@ extern void command_bt_sleep_check(const char *iters); #define KEEP_NON_ESSENTIAL_COMMANDS 1 #endif static const Command s_prompt_commands[] = { - // PULSE entry point, needed for anything PULSE-related to work - { "PULSEv1", pulse_start, 0 }, + // PULSE entry point, needed for anything PULSE-related to work + {"PULSEv1", pulse_start, 0}, #if KEEP_NON_ESSENTIAL_COMMANDS == 1 - // ==================================================================================== - // NOTE: The following commands are used by test automation. - // Disabling/removing them will break testing against those FW builds. - { "click short", command_button_press_short, 1 }, - { "click multiple", command_button_press_multiple, 4 }, - { "click long", command_button_press, 2 }, - { "reset", command_reset, 0 }, - { "crash", command_crash, 0 }, - { "hard crash", command_hard_crash, 0 }, + // ==================================================================================== + // NOTE: The following commands are used by test automation. + // Disabling/removing them will break testing against those FW builds. + {"click short", command_button_press_short, 1}, + {"click multiple", command_button_press_multiple, 4}, + {"click long", command_button_press, 2}, + {"reset", command_reset, 0}, + {"crash", command_crash, 0}, + {"hard crash", command_hard_crash, 0}, #ifndef RECOVERY_FW - { "factory reset fast", command_factory_reset_fast, 0 }, + {"factory reset fast", command_factory_reset_fast, 0}, #endif - { "factory reset", command_factory_reset, 0 }, - { "set time", command_set_time, 1 }, - { "version", command_version_info, 0 }, - { "boot bit set", command_boot_bit_set, 2 }, - { "window stack", command_window_stack_info, 0 }, - { "modal stack", command_modal_stack_info, 0 }, - { "battery chargeopt", command_battery_charge_option, 1}, - { "bt airplane mode", command_bt_airplane_mode, 1 }, - { "bt prefs wipe", command_bt_prefs_wipe, 0 }, - { "bt mac", command_bt_print_mac, 0 }, - { "bt set addr", command_bt_set_addr, 1 }, - { "bt set name", command_bt_set_name, 1 }, - { "bt cp set", command_bt_conn_param_set, 4 }, - { "bt disc start", command_bt_disc_start, 2 }, - { "bt disc stop", command_bt_disc_stop, 0 }, - { "timezone clear", command_timezone_clear, 0 }, - { "battery status", command_print_battery_status, 0 }, + {"factory reset", command_factory_reset, 0}, + {"set time", command_set_time, 1}, + {"version", command_version_info, 0}, + {"boot bit set", command_boot_bit_set, 2}, + {"window stack", command_window_stack_info, 0}, + {"modal stack", command_modal_stack_info, 0}, + {"battery chargeopt", command_battery_charge_option, 1}, + {"bt airplane mode", command_bt_airplane_mode, 1}, + {"bt prefs wipe", command_bt_prefs_wipe, 0}, + {"bt mac", command_bt_print_mac, 0}, + {"bt set addr", command_bt_set_addr, 1}, + {"bt set name", command_bt_set_name, 1}, + {"bt cp set", command_bt_conn_param_set, 4}, + {"bt disc start", command_bt_disc_start, 2}, + {"bt disc stop", command_bt_disc_stop, 0}, + {"timezone clear", command_timezone_clear, 0}, + {"battery status", command_print_battery_status, 0}, #ifndef RELEASE - { "audit delay", command_audit_delay_us, 0 }, - { "enter stop", command_enter_stop, 0}, + {"audit delay", command_audit_delay_us, 0}, + {"enter stop", command_enter_stop, 0}, #endif #ifndef RECOVERY_FW - { "app list", command_app_list, 0 }, - { "app launch", command_app_launch, 1 }, - { "app remove", command_app_remove, 1 }, + {"app list", command_app_list, 0}, + {"app launch", command_app_launch, 1}, + {"app remove", command_app_remove, 1}, #endif - // End of automation commands - // ==================================================================================== + // End of automation commands + // ==================================================================================== - { "erase flash", command_erase_flash, 2 }, - { "crc flash", command_crc_flash, 2 }, + {"erase flash", command_erase_flash, 2}, + {"crc flash", command_crc_flash, 2}, #ifndef RECOVERY_FW #if CAPABILITY_HAS_TEMPERATURE - { "temp read", command_temperature_read, 0 }, + {"temp read", command_temperature_read, 0}, #endif - { "als read", command_als_read, 0}, + {"als read", command_als_read, 0}, #ifndef RELEASE - { "litter pfs", command_litter_filesystem, 0 }, + {"litter pfs", command_litter_filesystem, 0}, #endif #endif - // ==================================================================================== - // Following commands are used for manufacturing. We use a PRF firmware for manufacturing, so - // we can only include these commands when we're building for PRF. Some of the commands are - // specific to snowy manufacturing as well +// ==================================================================================== +// Following commands are used for manufacturing. We use a PRF firmware for manufacturing, so +// we can only include these commands when we're building for PRF. Some of the commands are +// specific to snowy manufacturing as well #ifdef RECOVERY_FW #if CAPABILITY_HAS_ACCESSORY_CONNECTOR - { "accessory imaging start", command_accessory_imaging_start, 0 }, + {"accessory imaging start", command_accessory_imaging_start, 0}, #endif - { "info", command_version_info, 0 }, + {"info", command_version_info, 0}, - { "enter mfg", command_enter_mfg, 0 }, + {"enter mfg", command_enter_mfg, 0}, - { "enter standby", command_enter_standby, 0 }, + {"enter standby", command_enter_standby, 0}, - { "enter consumer", command_enter_consumer_mode, 0 }, + {"enter consumer", command_enter_consumer_mode, 0}, - { "serial read", command_serial_read, 0 }, - { "hwver read", command_hwver_read, 0 }, - { "pcbaserial read", command_pcba_serial_read, 0 }, - { "color read", command_color_read, 0 }, + {"serial read", command_serial_read, 0}, + {"hwver read", command_hwver_read, 0}, + {"pcbaserial read", command_pcba_serial_read, 0}, + {"color read", command_color_read, 0}, #if PBL_ROUND - { "disp offset read", command_disp_offset_read, 0 }, + {"disp offset read", command_disp_offset_read, 0}, #endif - { "rtcfreq read", command_rtcfreq_read, 0 }, - { "model read", command_model_read, 0 }, + {"rtcfreq read", command_rtcfreq_read, 0}, + {"model read", command_model_read, 0}, - { "serial write", command_serial_write, 1 }, - { "hwver write", command_hwver_write, 1 }, - { "pcbaserial write", command_pcba_serial_write, 1 }, + {"serial write", command_serial_write, 1}, + {"hwver write", command_hwver_write, 1}, + {"pcbaserial write", command_pcba_serial_write, 1}, #if MANUFACTURING_FW - { "color write", command_color_write, 1 }, + {"color write", command_color_write, 1}, #if PBL_ROUND - { "disp offset write", command_disp_offset_write, 2 }, + {"disp offset write", command_disp_offset_write, 2}, #endif - { "rtcfreq write", command_rtcfreq_write, 1 }, - { "model write", command_model_write, 1 }, -#endif // MANUFACTURING_FW - { "bootloader test", command_bootloader_test, 1 }, - - { "scheduler force active", command_scheduler_force_active, 0 }, - { "scheduler resume normal", command_scheduler_resume_normal, 0 }, - - { "bt status", command_bt_status, 0 }, - { "bt test start", command_bt_test_start, 0 }, - { "bt test stop", command_bt_test_stop, 0 }, - { "bt test hcipass", command_bt_test_hci_passthrough, 0 }, + {"rtcfreq write", command_rtcfreq_write, 1}, + {"model write", command_model_write, 1}, +#endif // MANUFACTURING_FW + {"bootloader test", command_bootloader_test, 1}, + + {"scheduler force active", command_scheduler_force_active, 0}, + {"scheduler resume normal", command_scheduler_resume_normal, 0}, + + {"bt status", command_bt_status, 0}, + {"bt test start", command_bt_test_start, 0}, + {"bt test stop", command_bt_test_stop, 0}, + {"bt test hcipass", command_bt_test_hci_passthrough, 0}, #if BT_CONTROLLER_DA14681 - { "bt sleep check", command_bt_sleep_check, 1 }, - { "btle tx test start", command_btle_test_le_tx_start, 3 }, - { "btle rx test start", command_btle_test_rx_start, 1 }, - { "btle test end", command_btle_test_end, 0 }, - { "btle umod tx test start", command_btle_unmod_tx_start, 1 }, - { "btle umod tx test stop", command_btle_unmod_tx_stop, 0 }, -# if PLATFORM_ROBERT - { "btle test pa", command_btle_pa_set, 1 }, -# endif + {"bt sleep check", command_bt_sleep_check, 1}, + {"btle tx test start", command_btle_test_le_tx_start, 3}, + {"btle rx test start", command_btle_test_rx_start, 1}, + {"btle test end", command_btle_test_end, 0}, + {"btle umod tx test start", command_btle_unmod_tx_start, 1}, + {"btle umod tx test stop", command_btle_unmod_tx_stop, 0}, +#if PLATFORM_ROBERT + {"btle test pa", command_btle_pa_set, 1}, +#endif #endif - { "bt test bt_sig_rf", command_bt_test_bt_sig_rf_mode, 0}, + {"bt test bt_sig_rf", command_bt_test_bt_sig_rf_mode, 0}, - { "backlight", command_backlight_ctl, 1 }, + {"backlight", command_backlight_ctl, 1}, - { "button read", command_button_read, 1 }, + {"button read", command_button_read, 1}, #if CAPABILITY_HAS_MAGNETOMETER - { "compass peek", command_compass_peek, 0 }, -#endif // CAPABILITY_HAS_MAGNETOMETER - { "accel read", command_accel_peek, 0 }, + {"compass peek", command_compass_peek, 0}, +#endif // CAPABILITY_HAS_MAGNETOMETER + {"accel read", command_accel_peek, 0}, - { "als read", command_als_read, 0}, + {"als read", command_als_read, 0}, -#ifdef PLATFORM_TINTIN // TINTIN/BIANCA only - { "power 2.5", command_power_2v5, 1 }, +#ifdef PLATFORM_TINTIN // TINTIN/BIANCA only + {"power 2.5", command_power_2v5, 1}, #else - { "selftest", command_selftest, 0 }, + {"selftest", command_selftest, 0}, - { "flash read", command_flash_read, 2}, - { "flash switchmode", command_flash_switch_mode, 1}, - { "flash fill", command_flash_fill, 3}, + {"flash read", command_flash_read, 2}, + {"flash switchmode", command_flash_switch_mode, 1}, + {"flash fill", command_flash_fill, 3}, #if CAPABILITY_USE_PARALLEL_FLASH - { "flash test", command_flash_test, 2}, + {"flash test", command_flash_test, 2}, #endif - { "flash validate", command_flash_validate, 0}, - { "flash erased_sectors", command_flash_show_erased_sectors, 1}, + {"flash validate", command_flash_validate, 0}, + {"flash erased_sectors", command_flash_show_erased_sectors, 1}, #if !RELEASE && (PLATFORM_SILK || PLATFORM_ROBERT || PLATFORM_CALCULUS) - { "flash apicheck", command_flash_apicheck, 1}, - //{ "flash signal test init", command_flash_signal_test_init, 0 }, - //{ "flash signal test run", command_flash_signal_test_run, 0 }, + {"flash apicheck", command_flash_apicheck, 1}, +//{ "flash signal test init", command_flash_signal_test_init, 0 }, +//{ "flash signal test run", command_flash_signal_test_run, 0 }, #endif - //{ "pmic rails", command_pmic_rails, 0}, - + //{ "pmic rails", command_pmic_rails, 0}, - { "disp", command_display_set, 1}, + {"disp", command_display_set, 1}, #if MFG_INFO_RECORDS_TEST_RESULTS - { "mfg ui test results", command_mfg_info_test_results, 0 }, -#endif // MFG_INFO_RECORDS_TEST_RESULTS + {"mfg ui test results", command_mfg_info_test_results, 0}, +#endif // MFG_INFO_RECORDS_TEST_RESULTS -#endif // PLATFORM_TINTIN -#endif // RECOVERY_FW +#endif // PLATFORM_TINTIN +#endif // RECOVERY_FW + +#if CAPABILITY_HAS_BUILTIN_HRM + {"hrm read", command_hrm_read, 0}, + {"hrm wipe", command_hrm_wipe, 0}, + {"hrm freeze", command_hrm_freeze, 0}, +#endif #if CAPABILITY_HAS_ACCESSORY_CONNECTOR - { "accessory power", command_accessory_power_set, 1 }, - { "accessory stress", command_accessory_stress_test, 0 }, + {"accessory power", command_accessory_power_set, 1}, + {"accessory stress", command_accessory_stress_test, 0}, #if !RELEASE && !RECOVERY_FW - { "smartstrap status", command_smartstrap_status, 0 }, -#endif // RELEASE -#endif // CAPABILITY_HAS_ACCESSORY_CONNECTOR + {"smartstrap status", command_smartstrap_status, 0}, +#endif // RELEASE +#endif // CAPABILITY_HAS_ACCESSORY_CONNECTOR #if CAPABILITY_HAS_PMIC - {"pmic regs", command_pmic_read_registers, 0}, + {"pmic regs", command_pmic_read_registers, 0}, #endif #if CAPABILITY_HAS_MICROPHONE - { "mic start", command_mic_start, 4}, - { "mic read", command_mic_read, 0}, + {"mic start", command_mic_start, 4}, + {"mic read", command_mic_read, 0}, #endif - - // End of manufacturing commands - // ==================================================================================== - - - // The rest of the commands are pretty much a misc free-for-all of functionality that's useful - // for debugging. - - // Meta - { "help", command_help, 0 }, - { "lowpowerdebug", command_low_power_debug, 1 }, - - { "log level set", command_log_level_set, 1 }, - { "log level get", command_log_level_get, 0 }, - - { "log dump current", command_log_dump_current, 0 }, - { "log dump last", command_log_dump_last, 0 }, - { "log spam", command_log_dump_spam, 0 }, - { "log dump gen", command_log_dump_generation, 1 }, - - { "ble mode", command_change_le_mode, 1 }, - { "ble ind svc", command_ble_send_service_changed_indication, 0 }, - { "ble rediscover", command_ble_rediscover, 0 }, - // { "ble mode_monkey", command_le_mode_chaos_monkey, 1 }, - { "ble set log level", command_ble_logging_set_level, 1}, - { "ble get log level", command_ble_logging_get_level, 0}, - { "ble core dump", command_ble_core_dump, 1 }, - - /* - { "stats dump now", command_stats_dump_now, 0 }, - { "stats dump current", command_stats_dump_current, 0 }, - { "stats dump last", command_stats_dump_last, 0 }, - { "stats dump generation", command_stats_dump_generation, 1 }, - */ - - // Buttons - { "raw button event", command_put_raw_button_event, 2 }, - // { "click button event", command_put_button_event, 2 }, - - // General utils - // { "boot prf", command_boot_prf, 0 }, - - /* - { "infinite loop", command_infinite_loop, 0 }, - { "assert fail", command_assert_fail, 0 }, - { "stuck timer", command_stuck_timer, 0 }, - { "hard fault", command_hardfault, 0 }, - */ - { "croak", command_croak, 0 }, + // End of manufacturing commands + // ==================================================================================== + + // The rest of the commands are pretty much a misc free-for-all of functionality that's useful + // for debugging. + + // Meta + {"help", command_help, 0}, + {"lowpowerdebug", command_low_power_debug, 1}, + + {"log level set", command_log_level_set, 1}, + {"log level get", command_log_level_get, 0}, + + {"log dump current", command_log_dump_current, 0}, + {"log dump last", command_log_dump_last, 0}, + {"log spam", command_log_dump_spam, 0}, + {"log dump gen", command_log_dump_generation, 1}, + + {"ble mode", command_change_le_mode, 1}, + {"ble ind svc", command_ble_send_service_changed_indication, 0}, + {"ble rediscover", command_ble_rediscover, 0}, + // { "ble mode_monkey", command_le_mode_chaos_monkey, 1 }, + {"ble set log level", command_ble_logging_set_level, 1}, + {"ble get log level", command_ble_logging_get_level, 0}, + {"ble core dump", command_ble_core_dump, 1}, + + /* + { "stats dump now", command_stats_dump_now, 0 }, + { "stats dump current", command_stats_dump_current, 0 }, + { "stats dump last", command_stats_dump_last, 0 }, + { "stats dump generation", command_stats_dump_generation, 1 }, + */ + + // Buttons + {"raw button event", command_put_raw_button_event, 2}, + // { "click button event", command_put_button_event, 2 }, + + // General utils + // { "boot prf", command_boot_prf, 0 }, + + /* + { "infinite loop", command_infinite_loop, 0 }, + { "assert fail", command_assert_fail, 0 }, + { "stuck timer", command_stuck_timer, 0 }, + { "hard fault", command_hardfault, 0 }, + */ + {"croak", command_croak, 0}, #ifdef MALLOC_INSTRUMENTATION - { "dump malloc kernel", command_dump_malloc_kernel, 0 }, - { "dump malloc app", command_dump_malloc_app, 0 }, - { "dump malloc worker", command_dump_malloc_worker, 0 }, + {"dump malloc kernel", command_dump_malloc_kernel, 0}, + {"dump malloc app", command_dump_malloc_app, 0}, + {"dump malloc worker", command_dump_malloc_worker, 0}, #if BT_CONTROLLER_CC2564X - { "dump malloc bt", command_dump_malloc_bt, 0 }, + {"dump malloc bt", command_dump_malloc_bt, 0}, #endif /* BT_CONTROLLER_CC2564X */ #endif /* MALLOC_INSTRUMENTATION */ - /* - { "read word", command_read_word, 1 }, +/* +{ "read word", command_read_word, 1 }, - { "remote os", command_get_connected_os, 0 }, - */ +{ "remote os", command_get_connected_os, 0 }, +*/ #ifdef UI_DEBUG - { "window dump", command_dump_window, 0 }, - { "layer nudge", command_layer_nudge, 1 }, + {"window dump", command_dump_window, 0}, + {"layer nudge", command_layer_nudge, 1}, #endif + // Drivers + //{ "rgb", command_rgb_set_color, 1 }, - // Drivers - //{ "rgb", command_rgb_set_color, 1 }, - -// { "watch", command_watch, 0 }, + // { "watch", command_watch, 0 }, - // Flash manipulation commands - { "dump flash", command_dump_flash, 2 }, - // { "format flash", command_format_flash, 0 }, + // Flash manipulation commands + {"dump flash", command_dump_flash, 2}, +// { "format flash", command_format_flash, 0 }, #if !PLATFORM_TINTIN - { "flash unprotect", command_flash_unprotect, 0 }, + {"flash unprotect", command_flash_unprotect, 0}, #endif #ifndef RECOVERY_FW - { "worker launch", command_worker_launch, 1 }, - { "worker kill", command_worker_kill, 0}, + {"worker launch", command_worker_launch, 1}, + {"worker kill", command_worker_kill, 0}, #endif #ifdef TEST_FLASH_LOCK_PROTECTION - { "flash lock test", command_flash_test_locked_sectors, 0 }, + {"flash lock test", command_flash_test_locked_sectors, 0}, #endif - /* - { "get time", command_get_time, 0 }, - */ - - // Firmware specific - //{ "task-list", command_print_task_list, 0 }, +/* +{ "get time", command_get_time, 0 }, +*/ - //{ "cpustats", dump_current_runtime_stats, 0 }, - //{ "bt prefs get", command_get_remote_prefs, 0 }, - //{ "bt prefs del", command_del_remote_pref, 1 }, - //{ "bt sniff bounce", command_bt_sniff_bounce, 0 }, - //{ "bt active enter", command_bt_active_enter, 0 }, - //{ "bt active exit", command_bt_active_exit, 0 }, +// Firmware specific +//{ "task-list", command_print_task_list, 0 }, +//{ "cpustats", dump_current_runtime_stats, 0 }, +//{ "bt prefs get", command_get_remote_prefs, 0 }, +//{ "bt prefs del", command_del_remote_pref, 1 }, +//{ "bt sniff bounce", command_bt_sniff_bounce, 0 }, +//{ "bt active enter", command_bt_active_enter, 0 }, +//{ "bt active exit", command_bt_active_exit, 0 }, #if !defined(RECOVERY_FW) - { "get active app metadata", command_get_active_app_metadata, 0 }, + {"get active app metadata", command_get_active_app_metadata, 0}, #endif -// { "boot bits get", command_boot_bits_get, 0 }, + // { "boot bits get", command_boot_bits_get, 0 }, - { "animations", command_animations_info, 0 }, - { "pause animations", command_pause_animations, 0 }, - { "resume animations", command_resume_animations, 0 }, + {"animations", command_animations_info, 0}, + {"pause animations", command_pause_animations, 0}, + {"resume animations", command_resume_animations, 0}, // { "animations_l2", command_legacy2_animations_info, 0 }, @@ -609,51 +617,51 @@ static const Command s_prompt_commands[] = { // #endif #if !defined(RECOVERY_FW) - { "alarm", command_alarm, 0 }, + {"alarm", command_alarm, 0}, - //{ "now playing", command_print_now_playing, 0 }, + //{ "now playing", command_print_now_playing, 0 }, - { "dls list", command_dls_list, 0 }, - // { "dls show", command_dls_show, 1 }, - { "dls wipe", command_dls_erase_all, 0 }, - { "dls send", command_dls_send_all, 0 }, + {"dls list", command_dls_list, 0}, + // { "dls show", command_dls_show, 1 }, + {"dls wipe", command_dls_erase_all, 0}, + {"dls send", command_dls_send_all, 0}, -#endif // !RECOVERY_FW +#endif // !RECOVERY_FW - { "dump mpu", memory_layout_dump_mpu_regions_to_dbgserial, 0 }, + {"dump mpu", memory_layout_dump_mpu_regions_to_dbgserial, 0}, #ifndef RECOVERY_FW - {"pfs format", pfs_command_fs_format, 1}, - {"pfs ls", pfs_command_fs_ls, 0}, - // {"pfs cat", pfs_command_cat, 2}, - // {"pfs rmall", pfs_remove_all, 0}, - {"pfs rm", pfs_remove, 1}, - {"pfs hdr", pfs_command_dump_hdr, 1}, - // {"pfs stress", pfs_command_stress, 0 }, - {"pfs crc", pfs_command_crc, 1}, - - // This command is dangerous to your flash, so it is commented out by default. - // {"flash stress", command_flash_stress, 0 }, + {"pfs format", pfs_command_fs_format, 1}, + {"pfs ls", pfs_command_fs_ls, 0}, + // {"pfs cat", pfs_command_cat, 2}, + // {"pfs rmall", pfs_remove_all, 0}, + {"pfs rm", pfs_remove, 1}, + {"pfs hdr", pfs_command_dump_hdr, 1}, + // {"pfs stress", pfs_command_stress, 0 }, + {"pfs crc", pfs_command_crc, 1}, + +// This command is dangerous to your flash, so it is commented out by default. +// {"flash stress", command_flash_stress, 0 }, #endif - { "ping", command_ping_send, 0}, + {"ping", command_ping_send, 0}, - { "runlevel", command_set_runlevel, 1 }, + {"runlevel", command_set_runlevel, 1}, #if defined(PROFILER) - { "profiler start", command_profiler_start, 0 }, - { "profiler stop", command_profiler_stop, 0 }, - { "profiler stats", command_profiler_stats, 0 }, + {"profiler start", command_profiler_start, 0}, + {"profiler stop", command_profiler_stop, 0}, + {"profiler stats", command_profiler_stats, 0}, #endif #if (LOG_DOMAIN_BT_PAIRING_INFO != 0) - // Note to future codespace saver ... this is on by default for debug builds - // Removing it will save ~2400 bytes but it is super useful for BT bringup debug! - { "gapdb dump", command_gapdb_dump, 0 }, - { "sprf nuke", command_bt_sprf_nuke, 0 }, + // Note to future codespace saver ... this is on by default for debug builds + // Removing it will save ~2400 bytes but it is super useful for BT bringup debug! + {"gapdb dump", command_gapdb_dump, 0}, + {"sprf nuke", command_bt_sprf_nuke, 0}, #if !RECOVERY_FW - { "sprf sync", command_force_shared_prf_flush, 0}, -#endif // !RECOVERY_FW + {"sprf sync", command_force_shared_prf_flush, 0}, +#endif // !RECOVERY_FW #endif #if 0 @@ -662,26 +670,26 @@ static const Command s_prompt_commands[] = { { "battui dismiss", command_battery_ui_dismiss, 0 }, #endif - { "waste time", command_waste_time, 2 }, + {"waste time", command_waste_time, 2}, #if !defined(RECOVERY_FW) - { "dump notif_pref_db", command_dump_notif_pref_db, 0 }, + {"dump notif_pref_db", command_dump_notif_pref_db, 0}, #endif #if PERFORMANCE_TESTS - { "perftest all line", command_perftest_line_all, 0 }, - { "perftest all text", command_perftest_text_all, 0 }, - { "perftest line", command_perftest_line, 2 }, - { "perftest text", command_perftest_text, 3 }, + {"perftest all line", command_perftest_line_all, 0}, + {"perftest all text", command_perftest_text_all, 0}, + {"perftest line", command_perftest_line, 2}, + {"perftest text", command_perftest_text, 3}, #endif -#endif // KEEP_NON_ESSENTIAL_COMMANDS +#endif // KEEP_NON_ESSENTIAL_COMMANDS #if PLATFORM_SILK && !TARGET_QEMU - { "accel samp", command_accel_num_samples, 1 }, - { "accel status", command_accel_status, 0 }, - { "accel selftest", command_accel_selftest, 0 }, - { "accel reset", command_accel_softreset, 0 }, -#endif // PLATFORM_SILK - { "vibe", command_vibe_ctl, 1 }, + {"accel samp", command_accel_num_samples, 1}, + {"accel status", command_accel_status, 0}, + {"accel selftest", command_accel_selftest, 0}, + {"accel reset", command_accel_softreset, 0}, +#endif // PLATFORM_SILK + {"vibe", command_vibe_ctl, 1}, }; #define NUM_PROMPT_COMMANDS ARRAY_LENGTH(s_prompt_commands) diff --git a/src/fw/drivers/hrm/README_AS7000.md b/src/fw/drivers/hrm/README_AS7000.md new file mode 100644 index 000000000..77469442f --- /dev/null +++ b/src/fw/drivers/hrm/README_AS7000.md @@ -0,0 +1,130 @@ +Some Information About the AMS AS7000 +===================================== + +The documentation about the AS7000 can be a bit hard to follow and a bit +incomplete at times. This document aims to fill in the gaps, using +knowledge gleaned from the datasheet, Communication Protocol document +and the SDK docs and sources. + +What is the AS7000 exactly? +--------------------------- + +The AS7000 is a Cortex-M0 SoC with some very specialized peripherals. +It can be programmed with a firmware to have it function as a heart-rate +monitor. It doesn't necessarily come with the HRM firmware preloaded, so +it is not one of those devices that you can treat as a black-box piece +of hardware that you just power up and talk to. + +There is 32 kB of flash for the main application, and some "reserved" +flash containing a loader application. There is also a ROM first-stage +bootloader. + +Boot Process +------------ + +The chip is woken from shutdown by pulling the chip's GPIO8 pin low. The +CPU core executes the ROM bootloader after powerup or wake from +shutdown. The bootloader's logic is apparently as follows: + + if loader is available: + jump to loader + elif application is valid: + remap flash to address 0 + jump to application + else: + wait on UART?? (not documented further) + +We will probably always be using parts with the loader application +preprogrammed by AMS so the existence of the bootloader just makes the +communication protocol document a bit more confusing to understand. It +can be safely ignored and we can pretend that the loader application is +the only bootloader. + +Once control is passed from the ROM bootloader to the loader, the loader +performs the following: + + wait 30 ms + if application is valid and GPIO8 is low: + remap flash to address 0 + jump to application + else: + run loader application + +The reason for the 30 ms wait and check for GPIO8 is to provide an +escape hatch for getting into the loader if a misbehaving application +is programmed which doesn't allow for a command to be used to enter the +loader. + +The "Application is valid" check is apparently to check that address +0x7FFC (top of main flash) contains the bytes 72 75 6C 75. + +The Loader +---------- + +The loader allows new applications to be programmed into the main flash +over I2C by sending Intel HEX records(!). The communication protocol +document describes the protocol well enough, though it is a bit lacking +in detail as to what HEX records it accepts. Luckily the SDK comes with +a header file which fills in some of the details, and the SDK Getting +Started document has some more details. + + - The supported record types are 00 (Data), 01 (EOF), 04 (Extended + Linear Address) and 05 (Start Linear Address). + - The HEX records must specify addresses in the aliased range 0-0x7FFF + - HEX records must be sent in order of strictly increasing addresses. + +A comment in loader.h from the SDK states that the maximum record size +supported by the loader can be configured as 256, 128, 64 or 32. The +`#define` in the same header which sets the max record length is set +to 256, strongly implying that this is the record length limit for the +loader firmware which is already programmed into the parts. Programming +an application firmware is successful when using records of length 203, +which seems to confirm that the max record length is indeed 256. + +The HEX files provided by AMS appear to be standard HEX files generated +by the Keil MDK-ARM toolchain. http://www.keil.com/support/docs/1584.htm + +The Start Linear Address record contains the address of the "pre-main" +function, which is not the reset vector. Since the reset vector is +already written as data in the application's vector table, the record is +unnecessary for flash loading and is likely just thrown away by the +loader. + +Empirical testing has confirmed that neither the Start Linear Address +nor Extended Linear Address records need to be sent for flashing to +succeed. It is acceptable to send only a series of Data records followed +by an EOF record. + +The loader will exit if one of the following conditions is true: + + - An EOF record is sent + - A valid HEX record is sent which the loader doesn't understand + - Data is sent which is not a valid HEX record + - No records are sent for approximately ten seconds + +The loader exits by waiting one second for the host controller to read +out the exit code register, then performs a system reset. + +The Application +--------------- + +Due to the simplicity of the CM0 and the limited resources available on +the chip, the application firmware takes full control of the system. The +applications in the SDK are written as a straightforward mainloop with +no OS. Think of it as a very simple form of cooperative multitasking. + +The "mandatory" I2C register map mentioned in the Communication Protocol +document is merely a part of the protocol; it is entirely up to the +application firmware to properly implement it. The Application ID +register doesn't do anything special on its own: the mainloop simply +polls the register value at each iteration to see if it needs to start +or stop any "apps" (read: modules). The application firmware could +misbehave, resulting in writes to that register having no effect. + +Writing 0x01 to the Application ID register isn't special either; it is +up to the application to recognize that value and reset into the loader. +That's why the escape hatch is necessary, and the fundamental reason why +the loader application cannot run at the same time as any other +application. + +GPIO8 isn't special while the firmware is running, either. diff --git a/src/fw/drivers/hrm/as7000.c b/src/fw/drivers/hrm/as7000.c new file mode 100644 index 000000000..6f31abb92 --- /dev/null +++ b/src/fw/drivers/hrm/as7000.c @@ -0,0 +1,1104 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define FILE_LOG_COLOR LOG_COLOR_GREEN +#include "as7000.h" + +#include "board/board.h" +#include "drivers/backlight.h" +#include "drivers/exti.h" +#include "drivers/gpio.h" +#include "drivers/i2c.h" +#include "kernel/events.h" +#include "kernel/util/interval_timer.h" +#include "kernel/util/sleep.h" +#include "mfg/mfg_info.h" +#include "resource/resource.h" +#include "resource/resource_ids.auto.h" +#include "services/common/analytics/analytics.h" +#include "services/common/hrm/hrm_manager.h" +#include "services/common/system_task.h" +#include "system/logging.h" +#include "system/passert.h" +#include "system/profiler.h" +#include "util/attributes.h" +#include "util/ihex.h" +#include "util/math.h" +#include "util/net.h" + +#define STM32F4_COMPATIBLE +#define STM32F7_COMPATIBLE +#include + +// Enable this to get some very verbose logs about collecting PPG data from the HRM +// Bump this up to 2 to get very verbose logs +#define PPG_DEBUG 0 + +#if PPG_DEBUG +#define PPG_DBG(...) \ + do { \ + PBL_LOG(LOG_LEVEL_DEBUG, __VA_ARGS__); \ + } while (0); +#else +#define PPG_DBG(...) +#endif + +#if PPG_DEBUG == 2 +#define PPG_DBG_VERBOSE(...) \ + do { \ + PBL_LOG_VERBOSE(LOG_LEVEL_DEBUG, __VA_ARGS__); \ + } while (0); +#else +#define PPG_DBG_VERBOSE(...) +#endif + +// The datasheet recommends waiting for 250ms for the chip to boot +#define NORMAL_BOOT_DELAY_MS (250) +// We need to wait an extra second for the loader to time-out +#define LOADER_REBOOT_DELAY_MS (NORMAL_BOOT_DELAY_MS + 1000) +// Usually takes a couple ms after writing a record, but spikes of ~20ms have been observed. Let's +// be conservative. +#define LOADER_READY_MAX_DELAY_MS (50) +// Give the sensor this much time to tear down the current app and go back to the idle mode +#define SHUT_DOWN_DELAY_MS (1000) +// Number of handshakes before samples are expected +#define WARMUP_HANDSHAKES (2) + +#define EXPECTED_PROTOCOL_VERSION_MAJOR (2) + +// White Threshold is 5000 +// Black Threshold is 3500 +// Value stored in the register is in units of 64 ADC counts +// e.g. 78 * 64 = 4992 ADC-counts +// Refer to AS7000 SW Communication Protocol section 6.7 +#define PRES_DETECT_THRSH_WHITE 78 // (5000 / 64) +#define PRES_DETECT_THRSH_BLACK 54 // (3500 / 64) + +// register addresses +#define ADDR_LOADER_STATUS (0x00) +#define ADDR_INFO_START (0x00) +#define ADDR_APP_IDS (0x04) + +#define ADDR_ACCEL_SAMPLE_FREQ_MSB (0x08) +#define ADDR_ACCEL_SAMPLE_FREQ_LSB (0x09) + +// Register that allows us to compensate for clock skew between us (the host) and the sensor. The +// sensor doesn't track time accurately, and gives us a heart rate value that's in the sensors +// time domain, which will need to be translated into "real time" according to our time domain. +// If we use these registers to tell the sensor how frequently it's handshaking with us in our +// time domain, this will let the sensor do this compensation for us. +// The value programmed in here is in units of 0.1ms (value of 10000 = 1 second). +#define ADDR_HOST_ONE_SECOND_TIME_MSB (0x0a) +#define ADDR_HOST_ONE_SECOND_TIME_LSB (0x0b) + +#define ADDR_NUM_ACCEL_SAMPLES (0x0e) +#define ADDR_NUM_PPG_SAMPLES (0x0f) + +#define ADDR_ACCEL_SAMPLE_IDX (0x14) +#define ADDR_ACCEL_X_MSB (0x15) +#define ADDR_ACCEL_Y_MSB (0x17) +#define ADDR_ACCEL_Z_MSB (0x19) + +#define ADDR_PPG_IDX (0x1b) +#define ADDR_PPG_MSB (0x1c) +#define ADDR_PPG_LSB (0x1d) +#define ADDR_TIA_MSB (0x1e) +#define ADDR_TIA_LSB (0x1f) + +#define ADDR_PRES_DETECT_THRSH (0x26) + +#define ADDR_LED_CURRENT_MSB (0x34) +#define ADDR_LED_CURRENT_LSB (0x35) +#define ADDR_HRM_STATUS (0x36) +#define ADDR_HRM_BPM (0x37) +#define ADDR_HRM_SQI (0x38) + +#define ADDR_SYNC (0x39) + +// The AS7000 wants Accel Frequency given in 0.001Hz increments, this can be used to scale +#define AS7000_ACCEL_FREQUENCY_SCALE (1000) + +//! Thresholds for quality conversion. These are upper bounds on readings. +enum AS7000SQIThreshold { + AS7000SQIThreshold_Excellent = 2, + AS7000SQIThreshold_Good = 5, + AS7000SQIThreshold_Acceptable = 8, + AS7000SQIThreshold_Poor = 10, + AS7000SQIThreshold_Worst = 20, + + AS7000SQIThreshold_OffWrist = 254, + + AS7000SQIThresholdInvalid, +}; + +enum AS7000Status { + AS7000Status_OK = 0, + AS7000Status_IllegalParameter = 1, + AS7000Status_LostData = 2, + AS7000Status_NoAccel = 4, +}; + +typedef enum AS7000AppId { + AS7000AppId_Idle = 0x00, + AS7000AppId_Loader = 0x01, + AS7000AppId_HRM = 0x02, + AS7000AppId_PRV = 0x04, + AS7000AppId_GSR = 0x08, + AS7000AppId_NTC = 0x10, +} AS7000AppId; + +typedef enum AS7000LoaderStatus { + AS7000LoaderStatus_Ready = 0x00, + AS7000LoaderStatus_Busy1 = 0x3A, + AS7000LoaderStatus_Busy2 = 0xFF, + // all other values indicate an error +} AS7000LoaderStatus; + +typedef struct PACKED AS7000FWUpdateHeader { + uint8_t sw_version_major; + uint8_t sw_version_minor; +} AS7000FWUpdateHeader; + +typedef struct PACKED AS7000FWSegmentHeader { + uint16_t address; + uint16_t len_minus_1; +} AS7000FWSegmentHeader; + +//! The maximum number of data bytes to include in a reconstituted +//! Intel HEX Data record when updating the HRM firmware. +//! This is the size of the binary data encoded in the record, __NOT__ +//! the size of the HEX record encoding the data. The HEX record itself +//! will be IHEX_RECORD_LENGTH(MAX_HEX_DATA_BYTES) +//! (MAX_HEX_DATA_BYTES*2 + 11) bytes in size. +#define MAX_HEX_DATA_BYTES (96) + +// The AS7000 loader cannot accept HEX records longer than 256 bytes. +_Static_assert(IHEX_RECORD_LENGTH(MAX_HEX_DATA_BYTES) <= 256, + "The value of MAX_HEX_DATA_BYTES will result in HEX records " + "which are longer than the AS7000 loader can handle."); + +// The sw_version_major field is actually a bitfield encoding both the +// major and minor components of the SDK version number. Define macros +// to extract the components for logging purposes. +#define HRM_SW_VERSION_PART_MAJOR(v) (v >> 6) +#define HRM_SW_VERSION_PART_MINOR(v) (v & 0x3f) + +// If this many watchdog interrupts occur before we receive an interrupt from the sensor, +// we assume the sensor requires a reset +#define AS7000_MAX_WATCHDOG_INTERRUPTS 5 + +// We use this regular timer as a watchdog for the sensor. We have seen cases where the sensor +// becomes unresponsive (PBL-40008). This timer watches to see if we have stopped receiving +// sensor interrupts and will trigger logic to reset the sensor if necessary. +static RegularTimerInfo s_as7000_watchdog_timer; + +// Incremented by s_as7000_watchdog_timer. Reset to 0 by our interrupt handler. +static uint8_t s_missing_interrupt_count; + +//! Interval timer to track how frequently the as7000 is handshaking with us +static IntervalTimer s_handshake_interval_timer; + +static void prv_enable_timer_cb(void *context); +static void prv_disable_watchdog(HRMDevice *dev); + +static bool prv_write_register(HRMDevice *dev, uint8_t register_address, uint8_t value) { + i2c_use(dev->i2c_slave); + bool rv = i2c_write_register(dev->i2c_slave, register_address, value); + i2c_release(dev->i2c_slave); + return rv; +} + +static bool prv_write_register_block(HRMDevice *dev, uint8_t register_address, const void *buffer, + uint32_t length) { + i2c_use(dev->i2c_slave); + bool rv = i2c_write_register_block(dev->i2c_slave, register_address, length, buffer); + i2c_release(dev->i2c_slave); + return rv; +} + +static bool prv_read_register(HRMDevice *dev, uint8_t register_address, uint8_t *value) { + i2c_use(dev->i2c_slave); + bool rv = i2c_read_register(dev->i2c_slave, register_address, value); + i2c_release(dev->i2c_slave); + return rv; +} + +static bool prv_read_register_block(HRMDevice *dev, uint8_t register_address, void *buffer, + uint32_t length) { + i2c_use(dev->i2c_slave); + bool rv = i2c_read_register_block(dev->i2c_slave, register_address, length, buffer); + i2c_release(dev->i2c_slave); + return rv; +} + +static bool prv_set_host_one_second_time_register(HRMDevice *dev, uint32_t average_ms) { + PPG_DBG("host one second time: %" PRIu32 " ms", average_ms); + + // Register takes a reading in 0.1ms increments + uint16_t value = average_ms * 10; + + const uint8_t msb = (value >> 8) & 0xff; + const uint8_t lsb = value & 0xff; + return prv_write_register(dev, ADDR_HOST_ONE_SECOND_TIME_MSB, msb) && + prv_write_register(dev, ADDR_HOST_ONE_SECOND_TIME_LSB, lsb); +} + +static void prv_read_ppg_data(HRMDevice *dev, HRMPPGData *data_out) { + uint8_t num_ppg_samples; + prv_read_register(HRM, ADDR_NUM_PPG_SAMPLES, &num_ppg_samples); + num_ppg_samples = MIN(num_ppg_samples, MAX_PPG_SAMPLES); + + for (int i = 0; i < num_ppg_samples; ++i) { + struct PACKED { + uint8_t idx; + uint16_t ppg; + uint16_t tia; + } ppg_reading; + + // Reading PPG data from the chip is a little weird. We need to read the PPG block of registers + // which maps to the ppg_reading struct above. We then need to verify that the index that we + // read matches the one that we expect. If we attempt to read the registers too quickly back to + // back that means that the AS7000 failed to update the value in time and we just need to try + // again. Limit this to a fixed number of attempts to make sure we don't infinite loop. + const int NUM_ATTEMPTS = 3; + bool success = false; + for (int j = 0; j < NUM_ATTEMPTS; ++j) { + prv_read_register_block(HRM, ADDR_PPG_IDX, &ppg_reading, sizeof(ppg_reading)); + if (ppg_reading.idx == i + 1) { + data_out->indexes[i] = ppg_reading.idx; + data_out->ppg[i] = ntohs(ppg_reading.ppg); + data_out->tia[i] = ntohs(ppg_reading.tia); + + success = true; + break; + } + + PPG_DBG_VERBOSE("FAIL: got %" PRIu16 " expected %u tia %" PRIu16, ppg_reading.idx, i + 1, + ntohs(ppg_reading.tia)); + // Keep trying... + } + + if (!success) { + // We didn't find a sample, just give up on reading PPG for this handshake + break; + } + + data_out->num_samples++; + } + + PPG_DBG("num_samples reg: %" PRIu8 " read: %u", num_ppg_samples, data_out->num_samples); +} + +static void prv_write_accel_sample(HRMDevice *dev, uint8_t sample_idx, AccelRawData *data) { + struct PACKED { + uint8_t sample_idx; + net16 accel_x; + net16 accel_y; + net16 accel_z; + } sample_data = { + .sample_idx = sample_idx, + .accel_x = hton16(data->x * 2), // Accel service supplies mGs, AS7000 expects lsb = 0.5 mG + .accel_y = hton16(data->y * 2), + .accel_z = hton16(data->z * 2)}; + prv_write_register_block(dev, ADDR_ACCEL_SAMPLE_IDX, &sample_data, sizeof(sample_data)); +} + +static void prv_read_hrm_data(HRMDevice *dev, HRMData *data) { + struct PACKED { + uint16_t led_current; + uint8_t hrm_status; + uint8_t bpm; + uint8_t sqi; + } hrm_data_regs; + + prv_read_register_block(dev, ADDR_LED_CURRENT_MSB, &hrm_data_regs, sizeof(hrm_data_regs)); + + data->led_current_ua = ntohs(hrm_data_regs.led_current); + data->hrm_status = hrm_data_regs.hrm_status; + data->hrm_bpm = hrm_data_regs.bpm; + + if (data->hrm_status & AS7000Status_NoAccel) { + data->hrm_quality = HRMQuality_NoAccel; + } else if (hrm_data_regs.sqi <= AS7000SQIThreshold_Excellent) { + data->hrm_quality = HRMQuality_Excellent; + } else if (hrm_data_regs.sqi <= AS7000SQIThreshold_Good) { + data->hrm_quality = HRMQuality_Good; + } else if (hrm_data_regs.sqi <= AS7000SQIThreshold_Acceptable) { + data->hrm_quality = HRMQuality_Acceptable; + } else if (hrm_data_regs.sqi <= AS7000SQIThreshold_Poor) { + data->hrm_quality = HRMQuality_Poor; + } else if (hrm_data_regs.sqi <= AS7000SQIThreshold_Worst) { + data->hrm_quality = HRMQuality_Worst; + } else if (hrm_data_regs.sqi == AS7000SQIThreshold_OffWrist) { + data->hrm_quality = HRMQuality_OffWrist; + } else { + data->hrm_quality = HRMQuality_NoSignal; + } +} + +// Sequence of events for handshake pulse (when in one-second burst mode): +// - [optional] Host writes the one-second time (registers 10,11) measured for the last 20 +// samples (about one second). +// - Host reads any data/HRV-result/LED-current, as needed (see registers [14...53]) +// - Host reads the HRM-result/SYNC-byte (registers [54...57]). +// If not in HRM-mode, the host can just read the SYNC-byte (register 57). +// Reading the SYNC-byte causes the AS7000 to release the handshake-signal +// and allows deep-sleep mode (if the AS7000 is configured for this). +// This step must be the last read for this handshake-pulse. +static void prv_handle_handshake_pulse(void *unused_data) { + PPG_DBG("Handshake handle"); + + mutex_lock(HRM->state->lock); + if (!hrm_is_enabled(HRM)) { + mutex_unlock(HRM->state->lock); + return; + } + + // We keep track of the number of handshakes so that we know when to expect samples + const bool should_expect_samples = (HRM->state->handshake_count > WARMUP_HANDSHAKES); + + HRMData data = (HRMData){}; + + // Immediately read the PPG data. The timing constraints are pretty tight (we need to read this + // within 30ms~ of getting the handshake or else we'll lose PPG data). The other registers can + // be read at anytime before the next handshake, so it's ok to do this first. + prv_read_ppg_data(HRM, &data.ppg_data); + + if (should_expect_samples) { + interval_timer_take_sample(&s_handshake_interval_timer); + } + + // Send the accel data out to the AS7000 + HRMAccelData *accel_data = hrm_manager_get_accel_data(); + const uint8_t num_samples = accel_data->num_samples; + prv_write_register(HRM, ADDR_NUM_ACCEL_SAMPLES, num_samples); + for (uint32_t i = 0; i < num_samples; ++i) { + prv_write_accel_sample(HRM, i + 1, &accel_data->data[i]); + } + data.accel_data = *accel_data; + hrm_manager_release_accel_data(); + + // Read the rest of the HRM data fields. + prv_read_hrm_data(HRM, &data); + + // Handle the clock skew register + uint32_t average_handshake_interval_ms; + uint32_t num_intervals = + interval_timer_get(&s_handshake_interval_timer, &average_handshake_interval_ms); + // Try to write the register frequently early on, and then every half second to accommodate + // changes over time. + if (num_intervals == 2 || num_intervals == 10 || (num_intervals % 30) == 0) { + prv_set_host_one_second_time_register(HRM, average_handshake_interval_ms); + } + + // Read the SYNC byte to release handshake signal and enter deep sleep mode. + uint8_t unused; + prv_read_register(HRM, ADDR_SYNC, &unused); + + PPG_DBG("Handshake handle done"); + HRM->state->handshake_count++; + + PROFILER_NODE_STOP(hrm_handling); + mutex_unlock(HRM->state->lock); + + // PPG_DBG log out each PPG data sample that we recorded + for (int i = 0; i < data.ppg_data.num_samples; i++) { + PPG_DBG_VERBOSE("idx %-2" PRIu8 " ppg %-6" PRIu16 " tia %-6" PRIu16, data.ppg_data.indexes[i], + data.ppg_data.ppg[i], data.ppg_data.tia[i]); + } + + hrm_manager_new_data_cb(&data); + + if (num_samples == 0 && should_expect_samples) { + analytics_inc(ANALYTICS_DEVICE_METRIC_HRM_ACCEL_DATA_MISSING, AnalyticsClient_System); + PBL_LOG(LOG_LEVEL_WARNING, "Falling behind: HRM got 0 accel samples"); + } +} + +static void prv_as7000_interrupt_handler(bool *should_context_switch) { + PPG_DBG("Handshake interrupt"); + + PROFILER_NODE_START(hrm_handling); // Starting to respond to handshake toggle + + // Reset the watchdog counter + s_missing_interrupt_count = 0; + + *should_context_switch = new_timer_add_work_callback_from_isr(prv_handle_handshake_pulse, NULL); +} + +static void prv_interrupts_enable(HRMDevice *dev, bool enable) { + mutex_assert_held_by_curr_task(dev->state->lock, true); + exti_configure_pin(dev->handshake_int, ExtiTrigger_Falling, prv_as7000_interrupt_handler); + exti_enable(dev->handshake_int); +} + +static void prv_log_running_apps(HRMDevice *dev) { + uint8_t app_ids = 0; + if (!prv_read_register(dev, ADDR_APP_IDS, &app_ids)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to get running apps"); + return; + } + PBL_LOG(LOG_LEVEL_DEBUG, "Running applications:"); + if (app_ids == AS7000AppId_Idle) { + PBL_LOG(LOG_LEVEL_DEBUG, " - None (idle)"); + } else { + if (app_ids & AS7000AppId_Loader) { + PBL_LOG(LOG_LEVEL_DEBUG, " - Loader"); + } + if (app_ids & AS7000AppId_HRM) { + PBL_LOG(LOG_LEVEL_DEBUG, " - HRM"); + } + if (app_ids & AS7000AppId_PRV) { + PBL_LOG(LOG_LEVEL_DEBUG, " - PRV"); + } + if (app_ids & AS7000AppId_GSR) { + PBL_LOG(LOG_LEVEL_DEBUG, " - GSR"); + } + if (app_ids & AS7000AppId_NTC) { + PBL_LOG(LOG_LEVEL_DEBUG, " - NTC"); + } + } +} + +static bool prv_get_and_log_device_info(HRMDevice *dev, AS7000InfoRecord *info, bool log_version) { + // get the device info + if (!prv_read_register_block(dev, ADDR_INFO_START, info, sizeof(AS7000InfoRecord))) { + return false; + } + + if (log_version) { + // print out the version information + PBL_LOG(LOG_LEVEL_INFO, + "AS7000 enabled! Protocol v%" PRIu8 ".%" PRIu8 ", SW v%" PRIu8 ".%" PRIu8 ".%" PRIu8 + ", HW Rev %" PRIu8, + info->protocol_version_major, info->protocol_version_minor, + HRM_SW_VERSION_PART_MAJOR(info->sw_version_major), + HRM_SW_VERSION_PART_MINOR(info->sw_version_major), info->sw_version_minor, + info->hw_revision); + } + prv_log_running_apps(dev); + return true; +} + +static bool prv_is_app_running(HRMDevice *dev, AS7000AppId app) { + uint8_t running_apps = 0; + if (!prv_read_register(dev, ADDR_APP_IDS, &running_apps)) { + return false; + } + PBL_LOG(LOG_LEVEL_DEBUG, "Apps running: 0x%" PRIx8, running_apps); + if (app == AS7000AppId_Idle) { + // no apps should be running + return running_apps == AS7000AppId_Idle; + } + return running_apps & app; +} + +//! Set the applications that should be running on the HRM. +//! +//! This commands the HRM to start or continue running any apps whose +//! flags are set, and to stop all apps whose flags are unset. Depending +//! on the firmware loaded onto the HRM, multiple apps can be run +//! concurrently by setting the logical OR of the App IDs. +static bool prv_set_running_apps(HRMDevice *dev, AS7000AppId apps) { + return prv_write_register(dev, ADDR_APP_IDS, apps); +} + +// Wait for the INT line to go low. Return true if it went low before timing out +static bool prv_wait_int_low(HRMDevice *dev) { + const int max_attempts = 2000; + int attempt; + for (attempt = 0; attempt < max_attempts; attempt++) { + if (!gpio_input_read(&dev->int_gpio)) { + break; + } + system_task_watchdog_feed(); + psleep(1); + } + return (attempt < max_attempts); +} + +// Wait for the INT line to go high. Return true if it went high before timing out +static bool prv_wait_int_high(HRMDevice *dev) { + const int max_attempts = 300; + int attempt; + for (attempt = 0; attempt < max_attempts; attempt++) { + if (gpio_input_read(&dev->int_gpio)) { + break; + } + system_task_watchdog_feed(); + psleep(1); + } + return (attempt < max_attempts); +} + +// NOTE: the caller must hold the device's state lock +static void prv_disable(HRMDevice *dev) { + mutex_assert_held_by_curr_task(dev->state->lock, true); + + // Turn off our watchdog timer + prv_disable_watchdog(dev); + + // Make sure interrupts are fully disabled before changing state + prv_interrupts_enable(dev, false); + // Put the INT pin back into a low power state that won't interfere with jtag using the pin + gpio_analog_init(&dev->int_gpio); + + PBL_LOG(LOG_LEVEL_DEBUG, "Shutting down device."); + switch (dev->state->enabled_state) { + case HRMEnabledState_PoweringOn: + new_timer_stop(dev->state->timer); + // Delay a bit so that we don't deassert the enable GPIO while in + // the loader and unintentionally activate force loader mode. + psleep(LOADER_READY_MAX_DELAY_MS); + // fallthrough + case HRMEnabledState_Enabled: + gpio_output_set(&dev->en_gpio, false); + dev->state->enabled_state = HRMEnabledState_Disabled; + break; + case HRMEnabledState_Disabled: + // nothing to do + break; + case HRMEnabledState_Uninitialized: + // the lock isn't even created yet - should never get here + // fallthrough + default: + WTF; + } + led_disable(LEDEnablerHRM); + analytics_stopwatch_stop(ANALYTICS_DEVICE_METRIC_HRM_ON_TIME); +} + +// NOTE: the caller must hold the device's state lock +static void prv_enable(HRMDevice *dev) { + mutex_assert_held_by_curr_task(dev->state->lock, true); + if (dev->state->enabled_state == HRMEnabledState_Uninitialized) { + PBL_LOG(LOG_LEVEL_ERROR, "Trying to enable HRM before initialization."); + + } else if (dev->state->enabled_state == HRMEnabledState_Disabled) { + led_enable(LEDEnablerHRM); + analytics_stopwatch_start(ANALYTICS_DEVICE_METRIC_HRM_ON_TIME, AnalyticsClient_System); + + // Enable the device and schedule a timer callback for when we can start communicating with it. + gpio_output_set(&dev->en_gpio, true); + dev->state->enabled_state = HRMEnabledState_PoweringOn; + dev->state->handshake_count = 0; + new_timer_start(dev->state->timer, NORMAL_BOOT_DELAY_MS, prv_enable_timer_cb, (void *)dev, + 0 /* flags */); + + interval_timer_init(&s_handshake_interval_timer, 900, 1100, 8); + + PBL_LOG(LOG_LEVEL_DEBUG, "Enabling AS7000..."); + } +} + +// This system task callback is triggered by the watchdog interrupt handler when we detect +// a frozen sensor +static void prv_watchdog_timer_system_cb(void *data) { + HRMDevice *dev = (HRMDevice *)data; + mutex_lock(dev->state->lock); + if (dev->state->enabled_state != HRMEnabledState_Enabled) { + goto exit; + } + + // If we have gone too long without getting an interrupt, let's reset the device + if (s_missing_interrupt_count >= AS7000_MAX_WATCHDOG_INTERRUPTS) { + PBL_LOG(LOG_LEVEL_ERROR, "Watchdog logic detected frozen sensor. Resetting now."); + analytics_inc(ANALYTICS_DEVICE_METRIC_HRM_WATCHDOG_TIMEOUT, AnalyticsClient_System); + prv_disable(dev); + psleep(SHUT_DOWN_DELAY_MS); + prv_enable(dev); + } +exit: + mutex_unlock(dev->state->lock); +} + +// This regular timer callback executes once a second. It is part of the watchdog logic used to +// detect if the sensor becomes unresponsive. +static void prv_watchdog_timer_cb(void *data) { + HRMDevice *dev = (HRMDevice *)data; + if (++s_missing_interrupt_count >= AS7000_MAX_WATCHDOG_INTERRUPTS) { + system_task_add_callback(prv_watchdog_timer_system_cb, (void *)dev); + } + if (s_missing_interrupt_count > 1) { + PBL_LOG(LOG_LEVEL_DEBUG, "Missing interrupt count: %" PRIu8 " ", s_missing_interrupt_count); + } +} + +// Enable the watchdog timer. This gets enabled when we enable the sensor and detects if +// the sensor stops generating interrupts. +static void prv_enable_watchdog(HRMDevice *dev) { + mutex_assert_held_by_curr_task(dev->state->lock, true); + s_as7000_watchdog_timer = (RegularTimerInfo){ + .cb = prv_watchdog_timer_cb, + .cb_data = (void *)dev, + }; + s_missing_interrupt_count = 0; + regular_timer_add_seconds_callback(&s_as7000_watchdog_timer); +} + +static void prv_disable_watchdog(HRMDevice *dev) { + mutex_assert_held_by_curr_task(dev->state->lock, true); + regular_timer_remove_callback(&s_as7000_watchdog_timer); + s_missing_interrupt_count = 0; +} + +static bool prv_start_loader(HRMDevice *dev) { + // check if the loader is already running + if (!prv_is_app_running(dev, AS7000AppId_Loader)) { + PBL_LOG(LOG_LEVEL_DEBUG, "Switching to loader"); + // we need to start the loader + if (!prv_set_running_apps(dev, AS7000AppId_Loader)) { + return false; + } + psleep(35); + + // make sure the loader is running + if (!prv_is_app_running(dev, AS7000AppId_Loader)) { + return false; + } + } + prv_log_running_apps(dev); + return true; +} + +static uint64_t prv_get_time_ms(void) { + time_t time_s; + uint16_t time_ms; + rtc_get_time_ms(&time_s, &time_ms); + return ((uint64_t)time_s) * 1000 + time_ms; +} + +static bool prv_wait_for_loader_ready(HRMDevice *dev) { + uint64_t end_time_ms = prv_get_time_ms() + LOADER_READY_MAX_DELAY_MS; + + do { + uint8_t status = 0; + if (!prv_read_register(dev, ADDR_LOADER_STATUS, &status)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed reading status"); + return false; + } + + if (status == AS7000LoaderStatus_Ready) { + // ready + return true; + } else if ((status != AS7000LoaderStatus_Busy1) && (status != AS7000LoaderStatus_Busy2)) { + // error + PBL_LOG(LOG_LEVEL_ERROR, "Error status: %" PRIx8, status); + return false; + } + psleep(1); + } while (prv_get_time_ms() < end_time_ms); + + PBL_LOG(LOG_LEVEL_ERROR, "Timed out waiting for the loader to be ready!"); + return false; +} + +static bool prv_flash_fw(HRMDevice *dev) { + // switch to the loader + if (!prv_start_loader(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to start loader"); + return false; + } + + // wait for the loader to be ready + if (!prv_wait_for_loader_ready(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, "Loader not ready"); + return false; + } + + const uint32_t image_length = resource_size(SYSTEM_APP, RESOURCE_ID_AS7000_FW_IMAGE); + PBL_ASSERTN(image_length); + PBL_LOG(LOG_LEVEL_DEBUG, "Loading FW image (%" PRIu32 " bytes encoded)", image_length); + // Skip over the image header. + uint32_t cursor = sizeof(AS7000FWUpdateHeader); + while (cursor < image_length) { + // Make sure we can load enough data for a valid segment. There is + // always at least one data byte in each segment, so there must be + // strictly more data to read past the end of the header. + PBL_ASSERTN((image_length - cursor) > sizeof(AS7000FWSegmentHeader)); + // Read the header. + AS7000FWSegmentHeader segment_header; + if (resource_load_byte_range_system(SYSTEM_APP, RESOURCE_ID_AS7000_FW_IMAGE, cursor, + (uint8_t *)&segment_header, + sizeof(segment_header)) != sizeof(segment_header)) { + PBL_LOG(LOG_LEVEL_ERROR, + "Failed to read FW image! " + "(segment header @ 0x%" PRIx32 ")", + cursor); + return false; + } + cursor += sizeof(segment_header); + // Write all the data bytes in the segment to the HRM. + uint16_t write_address = segment_header.address; + uint32_t bytes_remaining = segment_header.len_minus_1 + 1; + while (bytes_remaining) { + uint8_t chunk[MAX_HEX_DATA_BYTES]; + const size_t load_length = MIN(MAX_HEX_DATA_BYTES, bytes_remaining); + if (resource_load_byte_range_system(SYSTEM_APP, RESOURCE_ID_AS7000_FW_IMAGE, cursor, chunk, + load_length) != load_length) { + PBL_LOG(LOG_LEVEL_ERROR, + "Failed to read FW image! " + "(segment data @ 0x%" PRIx32 ")", + cursor); + return false; + } + + // Encode the chunk into an Intel HEX record and send it to the + // AS7000 loader. + uint8_t data_record[IHEX_RECORD_LENGTH(MAX_HEX_DATA_BYTES)]; + ihex_encode(data_record, IHEX_TYPE_DATA, write_address, chunk, load_length); + if (!prv_write_register_block(dev, ADDR_LOADER_STATUS, data_record, + IHEX_RECORD_LENGTH(load_length))) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to write hex record"); + return false; + } + + // Wait for the loader to be ready, indicating that the last + // record was successfully written. + if (!prv_wait_for_loader_ready(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, "Loader not ready"); + return false; + } + + system_task_watchdog_feed(); + + cursor += load_length; + write_address += load_length; + bytes_remaining -= load_length; + } + } + + // Write the EOF record, telling the loader that the image has been + // fully written. + uint8_t eof_record[IHEX_RECORD_LENGTH(0)]; + ihex_encode(eof_record, IHEX_TYPE_EOF, 0, NULL, 0); + if (!prv_write_register_block(dev, ADDR_LOADER_STATUS, eof_record, IHEX_RECORD_LENGTH(0))) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to write EOF record"); + return false; + } + + return true; +} + +static bool prv_set_accel_sample_frequency(HRMDevice *dev, uint16_t freq) { + const uint8_t msb = (freq >> 8) & 0xff; + const uint8_t lsb = freq & 0xff; + return prv_write_register(dev, ADDR_ACCEL_SAMPLE_FREQ_MSB, msb) && + prv_write_register(dev, ADDR_ACCEL_SAMPLE_FREQ_LSB, lsb); +} + +static void prv_enable_system_task_cb(void *context) { + HRMDevice *dev = context; + mutex_lock(dev->state->lock); + if (dev->state->enabled_state == HRMEnabledState_Disabled) { + // Enable was cancelled before this callback fired. + goto done; + } else if (dev->state->enabled_state != HRMEnabledState_PoweringOn) { + PBL_LOG(LOG_LEVEL_ERROR, + "Enable KernelBG callback fired while HRM was in " + "an unexpected state: %u", + (unsigned int)dev->state->enabled_state); + WTF; + } + + AS7000InfoRecord info; + if (!prv_get_and_log_device_info(dev, &info, false /* log_version */)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to query AS7000 device info"); + goto failed; + } + + if (info.application_id == AS7000AppId_Loader) { + // This shouldn't happen. The application firmware should have been + // flashed during boot. + PBL_LOG(LOG_LEVEL_ERROR, "AS7000 booted into loader! Something is very wrong."); + goto failed; + } + + // check that we can communicate with this chip + if (info.protocol_version_major != EXPECTED_PROTOCOL_VERSION_MAJOR) { + // we don't know how to talk with this chip, so bail + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected protocol version!"); + goto failed; + } + + if (info.application_id != AS7000AppId_Idle) { + PBL_LOG(LOG_LEVEL_ERROR, "Unexpected application running: 0x%" PRIx8, info.application_id); + goto failed; + } + + // the INT line should be low + if (gpio_input_read(&dev->int_gpio)) { + PBL_LOG(LOG_LEVEL_ERROR, "INT line is not low!"); + goto failed; + } + + // Set the accelerometer sample frequency + PBL_LOG(LOG_LEVEL_DEBUG, "Setting accel frequency"); + PBL_ASSERTN(HRM_MANAGER_ACCEL_RATE_MILLIHZ >= 10000 && HRM_MANAGER_ACCEL_RATE_MILLIHZ <= 20000); + if (!prv_set_accel_sample_frequency(dev, HRM_MANAGER_ACCEL_RATE_MILLIHZ)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to set accel frequency"); + goto failed; + } + + // Set the presence detection threshold + uint8_t pres_detect_thrsh; + WatchInfoColor model_color = mfg_info_get_watch_color(); + switch (model_color) { + case WATCH_INFO_COLOR_PEBBLE_2_HR_BLACK: + case WATCH_INFO_COLOR_PEBBLE_2_HR_FLAME: + pres_detect_thrsh = PRES_DETECT_THRSH_BLACK; + break; + case WATCH_INFO_COLOR_PEBBLE_2_HR_WHITE: + case WATCH_INFO_COLOR_PEBBLE_2_HR_LIME: + case WATCH_INFO_COLOR_PEBBLE_2_HR_AQUA: + pres_detect_thrsh = PRES_DETECT_THRSH_WHITE; + break; + default: + pres_detect_thrsh = 1; + break; + } + if (!prv_write_register(dev, ADDR_PRES_DETECT_THRSH, pres_detect_thrsh)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to set presence detection threshold"); + goto failed; + } + + // start the HRM app + PBL_LOG(LOG_LEVEL_DEBUG, "Starting HRM app"); + if (!prv_set_running_apps(dev, AS7000AppId_HRM)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to start HRM app!"); + goto failed; + } + + // Configure the int_gpio pin only when we're going to use it, as this pin is shared with + // the jtag pins and therefore can cause issues when flashing firmwares onto bigboards. + gpio_input_init_pull_up_down(&dev->int_gpio, GPIO_PuPd_UP); + + // wait for the INT line to go high indicating the Idle app has ended + if (!prv_wait_int_high(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, + "Timed-out waiting for the Idle app to end but we " + "probably just missed it"); + // TODO: The line only goes high for a few ms. If there is any kind of context switch while we + // wait for the line to go high we will miss this. Let's fix this the right way in PBL-41812 + // (check for this change via an ISR) for 4.2 but just go with the smallest change for 4.1 + } + + // wait for the INT line to go low indicating the HRM app is ready + if (!prv_wait_int_low(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, "Timed-out waiting for the HRM app to be ready"); + goto failed; + } + + // get the running apps (also triggers the app to start) + prv_log_running_apps(dev); + + // HRM app is ready, enable handshake interrupts + prv_interrupts_enable(dev, true); + + // We are now fully enabled + dev->state->enabled_state = HRMEnabledState_Enabled; + + // Enable the watchdog + prv_enable_watchdog(dev); + + goto done; + +failed: + prv_disable(dev); +done: + mutex_unlock(dev->state->lock); +} + +static void prv_enable_timer_cb(void *context) { + system_task_add_callback(prv_enable_system_task_cb, context); +} + +void hrm_init(HRMDevice *dev) { + PBL_ASSERTN(dev->state->enabled_state == HRMEnabledState_Uninitialized); + + dev->state->lock = mutex_create(); + dev->state->timer = new_timer_create(); + dev->state->enabled_state = HRMEnabledState_Disabled; + + // Boot up the HRM so that we can read off the firmware version to see + // if it needs to be updated. + + // First, read the version from the firmware update resource. + const uint32_t update_length = resource_size(SYSTEM_APP, RESOURCE_ID_AS7000_FW_IMAGE); + if (update_length == 0) { + // We don't have a firmware to write so there's no point in booting + // the HRM. + PBL_LOG(LOG_LEVEL_DEBUG, "No HRM FW update available"); + return; + } + + AS7000FWUpdateHeader image_header; + if (resource_load_byte_range_system(SYSTEM_APP, RESOURCE_ID_AS7000_FW_IMAGE, 0, + (uint8_t *)&image_header, + sizeof(image_header)) != sizeof(image_header)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to read HRM FW image header!"); + return; + } + PBL_LOG(LOG_LEVEL_DEBUG, "FW update image is v%" PRIu8 ".%" PRIu8 ".%" PRIu8, + HRM_SW_VERSION_PART_MAJOR(image_header.sw_version_major), + HRM_SW_VERSION_PART_MINOR(image_header.sw_version_major), image_header.sw_version_minor); + + // Now that we know what version the image is, actually boot up the + // HRM so we can read off the version. + + PBL_LOG(LOG_LEVEL_DEBUG, "Booting AS7000..."); + + gpio_output_init(&dev->en_gpio, GPIO_OType_PP, GPIO_Speed_2MHz); +#if HRM_FORCE_FLASH + // Force the HRM into loader mode which will cause the firmware to be + // reflashed on every boot. If the HRM is loaded with a broken + // firmware which doesn't enter standby when the enable pin is high, + // the board will need to be power-cycled (entering standby/shutdown + // is sufficient) in order to get force-flashing to succeed. + gpio_output_set(&dev->en_gpio, false); + psleep(50); + gpio_output_set(&dev->en_gpio, true); + psleep(20); + gpio_output_set(&dev->en_gpio, false); + psleep(20); +#else + gpio_output_set(&dev->en_gpio, true); + psleep(NORMAL_BOOT_DELAY_MS); +#endif + + AS7000InfoRecord hrm_info; + if (!prv_get_and_log_device_info(dev, &hrm_info, true /* log_version */)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to read AS7000 version info!"); + goto cleanup; + } + + if (hrm_info.application_id == AS7000AppId_Loader || + hrm_info.sw_version_major != image_header.sw_version_major || + hrm_info.sw_version_minor != image_header.sw_version_minor) { + // We technically could leave the firmware on the HRM alone if the + // minor version in the chip is newer than in the update image, but + // for sanity's sake let's always make sure the HRM firmware is in + // sync with the version shipped with the Pebble firmware. + PBL_LOG(LOG_LEVEL_DEBUG, "AS7000 firmware version mismatch. Flashing..."); + if (!prv_flash_fw(dev)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to flash firmware"); + goto cleanup; + } + // We need to wait for the HRM to reboot into the application before + // releasing the enable GPIO. If the loader sees the GPIO released + // during boot, it will activate "force loader mode" and fall back + // into the loader. Since we're waiting anyway, we might as well + // query the version info again to make sure the update took. + PBL_LOG(LOG_LEVEL_DEBUG, "Firmware flashed! Waiting for reboot..."); + gpio_output_set(&dev->en_gpio, true); + psleep(LOADER_REBOOT_DELAY_MS); + if (!prv_get_and_log_device_info(dev, &hrm_info, true /* log_version */)) { + PBL_LOG(LOG_LEVEL_ERROR, "Failed to read AS7000 version info after flashing!"); + goto cleanup; + } + } else { + PBL_LOG(LOG_LEVEL_DEBUG, "AS7000 firmware is up to date."); + } + +cleanup: + // At this point the HRM should either be booted and running the + // application firmware, at which point deasserting the enable GPIO + // will signal it to shut down, or the firmware update failed and the + // loader is running, where deasserting the GPIO will not do much. + gpio_output_set(&dev->en_gpio, false); +} + +void hrm_enable(HRMDevice *dev) { + if (!dev->state->lock) { + PBL_LOG(LOG_LEVEL_DEBUG, "Not an HRM Device."); + return; + } + + mutex_lock(dev->state->lock); + prv_enable(dev); + mutex_unlock(dev->state->lock); +} + +void hrm_disable(HRMDevice *dev) { + if (!dev->state->lock) { + PBL_LOG(LOG_LEVEL_DEBUG, "Not an HRM Device."); + return; + } + + mutex_lock(dev->state->lock); + prv_disable(dev); + mutex_unlock(dev->state->lock); +} + +bool hrm_is_enabled(HRMDevice *dev) { + return (dev->state->enabled_state == HRMEnabledState_Enabled || + dev->state->enabled_state == HRMEnabledState_PoweringOn); +} + +void as7000_get_version_info(HRMDevice *dev, AS7000InfoRecord *info_out) { + if (!dev->state->lock) { + PBL_LOG(LOG_LEVEL_DEBUG, "Not an HRM Device."); + return; + } + + mutex_lock(dev->state->lock); + if (!prv_get_and_log_device_info(dev, info_out, true /* log_version */)) { + PBL_LOG(LOG_LEVEL_WARNING, "Failed to read AS7000 version info"); + } + mutex_unlock(dev->state->lock); +} + +// Prompt Commands +// =============== + +#include + +#include "console/prompt.h" + +void command_hrm_wipe(void) { + // HEX records to write 0xFFFFFFFF to the magic number region. + const char *erase_magic_record = ":047FFC00FFFFFFFF85"; + const char *eof_record = ":00000001FF"; + + mutex_lock(HRM->state->lock); + gpio_output_set(&HRM->en_gpio, true); + psleep(NORMAL_BOOT_DELAY_MS); + + bool success = + prv_start_loader(HRM) && prv_wait_for_loader_ready(HRM) && + prv_write_register_block(HRM, ADDR_LOADER_STATUS, erase_magic_record, + strlen(erase_magic_record)) && + prv_wait_for_loader_ready(HRM) && + prv_write_register_block(HRM, ADDR_LOADER_STATUS, eof_record, strlen(eof_record)) && + prv_wait_for_loader_ready(HRM); + + gpio_output_set(&HRM->en_gpio, false); + mutex_unlock(HRM->state->lock); + + prompt_send_response(success ? "HRM Firmware invalidated" : "ERROR"); +} + +// Simulate a frozen sensor for testing the watchdog recovery logic +void command_hrm_freeze(void) { + HRMDevice *dev = HRM; + mutex_lock(dev->state->lock); + if (dev->state->enabled_state == HRMEnabledState_Enabled) { + prv_interrupts_enable(dev, false); + gpio_analog_init(&dev->int_gpio); + led_disable(LEDEnablerHRM); + } + mutex_unlock(dev->state->lock); +} diff --git a/src/fw/drivers/hrm/as7000.h b/src/fw/drivers/hrm/as7000.h index 1e6eaddad..3e874109d 100644 --- a/src/fw/drivers/hrm/as7000.h +++ b/src/fw/drivers/hrm/as7000.h @@ -16,17 +16,15 @@ #pragma once -#include "drivers/hrm.h" +#include #include "board/board.h" +#include "drivers/hrm.h" #include "drivers/i2c_definitions.h" #include "os/mutex.h" #include "services/common/new_timer/new_timer.h" #include "util/attributes.h" -#include - - typedef enum HRMEnabledState { HRMEnabledState_Uninitialized = 0, HRMEnabledState_Disabled, @@ -48,3 +46,16 @@ typedef const struct HRMDevice { OutputConfig en_gpio; I2CSlavePort *i2c_slave; } HRMDevice; + +typedef struct PACKED AS7000InfoRecord { + uint8_t protocol_version_major; + uint8_t protocol_version_minor; + uint8_t sw_version_major; + uint8_t sw_version_minor; + uint8_t application_id; + uint8_t hw_revision; +} AS7000InfoRecord; + +//! Fills a struct which contains version info about the AS7000 +//! This should probably only be used by the HRM Demo app +void as7000_get_version_info(HRMDevice *dev, AS7000InfoRecord *info_out); diff --git a/src/fw/drivers/wscript_build b/src/fw/drivers/wscript_build index 3a1d6230a..76273d9dd 100644 --- a/src/fw/drivers/wscript_build +++ b/src/fw/drivers/wscript_build @@ -1523,4 +1523,18 @@ elif mcu_family == 'NRF52840': ], ) +if bld.get_hrm() == 'AS7000' and bld.env.SILK_HR: + bld.objects( + name='driver_hrm', + source=[ + 'hrm/as7000.c', + ], + use=[ + 'driver_gpio', + 'driver_i2c', + 'fw_includes', + 'stm32_stlib' + ], + ) + # vim:filetype=python diff --git a/src/fw/shell/normal/system_app_registry_list.json b/src/fw/shell/normal/system_app_registry_list.json index a29f44a2a..6c26cdf6e 100644 --- a/src/fw/shell/normal/system_app_registry_list.json +++ b/src/fw/shell/normal/system_app_registry_list.json @@ -88,7 +88,9 @@ "id": -12, "enum": "BOUNCING_BOX_DEMO", "md_fn": "bouncing_box_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "robert" @@ -98,187 +100,249 @@ "id": -13, "enum": "PEBBLE_COLORS", "md_fn": "pebble_colors_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -14, "enum": "PEBBLE_SHAPES", "md_fn": "pebble_shapes_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -17, "enum": "MOVABLE_LINE", "md_fn": "movable_line_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -18, "enum": "GFX_TESTS", "md_fn": "gfx_tests_get_app_info", - "ifdefs": ["PERFORMANCE_TESTS"] + "ifdefs": [ + "PERFORMANCE_TESTS" + ] }, { "id": -19, "enum": "TEST_ARGS_RECEIVER", "md_fn": "test_args_receiver_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -20, "enum": "TEST_ARGS_SENDER", "md_fn": "test_args_sender_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -21, "enum": "FLASH_DIAGNOSTIC", "md_fn": "flash_diagnostic_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -22, "enum": "FS_RESOURCES", "md_fn": "fs_resources_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -23, "enum": "EXIT", "md_fn": "exit_app_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -25, "enum": "APP_HEAP_DEMO", "md_fn": "app_heap_demo_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -26, "enum": "DATA_LOGGING_TEST", "md_fn": "data_logging_test_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -27, "enum": "KILL_BT", "md_fn": "kill_bt_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -28, "enum": "TRIGGER_ALARM", "md_fn": "trigger_alarm_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -29, "enum": "ANIMATED_DEMO", "md_fn": "animated_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -30, "enum": "GRENADE_LAUNCHER", "md_fn": "grenade_launcher_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -31, "enum": "TEXT_LAYOUT", "md_fn": "text_layout_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -33, "enum": "MENU", "md_fn": "menu_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -34, "enum": "SIMPLE_MENU", "md_fn": "simple_menu_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -35, "enum": "SCROLL", "md_fn": "scroll_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -36, "enum": "CLICK", "md_fn": "click_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -37, "enum": "PROGRESS", "md_fn": "progress_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -38, "enum": "NUMBER_FIELD", "md_fn": "number_field_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -41, "enum": "EVENT_SERVICE", "md_fn": "event_service_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -43, "enum": "PERSIST", "md_fn": "persist_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -44, "enum": "TIMER", "md_fn": "timer_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -45, "enum": "TEST_SYS_TIMER", "md_fn": "test_sys_timer_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -46, "enum": "TEST_CORE_DUMP", "md_fn": "test_core_dump_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -47, "enum": "FLASH_PROF", "md_fn": "flash_prof_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -48, "enum": "TEST_BLUETOOTH", "md_fn": "test_bluetooth_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -50, "enum": "VIBE_AND_LOGS", "md_fn": "vibe_and_logs_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -51, "enum": "MENU_OVERFLOW", "md_fn": "menu_overflow_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -54, @@ -289,19 +353,25 @@ "id": -55, "enum": "TEXT_CLIPPING", "md_fn": "text_clipping_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -56, "enum": "LIGHT_CONFIG", "md_fn": "light_config_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -57, "enum": "STROKE_WIDTH", "md_fn": "stroke_width_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy" ] @@ -310,14 +380,16 @@ "id": -58, "enum": "AMB_LIGHT_READ", "md_fn": "ambient_light_reading_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -59, "enum": "WEATHER", "md_fn": "weather_app_get_info", "color_argb8": "GColorBlueMoonARGB8", - "target_platforms":[ + "target_platforms": [ "snowy", "spalding", "silk", @@ -347,7 +419,9 @@ "id": -61, "enum": "SWAP_LAYER", "md_fn": "swap_layer_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -62, @@ -358,85 +432,114 @@ "id": -63, "enum": "TEXT_SPACING", "md_fn": "text_spacing_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -64, "enum": "KINO_LAYER", "md_fn": "kino_layer_demo_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -65, "enum": "DIALOGS", "md_fn": "dialogs_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -66, "enum": "STATUSBAR", "md_fn": "statusbar_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -68, "enum": "MORPH_SQUARE", "md_fn": "morph_square_demo_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -70, "enum": "MENU_RIGHT_ICON", "md_fn": "menu_layer_right_icon_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -71, "enum": "VIBE_STRENGTH", "md_fn": "vibe_strength_demo_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -72, "enum": "ACTION_MENU", "md_fn": "action_menu_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -73, "enum": "OPTION_MENU", "md_fn": "option_menu_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -74, "enum": "PROFILE_MUTEXES", "md_fn": "profile_mutexes_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS", "DISABLED"] + "ifdefs": [ + "ENABLE_TEST_APPS", + "DISABLED" + ] }, { "id": -75, "enum": "DEADLOCK", "md_fn": "deadlock_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -76, "enum": "TIMELINE_PINS", "md_fn": "timeline_pins_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -77, "enum": "TEXT_FLOW", "md_fn": "text_flow_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -78, "enum": "MENU_ROUND", "md_fn": "menu_round_app_get_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "spalding" ] @@ -445,7 +548,9 @@ "id": -79, "enum": "ACTIVITY_DEMO", "md_fn": "activity_demo_get_app_info", - "ifdefs": ["SHOW_ACTIVITY_DEMO"], + "ifdefs": [ + "SHOW_ACTIVITY_DEMO" + ], "target_platforms": [ "snowy", "spalding", @@ -457,7 +562,9 @@ "id": -80, "enum": "ACTIVITY_TEST", "md_fn": "activity_test_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "spalding", @@ -469,7 +576,9 @@ "id": -81, "enum": "DOUBLE_TAP_TEST", "md_fn": "double_tap_test_get_info", - "ifdefs": ["ENABLE_TEST_APPS"] + "ifdefs": [ + "ENABLE_TEST_APPS" + ] }, { "id": -82, @@ -498,7 +607,9 @@ "id": -84, "enum": "VIBE_SCORE", "md_fn": "vibe_score_demo_get_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "spalding" @@ -508,7 +619,9 @@ "id": -86, "enum": "IDL", "md_fn": "idl_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "spalding" @@ -518,7 +631,9 @@ "id": -87, "enum": "TEMPERATURE_DEMO", "md_fn": "temperature_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy" ] @@ -527,12 +642,23 @@ "id": -88, "enum": "GDRAWMASK_DEMO", "md_fn": "gdrawmask_demo_get_app_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "spalding" ] }, + { + "id": -89, + "enum": "HRM_DEMO", + "md_fn": "hrm_demo_get_app_info", + "ifdefs": [ + "SHOW_ACTIVITY_DEMO", + "CAPABILITY_HAS_BUILTIN_HRM=1" + ] + }, { "id": -90, "enum": "REMINDERS", @@ -544,13 +670,17 @@ "silk", "robert" ], - "ifdefs": ["CAPABILITY_HAS_MICROPHONE=1"] + "ifdefs": [ + "CAPABILITY_HAS_MICROPHONE=1" + ] }, { "id": -91, "enum": "MPU_TEST", "md_fn": "test_mpu_cache_get_info", - "ifdefs": ["ENABLE_TEST_APPS"], + "ifdefs": [ + "ENABLE_TEST_APPS" + ], "target_platforms": [ "snowy", "spalding", diff --git a/src/fw/wscript b/src/fw/wscript index ce368a90c..57e5c083f 100644 --- a/src/fw/wscript +++ b/src/fw/wscript @@ -581,7 +581,7 @@ def _build_normal(bld): excludes.extend(_get_launcher_globs_to_exclude(bld)) - if not bld.capability('HAS_BUILTIN_HRM'): + if not bld.capability('HAS_BUILTIN_HRM') or not bld.env.SILK_HR: excludes.append('popups/ble_hrm/**') if bld.is_tintin(): diff --git a/tests/overrides/default/resources/robert/resource/resource_ids.auto.h b/tests/overrides/default/resources/robert/resource/resource_ids.auto.h index 1ca72dd8a..3f48d5f38 100644 --- a/tests/overrides/default/resources/robert/resource/resource_ids.auto.h +++ b/tests/overrides/default/resources/robert/resource/resource_ids.auto.h @@ -491,6 +491,7 @@ typedef enum { RESOURCE_ID_STORED_APP_GOLF = 464, RESOURCE_ID_BT_BOOT_IMAGE = 465, RESOURCE_ID_BT_FW_IMAGE = 466, + RESOURCE_ID_AS7000_FW_IMAGE = 467, RESOURCE_ID_TIMEZONE_DATABASE = 468, RESOURCE_ID_ACTION_BAR_ICON_CHECK = 469, RESOURCE_ID_GENERIC_WARNING_LARGE = 470, diff --git a/tests/overrides/default/resources/silk/resource/resource_ids.auto.h b/tests/overrides/default/resources/silk/resource/resource_ids.auto.h index 460058375..f2010f7fc 100644 --- a/tests/overrides/default/resources/silk/resource/resource_ids.auto.h +++ b/tests/overrides/default/resources/silk/resource/resource_ids.auto.h @@ -490,6 +490,7 @@ typedef enum { RESOURCE_ID_STORED_APP_GOLF = 463, RESOURCE_ID_BT_BOOT_IMAGE = 464, RESOURCE_ID_BT_FW_IMAGE = 465, + RESOURCE_ID_AS7000_FW_IMAGE = 466, RESOURCE_ID_TIMEZONE_DATABASE = 467, RESOURCE_ID_FONT_FALLBACK_INTERNAL = 468, RESOURCE_ID_ARROW_DOWN = 469, diff --git a/wscript b/wscript index fa7aeb43f..feac4654e 100644 --- a/wscript +++ b/wscript @@ -172,6 +172,8 @@ def options(opt): opt.add_option('--reboot_on_bt_crash', action='store_true', help='Forces a BT ' 'chip crash to immediately force a system reboot instead of just cycling airplane mode. ' 'This makes it easier for us to actually get crash info') + opt.add_option('--enable_silk_hr', action='store_true', + help='Enable heart rate sensor on Silk, downloads the proprietary firmware from the server if available') def handle_configure_options(conf): @@ -335,6 +337,89 @@ def handle_configure_options(conf): if not conf.options.mfg and not conf.options.no_pulse_everywhere: conf.env.append_value('DEFINES', 'PULSE_EVERYWHERE=1') + + if conf.options.enable_silk_hr and conf.is_silk(): + conf.env.SILK_HR = True + print("Enabling Silk heart rate sensor") + + import urllib.request + import json + + # Store the firmware in resources/normal/silk/blobs + hr_fw_dir = conf.path.make_node('resources/normal/silk/blobs') + if not os.path.exists(hr_fw_dir.abspath()): + os.makedirs(hr_fw_dir.abspath()) + + # Path to save the firmware + hr_fw_path = hr_fw_dir.make_node('as7000_fw_image.bin') + conf.env.HR_FW_PATH = hr_fw_path.abspath() + + # Download the firmware if it doesn't exist + if not os.path.exists(hr_fw_path.abspath()): + print("Downloading AS7000 heart rate sensor firmware...") + hr_fw_url = "https://github.com/jplexer/AS7000_FW/raw/refs/heads/main/as7000_fw_image.bin" + try: + urllib.request.urlretrieve(hr_fw_url, hr_fw_path.abspath()) + print("AS7000 firmware downloaded successfully") + except Exception as e: + print(f"Error downloading AS7000 firmware: {e}") + else: + print(f"Using existing AS7000 firmware at {hr_fw_path.abspath()}") + + # Add the firmware to the resource map manually to preserve formatting + resource_map_path = conf.path.make_node('resources/normal/silk/resource_map.json') + if os.path.exists(resource_map_path.abspath()): + try: + # Check if our entry is already there + with open(resource_map_path.abspath(), 'r') as f: + content = f.read() + + if '"name": "AS7000_FW_IMAGE"' not in content: + print("Adding AS7000 firmware to resource map") + + # Find the position to insert our entry - right before the last entry + insert_pos = content.rindex(' ]') + + # Create our entry with the same formatting style as the rest of the file + new_entry = ',\n {\n "type": "raw",\n "name": "AS7000_FW_IMAGE",\n "file": "normal/silk/blobs/as7000_fw_image.bin",\n "builtin": true\n }' + + # Insert our entry + new_content = content[:insert_pos] + new_entry + content[insert_pos:] + + # Write back the modified content + with open(resource_map_path.abspath(), 'w') as f: + f.write(new_content) + + print("AS7000 firmware added to resource map") + else: + print("AS7000 firmware already present in resource map") + except Exception as e: + print(f"Error modifying resource map: {e}") + else: + # If Silk HR is not enabled, remove the AS7000 firmware entry manually + if conf.is_silk(): + resource_map_path = conf.path.make_node('resources/normal/silk/resource_map.json') + if os.path.exists(resource_map_path.abspath()): + try: + with open(resource_map_path.abspath(), 'r') as f: + content = f.read() + + # Try to find and remove our entry + import re + pattern = r',?\s*\{\s*"type"\s*:\s*"raw"\s*,\s*"name"\s*:\s*"AS7000_FW_IMAGE"\s*,[^}]*\}' + new_content = re.sub(pattern, '', content) + + # Fix any double commas that might result + new_content = new_content.replace(',\n ,', ',') + + # Write back the modified content if it changed + if new_content != content: + print("Removing AS7000 firmware from resource map") + with open(resource_map_path.abspath(), 'w') as f: + f.write(new_content) + + except Exception as e: + print(f"Error modifying resource map: {e}") def _create_cm0_env(conf): prev_env = conf.env