|
|
View previous topic :: View next topic |
Author |
Message |
Alphada
Joined: 19 Jun 2017 Posts: 27
|
nt way (Bat Life) to generate 1Hz interrupt PIC16 |
Posted: Sun Jun 25, 2017 1:59 pm |
|
|
Need guidance with some Timer Terms.
Saw some post suggesting to enable CTC mode but couldn't find anything about it in the CCS manual.
I'm actually working on a 16f1615 chip i need to generate 1 second interrupt using internal oscillator running at 1Mhz.
I'm actually using this code:
Code: | //RTC variables
#define XTAL_FREQUENCY GobalFreQ
#define TIMER1_FREQUENCY (XTAL_FREQUENCY / 4) // 1 clock tick = 1 instr. cycle = crystal frequency / 4
#int_TIMER1
void TIMER1_isr()
{
Ticker -= 65536; // Decrement ticker by clocks per interrupt
if ( Ticker < 65536 ) // If second has expired
{ Ticker += TIMER1_FREQUENCY; // Increment ticker by clocks per second
if(seconds==0xFFFF){
//if(seconds % 0x78==0){
//Aqui se marca un reset de la variable
overflow=1;
}
seconds++; // Increment number of seconds
}
}
void Initialize_RTC(void){
Ticker = TIMER1_FREQUENCY; // initialize clock counter to number of clocks per second
setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 ); // initialize 16-bit Timer1 to interrupt
// exactly every 65536 clock cycles
// (about 76 times per second)
enable_interrupts( INT_TIMER1 ); // Start RTC
} |
Which works pretty well for the precision needed, but the interrupt occurs if I'm not wrong around 3.81 times in a second, I am trying to boost performance cause I'm trying to save battery life.
I used a calculator from MikroC to produce this code:
Code: | //Timer1
//Prescaler 1:4; TMR1 Preload = 3036; Actual Interrupt Time : 1
//Place/Copy this part in declaration section
void InitTimer1(){
T1CON = 0x21;
TMR1IF_bit = 0;
TMR1H = 0x0B;
TMR1L = 0xDC;
TMR1IE_bit = 1;
INTCON = 0xC0;
}
void Interrupt(){
if (TMR1IF_bit){
TMR1IF_bit = 0;
TMR1H = 0x0B;
TMR1L = 0xDC;
//Enter your code here
}
} |
I am very new working with CCS and PICs if im not mistaken T1CON,TMR1IF_bit,TMR1H,TMR1L, TMR1IE_bit, INTCON are registers.
How do I access them in CCS, and also which code would be more efficient?
and if so would be worth the change? the thing about the second option in my ignorance feels like more implemented in the hardware, but after every cycle there is a need to readjust the overflow point.
I tried tons of codes and also search in the forum already for RTC but what i need is some guidance since I'm pursuing efficiency. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9246 Location: Greensville,Ontario
|
|
Posted: Sun Jun 25, 2017 2:24 pm |
|
|
Ok, if you're open to options...
1) use an RTC like the DS1307. It will generate a 1Hz interrupt and is power frugal.....
2) check the 'code library' here for the 'software RTC'. It's accurate and a good 'base' to start with.
3) If possible choose a 'binary' xtal, something like 4.096 MHz, 1.024 Mhz, 32768 KHz. These all will divide down into a perfect 1Hz 'clock'. The problem with say 1MHz xtal is that the PIC cannot divide it into a perfect 1Hz....it's a 'binary thing'...
Jay |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Sun Jun 25, 2017 2:40 pm |
|
|
You'll find the difference in power consumption will be astonishingly small. Generally other things (like not biasing signals properly), will cost far more.
I've got some remote control systems that use a 2032 coin cell, and in some cases have been running for a couple of years on one such battery, waking several times a second....
However if you want to try waking every seconds (it won't be quite as accurate), use:
Code: |
#int_TIMER1
void TIMER1_isr()
{
set_timer1(get_timer1()+3036); //advance timer 3036 counts
seconds++; // Increment number of seconds
}
void Initialize_RTC(void){
setup_timer_1( T1_INTERNAL | T1_DIV_BY_4 );
// initialize 16-bit Timer1 to interrupt
// exactly every 1048576 clock cycles
// your original comment here is wrong.
enable_interrupts( INT_TIMER1 ); // Start RTC
}
|
|
|
|
Alphada
Joined: 19 Jun 2017 Posts: 27
|
|
Posted: Sun Jun 25, 2017 2:42 pm |
|
|
temtronic wrote: | Ok, if you're open to options...
1) use an RTC like the DS1307. It will generate a 1Hz interrupt and is power frugal.....
2) check the 'code library' here for the 'software RTC'. It's accurate and a good 'base' to start with.
3) If possible choose a 'binary' xtal, something like 4.096 MHz, 1.024 Mhz, 32768 KHz. These all will divide down into a perfect 1Hz 'clock'. The problem with say 1MHz xtal is that the PIC cannot divide it into a perfect 1Hz....it's a 'binary thing'...
Jay |
Thx for the suggestions. The only problem is that I'm actually using all the pins, but I was considering those: external crystal at 32768 KHz and the DS1307. My project actually works decently but my main motivation is to learn.
Now after some research I noticed in my second option that the interrupt is called (250000 / 4) times / sec which in my ignorance would be less efficient than my current code, even when it's just a single bit check "if (TMR1IF_bit){" the overhead caused from calling interrupt would beat my purpose to save power. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9246 Location: Greensville,Ontario
|
|
Posted: Sun Jun 25, 2017 4:39 pm |
|
|
more things to consider...
Microchip has an application note ( AN 69 ??? ), decades ago about PICs, batteries and how to save energy. It should be a 'must read' for everyone who use PICs, batteries and want to save energy.
I'm sure it can be found on their website.
Another point...kinda obvious, use a BIGGER battery. It's a case where bigger IS better ! A couple of AA cells will far exceed what a CRxxxx can supply and consider operating temperatures. batteries, like us humans, do NOT like the cold. Add a super cap.
As for PICs.. be sure your code sets any unused pin to hi or low, run at slowest clock speed (yet still do the job), use 1 LED instead of 3 for displaying of 'status'. If LCD module is used, the cheap 5 volt ones can be run off 3 volts IF you add a 'charge pump'. If LEDs are used, use ultra low current ones and if need be 'Charlieplex' them.
Jay |
|
|
Alphada
Joined: 19 Jun 2017 Posts: 27
|
|
Posted: Sun Jun 25, 2017 6:23 pm |
|
|
Ttelmah wrote: | You'll find the difference in power consumption will be astonishingly small. Generally other things (like not biasing signals properly), will cost far more.
I've got some remote control systems that use a 2032 coin cell, and in some cases have been running for a couple of years on one such battery, waking several times a second....
However if you want to try waking every seconds (it won't be quite as accurate), use:
Code: |
#int_TIMER1
void TIMER1_isr()
{
set_timer1(get_timer1()+3036); //advance timer 3036 counts
seconds++; // Increment number of seconds
}
void Initialize_RTC(void){
setup_timer_1( T1_INTERNAL | T1_DIV_BY_4 );
// initialize 16-bit Timer1 to interrupt
// exactly every 1048576 clock cycles
// your original comment here is wrong.
enable_interrupts( INT_TIMER1 ); // Start RTC
}
|
|
Thx for taking time to answer I really appreciate it. I will try it and also will read the application note you suggested, and yes that original comment is wrong since the original code was suppose to work at 20 MHz if I'm not mistaken. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Mon Jun 26, 2017 3:39 am |
|
|
In terms of battery life, going with a chip with extra pins, and having an external source, or an external oscillator is the way to go.
The problem is that the thing that draws the most power on the PIC is the CPU oscillator. In order to use the interrupt as you do, this has to be running....
Internally, the external oscillator available for Timer1, uses a lot less power than the main oscillator. So using a crystal on this, and stopping the master oscillator, gives low power. alternatively, stopping the PIC completely and waking it using a signal for an external RTC, again gives a lot less power consumption.
At 1Mhz, your chip will draw about 350uA.
Alternative. Turn the clock off when waiting.
Set timer1, to clock of the LFINTOSC, instead of the master oscillator. This runs at 31KHz. So set the prescaler to 1, and ensure that synchronous mode is not selected. Then on each interrupt advance the timer 34536 cycles.
Unless you need an interrupt handler, don't have one. Turn off the global interrupt flag. Then you can put the CPU to sleep, and it'll wake in a second. Then clear the interrupt, set the timer forward, do your 1/second tasks, and loop round to sleep again.
You should be able to get the average consumption under 10uA like this. |
|
|
Alphada
Joined: 19 Jun 2017 Posts: 27
|
|
Posted: Mon Jun 26, 2017 7:51 am |
|
|
Ttelmah wrote: | In terms of battery life, going with a chip with extra pins, and having an external source, or an external oscillator is the way to go.
The problem is that the thing that draws the most power on the PIC is the CPU oscillator. In order to use the interrupt as you do, this has to be running....
Internally, the external oscillator available for Timer1, uses a lot less power than the main oscillator. So using a crystal on this, and stopping the master oscillator, gives low power. alternatively, stopping the PIC completely and waking it using a signal for an external RTC, again gives a lot less power consumption.
At 1Mhz, your chip will draw about 350uA.
Alternative. Turn the clock off when waiting.
Set timer1, to clock of the LFINTOSC, instead of the master oscillator. This runs at 31KHz. So set the prescaler to 1, and ensure that synchronous mode is not selected. Then on each interrupt advance the timer 34536 cycles.
Unless you need an interrupt handler, don't have one. Turn off the global interrupt flag. Then you can put the CPU to sleep, and it'll wake in a second. Then clear the interrupt, set the timer forward, do your 1/second tasks, and loop round to sleep again.
You should be able to get the average consumption under 10uA like this. |
Thx for the tips im really looking fordward to the DS1307 or the crystal at 32KHZ.
but my project is working decently ATM I just want to test stuff and learn,
for example i want to test this code.
but is not working and i don't know why
Code: | #include <16F887.h>
#device ADC=10
#use delay(internal=1000000,restart_wdt)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8,stream=PORT1)
#byte T1CON = getenv("SFR:T1CON")
#byte TMR1L = getenv("SFR:TMR1L")
#byte TMR1H = getenv("SFR:TMR1H")
#byte INTCON = getenv("SFR:INTCON")
#BIT TMR1IF_bit = getenv("BIT:TMR1IF")
#BIT TMR1IE_bit = getenv("BIT:TMR1IE")
int16 LastS=0;
int16 seconds=0;
//Timer1
//Prescaler 1:4; TMR1 Preload = 3036; Actual Interrupt Time : 1
//Place/Copy this part in declaration section
void InitTimer1(){
T1CON = 0x21;
TMR1IF_bit = 0;
TMR1H = 0x0B;
TMR1L = 0xDC;
TMR1IE_bit = 1;
INTCON = 0xC0;
}
#int_TIMER1
void TIMER1_isr()
{
if (TMR1IF_bit){
TMR1IF_bit = 0;
TMR1H = 0x0B;
TMR1L = 0xDC;
//Enter your code here
seconds++;
}
}
void main(){
setup_oscillator(OSC_1MHZ | OSC_INTRC);
enable_interrupts(GLOBAL);
while(true){
if(seconds>LastS){
printf("Segs: %lu\n\r", seconds);
LastS=seconds;
}
}
}
|
btw tested yours
Code: | #int_TIMER1
void TIMER1_isr()
{
set_timer1(get_timer1()+3036); //advance timer 3036 counts
seconds++; // Increment number of seconds
}
void Initialize_RTC(void){
setup_timer_1( T1_INTERNAL | T1_DIV_BY_4 );
// initialize 16-bit Timer1 to interrupt
// exactly every 1048576 clock cycles
// your original comment here is wrong.
enable_interrupts( INT_TIMER1 ); // Start RTC
} |
and it works nicely tested at 17 minutes and didn't lost any second, but i wish to know why i cant make the other code work i think it has to be with the "if (TMR1IF_bit){"
the code was made for MikroC but i wanted to port to CCS just for learning purposes if i manage to make it work ill do a realife test with a 5mAh battery just to test running time. and will post result of the 3 codes. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Mon Jun 26, 2017 8:23 am |
|
|
You don't actually need that test.
If the handler is being called, that bit has already been tested for you by the compiler. The Timer1 handler is _only_ called if the Timer1 interrupt flag is set.
The reason it is not working though, is that you are not enabling the timer1 interrupt.....
You don't call their init_timer function, which enables this interrupt. |
|
|
Alphada
Joined: 19 Jun 2017 Posts: 27
|
|
Posted: Mon Jun 26, 2017 8:25 am |
|
|
Ttelmah wrote: | You don't actually need that test.
If the handler is being called, that bit has already been tested for you by the compiler. The Timer1 handler is _only_ called if the Timer1 interrupt flag is set.
The reason it is not working though, is that you are not enabling the timer1 interrupt..... |
YES you were right again, i forgot to call InitTimer1()... i feel very stupid after i figured it out and found you noticed that too.
:s |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1358
|
|
Posted: Mon Jun 26, 2017 11:01 am |
|
|
Side issue:
In your example, both seconds and LastS are 16 bit variables. On an 8 bit PIC this can lead to trouble. Consider this pseudo assembly. It takes at least two instructions to copy a 16 bit variable on an 8 bit processor
// LastS = seconds //this isn't real assembly, just pseudo code
MOV seconds_byte0, LastS_byte0
MOV seconds_byte1, LastS_byte1
Now what happens if the timer interrupt happens between those two instructions when the value seconds is 0x00FF?
MOV seconds_byte0, LastS_byte0 => puts the 0xFF into byte 0
Interrupt happens and increments seconds to 0x0100
Interrupt leaves
MOV seconds_byte1, LastS_byte1 => puts the 0x01 into byte 1
Now LastS contains the value 0x01FF instead of 0x00ff (or even 0x0100)
You either need to use atomic sized variables (8bit for 8bit processors), which limits you to 0-255 on your count or you need to do something to guard against that issue.
Common solutions:
1. disable interupts - this can cause drift in some implementations, but it should be very minute.
2. get your time using a loop
Code: |
volatile int16 temp;
...
do{
temp = seconds;
}while(temp != seconds);
// after this loop, you know that temp is not corrupted.
if(temp > LastS){
printf("Segs: %lu\n\r", temp);
LastS = temp;
}
|
This method only works for reading variables. If you wish to write variables, you'll need to disable interrupts or use an atomic sized variable. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19552
|
|
Posted: Mon Jun 26, 2017 11:26 am |
|
|
Except for the initialisation (which is before the interrupt is enabled), he is writing inside the interrupt handler. |
|
|
Alphada
Joined: 19 Jun 2017 Posts: 27
|
|
Posted: Mon Jun 26, 2017 12:18 pm |
|
|
jeremiah wrote: | Side issue:
In your example, both seconds and LastS are 16 bit variables. On an 8 bit PIC this can lead to trouble. Consider this pseudo assembly. It takes at least two instructions to copy a 16 bit variable on an 8 bit processor
// LastS = seconds //this isn't real assembly, just pseudo code
MOV seconds_byte0, LastS_byte0
MOV seconds_byte1, LastS_byte1
Now what happens if the timer interrupt happens between those two instructions when the value seconds is 0x00FF?
MOV seconds_byte0, LastS_byte0 => puts the 0xFF into byte 0
Interrupt happens and increments seconds to 0x0100
Interrupt leaves
MOV seconds_byte1, LastS_byte1 => puts the 0x01 into byte 1
Now LastS contains the value 0x01FF instead of 0x00ff (or even 0x0100)
You either need to use atomic sized variables (8bit for 8bit processors), which limits you to 0-255 on your count or you need to do something to guard against that issue.
Common solutions:
1. disable interupts - this can cause drift in some implementations, but it should be very minute.
2. get your time using a loop
Code: |
volatile int16 temp;
...
do{
temp = seconds;
}while(temp != seconds);
// after this loop, you know that temp is not corrupted.
if(temp > LastS){
printf("Segs: %lu\n\r", temp);
LastS = temp;
}
|
This method only works for reading variables. If you wish to write variables, you'll need to disable interrupts or use an atomic sized variable. |
Thx for the tip but that code is just to test the timer interrupt not really the final code just to test it. but thanks anyways for the tips. |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1358
|
|
Posted: Mon Jun 26, 2017 12:34 pm |
|
|
Ttelmah wrote: | Except for the initialisation (which is before the interrupt is enabled), he is writing inside the interrupt handler. |
Yep. I was just tossing the blurb about writing at the end incase they decide to write to it later. In general, it can still be an issue with reads as well. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|