© James R. Strickland 2016

James R. Strickland, Junk Box Arduino, 10.1007/978-1-4842-1425-1_10

10. Time Out For a Quick Game

James R. Strickland

(1)Highlands Ranch, Colorado, USA

Once upon a time, when you said the words “role playing game” you did not mean a game played on a computer. They were played with pencil and paper and a series of distinctive dice, based on the Platonic solids: a 4-sided die on a tetrahedron, 6-sided on a cube, 8-sided on an octahedron, 12-sided on a dodecahedron, and 20-sided on an icosahedron. As gaming moved on, it was common to add a pair of 10-sided dice, on petagonal bipyramids, which aren’t platonic solids, but do, conveniently, have 10 sides. These were used singly to roll values between 1 and 10, and in pairs to roll percentages, or percentile dice.

In those primitive times, we kept up with the gaming community by subscribing to a magazine. It was in the back of this magazine that we saw another device we all coveted but few of us bought: an electronic die roller.

Recently, while trying to dredge up images of this die roller, I stumbled across the expired patent for the device (it’s in the Credit Where Credit Is Due section). In addition to the shape of the device and the circuit (which contains no microcontroller of any kind) it contained an in-depth analysis of how the device generated randominity. This project, shown in Figure 10-1, is a reimagining of that device, using the same method of random generation, on the Cestino.

A340964_1_En_10_Fig1_HTML.jpg
Figure 10-1. Cestino Dice Device

Because this project also deals, for the first time, in time-critical functionality, it’s also a good vehicle to introduce interrupts and timers. Although we’ve largely ignored them until now, they are the bread and butter of ATmega and similar microcontrollers.

The Stuff You Need

Although this project has more parts than some of the previous ones, they’re smaller, simpler, and among the most common parts out there.

New Parts

  • 2 330Ω resistors. 1/4 watt will work, and they’re what I used, but they get a warm to the touch. I suggest 1/2 watt resistors for this project if you have them.

  • (Optional) 7 150Ω resistors, also ¼ watt. If your display gets dimmer or has dim segments and is hard to read, putting these in as shown in the schematic will fix it.

Used Parts

  • 1 ULN2803 or similar NPN Darlington transistor array. These are a pretty standard item in the Arduino world. If you don’t have one handy, a pair of TIP120 Darlington transistors will work without changing the rest of the circuit. If you haven’t got those either, any NPN transistor capable of switching 80mA or more and appropriate base and emitter resistors will work, but you’re on your own figuring out the resistor values and wiring. PNP devices will also work, and would actually be a better choice for common anode LED displays, but again, this will change the schematic.

  • 1 LTD 4608JG two-digit, right-hand decimal, seven-segment LED display. This is what I used, and since LED displays’ pinouts vary by manufacturer, if you want to use the sketch as written, this is the one you need. That said, there’s nothing at all special about this display. Just about any dual-digit, right-hand decimal seven-segment LED display will work, but you will have to change the wiring of the display to suit the display you have. You can also use two single seven-segment, common anode, right-hand decimal LED displays. Just wire the segment and decimal lines together and again, your setup will differ from the schematic. You’ll need the datasheet for whatever display you use.

  • Two Tactile Switches, (buttons) momentary on. I used the same kind I used for the Cestino’s reset button. Ideally, these will have different colored buttons or some other way to tell them apart, because they have different functions.

Electronic Dice

Computers in general are very bad at creating randominity when they’re working correctly. The entire point of logic circuitry is repeatability: a given set of inputs result in a given output. Randominity flies in the face of this. There are many random generation algorithms. It’s a heavily researched field, since encrypted data should be indistinguishable from random values. Indeed, a friend of mine who wrote software for slot machines once told me it takes a great deal of study and quite a lot of computing power to generate sufficiently random values for gambling.

So how did the $20 (about $60 or $80 in modern dollars) electronic dice device I saw in that magazine back in the bronze age of computers accomplish it? Here’s how the circuit worked.

We talked about counters in Chapter 6. If you tied the 74LS92 to the 20MHz oscillator on the Cestino, you undoubtedly observed that the 74LS92 can count very, very fast. You’ll recall from Chapter 7, the logic probe, that we humans can’t even perceive LEDs changing from bright to dark or back in much less than 20ms. The dice device used both these factors to its advantage.

It was a simple beast. It contained a pair of TTL decade counters, one for ones and one for tens, each of which had outputs for zero through nine. Because a die can’t roll zero, the ones counter was wired to show one higher than its actual count: its zero output is wired to LED 1, and so on.

When the ones counter’s output nine switched on, instead of lighting an LED, it incremented the tens counter. The tens counter, by contrast, was wired to count from zero to nine. Left to run freely, the counters would count from zero to ninety-nine, lighting up one or two LEDs to indicate values from 1 to 00. (00 had a separate LED, and represented a 10s digit of 0.)

A six-position switch and two AND gates were used to select the die. For four-, six-, and eight-sided dice, the switch simply tied output four, six, and eight (which represented five, seven, and nine, as the ones counter is skewed by one) to the reset circuit for both counters. For 12-and 20-sided dice, it connected to the output of an AND gate. The 12-sided die’s AND gate tied output 2 (representing 3) of the ones counter and output one (representing 10) of the tens counter together to represent 13. When the counters reached 13, the AND gate would reset them long before the LED could light. The 20-sided die worked the same way. Output zero of the ones digit (representing 1) and output 2 of the tens digit (representing 20) were wired to the other AND gate, and when the selector switch was set to its output, and those two outputs went high, the AND gate would reset the counters before the LED could light.

For percentiles, the reset circuit was grounded and both counters were permitted to run freely.

To actually roll the dice, you held a button down. It connected an oscillator to the counters, and this is where the randominity was generated. We don’t think of our actions as random, but measured on the scale of a few hundred kHz, when our thumb actually comes off a button, and when the button’s contacts stop bouncing (more on that later) is actually quite solidly random. So once you selected the die you wanted, you held the button down. All the LEDs up to your roll appeared to light simultaneously, even though no more than two were ever actually on, and when you let up, they stopped at your roll apparently instantly.

It’s an elegant, all TTL design . It uses two decade counters, a dual AND gate for die selection, and a quad NAND gate, used with a resistor and a capacitor as an oscillator. The rest are switches, LEDs, resistors, and a battery. Because they used 4000 series CMOS TTL, the device could tolerate a wide range of battery voltages as the battery ran down. Because it was simple, it would have been fairly cheap to manufacture.

Apparently they didn’t take the gaming world by storm. The patent, US4431189, was filed in 1981, and by 1987 it had expired for failure to pay maintenance fees. In 30-plus years I’ve been gaming, I’ve seen exactly one in use.

We’re going to have the Cestino generate random numbers the same way, by measuring the button-down time some hundreds of thousands to millions of times a second. We’re also going to exploit the low speed of human eyes and LEDs the same way as we drive our seven segment displays.

Driving 7 Segment Displays

So what exactly is a seven-segment display with a right-hand decimal and a common anode? It’s seven LEDs shaped like segments of a number from zero to 0xf plus one that is on the right and very small, where it serves as a decimal point. Common anode means all the anodes (positive terminals) of the LEDs are tied together. You put voltage on the anode, ground the cathode pins for the segments you want on (as always, with dropping resistors, but we’ll get to that) and you have a display. Put two of them together and tie the cathodes for each pair of segments in the same position, and you have a two-digit display, which is what the LTD4608JG is. You chose the digit you want by choosing an anode to apply power to, and you chose what segments to light by choosing which cathodes to ground. Note that the digits are numbered left to right, so the leftmost digit is digit one, and the rightmost digit is digit two.

The cannonical “correct” way to wire the seven-segment display is with one dropping resistor per segment. But this eats up a lot of breadboard real estate and takes a lot of extra wiring. Plus, two of our 330Ω resistors are permanently tied up in the logic probe circuit. If you’ve looked closely at the photo of the Cestino Dice Device (Figure 10-1), you’ll see I took another tack.

