|
|
View previous topic :: View next topic |
Author |
Message |
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
Using sleep() and so on |
Posted: Thu Feb 23, 2006 10:32 am |
|
|
Hi All!
Some code I am (re)writing has a lot of time delays in it. Rather than go round a loop wasting power it would be nice to be able to sleep/idle (not sure which) the processor instead to save power (which is at an absolute premium).
My Xtal clock is already at 100kHz to save power, but being able to stop everything for periods of say 50ms up to 59s would be great. What I would like to write is something like:
void ShutdownTime_ms(long Time)
Which would give a theoretical range of 1ms to 65535ms.
I have spent an afternoon trying to write a 1 second delay as a starter, but all I could get to work was the WDT cutting in, not Timer0 or Timer1.
Here's some example code - which also doesn't work. It should toggle the whole Port A output pins. If I enable the watchdog it cuts in as expected (only visible here if you swap the output_a statements round).
I was using MPLAB to simulate it, and assumed it was lying to me due to poor timer simulation. But blowing it into a chip and putting it on the logic analyser shows it wasn't lying to me....
Code: | // Development conducted using:
// CCS PCM C Compiler, Version 3.236
// MPLAB v7.30
//
// device specific header file
#include "18F2520.H"
//********************** Common fuse settings ********************************
#fuses NOLVP // *----------------- No low voltage programming
//#fuses WDT // *----------------- Watchdog timer enabled
#fuses PUT // *----------------- Power-up timer enabled
#fuses NOPROTECT // *----------------- No Code protect
#fuses NOBROWNOUT // *----------------- No reset on Brownout detection
#fuses LP // *----------------- Low power mode
//#fuses WDT64
//****************************************************************************
// Want to delay for 1000ms whilst sleeping.
// PIC timers count 'up' so that's why the calculation is as it is.
// Timer1
// 100kHz clock => instruction cycle of 40us
#define DELAY_FOR_1s (65536 - 25000) // 25000 ~= (1000ms / 40us)
void OneSecondDelay(void)
{
// Setup the timer interrupt
//
setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 );
set_timer1(DELAY_FOR_1s);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
// Sleep for a second...
sleep();
disable_interrupts(INT_TIMER1);
}
void main(void)
{
while (TRUE)
{
output_a(0);
OneSecondDelay();
output_a(255);
OneSecondDelay();
}
} |
Can anybody give me an idea of how I should _really_ be coding this?
TIA
Nige
PS Earlier I tried umpteen combinations of INTCON/OSCTUNE/OSCCON/etc/etc flags but started to go mad, so tried the example above.....
_________________ No comment! (Can't think of anything interesting to say!) |
|
|
Ttelmah Guest
|
|
Posted: Thu Feb 23, 2006 10:46 am |
|
|
When the chip is asleep, it's internal timers stop. You can use a timer to wake from sleep, if you use the timer with an external clock.
If you look in the chip's data sheet, it provides a list of what events can wake it from sleep. For the timers, you will find the comment that Timer1, or Timer3, can wake you from sleep, but that they _must_ be running as an 'asynchronous counter'. For Timer1, this is 'T1_EXTERNAL' mode.
The other possible modes (T1_INTERNAL, and T1_EXTERNAL_SYNC), are both dependant on the internal clock running, and will not work in a sleep.
Best Wishes |
|
|
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
|
Posted: Thu Feb 23, 2006 11:25 am |
|
|
Thanks Ttelmah,
I had suspected this may be the reason, but the datasheet inferred that Timer1 would keep running if enabled (sec 3.3 p37), but I guess that meant if you had a 32kHz watch crystal attached...
One of the many flag-setting attempts I had tried to get the internal 31kHz RC clock running and use that, but it was a bit beyond me...
It's annoying that the WDT time cannot be dynamically changed in the 18F series, otherwise a combination of binary multiples of WDT timeouts could be used to build timeouts with 18ms increments (plus a fair bit of overhead).
Nige _________________ No comment! (Can't think of anything interesting to say!) |
|
|
Brian S
Joined: 06 Sep 2005 Posts: 13
|
|
Posted: Thu Feb 23, 2006 3:13 pm |
|
|
If relatively low timing accuracy for your 1 second tick is acceptable,
you might set the WDT for 576mS, then on WDT Wake, run a timer to
wait out the required ~424 mS.
I believe there are still oscillator/divider chips available for watch
/clock apps; they run on 32768 crystals and output 1 tick/Sec. They're
optimised for low power consumption. The 1 tick/Sec could be used
as a Sleep/Wake interrupt...and are of course quite accurate. |
|
|
mpfj
Joined: 09 Sep 2003 Posts: 95 Location: UK
|
|
Posted: Fri Feb 24, 2006 4:11 am |
|
|
Ttelmah wrote: | When the chip is asleep, it's internal timers stop. |
Since you're using an 18F2520, you could always put the processor into IDLE mode, rather than SLEEP mode.
Code: | bit_set(OSCCON, OSCCON_IDLEN_BIT);
sleep();
|
Checking the datasheet, you'll see that all the peripherals keep running ... just the CPU portion is turned off.
Hope this helps |
|
|
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
|
Posted: Fri Feb 24, 2006 6:22 am |
|
|
Hi folks,
I had hoped someone would come up with the idle/sleep thing as that was one of the many things I tried yesterday....
I tried setting the IDLEN bit today using:
Code: |
#byte OSCCON = 0xFD3
#bit IDLEN = OSCCON.7
|
and
...in the code.
Didn't work I'm afraid. Sorry mpfj.
@Brian. No room for a divider and in any case I could use a 32kHz crystal on T1 with an 18F2520 - if I had the space. Which I don't....tiny size and tiny power are the only two things that count here. Oh, and they're designed for a 36.7 deg C /98.6 deg F thermally controlled oven usually. Industrial temp spec is what we're after.
Just to check I wasn't going mad, I did this:
Code: |
#int_timer0
void Timer0Expired(void)
{
set_timer0(DELAY_FOR_1s);
}
void main(void)
{
setup_timer_0( RTCC_INTERNAL | RTCC_DIV_2 );
set_timer0(DELAY_FOR_1s);
enable_interrupts(INT_TIMER0);
enable_interrupts(GLOBAL);
while (TRUE)
{
IDLEN = 1;
// sleep(); // Without; this timer0 works ok. With; it doesn't
}
}
|
Which gives me roughly 1s on timer 0. But as soon as I put the sleep() in, it stops working and the WDT kicks in at ~590s.
This would seem to infer the Idle isn't working okay. Or that Timer0 stops in sleep? Perhaps I should go back to Timer1? Does that keep going in sleep()? Is there a fuse I should be setting to enable these features (the ones I am using are the same as at the top of this post)?
Code: |
//////// Program memory: 16384x16 Data RAM: 1536 Stack: 31
//////// I/O: 25 Analog Pins: 10
//////// Data EEPROM: 256
//////// C Scratch area: 00 ID Location: 2000
//////// Fuses: LP,XT,HS,RC,EC,EC_IO,H4,RC_IO,PROTECT,NOPROTECT
//////// Fuses: BROWNOUT_NOSL,BROWNOUT_SW,NOBROWNOUT,BROWNOUT,WDT1,WDT2,WDT4
//////// Fuses: WDT8,WDT16,WDT32,WDT64,WDT128,WDT,NOWDT,BORV20,BORV27,BORV42
//////// Fuses: BORV45,PUT,NOPUT,CPD,NOCPD,NOSTVREN,STVREN,NODEBUG,DEBUG
//////// Fuses: NOLVP,LVP,WRT,NOWRT,WRTD,NOWRTD,IESO,NOIESO,FCMEN,NOFCMEN
//////// Fuses: PBADEN,NOPBADEN,CCP2B3,CCP2C1,WRTC,NOWRTC,WRTB,NOWRTB,EBTR
//////// Fuses: NOEBTR,EBTRB,NOEBTRB,CPB,NOCPB,LPT1OSC,NOLPT1OSC,MCLR,NOMCLR
//////// Fuses: XINST,NOXINST,INTRC,INTRC_IO,WDT256,WDT512,WDT1024,WDT2048
//////// Fuses: WDT4096,WDT8192,WDT16384,WDT32768
|
Oh well, off on hols in an hour...so I won't be able to reply for a week or so if someone does have an idea.
Thanks so far.
Nige _________________ No comment! (Can't think of anything interesting to say!) |
|
|
mpfj
Joined: 09 Sep 2003 Posts: 95 Location: UK
|
|
Posted: Fri Feb 24, 2006 9:08 am |
|
|
I don't have a PIC18F2520, but I've just coded this and it works. It produces a 10Hz "toggle" on pin A0.
Code: | /* Idle tester */
#include <18f1320.h>
#opt 9
#fuses INTRC,NOBROWNOUT,BORV27,NOWDT,NOPROTECT,NOPUT,NOCPD,STVREN,NOLVP,NOWRT,NOWRTB,NOWRTC,NOWRTD,NOEBTR,NOEBTRB,NOCPB
#define FOSC 8000000
#use delay(clock=FOSC)
#define DEBUG_A0 PIN_A0
#byte PORTA = 0xF80
#use fast_io(a)
#byte PORTB = 0xF81
#use fast_io(b)
#byte PIR2 = 0xFA1
#define PIR2_TMR3IF_BIT 1
#byte OSCCON = 0xFD3
#define OSCCON_IDLEN_BIT 7
#define TIMER3_MODE T3_INTERNAL|T3_DIV_BY_8
#define TIMER3_PERIOD 0xffff - ((FOSC / 4) / 8) / 10 // 10Hz
// Timer 0 interrupt routine
#int_timer3
void timer3_isr(void) {
// reset timer 3 count
set_timer3(TIMER3_PERIOD);
// toggle output
output_toggle(DEBUG_A0);
}
void main(void) {
// init pic hardware
port_b_pullups(true);
setup_adc_ports(NO_ANALOGS);
setup_ccp1(CCP_OFF);
// init ports
PORTA = 0x00; // xxiiiii0 : DEBUG_A0
set_tris_a(0xfe);
PORTB = 0x00; // iiiiiiii :
set_tris_b(0xff);
// cardreader timeout
setup_timer_3(TIMER3_MODE);
// clear any pending timer interrupt
bit_clear(PIR2, PIR2_TMR3IF_BIT);
// enable timer interrupt
enable_interrupts(INT_TIMER3);
// enable all interrupts
enable_interrupts(GLOBAL);
// main loop ... loop forever
while (1) {
// now enter "idle" mode, not sleep mode.
// in "sleep" mode, the pic shuts down the CPU
// *and* the peripherals.
// in "idle" mode, the pic only shuts doen the
// CPU, thus allowing the timers and uart to
// carry on as required.
// set idle bit
bit_set(OSCCON, OSCCON_IDLEN_BIT);
// go to sleep ...
sleep();
}
}
|
|
|
|
Ttelmah Guest
|
|
Posted: Fri Feb 24, 2006 4:21 pm |
|
|
The key problem wth the earlier code, is that the watchdog period is shorter than the timer period being used. The watchdog times out first...
In fact in the second code, you can disable the global interrupts, and code as:
Code: |
// disable all interrupts
disable_interrupts(GLOBAL);
// main loop ... loop forever
while (1) {
// now enter "idle" mode, not sleep mode.
// in "sleep" mode, the pic shuts down the CPU
// *and* the peripherals.
// in "idle" mode, the pic only shuts doen the
// CPU, thus allowing the timers and uart to
// carry on as required.
//Give 0.1 second from here
set_timer3(TIMER3_PERIOD);
//ensure watchdog will not occur first
reset_wdt();
//Make sure the timer interrupt is clear
clear_interrupts(INT_TIMER3);
// set idle bit
bit_set(OSCCON, OSCCON_IDLEN_BIT);
// go to sleep ...
sleep();
output_toggle(DEBUG_A0);
}
|
This avoids the fairly large time overhead needed to get into the interrupt handler. the only timing error will be the Tcsd delay that will occur on wake-up.
Best Wishes |
|
|
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
|
Posted: Mon Mar 06, 2006 6:14 am |
|
|
Hi mpfj and Ttelmah,
Just got back from my hols to find your contributions. Copied and compiled both and put them through the MPLAB simulator, but neither work!!
Is _THIS_ where I am going wrong?? I am _assuming_ the MPLAB simulator simulates sleep/idle correctly? Should I have been compiling and blowing in to a PIC to start with and seeing what _really_ happens?
If so, then I guess I have learnt not to trust the MPLAB sim and sleep().
After lunch I'll try and get an 18F2520 version of one or both of your code blocks blown in to a _REAL_ chip!!
Doh!
Thanks again,
Nige _________________ No comment! (Can't think of anything interesting to say!) |
|
|
mpfj
Joined: 09 Sep 2003 Posts: 95 Location: UK
|
|
Posted: Mon Mar 06, 2006 6:27 am |
|
|
aopg64 wrote: | Is _THIS_ where I am going wrong?? I am _assuming_ the MPLAB simulator simulates sleep/idle correctly? |
Although I've not tested this, a quick look at the MPLAB SIM help files (under General Limitations) ...
Depending on device, the following are *** not simulated ***:
User ID memory.
Programmable Switch-Mode Controller (PSMC).
Brown-out detection (BOD) and low voltage detection (LVD).
Power saving modes.
Serial I/O (i.e., SSP including I2C and SPI). As a result, the SSPSTAT register has been made readable and writable.
USB and CAN.
Parallel Slave Port (PSP).
D/A converter (DAC) and Op Amp (OPA).
Quadrature Encoder Interface (QEI) of the Motion Feedback module.
Now, whether that means (a) they are not supported by the Simulator or (b) they cannot be simulated and just work as they are meant to, I'm not sure. The working is vague.
I do know that sleep and idle are not supported by the ICD2 Debugger, so it may be the same for the Simulator. |
|
|
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
|
Posted: Mon Mar 06, 2006 7:39 am |
|
|
Hi mpfj + ttelmah,
Just blew an 18F2520 with ttelmah's code (it was the one I had tried last on screen - no preferential treatment!)
It works.....so all this time I was believing the simulator (foolish of me I know) and assuming my code was completely wrong.
Bummer! Back to the drawing board! I guess this shows how important it is to write small test blocks of code and actually blow them into a device.
Thanks again.
Nige _________________ No comment! (Can't think of anything interesting to say!) |
|
|
aopg64
Joined: 17 Oct 2005 Posts: 28 Location: Hampshire, UK
|
|
Posted: Tue Mar 07, 2006 8:54 am |
|
|
Hi All,
Just in case anybody has a similar problem, I have eventually ended up with this:
Code: |
// Development conducted using:
// CCS PCM C Compiler, Version 3.236
// MPLAB v7.30
//
// device specific header file
#include "18F2520.H"
//********************** Common fuse settings ********************************
#fuses NOLVP // *----------------- No low voltage programming
#fuses NOWDT // *----------------- Watchdog timer enabled
#fuses PUT // *----------------- Power-up timer enabled
#fuses NOPROTECT // *----------------- No Code protect
#fuses NOBROWNOUT // *----------------- No reset on Brownout detection
#fuses LP // *----------------- Low power mode
#fuses NOMCLR
#define XTAL_SPEED 100000
#byte PIR2 = 0xFA1
#define PIR2_TMR3IF_BIT 1
#byte OSCCON = 0xFD3
#define OSCCON_IDLEN_BIT 7
#byte PORTA = 0xF80
#use fast_io(a)
#byte PORTB = 0xF81
#use fast_io(b)
#define TIMER3_MODE T3_INTERNAL|T3_DIV_BY_8
#define TIMER3_PERIOD 0xffff - ((XTAL_SPEED / 4) / 8) / 10 // 10Hz
//****************************************************************************
// Want to delay for 1000ms whilst sleeping.
// PIC timers count 'up' so that's why the calculation is as it is.
// Timer1
// 100kHz clock => instruction cycle of 40us
#define DELAY_FOR_1s (65536 - 12500) // 12500 ~= (1000ms / (40us *2))
//****************************************************************************
#int_timer0
void Timer0Expired(void)
{
set_timer0(DELAY_FOR_1s);
output_toggle(PIN_B0);
}
#int_timer1
void Timer1Expired(void)
{
set_timer1(DELAY_FOR_1s);
output_toggle(PIN_B1);
}
#int_timer2
void Timer2Expired(void)
{
set_timer2(DELAY_FOR_1s);
output_toggle(PIN_B2);
}
#int_timer3
void Timer3Expired(void)
{
set_timer3(DELAY_FOR_1s);
output_toggle(PIN_B3);
}
void main(void)
{
// init pic hardware
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_ccp1(CCP_OFF);
setup_ccp2(CCP_OFF);
// init ports
PORTA = 0x00; // xxiiiii0 : PIN_A0 low indicates 'asleep'
set_tris_a(0xfe);
PORTB = 0x00; // iiiioooo :
set_tris_b(0xf0);
// Timer0
setup_timer_0( RTCC_INTERNAL | RTCC_DIV_2 );
set_timer0(DELAY_FOR_1s);
enable_interrupts(INT_TIMER0);
// Timer1
setup_timer_1( T1_INTERNAL | T1_DIV_BY_8 );
set_timer1(DELAY_FOR_1s);
enable_interrupts(INT_TIMER1);
// Timer2
setup_timer_2( T2_DIV_BY_16,255,1 );
set_timer2(0);
enable_interrupts(INT_TIMER2);
// Timer3
setup_timer_3( T3_INTERNAL | T3_DIV_BY_8 );
// clear any pending timer interrupt
bit_clear(PIR2, PIR2_TMR3IF_BIT);
enable_interrupts(INT_TIMER3);
enable_interrupts(GLOBAL);
while (TRUE)
{
// set idle bit
bit_set(OSCCON, OSCCON_IDLEN_BIT);
output_low(PIN_A0);
// go to sleep ...
sleep();
output_high(PIN_A0);
}
}
|
Putting this on the logic analyser I use A0 to see that the PIC is idling most of the time and B0-B3 are all ticking away at their various rates....(yes, I know the names of the values for the delays and other things are a bit wrong now!)
The point is, this works in reality, and _doesn't_ simulate in MPLAB. So that explains a lot of problems I've had in the past....
Nige _________________ No comment! (Can't think of anything interesting to say!) |
|
|
|
|
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
|