sync
This commit is contained in:
@@ -9,6 +9,7 @@ target_sources(app PRIVATE
|
|||||||
src/audio.c
|
src/audio.c
|
||||||
src/io.c
|
src/io.c
|
||||||
src/usb.c
|
src/usb.c
|
||||||
|
src/protocol.c
|
||||||
)
|
)
|
||||||
zephyr_include_directories(src)
|
zephyr_include_directories(src)
|
||||||
|
|
||||||
|
|||||||
2
VERSION
2
VERSION
@@ -1,5 +1,5 @@
|
|||||||
VERSION_MAJOR = 0
|
VERSION_MAJOR = 0
|
||||||
VERSION_MINOR = 0
|
VERSION_MINOR = 0
|
||||||
PATCHLEVEL = 1
|
PATCHLEVEL = 2
|
||||||
VERSION_TWEAK = 0
|
VERSION_TWEAK = 0
|
||||||
EXTRAVERSION = 0
|
EXTRAVERSION = 0
|
||||||
238
bench_lfs.py
238
bench_lfs.py
@@ -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)
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
status-led = &led2;
|
status-led = &led2;
|
||||||
buzzer-button = &button0;
|
buzzer-button = &button0;
|
||||||
audio-i2s = &i2s0;
|
audio-i2s = &i2s0;
|
||||||
|
usb-uart = &cdc_acm_uart0;
|
||||||
};
|
};
|
||||||
|
|
||||||
chosen {
|
chosen {
|
||||||
|
|||||||
3
prj.conf
3
prj.conf
@@ -44,4 +44,5 @@ CONFIG_NRFX_I2S=y
|
|||||||
CONFIG_HWINFO=y
|
CONFIG_HWINFO=y
|
||||||
CONFIG_ENTROPY_GENERATOR=y
|
CONFIG_ENTROPY_GENERATOR=y
|
||||||
CONFIG_BOOT_BANNER=n
|
CONFIG_BOOT_BANNER=n
|
||||||
CONFIG_NCS_BOOT_BANNER=n
|
CONFIG_NCS_BOOT_BANNER=n
|
||||||
|
CONFIG_CRC=y
|
||||||
321
send_file.py
321
send_file.py
@@ -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,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <fs.h>
|
#include <fs.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
|
||||||
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_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(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;
|
bool is_playing = false;
|
||||||
io_status(false);
|
io_status(false);
|
||||||
|
|
||||||
uint32_t queued = 0;
|
uint32_t queued = 0;
|
||||||
|
|
||||||
rc = audio_prepare_random_file();
|
rc = audio_prepare_random_file();
|
||||||
|
|||||||
2
src/fs.c
2
src/fs.c
@@ -1,6 +1,6 @@
|
|||||||
#include <zephyr/fs/littlefs.h>
|
#include <zephyr/fs/littlefs.h>
|
||||||
#include <fs.h>
|
#include <fs.h>
|
||||||
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)
|
#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(littlefs_storage)
|
||||||
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
||||||
|
|||||||
2
src/io.c
2
src/io.c
@@ -3,7 +3,7 @@
|
|||||||
#include <zephyr/drivers/gpio.h>
|
#include <zephyr/drivers/gpio.h>
|
||||||
#include <audio.h>
|
#include <audio.h>
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(io, LOG_LEVEL_DBG);
|
LOG_MODULE_REGISTER(io, LOG_LEVEL_INF);
|
||||||
|
|
||||||
#define STATUS_LED_NODE DT_ALIAS(status_led)
|
#define STATUS_LED_NODE DT_ALIAS(status_led)
|
||||||
#define USB_LED_NODE DT_ALIAS(usb_led)
|
#define USB_LED_NODE DT_ALIAS(usb_led)
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ static int print_custom_banner(void)
|
|||||||
printk("├───────────────────────────────────────────┤\n");
|
printk("├───────────────────────────────────────────┤\n");
|
||||||
printk("│ \x1b[22;37mZephyr Version: \x1b[1;37m%-20s │\n", KERNEL_VERSION_STRING);
|
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;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");
|
printk("└───────────────────────────────────────────┘\x1b[0m\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -71,6 +70,12 @@ int main(void)
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc = usb_cdc_acm_init();
|
||||||
|
if (rc < 0) {
|
||||||
|
LOG_ERR("USB initialization failed: %d", rc);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
rc = io_init();
|
rc = io_init();
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
LOG_ERR("I/O initialization failed: %d", rc);
|
LOG_ERR("I/O initialization failed: %d", rc);
|
||||||
|
|||||||
366
src/protocol.c
Normal file
366
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
src/protocol.h
Normal file
19
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
|
||||||
264
src/usb.c
264
src/usb.c
@@ -2,80 +2,136 @@
|
|||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
#include <zephyr/usb/usb_device.h>
|
#include <zephyr/usb/usb_device.h>
|
||||||
#include <zephyr/drivers/uart.h>
|
#include <zephyr/drivers/uart.h>
|
||||||
#include <zephyr/sys/atomic.h>
|
|
||||||
#if defined(CONFIG_SOC_SERIES_NRF52X)
|
|
||||||
#include <nrfx_power.h>
|
|
||||||
#elif defined(CONFIG_SOC_SERIES_STM32G0X)
|
|
||||||
// STM32 spezifische Header hier
|
|
||||||
#else
|
|
||||||
#error "Unsupported SOC Series for VBUS detection"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG);
|
LOG_MODULE_REGISTER(usb, LOG_LEVEL_DBG);
|
||||||
|
|
||||||
/* Semaphore oder Event-Flag zur Signalisierung der VBUS-Präsenz */
|
K_SEM_DEFINE(usb_rx_sem, 0, 1);
|
||||||
K_SEM_DEFINE(usb_vbus_detected_sem, 0, 1);
|
K_SEM_DEFINE(usb_tx_sem, 0, 1);
|
||||||
K_SEM_DEFINE(usb_vbus_removed_sem, 0, 1);
|
|
||||||
static atomic_t usb_vbus_present = ATOMIC_INIT(0);
|
|
||||||
|
|
||||||
/* Forward Declarations */
|
#define UART_NODE DT_ALIAS(usb_uart)
|
||||||
static void usb_status_cb(enum usb_dc_status_code cb_status, const uint8_t *param);
|
const struct device *cdc_dev = DEVICE_DT_GET(UART_NODE);
|
||||||
|
|
||||||
/* Hardware-spezifische VBUS Detection für nRF52 */
|
static void cdc_acm_irq_cb(const struct device *dev, void *user_data)
|
||||||
#if defined(CONFIG_SOC_SERIES_NRF52X)
|
|
||||||
static void vbus_handler(nrfx_power_usb_evt_t event)
|
|
||||||
{
|
{
|
||||||
if (event == NRFX_POWER_USB_EVT_DETECTED) {
|
ARG_UNUSED(user_data);
|
||||||
if (atomic_cas(&usb_vbus_present, 0, 1)) {
|
|
||||||
LOG_INF("VBUS detected (nRF52)");
|
if (!uart_irq_update(dev)) {
|
||||||
k_sem_give(&usb_vbus_detected_sem);
|
return;
|
||||||
}
|
}
|
||||||
} else if (event == NRFX_POWER_USB_EVT_READY) {
|
|
||||||
LOG_DBG("VBUS ready event (nRF52)");
|
if (uart_irq_rx_ready(dev)) {
|
||||||
} else if (event == NRFX_POWER_USB_EVT_REMOVED) {
|
uart_irq_rx_disable(dev);
|
||||||
if (atomic_cas(&usb_vbus_present, 1, 0)) {
|
k_sem_give(&usb_rx_sem);
|
||||||
LOG_INF("VBUS removed (nRF52)");
|
LOG_DBG("RX interrupt: data available");
|
||||||
k_sem_give(&usb_vbus_removed_sem);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
int usb_cdc_acm_init(void)
|
||||||
{
|
{
|
||||||
LOG_DBG("Initializing USB handling...");
|
LOG_DBG("Initializing USB Stack...");
|
||||||
|
|
||||||
/* nRF52 benötigt die Aktivierung der VBUS-Events */
|
|
||||||
#if defined(CONFIG_SOC_SERIES_NRF52X)
|
|
||||||
nrfx_err_t err;
|
|
||||||
|
|
||||||
if (!nrfx_power_init_check()) {
|
/* Zephyr-Treiber registrieren. Verbraucht keinen Strom ohne VBUS. */
|
||||||
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)
|
|
||||||
{
|
|
||||||
int ret = usb_enable(usb_status_cb);
|
int ret = usb_enable(usb_status_cb);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
LOG_ERR("Failed to enable USB (%d)", ret);
|
LOG_ERR("Failed to enable USB (%d)", ret);
|
||||||
@@ -90,95 +146,11 @@ int usb_cdc_acm_start(void)
|
|||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
||||||
(void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1);
|
#else
|
||||||
(void)uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1);
|
|
||||||
#else
|
|
||||||
LOG_ERR("CDC ACM UART device not found in devicetree");
|
LOG_ERR("CDC ACM UART device not found in devicetree");
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
io_usb_status(true);
|
LOG_DBG("USB Stack enabled and waiting for VBUS in hardware");
|
||||||
LOG_DBG("USB CDC ACM enabled");
|
|
||||||
return 0;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
41
src/usb.h
41
src/usb.h
@@ -6,4 +6,45 @@
|
|||||||
* @return 0 on success, negative error code on failure
|
* @return 0 on success, negative error code on failure
|
||||||
*/
|
*/
|
||||||
int usb_cdc_acm_init(void);
|
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
|
#endif // USB_CDC_ACM_H
|
||||||
Reference in New Issue
Block a user