Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 4 1 2 3 ... LastLast
Results 1 to 25 of 99

Thread: Teensy3 I2S with DMA

  1. #1
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46

    Teensy3 I2S with DMA

    I'm wading through the datasheet, the AN4520 application note, the AN4369 application note and its code samples...

    Lots to learn, and it seems quite a lot of work to initialize.

    Does anyone have working Teensy3 code for I2S, using a ping-pong DMA interrupt, already?

  2. #2
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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

  3. #3
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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.

  4. #4
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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.

  5. #5
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    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.

  6. #6
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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!

  7. #7
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    And... it works! Current code on github.

    Paul, will you take a big patch to mk20dx128.h for all the register flags? My version is here.

  8. #8
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    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
    */

  9. #9
    Senior Member
    Join Date
    Jan 2013
    Posts
    119
    Quote Originally Posted by hpyle View Post
    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 by loglow; 01-29-2013 at 11:58 PM.

  10. #10
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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

  11. #11
    Junior Member
    Join Date
    Jan 2013
    Posts
    13
    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

  12. #12
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    try this, I found in my old avr set but not sure if it's updated.
    Attached Files Attached Files

  13. #13
    Junior Member
    Join Date
    Jan 2013
    Posts
    13
    Thanks sumotoy.
    However, I now get this message:
    arm_math.h:257:24: fatal error: core_cm4.h: No such file or directory

  14. #14
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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

  15. #15
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    The library is updated for Teensyduino 1.12; you'll still need to copy the modified mk20dx128.h into arduino/hardware/teensy/cores/teensy3.

  16. #16
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,440
    Thanks for putting so much awesome work into this!

    I'll accept a patch for the missing definitions for Teensyduino 1.13.

  17. #17
    Junior Member
    Join Date
    Jan 2013
    Posts
    13
    Thanks much for the updates.
    However, I now get this:
    i2s.cpp:15:16: error: expected initializer before '_dma_Buffer_A'

    thanks, Sukandar

  18. #18
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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").

  19. #19
    Junior Member
    Join Date
    Jan 2013
    Posts
    13
    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

  20. #20
    Junior Member
    Join Date
    Jan 2013
    Posts
    13
    Hi again - finally got a chance to test the code in the lab.
    All looking good so far.
    Thanks much again!

  21. #21
    Senior Member
    Join Date
    Jan 2013
    Posts
    119
    Quote Originally Posted by hpyle View Post
    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).

    Quote Originally Posted by hpyle View Post
    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.

    Best, -Dan

  22. #22
    Senior Member MickMad's Avatar
    Join Date
    Feb 2013
    Location
    Italy
    Posts
    163
    hello there, I was wondering if anyone here has tried using the I2S bus for both transmitting and receiving data...

  23. #23
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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

  24. #24
    Senior Member
    Join Date
    Jan 2013
    Posts
    119
    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

  25. #25
    Member
    Join Date
    Jan 2013
    Location
    Salem, MA
    Posts
    46
    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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •