A Maze Running Game for PIC16F18313 Microcontrollers

1

Overview

    The point of the maze game is to find a path out of the maze. David's version used 4 LEDs to display the player's immediate surroundings and provided 4 buttons to select which direction to move in. I decided to make my version a tiny bit more immersive. I more than doubled the screen resolution and added colour in the process by using nine WS2812 LEDs, and changed to a joystick for input. This actually reduced the number of pins needed, compared to David's charlieplexed masterpiece. The PIC16F18313 chip I used is also an 8 pin device, like the original. The joystick push button functions as a reset button at present but reprogramming the PIC's configuration can change that to a general purpose input. One pin remains free for adding either a speaker like David's original or a vibration motor as used in a cell phone. I haven't decided which to use yet. (For those keeping count, the 6th I/O pin seems to be lost in the process of configuring the chip's sub-systems to generate the unusual serial data stream required by the WS2812s. See below for more.)

    My other change is to the firmware. Using smart LEDs for the display and joysticks for input on an entirely different type of MCU obviously required new firmware but I also implemented view rotation and multiple mazes. Thus, the joystick allows movement forward and backward and turns to the left or right. Coloured pixels, all 9 of them, also allow for a richer view of the world. I chose blue for the walls, a yellow-gold colour for the player and green for the exit of the maze. Furthermore, the player flashes red upon collisions with the walls.

 

2

The Hardware

    From the start, I knew I wanted joystick input and a larger LED matrix output. The typical game console thumb joystick and my supply of surface mount WS2812 LEDs dictated that my version would not be built on bread board. Instead, I whipped up a pair of single sided circuit boards and milled them out. The main one is used copper-side down to hold the joystick, the MCU and various connectors. The daughter board is used copper-side up for the surface mount LEDs. There are a few SMDs on the bottom of the main board, and some through-hole caps on the bottom of the daughter board because that turned out to be the easiest way to assemble it all.

    Figure 1: Rough Circuit Diagram. Right and Up on the joystick generate higher analog voltages.

    The circuit is simple enough, although carefully choosing the order of the LEDs on the circuit board simplifies the view rotation algorithm. For my prototype board, I ordered the LEDs as follows: (counting from 0, because, well, C)
     7 0 1
     6 8 2
     5 4 3
    If I make more, I will move the center pixel to the start of the chain. This will allow updates to the player pixel without refreshing the entire string.

    One look at my circuit boards will show the unusual layout I like to use when milling boards. Typical PCB milling uses isolation milling - the engraving bit cuts around the perimeter of each trace, isolating it from the rest of the copper. Long ago I decided that method was unnecessarily complex and could be simplified to just cutting once between each copper trace. The result is like a Voronoi diagram of a circuit board. An added benefit of this is maximum pad sizes. Pads without plated through holes are all too easy to break off circuit boards so making them as large as possible really helps with strength.

    Figure 2: Milling the main circuit board.

    Figure 3: The finished LED daughter board.

    Figure 4: Top view of finished assembly. A 3 pin header carrying 5V, ground and serial data joins the two boards.

    Figure 5: Bottom view. A standard micro-usb break-out board makes for a nice power jack.

    Figure 6: The cover plate. A piece of 3/16" thick piece of milky white acrylic makes for a fantastic diffuser.

    Figure 7: Who's ready to find the exit? Lego is great for creating levels.

 

3

The Software

    Software is the magic that brings the hardware to life. It reads the input signals, applies them to data, and generates the output. The maze data, which includes a bit-map representation of the maze, the width and height and the starting and exit coordinates, is stored in Flash. User input changes the player position and direction. This information is used to generate the output. Let's examine some of the details that make this possible.

    Figure 8: Maze data + player location + player direction + exit location = display output.

    For the above figure, the player is at coordinate location (1,6) and is facing west. The maze exit is at location (0,6) which results in the screen shown.

    Perhaps the first critical task is driving the RGB LEDs. Unlike ordinary LEDs, WS2812 LEDs require a specific serial data stream in order to work. The benefit of this is that a single serial output pin can control a large number of LEDs individually. The difficulty lies in the nature of this serial stream. Numerous web sites describe the details of the serial protocol and the importance of pulse lengths and timing but the short description is that short pulses are zeros and long pulses are ones. Pulses of around 350nS (that's nanoseconds) are interpreted as zeros and longer pulses around 700nS are ones. These pulses should be sent at about 800kHz. Eight pulses are one byte and three bytes form a data packet for a single LED. A given LED will take the first three bytes it receives for itself and rebroadcast any more bytes received to the next LED in the serial chain. After a period of about 50 microseconds with no data pulses, an LED will latch its newly received data to the actual LED output, with each of the three bytes setting the brightness of the three LED elements. The LEDs are somewhat tolerant of timing errors but if the pulse lengths get too out-of-spec, the LEDs will display unexpected colours or nothing at all. No standard serial peripheral generates this specific serial protocol.

    Figure 9: WS2812 Serial LED Data Format.

    A fast micro controller can generate this pulse train easily enough with some careful programming, but many of the advanced mid-range PIC chips have an interesting hardware peripheral that makes this task easy. This module is called a Configerable Logic Cell or CLC, which allows various signals to be combined in useful ways at a hardware level. This makes it possible to modify an ordinary SPI signal to generate the required pulse train. This is done by using the SPI data signal to select one of two pulses. If the data bit is low, a short pulse (which happens to be generated by a PWM module synced to the SPI clock) is selected. If the data bit is high, a longer pulse is used, which happens to match the SPI clock pulse. Microchip has an application note, AN1606, with details. Other web pages, such as this Geek Magazine article do a better job of explaining exactly how the CLC is used to generate the signals.

    Once the peripherals are configured and turned on, sending data to the string of LEDs is as simple as:
     while(!SSP1STATbits.BF); // wait for previous TX to finish
     SSP1BUF = databyte; // send next byte
    The hardware sends the byte with no further attention from the user's program. Send three bytes for each LED in the chain and finish with no serial data for at least 50 microseconds and you're done.

    The next key programming task is to figure out what to actually display on the massive 9 pixel display screen. By treating the maze as monochrome pixel image, a simple routine can read 'pixels':
     // Return cell at specified coordinate
     // If out of bounds, return 1 (wall)
     // If coordinate matches level exit location, return 2
     // map_width, world_width and world_height are global vars holding map dimensions
     // map_ptr points to the map data in (flash) memory
     unsigned char Read_Cell(char x, char y)
     {
       unsigned char result;

       // Do some bounds checking
       if (x<0) return 1;
       if (x>=map_width) return 1;
       if (y<0) return 1;
       if (y>=world_height) return 1;

       result = 0;
       if ((*(map_ptr + (x>>3) + y*world_width) & bit_mask[x & 7])) result = 1;
       if ((x == goal_x) && (y == goal_y)) result |= 2;
       return result;
     }
    Mazes are stored most efficiently if they are a multiple of 8 pixels wide. The height in contrast, can be any value. A nice trick in the above routine is the first few lines that do bounds checking. This gives us the outer perimeter wall of the maze for free. It does not need to be stored in the maze data itself.

    Since there are 8 possible wall locations around the central player position, these 'pixels' fit perfectly into a single byte. This leads nicely to the algorithm for rotating the view as the player turns: simply rotate this byte according to direction the player is facing, then shift the bits out to match the order of the LEDs. So we define the direction the player is facing according to the following:
     7 0 1
     6 * 2
     5 4 3
    which matches the order of the pixels in the hardware. If the player is facing, say, west, direction 6, the byte containing the local view is rotated 6 bits right. These bits are then shifted out one by one and converted to 3 bytes of data for each LED. I chose to also briefly display the view at 45 degrees during a turn. The image is a bit nonsensical but it helps convey the visual sense of rotation.

    The rest of the program is pretty straight forward. On start-up, the hardware and data structures are initialized. The starting position is displayed on screen. The joystick is read to determine player movement and the display is updated to match. If the player coordinates match the exit coordinates, a simple animation is displayed, the next maze is started and play continues. At this point, when the final exit is reached, the animation is displayed more slowly and the game starts over from the very beginning.

    So what's up with the 'missing' pin on the chip? Since manually configuring all the control registers to get the various peripherals set up correctly can be a tedious and error prone process, Microchip provides a GUI based configuration utility to do all the hard work for you. The result is a set of source and header files that are included at compile time. However, this utility insists on reserving one pin for the SPI clock signal, even though there is absolutely no need for this signal external to the chip. The documentation for the chip suggests that this pin could be used as an input pin to override the clock output but I have been unable to generate the required set-up configuration using the configuration tool. If I can figure out how, the clock signal could be re-routed to one of the pins used for input, freeing up the lost pin for another purpose. Manual configuration may be in my future.

 

4

Usage and Limitations

    Turn it on and play. It ain't rocket science. A group of ten-year-olds had no problem figuring out how the game works. Curiously, a number of adults did have difficulties with the fact that the view rotates. They would have prefered the player to move according to the joystick movement and always face the same way. Also, my father, who is colour-blind, had difficulty identifying some of the clours, although I'm sure if he tried for a while, he could figure it out.

    As the program currently stands, the flash memory of the PIC16F18313 chip is getting close to full, so more features are difficult to add. There is room for a few more mazes but that is about it. Rewriting in assembly language would probably shrink the code size significantly as Microchip's XC8 c-compiler in free mode is not particularly good. Ah well, you get what you pay for.

    I can see a few changes and additions if I make a second version. Most significantly, I would likely use a larger chip, not for the extra pins, but for the increased flash memory. The PIC16F18313 has 2K words of program memory. A PIC16F18326, for example, is a 14 pin device with 16K words of program memory. That much extra program space would allow for a lot more features. Possibilities include many more levels, a random level generator, saving progress and a bread-crumb mode that displays where the player has been. Sound and vibration are on my list too. I can also see two extra games that would fit the hardware: Tic-Tac-Toe (Naughts and Crosses for some) and Simon. Send me your comments to motivate me to do more!

 

5

The Source Code

    At the moment, source code is not published. Send me a message if you really want to see something. The HEX file for the 16F18313 is provided here if you have built the hardware according to the circuit diagram above and want to try it out.

 

    Send any comments, concerns, questions or anything else to
    vegipete at this domain dot net.

 

Last Update: March 23, 2018

Copyright Strong Edge Dynamics, May 2018.