Initial Release

pull/1/head
graham sanderson 2021-01-20 11:10:41 -06:00
commit f5c7be9a86
93 zmienionych plików z 15120 dodań i 0 usunięć

3
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
.idea
.vscode
cmake-*

5
.gitmodules vendored 100644
Wyświetl plik

@ -0,0 +1,5 @@
[submodule "lwip"]
path = lib/lwip
url = https://git.savannah.nongnu.org/git/lwip.git
[submodule "lib/lwip"]
url = git://git.savannah.nongnu.org/lwip.git

32
CMakeLists.txt 100644
Wyświetl plik

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.12)
# Pull in PICO SDK (must be before project)
include(pico_sdk_import.cmake)
project(pico_extras C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Initialize the SDK
pico_sdk_init()
pico_is_top_level_project(PICO_EXTRAS_TOP_LEVEL_PROJECT)
add_library(pico_extras_included INTERFACE)
target_compile_definitions(pico_extras_included INTERFACE
-DPICO_EXTRAS=1
)
pico_add_platform_library(pico_extras_included)
if (NOT PICO_EXTRAS_PATH)
set(PICO_EXTRAS_PATH ${CMAKE_CURRENT_LIST_DIR})
endif()
set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to Pico Extras")
add_subdirectory(src)
if (PICO_EXTRAS_TESTS_ENABLED OR PICO_EXTRAS_TOP_LEVEL_PROJECT)
add_subdirectory(test)
endif ()

21
LICENSE.TXT 100644
Wyświetl plik

@ -0,0 +1,21 @@
Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

46
README.md 100644
Wyświetl plik

@ -0,0 +1,46 @@
This repo has additional libraries that are not yet ready for inclusion the Pico SDK proper,
or are just useful but don't necessarily belong in the Pico SDK.
Note that any API here is a work in progress and subject to change.
See [pico-playground](https://github.com/raspberrypi/pico-playground) for buildable example code using these extra libraries.
Library|Description
---|---
[hardware_rosc](src/rp2_common/hardware_rosc)| API for the ring oscillator
[lwip](src/rp2_common/lwip)| [LWIP Lightweight IP Library](https://savannah.nongnu.org/projects/lwip/) packed as an INTERFACE library for use with the Pico SDK
[pico_audio](src/common/pico_audio)|Audio output support; this is highly functional, but the API is subject to change
   [pico_audio_i2s](src/rp2_common/pico_audio_spdif)|Audio output via I2S on 3 GPIOs using PIO. Arbitrary frequency
   [pico_audio_pwm](src/rp2_common/pico_audio_spdif)|Audio output via (PIO) PWM. Currently a bit limited in frequency support (it was developed on FPGA to do 22050Hz at 48Mhz system clock). It does however support error diffusion dithering and noise shaping with 16x oversampling to give surprsingly good audio quality. This code will be split to provide both a fixed frequencie(s) version and a slightly slower but rather better arbitrary frequency version supporting ever higher carrier frequencies
   [pico_audio_spdif](src/rp2_common/pico_audio_spdif)|Audio output in S/PDIF on a GPIO using PIO. Supports up to 192khz stereo. Consumed OK in test, haven't tried it with real hardware
[pico_sd_card](src/rp2_common/pico_sd_card)|1 and 4 bit SDIO support using PIO. This is functional (currently writing is only 1 bit), but the the code is very much prototype and the API is just a placeholder - the command set needs to be separated from the SDIO and shared with SPI
[pico_sleep](src/rp2_common/pico_sleep)|Low power related APIs, WIP because they are not sufficiently generic and also only handle core 0
[pico_scanvideo](src/common/pico_scanvideo)|Support for video output where every pixel is _scanned out_ everry frame. VGA/DPI support is highgly functional and stable, but the API is subject to change
   [pico_scanvideo_dbi](src/rp2_common/pico_scanvideo_dbi)| currently non-compiling... placeholder for adding scanvideo over MIPI DBI support.
   [pico_scanvideo_dpi](src/rp2_common/pico_scanvideo_dbi)| Highly functional and stable support for parallel RGB output and VSYNC/HSYNC/DEN/CLOCK for VGA/DPI.
[pico_util_buffer](src/common/pico_util_buffer)|Rather incomplete buffer abstraction, used by pico_audio and pico_scanvideo
[platypus](src/common/platypus)| Decoder for a custom image compression format suitable for dithered images (good for RGB555) and suitable for decoding on RP2040 at scanline speeds ... i.e you can easily decode a 320x240 image 60x per second to avoid storing the uncompressed image for scanout video. It gets about 50% compression (but is designed only for 4x4 fixed dithered RGB555 images, so is somewhat specific!). TODO add the encoder here :-)
[usb_device](src/rp2_common/usb_device), [usb_common](src/rp2_common/usb_common)| The custom and somewhat minimal USB device stack used in the bootrom. We now use TinyUSB in the Pico SDK but kep here for posterit
[usb_device_msc](src/rp2_common/usb_device_msc)| USB Mass Storage Class implementation using _usb_device_
You can add Pico Extras to your project similarly to the SDK (copying [external/pico_extras_import.cmake](external/pico_extras_import.cmake) into your project)
having set the `PICO_EXTRAS_PATH` variable in your environment or via cmake variable.
```cmake
cmake_minimum_required(VERSION 3.12)
# Pull in PICO SDK (must be before project)
include(pico_sdk_import.cmake)
# We also need PICO EXTRAS
include(pico_extras_import.cmake)
project(pico_playground C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
```
Alternative you can inject it into an existing project without modifying it via `PICO_CMAKE_POST_LIST_DIRS`
by passing `-DPICO_SDK_POST_LIST_DIRS=/path/to/pico_extras` to cmake

Wyświetl plik

@ -0,0 +1,62 @@
# This is a copy of <PICO_EXTRAS_PATH>/external/pico_extras_import.cmake
# This can be dropped into an external project to help locate pico-extras
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH))
set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH})
message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')")
endif ()
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT))
set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT})
message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH))
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH})
message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')")
endif ()
if (NOT PICO_EXTRAS_PATH)
if (PICO_EXTRAS_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_EXTRAS_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
PICO_EXTRAS
GIT_REPOSITORY https://github.com/raspberrypi/pico-extras
GIT_TAG master
)
if (NOT PICO_EXTRAS)
message("Downloading PICO EXTRAS")
FetchContent_Populate(PICO_EXTRAS)
set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras")
set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras)
message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}")
else()
message(FATAL_ERROR
"PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git."
)
endif()
endif ()
endif ()
set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS")
set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable")
set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS")
get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_EXTRAS_PATH})
message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found")
endif ()
set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE)
add_subdirectory(${PICO_EXTRAS_PATH} pico_extras)

1
lib/lwip 160000

@ -0,0 +1 @@
Subproject commit c385f31076b27efb8ee37f00cb5568783a58f299

Wyświetl plik

@ -0,0 +1,62 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
if (NOT pico_sdk)
message("Downloading PICO SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

Wyświetl plik

@ -0,0 +1,4 @@
add_subdirectory(common)
if (PICO_ON_DEVICE)
add_subdirectory(rp2_common)
endif()

Wyświetl plik

@ -0,0 +1,5 @@
add_subdirectory(pico_audio)
add_subdirectory(pico_scanvideo)
add_subdirectory(pico_sd_card)
add_subdirectory(pico_util_buffer)
add_subdirectory(platypus)

Wyświetl plik

@ -0,0 +1,16 @@
if (NOT TARGET pico_audio_headers)
add_library(pico_audio_headers INTERFACE)
target_include_directories(pico_audio_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_audio_headers INTERFACE pico_util_buffer)
endif()
if (NOT TARGET pico_audio)
add_library(pico_audio INTERFACE)
target_sources(pico_audio INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audio.cpp
$<$<NOT:$<BOOL:${PICO_NO_HARDWARE}>>:${CMAKE_CURRENT_LIST_DIR}/audio_utils.S>
)
target_link_libraries(pico_audio INTERFACE pico_audio_headers pico_sync)
endif()

Wyświetl plik

@ -0,0 +1,254 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstring>
#include "pico/audio.h"
#include "pico/sample_conversion.h"
// ======================
// == DEBUGGING =========
#define ENABLE_AUDIO_ASSERTIONS
#ifdef ENABLE_AUDIO_ASSERTIONS
#define audio_assert(x) assert(x)
#else
#define audio_assert(x) (void)0
#endif
inline static audio_buffer_t *list_remove_head(audio_buffer_t **phead) {
audio_buffer_t *ab = *phead;
if (ab) {
*phead = ab->next;
ab->next = NULL;
}
return ab;
}
inline static audio_buffer_t *list_remove_head_with_tail(audio_buffer_t **phead,
audio_buffer_t **ptail) {
audio_buffer_t *ab = *phead;
if (ab) {
*phead = ab->next;
if (!ab->next) {
audio_assert(*ptail == ab);
*ptail = NULL;
} else {
ab->next = NULL;
}
}
return ab;
}
inline static void list_prepend(audio_buffer_t **phead, audio_buffer_t *ab) {
audio_assert(ab->next == NULL);
audio_assert(ab != *phead);
ab->next = *phead;
*phead = ab;
}
// todo add a tail for these already sorted lists as we generally insert on the end
inline static void list_append_with_tail(audio_buffer_t **phead, audio_buffer_t **ptail,
audio_buffer_t *ab) {
audio_assert(ab->next == NULL);
audio_assert(ab != *phead);
audio_assert(ab != *ptail);
if (!*phead) {
audio_assert(!*ptail);
*ptail = ab;
// insert at the beginning
list_prepend(phead, ab);
} else {
// insert at end
(*ptail)->next = ab;
*ptail = ab;
}
}
audio_buffer_t *get_free_audio_buffer(audio_buffer_pool_t *context, bool block) {
audio_buffer_t *ab;
do {
uint32_t save = spin_lock_blocking(context->free_list_spin_lock);
ab = list_remove_head(&context->free_list);
spin_unlock(context->free_list_spin_lock, save);
if (ab || !block) break;
__wfe();
} while (true);
return ab;
}
void queue_free_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) {
assert(!ab->next);
uint32_t save = spin_lock_blocking(context->free_list_spin_lock);
list_prepend(&context->free_list, ab);
spin_unlock(context->free_list_spin_lock, save);
__sev();
}
audio_buffer_t *get_full_audio_buffer(audio_buffer_pool_t *context, bool block) {
audio_buffer_t *ab;
do {
uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock);
ab = list_remove_head_with_tail(&context->prepared_list, &context->prepared_list_tail);
spin_unlock(context->prepared_list_spin_lock, save);
if (ab || !block) break;
__wfe();
} while (true);
return ab;
}
void queue_full_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) {
assert(!ab->next);
uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock);
list_append_with_tail(&context->prepared_list, &context->prepared_list_tail, ab);
spin_unlock(context->prepared_list_spin_lock, save);
__sev();
}
void producer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) {
queue_full_audio_buffer(connection->producer_pool, buffer);
}
audio_buffer_t *producer_pool_take_buffer_default(audio_connection_t *connection, bool block) {
return get_free_audio_buffer(connection->producer_pool, block);
}
void consumer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) {
queue_free_audio_buffer(connection->consumer_pool, buffer);
}
audio_buffer_t *consumer_pool_take_buffer_default(audio_connection_t *connection, bool block) {
return get_full_audio_buffer(connection->consumer_pool, block);
}
static audio_connection_t connection_default = {
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = producer_pool_give_buffer_default,
.consumer_pool_take = consumer_pool_take_buffer_default,
.consumer_pool_give = consumer_pool_give_buffer_default,
};
audio_buffer_t *audio_new_buffer(audio_buffer_format_t *format, int buffer_sample_count) {
audio_buffer_t *buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t));
audio_init_buffer(buffer, format, buffer_sample_count);
return buffer;
}
void audio_init_buffer(audio_buffer_t *audio_buffer, audio_buffer_format_t *format, int buffer_sample_count) {
audio_buffer->format = format;
audio_buffer->buffer = pico_buffer_alloc(buffer_sample_count * format->sample_stride);
audio_buffer->max_sample_count = buffer_sample_count;
audio_buffer->sample_count = 0;
}
audio_buffer_pool_t *
audio_new_buffer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
audio_buffer_pool_t *ac = (audio_buffer_pool_t *) calloc(1, sizeof(audio_buffer_pool_t));
audio_buffer_t *audio_buffers = buffer_count ? (audio_buffer_t *) calloc(buffer_count,
sizeof(audio_buffer_t)) : 0;
ac->format = format->format;
for (int i = 0; i < buffer_count; i++) {
audio_init_buffer(audio_buffers + i, format, buffer_sample_count);
audio_buffers[i].next = i != buffer_count - 1 ? &audio_buffers[i + 1] : NULL;
}
// todo one per channel?
ac->free_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_FREE_LIST_LOCK);
ac->free_list = audio_buffers;
ac->prepared_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK);
ac->prepared_list = NULL;
ac->prepared_list_tail = NULL;
ac->connection = &connection_default;
return ac;
}
audio_buffer_t *audio_new_wrapping_buffer(audio_buffer_format_t *format, mem_buffer_t *buffer) {
audio_buffer_t *audio_buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t));
if (audio_buffer) {
audio_buffer->format = format;
audio_buffer->buffer = buffer;
audio_buffer->max_sample_count = buffer->size / format->sample_stride;
audio_buffer->sample_count = 0;
audio_buffer->next = 0;
}
return audio_buffer;
}
audio_buffer_pool_t *
audio_new_producer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count);
ac->type = audio_buffer_pool::ac_producer;
return ac;
}
audio_buffer_pool_t *
audio_new_consumer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) {
audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count);
ac->type = audio_buffer_pool::ac_consumer;
return ac;
}
void audio_complete_connection(audio_connection_t *connection, audio_buffer_pool_t *producer_pool,
audio_buffer_pool_t *consumer_pool) {
assert(producer_pool->type == audio_buffer_pool::ac_producer);
assert(consumer_pool->type == audio_buffer_pool::ac_consumer);
producer_pool->connection = connection;
consumer_pool->connection = connection;
connection->producer_pool = producer_pool;
connection->consumer_pool = consumer_pool;
}
void give_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer) {
buffer->user_data = 0;
assert(ac->connection);
if (ac->type == audio_buffer_pool::ac_producer)
ac->connection->producer_pool_give(ac->connection, buffer);
else
ac->connection->consumer_pool_give(ac->connection, buffer);
}
audio_buffer_t *take_audio_buffer(audio_buffer_pool_t *ac, bool block) {
assert(ac->connection);
if (ac->type == audio_buffer_pool::ac_producer)
return ac->connection->producer_pool_take(ac->connection, block);
else
return ac->connection->consumer_pool_take(ac->connection, block);
}
// todo rename this - this is s16 to s16
audio_buffer_t *mono_to_mono_consumer_take(audio_connection_t *connection, bool block) {
return consumer_pool_take<Mono<FmtS16>, Mono<FmtS16>>(connection, block);
}
// todo rename this - this is s16 to s16
audio_buffer_t *stereo_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
return consumer_pool_take<Stereo<FmtS16>, Stereo<FmtS16>>(connection, block);
}
// todo rename this - this is s16 to s16
audio_buffer_t *mono_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
return consumer_pool_take<Stereo<FmtS16>, Mono<FmtS16>>(connection, block);
}
audio_buffer_t *mono_s8_to_mono_consumer_take(audio_connection_t *connection, bool block) {
return consumer_pool_take<Mono<FmtS16>, Mono<FmtS8>>(connection, block);
}
audio_buffer_t *mono_s8_to_stereo_consumer_take(audio_connection_t *connection, bool block) {
return consumer_pool_take<Stereo<FmtS16>, Mono<FmtS8>>(connection, block);
}
void stereo_to_stereo_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
return producer_pool_blocking_give<Stereo<FmtS16>, Stereo<FmtS16>>(connection, buffer);
}

Wyświetl plik

@ -0,0 +1,256 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
.syntax unified
.cpu cortex-m0plus
.thumb
#define AUDIO_UPSAMPLE_SCALE_BITS 12
.align 2
.section .time_critical.audio_upsample
.global audio_upsample
.type audio_upsample,%function
// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined)
// void audio_upsample(int16_t *input, int16_t *output, int count, uint32_t step)
.thumb_func
audio_upsample:
push {r4, r5, r6, r7, lr}
lsls r2, #1
mov ip, r1
add ip, r2
ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET
// interp_configure_with_signed_and_blend
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 - AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// interp_configure_with_signed_and_cross_input
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
movs r0, #0
str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
mov r7, r0 // last_offset = 0 (invalid)
movs r2, #2
// r0 0
// r1 output
// r2 2
// r3 step
// r4 temp
// r5 temp
// r6 interp_hw
// r7 last_offset
// ip end
b 4f
1: // aligned
ldr r5, [r4]
str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
2: // unchanged sample ptr
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
add r1, r2
cmp r1, ip
beq 5f
3: // next sample
ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
cmp r4, r7
beq 2b
mov r7, r4
tst r4, r2
beq 1b
ldrsh r5, [r4, r0]
str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldrsh r4, [r4, r2]
str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
add r1, r2
4:
cmp r1, ip
bne 3b
5:
pop {r4, r5, r6, r7, pc}
.align 2
.section .time_critical.audio_upsample_words
.global audio_upsample_words
.type audio_upsample_words,%function
// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined)
// void audio_upsample(int16_t *input, int16_t *output_aligned, int output_word_count, uint32_t step)
.thumb_func
audio_upsample_words:
push {r4, r5, r6, r7, lr}
lsls r2, #2
mov ip, r1
add ip, r2
ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET
// interp_configure_with_blend
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 -AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// interp_configure_with_signed_and_cross_input
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
movs r0, #0
str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
mov r7, r0 // last_offset = 0 (invalid)
movs r2, #2
// r0 0
// r1 output
// r2 2
// r3 step
// r4 temp
// r5 temp
// r6 interp_hw
// r7 last_offset
// ip end
b 4f
1: // aligned A
ldr r5, [r4]
str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
2: // unchanged sample ptr A
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// output A
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// next sample B
ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
cmp r4, r7
beq 6f
mov r7, r4
tst r4, r2
bne 7f
8:
// aligned B
ldr r5, [r4]
str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
6: // unchanged sample ptr B
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1, r2]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
adds r1, #4
cmp r1, ip
beq 5f
3: // next sample A
ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
cmp r4, r7
beq 2b
mov r7, r4
tst r4, r2
beq 1b
ldrsh r5, [r4, r0]
str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldrsh r4, [r4, r2]
str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// next sample B
ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
cmp r4, r7
beq 6b
mov r7, r4
tst r4, r2
beq 8b
7: // unalignedb
ldrsh r5, [r4, r0]
str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldrsh r4, [r4, r2]
str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1, r2]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
adds r1, #4
4:
cmp r1, ip
bne 3b
5:
pop {r4, r5, r6, r7, pc}
.global audio_upsample_double
.type audio_upsample_double,%function
// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined)
// void audio_upsample(int16_t *input, int16_t *output, int count, uint32_t step)
.thumb_func
audio_upsample_double:
push {r4, r5, r6, r7, lr}
lsls r2, #2
mov ip, r1
add ip, r2
ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET
// interp_configure_with_signed_and_blend
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 - AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
// interp_configure_with_signed_and_cross_input
ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS
str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
movs r0, #0
str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
mov r7, r0 // last_offset = 0 (invalid)
movs r2, #2
// r0 0
// r1 output
// r2 2
// r3 step
// r4 temp
// r5 temp
// r6 interp_hw
// r7 last_offset
// ip end
b 4f
1: // aligned
ldr r5, [r4]
str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
2: // unchanged sample ptr
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1, #2]
add r1, r2
add r1, r2
cmp r1, ip
beq 5f
3: // next sample
ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
cmp r4, r7
beq 2b
mov r7, r4
tst r4, r2
beq 1b
ldrsh r5, [r4, r0]
str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldrsh r4, [r4, r2]
str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1]
str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET]
strh r4, [r1, #2]
add r1, r2
add r1, r2
4:
cmp r1, ip
bne 3b
5:
pop {r4, r5, r6, r7, pc}

Wyświetl plik

@ -0,0 +1,308 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_H
#define _PICO_AUDIO_H
#include "pico.h"
#include "pico/util/buffer.h"
#include "hardware/sync.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file audio.h
* \defgroup pico_audio pico_audio
*
* Common API for audio output
*
*/
// PICO_CONFIG: SPINLOCK_ID_AUDIO_FREE_LIST_LOCK, Spinlock number for the audio free list, min=0, max=31, default=6, group=audio
#ifndef SPINLOCK_ID_AUDIO_FREE_LIST_LOCK
#define SPINLOCK_ID_AUDIO_FREE_LIST_LOCK 6
#endif
// PICO_CONFIG: SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK, Spinlock number for the audio prepared list, min=0, max=31, default=7, group=audio
#ifndef SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK
#define SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK 7
#endif
// PICO_CONFIG: PICO_AUDIO_NOOP, Enable/disable audio by forcing NOOPS, type=bool, default=0, group=audio
#ifndef PICO_AUDIO_NOOP
#define PICO_AUDIO_NOOP 0
#endif
#define AUDIO_BUFFER_FORMAT_PCM_S16 1 ///< signed 16bit PCM
#define AUDIO_BUFFER_FORMAT_PCM_S8 2 ///< signed 8bit PCM
#define AUDIO_BUFFER_FORMAT_PCM_U16 3 ///< unsigned 16bit PCM
#define AUDIO_BUFFER_FORMAT_PCM_U8 4 ///< unsigned 16bit PCM
/** \brief Audio format definition
*/
typedef struct audio_format {
uint32_t sample_freq; ///< Sample frequency in Hz
uint16_t format; ///< Audio format \ref audio_formats
uint16_t channel_count; ///< Number of channels
} audio_format_t;
/** \brief Audio buffer format definition
*/
typedef struct audio_buffer_format {
const audio_format_t *format; ///< Audio format
uint16_t sample_stride; ///< Sample stride
} audio_buffer_format_t;
/** \brief Audio buffer definition
*/
typedef struct audio_buffer {
mem_buffer_t *buffer;
const audio_buffer_format_t *format;
uint32_t sample_count;
uint32_t max_sample_count;
uint32_t user_data; // only valid while the user has the buffer
// private - todo make an internal version
struct audio_buffer *next;
} audio_buffer_t;
typedef struct audio_connection audio_connection_t;
typedef struct audio_buffer_pool {
enum {
ac_producer, ac_consumer
} type;
const audio_format_t *format;
// private
audio_connection_t *connection;
spin_lock_t *free_list_spin_lock;
// ----- begin protected by free_list_spin_lock -----
audio_buffer_t *free_list;
spin_lock_t *prepared_list_spin_lock;
audio_buffer_t *prepared_list;
audio_buffer_t *prepared_list_tail;
} audio_buffer_pool_t;
typedef struct audio_connection audio_connection_t;
struct audio_connection {
audio_buffer_t *(*producer_pool_take)(audio_connection_t *connection, bool block);
void (*producer_pool_give)(audio_connection_t *connection, audio_buffer_t *buffer);
audio_buffer_t *(*consumer_pool_take)(audio_connection_t *connection, bool block);
void (*consumer_pool_give)(audio_connection_t *connection, audio_buffer_t *buffer);
audio_buffer_pool_t *producer_pool;
audio_buffer_pool_t *consumer_pool;
};
/*! \brief Allocate and initialise an audio producer pool
* \ingroup pico_audio
*
* \param format Format of the audio buffer
* \param buffer_count \todo
* \param buffer_sample_count \todo
* \return Pointer to an audio_buffer_pool
*/
audio_buffer_pool_t *audio_new_producer_pool(audio_buffer_format_t *format, int buffer_count,
int buffer_sample_count);
/*! \brief Allocate and initialise an audio consumer pool
* \ingroup pico_audio
*
* \param format Format of the audio buffer
* \param buffer_count
* \param buffer_sample_count
* \return Pointer to an audio_buffer_pool
*/
audio_buffer_pool_t *audio_new_consumer_pool(audio_buffer_format_t *format, int buffer_count,
int buffer_sample_count);
/*! \brief Allocate and initialise an audio wrapping buffer
* \ingroup pico_audio
*
* \param format Format of the audio buffer
* \param buffer \todo
* \return Pointer to an audio_buffer
*/
audio_buffer_t *audio_new_wrapping_buffer(audio_buffer_format_t *format, mem_buffer_t *buffer);
/*! \brief Allocate and initialise an new audio buffer
* \ingroup pico_audio
*
* \param format Format of the audio buffer
* \param buffer_sample_count \todo
* \return Pointer to an audio_buffer
*/
audio_buffer_t *audio_new_buffer(audio_buffer_format_t *format, int buffer_sample_count);
/*! \brief Initialise an audio buffer
* \ingroup pico_audio
*
* \param audio_buffer Pointer to an audio_buffer
* \param format Format of the audio buffer
* \param buffer_sample_count \todo
*/
void audio_init_buffer(audio_buffer_t *audio_buffer, audio_buffer_format_t *format, int buffer_sample_count);
/*! \brief \todo
* \ingroup pico_audio
*
* \param ac \todo
* \param buffer \todo
* \return Pointer to an audio_buffer
*/
void give_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer);
/*! \brief \todo
* \ingroup pico_audio
*
* \return Pointer to an audio_buffer
*/
audio_buffer_t *take_audio_buffer(audio_buffer_pool_t *ac, bool block);
/*! \brief \todo
* \ingroup pico_audio
*
*/
static inline void release_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer) {
buffer->sample_count = 0;
give_audio_buffer(ac, buffer);
}
/*! \brief \todo
* \ingroup pico_audio
*
* todo we are currently limited to 4095+1 input samples
* step is fraction of an input sample per output sample * 0x1000 and should be < 0x1000 i.e. we we are up-sampling (otherwise results are undefined)
*/
void audio_upsample(int16_t *input, int16_t *output, uint output_count, uint32_t step);
/*! \brief \todo
* \ingroup pico_audio
* similar but the output buffer is word aligned, and we output an even number of samples.. this is slightly faster than the above
* todo we are currently limited to 4095+1 input samples
* step is fraction of an input sample per output sample * 0x1000 and should be < 0x1000 i.e. we we are up-sampling (otherwise results are undefined)
*/
void audio_upsample_words(int16_t *input, int16_t *output_aligned, uint output_word_count, uint32_t step);
/*! \brief \todo
* \ingroup pico_audio
*/
void audio_upsample_double(int16_t *input, int16_t *output, uint output_count, uint32_t step);
/*! \brief \todo
* \ingroup pico_audio
*/
void audio_complete_connection(audio_connection_t *connection, audio_buffer_pool_t *producer,
audio_buffer_pool_t *consumer);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *get_free_audio_buffer(audio_buffer_pool_t *context, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
void queue_free_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *get_full_audio_buffer(audio_buffer_pool_t *context, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
void queue_full_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab);
/*! \brief \todo
* \ingroup pico_audio
*
* generally an pico_audio connection uses 3 of the defaults and does the hard work in one of them
*/
void consumer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *consumer_pool_take_buffer_default(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
void producer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *producer_pool_take_buffer_default(audio_connection_t *connection, bool block);
enum audio_correction_mode {
none,
fixed_dither,
dither,
noise_shaped_dither,
};
struct buffer_copying_on_consumer_take_connection {
struct audio_connection core;
audio_buffer_t *current_producer_buffer;
uint32_t current_producer_buffer_pos;
};
struct producer_pool_blocking_give_connection {
audio_connection_t core;
audio_buffer_t *current_consumer_buffer;
uint32_t current_consumer_buffer_pos;
};
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *mono_to_mono_consumer_take(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *mono_s8_to_mono_consumer_take(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *stereo_to_stereo_consumer_take(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *mono_to_stereo_consumer_take(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
audio_buffer_t *mono_s8_to_stereo_consumer_take(audio_connection_t *connection, bool block);
/*! \brief \todo
* \ingroup pico_audio
*/
void stereo_to_stereo_producer_give(audio_connection_t *connection, audio_buffer_t *buffer);
// not worth a separate header for now
typedef struct __packed pio_audio_channel_config {
uint8_t base_pin;
uint8_t dma_channel;
uint8_t pio_sm;
} pio_audio_channel_config_t;
#ifdef __cplusplus
}
#endif
#endif //_AUDIO_H

Wyświetl plik

@ -0,0 +1,287 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SOFTWARE_SAMPLE_CONVERSION_H
#define SOFTWARE_SAMPLE_CONVERSION_H
#include <algorithm>
#include <cstring>
#include "pico/audio.h"
#include "pico/util/buffer.h"
template<typename _sample_t>
struct FmtDetails {
public:
static const uint channel_count = 1;
static const uint frame_stride = channel_count * sizeof(_sample_t);
typedef _sample_t sample_t;
};
typedef struct : public FmtDetails<uint8_t> {
} FmtU8;
typedef struct : public FmtDetails<int8_t> {
} FmtS8;
typedef struct : public FmtDetails<uint16_t> {
} FmtU16;
typedef struct : public FmtDetails<int16_t> {
} FmtS16;
// Multi channel is just N samples back to back
template<typename Fmt, uint ChannelCount>
struct MultiChannelFmt {
static const uint channel_count = ChannelCount;
static const uint frame_stride = ChannelCount * Fmt::frame_stride;
typedef typename Fmt::sample_t sample_t;
};
// define Mono<X> details as one channel
template<typename Fmt> using Mono = MultiChannelFmt<Fmt, 1>;
// define Stereo<X> details as two channels
template<typename Fmt> using Stereo = MultiChannelFmt<Fmt, 2>;
template<typename ToFmt, typename FromFmt>
struct sample_converter {
static typename ToFmt::sample_t convert_sample(const typename FromFmt::sample_t &sample);
};
// noop conversion
template<typename Fmt>
struct sample_converter<Fmt, Fmt> {
static typename Fmt::sample_t convert_sample(const typename Fmt::sample_t &sample) {
return sample;
}
};
// converters to S16
template<>
struct sample_converter<FmtS16, FmtU16> {
static int16_t convert_sample(const uint16_t &sample) {
return sample ^ 0x8000u;
}
};
template<>
struct sample_converter<FmtS16, FmtS8> {
static int16_t convert_sample(const int8_t &sample) {
return sample << 8u;
}
};
template<>
struct sample_converter<FmtS16, FmtU8> {
static int16_t convert_sample(const uint8_t &sample) {
return (sample << 8u) ^ 0x8000u;
}
};
// converters to U16
template<>
struct sample_converter<FmtU16, FmtS8> {
static uint16_t convert_sample(const int8_t &sample) {
return (sample << 8u) ^ 0x8000u;
}
};
template<>
struct sample_converter<FmtU16, FmtU8> {
static uint16_t convert_sample(const uint8_t &sample) {
return sample << 8u;
}
};
template<>
struct sample_converter<FmtU16, FmtS16> {
static uint16_t convert_sample(const int16_t &sample) {
return sample ^ 0x8000u;
}
};
// converters to S8
template<>
struct sample_converter<FmtS8, FmtU16> {
static int8_t convert_sample(const uint16_t &sample) {
return (sample ^ 0x8000u) >> 8u;
}
};
template<>
struct sample_converter<FmtS8, FmtU8> {
static int8_t convert_sample(const uint8_t &sample) {
return sample ^ 0x80;
}
};
template<>
struct sample_converter<FmtS8, FmtS16> {
static int8_t convert_sample(const int16_t &sample) {
return sample >> 8u;
}
};
// converters to U8
template<>
struct sample_converter<FmtU8, FmtU16> {
static uint8_t convert_sample(const uint16_t &sample) {
return sample >> 8u;
}
};
template<>
struct sample_converter<FmtU8, FmtS8> {
static uint8_t convert_sample(const int8_t &sample) {
return sample ^ 0x80;
}
};
template<>
struct sample_converter<FmtU8, FmtS16> {
static uint8_t convert_sample(const int16_t &sample) {
return (sample ^ 0x8000u) >> 8u;
}
};
// template type for doing sample conversion
template<typename ToFmt, typename FromFmt>
struct converting_copy {
static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count);
};
// Efficient copies of same sample type
template<class Fmt, uint ChannelCount>
struct converting_copy<MultiChannelFmt<Fmt, ChannelCount>, MultiChannelFmt<Fmt, ChannelCount>> {
static void copy(typename MultiChannelFmt<Fmt, ChannelCount>::sample_t *dest,
const typename MultiChannelFmt<Fmt, ChannelCount>::sample_t *src,
uint sample_count) {
memcpy((void *) dest, (const void *) src, sample_count * MultiChannelFmt<Fmt, ChannelCount>::frame_stride);
}
};
// N channel to N channel
template<typename ToFmt, typename FromFmt, uint NumChannels>
struct converting_copy<MultiChannelFmt<ToFmt, NumChannels>, MultiChannelFmt<FromFmt, NumChannels>> {
static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
for (uint i = 0; i < sample_count * NumChannels; i++) {
*dest++ = sample_converter<ToFmt, FromFmt>::convert_sample(*src++);
}
}
};
// mono->stereo conversion
template<typename ToFmt, typename FromFmt>
struct converting_copy<Stereo<ToFmt>, Mono<FromFmt>> {
static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
for (; sample_count; sample_count--) {
typename ToFmt::sample_t mono_sample = sample_converter<ToFmt, FromFmt>::convert_sample(*src++);
*dest++ = mono_sample;
*dest++ = mono_sample;
}
}
};
// stereo->mono conversion
template<typename ToFmt, typename FromFmt>
struct converting_copy<Mono<ToFmt>, Stereo<FromFmt>> {
static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
for (; sample_count; sample_count--) {
// average first in case precision is better in source
typename FromFmt::sample_t averaged_sample = (src[0] + src[1]) / 2;
src += 2;
*dest++ = sample_converter<ToFmt, FromFmt>::convert_sample(averaged_sample);
}
}
};
template<typename ToFmt, typename FromFmt>
audio_buffer_t *consumer_pool_take(audio_connection_t *connection, bool block) {
struct buffer_copying_on_consumer_take_connection *cc = (struct buffer_copying_on_consumer_take_connection *) connection;
// for now we block until we have all the data in consumer buffers
audio_buffer_t *buffer = get_free_audio_buffer(cc->core.consumer_pool, block);
if (!buffer) return NULL;
assert(buffer->format->sample_stride == ToFmt::frame_stride);
uint32_t pos = 0;
while (pos < buffer->max_sample_count) {
if (!cc->current_producer_buffer) {
cc->current_producer_buffer = get_full_audio_buffer(cc->core.producer_pool, block);
if (!cc->current_producer_buffer) {
assert(!block);
if (!pos) {
queue_free_audio_buffer(cc->core.consumer_pool, buffer);
return NULL;
}
break;
}
assert(cc->current_producer_buffer->format->format->channel_count == FromFmt::channel_count);
assert(cc->current_producer_buffer->format->sample_stride == FromFmt::frame_stride);
cc->current_producer_buffer_pos = 0;
}
uint sample_count = std::min(buffer->max_sample_count - pos,
cc->current_producer_buffer->sample_count - cc->current_producer_buffer_pos);
converting_copy<ToFmt, FromFmt>::copy(
((typename ToFmt::sample_t *) buffer->buffer->bytes) + pos * ToFmt::channel_count,
((typename FromFmt::sample_t *) cc->current_producer_buffer->buffer->bytes) +
cc->current_producer_buffer_pos * FromFmt::channel_count,
sample_count);
pos += sample_count;
cc->current_producer_buffer_pos += sample_count;
if (cc->current_producer_buffer_pos == cc->current_producer_buffer->sample_count) {
queue_free_audio_buffer(cc->core.producer_pool, cc->current_producer_buffer);
cc->current_producer_buffer = NULL;
}
}
buffer->sample_count = pos;
return buffer;
}
template<typename ToFmt, typename FromFmt>
void producer_pool_blocking_give(audio_connection_t *connection, audio_buffer_t *buffer) {
struct producer_pool_blocking_give_connection *pbc = (struct producer_pool_blocking_give_connection *) connection;
// for now we block until we have all the data in consumer buffers
uint32_t pos = 0;
while (pos < buffer->sample_count) {
if (!pbc->current_consumer_buffer) {
pbc->current_consumer_buffer = get_free_audio_buffer(pbc->core.consumer_pool, true);
pbc->current_consumer_buffer_pos = 0;
}
uint sample_count = std::min(buffer->sample_count - pos,
pbc->current_consumer_buffer->max_sample_count - pbc->current_consumer_buffer_pos);
assert(buffer->format->sample_stride == FromFmt::frame_stride);
assert(buffer->format->format->channel_count == FromFmt::channel_count);
converting_copy<ToFmt, FromFmt>::copy(
((typename ToFmt::sample_t *) pbc->current_consumer_buffer->buffer->bytes) +
pbc->current_consumer_buffer_pos * ToFmt::channel_count,
((typename FromFmt::sample_t *) buffer->buffer->bytes) + pos * FromFmt::channel_count, sample_count);
pos += sample_count;
pbc->current_consumer_buffer_pos += sample_count;
if (pbc->current_consumer_buffer_pos == pbc->current_consumer_buffer->max_sample_count) {
pbc->current_consumer_buffer->sample_count = pbc->current_consumer_buffer->max_sample_count;
queue_full_audio_buffer(pbc->core.consumer_pool, pbc->current_consumer_buffer);
pbc->current_consumer_buffer = NULL;
}
}
// todo this should be a connection configuration (or a seaparate connection type)
#ifdef BLOCKING_GIVE_SYNCHRONIZE_BUFFERS
if (pbc->current_consumer_buffer) {
pbc->current_consumer_buffer->sample_count = pbc->current_consumer_buffer_pos;
queue_full_audio_buffer(pbc->core.consumer_pool, pbc->current_consumer_buffer);
pbc->current_consumer_buffer = NULL;
}
#endif
assert(pos == buffer->sample_count);
queue_free_audio_buffer(pbc->core.producer_pool, buffer);
}
#endif //SOFTWARE_SAMPLE_CONVERSION_H

Wyświetl plik

@ -0,0 +1,14 @@
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND PICO_DEOPTIMIZED_DEBUG)
message("scanvideo is disabled for 'Debug' builds when PICO_DEOPTIMIZED_DEBUG=1")
else()
add_library(pico_scanvideo INTERFACE)
pico_generate_pio_header(pico_scanvideo ${CMAKE_CURRENT_LIST_DIR}/scanvideo.pio PATH include/pico/scanvideo)
target_sources(pico_scanvideo INTERFACE
${CMAKE_CURRENT_LIST_DIR}/vga_modes.c
)
target_include_directories(pico_scanvideo INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_scanvideo INTERFACE pico_base_headers pico_util_buffer)
endif()

Wyświetl plik

@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SCANVIDEO_COMPOSABLE_SCANLINE_H_
#define SCANVIDEO_COMPOSABLE_SCANLINE_H_
#include "pico/types.h"
#include "scanvideo.pio.h"
// PICO_CONFIG: PICO_SCANVIDEO_USE_RAW1P_2CYCLE, Enable/disable SVideo use raw 1P 2 cycle, type=bool, default=0, group=video+-
#ifndef PICO_SCANVIDEO_USE_RAW1P_2CYCLE
#define PICO_SCANVIDEO_USE_RAW1P_2CYCLE 0
#endif
#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE
#define video_24mhz_composable_prefix video_24mhz_composable_default
#else
#define video_24mhz_composable_prefix video_24mhz_composable_raw1p_2cycle
#endif
// seems needed on some platforms
#define __EXTRA_CONCAT(x, y) __CONCAT(x,y)
#define video_24mhz_composable_program_extern(x) __EXTRA_CONCAT( __EXTRA_CONCAT(video_24mhz_composable_prefix, _offset_), x)
#define __DVP_JMP(x) ((unsigned)video_24mhz_composable_program_extern(x))
#define COMPOSABLE_COLOR_RUN __DVP_JMP(color_run)
#define COMPOSABLE_EOL_ALIGN __DVP_JMP(end_of_scanline_ALIGN)
#define COMPOSABLE_EOL_SKIP_ALIGN __DVP_JMP(end_of_scanline_skip_word_ALIGN)
#define COMPOSABLE_RAW_RUN __DVP_JMP(raw_run)
#define COMPOSABLE_RAW_1P __DVP_JMP(raw_1p)
#define COMPOSABLE_RAW_2P __DVP_JMP(raw_2p)
#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE
#define COMPOSABLE_RAW_1P_SKIP_ALIGN __DVP_JMP(raw_1p_skip_word_ALIGN)
#else
#define COMPOSABLE_RAW_1P_2CYCLE __DVP_JMP(raw_1p_2cycle)
#endif
#endif

Wyświetl plik

@ -0,0 +1,341 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SCANVIDEO_scanvideo_H_
#define SCANVIDEO_scanvideo_H_
#include "pico/types.h"
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file scanvideo_base.h
* \defgroup pico_scanvideo pico_scanvideo
*
* Common Scan-out Video API
*/
// == CONFIG ============
#ifndef PICO_SCANVIDEO_PLANE_COUNT
#define PICO_SCANVIDEO_PLANE_COUNT 1
#endif
#ifndef PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT
#define PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT 8
#endif
#ifndef PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS
#define PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS 0
#endif
#ifndef PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 0
#endif
#ifndef PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA 0
#endif
#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA 1
#endif
#ifndef PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 0
#endif
#ifndef PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA 0
#endif
#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA 1
#endif
#ifndef PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA 0
#endif
#ifndef PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA 0
#endif
#if PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA 1
#endif
// todo these may be DPI only
#ifndef PICO_SCANVIDEO_ENABLE_CLOCK_PIN
#define PICO_SCANVIDEO_ENABLE_CLOCK_PIN 1
#endif
#ifndef PICO_SCANVIDEO_ENABLE_DEN_PIN
#define PICO_SCANVIDEO_ENABLE_DEN_PIN 1
#endif
#ifndef PICO_SCANVIDEO_COLOR_PIN_BASE
#define PICO_SCANVIDEO_COLOR_PIN_BASE 0
#endif
#ifndef PICO_SCANVIDEO_COLOR_PIN_COUNT
#define PICO_SCANVIDEO_COLOR_PIN_COUNT 16
#endif
#ifndef PICO_SCANVIDEO_SYNC_PIN_BASE
#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT)
#endif
#ifndef PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN
#define PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN 0
#endif
// todo make multi plane play nicely with mode swapping;
// today we have hard coded blank/empty lines
//#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 1
//#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 1
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS 180
#endif
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS
#endif
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS
#endif
//extern struct semaphore vmode_updated;
// ======================
#define BPP 16
// most likely 24000000
extern const uint32_t video_clock_freq;
// todo pragma pack?
typedef struct scanvideo_timing {
uint32_t clock_freq;
uint16_t h_active;
uint16_t v_active;
uint16_t h_front_porch;
uint16_t h_pulse;
uint16_t h_total;
uint8_t h_sync_polarity;
uint16_t v_front_porch;
uint16_t v_pulse;
uint16_t v_total;
uint8_t v_sync_polarity;
uint8_t enable_clock;
uint8_t clock_polarity;
uint8_t enable_den;
} scanvideo_timing_t;
typedef struct scanvideo_pio_program scanvideo_pio_program_t;
// todo we need to handle blank data correctly (perhaps DMA should just not start for that scanline,
// though obviously this is slightly more complicated with multiple playfields, or perhaps worse with
// just one
typedef struct scanvideo_mode {
const scanvideo_timing_t *default_timing;
const scanvideo_pio_program_t *pio_program;
uint16_t width;
uint16_t height;
uint8_t xscale; // 1 == normal, 2 == double wide etc. up to what pio timing allows (not sure I have an assert based on delays)
uint16_t yscale; // same for y scale (except any yscale is possible)
// if > 1 then yscale is divided by this to provide the effective yscale;
// note that yscale must be > yscale_denominator; i.e. only stretching is supported
uint16_t yscale_denominator;
} scanvideo_mode_t;
extern bool scanvideo_setup(const scanvideo_mode_t *mode);
extern bool scanvideo_setup_with_timing(const scanvideo_mode_t *mode, const scanvideo_timing_t *timing);
extern void scanvideo_timing_enable(bool enable);
// these take effect after the next vsync
extern void scanvideo_display_enable(bool enable);
// doesn't exist yet!
// extern void video_set_display_mode(const struct scanvideo_mode *mode);
// --- scanline management ---
typedef struct scanvideo_scanline_buffer {
uint32_t scanline_id;
uint32_t *data;
uint16_t data_used;
uint16_t data_max;
#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA
uint16_t fragment_words;
#endif
#if PICO_SCANVIDEO_PLANE_COUNT > 1
uint32_t *data2;
uint16_t data2_used;
uint16_t data2_max;
#if PICO_SCANVIDEO_PLANE_COUNT > 2
uint32_t *data3;
uint16_t data3_used;
uint16_t data3_max;
#endif
#endif
void *user_data;
#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS
struct scanvideo_scanline_buffer *link;
uint8_t link_after;
#endif
uint8_t status;
} scanvideo_scanline_buffer_t;
enum {
SCANLINE_OK = 1,
SCANLINE_ERROR,
SCANLINE_SKIPPED
};
// note frame numbers wrap
static inline uint16_t scanvideo_frame_number(uint32_t scanline_id) {
return (uint16_t) (scanline_id >> 16u);
}
static inline uint16_t scanvideo_scanline_number(uint32_t scanline_id) {
return (uint16_t) scanline_id;
}
/**
* @return the current vga mode (if there is one)
*/
extern scanvideo_mode_t scanvideo_get_mode();
/**
* @return the next scanline_id to be displayed (may be from the next frame)
*/
extern uint32_t scanvideo_get_next_scanline_id();
/**
* @return true if in the vblank interval
*/
extern bool scanvideo_in_vblank();
/**
* @return true if in the hblank interval (or more accurately scanline data is not currently being sent to the PIO, which roughly corresponds, but is not exact). Note also that in
* yscale-d modes, there are height * yscale hblank intervals per frame.
*/
extern bool scanvideo_in_hblank();
extern void scanvideo_wait_for_vblank();
extern uint32_t scanvideo_wait_for_scanline_complete(uint32_t scanline_id);
/**
* Acquire a scanline that needs generating. The scanline_id field indicates which scanline is required.
*
* This method may be called concurrently
*
* @param block true to block if the vga system is not ready to generate a new scanline
* @return the scanline_buffer or NULL if block is false, and the vga system is not ready
*/
scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation(bool block);
scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation2(scanvideo_scanline_buffer_t **second, bool block);
#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS
scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation_linked(uint n, bool block);
#endif
/**
* Return a scanline that has been generated / or at least the client is done with.
*
* The status field indicates whether the scanline was actually generated OK
*
* This method may be called concurrently (for different buffers)
*
* @param scanline_buffer \todo
*/
void scanvideo_end_scanline_generation(scanvideo_scanline_buffer_t *scanline_buffer);
typedef uint (*scanvideo_scanline_repeat_count_fn)(uint32_t scanline_id);
void scanvideo_set_scanline_repeat_fn(scanvideo_scanline_repeat_count_fn fn);
extern const scanvideo_timing_t vga_timing_640x480_60_default;
extern const scanvideo_timing_t vga_timing_wide_480_50;
extern const scanvideo_timing_t vga_timing_648x480_60_alt1;
extern const scanvideo_mode_t vga_mode_160x120_60; // 3d monster maze anyone :-)
extern const scanvideo_mode_t vga_mode_213x160_60;
extern const scanvideo_mode_t vga_mode_320x240_60;
extern const scanvideo_mode_t vga_mode_640x480_60;
extern const scanvideo_mode_t vga_mode_800x600_54;
extern const scanvideo_mode_t vga_mode_800x600_60;
extern const scanvideo_mode_t vga_mode_1024x768_63;
extern const scanvideo_mode_t vga_mode_1280x1024_40;
extern const scanvideo_mode_t vga_mode_720p_60;
extern const scanvideo_mode_t vga_mode_1080p_60;
extern const scanvideo_mode_t vga_mode_tft_800x480_50;
extern const scanvideo_mode_t vga_mode_tft_400x240_50;
#ifndef NDEBUG
// todo this is only for vga composable 24... should exist behind mode impl
extern void validate_scanline(const uint32_t *dma_data, uint dma_data_size, uint max_pixels, uint expected_width);
#endif
// mode implementation
struct scanvideo_pio_program {
#if !PICO_NO_HARDWARE
const pio_program_t *program;
const uint8_t entry_point;
// modifiable_instructions is of size program->length
bool (*adapt_for_mode)(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode,
scanvideo_scanline_buffer_t *missing_scanline_buffer, uint16_t *modifiable_instructions);
pio_sm_config (*configure_pio)(pio_hw_t *pio, uint sm, uint offset);
#else
const char *id;
#endif
};
extern const scanvideo_pio_program_t video_24mhz_composable;
#if !PICO_NO_HARDWARE
extern void scanvideo_default_configure_pio(pio_hw_t *pio, uint sm, uint offset, pio_sm_config *config, bool overlay);
#endif
#ifndef PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK
#define PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK 2
#endif
#ifndef PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK
#define PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK 3
#endif
#ifndef PICO_SPINLOCK_ID_VIDEO_DMA_LOCK
#define PICO_SPINLOCK_ID_VIDEO_DMA_LOCK 4
#endif
#ifndef PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK
#define PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK 5
#endif
#define PICO_SCANVIDEO_ALPHA_MASK (1u << PICO_SCANVIDEO_ALPHA_PIN)
#define PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b) ((((b)>>3u)<<PICO_SCANVIDEO_PIXEL_BSHIFT)|(((g)>>3u)<<PICO_SCANVIDEO_PIXEL_GSHIFT)|(((r)>>3u)<<PICO_SCANVIDEO_PIXEL_RSHIFT))
#define PICO_SCANVIDEO_PIXEL_FROM_RGB5(r, g, b) (((b)<<PICO_SCANVIDEO_PIXEL_BSHIFT)|((g)<<PICO_SCANVIDEO_PIXEL_GSHIFT)|((r)<<PICO_SCANVIDEO_PIXEL_RSHIFT))
#define PICO_SCANVIDEO_R5_FROM_PIXEL(p) (((p)>>PICO_SCANVIDEO_PIXEL_RSHIFT)&0x1f)
#define PICO_SCANVIDEO_G5_FROM_PIXEL(p) (((p)>>PICO_SCANVIDEO_PIXEL_GSHIFT)&0x1f)
#define PICO_SCANVIDEO_B5_FROM_PIXEL(p) (((p)>>PICO_SCANVIDEO_PIXEL_BSHIFT)&0x1f)
#ifdef __cplusplus
}
#endif
#endif //_VIDEO_H

Wyświetl plik

@ -0,0 +1,119 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Default scanline program (|| means aligned word boundary, | means hword boundary)
.program video_24mhz_composable_default
.origin 0 ; must load at zero (offsets are hardcoded in instruction stream)
.define extra0 0 ; set later by code based on xscale
.define extra1 0 ; set later by code (1 more than extra0)
; note bpp must be a factor of 32
.define bpp 16
;.define bpp 8
public end_of_scanline_skip_word_ALIGN: ; || jmp end_of_scanline_skip_word_ALIGN | ignored ||
; was 16 but we just discard the reset of the OSR
; so as to also support 8 bit grayscale
out null, 32;
public end_of_scanline_ALIGN: ; | jmp end_of_scanline_ALIGN ||
public entry_point:
wait irq, 4 ; todo perhaps change this to out exec, 16... so that we can do multiple things (including setting black pixel)
public nop_raw:
out pc, bpp
public delay_h_0:
public color_run: ; | jmp color_run | color | count-3 |
out pins, bpp
out x, bpp
color_loop:
public delay_a_1:
jmp x-- color_loop [extra1]
public nop_extra1:
public delay_b_1:
out pc, bpp [extra1]
public raw_run: ; | jmp raw_run | color | n | <n + 2 colors> |
public delay_c_0:
out pins, bpp [extra0]
out x, bpp
pixel_loop:
public delay_d_0:
out pins, bpp [extra0]
jmp x-- pixel_loop
.wrap_target
public raw_1p: ; | jmp raw_1p | color |
public delay_e_0:
out pins, bpp [extra0]
out pc, bpp
public raw_2p: ; | jmp raw_2p | color | color |
public delay_f_1:
out pins, bpp [extra1]
.wrap
public raw_1p_skip_word_ALIGN: ; || jmp raw_1p_skip_word_ALIGN | color | ignored ||
out pins, 32 ; requires correct out mask
public nop_extra0:
public delay_g_0:
out pc, bpp [extra0] ; note moved extra0 from above line, so we can use this instruction for
; Variant that replaces raw_1p_skip_work_ALIGN with raw1p_2cycle
.program video_24mhz_composable_raw1p_2cycle
.origin 0 ; must load at zero (offsets are hardcoded in instruction stream)
.define extra0 0 ; set later by code based on xscale
.define extra1 0 ; set later by code (1 more than extra0)
; note bpp must be a factor of 32
.define bpp 16
;.define bpp 8
public end_of_scanline_skip_word_ALIGN: ; || jmp end_of_scanline_skip_word_ALIGN | ignored ||
; was 16 but we just discard the reset of the OSR
; so as to also support 8 bit grayscale
out null, 32;
public end_of_scanline_ALIGN: ; | jmp end_of_scanline_ALIGN ||
public entry_point:
wait irq, 4 ; todo perhaps change this to out exec, 16... so that we can do multiple things (including setting black pixel)
public nop_raw:
out pc, bpp
public color_run: ; | jmp color_run | color | count-3 |
public delay_h_0:
out pins, bpp
out x, bpp
color_loop:
public delay_a_1:
jmp x-- color_loop [extra1]
public nop_extra1:
public delay_b_1:
out pc, bpp [extra1]
public raw_run: ; | jmp raw_run | color | n | <n + 2 colors> |
public delay_c_0:
out pins, bpp [extra0]
out x, bpp
pixel_loop:
public delay_d_0:
out pins, bpp [extra0]
jmp x-- pixel_loop
.wrap_target
public raw_1p: ; | jmp raw_1p | color |
public delay_e_0:
out pins, bpp [extra0]
out pc, bpp
public raw_2p: ; | jmp raw_2p | color | color |
public delay_f_1:
out pins, bpp [extra1]
.wrap
public raw_1p_2cycle:
public delay_g_0:
out pins, bpp
out pc, bpp

Wyświetl plik

@ -0,0 +1,528 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/scanvideo.h"
// todo support for inverted-y (probably belongs in the scanline generators, as would inverted x)
extern const scanvideo_pio_program_t video_24mhz_composable;
const scanvideo_timing_t vga_timing_640x480_60_default =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 16,
.h_pulse = 64,
.h_total = 800,
.h_sync_polarity = 1,
.v_front_porch = 1,
.v_pulse = 2,
.v_total = 500,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_800x600_54_default =
{
.clock_freq = 24000000, // wrong, but ok for now
.h_active = 800,
.v_active = 600,
.h_front_porch = 3 * 8,
.h_pulse = 10 * 8,
.h_total = 126 * 8,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 3,
.v_total = 619,
.v_sync_polarity = 0,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_800x600_60_default =
{
.clock_freq = 38400000,
.h_active = 800,
.v_active = 600,
.h_front_porch = 4 * 8,
.h_pulse = 10 * 8,
.h_total = 128 * 8,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 3,
.v_total = 625,
.v_sync_polarity = 0,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_1024x768_63_default =
{
.clock_freq = 24000000, // wrong, but ok for now
.h_active = 1024,
.v_active = 768,
.h_front_porch = 7 * 8,
.h_pulse = 13 * 8,
.h_total = 168 * 8,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 3,
.v_total = 797,
.v_sync_polarity = 0,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_1280x1024_40_default =
{
.clock_freq = 24000000, // wrong, but ok for now
.h_active = 1280,
.v_active = 1024,
.h_front_porch = 7 * 8,
.h_pulse = 16 * 8,
.h_total = 206 * 8,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 3,
.v_total = 1048,
.v_sync_polarity = 0,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_648x480_60_alt1 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 16,
.h_pulse = 48,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 10,
.v_pulse = 2,
.v_total = 523,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_648x480_50ish =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 56,
.h_pulse = 72,
.h_total = 896,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 536,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_648x480_50ish2 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 32,
.h_pulse = 64,
.h_total = 832,
.h_sync_polarity = 1,
.v_front_porch = 27,
.v_pulse = 2,
.v_total = 577,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_648x480_50ish3 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 72,
.h_pulse = 96,
.h_total = 928,
.h_sync_polarity = 1,
.v_front_porch = 8,
.v_pulse = 2,
.v_total = 518,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
#define actual_vga_timing_50 vga_timing_648x480_50ish3
const scanvideo_mode_t vga_mode_160x120_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 160,
.height = 120,
.xscale = 4,
.yscale = 4,
};
const scanvideo_mode_t vga_mode_213x160_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 213,
.height = 160,
.xscale = 3,
.yscale = 3,
};
const scanvideo_mode_t vga_mode_320x240_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 320,
.height = 240,
.xscale = 2,
.yscale = 2,
};
const scanvideo_mode_t vga_mode_640x480_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 640,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_640x480_50 =
{
.default_timing = &actual_vga_timing_50,
.pio_program = &video_24mhz_composable,
.width = 640,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_320x240_50 =
{
.default_timing = &actual_vga_timing_50,
.pio_program = &video_24mhz_composable,
.width = 320,
.height = 240,
.xscale = 2,
.yscale = 2,
};
/* this is 50 hz */
const scanvideo_timing_t vga_timing_wide_480_50 =
{
.clock_freq = 24000000,
.h_active = 800,
.v_active = 480,
.h_front_porch = 32,
.h_pulse = 48,
.h_total = 960,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 2,
.v_total = 500,
.v_sync_polarity = 0,
.enable_clock = 1,
.clock_polarity = 0,
.enable_den = 1
};
const scanvideo_mode_t vga_mode_tft_800x480_50 =
{
.default_timing = &vga_timing_wide_480_50,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_tft_400x240_50 =
{
.default_timing = &vga_timing_wide_480_50,
.pio_program = &video_24mhz_composable,
.width = 400,
.height = 240,
.xscale = 2,
.yscale = 2,
};
const scanvideo_timing_t vga_timing_512x576_50_attempt1 =
{
.clock_freq = 24000000,
.h_active = 512,
.v_active = 576,
.h_front_porch = 64,
.h_pulse = 64,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 612,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_timing_t vga_timing_512x576_60_attempt1 =
{
.clock_freq = 24000000,
.h_active = 512,
.v_active = 576,
.h_front_porch = 64,
.h_pulse = 64,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 612,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_mode_t vga_mode_256x192_50 =
{
.default_timing = &vga_timing_512x576_50_attempt1,
.pio_program = &video_24mhz_composable,
.width = 256,
.height = 192,
.xscale = 2,
.yscale = 3,
};
const scanvideo_timing_t vga_timing_800x600_38 =
{
.clock_freq = 24000000,
.h_active = 800,
.v_active = 600,
.h_front_porch = 24,
.h_pulse = 80,
.h_total = 1008,
.h_sync_polarity = 1,
.v_front_porch = 3,
.v_pulse = 4,
.v_total = 621,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_mode_t vga_mode_800x600_38 =
{
.default_timing = &vga_timing_800x600_38,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 600,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_800x600_54 =
{
.default_timing = &vga_timing_800x600_54_default,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 600,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_800x600_60 =
{
.default_timing = &vga_timing_800x600_60_default,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 600,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_1024x768_63 =
{
.default_timing = &vga_timing_1024x768_63_default,
.pio_program = &video_24mhz_composable,
.width = 1024,
.height = 768,
.xscale = 1,
.yscale = 1,
};
const scanvideo_mode_t vga_mode_1280x1024_40 =
{
.default_timing = &vga_timing_1280x1024_40_default,
.pio_program = &video_24mhz_composable,
.width = 1280,
.height = 1024,
.xscale = 1,
.yscale = 1,
};
const scanvideo_timing_t vga_timing_1280x720_60_default =
{
.clock_freq = 74250000,
.h_active = 1280,
.v_active = 720,
.h_front_porch = 110,
.h_pulse = 40,
.h_total = 1650,
.h_sync_polarity = 1,
.v_front_porch = 5,
.v_pulse = 5,
.v_total = 750,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_mode_t vga_mode_720p_60 =
{
.default_timing = &vga_timing_1280x720_60_default,
.pio_program = &video_24mhz_composable,
.width = 1280,
.height = 720,
.xscale = 1,
.yscale = 1,
};
const scanvideo_timing_t vga_timing_1920x1080_60_default =
{
.clock_freq = 148500000,
.h_active = 1920,
.v_active = 1080,
.h_front_porch = 88,
.h_pulse = 44,
.h_total = 2200,
.h_sync_polarity = 1,
.v_front_porch = 4,
.v_pulse = 5,
.v_total = 1125,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const scanvideo_mode_t vga_mode_1080p_60 =
{
.default_timing = &vga_timing_1920x1080_60_default,
.pio_program = &video_24mhz_composable,
.width = 1920,
.height = 1080,
.xscale = 1,
.yscale = 1,
};

Wyświetl plik

@ -0,0 +1,4 @@
if (NOT TARGET pico_sd_card_headers)
add_library(pico_sd_card_headers INTERFACE)
target_include_directories(pico_sd_card_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
endif()

Wyświetl plik

@ -0,0 +1,54 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_SD_CARD_H
#define _PICO_SD_CARD_H
#ifdef __cplusplus
extern "C" {
#endif
#include "pico.h"
#define SD_OK (0)
#define SD_ERR_STUCK (-1)
#define SD_ERR_BAD_RESPONSE (-2)
#define SD_ERR_CRC (-3)
#define SD_ERR_BAD_PARAM (-4)
#ifndef PICO_SD_CLK_PIN
#define PICO_SD_CLK_PIN 23
#endif
#ifndef PICO_SD_CMD_PIN
#define PICO_SD_CMD_PIN 24
#endif
#ifndef PICO_SD_DAT0_PIN
#define PICO_SD_DAT0_PIN 19
#endif
// todo for now
#define PICO_SD_MAX_BLOCK_COUNT 32
// todo buffer pool
int sd_init_4pins();
int sd_init_1pin();
#define SD_SECTOR_SIZE 512
int sd_readblocks_sync(uint32_t *buf, uint32_t block, uint block_count);
int sd_readblocks_async(uint32_t *buf, uint32_t block, uint block_count);
int sd_readblocks_scatter_async(uint32_t *control_words, uint32_t block, uint block_count);
bool sd_scatter_read_complete(int *status);
int sd_writeblocks_async(const uint32_t *data, uint32_t sector_num, uint sector_count);
bool sd_write_complete(int *status);
int sd_read_sectors_1bit_crc_async(uint32_t *sector_buf, uint32_t sector, uint sector_count);
int sd_set_wide_bus(bool wide);
int sd_set_clock_divider(uint div);
#endif
#ifdef __cplusplus
}
#endif

Wyświetl plik

@ -0,0 +1,7 @@
add_library(pico_util_buffer INTERFACE)
target_include_directories(pico_util_buffer INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_sources(pico_util_buffer INTERFACE
${CMAKE_CURRENT_LIST_DIR}/buffer.c
)

Wyświetl plik

@ -0,0 +1,17 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/util/buffer.h"
#ifdef PICO_BUFFER_USB_ALLOC_HACK
#include <string.h>
uint8_t *usb_ram_alloc_ptr = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX);
static void __attribute__((constructor)) _clear_usb_ram() {
memset(usb_ram_alloc_ptr, 0, USB_DPRAM_SIZE - USB_DPRAM_MAX);
}
#endif

Wyświetl plik

@ -0,0 +1,99 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_UTIL_BUFFER_H
#define _PICO_UTIL_BUFFER_H
#include "pico/types.h"
/** \file buffer.h
* \defgroup util_buffer buffer
* \brief Buffer management
* \ingroup pico_util
*/
#ifdef PICO_BUFFER_USB_ALLOC_HACK
#include "hardware/address_mapped.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef DEBUG_MALLOC
#include <stdio.h>
#endif
#include <stdlib.h>
/** \struct mem_buffer
* \ingroup util_buffer
* \brief Wrapper structure around a memory buffer
*
* Wrapper could be around static or allocated memory
*
* \todo This module needs to be checked - think there are issues with the free function
*/
typedef struct mem_buffer {
size_t size;
uint8_t *bytes;
uint8_t flags;
} mem_buffer_t;
#ifdef PICO_BUFFER_USB_ALLOC_HACK
#if !defined(USB_DPRAM_MAX) || (USB_DPRAM_MAX > 0)
#include "hardware/structs/usb.h"
#else
#define USB_DPRAM_SIZE 4096
#endif
#endif
inline static bool pico_buffer_alloc_in_place(mem_buffer_t *buffer, size_t size) {
#ifdef PICO_BUFFER_USB_ALLOC_HACK
extern uint8_t *usb_ram_alloc_ptr;
if ((usb_ram_alloc_ptr + size) <= (uint8_t *)USBCTRL_DPRAM_BASE + USB_DPRAM_SIZE) {
buffer->bytes = usb_ram_alloc_ptr;
buffer->size = size;
#ifdef DEBUG_MALLOC
printf("balloc %d %p->%p\n", size, buffer->bytes, ((uint8_t *)buffer->bytes) + size);
#endif
usb_ram_alloc_ptr += size;
return true;
}
#endif // inline for now
buffer->bytes = (uint8_t *) calloc(1, size);
if (buffer->bytes) {
buffer->size = size;
return true;
}
buffer->size = 0;
return false;
}
inline static mem_buffer_t *pico_buffer_wrap(uint8_t *bytes, size_t size) {
mem_buffer_t *buffer = (mem_buffer_t *) malloc(sizeof(mem_buffer_t));
if (buffer) {
buffer->bytes = bytes;
buffer->size = size;
}
return buffer;
}
inline static mem_buffer_t *pico_buffer_alloc(size_t size) {
mem_buffer_t *b = (mem_buffer_t *) malloc(sizeof(mem_buffer_t));
if (b) {
if (!pico_buffer_alloc_in_place(b, size)) {
free(b);
b = NULL;
}
}
return b;
}
#ifdef __cplusplus
}
#endif
#endif

Wyświetl plik

@ -0,0 +1,12 @@
add_library(platypus INTERFACE)
target_sources(platypus INTERFACE
${CMAKE_CURRENT_LIST_DIR}/platypus.c
$<$<BOOL:${PICO_ON_DEVICE}>:${CMAKE_CURRENT_LIST_DIR}/decompress_row.S>
)
target_include_directories(platypus INTERFACE ${CMAKE_CURRENT_LIST_DIR})
if (PICO_ON_DEVICE)
target_link_libraries(platypus INTERFACE hardware_interp)
endif()

Wyświetl plik

@ -0,0 +1,377 @@
#include "hardware/regs/addressmap.h"
#include "hardware/regs/sio.h"
.syntax unified
.cpu cortex-m0plus
.thumb
#ifndef VIDEO_DBI
#define FIRST_TEST_SHIFT 16
#define SECOND_TEST_SHIFT 8
#else
#define FIRST_TEST_SHIFT 6
#define SECOND_TEST_SHIFT 11
#endif
#define r_output r0
#define r_interps r1
#define r_input r2
#define r_top r3
#define r_bottom r4
#define r_tmp3 r5
#define r_tmp2 r6
#define r_tmp1 r7
#define r_output_end r8
#define r_save r12
#define r_row_delta r14
#define INTERP_OFFSET0(x) (x - SIO_INTERP0_ACCUM0_OFFSET)
#define INTERP_OFFSET1(x) (INTERP_OFFSET0(x) + SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET)
// input r_top -- -- AB CD
//
// output r_top AB CD AB CD
// r_bottom AB CD AB CD
.macro duplicate_4_pixels tmp_a
lsls \tmp_a, r_top, #16
uxth r_top, r_top
orrs r_top, \tmp_a
mov r_bottom, r_top
.endm
.macro do_222
#ifndef VIDEO_DBI
lsls r_tmp1, #3
#else
// done in interp
// lsrs r_tmp1, #9
#endif
str r_tmp1, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_ACCUM1_OFFSET)]
ldr r_tmp3, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE1_OFFSET)]
ldmia r_tmp3!, {r_tmp2, r_tmp1}
add r_top, r_tmp2
add r_bottom, r_tmp1
.endm
.macro do_555
#ifndef VIDEO_DBI
rev16 r_tmp1, r_tmp1
lsls r_tmp1, #3
#else
// done in interp
// lsrs r_tmp1, #8
#endif
str r_tmp1, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)]
str r_tmp1, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)]
ldr r_tmp3, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)]
ldmia r_tmp3!, {r_tmp2, r_tmp1}
add r_top, r_tmp2
add r_bottom, r_tmp1
ldr r_tmp3, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)]
ldmia r_tmp3!, {r_tmp2, r_tmp1}
#ifndef VIDEO_DBI
lsls r_tmp2, #5
lsls r_tmp1, #5
#else
lsls r_tmp2, #6
lsls r_tmp1, #6
#endif
add r_top, r_tmp2
add r_bottom, r_tmp1
ldr r_tmp3, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE1_OFFSET)]
ldmia r_tmp3!, {r_tmp2, r_tmp1}
#ifndef VIDEO_DBI
lsls r_tmp2, #10
lsls r_tmp1, #10
#else
lsls r_tmp2, #11
lsls r_tmp1, #11
#endif
add r_top, r_tmp2
add r_bottom, r_tmp1
.endm
.macro shuffle_7_bytes_to_8 tmp_a tmp_b
#ifndef VIDEO_DBI
ldr \tmp_b, =#0xff018401
lsls \tmp_a, \tmp_b, #16
ands \tmp_b, r_bottom
ands \tmp_a, r_top
eors r_bottom, \tmp_b
eors r_top, \tmp_a
lsrs \tmp_a, \tmp_a, #15
orrs \tmp_b, \tmp_a
lsrs \tmp_a, \tmp_b, #10
lsls \tmp_b, #27
lsls \tmp_a, #24
orrs \tmp_b, \tmp_a
orrs r_bottom, \tmp_b
#else
ldr \tmp_b, =#0xff210821
lsls \tmp_a, \tmp_b, #16
ands \tmp_b, r_bottom
ands \tmp_a, r_top
eors r_bottom, \tmp_b
eors r_top, \tmp_a
// todo can we shave 1 more cycles to make cycles in the DBI code? a challenge to anyone who reads this!
lsrs \tmp_a, #13
orrs \tmp_b, \tmp_a
lsrs \tmp_a, \tmp_b, #10
eors \tmp_b, \tmp_a
lsrs \tmp_a, \tmp_b, #12
adcs \tmp_b, \tmp_b
lsls \tmp_b, #24
orrs r_bottom, \tmp_b
#endif
.endm
.macro write_output tmp_a
mov \tmp_a, r_row_delta
str r_bottom, [r_output, \tmp_a]
stmia r_output!, {r_top}
.endm
.macro decompressor name data_section_prefix code_section_prefix
.section \data_section_prefix\().\name\().data
.global \name\()_5_table
\name\()_5_table:
.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001
.word 0x00010001, 0x00000001, 0x00010000, 0x00000000, 0x00000000, 0x00010001, 0x00010000, 0x00010002
.word 0x00000000, 0x00010002, 0x00020000, 0x00010001, 0x00010000, 0x00020002, 0x00010000, 0x00010000
.word 0x00000001, 0x00000001, 0x00010001, 0x00000002, 0x00010001, 0x00000000, 0x00020001, 0x00000001
.word 0x00020000, 0x00020001, 0x00000001, 0x00000002, 0x00020002, 0x00000001, 0x00020001, 0x00000000
.word 0x00000000, 0x00000002, 0x00000001, 0x00010002, 0x00010000, 0x00020001, 0x00020000, 0x00010000
.word 0x00020000, 0x00020002, 0x00000000, 0x00020002, 0x00010002, 0x00000002, 0x00010000, 0x00020003
.word 0x00000000, 0x00020003, 0x00010000, 0x00030003, 0x00010000, 0x00000002, 0x00000001, 0x00010001
.global \name\()_222_table
\name\()_222_table:
#ifndef VIDEO_DBI
.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001
.word 0x00000000, 0x00000020, 0x00000000, 0x00000021, 0x00010000, 0x00010021, 0x00010000, 0x00000021
.word 0x00200000, 0x00200020, 0x00200000, 0x00200021, 0x00210000, 0x00210021, 0x00210000, 0x00200021
.word 0x00200000, 0x00000020, 0x00200000, 0x00000021, 0x00210000, 0x00010021, 0x00210000, 0x00000021
.word 0x00000000, 0x00000400, 0x00000000, 0x00000401, 0x00010000, 0x00010401, 0x00010000, 0x00000401
.word 0x00000000, 0x00000420, 0x00000000, 0x00000421, 0x00010000, 0x00010421, 0x00010000, 0x00000421
.word 0x00200000, 0x00200420, 0x00200000, 0x00200421, 0x00210000, 0x00210421, 0x00210000, 0x00200421
.word 0x00200000, 0x00000420, 0x00200000, 0x00000421, 0x00210000, 0x00010421, 0x00210000, 0x00000421
.word 0x04000000, 0x04000400, 0x04000000, 0x04000401, 0x04010000, 0x04010401, 0x04010000, 0x04000401
.word 0x04000000, 0x04000420, 0x04000000, 0x04000421, 0x04010000, 0x04010421, 0x04010000, 0x04000421
.word 0x04200000, 0x04200420, 0x04200000, 0x04200421, 0x04210000, 0x04210421, 0x04210000, 0x04200421
.word 0x04200000, 0x04000420, 0x04200000, 0x04000421, 0x04210000, 0x04010421, 0x04210000, 0x04000421
.word 0x04000000, 0x00000400, 0x04000000, 0x00000401, 0x04010000, 0x00010401, 0x04010000, 0x00000401
.word 0x04000000, 0x00000420, 0x04000000, 0x00000421, 0x04010000, 0x00010421, 0x04010000, 0x00000421
.word 0x04200000, 0x00200420, 0x04200000, 0x00200421, 0x04210000, 0x00210421, 0x04210000, 0x00200421
.word 0x04200000, 0x00000420, 0x04200000, 0x00000421, 0x04210000, 0x00010421, 0x04210000, 0x00000421
#else
.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001
.word 0x00000000, 0x00000040, 0x00000000, 0x00000041, 0x00010000, 0x00010041, 0x00010000, 0x00000041
.word 0x00400000, 0x00400040, 0x00400000, 0x00400041, 0x00410000, 0x00410041, 0x00410000, 0x00400041
.word 0x00400000, 0x00000040, 0x00400000, 0x00000041, 0x00410000, 0x00010041, 0x00410000, 0x00000041
.word 0x00000000, 0x00000800, 0x00000000, 0x00000801, 0x00010000, 0x00010801, 0x00010000, 0x00000801
.word 0x00000000, 0x00000840, 0x00000000, 0x00000841, 0x00010000, 0x00010841, 0x00010000, 0x00000841
.word 0x00400000, 0x00400840, 0x00400000, 0x00400841, 0x00410000, 0x00410841, 0x00410000, 0x00400841
.word 0x00400000, 0x00000840, 0x00400000, 0x00000841, 0x00410000, 0x00010841, 0x00410000, 0x00000841
.word 0x08000000, 0x08000800, 0x08000000, 0x08000801, 0x08010000, 0x08010801, 0x08010000, 0x08000801
.word 0x08000000, 0x08000840, 0x08000000, 0x08000841, 0x08010000, 0x08010841, 0x08010000, 0x08000841
.word 0x08400000, 0x08400840, 0x08400000, 0x08400841, 0x08410000, 0x08410841, 0x08410000, 0x08400841
.word 0x08400000, 0x08000840, 0x08400000, 0x08000841, 0x08410000, 0x08010841, 0x08410000, 0x08000841
.word 0x08000000, 0x00000800, 0x08000000, 0x00000801, 0x08010000, 0x00010801, 0x08010000, 0x00000801
.word 0x08000000, 0x00000840, 0x08000000, 0x00000841, 0x08010000, 0x00010841, 0x08010000, 0x00000841
.word 0x08400000, 0x00400840, 0x08400000, 0x00400841, 0x08410000, 0x00410841, 0x08410000, 0x00400841
.word 0x08400000, 0x00000840, 0x08400000, 0x00000841, 0x08410000, 0x00010841, 0x08410000, 0x00000841
#endif
.section \code_section_prefix\().\name\().code, "ax"
.global \name
.type \name,%function
.thumb_func
//const uint8_t* \name(uint32_t *d0, uint32_t *d1, const uint8_t *s, uint32_t w);
\name:
push {r4, r5, r6, r7, lr}
mov r4, r8
push {r4}
lsls r3, #1
adds r3, r0
// done (near)
bcs 0f
// todo assert r_output is r0
subs r1, r_output
mov r_row_delta, r1
// todo assert r_interps is r1
ldr r_interps, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET
// todo assert r_source is r2
mov r_output_end, r3
\name\()_rem_0: // r_top has 0 valid bytes
ldmia r_input!, {r_top}
\name\()_rem_4: // r_top has 4 valid bytes LSB first
lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT
bcs 2f
mov r_save, r_top // todo we could skip this (in case we hit 4 byte path anyway) if we have spare reg, or just move duplicate_4_pixels into both branches
duplicate_4_pixels r_tmp2
lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT
bcc 1f
do_555
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_0
0:
b \name\()_done
1:
do_222
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_1
b \name\()_done
2:
//bkpt #0
ldmia r_input!, {r_bottom}
mov r_save, r_bottom
shuffle_7_bytes_to_8 r_tmp2, r_tmp3
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
bge \name\()_done
// fall thru
\name\()_rem_1: // r_remaining_bits has 1 byte remaining (in the MSB)
ldmia r_input!, {r_tmp2}
lsrs r_top, #24
lsls r_tmp1, r_tmp2, #8
orrs r_top, r_tmp1
lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT
bcs 2f
mov r_save, r_tmp2
duplicate_4_pixels r_tmp2
lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT
bcc 1f
do_555
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_1
b \name\()_done
1:
do_222
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_2
b \name\()_done
2:
//bkpt #0
// r_top has 4, r_save has 1
ldmia r_input!, {r_tmp1}
lsrs r_bottom, r_tmp2, #24
lsls r_tmp2, r_tmp1, #8
orrs r_bottom, r_tmp2
shuffle_7_bytes_to_8 r_tmp2, r_tmp3
write_output r_tmp2
mov r_top, r_tmp1
cmp r_output, r_output_end
blt \name\()_rem_2
\name\()_done:
pop {r3, r4, r5, r6, r7}
mov r8, r3
pop {pc}
\name\()_rem_2: // r_remaining_bits has 2 bytes remaining (in the MSB)
ldmia r_input!, {r_tmp2}
lsrs r_top, #16
lsls r_tmp1, r_tmp2, #16
orrs r_top, r_tmp1
lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT
bcs 2f
mov r_save, r_tmp2
duplicate_4_pixels r_tmp2
lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT
bcc 1f
do_555
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_2
b \name\()_done
1:
do_222
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_3
b \name\()_done
2:
//bkpt #0
ldmia r_input!, {r_tmp1}
lsrs r_bottom, r_tmp2, #16
lsls r_tmp2, r_tmp1, #16
orrs r_bottom, r_tmp2
shuffle_7_bytes_to_8 r_tmp2, r_tmp3
write_output r_tmp2
mov r_top, r_tmp1
cmp r_output, r_output_end
bge \name\()_done
// fall thru
\name\()_rem_3: // r_remaining_bits has 3 bytes remaining (in the MSB)
ldmia r_input!, {r_tmp2}
lsrs r_top, #8
lsls r_tmp1, r_tmp2, #24
orrs r_top, r_tmp1
lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT
bcs 2f
mov r_save, r_tmp2
duplicate_4_pixels r_tmp2
lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT
bcc 1f
do_555
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
blt \name\()_rem_3
b \name\()_done
1:
do_222
write_output r_tmp2
mov r_top, r_save
cmp r_output, r_output_end
bge \name\()_done2
b \name\()_rem_4
2:
//bkpt #0
ldmia r_input!, {r_tmp1}
lsrs r_bottom, r_tmp2, #8
lsls r_tmp2, r_tmp1, #24
orrs r_bottom, r_tmp2
shuffle_7_bytes_to_8 r_tmp2, r_tmp3
write_output r_tmp2
mov r_top, r_tmp1
cmp r_output, r_output_end
bge \name\()_done2
b \name\()_rem_4
\name\()_done2:
pop {r3, r4, r5, r6, r7}
mov r8, r3
mov r0, r_input
pop {pc}
.endm
// put one decompressor in each scratch bank for use by each core
decompressor platypus_decompress_row_asm_a .scratch_x, .scratch_x
decompressor platypus_decompress_row_asm_b .scratch_y, .scratch_y

