This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC26xx I2S interface

Other Parts Discussed in Thread: CC2541, CC2640, CC3200, SYSBIOS, CC2650RC, CC2650STK

Hi!

I have an application that I'm porting from a CC2541 to a CC2640.    The application uses an I2S interface.   The original interface was built using an external FPGA containing the I2S module (that I designed).

One reason (among many) for switching to the CC2640 was that it had an internal I2S interface.     I see the "driver" code in cc26ware_2_21_01_15600, but not too much documentation beyond the little in i2s.h     The comments in i2s.h are similar (probably due to the use of Doxygen to create the documentation?) to the "online" documentation.

Is there a more detailed description of the driver that explains the "big" picture, including some timing diagrams that define what some of the variables mean?

Is there a description of how the i2s driver interfaces to the DMA?     

Is there an example showing the use of the I2S driver and the DMA interface?

I have some general ideas of what I think TI is trying to do, but some documentation (it may exist, but I haven't found it!) on the general philosophy (overview) of what is supposed to happen would be helpful.    I'd rather understand things now, while writing the code, than guess and try to debug with a logic analyzer later!

Thanks!!!

  • Hi Ed,
    I do not believe we have any I2S examples. Is there something specific you are trying to implement with I2S? You will have to use the documentation as a guide:
    C:\TI\tirtos_simplelink_2_13_00_06\docs\doxygen\html\_i2_s_8h.html

    Best wishes
  • FYI, the I2S driver in TI-RTOS currently is only for CC3200. Implementation of I2S driver for CC26xx is still on going.
  • Oh no!!!  I have committed to using this in the next release...   Which is due to be sent out for prototype runs in about 2 weeks!!

    When can I expect that a driver for 26xx will be available (and an example)?     How different are the I2S hardware blocks in the CC3200 and the CC2640?

    Help!!!

    Thanks...

  • Can you comment on the status of the "I2S driver" in the CC26xxWare library? Is it valid for CC26xx?
  • as stated, the I2S in the TI-RTOS is only for CC3200, and unfortunately I don't have timeline for the I2S driver.

    There will be a PDM driver using I2S hardware for CC2640 by the end of this year.
  • Thanks...

    Can you comment on the difference between the I2S hardware in CC3200 and the I2S hardware in CC2640? Would it be reasonable for me to start with the CC3200 driver and attempt to make modifications for the CC2640 myself?
  • How about the software in cc26xxware? Is the i2S interface in driverlib OK to use with CC2640?
  • yes, you can directly use drivelib
  • It's getting near to the end of the year...  Any news on the I2S driver?  

    I would really like an example for the CC2640 where the I2S is provides "streaming" data via DMA to a circular buffer in memory.    Is there a way for the CPU to be interrupted when a certain number of samples/transfers have been performed by the DMA (since the last check) (so the contents of the circular buffer can be packetized or moved?

    I know exactly what I want to do, but I don't have the visibility into the RTOS/HW/Driver interaction to know how to implement it on CC2640 HW!

    Thanks for any insight you can provide.

  • Hi,

    There will be a PDM driver using I2S module coming out next month. Regarding to other question you have, I already notified an expert to help you out.
  • Hi Ed,

    The I2S module is more than just an I2S module. It's actually a highly configurable Audio Interface. I2S is just one of the formats supported. We utilize this to drive a digital microphone to clock back PDM samples.

    The registers for this module is described in file:///C:/ti/tirtos_simplelink_2_14_02_22/products/cc26xxware_2_21_03_15980/doc/register_descriptions/CPU_MMAP/I2S0.html. 

    There are also DriverLib functions for it, which basically just abstracts these registers. The RTOS driver is using this.

    One important feature of this module is that it has its own DMA. An interrupt is generated when the DMA has completed a transfer of the configured amount of samples. Just before firing this interrupt the module updates the destination address for the DMA from the register containing the "next pointer" (AIFINPTRNEXT for input and AIFOUTPTRNEXT for output). There's an 8 bit value to configure the frame size, which that represents the DMA transfer length in samples.

    The RTOS driver that's coming out takes care of configuration, setting up the pointers to kick off the stream, and managing the pointers during the stream. It can be configured to use heap to dynamically allocate memory, or use a statically allocated area. You could easily update this to be a circular buffer. It is implemented using RTOS Queue.

    Hopefully 255 samples is sufficient for you, because if you need more I believe you would struggle a bit setting up a DMA to handle it. The DMA would have to be able to update the "next pointer" correctly, and wrap according to your buffer size. You could probably update the "next pointer" using a second DMA channel. However, I'm not sure how much you would benefit, as all the CPU has to do is figure what the next pointer should be, and then write this value.

    Since the module has its own DMA and it already collects as many samples as we want to process anyway, we don't have a need to use another layer of DMAs to collect a larger amount of samples.

  • which pin on the cc2640 to be assigned to the i2s 

  • Hi,

    Like all digital peripheral and GPIOs, the I2S pins can be assigned to any digital IO. You can find the package pin by looking at DIO_0-DIO_30

  • thanks Torbjorn!

  • Thanks... When will the driver be available?
  • Hi,

    PDM driver for CC26xx is now available on the newest RTOS release( Dec 16th 2015) which can be found here :
    software-dl.ti.com/.../index.html

    Please be noted that TI-RTOS has restructured the folders which makes it difficult to just use that revision. You can grab the needed files and included into your project. We are making a port guide now and hopefully will be ready before Christmas.
  • Hi Christin,

    Has the mentioned port guide release yet?

    ../ming

  • The port guide will be ready in 1 or 2 weeks.

  • Any update on the I2S driver for CC26xx?

  • Hi Christin. Thanks for the pointer to the PDM driver for CC26xx.

    I copied the empty_min project files into my workspace, I can build and run the binary, my LED is flashing, things are looking promising.
    I am able to include the headers, but the initialization function PDMCC26XX_init() does not seem to be documented.

    There seems to be a chicken and egg problem between PDMCC26XX_Params, PDMCC26XX_Handle, and running the PDMCC26XX_init() and open() functions. The documentation hints at a parameter generating function, but I only see that existing in PDMCC26XX_util.h, specifically for I2S, and not looking very compatible.

    I have been eager to use this microphone for many months. I had almost given up on this.

    if the open() function returns a handle and accepts the parameters, is that called after init()?

    This is the extent of doxygen documentation for the init function -

    ---
    void PDMCC26XX_init ( PDMCC26XX_Handle handle )
    PDM CC26XX initialization.
    ---

    That is not very useful :)

    The open() function seems to return a handle struct -
    PDMCC26XX_Handle PDMCC26XX_open ( PDMCC26XX_Params * params )

    When I try to run open with NULL params, like so -
    PDMCC26XX_Handle pdm_handle = PDMCC26XX_open(NULL);

    I run into this linker error -
    Error[Li005]: no definition for "ti_sysbios_knl_Event_post__E" [referenced from PDMCC26XX.orm3(pdm_cc26xxware.arm3)]

    I was also getting a build error just from including PDMCC26XX.h without first including PDMCC26XX_util.h (or family/arm/m3/Hwi.h).

    In case anyone else has the same issue, this was the error -

    Error[Pe020]: identifier "Hwi_Struct" is undefined C:\ti\tirtos_cc13xx_cc26xx_2_15_00_17\products\tidrivers_cc13xx_cc26xx_2_15_00_26\packages\ti\drivers\power\PowerCC26XX.h 253


    Some example code to follow would be extremely useful !
    I do realize this is a beta driver.
  • Hi Torbjorn,

    Thanks for the reply.  After dealing with other issues, I'm finally back working on the I2S portion of my design.

    It sounds like the driver you mentioned will do what I need.   Any chance of getting an early version of it?

    I'd hate to spend a lot of time trying to come up with something of my own if there is a TI solution coming out soon.

    If it helps, I need to be able to set the rx data to 24 bits and I need to be able to control the sampling frequency.   I only need 1 channel (single phase).

    So a partially complete solution may serve my needs.

    Thanks!

  • Hi Mike,

    The application examples are still pending release, the RTOS installer comes out first.

    The handle to pass to PDMCC26XX_init() is the one defined in the board file. This handle contains the buffer for Object and HWAttr structures. HWAttr should have the IO to use for the microphone signal.

    "...
    #if defined( CC2650RC_7ID )
    /* Initialize PDM driver (invokes I2S) */
    PDMCC26XX_init((PDMCC26XX_Handle)&(PDMCC26XX_config));
    /* PDM decimation and compression task */
    PDM_createTask();
    #endif
    ..."

    Then you can start using the driver with Open, then StartStream, and a sequence of RequestBuffer calls. Before calling Open you need to setup the parameters to use, including microphone gain and whether or not to apply compression.

  • Hi Ed,

    I checked the latest RTOS release, and the I2S driver is wrapped inside the PDM driver. Unfortunately it can't be modified just yet, since it's only released as a library for now.

  • Thanks for the quick answer Torbjorn.

    I was able to get rid of my linker error by adding this to the cfg file -
    var Event = xdc.useModule('ti.sysbios.knl.Event');

    I am able to call the PDMCC26XX_init() with the handle/config from the board file.

    My next issue is that no matter how I set up my parameters,
    my program chokes on PDMCC26XX_open() with either static defined parameters, or NULL to invoke defaults.
    The open() function is not returning, so I cannot check the return value for NULL.

    Since this is using the library, stepping thru this with the debugger is not very useful .
  • Mike,

    Just for sanity (I might have missed if it was mentioned in this in the thread), but can you check if the application has initialized the power module? Do a quick check to see that your application is calling Power_init() before you can open any TI-RTOS driver.

  • Hi Tom. Thanks for the quick reply.

    I think we're good regarding the power module.
    The empty_min project example calls Board_initGeneral() first thing in main().
    That's defined in Board.h like so -

    #define Board_initGeneral() { \
    Power_init(); \
    if (PIN_init(BoardGpioInitTable) != PIN_SUCCESS) \
    {System_abort("Error with PIN_init\n"); \
    } \
  • Hi Mike,

    Here's how we open and use the driver:

    "...

    static void PDMCC26XX_callbackFxn(PDMCC26XX_Handle handle, PDMCC26XX_StreamNotification *streamNotification);
    static PDMCC26XX_Handle pdmHandle;
    PDMCC26XX_Params pdmParams = {
        PDMCC26XX_callbackFxn,  /* callback */
        true,                           /* Use default filter */
        NULL,                       /* Since we use default filter we don't
                                             have to provide a filter */
        PDMCC26XX_GAIN_12,       /* Mic gain - Default is +12dBm */
        true,                         /* Mic power pin polarity (Active high if 
                                          true) */
        true,                           /* Apply compression if true */
        8 * PCM_BLOCK_SIZE_IN_SAMPLES, /* Delay after clock is applied until we can 
                                          use data from microphone */
        NULL                           /* Left for driver */
    };
    // Then your open function
    void yourOpenFx() {

    pdmHandle = PDMCC26XX_open(&pdmParams);

    if (pdmHandle)
    {

     // If you have stuff to prepare before you start the stream then do so. E.g. setup an RF link, or negotiate a serial connection.

     PDMCC26XX_startStream(pdmHandle);

    ...

    // Then upon callback, with data ready, we can request buffer. Preferably do this from application task context, instead of in the callback. We typically just post a semaphore in the callback function

    void yourTaskFx()
    {
     PDMCC26XX_BufferRequest bufferRequest;
     PDMCC26XX_BufferRelease bufferRelease;

     if (PDMCC26XX_requestBuffer(pdmHandle, &bufferRequest)) {
     // Use the data

     // Release after use
     bufferRelease.bufferHandle = bufferRequest.bufferHandle;
     PDMCC26XX_releaseBuffer(pdmHandle, &bufferRelease);
    }

    ..."

  • Thanks again for the quick reply, Torbjorn. 

    It seems I may have a different version of TI RTOS or the PDM driver. The PDMCC26XX_BufferRelease does not exist in my version. I grepped the whole tree for it. I did see a PDMCC26CC_I2S_BufferRelease, however it is not compatible. 

    In addition, the call to PDMCC26XX_open() is still failing. I copied your example params and had the exact same issue. PDMCC26XX_open() is not returning. 

    My top level folder is 

    tirtos_cc13xx_cc26xx_2_15_00_17

    and the tidrivers folder is

    tidrivers_cc13xx_cc26xx_2_15_00_26

    Does that look right? 

    Thanks again for your help on this. I can tell you that a lot of people on the e2e zigbee/6lowpan forum have been waiting for this. 

  • Hi Mike,

    You've got the latest version of RTOS. There is a sample implementation in the works which would really answer your question, but it's going to come out around feb/march.

    I'll try to give you the pieces here. This will only show you how to open the driver and start the stream. It will either run to completion, or run into an error likely because of allocation issues. You should call stopStream and close when you're done with it. Also notice that this one assumes that you have access to the UART driver. You can either look past these steps or look at the UART example. If you run without compression you'll have to set the baudrate to > 256000 --> 460800. This is because 16kHz 16-bit audio produces a 256kbps stream uncompressed. Compression produces a fourth of that.

    In board file:

    #include <ti/drivers/pdm/PDMCC26XX.h>
    PDMCC26XX_Object pdmCC26XXObject = {0};

    const PDMCC26XX_HWAttrs pdmCC26XXHWAttrs = {
    .micPower = Board_MIC_POWER,
    .taskPriority = 2,
    };

    /* PDM configuration structure */
    const PDMCC26XX_Config PDMCC26XX_config[] = {
    {
    .object = &pdmCC26XXObject,
    .hwAttrs = &pdmCC26XXHWAttrs
    },
    {NULL, NULL}
    };

    In main():
    /* Initialize PDM driver (invokes I2S) */
    PDMCC26XX_init((PDMCC26XX_Handle)&(PDMCC26XX_config));

    In your application:

    PDMCC26XX_Params pdmParams = {

    .callbackFxn = RSA_PDMCC26XX_callbackFxn,
    .useDefaultFilter = true,
    .decimationFilter = NULL,
    .micGain = PDMCC26XX_GAIN_12,
    .micPowerActiveHigh = true,
    .applyCompression = true,
    .startupDelayWithClockInSamples = 256,
    .retBufSizeInBytes = AUDIO_BUF_COMPRESSED_SIZE+PCM_METADATA_SIZE,
    .mallocFxn = (PDMCC26XX_MallocFxn) malloc, // Should point to an allocation function of your choice, e.g. RTOS default
    .freeFxn = (PDMCC26XX_FreeFxn) free, // Should point to a de-allocation function of your choice, e.g. RTOS default
    .custom = NULL

    };

    /* Open PDM driver */

    pdmHandle = PDMCC26XX_open(&pdmParams);

    if(pdmHandle)
    {

    /* Stream immediately if we simply dump over UART. */
    PDMCC26XX_startStream(pdmHandle);

    }

    static void RSA_PDMCC26XX_callbackFxn(
    PDMCC26XX_Handle handle,
    PDMCC26XX_StreamNotification *pStreamNotification)
    {

    if(pStreamNotification->status == PDMCC26XX_STREAM_BLOCK_READY)
    {

    events |= RSA_PCM_BLOCK_READY;

    }

    Semaphore_post(rsaSem);

    }

    void yourTaskFx()
    {

    if(events & RSA_PCM_BLOCK_READY)
    {

    while(PDMCC26XX_requestBuffer(pdmHandle, &bufferRequest))
    {

    if(bufferRequest.status == PDMCC26XX_STREAM_BLOCK_READY)
    {

    static int waitCnt = 0;

    if(waitCnt++ > 3)
    {

    if(pdmParams.applyCompression)
    {

    UART_write(uart, (Char const *)bufferRequest.buffer,
    PCM_METADATA_SIZE + AUDIO_BUF_COMPRESSED_SIZE;

    }

    else
    {

    UART_write(uart, (Char const *)bufferRequest.buffer +
    PCM_METADATA_SIZE, AUDIO_BUF_UNCOMPRESSED_SIZE);

    }

    }

    }

    }
    events &= ~RSA_PCM_BLOCK_READY;

    }

    free(bufferRequest.buffer,0);

    }

  • OK, thanks. Given that the TI driver isn't available, I am attempting to write an interface that makes use of the I2S module using the CC26xx abstraction of the I2S HW registers. I'm struggling a bit though, given the lack of documentation. I have read through the SWCU117D section on the I2S module and have also read through the code (and the Doxygen formatted web pages) and found that I still have questions:

    1. Can you give more information on the Control Table? In particular, the individual item descriptions lack detail. For example:
    A: ui16DMABufSize: Is this the number of samples per pointer swap? Is the DMA buffer the same as a channel buffer?
    B: ui16MemLen: Is this the length in bits or bytes of a sample?
    C: ui32InOffset: Is this something the user has to set or is this maintained by the HW?
    D: I assume that I2SBufferConfig is used to load the values into this table... What is the difference between the DMABufSize and ChanBufSize?
    2. If only input is needed (ie. a microphone), can the output pointers, etc. be ignored or pointers set to NULL?
    3. Can you shed more light on the operation of the SampleStamp Generator? Is this only for synchronization purposes or is it required for the basic operation of I2S? The documentation seems to indicated that it must be enabled for streaming, but setting the AIFINPTRNEXT starts the I2S. Are both needed? (I guess my confusion is what your definition of streaming (versus mine :-) might be.)

    I understand that the audio interface is somewhat autonomous and delivers (or takes) data to (from) buffers controlled by this control table. I guess a double, triple, (or more) buffering scheme can be set up by the user by appropriately manipulating the buffer pointers.

    A more complete example, or some diagrams with the appropriate text describing how this is intended to work would be very helpful.

    I'm trying to replace an I2S interface that I designed to work with CC2541. I committed to using CC2640 in the next revision this past summer and have been waiting for the driver from TI. I'm now on a deadline and appreciate your help!

    Thanks!
  • Hi Torbjorn,

    Like others, my application is to take the mem mic data and transmit to another side which could be a mobile or another CC2640 device. I have the following questions with the current (and future) implementation of the PDM driver:

    1. Can the driver support other sample rates and pcm data width, like 8K samples x 16 bits? Please specify the case for the current driver and the driver in the future.
    2. What kind of compression applies to the PCM data when compression is set on? We would need to know how to decode the pcm data at the other end.
    3. "AUDIO_BUF_COMPRESSED_SIZE" was not defined in either PDMCCXX.h or PDMCCXX_utils.h. What would be the value of this define in this case?
    4. Would this driver be open with source code in the future or will it be stay close?
    5. You quote: "There is a sample implementation in the works which would really answer your question, but it's going to come out around feb/march." Is this sample application the same as the "port guide" that mentioned by Christian in this same thread?

    thanks a lot in advance.

    ../ming

  • Hi Torbjorn,
    I've been looking further to see if I can use the PDM driver (versus going directly to I2S) for my custom board.
    When using the PDM driver, I assume that it interfaces with the microphone using the I2S pins and one uses the normal mechanism (IOCPortConfigurationSet) to associate physical pins to the I2S module. Does the PDM driver do all of the I2S initialization/setup (including clocks, etc.)? Which I2S data input (AD0?) should be connected to the microphone serial data (SD) pin? Are only BCLK, WCLK, and AD0(?) needed? Do I simply associate the physical pins, then initiate the PDM driver?
    Sorry if this is a "dumb" question...
    Thanks!
  • Hi Torbjorn,

    Can the PDM driver be used with the older version of TI-RTOS (I"m currently using tirtos_simplelink_2_13_00_06).   I believe this was suggested by Cristin?

    If so, are there any steps beyond the obvious ones of copying in PDMCC26XX.h, PDMCC26XX_util.h?   Are there other files required?

    If not, when will the porting guide be available?

    Thanks!

  • Hi Ed,

    There is more information in the register description.


    1. A: Number of words to sample before DMA switches to next pointer
      B: Yes
      C: Internally managed and used variable
      D: DMA will transfer from all channels into the same buffer. If you have two or more channels then the size is different between DMA and channel
    2. Yes
    3. It is required to kick of sampling, but nothing more. It is used to have highly synchronized audio networks. It was designed for the PurePathWireless protocol.

    Once the stream is started all you'll have to deal with is giving the module a new pointer every time it finishes a transfer.

  • Hi Ed,
    The PDM driver takes care of all initialization. You'll tell it what pins to use in the PDMCC26XX_I2S_HWAttrs structure:
    const PDMCC26XX_I2S_HWAttrs i2sCC26XXHWAttrs = {
    .baseAddr = I2S0_BASE,
    .intNum = INT_I2S_IRQ,
    .intPriority = ~0,
    .powerMngrId = PowerCC26XX_PERIPH_I2S,
    .mclkPin = Board_I2S_MCLK,
    .bclkPin = Board_I2S_BCLK,
    .wclkPin = Board_I2S_WCLK,
    .ad0Pin = Board_I2S_ADI,
    };
  • Thanks again Torbjorn. Unfortunately, I am still stuck.

    First, I switched from the empty_min project to the empty project, to ensure no related kernel functions were being disabled for the minimal footprint. 
    Then I re-introduced pieces of the code, and as soon as I try to try to open the pdmHandle, the program loses it. The open function is not returning. 

    I am calling PDMCC26XX_open() after PDMCC26XX_init() during the main() setup, before calling bios_start(). 
    My params are copied from yours, with the exception of retBufSizeInBytes. 
    I do not have these macros defined: AUDIO_BUF_COMPRESSED_SIZE, PCM_METADATA_SIZE
    I've tried to call open() with various hardcoded buf sizes from 16 to 1024 bytes, all with the same result. 

    I am using IAR for ARM v7.40.3.8938. 

    I had some build errors when I tried to make my board file PDM section look like yours.
    The PDM_I2S_Object was not defined without adding some of the code below - pdmC26XXI2SHWAttrs & PDMCC26XX_I2S_config

    The PDM section of my board file looks like this (default from the new examples that came with the TI-RTOS):

    /*
    * ============================= PDM begin ====================================
    */
    /* Place into subsections to allow the TI linker to remove items properly */
    #if defined(__TI_COMPILER_VERSION__)
    #pragma DATA_SECTION(PDMCC26XX_config, ".const:PDMCC26XX_config")
    #pragma DATA_SECTION(pdmCC26XXHWAttrs, ".const:pdmCC26XXHWAttrs")
    #pragma DATA_SECTION(PDMCC26XX_I2S_config, ".const:PDMCC26XX_I2S_config")
    #endif

    /* Include drivers */
    #include <ti/drivers/pdm/PDMCC26XX.h>
    #include <ti/drivers/pdm/PDMCC26XX_util.h>

    /* PDM objects, one for PDM driver, one for PDM/I2S helper file */
    PDMCC26XX_Object pdmCC26XXObjects[CC2650STK_PDMCOUNT];
    PDMCC26XX_I2S_Object pdmCC26XXI2SObjects[CC2650STK_PDMCOUNT];

    /* PDM driver hardware attributes */
    const PDMCC26XX_HWAttrs pdmCC26XXHWAttrs[CC2650STK_PDMCOUNT] = {
    {
    .micPower = Board_MIC_POWER,
    .taskPriority = 1
    }
    };

    /* PDM configuration structure */
    const PDMCC26XX_Config PDMCC26XX_config[] = {
    {
    .object = &pdmCC26XXObjects[0],
    .hwAttrs = &pdmCC26XXHWAttrs[0]
    }
    };

    /* PDM_I2S hardware attributes */
    const PDMCC26XX_I2S_HWAttrs pdmC26XXI2SHWAttrs[CC2650STK_PDMCOUNT] = {
    {
    .baseAddr = I2S0_BASE,
    .intNum = INT_I2S_IRQ,
    .powerMngrId = PowerCC26XX_PERIPH_I2S,
    .intPriority = ~0,
    .mclkPin = PIN_UNASSIGNED,
    .bclkPin = Board_AUDIO_CLK,
    .wclkPin = PIN_UNASSIGNED,
    .ad0Pin = Board_AUDIO_DI,
    }
    };

    /* PDM_I2S configuration structure */
    const PDMCC26XX_I2S_Config PDMCC26XX_I2S_config[] = {
    {
    .object = &pdmCC26XXI2SObjects[0],
    .hwAttrs = &pdmC26XXI2SHWAttrs[0]
    },
    {NULL, NULL}
    };

  • I am doing the same thing but got stuck in a different way. I am running BTStack 2.1, and I am back porting the PDM driver from tirtos_cc13xx_cc26xx_2_15_00_17 to tirtos_simplelink_2_13_00_06.

    I got those linker errors as below. How do I bring the PDM library files (pdm_cc26xxware.aem3, pdm_cc26xxware.am3g, pdm_cc26xxware.arm3) into the project? I tried to add the lib files into the workspace, but it doesn't work. 

    <Linking>
    
     undefined               first referenced
      symbol                     in file     
     ---------               ----------------
     PDMCC26XX_init          <whole-program> 
     PDMCC26XX_open          <whole-program> 
     PDMCC26XX_requestBuffer <whole-program> 
     PDMCC26XX_startStream   <whole-program> 
    

    Please help. Thanks.

    ../ming

  • I got the method to include the PDM library from this link, and I can include the PDM library.

    Now, I got the following linker error:

    <Linking>
    
     undefined               first referenced                                                                                  
      symbol                     in file                                                                                       
     ---------               ----------------                                                                                  
     Power_releaseDependency C:\ti\tirtos_simplelink_2_13_00_06\packages\ti\drivers\pdm\lib\pdm_cc26xxware.aem3<PDMCC26XX.oem3>
     Power_setConstraint     C:\ti\tirtos_simplelink_2_13_00_06\packages\ti\drivers\pdm\lib\pdm_cc26xxware.aem3<PDMCC26XX.oem3>
    
    >> Compilation failure
     Power_setDependency     C:\ti\tirtos_simplelink_2_13_00_06\packages\ti\drivers\pdm\lib\pdm_cc26xxware.aem3<PDMCC26XX.oem3>
    
    error #10234-D: unresolved symbols remain
    error #10010: errors encountered during linking; "Ble.out" not built
    gmake: *** [Ble.out] Error 1
    gmake: Target `all' not remade because of errors.
    
    **** Build Finished ****

    I don't understand why these 3 standard Power_XXX APIs will not be found. Is that related to different TIRTOS version?

    Please help

  • Thanks Torbjorn,

    I'm still struggling with how the DMA and Channel buffers are expected to be laid out if one wants to use the CC26xx I2S library and the control table structure.

    I got the I2S pins to "wiggle" using the example code that Andrei posted (Thanks Andrei!!!), but this code hits HW registers directly and I am hoping to use the CC26xx calls.

    It appears, by looking at the code in i2s.c,  that the I2SPointerUpdate() call has some expectation of the buffer/channel memory configuration.

    Can you describe what it expects?    Is the DMA buffer a large area of memory that is some integer multiple of the size of a channel buffer?

    I understand that the I2S DMA unit "chains" the buffers using the NXTPTR, so one could set up a set of buffers and cycle through them.   It doesn't look like this is what the I2SPointerUpdate() code is doing, but rather is cycling through a large memory area.

    I haven't seen any documentation on this except the online doxygen generated pages (and they don't really describe what is expected).

    TI needs to understand that the documentation should describe the functionality so that someone outside TI (ie. those of us who don't have access to internal documents)  can actually use the HW...

    Thanks...

  • +1 Been following the thread as I'm in the same boat as Ed, Mike, and Ming. We're hoping the I2S driver gets released sooner than later.
  • A further update...
    I am attempting to take Andrei's example (that writes HW registers directly) and merge it with work that I have done using the CC26xx driver library. I have not gotten my code to work. In fact one statement seems to crash the application (more on that below).

    I broke the code into two pieces: one that intializes the peripheral and the other which starts/stops transfers.


    #define NUM_MIC_BUF 4

    uint8_t micBuffer[NUM_MIC_SAMPLES_PACKET*3*NUM_MIC_BUF];

    I2SControlTable g_controlTable; // Define global table space


    The initialization code (in an init function):


    // Performed once
    // Sets up microphone
    g_pControlTable = &g_controlTable; // Assign pointer


    mic_i2sPinConfig(); // Step 1. Assign Pins

    mic_i2sEnableBclk(false); // Make sure BCLK is off
    mic_i2sClockConfig(); // Step 2. Configure the clocks
    mic_i2sPeriphConfig(); // Step 3. Configure the I2S
    mic_i2sEnableBclk(true); // Step 4. Enable BCLK
    mic_i2sSampleStampConfig(); // Step 5. Configure SampleStamp Gen
    #if 0
    mic_i2sConfigureIntrCallback(mic_i2sISR); // Step 7.
    #endif
    mic_i2sMemoryConfig();



    The #ifdef'd out statement, if included, crashes the application. It is simply the library call that registers the interrupt callback for the I2S.



    The functions are defined as:

    static void mic_i2sPinConfig() {
    PIN_Status res = PIN_SUCCESS;
    PIN_Config pinTable[] = {
    Board_I2S0_BCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH, // BCLK
    Board_I2S0_WCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW, // WCLK
    Board_I2S0_SD | PIN_INPUT_EN | PIN_PULLUP, // SD
    PIN_TERMINATE
    };
    // Open and assign pins through pin driver
    pinHandle = PIN_open(&pinState, pinTable);
    // Check to make sure pins are not already allocated
    if(NULL == pinHandle) while(1); // Stall here on error!

    // Set IO muxing for the I2S pins
    res |= PINCC26XX_setMux(pinHandle, Board_I2S0_WCLK, IOC_PORT_MCU_I2S_WCLK);
    res |= PINCC26XX_setMux(pinHandle, Board_I2S0_BCLK, IOC_PORT_MCU_I2S_BCLK);
    res |= PINCC26XX_setMux(pinHandle, Board_I2S0_SD, IOC_PORT_MCU_I2S_AD0);

    if(PIN_SUCCESS != res) while(1); // Stall here on error!
    }



    static void mic_i2sClockConfig() { // Set up I2S clocks
    #if 0
    Power_setConstraint(Power_SB_DISALLOW);
    if(!Power_setDependency(PERIPH_I2S)) while(1); // Stall here on error!
    #endif

    PRCMPeripheralRunEnable(PRCM_PERIPH_I2S); // I2S Clock Gate for Run Mode
    PRCMPeripheralSleepDisable(PRCM_PERIPH_I2S); // I2S Clock Gate for Sleep Mode
    PRCMPeripheralDeepSleepDisable(PRCM_PERIPH_I2S);// I2S Clock Gate for DeepSleep Mode

    // Sample at 16 KHz for now
    PRCMAudioClockConfigSet( PRCM_WCLK_DUAL_PHASE
    | PRCM_WCLK_POS_EDGE,
    I2S_SAMPLE_RATE_16K
    );
    // Use this to set up custom sample rate
    //PRCMAudioClockConfigSetOverride(PRCM_WCLK_DUAL_PHASE | PRCM_WCLK_NEG_EDGE,
    // masterDiv,
    // bitDiv,
    // wordDiv
    // );
    PRCMLoadSet(); // Load clock values into clock unit
    // Inform I2S where to
    // get Clock
    I2SClockConfigure(I2S0_BASE, I2S_INT_WCLK | I2S_INVERT_WCLK);

    }



    static void mic_i2sPeriphConfig() {
    I2SAudioFormatConfigure(I2S0_BASE, // I2S registers
    I2S_MEM_LENGTH_24 // 24-bits packed in mem
    | I2S_POS_EDGE // Sampled on pos edge
    | I2S_DUAL_PHASE_FMT // 2 phases in I2S
    | I2S_WORD_LENGTH_24, // 24-bit samples
    1 // 1 bit delay
    );
    I2SChannelConfigure(I2S0_BASE, // I2S registers
    I2S_LINE_INPUT // Chan 0: I2S - Mono
    | I2S_MONO_MODE,
    I2S_LINE_UNUSED, // Chan 1: unused
    I2S_LINE_UNUSED // Chan 2: unused
    );
    }



    static void mic_i2sEnableBclk(bool en) {
    if(en == true) {
    PRCMAudioClockEnable(); // Turn on BCLK
    } else {
    PRCMAudioClockDisable(); // Turn off BCLK
    }
    PRCMLoadSet();
    }



    static void mic_i2sSampleStampConfig() {
    I2SSampleStampConfigure(I2S0_BASE, true, false);
    //HWREG(I2S0_BASE + I2S_O_STMPWPER) = NUM_MIC_SAMPLES_PACKET - 1; // Number of samples per buffer
    //HWREG(I2S0_BASE + I2S_O_STMPINTRIG) = NUM_MIC_SAMPLES_PACKET + 5; // Set up beyond end for now
    //HWREG(I2S0_BASE + I2S_O_STMPOUTTRIG) = NUM_MIC_SAMPLES_PACKET + 5; // Set up beyond end for now
    }



    static void mic_i2sConfigureIntrCallback(void (*pfnHandler)(void)) {
    I2SIntRegister(I2S0_BASE, pfnHandler);
    }



    static void mic_i2sMemoryConfig() {
    I2SBufferConfig(I2S0_BASE, // I2S base
    (uint32_t) micBuffer, // Address of Input Buffer
    0, // Address of Output Buffer - not used
    NUM_MIC_SAMPLES_PACKET, // Number of samples per buffer
    NUM_MIC_SAMPLES_PACKET // Number of samples per channel
    );
    }


    The ISR function is defined as: (Note that I have commented out all of the functionality to try to find what is causing the crash)


    static void mic_i2sISR(void) {
    #if 0
    uint32_t intStatus = I2SIntStatus(I2S0_BASE, true);
    I2SIntClear(I2S0_BASE, I2S_INT_DMA_IN);
    #if 0
    I2SPointerUpdate(I2S0_BASE,true);
    #endif
    #endif

    }




    To start the sampling I wrote the following code:



    static void mic_i2sEnable(bool en) {
    if(en == true) {
    I2SIntEnable(I2S0_BASE, I2S_INT_DMA_IN | I2S_INT_TIMEOUT | I2S_INT_BUS_ERR | I2S_INT_WCLK_ERR | I2S_INT_PTR_ERR);
    I2SEnable(I2S0_BASE);
    } else {
    I2SDisable(I2S0_BASE);
    }
    }


    static void micStart() {

    mic_i2sEnable(true); // Enable I2S; pointer copied
    I2SPointerUpdate(I2S0_BASE,true); // Update the input pointer
    I2SSampleStampEnable(I2S0_BASE); // Start SampleStamp

    }

    This doesn't work. Including the statement to register the interrupt callback causes the system to hang (at the location that I think indicates a bad memory reference).

    If I comment the interrupt registering statement out, then the code "runs" (ie. doesn't crash), but starting the microphone does nothing (no pins wiggle as expected).

    What am I doing wrong?

    Any assistance would be appreciated!

    Thanks!
  • Hi Ed. 

    As a sanity check,  you could register the ISR using the lower level Hwi functions.  
    That way you could at least see if your I2S pins wiggle, and maybe pinpoint the problem to the I2SIntRegister() API. 

    i.e. - 

    Hwi_Params hwiParams;

    // Setup HWI handler
    Hwi_Params_init(&hwiParams);
    Hwi_construct(&hwiStruct, INT_I2S, i2s_Isr, &hwiParams, NULL);

  • Hi Mike,

    Hitting the registers directly seems to work for the interrupt.   The I2SIntRegister function causes the app to crash (evidently does an illegal access and traps to a holding loop).

    Thanks again to Andrei for posting his solution that hits registers directly.

    After lots of "trial-and-error" I found a sequence of CC26xx library calls that seem to work for I2S.

    It seems that the library is incomplete though.  There are a few places where direct hardware register accesses seem to be required (at least I couldn't find any CC26xx calls to do the equivalent functionality).    

    I also found that the order of initialization has to be different than prescribed in the reference manual.   I found this by reading through the i2s.h and i2s.c code, noticing that it expected certain items from the control table to be initialized.    Performing the sequence in the prescribed order caused uninitialized values to be used.

    Also, I expected the I2SPointerUpdate to perform buffer address calculations and properly set the PTRNEXT register.   It didn't work the way I expected, so I used the I2SPointerSet function to do the buffer update "manually".

    This code seems to work for me, but I'd appreciate any updates, or an explanation of what TI had in mind for their buffering scheme if I'm doing something wrong.

    Here is the code:

    #define NUM_MIC_SAMPLES_PACKET    48

    #define NUM_MIC_BUF 6

    uint8_t micBuffer[NUM_MIC_SAMPLES_PACKET*3*NUM_MIC_BUF];

    I2SControlTable g_controlTable;          // Define global table space

    uint32_t intStatus = 0;

    uint8_t index_in = 0;
    uint8_t justReadIndex;


    static PIN_State pinState;
    static PIN_Handle pinHandle;
    static ti_sysbios_family_arm_m3_Hwi_Struct hwiStruct;


    static void mic_i2sEnable(bool en) {
      if(en == true) {
        I2SIntEnable(I2S0_BASE, I2S_INT_DMA_IN | I2S_INT_TIMEOUT | I2S_INT_BUS_ERR | I2S_INT_WCLK_ERR | I2S_INT_PTR_ERR);
        I2SEnable(I2S0_BASE);
      } else {
        I2SDisable(I2S0_BASE);
      }
    }


    /*
    * Interrupt service routine.
    *
    */
    static void mic_i2sISR(void) {

      intStatus = I2SIntStatus(I2S0_BASE, true);
      I2SIntClear(I2S0_BASE, I2S_INT_DMA_IN);

      justReadIndex = index_in;

      uint8_t* newPtr = &(micBuffer[NUM_MIC_SAMPLES_PACKET * 3 * index_in]);
      index_in++; if(index_in == NUM_MIC_BUF) index_in = 0;
      I2SPointerSet(I2S0_BASE,true,newPtr);
      //I2SPointerUpdate(I2S0_BASE,true);      // Update the input pointer - why doesn't this work??


      i2s_extractData();    // Most of work done in task;  justReadIndex points to just received buffer


        // Wake the task
      micRdy = true;
      Semaphore_post(localSem);
    }


    static void mic_i2sConfigureIntrCallback(void (*pfnHandler)(void)) {

    //#if 0
      Hwi_Params hwiParams;

        // Setup HWI handler - This works... 
      Hwi_Params_init(&hwiParams);
      Hwi_construct(&hwiStruct, INT_I2S,pfnHandler, &hwiParams, NULL);
    //#endif

    #if 0

         // This doesn't work

      I2SIntRegister(I2S0_BASE, pfnHandler);

    #endif
    }

    static void mic_i2sEnableBclk(bool en) {
      if(en == true) {
        PRCMAudioClockEnable(); // Turn on BCLK
      } else {
        PRCMAudioClockDisable(); // Turn off BCLK
      }
    PRCMLoadSet();
    }


    static void mic_i2sPinConfig() {
          // Assign physical pins for I2S operation
      PIN_Status res = PIN_SUCCESS;
      PIN_Config pinTable[] = {
        Board_I2S0_BCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH,         // BCLK
        Board_I2S0_WCLK | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW,         // WCLK
        Board_I2S0_SD | PIN_INPUT_EN | PIN_PULLUP,                                                                                                       // SD
        Board_I2S0_CHIP_EN | PIN_INPUT_DIS | PIN_PUSHPULL | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW,   // Enable (use for debug)
        PIN_TERMINATE
      };
         // Open and assign pins through pin driver
      pinHandle = PIN_open(&pinState, pinTable);
        // Check to make sure pins are not already allocated
      if(NULL == pinHandle) while(1); // Stall here on error!

        // Set IO muxing for the I2S pins
      res |= PINCC26XX_setMux(pinHandle, Board_I2S0_WCLK, IOC_PORT_MCU_I2S_WCLK);
      res |= PINCC26XX_setMux(pinHandle, Board_I2S0_BCLK, IOC_PORT_MCU_I2S_BCLK);
      res |= PINCC26XX_setMux(pinHandle, Board_I2S0_SD, IOC_PORT_MCU_I2S_AD0);

      if(PIN_SUCCESS != res) while(1); // Stall here on error!
    }

    static void mic_i2sClockConfig() { // Set up I2S clocks

      Power_setConstraint(Power_SB_DISALLOW);
      if(!Power_setDependency(PERIPH_I2S)) while(1); // Stall here on error!

      PRCMPeripheralRunEnable(PRCM_PERIPH_I2S);                    // I2S Clock Gate for Run Mode
      PRCMPeripheralSleepDisable(PRCM_PERIPH_I2S);                // I2S Clock Gate for Sleep Mode
      PRCMPeripheralDeepSleepDisable(PRCM_PERIPH_I2S);      // I2S Clock Gate for DeepSleep Mode

           // No PRCMPeripheral Library call for this????
                                                                // I2S Clock Control. (Use internally generated clock)
      HWREG(PRCM_BASE + PRCM_O_I2SBCLKSEL) = PRCM_I2SBCLKSEL_SRC;

                                                               // Use this to set up custom sample rate
      PRCMAudioClockConfigSetOverride(PRCM_WCLK_DUAL_PHASE | PRCM_WCLK_POS_EDGE,
                                                                         0, // MstDiv
                                                                    240, // BitDiv 16KHz:60 8KHz:120 6.4KHz:150 6.0KHz:160 4KHz:240 2KHz:480
                                                                       25 // WordDiv
      );

      PRCMLoadSet(); // Load clock values into clock unit
    }

    static void mic_i2sPeriphConfig() {
                                                                             // Inform I2S where to get its clock
      I2SClockConfigure(I2S0_BASE, I2S_INT_WCLK | I2S_INVERT_WCLK);

      I2SAudioFormatConfigure(I2S0_BASE,                                // I2S registers
                                                       I2S_MEM_LENGTH_24          // 24-bits packed in mem
                                                     | I2S_POS_EDGE                       // Sampled on pos edge
                                                     | I2S_DUAL_PHASE_FMT       // 2 phases in I2S
                                                     | I2S_WORD_LENGTH_24,     // 24-bit samples
                                                      1 // 1 bit delay
      );

      I2SChannelConfigure(I2S0_BASE,                                       // I2S registers
                                               I2S_LINE_INPUT                             // Chan 0: I2S - Mono
                                             | I2S_MONO_MODE,
                                               I2S_LINE_UNUSED,                      // Chan 1: unused
                                               I2S_LINE_UNUSED                       // Chan 2: unused
      );
    }

    static void mic_i2sMemoryConfig() {

      I2SBufferConfig(I2S0_BASE,                                          // I2S base
                                  (uint32_t) micBuffer,                             // Address of Input Buffer
                                  0,                                                             // Address of Output Buffer - not used
                                  NUM_MIC_SAMPLES_PACKET,     // Number of samples per buffer
                                  NUM_MIC_SAMPLES_PACKET      // Number of samples per channel
      );
      I2SPointerUpdate(I2S0_BASE,
                                        true                                                   // Update input pointer
      );
    }


    static void mic_i2sSampleStampConfig() {

                                            // Set up SampleStamp for Input,
                                            // but not output
                                            // WCLK Counter Period Value

                                            // No library call for this
      HWREG(I2S0_BASE + I2S_O_STMPWPER) = NUM_MIC_SAMPLES_PACKET;

      I2SSampleStampConfigure(I2S0_BASE, true, false);

      I2SSampleStampEnable(I2S0_BASE);      // Start SampleStamp
    }


    static void micStart() {

      mic_i2sEnable(true);                                     // Enable I2S; pointer copied
      I2SPointerUpdate(I2S0_BASE,true);         // Update the input pointer
    }

    static void micStop() {
    }



    static inline void i2s_extractData(void) {

    }

    /*********************************************************************
    * @fn StethPatchMic_reset
    *
    * @brief Reset
    *
    * @param none
    *
    * @return none
    */
    void StethPatchMic_reset() {

      // Performed once
      // Sets up microphone
      g_pControlTable = &g_controlTable; // Assign pointer

      mic_i2sPinConfig();                         // Step 1. Assign Pins
      mic_i2sEnableBclk(false);             // Make sure BCLK is off
      mic_i2sClockConfig();                    // Step 2. Configure the clocks
      mic_i2sPeriphConfig();                  // Step 3. Configure the I2S
      mic_i2sEnableBclk(true);              // Step 4. Enable BCLK
      mic_i2sMemoryConfig();                      // Do memory config here instead of below
      mic_i2sSampleStampConfig();                        // Step 5. Configure SampleStamp Gen
      mic_i2sConfigureIntrCallback(mic_i2sISR); // Step 7.
      // mic_i2sMemoryConfig();

    }