From d307189b7561f2e548b77fac6e8da91a2e0d3a6f Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Wed, 9 Apr 2025 16:33:52 -0700 Subject: [PATCH 01/39] draft poke manager --- firmware/inc/poke_manager.h | 60 ++++++++++++++++++++++++ firmware/src/poke_manager.cpp | 87 +++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 firmware/inc/poke_manager.h create mode 100644 firmware/src/poke_manager.cpp diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h new file mode 100644 index 0000000..6261600 --- /dev/null +++ b/firmware/inc/poke_manager.h @@ -0,0 +1,60 @@ +#ifndef POKE_MANAGER_H +#define POKE_MANAGER_H + +#include + + +class PokeManager +{ +public: + + enum state_t + { + RESET, + ODOR_SETUP, + ODOR_DISPENSING_TO_EXHAUST, + ODOR_DELIVERY_TO_FINAL_VALVE, + ODOR_PRECLEAN, + VAC_START, + ODOR_PURGE, + }; + + PokeManager(); + + ~PokeManager(); + + + void update(); + + +/** + * \brief true if a poke was detected. + */ + bool poke_detected(); + + + +private: + +/** + * \brief time we've been in the current state. + */ + inline uint32_t state_duration_us() + {return time_us_32() - state_entry_time_us_;} + + state_t state_; + uint32_t state_entry_time_us_; + size_t poke_count_; + + // Constants + inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e5;// 100'000; + inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; + inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 20e3; // ?? + inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 10e3; + inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; + inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; + + +}; + +#endif // POKE_MANAGER_H diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp new file mode 100644 index 0000000..0f1a3fa --- /dev/null +++ b/firmware/src/poke_manager.cpp @@ -0,0 +1,87 @@ +#include + +PokeManager::PokeManager() +: state_{RESET}, poke_count_{0} +{ + // Nothing else to do! +} + + +PokeManager::~PokeManager() +{ + // TODO: implement this! +} + + + +void PokeManager::update() +{ + // Update inputs. + // TODO: poke detection here. + + + state_t next_state{state_}; // initialize next-state to current state. + + // Handling next-state logic. + switch {state_} + { + case RESET: + next_state_ = ODOR_SETUP; + break; + case ODOR_SETUP: + if (state_duration_us() >= VACUUM_CLOSE_TIME_US) + next_state_ = ODOR_DISPENSING_TO_EXHAUST; + break; + case ODOR_DISPENSING_TO_EXHAUST: + if (poke_detected()) + next_state_ = ODOR_DELIVERY_TO_FINAL_VALVE; + break; + case ODOR_DELIVERY_TO_FINAL_VALVE: + if (state_duration_us() >= ODOR_DELIVERY_TIME_US) + next_state_ = ODOR_PRECLEAN; + break; + case ODOR_PRECLEAN: + if (state_duration_us() >= ODOR_TRANSITION_TIME_US) + next_state_ = VAC_START; + break; + case VAC_START: + if (state_duration_us() >= VAC_SETUP_TIME_US) + next_state_ = ODOR_PURGE; + break; + case ODOR_PURGE: + if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) + next_state_ = ODOR_PURGE; + break; + default: + break; + } + + // Update how long we've been in the new state. + if (state_ != next_state_) + { + state_entry_time_us_ = time_us_32(); + } + + if (next_state_ == RESET) + { + deenergize_all_valves(); + state_entry_time_us_ = time_us_32(); // Force this to happen at reset. + } + if (next_state_ == ODOR_DELIVERY_TO_FINAL_VALVE) + { + ++poke_count_; + } + + + + // Handling state-and/or-input-depenendent output logic. + if (next_state_ == ODOR_PRECLEAN) + { + deenergize_valve(??); + // more stuff here. + } + + + // Update state: + state_ = next_state_; +} From 4495988e7b2c516602a3a6b7a4aced4891980c8e Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Thu, 10 Apr 2025 12:09:42 -0700 Subject: [PATCH 02/39] Draft poke manager tests --- firmware/inc/poke_manager.h | 15 +++--- firmware/src/poke_manager.cpp | 38 +++++++------- firmware/tests/poke_manager/CMakeLists.txt | 60 ++++++++++++++++++++++ firmware/tests/poke_manager/main.cpp | 18 +++++++ 4 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 firmware/tests/poke_manager/CMakeLists.txt create mode 100644 firmware/tests/poke_manager/main.cpp diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 6261600..a64b085 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -2,6 +2,8 @@ #define POKE_MANAGER_H #include +#include // for uart printing +#include // for printf class PokeManager @@ -47,13 +49,12 @@ class PokeManager size_t poke_count_; // Constants - inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e5;// 100'000; - inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; - inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 20e3; // ?? - inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 10e3; - inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; - inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; - + static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e5;// 100'000; + static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; + static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 20e3; // ?? + static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 10e3; + static inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; + static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 20e3; }; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 0f1a3fa..bf648b6 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -12,76 +12,78 @@ PokeManager::~PokeManager() // TODO: implement this! } - +bool PokeManager::poke_detected() +{ + return false; +} void PokeManager::update() { // Update inputs. // TODO: poke detection here. - + printf("Updating\r\n"); state_t next_state{state_}; // initialize next-state to current state. // Handling next-state logic. - switch {state_} + switch (state_) { case RESET: - next_state_ = ODOR_SETUP; + next_state = ODOR_SETUP; break; case ODOR_SETUP: if (state_duration_us() >= VACUUM_CLOSE_TIME_US) - next_state_ = ODOR_DISPENSING_TO_EXHAUST; + next_state = ODOR_DISPENSING_TO_EXHAUST; break; case ODOR_DISPENSING_TO_EXHAUST: if (poke_detected()) - next_state_ = ODOR_DELIVERY_TO_FINAL_VALVE; + next_state = ODOR_DELIVERY_TO_FINAL_VALVE; break; case ODOR_DELIVERY_TO_FINAL_VALVE: if (state_duration_us() >= ODOR_DELIVERY_TIME_US) - next_state_ = ODOR_PRECLEAN; + next_state = ODOR_PRECLEAN; break; case ODOR_PRECLEAN: if (state_duration_us() >= ODOR_TRANSITION_TIME_US) - next_state_ = VAC_START; + next_state = VAC_START; break; case VAC_START: if (state_duration_us() >= VAC_SETUP_TIME_US) - next_state_ = ODOR_PURGE; + next_state = ODOR_PURGE; break; case ODOR_PURGE: if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) - next_state_ = ODOR_PURGE; + next_state = ODOR_PURGE; break; default: break; } // Update how long we've been in the new state. - if (state_ != next_state_) + if (state_ != next_state) { state_entry_time_us_ = time_us_32(); } - if (next_state_ == RESET) + if (next_state == RESET) { - deenergize_all_valves(); + // deenergize_all_valves(); state_entry_time_us_ = time_us_32(); // Force this to happen at reset. } - if (next_state_ == ODOR_DELIVERY_TO_FINAL_VALVE) + if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) { ++poke_count_; } - // Handling state-and/or-input-depenendent output logic. - if (next_state_ == ODOR_PRECLEAN) + if (next_state == ODOR_PRECLEAN) { - deenergize_valve(??); + // deenergize_valve(??); // more stuff here. } // Update state: - state_ = next_state_; + state_ = next_state; } diff --git a/firmware/tests/poke_manager/CMakeLists.txt b/firmware/tests/poke_manager/CMakeLists.txt new file mode 100644 index 0000000..b974f90 --- /dev/null +++ b/firmware/tests/poke_manager/CMakeLists.txt @@ -0,0 +1,60 @@ +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.1.1) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.1.1) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico CACHE STRING "Board type") + +cmake_minimum_required(VERSION 3.13) +find_package(Git REQUIRED) +execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD OUTPUT_VARIABLE COMMIT_ID OUTPUT_STRIP_TRAILING_WHITESPACE) +message(STATUS "Computed Git Hash: ${COMMIT_ID}") +add_definitions(-DGIT_HASH="${COMMIT_ID}") # Usable in source code. + +#add_definitions(-DDEBUG) # Uncomment for debugging +add_definitions(-DUSBD_MANUFACTURER="Allen Institute") +add_definitions(-DUSBD_PRODUCT="test-poke_manager") + +# PICO_SDK_PATH must be defined. +include(${PICO_SDK_PATH}/pico_sdk_init.cmake) + +# Use modern conventions like std::invoke +set(CMAKE_CXX_STANDARD 17) + +project(poke-manager) + +pico_sdk_init() + +add_executable(${PROJECT_NAME} + main.cpp +) + +add_library(poke_manager + ../../src/poke_manager.cpp +) + +include_directories(../../inc) + +target_link_libraries(poke_manager + pico_stdlib) +target_link_libraries(${PROJECT_NAME} + poke_manager pico_stdlib) + +pico_add_extra_outputs(${PROJECT_NAME}) + +message(WARNING "Debug printf() messages from harp core to UART with baud \ + rate 921600.") +pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. +pico_enable_stdio_uart(poke_manager 1) # UART stdio for printf. +# Additional libraries need to have stdio init also. + + diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp new file mode 100644 index 0000000..ba17d58 --- /dev/null +++ b/firmware/tests/poke_manager/main.cpp @@ -0,0 +1,18 @@ +#include +#include +#include +#include // for uart printing +#include // for printf + +PokeManager poke_manager; + +// Core0 main. +int main() +{ + //Debug UART setup + stdio_uart_init_full(uart0, 921600, UART_TX_PIN, -1); // use uart1 tx only. + printf("Hello, from an RP2040!\r\n"); + + while(true) + poke_manager.update(); +} From 3fbc4b0835a2e4b54173aff56384168939c713e5 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 16 Apr 2025 12:22:28 -0700 Subject: [PATCH 03/39] Poke manager test bed --- firmware/inc/poke_manager.h | 18 +++++++++--------- firmware/src/poke_manager.cpp | 13 +++++-------- firmware/tests/poke_manager/CMakeLists.txt | 6 ++++-- firmware/tests/poke_manager/main.cpp | 10 ++++++---- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index a64b085..1ae2155 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -32,9 +32,8 @@ class PokeManager /** * \brief true if a poke was detected. */ - bool poke_detected(); - - + inline void poke() + {poke_detected_ = true;} private: @@ -47,14 +46,15 @@ class PokeManager state_t state_; uint32_t state_entry_time_us_; size_t poke_count_; + bool poke_detected_; // Constants - static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e5;// 100'000; - static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; - static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 20e3; // ?? - static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 10e3; - static inline constexpr uint32_t VAC_SETUP_TIME_US = 10e3; - static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 20e3; + static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e6;// 100'000; + static inline constexpr uint32_t MIN_POKE_TIME_US = 1e6; + static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 1e6; // ?? + static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 1e6; + static inline constexpr uint32_t VAC_SETUP_TIME_US = 1e6; + static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 1e6; }; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index bf648b6..f92583c 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,7 +1,7 @@ #include PokeManager::PokeManager() -: state_{RESET}, poke_count_{0} +: state_{RESET}, poke_count_{0}, poke_detected_{false} { // Nothing else to do! } @@ -12,16 +12,11 @@ PokeManager::~PokeManager() // TODO: implement this! } -bool PokeManager::poke_detected() -{ - return false; -} - void PokeManager::update() { // Update inputs. // TODO: poke detection here. - printf("Updating\r\n"); + // printf("Updating\r\n"); state_t next_state{state_}; // initialize next-state to current state. @@ -36,7 +31,7 @@ void PokeManager::update() next_state = ODOR_DISPENSING_TO_EXHAUST; break; case ODOR_DISPENSING_TO_EXHAUST: - if (poke_detected()) + if (poke_detected_) next_state = ODOR_DELIVERY_TO_FINAL_VALVE; break; case ODOR_DELIVERY_TO_FINAL_VALVE: @@ -63,6 +58,7 @@ void PokeManager::update() if (state_ != next_state) { state_entry_time_us_ = time_us_32(); + printf("State transition %d -> %d\r\n", state_, next_state); } if (next_state == RESET) @@ -73,6 +69,7 @@ void PokeManager::update() if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) { ++poke_count_; + poke_detected_ = false; } diff --git a/firmware/tests/poke_manager/CMakeLists.txt b/firmware/tests/poke_manager/CMakeLists.txt index b974f90..676cf76 100644 --- a/firmware/tests/poke_manager/CMakeLists.txt +++ b/firmware/tests/poke_manager/CMakeLists.txt @@ -53,8 +53,10 @@ pico_add_extra_outputs(${PROJECT_NAME}) message(WARNING "Debug printf() messages from harp core to UART with baud \ rate 921600.") -pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. -pico_enable_stdio_uart(poke_manager 1) # UART stdio for printf. +pico_enable_stdio_usb(${PROJECT_NAME} 1) +pico_enable_stdio_usb(poke_manager 1) +# pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. +# pico_enable_stdio_uart(poke_manager 1) # UART stdio for printf. # Additional libraries need to have stdio init also. diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index ba17d58..5bb1d09 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -8,11 +8,13 @@ PokeManager poke_manager; // Core0 main. int main() -{ - //Debug UART setup - stdio_uart_init_full(uart0, 921600, UART_TX_PIN, -1); // use uart1 tx only. +{ + stdio_usb_init(); + stdio_set_translate_crlf(&stdio_usb, false); // Don't replace outgoing chars. + while (!stdio_usb_connected()){} // Block until connection to serial port. printf("Hello, from an RP2040!\r\n"); - + poke_manager.poke(); while(true) poke_manager.update(); + } From f0f8f22e8dca4e702156a07f0b3a2b24b919846a Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 28 Apr 2025 10:49:43 -0700 Subject: [PATCH 04/39] FSM logic for delphi controller --- .gitmodules | 3 + firmware/inc/config.h | 1 + firmware/inc/poke_manager.h | 36 +++++++--- firmware/lib/etl | 1 + firmware/src/poke_manager.cpp | 81 ++++++++++++++++------ firmware/tests/poke_manager/CMakeLists.txt | 14 +++- firmware/tests/poke_manager/main.cpp | 18 ++++- 7 files changed, 115 insertions(+), 39 deletions(-) create mode 160000 firmware/lib/etl diff --git a/.gitmodules b/.gitmodules index cb79b01..e9033f6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "firmware/lib/rp2040.pwm"] path = firmware/lib/rp2040.pwm url = git@github.com:AllenNeuralDynamics/rp2040.pwm.git +[submodule "firmware/lib/etl"] + path = firmware/lib/etl + url = https://github.com/ETLCPP/etl.git diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 9f31f8d..85ed1dc 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -2,6 +2,7 @@ #define CONFIG_H #define NUM_VALVES (16) +#define NUM_ODOR_VALVES (3) #define UART_TX_PIN (0) #define HARP_SYNC_RX_PIN (5) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 1ae2155..bc8bdd7 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -1,9 +1,12 @@ -#ifndef POKE_MANAGER_H +#ifndef POKE_MANAGER_H // Include Gaurd #define POKE_MANAGER_H #include #include // for uart printing #include // for printf +#include +#include +#include class PokeManager @@ -21,20 +24,26 @@ class PokeManager ODOR_PURGE, }; - PokeManager(); - ~PokeManager(); + // Declare constructor + PokeManager( + ValveDriver& final_valve, //Pass by reference (work of this org. object) + ValveDriver& vac_valve, + etl::vector& odor_valves + ); + ~PokeManager(); // desctructor void update(); - /** - * \brief true if a poke was detected. + * \brief true if a poke was detected. Inline replaces function with code */ inline void poke() {poke_detected_ = true;} + void deenergize_all_valves(); + private: /** @@ -43,18 +52,23 @@ class PokeManager inline uint32_t state_duration_us() {return time_us_32() - state_entry_time_us_;} + // Declare data members state_t state_; uint32_t state_entry_time_us_; + int valve_index_; size_t poke_count_; bool poke_detected_; + ValveDriver& vac_valve_; + ValveDriver& final_valve_; + etl::vector& odor_valves_; - // Constants - static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 1e6;// 100'000; + // Declare Constants + static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 2e4;// 20ms static inline constexpr uint32_t MIN_POKE_TIME_US = 1e6; - static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 1e6; // ?? - static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 1e6; - static inline constexpr uint32_t VAC_SETUP_TIME_US = 1e6; - static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 1e6; + static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 1e6; // 1 second + static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 0; // No vaccum setup needed + static inline constexpr uint32_t VAC_SETUP_TIME_US = 0; + static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 0; }; diff --git a/firmware/lib/etl b/firmware/lib/etl new file mode 160000 index 0000000..bda1e86 --- /dev/null +++ b/firmware/lib/etl @@ -0,0 +1 @@ +Subproject commit bda1e8619de7df97da7fc69594f0a65552f506b8 diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index f92583c..346b05c 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,22 +1,27 @@ #include -PokeManager::PokeManager() -: state_{RESET}, poke_count_{0}, poke_detected_{false} +PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, etl::vector& odor_valves) +: state_{RESET}, poke_count_{0}, valve_index_{0}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves} { // Nothing else to do! } - PokeManager::~PokeManager() { // TODO: implement this! } +void PokeManager::deenergize_all_valves() +{ + final_valve_.deenergize(); + vac_valve_.deenergize(); +} + void PokeManager::update() { // Update inputs. - // TODO: poke detection here. - // printf("Updating\r\n"); + // Poke detection here. + state_t next_state{state_}; // initialize next-state to current state. @@ -29,6 +34,7 @@ void PokeManager::update() case ODOR_SETUP: if (state_duration_us() >= VACUUM_CLOSE_TIME_US) next_state = ODOR_DISPENSING_TO_EXHAUST; + poke(); // constant simulated pokes break; case ODOR_DISPENSING_TO_EXHAUST: if (poke_detected_) @@ -48,7 +54,7 @@ void PokeManager::update() break; case ODOR_PURGE: if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) - next_state = ODOR_PURGE; + next_state = ODOR_SETUP; break; default: break; @@ -59,28 +65,57 @@ void PokeManager::update() { state_entry_time_us_ = time_us_32(); printf("State transition %d -> %d\r\n", state_, next_state); - } + + // Next state logic should only be assessed if there is a state transition + if (next_state == RESET) //need to query state_ for initital + { + deenergize_all_valves(); + state_entry_time_us_ = time_us_32(); // Force this to happen at reset. + } - if (next_state == RESET) - { - // deenergize_all_valves(); - state_entry_time_us_ = time_us_32(); // Force this to happen at reset. - } - if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) - { - ++poke_count_; - poke_detected_ = false; - } + if (next_state == ODOR_SETUP) + { + // // Energize one of the odor valves + deenergize_all_valves(); + odor_valves_[valve_index_].energize(); + } + if (next_state == ODOR_DISPENSING_TO_EXHAUST) + { + // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke + } - // Handling state-and/or-input-depenendent output logic. - if (next_state == ODOR_PRECLEAN) - { - // deenergize_valve(??); - // more stuff here. - } + if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) + { + final_valve_.energize(); + ++poke_count_; + // printf("Number of pokes = %i\r\n", poke_count_); + poke_detected_ = false; + } + if (next_state == ODOR_PRECLEAN) + { + final_valve_.deenergize(); + } + + if (next_state == VAC_START) + { + // Deenergize odor valve + odor_valves_[valve_index_].deenergize(); + vac_valve_.energize(); + } + + + if (next_state == ODOR_PURGE) + { + // Energize the final valve + final_valve_.energize(); + ++valve_index_; + if (valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves + valve_index_ = 0; + } + } // Update state: state_ = next_state; } diff --git a/firmware/tests/poke_manager/CMakeLists.txt b/firmware/tests/poke_manager/CMakeLists.txt index 676cf76..04f65d0 100644 --- a/firmware/tests/poke_manager/CMakeLists.txt +++ b/firmware/tests/poke_manager/CMakeLists.txt @@ -34,6 +34,9 @@ project(poke-manager) pico_sdk_init() +add_subdirectory(../../lib/rp2040.pwm build/rp2040_pwm) # build in this folder +add_subdirectory(../../lib/etl build/etl) + add_executable(${PROJECT_NAME} main.cpp ) @@ -42,12 +45,17 @@ add_library(poke_manager ../../src/poke_manager.cpp ) +add_library(valve_driver + ../../src/valve_driver.cpp +) include_directories(../../inc) -target_link_libraries(poke_manager - pico_stdlib) +target_link_libraries(valve_driver rp2040_pwm pico_stdlib) + +target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) + target_link_libraries(${PROJECT_NAME} - poke_manager pico_stdlib) + poke_manager pico_stdlib valve_driver etl::etl) pico_add_extra_outputs(${PROJECT_NAME}) diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index 5bb1d09..4500a29 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -1,14 +1,29 @@ #include #include +#include #include +#include #include // for uart printing #include // for printf -PokeManager poke_manager; + +// Inititalize final and vac valve pins +ValveDriver final_valve(1); +ValveDriver vac_valve(2); +etl::vector odor_valves; + +// Pass valves into the poke manager constructor +PokeManager poke_manager(final_valve, vac_valve, odor_valves); // Core0 main. int main() { + // Odor valves vector + + for (int i = 4; i < 4 + NUM_ODOR_VALVES; i++){ + odor_valves.emplace_back(i); + } + stdio_usb_init(); stdio_set_translate_crlf(&stdio_usb, false); // Don't replace outgoing chars. while (!stdio_usb_connected()){} // Block until connection to serial port. @@ -16,5 +31,4 @@ int main() poke_manager.poke(); while(true) poke_manager.update(); - } From 23dc4244a8aaeda0855916c3f610c58a3232f7da Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Tue, 29 Apr 2025 16:17:03 -0700 Subject: [PATCH 05/39] Detect pokes and update FSM --- firmware/inc/poke_manager.h | 2 +- firmware/src/poke_manager.cpp | 30 +++++++-------- firmware/src/valve_controller_app.cpp | 1 + firmware/tests/poke_manager/main.cpp | 55 ++++++++++++++++++++++++--- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index bc8bdd7..4f80bb2 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -55,6 +55,7 @@ class PokeManager // Declare data members state_t state_; uint32_t state_entry_time_us_; + int valve_index_; size_t poke_count_; bool poke_detected_; @@ -64,7 +65,6 @@ class PokeManager // Declare Constants static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 2e4;// 20ms - static inline constexpr uint32_t MIN_POKE_TIME_US = 1e6; static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 1e6; // 1 second static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 0; // No vaccum setup needed static inline constexpr uint32_t VAC_SETUP_TIME_US = 0; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 346b05c..2d95fd8 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -15,13 +15,19 @@ void PokeManager::deenergize_all_valves() { final_valve_.deenergize(); vac_valve_.deenergize(); + for (int i = 0; i < NUM_ODOR_VALVES; i++){ + odor_valves_[i].deenergize(); + } } void PokeManager::update() { - // Update inputs. - // Poke detection here. - + //initialize RESET state + if (state_ == RESET) //need to query state_ for initital + { + // state_entry_time_us_ = time_us_32(); + deenergize_all_valves(); + } state_t next_state{state_}; // initialize next-state to current state. @@ -34,7 +40,6 @@ void PokeManager::update() case ODOR_SETUP: if (state_duration_us() >= VACUUM_CLOSE_TIME_US) next_state = ODOR_DISPENSING_TO_EXHAUST; - poke(); // constant simulated pokes break; case ODOR_DISPENSING_TO_EXHAUST: if (poke_detected_) @@ -63,19 +68,14 @@ void PokeManager::update() // Update how long we've been in the new state. if (state_ != next_state) { - state_entry_time_us_ = time_us_32(); printf("State transition %d -> %d\r\n", state_, next_state); + printf("State transition time %i\r\n", state_duration_us()); + state_entry_time_us_ = time_us_32(); // Next state logic should only be assessed if there is a state transition - if (next_state == RESET) //need to query state_ for initital - { - deenergize_all_valves(); - state_entry_time_us_ = time_us_32(); // Force this to happen at reset. - } - if (next_state == ODOR_SETUP) { - // // Energize one of the odor valves + // Energize one of the odor valves deenergize_all_valves(); odor_valves_[valve_index_].energize(); } @@ -89,7 +89,7 @@ void PokeManager::update() { final_valve_.energize(); ++poke_count_; - // printf("Number of pokes = %i\r\n", poke_count_); + printf("Number of pokes = %i\r\n", poke_count_); poke_detected_ = false; } @@ -98,7 +98,6 @@ void PokeManager::update() final_valve_.deenergize(); } - if (next_state == VAC_START) { // Deenergize odor valve @@ -106,7 +105,6 @@ void PokeManager::update() vac_valve_.energize(); } - if (next_state == ODOR_PURGE) { // Energize the final valve @@ -114,6 +112,8 @@ void PokeManager::update() ++valve_index_; if (valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves valve_index_ = 0; + + printf("Odor Valve: %i\r\n", valve_index_); //valve odor index } } // Update state: diff --git a/firmware/src/valve_controller_app.cpp b/firmware/src/valve_controller_app.cpp index 937d2e9..7f8533b 100644 --- a/firmware/src/valve_controller_app.cpp +++ b/firmware/src/valve_controller_app.cpp @@ -309,5 +309,6 @@ void reset_app() app_regs.AuxGPIOFallingInputs = 0; old_aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; + } diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index 4500a29..dfd0e1e 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -15,11 +15,29 @@ etl::vector odor_valves; // Pass valves into the poke manager constructor PokeManager poke_manager(final_valve, vac_valve, odor_valves); +// LED an Poke Port +const uint LED_PIN = 25; +const uint POKE_PIN = 0; //GPIO pin for pokes +bool beam_broken = false; //keep track of beam state +bool poke_initiated_once = false; //Only trigger the FSM on 1 poke +uint32_t poke_start_time_us; //poke start time +static inline constexpr uint32_t MIN_POKE_TIME_US = 1e6; //poke duration - 1s + // Core0 main. int main() { - // Odor valves vector - + // setup AUX GPIO pins and convert into pins + gpio_init_mask(GPIOS_MASK << GPIO_PIN_BASE); //pico sdk function + gpio_set_dir_masked(GPIOS_MASK << GPIO_PIN_BASE, 0); //0: input, 1: output + + // Initialize one input pin for the poke port + // gpio_init(POKE_PIN ); + // gpio_set_dir(POKE_PIN , 0); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + // Odor valves vector -- set valves for (int i = 4; i < 4 + NUM_ODOR_VALVES; i++){ odor_valves.emplace_back(i); } @@ -28,7 +46,34 @@ int main() stdio_set_translate_crlf(&stdio_usb, false); // Don't replace outgoing chars. while (!stdio_usb_connected()){} // Block until connection to serial port. printf("Hello, from an RP2040!\r\n"); - poke_manager.poke(); - while(true) - poke_manager.update(); + while(true){ + poke_manager.update(); // update through FSM + + // Beam is no longer broken + if (gpio_get(POKE_PIN) == 1){ + gpio_put(LED_PIN, 0); + beam_broken == false; + poke_start_time_us = time_us_32(); + poke_initiated_once = false; + } + + // Poke detected -- start poke timer + if (gpio_get(POKE_PIN) == 0 && beam_broken == false){ + poke_start_time_us = time_us_32(); + beam_broken = true; + } + + // Check duration since beam break/poke + if (gpio_get(POKE_PIN) == 0 && beam_broken == true){ + gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken + if ((time_us_32() - poke_start_time_us) >= MIN_POKE_TIME_US && poke_initiated_once == false){ + + //Poke was detected! + poke_manager.poke(); + + //Account for the successful poke so that another doesn't occur on the same poke + poke_initiated_once = true; + } + } + } } From ef032163f43f7452c604ba7a853a7cf48c8bf5f1 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 30 Apr 2025 13:57:01 -0700 Subject: [PATCH 06/39] Updated transition duration values --- firmware/inc/poke_manager.h | 10 +++++----- firmware/tests/poke_manager/main.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 4f80bb2..f054d4f 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -64,11 +64,11 @@ class PokeManager etl::vector& odor_valves_; // Declare Constants - static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 2e4;// 20ms - static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 1e6; // 1 second - static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 0; // No vaccum setup needed - static inline constexpr uint32_t VAC_SETUP_TIME_US = 0; - static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 0; + static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 20e3; + static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 30e3; + static inline constexpr uint32_t VAC_SETUP_TIME_US = 20e3; + static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 110e3; }; diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index dfd0e1e..04e6571 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -21,7 +21,7 @@ const uint POKE_PIN = 0; //GPIO pin for pokes bool beam_broken = false; //keep track of beam state bool poke_initiated_once = false; //Only trigger the FSM on 1 poke uint32_t poke_start_time_us; //poke start time -static inline constexpr uint32_t MIN_POKE_TIME_US = 1e6; //poke duration - 1s +static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; //poke duration - 1s, could return this value in the source file // Core0 main. int main() From b9d8cb6e0493fbbd4605d2dbf52f91b575380c51 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Fri, 2 May 2025 14:24:38 -0700 Subject: [PATCH 07/39] Added poke manager test files and started to add registers and harp protocol functionality --- firmware/CMakeLists.txt | 17 +- firmware/inc/delphi_controller_app.h | 128 +++++++ firmware/inc/poke_manager.h | 15 +- firmware/inc/poke_manager_test.h | 75 ++++ firmware/src/delphi_controller_app.cpp | 395 +++++++++++++++++++++ firmware/src/main.cpp | 4 +- firmware/src/poke_manager.cpp | 193 +++++----- firmware/src/poke_manager_test.cpp | 122 +++++++ firmware/tests/poke_manager/CMakeLists.txt | 10 +- firmware/tests/poke_manager/main.cpp | 2 +- 10 files changed, 865 insertions(+), 96 deletions(-) create mode 100644 firmware/inc/delphi_controller_app.h create mode 100644 firmware/inc/poke_manager_test.h create mode 100644 firmware/src/delphi_controller_app.cpp create mode 100644 firmware/src/poke_manager_test.cpp diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 7df4c24..d25ae2c 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -6,7 +6,7 @@ add_definitions(-DGIT_HASH="${COMMIT_ID}") # Usable in source code. #add_definitions(-DDEBUG) # Uncomment for debugging add_definitions(-DUSBD_MANUFACTURER="Allen Institute") -add_definitions(-DUSBD_PRODUCT="valve-controller") +add_definitions(-DUSBD_PRODUCT="delphi-controller") # PICO_SDK_PATH must be defined. include(${PICO_SDK_PATH}/pico_sdk_init.cmake) @@ -14,27 +14,33 @@ include(${PICO_SDK_PATH}/pico_sdk_init.cmake) # Use modern conventions like std::invoke set(CMAKE_CXX_STANDARD 17) -project(valve-controller) +project(delphi-controller) pico_sdk_init() add_subdirectory(lib/harp.core.rp2040/firmware) # Path to harp.core.rp2040. add_subdirectory(lib/rp2040.pwm) +add_subdirectory(lib/etl build/etl) # etl library path add_executable(${PROJECT_NAME} - src/main.cpp - src/valve_controller_app.cpp + main.cpp + delphi_controller_app.cpp ) add_library(valve_driver src/valve_driver.cpp ) +add_library(poke_manager + src/poke_manager.cpp +) + include_directories(inc) target_link_libraries(valve_driver rp2040_pwm pico_stdlib) +target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) target_link_libraries(${PROJECT_NAME} harp_core harp_c_app rp2040_pwm - valve_driver harp_sync pico_stdlib) + valve_driver harp_sync pico_stdlib etl::etl) pico_add_extra_outputs(${PROJECT_NAME}) @@ -42,6 +48,7 @@ if(DEBUG) message(WARNING "Debug printf() messages from harp core to UART with baud \ rate 921600.") pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. + pico_enable_stdio_usb(poke_manager 1) # Additional libraries need to have stdio init also. endif() diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h new file mode 100644 index 0000000..1f57be3 --- /dev/null +++ b/firmware/inc/delphi_controller_app.h @@ -0,0 +1,128 @@ +#ifndef DELPHI_CONTROLLER_APP_H +#define DELPHI_CONTROLLER_APP_H +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DEBUG + #include + #include // for printf +#endif + +// Setup for Harp App +inline constexpr size_t APP_REG_COUNT = 34; +// Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? +inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; +inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; +inline constexpr size_t AUX_GPIO_INPUT_RISE_EVENT_ADDRESS = LAST_VALVE_APP_ADDRESS + 5; +inline constexpr size_t AUX_GPIO_RISING_INPUTS_ADDRESS = AUX_GPIO_INPUT_RISE_EVENT_ADDRESS + 2; +inline constexpr size_t AUX_GPIO_FALLING_INPUTS_ADDRESS = AUX_GPIO_INPUT_RISE_EVENT_ADDRESS + 3; + +extern RegSpecs app_reg_specs[APP_REG_COUNT]; +extern RegFnPair reg_handler_fns[APP_REG_COUNT]; +extern HarpCApp& app; + +extern ValveDriver valve_drivers[NUM_VALVES]; + +extern uint8_t old_aux_gpio_inputs; + +// Valve configuration struct for configuring the Hit-and-hold driver +#pragma pack(push, 1) +struct ValveConfig +{ + float hit_output; + float hold_output; + uint32_t hit_duration_us; +}; +#pragma pack(pop) + +// Delphi task timing params +#pragma pack(push, 1) +struct DelphiTaskConfig +{ + uint32_t vacuum_close_time_us; + uint32_t odor_delivery_time_us; + uint32_t odor_transition_time; + uint32_t vac_setup_time_us; + uint32_t final_valve_energized_time_us; + uint32_t min_poke_time_us; +}; +#pragma pack(pop) + +// Registers +#pragma pack(push, 1) +struct app_regs_t +{ + uint16_t ValvesState; // Raw (energized/deenergized) state of all valves. + // Bitmask: one bit per valve. + // Write: 0 = deenergize. 1 = energize + // Read: 0 = deenergized. 1 = energized + uint16_t ValvesSet; // Energize the valves specified in the bitmask. + // Bitmask: one bit per valve. (1 = energize) + // Read values are identical to ValveStates. + uint16_t ValvesClear; // Deenergize the valve specified in the bitmask. + // Bitmask: one bit per valve. (1 = de-energize) + // Read values are the bitwise inverse of ValvesState + /// @ref ValveConfigs are represented as 16 individual registers instead + /// of a register with an array of 16 ValveConfigs. + ValveConfig ValveConfigs[NUM_VALVES]; // Represents app regs: 35, 36, ... 50 + // 16 Heterogeneous registers each + // representing a ValveConfig packed + // struct. + uint8_t AuxGPIODir; + uint8_t AuxGPIOState; + uint8_t AuxGPIOSet; + uint8_t AuxGPIOClear; + + uint8_t AuxGPIOInputRiseEvent; + uint8_t AuxGPIOInputFallEvent; + uint8_t AuxGPIORisingInputs; // Raw state of which inputs rose (could be multiple) + uint8_t AuxGPIOFallingInputs; // Raw state of which inputs fell (could be multiple) + + // Poke Manager app "registers" here. + volatile uint32_t PokeDometer; //Read only -- number of pokes the mouse has done since boot + volatile uint8_t PauseFSM; // Write only -- 1 = FSM disabled , 0 = FSM enabled (default) + volatile uint8_t RestartFSM; // Write only -- 1 = FSM enabled (default), 0 = FSM disabled + volatile uint8_t ResetFSM; // Write only -- force FSM into reset state when passed a value of 1, default = 0 + volatile uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered + volatile uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued + DelphiTaskConfig DelphiTaskConfig; // write and read +}; +#pragma pack(pop) + +extern app_regs_t app_regs; + +/** + * \brief update the app state. Called in a loop. + */ +void update_app_state(); + +/** + * \brief reset the app. + */ +void reset_app(); + +inline uint8_t read_aux_gpios() +{return uint8_t((gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK);} + +void read_valves_state(uint8_t reg_address); +void read_valves_set(uint8_t reg_address); +void read_valves_clear(uint8_t reg_address); +void read_any_valve_config(uint8_t reg_address); +void read_aux_gpio_state(uint8_t reg_address); +void read_pokedometer(uint8_t reg_address); + +void write_valves_state(msg_t& msg); +void write_valves_set(msg_t& msg); +void write_valves_clear(msg_t& msg); +void write_any_valve_config(msg_t& msg); +void write_aux_gpio_dir(msg_t& msg); +void write_aux_gpio_state(msg_t& msg); +void write_aux_gpio_set(msg_t& msg); +void write_aux_gpio_clear(msg_t& msg); + +#endif // DELPHI_CONTROLLER_APP_H diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index f054d4f..8004f42 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -36,6 +36,12 @@ class PokeManager void update(); + void reset(); // reset the fsm + + void pause(); // pause fsm + + void restart(); // restart fsm + /** * \brief true if a poke was detected. Inline replaces function with code */ @@ -44,6 +50,12 @@ class PokeManager void deenergize_all_valves(); + inline size_t get_poke_count() const + {return poke_count_;} + + inline int get_current_odor() const + {return odor_valve_index_;} + private: /** @@ -56,9 +68,10 @@ class PokeManager state_t state_; uint32_t state_entry_time_us_; - int valve_index_; + int odor_valve_index_; size_t poke_count_; bool poke_detected_; + bool disable_fsm_; ValveDriver& vac_valve_; ValveDriver& final_valve_; etl::vector& odor_valves_; diff --git a/firmware/inc/poke_manager_test.h b/firmware/inc/poke_manager_test.h new file mode 100644 index 0000000..f054d4f --- /dev/null +++ b/firmware/inc/poke_manager_test.h @@ -0,0 +1,75 @@ +#ifndef POKE_MANAGER_H // Include Gaurd +#define POKE_MANAGER_H + +#include +#include // for uart printing +#include // for printf +#include +#include +#include + + +class PokeManager +{ +public: + + enum state_t + { + RESET, + ODOR_SETUP, + ODOR_DISPENSING_TO_EXHAUST, + ODOR_DELIVERY_TO_FINAL_VALVE, + ODOR_PRECLEAN, + VAC_START, + ODOR_PURGE, + }; + + + // Declare constructor + PokeManager( + ValveDriver& final_valve, //Pass by reference (work of this org. object) + ValveDriver& vac_valve, + etl::vector& odor_valves + ); + + ~PokeManager(); // desctructor + + void update(); + +/** + * \brief true if a poke was detected. Inline replaces function with code + */ + inline void poke() + {poke_detected_ = true;} + + void deenergize_all_valves(); + +private: + +/** + * \brief time we've been in the current state. + */ + inline uint32_t state_duration_us() + {return time_us_32() - state_entry_time_us_;} + + // Declare data members + state_t state_; + uint32_t state_entry_time_us_; + + int valve_index_; + size_t poke_count_; + bool poke_detected_; + ValveDriver& vac_valve_; + ValveDriver& final_valve_; + etl::vector& odor_valves_; + + // Declare Constants + static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 20e3; + static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 30e3; + static inline constexpr uint32_t VAC_SETUP_TIME_US = 20e3; + static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 110e3; + +}; + +#endif // POKE_MANAGER_H diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp new file mode 100644 index 0000000..1a6bdf8 --- /dev/null +++ b/firmware/src/delphi_controller_app.cpp @@ -0,0 +1,395 @@ +#include + +app_regs_t app_regs; + +uint8_t old_aux_gpio_inputs; + + +// Create function aliases for readability. +void (&read_aux_gpio_dir)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&read_aux_gpio_set)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&read_aux_gpio_clear)(uint8_t reg_address) = HarpCore::read_reg_generic; + +void (&read_aux_gpio_rise_event)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&read_aux_gpio_fall_event)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&write_aux_gpio_rise_event)(msg_t& msg) = HarpCore::write_reg_generic; +void (&write_aux_gpio_fall_event)(msg_t& msg) = HarpCore::write_reg_generic; + +void (&read_aux_gpio_rise_input)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&read_aux_gpio_fall_input)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&write_aux_gpio_rise_input)(msg_t& msg) = HarpCore::write_reg_generic; +void (&write_aux_gpio_fall_input)(msg_t& msg) = HarpCore::write_reg_generic; + + +/// Create Hit-and-Hold Valve Drivers. +/// The underlying PWM peripheral, aka: a PWM Slice, controls two adjacent PWM +/// pins and must be configured with the same settings. This is OK since we are +/// enforcing the same underlying peripheral settings (i.e: frequency) across +/// all Slices. +ValveDriver valve_drivers[NUM_VALVES] +{{VALVE_PIN_BASE}, + {VALVE_PIN_BASE + 1}, + {VALVE_PIN_BASE + 2}, + {VALVE_PIN_BASE + 3}, + {VALVE_PIN_BASE + 4}, + {VALVE_PIN_BASE + 5}, + {VALVE_PIN_BASE + 6}, + {VALVE_PIN_BASE + 7}, + {VALVE_PIN_BASE + 8}, + {VALVE_PIN_BASE + 9}, + {VALVE_PIN_BASE + 10}, + {VALVE_PIN_BASE + 11}, + {VALVE_PIN_BASE + 12}, + {VALVE_PIN_BASE + 13}, + {VALVE_PIN_BASE + 14}, + {VALVE_PIN_BASE + 15}}; + +// Define "specs" per-register +RegSpecs app_reg_specs[APP_REG_COUNT] +{ + {(uint8_t*)&app_regs.ValvesState, sizeof(app_regs.ValvesState), U16}, + {(uint8_t*)&app_regs.ValvesSet, sizeof(app_regs.ValvesSet), U16}, + {(uint8_t*)&app_regs.ValvesClear, sizeof(app_regs.ValvesClear), U16}, + + {(uint8_t*)&app_regs.ValveConfigs[0], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[1], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[2], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[3], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[4], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[5], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[6], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[7], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[8], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[9], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[10], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[11], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[12], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[13], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[14], sizeof(ValveConfig), U8}, + {(uint8_t*)&app_regs.ValveConfigs[15], sizeof(ValveConfig), U8}, + + {(uint8_t*)&app_regs.AuxGPIODir, sizeof(app_regs.AuxGPIODir), U8}, + {(uint8_t*)&app_regs.AuxGPIOState, sizeof(app_regs.AuxGPIOState), U8}, + {(uint8_t*)&app_regs.AuxGPIOSet, sizeof(app_regs.AuxGPIOSet), U8}, + {(uint8_t*)&app_regs.AuxGPIOClear, sizeof(app_regs.AuxGPIOClear), U8}, + + {(uint8_t*)&app_regs.AuxGPIOInputRiseEvent, sizeof(app_regs.AuxGPIOInputRiseEvent), U8}, + {(uint8_t*)&app_regs.AuxGPIOInputFallEvent, sizeof(app_regs.AuxGPIOInputFallEvent), U8}, + {(uint8_t*)&app_regs.AuxGPIORisingInputs, sizeof(app_regs.AuxGPIORisingInputs), U8}, + {(uint8_t*)&app_regs.AuxGPIOFallingInputs, sizeof(app_regs.AuxGPIOFallingInputs), U8}, + + // More specs here for poke manager registers. + {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, + {(uint8_t*)&app_regs.PauseFSM, sizeof(app_regs.PauseFSM), U8}, + {(uint8_t*)&app_regs.RestartFSM, sizeof(app_regs.RestartFSM), U8}, + {(uint8_t*)&app_regs.ResetFSM, sizeof(app_regs.ResetFSM), U8}, + {(uint8_t*)&app_regs.CurrentOdor, sizeof(app_regs.CurrentOdor), U8}, + {(uint8_t*)&app_regs.NextOdor, sizeof(app_regs.NextOdor), U8}, + {(uint8_t*)&app_regs.DelphiTaskConfig, sizeof(DelphiTaskConfig), U8}, +}; + +RegFnPair reg_handler_fns[APP_REG_COUNT] +{ + {read_valves_state, write_valves_state}, + {read_valves_set, write_valves_set}, + {read_valves_clear, write_valves_clear}, + + {read_any_valve_config, write_any_valve_config}, // valve 0 + {read_any_valve_config, write_any_valve_config}, // valve 1 + {read_any_valve_config, write_any_valve_config}, // ... + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, + {read_any_valve_config, write_any_valve_config}, // valve 15 + + {read_aux_gpio_dir, write_aux_gpio_dir}, + {read_aux_gpio_state, write_aux_gpio_state}, + {read_aux_gpio_set, write_aux_gpio_set}, + {read_aux_gpio_clear, write_aux_gpio_clear}, + + {read_aux_gpio_rise_event, write_aux_gpio_rise_event}, + {read_aux_gpio_fall_event, write_aux_gpio_fall_event}, + {read_aux_gpio_rise_input, write_aux_gpio_rise_input}, //this handler function was missing + {read_aux_gpio_fall_input, write_aux_gpio_fall_input}, //this handler function was missing + + // Poke manager handler functions + {read_pokedometer, HarpCore::write_to_read_only_reg_error}, // read only + {HarpCore::read_reg_generic, write_pause_fsm}, // write only + {HarpCore::read_reg_generic, write_restart_fsm}, // write only + {HarpCore::read_reg_generic, write_reset_poke_manager_fsm}, // write only + {read_current_odor, HarpCore::write_to_read_only_reg_error}, // read only + {read_next_odor, write_next_odor}, //read and write +}; + + +void read_current_odor(uint8_t reg_address) +{ + // Get recent poke count value + app_regs.CurrentOdor = poke_manager.get_current_odor(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_restart_fsm(msg_t& msg) +{ + // Registered value is updated + // HarpCore::copy_msg_payload_to_register(msg); //upfates the register + if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + poke_manager.restart(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void write_pause_fsm(msg_t& msg) +{ + // Registered value is updated + // HarpCore::copy_msg_payload_to_register(msg); //upfates the register + if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + poke_manager.pause(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + + +void write_reset_poke_manager_fsm(msg_t& msg) +{ + // Registered value is updated + // HarpCore::copy_msg_payload_to_register(msg); //upfates the register + if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + poke_manager.reset(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_pokedometer(uint8_t reg_address) +{ + // Get recent poke count value + app_regs.PokeDometer = poke_manager.get_poke_count(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void read_valves_state(uint8_t reg_address) +{ + for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) + { + app_regs.ValvesState = 0; + if (valve_drivers[valve_index].is_energized()) + app_regs.ValvesState |= (typeof(app_regs.ValvesState))(1) << valve_index; + } + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_valves_state(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) + { + if ((app_regs.ValvesState >> valve_index) & (typeof(app_regs.ValvesState))(1)) + valve_drivers[valve_index].energize(); + else + valve_drivers[valve_index].deenergize(); + } + // Reply with the actual value that we wrote. + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_valves_set(uint8_t reg_address) +{ + // Return the most recently set value. + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_valves_set(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) + { + if ((app_regs.ValvesSet >> valve_index) & (typeof(app_regs.ValvesSet))(1)) + valve_drivers[valve_index].energize(); + } + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_valves_clear(uint8_t reg_address) +{ + // Return the most recently cleared value. + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_valves_clear(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) + { + if ((app_regs.ValvesClear >> valve_index) & (typeof(app_regs.ValvesClear))(1)) + valve_drivers[valve_index].deenergize(); + } + // Reply with the actual value that we wrote. + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + + +void read_any_valve_config(uint8_t reg_address) +{ + uint8_t valve_index = reg_address - VALVE_START_APP_ADDRESS; + ValveConfig& valve_cfg = app_regs.ValveConfigs[valve_index]; + const ValveDriver& valve_driver = valve_drivers[valve_index]; + // Update Harp App registers with ValveDriver class contents. + valve_cfg.hit_output = valve_driver.get_hit_output(); + valve_cfg.hold_output = valve_driver.get_hold_output(); + valve_cfg.hit_duration_us = valve_driver.get_hit_duration_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + + +void write_any_valve_config(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + uint8_t valve_index = msg.header.address - VALVE_START_APP_ADDRESS; + const ValveConfig& valve_cfg = app_regs.ValveConfigs[valve_index]; + ValveDriver& valve_driver = valve_drivers[valve_index]; + // Apply the configuration. + valve_driver.set_hit_duration_us(valve_cfg.hit_duration_us); + valve_driver.set_normalized_hit_output(valve_cfg.hit_output); + valve_driver.set_normalized_hold_output(valve_cfg.hold_output); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +//void read_aux_gpio_dir(uint8_t reg_address) +//{ +// // Nothing to do! +// // This register will stay consistent with the underlying peripheral +// // register after we initialize it the first time. +// if (!HarpCore::is_muted()) +// HarpCore::send_harp_reply(READ, reg_address); +//} + +void write_aux_gpio_dir(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + // Apply register settings (set bits are outputs; cleared bits are inputs). + gpio_set_dir_masked(uint32_t(GPIOS_MASK) << GPIO_PIN_BASE, + uint32_t(app_regs.AuxGPIODir) << GPIO_PIN_BASE); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_aux_gpio_state(uint8_t reg_address) +{ + // Update register contents. + app_regs.AuxGPIOState = read_aux_gpios(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_aux_gpio_state(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + // Note: only write to outputs + gpio_put_masked(uint32_t(app_regs.AuxGPIODir) << GPIO_PIN_BASE, + uint32_t(app_regs.AuxGPIOState) << GPIO_PIN_BASE); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void write_aux_gpio_set(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + // Note: only write to outputs (ie: mask on Set bits). + gpio_put_masked( + uint32_t(app_regs.AuxGPIODir & app_regs.AuxGPIOSet) << GPIO_PIN_BASE, + uint32_t(app_regs.AuxGPIOSet) << GPIO_PIN_BASE); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void write_aux_gpio_clear(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + // Note: only write to outputs (ie: mask on Clear bits). + gpio_put_masked( + uint32_t(app_regs.AuxGPIODir & app_regs.AuxGPIOClear) << GPIO_PIN_BASE, + 0); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void update_app_state() // Called when app.run() is called +{ + // Update poke manager FSM + poke_manager.update(); + + // Update valve controller state machines. + for (auto& valve_driver: valve_drivers) + valve_driver.update(); + // Process AuxGPIO input changes. + // FIXME: do we need to update old_aux_gpio_inputs if we change (write-to) + // app_regs.AuxGPIODir ? + uint8_t aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; + uint8_t changed_inputs = (old_aux_gpio_inputs ^ aux_gpio_inputs); + app_regs.AuxGPIORisingInputs = app_regs.AuxGPIOInputRiseEvent & aux_gpio_inputs & changed_inputs; + app_regs.AuxGPIOFallingInputs = app_regs.AuxGPIOInputFallEvent & ~aux_gpio_inputs & changed_inputs; + old_aux_gpio_inputs = aux_gpio_inputs; + // Emit EVENT messages for rising/falling edges on configured pins. + if (HarpCore::is_muted()) + return; + if (app_regs.AuxGPIOInputRiseEvent & app_regs.AuxGPIORisingInputs) + HarpCore::send_harp_reply(EVENT, AUX_GPIO_RISING_INPUTS_ADDRESS); + if (app_regs.AuxGPIOInputFallEvent & app_regs.AuxGPIOFallingInputs) + HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); +} + +void reset_app() +{ + // Reset poke manager + poke_manager.reset(); + + // Reset Harp register struct elements. + app_regs.ValvesState = 0; + app_regs.ValvesSet = 0; + app_regs.ValvesClear = 0; + // Turn off all outputs. + for (auto& valve_driver: valve_drivers) + valve_driver.reset(); + + // Init the exposed auxiliary GPIO pins we are using as all-inputs. + // This *must* be called once to setup the AUX GPIOs. + gpio_init_mask(GPIOS_MASK << GPIO_PIN_BASE); + gpio_set_dir_masked(GPIOS_MASK << GPIO_PIN_BASE, 0); + + app_regs.AuxGPIODir = 0; // All inputs (consistent with what we just set). + app_regs.AuxGPIOState = (gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK; + app_regs.AuxGPIOSet = 0; + app_regs.AuxGPIOClear = 0; + + // Clear aux input EVENT message configuration. + app_regs.AuxGPIORisingInputs = 0; + app_regs.AuxGPIOFallingInputs = 0; + + old_aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; + +} + diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 3508d32..ef92a06 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #ifdef DEBUG #include // for uart printing #include // for printf @@ -17,7 +17,7 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, HARP_VERSION_MAJOR, HARP_VERSION_MINOR, FW_VERSION_MAJOR, FW_VERSION_MINOR, UNUSED_SERIAL_NUMBER, - "valve-controller", + "delphi-controller", (uint8_t*)GIT_HASH, &app_regs, app_reg_specs, reg_handler_fns, APP_REG_COUNT, update_app_state, diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 2d95fd8..a3ca3ec 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,14 +1,15 @@ #include PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, etl::vector& odor_valves) -: state_{RESET}, poke_count_{0}, valve_index_{0}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves} +: state_{RESET}, poke_count_{0}, odor_valve_index_{0}, disable_fsm_{false}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves} { // Nothing else to do! } -PokeManager::~PokeManager() +PokeManager::~PokeManager() //destuctor { - // TODO: implement this! + //Deengergize all valves + deenergize_all_valves(); } void PokeManager::deenergize_all_valves() @@ -20,102 +21,130 @@ void PokeManager::deenergize_all_valves() } } -void PokeManager::update() +void PokeManager::reset() { - //initialize RESET state - if (state_ == RESET) //need to query state_ for initital - { - // state_entry_time_us_ = time_us_32(); - deenergize_all_valves(); - } + deenergize_all_valves(); + odor_valve_index_ = 0; + poke_count_ = 0; + poke_detected_ = false; + disable_fsm_ = false; + state_ = RESET; +} - state_t next_state{state_}; // initialize next-state to current state. +void PokeManager::pause() // Needed for odor changes/refills +{ + disable_fsm_ = true; + deenergize_all_valves(); // deenergize all valves +} - // Handling next-state logic. - switch (state_) - { - case RESET: - next_state = ODOR_SETUP; - break; - case ODOR_SETUP: - if (state_duration_us() >= VACUUM_CLOSE_TIME_US) - next_state = ODOR_DISPENSING_TO_EXHAUST; - break; - case ODOR_DISPENSING_TO_EXHAUST: - if (poke_detected_) - next_state = ODOR_DELIVERY_TO_FINAL_VALVE; - break; - case ODOR_DELIVERY_TO_FINAL_VALVE: - if (state_duration_us() >= ODOR_DELIVERY_TIME_US) - next_state = ODOR_PRECLEAN; - break; - case ODOR_PRECLEAN: - if (state_duration_us() >= ODOR_TRANSITION_TIME_US) - next_state = VAC_START; - break; - case VAC_START: - if (state_duration_us() >= VAC_SETUP_TIME_US) - next_state = ODOR_PURGE; - break; - case ODOR_PURGE: - if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) - next_state = ODOR_SETUP; - break; - default: - break; - } +void PokeManager::restart() // Needed for odor changes/refills +{ + disable_fsm_ = false; + poke_detected_ = false; + state_ = RESET; +} - // Update how long we've been in the new state. - if (state_ != next_state) +void PokeManager::update() +{ + //enabled by default, but if disabled, pause the FSM + if (disable_fsm_ == false) { - printf("State transition %d -> %d\r\n", state_, next_state); - printf("State transition time %i\r\n", state_duration_us()); - state_entry_time_us_ = time_us_32(); - - // Next state logic should only be assessed if there is a state transition - if (next_state == ODOR_SETUP) + //initialize RESET state + if (state_ == RESET) //need to query state_ for initital { - // Energize one of the odor valves + // state_entry_time_us_ = time_us_32(); deenergize_all_valves(); - odor_valves_[valve_index_].energize(); } - if (next_state == ODOR_DISPENSING_TO_EXHAUST) - { - // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke - } + state_t next_state{state_}; // initialize next-state to current state. - if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) + // Handling next-state logic. + switch (state_) { - final_valve_.energize(); - ++poke_count_; - printf("Number of pokes = %i\r\n", poke_count_); - poke_detected_ = false; + case RESET: + next_state = ODOR_SETUP; + break; + case ODOR_SETUP: + if (state_duration_us() >= VACUUM_CLOSE_TIME_US) + next_state = ODOR_DISPENSING_TO_EXHAUST; + break; + case ODOR_DISPENSING_TO_EXHAUST: + if (poke_detected_) + next_state = ODOR_DELIVERY_TO_FINAL_VALVE; + break; + case ODOR_DELIVERY_TO_FINAL_VALVE: + if (state_duration_us() >= ODOR_DELIVERY_TIME_US) + next_state = ODOR_PRECLEAN; + break; + case ODOR_PRECLEAN: + if (state_duration_us() >= ODOR_TRANSITION_TIME_US) + next_state = VAC_START; + break; + case VAC_START: + if (state_duration_us() >= VAC_SETUP_TIME_US) + next_state = ODOR_PURGE; + break; + case ODOR_PURGE: + if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) + next_state = ODOR_SETUP; + break; + default: + break; } - if (next_state == ODOR_PRECLEAN) + // Update how long we've been in the new state. + if (state_ != next_state) { - final_valve_.deenergize(); - } + printf("State transition %d -> %d\r\n", state_, next_state); + printf("State transition time %i\r\n", state_duration_us()); + state_entry_time_us_ = time_us_32(); + + // Next state logic should only be assessed if there is a state transition + if (next_state == ODOR_SETUP) + { + // Energize one of the odor valves + deenergize_all_valves(); + odor_valves_[odor_valve_index_].energize(); + } - if (next_state == VAC_START) - { - // Deenergize odor valve - odor_valves_[valve_index_].deenergize(); - vac_valve_.energize(); - } + if (next_state == ODOR_DISPENSING_TO_EXHAUST) + { + // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke + } - if (next_state == ODOR_PURGE) - { - // Energize the final valve - final_valve_.energize(); - ++valve_index_; - if (valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves - valve_index_ = 0; + if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) + { + final_valve_.energize(); + ++poke_count_; + printf("Number of pokes = %i\r\n", poke_count_); + poke_detected_ = false; + } + + if (next_state == ODOR_PRECLEAN) + { + final_valve_.deenergize(); + } - printf("Odor Valve: %i\r\n", valve_index_); //valve odor index + if (next_state == VAC_START) + { + // Deenergize odor valve + odor_valves_[odor_valve_index_].deenergize(); + vac_valve_.energize(); + } + + if (next_state == ODOR_PURGE) + { + // Energize the final valve + final_valve_.energize(); + ++odor_valve_index_; + if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves + odor_valve_index_ = 0; + + printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index + } } + // Update state: + state_ = next_state; + } - // Update state: - state_ = next_state; } diff --git a/firmware/src/poke_manager_test.cpp b/firmware/src/poke_manager_test.cpp new file mode 100644 index 0000000..0b00ba9 --- /dev/null +++ b/firmware/src/poke_manager_test.cpp @@ -0,0 +1,122 @@ +#include + +PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, etl::vector& odor_valves) +: state_{RESET}, poke_count_{0}, valve_index_{0}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves} +{ + // Nothing else to do! +} + +PokeManager::~PokeManager() //destuctor +{ + //Deengergize all valves + deenergize_all_valves(); +} + +void PokeManager::deenergize_all_valves() +{ + final_valve_.deenergize(); + vac_valve_.deenergize(); + for (int i = 0; i < NUM_ODOR_VALVES; i++){ + odor_valves_[i].deenergize(); + } +} + +void PokeManager::update() +{ + //initialize RESET state + if (state_ == RESET) //need to query state_ for initital + { + // state_entry_time_us_ = time_us_32(); + deenergize_all_valves(); + } + + state_t next_state{state_}; // initialize next-state to current state. + + // Handling next-state logic. + switch (state_) + { + case RESET: + next_state = ODOR_SETUP; + break; + case ODOR_SETUP: + if (state_duration_us() >= VACUUM_CLOSE_TIME_US) + next_state = ODOR_DISPENSING_TO_EXHAUST; + break; + case ODOR_DISPENSING_TO_EXHAUST: + if (poke_detected_) + next_state = ODOR_DELIVERY_TO_FINAL_VALVE; + break; + case ODOR_DELIVERY_TO_FINAL_VALVE: + if (state_duration_us() >= ODOR_DELIVERY_TIME_US) + next_state = ODOR_PRECLEAN; + break; + case ODOR_PRECLEAN: + if (state_duration_us() >= ODOR_TRANSITION_TIME_US) + next_state = VAC_START; + break; + case VAC_START: + if (state_duration_us() >= VAC_SETUP_TIME_US) + next_state = ODOR_PURGE; + break; + case ODOR_PURGE: + if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) + next_state = ODOR_SETUP; + break; + default: + break; + } + + // Update how long we've been in the new state. + if (state_ != next_state) + { + printf("State transition %d -> %d\r\n", state_, next_state); + printf("State transition time %i\r\n", state_duration_us()); + state_entry_time_us_ = time_us_32(); + + // Next state logic should only be assessed if there is a state transition + if (next_state == ODOR_SETUP) + { + // Energize one of the odor valves + deenergize_all_valves(); + odor_valves_[valve_index_].energize(); + } + + if (next_state == ODOR_DISPENSING_TO_EXHAUST) + { + // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke + } + + if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) + { + final_valve_.energize(); + ++poke_count_; + printf("Number of pokes = %i\r\n", poke_count_); + poke_detected_ = false; + } + + if (next_state == ODOR_PRECLEAN) + { + final_valve_.deenergize(); + } + + if (next_state == VAC_START) + { + // Deenergize odor valve + odor_valves_[valve_index_].deenergize(); + vac_valve_.energize(); + } + + if (next_state == ODOR_PURGE) + { + // Energize the final valve + final_valve_.energize(); + ++valve_index_; + if (valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves + valve_index_ = 0; + + printf("Odor Valve: %i\r\n", valve_index_); //valve odor index + } + } + // Update state: + state_ = next_state; +} diff --git a/firmware/tests/poke_manager/CMakeLists.txt b/firmware/tests/poke_manager/CMakeLists.txt index 04f65d0..62a150a 100644 --- a/firmware/tests/poke_manager/CMakeLists.txt +++ b/firmware/tests/poke_manager/CMakeLists.txt @@ -41,8 +41,8 @@ add_executable(${PROJECT_NAME} main.cpp ) -add_library(poke_manager - ../../src/poke_manager.cpp +add_library(poke_manager_test + ../../src/poke_manager_test.cpp ) add_library(valve_driver @@ -52,17 +52,17 @@ include_directories(../../inc) target_link_libraries(valve_driver rp2040_pwm pico_stdlib) -target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) +target_link_libraries(poke_manager_test pico_stdlib valve_driver etl::etl) target_link_libraries(${PROJECT_NAME} - poke_manager pico_stdlib valve_driver etl::etl) + poke_manager_test pico_stdlib valve_driver etl::etl) pico_add_extra_outputs(${PROJECT_NAME}) message(WARNING "Debug printf() messages from harp core to UART with baud \ rate 921600.") pico_enable_stdio_usb(${PROJECT_NAME} 1) -pico_enable_stdio_usb(poke_manager 1) +pico_enable_stdio_usb(poke_manager_test 1) # pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. # pico_enable_stdio_uart(poke_manager 1) # UART stdio for printf. # Additional libraries need to have stdio init also. diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index 04e6571..1608b2e 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include // for uart printing #include // for printf From b775ca5c1536726faa8fe509b707834dd751e555 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 5 May 2025 16:14:22 -0700 Subject: [PATCH 08/39] Added poke detection functionality into poke manager --- firmware/inc/delphi_controller_app.h | 14 ++- firmware/inc/poke_manager.h | 64 ++++++++++++-- firmware/src/delphi_controller_app.cpp | 69 ++++++++++++++- firmware/src/main.cpp | 18 +++- firmware/src/poke_manager.cpp | 115 ++++++++++++++++++++++--- 5 files changed, 258 insertions(+), 22 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 1f57be3..aea50d6 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -14,7 +14,7 @@ #endif // Setup for Harp App -inline constexpr size_t APP_REG_COUNT = 34; +inline constexpr size_t APP_REG_COUNT = 35; // Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; @@ -46,7 +46,7 @@ struct DelphiTaskConfig { uint32_t vacuum_close_time_us; uint32_t odor_delivery_time_us; - uint32_t odor_transition_time; + uint32_t odor_transition_time_us; uint32_t vac_setup_time_us; uint32_t final_valve_energized_time_us; uint32_t min_poke_time_us; @@ -91,6 +91,7 @@ struct app_regs_t volatile uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered volatile uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued DelphiTaskConfig DelphiTaskConfig; // write and read + uint8_t PokePin; // write only }; #pragma pack(pop) @@ -115,6 +116,9 @@ void read_valves_clear(uint8_t reg_address); void read_any_valve_config(uint8_t reg_address); void read_aux_gpio_state(uint8_t reg_address); void read_pokedometer(uint8_t reg_address); +void read_current_odor(uint8_t reg_address); +void read_next_odor(uint8_t reg_address); +void read_delphi_task_config(uint8_t reg_address); void write_valves_state(msg_t& msg); void write_valves_set(msg_t& msg); @@ -124,5 +128,11 @@ void write_aux_gpio_dir(msg_t& msg); void write_aux_gpio_state(msg_t& msg); void write_aux_gpio_set(msg_t& msg); void write_aux_gpio_clear(msg_t& msg); +void write_pause_fsm(msg_t& msg); +void write_restart_fsm(msg_t& msg); +void write_reset_poke_manager_fsm(msg_t& msg); +void write_next_odor(msg_t& msg); +void write_delphi_task_config(msg_t& msg); +void write_poke_pin(msg_t& msg); #endif // DELPHI_CONTROLLER_APP_H diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 8004f42..2bdb290 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -42,6 +42,22 @@ class PokeManager void restart(); // restart fsm + void update_next_odor(int next_odor); + + void set_vacuum_close_time_us(uint32_t vacuum_close_time_us); + + void set_odor_delivery_time_us(uint32_t odor_delivery_time_us); + + void set_odor_transition_time_us(uint32_t odor_transition_time_us); + + void set_vac_setup_time_us(uint32_t vac_setup_time_us); + + void set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us); + + void set_min_poke_time_us(uint32_t min_poke_time_us); + + void set_poke_pin(uint8_t pin); + /** * \brief true if a poke was detected. Inline replaces function with code */ @@ -50,12 +66,35 @@ class PokeManager void deenergize_all_valves(); + void check_poke_status(); + inline size_t get_poke_count() const {return poke_count_;} inline int get_current_odor() const {return odor_valve_index_;} + inline int get_next_odor() const + {return next_odor_index_;} + + inline uint32_t get_vacuum_close_time() const + {return vacuum_close_time_us_;} + + inline uint32_t get_odor_delivery_time() const + {return odor_delivery_time_us_;} + + inline uint32_t get_odor_transition_time() const + {return odor_transition_time_;} + + inline uint32_t get_vac_setup_time() const + {return vac_setup_time_us_;} + + inline uint32_t get_final_valve_energized_time() const + {return final_valve_energized_time_us_;} + + inline uint32_t get_min_poke_time() const + {return min_poke_time_us_;} + private: /** @@ -67,22 +106,35 @@ class PokeManager // Declare data members state_t state_; uint32_t state_entry_time_us_; + uint32_t poke_start_time_us_; + uint8_t poke_pin_; int odor_valve_index_; + int next_odor_index_; size_t poke_count_; bool poke_detected_; bool disable_fsm_; + bool beam_broken_; //keep track of beam state + bool poke_initiated_once_; //Only trigger the FSM on 1 poke ValveDriver& vac_valve_; ValveDriver& final_valve_; etl::vector& odor_valves_; - // Declare Constants - static inline constexpr uint32_t VACUUM_CLOSE_TIME_US = 20e3; - static inline constexpr uint32_t ODOR_DELIVERY_TIME_US = 10e3; - static inline constexpr uint32_t ODOR_TRANSITION_TIME_US = 30e3; - static inline constexpr uint32_t VAC_SETUP_TIME_US = 20e3; - static inline constexpr uint32_t FINAL_VALVE_ENERGIZED_TIME_US = 110e3; + uint32_t vacuum_close_time_us_; + uint32_t odor_delivery_time_us_; + uint32_t odor_transition_time_; + uint32_t vac_setup_time_us_; + uint32_t final_valve_energized_time_us_; + uint32_t min_poke_time_us_; + // Declare Constants + static inline constexpr uint32_t DEFAULT_VACUUM_CLOSE_TIME_US = 20e3; + static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t DEFAULT_ODOR_TRANSITION_TIME_US = 30e3; + static inline constexpr uint32_t DEFAULT_VAC_SETUP_TIME_US = 20e3; + static inline constexpr uint32_t DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US = 110e3; + static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; + static inline constexpr uint8_t DEFAUT_POKE_PIN = GPIO_PIN_BASE; }; #endif // POKE_MANAGER_H diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 1a6bdf8..425d1cf 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -1,7 +1,6 @@ #include app_regs_t app_regs; - uint8_t old_aux_gpio_inputs; @@ -86,6 +85,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.CurrentOdor, sizeof(app_regs.CurrentOdor), U8}, {(uint8_t*)&app_regs.NextOdor, sizeof(app_regs.NextOdor), U8}, {(uint8_t*)&app_regs.DelphiTaskConfig, sizeof(DelphiTaskConfig), U8}, + {(uint8_t*)&app_regs.PokePin, sizeof(app_regs.PokePin), U8}, }; RegFnPair reg_handler_fns[APP_REG_COUNT] @@ -127,9 +127,71 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {HarpCore::read_reg_generic, write_restart_fsm}, // write only {HarpCore::read_reg_generic, write_reset_poke_manager_fsm}, // write only {read_current_odor, HarpCore::write_to_read_only_reg_error}, // read only - {read_next_odor, write_next_odor}, //read and write + {read_next_odor, write_next_odor}, //read and write --ADD TIMING HANDLER FUNCTIONS AFTER THIS + {read_delphi_task_config, write_delphi_task_config}, //read and write --Delphi Task + {HarpCore::read_reg_generic, write_poke_pin}, // write only }; +void read_delphi_task_config(uint8_t reg_address) +{ + DelphiTaskConfig& delphi_cfg = app_regs.DelphiTaskConfig + + // Update Harp App registers with Poke Manager class contents. + delphi_cfg.vacuum_close_time_us = poke_manager.get_vacuum_close_time(); + delphi_cfg.odor_delivery_time_us = poke_manager.get_odor_delivery_time(); + delphi_cfg.odor_transition_time_us = poke_manager.get_odor_transition_time(); + delphi_cfg.vac_setup_time_us = poke_manager.get_vac_setup_time(); + delphi_cfg.final_valve_energized_time_us = poke_manager.get_final_valve_energized_time(); + delphi_cfg.min_poke_time_us = poke_manager.get_min_poke_time(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_poke_pin(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + + // Apply the configuration. + poke_manager.set_poke_pin((uint8_t) msg.payload); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void write_delphi_task_config(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + DelphiTaskConfig& delphi_cfg = app_regs.DelphiTaskConfig + + // Apply the configuration. + poke_manager.set_vacuum_close_time_us(delphi_cfg.vacuum_close_time_us); + poke_manager.set_odor_delivery_time_us(delphi_cfg.odor_delivery_time_us); + poke_manager.set_odor_transition_time_us(delphi_cfg.odor_transition_time_us); + poke_manager.set_vac_setup_time_us(delphi_cfg.vac_setup_time_us); + poke_manager.set_final_valve_energized_time_us(delphi_cfg.final_valve_energized_time_us); + poke_manager.set_min_poke_time_us(delphi_cfg.min_poke_time_us); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void write_next_odor(msg_t& msg) +{ + // Registered value is updated + HarpCore::copy_msg_payload_to_register(msg); //updates the register + poke_manager.update_next_odor((int) msg.payload); //write next odor + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_next_odor(uint8_t reg_address) +{ + // Get recent poke count value + app_regs.NextOdor = poke_manager.get_next_odor(); + + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} void read_current_odor(uint8_t reg_address) { @@ -337,7 +399,7 @@ void write_aux_gpio_clear(msg_t& msg) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void update_app_state() // Called when app.run() is called +void update_app_state() // Called when app.run() is called -- add poke detection here { // Update poke manager FSM poke_manager.update(); @@ -366,6 +428,7 @@ void reset_app() { // Reset poke manager poke_manager.reset(); + app_regs.PokeDometer = 0; // Reset Harp register struct elements. app_regs.ValvesState = 0; diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index ef92a06..1f56f67 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #ifdef DEBUG #include // for uart printing @@ -23,12 +26,25 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, reg_handler_fns, APP_REG_COUNT, update_app_state, reset_app); +// Inititalize final and vac valve pins -- Does this go here? +ValveDriver final_valve(VALVE_PIN_BASE); +ValveDriver vac_valve(VALVE_PIN_BASE + 1); +etl::vector odor_valves; + +// Pass valves into the poke manager constructor +PokeManager poke_manager(final_valve, vac_valve, odor_valves); + // Core0 main. int main() { -// Init Synchronizer. + // Init Synchronizer. HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); + + // Odor valves vector -- set valves + for (int i = VALVE_PIN_BASE + 2; i < VALVE_PIN_BASE + 2 + NUM_ODOR_VALVES; i++){ + odor_valves.emplace_back(i); +} #ifdef DEBUG stdio_uart_init_full(uart0, 921600, UART_TX_PIN, -1); // use uart1 tx only. printf("Hello, from an RP2040!\r\n"); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index a3ca3ec..5451321 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,7 +1,7 @@ #include PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, etl::vector& odor_valves) -: state_{RESET}, poke_count_{0}, odor_valve_index_{0}, disable_fsm_{false}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves} +: state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, beam_broken_{false}, poke_initiated_once_{false} { // Nothing else to do! } @@ -10,6 +10,12 @@ PokeManager::~PokeManager() //destuctor { //Deengergize all valves deenergize_all_valves(); + poke_count_ = 0; + poke_detected_ = false; + disable_fsm_ = false; + state_ = RESET; + beam_broken_ = false; + poke_initiated_once_ = false; } void PokeManager::deenergize_all_valves() @@ -21,14 +27,92 @@ void PokeManager::deenergize_all_valves() } } +// Functions to configure Delphi Task/update it +void PokeManager::set_vacuum_close_time_us(uint32_t vacuum_close_time_us) +{ + vacuum_close_time_us_ = vacuum_close_time_us; +} + +void PokeManager::set_odor_delivery_time_us(uint32_t odor_delivery_time_us) +{ + odor_delivery_time_us_ = odor_delivery_time_us; +} + +void PokeManager::set_odor_transition_time_us(uint32_t odor_transition_time_us) +{ + odor_transition_time_ = odor_transition_time_us; +} + +void PokeManager::set_vac_setup_time_uss(uint32_t vac_setup_time_us) +{ + vac_setup_time_us_ = vac_setup_time_us; +} + +void PokeManager::set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us) +{ + final_valve_energized_time_us_ = final_valve_energized_time_us; +} + +void PokeManager::set_min_poke_time_us(uint32_t min_poke_time_us) +{ + min_poke_time_us_ = min_poke_time_us; +} + +void PokeManager::set_poke_pin(uint8_t pin) +{ + poke_pin_ = pin; +} + +//Poke detection function +void PokeManager::check_poke_status() +{ + // Check to see if poke has been detected + // Beam is no longer broken + if (gpio_get(poke_pin_) == 1) // specify poke pin + { + beam_broken_ == false; + poke_start_time_us_ = time_us_32(); + poke_initiated_once_ = false; + } + + // Poke detected -- start poke timer + if (gpio_get(poke_pin_) == 0 && beam_broken_ == false) + { + poke_start_time_us_ = time_us_32(); + beam_broken_ = true; + } + + // Check duration since beam break/poke + if (gpio_get(poke_pin_) == 0 && beam_broken_ == true) + { + gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken + if ((time_us_32() - poke_start_time_us_) >= min_poke_time_us_ && poke_initiated_once_ == false){ + + //Poke was detected! + poke(); + + //Account for the successful poke so that another doesn't occur on the same poke + poke_initiated_once_ = true; + } + } +} + +// Functions to alter the FSM void PokeManager::reset() { deenergize_all_valves(); - odor_valve_index_ = 0; + odor_valve_index_ = next_odor_index_; poke_count_ = 0; poke_detected_ = false; disable_fsm_ = false; state_ = RESET; + beam_broken_ = false; + poke_initiated_once_ = false; + set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); + set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); + set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); + set_vac_setup_time_uss(DEFAULT_VAC_SETUP_TIME_US); + set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); } void PokeManager::pause() // Needed for odor changes/refills @@ -42,6 +126,13 @@ void PokeManager::restart() // Needed for odor changes/refills disable_fsm_ = false; poke_detected_ = false; state_ = RESET; + beam_broken_ = false; + poke_initiated_once_ = false; +} + +void PokeManager::update_next_odor(int next_odor) // Update the index of the next odor +{ + next_odor_index_ = next_odor; } void PokeManager::update() @@ -56,6 +147,9 @@ void PokeManager::update() deenergize_all_valves(); } + // check for poke + check_poke_status(); + state_t next_state{state_}; // initialize next-state to current state. // Handling next-state logic. @@ -65,7 +159,7 @@ void PokeManager::update() next_state = ODOR_SETUP; break; case ODOR_SETUP: - if (state_duration_us() >= VACUUM_CLOSE_TIME_US) + if (state_duration_us() >= vacuum_close_time_us_) next_state = ODOR_DISPENSING_TO_EXHAUST; break; case ODOR_DISPENSING_TO_EXHAUST: @@ -73,19 +167,19 @@ void PokeManager::update() next_state = ODOR_DELIVERY_TO_FINAL_VALVE; break; case ODOR_DELIVERY_TO_FINAL_VALVE: - if (state_duration_us() >= ODOR_DELIVERY_TIME_US) + if (state_duration_us() >= odor_delivery_time_us_) next_state = ODOR_PRECLEAN; break; case ODOR_PRECLEAN: - if (state_duration_us() >= ODOR_TRANSITION_TIME_US) + if (state_duration_us() >= odor_transition_time_) next_state = VAC_START; break; case VAC_START: - if (state_duration_us() >= VAC_SETUP_TIME_US) + if (state_duration_us() >= vac_setup_time_us_) next_state = ODOR_PURGE; break; case ODOR_PURGE: - if (state_duration_us() >= FINAL_VALVE_ENERGIZED_TIME_US) + if (state_duration_us() >= final_valve_energized_time_us_) next_state = ODOR_SETUP; break; default: @@ -136,9 +230,10 @@ void PokeManager::update() { // Energize the final valve final_valve_.energize(); - ++odor_valve_index_; - if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves - odor_valve_index_ = 0; + odor_valve_index_ = next_odor_index_; //DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR + // ++odor_valve_index_; + // if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves + // odor_valve_index_ = 0; printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index } From 7298d582d053c0c990d8d231628523ea0d33d250 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Thu, 29 May 2025 09:17:42 -0700 Subject: [PATCH 09/39] Started implementing harp registers and protocol functionality --- firmware/inc/delphi_controller_app.h | 12 +- firmware/src/delphi_controller_app.cpp | 16 +-- firmware/src/main.cpp | 12 +- firmware/src/poke_manager.cpp | 189 +++++++++++++------------ 4 files changed, 114 insertions(+), 115 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index aea50d6..6bc9db7 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -84,12 +84,12 @@ struct app_regs_t uint8_t AuxGPIOFallingInputs; // Raw state of which inputs fell (could be multiple) // Poke Manager app "registers" here. - volatile uint32_t PokeDometer; //Read only -- number of pokes the mouse has done since boot - volatile uint8_t PauseFSM; // Write only -- 1 = FSM disabled , 0 = FSM enabled (default) - volatile uint8_t RestartFSM; // Write only -- 1 = FSM enabled (default), 0 = FSM disabled - volatile uint8_t ResetFSM; // Write only -- force FSM into reset state when passed a value of 1, default = 0 - volatile uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered - volatile uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued + uint32_t PokeDometer; //Read only -- number of pokes the mouse has done since boot + uint8_t PauseFSM; // Write only -- 1 = FSM disabled , 0 = FSM enabled (default) + uint8_t RestartFSM; // Write only -- 1 = FSM enabled (default), 0 = FSM disabled + uint8_t ResetFSM; // Write only -- force FSM into reset state when passed a value of 1, default = 0 + uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered + uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued DelphiTaskConfig DelphiTaskConfig; // write and read uint8_t PokePin; // write only }; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 425d1cf..180c9ec 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -16,8 +16,6 @@ void (&write_aux_gpio_fall_event)(msg_t& msg) = HarpCore::write_reg_generic; void (&read_aux_gpio_rise_input)(uint8_t reg_address) = HarpCore::read_reg_generic; void (&read_aux_gpio_fall_input)(uint8_t reg_address) = HarpCore::read_reg_generic; -void (&write_aux_gpio_rise_input)(msg_t& msg) = HarpCore::write_reg_generic; -void (&write_aux_gpio_fall_input)(msg_t& msg) = HarpCore::write_reg_generic; /// Create Hit-and-Hold Valve Drivers. @@ -118,8 +116,8 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_aux_gpio_rise_event, write_aux_gpio_rise_event}, {read_aux_gpio_fall_event, write_aux_gpio_fall_event}, - {read_aux_gpio_rise_input, write_aux_gpio_rise_input}, //this handler function was missing - {read_aux_gpio_fall_input, write_aux_gpio_fall_input}, //this handler function was missing + {read_aux_gpio_rise_input, HarpCore::write_to_read_only_reg_error}, + {read_aux_gpio_fall_input, HarpCore::write_to_read_only_reg_error}, // Poke manager handler functions {read_pokedometer, HarpCore::write_to_read_only_reg_error}, // read only @@ -205,7 +203,7 @@ void read_current_odor(uint8_t reg_address) void write_restart_fsm(msg_t& msg) { // Registered value is updated - // HarpCore::copy_msg_payload_to_register(msg); //upfates the register + HarpCore::copy_msg_payload_to_register(msg); //upfates the register if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting poke_manager.restart(); @@ -216,19 +214,21 @@ void write_restart_fsm(msg_t& msg) void write_pause_fsm(msg_t& msg) { // Registered value is updated - // HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + HarpCore::copy_msg_payload_to_register(msg); //upfates the register + if (apps_regs.PauseFSM == 1) //FIX THIS EVERYWHERE poke_manager.pause(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); + + apps_regs.PauseFSM == 0; } void write_reset_poke_manager_fsm(msg_t& msg) { // Registered value is updated - // HarpCore::copy_msg_payload_to_register(msg); //upfates the register + HarpCore::copy_msg_payload_to_register(msg); //upfates the register if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting poke_manager.reset(); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 1f56f67..43dfc4e 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -27,12 +27,13 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, reset_app); // Inititalize final and vac valve pins -- Does this go here? -ValveDriver final_valve(VALVE_PIN_BASE); -ValveDriver vac_valve(VALVE_PIN_BASE + 1); -etl::vector odor_valves; +ValveDriver& final_valve = valve_drivers[0]; // add to config +ValveDriver& vac_valve = valve_drivers[1]; +ValveDriver& odor_valves[] = &valve_drivers[2]; // refer to rest of vale drivers as valves for odoor delivery + // i.e: odor_valves[app_regs.NextOdor].energize(); // check out harp core // Pass valves into the poke manager constructor -PokeManager poke_manager(final_valve, vac_valve, odor_valves); +PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); // Core0 main. int main() @@ -41,9 +42,6 @@ int main() HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); - // Odor valves vector -- set valves - for (int i = VALVE_PIN_BASE + 2; i < VALVE_PIN_BASE + 2 + NUM_ODOR_VALVES; i++){ - odor_valves.emplace_back(i); } #ifdef DEBUG stdio_uart_init_full(uart0, 921600, UART_TX_PIN, -1); // use uart1 tx only. diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 5451321..a882b49 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,6 +1,6 @@ #include -PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, etl::vector& odor_valves) +PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, ValveDriver& odor_valves[]) : state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, beam_broken_{false}, poke_initiated_once_{false} { // Nothing else to do! @@ -115,13 +115,13 @@ void PokeManager::reset() set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); } -void PokeManager::pause() // Needed for odor changes/refills +void PokeManager::pause() // Needed for odor changes/refills -- Change to enable { disable_fsm_ = true; deenergize_all_valves(); // deenergize all valves } -void PokeManager::restart() // Needed for odor changes/refills +void PokeManager::restart() // Needed for odor changes/refills -- change to disable { disable_fsm_ = false; poke_detected_ = false; @@ -137,109 +137,110 @@ void PokeManager::update_next_odor(int next_odor) // Update the index of the nex void PokeManager::update() { - //enabled by default, but if disabled, pause the FSM - if (disable_fsm_ == false) + //enabled by default, but if disabled, bail early + if (disable_fsm_ == true) + return; + + //initialize RESET state + if (state_ == RESET) //need to query state_ for initital + { + // state_entry_time_us_ = time_us_32(); + deenergize_all_valves(); + } + + // check for poke + check_poke_status(); + + state_t next_state{state_}; // initialize next-state to current state. + + // Handling next-state logic. + switch (state_) + { + case RESET: + next_state = ODOR_SETUP; + break; + case ODOR_SETUP: + if (state_duration_us() >= vacuum_close_time_us_) + next_state = ODOR_DISPENSING_TO_EXHAUST; + break; + case ODOR_DISPENSING_TO_EXHAUST: + if (poke_detected_) + next_state = ODOR_DELIVERY_TO_FINAL_VALVE; + break; + case ODOR_DELIVERY_TO_FINAL_VALVE: + if (state_duration_us() >= odor_delivery_time_us_) + next_state = ODOR_PRECLEAN; + break; + case ODOR_PRECLEAN: + if (state_duration_us() >= odor_transition_time_) + next_state = VAC_START; + break; + case VAC_START: + if (state_duration_us() >= vac_setup_time_us_) + next_state = ODOR_PURGE; + break; + case ODOR_PURGE: + if (state_duration_us() >= final_valve_energized_time_us_) + next_state = ODOR_SETUP; + break; + default: + break; + } + + // Update how long we've been in the new state. + if (state_ != next_state) { - //initialize RESET state - if (state_ == RESET) //need to query state_ for initital + printf("State transition %d -> %d\r\n", state_, next_state); + printf("State transition time %i\r\n", state_duration_us()); + state_entry_time_us_ = time_us_32(); + + // Next state logic should only be assessed if there is a state transition + if (next_state == ODOR_SETUP) { - // state_entry_time_us_ = time_us_32(); + // Energize one of the odor valves deenergize_all_valves(); + odor_valves_[odor_valve_index_].energize(); } - // check for poke - check_poke_status(); + if (next_state == ODOR_DISPENSING_TO_EXHAUST) + { + // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke + } - state_t next_state{state_}; // initialize next-state to current state. + if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) + { + final_valve_.energize(); + ++poke_count_; + printf("Number of pokes = %i\r\n", poke_count_); + poke_detected_ = false; + } - // Handling next-state logic. - switch (state_) + if (next_state == ODOR_PRECLEAN) { - case RESET: - next_state = ODOR_SETUP; - break; - case ODOR_SETUP: - if (state_duration_us() >= vacuum_close_time_us_) - next_state = ODOR_DISPENSING_TO_EXHAUST; - break; - case ODOR_DISPENSING_TO_EXHAUST: - if (poke_detected_) - next_state = ODOR_DELIVERY_TO_FINAL_VALVE; - break; - case ODOR_DELIVERY_TO_FINAL_VALVE: - if (state_duration_us() >= odor_delivery_time_us_) - next_state = ODOR_PRECLEAN; - break; - case ODOR_PRECLEAN: - if (state_duration_us() >= odor_transition_time_) - next_state = VAC_START; - break; - case VAC_START: - if (state_duration_us() >= vac_setup_time_us_) - next_state = ODOR_PURGE; - break; - case ODOR_PURGE: - if (state_duration_us() >= final_valve_energized_time_us_) - next_state = ODOR_SETUP; - break; - default: - break; + final_valve_.deenergize(); } - // Update how long we've been in the new state. - if (state_ != next_state) + if (next_state == VAC_START) { - printf("State transition %d -> %d\r\n", state_, next_state); - printf("State transition time %i\r\n", state_duration_us()); - state_entry_time_us_ = time_us_32(); - - // Next state logic should only be assessed if there is a state transition - if (next_state == ODOR_SETUP) - { - // Energize one of the odor valves - deenergize_all_valves(); - odor_valves_[odor_valve_index_].energize(); - } - - if (next_state == ODOR_DISPENSING_TO_EXHAUST) - { - // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke - } - - if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) - { - final_valve_.energize(); - ++poke_count_; - printf("Number of pokes = %i\r\n", poke_count_); - poke_detected_ = false; - } - - if (next_state == ODOR_PRECLEAN) - { - final_valve_.deenergize(); - } - - if (next_state == VAC_START) - { - // Deenergize odor valve - odor_valves_[odor_valve_index_].deenergize(); - vac_valve_.energize(); - } - - if (next_state == ODOR_PURGE) - { - // Energize the final valve - final_valve_.energize(); - odor_valve_index_ = next_odor_index_; //DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR - // ++odor_valve_index_; - // if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves - // odor_valve_index_ = 0; - - printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index - } + // Deenergize odor valve + odor_valves_[odor_valve_index_].deenergize(); + vac_valve_.energize(); } - // Update state: - state_ = next_state; + if (next_state == ODOR_PURGE) + { + // Energize the final valve + final_valve_.energize(); + odor_valve_index_ = next_odor_index_; //DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR + // ++odor_valve_index_; + // if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves + // odor_valve_index_ = 0; + + printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index + } } + // Update state: + state_ = next_state; + + } From db9348828760dc71a5cceb8015230353d2aca8db Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Sun, 8 Jun 2025 17:52:11 -0700 Subject: [PATCH 10/39] get project compiling; fix harp write fns --- firmware/CMakeLists.txt | 11 ++++++----- firmware/inc/delphi_controller_app.h | 5 +++-- firmware/inc/poke_manager.h | 16 ++++++++++------ firmware/src/delphi_controller_app.cpp | 26 ++++++++++++++------------ firmware/src/main.cpp | 4 +--- firmware/src/poke_manager.cpp | 26 ++++++++++++++++---------- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index d25ae2c..6a4664a 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -12,7 +12,7 @@ add_definitions(-DUSBD_PRODUCT="delphi-controller") include(${PICO_SDK_PATH}/pico_sdk_init.cmake) # Use modern conventions like std::invoke -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 23) project(delphi-controller) @@ -23,8 +23,8 @@ add_subdirectory(lib/etl build/etl) # etl library path add_executable(${PROJECT_NAME} - main.cpp - delphi_controller_app.cpp + src/main.cpp + src/delphi_controller_app.cpp ) add_library(valve_driver @@ -39,8 +39,9 @@ include_directories(inc) target_link_libraries(valve_driver rp2040_pwm pico_stdlib) target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) -target_link_libraries(${PROJECT_NAME} harp_core harp_c_app rp2040_pwm - valve_driver harp_sync pico_stdlib etl::etl) +target_link_libraries(${PROJECT_NAME} + harp_core harp_c_app rp2040_pwm poke_manager valve_driver harp_sync + pico_stdlib etl::etl) pico_add_extra_outputs(${PROJECT_NAME}) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 6bc9db7..313b328 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -27,6 +27,7 @@ extern RegFnPair reg_handler_fns[APP_REG_COUNT]; extern HarpCApp& app; extern ValveDriver valve_drivers[NUM_VALVES]; +extern PokeManager poke_manager; extern uint8_t old_aux_gpio_inputs; @@ -42,7 +43,7 @@ struct ValveConfig // Delphi task timing params #pragma pack(push, 1) -struct DelphiTaskConfig +struct delphi_task_config_t { uint32_t vacuum_close_time_us; uint32_t odor_delivery_time_us; @@ -90,7 +91,7 @@ struct app_regs_t uint8_t ResetFSM; // Write only -- force FSM into reset state when passed a value of 1, default = 0 uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued - DelphiTaskConfig DelphiTaskConfig; // write and read + delphi_task_config_t DelphiTaskConfig; // write and read uint8_t PokePin; // write only }; #pragma pack(pop) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 2bdb290..e49f713 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -27,9 +27,10 @@ class PokeManager // Declare constructor PokeManager( - ValveDriver& final_valve, //Pass by reference (work of this org. object) - ValveDriver& vac_valve, - etl::vector& odor_valves + ValveDriver& final_valve, //Pass by reference (work of this org. object) + ValveDriver& vac_valve, + ValveDriver (&odor_valves)[], + size_t num_odor_valves ); ~PokeManager(); // desctructor @@ -42,7 +43,7 @@ class PokeManager void restart(); // restart fsm - void update_next_odor(int next_odor); + void update_next_odor(uint32_t next_odor); void set_vacuum_close_time_us(uint32_t vacuum_close_time_us); @@ -107,10 +108,11 @@ class PokeManager state_t state_; uint32_t state_entry_time_us_; uint32_t poke_start_time_us_; - + uint8_t poke_pin_; int odor_valve_index_; int next_odor_index_; + size_t poke_count_; bool poke_detected_; bool disable_fsm_; @@ -118,7 +120,9 @@ class PokeManager bool poke_initiated_once_; //Only trigger the FSM on 1 poke ValveDriver& vac_valve_; ValveDriver& final_valve_; - etl::vector& odor_valves_; + + ValveDriver (&odor_valves_)[]; + size_t num_odor_valves_; uint32_t vacuum_close_time_us_; uint32_t odor_delivery_time_us_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 180c9ec..aff3479 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -82,7 +82,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.ResetFSM, sizeof(app_regs.ResetFSM), U8}, {(uint8_t*)&app_regs.CurrentOdor, sizeof(app_regs.CurrentOdor), U8}, {(uint8_t*)&app_regs.NextOdor, sizeof(app_regs.NextOdor), U8}, - {(uint8_t*)&app_regs.DelphiTaskConfig, sizeof(DelphiTaskConfig), U8}, + {(uint8_t*)&app_regs.DelphiTaskConfig, sizeof(delphi_task_config_t), U8}, {(uint8_t*)&app_regs.PokePin, sizeof(app_regs.PokePin), U8}, }; @@ -132,9 +132,10 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] void read_delphi_task_config(uint8_t reg_address) { - DelphiTaskConfig& delphi_cfg = app_regs.DelphiTaskConfig + delphi_task_config_t& delphi_cfg = app_regs.DelphiTaskConfig; // Update Harp App registers with Poke Manager class contents. + // FIXME: since they are the same data type, we can just use assignment. delphi_cfg.vacuum_close_time_us = poke_manager.get_vacuum_close_time(); delphi_cfg.odor_delivery_time_us = poke_manager.get_odor_delivery_time(); delphi_cfg.odor_transition_time_us = poke_manager.get_odor_transition_time(); @@ -150,7 +151,7 @@ void write_poke_pin(msg_t& msg) HarpCore::copy_msg_payload_to_register(msg); // Apply the configuration. - poke_manager.set_poke_pin((uint8_t) msg.payload); + poke_manager.set_poke_pin(app_regs.PokePin); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); @@ -159,7 +160,7 @@ void write_poke_pin(msg_t& msg) void write_delphi_task_config(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - DelphiTaskConfig& delphi_cfg = app_regs.DelphiTaskConfig + delphi_task_config_t& delphi_cfg = app_regs.DelphiTaskConfig; // Apply the configuration. poke_manager.set_vacuum_close_time_us(delphi_cfg.vacuum_close_time_us); @@ -176,7 +177,7 @@ void write_next_odor(msg_t& msg) { // Registered value is updated HarpCore::copy_msg_payload_to_register(msg); //updates the register - poke_manager.update_next_odor((int) msg.payload); //write next odor + poke_manager.update_next_odor(app_regs.NextOdor); //write next odor if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); @@ -203,8 +204,8 @@ void read_current_odor(uint8_t reg_address) void write_restart_fsm(msg_t& msg) { // Registered value is updated - HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + HarpCore::copy_msg_payload_to_register(msg); //updates the register + if (app_regs.RestartFSM) poke_manager.restart(); if (!HarpCore::is_muted()) @@ -215,13 +216,14 @@ void write_pause_fsm(msg_t& msg) { // Registered value is updated HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if (apps_regs.PauseFSM == 1) //FIX THIS EVERYWHERE + if (app_regs.PauseFSM) poke_manager.pause(); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(WRITE, msg.header.address); - - apps_regs.PauseFSM == 0; + { + HarpCore::send_harp_reply(WRITE, msg.header.address); + app_regs.PauseFSM == 0; + } } @@ -229,7 +231,7 @@ void write_reset_poke_manager_fsm(msg_t& msg) { // Registered value is updated HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if ((unit8_t) msg.payload == 1) //just extracting the payload of the register and not actually updating it // explict casting + if (app_regs.ResetFSM) poke_manager.reset(); if (!HarpCore::is_muted()) diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 43dfc4e..d4dc4eb 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -29,7 +29,7 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, // Inititalize final and vac valve pins -- Does this go here? ValveDriver& final_valve = valve_drivers[0]; // add to config ValveDriver& vac_valve = valve_drivers[1]; -ValveDriver& odor_valves[] = &valve_drivers[2]; // refer to rest of vale drivers as valves for odoor delivery +ValveDriver (&odor_valves)[] = (&valve_drivers)[2]; // refer to rest of valve drivers as valves for odoor delivery // i.e: odor_valves[app_regs.NextOdor].energize(); // check out harp core // Pass valves into the poke manager constructor @@ -41,8 +41,6 @@ int main() // Init Synchronizer. HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); - -} #ifdef DEBUG stdio_uart_init_full(uart0, 921600, UART_TX_PIN, -1); // use uart1 tx only. printf("Hello, from an RP2040!\r\n"); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index a882b49..d9332fd 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -1,7 +1,13 @@ #include -PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, ValveDriver& odor_valves[]) -: state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, beam_broken_{false}, poke_initiated_once_{false} +PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, + ValveDriver (&odor_valves)[], size_t num_odor_valves) +: final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, +num_odor_valves_{num_odor_valves}, +state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, +odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, +poke_detected_{false}, +beam_broken_{false}, poke_initiated_once_{false} { // Nothing else to do! } @@ -28,6 +34,11 @@ void PokeManager::deenergize_all_valves() } // Functions to configure Delphi Task/update it +void PokeManager::update_next_odor(uint32_t next_odor) +{ + next_odor_index_ = next_odor; +} + void PokeManager::set_vacuum_close_time_us(uint32_t vacuum_close_time_us) { vacuum_close_time_us_ = vacuum_close_time_us; @@ -43,7 +54,7 @@ void PokeManager::set_odor_transition_time_us(uint32_t odor_transition_time_us) odor_transition_time_ = odor_transition_time_us; } -void PokeManager::set_vac_setup_time_uss(uint32_t vac_setup_time_us) +void PokeManager::set_vac_setup_time_us(uint32_t vac_setup_time_us) { vac_setup_time_us_ = vac_setup_time_us; } @@ -85,7 +96,7 @@ void PokeManager::check_poke_status() // Check duration since beam break/poke if (gpio_get(poke_pin_) == 0 && beam_broken_ == true) { - gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken + //gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken if ((time_us_32() - poke_start_time_us_) >= min_poke_time_us_ && poke_initiated_once_ == false){ //Poke was detected! @@ -111,7 +122,7 @@ void PokeManager::reset() set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); - set_vac_setup_time_uss(DEFAULT_VAC_SETUP_TIME_US); + set_vac_setup_time_us(DEFAULT_VAC_SETUP_TIME_US); set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); } @@ -130,11 +141,6 @@ void PokeManager::restart() // Needed for odor changes/refills -- change to disa poke_initiated_once_ = false; } -void PokeManager::update_next_odor(int next_odor) // Update the index of the next odor -{ - next_odor_index_ = next_odor; -} - void PokeManager::update() { //enabled by default, but if disabled, bail early From 7dad877ec749213ccab98de3d8ed94b14d8896c9 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Sun, 8 Jun 2025 18:54:07 -0700 Subject: [PATCH 11/39] draft device.yml --- device.yml | 203 ++++++++++++++++++++++++- firmware/src/delphi_controller_app.cpp | 6 +- firmware/src/poke_manager.cpp | 3 - 3 files changed, 204 insertions(+), 8 deletions(-) diff --git a/device.yml b/device.yml index b93c2c4..c3c51ad 100644 --- a/device.yml +++ b/device.yml @@ -1,7 +1,206 @@ %YAML 1.1 --- # yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/device.json -device: ValveController -whoAmI: 1406 +device: DelphiController +whoAmI: 1407 firmwareVersion: "0.0.0" hardwareTargets: "1.0.0" +registers: + ValveState: + address: 32 + type: U16 + maskType: ValveMask + access: Write + description: "Set the enabled/disabled state (enabled = 1) of all valves" + ValvesSet: + address: 33 + type: U16 + maskType: ValveMask + access: Write + description: "Write a 1 to any bit to enable the corresponding valve." + ValvesClear: + address: 34 + type: U16 + maskType: ValveMask + access: Write + description: "Write a 1 to any bit to disable the corresponding valve." + ValveConfig0: &valveConfig + address: 35 + type: U8 + length: 12 + access: Write + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve0." + ValveConfig1: + <<: *valveConfig + address: 36 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve1." + ValveConfig2: + <<: *valveConfig + address: 37 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve2." + ValveConfig3: + <<: *valveConfig + address: 38 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve3." + ValveConfig4: + <<: *valveConfig + address: 39 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve4." + ValveConfig5: + <<: *valveConfig + address: 40 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve5." + ValveConfig6: + <<: *valveConfig + address: 41 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve6." + ValveConfig7: + <<: *valveConfig + address: 42 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve7." + ValveConfig8: + <<: *valveConfig + address: 43 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve8." + ValveConfig9: + <<: *valveConfig + address: 44 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve9." + ValveConfig10: + <<: *valveConfig + address: 45 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve10." + ValveConfig11: + <<: *valveConfig + address: 46 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve11." + ValveConfig12: + <<: *valveConfig + address: 47 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve12." + ValveConfig13: + <<: *valveConfig + address: 48 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve13." + ValveConfig14: + <<: *valveConfig + address: 49 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve14." + ValveConfig15: + <<: *valveConfig + address: 50 + description: "the hit duty cycle, hold duty cycle, and hit duration for Valve15." + AuxGPIODir: + address: 51 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "Specify each auxiliary GPIO pin as an input (0) or output (1)." + AuxGPIOState: + address: 52 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "Set the state (one or off) of any auxiliary GPIO pins specified as outputs." + AuxGPIOSet: + address: 52 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "When writing a 1 to any bit, turn on the specified auxiliary GPIO pins specified as outputs." + AuxGPIOClear: + address: 53 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "When writing a 1 to any bit, Turn off the specified auxiliary GPIO pins specified as outputs." + AuxGPIOInputRiseEvent: + address: 54 + type: U8 + maskType: AuxGPIOMask + access: Event + description: "" + AuxGPIOInputFallEvent: + address: 55 + type: U8 + maskType: AuxGPIOMask + access: Event + description: "" + AuxGPIORisingInputs: + address: 56 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "" + AuxGPIOFallingInputs: + address: 57 + type: U8 + maskType: AuxGPIOMask + access: Write + description: "" + + PokeDometer: + address: 58 + type: U32 + access: Read + description: "number of mouse pokes since boot." + FSMState: + address: 0 # FIXME + type: U32 + access: Write + description: "enable or disable the poke handling state machine." + ResetFSM: + address: 0 # FIXME + type: U8 + access: Write + description: "Reset the poke handling state machine." + ForceFSM: + address: 0 # FIXME + type: U8 + access: Write + description: "Force the poke hanlding state machine to iterate as if handling a mouse poke" + CurrentOdorIndex: + address: 0 # FIXME + type: U8 + access: Write + description: "The current odor being dispensed to the odor port. Must be specified before enabling the state machine." + NextOdorIndex: + address: 0 # FIXME + type: U8 + access: Write + description: "The next odor to be dispensed to the odor port after." + # TODO: poke timing config + # TODO: poke pin + + +groupMasks: + ValveMask: + description: "Valve that can be configured/enabled/disabled" + values: + Valve0: 0x0 + Valve1: 0x1 + Valve2: 0x2 + Valve3: 0x3 + Valve4: 0x4 + Valve5: 0x5 + Valve6: 0x6 + Valve7: 0x7 + Valve8: 0x8 + Valve9: 0x9 + Valve10: 0xA + Valve11: 0xB + Valve12: 0xC + Valve13: 0xD + Valve14: 0xE + Valve15: 0xF + AuxGPIOMask: + description: "Auxiliary GPIO pin." + values: + AuxGPIO0: 0x0 + AuxGPIO1: 0x1 + AuxGPIO2: 0x2 + AuxGPIO3: 0x3 + AuxGPIO4: 0x4 + AuxGPIO5: 0x5 + AuxGPIO6: 0x6 + AuxGPIO7: 0x7 diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index aff3479..4a224ba 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -403,9 +403,6 @@ void write_aux_gpio_clear(msg_t& msg) void update_app_state() // Called when app.run() is called -- add poke detection here { - // Update poke manager FSM - poke_manager.update(); - // Update valve controller state machines. for (auto& valve_driver: valve_drivers) valve_driver.update(); @@ -424,6 +421,9 @@ void update_app_state() // Called when app.run() is called -- add poke detection HarpCore::send_harp_reply(EVENT, AUX_GPIO_RISING_INPUTS_ADDRESS); if (app_regs.AuxGPIOInputFallEvent & app_regs.AuxGPIOFallingInputs) HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); + + // Update poke manager FSM + poke_manager.update(); } void reset_app() diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index d9332fd..5041632 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -238,9 +238,6 @@ void PokeManager::update() // Energize the final valve final_valve_.energize(); odor_valve_index_ = next_odor_index_; //DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR - // ++odor_valve_index_; - // if (odor_valve_index_ == NUM_ODOR_VALVES) // test logic for iterating through valves - // odor_valve_index_ = 0; printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index } From f4764ebb29b6b03f8d83fe3c8ccc3a75693b43ee Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Mon, 9 Jun 2025 14:13:27 -0700 Subject: [PATCH 12/39] update device.yaml --- device.yml | 134 +++++++++++++++++++++---------- software/pyharp/app_registers.py | 16 ++++ 2 files changed, 106 insertions(+), 44 deletions(-) diff --git a/device.yml b/device.yml index c3c51ad..06c6756 100644 --- a/device.yml +++ b/device.yml @@ -29,67 +29,67 @@ registers: type: U8 length: 12 access: Write - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve0." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve0." ValveConfig1: <<: *valveConfig address: 36 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve1." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve1." ValveConfig2: <<: *valveConfig address: 37 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve2." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve2." ValveConfig3: <<: *valveConfig address: 38 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve3." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve3." ValveConfig4: <<: *valveConfig address: 39 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve4." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve4." ValveConfig5: <<: *valveConfig address: 40 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve5." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve5." ValveConfig6: <<: *valveConfig address: 41 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve6." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve6." ValveConfig7: <<: *valveConfig address: 42 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve7." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve7." ValveConfig8: <<: *valveConfig address: 43 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve8." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve8." ValveConfig9: <<: *valveConfig address: 44 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve9." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve9." ValveConfig10: <<: *valveConfig address: 45 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve10." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve10." ValveConfig11: <<: *valveConfig address: 46 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve11." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve11." ValveConfig12: <<: *valveConfig address: 47 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve12." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve12." ValveConfig13: <<: *valveConfig address: 48 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve13." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve13." ValveConfig14: <<: *valveConfig address: 49 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve14." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve14." ValveConfig15: <<: *valveConfig address: 50 - description: "the hit duty cycle, hold duty cycle, and hit duration for Valve15." + description: "the hit duty cycle (float: 0 - 1.0), hold duty cycle (float: 0 - 1.0), and hit duration in microseconds (U32) for Valve15." AuxGPIODir: address: 51 type: U8 @@ -103,75 +103,110 @@ registers: access: Write description: "Set the state (one or off) of any auxiliary GPIO pins specified as outputs." AuxGPIOSet: - address: 52 + address: 53 type: U8 maskType: AuxGPIOMask access: Write description: "When writing a 1 to any bit, turn on the specified auxiliary GPIO pins specified as outputs." AuxGPIOClear: - address: 53 + address: 54 type: U8 maskType: AuxGPIOMask access: Write description: "When writing a 1 to any bit, Turn off the specified auxiliary GPIO pins specified as outputs." AuxGPIOInputRiseEvent: - address: 54 + address: 55 type: U8 maskType: AuxGPIOMask access: Event description: "" AuxGPIOInputFallEvent: - address: 55 + address: 56 type: U8 maskType: AuxGPIOMask access: Event description: "" AuxGPIORisingInputs: - address: 56 + address: 57 type: U8 maskType: AuxGPIOMask access: Write description: "" AuxGPIOFallingInputs: - address: 57 + address: 58 type: U8 maskType: AuxGPIOMask access: Write description: "" - - PokeDometer: - address: 58 + PokePinMask: + address: 59 + type: U8 + access: Write + maskType: PokePortMask + description: "which poke ports are active." + PokeEvent: + address: 60 + type: U8 + access: Event + maskType: PokePortMask + description: "One or more pokes occurred at the specified index." + PokeDometers: + address: 61 type: U32 + length: 8 access: Read - description: "number of mouse pokes since boot." + maskType: PokePortMask + description: "number of mouse pokes per port since boot or reset." FSMState: - address: 0 # FIXME + address: 62 type: U32 access: Write - description: "enable or disable the poke handling state machine." - ResetFSM: - address: 0 # FIXME - type: U8 - access: Write - description: "Reset the poke handling state machine." + description: "Enable (1) (aka reset) or Disable (0) the poke handling state machine. Note that CurrentOdorIndex must be specified first. Disabling and then enabling a previously-enabled FSM will reset it to its starting state." ForceFSM: - address: 0 # FIXME + address: 63 type: U8 access: Write - description: "Force the poke hanlding state machine to iterate as if handling a mouse poke" + description: "Force the poke handling state machine to iterate as if handling a mouse poke. PokeDometers are not incremented." CurrentOdorIndex: - address: 0 # FIXME - type: U8 + address: 64 + type: S8 access: Write description: "The current odor being dispensed to the odor port. Must be specified before enabling the state machine." NextOdorIndex: - address: 0 # FIXME - type: U8 + address: 65 + type: S8 access: Write - description: "The next odor to be dispensed to the odor port after." - # TODO: poke timing config - # TODO: poke pin - + description: "The next odor to be dispensed to the odor port after. Must be specified before the Odor Delivery FSM finishes a cycle." + VacuumCloseTimeUS: + address: 66 + type: U32 + access: Write + description: "Time alotted (in microseconds) for the vacuum valve to close." + OdorDeliveryTimeUS: + address: 67 + type: U32 + access: Write + description: "Time alotted (in microseconds) for the odor delivery state." + OdorTransitionTimeUS: + address: 68 + type: U32 + access: Write + description: "Time alotted (in microseconds) before the vacuum turns on to remove the current odor." + VacuumSetupTimeUS: + address: 69 + type: U32 + access: Write + description: "Time alotted (in microseconds) for the vacuum to open." + FinalValveEnergizedTimeUS: + address: 70 + type: U32 + access: Write + description: "Time alotted (in microseconds) for the final valve to open and remain on." + MinimumPokeTimeUS: + address: 71 + type: U32 + access: Write + description: "Minimum time (in microseconds) necessary for a mouse poke port beam to be broken before being interpretted as a poke." groupMasks: ValveMask: @@ -194,7 +229,7 @@ groupMasks: Valve14: 0xE Valve15: 0xF AuxGPIOMask: - description: "Auxiliary GPIO pin." + description: "Auxiliary GPIO index." values: AuxGPIO0: 0x0 AuxGPIO1: 0x1 @@ -204,3 +239,14 @@ groupMasks: AuxGPIO5: 0x5 AuxGPIO6: 0x6 AuxGPIO7: 0x7 + PokePortMask: + description: "Poke Port index." + values: + PokePort0: 0x0 + PokePort1: 0x1 + PokePort2: 0x2 + PokePort3: 0x3 + PokePort4: 0x4 + PokePort5: 0x5 + PokePort6: 0x6 + PokePort7: 0x7 diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index db935f7..1c443b2 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -32,3 +32,19 @@ class AppRegs(IntEnum): AuxGPIOInputFallEvent = 56 AuxGPIOInputRisingInputs = 57 AuxGPIOFallingInputs = 58 + + +class DelphiAppRegs(AppRegs): + PokePinMask = 59 + PokeEvent = 60 + PokeDometers = 61 + FSMState = 62 + ForceFSM = 63 + CurrentOdorIndex = 64 + NextOdorIndex = 65 + VacuumCloseTimeUS = 66 + OdorDeliveryTimeUS = 67 + OdorTransitionTimeUS = 68 + VacuumSetupTimeUS = 69 + FinalValveEnergizedTimeUS = 70 + MinimumPokeTimeUS = 71 From ea8a6475022a1fb224b2fe9a0479f919070f76b5 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Mon, 9 Jun 2025 16:02:16 -0700 Subject: [PATCH 13/39] inline short setters; fix pass sub-array-by-reference --- firmware/inc/poke_manager.h | 28 ++++++++++------ firmware/src/delphi_controller_app.cpp | 2 +- firmware/src/main.cpp | 12 +++---- firmware/src/poke_manager.cpp | 46 ++------------------------ software/pyharp/app_registers.py | 7 +++- 5 files changed, 33 insertions(+), 62 deletions(-) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index e49f713..300348b 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -43,21 +43,29 @@ class PokeManager void restart(); // restart fsm - void update_next_odor(uint32_t next_odor); + inline void update_next_odor(uint32_t next_odor) + {next_odor_index_ = next_odor;} - void set_vacuum_close_time_us(uint32_t vacuum_close_time_us); + inline void set_vacuum_close_time_us(uint32_t vacuum_close_time_us) + {vacuum_close_time_us_ = vacuum_close_time_us;} - void set_odor_delivery_time_us(uint32_t odor_delivery_time_us); + inline void set_odor_delivery_time_us(uint32_t odor_delivery_time_us) + {odor_delivery_time_us_ = odor_delivery_time_us;} - void set_odor_transition_time_us(uint32_t odor_transition_time_us); + void set_odor_transition_time_us(uint32_t odor_transition_time_us) + {odor_transition_time_us_ = odor_transition_time_us;} - void set_vac_setup_time_us(uint32_t vac_setup_time_us); + inline void set_vac_setup_time_us(uint32_t vac_setup_time_us) + {vac_setup_time_us_ = vac_setup_time_us;} - void set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us); + inline void set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us) + {final_valve_energized_time_us_ = final_valve_energized_time_us;} - void set_min_poke_time_us(uint32_t min_poke_time_us); + inline void set_min_poke_time_us(uint32_t min_poke_time_us) + {min_poke_time_us_ = min_poke_time_us;} - void set_poke_pin(uint8_t pin); + inline void set_poke_pin(uint8_t pin) + {poke_pin_ = pin;} /** * \brief true if a poke was detected. Inline replaces function with code @@ -85,7 +93,7 @@ class PokeManager {return odor_delivery_time_us_;} inline uint32_t get_odor_transition_time() const - {return odor_transition_time_;} + {return odor_transition_time_us_;} inline uint32_t get_vac_setup_time() const {return vac_setup_time_us_;} @@ -126,7 +134,7 @@ class PokeManager uint32_t vacuum_close_time_us_; uint32_t odor_delivery_time_us_; - uint32_t odor_transition_time_; + uint32_t odor_transition_time_us_; uint32_t vac_setup_time_us_; uint32_t final_valve_energized_time_us_; uint32_t min_poke_time_us_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 4a224ba..a622e55 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -423,7 +423,7 @@ void update_app_state() // Called when app.run() is called -- add poke detection HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); // Update poke manager FSM - poke_manager.update(); + //poke_manager.update(); } void reset_app() diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index d4dc4eb..137a042 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -12,7 +12,6 @@ #include // for uart printing #include // for printf #endif - // Create Harp App. HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, HW_VERSION_MAJOR, HW_VERSION_MINOR, @@ -26,11 +25,11 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, reg_handler_fns, APP_REG_COUNT, update_app_state, reset_app); -// Inititalize final and vac valve pins -- Does this go here? -ValveDriver& final_valve = valve_drivers[0]; // add to config -ValveDriver& vac_valve = valve_drivers[1]; -ValveDriver (&odor_valves)[] = (&valve_drivers)[2]; // refer to rest of valve drivers as valves for odoor delivery - // i.e: odor_valves[app_regs.NextOdor].energize(); // check out harp core + ValveDriver& final_valve = valve_drivers[0]; // add to config + ValveDriver& vac_valve = valve_drivers[1]; + // Consider the rest of the valves as odor delivery valves. + ValveDriver* odor_valves_start = valve_drivers + 2; + ValveDriver (&odor_valves)[] = *reinterpret_cast(odor_valves_start); // Pass valves into the poke manager constructor PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); @@ -38,6 +37,7 @@ PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); // Core0 main. int main() { + // Init Synchronizer. HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 5041632..017be75 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -28,50 +28,8 @@ void PokeManager::deenergize_all_valves() { final_valve_.deenergize(); vac_valve_.deenergize(); - for (int i = 0; i < NUM_ODOR_VALVES; i++){ + for (int i = 0; i < num_odor_valves_; ++i) odor_valves_[i].deenergize(); - } -} - -// Functions to configure Delphi Task/update it -void PokeManager::update_next_odor(uint32_t next_odor) -{ - next_odor_index_ = next_odor; -} - -void PokeManager::set_vacuum_close_time_us(uint32_t vacuum_close_time_us) -{ - vacuum_close_time_us_ = vacuum_close_time_us; -} - -void PokeManager::set_odor_delivery_time_us(uint32_t odor_delivery_time_us) -{ - odor_delivery_time_us_ = odor_delivery_time_us; -} - -void PokeManager::set_odor_transition_time_us(uint32_t odor_transition_time_us) -{ - odor_transition_time_ = odor_transition_time_us; -} - -void PokeManager::set_vac_setup_time_us(uint32_t vac_setup_time_us) -{ - vac_setup_time_us_ = vac_setup_time_us; -} - -void PokeManager::set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us) -{ - final_valve_energized_time_us_ = final_valve_energized_time_us; -} - -void PokeManager::set_min_poke_time_us(uint32_t min_poke_time_us) -{ - min_poke_time_us_ = min_poke_time_us; -} - -void PokeManager::set_poke_pin(uint8_t pin) -{ - poke_pin_ = pin; } //Poke detection function @@ -178,7 +136,7 @@ void PokeManager::update() next_state = ODOR_PRECLEAN; break; case ODOR_PRECLEAN: - if (state_duration_us() >= odor_transition_time_) + if (state_duration_us() >= odor_transition_time_us_) next_state = VAC_START; break; case VAC_START: diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 1c443b2..f6e386b 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -1,5 +1,6 @@ """ValveController app registers. Later these will be extracted from the device.yaml""" from enum import IntEnum +from itertools import chain @@ -34,7 +35,7 @@ class AppRegs(IntEnum): AuxGPIOFallingInputs = 58 -class DelphiAppRegs(AppRegs): +class DelphiOnlyAppRegs(IntEnum): PokePinMask = 59 PokeEvent = 60 PokeDometers = 61 @@ -48,3 +49,7 @@ class DelphiAppRegs(AppRegs): VacuumSetupTimeUS = 69 FinalValveEnergizedTimeUS = 70 MinimumPokeTimeUS = 71 + + +DelphiAppRegs = IntEnum("DelphiAppRegs", + [(i.name, i.value) for i in chain(AppRegs, DelphiOnlyAppRegs)]) From e5694818186c0491756a4adc2849e271def32524 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Mon, 9 Jun 2025 23:38:59 -0700 Subject: [PATCH 14/39] split writing config into separate registers --- device.yml | 52 +++--- firmware/inc/delphi_controller_app.h | 71 +++++--- firmware/inc/poke_manager.h | 85 +++++++-- firmware/src/delphi_controller_app.cpp | 243 +++++++++++++++++-------- firmware/src/poke_manager.cpp | 50 ++--- 5 files changed, 324 insertions(+), 177 deletions(-) diff --git a/device.yml b/device.yml index 06c6756..2656468 100644 --- a/device.yml +++ b/device.yml @@ -138,72 +138,73 @@ registers: maskType: AuxGPIOMask access: Write description: "" - PokePinMask: + PokePin: address: 59 type: U8 access: Write - maskType: PokePortMask description: "which poke ports are active." - PokeEvent: + PokePinInverted: address: 60 type: U8 - access: Event - maskType: PokePortMask - description: "One or more pokes occurred at the specified index." - PokeDometers: + access: Write + description: "Which poke ports are inverted (i.e: transition from HIGH to LOW when a poke occurs)." + PokeState: address: 61 + type: U8 + access: Event + description: "The raw state of all poke ports. Upon receiving a poke (rising-edge), this register will issue an event containing the current poke state." + PokeDometer: + address: 62 type: U32 - length: 8 access: Read - maskType: PokePortMask description: "number of mouse pokes per port since boot or reset." FSMState: - address: 62 - type: U32 + address: 63 + type: U8 access: Write description: "Enable (1) (aka reset) or Disable (0) the poke handling state machine. Note that CurrentOdorIndex must be specified first. Disabling and then enabling a previously-enabled FSM will reset it to its starting state." ForceFSM: - address: 63 + address: 64 type: U8 access: Write description: "Force the poke handling state machine to iterate as if handling a mouse poke. PokeDometers are not incremented." CurrentOdorIndex: - address: 64 + address: 65 type: S8 access: Write description: "The current odor being dispensed to the odor port. Must be specified before enabling the state machine." NextOdorIndex: - address: 65 + address: 66 type: S8 access: Write description: "The next odor to be dispensed to the odor port after. Must be specified before the Odor Delivery FSM finishes a cycle." VacuumCloseTimeUS: - address: 66 + address: 67 type: U32 access: Write description: "Time alotted (in microseconds) for the vacuum valve to close." OdorDeliveryTimeUS: - address: 67 + address: 68 type: U32 access: Write description: "Time alotted (in microseconds) for the odor delivery state." OdorTransitionTimeUS: - address: 68 + address: 69 type: U32 access: Write description: "Time alotted (in microseconds) before the vacuum turns on to remove the current odor." VacuumSetupTimeUS: - address: 69 + address: 70 type: U32 access: Write description: "Time alotted (in microseconds) for the vacuum to open." FinalValveEnergizedTimeUS: - address: 70 + address: 71 type: U32 access: Write description: "Time alotted (in microseconds) for the final valve to open and remain on." MinimumPokeTimeUS: - address: 71 + address: 72 type: U32 access: Write description: "Minimum time (in microseconds) necessary for a mouse poke port beam to be broken before being interpretted as a poke." @@ -239,14 +240,3 @@ groupMasks: AuxGPIO5: 0x5 AuxGPIO6: 0x6 AuxGPIO7: 0x7 - PokePortMask: - description: "Poke Port index." - values: - PokePort0: 0x0 - PokePort1: 0x1 - PokePort2: 0x2 - PokePort3: 0x3 - PokePort4: 0x4 - PokePort5: 0x5 - PokePort6: 0x6 - PokePort7: 0x7 diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 313b328..6067ef8 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -14,7 +14,7 @@ #endif // Setup for Harp App -inline constexpr size_t APP_REG_COUNT = 35; +inline constexpr size_t APP_REG_COUNT = 41; // Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; @@ -41,19 +41,6 @@ struct ValveConfig }; #pragma pack(pop) -// Delphi task timing params -#pragma pack(push, 1) -struct delphi_task_config_t -{ - uint32_t vacuum_close_time_us; - uint32_t odor_delivery_time_us; - uint32_t odor_transition_time_us; - uint32_t vac_setup_time_us; - uint32_t final_valve_energized_time_us; - uint32_t min_poke_time_us; -}; -#pragma pack(pop) - // Registers #pragma pack(push, 1) struct app_regs_t @@ -85,14 +72,20 @@ struct app_regs_t uint8_t AuxGPIOFallingInputs; // Raw state of which inputs fell (could be multiple) // Poke Manager app "registers" here. - uint32_t PokeDometer; //Read only -- number of pokes the mouse has done since boot - uint8_t PauseFSM; // Write only -- 1 = FSM disabled , 0 = FSM enabled (default) - uint8_t RestartFSM; // Write only -- 1 = FSM enabled (default), 0 = FSM disabled - uint8_t ResetFSM; // Write only -- force FSM into reset state when passed a value of 1, default = 0 - uint8_t CurrentOdor; //Read only -- Current odor by index [0-(NUM_ODOR_VALVES-1)] that is being delivered - uint8_t NextOdor; //Read and Write -- write the next odor index [0-(NUM_ODOR_VALVES-1)] that is queued - delphi_task_config_t DelphiTaskConfig; // write and read - uint8_t PokePin; // write only + uint8_t PokePin; + uint8_t PokePinInverted; + uint8_t PokeState; + uint32_t PokeDometer; + uint8_t FSMEnabledState; + uint8_t ForceFSM; + int8_t CurrentOdorIndex; + int8_t NextOdorIndex; + uint32_t VacuumCloseTimeUS; + uint32_t OdorDeliveryTimeUS; + uint32_t OdorTransitionTimeUS; + uint32_t VacuumSetupTimeUS; + uint32_t FinalValveEnergizedTimeUS; + uint32_t MinimumPokeTimeUS; }; #pragma pack(pop) @@ -116,10 +109,23 @@ void read_valves_set(uint8_t reg_address); void read_valves_clear(uint8_t reg_address); void read_any_valve_config(uint8_t reg_address); void read_aux_gpio_state(uint8_t reg_address); + +void read_poke_pin(uint8_t reg_address); +void read_poke_pin_inverted(uint8_t reg_address); +void read_poke_state(uint8_t reg_address); void read_pokedometer(uint8_t reg_address); + +void read_fsm_enabled_state(uint8_t reg_address); +//void read_force_fsm(uint8_t reg_address); // aliased to read_reg_generic void read_current_odor(uint8_t reg_address); void read_next_odor(uint8_t reg_address); -void read_delphi_task_config(uint8_t reg_address); +void read_vacuum_close_time_us(uint8_t reg_address); +void read_odor_delivery_time_us(uint8_t reg_address); +void read_odor_transition_time_us(uint8_t reg_address); +void read_vacuum_setup_time_us(uint8_t reg_address); +void read_final_valve_energized_time_us(uint8_t reg_address); +void read_minimum_poke_time_us(uint8_t reg_address); + void write_valves_state(msg_t& msg); void write_valves_set(msg_t& msg); @@ -129,11 +135,20 @@ void write_aux_gpio_dir(msg_t& msg); void write_aux_gpio_state(msg_t& msg); void write_aux_gpio_set(msg_t& msg); void write_aux_gpio_clear(msg_t& msg); -void write_pause_fsm(msg_t& msg); -void write_restart_fsm(msg_t& msg); -void write_reset_poke_manager_fsm(msg_t& msg); -void write_next_odor(msg_t& msg); -void write_delphi_task_config(msg_t& msg); + void write_poke_pin(msg_t& msg); +void write_poke_pin_inverted(msg_t& msg); +// Cannot write to poke_stage +// Cannot write to pokedometer +void write_fsm_enabled_state(msg_t& msg); +void write_force_fsm(msg_t& msg); +void write_current_odor(msg_t& msg); +void write_next_odor(msg_t& msg); +void write_vacuum_close_time_us(msg_t& msg); +void write_odor_delivery_time_us(msg_t& msg); +void write_odor_transition_time_us(msg_t& msg); +void write_vacuum_setup_time_us(msg_t& msg); +void write_final_valve_energized_time_us(msg_t& msg); +void write_minimum_poke_time_us(msg_t& msg); #endif // DELPHI_CONTROLLER_APP_H diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 300348b..e6c6a36 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -2,6 +2,7 @@ #define POKE_MANAGER_H #include +#include #include // for uart printing #include // for printf #include @@ -39,11 +40,21 @@ class PokeManager void reset(); // reset the fsm - void pause(); // pause fsm + inline void enable() + {set_enabled_state(true);} - void restart(); // restart fsm + inline void disable() + {set_enabled_state(false);} - inline void update_next_odor(uint32_t next_odor) +/* + * \brief enable (true) or disable (false) the odor delivery state machine. + */ + void set_enabled_state(bool enabled); + + inline void set_current_odor(uint32_t odor_index) + {odor_valve_index_ = odor_index;} + + inline void set_next_odor(uint32_t next_odor) {next_odor_index_ = next_odor;} inline void set_vacuum_close_time_us(uint32_t vacuum_close_time_us) @@ -55,7 +66,7 @@ class PokeManager void set_odor_transition_time_us(uint32_t odor_transition_time_us) {odor_transition_time_us_ = odor_transition_time_us;} - inline void set_vac_setup_time_us(uint32_t vac_setup_time_us) + inline void set_vacuum_setup_time_us(uint32_t vac_setup_time_us) {vac_setup_time_us_ = vac_setup_time_us;} inline void set_final_valve_energized_time_us(uint32_t final_valve_energized_time_us) @@ -65,47 +76,83 @@ class PokeManager {min_poke_time_us_ = min_poke_time_us;} inline void set_poke_pin(uint8_t pin) - {poke_pin_ = pin;} + { + poke_pin_ = pin; + set_poke_pin_override_state(override_state_); + } + + inline void force_poke() + {poke();} /** - * \brief true if a poke was detected. Inline replaces function with code - */ - inline void poke() - {poke_detected_ = true;} + * \brief set the poke pin input override state to (1) invert or (0) uninvert + * the input. +*/ + inline void set_poke_pin_override_state(gpio_override override_state) + { + gpio_set_inover(poke_pin_, override_state); + override_state_ = override_state; // Cache the override state. + } void deenergize_all_valves(); - void check_poke_status(); + +/** + * \brief true if the poke pin is inverted. +*/ + inline uint8_t poke_pin_is_inverted() const + { + gpio_override override_state = + gpio_override( + (io_bank0_hw->io[poke_pin_].ctrl & IO_BANK0_GPIO0_CTRL_INOVER_BITS) + >> IO_BANK0_GPIO0_CTRL_INOVER_LSB); + return override_state == GPIO_OVERRIDE_INVERT; + } + + inline uint8_t get_poke_pin() const + {return poke_pin_;} inline size_t get_poke_count() const {return poke_count_;} - inline int get_current_odor() const + inline uint32_t get_current_odor() const {return odor_valve_index_;} - inline int get_next_odor() const + inline uint32_t get_next_odor() const {return next_odor_index_;} - inline uint32_t get_vacuum_close_time() const + inline uint32_t get_vacuum_close_time_us() const {return vacuum_close_time_us_;} - inline uint32_t get_odor_delivery_time() const + inline uint32_t get_odor_delivery_time_us() const {return odor_delivery_time_us_;} - inline uint32_t get_odor_transition_time() const + inline uint32_t get_odor_transition_time_us() const {return odor_transition_time_us_;} - inline uint32_t get_vac_setup_time() const + inline uint32_t get_vacuum_setup_time_us() const {return vac_setup_time_us_;} - inline uint32_t get_final_valve_energized_time() const + inline uint32_t get_final_valve_energized_time_us() const {return final_valve_energized_time_us_;} - inline uint32_t get_min_poke_time() const + inline uint32_t get_min_poke_time_us() const {return min_poke_time_us_;} private: +/** + * \brief update whether or not the input has seen a poke. Called in a loop. + */ + void update_poke_status(); + +/** + * \brief count a poke. + */ + inline void poke() + {poke_detected_ = true;} + + /** * \brief time we've been in the current state. */ @@ -118,6 +165,8 @@ class PokeManager uint32_t poke_start_time_us_; uint8_t poke_pin_; + gpio_override override_state_; /// Whether or not the poke pin is inverted. + int odor_valve_index_; int next_odor_index_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index a622e55..fea0024 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -18,6 +18,8 @@ void (&read_aux_gpio_rise_input)(uint8_t reg_address) = HarpCore::read_reg_gener void (&read_aux_gpio_fall_input)(uint8_t reg_address) = HarpCore::read_reg_generic; +void (&read_force_fsm)(uint8_t reg_address) = HarpCore::read_reg_generic; + /// Create Hit-and-Hold Valve Drivers. /// The underlying PWM peripheral, aka: a PWM Slice, controls two adjacent PWM /// pins and must be configured with the same settings. This is OK since we are @@ -74,16 +76,21 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.AuxGPIOInputFallEvent, sizeof(app_regs.AuxGPIOInputFallEvent), U8}, {(uint8_t*)&app_regs.AuxGPIORisingInputs, sizeof(app_regs.AuxGPIORisingInputs), U8}, {(uint8_t*)&app_regs.AuxGPIOFallingInputs, sizeof(app_regs.AuxGPIOFallingInputs), U8}, - + // More specs here for poke manager registers. - {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, - {(uint8_t*)&app_regs.PauseFSM, sizeof(app_regs.PauseFSM), U8}, - {(uint8_t*)&app_regs.RestartFSM, sizeof(app_regs.RestartFSM), U8}, - {(uint8_t*)&app_regs.ResetFSM, sizeof(app_regs.ResetFSM), U8}, - {(uint8_t*)&app_regs.CurrentOdor, sizeof(app_regs.CurrentOdor), U8}, - {(uint8_t*)&app_regs.NextOdor, sizeof(app_regs.NextOdor), U8}, - {(uint8_t*)&app_regs.DelphiTaskConfig, sizeof(delphi_task_config_t), U8}, {(uint8_t*)&app_regs.PokePin, sizeof(app_regs.PokePin), U8}, + {(uint8_t*)&app_regs.PokePinInverted, sizeof(app_regs.PokePinInverted), U8}, + {(uint8_t*)&app_regs.PokeState, sizeof(app_regs.PokeState), U8}, + {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, + {(uint8_t*)&app_regs.FSMEnabledState, sizeof(app_regs.FSMEnabledState), U8}, + {(uint8_t*)&app_regs.ForceFSM, sizeof(app_regs.ForceFSM), U8}, + {(uint8_t*)&app_regs.CurrentOdorIndex, sizeof(app_regs.CurrentOdorIndex), S8}, + {(uint8_t*)&app_regs.NextOdorIndex, sizeof(app_regs.NextOdorIndex), S8}, + {(uint8_t*)&app_regs.VacuumCloseTimeUS, sizeof(app_regs.VacuumCloseTimeUS), U32}, + {(uint8_t*)&app_regs.OdorDeliveryTimeUS, sizeof(app_regs.OdorDeliveryTimeUS), U32}, + {(uint8_t*)&app_regs.VacuumSetupTimeUS, sizeof(app_regs.VacuumSetupTimeUS), U32}, + {(uint8_t*)&app_regs.FinalValveEnergizedTimeUS, sizeof(app_regs.FinalValveEnergizedTimeUS), U32}, + {(uint8_t*)&app_regs.MinimumPokeTimeUS, sizeof(app_regs.MinimumPokeTimeUS), U32}, }; RegFnPair reg_handler_fns[APP_REG_COUNT] @@ -116,32 +123,30 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_aux_gpio_rise_event, write_aux_gpio_rise_event}, {read_aux_gpio_fall_event, write_aux_gpio_fall_event}, - {read_aux_gpio_rise_input, HarpCore::write_to_read_only_reg_error}, - {read_aux_gpio_fall_input, HarpCore::write_to_read_only_reg_error}, + {read_aux_gpio_rise_input, HarpCore::write_to_read_only_reg_error}, + {read_aux_gpio_fall_input, HarpCore::write_to_read_only_reg_error}, // Poke manager handler functions - {read_pokedometer, HarpCore::write_to_read_only_reg_error}, // read only - {HarpCore::read_reg_generic, write_pause_fsm}, // write only - {HarpCore::read_reg_generic, write_restart_fsm}, // write only - {HarpCore::read_reg_generic, write_reset_poke_manager_fsm}, // write only - {read_current_odor, HarpCore::write_to_read_only_reg_error}, // read only - {read_next_odor, write_next_odor}, //read and write --ADD TIMING HANDLER FUNCTIONS AFTER THIS - {read_delphi_task_config, write_delphi_task_config}, //read and write --Delphi Task - {HarpCore::read_reg_generic, write_poke_pin}, // write only + {read_poke_pin, write_poke_pin}, + {read_poke_pin_inverted, write_poke_pin_inverted}, + {read_poke_state, HarpCore::write_to_read_only_reg_error}, + {read_pokedometer, HarpCore::write_to_read_only_reg_error}, + {read_fsm_enabled_state, write_fsm_enabled_state}, + {read_force_fsm, write_force_fsm}, + {read_current_odor, write_current_odor}, + {read_next_odor, write_next_odor}, + {read_vacuum_close_time_us, write_vacuum_close_time_us}, + {read_odor_delivery_time_us, write_odor_delivery_time_us}, + {read_odor_transition_time_us, write_odor_transition_time_us}, + {read_vacuum_setup_time_us, write_vacuum_setup_time_us}, + {read_final_valve_energized_time_us, write_final_valve_energized_time_us}, + {read_minimum_poke_time_us, write_minimum_poke_time_us} }; -void read_delphi_task_config(uint8_t reg_address) -{ - delphi_task_config_t& delphi_cfg = app_regs.DelphiTaskConfig; - // Update Harp App registers with Poke Manager class contents. - // FIXME: since they are the same data type, we can just use assignment. - delphi_cfg.vacuum_close_time_us = poke_manager.get_vacuum_close_time(); - delphi_cfg.odor_delivery_time_us = poke_manager.get_odor_delivery_time(); - delphi_cfg.odor_transition_time_us = poke_manager.get_odor_transition_time(); - delphi_cfg.vac_setup_time_us = poke_manager.get_vac_setup_time(); - delphi_cfg.final_valve_energized_time_us = poke_manager.get_final_valve_energized_time(); - delphi_cfg.min_poke_time_us = poke_manager.get_min_poke_time(); +void read_poke_pin(uint8_t reg_address) +{ + app_regs.PokePin = poke_manager.get_poke_pin(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } @@ -149,104 +154,190 @@ void read_delphi_task_config(uint8_t reg_address) void write_poke_pin(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - - // Apply the configuration. poke_manager.set_poke_pin(app_regs.PokePin); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} +void read_poke_pin_inverted(uint8_t reg_address) +{ + app_regs.PokePinInverted = poke_manager.poke_pin_is_inverted(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_poke_pin_inverted(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_poke_pin_override_state(gpio_override(app_regs.PokePinInverted)); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void write_delphi_task_config(msg_t& msg) +void read_poke_state(uint8_t reg_address) +{ + // FIXME + //app_regs.PokeState = poke_manager.get_poke_state(); // Doesn't exist. + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void read_pokedometer(uint8_t reg_address) +{ + app_regs.PokeDometer = poke_manager.get_poke_count(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void read_fsm_enabled_state(uint8_t reg_address) +{ + // FIXME: doesn't exist. +// app_regs.FSMEnabledState = poke_manager.get_enabled_state(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_fsm_enabled_state(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - delphi_task_config_t& delphi_cfg = app_regs.DelphiTaskConfig; + poke_manager.set_enabled_state(app_regs.FSMEnabledState); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} - // Apply the configuration. - poke_manager.set_vacuum_close_time_us(delphi_cfg.vacuum_close_time_us); - poke_manager.set_odor_delivery_time_us(delphi_cfg.odor_delivery_time_us); - poke_manager.set_odor_transition_time_us(delphi_cfg.odor_transition_time_us); - poke_manager.set_vac_setup_time_us(delphi_cfg.vac_setup_time_us); - poke_manager.set_final_valve_energized_time_us(delphi_cfg.final_valve_energized_time_us); - poke_manager.set_min_poke_time_us(delphi_cfg.min_poke_time_us); +void write_force_fsm(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.force_poke(); // FIXME: is this the correct way to force the fsm? if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void write_next_odor(msg_t& msg) +void read_current_odor(uint8_t reg_address) { - // Registered value is updated - HarpCore::copy_msg_payload_to_register(msg); //updates the register - poke_manager.update_next_odor(app_regs.NextOdor); //write next odor + // Get recent poke count value + app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_current_odor(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_current_odor(app_regs.CurrentOdorIndex); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(WRITE, msg.header.address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } void read_next_odor(uint8_t reg_address) { - // Get recent poke count value - app_regs.NextOdor = poke_manager.get_next_odor(); + app_regs.NextOdorIndex = poke_manager.get_next_odor(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_next_odor(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_next_odor(app_regs.NextOdorIndex); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(READ, reg_address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } -void read_current_odor(uint8_t reg_address) +void read_vacuum_close_time_us(uint8_t reg_address) { - // Get recent poke count value - app_regs.CurrentOdor = poke_manager.get_current_odor(); + app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_vacuum_close_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_vacuum_close_time_us(app_regs.VacuumCloseTimeUS); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(READ, reg_address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } -void write_restart_fsm(msg_t& msg) +void read_odor_delivery_time_us(uint8_t reg_address) { - // Registered value is updated - HarpCore::copy_msg_payload_to_register(msg); //updates the register - if (app_regs.RestartFSM) - poke_manager.restart(); + app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_odor_delivery_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_vacuum_close_time_us(app_regs.OdorDeliveryTimeUS); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(WRITE, msg.header.address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } -void write_pause_fsm(msg_t& msg) +void read_odor_transition_time_us(uint8_t reg_address) { - // Registered value is updated - HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if (app_regs.PauseFSM) - poke_manager.pause(); + app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_odor_transition_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_vacuum_close_time_us(app_regs.OdorTransitionTimeUS); if (!HarpCore::is_muted()) - { HarpCore::send_harp_reply(WRITE, msg.header.address); - app_regs.PauseFSM == 0; - } } +void read_vacuum_setup_time_us(uint8_t reg_address) +{ + app_regs.VacuumSetupTimeUS = poke_manager.get_vacuum_setup_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_vacuum_setup_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_vacuum_setup_time_us(app_regs.VacuumSetupTimeUS); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} -void write_reset_poke_manager_fsm(msg_t& msg) +void read_final_valve_energized_time_us(uint8_t reg_address) { - // Registered value is updated - HarpCore::copy_msg_payload_to_register(msg); //upfates the register - if (app_regs.ResetFSM) - poke_manager.reset(); + app_regs.FinalValveEnergizedTimeUS = + poke_manager.get_final_valve_energized_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_final_valve_energized_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_final_valve_energized_time_us(app_regs.FinalValveEnergizedTimeUS); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(WRITE, msg.header.address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } -void read_pokedometer(uint8_t reg_address) +void read_minimum_poke_time_us(uint8_t reg_address) { - // Get recent poke count value - app_regs.PokeDometer = poke_manager.get_poke_count(); + app_regs.MinimumPokeTimeUS = poke_manager.get_min_poke_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} +void write_minimum_poke_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_min_poke_time_us(app_regs.MinimumPokeTimeUS); if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(READ, reg_address); + HarpCore::send_harp_reply(WRITE, msg.header.address); } + + + void read_valves_state(uint8_t reg_address) { for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 017be75..46b4859 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -33,11 +33,11 @@ void PokeManager::deenergize_all_valves() } //Poke detection function -void PokeManager::check_poke_status() +void PokeManager::update_poke_status() { // Check to see if poke has been detected // Beam is no longer broken - if (gpio_get(poke_pin_) == 1) // specify poke pin + if (!gpio_get(poke_pin_)) { beam_broken_ == false; poke_start_time_us_ = time_us_32(); @@ -45,24 +45,23 @@ void PokeManager::check_poke_status() } // Poke detected -- start poke timer - if (gpio_get(poke_pin_) == 0 && beam_broken_ == false) + if (gpio_get(poke_pin_) && !beam_broken_) { poke_start_time_us_ = time_us_32(); beam_broken_ = true; } - + // Check duration since beam break/poke - if (gpio_get(poke_pin_) == 0 && beam_broken_ == true) + if (gpio_get(poke_pin_) && beam_broken_) { //gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken - if ((time_us_32() - poke_start_time_us_) >= min_poke_time_us_ && poke_initiated_once_ == false){ - + if ((time_us_32() - poke_start_time_us_) >= min_poke_time_us_ && poke_initiated_once_ == false) + { //Poke was detected! poke(); - - //Account for the successful poke so that another doesn't occur on the same poke + //Account for the successful poke so that another doesn't occur on the same poke poke_initiated_once_ = true; - } + } } } @@ -80,23 +79,26 @@ void PokeManager::reset() set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); - set_vac_setup_time_us(DEFAULT_VAC_SETUP_TIME_US); + set_vacuum_setup_time_us(DEFAULT_VAC_SETUP_TIME_US); set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); } -void PokeManager::pause() // Needed for odor changes/refills -- Change to enable +void PokeManager::set_enabled_state(bool enabled) { - disable_fsm_ = true; - deenergize_all_valves(); // deenergize all valves -} - -void PokeManager::restart() // Needed for odor changes/refills -- change to disable -{ - disable_fsm_ = false; - poke_detected_ = false; - state_ = RESET; - beam_broken_ = false; - poke_initiated_once_ = false; + // FIXME: validate that this is what we actually want to do. + if (enabled) + { + deenergize_all_valves(); // deenergize all valves + disable_fsm_ = false; + poke_detected_ = false; + state_ = RESET; + beam_broken_ = false; + poke_initiated_once_ = false; + } + else + { + disable_fsm_ = true; + } } void PokeManager::update() @@ -113,7 +115,7 @@ void PokeManager::update() } // check for poke - check_poke_status(); + update_poke_status(); state_t next_state{state_}; // initialize next-state to current state. From d7e8b091499f20b502ffce24abc3a9e36609986a Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Tue, 10 Jun 2025 21:36:27 -0700 Subject: [PATCH 15/39] update pyharp registers --- software/pyharp/app_registers.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index f6e386b..a422c40 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -36,19 +36,20 @@ class AppRegs(IntEnum): class DelphiOnlyAppRegs(IntEnum): - PokePinMask = 59 - PokeEvent = 60 - PokeDometers = 61 - FSMState = 62 - ForceFSM = 63 - CurrentOdorIndex = 64 - NextOdorIndex = 65 - VacuumCloseTimeUS = 66 - OdorDeliveryTimeUS = 67 - OdorTransitionTimeUS = 68 - VacuumSetupTimeUS = 69 - FinalValveEnergizedTimeUS = 70 - MinimumPokeTimeUS = 71 + PokePin = 59 + PokePinInverted = 60 + PokeState = 61 + PokeDometer = 62 + FSMState = 63 + ForceFSM = 64 + CurrentOdorIndex = 65 + NextOdorIndex = 66 + VacuumCloseTimeUS = 67 + OdorDeliveryTimeUS = 68 + OdorTransitionTimeUS = 69 + VacuumSetupTimeUS = 70 + FinalValveEnergizedTimeUS = 71 + MinimumPokeTimeUS = 72 DelphiAppRegs = IntEnum("DelphiAppRegs", From 09d8ad91a5b15d7985cf6b92ec573558b8746529 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Tue, 10 Jun 2025 22:03:25 -0700 Subject: [PATCH 16/39] cleanup reset state --- firmware/inc/config.h | 5 +++-- firmware/inc/poke_manager.h | 9 ++++++--- firmware/src/delphi_controller_app.cpp | 16 +++++++++++++--- firmware/src/poke_manager.cpp | 24 +++++++++++------------- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 85ed1dc..2e6b601 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -4,12 +4,13 @@ #define NUM_VALVES (16) #define NUM_ODOR_VALVES (3) + #define UART_TX_PIN (0) #define HARP_SYNC_RX_PIN (5) #define HARP_CORE_LED_PIN (2) -#define VALVE_PIN_BASE (6) -#define GPIO_PIN_BASE (22) +inline constexpr uint32_t VALVE_PIN_BASE = 6; +inline constexpr uint32_t GPIO_PIN_BASE = 22; #define VALVES_MASK (0x0000FFFF) #define GPIOS_MASK (0x000000FF) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index e6c6a36..6803ca6 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -109,6 +109,9 @@ class PokeManager return override_state == GPIO_OVERRIDE_INVERT; } + inline uint32_t get_enabled_state() const + {return !disable_fsm_;} + inline uint8_t get_poke_pin() const {return poke_pin_;} @@ -190,9 +193,9 @@ class PokeManager // Declare Constants static inline constexpr uint32_t DEFAULT_VACUUM_CLOSE_TIME_US = 20e3; - static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; - static inline constexpr uint32_t DEFAULT_ODOR_TRANSITION_TIME_US = 30e3; - static inline constexpr uint32_t DEFAULT_VAC_SETUP_TIME_US = 20e3; + static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t DEFAULT_ODOR_TRANSITION_TIME_US = 30e3; + static inline constexpr uint32_t DEFAULT_VACUUM_SETUP_TIME_US = 20e3; static inline constexpr uint32_t DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US = 110e3; static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; static inline constexpr uint8_t DEFAUT_POKE_PIN = GPIO_PIN_BASE; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index fea0024..a8f8e07 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -519,10 +519,20 @@ void update_app_state() // Called when app.run() is called -- add poke detection void reset_app() { - // Reset poke manager + // Reset poke manager and all poke-manager-related registers poke_manager.reset(); - app_regs.PokeDometer = 0; - + app_regs.PokeDometer = poke_manager.get_poke_count(); + app_regs.FSMEnabledState = poke_manager.get_enabled_state(); + app_regs.ForceFSM = 0; + app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); + app_regs.NextOdorIndex = poke_manager.get_next_odor(); + app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); + app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); + app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); + app_regs.VacuumSetupTimeUS = poke_manager.get_vacuum_setup_time_us(); + app_regs.FinalValveEnergizedTimeUS = poke_manager.get_final_valve_energized_time_us(); + app_regs.MinimumPokeTimeUS = poke_manager.get_min_poke_time_us(); + // Reset Harp register struct elements. app_regs.ValvesState = 0; app_regs.ValvesSet = 0; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 46b4859..eb91180 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -9,7 +9,7 @@ odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, beam_broken_{false}, poke_initiated_once_{false} { - // Nothing else to do! + reset(); // set timing constants to defaults. } PokeManager::~PokeManager() //destuctor @@ -69,42 +69,40 @@ void PokeManager::update_poke_status() void PokeManager::reset() { deenergize_all_valves(); - odor_valve_index_ = next_odor_index_; + disable(); + odor_valve_index_ = -1; + next_odor_index_ = -1; poke_count_ = 0; poke_detected_ = false; - disable_fsm_ = false; - state_ = RESET; beam_broken_ = false; poke_initiated_once_ = false; set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); - set_vacuum_setup_time_us(DEFAULT_VAC_SETUP_TIME_US); + set_vacuum_setup_time_us(DEFAULT_VACUUM_SETUP_TIME_US); set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); } void PokeManager::set_enabled_state(bool enabled) { - // FIXME: validate that this is what we actually want to do. if (enabled) + disable_fsm_ = false; + else { + disable_fsm_ = true; deenergize_all_valves(); // deenergize all valves - disable_fsm_ = false; - poke_detected_ = false; + // Clear internal state machine variables. state_ = RESET; + poke_detected_ = false; beam_broken_ = false; poke_initiated_once_ = false; } - else - { - disable_fsm_ = true; - } } void PokeManager::update() { //enabled by default, but if disabled, bail early - if (disable_fsm_ == true) + if (disable_fsm_) return; //initialize RESET state From 5a86bf37a8b01804479a55cf6a601d5f4ea14f1b Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Tue, 10 Jun 2025 23:31:14 -0700 Subject: [PATCH 17/39] add request-next-odor callback --- device.yml | 4 ++-- firmware/inc/delphi_controller_app.h | 7 ++++++ firmware/inc/poke_manager.h | 28 ++++++++++++++++++++++ firmware/src/delphi_controller_app.cpp | 12 +++++++++- firmware/src/poke_manager.cpp | 32 ++++++++++++++++---------- 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/device.yml b/device.yml index 2656468..997e108 100644 --- a/device.yml +++ b/device.yml @@ -176,8 +176,8 @@ registers: NextOdorIndex: address: 66 type: S8 - access: Write - description: "The next odor to be dispensed to the odor port after. Must be specified before the Odor Delivery FSM finishes a cycle." + access: Event + description: "The next odor to be dispensed to the odor port after. Must be specified before the Odor Delivery FSM finishes a cycle. An event will issue from this register when it is consumed." VacuumCloseTimeUS: address: 67 type: U32 diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 6067ef8..fc6d658 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -91,6 +91,13 @@ struct app_regs_t extern app_regs_t app_regs; + +/** + * \brief callback function to tell the PC we need another odor from + * within the PokeManager state machine logic. + */ +void request_next_odor(void); + /** * \brief update the app state. Called in a loop. */ diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 6803ca6..cf65a4c 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -46,6 +46,32 @@ class PokeManager inline void disable() {set_enabled_state(false);} + inline void set_odor_valve_state(bool enabled) + { + if (odor_valve_index_ >= 0) + { + if (enabled) + odor_valves_[odor_valve_index_].energize(); + else + odor_valves_[odor_valve_index_].deenergize(); + } + } + + inline void energize_odor_valve() + {set_odor_valve_state(1);} + + inline void deenergize_odor_valve() + {set_odor_valve_state(0);} + + inline void set_next_odor_callback_fn( void (* fn)(void)) + {request_next_odor_callback_fn_ = fn;} + + inline void request_next_odor() + { + if (request_next_odor_callback_fn_ != nullptr) + request_next_odor_callback_fn_(); + } + /* * \brief enable (true) or disable (false) the odor delivery state machine. */ @@ -191,6 +217,8 @@ class PokeManager uint32_t final_valve_energized_time_us_; uint32_t min_poke_time_us_; + void (*request_next_odor_callback_fn_)(void); + // Declare Constants static inline constexpr uint32_t DEFAULT_VACUUM_CLOSE_TIME_US = 20e3; static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index a8f8e07..4587ae4 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -492,6 +492,15 @@ void write_aux_gpio_clear(msg_t& msg) HarpCore::send_harp_reply(WRITE, msg.header.address); } +void request_next_odor(void) +{ + // FIXME: this is hardcoded. + const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; + app_regs.NextOdorIndex = -1; // Mark it as "used." + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); +} + void update_app_state() // Called when app.run() is called -- add poke detection here { // Update valve controller state machines. @@ -514,13 +523,14 @@ void update_app_state() // Called when app.run() is called -- add poke detection HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); // Update poke manager FSM - //poke_manager.update(); + poke_manager.update(); } void reset_app() { // Reset poke manager and all poke-manager-related registers poke_manager.reset(); + poke_manager.set_next_odor_callback_fn(request_next_odor); app_regs.PokeDometer = poke_manager.get_poke_count(); app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index eb91180..9a05d04 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -7,7 +7,8 @@ num_odor_valves_{num_odor_valves}, state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, -beam_broken_{false}, poke_initiated_once_{false} +beam_broken_{false}, poke_initiated_once_{false}, +request_next_odor_callback_fn_{nullptr} { reset(); // set timing constants to defaults. } @@ -129,7 +130,11 @@ void PokeManager::update() break; case ODOR_DISPENSING_TO_EXHAUST: if (poke_detected_) + { next_state = ODOR_DELIVERY_TO_FINAL_VALVE; + if (next_odor_index_ < 0) + request_next_odor(); + } break; case ODOR_DELIVERY_TO_FINAL_VALVE: if (state_duration_us() >= odor_delivery_time_us_) @@ -154,28 +159,30 @@ void PokeManager::update() // Update how long we've been in the new state. if (state_ != next_state) { +#if(DEBUG) printf("State transition %d -> %d\r\n", state_, next_state); printf("State transition time %i\r\n", state_duration_us()); +#endif state_entry_time_us_ = time_us_32(); - + // Next state logic should only be assessed if there is a state transition if (next_state == ODOR_SETUP) { - // Energize one of the odor valves deenergize_all_valves(); - odor_valves_[odor_valve_index_].energize(); + energize_odor_valve(); } - if (next_state == ODOR_DISPENSING_TO_EXHAUST) - { - // Don't need to do anything because odor is being sent to exhaust and we are waiting for a poke - } + // Don't need to do anything because odor is being sent to exhaust and + // we are waiting for a poke + if (next_state == ODOR_DISPENSING_TO_EXHAUST){} if (next_state == ODOR_DELIVERY_TO_FINAL_VALVE) { final_valve_.energize(); ++poke_count_; +#if(DEBUG) printf("Number of pokes = %i\r\n", poke_count_); +#endif poke_detected_ = false; } @@ -186,8 +193,7 @@ void PokeManager::update() if (next_state == VAC_START) { - // Deenergize odor valve - odor_valves_[odor_valve_index_].deenergize(); + deenergize_odor_valve(); vac_valve_.energize(); } @@ -195,9 +201,11 @@ void PokeManager::update() { // Energize the final valve final_valve_.energize(); - odor_valve_index_ = next_odor_index_; //DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR - + // FIXME: DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR + odor_valve_index_ = next_odor_index_; +#if(DEBUG) printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index +#endif } } // Update state: From 7db171fe6faf319ef66bee0ca552429972b5694b Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Thu, 7 Aug 2025 18:08:35 -0700 Subject: [PATCH 18/39] update poke-manager test; get gpio inversion working --- firmware/inc/poke_manager.h | 23 +++++- firmware/lib/harp.core.rp2040 | 2 +- firmware/src/delphi_controller_app.cpp | 7 +- firmware/src/poke_manager.cpp | 7 +- firmware/tests/poke_manager/CMakeLists.txt | 12 +-- firmware/tests/poke_manager/main.cpp | 91 ++++++++++------------ software/pyharp/app_registers.py | 2 +- 7 files changed, 80 insertions(+), 64 deletions(-) diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index cf65a4c..0659206 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -103,10 +103,26 @@ class PokeManager inline void set_poke_pin(uint8_t pin) { + clear_poke_pin(); + // Init new gpio pin. poke_pin_ = pin; + gpio_init(poke_pin_); + gpio_set_dir(poke_pin_, GPIO_IN); + poke_pin_is_initialized_ = true; + // Apply override state. set_poke_pin_override_state(override_state_); } + inline void clear_poke_pin() + { + if (!poke_pin_is_initialized_) + return; + set_poke_pin_override_state(GPIO_OVERRIDE_NORMAL); + gpio_deinit(poke_pin_); + poke_pin_ = DEFAUT_POKE_PIN; + poke_pin_is_initialized_ = false; + } + inline void force_poke() {poke();} @@ -116,8 +132,9 @@ class PokeManager */ inline void set_poke_pin_override_state(gpio_override override_state) { - gpio_set_inover(poke_pin_, override_state); - override_state_ = override_state; // Cache the override state. + override_state_ = override_state; // Cache the override setting. + if (poke_pin_is_initialized_) + gpio_set_inover(poke_pin_, override_state); } void deenergize_all_valves(); @@ -219,6 +236,8 @@ class PokeManager void (*request_next_odor_callback_fn_)(void); + bool poke_pin_is_initialized_; + // Declare Constants static inline constexpr uint32_t DEFAULT_VACUUM_CLOSE_TIME_US = 20e3; static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; diff --git a/firmware/lib/harp.core.rp2040 b/firmware/lib/harp.core.rp2040 index 51f5d99..6ba5a7f 160000 --- a/firmware/lib/harp.core.rp2040 +++ b/firmware/lib/harp.core.rp2040 @@ -1 +1 @@ -Subproject commit 51f5d9995c88d41bd2580591e24bd70c70c28ea3 +Subproject commit 6ba5a7fa2df6b6b55238f4e69fd3b9073910d7ec diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 4587ae4..f307171 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -494,8 +494,7 @@ void write_aux_gpio_clear(msg_t& msg) void request_next_odor(void) { - // FIXME: this is hardcoded. - const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; + const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. app_regs.NextOdorIndex = -1; // Mark it as "used." if (!HarpCore::is_muted()) HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); @@ -524,6 +523,7 @@ void update_app_state() // Called when app.run() is called -- add poke detection // Update poke manager FSM poke_manager.update(); + // Issue poke-related state changes as events. } void reset_app() @@ -535,7 +535,7 @@ void reset_app() app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); - app_regs.NextOdorIndex = poke_manager.get_next_odor(); + app_regs.NextOdorIndex = -1;//poke_manager.get_next_odor(); app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); @@ -567,5 +567,6 @@ void reset_app() old_aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; + } diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 9a05d04..9048c8b 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -8,7 +8,8 @@ state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, poke_detected_{false}, beam_broken_{false}, poke_initiated_once_{false}, -request_next_odor_callback_fn_{nullptr} +request_next_odor_callback_fn_{nullptr}, +poke_pin_is_initialized_{false} { reset(); // set timing constants to defaults. } @@ -60,6 +61,9 @@ void PokeManager::update_poke_status() { //Poke was detected! poke(); +#if(DEBUG) + printf("Poke detected!\r\n"); +#endif //Account for the successful poke so that another doesn't occur on the same poke poke_initiated_once_ = true; } @@ -77,6 +81,7 @@ void PokeManager::reset() poke_detected_ = false; beam_broken_ = false; poke_initiated_once_ = false; + clear_poke_pin(); set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); diff --git a/firmware/tests/poke_manager/CMakeLists.txt b/firmware/tests/poke_manager/CMakeLists.txt index 62a150a..06b20bf 100644 --- a/firmware/tests/poke_manager/CMakeLists.txt +++ b/firmware/tests/poke_manager/CMakeLists.txt @@ -20,7 +20,7 @@ execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD OUTPUT_VARIAB message(STATUS "Computed Git Hash: ${COMMIT_ID}") add_definitions(-DGIT_HASH="${COMMIT_ID}") # Usable in source code. -#add_definitions(-DDEBUG) # Uncomment for debugging +add_definitions(-DDEBUG) # Uncomment for debugging add_definitions(-DUSBD_MANUFACTURER="Allen Institute") add_definitions(-DUSBD_PRODUCT="test-poke_manager") @@ -41,8 +41,8 @@ add_executable(${PROJECT_NAME} main.cpp ) -add_library(poke_manager_test - ../../src/poke_manager_test.cpp +add_library(poke_manager + ../../src/poke_manager.cpp ) add_library(valve_driver @@ -52,17 +52,17 @@ include_directories(../../inc) target_link_libraries(valve_driver rp2040_pwm pico_stdlib) -target_link_libraries(poke_manager_test pico_stdlib valve_driver etl::etl) +target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) target_link_libraries(${PROJECT_NAME} - poke_manager_test pico_stdlib valve_driver etl::etl) + poke_manager pico_stdlib valve_driver etl::etl) pico_add_extra_outputs(${PROJECT_NAME}) message(WARNING "Debug printf() messages from harp core to UART with baud \ rate 921600.") pico_enable_stdio_usb(${PROJECT_NAME} 1) -pico_enable_stdio_usb(poke_manager_test 1) +pico_enable_stdio_usb(poke_manager 1) # pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. # pico_enable_stdio_uart(poke_manager 1) # UART stdio for printf. # Additional libraries need to have stdio init also. diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index 1608b2e..058fd6f 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -1,79 +1,70 @@ #include +#include #include #include -#include +#include #include #include // for uart printing #include // for printf -// Inititalize final and vac valve pins -ValveDriver final_valve(1); -ValveDriver vac_valve(2); -etl::vector odor_valves; +ValveDriver valve_drivers[NUM_VALVES] +{{VALVE_PIN_BASE}, + {VALVE_PIN_BASE + 1}, + {VALVE_PIN_BASE + 2}, + {VALVE_PIN_BASE + 3}, + {VALVE_PIN_BASE + 4}, + {VALVE_PIN_BASE + 5}, + {VALVE_PIN_BASE + 6}, + {VALVE_PIN_BASE + 7}, + {VALVE_PIN_BASE + 8}, + {VALVE_PIN_BASE + 9}, + {VALVE_PIN_BASE + 10}, + {VALVE_PIN_BASE + 11}, + {VALVE_PIN_BASE + 12}, + {VALVE_PIN_BASE + 13}, + {VALVE_PIN_BASE + 14}, + {VALVE_PIN_BASE + 15}}; + +ValveDriver& final_valve = valve_drivers[0]; // add to config +ValveDriver& vac_valve = valve_drivers[1]; +// Consider the rest of the valves as odor delivery valves. +ValveDriver* odor_valves_start = valve_drivers + 2; +ValveDriver (&odor_valves)[] = *reinterpret_cast(odor_valves_start); // Pass valves into the poke manager constructor -PokeManager poke_manager(final_valve, vac_valve, odor_valves); +PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); + -// LED an Poke Port -const uint LED_PIN = 25; -const uint POKE_PIN = 0; //GPIO pin for pokes +// LED and Poke Port +const uint LED_PIN = 2;//25; +const uint POKE_PIN = 22; //GPIO pin for pokes bool beam_broken = false; //keep track of beam state bool poke_initiated_once = false; //Only trigger the FSM on 1 poke uint32_t poke_start_time_us; //poke start time static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; //poke duration - 1s, could return this value in the source file -// Core0 main. -int main() -{ - // setup AUX GPIO pins and convert into pins - gpio_init_mask(GPIOS_MASK << GPIO_PIN_BASE); //pico sdk function - gpio_set_dir_masked(GPIOS_MASK << GPIO_PIN_BASE, 0); //0: input, 1: output - // Initialize one input pin for the poke port - // gpio_init(POKE_PIN ); - // gpio_set_dir(POKE_PIN , 0); +void request_next_odor() +{ + printf("Next odor, please!\r\n"); +} +// Core0 main. +int main() +{ gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); - // Odor valves vector -- set valves - for (int i = 4; i < 4 + NUM_ODOR_VALVES; i++){ - odor_valves.emplace_back(i); - } - stdio_usb_init(); stdio_set_translate_crlf(&stdio_usb, false); // Don't replace outgoing chars. while (!stdio_usb_connected()){} // Block until connection to serial port. printf("Hello, from an RP2040!\r\n"); + poke_manager.set_poke_pin(POKE_PIN); + poke_manager.set_poke_pin_override_state(GPIO_OVERRIDE_INVERT); + poke_manager.set_next_odor_callback_fn(request_next_odor); + poke_manager.set_enabled_state(true); while(true){ poke_manager.update(); // update through FSM - - // Beam is no longer broken - if (gpio_get(POKE_PIN) == 1){ - gpio_put(LED_PIN, 0); - beam_broken == false; - poke_start_time_us = time_us_32(); - poke_initiated_once = false; - } - - // Poke detected -- start poke timer - if (gpio_get(POKE_PIN) == 0 && beam_broken == false){ - poke_start_time_us = time_us_32(); - beam_broken = true; - } - - // Check duration since beam break/poke - if (gpio_get(POKE_PIN) == 0 && beam_broken == true){ - gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken - if ((time_us_32() - poke_start_time_us) >= MIN_POKE_TIME_US && poke_initiated_once == false){ - - //Poke was detected! - poke_manager.poke(); - - //Account for the successful poke so that another doesn't occur on the same poke - poke_initiated_once = true; - } - } } } diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index a422c40..30c270d 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -40,7 +40,7 @@ class DelphiOnlyAppRegs(IntEnum): PokePinInverted = 60 PokeState = 61 PokeDometer = 62 - FSMState = 63 + FSMEnabledState = 63 ForceFSM = 64 CurrentOdorIndex = 65 NextOdorIndex = 66 From 1026be568f4221a3a9f30396b93ea2c68af87438 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Thu, 7 Aug 2025 18:34:09 -0700 Subject: [PATCH 19/39] get next-odor event request working --- firmware/CMakeLists.txt | 2 +- firmware/src/delphi_controller_app.cpp | 12 +++++++----- firmware/src/poke_manager.cpp | 3 ++- firmware/tests/poke_manager/main.cpp | 4 ---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 6a4664a..be36812 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -49,7 +49,7 @@ if(DEBUG) message(WARNING "Debug printf() messages from harp core to UART with baud \ rate 921600.") pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. - pico_enable_stdio_usb(poke_manager 1) + pico_enable_stdio_uart(poke_manager 1) # Additional libraries need to have stdio init also. endif() diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index f307171..400e672 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -492,7 +492,7 @@ void write_aux_gpio_clear(msg_t& msg) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void request_next_odor(void) +void request_next_odor() { const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. app_regs.NextOdorIndex = -1; // Mark it as "used." @@ -505,6 +505,11 @@ void update_app_state() // Called when app.run() is called -- add poke detection // Update valve controller state machines. for (auto& valve_driver: valve_drivers) valve_driver.update(); + + // Update poke manager FSM + poke_manager.update(); + // TODO: Issue poke-related state changes as events. + // Process AuxGPIO input changes. // FIXME: do we need to update old_aux_gpio_inputs if we change (write-to) // app_regs.AuxGPIODir ? @@ -521,9 +526,6 @@ void update_app_state() // Called when app.run() is called -- add poke detection if (app_regs.AuxGPIOInputFallEvent & app_regs.AuxGPIOFallingInputs) HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); - // Update poke manager FSM - poke_manager.update(); - // Issue poke-related state changes as events. } void reset_app() @@ -535,7 +537,7 @@ void reset_app() app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); - app_regs.NextOdorIndex = -1;//poke_manager.get_next_odor(); + app_regs.NextOdorIndex = poke_manager.get_next_odor(); app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 9048c8b..d786fa3 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -82,6 +82,7 @@ void PokeManager::reset() beam_broken_ = false; poke_initiated_once_ = false; clear_poke_pin(); + request_next_odor_callback_fn_ = nullptr; set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); @@ -206,8 +207,8 @@ void PokeManager::update() { // Energize the final valve final_valve_.energize(); - // FIXME: DO SOMETHING HERE TO READ FROM THE REGISTER TO GET NEXT ODOR odor_valve_index_ = next_odor_index_; + next_odor_index_ = -1; // Consume next odor. #if(DEBUG) printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index #endif diff --git a/firmware/tests/poke_manager/main.cpp b/firmware/tests/poke_manager/main.cpp index 058fd6f..cde8c1f 100644 --- a/firmware/tests/poke_manager/main.cpp +++ b/firmware/tests/poke_manager/main.cpp @@ -39,10 +39,6 @@ PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); // LED and Poke Port const uint LED_PIN = 2;//25; const uint POKE_PIN = 22; //GPIO pin for pokes -bool beam_broken = false; //keep track of beam state -bool poke_initiated_once = false; //Only trigger the FSM on 1 poke -uint32_t poke_start_time_us; //poke start time -static inline constexpr uint32_t MIN_POKE_TIME_US = 10e3; //poke duration - 1s, could return this value in the source file void request_next_odor() From d2d1856a82f290d76d3d29167c0c772dfdeca3e6 Mon Sep 17 00:00:00 2001 From: Sonya Vasquez Date: Thu, 7 Aug 2025 18:36:59 -0700 Subject: [PATCH 20/39] add wait for pokes script --- software/pyharp/wait_for_pokes.py | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 software/pyharp/wait_for_pokes.py diff --git a/software/pyharp/wait_for_pokes.py b/software/pyharp/wait_for_pokes.py new file mode 100755 index 0000000..bc9cb37 --- /dev/null +++ b/software/pyharp/wait_for_pokes.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +from pyharp.device import Device, DeviceMode +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from app_registers import AppRegs, DelphiOnlyAppRegs +from struct import pack, unpack +from time import sleep +import os +import serial.tools.list_ports + +import logging +logger = logging.getLogger() +logger.addHandler(logging.StreamHandler()) + +# Open serial connection with the first Valve Controller. +com_port = None +ports = serial.tools.list_ports.comports() +for port, desc, hwid in sorted(ports): + if desc.startswith("delphi-controller"): + print("{}: {} [{}]".format(port, desc, hwid)) + com_port = port + break +device = Device(com_port) +device.info() # Display device's info on screen + +print() +print("Enabling all aux gpios as inputs.") +gpio_dir = 0b00000000 +reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIODir, gpio_dir).frame) +print(f"reply: {reply.payload[0]:08b}") +print() +reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.PokeDometer).frame) +print(f"Current pokedometer count is: {reply.payload}.") +print(f"Setting next odor.") +reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.NextOdorIndex, 0).frame) +print(f"Assigning poke pin.") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePin, 22).frame) +print(f"Inverting poke pin.") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePinInverted, 1).frame) +print("Enabling FSM") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.FSMEnabledState, 1).frame) +print() + +try: + while True: + for msg in device.get_events(): + print(msg) + print() +except KeyboardInterrupt: + print("Disabling FSM.") + reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.FSMEnabledState, 0).frame) + device.disconnect() From b73deb404d83894bed10229efb5c8c31c0d001fc Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Fri, 12 Sep 2025 14:38:02 -0700 Subject: [PATCH 21/39] fixed setting some state duration registers --- firmware/src/delphi_controller_app.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 400e672..de5a33a 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -269,7 +269,7 @@ void read_odor_delivery_time_us(uint8_t reg_address) void write_odor_delivery_time_us(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_vacuum_close_time_us(app_regs.OdorDeliveryTimeUS); + poke_manager.set_odor_delivery_time_us(app_regs.OdorDeliveryTimeUS); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -284,7 +284,7 @@ void read_odor_transition_time_us(uint8_t reg_address) void write_odor_transition_time_us(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_vacuum_close_time_us(app_regs.OdorTransitionTimeUS); + poke_manager.set_odor_transition_time_us(app_regs.OdorTransitionTimeUS); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -336,8 +336,6 @@ void write_minimum_poke_time_us(msg_t& msg) } - - void read_valves_state(uint8_t reg_address) { for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) From 5d948a98f7abdfdb17eae89da8155548fcc0d9a2 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 15 Sep 2025 11:32:38 -0700 Subject: [PATCH 22/39] made remaining valves odor valves --- firmware/inc/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 2e6b601..4d0b0f8 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -2,7 +2,7 @@ #define CONFIG_H #define NUM_VALVES (16) -#define NUM_ODOR_VALVES (3) +#define NUM_ODOR_VALVES (14) #define UART_TX_PIN (0) From 770e52231a9075b50b50ad07635ce0c96a4b1182 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 15 Sep 2025 13:15:32 -0700 Subject: [PATCH 23/39] fixed odor queue functionality and poke state --- firmware/inc/delphi_controller_app.h | 5 +--- firmware/inc/poke_manager.h | 10 +++----- firmware/src/delphi_controller_app.cpp | 32 ++++++-------------------- firmware/src/poke_manager.cpp | 20 +++++++++------- 4 files changed, 23 insertions(+), 44 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index fc6d658..050a41c 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -78,8 +78,7 @@ struct app_regs_t uint32_t PokeDometer; uint8_t FSMEnabledState; uint8_t ForceFSM; - int8_t CurrentOdorIndex; - int8_t NextOdorIndex; + int8_t QueuedOdorIndex; uint32_t VacuumCloseTimeUS; uint32_t OdorDeliveryTimeUS; uint32_t OdorTransitionTimeUS; @@ -125,7 +124,6 @@ void read_pokedometer(uint8_t reg_address); void read_fsm_enabled_state(uint8_t reg_address); //void read_force_fsm(uint8_t reg_address); // aliased to read_reg_generic void read_current_odor(uint8_t reg_address); -void read_next_odor(uint8_t reg_address); void read_vacuum_close_time_us(uint8_t reg_address); void read_odor_delivery_time_us(uint8_t reg_address); void read_odor_transition_time_us(uint8_t reg_address); @@ -150,7 +148,6 @@ void write_poke_pin_inverted(msg_t& msg); void write_fsm_enabled_state(msg_t& msg); void write_force_fsm(msg_t& msg); void write_current_odor(msg_t& msg); -void write_next_odor(msg_t& msg); void write_vacuum_close_time_us(msg_t& msg); void write_odor_delivery_time_us(msg_t& msg); void write_odor_transition_time_us(msg_t& msg); diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 0659206..c009a32 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -80,9 +80,6 @@ class PokeManager inline void set_current_odor(uint32_t odor_index) {odor_valve_index_ = odor_index;} - inline void set_next_odor(uint32_t next_odor) - {next_odor_index_ = next_odor;} - inline void set_vacuum_close_time_us(uint32_t vacuum_close_time_us) {vacuum_close_time_us_ = vacuum_close_time_us;} @@ -158,15 +155,15 @@ class PokeManager inline uint8_t get_poke_pin() const {return poke_pin_;} + inline bool get_poke_state() const //do this + {return poke_detected_;} + inline size_t get_poke_count() const {return poke_count_;} inline uint32_t get_current_odor() const {return odor_valve_index_;} - inline uint32_t get_next_odor() const - {return next_odor_index_;} - inline uint32_t get_vacuum_close_time_us() const {return vacuum_close_time_us_;} @@ -214,7 +211,6 @@ class PokeManager gpio_override override_state_; /// Whether or not the poke pin is inverted. int odor_valve_index_; - int next_odor_index_; size_t poke_count_; bool poke_detected_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index de5a33a..fffaaf4 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -84,8 +84,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, {(uint8_t*)&app_regs.FSMEnabledState, sizeof(app_regs.FSMEnabledState), U8}, {(uint8_t*)&app_regs.ForceFSM, sizeof(app_regs.ForceFSM), U8}, - {(uint8_t*)&app_regs.CurrentOdorIndex, sizeof(app_regs.CurrentOdorIndex), S8}, - {(uint8_t*)&app_regs.NextOdorIndex, sizeof(app_regs.NextOdorIndex), S8}, + {(uint8_t*)&app_regs.QueuedOdorIndex, sizeof(app_regs.QueuedOdorIndex), S8}, {(uint8_t*)&app_regs.VacuumCloseTimeUS, sizeof(app_regs.VacuumCloseTimeUS), U32}, {(uint8_t*)&app_regs.OdorDeliveryTimeUS, sizeof(app_regs.OdorDeliveryTimeUS), U32}, {(uint8_t*)&app_regs.VacuumSetupTimeUS, sizeof(app_regs.VacuumSetupTimeUS), U32}, @@ -134,7 +133,6 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_fsm_enabled_state, write_fsm_enabled_state}, {read_force_fsm, write_force_fsm}, {read_current_odor, write_current_odor}, - {read_next_odor, write_next_odor}, {read_vacuum_close_time_us, write_vacuum_close_time_us}, {read_odor_delivery_time_us, write_odor_delivery_time_us}, {read_odor_transition_time_us, write_odor_transition_time_us}, @@ -177,7 +175,7 @@ void write_poke_pin_inverted(msg_t& msg) void read_poke_state(uint8_t reg_address) { // FIXME - //app_regs.PokeState = poke_manager.get_poke_state(); // Doesn't exist. + app_regs.PokeState = poke_manager.get_poke_state(); // Doesn't exist. if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } @@ -216,7 +214,7 @@ void write_force_fsm(msg_t& msg) void read_current_odor(uint8_t reg_address) { // Get recent poke count value - app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); + app_regs.QueuedOdorIndex = poke_manager.get_current_odor(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } @@ -224,22 +222,7 @@ void read_current_odor(uint8_t reg_address) void write_current_odor(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_current_odor(app_regs.CurrentOdorIndex); - if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(WRITE, msg.header.address); -} - -void read_next_odor(uint8_t reg_address) -{ - app_regs.NextOdorIndex = poke_manager.get_next_odor(); - if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(READ, reg_address); -} - -void write_next_odor(msg_t& msg) -{ - HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_next_odor(app_regs.NextOdorIndex); + poke_manager.set_current_odor(app_regs.QueuedOdorIndex); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -492,8 +475,8 @@ void write_aux_gpio_clear(msg_t& msg) void request_next_odor() { - const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. - app_regs.NextOdorIndex = -1; // Mark it as "used." + const uint8_t NEXT_ODOR_INDEX_ADDRESS = 65; // FIXME: this is hardcoded. + app_regs.QueuedOdorIndex = -1; // Mark it as "used." if (!HarpCore::is_muted()) HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); } @@ -534,8 +517,7 @@ void reset_app() app_regs.PokeDometer = poke_manager.get_poke_count(); app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; - app_regs.CurrentOdorIndex = poke_manager.get_current_odor(); - app_regs.NextOdorIndex = poke_manager.get_next_odor(); + app_regs.QueuedOdorIndex = poke_manager.get_current_odor(); app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index d786fa3..e640b26 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -5,7 +5,7 @@ PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, : final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, num_odor_valves_{num_odor_valves}, state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, -odor_valve_index_{0}, next_odor_index_{0}, disable_fsm_{false}, +odor_valve_index_{-1}, disable_fsm_{false}, poke_detected_{false}, beam_broken_{false}, poke_initiated_once_{false}, request_next_odor_callback_fn_{nullptr}, @@ -76,7 +76,6 @@ void PokeManager::reset() deenergize_all_valves(); disable(); odor_valve_index_ = -1; - next_odor_index_ = -1; poke_count_ = 0; poke_detected_ = false; beam_broken_ = false; @@ -131,15 +130,20 @@ void PokeManager::update() next_state = ODOR_SETUP; break; case ODOR_SETUP: - if (state_duration_us() >= vacuum_close_time_us_) + if (odor_valve_index_ < 0){ + request_next_odor(); + // The odor should be primed before a poke + poke_detected_ = false; + } + else if (state_duration_us() >= vacuum_close_time_us_ && odor_valve_index_ != -1) + { next_state = ODOR_DISPENSING_TO_EXHAUST; + } break; case ODOR_DISPENSING_TO_EXHAUST: - if (poke_detected_) + if (poke_detected_) // poke detected and an odor is primed { next_state = ODOR_DELIVERY_TO_FINAL_VALVE; - if (next_odor_index_ < 0) - request_next_odor(); } break; case ODOR_DELIVERY_TO_FINAL_VALVE: @@ -174,6 +178,7 @@ void PokeManager::update() // Next state logic should only be assessed if there is a state transition if (next_state == ODOR_SETUP) { + // explicitly grab odor in the queue deenergize_all_valves(); energize_odor_valve(); } @@ -207,8 +212,7 @@ void PokeManager::update() { // Energize the final valve final_valve_.energize(); - odor_valve_index_ = next_odor_index_; - next_odor_index_ = -1; // Consume next odor. + odor_valve_index_ = -1; // Consume queued odor. #if(DEBUG) printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index #endif From b9fb8dd2b7cd4d25f80575b073684f66c31232f6 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 17 Sep 2025 07:12:25 -0700 Subject: [PATCH 24/39] fixed poke state register and added events for pokes --- firmware/inc/delphi_controller_app.h | 5 +++++ firmware/inc/poke_manager.h | 15 +++++++++++++-- firmware/src/delphi_controller_app.cpp | 9 +++++++++ firmware/src/poke_manager.cpp | 12 ++++++++++-- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 050a41c..ed902be 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -97,6 +97,11 @@ extern app_regs_t app_regs; */ void request_next_odor(void); +/** + * \brief callback function to tell the PC when the poke state changed + */ +void poke_state_changed(void); + /** * \brief update the app state. Called in a loop. */ diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index c009a32..9f244fb 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -72,6 +72,15 @@ class PokeManager request_next_odor_callback_fn_(); } + inline void set_poke_state_callback_fn( void (* fn)(void)) + {request_poke_state_callback_fn_ = fn;} + + inline void poke_state_changed() + { + if (request_poke_state_callback_fn_ != nullptr) + request_poke_state_callback_fn_(); + } + /* * \brief enable (true) or disable (false) the odor delivery state machine. */ @@ -155,8 +164,8 @@ class PokeManager inline uint8_t get_poke_pin() const {return poke_pin_;} - inline bool get_poke_state() const //do this - {return poke_detected_;} + inline uint8_t get_poke_state() const + {return poke_state_;} inline size_t get_poke_count() const {return poke_count_;} @@ -213,6 +222,7 @@ class PokeManager int odor_valve_index_; size_t poke_count_; + uint8_t poke_state_; bool poke_detected_; bool disable_fsm_; bool beam_broken_; //keep track of beam state @@ -231,6 +241,7 @@ class PokeManager uint32_t min_poke_time_us_; void (*request_next_odor_callback_fn_)(void); + void (*request_poke_state_callback_fn_)(void); bool poke_pin_is_initialized_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index fffaaf4..b14af56 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -481,6 +481,14 @@ void request_next_odor() HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); } +void poke_state_changed() +{ + const uint8_t POKE_STATE_INDEX_ADDRESS = 61; // FIXME: this is hardcoded. + app_regs.PokeState = 1; // Mark it as "used." + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS); +} + void update_app_state() // Called when app.run() is called -- add poke detection here { // Update valve controller state machines. @@ -514,6 +522,7 @@ void reset_app() // Reset poke manager and all poke-manager-related registers poke_manager.reset(); poke_manager.set_next_odor_callback_fn(request_next_odor); + poke_manager.set_poke_state_callback_fn(poke_state_changed); app_regs.PokeDometer = poke_manager.get_poke_count(); app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index e640b26..1dc748a 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -6,9 +6,9 @@ PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, num_odor_valves_{num_odor_valves}, state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{-1}, disable_fsm_{false}, -poke_detected_{false}, +poke_detected_{false}, poke_state_{0}, beam_broken_{false}, poke_initiated_once_{false}, -request_next_odor_callback_fn_{nullptr}, +request_next_odor_callback_fn_{nullptr}, request_poke_state_callback_fn_{nullptr}, poke_pin_is_initialized_{false} { reset(); // set timing constants to defaults. @@ -19,6 +19,7 @@ PokeManager::~PokeManager() //destuctor //Deengergize all valves deenergize_all_valves(); poke_count_ = 0; + poke_state_ = 0; poke_detected_ = false; disable_fsm_ = false; state_ = RESET; @@ -61,6 +62,8 @@ void PokeManager::update_poke_status() { //Poke was detected! poke(); + poke_state_changed(); + poke_state_ = 1; #if(DEBUG) printf("Poke detected!\r\n"); #endif @@ -77,11 +80,13 @@ void PokeManager::reset() disable(); odor_valve_index_ = -1; poke_count_ = 0; + poke_state_ = 0; poke_detected_ = false; beam_broken_ = false; poke_initiated_once_ = false; clear_poke_pin(); request_next_odor_callback_fn_ = nullptr; + request_poke_state_callback_fn_ = nullptr; set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); @@ -100,6 +105,7 @@ void PokeManager::set_enabled_state(bool enabled) // Clear internal state machine variables. state_ = RESET; poke_detected_ = false; + poke_state_ = 0; beam_broken_ = false; poke_initiated_once_ = false; } @@ -134,6 +140,7 @@ void PokeManager::update() request_next_odor(); // The odor should be primed before a poke poke_detected_ = false; + poke_state_ = 0; } else if (state_duration_us() >= vacuum_close_time_us_ && odor_valve_index_ != -1) { @@ -195,6 +202,7 @@ void PokeManager::update() printf("Number of pokes = %i\r\n", poke_count_); #endif poke_detected_ = false; + poke_state_ = 0; } if (next_state == ODOR_PRECLEAN) From fe581cebc304fde427b0fa4c8720e1c8774356d5 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 17 Sep 2025 10:08:18 -0700 Subject: [PATCH 25/39] added min and max odor delivery time functionality --- firmware/inc/delphi_controller_app.h | 9 +++++--- firmware/inc/poke_manager.h | 20 +++++++++++----- firmware/src/delphi_controller_app.cpp | 32 ++++++++++++++++++++------ firmware/src/poke_manager.cpp | 5 ++-- software/pyharp/app_registers.py | 8 +++---- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index ed902be..811be8d 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -80,7 +80,8 @@ struct app_regs_t uint8_t ForceFSM; int8_t QueuedOdorIndex; uint32_t VacuumCloseTimeUS; - uint32_t OdorDeliveryTimeUS; + uint32_t MinOdorDeliveryTimeUS; + uint32_t MaxOdorDeliveryTimeUS; uint32_t OdorTransitionTimeUS; uint32_t VacuumSetupTimeUS; uint32_t FinalValveEnergizedTimeUS; @@ -130,7 +131,8 @@ void read_fsm_enabled_state(uint8_t reg_address); //void read_force_fsm(uint8_t reg_address); // aliased to read_reg_generic void read_current_odor(uint8_t reg_address); void read_vacuum_close_time_us(uint8_t reg_address); -void read_odor_delivery_time_us(uint8_t reg_address); +void read_min_odor_delivery_time_us(uint8_t reg_address); +void read_max_odor_delivery_time_us(uint8_t reg_address); void read_odor_transition_time_us(uint8_t reg_address); void read_vacuum_setup_time_us(uint8_t reg_address); void read_final_valve_energized_time_us(uint8_t reg_address); @@ -154,7 +156,8 @@ void write_fsm_enabled_state(msg_t& msg); void write_force_fsm(msg_t& msg); void write_current_odor(msg_t& msg); void write_vacuum_close_time_us(msg_t& msg); -void write_odor_delivery_time_us(msg_t& msg); +void write_min_odor_delivery_time_us(msg_t& msg); +void write_max_odor_delivery_time_us(msg_t& msg); void write_odor_transition_time_us(msg_t& msg); void write_vacuum_setup_time_us(msg_t& msg); void write_final_valve_energized_time_us(msg_t& msg); diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 9f244fb..03f4e78 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -92,8 +92,11 @@ class PokeManager inline void set_vacuum_close_time_us(uint32_t vacuum_close_time_us) {vacuum_close_time_us_ = vacuum_close_time_us;} - inline void set_odor_delivery_time_us(uint32_t odor_delivery_time_us) - {odor_delivery_time_us_ = odor_delivery_time_us;} + inline void set_min_odor_delivery_time_us(uint32_t min_odor_delivery_time_us) + {min_odor_delivery_time_us_ = min_odor_delivery_time_us;} + + inline void set_max_odor_delivery_time_us(uint32_t max_odor_delivery_time_us) + {max_odor_delivery_time_us_ = max_odor_delivery_time_us;} void set_odor_transition_time_us(uint32_t odor_transition_time_us) {odor_transition_time_us_ = odor_transition_time_us;} @@ -176,8 +179,11 @@ class PokeManager inline uint32_t get_vacuum_close_time_us() const {return vacuum_close_time_us_;} - inline uint32_t get_odor_delivery_time_us() const - {return odor_delivery_time_us_;} + inline uint32_t get_min_odor_delivery_time_us() const + {return min_odor_delivery_time_us_;} + + inline uint32_t get_max_odor_delivery_time_us() const + {return max_odor_delivery_time_us_;} inline uint32_t get_odor_transition_time_us() const {return odor_transition_time_us_;} @@ -234,7 +240,8 @@ class PokeManager size_t num_odor_valves_; uint32_t vacuum_close_time_us_; - uint32_t odor_delivery_time_us_; + uint32_t min_odor_delivery_time_us_; + uint32_t max_odor_delivery_time_us_; uint32_t odor_transition_time_us_; uint32_t vac_setup_time_us_; uint32_t final_valve_energized_time_us_; @@ -247,7 +254,8 @@ class PokeManager // Declare Constants static inline constexpr uint32_t DEFAULT_VACUUM_CLOSE_TIME_US = 20e3; - static inline constexpr uint32_t DEFAULT_ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t DEFAULT_MIN_ODOR_DELIVERY_TIME_US = 10e3; + static inline constexpr uint32_t DEFAULT_MAX_ODOR_DELIVERY_TIME_US = 10e6; static inline constexpr uint32_t DEFAULT_ODOR_TRANSITION_TIME_US = 30e3; static inline constexpr uint32_t DEFAULT_VACUUM_SETUP_TIME_US = 20e3; static inline constexpr uint32_t DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US = 110e3; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index b14af56..b5aa72f 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -86,7 +86,8 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.ForceFSM, sizeof(app_regs.ForceFSM), U8}, {(uint8_t*)&app_regs.QueuedOdorIndex, sizeof(app_regs.QueuedOdorIndex), S8}, {(uint8_t*)&app_regs.VacuumCloseTimeUS, sizeof(app_regs.VacuumCloseTimeUS), U32}, - {(uint8_t*)&app_regs.OdorDeliveryTimeUS, sizeof(app_regs.OdorDeliveryTimeUS), U32}, + {(uint8_t*)&app_regs.MinOdorDeliveryTimeUS, sizeof(app_regs.MinOdorDeliveryTimeUS), U32}, + {(uint8_t*)&app_regs.MaxOdorDeliveryTimeUS, sizeof(app_regs.MaxOdorDeliveryTimeUS), U32}, {(uint8_t*)&app_regs.VacuumSetupTimeUS, sizeof(app_regs.VacuumSetupTimeUS), U32}, {(uint8_t*)&app_regs.FinalValveEnergizedTimeUS, sizeof(app_regs.FinalValveEnergizedTimeUS), U32}, {(uint8_t*)&app_regs.MinimumPokeTimeUS, sizeof(app_regs.MinimumPokeTimeUS), U32}, @@ -134,7 +135,8 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_force_fsm, write_force_fsm}, {read_current_odor, write_current_odor}, {read_vacuum_close_time_us, write_vacuum_close_time_us}, - {read_odor_delivery_time_us, write_odor_delivery_time_us}, + {read_min_odor_delivery_time_us, write_min_odor_delivery_time_us}, + {read_max_odor_delivery_time_us, write_max_odor_delivery_time_us}, {read_odor_transition_time_us, write_odor_transition_time_us}, {read_vacuum_setup_time_us, write_vacuum_setup_time_us}, {read_final_valve_energized_time_us, write_final_valve_energized_time_us}, @@ -242,17 +244,32 @@ void write_vacuum_close_time_us(msg_t& msg) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void read_odor_delivery_time_us(uint8_t reg_address) +void read_min_odor_delivery_time_us(uint8_t reg_address) { - app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); + app_regs.MinOdorDeliveryTimeUS = poke_manager.get_min_odor_delivery_time_us(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } -void write_odor_delivery_time_us(msg_t& msg) +void write_min_odor_delivery_time_us(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_odor_delivery_time_us(app_regs.OdorDeliveryTimeUS); + poke_manager.set_min_odor_delivery_time_us(app_regs.MinOdorDeliveryTimeUS); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_max_odor_delivery_time_us(uint8_t reg_address) +{ + app_regs.MaxOdorDeliveryTimeUS = poke_manager.get_max_odor_delivery_time_us(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_max_odor_delivery_time_us(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + poke_manager.set_max_odor_delivery_time_us(app_regs.MaxOdorDeliveryTimeUS); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -528,7 +545,8 @@ void reset_app() app_regs.ForceFSM = 0; app_regs.QueuedOdorIndex = poke_manager.get_current_odor(); app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); - app_regs.OdorDeliveryTimeUS = poke_manager.get_odor_delivery_time_us(); + app_regs.MinOdorDeliveryTimeUS = poke_manager.get_min_odor_delivery_time_us(); + app_regs.MaxOdorDeliveryTimeUS = poke_manager.get_max_odor_delivery_time_us(); app_regs.OdorTransitionTimeUS = poke_manager.get_odor_transition_time_us(); app_regs.VacuumSetupTimeUS = poke_manager.get_vacuum_setup_time_us(); app_regs.FinalValveEnergizedTimeUS = poke_manager.get_final_valve_energized_time_us(); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 1dc748a..87ae9ca 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -88,7 +88,8 @@ void PokeManager::reset() request_next_odor_callback_fn_ = nullptr; request_poke_state_callback_fn_ = nullptr; set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); - set_odor_delivery_time_us(DEFAULT_ODOR_DELIVERY_TIME_US); + set_min_odor_delivery_time_us(DEFAULT_MIN_ODOR_DELIVERY_TIME_US); + set_max_odor_delivery_time_us(DEFAULT_MAX_ODOR_DELIVERY_TIME_US); set_odor_transition_time_us(DEFAULT_ODOR_TRANSITION_TIME_US); set_vacuum_setup_time_us(DEFAULT_VACUUM_SETUP_TIME_US); set_final_valve_energized_time_us(DEFAULT_FINAL_VALVE_ENERGIZED_TIME_US); @@ -154,7 +155,7 @@ void PokeManager::update() } break; case ODOR_DELIVERY_TO_FINAL_VALVE: - if (state_duration_us() >= odor_delivery_time_us_) + if ((state_duration_us() >= min_odor_delivery_time_us_ && !poke_initiated_once_) || state_duration_us() >= max_odor_delivery_time_us_) //adjust to determine if the beam is still broken after the poke (up to max) next_state = ODOR_PRECLEAN; break; case ODOR_PRECLEAN: diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 30c270d..68d860e 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -42,10 +42,10 @@ class DelphiOnlyAppRegs(IntEnum): PokeDometer = 62 FSMEnabledState = 63 ForceFSM = 64 - CurrentOdorIndex = 65 - NextOdorIndex = 66 - VacuumCloseTimeUS = 67 - OdorDeliveryTimeUS = 68 + QueuedOdorIndex = 65 + VacuumCloseTimeUS = 66 + MinOdorDeliveryTimeUS = 67 + MaxOdorDeliveryTimeUS = 68 OdorTransitionTimeUS = 69 VacuumSetupTimeUS = 70 FinalValveEnergizedTimeUS = 71 From 988bbdb67976d0554b1b31627b180035047ec9cb Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 17 Sep 2025 11:55:52 -0700 Subject: [PATCH 26/39] added raw poke state register and rise and fall events --- firmware/inc/delphi_controller_app.h | 15 +++++++++-- firmware/inc/poke_manager.h | 27 ++++++++++++++++++- firmware/src/delphi_controller_app.cpp | 36 +++++++++++++++++++++++--- firmware/src/poke_manager.cpp | 24 ++++++++++++++++- software/pyharp/app_registers.py | 23 ++++++++-------- 5 files changed, 106 insertions(+), 19 deletions(-) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 811be8d..15368d7 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -14,7 +14,7 @@ #endif // Setup for Harp App -inline constexpr size_t APP_REG_COUNT = 41; +inline constexpr size_t APP_REG_COUNT = 42; // Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; @@ -75,6 +75,7 @@ struct app_regs_t uint8_t PokePin; uint8_t PokePinInverted; uint8_t PokeState; + uint8_t RawPokeState; uint32_t PokeDometer; uint8_t FSMEnabledState; uint8_t ForceFSM; @@ -103,6 +104,16 @@ void request_next_odor(void); */ void poke_state_changed(void); +/** + * \brief callback function to tell the PC when the beam broke (raw poke) + */ +void raw_poke_rise(void); + +/** + * \brief callback function to tell the PC when the beam broke (raw poke) + */ +void raw_poke_fall(void); + /** * \brief update the app state. Called in a loop. */ @@ -125,8 +136,8 @@ void read_aux_gpio_state(uint8_t reg_address); void read_poke_pin(uint8_t reg_address); void read_poke_pin_inverted(uint8_t reg_address); void read_poke_state(uint8_t reg_address); +void read_raw_poke_state(uint8_t reg_address); void read_pokedometer(uint8_t reg_address); - void read_fsm_enabled_state(uint8_t reg_address); //void read_force_fsm(uint8_t reg_address); // aliased to read_reg_generic void read_current_odor(uint8_t reg_address); diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index 03f4e78..bc57131 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -63,6 +63,7 @@ class PokeManager inline void deenergize_odor_valve() {set_odor_valve_state(0);} + // Event Handlers inline void set_next_odor_callback_fn( void (* fn)(void)) {request_next_odor_callback_fn_ = fn;} @@ -81,6 +82,25 @@ class PokeManager request_poke_state_callback_fn_(); } + // Rise and Fall poke events + inline void set_raw_poke_rise_callback_fn( void (* fn)(void)) + {request_raw_poke_rise_callback_fn_ = fn;} + + inline void raw_poke_rise() + { + if (request_raw_poke_rise_callback_fn_ != nullptr) + request_raw_poke_rise_callback_fn_(); + } + + inline void set_raw_poke_fall_callback_fn( void (* fn)(void)) + {request_raw_poke_fall_callback_fn_ = fn;} + + inline void raw_poke_fall() + { + if (request_raw_poke_fall_callback_fn_ != nullptr) + request_raw_poke_fall_callback_fn_(); + } + /* * \brief enable (true) or disable (false) the odor delivery state machine. */ @@ -148,7 +168,6 @@ class PokeManager void deenergize_all_valves(); - /** * \brief true if the poke pin is inverted. */ @@ -170,6 +189,9 @@ class PokeManager inline uint8_t get_poke_state() const {return poke_state_;} + inline uint8_t get_raw_poke_state() const + {return raw_poke_state_;} + inline size_t get_poke_count() const {return poke_count_;} @@ -229,6 +251,7 @@ class PokeManager size_t poke_count_; uint8_t poke_state_; + uint8_t raw_poke_state_; bool poke_detected_; bool disable_fsm_; bool beam_broken_; //keep track of beam state @@ -249,6 +272,8 @@ class PokeManager void (*request_next_odor_callback_fn_)(void); void (*request_poke_state_callback_fn_)(void); + void (*request_raw_poke_rise_callback_fn_)(void); + void (*request_raw_poke_fall_callback_fn_)(void); bool poke_pin_is_initialized_; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index b5aa72f..fa58dcc 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -81,6 +81,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.PokePin, sizeof(app_regs.PokePin), U8}, {(uint8_t*)&app_regs.PokePinInverted, sizeof(app_regs.PokePinInverted), U8}, {(uint8_t*)&app_regs.PokeState, sizeof(app_regs.PokeState), U8}, + {(uint8_t*)&app_regs.RawPokeState, sizeof(app_regs.RawPokeState), U8}, {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, {(uint8_t*)&app_regs.FSMEnabledState, sizeof(app_regs.FSMEnabledState), U8}, {(uint8_t*)&app_regs.ForceFSM, sizeof(app_regs.ForceFSM), U8}, @@ -88,6 +89,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.VacuumCloseTimeUS, sizeof(app_regs.VacuumCloseTimeUS), U32}, {(uint8_t*)&app_regs.MinOdorDeliveryTimeUS, sizeof(app_regs.MinOdorDeliveryTimeUS), U32}, {(uint8_t*)&app_regs.MaxOdorDeliveryTimeUS, sizeof(app_regs.MaxOdorDeliveryTimeUS), U32}, + {(uint8_t*)&app_regs.OdorTransitionTimeUS, sizeof(app_regs.OdorTransitionTimeUS), U32}, {(uint8_t*)&app_regs.VacuumSetupTimeUS, sizeof(app_regs.VacuumSetupTimeUS), U32}, {(uint8_t*)&app_regs.FinalValveEnergizedTimeUS, sizeof(app_regs.FinalValveEnergizedTimeUS), U32}, {(uint8_t*)&app_regs.MinimumPokeTimeUS, sizeof(app_regs.MinimumPokeTimeUS), U32}, @@ -130,6 +132,7 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_poke_pin, write_poke_pin}, {read_poke_pin_inverted, write_poke_pin_inverted}, {read_poke_state, HarpCore::write_to_read_only_reg_error}, + {read_raw_poke_state, HarpCore::write_to_read_only_reg_error}, {read_pokedometer, HarpCore::write_to_read_only_reg_error}, {read_fsm_enabled_state, write_fsm_enabled_state}, {read_force_fsm, write_force_fsm}, @@ -177,7 +180,15 @@ void write_poke_pin_inverted(msg_t& msg) void read_poke_state(uint8_t reg_address) { // FIXME - app_regs.PokeState = poke_manager.get_poke_state(); // Doesn't exist. + app_regs.PokeState = poke_manager.get_poke_state(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void read_raw_poke_state(uint8_t reg_address) +{ + // FIXME + app_regs.PokeState = poke_manager.get_raw_poke_state(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } @@ -492,7 +503,7 @@ void write_aux_gpio_clear(msg_t& msg) void request_next_odor() { - const uint8_t NEXT_ODOR_INDEX_ADDRESS = 65; // FIXME: this is hardcoded. + const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. app_regs.QueuedOdorIndex = -1; // Mark it as "used." if (!HarpCore::is_muted()) HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); @@ -503,7 +514,23 @@ void poke_state_changed() const uint8_t POKE_STATE_INDEX_ADDRESS = 61; // FIXME: this is hardcoded. app_regs.PokeState = 1; // Mark it as "used." if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS); + HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); +} + +void raw_poke_rise() +{ + const uint8_t POKE_STATE_INDEX_ADDRESS = 62; // FIXME: this is hardcoded. + app_regs.RawPokeState = 1; // Mark it as "used." + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); +} + +void raw_poke_fall() +{ + const uint8_t POKE_STATE_INDEX_ADDRESS = 62; // FIXME: this is hardcoded. + app_regs.RawPokeState = 0; // Mark it as "used." + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } void update_app_state() // Called when app.run() is called -- add poke detection here @@ -540,6 +567,8 @@ void reset_app() poke_manager.reset(); poke_manager.set_next_odor_callback_fn(request_next_odor); poke_manager.set_poke_state_callback_fn(poke_state_changed); + poke_manager.set_raw_poke_rise_callback_fn(raw_poke_rise); + poke_manager.set_raw_poke_fall_callback_fn(raw_poke_fall); app_regs.PokeDometer = poke_manager.get_poke_count(); app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; @@ -576,6 +605,5 @@ void reset_app() old_aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; - } diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 87ae9ca..9df544f 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -6,9 +6,10 @@ PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, num_odor_valves_{num_odor_valves}, state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, odor_valve_index_{-1}, disable_fsm_{false}, -poke_detected_{false}, poke_state_{0}, +poke_detected_{false}, poke_state_{0}, raw_poke_state_{0}, beam_broken_{false}, poke_initiated_once_{false}, request_next_odor_callback_fn_{nullptr}, request_poke_state_callback_fn_{nullptr}, +request_raw_poke_rise_callback_fn_{nullptr}, request_raw_poke_fall_callback_fn_{nullptr}, poke_pin_is_initialized_{false} { reset(); // set timing constants to defaults. @@ -20,6 +21,7 @@ PokeManager::~PokeManager() //destuctor deenergize_all_valves(); poke_count_ = 0; poke_state_ = 0; + raw_poke_state_ = 0; poke_detected_ = false; disable_fsm_ = false; state_ = RESET; @@ -45,6 +47,24 @@ void PokeManager::update_poke_status() beam_broken_ == false; poke_start_time_us_ = time_us_32(); poke_initiated_once_ = false; + + if (raw_poke_state_ == 1) + { + //falling edge event + raw_poke_fall(); + } + raw_poke_state_ = 0; + } + + // Beam broken -- update raw poke state + if (gpio_get(poke_pin_)) + { + if (raw_poke_state_ == 0) + { + //rising edge event + raw_poke_rise(); + } + raw_poke_state_ = 1; } // Poke detected -- start poke timer @@ -87,6 +107,8 @@ void PokeManager::reset() clear_poke_pin(); request_next_odor_callback_fn_ = nullptr; request_poke_state_callback_fn_ = nullptr; + request_raw_poke_rise_callback_fn_ = nullptr; + request_raw_poke_fall_callback_fn_ = nullptr; set_vacuum_close_time_us(DEFAULT_VACUUM_CLOSE_TIME_US); set_min_odor_delivery_time_us(DEFAULT_MIN_ODOR_DELIVERY_TIME_US); set_max_odor_delivery_time_us(DEFAULT_MAX_ODOR_DELIVERY_TIME_US); diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 68d860e..9b6559c 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -39,17 +39,18 @@ class DelphiOnlyAppRegs(IntEnum): PokePin = 59 PokePinInverted = 60 PokeState = 61 - PokeDometer = 62 - FSMEnabledState = 63 - ForceFSM = 64 - QueuedOdorIndex = 65 - VacuumCloseTimeUS = 66 - MinOdorDeliveryTimeUS = 67 - MaxOdorDeliveryTimeUS = 68 - OdorTransitionTimeUS = 69 - VacuumSetupTimeUS = 70 - FinalValveEnergizedTimeUS = 71 - MinimumPokeTimeUS = 72 + RawPokeState = 62 + PokeDometer = 63 + FSMEnabledState = 64 + ForceFSM = 65 + QueuedOdorIndex = 66 + VacuumCloseTimeUS = 67 + MinOdorDeliveryTimeUS = 68 + MaxOdorDeliveryTimeUS = 69 + OdorTransitionTimeUS = 70 + VacuumSetupTimeUS = 71 + FinalValveEnergizedTimeUS = 72 + MinimumPokeTimeUS = 73 DelphiAppRegs = IntEnum("DelphiAppRegs", From d953206cf7ebd59f4ef8af9929469a40e91e0a62 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Fri, 19 Sep 2025 14:21:40 -0700 Subject: [PATCH 27/39] added camera driver firmware --- firmware/CMakeLists.txt | 35 ++++- firmware/inc/config.h | 4 +- firmware/inc/delphi_controller_app.h | 19 ++- firmware/inc/pwm_pio.h | 209 +++++++++++++++++++++++++ firmware/src/delphi_controller_app.cpp | 94 ++++++++++- firmware/src/main.cpp | 12 +- firmware/src/pwm_pio.cpp | 124 +++++++++++++++ software/pyharp/app_registers.py | 5 + 8 files changed, 488 insertions(+), 14 deletions(-) create mode 100644 firmware/inc/pwm_pio.h create mode 100644 firmware/src/pwm_pio.cpp diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index be36812..64a408b 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -1,3 +1,19 @@ +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.2.0) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.2.0) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico CACHE STRING "Board type") + cmake_minimum_required(VERSION 3.13) find_package(Git REQUIRED) execute_process(COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD OUTPUT_VARIABLE COMMIT_ID OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -35,15 +51,22 @@ add_library(poke_manager src/poke_manager.cpp ) +add_library(pwm_pio + src/pwm_pio.cpp +) + include_directories(inc) -target_link_libraries(valve_driver rp2040_pwm pico_stdlib) -target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) +target_link_libraries(valve_driver rp2040_pwm pico_stdlib pico_stdio_usb) +target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl pico_stdio_usb) +target_link_libraries(pwm_pio hardware_pio pico_stdlib pico_stdio_usb) target_link_libraries(${PROJECT_NAME} - harp_core harp_c_app rp2040_pwm poke_manager valve_driver harp_sync - pico_stdlib etl::etl) + harp_core harp_c_app rp2040_pwm poke_manager hardware_pio pwm_pio valve_driver harp_sync + pico_stdlib etl::etl pico_stdio_usb) #pico_stdio_usb -pico_add_extra_outputs(${PROJECT_NAME}) +# Enable USB CDC (COM port) +pico_enable_stdio_usb(${PROJECT_NAME} 1) +pico_enable_stdio_usb(pwm_pio 1) if(DEBUG) message(WARNING "Debug printf() messages from harp core to UART with baud \ @@ -53,3 +76,5 @@ if(DEBUG) # Additional libraries need to have stdio init also. endif() +pico_add_extra_outputs(${PROJECT_NAME}) + diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 4d0b0f8..51b431f 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -3,7 +3,9 @@ #define NUM_VALVES (16) #define NUM_ODOR_VALVES (14) - +#define FINAL_VALVE_INDEX (0) +#define VACCUM_VALVE_INDEX (1) +#define CAM_TRIGGER_PIN (26) #define UART_TX_PIN (0) #define HARP_SYNC_RX_PIN (5) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 15368d7..c747016 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -8,13 +8,14 @@ #include #include #include +#include #ifdef DEBUG #include #include // for printf #endif // Setup for Harp App -inline constexpr size_t APP_REG_COUNT = 42; +inline constexpr size_t APP_REG_COUNT = 47; // Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; @@ -28,6 +29,7 @@ extern HarpCApp& app; extern ValveDriver valve_drivers[NUM_VALVES]; extern PokeManager poke_manager; +extern CameraDriver cam_driver; extern uint8_t old_aux_gpio_inputs; @@ -87,6 +89,11 @@ struct app_regs_t uint32_t VacuumSetupTimeUS; uint32_t FinalValveEnergizedTimeUS; uint32_t MinimumPokeTimeUS; + uint8_t CamPin; + uint8_t CamPinState; + uint32_t FrameRate; + float DutyCycle; + uint8_t EnableCamTrigger; }; #pragma pack(pop) @@ -149,6 +156,11 @@ void read_vacuum_setup_time_us(uint8_t reg_address); void read_final_valve_energized_time_us(uint8_t reg_address); void read_minimum_poke_time_us(uint8_t reg_address); +void read_cam_pin(uint8_t reg_address); +void read_cam_pin_state(uint8_t reg_address); +void read_frame_rate(uint8_t reg_address); +void read_duty_cycle(uint8_t reg_address); +void read_enable_cam_trigger(uint8_t reg_address); void write_valves_state(msg_t& msg); void write_valves_set(msg_t& msg); @@ -174,4 +186,9 @@ void write_vacuum_setup_time_us(msg_t& msg); void write_final_valve_energized_time_us(msg_t& msg); void write_minimum_poke_time_us(msg_t& msg); +void write_cam_pin(msg_t& msg); +void write_frame_rate(msg_t& msg); +void write_duty_cycle(msg_t& msg); +void write_enable_cam_trigger(msg_t& msg); + #endif // DELPHI_CONTROLLER_APP_H diff --git a/firmware/inc/pwm_pio.h b/firmware/inc/pwm_pio.h new file mode 100644 index 0000000..3bcb0a1 --- /dev/null +++ b/firmware/inc/pwm_pio.h @@ -0,0 +1,209 @@ +// ---------------------------------------------------------------- // +// This file is autogenerated by pioasm version 2.2.0; do not edit! // +// ---------------------------------------------------------------- // + +#pragma once + +#include +#include +#include +#include + +// #if !PICO_NO_HARDWARE +// #include +// #include +// #include +// #include +// #endif + +// --- // +// pwm // +// --- // + +#define pwm_wrap_target 0 +#define pwm_wrap 8 +#define pwm_pio_version 0 + +/** + * \brief Configure PIO for PWM + */ +static const uint16_t pwm_program_instructions[] = { + // .wrap_target + 0x80a0, // 0: pull block + 0xa027, // 1: mov x, osr + 0x80a0, // 2: pull block + 0xa047, // 3: mov y, osr + 0xe001, // 4: set pins, 1 + 0x0045, // 5: jmp x--, 5 + 0xe000, // 6: set pins, 0 + 0x0087, // 7: jmp y--, 7 + 0x0000, // 8: jmp 0 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program pwm_program = { + .instructions = pwm_program_instructions, + .length = 9, + .origin = -1, + .pio_version = pwm_pio_version, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config pwm_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap); + return c; +} +#endif + +class CameraDriver +{ +public: + +/** + * \brief constructor. + * \param pwm_pio_pin the pwm output pin to the controller enable pin. + */ + CameraDriver(uint8_t pwm_pio_pin); + +/** + * \brief destructor. + */ + ~CameraDriver(); + +/** + * \brief reset to PIO. + * \details PWM freqency will be set to zero until fps is specified + */ + void reset(); + +/** + * \brief Call periodically in a loop to update the internal finite state + * machine that controls the pwm. + */ + void update(); + +/** + * \brief Initialize PWM on PIO + */ + void pwm_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t pin); + +/** + * \brief Set PWM frequency + */ + void set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq); + +/** + * \brief Camera output pin + */ + inline void set_pio_pwm_pin(uint8_t pwm_pio_pin) + { + clear_pio_pwm_pin(); + // Init new gpio pin. + pwm_pio_pin_ = pwm_pio_pin; + gpio_init(pwm_pio_pin_); + gpio_set_dir(pwm_pio_pin_, true); + pin_is_initialized_ = true; + } + +/** + * \brief Clear current pio pwm pin + */ + inline void clear_pio_pwm_pin() + { + if (!pin_is_initialized_) + return; + gpio_deinit(pwm_pio_pin_); + pwm_pio_pin_= DEFAULT_PIO_PWM_PIN; + pin_is_initialized_ = false; + } + +/** + * \brief Check the triggering state of camera + */ + void check_camera_trigger_state(uint8_t enable); + +/** + * \brief PWM frequency -- Camera FPS + */ + inline void set_pwm_freq(uint32_t pwm_freq) + {pwm_freq_ = pwm_freq;} + +/** + * \brief PWM duty cycle-- shouldn't change from 0.5f + */ + inline void set_pwm_duty_cycle(float pwm_duty) + {pwm_duty_ = pwm_duty;} + +/** + * \brief Enable Camera Triggering + */ + inline void set_enable_state(uint8_t enable_state) + {enable_state_ = enable_state;} + + +// Read functions +/** + * \brief Get PIO PWM PIN + */ + inline uint8_t get_pio_pwm_pin() const + {return pwm_pio_pin_;} + +/** + * \brief Get the state of the PIO PWM pin + */ + inline float get_pwm_pin_state() const + {return pwm_pin_state_;} + +/** + * \brief Get PWM Frequency - current FPS + */ + inline uint32_t get_pwm_freq() const + {return pwm_freq_;} + +/** + * \brief Get PWM Duty Cycle + */ + inline float get_pwm_duty() const + {return pwm_duty_;} + +/** + * \brief Get camera trigger enable state + */ + inline float get_enable_state() const + {return enable_state_;} + + + +private: + +/** + * \brief Detect rise and fall events of PWM signal + */ + void pwm_signal_status(); + + // Declare data members + uint8_t pwm_pio_pin_; + uint8_t pwm_pin_state_; + uint8_t enable_state_; + uint32_t pwm_freq_; + float pwm_duty_; + bool pin_is_initialized_; + PIO pio_; + uint8_t sm_; + bool disabled_; + + // Declare Constants + static inline constexpr float DEFAULT_DUTY_CYCLE = 0.5f; + static inline constexpr uint32_t DEFAULT_FREQ = 60; + static inline constexpr uint8_t DEFAULT_PIO_PWM_PIN = CAM_TRIGGER_PIN; + static inline constexpr uint8_t DEFAULT_PIO_SM = 0; +}; + + + + + diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index fa58dcc..4aa061a 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -93,6 +93,11 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.VacuumSetupTimeUS, sizeof(app_regs.VacuumSetupTimeUS), U32}, {(uint8_t*)&app_regs.FinalValveEnergizedTimeUS, sizeof(app_regs.FinalValveEnergizedTimeUS), U32}, {(uint8_t*)&app_regs.MinimumPokeTimeUS, sizeof(app_regs.MinimumPokeTimeUS), U32}, + {(uint8_t*)&app_regs.CamPin, sizeof(app_regs.CamPin), U8}, + {(uint8_t*)&app_regs.CamPinState, sizeof(app_regs.CamPinState), U8}, + {(uint8_t*)&app_regs.FrameRate, sizeof(app_regs.FrameRate), U32}, + {(uint8_t*)&app_regs.DutyCycle, sizeof(app_regs.DutyCycle), Float}, + {(uint8_t*)&app_regs.EnableCamTrigger, sizeof(app_regs.EnableCamTrigger), U8} }; RegFnPair reg_handler_fns[APP_REG_COUNT] @@ -143,10 +148,83 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_odor_transition_time_us, write_odor_transition_time_us}, {read_vacuum_setup_time_us, write_vacuum_setup_time_us}, {read_final_valve_energized_time_us, write_final_valve_energized_time_us}, - {read_minimum_poke_time_us, write_minimum_poke_time_us} + {read_minimum_poke_time_us, write_minimum_poke_time_us}, + {read_cam_pin, write_cam_pin}, //Start here + {read_cam_pin_state, HarpCore::write_to_read_only_reg_error}, + {read_frame_rate, write_frame_rate}, + {read_duty_cycle, write_duty_cycle}, + {read_enable_cam_trigger, write_enable_cam_trigger} }; +void read_enable_cam_trigger(uint8_t reg_address) +{ + app_regs.EnableCamTrigger = cam_driver.get_enable_state(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_enable_cam_trigger(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + cam_driver.set_enable_state(app_regs.EnableCamTrigger); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_duty_cycle(uint8_t reg_address) +{ + app_regs.DutyCycle = cam_driver.get_pwm_duty(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_duty_cycle(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + cam_driver.set_pwm_duty_cycle(app_regs.DutyCycle); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_frame_rate(uint8_t reg_address) +{ + app_regs.FrameRate = cam_driver.get_pwm_freq(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_frame_rate(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + cam_driver.set_pwm_freq(app_regs.FrameRate); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_cam_pin(uint8_t reg_address) +{ + app_regs.CamPin = cam_driver.get_pio_pwm_pin(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_cam_pin(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + cam_driver.set_pio_pwm_pin(app_regs.CamPin); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} + +void read_cam_pin_state(uint8_t reg_address) +{ + app_regs.CamPinState = cam_driver.get_pwm_pin_state(); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + + void read_poke_pin(uint8_t reg_address) { app_regs.PokePin = poke_manager.get_poke_pin(); @@ -506,7 +584,7 @@ void request_next_odor() const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. app_regs.QueuedOdorIndex = -1; // Mark it as "used." if (!HarpCore::is_muted()) - HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS); + HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } void poke_state_changed() @@ -541,7 +619,9 @@ void update_app_state() // Called when app.run() is called -- add poke detection // Update poke manager FSM poke_manager.update(); - // TODO: Issue poke-related state changes as events. + + // Update Camera Driver FSM + cam_driver.update(); // Process AuxGPIO input changes. // FIXME: do we need to update old_aux_gpio_inputs if we change (write-to) @@ -558,7 +638,6 @@ void update_app_state() // Called when app.run() is called -- add poke detection HarpCore::send_harp_reply(EVENT, AUX_GPIO_RISING_INPUTS_ADDRESS); if (app_regs.AuxGPIOInputFallEvent & app_regs.AuxGPIOFallingInputs) HarpCore::send_harp_reply(EVENT, AUX_GPIO_FALLING_INPUTS_ADDRESS); - } void reset_app() @@ -581,6 +660,13 @@ void reset_app() app_regs.FinalValveEnergizedTimeUS = poke_manager.get_final_valve_energized_time_us(); app_regs.MinimumPokeTimeUS = poke_manager.get_min_poke_time_us(); + //Reset cam driver and all related registers + cam_driver.reset(); + app_regs.CamPinState = cam_driver.get_pwm_pin_state(); + app_regs.FrameRate = cam_driver.get_pwm_freq(); + app_regs.DutyCycle = cam_driver.get_pwm_duty(); + app_regs.EnableCamTrigger = cam_driver.get_enable_state(); + // Reset Harp register struct elements. app_regs.ValvesState = 0; app_regs.ValvesSet = 0; diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 137a042..0d20402 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -8,6 +8,9 @@ #include #include #include +#include +#include +#include #ifdef DEBUG #include // for uart printing #include // for printf @@ -25,8 +28,8 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, reg_handler_fns, APP_REG_COUNT, update_app_state, reset_app); - ValveDriver& final_valve = valve_drivers[0]; // add to config - ValveDriver& vac_valve = valve_drivers[1]; + ValveDriver& final_valve = valve_drivers[FINAL_VALVE_INDEX]; + ValveDriver& vac_valve = valve_drivers[VACCUM_VALVE_INDEX]; // Consider the rest of the valves as odor delivery valves. ValveDriver* odor_valves_start = valve_drivers + 2; ValveDriver (&odor_valves)[] = *reinterpret_cast(odor_valves_start); @@ -34,11 +37,14 @@ HarpCApp& app = HarpCApp::init(HARP_DEVICE_ID, // Pass valves into the poke manager constructor PokeManager poke_manager(final_valve, vac_valve, odor_valves, NUM_ODOR_VALVES); +// Select Cam pin for the CAM DRIVER constuctor +CameraDriver cam_driver(CAM_TRIGGER_PIN); + // Core0 main. int main() { - // Init Synchronizer. + stdio_init_all(); HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); #ifdef DEBUG diff --git a/firmware/src/pwm_pio.cpp b/firmware/src/pwm_pio.cpp new file mode 100644 index 0000000..56e6197 --- /dev/null +++ b/firmware/src/pwm_pio.cpp @@ -0,0 +1,124 @@ +#include + +CameraDriver::CameraDriver(uint8_t pwm_pio_pin) +: pwm_pio_pin_{DEFAULT_PIO_PWM_PIN}, pwm_freq_{DEFAULT_FREQ}, +pwm_duty_{DEFAULT_DUTY_CYCLE}, pin_is_initialized_{false}, +sm_{DEFAULT_PIO_SM}, pio_{pio0}, pwm_pin_state_{0}, +enable_state_{1}, disabled_{false} +{ + reset(); // set timing constants to defaults. +} + +CameraDriver::~CameraDriver() //destuctor +{ + pwm_freq_ = 0; + pwm_pin_state_ = 0; +} + +// Functions to alter the FSM +void CameraDriver::reset() +{ + sm_ = DEFAULT_PIO_SM; + uint8_t offset = pio_add_program(pio_, &pwm_program); + pwm_init(pio_, sm_, offset, pwm_pio_pin_); + pwm_freq_ = DEFAULT_FREQ; + pwm_duty_ = DEFAULT_DUTY_CYCLE; //50% duty cycle +} + +void CameraDriver::pwm_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t pin) + { + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = pwm_program_get_default_config(offset); + sm_config_set_clkdiv(&c, 1.0f); // full speed + sm_config_set_set_pins(&c, pin, 1); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) +{ + // const uint32_t total_us = 16667; // 60 Hz period in µs + if (freq != 0) + { + uint32_t sys_clk = clock_get_hz(clk_sys); + uint32_t period = sys_clk / freq; + uint32_t high_us = (uint32_t)(period * duty_cycle); + uint32_t low_us = period - high_us; + pio_sm_put_blocking(pio, sm, high_us); + pio_sm_put_blocking(pio, sm, low_us); + } + else + { + uint32_t period = 0; + uint32_t high_us = (uint32_t)(period * duty_cycle); + uint32_t low_us = period - high_us; + pio_sm_put_blocking(pio, sm, high_us); + pio_sm_put_blocking(pio, sm, low_us); + } + +} + +void CameraDriver::check_camera_trigger_state(uint8_t enable) +{ + if (enable == 1) + { + disabled_= false; + pio_sm_set_enabled(pio_, sm_, true); + } + else + { + pwm_freq_ = 0; + set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); + pio_sm_set_enabled(pio_, sm_, false); + disabled_= true; + } +} + + +//Rise and Fall events of camera triggering +void CameraDriver::pwm_signal_status() +{ + // Check to see if poke has been detected + // Beam is no longer broken + if (!gpio_get(pwm_pio_pin_)) + { + if (pwm_pin_state_ == 1) + { + //falling edge event + // raw_poke_fall(); + } + pwm_pin_state_ = 0; + } + + // Beam broken -- update raw poke state + if (gpio_get(pwm_pio_pin_)) + { + if (pwm_pin_state_ == 0) + { + //rising edge event + // raw_poke_rise(); + } + pwm_pin_state_ = 1; + } +} + + +void CameraDriver::update() +{ + //check trigger state + check_camera_trigger_state(enable_state_); + + //enabled by default, but if disabled, bail early + if (disabled_) + return; + + // check pin state -- trigger events + pwm_signal_status(); + + // set PWM + set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); + // sleep_ms(1); // see about elimininating +} + diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 9b6559c..1007a42 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -51,6 +51,11 @@ class DelphiOnlyAppRegs(IntEnum): VacuumSetupTimeUS = 71 FinalValveEnergizedTimeUS = 72 MinimumPokeTimeUS = 73 + CamPin = 74 + CamPinState = 75 + FrameRate = 76 + DutyCycle = 77 + EnableCamTrigger = 78 DelphiAppRegs = IntEnum("DelphiAppRegs", From 0e37762b0c1d1fb02c0d9572edf179ce4f08803c Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 22 Sep 2025 12:22:15 -0700 Subject: [PATCH 28/39] fixed camera triggering --- firmware/CMakeLists.txt | 14 ++- firmware/inc/config.h | 4 +- firmware/inc/delphi_controller_app.h | 3 +- firmware/inc/pwm_pio.h | 42 ++++----- firmware/src/delphi_controller_app.cpp | 18 ++-- firmware/src/main.cpp | 3 - firmware/src/pwm_pio.cpp | 120 ++++++++++--------------- 7 files changed, 85 insertions(+), 119 deletions(-) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 64a408b..5fbc176 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -57,16 +57,12 @@ add_library(pwm_pio include_directories(inc) -target_link_libraries(valve_driver rp2040_pwm pico_stdlib pico_stdio_usb) -target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl pico_stdio_usb) -target_link_libraries(pwm_pio hardware_pio pico_stdlib pico_stdio_usb) +target_link_libraries(valve_driver rp2040_pwm pico_stdlib) +target_link_libraries(poke_manager pico_stdlib valve_driver etl::etl) +target_link_libraries(pwm_pio hardware_pio pico_stdlib) target_link_libraries(${PROJECT_NAME} - harp_core harp_c_app rp2040_pwm poke_manager hardware_pio pwm_pio valve_driver harp_sync - pico_stdlib etl::etl pico_stdio_usb) #pico_stdio_usb - -# Enable USB CDC (COM port) -pico_enable_stdio_usb(${PROJECT_NAME} 1) -pico_enable_stdio_usb(pwm_pio 1) + harp_core harp_c_app rp2040_pwm poke_manager hardware_pio hardware_gpio pwm_pio valve_driver harp_sync + pico_stdlib etl::etl) #pico_stdio_usb if(DEBUG) message(WARNING "Debug printf() messages from harp core to UART with baud \ diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 51b431f..f0467e8 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -15,7 +15,9 @@ inline constexpr uint32_t VALVE_PIN_BASE = 6; inline constexpr uint32_t GPIO_PIN_BASE = 22; #define VALVES_MASK (0x0000FFFF) -#define GPIOS_MASK (0x000000FF) +// #define GPIOS_MASK (0x000000FF) +#define GPIOS_MASK_INPUT (0x03C00000) +#define GPIOS_MASK_OUTPUT (0x3C000000) #define HARP_DEVICE_ID (1406) #define HW_VERSION_MAJOR (1) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index c747016..bb7c5fb 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef DEBUG #include #include // for printf @@ -132,7 +133,7 @@ void update_app_state(); void reset_app(); inline uint8_t read_aux_gpios() -{return uint8_t((gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK);} +{return uint8_t((gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK_INPUT);} void read_valves_state(uint8_t reg_address); void read_valves_set(uint8_t reg_address); diff --git a/firmware/inc/pwm_pio.h b/firmware/inc/pwm_pio.h index 3bcb0a1..9cb4655 100644 --- a/firmware/inc/pwm_pio.h +++ b/firmware/inc/pwm_pio.h @@ -89,7 +89,7 @@ class CameraDriver /** * \brief Initialize PWM on PIO */ - void pwm_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t pin); + void pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t enable_state); /** * \brief Set PWM frequency @@ -101,31 +101,24 @@ class CameraDriver */ inline void set_pio_pwm_pin(uint8_t pwm_pio_pin) { - clear_pio_pwm_pin(); + gpio_deinit(DEFAULT_PIO_PWM_PIN); // Init new gpio pin. pwm_pio_pin_ = pwm_pio_pin; gpio_init(pwm_pio_pin_); - gpio_set_dir(pwm_pio_pin_, true); - pin_is_initialized_ = true; + gpio_set_dir(pwm_pio_pin_, 1); + sm_ = DEFAULT_PIO_SM; + uint offset = pio_add_program(pio0, &pwm_program); //FIXME pio0 is hard coded + pwm_init(pio0, sm_, offset, DEFAULT_PIO_PWM_PIN, enable_state_); + pio_gpio_init(pio0, pwm_pio_pin_); + pio_sm_set_consecutive_pindirs(pio0, sm_, pwm_pio_pin_, 1, true); + pio_sm_config c = pwm_program_get_default_config(offset); + sm_config_set_clkdiv(&c, 1.0f); // full speed + sm_config_set_set_pins(&c, pwm_pio_pin_, 1); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + pio_sm_init(pio0, sm_, offset, &c); + pio_sm_set_enabled(pio0, sm_, false); } -/** - * \brief Clear current pio pwm pin - */ - inline void clear_pio_pwm_pin() - { - if (!pin_is_initialized_) - return; - gpio_deinit(pwm_pio_pin_); - pwm_pio_pin_= DEFAULT_PIO_PWM_PIN; - pin_is_initialized_ = false; - } - -/** - * \brief Check the triggering state of camera - */ - void check_camera_trigger_state(uint8_t enable); - /** * \brief PWM frequency -- Camera FPS */ @@ -142,8 +135,7 @@ class CameraDriver * \brief Enable Camera Triggering */ inline void set_enable_state(uint8_t enable_state) - {enable_state_ = enable_state;} - + {pio_sm_set_enabled(pio_, sm_, bool(enable_state));} // Read functions /** @@ -193,14 +185,14 @@ class CameraDriver float pwm_duty_; bool pin_is_initialized_; PIO pio_; - uint8_t sm_; + uint sm_; bool disabled_; // Declare Constants static inline constexpr float DEFAULT_DUTY_CYCLE = 0.5f; static inline constexpr uint32_t DEFAULT_FREQ = 60; static inline constexpr uint8_t DEFAULT_PIO_PWM_PIN = CAM_TRIGGER_PIN; - static inline constexpr uint8_t DEFAULT_PIO_SM = 0; + static inline constexpr uint DEFAULT_PIO_SM = 0; }; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 4aa061a..14002dc 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -533,7 +533,7 @@ void write_aux_gpio_dir(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); // Apply register settings (set bits are outputs; cleared bits are inputs). - gpio_set_dir_masked(uint32_t(GPIOS_MASK) << GPIO_PIN_BASE, + gpio_set_dir_masked(uint32_t(GPIOS_MASK_INPUT) << GPIO_PIN_BASE, uint32_t(app_regs.AuxGPIODir) << GPIO_PIN_BASE); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); @@ -611,6 +611,7 @@ void raw_poke_fall() HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } + void update_app_state() // Called when app.run() is called -- add poke detection here { // Update valve controller state machines. @@ -666,7 +667,7 @@ void reset_app() app_regs.FrameRate = cam_driver.get_pwm_freq(); app_regs.DutyCycle = cam_driver.get_pwm_duty(); app_regs.EnableCamTrigger = cam_driver.get_enable_state(); - + // Reset Harp register struct elements. app_regs.ValvesState = 0; app_regs.ValvesSet = 0; @@ -675,16 +676,21 @@ void reset_app() for (auto& valve_driver: valve_drivers) valve_driver.reset(); - // Init the exposed auxiliary GPIO pins we are using as all-inputs. + // Init the exposed auxiliary GPIO pins we are using as 4-inputs. // This *must* be called once to setup the AUX GPIOs. - gpio_init_mask(GPIOS_MASK << GPIO_PIN_BASE); - gpio_set_dir_masked(GPIOS_MASK << GPIO_PIN_BASE, 0); + gpio_init_mask(GPIOS_MASK_INPUT << GPIO_PIN_BASE); + gpio_set_dir_masked(GPIOS_MASK_INPUT << GPIO_PIN_BASE, 0); app_regs.AuxGPIODir = 0; // All inputs (consistent with what we just set). - app_regs.AuxGPIOState = (gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK; + app_regs.AuxGPIOState = (gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK_INPUT; app_regs.AuxGPIOSet = 0; app_regs.AuxGPIOClear = 0; + // Init the exposed auxiliary GPIO pins we are using as 4-outputs. + // This *must* be called once to setup the AUX GPIOs. + gpio_init_mask(GPIOS_MASK_OUTPUT << (GPIO_PIN_BASE + 4)); + gpio_set_dir_masked(GPIOS_MASK_OUTPUT << (GPIO_PIN_BASE + 4), 1); + // Clear aux input EVENT message configuration. app_regs.AuxGPIORisingInputs = 0; app_regs.AuxGPIOFallingInputs = 0; diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 0d20402..303b318 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -9,8 +9,6 @@ #include #include #include -#include -#include #ifdef DEBUG #include // for uart printing #include // for printf @@ -44,7 +42,6 @@ CameraDriver cam_driver(CAM_TRIGGER_PIN); int main() { // Init Synchronizer. - stdio_init_all(); HarpSynchronizer::init(uart1, HARP_SYNC_RX_PIN); app.set_synchronizer(&HarpSynchronizer::instance()); #ifdef DEBUG diff --git a/firmware/src/pwm_pio.cpp b/firmware/src/pwm_pio.cpp index 56e6197..fc130ab 100644 --- a/firmware/src/pwm_pio.cpp +++ b/firmware/src/pwm_pio.cpp @@ -1,10 +1,10 @@ #include CameraDriver::CameraDriver(uint8_t pwm_pio_pin) -: pwm_pio_pin_{DEFAULT_PIO_PWM_PIN}, pwm_freq_{DEFAULT_FREQ}, +: pwm_pio_pin_{pwm_pio_pin}, pwm_freq_{DEFAULT_FREQ}, pwm_duty_{DEFAULT_DUTY_CYCLE}, pin_is_initialized_{false}, sm_{DEFAULT_PIO_SM}, pio_{pio0}, pwm_pin_state_{0}, -enable_state_{1}, disabled_{false} +enable_state_{0}, disabled_{false} { reset(); // set timing constants to defaults. } @@ -13,19 +13,20 @@ CameraDriver::~CameraDriver() //destuctor { pwm_freq_ = 0; pwm_pin_state_ = 0; + pio_sm_set_enabled(pio_, sm_, false); } // Functions to alter the FSM void CameraDriver::reset() { sm_ = DEFAULT_PIO_SM; - uint8_t offset = pio_add_program(pio_, &pwm_program); - pwm_init(pio_, sm_, offset, pwm_pio_pin_); + uint offset = pio_add_program(pio_, &pwm_program); + pwm_init(pio_, sm_, offset, DEFAULT_PIO_PWM_PIN, enable_state_); //pwm_pio_pin_ pwm_freq_ = DEFAULT_FREQ; pwm_duty_ = DEFAULT_DUTY_CYCLE; //50% duty cycle } -void CameraDriver::pwm_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t pin) +void CameraDriver::pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t enable_state) { pio_gpio_init(pio, pin); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); @@ -34,91 +35,62 @@ void CameraDriver::pwm_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t pin) sm_config_set_set_pins(&c, pin, 1); sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); pio_sm_init(pio, sm, offset, &c); - pio_sm_set_enabled(pio, sm, true); + pio_sm_set_enabled(pio, sm, enable_state); } void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) { // const uint32_t total_us = 16667; // 60 Hz period in µs - if (freq != 0) - { - uint32_t sys_clk = clock_get_hz(clk_sys); - uint32_t period = sys_clk / freq; - uint32_t high_us = (uint32_t)(period * duty_cycle); - uint32_t low_us = period - high_us; - pio_sm_put_blocking(pio, sm, high_us); - pio_sm_put_blocking(pio, sm, low_us); - } - else - { - uint32_t period = 0; - uint32_t high_us = (uint32_t)(period * duty_cycle); - uint32_t low_us = period - high_us; - pio_sm_put_blocking(pio, sm, high_us); - pio_sm_put_blocking(pio, sm, low_us); - } - -} - -void CameraDriver::check_camera_trigger_state(uint8_t enable) -{ - if (enable == 1) - { - disabled_= false; - pio_sm_set_enabled(pio_, sm_, true); - } - else - { - pwm_freq_ = 0; - set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); - pio_sm_set_enabled(pio_, sm_, false); - disabled_= true; - } + uint32_t sys_clk = clock_get_hz(clk_sys); //125000000; + uint32_t period = sys_clk / freq; + uint32_t high_us = (uint32_t)(period * duty_cycle); + uint32_t low_us = period - high_us; + pio_sm_put_blocking(pio, sm, high_us); + pio_sm_put_blocking(pio, sm, low_us); } -//Rise and Fall events of camera triggering -void CameraDriver::pwm_signal_status() -{ - // Check to see if poke has been detected - // Beam is no longer broken - if (!gpio_get(pwm_pio_pin_)) - { - if (pwm_pin_state_ == 1) - { - //falling edge event - // raw_poke_fall(); - } - pwm_pin_state_ = 0; - } +// //Rise and Fall events of camera triggering +// void CameraDriver::pwm_signal_status() +// { +// // Check to see if poke has been detected +// // Beam is no longer broken +// if (!gpio_get(pwm_pio_pin_)) +// { +// if (pwm_pin_state_ == 1) +// { +// //falling edge event +// // raw_poke_fall(); +// } +// pwm_pin_state_ = 0; +// } - // Beam broken -- update raw poke state - if (gpio_get(pwm_pio_pin_)) - { - if (pwm_pin_state_ == 0) - { - //rising edge event - // raw_poke_rise(); - } - pwm_pin_state_ = 1; - } -} +// // Beam broken -- update raw poke state +// if (gpio_get(pwm_pio_pin_)) +// { +// if (pwm_pin_state_ == 0) +// { +// //rising edge event +// // raw_poke_rise(); +// } +// pwm_pin_state_ = 1; +// } +// } void CameraDriver::update() { - //check trigger state - check_camera_trigger_state(enable_state_); - - //enabled by default, but if disabled, bail early - if (disabled_) - return; - // check pin state -- trigger events - pwm_signal_status(); + // pwm_signal_status(); // set PWM - set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); + // set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); + if (!pio_sm_is_tx_fifo_full(pio_, sm_)) + // if (!pio_sm_is_tx_fifo_full(pio0, 0)) + { + set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); + // set_pwm(pio0, 0, 0.5f, 100); + } // sleep_ms(1); // see about elimininating } From 62e07a8c19ecdfb6defff0094afc9f1ca1b74a50 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Tue, 23 Sep 2025 09:49:46 -0700 Subject: [PATCH 29/39] added pwm events and updated register documentation --- device.yml | 66 +++++++++++++------ firmware/CMakeLists.txt | 1 + firmware/inc/delphi_controller_app.h | 33 +++++++++- firmware/inc/pwm_pio.h | 58 ++++++++++++----- firmware/src/delphi_controller_app.cpp | 67 ++++++++++++++++++- firmware/src/pwm_pio.cpp | 42 ++++++------ software/pyharp/test_valve_timings.py | 89 ++++++++++++++++++++++++++ 7 files changed, 295 insertions(+), 61 deletions(-) create mode 100644 software/pyharp/test_valve_timings.py diff --git a/device.yml b/device.yml index 997e108..cc74003 100644 --- a/device.yml +++ b/device.yml @@ -152,62 +152,92 @@ registers: address: 61 type: U8 access: Event - description: "The raw state of all poke ports. Upon receiving a poke (rising-edge), this register will issue an event containing the current poke state." - PokeDometer: + description: "The state of the poke port. An event will be triggered given a poke/ beam break that is greater than the min poke time." + RawPokeState: address: 62 + type: U8 + access: Event + description: "The raw state of the poke pin. Events will be triggered at the onset of a beam break (1) and offset (0)." + PokeDometer: + address: 63 type: U32 access: Read description: "number of mouse pokes per port since boot or reset." FSMState: - address: 63 + address: 64 type: U8 access: Write description: "Enable (1) (aka reset) or Disable (0) the poke handling state machine. Note that CurrentOdorIndex must be specified first. Disabling and then enabling a previously-enabled FSM will reset it to its starting state." ForceFSM: - address: 64 + address: 65 type: U8 access: Write description: "Force the poke handling state machine to iterate as if handling a mouse poke. PokeDometers are not incremented." - CurrentOdorIndex: - address: 65 - type: S8 - access: Write - description: "The current odor being dispensed to the odor port. Must be specified before enabling the state machine." - NextOdorIndex: + QueuedOdorIndex: address: 66 type: S8 - access: Event - description: "The next odor to be dispensed to the odor port after. Must be specified before the Odor Delivery FSM finishes a cycle. An event will issue from this register when it is consumed." + access: Write + description: "Queued odor (value: odor valve index) that will be delievered to the odor port given a register poke. After an odor has been dispensed, the register will be set to -1, which indicates that a new odor is needed" VacuumCloseTimeUS: address: 67 type: U32 access: Write description: "Time alotted (in microseconds) for the vacuum valve to close." - OdorDeliveryTimeUS: + MinOdorDeliveryTimeUS: address: 68 type: U32 access: Write - description: "Time alotted (in microseconds) for the odor delivery state." - OdorTransitionTimeUS: + description: "Minimum time alotted (in microseconds) for the odor delivery state." + MaxOdorDeliveryTimeUS: address: 69 type: U32 access: Write + description: "Maximum time alotted (in microseconds) for the odor delivery state." + OdorTransitionTimeUS: + address: 70 + type: U32 + access: Write description: "Time alotted (in microseconds) before the vacuum turns on to remove the current odor." VacuumSetupTimeUS: - address: 70 + address: 71 type: U32 access: Write description: "Time alotted (in microseconds) for the vacuum to open." FinalValveEnergizedTimeUS: - address: 71 + address: 72 type: U32 access: Write description: "Time alotted (in microseconds) for the final valve to open and remain on." MinimumPokeTimeUS: - address: 72 + address: 73 type: U32 access: Write description: "Minimum time (in microseconds) necessary for a mouse poke port beam to be broken before being interpretted as a poke." + CamPin: + address: 74 + type: U8 + access: Write + description: "The GPIO output pin used for camera triggering. Default pin is 26." + CamPinState: + address: 75 + type: U8 + access: Event + description: "Event is initiated when a rising edge of the camera triggered signal (PWM) is detected. The actual value of the pin doesn't change." + FrameRate: + address: 76 + type: U32 + access: Write + description: "Set the frame rate of the camera trigger/ frequency of the PWM signal." + DutyCycle: + address: 77 + type: Float + access: Write + description: "Set the duty cycle of the PWM. Default and recommend is 0.5 for producing a square wave." + EnableCamTrigger: + address: 78 + type: U8 + access: Write + description: "Enable (1) and disable (0) camera triggering/ the PWM signal." groupMasks: ValveMask: diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 5fbc176..c02e046 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -69,6 +69,7 @@ if(DEBUG) rate 921600.") pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf. pico_enable_stdio_uart(poke_manager 1) + pico_enable_stdio_uart(pwm_pio 1) # Additional libraries need to have stdio init also. endif() diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index bb7c5fb..a6144af 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -34,6 +34,13 @@ extern CameraDriver cam_driver; extern uint8_t old_aux_gpio_inputs; +// struct for HARP event queueing +static inline constexpr uint8_t CAM_PIN_STATE_INDEX_ADDRESS = 75; +struct HarpEvent { + uint8_t index; + uint64_t timestamp; +}; + // Valve configuration struct for configuring the Hit-and-hold driver #pragma pack(push, 1) struct ValveConfig @@ -100,7 +107,6 @@ struct app_regs_t extern app_regs_t app_regs; - /** * \brief callback function to tell the PC we need another odor from * within the PokeManager state machine logic. @@ -122,6 +128,31 @@ void raw_poke_rise(void); */ void raw_poke_fall(void); + +// USED FOR EVENTS WHEN POOLING THE STATE OF THE PIN +// /** +// * \brief callback function to tell the PC when the rising edge of the camera happened +// */ +// void rising_edge_detected(void); + +// /** +// * \brief callback function to tell the PC when the falling edge of the camera happened +// */ +// void falling_edge_detected(void); + + +/** + * \brief callback for camera timestamp + */ +void camera_timestamp_callback(uint gpio, uint32_t events); + + +/** + * \brief function for queueing HARP events + */ +void push_event_from_isr(uint8_t index, uint64_t timestamp); +bool pop_event(HarpEvent &event); + /** * \brief update the app state. Called in a loop. */ diff --git a/firmware/inc/pwm_pio.h b/firmware/inc/pwm_pio.h index 9cb4655..3c983d6 100644 --- a/firmware/inc/pwm_pio.h +++ b/firmware/inc/pwm_pio.h @@ -9,13 +9,6 @@ #include #include -// #if !PICO_NO_HARDWARE -// #include -// #include -// #include -// #include -// #endif - // --- // // pwm // // --- // @@ -91,6 +84,42 @@ class CameraDriver */ void pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t enable_state); +/** + * \brief Initialize edge detection + */ + void edge_detection_init(PIO pio, uint sm_edge, uint8_t pin, uint8_t enable_state); + +/** + * \brief Edge detection + */ + void process_edges(PIO pio, uint sm_edge); + + +// FOR POOLING EVENTS +/** +// * \brief rise event handler +// */ +// inline void set_pwm_rise_callback_fn( void (* fn)(void)) +// {request_pwm_rise_callback_fn_ = fn;} + +// inline void rising_edge_detected() +// { +// if (request_pwm_rise_callback_fn_ != nullptr) +// request_pwm_rise_callback_fn_(); +// } + +// /** +// * \brief fall event handler +// */ +// inline void set_pwm_fall_callback_fn( void (* fn)(void)) +// {request_pwm_fall_callback_fn_ = fn;} + +// inline void falling_edge_detected() +// { +// if (request_pwm_fall_callback_fn_ != nullptr) +// request_pwm_fall_callback_fn_(); +// } + /** * \brief Set PWM frequency */ @@ -108,15 +137,7 @@ class CameraDriver gpio_set_dir(pwm_pio_pin_, 1); sm_ = DEFAULT_PIO_SM; uint offset = pio_add_program(pio0, &pwm_program); //FIXME pio0 is hard coded - pwm_init(pio0, sm_, offset, DEFAULT_PIO_PWM_PIN, enable_state_); - pio_gpio_init(pio0, pwm_pio_pin_); - pio_sm_set_consecutive_pindirs(pio0, sm_, pwm_pio_pin_, 1, true); - pio_sm_config c = pwm_program_get_default_config(offset); - sm_config_set_clkdiv(&c, 1.0f); // full speed - sm_config_set_set_pins(&c, pwm_pio_pin_, 1); - sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); - pio_sm_init(pio0, sm_, offset, &c); - pio_sm_set_enabled(pio0, sm_, false); + pwm_init(pio0, sm_, offset, pwm_pio_pin_, false); } /** @@ -186,7 +207,10 @@ class CameraDriver bool pin_is_initialized_; PIO pio_; uint sm_; - bool disabled_; + + //FOR POOLING EVENTS + // void (*request_pwm_rise_callback_fn_)(void); + // void (*request_pwm_fall_callback_fn_)(void); // Declare Constants static inline constexpr float DEFAULT_DUTY_CYCLE = 0.5f; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 14002dc..0c47454 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -212,7 +212,8 @@ void read_cam_pin(uint8_t reg_address) void write_cam_pin(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - cam_driver.set_pio_pwm_pin(app_regs.CamPin); + cam_driver.set_pio_pwm_pin(app_regs.CamPin); //disable previous camera pin + gpio_set_irq_enabled_with_callback(app_regs.CamPin, GPIO_IRQ_EDGE_RISE, true, &camera_timestamp_callback); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -224,7 +225,6 @@ void read_cam_pin_state(uint8_t reg_address) HarpCore::send_harp_reply(READ, reg_address); } - void read_poke_pin(uint8_t reg_address) { app_regs.PokePin = poke_manager.get_poke_pin(); @@ -611,6 +611,51 @@ void raw_poke_fall() HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } +// FOR EVENTS WHEN POOLING +// void rising_edge_detected() +// { +// const uint8_t CAM_PIN_STATE_INDEX_ADDRESS = 75; // FIXME: this is hardcoded. +// app_regs.CamPinState = 1; // 1:rise +// if (!HarpCore::is_muted()) +// HarpCore::send_harp_reply(EVENT, CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); +// } + +// void falling_edge_detected() +// { +// const uint8_t CAM_PIN_STATE_INDEX_ADDRESS = 75; // FIXME: this is hardcoded.. +// app_regs.CamPinState = 0; // 0:fall +// if (!HarpCore::is_muted()) +// HarpCore::send_harp_reply(EVENT, CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); +// } + +void camera_timestamp_callback(uint gpio, uint32_t events) { + // // Toggle LED for testing + // gpio_put(25, !gpio_get(25)); + push_event_from_isr(CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); + +} + +#define QUEUE_SIZE 64 +volatile HarpEvent eventQueue[QUEUE_SIZE]; +volatile uint8_t head = 0; +volatile uint8_t tail = 0; + +void push_event_from_isr(uint8_t index, uint64_t timestamp) { + uint8_t next = (head + 1) % QUEUE_SIZE; + if (next != tail) { // Prevent overflow + eventQueue[head].index = index; + eventQueue[head].timestamp = timestamp; + head = next; + } +} + +bool pop_event(HarpEvent &event) { + if (tail == head) return false; // Queue is empty + event.index = eventQueue[tail].index; + event.timestamp = eventQueue[tail].timestamp; + tail = (tail + 1) % QUEUE_SIZE; + return true; +} void update_app_state() // Called when app.run() is called -- add poke detection here { @@ -624,6 +669,14 @@ void update_app_state() // Called when app.run() is called -- add poke detection // Update Camera Driver FSM cam_driver.update(); + // Handle harp events + HarpEvent evt; + while (pop_event(evt)) { + if (!HarpCore::is_muted()) { + HarpCore::send_harp_reply(EVENT, evt.index, evt.timestamp); + } + } + // Process AuxGPIO input changes. // FIXME: do we need to update old_aux_gpio_inputs if we change (write-to) // app_regs.AuxGPIODir ? @@ -663,11 +716,17 @@ void reset_app() //Reset cam driver and all related registers cam_driver.reset(); + // cam_driver.set_pwm_rise_callback_fn(rising_edge_detected); //USED FOR POOLING EVENTS + // cam_driver.set_pwm_fall_callback_fn(falling_edge_detected); // USED FOR POOLING EVENTS app_regs.CamPinState = cam_driver.get_pwm_pin_state(); app_regs.FrameRate = cam_driver.get_pwm_freq(); app_regs.DutyCycle = cam_driver.get_pwm_duty(); app_regs.EnableCamTrigger = cam_driver.get_enable_state(); - + + // FOR TESTING -- LED blinking + // gpio_init(25); + // gpio_set_dir(25, GPIO_OUT); + // Reset Harp register struct elements. app_regs.ValvesState = 0; app_regs.ValvesSet = 0; @@ -697,5 +756,7 @@ void reset_app() old_aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; + // gpio_set_irq_enabled_with_callback(26, GPIO_IRQ_EDGE_RISE, true, &camera_timestamp_callback); + } diff --git a/firmware/src/pwm_pio.cpp b/firmware/src/pwm_pio.cpp index fc130ab..5bbe20b 100644 --- a/firmware/src/pwm_pio.cpp +++ b/firmware/src/pwm_pio.cpp @@ -4,7 +4,7 @@ CameraDriver::CameraDriver(uint8_t pwm_pio_pin) : pwm_pio_pin_{pwm_pio_pin}, pwm_freq_{DEFAULT_FREQ}, pwm_duty_{DEFAULT_DUTY_CYCLE}, pin_is_initialized_{false}, sm_{DEFAULT_PIO_SM}, pio_{pio0}, pwm_pin_state_{0}, -enable_state_{0}, disabled_{false} +enable_state_{0} // request_pwm_rise_callback_fn_{nullptr}, request_pwm_fall_callback_fn_{nullptr} { reset(); // set timing constants to defaults. } @@ -19,13 +19,26 @@ CameraDriver::~CameraDriver() //destuctor // Functions to alter the FSM void CameraDriver::reset() { + // request_pwm_rise_callback_fn_ = nullptr; // FOR POOLING EVENTS + // request_pwm_fall_callback_fn_ = nullptr; sm_ = DEFAULT_PIO_SM; uint offset = pio_add_program(pio_, &pwm_program); - pwm_init(pio_, sm_, offset, DEFAULT_PIO_PWM_PIN, enable_state_); //pwm_pio_pin_ + pwm_init(pio_, sm_, offset, pwm_pio_pin_, enable_state_); pwm_freq_ = DEFAULT_FREQ; pwm_duty_ = DEFAULT_DUTY_CYCLE; //50% duty cycle } +void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) +{ + // const uint32_t total_us = 16667; // 60 Hz period in µs + uint32_t sys_clk = clock_get_hz(clk_sys); //125000000; + uint32_t period = sys_clk / freq; + uint32_t high_us = (uint32_t)(period * duty_cycle); + uint32_t low_us = period - high_us; + pio_sm_put_blocking(pio, sm, high_us); + pio_sm_put_blocking(pio, sm, low_us); +} + void CameraDriver::pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t enable_state) { pio_gpio_init(pio, pin); @@ -38,19 +51,8 @@ void CameraDriver::pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t pio_sm_set_enabled(pio, sm, enable_state); } -void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) -{ - // const uint32_t total_us = 16667; // 60 Hz period in µs - uint32_t sys_clk = clock_get_hz(clk_sys); //125000000; - uint32_t period = sys_clk / freq; - uint32_t high_us = (uint32_t)(period * duty_cycle); - uint32_t low_us = period - high_us; - pio_sm_put_blocking(pio, sm, high_us); - pio_sm_put_blocking(pio, sm, low_us); -} - -// //Rise and Fall events of camera triggering +// //Pooling - Rise and Fall edges of camera triggering // void CameraDriver::pwm_signal_status() // { // // Check to see if poke has been detected @@ -60,7 +62,7 @@ void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) // if (pwm_pin_state_ == 1) // { // //falling edge event -// // raw_poke_fall(); +// falling_edge_detected(); // } // pwm_pin_state_ = 0; // } @@ -71,7 +73,7 @@ void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) // if (pwm_pin_state_ == 0) // { // //rising edge event -// // raw_poke_rise(); +// rising_edge_detected(); // } // pwm_pin_state_ = 1; // } @@ -80,17 +82,13 @@ void CameraDriver::set_pwm(PIO pio, uint sm, float duty_cycle, uint32_t freq) void CameraDriver::update() { - // check pin state -- trigger events - // pwm_signal_status(); + // USING A ISR INSTEAD: check pin state -- trigger events + // pwm_signal_status(); // polling for events works // set PWM - // set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); if (!pio_sm_is_tx_fifo_full(pio_, sm_)) - // if (!pio_sm_is_tx_fifo_full(pio0, 0)) { set_pwm(pio_, sm_, pwm_duty_, pwm_freq_); - // set_pwm(pio0, 0, 0.5f, 100); } - // sleep_ms(1); // see about elimininating } diff --git a/software/pyharp/test_valve_timings.py b/software/pyharp/test_valve_timings.py new file mode 100644 index 0000000..3dacd43 --- /dev/null +++ b/software/pyharp/test_valve_timings.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +from pyharp.device import Device, DeviceMode +from pyharp.messages import HarpMessage +from pyharp.messages import MessageType +from app_registers import AppRegs, DelphiOnlyAppRegs +from struct import pack, unpack +from time import sleep +import os +import serial.tools.list_ports + +import logging +logger = logging.getLogger() +logger.addHandler(logging.StreamHandler()) + +# functions +def print_poke_counts( + device, +): + reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.PokeDometer).frame) + print(f"Current pokedometer count is: {reply.payload}.") + return None + +# Open serial connection with the first Valve Controller. +com_port = 'COM3' #None +device = Device(com_port) +device.info() # Display device's info on screen + +print() +print("Enabling all aux gpios as inputs.") +gpio_dir = 0b00000000 +reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIODir, gpio_dir).frame) +print(f"reply: {reply.payload[0]:08b}") +print() +print_poke_counts(device) +print(f"Setting odor.") +reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, 0).frame) +print(f"Assigning poke pin.") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePin, 22).frame) +print(f"Inverting poke pin.") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePinInverted, 1).frame) +print("Enabling FSM") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.FSMEnabledState, 1).frame) +print("Camera Pin") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.CamPin, 26).frame) +print("FPS") +reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 10).frame) +print("Duty Cycle") +reply = device.send(HarpMessage.WriteFloat(DelphiOnlyAppRegs.DutyCycle, 0.5).frame) +print("Enable") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 1).frame) + +'''Set Timings''' +# print("Set Odor Delivery Time") +# reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.OdorDeliveryTimeUS, 1000000).frame) +# print("Set Final Valve Energized Time") +# reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FinalValveEnergizedTimeUS, 20000).frame) +# print("Set Vacumm Setup Time") +# reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.VacuumSetupTimeUS, 20000).frame) + +print() +odor_i = 0 +try: + while True: + for msg in device.get_events(): + print(msg) + print() + print_poke_counts(device) + + # odor is depleted -- assign a new one + # fps = device.send(HarpMessage.ReadU32(DelphiOnlyAppRegs.FrameRate).frame) + # print(f'FPS: {fps.payload[0]}') + + # cam = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.CamPin).frame) + # print(f'CAM: {cam.payload[0]}') + # if poke_status.payload[0] != 0: + # print(f'Poke State: {poke_status.payload[0]}') + + reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.QueuedOdorIndex).frame) + if reply.payload[0] == -1: + odor_i+=1 + if odor_i > 3: + odor_i = 0 + print(f'New odor index: {odor_i}') + reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) + +except KeyboardInterrupt: + print("Disabling FSM.") + reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.FSMEnabledState, 0).frame) + device.disconnect() From dfce105c9dfff933f804b8d202378816b071eafa Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Fri, 26 Sep 2025 15:22:29 -0700 Subject: [PATCH 30/39] added register for enabling or disabling valve LEDs --- device.yml | 5 +++++ firmware/inc/config.h | 1 + firmware/inc/delphi_controller_app.h | 5 ++++- firmware/src/delphi_controller_app.cpp | 31 +++++++++++++++++++++----- software/pyharp/app_registers.py | 1 + software/pyharp/test_valve_timings.py | 4 +++- 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/device.yml b/device.yml index cc74003..7a653bf 100644 --- a/device.yml +++ b/device.yml @@ -238,6 +238,11 @@ registers: type: U8 access: Write description: "Enable (1) and disable (0) camera triggering/ the PWM signal." + EnableValveLeds: + address: 79 + type: U8 + access: Write + description: "Enable (1) and disable (0) valve LEDs." groupMasks: ValveMask: diff --git a/firmware/inc/config.h b/firmware/inc/config.h index f0467e8..0c99ea4 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -6,6 +6,7 @@ #define FINAL_VALVE_INDEX (0) #define VACCUM_VALVE_INDEX (1) #define CAM_TRIGGER_PIN (26) +#define LED_ENABLE_PIN (4) #define UART_TX_PIN (0) #define HARP_SYNC_RX_PIN (5) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index a6144af..5260be0 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -16,7 +16,7 @@ #endif // Setup for Harp App -inline constexpr size_t APP_REG_COUNT = 47; +inline constexpr size_t APP_REG_COUNT = 48; // Numeric addresses for Harp Registers (clunky) -- DO ALL NEW REGISTERS NEED TO BE REFERENCED TO THESE?? inline constexpr size_t VALVE_START_APP_ADDRESS = APP_REG_START_ADDRESS + 3; inline constexpr size_t LAST_VALVE_APP_ADDRESS = VALVE_START_APP_ADDRESS + NUM_VALVES - 1; @@ -102,6 +102,7 @@ struct app_regs_t uint32_t FrameRate; float DutyCycle; uint8_t EnableCamTrigger; + uint8_t EnableValveLeds; }; #pragma pack(pop) @@ -193,6 +194,7 @@ void read_cam_pin_state(uint8_t reg_address); void read_frame_rate(uint8_t reg_address); void read_duty_cycle(uint8_t reg_address); void read_enable_cam_trigger(uint8_t reg_address); +void read_valve_leds(uint8_t reg_address); void write_valves_state(msg_t& msg); void write_valves_set(msg_t& msg); @@ -222,5 +224,6 @@ void write_cam_pin(msg_t& msg); void write_frame_rate(msg_t& msg); void write_duty_cycle(msg_t& msg); void write_enable_cam_trigger(msg_t& msg); +void write_valve_leds(msg_t& msg); #endif // DELPHI_CONTROLLER_APP_H diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 0c47454..2c888ab 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -2,6 +2,7 @@ app_regs_t app_regs; uint8_t old_aux_gpio_inputs; +uint8_t led_state; // Create function aliases for readability. @@ -97,7 +98,8 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.CamPinState, sizeof(app_regs.CamPinState), U8}, {(uint8_t*)&app_regs.FrameRate, sizeof(app_regs.FrameRate), U32}, {(uint8_t*)&app_regs.DutyCycle, sizeof(app_regs.DutyCycle), Float}, - {(uint8_t*)&app_regs.EnableCamTrigger, sizeof(app_regs.EnableCamTrigger), U8} + {(uint8_t*)&app_regs.EnableCamTrigger, sizeof(app_regs.EnableCamTrigger), U8}, + {(uint8_t*)&app_regs.EnableValveLeds, sizeof(app_regs.EnableValveLeds), U8} }; RegFnPair reg_handler_fns[APP_REG_COUNT] @@ -153,9 +155,24 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_cam_pin_state, HarpCore::write_to_read_only_reg_error}, {read_frame_rate, write_frame_rate}, {read_duty_cycle, write_duty_cycle}, - {read_enable_cam_trigger, write_enable_cam_trigger} + {read_enable_cam_trigger, write_enable_cam_trigger}, + {read_valve_leds, write_valve_leds} }; +void read_valve_leds(uint8_t reg_address) +{ + app_regs.EnableValveLeds = gpio_get(LED_ENABLE_PIN); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(READ, reg_address); +} + +void write_valve_leds(msg_t& msg) +{ + HarpCore::copy_msg_payload_to_register(msg); + gpio_put(LED_ENABLE_PIN, app_regs.EnableValveLeds); + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(WRITE, msg.header.address); +} void read_enable_cam_trigger(uint8_t reg_address) { @@ -632,7 +649,6 @@ void camera_timestamp_callback(uint gpio, uint32_t events) { // // Toggle LED for testing // gpio_put(25, !gpio_get(25)); push_event_from_isr(CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); - } #define QUEUE_SIZE 64 @@ -724,8 +740,13 @@ void reset_app() app_regs.EnableCamTrigger = cam_driver.get_enable_state(); // FOR TESTING -- LED blinking - // gpio_init(25); - // gpio_set_dir(25, GPIO_OUT); + // gpio_init(LED_ENABLE_PIN); + // gpio_set_dir(LED_ENABLE_PIN, GPIO_OUT); + + // Valve LED state + gpio_init(LED_ENABLE_PIN); + gpio_set_dir(LED_ENABLE_PIN, GPIO_OUT); + gpio_put(LED_ENABLE_PIN, 0); // Reset Harp register struct elements. app_regs.ValvesState = 0; diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 1007a42..6e18228 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -56,6 +56,7 @@ class DelphiOnlyAppRegs(IntEnum): FrameRate = 76 DutyCycle = 77 EnableCamTrigger = 78 + EnableValveLeds = 79 DelphiAppRegs = IntEnum("DelphiAppRegs", diff --git a/software/pyharp/test_valve_timings.py b/software/pyharp/test_valve_timings.py index 3dacd43..ac3e21c 100644 --- a/software/pyharp/test_valve_timings.py +++ b/software/pyharp/test_valve_timings.py @@ -21,7 +21,7 @@ def print_poke_counts( return None # Open serial connection with the first Valve Controller. -com_port = 'COM3' #None +com_port = 'COM4' #'COM3' #None device = Device(com_port) device.info() # Display device's info on screen @@ -48,6 +48,8 @@ def print_poke_counts( reply = device.send(HarpMessage.WriteFloat(DelphiOnlyAppRegs.DutyCycle, 0.5).frame) print("Enable") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 1).frame) +print("Enable Valve LEDS") +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 0).frame) '''Set Timings''' # print("Set Odor Delivery Time") From 3802221f5887f9c28d84fd698d64dedf1e899bbf Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Fri, 10 Oct 2025 12:26:00 -0700 Subject: [PATCH 31/39] Fixed event-based odor indexing and minor documentation errors --- device.yml | 2 +- firmware/inc/poke_manager.h | 9 +++-- firmware/src/poke_manager.cpp | 23 +++++++------ software/pyharp/test_valve_timings.py | 48 +++++++++++++++------------ 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/device.yml b/device.yml index 7a653bf..63d0049 100644 --- a/device.yml +++ b/device.yml @@ -167,7 +167,7 @@ registers: address: 64 type: U8 access: Write - description: "Enable (1) (aka reset) or Disable (0) the poke handling state machine. Note that CurrentOdorIndex must be specified first. Disabling and then enabling a previously-enabled FSM will reset it to its starting state." + description: "Enable (1) (aka reset) or Disable (0) the poke handling state machine. Note that QueuedOdorIndex must be specified first. Disabling and then enabling a previously-enabled FSM will reset it to its starting state." ForceFSM: address: 65 type: U8 diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index bc57131..d81c886 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -58,10 +58,14 @@ class PokeManager } inline void energize_odor_valve() - {set_odor_valve_state(1);} + {set_odor_valve_state(1); + valve_state_ = true; + } inline void deenergize_odor_valve() - {set_odor_valve_state(0);} + {set_odor_valve_state(0); + valve_state_ = false; + } // Event Handlers inline void set_next_odor_callback_fn( void (* fn)(void)) @@ -252,6 +256,7 @@ class PokeManager size_t poke_count_; uint8_t poke_state_; uint8_t raw_poke_state_; + bool valve_state_; bool poke_detected_; bool disable_fsm_; bool beam_broken_; //keep track of beam state diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 9df544f..1bceec1 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -10,7 +10,7 @@ poke_detected_{false}, poke_state_{0}, raw_poke_state_{0}, beam_broken_{false}, poke_initiated_once_{false}, request_next_odor_callback_fn_{nullptr}, request_poke_state_callback_fn_{nullptr}, request_raw_poke_rise_callback_fn_{nullptr}, request_raw_poke_fall_callback_fn_{nullptr}, -poke_pin_is_initialized_{false} +poke_pin_is_initialized_{false}, valve_state_{false} { reset(); // set timing constants to defaults. } @@ -27,6 +27,7 @@ PokeManager::~PokeManager() //destuctor state_ = RESET; beam_broken_ = false; poke_initiated_once_ = false; + valve_state_ = false; } void PokeManager::deenergize_all_valves() @@ -96,6 +97,7 @@ void PokeManager::update_poke_status() // Functions to alter the FSM void PokeManager::reset() { + state_ = RESET; deenergize_all_valves(); disable(); odor_valve_index_ = -1; @@ -104,6 +106,7 @@ void PokeManager::reset() poke_detected_ = false; beam_broken_ = false; poke_initiated_once_ = false; + valve_state_ = false; clear_poke_pin(); request_next_odor_callback_fn_ = nullptr; request_poke_state_callback_fn_ = nullptr; @@ -131,6 +134,7 @@ void PokeManager::set_enabled_state(bool enabled) poke_state_ = 0; beam_broken_ = false; poke_initiated_once_ = false; + valve_state_ = false; } } @@ -140,13 +144,6 @@ void PokeManager::update() if (disable_fsm_) return; - //initialize RESET state - if (state_ == RESET) //need to query state_ for initital - { - // state_entry_time_us_ = time_us_32(); - deenergize_all_valves(); - } - // check for poke update_poke_status(); @@ -156,15 +153,20 @@ void PokeManager::update() switch (state_) { case RESET: + //initialize RESET state by turning off all valves + deenergize_all_valves(); next_state = ODOR_SETUP; break; case ODOR_SETUP: if (odor_valve_index_ < 0){ - request_next_odor(); // The odor should be primed before a poke poke_detected_ = false; poke_state_ = 0; } + + else if (odor_valve_index_ != -1 && !valve_state_){ + energize_odor_valve(); // Need to initiated the event that the odor was consumed before this + } else if (state_duration_us() >= vacuum_close_time_us_ && odor_valve_index_ != -1) { next_state = ODOR_DISPENSING_TO_EXHAUST; @@ -210,7 +212,6 @@ void PokeManager::update() { // explicitly grab odor in the queue deenergize_all_valves(); - energize_odor_valve(); } // Don't need to do anything because odor is being sent to exhaust and @@ -244,6 +245,8 @@ void PokeManager::update() // Energize the final valve final_valve_.energize(); odor_valve_index_ = -1; // Consume queued odor. + request_next_odor(); //request + #if(DEBUG) printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index #endif diff --git a/software/pyharp/test_valve_timings.py b/software/pyharp/test_valve_timings.py index ac3e21c..af85651 100644 --- a/software/pyharp/test_valve_timings.py +++ b/software/pyharp/test_valve_timings.py @@ -43,13 +43,13 @@ def print_poke_counts( print("Camera Pin") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.CamPin, 26).frame) print("FPS") -reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 10).frame) +reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 100).frame) print("Duty Cycle") reply = device.send(HarpMessage.WriteFloat(DelphiOnlyAppRegs.DutyCycle, 0.5).frame) print("Enable") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 1).frame) print("Enable Valve LEDS") -reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 0).frame) +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 1).frame) '''Set Timings''' # print("Set Odor Delivery Time") @@ -64,26 +64,32 @@ def print_poke_counts( try: while True: for msg in device.get_events(): - print(msg) - print() - print_poke_counts(device) - - # odor is depleted -- assign a new one - # fps = device.send(HarpMessage.ReadU32(DelphiOnlyAppRegs.FrameRate).frame) - # print(f'FPS: {fps.payload[0]}') + # print(msg) + # print() + # print_poke_counts(device) + # print(f"event address: {msg.address}") + # print(f"event payload: {msg.payload[0]}") - # cam = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.CamPin).frame) - # print(f'CAM: {cam.payload[0]}') - # if poke_status.payload[0] != 0: - # print(f'Poke State: {poke_status.payload[0]}') - - reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.QueuedOdorIndex).frame) - if reply.payload[0] == -1: - odor_i+=1 - if odor_i > 3: - odor_i = 0 - print(f'New odor index: {odor_i}') - reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) + '''EVENT BASED ODOR UPDATING''' + event_address = msg.address + if event_address == 66: + event_payload = msg.payload[0] + if event_payload == -1: + odor_i+=1 + if odor_i > 3: + odor_i = 0 + print(f'New odor index: {odor_i}') + reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) + + + '''READ BASED ODOR UPDATING''' + # reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.QueuedOdorIndex).frame) + # if reply.payload[0] == -1: + # odor_i+=1 + # if odor_i > 3: + # odor_i = 0 + # print(f'New odor index: {odor_i}') + # reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) except KeyboardInterrupt: print("Disabling FSM.") From 9e56b62fb1eee41b796f962b518fbfd4392eefb6 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 29 Oct 2025 13:46:52 -0700 Subject: [PATCH 32/39] Pokes are only registered during the pre-odor delivery state --- firmware/src/poke_manager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 1bceec1..6556814 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -68,15 +68,15 @@ void PokeManager::update_poke_status() raw_poke_state_ = 1; } - // Poke detected -- start poke timer - if (gpio_get(poke_pin_) && !beam_broken_) + // Poke detected during -- start poke timer + if (gpio_get(poke_pin_) && !beam_broken_ && state_ == ODOR_DISPENSING_TO_EXHAUST) { poke_start_time_us_ = time_us_32(); beam_broken_ = true; } // Check duration since beam break/poke - if (gpio_get(poke_pin_) && beam_broken_) + if (gpio_get(poke_pin_) && beam_broken_ && state_ == ODOR_DISPENSING_TO_EXHAUST) { //gpio_put(LED_PIN, 1); // Turn on LED whenever the beam is broken if ((time_us_32() - poke_start_time_us_) >= min_poke_time_us_ && poke_initiated_once_ == false) From c4a9647c6b1a9ed6bd29ad132746340e71023714 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 29 Oct 2025 13:47:46 -0700 Subject: [PATCH 33/39] Fixed raw poke state read register --- firmware/src/delphi_controller_app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 2c888ab..5d6c9c0 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -283,7 +283,7 @@ void read_poke_state(uint8_t reg_address) void read_raw_poke_state(uint8_t reg_address) { // FIXME - app_regs.PokeState = poke_manager.get_raw_poke_state(); + app_regs.RawPokeState = poke_manager.get_raw_poke_state(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } From c8ad681fd7ccdda848367dd389de5dc930d418c8 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 19 Nov 2025 12:31:55 -0800 Subject: [PATCH 34/39] Fixed GPIO masking --- firmware/inc/config.h | 4 +--- firmware/inc/delphi_controller_app.h | 2 +- firmware/src/delphi_controller_app.cpp | 24 +++++++----------------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/firmware/inc/config.h b/firmware/inc/config.h index 0c99ea4..d8493d7 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -16,9 +16,7 @@ inline constexpr uint32_t VALVE_PIN_BASE = 6; inline constexpr uint32_t GPIO_PIN_BASE = 22; #define VALVES_MASK (0x0000FFFF) -// #define GPIOS_MASK (0x000000FF) -#define GPIOS_MASK_INPUT (0x03C00000) -#define GPIOS_MASK_OUTPUT (0x3C000000) +#define GPIOS_MASK (0x000000FF) #define HARP_DEVICE_ID (1406) #define HW_VERSION_MAJOR (1) diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 5260be0..1667bb0 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -165,7 +165,7 @@ void update_app_state(); void reset_app(); inline uint8_t read_aux_gpios() -{return uint8_t((gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK_INPUT);} +{return uint8_t((gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK);} void read_valves_state(uint8_t reg_address); void read_valves_set(uint8_t reg_address); diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 5d6c9c0..1054db9 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -550,7 +550,7 @@ void write_aux_gpio_dir(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); // Apply register settings (set bits are outputs; cleared bits are inputs). - gpio_set_dir_masked(uint32_t(GPIOS_MASK_INPUT) << GPIO_PIN_BASE, + gpio_set_dir_masked(uint32_t(GPIOS_MASK) << GPIO_PIN_BASE, uint32_t(app_regs.AuxGPIODir) << GPIO_PIN_BASE); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); @@ -637,14 +637,6 @@ void raw_poke_fall() // HarpCore::send_harp_reply(EVENT, CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); // } -// void falling_edge_detected() -// { -// const uint8_t CAM_PIN_STATE_INDEX_ADDRESS = 75; // FIXME: this is hardcoded.. -// app_regs.CamPinState = 0; // 0:fall -// if (!HarpCore::is_muted()) -// HarpCore::send_harp_reply(EVENT, CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); -// } - void camera_timestamp_callback(uint gpio, uint32_t events) { // // Toggle LED for testing // gpio_put(25, !gpio_get(25)); @@ -758,18 +750,16 @@ void reset_app() // Init the exposed auxiliary GPIO pins we are using as 4-inputs. // This *must* be called once to setup the AUX GPIOs. - gpio_init_mask(GPIOS_MASK_INPUT << GPIO_PIN_BASE); - gpio_set_dir_masked(GPIOS_MASK_INPUT << GPIO_PIN_BASE, 0); + gpio_init_mask(GPIOS_MASK << GPIO_PIN_BASE); + gpio_set_dir_masked(GPIOS_MASK << GPIO_PIN_BASE, 0); - app_regs.AuxGPIODir = 0; // All inputs (consistent with what we just set). - app_regs.AuxGPIOState = (gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK_INPUT; + app_regs.AuxGPIODir = 0b11110000; // GPIO pins 25-29 as outputs + app_regs.AuxGPIOState = (gpio_get_all() >> GPIO_PIN_BASE) & GPIOS_MASK; //all pins are set low app_regs.AuxGPIOSet = 0; app_regs.AuxGPIOClear = 0; - // Init the exposed auxiliary GPIO pins we are using as 4-outputs. - // This *must* be called once to setup the AUX GPIOs. - gpio_init_mask(GPIOS_MASK_OUTPUT << (GPIO_PIN_BASE + 4)); - gpio_set_dir_masked(GPIOS_MASK_OUTPUT << (GPIO_PIN_BASE + 4), 1); + gpio_set_dir_masked(uint32_t(GPIOS_MASK) << GPIO_PIN_BASE, + uint32_t(app_regs.AuxGPIODir) << GPIO_PIN_BASE); // Clear aux input EVENT message configuration. app_regs.AuxGPIORisingInputs = 0; From 629d07d84241d382773ecadb0411eb297ceb958b Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Thu, 20 Nov 2025 09:47:15 -0800 Subject: [PATCH 35/39] Updated GPIO indices in device.yaml --- device.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/device.yml b/device.yml index 63d0049..e49b80e 100644 --- a/device.yml +++ b/device.yml @@ -101,7 +101,7 @@ registers: type: U8 maskType: AuxGPIOMask access: Write - description: "Set the state (one or off) of any auxiliary GPIO pins specified as outputs." + description: "Set the state (on or off) of any auxiliary GPIO pins specified as outputs." AuxGPIOSet: address: 53 type: U8 @@ -267,11 +267,11 @@ groupMasks: AuxGPIOMask: description: "Auxiliary GPIO index." values: - AuxGPIO0: 0x0 - AuxGPIO1: 0x1 - AuxGPIO2: 0x2 - AuxGPIO3: 0x3 - AuxGPIO4: 0x4 - AuxGPIO5: 0x5 - AuxGPIO6: 0x6 - AuxGPIO7: 0x7 + AuxGPIO0: 00000001 + AuxGPIO1: 00000010 + AuxGPIO2: 00000100 + AuxGPIO3: 00001000 + AuxGPIO4: 00010000 + AuxGPIO5: 00100000 + AuxGPIO6: 01000000 + AuxGPIO7: 10000000 \ No newline at end of file From 5901c5c54e5434f5f2ef5c3978621d4628777a2c Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Thu, 20 Nov 2025 15:55:10 -0800 Subject: [PATCH 36/39] Refactorization for queuing an arbitrary number of odors --- device.yml | 22 +++--- firmware/inc/delphi_controller_app.h | 19 +---- firmware/inc/poke_manager.h | 32 ++++++--- firmware/inc/pwm_pio.h | 30 -------- firmware/src/delphi_controller_app.cpp | 16 ++--- firmware/src/poke_manager.cpp | 14 ++-- firmware/src/pwm_pio.cpp | 31 -------- software/pyharp/app_registers.py | 9 +-- software/pyharp/test_valve_timings.py | 99 +++++++++++++++----------- 9 files changed, 112 insertions(+), 160 deletions(-) diff --git a/device.yml b/device.yml index e49b80e..9ebafc9 100644 --- a/device.yml +++ b/device.yml @@ -173,11 +173,11 @@ registers: type: U8 access: Write description: "Force the poke handling state machine to iterate as if handling a mouse poke. PokeDometers are not incremented." - QueuedOdorIndex: + QueuedOdorMask: address: 66 - type: S8 + type: U16 access: Write - description: "Queued odor (value: odor valve index) that will be delievered to the odor port given a register poke. After an odor has been dispensed, the register will be set to -1, which indicates that a new odor is needed" + description: "Queued odors (value: odor valve mask) that will be delivered to the odor port given a register poke. After odors have been dispensed, the register will be set to 0, which indicates that new odors are needed" VacuumCloseTimeUS: address: 67 type: U32 @@ -267,11 +267,11 @@ groupMasks: AuxGPIOMask: description: "Auxiliary GPIO index." values: - AuxGPIO0: 00000001 - AuxGPIO1: 00000010 - AuxGPIO2: 00000100 - AuxGPIO3: 00001000 - AuxGPIO4: 00010000 - AuxGPIO5: 00100000 - AuxGPIO6: 01000000 - AuxGPIO7: 10000000 \ No newline at end of file + AuxGPIO0: 0x01 + AuxGPIO1: 0x02 + AuxGPIO2: 0x04 + AuxGPIO3: 0x08 + AuxGPIO4: 0x10 + AuxGPIO5: 0x20 + AuxGPIO6: 0x40 + AuxGPIO7: 0x80 \ No newline at end of file diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index 1667bb0..cfa511f 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -89,7 +89,7 @@ struct app_regs_t uint32_t PokeDometer; uint8_t FSMEnabledState; uint8_t ForceFSM; - int8_t QueuedOdorIndex; + int16_t QueuedOdorMask; uint32_t VacuumCloseTimeUS; uint32_t MinOdorDeliveryTimeUS; uint32_t MaxOdorDeliveryTimeUS; @@ -129,19 +129,6 @@ void raw_poke_rise(void); */ void raw_poke_fall(void); - -// USED FOR EVENTS WHEN POOLING THE STATE OF THE PIN -// /** -// * \brief callback function to tell the PC when the rising edge of the camera happened -// */ -// void rising_edge_detected(void); - -// /** -// * \brief callback function to tell the PC when the falling edge of the camera happened -// */ -// void falling_edge_detected(void); - - /** * \brief callback for camera timestamp */ @@ -180,7 +167,7 @@ void read_raw_poke_state(uint8_t reg_address); void read_pokedometer(uint8_t reg_address); void read_fsm_enabled_state(uint8_t reg_address); //void read_force_fsm(uint8_t reg_address); // aliased to read_reg_generic -void read_current_odor(uint8_t reg_address); +void read_current_odors(uint8_t reg_address); void read_vacuum_close_time_us(uint8_t reg_address); void read_min_odor_delivery_time_us(uint8_t reg_address); void read_max_odor_delivery_time_us(uint8_t reg_address); @@ -211,7 +198,7 @@ void write_poke_pin_inverted(msg_t& msg); // Cannot write to pokedometer void write_fsm_enabled_state(msg_t& msg); void write_force_fsm(msg_t& msg); -void write_current_odor(msg_t& msg); +void write_current_odors(msg_t& msg); void write_vacuum_close_time_us(msg_t& msg); void write_min_odor_delivery_time_us(msg_t& msg); void write_max_odor_delivery_time_us(msg_t& msg); diff --git a/firmware/inc/poke_manager.h b/firmware/inc/poke_manager.h index d81c886..8d114c7 100644 --- a/firmware/inc/poke_manager.h +++ b/firmware/inc/poke_manager.h @@ -46,14 +46,24 @@ class PokeManager inline void disable() {set_enabled_state(false);} + // Generate an ETL vector of which odor valves are queued + inline etl::vector bit_positions(uint16_t mask) { + etl::vector positions; + for (int i = 0; i < 16; ++i) if (mask & (1 << i)) positions.push_back(i); + return positions; + } + inline void set_odor_valve_state(bool enabled) { - if (odor_valve_index_ >= 0) - { - if (enabled) - odor_valves_[odor_valve_index_].energize(); - else - odor_valves_[odor_valve_index_].deenergize(); + auto odor_valve_indices = bit_positions(odor_valve_mask_); // get positions valves to activate + for (int odor_valve_index: odor_valve_indices){ + if (odor_valve_index < 14) // Only 14 odor valves -- any odor valve specified above this is ignored + { + if (enabled) + odor_valves_[odor_valve_index].energize(); + else + odor_valves_[odor_valve_index].deenergize(); + } } } @@ -110,8 +120,8 @@ class PokeManager */ void set_enabled_state(bool enabled); - inline void set_current_odor(uint32_t odor_index) - {odor_valve_index_ = odor_index;} + inline void set_current_odors(uint16_t odor_mask) + {odor_valve_mask_ = odor_mask;} inline void set_vacuum_close_time_us(uint32_t vacuum_close_time_us) {vacuum_close_time_us_ = vacuum_close_time_us;} @@ -199,8 +209,8 @@ class PokeManager inline size_t get_poke_count() const {return poke_count_;} - inline uint32_t get_current_odor() const - {return odor_valve_index_;} + inline uint16_t get_current_odors() const + {return odor_valve_mask_;} inline uint32_t get_vacuum_close_time_us() const {return vacuum_close_time_us_;} @@ -251,7 +261,7 @@ class PokeManager uint8_t poke_pin_; gpio_override override_state_; /// Whether or not the poke pin is inverted. - int odor_valve_index_; + uint16_t odor_valve_mask_; size_t poke_count_; uint8_t poke_state_; diff --git a/firmware/inc/pwm_pio.h b/firmware/inc/pwm_pio.h index 3c983d6..f22e15d 100644 --- a/firmware/inc/pwm_pio.h +++ b/firmware/inc/pwm_pio.h @@ -96,30 +96,6 @@ class CameraDriver // FOR POOLING EVENTS -/** -// * \brief rise event handler -// */ -// inline void set_pwm_rise_callback_fn( void (* fn)(void)) -// {request_pwm_rise_callback_fn_ = fn;} - -// inline void rising_edge_detected() -// { -// if (request_pwm_rise_callback_fn_ != nullptr) -// request_pwm_rise_callback_fn_(); -// } - -// /** -// * \brief fall event handler -// */ -// inline void set_pwm_fall_callback_fn( void (* fn)(void)) -// {request_pwm_fall_callback_fn_ = fn;} - -// inline void falling_edge_detected() -// { -// if (request_pwm_fall_callback_fn_ != nullptr) -// request_pwm_fall_callback_fn_(); -// } - /** * \brief Set PWM frequency */ @@ -189,8 +165,6 @@ class CameraDriver inline float get_enable_state() const {return enable_state_;} - - private: /** @@ -208,10 +182,6 @@ class CameraDriver PIO pio_; uint sm_; - //FOR POOLING EVENTS - // void (*request_pwm_rise_callback_fn_)(void); - // void (*request_pwm_fall_callback_fn_)(void); - // Declare Constants static inline constexpr float DEFAULT_DUTY_CYCLE = 0.5f; static inline constexpr uint32_t DEFAULT_FREQ = 60; diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index 1054db9..ddc9035 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -86,7 +86,7 @@ RegSpecs app_reg_specs[APP_REG_COUNT] {(uint8_t*)&app_regs.PokeDometer, sizeof(app_regs.PokeDometer), U32}, {(uint8_t*)&app_regs.FSMEnabledState, sizeof(app_regs.FSMEnabledState), U8}, {(uint8_t*)&app_regs.ForceFSM, sizeof(app_regs.ForceFSM), U8}, - {(uint8_t*)&app_regs.QueuedOdorIndex, sizeof(app_regs.QueuedOdorIndex), S8}, + {(uint8_t*)&app_regs.QueuedOdorMask, sizeof(app_regs.QueuedOdorMask), U16}, {(uint8_t*)&app_regs.VacuumCloseTimeUS, sizeof(app_regs.VacuumCloseTimeUS), U32}, {(uint8_t*)&app_regs.MinOdorDeliveryTimeUS, sizeof(app_regs.MinOdorDeliveryTimeUS), U32}, {(uint8_t*)&app_regs.MaxOdorDeliveryTimeUS, sizeof(app_regs.MaxOdorDeliveryTimeUS), U32}, @@ -143,7 +143,7 @@ RegFnPair reg_handler_fns[APP_REG_COUNT] {read_pokedometer, HarpCore::write_to_read_only_reg_error}, {read_fsm_enabled_state, write_fsm_enabled_state}, {read_force_fsm, write_force_fsm}, - {read_current_odor, write_current_odor}, + {read_current_odors, write_current_odors}, {read_vacuum_close_time_us, write_vacuum_close_time_us}, {read_min_odor_delivery_time_us, write_min_odor_delivery_time_us}, {read_max_odor_delivery_time_us, write_max_odor_delivery_time_us}, @@ -319,18 +319,18 @@ void write_force_fsm(msg_t& msg) HarpCore::send_harp_reply(WRITE, msg.header.address); } -void read_current_odor(uint8_t reg_address) +void read_current_odors(uint8_t reg_address) { // Get recent poke count value - app_regs.QueuedOdorIndex = poke_manager.get_current_odor(); + app_regs.QueuedOdorMask = poke_manager.get_current_odors(); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } -void write_current_odor(msg_t& msg) +void write_current_odors(msg_t& msg) { HarpCore::copy_msg_payload_to_register(msg); - poke_manager.set_current_odor(app_regs.QueuedOdorIndex); + poke_manager.set_current_odors(app_regs.QueuedOdorMask); if (!HarpCore::is_muted()) HarpCore::send_harp_reply(WRITE, msg.header.address); } @@ -599,7 +599,7 @@ void write_aux_gpio_clear(msg_t& msg) void request_next_odor() { const uint8_t NEXT_ODOR_INDEX_ADDRESS = 66; // FIXME: this is hardcoded. - app_regs.QueuedOdorIndex = -1; // Mark it as "used." + app_regs.QueuedOdorMask = 0; // Mark it as "used." if (!HarpCore::is_muted()) HarpCore::send_harp_reply(EVENT, NEXT_ODOR_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } @@ -713,7 +713,7 @@ void reset_app() app_regs.PokeDometer = poke_manager.get_poke_count(); app_regs.FSMEnabledState = poke_manager.get_enabled_state(); app_regs.ForceFSM = 0; - app_regs.QueuedOdorIndex = poke_manager.get_current_odor(); + app_regs.QueuedOdorMask = poke_manager.get_current_odors(); app_regs.VacuumCloseTimeUS = poke_manager.get_vacuum_close_time_us(); app_regs.MinOdorDeliveryTimeUS = poke_manager.get_min_odor_delivery_time_us(); app_regs.MaxOdorDeliveryTimeUS = poke_manager.get_max_odor_delivery_time_us(); diff --git a/firmware/src/poke_manager.cpp b/firmware/src/poke_manager.cpp index 6556814..94e6811 100644 --- a/firmware/src/poke_manager.cpp +++ b/firmware/src/poke_manager.cpp @@ -5,7 +5,7 @@ PokeManager::PokeManager(ValveDriver& final_valve, ValveDriver& vac_valve, : final_valve_{final_valve}, vac_valve_{vac_valve}, odor_valves_{odor_valves}, num_odor_valves_{num_odor_valves}, state_{RESET}, poke_count_{0}, poke_pin_{DEFAUT_POKE_PIN}, -odor_valve_index_{-1}, disable_fsm_{false}, +odor_valve_mask_{0}, disable_fsm_{false}, poke_detected_{false}, poke_state_{0}, raw_poke_state_{0}, beam_broken_{false}, poke_initiated_once_{false}, request_next_odor_callback_fn_{nullptr}, request_poke_state_callback_fn_{nullptr}, @@ -100,7 +100,7 @@ void PokeManager::reset() state_ = RESET; deenergize_all_valves(); disable(); - odor_valve_index_ = -1; + odor_valve_mask_ = 0; poke_count_ = 0; poke_state_ = 0; poke_detected_ = false; @@ -158,16 +158,16 @@ void PokeManager::update() next_state = ODOR_SETUP; break; case ODOR_SETUP: - if (odor_valve_index_ < 0){ + if (odor_valve_mask_ == 0){ // The odor should be primed before a poke poke_detected_ = false; poke_state_ = 0; } - else if (odor_valve_index_ != -1 && !valve_state_){ + else if (odor_valve_mask_ > 0 && !valve_state_){ energize_odor_valve(); // Need to initiated the event that the odor was consumed before this } - else if (state_duration_us() >= vacuum_close_time_us_ && odor_valve_index_ != -1) + else if (state_duration_us() >= vacuum_close_time_us_ && odor_valve_mask_ != 0) { next_state = ODOR_DISPENSING_TO_EXHAUST; } @@ -244,11 +244,11 @@ void PokeManager::update() { // Energize the final valve final_valve_.energize(); - odor_valve_index_ = -1; // Consume queued odor. + odor_valve_mask_ = 0; // Clear the mask request_next_odor(); //request #if(DEBUG) - printf("Odor Valve: %i\r\n", odor_valve_index_); //valve odor index + printf("Odor Valves: %i\r\n", odor_valve_mask_); //valve odor index #endif } } diff --git a/firmware/src/pwm_pio.cpp b/firmware/src/pwm_pio.cpp index 5bbe20b..590168d 100644 --- a/firmware/src/pwm_pio.cpp +++ b/firmware/src/pwm_pio.cpp @@ -52,39 +52,8 @@ void CameraDriver::pwm_init(PIO pio, uint sm, uint offset, uint8_t pin, uint8_t } -// //Pooling - Rise and Fall edges of camera triggering -// void CameraDriver::pwm_signal_status() -// { -// // Check to see if poke has been detected -// // Beam is no longer broken -// if (!gpio_get(pwm_pio_pin_)) -// { -// if (pwm_pin_state_ == 1) -// { -// //falling edge event -// falling_edge_detected(); -// } -// pwm_pin_state_ = 0; -// } - -// // Beam broken -- update raw poke state -// if (gpio_get(pwm_pio_pin_)) -// { -// if (pwm_pin_state_ == 0) -// { -// //rising edge event -// rising_edge_detected(); -// } -// pwm_pin_state_ = 1; -// } -// } - - void CameraDriver::update() { - // USING A ISR INSTEAD: check pin state -- trigger events - // pwm_signal_status(); // polling for events works - // set PWM if (!pio_sm_is_tx_fifo_full(pio_, sm_)) { diff --git a/software/pyharp/app_registers.py b/software/pyharp/app_registers.py index 6e18228..444164b 100644 --- a/software/pyharp/app_registers.py +++ b/software/pyharp/app_registers.py @@ -1,9 +1,9 @@ """ValveController app registers. Later these will be extracted from the device.yaml""" + from enum import IntEnum from itertools import chain - class AppRegs(IntEnum): ValvesState = 32 ValvesSet = 33 @@ -43,7 +43,7 @@ class DelphiOnlyAppRegs(IntEnum): PokeDometer = 63 FSMEnabledState = 64 ForceFSM = 65 - QueuedOdorIndex = 66 + QueuedOdorMask = 66 VacuumCloseTimeUS = 67 MinOdorDeliveryTimeUS = 68 MaxOdorDeliveryTimeUS = 69 @@ -59,5 +59,6 @@ class DelphiOnlyAppRegs(IntEnum): EnableValveLeds = 79 -DelphiAppRegs = IntEnum("DelphiAppRegs", - [(i.name, i.value) for i in chain(AppRegs, DelphiOnlyAppRegs)]) +DelphiAppRegs = IntEnum( + "DelphiAppRegs", [(i.name, i.value) for i in chain(AppRegs, DelphiOnlyAppRegs)] +) diff --git a/software/pyharp/test_valve_timings.py b/software/pyharp/test_valve_timings.py index af85651..0afb59d 100644 --- a/software/pyharp/test_valve_timings.py +++ b/software/pyharp/test_valve_timings.py @@ -1,95 +1,110 @@ #!/usr/bin/env python3 -from pyharp.device import Device, DeviceMode +from pyharp.device import Device from pyharp.messages import HarpMessage -from pyharp.messages import MessageType from app_registers import AppRegs, DelphiOnlyAppRegs -from struct import pack, unpack -from time import sleep -import os -import serial.tools.list_ports import logging + logger = logging.getLogger() logger.addHandler(logging.StreamHandler()) + # functions def print_poke_counts( - device, + device, ): reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.PokeDometer).frame) print(f"Current pokedometer count is: {reply.payload}.") return None - + + # Open serial connection with the first Valve Controller. -com_port = 'COM4' #'COM3' #None +com_port = "COM5" #'COM3' #None device = Device(com_port) -device.info() # Display device's info on screen +device.info() # Display device's info on screen print() -print("Enabling all aux gpios as inputs.") -gpio_dir = 0b00000000 -reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIODir, gpio_dir).frame) +print("Enabling aux gpios 25-29 as outputs") +# gpio_dir = 32 +# reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIODir, gpio_dir).frame) +# print(f"reply: {reply.payload[0]:08b}") +gpio_set = 0b00100000 +reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIOSet, gpio_set).frame) +print(f"reply: {reply}") print(f"reply: {reply.payload[0]:08b}") +# reply = device.send(HarpMessage.WriteU8(AppRegs.AuxGPIOClear, gpio_set).frame) +# print(f"reply: {reply.payload[0]:08b}") + + print() print_poke_counts(device) -print(f"Setting odor.") -reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, 0).frame) -print(f"Assigning poke pin.") +print("Setting odor.") +reply = device.send( + HarpMessage.WriteU16(DelphiOnlyAppRegs.QueuedOdorMask, 0x0001).frame +) +print("Assigning poke pin.") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePin, 22).frame) -print(f"Inverting poke pin.") +print("Inverting poke pin.") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.PokePinInverted, 1).frame) print("Enabling FSM") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.FSMEnabledState, 1).frame) print("Camera Pin") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.CamPin, 26).frame) print("FPS") -reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 100).frame) +reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 1).frame) print("Duty Cycle") reply = device.send(HarpMessage.WriteFloat(DelphiOnlyAppRegs.DutyCycle, 0.5).frame) print("Enable") -reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 1).frame) +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 0).frame) print("Enable Valve LEDS") -reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 1).frame) +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 0).frame) -'''Set Timings''' +"""Set Timings""" # print("Set Odor Delivery Time") # reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.OdorDeliveryTimeUS, 1000000).frame) # print("Set Final Valve Energized Time") # reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FinalValveEnergizedTimeUS, 20000).frame) -# print("Set Vacumm Setup Time") -# reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.VacuumSetupTimeUS, 20000).frame) +print("Min Poke Time") +reply = device.send( + HarpMessage.WriteU32(DelphiOnlyAppRegs.MinimumPokeTimeUS, 10000).frame +) print() -odor_i = 0 +odor_masks = [0x0002, 0x0004, 0x0008, 0x0003, 0x000F] +print(odor_masks) +odor_i = -1 try: while True: for msg in device.get_events(): - # print(msg) - # print() - # print_poke_counts(device) - # print(f"event address: {msg.address}") - # print(f"event payload: {msg.payload[0]}") + print(msg) + print() + print_poke_counts(device) + print(f"event address: {msg.address}") + print(f"event payload: {msg.payload[0]}") - '''EVENT BASED ODOR UPDATING''' + """EVENT BASED ODOR UPDATING""" event_address = msg.address if event_address == 66: event_payload = msg.payload[0] - if event_payload == -1: - odor_i+=1 - if odor_i > 3: + if event_payload == 0: # -1 previously + odor_i += 1 + if odor_i > len(odor_masks) - 1: odor_i = 0 - print(f'New odor index: {odor_i}') - reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) - + print(f"New odor index: {odor_masks[odor_i]}") + reply = device.send( + HarpMessage.WriteU16( + DelphiOnlyAppRegs.QueuedOdorMask, odor_masks[odor_i] + ).frame + ) - '''READ BASED ODOR UPDATING''' + """READ BASED ODOR UPDATING""" # reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.QueuedOdorIndex).frame) # if reply.payload[0] == -1: - # odor_i+=1 - # if odor_i > 3: - # odor_i = 0 - # print(f'New odor index: {odor_i}') - # reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) + # odor_i+=1 + # if odor_i > 3: + # odor_i = 0 + # print(f'New odor index: {odor_i}') + # reply = device.send(HarpMessage.WriteS8(DelphiOnlyAppRegs.QueuedOdorIndex, odor_i).frame) except KeyboardInterrupt: print("Disabling FSM.") From 07248d639ee49526ae45335fba817fc22ddc38f9 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Wed, 26 Nov 2025 15:58:29 -0800 Subject: [PATCH 37/39] Event for valve state changes --- device.yml | 3 +- firmware/inc/delphi_controller_app.h | 9 +++++ firmware/src/delphi_controller_app.cpp | 53 ++++++++++++++++---------- software/pyharp/test_valve_timings.py | 23 ++++++----- 4 files changed, 58 insertions(+), 30 deletions(-) diff --git a/device.yml b/device.yml index 9ebafc9..928f481 100644 --- a/device.yml +++ b/device.yml @@ -10,7 +10,7 @@ registers: address: 32 type: U16 maskType: ValveMask - access: Write + access: Event description: "Set the enabled/disabled state (enabled = 1) of all valves" ValvesSet: address: 33 @@ -264,6 +264,7 @@ groupMasks: Valve13: 0xD Valve14: 0xE Valve15: 0xF + AllValves: 0xFFFF AuxGPIOMask: description: "Auxiliary GPIO index." values: diff --git a/firmware/inc/delphi_controller_app.h b/firmware/inc/delphi_controller_app.h index cfa511f..b5dd6e5 100644 --- a/firmware/inc/delphi_controller_app.h +++ b/firmware/inc/delphi_controller_app.h @@ -41,6 +41,9 @@ struct HarpEvent { uint64_t timestamp; }; +//Valves state mask variables +const uint8_t VALVES_STATE_INDEX_ADDRESS = 32; + // Valve configuration struct for configuring the Hit-and-hold driver #pragma pack(push, 1) struct ValveConfig @@ -141,6 +144,12 @@ void camera_timestamp_callback(uint gpio, uint32_t events); void push_event_from_isr(uint8_t index, uint64_t timestamp); bool pop_event(HarpEvent &event); +/** + * \brief function getting valve state mask + */ +uint16_t get_valve_mask(); + + /** * \brief update the app state. Called in a loop. */ diff --git a/firmware/src/delphi_controller_app.cpp b/firmware/src/delphi_controller_app.cpp index ddc9035..0fdce33 100644 --- a/firmware/src/delphi_controller_app.cpp +++ b/firmware/src/delphi_controller_app.cpp @@ -444,12 +444,7 @@ void write_minimum_poke_time_us(msg_t& msg) void read_valves_state(uint8_t reg_address) { - for (size_t valve_index = 0; valve_index < NUM_VALVES; ++valve_index) - { - app_regs.ValvesState = 0; - if (valve_drivers[valve_index].is_energized()) - app_regs.ValvesState |= (typeof(app_regs.ValvesState))(1) << valve_index; - } + app_regs.ValvesState = get_valve_mask(); // Store the mask if (!HarpCore::is_muted()) HarpCore::send_harp_reply(READ, reg_address); } @@ -628,28 +623,21 @@ void raw_poke_fall() HarpCore::send_harp_reply(EVENT, POKE_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } -// FOR EVENTS WHEN POOLING -// void rising_edge_detected() -// { -// const uint8_t CAM_PIN_STATE_INDEX_ADDRESS = 75; // FIXME: this is hardcoded. -// app_regs.CamPinState = 1; // 1:rise -// if (!HarpCore::is_muted()) -// HarpCore::send_harp_reply(EVENT, CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); -// } - void camera_timestamp_callback(uint gpio, uint32_t events) { // // Toggle LED for testing // gpio_put(25, !gpio_get(25)); push_event_from_isr(CAM_PIN_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); } -#define QUEUE_SIZE 64 -volatile HarpEvent eventQueue[QUEUE_SIZE]; +// Delphi specific functions +#define QUEUE_SIZE 128 +#define QUEUE_MASK (QUEUE_SIZE - 1) +HarpEvent eventQueue[QUEUE_SIZE]; volatile uint8_t head = 0; volatile uint8_t tail = 0; void push_event_from_isr(uint8_t index, uint64_t timestamp) { - uint8_t next = (head + 1) % QUEUE_SIZE; + uint8_t next = (head + 1) & QUEUE_MASK; if (next != tail) { // Prevent overflow eventQueue[head].index = index; eventQueue[head].timestamp = timestamp; @@ -661,10 +649,27 @@ bool pop_event(HarpEvent &event) { if (tail == head) return false; // Queue is empty event.index = eventQueue[tail].index; event.timestamp = eventQueue[tail].timestamp; - tail = (tail + 1) % QUEUE_SIZE; + tail = (tail + 1) & QUEUE_MASK; return true; } +// Valve state mask +uint16_t get_valve_mask() { + uint16_t mask = 0; // Start with all bits cleared + + for (size_t valve_index = 0; valve_index < NUM_VALVES && valve_index < 16; ++valve_index) // limit considers num valves and bit mask size + { + if (valve_drivers[valve_index].is_energized()) + { + // mask |= (uint16_t)(1) << valve_index; // Set bit for this valve + mask |= (1u << valve_index); + } + } + return mask; +} + +uint16_t previous_mask = 0; // Initialize to zero or read initial state + void update_app_state() // Called when app.run() is called -- add poke detection here { // Update valve controller state machines. @@ -685,9 +690,17 @@ void update_app_state() // Called when app.run() is called -- add poke detection } } + // Handle valve state changes + uint16_t current_mask = get_valve_mask(); + if (current_mask != previous_mask) { + app_regs.ValvesState = current_mask; + if (!HarpCore::is_muted()) + HarpCore::send_harp_reply(EVENT, VALVES_STATE_INDEX_ADDRESS, HarpCore::harp_time_us_64()); + previous_mask = current_mask; + } + // Process AuxGPIO input changes. // FIXME: do we need to update old_aux_gpio_inputs if we change (write-to) - // app_regs.AuxGPIODir ? uint8_t aux_gpio_inputs = read_aux_gpios() & ~app_regs.AuxGPIODir; uint8_t changed_inputs = (old_aux_gpio_inputs ^ aux_gpio_inputs); app_regs.AuxGPIORisingInputs = app_regs.AuxGPIOInputRiseEvent & aux_gpio_inputs & changed_inputs; diff --git a/software/pyharp/test_valve_timings.py b/software/pyharp/test_valve_timings.py index 0afb59d..7b6b11f 100644 --- a/software/pyharp/test_valve_timings.py +++ b/software/pyharp/test_valve_timings.py @@ -51,11 +51,11 @@ def print_poke_counts( print("Camera Pin") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.CamPin, 26).frame) print("FPS") -reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 1).frame) +reply = device.send(HarpMessage.WriteU32(DelphiOnlyAppRegs.FrameRate, 100).frame) print("Duty Cycle") reply = device.send(HarpMessage.WriteFloat(DelphiOnlyAppRegs.DutyCycle, 0.5).frame) print("Enable") -reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 0).frame) +reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableCamTrigger, 1).frame) print("Enable Valve LEDS") reply = device.send(HarpMessage.WriteU8(DelphiOnlyAppRegs.EnableValveLeds, 0).frame) @@ -76,11 +76,11 @@ def print_poke_counts( try: while True: for msg in device.get_events(): - print(msg) - print() - print_poke_counts(device) - print(f"event address: {msg.address}") - print(f"event payload: {msg.payload[0]}") + # print(msg) + # print() + # print_poke_counts(device) + # print(f"event address: {msg.address}") + # print(f"event payload: {msg.payload[0]}") """EVENT BASED ODOR UPDATING""" event_address = msg.address @@ -97,8 +97,13 @@ def print_poke_counts( ).frame ) - """READ BASED ODOR UPDATING""" - # reply = device.send(HarpMessage.ReadU8(DelphiOnlyAppRegs.QueuedOdorIndex).frame) + """READ BASED ODOR UPDATING""" + if event_address == 32: + event_payload = msg.payload[0] + print(f"Valves State: {event_payload}") + # reply = device.send(HarpMessage.ReadU16(AppRegs.ValvesState).frame) + # if reply.payload[0] != 0: + # print(f"Valves State: {reply.payload[0]:16b}") # if reply.payload[0] == -1: # odor_i+=1 # if odor_i > 3: From a92b2674e726c5ff89b3780f9bb738e7ba68e740 Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Mon, 1 Dec 2025 12:49:57 -0800 Subject: [PATCH 38/39] Update register descriptions and access --- device.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/device.yml b/device.yml index 928f481..635c8ed 100644 --- a/device.yml +++ b/device.yml @@ -3,13 +3,12 @@ # yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/device.json device: DelphiController whoAmI: 1407 -firmwareVersion: "0.0.0" -hardwareTargets: "1.0.0" +firmwareVersion: "0.0" +hardwareTargets: "1.0" registers: ValveState: address: 32 type: U16 - maskType: ValveMask access: Event description: "Set the enabled/disabled state (enabled = 1) of all valves" ValvesSet: @@ -176,7 +175,7 @@ registers: QueuedOdorMask: address: 66 type: U16 - access: Write + access: Event description: "Queued odors (value: odor valve mask) that will be delivered to the odor port given a register poke. After odors have been dispensed, the register will be set to 0, which indicates that new odors are needed" VacuumCloseTimeUS: address: 67 From 5e4b96fdeef1545d0f22f59c2446c1fb8b20834e Mon Sep 17 00:00:00 2001 From: Prattbuw Date: Tue, 9 Dec 2025 10:55:42 -0800 Subject: [PATCH 39/39] Fix device id --- device.yml | 2 +- firmware/inc/config.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/device.yml b/device.yml index 635c8ed..ce6e917 100644 --- a/device.yml +++ b/device.yml @@ -2,7 +2,7 @@ --- # yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/device.json device: DelphiController -whoAmI: 1407 +whoAmI: 1409 firmwareVersion: "0.0" hardwareTargets: "1.0" registers: diff --git a/firmware/inc/config.h b/firmware/inc/config.h index d8493d7..fffa0f0 100644 --- a/firmware/inc/config.h +++ b/firmware/inc/config.h @@ -18,7 +18,7 @@ inline constexpr uint32_t GPIO_PIN_BASE = 22; #define VALVES_MASK (0x0000FFFF) #define GPIOS_MASK (0x000000FF) -#define HARP_DEVICE_ID (1406) +#define HARP_DEVICE_ID (1409) #define HW_VERSION_MAJOR (1) #define HW_VERSION_MINOR (0) #define HW_ASSEMBLY_VERSION (0)