Nathan Reed

Blog Stuff I’ve Made Talks About Me
Artist-Friendly HDR With Exposure ValuesRadiometry Versus Photometry

Electronic Music Box

July 11, 2014 · Coding, Eye Candy · 0 Comments

When I was in college, I took a one-semester electronics course, and for a final project, we had a few weeks to build pretty much whatever we wanted. As a computer-science major (the only one in the class), obviously I jumped at the chance to do something with microprocessors and digital logic. I ended up building an electronic music box—a device that plays a preprogrammed song over headphones, using a PIC microcontroller.

It was an interesting journey into the (to me) strange and mysterious world of hardware and embedded software, and I got to do some fun things along the way, like etching and soldering my own circuit board. I came across the files again the other day and thought I’d share it.

The Hardware

The only kind of microcontroller we had available in the lab was a PIC 16F84A, so my whole design was based around it. This is a tiny CPU that has very, very basic processing capabilities. It was quite a fun challenge to write software for this device, given its limitations…but more about that later. :)

The PIC has 18 pins, of which 13 are available for programmable I/O. If we connect some of them to an 8-bit digital-to-analog converter, we could in principle synthesize audio directly from the microcontroller. Of course, the more sophisticated the audio synthesis we want to do, the more processing horsepower we’d need, and the PIC hasn’t got much. In fact, even creating a single sine wave with a controllable frequency in software would be pretty difficult!

I decided to go a different route: do the audio synthesis with a separate chip that’s built for the purpose, and use the PIC to control it. After evaluating various options, I settled on the AD9835, and managed to score a couple of free samples.

The AD9835 is a thing called a “numerically-controlled oscillator”—basically a sine wave generator that lets you set the frequency digitally. It’s actually designed for radio applications, so using it for audio synthesis was a bit overkill—but it has a built-in 10-bit DAC, very precise frequency control, and best yet, it could be programmed using a simple serial interface with only 3 pins. That made it perfect for me—I could attach two of them to my PIC, and still have a few pins left for user controls. Two oscillators means the music can have two voices harmonizing—not much, but certainly more interesting than one!

Here’s the final schematic I arrived at, after much breadboard prototyping:

Schematic of electronic music box

On the left is the power supply and controls. I used a 4-position dipswitch with the intention of letting the user select one of 16 songs, though this didn’t end up working out—the PIC only had memory enough for a couple short songs at a time. There’s also a play button, and an LED that indicates when the song is done playing.

In the middle is the PIC and the two AD9835s, and on the right is the output block. The analog outputs from the two AD9835s are combined, passed through a couple of RC filters in a (probably unnecessary) attempt to cut any high-frequency noise from the DACs, then amplified and finally sent to the speaker.

The whole thing is clocked at 5 MHz by a crystal oscillator, and powered by 4 AA batteries through a 5-volt regulator. Unfortunately, the TTL-standard 5 volts doesn’t very well match the most common types of batteries, so we have to go slightly over-voltage and use a regulator to dissipate the excess as heat. Kind of like hot dogs and buns.

The Board

Probably the coolest part of this project was building my own printed circuit board. After finalizing the design on the breadboard, I learned how to use the EAGLE PCB design software to lay out the traces. EAGLE is handy because it has a built-in database of electronics parts, and automatically lays out the pads at the correct scale and spacing to ensure they’ll match up with the actual part. It also has some autorouting capabilities, but I ended up just laying out my small board by hand (and as I look at it now, I’m already spotting some places where I could’ve done a better job).

PCB trace diagram

The board itself was made using photolithography. We printed the board diagram on a transparent acetate sheet, fixed it to the PCB and exposed it to UV light—i.e. the southern-California sun. The board had a photosensitive coating which, after washing away the exposed parts, formed a mask; then we etched away the unwanted copper using ferric chloride (a fairly nasty chemical). Finally, I drilled all the holes and soldered all the components in place.

The two AD9835s only came in the miniscule surface-mount form factor, which is really designed for machine assembly, not for human fingers. We made a couple of little adapter boards so we could put them in DIP sockets. I still had to solder the AD9835s onto the adapter boards, though, working under a microscope to reduce eye strain from staring at the tiny things!

