Programming: Arduino
Check out my other articles:
- Galaga: Failed Attempt at writing a Game
- Sudoku
- Blockade / Snake
- Farkle
- Minesweeper
- Blackjack
- ADC Graph
- Analogue Clock
- Tetris
Analogue Clock
Originally I intended to build a clock using a set of Nixie tubes - a cold cathode display that screams '60s and '70s.
As supply for these was limited, I searched the internet to find someone who could sell me a set. Luckily, I found two suppliers who could supply the four I needed – one in Russia and one in Australia - and placed an order from the local company. After a few days I received a message saying that they were out of stock (despite the web site indicating otherwise) and that there would be a delay in processing my order. A few weeks later, they finally responded saying that they could not source any. I quickly logged back onto the Russian site but found they had sold out in the meantime. At this point the project was shelved
Whilst browsing the Ardufruit site, I happened to see a NEOPixel ring of RGB LEDs .. mmm, interesting especially when one of the rings contained 24 LEDs which lends itself to being mapping against a clock face. I ordered one but this time it turned up with no hassles along with a Real Time Clock and a spare Arduino board. The build was back on.
Overview
The Analogue Clock uses the following arts:
- Arduino Uno (of course)
- DS1307 Real Time Clock
- Adafruit NeoPixel 24 Segment
- Rotary Encoder
Although it is easy to set the time programmatically when uploading the program to the Arduino, I wanted to be able to set the time and the alarm without having to connect to a computer but I didn't eant a myriad of switches to control these functions. This was achieved using a Rotary Encoder. By pressing the button on the rotary encoder the clock switches between four modes (show the time, set the time, set the alarm, turn the alarm on / off). Rotating the switch one way will change the hours settings - turning it the other way will adjust the minutes.
As mentioned, I wanted to build in an alarm function but I didn't have a speaker or buzzer in my parts box. As such, when the alarm is on the Neopixel flashes through a range of colours until the rotary encoder switch is pressed. It is a beautiful demonstration of what the NeoPixel can do and I wish I could claim credit for the code but it came in the same code with the NeoPixel library.
Rendering the Time
Rendering a clock on a 24 element NeoPixel presents a couple of problems that are not found on real analogue clock. In a real clock, the long minute hand is visible as it passes under the top-most hour hand whereas on the NeoPixel we can only light the one pixel resulting in a display where one of the hands is missing.
I tried a couple of different approaches to this including making the hour hand a red pixel and the minute hand a green pixel. When the two hands clashed, I attempted to render the single pixel a different colour but this was simply confusing! Another approach was to render the minute hand on every odd pixel and the hour hand on the even pixels and although this never resulted in a clash, the results looked weird - especially at times like midday when the two hands never lined up.
Finally, I settled on the hour hand being rendered as a single pixel and the minute hand being rendered as three contiguous elements. In this way, when the clock showed a time like midday the two hands would be both pointing to the 12 o'clock position and would both be visible.
Pips and the AM / PM Indicator
Rendering the time on a blank clock face makes reading the time on quite hard - especially in the the dark where the entire face may not be visible. I have included an option SHOW_PIPS that will render lightly illuminated 'pips' at the 12, 3, 6 and 9 o'clock positions.
A second setting, SHOW_PM_INDICATOR, provides an AM / PM indicator by highlighting the elements on either side of the 12 o'clock pip when the time is after midday.
Telling the Time
As detailed above, I settled on the hour hand being rendered as a single pixel and the minute hand being rendered as three contiguous elements. The examples below show approximately 10:07 AM (on the left) and 9:15 PM (on the right with the PM indicated by the display of three white / blue pixels at the 12 o'clock position).
Code Fragments
The following code highlights areas of interest in the application. It is not intended to be a tutorial or a complete solution. I have provided the complete code for download and you may use or modify it as required.
SetupLike most Arduino programs, the clock sketch is a simple state engine with the various states correlated to the modes of the clock - normal operation, setting the time, setting the alarm, activating / deactivating the alarm and an alarm ringing mode. These states are enumerated as constants:
001 002 003 004 005 006 007 008 |
class Player { public: boolean enabled; char* playerName; char* imageName; char playerNameLength; int runningScore; int grandScore; int throwNumber; int playerNumber; char die1[10]; char die2[10]; char die3[10]; char die4[10]; char die5[10]; char die6[10]; char throwNo[10]; int thisScore[10]; int totalScore[10]; boolean divider[10]; void addThrowHistory(int throwNoValue, int dValue1, int dValue2, int dValue3, int dValue4, int dValue5, int dValue6, int thisScoreValue, int totalScoreValue, boolean dividerValue) { for (int x = 8; x >= 0; x--) { die1[x + 1] = die1[x]; die2[x + 1] = die2[x]; die3[x + 1] = die3[x]; die4[x + 1] = die4[x]; die5[x + 1] = die5[x]; die6[x + 1] = die6[x]; throwNo[x + 1] = throwNo[x]; thisScore[x + 1] = thisScore[x]; totalScore[x + 1] = totalScore[x]; divider[x + 1] = divider[x]; } die1[0] = dValue1; die2[0] = dValue2; die3[0] = dValue3; die4[0] = dValue4; die5[0] = dValue5; die6[0] = dValue6; throwNo[0] = throwNoValue; thisScore[0] = thisScoreValue; totalScore[0] = totalScoreValue; divider[0] = dividerValue; } void reset(int playerNo) { runningScore = 0; grandScore = 0; throwNumber = 1; playerNumber = playerNo; for (int i=0; i<=10; i++) { die1[i] = 0; die2[i] = 0; die3[i] = 0; die4[i] = 0; die5[i] = 0; die6[i] = 0; throwNo[i] = 0; thisScore[i] = 0; totalScore[i] = 0; divider[i] = false; } } }; |
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 |
class Player { public: boolean enabled; char* playerName; char* imageName; char playerNameLength; int runningScore; int grandScore; int throwNumber; int playerNumber; char die1[10]; char die2[10]; char die3[10]; char die4[10]; char die5[10]; char die6[10]; char throwNo[10]; int thisScore[10]; int totalScore[10]; boolean divider[10]; void addThrowHistory(int throwNoValue, int dValue1, int dValue2, int dValue3, int dValue4, int dValue5, int dValue6, int thisScoreValue, int totalScoreValue, boolean dividerValue) { for (int x = 8; x >= 0; x--) { die1[x + 1] = die1[x]; die2[x + 1] = die2[x]; die3[x + 1] = die3[x]; die4[x + 1] = die4[x]; die5[x + 1] = die5[x]; die6[x + 1] = die6[x]; throwNo[x + 1] = throwNo[x]; thisScore[x + 1] = thisScore[x]; totalScore[x + 1] = totalScore[x]; divider[x + 1] = divider[x]; } die1[0] = dValue1; die2[0] = dValue2; die3[0] = dValue3; die4[0] = dValue4; die5[0] = dValue5; die6[0] = dValue6; throwNo[0] = throwNoValue; thisScore[0] = thisScoreValue; totalScore[0] = totalScoreValue; divider[0] = dividerValue; } void reset(int playerNo) { runningScore = 0; grandScore = 0; throwNumber = 1; playerNumber = playerNo; for (int i=0; i<=10; i++) { die1[i] = 0; die2[i] = 0; die3[i] = 0; die4[i] = 0; die5[i] = 0; die6[i] = 0; throwNo[i] = 0; thisScore[i] = 0; totalScore[i] = 0; divider[i] = false; } } }; |
Wrapping the logic of a player in a single class allows it to be passed to functions as a parameter, right? Wrong. This is the first thing I discovered about inner classes in Arduino - the compiler is simply not smart enough to compile the following piece of code ..
001 002 003 |
void someFunction(Player *thePlayer) { } |
The compiler spits out the following errors:
001 002 003 |
farkle:126: error: variable or field 'someFunction' declared void farkle:126: error: 'Player' was not declared in this scope farkle:126: error: 'thePlayer' was not declared in this scope |
A search of the forums reveals the following post http://forum.arduino.cc/index.php/topic,41848.0.html which seems to describe the problem.
The Arduino IDE automatically generates prototypes for your functions. Unfortunately, this doesn't work for functions whose arguments or return value are types defined in the sketch, since the prototypes get inserted above the type definitions. I'm not sure there's really a good workaround for this.
I finally gave up and simply declared four instances of my class for the four possible players and a 'spare' reference (lines 001 - 005) that I assign to the current player (line 020 in the Setup routine starts the game with referencing the first player. All of the functions in the program refer to the global variable player.
A function called setNetUser() is called at the end of a player's turn to determine who the next player is based on the number of players in the game. For example, if the current player is the second player (line 040) and there are more than two players in the game (line 041) then the current player reference is updated to the third player (line 042). If there were only two players in the game, then the current player reference is updated to the first player (lines 044 - 046).
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 |
Player *player1; Player *player2; Player *player3; Player *player4; Player *player; ... void setup() { ... player1 = new Player(); player2 = new Player(); player3 = new Player(); player4 = new Player(); ... player = player1; } ... void setNextUser() { switch (player->playerNumber) { case 1: if (numberOfPlayers > 1) { player = player2; } else { player = player1; } break; case 2: if (numberOfPlayers > 2) { player = player3; } else { player = player1; } break; case 3: if (numberOfPlayers > 3) { player = player4; } else { player = player1; } break; case 4: player = player1; break; } } |
Wiring Diagram
The diagram below show the wiring diagram when using a standard Arduino Uno (or compatible) and the SmartGPU2. Click on the images to see a detailed drawing.
Downloads
Feel free to download, modify and adapt this code to your needs. Please credit me if you use some or all of this code in your own project. If you alter the code to fix bugs or add functionality, please submit them back to me and I will add them to the download.
Arduino Source Code |