Teensy3 I2S with DMA

Status
Not open for further replies.
Beware the AN4369 code sample - it's for the K60 chip, and all the registers are different.


I've put together most of the headers for Transmit, and some code that "should work", but doesn't. The driving sketch isn't talking to a codec or anything. It's just trying to initialize. It doesn't crash... but doesn't call back when the buffer is empty. So far I can't tell what's wrong at all. But if anyone else wants to dig in, feel free!

Most of i2s.h should end up in the mk20dx128.h eventually. For now, I also patched a few of the DMA defs in mk20dx128.h.

https://github.com/hughpyle/teensy-i2s
 
I2C to the Wolfson codec is working (using the Mikro proto board). I2S is **partially** working. The current version is using the FIFO directly -- DMA not working yet. Internally-generated bit clock and frame clock signals are looking good on the oscilloscope. Sync to external should be OK too, although I'm testing with internal clocks.

https://github.com/hughpyle/teensy-i2s

I could use some help with the next steps, if anyone out there is minded to!

Even though the code is pushing data into the TX register when the FIFO needs data, and it appears to be transmitting (because the FIFO keeps calling back to say it wants more), I don't see any signal on the data line. I've tried data on pin 3 (PTA12) as well as 22 (PTC1) with no effect. I'm stumped. :(
 
Moderate success!! Codec-as-slave sounds very distorted. Codec-as-master sounds pure but fades out immediately. Nevertheless, it's doing *something* end-to-end and producing sound from the headphones. Code is checked in on github.
 
WOW ;) Just found out this! Extremely interesting! I've worked a lot with many wolfson chip (expecially the Wm8805 and it's damn incomplete readout protocol) and found that it's possible communicate in I2S with Tennsy3 it's like a dream to me. I've succesfully communicate via I2C and SPI (I prefere SPI because it's higher freq intefrere less with audio stuff I have around this chip) with WM8805 and DACs, I can share some code if you need.
 
That's great! I'd like to see code to drive the WM8805 -- I have no experience with it, but I do have a SRC4382 waiting for a project that needs S/PDIF (it may be possible to build a Teensy-powered digital crossover).

My first project with I2S is just the transmit side, to make a small MIDI synth. The code shows signs of getting close, but debugging "why does that sound like static" is taking a while!
 
Really great work! I've ordered a chip to test it! Here's some code to work with WM8805 via SPI, if you need help with circuit I can post it.

Code:
/*
Wolfson WM8805 Digital Receiver SPI routines, extract
coded by sumotoy

NOTES:
WM8805 it's actually one of the best digital audio receiver chip around
but it's far to be perfect, for example it's not easy to get exaustive
infos about it's current working audio link connection. For example I cannot find a way
to determine if it's linked to 48K or 44.1K since the 2 freq are identified
with the same register out. To determine if chip it's linked or not to an audio stream, best
way to me it's check the GPO1 pin of WM chip and only then send queries to it
for read registers and check the quality and all infos.
WM8805 can communicate via I2C or SPI, the 2 ways are slight different (check
datasheet) and need slight different pin setup.
The following routines are for SPI and I checked and are working on
a ATMEGA1284P (arduino ide 1.0.3) If you need help with WM8805 circuit
I can post my setup for SPI and I2C.
*/

/* ----------------------WM8805 ------------------*/
#define WMRST  0x00
#define DEVID2  0x01
#define DEVREV  0x02
#define PLL1  0x03
#define PLL2  0x04
#define PLL3  0x05
#define PLL4  0x06
#define PLL5  0x07
#define PLL6  0x08
#define SPDMODE  0x09
#define INTMASK  0x0A
#define INTSTAT  0x0B
#define SPDSTAT  0x0C
#define RXCHAN1  0x0D
#define RXCHAN2  0x0E
#define RXCHAN3  0x0F
#define RXCHAN4  0x10
#define RXCHAN5  0x11
#define SDPTX1  0x12
#define SDPTX2  0x13
#define SDPTX3  0x14
#define SDPTX4  0x15
#define SDPTX5  0x16
#define GPO01  0x17
#define GPO23  0x18
#define GPO45  0x19
#define GPO67  0x1A
#define AIFTX  0x1B
#define AIFRX  0x1C
#define SPDXR1  0x1D
#define PWRDN  0x1E
//related to cpu
#define WMCS 10 //cs pin cpu side

byte wm_PLL6 = 0b00011000;
byte wm_PWRDN = 0b00000000;




//powerup chip
void WMPowerUp(){
  WMwrite(PWRDN,wm_PWRDN);
}

//enable/disable I2S interface
void WMI2SInterface(byte state){
  if (state){
    WMwrite(PWRDN,0x00);//power up I2S interface on WM8805
  } 
  else {
    WMwrite(PWRDN,0x10);//power down I2S interface on WM8805
  }
}

//write to wm8805
void WMwrite(byte reg, byte data){
  digitalWrite(WMCS,LOW);
  //Bit 7 t 0
  SPI.transfer(reg &= ~(1<<7));
  SPI.transfer(data);
  // stop transmitting
  digitalWrite(WMCS, HIGH);
}

//read from wm8805
int WMread(byte reg){
  digitalWrite(WMCS,LOW);
  //bit 7 to 1
  SPI.transfer(reg |= (1<<7));
  int _data = SPI.transfer(0x0);
  digitalWrite(WMCS,HIGH);
  return _data;
}


//select source (I know this is the worst way
//to do but easy to understand)
void WMsource(byte src)
{
  switch(src){
  case 0://000
    bitClear(wm_PLL6,0);
    bitClear(wm_PLL6,1);
    bitClear(wm_PLL6,2);
    break;
  case 1://001
    bitSet(wm_PLL6,0);
    bitClear(wm_PLL6,1);
    bitClear(wm_PLL6,2);
    break;
  case 2://010
    bitClear(wm_PLL6,0);
    bitSet(wm_PLL6,1);
    bitClear(wm_PLL6,2);
    break;
  case 3://011
    bitSet(wm_PLL6,0);
    bitSet(wm_PLL6,1);
    bitClear(wm_PLL6,2);
    break;
  case 4://100
    bitClear(wm_PLL6,0);
    bitClear(wm_PLL6,1);
    bitSet(wm_PLL6,2);
    break;
  case 5://101
    bitSet(wm_PLL6,0);
    bitClear(wm_PLL6,1);
    bitSet(wm_PLL6,2);
    break;
  case 6://110
    bitClear(wm_PLL6,0);
    bitSet(wm_PLL6,1);
    bitSet(wm_PLL6,2);
    break;
  case 7://111
    bitSet(wm_PLL6,0);
    bitSet(wm_PLL6,1);
    bitSet(wm_PLL6,2);
    break;
  }
  WMwrite(PLL6,wm_PLL6);
}

/*
Example:
  WMwrite(WMRST,0x00);//reset
  WMwrite(SPDXR1,B1000011);//reset
  //wait a little, wm it's slow to initialize
  WMwrite(SPDMODE,0xF4);//SPDIF modes (coax/TTL)
  WMwrite(SDPTX1,0x04);//SPDIF TX copyright = no
  WMwrite(SDPTX4,0x31);//SPDIF TX source
  WMwrite(GPO01,0x7A);//GPO Config - GPO0 = DEEMPH; GPO1 = UNLOCK// NEW!
  WMwrite(AIFTX,0x0A);//AIF TX = 24-bit I2S
  WMwrite(AIFRX,0x4A);//AIF = master mode
  WMsource(1);//select input 1
  WMwrite(PWRDN,wm_PWRDN);
  
  //check if chip it's connected
  if (WMread(DEVID2) == 0x88) it's connected and working
*/
 
And... it works!

This is fantastic, as I'm using the Teensy3 and require I2S support. I've been working on other aspects of the project (including getting setup with the AIC3204 codec that I'm using), while eagerly awaiting an I2S driver. Using your modified header and referencing your example code, I've at the very least gotten what appears to be a suitable 12.288 MHz clock to drive the codec with. I've also experimented with reassigning Teensy3 pins to make use of their alternate I2S functions. I still have a ridiculous amount to learn though.

Anyway, please let me know if there's anything I can do to assist/expedite the development of solid I2S drivers, even if that means just testing them.

Thanks again, -Dan
 
Last edited:
That's great! I hope you find the library useful. If you're rolling your own, the main trick is to not set the "transmit enable" flag until everything else is set up ;-)
You may find that my clock setup doesn't work for your application. It was done pretty much by trial and error with the Wolfson codec and a cheap single-channel oscilloscope... the only reliable way I could run a data stream was to set up as 32-bit data. YMMV.