Wyświetl plik

@ -0,0 +1,229 @@
#include <stdio.h>
#include "pico.h"
#include "platypus.h"
#include "pico/scanvideo.h"
#if PICO_ON_DEVICE
#include "hardware/interp.h"
#endif
#ifdef PLATYPUS_565
#define PIXEL_RSHIFT 0
#define PIXEL_GSHIFT 6
#define PIXEL_BSHIFT 11
#else
#define PIXEL_RSHIFT 0
#define PIXEL_GSHIFT 5
#define PIXEL_BSHIFT 10
#endif
#if PICO_NO_HARDWARE
const static uint32_t row_5_table[] = {
0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001,
0x00010001, 0x00000001, 0x00010000, 0x00000000, 0x00000000, 0x00010001, 0x00010000, 0x00010002,
0x00000000, 0x00010002, 0x00020000, 0x00010001, 0x00010000, 0x00020002, 0x00010000, 0x00010000,
0x00000001, 0x00000001, 0x00010001, 0x00000002, 0x00010001, 0x00000000, 0x00020001, 0x00000001,
0x00020000, 0x00020001, 0x00000001, 0x00000002, 0x00020002, 0x00000001, 0x00020001, 0x00000000,
0x00000000, 0x00000002, 0x00000001, 0x00010002, 0x00010000, 0x00020001, 0x00020000, 0x00010000,
0x00020000, 0x00020002, 0x00000000, 0x00020002, 0x00010002, 0x00000002, 0x00010000, 0x00020003,
0x00000000, 0x00020003, 0x00010000, 0x00030003, 0x00010000, 0x00000002, 0x00000001, 0x00010001,
};
#ifndef PLATYPUS_565
const static uint32_t row_222_table[] = {
0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001,
0x00000000, 0x00000020, 0x00000000, 0x00000021, 0x00010000, 0x00010021, 0x00010000, 0x00000021,
0x00200000, 0x00200020, 0x00200000, 0x00200021, 0x00210000, 0x00210021, 0x00210000, 0x00200021,
0x00200000, 0x00000020, 0x00200000, 0x00000021, 0x00210000, 0x00010021, 0x00210000, 0x00000021,
0x00000000, 0x00000400, 0x00000000, 0x00000401, 0x00010000, 0x00010401, 0x00010000, 0x00000401,
0x00000000, 0x00000420, 0x00000000, 0x00000421, 0x00010000, 0x00010421, 0x00010000, 0x00000421,
0x00200000, 0x00200420, 0x00200000, 0x00200421, 0x00210000, 0x00210421, 0x00210000, 0x00200421,
0x00200000, 0x00000420, 0x00200000, 0x00000421, 0x00210000, 0x00010421, 0x00210000, 0x00000421,
0x04000000, 0x04000400, 0x04000000, 0x04000401, 0x04010000, 0x04010401, 0x04010000, 0x04000401,
0x04000000, 0x04000420, 0x04000000, 0x04000421, 0x04010000, 0x04010421, 0x04010000, 0x04000421,
0x04200000, 0x04200420, 0x04200000, 0x04200421, 0x04210000, 0x04210421, 0x04210000, 0x04200421,
0x04200000, 0x04000420, 0x04200000, 0x04000421, 0x04210000, 0x04010421, 0x04210000, 0x04000421,
0x04000000, 0x00000400, 0x04000000, 0x00000401, 0x04010000, 0x00010401, 0x04010000, 0x00000401,
0x04000000, 0x00000420, 0x04000000, 0x00000421, 0x04010000, 0x00010421, 0x04010000, 0x00000421,
0x04200000, 0x00200420, 0x04200000, 0x00200421, 0x04210000, 0x00210421, 0x04210000, 0x00200421,
0x04200000, 0x00000420, 0x04200000, 0x00000421, 0x04210000, 0x00010421, 0x04210000, 0x00000421,
};
#else
const static uint32_t row_222_table[] = {
0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001,
0x00000000, 0x00000040, 0x00000000, 0x00000041, 0x00010000, 0x00010041, 0x00010000, 0x00000041,
0x00400000, 0x00400040, 0x00400000, 0x00400041, 0x00410000, 0x00410041, 0x00410000, 0x00400041,
0x00400000, 0x00000040, 0x00400000, 0x00000041, 0x00410000, 0x00010041, 0x00410000, 0x00000041,
0x00000000, 0x00000800, 0x00000000, 0x00000801, 0x00010000, 0x00010801, 0x00010000, 0x00000801,
0x00000000, 0x00000840, 0x00000000, 0x00000841, 0x00010000, 0x00010841, 0x00010000, 0x00000841,
0x00400000, 0x00400840, 0x00400000, 0x00400841, 0x00410000, 0x00410841, 0x00410000, 0x00400841,
0x00400000, 0x00000840, 0x00400000, 0x00000841, 0x00410000, 0x00010841, 0x00410000, 0x00000841,
0x08000000, 0x08000800, 0x08000000, 0x08000801, 0x08010000, 0x08010801, 0x08010000, 0x08000801,
0x08000000, 0x08000840, 0x08000000, 0x08000841, 0x08010000, 0x08010841, 0x08010000, 0x08000841,
0x08400000, 0x08400840, 0x08400000, 0x08400841, 0x08410000, 0x08410841, 0x08410000, 0x08400841,
0x08400000, 0x08000840, 0x08400000, 0x08000841, 0x08410000, 0x08010841, 0x08410000, 0x08000841,
0x08000000, 0x00000800, 0x08000000, 0x00000801, 0x08010000, 0x00010801, 0x08010000, 0x00000801,
0x08000000, 0x00000840, 0x08000000, 0x00000841, 0x08010000, 0x00010841, 0x08010000, 0x00000841,
0x08400000, 0x00400840, 0x08400000, 0x00400841, 0x08410000, 0x00410841, 0x08410000, 0x00400841,
0x08400000, 0x00000840, 0x08400000, 0x00000841, 0x08410000, 0x00010841, 0x08410000, 0x00000841,
};
#endif
const uint32_t* platypus_decompress_row(uint32_t *d0, uint32_t *d1, const uint32_t *s4, uint32_t w) {
const uint8_t *s = (const uint8_t *)s4;
for(uint x = 0; x < w; x += 2)
{
uint32_t row0, row1;
#ifndef PLATYPUS_565
if (s[1] & 0x80) {
#else
if (s[0] & 0x20) {
#endif
uint c0 = s[0] | (s[1] << 8);
uint c1 = s[2] | (s[3] << 8);
#ifndef PLATYPUS_565
if (s[3] & 0x80) {
#else
if (s[2] & 0x20) {
#endif
// todo this is unused right now
row0 = (c1 << 16) | c0;
row1 = (s[7] << 24) | (s[6] << 16) | (s[5] << 8) | s[4];
s+=8;
} else {
row0 = (c1 << 16) | c0;
row1 = (s[6] << 16) | (s[5] << 8) | s[4];
#ifndef PLATYPUS_565
uint32_t mask = 0xff018401; // note ff at top saves us a mask of 0x00ffffff first for row1
uint32_t hi_bits = row1 & mask;
row1 ^= hi_bits;
uint32_t lo_bits = row0 & (mask << 16u);
row0 ^= lo_bits;
hi_bits |= lo_bits >> 15u;
row1 |= (hi_bits >> 10u) << 24u;
row1 |= hi_bits << 27u;
#else
uint32_t mask = 0xff210821;
uint32_t hi_bits = row1 & mask;
row1 ^= hi_bits;
uint32_t lo_bits = row0 & (mask << 16u);
row0 ^= lo_bits;
lo_bits >>= 13u;
hi_bits |= lo_bits;
hi_bits ^= (hi_bits >> 10u);
hi_bits = (hi_bits * 2) + ((hi_bits >> 11u) & 1u);
row1 |= hi_bits << 24u;
#endif
s+=7;
// row0=row1=0;
}
} else {
row0 = s[0] | (s[1] << 8);
row1 = row0 = row0 | (row0 << 16);
uint v = s[2];
#ifndef PLATYPUS_565
if (v & 0x80u) {
v = (v << 8u) | s[3];
#else
if (v & 0x01u) {
v = (v >> 1u) | (s[3] << 7u);
#endif
// do Rs
#if PICO_ON_DEVICE
v <<= 11u;
interp1->accum[0] = v;
uint32_t *p = (uint32_t *)(interp1->peek[0]);
row0 += p[0];
row1 += p[1];
#else
// *2 for interleave
v <<= 1u;
row0 += row_5_table[v&0x3e] << PIXEL_RSHIFT;
row1 += row_5_table[(v&0x3e)+1] << PIXEL_RSHIFT;
#endif
// do Gs and Bs
#if PICO_ON_DEVICE
interp0->accum[0] = v;
uint32_t *p0 = (uint32_t *)(interp0->peek[0]);
uint32_t *p1 = (uint32_t *)(interp0->peek[1]);
row0 += p0[0] << PIXEL_GSHIFT;
row1 += p0[1] << PIXEL_GSHIFT;
row0 += p1[0] << PIXEL_BSHIFT;
row1 += p1[1] << PIXEL_BSHIFT;
#else
row0 += row_5_table[(v>>5u)&0x3eu] << PIXEL_GSHIFT;
row1 += row_5_table[1 + ((v>>5u)&0x3eu)] << PIXEL_GSHIFT;
row0 += row_5_table[(v>>10u)&0x3eu] << PIXEL_BSHIFT;
row1 += row_5_table[1 + ((v>>10u)&0x3eu)] << PIXEL_BSHIFT;
#endif
s += 4;
} else {
#ifdef PLATYPUS_565
v >>= 2;
#endif
#if PICO_ON_DEVICE
interp1->accum[1] = v << 12u;
uint32_t *p = (uint32_t *)(interp1->peek[1]);
row0 += p[0];
row1 += p[1];
#else
row0 += row_222_table[v*2];
row1 += row_222_table[v*2 + 1];
#endif
s += 3;
}
}
*d0++ = row0;
*d1++ = row1;
}
s += (4u - ((uintptr_t)s) & 3u) & 3u;
// round up to word boundary
return (uint32_t *)s;
}
#else
void platypus_decompress_configure_interp(bool is_b) {
extern uint32_t platypus_decompress_row_asm_a_222_table, platypus_decompress_row_asm_a_5_table;
extern uint32_t platypus_decompress_row_asm_b_222_table, platypus_decompress_row_asm_b_5_table;
uint32_t row_5 = (uintptr_t)(is_b?&platypus_decompress_row_asm_b_5_table:&platypus_decompress_row_asm_a_5_table);
interp0->base[0] = row_5;
interp0->base[1] = row_5;
interp1->base[0] = row_5;
interp1->base[1] = (uintptr_t)(is_b?&platypus_decompress_row_asm_b_222_table:&platypus_decompress_row_asm_a_222_table);
#ifndef VIDEO_DBI
const uint es_555 = 0;
const uint es_222 = 0;
#else
const uint es_555 = 8;
const uint es_222 = 9;
#endif
interp_config c = interp_default_config();
interp_config_set_shift(&c, 5 + es_555);
interp_config_set_mask(&c, 3, 7);
interp_set_config(interp0, 0, &c);
interp_config_set_shift(&c, es_555);
interp_set_config(interp1, 0, &c);
interp_config_set_mask(&c, 3, 8);
interp_config_set_shift(&c, es_222);
interp_set_config(interp1, 1, &c);
interp_config_set_mask(&c, 3, 7);
interp_config_set_shift(&c, 10 + es_555);
interp_config_set_cross_input(&c, true);
interp_set_config(interp0, 1, &c);
// interp_configure_none(interp0, 0, 5 + es_555, 3, 7);
// interp_configure_with_cross_input(interp0, 1, 10 + es_555, 3, 7);
// interp_configure_none(interp1, 0, es_555, 3, 7);
// interp_configure_none(interp1, 1, es_222, 3, 8);
// interp_add_force_bits(interp0, 0, 2);
// interp_add_force_bits(interp0, 1, 2);
// interp_add_force_bits(interp1, 0, 2);
// interp_add_force_bits(interp1, 1, 2);
}
#endif

Wyświetl plik

@ -0,0 +1,24 @@
#ifndef _PLATYPUS_H
#define _PLATYPUS_H
#include <stdint.h>
#ifdef VIDEO_DBI
#undef PLATYPUS_565
#define PLATYPUS_565
#endif
#if PICO_ON_DEVICE
extern const uint32_t* platypus_decompress_row_asm_a(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w);
extern const uint32_t* platypus_decompress_row_asm_b(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w);
extern void platypus_decompress_configure_interp(bool is_b);
#define platypus_decompress_row_a platypus_decompress_row_asm_a
#define platypus_decompress_row_b platypus_decompress_row_asm_b
#else
extern const uint32_t* platypus_decompress_row(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w);
#define platypus_decompress_row_a platypus_decompress_row
#define platypus_decompress_row_b platypus_decompress_row
#endif
#endif

Wyświetl plik

@ -0,0 +1,14 @@
# This CMakeLists.txt intended to be included from other projectgs
pico_add_subdirectory(hardware_rosc)
pico_add_subdirectory(lwip)
pico_add_subdirectory(pico_sleep)
pico_add_subdirectory(pico_audio_i2s)
pico_add_subdirectory(pico_audio_pwm)
pico_add_subdirectory(pico_audio_spdif)
pico_add_subdirectory(pico_sd_card)
# currently very old and non-compiling
#pico_add_subdirectory(pico_scanvideo_dbi)
pico_add_subdirectory(pico_scanvideo_dpi)
pico_add_subdirectory(usb_common)
pico_add_subdirectory(usb_device)
pico_add_subdirectory(usb_device_msc)

Wyświetl plik

@ -0,0 +1 @@
pico_simple_hardware_target(rosc)

Wyświetl plik

@ -0,0 +1,90 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_ROSC_H_
#define _HARDWARE_ROSC_H_
#include "pico.h"
#include "hardware/structs/rosc.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file rosc.h
* \defgroup hardware_rosc hardware_rosc
*
* Ring Oscillator (ROSC) API
*
* A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of
* inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the
* first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a
* crystal oscillator, youll likely want to switch to this as your reference clock as soon as possible, because the frequency is
* more accurate than the ring oscillator.
*/
/*! \brief Set frequency of the Ring Oscillator
* \ingroup hardware_rosc
*
* \param code The drive strengths. See the RP2040 datasheet for information on this value.
*/
void rosc_set_freq(uint32_t code);
/*! \brief Set range of the Ring Oscillator
* \ingroup hardware_rosc
*
* Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT).
* Clock output will not glitch when changing the range up one step at a time.
*
* \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High.
*/
void rosc_set_range(uint range);
/*! \brief Disable the Ring Oscillator
* \ingroup hardware_rosc
*
*/
void rosc_disable(void);
/*! \brief Put Ring Oscillator in to dormant mode.
* \ingroup hardware_rosc
*
* The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt.
* This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low.
* If no IRQ is configured before going into dormant mode the ROSC will never restart.
*
* PLLs should be stopped before selecting dormant mode.
*/
void rosc_set_dormant(void);
// FIXME: Add doxygen
uint32_t next_rosc_code(uint32_t code);
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz);
void rosc_set_div(uint32_t div);
inline static void rosc_clear_bad_write(void) {
hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS);
}
inline static bool rosc_write_okay(void) {
return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS);
}
inline static void rosc_write(io_rw_32 *addr, uint32_t value) {
rosc_clear_bad_write();
assert(rosc_write_okay());
*addr = value;
assert(rosc_write_okay());
};
#ifdef __cplusplus
}
#endif
#endif

Wyświetl plik

@ -0,0 +1,61 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
// For MHZ definitions etc
#include "hardware/clocks.h"
#include "hardware/rosc.h"
// Given a ROSC delay stage code, return the next-numerically-higher code.
// Top result bit is set when called on maximum ROSC code.
uint32_t next_rosc_code(uint32_t code) {
return ((code | 0x08888888u) + 1u) & 0xf7777777u;
}
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) {
// TODO: This could be a lot better
rosc_set_div(1);
for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) {
rosc_set_freq(code);
uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000;
if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) {
return rosc_mhz;
}
}
return 0;
}
void rosc_set_div(uint32_t div) {
assert(div <= 31 && div >= 1);
rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div);
}
void rosc_set_freq(uint32_t code) {
rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu));
rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u));
}
void rosc_set_range(uint range) {
// Range should use enumvals from the headers and thus have the password correct
rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range);
}
void rosc_disable(void) {
uint32_t tmp = rosc_hw->ctrl;
tmp &= (~ROSC_CTRL_ENABLE_BITS);
tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB);
rosc_write(&rosc_hw->ctrl, tmp);
// Wait for stable to go away
while(rosc_hw->status & ROSC_STATUS_STABLE_BITS);
}
void rosc_set_dormant(void) {
// WARNING: This stops the rosc until woken up by an irq
rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT);
// Wait for it to become stable once woken up
while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS));
}

Wyświetl plik

@ -0,0 +1,59 @@
set(LWIP_TEST_PATH "src/core/tcp.c")
if (NOT LWIP_PATH)
set(LWIP_PATH ${PICO_EXTRAS_PATH}/lib/lwip)
if (NOT EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH})
message(WARNING "lwIP submodule has not been initialized; TCP/IP support will be unavailable
hint: try 'git submodule update --init'.")
endif()
elseif (NOT EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH})
message(WARNING "LWIP_PATH specified but content not present.")
endif()
if (EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH})
message("lwIP available at ${LWIP_PATH}/${LWIP_TEST_PATH}; TCP/IP support is available.")
add_library(lwip INTERFACE)
target_sources(lwip INTERFACE
${LWIP_PATH}/src/core/altcp.c
${LWIP_PATH}/src/core/altcp_alloc.c
${LWIP_PATH}/src/core/altcp_tcp.c
${LWIP_PATH}/src/core/def.c
${LWIP_PATH}/src/core/dns.c
${LWIP_PATH}/src/core/inet_chksum.c
${LWIP_PATH}/src/core/init.c
${LWIP_PATH}/src/core/ip.c
${LWIP_PATH}/src/core/mem.c
${LWIP_PATH}/src/core/memp.c
${LWIP_PATH}/src/core/netif.c
${LWIP_PATH}/src/core/pbuf.c
${LWIP_PATH}/src/core/raw.c
${LWIP_PATH}/src/core/stats.c
${LWIP_PATH}/src/core/sys.c
${LWIP_PATH}/src/core/tcp.c
${LWIP_PATH}/src/core/tcp_in.c
${LWIP_PATH}/src/core/tcp_out.c
${LWIP_PATH}/src/core/timeouts.c
${LWIP_PATH}/src/core/udp.c
${LWIP_PATH}/src/core/ipv4/autoip.c
${LWIP_PATH}/src/core/ipv4/dhcp.c
${LWIP_PATH}/src/core/ipv4/etharp.c
${LWIP_PATH}/src/core/ipv4/icmp.c
${LWIP_PATH}/src/core/ipv4/igmp.c
${LWIP_PATH}/src/core/ipv4/ip4.c
${LWIP_PATH}/src/core/ipv4/ip4_addr.c
${LWIP_PATH}/src/core/ipv4/ip4_frag.c
${LWIP_PATH}/src/netif/ethernet.c
${LWIP_PATH}/src/netif/slipif.c
${LWIP_PATH}/src/apps/http/httpd.c
${LWIP_PATH}/src/apps/http/fs.c
${CMAKE_CURRENT_LIST_DIR}/lwip_arch.c
)
target_include_directories(lwip INTERFACE
${CMAKE_CURRENT_LIST_DIR}/include
${LWIP_PATH}/src/include
${LWIP_PATH}/src/include/ipv4
${LWIP_PATH}/src/include/lwip/apps
)
endif()

Wyświetl plik

@ -0,0 +1 @@
This directory contains files required to get lwip working on the Pico platform without needing to fork lwIP

Wyświetl plik

@ -0,0 +1,75 @@
/*
* Copyright (c) 2001-2003 Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* This file is part of the lwIP TCP/IP stack.
*
* Author: Adam Dunkels <adam@sics.se>
*
*/
#ifndef __CC_H__
#define __CC_H__
//#include "cpu.h"
typedef int sys_prot_t;
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
#define LWIP_PLATFORM_ASSERT(x) do { if(!(x)) while(1); } while(0)
#endif /* __CC_H__ */

Wyświetl plik

@ -0,0 +1,22 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lwip/init.h"
#include "pico/stdlib.h"
/* lwip has provision for using a mutex, when applicable */
sys_prot_t sys_arch_protect(void) {
return 0;
}
void sys_arch_unprotect(sys_prot_t pval) {
(void) pval;
}
/* lwip needs a millisecond time source, and the TinyUSB board support code has one available */
uint32_t sys_now(void) {
return to_ms_since_boot(get_absolute_time());
}

Wyświetl plik

@ -0,0 +1,12 @@
if (NOT TARGET pico_audio_i2s)
add_library(pico_audio_i2s INTERFACE)
pico_generate_pio_header(pico_audio_i2s ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio)
target_sources(pico_audio_i2s INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audio_i2s.c
)
target_include_directories(pico_audio_i2s INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_audio_i2s INTERFACE hardware_dma hardware_pio hardware_irq pico_audio)
endif()

Wyświetl plik

@ -0,0 +1,362 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/audio_i2s.h"
#include "audio_i2s.pio.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/clocks.h"
CU_REGISTER_DEBUG_PINS(audio_timing)
// ---- select at most one ---
//CU_SELECT_DEBUG_PINS(audio_timing)
#if PICO_AUDIO_I2S_MONO_OUTPUT
#define i2s_dma_configure_size DMA_SIZE_16
#else
#define i2s_dma_configure_size DMA_SIZE_32
#endif
#define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO)
#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_I2S_PIO)
#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_I2S_PIO), _TX0)
#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_I2S_DMA_IRQ)
#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_I2S_DMA_IRQ),_enabled)
#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_I2S_DMA_IRQ)
struct {
audio_buffer_t *playing_buffer;
uint32_t freq;
uint8_t pio_sm;
uint8_t dma_channel;
} shared_state;
audio_format_t pio_i2s_consumer_format;
audio_buffer_format_t pio_i2s_consumer_buffer_format = {
.format = &pio_i2s_consumer_format,
};
static audio_buffer_t silence_buffer;
static void __isr __time_critical_func(audio_i2s_dma_irq_handler)();
const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format,
const audio_i2s_config_t *config) {
uint func = GPIO_FUNC_PIOx;
gpio_set_function(config->data_pin, func);
gpio_set_function(config->clock_pin_base, func);
gpio_set_function(config->clock_pin_base + 1, func);
uint8_t sm = shared_state.pio_sm = config->pio_sm;
pio_sm_claim(audio_pio, sm);
uint offset = pio_add_program(audio_pio, &audio_i2s_program);
audio_i2s_program_init(audio_pio, sm, offset, config->data_pin, config->clock_pin_base);
silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH * 4);
silence_buffer.sample_count = PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH;
silence_buffer.format = &pio_i2s_consumer_buffer_format;
__mem_fence_release();
uint8_t dma_channel = config->dma_channel;
dma_channel_claim(dma_channel);
shared_state.dma_channel = dma_channel;
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
channel_config_set_dreq(&dma_config,
DREQ_PIOx_TX0 + sm
);
channel_config_set_transfer_data_size(&dma_config, i2s_dma_configure_size);
dma_channel_configure(dma_channel,
&dma_config,
&audio_pio->txf[sm], // dest
NULL, // src
0, // count
false // trigger
);
irq_add_shared_handler(DMA_IRQ_x, audio_i2s_dma_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irqx_enabled(dma_channel, 1);
return intended_audio_format;
}
static audio_buffer_pool_t *audio_i2s_consumer;
static void update_pio_frequency(uint32_t sample_freq) {
printf("setting pio freq %d\n", (int) sample_freq);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
assert(system_clock_frequency < 0x40000000);
uint32_t divider = system_clock_frequency * 4 / sample_freq; // avoid arithmetic overflow
printf("System clock at %u, I2S clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider);
assert(divider < 0x1000000);
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
shared_state.freq = sample_freq;
}
static audio_buffer_t *wrap_consumer_take(audio_connection_t *connection, bool block) {
// support dynamic frequency shifting
if (connection->producer_pool->format->sample_freq != shared_state.freq) {
update_pio_frequency(connection->producer_pool->format->sample_freq);
}
#if PICO_AUDIO_I2S_MONO_INPUT
#if PICO_AUDIO_I2S_MONO_OUTPUT
return mono_to_mono_consumer_take(connection, block);
#else
return mono_to_stereo_consumer_take(connection, block);
#endif
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
unsupported;
#else
return stereo_to_stereo_consumer_take(connection, block);
#endif
#endif
}
static void wrap_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
// support dynamic frequency shifting
if (connection->producer_pool->format->sample_freq != shared_state.freq) {
update_pio_frequency(connection->producer_pool->format->sample_freq);
}
#if PICO_AUDIO_I2S_MONO_INPUT
#if PICO_AUDIO_I2S_MONO_OUTPUT
assert(false);
// return mono_to_mono_producer_give(connection, block);
#else
assert(false);
//return mono_to_stereo_producer_give(connection, buffer);
#endif
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
unsupported;
#else
return stereo_to_stereo_producer_give(connection, buffer);
#endif
#endif
}
static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_ct_connection = {
.core = {
.consumer_pool_take = wrap_consumer_take,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = producer_pool_give_buffer_default,
}
};
static struct producer_pool_blocking_give_connection m2s_audio_i2s_pg_connection = {
.core = {
.consumer_pool_take = consumer_pool_take_buffer_default,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = wrap_producer_give,
}
};
bool audio_i2s_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection) {
return audio_i2s_connect_extra(producer, false, 2, 256, connection);
}
bool audio_i2s_connect(audio_buffer_pool_t *producer) {
return audio_i2s_connect_thru(producer, NULL);
}
bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
uint samples_per_buffer, audio_connection_t *connection) {
printf("Connecting PIO I2S audio\n");
// todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S16);
pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
// todo we could do mono
// todo we can't match exact, so we should return what we can do
pio_i2s_consumer_format.sample_freq = producer->format->sample_freq;
#if PICO_AUDIO_I2S_MONO_OUTPUT
pio_i2s_consumer_format.channel_count = 1;
pio_i2s_consumer_buffer_format.sample_stride = 2;
#else
pio_i2s_consumer_format.channel_count = 2;
pio_i2s_consumer_buffer_format.sample_stride = 4;
#endif
audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, buffer_count, samples_per_buffer);
update_pio_frequency(producer->format->sample_freq);
// todo cleanup threading
__mem_fence_release();
if (!connection) {
if (producer->format->channel_count == 2) {
#if PICO_AUDIO_I2S_MONO_INPUT
panic("need to merge channels down\n");
#else
#if PICO_AUDIO_I2S_MONO_OUTPUT
panic("trying to play stereo thru mono not yet supported");
#else
printf("Copying stereo to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
#endif
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
} else {
#if PICO_AUDIO_I2S_MONO_OUTPUT
printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq);
#else
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
}
connection = buffer_on_give ? &m2s_audio_i2s_pg_connection.core : &m2s_audio_i2s_ct_connection.core;
}
audio_complete_connection(connection, producer, audio_i2s_consumer);
return true;
}
static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_connection_s8 = {
.core = {
#if PICO_AUDIO_I2S_MONO_OUTPUT
.consumer_pool_take = mono_s8_to_mono_consumer_take,
#else
.consumer_pool_take = mono_s8_to_stereo_consumer_take,
#endif
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = producer_pool_give_buffer_default,
}
};
bool audio_i2s_connect_s8(audio_buffer_pool_t *producer) {
printf("Connecting PIO I2S audio (U8)\n");
// todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S8);
pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16;
// todo we could do mono
// todo we can't match exact, so we should return what we can do
pio_i2s_consumer_format.sample_freq = producer->format->sample_freq;
#if PICO_AUDIO_I2S_MONO_OUTPUT
pio_i2s_consumer_format.channel_count = 1;
pio_i2s_consumer_buffer_format.sample_stride = 2;
#else
pio_i2s_consumer_format.channel_count = 2;
pio_i2s_consumer_buffer_format.sample_stride = 4;
#endif
// we do this on take so should do it quickly...
uint samples_per_buffer = 256;
// todo with take we really only need 1 buffer
audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, 2, samples_per_buffer);
// todo we need a method to calculate this in clocks
uint32_t system_clock_frequency = 48000000;
// uint32_t divider = system_clock_frequency * 256 / producer->format->sample_freq * 16 * 4;
uint32_t divider = system_clock_frequency * 4 / producer->format->sample_freq; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
// todo cleanup threading
__mem_fence_release();
audio_connection_t *connection;
if (producer->format->channel_count == 2) {
#if PICO_AUDIO_I2S_MONO_OUTPUT
panic("trying to play stereo thru mono not yet supported");
#endif
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
connection = &m2s_audio_i2s_connection_s8.core;
} else {
#if PICO_AUDIO_I2S_MONO_OUTPUT
printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq);
#else
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
connection = &m2s_audio_i2s_connection_s8.core;
}
audio_complete_connection(connection, producer, audio_i2s_consumer);
return true;
}
static inline void audio_start_dma_transfer() {
assert(!shared_state.playing_buffer);
audio_buffer_t *ab = take_audio_buffer(audio_i2s_consumer, false);
shared_state.playing_buffer = ab;
if (!ab) {
DEBUG_PINS_XOR(audio_timing, 1);
DEBUG_PINS_XOR(audio_timing, 2);
DEBUG_PINS_XOR(audio_timing, 1);
//DEBUG_PINS_XOR(audio_timing, 2);
// just play some silence
ab = &silence_buffer;
}
assert(ab->sample_count);
// todo better naming of format->format->format!!
assert(ab->format->format->format == AUDIO_BUFFER_FORMAT_PCM_S16);
#if PICO_AUDIO_I2S_MONO_OUTPUT
assert(ab->format->format->channel_count == 1);
assert(ab->format->sample_stride == 2);
#else
assert(ab->format->format->channel_count == 2);
assert(ab->format->sample_stride == 4);
#endif
dma_channel_transfer_from_buffer_now(shared_state.dma_channel, ab->buffer->bytes, ab->sample_count);
}
// irq handler for DMA
void __isr __time_critical_func(audio_i2s_dma_irq_handler)() {
#if PICO_AUDIO_I2S_NOOP
assert(false);
#else
uint dma_channel = shared_state.dma_channel;
if (dma_intsx & (1u << dma_channel)) {
dma_intsx = 1u << dma_channel;
DEBUG_PINS_SET(audio_timing, 4);
// free the buffer we just finished
if (shared_state.playing_buffer) {
give_audio_buffer(audio_i2s_consumer, shared_state.playing_buffer);
#ifndef NDEBUG
shared_state.playing_buffer = NULL;
#endif
}
audio_start_dma_transfer();
DEBUG_PINS_CLR(audio_timing, 4);
}
#endif
}
static bool audio_enabled;
void audio_i2s_set_enabled(bool enabled) {
if (enabled != audio_enabled) {
#ifndef NDEBUG
if (enabled)
{
puts("Enabling PIO I2S audio\n");
printf("(on core %d\n", get_core_num());
}
#endif
irq_set_enabled(DMA_IRQ_x, enabled);
if (enabled) {
audio_start_dma_transfer();
}
pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled);
audio_enabled = enabled;
}
}

Wyświetl plik

@ -0,0 +1,63 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Transmit a mono or stereo I2S audio stream as stereo
; This is 16 bits per sample; can be altered by modifying the "set" params,
; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register.
;
; Autopull must be enabled, with threshold set to 32.
; Since I2S is MSB-first, shift direction should be to left.
; Hence the format of the FIFO word is:
;
; | 31 : 16 | 15 : 0 |
; | sample ws=0 | sample ws=1 |
;
; Data is output at 1 bit per clock. Use clock divider to adjust frequency.
; Fractional divider will probably be needed to get correct bit clock period,
; but for common syslck freqs this should still give a constant word select period.
;
; One output pin is used for the data output.
; Two side-set pins are used. Bit 0 is clock, bit 1 is word select.
; Send 16 bit words to the PIO for mono, 32 bit words for stereo
.program audio_i2s
.side_set 2
; /--- LRCLK
; |/-- BCLK
bitloop1: ; ||
out pins, 1 side 0b10
jmp x-- bitloop1 side 0b11
out pins, 1 side 0b00
set x, 14 side 0b01
bitloop0:
out pins, 1 side 0b00
jmp x-- bitloop0 side 0b01
out pins, 1 side 0b10
public entry_point:
set x, 14 side 0b11
% c-sdk {
static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, data_pin, 1);
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
sm_config_set_out_shift(&sm_config, false, true, 32);
pio_sm_init(pio, sm, offset, &sm_config);
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
pio_sm_set_pins(pio, sm, 0); // clear pins
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
}
%}

Wyświetl plik

@ -0,0 +1,182 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_I2S_H
#define _PICO_AUDIO_I2S_H
#include "pico/audio.h"
/** \file audio_i2s.h
* \defgroup pico_audio_i2s pico_audio_i2s
* I2S audio output using the PIO
*
* This library uses the \ref hardware_pio system to implement a I2S audio interface
*
* \todo Must be more we need to say here.
* \todo certainly need an example
*
*/
#ifdef __cplusplus
extern "C" {
#endif
#ifndef PICO_AUDIO_I2S_DMA_IRQ
#ifdef PICO_AUDIO_DMA_IRQ
#define PICO_AUDIO_I2S_DMA_IRQ PICO_AUDIO_DMA_IRQ
#else
#define PICO_AUDIO_I2S_DMA_IRQ 0
#endif
#endif
#ifndef PICO_AUDIO_I2S_PIO
#ifdef PICO_AUDIO_PIO
#define PICO_AUDIO_I2S_PIO PICO_AUDIO_PIO
#else
#define PICO_AUDIO_I2S_PIO 0
#endif
#endif
#if !(PICO_AUDIO_I2S_DMA_IRQ == 0 || PICO_AUDIO_I2S_DMA_IRQ == 1)
#error PICO_AUDIO_I2S_DMA_IRQ must be 0 or 1
#endif
#if !(PICO_AUDIO_I2S_PIO == 0 || PICO_AUDIO_I2S_PIO == 1)
#error PICO_AUDIO_I2S_PIO ust be 0 or 1
#endif
#ifndef PICO_AUDIO_I2S_MAX_CHANNELS
#ifdef PICO_AUDIO_MAX_CHANNELS
#define PICO_AUDIO_I2S_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS
#else
#define PICO_AUDIO_I2S_MAX_CHANNELS 2u
#endif
#endif
#ifndef PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL
#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL
#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL
#else
#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL 3u
#endif
#endif
#ifndef PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH
#ifdef PICO_AUDIO_BUFFER_SAMPLE_LENGTH
#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH PICO_AUDIO_BUFFER_SAMPLE_LENGTH
#else
#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH 576u
#endif
#endif
#ifndef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH
#ifdef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH
#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH PICO_AUDIO_SILENCE_BUFFER_SAMPLE_LENGTH
#else
#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH 256u
#endif
#endif
// Allow use of pico_audio driver without actually doing anything much
#ifndef PICO_AUDIO_I2S_NOOP
#ifdef PICO_AUDIO_NOOP
#define PICO_AUDIO_I2S_NOOP PICO_AUDIO_NOOP
#else
#define PICO_AUDIO_I2S_NOOP 0
#endif
#endif
#ifndef PICO_AUDIO_I2S_MONO_INPUT
#define PICO_AUDIO_I2S_MONO_INPUT 0
#endif
#ifndef PICO_AUDIO_I2S_MONO_OUTPUT
#define PICO_AUDIO_I2S_MONO_OUTPUT 0
#endif
#ifndef PICO_AUDIO_I2S_DATA_PIN
//#warning PICO_AUDIO_I2S_DATA_PIN should be defined when using AUDIO_I2S
#define PICO_AUDIO_I2S_DATA_PIN 27
#endif
#ifndef PICO_AUDIO_I2S_CLOCK_PIN_BASE
//#warning PICO_AUDIO_I2S_CLOCK_PIN_BASE should be defined when using AUDIO_I2S
#define PICO_AUDIO_I2S_CLOCK_PIN_BASE 25
#endif
// todo this needs to come from a build config
/** \brief Base configuration structure used when setting up
* \ingroup pico_audio_i2s
*/
typedef struct audio_i2s_config {
uint8_t data_pin;
uint8_t clock_pin_base;
uint8_t dma_channel;
uint8_t pio_sm;
} audio_i2s_config_t;
/** \brief Set up system to output I2S audio
* \ingroup pico_audio_i2s
*
* \param intended_audio_format \todo
* \param config The configuration to apply.
*/
const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format,
const audio_i2s_config_t *config);
/** \brief \todo
* \ingroup pico_audio_i2s
*
* \param producer
* \param connection
*/
bool audio_i2s_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection);
/** \brief \todo
* \ingroup pico_audio_i2s
*
* \param producer
*
* todo make a common version (or a macro) .. we don't want to pull in unnecessary code by default
*/
bool audio_i2s_connect(audio_buffer_pool_t *producer);
/** \brief \todo
* \ingroup pico_audio_i2s
*
* \param producer
*/
bool audio_i2s_connect_s8(audio_buffer_pool_t *producer);
bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, uint samples_per_buffer, audio_connection_t *connection);
/** \brief \todo
* \ingroup pico_audio_i2s
*
* \param producer
* \param buffer_on_give
* \param buffer_count
* \param samples_per_buffer
* \param connection
* \return
*/
bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
uint samples_per_buffer, audio_connection_t *connection);
/** \brief Set up system to output I2S audio
* \ingroup pico_audio_i2s
*
* \param enable true to enable I2S audio, false to disable.
*/
void audio_i2s_set_enabled(bool enabled);
#ifdef __cplusplus
}
#endif
#endif //_AUDIO_I2S_H

Wyświetl plik

@ -0,0 +1,19 @@
if (NOT TARGET pico_audio_pwm)
add_library(pico_audio_pwm INTERFACE)
pico_generate_pio_header(pico_audio_pwm ${CMAKE_CURRENT_LIST_DIR}/audio_pwm.pio)
target_sources(pico_audio_pwm INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audio_pwm.c
${CMAKE_CURRENT_LIST_DIR}/sample_encoding.cpp
)
target_include_directories(pico_audio_pwm INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_audio_pwm INTERFACE
hardware_dma
hardware_pio
hardware_irq
hardware_interp
pico_audio
pico_multicore)
endif()

Wyświetl plik

@ -0,0 +1,389 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include "pico/audio_pwm.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "hardware/irq.h"
#include "pico/multicore.h"
#include "pico/sem.h"
#include "pico/audio_pwm/sample_encoding.h"
#include "audio_pwm.pio.h"
// TODO: add noise shaped fixed dither
#define audio_pio __CONCAT(pio, PICO_AUDIO_PWM_PIO)
#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_PWM_PIO)
#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_PWM_PIO), _TX0)
#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_PWM_DMA_IRQ)
#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_PWM_DMA_IRQ),_enabled)
#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_PWM_DMA_IRQ)
// ======================
// == DEBUGGING =========
#define ENABLE_PIO_AUDIO_PWM_ASSERTIONS
CU_REGISTER_DEBUG_PINS(audio_timing, audio_underflow)
// ---- select at most one ---
//CU_SELECT_DEBUG_PINS(audio_timing)
// ======================
#ifdef ENABLE_PIO_AUDIO_PWM_ASSERTIONS
#define audio_assert(x) assert(x)
#else
#define audio_assert(x) (void)0
#endif
#define _UNDERSCORE(x, y) x ## _ ## y
#define _CONCAT(x, y) _UNDERSCORE(x,y)
#define audio_program _CONCAT(program_name,program)
#define audio_program_get_default_config _CONCAT(program_name,program_get_default_config)
#define audio_entry_point _CONCAT(program_name,offset_entry_point)
static bool audio_enabled;
static bool push_queuing_to_core1;
static void __isr __time_critical_func(audio_pwm_dma_irq_handler)();
static struct {
audio_buffer_pool_t *playback_buffer_pool[PICO_AUDIO_PWM_MAX_CHANNELS];
audio_buffer_t *playing_buffer[PICO_AUDIO_PWM_MAX_CHANNELS];
// ----- begin protected by free_list_spin_lock -----
uint8_t pio_sm[PICO_AUDIO_PWM_MAX_CHANNELS];
uint8_t dma_channel[PICO_AUDIO_PWM_MAX_CHANNELS];
int channel_count;
} shared_state;
const audio_pwm_channel_config_t default_left_channel_config =
{
.core = {
.base_pin = PICO_AUDIO_PWM_L_PIN,
.pio_sm = 0,
.dma_channel = 0
},
.pattern = 1,
};
const audio_pwm_channel_config_t default_right_channel_config =
{
.core = {
.base_pin = PICO_AUDIO_PWM_R_PIN,
.pio_sm = 1,
.dma_channel = 1
},
.pattern = 1,
};
const audio_pwm_channel_config_t default_mono_channel_config =
{
.core = {
.base_pin = PICO_AUDIO_PWM_MONO_PIN,
.pio_sm = 0,
.dma_channel = 0
},
.pattern = 3,
};
static audio_buffer_t silence_buffer;
static inline void audio_start_dma_transfer(int ch)
{
#if PICO_AUDIO_PWM_NOOP
assert(false);
#else
assert(!shared_state.playing_buffer[ch]);
audio_buffer_t *ab = take_audio_buffer(shared_state.playback_buffer_pool[ch], false);
shared_state.playing_buffer[ch] = ab;
DEBUG_PINS_SET(audio_underflow, 4);
if (!ab)
{
DEBUG_PINS_XOR(audio_underflow, 2);
// just play some silence
ab = &silence_buffer;
// static int foo;
// printf("underflow %d\n", foo++);
DEBUG_PINS_XOR(audio_underflow, 2);
}
DEBUG_PINS_CLR(audio_underflow, 4);
assert(ab->sample_count);
// todo better naming of format->format->format!!
assert(ab->format->format->format == NATIVE_BUFFER_FORMAT);
assert(ab->format->format->channel_count == 1);
assert(ab->format->sample_stride == sizeof(pwm_cmd_t));
dma_channel_transfer_from_buffer_now(shared_state.dma_channel[ch], ab->buffer->bytes,
ab->sample_count * sizeof(pwm_cmd_t) / 4);
}
semaphore_t sem_transfer_buffer_fill, sem_transfer_buffer_drain;
void *volatile transfer_buffer;
int32_t transfer_buffer_sample_count;
// irq handler for DMA
static void __isr __time_critical_func(audio_pwm_dma_irq_handler)()
{
#if PICO_AUDIO_PWM_NOOP
assert(false);
#else
// todo better DMA channel handling? (should we combine to keep channels in sync?)
// (pico_audio - sync should be maintained by source of pico_audio buffers, though we need to be able to insert
// the correct amount of silence to re-align)
for(int ch = 0; ch < shared_state.channel_count; ch++)
{
uint dma_channel = shared_state.dma_channel[ch];
if (dma_intsx & (1u << dma_channel))
{
dma_intsx = 1u << dma_channel;
DEBUG_PINS_SET(audio_timing, 4);
// free the buffer we just finished
if (shared_state.playing_buffer[ch])
{
give_audio_buffer(shared_state.playback_buffer_pool[ch], shared_state.playing_buffer[ch]);
#ifndef NDEBUG
shared_state.playing_buffer[ch] = 0;
#endif
}
audio_start_dma_transfer(ch);
DEBUG_PINS_CLR(audio_timing, 4);
}
}
#endif
}
audio_format_t pwm_consumer_format;
audio_buffer_format_t pwm_consumer_buffer_format = {
.format = &pwm_consumer_format,
.sample_stride = sizeof(pwm_cmd_t)
};
audio_buffer_pool_t *pwm_consumer_pool;
const audio_format_t *audio_pwm_setup(const audio_format_t *intended_audio_format, int32_t max_latency_ms,
const audio_pwm_channel_config_t *channel_config0, ...)
{
va_list args;
assert(max_latency_ms == -1); // not implemented yet
__builtin_memset(&shared_state, 0, sizeof(shared_state));
// init non zero members
#if !PICO_AUDIO_PWM_NOOP
shared_state.channel_count = intended_audio_format->channel_count;
#if !PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING
pwm_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1;
pwm_consumer_format.channel_count = 1;
#else
pwm_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3;
pwm_consumer_format.channel_count = 1;
#endif
#ifndef AUDIO_HALF_FREQ
pwm_consumer_format.sample_freq = 22058;
#else
pwm_consumer_format.sample_freq = 11029;
#endif
for(int i = 0; i < shared_state.channel_count; i++)
{
shared_state.playback_buffer_pool[i] = audio_new_consumer_pool(&pwm_consumer_buffer_format,
PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL,
PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH);
}
__mem_fence_release();
silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH * sizeof(silence_cmd));
for(int i = 0; i < PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH; i++)
{
__builtin_memcpy((void *) (silence_buffer.buffer->bytes + i * sizeof(silence_cmd)), &silence_cmd,
sizeof(silence_cmd));
}
silence_buffer.sample_count = PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH;
silence_buffer.format = &pwm_consumer_buffer_format;
va_start(args, channel_config0);
uint offset = pio_add_program(audio_pio, &audio_program);
const audio_pwm_channel_config_t *config = channel_config0;
// todo should be shared, but we don't have that yet
irq_set_exclusive_handler(DMA_IRQ_x, audio_pwm_dma_irq_handler);
for(int ch = 0; ch < shared_state.channel_count; ch++)
{
if (!config)
{
config = va_arg(args, const struct audio_pwm_channel_config *);
}
gpio_set_function(config->core.base_pin, GPIO_FUNC_PIOx);
uint8_t sm = shared_state.pio_sm[ch] = config->core.pio_sm;
pio_sm_claim(audio_pio, sm);
pio_sm_config sm_config = audio_program_get_default_config(offset);
sm_config_set_out_pins(&sm_config, config->core.base_pin, 1);
sm_config_set_sideset_pins(&sm_config, config->core.base_pin);
// disable auto-pull for !OSRE (which doesn't work with auto-pull)
static_assert(CYCLES_PER_SAMPLE <= 18, "");
sm_config_set_out_shift(&sm_config, true, false, CMD_BITS + CYCLES_PER_SAMPLE);
pio_sm_init(audio_pio, sm, offset, &sm_config);
pio_sm_set_consecutive_pindirs(audio_pio, sm, config->core.base_pin, 1, true);
pio_sm_set_pins(audio_pio, sm, 0);
// todo this should be part of sm_init
pio_sm_exec(audio_pio, sm, pio_encode_jmp(offset + audio_entry_point)); // jmp to ep
uint8_t dma_channel = config->core.dma_channel;
dma_channel_claim(dma_channel);
shared_state.dma_channel[ch] = dma_channel;
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
channel_config_set_dreq(&dma_config, DREQ_PIOx_TX0 + sm);
dma_channel_configure(dma_channel,
&dma_config,
&audio_pio->txf[sm], // dest
NULL, // src
0, // count
false // trigger
);
dma_channel_set_irqx_enabled(dma_channel, 1);
config = 0;
}
va_end(args);
#endif
#ifndef NDEBUG
puts("PicoAudio: initialized\n");
#endif
pwm_consumer_pool = (shared_state.playback_buffer_pool[0]); // forcing channel 0 to be consumer for now
// todo we need to update this to what is exact
return intended_audio_format;
}
void audio_pwm_set_enabled(bool enabled)
{
if (enabled != audio_enabled)
{
#ifndef NDEBUG
if (enabled)
{
puts("Enabling PIO PWM audio\n");
}
#endif
#if !PICO_AUDIO_PWM_NOOP
irq_set_enabled(DMA_IRQ_x, enabled);
if (enabled)
{
// todo this is wrong
for(int ch = 0; ch < shared_state.channel_count; ch++)
{
audio_start_dma_transfer(ch);
}
}
// todo need to start them in sync - need WAIT in program
for(int ch = 0; ch < shared_state.channel_count; ch++)
{
pio_sm_set_enabled(audio_pio, shared_state.pio_sm[ch], enabled);
}
#endif
audio_enabled = enabled;
}
}
#pragma GCC push_options
#ifdef __arm__
// seems uber keen to inline audio_queue_samples which is large
#pragma GCC optimize("O1")
#endif
void core1_worker()
{
while (true)
{
sem_acquire_blocking(&sem_transfer_buffer_drain);
// audio_queue_samples(0, transfer_buffer, transfer_buffer_sample_count, 1, true);
sem_release(&sem_transfer_buffer_fill);
}
__builtin_unreachable();
}
#pragma GCC pop_options
bool audio_start_queue_work_on_core_1()
{
if (!push_queuing_to_core1)
{
puts("In the spirit of the season, core 1 is helping out too...\n");
sem_init(&sem_transfer_buffer_drain, 0, 1);
// one fill is implicitly owned by the client application as it has a buffer
// (note the count here is actually the number of buffers the client has)
sem_init(&sem_transfer_buffer_fill, 2, 1);
multicore_launch_core1(core1_worker);
push_queuing_to_core1 = true;
}
return true;
}
#endif
static struct producer_pool_blocking_give_connection producer_pool_blocking_give_connection_singleton = {
.core = {
.consumer_pool_take = consumer_pool_take_buffer_default,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
}
// rest 0 initialized
};
bool audio_pwm_default_connect(audio_buffer_pool_t *producer_pool, bool dedicate_core_1)
{
if (!dedicate_core_1)
{
printf("Connecting PIO PWM audio via 'blocking give'\n");
assert(pwm_consumer_pool);
assert(pwm_consumer_pool->format->channel_count == 1); // for now
// todo oops this is pulling in everything!
switch (producer_pool->format->format) {
case AUDIO_BUFFER_FORMAT_PCM_S16:
producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s16;
break;
case AUDIO_BUFFER_FORMAT_PCM_S8:
producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s8;
break;
case AUDIO_BUFFER_FORMAT_PCM_U16:
producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s16;
break;
case AUDIO_BUFFER_FORMAT_PCM_U8:
producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s8;
break;
default:
return false;
}
audio_complete_connection(&producer_pool_blocking_give_connection_singleton.core, producer_pool,
pwm_consumer_pool);
return true;
}
else
{
assert(false);
}
return false;
}

Wyświetl plik

@ -0,0 +1,58 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program pwm_one_bit_dither
.side_set 1 opt
; Format:
; | high len | low len | (dither) * n |
; OSR level
; cycle length = 7 + 2 + 127
; 136 clocks/cycle frequency 352941 / 16 = 22058
delay:
nop [2]
.wrap_target
out pins, 1
loops:
mov x, isr side 1
loop1:
jmp x-- loop1
mov x, y side 0
loop0:
jmp x-- loop0
jmp !osre delay
public entry_point:
pull
out isr, 7
out y, 7
.wrap
.program pwm_two_bit_dither
.side_set 1 opt
; Format:
; | high len | low len | (dither) * n |
; OSR level
; this 138 clocks/cycle frequency 347826 / 16 = 21739Hz
delay:
nop [2]
.wrap_target
out pins, 1
out pins, 1
out pins, 1
loops:
mov x, isr side 1
loop1:
jmp x-- loop1
mov x, y side 0
loop0:
jmp x-- loop0
jmp !osre delay
entry_point:
pull
out isr, 7
out y, 7
.wrap

Wyświetl plik

@ -0,0 +1,183 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_PWM_H
#define _PICO_AUDIO_PWM_H
#include "pico/audio.h"
#ifdef __cplusplus
extern "C" {
#endif
// ======================
// == CONFIG ============
#ifndef PICO_AUDIO_PWM_DMA_IRQ
#ifdef PICO_AUDIO_IRQ
#define PICO_AUDIO_PWM_DMA_IRQ PICO_AUDIO_DMA_IRQ
#else
#define PICO_AUDIO_PWM_DMA_IRQ 1
#endif
#endif
#ifndef PICO_AUDIO_PWM_PIO
#ifdef PICO_AUDIO_PIO
#define PICO_AUDIO_PWM_PIO PICO_AUDIO_PIO
#else
#define PICO_AUDIO_PWM_PIO 0
#endif
#endif
#if !(PICO_AUDIO_PWM_DMA_IRQ == 0 || PICO_AUDIO_PWM_DMA_IRQ == 1)
#error PICO_AUDIO_PWM_DMA_IRQ must be 0 or 1
#endif
#if !(PICO_AUDIO_PWM_PIO == 0 || PICO_AUDIO_PWM_PIO == 1)
#error PICO_AUDIO_PWM_PIO ust be 0 or 1
#endif
#ifndef PICO_AUDIO_PWM_MAX_CHANNELS
#ifdef PICO_AUDIO_MAX_CHANNELS
#define PICO_AUDIO_PWM_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS
#else
#define PICO_AUDIO_PWM_MAX_CHANNELS 2u
#endif
#endif
#ifndef PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL
#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL
#define PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL
#else
#define PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL 3u
#endif
#endif
#ifndef PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH
#ifdef PICO_AUDIO_BUFFER_SAMPLE_LENGTH
#define PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH PICO_AUDIO_BUFFER_SAMPLE_LENGTH
#else
#define PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH 576u
#endif
#endif
#ifndef PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH
#ifdef PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH
#define PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH PICO_AUDIO_SILENCE_BUFFER_SAMPLE_LENGTH
#else
#define PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH 256u
#endif
#endif
// Enable noise shaping when super-sampling
//
// This allows for runtime selection of noise shaping or not, however having the compile
// time definition requires triple the pico_audio buffer RAM usage at runtime, and leads to marginally
// slower code in general.
#ifndef PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING
#define PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING 0
#endif
#ifndef PICO_AUDIO_PWM_L_PIN
#define PICO_AUDIO_PWM_L_PIN 0
#endif
#ifndef PICO_AUDIO_PWM_R_PIN
#define PICO_AUDIO_PWM_R_PIN 1
#endif
#ifndef PICO_AUDIO_PWM_MONO_PIN
#define PICO_AUDIO_PWM_MONO_PIN PICO_AUDIO_PWM_L_PIN
#endif
// Allow use of pico_audio driver without actually doing anything much
#ifndef PICO_AUDIO_PWM_NOOP
#ifdef PICO_AUDIO_NOOP
#define PICO_AUDIO_PWM_NOOP PICO_AUDIO_NOOP
#else
#define PICO_AUDIO_PWM_NOOP 0
#endif
#endif
/** \file audio_pwm.h
* \defgroup pico_audio_pwm pico_audio_pwm
* PWM audio output (with optional noise shaping and error diffusion) using the PIO
*
* This library uses the \ref hardware_pio system to implement a PWM audio interface
*
* \todo Must be more we need to say here.
* \todo certainly need an example
*
*/
// todo we need a place to register these or just allow them to overlap, or base them on a FOURCC - this is just made up
#define AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST 1000
#define AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1 (AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST)
#define AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3 (AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST+1)
typedef struct __packed audio_pwm_channel_config {
pio_audio_channel_config_t core;
uint8_t pattern;
} audio_pwm_channel_config_t;
// can copy this to modify just the pin
extern const audio_pwm_channel_config_t default_left_channel_config;
extern const audio_pwm_channel_config_t default_right_channel_config;
extern const audio_pwm_channel_config_t default_mono_channel_config;
/*! \brief
* \ingroup pico_audio_pwm
* \todo
*
* max_latency_ms may be -1 (for don't care)
* \param intended_audio_format
* \param max_latency_ms
* \param channel_config0
* \param ...
* \return
*/
extern const audio_format_t *
audio_pwm_setup(const audio_format_t *intended_audio_format, int32_t max_latency_ms,
const audio_pwm_channel_config_t *channel_config0, ...);
/*! \brief
* \ingroup pico_audio_pwm
* \todo
*
* \param producer_pool
* \param dedicate_core_1
* attempt a default mapping of producer buffers to pio pwm pico_audio output
* dedicate_core_1 to have core 1 set aside entirely to do work offloading as much stuff from the producer side as possible
* todo also allow IRQ handler to do it I guess
*/
extern bool audio_pwm_default_connect(audio_buffer_pool_t *producer_pool, bool dedicate_core_1);
/*! \brief
* \ingroup pico_audio_pwm
* \todo
*
* \param enable true to enable the PWM audio, false to disable
*/
extern void audio_pwm_set_enabled(bool enabled);
/*! \brief Set the PWM correction mode
* \ingroup pico_audio_pwm
*
* \param mode \todo
*/
extern bool audio_pwm_set_correction_mode(enum audio_correction_mode mode);
/*! \brief Get the PWM correction mode
* \ingroup pico_audio_pwm
*
* \return mode
*/
extern enum audio_correction_mode audio_pwm_get_correction_mode();
#ifdef __cplusplus
}
#endif
#endif //_PIO_AUDIO_PWM_H

Wyświetl plik

@ -0,0 +1,92 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_PWM_SAMPLE_ENCODING_H
#define _PICO_AUDIO_PWM_SAMPLE_ENCODING_H
#ifdef __cplusplus
extern "C" {
#endif
// todo some if not all of this can go in sample_encoding.cpp
#define FRACTIONAL_BITS 9u
#define QUANTIZED_BITS 7u
static_assert(FRACTIONAL_BITS + QUANTIZED_BITS == 16, "");
#ifndef ENABLE_NOISE_SHAPING
const uint32_t audio_carrier_freq = 350364;
#define program_name pwm_one_bit_dither
#define NATIVE_BUFFER_FORMAT AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1
#else
#define program_name pwm_two_bit_dither
#define NATIVE_BUFFER_FORMAT AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3
#endif
static_assert(QUANTIZED_BITS == 7, ""); // required by make_cmd below
#define MAKE_CMD(q) (((q)) | (127u - (q)) << 7u)
#define CMD_BITS (QUANTIZED_BITS * 2)
#define SILENCE_LEVEL 0x40u
#define SILENCE_CMD MAKE_CMD(SILENCE_LEVEL)
#ifdef ENABLE_NOISE_SHAPING
#define DITHER_BITS 3u
// this needs to be divisible by dither bits
#define CYCLES_PER_SAMPLE 15
typedef struct {
uint32_t a;
uint32_t b;
uint32_t c;
#ifdef AUDIO_HALF_FREQ
uint32_t d, e, f;
#endif
} pwm_cmd_t; // what we send to PIO for each sample
const pwm_cmd_t silence_cmd = {SILENCE_CMD, SILENCE_CMD, SILENCE_CMD,
#ifdef AUDIO_HALF_FREQ
SILENCE_CMD, SILENCE_CMD, SILENCE_CMD,
#endif
};
#else
#define CYCLES_PER_SAMPLE 16
#ifndef AUDIO_HALF_FREQ
typedef uint32_t pwm_cmd_t; // what we send to PIO for each sample
const pwm_cmd_t silence_cmd = SILENCE_CMD;
#else
typedef struct {
uint32_t a;
uint32_t b;
} pwm_cmd_t; // what we send to PIO for each sample
const pwm_cmd_t silence_cmd = { SILENCE_CMD, SILENCE_CMD };
#endif
#define DITHER_BITS 1u
#endif
static_assert(CYCLES_PER_SAMPLE % DITHER_BITS == 0, "");
#define CYCLES_PER_WORD (CYCLES_PER_SAMPLE / DITHER_BITS)
#ifndef AUDIO_HALF_FREQ
#define OUTER_LOOP_COUNT DITHER_BITS
#else
#define OUTER_LOOP_COUNT DITHER_BITS*2
#endif
#define FRACTIONAL_LSB 0u
#define FRACTIONAL_MSB (FRACTIONAL_LSB + FRACTIONAL_BITS - 1u)
#define FRACTIONAL_MASK ((1u << FRACTIONAL_BITS) - 1u)
#define QUANTIZED_LSB FRACTION_BITS
#define QUANTIZED_MSB (QUANTIZED_LSB + QUANTIZED_BITS - 1u)
#define QUANTIZED_MAX ((1u << QUANTIZED_BITS) - 1u)
#define QUANTIZED_MASK QUANTIZED_MAX
void producer_pool_blocking_give_to_pwm_s16(audio_connection_t *connection, audio_buffer_t *buffer);
void producer_pool_blocking_give_to_pwm_s8(audio_connection_t *connection, audio_buffer_t *buffer);
void producer_pool_blocking_give_to_pwm_u16(audio_connection_t *connection, audio_buffer_t *buffer);
void producer_pool_blocking_give_to_pwm_u8(audio_connection_t *connection, audio_buffer_t *buffer);
#ifdef __cplusplus
}
#endif
#endif //SOFTWARE_SAMPLE_ENCODING_H

Wyświetl plik

@ -0,0 +1,427 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include "pico/sample_conversion.h"
#include "pico/audio_pwm/sample_encoding.h"
#include "pico/audio_pwm.h"
#include "hardware/gpio.h"
#include "hardware/interp.h"
CU_REGISTER_DEBUG_PINS(encoding)
//CU_SELECT_DEBUG_PINS(encoding)
#ifndef PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE
#define PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE dither
#endif
static enum audio_correction_mode audio_correction_mode = PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE;
struct FmtPWM : public FmtDetails<pwm_cmd_t> {
};
bool audio_pwm_set_correction_mode(enum audio_correction_mode mode)
{
if (mode == none || mode == dither || mode == fixed_dither)
{
audio_correction_mode = mode;
return true;
}
#ifdef ENABLE_NOISE_SHAPING
if (mode == noise_shaped_dither) {
audio_correction_mode = mode;
return true;
}
#endif
return false;
}
enum audio_correction_mode audio_pwm_get_correction_mode()
{
return audio_correction_mode;
}
template <typename FromFmt> void
__no_inline_not_in_flash_func(encode_samples_none)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded)
{
const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count;
// hacky cast to allow use to DITHER_BITS > 1
uint32_t *e = (uint32_t *) encoded;
#if !PIO_AUDIO_PWM_NO_INTERP_SAVE
interp_hw_save_t saver;
interp_save(interp0, &saver);
#endif
// interp_configure_with_signed(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1);
interp_config config = interp_default_config();
interp_config_set_signed(&config, true);
interp_config_set_shift(&config, FRACTIONAL_BITS);
interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1);
interp_set_config(interp0, 0, &config);
interp0->base[0] = 0x8000u >> FRACTIONAL_BITS;
while (s < s_end)
{
// accum = signed_sample_16
interp0->accum[0] = sample_converter<FmtS16, FromFmt>::convert_sample(*s);
// quant = ((0x8000 + signed_sample_16) >> FRACTIONAL_BITS) & QUANTIZED_MASK
uint32_t quant = interp0->pop[0];
assert(quant >= 0 && quant <= 127);
uint32_t cmd = MAKE_CMD(quant);
for(uint k = 0; k < OUTER_LOOP_COUNT; k++)
{
*e++ = cmd;
}
s += FromFmt::channel_count;
}
#if !PIO_AUDIO_PWM_NO_INTERP_SAVE
interp_restore(interp0, &saver);
#endif
}
//void gen_fixed_dither()
//{
// for(int i = 0; i < 16; i++)
// {
// printf("0b");
// int e = 0;
// for(int j = 0; j < 15; j++)
// {
// e += i;
// if (e >= 16)
// {
// printf("1");
// e -= 16;
// }
// else
// {
// printf("0");
// }
// }
// printf("\n");
// }
//
// for(int i = 0; i < 16; i++)
// {
// printf("0b");
// int e = 0;
// for(int j = 0; j < 15; j++)
// {
// e += i;
// if (e >= 16)
// {
// printf("100");
// e -= 16;
// }
// else
// {
// printf("000");
// }
// if (j == 4 || j == 9)
// {
// printf(", 0b");
// }
// }
// printf("\n");
// }
#if DITHER_BITS == 3
#define FIXED_DITHER_SHIFT 2
// note 4 not 3 which wastes 16 bytes, but allows interpolator to handle address
static uint32_t fixed_dither_table[16*(1<<FIXED_DITHER_SHIFT)] = {
0b000000000000000 << CMD_BITS, 0b000000000000000 << CMD_BITS, 0b000000000000000 << CMD_BITS, 0 << CMD_BITS,
0b000000000000000 << CMD_BITS, 0b000000000000000 << CMD_BITS, 0b000000000000000 << CMD_BITS, 0 << CMD_BITS,
0b000000000000000 << CMD_BITS, 0b000000100000000 << CMD_BITS, 0b000000000000000 << CMD_BITS, 0 << CMD_BITS,
0b000000000000000 << CMD_BITS, 0b100000000000000 << CMD_BITS, 0b100000000000000 << CMD_BITS, 0 << CMD_BITS,
0b000000000100000 << CMD_BITS, 0b000000100000000 << CMD_BITS, 0b000100000000000 << CMD_BITS, 0 << CMD_BITS,
0b000000000100000 << CMD_BITS, 0b000100000000100 << CMD_BITS, 0b000000100000000 << CMD_BITS, 0 << CMD_BITS,
0b000000100000000 << CMD_BITS, 0b100000100000000 << CMD_BITS, 0b100000000100000 << CMD_BITS, 0 << CMD_BITS,
0b000000100000100 << CMD_BITS, 0b000100000000100 << CMD_BITS, 0b000100000100000 << CMD_BITS, 0 << CMD_BITS,
0b000100000100000 << CMD_BITS, 0b100000100000100 << CMD_BITS, 0b000100000100000 << CMD_BITS, 0 << CMD_BITS,
0b000100000100000 << CMD_BITS, 0b100000100100000 << CMD_BITS, 0b100000100000100 << CMD_BITS, 0 << CMD_BITS,
0b000100000100100 << CMD_BITS, 0b000100100000100 << CMD_BITS, 0b000100100000100 << CMD_BITS, 0 << CMD_BITS,
0b000100100000100 << CMD_BITS, 0b100000100100000 << CMD_BITS, 0b100100000100100 << CMD_BITS, 0 << CMD_BITS,
0b000100100100000 << CMD_BITS, 0b100100100000100 << CMD_BITS, 0b100100000100100 << CMD_BITS, 0 << CMD_BITS,
0b000100100100100 << CMD_BITS, 0b000100100100100 << CMD_BITS, 0b000100100100100 << CMD_BITS, 0 << CMD_BITS,
0b000100100100100 << CMD_BITS, 0b100100100000100 << CMD_BITS, 0b100100100100100 << CMD_BITS, 0 << CMD_BITS,
0b000100100100100 << CMD_BITS, 0b100100100100100 << CMD_BITS, 0b100100100100100 << CMD_BITS, 0 << CMD_BITS,
};
#elif DITHER_BITS == 1
#define FIXED_DITHER_SHIFT 0
static uint32_t fixed_dither_table[16*(1<<FIXED_DITHER_SHIFT)] = {
0b000000000000000 << CMD_BITS,
0b000000000000000 << CMD_BITS,
0b000000010000000 << CMD_BITS,
0b000001000010000 << CMD_BITS,
0b000100010001000 << CMD_BITS,
0b000100100100100 << CMD_BITS,
0b001001010010010 << CMD_BITS,
0b001010100101010 << CMD_BITS,
0b010101010101010 << CMD_BITS,
0b010101011010101 << CMD_BITS,
0b010110110101101 << CMD_BITS,
0b011011011011011 << CMD_BITS,
0b011101110111011 << CMD_BITS,
0b011110111101111 << CMD_BITS,
0b011111110111111 << CMD_BITS,
0b011111111111111 << CMD_BITS,
};
#else
#error
#endif
template <typename FromFmt> void
__no_inline_not_in_flash_func(encode_samples_fixed_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded)
{
const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count;
// hacky cast to allow use to DITHER_BITS > 1
uint32_t *e = (uint32_t *) encoded;
#if !PIO_AUDIO_PWM_NO_INTERP_SAVE
interp_hw_save_t saver;
interp_save(interp0, &saver);
#endif
//interp_configure_with_signed(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1);
interp_config config = interp_default_config();
interp_config_set_shift(&config, FRACTIONAL_BITS);
interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1);
interp_config_set_signed(&config, true);
interp_set_config(interp0, 0, &config);
// interp_configure_with_cross_input(interp0, 1, FRACTIONAL_BITS - FIXED_DITHER_SHIFT - 6, FIXED_DITHER_SHIFT + 2, FIXED_DITHER_SHIFT + 5);
config = interp_default_config();
interp_config_set_shift(&config, FRACTIONAL_BITS - FIXED_DITHER_SHIFT - 6);
interp_config_set_mask(&config, FIXED_DITHER_SHIFT + 2, FIXED_DITHER_SHIFT + 5);
interp_config_set_cross_input(&config, true);
interp_set_config(interp0, 1, &config);
interp0->base[0] = 0x8000u >> FRACTIONAL_BITS;
interp0->base[1] = (uintptr_t)fixed_dither_table;
static int16_t error = 0;
while (s < s_end)
{
// accum = signed_sample_16
interp0->accum[0] = sample_converter<FmtS16, FromFmt>::convert_sample(*s) + error;
uint32_t *fdt = (uint32_t *)interp0->peek[1];
uint32_t quant = interp0->pop[0];
assert(quant >= 0 && quant <= 127);
uint32_t cmd = MAKE_CMD(quant);
for(uint k = 0; k < OUTER_LOOP_COUNT; k++)
{
*e++ = cmd | fdt[k];
}
s += FromFmt::channel_count;
}
#if !PIO_AUDIO_PWM_NO_INTERP_SAVE
interp_restore(interp0, &saver);
#endif
}
template <typename FromFmt> void
__no_inline_not_in_flash_func(encode_samples_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded)
{
static_assert(DITHER_BITS > 0 && DITHER_BITS <= 3, "");
const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count;
// hacky cast to allow us to DITHER_BITS > 1
uint32_t *e = (uint32_t *) encoded;
#if PICO_AUDIO_PWM_INTERP_SAVE
interp_hw_save_t saver;
interp_save(interp0, &saver);
#endif
// interp_configure_with_signed_and_cross_result(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_MASK - 1);
interp_config config = interp_default_config();
interp_config_set_shift(&config, FRACTIONAL_BITS);
interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1);
interp_config_set_signed(&config, true);
interp_config_set_cross_result(&config, true);
interp_set_config(interp0, 0, &config);
// interp_configure_with_cross_input(interp0, 1, 0, 0, FRACTIONAL_BITS - 1);
config = interp_default_config();
interp_config_set_mask(&config, 0, FRACTIONAL_BITS - 1);
interp_config_set_cross_input(&config, true);
interp_set_config(interp0, 1, &config);
interp0->base[0] = 0;
int32_t last_sample_error = 0;
static uint32_t saved_error = 0;
// accum 0 is the error
interp0->accum[0] = saved_error;
while (s < s_end)
{
uint32_t sample = sample_converter<FmtU16, FromFmt>::convert_sample(*s);
// we will be adding this sample error to accumulated error each time in the super sample loop
uint32_t sample_error = sample & FRACTIONAL_MASK;
interp0->base[1] = sample_error;
// adjust the accumulated_error from (last_sample_error + error) to (sample_error + error)
// because the error is one cycle ahead of the loop (i.e. we need to have added the sample error once before
// the first loop)
interp0->add_raw[0] = sample_error - last_sample_error;
last_sample_error = sample_error;
uint32_t quant0 =
(sample >> FRACTIONAL_BITS) & QUANTIZED_MASK; // can't use interp here since accumulator has error in it
assert(quant0 >= 0 && quant0 <= QUANTIZED_MAX);
for(uint k = 0; k < OUTER_LOOP_COUNT; k++)
{
uint32_t cmd = MAKE_CMD(quant0);
uint32_t bit = CMD_BITS + DITHER_BITS - 1;
for(uint j = 0; j < CYCLES_PER_WORD; j++)
{
// accumulated_error (accum[0]) = previous_accumulated_error + sample_error - note this was done ahead of this iteration
// quant (result[0]) = (accumulated_error) >> FRACTIONAL_BITS) & QUANTIZED_MASK;
// accumulated_error (result[1]->accum[0]) = (previous_accumulated_error + sample_error) & FRACTIONAL_MASK;
uint32_t quant = interp0->pop[0];
// we can only dither 0 or +1
assert(quant == 0 || quant == 1);
if (!!quant)
cmd |= 1 << bit;
bit += DITHER_BITS;
}
*e++ = cmd;
}
s += FromFmt::channel_count;
}
saved_error = interp0->accum[0] - last_sample_error;
#if PICO_AUDIO_PWM_INTERP_SAVE
interp_restore(interp0, &saver);
#endif
}
#ifdef ENABLE_NOISE_SHAPING
static uint8_t shape_bits[4] = { 0b000, 0b100, 0b110, 0b111 };
template <typename FromFmt> void
__no_inline_not_in_flash_func(encode_samples_noise_shaped_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded) {
static_assert(DITHER_BITS <= 3, "");
const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count;
// hacky cast to allow us to DITHER_BITS > 1
uint32_t * e = (uint32_t *)encoded;
#if PICO_AUDIO_PWM_INTERP_SAVE
interp_hw_save_t saver;
interp_save(interp0, &saver);
#endif
interp_configure_with_signed_and_cross_result(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1);
interp_configure_with_cross_input(interp0, 1, 0, 0, FRACTIONAL_BITS - 1);
// we offset one to the quantized result because the error is between -1 and 2, giving us a range of 0 to 3
// todo i keep being tempted to make this zero and remove the -1 offset from quant0 but this does not work
// so i need to comment why... I believe this corrects something we later double to be zero based not -1 based
interp0->base[0] = 1;
int32_t last_sample_error = 0;
static uint32_t saved_error = 0, previous_accumulated_error = 0;
interp0->accum[0] = saved_error;
while (s < s_end)
{
uint32_t sample = sample_converter<Mono<FmtU16>, FromFmt>::convert_sample(s);
// we will be adding this sample error to accumulated error each time in the super sample loop
uint32_t sample_error = sample & FRACTIONAL_MASK;
interp0->base[1] = sample_error;
// adjust the accumulated_error from (last_sample_error + error) to (sample_error + error)
// because the error is one cycle ahead of the loop (i.e. we need to have added the sample error once before
// the first loop)
interp0->add_raw[0] = sample_error - last_sample_error;
last_sample_error = sample_error;
uint32_t quant0 = ((sample >> FRACTIONAL_BITS) - 1u) & QUANTIZED_MASK; // can't use interp here since accumulator has error in it
// todo clearly this could be a problem for too high a volume!
assert(quant0 >= 0 && quant0 <= QUANTIZED_MAX);
for(uint k=0; k < OUTER_LOOP_COUNT; k++) {
uint32_t cmd = MAKE_CMD(quant0);
uint base_bit = CMD_BITS;
for (uint j = 0; j < CYCLES_PER_WORD; j++) {
// accumulated_error (accum[0]) = previous_accumulated_error + sample_error - note this was done ahead of this iteration
uint32_t accumulated_error = interp0->add_raw[1];
// quant (result[0]) = 1 + (accumulated_error) >> FRACTIONAL_BITS) & QUANTIZED_MASK;
// accumulated_error (result[1]->accum[0]) = (previous_accumulated_error + sample_error) & FRACTIONAL_MASK;
uint32_t quant = interp0->pop[0];
// accumulated_error += accumulated_error - previous_accumulated_error
// i.e. accumulated_error = ((previous_accumulated_error + sample_error) & FRACTIONAL_MASK) * 2 - previous_accumulated_error
// which is the noise shaping
interp0->add_raw[0] = accumulated_error - previous_accumulated_error;
previous_accumulated_error = accumulated_error;
assert (quant >=0 && quant <= 3);
cmd |= shape_bits[quant] << base_bit;
base_bit += DITHER_BITS;
}
*e++ = cmd;
}
s += FromFmt::channel_count;
}
saved_error = interp0->accum[0] - last_sample_error;
#if PICO_AUDIO_PWM_INTERP_SAVE
interp_restore(interp0, &saver);
#endif
}
#endif
// encoding converter
template<typename FromFmt> struct converting_copy<FmtPWM,FromFmt> {
static void copy(typename FmtPWM::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
DEBUG_PINS_SET(encoding, 1);
switch (audio_correction_mode)
{
case dither:
encode_samples_dither<FromFmt>(sample_count, src, dest);
break;
case fixed_dither:
encode_samples_fixed_dither<FromFmt>(sample_count, src, dest);
break;
#ifdef ENABLE_NOISE_SHAPING
case noise_shaped_dither:
encode_samples_noise_shaped_dither<FromFmt>(sample_count, src, dest);
break;
#endif
default:
encode_samples_none<FromFmt>(sample_count, src, dest);
break;
}
DEBUG_PINS_CLR(encoding, 1);
}
};
void producer_pool_blocking_give_to_pwm_s16(audio_connection_t *connection, audio_buffer_t *buffer)
{
producer_pool_blocking_give<FmtPWM, FmtS16>(connection, buffer);
}
void producer_pool_blocking_give_to_pwm_s8(audio_connection_t *connection, audio_buffer_t *buffer)
{
producer_pool_blocking_give<FmtPWM, FmtS8>(connection, buffer);
}
void producer_pool_blocking_give_to_pwm_u16(audio_connection_t *connection, audio_buffer_t *buffer)
{
producer_pool_blocking_give<FmtPWM, FmtU16>(connection, buffer);
}
void producer_pool_blocking_give_to_pwm_u8(audio_connection_t *connection, audio_buffer_t *buffer)
{
producer_pool_blocking_give<FmtPWM, FmtU8>(connection, buffer);
}

Wyświetl plik

@ -0,0 +1,13 @@
if (NOT TARGET pico_audio_spdif)
add_library(pico_audio_spdif INTERFACE)
pico_generate_pio_header(pico_audio_spdif ${CMAKE_CURRENT_LIST_DIR}/audio_spdif.pio)
target_sources(pico_audio_spdif INTERFACE
${CMAKE_CURRENT_LIST_DIR}/audio_spdif.c
${CMAKE_CURRENT_LIST_DIR}/sample_encoding.cpp
)
target_include_directories(pico_audio_spdif INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_audio_spdif INTERFACE hardware_dma hardware_pio hardware_irq pico_audio)
endif()

Wyświetl plik

@ -0,0 +1,378 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/audio_spdif.h"
#include <pico/audio_spdif/sample_encoding.h>
#include "audio_spdif.pio.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/clocks.h"
CU_REGISTER_DEBUG_PINS(audio_timing)
// ---- select at most one ---
//CU_SELECT_DEBUG_PINS(audio_timing)
#define audio_pio __CONCAT(pio, PICO_AUDIO_SPDIF_PIO)
#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_SPDIF_PIO)
#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_SPDIF_PIO), _TX0)
#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_SPDIF_DMA_IRQ)
#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_SPDIF_DMA_IRQ),_enabled)
#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_SPDIF_DMA_IRQ)
struct {
audio_buffer_t *playing_buffer;
uint32_t freq;
uint8_t pio_sm;
uint8_t dma_channel;
} shared_state;
static audio_format_t pio_spdif_consumer_format;
audio_buffer_format_t pio_spdif_consumer_buffer_format = {
.format = &pio_spdif_consumer_format,
};
static audio_buffer_t silence_buffer = {
.sample_count = PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT,
.max_sample_count = PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT,
.format = &pio_spdif_consumer_buffer_format
};
static void __isr __time_critical_func(audio_spdif_dma_irq_handler)();
const audio_spdif_config_t audio_spdif_default_config = {
.pin = PICO_AUDIO_SPDIF_PIN,
.pio_sm = 0,
.dma_channel = 0,
};
#define SR_44100 0
#define SR_48000 1
#define PREAMBLE_X 0b11001001
#define PREAMBLE_Y 0b01101001
#define PREAMBLE_Z 0b00111001
#define SPDIF_CONTROL_WORD (\
0x4 | /* copying allowed */ \
0x20 | /* PCM encoder/decoder */ \
(SR_44100 << 24) /* todo is this required */ \
)
// each buffer is pre-filled with data
static void init_spdif_buffer(audio_buffer_t *buffer) {
// BIT DESCRIPTIONS:
// 0–3 Preamble A synchronisation preamble (biphase mark code violation) for audio blocks, frames, and subframes.
// 4–7 Auxiliary sample (optional) A low-quality auxiliary channel used as specified in the channel status word, notably for producer talkback or recording studio-to-studio communication.
// 8–27, or 4–27 Audio sample. One sample stored with most significant bit (MSB) last. If the auxiliary sample is used, bits 4–7 are not included. Data with smaller sample bit depths always have MSB at bit 27 and are zero-extended towards the least significant bit (LSB).
// 28 Validity (V) Unset if the audio data are correct and suitable for D/A conversion. During the presence of defective samples, the receiving equipment may be instructed to mute its output. It is used by most CD players to indicate that concealment rather than error correction is taking place.
// 29 User data (U) Forms a serial data stream for each channel (with 1 bit per frame), with a format specified in the channel status word.
// 30 Channel status (C) Bits from each frame of an audio block are collated giving a 192-bit channel status word. Its structure depends on whether AES3 or S/PDIF is used.
// 31 Parity (P)
// We want to pre-encode (in our fixed length 192 buffers), the
// * Preamble
// * Aux (0)
// * Low4 (0)
//
// * V(0)
// * U(0)
// * C(0) (or from SPDIF_CONTROL_WORD in the first 32)
// note everything is encoded in NRZI
// regular data bits are encoded
// 0 -> 10 (LSB first)
// 1 -> 11
assert(buffer->max_sample_count == PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT);
spdif_subframe_t *p = (spdif_subframe_t *)buffer->buffer->bytes;
for(uint i=0;i<PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT;i++) {
uint c_bit = i < 32 ? (SPDIF_CONTROL_WORD >> i) & 1u: 0;
// p->l = (i ? PREAMBLE_X : PREAMBLE_Z) | 0b10101010101010100000000 | 0x55000000;
// p->h = 0x55000000u | (c_bit << 25u) | 0x0055555555;
// p++;
// p->l = PREAMBLE_Y | 0b10101010101010100000000 | 0x55000000;
// p->h = 0x55000000u | (c_bit << 25u) | 0x0055555555;
// p++;
p->l = (i ? PREAMBLE_X : PREAMBLE_Z) | 0b10101010101010100000000;
p->h = 0x55000000u | (c_bit << 25u);
p++;
p->l = PREAMBLE_Y | 0b10101010101010100000000;
p->h = 0x55000000u | (c_bit << 25u);
p++;
}
}
uint32_t spdif_lookup[256];
const audio_format_t *audio_spdif_setup(const audio_format_t *intended_audio_format,
const audio_spdif_config_t *config) {
for(uint i=0;i<256;i++) {
uint32_t v = 0x5555;
uint p = 0;
for(uint j = 0; j<8; j++) {
if (i & (1<<j)) {
p ^= 1;
v |= (2<<(j*2));
}
}
spdif_lookup[i] = v | (p << 16u);
}
uint func = GPIO_FUNC_PIOx;
gpio_set_function(config->pin, func);
uint8_t sm = shared_state.pio_sm = config->pio_sm;
pio_sm_claim(audio_pio, sm);
uint offset = pio_add_program(audio_pio, &audio_spdif_program);
spdif_program_init(audio_pio, sm, offset, config->pin);
silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT * 2 * sizeof(spdif_subframe_t));
init_spdif_buffer(&silence_buffer);
spdif_subframe_t *sf = (spdif_subframe_t *)silence_buffer.buffer->bytes;
for(uint i=0;i<silence_buffer.sample_count;i++) {
spdif_update_subframe(sf++, 0);
spdif_update_subframe(sf++, 0);
}
__mem_fence_release();
uint8_t dma_channel = config->dma_channel;
dma_channel_claim(dma_channel);
shared_state.dma_channel = dma_channel;
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
channel_config_set_dreq(&dma_config,
DREQ_PIOx_TX0 + sm
);
dma_channel_configure(dma_channel,
&dma_config,
&audio_pio->txf[sm], // dest
NULL, // src
0, // count
false // trigger
);
irq_add_shared_handler(DMA_IRQ_x, audio_spdif_dma_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
dma_channel_set_irqx_enabled(dma_channel, 1);
return intended_audio_format;
}
static audio_buffer_pool_t *audio_spdif_consumer;
static void update_pio_frequency(uint32_t sample_freq) {
printf("setting pio freq %d\n", (int) sample_freq);
uint32_t system_clock_frequency = clock_get_hz(clk_sys);
assert(system_clock_frequency < 0x40000000);
// coincidentally * 256 (for 8 bit fraction) / 2 (channels) * 32 (bits) * 2 (time periods) * 2 cycles per time period)
uint32_t divider = system_clock_frequency / sample_freq;
printf("System clock at %u, S/PDIF clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider);
assert(divider < 0x1000000);
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
shared_state.freq = sample_freq;
}
static audio_buffer_t *wrap_consumer_take(audio_connection_t *connection, bool block) {
// support dynamic frequency shifting
if (connection->producer_pool->format->sample_freq != shared_state.freq) {
update_pio_frequency(connection->producer_pool->format->sample_freq);
}
return consumer_pool_take_buffer_default(connection, block);
}
static void wrap_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
if (buffer->format->format->format == AUDIO_BUFFER_FORMAT_PCM_S16) {
#if PICO_AUDIO_SPDIF_MONO_INPUT
mono_to_spdif_producer_give(connection, buffer);
#else
stereo_to_spdif_producer_gibe(connection, buffer);
#endif
} else {
panic_unsupported();
}
}
static struct producer_pool_blocking_give_connection m2s_audio_spdif_connection = {
.core = {
.consumer_pool_take = wrap_consumer_take,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = wrap_producer_give,
}
};
bool audio_spdif_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection) {
return audio_spdif_connect_extra(producer, true, 2, connection);
}
bool audio_spdif_connect(audio_buffer_pool_t *producer) {
return audio_spdif_connect_thru(producer, NULL);
}
bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
audio_connection_t *connection) {
printf("Connecting PIO S/PDIF audio\n");
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S16);
pio_spdif_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_SPDIF;
pio_spdif_consumer_format.sample_freq = producer->format->sample_freq;
pio_spdif_consumer_format.channel_count = 2;
pio_spdif_consumer_buffer_format.sample_stride = 2 * sizeof(spdif_subframe_t);
audio_spdif_consumer = audio_new_consumer_pool(&pio_spdif_consumer_buffer_format, buffer_count, PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT);
for (audio_buffer_t *buffer = audio_spdif_consumer->free_list; buffer; buffer = buffer->next) {
init_spdif_buffer(buffer);
}
update_pio_frequency(producer->format->sample_freq);
// todo cleanup threading
__mem_fence_release();
if (!connection) {
if (producer->format->channel_count == 2) {
#if PICO_AUDIO_SPDIF_MONO_INPUT
panic("need to merge channels down\n");
#else
printf("Copying stereo to stereo at %d Hz\n", (int) producer->format->sample_freq);
#endif
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
} else {
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
}
connection = &m2s_audio_spdif_connection.core;
}
audio_complete_connection(connection, producer, audio_spdif_consumer);
return true;
}
static struct buffer_copying_on_consumer_take_connection m2s_audio_spdif_connection_s8 = {
.core = {
.consumer_pool_take = wrap_consumer_take,
.consumer_pool_give = consumer_pool_give_buffer_default,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = wrap_producer_give,
}
};
bool audio_spdif_connect_s8(audio_buffer_pool_t *producer) {
panic_unsupported(); // needs fixing up
printf("Connecting PIO S/PDIF audio (U8)\n");
// todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100
assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S8);
pio_spdif_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_SPDIF;
// todo we could do mono
// todo we can't match exact, so we should return what we can do
pio_spdif_consumer_format.sample_freq = producer->format->sample_freq;
pio_spdif_consumer_format.channel_count = 2;
pio_spdif_consumer_buffer_format.sample_stride = 2 * sizeof(spdif_subframe_t);
// we do this on take so should do it quickly...
uint samples_per_buffer = 256;
// todo with take we really only need 1 buffer
audio_spdif_consumer = audio_new_consumer_pool(&pio_spdif_consumer_buffer_format, 2, samples_per_buffer);
// todo we need a method to calculate this in clocks
uint32_t system_clock_frequency = 48000000;
// uint32_t divider = system_clock_frequency * 256 / producer->format->sample_freq * 16 * 4;
uint32_t divider = system_clock_frequency * 4 / producer->format->sample_freq; // avoid arithmetic overflow
pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu);
// todo cleanup threading
__mem_fence_release();
audio_connection_t *connection;
if (producer->format->channel_count == 2) {
// todo we should support pass thru option anyway
printf("TODO... not completing stereo audio connection properly!\n");
connection = &m2s_audio_spdif_connection_s8.core;
} else {
printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq);
connection = &m2s_audio_spdif_connection_s8.core;
}
audio_complete_connection(connection, producer, audio_spdif_consumer);
return true;
}
static inline void audio_start_dma_transfer() {
assert(!shared_state.playing_buffer);
audio_buffer_t *ab = take_audio_buffer(audio_spdif_consumer, false);
shared_state.playing_buffer = ab;
if (!ab) {
DEBUG_PINS_XOR(audio_timing, 1);
DEBUG_PINS_XOR(audio_timing, 2);
DEBUG_PINS_XOR(audio_timing, 1);
//DEBUG_PINS_XOR(audio_timing, 2);
// just play some silence
ab = &silence_buffer;
}
assert(ab->sample_count);
// todo better naming of format->format->format!!
assert(ab->format->format->format == AUDIO_BUFFER_FORMAT_PIO_SPDIF);
assert(ab->format->format->channel_count == 2);
assert(ab->format->sample_stride == 2 * sizeof(spdif_subframe_t));
dma_channel_transfer_from_buffer_now(shared_state.dma_channel, ab->buffer->bytes, ab->sample_count * 4);
}
// irq handler for DMA
void __isr __time_critical_func(audio_spdif_dma_irq_handler)() {
#if PICO_AUDIO_SPDIF_NOOP
assert(false);
#else
uint dma_channel = shared_state.dma_channel;
if (dma_intsx & (1u << dma_channel)) {
dma_intsx = 1u << dma_channel;
DEBUG_PINS_SET(audio_timing, 4);
// free the buffer we just finished
if (shared_state.playing_buffer) {
give_audio_buffer(audio_spdif_consumer, shared_state.playing_buffer);
#ifndef NDEBUG
shared_state.playing_buffer = NULL;
#endif
}
audio_start_dma_transfer();
DEBUG_PINS_CLR(audio_timing, 4);
}
#endif
}
static bool audio_enabled;
void audio_spdif_set_enabled(bool enabled) {
if (enabled != audio_enabled) {
#ifndef NDEBUG
if (enabled)
{
puts("Enabling PIO S/PDIF audio\n");
printf("(on core %d\n", get_core_num());
}
#endif
irq_set_enabled(DMA_IRQ_x, enabled);
if (enabled) {
audio_start_dma_transfer();
}
pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled);
audio_enabled = enabled;
}
}

Wyświetl plik

@ -0,0 +1,28 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
// Strictly this is NRZI decoder
.program audio_spdif
.side_set 1
public output_low:
out x, 1 side 0
jmp !x, output_low side 0
output_high:
out x, 1 side 1
jmp !x, output_high side 1
% c-sdk {
void spdif_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config sm_config = audio_spdif_program_get_default_config(offset);
sm_config_set_out_shift(&sm_config, true, true, 32);
sm_config_set_sideset(&sm_config, 1, false, false);
sm_config_set_sideset_pins(&sm_config, pin);
pio_sm_init(pio, sm, offset, &sm_config);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_set_pins(pio, sm, 0);
pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_spdif_offset_output_low));
}
%}

Wyświetl plik

@ -0,0 +1,163 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_SPDIF_H
#define _PICO_AUDIO_SPDIF_H
#include "pico/audio.h"
/** \file audio_spdif.h
* \defgroup pico_audio_spdif pico_audio_spdif
* S/PDIF audio output using the PIO
*
* This library uses the \ref pio system to implement a S/PDIF audio interface
*
* \todo Must be more we need to say here.
* \todo certainly need an example
*
*/
#ifdef __cplusplus
extern "C" {
#endif
#ifndef PICO_AUDIO_SPDIF_DMA_IRQ
#ifdef PICO_AUDIO_DMA_IRQ
#define PICO_AUDIO_SPDIF_DMA_IRQ PICO_AUDIO_DMA_IRQ
#else
#define PICO_AUDIO_SPDIF_DMA_IRQ 0
#endif
#endif
#ifndef PICO_AUDIO_SPDIF_PIO
#ifdef PICO_AUDIO_PIO
#define PICO_AUDIO_SPDIF_PIO PICO_AUDIO_PIO
#else
#define PICO_AUDIO_SPDIF_PIO 0
#endif
#endif
#if !(PICO_AUDIO_SPDIF_DMA_IRQ == 0 || PICO_AUDIO_SPDIF_DMA_IRQ == 1)
#error PICO_AUDIO_SPDIF_DMA_IRQ must be 0 or 1
#endif
#if !(PICO_AUDIO_SPDIF_PIO == 0 || PICO_AUDIO_SPDIF_PIO == 1)
#error PICO_AUDIO_SPDIF_PIO ust be 0 or 1
#endif
#ifndef PICO_AUDIO_SPDIF_MAX_CHANNELS
#ifdef PICO_AUDIO_MAX_CHANNELS
#define PICO_AUDIO_SPDIF_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS
#else
#define PICO_AUDIO_SPDIF_MAX_CHANNELS 2u
#endif
#endif
#ifndef PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL
#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL
#define PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL
#else
#define PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL 3u
#endif
#endif
// fixed by S/PDIF
#define PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT 192u
// Allow use of pico_audio driver without actually doing anything much
#ifndef PICO_AUDIO_SPDIF_NOOP
#ifdef PICO_AUDIO_NOOP
#define PICO_AUDIO_SPDIF_NOOP PICO_AUDIO_NOOP
#else
#define PICO_AUDIO_SPDIF_NOOP 0
#endif
#endif
#ifndef PICO_AUDIO_SPDIF_MONO_INPUT
#define PICO_AUDIO_SPDIF_MONO_INPUT 0
#endif
#ifndef PICO_AUDIO_SPDIF_PIN
//#warning PICO_AUDIO_SPDIF_PIN should be defined when using AUDIO_SPDIF
#define PICO_AUDIO_SPDIF_PIN 0
#endif
#define AUDIO_BUFFER_FORMAT_PIO_SPDIF 1300
// todo this needs to come from a build config
/** \brief Base configuration structure used when setting up
* \ingroup audio_spdif
*/
typedef struct audio_spdif_config {
uint8_t pin;
uint8_t dma_channel;
uint8_t pio_sm;
} audio_spdif_config_t;
extern const audio_spdif_config_t audio_spdif_default_config;
/** \brief Set up system to output S/PDIF audio
* \ingroup audio_spdif
*
* \param intended_audio_format \todo
* \param config The configuration to apply.
*/
const audio_format_t *audio_spdif_setup(const audio_format_t *intended_audio_format,
const audio_spdif_config_t *config);
/** \brief \todo
* \ingroup audio_spdif
*
* \param producer
* \param connection
*/
bool audio_spdif_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection);
/** \brief \todo
* \ingroup audio_spdif
*
* \param producer
*/
bool audio_spdif_connect(audio_buffer_pool_t *producer);
/** \brief \todo
* \ingroup audio_spdif
*
* \param producer
*/
bool audio_spdif_connect_s8(audio_buffer_pool_t *producer);
bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
audio_connection_t *connection);
/** \brief \todo
* \ingroup audio_spdif
*
* \param producer
* \param buffer_on_give
* \param buffer_count
* \param samples_per_buffer
* \param connection
* \return
*/
bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count,
audio_connection_t *connection);
/** \brief Set up system to output S/PDIF audio
* \ingroup audio_spdif
*
* \param enabled true to enable S/PDIF audio, false to disable.
*/
void audio_spdif_set_enabled(bool enabled);
#ifdef __cplusplus
}
#endif
#endif //_AUDIO_SPDIF_H

Wyświetl plik

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_AUDIO_SPDIF_SAMPLE_ENCODING_H
#define _PICO_AUDIO_SPDIF_SAMPLE_ENCODING_H
#include "pico/audio.h"
#ifdef __cplusplus
extern "C" {
#endif
void mono_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer);
void stereo_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer);
typedef struct {
uint32_t l;
uint32_t h;
} spdif_subframe_t;
extern uint32_t spdif_lookup[256];
static inline void spdif_update_subframe(spdif_subframe_t *subframe, int16_t sample) {
// the subframe is partially initialized, so we need to insert the sample
// bits and update the parity
uint32_t sl = spdif_lookup[(uint8_t)sample];
uint32_t sh = spdif_lookup[(uint8_t)(sample>>8u)];
subframe->l = (subframe->l & 0xffffffu) | (sl << 24u);
uint32_t ph = subframe->h >> 24u;
uint32_t h = (((uint16_t)sh) << 8u) |
(((uint16_t)sl) >> 8u);
uint32_t p = (sl>>16u)^(sh>>16u);
p = p ^ ((__mul_instruction(ph&0x2a,0x2a) >> 6u) & 1u);
subframe->h = h | ((ph&0x7f) << 24u) | (p << 31u);
}
#ifdef __cplusplus
}
#endif
#endif //SOFTWARE_SAMPLE_ENCODING_H