Recall that Kirchhoff’s first law is that the sum of the currents flowing into a node of a circuit equal to the sum of the currents flowing out. This means that whichever side of the LED we put the dropping resistor on, it will impose the same current limit and cause the same voltage drop. We can, as easily, put our dropping resistors on the anodes. Because all of our segments in each display digit share a common anode, this cuts our resistors to two. If we connected one of these resistors from + to - it would drop 5 volts and dissipate a bit over 15mA, and a wattage of about 75mW. Our resistors are rated at 250mW (0.25 Watt), so we’re fine there. Does adding the LEDs in parallel decrease their resistance? Yes, but it’s unimportant. The forward drop of each LED remains the same, about 1.5 volts, which means our dropping resistor limits the current to about 10mA. (The datasheet doesn’t specify the forward drop. It’s a lousy datasheet.)

It’s tempting to calculate each segment’s load and add them together, and then worry about the wattage of the resistor, but recall Kirhhoff’s first law says it isn’t so. The current is limited by the resistor and all the circuits tied to it share that current. This means that the LEDs get slightly dimmer, the more of them that are switched on, as they divide the 10mA available among all eight (worst case) LEDs.

This isn’t standard industry practice, and there’s a reason. Each LED in a seven-segment display has slightly different characteristics. Some may draw more current than others, or they may have slightly different voltage drops, or different internal resistance. As you add these variations together, you can get a display that is hard to read, or has some segments brighter than others. My LED display worked fine, but if you’re having these problems, add a 150Ω resistor between each cathode and its respective PORT A pin.

So we know we can switch the current we need to light the seven segment LEDs, but what if we want more digits? What if we wanted to make a calculator? Can we really only drive four seven-segment displays with one ATmega1284P? Do we really have to use two full ports just for these two digits? Could we really only drive four seven-segment displays with an ATmega1284P, and that only if we didn’t want it to do anything else?

The answer is no. We can drive an arbitrary number of digits with one port, plus one pin per digit. The technique is called time division multiplexing.

Time Division Multiplexing

From our experiments with the logic probe , we know that between the speed of an LED and the speed of human vision, we can’t perceive off pulses on an LED of less than about 20ms, or a frequency of about 50Hz. We’ll use 60Hz from here on. I am an American, and our wall power is 60Hz. If the LEDs cycle much slower than the overhead lights, it can make them appear to flicker at the difference between the two frequencies.

For those outside the Americas, the Caribbean, and parts (but not all) of Korea and Japan, your power is probably 50Hz, which fits the metric way of thinking better. You might need to slow the display timing in the sketch to suit your lights if it appears to flicker. You could also increase the display speed until the flicker goes away.

As long as we power each LED at least every 16.6ms, we won’t see the difference. We know from those same experiments that 60Hz is glacial compared to the speed of transistor logic. That’s how the digits in this project are driven. If the port can react at least once every 8.3 ms per digit (hint: it can) we can drive two digits with different values and have them appear to be lit at the same time. Three digits would need updating every 5.53ms, and so on.

As with all things, there’s a cost. We have to control, with great precision, which display is active at any given time. Their common anodes give us an easy mechanism to do that. To talk to digit 1, on the left, we connect the common anode of that digit to the + bus (by way of the dropping resistor). To talk to digit 2, on the right, we disconnect digit 1 from the + bus and connect digit 2 to it. We can switch whichever cathode lines we want. Without power to its anode, digit 1 won’t do anything. Again, as long as we get back to powering digit 1 in less than 16.6ms, we won’t see the difference.

We could reduce the duty cycle further, powering each digit for less than half the time, either by adding more digits to the multiplexing system, or by making the duty cycle asymmetric—off more than it’s on—and if we needed to conserve power, the extra complexity might be worth it. For this project, we’ll stick with a 50 percent duty cycle, and two digits. With the circuit we’re going to build, we could run up to eight, on two ports. With a better design (an oscillator, a decade counter, and some buffers, perhaps) we could do many more with only one port. If we had an ATtiny, with only three or four GPIO pins available, we could multiplex the individual segments as well .

Interrupts

The big catch of multiplexing (muxing, for short) is that it’s time sensitive. If our loop function gets busy doing something else, and the display switching doesn’t happen on time, it will be visible. Worse, if the display switching and the port switching get out of sync, the digits could be transposed or superimposed over each other. We need a way to let the ATmega do the job we want of it, but make sure that every so often, it stops and updates the display. Fortunately, like nearly all microprocessors and microcontrollers ever made, the ATmega1284P has circuitry that, when activated, stops whatever it’s doing, and jumps to another part of the code. When that part is done, it jumps back and picks up where it left off. It’s called an interrupt.

The piece of code (a function, since we’re using C/C++) called when the interrupt is activated is called the Interrupt Service Routine, or ISR.

If we were looking at assembly language, the address in program memory that the interrupt causes a jump to is called the interrupt vector. These are handled for us by the Arduino core.

The ATmega1284P has two types of interrupts: internal and external We’ll deal with the external type first.

If you look at the pinout diagram in the ATmega1284P datasheet (I left them out of the one in Chapter 2) you’ll see that all the pins of all the ports have a PCINT number. These are not the interrupts we’re looking for. These are the PCINT system.

The PCINT interrupt system is designed to monitor whole ports at a time for any change to the pins, whether or not the pins are set up as outputs. Each port’s PCINT system can call one fixed interrupt service routine (ISR)—the code that the interrupt sends the processor to. This is handy for things like the ATA Explorer (although we didn’t use it) so that when the drive returns a byte of data, our code can go deal with it, regardless of what else it was doing at the time.

Although there are several libraries for Arduino that allow PCINT to be used in sketches, we won’t be using the PCINT system in this project. We need finer control over what events cause an interrupt.

Instead, we will use the external interrupt system, called INTs. There are three INT pins: INT0, INT1, and INT2 (ATmega328 based Arduinos have only the first two). These are found on pins INT0/PD2 (Pin 16), INT1/PD3 (Pin 17) and INT2/PB2 (Pin 3) of the ATmega1284P. These lines are, in other microprocessors and microcontrollers, called IRQ lines—Interrupt ReQuest lines. File that away. You’ll see IRQs again in chapter 11.

The ATmega1284P’s three interrupt lines can be configured to activate when the pin is low—below 0.3 volts, when it is high—above 0.6 volts, when it rises from below 0.3 volts to above 0.6 volts, when it falls from above 0.6 volts to below 0.3 volts, or on any change at all, like the PCINT system. Each interrupt pin has its own interrupt vector, so it can call a specific interrupt service routine. The INT system allows us to know which pin /exactly/ changed and what exact change occurred before we even get to the interrupt service routine (ISR). This is its big advantage over the PCINT system.

Configuring Interrupts

The Arduino core makes configuring external interrupts very, very easy with the attachinterrupt() function. This function returns no data, and takes three parameters: the interrupt number, the Interrupt Service Routine’s name, and the interrupt mode: LOW, CHANGE, RISING or FALLING. There is a HIGH mode, but it is only implemented on certain types of Arduino that don’t use an ATmega microcontroller.

If you read about Arduino interrupts, you’ll see they recommend using a function called digitalPinToInterrupt() to look up the interrupt number for a given pin. As of this writing, this function doesn’t work on the Cestino, and calls to it won’t compile.

Here’s an example of attaching an interrupt, snipped from the sketch we’ll be writing later in this chapter:

attachInterrupt(1, read_selector_isr, FALLING );

This command connects interrupt 1 to the function read_selector_isr, and configures the interrupt to occur when the voltage on that pin (PD3, aka Pin 17, if you wondered) falls from above 0.6 volts to below 0.3 volts.

Note especially that while read_selector_isr() is the correct name of the c function, it isn’t called with any parameter parenthesis. ISRs are not allowed to take any parameters, nor may they return any data, and they are attached without their (empty) parameter field.

Interrupt Service Routines (ISRs )

Interrupt Service Routines are, in C and C++, functions that take no parameters and return no data. They must, above all, be brief. The longer they run, the more likely additional interrupts will be missed (global interrupts are disabled while an ISR is running) and while the ISR is running, the ATmega is not doing whatever job was interrupted. Short, fast ISRs are important. Incrementing a variable is a good job for an ISR. Triggering a hardware event, such as turning a pin on or off is a good job for an ISR. Reading and storing an incoming piece of data is the most common use .