Do feel free to fork my library/example code. I'll be working on this very much part-time -- I expect my first project could take months, no particular rush -- so even if you send me pull requests with a big refactoring of the library that's just fine. Especially to add Receive functions (I'm only using Transmit right now).

I'd love to hear more baout the sort of projects you're building, too! Mine, in sequence I think:
- A tiny midi synth (additive synthesis, with a decent range of stringlike & drumlike sounds, but polyphony will be limited by how efficient I can code)
- A high-as-possible-quality S/PDIF digital crossover
- Head-tracking binaural something or other, maybe...

Hugh
 
Hi, thanks for posting this. I've just started using the new Teensy 3.0 and now that I covered the basic stuff, I too would like to get I2S working. However, your I2S example doesn't compile for me. I get this error message:
i2stestB.ino:4:22: fatal error: arm_math.h: No such file or directory
I guess my Teensy 3.0 installation isn't quite right. I've installed beta12 in combination with Arduino 1.0.3
Also, i2s.h can't be found even though it's in the standard folder for libraries.
Thanks,
Sukandar
 
try this, I found in my old avr set but not sure if it's updated.
 

Attachments

  • arm_math.zip
    27.1 KB · Views: 301
Thanks sumotoy.
However, I now get this message:
arm_math.h:257:24: fatal error: core_cm4.h: No such file or directory
 
Um, yes, sorry about that. There was really no need for the dependency on arm_math.h, and it's not part of the standard 1.0.3 Arduino (instead it's part of CMSIS).
Try check out from the github; I've refactored the example to not require arm_math, and also reworked the I2S library into C++ to make it easier to use. Let me know if you can run the example now.
-Hugh
 
The library is updated for Teensyduino 1.12; you'll still need to copy the modified mk20dx128.h into arduino/hardware/teensy/cores/teensy3.
 
Thanks for putting so much awesome work into this!

I'll accept a patch for the missing definitions for Teensyduino 1.13.
 
Thanks much for the updates.
However, I now get this:
i2s.cpp:15:16: error: expected initializer before '_dma_Buffer_A'

thanks, Sukandar
 
Are you using the Teensyduino 1.12 release? The latest I2S library code requires it (this morning I added the "DMAMEM" symbol to its DMA buffers, which means "memory optimized for DMA transfers").
 
silly me
I just saw "12" and thought it's the beta12 I had just installed.
With Teensyduino 1.12 it compiles indeed.
Tomorrow I'll check if it also works - I'm not in the lab right now.

thanks a bundle,
Sukandar
 
That's great! I hope you find the library useful.

So far as a reference, it's been very useful. After a fair amount of trial and error, I've got receive working! Using 48 kHz / 32-bit / 12.288 MHz MCLK. I haven't broken it out as a library yet, but I thought I'd share some of my code here anyway. I should note that I'm using your updated mk20dx128.h file. I've also moved and removed some of the code to make it suitable (aka clearer and more concise) for sharing on this forum.

First, I'm using the ALT4 pin settings for I2S (with wires soldered to the bottom pads) enabled with the pinAlt() macro:

Code:
#define I2S_DIN_PIN   13  // I2S data in
#define I2S_BCLK_PIN  27  // I2S bit clock
#define I2S_MCLK_PIN  28  // I2S master clock
#define I2S_WCLK_PIN  29  // I2S word clock

void setup() {

  pinAlt(I2S_MCLK_PIN, 4);
  pinAlt(I2S_BCLK_PIN, 4);
  pinAlt(I2S_WCLK_PIN, 4);
  pinAlt(I2S_DIN_PIN,  4);

}

Then the initialization code:

Code:
#define AUDIO_NUM_CHANS  2
#define AUDIO_BIT_DEPTH  32

