summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/accelerometer-test/app.c27
-rw-r--r--apps/spi-test/app.c207
-rwxr-xr-xmovement/make/Makefile1
-rw-r--r--movement/movement_faces.h1
-rw-r--r--movement/template/watch_face.py4
-rw-r--r--movement/watch_faces/sensor/accelerometer_data_acquisition_face.c480
-rw-r--r--movement/watch_faces/sensor/accelerometer_data_acquisition_face.h111
-rwxr-xr-xutils/motion_express_utilities/csv2gnuplot.sh379
-rw-r--r--utils/motion_express_utilities/plot.options2
-rw-r--r--utils/motion_express_utilities/process_motion_dump.py75
-rw-r--r--watch-library/shared/config/hpl_sercom_config.h2
-rw-r--r--watch-library/shared/driver/lis2dw.c55
-rw-r--r--watch-library/shared/driver/lis2dw.h22
-rw-r--r--watch-library/shared/driver/spiflash.h2
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