Wyświetl plik

@ -0,0 +1,51 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include "pico/sample_conversion.h"
#include "pico/audio_spdif/sample_encoding.h"
#include "pico/audio_spdif.h"
#include "hardware/gpio.h"
static_assert(8 == sizeof(spdif_subframe_t), "");
// subframe within SPDIF
typedef struct : public FmtDetails<spdif_subframe_t> {
} FmtSPDIF;
template<typename FromFmt>
struct converting_copy<Stereo<FmtSPDIF>, Stereo<FromFmt>> {
static void copy(FmtSPDIF::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
for (uint i = 0; i < sample_count * 2; i++) {
spdif_update_subframe(dest++, sample_converter<FmtS16 , FromFmt>::convert_sample(*src++));
}
}
};
template<typename FromFmt>
struct converting_copy<Stereo<FmtSPDIF>, Mono<FromFmt>> {
static void copy(FmtSPDIF::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) {
for (uint i = 0; i < sample_count; i++) {
int16_t sample = sample_converter<FmtS16 ,FromFmt>::convert_sample(*src++);
spdif_update_subframe(dest++, sample);
spdif_update_subframe(dest++, sample);
}
}
};
void stereo_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
producer_pool_blocking_give<Stereo<FmtSPDIF>, Stereo<FmtS16>>(connection, buffer);
}
void mono_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) {
producer_pool_blocking_give<Stereo<FmtSPDIF>, Mono<FmtS16>>(connection, buffer);
}

Wyświetl plik

@ -0,0 +1,16 @@
add_library(video_dbi INTERFACE)
# todo right now this is just a copy of video/
pico_generate_pio_header(video_dbi ${CMAKE_CURRENT_LIST_DIR}/video.pio)
pico_generate_pio_header(video_dbi ${CMAKE_CURRENT_LIST_DIR}/control.pio)
target_sources(video_dbi INTERFACE
${CMAKE_CURRENT_LIST_DIR}/video.h
${CMAKE_CURRENT_LIST_DIR}/vga_modes.c
$<$<BOOL:${UseHardware}>:${CMAKE_CURRENT_LIST_DIR}/tft_driver.c>
$<$<BOOL:${UseHardware}>:${CMAKE_CURRENT_LIST_DIR}/video_dbi.c>
)
target_compile_definitions(video_dbi INTERFACE VIDEO_DBI)
target_include_directories(video_dbi INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(video_dbi INTERFACE dma pio)

Wyświetl plik

@ -0,0 +1 @@
THIS IS CURRENTLY NOT COMPILING (A PLACEHOLDER THAT NEEDS FIXING)

Wyświetl plik

@ -0,0 +1,25 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program video_dbi_control
.define out_delay 1
.define clk_delay 1
.side_set 1 ; used for WR strobe
.extern data_run_out
out pins, 16 [out_delay] set 0
jmp x-- data_run_out [out_delay] set 1
.extern entry_point
.wrap_target
.extern new_state_wait
out exec, 16 set 1
out x, 11 set 1
out pc, 5 set 1
.extern clock_run
nop [clk_delay] set 0
jmp x-- clock_run [clk_delay] set 1
.wrap

Wyświetl plik

@ -0,0 +1,600 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "platform_defs_c.h"
#include "tft_driver.h"
#include "gpio.h"
#include "pio.h"
#include "control.pio.h"
#include "dma.h"
#include "debug.h"
CU_REGISTER_DEBUG_PINS(cmds)
//CU_SELECT_DEBUG_PINS(cmds)
#define MAKE_CMD(x, jmp) ((x) | ((jmp)<<11u))
//#define NO_SETUP
static struct __packed {
#ifndef NO_SETUP
uint16_t exec_csrs_l; //
uint16_t wr_1_cmd; //
uint16_t caset16; //
uint16_t exec_rs_h;
uint16_t wr_4_cmd; //
uint16_t x0_h;
uint16_t x0_l;
uint16_t x1_h;
uint16_t x1_l;
uint16_t exec_csrs_l_2; //
uint16_t wr_1_cmd_2; //
uint16_t paset16; //
uint16_t exec_rs_h_2; //
uint16_t wr_4_cmd_2; //
uint16_t y0_h;
uint16_t y0_l;
uint16_t y1_h;
uint16_t y1_l;
uint16_t exec_csrs_l_3; //
uint16_t wr_1_cmd_3; //
uint16_t ramwr16; //
uint16_t exec_rs_h_3; //
#else
uint16_t exec_rs_h;
#endif
uint16_t skip_cmd; //
uint16_t exec_set_irq4; //
uint16_t clk_n_cmd; //
uint16_t exec_csrs_h; //
uint16_t skip_cmd_2; //
uint16_t exec_set_irq0; //
uint16_t skip_cmd_3; //
#ifndef NO_SETUP
// to align on word boundary
uint16_t exec_skip; //
#endif
} __aligned(4) scanline_control_sequence;
static struct __packed {
uint16_t exec_csrs_l; //
uint16_t wr_1_cmd; //
// uint16_t ptlar16; //
// uint16_t exec_rs_h;
//
// uint16_t wr_4_cmd; //
// uint16_t y0_h;
//
// uint16_t y0_l;
// uint16_t y1_h;
//
// uint16_t y1_l;
// uint16_t exec_csrs_l_2; //
//
// uint16_t wr_1_cmd_2; //
uint16_t vscrsaddr16; //
uint16_t exec_rs_h_2;
uint16_t wr_2_cmd; //
uint16_t y_h;
uint16_t y_l;
uint16_t exec_csrs_h;
uint16_t skip_cmd;
uint16_t exec_skip;
} __aligned(4) switch_buffer_control_sequence;
//These define the ports and port bits used for the write, chip select (CS) and data/command (RS) lines
#define WR_L gpio_put(WR_PIN, false)
// We need a slower write strobe for the ILI9488
#ifdef ILI9486
#define ILI9481
#define WR_H ({gpio_put(WR_PIN, false); gpio_put(WR_PIN, true);})
#define WR_STB ({gpio_put(WR_PIN, false); gpio_put(WR_PIN, false); gpio_put(WR_PIN, true);})
#else
#define WR_H gpio_put(WR_PIN, true);
#define WR_STB gpio_put(WR_PIN, false); gpio_put(WR_PIN, true);
#endif
// Chip select must be toggled during setup
#define SETUP_CS_H gpio_put(CS_PIN, true)
#define SETUP_CS_L gpio_put(CS_PIN, false)
// Chip select can optionally be kept low after setup
#ifndef KEEP_CS_LOW
#define CS_H gpio_put(CS_PIN, true)
#define CS_L gpio_put(CS_PIN, false)
#else
#define CS_H // We do not define this so CS will not be set high
#define CS_L gpio_put(CS_PIN, false)
#endif
// If pin 4 is hard wired to pin 38 we benefit from all controls on PORTG
#ifdef FAST_RS
#define RS_L gpio_put(RS_PIN, false);
#define RS_H gpio_put(RS_PIN, true);
#else
#define RS_L gpio_put(RS_PIN, false)
#define RS_H gpio_put(RS_PIN, true)
#endif
static inline void digitalWrite(uint pin, bool value) {
gpio_put(pin, value);
}
static inline void set_data_pins16(uint16_t data) {
gpio_put_masked(0xffff, data);
}
static inline void set_data_pins8(uint8_t data) {
gpio_put_masked(0xff, data);
}
void writecommand(uint8_t c)
{
DEBUG_PINS_SET(cmds, 1);
SETUP_CS_L;
RS_L;
set_data_pins16(c);
WR_STB;
RS_H;
SETUP_CS_H;
DEBUG_PINS_CLR(cmds, 1);
}
/***************************************************************************************
** Function name: writedata
** Description: Send a 8 bit data value to the TFT
***************************************************************************************/
void writedata(uint8_t c)
{
SETUP_CS_L;
set_data_pins16(c);
WR_STB;
SETUP_CS_H;
}
#ifdef NO_SETUP
void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
//if((x1 >= _width) || (y1 >= _height)) return;
CS_L;
RS_L; set_data_pins8(HX8357_CASET); WR_STB; RS_H;
set_data_pins8(x0>>8); WR_STB;
set_data_pins8(x0); WR_STB;
set_data_pins8(x1>>8); WR_STB;
set_data_pins8(x1); WR_STB;
RS_L; set_data_pins8(HX8357_PASET); WR_STB; RS_H;
set_data_pins8(y0>>8); WR_STB;
set_data_pins8(y0); WR_STB;
set_data_pins8(y1>>8); WR_STB;
set_data_pins8(y1); WR_STB;
RS_L; set_data_pins8(HX8357_RAMWR); WR_STB; RS_H;
}
#endif
void tft_panel_init() {
// toggle RST low to reset
gpio_put(RST_PIN, true);
sleep_ms(50);
gpio_put(RST_PIN, false);
sleep_ms(10);
gpio_put(RST_PIN, true);
sleep_ms(10);
#ifndef HX8357C
#ifdef ILI9486
writecommand(0x01);
writedata(0x00);
sleep_ms(50);
writecommand(0x28);
writedata(0x00);
writecommand(0xC0); // Power Control 1
writedata(0x0d);
writedata(0x0d);
writecommand(0xC1); // Power Control 2
writedata(0x43);
writedata(0x00);
writecommand(0xC2); // Power Control 3
writedata(0x00);
writecommand(0xC5); // VCOM Control
writedata(0x00);
writedata(0x48);
writecommand(0xB6); // Display Function Control
writedata(0x00);
writedata(0x22); // 0x42 = Rotate display 180 deg.
writedata(0x3B);
writecommand(0xE0); // PGAMCTRL (Positive Gamma Control)
writedata(0x0f);
writedata(0x24);
writedata(0x1c);
writedata(0x0a);
writedata(0x0f);
writedata(0x08);
writedata(0x43);
writedata(0x88);
writedata(0x32);
writedata(0x0f);
writedata(0x10);
writedata(0x06);
writedata(0x0f);
writedata(0x07);
writedata(0x00);
writecommand(0xE1); // NGAMCTRL (Negative Gamma Control)
writedata(0x0F);
writedata(0x38);
writedata(0x30);
writedata(0x09);
writedata(0x0f);
writedata(0x0f);
writedata(0x4e);
writedata(0x77);
writedata(0x3c);
writedata(0x07);
writedata(0x10);
writedata(0x05);
writedata(0x23);
writedata(0x1b);
writedata(0x00);
writecommand(0x20); // Display Inversion OFF, 0x21 = ON
writecommand(0x36); // Memory Access Control
// writedata(0x0A);
writedata(0x02); // rgb -> bgr
writecommand(0x3A); // Interface Pixel Format
writedata(0x55);
// // scroll
// writecommand( 0x37);
// writedata(0);
// writedata(120);
//
writecommand( 0x30);
writedata(0);
writedata(0);
writedata(0);
writedata(240);
// partial mode
writecommand( 0x12);
writecommand(0x11);
sleep_ms(150);
writecommand(0x29);
sleep_ms(25);
#else
// Configure HX8357-B display
writecommand(0x11);
sleep_ms(20);
writecommand(0xD0);
writedata(0x07);
writedata(0x42);
writedata(0x18);
writecommand(0xD1);
writedata(0x00);
writedata(0x07);
writedata(0x10);
writecommand(0xD2);
writedata(0x01);
writedata(0x02);
writecommand(0xC0);
writedata(0x10);
writedata(0x3B);
writedata(0x00);
writedata(0x02);
writedata(0x11);
writecommand(0xC5);
writedata(0x08);
writecommand(0xC8);
writedata(0x00);
writedata(0x32);
writedata(0x36);
writedata(0x45);
writedata(0x06);
writedata(0x16);
writedata(0x37);
writedata(0x75);
writedata(0x77);
writedata(0x54);
writedata(0x0C);
writedata(0x00);
writecommand(0x36);
//writedata(0x0a);
writedata(0x02); // rgb -> bgr
writecommand(0x3A);
writedata(0x55);
writecommand(0x2A);
writedata(0x00);
writedata(0x00);
writedata(0x01);
writedata(0x3F);
writecommand(0x2B);
writedata(0x00);
writedata(0x00);
writedata(0x01);
writedata(0xDF);
sleep_ms(120);
writecommand(0x29);
sleep_ms(25);
// End of HX8357-B display configuration
#endif
#else
// HX8357-C display initialisation
writecommand(0xB9); // Enable extension command
writedata(0xFF);
writedata(0x83);
writedata(0x57);
sleep_ms(50);
writecommand(0xB6); //Set VCOM voltage
writedata(0x2C); //0x52 for HSD 3.0"
writecommand(0x11); // Sleep off
sleep_ms(200);
writecommand(0x35); // Tearing effect on
writedata(0x00); // Added parameter
writecommand(0x3A); // Interface pixel format
writedata(0x55); // 16 bits per pixel
//writecommand(0xCC); // Set panel characteristic
//writedata(0x09); // S960>S1, G1>G480, R-G-B, normally black
//writecommand(0xB3); // RGB interface
//writedata(0x43);
//writedata(0x00);
//writedata(0x06);
//writedata(0x06);
writecommand(0xB1); // Power control
writedata(0x00);
writedata(0x15);
writedata(0x0D);
writedata(0x0D);
writedata(0x83);
writedata(0x48);
writecommand(0xC0); // Does this do anything?
writedata(0x24);
writedata(0x24);
writedata(0x01);
writedata(0x3C);
writedata(0xC8);
writedata(0x08);
writecommand(0xB4); // Display cycle
writedata(0x02);
writedata(0x40);
writedata(0x00);
writedata(0x2A);
writedata(0x2A);
writedata(0x0D);
writedata(0x4F);
writecommand(0xE0); // Gamma curve
writedata(0x00);
writedata(0x15);
writedata(0x1D);
writedata(0x2A);
writedata(0x31);
writedata(0x42);
writedata(0x4C);
writedata(0x53);
writedata(0x45);
writedata(0x40);
writedata(0x3B);
writedata(0x32);
writedata(0x2E);
writedata(0x28);
writedata(0x24);
writedata(0x03);
writedata(0x00);
writedata(0x15);
writedata(0x1D);
writedata(0x2A);
writedata(0x31);
writedata(0x42);
writedata(0x4C);
writedata(0x53);
writedata(0x45);
writedata(0x40);
writedata(0x3B);
writedata(0x32);
writedata(0x2E);
writedata(0x28);
writedata(0x24);
writedata(0x03);
writedata(0x00);
writedata(0x01);
writecommand(0x36); // MADCTL Memory access control
writedata(0x48);
sleep_ms(20);
writecommand(0x21); //Display inversion on
sleep_ms(20);
writecommand(0x29); // Display on
sleep_ms(120);
#endif
#ifdef NO_SETUP
setAddrWindow(0, 0, 320, 240);
#endif
#ifdef KEEP_CS_LOW
SETUP_CS_L;
#endif
}
void tft_driver_init()
{
for(int i = 0; i < 16; i++)
{
gpio_init(i);
}
gpio_init(RS_PIN);
gpio_init(CS_PIN);
// gpio_init(_fcs);
gpio_init(WR_PIN);
gpio_init(RST_PIN);
gpio_set_dir(RST_PIN, true);
gpio_put(RST_PIN, true);
gpio_set_dir(RS_PIN, true);
gpio_set_dir(CS_PIN, true);
gpio_set_dir(WR_PIN, true);
gpio_put(RS_PIN, true);
#ifndef KEEP_CS_LOW
gpio_put(CS_PIN, true);
#else
gpio_put(CS_PIN, false);
#endif
gpio_put(WR_PIN, true);
// DDRA = 0xFF; // Set direction for the 2 8 bit data ports
// DDRC = 0xFF;
gpio_set_dir_masked(0xffff, 0xffff);
#define EXEC_CSL_RSL pio_encode_with_sideset_opt(pio_encode_set_pins(0), 2, 1)
#define EXEC_CSL_RSH pio_encode_with_sideset_opt(pio_encode_set_pins(1), 2, 1)
#define EXEC_CSH_RSH pio_encode_with_sideset_opt(pio_encode_set_pins(3), 2, 1)
#define EXEC_SET_IRQ(n) pio_encode_with_sideset_opt(pio_encode_irq_set((n), false), 2, 1)
#define DOH_PROGRAM_OFFSET 16
#define EXEC_SKIP pio_encode_with_sideset_opt(pio_encode_jmp(DOH_PROGRAM_OFFSET + video_dbi_control_offset_new_state_wait), 2, 1)
#define WR_CMD(n) MAKE_CMD( (n)-1, DOH_PROGRAM_OFFSET + video_dbi_control_offset_data_run_out)
#define SKIP_CMD MAKE_CMD( 0, DOH_PROGRAM_OFFSET + video_dbi_control_offset_new_state_wait)
#define CLOCK_CMD(n) MAKE_CMD((w)-1, DOH_PROGRAM_OFFSET + video_dbi_control_offset_clock_run)
#ifndef NO_SETUP
scanline_control_sequence.exec_csrs_l = EXEC_CSL_RSL;
scanline_control_sequence.wr_1_cmd = WR_CMD(1);
scanline_control_sequence.caset16 = HX8357_CASET;
scanline_control_sequence.wr_4_cmd = WR_CMD(4);
scanline_control_sequence.exec_csrs_l_2 = EXEC_CSL_RSL;
scanline_control_sequence.wr_1_cmd_2 = WR_CMD(1);
scanline_control_sequence.paset16 = HX8357_PASET;
scanline_control_sequence.exec_rs_h_2 = EXEC_CSL_RSH;
scanline_control_sequence.wr_4_cmd_2 = WR_CMD(4);
scanline_control_sequence.exec_csrs_l_3 = EXEC_CSL_RSL;
scanline_control_sequence.wr_1_cmd_3 = WR_CMD(1);
scanline_control_sequence.ramwr16 = HX8357_RAMWR;
scanline_control_sequence.exec_rs_h_3 = EXEC_CSL_RSH;
#endif
scanline_control_sequence.skip_cmd = SKIP_CMD;
scanline_control_sequence.exec_rs_h = EXEC_CSL_RSH;
scanline_control_sequence.exec_set_irq4 = EXEC_SET_IRQ(4);
scanline_control_sequence.exec_csrs_h = EXEC_CSH_RSH;
scanline_control_sequence.exec_set_irq0 = EXEC_SET_IRQ(0);
scanline_control_sequence.skip_cmd_2 = SKIP_CMD;
scanline_control_sequence.skip_cmd_3 = SKIP_CMD;
scanline_control_sequence.exec_skip = EXEC_SKIP;
scanline_control_sequence.x0_h = 0;
scanline_control_sequence.x0_l = 0;
switch_buffer_control_sequence.exec_csrs_l = EXEC_CSL_RSL; //
switch_buffer_control_sequence.wr_1_cmd = WR_CMD(1); //
// switch_buffer_control_sequence.ptlar16 = 0x30; //
// switch_buffer_control_sequence.exec_rs_h = EXEC_CSL_RSH;
// switch_buffer_control_sequence.wr_4_cmd = WR_CMD(4); //
//
// switch_buffer_control_sequence.exec_csrs_l_2 = EXEC_CSL_RSL; //
// switch_buffer_control_sequence.wr_1_cmd_2 = WR_CMD(1); //
switch_buffer_control_sequence.vscrsaddr16 = 0x37; //
switch_buffer_control_sequence.exec_rs_h_2 = EXEC_CSL_RSH;
switch_buffer_control_sequence.wr_2_cmd = WR_CMD(2); //
switch_buffer_control_sequence.exec_csrs_h = EXEC_CSH_RSH;
switch_buffer_control_sequence.skip_cmd = SKIP_CMD;
switch_buffer_control_sequence.exec_skip = EXEC_SKIP;
tft_panel_init();
}
#define video_pio pio0
// todo duplicate defines from elsewhere
#define SM_DATA 0
#define SM_CONTROL 3
#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL 0
uint32_t *get_control_sequence(uint w, uint y, uint *count, bool buffer) {
y += buffer ? 240 : 0;
#ifndef NO_SETUP
scanline_control_sequence.x1_h = w>>8;
scanline_control_sequence.x1_l = w&0xff;
scanline_control_sequence.y0_h = y>>8;
scanline_control_sequence.y0_l = y&0xff;
scanline_control_sequence.y1_h = (y+1)>>8;
scanline_control_sequence.y1_l = (y+1)&0xff;
#endif
scanline_control_sequence.clk_n_cmd = CLOCK_CMD(w);
static_assert(!(sizeof(scanline_control_sequence) & 3u), "");
*count = sizeof(scanline_control_sequence) / 4;
return (uint32_t *)&scanline_control_sequence;
}
extern uint32_t *get_switch_buffer_sequence(uint *count, bool buffer) {
// switch_buffer_control_sequence.y0_h = 0;
// switch_buffer_control_sequence.y0_l = buffer?240:0;
// switch_buffer_control_sequence.y1_h = buffer?(480>>8):0;
// switch_buffer_control_sequence.y1_l = buffer?(480&0xff):240;
switch_buffer_control_sequence.y_h = 0;
switch_buffer_control_sequence.y_l = buffer?240:0;
*count = sizeof(switch_buffer_control_sequence) / 4;
return (uint32_t *)&switch_buffer_control_sequence;
}

Wyświetl plik

@ -0,0 +1,94 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef SOFTWARE_TFT_DRIVER_H
#define SOFTWARE_TFT_DRIVER_H
// These register enumerations are not all used, but kept for possible future use
#define HX8357D 0xD
#define HX8357B 0xB7
#define HX8357_NOP 0x00
#define HX8357_SWRESET 0x01
#define HX8357_RDDID 0x04
#define HX8357_RDDST 0x09
#define HX8357_RDPOWMODE 0x0A
#define HX8357_RDMADCTL 0x0B
#define HX8357_RDCOLMOD 0x0C
#define HX8357_RDDIM 0x0D
#define HX8357_RDDSDR 0x0F
#define HX8357_SLPIN 0x10
#define HX8357_SLPOUT 0x11
#define HX8357B_PTLON 0x12
#define HX8357B_NORON 0x13
#define HX8357_INVOFF 0x20
#define HX8357_INVON 0x21
#define HX8357_DISPOFF 0x28
#define HX8357_DISPON 0x29
#define HX8357_CASET 0x2A
#define HX8357_PASET 0x2B
#define HX8357_RAMWR 0x2C
#define HX8357_RAMRD 0x2E
#define HX8357B_PTLAR 0x30
#define HX8357_TEON 0x35
#define HX8357_TEARLINE 0x44
#define HX8357_MADCTL 0x36
#define HX8357_COLMOD 0x3A
#define HX8357_SETOSC 0xB0
#define HX8357_SETPWR1 0xB1
#define HX8357B_SETDISPLAY 0xB2
#define HX8357_SETRGB 0xB3
#define HX8357D_SETCOM 0xB6
#define HX8357B_SETDISPMODE 0xB4
#define HX8357D_SETCYC 0xB4
#define HX8357B_SETOTP 0xB7
#define HX8357D_SETC 0xB9
#define HX8357B_SET_PANEL_DRIVING 0xC0
#define HX8357D_SETSTBA 0xC0
#define HX8357B_SETDGC 0xC1
#define HX8357B_SETID 0xC3
#define HX8357B_SETDDB 0xC4
#define HX8357B_SETDISPLAYFRAME 0xC5
#define HX8357B_GAMMASET 0xC8
#define HX8357B_SETCABC 0xC9
#define HX8357_SETPANEL 0xCC
#define HX8357B_SETPOWER 0xD0
#define HX8357B_SETVCOM 0xD1
#define HX8357B_SETPWRNORMAL 0xD2
#define HX8357B_RDID1 0xDA
#define HX8357B_RDID2 0xDB
#define HX8357B_RDID3 0xDC
#define HX8357B_RDID4 0xDD
#define HX8357D_SETGAMMA 0xE0
#define HX8357B_SETGAMMA 0xC8
#define HX8357B_SETPANELRELATED 0xE9
#define RS_PIN 24u
#define CS_PIN 25u
#define WR_PIN 26u
#define RST_PIN 27u
// don't care
#define FCS_PIN 0 //23
extern void tft_driver_init();
extern uint32_t *get_switch_buffer_sequence(uint *count, bool buffer);
extern uint32_t *get_control_sequence(uint w, uint y, uint *count, bool buffer);
#endif //SOFTWARE_TFT_DRIVER_H

Wyświetl plik

