harden USB telemetry transport
This commit is contained in:
+2
-1
@@ -1,5 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "trikke_sensor_main.c" "trikke_protocol.c"
|
||||
SRCS "trikke_sensor_main.c" "trikke_protocol.c" "trikke_transport.c"
|
||||
"trikke_usb_transport.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES adxl345 l3g4200d esp_timer esp_driver_gpio esp_driver_i2c
|
||||
esp_driver_usb_serial_jtag vfs
|
||||
|
||||
+39
-15
@@ -14,6 +14,8 @@
|
||||
#include "freertos/task.h"
|
||||
#include "l3g4200d.h"
|
||||
#include "trikke_protocol.h"
|
||||
#include "trikke_transport.h"
|
||||
#include "trikke_usb_transport.h"
|
||||
|
||||
// Seeed Studio XIAO ESP32-C3: D4/SDA = GPIO6, D5/SCL = GPIO7.
|
||||
#define TRIKKE_I2C_PORT I2C_NUM_0
|
||||
@@ -54,6 +56,8 @@ typedef struct {
|
||||
QueueHandle_t sample_queue;
|
||||
atomic_uint_least32_t dropped_sample_count;
|
||||
atomic_uint_least32_t loop_overrun_count;
|
||||
trikke_transport_t transport;
|
||||
trikke_usb_transport_t usb_transport;
|
||||
} trikke_context_t;
|
||||
|
||||
static trikke_context_t s_context;
|
||||
@@ -157,22 +161,26 @@ static void acquisition_task(void *argument)
|
||||
}
|
||||
}
|
||||
|
||||
static bool write_binary_packet(const uint8_t *packet, size_t packet_size)
|
||||
{
|
||||
const size_t written = fwrite(packet, 1, packet_size, stdout);
|
||||
const int flush_result = fflush(stdout);
|
||||
const bool complete = written == packet_size && flush_result == 0;
|
||||
if (!complete) {
|
||||
clearerr(stdout);
|
||||
}
|
||||
return complete;
|
||||
}
|
||||
|
||||
static void write_binary_packet_until_sent(
|
||||
trikke_context_t *context,
|
||||
trikke_transport_sender_t *sender,
|
||||
const uint8_t *packet,
|
||||
size_t packet_size)
|
||||
{
|
||||
while (!write_binary_packet(packet, packet_size)) {
|
||||
while (true) {
|
||||
const trikke_transport_status_t status =
|
||||
trikke_transport_sender_step(
|
||||
sender, &context->transport, packet, packet_size);
|
||||
if (status == TRIKKE_TRANSPORT_COMPLETE) {
|
||||
return;
|
||||
}
|
||||
if (status == TRIKKE_TRANSPORT_FATAL) {
|
||||
// Preserve the in-flight packet and stop consuming the queue. A
|
||||
// fatal backend invariant is not safely recoverable or retryable.
|
||||
while (true) {
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(TRIKKE_TRANSPORT_RETRY_DELAY_MS));
|
||||
}
|
||||
}
|
||||
@@ -185,12 +193,14 @@ static void output_task(void *argument)
|
||||
uint8_t packet[TRIKKE_WIRE_MAX_PACKET_SIZE] = {0};
|
||||
uint32_t packet_sequence = 0;
|
||||
uint32_t sample_packet_count = 0;
|
||||
trikke_transport_sender_t sender;
|
||||
trikke_transport_sender_init(&sender);
|
||||
|
||||
size_t packet_size = trikke_encode_metadata_packet(
|
||||
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
write_binary_packet_until_sent(context, &sender, packet, packet_size);
|
||||
|
||||
while (true) {
|
||||
trikke_wire_sample_t samples[TRIKKE_WIRE_MAX_RECORDS] = {0};
|
||||
@@ -227,14 +237,15 @@ static void output_task(void *argument)
|
||||
packet, sizeof(packet), packet_sequence++, esp_timer_get_time(),
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), &TRIKKE_METADATA);
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
write_binary_packet_until_sent(
|
||||
context, &sender, packet, packet_size);
|
||||
}
|
||||
|
||||
packet_size = trikke_encode_sample_packet(
|
||||
packet, sizeof(packet), packet_sequence++,
|
||||
atomic_load(&context->dropped_sample_count),
|
||||
atomic_load(&context->loop_overrun_count), samples, sample_count);
|
||||
write_binary_packet_until_sent(packet, packet_size);
|
||||
write_binary_packet_until_sent(context, &sender, packet, packet_size);
|
||||
++sample_packet_count;
|
||||
}
|
||||
}
|
||||
@@ -324,6 +335,17 @@ void app_main(void)
|
||||
// binary frames whenever a payload byte equals LF.
|
||||
usb_serial_jtag_vfs_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
|
||||
|
||||
err = trikke_usb_transport_init(
|
||||
&s_context.usb_transport, &s_context.transport);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB transport initialization failed: %s",
|
||||
esp_err_to_name(err));
|
||||
l3g4200d_deinit(&s_context.gyroscope);
|
||||
adxl345_deinit(&s_context.accelerometer);
|
||||
i2c_del_master_bus(bus);
|
||||
return;
|
||||
}
|
||||
|
||||
s_context.sample_queue =
|
||||
xQueueCreate(TRIKKE_SAMPLE_QUEUE_DEPTH, sizeof(trikke_wire_sample_t));
|
||||
if (s_context.sample_queue == NULL) {
|
||||
@@ -331,6 +353,7 @@ void app_main(void)
|
||||
l3g4200d_deinit(&s_context.gyroscope);
|
||||
adxl345_deinit(&s_context.accelerometer);
|
||||
i2c_del_master_bus(bus);
|
||||
trikke_usb_transport_deinit(&s_context.usb_transport);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -351,6 +374,7 @@ void app_main(void)
|
||||
l3g4200d_deinit(&s_context.gyroscope);
|
||||
adxl345_deinit(&s_context.accelerometer);
|
||||
i2c_del_master_bus(bus);
|
||||
trikke_usb_transport_deinit(&s_context.usb_transport);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "trikke_transport.h"
|
||||
|
||||
static bool status_is_valid(trikke_transport_status_t status)
|
||||
{
|
||||
return status >= TRIKKE_TRANSPORT_COMPLETE &&
|
||||
status <= TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
void trikke_transport_sender_init(trikke_transport_sender_t *sender)
|
||||
{
|
||||
if (sender != NULL) {
|
||||
sender->pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
trikke_transport_status_t trikke_transport_sender_step(
|
||||
trikke_transport_sender_t *sender,
|
||||
const trikke_transport_t *transport,
|
||||
const uint8_t *packet,
|
||||
size_t packet_size)
|
||||
{
|
||||
if (sender == NULL || transport == NULL || transport->begin == NULL ||
|
||||
transport->poll == NULL || packet == NULL || packet_size == 0) {
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
const trikke_transport_status_t status = sender->pending
|
||||
? transport->poll(transport->context)
|
||||
: transport->begin(transport->context, packet, packet_size);
|
||||
if (!status_is_valid(status)) {
|
||||
sender->pending = false;
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
if (status == TRIKKE_TRANSPORT_PENDING) {
|
||||
sender->pending = true;
|
||||
} else {
|
||||
// RETRY from poll is allowed only when the backend has discarded or
|
||||
// otherwise resolved the old transfer and knows resubmission is safe.
|
||||
sender->pending = false;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
TRIKKE_TRANSPORT_COMPLETE = 0,
|
||||
TRIKKE_TRANSPORT_RETRY,
|
||||
TRIKKE_TRANSPORT_PENDING,
|
||||
TRIKKE_TRANSPORT_FATAL,
|
||||
} trikke_transport_status_t;
|
||||
|
||||
typedef trikke_transport_status_t (*trikke_transport_begin_fn)(
|
||||
void *context,
|
||||
const uint8_t *packet,
|
||||
size_t packet_size);
|
||||
|
||||
typedef trikke_transport_status_t (*trikke_transport_poll_fn)(void *context);
|
||||
|
||||
typedef struct {
|
||||
void *context;
|
||||
trikke_transport_begin_fn begin;
|
||||
trikke_transport_poll_fn poll;
|
||||
} trikke_transport_t;
|
||||
|
||||
typedef struct {
|
||||
bool pending;
|
||||
} trikke_transport_sender_t;
|
||||
|
||||
void trikke_transport_sender_init(trikke_transport_sender_t *sender);
|
||||
|
||||
// Advances one bounded transport operation. The packet storage must remain valid
|
||||
// and unchanged from the first PENDING result through COMPLETE. While pending,
|
||||
// only poll is called: an ambiguous timeout can never duplicate a frame.
|
||||
trikke_transport_status_t trikke_transport_sender_step(
|
||||
trikke_transport_sender_t *sender,
|
||||
const trikke_transport_t *transport,
|
||||
const uint8_t *packet,
|
||||
size_t packet_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "trikke_usb_transport.h"
|
||||
|
||||
#include "driver/usb_serial_jtag.h"
|
||||
#include "driver/usb_serial_jtag_vfs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#define TRIKKE_USB_TX_BUFFER_SIZE 512
|
||||
#define TRIKKE_USB_RX_BUFFER_SIZE 128
|
||||
#define TRIKKE_USB_OPERATION_TIMEOUT_MS 50
|
||||
|
||||
static trikke_transport_status_t usb_begin_packet(
|
||||
void *context,
|
||||
const uint8_t *packet,
|
||||
size_t packet_size)
|
||||
{
|
||||
const trikke_usb_transport_t *usb = context;
|
||||
if (usb == NULL || !usb->initialized || packet == NULL || packet_size == 0 ||
|
||||
packet_size > TRIKKE_USB_TX_BUFFER_SIZE) {
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
const int written = usb_serial_jtag_write_bytes(
|
||||
packet, packet_size,
|
||||
pdMS_TO_TICKS(TRIKKE_USB_OPERATION_TIMEOUT_MS));
|
||||
if (written == (int)packet_size) {
|
||||
return TRIKKE_TRANSPORT_PENDING;
|
||||
}
|
||||
if (written == 0) {
|
||||
return TRIKKE_TRANSPORT_RETRY;
|
||||
}
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
static trikke_transport_status_t usb_poll_packet(void *context)
|
||||
{
|
||||
const trikke_usb_transport_t *usb = context;
|
||||
if (usb == NULL || !usb->initialized) {
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
const esp_err_t err = usb_serial_jtag_wait_tx_done(
|
||||
pdMS_TO_TICKS(TRIKKE_USB_OPERATION_TIMEOUT_MS));
|
||||
if (err == ESP_OK) {
|
||||
return TRIKKE_TRANSPORT_COMPLETE;
|
||||
}
|
||||
if (err == ESP_ERR_TIMEOUT) {
|
||||
return TRIKKE_TRANSPORT_PENDING;
|
||||
}
|
||||
return TRIKKE_TRANSPORT_FATAL;
|
||||
}
|
||||
|
||||
esp_err_t trikke_usb_transport_init(
|
||||
trikke_usb_transport_t *usb,
|
||||
trikke_transport_t *transport)
|
||||
{
|
||||
if (usb == NULL || transport == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (usb->initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
usb_serial_jtag_driver_config_t config = {
|
||||
.tx_buffer_size = TRIKKE_USB_TX_BUFFER_SIZE,
|
||||
.rx_buffer_size = TRIKKE_USB_RX_BUFFER_SIZE,
|
||||
};
|
||||
const esp_err_t err = usb_serial_jtag_driver_install(&config);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
usb->initialized = true;
|
||||
transport->context = usb;
|
||||
transport->begin = usb_begin_packet;
|
||||
transport->poll = usb_poll_packet;
|
||||
|
||||
// Route any unexpected VFS output through the installed driver as well, so
|
||||
// it cannot race the driver's ISR by touching the hardware FIFO directly.
|
||||
usb_serial_jtag_vfs_use_driver();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void trikke_usb_transport_deinit(trikke_usb_transport_t *usb)
|
||||
{
|
||||
if (usb == NULL || !usb->initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
usb_serial_jtag_vfs_use_nonblocking();
|
||||
usb_serial_jtag_driver_uninstall();
|
||||
usb->initialized = false;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "trikke_transport.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
} trikke_usb_transport_t;
|
||||
|
||||
esp_err_t trikke_usb_transport_init(
|
||||
trikke_usb_transport_t *usb,
|
||||
trikke_transport_t *transport);
|
||||
|
||||
void trikke_usb_transport_deinit(trikke_usb_transport_t *usb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user