Repository: steve-m/hsdaoh-rp2350 Branch: master Commit: e4d3619514bd Files: 34 Total size: 116.0 KB Directory structure: gitextract_c3pp2wt3/ ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── apps/ │ ├── CMakeLists.txt │ ├── counter/ │ │ ├── CMakeLists.txt │ │ ├── counter.c │ │ └── counter.pio │ ├── dual_external_adc/ │ │ ├── CMakeLists.txt │ │ ├── adc_24bit_input.pio │ │ ├── dual_external_adc.c │ │ └── pcm1802_fmt00.pio │ ├── external_adc/ │ │ ├── CMakeLists.txt │ │ ├── adc_12bit_input.pio │ │ ├── external_adc.c │ │ └── pcm1802_fmt00.pio │ ├── external_dualchan_adc/ │ │ ├── CMakeLists.txt │ │ ├── dualchan_adc_12bit_input.pio │ │ └── external_dualchan_adc.c │ ├── internal_adc/ │ │ ├── CMakeLists.txt │ │ └── internal_adc.c │ ├── logic_analyzer/ │ │ ├── 16bit_input.pio │ │ ├── CMakeLists.txt │ │ └── logic_analyzer.c │ └── sdr/ │ ├── CMakeLists.txt │ ├── adc_16bit_input.pio │ ├── adc_20bit_input.pio │ └── sdr.c ├── libpicohsdaoh/ │ ├── CMakeLists.txt │ ├── data_packet.c │ ├── data_packet.h │ ├── picohsdaoh.c │ └── picohsdaoh.h └── pico_sdk_import.cmake ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ build/* ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) include(pico_sdk_import.cmake) project(hsdaoh-rp2350 C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) pico_sdk_init() add_compile_options(-Wall) include_directories( include ) add_subdirectory(libpicohsdaoh) add_subdirectory(apps) ================================================ FILE: LICENSE ================================================ BSD 3-Clause License Copyright (c) 2024 by Steve Markgraf 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. ================================================ FILE: README.md ================================================ # hsdaoh-rp2350 - High Speed Data Acquisition over HDMI ## Stream up to 175 MByte/s from your Raspberry Pi Pico2 to your PC Using $5 USB3 HDMI capture sticks based on the MacroSilicon MS2130, this project allows to stream out up to 175 MByte/s of real time data from an RP2350 (with overclocking) to a host computer with USB3. For more information and the host library, see the [main repository](https://github.com/steve-m/hsdaoh) and the [talk at OsmoDevcon '24](https://media.ccc.de/v/osmodevcon2024-200-low-cost-high-speed-data-acquisition-over-hdmi). ![Raspberry Pi Pico2 with MS2130 stick](https://steve-m.de/projects/hsdaoh/pico2_hsdaoh.jpg) ## Building Make sure you have the latest version of the [pico-sdk](https://github.com/raspberrypi/pico-sdk) installed together with an appropriate compiler. You should be able to build the [pico-examples](https://github.com/raspberrypi/pico-examples). To build hsdaoh-rp2350: git clone https://github.com/steve-m/hsdaoh-rp2350.git mkdir hsdaoh-rp2350/build cd hsdaoh-rp2350/build export PICO_SDK_PATH=//pico-sdk cmake -DPICO_PLATFORM=rp2350 -DPICO_BOARD=pico2 ../ make -j 8 After the build succeeds you can copy the resulting *.uf2 file of the application you want to run to the board. Apart from the Pico2 with the [Pico-DVI-Sock](https://github.com/Wren6991/Pico-DVI-Sock), it also should work with the Adafruit Feather RP2350 with HSTX Port, but so far only the Pico2 was tested. ## Example applications The repository contains a library - libpicohsdaoh - which implements the main functionality. It reads the data from a ringbuffer, and streams it out via the HSTX port. In addition to that, the apps folder contains a couple of example applications: ### counter This application uses the PIO to generate a 16-bit counter value which is written to a DMA ringbuffer, which is then streamed out via hsdaoh. The counter can be verified using the hsdaoh_test host application. ### logic_analyzer Sample 16 GPIOs with the PIO and transfer the data, can be used as a 16 bit @ 32 MHz logic analyzer, or be adapted to 8 bit @ 64 MHz and so on. The IOs used for input are GP0-11, GP20-22 and GP26. ### internal_adc The data from the internal ADC is streamed out via USB. Default configuration is overclocking the ADC to 3.33 MS/s. Using the USB PLL and overvolting beyond VREG_VOLTAGE_MAX, up to 7.9 MS/s can be achieved. ### external_adc This app contains a PIO program that reads the data from a 12-bit ADC connected to GP0-GP11, outputs the ADC clock on GP20, and packs the 12-bit samples to 16-bit words to achieve maximum throughput. It is meant to be used with cheap AD9226 ADC boards. The default setting is overclocking the RP2350 to 160 MHz and driving the ADC with a 40 MHz clock. With higher overclocking up to 96 MHz ADC clock can be used. This can be used for sampling the IF of a tuner/downcoverter, as a direct-sampling HF SDR, or for capturing a video signal e.g. with [vhsdecode](https://github.com/oyvindln/vhs-decode). For the vhsdecode use-case, there is also an [adapter PCB](https://github.com/Sev5000/Pico2_12bitADC_PCMAudio). It also supports sampling a PCM1802 audio ADC board. ![Pico2 with AD9226 ADC board](https://steve-m.de/projects/hsdaoh/rp2350_external_adc_rot.jpg) ### external_dualchan_adc Similar to the external_adc app, but samples a AD9238 dual channel 12-bit ADC connected to GP0-GP11, with clock for both channels on GP20. The benefit of using the AD9238 is that only 12 data lines are required to transfer the data DDR-style, so it works with a regular RP2350A/Pico2 board. ### dual_external_adc Also similar to the external_adc app, but samples two 12-bit ADCs connected to a RP2350B, as well as two PCM1802 modules. Intended for use with vhs-decode, see [this PCB](https://github.com/Sev5000/RP2350B_DualADC_DualPCM) for the matching hardware. This example needs to be built with an RP2350B-board in order to work correctly: cmake -DPICO_PLATFORM=rp2350 -DPICO_BOARD=solderparty_rp2350_stamp_xl ../ ### sdr This app can be used for attaching the [hsdaohSDR prototype](https://github.com/steve-m/hsdaohSDR) to an RP2350B instead of the Tang nano 20K FPGA board. Contains PIO code for packing the 2x 10 bit AD9218 data, and also implements a USB UART to I2C bridge for controlling the tuner. Like the dual_external_adc, it also needs to be built with a RP2350B board. ## Credits hsdaoh-rp2350 is developed by Steve Markgraf, and is based on the [dvi_out_hstx_encoder](https://github.com/raspberrypi/pico-examples/tree/master/hstx/dvi_out_hstx_encoder) example, and code by Shuichi Takano [implementing the HDMI data island encoding](https://github.com/shuichitakano/pico_lib/blob/master/dvi/data_packet.cpp), required to send HDMI info frames. ================================================ FILE: apps/CMakeLists.txt ================================================ add_subdirectory(counter) add_subdirectory(external_adc) add_subdirectory(dual_external_adc) add_subdirectory(external_dualchan_adc) add_subdirectory(sdr) add_subdirectory(internal_adc) add_subdirectory(logic_analyzer) ================================================ FILE: apps/counter/CMakeLists.txt ================================================ add_executable(counter counter.c ) target_compile_options(counter PRIVATE -Wall) target_link_libraries(counter pico_stdlib pico_multicore pico_util hardware_pio hardware_dma libpicohsdaoh ) pico_generate_pio_header(counter ${CMAKE_CURRENT_LIST_DIR}/counter.pio) # enable usb output, disable uart output pico_enable_stdio_usb(counter 1) # create map/bin/hex file etc. pico_add_extra_outputs(counter) ================================================ FILE: apps/counter/counter.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * PIO counter value example, counter can be verified with hsdaoh_test * * Copyright (c) 2024 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "picohsdaoh.h" #include "counter.pio.h" #define SYS_CLK 336000 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = RBUF_MAX_DATA_LEN; hsdaoh_update_head(0, ringbuf_head); } void init_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &counter_program); uint sm_data = pio_claim_unused_sm(pio, true); counter_program_init(pio, sm_data, offset); dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], RBUF_MAX_DATA_LEN, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], RBUF_MAX_DATA_LEN, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } int main() { vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/1 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 1 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_4MA, GPIO_SLEW_RATE_SLOW); hsdaoh_add_stream(0, 1, (SYS_CLK/8) * 1000, RBUF_MAX_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_start(); init_pio_input(); while (1) __wfi(); } ================================================ FILE: apps/counter/counter.pio ================================================ ; ; Copyright (c) 2024 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Generate 16 bit counter in PIO ; .pio_version 0 .program counter public entry_point: .wrap_target jmp x-- dummylabel dummylabel: mov isr, ~x push .wrap % c-sdk { static inline void counter_program_init(PIO pio, uint sm, uint offset) { pio_sm_config c = counter_program_get_default_config(offset); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) false, // Autopush enabled 1 // Autopush threshold, ignored ); // disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 1.75f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/dual_external_adc/CMakeLists.txt ================================================ add_executable(dual_external_adc dual_external_adc.c ) target_compile_options(dual_external_adc PRIVATE -Wall) target_link_libraries(dual_external_adc pico_stdlib pico_util hardware_pio hardware_dma libpicohsdaoh ) pico_generate_pio_header(dual_external_adc ${CMAKE_CURRENT_LIST_DIR}/adc_24bit_input.pio) pico_generate_pio_header(dual_external_adc ${CMAKE_CURRENT_LIST_DIR}/pcm1802_fmt00.pio) # enable usb output, disable uart output pico_enable_stdio_usb(dual_external_adc 1) # create map/bin/hex file etc. pico_add_extra_outputs(dual_external_adc) ================================================ FILE: apps/dual_external_adc/adc_24bit_input.pio ================================================ ; ; Copyright (c) 2024-2025 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Sample 2x12 bit parallel ADC (AD9226) every 2 PIO cycles on rising clock edge, ; pack four 24 bit samples in three 32 bit words ; ADC clock output as side-set ; ; Data being pushed to the FIFO, four 24 bit samples A-D ; First word: A07 A06 A05 A04 A03 A02 A01 A00 B23 B22 B21 B20 B19 B18 B17 B16 B15 B14 B13 B12 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 ; Second word: A15 A14 A13 A12 A11 A10 A09 A08 C23 C22 C21 C20 C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00 ; Third word: A23 A22 A21 A20 A19 A18 A17 A16 D23 D22 D21 D20 D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 .pio_version 1 .program adc_24bit_input .side_set 3 public entry_point: .wrap_target mov osr, pins side 7 ; sample A out isr, 8 side 0 in pins, 24 side 7 ; sample B, autopush out isr, 8 side 0 in pins, 24 side 7 ; sample C, autopush out isr, 8 side 0 in pins, 24 side 7 ; sample D, autopush nop side 0 .wrap % c-sdk { static inline void adc_24bit_input_program_init(PIO pio, uint sm, uint offset, uint pin, uint clk_pin) { pio_sm_config c = adc_24bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // configure CLK pin for side-set sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_sideset(&c, 3, false, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 3, true); pio_gpio_init(pio, clk_pin); pio_gpio_init(pio, clk_pin+1); pio_gpio_init(pio, clk_pin+2); // Set the pin directions to input at the PIO // Set D0-D23 of the ADC(s) as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 24, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+24); i++) pio_gpio_init(pio, i); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) true, // Autopush enabled 32 // Autopush threshold = 32 ); // required in order to set shift-to-right to true (for the out ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 4.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); // pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/dual_external_adc/dual_external_adc.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * Dual External 12-bit ADC example, connected to the PIO * * Copyright (c) 2024-2025 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/pll.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "picohsdaoh.h" #include "adc_24bit_input.pio.h" #include "pcm1802_fmt00.pio.h" #if (PICO_PIO_USE_GPIO_BASE != 1) #warning "PICO_PIO_USE_GPIO_BASE is not set to 1, this application will not work correctly!" #endif /* The PIO is running with sys_clk/2, and needs 4 cycles per sample, * so the ADC clock is sys_clk/8 */ #define SYS_CLK 320000 // 40 MHz ADC clock // For alignment of 3x32 bit words in the payload, so that every line starts with word 0 #define ADC_DATA_LEN (RBUF_SLICE_LEN - 6) // Same here for 2x32 bit words #define AUDIO_DATA_LEN (RBUF_SLICE_LEN - 4) #define AUDIO_RBUF_SLICES 8 // ADC is attached to GP0 - GP11 with clock on GP20 #define PIO_INPUT_PIN_BASE 23 #define PIO_OUTPUT_CLK_PIN 20 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = ADC_DATA_LEN/2; hsdaoh_update_head(0, ringbuf_head); } void init_pio_input(void) { PIO pio = pio0; /* move up GPIO base of PIO to access all ADC pins */ pio_set_gpio_base(pio, 16); uint offset = pio_add_program(pio, &adc_24bit_input_program); uint sm_data = pio_claim_unused_sm(pio, true); adc_24bit_input_program_init(pio, sm_data, offset, PIO_INPUT_PIN_BASE, PIO_OUTPUT_CLK_PIN); dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN/2, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN/2, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } #define PCM1802_DATA_PIN_1 3 #define AUDIO1_STREAM_ID 2 #define DMACH_AUDIO1_PIO_PING 2 #define DMACH_AUDIO1_PIO_PONG 3 #define PCM1802_DATA_PIN_2 0 #define AUDIO2_STREAM_ID 3 #define DMACH_AUDIO2_PIO_PING 4 #define DMACH_AUDIO2_PIO_PONG 5 static bool audio1_pio_dma_pong = false; uint16_t audio1_ringbuffer[AUDIO_RBUF_SLICES * RBUF_SLICE_LEN]; int audio1_ringbuf_head = 2; static bool audio2_pio_dma_pong = false; uint16_t audio2_ringbuffer[AUDIO_RBUF_SLICES * RBUF_SLICE_LEN]; int audio2_ringbuf_head = 2; void __scratch_y("") audio1_pio_dma_irq_handler() { uint ch_num = audio1_pio_dma_pong ? DMACH_AUDIO1_PIO_PONG : DMACH_AUDIO1_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; audio1_pio_dma_pong = !audio1_pio_dma_pong; audio1_ringbuf_head = (audio1_ringbuf_head + 1) % AUDIO_RBUF_SLICES; ch->write_addr = (uintptr_t)&audio1_ringbuffer[audio1_ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = AUDIO_DATA_LEN/2; hsdaoh_update_head(AUDIO1_STREAM_ID, audio1_ringbuf_head); } void __scratch_y("") audio2_pio_dma_irq_handler() { uint ch_num = audio2_pio_dma_pong ? DMACH_AUDIO2_PIO_PONG : DMACH_AUDIO2_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; audio2_pio_dma_pong = !audio2_pio_dma_pong; audio2_ringbuf_head = (audio2_ringbuf_head + 1) % AUDIO_RBUF_SLICES; ch->write_addr = (uintptr_t)&audio2_ringbuffer[audio2_ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = AUDIO_DATA_LEN/2; hsdaoh_update_head(AUDIO2_STREAM_ID, audio2_ringbuf_head); } void init_audio_pio_dma(PIO pio, uint sm_data, uint dmach_ping, uint dmach_pong, uint16_t *rbuf) { dma_channel_config c; c = dma_channel_get_default_config(dmach_ping); channel_config_set_chain_to(&c, dmach_pong); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( dmach_ping, &c, &rbuf[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); c = dma_channel_get_default_config(dmach_pong); channel_config_set_chain_to(&c, dmach_ping); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( dmach_pong, &c, &rbuf[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); uint dma_irq = (dmach_ping == DMACH_AUDIO1_PIO_PING) ? DMA_IRQ_1 : DMA_IRQ_2; if (dma_irq == DMA_IRQ_1) { dma_hw->ints1 |= (1u << dmach_ping) | (1u << dmach_pong); dma_hw->inte1 |= (1u << dmach_ping) | (1u << dmach_pong); irq_set_exclusive_handler(dma_irq, audio1_pio_dma_irq_handler); } else { dma_hw->ints2 |= (1u << dmach_ping) | (1u << dmach_pong); dma_hw->inte2 |= (1u << dmach_ping) | (1u << dmach_pong); irq_set_exclusive_handler(dma_irq, audio2_pio_dma_irq_handler); } irq_set_enabled(dma_irq, true); dma_channel_start(dmach_ping); } void init_audio_pio_input(void) { PIO pio = pio1; pio_set_gpio_base(pio, 0); uint offset = pio_add_program(pio, &pcm1802_fmt00_program); uint sm_data = pio_claim_unused_sm(pio, true); pcm1802_fmt00_program_init(pio, sm_data, offset, PCM1802_DATA_PIN_1); init_audio_pio_dma(pio, sm_data, DMACH_AUDIO1_PIO_PING, DMACH_AUDIO1_PIO_PONG, audio1_ringbuffer); sm_data = pio_claim_unused_sm(pio, true); pcm1802_fmt00_program_init(pio, sm_data, offset, PCM1802_DATA_PIN_2); init_audio_pio_dma(pio, sm_data, DMACH_AUDIO2_PIO_PING, DMACH_AUDIO2_PIO_PONG, audio2_ringbuffer); } #define OVERVOLT 1 int main() { #ifdef OVERVOLT /* set maximum 'allowed' voltage without voiding warranty */ vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); #endif hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/1 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 1 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); #ifdef GPOUT_AUDIO_CLOCK pll_init(pll_usb, 1, 1536 * MHZ, 4, 2); /* set USB clock to clk_usb/4 */ hw_write_masked(&clocks_hw->clk[clk_usb].div, 4 << CLOCKS_CLK_USB_DIV_INT_LSB, CLOCKS_CLK_USB_DIV_INT_BITS); /* set GPOUT0 clock to USB PLL/10 -> 19.2 MHz, resulting in 75 kHz ADC sample rate (19.2M/256) */ clock_gpio_init(21, CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, 10); #endif stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_12MA, GPIO_SLEW_RATE_FAST); hsdaoh_add_stream(0, PIO_12BIT_DUAL, (SYS_CLK/8) * 1000, ADC_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_add_stream(AUDIO1_STREAM_ID, PIO_PCM1802_AUDIO, 78125, AUDIO_DATA_LEN, AUDIO_RBUF_SLICES, audio1_ringbuffer); hsdaoh_add_stream(AUDIO2_STREAM_ID, PIO_PCM1802_AUDIO, 78125, AUDIO_DATA_LEN, AUDIO_RBUF_SLICES, audio2_ringbuffer); hsdaoh_start(); init_pio_input(); init_audio_pio_input(); /* synchronously start data input */ pio_set_sm_mask_enabled(pio1, 3, true); pio_set_sm_mask_enabled(pio0, 1, true); while (1) __wfi(); } ================================================ FILE: apps/dual_external_adc/pcm1802_fmt00.pio ================================================ ; ; Copyright (c) 2023 Rene Wolf ; ; adapted for hsdaoh-rp2350: ; Copyright (c) 2024 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; See PCM1802 data sheet, figure 25, format 00 ; This output technically has 4 wires, however we don't need the frame sync: ; - Data is sampled on a positive edge of the bit clock ; - LRClk changes on the preceeding falling edge of bit clock ; - So we wait for an edge on LRClk which tells us ; - the next falling edge on bit clock is MSB of a channel ; - what channel follows determined by eitehr positive (left) or negative (right) edge ; - Frame sync would tell us how many bits are valid, but we know its 24 .define PUBLIC pcm1802_index_data 0 .define PUBLIC pcm1802_index_bitclk 1 .define PUBLIC pcm1802_index_lrclk 2 .define PUBLIC pcm1802_index_auxin 6 .define polarity_left 1 .define polarity_right 0 .program pcm1802_fmt00 public entry_point: right_ch: set x, 1 ; set jump to left next wait polarity_right pin pcm1802_index_lrclk ; right channel started, next rising edge on bitclk is msb jmp read_sample left_ch: set x, 0 ; set jump to right next wait polarity_left pin pcm1802_index_lrclk ; left channel started, next rising edge on bitclk is msb jmp read_sample read_sample: in x, 1 set y, 23 ; load 24-1 into y, because jmp is a pre-decrement check read_bit: wait 0 pin pcm1802_index_bitclk wait 1 pin pcm1802_index_bitclk ; positive edge -> data valid now in pins, 1 ; read one bit from data pin into ISR jmp y-- read_bit ; jump if Y!=0 && decrement Y if jump taken push ; sample the auxilary input pin (e.g. for headswitch signal) mov osr, pins out null, pcm1802_index_auxin in osr, 1 jmp !x right_ch ; jmp to right_ch if X==0 jmp left_ch ; go for next sample start % c-sdk { static inline void pcm1802_fmt00_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_config c = pcm1802_fmt00_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); uint32_t pin_mask = (1 << pcm1802_index_auxin) | (1 << pcm1802_index_lrclk) | (1 << pcm1802_index_bitclk) | (1 << pcm1802_index_data); pio_sm_set_pindirs_with_mask (pio, sm, 0, pin_mask << pin); sm_config_set_jmp_pin(&c, pin + pcm1802_index_data); // Connect these GPIOs to this PIO block pio_gpio_init(pio, pin + pcm1802_index_data); pio_gpio_init(pio, pin + pcm1802_index_bitclk); pio_gpio_init(pio, pin + pcm1802_index_lrclk); pio_gpio_init(pio, pin + pcm1802_index_auxin); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) false, // Autopush disabled 32 // Autopush threshold: ignored ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 2.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); // pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/external_adc/CMakeLists.txt ================================================ add_executable(external_adc external_adc.c ) target_compile_options(external_adc PRIVATE -Wall) target_link_libraries(external_adc pico_stdlib pico_util hardware_pio hardware_dma libpicohsdaoh ) pico_generate_pio_header(external_adc ${CMAKE_CURRENT_LIST_DIR}/adc_12bit_input.pio) pico_generate_pio_header(external_adc ${CMAKE_CURRENT_LIST_DIR}/pcm1802_fmt00.pio) # enable usb output, disable uart output pico_enable_stdio_usb(external_adc 1) # create map/bin/hex file etc. pico_add_extra_outputs(external_adc) ================================================ FILE: apps/external_adc/adc_12bit_input.pio ================================================ ; ; Copyright (c) 2024-2025 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Sample 12 bit parallel ADC (AD9226) every 2 PIO cycles on rising clock edge, ; pack four 12 bit samples in three 16 bit words ; ADC clock output as side-set ; ; Data being pushed to the FIFO, four 12 bit samples A-D ; First word: A03 A02 A01 A00 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 ; Second word: A07 A06 A05 A04 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00 ; Third word: A11 A10 A09 A08 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 .pio_version 1 .program adc_12bit_input .side_set 2 public entry_point: .wrap_target mov osr, pins side 3 ; sample A out isr, 4 side 0 in pins, 12 side 3 ; sample B, autopush out isr, 4 side 0 in pins, 12 side 3 ; sample C, autopush out isr, 4 side 0 in pins, 12 side 3 ; sample D, autopush nop side 0 .wrap % c-sdk { static inline void adc_12bit_input_program_init(PIO pio, uint sm, uint offset, uint pin, uint clk_pin) { pio_sm_config c = adc_12bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // configure CLK pin for side-set sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_sideset(&c, 2, false, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 2, true); pio_gpio_init(pio, clk_pin); pio_gpio_init(pio, clk_pin+1); // Set the pin directions to input at the PIO // Set D0-D11 of the ADC as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 12, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+12); i++) pio_gpio_init(pio, i); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) true, // Autopush enabled 16 // Autopush threshold = 16 ); // required in order to set shift-to-right to true (for the out ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 2.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); // pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/external_adc/external_adc.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * External 12-bit ADC example, connected to the PIO * * Copyright (c) 2024-2025 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/pll.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "picohsdaoh.h" #include "adc_12bit_input.pio.h" #include "pcm1802_fmt00.pio.h" /* The PIO is running with sys_clk/1, and needs 4 cycles per sample, * so the ADC clock is sys_clk/4 */ #define SYS_CLK 160000 // 40 MHz ADC clock //#define SYS_CLK 192000 // 48 MHz ADC clock //#define SYS_CLK 320000 // 80 MHz ADC clock //#define SYS_CLK 384000 // 96 MHz ADC clock, maximum that works on my Pico2 (with overvolting) // For alignment of 3x16 bit words in the payload, so that every line starts with word 0 #define ADC_DATA_LEN (RBUF_SLICE_LEN - 3) // Same here for 2x32 bit words #define AUDIO_DATA_LEN (RBUF_SLICE_LEN - 4) #define AUDIO_RBUF_SLICES 8 // ADC is attached to GP0 - GP11 with clock on GP20 #define PIO_INPUT_PIN_BASE 0 #define PIO_OUTPUT_CLK_PIN 20 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = ADC_DATA_LEN; hsdaoh_update_head(0, ringbuf_head); } void init_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &adc_12bit_input_program); uint sm_data = pio_claim_unused_sm(pio, true); adc_12bit_input_program_init(pio, sm_data, offset, PIO_INPUT_PIN_BASE, PIO_OUTPUT_CLK_PIN); dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } #define PCM1802_DATA_PIN 22 #define DMACH_AUDIO_PIO_PING 2 #define DMACH_AUDIO_PIO_PONG 3 static bool audio_pio_dma_pong = false; uint16_t audio_ringbuffer[AUDIO_RBUF_SLICES * RBUF_SLICE_LEN]; int audio_ringbuf_head = 2; void __scratch_y("") audio_pio_dma_irq_handler() { uint ch_num = audio_pio_dma_pong ? DMACH_AUDIO_PIO_PONG : DMACH_AUDIO_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; audio_pio_dma_pong = !audio_pio_dma_pong; audio_ringbuf_head = (audio_ringbuf_head + 1) % AUDIO_RBUF_SLICES; ch->write_addr = (uintptr_t)&audio_ringbuffer[audio_ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = AUDIO_DATA_LEN/2; hsdaoh_update_head(2, audio_ringbuf_head); } void init_audio_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &pcm1802_fmt00_program); uint sm_data = pio_claim_unused_sm(pio, true); pcm1802_fmt00_program_init(pio, sm_data, offset, PCM1802_DATA_PIN); dma_channel_config c; c = dma_channel_get_default_config(DMACH_AUDIO_PIO_PING); channel_config_set_chain_to(&c, DMACH_AUDIO_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_AUDIO_PIO_PING, &c, &audio_ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); c = dma_channel_get_default_config(DMACH_AUDIO_PIO_PONG); channel_config_set_chain_to(&c, DMACH_AUDIO_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_AUDIO_PIO_PONG, &c, &audio_ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); dma_hw->ints1 |= (1u << DMACH_AUDIO_PIO_PING) | (1u << DMACH_AUDIO_PIO_PONG); dma_hw->inte1 |= (1u << DMACH_AUDIO_PIO_PING) | (1u << DMACH_AUDIO_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_1, audio_pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_1, true); dma_channel_start(DMACH_AUDIO_PIO_PING); } int main() { #ifdef OVERVOLT /* set maximum 'allowed' voltage without voiding warranty */ vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); #endif hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/1 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 1 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); #ifdef GPOUT_AUDIO_CLOCK pll_init(pll_usb, 1, 1536 * MHZ, 4, 2); /* set USB clock to clk_usb/4 */ hw_write_masked(&clocks_hw->clk[clk_usb].div, 4 << CLOCKS_CLK_USB_DIV_INT_LSB, CLOCKS_CLK_USB_DIV_INT_BITS); /* set GPOUT0 clock to USB PLL/10 -> 19.2 MHz, resulting in 75 kHz ADC sample rate (19.2M/256) */ clock_gpio_init(21, CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, 10); #endif stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_4MA, GPIO_SLEW_RATE_SLOW); hsdaoh_add_stream(0, PIO_12BIT, (SYS_CLK/4) * 1000, ADC_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_add_stream(2, PIO_PCM1802_AUDIO, 78125, AUDIO_DATA_LEN, AUDIO_RBUF_SLICES, audio_ringbuffer); hsdaoh_start(); init_pio_input(); init_audio_pio_input(); /* synchronously start data input */ pio_set_sm_mask_enabled(pio0, 3, true); while (1) __wfi(); } ================================================ FILE: apps/external_adc/pcm1802_fmt00.pio ================================================ ; ; Copyright (c) 2023 Rene Wolf ; ; adapted for hsdaoh-rp2350: ; Copyright (c) 2024 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; See PCM1802 data sheet, figure 25, format 00 ; This output technically has 4 wires, however we don't need the frame sync: ; - Data is sampled on a positive edge of the bit clock ; - LRClk changes on the preceeding falling edge of bit clock ; - So we wait for an edge on LRClk which tells us ; - the next falling edge on bit clock is MSB of a channel ; - what channel follows determined by eitehr positive (left) or negative (right) edge ; - Frame sync would tell us how many bits are valid, but we know its 24 .define PUBLIC pcm1802_index_data 0 .define PUBLIC pcm1802_index_bitclk 4 .define PUBLIC pcm1802_index_lrclk 5 .define PUBLIC pcm1802_index_auxin 6 .define polarity_left 1 .define polarity_right 0 .program pcm1802_fmt00 public entry_point: right_ch: set x, 1 ; set jump to left next wait polarity_right pin pcm1802_index_lrclk ; right channel started, next rising edge on bitclk is msb jmp read_sample left_ch: set x, 0 ; set jump to right next wait polarity_left pin pcm1802_index_lrclk ; left channel started, next rising edge on bitclk is msb jmp read_sample read_sample: in x, 1 set y, 23 ; load 24-1 into y, because jmp is a pre-decrement check read_bit: wait 0 pin pcm1802_index_bitclk wait 1 pin pcm1802_index_bitclk ; positive edge -> data valid now in pins, 1 ; read one bit from data pin into ISR jmp y-- read_bit ; jump if Y!=0 && decrement Y if jump taken push ; sample the auxilary input pin (e.g. for headswitch signal) mov osr, pins out null, pcm1802_index_auxin in osr, 1 jmp !x right_ch ; jmp to right_ch if X==0 jmp left_ch ; go for next sample start % c-sdk { static inline void pcm1802_fmt00_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_config c = pcm1802_fmt00_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); uint32_t pin_mask = (1 << pcm1802_index_auxin) | (1 << pcm1802_index_lrclk) | (1 << pcm1802_index_bitclk) | (1 << pcm1802_index_data); pio_sm_set_pindirs_with_mask (pio, sm, 0, pin_mask << pin); sm_config_set_jmp_pin(&c, pin + pcm1802_index_data); // Connect these GPIOs to this PIO block pio_gpio_init(pio, pin + pcm1802_index_data); pio_gpio_init(pio, pin + pcm1802_index_bitclk); pio_gpio_init(pio, pin + pcm1802_index_lrclk); pio_gpio_init(pio, pin + pcm1802_index_auxin); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) false, // Autopush disabled 32 // Autopush threshold: ignored ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 2.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); // pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/external_dualchan_adc/CMakeLists.txt ================================================ add_executable(external_dualchan_adc external_dualchan_adc.c ) target_compile_options(external_dualchan_adc PRIVATE -Wall) target_link_libraries(external_dualchan_adc pico_stdlib pico_util hardware_pio hardware_dma libpicohsdaoh ) pico_generate_pio_header(external_dualchan_adc ${CMAKE_CURRENT_LIST_DIR}/dualchan_adc_12bit_input.pio) pico_generate_pio_header(external_dualchan_adc ${CMAKE_CURRENT_LIST_DIR}/../external_adc/pcm1802_fmt00.pio) # enable usb output, disable uart output pico_enable_stdio_usb(external_dualchan_adc 1) # create map/bin/hex file etc. pico_add_extra_outputs(external_dualchan_adc) ================================================ FILE: apps/external_dualchan_adc/dualchan_adc_12bit_input.pio ================================================ ; ; Copyright (c) 2024-2025 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Sample 12 bit dualchannel parallel ADC (AD9238) every 2 PIO cycles on both ; clock edges (DDR), pack four 12 bit samples in three 16 bit words ; ADC clock output as side-set ; ; Data being pushed to the FIFO, four 12 bit samples A-D ; First word: A03 A02 A01 A00 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 ; Second word: A07 A06 A05 A04 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00 ; Third word: A11 A10 A09 A08 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 .pio_version 1 .program dualchan_adc_12bit_input .side_set 2 public entry_point: .wrap_target mov osr, pins side 3 ; sample A out isr, 4 side 0 in pins, 12 side 0 ; sample B, autopush out isr, 4 side 3 in pins, 12 side 3 ; sample C, autopush out isr, 4 side 0 in pins, 12 side 0 ; sample D, autopush nop side 3 .wrap % c-sdk { static inline void dualchan_adc_12bit_input_program_init(PIO pio, uint sm, uint offset, uint pin, uint clk_pin) { pio_sm_config c = dualchan_adc_12bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // configure CLK pin for side-set sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_sideset(&c, 2, false, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 2, true); pio_gpio_init(pio, clk_pin); pio_gpio_init(pio, clk_pin+1); // Set the pin directions to input at the PIO // Set D0-D11 of the ADC as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 12, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+12); i++) pio_gpio_init(pio, i); sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) true, // Autopush enabled 16 // Autopush threshold = 16 ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 2.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); // pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/external_dualchan_adc/external_dualchan_adc.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * External dualchannel 12-bit ADC example, connected to the PIO * * Copyright (c) 2024-2025 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/pll.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "picohsdaoh.h" #include "dualchan_adc_12bit_input.pio.h" #include "pcm1802_fmt00.pio.h" /* The PIO is running with sys_clk/1, and needs 4 cycles per sample, * so the ADC clock is sys_clk/4 */ #define SYS_CLK 320000 // 40 MHz ADC clock // For alignment of 3x16 bit words in the payload, so that every line starts with word 0 #define ADC_DATA_LEN (RBUF_SLICE_LEN - 3) // Same here for 2x32 bit words #define AUDIO_DATA_LEN (RBUF_SLICE_LEN - 4) #define AUDIO_RBUF_SLICES 8 // ADC is attached to GP0 - GP11 with clock on GP20 #define PIO_INPUT_PIN_BASE 0 #define PIO_OUTPUT_CLK_PIN 20 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = ADC_DATA_LEN; hsdaoh_update_head(0, ringbuf_head); } void init_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &dualchan_adc_12bit_input_program); uint sm_data = pio_claim_unused_sm(pio, true); dualchan_adc_12bit_input_program_init(pio, sm_data, offset, PIO_INPUT_PIN_BASE, PIO_OUTPUT_CLK_PIN); dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], ADC_DATA_LEN, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } #define PCM1802_DATA_PIN 22 #define DMACH_AUDIO_PIO_PING 2 #define DMACH_AUDIO_PIO_PONG 3 static bool audio_pio_dma_pong = false; uint16_t audio_ringbuffer[AUDIO_RBUF_SLICES * RBUF_SLICE_LEN]; int audio_ringbuf_head = 2; void __scratch_y("") audio_pio_dma_irq_handler() { uint ch_num = audio_pio_dma_pong ? DMACH_AUDIO_PIO_PONG : DMACH_AUDIO_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; audio_pio_dma_pong = !audio_pio_dma_pong; audio_ringbuf_head = (audio_ringbuf_head + 1) % AUDIO_RBUF_SLICES; ch->write_addr = (uintptr_t)&audio_ringbuffer[audio_ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = AUDIO_DATA_LEN/2; hsdaoh_update_head(2, audio_ringbuf_head); } void init_audio_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &pcm1802_fmt00_program); uint sm_data = pio_claim_unused_sm(pio, true); pcm1802_fmt00_program_init(pio, sm_data, offset, PCM1802_DATA_PIN); dma_channel_config c; c = dma_channel_get_default_config(DMACH_AUDIO_PIO_PING); channel_config_set_chain_to(&c, DMACH_AUDIO_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_AUDIO_PIO_PING, &c, &audio_ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); c = dma_channel_get_default_config(DMACH_AUDIO_PIO_PONG); channel_config_set_chain_to(&c, DMACH_AUDIO_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_AUDIO_PIO_PONG, &c, &audio_ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], AUDIO_DATA_LEN/2, false ); dma_hw->ints1 |= (1u << DMACH_AUDIO_PIO_PING) | (1u << DMACH_AUDIO_PIO_PONG); dma_hw->inte1 |= (1u << DMACH_AUDIO_PIO_PING) | (1u << DMACH_AUDIO_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_1, audio_pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_1, true); dma_channel_start(DMACH_AUDIO_PIO_PING); } #define OVERVOLT 1 int main() { #ifdef OVERVOLT /* set maximum 'allowed' voltage without voiding warranty */ vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); #endif hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/1 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 1 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_12MA, GPIO_SLEW_RATE_FAST); hsdaoh_add_stream(0, PIO_DUALCHAN_12BIT, (SYS_CLK/4) * 1000, ADC_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_add_stream(2, PIO_PCM1802_AUDIO, 78125, AUDIO_DATA_LEN, AUDIO_RBUF_SLICES, audio_ringbuffer); hsdaoh_start(); init_pio_input(); init_audio_pio_input(); /* synchronously start data input */ pio_set_sm_mask_enabled(pio0, 3, true); while (1) __wfi(); } ================================================ FILE: apps/internal_adc/CMakeLists.txt ================================================ add_executable(internal_adc internal_adc.c ) target_compile_options(internal_adc PRIVATE -Wall) target_link_libraries(internal_adc pico_stdlib hardware_adc hardware_dma pico_util libpicohsdaoh ) # enable usb output, disable uart output pico_enable_stdio_usb(internal_adc 1) # create map/bin/hex file etc. pico_add_extra_outputs(internal_adc) ================================================ FILE: apps/internal_adc/internal_adc.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * Internal ADC example, overclocked to 3,33 MHz sample rate * When using the USB PLL for the ADC, almost 8 MHz sample rate * can be achieved! * * Copyright (c) 2024 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/dma.h" #include "hardware/adc.h" #include "hardware/pll.h" #include "picohsdaoh.h" #define SYS_CLK 320000 // Channel 0 is GPIO26 #define CAPTURE_CHANNEL 1 #define DMACH_ADC_PING 0 #define DMACH_ADC_PONG 1 static bool dma_adc_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") adc_dma_irq_handler() { uint ch_num = dma_adc_pong ? DMACH_ADC_PONG : DMACH_ADC_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; dma_adc_pong = !dma_adc_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = RBUF_MAX_DATA_LEN; hsdaoh_update_head(0, ringbuf_head); } void init_adc_input(void) { adc_init(); adc_select_input(CAPTURE_CHANNEL); adc_fifo_setup( true, // Write each completed conversion to the sample FIFO true, // Enable DMA data request (DREQ) 1, // DREQ (and IRQ) asserted when at least 1 sample present false, // Disable the ERR bit false // No shift of samples, use full 12 bit resolution ); adc_set_clkdiv(0); dma_channel_config c; c = dma_channel_get_default_config(DMACH_ADC_PING); channel_config_set_chain_to(&c, DMACH_ADC_PONG); channel_config_set_dreq(&c, DREQ_ADC); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_ADC_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &adc_hw->fifo, RBUF_MAX_DATA_LEN, false ); c = dma_channel_get_default_config(DMACH_ADC_PONG); channel_config_set_chain_to(&c, DMACH_ADC_PING); channel_config_set_dreq(&c, DREQ_ADC); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_ADC_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &adc_hw->fifo, RBUF_MAX_DATA_LEN, false ); dma_hw->ints0 |= (1u << DMACH_ADC_PING) | (1u << DMACH_ADC_PONG); dma_hw->inte0 |= (1u << DMACH_ADC_PING) | (1u << DMACH_ADC_PONG); irq_set_exclusive_handler(DMA_IRQ_0, adc_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_ADC_PING); adc_run(true); } int main() { /* set maximum 'allowed' voltage without voiding warranty */ vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/3 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 3 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); /* switch ADC clock source to sys_clk */ hw_write_masked( &clocks_hw->clk[clk_adc].ctrl, CLOCKS_CLK_ADC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS << CLOCKS_CLK_ADC_CTRL_AUXSRC_LSB, CLOCKS_CLK_ADC_CTRL_AUXSRC_BITS ); stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_4MA, GPIO_SLEW_RATE_SLOW); hsdaoh_add_stream(0, 1, (SYS_CLK/8) * 1000, RBUF_MAX_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_start(); init_adc_input(); while (1) __wfi(); } ================================================ FILE: apps/logic_analyzer/16bit_input.pio ================================================ ; ; Copyright (c) 2024 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; 16 bit logic analyzer ; .pio_version 0 .program la_16bit_input public entry_point: .wrap_target ; GP0 - GP11 are contiguous, then come the HSTX pins which are already in use ; Next pins we can use are 20, 21, 22 and then 26, 27, 28 mov osr, pins ; Sample all 32 pins in osr, 12 ; shift values of GP0-GP11 to ISR out null, 20 ; shift out GP0-GP11 and discard GP12-GP19 in osr, 3 ; shift in GP20 - GP22 out null, 6 ; drop GP20 - GP25 in osr, 1 ; shift in GP26 - now the ISR is full and the autopush is happening in null, 16 ; fill with zeroes so that data is in lower 16 bits nop [3] .wrap % c-sdk { static inline void la_16bit_input_program_init(PIO pio, uint sm, uint offset, uint pin) { pio_sm_config c = la_16bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // Set the pin directions to input at the PIO // Set D0-D11 of the ADC as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 27, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+12); i++) { pio_gpio_init(pio, pin + i); gpio_set_pulls(pin + i, false, false); } // Connect these GPIOs to this PIO block for (int i = 20; i < 23; i++) { pio_gpio_init(pio, i); gpio_set_pulls(i, false, false); } pio_gpio_init(pio, 26); gpio_set_pulls(26, false, false); sm_config_set_in_shift( &c, true, // Shift-to-right = true true, // Autopush enabled 32 // Autopush threshold = 32 ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 1.f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/logic_analyzer/CMakeLists.txt ================================================ add_executable(logic_analyzer logic_analyzer.c ) target_compile_options(logic_analyzer PRIVATE -Wall) target_link_libraries(logic_analyzer pico_stdlib pico_util hardware_pio hardware_dma libpicohsdaoh ) pico_generate_pio_header(logic_analyzer ${CMAKE_CURRENT_LIST_DIR}/16bit_input.pio) # enable usb output, disable uart output pico_enable_stdio_usb(logic_analyzer 1) # create map/bin/hex file etc. pico_add_extra_outputs(logic_analyzer) ================================================ FILE: apps/logic_analyzer/logic_analyzer.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * 16 bit logic analyzer example * * Copyright (c) 2024 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "picohsdaoh.h" #include "16bit_input.pio.h" /* The PIO is running with sys_clk, and needs 10 cycles per sample, * so the LA is sampling with 32 MHz */ #define SYS_CLK 320000 #define PIO_INPUT_PIN_BASE 0 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = RBUF_MAX_DATA_LEN; hsdaoh_update_head(0, ringbuf_head); } void init_pio_input(void) { PIO pio = pio0; uint offset = pio_add_program(pio, &la_16bit_input_program); uint sm_data = pio_claim_unused_sm(pio, true); la_16bit_input_program_init(pio, sm_data, offset, PIO_INPUT_PIN_BASE); dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], RBUF_MAX_DATA_LEN, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], RBUF_MAX_DATA_LEN, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } int main() { /* set maximum 'allowed' voltage without voiding warranty */ vreg_set_voltage(VREG_VOLTAGE_MAX); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); hsdaoh_set_sys_clock_khz(SYS_CLK); /* set HSTX clock to sysclk/2 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 2 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_4MA, GPIO_SLEW_RATE_SLOW); hsdaoh_add_stream(0, 1, (SYS_CLK/8) * 1000, RBUF_MAX_DATA_LEN, RBUF_DEFAULT_SLICES, ringbuffer); hsdaoh_start(); init_pio_input(); while (1) __wfi(); } ================================================ FILE: apps/sdr/CMakeLists.txt ================================================ add_executable(sdr sdr.c ) target_compile_options(sdr PRIVATE -Wall) target_link_libraries(sdr pico_stdlib pico_util hardware_pio hardware_dma hardware_i2c libpicohsdaoh ) pico_generate_pio_header(sdr ${CMAKE_CURRENT_LIST_DIR}/adc_16bit_input.pio) pico_generate_pio_header(sdr ${CMAKE_CURRENT_LIST_DIR}/adc_20bit_input.pio) # enable usb output, disable uart output pico_enable_stdio_usb(sdr 1) # create map/bin/hex file etc. pico_add_extra_outputs(sdr) ================================================ FILE: apps/sdr/adc_16bit_input.pio ================================================ ; ; Copyright (c) 2025 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Sample 2x10 bit parallel ADC (AD9218) every 4 PIO cycles on rising clock edge, ; pack four 20 bit samples in five 16 bit words ; ADC clock output as side-set ; ; Data being pushed to the FIFO, four 20 bit samples A-D ; First word: A15 A14 A13 A12 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 ; Second word: A19 A18 A17 A16 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 ; Third word: B19 B18 B17 B16 B15 B14 B13 B12 C07 C06 C05 C04 C03 C02 C01 C00 ; Fourth word: C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 D03 D02 D01 D00 ; Fifth word: D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 .pio_version 1 .program adc_16bit_input .side_set 1 public entry_point: .wrap_target in pins, 16 side 1 nop side 0 nop side 0 nop side 1 .wrap % c-sdk { static inline void adc_16bit_input_program_init(PIO pio, uint sm, uint offset, uint pin, uint clk_pin) { pio_sm_config c = adc_16bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // configure CLK pin for side-set sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_sideset(&c, 1, false, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true); pio_gpio_init(pio, clk_pin); gpio_disable_pulls(clk_pin); // gpio_set_drive_strength(clk_pin, GPIO_DRIVE_STRENGTH_12MA); // gpio_set_slew_rate(clk_pin, GPIO_SLEW_RATE_FAST); // Set the pin directions to input at the PIO // Set D0-D19 of the ADC(s) as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 20, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+20); i++) { //gpio_pull_down(i); pio_gpio_init(pio, i); } sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) true, // Autopush enabled 16 // Autopush threshold = 16 ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 1.00f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/sdr/adc_20bit_input.pio ================================================ ; ; Copyright (c) 2025 Steve Markgraf ; ; SPDX-License-Identifier: BSD-3-Clause ; ; Sample 2x10 bit parallel ADC (AD9218) every 4 PIO cycles on rising clock edge, ; pack four 20 bit samples in five 16 bit words ; ADC clock output as side-set ; ; Data being pushed to the FIFO, four 20 bit samples A-D ; First word: A15 A14 A13 A12 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 ; Second word: A19 A18 A17 A16 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 ; Third word: B19 B18 B17 B16 B15 B14 B13 B12 C07 C06 C05 C04 C03 C02 C01 C00 ; Fourth word: C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 D03 D02 D01 D00 ; Fifth word: D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 .pio_version 1 .program adc_20bit_input .side_set 1 public entry_point: .wrap_target ;---------------------------------------------------------------------------------------- mov osr, pins side 0 ; SAMP A ; OSR is now A19 A18 A17 A16 A15 A14 A13 A12 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 in osr, 16 side 1 ; AUTOPUSH: A15 A14 A13 A12 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 out null, 16 side 1 in osr, 4 side 0 ; ISR is now A19 A18 A17 A16 ;---------------------------------------------------------------------------------------- mov osr, pins side 0 ; SAMP B in osr, 12 side 1 ; AUTOPUSH: A19 A18 A17 A16 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 out null, 12 side 1 in osr, 8 side 0 ; ISR is now B19 B18 B17 B16 B15 B14 B13 B12 ;---------------------------------------------------------------------------------------- mov osr, pins side 0 ; SAMP C in osr, 8 side 1 ; AUTOPUSH: B19 B18 B17 B16 B15 B14 B13 B12 C07 C06 C05 C04 C03 C02 C01 C00 out null, 8 side 1 in osr, 12 side 0 ; ISR is now C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 ;---------------------------------------------------------------------------------------- mov osr, pins side 0 ; SAMP D in osr, 4 side 1 ; AUTOPUSH: C19 C18 C17 C16 C15 C14 C13 C12 C11 C10 C09 C08 D03 D02 D01 D00 out null, 4 side 1 in osr, 16 side 0 ; AUTOPUSH: D19 D18 D17 D16 D15 D14 D13 D12 D11 D10 D09 D08 D07 D06 D05 D04 ;---------------------------------------------------------------------------------------- .wrap % c-sdk { static inline void adc_20bit_input_program_init(PIO pio, uint sm, uint offset, uint pin, uint clk_pin) { pio_sm_config c = adc_20bit_input_program_get_default_config(offset); // Set the IN base pin to the provided `pin` parameter. sm_config_set_in_pins(&c, pin); // configure CLK pin for side-set sm_config_set_sideset_pins(&c, clk_pin); sm_config_set_sideset(&c, 1, false, false); pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true); pio_gpio_init(pio, clk_pin); gpio_disable_pulls(clk_pin); // gpio_set_drive_strength(clk_pin, GPIO_DRIVE_STRENGTH_12MA); // gpio_set_slew_rate(clk_pin, GPIO_SLEW_RATE_FAST); // Set the pin directions to input at the PIO // Set D0-D19 of the ADC(s) as input pio_sm_set_consecutive_pindirs(pio, sm, pin, 20, false); // Connect these GPIOs to this PIO block for (int i = pin; i < (pin+20); i++) { //gpio_pull_down(i); pio_gpio_init(pio, i); } sm_config_set_in_shift( &c, false, // Shift-to-right = false (i.e. shift to left) true, // Autopush enabled 16 // Autopush threshold = 16 ); // required in order to set shift-to-right to true (for the out, null ops) sm_config_set_out_shift( &c, true, // Shift-to-right = true false, // Autopush disabled 1 // Autopush threshold: ignored ); // We only receive, so disable the TX FIFO to make the RX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); sm_config_set_clkdiv(&c, 1.00f); // Load our configuration, and start the program from the beginning pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } %} ================================================ FILE: apps/sdr/sdr.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * External dual 10-bit ADC example, connected to the PIO * used for hsdaohSDR * * Copyright (c) 2024-2025 by Steve Markgraf * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "pico/stdlib.h" #include "hardware/clocks.h" #include "hardware/irq.h" #include "hardware/sync.h" #include "hardware/vreg.h" #include "hardware/pll.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "hardware/i2c.h" #include #include "picohsdaoh.h" #include "adc_16bit_input.pio.h" #include "adc_20bit_input.pio.h" #define I2C_TIMEOUT 50000 /* 50 ms */ /* The PIO is running with sys_clk/1, and needs 4 cycles per sample, * so the ADC clock is sys_clk/4 */ #define SYS_CLK_8BIT 328000 // 82 MHz ADC clock #define SYS_CLK_10BIT 264000 // 66 MHz ADC clock #define HSTX_CLK_MHZ 432 // If we want to push everything to the limits: #define SYS_CLK_MAX_SRATE 336000 // 84 MHz ADC clock @ 8 bit IQ #define HSTX_CLK_MAX_SRATE 440 // For alignment of 5x16 bit words in the payload, so that every line starts with word 0 #define ADC_DATA_LEN_10_BIT (RBUF_SLICE_LEN - 5) #define ADC_DATA_LEN_8_BIT (RBUF_SLICE_LEN - 3) #define PIO_INPUT_PIN_BASE 27 #define PIO_OUTPUT_CLK_PIN 26 #define DMACH_PIO_PING 0 #define DMACH_PIO_PONG 1 bool enable_8bit_mode = false; static bool pio_dma_pong = false; uint16_t ringbuffer[RBUF_DEFAULT_TOTAL_LEN]; int ringbuf_head = 2; void __scratch_y("") pio_dma_irq_handler() { uint ch_num = pio_dma_pong ? DMACH_PIO_PONG : DMACH_PIO_PING; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; pio_dma_pong = !pio_dma_pong; ringbuf_head = (ringbuf_head + 1) % RBUF_DEFAULT_SLICES; ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; ch->transfer_count = enable_8bit_mode ? ADC_DATA_LEN_8_BIT : ADC_DATA_LEN_10_BIT; hsdaoh_update_head(0, ringbuf_head); } uint offset; uint offset2; uint sm_data; PIO pio = pio0; void init_pio(void) { /* move up GPIO base of PIO to access all ADC pins */ pio_set_gpio_base(pio, 16); offset = pio_add_program(pio, &adc_16bit_input_program); offset2 = pio_add_program(pio, &adc_20bit_input_program); sm_data = pio_claim_unused_sm(pio, true); } void __no_inline_not_in_flash_func(init_pio_dma)(void) { ringbuf_head = 2; pio_dma_pong = false; dma_channel_config c; c = dma_channel_get_default_config(DMACH_PIO_PING); channel_config_set_chain_to(&c, DMACH_PIO_PONG); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PING, &c, &ringbuffer[0 * RBUF_SLICE_LEN], &pio->rxf[sm_data], enable_8bit_mode ? ADC_DATA_LEN_8_BIT : ADC_DATA_LEN_10_BIT, false ); c = dma_channel_get_default_config(DMACH_PIO_PONG); channel_config_set_chain_to(&c, DMACH_PIO_PING); channel_config_set_dreq(&c, pio_get_dreq(pio, sm_data, false)); channel_config_set_read_increment(&c, false); channel_config_set_write_increment(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_16); dma_channel_configure( DMACH_PIO_PONG, &c, &ringbuffer[1 * RBUF_SLICE_LEN], &pio->rxf[sm_data], enable_8bit_mode ? ADC_DATA_LEN_8_BIT : ADC_DATA_LEN_10_BIT, false ); dma_hw->ints0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); dma_hw->inte0 |= (1u << DMACH_PIO_PING) | (1u << DMACH_PIO_PONG); irq_set_exclusive_handler(DMA_IRQ_0, pio_dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); dma_channel_start(DMACH_PIO_PING); } void __no_inline_not_in_flash_func(init_pio_input)(void) { // irq_set_enabled(DMA_IRQ_0, false); // dma_channel_start(DMACH_PIO_PING); // dma_channel_start(DMACH_PIO_PONG); // pio_sm_set_enabled(pio, sm_data, false); set_sys_clock_khz(enable_8bit_mode ? SYS_CLK_8BIT : SYS_CLK_10BIT, true); init_pio_dma(); if (enable_8bit_mode) { hsdaoh_add_stream(0, PIO_8BIT_IQ, (SYS_CLK_8BIT/4) * 1000, ADC_DATA_LEN_8_BIT, RBUF_DEFAULT_SLICES, ringbuffer); adc_16bit_input_program_init(pio, sm_data, offset, PIO_INPUT_PIN_BASE, PIO_OUTPUT_CLK_PIN); } else { hsdaoh_add_stream(0, PIO_10BIT_IQ, (SYS_CLK_10BIT/4) * 1000, ADC_DATA_LEN_10_BIT, RBUF_DEFAULT_SLICES, ringbuffer); adc_20bit_input_program_init(pio, sm_data, offset2, PIO_INPUT_PIN_BASE, PIO_OUTPUT_CLK_PIN); } } void __no_inline_not_in_flash_func(handle_tuner_interface)(void) { /* handle tty -> I2C interface for I2C tuner access */ uint8_t i2c_addr, reg, val; int ret; while (1) { char c = getchar(); switch (c) { case 'a': hsdaoh_remove_stream(0); pio_sm_set_enabled(pio, sm_data, false); irq_set_enabled(DMA_IRQ_0, false); dma_channel_abort(DMACH_PIO_PING); dma_channel_abort(DMACH_PIO_PONG); pio_sm_clear_fifos(pio, sm_data); enable_8bit_mode = !enable_8bit_mode; init_pio_input(); break; case 'r': i2c_addr = getchar(); reg = getchar(); // special handling for RT7x0 read if (i2c_addr == 0x7a) { uint8_t regnull = 0; uint8_t bytes_to_read = reg + 1; uint8_t response[256]; ret = i2c_write_timeout_us(i2c_default, i2c_addr, ®null, 1, false, I2C_TIMEOUT); if (ret == 1) { ret = i2c_read_timeout_us(i2c_default, i2c_addr, response, bytes_to_read, false, I2C_TIMEOUT); if (ret == bytes_to_read) ret = 1; val = response[reg]; } } else { ret = i2c_write_timeout_us(i2c_default, i2c_addr, ®, 1, false, I2C_TIMEOUT); if (ret == 1) ret = i2c_read_timeout_us(i2c_default, i2c_addr, &val, 1, false, I2C_TIMEOUT); } putchar_raw((ret == 1) ? 1 : 0); // I2C ACK putchar_raw(val); break; case 'w': i2c_addr = getchar(); val = getchar(); reg = getchar(); uint8_t wr[2] = { reg, val }; ret = i2c_write_timeout_us(i2c_default, i2c_addr, wr, sizeof(wr), false, I2C_TIMEOUT); putchar_raw((ret == sizeof(wr)) ? 1 : 0); break; default: break; } } } #define OVERVOLT 1 int main() { #ifdef OVERVOLT /* set maximum 'allowed' voltage without voiding warranty */ //vreg_set_voltage(VREG_VOLTAGE_MAX); vreg_disable_voltage_limit(); vreg_set_voltage(VREG_VOLTAGE_1_65); sleep_us(SYS_CLK_VREG_VOLTAGE_AUTO_ADJUST_DELAY_US); #endif hsdaoh_set_sys_clock_khz(enable_8bit_mode ? SYS_CLK_8BIT : SYS_CLK_10BIT); int usbdiv = HSTX_CLK_MHZ / 48; #ifdef USE_SYS_CLK_FOR_USB /* set USB clock to clk_sys/n */ usbdiv = SYS_CLK/48000; hw_write_masked(&clocks_hw->clk[clk_usb].ctrl, CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS << CLOCKS_CLK_USB_CTRL_AUXSRC_LSB, CLOCKS_CLK_USB_CTRL_AUXSRC_BITS); #endif hw_write_masked(&clocks_hw->clk[clk_usb].div, usbdiv << CLOCKS_CLK_USB_DIV_INT_LSB, CLOCKS_CLK_USB_DIV_INT_BITS); /* Initialize USB PLL for HSTX clock */ pll_init(pll_usb, 1, HSTX_CLK_MHZ * 4 * MHZ, 2, 2); /* set HSTX divider to 1 */ hw_write_masked( &clocks_hw->clk[clk_hstx].div, 1 << CLOCKS_CLK_HSTX_DIV_INT_LSB, CLOCKS_CLK_HSTX_DIV_INT_BITS ); /* set HSTX clock source to PLL_USB */ hw_write_masked(&clocks_hw->clk[clk_hstx].ctrl, CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB << CLOCKS_CLK_HSTX_CTRL_AUXSRC_LSB, CLOCKS_CLK_HSTX_CTRL_AUXSRC_BITS); stdio_init_all(); hsdaoh_init(GPIO_DRIVE_STRENGTH_12MA, GPIO_SLEW_RATE_FAST); hsdaoh_start(); init_pio(); init_pio_input(); i2c_init(i2c_default, 100 * 1000); gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); handle_tuner_interface(); } ================================================ FILE: libpicohsdaoh/CMakeLists.txt ================================================ # Note we are using INTERFACE so that the library can be configured per-app # with compile-time defines add_library(libpicohsdaoh INTERFACE) target_sources(libpicohsdaoh INTERFACE ${CMAKE_CURRENT_LIST_DIR}/picohsdaoh.c ${CMAKE_CURRENT_LIST_DIR}/picohsdaoh.h ${CMAKE_CURRENT_LIST_DIR}/data_packet.c ${CMAKE_CURRENT_LIST_DIR}/data_packet.h ) target_include_directories(libpicohsdaoh INTERFACE ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(libpicohsdaoh INTERFACE pico_base_headers pico_util pico_multicore hardware_dma ) ================================================ FILE: libpicohsdaoh/data_packet.c ================================================ /* * Implementation of HDMI data packet and info frame encoding * (removed the audio frame encoding not needed by hsdaoh) * * Copyright (c) 2021-2022 by Shuichi Takano * https://github.com/shuichitakano/pico_lib/blob/master/dvi/data_packet.cpp * * ported to C by Marcelo Lorenzati: * https://github.com/mlorenzati/PicoDVI/blob/master/software/libdvi/data_packet.c * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "data_packet.h" #include // Compute 8 Parity Start // Parity table is build statically with the following code // for (int i = 0; i < 256; ++i){v_[i] = (i ^ (i >> 1) ^ (i >> 2) ^ (i >> 3) ^ (i >> 4) ^ (i >> 5) ^ (i >> 6) ^ (i >> 7)) & 1;} const uint8_t parityTable[32] = { 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69, 0x69, 0x96, 0x96, 0x69, 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69, 0x96, 0x69, 0x69, 0x96, 0x96, 0x69, 0x69, 0x96, 0x69, 0x96, 0x96, 0x69 }; bool compute8(uint8_t index) { return (parityTable[index / 8] >> (index % 8)) & 0x01; } bool compute8_2(uint8_t index1, uint8_t index2) { return compute8(index1) ^ compute8(index2); } bool compute8_3(uint8_t index1, uint8_t index2, uint8_t index3) { return compute8(index1) ^ compute8(index2) ^ compute8(index3); } // Compute 8 Parity End // BCH Encoding Start const uint8_t bchTable_[256] = { 0x00, 0xd9, 0xb5, 0x6c, 0x6d, 0xb4, 0xd8, 0x01, 0xda, 0x03, 0x6f, 0xb6, 0xb7, 0x6e, 0x02, 0xdb, 0xb3, 0x6a, 0x06, 0xdf, 0xde, 0x07, 0x6b, 0xb2, 0x69, 0xb0, 0xdc, 0x05, 0x04, 0xdd, 0xb1, 0x68, 0x61, 0xb8, 0xd4, 0x0d, 0x0c, 0xd5, 0xb9, 0x60, 0xbb, 0x62, 0x0e, 0xd7, 0xd6, 0x0f, 0x63, 0xba, 0xd2, 0x0b, 0x67, 0xbe, 0xbf, 0x66, 0x0a, 0xd3, 0x08, 0xd1, 0xbd, 0x64, 0x65, 0xbc, 0xd0, 0x09, 0xc2, 0x1b, 0x77, 0xae, 0xaf, 0x76, 0x1a, 0xc3, 0x18, 0xc1, 0xad, 0x74, 0x75, 0xac, 0xc0, 0x19, 0x71, 0xa8, 0xc4, 0x1d, 0x1c, 0xc5, 0xa9, 0x70, 0xab, 0x72, 0x1e, 0xc7, 0xc6, 0x1f, 0x73, 0xaa, 0xa3, 0x7a, 0x16, 0xcf, 0xce, 0x17, 0x7b, 0xa2, 0x79, 0xa0, 0xcc, 0x15, 0x14, 0xcd, 0xa1, 0x78, 0x10, 0xc9, 0xa5, 0x7c, 0x7d, 0xa4, 0xc8, 0x11, 0xca, 0x13, 0x7f, 0xa6, 0xa7, 0x7e, 0x12, 0xcb, 0x83, 0x5a, 0x36, 0xef, 0xee, 0x37, 0x5b, 0x82, 0x59, 0x80, 0xec, 0x35, 0x34, 0xed, 0x81, 0x58, 0x30, 0xe9, 0x85, 0x5c, 0x5d, 0x84, 0xe8, 0x31, 0xea, 0x33, 0x5f, 0x86, 0x87, 0x5e, 0x32, 0xeb, 0xe2, 0x3b, 0x57, 0x8e, 0x8f, 0x56, 0x3a, 0xe3, 0x38, 0xe1, 0x8d, 0x54, 0x55, 0x8c, 0xe0, 0x39, 0x51, 0x88, 0xe4, 0x3d, 0x3c, 0xe5, 0x89, 0x50, 0x8b, 0x52, 0x3e, 0xe7, 0xe6, 0x3f, 0x53, 0x8a, 0x41, 0x98, 0xf4, 0x2d, 0x2c, 0xf5, 0x99, 0x40, 0x9b, 0x42, 0x2e, 0xf7, 0xf6, 0x2f, 0x43, 0x9a, 0xf2, 0x2b, 0x47, 0x9e, 0x9f, 0x46, 0x2a, 0xf3, 0x28, 0xf1, 0x9d, 0x44, 0x45, 0x9c, 0xf0, 0x29, 0x20, 0xf9, 0x95, 0x4c, 0x4d, 0x94, 0xf8, 0x21, 0xfa, 0x23, 0x4f, 0x96, 0x97, 0x4e, 0x22, 0xfb, 0x93, 0x4a, 0x26, 0xff, 0xfe, 0x27, 0x4b, 0x92, 0x49, 0x90, 0xfc, 0x25, 0x24, 0xfd, 0x91, 0x48, }; int encode_BCH_3(const uint8_t *p) { uint8_t v = bchTable_[p[0]]; v = bchTable_[p[1] ^ v]; v = bchTable_[p[2] ^ v]; return v; } int encode_BCH_7(const uint8_t *p) { uint8_t v = bchTable_[p[0]]; v = bchTable_[p[1] ^ v]; v = bchTable_[p[2] ^ v]; v = bchTable_[p[3] ^ v]; v = bchTable_[p[4] ^ v]; v = bchTable_[p[5] ^ v]; v = bchTable_[p[6] ^ v]; return v; } // BCH Encoding End // TERC4 Start uint16_t TERC4Syms_[16] = { 0b1010011100, 0b1001100011, 0b1011100100, 0b1011100010, 0b0101110001, 0b0100011110, 0b0110001110, 0b0100111100, 0b1011001100, 0b0100111001, 0b0110011100, 0b1011000110, 0b1010001110, 0b1001110001, 0b0101100011, 0b1011000011, }; uint32_t makeTERC4x2Char(int i) { return TERC4Syms_[i] | (TERC4Syms_[i] << 10); } uint32_t makeTERC4x2Char_2(int i0, int i1) { return TERC4Syms_[i0] | (TERC4Syms_[i1] << 10); } #define TERC4_0x2CharSym_ 0x000A729C // Build time generated -> makeTERC4x2Char(0); #define dataGaurdbandSym_ 0x0004CD33 // Build time generated -> 0b0100110011'0100110011; uint32_t defaultDataPacket12_[N_DATA_ISLAND_WORDS] = { dataGaurdbandSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, TERC4_0x2CharSym_, dataGaurdbandSym_, }; // This table is built in compilation time from a function that uses makeTERC4x2Char uint32_t defaultDataPackets0_[4][N_DATA_ISLAND_WORDS] = { { 0xa3a8e, 0xa729c, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xb32cc, 0xa3a8e}, { 0x9c671, 0x98e63, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x4e539, 0x9c671}, { 0x58d63, 0xb92e4, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x6719c, 0x58d63}, { 0xb0ec3, 0xb8ae2, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb1ac6, 0xb0ec3} }; uint32_t *getDefaultDataPacket0(bool vsync, bool hsync) { return defaultDataPackets0_[(vsync << 1) | hsync]; } // TERC4 End void compute_header_parity(data_packet_t *data_packet) { data_packet->header[3] = encode_BCH_3(data_packet->header); } void compute_subpacket_parity(data_packet_t *data_packet, int i) { data_packet->subpacket[i][7] = encode_BCH_7(data_packet->subpacket[i]); } void compute_parity(data_packet_t *data_packet) { compute_header_parity(data_packet); compute_subpacket_parity(data_packet, 0); compute_subpacket_parity(data_packet, 1); compute_subpacket_parity(data_packet, 2); compute_subpacket_parity(data_packet, 3); } void compute_info_frame_checkSum(data_packet_t *data_packet) { int s = 0; for (int i = 0; i < 3; ++i) { s += data_packet->header[i]; } int n = data_packet->header[2] + 1; for (int j = 0; j < 4; ++j) { for (int i = 0; i < 7 && n; ++i, --n) { s += data_packet->subpacket[j][i]; } } data_packet->subpacket[0][0] = -s; } void encode_header(const data_packet_t *data_packet, uint32_t *dst, int hv, bool firstPacket) { int hv1 = hv | 8; if (!firstPacket) { hv = hv1; } for (int i = 0; i < 4; ++i) { uint8_t h = data_packet->header[i]; dst[0] = makeTERC4x2Char_2(((h << 2) & 4) | hv, ((h << 1) & 4) | hv1); dst[1] = makeTERC4x2Char_2((h & 4) | hv1, ((h >> 1) & 4) | hv1); dst[2] = makeTERC4x2Char_2(((h >> 2) & 4) | hv1, ((h >> 3) & 4) | hv1); dst[3] = makeTERC4x2Char_2(((h >> 4) & 4) | hv1, ((h >> 5) & 4) | hv1); dst += 4; hv = hv1; } } void encode_subpacket(const data_packet_t *data_packet, uint32_t *dst1, uint32_t *dst2) { for (int i = 0; i < 8; ++i) { uint32_t v = (data_packet->subpacket[0][i] << 0) | (data_packet->subpacket[1][i] << 8) | (data_packet->subpacket[2][i] << 16) | (data_packet->subpacket[3][i] << 24); uint32_t t = (v ^ (v >> 7)) & 0x00aa00aa; v = v ^ t ^ (t << 7); t = (v ^ (v >> 14)) & 0x0000cccc; v = v ^ t ^ (t << 14); // 01234567 89abcdef ghijklmn opqrstuv // 08go4cks 19hp5dlt 2aiq6emu 3bjr7fnv dst1[0] = makeTERC4x2Char_2((v >> 0) & 15, (v >> 16) & 15); dst1[1] = makeTERC4x2Char_2((v >> 4) & 15, (v >> 20) & 15); dst2[0] = makeTERC4x2Char_2((v >> 8) & 15, (v >> 24) & 15); dst2[1] = makeTERC4x2Char_2((v >> 12) & 15, (v >> 28) & 15); dst1 += 2; dst2 += 2; } } void set_null(data_packet_t *data_packet) { memset(data_packet, 0, sizeof(data_packet_t)); } void set_AVI_info_frame(data_packet_t *data_packet, scan_info s, pixel_format y, colorimetry c, picture_aspect_ratio m, active_format_aspect_ratio r, RGB_quantization_range q, video_code vic) { set_null(data_packet); data_packet->header[0] = 0x82; data_packet->header[1] = 2; // version data_packet->header[2] = 13; // len int sc = 0; // int sc = 3; // scaled hv data_packet->subpacket[0][1] = (int)(s) | (r == ACTIVE_FORMAT_ASPECT_RATIO_NO_DATA ? 0 : 16) | ((int)(y) << 5); data_packet->subpacket[0][2] = (int)(r) | ((int)(m) << 4) | ((int)(c) << 6); data_packet->subpacket[0][3] = sc | ((int)(q) << 2); data_packet->subpacket[0][4] = (int)(vic); compute_info_frame_checkSum(data_packet); compute_parity(data_packet); } void encode_data_island(data_island_stream_t *dst, const data_packet_t *packet, bool vsync, bool hsync) { int hv = (vsync ? 2 : 0) | (hsync ? 1 : 0); dst->data[0][0] = makeTERC4x2Char(0b1100 | hv); dst->data[1][0] = dataGaurdbandSym_; dst->data[2][0] = dataGaurdbandSym_; encode_header(packet, &dst->data[0][1], hv, true); encode_subpacket(packet, &dst->data[1][1], &dst->data[2][1]); dst->data[0][N_DATA_ISLAND_WORDS - 1] = makeTERC4x2Char(0b1100 | hv); dst->data[1][N_DATA_ISLAND_WORDS - 1] = dataGaurdbandSym_; dst->data[2][N_DATA_ISLAND_WORDS - 1] = dataGaurdbandSym_; } ================================================ FILE: libpicohsdaoh/data_packet.h ================================================ #ifndef DATA_PACKET_H #define DATA_PACKET_H #include "pico.h" #define TMDS_CHANNELS 3 #define N_LINE_PER_DATA 2 #define W_GUARDBAND 2 #define W_PREAMBLE 8 #define W_DATA_PACKET 32 #ifndef DVI_SYMBOLS_PER_WORD #define DVI_SYMBOLS_PER_WORD 2 #endif #if DVI_SYMBOLS_PER_WORD != 1 && DVI_SYMBOLS_PER_WORD !=2 #error "Unsupported value for DVI_SYMBOLS_PER_WORD" #endif #define W_DATA_ISLAND (W_GUARDBAND * 2 + W_DATA_PACKET) #define N_DATA_ISLAND_WORDS (W_DATA_ISLAND / DVI_SYMBOLS_PER_WORD) typedef enum { SCAN_INFO_NO_DATA, OVERSCAN, UNDERSCAN } scan_info; typedef enum { RGB, YCBCR422, YCBCR444 } pixel_format; typedef enum { COLORIMETRY_NO_DATA, ITU601, ITU709, EXTENDED } colorimetry; typedef enum { PIC_ASPECT_RATIO_NO_DATA, PIC_ASPECT_RATIO_4_3, PIC_ASPECT_RATIO_16_9 } picture_aspect_ratio; typedef enum { ACTIVE_FORMAT_ASPECT_RATIO_NO_DATA = -1, SAME_AS_PAR = 8, ACTIVE_FORMAT_ASPECT_RATIO_4_3, ACTIVE_FORMAT_ASPECT_RATIO_16_9, ACTIVE_FORMAT_ASPECT_RATIO_14_9 } active_format_aspect_ratio; typedef enum { DEFAULT, LIMITED, FULL } RGB_quantization_range; typedef enum { _640x480P60 = 1, _720x480P60 = 2, _1280x720P60 = 4, _1920x1080I60 = 5, _1920x1080P60 = 16, } video_code; typedef struct data_packet { uint8_t header[4]; uint8_t subpacket[4][8]; } data_packet_t; typedef struct data_island_stream { uint32_t data[TMDS_CHANNELS][N_DATA_ISLAND_WORDS]; } data_island_stream_t; // Functions related to the data_packet (requires a data_packet instance) void compute_header_parity(data_packet_t *data_packet); void compute_subpacket_parity(data_packet_t *data_packet, int i); void compute_parity(data_packet_t *data_packet); void compute_info_frame_checkSum(data_packet_t *data_packet); void encode_header(const data_packet_t *data_packet, uint32_t *dst, int hv, bool firstPacket); void encode_subpacket(const data_packet_t *data_packet, uint32_t *dst1, uint32_t *dst2); void set_null(data_packet_t *data_packet); void set_AVI_info_frame(data_packet_t *data_packet, scan_info s, pixel_format y, colorimetry c, picture_aspect_ratio m, active_format_aspect_ratio r, RGB_quantization_range q, video_code vic); // Public Functions extern uint32_t defaultDataPacket12_[N_DATA_ISLAND_WORDS]; inline uint32_t *getDefaultDataPacket12() { return defaultDataPacket12_; } uint32_t *getDefaultDataPacket0(bool vsync, bool hsync); void encode_data_island(data_island_stream_t *dst, const data_packet_t *packet, bool vsync, bool hsync); #endif ================================================ FILE: libpicohsdaoh/picohsdaoh.c ================================================ /* * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks * Implementation for the Raspberry Pi RP2350 HSTX peripheral * * Copyright (c) 2024-2025 by Steve Markgraf * * based on the pico-examples/hstx/dvi_out_hstx_encoder example: * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause * * 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 author nor the names of its contributors may * be used to endorse or promote products derived from this software * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "hardware/dma.h" #include "hardware/gpio.h" #include "hardware/irq.h" #include "hardware/clocks.h" #include "hardware/structs/bus_ctrl.h" #include "hardware/structs/hstx_ctrl.h" #include "hardware/structs/hstx_fifo.h" #include "hardware/structs/qmi.h" #include "pico/multicore.h" #include "pico/stdlib.h" #include "data_packet.h" #include "picohsdaoh.h" // Section 5.4.2 #define TMDS_CTRL_00 0x354u #define TMDS_CTRL_01 0x0abu #define TMDS_CTRL_10 0x154u #define TMDS_CTRL_11 0x2abu #define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) #define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) #define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) #define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) #define SYNC_V1_H1_WITH_PREAMBLE (TMDS_CTRL_11 | (TMDS_CTRL_01 << 10) | (TMDS_CTRL_00 << 20)) #define SYNC_V0_H0_WITH_DATA_ISLAND_PREAMBLE (TMDS_CTRL_00 | (TMDS_CTRL_01 << 10) | (TMDS_CTRL_01 << 20)) #define VIDEO_LEADING_GUARD_BAND (0x2ccu | (0x133u << 10) | (0x2ccu << 20)) #define HSTX_CMD_RAW (0x0u << 12) #define HSTX_CMD_RAW_REPEAT (0x1u << 12) #define HSTX_CMD_TMDS (0x2u << 12) #define HSTX_CMD_TMDS_REPEAT (0x3u << 12) #define HSTX_CMD_NOP (0xfu << 12) uint16_t idle_line_buf1[MODE_H_ACTIVE_PIXELS]; uint16_t idle_line_buf2[MODE_H_ACTIVE_PIXELS]; uint32_t info_p[64]; uint32_t info_len; typedef struct { bool active; uint16_t *rbuf; uint tail; uint head; uint rbuf_slices; uint16_t format; uint32_t srate; uint16_t len; uint64_t data_cnt; bool overflow; } stream_t; stream_t streams[MAX_STREAMS]; static uint32_t vblank_line_vsync_off[] = { HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH, SYNC_V1_H1, HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH, SYNC_V1_H0, HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS), SYNC_V1_H1, HSTX_CMD_NOP }; static uint32_t vblank_line_vsync_on[] = { HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH, SYNC_V0_H1, HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH, SYNC_V0_H0, HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS), SYNC_V0_H1, HSTX_CMD_NOP }; static uint32_t vactive_line[] = { HSTX_CMD_RAW_REPEAT | (MODE_H_FRONT_PORCH), SYNC_V1_H1, HSTX_CMD_RAW_REPEAT | (MODE_H_SYNC_WIDTH), SYNC_V1_H0, HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH-W_PREAMBLE-W_GUARDBAND), SYNC_V1_H1, HSTX_CMD_RAW_REPEAT | W_PREAMBLE, SYNC_V1_H1_WITH_PREAMBLE, HSTX_CMD_RAW_REPEAT | W_GUARDBAND, VIDEO_LEADING_GUARD_BAND, HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS }; /* Pre-compute the HDMI info packet that is required to switch the MS2130 to YCbCr422 mode */ void init_info_packet(void) { int len = 0; data_packet_t avi_info_frame; data_island_stream_t di_str; set_AVI_info_frame(&avi_info_frame, SCAN_INFO_NO_DATA, YCBCR422, ITU601, PIC_ASPECT_RATIO_16_9, SAME_AS_PAR, FULL, _1920x1080P60); encode_data_island(&di_str, &avi_info_frame, 0, 0); info_p[len++] = HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH; info_p[len++] = SYNC_V0_H1; info_p[len++] = HSTX_CMD_RAW_REPEAT | (MODE_H_SYNC_WIDTH - W_DATA_ISLAND - W_PREAMBLE); info_p[len++] = SYNC_V0_H0; info_p[len++] = HSTX_CMD_RAW_REPEAT | W_PREAMBLE; info_p[len++] = SYNC_V0_H0_WITH_DATA_ISLAND_PREAMBLE; info_p[len++] = HSTX_CMD_RAW | W_DATA_ISLAND; /* convert from the two symbols per word for each channel to one * symbol per word containing all three channels format */ for (int i = 0; i < N_DATA_ISLAND_WORDS; i++) { info_p[len++] = (di_str.data[0][i] & 0x3ff) | ((di_str.data[1][i] & 0x3ff) << 10) | ((di_str.data[2][i] & 0x3ff) << 20); info_p[len++] = (di_str.data[0][i] >> 10) | ((di_str.data[1][i] >> 10) << 10) | ((di_str.data[2][i] >> 10) << 20); } info_p[len++] = HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS); info_p[len++] = SYNC_V0_H1; info_len = len; } void __scratch_y("") hsdaoh_update_head(int stream_id, int head) { if (streams[stream_id].tail == head) streams[stream_id].overflow = true; else streams[stream_id].head = head; } #define DMACH_HSTX_START 13 #define DMACH_HSTX_COUNT 3 #define CRC16_INIT 0xffff static uint8_t hstx_dma_curchan = 0; static uint16_t saved_crc; static uint v_scanline = 3; static bool vactive_cmdlist_posted = false; static uint8_t dma_sniff_pipelined_ch = 0; static bool dma_sniff_pipelined_disable = false; metadata_t metadata = (metadata_t) { .magic = 0xda7acab1, .crc_config = CRC16_2_LINE, .version = 1, .flags = FLAG_STREAM_ID_PRESENT | FLAG_FORMAT_ID_PRESENT, .max_streamid = MAX_STREAMS }; /* HSTX DMA IRQ handler, reconfigures the channel that just completed while * ther other channel is currently busy */ void __scratch_x("") hstx_dma_irq_handler() { /* This is a bit tricky and time critical, we pipeline three DMA transfers to avoid an * underrun, but the DMA sniffer that is used to calculate the CRC cannot be pipelined * and needs to be reconfigured right before the DMA transfer starts - so we have to * do that as fast as possible during blanking, before the next DMA transfer with * active video data, which is right about to start. */ if (dma_sniff_pipelined_ch) { /* (re)initialize DMA CRC sniffer */ saved_crc = dma_sniffer_get_data_accumulator() & 0xffff; dma_sniffer_set_data_accumulator(CRC16_INIT); dma_sniffer_enable(dma_sniff_pipelined_ch, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); dma_sniff_pipelined_ch = 0; } else if (dma_sniff_pipelined_disable) { dma_sniffer_disable(); dma_sniff_pipelined_disable = false; } uint ch_num = hstx_dma_curchan + DMACH_HSTX_START; hstx_dma_curchan = (hstx_dma_curchan + 1) % DMACH_HSTX_COUNT; dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; dma_hw->intr = 1u << ch_num; if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) { /* on first line of actual VSYNC, output data packet */ if (v_scanline == MODE_V_FRONT_PORCH) { dma_sniff_pipelined_disable = true; ch->read_addr = (uintptr_t)info_p; ch->transfer_count = info_len; /* increment framecounter*/ metadata.framecounter++; /* latch data counters into metadata at beginning of frame */ for (uint i = 0; i < MAX_STREAMS; i++) metadata.stream_info[i].data_cnt = streams[i].data_cnt; } else { ch->read_addr = (uintptr_t)vblank_line_vsync_on; ch->transfer_count = count_of(vblank_line_vsync_on); } } else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) { ch->read_addr = (uintptr_t)vblank_line_vsync_off; ch->transfer_count = count_of(vblank_line_vsync_off); } else if (!vactive_cmdlist_posted) { ch->read_addr = (uintptr_t)vactive_line; ch->transfer_count = count_of(vactive_line); vactive_cmdlist_posted = true; } else { /* Output of actual data in active video lines */ uint16_t cur_active_line = v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES); uint16_t *next_line = (cur_active_line % 2) ? idle_line_buf1 : idle_line_buf2; next_line[RBUF_SLICE_LEN - 1] = 0; for (uint i = 0; i < MAX_STREAMS; i++) { stream_t *stream = &streams[i]; if (!stream->active) continue; int next_tail = (stream->tail + 1) % stream->rbuf_slices; if (stream->head != next_tail) { next_line = &stream->rbuf[stream->tail * RBUF_SLICE_LEN]; stream->tail = next_tail; stream->data_cnt += stream->len; if (stream->overflow) { /* signal that there has been an overflow */ next_line[RBUF_SLICE_LEN - 1] = 0x0fff; stream->overflow = false; } else next_line[RBUF_SLICE_LEN - 1] = stream->len; next_line[RBUF_SLICE_LEN - 3] = (stream->format << 6) | (i & 0x3f); // stream and format ID break; } } /* fill in metadata word (last word of line) */ if (cur_active_line < (sizeof(metadata_t) * 2)) { uint8_t *met_p = (uint8_t *)&metadata; if (cur_active_line % 2) next_line[RBUF_SLICE_LEN - 1] |= ((met_p[cur_active_line/2] & 0xf0) << 8); else next_line[RBUF_SLICE_LEN - 1] |= ((met_p[cur_active_line/2] & 0x0f) << 12); } /* on the second last word of the line, insert the CRC16 of the entire line before the last line */ next_line[RBUF_SLICE_LEN - 2] = saved_crc; dma_sniff_pipelined_ch = ch_num; ch->read_addr = (uintptr_t)next_line; ch->transfer_count = MODE_H_ACTIVE_PIXELS/2; vactive_cmdlist_posted = false; } if (!vactive_cmdlist_posted) v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES; } void core1_entry() { irq_set_exclusive_handler(DMA_IRQ_3, hstx_dma_irq_handler); irq_set_enabled(DMA_IRQ_3, true); while (1) __wfi(); } void hsdaoh_start(void) { multicore_launch_core1(core1_entry); dma_channel_start(DMACH_HSTX_START); } int hsdaoh_add_stream(uint16_t stream_id, uint16_t format, uint32_t samplerate, uint length, uint slices, uint16_t *ringbuf) { if (stream_id >= MAX_STREAMS) return -1; stream_t *stream = &streams[stream_id]; stream->active = false; stream->rbuf = ringbuf; stream->format = format; stream->srate = samplerate; stream->len = length; stream->rbuf_slices = slices; stream->tail = slices-1; stream->head = 0; stream->data_cnt = 0; stream->active = true; metadata.stream_info[stream_id].srate = samplerate; if (stream_id == 0) metadata.stream0_format = format; return 0; } int hsdaoh_remove_stream(uint16_t stream_id) { if (stream_id >= MAX_STREAMS) return -1; streams[stream_id].active = false; return 0; } /* set system clock, without overclocking the QSPI flash */ void hsdaoh_set_sys_clock_khz(uint32_t freq_khz) { uint clkdiv = freq_khz/40000; if (freq_khz % 40000) clkdiv++; if (clkdiv < 4) clkdiv = 4; else if (clkdiv > 8) clkdiv = 8; /* set QSPI clock divider */ hw_write_masked(&qmi_hw->m[0].timing, clkdiv << QMI_M0_TIMING_CLKDIV_LSB, QMI_M0_TIMING_CLKDIV_BITS ); __asm__ __volatile__("dmb sy"); set_sys_clock_khz(freq_khz, true); } void hsdaoh_init(int dstrength, int slewrate) { for (uint i = 0; i < MAX_STREAMS; i++) streams[i].active = false; init_info_packet(); /* Configure HSTX's TMDS encoder for YCbCr422 stream: L0 is unused in this * mode on the MS2130 and carries the same data as L1. This way we can * conveniently use 32-bit DMA transfers to transparently transfer data to * libhsdaoh on the host */ hstx_ctrl_hw->expand_tmds = 7 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | 8 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | 7 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | 0 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | 7 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | 0 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; /* Both to-be TMDS encoded pixels and raw control words arrive as 32-bit * words. While for the raw control words this means they carry the 3x 10 * bit data for the three lanes (1 word per symbol), the actual data * contains two times 16-bit data per 32-bit word */ hstx_ctrl_hw->expand_shift = 2 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | 16 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB | 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; /* Serial output config: clock period of 5 cycles, pop from command * expander every 5 cycles, shift the output shiftreg by 2 every cycle. */ hstx_ctrl_hw->csr = 0; hstx_ctrl_hw->csr = HSTX_CTRL_CSR_EXPAND_EN_BITS | 5u << HSTX_CTRL_CSR_CLKDIV_LSB | 5u << HSTX_CTRL_CSR_N_SHIFTS_LSB | 2u << HSTX_CTRL_CSR_SHIFT_LSB | HSTX_CTRL_CSR_EN_BITS; // HSTX outputs 0 through 7 appear on GPIO 12 through 19. // Pinout on Pico DVI sock: // // GP12 D0+ GP13 D0- // GP14 CK+ GP15 CK- // GP16 D2+ GP17 D2- // GP18 D1+ GP19 D1- // Assign clock pair to two neighbouring pins: hstx_ctrl_hw->bit[2] = HSTX_CTRL_BIT0_CLK_BITS; hstx_ctrl_hw->bit[3] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS; for (uint lane = 0; lane < 3; ++lane) { // For each TMDS lane, assign it to the correct GPIO pair based on the // desired pinout: static const int lane_to_output_bit[3] = {0, 6, 4}; int bit = lane_to_output_bit[lane]; // Output even bits during first half of each HSTX cycle, and odd bits // during second half. The shifter advances by two bits each cycle. uint32_t lane_data_sel_bits = (lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB | (lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB; // The two halves of each pair get identical data, but one pin is inverted. hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits; hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS; } for (int i = 12; i <= 19; ++i) { gpio_set_input_enabled(i, false); gpio_disable_pulls(i); gpio_set_drive_strength(i, dstrength); gpio_set_slew_rate(i, slewrate); gpio_set_function(i, 0); // HSTX } /* All channels are set up identically, to transfer a whole scanline and * then chain to the next channel. Each time a channel finishes, we * reconfigure the one that just finished, meanwhile another channel * is already making progress. */ for (int i = 0; i < DMACH_HSTX_COUNT; i++) { dma_channel_config c; c = dma_channel_get_default_config(DMACH_HSTX_START + i); int chain_to_ch = DMACH_HSTX_START + ((i + 1) % DMACH_HSTX_COUNT); channel_config_set_chain_to(&c, chain_to_ch); channel_config_set_dreq(&c, DREQ_HSTX); channel_config_set_sniff_enable(&c, true); channel_config_set_transfer_data_size(&c, DMA_SIZE_32); dma_channel_configure( DMACH_HSTX_START + i, &c, &hstx_fifo_hw->fifo, vblank_line_vsync_off, count_of(vblank_line_vsync_off), false ); dma_hw->ints3 |= 1u << (DMACH_HSTX_START + i); dma_hw->inte3 |= 1u << (DMACH_HSTX_START + i); } /* give the DMA the priority over the CPU on the bus */ bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS; } ================================================ FILE: libpicohsdaoh/picohsdaoh.h ================================================ #ifndef _PICOHSDAOH_H #define _PICOHSDAOH_H // trimmed to absolute minimum timings the MS2130 accepts #define MODE_H_FRONT_PORCH 6 #define MODE_H_SYNC_WIDTH 45 #define MODE_H_BACK_PORCH 11 #define MODE_H_ACTIVE_PIXELS 1920 #define MODE_V_FRONT_PORCH 1 #define MODE_V_SYNC_WIDTH 2 #define MODE_V_BACK_PORCH 1 #define MODE_V_ACTIVE_LINES 1080 #define MODE_H_TOTAL_PIXELS ( \ MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \ MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \ ) #define MODE_V_TOTAL_LINES ( \ MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \ MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \ ) // stream ID, CRC word and length/metadata word, so 3 reserved words #define NUM_RESERVED_WORDS 3 #define RBUF_DEFAULT_SLICES 16 #define RBUF_SLICE_LEN MODE_H_ACTIVE_PIXELS #define RBUF_MAX_DATA_LEN (RBUF_SLICE_LEN - NUM_RESERVED_WORDS) #define RBUF_DEFAULT_TOTAL_LEN (RBUF_SLICE_LEN * RBUF_DEFAULT_SLICES) #define MAX_STREAMS 4 enum crc_config { CRC_NONE, /* No CRC, just 16 bit idle counter */ CRC16_1_LINE, /* Line contains CRC of the last line */ CRC16_2_LINE /* Line contains CRC of the line before the last line */ }; typedef struct { uint64_t data_cnt; uint32_t srate; uint32_t reserved1; char reserved2[16]; } __attribute__((packed, aligned(1))) stream_info_t; typedef struct { uint32_t magic; uint16_t framecounter; uint8_t reserved1; uint8_t crc_config; uint16_t version; uint32_t flags; uint32_t reserved2[8]; uint16_t stream0_format; uint16_t max_streamid; stream_info_t stream_info[MAX_STREAMS]; } __attribute__((packed, aligned(1))) metadata_t; #define FLAG_STREAM_ID_PRESENT (1 << 0) #define FLAG_FORMAT_ID_PRESENT (1 << 1) enum { RAW_8BIT, RAW_16BIT, RAW_24BIT, RAW_32BIT, RAW_64BIT, PIO_1BIT, PIO_2BIT, PIO_3BIT, PIO_4BIT, PIO_5BIT, PIO_6BIT, PIO_7BIT, PIO_8BIT, PIO_8BIT_DUAL, PIO_8BIT_IQ, PIO_9BIT, PIO_10BIT, PIO_10BIT_DUAL, PIO_10BIT_IQ, PIO_11BIT, PIO_12BIT, PIO_12BIT_DUAL, PIO_12BIT_IQ, PIO_13BIT, PIO_14BIT, PIO_14BIT_DUAL, PIO_14BIT_IQ, PIO_15BIT, PIO_16BIT, PIO_16BIT_DUAL, PIO_16BIT_IQ, PIO_17BIT, PIO_18BIT, PIO_19BIT, PIO_20BIT, PIO_24BIT, PIO_24BIT_IQ, PIO_28BIT, PIO_32BIT, PIO_32BIT_IQ, PIO_PCM1802_AUDIO, PIO_AUDIO_PLACEHOLDER1, PIO_AUDIO_PLACEHOLDER2, PIO_AUDIO_PLACEHOLDER3, PIO_DUALCHAN_1BIT, PIO_DUALCHAN_1BIT_IQ, PIO_DUALCHAN_2BIT, PIO_DUALCHAN_2BIT_IQ, PIO_DUALCHAN_4BIT, PIO_DUALCHAN_4BIT_IQ, PIO_DUALCHAN_8BIT, PIO_DUALCHAN_8BIT_IQ, PIO_DUALCHAN_10BIT, PIO_DUALCHAN_10BIT_IQ, PIO_DUALCHAN_12BIT, PIO_DUALCHAN_12BIT_IQ, PIO_DUALCHAN_14BIT, PIO_DUALCHAN_14BIT_IQ, PIO_DUALCHAN_16BIT, PIO_DUALCHAN_16BIT_IQ, PIO_DUALCHAN_24BIT, PIO_DUALCHAN_24BIT_IQ, PIO_DUALCHAN_32BIT, PIO_DUALCHAN_32BIT_IQ, }; void hsdaoh_start(void); void hsdaoh_update_head(int stream_id, int head); int hsdaoh_add_stream(uint16_t stream_id, uint16_t format, uint32_t samplerate, uint length, uint slices, uint16_t *ringbuf); int hsdaoh_remove_stream(uint16_t stream_id); void hsdaoh_set_sys_clock_khz(uint32_t freq_khz); void hsdaoh_init(int dstrength, int slewrate); #endif ================================================ FILE: pico_sdk_import.cmake ================================================ # This is a copy of /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 () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") endif () if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) set(PICO_SDK_FETCH_FROM_GIT_TAG "master") message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") endif() set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of 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") set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for 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 () # GIT_SUBMODULES_RECURSE was added in 3.17 if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} GIT_SUBMODULES_RECURSE FALSE ) else () FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} ) endif () if (NOT pico_sdk) message("Downloading Raspberry Pi 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 "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 Raspberry Pi Pico SDK") endif () set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) include(${PICO_SDK_INIT_CMAKE_FILE})