Global variables that are changed by an interrupt service routine must be handled differently than normal. A normal variable exists in memory. A copy of it may also exist in the registers of the microprocessor. As long as the microprocessor is in the same section of code that the variable is in, the variable in memory can be updated when that section of code exits, or at some other interval determined by the compiler and/or the Arduino core. When the ATmega jumps to an interrupt vector and begins running a completely different piece of code, the registers and the variable in memory can get out of sync.

To avoid this problem, simply add a qualifier to the variable definition: volatile, as in volatile byte myvariablename. This tells the compiler to generate code such that the variable is /always/ accessed from memory. While somewhat slower than using registers in the ATmega, this ensures that the copy in memory is up to date with any changes made to it by the ISR. Local variables in the ISR need not be declared volatile, as their context remains the same for their entire (brief) existence .

Interrupt Vectors

For INTs (external interrupts), the interrupt vectors are handled automatically by attachInterrupt, and (presumably) a pointer to our ISR is placed in the interrupt vector for us. This is incredibly handy. Sadly, the Arduino Foundation did not see fit to extend this to internal interrupts, like those used by timers, for reasons known only to themselves. Here’s how interrupt vectors really work, so they’re not confusing later.

When an interrupt happens, the ATmega stops executing whatever code it’s executing, does some quick housekeeping so it can remember where it came from, then jumps to a specific pair of locations in memory. Those locations had better contain another jump instruction, and a location in memory that contains the code that will handle the interrupt.

Because internal interrupts (like those generated by the timer) are not handled by the attachInterrupt() function, the Arduino core sets them up to call specific function names, even though at the assembly language level, underneath the C we look at, internal and external interrupts are handled more or less the same.

The AVR/GCC compiler environment has #defined macros to handle all the interrupt vectors, for example: TIMER3_COMPA_vect, so that by using this name for the actual interrupt vector (which is 0x0040, in case you wondered) as a parameter to the ISR() macro, the vector would be set up correctly, and the compiler signals (which we haven’t talked about) would be handled correctly.

This is still how timer/counter interrupts are still handled. You’ll see this in the sketch where the selector interrupt service routine is set up with attachInterrupt and has a normal name, but the timer interrupt vector is set up and connected to its ISR with ISR(TIMER3_COMPA_vect), which will make a lot more sense after you read the section on timer/counters .

If you wanted to, you could handle external interrupts (INTs) the same way we do internal interrupts, using ISR(EXT_INT1_vect), changing the numeric part for whichever INT you actually wanted. You’d then have to set the interrupt mask to enable that external interrupt yourself too. You’ll sometimes see it done this way this in older Arduino sketches.

Timer/Counters

If you think back to Chapter 6, where we talked about the 74xx92 counter, timers will make a whole lot more sense. Imagine you connected that counter, or one of the decade counters I mentioned earlier to the 20MHz TTL oscillator that drives your Cestino. You know that the counter generates some combination of signals every time its input goes positive. You know that it will go positive exactly 20,000,000 times a second. If you know the oscillator’s speed and you count the pulses, you have a timer.

To set the timer, you have to have a place to store a bit pattern that you can check against the output of the counter, and some logic that does something if they match, or don’t match.

With an extra place to store some control settings and some logic, you could use the same electronics as a counter for other events, too. Two functions out of one circuit. Awesome.

This is exactly how timers work in the ATmega1284P. If you guessed that the control settings and the bit pattern to match are stored in registers, you catch on fast. The Arduino world goes to great machinations to hide the timer registers and make them “easy to use” by creating a #define for each bit of each mask, and suggesting you logically combine them, but we’re used to looking at eight and sixteen bit registers. We’ve done it for the last two chapters. Here’s how the ATmega1284P really handles timers.

ATMega Timer/Counter Counting

There are three timer/counters in the ATmega1284P. (ATmega328 based Arduinos have only 2.) They come in two flavors, one 8 bit counter/timer, and two 16 bit counter/timers, which means exactly what it sounds like. The 8 bit timer/counter can count to 255 (0xFF) before resetting, and the 16 bit timers can go to 65535 (0xFFFF) before resetting.

Each timer/counter has two possible clock (the signal that triggers the timer) sources. One is the external clock source, which is an edge detector, that picks up the rising or falling edge of an external pulse, just like the INTs do. We won’t be using that one here. The other source is internal, called the timer/counter prescaler. This is a 10 bit counter used as a divider, whose input is the system clock.

What does this mean? We can select the clock for our timer/counter to not be divided, and run at the full 20MHz of the system clock, or we can choose to have the clock divided by 8, 16, 32, 64, 128, 256, or 1024. This is set in the Timer Counter Control Register (TCCR ), along with a lot of other parameters I’ll get to. The value the timer has counted to is always stored in the Timer CouNTer register (TCNT) for that timer.

ATMega Timer/Counter Actions

Timer/counters would be pretty useful if they did just that, and we had to read the Timer Counter register and process the results in software, but like the commercials on late night TV always say “wait, there’s more.”

Timer/counters can make the comparison for us at hardware logic speeds, and they can trigger events in hardware. There are more registers for this, in case you hadn’t already guessed.

Timer/counters on the ATmega have many possible functions. They can trigger an event when the TCNT register reaches its maximum value. They can trigger an event when the TCNT register reaches its minimum value. They can trigger when a specific value stored in the Output Compare Register is reached. They can also do combinations of these functions.

Timer/counters can switch an output pin on and off, very quickly, without program intervention. This, plus some configuration, allows them to generate clock signals (we’ll talk about this more in Chapter 11) and Pulse Width Modulation (PWM) signals. In PWM , the duty cycle of a signal controls /how much/ current and voltage is available to down-stream electronics. PWM is essentially an analog output, with a couple of passive components. You can also use it to control servo motors.

Most important for our uses in this sketch, however, timer/counters can cause an interrupt in the ATmega, and the resulting jump to an interrupt handler.

What exactly the timer does and when, is configured in the TCCR register, and is broken up by modes. I’ll cover all the registers and modes next.

Configuring Timer/Counters

We’ll use the 16 bit timer 3 as our example. Eight bit timers will have smaller TCNT and Output Compare Registers (OCRs). For different timer/counters, you’d use a different number, like TCCR1A or TCCR2A, and so on. The numeric value determins which timer you’re using.

TCCR3A —TCCR3C

There are really three 8 bit Timer Counter Control Registers per timer/counter, for a total of 24 bits. A lot of this space isn’t used, but the engineers at Atmel wisely decided to leave some extra space for future capabilities.

TCCR3A contains these bits in the following order: COM3A1, COM3A0, COM3B1, COM3B0, unused, unused, WGM31, and WGM30.

TCCR3B contains these bits: ICNC3, ICES3, unused, WGM33, WGM32, CS32, CS31, and CS30.

TCCR3C contains these bits: FOC3A, FOC3B, and the rest are unused.

Clock Select Bits (CS30-CS32)

The Clock Select bits, CS30, CS31, and CS32 for Timer/Counter 3, allow us to select what triggers our counter to count. These three bits are the lowest three of TCCR3B.

0b000 for these three bits is no clock source. The counter is stopped.

0b001 uses the internal clock divided by 1. That is, with no prescaling. This means our counter will get to drink from the 20MHz firehose of the Cestino’s TTL oscillator.

0b010 will divide the internal clock by 8. Our counter will be triggered (clocked) every eight pulses of the 20MHz oscillator.

0b011 will divide the internal clock by 64.

0b100 will divide the internal clock by 256

0b101 will divide the internal clock by 1024.

0b110 uses an external clock source on the appropriate pin, (note that it’s different for different ATmega types) and it will clock on the falling edge, when the signal on that pin falls from above 0.6v to below 0.3v.

0b111 uses an external clock source on the appropriate pin, and it will clock on the rising edge, when the signal on that pin rises from below 0.3v to above 0.6v.

Waveform Generation Modes (WGM30-WGM33)

The four Waveform Generation Mode bits are located in the lowest two bits of TCCR3A and bits three and four of TCCR3B. I’m going to treat them like they’re one contiguous bit field, with WGM30 at the low end and WGM33 at the high, but you’ll need to break that bit field up and put the bits in the right registers when you use them.

0b0000 is normal mode. The timer counts from zero to 65535 and rolls over.

0b0001 to 0b0011 are phase correct PWM modes, from 8 bit to 10 bit resolution. This means the timer/counter counts more often to ensure that the resulting waveform’s phasing is consistent.

0b0100 is Clear Timer on Compare (CTC) mode 4. When the timer hits the value stored in OCR3A, the timer (TCNT3H and TCNT3L) will be zeroed and a timer event will be triggered.

0b0101 to 0b1011 are various other PWM modes, with the focus on correct phasing, correct frequencies, or both. These use the timer/counter in different ways, depending on what parts of the resulting PWM signal are most important: high frequency, the phase-accuracy, frequency accuracy, or phase and frequency accuracy. Generally speaking, the phase accurate and phase and frequency accurate modes are good for motor control (according to the datasheet) and the fast modes are good for power regulation and digital to analog conversion, where higher frequencies mean smaller passive components. We won’t be using PWM in this project.

0b1100 Clear Timer on Compare (CTC) mode 12 is the same as CTC mode 4 except that the ICR3 register is used for the comparison. That register can be set by input events. See IRC3H and ICR3L

0b1101 is reserved, so we can’t use it.

0b1110 to 0b1111 are more fast PWM modes.

Compare Output Pin Mode Bits (COM3A0-1, COM3B0-1)

The Compare Output Mode bits are the highest four bits of TCCR3A, arranged like this:

COM3A1, COM3A0, COM3B1, COM3B0.

The output compare system of each counter has two channels, either of which can trigger an action. One counter can trigger an action when OCR3A is reached, and a different action when OCR3B is reached. (see OCR3AH and OCR3AL and OCR3BH and OCR3BL, below).

The COM3A and COM3B registers set up whether a pin is toggled by the timer/counter event, and if so, when and how. COM3A and COM3B correspond to pins OC3A and OC3B. These are also known as PB6 and PB7 (Pin 7 and Pin 8). If Timer 3 is configured to trigger a pin, the timer will temporarily override the settings on that port to do what we’ve told it.

Since the COM3A and COM3B bits do the same things for their respective channels, I’m going to treat them as a pair of bits, with COM3x1 the highest, and COM3x0 the lowest.

0b00 means the port operates normally. No outputs are connected .

0b01 means that whatever state the OC3x pin is in, switch it when the timer/counter reaches the value in OCR3x (where x is A or B).

0b10 means when the timer/counter reaches the value of OCR3x, turn the pin off. If we’re counting down, turn it on.

0b11 means when the timer/counter reaches the value of OCR3x, turn the pin on. If we’re counting down, turn it off.

We don’t actually use the compare output pin modes in this project, although it would have saved some code and processing time if we had. File them away, though. We use compare output pin modes a lot in Chapter 11.

Input Capture Bits (ICNC, ICES)

ICNC3 and ICES3 are bits 7 and 6 of TCCR3B. They control aspects of the input capture system.

ICES3, Input Capture Edge Select determines whether the input capture pin for Timer/Counter 3 (ICP3, aka PB5, Pin 6) clocks the counter on the rising edge or the falling edge. If you want the timer/counter to clock when the pin transitions from below 0.3v to above 0.6v, use the rising edge, and set this bit to 1. If you want it to clock when the pin transitions from above 0.6v to below 0.3v, use the falling edge, and set this bit to 0.

ICNC3, The Input Capture Noise Canceler bit for timer/counter 3 turns the input capture noise canceler on and off. When it’s on (1), the input pin is read three more times, and the timer/counter is clocked only if all four readings are the same .

Force Output Compare Bits (FOC3A and FOC3B)

The Force Output Compare bits , FOC3A and FOC3B, are the only interesting bits in TCCR3C, bits 7 and 6, respectively. When written to, these bits force whatever output compare action is configured (changing the state of a pin, usually.) This will not set the OCF3A or B flag (see the TIFR3 register), nor will it reset the counter if we’re in CTC mode.

TCNT3H and TCNT3L

The TCNT3H and TCNT3L registers are two parts of a 16 bit register containing the timer/counter’s actual value. In Arduino, they are merged into TCNT3 and read or written to as an integer.

A timer/counter need not start from zero or 65535. You can write whatever starting value you want into this register and count up or down from it.

OCR3AH and OCR3AL and OCR3BH and OCR3BL

OCR3AH and OCR3AL and OCR3BH and OCR3BL are the Output Compare Registers for the A and B channels. Each pair (H and L) is a 16 bit register, which you’d expect to match a 16 bit TCNT3 value. Like TCNT3, the high and low (H and L) pairs are merged in Arduino into a single register: OCR3A and OCR3B, and can be read and written as integers .

ICR3H and ICR3L

The ICR3H and ICR3L registers are both halves (high and low) of the 16 bit ICR3 Input Capture Register. When the Input Capture Pin (ICP3 aka PB5, Pin 6) is triggered, according to the rules set in TCCR3B, the value of TCNT3 is copied into ICR3. As with all 16 bit registers, ICR3H and ICR3L are merged in Arduino into the ICR3 register, and may be written to or read from as integers.

TIFR3

The TIFR3 (timer/counter interrupt flag register) is an 8 bit register that contains all the flags for Timer/Counter 3. A flag is simply a boolean value, represented by a single bit, that is set when the bit is set to one, and cleared or not set when the bit is set to zero. These flags can be thought of as the interrupt signals. If the TIMSK3 register has the corresponding bit set, they interrupt the ATmega. If it doesn’t, they don’t, but you can still read them. Functionally, the timer/counter system ANDs the TIFR3, and TIMSK3 registers and a global interrupt enable register to determine which, if any, interrupts to fire.

The bits in this register go like this: reserved, reserved, ICF3, reserved, reserved, OCF3B, OCF3A, TOV3.

The reserved bits will always be zero. We can ignore them.

ICF3 is set when ICP3 is triggered according to the rules set for it in TCCR3B. This means that if the pin’s voltage rises, and the input capture system for Timer/Counter 3 has been set to capture the rising edge of that pin, when TCNT3 is copied to ICR3, the ICF3 flag will also be set.

OCF3B and OCF3A are set when an output compare match occurs on channel B or A (respectively).

TOV3 is the overflow flag. When the timer/counter overflows in either direction—high if it’s counting up, low if it’s counting down) TOV3 is set. TOV’s exact behavior is set by the WGM bits in TCCR3A and TCCR3B .

TIMSK3

TIMSK3 is an 8 bit register containing a mask to determine what interrupts are activated, if any, and when, when a timer match or overflow happens. Functionally, the timer/counter system ANDs the TIFR3, and TIMSK3 registers and a global interrupt enable register to determine which, if any, interrupts to fire.

This is the register we’ll use to enable an interrupt to call our interrupt service routine (ISR) to drive the LED displays. The bits in this register go like this: unused, unused, ICIE3, unused, unused, OCIE3B, OCIE3A, TOIE3.

ICIE3 is the input capture interrupt enable for timer/counter 3. If this bit is set to 1, and ICP3 (the input capture pin) goes high, an interrupt will be generated.

OCIE3B and OCIE3A are the output compare match interrupt enables for channels A and B. If either of these is set to 1, when OCRA3 or OCRB3 is matched (respectively) an interrupt will be generated.

TOIE3 is the overflow interrupt enable. When the counter overflows either high or low, depending on which direction it’s counting, the TOV3 flag is set, and the counter is reset. If this bit is set to 1, an interrupt will also be generated.

Build the Dice Device

For all the complexity of using the hardware timers and interrupts, as you can see from the schematic in Figure 10-2, the actual circuit of the dice device is very simple. (That’s the advantage of using the hardware timers and interrupts.) There’s also a certain dumbness in this circuit, that I’ll explain when we get to it.

A340964_1_En_10_Fig2_HTML.jpg
Figure 10-2. Dice Device Schematic

