added python tool inital version
This commit is contained in:
386
firmware/src/audio.c
Normal file
386
firmware/src/audio.c
Normal file
@@ -0,0 +1,386 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/random/random.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/i2s.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <audio.h>
|
||||
#include <fs.h>
|
||||
#include <io.h>
|
||||
|
||||
LOG_MODULE_REGISTER(audio, LOG_LEVEL_INF);
|
||||
|
||||
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
|
||||
|
||||
/* 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct fs_dir_t dirp;
|
||||
struct fs_dirent entry;
|
||||
int file_count = 0;
|
||||
int rc;
|
||||
|
||||
fs_dir_t_init(&dirp);
|
||||
|
||||
rc = fs_opendir(&dirp, path);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
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;
|
||||
|
||||
rc = fs_opendir(&dirp, path);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
int current_index = 0;
|
||||
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
|
||||
{
|
||||
if (entry.type == FS_DIR_ENTRY_FILE)
|
||||
{
|
||||
if (current_index == random_index)
|
||||
{
|
||||
snprintf(out_filename, max_len, "%s/%s", path, entry.name);
|
||||
break;
|
||||
}
|
||||
current_index++;
|
||||
}
|
||||
}
|
||||
|
||||
fs_closedir(&dirp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void audio_thread(void *arg1, void *arg2, void *arg3)
|
||||
{
|
||||
LOG_DBG("Audio thread started");
|
||||
int rc;
|
||||
|
||||
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);
|
||||
|
||||
while (1)
|
||||
{
|
||||
bool is_playing = false;
|
||||
io_status(false);
|
||||
uint32_t queued = 0;
|
||||
|
||||
rc = audio_prepare_random_file();
|
||||
if (rc == 0)
|
||||
{
|
||||
audio_prime_prefill(AUDIO_BLOCK_COUNT);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERR("Failed to prepare audio file, will retry on play event: %d", rc);
|
||||
k_sleep(K_SECONDS(5));
|
||||
continue;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
k_poll(poll_events, ARRAY_SIZE(poll_events), K_FOREVER);
|
||||
if (poll_events[0].state & K_POLL_STATE_SEM_AVAILABLE)
|
||||
{
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
.format = I2S_FMT_DATA_FORMAT_I2S,
|
||||
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
|
||||
.frame_clk_freq = AUDIO_SAMPLE_RATE,
|
||||
.mem_slab = &audio_slab,
|
||||
.block_size = AUDIO_BLOCK_SIZE,
|
||||
.timeout = SYS_FOREVER_MS,
|
||||
};
|
||||
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void audio_play(void)
|
||||
{
|
||||
LOG_DBG("Posting PLAY event");
|
||||
k_sem_give(&audio_start_sem);
|
||||
}
|
||||
35
firmware/src/audio.h
Normal file
35
firmware/src/audio.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
#define AUDIO_PATH "/lfs/a"
|
||||
#define AUDIO_EVENT_PLAY BIT(0)
|
||||
#define AUDIO_EVENT_STOP BIT(1)
|
||||
#define AUDIO_EVENT_SYNC BIT(8)
|
||||
|
||||
#define AUDIO_THREAD_STACK_SIZE 2048
|
||||
#define AUDIO_THREAD_PRIORITY 5
|
||||
#define AUDIO_EVENTS_MASK (AUDIO_EVENT_PLAY | AUDIO_EVENT_STOP | AUDIO_EVENT_SYNC)
|
||||
|
||||
#define AUDIO_BLOCK_SIZE 1024
|
||||
#define AUDIO_BLOCK_COUNT 4
|
||||
#define AUDIO_WORD_WIDTH 16
|
||||
#define AUDIO_SAMPLE_RATE 16000
|
||||
|
||||
/**
|
||||
* @brief Initializes the audio subsystem
|
||||
*
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int audio_init(void);
|
||||
|
||||
/**
|
||||
* @brief Plays an audio file from the filesystem
|
||||
*/
|
||||
void audio_play(void);
|
||||
|
||||
/**
|
||||
* @brief Stops the currently playing audio
|
||||
*/
|
||||
void audio_stop(void);
|
||||
|
||||
#endif // AUDIO_H
|
||||
23
firmware/src/fs.c
Normal file
23
firmware/src/fs.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include <zephyr/fs/littlefs.h>
|
||||
#include <fs.h>
|
||||
LOG_MODULE_REGISTER(buzz_fs, LOG_LEVEL_INF);
|
||||
|
||||
#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(littlefs_storage)
|
||||
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
||||
|
||||
static struct fs_mount_t fs_storage_mnt = {
|
||||
.type = FS_LITTLEFS,
|
||||
.fs_data = &fs_storage_data,
|
||||
.storage_dev = (void *)STORAGE_PARTITION_ID,
|
||||
.mnt_point = "/lfs",
|
||||
};
|
||||
|
||||
int fs_init(void) {
|
||||
int rc = fs_mount(&fs_storage_mnt);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Error mounting filesystem: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
LOG_DBG("Filesystem mounted successfully");
|
||||
return 0;
|
||||
}
|
||||
10
firmware/src/fs.h
Normal file
10
firmware/src/fs.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef FS_H
|
||||
#define FS_H
|
||||
|
||||
#include <zephyr/fs/fs.h>
|
||||
|
||||
/**
|
||||
* @brief Initializes the filesystem by mounting it
|
||||
*/
|
||||
int fs_init(void);
|
||||
#endif // FS_H
|
||||
91
firmware/src/io.c
Normal file
91
firmware/src/io.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <io.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <audio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(io, LOG_LEVEL_INF);
|
||||
|
||||
#define STATUS_LED_NODE DT_ALIAS(status_led)
|
||||
#define USB_LED_NODE DT_ALIAS(usb_led)
|
||||
#define BUZZER_BUTTON_NODE DT_ALIAS(buzzer_button)
|
||||
|
||||
static const struct gpio_dt_spec led_spec = GPIO_DT_SPEC_GET(STATUS_LED_NODE, gpios);
|
||||
static const struct gpio_dt_spec usb_led_spec = GPIO_DT_SPEC_GET(USB_LED_NODE, gpios);
|
||||
static const struct gpio_dt_spec button_spec = GPIO_DT_SPEC_GET(BUZZER_BUTTON_NODE, gpios);
|
||||
|
||||
static struct gpio_callback button_cb_data;
|
||||
static struct k_work_delayable debounce_work;
|
||||
|
||||
void button_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins) {
|
||||
gpio_pin_interrupt_configure_dt(&button_spec, GPIO_INT_DISABLE);
|
||||
|
||||
LOG_DBG("Button pressed, triggering audio play");
|
||||
audio_play();
|
||||
|
||||
k_work_reschedule(&debounce_work, K_MSEC(50));
|
||||
}
|
||||
|
||||
static void debounce_work_handler(struct k_work *work)
|
||||
{
|
||||
gpio_pin_interrupt_configure_dt(&button_spec, GPIO_INT_EDGE_TO_ACTIVE);
|
||||
LOG_DBG("Button debounce expired, re-enabling interrupt");
|
||||
}
|
||||
|
||||
int io_init(void)
|
||||
{
|
||||
LOG_DBG("Initializing I/O subsystem...");
|
||||
|
||||
if (!device_is_ready(led_spec.port))
|
||||
{
|
||||
LOG_ERR("LED GPIO device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
int ret = gpio_pin_configure_dt(&led_spec, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0)
|
||||
{
|
||||
LOG_ERR("Failed to configure LED GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!device_is_ready(usb_led_spec.port))
|
||||
{
|
||||
LOG_ERR("USB LED GPIO device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = gpio_pin_configure_dt(&usb_led_spec, GPIO_OUTPUT_INACTIVE);
|
||||
if (ret < 0)
|
||||
{
|
||||
LOG_ERR("Failed to configure USB LED GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!device_is_ready(button_spec.port))
|
||||
{
|
||||
LOG_ERR("Button GPIO device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
ret = gpio_pin_configure_dt(&button_spec, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure Button GPIO: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_work_init_delayable(&debounce_work, debounce_work_handler);
|
||||
gpio_pin_interrupt_configure_dt(&button_spec, GPIO_INT_EDGE_TO_ACTIVE);
|
||||
gpio_init_callback(&button_cb_data, button_isr, BIT(button_spec.pin));
|
||||
gpio_add_callback(button_spec.port, &button_cb_data);
|
||||
|
||||
LOG_DBG("I/O subsystem initialized successfully");
|
||||
LOG_DBG("Button: %s.%02u, LED: %s.%02u", button_spec.port->name, button_spec.pin, led_spec.port->name, led_spec.pin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void io_status(bool status)
|
||||
{
|
||||
gpio_pin_set_dt(&led_spec, status ? 1 : 0);
|
||||
}
|
||||
|
||||
void io_usb_status(bool connected)
|
||||
{
|
||||
gpio_pin_set_dt(&usb_led_spec, connected ? 1 : 0);
|
||||
}
|
||||
23
firmware/src/io.h
Normal file
23
firmware/src/io.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef IO_H
|
||||
#define IO_H
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
|
||||
/**
|
||||
* @brief Initializes the I/O subsystem, including GPIOs and any related hardware
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int io_init(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the status of the I/O subsystem
|
||||
* @param status The status to set
|
||||
*/
|
||||
void io_status(bool status);
|
||||
|
||||
/**
|
||||
* @brief Sets the USB connection status indicator
|
||||
* @param connected True if USB is connected, false otherwise
|
||||
*/
|
||||
void io_usb_status(bool connected);
|
||||
#endif
|
||||
84
firmware/src/main.c
Normal file
84
firmware/src/main.c
Normal file
@@ -0,0 +1,84 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <app_version.h>
|
||||
#include <version.h>
|
||||
#include <ncs_version.h>
|
||||
|
||||
#include <fs.h>
|
||||
#include <audio.h>
|
||||
#include <io.h>
|
||||
#include <usb.h>
|
||||
|
||||
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
|
||||
|
||||
void print_device_id(void) {
|
||||
uint8_t device_id[8]; // 64 Bit = 8 Bytes
|
||||
ssize_t length;
|
||||
|
||||
// Device ID auslesen
|
||||
length = hwinfo_get_device_id(device_id, sizeof(device_id));
|
||||
|
||||
if (length > 0) {
|
||||
char id_str[17]; // 16 Zeichen + Null-Terminator
|
||||
for (int i = 0; i < length; i++) {
|
||||
sprintf(&id_str[i * 2], "%02x", device_id[i]);
|
||||
}
|
||||
LOG_INF("Board Device ID: %s", id_str);
|
||||
} else {
|
||||
LOG_ERR("Konnte Device ID nicht lesen");
|
||||
}
|
||||
}
|
||||
|
||||
static int print_custom_banner(void)
|
||||
{
|
||||
|
||||
printk("\x1b[44m\x1b[2J\x1b[H");
|
||||
|
||||
// Oberer Rahmen
|
||||
printk("\x1b[1;37m┌───────────────────────────────────────────┐\n");
|
||||
printk("│ Edis Buzzer Version: %-20s │\n", APP_VERSION_STRING);
|
||||
printk("├───────────────────────────────────────────┤\n");
|
||||
printk("│ \x1b[22;37mZephyr Version: \x1b[1;37m%-20s │\n", KERNEL_VERSION_STRING);
|
||||
printk("│ \x1b[22;37mNCS Version: \x1b[1;37m%-20s │\n", NCS_VERSION_STRING);
|
||||
printk("└───────────────────────────────────────────┘\x1b[0m\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(print_custom_banner, PRE_KERNEL_1, 0);
|
||||
|
||||
int main(void)
|
||||
{
|
||||
LOG_INF("Starting Edis Buzzer Application");
|
||||
print_device_id();
|
||||
|
||||
int rc;
|
||||
|
||||
rc = fs_init();
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Filesystem initialization failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = audio_init();
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Audio initialization failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = usb_cdc_acm_init();
|
||||
if (rc < 0) {
|
||||
LOG_ERR("USB initialization failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = io_init();
|
||||
if (rc < 0) {
|
||||
LOG_ERR("I/O initialization failed: %d", rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
366
firmware/src/protocol.c
Normal file
366
firmware/src/protocol.c
Normal file
@@ -0,0 +1,366 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <string.h>
|
||||
#include <fs.h>
|
||||
#include <app_version.h>
|
||||
#include <zephyr/sys/crc.h>
|
||||
|
||||
#include <usb.h>
|
||||
#include <protocol.h>
|
||||
|
||||
#define PROTOCOL_VERSION 1
|
||||
|
||||
LOG_MODULE_REGISTER(protocol, LOG_LEVEL_DBG);
|
||||
|
||||
#define PROTOCOL_STACK_SIZE 2048
|
||||
#define PROTOCOL_PRIORITY 5
|
||||
#define BUFFER_SIZE 512
|
||||
|
||||
static uint8_t buffer[BUFFER_SIZE];
|
||||
static volatile uint32_t rx_index = 0;
|
||||
|
||||
static protocol_state_t current_protocol_state = PS_WAITING_FOR_COMMAND;
|
||||
static protocol_cmd_t current_command = CMD_INVALID;
|
||||
|
||||
void send_ok()
|
||||
{
|
||||
const char *response = "OK\n";
|
||||
usb_write_buffer((const uint8_t *)response, strlen(response));
|
||||
}
|
||||
|
||||
void send_error(int32_t error_code)
|
||||
{
|
||||
char response[32];
|
||||
snprintf(response, sizeof(response), "ERR %d\n", error_code);
|
||||
usb_write_buffer((const uint8_t *)response, strlen(response));
|
||||
}
|
||||
|
||||
int send_ls(const char *path)
|
||||
{
|
||||
struct fs_dir_t dirp;
|
||||
struct fs_dirent entry;
|
||||
const char *ls_path = (path == NULL || path[0] == '\0') ? "/" : path;
|
||||
fs_dir_t_init(&dirp);
|
||||
|
||||
if (fs_opendir(&dirp, ls_path) < 0)
|
||||
{
|
||||
LOG_ERR("Failed to open directory '%s'", ls_path);
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
char tx_buffer[300];
|
||||
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
|
||||
{
|
||||
snprintf(tx_buffer, sizeof(tx_buffer), "%s,%u,%s\n", entry.type == FS_DIR_ENTRY_FILE ? "F" : "D", entry.size, entry.name);
|
||||
usb_write_buffer((const uint8_t *)tx_buffer, strlen(tx_buffer));
|
||||
}
|
||||
|
||||
fs_closedir(&dirp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int send_info()
|
||||
{
|
||||
char info[112];
|
||||
struct fs_statvfs stat;
|
||||
int rc = fs_statvfs("/lfs", &stat);
|
||||
if (rc)
|
||||
{
|
||||
LOG_ERR("Failed to get filesystem stats: %d", rc);
|
||||
return -rc;
|
||||
}
|
||||
snprintf(info, sizeof(info), "%u;%s;%lu;%lu;%lu\n", PROTOCOL_VERSION, APP_VERSION_STRING, stat.f_frsize, stat.f_blocks, stat.f_bfree);
|
||||
usb_write_buffer((const uint8_t *)info, strlen(info));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int put_binary_file(const char *filename, ssize_t filesize, uint32_t expected_crc32)
|
||||
{
|
||||
int rc;
|
||||
struct fs_file_t file;
|
||||
ssize_t bytes_written = 0;
|
||||
uint32_t running_crc32 = 0;
|
||||
uint32_t retry_count = 0;
|
||||
|
||||
fs_file_t_init(&file);
|
||||
|
||||
rc = fs_open(&file, filename, FS_O_CREATE | FS_O_WRITE);
|
||||
if (rc < 0)
|
||||
{
|
||||
LOG_ERR("Failed to open file '%s' for writing: %d", filename, rc);
|
||||
return -rc;
|
||||
}
|
||||
|
||||
usb_write_buffer((const uint8_t *)"READY\n", 6);
|
||||
|
||||
while (bytes_written < filesize)
|
||||
{
|
||||
size_t to_write = MIN(sizeof(buffer), filesize - bytes_written);
|
||||
ssize_t read = usb_read_buffer(buffer, to_write);
|
||||
|
||||
if (read < 0)
|
||||
{
|
||||
LOG_ERR("Error reading from USB: %d", read);
|
||||
fs_close(&file);
|
||||
return -read;
|
||||
}
|
||||
else if (read == 0)
|
||||
{
|
||||
if (retry_count > 10)
|
||||
{
|
||||
LOG_ERR("No data received from USB after multiple attempts");
|
||||
fs_close(&file);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
usb_resume_rx();
|
||||
LOG_DBG("No data available from USB, waiting for data... (attempt %u)", retry_count + 1);
|
||||
|
||||
if (bytes_written == 0)
|
||||
{
|
||||
usb_wait_for_data(K_SECONDS(30));
|
||||
}
|
||||
else
|
||||
{
|
||||
usb_wait_for_data(K_SECONDS(1));
|
||||
}
|
||||
retry_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// ssize_t written = fs_write(&file, buffer, read);
|
||||
ssize_t written = read;
|
||||
if (written < 0)
|
||||
{
|
||||
LOG_ERR("Error writing to file '%s': %d", filename, (int)written);
|
||||
fs_close(&file);
|
||||
return (int)written;
|
||||
}
|
||||
running_crc32 = crc32_ieee_update(running_crc32, buffer, written);
|
||||
|
||||
bytes_written += written;
|
||||
}
|
||||
|
||||
fs_close(&file);
|
||||
|
||||
if (running_crc32 != expected_crc32)
|
||||
{
|
||||
LOG_ERR("CRC32 mismatch for file '%s': expected 0x%08x, got 0x%08x", filename, expected_crc32, running_crc32);
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void execute_current_command(void)
|
||||
{
|
||||
int rc;
|
||||
switch (current_command)
|
||||
{
|
||||
case CMD_LS:
|
||||
LOG_DBG("Executing LS command with parameters: '%s'", buffer);
|
||||
rc = send_ls((char *)buffer);
|
||||
if (rc == 0)
|
||||
{
|
||||
send_ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error(rc);
|
||||
}
|
||||
break;
|
||||
case CMD_INFO:
|
||||
if (buffer[0] != '\0')
|
||||
{
|
||||
LOG_WRN("INFO command received with unexpected parameters: '%s'", buffer);
|
||||
}
|
||||
LOG_DBG("Executing INFO command");
|
||||
rc = send_info();
|
||||
if (rc == 0)
|
||||
{
|
||||
send_ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error(rc);
|
||||
}
|
||||
break;
|
||||
case CMD_PUT_BINARY_FILE:
|
||||
char filename[128];
|
||||
ssize_t filesize;
|
||||
uint32_t crc32;
|
||||
|
||||
rc = sscanf((char *)buffer, "%127[^;];%zd;%i", filename, &filesize, &crc32);
|
||||
if (rc != 3)
|
||||
{
|
||||
LOG_ERR("Invalid parameters for PUT_BINARY_FILE command (got %d): '%s'", rc, buffer);
|
||||
send_error(EINVAL);
|
||||
break;
|
||||
}
|
||||
LOG_DBG("Executing PUT_BINARY_FILE command filename: '%s', filesize: %zd, crc32: 0x%08x", filename, filesize, crc32);
|
||||
rc = put_binary_file(filename, filesize, crc32);
|
||||
if (rc == 0)
|
||||
{
|
||||
send_ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error(rc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("No execution logic for command %d", current_command);
|
||||
send_error(ENOSYS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protocol_state_t waiting_for_command(uint8_t byte)
|
||||
{
|
||||
if (byte < 'a' || byte > 'z')
|
||||
{
|
||||
rx_index = 0;
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
buffer[rx_index++] = byte;
|
||||
return PS_READING_COMMAND;
|
||||
}
|
||||
|
||||
protocol_state_t reading_command(uint8_t byte)
|
||||
{
|
||||
if (byte == ' ' || byte == '\n' || byte == '\r')
|
||||
{
|
||||
buffer[rx_index] = '\0';
|
||||
rx_index = 0;
|
||||
|
||||
if (strcmp((char *)buffer, "ls") == 0)
|
||||
{
|
||||
LOG_DBG("Received LS command");
|
||||
current_command = CMD_LS;
|
||||
}
|
||||
else if (strcmp((char *)buffer, "info") == 0)
|
||||
{
|
||||
LOG_DBG("Received INFO command");
|
||||
current_command = CMD_INFO;
|
||||
}
|
||||
else if (strcmp((char *)buffer, "put") == 0)
|
||||
{
|
||||
LOG_DBG("Received PUT_BINARY_FILE command");
|
||||
current_command = CMD_PUT_BINARY_FILE;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
LOG_DBG("Unknown command: %s", buffer);
|
||||
current_command = CMD_INVALID;
|
||||
send_error(EILSEQ);
|
||||
if (byte != '\n' && byte != '\r')
|
||||
return PS_WAITING_FOR_END_OF_LINE;
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
|
||||
if (byte == ' ')
|
||||
{
|
||||
rx_index = 0;
|
||||
return PS_READING_PARAMETERS;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[0] = '\0';
|
||||
rx_index = 0;
|
||||
execute_current_command();
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rx_index < BUFFER_SIZE - 1)
|
||||
{
|
||||
buffer[rx_index++] = byte;
|
||||
}
|
||||
else
|
||||
{
|
||||
send_error(EMSGSIZE);
|
||||
return PS_WAITING_FOR_END_OF_LINE;
|
||||
}
|
||||
}
|
||||
return PS_READING_COMMAND;
|
||||
}
|
||||
|
||||
protocol_state_t reading_parameters(uint8_t byte)
|
||||
{
|
||||
if (byte == '\n' || byte == '\r')
|
||||
{
|
||||
buffer[rx_index] = '\0';
|
||||
rx_index = 0;
|
||||
execute_current_command();
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[rx_index++] = byte;
|
||||
if (rx_index >= BUFFER_SIZE)
|
||||
{
|
||||
rx_index = 0;
|
||||
send_error(EMSGSIZE);
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
return PS_READING_PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
protocol_state_t waiting_for_end_of_line(uint8_t byte)
|
||||
{
|
||||
if (byte == '\n' || byte == '\r')
|
||||
{
|
||||
return PS_WAITING_FOR_COMMAND;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PS_WAITING_FOR_END_OF_LINE;
|
||||
}
|
||||
}
|
||||
|
||||
void protocol_thread_entry(void *p1, void *p2, void *p3)
|
||||
{
|
||||
uint8_t rx_byte;
|
||||
|
||||
LOG_DBG("Protocol thread started, waiting for data...");
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* 1. Thread schläft, bis der USB-Interrupt triggert */
|
||||
if (usb_wait_for_data(K_FOREVER))
|
||||
{
|
||||
|
||||
while (usb_read_char(&rx_byte) > 0)
|
||||
{
|
||||
switch (current_protocol_state)
|
||||
{
|
||||
case PS_WAITING_FOR_COMMAND:
|
||||
current_protocol_state = waiting_for_command(rx_byte);
|
||||
break;
|
||||
case PS_READING_COMMAND:
|
||||
current_protocol_state = reading_command(rx_byte);
|
||||
break;
|
||||
case PS_READING_PARAMETERS:
|
||||
current_protocol_state = reading_parameters(rx_byte);
|
||||
break;
|
||||
case PS_WAITING_FOR_END_OF_LINE:
|
||||
current_protocol_state = waiting_for_end_of_line(rx_byte);
|
||||
break;
|
||||
default:
|
||||
LOG_ERR("Invalid protocol state: %d", current_protocol_state);
|
||||
current_protocol_state = PS_WAITING_FOR_COMMAND;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
usb_resume_rx();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Thread statisch definieren und automatisch starten lassen */
|
||||
K_THREAD_DEFINE(protocol_tid, PROTOCOL_STACK_SIZE,
|
||||
protocol_thread_entry, NULL, NULL, NULL,
|
||||
PROTOCOL_PRIORITY, 0, 0);
|
||||
19
firmware/src/protocol.h
Normal file
19
firmware/src/protocol.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef PROTOCOL_H
|
||||
#define PROTOCOL_H
|
||||
|
||||
typedef enum {
|
||||
PS_WAITING_FOR_COMMAND,
|
||||
PS_READING_COMMAND,
|
||||
PS_READING_PARAMETERS,
|
||||
PS_WAITING_FOR_END_OF_LINE,
|
||||
} protocol_state_t;
|
||||
|
||||
typedef enum {
|
||||
CMD_INVALID = 0,
|
||||
CMD_INFO,
|
||||
CMD_LS,
|
||||
CMD_PUT_BINARY_FILE,
|
||||
/* Weitere Kommandos folgen hier */
|
||||
} protocol_cmd_t;
|
||||
|
||||
#endif // PROTOCOL_H
|
||||
156
firmware/src/usb.c
Normal file
156
firmware/src/usb.c
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
#include <zephyr/drivers/uart.h>
|
||||
|
||||
#include <io.h>
|
||||
|
||||
LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG);
|
||||
|
||||
K_SEM_DEFINE(usb_rx_sem, 0, 1);
|
||||
K_SEM_DEFINE(usb_tx_sem, 0, 1);
|
||||
|
||||
#define UART_NODE DT_ALIAS(usb_uart)
|
||||
const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE);
|
||||
|
||||
static void cdc_acm_irq_cb(const struct device *dev, void *user_data)
|
||||
{
|
||||
ARG_UNUSED(user_data);
|
||||
|
||||
if (!uart_irq_update(dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uart_irq_rx_ready(dev)) {
|
||||
uart_irq_rx_disable(dev);
|
||||
k_sem_give(&usb_rx_sem);
|
||||
LOG_DBG("RX interrupt: data available");
|
||||
}
|
||||
|
||||
|
||||
if (uart_irq_tx_ready(dev)) {
|
||||
uart_irq_tx_disable(dev);
|
||||
k_sem_give(&usb_tx_sem);
|
||||
LOG_DBG("TX interrupt: ready for more data");
|
||||
}
|
||||
}
|
||||
|
||||
bool usb_wait_for_data(k_timeout_t timeout)
|
||||
{
|
||||
/* Wartet auf das Signal aus der ISR */
|
||||
return (k_sem_take(&usb_rx_sem, timeout) == 0);
|
||||
}
|
||||
|
||||
int usb_read_char(uint8_t *c)
|
||||
{
|
||||
if (!device_is_ready(cdc_dev)) return 0;
|
||||
return uart_fifo_read(cdc_dev, c, 1);
|
||||
}
|
||||
|
||||
int usb_read_buffer(uint8_t *buf, size_t max_len)
|
||||
{
|
||||
if (!device_is_ready(cdc_dev)) return 0;
|
||||
return uart_fifo_read(cdc_dev, buf, max_len);
|
||||
}
|
||||
|
||||
void usb_resume_rx(void)
|
||||
{
|
||||
if (device_is_ready(cdc_dev)) {
|
||||
uart_irq_rx_enable(cdc_dev);
|
||||
LOG_DBG("RX interrupt re-enabled");
|
||||
}
|
||||
}
|
||||
|
||||
void usb_write_char(uint8_t c)
|
||||
{
|
||||
if (!device_is_ready(cdc_dev)) {
|
||||
return;
|
||||
}
|
||||
uart_poll_out(cdc_dev, c);
|
||||
}
|
||||
|
||||
void usb_write_buffer(const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (!device_is_ready(cdc_dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t written;
|
||||
while (len > 0) {
|
||||
written = uart_fifo_fill(cdc_dev, buf, len);
|
||||
|
||||
len -= written;
|
||||
buf += written;
|
||||
|
||||
if (len > 0) {
|
||||
/* Der FIFO ist voll, aber wir haben noch Daten.
|
||||
* 1. TX-Interrupt aktivieren (meldet sich, wenn wieder Platz ist)
|
||||
* 2. Thread schlafen legen, bis die ISR die Semaphore gibt */
|
||||
uart_irq_tx_enable(cdc_dev);
|
||||
k_sem_take(&usb_tx_sem, K_FOREVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usb_status_cb(enum usb_dc_status_code cb_status, const uint8_t *param)
|
||||
{
|
||||
switch (cb_status) {
|
||||
case USB_DC_CONNECTED:
|
||||
/* VBUS wurde vom Zephyr-Stack erkannt */
|
||||
LOG_DBG("VBUS detected, USB device connected");
|
||||
break;
|
||||
case USB_DC_CONFIGURED:
|
||||
LOG_DBG("USB device configured by host");
|
||||
io_usb_status(true);
|
||||
if (device_is_ready(cdc_dev)) {
|
||||
(void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1);
|
||||
(void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1);
|
||||
|
||||
/* Interrupt-Handler binden und initial aktivieren */
|
||||
uart_irq_callback_set(cdc_dev, cdc_acm_irq_cb);
|
||||
uart_irq_rx_enable(cdc_dev);
|
||||
}
|
||||
break;
|
||||
case USB_DC_DISCONNECTED:
|
||||
/* Kabel wurde gezogen */
|
||||
LOG_DBG("VBUS removed, USB device disconnected");
|
||||
if (device_is_ready(cdc_dev)) {
|
||||
uart_irq_rx_disable(cdc_dev);
|
||||
}
|
||||
io_usb_status(false);
|
||||
break;
|
||||
case USB_DC_RESET:
|
||||
LOG_DBG("USB bus reset");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int usb_cdc_acm_init(void)
|
||||
{
|
||||
LOG_DBG("Initializing USB Stack...");
|
||||
|
||||
/* Zephyr-Treiber registrieren. Verbraucht keinen Strom ohne VBUS. */
|
||||
int ret = usb_enable(usb_status_cb);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("Failed to enable USB (%d)", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if DT_NODE_HAS_STATUS(DT_NODELABEL(cdc_acm_uart0), okay)
|
||||
const struct device *cdc_dev = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0));
|
||||
|
||||
if (!device_is_ready(cdc_dev)) {
|
||||
LOG_ERR("CDC ACM device not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#else
|
||||
LOG_ERR("CDC ACM UART device not found in devicetree");
|
||||
return -ENODEV;
|
||||
#endif
|
||||
|
||||
LOG_DBG("USB Stack enabled and waiting for VBUS in hardware");
|
||||
return 0;
|
||||
}
|
||||
50
firmware/src/usb.h
Normal file
50
firmware/src/usb.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef USB_CDC_ACM_H
|
||||
#define USB_CDC_ACM_H
|
||||
|
||||
/**
|
||||
* @brief Initializes the USB CDC ACM device
|
||||
* @return 0 on success, negative error code on failure
|
||||
*/
|
||||
int usb_cdc_acm_init(void);
|
||||
|
||||
/**
|
||||
* @brief Waits until data is available in the USB RX FIFO or the timeout expires
|
||||
* @param timeout Maximum time to wait for data. Use K_FOREVER for infinite wait.
|
||||
* @return true if data is available, false if timeout occurred
|
||||
*/
|
||||
bool usb_wait_for_data(k_timeout_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Reads a single character from the USB RX FIFO
|
||||
* @param c Pointer to store the read character
|
||||
* @return 1 if a character was read, 0 if no data was available
|
||||
*/
|
||||
int usb_read_char(uint8_t *c);
|
||||
|
||||
/**
|
||||
* @brief Reads a block of data from the USB RX FIFO
|
||||
* @param buf Buffer to store the read data
|
||||
* @param max_len Maximum number of bytes to read
|
||||
* @return Number of bytes read
|
||||
*/
|
||||
int usb_read_buffer(uint8_t *buf, size_t max_len);
|
||||
|
||||
/**
|
||||
* @brief Resumes the USB RX interrupt when all data has been read
|
||||
*/
|
||||
void usb_resume_rx(void);
|
||||
|
||||
/**
|
||||
* @brief Writes a single character to the USB TX FIFO
|
||||
* @param c Character to write
|
||||
*/
|
||||
void usb_write_char(uint8_t c);
|
||||
|
||||
/**
|
||||
* @brief Writes a block of data to the USB TX FIFO
|
||||
* @param buf Buffer containing the data to write
|
||||
* @param len Number of bytes to write
|
||||
*/
|
||||
void usb_write_buffer(const uint8_t *buf, size_t len);
|
||||
|
||||
#endif // USB_CDC_ACM_H
|
||||
Reference in New Issue
Block a user