Jump to content
Shree

Precise Timings With Timer 0

Recommended Posts

Hey Wizards,

I am trying to create a timer which would be on for the value entered in eeprom. This value along with a switch can work for mins as well as secs (i.e. if the switch is on, the eeprom value 'X' is X minutes else the X is X secs. This value is displayed on a 7 segment display (3 digits). I am implementing a 16F877A for this (Shame I am using a Heman's sword to kill an ant! But couldnt find a solution to drive all three displays at a time.). The program runs as per my expectation. The only problem though is I get a 1 sec error every minute (so 999 secs more for same mins!!!!). Below is the code I have written. I know my skill with the code writting is very primitive, but please can somebody help me by looking into it and let me know if there is any chance to get error free (or atleast unnoticeably low error) execution?

my software:

 

#include <system.h>
#include <Eeprom877A.h>
#pragma CLOCK_FREQ 4000000
#pragma DATA _CONFIG, _WDT_OFF & _XT_OSC & _LVP_OFF & _PWRTE_OFF & _CP_OFF
unsigned char t=0, t1=0, t2=0, u=0, x=0, y=0, disp=0, disp1=0, disp2=0, units=0, tens=0, hundreds=0, z=0;
unsigned int dist=0;
bit grn=0;
void eeprom_load(void);
void display(void);

void interrupt(void)
{
tmr0+=9; //Adjusting timer0 to count upto 250uSec only and not 256usec.
t++;
if(t>19)
{				//1 second routine and if value of u is 60, it would count 60 seconds
	t=0;
	t1++; //Increaments after 20 interrupts i.e. 5ms
	if(t1>199)
	{
		t1=0;
		t2++; //Increaments after 200X20 interrupts i.e. 1 sec 
		if(t2>u)
		{
			t2=0;
			dist--;
		}
	}
}
intcon.2=0; //TOIF
}

void main()
{
Begin: unsigned char ux=0;
u=ux;
trisa=0x1b;
adcon1=0x8e; //making all the pins on portA except AN0 as digital IO
porta.2=1; //Power ON Indicator
porta.5=0; //Relay pin
trisc=0x00;
portc=0x3f; //Display 0 on portC which is connected to LSB(units) digit.
trisb=0x00;
portb=0xbf; //Display 0 on portB which is MSB(hundreds) and b.7 is connected to cathode via trasistor, which means b.7 is display enable/disable pin
trisd=0x00;
portd=0x3f; //Displat 0 on portD which is the middle(tens) digit of display
trise=0x07;
if(porta.4==1)//porta.4 is a pin which when high makes the timer to enter programming mode where the delay time can be written in eeprom
{
	eeprom_load(); //Function to write eeprom
	while(1){}
}
else
{
	while(1)//Wait till the switch is pressed
	{
		if(porte.2==1) // Enable switch
		{
			delay_ms(64); //Switch debouncing
			while(porte.2==1)
			{}
			delay_ms(32);
			grn=1; //Flag 
			goto frm;
		}
	}
	frm: if(grn==1)
	{
		read_eeprom(0x01); //From header file
		disp2=eedata;
		read_eeprom(0x00);
		disp1=eedata;
		dist=((disp2*100)+disp1);//After reading both locations the original value stored is constructed
					 //Look in the last part of eeprom_load function here disp1 is same as 'z'
		if(porta.1==1) //Choosing if the count is for seconds or minutes
		{
			u=60;
		}
		if(porta.1==0)
		{
			u=0;
		}	
		option_reg=0x08; //Configuring timer0 prescaler 1:1, CS: Internal clock
		tmr0=0x06;
		intcon.5=1; // Enable Timer0 interrupt
		intcon.7=1; //Enable global interrupt
		while(dist!=0)
		{
			portb.7=1; //Enable Display
			porta.5=1; //Make the relay on
			units=dist%10;
			disp=units;
			display();
			portc=x;
			tens=(dist%100)/10;
			disp=tens;
			display();
			portd=x;
			hundreds=dist/100;
			disp=hundreds;
			display();
			portb=x+128;
		}

		porta.5=0; //Relay OFF
		intcon.5=0; //TMR0 interrupt off
		intcon.7=0; //GIE off
		goto Begin; // Be ready for another count
	}
}	

}

void eeprom_load()
{
portb.7=1; //Enable Display
delay_ms(128);
while(1)
{
	strt:
	if(porte.0==1) //Increaments the units display digit 
	{
		delay_ms(64);
		while(porte.0==1){}
		delay_ms(32);
		disp++;
		if(disp>9)
		{
			disp=0;
		}
		display();
		portc=x;
	}
	if(porte.1==1) //Shifts the value in the tens digit
	{
		delay_ms(64);
		while(porte.1==1){}
		delay_ms(32);
		y++;
		if(y==1)
		{
			portc=0x3f;
			disp1=disp;
			display();
			portd=x;
			disp=0;
		}
		if(y==2)
		{
			portc=0x3f;
			disp2=disp1;
			disp1=disp;
			display();
			portd=x;
			disp=disp2;
			display();
			portb=x+128;
			disp=0;
		}
		if(y>=3)
		{
			y=0;
			disp=0;
			x=0;
			disp1=0;
			disp2=0;
			portc=0x3f;
			portd=0x3f;
			portb=0xbf;
			goto strt;
		}
	}
	if(porte.2==1) //The value is final for should be written to eeprom
	{
		delay_ms(64);
		while(porte.2==1){}
		delay_ms(32);
		z=((disp1*10)+disp); //Adding value in units and tens so that it could be stored in 1 eeprom location instead of 2 (as the value
		goto entry;		  //wont exceed 255)
	}
}
entry:
write_eeprom(0x00,z); //Writing to eeprom units and tens
write_eeprom(0x01,disp2); //writing to eeprom hundreds digit
disp1=0;
disp2=0;
disp=0;
}		

void display()
{
if(disp==0)//Values are such that portX.0 is connected to 'a' segment and 'g' segment is connected to portX.6
{
	x=0x3f; 
}
if(disp==1)
{
	x=0x06;
}
if(disp==2)
{
	x=0x5b;
}
if(disp==3)
{
	x=0x4f;
}
if(disp==4)
{
	x=0x66;
}
if(disp==5)
{
	x=0x6d;
}
if(disp==6)
{
	x=0x7d;
}
if(disp==7)
{
	x=0x07;
}
if(disp==8)
{
	x=0x7f;
}
if(disp==9)
{
	x=0x6f;
}
}

 

 /* A Header file which would do reading and writing EEPROM in 16F877A */

unsigned char read_eeprom(unsigned char read_addr)
{		
eeadr=read_addr;
eecon1.7=0; //resetting EEPGD bit to access data memory
eecon1.0=1; //Enabling Read operation (RD bit high)
return (unsigned char)eedata;
}

void write_eeprom(unsigned char write_addr, unsigned char write_data)
{
intcon.7=0; //Turning off interrupts
pir2.4=0; //EEIF flag bit is reset in case there was any write previously 
eeadr=write_addr;
eedata=write_data;
eecon1.7=0; //EEPGD bit to access data memory
eecon1.2=1; //Enabling write operation by setting WREN bit
eecon2=0X55; //REquisite
eecon2=0XAA; //These are kinda passwords
eecon1.1=1; //enabling WR bit
while(eecon1.1!=0)
{
}
eecon1.2=0; //WREN bit
}

Thanks in Advance

Regards

Shree

Edited by Shree
added code tags around the code

Share this post


Link to post
Share on other sites

*** Edited in responce to Shree's edits above ***

Shree,

I apologise in advance if this offends you, it is not intended to. You admit you are not (YET) skilled at writing code, I hope this means you are keen to learn.

Now for the criticism which is intended to be constructive even if it appears to be very negative.

 

Just quickly looking at your code, I see your ISR is rather complicated. You aren't using symbolic bit names e.g. instead of intcon.2 it is recommended to use intcon.TMR0IF, (see the PnnFnnn.h header for your chosen PIC for what's defined) which increases the risk of mistakes creeping in and makes the code non-portable and hard to read even with a data sheet in the other hand. Also you haven't commented the code except for a few function calls (Edit: Thanks for the comments, Big Help). I cannot find any polite way to criticize your use of flow control structures (if, goto etc.) except to say it is NOT good. You have initialised nearly all the variables in their declarations which means when we encounter them in the code we have no idea of the initial value without searching for the declaration.

You haven't attached your Eeprom877A.h nor documented the connections to your displays and switches so we can't build and simulate your code and to finally insult the reader you haven't used any indentation to clarify the program structure. (Edit:Now corrected by Shree)

 

Thank you for staying with me so far. I am now going to *try* to offer some positive help.

 

Do you have access to a copy of K&R? If so *PLEASE* try to format your code like the examples in the book! There lots of good C style guides out there but most of them are more formal and better suited to large programs.

 

I have said it needs more comments, but you shouldn't just sprinkle them on like sugar on a donut. As a rough rule of thumb, comment *what* you are doing not how you are doing it unless it is very tricky and any small change is certain to break the code. A comment on every line is NOT desirable! One on every 10 lines is probably on the low side, find an intermediate level of commenting you are happy with. It is also helpful to use comment 'blocks' that start at the left margin before every function or significant block of code. Please see %ProgramFiles%\SourceBoost\plugin_examples\lcd\lcd4bit.c for an example of well structured and clearly commented code.

 

To handle three 7 segment displays (digits only, no use of decimal points) you only need 10 outputs. 7 for the segment drive lines and one for the common for each display. The method is called 'multiplexing'. To get enough brightness one would use driver transistors between the PIC and the three display common pins. I reckon it could be fitted into a 16F84 with two switches (seconds/minutes and start/stop) and an output for a relay or alarm but that would use all the pins. (I prefer 16F88 PICs as then I can use PICkit 2 ICD to debug my code, but that needs the ICSP PINS reserved, so I'd actually build this on a 16F886)

 

As to your actual bug, from its effects, it is almost certainly a classic C pitfall, but I'm not willing to wade through code like this to narrow it down.

 

Read http://en.wikipedia.org/wiki/Off-by-one_error and you'll probably be able to find it. LOOK CAREFULLY at where you handle the overflow from seconds to minutes.

 

I am 100% certain that you can get this program (or rather it's debugged version) working perfectly without redesigning the hardware and accuracy as good as that of your crystal. The design will not be pretty but it will do the job correctly and reliably. Once you have debugged it. you could then write and debug the 'pretty' version in under 1/4 the time.

 

For the moment, I'd leave the flow control structure as it is, and the initialisations where they are as if you start moving it all around you will risk introducing other bugs. You need to find the current bug first, fix it and clean up the code. Otherwise its discard and rewrite, but unless you know what went wrong this time you have no guarantee of doing better.

 

If you tidy it up and comment it, more people will be willing to look at it . . .

I *will* take another look if you improve it.

 

I remember some of my very early C programs, and yours is probably better . . .

 

Respectfully

 

Ian

Edited by IanM

Share this post


Link to post
Share on other sites

Dear Ian,

Accept my appologies for the mess above and a sincere thankyou for your reply. I have been always overwhelmed by the nature of this forum and may not for other things but for giving back this forum a tiny bit it has given me I want to be a good programmer! There's this saying in india which means its nice trick which kills the snake without breaking the stick! you have done same thing. I dont know what would I had done if I was you reading this kinda code.

You pointed at somethings which I would liked to be helped with

1. My code is indented. I just did a copy paste from IDE. I dont know how the indention got lost. I will try to attach the code file. I hope that would make it readable or else please tell me a better method.

2. All the variables declared are initialised as I needed them 0 when the program starts execution. I dont know how to improve this. Please do a favour by suggesting an alternative.

3. I think for now on I will add comments to the places where the name and bit of SFR's are directly used. I do think the way you told me is better for porting and readability. (Sorry Reynard! I remember you telling this same thing on a previous occasion, but I was too lazy!).

4. I had tried with multiplexing a port and 3 pins for 3X7seg display. But I was not satisfied with the output as though the output was what was desired digit, but the other segments just flickered very lightly. I dont know why. So I was reluctunt to implement that technique. Another idea was to implement hardware way. i.e. 4511 (7seg driver) and 4029(decade counter). With this I only needed to use 2 pins of PIC. but it looked like the circuit would be bit of bulky with 6 more ICs. Besides one of my friend had spare 877A's, which he lended me. So its just that!

For time being I am trying to present you the code as it is (with comments ofcourse). I hope this time its readable! I would be glad if you would be more harsh in pointing out whats wrong, because that would only result in me being a better and humble programmer!

Regards

Shree

P.S: I have edited the earlier posts and this one according to Ian's suggestions. Now the whole code and header file could be read in the first post itself so I think the need for attachments was no more.

Edited by Shree

Share this post


Link to post
Share on other sites
1. My code is indented. I just did a copy paste from IDE. I dont know how the indention got lost. I will try to attach the code file. I hope that would make it readable or else please tell me a better method.
Post your code inside a code block tags: [ code ] [ /code ] to retain the formatting.

 

Regards

Dave

Share this post


Link to post
Share on other sites
...

You pointed at some things which I would liked to be helped with

1. My code is indented. I just did a copy paste from IDE. I don't know how the indention got lost. I will try to attach the code file. I hope that would make it readable or else please tell me a better method.

I haven't had time to look at your code yet. Hopefully I will fit it in over the next day or two. I apologise for implying you would deliberately fail to indent.

Attached files are GREAT, I'll just drop them into Sourceboost, but for someone just browsing it is easier if you wrap your code in [ code]...[ /code] tags. (Take out the spaces inside the []. We had to put them there as IP.board BBcode AFAIK doesn't have an escape character like '\' in C.) if you click rte-code-button.png just above the edit box, the forum editor will put them in for you to paste in between or you can hilight the code and then click the button.

2. All the variables declared are initialised as I needed them 0 when the program starts execution. I dont know how to improve this. Please do a favour by suggesting an alternative.

 

It will often result in clearer code if you declare a variable at the start of a block or function then explicitly initialise it elsewhere with an assignment statement, usually at the last point the program flow lets you before you start using it.

 

3. I think for now on I will add comments to the places where the name and bit of SFR's are directly used. I do think the way you told me is better for porting and readability. (Sorry Reynard! I remember you telling this same thing on a previous occasion, but I was too lazy!).

I'm really lazy and usually cant be bothered to find the right page in the data sheet, note down the port bits etc. then return to my program. This is even more tedious if I am coming back to code I wrote last week or worse, last year. If in the IDE editor you click then right click on any function or variable name or name of a #defined constant it has the context menu option Go to the definition of '<name>' which will take you directly to it, opening another file if it has to. Do this on a register name like 'intcon' and it will take you to the header file for your PIC where it is defined. Scroll down and you will see all the bits defined for each register. Now that's really lazy and you are missing out by using the numbers directly. It can be *slightly* more typing, but I tend to use Ctrl-c and Ctrl-v a lot to copy and paste the names to avoid typing them. + it will make Reynard happy ;-)

4. I had tried with multiplexing a port and 3 pins for 3X7seg display. But I was not satisfied with the output as though the output was what was desired digit, but the other segments just flickered very lightly. I don't know why. So I was reluctant to implement that technique. Another idea was to implement hardware way. i.e. 4511 (7seg driver) and 4029(decade counter). With this I only needed to use 2 pins of PIC. but it looked like the circuit would be bit of bulky with 6 more ICs. Besides one of my friend had spare 877A's, which he lent me. So its just that!

Well with a good friend like that, it makes sense. If you have the chip with lots of pins in stock and you would have to buy the smaller one, you would be crazy to spend money and waste time. I just wasn't sure if you had heard of multiplexing so I thought I'd give you a word to Google. When you have this version working, I'd like to see the multiplexing code that flickered though. There is probably something very simple wrong with it. Start a new topic for it when you are ready.

For time being I am trying to present you the code as it is (with comments of course). I hope this time its readable! I would be glad if you would be more harsh in pointing out what's wrong, because that would only result in me being a better and humble programmer!

Regards

Shree

Don't put yourself down. I have been tinkering with electronics for over 30 years now and programming for about as long. I only spent a short time working as a full time commercial programmer because I found that coding full time was destroying my hobby, though I do write code for pay when its needed to get the project done.

 

Most of what you need is just experience and that will come with time and effort. Being humble is not an essential requirement. Being willing to learn something new each day is.

 

I will try NOT to be too harsh. Mostly because I would want to be criticized with a little kindness when It is my code being shot down ;-)

 

P.S. *PLEASE* delete any parts of the message you are quoting that are no longer important. It gets *BORING* scrolling down through repeats of the same text one has already read many times. You will note I quote parts of the previous post ONCE to give the questions I am answering or visa versa, but remove everything else. If you are just adding a couple of lines at the bottom, and no one else has posted in-between you don't need to quote anything so in that case, delete everything in the edit box before you start your reply.

I'm not saying I *wont* help if you don't 'snip' previous quotes, but if I'm busy I'm less likely to if the post starts with a long quote.

 

P.P.S. I had to change the following to build your code:

Line 2: change <> to "" - Eeprom877A.h is not a standard header but is in my project directory.

Line 38: change ; /Making to ; //Making - Invalid comment.

Edited by IanM

Share this post


Link to post
Share on other sites

Just a general comment on Ian's response. I think he has made the right compromise between frankness and politeness. It is not easy. I can easily tell that no insult was intended and that as much help as possible was given. His advice, I think was excellent. I hope you beat your project into submission and are happy with the results.

Share this post


Link to post
Share on other sites

Hey wizards

Thanks for all the spoon feeding you offered! Especially Dave who on his own helped me by rearranging the code and Ian for his priceless suggestions. Now I suppose that I have made upto most of suggestions and hope the code is readable. So I request all of you to suggest a possible solution to avoid a 1 sec per minute error (61secs instead of 60secs) in the count.

Thanks in Advance

Regards

Shree

Share this post


Link to post
Share on other sites

OK! Who's upsetting Reynard ?

 

Shree,

 

Remember that a program should be self documenting to limit the amout of comments that youu have to add to make it readable.

 

Example:

enum SW_STATE {OFF = 0, ON};	// switch states.

volatile bit PushButtonSwitch@PORTE.1;		// assign switch to port pin.
volatile unsigned char LED_Port_1@PORTC;	// assign units LED display to port.
volatile unsigned char LED_Port_10@PORTD;	// assign tens LED display to port.
volatile unsigned char LED_Port_100@PORTB;	// assign hundreds LED display to port.

// Segment to port bit assignment.
#define Sa	0x01
#define Sb	0x02
#define Sc	0x04
#define Sd	0x08
#define Se	0x10
#define Sf	0x20
#define Sg	0x40
#define Sdp 0x80

//	Digit segment patters.
#define DIGIT_0_SEGMENT (Sa | Sb | Sc | Sd | Se | Sf)
#define DIGIT_1_SEGMENT (Sb | Sc)
#define DIGIT_2_SEGMENT (Sa | Sb | Sd | Se | Sg)
#define DIGIT_3_SEGMENT (Sa | Sb | Sc | Sd | Sg)
#define DIGIT_4_SEGMENT (Sb | Sc | Sf | Sg)
#define DIGIT_5_SEGMENT (Sa | Sc | Sd | Sf | Sg)
#define DIGIT_6_SEGMENT (Sa | Sc | Sd | Se | Sf | Sg)
#define DIGIT_7_SEGMENT (Sa | Sb | Sc)
#define DIGIT_8_SEGMENT (Sa | Sb | Sc | Sd | Se | Sf | Sg)
#define DIGIT_9_SEGMENT (Sa | Sb | Sc | Sf | Sg)

unsigned char digitPattern[10] = {
DIGIT_0_SEGMENT,DIGIT_1_SEGMENT,DIGIT_2_SEGMENT,DIGIT_3_SEGMENT,DIGIT_4_SEG
MENT,
DIGIT_5_SEGMENT,DIGIT_6_SEGMENT,DIGIT_7_SEGMENT,DIGIT_8_SEGMENT,DIGIT_9_SEG
MENT
};

void main ()
{
...
if (PushButtonSwitch == ON)
{
	LED_Port_1 = LED_GetDigit(3);	// units
	LED_Port_10 = LED_GetDigit(2);	// tens
	LED_Port_100 = LED_GetDigit(1);	// hundreds
}
...
}

/***********************************************************
*	Get digit segment pattern							  *
***********************************************************/
unsigned char LED_GetDigit(unsigned char digit) {

return digitPattern[digit];
}

 

Redefine your hardware up front so that ports and bits actually mean something to your project. If you change your design and assign the push button switch to a different port pin you only have to change one line of code and not search your complete program for 'porte.1'.

 

Make variable names meaningful even if it means more typing. You can always use Ctrl-Space and use word completion.

 

Don't assign global variables a value when you declare them. Do it in main() where they are part of your program, the code size will be the same. If you declare them as static the compiler will insert code to clear them for you but is not always a good idea or good programming.

 

Cheers

 

Reynard

Share this post


Link to post
Share on other sites

Wow, Reynard that code was something!!!! I have full faith in what am I going to write now....Never in the heavens could I had thaught of writing things in this way. Maybe I have a complex that its too hard or may be I have a limited ability! whatever...Its for sure I will start copying the parts you have written in my coming-up codes ;)