Here’s the final result:

Interior of music box, with finished PCB

As you can see, we ended up mounting it in this box that someone found in a spare parts cupboard. On the top is a volume control pot, and a 3.5mm headphone jack for the output. Unfortunately, the dipswitch and play button aren’t very accessible, still being mounted on the PCB. We drilled holes in the top of the box, so you can poke at them with a pencil. Not the user-friendliest design. :)

Exterior of music box

The CPU

I mentioned earlier that the PIC microcontroller I used had some serious limitations as a CPU. First, it’s an 8-bit machine. It has 68 general-purpose registers, plus an accumulator register; the ALU can only talk to the accumulator, so in general all data has to be moved to it before being operated upon (making it more like a machine with one register and 68 bytes of memory, in practice). It has only integer arithmetic, and no hardware multiply or divide; the list of all its instructions fits on one page.

It uses a uniform 14-bit instruction encoding, and has 1024 words of read-only (flash) program memory. Since it’s an 8-bit machine, but has more than 256 program words, the PIC requires a segmented addressing scheme—like the old 8086 real mode. Doing a call or jump across 256-word segment boundaries requires some extra work, and functions can’t straddle a segment boundary, as the processor will not automatically advance to the next segment without an explicit call or jump.

On the plus side, all instructions complete in one cycle—no latency, pipelining, or multi-issue. There’s an 8-deep hardware stack for function calls, plus a configurable timer interrupt, which comes in handy for real-time applications like mine. There are also a few special registers for I/O and various other control tasks.

The Software

I programmed the PIC in assembly (MPASM). There do exist C compilers for PICs, but I thought it would be more fun to code closer to the bare metal—and I ended up using some crazy tricks that I doubt a C compiler would have been happy with!

The overall structure of the music box software (or firmware, rather) is simple. It has a main loop that waits for the play button to be pressed, then reads the desired song index from the dipswitch and sets up the timer system to play it at the appropriate tempo. The main loop then waits for the song to finish and repeats.

To manage the playback rate, I made use of the PIC’s built-in hardware timer interrupt. The timer works very simply: it consists of an 8-bit register that’s incremented every n clock cycles, where n is a configurable power of two. When the register wraps around from 255 to 0, an interrupt is generated; the CPU calls a predefined program memory address, at which you’ve hopefully placed an interrupt service routine.

More precise timer control can be achieved by resetting the timer register to a chosen value on each interrupt, which controls how long it takes to overflow again. For example, if the register is reset to 209, the timer will overflow and trigger the interrupt every 47*n cycles. I precomputed a timer reset value based on each song’s BPM, allowing the remainder of the code to be tempo-agnostic.

The main software-engineering problem I had to solve was how to represent the song data in a form compact enough to fit in the PIC’s very limited storage space. The PIC actually has 64 bytes of flash memory that can be read and written programmatically, but that’s not enough to store a song of any length, especially with two voices. Therefore, I had to cannibalize some of the program memory for song storage. I allocated 512 of the 1024 words to actual code, and the other 512 to song data.

Fortunately, the PIC’s designers anticipated using program memory to store data, and they provided the retlw instruction, which moves an 8-bit immediate value into the accumulator register and then returns. Therefore, you can implement a lookup table as a subroutine containing a sequence of these instructions. At the top of the subroutine, you add the desired table index into the PC register; this jumps to the retlw instruction containing the desired data, which then loads that data and returns from the subroutine. In this way, you can store 8 bits of data per program word.

; A snippet of the song data lookup table for the Phantom of the Opera theme
phantom_theme
    nop
    movf    data_idx, W
    addwf   PCL, F
    retlw   0x58
    retlw   0xA8
    retlw   0x58
    retlw   0x8A
    retlw   0x64
    retlw   0xC1
    retlw   0x6C
    retlw   0x38
    ; ... etc.

So, with 512 bytes to play with, how to encode something interesting? I took my inspiration from the MIDI file format. While MIDI itself isn’t compact enough for me to use it directly, it still has a useful structure. MIDI files consist of streams of events; each event has a delta-time to wait after the previous event, and a command telling the MIDI device to do something—such as start or stop a note, change instruments, and so on.

