commit 2e644a593b3eb7a12b449a3eb290f8203bd97476 Author: Steve Markgraf Date: Sun Nov 17 23:51:37 2024 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a007fea --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ad93ada --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +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) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3368b1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..115ac59 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# hsdaoh-rp2350 - High Speed Data Acquisition over HDMI +## Stream up to 75 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 75 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. + +### 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 GP22, 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 320 MHz and driving the ADC with a 40 MHz clock. With higher overclocking up to 50.25 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). + +![Pico2 with AD9226 ADC board](https://steve-m.de/projects/hsdaoh/rp2350_external_adc.jpg) + +## 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. diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt new file mode 100644 index 0000000..b14d41d --- /dev/null +++ b/apps/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(counter) +add_subdirectory(external_adc) +add_subdirectory(internal_adc) diff --git a/apps/counter/CMakeLists.txt b/apps/counter/CMakeLists.txt new file mode 100644 index 0000000..ab788a8 --- /dev/null +++ b/apps/counter/CMakeLists.txt @@ -0,0 +1,21 @@ +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) diff --git a/apps/counter/counter.c b/apps/counter/counter.c new file mode 100644 index 0000000..333656a --- /dev/null +++ b/apps/counter/counter.c @@ -0,0 +1,136 @@ +/* + * 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 250000 + +#define DMACH_PIO_PING 0 +#define DMACH_PIO_PONG 1 + +static bool pio_dma_pong = false; +uint16_t ringbuffer[RBUF_TOTAL_LEN]; +int ringbuf_head = 0; + +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_SLICES; + + ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; + ch->transfer_count = RBUF_DATA_LEN; + + hsdaoh_update_head(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_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_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_sys_clock_khz(SYS_CLK, true); + + /* 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(ringbuffer); + hsdaoh_start(); + init_pio_input(); + + while (1) + __wfi(); +} diff --git a/apps/counter/counter.pio b/apps/counter/counter.pio new file mode 100644 index 0000000..c6bd777 --- /dev/null +++ b/apps/counter/counter.pio @@ -0,0 +1,42 @@ +; +; 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, 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); +} +%} diff --git a/apps/external_adc/CMakeLists.txt b/apps/external_adc/CMakeLists.txt new file mode 100644 index 0000000..edca3c9 --- /dev/null +++ b/apps/external_adc/CMakeLists.txt @@ -0,0 +1,20 @@ +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) + +# 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) diff --git a/apps/external_adc/adc_12bit_input.pio b/apps/external_adc/adc_12bit_input.pio new file mode 100644 index 0000000..9ace45f --- /dev/null +++ b/apps/external_adc/adc_12bit_input.pio @@ -0,0 +1,110 @@ +; +; Copyright (c) 2024 Steve Markgraf +; +; SPDX-License-Identifier: BSD-3-Clause +; +; Sample 12 bit parallel ADC (AD9226) every 4 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: A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 B03 B02 B01 B00 +; Second word: B11 B10 B09 B08 B07 B06 B05 B04 C07 C06 C05 C04 C03 C02 C01 C00 +; Third word: D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 C11 C10 C09 C08 + +.pio_version 0 +.program adc_12bit_input +.side_set 1 + +public entry_point: + +.wrap_target + ;---------------------------------------------------------------------------------------- + in pins, 12 side 1 ; 12 bits of first sample in ISR SAMP + nop side 1 + nop side 0 + nop side 0 + ; ISR is now: 0 0 0 0 A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 + ;---------------------------------------------------------------------------------------- + mov osr, pins side 1 ; 12 bits of second sample in OSR SAMP + ; OSR is now: 0 0 0 0 B11 B10 B09 B08 B07 B06 B05 B04 B03 B02 B01 B00 + + in osr, 4 side 1 ; 12 of first, 4 of second sample in ISR AUTOPUSH + ; ISR is now: A11 A10 A09 A08 A07 A06 A05 A04 A03 A02 A01 A00 B03 B02 B01 B00 + + out null, 4 side 0 + ; OSR is now: 0 0 0 0 0 0 0 0 B11 B10 B09 B08 B07 B06 B05 B04 + + nop side 0 + ;---------------------------------------------------------------------------------------- + mov x, pins side 1 ; 12 bits of third sample in X, 8 of second remaining in OSR SAMP + in osr, 8 side 1 ; 8 of second sample now in ISR, OSR is now empty an can be re-used + ; ISR is now: 0 0 0 0 0 0 0 0 B11 B10 B09 B08 B07 B06 B05 B04 + + mov osr, x side 0 + ; OSR is now 0 0 0 0 C11 C10 C09 C08 C07 C06 C05 C04 C03 C02 C01 C00 + + in osr, 8 side 0 ; autopush happening AUTOPUSH + ; ISR is now: B11 B10 B09 B08 B07 B06 B05 B04 C07 C06 C05 C04 C03 C02 C01 C00 + ;---------------------------------------------------------------------------------------- + in pins, 12 side 1 ; sample fourth sample to ISR SAMP + ; ISR is now 0 0 0 0 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 + + out null, 8 side 1 + ; OSR is now 0 0 0 0 0 0 0 0 0 0 0 0 C11 C10 C09 C08 + + + in osr, 4 side 0 ; send out remaining part of third sample and fourth sample AUTOPUSH + ; ISR is now 0 0 0 0 D11 D10 D09 D08 D07 D06 D05 D04 D03 D02 D01 D00 C11 C10 C09 C08 + 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, 1, false, false); + + pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true); + pio_gpio_init(pio, clk_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, 12, false); + + // Connect these GPIOs to this PIO block + for (int i = pin; i < (pin+12); i++) + pio_gpio_init(pio, pin + 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); +} +%} diff --git a/apps/external_adc/external_adc.c b/apps/external_adc/external_adc.c new file mode 100644 index 0000000..0f99588 --- /dev/null +++ b/apps/external_adc/external_adc.c @@ -0,0 +1,148 @@ +/* + * 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 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 "adc_12bit_input.pio.h" + +/* 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 +//#define SYS_CLK 384000 // 48 MHz ADC clock +//#define SYS_CLK 402000 // 50.25 MHz ADC clock, maximum that works on my Pico2 (with overvolting) + +// ADC is attached to GP0 - GP11 with clock on GP22 +#define PIO_INPUT_PIN_BASE 0 +#define PIO_OUTPUT_CLK_PIN 22 + +#define DMACH_PIO_PING 0 +#define DMACH_PIO_PONG 1 + +static bool pio_dma_pong = false; +uint16_t ringbuffer[RBUF_TOTAL_LEN]; +int ringbuf_head = 0; + +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_SLICES; + + ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; + ch->transfer_count = RBUF_DATA_LEN; + + hsdaoh_update_head(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], + RBUF_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_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_ms(1); + + set_sys_clock_khz(SYS_CLK, true); + + /* 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(ringbuffer); + hsdaoh_start(); + init_pio_input(); + + while (1) + __wfi(); +} diff --git a/apps/internal_adc/CMakeLists.txt b/apps/internal_adc/CMakeLists.txt new file mode 100644 index 0000000..55322c3 --- /dev/null +++ b/apps/internal_adc/CMakeLists.txt @@ -0,0 +1,19 @@ +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) diff --git a/apps/internal_adc/internal_adc.c b/apps/internal_adc/internal_adc.c new file mode 100644 index 0000000..9c52a4f --- /dev/null +++ b/apps/internal_adc/internal_adc.c @@ -0,0 +1,161 @@ +/* + * 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_TOTAL_LEN]; +int ringbuf_head = 0; + +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_SLICES; + + ch->write_addr = (uintptr_t)&ringbuffer[ringbuf_head * RBUF_SLICE_LEN]; + ch->transfer_count = RBUF_DATA_LEN; + + hsdaoh_update_head(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_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_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_ms(1); + + set_sys_clock_khz(SYS_CLK, true); + + /* 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(ringbuffer); + hsdaoh_start(); + init_adc_input(); + + while (1) + __wfi(); +} diff --git a/libpicohsdaoh/CMakeLists.txt b/libpicohsdaoh/CMakeLists.txt new file mode 100644 index 0000000..85d65f3 --- /dev/null +++ b/libpicohsdaoh/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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 + ) diff --git a/libpicohsdaoh/data_packet.c b/libpicohsdaoh/data_packet.c new file mode 100644 index 0000000..2dde2dc --- /dev/null +++ b/libpicohsdaoh/data_packet.c @@ -0,0 +1,271 @@ +/* + * 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_; +} diff --git a/libpicohsdaoh/data_packet.h b/libpicohsdaoh/data_packet.h new file mode 100644 index 0000000..3899265 --- /dev/null +++ b/libpicohsdaoh/data_packet.h @@ -0,0 +1,97 @@ +#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 diff --git a/libpicohsdaoh/picohsdaoh.c b/libpicohsdaoh/picohsdaoh.c new file mode 100644 index 0000000..34a9c42 --- /dev/null +++ b/libpicohsdaoh/picohsdaoh.c @@ -0,0 +1,371 @@ +/* + * hsdaoh - High Speed Data Acquisition over MS213x USB3 HDMI capture sticks + * Implementation for the Raspberry Pi RP2350 HSTX peripheral + * + * Copyright (c) 2024 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/structs/bus_ctrl.h" +#include "hardware/structs/hstx_ctrl.h" +#include "hardware/structs/hstx_fifo.h" +#include "pico/multicore.h" + +#include "data_packet.h" + +#include "pico/stdlib.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 *ring_buf = NULL; +uint16_t idle_line_buf[MODE_H_ACTIVE_PIXELS]; +uint32_t info_p[64]; +uint32_t info_len; + +int fifo_tail = 0; +int fifo_head = 0; + +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 hsdaoh_update_head(int head) +{ + fifo_head = head; +} + +#define DMACH_HSTX_PING 14 +#define DMACH_HSTX_PONG 15 +#define CRC16_INIT 0xffff + +static bool hstx_dma_pong = false; +static uint v_scanline = 2; +static bool vactive_cmdlist_posted = false; +static uint16_t framecnt = 0; + +uint8_t metadata[] = { + /* 0xda7acab1 magic word for hsdaoh */ + 0x1, 0xb, 0xa, 0xc, 0xa, 0x7, 0xa, 0xd, + /* u16: placeholder for framecounter */ + 0, 0, 0, 0, + /* u8: pack_state */ + 0, 0, + /* u8: crc_config: CRC16 of last line */ + 1, 0, +}; + +/* HSTX DMA IRQ handler, reconfigures the channel that just completed while + * ther other channel is currently busy */ +void __scratch_x("") hstx_dma_irq_handler() +{ + uint ch_num = hstx_dma_pong ? DMACH_HSTX_PONG : DMACH_HSTX_PING; + dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; + dma_hw->intr = 1u << ch_num; + hstx_dma_pong = !hstx_dma_pong; + + /* for raw commands we need to use 32 bit DMA transfers */ + ch->al1_ctrl = (ch->al1_ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (DMA_SIZE_32 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); + + 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_sniffer_disable(); + ch->read_addr = (uintptr_t)info_p; + ch->transfer_count = info_len; + + /* update frame counter in metadata */ + metadata[8] = framecnt & 0xf; + metadata[9] = (framecnt >> 4) & 0xf; + metadata[10] = (framecnt >> 8) & 0xf; + metadata[11] = (framecnt >> 12) & 0xf; + framecnt++; + } 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 *next_line; + int next_tail = (fifo_tail + 1) % RBUF_SLICES; + + if (fifo_head == next_tail) { + /* No data to send, use idle line */ + next_line = idle_line_buf; + next_line[RBUF_SLICE_LEN - 1] = 0; + } else { + next_line = &ring_buf[fifo_tail * RBUF_SLICE_LEN]; + fifo_tail = next_tail; + next_line[RBUF_SLICE_LEN - 1] = RBUF_DATA_LEN; + } + + uint16_t cur_active_line = v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES); + + /* fill in metadata word (last word of line) */ + if (cur_active_line < sizeof(metadata)) + next_line[RBUF_SLICE_LEN - 1] |= (metadata[cur_active_line] << 12); + + /* on the second last word of the line, insert the CRC16 of the entire previous line */ + next_line[RBUF_SLICE_LEN - 2] = dma_sniffer_get_data_accumulator() & 0xffff; + + /* (re)initialize DMA CRC sniffer */ + dma_sniffer_set_data_accumulator(CRC16_INIT); + dma_sniffer_enable(ch_num, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + + /* switch to 16 bit DMA transfer size for the actual data, + * because for YCbCr422 TMDS channel 0 is unused */ + ch->al1_ctrl = (ch->al1_ctrl & ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS) | (DMA_SIZE_16 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); + ch->read_addr = (uintptr_t)next_line; + ch->transfer_count = MODE_H_ACTIVE_PIXELS; + 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_PING); +} + +void hsdaoh_init(uint16_t *ringbuf)//struct hsdaoh_inst *inst, uint16_t *ringbuf) +{ + ring_buf = ringbuf; + + 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 16-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 one word + * per symbol. While the raw control words arrive as 32-bit to carry the 3x 10 + * bit data for the three lanes, the actual data arrives as a 16-bit word + * that gets duplicated before entering the HSTX */ + hstx_ctrl_hw->expand_shift = + 1 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB | + 0 << 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_function(i, 0); // HSTX + + /* Both channels are set up identically, to transfer a whole scanline and + * then chain to the opposite channel. Each time a channel finishes, we + * reconfigure the one that just finished, meanwhile the opposite channel + * is already making progress. */ + dma_channel_config c; + c = dma_channel_get_default_config(DMACH_HSTX_PING); + channel_config_set_chain_to(&c, DMACH_HSTX_PONG); + channel_config_set_dreq(&c, DREQ_HSTX); + channel_config_set_sniff_enable(&c, true); + dma_channel_configure( + DMACH_HSTX_PING, + &c, + &hstx_fifo_hw->fifo, + vblank_line_vsync_off, + count_of(vblank_line_vsync_off), + false + ); + c = dma_channel_get_default_config(DMACH_HSTX_PONG); + channel_config_set_chain_to(&c, DMACH_HSTX_PING); + channel_config_set_dreq(&c, DREQ_HSTX); + channel_config_set_sniff_enable(&c, true); + dma_channel_configure( + DMACH_HSTX_PONG, + &c, + &hstx_fifo_hw->fifo, + vblank_line_vsync_off, + count_of(vblank_line_vsync_off), + false + ); + + dma_hw->ints3 = (1u << DMACH_HSTX_PING) | (1u << DMACH_HSTX_PONG); + dma_hw->inte3 = (1u << DMACH_HSTX_PING) | (1u << DMACH_HSTX_PONG); + + /* 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; +} diff --git a/libpicohsdaoh/picohsdaoh.h b/libpicohsdaoh/picohsdaoh.h new file mode 100644 index 0000000..22db75a --- /dev/null +++ b/libpicohsdaoh/picohsdaoh.h @@ -0,0 +1,37 @@ +#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 \ +) + +// CRC word and length/metadata word, so 2 reserved words +// padded to 3 so that for the 12bit/16bit packed format +// each line starts with word 0 +#define NUM_RESERVED_WORDS 3 +#define RBUF_SLICES 16 +#define RBUF_SLICE_LEN MODE_H_ACTIVE_PIXELS +#define RBUF_DATA_LEN (RBUF_SLICE_LEN - NUM_RESERVED_WORDS) +#define RBUF_TOTAL_LEN (RBUF_SLICE_LEN * RBUF_SLICES) + +void hsdaoh_start(void); +void hsdaoh_init(uint16_t *ringbuf); +void hsdaoh_update_head(int head); + +#endif diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..a0721d0 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,84 @@ +# 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})