@ -0,0 +1,362 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "video.h"
// todo support for inverted-y (probably belongs in the scanline generators, as would inverted x)
const uint32_t video_clock_freq = 24000000;
extern const video_pio_program_t video_24mhz_composable;
const video_timing_t vga_timing_640x480_60_default =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 16,
.h_pulse = 64,
.h_total = 800,
.h_sync_polarity = 1,
.v_front_porch = 1,
.v_pulse = 2,
.v_total = 500,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_640x240_60_default =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 240,
.h_front_porch = 16,
.h_pulse = 64,
.h_total = 800,
.h_sync_polarity = 1,
.v_front_porch = 1,
.v_pulse = 2,
.v_total = 250,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_648x480_60_alt1 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 16,
.h_pulse = 48,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 10,
.v_pulse = 2,
.v_total = 523,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_648x480_50ish =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 56,
.h_pulse = 72,
.h_total = 896,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 536,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_648x480_50ish2 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 32,
.h_pulse = 64,
.h_total = 832,
.h_sync_polarity = 1,
.v_front_porch = 27,
.v_pulse = 2,
.v_total = 577,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_648x480_50ish3 =
{
.clock_freq = 24000000,
.h_active = 640,
.v_active = 480,
.h_front_porch = 72,
.h_pulse = 96,
.h_total = 928,
.h_sync_polarity = 1,
.v_front_porch = 8,
.v_pulse = 2,
.v_total = 518,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
#define actual_vga_timing_50 vga_timing_648x480_50ish3
const video_mode_t vga_mode_160x120_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 160,
.height = 120,
.xscale = 4,
.yscale = 4,
};
const video_mode_t vga_mode_213x160_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 213,
.height = 160,
.xscale = 3,
.yscale = 3,
};
const video_mode_t vga_mode_320x240_60 =
{
.default_timing = &vga_timing_640x240_60_default,
.pio_program = &video_24mhz_composable,
.width = 320,
.height = 240,
.xscale = 2,
.yscale = 1,
};
const video_mode_t vga_mode_320x480_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 320,
.height = 480,
.xscale = 2,
.yscale = 2,
};
const video_mode_t vga_mode_640x480_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 640,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const video_mode_t vga_mode_640x480_50 =
{
.default_timing = &actual_vga_timing_50,
.pio_program = &video_24mhz_composable,
.width = 640,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const video_mode_t vga_mode_320x240_50 =
{
.default_timing = &actual_vga_timing_50,
.pio_program = &video_24mhz_composable,
.width = 320,
.height = 240,
.xscale = 2,
.yscale = 2,
};
/* this is 50 hz */
const video_timing_t vga_timing_wide_480_50 =
{
.clock_freq = 24000000,
.h_active = 800,
.v_active = 480,
.h_front_porch = 32,
.h_pulse = 48,
.h_total = 960,
.h_sync_polarity = 0,
.v_front_porch = 1,
.v_pulse = 2,
.v_total = 500,
.v_sync_polarity = 0,
.enable_clock = 1,
.clock_polarity = 0,
.enable_den = 1
};
const video_mode_t vga_mode_tft_800x480_50 =
{
.default_timing = &vga_timing_wide_480_50,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 480,
.xscale = 1,
.yscale = 1,
};
const video_mode_t vga_mode_tft_400x240_50 =
{
.default_timing = &vga_timing_wide_480_50,
.pio_program = &video_24mhz_composable,
.width = 400,
.height = 240,
.xscale = 2,
.yscale = 2,
};
const video_timing_t vga_timing_512x576_50_attempt1 =
{
.clock_freq = 24000000,
.h_active = 512,
.v_active = 576,
.h_front_porch = 64,
.h_pulse = 64,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 612,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_timing_t vga_timing_512x576_60_attempt1 =
{
.clock_freq = 24000000,
.h_active = 512,
.v_active = 576,
.h_front_porch = 64,
.h_pulse = 64,
.h_total = 768,
.h_sync_polarity = 1,
.v_front_porch = 30,
.v_pulse = 2,
.v_total = 612,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_mode_t vga_mode_256x192_50 =
{
.default_timing = &vga_timing_512x576_50_attempt1,
.pio_program = &video_24mhz_composable,
.width = 256,
.height = 192,
.xscale = 2,
.yscale = 3,
};
const video_timing_t vga_timing_800x600_38 =
{
.clock_freq = 24000000,
.h_active = 800,
.v_active = 600,
.h_front_porch = 24,
.h_pulse = 80,
.h_total = 1008,
.h_sync_polarity = 1,
.v_front_porch = 3,
.v_pulse = 4,
.v_total = 621,
.v_sync_polarity = 1,
.enable_clock = 0,
.clock_polarity = 0,
.enable_den = 0
};
const video_mode_t vga_mode_800x600_38 =
{
.default_timing = &vga_timing_800x600_38,
.pio_program = &video_24mhz_composable,
.width = 800,
.height = 600,
.xscale = 1,
.yscale = 1,
};

Wyświetl plik

@ -0,0 +1,271 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _VIDEO_H
#define _VIDEO_H
#include "platform.h"
#ifdef __cplusplus
extern "C" {
#endif
// == CONFIG ============
#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA 1
#endif
#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA) || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA 1
#endif
#if PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA
#define PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA 1
#endif
#define ENABLE_VIDEO_CLOCK
#define ENABLE_VIDEO_DEN
// todo make multi plane play nicely with mode swapping;
// today we have hard coded blank/empty lines
//#define ENABLE_VIDEO_PLANE2
//#define ENABLE_VIDEO_PLANE3
//#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 1
//#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 1
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS 128
#endif
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS 16
#endif
#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS
#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS 16
#endif
//extern struct semaphore vmode_updated;
// note by default we allow for alpha mask (and lose a bit of green)
// todo make this configurable
#define PICO_SCANVIDEO_ALPHA_MASK 0x0020
#define PICO_SCANVIDEO_PIXEL_RSHIFT 0
#define PICO_SCANVIDEO_PIXEL_GSHIFT 6
#define PICO_SCANVIDEO_PIXEL_BSHIFT 11
#define PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b) ((((b)>>3)<<PICO_SCANVIDEO_PIXEL_BSHIFT)|(((g)>>3)<<PICO_SCANVIDEO_PIXEL_GSHIFT)|(((r)>>3)<<PICO_SCANVIDEO_PIXEL_RSHIFT))
#define PICO_SCANVIDEO_PIXEL_FROM_RGB5(r, g, b) (((b)<<PICO_SCANVIDEO_PIXEL_BSHIFT)|((g)<<PICO_SCANVIDEO_PIXEL_GSHIFT)|((r)<<PICO_SCANVIDEO_PIXEL_RSHIFT))
// ======================
#define BPP 16
// most likely 24000000
extern const uint32_t vga_clock_freq;
// todo pragma pack?
struct video_timing
{
uint32_t clock_freq;
uint16_t h_active;
uint16_t v_active;
uint16_t h_front_porch;
uint16_t h_pulse;
uint16_t h_total;
uint8_t h_sync_polarity;
uint16_t v_front_porch;
uint16_t v_pulse;
uint16_t v_total;
uint8_t v_sync_polarity;
uint8_t enable_clock;
uint8_t clock_polarity;
uint8_t enable_den;
};
struct pio_program;
// todo we need to handle blank data correctly (perhaps DMA should just not start for that scanline,
// though obviously this is slightly more complicated with multiple playfields, or perhaps worse with
// just one
struct video_mode
{
const struct video_timing *default_timing;
const struct video_pio_program *pio_program;
uint16_t width;
uint16_t height;
uint8_t xscale; // 1 == normal, 2 == double wide etc. up to what pio timing allows (not sure I have an assert based on delays)
uint8_t yscale; // same for y scale (except any yscale is possible)
};
extern bool video_setup(const struct video_mode *mode);
extern bool video_setup_with_timing(const struct video_mode *mode, const struct video_timing *timing);
extern void video_timing_enable(bool enable);
// these take effect after the next vsync
extern void video_display_enable(bool enable);
// doesn't exist yet!
// extern void video_set_display_mode(const struct video_mode *mode);
// --- scanline management ---
struct scanline_buffer
{
uint32_t scanline_id;
uint32_t *data;
uint16_t data_used;
uint16_t data_max;
#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA
uint16_t fragment_words;
#endif
#if PICO_SCANVIDEO_PLANE_COUNT > 1
uint32_t *data2;
uint16_t data2_used;
uint16_t data2_max;
#if PICO_SCANVIDEO_PLANE_COUNT > 2
uint32_t *data3;
uint16_t data3_used;
uint16_t data3_max;
#endif
#endif
// useful to track state between the buffer being passed to
// video_end_scanline_generation, and when the buffer is no longer
// in use by the video code and is returned to a subsequent caller
// via video_begin_scanline_generation
// todo we caould add a callback to begin scanline generation to enuerate
// all already discarded buffers early - not clear this would be useful in general
// because it only saves you space if stuff is running with low buffer utilization
void *user_data;
uint8_t status;
};
enum
{
SCANLINE_OK = 1,
SCANLINE_ERROR,
SCANLINE_SKIPPED
};
// note frame numbers wrap
static inline uint16_t frame_number(uint32_t scanline_id)
{
return (uint16_t) (scanline_id >> 16u);
}
static inline uint16_t scanline_number(uint32_t scanline_id)
{
return (uint16_t) scanline_id;
}
/**
* @return the current vga mode (if there is one)
*/
extern struct video_mode video_get_mode();
/**
* @return the next scanline_id to be displayed (may be from the next frame)
*/
extern uint32_t video_get_next_scanline_id();
/**
* @return true if in the vblank interval
*/
extern bool video_in_vblank();
/**
* @return true if in the hblank interval (or more accurately scanline data is not currently being sent to the PIO, which roughly corresponds, but is not exact). Note also that in
* yscale-d modes, there are height * yscale hblank intervals per frame.
*/
extern bool video_in_hblank();
extern void video_wait_for_vblank();
extern uint32_t video_wait_for_scanline_complete(uint32_t scanline_id);
/**
* Acquire a scanline that needs generating. The scanline_id field indicates which scanline is required.
*
* This method may be called concurrently
*
* @param block true to block if the vga system is not ready to generate a new scanline
* @return the scanline_buffer or NULL if block is false, and the vga system is not ready
*/
struct scanline_buffer *video_begin_scanline_generation(bool block);
/**
* Return a scanline that has been generated / or at least the client is done with.
*
* The status field indicates whether the scanline was actually generated OK
*
* This method may be called concurrently (for different buffers)
*
* @param scanline_buffer \todo
*/
void video_end_scanline_generation(struct scanline_buffer *scanline_buffer);
extern const struct video_timing vga_timing_640x480_60_default;
extern const struct video_timing vga_timing_wide_480_50;
extern const struct video_timing vga_timing_648x480_60_alt1;
extern const struct video_mode vga_mode_160x120_60; // 3d monster maze anyone :-)
extern const struct video_mode vga_mode_213x160_60;
extern const struct video_mode vga_mode_320x240_60;
extern const struct video_mode vga_mode_640x480_60;
extern const struct video_mode vga_mode_320x480_60;
extern const struct video_mode vga_mode_tft_800x480_50;
extern const struct video_mode vga_mode_tft_400x240_50;
#ifndef NDEBUG
// todo this is only for vga composable 24... should exist behind mode impl
extern void validate_scanline(const uint32_t *dma_data, uint dma_data_size, uint max_pixels, uint expected_width);
#endif
// mode implementation
pio_hw_t;
struct video_pio_program
{
#if !PICO_NO_HARDWARE
const uint16_t *program;
const int program_size;
const int entry_point;
bool (*adapt_for_mode)(const struct video_pio_program *program, const struct video_mode *mode,
struct scanline_buffer *missing_scanline_buffer, uint16_t *buffer, uint buffer_max);
void (*configure_pio)(pio_hw_t *pio, uint sm);
#else
const char *id;
#endif
};
extern void video_default_configure_pio(pio_hw_t *pio, uint sm, uint wrap_trarget, uint wrap, bool overlay);
#include "video.pio.h"
#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE
#define video_24mhz_composable_prefix video_24mhz_composable_default
#else
#define video_24mhz_composable_prefix video_24mhz_composable_raw1p_2cycle
#endif
// yuk... extra __P needed for native on some platforms
#define video_24mhz_composable_program_extern(x) __SAFE_CONCAT(__SAFE_CONCAT(video_24mhz_composable_prefix, _offset_), x)
#define __DVP_JMP(x) ((unsigned)video_24mhz_composable_program_extern(x))
#define COMPOSABLE_COLOR_RUN __DVP_JMP(color_run)
#define COMPOSABLE_EOL_ALIGN __DVP_JMP(end_of_scanline_ALIGN)
#define COMPOSABLE_EOL_SKIP_ALIGN __DVP_JMP(end_of_scanline_skip_word_ALIGN)
#define COMPOSABLE_RAW_RUN __DVP_JMP(raw_run)
#define COMPOSABLE_RAW_1P __DVP_JMP(raw_1p)
#define COMPOSABLE_RAW_2P __DVP_JMP(raw_2p)
#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE
#define COMPOSABLE_RAW_1P_SKIP_ALIGN __DVP_JMP(raw_1p_skip_word_ALIGN)
#else
#define COMPOSABLE_RAW_1P_2CYCLE __DVP_JMP(raw_1p_2cycle)
#endif
#ifdef __cplusplus
}
#endif
#endif //_VIDEO_H

Wyświetl plik

@ -0,0 +1,13 @@
if (TARGET pico_scanvideo)
add_library(pico_scanvideo_dpi INTERFACE)
pico_generate_pio_header(pico_scanvideo_dpi ${CMAKE_CURRENT_LIST_DIR}/timing.pio)
target_sources(pico_scanvideo_dpi INTERFACE
${CMAKE_CURRENT_LIST_DIR}/scanvideo.c
)
target_include_directories(pico_scanvideo_dpi INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_compile_definitions(pico_scanvideo_dpi INTERFACE VIDEO_DPI)
target_link_libraries(pico_scanvideo_dpi INTERFACE hardware_dma hardware_pio hardware_irq pico_scanvideo)
endif()

Wyświetl plik

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef PICO_SCANVIDEO_H_
#define PICO_SCANVIDEO_H_
// note that defining to false will force non-inclusion also
#if !defined(PICO_SCANVIDEO_DPI)
#define PICO_SCANVIDEO_DPI 1
#ifndef PARAM_ASSERTIONS_ENABLED_SCANVIDEO_DPI
#define PARAM_ASSERTIONS_ENABLED_SCANVIDEO_DPI 0
#endif
#include "pico/scanvideo/scanvideo_base.h"
#ifndef PICO_SCANVIDEO_DPI_ALPHA_PIN
#define PICO_SCANVIDEO_DPI_ALPHA_PIN 5u
#endif
#ifndef PICO_SCANVIDEO_DPI_PIXEL_RSHIFT
#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 0u
#endif
#ifndef PICO_SCANVIDEO_DPI_PIXEL_GSHIFT
#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 6u
#endif
#ifndef PICO_SCANVIDEO_DPI_PIXEL_BSHIFT
#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 11u
#endif
#ifndef PICO_SCANVIDEO_ALPHA_PIN
#define PICO_SCANVIDEO_ALPHA_PIN PICO_SCANVIDEO_DPI_ALPHA_PIN
#endif
#ifndef PICO_SCANVIDEO_PIXEL_RSHIFT
#define PICO_SCANVIDEO_PIXEL_RSHIFT PICO_SCANVIDEO_DPI_PIXEL_RSHIFT
#endif
#ifndef PICO_SCANVIDEO_PIXEL_GSHIFT
#define PICO_SCANVIDEO_PIXEL_GSHIFT PICO_SCANVIDEO_DPI_PIXEL_GSHIFT
#endif
#ifndef PICO_SCANVIDEO_PIXEL_BSHIFT
#define PICO_SCANVIDEO_PIXEL_BSHIFT PICO_SCANVIDEO_DPI_PIXEL_BSHIFT
#endif
/** \file scanvideo.h
* \defgroup pico_scanvideo_dpi pico_scanvideo_dpi
*
* DPI Scan-out Video using the PIO
*/
#endif
#endif

Wyświetl plik

@ -0,0 +1,34 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program video_htiming
.side_set 1 ; used for clock
public entry_point:
; todo we can do this with one off setup via pio_exec
pull block side 0
.wrap_target
new_state:
out exec, 16 side 1 ; this does any per state inline work (or it can be a JMP to entry_point to sleep..
; note the EXECed instruction should have a side set 0
out x, 13 side 1
out pins, 3 side 0 ; we want an OUT EXEC above which sets an IRQ to start scanline output
; to cause this out and the pixel out in the same cycle (this would be positive clk edge latch)
loop:
nop side 1
jmp x-- loop side 0
.wrap
; these are the values used in the out exec in video_htiming
.program video_htiming_states
.side_set 1
; state 0 = set irq 0
irq 0 side 0
; state 1 = set irq 1
irq 1 side 0
; state 2 = set irq 4
irq 4 side 0
; state 3 = clear irq 4
irq clear 4 side 0

Wyświetl plik

@ -0,0 +1,12 @@
if (NOT TARGET pico_sd_card)
add_library(pico_sd_card INTERFACE)
pico_generate_pio_header(pico_sd_card ${CMAKE_CURRENT_LIST_DIR}/sd_card.pio)
target_sources(pico_sd_card INTERFACE
${CMAKE_CURRENT_LIST_DIR}/sd_card.c
)
target_include_directories(pico_sd_card INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(pico_sd_card INTERFACE pico_sd_card_headers hardware_dma hardware_pio)
endif()

Wyświetl plik

@ -0,0 +1 @@
THIS CODE IS AT PROTOTYPING LEVEL ONLY (THOUGH IS USABLE)

Wyświetl plik

@ -0,0 +1,34 @@
const uint16_t crc_itu_t_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

Wyświetl plik

@ -0,0 +1,34 @@
static const uint8_t crc7_table[256] = {
0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e,
0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c,
0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a,
0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28,
0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6,
0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84,
0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2,
0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0,
0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc,
0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce,
0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98,
0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa,
0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34,
0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06,
0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50,
0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62,
0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2
};

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,59 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; NOTE IT IS IMPERATIVE THAT YOU DON"T SCREW UP THE CLOCK EVEN FOR ONE HALF CYCLE, OTHERWISE THE DEVICE WILL LIKELY BE DISPLEASED AND MAY SEND GARBAGE (EVEN IF IT ISN"T DOING ANYTHING IMPORTANT WHEN YOU DO SO)
.define sd_irq_num 7
.program sd_clk
.side_set 1
.wrap_target
irq sd_irq_num side 1
irq clear sd_irq_num side 0
.wrap
.program sd_cmd_or_dat
.origin 0 ; must load at zero (offsets are hardcoded in instruction stream)
public no_arg_state_wait_high: ; this is a no arg state which means it must always appear in the second half of a word
; make sure pins are hi when we set output dir (note if we are 1 bit we'll be configured for 1 pin only, so sending 0b1111 is fine)
set pins, 0b1111
set pindirs, 0b1111
public no_arg_state_waiting_for_cmd:
out exec, 16 ; expected to be a jmp to a state
public state_send_bits:
out x, 16
wait 0 irq sd_irq_num
send_loop1:
out pins, 1
jmp x-- send_loop1
public state_inline_instruction:
out exec, 16 ; may be any instruction
.wrap_target
out exec, 16 ; expected to be a jmp to a state
public state_receive_bits:
out x, 16
set pindirs, 0
wait 1 pin, 0
wait 0 pin, 0
wait 0 irq sd_irq_num
; note we use wrap setup to configure receive bit/nibble transfers
public wrap_for_4bit_receive:
receive_loop1:
in pins, 1
jmp x-- receive_loop1
.wrap
; #if INCLUDE_4BIT
public wrap_target_for_4bit_receive:
receive_loop4:
in pins, 4
jmp x-- receive_loop4
out exec, 16 ; expected to be a jmp to a state
; #endif

Wyświetl plik

@ -0,0 +1,5 @@
pico_simple_hardware_target(sleep)
target_link_libraries(hardware_sleep INTERFACE
hardware_clocks
hardware_rosc
hardware_rtc)

Wyświetl plik

@ -0,0 +1,107 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PICO_SLEEP_H_
#define _PICO_SLEEP_H_
#include "pico.h"
#include "hardware/rtc.h"
#ifdef __cplusplus
extern "C" {
#endif
/** \file sleep.h
* \defgroup hardware_sleep hardware_sleep
*
* Lower Power Sleep API
*
* The difference between sleep and dormant is that ALL clocks are stopped in dormant mode,
* until the source (either xosc or rosc) is started again by an external event.
* In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks
* block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic)
* can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again.
*
* \subsection sleep_example Example
* \addtogroup hardware_sleep
* \include hello_sleep.c
*/
typedef enum {
DORMANT_SOURCE_NONE,
DORMANT_SOURCE_XOSC,
DORMANT_SOURCE_ROSC
} dormant_source_t;
/*! \brief Set all clock sources to the the dormant clock source to prepare for sleep.
* \ingroup hardware_sleep
*
* \param dormant_source The dormant clock source to use
*/
void sleep_run_from_dormant_source(dormant_source_t dormant_source);
/*! \brief Set the dormant clock source to be the crystal oscillator
* \ingroup hardware_sleep
*/
static inline void sleep_run_from_xosc(void) {
sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC);
}
/*! \brief Set the dormant clock source to be the ring oscillator
* \ingroup hardware_sleep
*/
static inline void sleep_run_from_rosc(void) {
sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC);
}
/*! \brief Send system to sleep until the specified time
* \ingroup hardware_sleep
*
* One of the sleep_run_* functions must be called prior to this call
*
* \param t The time to wake up
* \param callback Function to call on wakeup.
*/
void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback);
/*! \brief Send system to sleep until the specified GPIO changes
* \ingroup hardware_sleep
*
* One of the sleep_run_* functions must be called prior to this call
*
* \param gpio_pin The pin to provide the wake up
* \param edge true for leading edge, false for trailing edge
* \param high true for active high, false for active low
*/
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high);
/*! \brief Send system to sleep until a leading high edge is detected on GPIO
* \ingroup hardware_sleep
*
* One of the sleep_run_* functions must be called prior to this call
*
* \param gpio_pin The pin to provide the wake up
*/
static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) {
sleep_goto_dormant_until_pin(gpio_pin, true, true);
}
/*! \brief Send system to sleep until a high level is detected on GPIO
* \ingroup hardware_sleep
*
* One of the sleep_run_* functions must be called prior to this call
*
* \param gpio_pin The pin to provide the wake up
*/
static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) {
sleep_goto_dormant_until_pin(gpio_pin, false, true);
}
#ifdef __cplusplus
}
#endif
#endif

Wyświetl plik

@ -0,0 +1,155 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico.h"
#include "pico/stdlib.h"
#include "pico/sleep.h"
#include "hardware/rtc.h"
#include "hardware/pll.h"
#include "hardware/clocks.h"
#include "hardware/xosc.h"
#include "hardware/rosc.h"
#include "hardware/regs/io_bank0.h"
// For __wfi
#include "hardware/sync.h"
// For scb_hw so we can enable deep sleep
#include "hardware/structs/scb.h"
// The difference between sleep and dormant is that ALL clocks are stopped in dormant mode,
// until the source (either xosc or rosc) is started again by an external event.
// In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks
// block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic)
// can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again.
// TODO: Optionally, memories can also be powered down.
static dormant_source_t _dormant_source;
bool dormant_source_valid(dormant_source_t dormant_source) {
return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC);
}
// In order to go into dormant mode we need to be running from a stoppable clock source:
// either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks
// and all PLLs
void sleep_run_from_dormant_source(dormant_source_t dormant_source) {
assert(dormant_source_valid(dormant_source));
_dormant_source = dormant_source;
// FIXME: Just defining average rosc freq here.
uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_MHZ * MHZ : 6.5 * MHZ;
uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ?
CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC :
CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH;
// CLK_REF = XOSC or ROSC
clock_configure(clk_ref,
clk_ref_src,
0, // No aux mux
src_hz,
src_hz);
// CLK SYS = CLK_REF
clock_configure(clk_sys,
CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,
0, // Using glitchless mux
src_hz,
src_hz);
// CLK USB = 0MHz
clock_stop(clk_usb);
// CLK ADC = 0MHz
clock_stop(clk_adc);
// CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc
uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ?
CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC :
CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH;
clock_configure(clk_rtc,
0, // No GLMUX
clk_rtc_src,
src_hz,
46875);
// CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable
clock_configure(clk_peri,
0,
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
src_hz,
src_hz);
pll_deinit(pll_sys);
pll_deinit(pll_usb);
// Assuming both xosc and rosc are running at the moment
if (dormant_source == DORMANT_SOURCE_XOSC) {
// Can disable rosc
rosc_disable();
} else {
// Can disable xosc
xosc_disable();
}
// Reconfigure uart with new clocks
setup_default_uart();
}
// Go to sleep until woken up by the RTC
void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) {
// We should have already called the sleep_run_from_dormant_source function
assert(dormant_source_valid(_dormant_source));
// Turn off all clocks when in sleep mode except for RTC
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
clocks_hw->sleep_en1 = 0x0;
rtc_set_alarm(t, callback);
uint save = scb_hw->scr;
// Enable deep sleep at the proc
scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS;
// Go to sleep
__wfi();
}
static void _go_dormant(void) {
assert(dormant_source_valid(_dormant_source));
if (_dormant_source == DORMANT_SOURCE_XOSC) {
xosc_dormant();
} else {
rosc_set_dormant();
}
}
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) {
bool low = !high;
bool level = !edge;
// Configure the appropriate IRQ at IO bank 0
assert(gpio_pin < NUM_BANK0_GPIOS);
uint32_t event = 0;
if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS;
if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS;
if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS;
if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
gpio_set_dormant_irq_enabled(gpio_pin, event, true);
_go_dormant();
// Execution stops here until woken up
// Clear the irq so we can go back to dormant mode again if we want
gpio_acknowledge_irq(gpio_pin, event);
}

Wyświetl plik

@ -0,0 +1,3 @@
add_library(usb_common INTERFACE)
target_include_directories(usb_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)

Wyświetl plik

@ -0,0 +1,134 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _USB_USB_COMMON_H
#define _USB_USB_COMMON_H
#include "pico/types.h"
#include "hardware/structs/usb.h"
// bmRequestType bit definitions
#define USB_REQ_TYPE_STANDARD 0x00u
#define USB_REQ_TYPE_TYPE_MASK 0x60u
#define USB_REQ_TYPE_TYPE_CLASS 0x20u
#define USB_REQ_TYPE_TYPE_VENDOR 0x40u
#define USB_REQ_TYPE_RECIPIENT_MASK 0x1fu
#define USB_REQ_TYPE_RECIPIENT_DEVICE 0x00u
#define USB_REQ_TYPE_RECIPIENT_INTERFACE 0x01u
#define USB_REQ_TYPE_RECIPIENT_ENDPOINT 0x02u
#define USB_DIR_OUT 0x00u
#define USB_DIR_IN 0x80u
#define USB_TRANSFER_TYPE_CONTROL 0x0
#define USB_TRANSFER_TYPE_ISOCHRONOUS 0x1
#define USB_TRANSFER_TYPE_BULK 0x2
#define USB_TRANSFER_TYPE_INTERRUPT 0x3
#define USB_TRANSFER_TYPE_BITS 0x3
// Descriptor types
#define USB_DT_DEVICE 0x01
#define USB_DT_CONFIG 0x02
#define USB_DT_STRING 0x03
#define USB_DT_INTERFACE 0x04
#define USB_DT_ENDPOINT 0x05
#define USB_REQUEST_GET_STATUS 0x0
#define USB_REQUEST_CLEAR_FEATURE 0x01
#define USB_REQUEST_SET_FEATURE 0x03
#define USB_REQUEST_SET_ADDRESS 0x05
#define USB_REQUEST_GET_DESCRIPTOR 0x06
#define USB_REQUEST_SET_DESCRIPTOR 0x07
#define USB_REQUEST_GET_CONFIGURATION 0x08
#define USB_REQUEST_SET_CONFIGURATION 0x09
#define USB_REQUEST_GET_INTERFACE 0x0a
#define USB_REQUEST_SET_INTERFACE 0x0b
#define USB_REQUEST_SYNC_FRAME 0x0c
#define USB_REQUEST_MSC_GET_MAX_LUN 0xfe
#define USB_REQUEST_MSC_RESET 0xff
#define USB_FEAT_ENDPOINT_HALT 0x00
#define USB_FEAT_DEVICE_REMOTE_WAKEUP 0x01
#define USB_FEAT_TEST_MODE 0x02
#define USB_DESCRIPTOR_TYPE_ENDPOINT 0x05
struct usb_setup_packet {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} __packed;
struct usb_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
};
struct usb_device_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} __packed;
struct usb_configuration_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
} __packed;
struct usb_interface_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __packed;
struct usb_endpoint_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} __packed;
struct usb_endpoint_descriptor_long {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
uint8_t bRefresh;
uint8_t bSyncAddr;
} __attribute__((packed));
#endif

Wyświetl plik

@ -0,0 +1,9 @@
add_library(usb_device INTERFACE)
target_sources(usb_device INTERFACE
${CMAKE_CURRENT_LIST_DIR}/usb_device.c
${CMAKE_CURRENT_LIST_DIR}/usb_stream_helper.c
)
target_include_directories(usb_device INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(usb_device INTERFACE usb_common hardware_irq hardware_dma hardware_pio pico_fix_rp2040_usb_device_enumeration)

Wyświetl plik

@ -0,0 +1,188 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _USB_USB_DEVICE_H
#define _USB_USB_DEVICE_H
#include "usb/usb_common.h"
#include "pico/assert.h"
#ifndef PICO_USBDEV_ENABLE_DEBUG_TRACE
#define PICO_USBDEV_ENABLE_DEBUG_TRACE 0
#endif
#ifndef PICO_USBDEV_ASSUME_ZERO_INIT
#define PICO_USBDEV_ASSUME_ZERO_INIT 0
#endif
#ifndef PICO_USBDEV_MAX_ENDPOINTS
#define PICO_USBDEV_MAX_ENDPOINTS USB_NUM_ENDPOINTS
#endif
static_assert(PICO_USBDEV_MAX_ENDPOINTS >= 1 && PICO_USBDEV_MAX_ENDPOINTS <= 16, "");
#ifndef PICO_USBDEV_MAX_DESCRIPTOR_SIZE
#define PICO_USBDEV_MAX_DESCRIPTOR_SIZE 64
#endif
// Enabling configuration items can reduce the size of the runtime code at the cost of some functionality
// or improve speed at the cost of some flexibility
// no custom per device setup packet handler
#ifndef PICO_USBDEV_NO_DEVICE_SETUP_HANDLER
#define PICO_USBDEV_NO_DEVICE_SETUP_HANDLER 0
#endif
// no custom per endpoint setup packet handlers
#ifndef PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER
#define PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER 0
#endif
// if all endpoints are bulk, then it allows simplification of some code
#ifndef PICO_USBDEV_BULK_ONLY_EP1_THRU_16
#define PICO_USBDEV_BULK_ONLY_EP1_THRU_16 0
#endif
// our interfaces are zero based number in the order they appear on the device - require that
#ifndef PICO_USBDEV_USE_ZERO_BASED_INTERFACES
#define PICO_USBDEV_USE_ZERO_BASED_INTERFACES 0
#endif
// no_init method for transfer
#ifndef PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD
#define PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD 0
#endif
// do on_cancel method for transfer
#ifndef PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD
#define PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD 0
#endif
// todo this needs to be part of configuration
#ifndef PICO_USBDEV_NO_INTERFACE_ALTERNATES
#define PICO_USBDEV_NO_INTERFACE_ALTERNATES 0
#endif
// todo this needs to be part of configuration
#ifndef PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE
#define PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE 0
#endif
static_assert((PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE >= 0) && (PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE < 4),
"");
// don't zero out most structures (since we do so globablly for BSS)
//#define USB_SKIP_COMMON_INIT
// only 16 bytes saved to not set a sense code
//#define USB_SILENT_FAIL_ON_EXCLUSIVE
struct usb_transfer;
struct usb_endpoint;
typedef void (*usb_transfer_func)(struct usb_endpoint *ep);
typedef void (*usb_transfer_completed_func)(struct usb_endpoint *ep, struct usb_transfer *transfer);
struct usb_buffer {
uint8_t *data;
uint8_t data_len;
uint8_t data_max;
// then...
bool valid; // aka user owned
};
#include "usb_device_private.h"
struct usb_transfer_type {
// for IN transfers this is called to setup new packet buffers
// for OUT transfers this is called with packet data
//
// In any case usb_packet_done must be called if this function has handled the buffer
usb_transfer_func on_packet;
#if !PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD
usb_transfer_func on_init;
#endif
#if !PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD
usb_transfer_func on_cancel;
#endif
uint8_t initial_packet_count;
};
struct usb_interface *usb_interface_init(struct usb_interface *interface, const struct usb_interface_descriptor *desc,
struct usb_endpoint *const *endpoints, uint endpoint_count,
bool double_buffered);
struct usb_device *usb_device_init(const struct usb_device_descriptor *desc,
const struct usb_configuration_descriptor *config_desc,
struct usb_interface *const *interfaces, uint interface_count,
const char *(*get_descriptor_string)(uint index));
void usb_device_start();
void usb_device_stop();
// explicit stall
void usb_halt_endpoint_on_condition(struct usb_endpoint *ep);
void usb_halt_endpoint(struct usb_endpoint *endpoint);
void usb_clear_halt_condition(struct usb_endpoint *ep);
static inline bool usb_is_endpoint_stalled(struct usb_endpoint *endpoint);
void usb_set_default_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer);
void usb_reset_transfer(struct usb_transfer *transfer, const struct usb_transfer_type *type,
usb_transfer_completed_func on_complete);
void usb_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer);
void usb_reset_and_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer,
const struct usb_transfer_type *type, usb_transfer_completed_func on_complete);
void usb_chain_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer);
void usb_grow_transfer(struct usb_transfer *transfer, uint packet_count);
void usb_start_default_transfer_if_not_already_running_or_halted(struct usb_endpoint *ep);
typedef void (*usb_transfer_func)(struct usb_endpoint *ep);
struct usb_buffer *usb_current_in_packet_buffer(struct usb_endpoint *ep);
struct usb_buffer *usb_current_out_packet_buffer(struct usb_endpoint *ep);
uint8_t *usb_get_single_packet_response_buffer(struct usb_endpoint *ep, uint len);
// call during (or asynchronously after) on_packet to mark the packet as done
void usb_packet_done(struct usb_endpoint *ep);
extern const struct usb_transfer_type usb_current_packet_only_transfer_type;
static inline struct usb_endpoint *usb_get_control_in_endpoint();
static inline struct usb_endpoint *usb_get_control_out_endpoint();
void usb_start_empty_control_in_transfer(usb_transfer_completed_func on_complete);
void usb_start_empty_control_in_transfer_null_completion();
void usb_start_tiny_control_in_transfer(uint32_t data, uint len);
void usb_start_single_buffer_control_in_transfer();
void usb_start_control_out_transfer(const struct usb_transfer_type *type);
void usb_start_empty_transfer(struct usb_endpoint *endpoint, struct usb_transfer *transfer,
usb_transfer_completed_func on_complete);
void usb_soft_reset_endpoint(struct usb_endpoint *ep);
void usb_hard_reset_endpoint(struct usb_endpoint *ep);
#if PICO_USBDEV_ENABLE_DEBUG_TRACE
void usb_dump_trace(void);
void usb_reset_trace(void);
#else
static inline void usb_dump_trace() {}
static inline void usb_reset_trace() {}
#endif
#define usb_warn(format, args...) ({printf("WARNING: "); printf(format, ## args); })
#if false && !defined(NDEBUG)
#define usb_debug(format,args...) printf(format, ## args)
#else
#define usb_debug(format, ...) ((void)0)
#endif
#if false && !defined(NDEBUG)
#define usb_trace(format,args...) printf(format, ## args)
#else
#define usb_trace(format, ...) ((void)0)
#endif
#endif

Wyświetl plik

@ -0,0 +1,135 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _USB_DEVICE_PRIVATE_H
#define _USB_DEVICE_PRIVATE_H
#include "usb/usb_common.h"
#include <stdbool.h>
struct usb_transfer {
// prototype
const struct usb_transfer_type *type;
usb_transfer_completed_func on_complete;
// total number of buffers (packets) that still need to be handed over to the hardware
// during the remaining course of the transfer (with data for IN, empty for data for out)
uint32_t remaining_packets_to_submit;
// total number of buffers when we will expect to receive IRQ/handle_buffer for during
// the remaining course of the transfer
uint32_t remaining_packets_to_handle;
#ifdef GENERAL_SIZE_HACKS
union {
struct {
#endif
// number of packets which require usb_packet_done()
bool outstanding_packet;
// received a packet which we couldn't deliver because there was one outstanding
bool packet_queued;
bool started;
bool completed;
#ifdef GENERAL_SIZE_HACKS
};
uint32_t all_flags;
};
#endif
};
struct usb_interface {
const struct usb_interface_descriptor *descriptor;
struct usb_endpoint *const *endpoints;
uint8_t endpoint_count;
bool (*setup_request_handler)(struct usb_interface *interface, struct usb_setup_packet *setup);
#if !PICO_USBDEV_NO_INTERFACE_ALTERNATES
bool (*set_alternate_handler)(struct usb_interface *interface, uint alt);
uint8_t alt;
#endif
};
struct usb_configuration {
const struct usb_configuration_descriptor *descriptor;
struct usb_interface *const *interfaces;
#ifndef PICO_USBDEV_FIXED_INTERFACE_COUNT
uint8_t interface_count;
#endif
};
#ifdef PICO_USBDEV_FIXED_INTERFACE_COUNT
#define _usb_interface_count(config) PICO_USBDEV_FIXED_INTERFACE_COUNT
#else
#define _usb_interface_count(config) config->interface_count
#endif
struct usb_device {
const struct usb_device_descriptor *descriptor;
#if !PICO_USBDEV_NO_DEVICE_SETUP_HANDLER
bool (*setup_request_handler)(struct usb_device *dev, struct usb_setup_packet *setup);
#endif
void (*on_configure)(struct usb_device *dev, bool configured);
const char *(*get_descriptor_string)(uint index);
// only support one config for now
struct usb_configuration config;
uint8_t current_address; // 0 if unaddressed
uint8_t current_config_num; // 0 if unconfigured
uint8_t pending_address; // address to set on completion of SET_ADDRESS CSW
uint16_t next_buffer_offset;
// bool started;
};
enum usb_halt_state {
HS_NONE = 0,
HS_NON_HALT_STALL = 1, // just stalled
HS_HALTED = 2, // halted or command halted
HS_HALTED_ON_CONDITION = 3 // halted that cannot be simply cleared by CLEAR_FEATURE
};
struct usb_endpoint {
const struct usb_endpoint_descriptor *descriptor;
struct usb_transfer *default_transfer;
struct usb_transfer *current_transfer;
struct usb_transfer *chain_transfer;
void (*on_stall_change)(struct usb_endpoint *ep);
#if !PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER
bool (*setup_request_handler)(struct usb_endpoint *ep, struct usb_setup_packet *setup);
#endif
uint16_t dpram_buffer_offset;
uint16_t buffer_size; // for an individual buffer
struct usb_buffer current_hw_buffer;
#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16
uint16_t buffer_stride;
#endif
uint8_t num;
uint8_t next_pid;
uint8_t buffer_bit_index;
uint8_t owned_buffer_count;
uint8_t current_give_buffer;
uint8_t current_take_buffer;
uint8_t halt_state;
bool first_buffer_after_reset;
bool double_buffered;
bool in;
};
static inline uint usb_endpoint_number(struct usb_endpoint *ep) {
assert(ep);
return ep->descriptor ? ep->descriptor->bEndpointAddress & 0xfu : 0;
}
static inline bool usb_is_endpoint_stalled(struct usb_endpoint *endpoint) {
return endpoint->halt_state != HS_NONE;
}
const char *usb_endpoint_dir_string(struct usb_endpoint *ep);
static inline struct usb_endpoint *usb_get_control_in_endpoint() {
extern struct usb_endpoint usb_control_in;
return &usb_control_in;
}
static inline struct usb_endpoint *usb_get_control_out_endpoint() {
extern struct usb_endpoint usb_control_out;
return &usb_control_out;
}
#endif //_USB_DEVICE_PRIVATE_H

Wyświetl plik

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _USB_STREAM_HELPER_H
#define _USB_STREAM_HELPER_H
#include "usb_device.h"
struct usb_transfer_funcs;
struct usb_stream_transfer {
struct usb_transfer core;
uint32_t offset; // offset within the stream
uint32_t transfer_length;
uint32_t chunk_size;
uint8_t *chunk_buffer;
struct usb_endpoint *ep;
const struct usb_stream_transfer_funcs *funcs;
#ifndef NDEBUG
bool packet_handler_complete_expected;
#endif
};
typedef void (*stream_on_packet_complete_function)(struct usb_stream_transfer *transfer);
typedef bool (*stream_on_chunk_function)(uint32_t chunk_len,
struct usb_stream_transfer *transfer);
struct usb_stream_transfer_funcs {
stream_on_packet_complete_function on_packet_complete;
// returns whether processing async
stream_on_chunk_function on_chunk;
};
void usb_stream_setup_transfer(struct usb_stream_transfer *transfer, const struct usb_stream_transfer_funcs *funcs,
uint8_t *chunk_buffer, uint32_t chunk_size, uint32_t transfer_length,
usb_transfer_completed_func on_complete);
void usb_stream_chunk_done(struct usb_stream_transfer *transfer);
void usb_stream_noop_on_packet_complete(struct usb_stream_transfer *transfer);
bool usb_stream_noop_on_chunk(uint32_t chunk_len, struct usb_stream_transfer *transfer);
#endif //SOFTWARE_USB_STREAM_HELPER_H

Wyświetl plik

@ -0,0 +1,138 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include <stdio.h>
#include "pico/usb_stream_helper.h"
static uint32_t _usb_stream_chunk_offset(struct usb_stream_transfer *transfer) {
return transfer->offset & (transfer->chunk_size - 1);
}
void usb_stream_packet_handler_complete(struct usb_stream_transfer *transfer) {
struct usb_buffer *buffer;
struct usb_endpoint *ep = transfer->ep;
#ifndef NDEBUG
assert(transfer->packet_handler_complete_expected);
transfer->packet_handler_complete_expected = false;
#endif
assert(ep);
if (ep->in) {
buffer = usb_current_in_packet_buffer(ep);
assert(buffer);
assert(buffer->data_max == 64);
uint chunk_offset = _usb_stream_chunk_offset(transfer);
uint data_len = 64;
if (transfer->offset + 64 > transfer->transfer_length) {
data_len = transfer->transfer_length - transfer->offset;
}
buffer->data_len = data_len;
memcpy(buffer->data, transfer->chunk_buffer + chunk_offset, data_len);
} else {
buffer = usb_current_out_packet_buffer(ep);
assert(buffer);
assert(buffer->data_len);
}
transfer->offset += buffer->data_len;
if (ep->num > 2) usb_debug(" %d transfer_offset %d\n", ep->num, (uint) transfer->offset);
assert(transfer->funcs && transfer->funcs->on_packet_complete);
transfer->funcs->on_packet_complete(transfer);
#ifdef USE_BOOTROM_GPIO
gpio_clr_mask(usb_activity_gpio_pin_mask);
#endif
usb_packet_done(ep);
}
void usb_stream_chunk_done(struct usb_stream_transfer *transfer) {
usb_stream_packet_handler_complete(transfer);
}
void _usb_stream_packet_packet_handler(struct usb_endpoint *ep) {
#ifdef USE_BOOTROM_GPIO
gpio_set_mask(usb_activity_gpio_pin_mask);
#endif
// todo assert type
struct usb_stream_transfer *transfer = (struct usb_stream_transfer *) ep->current_transfer;
uint chunk_offset = _usb_stream_chunk_offset(transfer);
uint chunk_len = 0; // set to non zero to call on_chunk
if (ep->in) {
if (!chunk_offset) {
// we are at the beginning of a chunk so want to call on_chunk
chunk_len = (transfer->offset + transfer->chunk_size) > transfer->transfer_length ?
transfer->transfer_length - transfer->offset : transfer->chunk_size;
if (ep->num > 2)
usb_warn("chunko %d len %05x offset %08x size %04x transfer %08x\n", ep->num, chunk_len, chunk_offset,
(uint) transfer->chunk_size, (uint) transfer->transfer_length);
}
} else {
// usb_debug("write packet %04x %d\n", (uint)transfer->offset, ep->current_take_buffer);
struct usb_buffer *buffer = usb_current_out_packet_buffer(ep);
assert(buffer);
// note we only set chunk_len if this is the end of a chunk
if (transfer->offset + 64 >= transfer->transfer_length) {
// we have ended the transfer (possibly mid-chunk)
chunk_len = transfer->transfer_length & (transfer->chunk_size - 1);
if (chunk_len) {
usb_warn(">> Truncated %08x\n", chunk_len);
} else {
chunk_len = transfer->chunk_size;
}
} else if (chunk_offset + 64 >= transfer->chunk_size) {
// end of regular chunk
chunk_len = transfer->chunk_size;
}
assert(chunk_len || buffer->data_len == 64);
// if (!(!chunk_len || buffer->data_len == ((chunk_len & 63u) ? (chunk_len & 63u) : 64u))) {
// usb_warn("ooh off=%08x len=%08x chunk_off=%04x chunk_len=%04x data_len=%04x\n", (uint)transfer->offset, (uint)transfer->transfer_length, chunk_offset, chunk_len, buffer->data_len);
// }
assert(!chunk_len || buffer->data_len == ((chunk_len & 63u) ? (chunk_len & 63u) : 64u));
// zero buffer when we start a new buffer, so that the chunk callback never sees data it shouldn't (for partial chunks)
if (!chunk_offset) {
memset(transfer->chunk_buffer, 0, transfer->chunk_size);
}
memcpy(transfer->chunk_buffer + chunk_offset, buffer->data, buffer->data_len); // always safe to copy all
}
#ifndef NDEBUG
transfer->packet_handler_complete_expected = true;
#endif
// todo i think this is reasonable since 0 length chunk does nothing
if (chunk_len) {
assert(transfer->funcs && transfer->funcs->on_chunk);
if (transfer->funcs->on_chunk(chunk_len, transfer))
return;
}
usb_stream_packet_handler_complete(transfer);
}
static const struct usb_transfer_type _usb_stream_transfer_type = {
.on_packet = _usb_stream_packet_packet_handler
};
void usb_stream_setup_transfer(struct usb_stream_transfer *transfer, const struct usb_stream_transfer_funcs *funcs,
uint8_t *chunk_buffer, uint32_t chunk_size, uint32_t transfer_length,
usb_transfer_completed_func on_complete) {
transfer->funcs = funcs;
transfer->chunk_buffer = chunk_buffer;
assert(!(chunk_size & 63u)); // buffer should be a multiple of USB packet buffer size
transfer->chunk_size = chunk_size;
transfer->offset = 0;
// todo combine with residue?
transfer->transfer_length = transfer_length;
usb_reset_transfer(&transfer->core, &_usb_stream_transfer_type, on_complete);
usb_grow_transfer(&transfer->core, (transfer_length + 63) / 64);
}
void usb_stream_noop_on_packet_complete(__unused struct usb_stream_transfer *transfer) {
}
bool usb_stream_noop_on_chunk(uint32_t size, __unused struct usb_stream_transfer *transfer) {
return false;
}

