STM32: How can I go about outputting audio to a speaker? - Page 1
Forum section: electronics / general

author: TC2
author_rank: Newbie
timestamp: May 25, 2021, 09:00:23 pm
content: I am hoping someone can point me in the right direction as so far I haven't been able to get help on other forums. I am trying to learn how to output audio on a STM32 board. The board I have is the STM32F746ZG NUCLEO board.I have small (1-3) second audio files I plan to have stored on the internal flash of my STM32, so there is no external SD card or anything. They have 16 bit data @44.1kHz. I was able to convert to 8bit so I tried both 8 and 16 bit. I plan to output the audio to a PAM8302a amplifier which is connected to a speaker. I was following along with the AN3126 (Waveform generation using DAC) from STM. I was able to successfully generate waveforms and view them on my oscilloscope. But when I send audio data, it's just static. I tried to follow along this video, but it just sounds like static for the most part with a hint of sound in the background. Not sure what I'm doing wrong but I feel this is not the way to go about this.So does anyone know what I should be using on the board or reading on? I see things on SAI, I2S, etc and honestly just don't know where to start. Am I missing components? I'm not interested in filtering and what not at the moment, that's something I feel can come after. I just want to at least be able to output something audible for now.I have the code I used to generate the sine wave according to the doc along with my scope picCode:uint8_t samples = 100;void getSineVal(){for(int i = 0; i < samples; i++){sineVal = (sin(2*i*PI/samples)+1)*((0xFFF+1)/2);}}getSineVal();HAL_TIM_Base_Start(&htim6);HAL_DAC_Start(&hdac, DAC_CHANNEL_1);HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, sineVal, 100, DAC_ALIGN_12B_R);// sineVal being the data, 100 being the # of data pointsThe clock for TIM6 is at 80Mhz. The prescalar is 80-1, the period is 100-1, and the #samples is 100. So 80Mhz/(80 * 100 * 100) = 100Hz.Here is the picture of the sine wave I generated at 100 HzNow here is the code I made recently for the audioCode:uint8_t *p = (uint8_t *)&audioArray;... // I didn't paste here but I move the pointer to the data sectionif(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13) == GPIO_PIN_SET){uint8_t *pp = (uint8_t *)p;uint32_t wavBuffer1;uint32_t wavBuffer2;flg_dma_done = 1;int count = 65536;while(count > 0){for(int i = 0; i < 512; i++){wavBuffer2 = *pp;pp++;}while(!flg_dma_done){ // check if the flag is done__NOP();}flg_dma_done = 0; // clear/reset the flagcount = count - 512; // 512 bytes decreaseHAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_1); // Stop the transferHAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer2, 512, DAC_ALIGN_12B_R); // Start transmissionfor(int i = 0; i < 512; i++){wavBuffer1 = *pp;pp++;}while(!flg_dma_done){ // check if the flag is done__NOP();}flg_dma_done = 0; // Clear/reset the flagcount = count - 512;HAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_1); // Stop the transferHAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer1, 512, DAC_ALIGN_12B_R); // Start transmission}}...void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef* hdac){flg_dma_done = 1;}Here is an image of the schematicThanks for reading. I've been on this for a month to a month and a half now when I can and am just completely lost. It's a personal project for me, I've recently graduated in the past year and I'm trying to learn STM32. Thanks.P.S: Yes, I do have the DAC in buffer mode, and the sine wave produces an audible tone.

author: james_s
author_rank: Super Contributor
timestamp: May 25, 2021, 10:22:02 pm
content: In your diagram you do not have a complete circuit. Is there an implied ground wire that is not shown?

author: newbrain
author_rank: Super Contributor
timestamp: May 26, 2021, 08:18:12 am
content: I did not check the code (and there should be no difference whether a sine or something else is generated, as long as we are talking about pre-calculated data), but I have some doubts on the HW side:Is the sine wave taken from the DAC or the amplifier output?If the former: remember that the amplifier has a gain of ~24 dB, so your 3.3 V p2p sine will become a very nice square wave; you should attenuate the signal with a resistor divider (or lower the digital values, but that would increase quantization noise).Also, a DC blocking cap is needed on the amplifier input, but if you are using a module, that might be already included.SAI and I2S are audio synchronous serial interfaces, and they are used with a separate codec - there's no codec on a Nucleo board.

author: ucanel
author_rank: Regular Contributor
timestamp: May 26, 2021, 09:01:42 am
content: You may have flash read problem.Check the values you copy to the ram in the debugger.

author: TC2
author_rank: Newbie
timestamp: May 26, 2021, 09:57:25 pm
content: Quote from: james_s on May 25, 2021, 10:22:02 pmHi, you are right, it is not complete, I was meaning to just show the general connections. The STM32 is connected to my PC so it shares the ground I believe? The amp is connected to the bench PSU but to the positive and negative, not the earth ground.Quote from: newbrain on May 26, 2021, 08:18:12 amThe sine wave is taken from the DAC, it is not from the PAM amplifier. But wow, this is something I've forgotten from my classes that could happen.You are right, the datasheet discusses choosing a DC input capacitor. I do have a module and will have to double check this week. But without it, would it be that much noise where it makes the audio inaudible?Quote from: ucanel on May 26, 2021, 09:01:42 amHiya. In debug mode when I was debugging, I observed the pointer that points to the audioArray. It seems to get the correct values and assign it to the buffers, so not sure if that is an issue.

author: james_s
author_rank: Super Contributor
timestamp: May 26, 2021, 10:02:23 pm
content: Quote from: TC2 on May 26, 2021, 09:57:25 pmIt is critical that both the STM32 board and the amplifier board share a common ground. Do not rely on the earth ground of your PC through the USB cable, even if that works in some situations it is just asking for trouble. Connect a wire from the ground node on the STM32 board to the ground on the amplifier board. You will want to have a DC blocking capacitor too in series with the audio line too, value is not critical, 10uF electrolytic is probably reasonable.

author: DavidAlfa
author_rank: Super Contributor
timestamp: May 26, 2021, 11:26:36 pm
content: I recently used a cheap pcm5102 i2s dac($4), worked great in dma mode while reading and decoding MP3 files from a pendrive.If you are interested I'll share the project when I get back to home in a few days.

author: ucanel
author_rank: Regular Contributor
timestamp: May 27, 2021, 12:17:05 am
content: Last answer at that link:https://community.st.com/s/question/0D50X0000BEXUjV/haldacstartdma-stops-on-start-calling-haldacerrorcallbackch1If that is not the issue,what does oscilloscope shows for the memory to dac version?Maybe the timing is an issue you are interrupting the dac (start stop for each 512 bytes).Dac output new value frequency and the audio sample frequency must be same that need to be checked.And if you can make it work there is a better way by using Full and Half Transfer completed Callbackin this case you do not need to use two wavbuffers,use one wavBuffer with first 512 registered,start dac dma, (dma circular mode)you just need to renew first half of the wsvBuffer values at the half transfer complete callback andrenew second half of the wavBuffer values at the Full TCC.So you will not start stop the Dac Dma until the end of the all values sended.

author: langwadt
author_rank: Super Contributor
timestamp: May 27, 2021, 12:22:14 am
content: check the data format, e.g. offset binary vs. two-complement

author: Siwastaja
author_rank: Super Contributor
timestamp: May 27, 2021, 01:04:17 pm
content: As the oscillosscope is already showing a nice sine wave as the output of the DAC, it makes no sense to debug anything before that; it's not a microcontroller or code question at all, the problem is what comes after the DAC output, i.e., connection to the amplifier and the amplifier itself.Really, I can't think anything else than* Excessive gain resulting in massive distortion in the amplifier* Lack of AC coupling* Miswiring or abusing the amplifier chip in some way.

author: ledtester
author_rank: Super Contributor
timestamp: May 27, 2021, 01:20:15 pm
content: Quote from: TC2 on May 25, 2021, 09:00:23 pmWhat's SD (shutdown) connected to?Are you using the bare chip or a PAM8302 module like from Adafruit/ebay/aliexpress?Can you test with a different chip/module?

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 27, 2021, 03:29:05 pm
content: Quote from: Siwastaja on May 27, 2021, 01:04:17 pmExactly my thought, but I've found the OP's explanation not very clear. So first thing they should do: use the "problematic" setup exactly (DAC connected to powered on amp, same MCU code, etc) and look at the DAC signal with a scope (while it's connected to the amp!) If everything looks correct, then obviously it's an amp setup issue. If the signal doesn't look correct, but does when you disconnect the DAC from the amp, then there's something faulty in your amp or your connections somehow. As always, debug one thing at a time and with some logic.

author: TC2
author_rank: Newbie
timestamp: May 27, 2021, 09:52:30 pm
content: Quote from: james_s on May 26, 2021, 10:02:23 pmHiya. Okay, I connected GND from the STM32 to the GND of the amplifier, and the speaker negative terminal to the ground as well. I guess that makes the - terminal ground basically then? I added the capacitors which got rid of the static noise at the speaker, and adding the connection of the GND from the STM32 produces an audible tone.Quote from: SiliconWizard on May 27, 2021, 03:29:05 pmThe sinewav I generated is a 441Hz Sine Wave which I checked from the STM32 DAC output and it looked fine. On the +,- output of the amplifier without the speaker connected, I get the following output.Is this the "nice" square wave that @newbrain mentioned? As you all can see, it doesn't look that nice! These "square waves" (if it is indeed a square wave) appear for around 30ms, then flatlines for around 260ms.Quote from: ledtester on May 27, 2021, 01:20:15 pmI read to leave SD floating if you don't plan to use the shutdown feature.I'm using a module from adafruit.I do not have another module.Quote from: DavidAlfa on May 26, 2021, 11:26:36 pmThanks for the offer, but I am looking to specifically use the STM board's DAC to do this. But if there is something that I can learn from your project maybe it is not a bad idea.As for responses related to the code, I will poke around as per the responses.I will mention that the @ucanel that the DAC frequency does indeed match the audio sampling frequencyI hope I don't seem disinterested in responding because of how long it's taking me, I just have some other things going on, so I appreciate the help

author: james_s
author_rank: Super Contributor
timestamp: May 27, 2021, 11:01:12 pm
content: Quote from: TC2 on May 27, 2021, 09:52:30 pmYes, the - terminal is ground, that's typically what - implies. I don't want to confuse you by mentioning this, but technically "ground" can be any node you want, and just serves to identify a point to which all other voltage measurements are referenced to, but typically in most circuits it will be the node that the - terminal of the battery or power supply connects to.Sounds like you're all set then, you have a complete circuit with the input to the amplifier and capacitor to block the DC offset and now you get a tone which probably means that other audio will play fine too.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 28, 2021, 12:50:27 am
content: Quote from: TC2 on May 27, 2021, 09:52:30 pmIt's a Class-D amplifier. So it doesn't look all that surprising. Running it without any speaker connected will give you a nasty looking output. That is normal.From the datasheet, it shouldn't require any output capacitor, as it's a "differential" output. Of course, for that to work properly, the speaker must be connected to the + and - outputs of the amplifier IC. Do NOT ground any of the speaker's terminals!But it does require an input capacitor - to accomodate the internal biasing. It's clearly stated in the datasheet.

author: james_s
author_rank: Super Contributor
timestamp: May 28, 2021, 04:34:18 am
content: I should have looked closer at that schematic, when I said the - terminal I meant what they're showing as A-, which is tied to Gnd. I haven't looked at the datasheet but that looks like a differential input and they're just tying one side to ground to make it single ended.

author: boB
author_rank: Frequent Contributor
timestamp: May 28, 2021, 06:56:50 am
content: Is the signal AC coupled ? Maybe you need to use a capacitor to isolate the DC output of the DAC ?Is the speaker cone being pushed out or sucking in all the way ? That would be a sine of DC.Is the speaker good enough to reproduce 440 Hz ? Maybe try a bit higher frequency ? 1 or 2 kHz ?The first scope shot looked great. The second one, if it is a switching amp might need an L-C output filter ?Either way, I would think you would hear "something " ?boB

author: TC2
author_rank: Newbie
timestamp: May 28, 2021, 11:22:30 pm
content: Quote from: james_s on May 27, 2021, 11:01:12 pmUnfortunately it does not seem to be the caseQuote from: ucanel on May 27, 2021, 12:17:05 amI have set it to word, it was set to byte. Now there when I send the audio, no output at the speaker. A byte makes the static sound and I get a weird output on the scope from the DAC directly, but on word I just get around a 3V flat output for a while.Either way, I'm getting an underrun from the DAC_SR (dac status register) on channel 1 which I'm using. I checked the DMA registers and it doesn't have any error flags up for the transfers, it says the transfer is complete.Quote from: boB on May 28, 2021, 06:56:50 amI produced a 1kHz wave and I can hear a tone from the speaker. Though I should mention I can't really produce sine waves higher. I had this problem before, and I realized the timer I was using (TIM6) was set with either HSI or PLLCLK, and I was using HSE. Then I was able to produce higher frequency sine waves, but now I again have the issue where it's flat lining if I try to make for example a 4kHz sine wave.Edit: Okay referring to me generating the sine wave, this issue has gone away when I removed the Start_Dma from outside the while loop into inside it. I have a 22.5kHz wave on my screen at the moment for example. Why do the higher frequency sine waves only generate inside the while loop versus being outside in the initialization area?NOTE: My sine wave generation code is in my main project, where the audio I'm currently working on inside a separate project, just to figure it out before implementing, so that's why I'm saying the sine wave is not in the while loop. My audio project has the Start_Dma in the while loop, but I don't know why it's flat lining there as I mentioned above. Anyways, I applied the 22.55kHz to the amplifier and can hear a extremely high pitch sound as expected.Quote from: langwadt on May 27, 2021, 12:22:14 amDoesn't this only matter for signed values? I'm using unsigned.

author: TC2
author_rank: Newbie
timestamp: June 01, 2021, 10:24:31 pm
content: Quote from: ucanel on May 27, 2021, 12:17:05 amI'm a bit confused on how half callback and callback works.Okay so I have a 512 byte sized buffer wavBuffer.I will fill wavBuffer with the first 512 values of X data points of the WAV.I start the DAC DMA in circular mode, passing wavBuffer, length 1024.I am confused on what I do at the half call back. Do I now refill wavBuffer with the next 512 bytes?Then at full TCC, I pass the next 512, and it keeps repeating until there is no more audio data to be sent?Also, is it assumed I need to change the digital values by multiply them by (0xFF+1)/2?

author: ucanel
author_rank: Regular Contributor
timestamp: June 01, 2021, 11:13:42 pm
content: Sorry i explained it for 512 byte for one circle DMA stream.If i need to explain it according to your original code:We have 1024 bytes to send to DAC via DMA.Code:HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer1024, 1024, DAC_ALIGN_12B_R); // Start transmissionwavBuffer1024 is our wav values buffer.Just for the first samples we fully load the 1024 samples to wavBuffer1024.And start the dac dma.When this dma dac reaches the 512th sample it gives us a half complete callbacksays us that i have send the half of your buffer.So we have our buffers first half free of use, what we dowe are preparing the next 1024 bytesbecause our dac dma is now sending first 1024 bytes' second half,we are thinking ahead of time and right nowwe need to fill the wavBuffer1024's first half with second 1024 wav values first 512 bytes.When the full transfer complete callback fires we already prepared the next 1024 wav values first half to the wavBuffer1024 sonow we have a time to fill second half of the wavBuffer1024 with second 1024 wav values last 512bytes.It confused me too for the first time.If you think it on the paper it is obvious.You have a interrupt that tells you your buffer's first half is sended and then tells you your buffer's all of it sendedand you need to fill that buffer's first half while it's second half is being send,and you need to fill that buffer's second half while it's first half is being send.

