view modulator.c @ 24:c7845db23ab2

Add sideset for trigger output. Trigger both SMs
author Daniel O'Connor <darius@dons.net.au>
date Tue, 25 Feb 2025 16:47:00 +1030
parents 3c713073dd0c
children 6070d2e66b4c
line wrap: on
line source

/******************************************************************
*******************************************************************
**
** This is proprietary unpublished source code, property
** of Genesis Software.  Use or disclosure without prior
** agreement is expressly prohibited.
**
** Copyright (c) 2025 Genesis Software, all rights reserved.
**
*******************************************************************
 ******************************************************************/

/*
** MODULATOR.C
**
** Create modulation shape
**
*/
#define WITH_TRIGGER

#include <stdio.h>
#include <string.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"
#include "ctrl.pio.h"
#include "trigger.pio.h"

// https://github.com/howerj/q
// Modified to be Q20.12 rather than Q16.16
#include "q/q.h"

#include "shaped-trap.h"

// Base of DAC pins
#define DACOUT_GPIO	7
// Base of ctrl pins
#define CTRLOUT_GPIO	16
// PWM output pin
#define TRIGOUT_GPIO	22
// PIO SM trigger input pin (connected to above for testing)
// Also outputs trigger on next pin
#define TRIGIN_GPIO	27

// Pulse control bits
#define PACTIVE		0x01
#define PHINV		0x02
#define SENSE1		0x04
#define SENSE2		0x08
#define GATE		0x10
#define TRSW		0x20

// Pulse shape data
uint8_t pulse_data[65536] __attribute__((aligned(4)));
// Pulse control data
uint8_t pulse_ctrl[65536] __attribute__((aligned(4)));
// PWM slice for PRF timer
unsigned slice_num = 0;

// PIO for pulse generation
PIO pulse_pio = pio0;

// DMA channel to feed DAC PIO
static int dac_dma_chan;
// DAC SM
uint dac_sm;
// Instruction offset for DAC PIO program
uint dac_pio_sm_offset;

// DMA channel to feed ctrl PIO
static int ctrl_dma_chan;
// Ctrl SM
uint ctrl_sm;
// Instruction offset for ctrl PIO program
uint ctrl_pio_sm_offset;

