Merge pull request 'audiocore-i2s-driver' (#3) from audiocore-i2s-driver into main
Reviewed-on: #3 Reviewed-by: stefank <kratochwil-la@gmx.de>
This commit was merged in pull request #3.
This commit is contained in:
29
.gitea/workflows/check-format.yaml
Normal file
29
.gitea/workflows/check-format.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Check code formatting
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
Check-C-Format:
|
||||
runs-on: ubuntu-22.04-full
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Run clang format
|
||||
run: |
|
||||
cmake software -B build
|
||||
cmake --build build -- check-format
|
||||
Check-Python-Flake8:
|
||||
runs-on: ubuntu-22.04-full
|
||||
steps:
|
||||
- name: Get Flake8
|
||||
run: |
|
||||
python -m venv flake-venv
|
||||
flake-venv/bin/pip install flake8==7.0
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: git
|
||||
- name: Check python
|
||||
run: |
|
||||
cd git/software/src &&
|
||||
find . -iname '*.py' -exec ../../../flake-venv/bin/flake8 {} +
|
||||
22
.gitea/workflows/unit-tests-host.yaml
Normal file
22
.gitea/workflows/unit-tests-host.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Run unit tests on host
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
Run-Unit-Tests:
|
||||
runs-on: ubuntu-22.04-full
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Build and test
|
||||
run: |
|
||||
cmake software -B build
|
||||
cmake --build build
|
||||
ctest --test-dir build
|
||||
- name: Upload test report
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: junit-xml
|
||||
path: build/junit.xml
|
||||
# Use always() to always run this step to publish test results when there are test failures
|
||||
if: ${{ always() }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
hardware/tonberry-pico/tonberry-pico-backups/
|
||||
~*
|
||||
*~
|
||||
*.kicad_sch-bak
|
||||
*.kicad_sch.lck
|
||||
software/build
|
||||
|
||||
61
software/CMakeLists.txt
Normal file
61
software/CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
project(tonberry-pico LANGUAGES C)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(unity
|
||||
GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git
|
||||
GIT_TAG v2.6.0
|
||||
)
|
||||
FetchContent_MakeAvailable(unity)
|
||||
include(CTest)
|
||||
function(make_unity_test )
|
||||
set(options "")
|
||||
set(oneValueArgs NAME TEST_SOURCE)
|
||||
set(multiValueArgs SOURCES INCLUDES)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 MAKE_UNITY_TEST
|
||||
"${options}" "${oneValueArgs}" "${multiValueArgs}"
|
||||
)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${MAKE_UNITY_TEST_NAME}_runner.c
|
||||
COMMAND ${unity_SOURCE_DIR}/auto/generate_test_runner.rb ${MAKE_UNITY_TEST_TEST_SOURCE} ${CMAKE_CURRENT_BINARY_DIR}/${MAKE_UNITY_TEST_NAME}_runner.c
|
||||
DEPENDS ${MAKE_UNITY_TEST_TEST_SOURCE}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_executable(${MAKE_UNITY_TEST_NAME}
|
||||
${MAKE_UNITY_TEST_TEST_SOURCE}
|
||||
${MAKE_UNITY_TEST_SOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${MAKE_UNITY_TEST_NAME}_runner.c
|
||||
)
|
||||
target_link_libraries(${MAKE_UNITY_TEST_NAME} PUBLIC unity::framework)
|
||||
target_include_directories(${MAKE_UNITY_TEST_NAME}
|
||||
PRIVATE ${MAKE_UNITY_TEST_INCLUDES})
|
||||
|
||||
add_test(NAME ${MAKE_UNITY_TEST_NAME}
|
||||
COMMAND sh -c "$<TARGET_FILE:${MAKE_UNITY_TEST_NAME}> > ${PROJECT_BINARY_DIR}/reports/${MAKE_UNITY_TEST_NAME}.testresults"
|
||||
)
|
||||
set_tests_properties(${MAKE_UNITY_TEST_NAME} PROPERTIES FIXTURES_REQUIRED Report)
|
||||
endfunction()
|
||||
|
||||
add_test(NAME clean-reports
|
||||
COMMAND sh -c "rm -rf ${PROJECT_BINARY_DIR}/reports; mkdir ${PROJECT_BINARY_DIR}/reports"
|
||||
)
|
||||
add_test(NAME generate-xml-report
|
||||
COMMAND ${unity_SOURCE_DIR}/auto/stylize_as_junit.rb -r ${PROJECT_BINARY_DIR}/reports/ -o junit.xml
|
||||
)
|
||||
set_tests_properties(clean-reports PROPERTIES FIXTURES_SETUP "Report")
|
||||
set_tests_properties(generate-xml-report PROPERTIES FIXTURES_CLEANUP "Report")
|
||||
|
||||
add_subdirectory(src/audiocore)
|
||||
|
||||
add_custom_target(check-format
|
||||
find . -iname '*.[ch]' -exec clang-format -Werror --dry-run {} +
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
add_custom_target(clang-format
|
||||
find . -iname '*.[ch]' -exec clang-format -i {} +
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
@@ -6,3 +6,5 @@ require("bundle-networking")
|
||||
require("aioble")
|
||||
|
||||
module("rp2_neopixel.py", "../../src")
|
||||
require("sdcard")
|
||||
require("aiorepl")
|
||||
|
||||
@@ -7,7 +7,8 @@ set -eu
|
||||
( cd lib/micropython
|
||||
make -C mpy-cross -j $(nproc)
|
||||
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W clean
|
||||
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W -j $(nproc)
|
||||
make -C ports/rp2 BOARD=TONBERRY_RPI_PICO_W BOARD_DIR=$TOPDIR/boards/RPI_PICO_W \
|
||||
USER_C_MODULES=$TOPDIR/src/audiocore/micropython.cmake -j $(nproc)
|
||||
)
|
||||
|
||||
echo "Output in lib/micropython/ports/rp2/build-TONBERRY_RPI_PICO_W/firmware.uf2"
|
||||
|
||||
@@ -7,5 +7,7 @@ while [ ! -e "$DEVICEPATH" ] ; do sleep 1; echo 'Waiting for RP2...'; done
|
||||
|
||||
set -eu
|
||||
|
||||
while [ ! -e "$DEVICEPATH" ] ; do sleep 1; echo 'Waiting for RP2...'; done
|
||||
|
||||
udisksctl mount -b "$DEVICEPATH"
|
||||
cp "$IMAGEPATH" "$(findmnt "$DEVICEPATH" -n -o TARGET)"
|
||||
|
||||
6
software/src/.clang-format
Normal file
6
software/src/.clang-format
Normal file
@@ -0,0 +1,6 @@
|
||||
# -*- mode: yaml -*-
|
||||
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 120
|
||||
BreakBeforeBraces: WebKit # Only break at start of functions
|
||||
2
software/src/.flake8
Normal file
2
software/src/.flake8
Normal file
@@ -0,0 +1,2 @@
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
8
software/src/audiocore/CMakeLists.txt
Normal file
8
software/src/audiocore/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
make_unity_test(NAME test_audiocore
|
||||
TEST_SOURCE test/test_audiocore.c
|
||||
SOURCES audiocore.c
|
||||
INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/../test/include" "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
27
software/src/audiocore/audiocore.c
Normal file
27
software/src/audiocore/audiocore.c
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#include "audiocore.h"
|
||||
#include "i2s.h"
|
||||
|
||||
#include "py/mperrno.h"
|
||||
|
||||
void core1_main(void)
|
||||
{
|
||||
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base, shared_context.samplerate)) {
|
||||
multicore_fifo_push_blocking(MP_EIO);
|
||||
return;
|
||||
}
|
||||
|
||||
multicore_fifo_push_blocking(0);
|
||||
uint32_t cmd;
|
||||
while ((cmd = multicore_fifo_pop_blocking()) != AUDIOCORE_CMD_SHUTDOWN) {
|
||||
switch (cmd) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i2s_deinit();
|
||||
multicore_fifo_push_blocking(0);
|
||||
}
|
||||
85
software/src/audiocore/audiocore.h
Normal file
85
software/src/audiocore/audiocore.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <hardware/sync.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Access rules
|
||||
* audiocore processing runs on core 1 and, unless stated otherwise, all
|
||||
* variables may only be accessed from core 1. Take care of interrupt safety
|
||||
* where needed. The micropython interface lives in module.c and is invoked from
|
||||
* the micropython runtime on core 0. Micropython objects may only be handled in
|
||||
* that context The audiocore_shared_context struct defined below is used for
|
||||
* communication between the cores.
|
||||
*/
|
||||
|
||||
#define AUDIO_BUFFER_SIZE 2048
|
||||
|
||||
// Context shared between the micropython runtime on core0 and the audio task on
|
||||
// core1 All access must hold "lock" unless otherwise noted
|
||||
struct audiocore_shared_context {
|
||||
spin_lock_t *lock;
|
||||
|
||||
// Set by module.c before core1 is launched and then never changed, can be read without lock
|
||||
int out_pin, sideset_base, samplerate;
|
||||
|
||||
// Must hold lock
|
||||
uint32_t audio_buffer[AUDIO_BUFFER_SIZE];
|
||||
int audio_buffer_write, audio_buffer_read;
|
||||
int underruns;
|
||||
};
|
||||
|
||||
extern struct audiocore_shared_context shared_context;
|
||||
|
||||
static inline unsigned audiocore_get_audio_buffer_space(void)
|
||||
{
|
||||
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
|
||||
return AUDIO_BUFFER_SIZE - 1 - (shared_context.audio_buffer_write - shared_context.audio_buffer_read);
|
||||
else
|
||||
return shared_context.audio_buffer_read - shared_context.audio_buffer_write - 1;
|
||||
}
|
||||
|
||||
static inline unsigned audiocore_get_audio_buffer_avail(void)
|
||||
{
|
||||
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
|
||||
return shared_context.audio_buffer_write - shared_context.audio_buffer_read;
|
||||
else
|
||||
return AUDIO_BUFFER_SIZE - (shared_context.audio_buffer_read - shared_context.audio_buffer_write);
|
||||
}
|
||||
|
||||
static inline void audiocore_audio_buffer_put(const uint32_t *restrict src, const size_t len)
|
||||
{
|
||||
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_write;
|
||||
memcpy(shared_context.audio_buffer + shared_context.audio_buffer_write, src,
|
||||
4 * ((end_samples >= len) ? len : end_samples));
|
||||
if (end_samples < len) {
|
||||
memcpy(shared_context.audio_buffer, src + end_samples, 4 * (len - end_samples));
|
||||
shared_context.audio_buffer_write = len - end_samples;
|
||||
} else {
|
||||
shared_context.audio_buffer_write += len;
|
||||
}
|
||||
shared_context.audio_buffer_write %= AUDIO_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
static inline void audiocore_audio_buffer_get(uint32_t *restrict dst, const size_t len)
|
||||
{
|
||||
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_read;
|
||||
memcpy(dst, shared_context.audio_buffer + shared_context.audio_buffer_read,
|
||||
4 * ((end_samples >= len) ? len : end_samples));
|
||||
if (end_samples < len) {
|
||||
memcpy(dst + end_samples, shared_context.audio_buffer, 4 * (len - end_samples));
|
||||
shared_context.audio_buffer_read = len - end_samples;
|
||||
} else {
|
||||
shared_context.audio_buffer_read += len;
|
||||
}
|
||||
shared_context.audio_buffer_read %= AUDIO_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
void core1_main(void);
|
||||
|
||||
// SHUTDOWN - no arguments - return 0
|
||||
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de
|
||||
87
software/src/audiocore/i2s.c
Normal file
87
software/src/audiocore/i2s.c
Normal file
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#include "audiocore.h"
|
||||
#include "i2s_max98357.pio.h"
|
||||
|
||||
#include <hardware/dma.h>
|
||||
#include <hardware/sync.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define audiocore_pio pio1
|
||||
|
||||
#define I2S_DMA_BUF_SIZE 256
|
||||
|
||||
struct i2s_context {
|
||||
unsigned pio_program_offset;
|
||||
int pio_sm;
|
||||
int dma_ch;
|
||||
dma_channel_config dma_config;
|
||||
uint32_t dma_buf[I2S_DMA_BUF_SIZE];
|
||||
};
|
||||
|
||||
static struct i2s_context i2s_context;
|
||||
|
||||
static void dma_isr(void)
|
||||
{
|
||||
if (!dma_channel_get_irq1_status(i2s_context.dma_ch))
|
||||
return;
|
||||
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
|
||||
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||
if (audiocore_get_audio_buffer_avail() >= I2S_DMA_BUF_SIZE) {
|
||||
audiocore_audio_buffer_get(i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
|
||||
spin_unlock(shared_context.lock, flags);
|
||||
} else {
|
||||
++shared_context.underruns;
|
||||
spin_unlock(shared_context.lock, flags);
|
||||
memset(i2s_context.dma_buf, 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
|
||||
}
|
||||
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
|
||||
}
|
||||
|
||||
static void setup_dma_config(void)
|
||||
{
|
||||
i2s_context.dma_config = dma_channel_get_default_config(i2s_context.dma_ch);
|
||||
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
|
||||
}
|
||||
|
||||
bool i2s_init(int out_pin, int sideset_base, int samplerate)
|
||||
{
|
||||
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0]) * I2S_DMA_BUF_SIZE);
|
||||
if (!pio_can_add_program(audiocore_pio, &i2s_max98357_program))
|
||||
return false;
|
||||
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
|
||||
if (i2s_context.pio_sm == -1)
|
||||
return false;
|
||||
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, &i2s_max98357_program);
|
||||
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, out_pin, sideset_base,
|
||||
samplerate);
|
||||
|
||||
i2s_context.dma_ch = dma_claim_unused_channel(false);
|
||||
if (i2s_context.dma_ch == -1)
|
||||
goto out_dma_claim;
|
||||
setup_dma_config();
|
||||
irq_set_exclusive_handler(DMA_IRQ_1, &dma_isr);
|
||||
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
|
||||
irq_set_enabled(DMA_IRQ_1, true);
|
||||
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
|
||||
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
|
||||
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
|
||||
|
||||
return true;
|
||||
|
||||
out_dma_claim:
|
||||
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
|
||||
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
||||
return false;
|
||||
}
|
||||
|
||||
void i2s_deinit(void)
|
||||
{
|
||||
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
|
||||
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
|
||||
dma_channel_unclaim(i2s_context.dma_ch);
|
||||
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
|
||||
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
||||
}
|
||||
10
software/src/audiocore/i2s.h
Normal file
10
software/src/audiocore/i2s.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool i2s_init(int out_pin, int sideset_base, int samplerate);
|
||||
|
||||
void i2s_deinit(void);
|
||||
59
software/src/audiocore/i2s_max98357.pio
Normal file
59
software/src/audiocore/i2s_max98357.pio
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
.program i2s_max98357
|
||||
.side_set 2
|
||||
|
||||
.lang_opt python sideset_init = pico.PIO.OUT_LOW
|
||||
.lang_opt python out_init = pico.PIO.OUT_LOW
|
||||
.lang_opt python out_shiftdir = pcio.PIO.SHIFT_LEFT
|
||||
.lang_opt python autopull = True
|
||||
|
||||
// data - DOUT
|
||||
// sideset - 2-BCLK, 1-LRCLK
|
||||
|
||||
set x,15 side 0
|
||||
nop side 1
|
||||
startup_loop:
|
||||
nop side 2
|
||||
jmp x-- startup_loop side 3
|
||||
nop side 0
|
||||
set x, 14 side 1
|
||||
|
||||
left_loop:
|
||||
.wrap_target
|
||||
out pins, 1 side 0
|
||||
jmp x-- left_loop side 1
|
||||
out pins, 1 side 2
|
||||
set x, 14 side 3
|
||||
right_loop:
|
||||
out pins, 1 side 2
|
||||
jmp x-- right_loop side 3
|
||||
out pins, 1 side 0
|
||||
set x, 14 side 1
|
||||
.wrap
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
static inline void i2s_max98357_program_init(PIO pio, uint sm, uint offset, uint pin, uint sideset, uint samplerate) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_gpio_init(pio, sideset);
|
||||
pio_gpio_init(pio, sideset+1);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, sideset, 2, true);
|
||||
|
||||
pio_sm_config c = i2s_max98357_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, pin, 1);
|
||||
sm_config_set_sideset_pins(&c, sideset);
|
||||
sm_config_set_out_shift(&c, false, true, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
const unsigned i2s_freq = samplerate * 2 * 16 * 2;
|
||||
const float div = clock_get_hz(clk_sys) / (float)i2s_freq;
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
//pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
19
software/src/audiocore/micropython.cmake
Normal file
19
software/src/audiocore/micropython.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
add_library(usermod_audiocore INTERFACE)
|
||||
|
||||
pico_generate_pio_header(usermod_audiocore ${CMAKE_CURRENT_LIST_DIR}/i2s_max98357.pio)
|
||||
|
||||
target_sources(usermod_audiocore INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/audiocore.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/module.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/i2s.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
|
||||
)
|
||||
|
||||
target_include_directories(usermod_audiocore INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(usermod INTERFACE usermod_audiocore)
|
||||
149
software/src/audiocore/module.c
Normal file
149
software/src/audiocore/module.c
Normal file
@@ -0,0 +1,149 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#include "audiocore.h"
|
||||
|
||||
// Include MicroPython API.
|
||||
#include "py/mperrno.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
// This module is RP2 specific
|
||||
#include "mphalport.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct audiocore_shared_context shared_context = {.lock = NULL};
|
||||
static bool initialized = false;
|
||||
|
||||
struct audiocore_Context_obj {
|
||||
mp_obj_base_t base;
|
||||
};
|
||||
|
||||
/*
|
||||
* audiocore.Context.deinit(self)
|
||||
*
|
||||
* Deinitializes the audiocore.Context and shuts down processing on core1
|
||||
*/
|
||||
static mp_obj_t audiocore_Context_deinit(mp_obj_t self_in)
|
||||
{
|
||||
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
|
||||
multicore_fifo_pop_blocking();
|
||||
(void)self;
|
||||
initialized = false;
|
||||
return mp_const_none;
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_Context_deinit_obj, audiocore_Context_deinit);
|
||||
|
||||
/*
|
||||
* (copied, buf_space, undderuns) = audiocore.Context.put(self, buffer)
|
||||
*
|
||||
* Copies as many integers as possible from the buffer to the audiocore ring buffer for playback.
|
||||
* 'buffer' must be any object supporting the buffer protocol with data in unsigned int (array typecode 'I')
|
||||
* format. The actual number of elements copied is returned in 'copied', the remaining free ring buffer space
|
||||
* is in 'buf_space', and the total number of buffer underruns since initialization of the audiocore Context
|
||||
* is in 'underruns'.
|
||||
*/
|
||||
static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer)
|
||||
{
|
||||
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||
(void)self;
|
||||
mp_buffer_info_t bufinfo;
|
||||
if (!mp_get_buffer(buffer, &bufinfo, MP_BUFFER_READ))
|
||||
mp_raise_ValueError("not a read buffer");
|
||||
if (bufinfo.typecode != 'I')
|
||||
mp_raise_ValueError("unsupported buffer type");
|
||||
unsigned to_copy = bufinfo.len / 4;
|
||||
|
||||
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||
const unsigned buf_space = audiocore_get_audio_buffer_space();
|
||||
if (to_copy > buf_space)
|
||||
to_copy = buf_space;
|
||||
if (to_copy > 0) {
|
||||
audiocore_audio_buffer_put(bufinfo.buf, to_copy);
|
||||
}
|
||||
const unsigned underruns = shared_context.underruns;
|
||||
spin_unlock(shared_context.lock, flags);
|
||||
|
||||
mp_obj_t items[] = {
|
||||
mp_obj_new_int(to_copy),
|
||||
mp_obj_new_int(buf_space),
|
||||
mp_obj_new_int(underruns),
|
||||
};
|
||||
return mp_obj_new_tuple(3, items);
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_Context_put_obj, audiocore_Context_put);
|
||||
|
||||
static const mp_rom_map_elem_t audiocore_Context_locals_dict_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_Context_put_obj)},
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(audiocore_Context_locals_dict, audiocore_Context_locals_dict_table);
|
||||
const mp_obj_type_t audiocore_Context_type;
|
||||
|
||||
MP_DEFINE_CONST_OBJ_TYPE(audiocore_Context_type, MP_QSTR_Context, MP_TYPE_FLAG_NONE, locals_dict,
|
||||
&audiocore_Context_locals_dict);
|
||||
|
||||
/*
|
||||
* audiocore.init(pin, sideset, samplerate)
|
||||
* Returns: audiocore.Context
|
||||
*
|
||||
* Initialize the audiocore module, starting the audio processing on core1 and initializing the I2S driver.
|
||||
* The pin parameter specifies the I2S data pin, the sideset parameter specifies the I2S LRCLK pin, the
|
||||
* BCLK must be on pin sideset+1.
|
||||
* Samplerate should be 44100 or 48000.
|
||||
* Only a single audiocore.Context may exist at a time.
|
||||
* Return a audiocore.Context object on success, raises a OSError or ValueError on failure.
|
||||
*/
|
||||
static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t samplerate_obj)
|
||||
{
|
||||
if (initialized)
|
||||
mp_raise_OSError(MP_EBUSY);
|
||||
if (!shared_context.lock) {
|
||||
// initialize shared context lock on first init
|
||||
int lock = spin_lock_claim_unused(false);
|
||||
if (lock == -1)
|
||||
mp_raise_OSError(MP_ENOMEM);
|
||||
shared_context.lock = spin_lock_init(lock);
|
||||
}
|
||||
shared_context.audio_buffer_write = shared_context.audio_buffer_read = shared_context.underruns = 0;
|
||||
memset(shared_context.audio_buffer, 0, AUDIO_BUFFER_SIZE * 4);
|
||||
multicore_reset_core1();
|
||||
struct audiocore_Context_obj *context = m_malloc_with_finaliser(sizeof(struct audiocore_Context_obj));
|
||||
context->base.type = &audiocore_Context_type;
|
||||
mp_hal_pin_obj_t pin = pin_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(pin_obj);
|
||||
if (pin == -1)
|
||||
mp_raise_ValueError("Invalid out pin");
|
||||
mp_hal_pin_obj_t sideset_pin = sideset_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(sideset_obj);
|
||||
if (sideset_pin == -1)
|
||||
mp_raise_ValueError("Invalid sideset base pin");
|
||||
int samplerate = mp_obj_get_int(samplerate_obj);
|
||||
shared_context.out_pin = pin;
|
||||
shared_context.sideset_base = sideset_pin;
|
||||
shared_context.samplerate = samplerate;
|
||||
initialized = true;
|
||||
multicore_launch_core1(&core1_main);
|
||||
uint32_t result = multicore_fifo_pop_blocking();
|
||||
if (result != 0) {
|
||||
multicore_reset_core1();
|
||||
initialized = false;
|
||||
mp_raise_OSError(result);
|
||||
}
|
||||
|
||||
return MP_OBJ_FROM_PTR(context);
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_3(audiocore_init_obj, audiocore_init);
|
||||
|
||||
static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
|
||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)},
|
||||
{MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&audiocore_init_obj)},
|
||||
{MP_ROM_QSTR(MP_QSTR_Context), MP_ROM_PTR(&audiocore_Context_type)},
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
|
||||
|
||||
const mp_obj_module_t audiocore_cmodule = {
|
||||
.base = {&mp_type_module},
|
||||
.globals = (mp_obj_dict_t *)&audiocore_module_globals,
|
||||
};
|
||||
MP_REGISTER_MODULE(MP_QSTR_audiocore, audiocore_cmodule);
|
||||
192
software/src/audiocore/test/test_audiocore.c
Normal file
192
software/src/audiocore/test/test_audiocore.c
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#include "audiocore.h"
|
||||
#include "i2s.h"
|
||||
#include "py/mperrno.h"
|
||||
|
||||
#include "unity.h"
|
||||
|
||||
struct audiocore_shared_context shared_context;
|
||||
|
||||
static bool i2s_init_return;
|
||||
static bool i2s_initialized = false;
|
||||
static unsigned multicore_fifo_push_last;
|
||||
|
||||
static unsigned (*multicore_fifo_pop_blocking_cb)(void);
|
||||
|
||||
bool i2s_init(int out_pin, int sideset_base, int samplerate)
|
||||
{
|
||||
TEST_ASSERT_FALSE(i2s_initialized);
|
||||
if (i2s_init_return)
|
||||
i2s_initialized = true;
|
||||
return i2s_init_return;
|
||||
}
|
||||
|
||||
void i2s_deinit(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(i2s_initialized);
|
||||
i2s_initialized = false;
|
||||
}
|
||||
|
||||
void multicore_fifo_push_blocking(unsigned val) { multicore_fifo_push_last = val; }
|
||||
|
||||
unsigned multicore_fifo_pop_blocking(void)
|
||||
{
|
||||
if (multicore_fifo_pop_blocking_cb)
|
||||
return multicore_fifo_pop_blocking_cb();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_audiocore_handles_i2sinit_failure(void)
|
||||
{
|
||||
i2s_init_return = false;
|
||||
core1_main();
|
||||
TEST_ASSERT_EQUAL(multicore_fifo_push_last, MP_EIO);
|
||||
}
|
||||
|
||||
unsigned audiocore_init_deinit_pop_cb(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_TRUE(i2s_initialized);
|
||||
return AUDIOCORE_CMD_SHUTDOWN;
|
||||
}
|
||||
|
||||
void test_audiocore_init_deinit(void)
|
||||
{
|
||||
multicore_fifo_pop_blocking_cb = &audiocore_init_deinit_pop_cb;
|
||||
i2s_init_return = true;
|
||||
|
||||
core1_main();
|
||||
TEST_ASSERT_EQUAL(0, multicore_fifo_push_last);
|
||||
TEST_ASSERT_FALSE(i2s_initialized);
|
||||
}
|
||||
|
||||
void test_audiocore_buffer_space(void)
|
||||
{
|
||||
// empty ring buffer
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
||||
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
||||
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
||||
|
||||
// full ring buffer
|
||||
shared_context.audio_buffer_write = 0;
|
||||
shared_context.audio_buffer_read = 1;
|
||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
|
||||
|
||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
||||
shared_context.audio_buffer_read = 0;
|
||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
|
||||
|
||||
// write > read
|
||||
shared_context.audio_buffer_write = 10;
|
||||
shared_context.audio_buffer_read = 0;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1 - 10, audiocore_get_audio_buffer_space());
|
||||
|
||||
// write < read
|
||||
shared_context.audio_buffer_write = 0;
|
||||
shared_context.audio_buffer_read = 10;
|
||||
TEST_ASSERT_EQUAL(9, audiocore_get_audio_buffer_space());
|
||||
}
|
||||
|
||||
void test_audiocore_buffer_avail(void)
|
||||
{
|
||||
// empty ring buffer
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
||||
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
|
||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
||||
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
||||
|
||||
// full ring buffer
|
||||
shared_context.audio_buffer_write = 0;
|
||||
shared_context.audio_buffer_read = 1;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
|
||||
|
||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
||||
shared_context.audio_buffer_read = 0;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
|
||||
|
||||
// write > read
|
||||
shared_context.audio_buffer_write = 10;
|
||||
shared_context.audio_buffer_read = 0;
|
||||
TEST_ASSERT_EQUAL(10, audiocore_get_audio_buffer_avail());
|
||||
|
||||
// write < read
|
||||
shared_context.audio_buffer_write = 0;
|
||||
shared_context.audio_buffer_read = 10;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 10, audiocore_get_audio_buffer_avail());
|
||||
}
|
||||
|
||||
static unsigned fill_buffer_helper(void)
|
||||
{
|
||||
uint32_t test_data[100];
|
||||
uint32_t ctr = 0;
|
||||
|
||||
unsigned avail, filled = 0;
|
||||
while ((avail = audiocore_get_audio_buffer_space()) > 0) {
|
||||
const unsigned todo = avail > 100 ? 100 : avail;
|
||||
for (unsigned i = 0; i < todo; ++i)
|
||||
test_data[i] = ctr++;
|
||||
audiocore_audio_buffer_put(test_data, todo);
|
||||
filled += todo;
|
||||
}
|
||||
return filled;
|
||||
}
|
||||
|
||||
void test_audiocore_buffer_put(void)
|
||||
{
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
|
||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
|
||||
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[i]);
|
||||
}
|
||||
|
||||
// test wraparound fill
|
||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 10;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
|
||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
|
||||
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[(i + AUDIO_BUFFER_SIZE - 10) % AUDIO_BUFFER_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned get_buffer_helper(void)
|
||||
{
|
||||
uint32_t test_data[100];
|
||||
uint32_t ctr = 0;
|
||||
|
||||
unsigned avail, gotten = 0;
|
||||
while ((avail = audiocore_get_audio_buffer_avail()) > 0) {
|
||||
const unsigned todo = avail > 100 ? 100 : avail;
|
||||
audiocore_audio_buffer_get(test_data, todo);
|
||||
for (unsigned i = 0; i < todo; ++i)
|
||||
TEST_ASSERT_EQUAL(ctr++, test_data[i]);
|
||||
gotten += todo;
|
||||
}
|
||||
return gotten;
|
||||
}
|
||||
|
||||
void test_audiocore_buffer_get(void)
|
||||
{
|
||||
shared_context.audio_buffer_read = 0;
|
||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
|
||||
shared_context.audio_buffer[i] = i;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
|
||||
|
||||
// test wraparound read
|
||||
shared_context.audio_buffer_read = 10;
|
||||
shared_context.audio_buffer_write = 9;
|
||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
|
||||
shared_context.audio_buffer[(i + 10) % AUDIO_BUFFER_SIZE] = i;
|
||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
|
||||
}
|
||||
@@ -14,17 +14,18 @@ T1 = 2
|
||||
T2 = 5
|
||||
T3 = 3
|
||||
|
||||
|
||||
@asm_pio(sideset_init=(PIO.OUT_LOW), fifo_join=PIO.JOIN_TX, autopull=True,
|
||||
out_shiftdir=PIO.SHIFT_LEFT)
|
||||
def _ws2812_pio(T1=T1, T2=T2, T3=T3):
|
||||
label("bitloop")
|
||||
out(x, 1).side(0).delay(T3-1)
|
||||
jmp(not_x, "do_zero").side(1) [T1-1]
|
||||
label("do_one")
|
||||
jmp("bitloop").side(1) [T2-1]
|
||||
label("do_zero")
|
||||
nop().side(0) [T2-1]
|
||||
wrap()
|
||||
label("bitloop") # noqa:F821
|
||||
out(x, 1).side(0).delay(T3-1) # noqa:F821
|
||||
jmp(not_x, "do_zero").side(1).delay(T1-1) # noqa:F821
|
||||
label("do_one") # noqa:F821
|
||||
jmp("bitloop").side(1).delay(T2-1) # noqa:F821
|
||||
label("do_zero") # noqa:F821
|
||||
nop().side(0).delay(T2-1) # noqa:F821
|
||||
wrap() # noqa:F821
|
||||
|
||||
|
||||
class NeoPixel:
|
||||
|
||||
129
software/src/test.py
Normal file
129
software/src/test.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
import aiorepl
|
||||
import asyncio
|
||||
import audiocore
|
||||
import machine
|
||||
import micropython
|
||||
import os
|
||||
import time
|
||||
from array import array
|
||||
from machine import Pin, SPI
|
||||
from math import pi, sin, pow
|
||||
from micropython import const
|
||||
from rp2_neopixel import NeoPixel
|
||||
from sdcard import SDCard
|
||||
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
asyncio.create_task(aiorepl.task())
|
||||
|
||||
leds = const(10)
|
||||
brightness = 0.5
|
||||
|
||||
|
||||
def gamma(value, X=2.2):
|
||||
return min(max(int(brightness * pow(value / 255.0, X) * 255.0 + 0.5), 0), 255)
|
||||
|
||||
|
||||
async def rainbow(np, period=10):
|
||||
count = 0.0
|
||||
while True:
|
||||
for i in range(leds):
|
||||
ofs = (count + i) % leds
|
||||
np[i] = (gamma((sin(ofs / leds * 2 * pi) + 1) * 127),
|
||||
gamma((sin(ofs / leds * 2 * pi + 2/3*pi) + 1) * 127),
|
||||
gamma((sin(ofs / leds * 2 * pi + 4/3*pi) + 1) * 127))
|
||||
count += 0.2
|
||||
before = time.ticks_ms()
|
||||
await np.async_write()
|
||||
now = time.ticks_ms()
|
||||
if before + 20 > now:
|
||||
await asyncio.sleep_ms(20 - (now - before))
|
||||
|
||||
|
||||
samplerate = 44100
|
||||
hz = 441
|
||||
count = 100
|
||||
amplitude = 0x1fff
|
||||
buf = array('I', range(count))
|
||||
for i in range(len(buf)):
|
||||
val = int(sin(i * hz / samplerate * 2 * pi)*amplitude) & 0xffff
|
||||
buf[i] = (val << 16) | val
|
||||
|
||||
|
||||
async def output_sound(audioctx):
|
||||
pos = 0
|
||||
known_underruns = 0
|
||||
while True:
|
||||
pushed, avail, underruns = audioctx.put(buf[pos:])
|
||||
pos = (pos + pushed) % len(buf)
|
||||
# print(f"pushed {pushed}, pos {pos}, avail {avail}")
|
||||
if underruns > known_underruns:
|
||||
print(f"{underruns:x}")
|
||||
known_underruns = underruns
|
||||
await asyncio.sleep(0)
|
||||
|
||||
|
||||
# Set 8 mA drive strength and fast slew rate
|
||||
machine.mem32[0x4001c004 + 6*4] = 0x67
|
||||
machine.mem32[0x4001c004 + 7*4] = 0x67
|
||||
machine.mem32[0x4001c004 + 8*4] = 0x67
|
||||
|
||||
|
||||
def list_sd():
|
||||
sd_spi = SPI(0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
|
||||
try:
|
||||
sd = SDCard(sd_spi, Pin(5), 25000000)
|
||||
except OSError:
|
||||
for i in range(leds):
|
||||
np[i] = (255, 0, 0)
|
||||
np.write()
|
||||
return
|
||||
try:
|
||||
os.mount(sd, '/sd')
|
||||
print(os.listdir('/sd'))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
delay_sum = 0
|
||||
delay_count = 0
|
||||
max_delay = 0
|
||||
|
||||
|
||||
async def latency_test():
|
||||
global delay_sum
|
||||
global delay_count
|
||||
global max_delay
|
||||
await asyncio.sleep_ms(1)
|
||||
while True:
|
||||
for _ in range(2000):
|
||||
before = time.ticks_us()
|
||||
await asyncio.sleep(0)
|
||||
after = time.ticks_us()
|
||||
delay = after - before
|
||||
delay_sum += delay
|
||||
delay_count += 1
|
||||
if delay > max_delay:
|
||||
max_delay = delay
|
||||
await asyncio.sleep_ms(1)
|
||||
print(f"Max delay {max_delay} us, average {delay/delay_sum} us")
|
||||
|
||||
pin = Pin.board.GP16
|
||||
np = NeoPixel(pin, leds)
|
||||
|
||||
# Test SD card
|
||||
list_sd()
|
||||
|
||||
# Test NeoPixel
|
||||
asyncio.create_task(rainbow(np))
|
||||
|
||||
# Test audio
|
||||
audioctx = audiocore.init(Pin(8), Pin(6), samplerate)
|
||||
asyncio.create_task(output_sound(audioctx))
|
||||
|
||||
asyncio.create_task(latency_test())
|
||||
|
||||
asyncio.get_event_loop().run_forever()
|
||||
10
software/src/test/include/hardware/sync.h
Normal file
10
software/src/test/include/hardware/sync.h
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
#pragma once
|
||||
|
||||
struct spin_lock;
|
||||
typedef struct spin_lock spin_lock_t;
|
||||
|
||||
void multicore_fifo_push_blocking(unsigned val);
|
||||
unsigned multicore_fifo_pop_blocking(void);
|
||||
5
software/src/test/include/py/mperrno.h
Normal file
5
software/src/test/include/py/mperrno.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#define MP_EIO EIO
|
||||
Reference in New Issue
Block a user