author: TC2
author_rank: Newbie
timestamp: June 02, 2021, 01:36:41 am
content: Quote from: ucanel on June 01, 2021, 11:13:42 pmOkay, this is what I came up with. Btw, I used 1024 because I wasn't clear whether the document I talked about in my original post meant 512 was the half, so total is 1024 or 512 is total. I will go back to my 512 example.Code:uint8_t *p = (uint8_t *)&audioArray;uint8_t *pp; // 8, not 16 since it skips bytesint count = 65536;uint8_t volatile wavBuffer1;int main(){HAL_TIM_Base_Start(&htim6);HAL_DAC_Start(&hdac, DAC_CHANNEL_1);initializePointer(); // This function moves *p to the datawhile (1){if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET){ // Button on boardfor(int i = 0; i < 512; i++){wavBuffer1 = *pp;pp++;}count -= 512;while(count > 0){HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer1, 512, DAC_ALIGN_12B_R);}count = 65536; // Reset countHAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_1);pp = (uint8_t *)p; // Reset pointer back to beginning of audio data}}}void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef* hdac){for(int i = 0; i < 255; i++){wavBuffer1 = *pp;pp++;}HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); // for testing}void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef* hdac){for(int i = 255; i < 512; i++){wavBuffer1 = *pp;pp++;}count -= 512;HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); // for testing}Did I understand the concept correctly? If so, my audio sounds like this nowhttps://sndup.net/53qz. Barely audible. Here is also the outputbeforeit goes through the amplifierHere it isafterit goes through the amplifierNote I took these snapshots at random points of the reading.Edit: I guess my brain turned to mush after 8 hours. I should of just hooked up my second probe and put the waves side by side. Sorry, I will take another reading when I can.

