From 288b1e45ef822cca06bdcc8c1a83428a70c5e6cd Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Wed, 25 Feb 2026 08:43:29 +0100 Subject: [PATCH] sync --- CMakeLists.txt | 1 + VERSION | 2 +- bench_lfs.py | 238 ------------------- boards/nrf52840dk_nrf52840.overlay | 1 + prj.conf | 3 +- send_file.py | 321 ------------------------- src/audio.c | 3 +- src/fs.c | 2 +- src/io.c | 2 +- src/main.c | 7 +- src/protocol.c | 366 +++++++++++++++++++++++++++++ src/protocol.h | 19 ++ src/usb.c | 264 ++++++++++----------- src/usb.h | 41 ++++ 14 files changed, 558 insertions(+), 712 deletions(-) delete mode 100644 bench_lfs.py delete mode 100644 send_file.py create mode 100644 src/protocol.c create mode 100644 src/protocol.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a10101..03145a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ target_sources(app PRIVATE src/audio.c src/io.c src/usb.c + src/protocol.c ) zephyr_include_directories(src) diff --git a/VERSION b/VERSION index 6657576..57ab7a2 100644 --- a/VERSION +++ b/VERSION @@ -1,5 +1,5 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 -PATCHLEVEL = 1 +PATCHLEVEL = 2 VERSION_TWEAK = 0 EXTRAVERSION = 0 \ No newline at end of file diff --git a/bench_lfs.py b/bench_lfs.py deleted file mode 100644 index ed08613..0000000 --- a/bench_lfs.py +++ /dev/null @@ -1,238 +0,0 @@ -import argparse -import time - -import serial - - -DEFAULT_SWEEP_CASES = [ - (4096, 100, 100), - (4096, 100, 1), - (512, 400, 400), - (1024, 200, 200), - (2048, 100, 100), - (4096, 500, 500), -] - - -def wait_line(serial_port, timeout_s): - deadline = time.monotonic() + timeout_s - while time.monotonic() < deadline: - raw = serial_port.readline() - if not raw: - continue - line = raw.decode("utf-8", errors="ignore").strip() - if line: - return line - return "" - - -def parse_bench_result(line): - parts = line.split(";") - if len(parts) < 8 or parts[0] != "BENCH": - return None - - try: - return { - "block": int(parts[1]), - "count": int(parts[2]), - "sync_every": int(parts[3]), - "total_bytes": int(parts[4]), - "sync_ms": int(parts[5]), - "total_ms": int(parts[6]), - "kib_s": int(parts[7]), - } - except ValueError: - return None - - -def run_bench(port, baudrate, block_size, count, sync_every, timeout_s, verbose=True): - command = f"BENCH {block_size} {count} {sync_every}\n" - - with serial.Serial(port, baudrate, timeout=0.2, write_timeout=None) as serial_port: - serial_port.dtr = True - time.sleep(0.1) - serial_port.reset_input_buffer() - - if verbose: - print(f"Verbindung: {port} @ {baudrate}") - print(f"Sende Befehl: {command.strip()}") - - serial_port.write(command.encode("utf-8")) - serial_port.flush() - - bench_line = "" - bench_result = None - final_status = "" - - start = time.monotonic() - while time.monotonic() - start < timeout_s: - line = wait_line(serial_port, 0.5) - if not line: - continue - - if line.startswith("BENCH;"): - candidate = parse_bench_result(line) - if candidate is None: - continue - - if ( - candidate["block"] == block_size - and candidate["count"] == count - and candidate["sync_every"] == sync_every - ): - bench_line = line - bench_result = candidate - if verbose: - print(f"Ergebnis: {bench_line}") - elif verbose: - print(f"Info: Fremdes BENCH-Ergebnis ignoriert: {line}") - continue - - if line in {"OK", "ERR"}: - final_status = line - break - - if verbose: - print(f"Info: {line}") - - if final_status != "OK": - if verbose: - print(f"Fehler: Kein OK erhalten (Status: '{final_status or 'timeout'}')") - return 1, None - - if not bench_line: - if verbose: - print("Hinweis: Kein BENCH-Datensatz empfangen.") - return 0, None - - result = bench_result - if verbose and result is not None: - print( - "Zusammenfassung: " - f"block={result['block']}, runs={result['count']}, sync_every={result['sync_every']}, " - f"bytes={result['total_bytes']}, sync_ms={result['sync_ms']}, " - f"total_ms={result['total_ms']}, speed={result['kib_s']} KiB/s" - ) - - return 0, result - - -def parse_case_string(case_text): - cleaned = case_text.lower().replace(" ", "") - for separator in ("x", ":", ","): - cleaned = cleaned.replace(separator, ";") - parts = cleaned.split(";") - if len(parts) != 3: - raise ValueError(f"Ungültiges Case-Format: '{case_text}'") - block_size = max(int(parts[0]), 1) - count = max(int(parts[1]), 1) - sync_every = max(int(parts[2]), 1) - return block_size, count, sync_every - - -def run_sweep(port, baudrate, timeout_s, case_strings): - if case_strings: - cases = [parse_case_string(text) for text in case_strings] - else: - cases = DEFAULT_SWEEP_CASES - - print(f"Starte Sweep mit {len(cases)} Fällen ...") - rows = [] - - for index, (block_size, count, sync_every) in enumerate(cases, start=1): - print(f"[{index}/{len(cases)}] BENCH {block_size} {count} {sync_every}") - status, result = run_bench( - port, - baudrate, - block_size, - count, - sync_every, - timeout_s, - verbose=False, - ) - - if status != 0 or result is None: - rows.append({ - "block": block_size, - "count": count, - "sync_every": sync_every, - "kib_s": "FAIL", - "total_ms": "-", - "sync_ms": "-", - }) - continue - - rows.append({ - "block": result["block"], - "count": result["count"], - "sync_every": result["sync_every"], - "kib_s": result["kib_s"], - "total_ms": result["total_ms"], - "sync_ms": result["sync_ms"], - }) - - print("\nErgebnis-Tabelle") - print("block count sync_every speed(KiB/s) total_ms sync_ms") - print("----- ----- ---------- ------------ -------- -------") - for row in rows: - print( - f"{str(row['block']).rjust(5)} " - f"{str(row['count']).rjust(5)} " - f"{str(row['sync_every']).rjust(10)} " - f"{str(row['kib_s']).rjust(12)} " - f"{str(row['total_ms']).rjust(8)} " - f"{str(row['sync_ms']).rjust(7)}" - ) - - numeric_rows = [row for row in rows if isinstance(row["kib_s"], int)] - if numeric_rows: - best = max(numeric_rows, key=lambda row: row["kib_s"]) - print( - "\nBestes Ergebnis: " - f"block={best['block']}, count={best['count']}, sync_every={best['sync_every']}, " - f"speed={best['kib_s']} KiB/s" - ) - - return 0 if numeric_rows else 1 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="LittleFS write benchmark über CDC-Protokoll") - parser.add_argument("-p", "--port", required=True, help="Serieller Port (z.B. COM12)") - parser.add_argument("-b", "--baud", type=int, default=2500000, help="Baudrate (Standard: 2500000)") - parser.add_argument("--block", type=int, default=4096, help="Blockgröße in Bytes (Standard: 4096)") - parser.add_argument("--count", type=int, default=100, help="Anzahl Writes (Standard: 100)") - parser.add_argument( - "--sync-every", - type=int, - default=100, - help="fs_sync Intervall in Writes (Standard: 100, also nur am Ende)", - ) - parser.add_argument("--timeout", type=float, default=45.0, help="Timeout in Sekunden (Standard: 45)") - parser.add_argument( - "--sweep", - action="store_true", - help="Führt mehrere vordefinierte BENCH-Fälle nacheinander aus", - ) - parser.add_argument( - "--case", - action="append", - default=[], - help="Eigener Sweep-Fall als block,count,sync_every (mehrfach möglich)", - ) - - args = parser.parse_args() - - if args.sweep: - exit(run_sweep(args.port, args.baud, args.timeout, args.case)) - - status, _ = run_bench( - args.port, - args.baud, - max(args.block, 1), - max(args.count, 1), - max(args.sync_every, 1), - args.timeout, - verbose=True, - ) - exit(status) diff --git a/boards/nrf52840dk_nrf52840.overlay b/boards/nrf52840dk_nrf52840.overlay index 861f4cc..d7f6c57 100644 --- a/boards/nrf52840dk_nrf52840.overlay +++ b/boards/nrf52840dk_nrf52840.overlay @@ -6,6 +6,7 @@ status-led = &led2; buzzer-button = &button0; audio-i2s = &i2s0; + usb-uart = &cdc_acm_uart0; }; chosen { diff --git a/prj.conf b/prj.conf index 5cac18d..650cdc1 100644 --- a/prj.conf +++ b/prj.conf @@ -44,4 +44,5 @@ CONFIG_NRFX_I2S=y CONFIG_HWINFO=y CONFIG_ENTROPY_GENERATOR=y CONFIG_BOOT_BANNER=n -CONFIG_NCS_BOOT_BANNER=n \ No newline at end of file +CONFIG_NCS_BOOT_BANNER=n +CONFIG_CRC=y \ No newline at end of file diff --git a/send_file.py b/send_file.py deleted file mode 100644 index 80a9b89..0000000 --- a/send_file.py +++ /dev/null @@ -1,321 +0,0 @@ -import argparse -import os -import sys -import time -import zlib - -import serial - -REQUIRED_PROTOCOL_VERSION = 3 - - -def calculate_crc32(file_path): - with open(file_path, "rb") as file_handle: - data = file_handle.read() - return data, len(data), zlib.crc32(data) & 0xFFFFFFFF - - -def wait_for_tokens(serial_port, timeout_s, accepted_tokens): - deadline = time.monotonic() + timeout_s - last_line = "" - - while time.monotonic() < deadline: - raw = serial_port.readline() - if not raw: - continue - - line = raw.decode("utf-8", errors="ignore").strip() - if not line: - continue - - last_line = line - if line in accepted_tokens: - return True, line - if line == "ERR": - return False, line - - return False, last_line - - -def query_device_info(serial_port, timeout_s): - attempts = 3 - per_attempt_timeout = max(timeout_s / attempts, 1.5) - - for _ in range(attempts): - serial_port.reset_input_buffer() - serial_port.write(b"INFO\n") - serial_port.flush() - - deadline = time.monotonic() + per_attempt_timeout - parsed_info = None - - while time.monotonic() < deadline: - raw = serial_port.readline() - if not raw: - continue - - line = raw.decode("utf-8", errors="ignore").strip() - if not line: - continue - - if line == "ERR": - break - - if line == "OK": - if parsed_info is not None: - return parsed_info - continue - - parts = line.split(";") - if len(parts) < 5: - continue - - try: - version = int(parts[0]) - recommended = int(parts[4]) - ack_window = int(parts[5]) if len(parts) >= 6 else 1 - except ValueError: - continue - - if recommended <= 0: - recommended = None - - if ack_window <= 0: - ack_window = 1 - - parsed_info = (version, recommended, ack_window) - - time.sleep(0.15) - - return None, None, None - - -def send_file_once( - port, - baudrate, - target_path, - data, - file_size, - crc, - chunk_size, - timeout_s, - write_timeout_s, - pace_us, -): - with serial.Serial(port, baudrate, timeout=0.2, write_timeout=write_timeout_s) as serial_port: - serial_port.dtr = True - time.sleep(0.1) - serial_port.reset_input_buffer() - - protocol_version, recommended_chunk, ack_window = query_device_info(serial_port, timeout_s) - if protocol_version is None: - print("Fehler: Konnte INFO-Antwort des Geräts nicht auswerten.") - return 1 - - if protocol_version != REQUIRED_PROTOCOL_VERSION: - print( - f"Fehler: Inkompatible Protokollversion {protocol_version} " - f"(erwartet {REQUIRED_PROTOCOL_VERSION})." - ) - return 1 - - if recommended_chunk is not None: - selected_chunk = recommended_chunk - else: - selected_chunk = chunk_size - - selected_chunk = max(64, selected_chunk) - print( - f"Gerät meldet Protokoll v{protocol_version}, " - f"Chunk={selected_chunk}, ACK-Window={ack_window}" - ) - - wait_window = ack_window - - command = f"SEND {target_path} {file_size} {crc} {selected_chunk}\n" - print(f"Verbindung: {port} @ {baudrate}") - print(f"Sende Befehl: {command.strip()} (CRC32=0x{crc:08X})") - serial_port.write(command.encode("utf-8")) - serial_port.flush() - - ready_ok, ready_response = wait_for_tokens(serial_port, timeout_s, {"OK"}) - if not ready_ok: - print(f"Fehler: Gerät nicht bereit. Antwort: '{ready_response}'") - return 1 - - print(f"Übertrage {file_size} Bytes in Blöcken à {selected_chunk} Bytes ...") - print(f"Warte auf CONT alle {wait_window} Chunks") - sent = 0 - chunks_since_ack = 0 - start = time.monotonic() - - while sent < file_size: - end = min(sent + selected_chunk, file_size) - try: - serial_port.write(data[sent:end]) - except serial.SerialTimeoutException: - time.sleep(0.02) - continue - - sent = end - chunks_since_ack += 1 - - if pace_us > 0: - time.sleep(pace_us / 1_000_000.0) - - progress = int((sent * 100) / file_size) if file_size else 100 - sys.stdout.write(f"\rFortschritt: {sent}/{file_size} Bytes ({progress}%)") - sys.stdout.flush() - - if sent < file_size and chunks_since_ack >= wait_window: - cont_ok, cont_response = wait_for_tokens(serial_port, timeout_s, {"CONT"}) - if not cont_ok: - print(f"\nFehler beim Chunk-Ack: '{cont_response}'") - return 1 - chunks_since_ack = 0 - - serial_port.flush() - duration = max(time.monotonic() - start, 0.001) - rate_kib_s = (file_size / 1024.0) / duration - print(f"\nUpload beendet: {rate_kib_s:.1f} KiB/s") - - final_ok, final_response = wait_for_tokens(serial_port, timeout_s, {"OK"}) - if final_ok: - print("Übertragung erfolgreich abgeschlossen (CRC geprüft).") - return 0 - - print(f"Fehler beim Abschluss: '{final_response}'") - return 1 - - -def send_file( - port, - baudrate, - file_path, - target_path, - chunk_size, - timeout_s, - retries, - write_timeout_s, - pace_us, -): - if not os.path.exists(file_path): - print(f"Fehler: Lokale Datei '{file_path}' nicht gefunden.") - return 1 - - if not target_path.startswith("/"): - print("Fehler: Zielpfad muss mit '/' beginnen (z.B. /lfs/test).") - return 1 - - data, file_size, crc = calculate_crc32(file_path) - attempts = retries + 1 - - for attempt in range(1, attempts + 1): - if attempt > 1: - print(f"\nNeuer Versuch {attempt}/{attempts} ...") - - try: - result = send_file_once( - port, - baudrate, - target_path, - data, - file_size, - crc, - chunk_size, - timeout_s, - write_timeout_s, - pace_us, - ) - if result == 0: - return 0 - except serial.SerialException as error: - print(f"Serial Fehler: {error}") - except Exception as error: - print(f"Allgemeiner Fehler: {error}") - - if attempt < attempts: - time.sleep(0.4) - - print(f"Upload fehlgeschlagen nach {attempts} Versuch(en).") - return 1 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Datei über Serial-Protokoll an Zephyr/nRF senden" - ) - - parser.add_argument( - "-p", - "--port", - required=True, - help="Serieller Port (z.B. COM12 oder /dev/ttyACM0)", - ) - parser.add_argument( - "-f", - "--file", - required=True, - help="Lokaler Pfad zur Datei", - ) - parser.add_argument( - "-t", - "--target", - default="/lfs/test", - help="Zielpfad auf dem Gerät (Standard: /lfs/test)", - ) - parser.add_argument( - "-b", - "--baud", - type=int, - default=115200, - help="Baudrate (Standard: 115200)", - ) - parser.add_argument( - "--chunk-size", - type=int, - default=1024, - help="Fallback-Chunkgröße in Bytes falls INFO keine Empfehlung liefert (Standard: 1024)", - ) - parser.add_argument( - "--timeout", - type=float, - default=8.0, - help="Timeout für Geräteantworten in Sekunden (Standard: 8)", - ) - parser.add_argument( - "--retries", - type=int, - default=0, - help="Anzahl automatischer Wiederholungen bei Fehlern (Standard: 0)", - ) - parser.add_argument( - "--write-timeout", - type=float, - default=0.0, - help="Serial write timeout in Sekunden (0 = blockierend, Standard: 0)", - ) - parser.add_argument( - "--pace-us", - type=int, - default=300, - help="Pause nach jedem Block in Mikrosekunden (Standard: 300)", - ) - arguments = parser.parse_args() - retry_count = max(arguments.retries, 0) - chunk_size = max(arguments.chunk_size, 64) - pace_us = max(arguments.pace_us, 0) - write_timeout_s = None if arguments.write_timeout <= 0 else arguments.write_timeout - sys.exit( - send_file( - arguments.port, - arguments.baud, - arguments.file, - arguments.target, - chunk_size, - arguments.timeout, - retry_count, - write_timeout_s, - pace_us, - ) - ) diff --git a/src/audio.c b/src/audio.c index bfeaa37..f545d34 100644 --- a/src/audio.c +++ b/src/audio.c @@ -9,7 +9,7 @@ #include #include -LOG_MODULE_REGISTER(audio, LOG_LEVEL_DBG); +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); @@ -196,7 +196,6 @@ void audio_thread(void *arg1, void *arg2, void *arg3) { bool is_playing = false; io_status(false); - uint32_t queued = 0; rc = audio_prepare_random_file(); diff --git a/src/fs.c b/src/fs.c index dde5774..cb50124 100644 --- a/src/fs.c +++ b/src/fs.c @@ -1,6 +1,6 @@ #include #include -LOG_MODULE_REGISTER(buzz_fs, LOG_LEVEL_DBG); +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); diff --git a/src/io.c b/src/io.c index fe3b1fb..761a034 100644 --- a/src/io.c +++ b/src/io.c @@ -3,7 +3,7 @@ #include #include -LOG_MODULE_REGISTER(io, LOG_LEVEL_DBG); +LOG_MODULE_REGISTER(io, LOG_LEVEL_INF); #define STATUS_LED_NODE DT_ALIAS(status_led) #define USB_LED_NODE DT_ALIAS(usb_led) diff --git a/src/main.c b/src/main.c index de58a7e..d5cdef7 100644 --- a/src/main.c +++ b/src/main.c @@ -44,7 +44,6 @@ static int print_custom_banner(void) 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[22;37mBuild Time: \x1b[1;37m%-10s %8s │\n", __DATE__, __TIME__); printk("└───────────────────────────────────────────┘\x1b[0m\n"); return 0; @@ -71,6 +70,12 @@ int main(void) 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); diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..206b097 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,366 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#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); \ No newline at end of file diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 0000000..9a8a852 --- /dev/null +++ b/src/protocol.h @@ -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 \ No newline at end of file diff --git a/src/usb.c b/src/usb.c index 93c13f5..e11d28c 100644 --- a/src/usb.c +++ b/src/usb.c @@ -2,80 +2,136 @@ #include #include #include -#include -#if defined(CONFIG_SOC_SERIES_NRF52X) -#include -#elif defined(CONFIG_SOC_SERIES_STM32G0X) -// STM32 spezifische Header hier -#else -#error "Unsupported SOC Series for VBUS detection" -#endif #include LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG); -/* Semaphore oder Event-Flag zur Signalisierung der VBUS-Präsenz */ -K_SEM_DEFINE(usb_vbus_detected_sem, 0, 1); -K_SEM_DEFINE(usb_vbus_removed_sem, 0, 1); -static atomic_t usb_vbus_present = ATOMIC_INIT(0); +K_SEM_DEFINE(usb_rx_sem, 0, 1); +K_SEM_DEFINE(usb_tx_sem, 0, 1); -/* Forward Declarations */ -static void usb_status_cb(enum usb_dc_status_code cb_status, const uint8_t *param); +#define UART_NODE DT_ALIAS(usb_uart) +const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE); -/* Hardware-spezifische VBUS Detection für nRF52 */ -#if defined(CONFIG_SOC_SERIES_NRF52X) -static void vbus_handler(nrfx_power_usb_evt_t event) +static void cdc_acm_irq_cb(const struct device *dev, void *user_data) { - if (event == NRFX_POWER_USB_EVT_DETECTED) { - if (atomic_cas(&usb_vbus_present, 0, 1)) { - LOG_INF("VBUS detected (nRF52)"); - k_sem_give(&usb_vbus_detected_sem); - } - } else if (event == NRFX_POWER_USB_EVT_READY) { - LOG_DBG("VBUS ready event (nRF52)"); - } else if (event == NRFX_POWER_USB_EVT_REMOVED) { - if (atomic_cas(&usb_vbus_present, 1, 0)) { - LOG_INF("VBUS removed (nRF52)"); - k_sem_give(&usb_vbus_removed_sem); + 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); } } } -#endif + +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 handling..."); - - /* nRF52 benötigt die Aktivierung der VBUS-Events */ - #if defined(CONFIG_SOC_SERIES_NRF52X) - nrfx_err_t err; + LOG_DBG("Initializing USB Stack..."); - if (!nrfx_power_init_check()) { - err = nrfx_power_init(NULL); - if ((err != NRFX_SUCCESS) && (err != NRFX_ERROR_ALREADY)) { - LOG_ERR("nrfx_power_init failed: %d", err); - return -EIO; - } - } - - static const nrfx_power_usbevt_config_t usb_config = { .handler = vbus_handler }; - nrfx_power_usbevt_init(&usb_config); - - nrfx_power_usbevt_enable(); - - if (nrfx_power_usbstatus_get() != NRFX_POWER_USB_STATE_DISCONNECTED) { - LOG_DBG("VBUS already present at boot"); - atomic_set(&usb_vbus_present, 1); - k_sem_give(&usb_vbus_detected_sem); - } - #endif - - return 0; -} - -int usb_cdc_acm_start(void) -{ + /* 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); @@ -90,95 +146,11 @@ int usb_cdc_acm_start(void) return -ENODEV; } - (void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1); - (void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1); - #else +#else LOG_ERR("CDC ACM UART device not found in devicetree"); return -ENODEV; - #endif +#endif - io_usb_status(true); - LOG_DBG("USB CDC ACM enabled"); + LOG_DBG("USB Stack enabled and waiting for VBUS in hardware"); return 0; -} - -int usb_cdc_acm_stop(void) -{ - int ret = usb_disable(); - if (ret != 0) { - LOG_ERR("Failed to disable USB (%d)", ret); - return ret; - } - io_usb_status(false); - LOG_DBG("USB CDC ACM disabled"); - return 0; -} - -/* Der USB Management Thread */ -void usb_thread(void *p1, void *p2, void *p3) -{ - bool usb_enabled = false; - - if (usb_cdc_acm_init() != 0) { - LOG_ERR("USB init failed"); - return; - } - - while (1) { - if (!usb_enabled) { - k_sem_take(&usb_vbus_detected_sem, K_FOREVER); - - if (!atomic_get(&usb_vbus_present)) { - continue; - } - - k_sleep(K_MSEC(100)); - - if (!atomic_get(&usb_vbus_present)) { - continue; - } - - if (usb_cdc_acm_start() == 0) { - usb_enabled = true; - } - } else { - k_sem_take(&usb_vbus_removed_sem, K_FOREVER); - - if (atomic_get(&usb_vbus_present)) { - continue; - } - - if (usb_cdc_acm_stop() == 0) { - usb_enabled = false; - } - } - } -} - -K_THREAD_DEFINE(usb_mgmt_tid, 1024, usb_thread, NULL, NULL, NULL, 7, 0, 0); - -static void usb_status_cb(enum usb_dc_status_code cb_status, const uint8_t *param) -{ - switch (cb_status) { - case USB_DC_CONNECTED: - LOG_INF("USB device connected"); - break; - case USB_DC_CONFIGURED: - LOG_INF("USB device configured"); - break; - case USB_DC_RESET: - LOG_INF("USB bus reset"); - break; - case USB_DC_DISCONNECTED: - LOG_INF("USB device disconnected"); - break; - case USB_DC_SUSPEND: - LOG_DBG("USB device suspended"); - break; - case USB_DC_RESUME: - LOG_DBG("USB device resumed"); - break; - default: - break; - } } \ No newline at end of file diff --git a/src/usb.h b/src/usb.h index 4c6425c..84fb6ac 100644 --- a/src/usb.h +++ b/src/usb.h @@ -6,4 +6,45 @@ * @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 \ No newline at end of file