Quad channel output on Teensy 3.6

OSHPark board (x3) and spare teensy 3.2 ordered :)
paul, something I dont understand in your design - you seem to connect pin 1 (the i2c address) to both high AND low, and both resistors seem to be populated in the photos. you mention wanting to be able to configure i2c address - I guess you've just got a divider and can choose which way up it goes? whereas I tied both address pins straight to ground. do you think this could be the source of my woes? its a bit odd, as the i2c side of things works fine but.... I dont think I fully understand why a divider is needed on these pins at all? just trying to flush out all differences between the designs.
 
Yeah, those resistors are redundant and unnecessary. The 150 ohm resistor could just be a short, and the 100K is totally unnecessary.

The way I do these sorts of boards, managing the 2 week wait from OSH Park while juggle lots of other stuff for Teensy, I tend label all the part values when I design the board and then it's "out of sight, out of mind". Two weeks later, when I or someone else actually solders the board (really can't recall if I soldered this one, or if Erin or Brian did it), I don't bother looking at these sorts of things again if the board "just works", as this one did. Of course it didn't immediately spring the life... I had to write the TDM code. But fortunate the hardware worked on the first try.

If you want to do exact the same as I did, populate both resistors. But there wasn't any reason to use both, other than I wanted the board to be configurable, and when the boards actually arrived the configuration stuff was forgotten and the boards built from the docs I made when I created the layout.
 
you're a more thorough man than I. :) while I wait for the oshpark board, I have done the filthy thing and patched a bodge wire from VQ to a spare teensy pin, which I found that a brief 10us high output is enough to kick the codec into normal operation. I feel bad that I dont know why it doesn't work without it, and know that this is definitely not what the chip designers intended, but... at least I can move on and test the rest of the circuit :) (the bodge cap in the picture was me replacing an SMT 4.7uf with various other sizes, to see if it helped. it didnt). oh well... onwards!
I'm doing pinMode(3,OUTPUT); digitalWriteFast(3,1); delayMicroseconds(10); pinMode(3,INPUT); to generate the high pulse (ie short VQ to 3v3) and then trying to disable the pin by setting it to INPUT (hi-z); this is ok and works but, since the voltage on the pin then hovers around 2.5v I worry that it'll be toggling the input transistors needlessly. is there a way to fully disable a pin? I searched a bit but kept finding AVR/teensy2 results, so I was unsure. thanks
Dki_48FXsAAvr3c.jpg
 
[...] - I've moved to an OLED SPI display (but one without a noisy boost converter! avoid the adafruit ones for audio work unless you have really good power supply) .

Sorry, slightly OT: Mind to share - Which OLED is that, mmalex?
 

Ah, thanks! Figured so, but was looking at the 128x64 version which looked too big. 102x64 ... didn't know that was a thing!

And one OT follow-up, if I may (sorry, just was working backwards through the thread): You mentioned you had 2x AD5668 DACs working with SPI/DMA. Would you consider sharing the relevant code snippets? I've been trying, but failing, to get a multichannel DAC working via DMA.
 
You mentioned you had 2x AD5668 DACs working with SPI/DMA. Would you consider sharing the relevant code snippets? I've been trying, but failing, to get a multichannel DAC working via DMA.
sure! I pasted together the code, sorry if I missed a definition in the pasting process. i've added some comments too.
Code:
DMAChannel dacdma;
#define PSPI0_CSDAC0 10 // teensy pin 10 = cs0_0
#define PSPI0_CSDAC1 9  // teensy pin 9 = cs0_1
#define DACCS0 (1<<0) // which CS0_x bit the two dacs are on. this is bit 0 
#define DACCS1 (1<<1) // this is bit 1
#define DACDMA(data,cs,chan) SPI_PUSHR_CTAS(1) | (1ul<<31) | (cs<<16) | (0x0300) | ((chan)<<4) | ((data)>>12), \
					    	 SPI_PUSHR_CTAS(1) |           (cs<<16) | (((data)<<4)&0xfff0)

u32 dacdmabuffer[8*4]={
	DACDMA(32768,DACCS0,0), DACDMA(32768,DACCS1,0),	
	DACDMA(32768,DACCS0,1), DACDMA(32768,DACCS1,1),	
	DACDMA(32768,DACCS0,2), DACDMA(32768,DACCS1,2),	
	DACDMA(32768,DACCS0,3), DACDMA(32768,DACCS1,3),	
	DACDMA(32768,DACCS0,4), DACDMA(32768,DACCS1,4),	
	DACDMA(32768,DACCS0,5), DACDMA(32768,DACCS1,5),	
	DACDMA(32768,DACCS0,6), DACDMA(32768,DACCS1,6),	
	DACDMA(32768,DACCS0,7), DACDMA(32768,DACCS1,7),	
};

inline void ClearSPIFinishedFlag() {
	SPI0_SR = SPI_SR_TCF; // clear spi complete flag	
}

inline void WaitForSPIFinishedFlag() {
	while (!(SPI0_SR & SPI_SR_TCF)) yield(); // wait
}

void InitDAC() {
        // set up DACs
        // NB this setCS() hands the pins to the SPI unit on teensy
        // otherwise the top bits of PUSHR have no effect. crucial! took me a while to find
	u32 cs0=SPI.setCS(PSPI0_CSDAC0); // PSPI0_CSDACx is a teensy pin number from my design, must be a CS0_x pin
    u32 cs1=SPI.setCS(PSPI0_CSDAC1);
    assert(cs0==DACCS0); // did you set your defines right
    assert(cs1==DACCS1); // did you set your defines right
    u32 cs01=cs0|cs1; // you can even talk to both dacs at the same time...
    SPI.begin();
    SPISettings dacsettings(12*1000*1000,MSBFIRST, SPI_MODE0);
    SPI.beginTransaction(dacsettings);
    const static u32 ref_enable=(1<<27)+1;
    ClearSPIFinishedFlag();
    // uses SPI0 fifo to write two times in one go
    SPI0_PUSHR = SPI_PUSHR_CTAS(1) | (1<<31) | (cs01<<16) | (ref_enable>>16);
    SPI0_PUSHR = SPI_PUSHR_CTAS(1) |           (cs01<<16) | (ref_enable&0xffff);
    WaitForSPIFinishedFlag();
}

void InitDMA() {
	dacdma.disable();
    dacdma.destination((volatile uint32_t&)SPI0_PUSHR);
    dacdma.disableOnCompletion();
    dacdma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
	dacdma.sourceBuffer(dacdmabuffer, sizeof(dacdmabuffer)); 

    // ask for DMA pokes from SPI unit
    SPI0_SR = 0xFF0F0000; // TODO what are these bits
    SPI0_RSER = /*SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS |*/ SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
}

void KickDMABuffer(const u16 anaout[16]) { // a single sample across 16 channels
    const u16 *src=anaout;
	for (int chan=0;chan<8;++chan) {
		u16 data0=*src++;
		u16 data1=*src++;
        // note that I alternate a register-width (2 writes) for dac0, then dac1, then dac0, etc
        // ie alternate chips. also note that the dma buffer high u16 of each PUSHR is already setup
        // so this code only touches the bottom u16 which has the data in it

        // that gives each chip a chance to 'breathe' between each 32 bit write
        // alternatively you can set up CS delays I believe via SPI registers

		((u16*)dacdmabuffer)[chan*8+0] = (0x0300) | ((chan)<<4) | ((data0)>>12);
		((u16*)dacdmabuffer)[chan*8+2] = ((data0)<<4);
		((u16*)dacdmabuffer)[chan*8+4] = (0x0300) | ((chan)<<4) | ((data1)>>12);
		((u16*)dacdmabuffer)[chan*8+6] = ((data1)<<4);
	} 
	dacdma.enable();
}

main type thing {
    InitDAC();
    delay(1); // for good measure
    InitDMA();
    //... set up an IntervalTimer to do some DSP and call KickDMABuffer() for each sample. I ran at 32khz
}
 
sure! I pasted together the code, sorry if I missed a definition in the pasting process. i've added some comments too.


Cool, thanks. This did the trick ... Getting output on all 8 channels now, I guess I must have done something wrong when stuffing the buffer. Now on to trying to hook things up to the PDB / audio library.

FWIW, here's an .ino-version of mmalex's code above for just one 8-channel DAC (TI DAC8568 in this case. Turns out the input register is almost identical to AD5668). NB: using pin 7 for MOSI.

Code:
/* DAC8568 */

#include <DmaChannel.h>
#include <SPI.h>
#include "dspinst.h"

#define PSPI0_CSDAC0 15   // teensy pin 15 = cs0_0
#define LDAC 14
#define DACCS0 0x10       // ~ pcs (CS = PTC0 = T15)

/* 0  X  X  X  0  0  1  1  A3  A2  A1  A0  [ Data 15 - 0 ]  X  X  X  X */ 
/* p.37: "Write to Selected DAC Input Register and Update Respective DAC Register" */
    
#define DAC_CHANNEL_OFFSET 4  // A0-A3
#define SELECT_UPDATE 0x0300  // C0-C3
#define INTREF_ENABLE 0x8000001
#define NUM_CHANNELS 8

#define DACDMA(data,cs,chan) SPI_PUSHR_CTAS(1) | (1ul << 31) | (cs << 16) | SELECT_UPDATE | ((chan) << DAC_CHANNEL_OFFSET) | ((data) >> 12), \
                             SPI_PUSHR_CTAS(1) | (cs << 16) | (((data) << 4) & 0xFFF0)
                             
DMAChannel dacdma;
IntervalTimer timer;
const uint32_t timer_rate = 10;
uint32_t phase_accumulator = 0, phase_increment = 0xFFFFFF;

  // plucked from synth_sine.cpp:
  
static int32_t taylor(uint32_t ph)
{
  int32_t angle, sum, p1, p2, p3, p5, p7, p9, p11;

  if (ph >= 0xC0000000 || ph < 0x40000000) {                            // ph:  0.32
    angle = (int32_t)ph; // valid from -90 to +90 degrees
  } else {
    angle = (int32_t)(0x80000000u - ph);                        // angle: 2.30
  }
  p1 =  multiply_32x32_rshift32_rounded(angle, 1686629713) << 2;        // p1:  2.30
  p2 =  multiply_32x32_rshift32_rounded(p1, p1) << 1;                   // p2:  3.29
  p3 =  multiply_32x32_rshift32_rounded(p2, p1) << 2;                   // p3:  3.29
  sum = multiply_subtract_32x32_rshift32_rounded(p1, p3, 1431655765);   // sum: 2.30
  p5 =  multiply_32x32_rshift32_rounded(p3, p2);                        // p5:  6.26
  sum = multiply_accumulate_32x32_rshift32_rounded(sum, p5, 572662306);
  p7 =  multiply_32x32_rshift32_rounded(p5, p2);                        // p7:  9.23
  sum = multiply_subtract_32x32_rshift32_rounded(sum, p7, 109078534);
  p9 =  multiply_32x32_rshift32_rounded(p7, p2);                        // p9: 12.20
  sum = multiply_accumulate_32x32_rshift32_rounded(sum, p9, 12119837);
  p11 = multiply_32x32_rshift32_rounded(p9, p2);                       // p11: 15.17
  sum = multiply_subtract_32x32_rshift32_rounded(sum, p11, 881443);
  return sum <<= 1;                                                 // return:  1.31
}


void FASTRUN ISR() {

  uint16_t buf[NUM_CHANNELS];
  
  uint32_t sample = (taylor(phase_accumulator) >> 16) + 0x8000;
  // fill the buffer
  for (int i = 0; i < NUM_CHANNELS; i++) buf[i] = sample;
  
  KickDMABuffer(buf);
  phase_accumulator += phase_increment;
}

uint32_t dacdmabuffer[NUM_CHANNELS * 2] = { 
  
  DACDMA(32768,DACCS0,0),  
  DACDMA(32768,DACCS0,1),
  DACDMA(32768,DACCS0,2),
  DACDMA(32768,DACCS0,3),
  DACDMA(32768,DACCS0,4),
  DACDMA(32768,DACCS0,5),
  DACDMA(32768,DACCS0,6),
  DACDMA(32768,DACCS0,7) 
};

inline void ClearSPIFinishedFlag() {
  SPI0_SR = SPI_SR_TCF; // clear spi complete flag  
}

inline void WaitForSPIFinishedFlag() {
  while (!(SPI0_SR & SPI_SR_TCF)) 
    yield(); // wait
}

void InitDAC() {
  
    // set up DAC    
    uint32_t pcs = SPI.setCS(PSPI0_CSDAC0); // must be a CS pin

    SPI.setMOSI(7);
    SPI.begin();
    SPISettings dacsettings(30*1000*1000, MSBFIRST, SPI_MODE0);
    SPI.beginTransaction(dacsettings);
    
    uint32_t ref_enable = INTREF_ENABLE;
    ClearSPIFinishedFlag();
    // uses SPI0 fifo to write two times in one go
    SPI0_PUSHR = SPI_PUSHR_CTAS(1) | (1ul << 31) | (pcs << 16) | (ref_enable >> 16);
    SPI0_PUSHR = SPI_PUSHR_CTAS(1) | (pcs << 16) | (ref_enable & 0xFFFF);
    
    WaitForSPIFinishedFlag();
    Serial.println("DAC initialised");
}

