2019-05-25 04:23 PM
I don't use libraries.
I got I2S working by polling. Below was my test. I had an array of 16 bit signed words for a tone of 1khz. The I2S pins from the MCU feed a an external DAC and produces a very nice tone.
RCC->APB1ENR |= RCC_APB1ENR_SPI3EN; // turn SPI3 clock on
Config_STM_Pin(GPIOB, 3, 6, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_Bclk B3
Config_STM_Pin(GPIOA, 15, 6, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_LRClk A15
Config_STM_Pin(GPIOD, 6, 5, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_Data D6
AudioMutePIN(1); // mute off
// set up I2SPLL
RCC->PLLI2SCFGR = (271 << RCC_PLLI2SCFGR_PLLI2SN_Pos) | (2 << RCC_PLLI2SCFGR_PLLI2SR_Pos); // this gives correct I2S_clock
// enable I2S clock
RCC->CR |= RCC_CR_PLLI2SON;
// wait until stable
while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
{}
// set up SPI4
SPI3->I2SCFGR = SPI_I2SCFGR_I2SMOD | (2 << SPI_I2SCFGR_I2SCFG_Pos);
SPI3->I2SPR = 12; // odd = 0, idiv = 6
SPI3->I2SCFGR |= SPI_I2SCFGR_I2SE;
while(1)
{
if(SPI3->SR & SPI_SR_TXE)
{
SPI3->DR = AA[I];
I++;
if(I >= Num_Audio_Samples)
I = 0;
}
}
Next step was to use DMA instead of polling.
I need to use two buffers but I can't use double buffer DMA because I have additional processing that needs to be done at the conclusion of each buffer. I removed the while loop and replaced it with 16 bit SPI DMA code that I use in a SPI process.
void Init_I2S(void)
{
dword V;
RCC->APB1ENR |= RCC_APB1ENR_SPI3EN; // turn SPI3 clock on
Config_STM_Pin(GPIOB, 3, 6, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_Bclk B3
Config_STM_Pin(GPIOA, 15, 6, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_LRClk A15
Config_STM_Pin(GPIOD, 6, 5, H_GPIO_Alt_Function, H_GPIO_Output_PushPull, SlewRate_Medium, No_Pull_Up_or_Down); // I2S_Data D6
AudioMutePIN(1);
// set up I2SPLL
RCC->PLLI2SCFGR = (271 << RCC_PLLI2SCFGR_PLLI2SN_Pos) | (2 << RCC_PLLI2SCFGR_PLLI2SR_Pos); // this gives correct I2S_clock
// enable I2S clock
RCC->CR |= RCC_CR_PLLI2SON;
// wait until stable
while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
{}
// set up SPI3
SPI3->I2SCFGR = SPI_I2SCFGR_I2SMOD | (2 << SPI_I2SCFGR_I2SCFG_Pos);
SPI3->I2SPR = 12; // odd = 0, idiv = 6
SPI3->I2SCFGR |= SPI_I2SCFGR_I2SE; // enable I2S
SPI3->CR2 |= SPI_CR2_TXDMAEN; // allow spi to tell dma
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // turn clock on for DMA1
// stream 5 channel 0 is SPI3_Xmit
DMA1_Stream5->CR = (0 << DMA_SxCR_CHSEL_Pos) | (3 << DMA_SxCR_PL_Pos) | (1 << DMA_SxCR_DIR_Pos) | DMA_SxCR_MINC | (1 << DMA_SxCR_MSIZE_Pos) | DMA_SxCR_TCIE;
// TX dma stream 5 - channel 0, single transfer, 16 bits, Priority very hi, peripheral fixed, direction memory to spi, dma controls flow, transfer complete irq Active,
DMA1_Stream5->PAR = (dword)&SPI3->DR;
NVIC_SetPriority(DMA1_Stream5_IRQn, AudioDMA_Priority);
NVIC_EnableIRQ(DMA1_Stream5_IRQn);
Audio_A_Playing = 1;
DMA1_Stream5->M0AR = (dword)Audio_A;
DMA1_Stream5->NDTR = BytesPerAudioBlock;
DMA1_Stream5->CR |= DMA_SxCR_EN; // starts DMA I2S process
} /* end init() */
void DMA1_Stream5_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
DMA1->HIFCR = DMA_HIFCR_CFEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTCIF5;
if(Audio_A_Playing) // finished playing buffer A
{
Audio_A_Playing = 0;
DMA1_Stream5->M0AR = (dword)Audio_B;
}
else
{
Audio_A_Playing = 1;
DMA1_Stream5->M0AR = (dword)Audio_A;
}
DMA1_Stream5->NDTR = BytesPerAudioBlock;
DMA1_Stream5->CR |= DMA_SxCR_EN;
// DMA finished playback an ready for a new buffer
xTaskNotifyFromISR(Audio_TaskHandle, Audio_I2S_IRQ, eSetBits, &xHigherPriorityTaskWoken ); // Audio_DMA_IRQ_Fired
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
The Audio_TaskHandle task that is released via the IRQ fills the empty buffer and does other work.
I've stared at this for two days and can't figure out why noise - not audio. The audio source is PCM data coming off an SD card. I've checked to make sure the audio data is good.
Can anyone see anything I've done incorrectly?
Thanks.
Solved! Go to Solution.
2019-05-26 12:55 AM
You haven't set PSIZE in DMA stream CR register. By default it is 8-bit, but for SPI/I2S DR register it should be 16-bit.
2019-05-26 12:55 AM
You haven't set PSIZE in DMA stream CR register. By default it is 8-bit, but for SPI/I2S DR register it should be 16-bit.
2019-05-26 04:20 AM
That was it. My mind thought it was tied directly to msize and I was overlooking that setting altogether.
I knew it had to be something simple.
Thanks for taking your time to look at the code.
2019-05-26 02:24 PM
> My mind thought it was tied directly to msize
It's the other way round, i.e. PSIZE determines both the peripheral- and memory-side transfer, unless FIFO is switched on.
In audio, whatever you mess up, it almost invariably ends with ugly noise. Wrong endian, noise. Swapped halfwords (which is the case in STM32's 16-bit I2S/SPI if you want 24/32-bit transfers) - noise. Messed up signed arithmetics (24-bit signed on arm/C is lots of fun) - noise.... ;)
JW