initial commit

This commit is contained in:
Steve Markgraf 2024-11-17 23:51:37 +01:00
commit 2e644a593b
19 changed files with 1633 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build/*

16
CMakeLists.txt Normal file
View file

@ -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)

28
LICENSE Normal file
View file

@ -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.

49
README.md Normal file
View file

@ -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=/<path-to>/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.

3
apps/CMakeLists.txt Normal file
View file

@ -0,0 +1,3 @@
add_subdirectory(counter)
add_subdirectory(external_adc)
add_subdirectory(internal_adc)

View file

@ -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)

136
apps/counter/counter.c Normal file
View file

@ -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 <steve@steve-m.de>
*
* 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();
}

42
apps/counter/counter.pio Normal file
View file

@ -0,0 +1,42 @@
;
; Copyright (c) 2024 Steve Markgraf <steve@steve-m.de>
;
; 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);
}
%}

View file

@ -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)

View file

@ -0,0 +1,110 @@
;
; Copyright (c) 2024 Steve Markgraf <steve@steve-m.de>
;
; 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);
}
%}

View file

@ -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 <steve@steve-m.de>
*
* 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();
}

View file

@ -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)

View file

@ -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 <steve@steve-m.de>
*
* 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();
}

View file

@ -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
)

271
libpicohsdaoh/data_packet.c Normal file
View file

@ -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 <string.h>
// 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_;
}

View file

@ -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

371
libpicohsdaoh/picohsdaoh.c Normal file
View file

@ -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 <steve@steve-m.de>
*
* 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;
}

View file

@ -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

84
pico_sdk_import.cmake Normal file
View file

@ -0,0 +1,84 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
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})