void InitDMA() {
  
    dacdma.disable();
    dacdma.destination((volatile uint32_t&)SPI0_PUSHR);
    dacdma.disableOnCompletion();
    dacdma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPI0_TX);
    dacdma.sourceBuffer(dacdmabuffer, sizeof(dacdmabuffer)); 

    // ask for DMA pokes from SPI unit
    SPI0_SR = 0xFF0F0000; // Setup SPI for DMA transfer
    SPI0_RSER = /*SPI_RSER_RFDF_RE | SPI_RSER_RFDF_DIRS |*/ SPI_RSER_TFFF_RE | SPI_RSER_TFFF_DIRS;
    Serial.println("DMA initialised");
}

void KickDMABuffer(const uint16_t anaout[NUM_CHANNELS]) { // a single sample across 8 channels
  
  const uint16_t *src = anaout;
  
  for (int chan = 0; chan < NUM_CHANNELS; ++chan) {
    
    uint16_t data0 = *src++;
    
    ((uint16_t*)dacdmabuffer)[chan * NUM_CHANNELS/2 + 0] = SELECT_UPDATE | ((chan) << DAC_CHANNEL_OFFSET) | ((data0) >> 12);
    ((uint16_t*)dacdmabuffer)[chan * NUM_CHANNELS/2 + 2] = ((data0) << 4);
  } 
  dacdma.enable();
}

void setup() {

    delay(10);
    // LDAC pin 
    pinMode(LDAC, OUTPUT);
    digitalWrite(LDAC, LOW);
    //
    InitDAC();
    delay(10); // for good measure
    InitDMA();
    timer.priority(64);
    timer.begin(ISR, timer_rate);
}

void loop() { }
 
Last edited:
progress! https://twitter.com/mmalex/status/1030925272508456960 :
with a single (evil) bodge wire, everything seems to work. plus, ive built 2 copies! code atm is just: 10 sine oscs out, and 10 ins graphed on a 'scope'. buttons, leds, encoders all fine. omg. next step - write some audio dsp?
this is using the cs24228 from this thread to do 6 of the ins and 8 of the outs; the teensy3.6 DACs provide the last 2 outs, and the ADCs (multiplexed) provide the last 4 ins. so it's a mashup of input/output devices to get 10 in, 10 out (+2 digital in, 1 digital out, 8 encoders with 8 RGB leds and 4 illuminated tact switches). now, lots of software ahead...
the teensy has been amazing to work with. the cs42448, not so much.
Dk6VDLPX4AAKPpR.jpgDk6VDLKX0AUZZWd.jpg
 
Hi Paul and Co!

I've enjoyed making projects with my 3.2 for some time now. I wanted to try out the CS42448 board for an audio project and had a question:

Is it possible to order a fully populated CS42448 board from OSH park or someone on the forum like the Soldering Goddess? I'm a bit confused on how people are getting these completed boards ordered other than through private messaging. I have 0 experience soldering SMD so I'm not sure I could make a working one myself!

Thanks for your time!
 
Hello,
This thread has been most useful!
i've been working on an audio project with teensy 3.6 for a few weeks now, developed my proof of concept using the audio shield and the 3.6 internal sd card (recording wav files into it) and all was well.
Now i've upgraded to the CS42448 audio board, all audio is already working properly but the sd card is not saving any files and does not report any error!
If i remove all the cs42448 related code and leave just my sd card code it works.
I noticed that pins 11 and 13 are being used for both the cs42448 and internal sd card although it has the comment "// not actually used".
also removed the SPI.setXXX pins and used the :
#define SD_SELECT BUILTIN_SDCARD
and also used this initialization method instead:
if (!(SD.begin(SD_SELECT))) {
although this change also worked in my sd card only code it still isn't working on my main project.
still no errors are reported in the serial monitor...

i wonder if this is a know issue/incompatibility or something else is breaking...
any help is greatly appreciated!
All the very best
Andre
 
Hi, I've just soldered a cs42448 board from OSH Park to be used with the teensy 3.6. I'm not sure though if the 5 pins which differ in position from v3.2 (reset, program, ground, 3.3v, vbat), should be connected to the board. It seems that the ground on the OSH board is connected anyway to the common for both v3.6 and v3.2 ground pin, but I've seen people here who connect this anyway. I think the same goes for the 3.3V pin. Do I need to take care of this, or will I fry the IC if I don't, and just hook up the 3.6 neglecting the 5 "horizontal" pins? @__ag how does your setup look like?
Thanks!
 
Those 5 pins are ignorable generally. Indeed the GND and 3v3 pins are just duplicates, and the other 3 special purpose pins are only selectively needed as labelled.
 
Great, thanks! There must be something wrong with my soldering then, as all I can hear from a simple passthrough patch is noise. Good thing three boards is the minimum order at OSH :)
 
