I recently got exposed to Arduino programming and hardware design at the 2019 National Garden Railway Convention in Portland, OR. Geoff Bunza (Blog: https://model-railroad-hobbyist.com/blog/geoff-bunza YouTube: https://www.youtube.com/channel/UCKzeYLMEPxWGilqZTNuG0JA) held an Introduction to Arduino in Model Railroading clinic. As the Clinic Chairman, I offered to help during the presentations in any way I could. Because I’ve been programming forever (since I was a teen), and have been an electronics hobbyist, none of the topics were foreign to me. I had heard of the Arduino, but had never played with one.
This was really fun, using simple components and software to make blinkenlights and such. There are posts such as this one https://maker.pro/arduino/tutorial/how-to-easily-prototype-with-an-arduino-multi-function-shield about this shield, and other examples using libraries to control the devices.
I’m not sure why, but I couldn’t get the libraries to work. They were overly complex for something as simple as controlling a multiplexed display. I attempted to just update the display during the loop section of the sketch, but I couldn’t get the brightness of each segment to be even. Inevitably, the last segment to be updated was brightest (as it was “on” during the loop code) and the others were dim.
I looked for a more basic example using a timer that wasn’t 20,000 lines of code and obfuscation. I found this example that showed how to set up Timer 1 and provide an interrupt handler: http://www.hobbytronics.co.uk/arduino-timer-interrupts That was more like it!
I extended that code and modified my sketch display code to interface with the ISR, and got everything to work well. I built a simple software count-down timer that uses the buttons to set the time and beeps at the end.
Note: I should probably move the display code to a library or something, rather than have so many lines of code.
/* Multi Function Shield - Count-down timer example.
* Implements a timer configurable as Hours and Minutes or
* Minutes and Seconds.
*
* (C) Copyright 2019 by Russell Shilling
*/
// Buttons and Buzzer
#define BUTTON_A A1
#define BUTTON_B A2
#define BUTTON_C A3
#define PIEZO 3
// Define shift register pins used for seven segment display
#define LATCH_DIO 4
#define CLK_DIO 7
#define DATA_DIO 8
// LEDs
#define LED_A 10
#define LED_B 11
#define LED_C 12
#define LED_D 13
// Segment byte maps for numbers 0 to 9, A to F
// 0 1 2 3 4 5 6 7 8 9
// A B C D E F G H I J
// K L m N O P Q R S T
// U v w x Y z
const byte SEGMENT_MAP[] = { 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90, // 0-9
0x88,0x83,0xA7,0xA1,0x86,0x8E,0xC2,0x89,0xFB,0xE1, // A-J
0x8F,0xC7,0xFF,0xAB,0xA3,0x8C,0x23,0xAF,0x92,0x87, // K-T
0xE3,0xF3,0x63,0xFF,0x99,0xFF }; // U-Z
// Segments -a- a=0x01, b=0x02, c=0x04, d=0x08
// f b e=0x10, f=0x20, g=0x40, h=0x80
// -g-
// e c
// -d- h
// Byte maps to select digit 1 to 4
const byte SEGMENT_SELECT[] = {0xF1,0xF2,0xF4,0xF8};
String words[] = { "end", "no", "yes", "on", "go", "run", "dot", "art",
"abcd", "efgh", "ijkl", "mnop", "qrst", "uvw", "xyz" };
byte SEGMENT_BUFFER[] = {0xCF,0x3F,0xFC,0xF3};
// Timer interrupt
int timer1_counter;
int timer1_state;
// State: button, debounce, alarm
int button = 0;
unsigned int bounce = 0;
int alarm = 0;
int alarmFlag = 1;
int count = 0;
long timer = 0;
long tick = millis();
long secs = tick / 1000;
#define TIMER_MMSS 0
#define TIMER_HHMM 1
const byte timer_mode = TIMER_HHMM;
void setup ()
{
// Set Buzzer
pinMode(PIEZO,OUTPUT);
digitalWrite(PIEZO,HIGH);
// Set LEDs to outputs
pinMode(LED_A,OUTPUT);
pinMode(LED_B,OUTPUT);
pinMode(LED_C,OUTPUT);
pinMode(LED_D,OUTPUT);
// Initial LED State
digitalWrite(LED_A,HIGH);
digitalWrite(LED_B,HIGH);
digitalWrite(LED_C,HIGH);
digitalWrite(LED_D,HIGH);
// Set DIO pins to outputs
pinMode(LATCH_DIO,OUTPUT);
pinMode(CLK_DIO,OUTPUT);
pinMode(DATA_DIO,OUTPUT);
digitalWrite(LATCH_DIO,LOW);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, 0);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, 0);
digitalWrite(LATCH_DIO,HIGH);
// initialize timer1
// http://www.hobbytronics.co.uk/arduino-timer-interrupts
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
// Set timer1_counter to the correct value for our interrupt interval
timer1_counter = 65208; // preload timer 65536-16MHz/256/200Hz
//timer1_counter = 64911; // preload timer 65536-16MHz/256/100Hz
//timer1_counter = 64286; // preload timer 65536-16MHz/256/50Hz
//timer1_counter = 34286; // preload timer 65536-16MHz/256/2Hz
TCNT1 = timer1_counter; // preload timer
TCCR1B |= (1 << CS12); // 256 prescaler
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
interrupts(); // enable all interrupts
// done with init
Serial.begin(9600);
}
// Interrupt service routine
ISR(TIMER1_OVF_vect) // interrupt service routine
{
TCNT1 = timer1_counter; // preload timer
if (SEGMENT_BUFFER[timer1_state] != 0xFF)
{
digitalWrite(LATCH_DIO,LOW);
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_BUFFER[timer1_state] );
shiftOut(DATA_DIO, CLK_DIO, MSBFIRST, SEGMENT_SELECT[timer1_state] );
digitalWrite(LATCH_DIO,HIGH);
}
timer1_state = (timer1_state + 1) & 3;
}
// Main program
void loop()
{
// Debug
/*
Serial.print(button);
Serial.print(" ");
Serial.print(timer / 60);
Serial.print(":");
Serial.println(timer % 60);
*/
// Timer start
if (analogRead(BUTTON_A) < 500)
{
button = 0;
alarmFlag = 0;
}
// Set Seconds or Minutes
else if (analogRead(BUTTON_B) < 500)
{
if (bounce < 1)
{
if (timer_mode == TIMER_HHMM)
{
if ((timer % 3600) / 60 >= 59)
{
timer = (timer / 3600) * 3600;
}
else
{
timer = timer + 60;
}
}
else
{
if (timer % 60 == 59)
{
timer = 60 * (timer / 60);
}
else
{
timer += 1;
}
}
if (button == 1)
bounce = 250;
else
bounce = 500;
}
button = 1;
}
// Minutes or Hours
else if (analogRead(BUTTON_C) < 500)
{
if (bounce < 1)
{
if (timer_mode == TIMER_HHMM)
{
timer += 3600;
if (timer / 3600 > 10)
{
timer = timer % 3600;
}
}
else
{
timer += 60;
if (timer > 3600)
{
timer = 0;
}
}
if (button == 2)
bounce = 250;
else
bounce = 500;
}
button = 2;
}
else
{
bounce = 0;
}
// Display
if (timer > 0)
{
if (timer_mode == TIMER_HHMM)
WriteTimeHHMM(timer);
else
WriteTimeMMSS(timer);
}
else
{
WriteString("end");
if ((alarm == 0) && (alarmFlag == 0))
{
alarm = 100;
alarmFlag = 1;
digitalWrite(PIEZO,LOW);
}
}
// Flash the LED once per second
if (button == 0)
{
if (timer > 0)
{
long secs = millis() / 500;
if (secs & 1)
{
digitalWrite(LED_A,LOW);
}
else
{
digitalWrite(LED_A,HIGH);
}
}
}
if (tick < millis())
{
tick = millis();
if (alarm > 0)
{
alarm--;
if (alarm == 0)
digitalWrite(PIEZO,HIGH);
}
if (bounce > 0)
{
bounce--;
}
}
long s = millis() / 1000;
if (secs < s)
{
if (button == 0)
{
if (timer > 0)
{
timer --;
}
}
secs = s;
}
}
// Write a String
void WriteString(String text)
{
WriteSegment(0, (10 + text[0] - 'a'));
if (text.length() >= 2)
WriteSegment(1, (10 + text[1] - 'a'));
else
ClearSegment(1);
if (text.length() >= 3)
WriteSegment(2, (10 + text[2] - 'a'));
else
ClearSegment(2);
if (text.length() >= 4)
WriteSegment(3, (10 + text[3] - 'a'));
else
ClearSegment(3);
}
// Write a Time value
void WriteTimeMMSS(int Value)
{
int minute = Value / 60;
int second = Value % 60;
WriteSegment(3 , (second % 10));
WriteSegment(2 , (second / 10));
WriteSegment(1 , (minute % 10), 1);
if (minute >= 10)
WriteSegment(0 , (minute / 10));
else
ClearSegment(0);
}
// Write a Time value
void WriteTimeHHMM(long Value)
{
// Round up
int hour = (Value + 59) / 3600;
int minute = ((Value + 59) % 3600) / 60;
WriteSegment(3 , (minute % 10));
WriteSegment(2 , (minute / 10));
WriteSegment(1 , (hour % 10), 1);
if (hour >= 10)
WriteSegment(0 , (hour / 10));
else
ClearSegment(0);
}
// Write a Decimal number
void WriteDecNumber(int Value)
{
WriteSegment(3 , (Value % 10));
if (Value >= 10)
WriteSegment(2 , (Value / 10) % 10);
else
ClearSegment(2);
if (Value >= 100)
WriteSegment(1 , (Value / 100) % 10);
else
ClearSegment(1);
if (Value >= 1000)
WriteSegment(0 , (Value / 1000) % 10);
else
ClearSegment(0);
}
// Write a Float number
void WriteFloatNumber(float FValue, byte Decs)
{
if (Decs == 1)
{
int Value = FValue * 10;
WriteSegment(3, (Value % 10));
WriteSegment(2, (Value / 10) % 10, 1);
if (Value >= 100)
WriteSegment(1, (Value / 100) % 10);
else
ClearSegment(1);
if (Value >= 1000)
WriteSegment(0, (Value / 1000) % 10);
else
ClearSegment(0);
}
else if (Decs == 2)
{
int Value = FValue * 100;
WriteSegment(3, (Value % 10), 0);
WriteSegment(2, (Value / 10) % 10, 0);
WriteSegment(1, (Value / 100) % 10, 1);
if (Value >= 1000)
WriteSegment(0, (Value / 1000) % 10, 0);
else
ClearSegment(0);
}
else if (Decs == 3)
{
int Value = FValue * 1000;
WriteSegment(3, (Value % 10), 0);
WriteSegment(2, (Value / 10) % 10, 0);
WriteSegment(1, (Value / 100) % 10, 0);
WriteSegment(0, (Value / 1000) % 10, 1);
}
}
// Write a Hexadecimal number
void WriteHexNumber(int Value)
{
WriteSegment(3, (Value & 15));
if (Value >= 16)
WriteSegment(2, (Value >> 4) & 15);
else
ClearSegment(2);
if (Value >= 256)
WriteSegment(1, (Value >> 8) & 15);
else
ClearSegment(1);
if (Value >= 4096)
WriteSegment(0, (Value >> 12) & 15);
else
ClearSegment(0);
}
// Write a blank to one of the 4 digits of the display
void ClearSegment(byte Segment)
{
SEGMENT_BUFFER[Segment] = 0xFF;
}
// Write a character to one of the 4 digits of the display
void WriteSegment(byte Segment, byte Value)
{
SEGMENT_BUFFER[Segment] = SEGMENT_MAP[Value];
}
void WriteSegment(byte Segment, byte Value, byte Dp)
{
byte Dpv = Dp == 1 ? 0x7F : 0xFF;
SEGMENT_BUFFER[Segment] = SEGMENT_MAP[Value] & Dpv;
}