Mercurial > ~darius > hgwebdir.cgi > modulator
changeset 9:3acdebd7eec7
Make it actually work
author | Daniel O'Connor <darius@dons.net.au> |
---|---|
date | Fri, 21 Feb 2025 17:27:22 +1030 |
parents | 0249d0cecac4 |
children | 98880b18bcc1 |
files | CMakeLists.txt dac.pio modulator.c |
diffstat | 3 files changed, 96 insertions(+), 100 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sun Feb 16 14:36:33 2025 +1030 +++ b/CMakeLists.txt Fri Feb 21 17:27:22 2025 +1030 @@ -26,6 +26,7 @@ #target_compile_options(${NAME} PRIVATE -Wall -Wextra -Werror) pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/dac.pio) +pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/trigger.pio) target_link_libraries(${NAME} pico_stdlib
--- a/dac.pio Sun Feb 16 14:36:33 2025 +1030 +++ b/dac.pio Fri Feb 21 17:27:22 2025 +1030 @@ -1,14 +1,24 @@ ; -; Copyright (c) 2021 Daniel O'Connor +; Copyright (c) 2025 Daniel O'Connor ; .program dac +.define TRIGGER_IRQ 0 +; Need 1 side set pin, the clock +.side_set 1 +; Force load at address 0 so DMA handler can force a jump +; presumably we could calculate the address but this is easier. +.origin 0 -.side_set 1 +; Clock in a 0 byte +; mov pins, null side 0 +; nop side 1 +; Wait for start trigger +; wait 1 irq TRIGGER_IRQ side 0 ; Clock DAC and write data from the FIFO - +; DAC clocks data in on the rising clock edge +; Autopull is enabled so no need to pull .wrap_target - pull side 1 out pins 8 side 0 nop side 1 out pins 8 side 0 @@ -16,6 +26,7 @@ out pins 8 side 0 nop side 1 out pins 8 side 0 + nop side 1 .wrap % c-sdk { @@ -34,12 +45,12 @@ sm_config_set_out_shift( &c, true, // Shift-to-right - false, // Autopull enabled + true, // Autopull enabled 32 // Autopull threshold (bits!) ); - // Configure clock as sideset pin - sm_config_set_sideset_pins(&c, pin + 7); + // Configure sideset pin to use for clock + sm_config_set_sideset_pins(&c, pin + 8); // We only send, so disable the RX FIFO to make the TX FIFO deeper. sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
--- a/modulator.c Sun Feb 16 14:36:33 2025 +1030 +++ b/modulator.c Fri Feb 21 17:27:22 2025 +1030 @@ -37,6 +37,7 @@ #pragma GCC diagnostic pop #include "dac.pio.h" +#include "trigger.pio.h" // https://github.com/howerj/q // Modified to be Q20.12 rather than Q16.16 @@ -44,19 +45,40 @@ #include "shaped-trap.h" +// Pulse control bits +#define SENSE 0x01 +#define GATE 0x02 +#define PHINV 0x04 +#define PACTIVE 0x08 + static int dma_chan; uint8_t pulse_data[65536]; uint8_t pulse_ctrl[65536]; +unsigned slice_num = 0; +PIO pulse_pio; +uint pulse_sm; +/* + * Use a DMA channel to feed PIO0 SM0 with pulse data. + * Each DMA transfer is a single pulse. + * + * The PIO state machine waits to be triggered before starting + * so we can use another state machine to look for the trigger edge. + * + * When the DMA is done the IRQ handler will configure it for the next + * pulse (or not if it should stop). ie reset the PIO state machine + * back to waiting for an edge and re-arm the DMA. + */ void dma_handler(void) { // Clear the interrupt request. dma_hw->ints0 = 1u << dma_chan; // Give the channel a new wave table entry to read from, and re-trigger it //dma_channel_set_read_addr(dma_chan, shaped_trap_dat_start, true); + // Reset pulse state machine back to waiting for trigger + //pio_sm_exec(pulse_pio, pulse_sm, pio_encode_jump(0); } -unsigned slice_num = 0; void pwm_wrap(void) { @@ -70,17 +92,14 @@ // Trigger another pulse read out dma_channel_set_read_addr(dma_chan, pulse_data, true); + gpio_put(2, 1); + gpio_put(2, 0); } // Calculate pulse shape data // TODO: predistortion, proper sense, gate, phase, active, T/R switch // Could encode them as bit stream like data but more compact would be // (say) a list of counts to toggle pins at -#define SENSE 0x01 -#define GATE 0x02 -#define PHINV 0x04 -#define PACTIVE 0x08 - // Need to add pre/postgate/sense/phase counters unsigned compute_pulse(uint8_t *data, uint8_t *ctrl, unsigned datalen, uint16_t plen, char *code, uint8_t ncode, const uint8_t *shape, uint8_t shapelen, uint8_t codegap, uint8_t slew1, uint8_t slew2, uint8_t dcofs) { @@ -199,8 +218,6 @@ // Pulse down // Since the pulse is symmetrical just copy the up slope in reverse // XXX: if we had asymmetrical predistortion this wouldn't be true - // In that case probably best to do a single pulse up/down, then - // add predistortion and copy it to other bits for (uint16_t i = 0; i < shapesamples; i++) { data[idx++] = data[bit1stopup - i]; // Could replace this with a separate loop to poke it into place @@ -227,51 +244,6 @@ return idx - 1; } -// As per hello_interp.c -void interp_test(uint8_t *data, const uint8_t *shape) { - interp_config cfg; - const int uv_fractional_bits = 12; - - // Step size is 0.25 (ie stretching by 4x) - unsigned step = (1 << uv_fractional_bits) / 4; - - puts("interp_test\n"); - // Setup lane 0 to generate index into shape table - // Mask start is 0 because we use 8 bit samples - cfg = interp_default_config(); - interp_config_set_shift(&cfg, uv_fractional_bits); - interp_config_set_mask(&cfg, 0, 32 - uv_fractional_bits); - interp_config_set_blend(&cfg, true); - interp_set_config(interp0, 0, &cfg); - - // Setup lane 1 to LERP each sample pair - cfg = interp_default_config(); - interp_config_set_shift(&cfg, uv_fractional_bits - 8); - interp_config_set_signed(&cfg, false); - interp_config_set_cross_input(&cfg, true); // unsigned blending - interp_set_config(interp0, 1, &cfg); - - interp0->accum[0] = 0; // Initial offset into shape table - interp0->base[2] = (uintptr_t)shape; // Start of shape table - for (unsigned i = 0; i < 122 * 4; i++) { - // Get sample pair - uint8_t *sample_pair = (uint8_t *) interp0->peek[2]; - // Ask lane 1 for a LERP, using the lane 0 accumulator - interp0->base[0] = sample_pair[0]; - interp0->base[1] = sample_pair[1]; - uint32_t peek1 = interp0->peek[1]; - uint32_t add_raw1 = interp0->add_raw[1]; - data[i] = peek1; - printf("%d\t(%lu%% between %d and %d) %lu\n", - (int)peek1, - 100 * (add_raw1 & 0xff) / 0xff, - sample_pair[0], sample_pair[1], - interp_get_accumulator(interp0, 0)); - interp0->add_raw[0] = step; - } - puts("done"); -} - int main(void) { absolute_time_t then, now; @@ -288,13 +260,26 @@ gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); - - //interp_test(pulse_data, shaped_trap); + gpio_init(2); + gpio_set_dir(2, GPIO_OUT); +#if 0 + for (unsigned i = 7; i < 7 + 9; i++) { + printf("GPIO %d\n", i); + gpio_init(i); + gpio_set_dir(i, GPIO_OUT); + printf("on\n"); + gpio_put(i, 1); + __breakpoint(); + printf("off\n"); + gpio_put(i, 0); + __breakpoint(); + } +#endif uint32_t idx; uint16_t plen; char *code; - if (0) { + if (1) { plen = 8000; code = "1110010"; } else { @@ -320,10 +305,45 @@ printf("Pulse computation took %lld usec and created %lu samples - %.1f nsec/sample\n", diff, idx, (float)diff * 1000.0 / idx); __breakpoint(); - pwm_config c = pwm_get_default_config(); + + // Load the clocked_input program, and configure a free state machine + // to run the program. + PIO pulse_pio = pio0; + uint offset = pio_add_program(pulse_pio, &dac_program); + uint pulse_sm = pio_claim_unused_sm(pulse_pio, true); + // Data is GPIO7 to GPIO14, clock is GPIO15 + // Clock divisor of 2 so it runs at 60MHz and + // generates a 30MHz clock + dac_program_init(pulse_pio, pulse_sm, offset, 7, 2); + + // Configure a channel to write 32 bits at a time to PIO0 + // SM0's TX FIFO, paced by the data request signal from that peripheral. + dma_chan = dma_claim_unused_channel(true); + dma_channel_config dmac = dma_channel_get_default_config(dma_chan); + channel_config_set_transfer_data_size(&dmac, DMA_SIZE_32); + channel_config_set_read_increment(&dmac, true); + channel_config_set_dreq(&dmac, DREQ_PIO0_TX0); + + dma_channel_configure( + dma_chan, + &dmac, + &pio0_hw->txf[0], // Write address (only need to set this once) + NULL, // Don't provide a read address yet + (idx + 1) >> 2, // Transfer count (round up to 4 bytes) + false // Don't start yet + ); + + // Tell the DMA to raise IRQ line 0 when the channel finishes a block + dma_channel_set_irq0_enabled(dma_chan, true); + + // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted + irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); + irq_set_enabled(DMA_IRQ_0, true); + // 120MHz / 250 = 480kHz base // Maximum divisor is only 256 which limits the low end, // could further subdivide in the IRQ handler + pwm_config c = pwm_get_default_config(); pwm_config_set_clkdiv_int(&c, 250); // 8Hz pwm_config_set_wrap(&c, 60000 - 1); @@ -334,42 +354,6 @@ irq_set_enabled(PWM_IRQ_WRAP, true); pwm_init(slice_num, &c, true); - // Load the clocked_input program, and configure a free state machine - // to run the program. - PIO pio = pio0; - uint offset = pio_add_program(pio, &dac_program); - uint sm = pio_claim_unused_sm(pio, true); - // XXX: I would prefer starting at GPIO16 but in that case the top 2 - // bits don't seem to work - // Clock divisor of 2 so it runs at 60MHz and - // generates a 30MHz clock - dac_program_init(pio, sm, offset, 14, 2); - - // Configure a channel to write the same word (8 bits) repeatedly to PIO0 - // SM0's TX FIFO, paced by the data request signal from that peripheral. - dma_chan = dma_claim_unused_channel(true); - dma_channel_config dmac = dma_channel_get_default_config(dma_chan); - // XXX: need to get pulse generator to pad to 32 bits - channel_config_set_transfer_data_size(&dmac, DMA_SIZE_32); - channel_config_set_read_increment(&dmac, true); - channel_config_set_dreq(&dmac, DREQ_PIO0_TX0); - - dma_channel_configure( - dma_chan, - &dmac, - &pio0_hw->txf[0], // Write address (only need to set this once) - NULL, // Don't provide a read address yet - idx >> 2, // Transfer count - false // Don't start yet - ); - - // Tell the DMA to raise IRQ line 0 when the channel finishes a block - dma_channel_set_irq0_enabled(dma_chan, true); - - // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted - irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); - irq_set_enabled(DMA_IRQ_0, true); - // Everything else from this point is interrupt-driven. The processor has // time to sit and think about its early retirement -- maybe open a bakery? while (true) {