Skip to content

Instantly share code, notes, and snippets.

@snipsnipsnip
Last active August 24, 2019 15:38
Show Gist options
  • Save snipsnipsnip/168284 to your computer and use it in GitHub Desktop.
Save snipsnipsnip/168284 to your computer and use it in GitHub Desktop.
// Meeting Cost Clock
// 12 Aug 2009 Keijiro Takahashi
/* standard definition */
#include <iostream>
namespace Arduino
{
typedef unsigned char uint8_t;
typedef signed char int8_t;
typedef unsigned int uint32_t;
typedef signed int int32_t;
typedef signed int int16_t;
typedef bool boolean;
using namespace std;
namespace Pin
{
const bool LOW = false;
const bool HIGH = true;
const bool INPUT = false;
const bool OUTPUT = true;
typedef uint8_t Pin;
typedef bool Output;
typedef bool IO;
void pinMode(Pin pin, IO bit)
{
cout << "pinMode(" << (int)pin << ", " << (bit ? "OUT" : "IN") << ")" << endl;
}
void digitalWrite(Pin pin, Output bit)
{
cout << "digitalWrite(" << (int)pin << ", " << bit << ")" << endl;
}
uint8_t digitalRead(int8_t pin)
{
cout << "digitalRead(" << (int)pin << ")" << endl;
return 0;
}
}
namespace Clock
{
void delay(double duration)
{
cout << "delay(" << duration << ")" << endl;
}
uint32_t millis()
{
cout << "millis()" << endl;
return 0;
}
}
namespace MsTimer2
{
typedef void (*TimerCallback)();
void set(uint8_t interval, TimerCallback callback)
{
cout << "set(" << interval << "," << showbase << hex << callback << ")" << dec << endl;
}
void start()
{
cout << "start()" << endl;
}
}
}
using namespace Arduino;
using namespace Pin;
using namespace Clock;
using namespace MsTimer2;
// Configuration class - You can modify this.
namespace Config
{
// Average hourly wage of your company
const int kHourlyWage = 5000;
// Default number of attendee of meetings
const int kDefaultAttendee = 5;
}
// Used to generate a bit pattern with a given character.
// This is also used for character animation.
namespace LEDChar
{
// * LED bit assignment
// 7 6 5 4 3 2 1 0
// digit 1 - F A B P C G D E
// digit 2 - B A G F E D C P
// digit 3 - F A G B P C D E
// Spacial characters
const uint8_t kCharBlank = 10;
const uint8_t kCharUscore = 11;
// Attribute flgas
const uint8_t kAttrDP = 0x10; // Decimal point
const uint8_t kAttrBlink = 0x20;
uint8_t flasher_; // Uninitialized variable but no problem
uint8_t getBits(int8_t pos, uint8_t ch)
{
// Blinking
if ((ch & kAttrBlink) && (flasher_ & 1)) return 0;
// Character
const uint8_t charPatterns[3][16] =
{
{0xeb, 0x28, 0x67, 0x6e, 0xac, 0xce, 0xcf, 0xe8, 0xef, 0xee, 0, 2},
{0xde, 0x82, 0xec, 0xe6, 0xb2, 0x76, 0x7e, 0xd2, 0xfe, 0xf6, 0, 4},
{0xd7, 0x14, 0x73, 0x76, 0xb4, 0xe6, 0xe7, 0xd4, 0xf7, 0xf6, 0, 2}
};
uint8_t bits = charPatterns[pos][ch & 0xf];
// Decimal point
if (ch & kAttrDP)
{
const uint8_t dpPatterns[3] = {0x10, 0x01, 0x08};
bits |= dpPatterns[pos];
}
return bits;
}
void onTick()
{
flasher_++;
}
}
// Used to controls the display via digital pins.
// This converts numerical values into digit characters.
namespace Display
{
const int8_t kCathodePin = 2; // 1st cathode pin
const int8_t kAnodePin = 10; // 1st anode pin
uint8_t digits_[3]; // Digit buffer
int8_t refresher_; // Refresh counter
void initialize()
{
digits_[0] = digits_[1] = digits_[2] = 0;
refresher_ = 0;
for (int8_t i = 0; i < 8; ++i) digitalWrite(kCathodePin + i, LOW);
for (int8_t i = 0; i < 3; ++i) digitalWrite(kAnodePin + i, HIGH);
}
// Set the digits individually
void setDigits(uint8_t digit1, uint8_t digit2, uint8_t digit3)
{
digits_[0] = digit1;
digits_[1] = digit2;
digits_[2] = digit3;
}
// Show a numerical value on the display
void showNumber(int16_t num, uint8_t attr = 0)
{
if (num < 10)
{
setDigits(attr | (num % 10),
LEDChar::kCharBlank,
LEDChar::kCharBlank);
}
else if (num < 100)
{
setDigits(attr | (num % 10),
attr | (num % 100) / 10,
LEDChar::kCharBlank);
}
else if (num < 1000)
{
setDigits(attr | (num % 10),
attr | (num % 100 / 10),
attr | (num % 1000 / 100));
}
else
{
uint8_t ch = 9 | LEDChar::kAttrBlink;
setDigits(ch, ch, ch);
}
}
// Show an amount of money on the display
void showBill(int32_t num)
{
if (num < 100L)
{
setDigits(num % 10,
num % 100 / 10,
LEDChar::kCharUscore);
}
else if (num < 10L * 10000)
{
int16_t temp = num / 100;
setDigits(temp % 10,
temp % 100 / 10,
(temp % 1000 / 100) | LEDChar::kAttrDP);
}
else if (num < 100L * 10000)
{
int16_t temp = num / 1000;
setDigits(temp % 10,
(temp % 100 / 10) | LEDChar::kAttrDP,
temp % 1000 / 100);
}
else if (num < 1000L * 10000)
{
int16_t temp = num / 10000;
setDigits(temp % 10,
temp % 100 / 10,
temp % 1000 / 100);
}
else
{
uint8_t ch = 9 | LEDChar::kAttrBlink;
setDigits(ch, ch, ch);
}
}
void onRefresh()
{
// turn off the current digit
pinMode(kAnodePin + refresher_, INPUT);
// advance the refresher
refresher_ = (refresher_ == 2) ? 0 : refresher_ + 1;
// generate a bit pattern for the current digit
uint8_t pattern = LEDChar::getBits(refresher_, digits_[refresher_]);
// switch the LEDs
for (uint8_t i = 0, bit = 1; i < 8; ++i, bit <<= 1)
{
pinMode(kCathodePin + i, (pattern & bit) ? OUTPUT : INPUT);
}
// turn on the current digit
pinMode(kAnodePin + refresher_, OUTPUT);
// minimum wait
delay(1);
}
}
// Used to sum up the cost of meeting.
namespace CostCounter
{
int8_t numAttendee_; // Number of attendee of the meeting
// The variables below are stored as fixed-point value (8-bit fractional)
int32_t fpCostPerSec_; // Cost per second
int32_t fpTotalCost_; // Total cost
void recalcCostPerSec();
void reset();
void initialize()
{
numAttendee_ = Config::kDefaultAttendee;
recalcCostPerSec();
reset();
}
void reset()
{
fpTotalCost_ = 0;
}
void recalcCostPerSec()
{
fpCostPerSec_ = 0x100L * Config::kHourlyWage * numAttendee_ / (60 * 60);
}
int32_t getTotalCost()
{
return fpTotalCost_ >> 8;
}
void incAttendee()
{
numAttendee_++;
recalcCostPerSec();
}
void decAttendee()
{
if (numAttendee_ > 0)
{
numAttendee_--;
recalcCostPerSec();
}
}
void advanceSecond()
{
fpTotalCost_ += fpCostPerSec_;
}
}
// Used to handle timer interrupts and make heartbeat.
// Interruption period is set to 0.1 sec for blink animation.
namespace Timer
{
const int8_t kClockDivider = 10;
const int8_t kHeartbeatPin = 13;
boolean running_;
int8_t prescaler_;
void onTick();
void initialize()
{
running_ = false;
prescaler_ = 0;
// Prepare the heartbeat
pinMode(kHeartbeatPin, OUTPUT);
// Boot up the timer
MsTimer2::set(1000 / kClockDivider, onTick);
MsTimer2::start();
}
void start()
{
running_ = true;
// The first heartbeat
digitalWrite(kHeartbeatPin, HIGH);
}
void stop()
{
running_ = false;
// Cancel prescaling
prescaler_ = 0;
}
void onTick()
{
LEDChar::onTick();
if (prescaler_ == kClockDivider - 1)
{
// One second elapsed
CostCounter::advanceSecond();
digitalWrite(kHeartbeatPin, HIGH);
prescaler_ = 0;
}
else
{
digitalWrite(kHeartbeatPin, LOW);
if (running_) prescaler_++;
}
}
}
// Used to handle start/stop button events.
namespace StartStopHandler
{
const int8_t kButtonPin = 16;
const int32_t kHoldTime = 1500; // To detect holding-downs
int32_t timeButtonDown_; // Time when the button was pressed down
boolean running_; // True while the counter is running
void initialize()
{
timeButtonDown_ = 0;
running_ = false;
digitalWrite(kButtonPin, HIGH);
}
void update()
{
if (digitalRead(kButtonPin) == LOW)
{
if (timeButtonDown_ == 0)
{
// Start pressing
timeButtonDown_ = millis();
}
else if (millis() - timeButtonDown_ >= kHoldTime)
{
// Held down; stop and reset
if (running_)
{
Timer::stop();
running_ = false;
}
CostCounter::reset();
}
}
else if (timeButtonDown_ > 0)
{
// Switch the timer if short pressing
if (millis() - timeButtonDown_ < kHoldTime)
{
if (running_) { Timer::stop(); } else { Timer::start(); }
running_ = !running_;
}
timeButtonDown_ = 0;
}
}
}
// Used to handle inc/dec button events.
namespace IncDecHandler
{
const int8_t kDecButtonPin = 14;
const int8_t kIncButtonPin = 15;
const int32_t kDisplayDuration = 1500; // Duration of attendee display
boolean prevDecButton_; // Previous state of buttons
boolean prevIncButton_;
int32_t timeDisplay_; // Time when attendee display begins
void initialize()
{
prevDecButton_ = false;
prevIncButton_ = false;
timeDisplay_ = 0;
digitalWrite(kDecButtonPin, HIGH);
digitalWrite(kIncButtonPin, HIGH);
}
boolean isAttendeeDisplayActive()
{
return timeDisplay_ > 0 && millis() - timeDisplay_ < kDisplayDuration;
}
void update()
{
bool decButton = (digitalRead(kDecButtonPin) == LOW);
bool incButton = (digitalRead(kIncButtonPin) == LOW);
if (decButton && !prevDecButton_)
{
// increment
CostCounter::incAttendee();
timeDisplay_ = millis();
}
else if (incButton && !prevIncButton_)
{
// decrement
CostCounter::decAttendee();
timeDisplay_ = millis();
}
if (timeDisplay_ > 0 && millis() - timeDisplay_ >= kDisplayDuration)
{
// Terminate attendee display
timeDisplay_ = 0;
}
prevDecButton_ = decButton;
prevIncButton_ = incButton;
}
}
void setup()
{
Display::initialize();
CostCounter::initialize();
Timer::initialize();
StartStopHandler::initialize();
IncDecHandler::initialize();
}
void loop()
{
StartStopHandler::update();
IncDecHandler::update();
if (IncDecHandler::isAttendeeDisplayActive())
{
Display::showNumber(CostCounter::numAttendee_, LEDChar::kAttrBlink);
}
else
{
Display::showBill(CostCounter::getTotalCost());
}
Display::onRefresh();
}
int main()
{
setup();
for (;;) { loop(); }
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment