Game Timer

by Cathy Saxton
Posted October 14, 2011
Last Updated January 3, 2012

timer

We get together with friends to play games on a semi-regular basis. One group plays a lot of games that require a timer. Those cheap little sand hourglass egg timers that come in games are terrible. They're hard to see and someone has to watch them carefully to see when the last grain falls. We often use a smartphone with a timer, but they have different interfaces which causes some confusion, and an expensive phone is at risk of being knocked off the coffee table or glopped with wine or bean dip. I used this as an excuse to work on a fun project: a game timer that would be easy to use, easy to see, less expensive than a phone, and less likely to be destroyed by a minor mishap.

The timer turned out great, pretty much as I had envisioned it. Here's a description of its features:

  • It has a ring of lights showing the progress of the timer. Each light in turn becomes red as more time passes. This provides a nice visual indication of the amount of time remaining. The ring starts out green (instead of unlit) to give better feedback that the timer is on and working.
  • There's a large, easy-to-hit button in the middle of the circle of lights. In addition to starting the timer, it can be used to either pause or reset the timer (based on settings).
  • When the timer is done, it plays a tune. There is a knob that adjusts volume.
  • The "idle" state shows four green lights. If the battery is low, those lights glow red instead. When the button is pressed, the lights turn yellow while the button is down, then the timer starts when the button is released.
  • switches
  • On the bottom of the timer, there are switches to control settings. These settings are used to specify the timer length, end-of-time tune, and whether to respond to a button press during timing by pausing the timer or resetting to idle.
  • There are of course standard things like batteries, a power switch, and a box around the electronics.
  • If you hold the button down while turning on the timer, it will show the battery level by illuminating lights in the ring, with more lights for a "juicier" battery. It will also repeatedly play the selected end-of-time tune, allowing you to adjust volume level.

One thing I'd like to note: this was a project for fun, not to save money; it's about $50 for the parts.


The rest of this page describes the development process for the game timer. There are resources at the bottom of the page if you're interested in using this information as a starting point for your own projects.

Project Design

I thought about how I wanted the timer to look and function and came up with the list of features above. I used that to compile a list of components that I'd need:

  • Lights – I chose bi-color (red/green) LED lamps, with 12 in a ring.
  • Start button
  • Piezo buzzer for sound
  • Potentiometer (variable resistor) for volume knob
  • DIP switches to make settings
  • Microcontroller unit (MCU) – I decided to use one of the Atmel ATmega48 / 88 / 168 family. The three options are pin-compatible and differ mainly in the amount of Flash RAM available for storing code. I planned to choose which one to use after I wrote the code and saw how much space I needed.
  • Resistors for voltage divider – This is used to check battery voltage.
  • Resistor arrays for LEDs – These are IC packages with multiple parallel resistors; they are more convenient to use than many individual resistors.
  • Various resistors, capacitors, headers
  • Power switch
  • Battery holder for 3 AAA batteries – These will provide 3.3 - 4.8 V, depending on chemistry (NiMH, Alkaline, etc) and how much they've been used. This voltage range is comfortably within the range tolerated by the microcontroller. AAA batteries have enough energy to power the timer for many hours, so I didn't need to use the larger AA batteries.

Before I could create a schematic, I needed to determine how I wanted to handle a couple of the features:

  • Playing sound – I chose to use a piezo buzzer, which makes tones based on the frequency of a square-wave pulse sent to it. I can create this pulse using the MCU's built-in PWM wave generation.
  • Controlling 24 LEDs – There are 12 "lamps," each of which contains both a red and a green LED. I chose to use a technique called Charlieplexing. This lets me control the 24 LEDs with just six MCU connections.

Sound

We can generate sound with a piezo buzzer by causing it to vibrate, which is done by toggling its input.

The tone of the generated sound is determined by the frequency of the vibration. Higher frequencies are higher notes. The A note above middle C is 440 Hz. Each octave is a factor of 2, so the A note one octave higher is 880 Hz. (Each step, e.g. A to A#, is a factor of 12√2 = 1.059463.)

Pure tones are sine waves, as is depicted in the first wave below. From the MCU, we generate a square wave, which works well enough to produce a recognizable tone.

audio wave 440 Hz audio wave
440 Hz wave 440 Hz MCU-generated
880 Hz wave 880 Hz MCU-generated

The volume is controlled by varying the width of the pulse. When the high and low portions of the wave are the same duration (50% duty cycle), the volume is loudest. The more they differ, the quieter the sound. It doesn't matter whether the high or low portion is longer. For example, 3% and 97% will result in the same (relatively quiet) volume.

In the image below, each wave will produce the same tone since they are all the same frequency. The top wave will be the loudest since it has a 50% duty cycle. The second wave will be a bit less loud, and the last two will be the quietest in the group. Note that the last two represent the same volume level, just with high and low duration swapped.

volume

Perceived volume is based on a logarithmic scale; I noticed a difference in volume between widths of 1%, 2%, and 3%, but not between 48%, 49% and 50%.

Microcontroller Interface

The Atmel AVR microcontrollers can automatically generate a (square) wave using a built-in Timer / Counter resource. The output from this counter can be sent to a MCU pin, so the circuit will want to have one of the outputs from a Timer / Counter connected to the piezo buzzer.

Since we control volume in software (by altering the pulse width), we'll connect the volume knob directly to the MCU. We'll pair the potentiometer with a fixed resistor to create a voltage divider. Its output will be connected to one of the MCU's analog inputs. The MCU will read this voltage and use it to set the duty cycle on the square wave.

Charlieplexing

Charlieplexing is a technique for controlling many LEDs with relatively few MCU outputs. It is named after Charlie Allen at Maxim.

Let's start with a quick review of LEDs and MCU control of them.

simple LED

An LED lights when…

  • Its anode is connected to +V and
  • Its cathode is grounded.

A resistor is chosen to give the desired voltage and current for the LED.

From this point forward, we'll temporarily ignore the resistor; we'll add it back in at the end.

MCU control of 3 LEDs

To control an LED with a microcontroller, we can connect the LED's anode to an output from the MCU.

The image on the right shows three LEDs with anodes connected to MCU pins 1, 2, and 3.

To light the LED, its anode is pulled high (as shown in this example).

To turn off the LED, its anode can be grounded or put into a high-impedance state. In a high-impedance state, current is restricted from flowing, so the LED won't light. We can make an MCU pin high-impedance by configuring it as an input.

Multiplexing

basic multiplexing

We can also connect an LED's cathode to an MCU output, and we can share the MCU's anode and cathode control lines across multiple LEDs, enabling us to control an array of LEDs.

In the example on the right…

  • The MCU controls 6 data lines:
    • 1, 2, 3 (to anodes)
    • X, Y, Z (to cathodes)
  • 9 LEDs can be individually controlled.

To light an LED, the MCU outputs high on the line connected to the LED's anode and grounds the line connected to the LED's cathode. All other MCU connections are put into a high impedance state (configured as input).

In the example below, LED "2X" is lit by pulling line 2 high and grounding X.
multiplexing 2X

LEDs sharing a cathode line (X, Y, or Z) can be turned on together. In the following example, LEDs 1Y and 3Y are lit by pulling lines 1 and 3 high while Y is low.
multiplexing 1Y + 3Y

As you've probably noticed, we can't turn on all of the LEDs at one time. But, we can create the effect of having them all lit by rapidly cycling through the "strings" of LEDs.

Each of X, Y, and Z will take a turn being grounded, with lines 1, 2, 3 optionally pulled high based on which LEDs in that string should be lit. If this is done rapidly, all of the LEDs appear to be on at the same time. Their brightness will be reduced since they are only on for a fraction of the time; in this example, 1/3 of the time (due to 3 cathode lines).

Charlieplexing

Recall the multiplexing diagram shown on the left below. We can use the MCU's lines 1, 2, and 3 for the cathode lines, too, eliminating the need for lines X, Y, and Z. When we do this, the LEDs on the diagonal are removed since they would have anode and cathode connected to the same MCU control line (and would thus never light). This new configuration – Charlieplexing – is shown on the right below.

basic multiplexing charlieplexing array
chalieplexing alternate

With our new Charlieplexing arrangement, you can see that 3 MCU lines can control 6 LEDs.

All combinations of two control lines are used. The image on the right shows another representation of the same circuit, illustrating how pairs of control lines are connected to LEDs.

So, how many LEDs can you control with n MCU lines?

n × (n - 1)

There are two ways to derive this formula:

  • From the grid – it's n × n, but the diagonal (n LEDs) goes away, so n × n - n = n × (n - 1)
  • Based on ordered pairs – there are n possibilities for the anode, then n - 1 for the cathode, thus n × (n - 1)

Adding Resistors – Simple Technique

simple R

We need resistors to protect the LEDs from over-current.

The image on the right shows a simple technique of adding a resistor on each control line. Let's explore how well this will work.


simple R, 1 LED

The image on the right shows an example of a circuit lighting a single LED. Given the following:

  • 5V supply
  • 2V drop over LED
  • Want 10 mA current
  • Equal-value resistors

Solve for R (recall that V = I × R):

5 V = 2 V + 2 (10 mA × R)
R = 1.5 V / 10 mA
R = 150 Ω
simple R, 2 LEDs

Now let's consider lighting two LEDs. In this case:

  • 5V supply
  • 2V drop over LED
  • R = 150 Ω (from above)

We want to solve for i (current):

5 V = i × 150 Ω + 2 V + 2i × 150 Ω
i = 3 V / 450 Ω
i = 6.7 mA

Note that this is < 10 mA!

With this method of placing resistors on each MCU control line, LEDs get less current and thus become dimmer as more LEDs are turned on at the same time.

Fortunately, there's a better way to do this!

Adding Resistors – Better Method

For this (better) method, we put a resistor in series with each LED pair. At most one of the pair will be on at a time since they turn on with opposite power/ground connections.

Since the shared portion of the circuit is just a wire, each branch (resistor + LED) will see the full voltage drop, and thus the current for each LED is constant regardless of how many LEDs are lit.

better R
The images below show the circuits for lighting either one or two LEDs.
better R, 1 LED better R, 2 LEDs

The limiting factor becomes the MCU's current-sourcing ability. When multiple LEDs are powered at once, the MCU has to source current for each LED. Even more constraining is that just one pin needs to sink the current from all of the LEDs illuminated at a time. If the current needs are too high, one possible solution is to program the sequence to have more steps and turn on fewer LEDs at a time. In this case, each line would take a turn as the cathode multiple times in the sequence.

Game Timer Implementation

The game timer has 24 LEDs (12 bi-color lamps). By using Charlieplexing, we can individually control those 24 LEDs with just 6 MCU lines.

I chose 33 Ω resistors, placed in series with each lamp. This results in 33 - 85 mA per LED, depending on the battery voltage and LED voltage drop. With six lines each taking a turn as the cathode, each LED will be lit for 1/6 of the time, producing a brightness equivalent to approximately 5.5 - 14 mA.

For the timing, I chose to give each cathode group a 0.1 ms period.

The LEDs allow 140 mA at 1/10 duty cycle for a 0.1 ms pulse, so the LEDs should be happy. They get current slightly more frequently (1/6 duty cycle vs. 1/10), but at a maximum of 85 mA.

The MCU allows a maximum 40 mA / pin, with max 200 mA total for all pins. I'm obviously exceeding the 40 mA limit when there are fresh alkaline batteries in the timer, but the MCU seems to be holding up fine so far. I expect that most stress to the MCU comes not from the anode pins sourcing current, but from the cathode pin, which sinks current from multiple LEDs in each string. With the circuit design and functionality of the timer, I've limited that to a maximum of three LEDs at a time (instead of the theoretical five). My hope is that the MCU will tolerate this because of the 1/6 duty cycle and since it is sinking instead of sourcing the large current.

Hardware

There were four basic steps to creating the hardware for this project: documenting all the connections in a schematic, designing the board layout, having the board fabricated, and soldering items to the board.

Schematic and PCB Design

I use EAGLE PCB design software for creating the schematic and board layout. Tips, files, and reference materials for EAGLE are on our EAGLE resources page.

I started the schematic by adding all the components that I knew I'd use. Then, I considered the connections that I needed to make to the MCU. That imposed some constraints, but there was still plenty of flexibility.

MCU connection Constraints Flexibility
6 LED outputs I wanted all of these on the same port (register) so it would be quick to change them all at once. They didn't need to be in any specific order or even on consecutive pins.
Output from a Timer / Counter to the buzzer I decided that I wanted to use the 16-bit counter to have better resolution for generating tones. Either of the output pins from the 16-bit timer would be acceptable.
10 DIP inputs Grouping and ordering as much as possible would simplify (and speed) code. Different purposes for sub-groups of DIPs means that they can be divided among various ports.
2 analog inputs Nothing "noisy" should be on the analog port. This would include the LED outputs since they are constantly changing and the button since it is used frequently. DIPs change infrequently, so they would be ok on the analog port.
Button input None. This could go almost anywhere.

I determined an arrangement of LEDs that had each MCU control line acting as cathode for four LEDs and provided a nice layout for optimizing the traces. I placed the symbols on the schematic in an arrangement that matched the layout I would use on the board.

At this point, I had a rough idea of the MCU connections, so I worked on the printed circuit board (PCB) layout. Once I could see where each component was located relative to the MCU, I could pick among the available connection options.

I wanted the timer to be fairly compact, so I expected the AAA battery holder to cover a large portion of the board. With the LEDs installed, their leads would poke through to the back of the board, but that would prevent the battery holder from resting flat against the board. It was also looking challenging to get all of the components squeezed into the small-ish footprint that I was envisioning.

The solution was to use two boards. That gave me plenty of extra space and also enabled me to have a clean look on the top surface for the LEDs and buttons with no additional components – or even traces.

PCB bottom PCB top

For PCB fabrication, I sent Gerber files exported from EAGLE to Gold Phoenix PCB.

Soldering

The MCU, resistor arrays, and a handful of resistors and capacitors are surface-mount components on the back of the bottom board. For that, I used a stencil to apply solder paste and then a griddle to heat the solder. My favorite method for making stencils is to use a cutting plotter.

The remaining components were through-hole items, which were easily soldered with an iron.

box bottom

Box design

I wanted a box to enclose the electronics and give the timer a more finished look. For various reasons, I decided that I'd create a design and have panels laser cut instead of using a pre-fabricated box. This gave me more flexibility on size and shape and ensured that I would have access to batteries and switches.

The design criteria:

  • Holes in the top for the LEDs and start button.
  • Side access for the volume control.
  • Open air for the buzzer, to avoid muffling the sound.
  • Mounting system for the PCBs.
  • Access to the bottom (for batteries, power switch, DIP switches).
CAD cutaway

For the box, I'm using a common "sandwich" design: slots in the side panels hold the front, back, and mid-level panels in place via tabs, and the sides are held together with a long bolt. The picture on the right shows one of the side panels (it's the translucent panel on the right) and how tabs on the two mid-level panels and the back panel are mounted into slots on the side.

I had planned to glue the top to the front and back panels, but it turned out that there's enough friction to keep it in place without needing any glue.

For mounting the PCBs, I decided to attach the bottom PCB to a panel located between the two PCBs. In the picture above, you can see the mounting holes in the bottom PCB and the mid-level panel. The top PCB connects to the bottom PCB through headers making the necessary electrical connections.

I ultimately just needed a 2D drawing for lasercutting, but decided to model in 3D to ensure correct vertical placement for the mid-level PCB-mounting boards and access to the volume knob. I used Alibre Design.

For the 3D model, I first created parts for the PCBs including components, then created an assembly with the boards, and finally created the box panels directly in the assembly.

For lasercutting, I created a drawing and inserted a "standard view" for each panel. Note that it's important to ensure that the scale is 1:1 for both the drawing and the insertions! I exported as DXF (AutoCAD 2004) to create a file to be used with the lasercutter.

Here are the box panels and hardware:
box panels

Software

State Machine

The main control code for the timer is a "state machine." It keeps track of the current state of the timer and checks for activities that would trigger a transition to a new state.

There are numerous states, but they fall within four groups:

  • Idle – waiting for the button to start the timer.
  • Timer – the timer is running.
  • Play – the tune is playing and lights are flashing after the timer finished.
  • Pause – the timer is paused.

