I have been working on a clock with cross-fading digits. I have stripped the sketch down to it's bare essentials and done everything I can think of to optimize the code. But I still don't get a fast enough refresh rate, there is very noticeable visible flickering of the digits as it cross-fades. I estimate my refresh rate is only around 15-20Hz.
- Rather than use the time.h library, I rolled my own really basic one. Reasoning for that is that the time.h library stores its value internally as the # of seconds, and when you query minutes, hours, etc. -- as you are constantly doing when refreshing the display -- it has to divide the # of seconds by 60, 3600, etc. My version caches the number of seconds, minutes, hours in individual bytes.
- I put (almost) all the code into the loop() routine to avoid the overhead of call stacks.
- I got rid of the timer interrupts and just let it run balls-to-the-wall, again to avoid the overhead of call stacks.
- I write directly to the ports. I grouped the bits together in order.
- I tried to simplify all my math wherever I could. No floating point, and alot of the math is bitwise, utilizing powers of 2.
- I eliminated Serial.print and all debug code.
- WITH just one Serial.print it takes about 3-4 ms per pass. At 16MHz, that works out to about 5000-6000 clock cycles per pass. I can't see where my code would take that many clock cycles!
So can anyone see where I screwed up?
Can I not see the forest for the trees? 8) Why does each pass take so long to execute? :0 How can I optimize this some more? :~
/////////////////////////////////////////////////////////////////////////////
// Digital Clock with cross-fading digits
// UnCopyright (u) 2014 Dr. Wizard
// Open Source GPL rules apply: Use at your own risk, no warranty, yada, yada
// Bugs? Comments? Suggestions? email wizard@drwiz.net
/////////////////////////////////////////////////////////////////////////////
// These 4 pins are the value of the digit
const byte digitA1 = 8;
const byte digitB2 = 9;
const byte digitC4 = 10;
const byte digitD8 = 11;
// These 3 pins are which digit
const byte digSelA1 = 4;
const byte digSelB2 = 5;
const byte digSelC4 = 6;
// This pin is the latch
const byte digClock = 7;
// Time structure, with milliseconds, and 12-hour format
struct tmt {
uint16_t tm_msec; // Milliseconds 0-999
uint8_t tm_sec; // Seconds after the minute 0-59
uint8_t tm_min; // Minutes after the hour 0-59
uint8_t tm_hour; // Hours since Midnight 0-23
uint8_t tm_mday; // Number of Days 0-255
uint8_t tm_12hour; // Hours in 12-hour format 1-12
bool tm_pm; // True for PM, After Noon 0-1 (false-true)
};
tmt displTimeF; // Time FROM
tmt displTimeT; // Time TO
struct tmt *whichTime; // Which to display
byte curDigit = 0; // Which digit is currently being refreshed
// 0 = 10ths of a second (10 LEDS in a circle, simulating a Dekatron Tube)
// 1 = Seconds % 10
// 2 = Tens of Seconds
// 3 = Minutes % 10
// 4 = Tens of Minutes
// 5 = Hours % 10
// 6 = Tens of Hours
// 7 = Colons
byte digiVal; // Value of the digit being refreshed
byte digiSel; // CurDigit shifted 4 bits, cuz upper 4 bits of port is used
long TickNo; // Used for crossfade, Milliseconds shifted by 10 bits (1024)
int pct; // Percentage for crossfade, ranges from 0-1023
int micron; // Psudo random number 0-1023
unsigned long curMillis; // Current millis()
unsigned long lastTick; // millis() on the last pass
uint16_t amt; // How many millis() since the last pass
void setup() {
//Serial.begin(9600);
// Set starting times
displTimeF.tm_msec = 200;
displTimeF.tm_sec = 56;
displTimeF.tm_min = 34;
displTimeF.tm_hour = 12;
displTimeF.tm_12hour = 12;
displTimeF.tm_pm = true;
// Note that the To: time is 600 ms ahead of the From: time
// Fades last 600 ms, with the remaing 400 ms static
displTimeT.tm_msec = 800;
displTimeT.tm_sec = 56;
displTimeT.tm_min = 34;
displTimeT.tm_hour = 12;
displTimeT.tm_12hour = 12;
displTimeT.tm_pm = true;
SetLEDpinModes();
lastTick = millis();
} // void setup()
void loop() {
if (displTimeT.tm_msec >= 800) {
// Display From: time, static
whichTime = &displTimeF;
} else if (displTimeT.tm_msec >= 600) {
// Display To: time, static
whichTime = &displTimeT;
} else {
// Fade
// Percentage of how far done we are, 0-1023 = 0.0% to 100.0%
// Rather than using floating point math, shift the bits
TickNo = (displTimeT.tm_msec + 500) << 10;
pct = TickNo/600;
// Generate psudo random number 0-1023
micron = micros() | 0x3ff;
// compare that to our "Percent" of 0-1023
if (pct > micron) {
whichTime = &displTimeT;
} else {
whichTime = &displTimeF;
}
} // if (TickNo <= 0)
// Binary IF tree is faster than Switch/Case or If/Elseif/Elseif/Elseif
if (curDigit & 0x4) {
if (curDigit & 0x2) {
if (curDigit & 0x1) {
//7
digiVal = 16; // All 4 Colon Dots On
} else {
//6
digiVal = whichTime->tm_12hour / 10;
if (digiVal == 0) digiVal = 10; // Creates a blank, only 0-9 will display
}
} else {
if (curDigit & 0x1) {
//5
digiVal = whichTime->tm_12hour % 10;
} else {
//4
digiVal = whichTime->tm_min / 10;
}
}
} else {
if (curDigit & 0x2) {
if (curDigit & 0x1) {
//3
digiVal = whichTime->tm_min % 10;
} else {
//2
digiVal = whichTime->tm_sec / 10;
}
} else {
if (curDigit & 0x1) {
//1
digiVal = whichTime->tm_sec % 10;
} else {
//0
digiVal = whichTime->tm_msec / 1000;
}
}
} // End of Binary IF tree
digiSel = curDigit << 4; // Multiply by 16 to use upper 4 bits
PORTD &= 0x7f; // Set pin 7 low again
PORTB = PORTB & 0Xf0 | digiVal; // Put the data (digit value) on pins 8-11
PORTD = PORTD & 0x0f | digiSel; // Put the address (which digit) on pins 4-6
PORTD |= 0x80; // Set pin 7 high to latch data
curDigit ++; // Increment digit counter
curDigit &= 0x7; // % 8 throw away upper bits
curMillis = millis(); // Store current value
amt = curMillis - lastTick; // Figure out how many have elapsed
//Serial.println (amt);
tick(displTimeF, amt); // Increment the From Time
tick(displTimeT, amt); // And the To Time
lastTick = curMillis; // Remember it for next pass
} void loop();
void tick(struct tmt &theTmt, uint16_t milliseconds) {
theTmt.tm_msec += milliseconds;
if (theTmt.tm_msec > 999) {
theTmt.tm_msec -= 1000;
theTmt.tm_sec += 1;
if (theTmt.tm_sec > 59) {
theTmt.tm_sec -= 60;
theTmt.tm_min += 1;
if (theTmt.tm_min > 59) {
theTmt.tm_min -= 60;
theTmt.tm_hour += 1;
if (theTmt.tm_hour > 23) {
theTmt.tm_hour -= 24;
} // hour rollover
theTmt.tm_12hour = theTmt.tm_hour;
if (theTmt.tm_12hour > 12) {
theTmt.tm_12hour -= 12;
} else if (theTmt.tm_12hour == 0) {
theTmt.tm_12hour = 12;
} // 12-hour rollover
theTmt.tm_pm = (theTmt.tm_hour > 11);
} // min rollover
} // sec rollover
} // msec rollover
} // void tick()
void SetLEDpinModes() {
pinMode(digitA1, OUTPUT); // = 8;
pinMode(digitB2, OUTPUT); // = 9;
pinMode(digitC4, OUTPUT); // = 10;
pinMode(digitD8, OUTPUT); // = 11;
pinMode(digSelA1, OUTPUT); // = 4;
pinMode(digSelB2, OUTPUT); // = 5;
pinMode(digSelC4, OUTPUT); // = 6;
pinMode(digClock, OUTPUT); // = 7
}
A note about the hardware, each digit is latched, using it's own 4543 bcd-to-7-segment chip. Thus 4 bits representing the value of a digit are placed on a bus going to all chips. Another 3 bits plus a latch signal are sent to a 74hc238 3-to-8 decoder chip which selects which 4543 chip will get the data. Similar to multiplexing but I can send the data out in bcd format instead of figuring out which segments to light, and the chip latches the value so I get 100% brightness on every digit of the display at all times. I will send you eagle schematics upon request.