2024-09-05 04:07 PM - edited 2024-09-05 04:25 PM
Hi,
I am using a STM32F407 with the TLV320AIC3204 audio codec. I have verified that the I2S works with a logic analyzer:
I am trying to read the data using DMA (I tried both circular and normal).
Here is my I2S configuration:
I made sure to add the following line to fix the DMA bug:
__HAL_RCC_DMA1_CLK_ENABLE();
Here is the code that is giving me problems:
#include "dsp.h"
#define BUFSIZE 8192
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
static volatile int64_t sin_a;
static volatile int64_t cos_a;
static volatile int64_t sin_b;
static volatile int64_t cos_b;
uint16_t adcBuf[BUFSIZE];
static volatile int done;
static int bufLen;
static int32_t combineUint(uint16_t a, uint16_t b){
return (int32_t)(((uint32_t)a << 16))| (uint32_t)b ;
}
static int processArray(uint16_t* arr, int len){
for (int i = 0 ; i < len; i+= 16){
cos_a += combineUint(arr[i], arr[i+1]);
cos_b += combineUint(arr[i+2], arr[i+3]);
sin_a += combineUint(arr[i+4], arr[i+5]);
sin_b += combineUint(arr[i+6], arr[i+7]);
cos_a -= combineUint(arr[i+8], arr[i+9]);
cos_b -= combineUint(arr[i+10], arr[i+11]);
sin_a -= combineUint(arr[i+12], arr[i+13]);
sin_b -= combineUint(arr[i+14], arr[i+15]);
}
}
void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s2){
if (bufLen >= 128){
//processArray(adcBuf, bufLen );
}
}
void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s2){
if (bufLen >= 128){
// processArray(adcBuf+bufLen , bufLen );
} else {
// processArray(adcBuf, bufLen * 2);
}
done = 1;
}
int dspDone(){
return done;
}
void startDSP(int avg){
bufLen = avg * 8;
if (bufLen * 2 > BUFSIZE ) return;
sin_a = 0;
sin_b = 0;
cos_a = 0;
cos_b = 0;
done = 0;
// while (HAL_GPIO_ReadPin (GPIOB, GPIO_PIN_12));
// while (!HAL_GPIO_ReadPin (GPIOB, GPIO_PIN_12)); // Wait for WS = LOW
HAL_I2S_Receive_DMA(&hi2s2, adcBuf, bufLen);
}
static float magnitude (int64_t a, int64_t b){
float c = a;
float d = b;
return sqrtf(c*c+d*d);
}
void getResults(float *mag, float *ang){
*mag = magnitude(sin_a, cos_a)/magnitude(sin_b, cos_b);
*ang = atan2(sin_a, cos_a) - atan2(sin_b , cos_b);
}
With lines 63 and 64 commented, I get random data sometimes (see picture below where I breakpointed on line 78, which is called after done = 1. You can see that the data is bit shifted by a random amount. There is nothing connected to the ADC, so the MSB should be close to 0xFFFF or 0x0000. However, these values are not.
I was able to fix the problem somewhat by uncommenting lines 63 and 64, which only allows the I2S to start when WS is low. (Trying it when WS=high gave me garbage every time). The issue now is that I am seeing the data offset by 16 bits sometimes.
For example, here, the MSB are in the even positions (all of the numbers in the even positions don't change very much, and are close to 0xFFFF):
However, if I wait until the breakpoint is triggered again, I get
Now the MSB is in the odd positions. Any idea why this is happening? With lines 63 and 64 uncommented, on most runs the MSB is in the even positions the first time, and every time after that the MSB is in the odd positions.
Here is my main function:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
__HAL_RCC_DMA1_CLK_ENABLE();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2S2_Init();
MX_DAC_Init();
MX_I2C1_Init();
MX_SPI3_Init();
MX_USART1_UART_Init();
MX_ADC1_Init();
MX_DMA_Init();
MX_USB_DEVICE_Init();
MX_ADC2_Init();
/* USER CODE BEGIN 2 */
resetRegisters();
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
HAL_DAC_Start(&hdac, DAC_CHANNEL_2);
setVoltage(-1.5, 1);
setVoltage(-1.5, 2);
HAL_Delay(100);
tlv320aic3204_init();
resetLMX();
int counter = 10;
float voltage = -2;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (counter > 226){
counter = 10;
}
if (voltage > -1.45){
voltage = -2;
}
setFrequency(counter * 100 * MHz, 2);
HAL_Delay(20);
counter++;
voltage += 0.1;
setVoltage(voltage, 2);
setVoltage(voltage, 1);
startDSP(16);
while (!dspDone());
float m;
float a;
getResults(&m, &a);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
2024-09-05 04:56 PM
For the need to wait for a certain WS level, see errata.
And use circular DMA.
JW
2024-09-05 05:13 PM - edited 2024-09-05 05:22 PM
I tried that by using this:
// while (HAL_GPIO_ReadPin (GPIOB, GPIO_PIN_12)); // while (!HAL_GPIO_ReadPin (GPIOB, GPIO_PIN_12)); // Wait for WS = LOW
Is there a smarter way of doing it? Right now I'm just waiting for the WS pin to change from high to low, and then I start I2S. I also still have issues where sometimes the data is shifted by 16 bits. Usually, the first time the breakpoint is triggered the data is correct, but on the next breakpoint trigger, there is a 16 bit offset with the data.
I also tried using circular DMA. However, if I stop the DMA and start it again, the problem comes back. I need to stop the DMA because I am trying to build a crude network analyzer, and the data is only valid after the frequency of the oscillators settles. My plan was to stop the DMA after the amount of data needed is obtained, and then start it again once the frequency of the oscillators changes to a new one. Basically I need to change the frequency of another component, wait a bit, start DMA to collect some points, stop DMA after the points are collected, and then repeat with other frequencies.
2024-09-05 06:07 PM
If you want WS to be low, shouldn't your statements be reversed?
// WS is high or low here
while (!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)); // wait while WS is low
// WS is high here, but could be mid-transaction
while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)); // wait while WS is high
// WS just became low
> MX_DMA_Init();
This statement should be before the I2S gets initialized. Is this an old project? This was a bug that was fixed a few years ago. See:
Solved: MX_DMA_Init order in the main.c file generated by ... - STMicroelectronics Community
> There is nothing connected to the ADC, so the MSB should be close to 0xFFFF or 0x0000
I wouldn't ascribe any significance to the ADC result if nothing is connected to the input.
2024-09-05 08:54 PM
@TDK wrote:If you want WS to be low, shouldn't your statements be reversed?
// WS is high or low here
while (!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)); // wait while WS is low
// WS is high here, but could be mid-transaction
while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12)); // wait while WS is high
// WS just became low
Sure, I might have made a typo. Flipping it around causes the data to be shifted by a random amount every time, which is what would happen according to the errata. Not flipping it around causes the data to be shifted by 0 or 16 bits, which I am currently trying to fix.
> MX_DMA_Init();
This statement should be before the I2S gets initialized. Is this an old project? This was a bug that was fixed a few years ago. See:
Solved: MX_DMA_Init order in the main.c file generated by ... - STMicroelectronics Community
I am using a older version of CubeIDE, but I fixed the problem above with this line:
__HAL_RCC_DMA1_CLK_ENABLE();
I also tried manually swapping the position of the two inits, but that did not do anything to fix the issue.
@TDK wrote:> There is nothing connected to the ADC, so the MSB should be close to 0xFFFF or 0x0000
I wouldn't ascribe any significance to the ADC result if nothing is connected to the input.
I checked with a logic analyzer and the output of the ADC was 0xFF..... (see picture attached). At the very least the first 16 bits should be at least 0xFF00 which is 65280. I also connected a 50 ohm load to the inputs, which did nothing.
2024-09-05 10:46 PM
Maybe I have overseen something in the posts above, but why do you use 32 bit I2S with half-word = 16 bit DMA?
If you're using only 16 bits ,maybe you can set the Codec to 16 bit, or maybe it's enough to just make the STM's I2S run with 16 bit?
2024-09-05 11:06 PM
If we look at the HAL_I2S_Receive_DMA function, it only accepts 16 bit arrays for pData. So I am forced to use 16 bits for DMA too. I tried using "Word" instead of "Half-Word", but it just writes 16 bit values regardless.
You can also see that if it is 24 or 32 bits, the size of the array is multiplied by 2, since each sample takes up two 16 bit spaces.
Once I fix the issue I can rewrite the code to accept an array of 32 bits, which is cast into an array of 16 bits.
/**
* @brief Receive an amount of data in non-blocking mode with DMA
* @PAram hi2s pointer to a I2S_HandleTypeDef structure that contains
* the configuration information for I2S module
* @PAram pData a 16-bit pointer to the Receive data buffer.
* @PAram Size number of data sample to be sent:
* @note When a 16-bit data frame or a 16-bit data frame extended is selected during the I2S
* configuration phase, the Size parameter means the number of 16-bit data length
* in the transaction and when a 24-bit data frame or a 32-bit data frame is selected
* the Size parameter means the number of 24-bit or 32-bit data length.
* @note The I2S is kept enabled at the end of transaction to avoid the clock de-synchronization
* between Master and Slave(example: audio streaming).
* @retval HAL status
*/
HAL_StatusTypeDef HAL_I2S_Receive_DMA(I2S_HandleTypeDef *hi2s, uint16_t *pData, uint16_t Size)
{
uint32_t tmpreg_cfgr;
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(hi2s);
if (hi2s->State != HAL_I2S_STATE_READY)
{
__HAL_UNLOCK(hi2s);
return HAL_BUSY;
}
/* Set state and reset error code */
hi2s->State = HAL_I2S_STATE_BUSY_RX;
hi2s->ErrorCode = HAL_I2S_ERROR_NONE;
hi2s->pRxBuffPtr = pData;
tmpreg_cfgr = hi2s->Instance->I2SCFGR & (SPI_I2SCFGR_DATLEN | SPI_I2SCFGR_CHLEN);
if ((tmpreg_cfgr == I2S_DATAFORMAT_24B) || (tmpreg_cfgr == I2S_DATAFORMAT_32B))
{
hi2s->RxXferSize = (Size << 1U);
hi2s->RxXferCount = (Size << 1U);
}
else
{
hi2s->RxXferSize = Size;
hi2s->RxXferCount = Size;
}
/* Set the I2S Rx DMA Half transfer complete callback */
hi2s->hdmarx->XferHalfCpltCallback = I2S_DMARxHalfCplt;
/* Set the I2S Rx DMA transfer complete callback */
hi2s->hdmarx->XferCpltCallback = I2S_DMARxCplt;
/* Set the DMA error callback */
hi2s->hdmarx->XferErrorCallback = I2S_DMAError;
/* Check if Master Receiver mode is selected */
if ((hi2s->Instance->I2SCFGR & SPI_I2SCFGR_I2SCFG) == I2S_MODE_MASTER_RX)
{
/* Clear the Overrun Flag by a read operation to the SPI_DR register followed by a read
access to the SPI_SR register. */
__HAL_I2S_CLEAR_OVRFLAG(hi2s);
}
/* Enable the Rx DMA Stream/Channel */
if (HAL_OK != HAL_DMA_Start_IT(hi2s->hdmarx, (uint32_t)&hi2s->Instance->DR, (uint32_t)hi2s->pRxBuffPtr,
hi2s->RxXferSize))
{
/* Update SPI error code */
SET_BIT(hi2s->ErrorCode, HAL_I2S_ERROR_DMA);
hi2s->State = HAL_I2S_STATE_READY;
__HAL_UNLOCK(hi2s);
return HAL_ERROR;
}
/* Check if the I2S is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->I2SCFGR, SPI_I2SCFGR_I2SE))
{
/* Enable I2S peripheral */
__HAL_I2S_ENABLE(hi2s);
}
/* Check if the I2S Rx request is already enabled */
if (HAL_IS_BIT_CLR(hi2s->Instance->CR2, SPI_CR2_RXDMAEN))
{
/* Enable Rx DMA Request */
SET_BIT(hi2s->Instance->CR2, SPI_CR2_RXDMAEN);
}
__HAL_UNLOCK(hi2s);
return HAL_OK;
}
2024-09-05 11:11 PM
Well, why then use HAL with these limitations? ;)
2024-09-06 04:08 AM - edited 2024-09-06 04:10 AM
Said erratum says:
Make sure the I2S peripheral is enabled when the external master sets the WS line at:
High level when the I2S protocol is selected.
That's why the original version is OK; provided that the functions called after that are fast enough to enable I2S within the high WS(LRCK) period (including any interrupt which may come into way).
And, as I've said, use circular mode, and then never disable I2S and DMA. Write your program so that it simply ignores data when they are not valid.
Or, do disable/reenable them, but then you have to be careful to follow the very same procedure as when enabling them originally; i.e. clear all DMA status bits, enable DMA, then wait until the proper WS (LRCK) level, then within the WS-high period enable I2S.
As @LCE said above, by using Cube/HAL you give up control, read, you may be lucky and then "things work", but you shall not complain/it's your problem, if they won't. Write your own program if you want control. The 16-bit SPI/I2S interface is a hardware limitation (legacy from ST's 16-bit mcus), though.
JW