History of Xmega SD Player
A few months back I started on a small project for my girlfriend (Het Kunstwezen). She was preparing a detective game for a museum, to use for birthday parties. One of the items for this game would be the detective giving clues on how the game should proceed (give new clues, give assignments, etc.). How this should be done was unclear.
One of the ideas was to use an old phone and enter codes that would give the participants some info. I decided to try and realize this on an ATtiny2313, and I already made a nice library for a 2-row display when someone thought that it would be SO COOL if I could add one small feature: make the detective speak through the phone handset…
After making clear that this would prolong the development of the phone, but also seeing a nice challenge I started on this project.
One of the projects I really had in mind for a long time was making some sort of audio player based on a microcontroller board. I had heard about the ATxMega series from Atmel, and thought I might have a go at it.
Why use the AVR atxmega? I had several reasons to choose this microcontroller:
- I wanted to learn myself programming a more complex microcontroller than standard AVRs, also to be ready for potential new projects
- The ATxMega has timers, DACs, UARTS and an SPI bus plus enough clock speed to not worry about implementation in C
- A very nice development board is available from Alvidi, containing a micro-SD slot, voltage regulator, crystals and programming headers for less than 50 Euros including postage
- I had seen that the FatFs code from ELM-chan.org was used on xmega architecture.
I started with the phone pictured on the right. I needed to read out the keyboard (row-column matrix), mimic ordinary phone behaviour (dialling tone, DTMF tones), and be reasonably idiot proof as kids would play with this thing.
This is the ‘architecture’ I chose:
- Try to run as much as possible from interrupt routines
- Audio files are read from SD card
- To keep things simple, these files should be mono, 16kHz sampled, 16 bit files. No checks are performed.
- Audio is output from one of the DACs
- A second hand PC speaker setup is salvaged to provide power supply and an amplifier to drive the original handset speaker at a level suitable for a small group of participants to hear the assignments.
- Use a UART to give debug info (I only have a AVRISPmkII, so I can’t place breakpoints)
- I did not know what the story of the game would be. I thought it would be best if I provided a way to keep this open forever so gameplay can be changed. The participants get 9-digit codes from each finished assignment, if the code is correct they should hear a new message. Implementation is as follows:
- When the phone is picked up, a dialing tone is played
- When a number is pressed, the corresponding DTMF tone is played
- After entering all digits a ‘check code’ button should be pressed (bottom right corner
- The firmware checks the existence of a file called <code>.wav, so for example “123456789.wav” . When this file is found, it is played, when it is not found (wrong code) a ‘this number is disconnected’ file is played.
- Whenever the handset is placed on the hook, all playback is cancelled.
The game can always be changed by replacing files on the SD card; to change or add scenarios to the game just add audio files with new 9-digit codes (or remove them).
FatFS / SD card
Thanks, thanks, thanks for writing this library! This has given me the opportunity to make this project in the first place! I just needed to write the low-level functions for SPI communication to make this work. At first I found that the connection after startup was very unreliable; after flashing it worked most of the times, but on ‘cold start’ the connection to the SD card failed often. I found out that I forgot to check on the default pin states (pullup, pulldown) for the IO lines, after checking with the scope I corrected this in the main function. Again: check FatFS. The only thing that I had to find out by reading the sources was that whatever size you give for ‘read’, a block of 512 bytes is always read. Reading smaller sized blocks gives no speed advantage.
One thing that is ‘throwing away performance’ in timing is that nothing runs from interrupt routines. In the SPI routines this means that ‘while bit is set’ wait routines are used to read / write data. Waiting for these bits to set means that other routines have a variable delay. This is why the circular buffer is so important.
Reading /playing wave files
The WAV file format is very easy; the firmware just skips the header, except for the ‘length’ field, which is used to determine how many bytes should be read from the SD card. The data is written to a circular buffer, and read from this buffer when playing back the audio. One routine is used to convert the 16-bit signed data to a 12-bit unsigned value for the DAC.
Audio output to amplifier
When building this setup I only had a rail-to-rail output opamp laying around, but I wanted to use the full analog output scale (0-3.3V) for output. To do this I am using an inverting amplifier circuit; the outpur from the DAC can be 0-3V3, but the input stays around 1.65V. I am using two DAC outputs to drive the output buffer that drives the amplifier. The DC offset for the positive input of the amplifier is generated by the second DAC channel to minimize the number of components needed outside the demo board:
Amplifier should be RRO.
But: please read the possible improvements below!
The keyboard is a matrix of keys, I wrote a couple of functions to read the matrix. To be independent of hardware, the rows and columns can be on different microcontroller ports, and row / column position can be shifted in software. So: just connect the rows and columns, and correct your hardware mistakes in firmware! Debouncing is also taken care of in firmware.
In a later project I found out that on a older make of the T65 a different type of keyboard interface was used. When a key was pressed, one switch was closed for the row that was pressed, and another key was pressed for the column. In the newer phones the key that is pressed shorts a row and a column contact, the ‘standard’ matrix keyboard style. I decided to use the same function names, but decide based on the state of an otherwise unused IO pin which part of the function should be run: the ‘old phone’ or ‘new phone’ content. This way I can always flash the same file, and just short one pin to GND to get an other keyboard readout function.
Now, this feature is VERY useful! I’m using a five-level playlist where each item is ‘interruptible’ or not. If an item is interruptible, a new item being added to the list will be played instantly. If it is ‘uniterruptible’ the sound will play until finished or until the horn is placed on the hook. For example: the dialing tone is interruptible; when a key is pressed the dialing tone stops and the DTMF tone is played. The audio fragments of the ‘detective’ are ‘uninterruptible’. Key presses have no effect.
This feature also limits the amount of editing needed in WAVs. Audio effects / samples of picking up the phone and laying it down can be added to the playlist, and do not need to be added to WAV files.
Not used, but fully functional and included in the zip file with code. To be used with this display or similar.
Source Code and Pictures
The code posted is not what was used in the museum game, but what was used for an event where ‘infotelefoons’ were needed. The two T65 phones pictured below were built, and used the same code as basis. Differences:
- - No DTMF tones
- - Press a button, hear an attached audio fragment immediately (info1.wav – info0.wav), so no ‘missing connection’ tone needed.
- - Keys next to zero key are not used.,/li>
- + Added old keyboard matrix
- + Improved initial pin state for SPI lines
Click here for the sources. The folder ‘SD’ is the default content of the SD card.
Caveats / improvements
What can be improved?
- As I later found in the device Errata: up to 100LSB noise is added when using the DAC in sampled mode. This noise is clearly audible. One DAC is only used for creating a DC offset, this can also be done with an external resistor divider.
- Use the DMA possibilities of the Xmega. In a later project I did for this device in my daytime job, I found that the DMA can be used very easily. Might have made it easier to copy data from the SD card to the ring buffer.
Quite boring video of a …phone…
It shows picking up the phone, entering wrong and ‘correct’ codes (223456789, you hear the phone being picked up and hung up. The switch under key 1 is defect…).
See pictures below. Clicking each of them will show the complete gallery. Note that each phone consists of the development board and the amplifier of a lowcost Logitech speaker set.