Wyświetl plik

@ -0,0 +1,8 @@
add_library(usb_device_msc INTERFACE)
target_sources(usb_device_msc INTERFACE
${CMAKE_CURRENT_LIST_DIR}/usb_device_msc.c
)
target_include_directories(usb_device_msc INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
target_link_libraries(usb_device_msc INTERFACE usb_device)

Wyświetl plik

@ -0,0 +1,99 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _SCSI_H
#define _SCSI_H
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#define CBW_SIG 0x43425355
struct __aligned(4) __packed scsi_cbw {
uint32_t sig;
uint32_t tag;
uint32_t data_transfer_length;
uint8_t flags;
uint8_t lun;
uint8_t cb_length;
uint8_t cb[16];
};
#define CSW_SIG 0x53425355
struct __packed scsi_csw {
uint32_t sig;
uint32_t tag;
uint32_t residue;
uint8_t status;
};
struct __packed scsi_capacity {
uint32_t lba; // last block addr
uint32_t block_len; // probably 512
};
struct __packed scsi_read_cb {
uint8_t opcode;
uint8_t flags;
uint32_t lba;
uint8_t reserved;
uint16_t blocks;
uint8_t control;
};
enum csw_status {
CSW_STATUS_COMMAND_PASSED = 0x00,
CSW_STATUS_COMMAND_FAILED = 0x01,
CSW_STATUS_PHASE_ERROR = 0x02,
};
enum scsi_cmd {
INQUIRY = 0x12,
MODE_SELECT_6 = 0x15,
MODE_SELECT_10 = 0x55,
MODE_SENSE_6 = 0x1a,
MODE_SENSE_10 = 0x5a,
PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e,
READ_6 = 0x08,
READ_10 = 0x28,
READ_12 = 0xa8,
READ_FORMAT_CAPACITIES = 0x23,
READ_CAPACITY_10 = 0x25,
REPORT_LUNS = 0xa0,
REQUEST_SENSE = 0x03,
SEND_DIAGNOSTIC = 0x1d,
START_STOP_UNIT = 0x1b,
SYNCHRONIZE_CACHE = 0x35,
TEST_UNIT_READY = 0x00,
VERIFY = 0x2f,
WRITE_6 = 0x0a,
WRITE_10 = 0x2a,
WRITE_12 = 0xaa,
};
enum scsi_sense_key {
SK_OK = 0x00,
SK_NOT_READY = 0x02,
SK_ILLEGAL_REQUEST = 0x05,
SK_UNIT_ATTENTION = 0x06,
SK_DATA_PROTECT = 0x07
};
enum scsi_additional_sense_code {
ASC_NONE = 0x00,
ASC_INVALID_COMMAND_OPERATION_CODE = 0x20,
ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03,
ASC_ACCESS_DENIED = 0x20,
ASC_LBA_OUT_OF_RANGE = 0x21,
ASC_WRITE_PROTECTED = 0x27,
ASC_NOT_READY_TO_READY_CHANGE = 0x28,
ASC_MEDIUM_NOT_PRESENT = 0x3a,
};
enum scsi_additional_sense_code_qualifier {
ASCQ_NA = 0x00,
};
#endif

Wyświetl plik

@ -0,0 +1,38 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _SCSI_IR_H
#define _SCSI_IR_H
// NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT
typedef unsigned char uint8_t;
struct scsi_inquiry_response {
uint8_t pdt;
uint8_t rmb;
uint8_t spc_version;
uint8_t rdf;
uint8_t additional_length;
uint8_t inquiry5;
uint8_t inquiry6;
uint8_t inquiry7;
char vendor[8];
char product[16];
char version[4];
} __packed;
#ifndef COMPRESS_TEXT
static const struct scsi_inquiry_response scsi_ir = {
.rmb = 0x80,
.spc_version = 2,
.rdf = 2,
.additional_length = sizeof(struct scsi_inquiry_response) - 4,
.vendor = "RPI ",
.product = "RP2 ",
.version = "1 ",
};
#endif
#endif

Wyświetl plik

@ -0,0 +1,21 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _USB_MSC_H
#define _USB_MSC_H
#define SECTOR_SIZE 512u
bool msc_setup_request_handler(struct usb_interface *interface, struct usb_setup_packet *setup);
void msc_on_configure(__unused struct usb_device *device, bool configured);
//struct usb_endpoint msc_in, msc_out;
extern struct usb_endpoint msc_endpoints[2];
// provided by the hosting code
uint32_t msc_get_serial_number32();
void msc_eject();
#endif

Wyświetl plik

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _VIRTUAL_DISK_H
#define _VIRTUAL_DISK_H
#include "usb_device_msc.h"
#define USE_INFO_UF2
void vd_init();
void vd_reset();
// return true for async operation
bool vd_read_block(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size);
bool vd_write_block(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size);
// give us ourselves 16M which should strictly be the minimum for FAT16 - Note Win10 doesn't like FAT12 - go figure!
// upped to 64M which allows us to download a 32M UF2
#define CLUSTER_UP_SHIFT 0u
#define CLUSTER_UP_MUL (1u << CLUSTER_UP_SHIFT)
#define VOLUME_SIZE (CLUSTER_UP_MUL * 128u * 1024u * 1024u)
#define SECTOR_COUNT (VOLUME_SIZE / SECTOR_SIZE)
#ifndef GENERAL_SIZE_HACKS
static inline uint32_t vd_sector_count() {
return SECTOR_COUNT;
}
#else
// needs to be a compile time constant
#define vd_sector_count() SECTOR_COUNT
#endif
void vd_async_complete(uint32_t token, uint32_t result);
#endif

Wyświetl plik

@ -0,0 +1,488 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#include "hardware/sync.h"
#include "pico/usb_device.h"
#include "pico/usb_device_msc.h"
#include "pico/scsi.h"
#include "pico/scsi_ir.h"
#include "pico/virtual_disk.h"
#include "pico/usb_stream_helper.h"
static __attribute__((aligned(4))) uint8_t _sector_buf[SECTOR_SIZE];
struct __packed scsi_request_sense_response {
uint8_t code;
uint8_t _pad;
uint8_t key;
uint32_t _info;
uint8_t additonal_sense_len;
uint32_t _cmd_specific;
uint8_t asc;
uint8_t ascq;
uint8_t _fruc;
uint8_t _sense_specific[3];
};
static_assert(sizeof(struct scsi_request_sense_response) == 18, "");
enum scsi_direction {
SCSI_DIR_NONE = 0,
SCSI_DIR_IN = 1,
SCSI_DIR_OUT = 2,
};
static struct msc_state {
struct scsi_csw csw;
struct scsi_request_sense_response request_sense;
uint32_t data_phase_length;
uint8_t stall_direction_before_csw;
bool send_csw_on_unstall;
bool ejected;
} _msc_state;
// not part of _msc_state since we never reset it
static uint32_t _msc_async_token;
void _msc_cmd_packet(struct usb_endpoint *ep);
static const struct usb_transfer_type _msc_cmd_transfer_type = {
.on_packet = _msc_cmd_packet,
.initial_packet_count = 1,
};
// note we need these to be adjacent, so rather than relying on the fact just making them into an array which seems to produce the same code otherwise
//struct usb_endpoint msc_in, msc_out;
struct usb_endpoint msc_endpoints[2];
#define msc_in msc_endpoints[0]
#define msc_out msc_endpoints[1]
static struct usb_transfer _msc_cmd_transfer;
static struct usb_transfer _msc_cmd_response_transfer;
static void _tf_wait_command(__unused struct usb_endpoint *ep, __unused struct usb_transfer *transfer) {
assert(ep == &msc_in);
usb_debug("_tf_wait_command\n");
assert(msc_out.default_transfer);
#ifndef GENERAL_SIZE_HACKS
usb_start_default_transfer_if_not_already_running_or_halted(&msc_out);
#else
assert(&msc_out == &msc_in + 1);
usb_start_default_transfer_if_not_already_running_or_halted(ep + 1);
#endif
}
static __noinline void _msc_reset_and_start_cmd_response_transfer(usb_transfer_completed_func func) {
usb_reset_and_start_transfer(&msc_in, &_msc_cmd_response_transfer, &usb_current_packet_only_transfer_type, func);
}
static void _msc_send_csw() {
_msc_state.send_csw_on_unstall = false;
uint8_t *buffer = usb_get_single_packet_response_buffer(&msc_in, sizeof(_msc_state.csw));
memcpy(buffer, &_msc_state.csw, sizeof(_msc_state.csw));
_msc_reset_and_start_cmd_response_transfer(_tf_wait_command);
}
static void _msc_set_csw_failed(enum scsi_sense_key sk, enum scsi_additional_sense_code asc,
enum scsi_additional_sense_code_qualifier ascq) {
_msc_state.csw.status = CSW_STATUS_COMMAND_FAILED;
_msc_state.request_sense.key = sk;
_msc_state.request_sense.asc = asc;
_msc_state.request_sense.ascq = ascq;
}
static void _msc_data_phase_complete() {
if (_msc_state.stall_direction_before_csw == SCSI_DIR_IN) {
_msc_state.stall_direction_before_csw = SCSI_DIR_NONE;
_msc_state.send_csw_on_unstall = true;
usb_debug("Stalling in\n");
usb_halt_endpoint(&msc_in);
} else {
if (_msc_state.stall_direction_before_csw == SCSI_DIR_OUT) {
_msc_state.stall_direction_before_csw = SCSI_DIR_NONE;
usb_debug("Stalling out\n");
usb_halt_endpoint(&msc_out);
}
_msc_send_csw();
}
}
#ifndef GENERAL_SIZE_HACKS
static void _tf_data_phase_complete(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) {
assert(endpoint == &msc_in || endpoint == &msc_out);
usb_debug("_tf_data_phase_complete\n");
_msc_data_phase_complete();
}
#else
#define _tf_data_phase_complete ((usb_transfer_completed_func)_msc_data_phase_complete)
#endif
// noinline here saves us 4 bytes; go figure
static enum scsi_direction _scsi_dir(const struct scsi_cbw *cbw) {
return (cbw->flags & USB_DIR_IN) ? SCSI_DIR_IN : SCSI_DIR_OUT;
}
static void _msc_init_for_dn(const struct scsi_cbw *cbw) {
_msc_state.stall_direction_before_csw = SCSI_DIR_NONE;
if (cbw->data_transfer_length) {
enum scsi_direction cbw_dir = _scsi_dir(cbw);
_msc_state.stall_direction_before_csw = cbw_dir;
}
_msc_data_phase_complete();
}
static bool _msc_init_for_di_or_do(const struct scsi_cbw *cbw, uint32_t expected_length, enum scsi_direction dir) {
_msc_state.stall_direction_before_csw = SCSI_DIR_NONE;
_msc_state.data_phase_length = 0;
enum scsi_direction cbw_dir = _scsi_dir(cbw);
if (cbw_dir != dir) {
usb_debug("Will stall because direction wrong\n");
_msc_state.stall_direction_before_csw = cbw_dir;
_msc_state.csw.status = CSW_STATUS_PHASE_ERROR;
} else {
if (expected_length != cbw->data_transfer_length) {
_msc_state.stall_direction_before_csw = dir;
}
if (expected_length > cbw->data_transfer_length) {
_msc_state.csw.status = CSW_STATUS_PHASE_ERROR;
}
_msc_state.data_phase_length = MIN(expected_length, cbw->data_transfer_length);
}
usb_debug("_msc_init_for_di exp = %d tran = %d stall = %d status = %d length = %d\n", (uint) expected_length,
(uint) cbw->data_transfer_length,
_msc_state.stall_direction_before_csw, _msc_state.csw.status, (uint) _msc_state.data_phase_length);
if (!_msc_state.data_phase_length) {
_msc_data_phase_complete();
return false;
}
return true;
}
static void _scsi_fail_cmd(const struct scsi_cbw *cbw, enum scsi_sense_key sk, enum scsi_additional_sense_code asc,
enum scsi_additional_sense_code_qualifier ascq) {
_msc_set_csw_failed(sk, asc, ascq);
// this handily takes care of the STALLing/CSW based on us not intending to send data
_msc_init_for_dn(cbw);
}
static void _scsi_standard_response(const struct scsi_cbw *cbw) {
struct usb_buffer *buffer = usb_current_in_packet_buffer(&msc_in);
assert(buffer->data_len);
if (_msc_init_for_di_or_do(cbw, MIN(buffer->data_len, cbw->data_transfer_length), SCSI_DIR_IN)) {
assert(_msc_state.data_phase_length <= buffer->data_len);
buffer->data_len = _msc_state.data_phase_length; // truncate buffer
_msc_state.csw.residue -= buffer->data_len;
_msc_reset_and_start_cmd_response_transfer(_tf_data_phase_complete);
}
}
static_assert(sizeof(struct scsi_inquiry_response) == 36, "");
static void _scsi_handle_inquiry_response(struct scsi_cbw *cbw) {
uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, sizeof(struct scsi_inquiry_response));
memcpy(buf, &scsi_ir, sizeof(scsi_ir));
_scsi_standard_response(cbw);
}
static struct msc_sector_transfer {
struct usb_stream_transfer stream;
uint32_t lba;
} _msc_sector_transfer;
static void _msc_on_sector_stream_packet_complete(__unused struct usb_stream_transfer *transfer) {
assert(transfer == &_msc_sector_transfer.stream);
_msc_state.csw.residue -= 64;
}
bool _msc_on_sector_stream_chunk(__unused uint32_t chunk_len, __unused struct usb_stream_transfer *transfer) {
assert(transfer == &_msc_sector_transfer.stream);
assert(chunk_len == SECTOR_SIZE);
bool (*vd_read_or_write)(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size);
vd_read_or_write = _msc_sector_transfer.stream.ep->in ? vd_read_block : vd_write_block;
return vd_read_or_write(++_msc_async_token, _msc_sector_transfer.lba++, _sector_buf, SECTOR_SIZE);
}
static const struct usb_stream_transfer_funcs _msc_sector_funcs = {
.on_packet_complete = _msc_on_sector_stream_packet_complete,
.on_chunk = _msc_on_sector_stream_chunk
};
// note that this may be called during regular vd_operation
void vd_async_complete(uint32_t token, uint32_t result) {
usb_debug("complete token %d\n", (int) token);
// note that this USB library is not thread safe, however this is the only function called
// from non IRQ handler code after usb_device_start; therefore we just disable IRQs for this call
uint32_t save = save_and_disable_interrupts();
if (token == _msc_async_token) {
if (result) {
// if we error, we'll just abort and send csw
// todo does it matter what we send? - we have a residue - prefer to send locked or write error
#ifndef USB_SILENT_FAIL_ON_EXCLUSIVE
_msc_set_csw_failed(SK_DATA_PROTECT, ASC_ACCESS_DENIED, 2); // no access rights
#endif
_msc_state.stall_direction_before_csw = SCSI_DIR_OUT;
_msc_data_phase_complete();
}
usb_stream_chunk_done(&_msc_sector_transfer.stream);
} else {
usb_warn("async complete for incorrect token %d != %d\n", (int) token, (int) _msc_async_token);
}
restore_interrupts(save);
}
static void _scsi_read_or_write_blocks(const struct scsi_cbw *cbw, uint32_t lba, uint32_t blocks,
enum scsi_direction dir) {
assert(dir);
_msc_sector_transfer.stream.ep = (dir == SCSI_DIR_IN) ? &msc_in : &msc_out;
_msc_sector_transfer.lba = lba;
uint32_t expected_length = blocks * SECTOR_SIZE;
if (_msc_init_for_di_or_do(cbw, expected_length, dir)) {
assert(_msc_state.data_phase_length <= expected_length);
expected_length = _msc_state.data_phase_length /
64; // round down... this means we may send less than dwTransferLength, but residue will be correct
// todo we could remove the if if start_transfer allows empty transfers
if (expected_length) {
_msc_async_token++;
// transfer length is exact multiple of 64 as per above rounding comment
usb_stream_setup_transfer(&_msc_sector_transfer.stream, &_msc_sector_funcs, _sector_buf, SECTOR_SIZE,
expected_length * 64,
_tf_data_phase_complete);
if (dir == SCSI_DIR_IN) {
usb_start_transfer(&msc_in, &_msc_sector_transfer.stream.core);
} else {
usb_chain_transfer(&msc_out, &_msc_sector_transfer.stream.core);
}
} else {
_msc_data_phase_complete();
}
}
}
static void _scsi_handle_test_unit_ready(const struct scsi_cbw *cbw) {
if (_msc_state.ejected) {
return _scsi_fail_cmd(cbw, SK_NOT_READY, ASC_MEDIUM_NOT_PRESENT, ASCQ_NA);
}
return _msc_init_for_dn(cbw);
}
void msc_eject() {
_msc_state.ejected = true;
}
static void _scsi_handle_start_stop_unit(const struct scsi_cbw *cbw) {
if (2u == (cbw->cb[4] & 3u)) {
usb_warn("EJECT immed %02x\n", cbw->cb[1]);
msc_eject();
}
return _msc_init_for_dn(cbw);
}
static void _scsi_handle_read_or_write_command(const struct scsi_cbw *cbw, enum scsi_direction dir) {
const struct scsi_read_cb *cb = (const struct scsi_read_cb *) &cbw->cb[0];
uint32_t lba = __builtin_bswap32(cb->lba);
uint16_t blocks = __builtin_bswap16(cb->blocks);
usb_debug(dir == SCSI_DIR_IN ? "Read %d blocks starting at lba %ld\n" :
"Write %d blocks starting at lba %ld\n",
blocks, lba);
_scsi_read_or_write_blocks(cbw, lba, blocks, dir);
}
static void _scsi_memcpy_response(const struct scsi_cbw *cbw, uint8_t *data, uint len) {
memcpy(usb_get_single_packet_response_buffer(&msc_in, len), data, len);
_scsi_standard_response(cbw);
}
static void _scsi_handle_read_capacity(const struct scsi_cbw *cbw) {
struct scsi_capacity _resp = {
.lba = __builtin_bswap32(vd_sector_count() - 1),
.block_len = __builtin_bswap32(SECTOR_SIZE)
};
_scsi_memcpy_response(cbw, (uint8_t *) &_resp, sizeof(_resp));
}
struct __packed scsi_read_format_capacity_response {
uint8_t _pad[3];
uint8_t descriptors_size;
uint32_t descriptor_1_block_count_msb;
uint32_t descriptor_1_type_and_block_size;
};
static void _scsi_handle_read_format_capacities(const struct scsi_cbw *cbw) {
struct scsi_read_format_capacity_response _resp = {
.descriptor_1_block_count_msb = __builtin_bswap32(vd_sector_count() - 1),
.descriptor_1_type_and_block_size = 2u | // formatted
__builtin_bswap32(SECTOR_SIZE)
};
_scsi_memcpy_response(cbw, (uint8_t *) &_resp, sizeof(_resp));
}
static void _scsi_handle_request_sense(const struct scsi_cbw *cbw) {
uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, sizeof(_msc_state.request_sense));
// printf("RS %d\n", scsi.request_sense.key);
memcpy(buf, &_msc_state.request_sense, sizeof(_msc_state.request_sense));
_msc_state.request_sense.key = SK_OK;
_msc_state.request_sense.asc = 0;
_msc_state.request_sense.ascq = 0;
_scsi_standard_response(cbw);
}
static void _scsi_handle_mode_sense(const struct scsi_cbw *cbw) {
uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, 4);
*(uint32_t *) buf = 3;
_scsi_standard_response(cbw);
}
static void _msc_in_on_stall_change(struct usb_endpoint *ep) {
usb_debug("Stall change in stalled %d send csw %d \n", usb_is_endpoint_stalled(ep), _msc_state.send_csw_on_unstall);
if (!usb_is_endpoint_stalled(ep) && ep == &msc_in) {
// todo we need to clear this on the ep cancel
if (_msc_state.send_csw_on_unstall) {
usb_debug("Sending CSW on unstall\n");
_msc_send_csw();
}
}
}
static void _msc_reset(void) {
static bool one_time;
if (!one_time) {
_msc_cmd_transfer.type = &_msc_cmd_transfer_type;
usb_set_default_transfer(&msc_out, &_msc_cmd_transfer);
msc_in.on_stall_change = _msc_in_on_stall_change;
vd_init();
one_time = true;
}
memset(&_msc_state, 0, sizeof(_msc_state));
_msc_state.request_sense.code = 0x70;
_msc_state.request_sense.additonal_sense_len = 0xa;
vd_reset();
usb_soft_reset_endpoint(&msc_in);
usb_soft_reset_endpoint(&msc_out);
}
static void _msc_cmd_halt() {
usb_halt_endpoint_on_condition(&msc_in);
usb_halt_endpoint_on_condition(&msc_out);
}
static void _msc_cmd_packet_internal(struct usb_endpoint *ep) {
struct usb_buffer *buffer = usb_current_out_packet_buffer(ep);
uint len = buffer->data_len;
struct scsi_cbw *cbw = (struct scsi_cbw *) buffer->data;
if (len == 31u && cbw->sig == CBW_SIG && !cbw->lun && !(cbw->flags & 0x7fu) && cbw->cb_length &&
cbw->cb_length <= 16) {
// todo we need to validate CBW sizes
_msc_state.csw.sig = CSW_SIG;
_msc_state.csw.tag = cbw->tag;
_msc_state.csw.residue = cbw->data_transfer_length;
usb_debug("SCSI: ");
enum scsi_cmd cmd = cbw->cb[0];
if (cmd != REQUEST_SENSE) {
_msc_state.request_sense.key = SK_OK;
_msc_state.request_sense.asc = 0;
_msc_state.request_sense.ascq = 0;
}
_msc_state.csw.status = CSW_STATUS_COMMAND_PASSED;
switch (cmd) {
case INQUIRY:
usb_debug("INQUIRY\n");
return _scsi_handle_inquiry_response(cbw);
case MODE_SENSE_6:
usb_debug("MODESENSE(6)\n");
return _scsi_handle_mode_sense(cbw);
case PREVENT_ALLOW_MEDIUM_REMOVAL:
usb_debug("PREVENT ALLOW MEDIUM REMOVAL\n");// %d\n", buf[4] & 3u);
// Nothing to do just reply success
return _msc_init_for_dn(cbw);
case READ_10:
usb_debug("READ(10)\n");
return _scsi_handle_read_or_write_command(cbw, SCSI_DIR_IN);
case WRITE_10:
usb_debug("WRITE(10)\n");
return _scsi_handle_read_or_write_command(cbw, SCSI_DIR_OUT);
case READ_FORMAT_CAPACITIES:
usb_debug("READ FORMAT_CAPACITIES\n");
return _scsi_handle_read_format_capacities(cbw);
case READ_CAPACITY_10:
usb_debug("READ CAPACITY(10)\n");
return _scsi_handle_read_capacity(cbw);
case REQUEST_SENSE:
usb_debug("REQUEST SENSE\n");
return _scsi_handle_request_sense(cbw);
case TEST_UNIT_READY:
usb_debug("TEST UNIT READY\n");
return _scsi_handle_test_unit_ready(cbw);
case START_STOP_UNIT:
usb_debug("START STOP UNIT\n");
return _scsi_handle_start_stop_unit(cbw);
case SYNCHRONIZE_CACHE:
usb_debug("SYNCHRONIZE CACHE(10)\n");
return _msc_init_for_dn(cbw);
case VERIFY:
usb_debug("VERIFY\n");
return _msc_init_for_dn(cbw);
default:
usb_debug("cmd %02x\n", cbw->cb[0]);
break;
}
return _scsi_fail_cmd(cbw, SK_ILLEGAL_REQUEST, ASC_INVALID_COMMAND_OPERATION_CODE, ASCQ_NA);
} else {
usb_debug("invalid cbw\n");
return _msc_cmd_halt();
}
}
void _msc_cmd_packet(struct usb_endpoint *ep) {
_msc_cmd_packet_internal(ep);
usb_packet_done(ep);
}
bool msc_setup_request_handler(__unused struct usb_interface *interface, struct usb_setup_packet *setup) {
setup = __builtin_assume_aligned(setup, 4);
if (USB_REQ_TYPE_TYPE_CLASS == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) {
if (setup->bmRequestType & USB_DIR_IN) {
if (setup->bRequest == USB_REQUEST_MSC_GET_MAX_LUN) {
if (!setup->wValue && setup->wLength) {
usb_debug("GET_MAX_LUN\n");
struct usb_buffer *buffer = usb_current_in_packet_buffer(usb_get_control_in_endpoint());
buffer->data[0] = 0;
buffer->data_len = 1;
usb_start_single_buffer_control_in_transfer();
return true;
} else {
usb_debug("INVALID GET_MAX_LUN\n");
}
}
} else {
if (setup->bRequest == USB_REQUEST_MSC_RESET) {
if (!setup->wValue && !setup->wLength) {
usb_debug("MSC_RESET\n");
// doesn't unstall, but allows CLEAR_HALT to proceed
usb_clear_halt_condition(&msc_in);
usb_clear_halt_condition(&msc_out);
_msc_reset();
usb_start_empty_control_in_transfer_null_completion();
return true;
} else {
usb_debug("INVALID MSC_RESET\n");
}
}
}
}
return false;
}
void msc_on_configure(__unused struct usb_device *device, bool configured) {
if (configured) {
_msc_reset();
}
}

Wyświetl plik

@ -0,0 +1,2 @@
add_subdirectory(sample_conversion_test)
add_subdirectory(sd_test)

Wyświetl plik

@ -0,0 +1,9 @@
if (NOT PICO_ON_DEVICE OR NOT PICO_NO_FLASH) # too big for RAM
add_executable(sample_conversion_test sample_conversion_test.cpp)
target_compile_definitions(sample_conversion_test PRIVATE
#PICO_ENTER_USB_BOOT_ON_EXIT=1
)
target_link_libraries(sample_conversion_test PRIVATE pico_stdlib pico_audio)
pico_add_extra_outputs(sample_conversion_test)
endif()

Wyświetl plik

@ -0,0 +1,129 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include <iostream>
#include <sstream>
#include "pico/stdlib.h"
#include "pico/bit_ops.h"
#include "pico/sample_conversion.h"
typedef int (*sample_converter_fn)(int);
int u16_to_u16(int s) { return (uint16_t) s; }
int s16_to_u16(int s) { return (uint16_t) (s ^ 0x8000u); }
int u8_to_u16(int s) { return (uint16_t) (s << 8); }
int s8_to_u16(int s) { return (uint16_t) ((s << 8u) ^ 0x8000u); }
int u16_to_s16(int s) { return (int16_t) (s ^ 0x8000u); }
int s16_to_s16(int s) { return (int16_t) s; }
int u8_to_s16(int s) { return (int16_t) ((s << 8u) ^ 0x8000u); }
int s8_to_s16(int s) { return (int16_t) (s << 8u); }
int u16_to_u8(int s) { return (uint8_t) (s >> 8u); }
int s16_to_u8(int s) { return (uint8_t) ((s ^ 0x8000u) >> 8u); }
int u8_to_u8(int s) { return (uint8_t) s; }
int s8_to_u8(int s) { return (uint8_t) (s ^ 0x80); }
int u16_to_s8(int s) { return (int8_t) ((s ^ 0x8000u) >> 8u); }
int s16_to_s8(int s) { return (int8_t) (s >> 8u); }
int u8_to_s8(int s) { return (int8_t) (s ^ 0x80); }
int s8_to_s8(int s) { return (int8_t) s; }
template<typename Fmt>
typename Fmt::sample_t random_sample() {
return (typename Fmt::sample_t) rand();
}
void check_sample(int from, int expected, int actual) {
if (expected != actual) {
printf("Failed converting %04x to %04x (got %04x)\n", from, expected, actual);
assert(false);
}
}
template<typename ToFmt, typename FromFmt>
void check_conversion(sample_converter_fn converter_fn) {
uint length = 256 + rand() & 0xffu;
typename ToFmt::sample_t to_buffer[length * ToFmt::channel_count];
typename FromFmt::sample_t from_buffer[length * FromFmt::channel_count];
for (uint i = 0; i < length * FromFmt::channel_count; i++) {
from_buffer[i] = random_sample<FromFmt>();
}
converting_copy<ToFmt, FromFmt>::copy(to_buffer, from_buffer, length);
if (ToFmt::channel_count == FromFmt::channel_count) {
for (uint i = 0; i < length * ToFmt::channel_count; i++) {
check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i]);
}
} else if (ToFmt::channel_count == 2 & FromFmt::channel_count == 1) {
// mono -> stereo duplicates
for (uint i = 0; i < length; i++) {
check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i * 2]);
check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i * 2 + 1]);
}
} else if (ToFmt::channel_count == 1 & FromFmt::channel_count == 2) {
// stereo -> mono averages
for (uint i = 0; i < length; i++) {
// can't represent both samples
check_sample(0xf00d, converter_fn((from_buffer[i * 2] + from_buffer[i * 2 + 1]) / 2), to_buffer[i]);
}
} else {
assert(false);
}
}
template<class ToFmt, class FromFmt>
void check_conversions(sample_converter_fn converter_fn) {
// for a given format check conversions to and from
check_conversion<Mono<ToFmt>, Mono<FromFmt>>(converter_fn);
check_conversion<Stereo<ToFmt>, Mono<FromFmt>>(converter_fn);
check_conversion<Mono<ToFmt>, Stereo<FromFmt>>(converter_fn);
check_conversion<Stereo<ToFmt>, Stereo<FromFmt>>(converter_fn);
}
int main() {
// On FPGA, pins 28 and 29 are connected to the VC707 board USB-UART
uart_init(uart0, 115200);
gpio_set_function(28, GPIO_FUNC_UART);
gpio_set_function(29, GPIO_FUNC_UART);
// check all permutations of supported formats
check_conversions<FmtU16, FmtU16>(u16_to_u16);
check_conversions<FmtS16, FmtU16>(u16_to_s16);
check_conversions<FmtU8, FmtU16>(u16_to_u8);
check_conversions<FmtS8, FmtU16>(u16_to_s8);
check_conversions<FmtU16, FmtS16>(s16_to_u16);
check_conversions<FmtS16, FmtS16>(s16_to_s16);
check_conversions<FmtU8, FmtS16>(s16_to_u8);
check_conversions<FmtS8, FmtS16>(s16_to_s8);
check_conversions<FmtU16, FmtU8>(u8_to_u16);
check_conversions<FmtS16, FmtU8>(u8_to_s16);
check_conversions<FmtU8, FmtU8>(u8_to_u8);
check_conversions<FmtS8, FmtU8>(u8_to_s8);
check_conversions<FmtU16, FmtS8>(s8_to_u16);
check_conversions<FmtS16, FmtS8>(s8_to_s16);
check_conversions<FmtU8, FmtS8>(s8_to_u8);
check_conversions<FmtS8, FmtS8>(s8_to_s8);
printf("OK\n");
}

Wyświetl plik

@ -0,0 +1,15 @@
if (PICO_ON_DEVICE)
if (TARGET pico_sd_card)
add_executable(sd_test
sd_test.c
)
# target_compile_definitions(sd_test PRIVATE
# PICO_DEFAULT_UART_TX_PIN=28
# PICO_DEFAULT_UART_RX_PIN=29
# PICO_DEFAULT_UART=0
# )
target_link_libraries(sd_test pico_stdlib pico_sd_card)
pico_add_extra_outputs(sd_test)
endif()
endif()

Wyświetl plik

@ -0,0 +1,131 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/sd_card.h"
static uint8_t sector_data[1024];
#define ENABLE_4_PIN 0
int main(void) {
set_sys_clock_48mhz();
stdio_init_all();
printf("SD Card test\n");
int i = 0;
// 0-2 for SD_CLK, SD_CMD, SD_DAT
int sd_pin_base = 25;
// gpio_init(0);
// gpio_init(5);
// gpio_init(10);
// gpio_set_dir_out_masked(0x421);
#if ENABLE_4_PIN
if (sd_init_4pin() < 0)
#else
if (sd_init_1pin() < 0)
#endif
{
panic("doh");
}
static int block_base = 0;
#define BLOCK_COUNT 2
#define STREAMING
#ifdef STREAMING
static uint32_t b[BLOCK_COUNT * 128];
for(int div = 4; div >= 1; div--)
{
uint8_t *buf = (uint8_t *)b;
printf("-----------------------\n");
printf("SPEED %uMB/s\n", 12/div);
sd_set_clock_divider(div);
printf("1 bit no crc\n");
sd_set_wide_bus(false);
memset(buf, 0xaa, 512);
sd_readblocks_sync(b, block_base, BLOCK_COUNT);
for(int byte = 0; byte < 512; byte += 16)
{
printf("%08x ", i * 512 + byte);
for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]);
for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.');
printf("\n");
}
#if ENABLE_4_PIN
memset(buf, 0xaa, 512);
printf("4 bit no crc\n");
sd_set_wide_bus(true);
sd_readblocks_sync(b, block_base, BLOCK_COUNT);
for(int byte = 0; byte < 512; byte += 16)
{
printf("%08x ", i * 512 + byte);
for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]);
for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.');
printf("\n");
}
#endif
memset(buf, 0xaa, 512);
printf("1 bit crc\n");
sd_read_sectors_1bit_crc_async(b, block_base, BLOCK_COUNT);
int status = 0;
while (!sd_scatter_read_complete(&status));
printf("Status: %d\n", status);
#endif
for(i = 0; i < BLOCK_COUNT; i++)
{
#ifndef STREAMING
uint8_t *buf = sd_readblock(i);
#endif
//if (i == BLOCK_COUNT-1)
for(int byte = 0; byte < 512; byte += 16)
{
printf("%08x ", i * 512 + byte);
for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]);
for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.');
printf("\n");
}
printf("\n");
#ifdef STREAMING
buf += 512;
#endif
}
}
#if 0
strcpy(sector_data, "fish And Hello there zif squiffy!");
sector_data[511] = 0xaa;
sd_writeblocks_async((uint32_t*)sector_data, 0, 1);
static int timeout = 10;
int rc;
while (!sd_write_complete(&rc)) {
printf("Waiting for completion\n");
if (!--timeout) break;
}
printf("Done %d!\n", rc);
strcpy(sector_data, "vasil fleplic yoeville frentucky arrivant sklim avary ron giblet And Hello there zif squiffy!");
sector_data[511] = 0x55;
strcpy(sector_data + 512, "and this is sector 2 folks");
sd_writeblocks_async((uint32_t*)sector_data, 0, 2);
timeout = 10;
while (!sd_write_complete(&rc)) {
printf("Waiting for completion\n");
if (!--timeout) break;
}
#endif
printf("Done!\n");
__breakpoint();
}