13_title.png

In this chapter, we will be making some noise!  Audio generation was something that I found difficult to understand and implement in my first embedded design.  For whatever reason, I could not find many examples to help me create loud and clear audio with an MCU.  Things can get complicated really fast with new terminology, and everything seems to be written for an expert, but I figured it out for the most part and I will share my findings.

 

Many embedded devices utilize audio sound effects such as beeps or clicks for user feedback, and some devices can also deliver complex audio like speech and music.  These are two different goals for your embedded project, and they have different requirements.  In this chapter, we will learn how to generate high quality audio from sound files that you can create on your computer.  This is in some ways easier than creating more primitive sound effects on an MCU.  You will also learn how to read the audio data files from a Micro SD card that contain the sound files, with the help DMA.  The Ping Pong DMA mode is used and explained.

 

The goal in this chapter is to just get our feet wet in learning about one way that audio can be generated by an MCU.  There are many ways to do it, and we will be exploring a few more ways to accomplish the same task in the next chapter, where we will also learn about amplification.  For now, we will be focusing on simply creating the audio with the help of an external Digital to Analog Converter (DAC) that we can communicate with using our USART peripheral in Inter-IC Sound (I2S) mode.

 

Materials Needed for this Chapter:

 

Cirrus Logic CS4344 Overview

The Cirrus Logic CS4344 is a 24-bit, 192 kHz DAC that translates digital data received on its inputs via I2S into analog sound.  What do those numbers mean?  The number of bits is used to compute the maximum number of voltage steps used to reproduce the analog waveform.  Taken together, these are called bit rates.  With 24 bits, that creates about 17 million discrete voltage steps of resolution, which is incredibly accurate for even the most discerning audiophile.  The frequency of the DAC dictates how fast the DAC can output a new voltage value.  Since human hearing can only discern up to 20 kHz of audio, 192 kHz gives about 10 samples per period at 20 kHz, and 64 samples per period down in the 3 kHz range, where more of the audio information for human consumption is located.   Therefore, the CS4344 is a very capable device for high quality audio.

13_bit_rates.png
The I2S bus consists of a serial data signal called SDIN, a clock to latch the data called SCLK, and a word clock, which the CS4344 calls a LRCLK, for left-right clock.  This is the clock that selects whether the data on SDIN is for the left or right channel.  The CS4344 also requires a master clock called MCLK which is separate from the I2S bus requirement and drives the internal circuitry for the DAC. 

 cs434x_diagram.jpg

The I2S data that is sent from the MCU to the CS4344 is uncompressed digital data that communicates a single 24-bit voltage per sample.  Since the CS4344 is a stereo DAC, the samples for the left and right channels alternate one after another in time, and it uses the LRCLK to designate a sample as a left or right channel.  Note that you are not required to use the maximum bit rates for this chip.  You can send in lower bit rates as long as the LRCLK to MCLK ratio is within the following ratios as required by the CS4344:

 

13_clock_ratios.png


Therefore, in order to use the CS4344, we must figure out a way to generate the MCLK so that the MCLK-to-LRCLK ratio falls somewhere in the valid region in the table.  This means that we either must already know the sample rate of the audio that we intend to play, be able to dynamically adjust MCLK for the audio file that we want to play, or be prepared to make conversions on the fly to a different rate.  By the way, the abbreviations SSM (single), DSM (double), and QSM (quad) are the speed modes and appear to be internal Cirrus Logic nomenclature that have no I2S implications.
 

It is worth mentioning a note of warning on some of the content in this chapter.  This stuff is NOT easy to figure out!  It literally took me around ten hours of work to figure all of this out.  This is despite the fact that I already knew my way around audio formats, audio editing programs, and I have a computer engineering background.  It is always difficult to learn new chips.  The word engineer is shorthand for “really persistent person.”  Don’t ever give up.  The pain that you are feeling is completely normal.

 

For my first attempt at using this chip, I found a small sound file that had a sample rate of 24000 Hz and was a 16-bit uncompressed WAV file with the .wav file extension.  There are a lot of files that can use this extension, and the format of the data is stored in the header for the file.  In order to find out information about my sound file, I opened up an open source program called Audacity, which allows me to see the waveform and the information about how the data is formatted.

 

13_audacity.png 

The filename is called sweet1.wav and the information about the waveform can be found to the left of the graph.  The file is a mono, or single channel, file at 24000 Hz sample rate.  Note that the 32-bit float label is actually incorrect.  This is actually a 16-bit 2’s complement data file, which is a way that computers can communicate both positive and negative numbers.  I don’t know why Audacity gets that wrong, but even if I export this as a 16-bit file, the file size is the same and I know from other tools that this is in fact a 16-bit file.  All of the other data indicated by Audacity is correct.

 

I have placed my example audio files in the github repo here.  You can choose to use those or play your own, but we need to use uncompressed audio for the I2S bus, since the DAC provides no decompression.   Audacity can open many data formats of compressed audio files such as MP3 files and then export them as uncompressed Pulse Code Modulation (PCM) audio.  You can use this tool to play with sounds other than the ones in the repo.

 

