ESP-IDF Firmware
Firmware architecture and call graph
Loading...
Searching...
No Matches
main/main.c
Go to the documentation of this file.
1#include <assert.h>
2#include <math.h>
3#include <stdint.h>
4#include <stdio.h>
5#include "FreeRTOS.h"
6
7#include "esp_adc/adc_cali.h"
8#include "esp_adc/adc_cali_scheme.h"
9#include "esp_adc/adc_continuous.h"
10#include "esp_adc/adc_filter.h"
11#include "esp_attr.h"
12#include "esp_crc.h"
13#include "esp_dsp.h"
14#include "esp_err.h"
15#include "esp_heap_caps.h"
16#include "esp_log.h"
17#include "esp_timer.h"
18#include "freertos/queue.h"
19#include "freertos/semphr.h"
20#include "freertos/task.h"
21#include "led_strip.h"
22#include "string.h"
23#include "tinyusb.h"
24#include "tusb_cdc_acm.h"
25
26#if CONFIG_ENABLE_I2S_WAVE_GEN || CONFIG_ENABLE_SDM_WAVE_GEN
27#include "wave_gen.h"
28#endif
29
30
31static const char *TAG = "adc_fft";
32
33#if defined(__GNUC__) && !defined(__clang__)
34#define OPTIMIZE_O2 __attribute__((optimize("O2")))
35#else
36#define OPTIMIZE_O2
37#endif
38
39#define FFT_SIZE CONFIG_ADC_FFT_SIZE
40#define ADC_SPS CONFIG_ADC_FS
41
42/* Build invariants: transport builds must not modify spectra. */
43#if CONFIG_FFT_BUILD_TRANSPORT
44#if CONFIG_ADC_ENABLE_WINDOW || CONFIG_OUTPUT_TOPK_ENABLE || \
45 (CONFIG_OUTPUT_CUTOFF_HZ > 0) || CONFIG_ADC_IIR_FILTER_ENABLE
46#error "Transport build forbids windowing/top-K/cutoff/IIR filtering"
47#endif
48#endif
49
50#define CHANNELS 2
51#define SAMPLE_MAX 4096
52
53#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
54
55#define SPIKE_DB_THRESHOLD 20.0f
56#define SPIKE_RATIO_THRESHOLD 8.0f
57#define SPIKE_MIN_AVG_LINEAR 1e-7f
58
59static float *ch0 = NULL;
60static float *ch1 = NULL;
61static float *win = NULL;
62
63static adc_cali_handle_t cali_handle;
64
65typedef struct __attribute__((packed)) {
66 uint64_t t_ms;
67 uint32_t sps;
68 uint32_t fft;
69 char label[4];
70 float data[FFT_SIZE];
72
73#define FRAME_MAGIC_0 0xF0 // "FOOD"
74#define FRAME_MAGIC_1 0x0D // "FOOD"
75#define FRAME_MAGIC_2 0xF0 // "FOOD"
76#define FRAME_MAGIC_3 0x0D // "FOOD"
77#define FRAME_VERSION 0x02 // Version 2: uint64_t timestamp
78#define FRAME_HEADER_SIZE 9u // 4 magic + 1 version + 2 seq + 2 payload length
79#define FRAME_PAYLOAD_SIZE (sizeof(frame_payload_t)) // Real data
80#define FRAME_TOTAL_SIZE \
81 (FRAME_HEADER_SIZE + FRAME_PAYLOAD_SIZE + 4u) // +4 for CRC32
82_Static_assert(FRAME_PAYLOAD_SIZE <= UINT16_MAX,
83 "payload length must fit in uint16");
84_Static_assert((FRAME_PAYLOAD_SIZE % 4u) == 0u,
85 "payload must stay 32-bit aligned");
86
87typedef struct {
90
91static QueueHandle_t g_frameq;
92static uint16_t g_seq;
94static TaskHandle_t g_cdc_task_handle;
95static TaskHandle_t g_adc_task_handle;
96
97#if CONFIG_STATUS_NEOPIXEL_ENABLE
98static led_strip_handle_t g_led_strip;
99static SemaphoreHandle_t g_led_lock;
100#endif
101
102// Task stack size: 4096 words (~16 KB). CDC TX uses small locals and no large
103// stack arrays; 4K words leaves comfortable headroom for TinyUSB calls.
104#define CDC_TX_TASK_STACK_SIZE 4096
105// FFT producer task stack size: 8192 words (~32 KB). FFT math runs in-place on
106// globals; stack usage is modest but we keep extra headroom for IDF/DSP calls.
107#define ADC_TASK_STACK_SIZE 8192
108
109static inline void write_u16_le(uint8_t *dst, uint16_t value) {
110 dst[0] = (uint8_t)(value & 0xFFu);
111 dst[1] = (uint8_t)((value >> 8) & 0xFFu);
112}
113
114static inline void write_u32_le(uint8_t *dst, uint32_t value) {
115 dst[0] = (uint8_t)(value & 0xFFu);
116 dst[1] = (uint8_t)((value >> 8) & 0xFFu);
117 dst[2] = (uint8_t)((value >> 16) & 0xFFu);
118 dst[3] = (uint8_t)((value >> 24) & 0xFFu);
119}
120
121static inline void write_u64_le(uint8_t *dst, uint64_t value) {
122 write_u32_le(dst, (uint32_t)(value & 0xFFFFFFFFu));
123 write_u32_le(dst + 4, (uint32_t)((value >> 32) & 0xFFFFFFFFu));
124}
125
126static uint32_t frame_crc32(const uint8_t *data, size_t len) {
127 return esp_crc32_le(0, data, len);
128}
129
130static void build_frame(frame_wire_t *out, const float *data,
131 const char label[4], uint64_t t_ms, uint32_t sps,
132 uint16_t seq) {
133 uint8_t *dst = out->bytes;
134 dst[0] = FRAME_MAGIC_0;
135 dst[1] = FRAME_MAGIC_1;
136 dst[2] = FRAME_MAGIC_2;
137 dst[3] = FRAME_MAGIC_3;
138 dst[4] = FRAME_VERSION;
139 write_u16_le(dst + 5, seq);
140 write_u16_le(dst + 7, (uint16_t)FRAME_PAYLOAD_SIZE);
141
142 size_t off = FRAME_HEADER_SIZE;
143 write_u64_le(dst + off, t_ms);
144 off += sizeof(uint64_t);
145 write_u32_le(dst + off, sps);
146 off += sizeof(uint32_t);
147 write_u32_le(dst + off, FFT_SIZE);
148 off += sizeof(uint32_t);
149 memcpy(dst + off, label, 4);
150 off += 4;
151 memcpy(dst + off, data, sizeof(float) * FFT_SIZE);
152 off += sizeof(float) * FFT_SIZE;
153
154 uint32_t crc = frame_crc32(dst, FRAME_HEADER_SIZE + FRAME_PAYLOAD_SIZE);
155 write_u32_le(dst + off, crc);
156}
157
158#if CONFIG_DIAG_CDC_ENABLE
159typedef struct {
160 uint64_t total_bytes_sent; /* lifetime */
161 uint32_t window_bytes; /* bytes in current window */
162 uint64_t window_start_ms;
163 uint32_t wa_min;
164 uint32_t wa_max;
165 uint64_t wa_sum;
166 uint32_t wa_count;
167} diag_cdc_t;
168static diag_cdc_t s_diag_cdc;
169
170static void diag_cdc_reset_window(uint64_t now_ms)
171{
172 s_diag_cdc.window_bytes = 0;
173 s_diag_cdc.window_start_ms = now_ms;
174 s_diag_cdc.wa_min = UINT32_MAX;
175 s_diag_cdc.wa_max = 0;
176 s_diag_cdc.wa_sum = 0;
177 s_diag_cdc.wa_count = 0;
178}
179
180static void diag_cdc_sample_wa(uint32_t v)
181{
182 if (v < s_diag_cdc.wa_min) s_diag_cdc.wa_min = v;
183 if (v > s_diag_cdc.wa_max) s_diag_cdc.wa_max = v;
184 s_diag_cdc.wa_sum += v;
185 s_diag_cdc.wa_count++;
186}
187
188static void diag_cdc_on_queued(size_t q)
189{
190 s_diag_cdc.total_bytes_sent += q;
191 s_diag_cdc.window_bytes += (uint32_t)q;
192}
193
194static void diag_cdc_maybe_log(uint64_t now_ms)
195{
196 const uint64_t interval_ms = (uint64_t)CONFIG_DIAG_CDC_LOG_INTERVAL_S * 1000ULL;
197 if (now_ms < s_diag_cdc.window_start_ms + interval_ms) return;
198
199 uint64_t elapsed = now_ms - s_diag_cdc.window_start_ms;
200 float bps = elapsed ? (s_diag_cdc.window_bytes * 1000.0f) / (float)elapsed : 0.0f;
201 uint32_t wa_avg = s_diag_cdc.wa_count ? (uint32_t)(s_diag_cdc.wa_sum / s_diag_cdc.wa_count) : 0;
202
203 ESP_LOGI(TAG, "CDC diag: bytes_sec=%.1f B/s window=%llums total=%llu bytes, wa[min/avg/max]=%u/%u/%u count=%u",
204 bps, (unsigned long long)elapsed,
205 (unsigned long long)s_diag_cdc.total_bytes_sent,
206 (unsigned)s_diag_cdc.wa_min == UINT32_MAX ? 0 : (unsigned)s_diag_cdc.wa_min,
207 (unsigned)wa_avg,
208 (unsigned)s_diag_cdc.wa_max,
209 (unsigned)s_diag_cdc.wa_count);
210
211 /* reset window */
212 diag_cdc_reset_window(now_ms);
213}
214#endif /* CONFIG_DIAG_CDC_ENABLE */
215
216/* Helper: bounded retry flush with small exponential backoff.
217 * Returns true on success, false if all retries failed or port disconnected.
218 * This avoids tight-loop logging from tinyusb when the host is transiently
219 * unable to accept data.
220 */
221static bool safe_cdcacm_write_flush(tinyusb_cdcacm_itf_t port, int max_retries)
222{
223 if (!tud_cdc_n_connected((uint8_t)port)) {
224 return false;
225 }
226
227 const int base_delay_ms = 100;
228 uint64_t now_ms = 0;
229 static uint64_t last_flush_log_ms = 0;
230
231 for (int attempt = 0; attempt < max_retries; ++attempt) {
232 /* Increase flush timeout to 1000ms to allow large buffers (32KB) to drain
233 * at minimum USB speeds (335KB/s -> ~96ms), with margin for host latency.
234 */
235 esp_err_t err = tinyusb_cdcacm_write_flush(port, 1000);
236 if (err == ESP_OK) {
237 return true;
238 }
239 if (!tud_cdc_n_connected((uint8_t)port)) {
240 return false;
241 }
242 vTaskDelay(pdMS_TO_TICKS(base_delay_ms << attempt));
243 }
244
245 /* Rate-limit the warning to once per second to avoid spam. */
246 now_ms = esp_timer_get_time() / 1000ULL;
247 if (now_ms > last_flush_log_ms + 1000ULL) {
248 ESP_LOGW(TAG, "CDC flush failed after %d retries", max_retries);
249 last_flush_log_ms = now_ms;
250 }
251 return false;
252}
253
254static void cdc_tx_task(void *arg) {
255#if 1
256 static frame_wire_t frm;
257
258 const size_t max_chunk = 512;
259 const uint32_t wait_step_ms = 2;
260 /* Increase timeout significantly for large FFT sizes to prevent frame drops
261 * that cause stream desynchronization. For FFT=4096, frame interval is ~100ms;
262 * allow up to 10 seconds for host processing/backpressure.
263 */
264 const uint32_t wait_budget_ms = 10000;
265
266
267#if CONFIG_DIAG_CDC_ENABLE
268 /* initialize diagnostic window at task start */
269 diag_cdc_reset_window(esp_timer_get_time() / 1000ULL);
270 uint64_t last_stack_log_ms = esp_timer_get_time() / 1000ULL;
271#endif
272 while (1) {
273 if (xQueueReceive(g_frameq, &frm, portMAX_DELAY) == pdTRUE) {
275 !tud_cdc_n_connected((uint8_t)g_data_port)) {
276 /* Host not connected: sleep longer to avoid starving idle/other tasks
277 */
278 vTaskDelay(pdMS_TO_TICKS(100));
279 continue;
280 }
281
282 /* Send in bounded chunks, waiting briefly for space if needed. */
283 size_t sent = 0;
284 uint32_t waited_ms = 0;
285 while (sent < FRAME_TOTAL_SIZE) {
286 if (!tud_cdc_n_connected((uint8_t)g_data_port)) {
287 break;
288 }
289
290 size_t chunk = FRAME_TOTAL_SIZE - sent;
291 if (chunk > max_chunk) {
292 chunk = max_chunk;
293 }
294
295 uint32_t wa = tud_cdc_n_write_available((uint8_t)g_data_port);
296#if CONFIG_DIAG_CDC_ENABLE && CONFIG_DIAG_CDC_SAMPLE_WRITE_AVAILABLE
297 diag_cdc_sample_wa(wa);
298#endif
299 if (wa < chunk) {
300 if (waited_ms >= wait_budget_ms) {
301 ESP_LOGW(TAG, "CDC TX backpressure (%u ms), dropping frame",
302 (unsigned)waited_ms);
303 break;
304 }
305 vTaskDelay(pdMS_TO_TICKS(wait_step_ms));
306 waited_ms += wait_step_ms;
307 continue;
308 }
309
310 size_t queued =
311 tinyusb_cdcacm_write_queue(g_data_port, frm.bytes + sent, chunk);
312 if (queued == 0) {
313 if (waited_ms >= wait_budget_ms) {
314 ESP_LOGW(TAG, "CDC TX stalled (%u ms), dropping frame",
315 (unsigned)waited_ms);
316 break;
317 }
318 vTaskDelay(pdMS_TO_TICKS(wait_step_ms));
319 waited_ms += wait_step_ms;
320 continue;
321 }
322#if CONFIG_DIAG_CDC_ENABLE
323 diag_cdc_on_queued(queued);
324#endif
325 sent += queued;
326 }
327
328 if (sent < FRAME_TOTAL_SIZE) {
329 ESP_LOGW(TAG, "CDC send dropped %u bytes",
330 (unsigned)(FRAME_TOTAL_SIZE - sent));
331#if CONFIG_DIAG_CDC_ENABLE
332 /* Report diagnostics even when a frame is dropped. */
333 uint64_t now_ms = esp_timer_get_time() / 1000ULL;
334 diag_cdc_maybe_log(now_ms);
335#endif
336 continue;
337 }
338
340 ESP_LOGD(TAG, "CDC flush timeout (non-fatal)");
341 }
342#if CONFIG_STATUS_NEOPIXEL_ENABLE
343 if (g_led_strip) {
344 if (!g_led_lock ||
345 xSemaphoreTake(g_led_lock, pdMS_TO_TICKS(10)) == pdTRUE) {
346 uint8_t b = (uint8_t)CONFIG_STATUS_NEOPIXEL_BRIGHTNESS;
347 ESP_ERROR_CHECK(led_strip_set_pixel(g_led_strip, 0, 0, b, 0));
348 ESP_ERROR_CHECK(led_strip_refresh(g_led_strip));
349 if (g_led_lock) {
350 xSemaphoreGive(g_led_lock);
351 }
352 }
353 }
354#endif
355#if CONFIG_DIAG_CDC_ENABLE
356 /* periodic summary log (non-blocking) */
357 uint64_t now_ms = esp_timer_get_time() / 1000ULL;
358 diag_cdc_maybe_log(now_ms);
359
360 if (now_ms > last_stack_log_ms + 5000ULL) {
361 UBaseType_t cdc_hwm = uxTaskGetStackHighWaterMark(g_cdc_task_handle);
362 UBaseType_t adc_hwm = uxTaskGetStackHighWaterMark(g_adc_task_handle);
363 ESP_LOGI(TAG, "Stack HWM: cdc_tx=%lu words, adc_fft=%lu words",
364 (unsigned long)cdc_hwm, (unsigned long)adc_hwm);
365 last_stack_log_ms = now_ms;
366 }
367#endif
368 }
369 }
370}
371#endif
372
373#if CONFIG_STATUS_NEOPIXEL_ENABLE
374static void status_led_init(void) {
375 g_led_lock = xSemaphoreCreateMutex();
376 led_strip_config_t strip_config = {
377 .strip_gpio_num = CONFIG_STATUS_NEOPIXEL_GPIO,
378 .max_leds = 1,
379 .led_pixel_format = LED_PIXEL_FORMAT_GRB,
380 .led_model = LED_MODEL_WS2812,
381 .flags.invert_out = false,
382 };
383 led_strip_rmt_config_t rmt_config = {
384 .clk_src = RMT_CLK_SRC_DEFAULT,
385 .resolution_hz = LED_STRIP_RMT_RES_HZ,
386 .flags.with_dma = false,
387 };
388 ESP_ERROR_CHECK(
389 led_strip_new_rmt_device(&strip_config, &rmt_config, &g_led_strip));
390}
391
392static void status_led_set(uint8_t r, uint8_t g, uint8_t b) {
393 if (!g_led_strip) {
394 return;
395 }
396 if (g_led_lock && xSemaphoreTake(g_led_lock, pdMS_TO_TICKS(10)) != pdTRUE) {
397 return;
398 }
399 uint8_t scale = (uint8_t)CONFIG_STATUS_NEOPIXEL_BRIGHTNESS;
400 uint8_t r_s = (uint8_t)((r * scale) / 255u);
401 uint8_t g_s = (uint8_t)((g * scale) / 255u);
402 uint8_t b_s = (uint8_t)((b * scale) / 255u);
403 ESP_ERROR_CHECK(led_strip_set_pixel(g_led_strip, 0, r_s, g_s, b_s));
404 ESP_ERROR_CHECK(led_strip_refresh(g_led_strip));
405 if (g_led_lock) {
406 xSemaphoreGive(g_led_lock);
407 }
408}
409#endif
410
411bool adc_calibration_init(adc_unit_t unit, adc_atten_t atten) {
412 adc_cali_curve_fitting_config_t cali_config = {
413 .unit_id = unit,
414 .atten = atten,
415 .bitwidth = ADC_BITWIDTH_12,
416 };
417 if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) ==
418 ESP_OK) {
419 ESP_LOGI(TAG, "ADC calibration ready (curve fitting)");
420 return true;
421 }
422 ESP_LOGW(TAG, "ADC calibration not available; raw counts will be used");
423 return false;
424}
425
426static inline float sample_to_volts(uint16_t raw, bool calibrated) {
427 if (calibrated) {
428 int mv = 0;
429 if (adc_cali_raw_to_voltage(cali_handle, raw, &mv) == ESP_OK) {
430 return (float)mv / 1000.0f;
431 }
432 }
433 return (float)raw; // fallback raw counts
434}
435
436static inline float sample_to_centered(uint16_t raw, bool calibrated,
437 int midpoint_mv) {
438 if (calibrated) {
439 int mv = 0;
440 if (adc_cali_raw_to_voltage(cali_handle, raw, &mv) == ESP_OK) {
441 return ((float)(mv - midpoint_mv)) / 1000.0f;
442 }
443 }
444 return (float)raw - 2048.0f; // 12-bit midpoint
445}
446
447OPTIMIZE_O2 static void apply_window(float *buf) {
448 for (int i = 0; i < FFT_SIZE; i++) {
449 buf[i] *= win[i];
450 }
451}
452
453OPTIMIZE_O2 static void perform_fft(float *buf) {
454 dsps_fft4r_fc32(buf, FFT_SIZE >> 1);
455 dsps_bit_rev4r_fc32(buf, FFT_SIZE >> 1);
456 dsps_cplx2real_fc32(buf, FFT_SIZE >> 1);
457}
458
459OPTIMIZE_O2 static void apply_complex_cutoff(float *buf, uint32_t sps) {
460#if CONFIG_OUTPUT_CUTOFF_HZ > 0
461 uint32_t cutoff_hz = (uint32_t)CONFIG_OUTPUT_CUTOFF_HZ;
462 uint32_t max_bin = (cutoff_hz * (uint64_t)FFT_SIZE) / sps;
463 if (max_bin >= FFT_SIZE / 2) {
464 return;
465 }
466 if (max_bin < FFT_SIZE / 2) {
467 buf[1] = 0.0f; // Nyquist packed at index 1
468 }
469 for (uint32_t k = max_bin + 1; k < FFT_SIZE / 2; k++) {
470 buf[2 * k] = 0.0f;
471 buf[2 * k + 1] = 0.0f;
472 }
473#endif
474}
475
476static adc_atten_t cfg_atten(void) {
477#if CONFIG_ADC_ATTEN_DB_0
478 return ADC_ATTEN_DB_0;
479#elif CONFIG_ADC_ATTEN_DB_2_5
480 return ADC_ATTEN_DB_2_5;
481#elif CONFIG_ADC_ATTEN_DB_6
482 return ADC_ATTEN_DB_6;
483#else
484 return ADC_ATTEN_DB_12;
485#endif
486}
487
488static void adc_fft_task(void *arg) {
489 adc_atten_t atten = cfg_atten();
490 bool calibrated = adc_calibration_init(ADC_UNIT_1, atten);
491 int midpoint_mv = 0;
492 if (calibrated) {
493 (void)adc_cali_raw_to_voltage(cali_handle, 2048, &midpoint_mv);
494 }
495
496 /* ADC decode diagnostics: validates that DMA words are parsed as expected
497 * (unit/channel/bitwidth). If ADC output format mismatches parsing, these
498 * counters will look obviously wrong (e.g., few accepted samples, many
499 * skipped/other channels).
500 */
501 uint64_t last_adc_diag_ms = esp_timer_get_time() / 1000ULL;
502 uint32_t diag_reads = 0;
503 uint32_t diag_out_bytes = 0;
504 uint32_t diag_accept_ch0 = 0;
505 uint32_t diag_accept_ch1 = 0;
506 uint32_t diag_skip_unit = 0;
507 uint32_t diag_other_chan = 0;
508
509 adc_continuous_handle_t adc_handle = NULL;
510 adc_continuous_handle_cfg_t handle_cfg = {
511 .max_store_buf_size =
512 FFT_SIZE * CHANNELS * sizeof(adc_digi_output_data_t),
513 .conv_frame_size = 512,
514 };
515 ESP_ERROR_CHECK(adc_continuous_new_handle(&handle_cfg, &adc_handle));
516
517 uint32_t sample_rate = ADC_SPS;
518#if defined(CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH)
519 if (sample_rate > CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH) {
520 ESP_LOGW(TAG, "ADC_SPS=%u above SOC max %u; clamping", sample_rate,
521 (unsigned)CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH);
522 sample_rate = CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH;
523 }
524#endif
525#if defined(CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW)
526 if (sample_rate < CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW) {
527 ESP_LOGW(TAG, "ADC_SPS=%u below SOC min %u; clamping", sample_rate,
528 (unsigned)CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW);
529 sample_rate = CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW;
530 }
531#endif
532
533 /* Per-channel sampling rate: adc_continuous divides sample_freq_hz by the
534 * pattern length. With two channels, each gets half the configured rate. */
535 const uint32_t per_channel_sps = sample_rate / CHANNELS;
536
537 adc_digi_pattern_config_t pattern[CHANNELS] = {0};
538 pattern[0].atten = atten;
539 pattern[0].channel = CONFIG_ADC_CH0;
540 pattern[0].unit = ADC_UNIT_1;
541 pattern[0].bit_width = ADC_BITWIDTH_12;
542 pattern[1].atten = atten;
543 pattern[1].channel = CONFIG_ADC_CH1;
544 pattern[1].unit = ADC_UNIT_1;
545 pattern[1].bit_width = ADC_BITWIDTH_12;
546
547 adc_continuous_config_t dig_cfg = {
548 .sample_freq_hz = sample_rate,
549 .conv_mode = ADC_CONV_SINGLE_UNIT_1,
550 /* Must match adc_digi_output_data_t parsing below (p->type2.*). */
551 .format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
552 .pattern_num = CHANNELS,
553 .adc_pattern = pattern,
554 };
555 ESP_ERROR_CHECK(adc_continuous_config(adc_handle, &dig_cfg));
556
557#if CONFIG_ADC_IIR_FILTER_ENABLE
558 adc_digi_iir_filter_t coeff = ADC_DIGI_IIR_FILTER_COEFF_2;
559#if CONFIG_ADC_IIR_FILTER_COEFF_4
560 coeff = ADC_DIGI_IIR_FILTER_COEFF_4;
561#elif CONFIG_ADC_IIR_FILTER_COEFF_8
562 coeff = ADC_DIGI_IIR_FILTER_COEFF_8;
563#elif CONFIG_ADC_IIR_FILTER_COEFF_16
564 coeff = ADC_DIGI_IIR_FILTER_COEFF_16;
565#elif CONFIG_ADC_IIR_FILTER_COEFF_64
566 coeff = ADC_DIGI_IIR_FILTER_COEFF_64;
567#endif
568 adc_continuous_iir_filter_config_t filter_cfg = {
569 .unit = ADC_UNIT_1,
570 .coeff = coeff,
571 };
572 adc_iir_filter_handle_t iir_handle = NULL;
573 ESP_ERROR_CHECK(
574 adc_new_continuous_iir_filter(adc_handle, &filter_cfg, &iir_handle));
575 ESP_ERROR_CHECK(adc_continuous_iir_filter_enable(iir_handle));
576 ESP_LOGI(TAG, "ADC IIR Filter enabled");
577#endif
578
579 ESP_ERROR_CHECK(adc_continuous_start(adc_handle));
580
581 ESP_LOGI(TAG,
582 "Sampling ADC1 CH%d (GPIO%d) and CH%d (GPIO%d) at %u sps/ch, FFT %d",
583 CONFIG_ADC_CH0, CONFIG_ADC_CH0 + 1, CONFIG_ADC_CH1,
584 CONFIG_ADC_CH1 + 1, (unsigned)per_channel_sps, FFT_SIZE);
585
586 // Reduce DMA buffer size to save Internal RAM; we loop to fill the FFT buffer anyway.
587 const size_t read_len = 2048 * sizeof(adc_digi_output_data_t);
588 uint8_t *raw = heap_caps_aligned_alloc(16, read_len,
589 MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
590 if (!raw) {
591 raw = heap_caps_aligned_alloc(16, read_len, MALLOC_CAP_DMA);
592 }
593 if (!raw) {
594 ESP_LOGE(TAG, "Failed to reserve DMA buffer (%zu bytes)", read_len);
595 vTaskDelete(NULL);
596 return;
597 }
598
599 static frame_wire_t frame;
600 while (1) {
601#if CONFIG_STATUS_NEOPIXEL_ENABLE
602 status_led_set(0, 0, 255);
603#endif
604 int count0 = 0;
605 int count1 = 0;
606 float sum0 = 0.0f;
607 float sum1 = 0.0f;
608 while (count0 < FFT_SIZE || count1 < FFT_SIZE) {
609 uint32_t out_len = 0;
610 esp_err_t ret = adc_continuous_read(adc_handle, raw, read_len, &out_len,
611 pdMS_TO_TICKS(100));
612 if (ret == ESP_ERR_TIMEOUT) {
613 continue;
614 }
615 if (ret != ESP_OK) {
616 ESP_LOGW(TAG, "adc_continuous_read returned %s", esp_err_to_name(ret));
617 vTaskDelay(pdMS_TO_TICKS(1));
618 continue;
619 }
620
621 /* Per-read diagnostics. */
622 diag_reads++;
623 diag_out_bytes += out_len;
624
625 for (uint32_t i = 0; i + sizeof(adc_digi_output_data_t) <= out_len;
626 i += sizeof(adc_digi_output_data_t)) {
627 adc_digi_output_data_t *p = (adc_digi_output_data_t *)&raw[i];
628 if (p->type2.unit != ADC_UNIT_1) {
629 diag_skip_unit++;
630 continue;
631 }
632 if (p->type2.channel == CONFIG_ADC_CH0 && count0 < FFT_SIZE) {
633 float val = sample_to_centered(p->type2.data, calibrated, midpoint_mv);
634 ch0[count0++] = val;
635 sum0 += val;
636 diag_accept_ch0++;
637 } else if (p->type2.channel == CONFIG_ADC_CH1 && count1 < FFT_SIZE) {
638 float val = sample_to_centered(p->type2.data, calibrated, midpoint_mv);
639 ch1[count1++] = val;
640 sum1 += val;
641 diag_accept_ch1++;
642 } else {
643 diag_other_chan++;
644 }
645 }
646
647 /* Rate-limit to avoid log spam. */
648 uint64_t now_ms = esp_timer_get_time() / 1000ULL;
649 if (now_ms > last_adc_diag_ms + 2000ULL) {
650 ESP_LOGI(TAG,
651 "ADC diag (2s): reads=%u out=%uB accept[ch0/ch1]=%u/%u skip_unit=%u other_chan=%u",
652 (unsigned)diag_reads, (unsigned)diag_out_bytes,
653 (unsigned)diag_accept_ch0, (unsigned)diag_accept_ch1,
654 (unsigned)diag_skip_unit, (unsigned)diag_other_chan);
655 diag_reads = 0;
656 diag_out_bytes = 0;
657 diag_accept_ch0 = 0;
658 diag_accept_ch1 = 0;
659 diag_skip_unit = 0;
660 diag_other_chan = 0;
661 last_adc_diag_ms = now_ms;
662 }
663 }
664
665 /* Dynamic DC bias removal: subtract current block mean */
666 float mean0 = (float)(sum0 / FFT_SIZE);
667 float mean1 = (float)(sum1 / FFT_SIZE);
668 for (int i = 0; i < FFT_SIZE; i++) {
669 ch0[i] -= mean0;
670 ch1[i] -= mean1;
671 }
672
673#if CONFIG_ADC_ENABLE_WINDOW
676#endif
679
680 // apply_complex_cutoff(ch0, per_channel_sps);
681 // apply_complex_cutoff(ch1, per_channel_sps);
682
683 uint64_t now_ms = esp_timer_get_time() / 1000ULL;
684
685 build_frame(&frame, ch0, "ch0", now_ms, per_channel_sps, g_seq++);
686 if (xQueueSend(g_frameq, &frame, portMAX_DELAY) != pdTRUE) {
687 ESP_LOGW(TAG, "Frame queue send failed (ch0)");
688 }
689
690 build_frame(&frame, ch1, "ch1", now_ms, per_channel_sps, g_seq++);
691 if (xQueueSend(g_frameq, &frame, portMAX_DELAY) != pdTRUE) {
692 ESP_LOGW(TAG, "Frame queue send failed (ch1)");
693 }
694 }
695}
696
697#if CONFIG_ENABLE_I2S_WAVE_GEN
698static void sweep_task(void *arg) {
699 const float f_start = 33.0f;
700 const float f_end = 3333.0f;
701 const float duration_sec = 9.0f;
702 const int update_ms = 50;
703
704 // Linear sweep step
705 const float step = (f_end - f_start) / (duration_sec * (1000.0f / update_ms));
706
707 float freq = f_start;
708 while (1) {
709 wave_gen_set_freq(freq);
710 vTaskDelay(pdMS_TO_TICKS(update_ms));
711
712 freq += step;
713 if (freq > f_end) {
714 freq = f_start;
715 }
716 }
717}
718#endif
719
720void app_main(void) {
721#if CONFIG_STATUS_NEOPIXEL_ENABLE
722 status_led_init();
723 status_led_set(255, 0, 0);
724#endif
725 tinyusb_config_t tusb_cfg = {
726 .device_descriptor = NULL,
727 .string_descriptor = NULL,
728 .external_phy = false,
729 };
730 ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
731
732#if CONFIG_TINYUSB_CDC_COUNT > 1
734#else
736#endif
737
738 tinyusb_config_cdcacm_t cdc_cfg_data = {
739 .usb_dev = TINYUSB_USBDEV_0,
740 .cdc_port = g_data_port,
741 .rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
742 .callback_rx = NULL,
743 .callback_rx_wanted_char = NULL,
744 .callback_line_state_changed = NULL,
745 .callback_line_coding_changed = NULL,
746 };
747 ESP_ERROR_CHECK(tusb_cdc_acm_init(&cdc_cfg_data));
748
749
750#if CONFIG_ENABLE_I2S_WAVE_GEN
751 // Initialize Wave Gen
752 // Using GPIO 15-18 to avoid VDD_SPI (GPIO 11) and ADC1 interference
753 wave_gen_config_t wg_cfg = {
754 .mck_io_num = -1, // SCK disconnected (PCM5102 internal generation)
755 .bck_io_num = 16,
756 .ws_io_num = 17,
757 .data_out_num = 18,
758 .sample_rate = 44100,
759 };
760 ESP_ERROR_CHECK(wave_gen_init(&wg_cfg));
761 // wave_gen_set_freq(33.0f); // Controlled by sweep_task
764 ESP_ERROR_CHECK(wave_gen_start());
765#endif
766
767#if CONFIG_ENABLE_SDM_WAVE_GEN
768 ESP_ERROR_CHECK(wave_gen_sdm_start());
769#endif
770
771 size_t fft_mem_sz = FFT_SIZE * sizeof(float);
772 ch0 = heap_caps_aligned_alloc(16, fft_mem_sz, MALLOC_CAP_SPIRAM);
773 ch1 = heap_caps_aligned_alloc(16, fft_mem_sz, MALLOC_CAP_SPIRAM);
774 win = heap_caps_aligned_alloc(16, fft_mem_sz, MALLOC_CAP_SPIRAM);
775
776 if (!ch0 || !ch1 || !win) {
777 ESP_LOGE(TAG, "Failed to allocate FFT buffers");
778 abort();
779 }
780
781 /* Initialize DSP before any task can call FFT/window code. */
782 ESP_ERROR_CHECK(dsps_fft4r_init_fc32(NULL, FFT_SIZE >> 1));
783#if CONFIG_ADC_ENABLE_WINDOW
784#if CONFIG_ADC_WINDOW_HANN
786#elif CONFIG_ADC_WINDOW_BLACKMAN
788#elif CONFIG_ADC_WINDOW_BLACKMAN_HARRIS
790#elif CONFIG_ADC_WINDOW_BLACKMAN_NUTTALL
792#elif CONFIG_ADC_WINDOW_NUTTALL
794#elif CONFIG_ADC_WINDOW_FLAT_TOP
796#else
797 for (int i = 0; i < FFT_SIZE; i++) {
798 win[i] = 1.0f;
799 }
800#endif
801#endif
802
803
804#if CONFIG_ENABLE_I2S_WAVE_GEN
805 xTaskCreate(sweep_task, "sweep", 2048, NULL, 5, NULL);
806#endif
807
808 g_frameq = xQueueCreate(
809 2,
810 sizeof(frame_wire_t));
811 assert(g_frameq);
812
813 const BaseType_t tusb_core = 0;
814#if CONFIG_FREERTOS_UNICORE
815 const BaseType_t adc_core = 0;
816#else
817 const BaseType_t adc_core = 1;
818#endif
819
820 xTaskCreatePinnedToCore(
822 "cdc_tx",
824 NULL,
825 8,
827 tusb_core);
828
829
830
831
832 xTaskCreatePinnedToCore(
834 "adc_fft",
836 NULL,
837 8,
839 adc_core);
840
841 xTaskCreatePinnedToCore(
843 "adc_fft",
845 NULL,
846 8,
848 adc_core
849 );
850
851 xTaskCreatePinnedToCore(adc_fft_task,"adc_fft",ADC_TASK_STACK_SIZE,NULL,8,&g_adc_task_handle,adc_core);
852
853
854 // Tasks run indefinitely; app_main can return.
855}
856
857
#define dsps_bit_rev4r_fc32
Definition dsps_fft4r.h:184
#define dsps_cplx2real_fc32
Definition dsps_fft4r.h:185
#define dsps_fft4r_fc32
Definition dsps_fft4r.h:182
esp_err_t dsps_fft4r_init_fc32(float *fft_table_buff, int max_fft_size)
init fft tables
void dsps_wind_blackman_f32(float *window, int len)
Blackman window.
void dsps_wind_blackman_harris_f32(float *window, int len)
Blackman-Harris window.
void dsps_wind_blackman_nuttall_f32(float *window, int len)
Blackman-Nuttall window.
void dsps_wind_flat_top_f32(float *window, int len)
Flat-Top window.
void dsps_wind_hann_f32(float *window, int len)
Hann window.
void dsps_wind_nuttall_f32(float *window, int len)
Nuttall window.
int esp_err_t
Definition esp_err.h:21
#define ESP_OK
Definition esp_err.h:23
#define ESP_LOGD
Definition esp_log.h:22
esp_err_t led_strip_refresh(led_strip_handle_t strip)
Refresh memory colors to LEDs.
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
Set RGB for a specific pixel.
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
Create LED strip based on RMT TX channel.
@ LED_MODEL_WS2812
@ LED_PIXEL_FORMAT_GRB
struct led_strip_t * led_strip_handle_t
LED strip handle.
static void apply_complex_cutoff(float *buf, uint32_t sps)
Definition main/main.c:459
#define FRAME_TOTAL_SIZE
Definition main/main.c:80
#define FRAME_MAGIC_2
Definition main/main.c:75
frame_payload_t
Definition main/main.c:71
static QueueHandle_t g_frameq
Definition main/main.c:91
#define CHANNELS
Definition main/main.c:50
static void write_u32_le(uint8_t *dst, uint32_t value)
Definition main/main.c:114
#define ADC_TASK_STACK_SIZE
Definition main/main.c:107
static float * ch0
Definition main/main.c:59
#define FRAME_PAYLOAD_SIZE
Definition main/main.c:79
static float sample_to_centered(uint16_t raw, bool calibrated, int midpoint_mv)
Definition main/main.c:436
#define FRAME_MAGIC_3
Definition main/main.c:76
static const char * TAG
Definition main/main.c:31
#define FRAME_VERSION
Definition main/main.c:77
#define CDC_TX_TASK_STACK_SIZE
Definition main/main.c:104
void app_main(void)
Definition main/main.c:720
#define FFT_SIZE
Definition main/main.c:39
#define FRAME_HEADER_SIZE
Definition main/main.c:78
static void write_u16_le(uint8_t *dst, uint16_t value)
Definition main/main.c:109
static float * win
Definition main/main.c:61
#define OPTIMIZE_O2
Definition main/main.c:36
static void build_frame(frame_wire_t *out, const float *data, const char label[4], uint64_t t_ms, uint32_t sps, uint16_t seq)
Definition main/main.c:130
static TaskHandle_t g_cdc_task_handle
Definition main/main.c:94
static void apply_window(float *buf)
Definition main/main.c:447
static adc_cali_handle_t cali_handle
Definition main/main.c:63
static void adc_fft_task(void *arg)
Definition main/main.c:488
bool adc_calibration_init(adc_unit_t unit, adc_atten_t atten)
Definition main/main.c:411
#define FRAME_MAGIC_1
Definition main/main.c:74
static tinyusb_cdcacm_itf_t g_data_port
Definition main/main.c:93
static void perform_fft(float *buf)
Definition main/main.c:453
struct __attribute__((packed))
Definition main/main.c:65
static float * ch1
Definition main/main.c:60
static void cdc_tx_task(void *arg)
Definition main/main.c:254
static bool safe_cdcacm_write_flush(tinyusb_cdcacm_itf_t port, int max_retries)
Definition main/main.c:221
static adc_atten_t cfg_atten(void)
Definition main/main.c:476
static float sample_to_volts(uint16_t raw, bool calibrated)
Definition main/main.c:426
static uint32_t frame_crc32(const uint8_t *data, size_t len)
Definition main/main.c:126
#define LED_STRIP_RMT_RES_HZ
Definition main/main.c:53
#define FRAME_MAGIC_0
Definition main/main.c:73
static TaskHandle_t g_adc_task_handle
Definition main/main.c:95
static void write_u64_le(uint8_t *dst, uint64_t value)
Definition main/main.c:121
#define ADC_SPS
Definition main/main.c:40
static uint16_t g_seq
Definition main/main.c:92
uint8_t bytes[(9u+(sizeof(frame_payload_t))+4u)]
Definition main/main.c:88
LED Strip Configuration.
LED Strip RMT specific configuration.
Configuration structure for CDC-ACM.
Configuration structure of the TinyUSB core.
Definition tinyusb.h:29
Configuration structure for Wave Generator.
Definition wave_gen.h:22
static float data[128 *2]
Definition test_fft2r.c:34
const int k
Definition test_mmult.c:18
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
This is an all-in-one helper function, including:
Definition tinyusb.c:32
@ TINYUSB_USBDEV_0
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size)
Write data to write buffer from a byte array.
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
Check if the ACM initialized.
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
Initialize CDC ACM. Initialization will be finished with the tud_cdc_line_state_cb callback.
tinyusb_cdcacm_itf_t
CDC ports available to setup.
@ TINYUSB_CDC_ACM_1
@ TINYUSB_CDC_ACM_0
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
Send all data from a write buffer. Use tinyusb_cdcacm_write_queue to add data to the buffer.
void wave_gen_set_type(wave_type_t type)
Set the waveform type.
Definition wave_gen.c:291
void wave_gen_set_freq(float freq_hz)
Set the frequency of the generated wave.
Definition wave_gen.c:284
esp_err_t wave_gen_init(const wave_gen_config_t *config)
Initialize the Wave Generator component.
Definition wave_gen.c:213
esp_err_t wave_gen_start(void)
Start the wave generation task.
Definition wave_gen.c:253
@ WAVE_TYPE_SINE
Definition wave_gen.h:13
esp_err_t wave_gen_sdm_start(void)
Start the SDM sine generator (uses CONFIG_SDM_* settings).
Definition wave_gen.c:303
void wave_gen_set_volume(float volume)
Set the output volume/amplitude.
Definition wave_gen.c:296