Using new I2S Driver

ChibiOS public support forum for topics related to the STMicroelectronics STM32 family of micro-controllers.

Moderator: RoccoMarco

chiVelle
Posts: 21
Joined: Sun Aug 25, 2013 4:16 pm

Using new I2S Driver

Post by chiVelle »

I have been working with the new I2S driver checked into the 3.0 branch, and so far have been receiving audio from an external wolfson codec. What I am trying to do is write the data coming in from the wolfson to a file with FatFS using the I2scallback function that is placed into the i2sconfig object. Unfortunately, a lot of zeros show up in the file that it is being written. The callback is an I-lock zone from what I can see, so calling f_write() directly in the callback is not available correct? Is there a way to get around this? If not, is there a standard procedure on what to do in that callback to get the data to a file? Also, would enabling double buffering with the DMA be a challenge so that two independent buffers could be used?

Thanks for the help.
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Using new I2S Driver

Post by Giovanni »

Callbacks are always invoked from ISRs so only i-class functions there.

You need a buffers system between the callback and a consumer thread. While the the callback fills one buffer the thread is writing the other one.

This assumes that the thread is able to write data faster than the I2S receives, if speeds are close you may need extra buffers.

You may use a pool of free buffers (min 2) and a mailbox between callback and thread.

Giovanni
chiVelle
Posts: 21
Joined: Sun Aug 25, 2013 4:16 pm

Re: Using new I2S Driver

Post by chiVelle »

Thanks, that makes sense.
Lemming
Posts: 6
Joined: Mon Apr 07, 2014 2:53 pm

Re: Using new I2S Driver

Post by Lemming »

I've been using the new driver as well, and while copying each half of a single circular buffer into two intermediary buffers for writing to the file, these are the results I've been seeing. I attempted to use memory pools, but they produced the same results. The one thing I did notice was that addressing i2sDriver->config->rx_buffer actually provided more information (IE: smaller gaps) than addressing the buffer i2sDriver->config->rx_buffer points to. I don't think this is an issue of not being able to write to the file fast enough, since using larger buffers produced the same result, albeit with larger chunks of data and silence.

By the way, this was a 5-second sweep from 250Hz to 2kHz. The file recorded a 10-second sweep. I'm assuming this is due to the gaps and not an issue with PLL setup, since the data looks like it should be good once the gaps are removed. It appears that every time we receive a transfer complete interrupt, the buffer is empty. However, we're getting data at the half transfer interrupt, though it is out of order. What could cause this?

sweep.jpg
sweep.jpg (258.18 KiB) Viewed 5990 times
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Using new I2S Driver

Post by Giovanni »

A bug could cause it :) the driver is experimental and missing a good test. Is the driver providing the data fast enough or it has gaps by itself?

Giovanni
Lemming
Posts: 6
Joined: Mon Apr 07, 2014 2:53 pm

Re: Using new I2S Driver

Post by Lemming »

Since the driver is firing both half transfer and transfer complete interrupts, I assume it thinks it has filled the buffer. It just seems like the offset into the buffer is incorrect, or perhaps it's not circling properly.

Could you elaborate more on what you mean by the driver not being fast enough? Even when I just address the rx_buffer itself, I see the final portion of the buffer is populated with zeros. It does appear to be writing new data, so I don't think it's an issue of the driver not supplying data fast enough. It just isn't filling the buffer.
Lemming
Posts: 6
Joined: Mon Apr 07, 2014 2:53 pm

Re: Using new I2S Driver

Post by Lemming »

Here's my I2SConfig and the receiver code. I've set the PLLI2SN and PLLI2SR values to 192 and 2, as recommended by the reference manual for 8kHz, 16-bit audio.

Code: Select all

#define I2S_BUF_SIZE 4096
#define I2S_HALF_BUF I2S_BUF_SIZE/2

int16_t i2s_rx_buf[I2S_BUF_SIZE] __attribute__((aligned(sizeof(stkalign_t)))); // alignment added for memory pool use

static const I2SConfig i2scfg =
{
    NULL,           // tx buffer
    i2s_rx_buf,     // rx buffer
    I2S_BUF_SIZE,   // size of the buffers
    i2sReceive,     // streaming data callback
    0x000,          // I2SCFGR register setup (I2S master/receive, I2S output, I2S mode are defaults set by the driver)
    0x01BB,         // I2SPR register setup (odd, divider = 187)
};

Code: Select all

