Hello, this is Ivan, and welcome back to another complete project from Artekit.
This time we bring you a board that simulates the sounds from a lightsaber, using low cost components. This project is a little more complex than the projects we have done before for the blog, and it will need a few external components. But don’t worry, it’s nothing hard to get.
We wanted to do another project before the end of the summer. So as usual, we filled the table with every little piece of hardware we found around the lab. And nothing came out. After a few days, we received the breakout boards batch, and that changed the game. So, between one earthquake and the other, the project took shape.
Enough chat. Let’s see what we are talking about:
What’s on the board?
For this demo board we have used:
- A STM32F103T8 Cortex M3 (we use the AK-STM32-DIP36 breakout board).
- A Freescale MMA8452Q accelerometer (we use the AK-MMA8452 breakout board).
- A SST SST25VF080B 8Mbit serial flash (we use the AK-SST25VF080B breakout board).
- A Microchip DAC, the MCP4921.
- A JRC NJM2070D audio amplifier.
- An 8 omh speaker.
- A 9V battery with battery holder.
- An AK-POWER to step down the 9V from the battery.
- Resistors: 1 x 47K, 1 x 1 ohm, 10K potentiometer.
- Capacitors: 2 x 100uF/16V Capacitor, 1 x 10nF/16V Capacitor, 1 x 470uF/16V Capacitor.
- A couple of push buttons.
As you can see in the video, we are using a desktop PC speaker, but just for the plastic case around it (there is no amp inside the case). So you can use any 8 ohm, 4W speaker. If you are using a hacked-from-somewhere speaker, we recommend you to find a proper enclosure, until you find a nice pitch. Having the speaker lying on the table only makes low volume noises.
How does this works?
We have the STM32 running the program (source code here), that senses the buttons and processes the accelerometer indications, reads the waveforms from the serial flash and sends them to the DAC, then to the audio amplifier.
Basically we need to detect two events: when the blade hits something and when the blade moves. The MMA8452Q has two interrupt pins that can be set to signal pre-configured events. At startup we tell the accelerometer to detect a “pulse” (a tap) and a transient (that is a certain acceleration in a given window of time). With this two events we can detect when the blade hits something (a pulse) or moves (a transient). We have the transient detection on pin PB1 and the pulse detection on PB2, of the STM32.
Maths are done inside the accelerometer (by Freescale), so the I2C channel is free for whatever you want after the startup. This is a nice feature of the MMA8452Q, that frees resources by processing the most common accelerometer operations inside the IC. Optionally you can query the accelerometer to know on which axis a given event was detected, current acceleration, etc.
Where is the sound coming from?
The waveforms are stored inside the SST25VF080B serial flash. In this case we are using 16-Bit WAV files at 22050 KHz sample rate. We have found the sound files at freesound.org, from an user called joe93barlow (thanks to him).
At first we have tried to use PWM and delta-sigma PWM to generate the sound. But at 16-Bit 22050 Hz it needs a very high PWM frequency. It might work with 8000 Hz audio, but we wanted something with a little bit more quality. So we opted for the external DAC (if you are using a high-density or XL-density STM32, those have an integrated DAC).
The DAC we are using is a 12-Bit DAC. So we have to shift the 16-Bit samples down to 12-Bit. This is a limitation by the fact that the MCP4921 was the only non-SMD DAC we’ve found in our lab. A 16-Bit DAC can be used in place of the MCP4921, and they are more or less the same to use.
Here we enter more in detail on the source code: these waveform are logically divided into two sound groups. The HUM sound, and the ALT sounds: the HUM is the background sound that is always on. The ALT are sporadic sounds that are mixed with the HUM sound. These ALT sounds are divided in the following way: the ON sound, the OFF sound, the HIT sound (a laser from a pistol hits the blade), the STRIKE sound (two saber blades that meet) and the SWING (sort of a doppler simulation when the blade is moved quickly). These are the kind of sound we are playing when an event (pulse, transient and buttons) is detected.
TIM1 generates an interrupt at every sample we have to send (at 22050 Hz). The interrupt code fetches a sample and enables TIM2 to send the sample to the DAC.
If you are quick on maths you have noticed we have to send a sample to the DAC every 0.045 ms, and we have to achieve a fluid, constant output without interruptions. Sometimes we have to mix the HUM sound with the ALT sound so we need to read two waveforms instead of one. All this while leaving to the STM32 enough room to do some processing.
Interesting. Tell me more.
The solution was to read chunks of audio samples using a double buffer. While we play a chunk of samples, in the meantime, we quickly read (at 36Mhz from the serial flash) the next chunk of samples. Since we are dealing only with 20KB of RAM, the secret of this project is how fast we can read from the serial flash. If you are not using a SST25VF080B, you will need an equally fast serial flash.
When the first chunk of data is done playing, we quickly switch buffers and start playing the next chunk. The first buffer is free to load another chunk of samples. The cycle repeats. Since we are playing 22050 samples per second, the chunk size has to be long enough to keep playing while we read another chunk.
The said method is done for two kinds of sounds (ALT and HUM), that have to be loaded and mixed in real time. So instead of two switching-buffers, we have four. This sums to the time the chunk has to be playing while we grab (possibly) another two chunks (one for HUM, one for ALT when it’s playing).
So we have TIM1 running at aprox. 22050 Hz, mixing samples. And TIM2 sending the last read (and mixed) sample to the DAC. Since we don’t want to be busy all the time inside a timer interrupt, we use SPI with DMA. There is a “DMA request queue” mechanism defined in dma.c that appends DMA requests for sound chunks and executes them one after another, and flags the chuncks as “ready” when it’s done reading from the serial flash. The serial flash is so fast that I believe we are wasting more time setting the DMA stuff than actually reading.
The samples are mixed simply by adding the PCM values of the HUM and ALT waveforms. The PCM values for a (signed) 16-Bit audio file go from -32768 up to 32767. If the sum goes beyond that range we clip it at the maximum positive or negative value. There is a lot of discussion out there of how the mixing should be done. We picked that because it sounds decent with our waveforms. If you want to change the algorithm, it is in the MixSamples() function in main.c.
Buttons are simple: one for turning the thing on and off, and one for the HIT sound since we didn’t find a simpler way to simulate a laser pistol shooting at a laser blade we don’t have. 🙂
You can find more insights on the implementation by browsing the source code.
I want to build one.
This is what you need:
For uploading the sound files (included in the source code) you have to connect the serial port to the PC (indicated in the schematics, through screw terminals, and it is TTL, so make sure you put a level translator). Connect the serial port and open up a terminal (like TeraTerm) at 115200 bps, 8, N, 1, and press any key. The sound output will stop and you will see the message “Start sending the file. Reset the board when finished.”. Send the “sounds.bin” file, and reset the board when finished. If you are using TeraTerm make sure you click on the “binary” checkbox in the “Open file” dialog before sending the file.
The “sounds.bin” file can be generated using a program included in the package (VS2010 source code available). This is a very (very) simple program that assembles the sound file. Press the “Add file” button and pick a 16-Bit WAV PCM file at 22050 KHz (yes, only in that format) of a given type: HUM, ON, OFF, HIT, STRIKE, SWING. Then select the right type in the combo-box. Just make sure to include only one HUM sound, one ON sound, one OFF sound, and try to add the HIT/STRIKE/SWING types all together (add all the HIT sounds, then all the STRIKE sounds, and so on). With the waveforms ordered that way, the program can select them pseudo-randomly, in order to not repeat the same sounds.
We hope you have enjoyed reading (and implementing) this project as much as we have enjoyed building it. This project also settles the base for all kind of audio projects you can do, like an audio recorder or a generic music player. The code size is quite small (7200 bytes with O3 optimization) so there is plenty of room for you to play and improve the project.
It is known that every initiated into the force has to build his own lightsaber. Well… you can start with this.
The Artekit Team.