There are four components besides the Cestino in this circuit: two buttons, the dual LED display, and a ULN2803. This last is an integrated circuit containing eight Darlington transistor channels, complete with built-in load and base resistors, as well as diode protection. It’s a staple of the Arduino world, used to drive loads up to half an amp per channel, for electric motors (where the diode protection is a must), light bulbs, solenoids, and large strings of LEDs. Here’s where the dumbness comes in: it’s not really the right choice to drive a common anode LED display. The common anodes connect to the + bus of the Cestino. When one of the bases of the ULN2803A is raised high, the corresponding output pin is connected to a common ground. All the emitters of the Darlington transistors are tied to it. In order to use it in this circuit, what I’ve done is put the 330 Ohm dropping resistors between the anodes of the LED displays and the + bus, and then wired the display side of each resistor to one of the ULN2803 channels . When that channel of the ULN2803 is activated, the common anode is shorted to ground. This works, but it means that when a display is turned off, more current is consumed and turned into heat by the dropping resistor, than when it’s switched on. In a battery driven situation, this would be incredibly wasteful. If you have separate Darlington transistors, such as the TIP120, or any other power transistors rated at 15mA or more plus whatever resistors they need to switch at 5v, you could wire the emitter to the common anode (via the dropping resistor), and the collector to the + bus and only consume current to make light. The ULN2803A is not really the right IC for the job, but when you’re making electronics from stuff in your junk box, sometimes you have to compromise. In fairness, we’re making no effort whatever to conserve power with the Cestino either.

That said, let’s go ahead and build it.

As you saw in Figure 10-1, I put the display at the top of the breadboard, followed by the ULN2803A, followed at some distance by the buttons. Make sure you can tell your buttons apart. The upper one will roll, and the lower one will select what die to roll.

We’ll drive the LED display with port A, so wire pins 1, 2, 3, and 5 of the LED display (assuming you’re using the LTD4608JG or JR—the red version) to PA0-PA3, and 6, 7, 8, and 10 to PA4-PA7. Yes, seven segment displays have only seven cathodes, but the decimal points have one each, too. If you find your display gets too dim, or the segments are not evenly lit, you can go back and add the resistors marked Opt. 150 (optional 150Ω) between PA0-8 and pins 1,2,3,5,6,7,8, and 10 on the LED display.

Wire a 330Ω resistor to pin 18 of the ULN2803, and from there to the + bus. Do the same with the second 330Ω resistor and pin 17. Then wire the junction between pin 18 and the resistor to pin 9 of the LTD4608JG LED display. This is the common anode line for digit 1, which is the leftmost, or most significant digit. That’s how the datasheet for the LTD4608J series reads, and for clarity, I’ll call them the same thing.

Connect the junction between the resistor and pin 17 of the ULN2803 to pin 4 of the LTD4608JG. This is the common anode for digit 2, the rightmost, least significant digit.

Now wire pin 1 of the ULN2803, which controls the common anode for digit 1, to PC0, (Pin22) and pin 2, which controls the common anode for digit 2 to PC1, (Pin 23).

The display is now wired.

If you are using a different LED display than the LTD-4608JG or JR, it is vital that you find a datasheet on it and read it. Your pinout will be different. Don’t count on the segments being lettered the same either. If your display is a common cathode display (they’re not as common) you will need to change the common anode driver circuit (the connections to the ULN2803) accordingly and change part of the sketch to reflect that your segments are active high instead of active low. You can do this. It took me maybe half an hour to convert the sketch and the circuit to this display from the one I used in the prototype. It’s just lighting up LEDs. As long as they make light in the right place given the right signal, it’ll work fine.

The roll button (the red one in Figure 10-1) is connected to pin 16. You might notice that this is INT0, and you may have heard that using INT0 messes up serial communications between Arduinos and the host computer. All true, but we’re not using the interrupt on this pin. This pin will be poled by the loop() function , that is, loop() will read it over and over again (using digitalRead, no less.) Go ahead and wire PD2 (Pin 16) of the ATmega to one side of the roll button. Wire the other side of the roll button to the - bus.

The selector button will use an interrupt, INT1, in this case, which is connected to PD3 (Pin 17), so go ahead and connect one side of your button switch there and the other side to ground. If you’re guessing we’ll be turning on the pull-up resistors on both these pins, you’re right, and normally you’d be jumping ahead, except that we’re done with the wiring. Really, that’s all there is to it.

The Sketch

The sketch, for all the complexity we’ve added using interrupts and hardware timer/counters isn’t that complex either. Sometimes knowing why we do something is far more complicated than actually doing it. But the why is half of why we’re doing it, right?

In any case, we begin with the usual precompiler definitions. First, we define our busses. SEG_BUS drives the displays and MUX_BUS drives the common anodes to allow us to multiplex, mux for short, the displays.

#define SEG_BUS PORTA
#define MUX_BUS PORTC

We define the port settings for the MUX_BUS next. Remember that the common anodes, because of how we had to wire them to use the ULN2803, are active low. So to turn /on/ the MSD, which is digit 1, we need to make sure that PC0 is low and PC1 is HIGH. We do this by setting the port to 0x2, in hex, which turns on the two bit, and turns all other bits off. LSD_COMMON works the same way, save that it turns on the ones bit and turns off all the others. Active low, as we’ve seen over and over again, makes everything backward.

#define MSD_COMMON 0x2
#define LSD_COMMON 0x1

Most of our displayed values will fit the usual hexidecimal digits, but because the seven segment (plus decimal) displays can display some values that aren’t in hex, we define those here. We define them in decimal for clarity, but they’d be 0x10 and 0x11 in hex.

#define DECIMAL 16
#define BAR 17

Next we define which pin is the button pin (the roll button is connected here) and its interrupt (even though we don’t use it) and the selector button pin, and its interrupt, which we do use.

#define BUTTON_PIN 16
#define BUTTON_PIN_INTERRUPT 0
#define SELECTOR 17
#define SELECTOR_INTERRUPT 1

Interrupt service routines have one particular annoying feature: they can neither take data nor return data. Since we’re dealing with them in C as functions instead of C++ objects, we have to store their data in global variables. Some of the variables also need to be persistent, rather than recreated every time a function is called, to save compute time. Again, using an object would make this simpler.

The first global is seg_decode_array . It defines an array from zero to 17 that contains all the possible port settings to drive that particular value on one digit of the LED display. Remember that we defined 16 as BAR and 17 as DECIMAL, so they’re the last two entries in the array. Because we’re initializing the array at the same time we define it, and it’s a constant, we don’t have to specify an index number. With this specific LED display, and the segment order used in its datasheet, the order of the bits in this pattern are, from MSB to LSB:

A B G F D E Decimal Point C

const byte seg_decode_array[] = {
  0b00100010, //0
  0b10111110, //1
  0b00010011, //2
  0b00010110, //3
  0b10001110, //4
  0b01000110, //5
  0b01000010, //6
  0b00111110, //7
  0b00000010, //8
  0b00001110, //9
  0b00001010, //A
  0b11000010, //B
  0b01100011, //C
  0b10010010, //D
  0b01000011, //E
  0b01001011, //F
  0b11111101, //DECIMAL
  0b11011111  //BAR
};

Each 0 in these bit patterns corresponds to a lit LED segment, and each 1 corresponds to one that is left dark. Obviously, if your display is wired differently from the one in the schematic (note that the A B G assignments may also be different) these patterns will produce gibberish on your display, and you’ll need to create your own .

The next global variables are for ISRs. The first of these is msecs, a count of miliseconds, which we use for debouncing the selector button. What does this mean? Well, consider that the ATmega can respond to an interrupt in microseconds to nanoseconds, and that nothing we do with our fingers happens in that time. Further, a switch’s contacts are physical. They can bounce around and remake contact. Because the ATmega is so fast, we’ll interrupt on every one of them. This makes a push-button selector pretty much useless. We could put a capacitor in series with the push button, so that the jitter is soaked up by the capacitor, but we can do the same thing in software and save a component. As you get deeper into electronics, especially if you’re designing for manufacture, you’ll find that saving a component by adding software is a constant theme. Ever wonder why nothing has a real power switch anymore?

You might note that milis is declared volatile. This tells the compiler that this variable is always to be read from RAM, rather than from a stored register. Globals modified by interrupt service routines should be volatile, since when a given variable is updated and by what routine (the ISR or a regular function) are difficult to know.

The other value here is a flag, a simple boolean to tell us whether we’ve rolled since the last time we selected. This is used in loop() and I’ll cover it there.

volatile unsigned long msecs = 0;
boolean rolled_since_select = false;

Roll stores the value of the last die rolled. It’s sent to the display system every time loop() is called when rolled_since_select is true.

Die does the same thing, except that it’s set by the selector button ISR, so it’s declared volatile.

D_select is the value of the die that is selected. It’s modified by the selector button ISR, so it’s volatile.

LSD and MSD are the variables that hold the value of each of the two digits in the display, from 0x0 to 0x11, remembering that 0x10 and 0x11 are bar and decimal, respectively. While they’re read by the timer/counter interrupt ISR, they’re not changed by it, so they need not be volatile.

Lsd_active is modified by the timer/counter ISR, so it is declared volatile. It’s used to determine which digit is currently active.

byte roll = 1;
volatile byte die = 4;
volatile byte d_select = 0;
byte lsd = BAR;
byte msd = BAR;
volatile boolean lsd_active = true;

Next comes the read_selector interrupt service routine. When the selector button is pushed, and the interrupt sends the ATmega off on the interrupt vector to run some code there, this is the code that gets run.

The millis() function returns the number of milliseconds that have elapsed since the sketch started. We subtract msecs from it. If more than 250ms have elapsed since the last time the selector interrupt fired, we increment d_select, and set msecs to millis(). If it’s less than 250ms, a quarter of a second, we’re probably looking at button jitter, so we ignore it. Regardless, we set rolled_since_select false. We’ve just selected. Once all this is done, the ATmega goes back to what it was doing. Most likely executing code in loop().

void read_selector_isr() {
  if (millis() -msecs > 250) {
    d_select = d_select + 1;
    msecs = millis();
  }
  rolled_since_select = false;
}

What’s next is the interrupt service routine for Timer/Counter 3. Note that, like I said at the end of the section about interrupts, it’s defined using the ISR() macro, which is part of the AVR GCC compiler . It passes that macro a parameter to tell it which interrupt vector to use. The result of all this is that the interrupt vector points directly at the function below, without the benefit of attachInterrupt.

Once called, the ISR sets all the bits of SEG_BUS to 1s to turn them off (active low, remember?). It then clears the two bits of MUX_BUS that we’re using by anding it with 0b11111100.

if lsd_active is true, we’re writing to the least significant (rightmost) digit, aka digit two. We set MUX_BUS to LSD_COMMON to activate the correct common anode. We then set SEG_BUS to the value of seg_decode_array[lsd], which looks up the correct bit pattern for the value stored in lsd, and displays it.

if lsd_active is false, we are writing to the most significant digit, or the leftmost digit. This is different.

In some cases, if the most significant digit is 0, we don’t want to display it. When using a dedicated driver IC for 7 segment displays (you knew they existed) this would be done with the ripple blanking pin. Here, we do it in software. The only time we want to display the leading zero is when we’ve rolled 100. In this case, MSD will equal 0xA. So we first test to see if msd is greater than zero, and if it isn’t, we don’t display anything. If it is greater than zero and it’s equal to 0xA, or 10, we set it to zero. Since we’ve already passed the greater than zero test, that leading zero will be displayed. After that, we set mux_bus to MSD_COMMON to activate the correct digit, (active low, remember?) and look up the bit pattern for msd in seg_decode_array, the same as we did for the least significant digit.

ISR(TIMER3_COMPA_vect) {//toggle the LED pin
  SEG_BUS = 0b11111111;
  MUX_BUS = MUX_BUS & 0b11111100;


  if (lsd_active) {
    MUX_BUS = MUX_BUS | LSD_COMMON;
    SEG_BUS = seg_decode_array[lsd];
  }
  else { //we must be writing to the most significant digit.
    if ((msd != 0)) { //only display msd if it’s nonzero.
      if (msd == 0xa) msd = 0;
      MUX_BUS = MUX_BUS | MSD_COMMON;
      SEG_BUS = seg_decode_array[msd];
    }
  }
  lsd_active = !lsd_active;
}

The next function is setup(). Normally a mild-mannered function where we initialize the serial console (which we don’t do here, since we’re not using it), this function is where we’ll see all the machinations we do to use the external interrupt for the selector and the counter/timer and its interrupt. It’s not that bad.

We start by setting our ports up. We set PORTA up to write on all pins. We set PORTC up to read on the top four pins and write on the bottom four pins. We don’t use the other four pins of PORTC for anything, so their setting doesn’t actually matter. PORTD is set to read, as you’d expect, and we OR the two bits where we have the buttons wired with 1s to switch on the pull-up resistors. These pins will be positive unless some low resistance connects them to the - bus. Like the roll or selector button.

void setup() {
  DDRA = 0b11111111;
  DDRC = 0b00001111;
  PORTC = 0b00001100;
  DDRD = 0b00000000;
  PORTD = PORTD | 0b00001100;

Here’s where we set up both our timer/counter interrupt and our external INT interrupt. The first thing we do is turn interrupts off, so that we don’t get interrupted while we’re messing with the interrupts and interrupt vectors. It could happen, and the results could be unpredictable. How does this work? Remember in Configuring Timer/Counters where I mentioned that there’s a global interrupt enable flag? This Arduino function sets and clears that flag.

  noInterrupts();

Next, we set TCCR3A to 0. We don’t need any of its bits set. We’re not using any compare output pin modes, and the two clock select bits it has aren’t ones we need.

  TCCR3A=0;

TCCR3B is set to 0b00001101. This means the three Clock Source bits are set to 101, which gives us the prescaler dividing the system clock by 1024. Note that we’ve turned the timer/counter on, too, not that it can do anything with interrupts turned off. It is counting, however. We also set WGM32 on. Combined with the zero values in WGM30, WGM31 (in TCCR3A), and WGM33 here, this gives our total WGM bit field a value of 0b0100, which is CTC mode 4. The timer/counter will count to the value in OCR3A and reset when it gets there. I’ve left the bit field definition comment in place for clarity.

  // ICNC3 ICESN0 - WGM33 WGM32 CS32 CS31 CS30
  TCCR3B=0b00001101;

Finally we set TCCR3C to zero. We don’t want to force any compare-matches. This isn’t really necessary.

  TCCR3C=0;

Having set our timer/counter in motion by setting it up with a clock source, we need to tell it what to count to, and what to do when it gets there, so we set OCR3A (remember, it’s a 16 bit register, so I set it with a 16 bit hex value. You don’t have to use the leading zeros, but I think it makes things clearer.) Hex 00A3 is 163. if we divide a 20MHz clock by 1024, we get about 19.5kHz. If we divide that by 163, we get a bit under 120Hz. Because a given display is only updated on alternate interrupts, this gives us about 60Hz per display which should keep flicker from happening. If you’re wondering whether this could have been done using timer 3’s own output pins instead of writing the ISR, technically yes, although ripple blanking would have been a problem, and we wouldn’t have learned how to write timer/counter ISRs.

  OCR3A = 0x00A3;

Okay, our timer is running, it’s counting from zero to 163 about 120 times a second. It’s busily setting the OCF3A flag (bit) in the TIFR3 register, but even if global interrupts were on, it wouldn’t do anything. Yet. Let’s change the timer/counter interrupt mask to let OCF3A get through. The OCF3A flag corresponds to the OCIE3A interrupt enable bit in the mask, so we set TIMSK3 to 0b00000010, and set that output compare interrupt enable bit. Once again, I’ve left the comment with the bit pattern in place for clarity.

  // - - ICIE3 - - OCIE3B OCIE3A TOIE3
  TIMSK3 = 0b00000010;

Now the timer’s hooked up. We’ve already set up its ISR, so once we turn interrupts back on, our display should switch back and forth between the two digits, so fast we can’t see them.

We do have to hook up the interrupt for the selector button too, but because it’s an external interrupt, and external interrupts are covered by the attachInterrupt mechanism in Arduino, we can set the interrupt, ISR, and mode up in one statement. SELECTOR_INTERRUPT is #defined at the beginning of the sketch, and read_selector_isr is the function we defined earlier to do the job. Note that we DON’T put the usual parens () when we name read_selector_isr. It looks wrong, but I’m sure the attachInterrupt function is creating a pointer to our function. When you do that, the parameters are in a separate pair of parens. In any case, that’s the syntax and that’s how the Arduino foundation said it should be. When the button attached to SELECTOR_INTERRUPT’s pin shorts that pin to ground, the falling edge (transition from above 0.6v to below 0.3v) will trigger the interrupt, which will jump to read_selector().

  attachInterrupt(SELECTOR_INTERRUPT, read_selector_isr, FALLING );

Finally, we turn interrupts on, and let the whole thing loose.

  interrupts(); //turn interrupts on.
}

The loop function, as you’re undoubtedly tired of hearing by now, is called over and over again by the Arduino core. A lot of the time, we’ve stopped that from happening by putting an infinite loop at the end of our code, so we can control when loop runs, and what variables are reset.

I didn’t do that here. I let loop() run over and over again. This is the other reason there are so many global variables.

The first thing we do is initialize a byte variable called display_value and set it to zero. Display_value is split into two decimal digits to make LSD and MSD later.

If rolled_since_select is true, we set display_value to roll, the last value rolled on the dice device. Otherwise we set it to die, the last die type selected.

void loop() {
  byte display_value = 0;


  if (rolled_since_select) {
    display_value = roll;
  }
  else {
    display_value = die;
  }

Next, we break display_value apart into its least and most significant decimal digits. We get the low digit by taking the modulus of display_value by 10. The high digit is set to the display value divided by ten. There is a huge assumption here. This mechanism depends on the fact that this is integer division. Anything less than zero is ignored. If you tried to mistreat real numbers this way, you’d get bizarre results. By the time we reach the bottom of this part of the sketch, either the last die selected or the last roll rolled will be set to display on the LED display. We don’t have to touch the display itself. That’s handled by the timer interrupt. It’s a little strange when you’re used to controlling when everything happens in a sketch to just say “that happens in the background.” But it does.

  lsd = display_value % 10;
  msd = display_value / 10;

The other goings on in the background, whether the user has pushed the selector button or not, may have incremented the d_select variable. We declare a quick constant array with all the possible die_values in it, and then look up the die at d_select, and put that value in die. If the user has pressed the selector button since the last time the roll button was pressed, rolled_since_select will be true. We don’t need to do anything about that right now. Just set the variables and let loop() do the additional processing next time it comes around.

  const int die_value[] = {4, 6, 8, 10, 12, 20, 100, 18};
  die = die_value[d_select];


  if (d_select > 7) d_select = 0;

Enough with the spooky background stuff and letting loop pick up changes faster than we can see them. Here’s the part of the sketch where we actually pole the roll button as fast as we can (once per cycle of loop()). As long as the roll button is held down, keep setting LSD and MSD to BAR and keep incrementing roll If roll goes higher than die, reset roll to 1. Also, keep setting rolled_since_select true. Over and over again. Don’t do anything else until the user lets up on the button. Except, of course, that the timer interrupt for the display will go on interrupting. If we were really insistent on this loop running as fast as it can and not being interrupted, we could turn interrupts off. We’re not that time sensitive here.

  if (!digitalRead(BUTTON_PIN)) {
    while (!digitalRead(BUTTON_PIN)) {
      lsd = BAR;
      msd = BAR;
      roll++;
      if (roll > die)roll = 1;
      rolled_since_select = true;
    }

Okay, we’re out of the die rolling loop. The user has let up on the button, but we’re still inside the original if statement that tested the button.

You might be wondering how we’re going to roll 3d6. Just roll from 1 to 21 and add three? No, that gives you different odds. Having generated a random number, if die is 18, and if rolled_since_select is true, we seed the clib random function with roll, and add three calls to the random() function with a low value of one and a high value of 6 together and put them in roll. Those are the correct odds.

    if (die == 18 && rolled_since_select) {
      randomSeed(roll);
      roll = (int)random(1, 6) + (int)random(1, 6) + (int)random(1, 6);
    }
  }
}

And that’s the end. The loop() program is called over and over again forever, so our newly generated roll will be displayed next time around, and of course, our timer/counter will go on interrupting loop() to update the display.

As always, the complete sketch with all the comments (and there are a lot of them) is included here.

The Complete Sketch

//Dice Device
//-----------------------------------------------------------
// This sketch generates die rolls based on timing randominity.
// Essentially, this method of random generation assumes that
// human reflexes are not accurate to less than about a
// quarter of a second, and a sufficiently fast counter that
// rolls over will generate good random values when switched
// on and off by a human pushing a button.
//
// This sketch generates the rolls for a full set of
// polyhedral dice used in various role playing games,
// including a properly implimented 3d6 roll, and displays
// them to a multiplexed pair of 7 segment displays.
//-----------------------------------------------------------


// Precompiler #defines
//-----------------------------------------------------------
#define SEG_BUS PORTA
#define MUX_BUS PORTC
// Define the port to talk to the 7 segment display
// and the one that controls multiplexing via a uln2803


#define MSD_COMMON 0x2
#define LSD_COMMON 0x1
// Define the port settings for the multiplexer.


#define DECIMAL 16
#define BAR 17
// Define special characters as integer values.


#define BUTTON_PIN 16
#define BUTTON_PIN_INTERRUPT 0
#define SELECTOR 17
#define SELECTOR_INTERRUPT 1
// Define the pin and interrupt used by the roll button and
// the selector button.


//Global Variables
//-----------------------------------------------------------
const byte seg_decode_array[] = {
  //7 segment decoding byte array.
  //A B G F D E DP C
  0b00100010, //0
  0b10111110, //1
  0b00010011, //2
  0b00010110, //3
  0b10001110, //4
  0b01000110, //5
  0b01000010, //6
  0b00111110, //7
  0b00000010, //8
  0b00001110, //9
  0b00001010, //A
  0b11000010, //B
  0b01100011, //C
  0b10010010, //D
  0b01000011, //E
  0b01001011, //F
  0b11111101, //DECIMAL
  0b11011111  //BAR
};
volatile unsigned long msecs = 0; //stores time in ISRs for debouncing.
boolean rolled_since_select = false; //Have we rolled?
//If so, this is true. Used in loop()


byte roll = 1; //value of the last roll of the dice.

volatile byte die = 4; //Die, as in d4, d8, d16, etc.
volatile byte d_select = 0; //what die is selected.
volatile byte lsd = BAR; // leftmost digit’s integer value.
volatile byte msd = BAR; // rightmost digit’s integer value.


volatile boolean lsd_active = true; //Used by timer ISR
// to store whether we’re writing to the least significant
// digit or the most significant digit.


//-----------------------------------------------------------
//read selector ISR
//
// This function is an interrupt service routine. It takes no
// parameters and returns no data.
// When the button connected to BUTTON_PIN is pressed, the
// interrupt executes this function. It checks to see if 250ms
// have elapsed since the last press (for debouncing) and if
// so, increments d_select by 1.
// d_select is actually processed in the loop() function.
//-----------------------------------------------------------
void read_selector_isr() {


  Serial.println("read_selector Fired");
  if (millis() -msecs > 250) {
    d_select = d_select + 1;
    msecs = millis();
  }
  rolled_since_select = false;
}


//-----------------------------------------------------------
// ISR Timer interrupt service routine)
//
// This function takes no parameters and returns no data.
// it selects which digit is enabled (lsb or msb) by clearing
// MUX_BUS and then setting to LSD_COMMON or MSD_COMMON, then
// sets SEG_BUS to the bit pattern for the value in LSD or
// MSD respectively.
//
// This function also impliments ripple blanking, where
// the MSD is not displayed if it contains zero /unless/
// MSD contains 0xa (10), which we only hit if we’re selecting
// or rolling percentile dice.
//
// We’re using timer 3 for the display multiplexer. ATmega328
// based Arduinos will need to use a different timer.
// This ISR is connected to the TIMER3_COMPA interrupt vector.
// This means that when the timer’s value matches the contents
// of TIMER_3_COMPA, this ISR will be called.
//-----------------------------------------------------------
ISR(TIMER3_COMPA_vect) {//toggle the LED pin
  SEG_BUS = 0b11111111;
  MUX_BUS = MUX_BUS & 0b11111100;


  if (lsd_active) { //write to lsd

    MUX_BUS = MUX_BUS | LSD_COMMON;
    SEG_BUS = seg_decode_array[lsd];
  }
  else { //we must be writing to the most significant digit.
    if ((msd != 0)) { //only display msd if it’s nonzero.
      if (msd == 0xa) msd = 0;
      // if msd is 0xa set it to zero after the zero test.
      // leading zero will show on d100 select and 100 rolls.


      MUX_BUS = MUX_BUS | MSD_COMMON;
      SEG_BUS = seg_decode_array[msd];
    }
  }
  lsd_active = !lsd_active;
}


//-----------------------------------------------------------
// setup()
//
// This function is called once by the Arduino core. It returns
// no data and takes no parameters.
// It sets up the serial console, sets the ports into the
// necessary states, and sets up the segment decoding array.
// It then turns interrupts off, sets up the timer interrupt
// and attaches the external interrupt to the
// SELECTOR_INTERRUPT interrupt and to the read_selector
// function. After that, it re-enables interrupts.
//-----------------------------------------------------------
void setup() {
  Serial.begin(115200);
  //Init the serial console.


  DDRA = 0b11111111;
  DDRC = 0b00001111;
  PORTC = 0b00001100;
  DDRD = 0b00000000;
  PORTD = PORTD | 0b00001100;
  //Set up the various ports. Note the pullup resistors
  //being set on port D.


  noInterrupts(); //turn interrupts off

  //Set up the timer/counter and timer/counter interrupt.
  //---------------------------------------------------


  TCCR3A = 0; // Clear all pin control bits and
  TCCR3B = 0; // set clock select to 0. Stops the timer.
  TCNT3 = 0;  // Zero the timer count register


  //TCCR3A:
  // COM3a1 COM3a0 COM3b1 COM3b0 - - WGM31 WGM30
  TCCR3A=0;
  // All four COM3 bits remain at zero: the port
  // operates normally. Likewise the WGM31 and 30
  // bits remain at zero. We only turn on WGM32,
  // for CTC mode 4, and that’s in TCCRB3.


  //TCCR3B
  // ICNC3 ICESN0 - WGM33 WGM32 CS32 CS31 CS30
  TCCR3B=0b00001101;
  // turn on WGM32 for CTC mode 4
  // and set the clock select bits to 101 for the
  // 1024 prescaler.


  //TCCR3C
  // FOC3A, FOC3B, no other pins are used.
  TCCR3C=0;
  // Make sure FOC3A and B are cleared. We don’t want to
  // force a compare.


  OCR3A = 0x00A3; //output compare register.
  // 20mHz/1024 (the prescaler) is about 19.5kHz.
  // Hex 00A3 is 163. 19.5kHz/163 is a bit over 120Hz.
  // Bearing in mind that each digit is refreshed every other
  // interrupt, that gives us about 60Hz per digit,
  // which is enough to avoid flicker. Persistence of vision
  // FTW.
  // Yes, this could have been done with a timer/counter
  // configuration using the OC3A and OC3B pins and no
  // ISR at all, but then we wouldn’t learn how to do
  // timer ISRs.


  //TMSK3
  // - - ICIE3 - - OCIE3B OCIE3A TOIE3
  TIMSK3 = 0b00000010;
  // Set OCIE3A - enable an interrupt when output compare
  // match A (TCNT3 = OCR3A) occurs.


  //Set up the selector interrupt.
  //---------------------------------------------------


  attachInterrupt(SELECTOR_INTERRUPT, read_selector_isr, FALLING );
  //read_selector interrupt - when the read_selector pin changes
  //from high to low, interrupt.


  interrupts(); //turn interrupts on.
}


//-----------------------------------------------------------
// loop()
//
// Called over and over forever by the Arduino core.
// Loop sets the values of the globals lsd and msd for the
// display ISR to pick up,
// processes what the read_selector ISR has set in the
// d_select global variable,
//-----------------------------------------------------------
void loop() {
  byte display_value = 0;


  if (rolled_since_select) {
    display_value = roll;
  }
  else {
    display_value = die;
  }


  lsd = display_value % 10;
  msd = display_value / 10;
  //Button is active LOW, so if the roll button is NOT pushed,
  //display either the selected die or the last roll.


  const int die_value[] = {4, 6, 8, 10, 12, 20, 100, 18};
  die = die_value[d_select];


  if (d_select > 7) d_select = 0;
// set die to whatever value was last selected by the
// select ISR with die_value[].


  if (!digitalRead(BUTTON_PIN)) {
    while (!digitalRead(BUTTON_PIN)) {
      lsd = BAR;
      msd = BAR;
      roll++;
      if (roll > die)roll = 1;
      rolled_since_select = true;
    }
// If the roll button is pressed, display bars in both digits
// and increment roll every time loop is called.Also set
// rolled_since_select to true.


    if (die == 18 && rolled_since_select) {
      randomSeed(roll);
      roll = (int)random(1, 6) + (int)random(1, 6) + (int)random(1, 6);
      //The D18 mode is really 3d6 for chargen.
      // Seed with roll and call random() 3 times
      //Cast random results as int since they are long
      // otherwise.
    }
  }
}

Credit where Credit is Due

The Dice Device drew inspiration from an electronic die roller from the early 1980s whose name I won’t mention lest I stumble over a trademark. Nevertheless, I will mention that you can read its patent, look at the schematic, and understand the thing’s marvelous simplicity here: https://www.google.com/patents/US4431189 . I may yet have to build one for my own use with the original ICs, as they’re unobtanium on the secondary market.

Understanding the timers and interrupts was something I had to do in spite of the Arduino foundation, rather than based on their documentation. I got most of it from the ATmega1284P datasheet, but I got the code working first based on several web tutorials. There are so many now that I can’t put my finger on which one exactly. Nevertheless, this is a good set of tutorials on the subject: http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/

And of course, one must credit the late E. Gary Gygax for inventing the game, whose name is also a trademark, for which the dice were used, and without whose invention my life might have been more productive, but far less fun. https://en.wikipedia.org/wiki/Gary_Gygax

The Stand-Alone version

It wouldn’t be difficult to convert this sketch and schematic to a standalone device, powered by a battery. you’d want to change how the common anodes are handled, and you might also want to use the hardware timer/counter and output pins without an interrupt to drive them. You might want to move the roll button to an interrupt, and set the fuses so that the ATmega sleeps any time it’s not interrupted. (You’d need to make sure this doesn’t turn the timer’s output pins off.) Since we don’t need many pins, you could do this with an ATmega328, the standard ATmega used for Arduino Uno and similar arduinos. There are a number of core/bootloader setups available to do just that, some of which use the internal oscillator as well. It would not be rocket science to build this device around an ATmega, where the only thing Arduino about it is software.

Likewise, it would not be difficult to copy the original device from the schematic listed in the expired patent, though if you’re contemplating selling the finished devices you’d be well advised to make sure there aren’t other patents you’d be infringing. Patent trolls are a way of life today, and the only industry I know of with more lawyers and less sense than the computer industry is the gaming industry.

The truth of the matter is, it would be far easier and likely far better received to write the dice device entirely in software for your favorite smartphone/tablet platform.

The same technique used here to drive a two-digit display could drive up to eight, although I’d seriously recommend using a UDN2981A , which is a high-side driver. If you wanted to go with more than eight digits, you could add a second such IC and use another port. At some point, it will make more sense to use something like an SPI or I^2C GPIO expander like the MCP23017 to do common anode switching. Although their serial interfaces will eventually limit your speed, you’ll probably run out of board space for the displays long before that happens. Displays also come in more than two digit packages, but if you’re looking at more than a few, consider a serial LCD panel instead. They may be more cost-effective.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.217.187