483 lines
16 KiB
C
483 lines
16 KiB
C
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
|
|
#include "nrf.h"
|
|
#include "nrf_drv_usbd.h"
|
|
#include "nrf_drv_clock.h"
|
|
#include "nrf_gpio.h"
|
|
#include "nrf_delay.h"
|
|
#include "nrf_drv_power.h"
|
|
|
|
#include "app_error.h"
|
|
#include "app_util.h"
|
|
#include "app_usbd_core.h"
|
|
#include "app_usbd.h"
|
|
#include "app_usbd_string_desc.h"
|
|
#include "app_usbd_cdc_acm.h"
|
|
#include "app_usbd_serial_num.h"
|
|
|
|
#include "app_timer.h"
|
|
|
|
#include "nrf_log.h"
|
|
#include "nrf_log_ctrl.h"
|
|
#include "nrf_log_default_backends.h"
|
|
|
|
#include "custom_board.h"
|
|
|
|
static enum recording_mode_t recording_mode = RECORDING_MODE_DISABLED;
|
|
|
|
#if ADS1298
|
|
#include "ads1298.h"
|
|
#include "nrf_drv_gpiote.h"
|
|
|
|
static bool drdy_flag = false;
|
|
ads1298_info_t m_info;
|
|
#endif
|
|
|
|
// #TEMP: This should be generated dynamically maybe?
|
|
char g_Peripheral_Info[] = "ADS1298|";
|
|
char g_WhoAmI[] = "multimodal_cv_dev0_ecg";
|
|
uint8_t g_HWFW_Ver[] = {1,0,0,1}; // HW Maj, HW Min, FW Maj, FW Min
|
|
|
|
static volatile bool run_throughput_test = false;
|
|
static char usb_send_buffer[NRF_DRV_USBD_EPSIZE * 2]; // * 2 is just for safety margin in case we overrun!
|
|
static uint8_t usb_send_buffer_offset = 0;
|
|
|
|
#define READ_SIZE 64
|
|
static char usb_read_buffer[READ_SIZE];
|
|
|
|
|
|
#ifndef USBD_POWER_DETECTION
|
|
#define USBD_POWER_DETECTION true
|
|
#endif
|
|
|
|
static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
|
|
app_usbd_cdc_acm_user_event_t event);
|
|
|
|
#define CDC_ACM_COMM_INTERFACE 0
|
|
#define CDC_ACM_COMM_EPIN NRF_DRV_USBD_EPIN2
|
|
|
|
#define CDC_ACM_DATA_INTERFACE 1
|
|
#define CDC_ACM_DATA_EPIN NRF_DRV_USBD_EPIN1
|
|
#define CDC_ACM_DATA_EPOUT NRF_DRV_USBD_EPOUT1
|
|
|
|
|
|
APP_USBD_CDC_ACM_GLOBAL_DEF(m_app_cdc_acm,
|
|
cdc_acm_user_ev_handler,
|
|
CDC_ACM_COMM_INTERFACE,
|
|
CDC_ACM_DATA_INTERFACE,
|
|
CDC_ACM_COMM_EPIN,
|
|
CDC_ACM_DATA_EPIN,
|
|
CDC_ACM_DATA_EPOUT,
|
|
APP_USBD_CDC_COMM_PROTOCOL_AT_V250);
|
|
|
|
__STATIC_INLINE void reset_buffer_count(uint8_t *buffer_start) {
|
|
buffer_start[1] = 0;
|
|
}
|
|
|
|
__STATIC_INLINE void increment_packet(uint8_t *buffer_start) {
|
|
buffer_start[1] += 1;
|
|
}
|
|
|
|
void reset_counters(void) {
|
|
#if ADS1298
|
|
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
|
reset_buffer_count(m_info.usb_buffer);
|
|
#if ADS1298_STATS
|
|
m_info.drdy_trigger_count = 0;
|
|
m_info.seconds_elapsed = 0;
|
|
m_info.bytes_sent_usb = 0;
|
|
#endif
|
|
// #TODO: elapsed time Milliseconds (need last second timer).
|
|
#endif
|
|
}
|
|
|
|
// #DO NOT USE THIS. IT BREAKS THE PACKET FORMAT. USE
|
|
// void usb_send_message_1byte(uint8_t message) {
|
|
// usb_send_buffer[0] = message;
|
|
// ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, 1);
|
|
// NRF_LOG_INFO("[push]Writing message of length %d, ret: %d", 1, ret);
|
|
// }
|
|
|
|
// 1. For mushing together small messages into a 64-byte packet:
|
|
void usb_send_append_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
|
|
usb_send_buffer[usb_send_buffer_offset] = message_prefix;
|
|
usb_send_buffer[usb_send_buffer_offset + 1] = message_length + 2;
|
|
memcpy(&usb_send_buffer[usb_send_buffer_offset + 2], message, message_length);
|
|
|
|
usb_send_buffer_offset += (2 + message_length);
|
|
|
|
if (usb_send_buffer_offset >= 64) { // end of buffer
|
|
NRF_LOG_INFO("[WARNING] exceeded limit of usb_send_buffer\n");
|
|
usb_send_buffer_offset = 64;
|
|
}
|
|
}
|
|
|
|
|
|
// 2. For pushing all messages queued with usb_send_append_message
|
|
void usb_send_push(void) {
|
|
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, usb_send_buffer_offset);
|
|
|
|
NRF_LOG_INFO("[push]Writing message of length %d, ret: %d", usb_send_buffer_offset, ret);
|
|
|
|
usb_send_buffer_offset = 0; // reset.
|
|
}
|
|
|
|
// 3. For sending two-byte prefixed messages
|
|
void usb_send_push_ex(uint8_t message_prefix, uint8_t message_part_two, uint8_t* message, uint8_t message_length) {
|
|
usb_send_buffer[0] = message_prefix;
|
|
usb_send_buffer[1] = message_part_two;
|
|
usb_send_buffer[2] = message_length + 3;
|
|
memcpy(&usb_send_buffer[3], message, message_length);
|
|
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, message_length + 3);
|
|
|
|
NRF_LOG_INFO("[push ex]Writing message of length %d, ret: %d", message_length + 3, ret);
|
|
}
|
|
|
|
|
|
// length is always `READ_SIZE`
|
|
void write_ic_settings(uint8_t* new_packet) {
|
|
switch (SECOND_NIBBLE(new_packet[0])) {
|
|
case TN_IC_ADS1298: {
|
|
// #TODO: &new_packet[1]
|
|
#if ADS1298
|
|
ads1298_stop_rdatac();
|
|
NRF_LOG_INFO("Writing new ADS1298 registers:");
|
|
NRF_LOG_HEXDUMP_INFO(&new_packet[1], ADS1298_REGISTER_COUNT);
|
|
memcpy(m_info.registers, &new_packet[1], ADS1298_REGISTER_COUNT);
|
|
ads1298_update_registers(&m_info);
|
|
ads1298_set_data_buffer_length(&m_info);
|
|
ads1298_start_rdatac();
|
|
ads1298_standby();
|
|
// #TODO: readback registers into m_info.registers to confirm correct write.
|
|
uint8_t message[] = {0x00}; // LENGTH OF ZERO.
|
|
usb_send_push_ex(CTRL_ACK_WRITE_IC_REGISTER, IC_ID_ADS1298, message, strlen(message));
|
|
#endif
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void read_ic_settings(uint8_t* new_packet) {
|
|
switch (SECOND_NIBBLE(new_packet[0])) {
|
|
case TN_IC_ADS1298: {
|
|
// #NOTE: you will not be able to read registers while in RDATAC mode.
|
|
|
|
// ads1298_check_id(&m_info);
|
|
ads1298_stop_rdatac();
|
|
// read all registers into m_info.registers
|
|
ads1298_readback_registers(&m_info);
|
|
// Send back over USB.
|
|
usb_send_push_ex(CTRL_REGISTER_READBACK, IC_ID_ADS1298,
|
|
m_info.registers, ADS1298_REGISTER_COUNT);
|
|
ads1298_start_rdatac();
|
|
ads1298_standby();
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// length is always `READ_SIZE`
|
|
static void process_new_packet(uint8_t* new_packet) {
|
|
switch (FIRST_NIBBLE(new_packet[0])) {
|
|
case CN_WRITE_IC_REGS: {
|
|
write_ic_settings(new_packet);
|
|
} break;
|
|
case CN_READ_IC_REGS: {
|
|
read_ic_settings(new_packet);
|
|
} break;
|
|
case CN_STREAM_CONTROL: {
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_START) {
|
|
recording_mode = RECORDING_MODE_ALL;
|
|
#if ADS1298
|
|
if (m_info.state == 0) {
|
|
m_info.state = 1;
|
|
// ads1298_update_registers(&m_info);
|
|
// ads1298_set_data_buffer_length(&m_info);
|
|
ads1298_wakeup();
|
|
NRF_LOG_INFO("[ADS129x] Active channels: 0x%X", m_info.active_chs);
|
|
}
|
|
#endif
|
|
reset_counters();
|
|
}
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_STOP) {
|
|
recording_mode = RECORDING_MODE_DISABLED;
|
|
#if ADS1298
|
|
if (m_info.state == 1) {
|
|
m_info.state = 0;
|
|
ads1298_standby();
|
|
}
|
|
#endif
|
|
#if ADS1298_STATS
|
|
NRF_LOG_INFO("Bytes sent via USB: %lu\n", m_info.bytes_sent_usb);
|
|
#endif
|
|
reset_counters();
|
|
}
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_PERIPHERAL_INFO) {
|
|
// #TODO: we should assert that the length of these messages does not exceed the packet
|
|
// size!
|
|
usb_send_append_message(CTRL_STAT_INFO_PERIPHERALS, g_Peripheral_Info, strlen(g_Peripheral_Info));
|
|
usb_send_append_message(CTRL_STAT_INFO_WHO_AM_I, g_WhoAmI, strlen(g_WhoAmI));
|
|
usb_send_append_message(CTRL_STAT_INFO_HW_FW_VERSION, g_HWFW_Ver, sizeof(g_HWFW_Ver));
|
|
usb_send_push();
|
|
}
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_REQUEST_BATTERY_LEVEL) {
|
|
// #NOTE: Will not use. This is not a battery-powered device. (I mean, it can
|
|
// be, but it's also wired, so who cares.)
|
|
// Just return some constant.
|
|
}
|
|
|
|
} break;
|
|
case CN_MISC_CONTROLS: {
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_MISC_THROUGHPUT_TEST) { // 0xEF
|
|
NRF_LOG_INFO("Starting throughput test!");
|
|
run_throughput_test = true;
|
|
}
|
|
if (SECOND_NIBBLE(new_packet[0]) == TN_MISC_RTT_REQUEST) {
|
|
uint8_t message[] = {0x00}; // LENGTH OF ZERO.
|
|
usb_send_append_message(CTRL_ACK_RTT_REQUEST, message, strlen(message));
|
|
usb_send_push();
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cdc_acm_user_ev_handler(app_usbd_class_inst_t const * p_inst,
|
|
app_usbd_cdc_acm_user_event_t event) {
|
|
app_usbd_cdc_acm_t const * p_cdc_acm = app_usbd_cdc_acm_class_get(p_inst);
|
|
|
|
switch (event) {
|
|
case APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN: {
|
|
|
|
/*Setup first transfer*/
|
|
ret_code_t ret = app_usbd_cdc_acm_read(&m_app_cdc_acm, usb_read_buffer, READ_SIZE);
|
|
UNUSED_VARIABLE(ret);
|
|
break;
|
|
}
|
|
case APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE:
|
|
break;
|
|
case APP_USBD_CDC_ACM_USER_EVT_TX_DONE:
|
|
break;
|
|
case APP_USBD_CDC_ACM_USER_EVT_RX_DONE: {
|
|
ret_code_t ret;
|
|
NRF_LOG_INFO("Bytes waiting: %d", app_usbd_cdc_acm_bytes_stored(p_cdc_acm));
|
|
do {
|
|
/*Get amount of data transfered*/
|
|
size_t size = app_usbd_cdc_acm_rx_size(p_cdc_acm);
|
|
|
|
NRF_LOG_INFO("RX[size:%lu] [0]: 0x%X", size, usb_read_buffer[0]);
|
|
|
|
process_new_packet(usb_read_buffer);
|
|
|
|
/* Fetch data until internal buffer is empty */
|
|
ret = app_usbd_cdc_acm_read(&m_app_cdc_acm, usb_read_buffer, READ_SIZE);
|
|
} while (ret == NRF_SUCCESS);
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void usbd_user_ev_handler(app_usbd_event_type_t event) {
|
|
// #TODO: should reset_counters() somewhere here?
|
|
switch (event) {
|
|
case APP_USBD_EVT_DRV_SUSPEND:
|
|
break;
|
|
case APP_USBD_EVT_DRV_RESUME:
|
|
break;
|
|
case APP_USBD_EVT_STARTED:
|
|
break;
|
|
case APP_USBD_EVT_STOPPED:
|
|
app_usbd_disable();
|
|
break;
|
|
case APP_USBD_EVT_POWER_DETECTED:
|
|
NRF_LOG_INFO("USB power detected");
|
|
|
|
if (!nrf_drv_usbd_is_enabled()) {
|
|
app_usbd_enable();
|
|
}
|
|
break;
|
|
case APP_USBD_EVT_POWER_REMOVED:
|
|
NRF_LOG_INFO("USB power removed");
|
|
app_usbd_stop();
|
|
break;
|
|
case APP_USBD_EVT_POWER_READY:
|
|
NRF_LOG_INFO("USB ready");
|
|
app_usbd_start();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if ADS1298
|
|
void drdy_pin_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
|
|
UNUSED_PARAMETER(pin);
|
|
UNUSED_PARAMETER(action);
|
|
drdy_flag = true;
|
|
#if ADS1298_STATS
|
|
m_info.drdy_trigger_count += 1;
|
|
#endif
|
|
}
|
|
|
|
void ads1298_interrupt_setup(void) {
|
|
nrf_gpio_cfg_output(ADS1298_PWDN_PIN);
|
|
nrf_gpio_pin_clear(ADS1298_PWDN_PIN);
|
|
nrf_gpio_cfg_input(ADS1298_DRDY_PIN, NRF_GPIO_PIN_PULLUP);
|
|
// Initialize GPIOTE:
|
|
ret_code_t err_code = NRF_SUCCESS;
|
|
if (!nrf_drv_gpiote_is_init()) {
|
|
err_code = nrf_drv_gpiote_init();
|
|
}
|
|
NRF_LOG_INFO("GPIOTE error code: %d", err_code);
|
|
APP_ERROR_CHECK(err_code);
|
|
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_HITOLO(true);
|
|
in_config.is_watcher = true;
|
|
in_config.pull = NRF_GPIO_PIN_NOPULL;
|
|
err_code = nrf_drv_gpiote_in_init(ADS1298_DRDY_PIN, &in_config, drdy_pin_handler);
|
|
APP_ERROR_CHECK(err_code);
|
|
nrf_drv_gpiote_in_event_enable(ADS1298_DRDY_PIN, true);
|
|
ads1298_power_down();
|
|
}
|
|
#endif
|
|
|
|
#if ADS1298_STATS
|
|
APP_TIMER_DEF(ads_timer_id);
|
|
#define APP_TIMER_INTERVAL APP_TIMER_TICKS(1000) // 1 second.
|
|
static volatile bool ads_timer_timeout = false;
|
|
|
|
static void ads_timer_timeout_handler(void* p_context) {
|
|
UNUSED_PARAMETER(p_context);
|
|
|
|
ads_timer_timeout = true;
|
|
m_info.seconds_elapsed += 1;
|
|
}
|
|
#endif
|
|
|
|
static void application_timers_start(void) {
|
|
#if ADS1298_STATS
|
|
ret_code_t err_code;
|
|
err_code = app_timer_start(ads_timer_id, APP_TIMER_INTERVAL, NULL);
|
|
APP_ERROR_CHECK(err_code);
|
|
#endif
|
|
}
|
|
|
|
static void timers_init(void) {
|
|
ret_code_t ret = app_timer_init();
|
|
APP_ERROR_CHECK(ret);
|
|
|
|
#if ADS1298_STATS
|
|
ret_code_t err_code =
|
|
app_timer_create(&ads_timer_id, APP_TIMER_MODE_REPEATED, ads_timer_timeout_handler);
|
|
APP_ERROR_CHECK(err_code);
|
|
#endif
|
|
}
|
|
|
|
int main(void) {
|
|
ret_code_t ret;
|
|
static const app_usbd_config_t usbd_config = {
|
|
.ev_state_proc = usbd_user_ev_handler
|
|
};
|
|
|
|
ret = NRF_LOG_INIT(NULL);
|
|
APP_ERROR_CHECK(ret);
|
|
|
|
NRF_LOG_DEFAULT_BACKENDS_INIT(); // Init RTT Backend
|
|
|
|
ret = nrf_drv_clock_init();
|
|
APP_ERROR_CHECK(ret);
|
|
|
|
nrf_drv_clock_lfclk_request(NULL);
|
|
|
|
// Wait for LFCLK to init.
|
|
while(!nrf_drv_clock_lfclk_is_running()) { }
|
|
|
|
timers_init();
|
|
|
|
app_usbd_serial_num_generate();
|
|
|
|
ret = app_usbd_init(&usbd_config);
|
|
APP_ERROR_CHECK(ret);
|
|
NRF_LOG_INFO("USBD CDC ACM example started.");
|
|
|
|
app_usbd_class_inst_t const * class_cdc_acm = app_usbd_cdc_acm_class_inst_get(&m_app_cdc_acm);
|
|
ret = app_usbd_class_append(class_cdc_acm);
|
|
APP_ERROR_CHECK(ret);
|
|
|
|
if (USBD_POWER_DETECTION) {
|
|
ret = app_usbd_power_events_enable();
|
|
APP_ERROR_CHECK(ret);
|
|
} else {
|
|
NRF_LOG_INFO("No USB power detection enabled\r\nStarting USB now");
|
|
|
|
app_usbd_enable();
|
|
app_usbd_start();
|
|
}
|
|
|
|
// Init peripherals:
|
|
#if ADS1298
|
|
ads1298_interrupt_setup();
|
|
nrf_delay_ms(200);
|
|
ads1298_initialize(&m_info);
|
|
#endif
|
|
|
|
memset(usb_send_buffer, 0x41, NRF_DRV_USBD_EPSIZE); // #TEMP
|
|
|
|
reset_counters();
|
|
|
|
// #TEMP: application_timers_start
|
|
application_timers_start();
|
|
|
|
while (true) {
|
|
while (app_usbd_event_queue_process()) { /* Nothing to do */ }
|
|
|
|
if (recording_mode == RECORDING_MODE_ALL) {
|
|
// if (recording_mode & drdy_flag) { // may be faster if we're just using ADS1298
|
|
#if ADS1298
|
|
if (drdy_flag) {
|
|
drdy_flag = false;
|
|
#if ADS1298_STATS
|
|
|
|
if (ads_timer_timeout) {
|
|
ads_timer_timeout = false;
|
|
//NRF_LOG_INFO("[ADS1298] Collected %lu samples in %d seconds", m_info.drdy_trigger_count, m_info.seconds_elapsed);
|
|
NRF_LOG_INFO("[ADS1298] DR: %lu", m_info.drdy_trigger_count / m_info.seconds_elapsed);
|
|
}
|
|
#endif
|
|
|
|
// :ADS1298_FAST_PATH
|
|
ads1298_get_data_fast(&m_info);
|
|
|
|
if (m_info.usb_buffer_count >= m_info.usb_buffer_size_max) {
|
|
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
|
|
app_usbd_cdc_acm_write(&m_app_cdc_acm, m_info.usb_buffer, m_info.usb_buffer_size_max);
|
|
#if ADS1298_STATS
|
|
m_info.bytes_sent_usb += m_info.usb_buffer_size_max;
|
|
#endif
|
|
increment_packet(m_info.usb_buffer);
|
|
|
|
// NRF_LOG_INFO("Current time tick: %lu", app_timer_cnt_get());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (run_throughput_test) {
|
|
app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, NRF_DRV_USBD_EPSIZE);
|
|
}
|
|
|
|
UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
|
|
|
|
/* Sleep CPU only if there was no interrupt since last loop processing */
|
|
__WFE();
|
|
}
|
|
}
|