# No Frills LED Clock - 16F54 controller - Flicker free

#### takao21203

Joined Apr 28, 2012
3,700
I need a LED clock so I used a PCB I made some years ago.
Soldering the TSSOP IC on the small pads isnt comfortable.

Added a watch crystal and 6pF capacitors.

There is no schematic but a table how the display is wired to the controller.
Most of the work here is done in software with two decoding tables so how the display is wired is secondary.
Such clocks have been done before- in assembler, and requiring to wire the display to 8bit port straight.
Of course in such a case, IO is easy and fast. But its bound to a particular kind display then.

Ive rewitten the firmware completely.

Now I use 4 copies of the 7segment decoding table, for each multiplex phase (the data is same but sink bits change),
and for two IO ports. Its about the limit what the 16F54 can contain!

Second topic that bothered me was the flickering its almost unbearable at 32 KHz when C language is used.
But the 7segment data is only updated once a minute! Table reads with offset take quite a long time.
While using a pointer dereference in C is much faster.

So Im buffering the data for each digit in the small RAM. It works out well.

Heres the complete source code, that is, a push button query should be added to set the time, or its just a timer, not a clock.
It has a blinking dot too!

C:
#include  <xc.h> // 32 KHZ LEd 7 segment clock
#pragma config OSC = LP  WDT = OFF CP = OFF // configuration 16F54

const unsigned char dig_PORTA[] @0x01a ={0b1111,0b0011,0b1011,0b1011,\
0b0111,0b1110,0b1110,0b1011,\
0b1111,0b1111,0b1111,0b0110,\
0b1110,0b0011,0b1010,0b1010,\
0b1101,0b0001,0b1001,0b1001,\
0b0101,0b1100,0b1100,0b1001,\
0b1101,0b1101,0b1101,0b0100,\
0b1100,0b0001,0b1000,0b1000,\
0b1111,0b0011,0b1011,0b1011,\
0b0111,0b1110,0b1110,0b1011,\
0b1111,0b1111,0b1111,0b0110,\
0b1110,0b0011,0b1010,0b1010,\
0b1111,0b0011,0b1011,0b1011,\
0b0111,0b1110,0b1110,0b1011,\
0b1111,0b1111,0b1111,0b0110,\
0b1110,0b0011,0b1010,0b1010}; // segment and sink driving table for PORTA

const unsigned char dig_PORTB[] @0x60 ={0b11010110,0b11010000,0b11100110,0b11110100,\
0b11110000,0b11110100,0b11110110,0b11110000,\
0b11110110,0b11110100,0b11110010,0b11100110,\
0b11000110,0b11010110,0b11110110,0b11100110,\
0b11010111,0b11010001,0b11100111,0b11110101,\
0b11110001,0b11110101,0b11110111,0b11110001,\
0b11110111,0b11110101,0b11110011,0b11100111,\
0b11000111,0b11010111,0b11110111,0b11100111,\
0b01010111,0b01010001,0b01100111,0b01110101,\
0b01110001,0b01110101,0b01110111,0b01110001,\
0b01110111,0b01110101,0b01110011,0b01100111,\
0b01000111,0b01010111,0b01110111,0b01100111,\
0b10010111,0b10010001,0b10100111,0b10110101,\
0b10110001,0b10110101,0b10110111,0b10110001,\
0b10110111,0b10110101,0b10110011,0b10100111,\
0b10000111,0b10010111,0b10110111,0b10100111}; // segment and sink driving table for PORTB

unsigned char* tptr;
unsigned char v_phase,v_digphase,hrh,hrl,minh,minl,secs;
unsigned char ddata[8];

void update() // precompute the display data, load from ROM table
{
*(tptr)=dig_PORTA[hrh];
*(tptr+1)=dig_PORTB[hrh];
*(tptr+2)=dig_PORTA[hrl+0x10];
*(tptr+3)=dig_PORTB[hrl+0x10];
*(tptr+4)=dig_PORTA[minh+0x20];
*(tptr+5)=dig_PORTB[minh+0x20];
*(tptr+6)=dig_PORTA[minl+0x30];
*(tptr+7)=dig_PORTB[minl+0x30];
}

void display() // update the multiplex display
{
TRISA=0xff;TRISB=0xff;
PORTA=*(tptr+v_digphase);
PORTB=*(tptr+1+v_digphase);
TRISA=0;TRISB=0;
v_phase+=0x10;v_phase&=0x30;
v_digphase+=2;v_digphase&=0x07;
}

void main(void)
{
v_phase=0;v_digphase=0; //initialization
secs=0;minl=0;minh=0;hrl=0;hrh=0;
TRISB=0;TRISA=0;OPTION=0x07;
tptr=&ddata[0];
update(); // display 00.00

while(1)
{
if(TMR0&0x20) // clock logic, quite simple
{
TMR0&=0x1f; secs++;
*(tptr+3)^=0x8; // blink rhe LED
}else if(secs==60)
{
secs=0;minl++;if(minl==10)
{
minl=0;minh++;if(minh==6)
{
minh=0;
hrl++;if(hrl==10){hrh++;hrl=0;}
if(hrl==4)if(hrh==2){hrh=0;hrl=0;}
}
}
update();
}
display();
}
}
The display used is a very common one. At first I did run into the trap to enter the bits backwards, had to enter them again.
Using a table like this you can use any IO bits, though, if you use 3 or 4 ports you need as many tables.
When you share the ports you need extra masking tables as well.

#### ScottWang

Joined Aug 23, 2012
6,993
The project post on Completed Projects Forum, should be easy to let our members to follow and duplicate, if miss something like hardware or software and some completed clear photos then it is not a completed project, please attach the schematic and some completed clear photos, otherwise we don't know exactly what to do from your table, when your complement is completed and our Mods team will discuss and approve then you can post the new completed project to the Completed Projects Forum including all stuffs the forum should have.

#### takao21203

Joined Apr 28, 2012
3,700
I think the project is not suitable for people who never used embedded C. Schematic is pointless as you could connect it in any way then do in software.

I have added a pushbutton today, however.

There are so many led displays then need different schematic or do you wish a BOM with ordering numbers?

Missed software, need link for downloads the compiler, IDE and datasheet right?
I've seen programming books full of these frills but something essential is missing.

So you wish a novice guidance including all you need. I can do schematic as there are some new small parts. Should I do BOM

#### ScottWang

Joined Aug 23, 2012
6,993
I think the project is not suitable for people who never used embedded C. Schematic is pointless as you could connect it in any way then do in software.

I have added a pushbutton today, however.

There are so many led displays then need different schematic or do you wish a BOM with ordering numbers?
If you could provide the BOM is better, and attach a *.hex.
You just post the 7-segs leds you used in schematic and BOM, if you want then you can suggest some 7-segs leds suit for your project.

Missed software, need link for downloads the compiler, IDE and datasheet right?
I've seen programming books full of these frills but something essential is missing.

So you wish a novice guidance including all you need. I can do schematic as there are some new small parts. Should I do BOM
If you providing some more infos then the members who can write the program then they can modify the program by themselves, this uC project already has its limits for some members to duplicate, that is not your fault, you just provide that you should provide, you don't have to worry about the compiler and IDE environments, that's their problem, if you attach the *.hex then the members need to solve the problem as how to write the hex code into uC.

A explanation as how to run the project and the functions of keys should be included.

#### dannyf

Joined Sep 13, 2015
2,197
Some suggestions - feel free to discard:

Code:
#pragma config OSC = LP  WDT = OFF CP = OFF // configuration 16F54
You want to specify all fuse settings, even if you don't use them for this application. It helps to establish what fuse settings you are using and to change them in the future.

Code:
const unsigned char dig_PORTA[] @0x01a ={0b1111,0b0011,0b1011,0b1011,\
Try to document what the array is for and how you arrive at those values.

If you have to use allocation it to a particular address, try to use the macros if possible.

Code:
unsigned char* tptr;
unsigned char v_phase,v_digphase,hrh,hrl,minh,minl,secs;
unsigned char ddata[8];
Try to comment on those variables, as well as your code, so people know what they are for

Code:
void update() // precompute the display data, load from ROM table
...
void display() // update the multiplex display
When you write those pieces, always ask yourself how those pieces can be reused in a future project, where the hardware may be different, there may be other devices on the ports, and the wiring may be different. The purpose of writing any code, in my view, is so that you don't have to write them in the future.

Code:
void main(void)
Try to use int main(void) if possible.

Also, if your piece of code utilizes a peripheral, explicitly initialize it - it improves the code's re-usability and helps you a year from now undestand how the code works.

Code:
  if(TMR0&0x20) // clock logic, quite simple
Try to see if you could put it in an interrupt so it runs all by itself in the background.

Hope it helps.

#### takao21203

Joined Apr 28, 2012
3,700
Thanks for your requests.

I can outline how to get the bits table for just any display.

The 16f54 is no frills mcu, no interrupt, it only has very few configuration bits. Basically the oscillator selection, then wdt and code protection which I don't use.

The absolute allocation is required because the banking of this mcu. Only the lower half can be used for const ROM.

I'll add the setting key, draw schematic, hex file, and give more comments as well general outline how to derive the io tables.

Actually I've done random io with 16f884 before, because ports are shared need to use masking tables as well. It's almost as much info to fill a chapter in a book in this case.

So I'm using the 16f54 because it's so easy to work with, be it, the ram is painfully small, table reads are slow and costly, and it has banking for the program memory

I can really improve performance of my c script, better say I have to

#### takao21203

Joined Apr 28, 2012
3,700
Ive changed the code since there was only 80 words left, a little narrow to add time setting function.

Its 163 words free, 68% used.
The decoding table is omitted.

Ive really concluded most 7segment programs only have a small table, sometimes 10 values.
The IO assignment is done inside the code.
So the code is not reuseable.

Theres multiplexing also needs to hardcode the required bits into code.

But in the end, a microcontroller just has IO ports. This is the interface to the outside.
A 7segment LED display has pins which are connected to internal LEDs. This is the interface.
Then you want numbers to show on the display. This can be transribed into a certain bit pattern on the 7segment displays external interface.

My new approach is to put the patterns for all IO ports and all possible multiplexing phases into tables.
It does simplify the code down to a few lines, as well this code is not dependent on some display or controller.

Code:
unsigned char* tptr;
unsigned char v_phase,v_digphase,secs,i,i2;
unsigned char ddata[8];
unsigned char timet[4];
#define hrh 0
#define hrl 1
#define minh 2
#define minl 3

void update() // precompute the display data, load from ROM table
{
for(i=0;i<4;i++)
{
i2=timet[i]+(i<<4);
*(tptr+i+i)=dig_PORTA[i2];
*(tptr+1+i+i)=dig_PORTB[i2];
}
}

void display() // update the multiplex display
{
TRISA=0xff;TRISB=0xff;
PORTA=*(tptr+v_digphase);
PORTB=*(tptr+1+v_digphase);
TRISA=0;TRISB=0;
v_phase+=0x10;v_phase&=0x30;
v_digphase+=2;v_digphase&=0x07;
}

void main(void)
{
v_phase=0;v_digphase=0; //initialization
secs=0;
timet[minl]=0;timet[minh]=0;timet[hrl]=0;timet[hrh]=0;
TRISB=0;TRISA=0;OPTION=0x07;
tptr=&ddata[0];
update(); // display 00.00

while(1)
{
if(TMR0&0x20) // clock logic, quite simple
{
TMR0&=0x1f; secs++;
*(tptr+3)^=0x8; // blink rhe LED
}else if(secs==60)
{
secs=0;
if(++timet[minl]==10)
{
timet[minl]=0;if(++timet[minh]==6)
{
timet[minh]=0;
if(++timet[hrl]==10){timet[hrh]+=1;timet[hrl]=0;}
if(timet[hrl]==4)if(timet[hrh]==2){timet[hrh]=0;timet[hrl]=0;}
}
}
update();
}
display();
}
}
I understand the code needs better explanation, but I make use of ROM table, pointer and array.
I can not explain these techniques from ground zero.

So the time is now kept in an array. The individual statements were too costly in terms of code size.

#### ScottWang

Joined Aug 23, 2012
6,993
How is the hex code?

#### takao21203

Joined Apr 28, 2012
3,700
I need to add pushbutton query or its just a timer.
Need to do more documentation.

HEX code is only good for 3461AS though its a common kind.

#### ScottWang

Joined Aug 23, 2012
6,993
This is your project, so don't worry, just take your time as your schedule, I'm not pushing you, just mention something you should know.

#### dannyf

Joined Sep 13, 2015
2,197
Its 163 words free, 68% used.
The chip has 500+ words in flash space. so 70% used or 350 words are a lot.

I think I can write a more modular / re-usable, more readable, more user-configurable piece of code that's otherwise equivalent function-wise within 350 words

#### takao21203

Joined Apr 28, 2012
3,700
The chip has 500+ words in flash space. so 70% used or 350 words are a lot.

I think I can write a more modular / re-usable, more readable, more user-configurable piece of code that's otherwise equivalent function-wise within 350 words
The chip has 500+ words in flash space. so 70% used or 350 words are a lot.

I think I can write a more modular / re-usable, more readable, more user-configurable piece of code that's otherwise equivalent function-wise within 350 words
Simply put I don't think so at all.

Assembler is not allowed.
The io is allowed random
The crystal is 32 kHz means 8000 cycles per second.

Can you give just some idea how to?
Don't come up with a small table and assigning all the sinks to the 4 bit port. There are reasons why the sinks are distributed across ports. For instance reduce brightness variation. So the io bits could be totally random.

There's little space for additional variables.

Without ram buffering the table read is so slow the display flickers.

I really wonder how it could be possible.

#### dannyf

Joined Sep 13, 2015
2,197
Exactly 300 words, in MPLAB Express, using a free compiler.

Code:
Starting Build...
>make -f Makefile CONF=free
make -f nbproject/Makefile-free.mk SUBPROJECTS= .build-conf
make -f nbproject/Makefile-free.mk dist/free/production/16f54_ledx7.production.hex
"xc8.exe" --pass1 --chip=16F54 -Q -G --double=24 --float=24 --opt=default,+asm,+asmfile,-speed,+space,-debug --addrqual=ignore --mode=free -P -N255 --warn=0 --asmlist --summary=default,-psect,-class,+mem,-hex,-file --output=default,-inhx032 --runtime=default,+clear,+init,-keep,-no_startup,-osccal,-resetbits,-download,-stackcall,+clib --output=-mcof,+elf:multilocs --stack=compiled:auto "--errformat=%f:%l: error: (%n) %s" "--warnformat=%f:%l: warning: (%n) %s" "--msgformat=%f:%l: advisory: (%n) %s" -obuild/free/production/main.p1 main.c
"xc8.exe" --chip=16F54 -G -mdist/free/production/16f54_ledx7.production.map --double=24 --float=24 --opt=default,+asm,+asmfile,-speed,+space,-debug --addrqual=ignore --mode=free -P -N255 --warn=0 --asmlist --summary=default,-psect,-class,+mem,-hex,-file --output=default,-inhx032 --runtime=default,+clear,+init,-keep,-no_startup,-osccal,-resetbits,-download,-stackcall,+clib --output=-mcof,+elf:multilocs --stack=compiled:auto "--errformat=%f:%l: error: (%n) %s" "--warnformat=%f:%l: warning: (%n) %s" "--msgformat=%f:%l: advisory: (%n) %s" --memorysummary dist/free/production/memoryfile.xml -odist/free/production/16f54_ledx7.production.elf build/free/production/main.p1
Microchip MPLAB XC8 C Compiler (Free Mode) V1.35
Build date: Jul 7 2015
Part Support Version: 1.35
Copyright (C) 2015 Microchip Technology Inc.
License type: Node Configuration

Memory Summary:
Program space used 12Ch ( 300) of 200h words ( 58.6%)
Data space used 12h ( 18) of 19h bytes ( 72.0%)
EEPROM space None available
Data stack space used 0h ( 0) of 7h bytes ( 0.0%)
Configuration bits used 0h ( 0) of 1h word ( 0.0%)
ID Location space used 0h ( 0) of 4h bytes ( 0.0%)

You have compiled in FREE mode.
Using Omnicient Code Generation that is available in PRO mode,
you could have produced up to 60% smaller and 400% faster code.
See [URL]http://www.microchip.com/MPLABXCcompilers[/URL] for more information.
this is how the hardware connection is specified - fully user configurable.

Code:
//hardware configuration
//Pin12/Dig1 on RA1
#define DIG1_PORT                   PORTA
#define DIG1_DDR                    TRISA
#define DIG1                        (1<<1)          //D1 on RA1

//Pin11/SegA on RA0
#define SEGA_PORT                   PORTA
#define SEGA_DDR                    TRISA
#define SEGA                        (1<<0)

//Pin10/SegF on RB7
#define SEGF_PORT                   PORTB
#define SEGF_DDR                    TRISB
#define SEGF                        (1<<7)

//Pin9/Dig2 on RB6
#define DIG2_PORT                   PORTB
#define DIG2_DDR                    TRISB
#define DIG2                        (1<<6)

//Pin8/Dig3 on RB5
#define DIG3_PORT                   PORTB
#define DIG3_DDR                    TRISB
#define DIG3                        (1<<5)

//Pin7/SegB on RB4
#define SEGB_PORT                   PORTB
#define SEGB_DDR                    TRISB
#define SEGB                        (1<<4)

//Pin6/Dig4 on RB3
#define DIG4_PORT                   PORTB
#define DIG4_DDR                    TRISB
#define DIG4                        (1<<3)

//Pin5/SegG on RB2
#define SEGG_PORT                   PORTB
#define SEGG_DDR                    TRISB
#define SEGG                        (1<<2)

//Pin4/SegC on RB1
#define SEGC_PORT                   PORTB
#define SEGC_DDR                    TRISB
#define SEGC                        (1<<1)

//Pin3/SegDP on RB0
#define SEGDP_PORT                  PORTB
#define SEGDP_DDR                   TRISB
#define SEGDP                       (1<<0)

//Pin2/SegD on RA3
#define SEGD_PORT                   PORTA
#define SEGD_DDR                    TRISA
#define SEGD                        (1<<3)

//Pin1/SegE on RA2
#define SEGE_PORT                   PORTA
#define SEGE_DDR                    TRISA
#define SEGE                        (1<<2)
All the TRIS bits are not necessary on this chip.

With minimum reconfiguration, you can pretty much run it on any mcu.

Last edited:

#### dannyf

Joined Sep 13, 2015
2,197
Worst case scenario: the C routines take 90 ticks to update the display buffer, and 130 ticks to update the display. So running off a 32Khz crystal, the led display is updated at least 40x per second. Flicker free I guess for the mortal mass,

#### takao21203

Joined Apr 28, 2012
3,700
so heres my criticism. No source code.
No configuration bits even. Are you kidding me.

If you dont want to show source code then dont reply to my thread where I put the full source.
And say your code is better in any kind.
Sorry for being blunt.

#### dannyf

Joined Sep 13, 2015
2,197
so heres my criticism. No source code.
No configuration bits even. Are you kidding me...
At the appropriate time / venue, i will share the source code - it basically incorporates what I told you to do earlier.

So no need to hyperventilate.

As to your code, you should read the datasheet a little bit carefully, because as written, this little statement of yours has TWO bugs:

Code:
   TMR0&=0x1f;
You will find that the timing is just slightly longer than you had anticipated.

Again, don't hyperventilate. Just go back and read the timer section thoroughly and you will figure it out.

#### takao21203

Joined Apr 28, 2012
3,700
At the appropriate time / venue, i will share the source code - it basically incorporates what I told you to do earlier.

So no need to hyperventilate.

As to your code, you should read the datasheet a little bit carefully, because as written, this little statement of yours has TWO bugs:

Code:
   TMR0&=0x1f;
You will find that the timing is just slightly longer than you had anticipated.

Again, don't hyperventilate. Just go back and read the timer section thoroughly and you will figure it out.
No, now you are being cryptic.
I want to be frank I dont believe your code is faster or smaller.
You dont want to show it, fine. But then you compare two different things.

Sure I need to keep secrect a small script doing something that was done 30 years ago.

Hyperventilating? Now youre crazy. The matter leaves me cold. I just would like to verify your statements.
For this purpose, please show the complete source.

90 cycles sure. You do all in 22.5 cycles. I believe it. But seeing is better than believing.

#### dannyf

Joined Sep 13, 2015
2,197
for your coding enjoyment,

The code running on a 16F84A - minimal changes from a 16F54.

#### Attachments

• 50.4 KB Views: 11

#### dannyf

Joined Sep 13, 2015
2,197
I told you earlier about this line of your code being buggy - the timing will be longer than you expected:

Code:
TMR0&=0x1f;
Here is a simulation of your code running on a 16F84A (pretty much identical to 16F54). I was running to the line mentioned earlier, and counting time - the mcu is running on a 32Khz crystal.

At the bottom of the picture is time elapsed since the last time the code executed to the above line. In a correctly designed code, it should be exactly 1.0 second. It took your code 1.0011 second to do that. ie, your time-keeping is off roughly 4 seconds off per hour, or 2 minutes per day.

That's a very poor clock. Even a knock-off Rolex is better than that.

#### Attachments

• 43.8 KB Views: 5

#### takao21203

Joined Apr 28, 2012
3,700
OK according to my calculations, I used a CASIO calculator, the deviation is 1 second every 4096

Since the prescaler is 256 the write is every 32 timer increments.
I never actually understood if this stalling affects the prescaler or just the timer register update. Anyway Ive added code to correct this.

Its not fair to use 16F84. You use division as well. Lets say, division also is not allowed.
How you do the multiplexing isnt clear either. Does the 16f84 for instance have the restriction on lower 256 words for ROM constants? Does it have 25 bytes only? At 1K the savings wont be required but I need some extra space for the drift correction so maybe, just 50 remaining.

OK your go-cart is faster and better just for some reason, you cant demonstrate it, instead you bring another with a slightly different engine kind, and say, for some reason, this engine even cant be looked at.

Sure I have many other PICs around, DS1307, even completely ready made RTC modules. Sure maybe in some way youre right.

But also for sure I did these small 7seg tables years ago and did set port bits explicitely and I wasnt too happy about it.

I dont yet understand if your program is equal in all details and if it is really faster and or shorter.

And yes in some way, correcting the drift and having a pushbutton, these are already frills of some kind. Just for a timer, the drift makes little difference, and if you start the clock at midnight, well there you go...

When the overhead added is 2 cycles, on top of 256, and this occurs every one in 32 times, its 1/4096? 68 minutes, 16 seconds. Its not explained in the datasheet if this affects the prescaler or just the timer register. At least its not spelled out.

When the pushbutton works I will do a long time test. And prep up the documentation.
I had the crystall cap ground soldered to a display pin of course this caused drifting visible after just 1/2 hour.

Last edited: