changeset 5:2db42eaba3c8

WIP
author Daniel O'Connor <darius@dons.net.au>
date Sat, 15 Feb 2025 22:57:18 +1030
parents b6416c4aadc8
children 5bed069f0c33
files CMakeLists.txt dac.pio modulator.c pico_sdk_import.cmake test.c
diffstat 5 files changed, 326 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Feb 04 14:20:07 2025 +1030
+++ b/CMakeLists.txt	Sat Feb 15 22:57:18 2025 +1030
@@ -16,15 +16,25 @@
 
 add_executable(${NAME}
     modulator.c
-)
+    q/q.c
+    )
+
+# Bring the pain
+set_source_files_properties(modulator.c PROPERTIES COMPILE_OPTIONS -Wall -Wextra -Werror)
+set_source_files_properties(q/q.c PROPERTIES COMPILE_OPTIONS -DQVERSION=0x902)
+# XXX: this didn't work - everything got the flags
+#target_compile_options(${NAME} PRIVATE -Wall -Wextra -Werror)
 
 pico_generate_pio_header(${NAME} ${CMAKE_CURRENT_LIST_DIR}/dac.pio)
 
 target_link_libraries(${NAME}
 		pico_stdlib
+		hardware_clocks
 		hardware_dma
+		hardware_interp
 		hardware_irq
 		hardware_pio
+		hardware_pwm
 )
 
 #bin2elf(${NAME} shaped-trap.dat)
--- a/dac.pio	Tue Feb 04 14:20:07 2025 +1030
+++ b/dac.pio	Sat Feb 15 22:57:18 2025 +1030
@@ -19,7 +19,7 @@
 .wrap
 
 % c-sdk {
-static inline void dac_program_init(PIO pio, uint sm, uint offset, uint pin) {
+static inline void dac_program_init(PIO pio, uint sm, uint offset, uint pin, uint clkdiv) {
     pio_sm_config c = dac_program_get_default_config(offset);
 
     // Set the OUT base pin to the provided `pin` parameter.
@@ -35,7 +35,7 @@
         &c,
         true,  // Shift-to-right
         false, // Autopull enabled
-        8      // Autopull threshold (bits!)
+        32     // Autopull threshold (bits!)
     );
 
     // Configure clock as sideset pin
@@ -44,7 +44,7 @@
     // We only send, so disable the RX FIFO to make the TX FIFO deeper.
     sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
 
-//    sm_config_set_clkdiv(&c, 100);
+    sm_config_set_clkdiv(&c, clkdiv);
 
     // Load our configuration, and start the program from the beginning
     pio_sm_init(pio, sm, offset, &c);
--- a/modulator.c	Tue Feb 04 14:20:07 2025 +1030
+++ b/modulator.c	Sat Feb 15 22:57:18 2025 +1030
@@ -17,46 +17,259 @@
 **
 */
 
+#include <stdio.h>
+#include <string.h>
+#include "bitstring.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+#pragma GCC diagnostic ignored "-Wsign-compare"
 #include "pico/stdlib.h"
 #include "hardware/clocks.h"
 #include "hardware/dma.h"
+#include "hardware/interp.h"
 #include "hardware/irq.h"
+#include "hardware/pll.h"
 #include "hardware/pio.h"
+#include "hardware/pwm.h"
+#include "hardware/structs/pll.h"
+#include "hardware/structs/clocks.h"
+#pragma GCC diagnostic pop
 
 #include "dac.pio.h"
 
-#if 0
-extern void* shaped_trap_dat_size;
-extern void* shaped_trap_dat_start;
+// https://github.com/howerj/q
+#include "q/q.h"
 
-extern void* sgauss_dat_size;
-extern void* sgauss_dat_start;
-#endif
-
-uint8_t shaped_trap_dat_start[] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 0, 0, 0, 0, 0, 0, 0 };
-//uint8_t shaped_trap_dat_start[] = { 0, 1, 2, 3, 1, 2, 0, 3, 1, 1, 2, 3, 0, 1, 2, 3 };
-#define SHAPED_TRAP_DAT_BITS 4
-_Static_assert((1 << SHAPED_TRAP_DAT_BITS) == sizeof(shaped_trap_dat_start));
+#include "shaped-trap.h"
 
 static int dma_chan;
+uint8_t pulse_data[65536];
+uint8_t pulse_ctrl[65536];
 
 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);
+    //dma_channel_set_read_addr(dma_chan, shaped_trap_dat_start, true);
+}
+
+unsigned slice_num = 0;
+
+void
+pwm_wrap(void) {
+  pwm_clear_irq(slice_num);
+#if 0
+  static unsigned state = 0;
+
+  gpio_put(PICO_DEFAULT_LED_PIN, state);
+  state = !state;
+#endif
+
+  // Trigger another pulse read out
+  dma_channel_set_read_addr(dma_chan, pulse_data, true);
+}
+
+// Calculate pulse shape data
+// TODO: 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) {
+  uint32_t shapesamples, nsamples, idx;
+  q_t dcscale, stepsize;
+  char tmps[20];
+
+  dcscale = qdiv(qsub(qint(255), qint(dcofs)), qint(255));
+  qsprint(dcscale, tmps, sizeof(tmps));
+  printf("dcscale = %s\n", tmps);
+
+  if (ncode == 1) {
+    // Number of samples for half of the pulse
+    // Do division first so we don't overflow Q16.16
+    shapesamples = qtoi(qmul(qdiv(qint(plen), qint(100)), qint(shapelen / 2)));
+    // Number of samples for everything
+    nsamples = shapesamples * 2 + slew1 + slew2;
+  } else {
+    shapesamples = plen / 2;
+    nsamples = shapesamples * 2 * ncode + codegap * (ncode - 1) + slew1 + slew2;
+  }
+
+  // Number of samples per step in the pulse shape
+  stepsize = qdiv(qint(shapesamples), qint(shapelen));
+  qsprint(stepsize, tmps, sizeof(tmps));
+  printf("shapelen = %d shapesamples = %lu nsamples = %lu stepsize = %s\n", shapelen, shapesamples, nsamples, tmps);
+
+  if (nsamples > datalen) {
+    printf("Pulse too long (%ld > %u)\n", nsamples, datalen);
+    return 0;
+  }
+  if (shapesamples < 2) {
+    printf("Pulse too short (%lu < %d)\n", shapesamples, 2);
+    return 0;
+  }
+  if (qtoi(shapesamples) > 65535) {
+    printf("Shape too long (%u > %d)\n", qtoi(shapesamples), 65535);
+    return 0;
+  }
+  idx = 0;
+
+  // Up slew
+  for (uint16_t i = 0; i < slew1; i++) {
+    data[idx++] = qtoi(qdiv(qmul(qint(dcofs), qint(i)), qint(slew1)));
+    ctrl[idx] |= PACTIVE;
+  }
+  for (uint16_t c = 0; c < ncode; c++) {
+    uint ctrltmp = PACTIVE;
+    if (code[c] == '0')
+      ctrltmp |= PHINV;
+
+    // Pulse up
+    for (uint16_t i = 0; i < shapesamples; i++) {
+      data[idx++] = dcofs + qtoi(qmul(qint(shape[qtoi(qdiv(qint(i), stepsize))]), dcscale));
+      ctrl[idx] = ctrltmp;
+    }
+    // Pulse down
+    for (uint16_t i = 0; i < shapesamples; i++) {
+      data[idx++] = dcofs + qtoi(qmul(qint(shape[qtoi(qdiv(qint(shapesamples - i - 1), stepsize))]), dcscale));
+      // Could replace this with a separate loop to poke it into place
+      // Similarly for TR switch when implemented
+      if (i == 0 && c == 0)
+	ctrl[idx] = ctrltmp | SENSE;
+      else
+	ctrl[idx] = ctrltmp;
+    }
+
+    // Code gap
+    if (c < ncode - 1)
+      for (uint16_t i = 0; i < codegap; i++) {
+	data[idx++] = dcofs;
+	ctrl[idx] = ctrltmp;
+      }
+  }
+
+  // Down slew
+  for (uint16_t i = 0; i < slew2; i++) {
+    data[idx++] = qtoi(qdiv(qmul(qint(dcofs), qint(slew2 - i)), qint(slew2)));
+    ctrl[idx] |= PACTIVE;
+  }
+  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
+  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
+  // shift XXXX XXXX XXXX XXXX XXXX FFFF FFFF FFFF (accum 0 via cross input)
+  // to 0000 XXXX XXXX XXXX XXXX FFFF FFFF FFFF
+  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) {
-    int i;
-    const uint LED_PIN = PICO_DEFAULT_LED_PIN;
+    absolute_time_t then, now;
+
+    // Set sysclk to 120MHz
+    set_sys_clock_khz(120000, true);
 
     stdio_init_all();
+    printf("\n\n\nIniting\n");
 
-    gpio_init(LED_PIN);
-    gpio_set_dir(LED_PIN, GPIO_OUT);
+    // Needed otherwise timer related functions hang under debugging
+    // https://github.com/raspberrypi/pico-sdk/issues/1152#issuecomment-1418248639
+    timer_hw->dbgpause = 0;
+
+    gpio_init(PICO_DEFAULT_LED_PIN);
+    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
+
+    interp_config cfg = interp_default_config();
+    interp_config_set_blend(&cfg, true);
+    interp_set_config(interp0, 0, &cfg);
+
+    cfg = interp_default_config();
+    interp_set_config(interp0, 1, &cfg);
+
+    interp_test(pulse_data, shaped_trap);
+
+    uint32_t idx;
+    uint16_t plen = 4000;
+    char *code = "1110010";
+    //char *code = "10";
+    uint8_t codegap = 4;
+    uint8_t slew1 = 10;
+    uint8_t slew2 = 10;
+    uint8_t dcofs = 110;
+    memset(pulse_data, 0, sizeof(pulse_data));
+    memset(pulse_ctrl, 0, sizeof(pulse_ctrl));
+    then = get_absolute_time();
+    if ((idx = compute_pulse(pulse_data, pulse_ctrl, sizeof(pulse_data),
+			     plen, code, strlen(code),
+			     shaped_trap, sizeof(shaped_trap),
+			     codegap, slew1, slew2, dcofs)) == 0) {
+      printf("Failed to compute pulse\n");
+      while (1)
+	;
+    }
+    now = get_absolute_time();
+    printf("Pulse computation took %lld usec and created %lu samples\n", absolute_time_diff_us(then, now), idx);
+
+    pwm_config c = pwm_get_default_config();
+    // 120MHz / 250 = 480kHz base
+    // Maximum divisor is only 256 which limits the low end,
+    // could further subdivide in the IRQ handler
+    pwm_config_set_clkdiv_int(&c, 250);
+    // 8Hz
+    pwm_config_set_wrap(&c, 60000 - 1);
+    pwm_init(slice_num, &c, true);
+    pwm_clear_irq(slice_num);
+    pwm_set_irq_enabled(slice_num, true);
+    irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_wrap);
+    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.
@@ -65,15 +278,17 @@
     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
-    dac_program_init(pio, sm, offset, 14);
+    // 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 (32 bits) repeatedly to PIO0
+    // 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_ring(&dmac, false, SHAPED_TRAP_DAT_BITS);
     channel_config_set_dreq(&dmac, DREQ_PIO0_TX0);
 
     dma_channel_configure(
@@ -81,7 +296,7 @@
         &dmac,
         &pio0_hw->txf[0], // Write address (only need to set this once)
         NULL,             // Don't provide a read address yet
-        100, 		  // Write this many times then interrupt
+        idx >> 2, 	  // Transfer count
         false             // Don't start yet
     );
 
@@ -92,21 +307,15 @@
     irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
     irq_set_enabled(DMA_IRQ_0, true);
 
-    // Manually call the handler once, to trigger the first transfer
-    dma_handler();
-
     // 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) {
-#if 0
-	for (int i = 0; i < sizeof(shaped_trap_dat_start) / 4; i++) {
-	    pio_sm_put_blocking(pio, sm, ((uint32_t *)shaped_trap_dat_start)[i]);
-	}
-#endif
-#if 0
-	gpio_put(LED_PIN, 1);
+      dma_channel_wait_for_finish_blocking(dma_chan);
+	gpio_put(PICO_DEFAULT_LED_PIN, 1);
+	sleep_ms(100);
+	gpio_put(PICO_DEFAULT_LED_PIN, 0);
 	sleep_ms(100);
