2025-06-02 8:55 AM - edited 2025-06-03 11:31 PM
I successfully configured the I2S2 interface to a sample rate of 48KHz. The attached WCMCU-5102 is connected to these three wires.
I2S2_WS = PA3
I2S2_CK = PA5
I2S2_SDO = PB1
FLT = GND
DMP = GND
SCL = GND
FMT = GND
XMT = Vdd (3.3V)
static void MX_I2S2_Init(void)
{
/* USER CODE BEGIN I2S2_Init 0 */
/* USER CODE END I2S2_Init 0 */
/* USER CODE BEGIN I2S2_Init 1 */
/* USER CODE END I2S2_Init 1 */
hi2s2.Instance = SPI2;
hi2s2.Init.Mode = I2S_MODE_MASTER_TX;
hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_48K;
hi2s2.Init.CPOL = I2S_CPOL_LOW;
hi2s2.Init.FirstBit = I2S_FIRSTBIT_MSB;
hi2s2.Init.WSInversion = I2S_WS_INVERSION_DISABLE;
hi2s2.Init.Data24BitAlignment = I2S_DATA_24BIT_ALIGNMENT_RIGHT;
hi2s2.Init.MasterKeepIOState = I2S_MASTER_KEEP_IO_STATE_DISABLE;
if (HAL_I2S_Init(&hi2s2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2S2_Init 2 */
/* USER CODE END I2S2_Init 2 */
}
main.c has:
uint16_t test_amplitudes[] = {0,0,65535,65535};
while (1)
{
HAL_I2S_Transmit(&hi2s2, test_amplitudes, 4 ,5000);
}
My questions:
why don't I see any analog signal? I tested two devices to no avail.
The protocol sends two signals in one sample (L and R, during the I2S2_WS signal high/low phase).
How are the values organized in storage (RAM)? I'm assuming in sequence L,R,L,R ...
I'd be glad someone could enlighten me.
2025-06-03 2:37 AM - edited 2025-06-03 6:30 AM
I'm having signal! My code looks like
uint16_t sine_amplitudes[] = {
0 , 0 ,
804 , 804 ,
1607 , 1607 ,
2410 , 2410 ,
3211 , 3211 ,
4011 , 4011 ,
4807 , 4807 ,
5601 , 5601 ,
6392 , 6392 ,
7179 , 7179 ,
7961 , 7961 ,
8739 , 8739 ,
9511 , 9511 ,
10278 , 10278 ,
11038 , 11038 ,
11792 , 11792 ,
12539 , 12539 ,
13278 , 13278 ,
14009 , 14009 ,
14732 , 14732 ,
15446 , 15446 ,
16150 , 16150 ,
16845 , 16845 ,
17530 , 17530 ,
18204 , 18204 ,
18867 , 18867 ,
19519 , 19519 ,
20159 , 20159 ,
20787 , 20787 ,
21402 , 21402 ,
22004 , 22004 ,
22594 , 22594 ,
23169 , 23169 ,
23731 , 23731 ,
24278 , 24278 ,
24811 , 24811 ,
25329 , 25329 ,
25831 , 25831 ,
26318 , 26318 ,
26789 , 26789 ,
27244 , 27244 ,
27683 , 27683 ,
28105 , 28105 ,
28510 , 28510 ,
28897 , 28897 ,
29268 , 29268 ,
29621 , 29621 ,
29955 , 29955 ,
30272 , 30272 ,
30571 , 30571 ,
30851 , 30851 ,
31113 , 31113 ,
31356 , 31356 ,
31580 , 31580 ,
31785 , 31785 ,
31970 , 31970 ,
32137 , 32137 ,
32284 , 32284 ,
32412 , 32412 ,
32520 , 32520 ,
32609 , 32609 ,
32678 , 32678 ,
32727 , 32727 ,
32757 , 32757 ,
32767 , 32767 ,
32757 , 32757 ,
32727 , 32727 ,
32678 , 32678 ,
32609 , 32609 ,
32520 , 32520 ,
32412 , 32412 ,
32284 , 32284 ,
32137 , 32137 ,
31970 , 31970 ,
31785 , 31785 ,
31580 , 31580 ,
31356 , 31356 ,
31113 , 31113 ,
30851 , 30851 ,
30571 , 30571 ,
30272 , 30272 ,
29955 , 29955 ,
29621 , 29621 ,
29268 , 29268 ,
28897 , 28897 ,
28510 , 28510 ,
28105 , 28105 ,
27683 , 27683 ,
27244 , 27244 ,
26789 , 26789 ,
26318 , 26318 ,
25831 , 25831 ,
25329 , 25329 ,
24811 , 24811 ,
24278 , 24278 ,
23731 , 23731 ,
23169 , 23169 ,
22594 , 22594 ,
22004 , 22004 ,
21402 , 21402 ,
20787 , 20787 ,
20159 , 20159 ,
19519 , 19519 ,
18867 , 18867 ,
18204 , 18204 ,
17530 , 17530 ,
16845 , 16845 ,
16150 , 16150 ,
15446 , 15446 ,
14732 , 14732 ,
14009 , 14009 ,
13278 , 13278 ,
12539 , 12539 ,
11792 , 11792 ,
11038 , 11038 ,
10278 , 10278 ,
9511 , 9511 ,
8739 , 8739 ,
7961 , 7961 ,
7179 , 7179 ,
6392 , 6392 ,
5601 , 5601 ,
4807 , 4807 ,
4011 , 4011 ,
3211 , 3211 ,
2410 , 2410 ,
1607 , 1607 ,
804 , 804 ,
0 , 0 ,
-804 , -804 ,
-1607 , -1607 ,
-2410 , -2410 ,
-3211 , -3211 ,
-4011 , -4011 ,
-4807 , -4807 ,
-5601 , -5601 ,
-6392 , -6392 ,
-7179 , -7179 ,
-7961 , -7961 ,
-8739 , -8739 ,
-9511 , -9511 ,
-10278 , -10278 ,
-11038 , -11038 ,
-11792 , -11792 ,
-12539 , -12539 ,
-13278 , -13278 ,
-14009 , -14009 ,
-14732 , -14732 ,
-15446 , -15446 ,
-16150 , -16150 ,
-16845 , -16845 ,
-17530 , -17530 ,
-18204 , -18204 ,
-18867 , -18867 ,
-19519 , -19519 ,
-20159 , -20159 ,
-20787 , -20787 ,
-21402 , -21402 ,
-22004 , -22004 ,
-22594 , -22594 ,
-23169 , -23169 ,
-23731 , -23731 ,
-24278 , -24278 ,
-24811 , -24811 ,
-25329 , -25329 ,
-25831 , -25831 ,
-26318 , -26318 ,
-26789 , -26789 ,
-27244 , -27244 ,
-27683 , -27683 ,
-28105 , -28105 ,
-28510 , -28510 ,
-28897 , -28897 ,
-29268 , -29268 ,
-29621 , -29621 ,
-29955 , -29955 ,
-30272 , -30272 ,
-30571 , -30571 ,
-30851 , -30851 ,
-31113 , -31113 ,
-31356 , -31356 ,
-31580 , -31580 ,
-31785 , -31785 ,
-31970 , -31970 ,
-32137 , -32137 ,
-32284 , -32284 ,
-32412 , -32412 ,
-32520 , -32520 ,
-32609 , -32609 ,
-32678 , -32678 ,
-32727 , -32727 ,
-32757 , -32757 ,
-32767 , -32767 ,
-32757 , -32757 ,
-32727 , -32727 ,
-32678 , -32678 ,
-32609 , -32609 ,
-32520 , -32520 ,
-32412 , -32412 ,
-32284 , -32284 ,
-32137 , -32137 ,
-31970 , -31970 ,
-31785 , -31785 ,
-31580 , -31580 ,
-31356 , -31356 ,
-31113 , -31113 ,
-30851 , -30851 ,
-30571 , -30571 ,
-30272 , -30272 ,
-29955 , -29955 ,
-29621 , -29621 ,
-29268 , -29268 ,
-28897 , -28897 ,
-28510 , -28510 ,
-28105 , -28105 ,
-27683 , -27683 ,
-27244 , -27244 ,
-26789 , -26789 ,
-26318 , -26318 ,
-25831 , -25831 ,
-25329 , -25329 ,
-24811 , -24811 ,
-24278 , -24278 ,
-23731 , -23731 ,
-23169 , -23169 ,
-22594 , -22594 ,
-22004 , -22004 ,
-21402 , -21402 ,
-20787 , -20787 ,
-20159 , -20159 ,
-19519 , -19519 ,
-18867 , -18867 ,
-18204 , -18204 ,
-17530 , -17530 ,
-16845 , -16845 ,
-16150 , -16150 ,
-15446 , -15446 ,
-14732 , -14732 ,
-14009 , -14009 ,
-13278 , -13278 ,
-12539 , -12539 ,
-11792 , -11792 ,
-11038 , -11038 ,
-10278 , -10278 ,
-9511 , -9511 ,
-8739 , -8739 ,
-7961 , -7961 ,
-7179 , -7179 ,
-6392 , -6392 ,
-5601 , -5601 ,
-4807 , -4807 ,
-4011 , -4011 ,
-3211 , -3211 ,
-2410 , -2410 ,
-1607 , -1607 ,
-804 , -804 ,
};
int size= sizeof(sine_amplitudes)/sizeof(sine_amplitudes[0]);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//HAL_I2S_Transmit_DMA(&hi2s2, sine_amplitudes, size/2);
HAL_I2S_Transmit(&hi2s2, sine_amplitudes, size,5000);
//HAL_I2S_Transmit(&hi2s2, test_amplitudes, 4 ,5000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
The timing looks also ok. 48KHz.
Now my idea is, not to use HAL_I2S_Transmit but use DMA.
I would like to have two adjacent 16bit RAM locations L,R, and configure a DMA continously running at 48KHz, reading exactly this one RAM location (two, since stereo) with one 32bit gulp and writing it to the output stream of the I2S. So the DMA transfersize would be 1 32bit word. While the DMA is continously writing out the RAM location to the DAC it is the purpose of another process, to update that RAM location with the actual data (from a lookup table e.g.).
Is the principle correct from the design standpoint? And how would I have to configure the DMA? I don't want to use interrupts or high water marks or such things. Just a DMA hard as bone continously running.
Ideas, comments?
2025-06-03 4:13 AM
I've never used I²S, but I do use DMA for other things.
Had the DMA controller in your chosen device had a double-buffer mode, this would have been super simple. Alas, it doesn't.
However, it does have a linked-list functionality, where you can create a number of different transfers. The linked-list data structure contains a number of settings for the DMA controller, plus the address of the next data structure. (This is what makes it "linked".) When the DMA controller has completed the transfer described in the first linked-list data structure, it loads the data from the second structure and continues from there. This can also be set to run in circular mode, which is what you need.
My suggestion is that you assign two memory chunks (e.g. in the form of arrays) for the audio data, and then create a linked list, with one linked-list data structure for each audio memory chunk. Then, while the DMA transfer is running, your main code needs to keep track of where the DMA controller is currently getting its data from. I imagine that you can poll the SAR (Source Address Register) to determine this, although I am not completely sure. When your code sees that the DMA controller has switched from audio data chunk 1 to chunk 2, your code can then update the contents of chunk 1. And vice versa.
Now, this isn't something I've tried myself, but it is the best I've come up with by reading the datasheet for your device. It is possible that there is a simpler solution I've overlooked. However, provided that you can build the linked-list data structure correctly, it doesn't seem to be overly complicated.
2025-06-03 6:26 AM - edited 2025-06-03 6:26 AM
Thanks, @EThom.3 , for your ideas. It would be weird to me to present the data in an array. My idea is
to write all data in one (due to stereo actually two) memory location(s). The data appears there like the landscape passes by when you look through a train waggon window (metaphorically speaking).
Assume the DMA is configured such that it always writes out (samples with the inherent 48KHz rate) that (summing point) memory location representing all analog data summed up from various sources in the application (as a digital sample value).
Supplying buffers before putting them out is simply too weird with all the timing constraints etc. I know well the double buffering technique from a data acquisition project I built in the 80ies (using a 68000 ) where data had to be collected at high speed.
Please, think again about my idea of using a DMA of size 1.
2025-06-03 7:51 AM
Hello @chriskuku
Oh... so your code is actually generating audio on-the-fly, rather than playing something predetermined? If that is the case, then you truly won't have much of a buffer. Yeah, I definitely missed something there, but now I think I understand what you are trying to do.
My understanding is that you want to continuously dump samples in a specific memory location. Whenever there is room in the I²S transmit buffer, you want the DMA to transfer whatever is in that memory location to the I²S transmit buffer. Is that correctly understood?
For this to work, I guess that you would need to set the DMA controller not to increment the source address (easy enough), and to run continuously. An infinite burst, if you like. However, I can't figure out if the latter is possible.
I also tried to see if I²S Tx Buffer "not full" could generate an event that could trigger the DMA controller, but couldn't find anything like that.
There might be a different solution. If the memory location you keep writing the samples to happens to be the transmit data register (TXDR), then I imagine that the sample will enter the transmit buffer only if there is an empty space in it. If there isn't, I guess that the sample is ignored. If this works, then I believe that you get much the same result as with a continuously working DMA controller.
No guarantees, though.
2025-06-03 8:59 AM
@EThom.3 wrote:Hello @chriskuku
My understanding is that you want to continuously dump samples in a specific memory location. Whenever there is room in the I²S transmit buffer, you want the DMA to transfer whatever is in that memory location to the I²S transmit buffer. Is that correctly understood?
Thanks for answering. Yes, exactly.
I could simulate the behaviour by using a HAL_I2S_Transmit(&hi2s2, ram_loc, 1,HAL_MAX_TIMEOUT); and wait for the SPI2 interrupt to occur. Then, in the ISR, I start the Transmit again, this repeatingly, just to simulate the principle.
2025-06-03 9:17 AM
Okay...
I know that you don't want to use interrupts, but that is exactly what I would do. Except I would reduce the interrupt code it to something like
SPI2->SPI_TXDR = SampleDump;
and omit using DMA for this.
Or perhaps try using the TXDR as the memory location as I explained above.
I don't think that I am able to contribute with anything further. Good luck with your project.
2025-06-04 12:04 AM
I tried to simulate the desired method by using interrupt.
In main.c I start a first conversion,
HAL_I2S_Transmit_IT(&hi2s2, ram_loc, 1);
I can catch (breakpoint occurs) the end of that conversion in the interrupt routine:
void SPI2_IRQHandler(void)
{
/* USER CODE BEGIN SPI2_IRQn 0 */
/* USER CODE END SPI2_IRQn 0 */
HAL_I2S_IRQHandler(&hi2s2);
/* USER CODE BEGIN SPI2_IRQn 1 */
HAL_I2S_Transmit_IT(&hi2s2, ram_loc, 1);
/* USER CODE END SPI2_IRQn 1 */
}
But the next interrupt never occurs. I would expect that the breakpoint I had set in line 8, would be hit. Nope.
Do I have to re-enable interrupts? And if, by which call?