author: ucanel
author_rank: Regular Contributor
timestamp: June 02, 2021, 10:21:26 am
content: Yes more like it.Maybe there is some little problems i added ? mark in your code.A question you have 65535 sampleswhat is your sample rate?For example if you have sample rate 8 kHz ( bare minimum for audio)it means you have 8000 samples per secondand your 65k memory can hold almost 8 seconds of audio data.You may also check audio play duration for to be sure.If your pointer settings and iterations and your sample rate is correct you now need to be hearing the good sound.Code:uint8_t *p = (uint8_t *)&audioArray;uint8_t *pp; // 8, not 16 since it skips bytes? int32t count = 65536;uint8_t volatile wavBuffer1;int main(){HAL_TIM_Base_Start(&htim6);HAL_DAC_Start(&hdac, DAC_CHANNEL_1);initializePointer(); // This function moves *p to the datawhile (1){if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET){ // Button on boardfor(int i = 0; i < 512; i++){wavBuffer1 = *pp;pp++;}count -= 512;//you just need to start dma once// if you start it again and again// it may cause jitterHAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer1, 512, DAC_ALIGN_12B_R);? while(count > 0){ };count = 65536; // Reset countHAL_DAC_Stop_DMA(&hdac, DAC_CHANNEL_1);pp = (uint8_t *)p; // Reset pointer back to beginning of audio data}}}void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef* hdac){?for(int i = 0; i <= 255; i++){ //first 512/2wavBuffer1 = *pp;pp++;}HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); // for testing}void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef* hdac){?for(int i = 256; i <= 511; i++){ // second half 512/2wavBuffer1 = *pp;pp++;}count -= 512;HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_11); // for testing}Also check whether it enters calbacks if notyou may need to useHAL_DAC_Start_DMA_ITinstead ofHAL_DAC_Start_DMA.I do not remember correctly, you may need to look at the callback examples.Edit:Code:HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)wavBuffer1, 512, DAC_ALIGN_12B_R);DAC_ALIGN_12B_RIf your samples are 8bits you need to change this parameter.

