I should probably preface this by stating that I am relatively amateur when it comes to microprocessor and low level programming languages so bear with me.
I am trying to stream data from the computer to some kind of DAC and I would like to be able to do 4 channels at 1Msps for 8 seconds. This is to reproduced data that was recorded at 10Khz. I would like to be able to oversample as much as possible. 400Ksps would probably be plenty.
I have considered trying to get a more complicated solution setup making use of additional memory using SPI or something but I would need to have a fair bit and was unsure that I would be successful.
Instead I have arrived at trying to split the data between two computer USB ports and stream to two teensys simultaneously. This should provide roughly 24Mbit/s of usable bandwidth which using only the 12 bits needed to run the DAC with minimal additional communication overhead should make ~500Ksps/channel possible.
I have a rough solution which makes use of elapsedMicros but I can see the time jitter on my scope while the serial is active. Once the serial is done and the loop settles to just running through the buffer it becomes very stable. But the frequency I am reading on the scope versus the projected one is consistently off. The measured frequency is lower. I can adjust timing on either end and get close.
I was wondering if someone could point me in the direction of an example making use of DMA and PDB to stream serial data to the DAC. I imagine the audio library might have someone close to this but I am unsure where to look.
I am trying to stream data from the computer to some kind of DAC and I would like to be able to do 4 channels at 1Msps for 8 seconds. This is to reproduced data that was recorded at 10Khz. I would like to be able to oversample as much as possible. 400Ksps would probably be plenty.
I have considered trying to get a more complicated solution setup making use of additional memory using SPI or something but I would need to have a fair bit and was unsure that I would be successful.
Instead I have arrived at trying to split the data between two computer USB ports and stream to two teensys simultaneously. This should provide roughly 24Mbit/s of usable bandwidth which using only the 12 bits needed to run the DAC with minimal additional communication overhead should make ~500Ksps/channel possible.
I have a rough solution which makes use of elapsedMicros but I can see the time jitter on my scope while the serial is active. Once the serial is done and the loop settles to just running through the buffer it becomes very stable. But the frequency I am reading on the scope versus the projected one is consistently off. The measured frequency is lower. I can adjust timing on either end and get close.
I was wondering if someone could point me in the direction of an example making use of DMA and PDB to stream serial data to the DAC. I imagine the audio library might have someone close to this but I am unsure where to look.
Code:
//PYTHON CODE COMPRESSED=TRUE
//microsecond elapsed time timer
elapsedMicros usec = 0;
//number of microseconds between DAC update
const unsigned int numMicros=5;
//for processing of incoming serial data
unsigned int commandBytes=0;//the command bytes value read from the serial buffer
unsigned int command_data = 0xFFFF;//Data command
//masks to split 3x bytes into 2x 12 bit values
byte splitByteMask_1 = 0xF0; //upper mask of split byte
byte splitByteMask_2 = 0x0F; //lower mask of split byte
const unsigned int num_buffered_values = 24000;
const unsigned int DAC_watermark = 12000;
//RAM buffers
unsigned int buffer_DAC_1[num_buffered_values];
unsigned int buffer_DAC_2[num_buffered_values];
unsigned int *DAC_buffers[2] = {buffer_DAC_1,buffer_DAC_2};
//Array of IDX's
unsigned int buffer_DAC_write_idxs[2] = {0,0};
unsigned int buffer_DAC_read_idxs[2] = {0,0};
//Flag to turn DAC output on
bool DAC_OUTPUT_FLAG = false;
//temporary containers while reading the serial buffer
unsigned int BYTE_1=0;
unsigned int BYTE_2=0;
unsigned int BYTE_3=0;
unsigned int tmpVal_1=0;
unsigned int tmpVal_2=0;
unsigned int tmpVals[2];
void splitBytes(byte BYTE1, byte BYTE2, byte BYTE3){
//Convert from 3 bytes to 2x 12 bit values done to conserve data transfer overhead
//12-bit Value 1
tmpVals[0] = BYTE1;
tmpVals[0] <<= 4;
tmpVals[0] |= ((BYTE2 & splitByteMask_1)>>4); //mask to get the upper nibble of BYTE2 then shift down to LSB and combine to find Val1
//12-bit Value 2
tmpVals[1] = (BYTE2 & splitByteMask_2);
tmpVals[1] <<= 8;
tmpVals[1] |= BYTE3;
}
void setup() {
analogWriteResolution(12);
// put your setup code here, to run once:
Serial.begin(9600); //start serial listening (Note that teensy ignores Baud and always runs at High speed 12Mbit/s)
// Initially fill the memory buffers with all 0's
for(int idx=0; idx<num_buffered_values;idx++){
buffer_DAC_1[idx]=0;
buffer_DAC_2[idx]=0;
}
}
void loop() {
// put your main code here, to run repeatedly:
if(Serial.available()==50){//Serial packet size is set at 50 bytes, 2 command bytes followed by 32 12-bit values
//Read in the first two bytes as the commandBytes
commandBytes = Serial.read();//read in the first 8 most significant bits
commandBytes = commandBytes<<8; //shift 8 bits left
commandBytes |= Serial.read();//fill in the 8 least significant bits
//if this is data
if(commandBytes==command_data){
//Read 24 byte of data for each DAC channel to memory
for(int DAC_channel=0;DAC_channel<2;DAC_channel++){
//for this channel perform 8x 3 byte reads (24 bytes total)
for(int idx=0;idx<8;idx++){
//Read 3 bytes containing 2x 12-bit values for DAC
BYTE_1 = Serial.read();
BYTE_2 = Serial.read();
BYTE_3 = Serial.read();
splitBytes(BYTE_1, BYTE_2, BYTE_3);
//Write to the memory buffer
DAC_buffers[DAC_channel][buffer_DAC_write_idxs[DAC_channel]]=tmpVals[0];
buffer_DAC_write_idxs[DAC_channel]+=1;
DAC_buffers[DAC_channel][buffer_DAC_write_idxs[DAC_channel]]=tmpVals[1];
buffer_DAC_write_idxs[DAC_channel]+=1;
}
}
//If the RAM buffer is full then restart at zero index
if(buffer_DAC_write_idxs[0]==num_buffered_values){
buffer_DAC_write_idxs[0]=0;
}
if(buffer_DAC_write_idxs[1]==num_buffered_values){
buffer_DAC_write_idxs[1]=0;
}
}
//Otherwise clear these bytes
else{
BYTE_1 = Serial.read();
BYTE_2 = Serial.read();
BYTE_3 = Serial.read();
splitBytes(BYTE_1, BYTE_2, BYTE_3);
Serial.println("As read:");
Serial.println(BYTE_1);
Serial.println(BYTE_2);
Serial.println(BYTE_3);
Serial.println("De-compressed:");
Serial.println(tmpVals[0]);
Serial.println(tmpVals[1]);
for(int idx=0;idx<45;idx++){
Serial.read();
}
Serial.println("!!SERIAL CLEARED!!");
}
}
//If the DAC is not already running
if(!DAC_OUTPUT_FLAG){
//Once reaching the watermark in RAM buffers then start the DAC
if(buffer_DAC_write_idxs[0]==DAC_watermark && buffer_DAC_write_idxs[1]==DAC_watermark){
DAC_OUTPUT_FLAG=true;
usec = 0;
analogWrite(A22, 0);
analogWrite(A21, 0);
}
}
//If one time step has elapsed then update the DAC
if(usec >= numMicros){
analogWrite(A22, DAC_buffers[0][buffer_DAC_read_idxs[0]]);
analogWrite(A21, DAC_buffers[1][buffer_DAC_read_idxs[1]]);
usec = 0;
buffer_DAC_read_idxs[0]+=1;
buffer_DAC_read_idxs[1]+=1;
//If at the end of the RAM buffer then wrap around to zero idx
if(buffer_DAC_read_idxs[0]==num_buffered_values){
buffer_DAC_read_idxs[0]=0;
}
if(buffer_DAC_read_idxs[1]==num_buffered_values){
buffer_DAC_read_idxs[1]=0;
}
}
}
Last edited: