i2s for SAMG53 using PDC

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all, I'm using the SAMG53 xplained with Atmel Studio 6 and ASF 3.19. 

I have written the following code and am sure that it is doing what it is supposed to in terms of reading from my audio codec when the RXRDY flag is set, and placing the values from the RHR in the DMA buffer I have set up. Codec generates all clocks, works as expected. Definitely sending data into the MCU, and if I pause/stop over and over, it places a new value in the buffer, in the appropriate location.

 

The DMA counter register does not decrement in debug mode, which is expected. However writing a one to any bit of the SSR as stated on page 701 of the datasheet, doesn't seem to solve this. 

 

I'm hoping someone can 1)verify this code as proper, and 2) make a suggestion as to how I might be able to let the program run without having to write to the SSR, or how to write properly to the SSR (could be a polled loop for right now), in order to see the data being captured.

Although I have the xplained board, I also have the Atmel ICE. Is there a way I can run the ICE to set conditional breakpoints (like "hey there, your dma buffer is full, check it out!") so that I can then examine the contents in the ASF watch window, or do I need to get into some other form of trace?

 

Suggestions welcome and appreciated. 

 

    /***** DATA STRUCTURES *****/
    struct i2s_config i2s1_config;        //Struct for configuration
    struct i2s_dev_inst i2s1_dev_inst;    //Struct for device instance
    
    /***** CONFIGURE i2s ******/
    pmc_enable_periph_clk(ID_I2SC1);
    
    i2s1_config.data_format = I2S_DATE_16BIT;    //Bit mask for data format (16 bit)
    //i2s1_config.fs_ratio = I2S_FS_RATE_1024;    //Master clock fs*256
    i2s1_config.tx_channels = 0;                //Transmit off
    i2s1_config.rx_channels = 1;                //Receive mono
    i2s1_config.rx_dma = I2S_ONE_DMA_CHANNEL_FOR_ONE_CHANNEL; //One dma per audio channel
    i2s1_config.loopback = false;                //No loopback
    i2s1_config.master_mode = false;            //Master Mode
    i2s1_config.master_clock_enable = false;    //Generate Master Clock
    i2s1_config.slot_length_24 = false;            //Slot length set to 16/32 bits (both words?)

    i2s_init(&i2s1_dev_inst, I2SC1, &i2s1_config);
    i2s_disable(&i2s1_dev_inst);
    
    /***** ENABLE DMA TRANSFER FROM i2s1 *****/
    /***** PDC REGISTERS FOR I2S *****/
    /* Get pointer to I2S PDC register base */
    g_p_i2s1_pdc = i2s_get_pdc_base(&i2s1_dev_inst);
    /* Initialize PDC data packet for transfer */
    g_p_i2s1_pdc_packet.ul_addr = &q15dmabuffer;
    g_p_i2s1_pdc_packet.ul_size = 1024;
    
    //PERIPH_TNCR
    g_p_i2s1_pdc->PERIPH_PTCR |= PERIPH_PTCR_RXCBEN; //Turn on Circular Buffer
    /* Configure PDC for data receive */
    pdc_rx_init(g_p_i2s1_pdc, &g_p_i2s1_pdc_packet, &g_p_i2s1_pdc_packet);
    /* Enable PDC transfers */
    pdc_enable_transfer(g_p_i2s1_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTDIS);
    i2s_enable_reception(&i2s1_dev_inst);
    i2s_enable_interrupt(&i2s1_dev_inst, I2SC_SR_RXRDY);
    NVIC_SetPriority(I2SC1_IRQn, 0);
    NVIC_EnableIRQ(I2SC1_IRQn);
    
    i2s_enable(&i2s1_dev_inst);

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually scrap the ATMEL-ICE if you can, because it's back to not being recognized by Atmel Studio...when will it end?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually scrap the ATMEL-ICE if you can, because it's back to not being recognized by Atmel Studio...when will it end?

Sounds like you have a separate problem?

 

 

ICE to set conditional breakpoints

I would probably use a data breakpoint (In studio 6.2, Debug->View->Data Breakpoint) on the last element on the buffer, causing the core to break when that position is written to... Not sure that totaly covers your need, but worth a try...

 

Also, the core is a CM4 isnt it, so it does have SWO trace available on the xpro. Not really sure how to use it in your use case though...

:: Morten

 

(yes, I work for Atmel, yes, I do this in my spare time, now stop sending PMs)

 

The postings on this site are my own and do not represent Microchip’s positions, strategies, or opinions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

meolsen wrote:

Sounds like you have a separate problem?

I have the xplained board which uses EDBG, so the ICE is out of the picture, and unrelated to anything presented from this point forward.

 

meolsen wrote:

I would probably use a data breakpoint (In studio 6.2, Debug->View->Data Breakpoint) on the last element on the buffer, causing the core to break when that position is written to... Not sure that totaly covers your need, but worth a try...

It won't make it to the end of the buffer unless it's read by pollling and using RXRDY...I'll explain below. 

 

meolsen wrote:

Also, the core is a CM4 isnt it, so it does have SWO trace available on the xpro. Not really sure how to use it in your use case though...

Yes, it's CM4. That's at least a start to look in to, so thank you.

 

Now, back to the actual problem at hand and some possible discrepancies in the datasheet...

 

According to the datasheet (pg 701) : 

"An i2sC interrupt request can be triggered whenever one of several of the following bits are set.. "

RXRDY and RXOR are what we'll focus on since I'm only receiving. 

-IMR bits are set by writing a one in IER

-The request remains active until a 1 is written the status clear (SCR)

- Reading RHR is supposed to clear this bit, however it does not. I have proven this using the i2s_read function in a polled loop, and by running interrupt driven callback using RXOR, and manually clearing the SR_RXOR within the interrupt. If you don't manually clear the SR, you end up with an infinite loop that does not allow any other code to execute because it constantly enters the interrupt. In both the loop and interrupt case, you can see by how the buffer fills up (chunks of the same sample over and over for 20-30 samples) and that a block which should take 30ms to fill, happens in less than 2 ms. 

 

In short SCR does not do anything after reading RHR, which goes against the statement on pg 700 : "RXRDY in I2SC_SR is set. Reading I2SC_RHR clears this bit"

 

- The real killer here is that there IS NO CLEAR FOR RXRDY IN THE SCR??? So, how can the status be cleared short of disabling and re-enabling the interrupt? 

If that's a bit used to trigger an interrupt request and you can't clear it, and it doesn't clear itself, what is the point of it? 

 

If anyone can shed some light on this, I'm very curious.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This sounds a lot like you have hit this errata:

 

I2SC
I2SC: Mono Mode is not working
When I2S is set in Mono mode (RXMONO), the status flag RXRDY (in I2S_SR) is stuck at 1.
Problem Fix/Workaround

  •  Set the Stereo mode (RXMONO = 0) and use only one way.
  • Use the PDC (Peripheral DMA Controller) in Mono mode.

 

(http://www.atmel.com/images/atme... - Errata chapter)

 

 

Last Edited: Tue. Oct 21, 2014 - 10:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Pretty sure that Errata is new. I looked at it earlier and it didn't appear in the revision history and now the link for the datasheet is dead. Makes sense though as to why it was failing - even in polling mode. There also appears to be an issue with the PDC and circular buffering so the suggested workaround isn't really that good. 

 

When I run in stereo RX, using two PDC channels and circular buffer, if I use a stereo signal as input, the L and R bounce back and forth on opposite channels every time the counter resets. 

When I run in stereo but only send a mono signal (L), every other buffer fills with zeros, then information - L goes to buffer A and B fills with 0's, then L goes to buffer B and A fills with 0's.

To me, this is not circular buffering - this automatic ping-pong which would be fine for my double buffering application except the whole filling with the previous buffer with zeros while filling the current buffer....

 

Seems like there may be more than one issue that needs to be explored.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Pretty sure that Errata is new. I looked at it earlier and it didn't appear in the revision history and now the link for the datasheet is dead. Makes sense though as to why it was failing - even in polling mode. There also appears to be an issue with the PDC and circular buffering so the suggested workaround isn't really that good. 

 

When I run in stereo RX, using two PDC channels and circular buffer, if I use a stereo signal as input, the L and R bounce back and forth on opposite channels every time the counter resets. 

When I run in stereo but only send a mono signal (L), every other buffer fills with zeros, then information - L goes to buffer A and B fills with 0's, then L goes to buffer B and A fills with 0's.

To me, this is not circular buffering - this automatic ping-pong which would be fine for my double buffering application except the whole filling with the previous buffer with zeros while filling the current buffer....

 

Seems like there may be more than one issue that needs to be explored.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Data sheet is back up with a slew of edits attributed to 10-17-2014 in Revision History.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Did you get anywhere with I2S? I'm having problems with making PDC work, it seems to never start the transmission, but status bits indicate that it is already done.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I2S IP block does not work in Mono with polling mode - an errata was published regarding this after I provided Atmel with software examples (as I imagine others must have as well). With PDC in Mono mode, I was getting a buffer of zeros, followed by a buffer of real values. Their last reply was that it supposedly works properly in stereo, however they provided words, not a working example - the same as when they told me that it "should work in mono"... I personally think their I2S IP block has deeper issues than they are willing to currently admit, as that IP block (according to a rep I spoke with) is the same in the brand new SAMD's as well.

 

I saw they just released a new version of ASF - maybe there is an I2S example for G53 specifically?

If you post your initialization code I'll look at it to see if I see anything out of the ordinary. You should be getting numbers at least some of the time according to what I had going.

What environment and tool are you using?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I also switched MCU's...unfortunately.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm generating the sount and TX channel seem to be unaffected by this errata. In any case I can get it to work in polled mode. I'm using direct register access.

 

Here is relevant part of the code:

#define SOUND_BUF_SIZE    512

static int16_t sound_buf[SOUND_BUF_SIZE];

static void i2s_test(void)
{
  PIOA->PIO_PDR = PIN_SCK | PIN_WS | PIN_SDO | PIN_MCK;

  PMC->PMC_PCER0 =  (1 << ID_I2SC0);

  I2SC0->I2SC_CR = I2SC_CR_CKEN | I2SC_CR_TXEN;
  I2SC0->I2SC_MR = I2SC_MR_MODE_MASTER | I2SC_MR_DATALENGTH_16_BITS | I2SC_MR_TXMONO |
      I2SC_MR_IMCKMODE | I2SC_MR_IMCKFS_M2SF1024 | I2SC_MR_IMCKDIV(3);

  for (int i = 0; i < SOUND_BUF_SIZE; i++)
    sound_buf[i] = i;

  while (1)
  {
/* -- this is polled mode and it works perfectly fine
    for (int i = 0; i < SOUND_BUF_SIZE; i++)
    {
      while (0 == (I2SC0->I2SC_SR & I2SC_SR_TXRDY));
      I2SC0->I2SC_THR = sound_buf[i];
    }
*/

    PDC_I2SC0->PERIPH_TPR = (uint32_t)sound_buf;
    PDC_I2SC0->PERIPH_TCR = SOUND_BUF_SIZE;
    PDC_I2SC0->PERIPH_PTCR = PERIPH_PTCR_TXTEN;

    while (0 == (I2SC0->I2SC_SR & I2SC_SR_ENDTX)); // Technically one of these should block, but they do not 
    while (0 == (I2SC0->I2SC_SR & I2SC_SR_TXBUFE)); // and PDC_I2SC0->PERIPH_TCR does not change
  }
}

So it looks like PDC does not even try to start. And there are no more bits to try :)

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What I'm saying on the errata, is that I don't know that they have truly worked out everything that is wrong with the I2S block, because they were sort of stubborn to admit the first errata. 

 

According to the data sheet: 

30.5.4 Peripheral DMA Controller
The I2SC interfaces to the Peripheral DMA Controller (PDC). Using the I2SC DMA functionality requires the PDC to be programmed first.

 

The only difference I see between your code, and code I got to at least get some numbers flowing, was that I did not enable reception or clocks until I already had the DMA controller configured. So, try setting I2SC_CR_TXEN, after configuring the DMA.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Yeah, I've read that. I don't really understand what it means, there is no PDC programming per se, all the registers in PDC have to be updated in run-time. In any case, it does not seem to help.

 

