added python tool inital version

This commit is contained in:
2026-02-25 10:09:17 +01:00
parent 288b1e45ef
commit 80c0e825a7
26 changed files with 369 additions and 0 deletions

386
firmware/src/audio.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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