diff --git a/app/other/airbraker/quaternions b/app/other/airbraker/quaternions new file mode 160000 index 000000000..a5ef2e58b --- /dev/null +++ b/app/other/airbraker/quaternions @@ -0,0 +1 @@ +Subproject commit a5ef2e58b05b9b314aa21ff5b2791cd86bf241a7 diff --git a/app/other/solids_test/CMakeLists.txt b/app/other/solids_test/CMakeLists.txt new file mode 100644 index 000000000..a3208d3f3 --- /dev/null +++ b/app/other/solids_test/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(grim_reefer LANGUAGES C) + +FILE(GLOB sources src/*.c) +target_sources(app PRIVATE ${sources}) +target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/app/other/solids_test/Kconfig b/app/other/solids_test/Kconfig new file mode 100644 index 000000000..0ffe17553 --- /dev/null +++ b/app/other/solids_test/Kconfig @@ -0,0 +1 @@ +source "Kconfig.zephyr" \ No newline at end of file diff --git a/app/other/solids_test/boards/grim_reefer.overlay b/app/other/solids_test/boards/grim_reefer.overlay new file mode 100644 index 000000000..773d4bc55 --- /dev/null +++ b/app/other/solids_test/boards/grim_reefer.overlay @@ -0,0 +1,60 @@ +/delete-node/ &adc; + +/ { + zephyr,user { + io-channels = <&adc 0>; + }; + + aliases { + adc0 = &adc; + buzzer = &buzzer; + }; + + leds: leds { + button: button{ // TX + gpios = <&gpioa 0 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; + label = "Button"; + }; + key_switch: key_switch{ // RX + gpios = <&gpioa 1 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>; + label = "Key Switch"; + }; + }; +}; + +&uart4 { + status = "disabled"; +}; + +&spi2 { + status = "okay"; + adc: mcp3561@0 { // new and cool driver + #io-channel-cells = <1>; + #address-cells = <1>; + #size-cells = <0>; + compatible = "microchip,mcp356xr"; + status = "okay"; + reg = <0>; + address = <1>; + analog-clock-prescaler = <0>; + boost-current-bias = <0>; + spi-max-frequency = ; + irq-gpios = <&gpiob 14 GPIO_ACTIVE_LOW>; + use-internal-clock; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_32"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + + zephyr,resolution = <24>; + + zephyr,differential; + zephyr,input-positive = <0x1>; + zephyr,input-negative = <0x0>; + + zephyr,oversampling = <7>; + }; + }; +}; \ No newline at end of file diff --git a/app/other/solids_test/include/adc_reading.h b/app/other/solids_test/include/adc_reading.h new file mode 100644 index 000000000..0c87aaea8 --- /dev/null +++ b/app/other/solids_test/include/adc_reading.h @@ -0,0 +1,44 @@ +#ifndef ADC_READING_H +#define ADC_READING_H + +#include +#include + +/** + * @brief Struct to hold one ADC sample + */ +struct adc_sample { + uint32_t timestamp; /** Timestamp the sample was recorded in µs */ + int32_t value; /** Value of the sample */ +}; + +/** + * @brief Initialize the ADC device and channel + * @return 0 if successful + */ +int adc_init(); + +/** + * @brief Read one ADC sample + * @param[out] adc_val Pointer to value where sample will be written + */ +void adc_read_one(uint32_t *adc_val); + +/** + * @brief Waits for ADC reading event to start, then reads ADC samples for 10 seconds + */ +void adc_reading_task(void*, void*, void*); + +/** + * @brief Starts test + * @param[in] terminal_test Whether test was triggered by terminal cmd or meep + * If test was triggered by terminal, ematch will NOT light + */ +void adc_start_reading(bool terminal_test); + +/** + * @brief Stops test + */ +void adc_stop_recording(); + +#endif // ADC_READING_H \ No newline at end of file diff --git a/app/other/solids_test/include/button.h b/app/other/solids_test/include/button.h new file mode 100644 index 000000000..0ca6ee46b --- /dev/null +++ b/app/other/solids_test/include/button.h @@ -0,0 +1,19 @@ +#ifndef BUTTON_H +#define BUTTON_H + +#include +#include +#include + +/** + * @brief Configures TX and RX pins as gpio + * @return 0 if successful + */ +int button_switch_init(); + +/** + * @brief Thread to beep continuously if key switch is closed and test is not started + */ +void buzzer_task(void*, void*, void*); + +#endif // BUTTON_H \ No newline at end of file diff --git a/app/other/solids_test/include/buzzer.h b/app/other/solids_test/include/buzzer.h new file mode 100644 index 000000000..720f0f834 --- /dev/null +++ b/app/other/solids_test/include/buzzer.h @@ -0,0 +1,48 @@ +#ifndef BUZZER_H +#define BUZZER_H + +/** + * @brief Configure buzzer and ldo gpio pins + * @return 0 if successful + */ +int buzzer_init(); + +/** + * @brief Start or stop buzzer + * @param[in] which Value to set buzzer + */ +void set_buzz(int which); + +/** + * @brief Set ematch gpio pin + * @param[in] level Value to assign to pin + */ +void set_ematch(int level); + +/** + * @brief Set ldo gpio pin + * @param[in] level Value to assign to pin + */ +void set_ldo(int level); + +/** + * @brief Beep loudly in 1 second intervals for 10 seconds to indicate that flash is full (max tests reached) + */ +void beep_full(); + +/** + * @brief Beep loudly 3 times to indicate test start + */ +void test_start_beep(); + +/** + * @brief Beep loudly 2 times to indicate test end + */ +void test_end_beep(); + +/** + * @brief Beep until test starts + */ +void continuous_beep(); + +#endif // BUZZER_H \ No newline at end of file diff --git a/app/other/solids_test/include/config.h b/app/other/solids_test/include/config.h new file mode 100644 index 000000000..bcc2682d4 --- /dev/null +++ b/app/other/solids_test/include/config.h @@ -0,0 +1,13 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define SAMPLE_RATE 1000 +#define TEST_DURATION 10000 // ms +#define MAX_TESTS 30 +#define CALIB_NAME_MAX_LEN 32 // If solids members need more than 32 characters then they need better naming standards + +#define STORAGE_THREAD_PRIORITY 1 +#define THREAD_START_DELAY 100 +#define SYS_INIT_PRIORITY 1 + +#endif // CONFIG_H \ No newline at end of file diff --git a/app/other/solids_test/include/control.h b/app/other/solids_test/include/control.h new file mode 100644 index 000000000..ac649fa8d --- /dev/null +++ b/app/other/solids_test/include/control.h @@ -0,0 +1,63 @@ +#ifndef CONTROL_H +#define CONTROL_H + +#include +#include + +/** + * @brief Starts flash storage and ADC reading + * @param[in] calib_name Name of calibration to store in flash storage + * @param[in] terminal_test Whether test was triggered by terminal cmd or meep + */ +void control_start_test(char calib_name[], bool terminal_test); + +/** + * @brief Stops flash storage and ADC reading + */ +void control_stop_test(); + +/** + * @brief Reads and prints n number of ADC samples + * @param[in] shell Pointer to shell instance + * @param[in] num Number of samples to read + */ +void control_print_n(const struct shell *shell, int num); + +/** + * @brief Dumps all ADC data from flash storage + * @param[in] shell Pointer to shell instance + */ +void control_dump_data(const struct shell *shell); + +/** + * @brief Dumps one ADC test from flash storage + * @param[in] shell Pointer to shell instance + * @param[in] test_index The test number to dump + */ +void control_dump_one(const struct shell *shell, uint32_t test_index); + +/** + * @brief Clear all flash blocks + * @param[in] shell Pointer to shell instance + */ +void control_erase_all(const struct shell *shell); + +/** + * @brief Set ematch gpio high + * @param[in] shell Pointer to shell instance + */ +void control_set_ematch(const struct shell *shell); + +/** + * @brief Set ematch gpio low + * @param[in] shell Pointer to shell instance + */ +void control_stop_ematch(const struct shell *shell); + +/** + * @brief Get status of test + * @return Whether a test is running or not + */ +bool control_get_test_status(); + +#endif // CONTROL_H \ No newline at end of file diff --git a/app/other/solids_test/include/flash_storage.h b/app/other/solids_test/include/flash_storage.h new file mode 100644 index 000000000..03bcd8574 --- /dev/null +++ b/app/other/solids_test/include/flash_storage.h @@ -0,0 +1,49 @@ +#ifndef FLASH_STORAGE_H +#define FLASH_STORAGE_H + +#include +#include + +/** + * @brief Thread to save test data to flash storage + */ +void flash_storage_thread_entry(void*, void*, void*); + +/** + * @brief Begin flash storage event + * @param[in] calib_name Name of calibration to store. Defaults to "Test [#]" if empty or default string passed in + * @param[in] terminal_test Whether test was triggered by terminal cmd or meep + * @return 0 if successful + */ +int start_flash_storage(char calib_name[], bool terminal_test); + +/** + * @brief End flash storage event + */ +void stop_flash_storage(); + +/** + * @brief Dumps one ADC test from flash storage + * @param[in] shell Pointer to shell instance + * @param[in] test_index The test number to dump + * @return 0 if successful + */ +int flash_dump_one(const struct shell *shell, uint32_t test_index); + +/** + * @brief Dumps all ADC data from flash storage + * @param[in] shell Pointer to shell instance + * @return 0 if successful + */ +int flash_dump_all(const struct shell *shell); + +/** + * @brief Clear all flash blocks + * @param[in] shell Pointer to shell instance + * @return 0 if successful + */ +int flash_erase_all(const struct shell *shell); + +extern struct k_msgq storage_control_queue; + +#endif // FLASH_STORAGE_H \ No newline at end of file diff --git a/app/other/solids_test/prj.conf b/app/other/solids_test/prj.conf new file mode 100644 index 000000000..e76dcffda --- /dev/null +++ b/app/other/solids_test/prj.conf @@ -0,0 +1,47 @@ +CONFIG_SENSOR=y +CONFIG_SENSOR_SHELL=y +CONFIG_SPEED_OPTIMIZATIONS=y +CONFIG_SYS_CLOCK_TICKS_PER_SEC=100000 + +CONFIG_EVENTS=y +CONFIG_BASE64=y + +# Logging +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=n +CONFIG_CRC=y + +# GPIO and bus enables +CONFIG_GPIO=y +CONFIG_I2C=y +CONFIG_SPI=y +CONFIG_SPI_STM32_DMA=y + +# ADC + sensor enables +CONFIG_ADC=y +CONFIG_ADC_LOG_LEVEL_ERR=y # Will spam annoying error at wrn level + +CONFIG_ADC_MCP356XR=y +CONFIG_ADC_MCP356XR_THREAD_STACK_SIZE=2048 +CONFIG_ADC_MCP356XR_THREAD_PRIORITY=21 +CONFIG_INA260=y + +CONFIG_SPI_NOR_SFDP_RUNTIME=y +CONFIG_SPI_NOR_SLEEP_ERASE_MS=10 + +# Flash + file system +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_DISK_ACCESS=y +CONFIG_FILE_SYSTEM=y + +# Data dumping +CONFIG_SHELL=y +CONFIG_FLASH_SHELL=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_UART_CONSOLE=y + +CONFIG_SERIAL=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_CBPRINTF_FP_SUPPORT=y \ No newline at end of file diff --git a/app/other/solids_test/sample.yaml b/app/other/solids_test/sample.yaml new file mode 100644 index 000000000..f65d3b3ff --- /dev/null +++ b/app/other/solids_test/sample.yaml @@ -0,0 +1,9 @@ +sample: + description: + name: solids_board +common: + build_only: true + platform_allow: + - grim_reefer +tests: + solids_test.default: {} diff --git a/app/other/solids_test/src/adc_reading.c b/app/other/solids_test/src/adc_reading.c new file mode 100644 index 000000000..b60f0806c --- /dev/null +++ b/app/other/solids_test/src/adc_reading.c @@ -0,0 +1,157 @@ +#include "adc_reading.h" +#include "buzzer.h" +#include "config.h" +#include "flash_storage.h" +#include "control.h" + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(adc_reader, LOG_LEVEL_INF); + +#define ADC_NODE DT_ALIAS(adc0) + +#if !DT_NODE_HAS_STATUS(ADC_NODE, okay) +#error "No ADC node found" +#endif + +static const struct device *adc_dev = DEVICE_DT_GET(ADC_NODE); + +extern struct k_msgq adc_data_queue; + +#define BEGIN_READING_EVENT 1 +#define STOP_READING_EVENT 2 + +static K_EVENT_DEFINE(adc_control_event); +static K_TIMER_DEFINE(adc_timer, NULL, NULL); + +void adc_reading_task(void*, void*, void*); + +static uint32_t adc_buffer; +static struct adc_sequence sequence = {.buffer = &adc_buffer, .buffer_size = sizeof(adc_buffer), .resolution = 24}; +static bool terminal = true; + +#define DT_SPEC_AND_COMMA(node_id, prop, idx) ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +static const struct adc_dt_spec adc_channels[] = { + DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA)}; + +int adc_init() { + if (!adc_is_ready_dt(&adc_channels[0])) { + LOG_ERR("ADC controller device %s not ready\n", adc_channels[0].dev->name); + return -1; + } + + int err = adc_channel_setup_dt(&adc_channels[0]); + if (err < 0) { + LOG_ERR("Could not setup channel (%d)\n", err); + return -1; + } + + (void) adc_sequence_init_dt(&adc_channels[0], &sequence); + + LOG_INF("ADC initialized"); + return 0; +} + +void adc_read_one(uint32_t *adc_val) { + sequence.buffer = adc_val; + sequence.buffer_size = sizeof(*adc_val); + + int ret = adc_read(adc_dev, &sequence); + if (ret < 0) { + LOG_ERR("ADC read failed (%d)", ret); + return; + } +} + +void adc_reading_task(void*, void*, void*) { + uint32_t adc_val = 0; + struct adc_sample sample = {0}; + while (true) { + LOG_INF("ADC reading task waiting to start..."); + + // Wait for start event + k_event_wait(&adc_control_event, BEGIN_READING_EVENT, true, K_FOREVER); + LOG_INF("ADC reading started"); + set_ldo(1); + // Delay test 2 seconds, beep when test actually starts + k_msleep(2000); + test_start_beep(); + + k_timer_start(&adc_timer, K_USEC(SAMPLE_RATE), K_USEC(SAMPLE_RATE)); + + int x = 0; + uint32_t dropped_samples = 0; + uint64_t total_adc_ticks = 0; + uint64_t total_loop_ticks = 0; + uint32_t num_missed_expires = 0; + uint64_t start_time_ticks = k_uptime_ticks(); + + while (true) { + uint32_t start_loop_ticks = k_uptime_ticks(); + uint32_t events = + k_event_wait(&adc_control_event, BEGIN_READING_EVENT | STOP_READING_EVENT, false, K_NO_WAIT); + if (events & STOP_READING_EVENT) { + break; + } + + uint32_t num_expiries = k_timer_status_get(&adc_timer); + if (num_expiries == 0) { // timer still running + k_timer_status_sync(&adc_timer); + } else { // timer expired + num_missed_expires += num_expiries - 1; + } + + // Only set off ematch if test is triggered by meep + if (!terminal) { + if (x == 500) { + set_ematch(1); + } else if (x == 900) { + set_ematch(0); + } + } + + // Read from ADC + uint32_t start_read = k_uptime_ticks(); + adc_read_one(&adc_val); + total_adc_ticks += k_uptime_ticks() - start_read; + + sample.timestamp = k_ticks_to_us_near32(k_uptime_ticks() - start_time_ticks); + sample.value = adc_val; + + if (k_msgq_put(&adc_data_queue, &sample, K_NO_WAIT) != 0) { + dropped_samples++; + } + x++; + total_loop_ticks += k_uptime_ticks() - start_loop_ticks; + + // Stop test after 10 seconds + if ((k_ticks_to_ms_near32(k_uptime_ticks()) - k_ticks_to_ms_near32(start_time_ticks)) >= TEST_DURATION) { + control_stop_test(); + break; + } + } + + set_ldo(0); + k_timer_stop(&adc_timer); + LOG_INF( + "number of samples: %d, %u missed, %u dropped, read time %llu, ms per = %.2f, loop time: %llu, loop time ticks: %llu", + x, num_missed_expires, dropped_samples, k_ticks_to_ms_near64(total_adc_ticks), + (double) k_ticks_to_ms_near64(total_adc_ticks) / (double) x, k_ticks_to_ms_near64(total_loop_ticks), + total_loop_ticks); + test_end_beep(); + } +} + +void adc_start_reading(bool terminal_test) { + terminal = terminal_test; // Whether test was triggered by terminal command or meep + k_event_set(&adc_control_event, BEGIN_READING_EVENT); +} + +void adc_stop_recording() { k_event_set(&adc_control_event, STOP_READING_EVENT); } \ No newline at end of file diff --git a/app/other/solids_test/src/button.c b/app/other/solids_test/src/button.c new file mode 100644 index 000000000..180faabfd --- /dev/null +++ b/app/other/solids_test/src/button.c @@ -0,0 +1,102 @@ +#include "button.h" +#include "control.h" +#include "buzzer.h" + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(button, LOG_LEVEL_INF); + +// button = tx, key switch = rx +#define BUTTON_NODE DT_NODELABEL(button) +#define SWITCH_NODE DT_NODELABEL(key_switch) +static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE, gpios); +static const struct gpio_dt_spec key_switch = GPIO_DT_SPEC_GET(SWITCH_NODE, gpios); + +void buzzer_task(void*, void*, void*); +void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins); +void key_switch_state(const struct device *dev, struct gpio_callback *cb, uint32_t pins); + +static struct gpio_callback button_cb_data; +static struct gpio_callback switch_cb_data; + +static bool key_switched = false; +static bool buzzing = false; + +int button_switch_init() { + int ret = gpio_pin_configure_dt(&key_switch, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to conf key switch (rx) :("); + return -1; + } + + ret = gpio_pin_configure_dt(&button, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to conf button (tx) :("); + return -1; + } + + if (!gpio_is_ready_dt(&button)) { + LOG_ERR("Error: button device %s is not ready\n", button.port->name); + return -1; + } + + ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("Error %d: failed to configure interrupt on %s pin %d\n", ret, button.port->name, button.pin); + return -1; + } + + ret = gpio_pin_interrupt_configure_dt(&key_switch, GPIO_INT_EDGE_TO_ACTIVE); + if (ret != 0) { + LOG_ERR("Error %d: failed to configure interrupt on %s pin %d\n", ret, key_switch.port->name, key_switch.pin); + return -1; + } + + gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin)); + gpio_add_callback(button.port, &button_cb_data); + + gpio_init_callback(&switch_cb_data, key_switch_state, BIT(key_switch.pin)); + gpio_add_callback(key_switch.port, &switch_cb_data); + + return 0; +} + +void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + LOG_INF("Starting test..."); + control_start_test("", false); +} + +void key_switch_state(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { + int val = gpio_pin_get_dt(&key_switch); + if (val > 0) { + key_switched = true; + set_ematch(1); + LOG_INF("Key switch closed"); + + if (!control_get_test_status()) { + buzzing = true; // scream until test start + } + } else { + key_switched = false; + buzzing = false; + set_buzz(0); + set_ematch(0); + LOG_INF("Key switch open"); + } +} + +void buzzer_task(void*, void*, void*) { + while (true) { + if (buzzing && !control_get_test_status()) { + continuous_beep(); + } else { + set_buzz(0); + k_msleep(50); + } + } +} \ No newline at end of file diff --git a/app/other/solids_test/src/buzzer.c b/app/other/solids_test/src/buzzer.c new file mode 100644 index 000000000..ad16a8627 --- /dev/null +++ b/app/other/solids_test/src/buzzer.c @@ -0,0 +1,89 @@ +#include "buzzer.h" +#include "control.h" + +#include +#include +#include + +LOG_MODULE_REGISTER(buzzer, LOG_LEVEL_INF); + +#define LDO_EN_NODE DT_NODELABEL(ldo_enable) +#define CAM_EN_NODE DT_NODELABEL(cam_enable) + +const struct gpio_dt_spec ldo_enable = GPIO_DT_SPEC_GET(LDO_EN_NODE, gpios); +static const struct gpio_dt_spec buzzer = GPIO_DT_SPEC_GET(DT_ALIAS(buzzer), gpios); +static const struct gpio_dt_spec ematch = GPIO_DT_SPEC_GET(CAM_EN_NODE, gpios); + +static bool test_running = false; + +int buzzer_init() { + int ret = gpio_pin_configure_dt(&buzzer, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to conf buzzer pin :("); + return -1; + } + + ret = gpio_pin_configure_dt(&ldo_enable, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to conf ldo enable pin :("); + return -1; + } + + ret = gpio_pin_configure_dt(&ematch, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to conf ematch pin :("); + return -1; + } + + return 0; +} + +void set_buzz(int which) { + gpio_pin_set_dt(&ldo_enable, which); + gpio_pin_set_dt(&buzzer, which); +} + +void set_ldo(int level) { + gpio_pin_set_dt(&ldo_enable, level); +} + +void set_ematch(int level) { + gpio_pin_set_dt(&ematch, level); +} + +void beep_full() { + for (int i = 0; i < 10; i++) { + set_buzz(1); + k_msleep(1000); + set_buzz(0); + k_msleep(1000); + } +} + +void test_start_beep() { + for (int i = 0; i < 3; i++) { + set_buzz(1); + k_msleep(100); + set_buzz(0); + k_msleep(100); + } +} + +void test_end_beep() { + set_buzz(1); + k_msleep(100); + set_buzz(0); + k_msleep(100); + set_buzz(1); + k_msleep(1000); + set_buzz(0); +} + +void continuous_beep() { + while (!test_running) { + set_buzz(1); + k_msleep(10); + test_running = control_get_test_status(); + } + set_buzz(0); +} \ No newline at end of file diff --git a/app/other/solids_test/src/control.c b/app/other/solids_test/src/control.c new file mode 100644 index 000000000..12bcd1f99 --- /dev/null +++ b/app/other/solids_test/src/control.c @@ -0,0 +1,82 @@ +#include "control.h" +#include "adc_reading.h" +#include "buzzer.h" +#include "config.h" +#include "flash_storage.h" + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(control, LOG_LEVEL_INF); + +static bool test_running = false; +static char curr_name[CALIB_NAME_MAX_LEN]; + +void control_start_test(char calib_name[], bool terminal_test) { + // If string is empty, get calibration name from last named test - name will stay until new one is passed in + // Default name will be set in flash_storage if no name specified in shell_cmd + if (calib_name && calib_name[0] != '\0') { + memcpy(curr_name, calib_name, sizeof(curr_name)); + } + + if (test_running) { + LOG_WRN("Test already running"); + return; + } + + test_running = true; + + start_flash_storage(curr_name, terminal_test); + adc_start_reading(terminal_test); +} + +void control_stop_test() { + if (!test_running) { + LOG_WRN("No test running"); + return; + } + + LOG_INF("Stopping test..."); + adc_stop_recording(); + stop_flash_storage(); + test_running = false; +} + +void control_print_n(const struct shell *shell, int num) { + set_ldo(1); + k_msleep(5000); + uint32_t adc_val = 0; + uint32_t start = k_uptime_ticks(); + + for (int i = 0; i < num; i++) { + adc_read_one(&adc_val); + uint32_t t = k_uptime_ticks() - start; + shell_print(shell, "%u, %d", k_ticks_to_us_near32(t), adc_val); + k_msleep(1); + } + set_ldo(0); +} + +void control_dump_data(const struct shell *shell) { flash_dump_all(shell); } + +void control_dump_one(const struct shell *shell, uint32_t test_index) { flash_dump_one(shell, test_index); } + +void control_erase_all(const struct shell *shell) { flash_erase_all(shell); } + +void control_set_ematch(const struct shell *shell) { + set_ematch(1); + shell_print(shell, "Ematch enabled"); +} + +void control_stop_ematch(const struct shell *shell) { + set_ematch(0); + shell_print(shell, "Ematch disabled"); +} + +bool control_get_test_status() { + return test_running; +} \ No newline at end of file diff --git a/app/other/solids_test/src/flash_storage.c b/app/other/solids_test/src/flash_storage.c new file mode 100644 index 000000000..b399f3c25 --- /dev/null +++ b/app/other/solids_test/src/flash_storage.c @@ -0,0 +1,282 @@ +#include "flash_storage.h" +#include "adc_reading.h" +#include "buzzer.h" +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(flash_storage, LOG_LEVEL_INF); + +#define SPI_FLASH_ADDR 0x00000000 +#define FLASH_METADATA_ADDR SPI_FLASH_ADDR +#define PAGE_SIZE 256 +#define SAMPLE_PER_PAGE (PAGE_SIZE / sizeof(struct adc_sample)) +#define SPI_FLASH_BLOCK_SIZE (64 * 2048) // 128KB +#define FLASH_METADATA_SIZE (4 * 1024) // 4KB +#define SPI_FLASH_START_ADDR (FLASH_METADATA_ADDR + FLASH_METADATA_SIZE) + +enum storage_event { BEGIN_STORAGE, END_STORAGE }; + +K_MSGQ_DEFINE(storage_control_queue, sizeof(enum storage_event), 5, 1); + +K_MSGQ_DEFINE(adc_data_queue, sizeof(struct adc_sample), 1000, alignof(struct adc_sample)); + +static const struct device *flash_dev = DEVICE_DT_GET(DT_ALIAS(storage)); +static uint32_t current_test_number = 0; +static off_t current_write_addr = 0; +static char test_type[] = "terminal"; + +void flash_storage_thread_entry(void*, void*, void*); + +static char calibration[CALIB_NAME_MAX_LEN]; + +// Check if flash block is all 0xFF +static bool flash_block_is_empty(off_t addr) { + uint8_t buf[16]; + if (flash_read(flash_dev, addr, buf, sizeof(buf)) < 0) { + LOG_ERR("Flash read failed"); + return false; + } + + for (int i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0xFF) { + return false; + } + } + return true; +} + +// Get and update current test number +static void load_metadata() { + uint8_t buf[4]; + if (flash_read(flash_dev, FLASH_METADATA_ADDR, buf, sizeof(buf)) < 0) { + LOG_ERR("Failed to read metadata"); + current_test_number = 0; + return; + } + + // If all are erased (0xFF), assume no tests yet + if (buf[0] == 0xFF && buf[1] == 0xFF && buf[2] == 0xFF && buf[3] == 0xFF) { + current_test_number = 0; + } else { + memcpy(¤t_test_number, buf, sizeof(current_test_number)); + } + + LOG_INF("Next test: %d", current_test_number); +} + +static void save_metadata() { + int ret = flash_erase(flash_dev, FLASH_METADATA_ADDR, FLASH_METADATA_SIZE); + + if (ret < 0) { + LOG_ERR("flash_erase(metadata) failed: %d", ret); + return; + } + + ret = flash_write(flash_dev, FLASH_METADATA_ADDR, ¤t_test_number, sizeof(current_test_number)); + + if (ret < 0) { + LOG_ERR("flash_write(metadata) failed: %d", ret); + } + + LOG_INF("Next test: %d", current_test_number); +} + +static off_t get_test_block_addr(uint32_t test_index) { + return (off_t) (SPI_FLASH_START_ADDR + (test_index * SPI_FLASH_BLOCK_SIZE)); +} + +void flash_storage_thread_entry(void*, void*, void*) { + enum storage_event event; + + if (!device_is_ready(flash_dev)) { + LOG_ERR("Flash device not ready"); + return; + } + + load_metadata(); + + while (true) { + // Wait for start command + k_msgq_get(&storage_control_queue, &event, K_FOREVER); + if (event != BEGIN_STORAGE) continue; + + if (current_test_number >= MAX_TESTS) { + LOG_ERR("Maximum number of tests reached!"); + beep_full(); + continue; + } + + off_t test_block_addr = get_test_block_addr(current_test_number); + LOG_INF("Starting test %u at addr 0x%08lx", current_test_number, (long) test_block_addr); + + // Erase block + int ret = flash_erase(flash_dev, test_block_addr, SPI_FLASH_BLOCK_SIZE); + if (ret < 0) { + LOG_ERR("flash_erase(test block) failed: %d", ret); + continue; + } + + current_write_addr = test_block_addr; + + // Save calibration name and test type (terminal or meep) + flash_write(flash_dev, current_write_addr, calibration, CALIB_NAME_MAX_LEN); + current_write_addr += CALIB_NAME_MAX_LEN; + + flash_write(flash_dev, current_write_addr, test_type, sizeof(test_type)); + current_write_addr += sizeof(test_type); + + static struct adc_sample page[SAMPLE_PER_PAGE] = {0}; + size_t i = 0; + size_t pages = 0; + + while (true) { + if (k_msgq_get(&storage_control_queue, &event, K_NO_WAIT) == 0 && event == END_STORAGE) { + LOG_INF("Test %d complete", current_test_number); + current_test_number++; + save_metadata(); + break; + } + struct adc_sample *target = &page[i % SAMPLE_PER_PAGE]; + if (k_msgq_get(&adc_data_queue, target, K_MSEC(50)) == 0) { + i++; + } + if (i % SAMPLE_PER_PAGE == 0 && i != 0) { + int ret = flash_write(flash_dev, current_write_addr, page, PAGE_SIZE); + if (ret < 0) { + LOG_ERR("Flash write failed (%d)", ret); + } else { + current_write_addr += PAGE_SIZE; + pages++; + } + } + } + size_t j = i % SAMPLE_PER_PAGE; + while (j < SAMPLE_PER_PAGE) { + page[j] = (struct adc_sample) {0xFFFFFFFF, 0xFFFFFFFF}; + j++; + } + ret = flash_write(flash_dev, current_write_addr, page, PAGE_SIZE); + if (ret < 0) { + LOG_ERR("Final page flash write failed (%d)", ret); + } else { + current_write_addr += PAGE_SIZE; + pages++; + } + + LOG_INF("Saved %u packets to %u pages", i, pages); + } +} + +int start_flash_storage(char calib_name[], bool terminal_test) { + // Set calibration name + int res1 = strcmp(calib_name, "default"); + int res2 = strcmp(calib_name, ""); + if (res1 == 0 || res2 == 0) { + snprintf(calibration, sizeof(calibration), "Test %u", current_test_number); + } else { + snprintf(calibration, sizeof(calibration), "%s", calib_name); + } + + if (!terminal_test) { + snprintf(test_type, sizeof(test_type), "meep"); + } + + enum storage_event event = BEGIN_STORAGE; + return k_msgq_put(&storage_control_queue, &event, K_FOREVER); +} + +void stop_flash_storage() { + enum storage_event event = END_STORAGE; + k_msgq_put(&storage_control_queue, &event, K_FOREVER); +} + +int flash_dump_one(const struct shell *shell, uint32_t test_index) { + if (test_index >= MAX_TESTS ){ + shell_print(shell, "Pick a valid test [0-%d]", MAX_TESTS - 1); + return -1; + } + struct adc_sample sample; + + if (!device_is_ready(flash_dev)) { + shell_error(shell, "Flash not ready"); + return -1; + } + + off_t block_addr = get_test_block_addr(test_index); + if (flash_block_is_empty(block_addr)) { + shell_print(shell, "Flash block empty"); + return 0; + } + + char calib_name[CALIB_NAME_MAX_LEN]; + char local_test_type[sizeof(test_type)]; + + flash_read(flash_dev, block_addr, calib_name, sizeof(calib_name)); + block_addr += CALIB_NAME_MAX_LEN; + + flash_read(flash_dev, block_addr, local_test_type, sizeof(local_test_type)); + block_addr += sizeof(local_test_type); + + shell_print(shell, "================================\nDumping Test #%d", test_index); + shell_print(shell, "CALIBRATION: %s", calib_name); + shell_print(shell, "Test triggered by %s", local_test_type); + shell_print(shell, "timestamp, value\n================================"); + + for (int i = 0; i < (SPI_FLASH_BLOCK_SIZE / sizeof(sample)); i++) { + if (flash_read(flash_dev, block_addr, &sample, sizeof(sample)) < 0) { + shell_print(shell, "Flash read failed"); + break; + } + + if (sample.value == 0xFFFFFFFF && sample.timestamp == 0xFFFFFFFF) { + shell_print(shell, "Flash block unwritten. Read %d packets", i); + break; + } + + shell_print(shell, "%u,%d", sample.timestamp, sample.value); + block_addr += sizeof(sample); + } + + return 0; +} + +int flash_dump_all(const struct shell *shell) { + if (!device_is_ready(flash_dev)) { + shell_error(shell, "Flash not ready"); + return -1; + } + + for (uint32_t test_index = 0; test_index < MAX_TESTS; test_index++) { + flash_dump_one(shell, test_index); + } + + return 0; +} + +int flash_erase_all(const struct shell *shell) { + for (uint32_t i = 0; i < MAX_TESTS; i++) { + off_t curr_add = get_test_block_addr(i); + int ret = flash_erase(flash_dev, curr_add, SPI_FLASH_BLOCK_SIZE); + if (ret < 0) { + shell_error(shell, "flash_erase failed: %d", ret); + continue; + } else { + shell_print(shell, "Flash block %d erased", i); + } + } + + current_test_number = 0; + save_metadata(); + + return 0; +} \ No newline at end of file diff --git a/app/other/solids_test/src/main.c b/app/other/solids_test/src/main.c new file mode 100644 index 000000000..98eeb3da3 --- /dev/null +++ b/app/other/solids_test/src/main.c @@ -0,0 +1,26 @@ +#include "adc_reading.h" +#include "flash_storage.h" +#include "buzzer.h" +#include "button.h" +#include "config.h" + +#include +#include + +LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); + +// APPLICATION: Executed just before application code (main) +SYS_INIT(adc_init, APPLICATION, SYS_INIT_PRIORITY); +SYS_INIT(buzzer_init, APPLICATION, SYS_INIT_PRIORITY); +SYS_INIT(button_switch_init, APPLICATION, SYS_INIT_PRIORITY); + +K_THREAD_DEFINE(adc_thread, 1024, adc_reading_task, NULL, NULL, NULL, 15, 0, THREAD_START_DELAY); +K_THREAD_DEFINE(buzz_thread, 512, buzzer_task, NULL, NULL, NULL, 10, 0, 0); +K_THREAD_DEFINE(storage_thread, 2048, flash_storage_thread_entry, NULL, NULL, NULL, STORAGE_THREAD_PRIORITY, 0, 1000); + +int main(void) { + LOG_INF("Solids Test Start"); + LOG_INF("Use 'test start [calibration name]' to begin test"); + LOG_INF("Use 'test help' to see all available commands"); + return 0; +} \ No newline at end of file diff --git a/app/other/solids_test/src/shell_cmds.c b/app/other/solids_test/src/shell_cmds.c new file mode 100644 index 000000000..7bab7d850 --- /dev/null +++ b/app/other/solids_test/src/shell_cmds.c @@ -0,0 +1,104 @@ +#include "adc_reading.h" +#include "control.h" +#include "config.h" +#include "flash_storage.h" + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(shell_cmds, LOG_LEVEL_INF); + +static int cmd_test_start(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + char calib_name[CALIB_NAME_MAX_LEN] = "default"; // If no name arg, set to default. Name will be set to "Test [#]" in flash_storage + if (argc >= 2) { + calib_name[0] = '\0'; // clear "default" + for (int i = 1; i < argc; i++) { + snprintf(calib_name + strlen(calib_name), + sizeof(calib_name) - strlen(calib_name), + i == 1 ? "%s" : " %s", argv[i]); + } + } + + shell_print(shell, "Starting test..."); + control_start_test(calib_name, true); + return 0; +} + +static int cmd_test_print_one(const struct shell *shell, size_t argc, char **argv) { + int num = 1; + if (argc == 2) { + num = atoi(argv[1]); + } + shell_print(shell, "Getting %d sample(s)...", num); + control_print_n(shell, num); + return 0; +} + +static int cmd_test_stop(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + shell_print(shell, "Stopping test..."); + control_stop_test(); + return 0; +} + +static int cmd_test_erase(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + shell_print(shell, "Erasing all test data..."); + + control_erase_all(shell); + return 0; +} + +static int cmd_test_dump(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + if (argc == 1) { + shell_print(shell, "Dumping all test data..."); + control_dump_data(shell); + } else if (argc == 2) { + uint32_t test_num = atoi(argv[1]); + shell_print(shell, "Dumping test %u data...", test_num); + control_dump_one(shell, test_num); + } else { + shell_print(shell, "Enter or "); + return -1; + } + + return 0; +} + +static int cmd_test_ematch(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + shell_print(shell, "Setting ematch..."); + control_set_ematch(shell); + return 0; +} + +static int cmd_test_estop(const struct shell *shell, size_t argc, char **argv) { + ARG_UNUSED(argc); + ARG_UNUSED(argv); + shell_print(shell, "Stopping ematch..."); + control_stop_ematch(shell); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_test, SHELL_CMD(start, NULL, "Start test. Arg [calibration name]", cmd_test_start), + SHELL_CMD(stop, NULL, "Stop test preemptively", cmd_test_stop), + SHELL_CMD(dump, NULL, "Dump flash data. Optional arg [test #]", cmd_test_dump), + SHELL_CMD(erase, NULL, "Erase all flash data, prepare for new tests", cmd_test_erase), + SHELL_CMD(read, NULL, "Read one (or more) samples", cmd_test_print_one), + SHELL_CMD(ematch, NULL, "Set ematch high", cmd_test_ematch), + SHELL_CMD(estop, NULL, "Set ematch low", cmd_test_estop), + SHELL_SUBCMD_SET_END); + +SHELL_CMD_REGISTER(test, &sub_test, "Solids Test Board control commands", NULL); \ No newline at end of file