uWFG-Pico/gen.c

191 wiersze
9.7 KiB
C

/*
* gen.c
*
* Created: Dec 2021
* Author: Arjan te Marvelde
*
* The generation of the output.
*
* The output is generated by simply taking an array of samples which is repeatedly output on a set of GPIO pins.
* To accomplish highest speed, the generator utilizes the Pico PIO feature, which enables outputting new data
* every system clock cycle.
* A PIO consists of common instruction memory, that may contain several PIO programs at different offsets. These
* programs can then be executed by each of 4 statemachines contained in the PIO. Each SM has its own I/O FIFOs to
* the system, its own GPIO interfacing and a set of internal registers to let the program do its work.
* The SM clock rate is derived from the system clock by means of a divider, [1.0 - 65536.0], in 1/256 steps.
*
* For the waveform generator, a simple PIO program is defined that moves a word from the SM TX fifo into the SM output
* shift register (OSR), and then clocks out one byte on the designated pins on every SM clock tick.
* A separate SM is allocated for each output channel, thus providing two independent outputs.
*
* Two DMA channels are used for each output, chained in a loop to keep the flow going.
* The dma_data channel transfers from *buffer to the PIO TX FIFO (paced by the PIO DREQ signal), chained to dma_ctrl channel.
* The dma_ctrl channel transfers the buffer address back into the dma_data channel read_addr, and chains back to dma_data.
From RP2040 datasheet, DMA Control / Status word layout:
0x80000000 [31] : AHB_ERROR (0): Logical OR of the READ_ERROR and WRITE_ERROR flags
0x40000000 [30] : READ_ERROR (0): If 1, the channel received a read bus error
0x20000000 [29] : WRITE_ERROR (0): If 1, the channel received a write bus error
0x01000000 [24] : BUSY (0): This flag goes high when the channel starts a new transfer sequence, and low when the...
0x00800000 [23] : SNIFF_EN (0): If 1, this channel's data transfers are visible to the sniff hardware, and each...
0x00400000 [22] : BSWAP (0): Apply byte-swap transformation to DMA data
0x00200000 [21] : IRQ_QUIET (0): In QUIET mode, the channel does not generate IRQs at the end of every transfer block
0x001f8000 [20:15] : TREQ_SEL (0): Select a Transfer Request signal
0x00007800 [14:11] : CHAIN_TO (0): When this channel completes, it will trigger the channel indicated by CHAIN_TO
0x00000400 [10] : RING_SEL (0): Select whether RING_SIZE applies to read or write addresses
0x000003c0 [9:6] : RING_SIZE (0): Size of address wrap region
0x00000020 [5] : INCR_WRITE (0): If 1, the write address increments with each transfer
0x00000010 [4] : INCR_READ (0): If 1, the read address increments with each transfer
0x0000000c [3:2] : DATA_SIZE (0): Set the size of each bus transfer (byte/halfword/word)
0x00000002 [1] : HIGH_PRIORITY (0): HIGH_PRIORITY gives a channel preferential treatment in issue scheduling: in...
0x00000001 [0] : EN (0): DMA Channel Enable
* DMA channel CTRL words, assuming DMA CH0..3 and PIO0 fifo TX0 and TX1:
* CH0: 0x0020081f (IRQ_QUIET=0x1, TREQ_SEL=0x00, CHAIN_TO=1, INCR_WRITE=0, INCR_READ=1, DATA_SIZE=2, HIGH_PRIORITY=1, EN=1)
* CH1: 0x003f800f (IRQ_QUIET=0x1, TREQ_SEL=0x3f, CHAIN_TO=0, INCR_WRITE=0, INCR_READ=0, DATA_SIZE=2, HIGH_PRIORITY=1, EN=1)
* CH2: 0x0020981f (IRQ_QUIET=0x1, TREQ_SEL=0x01, CHAIN_TO=3, INCR_WRITE=0, INCR_READ=1, DATA_SIZE=2, HIGH_PRIORITY=1, EN=1)
* CH3: 0x003f900f (IRQ_QUIET=0x1, TREQ_SEL=0x3f, CHAIN_TO=2, INCR_WRITE=0, INCR_READ=0, DATA_SIZE=2, HIGH_PRIORITY=1, EN=1)
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "pico/stdlib.h"
#include "pico/sem.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/pll.h"
#include "wfgout.pio.h"
#include "gen.h"
float _fsys; // System clock frequency
/*
* DMA pipe definitions, assume channels 0, 1, 2 and 3 for A-DATA, A-CTRL, B-DATA and B-CTRL
*/
#define DMA_DC(i) (((i)==0) ? 0x0020081f : 0x0020981f) // See derivation above
#define DMA_CC(i) (((i)==0) ? 0x003f800f : 0x003f900f)
/*
* Global variables that hold the active parameters for both output channels
*/
#define PINA 0 // PIO channe A and B start pin numbers
#define PINB 8
#define GEN_MINBUFLEN 20 // Minimum nr of samples (10msec - 160nsec)
#define GEN_MAXBUFLEN 2000 // Maximum buffer size (1sec - 16usec)
wfg_t wfg_ctrl[2]; // Active
// Allocate maximum size samplebuffers for channel A and B
uint8_t a_buf[GEN_MAXBUFLEN] __attribute__((aligned(4))); // DMA requires to align on 32 bit boundary
uint8_t b_buf[GEN_MAXBUFLEN] __attribute__((aligned(4)));
/*
* Unit initialization, !only call this once!
* This function initializes the WFG parameters, the PIO statemachines and teh DMA channels.
*/
void gen_init()
{
float div;
uint i, offset;
int ch;
/* Retrieve system clock frequency */
_fsys = 1.2e7; // Assume 12MHz XOSC
_fsys *= pll_sys_hw->fbdiv_int&0xfff; // Feedback divider
_fsys /= (pll_sys_hw->prim&0x00070000)>>16; // Primary divider 1
_fsys /= (pll_sys_hw->prim&0x00007000)>>12; // Primary divider 2
/* Set GPIO pin behaviour */
for (i=0; i<8; i++) // Initialize the channel A and B pins
{
gpio_set_function(PINA+i, GPIO_FUNC_PIO0); // Function: PIO
gpio_set_slew_rate(PINA+i, GPIO_SLEW_RATE_FAST); // No slewrate limiting
gpio_set_drive_strength(PINA+i, GPIO_DRIVE_STRENGTH_8MA); // Drive 8mA (might increase to 12)
gpio_set_function(PINB+i, GPIO_FUNC_PIO0);
gpio_set_slew_rate(PINB+i, GPIO_SLEW_RATE_FAST);
gpio_set_drive_strength(PINB+i, GPIO_DRIVE_STRENGTH_8MA);
}
/* Initialize the buffers and channel control structures */
for (i= 0; i<64; i++) {a_buf[i] = 0x00; a_buf[i+64] = 0xff;} // Square wave
wfg_ctrl[0].buf = a_buf; // in A sample buffer
wfg_ctrl[0].len = 128; // of 128 samples
wfg_ctrl[0].dur = 1.0e-6; // and 1 usec duration
for (i= 0; i<64; i++) {b_buf[i] = i*4; b_buf[i+64] = 0xff-(i*4);} // Triangle wave
wfg_ctrl[1].buf = b_buf; // in B sample buffer
wfg_ctrl[1].len = 128; // of 128 samples
wfg_ctrl[1].dur = 1.0e-6; // and 1usec duration
/* Initialize PIO and channel A and B statemachines */
offset = pio_add_program(pio0, &wfgout_program); // Move program to PIO space and obtain its offset
for (ch=0; ch<2; ch++)
{
div = _fsys * wfg_ctrl[ch].dur / wfg_ctrl[ch].len; // Ratio of fsys and channel B sampleclock
if (div < 1.0) div=1.0; // Cannot get higher than FSYS
wfgout_program_init(pio0, ch, offset, (uint)(ch*PINB), (uint)8, div); // Invoke PIO initializer for channel B
dma_hw->ch[2*ch].read_addr = (io_rw_32)wfg_ctrl[ch].buf; // Read from waveform buffer
dma_hw->ch[2*ch].write_addr = (io_rw_32)&pio0->txf[ch]; // Write to PIO TX fifo
dma_hw->ch[2*ch].transfer_count = wfg_ctrl[ch].len/4; // Nr of 32 bit words to transfer
dma_hw->ch[2*ch].al1_ctrl = DMA_DC(ch); // Write ctrl word without starting the DMA
dma_hw->ch[2*ch+1].read_addr = (io_rw_32)&(wfg_ctrl[ch].buf); // Read from waveform buffer address reference
dma_hw->ch[2*ch+1].write_addr = (io_rw_32)&dma_hw->ch[2*ch].read_addr; // Write to data channel read address
dma_hw->ch[2*ch+1].transfer_count = 1; // One word to transfer
dma_hw->ch[2*ch+1].ctrl_trig = DMA_CC(ch); // Write ctrl word and start DMA
}
}
/*
* This function is the main API of the generator on channel ch.
* Parameters are a waveform samples buffer, its length and a desired frequency.
* It is assumed that the buffer contains one wave, and the frequency will be maximized at fsys/buflen
*/
void gen_play(int ch, wfg_t *wave)
{
uint32_t clkdiv; // 31:16 int part, 15:8 frac part (in 1/256)
uint32_t len;
float div;
ch &= 1; // Truncate channel into range
len = (uint32_t)wave->len; len &= ~3; // Force multiple of 4
if (len<GEN_MINBUFLEN) return; // Insufficient samples
if (len>GEN_MAXBUFLEN) len = GEN_MAXBUFLEN; // Truncate to maximum
/* Calculate PIO clock divider */
div = _fsys * wave->dur / len; // Sample rate to fsys ratio
if (div < 1.0) div=1.0; // Sample rate too high: top off
clkdiv = (uint32_t)div; // Extract integer part
div = (div - clkdiv)*256; // Fraction x 256
clkdiv = (clkdiv << 8) + (uint32_t)div; // Add 8bit integer part of fraction
clkdiv = clkdiv << 8; // Final shift to match required format
/* Store waveform */
dma_channel_abort(2*ch); // Stop DMA transfers, to prevent collisions
dma_channel_abort(2*ch+1);
memcpy(wfg_ctrl[ch].buf, wave->buf, len); // Copy samples from input
wfg_ctrl[ch].len = len;
wfg_ctrl[ch].dur = wave->dur;
/* Re-program PIO */
pio0_hw->sm[ch].clkdiv = (io_rw_32)clkdiv; // Set new value
pio_sm_clkdiv_restart(pio0, ch); // Restart clock
/* Re-program DMA */
dma_hw->ch[2*ch].read_addr = (io_rw_32)wfg_ctrl[ch].buf; // Read from waveform buffer
dma_hw->ch[2*ch].write_addr = (io_rw_32)&pio0->txf[ch]; // Write to PIO TX fifo
dma_hw->ch[2*ch].transfer_count = wfg_ctrl[ch].len/4; // Nr of 32 bit words to transfer
dma_hw->ch[2*ch].al1_ctrl = DMA_DC(ch); // Write ctrl word without starting the DMA
dma_hw->ch[2*ch+1].read_addr = (io_rw_32)&(wfg_ctrl[ch].buf); // Read from waveform buffer address reference
dma_hw->ch[2*ch+1].write_addr = (io_rw_32)&dma_hw->ch[2*ch].read_addr; // Write to data channel read address
dma_hw->ch[2*ch+1].transfer_count = 1; // One word to transfer
dma_hw->ch[2*ch+1].ctrl_trig = DMA_CC(ch); // Write ctrl word and start DMA
}