From d48bc335307d6164228a3f884d032cf476ab8a62 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Thu, 26 Feb 2026 11:53:52 +0100 Subject: [PATCH] sync --- firmware/CMakeLists.txt | 1 - firmware/src/audio.c | 385 ++++++++++++---------------------------- firmware/src/audio.h | 9 +- firmware/src/io.c | 2 +- firmware/src/main.c | 8 + firmware/src/protocol.c | 2 +- firmware/src/usb.c | 2 +- 7 files changed, 128 insertions(+), 281 deletions(-) diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 03145a9..63019d5 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -6,7 +6,6 @@ project(buzzer) target_sources(app PRIVATE src/main.c src/fs.c - src/audio.c src/io.c src/usb.c src/protocol.c diff --git a/firmware/src/audio.c b/firmware/src/audio.c index af5426c..b416293 100644 --- a/firmware/src/audio.c +++ b/firmware/src/audio.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -11,134 +10,21 @@ LOG_MODULE_REGISTER(audio, LOG_LEVEL_INF); +/* Slab für I2S. Keine weiteren Queues oder Threads nötig. */ K_MEM_SLAB_DEFINE(audio_slab, AUDIO_BLOCK_SIZE, AUDIO_BLOCK_COUNT, 4); -K_MSGQ_DEFINE(free_slab_msgq, sizeof(void *), AUDIO_BLOCK_COUNT, 4); -K_MSGQ_DEFINE(filled_slab_msgq, sizeof(void *), AUDIO_BLOCK_COUNT, 4); -K_SEM_DEFINE(audio_start_sem, 0, 1); // Buzzer -K_SEM_DEFINE(fs_sync_sem, 0, 1); // USB Sync +/* Message Queue für Play-Kommandos (Pfade zu Dateien, max 64 Zeichen) */ +K_MSGQ_DEFINE(audio_play_msgq, 64, 4, 4); + +/* Startup-Sicherung */ +K_SEM_DEFINE(audio_ready_sem, 0, 1); -/* Get the node identifier for the alias "audio-i2s" */ #define I2S_NODE DT_ALIAS(audio_i2s) - -/* Verify the node exists to avoid cryptic compiler errors */ #if !DT_NODE_EXISTS(I2S_NODE) #error "Audio I2S alias not defined in devicetree" #endif static const struct device *const i2s_dev = DEVICE_DT_GET(I2S_NODE); -static struct k_poll_event poll_events[3]; -static struct fs_file_t cached_file; -static bool cached_file_open; -static bool cached_file_eof; -static char cached_filename[64]; - -int get_random_file(const char *path, char *out_filename, size_t max_len); - -static int audio_prepare_random_file(void) -{ - int rc; - - if (cached_file_open) - { - fs_close(&cached_file); - cached_file_open = false; - } - - rc = get_random_file(AUDIO_PATH, cached_filename, sizeof(cached_filename)); - if (rc < 0) - { - LOG_ERR("No random file available in %s (%d)", AUDIO_PATH, rc); - return rc; - } - - fs_file_t_init(&cached_file); - rc = fs_open(&cached_file, cached_filename, FS_O_READ); - if (rc < 0) - { - LOG_ERR("Failed to open cache file %s (%d)", cached_filename, rc); - return rc; - } - - cached_file_open = true; - cached_file_eof = false; - LOG_DBG("Priming from file: %s", cached_filename); - - return 0; -} - -static int audio_fill_slab_from_cache(void *block) -{ - ssize_t bytes_read; - uint8_t *bytes = block; - - if (!cached_file_open || cached_file_eof) - { - return -ENODATA; - } - - bytes_read = fs_read(&cached_file, bytes, AUDIO_BLOCK_SIZE/2); - if (bytes_read < 0) - { - LOG_ERR("fs_read failed: %d", (int)bytes_read); - return (int)bytes_read; - } - - if (bytes_read == 0) - { - cached_file_eof = true; - return -ENODATA; - } - - if (bytes_read < AUDIO_BLOCK_SIZE) - { - memset(&bytes[bytes_read], 0, AUDIO_BLOCK_SIZE - bytes_read); - cached_file_eof = true; - } - - for (int i = AUDIO_BLOCK_SIZE/2 - 1; i >= 0; i--) - { - bytes[2*i + 1] = bytes[i]; - bytes[2*i] = bytes[i]; - } - - return 0; -} - -static void audio_prime_prefill(uint32_t target_blocks) -{ - uint32_t primed = 0; - - while (primed < target_blocks) - { - void *block; - - if (k_msgq_get(&free_slab_msgq, &block, K_MSEC(20)) != 0) - { - break; - } - - if (audio_fill_slab_from_cache(block) == 0) - { - if (k_msgq_put(&filled_slab_msgq, &block, K_NO_WAIT) == 0) - { - primed++; - } - else - { - k_mem_slab_free(&audio_slab, &block); - break; - } - } - else - { - k_mem_slab_free(&audio_slab, &block); - break; - } - } - - LOG_DBG("Prefilled %u/%u slabs", primed, target_blocks); -} int get_random_file(const char *path, char *out_filename, size_t max_len) { @@ -148,7 +34,6 @@ int get_random_file(const char *path, char *out_filename, size_t max_len) int rc; fs_dir_t_init(&dirp); - rc = fs_opendir(&dirp, path); if (rc < 0) return rc; @@ -156,17 +41,14 @@ int get_random_file(const char *path, char *out_filename, size_t max_len) while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0') { if (entry.type == FS_DIR_ENTRY_FILE) - { file_count++; - } } fs_closedir(&dirp); if (file_count == 0) return -ENOENT; - uint32_t random_index = sys_rand32_get() % file_count; - + uint32_t random_index = k_cycle_get_32() % file_count; rc = fs_opendir(&dirp, path); if (rc < 0) return rc; @@ -184,184 +66,135 @@ int get_random_file(const char *path, char *out_filename, size_t max_len) current_index++; } } - fs_closedir(&dirp); return 0; } +void audio_system_ready(void) +{ + k_sem_give(&audio_ready_sem); +} + void audio_thread(void *arg1, void *arg2, void *arg3) { LOG_DBG("Audio thread started"); - int rc; + k_sem_take(&audio_ready_sem, K_FOREVER); - k_poll_event_init(&poll_events[0], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &audio_start_sem); - k_poll_event_init(&poll_events[1], K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &fs_sync_sem); - k_poll_event_init(&poll_events[2], K_POLL_TYPE_MSGQ_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &free_slab_msgq); + char filename[64]; while (1) { - bool is_playing = false; - io_status(false); - uint32_t queued = 0; + /* 1. Auf Play-Kommando warten */ + k_msgq_get(&audio_play_msgq, &filename, K_FOREVER); - rc = audio_prepare_random_file(); - if (rc == 0) + /* Sicherstellen, dass die I2S-Hardware nach einem vorherigen DRAIN + oder bei extrem schnellem Neudrücken garantiert gestoppt und leer ist. */ + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); + + /* 2. Datei bestimmen */ + if (filename[0] == '\0') { - audio_prime_prefill(AUDIO_BLOCK_COUNT); + if (get_random_file(AUDIO_PATH, filename, sizeof(filename)) < 0) + { + LOG_ERR("No file found in %s", AUDIO_PATH); + continue; + } } - else + + struct fs_file_t file; + fs_file_t_init(&file); + if (fs_open(&file, filename, FS_O_READ) < 0) { - LOG_ERR("Failed to prepare audio file, will retry on play event: %d", rc); - k_sleep(K_SECONDS(5)); + LOG_ERR("Failed to open %s", filename); continue; } + LOG_INF("Playing: %s", filename); + io_status(true); + bool i2s_started = false; + bool aborted = false; + + /* 3. Synchrone Lese- und Abspiel-Schleife */ while (1) { - k_poll(poll_events, ARRAY_SIZE(poll_events), K_FOREVER); - if (poll_events[0].state & K_POLL_STATE_SEM_AVAILABLE) + /* WICHTIG: Prüfen, ob während des Abspielens ein neues Kommando in die Queue gelegt wurde */ + if (k_msgq_num_used_get(&audio_play_msgq) > 0) { - int trigger_rc; - int drop_rc; - void *block; - - queued = 0; - - LOG_DBG("Handling PLAY event"); - k_sem_take(&audio_start_sem, K_NO_WAIT); - poll_events[0].state = K_POLL_STATE_NOT_READY; - - if (is_playing) - { - LOG_DBG("Audio already playing, canceling current playback and restarting"); - drop_rc = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); - if (drop_rc < 0) - { - LOG_WRN("I2S drop trigger failed: %d", drop_rc); - } - - while (k_msgq_get(&filled_slab_msgq, &block, K_NO_WAIT) == 0) - { - k_mem_slab_free(&audio_slab, &block); - } - - rc = audio_prepare_random_file(); - if (rc == 0) - { - audio_prime_prefill(MIN(2, AUDIO_BLOCK_COUNT)); // Sofort mit 2 Blöcken vorfüllen für schnelleren Restart - } - else - { - LOG_ERR("Failed to prepare audio file, will retry on play event: %d", rc); - break; - } - } - LOG_INF("PLAY: %u slabs ready (prefilled)", k_msgq_num_used_get(&filled_slab_msgq)); - - - while (k_msgq_get(&filled_slab_msgq, &block, K_NO_WAIT) == 0) - { - rc = i2s_write(i2s_dev, block, AUDIO_BLOCK_SIZE); - if (rc == 0) - { - queued++; - } - else - { - LOG_ERR("i2s_write prefilled block failed: %d", rc); - k_mem_slab_free(&audio_slab, &block); - } - } - - if (queued > 0) - { - trigger_rc = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); - if (trigger_rc < 0) - { - LOG_DBG("I2S start trigger failed: %d", trigger_rc); - } - else - { - is_playing = true; - io_status(true); - } - } - else - { - LOG_WRN("PLAY requested, but no prefilled slabs available"); - } - } - if (poll_events[1].state & K_POLL_STATE_SEM_AVAILABLE) - { - LOG_DBG("Handling FS SYNC event"); - k_sem_take(&fs_sync_sem, K_NO_WAIT); - poll_events[1].state = K_POLL_STATE_NOT_READY; - } - if (poll_events[2].state & K_POLL_STATE_MSGQ_DATA_AVAILABLE) - { - void *block; - - while (k_msgq_get(&free_slab_msgq, &block, K_NO_WAIT) == 0) - { - if (audio_fill_slab_from_cache(block) == 0) - { - if (is_playing) - { - rc = i2s_write(i2s_dev, block, AUDIO_BLOCK_SIZE); - if (rc != 0) - { - LOG_ERR("i2s_write refill block failed: %d", rc); - k_mem_slab_free(&audio_slab, &block); - } - } - else if (k_msgq_put(&filled_slab_msgq, &block, K_NO_WAIT) != 0) - { - k_mem_slab_free(&audio_slab, &block); - LOG_ERR("Audio not playing, but filled queue is full, freeing slab"); - } - } - else - { - k_mem_slab_free(&audio_slab, &block); - } - } - poll_events[2].state = K_POLL_STATE_NOT_READY; - } - if (is_playing && cached_file_eof) - { - LOG_INF("Reached end of file, draining I2S"); - i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + LOG_DBG("New play request received, aborting current playback"); + aborted = true; break; } + + void *block; + if (k_mem_slab_alloc(&audio_slab, &block, K_FOREVER) != 0) + { + break; + } + + ssize_t bytes_read = fs_read(&file, block, AUDIO_BLOCK_SIZE / 2); + + if (bytes_read <= 0) + { + k_mem_slab_free(&audio_slab, &block); + break; /* EOF oder Fehler */ + } + + /* In-Place Konvertierung Mono -> Stereo */ + int16_t *samples = (int16_t *)block; + int samples_read = bytes_read / 2; + + for (int i = samples_read - 1; i >= 0; i--) + { + int16_t sample = samples[i]; + samples[i * 2] = sample; + samples[i * 2 + 1] = sample; + } + + if (bytes_read < (AUDIO_BLOCK_SIZE / 2)) + { + size_t valid_bytes = bytes_read * 2; + memset((uint8_t *)block + valid_bytes, 0, AUDIO_BLOCK_SIZE - valid_bytes); + } + + if (i2s_write(i2s_dev, block, AUDIO_BLOCK_SIZE) < 0) + { + k_mem_slab_free(&audio_slab, &block); + break; + } + + if (!i2s_started) + { + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START); + i2s_started = true; + } } + + /* 4. Aufräumen */ + if (aborted) + { + /* Hart abbrechen, Puffer sofort verwerfen */ + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP); + } + else + { + /* Sauber ausklingen lassen, bis der letzte I2S-Puffer leer ist */ + i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + } + + fs_close(&file); + io_status(false); + LOG_DBG("Playback finished or aborted"); } } K_THREAD_DEFINE(audio_thread_id, AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL, AUDIO_THREAD_PRIORITY, 0, 0); -void slab_thread(void *arg1, void *arg2, void *arg3) -{ - LOG_DBG("Slab thread started"); - void *block; - while (1) - { - k_mem_slab_alloc(&audio_slab, &block, K_FOREVER); - k_msgq_put(&free_slab_msgq, &block, K_FOREVER); - } -} - -K_THREAD_DEFINE(slab_thread_id, 512, slab_thread, NULL, NULL, NULL, AUDIO_THREAD_PRIORITY + 1, 0, 0); - int audio_init(void) { LOG_DBG("Initializing audio subsystem..."); if (!device_is_ready(i2s_dev)) - { - LOG_ERR("I2S device not ready"); return -ENODEV; - } - /* Initial configuration of the I2S peripheral */ struct i2s_config config = { .word_size = AUDIO_WORD_WIDTH, .channels = 2, @@ -375,18 +208,18 @@ int audio_init(void) int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &config); if (ret < 0) - { - LOG_ERR("Failed to configure I2S: %d", ret); return ret; - } - LOG_INF("Audio subsystem initialized successfully, %u bits @ %u.%03u kHz", - config.word_size, config.frame_clk_freq / 1000, config.frame_clk_freq % 1000); + LOG_INF("Audio initialized: %u bits, %u.%03u kHz", config.word_size, config.frame_clk_freq / 1000, config.frame_clk_freq % 1000); return 0; } -void audio_play(void) +void audio_play(const char *filename) { - LOG_DBG("Posting PLAY event"); - k_sem_give(&audio_start_sem); + char buf[64] = {0}; + if (filename != NULL) + { + strncpy(buf, filename, sizeof(buf) - 1); + } + k_msgq_put(&audio_play_msgq, &buf, K_NO_WAIT); } \ No newline at end of file diff --git a/firmware/src/audio.h b/firmware/src/audio.h index 9328e23..2706a74 100644 --- a/firmware/src/audio.h +++ b/firmware/src/audio.h @@ -24,12 +24,19 @@ int audio_init(void); /** * @brief Plays an audio file from the filesystem + * + * @param filename The path to the audio file to play */ -void audio_play(void); +void audio_play(const char *filename) /** * @brief Stops the currently playing audio */ void audio_stop(void); +/** + * @brief Signals the audio thread that the system is fully booted + */ +void audio_system_ready(void); + #endif // AUDIO_H \ No newline at end of file diff --git a/firmware/src/io.c b/firmware/src/io.c index 761a034..664431a 100644 --- a/firmware/src/io.c +++ b/firmware/src/io.c @@ -20,7 +20,7 @@ void button_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pin gpio_pin_interrupt_configure_dt(&button_spec, GPIO_INT_DISABLE); LOG_DBG("Button pressed, triggering audio play"); - audio_play(); + audio_play(NULL); k_work_reschedule(&debounce_work, K_MSEC(50)); } diff --git a/firmware/src/main.c b/firmware/src/main.c index 2ec6248..fa590a1 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -71,4 +72,11 @@ int main(void) LOG_ERR("I/O initialization failed: %d", rc); return rc; } + + LOG_INF("All subsystems initialized. Starting application threads."); + audio_system_ready(); + audio_play("/lfs/sys/404"); + while (1) { + k_sleep(K_FOREVER); + } } \ No newline at end of file diff --git a/firmware/src/protocol.c b/firmware/src/protocol.c index e46ccdc..0422c0b 100644 --- a/firmware/src/protocol.c +++ b/firmware/src/protocol.c @@ -14,7 +14,7 @@ LOG_MODULE_REGISTER(protocol, LOG_LEVEL_DBG); #define PROTOCOL_STACK_SIZE 2048 #define PROTOCOL_PRIORITY 5 -#define BUFFER_SIZE 4096 +#define BUFFER_SIZE 256 static uint8_t buffer[BUFFER_SIZE]; static volatile uint32_t rx_index = 0; diff --git a/firmware/src/usb.c b/firmware/src/usb.c index 1a79342..b61592f 100644 --- a/firmware/src/usb.c +++ b/firmware/src/usb.c @@ -15,7 +15,7 @@ K_SEM_DEFINE(usb_tx_sem, 0, 1); const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE); /* NEU: Ringbuffer für stabilen asynchronen USB-Empfang */ -#define RX_RING_BUF_SIZE 4096 +#define RX_RING_BUF_SIZE 5*1024 /* 8 KB Ringpuffer für eingehende USB-Daten */ RING_BUF_DECLARE(rx_ringbuf, RX_RING_BUF_SIZE); static void cdc_acm_irq_cb(const struct device *dev, void *user_data)