Introduction
I have been working on a calculator for my girlfriend, but unforeseen complexity has caused the project to stretch out over months rather than weeks. In place of finishing the calculator, I came up with a more simple, more useful project (birthday present) which has the potential to be profitable if the design is tweaked to be easier to manufacture. This post contains (1) a video of the finished product (2) followed by background information (3) followed by a gallery detailing each step of the build process and (4) finally the Arduino code behind the functionality.Lizz's Whiteboard Clock
Background
Each led requires a high signal to turn on. This means that I need a total of 72 separate signals to turn on each led individually. However an Arduino only has 14 digital output. If we consider using pins in combinations there are a few methods of controlling large number of signals using a small number of I/O ports.
- Multiplexing: This allows a microprocessor to control 2^n signals with n outputs. These are used heavily when building custom hardware (verilog, VHDL, etc.) but there are not large IC chips available. I would have to string multiple smaller DEMUX chips together to achieve a 7 bit DEMUX.
- Charlieplexing: This allows a microprocessor to control n(n-1) signals with n outputs. Charlieplexing relies on the fact that LED are directional i.e. they will only turn on when a voltage is applied to then in the correct direction. This means that we can have an LED hooked up to pin 1 and 2 and another LED hooked to pin 2 and 1 while turning them on or off independently.
Both of these methods allows for a single LED to be turning on at any given time. However, for a clock we need at most 2 or 3 LEDs turned on at once (hours, minutes, seconds). We can accomplish this using persistence of vision. Most video on the internet is only 30 frames per second (updates 30 times a second) because the human eye cannot perceive changes faster than that. This means that if we update the clock 30 times per second while cycling through hours, minutes and seconds, then we can make each LED appear to be on simultaneous to the other two. There are specialized chips which do this (MAX7221) for 7-segment display and LED dot-matrices.
The Build
The Code
LizzClock.h
#ifndef LIZZCLOCK_h #define LIZZCLOCK_h typedef struct LED { char high; char low; } LED; typedef struct BUTTON { long down; char set; char PIN; } BUTTON; typedef struct TIME { char hours; char minutes; char seconds; } TIME; #endif
LizzClock.ino
#include "LizzClock.h" #include <TimerOne.h> #define TRUE 1 #define FALSE 0 // Display Constants #define OFFSET 2 LED lights[72]; LED* curr_led = 0; char PAUSE = 50; char BRIGHTNESS = 0; int UPDATE_COUNT (100/PAUSE); int ON_DELAY = PAUSE; int OFF_DELAY = 0; TIME *curr_time; long start_time; BUTTON *minutes_button; BUTTON *hours_button; void setup() { /* * Initalize current time and update interrupt */ start_time = millis(); curr_time = (TIME*) malloc(sizeof(TIME)); curr_time->seconds = 0; curr_time->minutes = 0; curr_time->hours = 0; Timer1.initialize(100000); Timer1.attachInterrupt(update_time); /* * Initalize buttons */ minutes_button = (BUTTON*)malloc(sizeof(BUTTON)); minutes_button->down = 0; minutes_button->PIN = 12; pinMode(minutes_button->PIN, INPUT); hours_button = (BUTTON*)malloc(sizeof(BUTTON)); hours_button->down = 0; hours_button->PIN = 13; pinMode(hours_button->PIN, INPUT); /* * Initalize display */ set_brightness(30); /* * MINUTES/SECONDS */ init_LED( 0, 0, 1); // 0 and 1 init_LED( 2, 0, 2); // 2 and 3 init_LED( 4, 1, 2); // 4 and 5 init_LED( 6, 0, 3); // 6 and 7 init_LED( 8, 1, 3); // 8 and 9 init_LED(10, 2, 3); // 10 and 11 init_LED(12, 0, 4); // 12 and 13 init_LED(14, 1, 4); // 14 and 15 init_LED(16, 2, 4); // 16 and 17 init_LED(18, 3, 4); // 18 and 19 init_LED(20, 0, 5); // 20 and 21 init_LED(22, 1, 5); // 22 and 23 init_LED(24, 2, 5); // 24 and 25 init_LED(26, 3, 5); // 26 and 27 init_LED(28, 4, 5); // 28 and 29 init_LED(30, 0, 6); // 30 and 31 init_LED(32, 1, 6); // 32 and 33 init_LED(34, 2, 6); // 34 and 35 init_LED(36, 3, 6); // 36 and 37 init_LED(38, 4, 6); // 38 and 39 init_LED(40, 5, 6); // 40 and 41 init_LED(42, 0, 7); // 42 and 43 init_LED(44, 1, 7); // 44 and 45 init_LED(46, 2, 7); // 46 and 47 init_LED(48, 3, 7); // 48 and 49 init_LED(50, 4, 7); // 50 and 51 init_LED(52, 5, 7); // 52 and 53 init_LED(54, 6, 7); // 54 and 55 init_LED(56, 0, 8); // 56 and 57 init_LED(58, 1, 8); // 58 and 59 /* * HOURS */ init_LED(60, 2, 8); // 12 and 1 init_LED(62, 3, 8); // 2 and 3 init_LED(64, 4, 8); // 4 and 5 init_LED(66, 5, 8); // 6 and 7 init_LED(68, 6, 8); // 8 and 9 init_LED(70, 7, 8); // 10 and 11 } /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * [[ DISPLAY INIT START ]] */ char set_brightness(int new_brightness) { if (new_brightness >= 0 && new_brightness <= PAUSE) { BRIGHTNESS = new_brightness; ON_DELAY = (PAUSE > BRIGHTNESS ? PAUSE - BRIGHTNESS : 0); OFF_DELAY = (PAUSE < BRIGHTNESS ? PAUSE : BRIGHTNESS); return TRUE; } return FALSE; } void init_LED(char index, char high, char low) { lights[index+1].high = high; lights[index+1].low = low; lights[index].high = low; lights[index].low = high; } // [[ DISPLAY INIT END ]] ////////////////////////////////////////////////////////// /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * [[ EXECUTION START ]] */ void loop() { // update_time called on timed interrupt 10 times a second display_time(curr_time); check_user_set_time(); } // [[ EXECUTION END ]] ////////////////////////////////////////////////////////// /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * [[ MAINTAIN DISPLAY START ]] */ LED* get_hour(char hour) { if (hour <= 0 || hour >= 12) return &lights[60]; return &lights[hour + 60]; } void all_off() { for(int i = 2; i < 12; i++) { pinMode(i, INPUT); digitalWrite(i, LOW); } } char get_pin(char index) { return index + OFFSET; } void display_time(TIME *time) { lightup(&lights[time->seconds]); lightup(&lights[time->minutes]); lightup(get_hour(time->hours)); } void lightup(LED* led) { // blank display and pause to control brightness all_off(); delayMicroseconds(OFF_DELAY); //delayMicroseconds(); // turn on display for short time to allow for persistance of vision. pinMode(get_pin(led->high), OUTPUT); digitalWrite(get_pin(led->high), HIGH); pinMode(get_pin(led->low), OUTPUT); digitalWrite(get_pin(led->low), LOW); curr_led = led; delayMicroseconds(ON_DELAY); } // [[ MAINTAIN DISPLAY END ]] ////////////////////////////////////////////////////////// /*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ * [[ MAINTAIN CLOCK START ]] */ void update_time() { long temp = (millis() - start_time) / 1000.0; //long temp = (millis() - start_time) / 3; curr_time->seconds = temp % 60; temp /= 60.0; curr_time->minutes = temp % 60; curr_time->hours = temp / 60.0; if (curr_time->hours == 12) { curr_time->hours = 0; // assume time is updated more than once a second. start_time = millis() - (curr_time->seconds * 1000); //start_time = millis() - (curr_time->seconds * 3); } } void check_user_set_time() { if (digitalRead(minutes_button->PIN)) { // if button down if (minutes_button->down) { // if currently debouncing if (!minutes_button->set && millis() - minutes_button->down > 20) { // if debounced long enough start_time -= 60000; // seconds * milliseconds minutes_button->set = 1; } } else { // start debouncing minutes_button->down = millis() ^ 1; } } else if (minutes_button->down) { minutes_button->down = 0; minutes_button->set = 0; } if (digitalRead(hours_button->PIN)) { // if button down if (hours_button->down) { // if currently debouncing if (!hours_button->set && millis() - hours_button->down > 20) { // if debounced long enough start_time -= 3600000; // minutes * seconds * milliseconds hours_button->set = 1; } } else { // start debouncing hours_button->down = millis() ^ 1; } } else if (hours_button->down) { hours_button->down = 0; hours_button->set = 0; } } // [[ MAINTAIN CLOCK END ]] //////////////////////////////////////////////////////////
No comments:
Post a Comment