I have a mysterious tendency to add electronics to bow ties. Back in high school I made a light-up bow tie with 10 LEDs that flashed in a variety of exciting patterns. I made that bow tie for prom and I ended up winning the male "best dressed" award despite my plain, rented suit.
1 year after graduating university I had the urge to create a follow-up for this project and refresh some micro controller knowledge at the same time. The old project ran off an Arduino Uno. Due to the size of the Arduino and other components you needed to have a sizable project box strapped to your abdomen along with a discrete button and colourful cable which -I discovered during some security pat-downs- strongly resembles a bomb. I knew I could make the new design much more compact, and less explosive-like. After a little brainstorming I had the PERFECT, nerdiest idea: A bow tie with a built in video game.
Project Summary (The Fun Stuff):If you're just interested in seeing the final product, this section is for you! The rest of the project blog will go into depth about the design process, learnings, and challenges I had creating this project.
Demo video:
Here are some glamour shots:
I had several objectives for the project:
- To write the project in C using minimal libraries to refresh some C and micro controller skills
- Run the project on a CR2032 "coin cell" battery
- Have all of the components contained in the bow tie enclosure.
The main hardware decisions I had to make were what micro controller to use and what display to use.
I chose to use an ATtiny85 for this project because the vast amount of resources available, and it's simplicity. One of the goals of this project was to refresh some low-level micro controller knowledge and the ATtiny85 is a small micro controller that I could easily experiment with on a low level. The ATtiny85 is also low power and has enough IO and processing power for the simple game I intended to implement (either breakout or pong).
For the display I ended up purchasing the 64x48 Sparkfun Mini OLED Breakout and the 128x64 Adafruit OLED Display which are both based on the same driver chip: the SSD1306 OLED driver. I chose these displays because the low power, small size, and pleasant appearance of OLED displays. I bought both displays because I was concerned that the Sparkfun display would be too small. In the end I did choose to use the 64x48 display from Sparkfun. The 64x48 resolution is the perfect size to implement a simple version of breakout on, the blue colour of the pixels on the Sparkfun display looks fantastic, and the breakout board for the Adafruit display was too large to reasonably fit in the center of a bow tie. The Adafruit display did come in handy while developing the software because it has the same OLED driver chip, and can accept 5 volts meaning that I could leave it connected to the micro controller while using my AVR programmer to upload and test new code.
Hardware Design/ConstructionThe enclosure of the bow tie went through several different iterations before I decided on the final design. I wanted to show off the ATtiny85 and also have it removable so it could be easily re-programmed. I also needed easy access to the battery.
I was originally going to use a small SOIC package ATtiny85 sold as the Sparkfun LilyTiny. I thought up several ways of making this little board removable from the bow tie (SD card slot, magnets, etc) but ultimately I decided to obey the KISS rule and use a DIP package ATtiny85 and add an accompanying socket on one of the bow tie petals.
I used a real bow tie to decide what size I wanted the enclosure to be, and made a template by hand that I could trace on both wood pieces. Using the measurements of the components and the router bit I was going to use I also traced out the inner part of the bow tie which would be hollow. I took care to leave enough material to keep the strength of the wood, and oriented each bow tie piece 90 degrees to each other with respect to the wood grain. The reason that I oriented the wood grain in different directions on both pieces was because the thin wood boards I used were slightly curved due to their thickness and natural warping. Orienting the grain in different directions would ensure strength when the two pieces were sandwiched together and prevent the bow tie from warping.
After tracing the design it was router time! (Exciting!!) I routed out the hollow portions before cutting out the perimeter so I could reliably clamp the wood down while routing. The front of the bow tie houses all of the electronics so I routed a few layers into this portion to fit all the components and wires. I used a jig saw to cut out the basic shape of the bow tie. The jig saw wasn't really the best tool for the job considering the small size of the pieces and thickness of the wood. I had to pay close attention to the order of my cuts so the blade didn't cause too much vibration and damage the wood. I did end up destroying one of the pieces and re-tracing part of the tie. If I did this again I would use a scroll saw, laser cutter, or other tool better suited for cutting out the small shape.
I bolted both halves together and used a rasp and a sanding block to finalize the shape of the tie and make the halves fit together flush. I carefully measured and planned the layout for the front of the bow tie. I traced the holes for the toggle switch, rocker switch, DIP socket and screen which would all be on the front of the tie. I cut the holes out using a drill to take out some material and used a file to shape the details. This worked well for every part except the recessed hole for the toggle switch. Admittedly I just used a drill bit to take out enough material until the toggle switch would fit straight and could be glued in. It looks poor from the inside but it's straight, solid and looks good from the front so I'm happy.
To finish the tie I sanded it with several varieties of sandpaper, finishing with 320 grit. I lightly sanded the edges with a sanding block so they had slight bevel instead of a sharp edge. I chose to apply boiled linseed oil to the bow tie as a finish. Linseed oil brings out the natural grain in the wood and provides mild protection without appearing glossy or artificial. I applied three coats of linseed oil over about 36 hours using the following procedure:
1. Thoroughly clean the wood to remove sawdust, hairs, etc
2. Apply linseed oil generously with a rag
3. Wipe off excess oil after 10 minutes
4. Let the piece dry for about 12-24 hours
5. Repeat steps 1-4, Lightly sanding after previous coat is cured if desired
NOTE: Linseed oil soaked rags can self-ignite if not disposed of properly!
I bought a piece of leather and a bow tie hardware kit to make the strap. I used some existing ties I have to gauge the range of the strap length, and glued the strap together with the hardware. I attached a magnetic clasp to the strap which mates with a clasp on the back of the bow tie.
The final part of this build was to install the electronics into the case! Luckily I had planned out the case fairly thoroughly so this wasn't too challenging except for a slight thickness issue I experienced with the rocker switch. This was solved by carefully taking some material from the back of the bow tie over top of the rocker switch. I soldered all the parts together and tested the tie before shrinking down all the heat shrink. I also cut some holes in the back half of the bow tie to fit in the magnetic clasp.
And with that the tie was done!
Software DesignI primarily work with hardware description languages (HDL) such as VHDL in my career. HDLs are significantly different than regular programming languages as you're writing a description for a physical circuit instead of writing sequential code to run on a processor. One of the reasons I did this project was to refresh some of my rusty C/micro controller skills. The code for this project was written in stages as I did some studying so FAIR WARING: It's not the most readable and isn't designed in the best way. I also think that it could be re-written to save program space on the micro controller. I have included the link to the repository with the code but use it at your own risk. Now that we have that out of the way, lets jump in.
First lets talk tools. This whole project was developed using version control (Git). Most of my version control experience is with Subversion (SVN) and I really didn't have much experience with a distributed version control system such as Git. I learned a lot about using Git and pretended as if I was working on the project with multiple developers (by always working on a branch, checking Git status often and making sure my local project was up to date after taking long breaks from the code). When I started the project I was using Notepad++ for writing code, and using a makefile in the command line to compile the code (AVR-GCC). After I realized how painful this was I decided to look into an IDE I could use. I stumbled across Atmel Studio which is meant for AVR development and has a ton of useful features. Some of the features I found most useful were: code completion, the Attiny85 simulator, the disassembly view (to help understand how the compiler was interpreting my code) and the "External Tools" feature. The external tools feature allowed me to add an "Arduino ISP" button to the tools menu so I could upload code to the micro controller with one click right in Atmel Studio (As opposed to using the command line).
Due to my constraints (No arduino, minimal libraries) I needed to become familiar with the various peripherals that the Attiny85 has available. The peripherals that the main code makes use of are counter 0, counter 1 and the analog to digital converter (ADC). USI_TWI_Master.c is a library I decided to use for communication and implements I2C communication using the universal serial interface (USI) in the Attiny85. Interrupts were not used except for testing.
The main structure of the code is an infinite loop with a state machine. The state machine has four states: IDLE, PLAYING, GAME_OVER, and TEST.
IDLE is a default state where the game is reset and the user can choose to start playing or enter demo mode. When user input is detected (Using the ADC) the state will change from IDLE to PLAYING. If no user input is detected for about 10 seconds then the state will change from IDLE to PLAYING with the "demo_mode" flag set. I used timer 1 in the micro controller to count approximately 10s after the device is powered. I chose to use timer 1 because this timer has a clock pre-scaler with many options that allows you to divide the clock and count longer periods of time.
Each time the game starts the ball and paddle begin in a pseudo-random X position. To seed the random value I used the ADC to read the raw temperature value of from the temperature sensor. To generate the random numbers I used a simple 32-bit randomization algorithm, XORSHIFT32. Each time the game enters the IDLE state the randomization algorithm will run to provide a new random position.
PLAYING is a "timed event loop" where a series of tasks are given a certain period of time to run. The sum of the maximum run-time for each task must be less than the selected "time slice" of the event loop. A timed event loop was a good solution for this project because it's simple, and allows the micro controller to perform several tasks (getting user input, moving the ball, detecting collisions) concurrently. I decided on a time slice of ~100ms for the event loop because my constraining task was getting user input. I chose 100ms because it is considerably shorter than human reaction time to a visual stimulus (aka the ball moving). I found several stats on the average human reaction time and also tested my own using an online tool. Most of the stats reported something north of 200ms and my reaction time was certainly above this. Timer 0 was used to count out the 100ms tick for the time slice. Timer 0 was used in Clear Timer on Compare match (CTC) mode which auto-resets the timer value on each tick. The event loop runs through several tasks:
- Calculate the ball's next position
- Check if the ball has hit a boundary
- Check for user input (or calculate next paddle position in demo mode) and move (redraw) the paddle if applicable
- Check for brick collisions or paddle collisions depending on the ball's vertical position. Remove bricks if necessary.
- Re-draw the ball
An important factor in deciding what tasks include was the fact that I originally meant to use the larger 128x64 display. This is important because I thought I would not have enough RAM to buffer the whole game screen (the Attiny85 has only 512 bytes of RAM). I designed the whole program to re-draw the screen in sections (brick, paddle, ball) as needed instead of buffering the whole screen and writing the entire frame. After switching to the 64x48 display I decided to stay with this scheme.
I made sure that the 100ms time slice was sufficient by doing a quick timing analysis on each of the tasks with major loops, display communication, or user input. I measured communication time by attaching a cheap logic analyzer and measuring how long a test program would take to finish communicating with the display. I estimated the code run-times of major chunks of code by viewing the dis-assembled code on the micro controller and calculating the number of clock cycles it would take up in the worst case. All timing calculations were made at worst case and were a rough estimate (rounding up). My timing estimates showed that 100ms was enough time for all tasks to run in the worst case scenario.
The demo mode AI uses a simple algorithm so the bow tie can play against itself. Each time the ball hits a brick or the roof, the micro controller will calculate where the ball is headed based on it's trajectory (factoring in wall bounces). The paddle always works its way towards the ball target. If the ball hits the paddle, the MCU sets its target position to the middle of the screen which is what the algorithm considers to be the best position if it doesn't know where the ball is going to end up.
GAME_OVER becomes the next state whenever the current state is PLAYING and the ball either hits the bottom of the screen (loss) or the number of bricks remaining reaches 0 (win). Depending on the "outcome" variable the GAME_OVER state will either display the win screen or the lose screen. The graphics for these screens include text so instead of implementing a font system I stored the bytes needed to draw these screens in the program memory of the MCU using the <avr/pgmspace.h> library. The function "pgm_read_byte" can be used to read bytes from the program memory. I decided to store the win screens, sprites, and default brick pattern in program memory to preserve RAM.
TESTING is a state that I only used for debugging/testing while developing the software. When "#define TESTING" is uncommented at the top of the file, the state machine will immediately go into the testing state after the tie is powered. Currently TESTING is the only state which uses the "print8BitNum" function which prints an 8 bit number on the screen. I wrote this function for debugging purposes.
Collision detection, graphics tasks and most of the communication is encapsulated in functions such as drawBall, checkCollision, etc which I won't go into detail about.
The EndThanks for reading about my breakout bow tie project! I had a lot of fun creating this project and I certainly was able to refresh some micro controller knowledge as well as learning some new things. I have thought of way more ways to improve upon this project than I have time for so perhaps in the future I will re-visit it again. But for now catch me cruising around with the worlds first fashion forward personal entertainment accessory.
Comments