Anyways, is my code still unreadable? Because I am just cant wait to find a solution for that 1 sec error. I tried many things, but to no avail! I hope I will get some help in the matter.

Thanks for your example code Reynard

Regards

Shree

Share this post


Link to post
Share on other sites

Hi Sree,

I didn't have time to go through your code. I think the eeprom read & write functions disable the interrupts while writing & reading. May be that could be one reason why your timing is not accurate.

Regards

Raghunathan.

Share this post


Link to post
Share on other sites
Hi Sree,

I didn't have time to go through your code. I think the eeprom read & write functions disable the interrupts while writing & reading. May be that could be one reason why your timing is not accurate.

Regards

Raghunathan.

Hello Mr. Raghunathan,

If you see the code, the interrupt and eeprom functions never occur toeghter. Besides there is always 1 second more in every minute. I dont know why.

Regards

Shree

Share this post


Link to post
Share on other sites

Shree,

 

Are you taking into account that seconds go from 0 to 59 and 60 seconds doesn't exist ?

 

Cheers

 

Reynard

Share this post


Link to post
Share on other sites
Hi Sree,

I didn't have time to go through your code. I think the eeprom read & write functions disable the interrupts while writing & reading. May be that could be one reason why your timing is not accurate.

Regards

Raghunathan.

Hello Mr. Raghunathan,

If you see the code, the interrupt and eeprom functions never occur toeghter. Besides there is always 1 second more in every minute. I dont know why.

Regards

Shree

Well, if the eeprom write & read are not called while your timer/ interrupt is activated, there could be some other problem. you can probably check the timing on the mplab simulator using stop watch.

Make your global variable dist as signed int. since you keep decrementing its value in the ISR. it may take negative value.

use while(dist>0) or while(dist>-1)

Regards

Raghunathan.

Edited by ra68gi

Share this post


Link to post
Share on other sites

Hey Sree,

Once you enter into a while(1) loop in the main, don't get out off it. Why do you go back to initializing the ports. I would suggest not to use goto.

If u are using flags then there is no need to use goto.

Regards

Raghunathan

Edited by ra68gi

Share this post


Link to post
Share on other sites

Hi, Shree

 

I've been through your Final.c, putting in symbolic names where I can to make it easier to follow. My cleaned up version builds a totally identical HEX file to the original and to the cleaned up version in your revised first post as of the date of this reply. I haven't changed the structure of your program yet as I believe you will learn more if we find the bug before you rewrite it with Reynard's excellent suggestions included and a few more from me and no doubt from others as well. I have modified a few comments to account for symbols instead of port bit numbers.

 