/*
 * 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 << dac_dma_chan;

#if 0
  printf("DAC transfers %lu\n", dma_channel_hw_addr(dac_dma_chan)->transfer_count);
  printf("Ctrl transfers %lu\n", dma_channel_hw_addr(ctrl_dma_chan)->transfer_count);
#endif

  // Reset DAQ & ctrl PIO SMs so they are  waiting for a trigger
  pio_sm_exec(pulse_pio, dac_sm, pio_encode_jmp(dac_pio_sm_offset));
  pio_sm_exec(pulse_pio, ctrl_sm, pio_encode_jmp(ctrl_pio_sm_offset));

  // Setup next pulse data & ctrl DMA addresses
  dma_channel_wait_for_finish_blocking(dac_dma_chan);
  dma_channel_set_read_addr(dac_dma_chan, pulse_data, true);
  dma_channel_wait_for_finish_blocking(ctrl_dma_chan);
  dma_channel_set_read_addr(ctrl_dma_chan, pulse_ctrl, true);
}

void
pwm_wrap(void) {
  pwm_clear_irq(slice_num);

#ifndef WITH_TRIGGER
  // Manually trigger DAQ SM (cleared by SM)
  pulse_pio->irq_force = 3;

  // 'scope trigger
  gpio_put(2, 1);
  gpio_put(2, 0);
#endif
}

// 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
// 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, bit1startup, bit1stopup;
  q_t dcscale, stepsize;
  char tmps[20];
  interp_config cfg;

  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
    // XXX: Need the +1 otherwise slew2 is truncated
    nsamples = shapesamples * 2 + slew1 + slew2 + 1;
  } else {
    shapesamples = plen / 2;
    nsamples = shapesamples * 2 * ncode + codegap * (ncode - 1) + slew1 + slew2 + 1;
  }

  // Number of steps per samples in the pulse shape
  stepsize = qdiv(qint(shapelen), qint(shapesamples));
  qsprint(stepsize, tmps, sizeof(tmps));
  printf("shapelen = %d shapesamples = %lu nsamples = %lu stepsize = %s\n", shapelen, shapesamples, nsamples, tmps);

  // Check the requested pulse will not overflow given data
  if (nsamples > datalen) {
    printf("Pulse too long (%ld > %u)\n", nsamples, datalen);
    return 0;
  }
  // Check it is not too short
  if (shapesamples < 2) {
    printf("Pulse too short (%lu < %d)\n", shapesamples, 2);
    return 0;
  }
  // Or too long (will overflow for loop variable)
  if (qtoi(shapesamples) > 65535) {
    printf("Shape too long (%u > %d)\n", qtoi(shapesamples), 65535);
    return 0;
  }

  // Setup interp 0 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, QBITS);
  interp_config_set_mask(&cfg, 0, 32 - QBITS);
  interp_config_set_blend(&cfg, true);
  interp_set_config(interp0, 0, &cfg);

  // Setup interp 0 lane 1 to LERP each sample pair
  cfg = interp_default_config();
  interp_config_set_shift(&cfg, QBITS - 8);
  interp_config_set_signed(&cfg, false);
  interp_config_set_cross_input(&cfg, true); // unsigned blending
  interp_set_config(interp0, 1, &cfg);

  // Setup interp 1 lane 0 to clamp 0-255
  cfg = interp_default_config();
  interp_config_set_clamp(&cfg, true);
  interp_config_set_shift(&cfg, 0);
  interp_config_set_mask(&cfg, 0, 8);
  interp_config_set_signed(&cfg, false);
  interp_set_config(interp1, 0, &cfg);
  interp1->base[0] = 0;
  interp1->base[1] = 255;

  interp0->accum[0] = 0; // Initial offset into shape table
  interp0->base[2] = (uintptr_t)shape; // Start of shape table

  dcscale = qdiv(qsub(qint(256), qint(dcofs)), qint(255));
  qsprint(dcscale, tmps, sizeof(tmps));
  printf("dcscale = %s\n", tmps);

  memset(pulse_data, 0, datalen);
  memset(pulse_ctrl, 0, datalen);
  idx = 0;

  // Up slew
  for (uint16_t i = 0; i < slew1; i++) {
    data[idx++] = qtoi(qdiv(qmul(qint(dcofs), qint(i)), qint(slew1)));
  }
  for (uint16_t c = 0; c < ncode; c++) {
    if (c == 0)
      bit1startup = idx;

    uint ctrltmp = PACTIVE;
    if (code[c] == '0')
      ctrltmp |= PHINV;

    // Pulse up
    if (c == 0) {
      interp0->accum[0] = 0; // Initial offset into shape table
      interp0->base[2] = (uintptr_t)shape; // Start of shape table
    }
    for (uint16_t i = 0; i < shapesamples; i++) {
      ctrl[idx] = ctrltmp;
      if (c == 0) {
	// 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];
	uint8_t peek = interp0->peek[1];
	// Apply DC offset scaling & clamp
	interp1->accum[0] = dcofs + qtoi(qmul(qint(peek), dcscale));
	data[idx++] = interp1->peek[0];
	// Update interpolator for next point
	interp0->add_raw[0] = stepsize;
      } else
	// Already done it before, just copy the previous instance
	data[idx++] = data[bit1startup + i];
    }
    if (c == 0)
      bit1stopup = idx - 1;
    // 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
    for (uint16_t i = 0; i < shapesamples; i++) {
      // 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 | SENSE1;
      else
	ctrl[idx] = ctrltmp;
      data[idx++] = data[bit1stopup - i];
    }

    // Code gap
    if (c < ncode - 1)
      for (uint16_t i = 0; i < codegap; i++) {
	ctrl[idx] = ctrltmp;
	data[idx++] = dcofs;
      }
  }

  // Down slew
  for (uint16_t i = 0; i < slew2 + 1; i++) {
    data[idx++] = qtoi(qdiv(qmul(qint(dcofs), qint(slew2 - i)), qint(slew2)));
  }

  data[idx++] = 0;
  ctrl[idx] = 0;

  return idx + 1;
}

int
main(void) {
    absolute_time_t then, now;

    // Set sysclk to 120MHz
    set_sys_clock_khz(120000, true);

    stdio_init_all();
    printf("\n\n\nIniting\n");

    // 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);
    gpio_init(2);
    gpio_set_dir(2, GPIO_OUT);

#if 0
    // GPIO tester to check breadboard wiring
    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 (1) {
      plen = 8000;
      code = "1110010";
    } else {
      plen = 53000;
      code = "1";
    }

    uint8_t codegap = 4;
    uint8_t slew1 = 10;
    uint8_t slew2 = 10;
    uint8_t dcofs = 110;
    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();
    unsigned long long diff = absolute_time_diff_us(then, now);
    printf("Pulse computation took %lld usec and created %lu samples - %.1f nsec/sample\n",
	   diff, idx, (float)diff * 1000.0 / idx);
    unsigned transfers = (idx + 3) >> 2;
    printf("Using %u transfers\n", transfers);
    //__breakpoint();

    // Load the DAC program, and configure a free state machine
    // to run the program.
    dac_pio_sm_offset = pio_add_program(pulse_pio, &dac_program);
    if (dac_pio_sm_offset < 0) {
      printf("Unable to load DAC program\n");
      __breakpoint();
    }
    dac_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, dac_sm, dac_pio_sm_offset, DACOUT_GPIO, 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.
    dac_dma_chan = dma_claim_unused_channel(true);
    dma_channel_config dac_dmac = dma_channel_get_default_config(dac_dma_chan);
    channel_config_set_transfer_data_size(&dac_dmac, DMA_SIZE_32);
    channel_config_set_read_increment(&dac_dmac, true);
    channel_config_set_dreq(&dac_dmac, PIO_DREQ_NUM(pulse_pio, dac_sm, true));

    dma_channel_configure(
        dac_dma_chan,
        &dac_dmac,
        &pulse_pio->txf[dac_sm],   // Write address
        pulse_data,                // Pulse data
        transfers,	           // Transfer count
        true			   // Start transfer
    );

    // Tell the DMA to raise IRQ line 0 when the channel finishes a block
    dma_channel_set_irq0_enabled(dac_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);

    // Load the ctrl program, and configure a free state machine
    // to run the program.
    ctrl_pio_sm_offset = pio_add_program(pulse_pio, &ctrl_program);
    if (ctrl_pio_sm_offset < 0) {
      printf("Unable to load ctrl program\n");
      __breakpoint();
    }
    ctrl_sm = pio_claim_unused_sm(pulse_pio, true);
    ctrl_program_init(pulse_pio, ctrl_sm, ctrl_pio_sm_offset, CTRLOUT_GPIO, 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.
    ctrl_dma_chan = dma_claim_unused_channel(true);
    dma_channel_config ctrl_dmac = dma_channel_get_default_config(ctrl_dma_chan);
    channel_config_set_transfer_data_size(&ctrl_dmac, DMA_SIZE_32);
    channel_config_set_read_increment(&ctrl_dmac, true);
    channel_config_set_dreq(&ctrl_dmac, PIO_DREQ_NUM(pulse_pio, ctrl_sm, true));

    dma_channel_configure(
        ctrl_dma_chan,
        &ctrl_dmac,
        &pulse_pio->txf[ctrl_sm],  // Write address
        pulse_ctrl,                // Ctrl data
        transfers,	           // Transfer count
        true			   // Start transfer
    );
    // No IRQ, piggyback on the data one

#ifdef WITH_TRIGGER
    // Load the trigger program, and configure a free state machine
    // to run the program.
    uint trigger_pio_sm_offset = pio_add_program(pulse_pio, &trigger_program);
    if (trigger_pio_sm_offset < 0) {
      printf("Unable to load trigger program\n");
      __breakpoint();
    }
    uint trigger_sm = pio_claim_unused_sm(pulse_pio, true);
    trigger_program_init(pulse_pio, trigger_sm, trigger_pio_sm_offset, TRIGIN_GPIO, 2);
#endif

    //
    // Setup PWM
    // Used here to output a trigger which gets fed back into the trigger SM
    //
    // 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);

    gpio_set_function(TRIGOUT_GPIO, GPIO_FUNC_PWM);

    slice_num = pwm_gpio_to_slice_num(TRIGOUT_GPIO);
    pwm_init(slice_num, &c, true);
    pwm_clear_irq(slice_num);
    pwm_set_chan_level(slice_num, PWM_CHAN_A, 1);
    pwm_set_enabled(slice_num, 1);
    pwm_set_irq_enabled(slice_num, true);
    irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_wrap);
    irq_set_enabled(PWM_IRQ_WRAP, 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) {
	gpio_put(PICO_DEFAULT_LED_PIN, 1);
	sleep_ms(100);
	gpio_put(PICO_DEFAULT_LED_PIN, 0);
	sleep_ms(100);
    }

    __breakpoint();
}