CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

Put PIC24 to sleep [SOLVED]

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

Put PIC24 to sleep [SOLVED]
PostPosted: Thu Sep 15, 2016 1:42 pm     Reply with quote

Compiler: 5.026
Device: PIC24HJ256GP806

Hi guys,

I'm going through the MCP datasheet and the CCS help but it's unclear to me exactly how I can put my MCU to 'sleep' and basically, completely shut-off via software and get woken-up via an external interrupt. I have an accelerometer tied to it and it generates a HW interrupt (pin goes high) when motion is detected.

I tried the <sleep(SLEEP_FULL)> command and I happened to have an fprintf after the sleep and I thought that the fprintf would not be executed since the line prior to that was the <sleep> line but it turns-out that the fprint still got executed and the device doesn't appear to be sleeping at all because the current draw doesn't drop on my meter.

My MCU is tied to a few peripherals like a modem, an audio CODEC etc and these guys I can turn-them off because I see the current draw drop on my multimeter as I issue the commands to turn-off these devices... but nothing happens when the <sleep> command is issued for the MCU.

So... long story short, what is the best method to tell the MCU "go to sleep!" so that it draws the minimum amount of current, just enough to have it woken-up by an external interrupt?

Thanks again,

Ben


Last edited by benoitstjean on Mon Mar 06, 2017 6:08 am; edited 5 times in total
jeremiah



Joined: 20 Jul 2010
Posts: 1339

View user's profile Send private message

PostPosted: Thu Sep 15, 2016 2:28 pm     Reply with quote

I use a lot of PIC24 chips, though very few HJ chips. For all of mine, you simply use:

Code:
sleep();


And that puts it into the lowest sleep state unless your chip has deep sleep (which requires a different setting).

I would start smaller. Do a program like:

Code:

//your pic include, fuses, and clock statement

void main(void){

   delay_ms(5000);
   sleep();
   while(TRUE);

}


see if your current stays high for 5 seconds after power up and then drops.

If it works correctly, enable the external interrupt (and the global one) and give it a dummy ISR and repeat the test.

Make sure there is no other code affecting the test, so just use a simple one.

EDIT: also note that somewhere between 5.025 and 5.051 they mucked up the sleep() command to generate the wrong assembly. Post the LST output just below the sleep() call. It should be a very simple ASM listing.

EDIT2: like 5.049 incorrectly puts
Code:

....................       // Sleep to conserve power
....................       sleep();           
1D0A:  BTSC.B  1A.0
1D0C:  BTSC.B  868.1
1D0E:  BTSC.B  86A.2
1D10:  PWRSAV  #0


while 5.058 correctly puts:
Code:

....................       // Sleep to conserve power
....................       sleep();           
1C22:  PWRSAV  #0
Ttelmah



Joined: 11 Mar 2010
Posts: 19451

View user's profile Send private message

PostPosted: Thu Sep 15, 2016 2:40 pm     Reply with quote

Every interrupt that is enabled, can wake the chip. So to use INT_EXT, you need to disable all other interrupts before trying to sleep.

If any interrupt flag is set, on an enabled interrupt, the chip will not sleep. So after you enable INT_EXT, you need to clear it's flag, before trying to sleep.

The interrupt handler will only be called if the GLOBAL flag is also set at the same time. Generally easier and quicker to just let the code continue.

So sequence is:
Code:

    //disable all other interrupts in use first
    disable_interrupts(GLOBAL);
    enable_interrupts(INT_EXT);
    clear_interrupt(INT_EXT);
   
    //Now trigger the sleep
   
    sleep(SLEEP_FULL);
    delay_cycles(1);
    //The instruction after the sleep is 'pre-fetched' when you sleep
    //Should be a NOP - delay_cycles(1) gives this.
    disable_interrupts(INT_EXT);

    //Then since you have stopped the oscillator, you should check
    //that this is back to speed _[u]before[/u]_ enabling things like serial.

    //then re-enable the other interrupts and then the global bit.