author: AndyC_772
author_rank: Super Contributor
timestamp: June 02, 2021, 01:01:13 pm
content: Quote from: TC2 on June 02, 2021, 01:36:41 amThat looks to me as though your audio data is using signed binary, but the DAC expects unsigned.Try adding 0x8000 to every sample and see if that helps.

author: TC2
author_rank: Newbie
timestamp: June 02, 2021, 11:21:36 pm
content: Quote from: ucanel on June 02, 2021, 10:21:26 amMy sample rate now is 44.1kHz. I do not see any interrupt functions for the DAC according to my manual. But if you take a look at my callbacks, I actually toggle a pin for testing to see if it reaches the callback, and I hooked up my oscilloscope. I observed a square wave when I pressed the button until the audio finished, so I indeed believe it's working. I also switched it to the 8B, sorry about that.Well I havegood news! I can now hear audio!!. Thanks for the help with creating the callbacksQuote from: AndyC_772 on June 02, 2021, 01:01:13 pmI was attempting to but I noticed in the debugger it wasn't adding 0x8000 even though I had it. Then I realized oh right, my wavBuffer1 is 8bit, let me change it to 16 bit. Once I did, I heard audio! Even without the 0x8000 I can hear audio now.How were you able to tell it was signed from the scope? My stuff is put away right now so I can't exactly answer it myself at the moment. Also I'm a bit confused on how the data was signed, since in my audiodata array I have it declared as an unsigned 8 bit array, and the program I used WavToCode I set the output to unsigned. Am I confused on something?Also another thing is my sound is audible, but the sound level sounds a bit low compared to the sine wave which the sound of the tone was very loud. What could be a cause?

author: ucanel
author_rank: Regular Contributor
timestamp: June 03, 2021, 10:08:59 am
content: QuoteGlad to hear that. I would also be happy to hear what it sounds like.Maybe you can share with us a working code template, cubemx setting etc.About sound level:It is probably because of your wav sample values 0~255and dac expects 0~4096.If i recall correctly you can not change the dac scale.DACoutput = (VREF+) x (DOR/4096)DOR corresponds to (comes from) your wav buffer values.So you may try to use 16bit wav buffer,shift your 0~255 wav values to 4bit left beforehand and save it to the flash memory like that,use 12B_R dac mode.(maybe just using 12B_R dac mode with unmodified wav values may do the trick try them both)If it does not solves the problem there is another thing to look at.It is about buffered dac modeand dac output channel load,at figure 10 in that document:AN3126 Application noteAudio and waveform generation using the DAC in STM32 productsNext thing to do,the dac module is not common, many low end mcus does not have it or just have one dac module.You may have achive the same thing with PWM module.Timer triggered Dma, callbacks, etc. are almost the same,you will set a stand alone PWM with output frequency at least wav frequency * 2,you will set a timer triggered dma in circular mode,that dma will send the wav value buffer to PWM CCRx,at the pwm pin output you will filter the pwm carrier frequency with a simple RC filter.

author: AndyC_772
author_rank: Super Contributor
timestamp: June 03, 2021, 05:44:12 pm
content: Quote from: TC2 on June 02, 2021, 11:21:36 pmIn a word, experience. I've seen this bug before.Look closely at the shape of the waveform. It has very distinct upper and lower limits, and it spends most of the time close to one or the other of them. This is exactly what happens during quiet periods, which would correctly have values 0x8000 +/- some small value 'a', but instead end up taking values that are within 'a' of either 0x0000 or 0xffff.