I have been using the T3.6 in a project I created that runs a piezo device that responds to analog voltage. I have the teensy connected to a board that has amplification and shift registers to link the 2 DAC channels to 16 outputs. Until now the Teensy only had one time critical activity (creating the voltage signals, I will call them waveforms). I want the teensy to start handling more time critical activities and I think the DMA will be perfect for sending from memory the waveform to the DAC. My previous experience of all this is basically just Arduino I did do mech. eng. but it didn't really cover much past what basic assembler is.
Firstly where can I go to understand the DMA, the DAC and the PDB (possible a useful module)? Is there a hardware sheet for the Teensy processor that covers these?
Secondly I found a post on hackaday blog and have built an example in teensyduino that is close to what I want.
You will see that this code sets up the PDB and the DMA/DAC and at the same time controls a stepper motor (the other time sensitive application). Currently this script just endlessly repeats the waveform but I want to be able to have two different modes.
1. Repeating a set interval e.g. 2khz, 4 khz
2. Single shot every time I command
I tried playing with the PDB and setting to non-continuous then software triggering it at a set interval. Instead of burst sending the waveform then waiting it just seemed to affect the total period of the signal.
This is not the behavior I want and I guess points to me not understanding what this is actually doing. Hence why I wanted help from people or a reference I can look up what the above lines are and what other options their may be.
Firstly where can I go to understand the DMA, the DAC and the PDB (possible a useful module)? Is there a hardware sheet for the Teensy processor that covers these?
Secondly I found a post on hackaday blog and have built an example in teensyduino that is close to what I want.
Code:
// Blocking.pde
// -*- mode: C++ -*-
//
// Shows how to use the blocking call runToNewPosition
// Which sets a new target position and then waits until the stepper has
// achieved it.
//
// Copyright (C) 2009 Mike McCauley
// $Id: Blocking.pde,v 1.1 2011/01/05 01:51:01 mikem Exp mikem $
#include <AccelStepper.h>
#define steps_mm 134
#define BUFFER_SIZE 1024
// Define a stepper and the pins it will use
AccelStepper stepper(1, 33, 34) ; // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5
bool ran = false;
bool homed = false;
int k = 0;
static volatile uint16_t waveform_table[BUFFER_SIZE];
void setup() {
// fill up the sine table
for (int i = 1; i < BUFFER_SIZE; i++) {
waveform_table[i] = 2 * 8 * 256 - 1;
}
for (int i = 0; i < BUFFER_SIZE; i = i + 50) {
for (int idx = 0; idx < 25; idx ++) {
waveform_table[i + idx] = 0;
}
}
for( int i = 800; i < BUFFER_SIZE; i++) { waveform_table[i] = 0;}
// initialise the DAC
SIM_SCGC2 |= SIM_SCGC2_DAC0; // enable DAC clock
DAC0_C0 = DAC_C0_DACEN | DAC_C0_DACRFS; // enable the DAC module, 3.3V reference
// initialise the DMA
// first, we need to init the dma and dma mux
// to do this, we enable the clock to DMA and DMA MUX using the system timing registers
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // enable DMA MUX clock
SIM_SCGC7 |= SIM_SCGC7_DMA; // enable DMA clock
// next up, the channel in the DMA MUX needs to be configured
DMAMUX0_CHCFG0 |= DMAMUX_SOURCE_DAC0; //Select DAC as request source
DMAMUX0_CHCFG0 |= DMAMUX_ENABLE; //Enable DMA channel 0
// then, we enable requests on our channel
DMA_ERQ = DMA_ERQ_ERQ0; // Enable requests on DMA channel 0
// first, we need to init the dma and dma mux
// to do this, we enable the clock to DMA and DMA MUX using the system timing registers
SIM_SCGC6 |= SIM_SCGC6_DMAMUX; // enable DMA MUX clock
SIM_SCGC7 |= SIM_SCGC7_DMA; // enable DMA clock
// next up, the channel in the DMA MUX needs to be configured
DMAMUX0_CHCFG0 |= DMAMUX_SOURCE_DAC0; //Select DAC as request source
DMAMUX0_CHCFG0 |= DMAMUX_ENABLE; //Enable DMA channel 0
// then, we enable requests on our channel
DMA_ERQ = DMA_ERQ_ERQ0; // Enable requests on DMA channel 0
// Here we choose where our data is coming from, and where it is going
DMA_TCD0_SADDR = waveform_table; // set the address of the first byte in our LUT as the source address
DMA_TCD0_DADDR = &DAC0_DAT0L; // set the first data register as the destination address
// now we need to set the read and write offsets - kind of boring
DMA_TCD0_SOFF = 4; // advance 32 bits, or 4 bytes per read
DMA_TCD0_DOFF = 4; // advance 32 bits, or 4 bytes per write
// this is the fun part! Now we get to set the data transfer size...
DMA_TCD0_ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT);
DMA_TCD0_ATTR |= DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT) | DMA_TCD_ATTR_DMOD(31 - __builtin_clz(32)); // set the data transfer size to 32 bit for both the source and the destination
// ...and the number of bytes to be transferred per request (or 'minor loop')...
DMA_TCD0_NBYTES_MLNO = 16; // we want to fill half of the DAC buffer, which is 16 words in total, so we need 8 words - or 16 bytes - per transfer
// set the number of minor loops (requests) in a major loop
// the circularity of the buffer is handled by the modulus functionality in the TCD attributes
DMA_TCD0_CITER_ELINKNO = DMA_TCD_CITER_ELINKYES_CITER(BUFFER_SIZE * 2 / 16);
DMA_TCD0_BITER_ELINKNO = DMA_TCD_BITER_ELINKYES_BITER(BUFFER_SIZE * 2 / 16);
// the address is adjusted by these values when a major loop completes
// we don't need this for the destination, because the circularity of the buffer is already handled
DMA_TCD0_SLAST = -BUFFER_SIZE * 2;
DMA_TCD0_DLASTSGA = 0;
// do the final init of the channel
DMA_TCD0_CSR = 0;
// enable DAC DMA
DAC0_C0 |= DAC_C0_DACBBIEN | DAC_C0_DACBWIEN; // enable read pointer bottom and waterwark interrupt
DAC0_C1 |= DAC_C1_DMAEN | DAC_C1_DACBFEN | DAC_C1_DACBFWM(3); // enable dma and buffer
DAC0_C2 |= DAC_C2_DACBFRP(0);
// init the PDB for DAC interval generation
SIM_SCGC6 |= SIM_SCGC6_PDB; // turn on the PDB clock
PDB0_SC |= PDB_SC_PDBEN; // enable the PDB
PDB0_SC |= PDB_SC_TRGSEL(15); // trigger the PDB on software start (SWTRIG)
//PDB0_SC |= PDB_SC_CONT; // run in continuous mode
PDB0_MOD = 40; // modulus time for the PDB
PDB0_DACINT0 = (uint16_t)(1); // we won't subdivide the clock...
PDB0_DACINTC0 |= 0x01; // enable the DAC interval trigger
PDB0_SC |= PDB_SC_LDOK; // update pdb registers
PDB0_SC |= PDB_SC_SWTRIG; // ...and start the PDB
pinMode(35, OUTPUT);
pinMode(36, INPUT);
pinMode(37, INPUT);
digitalWrite(35, LOW);
stepper.setMaxSpeed (750 * steps_mm);
stepper.setAcceleration(5000 * steps_mm);
stepper.setCurrentPosition(0);
//stepper.setSpeed(500*steps_mm);
Serial.begin(115200);
Serial.println("fin settuping");
stepper.setSpeed (-150 * steps_mm);
while (homed == false) {
stepper.runSpeed();
if (digitalRead(37) == LOW) {
stepper.stop();
if (ran == false) {
ran = true;
stepper.setCurrentPosition(0);
stepper.runToNewPosition(1 * steps_mm);
stepper.setSpeed(-1 * steps_mm);
}
else {
stepper.stop();
stepper.setCurrentPosition(4);
stepper.runToNewPosition(5 * steps_mm);
homed = true;
}
}
}
}
void loop()
{
PDB0_SC |= PDB_SC_SWTRIG; // ...and start the PDB
/*
Serial.println("run..");
if (!stepper.isRunning()) {
if (stepper.currentPosition() > 250 * steps_mm) {
stepper.moveTo(50 * steps_mm);
}
else {
stepper.moveTo(450 * steps_mm);
}
}
stepper.run();
if (Serial.available() > 0)
{
while (Serial.available() > 0) {
char t = Serial.read();
}
stepper.runToNewPosition(5 * steps_mm);
stepper.setSpeed(-1 * steps_mm);
while (digitalRead(37) == HIGH) {
stepper.runSpeed();
}
Serial.println(stepper.currentPosition());
delay(1500);
}
*/
}
You will see that this code sets up the PDB and the DMA/DAC and at the same time controls a stepper motor (the other time sensitive application). Currently this script just endlessly repeats the waveform but I want to be able to have two different modes.
1. Repeating a set interval e.g. 2khz, 4 khz
2. Single shot every time I command
I tried playing with the PDB and setting to non-continuous then software triggering it at a set interval. Instead of burst sending the waveform then waiting it just seemed to affect the total period of the signal.
Code:
//PDB0_SC |= PDB_SC_CONT; // run in continuous mode
...
void loop()
{
PDB0_SC |= PDB_SC_SWTRIG; // ...and start the PDB
}
This is not the behavior I want and I guess points to me not understanding what this is actually doing. Hence why I wanted help from people or a reference I can look up what the above lines are and what other options their may be.