Here is your code after my edits:

//Shree's Final.c 16/11/09 with symbols added by IanM
//Program structure has NOT been improved.
#include <system.h>
#include "Eeprom877A.h"
#pragma CLOCK_FREQ 4000000
#pragma DATA _CONFIG, _WDT_OFF & _XT_OSC & _LVP_OFF & _PWRTE_OFF & _CP_OFF

//Some care will be needed with these defines especially if used in expressions
//but at this stage it is desirable to build an identical hex file to
//Shree's original.  We are also going to have trouble with the RMW on the ports.

#define PERIOD_SW porta.1
#define PWR_LED porta.2
#define PROG_MODE porta.4
#define RELAY porta.5

#define DISP_EN portb.7

#define UNITS_SW porte.0
#define TENS_SW porte.1
#define ENABLE_SW porte.2

//All line numbers after here are 20 higher than in Shree's original.

unsigned char t=0, t1=0, t2=0, u=0, x=0, y=0, disp=0, disp1=0, disp2=0, units=0, tens=0, hundreds=0, z=0;
unsigned int dist=0;
bit grn=0;
void eeprom_load(void);
void display(void);

void interrupt(void)
{
tmr0+=9; //Adjusting timer0 to count upto 250uSec only and not 256usec.
t++;
if(t>19)
{				//1 second routine and if value of u is 60, it would count 60 seconds
	t=0;
	t1++; //Increaments after 20 interrupts i.e. 5ms
	if(t1>199)
	{
		t1=0;
		t2++; //Increaments after 200X20 interrupts i.e. 1 sec 
		if(t2>u)
		{
			t2=0;
			dist--;
		}
	}
}
intcon.T0IF=0; //TOIF
}

void main()
{
Begin: unsigned char ux=0;
u=ux;
trisa=0x1b;
adcon1=0x8e; //making all the pins on portA except AN0 as digital IO
PWR_LED=1; //Power ON Indicator
RELAY=0; //Relay pin
trisc=0x00;
portc=0x3f; //Display 0 on portC which is connected to LSB(units) digit.
trisb=0x00;
portb=0xbf; //Display 0 on portB which is MSB(hundreds) and portb.7 (DISP_EN) is connected to cathode via transistor as display enable/disable pin.
trisd=0x00;
portd=0x3f; //Displat 0 on portD which is the middle(tens) digit of display
trise=0x07;
if(PROG_MODE==1)//PROG_MODE is a pin on portA which when high makes the timer to enter programming mode where the delay time can be written in eeprom
{
	eeprom_load(); //Function to write eeprom
	while(1){}
}
else
{
	while(1)//Wait till the switch is pressed
	{
		if(ENABLE_SW==1) // Enable switch
		{
			delay_ms(64); //Switch debouncing
			while(ENABLE_SW==1)
			{}
			delay_ms(32);
			grn=1; //Flag 
			goto frm;
		}
	}
	frm: if(grn==1)
	{
		read_eeprom(0x01); //From header file
		disp2=eedata;
		read_eeprom(0x00);
		disp1=eedata;
		dist=((disp2*100)+disp1);//After reading both locations the original value stored is constructed
					 //Look in the last part of eeprom_load function here disp1 is same as 'z'
		if(PERIOD_SW==1) //Choosing if the count is for seconds or minutes
		{
			u=60;
		}
		if(PERIOD_SW==0)
		{
			u=0;
		}	
		option_reg=0x08; //Configuring timer0 prescaler 1:1, CS: Internal clock
		tmr0=0x06;
		intcon.T0IE=1; // Enable Timer0 interrupt
		intcon.GIE=1; //Enable global interrupt
		while(dist!=0)
		{
			DISP_EN=1; //Enable Display
			RELAY=1; //Make the relay on
			units=dist%10;
			disp=units;
			display();
			portc=x;
			tens=(dist%100)/10;
			disp=tens;
			display();
			portd=x;
			hundreds=dist/100;
			disp=hundreds;
			display();
			portb=x+128; //Set high bit, so equivalent to DISP_EN=1
		}

		RELAY=0; //Relay OFF
		intcon.T0IE=0; //TMR0 interrupt off
		intcon.GIE=0; //GIE off
		goto Begin; // Be ready for another count
	}
}	

}