Look at this thread:
<https://www.ccsinfo.com/forum/viewtopic.php?p=195993>

Which shows how to wait for the oscillator to be stable.
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Fri Sep 16, 2016 5:58 am     Reply with quote

Thanks Jeremiah and Ttelmah.

**************************************
EDIT: Yes, the <sleep()> ASM listing is this:

.................... sleep( SLEEP_FULL );
11308: PWRSAV #0
**************************************


Now, Ttelmah, your second line of code is <enable_interrupts( INT_EXT )> but your description above it says "disable all other interrupts".... so is this a typo? Should it be "disable_interrupts( INT_EXT )"?


Before going to sleep, the MCU needs to power down the other devices attached to it (a modem and other IC's). I have a means of turning them off.

When the device wakes-up from sleep via an interrupt, it will need to completely reboot from scratch as if it was doing a normal power-up so basically, when it reboots, I will need to call <reset_cpu()>. That's something I'll have to figure-out, perhaps write a bit in my external eeprom and read that bit upon a wake-up so that the unit knows it must do a clean reboot.

As for the interrupts, I use a total of 14 which I've listed below. I do not use the 'GLOBAL' interrupt anywhere in my code. The 14 interrupts listed are not always on at the same time; depending on the state of the device, some interrupts are inactive, others are active.

Here are the interrupts I use:
Code:

enable_interrupts( INT_UART1E );
enable_interrupts( INT_TBE );
enable_interrupts( INT_RDA );
enable_interrupts( INT_DMA0 );

enable_interrupts( INT_RDA2 );

enable_interrupts( INT_TIMER1 );
enable_interrupts( INT_TIMER2 );
enable_interrupts( INT_TIMER3 );

enable_interrupts( INTR_CN_PIN | {some pin} );
enable_interrupts( INT_EXT0 );
enable_interrupts( INT_EXT1 );
enable_interrupts( INT_EXT2 );
enable_interrupts( INT_EXT3 );
enable_interrupts( INT_EXT4 );

In your example, you have one interrupt before the <delay_cycles(1)> and after this command, you disable it. In my case, I have 14 interrupts. Should I call <disable_interrupts( INT_xxx)> after the <delay_cycles(1)> for each interrupt?

Again, thank you so much, your help is greatly appreciated.

Ben
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

Put PIC24 to sleep [SOLVED]
PostPosted: Sun Sep 18, 2016 6:51 am     Reply with quote

Seems to be working. These are my lines:
Code:
 
   setup_wdt( WDT_OFF );               // If I don't put this line, then eventually the WDT kicks-in and the unit resets (about 3 minutes).
   
   disable_interrupts( GLOBAL );
   
   enable_interrupts( INT_UART1E );
   enable_interrupts( INT_TBE );
   enable_interrupts( INT_RDA );
   enable_interrupts( INT_DMA0 );
   
   enable_interrupts( INT_RDA2 );
   
   enable_interrupts( INT_TIMER1 );
   enable_interrupts( INT_TIMER2 );
   enable_interrupts( INT_TIMER3 );
   
   enable_interrupts( INTR_CN_PIN | {some pin} );
   enable_interrupts( INT_EXT0 );
   enable_interrupts( INT_EXT1 );
   enable_interrupts( INT_EXT2 );
   enable_interrupts( INT_EXT3 );
   enable_interrupts( INT_EXT4 );
   
   clear_interrupt( INT_UART1E );
   clear_interrupt( INT_TBE );
   clear_interrupt( INT_RDA );
   clear_interrupt( INT_DMA0 );
   
   clear_interrupt( INT_RDA2 );
   
   clear_interrupt( INT_TIMER1 );
   clear_interrupt( INT_TIMER2 );
   clear_interrupt( INT_TIMER3 );
   
   clear_interrupt( INTR_CN_PIN | {some pin} );
   clear_interrupt( INT_EXT0 );
   clear_interrupt( INT_EXT1 );
   clear_interrupt( INT_EXT2 );
   clear_interrupt( INT_EXT3 );
   clear_interrupt( INT_EXT4 );
   
   sleep( SLEEP_FULL );

At this point, current draw goes from 43mA to 25mA and the MCU sits there and nothing is going-on. Then if I trigger one of the external interrupts, then the cpu_reset() gets called and the entire unit resets and goes back to normal state.
Code:

   delay_cycles(1);
   cpu_reset()

Thanks a million guys! Very appreciated!

Benoit
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Sat Mar 04, 2017 7:24 am     Reply with quote

Compiler: 5.026
Device: PIC24EP512GP806

I am reopening this thread I had created because I am not sure if I have everything setup properly.

I thought I had all the features enabled or disabled (whichever it is) to maximize power management on the device but I guess there's more to what I am doing.

Basically, when I go in shutdown mode, I only enable 7 external interrupts, I disable all timers and stop the WDT. The external interrupts are tied to other peripherals that will wake-up the MCU.

So ideally, I want the MCU to be dead or whatnot but still respond to an external interrupt.

When the external interrupt occurs, the MCU will reboot en entire system from scratch and re-initialize everything.

This is what I currently have:

Code:

   delay_ms( 250 );

   // Disable the global interrupt
   disable_interrupts( GLOBAL );

   // Disable all non-essential interrupts
   disable_interrupts( INT_UART1E );
   disable_interrupts( INT_TBE );
   disable_interrupts( INT_TIMER1 );
   disable_interrupts( INT_TIMER2 );
   disable_interrupts( INT_DMA0 );
   disable_interrupts( INT_RDA2 );
   disable_interrupts( INTR_CN_PIN | CODEC_PIN_WCLK  );
   disable_interrupts( INT_RDA );

   // Clear any pending interrupt flags
   clear_interrupt( INT_UART1E );
   clear_interrupt( INT_TBE );
   clear_interrupt( INT_RDA2 );
   clear_interrupt( INT_DMA0 );
   clear_interrupt( INT_TIMER1 );
   clear_interrupt( INT_TIMER2 );
   clear_interrupt( INTR_CN_PIN | CODEC_PIN_WCLK );
   clear_interrupt( INT_RDA );
   
   // Enable all interrupts that can wake-up the unit
   enable_interrupts( INT_RDA );
   enable_interrupts( INTR_CN_PIN | MODEM_RING_INDICATOR );
   enable_interrupts( INT_EXT0 );
   enable_interrupts( INT_EXT1 );
   enable_interrupts( INT_EXT2 );
   enable_interrupts( INT_EXT3 );
   enable_interrupts( INT_EXT4 );
   
   // Clear any pending interrupt flags
   clear_interrupt( INT_RDA );
   clear_interrupt( INTR_CN_PIN | MODEM_RING_INDICATOR );
   clear_interrupt( INT_EXT0 );
   clear_interrupt( INT_EXT1 );
   clear_interrupt( INT_EXT2 );
   clear_interrupt( INT_EXT3 );
   clear_interrupt( INT_EXT4 );

   // Stop the watchdog timer
   setup_wdt( WDT_OFF );

   // Not sure if this section of code actually does something to minimize current draw

   // Disable all peripheral power
   #byte PMD1 = getenv("byte:PMD1")
   #byte PMD2 = getenv("byte:PMD2")
   #byte PMD3 = getenv("byte:PMD3")
   #byte PMD4 = getenv("byte:PMD4")
   #byte PMD5 = getenv("byte:PMD5")
   #byte PMD6 = getenv("byte:PMD6")
   #byte PMD7 = getenv("byte:PMD7")

   PMD1 = 0xFF;
   PMD2 = 0xFF;
   PMD3 = 0xFF;
   PMD4 = 0xFF;
   PMD5 = 0xFF;
   PMD6 = 0xFF;
   PMD7 = 0xFF;

   #byte NVMCON=getenv("SFR:NVMCON");
   #bit NVMSIDL=NVMCON.12;
   NVMSIDL=TRUE;

   #byte RCON = getenv("SFR:RCON")
   #bit VREGS = RCON.8
   #bit VREGSF = RCON.11
   VREGS  = FALSE;
   VREGSF = FALSE;

   #byte OSCCON = getenv("SFR:OSCCON");
   #bit LPOSCEN = OSCCON.1;
   LPOSCEN = FALSE;

   // Sleep NOW
   sleep( SLEEP_FULL );

Does this seem correct and/or is there anything missing, useless or whatever it may be?

Thanks again,

Benoit
jeremiah



Joined: 20 Jul 2010
Posts: 1339

View user's profile Send private message

PostPosted: Sat Mar 04, 2017 8:40 am     Reply with quote

I don't know that chip as well, but that's a lot similar to what I do.

Some notes:
1. Disabling via the PMD will clear some of the control registers for those peripherals, so when you later set PMD bits to 0, make sure to call the appropriate setup_xxx() methods to reinit them.

2. Disabling a peripheral via the PMD makes interrupts for that peripheral stop working. If any of the wake up interrupts you have chosen are linked to a PMD bit, they won't work.

3. Make sure you really really want to disable all peripherals in the PMD. I haven't checked your data sheet, but on some chips it can disable some stuff you actually need while sleeping. chip by chip basis

4. I would recommend setting any unused pins to output_low()/output_high(), which ever is appropriate. they can be a source of power draw if not.

5. Is your UART configured to wake from sleep? For chips that support this, it is often not the default configuration.
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Sat Mar 04, 2017 1:19 pm     Reply with quote

Hi Jeremiah,

Thanks for the info.

Regarding all PMDs getting disabled, all external interrupts seem functional even after disabling the PMDs.

When you say unused pins, do you mean pins that are not physically tied to anything or do you mean the pins that are not used to wake-up the device? I have about 9-10 pins not used at all on the MCU.

I haven't checked for the UART if it can wake-up the MCU but actually, I don't really need the UART now that I am thinking of it because all interrupts are generated by external hardware pins that toggle vs the UART which is data arriving. The only external perripheral that would send a message to the UART is also put to sleep and that device generates an interrupt on an output pin tied to one of the MCU's input interrupt pin when it signals the MCU.

If anyone else can think of other features I can shut-down, let me know.

Thanks!

Benoit
Ttelmah



Joined: 11 Mar 2010
Posts: 19451

View user's profile Send private message

PostPosted: Sat Mar 04, 2017 1:24 pm     Reply with quote

Pins that aren't connected to things.

Basically if a pin floats into the intermediate area between ViL and ViH, it can result in extra current being drawn. Pins should either be biased to a supply rail (so a resistor to -ve or +ve), or be set as outputs, and driven high or low.
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Sun Mar 05, 2017 6:21 am     Reply with quote

Ah ok, that makes sense.

And how about the registers? Anythine else other than what I indicated? Are about the ones I indicated, does it even make sense?

Thanks again,

Ben
Ttelmah



Joined: 11 Mar 2010
Posts: 19451

View user's profile Send private message

PostPosted: Sun Mar 05, 2017 9:17 am     Reply with quote

If you were actually waking on the UART, there would be the question of a clock for this. However you have the much simpler solution of actually waking on a pin, so no clock is needed.
Looks pretty much OK.
benoitstjean



Joined: 30 Oct 2007
Posts: 564
Location: Ottawa, Ontario, Canada

View user's profile Send private message

PostPosted: Mon Mar 06, 2017 6:08 am     Reply with quote

All righty, thanks for your help.

It is very much appreciated.

Benoit
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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