diff options
-rw-r--r-- | apps/accelerometer-test/app.c | 27 | ||||
-rw-r--r-- | apps/spi-test/app.c | 207 | ||||
-rwxr-xr-x | movement/make/Makefile | 1 | ||||
-rw-r--r-- | movement/movement_faces.h | 1 | ||||
-rw-r--r-- | movement/template/watch_face.py | 4 | ||||
-rw-r--r-- | movement/watch_faces/sensor/accelerometer_data_acquisition_face.c | 480 | ||||
-rw-r--r-- | movement/watch_faces/sensor/accelerometer_data_acquisition_face.h | 111 | ||||
-rwxr-xr-x | utils/motion_express_utilities/csv2gnuplot.sh | 379 | ||||
-rw-r--r-- | utils/motion_express_utilities/plot.options | 2 | ||||
-rw-r--r-- | utils/motion_express_utilities/process_motion_dump.py | 75 | ||||
-rw-r--r-- | watch-library/shared/config/hpl_sercom_config.h | 2 | ||||
-rw-r--r-- | watch-library/shared/driver/lis2dw.c | 55 | ||||
-rw-r--r-- | watch-library/shared/driver/lis2dw.h | 22 | ||||
-rw-r--r-- | watch-library/shared/driver/spiflash.h | 2 |
14 files changed, 1327 insertions, 41 deletions
diff --git a/apps/accelerometer-test/app.c b/apps/accelerometer-test/app.c index 1a3acafa..a52fbbfd 100644 --- a/apps/accelerometer-test/app.c +++ b/apps/accelerometer-test/app.c @@ -16,18 +16,13 @@ static void cb_alarm_pressed(void) { uint8_t interrupts = 0; uint8_t last_interrupts = 0; -uint8_t ticks = 0; +bool tick = false; char buf[13] = {0}; static void cb_tick(void) { - watch_clear_indicator(WATCH_INDICATOR_SIGNAL); if (!lis2dw_have_new_data()) return; - watch_set_indicator(WATCH_INDICATOR_SIGNAL); - lis2dw_fifo_t fifo; - bool overrun = lis2dw_read_fifo(&fifo); - printf("FIFO captured %d readings.\n", fifo.count); - if (overrun) printf("\tThere was an overrun!\n\n"); + tick = true; } void app_init(void) { @@ -41,10 +36,10 @@ void app_init(void) { watch_enable_i2c(); lis2dw_begin(); - lis2dw_set_low_power_mode(LIS2DW_LP_MODE_2); // lowest power 14-bit mode, 25 Hz is 3.5 µA @ 1.8V w/ low noise, 3µA without - lis2dw_set_low_noise_mode(true); // consumes a little more power - lis2dw_set_range(LIS2DW_CTRL6_VAL_RANGE_4G); - lis2dw_set_data_rate(LIS2DW_DATA_RATE_25_HZ); // is this enough for training? + lis2dw_set_data_rate(LIS2DW_DATA_RATE_25_HZ); + lis2dw_set_range(LIS2DW_RANGE_4_G); + lis2dw_set_low_noise_mode(true); + lis2dw_enable_fifo(); lis2dw_enable_fifo(); @@ -64,8 +59,14 @@ void app_wake_from_standby(void) { } bool app_loop(void) { - // TODO: interrupt configuration for LIS2DW - watch_display_string(buf, 0); + if (tick) { + tick = false; + lis2dw_fifo_t fifo; + lis2dw_read_fifo(&fifo); + for(int i = 0; i < fifo.count; i++) { + printf("%d, %d, %d, %d, %d\n", fifo.readings[i].x, fifo.readings[i].y, fifo.readings[i].z, i, fifo.count); + } + } return true; } diff --git a/apps/spi-test/app.c b/apps/spi-test/app.c index 428f55d3..f45c783b 100644 --- a/apps/spi-test/app.c +++ b/apps/spi-test/app.c @@ -1,10 +1,45 @@ #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <peripheral_clk_config.h> #include "watch.h" +#include "watch_utility.h" #include "spiflash.h" +#include "lis2dw.h" -// this is a very basic app to confirm that SPI is working, tested with board OSO-MISC-21-017 and a GD25Q16C Flash chip. +#define ACCELEROMETER_DATA_ACQUISITION_INVALID ((uint64_t)(0b11)) // all bits are 1 when the flash is erased +#define ACCELEROMETER_DATA_ACQUISITION_HEADER ((uint64_t)(0b10)) +#define ACCELEROMETER_DATA_ACQUISITION_DATA ((uint64_t)(0b01)) +#define ACCELEROMETER_DATA_ACQUISITION_DELETED ((uint64_t)(0b00)) // You can always write a 0 to any 1 bit + +typedef union { + struct { + struct { + uint16_t record_type : 2; // see above, helps us identify record types when reading back + uint16_t range : 2; // accelerometer range (see lis2dw_range_t) + uint16_t temperature : 12; // raw value from the temperature sensor + } info; + uint8_t char1 : 8; // First character of the activity type + uint8_t char2 : 8; // Second character of the activity type + uint32_t timestamp : 32; // UNIX timestamp for the measurement + } header; + struct { + struct { + uint16_t record_type : 2; // duplicate; this is the same field as info above + uint16_t accel : 14; // X acceleration value, raw, offset by 8192 + } x; + struct { + uint16_t lpmode : 2; // low power mode (see lis2dw_low_power_mode_t) + uint16_t accel : 14; // Y acceleration value, raw, offset by 8192 + } y; + struct { + uint16_t filter : 2; // bandwidth filtering selection (see lis2dw_bandwidth_filtering_mode_t) + uint16_t accel : 14; // Z acceleration value, raw, offset by 8192 + } z; + uint32_t counter : 16; // number of centiseconds since timestamp in header + } data; + uint64_t value; +} accelerometer_data_acquisition_record_t; static bool wait_for_flash_ready(void) { watch_set_pin_level(A3, false); @@ -12,20 +47,168 @@ static bool wait_for_flash_ready(void) { uint8_t read_status_response[1] = {0x00}; do { ok = spi_flash_read_command(CMD_READ_STATUS, read_status_response, 1); - } while (ok && (read_status_response[0] & 0x3) != 0); + } while ((read_status_response[0] & 0x3) != 0); + delay_ms(1); // why do i need this? watch_set_pin_level(A3, true); return ok; } +static void write_buffer_to_page(uint8_t *buf, uint16_t page) { + uint32_t address = 256 * page; + + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_command(CMD_ENABLE_WRITE); + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_write_data(address, buf, 256); + wait_for_flash_ready(); + + uint8_t buf2[256]; + watch_set_pin_level(A3, false); + spi_flash_read_data(address, buf2, 256); + wait_for_flash_ready(); + + uint8_t used_pages[256] = {0xFF}; + uint16_t address_to_mark_used = page / 8; + uint8_t header_page = address_to_mark_used / 256; + uint8_t used_byte = 0x7F >> (page % 8); + uint8_t offset_in_buf = address_to_mark_used % 256; + + watch_set_pin_level(A3, false); + spi_flash_read_data(header_page * 256, used_pages, 256); + used_pages[offset_in_buf] = used_byte; + watch_set_pin_level(A3, false); + spi_flash_command(CMD_ENABLE_WRITE); + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_write_data(header_page * 256, used_pages, 256); + wait_for_flash_ready(); +} + +static void print_records_at_page(uint16_t page) { + accelerometer_data_acquisition_record_t records[32]; + static uint64_t timestamp = 0; + // static uint16_t temperature = 0; + static lis2dw_range_t range = LIS2DW_RANGE_2_G; + static double lsb_value = 1; + static bool printing_header = false; + + wait_for_flash_ready(); + spi_flash_read_data(page * 256, (void *)records, 256); + for(int i = 0; i < 32; i++) { + switch (records[i].header.info.record_type) { + case ACCELEROMETER_DATA_ACQUISITION_HEADER: + printing_header = true; + timestamp = records[i].header.timestamp; + // temperature = records[i].header.info.temperature; + printf("%c%c.%lld.", records[i].header.char1, records[i].header.char2, timestamp); + range = records[i].header.info.range; + break; + case ACCELEROMETER_DATA_ACQUISITION_DATA: + if (printing_header) { + printing_header = false; + uint8_t filter = 0; + switch (records[i].data.z.filter) { + case LIS2DW_BANDWIDTH_FILTER_DIV2: + filter = 2; + break; + case LIS2DW_BANDWIDTH_FILTER_DIV4: + filter = 4; + break; + case LIS2DW_BANDWIDTH_FILTER_DIV10: + filter = 10; + break; + case LIS2DW_BANDWIDTH_FILTER_DIV20: + filter = 20; + break; + } + switch (range) { + case LIS2DW_RANGE_16_G: + lsb_value = (records[i].data.y.lpmode == LIS2DW_LP_MODE_1) ? 7.808 : 1.952; + range = 16; + break; + case LIS2DW_RANGE_8_G: + lsb_value = (records[i].data.y.lpmode == LIS2DW_LP_MODE_1) ? 3.904 : 0.976; + range = 8; + break; + case LIS2DW_RANGE_4_G: + lsb_value = (records[i].data.y.lpmode == LIS2DW_LP_MODE_1) ? 1.952 : 0.488; + range = 4; + break; + case LIS2DW_RANGE_2_G: + lsb_value = (records[i].data.y.lpmode == LIS2DW_LP_MODE_1) ? 0.976 : 0.244; + range = 2; + break; + } + printf("RANGE%d_LP%d_FILT%d.CSV\n", range, records[i].data.y.lpmode + 1, filter); + printf("timestamp,accX,accY,accZ\n"); + } + printf("%lld,%f,%f,%f\n", + (timestamp * 100 + records[i].data.counter) * 10, + 9.80665 * ((double)(records[i].data.x.accel - 8192)) * lsb_value / 1000, + 9.80665 * ((double)(records[i].data.y.accel - 8192)) * lsb_value / 1000, + 9.80665 * ((double)(records[i].data.z.accel - 8192)) * lsb_value / 1000); + break; + case ACCELEROMETER_DATA_ACQUISITION_INVALID: + case ACCELEROMETER_DATA_ACQUISITION_DELETED: + // don't print anything + break; + } + records[i].header.info.record_type = ACCELEROMETER_DATA_ACQUISITION_DELETED; + } + + // uncomment this to mark all pages deleted + // write_buffer_to_page((uint8_t *)records, page); +} + +static void print_records() { + uint8_t buf[256]; + for(int16_t i = 0; i < 4; i++) { + wait_for_flash_ready(); + spi_flash_read_data(i * 256, buf, 256); + for(int16_t j = 0; j < 256; j++) { + uint8_t pages_written = buf[j]; + uint8_t start = 0; + if (i == 0 && j == 0) { + pages_written <<= 4; + start = 4; + } + for(int k = start; k < 8; k++) { + if ((pages_written & 0x80) == 0) { + print_records_at_page(i * 2048 + j * 8 + k); + } + pages_written <<= 1; + } + } + } + + printf("=== END ===\n"); +} + void app_init(void) { - spi_flash_init(); - delay_ms(5000); } void app_wake_from_backup(void) { } void app_setup(void) { + spi_flash_init(); + delay_ms(5000); + + // bool erase = false; + // if (erase) { + // printf("Erasing...\n"); + // wait_for_flash_ready(); + // watch_set_pin_level(A3, false); + // spi_flash_command(CMD_ENABLE_WRITE); + // wait_for_flash_ready(); + // watch_set_pin_level(A3, false); + // spi_flash_command(CMD_CHIP_ERASE); + // delay_ms(10000); + // } + + print_records(); } void app_prepare_for_standby(void) { @@ -34,20 +217,8 @@ void app_prepare_for_standby(void) { void app_wake_from_standby(void) { } - bool app_loop(void) { - uint8_t buf[3] = {0}; - printf("loop\n"); - - wait_for_flash_ready(); - watch_set_pin_level(A3, false); - spi_flash_read_command(CMD_READ_JEDEC_ID, buf, 3); - printf("ident: %x, %x, %x\n", buf[0], buf[1], buf[2]); - - watch_set_pin_level(A3, true); - wait_for_flash_ready(); - - delay_ms(10000); + delay_ms(5000); - return false; + return true; } diff --git a/movement/make/Makefile b/movement/make/Makefile index 5bed6bd2..0936a26c 100755 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -56,6 +56,7 @@ SRCS += \ ../watch_faces/complication/counter_face.c \ ../watch_faces/complication/blinky_face.c \ ../watch_faces/complication/moon_phase_face.c \ + ../watch_faces/sensor/accelerometer_data_acquisition_face.c \ ../watch_faces/clock/mars_time_face.c \ ../watch_faces/complication/orrery_face.c \ ../watch_faces/complication/astronomy_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index f27d9f91..e7e78ff4 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -47,6 +47,7 @@ #include "counter_face.h" #include "blinky_face.h" #include "moon_phase_face.h" +#include "accelerometer_data_acquisition_face.h" #include "mars_time_face.h" #include "orrery_face.h" #include "astronomy_face.h" diff --git a/movement/template/watch_face.py b/movement/template/watch_face.py index 4833c382..4143b01f 100644 --- a/movement/template/watch_face.py +++ b/movement/template/watch_face.py @@ -71,8 +71,8 @@ def update_include_file(file_path, indicator_line, line_to_insert): def main(): parser = argparse.ArgumentParser(description="Create a new watch face.") - parser.add_argument("watch_face_type", metavar="face_type", type=str, choices=["complication", "clock"], help="The type of watch face to create, either \"complication\" or \"clock\"") - parser.add_argument("watch_face_name", metavar="face_name", type=str, help="The name of the watch face") + parser.add_argument("watch_face_type", metavar="face_type", type=str, choices=["clock", "complication", "demo", "sensor", "settings"], help="The type of watch face to create ('clock', 'complication', 'demo', 'sensor', 'settings')") + parser.add_argument("watch_face_name", metavar="face_name", type=str, help="The name of the watch face. Use underscores between words if you have more than one.") parser.add_argument("--author-name", metavar="author_name", type=str, nargs='*', help="The name of the author") args = parser.parse_args() diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c new file mode 100644 index 00000000..6ef9c6db --- /dev/null +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.c @@ -0,0 +1,480 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdlib.h> +#include <string.h> +#include "accelerometer_data_acquisition_face.h" +#include "watch_utility.h" +#include "lis2dw.h" +#include "spiflash.h" + +#define ACCELEROMETER_RANGE LIS2DW_RANGE_4_G +#define ACCELEROMETER_LPMODE LIS2DW_LP_MODE_2 +#define ACCELEROMETER_FILTER LIS2DW_BANDWIDTH_FILTER_DIV2 +#define ACCELEROMETER_LOW_NOISE true +#define SECONDS_TO_RECORD 15 + +static const char activity_types[][3] = { + "TE", // Testing + "ID", // Idle + "OF", // Off-wrist + "SL", // Sleeping + "WH", // Washing Hands + "WA", // Walking + "WB", // Walking with Beverage + "JO", // Jogging + "RU", // Running + "BI", // Biking + "HI", // Hiking + "EL", // Elliptical + "SU", // Stairs Up + "SD", // Stairs Down + "WL", // Weight Lifting +}; + +static void update(accelerometer_data_acquisition_state_t *state); +static void update_settings(accelerometer_data_acquisition_state_t *state); +static void advance_current_setting(accelerometer_data_acquisition_state_t *state); +static void start_reading(accelerometer_data_acquisition_state_t *state, movement_settings_t *settings); +static void continue_reading(accelerometer_data_acquisition_state_t *state); +static void finish_reading(accelerometer_data_acquisition_state_t *state); +static bool wait_for_flash_ready(void); +static int16_t get_next_available_page(void); +static void write_buffer_to_page(uint8_t *buf, uint16_t page); +static void write_page(accelerometer_data_acquisition_state_t *state); +static void log_data_point(accelerometer_data_acquisition_state_t *state, lis2dw_reading_t reading, uint8_t centiseconds); + +void accelerometer_data_acquisition_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) settings; + (void) watch_face_index; + accelerometer_data_acquisition_state_t *state = (accelerometer_data_acquisition_state_t *)*context_ptr; + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(accelerometer_data_acquisition_state_t)); + memset(*context_ptr, 0, sizeof(accelerometer_data_acquisition_state_t)); + state = (accelerometer_data_acquisition_state_t *)*context_ptr; + state->beep_with_countdown = true; + state->countdown_length = 3; + } + spi_flash_init(); + wait_for_flash_ready(); + uint8_t buf[256] = {0xFF}; + spi_flash_read_data(0, buf, 256); + if (buf[0] & 0xF0) { + // mark first four pages as used + buf[0] = 0x0F; + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_command(CMD_ENABLE_WRITE); + wait_for_flash_ready(); + spi_flash_write_data(0, buf, 256); + } + +} + +void accelerometer_data_acquisition_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + accelerometer_data_acquisition_state_t *state = (accelerometer_data_acquisition_state_t *)context; + state->next_available_page = get_next_available_page(); +} + +bool accelerometer_data_acquisition_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + accelerometer_data_acquisition_state_t *state = (accelerometer_data_acquisition_state_t *)context; + + switch (event.event_type) { + case EVENT_ACTIVATE: + case EVENT_TICK: + switch (state->mode) { + case ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE: + update(state); + if (state->repeat_ticks > 0) { + state->repeat_ticks--; + if (state->repeat_ticks == 0) { + state->countdown_ticks = state->countdown_length; + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN; + } + } + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN: + if (state->next_available_page < 0) { + state->countdown_ticks = 0; + state->repeat_ticks = 0; + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE; + } + if (state->countdown_ticks > 0) { + state->countdown_ticks--; + printf("countdown: %d\n", state->countdown_ticks); + if (state->countdown_ticks == 0) { + // at zero, begin reading + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_SENSING; + state->reading_ticks = SECONDS_TO_RECORD + 1; + // also beep if the user asked for it + if (state->beep_with_countdown) watch_buzzer_play_note(BUZZER_NOTE_C6, 75); + start_reading(state, settings); + } else if (state->countdown_ticks < 3) { + // beep for last two ticks before reading + if (state->beep_with_countdown) watch_buzzer_play_note(BUZZER_NOTE_C5, 75); + } + } + update(state); + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_SENSING: + if (state->reading_ticks > 0) { + state->reading_ticks--; + if (state->reading_ticks > 0) { + continue_reading(state); + } else { + finish_reading(state); + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE; + watch_buzzer_play_note(BUZZER_NOTE_C4, 125); + watch_buzzer_play_note(BUZZER_NOTE_REST, 50); + watch_buzzer_play_note(BUZZER_NOTE_C4, 125); + } + } + update(state); + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_SETTINGS: + update_settings(state); + break; + } + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + case EVENT_LIGHT_BUTTON_UP: + switch (state->mode) { + case ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE: + state->activity_type_index = (state->activity_type_index + 1) % (sizeof(activity_types) / sizeof(activity_types[0])); + update(state); + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_SETTINGS: + state->settings_page++; + if (state->settings_page > ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_REPEAT) { + state->settings_page = 0; + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE; + update(state); + } else { + update_settings(state); + } + break; + default: + break; + } + break; + case EVENT_LIGHT_LONG_PRESS: + movement_illuminate_led(); + break; + case EVENT_ALARM_BUTTON_UP: + printf("Alarm up! Mode is %d\n", state->mode); + switch (state->mode) { + case ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE: + state->countdown_ticks = state->countdown_length; + printf("Setting countdown ticks to %d\n", state->countdown_ticks); + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN; + printf("and mode to %d\n", state->mode); + update(state); + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN: + // cancel countdown + state->countdown_ticks = 0; + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE; + update(state); + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_SETTINGS: + advance_current_setting(state); + update_settings(state); + break; + default: + break; + } + break; + case EVENT_ALARM_LONG_PRESS: + printf("Alarm long\n"); + if (state->mode == ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE) { + state->repeat_ticks = 0; + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_SETTINGS; + update_settings(state); + } + break; + default: + break; + } + + return true; +} + +void accelerometer_data_acquisition_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + accelerometer_data_acquisition_state_t *state = (accelerometer_data_acquisition_state_t *)context; + if (state->reading_ticks) { + state->reading_ticks = 0; + finish_reading(state); + } + state->mode = ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE; + state->settings_page = 0; + state->countdown_ticks = 0; + state->repeat_ticks = 0; + state->reading_ticks = 0; +} + +static void update(accelerometer_data_acquisition_state_t *state) { + char buf[14]; + uint8_t ticks = 0; + switch (state->mode) { + case ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE: + ticks = state->countdown_length; + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN: + ticks = state->countdown_ticks; + break; + case ACCELEROMETER_DATA_ACQUISITION_MODE_SENSING: + ticks = state->reading_ticks; + break; + default: + ticks = 0; + break; + } + sprintf(buf, "%s%2dre%2d#o", + activity_types[state->activity_type_index], + ticks, + (8192 - state->next_available_page) / 82); + watch_display_string(buf, 0); + + watch_set_colon(); + + // special case: display full if full, <1% if nearly full + if (state->next_available_page < 0) watch_display_string(" FUL", 6); + else if (state->next_available_page > 8110) watch_display_string("<1", 6); + + // Bell if beep enabled + if (state->beep_with_countdown) watch_set_indicator(WATCH_INDICATOR_BELL); + else watch_clear_indicator(WATCH_INDICATOR_BELL); + + // Signal if sensing + if (state->reading_ticks) watch_set_indicator(WATCH_INDICATOR_SIGNAL); + else watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + + // LAP if repeating + if (state->repeat_ticks) watch_set_indicator(WATCH_INDICATOR_LAP); + else watch_clear_indicator(WATCH_INDICATOR_LAP); + +} + +static void update_settings(accelerometer_data_acquisition_state_t *state) { + char buf[12]; + watch_clear_colon(); + if (state->beep_with_countdown) watch_set_indicator(WATCH_INDICATOR_BELL); + else watch_clear_indicator(WATCH_INDICATOR_BELL); + switch (state->settings_page) { + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_SOUND: + sprintf(buf, "SO Beep %c", state->beep_with_countdown ? 'Y' : 'N'); + watch_display_string(buf, 0); + break; + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_DELAY: + sprintf(buf, "DL %2d SeC", state->countdown_length); + watch_display_string(buf, 0); + break; + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_REPEAT: + if (state->repeat_interval == 0) { + watch_display_string("rE none ", 0); + } else { + sprintf(buf, "rE %2dn&in", state->repeat_interval / 60); + watch_display_string(buf, 0); + } + break; + } +} + +static void advance_current_setting(accelerometer_data_acquisition_state_t *state) { + switch (state->settings_page) { + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_SOUND: + state->beep_with_countdown = !state->beep_with_countdown; + break; + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_DELAY: + // this is so lazy, i'm sorry + if (state->countdown_length == 1) state->countdown_length = 3; + else if (state->countdown_length == 3) state->countdown_length = 10; + else if (state->countdown_length == 10) state->countdown_length = 30; + else state->countdown_length = 1; + break; + case ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_REPEAT: + if (state->repeat_interval == 0) state->repeat_interval = 60; + else if (state->repeat_interval == 60) state->repeat_interval = 600; + else if (state->repeat_interval == 600) state->repeat_interval = 1800; + else if (state->repeat_interval == 1800) state->repeat_interval = 3600; + else state->repeat_interval = 0; + break; + } +} + +static int16_t get_next_available_page(void) { + uint8_t buf[256] = {0}; + + uint16_t page = 0; + for(int16_t i = 0; i < 4; i++) { + wait_for_flash_ready(); + spi_flash_read_data(i * 256, buf, 256); + for(int16_t j = 0; j < 256; j++) { + if(buf[j] == 0) { + page += 8; + } else { + page += __builtin_clz(((uint32_t)buf[j]) << 24); + break; + } + } + } + + if (page >= 8192) return -1; + + return page; +} + +static void write_buffer_to_page(uint8_t *buf, uint16_t page) { + uint32_t address = 256 * page; + + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_command(CMD_ENABLE_WRITE); + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_write_data(address, buf, 256); + wait_for_flash_ready(); + + uint8_t buf2[256]; + watch_set_pin_level(A3, false); + spi_flash_read_data(address, buf2, 256); + wait_for_flash_ready(); + + uint8_t used_pages[256] = {0xFF}; + uint16_t address_to_mark_used = page / 8; + uint8_t header_page = address_to_mark_used / 256; + uint8_t used_byte = 0x7F >> (page % 8); + uint8_t offset_in_buf = address_to_mark_used % 256; + + printf("\twrite 256 bytes to address %ld, page %d.\n", address, page); + for(int i = 0; i < 256; i++) { + if (buf[i] != buf2[i]) { + printf("\tData mismatch detected at offset %d: %d != %d.\n", i, buf[i], buf2[i]); + } + } + + watch_set_pin_level(A3, false); + spi_flash_read_data(header_page * 256, used_pages, 256); + used_pages[offset_in_buf] = used_byte; + watch_set_pin_level(A3, false); + spi_flash_command(CMD_ENABLE_WRITE); + wait_for_flash_ready(); + watch_set_pin_level(A3, false); + spi_flash_write_data(header_page * 256, used_pages, 256); + wait_for_flash_ready(); +} + +static bool wait_for_flash_ready(void) { + watch_set_pin_level(A3, false); + bool ok = true; + uint8_t read_status_response[1] = {0x00}; + do { + ok = spi_flash_read_command(CMD_READ_STATUS, read_status_response, 1); + } while ((read_status_response[0] & 0x3) != 0); + delay_ms(1); // why do i need this? + watch_set_pin_level(A3, true); + return ok; +} + +static void write_page(accelerometer_data_acquisition_state_t *state) { + if (state->next_available_page > 0) { + write_buffer_to_page((uint8_t *)(state->records), state->next_available_page); + wait_for_flash_ready(); + state->next_available_page++; + } + state->pos = 0; + memset(state->records, 0xFF, sizeof(state->records)); +} + +static void log_data_point(accelerometer_data_acquisition_state_t *state, lis2dw_reading_t reading, uint8_t centiseconds) { + accelerometer_data_acquisition_record_t record; + record.data.x.record_type = ACCELEROMETER_DATA_ACQUISITION_DATA; + record.data.y.lpmode = ACCELEROMETER_LPMODE; + record.data.z.filter = ACCELEROMETER_FILTER; + record.data.x.accel = (reading.x >> 2) + 8192; + record.data.y.accel = (reading.y >> 2) + 8192; + record.data.z.accel = (reading.z >> 2) + 8192; + record.data.counter = 100 * (SECONDS_TO_RECORD - state->reading_ticks + 1) + centiseconds; + printf("logged data point for %d\n", record.data.counter); + state->records[state->pos++] = record; + if (state->pos >= 32) { + write_page(state); + } +} + +static void start_reading(accelerometer_data_acquisition_state_t *state, movement_settings_t *settings) { + printf("Start reading\n"); + watch_enable_i2c(); + lis2dw_begin(); + lis2dw_set_data_rate(LIS2DW_DATA_RATE_25_HZ); + lis2dw_set_range(ACCELEROMETER_RANGE); + lis2dw_set_low_power_mode(ACCELEROMETER_LPMODE); + lis2dw_set_bandwidth_filtering(ACCELEROMETER_FILTER); + if (ACCELEROMETER_LOW_NOISE) lis2dw_set_low_noise_mode(true); + lis2dw_enable_fifo(); + + accelerometer_data_acquisition_record_t record; + watch_date_time date_time = watch_rtc_get_date_time(); + state->starting_timestamp = watch_utility_date_time_to_unix_time(date_time, movement_timezone_offsets[settings->bit.time_zone] * 60); + record.header.info.record_type = ACCELEROMETER_DATA_ACQUISITION_HEADER; + record.header.info.range = ACCELEROMETER_RANGE; + record.header.info.temperature = lis2dw_get_temperature(); + record.header.char1 = activity_types[state->activity_type_index][0]; + record.header.char2 = activity_types[state->activity_type_index][1]; + record.header.timestamp = state->starting_timestamp; + + state->records[state->pos++] = record; + lis2dw_fifo_t fifo; + lis2dw_read_fifo(&fifo); // dump the fifo, this starts a fresh round of data in continue_reading +} + +static void continue_reading(accelerometer_data_acquisition_state_t *state) { + printf("Continue reading\n"); + lis2dw_fifo_t fifo; + lis2dw_read_fifo(&fifo); + + fifo.count = min(fifo.count, 25); // hacky, but we need a consistent data rate; if we got a 26th data point, chuck it. + uint8_t offset = 4 * (25 - fifo.count); // also hacky: we're sometimes short at the start. align to beginning of next second. + // TODO: use the threshold interrupt for this, will mean we get consistent 25 Hz data as the accelerometer sees it. + + for(int i = 0; i < fifo.count; i++) { + log_data_point(state, fifo.readings[i], i * 4 + offset); + } +} + +static void finish_reading(accelerometer_data_acquisition_state_t *state) { + printf("Finish reading\n"); + if (state->pos != 0) { + write_page(state); + } + lis2dw_set_data_rate(LIS2DW_DATA_RATE_POWERDOWN); + watch_disable_i2c(); + + state->repeat_ticks = state->repeat_interval; +} diff --git a/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h new file mode 100644 index 00000000..9cea8095 --- /dev/null +++ b/movement/watch_faces/sensor/accelerometer_data_acquisition_face.h @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2022 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ACCELEROMETER_DATA_ACQUISITION_FACE_H_ +#define ACCELEROMETER_DATA_ACQUISITION_FACE_H_ + +#include "movement.h" + +#define ACCELEROMETER_DATA_ACQUISITION_INVALID ((uint64_t)(0b11)) // all bits are 1 when the flash is erased +#define ACCELEROMETER_DATA_ACQUISITION_HEADER ((uint64_t)(0b10)) +#define ACCELEROMETER_DATA_ACQUISITION_DATA ((uint64_t)(0b01)) +#define ACCELEROMETER_DATA_ACQUISITION_DELETED ((uint64_t)(0b00)) // You can always write a 0 to any 1 bit + +typedef union { + struct { + struct { + uint16_t record_type : 2; // see above, helps us identify record types when reading back + uint16_t range : 2; // accelerometer range (see lis2dw_range_t) + uint16_t temperature : 12; // raw value from the temperature sensor + } info; + uint8_t char1 : 8; // First character of the activity type + uint8_t char2 : 8; // Second character of the activity type + uint32_t timestamp : 32; // UNIX timestamp for the measurement + } header; + struct { + struct { + uint16_t record_type : 2; // duplicate; this is the same field as info above + uint16_t accel : 14; // X acceleration value, raw, offset by 8192 + } x; + struct { + uint16_t lpmode : 2; // low power mode (see lis2dw_low_power_mode_t) + uint16_t accel : 14; // Y acceleration value, raw, offset by 8192 + } y; + struct { + uint16_t filter : 2; // bandwidth filtering selection (see lis2dw_bandwidth_filtering_mode_t) + uint16_t accel : 14; // Z acceleration value, raw, offset by 8192 + } z; + uint32_t counter : 16; // number of centiseconds since timestamp in header + } data; + uint64_t value; +} accelerometer_data_acquisition_record_t; + +typedef enum { + ACCELEROMETER_DATA_ACQUISITION_MODE_IDLE, + ACCELEROMETER_DATA_ACQUISITION_MODE_COUNTDOWN, + ACCELEROMETER_DATA_ACQUISITION_MODE_SENSING, + ACCELEROMETER_DATA_ACQUISITION_MODE_SETTINGS, +} accelerometer_data_acquisition_mode_t; + +typedef enum { + ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_SOUND, + ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_DELAY, + ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_REPEAT, + // ACCELEROMETER_DATA_ACQUISITION_SETTINGS_PAGE_NAME, +} accelerometer_data_acquisition_settings_page_t; + +typedef struct { + // mode + accelerometer_data_acquisition_mode_t mode; + accelerometer_data_acquisition_settings_page_t settings_page; + // current settings + uint8_t activity_type_index; // active activity type + bool beep_with_countdown; // should we beep at the countdown + uint8_t countdown_length; // how many seconds to count down + uint16_t repeat_interval; // how many seconds to wait for a repeat + // info about the flash chip + int16_t next_available_page; + // transient properties + uint8_t countdown_ticks; + uint8_t repeat_ticks; + uint8_t reading_ticks; + uint32_t starting_timestamp; + accelerometer_data_acquisition_record_t records[32]; + uint16_t pos; +} accelerometer_data_acquisition_state_t; + +void accelerometer_data_acquisition_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void accelerometer_data_acquisition_face_activate(movement_settings_t *settings, void *context); +bool accelerometer_data_acquisition_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void accelerometer_data_acquisition_face_resign(movement_settings_t *settings, void *context); + +#define accelerometer_data_acquisition_face ((const watch_face_t){ \ + accelerometer_data_acquisition_face_setup, \ + accelerometer_data_acquisition_face_activate, \ + accelerometer_data_acquisition_face_loop, \ + accelerometer_data_acquisition_face_resign, \ + NULL, \ +}) + +#endif // ACCELEROMETER_DATA_ACQUISITION_FACE_H_ diff --git a/utils/motion_express_utilities/csv2gnuplot.sh b/utils/motion_express_utilities/csv2gnuplot.sh new file mode 100755 index 00000000..5e2901e8 --- /dev/null +++ b/utils/motion_express_utilities/csv2gnuplot.sh @@ -0,0 +1,379 @@ +#!/bin/bash +# +# This transforms a CSV file into a gnuplot file. +# use option '-h' to display a help screen for all options. +# +# FracPete + +# the usage of this script +function usage() +{ + echo + echo "usage: ${0##*/} [-i <file>] [-o <file>] [-g <file>] [-G <file>]" + echo " [-O <file>] [-d <delim>] [-t] [-x] [-a] [-l] [-T]" + echo " [-W <width> -H <height>]] [-F <x11|png|ps>]" + echo " [-b <files>] [-e]" + echo " [-h]" + echo + echo "Transforms a given CSV file into a gnuplot input file. It can also" + echo "produce a gnuplot script for plotting the data, as well as batch" + echo "processing of several files with automatic output generation." + echo + echo " -h this help" + echo " -i <file>" + echo " the CSV file to use as input" + echo " -o <file>" + echo " the gnuplot output file, output to stdout if not provided" + echo " -g <file>" + echo " generates a gnuplot script with this name to display the data" + echo " it assumes that the first column is the index for the x-axis." + echo " In combination with '-b' this parameter is only used to indicate" + echo " that a script is wanted, the filename itself is ignored." + echo " -G <file>" + echo " a file containing gnuplot options, comments etc. to be added " + echo " before the plots" + echo " -O <file>" + echo " generates a script that outputs the plot in the format specified" + echo " with '-F' in a file with the given name, instead of displaying " + echo " it in a window" + echo " -d <delim>" + echo " the delimiter that separates the columns, default: $DELIMITER" + echo " -t transposes the matrix first" + echo " -x adds a column for the x-axis (numbers starting from 1)" + echo " -a generates the average of the columns" + echo " -l adds 'with lines' to the gnuplot script" + echo " -T adds a number as title to the gnuplot script" + echo " -F <x11|png|ps>" + echo " the format of the output, default: $FORMAT" + echo " -W <width>" + echo " the width of the output (if '-F png'), default: $WIDTH" + echo " -H <height>" + echo " the height of the output (if '-F png'), default: $HEIGHT" + echo " -b <files>" + echo " processes the given files in batch mode, i.e. '-i' and '-o' are" + echo " not necessary. the files get new extensions automatically." + echo " Note: use \" if you're using wildcards like '*'" + echo " -e generates the desired output files directly, i.e. in creates a" + echo " temp. gnuplot file and runs this (in combination with '-b'," + echo " otherwise '-g' must be given). " + echo " Works only if format is ps or png ('-F')." + echo +} + +# variables +INPUT="" +OUTPUT="" +OUTPUT_PLOT="" +GNUPLOT="" +GNUPLOT_OPTIONS="" +HAS_OUTPUT="no" +HAS_GNUPLOT="no" +DELIMITER="," +TRANSPOSE="no" +XAXIS="no" +AVERAGE="no" +LINES="no" +TITLE="no" +FORMAT="x11" +WIDTH="800" +HEIGHT="600" +BATCH_FILES="" +BATCH_OPTIONS="" +EXECUTE="no" + +# interprete parameters +while getopts ":hi:o:g:d:txalTF:W:H:O:b:eG:" flag +do + case $flag in + i) INPUT=$OPTARG + ;; + o) OUTPUT=$OPTARG + HAS_OUTPUT="yes" + ;; + g) GNUPLOT=$OPTARG + HAS_GNUPLOT="yes" + ;; + G) GNUPLOT_OPTIONS=$OPTARG + BATCH_OPTIONS="$BATCH_OPTIONS -$flag $OPTARG" + ;; + d) DELIMITER=$OPTARG + ;; + t) TRANSPOSE="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + x) XAXIS="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + a) AVERAGE="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + l) LINES="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + T) TITLE="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + O) OUTPUT_PLOT=$OPTARG + ;; + W) WIDTH=$OPTARG + BATCH_OPTIONS="$BATCH_OPTIONS -$flag $OPTARG" + ;; + H) HEIGHT=$OPTARG + BATCH_OPTIONS="$BATCH_OPTIONS -$flag $OPTARG" + ;; + F) FORMAT=$OPTARG + BATCH_OPTIONS="$BATCH_OPTIONS -$flag $OPTARG" + ;; + b) BATCH_FILES=$OPTARG + ;; + e) EXECUTE="yes" + BATCH_OPTIONS="$BATCH_OPTIONS -$flag" + ;; + h) usage + exit 0 + ;; + *) echo + echo "Unknown option: '-$OPTARG'" + echo + usage + exit 1 + ;; + esac +done + +# valid combinations of parameters? +if [ ! "$BATCH_FILES" = "" ] && [ "$EXECUTE" = "yes" ] && [ "$FORMAT" = "x11" ] +then + echo + echo "ERROR: a format other than '$FORMAT' must be specified if '-b' and" + echo " '-e' are specified, e.g. 'ps'." + echo + usage + exit 2 +fi + +# batch-mode? +if [ ! "$BATCH_FILES" = "" ] +then + for i in $BATCH_FILES + do + echo "$i..." + + # build options + OPTIONS=$BATCH_OPTIONS + OPTIONS="$OPTIONS -i $i" + OPTIONS="$OPTIONS -o $i.dat" + if [ "$HAS_GNUPLOT" = "yes" ] + then + OPTIONS="$OPTIONS -g $i.scr" + fi + if [ "$FORMAT" = "png" ] + then + OPTIONS="$OPTIONS -O $i.png" + fi + if [ "$FORMAT" = "ps" ] + then + OPTIONS="$OPTIONS -O $i.ps" + fi + + # run script + $0 $OPTIONS + done + + exit 0 +fi + +# test files +if [ ! "$INPUT" = "" ] && [ ! -f "$INPUT" ] +then + INPUT="" +fi +if [ ! "$GNUPLOT_OPTIONS" = "" ] && [ ! -f "$GNUPLOT_OPTIONS" ] +then + echo "Warning: '$GNUPLOT_OPTIONS' not found - ignored!" + GNUPLOT_OPTIONS="" +fi + +if [ "$HAS_OUTPUT" = "no" ] +then + OUTPUT=$INPUT".tmp" +fi + +# everything provided? +if [ "$INPUT" = "" ] || [ "$DELIMITER" = "" ] +then + echo + echo "ERROR: not all parameters provided or incorrect!" + echo + usage + exit 1 +fi + +if [ "$EXECUTE" = "yes" ] && [ "$HAS_GNUPLOT" = "no" ] +then + echo + echo "ERROR: '-g' must be specified with option '-e'!" + echo + usage + exit 3 +fi + +if [ "$OUTPUT_PLOT" = "" ] && [ ! "$FORMAT" = "x11" ] +then + echo "Warning: output file for format '$FORMAT' not specified, falling back to 'x11'" + FORMAT="x11" +fi + +# some variables +TMPFILE=$OUTPUT".tmp" + +# init +cp $INPUT $OUTPUT + +# change modifier into " " +if [ ! "$DELIMITER" = " " ] +then + cat $OUTPUT | sed s/$DELIMITER/" "/g > $TMPFILE + cp $TMPFILE $OUTPUT +fi + +# transpose matrix? +if [ "$TRANSPOSE" = "yes" ] +then + cat $OUTPUT | exec awk ' + NR == 1 { + n = NF + for (i = 1; i <= NF; i++) + row[i] = $i + next + } + { + if (NF > n) + n = NF + for (i = 1; i <= NF; i++) + row[i] = row[i] " " $i + } + END { + for (i = 1; i <= n; i++) + print row[i] + }' > $TMPFILE + cp $TMPFILE $OUTPUT +fi + +# average columns? +if [ "$AVERAGE" = "yes" ] +then + COLCOUNT=`head -n1 $OUTPUT | wc -w | sed s/" "*//g` + ROWCOUNT=`cat $OUTPUT | wc -l | sed s/" "*//g` + rm -f $TMPFILE + + for ((i = 1; i <= $COLCOUNT; i++)) + do + COL=`cat $OUTPUT | cut -f$i -d" "` + + # average + TMP="("`echo $COL | sed s/" "/+/g`")/$ROWCOUNT" + if [ $i -gt 1 ] + then + echo -n " " >> $TMPFILE + fi + TMP=`echo "scale=4; $TMP" | bc -l` + echo -n $TMP >> $TMPFILE + + # stddev + echo -n " " >> $TMPFILE + TMP="sqrt(($ROWCOUNT * ("`echo $COL | sed s/" "/"^2+"/g | sed s/$/"^2"/g`") - ("`echo $COL | sed s/" "/"+"/g`")^2) / ($ROWCOUNT * ($ROWCOUNT - 1)))" + TMP=`echo "scale=4; $TMP" | bc -l` + echo -n $TMP >> $TMPFILE + done + echo >> $TMPFILE + cp $TMPFILE $OUTPUT +fi + +# add x-axis? +if [ "$XAXIS" = "yes" ] +then + cat $OUTPUT | grep -n "." | sed s/":"/" "/g > $TMPFILE + cp $TMPFILE $OUTPUT +fi + +# gnuplot script? +if [ "$HAS_GNUPLOT" = "yes" ] +then + # data columns + COUNT=`head -n1 $OUTPUT | wc -w | sed s/" "*//g` + + # build output/format statement + TERM="set terminal X11" + OUT="set output" + if [ "$FORMAT" = "png" ] + then + TERM="set terminal png size $WIDTH,$HEIGHT" + OUT="set output \"$OUTPUT_PLOT\"" + fi + if [ "$FORMAT" = "ps" ] + then + TERM="set terminal postscript" + OUT="set output \"$OUTPUT_PLOT\"" + fi + + # build "with" statement + TMP="" + WITH="" + if [ "$LINES" = "yes" ] + then + TMP=$TMP" lines" + fi + if [ "$TITLE" = "yes" ] + then + TMP=$TMP" title #" + fi + if [ ! "$TMP" = "" ] + then + WITH=" with"$TMP + fi + + # init + echo "# gnuplot script for '$OUTPUT'" > $GNUPLOT + if [ ! "$GNUPLOT_OPTIONS" = "" ] + then + cat $GNUPLOT_OPTIONS >> $GNUPLOT + fi + + # the plots + echo "set title \"$INPUT\"" >> $GNUPLOT + echo "plot \"$OUTPUT\" using 1:2 `echo $WITH | sed s/"#"/"\'1\'"/g`" >> $GNUPLOT + for ((i = 2; i < $COUNT; i++)) + do + echo "replot \"$OUTPUT\" using 1:$((i+1)) `echo $WITH | sed s/"#"/"\'$i\'"/g`" >> $GNUPLOT + done + echo >> $GNUPLOT + + # only pause if displayed in window + if [ "$FORMAT" = "x11" ] + then + echo "pause -1" >> $GNUPLOT + else + echo "$TERM" >> $GNUPLOT + echo "$OUT" >> $GNUPLOT + echo "replot" >> $GNUPLOT + fi + + # run gnuplot + if [ "$EXECUTE" = "yes" ] + then + if [ "$FORMAT" = "x11" ] + then + echo "Press <Return> to close window..." + fi + gnuplot "$GNUPLOT" + fi +fi + +# clean up +rm -f $TMPFILE +if [ "$HAS_OUTPUT" = "no" ] +then + cat $OUTPUT + rm -f $OUTPUT +fi
\ No newline at end of file diff --git a/utils/motion_express_utilities/plot.options b/utils/motion_express_utilities/plot.options new file mode 100644 index 00000000..b455b77c --- /dev/null +++ b/utils/motion_express_utilities/plot.options @@ -0,0 +1,2 @@ +set key autotitle columnhead +set yrange [-40:40] diff --git a/utils/motion_express_utilities/process_motion_dump.py b/utils/motion_express_utilities/process_motion_dump.py new file mode 100644 index 00000000..fc246f8e --- /dev/null +++ b/utils/motion_express_utilities/process_motion_dump.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +import sys +from pathlib import Path + +if not sys.stdin.isatty(): + input_stream = sys.stdin + +else: + try: + input_filename = sys.argv[1] + except IndexError: + message = 'need filename as first argument if stdin is not full' + raise IndexError(message) + else: + input_stream = open(input_filename, 'r') + +Path("output/plots").mkdir(parents=True, exist_ok=True) + +s = open(f'output/makeplots.sh', 'w') +f = None +num_events = 0 +num_records = 0 + +for line in input_stream: + if not len(line): + continue + if line.strip() == '=== END ===': + if f is not None: + f.close() + elif line.upper().strip().endswith('.CSV'): + num_events += 1 + components = line.strip().split('.')[:-1] + if components[0] == 'TE': + components[0] = 'testing' + elif components[0] == 'ID': + components[0] = 'idle' + elif components[0] == 'OF': + components[0] = 'off_wrist' + elif components[0] == 'SL': + components[0] = 'sleeping' + elif components[0] == 'WH': + components[0] = 'washing_hands' + elif components[0] == 'WA': + components[0] = 'walking' + elif components[0] == 'WB': + components[0] = 'walking_with_beverage' + elif components[0] == 'JO': + components[0] = 'jogging' + elif components[0] == 'RU': + components[0] = 'running' + elif components[0] == 'BI': + components[0] = 'biking' + elif components[0] == 'HI': + components[0] = 'hiking' + elif components[0] == 'EL': + components[0] = 'elliptical' + elif components[0] == 'SU': + components[0] = 'stairs_up' + elif components[0] == 'SD': + components[0] = 'stairs_down' + elif components[0] == 'WL': + components[0] = 'weight_lifting' + name = '.'.join(components).lower().replace('_', '-') + s.write(f'../csv2gnuplot.sh -i "{name}.csv" -O "./plots/{name}.png" -g "{name}.gnuplot" -F png -W 1200 -H 675 -e -l -G ../plot.options && rm "{name}.gnuplot"\n') + if f is not None: + f.close() + f = open(f'output/{name}.csv', 'w') + else: + num_records += 1 + f.write(line) + +s.close() + +print(f"Processed {num_records} records in {num_events} events!") +print("To generate plots: cd output && bash makeplots.sh")
\ No newline at end of file diff --git a/watch-library/shared/config/hpl_sercom_config.h b/watch-library/shared/config/hpl_sercom_config.h index 6df4b08e..24525fbe 100644 --- a/watch-library/shared/config/hpl_sercom_config.h +++ b/watch-library/shared/config/hpl_sercom_config.h @@ -172,7 +172,7 @@ // <i> The SPI data transfer rate // <id> spi_master_baud_rate #ifndef CONF_SERCOM_3_SPI_BAUD -#define CONF_SERCOM_3_SPI_BAUD 50000 +#define CONF_SERCOM_3_SPI_BAUD 1000000 #endif // </h> diff --git a/watch-library/shared/driver/lis2dw.c b/watch-library/shared/driver/lis2dw.c index 4d60fcff..b90acc7e 100644 --- a/watch-library/shared/driver/lis2dw.c +++ b/watch-library/shared/driver/lis2dw.c @@ -31,12 +31,18 @@ bool lis2dw_begin(void) { } watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL2, LIS2DW_CTRL2_VAL_BOOT); watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL2, LIS2DW_CTRL2_VAL_SOFT_RESET); - // Start at lowest possible data rate and lowest possible power mode - watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1, LIS2DW_CTRL1_VAL_ODR_LOWEST | LIS2DW_CTRL1_VAL_MODE_LOW_POWER | LIS2DW_CTRL1_VAL_LPMODE_1); // Enable block data update (output registers not updated until MSB and LSB have been read) and address autoincrement watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL2, LIS2DW_CTRL2_VAL_BDU | LIS2DW_CTRL2_VAL_IF_ADD_INC); - // Set range to ±2G - watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6, LIS2DW_CTRL6_VAL_RANGE_2G); + + // Parameters at startup: + // * Data rate 0 (powered down) + // * Low power mode enabled + // * LP mode 1 (12-bit) + // * Bandwidth filtering ODR/2 + // * Low pass filter path + // * ±2g range + // * Low noise mode off + // * FIFO disabled return true; } @@ -91,6 +97,10 @@ lis2dw_reading_t lis2dw_get_raw_reading(void) { return retval; } +uint16_t lis2dw_get_temperature(void) { + return watch_i2c_read16(LIS2DW_ADDRESS, LIS2DW_REG_OUT_TEMP_L); +} + void lis2dw_set_range(lis2dw_range_t range) { uint8_t val = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6) & ~(LIS2DW_RANGE_16_G << 4); uint8_t bits = range << 4; @@ -115,6 +125,41 @@ lis2dw_data_rate_t lis2dw_get_data_rate(void) { return watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1) >> 4; } +void lis2dw_set_filter_type(lis2dw_filter_t bwfilter) { + uint8_t val = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6) & ~(LIS2DW_CTRL6_VAL_FDS_HIGH); + uint8_t bits = bwfilter << 3; + watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6, val | bits); +} + +lis2dw_filter_t lis2dw_get_filter_type(void) { + uint8_t retval = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6) & (LIS2DW_CTRL6_VAL_FDS_HIGH); + retval >>= 3; + return (lis2dw_filter_t)retval; +} + +void lis2dw_set_bandwidth_filtering(lis2dw_bandwidth_filtering_mode_t bwfilter) { + uint8_t val = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6) & ~(LIS2DW_CTRL6_VAL_BANDWIDTH_DIV20); + uint8_t bits = bwfilter << 6; + watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6, val | bits); +} + +lis2dw_bandwidth_filtering_mode_t lis2dw_get_bandwidth_filtering(void) { + uint8_t retval = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL6) & (LIS2DW_CTRL6_VAL_BANDWIDTH_DIV20); + retval >>= 6; + return (lis2dw_bandwidth_filtering_mode_t)retval; +} + +void lis2dw_set_mode(lis2dw_mode_t mode) { + uint8_t val = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1) & ~(0b1100); + uint8_t bits = (mode << 2) & 0b1100; + + watch_i2c_write8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1, val | bits); +} + +lis2dw_mode_t lis2dw_get_mode(void) { + return (lis2dw_mode_t)(watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1) & 0b1100) >> 2; +} + void lis2dw_set_low_power_mode(lis2dw_low_power_mode_t mode) { uint8_t val = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_CTRL1) & ~(0b11); uint8_t bits = mode & 0b11; @@ -148,12 +193,10 @@ inline void lis2dw_enable_fifo(void) { bool lis2dw_read_fifo(lis2dw_fifo_t *fifo_data) { uint8_t temp = watch_i2c_read8(LIS2DW_ADDRESS, LIS2DW_REG_FIFO_SAMPLE); bool overrun = !!(temp & LIS2DW_FIFO_SAMPLE_OVERRUN); - uint8_t buffer[6]; fifo_data->count = temp & LIS2DW_FIFO_SAMPLE_COUNT; for(int i = 0; i < fifo_data->count; i++) { - watch_i2c_receive(LIS2DW_ADDRESS, (uint8_t *)&buffer, 6); fifo_data->readings[i] = lis2dw_get_raw_reading(); } diff --git a/watch-library/shared/driver/lis2dw.h b/watch-library/shared/driver/lis2dw.h index f9373e04..c791c461 100644 --- a/watch-library/shared/driver/lis2dw.h +++ b/watch-library/shared/driver/lis2dw.h @@ -88,6 +88,11 @@ typedef enum { } lis2dw_bandwidth_filtering_mode_t; typedef enum { + LIS2DW_FILTER_LOW_PASS = 0, + LIS2DW_FILTER_HIGH_PASS = 1, +} lis2dw_filter_t; + +typedef enum { LIS2DW_RANGE_16_G = 0b11, // +/- 16g LIS2DW_RANGE_8_G = 0b10, // +/- 8g LIS2DW_RANGE_4_G = 0b01, // +/- 4g @@ -185,7 +190,8 @@ typedef enum { #define LIS2DW_CTRL6_VAL_RANGE_4G (LIS2DW_RANGE_4_G << 4) #define LIS2DW_CTRL6_VAL_RANGE_8G (LIS2DW_RANGE_8_G << 4) #define LIS2DW_CTRL6_VAL_RANGE_16G (LIS2DW_RANGE_16_G << 4) -#define LIS2DW_CTRL6_VAL_FDS 0b00001000 +#define LIS2DW_CTRL6_VAL_FDS_LOW (LIS2DW_FILTER_LOW_PASS << 3) +#define LIS2DW_CTRL6_VAL_FDS_HIGH (LIS2DW_FILTER_HIGH_PASS << 3) #define LIS2DW_CTRL6_VAL_LOW_NOISE 0b00000100 #define LIS2DW_REG_OUT_TEMP 0x26 @@ -291,6 +297,8 @@ lis2dw_reading_t lis2dw_get_raw_reading(void); lis2dw_acceleration_measurement_t lis2dw_get_acceleration_measurement(lis2dw_reading_t *out_reading); +uint16_t lis2dw_get_temperature(void); + void lis2dw_set_range(lis2dw_range_t range); lis2dw_range_t lis2dw_get_range(void); @@ -299,6 +307,18 @@ void lis2dw_set_data_rate(lis2dw_data_rate_t dataRate); lis2dw_data_rate_t lis2dw_get_data_rate(void); +void lis2dw_set_filter_type(lis2dw_filter_t filter); + +lis2dw_filter_t lis2dw_get_filter_type(void); + +void lis2dw_set_bandwidth_filtering(lis2dw_bandwidth_filtering_mode_t bwfilter); + +lis2dw_bandwidth_filtering_mode_t lis2dw_get_bandwidth_filtering(void); + +void lis2dw_set_mode(lis2dw_mode_t mode); + +lis2dw_mode_t lis2dw_get_mode(void); + void lis2dw_set_low_power_mode(lis2dw_low_power_mode_t mode); lis2dw_low_power_mode_t lis2dw_get_low_power_mode(void); diff --git a/watch-library/shared/driver/spiflash.h b/watch-library/shared/driver/spiflash.h index 0d8641a7..9b1d00f5 100644 --- a/watch-library/shared/driver/spiflash.h +++ b/watch-library/shared/driver/spiflash.h @@ -33,6 +33,8 @@ #define CMD_FAST_READ_DATA 0x0B #define CMD_SECTOR_ERASE 0x20 // #define CMD_SECTOR_ERASE CMD_READ_JEDEC_ID +#define CMD_CHIP_ERASE 0xC7 +// #define CMD_CHIP_ERASE CMD_READ_JEDEC_ID #define CMD_DISABLE_WRITE 0x04 #define CMD_ENABLE_WRITE 0x06 #define CMD_PAGE_PROGRAM 0x02 |