From a00a4b7db4523dd0d175e010231548be7152b33c Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Wed, 10 Jan 2024 13:11:26 -0700 Subject: [PATCH 01/11] creates basic classes, need to fix --- CMakeLists.txt | 3 +++ src/AnalogFrontEndWrapper.h | 15 +++++++++++ src/CMakeLists.txt | 6 +++++ src/CustomFrontEndWrapper.cpp | 35 ++++++++++++++++++++++++ src/CustomFrontEndWrapper.h | 25 ++++++++++++++++++ src/Data.h | 16 +++++++++++ src/DataAcquisitionThread.cpp | 36 +++++++++++++++++++++++++ src/DataAcquisitionThread.h | 25 ++++++++++++++++++ src/DataBufferManager.cpp | 40 ++++++++++++++++++++++++++++ src/DataBufferManager.h | 23 ++++++++++++++++ src/TIFrontEnd.cpp | 13 +++++++++ src/TIFrontEndWrapper.h | 12 +++++++++ src/core/CMakeLists.txt | 5 ++++ src/core/Semaphore.cpp | 50 +++++++++++++++++++++++++++++++++++ src/core/Semaphore.h | 45 +++++++++++++++++++++++++++++++ src/core/Thread.cpp | 24 +++++++++++++++++ src/core/Thread.h | 30 +++++++++++++++++++++ src/core/ThreadCommand.h | 28 ++++++++++++++++++++ src/main.cpp | 33 +++++++---------------- 19 files changed, 441 insertions(+), 23 deletions(-) create mode 100644 src/AnalogFrontEndWrapper.h create mode 100644 src/CMakeLists.txt create mode 100644 src/CustomFrontEndWrapper.cpp create mode 100644 src/CustomFrontEndWrapper.h create mode 100644 src/Data.h create mode 100644 src/DataAcquisitionThread.cpp create mode 100644 src/DataAcquisitionThread.h create mode 100644 src/DataBufferManager.cpp create mode 100644 src/DataBufferManager.h create mode 100644 src/TIFrontEnd.cpp create mode 100644 src/TIFrontEndWrapper.h create mode 100644 src/core/CMakeLists.txt create mode 100644 src/core/Semaphore.cpp create mode 100644 src/core/Semaphore.h create mode 100644 src/core/Thread.cpp create mode 100644 src/core/Thread.h create mode 100644 src/core/ThreadCommand.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f1c568..fb8a6d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ target_sources(app PRIVATE src/main.cpp) zephyr_library_include_directories(.) +# Add project directories +add_subdirectory(src) + # Add CMSIS library from nrfSDK include_directories(..\\..\\..\\..\\modules\\hal\\cmsis\\CMSIS\\DSP\\Include) diff --git a/src/AnalogFrontEndWrapper.h b/src/AnalogFrontEndWrapper.h new file mode 100644 index 0000000..7fed64d --- /dev/null +++ b/src/AnalogFrontEndWrapper.h @@ -0,0 +1,15 @@ +#ifndef ANALOG_FRONT_END_WRAPPER_H +#define ANALOG_FRONT_END_WRAPPER_H + +#include "Data.h" + +class AnalogFrontEndWrapper { + +public: + virtual ~AnalogFrontEndWrapper() = default; + virtual void Initialize() = 0; + virtual void Configure() = 0; + virtual eeg_sample ReadData() = 0; +}; + +#endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e0c65fa --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,6 @@ +# Include source files in the src directory +file(GLOB_RECURSE APP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +target_sources(app PRIVATE ${APP_SOURCES}) + +# Add the core directory +add_subdirectory(core) diff --git a/src/CustomFrontEndWrapper.cpp b/src/CustomFrontEndWrapper.cpp new file mode 100644 index 0000000..77e9485 --- /dev/null +++ b/src/CustomFrontEndWrapper.cpp @@ -0,0 +1,35 @@ +#include "CustomFrontEndWrapper.h" + +const char *CustomFrontEndWrapper::spi_device_name = "ADS1299"; +const struct device *CustomFrontEndWrapper::spi_dev; + +CustomFrontEndWrapper::CustomFrontEndWrapper() { + spi_dev = device_get_binding(SPI_DEV_NAME); + if (!spi_dev) { + // TODO: print on debug uart + return; + } +} + +void CustomFrontEndWrapper::Initialize() { + // // Set SPI to default settings + // spi_cfg_t spi_config spi_cfg = { + // .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8), + // .frequency = 500000, // 500 kHz + // .slave = 0, + // }; + + // if (spi_configure(spi_dev, &spi_cfg) != 0) { + // // TODO: print on debug UART + // } + + // DataBufferManager::spi_dma_setup(spi_dev); +} + +void CustomFrontEndWrapper::Configure() { + // TODO +} + +eeg_sample CustomFrontEndWrapper::ReadData() { + // TODO +} \ No newline at end of file diff --git a/src/CustomFrontEndWrapper.h b/src/CustomFrontEndWrapper.h new file mode 100644 index 0000000..e8475a8 --- /dev/null +++ b/src/CustomFrontEndWrapper.h @@ -0,0 +1,25 @@ +#ifndef CUSTOM_FRONT_END_WRAPPER_H +#define CUSTOM_FRONT_END_WRAPPER_H + +#include +#include + +#include "AnalogFrontEndWrapper.h" +#include "DataBufferManager.h" + +#define SPI_DEV_NAME DT_LABEL(DT_NODELABEL(spi0)) + +class CustomFrontEndWrapper : public AnalogFrontEndWrapper { +private: + static const char *spi_device_name; + static const struct device *spi_dev; + +public: + CustomFrontEndWrapper(); + ~CustomFrontEndWrapper() = default; + void Initialize() override; + void Configure() override; + eeg_sample ReadData() override; +}; + +#endif \ No newline at end of file diff --git a/src/Data.h b/src/Data.h new file mode 100644 index 0000000..48286bf --- /dev/null +++ b/src/Data.h @@ -0,0 +1,16 @@ +#ifndef DATA_H +#define DATA_H + +// #include +#include + +constexpr uint8_t max_electrodes = 6; +constexpr uint8_t max_sample_rate_hz = 250; +constexpr uint32_t max_samples = max_electrodes * max_sample_rate_hz * 30; + +struct eeg_sample { + uint8_t buf[3] = { 0 }; // for max 24 bit adc readings TODO: add units + uint64_t timestamp_ms = 0; +}; + +#endif \ No newline at end of file diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp new file mode 100644 index 0000000..a07f30e --- /dev/null +++ b/src/DataAcquisitionThread.cpp @@ -0,0 +1,36 @@ +#include "DataAcquisitionThread.h" + +void DataAcquisitionThread::Initialize() { + if (thread_id == nullptr) { + // thread doesnt exist, make one naow: + thread_id = k_thread_create( + K_THREAD_STACK_ALLOC(daq_thread_stack, DATA_ACQUISITION_THREAD_STACK_SIZE_B), + K_THREAD_STACK_SIZEOF(daq_thread_stack), + Run, + this, // NEED to pass this ptr because it doesnt exist in the static function + nullptr, + nullptr, + K_PRIO_COOP(7), // TODO: change + 0, + K_NO_WAIT); + + k_thread_name_set(thread_id, "DataAcquisitionThread"); + + if (thread_id == nullptr) { + // TODO: print err on debug uart + } + } +} +void DataAcquisitionThread::Run() { + while (true) { + ThreadMessage msg; + if (k_msgq_get(&message_queue, &msg, K_FOREVER) == 0) { + switch(msg.data_acq_msg) { + STOP_READING_AFE: + START_READING_AFE: + INVALID: + break; + } + } + } +} diff --git a/src/DataAcquisitionThread.h b/src/DataAcquisitionThread.h new file mode 100644 index 0000000..3e160ad --- /dev/null +++ b/src/DataAcquisitionThread.h @@ -0,0 +1,25 @@ +#ifndef DATA_ACQUISITION_THREAD_H +#define DATA_ACQUISITION_THREAD_H + +#include "core/Thread.h" + +#define DATA_ACQUISITION_THREAD_STACK_SIZE_B 1024 + +class DataAcquisitionThread : public Thread { +private: + k_tid_t thread_id; + DataAcquisitionThread(); + +public: + void Initialize() override; + void Run() override; + + // NOTE: using singleton + static DataAcquisitionThread& GetInstance() { + // NOTE: this method of using static local variable is thread-safe + static DataAcquisitionThread instance; + return instance; + } +}; + +#endif \ No newline at end of file diff --git a/src/DataBufferManager.cpp b/src/DataBufferManager.cpp new file mode 100644 index 0000000..9a6eadd --- /dev/null +++ b/src/DataBufferManager.cpp @@ -0,0 +1,40 @@ +#include "DataBufferManager.h" + +struct Semaphore DataBufferManager::eeg_buffer_semaphore; +eeg_sample DataBufferManager::dma_buffer[max_samples]; +size_t DataBufferManager::buffer_index; + +void DataBufferManager::Write() { + +} + +void DataBufferManager::Read() { + +} + +void DataBufferManager::spi_dma_setup() { + // IF NOT SET UP, CONTINUE; ELSE ERR + // // Configure SPI CS pin as GPIO output + // const struct device *cs_dev = device_get_binding(DT_GPIO_LABEL(DT_NODELABEL(spi0), cs_gpios)); + // gpio_pin_configure(cs_dev, SPI_CS_PIN, GPIO_OUTPUT_ACTIVE); + + // // Configure SPI device + // struct spi_config spi_cfg = { + // .frequency = 500000, // Set your desired frequency + // .operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), + // .cs = NULL, + // }; + + // // Set up SPI device + // if (spi_configure(spi_dev, &spi_cfg) != 0) { + // printk("SPI configuration failed\n"); + // return; + // } + + // printk("SPI configured successfully\n"); + +} + +void DataBufferManager::spi_sensor_read_and_transfer() { + +} diff --git a/src/DataBufferManager.h b/src/DataBufferManager.h new file mode 100644 index 0000000..a8e41a6 --- /dev/null +++ b/src/DataBufferManager.h @@ -0,0 +1,23 @@ +#ifndef DATA_BUFFER_MANAGER_H +#define DATA_BUFFER_MANAGER_H + +#include +#include +#include "Data.h" +#include "core/Semaphore.h" + +class DataBufferManager { +// NOTE: needs to be fully static, only one instance should exist +private: + static Semaphore eeg_buffer_semaphore; // TODO: use this instead + static eeg_sample dma_buffer[max_samples]; + static size_t buffer_index; + +public: + static void Read(); + static void Write(); + static void spi_dma_setup(); + static void spi_sensor_read_and_transfer(); +}; + +#endif \ No newline at end of file diff --git a/src/TIFrontEnd.cpp b/src/TIFrontEnd.cpp new file mode 100644 index 0000000..b609d51 --- /dev/null +++ b/src/TIFrontEnd.cpp @@ -0,0 +1,13 @@ +#include "TIFrontEndWrapper.h" + +void TIFrontEndWrapper::Initialize() { + // TODO +} + +void TIFrontEndWrapper::Configure() { + // TODO +} + +eeg_sample TIFrontEndWrapper::ReadData() { + // TODO +} \ No newline at end of file diff --git a/src/TIFrontEndWrapper.h b/src/TIFrontEndWrapper.h new file mode 100644 index 0000000..661c077 --- /dev/null +++ b/src/TIFrontEndWrapper.h @@ -0,0 +1,12 @@ +#ifndef TI_FRONT_END_WRAPPER_H +#define TI_FRONT_END_WRAPPER_H + +#include "AnalogFrontEndWrapper.h" +class TIFrontEndWrapper : public AnalogFrontEndWrapper { +public: + void Initialize() override; + void Configure() override; + eeg_sample ReadData() override; +}; + +#endif \ No newline at end of file diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..f2cc183 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,5 @@ +# Include all source files in the src/core directory +file(GLOB_RECURSE CORE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") + +# Add the sources to the app target +target_sources(app PRIVATE ${CORE_SOURCES}) diff --git a/src/core/Semaphore.cpp b/src/core/Semaphore.cpp new file mode 100644 index 0000000..1bdd0de --- /dev/null +++ b/src/core/Semaphore.cpp @@ -0,0 +1,50 @@ +#include "Semaphore.h" + +/* + * @brief Semaphore basic constructor + */ +Semaphore::Semaphore() +{ + printk("Create Semaphore %p\n", this); + k_sem_init(&_sema_internal, 0, SEMAPHORE_MAX_TAKE); +} + +/* + * @brief wait for a Semaphore + * + * Test a Semaphore to see if it has been signaled. If the signal + * count is greater than zero, it is decremented. + * + * @return 1 when Semaphore is available + */ +int Semaphore::wait(void) +{ + k_sem_take(&_sema_internal, K_FOREVER); + return 1; +} + +/* + * @brief wait for a Semaphore within a specified timeout + * + * Test a Semaphore to see if it has been signaled. If the signal + * count is greater than zero, it is decremented. The function + * waits for timeout specified + * + * @param timeout the specified timeout in ticks + * + * @return 1 if Semaphore is available, 0 if timed out + */ +int Semaphore::wait(int timeout) +{ + return k_sem_take(&_sema_internal, K_MSEC(timeout)); +} + +/** + * @brief Signal a Semaphore + * + * This routine signals the specified Semaphore. + */ +void Semaphore::give(void) +{ + k_sem_give(&_sema_internal); +} \ No newline at end of file diff --git a/src/core/Semaphore.h b/src/core/Semaphore.h new file mode 100644 index 0000000..e1dbbc1 --- /dev/null +++ b/src/core/Semaphore.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015-2016 Wind River Systems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SEMAPHORE_H +#define SEMAPHORE_H +#include +#include +#include +#include + +/* specify delay between greetings (in ms); compute equivalent in ticks */ +#define SLEEPTIME 500 +#define SEMAPHORE_MAX_TAKE 1 +/* + * @class cpp_semaphore + * @brief Semaphore + * + * Class derives from the pure virtual semaphore class and + * implements it's methods for the semaphore + */ +class Semaphore { +protected: + struct k_sem _sema_internal; +public: + Semaphore(); + ~Semaphore() {} + int wait(void); + int wait(int timeout); + void give(void); +}; + +#endif \ No newline at end of file diff --git a/src/core/Thread.cpp b/src/core/Thread.cpp new file mode 100644 index 0000000..74c7794 --- /dev/null +++ b/src/core/Thread.cpp @@ -0,0 +1,24 @@ +#include "Thread.h" + +Thread::Thread() { + // TODO + if (k_msgq_init(&message_queue, message_queue_buffer, MAX_QUEUE_DEPTH, MESSAGE_SIZE_BYTES) != 0) { + // TODO: debug message err + return; + } +} + +bool Thread::SendMessage(ThreadMessage msg) { + if (k_msgq_put(&message_queue, &msg, K_NO_WAIT) != 0) { + // TODO: uart debug message send failed + return false; + } + return true; +} + +// This is a static function required by k_thread_create +void Thread::RunThreadSequence(void* instance, void*, void*) { + // HAVE to receive an instance ptr to run the correct thread sequence + // because static functions are not associated with thread objects + static_cast(instance)->Run(); +} \ No newline at end of file diff --git a/src/core/Thread.h b/src/core/Thread.h new file mode 100644 index 0000000..25db09a --- /dev/null +++ b/src/core/Thread.h @@ -0,0 +1,30 @@ +#ifndef THREAD_H +#define THREAD_H + +#include +#include +#include "ThreadCommand.h" + +#define MAX_QUEUE_DEPTH 10 + +class Thread { +protected: + k_tid_t thread_id; + struct k_msgq message_queue; + ThreadMessage message_queue_buffer[MAX_QUEUE_DEPTH]; + Thread(void); + +private: + // need to delete copy cstor and assign optor + Thread(const Thread &) = delete; + Thread& operator=(const Thread&) = delete; + +public: + bool SendMessage(ThreadMessage msg); + virtual void Initialize() = 0; + static void RunThreadSequence(void* instance, void*, void*); + virtual void Run() = 0; + +}; + +#endif \ No newline at end of file diff --git a/src/core/ThreadCommand.h b/src/core/ThreadCommand.h new file mode 100644 index 0000000..a216ea1 --- /dev/null +++ b/src/core/ThreadCommand.h @@ -0,0 +1,28 @@ +#ifndef THREAD_COMMAND_H +#define THREAD_COMMAND_H + +#include + +#define MESSAGE_SIZE_BYTES sizeof(ThreadMessage) + +enum class DataAcquisitionThreadMessage : uint8_t { + STOP_READING_AFE = 0, + START_READING_AFE, + INVALID, +}; + +enum class SignalProcessingThreadMessage : uint8_t { + STOP_PROCESSING_EEG = 0, + START_PROCESSING_EEG, + INVALID, +}; + +// Combined message type +union ThreadMessage { + DataAcquisitionThreadMessage data_acq_msg; + SignalProcessingThreadMessage sig_proc_msg; + + // Add types as needed +}; + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 48c3543..67d940c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,11 +30,6 @@ */ #define SNR_ERROR_THRESH ((float32_t)120) -constexpr int32_t sigproc_buf_num_int = 8 * 1000 * 5; -constexpr int32_t afe_buf_num_int = 8 * 1000 * 1; - -static float32_t test_buffer_sigproc [sigproc_buf_num_int] = { 0 }; -static float32_t test_buffer_afe [afe_buf_num_int] = { 0 }; void disable_ram_and_wfi(register volatile uint32_t *reg_begin, register volatile uint32_t *reg_last) @@ -43,7 +38,7 @@ void disable_ram_and_wfi(register volatile uint32_t *reg_begin, do { *reg_begin = 0; - reg_begin += sizeof(NRF_VMC->RAM[0]) / sizeof(reg_begin[0]); + // reg_begin += sizeof(NRF_VMC->RAM[0]) / sizeof(reg_begin[0]); } while (reg_begin <= reg_last); __DSB(); @@ -100,14 +95,6 @@ int main(void) // disable_ram_and_wfi(&NRF_VMC->RAM[0].POWER, // &NRF_VMC->RAM[ARRAY_SIZE(NRF_VMC->RAM) - 1].POWER); - test_buffer_sigproc[0] = 1; - test_buffer_afe[0] = 1; - - size_t fft_length = 100; - uint32_t fft_input[fft_length] = { 0 }; - uint32_t fft_output[fft_length] = { 0 }; - - test_arm_rfft_f32_real(fft_input, fft_output, fft_length); return 0; } @@ -129,15 +116,15 @@ static int network_gpio_allow(void) uint32_t start_pin = (IS_ENABLED(CONFIG_SOC_ENABLE_LFXO) ? 2 : 0); /* Allow the network core to use all GPIOs. */ - for (uint32_t i = start_pin; i < P0_PIN_NUM; i++) { - NRF_P0_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << - GPIO_PIN_CNF_MCUSEL_Pos); - } - - for (uint32_t i = 0; i < P1_PIN_NUM; i++) { - NRF_P1_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << - GPIO_PIN_CNF_MCUSEL_Pos); - } + // for (uint32_t i = start_pin; i < P0_PIN_NUM; i++) { + // NRF_P0_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << + // GPIO_PIN_CNF_MCUSEL_Pos); + // } + + // for (uint32_t i = 0; i < P1_PIN_NUM; i++) { + // NRF_P1_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << + // GPIO_PIN_CNF_MCUSEL_Pos); + // } return 0; From 589c95b3fa9170e530c652f380d5f81f70e747b7 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Mon, 15 Jan 2024 17:49:19 -0700 Subject: [PATCH 02/11] adds generic driver written for AFE + nrf52 --- drivers/ads1299-x.c | 337 ++++++++++++++++++++++++++++++++++++++++++++ drivers/ads1299-x.h | 209 +++++++++++++++++++++++++++ 2 files changed, 546 insertions(+) create mode 100644 drivers/ads1299-x.c create mode 100644 drivers/ads1299-x.h diff --git a/drivers/ads1299-x.c b/drivers/ads1299-x.c new file mode 100644 index 0000000..caa8ecd --- /dev/null +++ b/drivers/ads1299-x.c @@ -0,0 +1,337 @@ +/* Copyright (c) 2016 Musa Mahmood + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ads1299-x.h" +#include "app_error.h" +#include "nrf_drv_spi.h" +#include "nrf_gpio.h" +#include "app_util_platform.h" +#include "nrf_log.h" +#include "nrf_delay.h" +/**@headers for �s delay:*/ +#include +#include "compiler_abstraction.h" +#include "nrf.h" + +/**@TX,RX Stuff: */ +#define TX_RX_MSG_LENGTH 7 + +/**@DEBUG STUFF */ +#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" +#define BYTE_TO_BINARY(byte) \ + (byte & 0x80 ? '1' : '0'), \ + (byte & 0x40 ? '1' : '0'), \ + (byte & 0x20 ? '1' : '0'), \ + (byte & 0x10 ? '1' : '0'), \ + (byte & 0x08 ? '1' : '0'), \ + (byte & 0x04 ? '1' : '0'), \ + (byte & 0x02 ? '1' : '0'), \ + (byte & 0x01 ? '1' : '0') +#define BYTE_TO_BINARY_PATTERN_16BIT "%c%c%c%c %c%c%c%c %c%c%c%c %c%c%c%c\r\n" +#define BYTE_TO_BINARY_16BIT(byte) \ + (byte & 0x8000 ? '1' : '0'), \ + (byte & 0x4000 ? '1' : '0'), \ + (byte & 0x2000 ? '1' : '0'), \ + (byte & 0x1000 ? '1' : '0'), \ + (byte & 0x800 ? '1' : '0'), \ + (byte & 0x400 ? '1' : '0'), \ + (byte & 0x200 ? '1' : '0'), \ + (byte & 0x100 ? '1' : '0'), \ + (byte & 0x80 ? '1' : '0'), \ + (byte & 0x40 ? '1' : '0'), \ + (byte & 0x20 ? '1' : '0'), \ + (byte & 0x10 ? '1' : '0'), \ + (byte & 0x08 ? '1' : '0'), \ + (byte & 0x04 ? '1' : '0'), \ + (byte & 0x02 ? '1' : '0'), \ + (byte & 0x01 ? '1' : '0') + +/** +8 \Note: ADS1299 Default Registers +*/ + +uint8_t ads1299_default_registers[] = { + ADS1299_REGDEFAULT_CONFIG1, + ADS1299_REGDEFAULT_CONFIG2, + ADS1299_REGDEFAULT_CONFIG3, + ADS1299_REGDEFAULT_LOFF, + ADS1299_REGDEFAULT_CH1SET, + ADS1299_REGDEFAULT_CH2SET, + ADS1299_REGDEFAULT_CH3SET, + ADS1299_REGDEFAULT_CH4SET, + ADS1299_REGDEFAULT_CH5SET, + ADS1299_REGDEFAULT_CH6SET, + ADS1299_REGDEFAULT_CH7SET, + ADS1299_REGDEFAULT_CH8SET, + ADS1299_REGDEFAULT_BIAS_SENSP, + ADS1299_REGDEFAULT_BIAS_SENSN, + ADS1299_REGDEFAULT_LOFF_SENSP, + ADS1299_REGDEFAULT_LOFF_SENSN, + ADS1299_REGDEFAULT_LOFF_FLIP, + ADS1299_REGDEFAULT_LOFF_STATP, + ADS1299_REGDEFAULT_LOFF_STATN, + ADS1299_REGDEFAULT_GPIO, + ADS1299_REGDEFAULT_MISC1, + ADS1299_REGDEFAULT_MISC2, + ADS1299_REGDEFAULT_CONFIG4 +}; + +/**@SPI HANDLERS: + * @brief SPI user event handler. + * @param event + */ +void spi_event_handler(nrf_drv_spi_evt_t const * p_event) +{ + /*switch (p_event->type) { + case NRF_DRV_SPI_EVENT_DONE: + break; + default: + break; + }*/ + //NRF_LOG_PRINTF(" >>> Transfer completed.\r\n"); +} + +/**@INITIALIZE SPI INSTANCE */ +static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0); //SPI INSTANCE +void ads_spi_init(void) { + nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG(0); + spi_config.bit_order = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST; + //SCLK = 1MHz is right speed because fCLK = (1/2)*SCLK, and fMOD = fCLK/4, and fMOD MUST BE 128kHz. Do the math. + spi_config.frequency = NRF_DRV_SPI_FREQ_1M; + spi_config.irq_priority = APP_IRQ_PRIORITY_LOW; + spi_config.mode = NRF_DRV_SPI_MODE_1; //CPOL = 0 (Active High); CPHA = TRAILING (1) + spi_config.miso_pin = ADS1299_SPI_MISO_PIN; + spi_config.sck_pin = ADS1299_SPI_SCLK_PIN; + spi_config.mosi_pin = ADS1299_SPI_MOSI_PIN; + spi_config.ss_pin = ADS1299_SPI_CS_PIN; + spi_config.orc = 0x55; + APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler)); + NRF_LOG_PRINTF(" SPI Initialized..\r\n"); +} + +/**@SPI-CLEARS BUFFER + * @brief The function initializes TX buffer to values to be sent and clears RX buffer. + * + * @note Function clears RX and TX buffers. + * + * @param[out] p_tx_data A pointer to a buffer TX. + * @param[out] p_rx_data A pointer to a buffer RX. + * @param[in] len A length of the data buffers. + */ +void init_buf(uint8_t * const p_tx_buffer, + uint8_t * const p_rx_buffer, + const uint16_t len) +{ + uint16_t i; + + for (i = 0; i < len; i++) + { + p_tx_buffer[i] = 0; + p_rx_buffer[i] = 0; + } + NRF_LOG_PRINTF(" SPI Buffer Cleared..\r\n"); +} +/************************************************************************************************************************************************** + * Function Definitions * + **************************************************************************************************************************************************/ + +/* + * ADS1299 CONTROL FUNCTIONS: + */ +void ads1299_init_regs(void) { + uint8_t err_code; + uint8_t num_registers = 23; + uint8_t txrx_size = num_registers+2; + uint8_t tx_data_spi[txrx_size]; //Size = 14 bytes + uint8_t rx_data_spi[txrx_size]; //Size = 14 bytes + uint8_t wreg_init_opcode = 0x41; + for (int i = 0; i < txrx_size; ++i) { + tx_data_spi[i] = 0; + rx_data_spi[i] = 0; + } + tx_data_spi[0] = wreg_init_opcode; + tx_data_spi[1] = num_registers - 1; + for (int j = 0; j < num_registers; ++j) { + tx_data_spi[j+2] = ads1299_default_registers[j]; + } + err_code = nrf_drv_spi_transfer(&spi, tx_data_spi, num_registers+2, rx_data_spi, num_registers+2); + nrf_delay_ms(150); + NRF_LOG_PRINTF(" Power-on reset and initialization procedure.. EC: %d \r\n",err_code); +} + +void ads1299_powerup_reset(void) +{ + #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) + nrf_gpio_pin_clear(ADS1299_PWDN_RST_PIN); + #endif + #if defined(BOARD_FULL_EEG_V1) + nrf_gpio_pin_clear(ADS1299_RESET_PIN); + nrf_gpio_pin_clear(ADS1299_PWDN_PIN); + #endif + nrf_delay_ms(50); + NRF_LOG_PRINTF(" ADS1299-x POWERED UP AND RESET..\r\n"); +} + +void ads1299_powerdn(void) +{ + #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) + nrf_gpio_pin_clear(ADS1299_PWDN_RST_PIN); + #endif + #if defined(BOARD_FULL_EEG_V1) + nrf_gpio_pin_clear(ADS1299_RESET_PIN); + nrf_gpio_pin_clear(ADS1299_PWDN_PIN); + #endif + nrf_delay_us(20); + NRF_LOG_PRINTF(" ADS1299-x POWERED DOWN..\r\n"); +} + +void ads1299_powerup(void) +{ + #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) + nrf_gpio_pin_set(ADS1299_PWDN_RST_PIN); + #endif + #if defined(BOARD_FULL_EEG_V1) + nrf_gpio_pin_set(ADS1299_RESET_PIN); + nrf_gpio_pin_set(ADS1299_PWDN_PIN); + #endif + nrf_delay_ms(1000); // Allow time for power-on reset + NRF_LOG_PRINTF(" ADS1299-x POWERED UP...\r\n"); +} + +void ads1299_standby(void) { + uint8_t tx_data_spi; + uint8_t rx_data_spi; + + tx_data_spi = ADS1299_OPC_STANDBY; + + nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); + NRF_LOG_PRINTF(" ADS1299-x placed in standby mode...\r\n"); +} + +void ads1299_wake(void) { + uint8_t tx_data_spi; + uint8_t rx_data_spi; + + tx_data_spi = ADS1299_OPC_WAKEUP; + + nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); + nrf_delay_ms(10); // Allow time to wake up - 10ms + NRF_LOG_PRINTF(" ADS1299-x Wakeup..\r\n"); +} + +void ads1299_soft_start_conversion(void) { + uint8_t tx_data_spi; + uint8_t rx_data_spi; + + tx_data_spi = ADS1299_OPC_START; + + nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); + NRF_LOG_PRINTF(" Start ADC conversion..\r\n"); +} + +void ads1299_stop_rdatac(void) { + uint8_t tx_data_spi; + uint8_t rx_data_spi; + + tx_data_spi = ADS1299_OPC_SDATAC; + + nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); + NRF_LOG_PRINTF(" Continuous Data Output Disabled..\r\n"); +} + +void ads1299_start_rdatac(void) { + uint8_t tx_data_spi; + uint8_t rx_data_spi; + + tx_data_spi = ADS1299_OPC_RDATAC; + + nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); + NRF_LOG_PRINTF(" Continuous Data Output Enabled..\r\n"); +} + +void ads1299_check_id(void) { + uint8_t device_id_reg_value; + uint8_t tx_data_spi[3]; + uint8_t rx_data_spi[7]; + tx_data_spi[0] = 0x20; //Request Device ID + tx_data_spi[1] = 0x01; //Intend to read 1 byte + tx_data_spi[2] = 0x00; //This will be replaced by Reg Data + nrf_drv_spi_transfer(&spi, tx_data_spi, 2+tx_data_spi[1], rx_data_spi, 2+tx_data_spi[1]); + nrf_delay_ms(20); //Wait for response: + device_id_reg_value = rx_data_spi[2]; + bool is_ads_1299_4 = (device_id_reg_value & 0x1F) == (ADS1299_4_DEVICE_ID); + bool is_ads_1299_6 = (device_id_reg_value & 0x1F) == (ADS1299_6_DEVICE_ID); + bool is_ads_1299 = (device_id_reg_value & 0x1F) == (ADS1299_DEVICE_ID); + uint8_t revisionVersion = (device_id_reg_value & 0xE0)>>5; + if (is_ads_1299||is_ads_1299_6||is_ads_1299_4) { + NRF_LOG_PRINTF("Device Address Matches!\r\n"); + } else { + NRF_LOG_PRINTF("********SPI I/O Error, Device Not Detected! *********** \r\n"); + NRF_LOG_PRINTF("SPI Transfer Dump: \r\n"); + NRF_LOG_PRINTF("ID[b0->2]: [0x%x | 0x%x | 0x%x] \r\n", rx_data_spi[0],rx_data_spi[1],rx_data_spi[2]); + NRF_LOG_PRINTF("ID[b3->6]: [0x%x | 0x%x | 0x%x | 0x%x] \r\n", rx_data_spi[3],rx_data_spi[4],rx_data_spi[5],rx_data_spi[6]); + } + if (is_ads_1299) { + NRF_LOG_PRINTF("Device Name: ADS1299 \r\n"); + } else if (is_ads_1299_6) { + NRF_LOG_PRINTF("Device Name: ADS1299-6 \r\n"); + } else if (is_ads_1299_4) { + NRF_LOG_PRINTF("Device Name: ADS1299-4 \r\n"); + } + if (is_ads_1299||is_ads_1299_6||is_ads_1299_4) { + NRF_LOG_PRINTF("Device Revision #%d\r\n",revisionVersion); + NRF_LOG_PRINTF("Device ID: 0x%x \r\n",device_id_reg_value); + } +} + +/* DATA RETRIEVAL FUNCTIONS **********************************************************************************************************************/ + +/**@brief Function for acquiring a EEG Voltage Measurement samples. + * + * @details Uses SPI + * + */ +void get_eeg_voltage_samples (int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4) { + uint8_t tx_rx_data[15] = {0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00}; + nrf_drv_spi_transfer(&spi, tx_rx_data, 15, tx_rx_data, 15); + uint8_t cnt = 0; + do { + if(tx_rx_data[0]==0xC0){ + *eeg1 = ( (tx_rx_data[3] << 16) | (tx_rx_data[4] << 8) | (tx_rx_data[5]) ); + *eeg2 = ( (tx_rx_data[6] << 16) | (tx_rx_data[7] << 8) | (tx_rx_data[8]) ); + *eeg3 = ( (tx_rx_data[9] << 16) | (tx_rx_data[10] << 8) | (tx_rx_data[11]) ); + *eeg4 = ( (tx_rx_data[12] << 16) | (tx_rx_data[13] << 8) | (tx_rx_data[14]) ); + break; + } + cnt++; + nrf_delay_us(1); + } while(cnt<255); + //NRF_LOG_PRINTF("B0-2 = [0x%x 0x%x 0x%x | cnt=%d]\r\n",tx_rx_data[0],tx_rx_data[1],tx_rx_data[2],cnt); + //NRF_LOG_PRINTF("DATA:[0x%x 0x%x 0x%x 0x%x]\r\n",*eeg1,*eeg2,*eeg3,*eeg4); +} + +// // // // // // +// End of File // +// // // // // // diff --git a/drivers/ads1299-x.h b/drivers/ads1299-x.h new file mode 100644 index 0000000..0f0336f --- /dev/null +++ b/drivers/ads1299-x.h @@ -0,0 +1,209 @@ +/* Copyright (c) 2017 Musa Mahmood + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** @file + * + * @brief Functions for initializing and controlling Texas Instruments ADS1299 analog front-end. + */ + +#ifndef ADS1299_H__ +#define ADS1299_H__ + +#include +#include "nrf_drv_spi.h" + +#ifdef __cplusplus + extern "C" { +#endif +/**@SPI STUFF*/ +#ifdef BOARD_PCA10028 + #define ADS1299_SPI_SCLK_PIN 10 + #define ADS1299_SPI_CS_PIN 11 + #define ADS1299_SPI_MOSI_PIN 14 //MASTER (nRF) OUT; SLAVE (ADS) DIN + #define ADS1299_SPI_MISO_PIN 9 //MASTER (nRF) IN ; SLAVE (ADS) DOUT + #define ADS1299_PWDN_RST_PIN 12 + #define ADS1299_DRDY_PIN 8 +#elif defined(BOARD_NRF_BREAKOUT) + //BOARD_CUSTOM BOARD_NRF_BREAKOUT + #define ADS1299_SPI_SCLK_PIN 9 + #define ADS1299_SPI_CS_PIN 10 + #define ADS1299_SPI_MOSI_PIN 12 //MASTER (nRF) OUT; SLAVE (ADS) DIN + #define ADS1299_SPI_MISO_PIN 8 //MASTER (nRF) IN ; SLAVE (ADS) DOUT + #define ADS1299_PWDN_RST_PIN 11 + #define ADS1299_DRDY_PIN 15 +#elif defined(BOARD_FULL_EEG_V1) + #define ADS1299_DRDY_PIN 8 + #define ADS1299_SPI_MISO_PIN 9 //MASTER (nRF) IN ; SLAVE (ADS) DOUT + #define ADS1299_SPI_SCLK_PIN 10 + #define ADS1299_SPI_CS_PIN 11 + #define ADS1299_RESET_PIN 12 + #define ADS1299_PWDN_PIN 13 + #define ADS1299_SPI_MOSI_PIN 14 //MASTER (nRF) OUT; SLAVE (ADS) DIN +#endif + + +#define ADS1299_NUM_REGS 24 + +/** + * \brief ADS1291_2 register addresses. + * + * Consult the ADS1291/2 datasheet and user's guide for more information. + */ +#define ADS1299_REGADDR_ID 0x00 ///< Chip ID register. Read-only. +#define ADS1299_REGADDR_CONFIG1 0x01 ///< Configuration register 1. Controls conversion mode and data rate. +#define ADS1299_REGADDR_CONFIG2 0x02 ///< Configuration register 2. Controls LOFF comparator, reference, CLK pin, and test signal. +#define ADS1299_REGADDR_CONFIG3 0x03 +#define ADS1299_REGADDR_LOFF 0x04 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. +#define ADS1299_REGADDR_CH1SET 0x05 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. +#define ADS1299_REGADDR_CH2SET 0x06 ///< Channel 2 settings register (ADS1292x only). Controls channel 2 input mux, gain, and power-down. +#define ADS1299_REGADDR_CH3SET 0x07 +#define ADS1299_REGADDR_CH4SET 0x08 +#define ADS1299_REGADDR_CH5SET 0x09 +#define ADS1299_REGADDR_CH6SET 0x0A +#define ADS1299_REGADDR_CH7SET 0x0B +#define ADS1299_REGADDR_CH8SET 0x0C +#define ADS1299_REGADDR_BIAS_SENSP 0x0D ///< RLD sense selection. Controls PGA chop frequency, RLD buffer, and channels for RLD derivation. +#define ADS1299_REGADDR_BIAS_SENSN 0x0E +#define ADS1299_REGADDR_LOFF_SENSP 0x0F ///< Lead-off sense selection. Controls current direction and selects channels that will use lead-off detection. +#define ADS1299_REGADDR_LOFF_SENSN 0x10 +#define ADS1299_REGADDR_LOFF_FLIP 0x11 +#define ADS1299_REGADDR_LOFF_STATP 0x12 ///< Lead-off status register. Bit 6 controls clock divider. For bits 4:0, 0: lead on, 1: lead off. +#define ADS1299_REGADDR_LOFF_STATN 0x13 +#define ADS1299_REGADDR_GPIO 0x14 ///< GPIO register. Controls state and direction of the ADS1291_2 GPIO pins. +#define ADS1299_REGADDR_MISC1 0x15 ///< Respiration 1 (ADS1292R only). See datasheet. +#define ADS1299_REGADDR_MISC2 0x16 ///< Respiration 2. Controls offset calibration, respiration modulator freq, and RLDREF signal source. +#define ADS1299_REGADDR_CONFIG4 0x17 + +/** + * \brief ADS1299 SPI communication opcodes. + * + * Consult the ADS1299 datasheet and user's guide for more information. + * For RREG and WREG opcodes, the first byte (opcode) must be ORed with the address of the register to be read/written. + * The command is completed with a second byte 000n nnnn, where n nnnn is (# registers to read) - 1. + */ + +#define ADS1299_OPC_WAKEUP 0x02 ///< Wake up from standby. +#define ADS1299_OPC_STANDBY 0x04 ///< Enter standby. +#define ADS1299_OPC_RESET 0x06 ///< Reset all registers. +#define ADS1299_OPC_START 0x08 ///< Start data conversions. +#define ADS1299_OPC_STOP 0x0A ///< Stop data conversions. +#define ADS1299_OPC_OFFSETCAL 0x1A ///< Calibrate channel offset. RESP2.CALIB_ON must be 1. Execute after every PGA gain change. +#define ADS1299_OPC_RDATAC 0x10 ///< Read data continuously (registers cannot be read or written in this mode). +#define ADS1299_OPC_SDATAC 0x11 ///< Stop continuous data read. +#define ADS1299_OPC_RDATA 0x12 ///< Read single data value. + +#define ADS1299_OPC_RREG 0x20 ///< Read register value. System must not be in RDATAC mode. +#define ADS1299_OPC_WREG 0x40 ///< Write register value. + + + /**********************************/ + +/* ID REGISTER ********************************************************************/ + +/** + * \brief Factory-programmed device ID for ADS1299 & ADS1299-x. + */ + +#define ADS1299_4_DEVICE_ID 0x1C //Device ID [0bvvv11100] Where vvv is the version bits +#define ADS1299_6_DEVICE_ID 0x1D //Device ID [0bvvv11101] +#define ADS1299_DEVICE_ID 0x1E //Device ID [0bvvv11101] + +/* DEFAULT REGISTER VALUES ********************************************************/ + +//Use multi-line copy from excel file. +#define ADS1299_REGDEFAULT_CONFIG1 0xB6 ///< Configuration register 1. Controls conversion mode and data rate. +#define ADS1299_REGDEFAULT_CONFIG2 0xD2 ///< Configuration register 2. Controls LOFF comparator, reference, CLK pin, and test signal. +#define ADS1299_REGDEFAULT_CONFIG3 0xEC +#define ADS1299_REGDEFAULT_LOFF 0x02 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. +#define ADS1299_REGDEFAULT_CH1SET 0x60 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. +#define ADS1299_REGDEFAULT_CH2SET 0x60 ///< +#define ADS1299_REGDEFAULT_CH3SET 0x60 +#define ADS1299_REGDEFAULT_CH4SET 0x60 +#define ADS1299_REGDEFAULT_CH5SET 0xF1 +#define ADS1299_REGDEFAULT_CH6SET 0xF1 +#define ADS1299_REGDEFAULT_CH7SET 0xF1 +#define ADS1299_REGDEFAULT_CH8SET 0xF1 +#define ADS1299_REGDEFAULT_BIAS_SENSP 0x0F ///< +#define ADS1299_REGDEFAULT_BIAS_SENSN 0x0F +#define ADS1299_REGDEFAULT_LOFF_SENSP 0x00 ///< +#define ADS1299_REGDEFAULT_LOFF_SENSN 0x00 +#define ADS1299_REGDEFAULT_LOFF_FLIP 0x00 +#define ADS1299_REGDEFAULT_LOFF_STATP 0x00 ///< +#define ADS1299_REGDEFAULT_LOFF_STATN 0x00 +#define ADS1299_REGDEFAULT_GPIO 0x0F ///< +#define ADS1299_REGDEFAULT_MISC1 0x20 ///< +#define ADS1299_REGDEFAULT_MISC2 0x00 ///< +#define ADS1299_REGDEFAULT_CONFIG4 0x00 + + +// +// 0x00 = 125SPS +// 0x01 = 250SPS +// 0x02 = 500SPS +// 0x03 = 1000SPS +// 0x04 = 2000SPS +// 0x05 = 4000SPS +// 0x06 = 8000SPS +// + +/**@TYPEDEFS: */ +typedef int16_t body_voltage_t; + +/************************************************************************************************************************************************** +* Function Prototypes ADS1299-x * +**************************************************************************************************************************************************/ +void ads_spi_init(void); + + +void init_buf(uint8_t * const p_tx_buffer, + uint8_t * const p_rx_buffer, + const uint16_t len); + +/** + * \brief Initialize the ADS1299-x. + * + * This function performs the power-on reset and initialization procedure documented on page 61 of the + * ADS1299 datasheet, up to "Send SDATAC Command." + */ +void ads1299_powerup_reset(void); + +void ads1299_init_regs(void); + +void ads1299_powerdn(void); + +void ads1299_powerup(void); + +void ads1299_standby(void); + +void ads1299_wake(void); + +void ads1299_soft_start_conversion(void); + +void ads1299_stop_rdatac(void); + +void ads1299_start_rdatac(void); + +void ads1299_check_id(void); + +void get_eeg_voltage_samples (int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4); + + +#endif // ADS1299_H__ From 896284ad5419a5d9a8b4e165612337f5c76907a6 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Mon, 15 Jan 2024 19:29:38 -0700 Subject: [PATCH 03/11] fixes thread classes, firmware builds --- CMakeLists.txt | 1 + prj.conf | 3 +- src/CustomFrontEndWrapper.cpp | 20 ++++------ src/CustomFrontEndWrapper.h | 6 +-- src/DataAcquisitionThread.cpp | 40 +++++++++----------- src/DataAcquisitionThread.h | 23 ++++++++++-- src/core/MessageQueue.h | 69 +++++++++++++++++++++++++++++++++++ src/core/Thread.cpp | 24 ------------ src/core/Thread.h | 43 ++++++++++++++-------- src/core/ThreadCommand.h | 28 -------------- src/main.cpp | 4 -- 11 files changed, 145 insertions(+), 116 deletions(-) create mode 100644 src/core/MessageQueue.h delete mode 100644 src/core/Thread.cpp delete mode 100644 src/core/ThreadCommand.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fb8a6d5..c7ed7b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(app_core) # NORDIC SDK APP START diff --git a/prj.conf b/prj.conf index 4cd7834..9f56256 100644 --- a/prj.conf +++ b/prj.conf @@ -16,7 +16,7 @@ CONFIG_SPI=y CONFIG_SERIAL=y CONFIG_FLASH=n CONFIG_PM=n -CONFIG_BOARD_ENABLE_CPUNET=y +# CONFIG_BOARD_ENABLE_CPUNET=y # Enable CMSIS to be used through nrfSDK CONFIG_FPU=y @@ -33,6 +33,7 @@ CONFIG_DMA=y CONFIG_CPP=y # Enable firmware settings +CONFIG_USERSPACE=y CONFIG_DEBUG_THREAD_INFO=y CONFIG_DEBUG_OPTIMIZATIONS=y CONFIG_DYNAMIC_INTERRUPTS=n diff --git a/src/CustomFrontEndWrapper.cpp b/src/CustomFrontEndWrapper.cpp index 77e9485..a53c872 100644 --- a/src/CustomFrontEndWrapper.cpp +++ b/src/CustomFrontEndWrapper.cpp @@ -1,23 +1,17 @@ #include "CustomFrontEndWrapper.h" -const char *CustomFrontEndWrapper::spi_device_name = "ADS1299"; -const struct device *CustomFrontEndWrapper::spi_dev; +// const char *CustomFrontEndWrapper::spi_device_name = "ADS1299"; +// const struct device *CustomFrontEndWrapper::spi_dev; CustomFrontEndWrapper::CustomFrontEndWrapper() { - spi_dev = device_get_binding(SPI_DEV_NAME); - if (!spi_dev) { - // TODO: print on debug uart - return; - } + // spi_dev = device_get_binding(SPI_DEV_NAME); + // if (!spi_dev) { + // // TODO: print on debug uart + // return; + // } } void CustomFrontEndWrapper::Initialize() { - // // Set SPI to default settings - // spi_cfg_t spi_config spi_cfg = { - // .operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8), - // .frequency = 500000, // 500 kHz - // .slave = 0, - // }; // if (spi_configure(spi_dev, &spi_cfg) != 0) { // // TODO: print on debug UART diff --git a/src/CustomFrontEndWrapper.h b/src/CustomFrontEndWrapper.h index e8475a8..9184395 100644 --- a/src/CustomFrontEndWrapper.h +++ b/src/CustomFrontEndWrapper.h @@ -7,12 +7,12 @@ #include "AnalogFrontEndWrapper.h" #include "DataBufferManager.h" -#define SPI_DEV_NAME DT_LABEL(DT_NODELABEL(spi0)) +// #define SPI_DEV_NAME DT_LABEL(DT_NODELABEL(spi0)) class CustomFrontEndWrapper : public AnalogFrontEndWrapper { private: - static const char *spi_device_name; - static const struct device *spi_dev; + // static const char *spi_device_name; + // static const struct device *spi_dev; public: CustomFrontEndWrapper(); diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp index a07f30e..60617e9 100644 --- a/src/DataAcquisitionThread.cpp +++ b/src/DataAcquisitionThread.cpp @@ -1,34 +1,28 @@ #include "DataAcquisitionThread.h" void DataAcquisitionThread::Initialize() { - if (thread_id == nullptr) { - // thread doesnt exist, make one naow: - thread_id = k_thread_create( - K_THREAD_STACK_ALLOC(daq_thread_stack, DATA_ACQUISITION_THREAD_STACK_SIZE_B), - K_THREAD_STACK_SIZEOF(daq_thread_stack), - Run, - this, // NEED to pass this ptr because it doesnt exist in the static function - nullptr, - nullptr, - K_PRIO_COOP(7), // TODO: change - 0, - K_NO_WAIT); + if (id == nullptr) { + id = k_thread_create( + &data_acq_thread_data, data_acq_stack_area, + K_THREAD_STACK_SIZEOF(data_acq_stack_area), + DataAcquisitionThread::RunThreadSequence, + this, NULL, NULL, + DATA_ACQ_THREAD_PRIORITY, 0, K_NO_WAIT + ); - k_thread_name_set(thread_id, "DataAcquisitionThread"); - - if (thread_id == nullptr) { - // TODO: print err on debug uart - } + k_thread_name_set(id, "DataAcquisitionThread"); } } + void DataAcquisitionThread::Run() { + uint8_t message = 0; while (true) { - ThreadMessage msg; - if (k_msgq_get(&message_queue, &msg, K_FOREVER) == 0) { - switch(msg.data_acq_msg) { - STOP_READING_AFE: - START_READING_AFE: - INVALID: + if (message_queue.get(message)) { + switch (static_cast(message)) { + case STOP_READING_AFE: + case START_READING_AFE: + case INVALID: + default: break; } } diff --git a/src/DataAcquisitionThread.h b/src/DataAcquisitionThread.h index 3e160ad..b282d4c 100644 --- a/src/DataAcquisitionThread.h +++ b/src/DataAcquisitionThread.h @@ -3,20 +3,35 @@ #include "core/Thread.h" -#define DATA_ACQUISITION_THREAD_STACK_SIZE_B 1024 +#define DATA_ACQ_THREAD_STACK_SIZE_B 1024 +#define DATA_ACQ_THREAD_PRIORITY 5 +#define DATA_ACQ_THREAD_MSG_Q_DEPTH 10 -class DataAcquisitionThread : public Thread { +K_THREAD_STACK_DEFINE(data_acq_stack_area, DATA_ACQ_THREAD_STACK_SIZE_B); +struct k_thread data_acq_thread_data; + +class DataAcquisitionThread : public Thread { private: - k_tid_t thread_id; DataAcquisitionThread(); + ~DataAcquisitionThread() = default; + + // need to delete copy cstor and assign optor + DataAcquisitionThread(const DataAcquisitionThread &) = delete; + DataAcquisitionThread& operator=(const DataAcquisitionThread&) = delete; public: + enum DataAcquisitionThreadMessage : uint8_t { + STOP_READING_AFE = 0, + START_READING_AFE, + INVALID, + }; + void Initialize() override; void Run() override; - // NOTE: using singleton static DataAcquisitionThread& GetInstance() { // NOTE: this method of using static local variable is thread-safe + // NOTE: this has to be implemented in the child class static DataAcquisitionThread instance; return instance; } diff --git a/src/core/MessageQueue.h b/src/core/MessageQueue.h new file mode 100644 index 0000000..3327253 --- /dev/null +++ b/src/core/MessageQueue.h @@ -0,0 +1,69 @@ +#ifndef THREAD_MESSAGE_H +#define THREAD_MESSAGE_H + +#include + +template class MessageQueue { +private: + size_t msg_size_B; + size_t queue_depth_B; + char message_buffer[depth * sizeof(element_type)]; + struct k_msgq queue; +public: + MessageQueue() { + msg_size_B = sizeof(element_type); + queue_depth_B = msg_size_B * depth; + k_msgq_init(&queue, message_buffer, depth, msg_size_B); + }; + ~MessageQueue() = default; + + bool push(const element_type& msg) { + if (k_msgq_put(&queue, static_cast(&msg), K_NO_WAIT) != 0) { + // TODO: uart debug message send failed + return false; + } + return true; + }; + + bool push_with_timeout(const element_type& msg, int64_t ticks) { + if (k_msgq_put(&queue, static_cast(&msg), k_timeout_t(ticks)) != 0) { + return false; + } + return true; + }; + + bool get(element_type& msg) { + // NOTE: this MUST be used if accessing queue from ISR + if (k_msgq_get(&queue, static_cast(&msg), K_NO_WAIT) != 0) { + return false; + } + return true; + } + + bool get_with_timeout(element_type& msg, int64_t ticks) { + if (k_msgq_get(&queue, static_cast(&msg), k_timeout_t(ticks)) != 0) { + return false; + } + return true; + } + + bool peak(element_type& msg) { + if (k_msgq_peek(&queue, static_cast(&msg)) != 0) { + return false; + } + return true; + }; + + bool peak_at(element_type& msg, uint32_t idx) { + if (k_msgq_peek_at(&queue, static_cast(&msg), idx) != 0) { + return false; + } + return true; + }; + + void resetMessageQueue() { + k_msgq_purge(&queue); + }; +}; + +#endif \ No newline at end of file diff --git a/src/core/Thread.cpp b/src/core/Thread.cpp deleted file mode 100644 index 74c7794..0000000 --- a/src/core/Thread.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "Thread.h" - -Thread::Thread() { - // TODO - if (k_msgq_init(&message_queue, message_queue_buffer, MAX_QUEUE_DEPTH, MESSAGE_SIZE_BYTES) != 0) { - // TODO: debug message err - return; - } -} - -bool Thread::SendMessage(ThreadMessage msg) { - if (k_msgq_put(&message_queue, &msg, K_NO_WAIT) != 0) { - // TODO: uart debug message send failed - return false; - } - return true; -} - -// This is a static function required by k_thread_create -void Thread::RunThreadSequence(void* instance, void*, void*) { - // HAVE to receive an instance ptr to run the correct thread sequence - // because static functions are not associated with thread objects - static_cast(instance)->Run(); -} \ No newline at end of file diff --git a/src/core/Thread.h b/src/core/Thread.h index 25db09a..ebc983c 100644 --- a/src/core/Thread.h +++ b/src/core/Thread.h @@ -3,28 +3,39 @@ #include #include -#include "ThreadCommand.h" +#include "MessageQueue.h" -#define MAX_QUEUE_DEPTH 10 - -class Thread { +template class Thread { protected: - k_tid_t thread_id; - struct k_msgq message_queue; - ThreadMessage message_queue_buffer[MAX_QUEUE_DEPTH]; - Thread(void); - -private: - // need to delete copy cstor and assign optor - Thread(const Thread &) = delete; - Thread& operator=(const Thread&) = delete; + k_tid_t id; + MessageQueue message_queue; public: - bool SendMessage(ThreadMessage msg); + Thread() : id(nullptr) {}; + virtual ~Thread() {}; + virtual void Initialize() = 0; - static void RunThreadSequence(void* instance, void*, void*); - virtual void Run() = 0; + virtual void Run () = 0; + + bool SendMessage(uint8_t msg) { + if (message_queue.push(msg) == false) { + // TODO: uart debug message send failed + return false; + } + return true; + }; + + // This is a static function required by k_thread_create + // NOTE: can't have a virtual static function, need this to be defined for all + static void RunThreadSequence(void* instance, void*, void*) { + // HAVE to receive an instance ptr to run the correct thread sequence + // because static functions are not associated with thread objects + static_cast(instance)->Run(); + }; + //// NOTE: MAY need to add killing/re-starting threads + // void Start(); + // void Kill(); }; #endif \ No newline at end of file diff --git a/src/core/ThreadCommand.h b/src/core/ThreadCommand.h deleted file mode 100644 index a216ea1..0000000 --- a/src/core/ThreadCommand.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef THREAD_COMMAND_H -#define THREAD_COMMAND_H - -#include - -#define MESSAGE_SIZE_BYTES sizeof(ThreadMessage) - -enum class DataAcquisitionThreadMessage : uint8_t { - STOP_READING_AFE = 0, - START_READING_AFE, - INVALID, -}; - -enum class SignalProcessingThreadMessage : uint8_t { - STOP_PROCESSING_EEG = 0, - START_PROCESSING_EEG, - INVALID, -}; - -// Combined message type -union ThreadMessage { - DataAcquisitionThreadMessage data_acq_msg; - SignalProcessingThreadMessage sig_proc_msg; - - // Add types as needed -}; - -#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 67d940c..573fd2b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,10 +85,6 @@ static void test_arm_rfft_f32_real_backend( free(scratch); } -static void test_arm_rfft_f32_real(const uint32_t *input, const uint32_t *ref, size_t length) { - test_arm_rfft_f32_real_backend(false, input, ref, length); -} - int main(void) { /* Power off RAM and suspend CPU */ From 6a14d6cd24a1949929c12ddb5d3a1deaf642f2c8 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Tue, 16 Jan 2024 01:44:11 -0700 Subject: [PATCH 04/11] fixes thread priority issue. adds duplicate log module for daq thread?? --- CMakeLists.txt | 3 +- prj.conf | 22 ++++--- src/AnalogFrontEndWrapper.h | 7 +-- src/CustomFrontEndWrapper.h | 7 +-- src/Data.h | 6 +- src/DataAcquisitionThread.cpp | 36 ++++++++--- src/DataAcquisitionThread.h | 16 ++--- src/DataBufferManager.h | 7 +-- src/TIFrontEndWrapper.h | 5 +- src/core/MessageQueue.h | 7 +-- src/core/Semaphore.h | 7 +-- src/core/Thread.h | 7 +-- src/main.cpp | 115 ++++------------------------------ 13 files changed, 75 insertions(+), 170 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7ed7b8..d960b4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,9 +14,10 @@ project(app_core) target_sources(app PRIVATE src/main.cpp) # NORDIC SDK APP END -zephyr_library_include_directories(.) +# COMPILER AND LINKER OPTIONS # Add project directories +zephyr_library_include_directories(.) add_subdirectory(src) # Add CMSIS library from nrfSDK diff --git a/prj.conf b/prj.conf index 9f56256..144a760 100644 --- a/prj.conf +++ b/prj.conf @@ -3,10 +3,9 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # - CONFIG_MULTITHREADING=y CONFIG_KERNEL_MEM_POOL=y -CONFIG_NUM_PREEMPT_PRIORITIES=0 +CONFIG_NUM_PREEMPT_PRIORITIES=5 CONFIG_SYS_CLOCK_EXISTS=y CONFIG_ARM_MPU=y CONFIG_I2C=n @@ -16,6 +15,10 @@ CONFIG_SPI=y CONFIG_SERIAL=y CONFIG_FLASH=n CONFIG_PM=n + +# Enable log settings +CONFIG_LOG=y +CONFIG_CBPRINTF_FP_A_SUPPORT=y # CONFIG_BOARD_ENABLE_CPUNET=y # Enable CMSIS to be used through nrfSDK @@ -25,7 +28,6 @@ CONFIG_CMSIS_DSP=y CONFIG_CMSIS_DSP_TRANSFORM=y CONFIG_CMSIS_DSP_BASICMATH=y CONFIG_CMSIS_DSP_COMPLEXMATH=y -CONFIG_CMSIS_NN=y CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=2048 # Enable hardware peripherals @@ -43,8 +45,14 @@ CONFIG_THREAD_STACK_INFO=y CONFIG_THREAD_CUSTOM_DATA=n CONFIG_BOOT_BANNER=n CONFIG_BOOT_DELAY=0 -CONFIG_CONSOLE=y -CONFIG_UART_CONSOLE=y CONFIG_STDOUT_CONSOLE=n -CONFIG_PRINTK=y -CONFIG_EARLY_CONSOLE=n \ No newline at end of file +CONFIG_EARLY_CONSOLE=n + +# Enable CPP specific +CONFIG_CPLUSPLUS=y +CONFIG_LIB_CPLUSPLUS=y +CONFIG_NEWLIB_LIBC=y +CONFIG_STD_CPP14=y +# CONFIG_CPLUSPLUS=y +# CONFIG_CPLUSPLUS_STANDARD_CXX11=y +# CONFIG_CPLUSPLUS_LIBSTDCXX=y diff --git a/src/AnalogFrontEndWrapper.h b/src/AnalogFrontEndWrapper.h index 7fed64d..0476350 100644 --- a/src/AnalogFrontEndWrapper.h +++ b/src/AnalogFrontEndWrapper.h @@ -1,5 +1,4 @@ -#ifndef ANALOG_FRONT_END_WRAPPER_H -#define ANALOG_FRONT_END_WRAPPER_H +#pragma once #include "Data.h" @@ -10,6 +9,4 @@ class AnalogFrontEndWrapper { virtual void Initialize() = 0; virtual void Configure() = 0; virtual eeg_sample ReadData() = 0; -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/CustomFrontEndWrapper.h b/src/CustomFrontEndWrapper.h index 9184395..dc9115b 100644 --- a/src/CustomFrontEndWrapper.h +++ b/src/CustomFrontEndWrapper.h @@ -1,5 +1,4 @@ -#ifndef CUSTOM_FRONT_END_WRAPPER_H -#define CUSTOM_FRONT_END_WRAPPER_H +#pragma once #include #include @@ -20,6 +19,4 @@ class CustomFrontEndWrapper : public AnalogFrontEndWrapper { void Initialize() override; void Configure() override; eeg_sample ReadData() override; -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/Data.h b/src/Data.h index 48286bf..40bb926 100644 --- a/src/Data.h +++ b/src/Data.h @@ -1,6 +1,4 @@ -#ifndef DATA_H -#define DATA_H - +#pragma once // #include #include @@ -12,5 +10,3 @@ struct eeg_sample { uint8_t buf[3] = { 0 }; // for max 24 bit adc readings TODO: add units uint64_t timestamp_ms = 0; }; - -#endif \ No newline at end of file diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp index 60617e9..c5e8b99 100644 --- a/src/DataAcquisitionThread.cpp +++ b/src/DataAcquisitionThread.cpp @@ -1,7 +1,19 @@ #include "DataAcquisitionThread.h" +LOG_MODULE_REGISTER(eegals_app_core_daq, LOG_LEVEL_DBG); +K_THREAD_STACK_DEFINE(data_acq_stack_area, DATA_ACQ_THREAD_STACK_SIZE_B); +struct k_thread data_acq_thread_data; + +DataAcquisitionThread::DataAcquisitionThread() { + // todo + LOG_DBG("DataAcq::%s -- constructor called", __FUNCTION__); +} + void DataAcquisitionThread::Initialize() { + LOG_DBG("DataAcq::%s -- making thread", __FUNCTION__); + if (id == nullptr) { + LOG_DBG("DataAcq::%s -- id == nullptr passed", __FUNCTION__); id = k_thread_create( &data_acq_thread_data, data_acq_stack_area, K_THREAD_STACK_SIZEOF(data_acq_stack_area), @@ -10,21 +22,27 @@ void DataAcquisitionThread::Initialize() { DATA_ACQ_THREAD_PRIORITY, 0, K_NO_WAIT ); + LOG_DBG("DataAcq::%s -- thread create successful", __FUNCTION__); + k_thread_name_set(id, "DataAcquisitionThread"); + + LOG_DBG("DataAcq::%s -- set name successful", __FUNCTION__); } } void DataAcquisitionThread::Run() { uint8_t message = 0; while (true) { - if (message_queue.get(message)) { - switch (static_cast(message)) { - case STOP_READING_AFE: - case START_READING_AFE: - case INVALID: - default: - break; - } - } + // if (message_queue.get(message)) { + // switch (static_cast(message)) { + // case STOP_READING_AFE: + // case START_READING_AFE: + // case INVALID: + // default: + // break; + // } + // } + LOG_DBG("DataAcq::%s -- up time %u ms", __FUNCTION__, k_uptime_get_32()); + k_msleep(2000); } } diff --git a/src/DataAcquisitionThread.h b/src/DataAcquisitionThread.h index b282d4c..9187869 100644 --- a/src/DataAcquisitionThread.h +++ b/src/DataAcquisitionThread.h @@ -1,15 +1,13 @@ -#ifndef DATA_ACQUISITION_THREAD_H -#define DATA_ACQUISITION_THREAD_H +#pragma once +#include +#include #include "core/Thread.h" -#define DATA_ACQ_THREAD_STACK_SIZE_B 1024 -#define DATA_ACQ_THREAD_PRIORITY 5 +#define DATA_ACQ_THREAD_STACK_SIZE_B 4096 +#define DATA_ACQ_THREAD_PRIORITY 4 // max based on prj config #define DATA_ACQ_THREAD_MSG_Q_DEPTH 10 -K_THREAD_STACK_DEFINE(data_acq_stack_area, DATA_ACQ_THREAD_STACK_SIZE_B); -struct k_thread data_acq_thread_data; - class DataAcquisitionThread : public Thread { private: DataAcquisitionThread(); @@ -35,6 +33,4 @@ class DataAcquisitionThread : public Thread { static DataAcquisitionThread instance; return instance; } -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/DataBufferManager.h b/src/DataBufferManager.h index a8e41a6..995cb6d 100644 --- a/src/DataBufferManager.h +++ b/src/DataBufferManager.h @@ -1,5 +1,4 @@ -#ifndef DATA_BUFFER_MANAGER_H -#define DATA_BUFFER_MANAGER_H +#pragma once #include #include @@ -18,6 +17,4 @@ class DataBufferManager { static void Write(); static void spi_dma_setup(); static void spi_sensor_read_and_transfer(); -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/TIFrontEndWrapper.h b/src/TIFrontEndWrapper.h index 661c077..3daef2a 100644 --- a/src/TIFrontEndWrapper.h +++ b/src/TIFrontEndWrapper.h @@ -1,5 +1,4 @@ -#ifndef TI_FRONT_END_WRAPPER_H -#define TI_FRONT_END_WRAPPER_H +#pragma once #include "AnalogFrontEndWrapper.h" class TIFrontEndWrapper : public AnalogFrontEndWrapper { @@ -8,5 +7,3 @@ class TIFrontEndWrapper : public AnalogFrontEndWrapper { void Configure() override; eeg_sample ReadData() override; }; - -#endif \ No newline at end of file diff --git a/src/core/MessageQueue.h b/src/core/MessageQueue.h index 3327253..5886331 100644 --- a/src/core/MessageQueue.h +++ b/src/core/MessageQueue.h @@ -1,5 +1,4 @@ -#ifndef THREAD_MESSAGE_H -#define THREAD_MESSAGE_H +#pragma once #include @@ -64,6 +63,4 @@ template class MessageQueue { void resetMessageQueue() { k_msgq_purge(&queue); }; -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/Semaphore.h b/src/core/Semaphore.h index e1dbbc1..8562090 100644 --- a/src/core/Semaphore.h +++ b/src/core/Semaphore.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef SEMAPHORE_H -#define SEMAPHORE_H +#pragma once #include #include #include @@ -40,6 +39,4 @@ class Semaphore { int wait(void); int wait(int timeout); void give(void); -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/core/Thread.h b/src/core/Thread.h index ebc983c..f927bf9 100644 --- a/src/core/Thread.h +++ b/src/core/Thread.h @@ -1,5 +1,4 @@ -#ifndef THREAD_H -#define THREAD_H +#pragma once #include #include @@ -36,6 +35,4 @@ template class Thread { //// NOTE: MAY need to add killing/re-starting threads // void Start(); // void Kill(); -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 573fd2b..4223e90 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -13,117 +15,22 @@ #include #include "arm_math.h" +#include "DataAcquisitionThread.h" -#define ASSERT_MSG_BUFFER_ALLOC_FAILED "buffer allocation failed" -#define ASSERT_MSG_SNR_LIMIT_EXCEED "signal-to-noise ratio " \ - "error limit exceeded" +LOG_MODULE_REGISTER(eegals_app_core, LOG_LEVEL_DBG); -/** @brief Power OFF entire RAM and suspend CPU forever. - * - * Function operates only on `register` variables, so GCC will not use - * RAM after function prologue. This is also true even with optimizations - * disabled. Interrupts are disabled to make sure that they will never - * be executed when RAM is powered off. - * - * @param reg_begin Address to `POWER` register of the first NRF_VMC->RAM item - * @param reg_last Address to `POWER` register of the last NRF_VMC->RAM item - */ - -#define SNR_ERROR_THRESH ((float32_t)120) - -void disable_ram_and_wfi(register volatile uint32_t *reg_begin, - register volatile uint32_t *reg_last) -{ - __disable_irq(); - - do { - *reg_begin = 0; - // reg_begin += sizeof(NRF_VMC->RAM[0]) / sizeof(reg_begin[0]); - } while (reg_begin <= reg_last); - - __DSB(); - do { - __WFI(); - } while (1); -} - -/** - * NOTE: SHOULD NOT USE MALLOC OR USE SAFER ZEPHYR METHOD - * NOTE: USE ZEPHYR SAFE ASSERT -*/ -static void test_arm_rfft_f32_real_backend( - bool inverse, const uint32_t *input, const uint32_t *ref, - size_t length) -{ - arm_rfft_fast_instance_f32 inst; - float32_t *output, *scratch; - - /* Initialise instance */ - arm_rfft_fast_init_f32(&inst, length); - - /* Allocate output buffer */ - output = (float32_t*)malloc(length * sizeof(float32_t)); - // zassert_not_null(output, ASSERT_MSG_BUFFER_ALLOC_FAILED); - - scratch = (float32_t*)calloc(length + 2, sizeof(float32_t)); /* see #24701 */ - // zassert_not_null(scratch, ASSERT_MSG_BUFFER_ALLOC_FAILED); - - /* Load data in place */ - memcpy(scratch, input, length * sizeof(float32_t)); - - /* Run test function */ - arm_rfft_fast_f32(&inst, scratch, output, inverse); - - /* Validate output */ - // zassert_true( - // test_snr_error_f32(length, output, (float32_t *)ref, - // SNR_ERROR_THRESH), - // ASSERT_MSG_SNR_LIMIT_EXCEED); - - /* Free output buffer */ - free(output); - free(scratch); -} +#define LOG_DELAY_MS 1000 int main(void) { - /* Power off RAM and suspend CPU */ - // disable_ram_and_wfi(&NRF_VMC->RAM[0].POWER, - // &NRF_VMC->RAM[ARRAY_SIZE(NRF_VMC->RAM) - 1].POWER); + LOG_INF("Hello world from %s", CONFIG_BOARD); + DataAcquisitionThread::GetInstance().Initialize(); - return 0; -} - -/** @brief Allow access to specific GPIOs for the network core. - * - * Function is executed very early during system initialization to make sure - * that the network core is not started yet. More pins can be added if the - * network core needs them. - */ -static int network_gpio_allow(void) -{ - - /* When the use of the low frequency crystal oscillator (LFXO) is - * enabled, do not modify the configuration of the pins P0.00 (XL1) - * and P0.01 (XL2), as they need to stay configured with the value - * Peripheral. - */ - uint32_t start_pin = (IS_ENABLED(CONFIG_SOC_ENABLE_LFXO) ? 2 : 0); - - /* Allow the network core to use all GPIOs. */ - // for (uint32_t i = start_pin; i < P0_PIN_NUM; i++) { - // NRF_P0_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << - // GPIO_PIN_CNF_MCUSEL_Pos); - // } - - // for (uint32_t i = 0; i < P1_PIN_NUM; i++) { - // NRF_P1_S->PIN_CNF[i] = (GPIO_PIN_CNF_MCUSEL_NetworkMCU << - // GPIO_PIN_CNF_MCUSEL_Pos); - // } - + while(1) { + LOG_DBG("main thread up time: %u ms", k_uptime_get_32()); + k_msleep(LOG_DELAY_MS); + } return 0; } - -SYS_INIT(network_gpio_allow, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS); From 5677d3ffcdc8a1072aebf0efb8de0e018d1bb5a8 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Sun, 28 Jan 2024 16:20:44 -0700 Subject: [PATCH 05/11] Adds ads1299 driver, should be compatible with nrf53 now --- nrf5340dk_nrf5340_cpuapp.overlay | 17 + prj.conf | 6 +- src/CMakeLists.txt | 1 + src/DataAcquisitionThread.cpp | 22 +- src/drivers/CMakeLists.txt | 7 + .../ads1299-x.c => src/drivers/ads1299-x.cpp | 293 +++++++++--------- {drivers => src/drivers}/ads1299-x.h | 105 ++++--- src/main.cpp | 5 +- 8 files changed, 251 insertions(+), 205 deletions(-) create mode 100644 nrf5340dk_nrf5340_cpuapp.overlay create mode 100644 src/drivers/CMakeLists.txt rename drivers/ads1299-x.c => src/drivers/ads1299-x.cpp (54%) rename {drivers => src/drivers}/ads1299-x.h (69%) diff --git a/nrf5340dk_nrf5340_cpuapp.overlay b/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 0000000..b630728 --- /dev/null +++ b/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,17 @@ +// To get started, press Ctrl+Space to bring up the completion menu and view the available nodes. + +// You can also use the buttons in the sidebar to perform actions on nodes. +// Actions currently available include: + +// * Enabling / disabling the node +// * Adding the bus to a bus +// * Removing the node +// * Connecting ADC channels + +// For more help, browse the DeviceTree documentation at https://docs.zephyrproject.org/latest/guides/dts/index.html +// You can also visit the nRF DeviceTree extension documentation at https://nrfconnect.github.io/vscode-nrf-connect/devicetree/nrfdevicetree.html + +&spi0 { + max-frequency = ; + easydma-maxcnt-bits = <16>; +}; diff --git a/prj.conf b/prj.conf index 144a760..5da5242 100644 --- a/prj.conf +++ b/prj.conf @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # + CONFIG_MULTITHREADING=y CONFIG_KERNEL_MEM_POOL=y CONFIG_NUM_PREEMPT_PRIORITIES=5 @@ -10,11 +11,10 @@ CONFIG_SYS_CLOCK_EXISTS=y CONFIG_ARM_MPU=y CONFIG_I2C=n CONFIG_WATCHDOG=y -CONFIG_GPIO=y CONFIG_SPI=y +CONFIG_SPI_ASYNC=y CONFIG_SERIAL=y CONFIG_FLASH=n -CONFIG_PM=n # Enable log settings CONFIG_LOG=y @@ -53,6 +53,8 @@ CONFIG_CPLUSPLUS=y CONFIG_LIB_CPLUSPLUS=y CONFIG_NEWLIB_LIBC=y CONFIG_STD_CPP14=y + +CONFIG_NRFX_SPIM0=y # CONFIG_CPLUSPLUS=y # CONFIG_CPLUSPLUS_STANDARD_CXX11=y # CONFIG_CPLUSPLUS_LIBSTDCXX=y diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e0c65fa..1f7adea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,3 +4,4 @@ target_sources(app PRIVATE ${APP_SOURCES}) # Add the core directory add_subdirectory(core) +add_subdirectory(drivers) diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp index c5e8b99..6d9d920 100644 --- a/src/DataAcquisitionThread.cpp +++ b/src/DataAcquisitionThread.cpp @@ -33,16 +33,16 @@ void DataAcquisitionThread::Initialize() { void DataAcquisitionThread::Run() { uint8_t message = 0; while (true) { - // if (message_queue.get(message)) { - // switch (static_cast(message)) { - // case STOP_READING_AFE: - // case START_READING_AFE: - // case INVALID: - // default: - // break; - // } - // } - LOG_DBG("DataAcq::%s -- up time %u ms", __FUNCTION__, k_uptime_get_32()); - k_msleep(2000); + if (message_queue.get(message)) { + switch (static_cast(message)) { + case STOP_READING_AFE: + case START_READING_AFE: + case INVALID: + default: + break; + } + } + // LOG_DBG("DataAcq::%s -- up time %u ms", __FUNCTION__, k_uptime_get_32()); + // k_msleep(2000); } } diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt new file mode 100644 index 0000000..39d3f19 --- /dev/null +++ b/src/drivers/CMakeLists.txt @@ -0,0 +1,7 @@ +# Include all source files in the src/drivers directory +file(GLOB_RECURSE DRIVER_CPP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") +file(GLOB_RECURSE DRIVER_C_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.c") + +# Add the sources to the app target +target_sources(app PRIVATE ${DRIVER_CPP_SOURCES}) +target_sources(app PRIVATE ${DRIVER_C_SOURCES}) diff --git a/drivers/ads1299-x.c b/src/drivers/ads1299-x.cpp similarity index 54% rename from drivers/ads1299-x.c rename to src/drivers/ads1299-x.cpp index caa8ecd..3016cbd 100644 --- a/drivers/ads1299-x.c +++ b/src/drivers/ads1299-x.cpp @@ -20,16 +20,14 @@ */ #include "ads1299-x.h" -#include "app_error.h" -#include "nrf_drv_spi.h" -#include "nrf_gpio.h" -#include "app_util_platform.h" -#include "nrf_log.h" -#include "nrf_delay.h" -/**@headers for �s delay:*/ -#include -#include "compiler_abstraction.h" -#include "nrf.h" +#include +#include +// #include "compiler_abstraction.h" +// #include "nrf.h" + +LOG_MODULE_REGISTER(ads1299_driver, LOG_LEVEL_DBG); + +#define SPI_DEV DT_NODELABEL(spi_afe) /**@TX,RX Stuff: */ #define TX_RX_MSG_LENGTH 7 @@ -94,40 +92,22 @@ uint8_t ads1299_default_registers[] = { ADS1299_REGDEFAULT_CONFIG4 }; -/**@SPI HANDLERS: - * @brief SPI user event handler. - * @param event - */ -void spi_event_handler(nrf_drv_spi_evt_t const * p_event) -{ - /*switch (p_event->type) { - case NRF_DRV_SPI_EVENT_DONE: - break; - default: - break; - }*/ - //NRF_LOG_PRINTF(" >>> Transfer completed.\r\n"); -} +struct device *ADS1299Driver::spi_dev = nullptr; +struct spi_config spi_cfg = { + .frequency = ADS1299_SPI_FREQUENCY_HZ, + .operation = (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)), + .slave = 0, // Set the slave number +}; /**@INITIALIZE SPI INSTANCE */ -static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0); //SPI INSTANCE -void ads_spi_init(void) { - nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG(0); - spi_config.bit_order = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST; - //SCLK = 1MHz is right speed because fCLK = (1/2)*SCLK, and fMOD = fCLK/4, and fMOD MUST BE 128kHz. Do the math. - spi_config.frequency = NRF_DRV_SPI_FREQ_1M; - spi_config.irq_priority = APP_IRQ_PRIORITY_LOW; - spi_config.mode = NRF_DRV_SPI_MODE_1; //CPOL = 0 (Active High); CPHA = TRAILING (1) - spi_config.miso_pin = ADS1299_SPI_MISO_PIN; - spi_config.sck_pin = ADS1299_SPI_SCLK_PIN; - spi_config.mosi_pin = ADS1299_SPI_MOSI_PIN; - spi_config.ss_pin = ADS1299_SPI_CS_PIN; - spi_config.orc = 0x55; - APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler)); - NRF_LOG_PRINTF(" SPI Initialized..\r\n"); -} - -/**@SPI-CLEARS BUFFER +void ADS1299Driver::ads_spi_init(void) { + spi_dev = const_cast(DEVICE_DT_GET(SPI_DEV)); + if(!device_is_ready(spi_dev)) { + printk("SPI master device not ready!\n"); + } +}; + +/**@SPI-UTIL CLEARS BUFFER * @brief The function initializes TX buffer to values to be sent and clears RX buffer. * * @note Function clears RX and TX buffers. @@ -136,48 +116,70 @@ void ads_spi_init(void) { * @param[out] p_rx_data A pointer to a buffer RX. * @param[in] len A length of the data buffers. */ -void init_buf(uint8_t * const p_tx_buffer, +void ADS1299Driver::init_buf(uint8_t * const p_tx_buffer, uint8_t * const p_rx_buffer, const uint16_t len) { - uint16_t i; + memset(p_tx_buffer, 0, len * sizeof(p_tx_buffer[0])); + memset(p_rx_buffer, 0, len * sizeof(p_rx_buffer[0])); - for (i = 0; i < len; i++) - { - p_tx_buffer[i] = 0; - p_rx_buffer[i] = 0; - } - NRF_LOG_PRINTF(" SPI Buffer Cleared..\r\n"); -} + LOG_DBG(" SPI Buffer Cleared..\r\n"); +}; /************************************************************************************************************************************************** * Function Definitions * **************************************************************************************************************************************************/ - /* - * ADS1299 CONTROL FUNCTIONS: + * SPI Transceive Wrapper: */ -void ads1299_init_regs(void) { - uint8_t err_code; - uint8_t num_registers = 23; - uint8_t txrx_size = num_registers+2; - uint8_t tx_data_spi[txrx_size]; //Size = 14 bytes - uint8_t rx_data_spi[txrx_size]; //Size = 14 bytes - uint8_t wreg_init_opcode = 0x41; - for (int i = 0; i < txrx_size; ++i) { - tx_data_spi[i] = 0; - rx_data_spi[i] = 0; +void ADS1299Driver::ads1299_spi_transfer(uint8_t* tx_data, size_t tx_len, uint8_t* rx_data, size_t rx_len) { + // current behaviour: function will continue to transmit despite errors in tx/rx buf + struct spi_buf tx_buf = { .buf = NULL, .len = 0 }; + struct spi_buf rx_buf = { .buf = NULL, .len = 0 }; + + struct spi_buf_set tx_buf_set = { .buffers = NULL, .count = 0,}; + struct spi_buf_set rx_buf_set = { .buffers = NULL, .count = 0,}; + + if (tx_data != NULL && tx_len > 0) { + tx_buf.buf = tx_data; + tx_buf.len = tx_len; + tx_buf_set.buffers = &tx_buf; } - tx_data_spi[0] = wreg_init_opcode; - tx_data_spi[1] = num_registers - 1; - for (int j = 0; j < num_registers; ++j) { - tx_data_spi[j+2] = ads1299_default_registers[j]; + if (rx_data != NULL && rx_len > 0) { + rx_buf.buf = rx_data; + rx_buf.len = rx_len; + rx_buf_set.buffers = &rx_buf; } - err_code = nrf_drv_spi_transfer(&spi, tx_data_spi, num_registers+2, rx_data_spi, num_registers+2); - nrf_delay_ms(150); - NRF_LOG_PRINTF(" Power-on reset and initialization procedure.. EC: %d \r\n",err_code); -} -void ads1299_powerup_reset(void) + int ret = spi_transceive(spi_dev, &spi_cfg, &tx_buf_set, &rx_buf_set); + if (ret) { + LOG_ERR("SPI transfer failed with error %d", ret); + return; + } +}; + +/* + * ADS1299 CONTROL FUNCTIONS: + */ +void ADS1299Driver::ads1299_init_regs(void) { + uint8_t num_registers = 23; + uint8_t txrx_size = num_registers + 2; + uint8_t wreg_init_opcode = 0x41; + uint8_t tx_data_spi[txrx_size] = { 0 }; + uint8_t rx_data_spi[txrx_size] = { 0 }; + + tx_data_spi[0] = wreg_init_opcode; + tx_data_spi[1] = num_registers - 1; + + for (int j = 0; j < num_registers; ++j) { + tx_data_spi[j + 2] = ads1299_default_registers[j]; + } + + ads1299_spi_transfer(tx_data_spi, txrx_size, rx_data_spi, txrx_size); + k_msleep(150); + LOG_INF("Power-on reset and initialization procedure.."); +}; + +void ADS1299Driver::ads1299_powerup_reset(void) { #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) nrf_gpio_pin_clear(ADS1299_PWDN_RST_PIN); @@ -186,11 +188,11 @@ void ads1299_powerup_reset(void) nrf_gpio_pin_clear(ADS1299_RESET_PIN); nrf_gpio_pin_clear(ADS1299_PWDN_PIN); #endif - nrf_delay_ms(50); - NRF_LOG_PRINTF(" ADS1299-x POWERED UP AND RESET..\r\n"); -} + k_msleep(50); + LOG_DBG(" ADS1299-x POWERED UP AND RESET..\r\n"); +}; -void ads1299_powerdn(void) +void ADS1299Driver::ads1299_powerdn(void) { #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) nrf_gpio_pin_clear(ADS1299_PWDN_RST_PIN); @@ -199,11 +201,11 @@ void ads1299_powerdn(void) nrf_gpio_pin_clear(ADS1299_RESET_PIN); nrf_gpio_pin_clear(ADS1299_PWDN_PIN); #endif - nrf_delay_us(20); - NRF_LOG_PRINTF(" ADS1299-x POWERED DOWN..\r\n"); -} + k_msleep(20); + LOG_DBG(" ADS1299-x POWERED DOWN..\r\n"); +}; -void ads1299_powerup(void) +void ADS1299Driver::ads1299_powerup(void) { #if defined(BOARD_PCA10028) | defined(BOARD_NRF_BREAKOUT) nrf_gpio_pin_set(ADS1299_PWDN_RST_PIN); @@ -212,95 +214,88 @@ void ads1299_powerup(void) nrf_gpio_pin_set(ADS1299_RESET_PIN); nrf_gpio_pin_set(ADS1299_PWDN_PIN); #endif - nrf_delay_ms(1000); // Allow time for power-on reset - NRF_LOG_PRINTF(" ADS1299-x POWERED UP...\r\n"); -} - -void ads1299_standby(void) { - uint8_t tx_data_spi; - uint8_t rx_data_spi; + k_msleep(1000); // Allow time for power-on reset + LOG_DBG(" ADS1299-x POWERED UP...\r\n"); +}; - tx_data_spi = ADS1299_OPC_STANDBY; +void ADS1299Driver::ads1299_standby(void) { + uint8_t tx_data_spi = ADS1299_OPC_STANDBY; + uint8_t rx_data_spi; - nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); - NRF_LOG_PRINTF(" ADS1299-x placed in standby mode...\r\n"); -} + ads1299_spi_transfer(&tx_data_spi, 1, &rx_data_spi, 1); + LOG_DBG("ADS1299-x placed in standby mode..."); +}; -void ads1299_wake(void) { - uint8_t tx_data_spi; +void ADS1299Driver::ads1299_wake(void) { + uint8_t tx_data_spi = ADS1299_OPC_WAKEUP; uint8_t rx_data_spi; - tx_data_spi = ADS1299_OPC_WAKEUP; - - nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); - nrf_delay_ms(10); // Allow time to wake up - 10ms - NRF_LOG_PRINTF(" ADS1299-x Wakeup..\r\n"); -} + ads1299_spi_transfer(&tx_data_spi, 1, &rx_data_spi, 1); + k_msleep(10); // Allow time to wake up - 10ms + LOG_DBG(" ADS1299-x Wakeup.."); +}; -void ads1299_soft_start_conversion(void) { - uint8_t tx_data_spi; +void ADS1299Driver::ads1299_soft_start_conversion(void) { + uint8_t tx_data_spi = ADS1299_OPC_START; uint8_t rx_data_spi; - tx_data_spi = ADS1299_OPC_START; - - nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); - NRF_LOG_PRINTF(" Start ADC conversion..\r\n"); -} + ads1299_spi_transfer(&tx_data_spi, 1, &rx_data_spi, 1); + LOG_DBG(" Start ADC conversion.."); +}; -void ads1299_stop_rdatac(void) { - uint8_t tx_data_spi; +void ADS1299Driver::ads1299_stop_rdatac(void) { + uint8_t tx_data_spi = ADS1299_OPC_SDATAC; uint8_t rx_data_spi; - tx_data_spi = ADS1299_OPC_SDATAC; - - nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); - NRF_LOG_PRINTF(" Continuous Data Output Disabled..\r\n"); -} + ads1299_spi_transfer(&tx_data_spi, 1, &rx_data_spi, 1); + LOG_DBG(" Continuous Data Output Disabled.."); +}; -void ads1299_start_rdatac(void) { - uint8_t tx_data_spi; +void ADS1299Driver::ads1299_start_rdatac(void) { + uint8_t tx_data_spi = ADS1299_OPC_RDATAC; uint8_t rx_data_spi; - tx_data_spi = ADS1299_OPC_RDATAC; - - nrf_drv_spi_transfer(&spi, &tx_data_spi, 1, &rx_data_spi, 1); - NRF_LOG_PRINTF(" Continuous Data Output Enabled..\r\n"); -} + ads1299_spi_transfer(&tx_data_spi, 1, &rx_data_spi, 1); + LOG_DBG(" Continuous Data Output Enabled.."); +}; -void ads1299_check_id(void) { +void ADS1299Driver::ads1299_check_id(void) { uint8_t device_id_reg_value; uint8_t tx_data_spi[3]; uint8_t rx_data_spi[7]; tx_data_spi[0] = 0x20; //Request Device ID tx_data_spi[1] = 0x01; //Intend to read 1 byte tx_data_spi[2] = 0x00; //This will be replaced by Reg Data - nrf_drv_spi_transfer(&spi, tx_data_spi, 2+tx_data_spi[1], rx_data_spi, 2+tx_data_spi[1]); - nrf_delay_ms(20); //Wait for response: + + ads1299_spi_transfer(tx_data_spi, 2+tx_data_spi[1], rx_data_spi, 2+tx_data_spi[1]); + + k_msleep(20); //Wait for response: device_id_reg_value = rx_data_spi[2]; - bool is_ads_1299_4 = (device_id_reg_value & 0x1F) == (ADS1299_4_DEVICE_ID); + bool is_ads_1299_4 = (device_id_reg_value & 0x1F) == (ADS1299_4_DEVICE_ID); bool is_ads_1299_6 = (device_id_reg_value & 0x1F) == (ADS1299_6_DEVICE_ID); - bool is_ads_1299 = (device_id_reg_value & 0x1F) == (ADS1299_DEVICE_ID); + bool is_ads_1299 = (device_id_reg_value & 0x1F) == (ADS1299_DEVICE_ID); + uint8_t revisionVersion = (device_id_reg_value & 0xE0)>>5; if (is_ads_1299||is_ads_1299_6||is_ads_1299_4) { - NRF_LOG_PRINTF("Device Address Matches!\r\n"); + LOG_DBG("Device Address Matches!"); } else { - NRF_LOG_PRINTF("********SPI I/O Error, Device Not Detected! *********** \r\n"); - NRF_LOG_PRINTF("SPI Transfer Dump: \r\n"); - NRF_LOG_PRINTF("ID[b0->2]: [0x%x | 0x%x | 0x%x] \r\n", rx_data_spi[0],rx_data_spi[1],rx_data_spi[2]); - NRF_LOG_PRINTF("ID[b3->6]: [0x%x | 0x%x | 0x%x | 0x%x] \r\n", rx_data_spi[3],rx_data_spi[4],rx_data_spi[5],rx_data_spi[6]); + LOG_DBG("********SPI I/O Error, Device Not Detected! ***********"); + LOG_DBG("SPI Transfer Dump:"); + LOG_DBG("ID[b0->2]: [0x%x | 0x%x | 0x%x]", rx_data_spi[0],rx_data_spi[1],rx_data_spi[2]); + LOG_DBG("ID[b3->6]: [0x%x | 0x%x | 0x%x | 0x%x]", rx_data_spi[3],rx_data_spi[4],rx_data_spi[5],rx_data_spi[6]); } if (is_ads_1299) { - NRF_LOG_PRINTF("Device Name: ADS1299 \r\n"); + LOG_DBG("Device Name: ADS1299"); } else if (is_ads_1299_6) { - NRF_LOG_PRINTF("Device Name: ADS1299-6 \r\n"); + LOG_DBG("Device Name: ADS1299-6"); } else if (is_ads_1299_4) { - NRF_LOG_PRINTF("Device Name: ADS1299-4 \r\n"); + LOG_DBG("Device Name: ADS1299-4"); } if (is_ads_1299||is_ads_1299_6||is_ads_1299_4) { - NRF_LOG_PRINTF("Device Revision #%d\r\n",revisionVersion); - NRF_LOG_PRINTF("Device ID: 0x%x \r\n",device_id_reg_value); + LOG_DBG("Device Revision #%d",revisionVersion); + LOG_DBG("Device ID: 0x%x",device_id_reg_value); } -} +}; /* DATA RETRIEVAL FUNCTIONS **********************************************************************************************************************/ @@ -309,13 +304,17 @@ void ads1299_check_id(void) { * @details Uses SPI * */ -void get_eeg_voltage_samples (int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4) { - uint8_t tx_rx_data[15] = {0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00}; - nrf_drv_spi_transfer(&spi, tx_rx_data, 15, tx_rx_data, 15); +void ADS1299Driver::get_eeg_voltage_samples(int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4) { + uint8_t tx_rx_data[15] = { + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + }; + + ads1299_spi_transfer(tx_rx_data, 15, tx_rx_data, 15); + uint8_t cnt = 0; do { if(tx_rx_data[0]==0xC0){ @@ -326,11 +325,11 @@ void get_eeg_voltage_samples (int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32 break; } cnt++; - nrf_delay_us(1); + k_msleep(1); } while(cnt<255); - //NRF_LOG_PRINTF("B0-2 = [0x%x 0x%x 0x%x | cnt=%d]\r\n",tx_rx_data[0],tx_rx_data[1],tx_rx_data[2],cnt); - //NRF_LOG_PRINTF("DATA:[0x%x 0x%x 0x%x 0x%x]\r\n",*eeg1,*eeg2,*eeg3,*eeg4); -} + //LOG_DBG("B0-2 = [0x%x 0x%x 0x%x | cnt=%d]\r\n",tx_rx_data[0],tx_rx_data[1],tx_rx_data[2],cnt); + //LOG_DBG("DATA:[0x%x 0x%x 0x%x 0x%x]\r\n",*eeg1,*eeg2,*eeg3,*eeg4); +}; // // // // // // // End of File // diff --git a/drivers/ads1299-x.h b/src/drivers/ads1299-x.h similarity index 69% rename from drivers/ads1299-x.h rename to src/drivers/ads1299-x.h index 0f0336f..3ac3bb6 100644 --- a/drivers/ads1299-x.h +++ b/src/drivers/ads1299-x.h @@ -28,19 +28,29 @@ #define ADS1299_H__ #include -#include "nrf_drv_spi.h" +#include +#include +#include + +// #ifdef __cplusplus +// extern "C" { +// #endif -#ifdef __cplusplus - extern "C" { -#endif /**@SPI STUFF*/ -#ifdef BOARD_PCA10028 +#define BOARD_NRF53_DEV +#ifdef BOARD_NRF53_DEV + #define ADS1299_SPI_SCLK_PIN DT_GPIO_PIN(DT_ALIAS(spi0_sck), gpios) + #define ADS1299_SPI_CS_PIN DT_GPIO_PIN(DT_ALIAS(spi0_cs), gpios) + #define ADS1299_SPI_MOSI_PIN DT_GPIO_PIN(DT_ALIAS(spi0_mosi), gpios) + #define ADS1299_SPI_MISO_PIN DT_GPIO_PIN(DT_ALIAS(spi0_miso), gpios) + #define ADS1299_SPI_FREQUENCY_HZ 1000000 +#elif defined(BOARD_PCA10028) #define ADS1299_SPI_SCLK_PIN 10 #define ADS1299_SPI_CS_PIN 11 #define ADS1299_SPI_MOSI_PIN 14 //MASTER (nRF) OUT; SLAVE (ADS) DIN #define ADS1299_SPI_MISO_PIN 9 //MASTER (nRF) IN ; SLAVE (ADS) DOUT #define ADS1299_PWDN_RST_PIN 12 - #define ADS1299_DRDY_PIN 8 + #define ADS1299_DRDY_PIN 8 #elif defined(BOARD_NRF_BREAKOUT) //BOARD_CUSTOM BOARD_NRF_BREAKOUT #define ADS1299_SPI_SCLK_PIN 9 @@ -67,12 +77,12 @@ * * Consult the ADS1291/2 datasheet and user's guide for more information. */ -#define ADS1299_REGADDR_ID 0x00 ///< Chip ID register. Read-only. +#define ADS1299_REGADDR_ID 0x00 ///< Chip ID register. Read-only. #define ADS1299_REGADDR_CONFIG1 0x01 ///< Configuration register 1. Controls conversion mode and data rate. #define ADS1299_REGADDR_CONFIG2 0x02 ///< Configuration register 2. Controls LOFF comparator, reference, CLK pin, and test signal. #define ADS1299_REGADDR_CONFIG3 0x03 -#define ADS1299_REGADDR_LOFF 0x04 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. -#define ADS1299_REGADDR_CH1SET 0x05 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. +#define ADS1299_REGADDR_LOFF 0x04 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. +#define ADS1299_REGADDR_CH1SET 0x05 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. #define ADS1299_REGADDR_CH2SET 0x06 ///< Channel 2 settings register (ADS1292x only). Controls channel 2 input mux, gain, and power-down. #define ADS1299_REGADDR_CH3SET 0x07 #define ADS1299_REGADDR_CH4SET 0x08 @@ -80,16 +90,16 @@ #define ADS1299_REGADDR_CH6SET 0x0A #define ADS1299_REGADDR_CH7SET 0x0B #define ADS1299_REGADDR_CH8SET 0x0C -#define ADS1299_REGADDR_BIAS_SENSP 0x0D ///< RLD sense selection. Controls PGA chop frequency, RLD buffer, and channels for RLD derivation. -#define ADS1299_REGADDR_BIAS_SENSN 0x0E -#define ADS1299_REGADDR_LOFF_SENSP 0x0F ///< Lead-off sense selection. Controls current direction and selects channels that will use lead-off detection. -#define ADS1299_REGADDR_LOFF_SENSN 0x10 -#define ADS1299_REGADDR_LOFF_FLIP 0x11 -#define ADS1299_REGADDR_LOFF_STATP 0x12 ///< Lead-off status register. Bit 6 controls clock divider. For bits 4:0, 0: lead on, 1: lead off. -#define ADS1299_REGADDR_LOFF_STATN 0x13 -#define ADS1299_REGADDR_GPIO 0x14 ///< GPIO register. Controls state and direction of the ADS1291_2 GPIO pins. -#define ADS1299_REGADDR_MISC1 0x15 ///< Respiration 1 (ADS1292R only). See datasheet. -#define ADS1299_REGADDR_MISC2 0x16 ///< Respiration 2. Controls offset calibration, respiration modulator freq, and RLDREF signal source. +#define ADS1299_REGADDR_BIAS_SENSP 0x0D ///< RLD sense selection. Controls PGA chop frequency, RLD buffer, and channels for RLD derivation. +#define ADS1299_REGADDR_BIAS_SENSN 0x0E +#define ADS1299_REGADDR_LOFF_SENSP 0x0F ///< Lead-off sense selection. Controls current direction and selects channels that will use lead-off detection. +#define ADS1299_REGADDR_LOFF_SENSN 0x10 +#define ADS1299_REGADDR_LOFF_FLIP 0x11 +#define ADS1299_REGADDR_LOFF_STATP 0x12 ///< Lead-off status register. Bit 6 controls clock divider. For bits 4:0, 0: lead on, 1: lead off. +#define ADS1299_REGADDR_LOFF_STATN 0x13 +#define ADS1299_REGADDR_GPIO 0x14 ///< GPIO register. Controls state and direction of the ADS1291_2 GPIO pins. +#define ADS1299_REGADDR_MISC1 0x15 ///< Respiration 1 (ADS1292R only). See datasheet. +#define ADS1299_REGADDR_MISC2 0x16 ///< Respiration 2. Controls offset calibration, respiration modulator freq, and RLDREF signal source. #define ADS1299_REGADDR_CONFIG4 0x17 /** @@ -114,7 +124,7 @@ #define ADS1299_OPC_WREG 0x40 ///< Write register value. - /**********************************/ +/**********************************/ /* ID REGISTER ********************************************************************/ @@ -124,7 +134,7 @@ #define ADS1299_4_DEVICE_ID 0x1C //Device ID [0bvvv11100] Where vvv is the version bits #define ADS1299_6_DEVICE_ID 0x1D //Device ID [0bvvv11101] -#define ADS1299_DEVICE_ID 0x1E //Device ID [0bvvv11101] +#define ADS1299_DEVICE_ID 0x1E //Device ID [0bvvv11101] /* DEFAULT REGISTER VALUES ********************************************************/ @@ -146,7 +156,7 @@ #define ADS1299_REGDEFAULT_LOFF_SENSP 0x00 ///< #define ADS1299_REGDEFAULT_LOFF_SENSN 0x00 #define ADS1299_REGDEFAULT_LOFF_FLIP 0x00 -#define ADS1299_REGDEFAULT_LOFF_STATP 0x00 ///< +#define ADS1299_REGDEFAULT_LOFF_STATP 0x00 ///< #define ADS1299_REGDEFAULT_LOFF_STATN 0x00 #define ADS1299_REGDEFAULT_GPIO 0x0F ///< #define ADS1299_REGDEFAULT_MISC1 0x20 ///< @@ -164,46 +174,53 @@ // 0x06 = 8000SPS // -/**@TYPEDEFS: */ +/* typedefs */ typedef int16_t body_voltage_t; +class ADS1299Driver { +public: + /* structs */ + static device *spi_dev; + static spi_config spi_cfg; /************************************************************************************************************************************************** * Function Prototypes ADS1299-x * **************************************************************************************************************************************************/ -void ads_spi_init(void); + static void ads_spi_init(void); -void init_buf(uint8_t * const p_tx_buffer, - uint8_t * const p_rx_buffer, - const uint16_t len); + static void init_buf(uint8_t * const p_tx_buffer, + uint8_t * const p_rx_buffer, + const uint16_t len); -/** - * \brief Initialize the ADS1299-x. - * - * This function performs the power-on reset and initialization procedure documented on page 61 of the - * ADS1299 datasheet, up to "Send SDATAC Command." - */ -void ads1299_powerup_reset(void); + /** + * \brief Initialize the ADS1299-x. + * + * This function performs the power-on reset and initialization procedure documented on page 61 of the + * ADS1299 datasheet, up to "Send SDATAC Command." + */ + static void ads1299_spi_transfer(uint8_t* tx_data, size_t tx_len, uint8_t* rx_data, size_t rx_len); -void ads1299_init_regs(void); + static void ads1299_powerup_reset(void); -void ads1299_powerdn(void); + static void ads1299_init_regs(void); -void ads1299_powerup(void); + static void ads1299_powerdn(void); -void ads1299_standby(void); + static void ads1299_powerup(void); -void ads1299_wake(void); + static void ads1299_standby(void); -void ads1299_soft_start_conversion(void); + static void ads1299_wake(void); -void ads1299_stop_rdatac(void); + static void ads1299_soft_start_conversion(void); -void ads1299_start_rdatac(void); + static void ads1299_stop_rdatac(void); -void ads1299_check_id(void); + static void ads1299_start_rdatac(void); -void get_eeg_voltage_samples (int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4); + static void ads1299_check_id(void); + static void get_eeg_voltage_samples(int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4); +}; #endif // ADS1299_H__ diff --git a/src/main.cpp b/src/main.cpp index 4223e90..2294c57 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ #include "arm_math.h" #include "DataAcquisitionThread.h" +#include "drivers/ads1299-x.h" LOG_MODULE_REGISTER(eegals_app_core, LOG_LEVEL_DBG); @@ -27,10 +28,12 @@ int main(void) DataAcquisitionThread::GetInstance().Initialize(); + ADS1299Driver::ads_spi_init(); + while(1) { LOG_DBG("main thread up time: %u ms", k_uptime_get_32()); k_msleep(LOG_DELAY_MS); } return 0; -} +}; From 7861aa27833a82dfe94f68689c3641a602d24559 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Sun, 28 Jan 2024 21:19:02 -0700 Subject: [PATCH 06/11] refactors ads driver to be controlled by daq thread --- src/AnalogFrontEndWrapper.h | 9 ++- src/CustomFrontEndWrapper.cpp | 36 ++++++----- src/CustomFrontEndWrapper.h | 21 +++---- src/DataAcquisitionThread.cpp | 25 ++++---- src/DataAcquisitionThread.h | 2 + src/TIFrontEnd.cpp | 13 ---- src/TIFrontEndWrapper.cpp | 48 +++++++++++++++ src/TIFrontEndWrapper.h | 24 +++++++- src/core/MessageQueue.h | 13 +++- src/drivers/ads1299-x.cpp | 40 +------------ src/drivers/ads1299-x.h | 109 +++++++++++++++------------------- src/main.cpp | 5 +- 12 files changed, 181 insertions(+), 164 deletions(-) delete mode 100644 src/TIFrontEnd.cpp create mode 100644 src/TIFrontEndWrapper.cpp diff --git a/src/AnalogFrontEndWrapper.h b/src/AnalogFrontEndWrapper.h index 0476350..40d8d80 100644 --- a/src/AnalogFrontEndWrapper.h +++ b/src/AnalogFrontEndWrapper.h @@ -7,6 +7,11 @@ class AnalogFrontEndWrapper { public: virtual ~AnalogFrontEndWrapper() = default; virtual void Initialize() = 0; - virtual void Configure() = 0; - virtual eeg_sample ReadData() = 0; + // virtual void Configure() = 0; + virtual void Start()=0; + virtual void Wakeup()=0; + virtual void Standby()=0; + virtual void Reset()=0; + virtual void Stop()=0; + virtual void ReadData()=0; }; \ No newline at end of file diff --git a/src/CustomFrontEndWrapper.cpp b/src/CustomFrontEndWrapper.cpp index a53c872..bb0e977 100644 --- a/src/CustomFrontEndWrapper.cpp +++ b/src/CustomFrontEndWrapper.cpp @@ -1,29 +1,33 @@ #include "CustomFrontEndWrapper.h" -// const char *CustomFrontEndWrapper::spi_device_name = "ADS1299"; -// const struct device *CustomFrontEndWrapper::spi_dev; - -CustomFrontEndWrapper::CustomFrontEndWrapper() { - // spi_dev = device_get_binding(SPI_DEV_NAME); - // if (!spi_dev) { - // // TODO: print on debug uart - // return; - // } +void CustomFrontEndWrapper::Initialize() { + // TODO } -void CustomFrontEndWrapper::Initialize() { +void CustomFrontEndWrapper::Configure() { + // TODO +} + +void CustomFrontEndWrapper::Start() { + // TODO +} + +void CustomFrontEndWrapper::Wakeup() { + // TODO +} - // if (spi_configure(spi_dev, &spi_cfg) != 0) { - // // TODO: print on debug UART - // } +void CustomFrontEndWrapper::Standby() { + // TODO +} - // DataBufferManager::spi_dma_setup(spi_dev); +void CustomFrontEndWrapper::Reset() { + // TODO } -void CustomFrontEndWrapper::Configure() { +void CustomFrontEndWrapper::Stop() { // TODO } -eeg_sample CustomFrontEndWrapper::ReadData() { +void CustomFrontEndWrapper::ReadData() { // TODO } \ No newline at end of file diff --git a/src/CustomFrontEndWrapper.h b/src/CustomFrontEndWrapper.h index dc9115b..ea1475e 100644 --- a/src/CustomFrontEndWrapper.h +++ b/src/CustomFrontEndWrapper.h @@ -1,22 +1,15 @@ #pragma once -#include -#include - #include "AnalogFrontEndWrapper.h" -#include "DataBufferManager.h" - -// #define SPI_DEV_NAME DT_LABEL(DT_NODELABEL(spi0)) class CustomFrontEndWrapper : public AnalogFrontEndWrapper { -private: - // static const char *spi_device_name; - // static const struct device *spi_dev; - public: - CustomFrontEndWrapper(); - ~CustomFrontEndWrapper() = default; void Initialize() override; - void Configure() override; - eeg_sample ReadData() override; + void Configure(); + void Start() override; + void Wakeup() override; + void Standby() override; + void Reset() override; + void Stop() override; + void ReadData() override; }; \ No newline at end of file diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp index 6d9d920..19e2b31 100644 --- a/src/DataAcquisitionThread.cpp +++ b/src/DataAcquisitionThread.cpp @@ -5,15 +5,17 @@ K_THREAD_STACK_DEFINE(data_acq_stack_area, DATA_ACQ_THREAD_STACK_SIZE_B); struct k_thread data_acq_thread_data; DataAcquisitionThread::DataAcquisitionThread() { - // todo - LOG_DBG("DataAcq::%s -- constructor called", __FUNCTION__); + AFEWrapper = TIFrontEndWrapper(); + // set up data manager to listen to AFE + // DataBufferManager::spi_dma_setup(spi_dev); } void DataAcquisitionThread::Initialize() { - LOG_DBG("DataAcq::%s -- making thread", __FUNCTION__); + LOG_DBG("DataAcq::%s -- initializing AFE Wrapper", __FUNCTION__); + AFEWrapper.Initialize(); if (id == nullptr) { - LOG_DBG("DataAcq::%s -- id == nullptr passed", __FUNCTION__); + LOG_DBG("DataAcq::%s -- making thread", __FUNCTION__); id = k_thread_create( &data_acq_thread_data, data_acq_stack_area, K_THREAD_STACK_SIZEOF(data_acq_stack_area), @@ -22,27 +24,28 @@ void DataAcquisitionThread::Initialize() { DATA_ACQ_THREAD_PRIORITY, 0, K_NO_WAIT ); - LOG_DBG("DataAcq::%s -- thread create successful", __FUNCTION__); - k_thread_name_set(id, "DataAcquisitionThread"); - - LOG_DBG("DataAcq::%s -- set name successful", __FUNCTION__); + LOG_DBG("DataAcq::%s -- thread create successful", __FUNCTION__); } } void DataAcquisitionThread::Run() { + // set AFE in continuous read mode uint8_t message = 0; while (true) { - if (message_queue.get(message)) { + if (message_queue.get_with_blocking_wait(message)) { + LOG_DBG("DataAcq::%s -- received message at %u ms", __FUNCTION__, k_uptime_get_32()); switch (static_cast(message)) { case STOP_READING_AFE: + AFEWrapper.Stop(); + break; case START_READING_AFE: + AFEWrapper.Start(); + break; case INVALID: default: break; } } - // LOG_DBG("DataAcq::%s -- up time %u ms", __FUNCTION__, k_uptime_get_32()); - // k_msleep(2000); } } diff --git a/src/DataAcquisitionThread.h b/src/DataAcquisitionThread.h index 9187869..f94ab91 100644 --- a/src/DataAcquisitionThread.h +++ b/src/DataAcquisitionThread.h @@ -3,6 +3,7 @@ #include #include #include "core/Thread.h" +#include "TIFrontEndWrapper.h" #define DATA_ACQ_THREAD_STACK_SIZE_B 4096 #define DATA_ACQ_THREAD_PRIORITY 4 // max based on prj config @@ -10,6 +11,7 @@ class DataAcquisitionThread : public Thread { private: + TIFrontEndWrapper AFEWrapper; DataAcquisitionThread(); ~DataAcquisitionThread() = default; diff --git a/src/TIFrontEnd.cpp b/src/TIFrontEnd.cpp deleted file mode 100644 index b609d51..0000000 --- a/src/TIFrontEnd.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "TIFrontEndWrapper.h" - -void TIFrontEndWrapper::Initialize() { - // TODO -} - -void TIFrontEndWrapper::Configure() { - // TODO -} - -eeg_sample TIFrontEndWrapper::ReadData() { - // TODO -} \ No newline at end of file diff --git a/src/TIFrontEndWrapper.cpp b/src/TIFrontEndWrapper.cpp new file mode 100644 index 0000000..28699e1 --- /dev/null +++ b/src/TIFrontEndWrapper.cpp @@ -0,0 +1,48 @@ +#include "TIFrontEndWrapper.h" +// log level declaration +LOG_MODULE_REGISTER(TI_front_end_wrapper, LOG_LEVEL_DBG); + +TIFrontEndWrapper::TIFrontEndWrapper() : + afe_spi_device(const_cast(DEVICE_DT_GET(AFE_SPI))), + afe_spi_config{ADS1299_SPI_FREQUENCY_HZ, (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)), 0}, + afe_driver(afe_spi_device, &afe_spi_config) +{ + if (!device_is_ready(afe_spi_device)) { + LOG_ERR("TIFE::%s -- SPI device not ready!", __FUNCTION__); + // todo: error handling + } +} + +void TIFrontEndWrapper::Initialize() { + // set up AFE + afe_driver.ads1299_powerup_reset(); + afe_driver.ads1299_check_id(); + afe_driver.ads1299_init_regs(); +} + +void TIFrontEndWrapper::Start() { + afe_driver.ads1299_start_rdatac(); +} + +void TIFrontEndWrapper::Wakeup() { + afe_driver.ads1299_wake(); +} + +void TIFrontEndWrapper::Standby() { + afe_driver.ads1299_standby(); +} + +void TIFrontEndWrapper::Reset() { + afe_driver.ads1299_powerup_reset(); +} + +void TIFrontEndWrapper::Stop() { + afe_driver.ads1299_stop_rdatac(); +} + +void TIFrontEndWrapper::ReadData() { + int32_t channel_1, channel_2, channel_3, channel_4; + + afe_driver.get_eeg_voltage_samples(&channel_1, &channel_2, &channel_3, &channel_4); + LOG_DBG("TIFE::%s -- Read samples: %d, %d, %d, %d", __FUNCTION__, channel_1, channel_2, channel_3, channel_4); +} \ No newline at end of file diff --git a/src/TIFrontEndWrapper.h b/src/TIFrontEndWrapper.h index 3daef2a..9db7eea 100644 --- a/src/TIFrontEndWrapper.h +++ b/src/TIFrontEndWrapper.h @@ -1,9 +1,29 @@ #pragma once +#include +#include +#include + +#include "drivers/ads1299-x.h" #include "AnalogFrontEndWrapper.h" +#include "DataBufferManager.h" + +#define AFE_SPI DT_NODELABEL(spi_afe) + class TIFrontEndWrapper : public AnalogFrontEndWrapper { +private: + device* afe_spi_device; + spi_config afe_spi_config; + ADS1299Driver afe_driver; + public: + TIFrontEndWrapper(); + ~TIFrontEndWrapper() = default; void Initialize() override; - void Configure() override; - eeg_sample ReadData() override; + void Start() override; + void Wakeup() override; + void Standby() override; + void Reset() override; + void Stop() override; + void ReadData() override; }; diff --git a/src/core/MessageQueue.h b/src/core/MessageQueue.h index 5886331..8c180f4 100644 --- a/src/core/MessageQueue.h +++ b/src/core/MessageQueue.h @@ -16,7 +16,7 @@ template class MessageQueue { }; ~MessageQueue() = default; - bool push(const element_type& msg) { + bool push(element_type& msg) { if (k_msgq_put(&queue, static_cast(&msg), K_NO_WAIT) != 0) { // TODO: uart debug message send failed return false; @@ -24,7 +24,7 @@ template class MessageQueue { return true; }; - bool push_with_timeout(const element_type& msg, int64_t ticks) { + bool push_with_timeout(element_type& msg, int64_t ticks) { if (k_msgq_put(&queue, static_cast(&msg), k_timeout_t(ticks)) != 0) { return false; } @@ -37,13 +37,20 @@ template class MessageQueue { return false; } return true; - } + }; bool get_with_timeout(element_type& msg, int64_t ticks) { if (k_msgq_get(&queue, static_cast(&msg), k_timeout_t(ticks)) != 0) { return false; } return true; + }; + + bool get_with_blocking_wait(element_type& msg) { + if (k_msgq_get(&queue, static_cast(&msg), K_FOREVER) != 0) { + return false; + } + return true; } bool peak(element_type& msg) { diff --git a/src/drivers/ads1299-x.cpp b/src/drivers/ads1299-x.cpp index 3016cbd..54dc6ba 100644 --- a/src/drivers/ads1299-x.cpp +++ b/src/drivers/ads1299-x.cpp @@ -27,8 +27,6 @@ LOG_MODULE_REGISTER(ads1299_driver, LOG_LEVEL_DBG); -#define SPI_DEV DT_NODELABEL(spi_afe) - /**@TX,RX Stuff: */ #define TX_RX_MSG_LENGTH 7 @@ -92,42 +90,6 @@ uint8_t ads1299_default_registers[] = { ADS1299_REGDEFAULT_CONFIG4 }; -struct device *ADS1299Driver::spi_dev = nullptr; -struct spi_config spi_cfg = { - .frequency = ADS1299_SPI_FREQUENCY_HZ, - .operation = (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8)), - .slave = 0, // Set the slave number -}; - -/**@INITIALIZE SPI INSTANCE */ -void ADS1299Driver::ads_spi_init(void) { - spi_dev = const_cast(DEVICE_DT_GET(SPI_DEV)); - if(!device_is_ready(spi_dev)) { - printk("SPI master device not ready!\n"); - } -}; - -/**@SPI-UTIL CLEARS BUFFER - * @brief The function initializes TX buffer to values to be sent and clears RX buffer. - * - * @note Function clears RX and TX buffers. - * - * @param[out] p_tx_data A pointer to a buffer TX. - * @param[out] p_rx_data A pointer to a buffer RX. - * @param[in] len A length of the data buffers. - */ -void ADS1299Driver::init_buf(uint8_t * const p_tx_buffer, - uint8_t * const p_rx_buffer, - const uint16_t len) -{ - memset(p_tx_buffer, 0, len * sizeof(p_tx_buffer[0])); - memset(p_rx_buffer, 0, len * sizeof(p_rx_buffer[0])); - - LOG_DBG(" SPI Buffer Cleared..\r\n"); -}; -/************************************************************************************************************************************************** - * Function Definitions * - **************************************************************************************************************************************************/ /* * SPI Transceive Wrapper: */ @@ -150,7 +112,7 @@ void ADS1299Driver::ads1299_spi_transfer(uint8_t* tx_data, size_t tx_len, uint8_ rx_buf_set.buffers = &rx_buf; } - int ret = spi_transceive(spi_dev, &spi_cfg, &tx_buf_set, &rx_buf_set); + int ret = spi_transceive(spi_dev, spi_cfg, &tx_buf_set, &rx_buf_set); if (ret) { LOG_ERR("SPI transfer failed with error %d", ret); return; diff --git a/src/drivers/ads1299-x.h b/src/drivers/ads1299-x.h index 3ac3bb6..135c08a 100644 --- a/src/drivers/ads1299-x.h +++ b/src/drivers/ads1299-x.h @@ -32,10 +32,6 @@ #include #include -// #ifdef __cplusplus -// extern "C" { -// #endif - /**@SPI STUFF*/ #define BOARD_NRF53_DEV #ifdef BOARD_NRF53_DEV @@ -112,19 +108,16 @@ #define ADS1299_OPC_WAKEUP 0x02 ///< Wake up from standby. #define ADS1299_OPC_STANDBY 0x04 ///< Enter standby. -#define ADS1299_OPC_RESET 0x06 ///< Reset all registers. -#define ADS1299_OPC_START 0x08 ///< Start data conversions. -#define ADS1299_OPC_STOP 0x0A ///< Stop data conversions. +#define ADS1299_OPC_RESET 0x06 ///< Reset all registers. +#define ADS1299_OPC_START 0x08 ///< Start data conversions. +#define ADS1299_OPC_STOP 0x0A ///< Stop data conversions. #define ADS1299_OPC_OFFSETCAL 0x1A ///< Calibrate channel offset. RESP2.CALIB_ON must be 1. Execute after every PGA gain change. #define ADS1299_OPC_RDATAC 0x10 ///< Read data continuously (registers cannot be read or written in this mode). #define ADS1299_OPC_SDATAC 0x11 ///< Stop continuous data read. -#define ADS1299_OPC_RDATA 0x12 ///< Read single data value. - -#define ADS1299_OPC_RREG 0x20 ///< Read register value. System must not be in RDATAC mode. -#define ADS1299_OPC_WREG 0x40 ///< Write register value. +#define ADS1299_OPC_RDATA 0x12 ///< Read single data value. - -/**********************************/ +#define ADS1299_OPC_RREG 0x20 ///< Read register value. System must not be in RDATAC mode. +#define ADS1299_OPC_WREG 0x40 ///< Write register value. /* ID REGISTER ********************************************************************/ @@ -142,15 +135,15 @@ #define ADS1299_REGDEFAULT_CONFIG1 0xB6 ///< Configuration register 1. Controls conversion mode and data rate. #define ADS1299_REGDEFAULT_CONFIG2 0xD2 ///< Configuration register 2. Controls LOFF comparator, reference, CLK pin, and test signal. #define ADS1299_REGDEFAULT_CONFIG3 0xEC -#define ADS1299_REGDEFAULT_LOFF 0x02 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. -#define ADS1299_REGDEFAULT_CH1SET 0x60 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. -#define ADS1299_REGDEFAULT_CH2SET 0x60 ///< -#define ADS1299_REGDEFAULT_CH3SET 0x60 -#define ADS1299_REGDEFAULT_CH4SET 0x60 -#define ADS1299_REGDEFAULT_CH5SET 0xF1 -#define ADS1299_REGDEFAULT_CH6SET 0xF1 -#define ADS1299_REGDEFAULT_CH7SET 0xF1 -#define ADS1299_REGDEFAULT_CH8SET 0xF1 +#define ADS1299_REGDEFAULT_LOFF 0x02 ///< Lead-off control register. Controls lead-off frequency, magnitude, and threshold. +#define ADS1299_REGDEFAULT_CH1SET 0x60 ///< Channel 1 settings register. Controls channel 1 input mux, gain, and power-down. +#define ADS1299_REGDEFAULT_CH2SET 0x60 ///< +#define ADS1299_REGDEFAULT_CH3SET 0x60 +#define ADS1299_REGDEFAULT_CH4SET 0x60 +#define ADS1299_REGDEFAULT_CH5SET 0xF1 +#define ADS1299_REGDEFAULT_CH6SET 0xF1 +#define ADS1299_REGDEFAULT_CH7SET 0xF1 +#define ADS1299_REGDEFAULT_CH8SET 0xF1 #define ADS1299_REGDEFAULT_BIAS_SENSP 0x0F ///< #define ADS1299_REGDEFAULT_BIAS_SENSN 0x0F #define ADS1299_REGDEFAULT_LOFF_SENSP 0x00 ///< @@ -158,39 +151,31 @@ #define ADS1299_REGDEFAULT_LOFF_FLIP 0x00 #define ADS1299_REGDEFAULT_LOFF_STATP 0x00 ///< #define ADS1299_REGDEFAULT_LOFF_STATN 0x00 -#define ADS1299_REGDEFAULT_GPIO 0x0F ///< -#define ADS1299_REGDEFAULT_MISC1 0x20 ///< -#define ADS1299_REGDEFAULT_MISC2 0x00 ///< +#define ADS1299_REGDEFAULT_GPIO 0x0F ///< +#define ADS1299_REGDEFAULT_MISC1 0x20 ///< +#define ADS1299_REGDEFAULT_MISC2 0x00 ///< #define ADS1299_REGDEFAULT_CONFIG4 0x00 - -// -// 0x00 = 125SPS -// 0x01 = 250SPS -// 0x02 = 500SPS -// 0x03 = 1000SPS -// 0x04 = 2000SPS -// 0x05 = 4000SPS -// 0x06 = 8000SPS -// - -/* typedefs */ -typedef int16_t body_voltage_t; +/* SUPPORTED DATA RATES ********************************************************/ +#define ADS1299_ODR_125_SPS 0x00 +#define ADS1299_ODR_250_SPS 0x01 +#define ADS1299_ODR_500_SPS 0x02 +#define ADS1299_ODR_1000_SPS 0x03 +#define ADS1299_ODR_2000_SPS 0x04 +#define ADS1299_ODR_4000_SPS 0x05 +#define ADS1299_ODR_8000_SPS 0x06 class ADS1299Driver { -public: - /* structs */ - static device *spi_dev; - static spi_config spi_cfg; -/************************************************************************************************************************************************** -* Function Prototypes ADS1299-x * -**************************************************************************************************************************************************/ +private: + /* spi structs */ + // Both are created and managed outside, should not be deleted here + // consider shared ptr? + const device* spi_dev; + const spi_config* spi_cfg; - static void ads_spi_init(void); - - static void init_buf(uint8_t * const p_tx_buffer, - uint8_t * const p_rx_buffer, - const uint16_t len); +public: + explicit ADS1299Driver(device* spi, spi_config* cfg) : spi_dev(spi), spi_cfg(cfg) {}; + ~ADS1299Driver() {}; // do nothing /** * \brief Initialize the ADS1299-x. @@ -198,29 +183,29 @@ class ADS1299Driver { * This function performs the power-on reset and initialization procedure documented on page 61 of the * ADS1299 datasheet, up to "Send SDATAC Command." */ - static void ads1299_spi_transfer(uint8_t* tx_data, size_t tx_len, uint8_t* rx_data, size_t rx_len); + void ads1299_spi_transfer(uint8_t* tx_data, size_t tx_len, uint8_t* rx_data, size_t rx_len); - static void ads1299_powerup_reset(void); + void ads1299_powerup_reset(void); - static void ads1299_init_regs(void); + void ads1299_init_regs(void); - static void ads1299_powerdn(void); + void ads1299_powerdn(void); - static void ads1299_powerup(void); + void ads1299_powerup(void); - static void ads1299_standby(void); + void ads1299_standby(void); - static void ads1299_wake(void); + void ads1299_wake(void); - static void ads1299_soft_start_conversion(void); + void ads1299_soft_start_conversion(void); - static void ads1299_stop_rdatac(void); + void ads1299_stop_rdatac(void); - static void ads1299_start_rdatac(void); + void ads1299_start_rdatac(void); - static void ads1299_check_id(void); + void ads1299_check_id(void); - static void get_eeg_voltage_samples(int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4); + void get_eeg_voltage_samples(int32_t *eeg1, int32_t *eeg2, int32_t *eeg3, int32_t *eeg4); }; #endif // ADS1299_H__ diff --git a/src/main.cpp b/src/main.cpp index 2294c57..e999b6c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,8 +27,9 @@ int main(void) LOG_INF("Hello world from %s", CONFIG_BOARD); DataAcquisitionThread::GetInstance().Initialize(); - - ADS1299Driver::ads_spi_init(); + DataAcquisitionThread::GetInstance().SendMessage( + DataAcquisitionThread::START_READING_AFE + ); while(1) { LOG_DBG("main thread up time: %u ms", k_uptime_get_32()); From b4032529385d9044ae43601f5385bc6cd1f3d87c Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Sun, 28 Jan 2024 23:41:12 -0700 Subject: [PATCH 07/11] adds basic dma set up for reading AFE, needs to be tested! --- prj.conf | 7 ++-- src/DataBufferManager.cpp | 72 +++++++++++++++++++++++++-------------- src/DataBufferManager.h | 30 +++++++++++++--- 3 files changed, 74 insertions(+), 35 deletions(-) diff --git a/prj.conf b/prj.conf index 5da5242..5d803d4 100644 --- a/prj.conf +++ b/prj.conf @@ -8,6 +8,7 @@ CONFIG_MULTITHREADING=y CONFIG_KERNEL_MEM_POOL=y CONFIG_NUM_PREEMPT_PRIORITIES=5 CONFIG_SYS_CLOCK_EXISTS=y +CONFIG_ZERO_LATENCY_IRQS=y CONFIG_ARM_MPU=y CONFIG_I2C=n CONFIG_WATCHDOG=y @@ -48,13 +49,11 @@ CONFIG_BOOT_DELAY=0 CONFIG_STDOUT_CONSOLE=n CONFIG_EARLY_CONSOLE=n -# Enable CPP specific +# Enable CPP specific flags +# NOTE: some are deprecated but we need them to avoid linker errors CONFIG_CPLUSPLUS=y CONFIG_LIB_CPLUSPLUS=y CONFIG_NEWLIB_LIBC=y CONFIG_STD_CPP14=y CONFIG_NRFX_SPIM0=y -# CONFIG_CPLUSPLUS=y -# CONFIG_CPLUSPLUS_STANDARD_CXX11=y -# CONFIG_CPLUSPLUS_LIBSTDCXX=y diff --git a/src/DataBufferManager.cpp b/src/DataBufferManager.cpp index 9a6eadd..db0934b 100644 --- a/src/DataBufferManager.cpp +++ b/src/DataBufferManager.cpp @@ -1,8 +1,17 @@ #include "DataBufferManager.h" +// log level declaration +LOG_MODULE_REGISTER(data_buffer_manager, LOG_LEVEL_DBG); -struct Semaphore DataBufferManager::eeg_buffer_semaphore; -eeg_sample DataBufferManager::dma_buffer[max_samples]; -size_t DataBufferManager::buffer_index; +struct dma_config DataBufferManager::dma_cfg = { + .channel_direction = PERIPHERAL_TO_MEMORY, + .complete_callback_en = 1, + .source_burst_length = 1, // accept 1 sample at a time + .dma_callback = DMATransfer, +}; + +// struct Semaphore DataBufferManager::eeg_buffer_semaphore; +// eeg_sample DataBufferManager::dma_buffer[max_samples]; +// size_t DataBufferManager::buffer_index; void DataBufferManager::Write() { @@ -12,29 +21,40 @@ void DataBufferManager::Read() { } -void DataBufferManager::spi_dma_setup() { - // IF NOT SET UP, CONTINUE; ELSE ERR - // // Configure SPI CS pin as GPIO output - // const struct device *cs_dev = device_get_binding(DT_GPIO_LABEL(DT_NODELABEL(spi0), cs_gpios)); - // gpio_pin_configure(cs_dev, SPI_CS_PIN, GPIO_OUTPUT_ACTIVE); - - // // Configure SPI device - // struct spi_config spi_cfg = { - // .frequency = 500000, // Set your desired frequency - // .operation = SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), - // .cs = NULL, - // }; - - // // Set up SPI device - // if (spi_configure(spi_dev, &spi_cfg) != 0) { - // printk("SPI configuration failed\n"); - // return; - // } - - // printk("SPI configured successfully\n"); - +void DataBufferManager::DMASetup(device* spi_dev) { + // set up basic source buffer for dma + dma_block_cfg.dest_address = (uintptr_t)source_buffer; + dma_block_cfg.source_address = (uintptr_t)&spi_dev->data; + + if (dma_config(dma_dev, AFE_DMA_CHANNEL, &dma_cfg)) { + LOG_ERR("Failed to configure DMA"); + // TODO: error handling :D + return; + } + + if (dma_start(dma_dev, AFE_DMA_CHANNEL)) { + LOG_ERR("Failed to start DMA transfer"); + // TODO: error handling + } } -void DataBufferManager::spi_sensor_read_and_transfer() { - +void DataBufferManager::DMATransfer(const struct device *dev, void *user_data, uint32_t channel, int status) { + if (status == 0) { + LOG_DBG("DMA transfer complete success"); + for (size_t i = 0; i < AFE_DMA_BLOCK_SIZE; i++) { + LOG_INF("Sample[%u]: %d", i, source_buffer[i]); + } + ResetBuffer(); + // Perform any post-transfer processing or signal completion + + } else { + LOG_ERR("DMA transfer failed with status %d", status); + // TODO: error handling + } + + // in either case, start the DMA again + if (dma_start(dma_dev, AFE_DMA_CHANNEL)) { + LOG_ERR("Failed to start DMA transfer"); + // TODO: error handling + } } diff --git a/src/DataBufferManager.h b/src/DataBufferManager.h index 995cb6d..2b2fef5 100644 --- a/src/DataBufferManager.h +++ b/src/DataBufferManager.h @@ -2,19 +2,39 @@ #include #include +#include +#include +#include +#include #include "Data.h" #include "core/Semaphore.h" +#define AFE_DMA_CHANNEL 0 +#define AFE_DMA_BURST_NUM_SAMPLES 5 +#define AFE_DMA_BLOCK_SIZE 256 + class DataBufferManager { // NOTE: needs to be fully static, only one instance should exist private: - static Semaphore eeg_buffer_semaphore; // TODO: use this instead - static eeg_sample dma_buffer[max_samples]; - static size_t buffer_index; + static struct device* dma_dev; + static struct dma_config dma_cfg; + static struct dma_block_config dma_block_cfg; + // note: each afe sample is 15 bytes, burst size is 5, so max usage is 15 * 5 = 75 + static uint8_t source_buffer[AFE_DMA_BLOCK_SIZE]; + + //// TODO: use this instead + // static Semaphore eeg_buffer_semaphore; + // static eeg_sample dma_buffer[max_samples]; + // static size_t buffer_index; public: static void Read(); static void Write(); - static void spi_dma_setup(); - static void spi_sensor_read_and_transfer(); + static void DMASetup(device* spi_dev); + static void DMATransfer(const struct device *dev, void *user_data, uint32_t channel, int status); + + // inline function to be used in callback + static void ResetBuffer() { + memset(source_buffer, 0, sizeof(source_buffer)); + }; }; \ No newline at end of file From 8dcd416ada9c3bb0b10f7a5faff52a63e46e1076 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Tue, 6 Feb 2024 22:58:35 -0700 Subject: [PATCH 08/11] adds temporary status led --- nrf5340dk_nrf5340_cpuapp.overlay | 13 +++++++++++++ prj.conf | 14 ++++++++++---- src/DataAcquisitionThread.cpp | 4 ++-- src/main.cpp | 25 +++++++++++++++++++------ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/nrf5340dk_nrf5340_cpuapp.overlay b/nrf5340dk_nrf5340_cpuapp.overlay index b630728..40bcef6 100644 --- a/nrf5340dk_nrf5340_cpuapp.overlay +++ b/nrf5340dk_nrf5340_cpuapp.overlay @@ -15,3 +15,16 @@ max-frequency = ; easydma-maxcnt-bits = <16>; }; + +&pwm0 { + status = "disabled"; +}; + +&sw_pwm { + status = "okay"; + channel-gpios = <&gpio0 4 PWM_POLARITY_INVERTED>; +}; + +&pwm_led0 { + pwms = <&sw_pwm 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>; +}; diff --git a/prj.conf b/prj.conf index 5d803d4..e44d05c 100644 --- a/prj.conf +++ b/prj.conf @@ -10,6 +10,7 @@ CONFIG_NUM_PREEMPT_PRIORITIES=5 CONFIG_SYS_CLOCK_EXISTS=y CONFIG_ZERO_LATENCY_IRQS=y CONFIG_ARM_MPU=y +CONFIG_PWM=y CONFIG_I2C=n CONFIG_WATCHDOG=y CONFIG_SPI=y @@ -19,16 +20,21 @@ CONFIG_FLASH=n # Enable log settings CONFIG_LOG=y +CONFIG_STDOUT_CONSOLE=n +CONFIG_UART_CONSOLE=n +# CONFIG_RTT=y +CONFIG_USE_SEGGER_RTT=y +CONFIG_LOG_BACKEND_RTT=y CONFIG_CBPRINTF_FP_A_SUPPORT=y # CONFIG_BOARD_ENABLE_CPUNET=y # Enable CMSIS to be used through nrfSDK CONFIG_FPU=y CONFIG_NEWLIB_LIBC=y -CONFIG_CMSIS_DSP=y -CONFIG_CMSIS_DSP_TRANSFORM=y -CONFIG_CMSIS_DSP_BASICMATH=y -CONFIG_CMSIS_DSP_COMPLEXMATH=y +# CONFIG_CMSIS_DSP=y +# CONFIG_CMSIS_DSP_TRANSFORM=y +# CONFIG_CMSIS_DSP_BASICMATH=y +# CONFIG_CMSIS_DSP_COMPLEXMATH=y CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=2048 # Enable hardware peripherals diff --git a/src/DataAcquisitionThread.cpp b/src/DataAcquisitionThread.cpp index 19e2b31..227f1ef 100644 --- a/src/DataAcquisitionThread.cpp +++ b/src/DataAcquisitionThread.cpp @@ -37,10 +37,10 @@ void DataAcquisitionThread::Run() { LOG_DBG("DataAcq::%s -- received message at %u ms", __FUNCTION__, k_uptime_get_32()); switch (static_cast(message)) { case STOP_READING_AFE: - AFEWrapper.Stop(); + // AFEWrapper.Stop(); break; case START_READING_AFE: - AFEWrapper.Start(); + // AFEWrapper.Start(); break; case INVALID: default: diff --git a/src/main.cpp b/src/main.cpp index e999b6c..4ef7b96 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,26 +14,39 @@ #include -#include "arm_math.h" +// #include "arm_math.h" #include "DataAcquisitionThread.h" #include "drivers/ads1299-x.h" +// temporary +#include + LOG_MODULE_REGISTER(eegals_app_core, LOG_LEVEL_DBG); -#define LOG_DELAY_MS 1000 +static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0)); + +#define LOG_DELAY_MS 500 +#define LED_PERIOD 1000 +#define LED_OFF 0 +#define LED_CHANNEL 0 +#define LED_BLUE_PULSE_WIDTH 1000 + int main(void) { LOG_INF("Hello world from %s", CONFIG_BOARD); - DataAcquisitionThread::GetInstance().Initialize(); - DataAcquisitionThread::GetInstance().SendMessage( - DataAcquisitionThread::START_READING_AFE - ); + // DataAcquisitionThread::GetInstance().Initialize(); while(1) { LOG_DBG("main thread up time: %u ms", k_uptime_get_32()); + pwm_set_dt(&pwm_led0, LED_PERIOD, LED_BLUE_PULSE_WIDTH); + k_msleep(LOG_DELAY_MS); + pwm_set_dt(&pwm_led0, LED_PERIOD, LED_OFF); k_msleep(LOG_DELAY_MS); + // DataAcquisitionThread::GetInstance().SendMessage( + // DataAcquisitionThread::START_READING_AFE + // ); } return 0; From 69d230aefb615defd110063b4e9030ffcaa56bb5 Mon Sep 17 00:00:00 2001 From: AgamAulakh Date: Wed, 7 Feb 2024 01:15:52 -0700 Subject: [PATCH 09/11] creates a CMSIS wrapper class! --- prj.conf | 8 +- src/ArmMatrixWrapper.h | 222 +++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 2 +- 3 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 src/ArmMatrixWrapper.h diff --git a/prj.conf b/prj.conf index e44d05c..2f84eb1 100644 --- a/prj.conf +++ b/prj.conf @@ -31,10 +31,10 @@ CONFIG_CBPRINTF_FP_A_SUPPORT=y # Enable CMSIS to be used through nrfSDK CONFIG_FPU=y CONFIG_NEWLIB_LIBC=y -# CONFIG_CMSIS_DSP=y -# CONFIG_CMSIS_DSP_TRANSFORM=y -# CONFIG_CMSIS_DSP_BASICMATH=y -# CONFIG_CMSIS_DSP_COMPLEXMATH=y +CONFIG_CMSIS_DSP=y +CONFIG_CMSIS_DSP_TRANSFORM=y +CONFIG_CMSIS_DSP_BASICMATH=y +CONFIG_CMSIS_DSP_COMPLEXMATH=y CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=2048 # Enable hardware peripherals diff --git a/src/ArmMatrixWrapper.h b/src/ArmMatrixWrapper.h new file mode 100644 index 0000000..882517c --- /dev/null +++ b/src/ArmMatrixWrapper.h @@ -0,0 +1,222 @@ +#include +#include +#include "arm_math.h" + +template +class ArmMatrixWrapper { +private: + static constexpr uint32_t MaxBufferSize = MaxRows * MaxCols; + float32_t data[MaxBufferSize]; + arm_matrix_instance_f32 matrix; + +public: + ArmMatrixWrapper() { + arm_mat_init_f32(&matrix, MaxRows, MaxCols, data); + }; + + // Copy cstor + ArmMatrixWrapper(const ArmMatrixWrapper& other) { + memcpy(data, other.data, MaxBufferSize * sizeof(float32_t)); + arm_mat_init_f32(&matrix, MaxRows, MaxCols, data); + }; + + // Assignment optor + ArmMatrixWrapper& operator=(const ArmMatrixWrapper& other) { + if (this != &other) { + memcpy(data, other.data, MaxBufferSize * sizeof(float32_t)); + arm_mat_init_f32(&matrix, MaxRows, MaxCols, data); + } + return *this; + }; + + // Dstor + ~ArmMatrixWrapper() {}; + + // Getters and Setters + bool set_rows(uint32_t rows) { + if (rows <= MaxRows) { + arm_mat_init_f32(&matrix, rows, matrix.numCols, data); + return true; + } else { + // NOTE: silent failure + return false; + } + }; + + bool set_cols(uint32_t cols) { + if (cols <= MaxCols) { + arm_mat_init_f32(&matrix, matrix.numRows, cols, data); + return true; + } else { + // Handle error: Columns exceeds maximum allowed value + return false; + } + }; + + uint32_t get_rows() const { + return matrix.numRows; + }; + + uint32_t get_cols() const { + return matrix.numCols; + }; + + // Access Functions + float32_t at(uint32_t index) const { + if (index < MaxBufferSize) { + return data[index]; + } else { + // Note: silent error + return 0.0f; + } + }; + + bool set_at(float32_t value, uint32_t index) { + if (index < MaxBufferSize) { + data[index] = value; + return true; + } else { + return false; + } + }; + + float32_t at(uint32_t i, uint32_t j) const { + if (i < matrix.numRows && j < matrix.numCols) { + return data[i * matrix.numCols + j]; + } else { + return 0.0f; + } + }; + + bool set_at(float32_t value, uint32_t i, uint32_t j) { + if (i < matrix.numRows && j < matrix.numCols) { + data[i * matrix.numCols + j] = value; + return true; + } else { + return false; + } + }; + + // Overloaded Operators + ArmMatrixWrapper operator+(const ArmMatrixWrapper& other) const { + ArmMatrixWrapper result; // deepy copy + arm_mat_add_f32(&matrix, &other.matrix, &result.matrix); + return result; + }; + + ArmMatrixWrapper operator-(const ArmMatrixWrapper& other) const { + ArmMatrixWrapper result; + arm_mat_sub_f32(&matrix, &other.matrix, &result.matrix); + return result; + }; + + ArmMatrixWrapper operator*(const ArmMatrixWrapper& other) const { + ArmMatrixWrapper result; + arm_mat_mult_f32(&matrix, &other.matrix, &result.matrix); + return result; + }; + + ArmMatrixWrapper& operator+=(const ArmMatrixWrapper& other) { + arm_mat_add_f32(&matrix, &other.matrix, &matrix); + return *this; + }; + + ArmMatrixWrapper& operator-=(const ArmMatrixWrapper& other) { + arm_mat_sub_f32(&matrix, &other.matrix, &matrix); + return *this; + }; + + ArmMatrixWrapper& operator*=(const ArmMatrixWrapper& other) { + arm_mat_mult_f32(&matrix, &other.matrix, &matrix); + return *this; + }; + + // Matrix Operations + ArmMatrixWrapper transpose() const { + ArmMatrixWrapper result; + arm_mat_trans_f32(&matrix, &result.matrix); + return result; + }; + + bool inverse() { + arm_status status = arm_mat_inverse_f32(&matrix, &matrix); + return (status == ARM_MATH_SUCCESS); + }; + + bool scale(float32_t scaleFactor) { + arm_mat_scale_f32(&matrix, scaleFactor, &matrix); + return true; // Assuming the CMSIS-DSP function always succeeds + }; + + // Utility + void set_to_identity() { + // arm_mat_identity_f32(&matrix); + }; + + void set_to_zero() { + memset(data, 0, MaxBufferSize * sizeof(float32_t)); + matrix.numCols = 0; + matrix.numRows = 0; + }; + + // statistics + float32_t maximum() const { + float32_t maxValue; + uint32_t maxIndex; + arm_max_f32(data, MaxBufferSize, &maxValue, &maxIndex); + return maxValue; + }; + + float32_t minimum() const { + float32_t minValue; + uint32_t minIndex; + arm_min_f32(data, MaxBufferSize, &minValue, &minIndex); + return minValue; + }; + + float32_t mean() const { + float32_t meanValue; + arm_mean_f32(data, MaxBufferSize, &meanValue); + return meanValue; + }; + + float32_t power() const { + float32_t powerValue; + arm_power_f32(data, MaxBufferSize, &powerValue); + return powerValue; + }; + + float32_t rms() const { + float32_t rmsValue; + arm_rms_f32(data, MaxBufferSize, &rmsValue); + return rmsValue; + }; + + float32_t standardDeviation() const { + float32_t stdDevValue; + arm_std_f32(data, MaxBufferSize, &stdDevValue); + return stdDevValue; + }; + + float32_t variance() const { + float32_t varianceValue; + arm_var_f32(data, MaxBufferSize, &varianceValue); + return varianceValue; + }; + + float32_t meanSquareError(const ArmMatrixWrapper& other) const { + float32_t mseValue; + arm_mean_squared_error_f32(data, other.data, MaxBufferSize, &mseValue); + return mseValue; + }; + + // // TEMPORARY!!! + // // Fast Fourier Transform Wrapper (FFT) + // // Assumes input is real data row vector, and output will be complex row vector + // ArmMatrixWrapper<1, MaxCols> fft() const { + // ArmMatrixWrapper<1, MaxCols> result; + // arm_cfft_f32(&arm_cfft_sR_f32_len, reinterpret_cast(data), 0, 1); + // memcpy(result.data, data, MaxBufferSize * sizeof(float32_t)); + // return result; + // }; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4ef7b96..2244444 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ #include -// #include "arm_math.h" +#include "ArmMatrixWrapper.h" #include "DataAcquisitionThread.h" #include "drivers/ads1299-x.h" From 811b11ac2b718ad9cae75bf3bd23c974600bc96d Mon Sep 17 00:00:00 2001 From: liy-hu Date: Mon, 12 Feb 2024 17:33:08 -0700 Subject: [PATCH 10/11] Add functions for single-sided FFT and Power calculations --- src/ArmMatrixWrapper.h | 82 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/src/ArmMatrixWrapper.h b/src/ArmMatrixWrapper.h index 882517c..93d4b48 100644 --- a/src/ArmMatrixWrapper.h +++ b/src/ArmMatrixWrapper.h @@ -210,13 +210,77 @@ class ArmMatrixWrapper { return mseValue; }; - // // TEMPORARY!!! - // // Fast Fourier Transform Wrapper (FFT) - // // Assumes input is real data row vector, and output will be complex row vector - // ArmMatrixWrapper<1, MaxCols> fft() const { - // ArmMatrixWrapper<1, MaxCols> result; - // arm_cfft_f32(&arm_cfft_sR_f32_len, reinterpret_cast(data), 0, 1); - // memcpy(result.data, data, MaxBufferSize * sizeof(float32_t)); - // return result; - // }; + // Computes the raw FFT for specific channel given a number of samples + ArmMatrixWrapper rawFFT(uint32_t channel) const { + + // Struct to store the output of the FFT for one channel + ArmMatrixWrapper rawResult; + + // Temporary arrays to store initial data and intermediate FFT values + float32_t inputFFT[matrix.numRows]; + float32_t outputFFT[matrix.numRows]; + + // O indicates FFT and 1 indicates inverse FFT (This flag will not change) + const uint32_t ifftFlag = 0; + + // Initalize the instance with the specified number of rows + arm_rfft_fast_instance_f32 fft_instance; + arm_rfft_fast_init_f32(&fft_instance, matrix.numRows); + + for (uint32_t i = 0; i < matrix.numRows; i++) + { + inputFFT[i] = at(i, channel); + } + + // Compute the FFT and store the outputs of the FFT in the array + arm_rfft_fast_f32(&fft_instance, inputFFT , outputFFT, ifftFlag); + + // The first entry is the DC offset so simply set to 0 + outputFFT[0] = 0; + + // Copy output FFT to result + memcpy(rawResult.data, outputFFT, MaxBufferSize * sizeof(float32_t)); + return rawResult; + }; + + + // Computes the single-sided FFT for a specific channel given a number of samples + ArmMatrixWrapper singleSideFFT(uint32_t channel) const { + + // Struct to store the output of the FFT for one channel + ArmMatrixWrapper FFTResult; + + // Temporary arrays to store initial data and intermediate FFT values + float32_t outputFFTMag[matrix.numRows/2]; + + // Extract the magnitude values because FFT values are complex and have both a magnitude and phase. + // matrix.numRows/2 because we are looking at one-sided spectrum (# of magnitudes we want to look at) + arm_cmplx_mag_f32(rawFFT(channel).data, outputFFTMag, matrix.numRows/2); + + // Copy the output to the result matrix + memcpy(FFTResult.data, outputFFTMag, MaxBufferSize * sizeof(float32_t)); + return FFTResult; + + } + + // Computes the single-sided Power given the single-sided FFT of a channel + ArmMatrixWrapper singleSidePower(uint32_t channel) const { + + // Struct to store the output of the FFT for one channel + ArmMatrixWrapper PowerResult; + + // Temporary arrays to store power FFT values + float32_t powerFFT[matrix.numRows/2]; + + // Compute the power of the FFT + arm_cmplx_mag_squared_f32(rawFFT(channel).data, powerFFT, matrix.numRows/2); + + // Check if the scaling is required for the above function + // Copy the output to the result matrix + memcpy(PowerResult.data, powerFFT, MaxBufferSize * sizeof(float32_t)); + return PowerResult; + + } + + }; \ No newline at end of file From 39b93a4db0d2526ecaf8eadf43e91412c8762716 Mon Sep 17 00:00:00 2001 From: liy-hu Date: Fri, 1 Mar 2024 13:34:03 -0700 Subject: [PATCH 11/11] Add functions for calculating single-sided band power and single-sided relative power --- src/ArmMatrixWrapper.h | 71 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/ArmMatrixWrapper.h b/src/ArmMatrixWrapper.h index 93d4b48..ec9c420 100644 --- a/src/ArmMatrixWrapper.h +++ b/src/ArmMatrixWrapper.h @@ -2,6 +2,8 @@ #include #include "arm_math.h" + + template class ArmMatrixWrapper { private: @@ -282,5 +284,72 @@ class ArmMatrixWrapper { } - + // Computes the single-sided Band Power given the single-sided FFT of a channel, + // Pwelch of specified channel and specific band power range type + float32_t singleSideBandPower(uint32_t channel, uint32_t sampleFreq, uint32_t bandSelect, const ArmMatrixWrapper& PWelch,) const { + + // The frequency resolution or frequency bin width + float32_t freqRes = sampleFreq / MaxRows; + + // The following band limits: delta band, theta band, alpha band, beta band + float32_t bandRanges[] = {1,3,4,7,8,12,13,30}; + + // Low and high limit for specified band range of frequencies + uint32_t lowLimit = 0; + uint32_t highLimit = 0; + + switch(bandSelect){ + + // delta band (Note: make an enum for the different band types) + case 0:{ + lowLimit = (uint32_t)(floor(bandRanges[0] / freqRes)); + highLimit = (uint32_t)(ceil(bandRanges[1] / freqRes)); + break; + } + // theta band + case 1:{ + lowLimit = (uint32_t)(floor(bandRanges[2] / freqRes)); + highLimit = (uint32_t)(ceil(bandRanges[3] / freqRes)); + break; + } + // alpha band + case 2:{ + lowLimit = (uint32_t)(floor(bandRanges[4] / freqRes)); + highLimit = (uint32_t)(ceil(bandRanges[5] / freqRes)); + break; + } + // beta band + case 3:{ + lowLimit = (uint32_t)(floor(bandRanges[6] / freqRes)); + highLimit = (uint32_t)(ceil(bandRanges[7] / freqRes)); + break; + } + default:{ + break; + } + + } + + // Calculating the band power for specified band range of frequnencies + float32_t bandPower = 0; + for(uint32_t i = lowLimit; i < highLimit; lowLimit++){ + bandPower += (PWelch.data[i] * freqRes); + } + return bandPower; + } + + // Computes the single-sided Relative Band Power given the specified channel and + // specified band power + float32_t singleSideRelativeBandPower(uint32_t channel, float32_t bandPower){ + + // Calculate summation of total power + float32_t totalPower = 0; + for(uint32_t i = 0; i < MaxRows/2; i++){ + totalPower += singleSidePower(channel).data[i]; + } + // Calculate relative band power relative to the total power + float32_t relativeBandpower = bandPower / totalPower; + return relativeBandpower; + } + }; \ No newline at end of file