Transitions can be triggered by:

  • Elapsed time.
  • Button activity (press or release).

The main program loop has three phases:

  • Check for button activity – When the button is pressed or released, the code sets variables to indicate the new position and records the start time for a debounce period.
    • Wiktionary defines debounce as "to remove the small ripple of current that forms when a mechanical switch is pushed ... and makes a series of short contacts."
    • Any button press or release detected during the debounce period is ignored.
  • Perform any actions required by the current state – This includes checking for transitions to other states.
  • Do idle processing – This includes checking to see whether the LEDs are ready for the next stage of their cycle and whether it's time for the next note when playing a tune.

This flowchart shows the various states and the transitions between them.
flowchart

Microcontroller resources

The game timer board is using an Atmel ATmega48A, which has the following features:

  • 8 MHz system clock.
  • 4k bytes Flash program memory.
  • 512 bytes SRAM (run-time) memory.
  • Timer/Counters – two (of three) are used:
    • Timer/Counter 0 (8-bit) is used for the timer (clock).
    • Timer/Counter 1 (16-bit) is used for sound output.
  • Analog-to-Digital Converter (ADC) – This is used for reading the battery voltage level and the volume knob position.

There are two ways for the software to learn about interesting activities – interrupts and polling. With an interrupt, an internal process of the MCU watches for the specified trigger and interrupts code execution to call a special function when the trigger happens. With polling, the software repeatedly checks for any changes that need to be handled. Each has its benefits and drawbacks.

