Finally! I've finally completed my first microcontroller-based device...
The kind of material meant to be the bread-and-butter of this blog.
The kind of material meant to be the bread-and-butter of this blog.
―Hi. This is present (at the time of publishing), disgruntled me. I started writing this article very early in the project (including the lines above. I really counted my chickens before they hatched)... Years ago. You'll soon know why it took so long. Anyway, I'll add my more recent comments in italics, for clarity.
I'm not counting my unoriginal ventures, such as building an Arduino Uno clone from electronic components I sourced myself, assembling kits from Adafruit Industries, and installing software on a Raspberry Pi board. One could argue it's still tinkering, but I want to create something unique... albeit of questionable utility. I've started several projects, but they are a tad too ambitious. There is no end in sight, each of them comes with a long bucket list of skills to acquire. Even this latest idea of mine, which looked simple and stupid, turned out to be less so. ―That's quite an understatement, boy.
What is it? A dice roller. I've named it "edice", for "electronic dice". An object with the look and feel of a pocket calculator, which could be replaced, for all intents and purposes, by a one-liner smartphone application. Hence the "simple and stupid" qualifiers. I'd like to think that I will use the gizmo when I play tabletop role playing games (TTRPGs) in the distant future. To this end, I'll need more than the traditional six-sided dice. At least four-sided, ten-sided and twenty-sided ones too. It would be neat to let the user pick the number of sides, to ensure that our dice roller is compatible with any game. Simultaneous rolls is also a nice-to-have!
My first idea was to reproduce the slick 3D rolling dice available in the web application roll20.net—They look so good! It's exciting to watch each die bump around the screen, almost stop on a given side, then roll a bit more and—phew!—you narrowly escaped a fumble. I quickly gave up though. Before facing the complexity of the physics involved, the rendering and the animation (which, now that I think about it, would definitely be WAY too much to handle for a poor Atmel AVR, and call for an expensive hi-res screen), I ran into a much more basic issue. How do you number the sides of a D20? I googled it and found an interesting research paper claiming that most dice are numbered in a way that does NOT make them perfectly fair. Which is annoying. A loaded die is not a good die. So why go to great lengths to make a model of a flawed device? And how do you represent a die with an arbitrary number of sides anyway? Let's just display a number to the user. We can still mimic the bouncing by recalculating the result a couple of times before the roll is done.
Version 1.0: Mad Max
As much as possible, I incorporate reclaimed materials and salvaged, patched-up parts to give my final product the genuine post-apocalyptic look we all love. My device (v1.0) is almost completely made up of freebies: a liquid crystal display (10 pin monochrome LCD, 16×2 characters) probably ripped off a dumpstered printer, a keypad from a landline telephone, a Pro Trinket board that Adrafruit graciously added to one of my orders... I'm happy that after stashing these parts for years, they finally served a purpose. And I managed to cobble something without totally destroying them in the process! Hurray! ―Don't rub it in. You eventually destroyed every frakking thing.
![]() |
The Monstrosity. |
I've started with the keypad, in retrospect the hairiest step.
—I'm not saying anything.
It was my first time working with a matrix keypad. I knew very little, but with Google searches and Adafruit tutorials, anything is possible! The easiest part was the software. Enter the Keypad library. You simply provide the list of pins connected to your keypad and it does all the work. But unlike an off-the-shelf product, the PCB of my repurposed keyboard did not flaunt any easy-to-use contact pads or pins. I needed to figure out where to connect the wires on the PCB, and how many pins were required. I've used an alkaline battery and an LED to identify the circuits. It's not an easy thing to do with the naked eye, because this PCB has several layers. Signal traces also go through holes (called "vias"), zigzagging across the two sides of the board. A pen and a piece of paper came in handy!
—I'm not saying anything.
It was my first time working with a matrix keypad. I knew very little, but with Google searches and Adafruit tutorials, anything is possible! The easiest part was the software. Enter the Keypad library. You simply provide the list of pins connected to your keypad and it does all the work. But unlike an off-the-shelf product, the PCB of my repurposed keyboard did not flaunt any easy-to-use contact pads or pins. I needed to figure out where to connect the wires on the PCB, and how many pins were required. I've used an alkaline battery and an LED to identify the circuits. It's not an easy thing to do with the naked eye, because this PCB has several layers. Signal traces also go through holes (called "vias"), zigzagging across the two sides of the board. A pen and a piece of paper came in handy!
—What a hellish job. Not only it took a lot of time and effort to map all the circuits, but when time came to solder wires, I realized that the conductive areas for the buttons were made of an alcohol-soluble material. As I was washing away the flux, I was also eroding them. And no way to fix it with solder, it just would not stick. The longer I tend to work on a piece, the higher the chance that I will break something. Then it's a downward spiral of "fixes" leading to more breaking, and before you know it, your PCB is an ugly mass of patched-up traces, mismatched wires and hot glue. Surprisingly, it worked... I don't recall for how long, but it worked. I think I remember some glitches though, e.g. some keys inputting the wrong digit. It could have had something to do with the abysmal quality of the rig.
![]() |
Even worse. |
Unfortunately, I ran into a roadblock with the LCD. I had managed to display text on the screen with Arduino's LiquidCrystal library. But it did not work long. Some interference in the system randomly garbled the characters:
![]() |
I think he wants to communicate. |
I read that thicker cables might reduce the electronic "noise" on the line. I also sprinkled capacitors on my circuits in the hope of filtering out the noise. No dice (pun intended). And as usual, the longer I tinker with something, the more I damage it. I ended up breaking off the LCD's pins. :(
―I'm glad I took the picture above before I totaled it! :)
Version 2.0: To Live and Let Die
I abandoned the first version and started again from scratch.
Lesson learned: using unreliable parts makes a project more difficult to troubleshoot. Does it not work because of the code, faulty connections/wires, or the parts themselves? If you don't have a spare and cannot trust that a part is in working order, then you cannot know where the issue is. Especially when a problem occurs once everything is soldered into place.
I was tired of dealing with defective parts, so I got brand new ones. Screw the "post-apo" look, let's shoot for reliability, durability, ease of build and repair. I have even ordered spares, and I did well, because I broke some while trying to fit them on the perfboard. It's always sad to ruin a part, but less so when it doesn't jeopardize your entire project.
I meant to order the same LCD, but I realized later that the new one was significantly different: sending data through dedicaded pins (parallel mode) was no longer an option. According to its datasheet, the new LCD (poetically named ERC1602-4) was to be used with either two serial protocols: 4-Wire SPI or I2C (a.k.a. IIC). I opted for I2C because it only requires 2 wires to send and receive data, versus 4 for SPI.
This was my first encounter with I2C, so figuring things out proved quite challenging. On the microcontroller side, two pins (A4 and A5) are used by a library called "Wire" to do low-level I2C communication with any I2C device. Low-level means you can only send bytes. Each device (such as an LCD) relies on additional protocols built on top of I2C, so that its particular functionalities (e.g. "print a character", "move the cursor", "clear the screen") can be invoked from the microcontroller. There are also libraries for this extra layer, however I failed to make them work with my LCD. Somehow, all that I could find online was about serial LCDs bundled with an I2C module (called a "backpack") which does serial-to-I2C. The libraries were meant for the IC in this backpack, which left me confused. Finally, I chanced upon ERC1602-4-i2c, a library written by the Spaniard Paul Staron specifically for the model of LCD I have. But my luck also ended there: it was meant to be run with mbed, not the Arduino IDE. I've tried mbino, a library claiming to (partially) emulate the mbed environment for Arduino. That did not work. Finally, after weeks of frustration, out of options, I observed that ERC1602-4-i2c was actually pretty small, so I decided to adapt it myself for Arduino. I basically copy/paste'd the code into my project, replaced the mbed low-level calls with Wire equivalent ones, and... it just worked!!! Miracles do happen. O Joy. Paul, you're a real lifesaver, I'm eternally grateful. The last resort would have been to figure out the Wire calls myself, and considering how much I trudged through simpler tasks, I prefer not to think about it. But it can be done: in the ERC1602-4 datasheet, fish out the reference ("ST7032") of the chip-on-glass (COG) controller, then look up the ST7032 datasheet. The protocols are detailed there. Good luck.
![]() |
Not a time machine. |
![]() |
Not a bomb. |
As I was debugging my software, some buttons of the keypad stopped working (I mean—the key presses did not register). I tested the keypad with another Arduino board: it worked fine. The problem could come from the wiring between the keypad and the Pro Trinket, or the Pro Trinket itself. I cleaned it well, thinking its pins might be dirty or corroded. But it did not help. Finally, I swapped the Pro Trinket board for a brand new one: all the buttons worked again. My conclusion: I've fried some of the pins of the original board. My ignorance of what happened portends that replacing it could just be a waste... The mishap could happen again. What to do?! Damn e-dice! You were the Chosen One! You were supposed to be an easy project, not a nightmare! You were supposed to introduce me to electronics, not put me off it!
For a moment, I thought I understood the reason why the board was damaged: maybe I forgot to add resistors in series with the keypad pins? But according to the official page, it's not necessary: "You won't need external resistors or diodes because the library uses the internal pullup resistors and additonally ensures that all unused column pins are high-impedance." Nope, I had no idea.
If I couldn't get something so basic to work, there was no hope. I really thought of giving up at that point. The project, the hobby... There was a lot at stake. I had nothing to lose by keeping the new board in place. Assuming that there was nothing wrong with my circuit, that the former Pro Trinket had died of cancer after being mistreated for 2 years. Hoping for the best.
If I couldn't get something so basic to work, there was no hope. I really thought of giving up at that point. The project, the hobby... There was a lot at stake. I had nothing to lose by keeping the new board in place. Assuming that there was nothing wrong with my circuit, that the former Pro Trinket had died of cancer after being mistreated for 2 years. Hoping for the best.
―Second miracle: no repeat of this issue. We're on a roll! Let me know if you're tired of dice puns.
Version 2.1 To Beep or Not to Beep
I had a secondary objective: to reproduce phone dial sounds. The dual-tone multi-frequency signaling (DTMF) or "Touch-Tone" encodes each key with a pair of frequencies played together. It serves no purpose in my project, it's just for fun. By the way, fun fact: one of the first phones I've ever used was a rotary dial telephone, the technology predating push-button telephones. I don't miss it.
Playing one pure tone is easy as pie. The Arduino programming language provides the function tone() to generate a square wave of an arbitrary frequency. You just need to connect the output pin to a piezo buzzer (don't forget to add a resistor in series to avoid frying your board) to hear the tone. But playing two tones simultaneously is another story. It's so complicated that a libary was written to just do that: ToneLibrary. Unfortunately, it appears to conflict with the library I use to generate random numbers, Entropy.
There are 3 timers on an Arduino-Uno-compatible board such as the Pro Trinket. Timer 0 is used by the board for low-level functionalities. Entropy relies on Timer 1. If you're curious, it makes use of the natural jitter of the watchdog timer (WDT) to produce a reliable stream of true random numbers. ToneLibrary needs one timer per tone: Timer 1 and Timer 2. You can see why the two libraries don't play nice with each other. When ToneLibrary runs, Entropy glitches. However, it seems that only the first couple of rolls are impacted (they always yield a 1). I think that at that point, I do not care anymore. I've just signaled it as a known bug in the source code and moved on.
―Maybe I should try a more recent version of ToneLibrary (renamed Tone for some reason), or add a chip dedicated to sound generation. But really, no... I've already spent too much time on this side quest.
Version 2.2 Johnny Mnemonic
A late addition: the possibility to store settings (i.e. the number of bounces) in the non-volatile memory EEPROM. Arduino makes it relatively painless with the EEPROM Library. Neat! I also added a test routine to check the distribution of the randomly generated numbers.
![]() |
Shortly before the final assembly. |
Lastly, I've connected one 4×AA battery holder to the board and built a case with reclaimed plywood. In hindsight, I should have made it slightly bigger. It's awful when everything's ready but the guts don't fit in the case. A tight fit can make the assembly unnecessarily burdensome. Until the last minute, I didn't know whether it would be a success or an utter failure. I kept breaking parts. By the way! Don't solder all your components (especially the screen) directly to the main board. It makes repairs very difficult, and it's mechanically doomed to failure. Anyway. It's done. I won't vouch for the longevity of my dice roller, but at least I can shoot a video to demonstrate that it works:
As a finishing touch, I shaved five seconds off the boot time by disabling the bootloader. To do so, you must mess with a separate permanent memory called the fuses. Flip the bit "BOOTRST", and voila! The board loads your program directly, instead of waiting a little to allow you to upload something. But you cannot "burn" the fuses like you send your code, over USB. It's a more involved process: you need a programmer. There are many variants out there, and the simplest approach is to jury-rig one with a spare Arduino board. Conveniently, Ladyada provides a sketch to do just that. She did all the work; I only had to change the value of the "high byte" fuse, which least significant bit is BOOTRST. Another way to prevent the bootloader from running is to overwrite it, but I haven't tried that yet.
![]() |
My development board acting as ISP for the Pro Trinket. |
Final Thoughts
What matters is not the finished product, which I don't need. It's all about learning by doing (especially by making mistakes). The journey, not the destination...
―Ha! I almost forgot. You can get the source code here: https://github.com/The-Freetinker/edice