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