Once I had my test audio file, I had to figure out how to get everything working and started looking around for I2S examples.  I found one inside the EFM32 SDK under the DK3850 kit software called DK3850_wavplayer and imported it into Simplicity Studio as a new project.  It was written for either the internal DAC on the EFM32 or an external DAC via I2S.  So I lucked out and it allowed me to get started more easily than starting from scratch.  However, I soon found out that the CS4344 device that I had chosen requires an MCLK that needs to have a specific relationship to the LRCLK, which the DK3850_wavplayer didn’t implement. 

 

In order to get this working, I began changing up the example wavplayer file, since once Simplicity Studio imported the project, it was my private copy to change all that I wanted.  The first order of business was to generate that MCLK that was at a ratio of 256 or 284 times the LRCLK frequency.  Table 1, above, starts at 32000 Hz, which is higher than my 24000 Hz file.  I figured that a ratio of 256 would probably work and I was proven correct.   The only problem is that 256 x 24000 Hz = 6.144 MHz for MCLK, and that is sort of an odd frequency for the MCU to generate.  My approach was to use a hardware timer but the fastest frequency that I could generate on a timer with the HFPERCLK (high frequency peripheral clock) running at 24 MHz results in a maximum timer frequency of 12 MHz.  That means that by dividing it down, I was stuck with very even stops of 8 MHz, 6 MHz, 4.8 MHz, and so on, with nothing else in between.  There was no ability to generate an exact 6.144 MHz clock with the onboard peripherals.  Had I realized this when I selected this CS4344 part, I probably would have looked for a different part or find some other off-board component that could generate that particular frequency.  By picking the closest frequency of 6MHz for MCLK, which is about 98% of the ideal frequency, our sounds will play 2% slower than intended.

 

Here was the code that was added to the DK3850_wavplayer example to give a 6 MHz clock on a GPIO pin:

// Toggle GPIO pin PD7 for MCLK
void create_gpio_clock()
{
      CMU_ClockEnable(cmuClock_TIMER1, true);
      CMU_ClockEnable(cmuClock_GPIO, true);
 
      // Enable MCLK output
      GPIO_PinModeSet(MCLK_PORT, MCLK_PIN, gpioModePushPull, 0);
 
      // Create the object initializer for compare channel
      TIMER_InitCC_TypeDef timerCCInit = TIMER_INITCC_DEFAULT;
      timerCCInit.mode = timerCCModeCompare;
      timerCCInit.cufoa = timerOutputActionNone;
      timerCCInit.cofoa = timerOutputActionToggle;
      timerCCInit.cmoa = timerOutputActionNone;
      timerCCInit.filter = true;
 
      // Configure TIMER1 CC channel 1
      TIMER_InitCC(TIMER1, 1, &timerCCInit);
 
      // Route CC1 to location 1 (PD7) and enable pin for cc1
      TIMER1->ROUTE |= (TIMER_ROUTE_CC1PEN | TIMER_ROUTE_LOCATION_LOC4);
 
      // Set TIMER Top value to 3, to generate 6MHz clock
      TIMER_TopSet(TIMER1, 3);
 
      /* Select timer parameters */
      TIMER_Init_TypeDef timerInit =
      {
      .enable     = true,
      .debugRun   = true,
      .prescale   = timerPrescale1,
      .clkSel     = timerClkSelHFPerClk,
      .fallAction = timerInputActionNone,
      .riseAction = timerInputActionNone,
      .mode       = timerModeUp,
      .dmaClrAct  = false,
      .quadModeX4 = false,
      .oneShot    = false,
      .sync       = false,
      };
 
      /* Configure timer */
      TIMER_Init(TIMER1, &timerInit);
}

 

I commented out any code in wavplayer.c that had to do with BSP (board support package), buttons, and volume control.  My goal was just to produce sound after reset at this stage.  Now that the MCLK is running, we can finally try out reading from the MicroSD card and getting some audio going. 

 

We will connect to the MicroSD card and read some files in the next section.

 

 PREVIOUS | NEXT

  • Blog Posts
  • Makers
  • There is a simple yet effective solution for generating audio-related frequencies with the EFM32: use the HFRCO and tune it:

     

    void set_clocks_for_44_1kHz(void)
    {
       CMU_HFRCOBandSet(cmuHFRCOBand_28MHz);
       // BandSet reloads callibration from which we are offseting
    // tuning to 22.5792 Mhz CMU_OscillatorTuningSet(cmuOsc_HFRCO, CMU_OscillatorTuningGet(cmuOsc_HFRCO) - 78); // for 44.1KHz UART0->CLKDIV = 2880; } void set_clocks_for_48kHz(void) { CMU_HFRCOBandSet(cmuHFRCOBand_28MHz); // BandSet reloads callibration from which we are offseting
    // tuning to 24.576 Mhz CMU_OscillatorTuningSet(cmuOsc_HFRCO, CMU_OscillatorTuningGet(cmuOsc_HFRCO) - 51); // for 48KHz UART0->CLKDIV = 3157; }

    (...)

    if(wavHeader.frequency == 44100)
    set_clocks_for_44_1kHz();
    if(wavHeader.frequency == 48000)
    set_clocks_for_48kHz();

    Of course, this is not an audiophile-grade solution but does much better than ~2% speed mismatch. You can tune into different sample rates on the fly for free. :-)

    The HFRCO is very much voltage/temperature dependent so probably it should be periodicaly tuned against some crystal, I just used static offsets (probed using scope) for simplicity. This could change the whole fast clock tree so take that into account - UART speed corrections in my example.

     

    Nice writeup. Regards,

    S.

    0