kopia lustrzana https://github.com/raspberrypi/pico-extras
Initial Release
commit
f5c7be9a86
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
.vscode
|
||||
cmake-*
|
|
@ -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
|
|
@ -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 ()
|
|
@ -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.
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c385f31076b27efb8ee37f00cb5568783a58f299
|
|
@ -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})
|
|
@ -0,0 +1,4 @@
|
|||
add_subdirectory(common)
|
||||
if (PICO_ON_DEVICE)
|
||||
add_subdirectory(rp2_common)
|
||||
endif()
|
|
@ -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)
|
|
@ -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()
|
|
@ -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);
|
||||
}
|
|
@ -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}
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
pico_simple_hardware_target(rosc)
|
|
@ -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, you’ll 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
|
|
@ -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));
|
||||
}
|
|
@ -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()
|
|
@ -0,0 +1 @@
|
|||
This directory contains files required to get lwip working on the Pico platform without needing to fork lwIP
|
|
@ -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__ */
|
|
@ -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());
|
||||
}
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
%}
|
|
@ -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
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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()
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
%}
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
THIS IS CURRENTLY NOT COMPILING (A PLACEHOLDER THAT NEEDS FIXING)
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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
|
Plik diff jest za duży
Load Diff
|
@ -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()
|
|
@ -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
|
Plik diff jest za duży
Load Diff
|
@ -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
|
|
@ -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()
|
|
@ -0,0 +1 @@
|
|||
THIS CODE IS AT PROTOTYPING LEVEL ONLY (THOUGH IS USABLE)
|
|
@ -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
|
||||
};
|
|
@ -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
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
pico_simple_hardware_target(sleep)
|
||||
target_link_libraries(hardware_sleep INTERFACE
|
||||
hardware_clocks
|
||||
hardware_rosc
|
||||
hardware_rtc)
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
add_library(usb_common INTERFACE)
|
||||
|
||||
target_include_directories(usb_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Plik diff jest za duży
Load Diff
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
add_subdirectory(sample_conversion_test)
|
||||
add_subdirectory(sd_test)
|
|
@ -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()
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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()
|
|
@ -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();
|
||||
}
|
Ładowanie…
Reference in New Issue