void eeprom_load()
{
DISP_EN=1; //Enable Display
delay_ms(128);
while(1)
{
	strt:
	if(UNITS_SW==1) //Increaments the units display digit 
	{
		delay_ms(64);
		while(UNITS_SW==1){}
		delay_ms(32);
		disp++;
		if(disp>9)
		{
			disp=0;
		}
		display();
		portc=x;
	}
	if(TENS_SW==1) //Shifts the value in the tens digit
	{
		delay_ms(64);
		while(TENS_SW==1){}
		delay_ms(32);
		y++;
		if(y==1)
		{
			portc=0x3f;
			disp1=disp;
			display();
			portd=x;
			disp=0;
		}
		if(y==2)
		{
			portc=0x3f;
			disp2=disp1;
			disp1=disp;
			display();
			portd=x;
			disp=disp2;
			display();
			portb=x+128;
			disp=0;
		}
		if(y>=3)
		{
			y=0;
			disp=0;
			x=0;
			disp1=0;
			disp2=0;
			portc=0x3f;
			portd=0x3f;
			portb=0xbf;
			goto strt;
		}
	}
	if(ENABLE_SW==1) //The value is final for should be written to eeprom
	{
		delay_ms(64);
		while(ENABLE_SW==1){}
		delay_ms(32);
		z=((disp1*10)+disp); //Adding value in units and tens so that it could be stored in 1 eeprom location instead of 2 (as the value
		goto entry;		  //wont exceed 255)
	}
}
entry:
write_eeprom(0x00,z); //Writing to eeprom units and tens
write_eeprom(0x01,disp2); //writing to eeprom hundreds digit
disp1=0;
disp2=0;
disp=0;
}		

void display()
{
if(disp==0)//Values are such that portX.0 is connected to 'a' segment and 'g' segment is connected to portX.6
{
	x=0x3f; 
}
if(disp==1)
{
	x=0x06;
}
if(disp==2)
{
	x=0x5b;
}
if(disp==3)
{
	x=0x4f;
}
if(disp==4)
{
	x=0x66;
}
if(disp==5)
{
	x=0x6d;
}
if(disp==6)
{
	x=0x7d;
}
if(disp==7)
{
	x=0x07;
}
if(disp==8)
{
	x=0x7f;
}
if(disp==9)
{
	x=0x6f;
}
}

 

For convenience I'm also attaching the code and the EPROM877A.h header to save everyone time cutting and pasting into a new project and also so we can refer to line numbers and be talking about the same line. I will be taking this attachment down about one month after this thread ends or if Shree posts a new version.

 

Eeprom877A.h

Final2_IanM.c

Share this post


Link to post
Share on other sites
Shree,

 

Are you taking into account that seconds go from 0 to 59 and 60 seconds doesn't exist ?

 

Cheers

 

Reynard

 

Hello again Reynard,

If you see my ISR code it first loads 6 (aka 9) in timer0 then increaments some variables to total count of 4000 (increaments everytime on interrupt), hence the total count is 4000X250uSec= 1 sec. Now a variable 'u' is tested and if its value is 60, the above loop of 1 sec gets executed 60 times else if the value of 'u' is 0, the loop ends in 1 sec. Do you think that I must enter a value 59 in 'u' instead of 60. Actually I could not comprehend your suggestion becuase the stop watch is set for 2 mins would start from 2:00-1:59-1:58-----0:01-0:00, so actually its 60secs a minute. I dont know if my logic is correct. Please do correct me if I am wrong

Regards

Shree

Share this post


Link to post
Share on other sites

Your timings are all too long by one part in 60?

How accurately have you measured that?

Can you add code to toggle a spare pin every interrupt?

 

I believe portA.0 might be available. Obviously you'll need to clear its Tris bit, then adding:

porta.0=!porta.0;

in the ISR and connecting a frequency meter to that pin should give you exactly 2 KHz. If it is lower, you may also have a bug with how you have your timer set up and are shortening the period. One subtle 'gotcha' is the timer freezes for 2 instruction cycles after writing to it and 2/256 is about half the error you are seeing.

 

I think you may have tried to allow for this as the counter counts 0 to 255 and you are skipping 9 so it is supposed to go {INT} 0,1,2,3,4,...n,n+9,n+9,n+10...254,255 then another interrupt and back to 0 but I think you should be adding 8 for the correct period, not 9.

I may have mis-read the data sheet and the frequency meter should be the final judge.

 

There is almost certainly an off by one error in your seconds code as this wouldn't be enough to account for the observed error but it would be good to confirm your crystal is exactly on frequency and your interrupt rate is correct.

 

Ian

 

P.S. I just checked the number of instructions in the .lst file (+1 for the Goto) from ORG 0x04 from the ISR entry point to the RETFIE and got 48. That's 20% of your processor cycles in the ISR but at least you aren't overlapping yourself. I was worried that you might be missing some interrupts, but it seems not.

 

P.P.S. u definitely needs to be 59 or 0, not 60 or 0. 60 would be be ok if you were testing >= but then it would be 1 or 60. Its just like your 199 and 19 in the count 20*200 section. I believe that's the bug but it would only affect the minutes.

Edited by IanM

Share this post


Link to post
Share on other sites

Hello Wizards

Finally found the bug! The value of 'u' which determines if the count must be minutes or seconds (its value was 60 for minutes and 0 for seconds). Now this 'u' is compared with variable 't2' in the ISR and if 't2' is more then 'u' the code below executes ('dist' value decreaments). So for the 't2' to be more then 'u' it has to be atleast 61 and so there is a 61 seconds count instead of intended 60 seconds. I fixed that by making u=59 instead of u=60 and now the code works obsolutely error free! But I want to thank all of you first of all for bearing with me and despite my very primitive styled coding trying best to help me out! Also IanM deserves a special thanks for that constructive criticism and for his painstaking efforts of editing the code along with Dave who made my code readable. Reynard as always has given me some very special ideas of writting things differently so a big thanks to him for that!

Thanks all of you

Regards

Shree

Share this post


Link to post
Share on other sites

A few years back we talked about the fact that TIMER 0 is not a good choice for a clock as there is an inherent error in reloading the counter. Since you are using a device that has newer timers (TIMER 2) you should use the new timer since you can set the compare value once and just recieve an interrupt at the desired rate. TIMER 0 is fine for keyboad debouncing but not for real-time clocks.

 

I attached Clock.c and Clock.h that implements a clock using timer 2 that I used for a timelapse photo machine ( http://www.tedrossin.x10hosting.com/Electronics/Pic/Pic.html ) . The code includes a fine adjust to calibrate out crystal errors. The parts per million error on a cyrstal > 1MHz is usually pretty bad (20 to 50) such that your clock will lose or gain 10 to 20 seconds a day. A tuned 32KHz cyrstal connected to be a timer source is another way to get a good timebase. These cyrstals have very low PPM errors.

This code has more than you want but should at least show you how to modify your code to use timer 2. If it is confusing, never mind as what you have may be good enough if you don't plan on leaving it running for more than a few days.

 

If you want the ulitimate in accuracy on the cheap, go for sampling the power line (dropped down in voltage of course) to get a very reliable 60 or 50 Hz depending on where you live. The power companies control the beats per day to keep good old synchonous clock motors on the right time.

 

I tried to search for the thread on this subject but could not find it. We had a blast a few year back about this issue.

CLOCK.C

CLOCK.H

Share this post


Link to post
Share on other sites

Thanks for that Ted.

I am sure Shree will learn a lot from your code and I probably will as well :-)

 

