From 40dc8e2b17be6c7cdfdbefb2d88dd40a2e3b7690 Mon Sep 17 00:00:00 2001 From: Luke Wren Date: Sun, 24 Jan 2021 21:46:02 +0000 Subject: [PATCH] Add ADC DMA example --- adc/CMakeLists.txt | 1 + adc/dma_capture/CMakeLists.txt | 20 +++++ adc/dma_capture/dma_capture.c | 136 +++++++++++++++++++++++++++++++ adc/dma_capture/resistor_dac.pio | 38 +++++++++ 4 files changed, 195 insertions(+) create mode 100644 adc/dma_capture/CMakeLists.txt create mode 100644 adc/dma_capture/dma_capture.c create mode 100644 adc/dma_capture/resistor_dac.pio diff --git a/adc/CMakeLists.txt b/adc/CMakeLists.txt index c0e75fc..2fbb4fa 100644 --- a/adc/CMakeLists.txt +++ b/adc/CMakeLists.txt @@ -1,5 +1,6 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(adc_console) + add_subdirectory(dma_capture) add_subdirectory(hello_adc) add_subdirectory(joystick_display) endif () diff --git a/adc/dma_capture/CMakeLists.txt b/adc/dma_capture/CMakeLists.txt new file mode 100644 index 0000000..ff965e9 --- /dev/null +++ b/adc/dma_capture/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(adc_dma_capture + dma_capture.c + ) + +pico_generate_pio_header(adc_dma_capture ${CMAKE_CURRENT_LIST_DIR}/resistor_dac.pio) + +target_link_libraries(adc_dma_capture + pico_stdlib + hardware_adc + hardware_dma + # For the dummy output: + hardware_pio + pico_multicore + ) + +# create map/bin/hex file etc. +pico_add_extra_outputs(adc_dma_capture) + +# add url via pico_set_program_url +example_auto_set_url(adc_dma_capture) diff --git a/adc/dma_capture/dma_capture.c b/adc/dma_capture/dma_capture.c new file mode 100644 index 0000000..3a6688c --- /dev/null +++ b/adc/dma_capture/dma_capture.c @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +// For ADC input: +#include "hardware/adc.h" +#include "hardware/dma.h" +// For resistor DAC output: +#include "pico/multicore.h" +#include "hardware/pio.h" +#include "resistor_dac.pio.h" + +// This example uses the DMA to capture many samples from the ADC. +// +// - We are putting the ADC in free-running capture mode at 0.5 Msps +// +// - A DMA channel will be attached to the ADC sample FIFO +// +// - Configure the ADC to right-shift samples to 8 bits of significance, so we +// can DMA into a byte buffer +// +// This could be extended to use the ADC's round robin feature to sample two +// channels concurrently at 0.25 Msps each. +// +// It would be nice to have some analog samples to measure! This example also +// drives waves out through a 5-bit resistor DAC, as found on the reference +// VGA board. If you have that board, you can take an M-F jumper wire from +// GPIO 26 to the Green pin on the VGA connector (top row, next-but-rightmost +// hole). Or you can ignore that part of the code and connect your own signal +// to the ADC input. + +// Channel 0 is GPIO26 +#define CAPTURE_CHANNEL 0 +#define CAPTURE_DEPTH 1000 + +uint8_t capture_buf[CAPTURE_DEPTH]; + +void core1_main(); + +int main() { + stdio_init_all(); + + // Send core 1 off to start driving the "DAC" whilst we configure the ADC. + multicore_launch_core1(core1_main); + + // Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer. + adc_gpio_init(26 + CAPTURE_CHANNEL); + + 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, // We won't see the ERR bit because of 8 bit reads; disable. + true // Shift each sample to 8 bits when pushing to FIFO + ); + + // Divisor of 0 -> full speed. Free-running capture with the divider is + // equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1` + // cycles (div not necessarily an integer). Each conversion takes 96 + // cycles, so in general you want a divider of 0 (hold down the button + // continuously) or > 95 (take samples less frequently than 96 cycle + // intervals). This is all timed by the 48 MHz ADC clock. + adc_set_clkdiv(0); + + printf("Arming DMA\n"); + sleep_ms(1000); + // Set up the DMA to start transferring data as soon as it appears in FIFO + uint dma_chan = dma_claim_unused_channel(true); + dma_channel_config cfg = dma_channel_get_default_config(dma_chan); + + // Reading from constant address, writing to incrementing byte addresses + channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + + // Pace transfers based on availability of ADC samples + channel_config_set_dreq(&cfg, DREQ_ADC); + + dma_channel_configure(dma_chan, &cfg, + capture_buf, // dst + &adc_hw->fifo, // src + CAPTURE_DEPTH, // transfer count + true // start immediately + ); + + printf("Starting capture\n"); + adc_run(true); + + // Once DMA finishes, stop any new conversions from starting, and clean up + // the FIFO in case the ADC was still mid-conversion. + dma_channel_wait_for_finish_blocking(dma_chan); + printf("Capture finished\n"); + adc_run(false); + adc_fifo_drain(); + + // Print samples to stdout so you can display them in pyplot, excel, matlab + for (int i = 0; i < CAPTURE_DEPTH; ++i) { + printf("%-3d, ", capture_buf[i]); + if (i % 10 == 9) + printf("\n"); + } +} + +// ---------------------------------------------------------------------------- +// Code for driving the "DAC" output for us to measure + +// Core 1 is just going to sit and drive samples out continously. PIO provides +// consistent sample frequency. + +#define OUTPUT_FREQ_KHZ 5 +#define SAMPLE_WIDTH 5 +// This is the green channel on the VGA board +#define DAC_PIN_BASE 6 + +void core1_main() { + PIO pio = pio0; + uint sm = pio_claim_unused_sm(pio0, true); + uint offset = pio_add_program(pio0, &resistor_dac_5bit_program); + resistor_dac_5bit_program_init(pio0, sm, offset, + OUTPUT_FREQ_KHZ * 1000 * 2 * (1 << SAMPLE_WIDTH), DAC_PIN_BASE); + while (true) { + // Triangle wave + for (int i = 0; i < (1 << SAMPLE_WIDTH); ++i) + pio_sm_put_blocking(pio, sm, i); + for (int i = 0; i < (1 << SAMPLE_WIDTH); ++i) + pio_sm_put_blocking(pio, sm, (1 << SAMPLE_WIDTH) - 1 - i); + } +} + + diff --git a/adc/dma_capture/resistor_dac.pio b/adc/dma_capture/resistor_dac.pio new file mode 100644 index 0000000..2dca1f1 --- /dev/null +++ b/adc/dma_capture/resistor_dac.pio @@ -0,0 +1,38 @@ +; +; Copyright (c) 2021 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program resistor_dac_5bit + +; Drive one of the 5-bit resistor DACs on the VGA reference board. (this isn't +; a good way to do VGA -- just want a nice sawtooth for the ADC example!) + + out pins, 5 + + + +% c-sdk { +#include "hardware/clocks.h" +static inline void resistor_dac_5bit_program_init(PIO pio, uint sm, uint offset, + uint sample_rate_hz, uint pin_base) { + + pio_sm_set_pins_with_mask(pio, sm, 0, 0x1fu << pin_base); + pio_sm_set_pindirs_with_mask(pio, sm, ~0u, 0x1fu << pin_base); + for (int i = 0; i < 5; ++i) + pio_gpio_init(pio, pin_base + i); + + pio_sm_config c = resistor_dac_5bit_program_get_default_config(offset); + sm_config_set_out_pins(&c, pin_base, 5); + // Shift to right, autopull threshold 5 + sm_config_set_out_shift(&c, true, true, 5); + // Deeper FIFO as we're not doing any RX + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + float div = (float)clock_get_hz(clk_sys) / sample_rate_hz; + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%}