Based on this idea, I designed a data format in which each byte encodes one song command. The lower nybble stores a delta-time between this command and the next. The 16 possible values represent musically-meaningful lengths, such as eighth note, quarter note, and so on. During playback, these values are mapped to actual timings by another lookup table, using a standard of 48 ticks per quarter note (divisible by three to allow triplets). As mentioned earlier, the length of a “tick” is set by controlling the timer interrupt rate, based on the song’s BPM.

The upper nybble stores the song command. Of the 16 possible values, 12 represent the pitches of the standard chromatic scale; like the note lengths, these are mapped to the actual frequencies by another lookup table. A 13th command value represents silence—treated as just another pitch, which happens to have frequency zero. Two more command values are used to move up or down an octave, and the last is used to toggle which of the two AD9835s the commands are going to. These last three are stateful—the playback routine remembers which octave and which AD9835 it’s currently on, so state changes affect all following notes until another state change.

The interrupt service routine has the job of decoding all of this. It counts ticks in order to measure out the previous command’s delta-time, then processes the next command—either setting state, or sending out a new frequency to one of the AD9835s. Each frequency is a 32-bit value representing a fraction of the 5 MHz clock rate; the octave is adjusted by bit-shifting.

; A snippet of the interrupt service routine
    org 0x04
    ; save W and STATUS registers
    movwf   stored_w
    swapf   STATUS, W
    movwf   stored_status
    ; has tick counter gotten to zero?
    decfsz  tick_counter, F
    goto    ret     ; if not, return immediately

    ; get next song command
getcmd
    movf    song_addr, W
    farcall indirect_song_lookup
    movwf   command
    incf    data_idx, F ; move the pointer to the next item

    ; process upper nybble
    andlw   0xF0
    sublw   REST        ; calculate REST - W
    btfss   STATUS, C   ; if (W > REST), go to the next section
    goto    cmd

    ; it's a pitch (or rest) command; look up the frequency
    swapf   command, W
    andlw   0x0F
    call    freq_lookup

    ; start playing the new pitch
    ; send the frequency to the current AD9835
    btfsc   device, 3
    goto    todev1
    farcall setfreq0
    goto    settime
todev1
    farcall setfreq1
    goto    settime

To generate the song data, I wrote a simple C++ program that converts MIDI files into the compact format for the music box. It requires the MIDI file to work within the restrictions of my format—at most two voices, all note lengths must match one of the 16 predefined values, and so on. The program interleaves the two voices, inserting commands to toggle between AD9835s, and tracks the current octave, inserting commands to adjust it as necessary. I arranged the songs in NoteWorthy Composer, working from publicly-available MIDI files.

Part of the Harry Potter theme, in NoteWorthy Composer

Take a Listen!

If you’ve taken the time to read this far, I thought it would be cruel to leave you without a chance to hear the results of this project! Here are recordings of a couple of short songs played by the music box:

Harry Potter theme:
Phantom of the Opera theme:

Conclusion

I would recommend any coder to take an electronics class or two, if you have the opportunity. Since I took that electronics class in college in 2006, I haven’t done any more hardware or embedded software projects of my own; nevertheless, I’m quite glad to have had the exposure to that world. Coding on a tiny microcontroller, to the bare metal of a device I designed and built, is so different—and in some ways, more fun—than coding in the carefully sandboxed userland of an OS on a full-featured CPU in a commodity PC. On the other hand, when this project was done, I can’t pretend that I didn’t enjoy getting back to a machine with more than 8-bit registers and 68 bytes of memory. 😂

Artist-Friendly HDR With Exposure ValuesRadiometry Versus Photometry

0 Comments on “Electronic Music Box”

Subscribe

  • RSS RSS

Recent Posts

  • Reading Veach’s Thesis, Part 2
  • Reading Veach’s Thesis
  • Texture Gathers and Coordinate Precision
  • git-partial-submodule
  • Slope Space in BRDF Theory
  • Hash Functions for GPU Rendering
  • All Posts

Categories

  • Graphics(32)
  • Coding(23)
  • Math(21)
  • GPU(15)
  • Physics(6)
  • Eye Candy(4)
© 2007–2024 by Nathan Reed. Licensed CC-BY-4.0.