sync
This commit is contained in:
@@ -6,7 +6,6 @@ project(buzzer)
|
|||||||
target_sources(app PRIVATE
|
target_sources(app PRIVATE
|
||||||
src/main.c
|
src/main.c
|
||||||
src/fs.c
|
src/fs.c
|
||||||
src/audio.c
|
|
||||||
src/io.c
|
src/io.c
|
||||||
src/usb.c
|
src/usb.c
|
||||||
src/protocol.c
|
src/protocol.c
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
#include <zephyr/random/random.h>
|
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
#include <zephyr/drivers/i2s.h>
|
#include <zephyr/drivers/i2s.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -11,134 +10,21 @@
|
|||||||
|
|
||||||
LOG_MODULE_REGISTER(audio, LOG_LEVEL_INF);
|
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_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
|
/* Message Queue für Play-Kommandos (Pfade zu Dateien, max 64 Zeichen) */
|
||||||
K_SEM_DEFINE(fs_sync_sem, 0, 1); // USB Sync
|
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)
|
#define I2S_NODE DT_ALIAS(audio_i2s)
|
||||||
|
|
||||||
/* Verify the node exists to avoid cryptic compiler errors */
|
|
||||||
#if !DT_NODE_EXISTS(I2S_NODE)
|
#if !DT_NODE_EXISTS(I2S_NODE)
|
||||||
#error "Audio I2S alias not defined in devicetree"
|
#error "Audio I2S alias not defined in devicetree"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct device *const i2s_dev = DEVICE_DT_GET(I2S_NODE);
|
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)
|
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;
|
int rc;
|
||||||
|
|
||||||
fs_dir_t_init(&dirp);
|
fs_dir_t_init(&dirp);
|
||||||
|
|
||||||
rc = fs_opendir(&dirp, path);
|
rc = fs_opendir(&dirp, path);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
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')
|
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
|
||||||
{
|
{
|
||||||
if (entry.type == FS_DIR_ENTRY_FILE)
|
if (entry.type == FS_DIR_ENTRY_FILE)
|
||||||
{
|
|
||||||
file_count++;
|
file_count++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fs_closedir(&dirp);
|
fs_closedir(&dirp);
|
||||||
|
|
||||||
if (file_count == 0)
|
if (file_count == 0)
|
||||||
return -ENOENT;
|
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);
|
rc = fs_opendir(&dirp, path);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
return rc;
|
return rc;
|
||||||
@@ -184,184 +66,135 @@ int get_random_file(const char *path, char *out_filename, size_t max_len)
|
|||||||
current_index++;
|
current_index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_closedir(&dirp);
|
fs_closedir(&dirp);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void audio_system_ready(void)
|
||||||
|
{
|
||||||
|
k_sem_give(&audio_ready_sem);
|
||||||
|
}
|
||||||
|
|
||||||
void audio_thread(void *arg1, void *arg2, void *arg3)
|
void audio_thread(void *arg1, void *arg2, void *arg3)
|
||||||
{
|
{
|
||||||
LOG_DBG("Audio thread started");
|
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);
|
char filename[64];
|
||||||
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);
|
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
bool is_playing = false;
|
/* 1. Auf Play-Kommando warten */
|
||||||
io_status(false);
|
k_msgq_get(&audio_play_msgq, &filename, K_FOREVER);
|
||||||
uint32_t queued = 0;
|
|
||||||
|
|
||||||
rc = audio_prepare_random_file();
|
/* Sicherstellen, dass die I2S-Hardware nach einem vorherigen DRAIN
|
||||||
if (rc == 0)
|
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);
|
LOG_ERR("Failed to open %s", filename);
|
||||||
k_sleep(K_SECONDS(5));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_INF("Playing: %s", filename);
|
||||||
|
io_status(true);
|
||||||
|
bool i2s_started = false;
|
||||||
|
bool aborted = false;
|
||||||
|
|
||||||
|
/* 3. Synchrone Lese- und Abspiel-Schleife */
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
k_poll(poll_events, ARRAY_SIZE(poll_events), K_FOREVER);
|
/* WICHTIG: Prüfen, ob während des Abspielens ein neues Kommando in die Queue gelegt wurde */
|
||||||
if (poll_events[0].state & K_POLL_STATE_SEM_AVAILABLE)
|
if (k_msgq_num_used_get(&audio_play_msgq) > 0)
|
||||||
{
|
{
|
||||||
int trigger_rc;
|
LOG_DBG("New play request received, aborting current playback");
|
||||||
int drop_rc;
|
aborted = true;
|
||||||
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);
|
|
||||||
break;
|
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);
|
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)
|
int audio_init(void)
|
||||||
{
|
{
|
||||||
LOG_DBG("Initializing audio subsystem...");
|
LOG_DBG("Initializing audio subsystem...");
|
||||||
if (!device_is_ready(i2s_dev))
|
if (!device_is_ready(i2s_dev))
|
||||||
{
|
|
||||||
LOG_ERR("I2S device not ready");
|
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
|
||||||
|
|
||||||
/* Initial configuration of the I2S peripheral */
|
|
||||||
struct i2s_config config = {
|
struct i2s_config config = {
|
||||||
.word_size = AUDIO_WORD_WIDTH,
|
.word_size = AUDIO_WORD_WIDTH,
|
||||||
.channels = 2,
|
.channels = 2,
|
||||||
@@ -375,18 +208,18 @@ int audio_init(void)
|
|||||||
|
|
||||||
int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &config);
|
int ret = i2s_configure(i2s_dev, I2S_DIR_TX, &config);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
|
||||||
LOG_ERR("Failed to configure I2S: %d", ret);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
|
||||||
|
|
||||||
LOG_INF("Audio subsystem initialized successfully, %u bits @ %u.%03u kHz",
|
LOG_INF("Audio initialized: %u bits, %u.%03u kHz", config.word_size, config.frame_clk_freq / 1000, config.frame_clk_freq % 1000);
|
||||||
config.word_size, config.frame_clk_freq / 1000, config.frame_clk_freq % 1000);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_play(void)
|
void audio_play(const char *filename)
|
||||||
{
|
{
|
||||||
LOG_DBG("Posting PLAY event");
|
char buf[64] = {0};
|
||||||
k_sem_give(&audio_start_sem);
|
if (filename != NULL)
|
||||||
|
{
|
||||||
|
strncpy(buf, filename, sizeof(buf) - 1);
|
||||||
|
}
|
||||||
|
k_msgq_put(&audio_play_msgq, &buf, K_NO_WAIT);
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,19 @@ int audio_init(void);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Plays an audio file from the filesystem
|
* @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
|
* @brief Stops the currently playing audio
|
||||||
*/
|
*/
|
||||||
void audio_stop(void);
|
void audio_stop(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signals the audio thread that the system is fully booted
|
||||||
|
*/
|
||||||
|
void audio_system_ready(void);
|
||||||
|
|
||||||
#endif // AUDIO_H
|
#endif // AUDIO_H
|
||||||
@@ -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);
|
gpio_pin_interrupt_configure_dt(&button_spec, GPIO_INT_DISABLE);
|
||||||
|
|
||||||
LOG_DBG("Button pressed, triggering audio play");
|
LOG_DBG("Button pressed, triggering audio play");
|
||||||
audio_play();
|
audio_play(NULL);
|
||||||
|
|
||||||
k_work_reschedule(&debounce_work, K_MSEC(50));
|
k_work_reschedule(&debounce_work, K_MSEC(50));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <zephyr/drivers/hwinfo.h>
|
#include <zephyr/drivers/hwinfo.h>
|
||||||
#include <zephyr/init.h>
|
#include <zephyr/init.h>
|
||||||
#include <zephyr/sys/printk.h>
|
#include <zephyr/sys/printk.h>
|
||||||
|
#include <zephyr/logging/log_ctrl.h>
|
||||||
#include <app_version.h>
|
#include <app_version.h>
|
||||||
#include <version.h>
|
#include <version.h>
|
||||||
#include <ncs_version.h>
|
#include <ncs_version.h>
|
||||||
@@ -71,4 +72,11 @@ int main(void)
|
|||||||
LOG_ERR("I/O initialization failed: %d", rc);
|
LOG_ERR("I/O initialization failed: %d", rc);
|
||||||
return 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ LOG_MODULE_REGISTER(protocol, LOG_LEVEL_DBG);
|
|||||||
|
|
||||||
#define PROTOCOL_STACK_SIZE 2048
|
#define PROTOCOL_STACK_SIZE 2048
|
||||||
#define PROTOCOL_PRIORITY 5
|
#define PROTOCOL_PRIORITY 5
|
||||||
#define BUFFER_SIZE 4096
|
#define BUFFER_SIZE 256
|
||||||
|
|
||||||
static uint8_t buffer[BUFFER_SIZE];
|
static uint8_t buffer[BUFFER_SIZE];
|
||||||
static volatile uint32_t rx_index = 0;
|
static volatile uint32_t rx_index = 0;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ K_SEM_DEFINE(usb_tx_sem, 0, 1);
|
|||||||
const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE);
|
const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE);
|
||||||
|
|
||||||
/* NEU: Ringbuffer für stabilen asynchronen USB-Empfang */
|
/* 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);
|
RING_BUF_DECLARE(rx_ringbuf, RX_RING_BUF_SIZE);
|
||||||
|
|
||||||
static void cdc_acm_irq_cb(const struct device *dev, void *user_data)
|
static void cdc_acm_irq_cb(const struct device *dev, void *user_data)
|
||||||
|
|||||||
Reference in New Issue
Block a user