If you're reading this, you're probably having some issue with getting SAI to work. For those of you who already figured it out, don't read this because some of this was probably painfully obvious to you...I'm not you, haha. If you're like me you may have found the recommended application notes a bit out of date for the new EDMA, that dissecting the SND API in order to use SAI with EDMA was a bit of a pain, and that the documentation for SAI itself leaves some guesswork...particularly in what the actual values for various functions like EDMA_DRV_ConfigLoop should be - are they bytes? Are they actual lengths? If you also can't get mono mode to work properly this may shed some light on that for you as well. Suggestions on how to make this more efficient are welcome.
***I have not implemented half and full loop interrupts as of yet, but as soon as I do I will add them to this.
1) MONO MODE, ISN'T REALLY MONO MODE GENERICALLY - kSaiMono should be called kSai_Right_Channel_Only
*EDIT : This Depends on the I2S Mode you are using, so just be aware of which channel is being masked if you aren't getting data.
If you're using the SAI API, then you'll notice there is a value under i2s_data_format.mono_streo, where the enum is either kSaiMono or KSaiStreo. Simple enough, right?
This value is used in SAI_DRV_RxConfigDataFormat with the following logic and the issue is this :
if (format->mono_streo == kSaiMono)
{
SAI_HAL_RxSetWordMask(reg_base, 2u); <---- DEFAULTS TO MASKING THE LEFT CHANNEL, ALLOWING RIGHT TO PASS
}
else
{
SAI_HAL_RxSetWordMask(reg_base, 0u); <---- MASKS NOTHING, STEREO, CORRECT
}
In order to use the left channel only instead of the right either call SAI_HAL_RxSetWordMask with a mask value of 1, or set I2S0_RMR equal to 0x01.
Voila, left channel.
2)A couple of details for the API
Some of these are simple/clear enough. Others, I had to hunt and peck with settings.
For EDMA_DRV_ConfigLoopTransfer:
chn | The pointer to the channel state structure. Simple Enough... |
stcd | Memory pointing to software TCDs. The user must prepare this memory block. The required memory size is equal to a "period" * size of(edma_software_tcd_t). At the same time, the "stcd" must align with 32 bytes. If not, an error occurs in the eDMA driver. Software tcd, easy enough...the big thing is to follow the rule that tcd needs to be aligned! If you don't, you're dead in the water. |
type | Transfer type. Enum with names like kEDMAPeripheraltoMemory...OK |
srcAddr | A source register address or a start memory address. Ensure to cast if necessary - also I recommend using SAI_DRV_GetFifoAddr and make sure argument two matches your channel value inside of sai_user_config_t for your SAI instance. |
destAddr | A destination register address or a start memory address. Ensure to cast if necessary - ensure that your destination memory is valid if you are using Rx into memory. |
size | Size to be transferred on every DMA write/read. Source/Dest share the same write/read size. Size is the number of bytes in your data. So for int16_t, size is 2. |
bytesOnEachRequest | Size write/read for every trigger of the DMA request. This is equal to size * watermark value set in sai_user_config_t. |
totalLength | Total length of Memory. This is in bytes - so size times length of the array you would like to fill over the life of the loop. For int16_t array of length 1024, the value is 2048. |
number | A number of the descriptor that is configured for this transfer configuration. TCD's, like any other structure can be placed into an array of structures which I. The descriptor is actually the "index" of the tcd structure if you have multiple tcd's in a single structure. It appears you add 1 to the value of the tcd. For mono audio in a single TCD structure, this value is simply 1. If you're using stereo, set it to 2 and round robin will take care of placing your audio values in the appropriate array of memory. |
3)Last, here is the sequence I used to successfully configure RxMono(left) using EDMA. (again no interrupts yet, but will add once they are done)
*EDIT : INTERRUPTS ADDED BELOW...
OSA_Init();
sai_user_config_t *i2s_config = (sai_user_config_t *) OSA_MemAlloc(sizeof(sai_user_config_t));
sai_state_t *i2s_state = (sai_state_t *) OSA_MemAlloc(sizeof(sai_state_t));
/* I2S FORMAT STRUCTURE */
i2s_data_format.bits = bits;
i2s_data_format.sample_rate = 16000;
i2s_data_format.mclk = 256 * i2s_data_format.sample_rate;
i2s_data_format.mono_streo = kSaiMono;
/* RECIEVER SETTINGS */
i2s_config->bclk_source = kSaiBclkSourceMclkDiv;
i2s_config->channel = chan; //SET TO 0?
i2s_config->mclk_divide_enable = true;
i2s_config->mclk_source = kSaiMclkSourceSysclk;
i2s_config->protocol = kSaiBusI2SLeft;
i2s_config->slave_master = kSaiMaster;
i2s_config->sync_mode = kSaiModeAsync;
i2s_config->watermark = watermark_val;
fifo_addr = SAI_DRV_RxGetFifoAddr(0, chan);
for (i = 0; i < 1024; i++) {
audio_buffer[i] = 1;
}
/* DMA */
i2s0_tcd = (edma_software_tcd_t *) memalign(32, sizeof(edma_software_tcd_t));
memset(i2s0_tcd, 0, sizeof(edma_software_tcd_t));
g_edma_userconfig.chnArbitration = kEDMAChnArbitrationRoundrobin;
g_edma_userconfig.notHaltOnError = false;
EDMA_DRV_Init(&g_edma_state, &g_edma_userconfig);
OSA_SemaCreate(&I2Sdma_sema, 0);
SAI_DRV_RxInit(0, i2s_config, i2s_state);
codec_init(); //Write settings to MAX98090 codec
EDMA_DRV_RequestChannel(0, kDmaRequestMux0I2S0Rx, &dmaCh0);
SAI_DRV_RxConfigDataFormat(0, &i2s_data_format);
I2S0_RMR = 1; <<< IGNORE IF RIGHT CHANNEL IS REQUIRED IN MONO, OR USING STEREO
EDMA_DRV_ConfigLoopTransfer(&dmaCh0, i2s0_tcd, kEDMAPeripheralToMemory, (uint32_t) fifo_addr, (uint32_t) audio_buffer, size, size * watermark_val, size * 1024, 1);
EDMA_DRV_StartChannel(&dmaCh0);
SAI_DRV_RxSetDmaCmd(0, true);
EDMA_DRV_StartChannel(&dmaCh0);
SAI_DRV_RxStartModule(0);
Kadaboom.
*NOTE : RX1 DOES NOT SEEM TO WORK? ALTHOUGH LISTED IN THE DATASHEET, I CAN'T REPEAT SUCCESS USING RX1, EVEN WITH ALL THE SAME SETTINGS, AND USING GET FIFO ADDRESS...
*UPDATED CODE WITH INTERRUPTS:
chan = 0;
i2s_data_format.bits = bits;
i2s_data_format.sample_rate = 8000;
i2s_data_format.mclk = 256 * i2s_data_format.sample_rate;
i2s_data_format.mono_streo = kSaiMono;
/* RECEIVER SETTINGS */
i2s_config->bclk_source = kSaiBclkSourceMclkDiv;
i2s_config->channel = chan; //SET TO 0?
i2s_config->mclk_divide_enable = true;
i2s_config->mclk_source = kSaiMclkSourceSysclk;
i2s_config->protocol = kSaiBusI2SType;
i2s_config->slave_master = kSaiMaster;
i2s_config->sync_mode = kSaiModeAsync;
i2s_config->watermark = watermark_val;
fifo_addr = SAI_DRV_RxGetFifoAddr(0, chan);
for (i = 0; i < 1024; i++) {
audio_buffer[i] = 1;
}
/* DMA */
g_edma_userconfig.chnArbitration = kEDMAChnArbitrationRoundrobin;
g_edma_userconfig.notHaltOnError = false;
EDMA_DRV_Init(&g_edma_state, &g_edma_userconfig);
SAI_DRV_RxInit(0, i2s_config, i2s_state);
EDMA_DRV_RequestChannel(0, kDmaRequestMux0I2S0Rx, &dmaCh0);
SAI_DRV_RxConfigDataFormat(0, &i2s_data_format);
EDMA_DRV_ConfigLoopTransfer(&dmaCh0, i2s0_tcd,
kEDMAPeripheralToMemory, (uint32_t) fifo_addr,
(uint32_t) audio_buffer, size, size * watermark_val, size * 1024, 1);
EDMA_DRV_StartChannel(&dmaCh0);
SAI_DRV_RxSetDmaCmd(0, true);
EDMA_DRV_InstallCallback(&dmaCh0, RxDmaCallback, (void *) process_block); <<--- I AM STILL UNSURE ON WHAT (void *) param IS SUPPOSED TO BE - THOUGHTS?
EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE, 0, true);
EDMA_DRV_StartChannel(&dmaCh0);
SAI_DRV_RxStartModule(0);
AND MY INTERRUPT FUNCTION LOOKS LIKE THIS, WHICH ALLOWS ME TO PROCESS THE APPROPRIATE BLOCK IN MAIN() USING BOOL if(processA), etc...:
void RxDmaCallback(void *param, edma_chn_status_t status) {
if (EDMA_HAL_HTCDGetCurrentMajorCount(DMA_BASE, 0) >= 128) {
processB = true;
EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE, 0, true);
} else {
processA = true;
EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE, 0, true);
}
}
Tried to import this code into KDS 1.1 - it won't generate the appropriate values in the TCD structures. Getting errors on NCE, DOE, DAE, SOE, and SAE. The TCD structure appears to be filled with 0's even when using the config loop transfer.
This previously worked, what was changed in the library that would cause it not to work now?