Compare commits
11 Commits
947346777f
...
upgrade/nc
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fed249430 | |||
| 7381173026 | |||
| e85d51488e | |||
| 12e82d35d2 | |||
| 7a2428f54d | |||
| fca4aeb115 | |||
| 35639a2615 | |||
| 5d20d2820a | |||
| ad2dc19641 | |||
| 79676e2e1e | |||
| e8373bd0c0 |
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -2,5 +2,9 @@
|
|||||||
"svelte.plugin.svelte.format.config.printWidth": 300,
|
"svelte.plugin.svelte.format.config.printWidth": 300,
|
||||||
"nrf-connect.applications": [
|
"nrf-connect.applications": [
|
||||||
"${workspaceFolder}/firmware"
|
"${workspaceFolder}/firmware"
|
||||||
]
|
],
|
||||||
|
"nrf-connect.boardRoots": [
|
||||||
|
"${workspaceFolder}/firmware"
|
||||||
|
],
|
||||||
|
"cmake.sourceDirectory": "C:/Projekte/buzzer_2/firmware/libs/ble_mgmt"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
VERSION_MAJOR = 0
|
VERSION_MAJOR = 0
|
||||||
VERSION_MINOR = 0
|
VERSION_MINOR = 0
|
||||||
PATCHLEVEL = 2
|
PATCHLEVEL = 6
|
||||||
VERSION_TWEAK = 0
|
VERSION_TWEAK = 0
|
||||||
#if (IS_ENABLED(CONFIG_LOG))
|
#if (IS_ENABLED(CONFIG_LOG))
|
||||||
EXTRAVERSION = debug
|
EXTRAVERSION = debug
|
||||||
|
|||||||
11
firmware/boards/buzzy_nrf52840.conf
Normal file
11
firmware/boards/buzzy_nrf52840.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
### Console / Logging: RTT
|
||||||
|
CONFIG_USE_SEGGER_RTT=y
|
||||||
|
CONFIG_CONSOLE=y
|
||||||
|
CONFIG_RTT_CONSOLE=y
|
||||||
|
|
||||||
|
CONFIG_UART_CONSOLE=n
|
||||||
|
CONFIG_LOG_BACKEND_UART=n
|
||||||
|
CONFIG_LOG_BACKEND_RTT=y
|
||||||
|
|
||||||
|
# Keep SPI NOR page layout aligned with generated LittleFS block size (4KB).
|
||||||
|
CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
|
||||||
2
firmware/boards/iten/buzzy/Kconfig.buzzy
Normal file
2
firmware/boards/iten/buzzy/Kconfig.buzzy
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
config BOARD_BUZZY
|
||||||
|
select SOC_NRF52840_QIAA
|
||||||
9
firmware/boards/iten/buzzy/board.cmake
Normal file
9
firmware/boards/iten/buzzy/board.cmake
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
set(OPENOCD_NRF5_SUBFAMILY "nrf52")
|
||||||
|
board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000")
|
||||||
|
board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000")
|
||||||
|
|
||||||
|
include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake)
|
||||||
|
include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake)
|
||||||
|
include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake)
|
||||||
|
include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake)
|
||||||
|
include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake)
|
||||||
6
firmware/boards/iten/buzzy/board.yml
Normal file
6
firmware/boards/iten/buzzy/board.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
board:
|
||||||
|
name: buzzy
|
||||||
|
full_name: Buzzy
|
||||||
|
vendor: iten
|
||||||
|
socs:
|
||||||
|
- name: nrf52840
|
||||||
64
firmware/boards/iten/buzzy/buzzy-pinctrl.dtsi
Normal file
64
firmware/boards/iten/buzzy/buzzy-pinctrl.dtsi
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>
|
||||||
|
|
||||||
|
&pinctrl {
|
||||||
|
i2s0_default: i2s0_default {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(I2S_SCK_M, 0, 3)>, /* BCLK an P0.03 */
|
||||||
|
<NRF_PSEL(I2S_LRCK_M, 0, 28)>, /* LRCLK an P0.28 */
|
||||||
|
<NRF_PSEL(I2S_SDOUT, 1, 10)>; /* DIN an P1.10 */
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
i2s0_sleep: i2s0_sleep {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(I2S_SCK_M, 0, 3)>,
|
||||||
|
<NRF_PSEL(I2S_LRCK_M, 0, 28)>,
|
||||||
|
<NRF_PSEL(I2S_SDOUT, 1, 10)>;
|
||||||
|
low-power-enable;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
spi3_default: spi3_default {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(SPIM_SCK, 0, 2)>,
|
||||||
|
<NRF_PSEL(SPIM_MOSI, 0, 29)>,
|
||||||
|
<NRF_PSEL(SPIM_MISO, 0, 30)>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
spi3_sleep: spi3_sleep {
|
||||||
|
group1 {
|
||||||
|
psels = <NRF_PSEL(SPIM_SCK, 0, 2)>,
|
||||||
|
<NRF_PSEL(SPIM_MOSI, 0, 29)>,
|
||||||
|
<NRF_PSEL(SPIM_MISO, 0, 30)>;
|
||||||
|
low-power-enable;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional future QSPI pinctrl states (keep disabled for now).
|
||||||
|
* Use these when switching from &spi3 to &qspi in buzzy.dts.
|
||||||
|
*/
|
||||||
|
// qspi_default: qspi_default {
|
||||||
|
// group1 {
|
||||||
|
// psels = <NRF_PSEL(QSPI_SCK, 0, 2)>,
|
||||||
|
// <NRF_PSEL(QSPI_CSN, 0, 5)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO0, 0, 29)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO1, 0, 30)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO2, 0, 31)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO3, 1, 13)>;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// qspi_sleep: qspi_sleep {
|
||||||
|
// group1 {
|
||||||
|
// psels = <NRF_PSEL(QSPI_SCK, 0, 2)>,
|
||||||
|
// <NRF_PSEL(QSPI_CSN, 0, 5)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO0, 0, 29)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO1, 0, 30)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO2, 0, 31)>,
|
||||||
|
// <NRF_PSEL(QSPI_IO3, 1, 13)>;
|
||||||
|
// low-power-enable;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
};
|
||||||
275
firmware/boards/iten/buzzy/buzzy.dts
Normal file
275
firmware/boards/iten/buzzy/buzzy.dts
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
/dts-v1/;
|
||||||
|
#include <nordic/nrf52840_qiaa.dtsi>
|
||||||
|
#include <zephyr/dt-bindings/adc/adc.h>
|
||||||
|
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
|
||||||
|
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||||
|
#include <zephyr/dt-bindings/regulator/nrf5x.h>
|
||||||
|
#include "buzzy-pinctrl.dtsi"
|
||||||
|
|
||||||
|
/ {
|
||||||
|
model = "Buzzy";
|
||||||
|
compatible = "iten,buzzy";
|
||||||
|
|
||||||
|
chosen {
|
||||||
|
zephyr,sram = &sram0;
|
||||||
|
zephyr,flash = &flash0;
|
||||||
|
zephyr,code-partition = &slot0_partition;
|
||||||
|
nordic,pm-ext-flash = &mx25r64;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* SD_MODE pin for MAX98357A power gating */
|
||||||
|
dac_pwr: dac-pwr {
|
||||||
|
compatible = "regulator-fixed";
|
||||||
|
regulator-name = "max98357a-sd-mode";
|
||||||
|
enable-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Simple SW1 setup: internal pull-up on P1.09, active low when pressed. */
|
||||||
|
buttons {
|
||||||
|
compatible = "gpio-keys";
|
||||||
|
sw1: sw1 {
|
||||||
|
gpios = <&gpio1 9 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
|
||||||
|
// gpios = <&gpio1 9 (GPIO_ACTIVE_LOW)>;
|
||||||
|
label = "SW1";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* P0.24 is kept as a normal runtime-controlled GPIO (no gpio-hog).
|
||||||
|
* The app can drive this pin high/low to use it as external SW1 pull-up source.
|
||||||
|
*/
|
||||||
|
sw1_ctrl {
|
||||||
|
compatible = "gpio-leds";
|
||||||
|
sw1_pullup: sw1_pullup {
|
||||||
|
gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
|
||||||
|
label = "SW1_PULLUP_CTRL";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ETA6003 charger status input on P0.13.
|
||||||
|
* Note: CHG status is routed through discrete logic (Q1 path), so polarity may
|
||||||
|
* need to be inverted in software. If needed, switch to GPIO_ACTIVE_LOW.
|
||||||
|
*/
|
||||||
|
charger_status {
|
||||||
|
compatible = "gpio-keys";
|
||||||
|
chg_status: chg_status {
|
||||||
|
gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
|
||||||
|
label = "ETA6003_CHG_STATUS";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ETA6003 charge-current select (FCHG) on P0.09:
|
||||||
|
* - 0V -> 500 mA
|
||||||
|
* - 1.8V -> 1 A
|
||||||
|
*/
|
||||||
|
charger_ctrl {
|
||||||
|
compatible = "gpio-leds";
|
||||||
|
chg_fast: chg_fast {
|
||||||
|
gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||||
|
label = "ETA6003_FAST_CHARGE";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* VDDH battery voltage measurement via internal VDDHDIV5 input.
|
||||||
|
* The SAADC measures VDDH/5; multiply by 5 in software to get the actual VDDH in mV.
|
||||||
|
* 3.1V threshold -> SAADC reads ~620 mV.
|
||||||
|
*/
|
||||||
|
zephyr_user: zephyr,user {
|
||||||
|
io-channels = <&adc 0>;
|
||||||
|
io-channel-names = "vddh";
|
||||||
|
};
|
||||||
|
|
||||||
|
aliases {
|
||||||
|
dac-pwr = &dac_pwr;
|
||||||
|
sw1 = &sw1;
|
||||||
|
sw1-pullup = &sw1_pullup;
|
||||||
|
chg-status = &chg_status;
|
||||||
|
chg-fast = &chg_fast;
|
||||||
|
external-flash = &mx25r64;
|
||||||
|
i2s-audio = &i2s0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&flash0 {
|
||||||
|
partitions {
|
||||||
|
compatible = "fixed-partitions";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
|
boot_partition: partition@0 {
|
||||||
|
label = "mcuboot";
|
||||||
|
reg = <0x00000000 DT_SIZE_K(48)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
slot0_partition: partition@c000 {
|
||||||
|
label = "image-0";
|
||||||
|
reg = <0x0000c000 DT_SIZE_K(472)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
slot1_partition: partition@82000 {
|
||||||
|
label = "image-1";
|
||||||
|
reg = <0x00082000 DT_SIZE_K(472)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
storage_partition: partition@f8000 {
|
||||||
|
label = "storage";
|
||||||
|
reg = <0x000f8000 DT_SIZE_K(32)>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* I2S0 is used for audio output; pins are configured in the pinctrl file. */
|
||||||
|
&i2s0 {
|
||||||
|
status = "okay";
|
||||||
|
pinctrl-0 = <&i2s0_default>;
|
||||||
|
pinctrl-1 = <&i2s0_sleep>;
|
||||||
|
pinctrl-names = "default", "sleep";
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* External flash over classic SPI (bring-up-first path).
|
||||||
|
* Net mapping from hardware:
|
||||||
|
* SCLK -> P0.02
|
||||||
|
* SI/SIO0-> P0.29
|
||||||
|
* SO/SIO1-> P0.30
|
||||||
|
* CS -> P0.05
|
||||||
|
*/
|
||||||
|
&spi3 {
|
||||||
|
status = "okay";
|
||||||
|
pinctrl-0 = <&spi3_default>;
|
||||||
|
pinctrl-1 = <&spi3_sleep>;
|
||||||
|
pinctrl-names = "default", "sleep";
|
||||||
|
cs-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
|
||||||
|
|
||||||
|
mx25r64: flash@0 {
|
||||||
|
compatible = "jedec,spi-nor";
|
||||||
|
reg = <0>;
|
||||||
|
spi-max-frequency = <8000000>;
|
||||||
|
jedec-id = [c2 28 17];
|
||||||
|
size = <DT_SIZE_M(64)>;
|
||||||
|
has-dpd;
|
||||||
|
t-enter-dpd = <10000>;
|
||||||
|
t-exit-dpd = <35000>;
|
||||||
|
|
||||||
|
partitions {
|
||||||
|
compatible = "fixed-partitions";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
|
ext_flash_lfs: partition@0 {
|
||||||
|
label = "ext-littlefs";
|
||||||
|
reg = <0x00000000 DT_SIZE_M(8)>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional future QSPI variant (keep disabled for now):
|
||||||
|
* - Disable &spi3 block above.
|
||||||
|
* - Enable &qspi block below.
|
||||||
|
* - Keep the same flash partition layout.
|
||||||
|
*/
|
||||||
|
// &qspi {
|
||||||
|
// status = "okay";
|
||||||
|
// pinctrl-0 = <&qspi_default>;
|
||||||
|
// pinctrl-1 = <&qspi_sleep>;
|
||||||
|
// pinctrl-names = "default", "sleep";
|
||||||
|
|
||||||
|
// mx25r64: flash@0 {
|
||||||
|
// compatible = "nordic,qspi-nor";
|
||||||
|
// reg = <0>;
|
||||||
|
// jedec-id = [c2 28 17];
|
||||||
|
// size = <DT_SIZE_M(64)>;
|
||||||
|
// has-dpd;
|
||||||
|
// t-enter-dpd = <10000>;
|
||||||
|
// t-exit-dpd = <35000>;
|
||||||
|
|
||||||
|
// /* Net mapping from hardware: *
|
||||||
|
// * SCK=P0.02, CSN=P0.05, IO0=P0.29, IO1=P0.30, IO2=P0.31, IO3=P1.13
|
||||||
|
// */
|
||||||
|
// sck-pin = <2>;
|
||||||
|
// csn-pins = <5>;
|
||||||
|
// io-pins = <29>, <30>, <31>, <45>;
|
||||||
|
|
||||||
|
// partitions {
|
||||||
|
// compatible = "fixed-partitions";
|
||||||
|
// #address-cells = <1>;
|
||||||
|
// #size-cells = <1>;
|
||||||
|
|
||||||
|
// ext_flash_lfs: partition@0 {
|
||||||
|
// label = "ext-littlefs";
|
||||||
|
// reg = <0x00000000 DT_SIZE_M(8)>;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
&gpio0 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&gpio1 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&gpiote {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
/* DC/DC mode for regulator 1 (core voltage regulator). */
|
||||||
|
®1 {
|
||||||
|
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DC/DC mode for regulator 0 (I/O voltage regulator, VDD).
|
||||||
|
* Default output is 1.8V, suitable for on-board peripherals (flash).
|
||||||
|
*/
|
||||||
|
®0 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Optional gpio-hog variant for external pull-up via R8 from P0.24 to P1.09:
|
||||||
|
* - Keep this disabled when you want runtime control over P0.24.
|
||||||
|
* - Remove GPIO_PULL_UP from SW1 gpios above (keep only GPIO_ACTIVE_LOW).
|
||||||
|
* - Enable the node below to drive P0.24 high from boot.
|
||||||
|
*/
|
||||||
|
// &gpio0 {
|
||||||
|
// sw1_pullup_hog: sw1_pullup_hog {
|
||||||
|
// gpio-hog;
|
||||||
|
// gpios = <24 GPIO_ACTIVE_HIGH>;
|
||||||
|
// output-high;
|
||||||
|
// line-name = "SW1_PULLUP_SRC";
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Battery voltage measurement via internal VDDHDIV5 channel.
|
||||||
|
* No external pin needed; VDDH is divided by 5 internally.
|
||||||
|
*/
|
||||||
|
&adc {
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
channel@0 {
|
||||||
|
reg = <0>;
|
||||||
|
zephyr,gain = "ADC_GAIN_1_6";
|
||||||
|
zephyr,reference = "ADC_REF_INTERNAL";
|
||||||
|
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)>;
|
||||||
|
zephyr,resolution = <12>;
|
||||||
|
zephyr,input-positive = <NRF_SAADC_VDDHDIV5>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Use NFC pins as GPIOs. */
|
||||||
|
&uicr {
|
||||||
|
nfct-pins-as-gpios;
|
||||||
|
};
|
||||||
10
firmware/boards/iten/buzzy/buzzy.yaml
Normal file
10
firmware/boards/iten/buzzy/buzzy.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
identifier: buzzy/nrf52840
|
||||||
|
name: Buzzy
|
||||||
|
vendor: iten
|
||||||
|
type: mcu
|
||||||
|
arch: arm
|
||||||
|
ram: 256
|
||||||
|
flash: 1024
|
||||||
|
toolchain:
|
||||||
|
- zephyr
|
||||||
|
supported: []
|
||||||
5
firmware/boards/iten/buzzy/buzzy_defconfig
Normal file
5
firmware/boards/iten/buzzy/buzzy_defconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CONFIG_ARM_MPU=y
|
||||||
|
CONFIG_HW_STACK_PROTECTION=y
|
||||||
|
|
||||||
|
CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y
|
||||||
|
CONFIG_CLOCK_CONTROL_NRF_K32SRC_20PPM=y
|
||||||
2
firmware/boards/iten/buzzy/pre_dt_board.cmake
Normal file
2
firmware/boards/iten/buzzy/pre_dt_board.cmake
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Suppress "unique_unit_address_if_enabled" to handle some overlaps
|
||||||
|
list(APPEND EXTRA_DTC_FLAGS "-Wno-unique_unit_address_if_enabled")
|
||||||
4
firmware/boards/nrf52840dk_nrf52840.conf
Normal file
4
firmware/boards/nrf52840dk_nrf52840.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### Console / Logging: UART
|
||||||
|
CONFIG_UART_CONSOLE=y
|
||||||
|
CONFIG_LOG_BACKEND_UART=y
|
||||||
|
CONFIG_LOG_BACKEND_RTT=n
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
nordic,pm-ext-flash = &mx25r64;
|
nordic,pm-ext-flash = &mx25r64;
|
||||||
};
|
};
|
||||||
aliases {
|
aliases {
|
||||||
|
external-flash = &mx25r64;
|
||||||
qspi-flash = &mx25r64;
|
qspi-flash = &mx25r64;
|
||||||
i2s-audio = &i2s0;
|
i2s-audio = &i2s0;
|
||||||
};
|
};
|
||||||
|
|||||||
8
firmware/debug.conf
Normal file
8
firmware/debug.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
### Logging
|
||||||
|
CONFIG_LOG=y
|
||||||
|
CONFIG_AUDIO_LOG_LEVEL_DBG=y
|
||||||
|
|
||||||
|
CONFIG_DEBUG_OPTIMIZATIONS=y
|
||||||
|
|
||||||
|
CONFIG_INIT_STACKS=y
|
||||||
|
CONFIG_THREAD_STACK_INFO=y
|
||||||
3
firmware/fs/.gitignore
vendored
Normal file
3
firmware/fs/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
lfs_external_flash.hex
|
||||||
|
.tmp_lfs_build/
|
||||||
|
*.wav
|
||||||
86
firmware/fs/README.md
Normal file
86
firmware/fs/README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# FS Tooling
|
||||||
|
|
||||||
|
This directory contains the tooling to build and flash the external LittleFS image used by the firmware.
|
||||||
|
|
||||||
|
## Python Version
|
||||||
|
|
||||||
|
Use Python 3.11.
|
||||||
|
|
||||||
|
The filesystem/audio build tool is currently tested with Python 3.11.
|
||||||
|
Python 3.13 is not supported at the moment, especially on Windows, because some dependencies in the TTS toolchain may fall back to source builds and fail to install.
|
||||||
|
|
||||||
|
In particular, the dependency chain around `kokoro`, `spacy`, `thinc`, and `blis` may fail to install on Windows when no compatible wheels are available.
|
||||||
|
|
||||||
|
## Install Dependencies
|
||||||
|
|
||||||
|
`ffmpeg` is required in addition to the Python packages.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3.11 -m venv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
py -3.11 -m venv venv
|
||||||
|
venv\Scripts\activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build LittleFS Image
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./venv/bin/python build_lfs_audio.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with imported WAV files and a custom system prompt YAML:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./venv/bin/python build_lfs_audio.py \
|
||||||
|
--wav-dir ./my_wavs \
|
||||||
|
--sys-yaml ./sys_prompts.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `--wav-dir` imports all `.wav` files from the given directory into `/lfs/a`
|
||||||
|
- `--sys-yaml` overrides the default TTS prompt list for `/lfs/sys`
|
||||||
|
- generated audio is converted to mono 16 kHz PCM before it is written into the LittleFS image
|
||||||
|
|
||||||
|
This generates:
|
||||||
|
|
||||||
|
- `lfs_external_flash.hex`
|
||||||
|
|
||||||
|
The default image layout is aligned to a 4 KiB flash page layout.
|
||||||
|
|
||||||
|
After the build, the script also prints a usage summary for `/lfs/sys`, `/lfs/a`, and the combined image.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Usage summary:
|
||||||
|
sys: files=4, raw=48.0 KiB, fs=56.0 KiB (14 blocks, standalone estimate)
|
||||||
|
audio: files=12, raw=224.0 KiB, fs=264.0 KiB (66 blocks, standalone estimate)
|
||||||
|
total: raw=272.0 KiB, fs=300.0 KiB (75 blocks in combined image)
|
||||||
|
```
|
||||||
|
|
||||||
|
`raw` is the sum of file sizes in the staging tree.
|
||||||
|
`fs` is the estimated or measured LittleFS space usage with the configured block size.
|
||||||
|
|
||||||
|
## Flash Image
|
||||||
|
|
||||||
|
On macOS/Linux:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./program.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows:
|
||||||
|
|
||||||
|
```bat
|
||||||
|
program.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
Both scripts use paths relative to their own location, so they can be started from any working directory.
|
||||||
282
firmware/fs/build_lfs_audio.py
Normal file
282
firmware/fs/build_lfs_audio.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import soundfile as sf
|
||||||
|
import yaml
|
||||||
|
from intelhex import IntelHex
|
||||||
|
from kokoro import KPipeline
|
||||||
|
from littlefs import LittleFS
|
||||||
|
|
||||||
|
DEFAULT_SAMPLE_RATE = 16000
|
||||||
|
DEFAULT_VOICE = "af_bella"
|
||||||
|
DEFAULT_BLOCK_SIZE = 4096
|
||||||
|
DEFAULT_BLOCK_COUNT = 2048
|
||||||
|
DEFAULT_READ_SIZE = 512
|
||||||
|
DEFAULT_LOOKAHEAD_SIZE = 256
|
||||||
|
DEFAULT_FILTERS = [
|
||||||
|
"highpass=f=120",
|
||||||
|
"lowpass=f=6000",
|
||||||
|
"acompressor=threshold=-18dB:ratio=3:attack=5:release=80",
|
||||||
|
"loudnorm=I=-16:TP=-1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
DEFAULT_SYS_PROMPTS = [
|
||||||
|
{"id": "404", "text": "No sound sample was found on the device."},
|
||||||
|
{"id": "update", "text": "Firmware updated. Awaiting confirmation."},
|
||||||
|
{"id": "confirm", "text": "State confirmed."},
|
||||||
|
{"id": "voltest", "text": "Volume test. This is a sample of the current volume level."},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run_ffmpeg(cmd: list[str]) -> None:
|
||||||
|
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
def export_raw_pcm(input_path: Path, output_path: Path, sample_rate: int, filters: list[str]) -> None:
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
filter_str = ",".join(filters)
|
||||||
|
run_ffmpeg([
|
||||||
|
"ffmpeg",
|
||||||
|
"-y",
|
||||||
|
"-i",
|
||||||
|
str(input_path),
|
||||||
|
"-af",
|
||||||
|
filter_str,
|
||||||
|
"-ar",
|
||||||
|
str(sample_rate),
|
||||||
|
"-ac",
|
||||||
|
"1",
|
||||||
|
"-f",
|
||||||
|
"s16le",
|
||||||
|
"-acodec",
|
||||||
|
"pcm_s16le",
|
||||||
|
str(output_path),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def load_sys_prompts(yaml_path: Path | None) -> list[dict]:
|
||||||
|
if yaml_path is None:
|
||||||
|
return DEFAULT_SYS_PROMPTS
|
||||||
|
|
||||||
|
with open(yaml_path, "r", encoding="utf-8") as f:
|
||||||
|
data = yaml.safe_load(f) or []
|
||||||
|
|
||||||
|
prompts: list[dict] = []
|
||||||
|
if isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
if isinstance(item, dict) and item.get("id") and item.get("text"):
|
||||||
|
prompts.append({"id": item["id"], "text": item["text"]})
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
for item in data.get("assets", []):
|
||||||
|
if isinstance(item, dict) and item.get("id") and item.get("text"):
|
||||||
|
prompts.append({"id": item["id"], "text": item["text"]})
|
||||||
|
|
||||||
|
return prompts
|
||||||
|
|
||||||
|
|
||||||
|
def build_sys_tts(prompts: list[dict], out_sys_dir: Path, sample_rate: int) -> int:
|
||||||
|
pipeline = KPipeline(lang_code="a")
|
||||||
|
generated = 0
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix="tts_tmp_") as tmp_dir_name:
|
||||||
|
tmp_dir = Path(tmp_dir_name)
|
||||||
|
|
||||||
|
for prompt in prompts:
|
||||||
|
asset_id = prompt["id"]
|
||||||
|
text = prompt["text"]
|
||||||
|
|
||||||
|
tmp_wav = tmp_dir / f"{asset_id}.wav"
|
||||||
|
generator = pipeline(text, voice=DEFAULT_VOICE, speed=1.0)
|
||||||
|
for _, _, audio in generator:
|
||||||
|
sf.write(tmp_wav, audio, 24000)
|
||||||
|
break
|
||||||
|
|
||||||
|
out_file = out_sys_dir / asset_id
|
||||||
|
export_raw_pcm(tmp_wav, out_file, sample_rate=sample_rate, filters=DEFAULT_FILTERS)
|
||||||
|
generated += 1
|
||||||
|
|
||||||
|
return generated
|
||||||
|
|
||||||
|
|
||||||
|
def import_wavs_to_a(wav_dir: Path | None, out_a_dir: Path, sample_rate: int) -> int:
|
||||||
|
if wav_dir is None:
|
||||||
|
return 0
|
||||||
|
if not wav_dir.exists() or not wav_dir.is_dir():
|
||||||
|
raise FileNotFoundError(f"WAV directory not found: {wav_dir}")
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for wav_file in sorted(wav_dir.glob("*.wav")):
|
||||||
|
out_file = out_a_dir / wav_file.stem
|
||||||
|
export_raw_pcm(wav_file, out_file, sample_rate=sample_rate, filters=DEFAULT_FILTERS)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def add_files_recursive(fs: LittleFS, local_path: Path, lfs_path: str = "/") -> None:
|
||||||
|
if not local_path.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in sorted(local_path.iterdir()):
|
||||||
|
target_path = f"{lfs_path.rstrip('/')}/{item.name}".replace("//", "/")
|
||||||
|
if item.is_file():
|
||||||
|
with open(item, "rb") as f:
|
||||||
|
with fs.open(target_path, "wb") as lfs_file:
|
||||||
|
lfs_file.write(f.read())
|
||||||
|
elif item.is_dir():
|
||||||
|
fs.mkdir(target_path)
|
||||||
|
add_files_recursive(fs, item, target_path)
|
||||||
|
|
||||||
|
|
||||||
|
def build_littlefs_image(source_folder: Path, block_size: int, block_count: int) -> tuple[LittleFS, int]:
|
||||||
|
fs = LittleFS(
|
||||||
|
block_size=block_size,
|
||||||
|
block_count=block_count,
|
||||||
|
read_size=DEFAULT_READ_SIZE,
|
||||||
|
prog_size=256,
|
||||||
|
lookahead_size=DEFAULT_LOOKAHEAD_SIZE,
|
||||||
|
cache_size=4096,
|
||||||
|
)
|
||||||
|
|
||||||
|
add_files_recursive(fs, source_folder)
|
||||||
|
|
||||||
|
used_blocks = 0
|
||||||
|
lfs_buffer = fs.context.buffer
|
||||||
|
for idx in range(block_count):
|
||||||
|
offset = idx * block_size
|
||||||
|
block_data = lfs_buffer[offset : offset + block_size]
|
||||||
|
if any(byte != 0xFF for byte in block_data):
|
||||||
|
used_blocks += 1
|
||||||
|
|
||||||
|
return fs, used_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def build_littlefs_hex(source_folder: Path, output_hex: Path, block_size: int, block_count: int, start_addr: int) -> int:
|
||||||
|
fs, used_blocks = build_littlefs_image(source_folder, block_size, block_count)
|
||||||
|
|
||||||
|
ih = IntelHex()
|
||||||
|
lfs_buffer = fs.context.buffer
|
||||||
|
|
||||||
|
for idx in range(block_count):
|
||||||
|
offset = idx * block_size
|
||||||
|
block_data = lfs_buffer[offset : offset + block_size]
|
||||||
|
if any(byte != 0xFF for byte in block_data):
|
||||||
|
ih.frombytes(block_data, offset=start_addr + offset)
|
||||||
|
|
||||||
|
ih.tofile(str(output_hex), format="hex")
|
||||||
|
return used_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def summarize_directory(directory: Path) -> tuple[int, int]:
|
||||||
|
if not directory.exists():
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
file_count = 0
|
||||||
|
raw_bytes = 0
|
||||||
|
for file_path in directory.rglob("*"):
|
||||||
|
if file_path.is_file():
|
||||||
|
file_count += 1
|
||||||
|
raw_bytes += file_path.stat().st_size
|
||||||
|
|
||||||
|
return file_count, raw_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def format_bytes(size: int) -> str:
|
||||||
|
units = ["B", "KiB", "MiB", "GiB"]
|
||||||
|
value = float(size)
|
||||||
|
for unit in units:
|
||||||
|
if value < 1024.0 or unit == units[-1]:
|
||||||
|
if unit == "B":
|
||||||
|
return f"{int(value)} {unit}"
|
||||||
|
return f"{value:.1f} {unit}"
|
||||||
|
value /= 1024.0
|
||||||
|
|
||||||
|
|
||||||
|
def report_directory_usage(label: str, directory: Path, block_size: int, block_count: int) -> None:
|
||||||
|
file_count, raw_bytes = summarize_directory(directory)
|
||||||
|
_, used_blocks = build_littlefs_image(directory, block_size, block_count)
|
||||||
|
fs_bytes = used_blocks * block_size
|
||||||
|
print(
|
||||||
|
f" {label}: files={file_count}, raw={format_bytes(raw_bytes)}, "
|
||||||
|
f"fs={format_bytes(fs_bytes)} ({used_blocks} blocks, standalone estimate)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=(
|
||||||
|
"Generate Bella-only TTS in /lfs/sys, optionally import WAVs into /lfs/a, "
|
||||||
|
"then create LittleFS HEX."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
parser.add_argument("--wav-dir", default=None, help="Optional directory with WAV files for /lfs/a")
|
||||||
|
parser.add_argument("--sys-yaml", default=None, help="Optional YAML with system prompts (id/text)")
|
||||||
|
parser.add_argument("--sample-rate", type=int, default=DEFAULT_SAMPLE_RATE)
|
||||||
|
parser.add_argument("--block-size", type=int, default=DEFAULT_BLOCK_SIZE)
|
||||||
|
parser.add_argument("--block-count", type=int, default=DEFAULT_BLOCK_COUNT)
|
||||||
|
parser.add_argument("--start-addr", type=lambda x: int(x, 0), default=0x12000000)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-hex",
|
||||||
|
default=str(Path(__file__).resolve().parent / "lfs_external_flash.hex"),
|
||||||
|
help="Output path for generated HEX",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keep-staging",
|
||||||
|
action="store_true",
|
||||||
|
help="Keep temporary staging directory for inspection",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
fs_dir = Path(__file__).resolve().parent
|
||||||
|
output_hex = Path(args.output_hex).resolve()
|
||||||
|
wav_dir = Path(args.wav_dir).resolve() if args.wav_dir else None
|
||||||
|
sys_yaml = Path(args.sys_yaml).resolve() if args.sys_yaml else None
|
||||||
|
|
||||||
|
staging_root = fs_dir / ".tmp_lfs_build"
|
||||||
|
if staging_root.exists():
|
||||||
|
shutil.rmtree(staging_root)
|
||||||
|
|
||||||
|
out_sys = staging_root / "lfs" / "sys"
|
||||||
|
out_a = staging_root / "lfs" / "a"
|
||||||
|
out_sys.mkdir(parents=True, exist_ok=True)
|
||||||
|
out_a.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
prompts = load_sys_prompts(sys_yaml)
|
||||||
|
if not prompts:
|
||||||
|
raise RuntimeError("No system prompts available. Provide --sys-yaml or use defaults.")
|
||||||
|
|
||||||
|
tts_count = build_sys_tts(prompts, out_sys, args.sample_rate)
|
||||||
|
wav_count = import_wavs_to_a(wav_dir, out_a, args.sample_rate)
|
||||||
|
|
||||||
|
total_used_blocks = build_littlefs_hex(
|
||||||
|
source_folder=staging_root,
|
||||||
|
output_hex=output_hex,
|
||||||
|
block_size=args.block_size,
|
||||||
|
block_count=args.block_count,
|
||||||
|
start_addr=args.start_addr,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Done. TTS assets: {tts_count}, WAV imports: {wav_count}, HEX: {output_hex}")
|
||||||
|
print("Usage summary:")
|
||||||
|
report_directory_usage("sys", out_sys, args.block_size, args.block_count)
|
||||||
|
report_directory_usage("audio", out_a, args.block_size, args.block_count)
|
||||||
|
print(
|
||||||
|
f" total: raw={format_bytes(summarize_directory(staging_root)[1])}, "
|
||||||
|
f"fs={format_bytes(total_used_blocks * args.block_size)} "
|
||||||
|
f"({total_used_blocks} blocks in combined image)"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not args.keep_staging and staging_root.exists():
|
||||||
|
shutil.rmtree(staging_root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
23
firmware/fs/buzzy.json
Normal file
23
firmware/fs/buzzy.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"firmware_config": {
|
||||||
|
"peripheral": "QSPI",
|
||||||
|
"compress": true
|
||||||
|
},
|
||||||
|
"pins": {
|
||||||
|
"sck": 2,
|
||||||
|
"csn": 5,
|
||||||
|
"io0": 29,
|
||||||
|
"io1": 30,
|
||||||
|
"io2": 31,
|
||||||
|
"io3": 45
|
||||||
|
},
|
||||||
|
"flash_size": 8388608,
|
||||||
|
"sck_frequency": 8000000,
|
||||||
|
"address_mode": "MODE24BIT",
|
||||||
|
"readoc": "READ4IO",
|
||||||
|
"writeoc": "PP4IO",
|
||||||
|
"pp_size": "PPSIZE256",
|
||||||
|
"sck_delay": 0,
|
||||||
|
"rx_delay": 2,
|
||||||
|
"page_size": 4096
|
||||||
|
}
|
||||||
46
firmware/fs/buzzy.toml
Normal file
46
firmware/fs/buzzy.toml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
[qspi]
|
||||||
|
mem_size = 0x800000
|
||||||
|
read_mode = "READ4IO"
|
||||||
|
write_mode = "PP4IO"
|
||||||
|
address_mode = "BIT24"
|
||||||
|
frequency = "M16"
|
||||||
|
spi_mode = "MODE0"
|
||||||
|
rx_delay = 2
|
||||||
|
wip_index = 0
|
||||||
|
page_program_size = "PAGE256"
|
||||||
|
retain_ram = true
|
||||||
|
|
||||||
|
# Pin-Zuweisungen (angepasst an die Netlist)
|
||||||
|
[qspi.sck]
|
||||||
|
delay = 0x80
|
||||||
|
pin = 2
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
[qspi.csn]
|
||||||
|
pin = 5
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
[qspi.dio0]
|
||||||
|
pin = 29
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
[qspi.dio1]
|
||||||
|
pin = 30
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
[qspi.dio2]
|
||||||
|
pin = 31
|
||||||
|
port = 0
|
||||||
|
|
||||||
|
[qspi.dio3]
|
||||||
|
pin = 13
|
||||||
|
port = 1
|
||||||
|
|
||||||
|
[qspi.custom]
|
||||||
|
io2_level = "LEVEL_LOW"
|
||||||
|
io3_level = "LEVEL_HIGH"
|
||||||
|
# Aktiviert Quad-IO und High-Performance Mode für Macronix MX25R
|
||||||
|
instructions = [
|
||||||
|
{command=0x06, data=[]},
|
||||||
|
{command=0x01, data=[0x40, 0, 0x2]}
|
||||||
|
]
|
||||||
1
firmware/fs/program.bat
Normal file
1
firmware/fs/program.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nrfutil device --x-ext-mem-config-file "%~dp0buzzy.json" program --firmware "%~dp0lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM
|
||||||
4
firmware/fs/program.sh
Executable file
4
firmware/fs/program.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||||
|
|
||||||
|
nrfutil device --x-ext-mem-config-file "$SCRIPT_DIR/buzzy.json" program --firmware "$SCRIPT_DIR/lfs_external_flash.hex" --options verify=VERIFY_READ,reset=RESET_SYSTEM
|
||||||
7
firmware/fs/requirements.txt
Normal file
7
firmware/fs/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
kokoro>=0.1.0
|
||||||
|
soundfile
|
||||||
|
numpy
|
||||||
|
torch
|
||||||
|
PyYAML
|
||||||
|
littlefs-python
|
||||||
|
intelhex
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
if(CONFIG_AUDIO)
|
if(CONFIG_BUZZ_AUDIO)
|
||||||
zephyr_library()
|
zephyr_library()
|
||||||
zephyr_library_sources(src/audio.c)
|
zephyr_library_sources(src/audio.c)
|
||||||
zephyr_include_directories(include)
|
zephyr_include_directories(include)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
menuconfig AUDIO
|
menuconfig BUZZ_AUDIO
|
||||||
bool "Audio handling"
|
bool "Audio handling"
|
||||||
default y
|
default n
|
||||||
select I2S
|
select I2S
|
||||||
select POLL
|
select POLL
|
||||||
|
|
||||||
if AUDIO
|
if BUZZ_AUDIO
|
||||||
config AUDIO_NO_SAMPLES_SAMPLE
|
config AUDIO_NO_SAMPLES_SAMPLE
|
||||||
string "Audio no samples sample"
|
string "Audio no samples sample"
|
||||||
default "404"
|
default "404"
|
||||||
@@ -57,4 +57,4 @@ if AUDIO
|
|||||||
module = AUDIO
|
module = AUDIO
|
||||||
module-str = audio
|
module-str = audio
|
||||||
source "subsys/logging/Kconfig.template.log_config"
|
source "subsys/logging/Kconfig.template.log_config"
|
||||||
endif # AUDIO
|
endif # BUZZ_AUDIO
|
||||||
@@ -13,17 +13,12 @@ const struct device *i2s_dev = DEVICE_DT_GET(DT_ALIAS(i2s_audio));
|
|||||||
|
|
||||||
K_MSGQ_DEFINE(audio_cmd_q, sizeof(struct audio_cmd_msg), 10, 4);
|
K_MSGQ_DEFINE(audio_cmd_q, sizeof(struct audio_cmd_msg), 10, 4);
|
||||||
K_SEM_DEFINE(audio_files_count_sem, 0, 1);
|
K_SEM_DEFINE(audio_files_count_sem, 0, 1);
|
||||||
K_SEM_DEFINE(audio_file_select_sem, 0, 1);
|
|
||||||
|
|
||||||
K_MEM_SLAB_DEFINE(audio_cache_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE, CONFIG_AUDIO_CACHE_SLAB_COUNT, 4);
|
K_MEM_SLAB_DEFINE(audio_cache_slab, CONFIG_AUDIO_CACHE_SLAB_SIZE, CONFIG_AUDIO_CACHE_SLAB_COUNT, 4);
|
||||||
|
|
||||||
struct k_work_q audio_work_q;
|
|
||||||
K_THREAD_STACK_DEFINE(audio_work_q_stack, CONFIG_AUDIO_WORKQUEUE_STACK_SIZE);
|
|
||||||
struct k_work select_next_file_work;
|
|
||||||
|
|
||||||
enum audio_thread_state_t
|
enum audio_thread_state_t
|
||||||
{
|
{
|
||||||
AUDIO_ARMED,
|
AUDIO_IDLE,
|
||||||
AUDIO_PRECACHING,
|
AUDIO_PRECACHING,
|
||||||
AUDIO_WAIT_FOR_CACHE,
|
AUDIO_WAIT_FOR_CACHE,
|
||||||
AUDIO_PLAYING,
|
AUDIO_PLAYING,
|
||||||
@@ -35,8 +30,7 @@ enum audio_thread_state_t
|
|||||||
#define EV_CACHE_READY BIT(2)
|
#define EV_CACHE_READY BIT(2)
|
||||||
#define EV_CACHE_DONE BIT(3)
|
#define EV_CACHE_DONE BIT(3)
|
||||||
#define EV_STATE_STEP BIT(4)
|
#define EV_STATE_STEP BIT(4)
|
||||||
#define EV_AUTOSTART BIT(5)
|
#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP)
|
||||||
#define EV_ALL (EV_PLAY_RANDOM | EV_MSGQ_NOT_EMPTY | EV_CACHE_READY | EV_CACHE_DONE | EV_STATE_STEP | EV_AUTOSTART)
|
|
||||||
|
|
||||||
K_EVENT_DEFINE(audio_events);
|
K_EVENT_DEFINE(audio_events);
|
||||||
|
|
||||||
@@ -50,13 +44,15 @@ struct audio_ctx_t
|
|||||||
char next_file_name[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
char next_file_name[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
||||||
struct fs_file_t file;
|
struct fs_file_t file;
|
||||||
bool is_file_open;
|
bool is_file_open;
|
||||||
|
bool cache_ready_signaled;
|
||||||
|
uint8_t cached_blocks;
|
||||||
ssize_t audio_size;
|
ssize_t audio_size;
|
||||||
ssize_t cached_bytes;
|
ssize_t cached_bytes;
|
||||||
} audio_ctx;
|
} audio_ctx;
|
||||||
|
|
||||||
static struct i2s_config i2s_cfg = {
|
static struct i2s_config i2s_cfg = {
|
||||||
.word_size = 16,
|
.word_size = 16,
|
||||||
.channels = 2,
|
.channels = 1,
|
||||||
.format = I2S_FMT_DATA_FORMAT_I2S,
|
.format = I2S_FMT_DATA_FORMAT_I2S,
|
||||||
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
|
.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER,
|
||||||
.frame_clk_freq = 16000,
|
.frame_clk_freq = 16000,
|
||||||
@@ -70,7 +66,39 @@ K_MUTEX_DEFINE(audio_ctx_mutex);
|
|||||||
atomic_t thread_state = ATOMIC_INIT(0);
|
atomic_t thread_state = ATOMIC_INIT(0);
|
||||||
atomic_t num_files = ATOMIC_INIT(0);
|
atomic_t num_files = ATOMIC_INIT(0);
|
||||||
|
|
||||||
static uint8_t audio_mono_stage[CONFIG_AUDIO_CACHE_SLAB_SIZE / 2];
|
static const char *audio_state_name(enum audio_thread_state_t state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case AUDIO_IDLE:
|
||||||
|
return "IDLE";
|
||||||
|
case AUDIO_PRECACHING:
|
||||||
|
return "PRECACHING";
|
||||||
|
case AUDIO_WAIT_FOR_CACHE:
|
||||||
|
return "WAIT_FOR_CACHE";
|
||||||
|
case AUDIO_PLAYING:
|
||||||
|
return "PLAYING";
|
||||||
|
case AUDIO_DRAINING:
|
||||||
|
return "DRAINING";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void audio_set_state(enum audio_thread_state_t new_state, const char *reason)
|
||||||
|
{
|
||||||
|
enum audio_thread_state_t old_state = atomic_get(&thread_state);
|
||||||
|
|
||||||
|
if (old_state != new_state)
|
||||||
|
{
|
||||||
|
LOG_INF("Audio state %s -> %s (%s)",
|
||||||
|
audio_state_name(old_state),
|
||||||
|
audio_state_name(new_state),
|
||||||
|
reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_set(&thread_state, new_state);
|
||||||
|
}
|
||||||
|
|
||||||
int audio_queue_play(const char *filename, bool is_interrupt)
|
int audio_queue_play(const char *filename, bool is_interrupt)
|
||||||
{
|
{
|
||||||
@@ -102,40 +130,32 @@ int audio_queue_play(const char *filename, bool is_interrupt)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wake immediately for interrupts or when not currently playing.
|
* Wake immediately for interrupts or when idle.
|
||||||
* Non-interrupt commands during playback are picked up after drain.
|
* Non-interrupt commands during active playback are picked up after drain.
|
||||||
*/
|
*/
|
||||||
if (is_interrupt || (atomic_get(&thread_state) != AUDIO_PLAYING))
|
if (is_interrupt || (atomic_get(&thread_state) == AUDIO_IDLE))
|
||||||
{
|
{
|
||||||
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
|
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("Enqueued audio command: filename='%s', is_interrupt=%d", cmd.filename, cmd.is_interrupt);
|
LOG_INF("Enqueued audio command: filename='%s', is_interrupt=%d, queued=%u", cmd.filename,
|
||||||
|
cmd.is_interrupt, (unsigned int)k_msgq_num_used_get(&audio_cmd_q));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int audio_select_random_sound(void)
|
static int audio_select_random_to_buf(char *buf, size_t buf_size)
|
||||||
{
|
{
|
||||||
k_sem_reset(&audio_file_select_sem);
|
|
||||||
if (k_sem_take(&audio_files_count_sem, K_FOREVER) != 0)
|
if (k_sem_take(&audio_files_count_sem, K_FOREVER) != 0)
|
||||||
{
|
{
|
||||||
LOG_ERR("Failed to take audio files count semaphore");
|
|
||||||
k_sem_give(&audio_files_count_sem);
|
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = atomic_get(&num_files);
|
int count = atomic_get(&num_files);
|
||||||
|
|
||||||
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
{
|
{
|
||||||
LOG_WRN("No audio files available to select, returning no files sound");
|
LOG_WRN("No audio files available, using fallback sound");
|
||||||
FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_SYSTEM_PATH, CONFIG_AUDIO_NO_SAMPLES_SAMPLE);
|
snprintf(buf, buf_size, "%s/%s", FS_SYSTEM_PATH, CONFIG_AUDIO_NO_SAMPLES_SAMPLE);
|
||||||
|
|
||||||
k_sem_give(&audio_file_select_sem);
|
|
||||||
k_sem_give(&audio_files_count_sem);
|
k_sem_give(&audio_files_count_sem);
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,29 +163,22 @@ static int audio_select_random_sound(void)
|
|||||||
struct fs_dir_t dir;
|
struct fs_dir_t dir;
|
||||||
struct fs_dirent entry;
|
struct fs_dirent entry;
|
||||||
int rc;
|
int rc;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
fs_dir_t_init(&dir);
|
fs_dir_t_init(&dir);
|
||||||
rc = fs_mgmt_pm_opendir(&dir, FS_AUDIO_PATH);
|
rc = fs_mgmt_pm_opendir(&dir, FS_AUDIO_PATH);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
{
|
{
|
||||||
LOG_ERR("Failed to open audio directory '%s': %d", FS_AUDIO_PATH, rc);
|
LOG_ERR("Failed to open audio directory '%s': %d", FS_AUDIO_PATH, rc);
|
||||||
k_sem_give(&audio_file_select_sem);
|
|
||||||
k_sem_give(&audio_files_count_sem);
|
k_sem_give(&audio_files_count_sem);
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
int current_index = 0;
|
int current_index = 0;
|
||||||
bool found = false;
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
rc = fs_readdir(&dir, &entry);
|
rc = fs_readdir(&dir, &entry);
|
||||||
if (rc < 0)
|
if (rc < 0 || entry.name[0] == '\0')
|
||||||
{
|
|
||||||
LOG_ERR("Directory read error: %d", rc);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (entry.name[0] == '\0')
|
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -173,8 +186,9 @@ static int audio_select_random_sound(void)
|
|||||||
{
|
{
|
||||||
if (current_index == random_index)
|
if (current_index == random_index)
|
||||||
{
|
{
|
||||||
FS_MGMT_ASSEMBLE_PATH(audio_ctx.next_file_name, FS_AUDIO_PATH, entry.name);
|
snprintf(buf, buf_size, "%s/%.*s", FS_AUDIO_PATH,
|
||||||
LOG_DBG("Selected random audio file: %s", audio_ctx.next_file_name);
|
(int)(buf_size - sizeof(FS_AUDIO_PATH) - 1U), entry.name);
|
||||||
|
LOG_DBG("Selected random audio file: %s", buf);
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -183,9 +197,7 @@ static int audio_select_random_sound(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs_mgmt_pm_closedir(&dir);
|
fs_mgmt_pm_closedir(&dir);
|
||||||
k_sem_give(&audio_file_select_sem);
|
|
||||||
k_sem_give(&audio_files_count_sem);
|
k_sem_give(&audio_files_count_sem);
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
|
||||||
return found ? 0 : -ENOENT;
|
return found ? 0 : -ENOENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,31 +242,11 @@ void audio_refresh_files(void)
|
|||||||
fs_mgmt_pm_closedir(&dir);
|
fs_mgmt_pm_closedir(&dir);
|
||||||
k_sem_give(&audio_files_count_sem);
|
k_sem_give(&audio_files_count_sem);
|
||||||
atomic_set(&num_files, count);
|
atomic_set(&num_files, count);
|
||||||
audio_select_random_sound();
|
LOG_INF("Audio refresh found %d file(s) in %s", count, FS_AUDIO_PATH);
|
||||||
}
|
|
||||||
|
|
||||||
static void select_next_file_work_handler(struct k_work *work)
|
|
||||||
{
|
|
||||||
ARG_UNUSED(work);
|
|
||||||
LOG_DBG("Select next file work handler");
|
|
||||||
audio_select_random_sound();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int audio_init(void)
|
static int audio_init(void)
|
||||||
{
|
{
|
||||||
struct k_work_queue_config audio_work_q_config = {
|
|
||||||
.name = "audio_work_q",
|
|
||||||
.no_yield = false,
|
|
||||||
.essential = true,
|
|
||||||
.work_timeout_ms = 0};
|
|
||||||
|
|
||||||
k_work_queue_start(&audio_work_q,
|
|
||||||
audio_work_q_stack,
|
|
||||||
K_THREAD_STACK_SIZEOF(audio_work_q_stack),
|
|
||||||
CONFIG_AUDIO_WORKQUEUE_PRIORITY, &audio_work_q_config);
|
|
||||||
|
|
||||||
k_work_init(&select_next_file_work, select_next_file_work_handler);
|
|
||||||
|
|
||||||
if (!device_is_ready(i2s_dev))
|
if (!device_is_ready(i2s_dev))
|
||||||
{
|
{
|
||||||
LOG_ERR("I2S device not ready");
|
LOG_ERR("I2S device not ready");
|
||||||
@@ -272,14 +264,7 @@ static int audio_init(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); // Stelle sicher, dass dies nach der FS-Initialisierung erfolgt
|
SYS_INIT(audio_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
|
||||||
|
|
||||||
static void audio_trigger_next_file_selection(void)
|
|
||||||
{
|
|
||||||
k_sem_reset(&audio_file_select_sem);
|
|
||||||
LOG_DBG("Triggering workq file selection");
|
|
||||||
k_work_submit_to_queue(&audio_work_q, &select_next_file_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
void audio_start_random_playback(void)
|
void audio_start_random_playback(void)
|
||||||
{
|
{
|
||||||
@@ -293,11 +278,12 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
ARG_UNUSED(arg2);
|
ARG_UNUSED(arg2);
|
||||||
ARG_UNUSED(arg3);
|
ARG_UNUSED(arg3);
|
||||||
|
|
||||||
|
LOG_INF("Audio thread waiting for filesystem ready event");
|
||||||
k_event_wait(&event_mgmt_events, EVENT_MGMT_FS_READY, false, K_FOREVER);
|
k_event_wait(&event_mgmt_events, EVENT_MGMT_FS_READY, false, K_FOREVER);
|
||||||
|
LOG_INF("Audio thread received filesystem ready event");
|
||||||
|
|
||||||
audio_refresh_files();
|
audio_refresh_files();
|
||||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
audio_set_state(AUDIO_IDLE, "filesystem ready");
|
||||||
k_event_set(&audio_events, EV_STATE_STEP);
|
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
@@ -311,65 +297,89 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
k_event_clear(&audio_events, EV_STATE_STEP);
|
k_event_clear(&audio_events, EV_STATE_STEP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EV_PLAY_RANDOM: button press requests a random sound.
|
||||||
|
* If idle, start it immediately. Otherwise queue it as a non-interrupt command
|
||||||
|
* so it plays after the current sound finishes.
|
||||||
|
*/
|
||||||
if (active_events & EV_PLAY_RANDOM)
|
if (active_events & EV_PLAY_RANDOM)
|
||||||
{
|
{
|
||||||
LOG_DBG("Play random event received");
|
|
||||||
k_event_clear(&audio_events, EV_PLAY_RANDOM);
|
k_event_clear(&audio_events, EV_PLAY_RANDOM);
|
||||||
|
|
||||||
if (state == AUDIO_ARMED)
|
char random_file[CONFIG_FS_MGMT_MAX_PATH_LENGTH];
|
||||||
|
if (audio_select_random_to_buf(random_file, sizeof(random_file)) == 0)
|
||||||
{
|
{
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
|
if (state == AUDIO_IDLE)
|
||||||
atomic_set(&thread_state, AUDIO_PLAYING);
|
{
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, random_file, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
audio_set_state(AUDIO_PRECACHING, "play random while idle");
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
struct audio_cmd_msg cmd;
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
strncpy(cmd.filename, random_file, sizeof(cmd.filename) - 1);
|
||||||
|
cmd.filename[sizeof(cmd.filename) - 1] = '\0';
|
||||||
audio_select_random_sound();
|
cmd.is_interrupt = false;
|
||||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
if (k_msgq_put(&audio_cmd_q, &cmd, K_NO_WAIT) != 0)
|
||||||
k_event_set(&audio_events, EV_STATE_STEP);
|
{
|
||||||
|
LOG_WRN("Random play queue full, discarding");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_INF("Random play queued: '%s'", random_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case AUDIO_PRECACHING:
|
case AUDIO_IDLE:
|
||||||
LOG_DBG("Audio thread starting precache task");
|
|
||||||
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
|
||||||
atomic_set(&thread_state, AUDIO_WAIT_FOR_CACHE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AUDIO_WAIT_FOR_CACHE:
|
|
||||||
if (active_events & EV_CACHE_READY)
|
|
||||||
{
|
|
||||||
k_event_clear(&audio_events, EV_CACHE_READY);
|
|
||||||
atomic_set(&thread_state, AUDIO_ARMED);
|
|
||||||
|
|
||||||
if (k_event_wait(&audio_events, EV_AUTOSTART, false, K_NO_WAIT) & EV_AUTOSTART)
|
|
||||||
{
|
|
||||||
k_event_clear(&audio_events, EV_AUTOSTART);
|
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
|
|
||||||
atomic_set(&thread_state, AUDIO_PLAYING);
|
|
||||||
LOG_DBG("Autostarting queued audio playback");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_DBG("System Armed. Waiting for Buzzer...");
|
|
||||||
audio_trigger_next_file_selection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AUDIO_ARMED:
|
|
||||||
if (active_events & EV_MSGQ_NOT_EMPTY)
|
if (active_events & EV_MSGQ_NOT_EMPTY)
|
||||||
{
|
{
|
||||||
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
|
||||||
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
|
||||||
|
|
||||||
struct audio_cmd_msg cmd;
|
struct audio_cmd_msg cmd;
|
||||||
|
if (k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT) != 0)
|
||||||
|
{
|
||||||
|
LOG_WRN("EV_MSGQ_NOT_EMPTY set but queue empty");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
LOG_INF("Dequeued command while idle: '%s'", cmd.filename);
|
||||||
|
audio_set_state(AUDIO_PRECACHING, "command while idle");
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_PRECACHING:
|
||||||
|
LOG_INF("Precaching '%s'", audio_ctx.next_file_name);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_PREPARE);
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_START);
|
||||||
|
audio_set_state(AUDIO_WAIT_FOR_CACHE, "cache start requested");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AUDIO_WAIT_FOR_CACHE:
|
||||||
|
if (active_events & EV_MSGQ_NOT_EMPTY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
|
||||||
|
struct audio_cmd_msg cmd;
|
||||||
|
if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt)
|
||||||
|
{
|
||||||
|
/* Interrupt while caching: flush and restart for the new file */
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_PREPARE);
|
||||||
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
||||||
|
|
||||||
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
@@ -377,9 +387,20 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
k_event_set(&audio_events, EV_AUTOSTART);
|
LOG_INF("Interrupt while caching, switching to '%s'", cmd.filename);
|
||||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
audio_set_state(AUDIO_PRECACHING, "interrupt while caching");
|
||||||
k_event_set(&audio_events, EV_STATE_STEP);
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Non-interrupt: already queued, will play after current */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_events & EV_CACHE_READY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_CACHE_READY);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
|
||||||
|
audio_set_state(AUDIO_PLAYING, "cache primed, starting");
|
||||||
|
LOG_INF("Playback started: '%s'", audio_ctx.next_file_name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -389,31 +410,25 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
|
||||||
struct audio_cmd_msg cmd;
|
struct audio_cmd_msg cmd;
|
||||||
if (k_msgq_peek(&audio_cmd_q, &cmd) == 0)
|
if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt)
|
||||||
{
|
{
|
||||||
if (cmd.is_interrupt)
|
|
||||||
{
|
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
|
||||||
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||||
|
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
||||||
|
|
||||||
if (k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT) == 0)
|
|
||||||
{
|
|
||||||
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
k_event_set(&audio_events, EV_AUTOSTART);
|
LOG_INF("Interrupting playback with '%s'", cmd.filename);
|
||||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
audio_set_state(AUDIO_PRECACHING, "interrupt during playback");
|
||||||
k_event_set(&audio_events, EV_STATE_STEP);
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_DBG("Non-interrupt command queued during playback; will process after drain");
|
LOG_DBG("Non-interrupt command queued; will play after drain");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,27 +436,61 @@ void audio_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
{
|
{
|
||||||
k_event_clear(&audio_events, EV_CACHE_DONE);
|
k_event_clear(&audio_events, EV_CACHE_DONE);
|
||||||
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
|
||||||
atomic_set(&thread_state, AUDIO_DRAINING);
|
audio_set_state(AUDIO_DRAINING, "cache complete");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUDIO_DRAINING:
|
case AUDIO_DRAINING:
|
||||||
|
{
|
||||||
|
if (active_events & EV_MSGQ_NOT_EMPTY)
|
||||||
|
{
|
||||||
|
k_event_clear(&audio_events, EV_MSGQ_NOT_EMPTY);
|
||||||
|
|
||||||
|
struct audio_cmd_msg cmd;
|
||||||
|
if (k_msgq_peek(&audio_cmd_q, &cmd) == 0 && cmd.is_interrupt)
|
||||||
|
{
|
||||||
|
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
||||||
|
k_event_set(&audio_cache_event, AUDIO_CACHE_EVT_STOP);
|
||||||
|
i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DROP);
|
||||||
|
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
LOG_INF("Interrupt during drain, switching to '%s'", cmd.filename);
|
||||||
|
audio_set_state(AUDIO_PRECACHING, "interrupt during drain");
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (k_mem_slab_num_free_get(&audio_cache_slab) == CONFIG_AUDIO_CACHE_SLAB_COUNT)
|
if (k_mem_slab_num_free_get(&audio_cache_slab) == CONFIG_AUDIO_CACHE_SLAB_COUNT)
|
||||||
{
|
{
|
||||||
LOG_DBG("Audio for file drained, ready for next file");
|
LOG_DBG("Drain complete");
|
||||||
if (k_msgq_num_used_get(&audio_cmd_q) > 0)
|
if (k_msgq_num_used_get(&audio_cmd_q) > 0)
|
||||||
{
|
{
|
||||||
k_event_set(&audio_events, EV_MSGQ_NOT_EMPTY);
|
struct audio_cmd_msg cmd;
|
||||||
|
k_msgq_get(&audio_cmd_q, &cmd, K_NO_WAIT);
|
||||||
|
|
||||||
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
strncpy(audio_ctx.next_file_name, cmd.filename, sizeof(audio_ctx.next_file_name) - 1);
|
||||||
|
audio_ctx.next_file_name[sizeof(audio_ctx.next_file_name) - 1] = '\0';
|
||||||
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
|
|
||||||
|
LOG_INF("Drain complete, next: '%s'", cmd.filename);
|
||||||
|
audio_set_state(AUDIO_PRECACHING, "drain complete, queued command");
|
||||||
|
k_event_set(&audio_events, EV_STATE_STEP);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
atomic_set(&thread_state, AUDIO_PRECACHING);
|
audio_set_state(AUDIO_IDLE, "drain complete, no more commands");
|
||||||
k_event_set(&audio_events, EV_STATE_STEP);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
K_THREAD_DEFINE(audio_thread_id, CONFIG_AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL,
|
K_THREAD_DEFINE(audio_thread_id, CONFIG_AUDIO_THREAD_STACK_SIZE, audio_thread, NULL, NULL, NULL,
|
||||||
@@ -453,7 +502,6 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
ARG_UNUSED(arg2);
|
ARG_UNUSED(arg2);
|
||||||
ARG_UNUSED(arg3);
|
ARG_UNUSED(arg3);
|
||||||
|
|
||||||
uint8_t num_channels = 1;
|
|
||||||
void *mem_slab;
|
void *mem_slab;
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
@@ -487,10 +535,13 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
|
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
{
|
{
|
||||||
|
bool signal_cache_ready = false;
|
||||||
|
|
||||||
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
k_mutex_lock(&audio_ctx_mutex, K_FOREVER);
|
||||||
|
|
||||||
if (!audio_ctx.is_file_open)
|
if (!audio_ctx.is_file_open)
|
||||||
{
|
{
|
||||||
|
LOG_INF("Opening audio file '%s' for playback", audio_ctx.next_file_name);
|
||||||
rc = fs_mgmt_pm_open(&audio_ctx.file, audio_ctx.next_file_name, FS_O_READ);
|
rc = fs_mgmt_pm_open(&audio_ctx.file, audio_ctx.next_file_name, FS_O_READ);
|
||||||
if (rc < 0)
|
if (rc < 0)
|
||||||
{
|
{
|
||||||
@@ -502,8 +553,10 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
}
|
}
|
||||||
|
|
||||||
audio_ctx.is_file_open = true;
|
audio_ctx.is_file_open = true;
|
||||||
|
audio_ctx.cache_ready_signaled = false;
|
||||||
|
audio_ctx.cached_blocks = 0;
|
||||||
|
|
||||||
// NEU: Größen- und Zähler-Initialisierung exklusiv hier!
|
/* File length and cache position are initialized once per playback start. */
|
||||||
audio_ctx.audio_size = fs_mgmt_get_audio_data_len(&audio_ctx.file);
|
audio_ctx.audio_size = fs_mgmt_get_audio_data_len(&audio_ctx.file);
|
||||||
audio_ctx.cached_bytes = 0;
|
audio_ctx.cached_bytes = 0;
|
||||||
|
|
||||||
@@ -533,8 +586,8 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t bytes_to_read = MIN(CONFIG_AUDIO_CACHE_SLAB_SIZE / 2, remaining_bytes);
|
ssize_t bytes_to_read = MIN(CONFIG_AUDIO_CACHE_SLAB_SIZE, remaining_bytes);
|
||||||
ssize_t bytes_read = fs_read(&audio_ctx.file, audio_mono_stage, bytes_to_read);
|
ssize_t bytes_read = fs_read(&audio_ctx.file, mem_slab, bytes_to_read);
|
||||||
|
|
||||||
if (bytes_read <= 0) // <= 0, um EOF (0) und Fehler (< 0) abzufangen
|
if (bytes_read <= 0) // <= 0, um EOF (0) und Fehler (< 0) abzufangen
|
||||||
{
|
{
|
||||||
@@ -556,9 +609,9 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
audio_ctx.cached_bytes += bytes_read;
|
audio_ctx.cached_bytes += bytes_read;
|
||||||
// LOG_DBG("Cached %u%% ", (int)((audio_ctx.cached_bytes * 100) / audio_ctx.audio_size));
|
// LOG_DBG("Cached %u%% ", (int)((audio_ctx.cached_bytes * 100) / audio_ctx.audio_size));
|
||||||
|
|
||||||
if (bytes_read > CONFIG_AUDIO_CACHE_SLAB_SIZE / 2)
|
if (bytes_read > CONFIG_AUDIO_CACHE_SLAB_SIZE)
|
||||||
{
|
{
|
||||||
LOG_ERR("Read size %d exceeds half slab size %d", (int)bytes_read, CONFIG_AUDIO_CACHE_SLAB_SIZE / 2);
|
LOG_ERR("Read size %d exceeds slab size %d", (int)bytes_read, CONFIG_AUDIO_CACHE_SLAB_SIZE);
|
||||||
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
fs_close(&audio_ctx.file);
|
fs_close(&audio_ctx.file);
|
||||||
audio_ctx.is_file_open = false;
|
audio_ctx.is_file_open = false;
|
||||||
@@ -569,9 +622,9 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
|
|
||||||
if ((bytes_read & 0x1) != 0)
|
if ((bytes_read & 0x1) != 0)
|
||||||
{
|
{
|
||||||
if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2))
|
if (bytes_read >= (CONFIG_AUDIO_CACHE_SLAB_SIZE))
|
||||||
{
|
{
|
||||||
LOG_ERR("Odd mono byte count at half-slab boundary: %d", (int)bytes_read);
|
LOG_ERR("Odd PCM byte count at slab boundary: %d", (int)bytes_read);
|
||||||
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
fs_close(&audio_ctx.file);
|
fs_close(&audio_ctx.file);
|
||||||
audio_ctx.is_file_open = false;
|
audio_ctx.is_file_open = false;
|
||||||
@@ -580,31 +633,23 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_mono_stage[bytes_read] = 0;
|
((uint8_t *)mem_slab)[bytes_read] = 0;
|
||||||
bytes_read++;
|
bytes_read++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE / 2)
|
if (bytes_read < CONFIG_AUDIO_CACHE_SLAB_SIZE)
|
||||||
{
|
{
|
||||||
memset(audio_mono_stage + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE / 2) - bytes_read);
|
memset((uint8_t *)mem_slab + bytes_read, 0, (CONFIG_AUDIO_CACHE_SLAB_SIZE) - bytes_read);
|
||||||
bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE / 2;
|
bytes_read = CONFIG_AUDIO_CACHE_SLAB_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num_channels == 1)
|
audio_ctx.cached_blocks++;
|
||||||
{
|
|
||||||
uint8_t *dst = (uint8_t *)mem_slab;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < (size_t)bytes_read; i += 2)
|
if (!audio_ctx.cache_ready_signaled &&
|
||||||
|
(audio_ctx.cached_blocks >= 2 || audio_ctx.cached_bytes >= audio_ctx.audio_size))
|
||||||
{
|
{
|
||||||
uint8_t lo = audio_mono_stage[i];
|
audio_ctx.cache_ready_signaled = true;
|
||||||
uint8_t hi = audio_mono_stage[i + 1];
|
signal_cache_ready = true;
|
||||||
size_t out = i * 2;
|
|
||||||
|
|
||||||
dst[out] = lo;
|
|
||||||
dst[out + 1] = hi;
|
|
||||||
dst[out + 2] = lo;
|
|
||||||
dst[out + 3] = hi;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
k_mutex_unlock(&audio_ctx_mutex);
|
k_mutex_unlock(&audio_ctx_mutex);
|
||||||
@@ -614,6 +659,13 @@ void audio_pump_thread(void *arg1, void *arg2, void *arg3)
|
|||||||
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
k_mem_slab_free(&audio_cache_slab, &mem_slab);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signal_cache_ready)
|
||||||
|
{
|
||||||
|
LOG_DBG("Cache ready for '%s' after %u block(s)", audio_ctx.next_file_name,
|
||||||
|
(unsigned int)audio_ctx.cached_blocks);
|
||||||
|
k_event_set(&audio_events, EV_CACHE_READY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,8 +246,10 @@ int ble_mgmt_init(ble_mgmt_rx_cb_t rx_cb, const char *device_name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const char *name_to_use = (device_name != NULL) ? device_name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME;
|
const char *name_to_use = (device_name != NULL) ? device_name : CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME;
|
||||||
|
LOG_INF("BLE init: set_device_name");
|
||||||
set_device_name(name_to_use);
|
set_device_name(name_to_use);
|
||||||
|
|
||||||
|
LOG_INF("BLE init: bt_le_adv_start");
|
||||||
rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
|
rc = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
|
||||||
if (rc)
|
if (rc)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ menuconfig FS_MGMT
|
|||||||
select FILE_SYSTEM_LITTLEFS
|
select FILE_SYSTEM_LITTLEFS
|
||||||
select FILE_SYSTEM_MKFS
|
select FILE_SYSTEM_MKFS
|
||||||
select FLASH_PAGE_LAYOUT
|
select FLASH_PAGE_LAYOUT
|
||||||
select NORDIC_QSPI_NOR if SOC_SERIES_NRF52X && (SOC_NRF52840_QIAA || SOC_NRF52833_QIAA)
|
select SPI_NOR if BOARD_BUZZY
|
||||||
|
select PM_OVERRIDE_EXTERNAL_DRIVER_CHECK if BOARD_BUZZY
|
||||||
|
select NORDIC_QSPI_NOR if BOARD_NRF52840DK_NRF52840
|
||||||
help
|
help
|
||||||
Library for initializing and managing the file system.
|
Library for initializing and managing the file system.
|
||||||
|
|
||||||
@@ -56,13 +58,13 @@ if FS_MGMT
|
|||||||
endif # SOC_SERIES_NRF52X
|
endif # SOC_SERIES_NRF52X
|
||||||
|
|
||||||
config FS_LITTLEFS_READ_SIZE
|
config FS_LITTLEFS_READ_SIZE
|
||||||
default 256
|
default 512
|
||||||
config FS_LITTLEFS_PROG_SIZE
|
config FS_LITTLEFS_PROG_SIZE
|
||||||
default 256
|
default 256
|
||||||
config FS_LITTLEFS_CACHE_SIZE
|
config FS_LITTLEFS_CACHE_SIZE
|
||||||
default 4096
|
default 4096
|
||||||
config FS_LITTLEFS_LOOKAHEAD_SIZE
|
config FS_LITTLEFS_LOOKAHEAD_SIZE
|
||||||
default 512
|
default 256
|
||||||
|
|
||||||
module = FS_MGMT
|
module = FS_MGMT
|
||||||
module-str = fs_mgmt
|
module-str = fs_mgmt
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <zephyr/fs/littlefs.h>
|
#include <zephyr/fs/littlefs.h>
|
||||||
#include <zephyr/fs/fs.h>
|
#include <zephyr/fs/fs.h>
|
||||||
|
#include <zephyr/storage/flash_map.h>
|
||||||
#include <zephyr/sys/byteorder.h>
|
#include <zephyr/sys/byteorder.h>
|
||||||
#include <zephyr/logging/log.h>
|
#include <zephyr/logging/log.h>
|
||||||
#include <zephyr/pm/device.h>
|
#include <zephyr/pm/device.h>
|
||||||
@@ -11,11 +12,18 @@
|
|||||||
|
|
||||||
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
LOG_MODULE_REGISTER(fs_mgmt, CONFIG_FS_MGMT_LOG_LEVEL);
|
||||||
|
|
||||||
#define FS_PARTITION_ID FLASH_AREA_ID(littlefs_storage)
|
/* Prefer external LittleFS partition when present, otherwise internal storage partition. */
|
||||||
|
#if DT_NODE_EXISTS(DT_NODELABEL(ext_flash_lfs))
|
||||||
|
#define FS_PARTITION_ID FIXED_PARTITION_ID(ext_flash_lfs)
|
||||||
|
#elif DT_NODE_EXISTS(DT_NODELABEL(storage_partition))
|
||||||
|
#define FS_PARTITION_ID FIXED_PARTITION_ID(storage_partition)
|
||||||
|
#else
|
||||||
|
#error "No compatible LittleFS partition node label found (expected ext_flash_lfs or storage_partition)"
|
||||||
|
#endif
|
||||||
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(fs_storage_data);
|
||||||
|
|
||||||
#define QSPI_FLASH_NODE DT_ALIAS(qspi_flash)
|
#define EXTERNAL_FLASH_NODE DT_ALIAS(external_flash)
|
||||||
static const struct device *flash_dev = DEVICE_DT_GET(QSPI_FLASH_NODE);
|
static const struct device *flash_dev = DEVICE_DT_GET(EXTERNAL_FLASH_NODE);
|
||||||
|
|
||||||
#define TAG_MAGIC "TAG!"
|
#define TAG_MAGIC "TAG!"
|
||||||
#define TAG_FORMAT_VERSION 1U
|
#define TAG_FORMAT_VERSION 1U
|
||||||
|
|||||||
5
firmware/pm_static.yml
Normal file
5
firmware/pm_static.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# External Flash
|
||||||
|
littlefs_storage:
|
||||||
|
address: 0x0
|
||||||
|
size: 0x800000
|
||||||
|
region: external_flash
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
### Logging
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_AUDIO_LOG_LEVEL_DBG=y
|
|
||||||
|
|
||||||
### Bluetooth
|
### Bluetooth
|
||||||
CONFIG_BLE_MGMT=y
|
CONFIG_BLE_MGMT=y
|
||||||
|
|
||||||
@@ -14,8 +10,3 @@ CONFIG_PM_DEVICE=y
|
|||||||
|
|
||||||
### Stack
|
### Stack
|
||||||
CONFIG_MAIN_STACK_SIZE=2048
|
CONFIG_MAIN_STACK_SIZE=2048
|
||||||
CONFIG_INIT_STACKS=y
|
|
||||||
CONFIG_THREAD_STACK_INFO=y
|
|
||||||
CONFIG_STACK_SENTINEL=y
|
|
||||||
|
|
||||||
# CONFIG_LOG_MODE_IMMEDIATE=y
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "fs_mgmt.h"
|
#include "fs_mgmt.h"
|
||||||
#include "buzz_proto.h"
|
#include "buzz_proto.h"
|
||||||
#include "fw_mgmt.h"
|
#include "fw_mgmt.h"
|
||||||
#include "audio.h"
|
// #include "audio.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(main);
|
LOG_MODULE_REGISTER(main);
|
||||||
|
|
||||||
@@ -44,6 +44,7 @@ void ble_rx_cb(const uint8_t *data, uint16_t len)
|
|||||||
buzz_proto_buf_free(&buf); /* Speicher bei Fehler sofort wieder freigeben */
|
buzz_proto_buf_free(&buf); /* Speicher bei Fehler sofort wieder freigeben */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
@@ -55,16 +56,19 @@ int main(void)
|
|||||||
LOG_ERR("Failed to initialize BLE management: %d", rc);
|
LOG_ERR("Failed to initialize BLE management: %d", rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
LOG_WRN("After BLE init");
|
||||||
|
#else
|
||||||
|
LOG_WRN("BLE not enabled");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LOG_INF("Init complete. Starting audio playback test...");
|
LOG_WRN("Main park loop active");
|
||||||
|
|
||||||
k_sleep(K_SECONDS(1));
|
// k_sleep(K_MSEC(500));
|
||||||
audio_queue_play("/lfs/sys/update", false);
|
// LOG_INF("Playing test audio files...");
|
||||||
k_sleep(K_SECONDS(1));
|
// audio_queue_play("/lfs/sys/update", false);
|
||||||
audio_start_random_playback(); // Starte die Wiedergabe eines zufälligen Sounds
|
// audio_queue_play("/lfs/sys/confirm", false);
|
||||||
k_sleep(K_SECONDS(1));
|
for (;;) {
|
||||||
audio_queue_play("/lfs/sys/404", true);
|
int32_t rem_ms = k_sleep(K_FOREVER);
|
||||||
|
LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms);
|
||||||
k_sleep(K_FOREVER);
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
CONFIG_LOG=y
|
CONFIG_LOG=y
|
||||||
# CONFIG_MCUBOOT_SERIAL=y
|
# CONFIG_MCUBOOT_SERIAL=y
|
||||||
CONFIG_UART_CONSOLE=y
|
|
||||||
# CONFIG_SINGLE_APPLICATION_SLOT=n
|
# CONFIG_SINGLE_APPLICATION_SLOT=n
|
||||||
# CONFIG_MCUBOOT_INDICATION_LED=y
|
# CONFIG_MCUBOOT_INDICATION_LED=y
|
||||||
# CONFIG_BOOT_SERIAL_CDC_ACM=y
|
# CONFIG_BOOT_SERIAL_CDC_ACM=y
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
/ {
|
|
||||||
aliases {
|
|
||||||
mcuboot-button0 = &button0;
|
|
||||||
mcuboot-led0 = &led0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Step 2.1 - Configure CDC ACM */
|
|
||||||
&zephyr_udc0 {
|
|
||||||
cdc_acm_uart0: cdc_acm_uart0 {
|
|
||||||
compatible = "zephyr,cdc-acm-uart";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user