Meanwhile we are trying to help Shree improve *HIS* programming, so need to push on with his Timer 0 based program even if that wasn't the best initial choice. I believe the error should be reasonable as with Timer0 running 1:1 off the instruction cycle clock the missed counts after a write are predictable and can be compensated for. As you point out, his crystal accuracy may be more of a problem

 

Shree, I've cleaned up your code and have a new version. I've rearranged control structures and some routines, got rid of the gotos and made as many variables as possible local to the functions but I hope you still recognise the design as being essentially yours. I haven't incorporated Reynard's hardware abstraction layer yet but have moved the display digit patterns to a ROM array.

 

As I don't have the right hardware and I am running an old Win98SE box here that only achieves 5% of real-time speed in the simulator this is very much UNTESTED CODE but it does compile OK.

//Shree's Final.c 16/11/09 rewritten 24/11/09 (bugfix) with symbols added by IanM
//Program structure has been improved.
#include <system.h>
#include "Eeprom877A.h"
#pragma CLOCK_FREQ 4000000
#pragma DATA _CONFIG, _WDT_OFF & _XT_OSC & _LVP_OFF & _PWRTE_OFF & _CP_OFF

//Some care will be needed with these defines especially if used in expressions
//We might also have trouble with the RMW on the ports.

#define PERIOD_SW porta.1
#define PWR_LED porta.2
#define PROG_MODE porta.4
#define RELAY porta.5

#define DISP_EN portb.7
#define DISP_BIT 0x80

#define UNITS_SW porte.0
#define SHIFT_SW porte.1
#define ENABLE_SW porte.2

unsigned char disp, disp1, disp2;
volatile unsigned int time_left=0;
bool long_time=false;
int ticks=0; // here, not in ISR so main() can clear it

//LED Segment bitmaps for digits 0 to 9: portX.0 is connected to 'a' segment and 'g' segment is connected to portX.6
rom unsigned char LEDsegs[]={ 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f };

void eeprom_load(void);

//ISR - at the moment only Timer0 overflow is enabled
void interrupt(void)
{
tmr0+=9;	//Adjust timer0 to count upto 250uSec only and not 256usec.
		//Should this be tmr0+=8; ?  NO. Shree proved it needs to be 9
ticks++;
if(ticks>=long_time?400*60:400)	//1 second or minute routine. if Longtime is 0 it fires every second otherwise every minute.  
{	
	ticks=0;
	if (time_left>0) time_left--; //Don't decrement past zero

} // end if ticks>=long_time?400*60:400
intcon.T0IF=0; //clear Timer0 interrupt flag
} // end interrupt

void init()
{
trisa=0x1b;
adcon1=0x8e; //make all the pins on portA except AN0 as digital IO
option_reg=0x08; //Configure timer0 prescaler 1:1, CS: Internal clock
PWR_LED=1; //Power ON Indicator
RELAY=0; //Relay pin
portc=LEDsegs[0]; 	//Display 0 on portC which is connected to LSB(units) digit.
portd=LEDsegs[0]; 	//Display 0 on portD which is the middle(tens) digit of display
portb=LEDsegs[0]|DISP_BIT; 	//Display 0 on portB which is MSB(hundreds)
			//and portb.7 (DISP_EN) is connected to cathode via transistor as display enable/disable pin.
disp2=disp1=disp=0;	//Also clear the digit variables 
trisc=0x00;		//now ready to enable the outputs
trisb=0x00;
trisd=0x00;
trise=0x07;
long_time=false;	//Seconds
intcon.T0IE=1; // Enable Timer0 interrupt
intcon.GIE=1; //Enable global interrupt
} // end init

void main()
{
unsigned char units, tens, hundreds;

init();			//Set up the ports and timer.
while(1)		//master loop
{

	if(PROG_MODE==1)	//PROG_MODE is a pin on portA which when high makes the timer to enter programming mode 
	{			//where the delay time can be written in eeprom
		eeprom_load(); //Function to write eeprom
		while(PROG_MODE==1); //wait for key up
		delay_ms(32); //debouncing
	} 
	else
	{
		while(ENABLE_SW==0);//Wait till the switch is pressed
		delay_ms(64); //Switch debouncing
		while(ENABLE_SW==1); //wait for key up
		delay_ms(32); //more debouncing
		long_time=PERIOD_SW; 	//Choose if the count is for seconds (0) or minutes (1)
		DISP_EN=1; //Enable Display

		intcon.T0IE=0; //disable Timer0 interrupt while period setting
		disp2=read_eeprom(0x01); //Read the stored period
		disp1=read_eeprom(0x00); //disp1 and disp are packed 0 - 99
		time_left=(disp2*100+disp1);	//After reading both locations the original value stored is constructed
		tmr0=0x06;	//preload timer
		ticks=0;	//and ticks
		RELAY=1; //Turn the relay on
		intcon.T0IE=1; //enable Timer0 interrupt to start timing

						//Look in the last part of eeprom_load function here disp1 is same as 'z'	
		while(time_left!=0)
		{
			units=time_left%10;
			portc=LEDsegs[units];
			tens=(time_left%100)/10;
			portd=LEDsegs[tens];
			hundreds=time_left/100;
			portb=LEDsegs[hundreds]|DISP_BIT; //Set high bit, so equivalent to DISP_EN=1
		} // end while time_left!=0
		RELAY=0; //Relay OFF
		portc=LEDsegs[0]; 	//Display 0 on portC which is connected to LSB(units) digit.
		portd=LEDsegs[0]; 	//Display 0 on portD which is the middle(tens) digit of display
		portb=LEDsegs[0]|DISP_BIT; 	//Display 0 on portB which is MSB(hundreds)
	} //end if PROG_MODE==1 	
} // end master loop
} // end main

void eeprom_load()
{
int y=0,z;

disp2=read_eeprom(0x01); //Read the stored period
disp1=read_eeprom(0x00); //disp1 and disp are packed 0 - 99
disp=disp1%10;
disp1=disp1/10;	// now unpacked 	
delay_ms(128);
while(1) // program loop
{
	portc=LEDsegs[disp]; //display update moved to start of loop
	portd=LEDsegs[disp1]; //so we dont need to do it elsewhere
	portb=LEDsegs[disp2]|DISP_BIT; //Set high bit, so equivalent to DISP_EN=1
	if(UNITS_SW==1) //Increments the units display digit 
	{
		delay_ms(64);
		while(UNITS_SW==1);
		delay_ms(32);
		disp++;
		if(disp>9) disp=0;
	}
	else if(SHIFT_SW==1) 	//Shifts the units to the tens digit
				//and tens to hundreds or clears on third press
	{
		delay_ms(64);
		while(SHIFT_SW==1){}
		delay_ms(32);
		y++;
		if (y<3)
		{
			disp2=disp1;
			disp1=disp;
			disp=0;
		} else {
			//third press clears it.		
			y=0;
			disp=0;
			disp1=0;
			disp2=0;
		} // end if y<3
	}
	else if(ENABLE_SW==1) //The value is final for should be written to eeprom
	{
		delay_ms(64);
		while(ENABLE_SW==1){}
		delay_ms(32);
		z=((disp1*10)+disp); 	//Adding value in units and tens so that it could be stored in 1 eeprom location instead of 2 (as the value
					//wont exceed 255)
		write_eeprom(0x00,z); 	//Writing to eeprom units and tens
		write_eeprom(0x01,disp2); //writing to eeprom hundreds digit
		return;
	} //end all if <switch>_SW==1
} //end while 1
}

 