The game timer code uses both interrupts (for the timer) and polling (for button activity, transition times for LEDs and sound, and completion of an ADC conversion). Details are in the relevant sections below.

Classes

Classes can help provide organization and encapsulation. The game timer code has a C++ class for each "object" (listed below). The implementation details are handled by private class code (and stored in a class-specific file). Callers don't need to know or understand details about the implementation; they just communicate with the class through a public interface that provides functions for the features supported by the class (e.g. 'play a tune', or 'is the tune still playing?').

Public interfaces and some implementation details for the classes are described below. It may be helpful to consult the MCU datasheet when exploring the code.

DIP Switch class

The public interface provides functions to:

  • Ask which sound is selected.
  • Ask whether the button should pause or reset the timer.
  • Ask for the timer duration.

The private class code deciphers the DIP switch settings. It knows which inputs to look at for each setting and how to interpret the switch positions.

Timer class

The public interface provides functions to:

  • Get the current time in milliseconds.
  • Get the current time in high-resolution "ticks" (125,000 ticks per second)
  • Ask if a specified amount of time has elapsed.

The private class code sets up Timer/Counter 0 for measuring time:

  • It uses the Clear Timer on Compare Match mode, where the counter resets to 0 when it matches a specified value.
  • In order to generate a "match" event every 1ms, it uses the following values:
    • Prescaler: CLK / 64 = 125 kHz (8 μs per "tick")
    • Match value: 124 (125 ticks, values 0-124)
  • It sets up an interrupt for a "compare match," which it handles by incrementing a count of milliseconds.

Sound class

The public interface provides functions to:

  • Start or stop a sound.
  • Set the volume level.
  • Ask if a sound is currently playing.

During idle loop processing, the sound class checks elapsed time for the current note and transitions to the next note (or stops playing) when appropriate.

The private class code sets up Timer/Counter 1 to generate a waveform on the output pin. It uses the Fast PWM with Compare Match Output mode, which works as follows:

  • At the Bottom value (0): Output is set to high.
  • When the Match value is reached: Output changes to low (and counting continues).
  • When the Top value is reached: Counter reset to 0 (back to bottom).

The tone determines the Prescaler and Top values (period / frequency).

The volume level determines the Match value (pulse width). Recall that maximum volume is at a 50% duty cycle, i.e. when Match = Top / 2; the volume will be quiet for small (or large) Match values.

Analog class

The public interface provides functions to:

  • Get the volume level – This function tells the caller whether the volume level has changed, and if so, reports the new value.
  • Ask whether the battery voltage has transitioned past the "low" threshold.
  • Get the battery voltage level – The battery voltage is reported as a value from 0-11. If reading / conversion is still in progress, this function lets the caller know that no value is available currently (and it should try again later).
analog circuits

The private class code handles selecting the appropriate channel (volume or battery), starting conversions, and checking to see whether prior requests have completed. When responding to caller requests asking if a value has changed, the class code requires that the value has changed by a minimum tolerance.

When an analog to digital conversion is made, it uses a "reference voltage." The result of the conversion is the ratio of the input divided by the reference voltage.

When reading the volume level, we use Vcc (the voltage from the battery pack) as the reference voltage. Since the volume input is a voltage divider, comparing against the battery voltage ensures that a particular volume setting will always yield the same result, even as the battery voltage changes.

When checking battery voltage, we use the internal 1.1 V reference voltage. The battery input comes from the voltage divider shown on the right. The input to the MCU will be at a voltage level equal to Vcc × 25/(25+77). This means that Vcc voltages of 4.49 V and higher will result in the maximum reading, with lower values produced as the voltage drops.

LED class

The public interface provides a function to let the caller specify which pattern to show.

During idle loop processing, the LED class transitions to next "string" in the Charlieplexing cycle when enough time has elapsed.

The private class code has a table of bytes that are the register values to use for each step of each pattern. Each pattern has six steps per cycle, one for each LED control line taking a turn being grounded (as the cathode). Each step needs to set direction register and output register bits as follows:

  Direction Output
Common cathode line Output Low
LEDs to turn on Output High
Other LED lines Input Low (no pull-up)

The direction output values for each step are calculated in an Excel spreadsheet and stored in program memory (Flash, not SRAM).

The buzzer and start button also use the same register as the LEDs; the LED code maintains their states.

Putting it All Together

