Allow buffering of small messages (e.g. peripheral info, who-am-i, and hw/fw version) over USB

This commit is contained in:
Musa Mahmood 2025-04-17 07:14:22 -04:00
parent 3688db710a
commit 919523a465
5 changed files with 119 additions and 47 deletions

View File

@ -76,10 +76,11 @@ void ads1298_initialize(ads1298_info_t *p_info) {
spi_config.miso_pin = ADS1298_MISO_PIN;
spi_config.mosi_pin = ADS1298_MOSI_PIN;
spi_config.sck_pin = ADS1298_SCK_PIN;
//spi_config.frequency = NRF_DRV_SPI_FREQ_125K;
spi_config.frequency = NRF_DRV_SPI_FREQ_250K;
//spi_config.frequency = NRF_DRV_SPI_FREQ_500K;
spi_config.frequency = NRF_DRV_SPI_FREQ_1M;
//spi_config.frequency = NRF_DRV_SPI_FREQ_1M;
//spi_config.frequency = NRF_DRV_SPI_FREQ_2M;
spi_config.irq_priority = APP_IRQ_PRIORITY_HIGH;
//spi_config.irq_priority = APP_IRQ_PRIORITY_HIGHEST;
spi_config.mode = NRF_DRV_SPI_MODE_1;
spi_config.orc = 0x55;
@ -487,6 +488,15 @@ __STATIC_INLINE void copy_relevant_data(ads1298_info_t* p_info) {
}
}
void ads1298_get_data_fast(ads1298_info_t* p_info) {
spi_xfer_done = false;
// necessary?
memset(rx_buffer, 0, 27);
nrf_drv_spi_transfer(&spi0, rx_buffer, 27, rx_buffer, 27);
while (!spi_xfer_done) { __WFE(); }
memcpy(&p_info->usb_buffer[p_info->usb_buffer_count], &rx_buffer[3], 8 * 3);
p_info->usb_buffer_count += 3 * 8;
}
// Buffer size depends on version (27 bytes for ADS1298, 21 for ADS1296, and 15 for ADS1294)
void ads1298_get_data(ads1298_info_t* p_info) {

View File

@ -65,7 +65,8 @@
#define ADS129x_8CH_BITMASK 0x02 // 0x[...]10[010]
/** DEFAULT REGISTER VALUES **/
#define ADS1298_REGDEFAULT_CONFIG1 0xC6 // High-res mode, Multiple readback mode, clk output disabled, LP: 250 SPS
//
#define ADS1298_REGDEFAULT_CONFIG1 0xC5 // High-res mode, Multiple readback mode, clk output disabled, LP: 250 SPS
#define ADS1298_REGDEFAULT_CONFIG2 0x00 // Test signals
#define ADS1298_REGDEFAULT_CONFIG3 0xCE //
#define ADS1298_REGDEFAULT_LOFF 0x00
@ -112,6 +113,7 @@ typedef struct {
#if ADS1298_STATS
uint32_t drdy_trigger_count;
uint32_t seconds_elapsed;
uint32_t bytes_sent_usb;
#endif
} ads1298_info_t;
@ -147,6 +149,7 @@ void ads1294_get_data(ads1298_info_t* p_info);
void ads1296_get_data(ads1298_info_t* p_info);
void ads1298_get_data(ads1298_info_t* p_info);
void ads1298_get_data_fast(ads1298_info_t* p_info);
uint16_t ads1298_sampling_rate(ads1298_info_t* p_info);

136
main.c
View File

@ -42,7 +42,8 @@ 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_tx_buffer[NRF_DRV_USBD_EPSIZE];
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];
@ -84,30 +85,55 @@ void reset_counters(void) {
#if ADS1298
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
reset_buffer_count(m_info.usb_buffer);
// #TODO: Clear stats:
#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
}
void usb_transmit_message_ex(uint8_t message_prefix, uint8_t message_part_two, uint8_t* message, uint8_t message_length) {
usb_tx_buffer[0] = message_prefix;
usb_tx_buffer[1] = message_part_two;
usb_tx_buffer[2] = message_length + 3;
memcpy(&usb_tx_buffer[3], message, message_length);
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, message_length + 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("[ex]Writing message of length %d, ret: %d", message_length + 3, ret);
}
void usb_transmit_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
usb_tx_buffer[0] = message_prefix;
usb_tx_buffer[1] = message_length + 2;
memcpy(&usb_tx_buffer[2], message, message_length);
ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, message_length + 2);
// void usb_transmit_message(uint8_t message_prefix, uint8_t* message, uint8_t message_length) {
// usb_send_buffer[0] = message_prefix;
// usb_send_buffer[1] = message_length + 2;
// memcpy(&usb_send_buffer[2], message, message_length);
// ret_code_t ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, message_length + 2);
NRF_LOG_INFO("[0]Writing message of length %d, ret: %d", message_length + 2, ret);
// NRF_LOG_INFO("[0]Writing message of length %d, ret: %d", message_length + 2, ret);
// }
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("[1]Writing message of length %d, ret: %d", usb_send_buffer_offset, ret);
usb_send_buffer_offset = 0; // reset.
}
// 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;
}
}
// length is always `READ_SIZE`
@ -142,11 +168,8 @@ void read_ic_settings(uint8_t* new_packet) {
// read all registers into m_info.registers
ads1298_readback_registers(&m_info);
// Send back over USB.
usb_transmit_message_ex(REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
usb_send_push_ex(REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
m_info.registers, ADS1298_REGISTER_COUNT);
// This is totally unnecessary:
// usb_transmit_message_ex(ID_REGISTER_READBACK_PREFIX, REGISTER_READBACK_ADS1298,
// m_info.id_buffer, sizeof(m_info.id_buffer));
ads1298_start_rdatac();
ads1298_standby();
} break;
@ -180,18 +203,24 @@ static void process_new_packet(uint8_t* new_packet) {
}
if (SECOND_NIBBLE(new_packet[0]) == TN_STREAM_STOP) {
recording_mode = RECORDING_MODE_DISABLED;
#if ADS1298
#if ADS1298
if (m_info.state == 1) {
m_info.state = 0;
ads1298_standby();
}
#endif
#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_INFO) {
usb_transmit_message(CTRL_STAT_INFO_PERIPHERALS, g_Peripheral_Info, strlen(g_Peripheral_Info));
usb_transmit_message(CTRL_STAT_INFO_WHO_AM_I, g_WhoAmI, strlen(g_WhoAmI));
usb_transmit_message(CTRL_STAT_INFO_HW_FW_VERSION, g_HWFW_Ver, sizeof(g_HWFW_Ver));
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
@ -289,7 +318,9 @@ 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) {
@ -393,15 +424,29 @@ int main(void) {
ads1298_initialize(&m_info);
#endif
memset(usb_tx_buffer, 0x41, NRF_DRV_USBD_EPSIZE); // #TEMP
memset(usb_send_buffer, 0x41, NRF_DRV_USBD_EPSIZE); // #TEMP
reset_counters();
// #TEMP: application_timers_start
application_timers_start();
#if AUTO_START_ADS1298 // for debugging
ads1298_stop_rdatac();
NRF_LOG_INFO("Writing new ADS1298 registers:");
uint8_t new_registers[] = {0xC5,0x00,0xCE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00};
memcpy(m_info.registers, new_registers, ADS1298_REGISTER_COUNT);
ads1298_update_registers(&m_info);
ads1298_set_data_buffer_length(&m_info);
ads1298_start_rdatac();
ads1298_wakeup();
recording_mode = RECORDING_MODE_ALL;
#endif
while (true) {
#if !DISABLE_USBD_IN_MAIN_LOOP
while (app_usbd_event_queue_process()) { /* Nothing to do */ }
#endif
if (recording_mode == RECORDING_MODE_ALL) {
// if (recording_mode & drdy_flag) { // may be faster if we're just using ADS1298
@ -417,23 +462,30 @@ int main(void) {
}
#endif
switch (m_info.nChs) {
case 4:
ads1294_get_data(&m_info);
break;
case 6:
ads1296_get_data(&m_info);
break;
case 8:
ads1298_get_data(&m_info);
break;
default:
break;
}
// :ADS1298_FAST_PATH
ads1298_get_data_fast(&m_info);
// switch (m_info.nChs) {
// case 4:
// ads1294_get_data(&m_info);
// break;
// case 6:
// ads1296_get_data(&m_info);
// break;
// case 8:
// ads1298_get_data(&m_info);
// break;
// default:
// break;
// }
if (m_info.usb_buffer_count >= m_info.usb_buffer_size_max) {
m_info.usb_buffer_count = ADS1298_PACKET_OFFSET;
#if !DISABLE_USBD_IN_MAIN_LOOP
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
#endif
increment_packet(m_info.usb_buffer);
// NRF_LOG_INFO("Current time tick: %lu", app_timer_cnt_get());
@ -441,14 +493,16 @@ int main(void) {
}
#endif
}
#if !DISABLE_USBD_IN_MAIN_LOOP
if (run_throughput_test) {
/*ret = */app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_tx_buffer, NRF_DRV_USBD_EPSIZE);
/*ret = */app_usbd_cdc_acm_write(&m_app_cdc_acm, usb_send_buffer, NRF_DRV_USBD_EPSIZE);
}
#endif
UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
/* Sleep CPU only if there was no interrupt since last loop processing */
#if !DISABLE_USBD_IN_MAIN_LOOP
__WFE();
#endif
}
}

View File

@ -4,9 +4,13 @@
#define NRF52840_BREAKOUT_BOARD 1
#define ADS1298 1
#if ADS1298
#define ADS1298_STATS 1
#define AUTO_START_ADS1298 0
#define ADS1298_STATS 0
#endif
// Debugging flags
#define DISABLE_USBD_IN_MAIN_LOOP 0
// Testing with nRF52840:
#if NRF52840_BREAKOUT_BOARD
#define ADS1298_DRDY_PIN 11
@ -35,7 +39,8 @@
#define TN_STREAM_START 0x2
#define TN_STREAM_STOP 0xF
#define TN_STREAM_REQUEST_BATTERY_LEVEL 0xB
#define TN_STREAM_REQUEST_INFO 0xE
// #define TN_STREAM_REQUEST_WHO_AM_I 0xD
#define TN_STREAM_REQUEST_PERIPHERAL_INFO 0xE
#define TN_MISC_THROUGHPUT_TEST 0xF
#define TN_MISC_RTT_REQUEST 0x1

View File

@ -1628,7 +1628,7 @@
// <i> This may limit throughput if a lot of binary data is sent, but in terminal mode operation it makes sure that the data is always displayed right after it is sent.
#ifndef APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE
#define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 1
#define APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE 0
#endif
// </h>