Shree, please test this to see if this works pretty much the same as your version. I may have introduced some bugs, if so, tell me and I will try to find and fix them. If I have the design wrong so it does not meet your requirements, please also tell me.

 

Anyone else, please feel free to chip in. I know this code is nowhere near being pretty yet, but I would like to keep it close enough to what Shree is comfortable with for him to keep up.

 

Attached for your convenience (bugfix):

Final3_IanM.c

The EEPROM include file is unchanged:

EDIT: Include file rewritten to eliminate a nasty GIE bug that did not surface in Shree's code but totally screwed up mine. See my next post for the new version. AIlso removed a short tick bug when the timer starts. If you have downloaded the program, please do so again.

 

IanM

Edited by IanM

Share this post


Link to post
Share on other sites

Another way to count 5mS is to keep track of how many uS have passed.

void interrupt(void)
{
t+=256;			 //count uS
if(t>5000)		  //has 5mS passed?
{				   //1 second routine and if value of u is 60, it would count 60 seconds
	t-=5000;		//subtract 5mS
	t1++;		   //Increaments after 20 interrupts i.e. 5ms
	if(t1>199)
	{
		t1=0;
		t2++; //Increaments after 200X20 interrupts i.e. 1 sec 
		if(t2>u)
		{
			t2=0;
			dist--;
		}
	}
}
intcon.2=0; //TOIF
}

 

Mike.

Share this post


Link to post
Share on other sites

Hey Wizards,

Thanks again for all your suggestions and snippets. IanM is doing his best to civilise me and sober me up. I really feel lucky. Well Ian, some things about the code you wrote:

1. The timer 0 after modified inhibits for 2 cycles and writes in the timer0 register at 3rd cycle. So we have to keep an offset of 3 cycles. I had tried to simulate with value 8 as well as 9 in timer0. The MPLAB SIM counted 1.004010 Seconds with value 8 in timer 0 and 1.000011 Seconds with value 9 in timer 0. So I suppose my thoery of 3 cycles offset might be right.

2. What is the significance of using ROM char array instead of normal unsigned char array? I suppose ROM means flash. So would writting to ROM reduce usage of RAM?

3. You have initialised and enabled the timer 0 interrupt in init(). Wont this trigger the interrupt even while in programming mode (aka eeprom_load() routine), which we might not want?

I dont know what I pointed out was right or wrong, but I am sure the we all would discuss it out.

Regards

Shree

Share this post


Link to post
Share on other sites

Hi Shree,

 

As you are using a 16F877A device, why not try using timer 2 and the preload register. That way you should be an interrupt every 250us and just let the timer free run and do its own thing. If you use the x4 post scaler the interrupts can arrive at 1ms intervals.

 

Worth a try dude.

 

Cheers

 

Reynard

Share this post


Link to post
Share on other sites
As you are using a 16F877A device, why not try using timer 2 and the preload register. That way you should be an interrupt every 250us and just let the timer free run and do its own thing. If you use the x4 post scaler the interrupts can arrive at 1ms intervals.

Personally I would go for this suggestion myself, but maybe Shree has other plans for timer 2 . . . .

The task *CAN* be performed by timer 0, its not the best choice but can be made to work. Lets see what Shree wants to do.

 

... Well Ian, some things about the code you wrote:

1. The timer 0 after modified inhibits for 2 cycles and writes in the timer0 register at 3rd cycle. So we have to keep an offset of 3 cycles. I had tried to simulate with value 8 as well as 9 in timer0. The MPLAB SIM counted 1.004010 Seconds with value 8 in timer 0 and 1.000011 Seconds with value 9 in timer 0. So I suppose my theory of 3 cycles offset might be right.

Seems so. Thank you for checking it. I asked if it should be 8 and did not change it as I wasn't certain myself.

2. What is the significance of using ROM char array instead of normal unsigned char array? I suppose ROM means flash. So would writing to ROM reduce usage of RAM?

The map of lit segments for each digit 0-9 NEVER changes so it doesn't need to be in RAM. If it is in ROM it saves about ten bytes of RAM. ROM in this case means program memory, which for this PIC is FLASH. If we were using a PIC16C series chip it would mean real EPROM (apart from the misnamed flash PIC16C84). Generally it is meaningless to write to ROM in a program, It just can't be done. Some PICS DO have the capability to write to FLASH by an extension of the EEPROM access mechanism but it does not involve ROM arrays like this one in any way.

3. You have initialised and enabled the timer 0 interrupt in init(). Wont this trigger the interrupt even while in programming mode (aka eeprom_load() routine), which we might not want?

No, the original write_eeprom() code disabled interrupts and 'time_left' is not used anywhere in eeprom_load() so it really doesn't matter there. There is a *BIG* problem I have found and fixed, that write_eeprom() didn't restore the previous GIE state when it returned. This would totally kill the timing if you change the period and not recover till you reset the PIC. I've just fixed that and put in symbolic names. Thank you for drawing my attention to it.

 

Revised Eeprom877A.h - now preserving interrupt enable status, compatible with old version for *most* programs that don't get too 'clever' with GIE

/* A Header file which would do reading and writing EEPROM in 16F877A */
/* Now with symbolic names and preserving Interrupt Enable status	 */
unsigned char read_eeprom(unsigned char read_addr)
{		
eeadr=read_addr;
eecon1.EEPGD=0; //resetting EEPGD bit to access data memory
eecon1.RD=1; //Enabling Read operation (RD bit high)
return (unsigned char)eedata;
}

void write_eeprom(unsigned char write_addr, unsigned char write_data)
{
bit saved_gie;

saved_gie=intcon.GIE; //Save interrupt state
if (saved_gie) 	//DANGER: interrupts are on
{
	intcon^=1<<GIE; //Toggle off interrupts
	if (intcon.GIE) saved_gie=intcon.GIE=0;	//EXTREME DANGER: Interrupts STILL on 
						//so some ISR turned them off after we 
						//saved GIE!  Fix it ASAP.
}
while(eecon1.WR!=0); //loop if there was a write in progress till its done!
pir2.EEIF=0; 	//EEIF flag bit is reset in case there was any write previously 
eeadr=write_addr;
eedata=write_data;
eecon1.EEPGD=0;	//EEPGD bit to access data memory
eecon1.WREN=1; 	//Enabling write operation by setting WREN bit
eecon2=0X55; 	//Requisite 'magic numbers'
eecon2=0XAA; 	//These are kinda passwords
eecon1.WR=1; 	//enabling WR bit
while(eecon1.WR!=0); //loop till it clears and we are done
eecon1.WREN=0; 	//Clear WREN bit to disable writes for safety.
pir2.EEIF=0; 	//EEIF flag bit is cleared 
		//so *this* write does not cause an interrupt
intcon.GIE=saved_gie; //restore interrupt enable state
}

 

I dont know what I pointed out was right or wrong, but I am sure the we all would discuss it out.

 

*GOOD* points and it shows you are studying the code. You were right more often than not :-)

 

Try it with the new Eeprom877A.h and see if I have any other stupid bugs!