void init_I2S() {
  
  uint8_t nChans = AUDIO_NUM_CHANS - 1;
  uint8_t nBits  = AUDIO_BIT_DEPTH - 1;

  SIM_SCGC6 |= SIM_SCGC6_I2S;          // enable clock to the I2S module
  I2S0_MDR  |= I2S_MDR_FRACT(15);      // output = input * (FRACT + 1) / (DIVIDE + 1)
  I2S0_MDR  |= I2S_MDR_DIVIDE(124);    // 12.288 MHz = 96 MHz * (16 / 125)
  I2S0_MCR  |= I2S_MCR_MOE;            // enable MCLK pin as output
  // --------------------------------------------------------------------------------
  I2S0_RCR1 |= I2S_RCR1_RFW(nChans);   // set FIFO watermark
  // --------------------------------------------------------------------------------
  I2S0_RCR2 |= I2S_RCR2_MSEL(1);       // use MCLK as BCLK source
  I2S0_RCR2 |= I2S_RCR2_SYNC(0);       // use asynchronous mode
  I2S0_RCR2 |= I2S_RCR2_DIV(1);        // (DIV + 1) * 2, 12.288 MHz / 4 = 3.072 MHz
  I2S0_RCR2 |= I2S_RCR2_BCD;           // generate BCLK, master mode
  I2S0_RCR2 |= I2S_TCR2_BCP;           // BCLK is active low
  // --------------------------------------------------------------------------------
  I2S0_RCR3 |= I2S_RCR3_RCE;           // enable receive channel
  // --------------------------------------------------------------------------------
  I2S0_RCR4 |= I2S_RCR4_FRSZ(nChans);  // frame size in words
  I2S0_RCR4 |= I2S_RCR4_SYWD(nBits);   // bit width of WCLK
  I2S0_RCR4 |= I2S_RCR4_MF;            // MSB (most significant bit) first
  I2S0_RCR4 |= I2S_RCR4_FSD;           // generate WCLK, master mode
  I2S0_RCR4 |= I2S_RCR4_FSE;           // extra bit before frame starts
  // --------------------------------------------------------------------------------
  I2S0_RCR5 |= I2S_RCR5_W0W(nBits);    // bits per word, first frame
  I2S0_RCR5 |= I2S_RCR5_WNW(nBits);    // bits per word, nth frame
  I2S0_RCR5 |= I2S_RCR5_FBT(nBits);    // index shifted for FIFO
  // --------------------------------------------------------------------------------
  I2S0_RCSR |= I2S_RCSR_BCE;           // enable the BCLK output
  I2S0_RCSR |= I2S_RCSR_RE;            // enable receive globally
  I2S0_RCSR |= I2S_RCSR_FRIE;          // enable FIFO request interrupt

}

Then elsewhere, when I want to start receiving data, I call:

Code:
NVIC_ENABLE_IRQ(IRQ_I2S0_RX);

And when I want to stop:

Code:
NVIC_DISABLE_IRQ(IRQ_I2S0_RX);

Finally there's the I2S receiver ISR. I'm doing more than just this, like moving the data into various buffers, but that's somewhat outside the scope of just I2S. The strangest (and most annoying) thing to figure out was the extra "dummy" read from the data register. While this seems to be working, I'm not entirely sure it's correct, since this is all still very new to me.

Code:
uint32_t audio_ch0;
uint32_t audio_ch1;

void i2s0_rx_isr() {
  
  // read data from FIFO list
  audio_ch0 = I2S0_RDR0; // read channel 0
  audio_ch1 = I2S0_RDR0; // dummy read
  audio_ch1 = I2S0_RDR0; // read channel 1
  
   // reset the FIFO
  I2S0_RCSR |= I2S_RCSR_FR;

}

I briefly attempted to integrate some of this into your library on GitHub, but ended up not wanting to mess with the way you've structured things too much. That, and I haven't touched DMA once in my life (yet).

I'd love to hear more baout the sort of projects you're building, too!

I'm working on an audio-recording project of sorts. I'll have more to share about it in the near future!

Your "head-tracking binaural something or other" sounds wicked interesting, if not somewhat mysterious. :p

Best, -Dan
 
hello there, I was wondering if anyone here has tried using the I2S bus for both transmitting and receiving data...
 
MickMad: I don't think anyone has done both transmit and receive yet.

Dan: I've refactored my library to an interface that should work for both transmit and receive; and I've included your setup code in there. I tested transmit-with-DMA to make sure I didn't break anything in my own project, but didn't test receive at all. Can you take a look and see whether it works? I'm interested in the shape of the I2S object as much as the implementation. e.g. does the "pin pattern" stuff make sense?

There are several things that aren't configurable yet, such as bit-depth. Are you actually interested in all 32-bit data? (Even if you're not, I have some hifi projects that would want 24-bit). Any suggestions how to provide a callback API like the DMA one (which hands you a buffer of int16_t right now and says "fill this" or "read from this") but handling different bit-depth usages?

-Hugh
 
hey Hugh, just noticed your update, and I'm gonna check it out now. I see you found my basic upload at https://github.com/loglow/I2Sound -- I'm excited to get something working w/ both TX and RX, since I'm going to need TX before long too. I'll reply here again after I've checked it out...

Oh also, I have a request... can you restructure the repo so the top level is the contents of the library? Then I can clone it directly into my arduino lib folder. Unless there's an easy way to clone just a subdir with git?

Also noticed you're not very far from me! I'm in Northampton, MA :) -Dan
 
Thanks. I saw your I2Sound library and picked up some good things from it! It's nice and clean.

I'll try that directory restructure. Gotta learn how to use git properly :)

Hugh
 
Status
Not open for further replies.
Back
Top