This commit is contained in:
1
firmware/apps/_samples/audio/.gitignore
vendored
Normal file
1
firmware/apps/_samples/audio/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build*/
|
||||
10
firmware/apps/_samples/audio/CMakeLists.txt
Normal file
10
firmware/apps/_samples/audio/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
|
||||
# Tell Zephyr to look into our libs folder for extra modules
|
||||
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs)
|
||||
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
|
||||
project(_mcumgr)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
44
firmware/apps/_samples/audio/nrf52840dk_nrf52840.overlay
Normal file
44
firmware/apps/_samples/audio/nrf52840dk_nrf52840.overlay
Normal file
@@ -0,0 +1,44 @@
|
||||
// To get started, press Ctrl+Space (or Option+Esc) to bring up the completion menu and view the available nodes.
|
||||
|
||||
// You can also use the buttons in the sidebar to perform actions on nodes.
|
||||
// Actions currently available include:
|
||||
|
||||
// * Enabling / disabling the node
|
||||
// * Adding the bus to a bus
|
||||
// * Removing the node
|
||||
// * Connecting ADC channels
|
||||
|
||||
// For more help, browse the DeviceTree documentation at https://docs.zephyrproject.org/latest/guides/dts/index.html
|
||||
// You can also visit the nRF DeviceTree extension documentation at https://docs.nordicsemi.com/bundle/nrf-connect-vscode/page/guides/ncs_configure_app.html#devicetree-support-in-the-extension
|
||||
|
||||
/ {
|
||||
chosen {
|
||||
nordic,pm-ext-flash = &mx25r64;
|
||||
};
|
||||
};
|
||||
|
||||
&pinctrl {
|
||||
i2s0_default: i2s0_default {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(I2S_SCK_M, 0, 31)>, /* SCK Pin */
|
||||
<NRF_PSEL(I2S_LRCK_M, 0, 30)>, /* WS/LRCK Pin */
|
||||
<NRF_PSEL(I2S_SDOUT, 0, 29)>; /* SD Pin (DIN am MAX) */
|
||||
};
|
||||
};
|
||||
|
||||
i2s0_sleep: i2s0_sleep {
|
||||
group1 {
|
||||
psels = <NRF_PSEL(I2S_SCK_M, 0, 31)>,
|
||||
<NRF_PSEL(I2S_LRCK_M, 0, 30)>,
|
||||
<NRF_PSEL(I2S_SDOUT, 0, 29)>;
|
||||
low-power-enable;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
&i2s0 {
|
||||
status = "okay";
|
||||
pinctrl-0 = <&i2s0_default>;
|
||||
pinctrl-1 = <&i2s0_sleep>;
|
||||
pinctrl-names = "default", "sleep";
|
||||
};
|
||||
4
firmware/apps/_samples/audio/pm_static.yml
Normal file
4
firmware/apps/_samples/audio/pm_static.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
littlefs_storage:
|
||||
address: 0x0
|
||||
size: 0x800000
|
||||
region: external_flash
|
||||
29
firmware/apps/_samples/audio/prj.conf
Normal file
29
firmware/apps/_samples/audio/prj.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
CONFIG_LOG=y
|
||||
|
||||
# UART-Grundlagen
|
||||
CONFIG_SERIAL=y
|
||||
CONFIG_UART_INTERRUPT_DRIVEN=y
|
||||
|
||||
# Shell-Konfiguration
|
||||
CONFIG_SHELL=y
|
||||
CONFIG_SHELL_BACKEND_SERIAL=y
|
||||
|
||||
# # MCU Manager
|
||||
# CONFIG_NET_BUF=y
|
||||
# CONFIG_ZCBOR=y
|
||||
# CONFIG_MCUMGR=y
|
||||
# CONFIG_BASE64=y
|
||||
# CONFIG_CRC=y
|
||||
# CONFIG_MCUMGR_TRANSPORT_SHELL=y
|
||||
|
||||
# # MCUMGR Gruppen
|
||||
# CONFIG_MCUMGR_GRP_OS=y
|
||||
# CONFIG_MCUMGR_GRP_OS_ECHO=y
|
||||
# CONFIG_MCUMGR_GRP_FS=y
|
||||
# CONFIG_MCUMGR_GRP_FS_CHECKSUM_HASH=y
|
||||
|
||||
# Lasertag-spezifische Konfiguration
|
||||
CONFIG_FS_MGMT=y
|
||||
CONFIG_FS_MGMT_LOG_LEVEL_DBG=y
|
||||
CONFIG_AUDIO=y
|
||||
CONFIG_AUDIO_LOG_LEVEL_DBG=y
|
||||
36
firmware/apps/_samples/audio/src/main.c
Normal file
36
firmware/apps/_samples/audio/src/main.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <fs_mgmt.h>
|
||||
#include <audio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(MMS, LOG_LEVEL_INF);
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int err;
|
||||
LOG_INF("Audio test snippet");
|
||||
err = fs_mgmt_init();
|
||||
if (err)
|
||||
{
|
||||
LOG_ERR("Failed to initialize fs_mgmt: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
k_sleep(K_MSEC(100)); // Give some time for the filesystem to initialize
|
||||
err = audio_init();
|
||||
if (err)
|
||||
{
|
||||
LOG_ERR("Failed to initialize audio: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
LOG_INF("Triggering first sound...");
|
||||
audio_play_sound("s1");
|
||||
audio_play_sound("dead");
|
||||
audio_play_sound("g1");
|
||||
audio_play_sound("s1");
|
||||
audio_play_sound("dead");
|
||||
audio_play_sound("g1");
|
||||
return 0;
|
||||
}
|
||||
2
firmware/apps/_samples/audio/tool/requirements.txt
Normal file
2
firmware/apps/_samples/audio/tool/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
pyserial
|
||||
cbor2
|
||||
120
firmware/apps/_samples/audio/tool/tool.py
Normal file
120
firmware/apps/_samples/audio/tool/tool.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import serial
|
||||
import base64
|
||||
import cbor2
|
||||
import struct
|
||||
import time
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
# Icons (NerdFont / Emoji)
|
||||
ICON_DIR = "📁"
|
||||
ICON_FILE = "📄"
|
||||
|
||||
class nRF_FS_Client:
|
||||
def __init__(self, port, baud):
|
||||
try:
|
||||
self.ser = serial.Serial(port, baud, timeout=0.2)
|
||||
self.seq = 0
|
||||
self.ser.reset_input_buffer()
|
||||
except serial.SerialException as e:
|
||||
print(f"Fehler: Konnte {port} nicht öffnen ({e})")
|
||||
sys.exit(1)
|
||||
|
||||
def crc16(self, data):
|
||||
crc = 0x0000
|
||||
for byte in data:
|
||||
crc ^= (byte << 8)
|
||||
for _ in range(8):
|
||||
if crc & 0x8000:
|
||||
crc = (crc << 1) ^ 0x1021
|
||||
else:
|
||||
crc = crc << 1
|
||||
crc &= 0xFFFF
|
||||
return crc
|
||||
|
||||
def build_packet(self, group, cmd, payload):
|
||||
self.seq = (self.seq + 1) % 256
|
||||
cbor_payload = cbor2.dumps(payload)
|
||||
header = struct.pack(">BBHHBB", 0x00, 0x08, len(cbor_payload), group, self.seq, cmd)
|
||||
full_body = header + cbor_payload
|
||||
checksum = self.crc16(full_body)
|
||||
full_msg = full_body + struct.pack(">H", checksum)
|
||||
return struct.pack(">H", len(full_msg)) + full_msg
|
||||
|
||||
def request(self, group, cmd, payload):
|
||||
packet = self.build_packet(group, cmd, payload)
|
||||
b64_data = base64.b64encode(packet).decode()
|
||||
self.ser.write(f"\x06\t{b64_data}\n".encode())
|
||||
|
||||
full_response_b64 = ""
|
||||
expected_len = -1
|
||||
start_time = time.time()
|
||||
|
||||
while (time.time() - start_time) < 3.0:
|
||||
line = self.ser.readline().strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
is_smp = line.startswith(b'\x06\t') or line.startswith(b'\x06\n')
|
||||
is_cont_special = line.startswith(b'\x04\x14') and expected_len > 0
|
||||
|
||||
if is_smp or is_cont_special:
|
||||
full_response_b64 += line[2:].decode()
|
||||
try:
|
||||
raw_data = base64.b64decode(full_response_b64)
|
||||
if expected_len == -1 and len(raw_data) >= 2:
|
||||
expected_len = struct.unpack(">H", raw_data[:2])[0]
|
||||
|
||||
if expected_len != -1 and len(raw_data) >= expected_len + 2:
|
||||
if raw_data[8] == self.seq:
|
||||
return cbor2.loads(raw_data[10:-2])
|
||||
except:
|
||||
continue
|
||||
return None
|
||||
|
||||
def list_recursive(self, path="/", prefix=""):
|
||||
res = self.request(64, 0, {"path": path})
|
||||
if res is None or 'files' not in res:
|
||||
return
|
||||
|
||||
# Sortierung: Verzeichnisse zuerst, dann Namen
|
||||
entries = sorted(res['files'], key=lambda x: (x.get('t', 'f') != 'd', x['n']))
|
||||
count = len(entries)
|
||||
|
||||
for i, entry in enumerate(entries):
|
||||
is_last = (i == count - 1)
|
||||
name = entry['n']
|
||||
is_dir = entry.get('t', 'f').startswith('d')
|
||||
|
||||
# Line-Art Auswahl
|
||||
# connector = "└── " if is_last else "├── "
|
||||
connector = "└─ " if is_last else "├─ "
|
||||
|
||||
|
||||
print(f"{prefix}{connector}{ICON_DIR if is_dir else ICON_FILE} {name}")
|
||||
|
||||
if is_dir:
|
||||
# Prefix für die nächste Ebene erweitern
|
||||
extension = " " if is_last else "│ "
|
||||
sub_path = f"{path}/{name}".replace("//", "/")
|
||||
self.list_recursive(sub_path, prefix + extension)
|
||||
|
||||
def close(self):
|
||||
if hasattr(self, 'ser') and self.ser.is_open:
|
||||
self.ser.close()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="nRF52840 LittleFS Tree Tool")
|
||||
parser.add_argument("port", help="Serieller Port (z.B. /dev/cu.usbmodem...)")
|
||||
args = parser.parse_args()
|
||||
|
||||
client = nRF_FS_Client(args.port, 115200)
|
||||
print(f"--- Dateistruktur auf nRF ({args.port}) ---")
|
||||
try:
|
||||
# Initialer Aufruf
|
||||
client.list_recursive("/")
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
firmware/apps/_samples/watchdog
Submodule
1
firmware/apps/_samples/watchdog
Submodule
Submodule firmware/apps/_samples/watchdog added at 10ca5017c5
@@ -1,8 +1,8 @@
|
||||
# Add library subdirectories
|
||||
# Build ble_mgmt and thread_mgmt first since lasertag_utils depends on them
|
||||
add_subdirectory(ble_mgmt)
|
||||
add_subdirectory(thread_mgmt)
|
||||
add_subdirectory(lasertag_utils)
|
||||
add_subdirectory(ir)
|
||||
add_subdirectory(game_mgmt)
|
||||
add_subdirectory(fs_mgmt)
|
||||
add_subdirectory(fs_mgmt)
|
||||
add_subdirectory(audio)
|
||||
@@ -4,4 +4,5 @@ rsource "thread_mgmt/Kconfig"
|
||||
rsource "ble_mgmt/Kconfig"
|
||||
rsource "ir/Kconfig"
|
||||
rsource "game_mgmt/Kconfig"
|
||||
rsource "fs_mgmt/Kconfig"
|
||||
rsource "fs_mgmt/Kconfig"
|
||||
rsource "audio/Kconfig"
|
||||
5
firmware/libs/audio/CMakeLists.txt
Normal file
5
firmware/libs/audio/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
if(CONFIG_AUDIO)
|
||||
zephyr_library()
|
||||
zephyr_sources(src/audio.c)
|
||||
zephyr_include_directories(include)
|
||||
endif()
|
||||
76
firmware/libs/audio/Kconfig
Normal file
76
firmware/libs/audio/Kconfig
Normal file
@@ -0,0 +1,76 @@
|
||||
menuconfig AUDIO
|
||||
bool "Audio Support"
|
||||
depends on FS_MGMT
|
||||
select I2S
|
||||
select I2S_NRFX
|
||||
help
|
||||
Library for initializing and managing the audio subsystem.
|
||||
|
||||
if AUDIO
|
||||
config AUDIO_DEFAULT_VOLUME
|
||||
int "Default Audio Volume (0..255)"
|
||||
default 128
|
||||
range 0 255
|
||||
help
|
||||
Set the default audio volume level. 0 is silent, 255 is maximum volume. Default is 128 (50% volume).
|
||||
|
||||
config AUDIO_SAMPLE_RATE
|
||||
int "Audio Sample Rate (Hz)"
|
||||
default 16000
|
||||
range 8000 48000
|
||||
help
|
||||
Set the audio sample rate in Hz. Common values are 8000, 16000, 44100, and 48000 Hz. Default is 16000 Hz.
|
||||
|
||||
config AUDIO_BIT_WIDTH
|
||||
int "Audio Bit Depth"
|
||||
default 16
|
||||
range 8 32
|
||||
help
|
||||
Set the audio bit depth. Common values are 8, 16, 24, and 32 bits. Default is 16 bits.
|
||||
|
||||
config AUDIO_BLOCK_COUNT
|
||||
int "Audio Block Count"
|
||||
default 4
|
||||
range 1 16
|
||||
help
|
||||
Set the number of audio blocks for buffering. More blocks can help with smoother audio but use more memory. Default is 4 blocks.
|
||||
|
||||
config AUDIO_BLOCK_SIZE
|
||||
int "Audio Block Size (bytes)"
|
||||
default 1024
|
||||
range 256 4096
|
||||
help
|
||||
Set the size of each audio block in bytes. Larger blocks can reduce CPU overhead but increase latency. Default is 1024 bytes.
|
||||
|
||||
config AUDIO_THREAD_PRIORITY
|
||||
int "Audio Thread Priority"
|
||||
default 5
|
||||
range 0 255
|
||||
help
|
||||
Set the thread priority for audio processing. Lower numbers indicate higher priority. Default is 5
|
||||
|
||||
config AUDIO_STACK_SIZE
|
||||
int "Audio Thread Stack Size (bytes)"
|
||||
default 2048
|
||||
range 256 8192
|
||||
help
|
||||
Set the stack size for the audio processing thread in bytes. Default is 2048 bytes.
|
||||
|
||||
config AUDIO_SAMPLE_FOLDER
|
||||
string "Audio Sample Folder"
|
||||
default "a"
|
||||
help
|
||||
Set the folder path where audio sample files are stored. No leading or trailing slashes. Default is "a".
|
||||
|
||||
config AUDIO_MAX_PATH_LEN
|
||||
int "Maximum Audio File Path Length"
|
||||
default 16
|
||||
range 8 128
|
||||
help
|
||||
Set the maximum length for audio file paths. Default is 16 characters.
|
||||
|
||||
# Logging configuration for the Audio module
|
||||
module = AUDIO
|
||||
module-str = audio
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif # AUDIO
|
||||
10
firmware/libs/audio/include/audio.h
Normal file
10
firmware/libs/audio/include/audio.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef AUDIO_H
|
||||
#define AUDIO_H
|
||||
|
||||
int audio_init(void);
|
||||
int audio_play_sound(const char* file);
|
||||
int audio_play_file(const char* file);
|
||||
void audio_stop(void);
|
||||
|
||||
#endif // AUDIO_H
|
||||
|
||||
244
firmware/libs/audio/src/audio.c
Normal file
244
firmware/libs/audio/src/audio.c
Normal file
@@ -0,0 +1,244 @@
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/i2s.h>
|
||||
#include <zephyr/fs/fs.h>
|
||||
#include <audio.h>
|
||||
#include <string.h>
|
||||
|
||||
LOG_MODULE_REGISTER(audio, CONFIG_AUDIO_LOG_LEVEL);
|
||||
|
||||
/* Get the I2S device from the devicetree */
|
||||
#define I2S_NODE DT_NODELABEL(i2s0)
|
||||
static const struct device *i2s_dev = DEVICE_DT_GET(I2S_NODE);
|
||||
|
||||
/* Memory Slab for I2S DMA */
|
||||
K_MEM_SLAB_DEFINE(audio_slab, CONFIG_AUDIO_BLOCK_SIZE, CONFIG_AUDIO_BLOCK_COUNT, 4);
|
||||
|
||||
/* Globals */
|
||||
static volatile bool abort_playback = false;
|
||||
static volatile uint8_t audio_volume = CONFIG_AUDIO_DEFAULT_VOLUME;
|
||||
|
||||
static void wait_for_i2s_drain(void)
|
||||
{
|
||||
const uint32_t frames_per_block = CONFIG_AUDIO_BLOCK_SIZE / 4;
|
||||
const uint32_t block_ms = (frames_per_block * 1000U) / CONFIG_AUDIO_SAMPLE_RATE;
|
||||
const uint32_t max_wait_ms = (block_ms + 1U) * CONFIG_AUDIO_BLOCK_COUNT + 5U;
|
||||
int64_t deadline = k_uptime_get() + max_wait_ms;
|
||||
|
||||
while (k_mem_slab_num_free_get(&audio_slab) < CONFIG_AUDIO_BLOCK_COUNT)
|
||||
{
|
||||
if (k_uptime_get() >= deadline)
|
||||
{
|
||||
LOG_WRN("Timeout waiting for I2S drain");
|
||||
break;
|
||||
}
|
||||
k_sleep(K_MSEC(1));
|
||||
}
|
||||
}
|
||||
|
||||
/* Message Queue: transfers the file path to the thread */
|
||||
K_MSGQ_DEFINE(audio_msgq, CONFIG_AUDIO_MAX_PATH_LEN, 10, 4);
|
||||
|
||||
/* Audio thread function */
|
||||
void audio_thread_fn(void *p1, void *p2, void *p3)
|
||||
{
|
||||
ARG_UNUSED(p1);
|
||||
ARG_UNUSED(p2);
|
||||
ARG_UNUSED(p3);
|
||||
|
||||
char file_path[CONFIG_AUDIO_MAX_PATH_LEN];
|
||||
struct fs_file_t file;
|
||||
fs_file_t_init(&file);
|
||||
|
||||
LOG_DBG("Audio thread started, priority %d", k_thread_priority_get(k_current_get()));
|
||||
while (1)
|
||||
{
|
||||
bool trigger_started = false;
|
||||
if (k_msgq_get(&audio_msgq, &file_path, K_FOREVER) == 0)
|
||||
{
|
||||
|
||||
abort_playback = false;
|
||||
|
||||
if (fs_open(&file, file_path, FS_O_READ) < 0)
|
||||
{
|
||||
LOG_ERR("thread: could not open %s", file_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_DBG("thread: preparing %s...", file_path);
|
||||
|
||||
uint32_t queued_blocks = 0;
|
||||
|
||||
while (!abort_playback)
|
||||
{
|
||||
void *mem_block;
|
||||
if (k_mem_slab_alloc(&audio_slab, &mem_block, K_MSEC(10)) < 0)
|
||||
continue;
|
||||
|
||||
int16_t *data_ptr = (int16_t *)mem_block;
|
||||
const uint32_t max_mono_samples = CONFIG_AUDIO_BLOCK_SIZE / 4;
|
||||
|
||||
ssize_t bytes_read = fs_read(&file, data_ptr, max_mono_samples * sizeof(int16_t));
|
||||
|
||||
if (bytes_read <= 0)
|
||||
{
|
||||
k_mem_slab_free(&audio_slab, mem_block);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t samples_read = bytes_read / sizeof(int16_t);
|
||||
|
||||
// Padding with zeros if we read less than a full block of mono samples
|
||||
if (samples_read < max_mono_samples)
|
||||
{
|
||||
memset(&data_ptr[samples_read], 0, (max_mono_samples - samples_read) * sizeof(int16_t));
|
||||
}
|
||||
|
||||
uint32_t *stereo_dst = (uint32_t *)mem_block;
|
||||
for (int32_t i = max_mono_samples - 1; i >= 0; i--)
|
||||
{
|
||||
int32_t scaled = (int32_t)data_ptr[i] * audio_volume;
|
||||
int16_t sample = (int16_t)(scaled >> 8);
|
||||
|
||||
stereo_dst[i] = ((uint16_t)sample << 16) | (uint16_t)sample;
|
||||
}
|
||||
|
||||
if (i2s_write(i2s_dev, mem_block, CONFIG_AUDIO_BLOCK_SIZE) < 0)
|
||||
{
|
||||
k_mem_slab_free(&audio_slab, mem_block);
|
||||
break;
|
||||
}
|
||||
|
||||
queued_blocks++;
|
||||
|
||||
// We start playback only when 2 blocks are in the DMA queue to avoid underruns
|
||||
if (!trigger_started && queued_blocks >= 2)
|
||||
{
|
||||
if (i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START) == 0)
|
||||
{
|
||||
trigger_started = true;
|
||||
LOG_DBG("thread: playback started.");
|
||||
}
|
||||
}
|
||||
|
||||
if (samples_read < max_mono_samples)
|
||||
{
|
||||
// Short sample: start with a single queued block so DRAIN can play it.
|
||||
if (!trigger_started)
|
||||
{
|
||||
if (i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START) == 0)
|
||||
{
|
||||
trigger_started = true;
|
||||
LOG_DBG("thread: playback started (short sample).");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (abort_playback)
|
||||
{
|
||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||
trigger_started = false;
|
||||
LOG_DBG("thread: playback aborted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (k_msgq_num_used_get(&audio_msgq) > 0)
|
||||
{
|
||||
LOG_DBG("thread: play request pending, not draining I2S to minimize latency...");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DBG("thread: sample finished, waiting for I2S to drain...");
|
||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
|
||||
trigger_started = false;
|
||||
wait_for_i2s_drain();
|
||||
LOG_DBG("thread: playback finished.");
|
||||
}
|
||||
}
|
||||
fs_close(&file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
K_THREAD_DEFINE(audio_thread, CONFIG_AUDIO_STACK_SIZE, audio_thread_fn, NULL, NULL, NULL, CONFIG_AUDIO_THREAD_PRIORITY, 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 = CONFIG_AUDIO_BIT_WIDTH,
|
||||
.channels = 2,
|
||||
.format = I2S_FMT_DATA_FORMAT_I2S,
|
||||
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
|
||||
.frame_clk_freq = CONFIG_AUDIO_SAMPLE_RATE,
|
||||
.mem_slab = &audio_slab,
|
||||
.block_size = CONFIG_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_DBG("I2S driver initialized and configured for %d Hz", CONFIG_AUDIO_SAMPLE_RATE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audio_play_file(const char *file)
|
||||
{
|
||||
if (file == NULL)
|
||||
{
|
||||
LOG_ERR("audio_play_file: file path is NULL");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size_t len = strnlen(file, CONFIG_AUDIO_MAX_PATH_LEN);
|
||||
if (len >= CONFIG_AUDIO_MAX_PATH_LEN)
|
||||
{
|
||||
LOG_ERR("audio_play_file: file path too long");
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
|
||||
char path_item[CONFIG_AUDIO_MAX_PATH_LEN];
|
||||
memcpy(path_item, file, len + 1);
|
||||
|
||||
if (k_msgq_put(&audio_msgq, path_item, K_NO_WAIT) < 0)
|
||||
{
|
||||
LOG_ERR("audio_play_file: message queue full");
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
LOG_DBG("Queued file for playback: %s", file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audio_play_sound(const char *file)
|
||||
{
|
||||
char path[CONFIG_AUDIO_MAX_PATH_LEN];
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/%s",
|
||||
CONFIG_FS_MGMT_MOUNT_POINT,
|
||||
CONFIG_AUDIO_SAMPLE_FOLDER,
|
||||
file);
|
||||
|
||||
return audio_play_file(path);
|
||||
}
|
||||
|
||||
void audio_stop(void)
|
||||
{
|
||||
abort_playback = true;
|
||||
k_msgq_purge(&audio_msgq);
|
||||
LOG_DBG("Playback stop requested, message queue purged");
|
||||
}
|
||||
@@ -9,6 +9,12 @@ menuconfig FS_MGMT
|
||||
Library for initializing and managing the file system.
|
||||
|
||||
if FS_MGMT
|
||||
config FS_MGMT_MOUNT_POINT
|
||||
string "Littlefs Mount Point"
|
||||
default "/lfs"
|
||||
help
|
||||
Set the mount point for the Littlefs file system. Default is "/lfs".
|
||||
|
||||
config FS_MGMT_MCUMGR_HANDLER
|
||||
bool "Enable Custom MCUMGR FS Handlers"
|
||||
default y
|
||||
|
||||
@@ -12,7 +12,7 @@ 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",
|
||||
.mnt_point = CONFIG_FS_MGMT_MOUNT_POINT,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_FS_MGMT_MCUMGR_HANDLER
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
if(CONFIG_BLE_MGMT)
|
||||
if(CONFIG_GAME_MGMT)
|
||||
zephyr_library()
|
||||
zephyr_sources(src/game_mgmt.c)
|
||||
zephyr_include_directories(include)
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
add_subdirectory(send)
|
||||
add_subdirectory(recv)
|
||||
if(CONFIG_IR_SEND)
|
||||
add_subdirectory(send)
|
||||
endif()
|
||||
|
||||
if(CONFIG_IR_RECV)
|
||||
add_subdirectory(recv)
|
||||
endif()
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
menuconfig LASERTAG_UTILS
|
||||
bool "Lasertag Utilities"
|
||||
depends on BLE_MGMT
|
||||
select SETTINGS
|
||||
select FLASH
|
||||
select NVS
|
||||
help
|
||||
Enable shared logic for all lasertag devices (Vest, Weapon, Leader).
|
||||
|
||||
if LASERTAG_UTILS
|
||||
config LASERTAG_UTILS_LOG_LEVEL
|
||||
int "Utility Log Level"
|
||||
default 3
|
||||
|
||||
config LASERTAG_UTILS_WD_TIMEOUT
|
||||
int "Watchdog Timeout (ms)"
|
||||
range 1000 60000
|
||||
default 5000
|
||||
help
|
||||
Set the verbosity of the lasertag utility library.
|
||||
Set the timeout for the lasertag utility watchdog timer.
|
||||
Minimum is 1000 ms, maximum is 60000 ms (1 minute). Default is 5000 ms (5 seconds).
|
||||
|
||||
config LASERTAG_SHELL
|
||||
bool "Enable Lasertag Shell Commands"
|
||||
@@ -17,4 +22,9 @@ if LASERTAG_UTILS
|
||||
depends on SHELL
|
||||
help
|
||||
Provides commands like 'lasertag name' and 'lasertag reboot'.
|
||||
endif
|
||||
|
||||
# Logging configuration for the Lasertag Utilities module
|
||||
module = LASERTAG_UTILS
|
||||
module-str = lt_utils
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
endif
|
||||
|
||||
@@ -34,4 +34,14 @@ const char* lasertag_get_device_name(void);
|
||||
*/
|
||||
int lasertag_set_device_name(const char *name, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Initialize the watchdog timer for lasertag utilities.
|
||||
* @return 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int lasertag_init_watchdog(void);
|
||||
|
||||
/**
|
||||
* @brief Feed the watchdog timer to prevent a system reset.
|
||||
*/
|
||||
void lasertag_feed_watchdog(void);
|
||||
#endif /* LASERTAG_UTILS_H */
|
||||
@@ -5,12 +5,11 @@
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <lasertag_utils.h>
|
||||
#include <ble_mgmt.h>
|
||||
#include <thread_mgmt.h>
|
||||
#include <stdio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(lasertag_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL);
|
||||
LOG_MODULE_REGISTER(lt_utils, CONFIG_LASERTAG_UTILS_LOG_LEVEL);
|
||||
|
||||
static char device_name[32] = "Eriks Lasertag Device";
|
||||
|
||||
@@ -55,38 +54,56 @@ int lasertag_set_device_name(const char *name, size_t len)
|
||||
return settings_save_one("lasertag/name", device_name, len);
|
||||
}
|
||||
|
||||
/* --- Shell Commands --- */
|
||||
#ifdef CONFIG_WATCHDOG
|
||||
#include <zephyr/drivers/watchdog.h>
|
||||
#include <zephyr/logging/log_ctrl.h>
|
||||
|
||||
#if CONFIG_LASERTAG_SHELL
|
||||
static int cmd_reboot(const struct shell *sh, size_t argc, char **argv)
|
||||
#define WDT_NODE DT_ALIAS(watchdog0)
|
||||
|
||||
static int wdt_channel_id;
|
||||
static const struct device *wdt = DEVICE_DT_GET(WDT_NODE);
|
||||
|
||||
int lasertag_init_watchdog(void)
|
||||
{
|
||||
shell_print(sh, "Rebooting...");
|
||||
sys_reboot(SYS_REBOOT_COLD);
|
||||
struct wdt_timeout_cfg wdt_config = {
|
||||
.window.min = 0U,
|
||||
.window.max = CONFIG_LASERTAG_UTILS_WD_TIMEOUT,
|
||||
.flags = WDT_FLAG_RESET_SOC,
|
||||
};
|
||||
|
||||
if (!device_is_ready(wdt)) {
|
||||
LOG_ERR("Watchdog device is not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
wdt_channel_id = wdt_install_timeout(wdt, &wdt_config);
|
||||
if (wdt_channel_id == -EBUSY) {
|
||||
wdt_channel_id = 0;
|
||||
LOG_WRN("Watchdog already running, using channel %d", wdt_channel_id);
|
||||
return 0;
|
||||
}
|
||||
if (wdt_channel_id < 0) {
|
||||
LOG_ERR("Failed to install watchdog timeout");
|
||||
return wdt_channel_id;
|
||||
}
|
||||
|
||||
int rc = wdt_setup(wdt, 0);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to setup watchdog");
|
||||
return rc;
|
||||
}
|
||||
|
||||
LOG_DBG("Watchdog '%s' initialized channel %d with a %d ms timeout", wdt->name, wdt_channel_id, CONFIG_LASERTAG_UTILS_WD_TIMEOUT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_name_set(const struct shell *sh, size_t argc, char **argv)
|
||||
void lasertag_feed_watchdog(void)
|
||||
{
|
||||
lasertag_set_device_name(argv[1], strlen(argv[1]));
|
||||
shell_print(sh, "Name gespeichert.");
|
||||
return 0;
|
||||
int rc = wdt_feed(wdt, wdt_channel_id);
|
||||
if (rc < 0) {
|
||||
LOG_ERR("Failed to feed watchdog, error code: %d", rc);
|
||||
} else {
|
||||
LOG_DBG("Watchdog '%s' fed successfully", wdt->name);
|
||||
}
|
||||
}
|
||||
|
||||
static int cmd_ble_start(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
return ble_mgmt_adv_start();
|
||||
}
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_ble,
|
||||
SHELL_CMD(start, NULL, "Start BLE", cmd_ble_start),
|
||||
SHELL_SUBCMD_SET_END);
|
||||
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_lasertag,
|
||||
SHELL_CMD_ARG(name, NULL, "Set name", cmd_name_set, 2, 0),
|
||||
SHELL_CMD(ble, &sub_ble, "BLE Management", NULL),
|
||||
SHELL_CMD(reboot, NULL, "Reboot", cmd_reboot),
|
||||
SHELL_SUBCMD_SET_END);
|
||||
|
||||
SHELL_CMD_REGISTER(lasertag, &sub_lasertag, "Lasertag Befehle", NULL);
|
||||
|
||||
#endif
|
||||
#endif /* CONFIG_WATCHDOG */
|
||||
2
firmware/tools/littlefs_generator/upload.sh
Normal file → Executable file
2
firmware/tools/littlefs_generator/upload.sh
Normal file → Executable file
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
nrfjprog --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000
|
||||
nrfjprog -s 1050225991 --program lfs_external_flash.hex --qspisectorerase --verify --fast --reset --clockspeed 12000
|
||||
|
||||
Reference in New Issue
Block a user