If you have kept my "" around its name in the #include, it can and should go in the same directory as the program. The <> you had implies it is in the compiler include directory. Now, it must always be after #include <system.h>.

 

Revised Eeprom877A.h

 

Ian

Share this post


Link to post
Share on other sites

Hello Wizards,

I understand from the little reading that, microchip would prefer real time based operations performed with Timer1 as it can be driven with an external 32Khz(typically) crystal oscillator to draw an independent and rather accurate timing. But the question is how far accurate can we go with timer 0. No doubt Reynard's suggestion is worth trying and rather would be more accurate, but I just wanted to test the limit with timer0. Ofcourse there are many other nicer ways to do that, but after this project (infact due to huge help from all of you), I would never have any doubts about Timer0 and its modus operndi. Maybe this sounds stupid professionally, doing things which would not be 100% accurate, but I am very passionate about it. I thoroughly respect Reynard's and Ian's suggestions and I am going to implement the same thing with timer2, but only after I test timer0's limits. If Reynard and Ian, both of you think that I am wasting my time, please let me know, because you guys are lot more experienced.

Regards

Shree

Share this post


Link to post
Share on other sites

Doing it the way I posted earlier will give you a completely accurate timer even if you have other interrupts running.

 

Mike.

Share this post


Link to post
Share on other sites
Doing it the way I posted earlier will give you a completely accurate timer even if you have other interrupts running.

 

Mike.

Agreed. That is the classic way to use Timer 0 for accurate timing. The only problem is the jitter as it can be up to 255 Timer 0 clocks late firing on any particular tick as unless we are very lucky with the ratio of Timer 0 period to tick length, Tick/Period is not an integer. The problem is worse if we use the prescaler to increase the period with the intention of reducing the interrupt load.

 

I am working on some code to drive a 100 Hz 'tick' routine that can be used for stopwatch quality timing, display multiplexing and key or button debouncing without using Timer 1 or Timer 2 resources.

 

The method I am working on is to synchronise to the timer before updating it to make the counts lost in the update inhibit delay predictable and only update it in 1 of N interrupts with a fairly long period (/16 prescaler) to maintain good timing acuracy. It will also check if the update will overflow the counter as other interrupts or critical sections may caue high latency. If so it will defer the update to the next suitable tick to maintain accuracy.

 

The reasons for this choice to use an inferior timer are many. Apart from those already discussed, Its a challenge, some PICS such as the PIC16F84 don't have any other timers and I have plans for a project that needs to use the CCP/PWM module for frequency measurement and to update an analog meter display that will fully occupy Timers 1 and 2 so the hour meter *MUST* either run on Timer 0 or I'll be forced to use an external RTCC chip.

 

... I understand from the little reading that, microchip would prefer real time based operations performed with Timer1 as it can be driven with an external 32Khz(typically) crystal oscillator to draw an independent and rather accurate timing. But the question is how far accurate can we go with timer 0. ...

... If Reynard and Ian, both of you think that I am wasting my time, please let me know, because you guys are lot more experienced.

Regards

Shree

There are some serious cautions for 18 pin PIC16 series chips against using an external crystal on Timer 1 with /MCLR disabled *AND* the internal oscillator enabled. Just thought I'd warn you.

 

I am in favor of both this project and you learning the other timers as soon as you can after we have this one done.

 

Gotta dash, work calls . . .

IanM

Share this post


Link to post
Share on other sites
A few years back we talked about the fact that TIMER 0 is not a good choice for a clock as there is an inherent error in reloading the counter. Since you are using a device that has newer timers (TIMER 2) you should use the new timer since you can set the compare value once and just recieve an interrupt at the desired rate. TIMER 0 is fine for keyboad debouncing but not for real-time clocks.

 

I attached Clock.c and Clock.h that implements a clock using timer 2 that I used for a timelapse photo machine ( http://www.tedrossin.x10hosting.com/Electronics/Pic/Pic.html ) . The code includes a fine adjust to calibrate out crystal errors. The parts per million error on a cyrstal > 1MHz is usually pretty bad (20 to 50) such that your clock will lose or gain 10 to 20 seconds a day. A tuned 32KHz cyrstal connected to be a timer source is another way to get a good timebase. These cyrstals have very low PPM errors.

This code has more than you want but should at least show you how to modify your code to use timer 2. If it is confusing, never mind as what you have may be good enough if you don't plan on leaving it running for more than a few days.

 

If you want the ulitimate in accuracy on the cheap, go for sampling the power line (dropped down in voltage of course) to get a very reliable 60 or 50 Hz depending on where you live. The power companies control the beats per day to keep good old synchonous clock motors on the right time.

 

I tried to search for the thread on this subject but could not find it. We had a blast a few year back about this issue.

 

Hi Trossin,

I disagree that you can't get accurate timing with tmr0. As of the crystal error is concerned you may be right. But every one uses a crystal.

The sample code I had given in the thread "programming pic micro using boostc" is correct. I have checked it up using mplab stopwatch. And I have used it

as a real time clock & it works fine. Build the program in mplab sim, make a break point in the first line in the ISR, reset the stop watch on the first interrupt

& run it several times & you will see that the stop watch show 250us every time. The code tmr0 +=9 ; is all you need to add in the isr. Its a very simple code & it works.

Shree, the error u might have got could be because u did'nt reset the timer at the first interrupt. The first interrupt will have a over head of the initial port setting & register config settings.

Where u don't need to measure time intervals of 250us, Pommie's code is better because your code doesn't get interrupted often.

 

Regards

Raghunathan.

Edited by ra68gi

Share this post


Link to post
Share on other sites
Hi Shree,

 

As you are using a 16F877A device, why not try using timer 2 and the preload register. That way you should be an interrupt every 250us and just let the timer free run and do its own thing. If you use the x4 post scaler the interrupts can arrive at 1ms intervals.

 

Worth a try dude.

 

Cheers

 

Reynard

 

Hi Reynard, Its not called preload register but period register. :unsure: timer2 is used for the pwm module & its value is compared with PR2 register and the duty cycle value( stored in a crazy way in the PIC). One can activate the interrupt for tmr2 PR2 match.

 

Regards

Raghu.

Share this post


Link to post
Share on other sites
Shree, the error u might have got could be because u did'nt reset the timer at the first interrupt. The first interrupt will have a over head of the initial port setting & register config settings.

Where u don't need to measure time intervals of 250us, Pommie's code is better because your code doesn't get interrupted often.

Hello Mr. Raghunathan,

Well I had posted the error in the code earlier, and now I get a good timing (1.000000 secs) on MPLAB sim. About the first interrupt overhead, I had seen the problem and this could be sorted out by preloading the timer0 just before activating interrupts. The value could be calibrated while simulating. My preload value of 44 yeilded me a perfect 1 sec (atleast on mplab sim).

Regards

Shree

 

P.S: Why dont we make a pinned topic of web addresses for code snippets/harware projects which are based exclusively on boostC by eminent programmers like ted and many others who have worked around pic and done many projects, which they want to share with others. Anybody can give a link to their own web address too where he/she has done some job with pics. It might be of quite help. This clicked me when I saw ted rossin's site. What we could do is just provide link to these sites, but only condition would be all (or most) of the coding must be done in boostC.

Share this post


Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...