Given you have boards a quick tie of the GND and 3v3 from T_3.6 would tell for sure if the extra GND or 3v3 path helps - either the center pins - or the handy extra edge pins.
 
Thanks for the tip! It seems it's not necessary to connect the extra GND ans 3v3. The noise and other problems I had (working for a few seconds and then dying) were because of my inexperience on soldering something this tiny. The flux I used is apparently conductive, and there was still some leftover after cleaning the board.
It took me while as well to realize that only the even numbered in/outs of the TDM objects are valid, at least for v3.6 (0-10 for the ins and 0-14 for the outs). And now coding!
IMG_20190117_134659.jpg
 
I think I managed to solder the chip onto the board, hurray! Now, next task: solder all the little pieces too.

Now I wondered: how to recognize the orientation of the capacitors? I just inspected one of the 10uF capacitors. There is a circle going through one of the ends. Does this signify anything? The clearest placement diagram was this of course: https://www.pjrc.com/teensy/beta/cs42448_placement.png. The parts are all readable in the same directions. Yet I would guess the orientation on the left hand side should be the opposite from the right hand side?
 
Andre,
Did you resolve this (T3.6 SD card/cs42448 potential conflict on SPI pins)?
I'm spooling up to maybe build that combo.
- Philip

...I noticed that pins 11 and 13 are being used for both the cs42448 and internal sd card although it has the comment "// not actually used".
also removed the SPI.setXXX pins and used the :
#define SD_SELECT BUILTIN_SDCARD
and also used this initialization method instead:
if (!(SD.begin(SD_SELECT))) {
although this change also worked in my sd card only code it still isn't working on my main project.
still no errors are reported in the serial monitor...

i wonder if this is a know issue/incompatibility or something else is breaking...
...
Andre
 
I posted in these forums a while ago about doing something with TDM that may have spurred Paul to make his test boards (a lot of I/O in eurorack). Since my idea of what to do has changed a bit and I am only looking to do 4 in 4 out. I was wondering if there is any roadblock to using a WM8731 instead of the SGTL5000? I also had some performance uncertainty between TDM and quad I2S, am I right in thinking that quad I2S will require less of the processor? Going to doing some rather taxing things with reverb and granular, so any processor performance I can save would be great.

Basically I have a desire to use 2 WM8731 instead of a CS42448 for a number of reasons: lower power requirement, lower part count, single ended, better understood in use case, and easier to manufacture. Cost is the same and audio performance should be similar.

If anyone has an interest what my project will look like I am keeping a sketch of my ideas here, got some board layouts and outdated schematics there for using the CS42448. http://www.sinphi.com/synths/humours/humours.html
 
I've attempted to create a schematic based on the discussions in this thread about the cs42448 codec...
I still need to add the CAT811T to the circuit.
I've used 4 stereo input channels instead of 3. Im not sure if the extra 2 input channels will be available in the TDM stream.
Please feel free to suggest corrections and improvements.

https://github.com/newdigate/teensy-cs42448

In the meantime, I have ordered the oshpark cs42448 board and parts

cs42448.png

teensy.png

input.png

output.png
 
I can't see your images here, but I do see them on the github page. On your next post, any chance you can put PDF files here? PDF usually works best for quickly looking at schematics.

Here's some issues I notice...

1: You have 4 copies of the ST_IN block on the main sheet, but there are only 3 stereo inputs on the PCB.

2: The ST_OUT circuit shows 470 ohm resistors, but the design uses 560.

3: CS42448 pin 1 is shown connected to GND. On the PCB, it goes to 2 resistors which are intended to allow configuring the address.

4: The AOUT- pins (25, 28, 29, 32, 33, 37, 38, 41) are shown connected to AGND. On the PCB, they are unconnected.

5: CS42448 pin 21 (AUX_SCLK) is shown connected to GND. That's probably correct, but on the PCB that pin is unconnected.

6: CS42448 VA power (pins 44 & 53) is shown connected only to an inductor. On the PCB, 5 decoupling capacitors are present on VA: one 10uF, two 0.1uF, and two 10nF

7: The 2nd output of ST_IN3 is shown connected to pin 57. On the PCB, it connects to pin 60. Pins 57 & 59 are unconnected.

8: As you mentioned, the reset chip is missing.

There may be other issues, but those are the things I see from a quick comparison with the PCB layout.
 
Back
Top