-	gpio_put(LED_PIN, 0);
-#endif
     }
+
+    __breakpoint();
 }
--- a/pico_sdk_import.cmake	Tue Feb 04 14:20:07 2025 +1030
+++ b/pico_sdk_import.cmake	Sat Feb 15 22:57:18 2025 +1030
@@ -29,11 +29,22 @@
         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 ()
-        FetchContent_Declare(
-                pico_sdk
-                GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
-                GIT_TAG master
-        )
+        # 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 master
+                    GIT_SUBMODULES_RECURSE FALSE
+            )
+        else ()
+            FetchContent_Declare(
+                    pico_sdk
+                    GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
+                    GIT_TAG master
+            )
+        endif ()
+
         if (NOT pico_sdk)
             message("Downloading Raspberry Pi Pico SDK")
             FetchContent_Populate(pico_sdk)
--- a/test.c	Tue Feb 04 14:20:07 2025 +1030
+++ b/test.c	Sat Feb 15 22:57:18 2025 +1030
@@ -4,30 +4,28 @@
 #include <stdlib.h>
 #include <string.h>
 
+#if 1
 #include <q.h>
+#else
+// We want Q24.8 - Q16.16 is too small for sample calculations
+#define FPT_WBITS 24
+#include "fptc-lib/src/fptc.h"
+#define q_t fpt
+#define qmul fpt_mul
+#define qdiv fpt_div
+#define qtoi fpt2i
+#define qint i2fpt
+#define qsub(x, y) ((x) - (y))
+#define qsprint(n, s, len) fpt_str(n, s, 3)
+#endif
 
 #include "shaped-trap.h"
 
-int
-main(int argc, char **argv) {
-  uint8_t data[50000], shapelen, codegap, slew1, slew2, dcofs;
-  uint16_t plen, ncode;
+unsigned
+compute_pulse(uint8_t *data, unsigned datalen, uint16_t plen, char *code, uint8_t ncode, uint8_t shapelen, const uint8_t *shape, uint8_t codegap, uint8_t slew1, uint8_t slew2, uint8_t dcofs) {
   uint32_t shapesamples, nsamples, idx;
   q_t dcscale, stepsize;
-  char *code, tmps[20];
-
-  if (argc != 7) {
-    printf("Bad usage\n");
-    exit(1);
-  }
-  plen = atoi(argv[1]);
-  code = argv[2];
-  ncode = strlen(code);
-  shapelen = sizeof(shaped_trap);
-  codegap = atoi(argv[3]);
-  slew1 = atoi(argv[4]);
-  slew2 = atoi(argv[5]);
-  dcofs = atoi(argv[6]);
+  char tmps[20];
 
   dcscale = qdiv(qsub(qint(255), qint(dcofs)), qint(255));
   qsprint(dcscale, tmps, sizeof(tmps));
@@ -35,8 +33,8 @@
 
   if (ncode == 1) {
     // Number of samples for half of the pulse
-    // Can't use q library here because it is Q16.16 so not large enough
-    shapesamples = (plen * shapelen) / 100;
+    // Do division first so we don't overflow Q16.16
+    shapesamples = qtoi(qmul(qdiv(qint(plen), qint(100)), qint(shapelen)));
     // Number of samples for everything
     nsamples = shapesamples * 2 + slew1 + slew2;
   } else {
@@ -44,21 +42,23 @@
     nsamples = shapesamples * 2 * ncode + codegap * (ncode - 1) + slew1 + slew2;
   }
 
-  if (nsamples > sizeof(data)) {
-    printf("Pulse too long (%d > %lu)\n", nsamples, sizeof(data));
-    exit(1);
-  }
-
   // Number of samples per step in the pulse shape
   stepsize = qdiv(qint(shapesamples), qint(shapelen));
   qsprint(stepsize, tmps, sizeof(tmps));
   printf("shapelen = %d shapesamples = %u nsamples = %u stepsize = %s\n", shapelen, shapesamples, nsamples, tmps);
 
+  if (nsamples > datalen) {
+    printf("Pulse too long (%d > %u)\n", nsamples, datalen);
+    return 0;
+  }
   if (shapesamples < 2) {
     printf("Pulse too short (%u < %d)\n", shapesamples, 2);
-    exit(1);
+    return 0;
   }
-
+  if (qtoi(shapesamples) > 65535) {
+    printf("Shape too long (%u > %d)\n", qtoi(shapesamples), 65535);
+    return 0;
+  }
   idx = 0;
 
   // Up slew
@@ -68,11 +68,11 @@
   for (uint16_t c = 0; c < ncode; c++) {
     // Pulse up
     for (uint16_t i = 0; i < shapesamples; i++)
-      data[idx++] = dcofs + qtoi(qmul(qint(shaped_trap[qtoi(qdiv(qint(i), stepsize))]), dcscale));
+      data[idx++] = dcofs + qtoi(qmul(qint(shape[qtoi(qdiv(qint(i), stepsize))]), dcscale));
 
     // Pulse down
     for (uint16_t i = 0; i < shapesamples; i++)
-      data[idx++] = dcofs + qtoi(qmul(qint(shaped_trap[qtoi(qdiv(qint(shapesamples - i - 1), stepsize))]), dcscale));
+      data[idx++] = dcofs + qtoi(qmul(qint(shape[qtoi(qdiv(qint(shapesamples - i - 1), stepsize))]), dcscale));
 
     // Code gap
     if (c < ncode - 1)
@@ -84,8 +84,33 @@
   for (uint16_t i = 0; i < slew2; i++)
     data[idx++] = qtoi(qdiv(qmul(qint(dcofs), qint(slew2 - i)), qint(slew2)));
 
+  return idx - 1;
+}
+
+int
+main(int argc, char **argv) {
+  uint8_t data[65536], codegap, slew1, slew2, dcofs;
+  uint16_t plen;
+  uint32_t idx;
+  char *code;
+
+  if (argc != 7) {
+    printf("Bad usage\n");
+    exit(1);
+  }
+  plen = atoi(argv[1]);
+  code = argv[2];
+  codegap = atoi(argv[3]);
+  slew1 = atoi(argv[4]);
+  slew2 = atoi(argv[5]);
+  dcofs = atoi(argv[6]);
+
+  if ((idx = compute_pulse(data, sizeof(data), plen, code, strlen(code), sizeof(shaped_trap), shaped_trap, codegap, slew1, slew2, dcofs)) == 0) {
+    printf("Failed to build pulse\n");
+    exit(1);
+  }
+
   FILE *fh;
-
   if ((fh = fopen("/tmp/out.bin", "w")) == NULL) {
     printf("Unable to open file: %s\n", strerror(errno));
     exit(1);