I'll keep bothering support about this.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alexru wrote:

... there is no PDC programming per se, all the registers in PDC have to be updated in run-time...

 

I have to say this isn't really true; I've used the PDC with I2C, SPI, etc on this device...and every case you have to set up at least certain basic parameters in the peripherals PDC related registers prior to turning the peripheral on - the registers like pointer counters, should update themselves once TX is initiated, and sure, you may change a pointer to a tx buffer or length but PDC must be enabled properly prior to using it. You don't appear to be doing that.

 

In something like circular buffering mode, which I would guess you would be using if you are creating an actual audio application, you should be able to set the PDC in motion and your have code that is while(1){} with absolutely no user intervention. The only thing you may need to set are interrupts at TXEND, in order to fill your audio buffer with new samples to send out from a different pointer, or some sound generation algorithm going on in while(1)...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

So you are suggesting that peripheral should be disabled entirely before each transfer request to PDC? This does not make any sense to me. And I have tried it both ways, it does not work.

 

For a final application my plan was to have main loop do the sound synthesis work, wait in a busy-wait loop until next PDC channel is available, setup the PDC transfer using the next channel pointer. This will ensure nice pipeline flow.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Also, enabling and disabling the peripheral will screw up clock timings, which is not great for audio applications.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should be using circular buffering or a callback then. You have neither of these established in your code.

 

Using ASF, I got an alternating buffer of zeros for rx using the following functions; you may may want to examine the bitsets/clears in these and match your code to them, but you absolutely must have the PDC programmed before starting the I2S. You can change values of the PDC like the tx pointer and address length without turning the peripheral off, but you must set the bits for initialization in the order outlined by the datasheet and you are totally ignoring that, which is why it isn't starting.

 

i2s_init(&dev_inst, I2SC0, &config); 

i2s_enable(&dev_inst);

p_i2sc_pdc = i2s_get_pdc_base(&dev_inst);

pdc_i2sc_packet_rx.ul_addr = (uint32_t) input_samples_left;
pdc_i2sc_packet_rx.ul_size = SOUND_SAMPLES;

pdc_rx_init(p_i2sc_pdc, &pdc_i2sc_packet_rx, &pdc_i2sc_packet_rx);
p_i2sc_pdc->PERIPH_PTCR |= PERIPH_PTCR_RXCBEN;

pdc_enable_transfer(p_i2sc_pdc, PERIPH_PTCR_RXTEN );

i2s_enable_reception(&dev_inst);
i2s_enable_clocks(&dev_inst);

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Atmel said the buffer was zeros every other time because I hadn't established an interrupt on RXBUFF - so there may be hope for you that the peripheral works completely as described.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here are the files they provided as reference. You should be able to get it working from these :  

 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Here is updated code, still does not work. Counters still just stay there as written at all times.

 

  PMC->PMC_PCER0 =  (1 << ID_I2SC0);

  I2SC0->I2SC_MR = I2SC_MR_MODE_MASTER | I2SC_MR_DATALENGTH_16_BITS |
      I2SC_MR_IMCKMODE | I2SC_MR_IMCKFS_M2SF1024 | I2SC_MR_IMCKDIV(3);

  I2SC0->I2SC_TPR = (uint32_t)sound_buf_0;
  I2SC0->I2SC_TCR = SOUND_BUF_SIZE;
  I2SC0->I2SC_TNPR = (uint32_t)sound_buf_0;
  I2SC0->I2SC_TNCR = SOUND_BUF_SIZE;
  I2SC0->I2SC_PTCR = I2SC_PTCR_TXTEN | I2SC_PTCR_TXCBEN;

  I2SC0->I2SC_CR = I2SC_CR_TXEN | I2SC_CR_CKEN;

I'm also not entirely sure that circular buffer mode is what I want, but at the moment I want to see anything on the output.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Those examples are for RX (slave mode), which you were interested in, of course. I wounder if PDC TX is somehow broken. I don't have enough willpower right now to experiment with RX.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.