|
|
Wed. 02.09.2026 |
| |
As a prototype for a systematic component of my chess game device, where I wanted to test the input and output system of a button field and an LED grid, I soldered together this Tic-Tac-Toe game device.
With this project, I also wanted to learn how to program an AVR microcontroller without the Arduino development environment — essentially bare-metal. In particular, I hoped this approach would allow for more efficient use of the available program memory, as well as more direct and faster execution of the individual program routines.
The Arduino development environment is an excellent way for beginners to become familiar with microcontroller programming, particularly due to its ease of use. However, I no longer consider myself a beginner in electronics, so I wanted to gradually move away from Arduino and explore the technical details of microcontroller programming in greater depth.
Since the Arduino development environment provides numerous pre-programmed and highly generalized functions that are not necessarily tailored to the specific hardware or application, it seems reasonable to assume that bare-metal programming without Arduino can save resources in many cases.
I now know how to write C and C++ programs for the avr-gcc compiler and transfer them to my microcontroller via the ICSP interface using avrdude. This represents another step away from the abstracted Arduino environment and toward direct programming of AVR MCUs.
Since the 8-bit microcontroller ATmega328P used in this project has only a single processor core and therefore cannot perform multiple tasks in true parallel, the tasks are divided according to the principle of Time Division Multiple Access (TDMA). The different tasks of the program are treated as independent processes, each being allocated a time slot of approximately equal length for execution on the hardware.
Reading and validating input values as well as controlling the LED grid are tasks that the small ATmega328P has to handle on its own. Since the LED grid cannot maintain a static output, the continuous perception of the playing field is only possible by constantly switching the respective LEDs on and off.
To ensure continuous updating of the display, a hardware timer was configured to trigger an interrupt once every millisecond. Within this interrupt, a corresponding flag is set, after which the program's main loop executes the next display cycle.
As is not uncommon with low-level programming, the source code is somewhat difficult to read in places. Admittedly, this is also partly due to my own less-than-optimal structuring. Nevertheless, I would like to draw attention to the conceptual flow, which illustrates the idea of quasi-parallel processing using TDMA.
In the main loop, the two functions drawYellowPixel(column, row) and drawGreenPixel(column, row) are called to activate an individual pixel on the LED grid. The two functions are implemented separately despite their similarity, because controlling the green LEDs requires addressing a different output register than controlling the yellow LEDs.
Each of the two LED grids uses two SIPO shift registers to control the LEDs through I/O expansion. For each shift register, the microcontroller uses two outputs, CLK and DATA, to load data into the register serially. The key advantage of this approach is that regardless of the size of the grid, only four outputs are required to control the entire field. This method was specifically used to validate a comparable 8x8 LED grid for my chess game device.
An example illustrates how the LED control works. Suppose the innermost LED of the grid is to be illuminated. According to the circuit diagram, the column Q2_B_X must be set to HIGH (5V) and the row Q2_A_X must be set to LOW (0V). This creates a potential difference of 5V across the LED and its series resistor, causing the LED to illuminate. To ensure that only the desired LED lights up, the potential differences across all other LEDs in the grid must remain below their forward voltage or be 0V. This is achieved by configuring the column registers QN_B_X to output a HIGH level only at the position of the selected LED, while remaining LOW at all other positions. The same principle applies analogously to the row registers.
| | The rapid succession of register configurations for all LEDs that are supposed to illuminate on the grid creates the impression to the observer that all LEDs are lit simultaneously. Since the last bit of the 4-bit shift register is not required, a |
Due to the large number of connections coming from the keypad and the two LED grids, wiring the circuit proved to be extremely challenging. My usual approach, in which I create all connections on the back of a perfboard using thick solder traces, was not sufficient for this project. Instead, I had to create additional layers by routing wires through the circuit. This resulted in the rather chaotic appearance of the finished assembly.
The circuit is powered by four commercially available AAA batteries connected in series and installed underneath the circuit. A small silver-colored rocker switch is used to turn the power supply on and off.
The enclosure is particularly interesting. Since I built this small game device at a time when I could not use my old Renkforce RF100 3D printer due to a completely clogged extruder system, I had to come up with an improvised solution. I simply screwed the circuit onto a small piece of craft wood using four standoffs and wedged the battery compartment in between.
The back of the craft wood board is decorated with a special textured fabric, which I happened to discover while shopping at Pagro Discount.
| |