Mercurial > ~darius > hgwebdir.cgi > modulator
comparison modulator.c @ 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 |
comparison
equal
deleted
inserted
replaced
8:0249d0cecac4 | 9:3acdebd7eec7 |
---|---|
35 #include "hardware/structs/pll.h" | 35 #include "hardware/structs/pll.h" |
36 #include "hardware/structs/clocks.h" | 36 #include "hardware/structs/clocks.h" |
37 #pragma GCC diagnostic pop | 37 #pragma GCC diagnostic pop |
38 | 38 |
39 #include "dac.pio.h" | 39 #include "dac.pio.h" |
40 #include "trigger.pio.h" | |
40 | 41 |
41 // https://github.com/howerj/q | 42 // https://github.com/howerj/q |
42 // Modified to be Q20.12 rather than Q16.16 | 43 // Modified to be Q20.12 rather than Q16.16 |
43 #include "q/q.h" | 44 #include "q/q.h" |
44 | 45 |
45 #include "shaped-trap.h" | 46 #include "shaped-trap.h" |
46 | 47 |
48 // Pulse control bits | |
49 #define SENSE 0x01 | |
50 #define GATE 0x02 | |
51 #define PHINV 0x04 | |
52 #define PACTIVE 0x08 | |
53 | |
47 static int dma_chan; | 54 static int dma_chan; |
48 uint8_t pulse_data[65536]; | 55 uint8_t pulse_data[65536]; |
49 uint8_t pulse_ctrl[65536]; | 56 uint8_t pulse_ctrl[65536]; |
50 | 57 unsigned slice_num = 0; |
58 PIO pulse_pio; | |
59 uint pulse_sm; | |
60 | |
61 /* | |
62 * Use a DMA channel to feed PIO0 SM0 with pulse data. | |
63 * Each DMA transfer is a single pulse. | |
64 * | |
65 * The PIO state machine waits to be triggered before starting | |
66 * so we can use another state machine to look for the trigger edge. | |
67 * | |
68 * When the DMA is done the IRQ handler will configure it for the next | |
69 * pulse (or not if it should stop). ie reset the PIO state machine | |
70 * back to waiting for an edge and re-arm the DMA. | |
71 */ | |
51 void | 72 void |
52 dma_handler(void) { | 73 dma_handler(void) { |
53 // Clear the interrupt request. | 74 // Clear the interrupt request. |
54 dma_hw->ints0 = 1u << dma_chan; | 75 dma_hw->ints0 = 1u << dma_chan; |
55 // Give the channel a new wave table entry to read from, and re-trigger it | 76 // Give the channel a new wave table entry to read from, and re-trigger it |
56 //dma_channel_set_read_addr(dma_chan, shaped_trap_dat_start, true); | 77 //dma_channel_set_read_addr(dma_chan, shaped_trap_dat_start, true); |
78 // Reset pulse state machine back to waiting for trigger | |
79 //pio_sm_exec(pulse_pio, pulse_sm, pio_encode_jump(0); | |
57 } | 80 } |
58 | 81 |
59 unsigned slice_num = 0; | |
60 | 82 |
61 void | 83 void |
62 pwm_wrap(void) { | 84 pwm_wrap(void) { |
63 pwm_clear_irq(slice_num); | 85 pwm_clear_irq(slice_num); |
64 #if 0 | 86 #if 0 |
68 state = !state; | 90 state = !state; |
69 #endif | 91 #endif |
70 | 92 |
71 // Trigger another pulse read out | 93 // Trigger another pulse read out |
72 dma_channel_set_read_addr(dma_chan, pulse_data, true); | 94 dma_channel_set_read_addr(dma_chan, pulse_data, true); |
95 gpio_put(2, 1); | |
96 gpio_put(2, 0); | |
73 } | 97 } |
74 | 98 |
75 // Calculate pulse shape data | 99 // Calculate pulse shape data |
76 // TODO: predistortion, proper sense, gate, phase, active, T/R switch | 100 // TODO: predistortion, proper sense, gate, phase, active, T/R switch |
77 // Could encode them as bit stream like data but more compact would be | 101 // Could encode them as bit stream like data but more compact would be |
78 // (say) a list of counts to toggle pins at | 102 // (say) a list of counts to toggle pins at |
79 #define SENSE 0x01 | |
80 #define GATE 0x02 | |
81 #define PHINV 0x04 | |
82 #define PACTIVE 0x08 | |
83 | |
84 // Need to add pre/postgate/sense/phase counters | 103 // Need to add pre/postgate/sense/phase counters |
85 unsigned | 104 unsigned |
86 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) { | 105 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) { |
87 uint32_t shapesamples, nsamples, idx, bit1startup, bit1stopup; | 106 uint32_t shapesamples, nsamples, idx, bit1startup, bit1stopup; |
88 q_t dcscale, stepsize; | 107 q_t dcscale, stepsize; |
197 if (c == 0) | 216 if (c == 0) |
198 bit1stopup = idx - 1; | 217 bit1stopup = idx - 1; |
199 // Pulse down | 218 // Pulse down |
200 // Since the pulse is symmetrical just copy the up slope in reverse | 219 // Since the pulse is symmetrical just copy the up slope in reverse |
201 // XXX: if we had asymmetrical predistortion this wouldn't be true | 220 // XXX: if we had asymmetrical predistortion this wouldn't be true |
202 // In that case probably best to do a single pulse up/down, then | |
203 // add predistortion and copy it to other bits | |
204 for (uint16_t i = 0; i < shapesamples; i++) { | 221 for (uint16_t i = 0; i < shapesamples; i++) { |
205 data[idx++] = data[bit1stopup - i]; | 222 data[idx++] = data[bit1stopup - i]; |
206 // Could replace this with a separate loop to poke it into place | 223 // Could replace this with a separate loop to poke it into place |
207 // Similarly for TR switch when implemented | 224 // Similarly for TR switch when implemented |
208 if (i == 0 && c == 0) | 225 if (i == 0 && c == 0) |
225 ctrl[idx] |= PACTIVE; | 242 ctrl[idx] |= PACTIVE; |
226 } | 243 } |
227 return idx - 1; | 244 return idx - 1; |
228 } | 245 } |
229 | 246 |
230 // As per hello_interp.c | |
231 void interp_test(uint8_t *data, const uint8_t *shape) { | |
232 interp_config cfg; | |
233 const int uv_fractional_bits = 12; | |
234 | |
235 // Step size is 0.25 (ie stretching by 4x) | |
236 unsigned step = (1 << uv_fractional_bits) / 4; | |
237 | |
238 puts("interp_test\n"); | |
239 // Setup lane 0 to generate index into shape table | |
240 // Mask start is 0 because we use 8 bit samples | |
241 cfg = interp_default_config(); | |
242 interp_config_set_shift(&cfg, uv_fractional_bits); | |
243 interp_config_set_mask(&cfg, 0, 32 - uv_fractional_bits); | |
244 interp_config_set_blend(&cfg, true); | |
245 interp_set_config(interp0, 0, &cfg); | |
246 | |
247 // Setup lane 1 to LERP each sample pair | |
248 cfg = interp_default_config(); | |
249 interp_config_set_shift(&cfg, uv_fractional_bits - 8); | |
250 interp_config_set_signed(&cfg, false); | |
251 interp_config_set_cross_input(&cfg, true); // unsigned blending | |
252 interp_set_config(interp0, 1, &cfg); | |
253 | |
254 interp0->accum[0] = 0; // Initial offset into shape table | |
255 interp0->base[2] = (uintptr_t)shape; // Start of shape table | |
256 for (unsigned i = 0; i < 122 * 4; i++) { | |
257 // Get sample pair | |
258 uint8_t *sample_pair = (uint8_t *) interp0->peek[2]; | |
259 // Ask lane 1 for a LERP, using the lane 0 accumulator | |
260 interp0->base[0] = sample_pair[0]; | |
261 interp0->base[1] = sample_pair[1]; | |
262 uint32_t peek1 = interp0->peek[1]; | |
263 uint32_t add_raw1 = interp0->add_raw[1]; | |
264 data[i] = peek1; | |
265 printf("%d\t(%lu%% between %d and %d) %lu\n", | |
266 (int)peek1, | |
267 100 * (add_raw1 & 0xff) / 0xff, | |
268 sample_pair[0], sample_pair[1], | |
269 interp_get_accumulator(interp0, 0)); | |
270 interp0->add_raw[0] = step; | |
271 } | |
272 puts("done"); | |
273 } | |
274 | |
275 int | 247 int |
276 main(void) { | 248 main(void) { |
277 absolute_time_t then, now; | 249 absolute_time_t then, now; |
278 | 250 |
279 // Set sysclk to 120MHz | 251 // Set sysclk to 120MHz |
286 // https://github.com/raspberrypi/pico-sdk/issues/1152#issuecomment-1418248639 | 258 // https://github.com/raspberrypi/pico-sdk/issues/1152#issuecomment-1418248639 |
287 timer_hw->dbgpause = 0; | 259 timer_hw->dbgpause = 0; |
288 | 260 |
289 gpio_init(PICO_DEFAULT_LED_PIN); | 261 gpio_init(PICO_DEFAULT_LED_PIN); |
290 gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); | 262 gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); |
291 | 263 gpio_init(2); |
292 //interp_test(pulse_data, shaped_trap); | 264 gpio_set_dir(2, GPIO_OUT); |
265 #if 0 | |
266 for (unsigned i = 7; i < 7 + 9; i++) { | |
267 printf("GPIO %d\n", i); | |
268 gpio_init(i); | |
269 gpio_set_dir(i, GPIO_OUT); | |
270 printf("on\n"); | |
271 gpio_put(i, 1); | |
272 __breakpoint(); | |
273 printf("off\n"); | |
274 gpio_put(i, 0); | |
275 __breakpoint(); | |
276 } | |
277 #endif | |
293 | 278 |
294 uint32_t idx; | 279 uint32_t idx; |
295 uint16_t plen; | 280 uint16_t plen; |
296 char *code; | 281 char *code; |
297 if (0) { | 282 if (1) { |
298 plen = 8000; | 283 plen = 8000; |
299 code = "1110010"; | 284 code = "1110010"; |
300 } else { | 285 } else { |
301 plen = 53000; | 286 plen = 53000; |
302 code = "1"; | 287 code = "1"; |
318 now = get_absolute_time(); | 303 now = get_absolute_time(); |
319 unsigned long long diff = absolute_time_diff_us(then, now); | 304 unsigned long long diff = absolute_time_diff_us(then, now); |
320 printf("Pulse computation took %lld usec and created %lu samples - %.1f nsec/sample\n", | 305 printf("Pulse computation took %lld usec and created %lu samples - %.1f nsec/sample\n", |
321 diff, idx, (float)diff * 1000.0 / idx); | 306 diff, idx, (float)diff * 1000.0 / idx); |
322 __breakpoint(); | 307 __breakpoint(); |
323 pwm_config c = pwm_get_default_config(); | 308 |
309 // Load the clocked_input program, and configure a free state machine | |
310 // to run the program. | |
311 PIO pulse_pio = pio0; | |
312 uint offset = pio_add_program(pulse_pio, &dac_program); | |
313 uint pulse_sm = pio_claim_unused_sm(pulse_pio, true); | |
314 // Data is GPIO7 to GPIO14, clock is GPIO15 | |
315 // Clock divisor of 2 so it runs at 60MHz and | |
316 // generates a 30MHz clock | |
317 dac_program_init(pulse_pio, pulse_sm, offset, 7, 2); | |
318 | |
319 // Configure a channel to write 32 bits at a time to PIO0 | |
320 // SM0's TX FIFO, paced by the data request signal from that peripheral. | |
321 dma_chan = dma_claim_unused_channel(true); | |
322 dma_channel_config dmac = dma_channel_get_default_config(dma_chan); | |
323 channel_config_set_transfer_data_size(&dmac, DMA_SIZE_32); | |
324 channel_config_set_read_increment(&dmac, true); | |
325 channel_config_set_dreq(&dmac, DREQ_PIO0_TX0); | |
326 | |
327 dma_channel_configure( | |
328 dma_chan, | |
329 &dmac, | |
330 &pio0_hw->txf[0], // Write address (only need to set this once) | |
331 NULL, // Don't provide a read address yet | |
332 (idx + 1) >> 2, // Transfer count (round up to 4 bytes) | |
333 false // Don't start yet | |
334 ); | |
335 | |
336 // Tell the DMA to raise IRQ line 0 when the channel finishes a block | |
337 dma_channel_set_irq0_enabled(dma_chan, true); | |
338 | |
339 // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted | |
340 irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); | |
341 irq_set_enabled(DMA_IRQ_0, true); | |
342 | |
324 // 120MHz / 250 = 480kHz base | 343 // 120MHz / 250 = 480kHz base |
325 // Maximum divisor is only 256 which limits the low end, | 344 // Maximum divisor is only 256 which limits the low end, |
326 // could further subdivide in the IRQ handler | 345 // could further subdivide in the IRQ handler |
346 pwm_config c = pwm_get_default_config(); | |
327 pwm_config_set_clkdiv_int(&c, 250); | 347 pwm_config_set_clkdiv_int(&c, 250); |
328 // 8Hz | 348 // 8Hz |
329 pwm_config_set_wrap(&c, 60000 - 1); | 349 pwm_config_set_wrap(&c, 60000 - 1); |
330 pwm_init(slice_num, &c, true); | 350 pwm_init(slice_num, &c, true); |
331 pwm_clear_irq(slice_num); | 351 pwm_clear_irq(slice_num); |
332 pwm_set_irq_enabled(slice_num, true); | 352 pwm_set_irq_enabled(slice_num, true); |
333 irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_wrap); | 353 irq_set_exclusive_handler(PWM_IRQ_WRAP, pwm_wrap); |
334 irq_set_enabled(PWM_IRQ_WRAP, true); | 354 irq_set_enabled(PWM_IRQ_WRAP, true); |
335 pwm_init(slice_num, &c, true); | 355 pwm_init(slice_num, &c, true); |
336 | 356 |
337 // Load the clocked_input program, and configure a free state machine | |
338 // to run the program. | |
339 PIO pio = pio0; | |
340 uint offset = pio_add_program(pio, &dac_program); | |
341 uint sm = pio_claim_unused_sm(pio, true); | |
342 // XXX: I would prefer starting at GPIO16 but in that case the top 2 | |
343 // bits don't seem to work | |
344 // Clock divisor of 2 so it runs at 60MHz and | |
345 // generates a 30MHz clock | |
346 dac_program_init(pio, sm, offset, 14, 2); | |
347 | |
348 // Configure a channel to write the same word (8 bits) repeatedly to PIO0 | |
349 // SM0's TX FIFO, paced by the data request signal from that peripheral. | |
350 dma_chan = dma_claim_unused_channel(true); | |
351 dma_channel_config dmac = dma_channel_get_default_config(dma_chan); | |
352 // XXX: need to get pulse generator to pad to 32 bits | |
353 channel_config_set_transfer_data_size(&dmac, DMA_SIZE_32); | |
354 channel_config_set_read_increment(&dmac, true); | |
355 channel_config_set_dreq(&dmac, DREQ_PIO0_TX0); | |
356 | |
357 dma_channel_configure( | |
358 dma_chan, | |
359 &dmac, | |
360 &pio0_hw->txf[0], // Write address (only need to set this once) | |
361 NULL, // Don't provide a read address yet | |
362 idx >> 2, // Transfer count | |
363 false // Don't start yet | |
364 ); | |
365 | |
366 // Tell the DMA to raise IRQ line 0 when the channel finishes a block | |
367 dma_channel_set_irq0_enabled(dma_chan, true); | |
368 | |
369 // Configure the processor to run dma_handler() when DMA IRQ 0 is asserted | |
370 irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); | |
371 irq_set_enabled(DMA_IRQ_0, true); | |
372 | |
373 // Everything else from this point is interrupt-driven. The processor has | 357 // Everything else from this point is interrupt-driven. The processor has |
374 // time to sit and think about its early retirement -- maybe open a bakery? | 358 // time to sit and think about its early retirement -- maybe open a bakery? |
375 while (true) { | 359 while (true) { |
376 dma_channel_wait_for_finish_blocking(dma_chan); | 360 dma_channel_wait_for_finish_blocking(dma_chan); |
377 gpio_put(PICO_DEFAULT_LED_PIN, 1); | 361 gpio_put(PICO_DEFAULT_LED_PIN, 1); |