Once I had the code all written and was ready to download and test it, it occurred to me that I'd forgotten to check the code size before selecting the MCU. I'd gotten the smallest (4k Flash) option when I'd ordered parts for the timer. It's a good idea to ensure that the physical parts match the dimensions from the spec sheets, so I like to get the parts and verify their sizes before ordering boards. Normally, I prototype with the biggest part, but for some reason I'd gone with the cheapest this time.

I checked the program and discovered it was 6k bytes. I was not a happy camper! Being a few bytes over would be easy to solve, but it seemed unlikely that I could find 2k of extraneous stuff.

I'd already soldered everything to the board and wasn't optimistic about the likelihood of being able to desolder the MCU without damaging other components. That meant that if I needed a larger MCU, I'd have to start over with a new board. Plus, of course, I didn't have any of the larger MCUs on hand.

In general, I try to write small, efficient code. There are a few places where I choose maintainability over efficiency, but that has a minimal effect on code size.

I checked the "release" build to see how much space I'd save when removing debug code, but that had a minimal impact.

I considered whether I could trim down program-memory tables that I was using for things like the LED patterns and the timer end tune. Reducing these would mean that I'd lose some features, but I was getting desperate. It turns out that I'd only be able to save a hundred bytes or so (out of 2,000 needed).

At that point, I decided to investigate what was taking up so much space in the code, so I looked at the .map file. Aha! There was a lot of space used for floating point functions! I was using floating point when making calculations for the sound volume; I had three assignments and four multiplications using 'float' variables.

I changed to fixed point math and dropped from 6,248 bytes to 2,852! Yippee!

This would have been a fun discovery if it had just been a cool optimization, but with the current state of the project, it was especially sweet.

Fixed Point Math

Fixed point math uses integers to represent fractions.

There are two advantages to using integers: We don't need special libraries (all that code space!), and calculations are faster than with floating point (on MCUs).

For a fixed point implementation, a constant denominator or "base," is chosen. Stored values are determined by multiplying the actual number by the base.

For example, consider wanting to store dollars and cents. We'll choose 100 as the base. $1.23 is represented as 123. $2.90 is 290; $0.07 is 7; $13 is 1300.

Addition and subtraction work as usual:

Calculation
to Make
Fixed Point
Representation
Result of
Calculation
Corresponding
Dollar Value
$3.50 + $0.23 350 + 23 373 $3.73
$1.23 + $2.90 123 + 290 413 $4.13
$2.75 - $1.12 275 - 112 163 $1.63
$4.05 - $0.20 405 - 20 385 $3.85

Note that a "carry" from fraction to whole number happens automatically (as does a "borrow").

For multiplication, one additional step is needed: the result must be divided by the base after doing the multiplication of the two fixed point values:

Calculation
to Make
Fixed Point
Representation
Result of
Multiplication
Division
by Base
Corresponding
Dollar Value
0.5 × $2.50 50 × 250 12500 125 $1.25
3 × $0.25 300 × 25 7500 75 $0.75

For Division, we first multiply the dividend by the base, then do the specified division:

Calculation
to Make
Fixed Point
Representation
Multiplication
of Dividend
Result of
Division
Corresponding
Dollar Value
$6.75 / 1.5 675 / 150 67500 / 150 450 $4.50
$12.34 / 10 1234 / 1000 123400 / 1000 123 $1.23

Note that in the second example, the result is truncated; remember, we're using integer math!

Fixed Point Math in the Game Timer

For the implementation of fixed point math in the game timer, I chose a base (denominator) of 216. Powers of two are commonly used for the base because multiplication and division are simple bit shifts.

I'm using a fixed point variable to store the fraction of the sound wave period when the pulse should be high (to set the volume level). This number is always less than one (since the maximum value will be half the pulse, i.e. 0.5). So, I just need a short (16 bits) to store my fraction.

I multiply this fraction by the counter's Top value (period) to calculate the counter's Match value (pulse width). This calculation is made using a long (32-bits) in order to avoid overflow.


Resources

  • Game Timer Flowchart (PDF, 60k) – This is a higher resolution version of the flowchart shown above.
  • Schematic (PDF, 269k)
  • Code (ZIP, 59k) – This code is distributed under the GNU General Public License. It comes with no warranty or support.
  • Tone Calculations (XLS, 34k) – This contains a table with counter 'top' values to use for each note. These are general-purpose calculations that could be used with other projects or MCUs.
 
©2000-2024 Idle Loop Software Design, LLC. You may not copy or reproduce any content from this site without our consent.