unsigned int g_n1, g_n2; // used to maintain proper byte sizes for external buffers
int16_t i2s_store1[I2S_HALF_BUF] __attribute__((aligned(sizeof(stkalign_t))));
Int16_t i2s_store2[I2S_HALF_BUF] __attribute__((aligned(sizeof(stkalign_t))));

void i2sReceive(I2SDriver *i2sp, size_t offset, size_t n)
{
    (void)i2sp;
    (void)offset;
    (void)n;

    chSysLockFromIsr();
    if(offset == 0)
    {
        g_n1 = n*2;
        memcpy(i2s_store1, i2s_rx_buf, g_n1);
        chEvtSignalI(p_audioThread, EVENT_MASK(HALF_EVENT));
        // HALF TRANSFER COMPLETE
    }
    else
    {
        g_n2 = n*2;
        memcpy(i2s_store2, i2s_rx_buf + offset, g_n2);
        chEvtSignalI(p_audioThread, EVENT_MASK(FULL_EVENT));
        // TRANSFER COMPLETE
    }
    chSysUnlockFromIsr();
}

The audio thread waits for the half and full events, then uses f_write to write the appropriate i2s_storeX buffer to the SD card. The driver is consistently providing half and full events in alternating order.
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Using new I2S Driver

Post by Giovanni »

Hi,

Not sure, I don't understand the problem entirely. Could you try and confirm if the driver fills the whole circular buffer correctly independently from the card writing part? We need to understand if it is a problem with the driver itself or not.

About the buffering, instead of copying that data and then sending the event, you could as well just send the event and write directly from the I2S buffer, copying the data that way does not help decoupling the two processes IMHO.

Giovanni
Lemming
Posts: 6
Joined: Mon Apr 07, 2014 2:53 pm

Re: Using new I2S Driver

Post by Lemming »

Alright, I've updated the callback so it doesn't copy anything and the thread addresses the buffer directly. Having seeded the buffer with a recognizable pattern, you can see that the buffer isn't being completely filled. There is some weirdness though, in that depending how I address the buffer, I'm seeing two different results.

With the following code, I'm never seeing any data in the second half.

Code: Select all

__attribute__((noreturn))
msg_t audioThread(void *arg)
{
    (void)arg;
    EventListener el;
    EventSource es;
    chEvtRegisterMask(&es, &el, EVENT_MASK(FULL_EVENT) | EVENT_MASK(HALF_EVENT);
   
    int16_t pattern[I2S_HALF_BUF], *i2sPtr;
    memset(pattern, 0x77, I2S_HALF_BUF*sizeof(int16_t));
    memset(i2s_rx_buf, 0x77, I2S_BUF_SIZE*sizeof(int16_t));

    i2sStart(&I2SD2, &i2scfg);
    if(I2SD2.state == I2S_READY)
    {
        printConsole("starting i2s\n");
        i2sStartExchange(&I2SD2);
    }

    while(TRUE)
    {
        eventmask_t mask = chEvtWaitAny(ALL_EVENTS);

        if(mask & EVENT_MASK(HALF_EVENT))
        {
            i2sPtr = i2s_rx_buf;
            if(i2sPtr != NULL)
            {
                if(memcmp(i2sPtr, pattern, I2S_BUF_SIZE) == 0)
                { printConsole("FIRST HALF NOT FILLED\n"); }
            }
        }
        if(mask & EVENT_MASK(FULL_EVENT))
        {
            i2sPtr = i2s_rx_buf + I2S_HALF_BUF;
            if(i2sPtr != NULL)
            {
                if(memcmp(i2sPtr, pattern, I2S_BUF_SIZE) == 0)
                { printConsole("SECOND HALF NOT FILLED\n"); }
            }
        }
        chEvtGetAndClearEvents(mask);
    }
    chThdExit(1);
}

However, if I change "i2s_rx_buf" to "I2SD2.config->rx_buffer", then the first 3/4 of the buffer is filled. The last quarter still shows the 0x77 pattern.
User avatar
Giovanni
Site Admin
Posts: 14892
Joined: Wed May 27, 2009 8:48 am
Has thanked: 1202 times
Been thanked: 996 times

Re: Using new I2S Driver

Post by Giovanni »

I don't understand the buffer thing, is the address of the pointer the same base address of the buffer?

Anyway, I will give the driver a try when I can but I will be out for travel the whole next week.

Giovanni
Post Reply