Sleep Mode

I’ve run into a situation where I need one of my wixels to consume as little power as possible (standby / sleep) and have been reading as much as I can find. I’m very weak on skills so most of what I read is over my head. I did find this on the web however…

ublo.ro/wixel-pm3-low-power-slee … cc2511f32/

I copied the code into a project, loaded and test it. It works very well… Now what I need to is try and figure out how it works so that I can alter it for my needs… What I would like to do is have the wixel “Wake Up” on a low reading on P0_1 or P_02. Right now the code is tied to P1_0…

Another side affect with the developers original code is I always have to force the wixel into bootloader mode using the 3V jumper to P2_2… It would be nice to understand how to avoid that too…

Help is appreciated…

Tim

Here is the develpers posted test code (the code I’m trying to adapt…)


#include <wixel.h>
#include <usb.h>
#include <usb_com.h>
#include <stdio.h>

int32 CODE param_blink_period_ms = 500;
int32 CODE param_count = 20; // counting 20 changes of the red led state, then go to sleep
uint32 count = 0; // the actual counter

uint32 lastToggle = 0;

/* catch interrupts on P1INT					*/
ISR (P1INT, 0) {
	/* clearing the CPU interrupt registers			*/
	/* 1. first the general interrupt register, IRCON2	*/
	IRCON2 &= ~0x08;				// clear IRCON2.P1IF
	/* 2. followed by the P1 interrupt register, P1IFG	*/
	P1IFG &= ~0x01;					// clear P1IF0
	/* not related to the interrupt itself, but to the	*/
	/* sleep mode: clear SLEEP.MODE flag			*/
	SLEEP &= ~0x03;					// clear SLEEP.MODE
	/* disable interrupt only for P1_0			*/
	P1IEN &= ~0x01;					// clear P1_0IEN
	/* wait, disabling interrupts on P1 also  		*/
	IEN2 &= ~0x10;					// clear IEN2.P1IE
	}

/* the function that puts the system to sleep			*/
/* i've chosen PM3 as i don't need a timer wake-up event	*/
void putToSleep () {
	/* make the P1_0 a input pin				*/
	P1DIR &= ~0x01;					// P1_0DIR = 0 -> input
	/* clear any interrupt flags. see above for details	*/
	IRCON2 &= ~0x08;				// clear IRCON2.P1IF
	P1IFG &= ~0x01;					// clear P1IF0
	/* set the interrupt enable flag on P1_0		*/
	P1IEN |= 0x01;					// P1_0IEN = 1;
	/* set the type of interrupt: 0=rising edge; 1=falling	*/
	PICTL &= ~0x02;					// PICTL.P1ICON = 0
	/* set the interrupt enable flag for the entire P1	*/
	IEN2 |= 0x10;					// IEN2.P1IE = 1;
	/* enable global interrupts				*/
	IEN0 |= 0x80;					// IEN0.EA = 1;
	/* the sleep mode i've chosen is PM3			*/
	SLEEP |= 0x03;					// SLEEP.MODE = PM3
	/* idling the CPU - required in the manual		*/
	if (SLEEP & 0x03) PCON |= 0x01;	// PCON.IDLE = 1;
	}

void updateLeds()
{
    usbShowStatusWithGreenLed();

    LED_YELLOW(0);

    if (getMs() - lastToggle >= param_blink_period_ms/2)
    {
        LED_RED(!LED_RED_STATE);
        lastToggle = getMs();
		/* the piece of code that counts and if the counter	*/
		/* hits the limit, puts the system to sleep		*/
		count++;
		if (count == param_count) {
			count = 0;
			/* here the blinking freezes, waiting for a	*/
			/* falling edge on P1_0				*/
			putToSleep();
			}
    }
}

void main()
{
    systemInit();
    usbInit();
	
    while(1)
    {
        boardService();
        updateLeds();
        usbComService();
    }
}

Hello, Tim.

We don’t really have any low-power code in the Wixel SDK, but one of the engineers here recently did a low-power Wixel project for his own purposes and you could look at his code.

Code: github.com/kevinwchang/wixel-sd … da_chest.c

Copy of code for future reference:


#include <wixel.h>
#include <usb.h>
#include <usb_com.h>

#define SOMO_CLK  04
#define SOMO_DATA 05

#define LID_SWITCH  12 // high = lid opened
#define ITEM_SWITCH 13 // low = item removed

void somoCmd(uint16 cmd)
{
    int8 b;
    
    setDigitalOutput(SOMO_CLK, LOW);
    delayMs(2);
    
    for (b = 15; b >= 0; b--)
    {
        setDigitalOutput(SOMO_CLK, LOW);
        setDigitalOutput(SOMO_DATA, (cmd >> b) & 1);
        delayMicroseconds(100);
        setDigitalOutput(SOMO_CLK, HIGH);
        delayMicroseconds(100);

    }
    delayMs(2);
}

// http://ublo.ro/wixel-pm3-low-power-sleep-mode-cc2511f32/

// setting PICTL bit to 0 seems to trigger on BOTH rising and falling edges, while setting to 1 captures neither!

void setupLidInterrupt()
{
    // clear port 1 interrupt status flag
    P1IFG = 0;     // P1IFG.P1IF[7:0] = 0
    // clear cpu interrupt flag
    IRCON2 &= ~0x08;    // IRCON2.P1IF = 0    
    // set port 1 interrupt mask
    P1IEN = 0x04;      // P1IEN.P1_2IEN = 1
    // select rising edge interrupt on port 1
    PICTL &= ~0x02;     // PICTL.P1ICON = 0
    // enable port 1 interrupt
    IEN2 |= 0x10;       // IEN2.P1IE = 1
}

void setupItemOrLidCloseInterrupt()
{
    // clear port 1 interrupt status flag
    P1IFG = 0;     // P1IFG.P1IF[7:0] = 0
    // clear cpu interrupt flag
    IRCON2 &= ~0x08;    // IRCON2.P1IF = 0    
    // set port 1 interrupt mask
    P1IEN = 0x0C;       // P1IEN.P1_[2-3]IEN = 1
    // select rising edge interrupt on port 1??? (we actually want falling edge)
    PICTL &= ~0x02;     // PICTL.P1ICON = 0
    // enable port 1 interrupt
    IEN2 |= 0x10;       // IEN2.P1IE = 1
}

ISR (P1INT, 0)
{
    // clear port 1 interrupt status flag
    P1IFG = 0;          // P1IFG.P1IF[7:0] = 0
    // clear cpu interrupt flag
    IRCON2 &= ~0x08;    // IRCON2.P1IF = 0
   // clear sleep mode 
    SLEEP &= ~0x03;     // SLEEP.MODE = 11
    // clear port 1 interrupt mask
    P1IEN = 0;          // P1IEN.P1_[7:0]IEN = 0
    // disable port 1 interrupt
    IEN2 &= ~0x10;      // IEN2.P1IE = 0
}

void putToSleep()
{
   // select sleep mode PM1
    SLEEP |= 0x03;  // SLEEP.MODE = 11
    // 3 NOPs as specified in 12.1.3
    __asm nop __endasm;
    __asm nop __endasm;
    __asm nop __endasm;
    // enter sleep mode
    if (SLEEP & 0x03) PCON |= 0x01; // PCON.IDLE = 1
}

void main()
{
    systemInit();
    
    // set P1_0 and P1_1 to outputs to avoid leakage current
    setDigitalOutput(10, LOW);
    setDigitalOutput(11, LOW);
    
    setDigitalOutput(SOMO_CLK, HIGH);
    setDigitalOutput(SOMO_DATA, LOW);
    
    if (usbPowerPresent())
    {
        usbInit();
        
        while (1)
        {
            usbShowStatusWithGreenLed();
            boardService();
            usbComService();
        }
    }
    else
    {
        LED_YELLOW(1);
        delayMs(300);
        LED_YELLOW(0);
        
        while (1)
        {
            do
            {
                setupLidInterrupt();
                putToSleep();
                        
                // on wake, wait and check again to debounce
                delayMs(25);
            } while (!isPinHigh(LID_SWITCH));
            
            // if item not present, wait for lid open again
            if (!isPinHigh(ITEM_SWITCH)) continue;
            
            // play chest open music
            somoCmd(0x0000);
            delayMs(7951);
            
            // if lid was closed, start over & wait for lid open again
            if (!isPinHigh(LID_SWITCH)) continue;
            
            // if item present, wait for item to be removed or lid to be closed
            if (isPinHigh(ITEM_SWITCH)) 
            {
                do
                {
                    setupItemOrLidCloseInterrupt();
                    putToSleep();
                    
                    // on wake, wait and check again to debounce
                    delayMs(25);
                } while (isPinHigh(ITEM_SWITCH) && isPinHigh(LID_SWITCH));
                
                // if lid was closed, start over & wait for lid open again
                if (!isPinHigh(LID_SWITCH)) continue;
            }
            
            // play get item music
            somoCmd(0x0001);
            delayMs(2578);
        }
    }
}

The reason you had to force the Wixel into bootloader mode is because the code you are using was not properly attending to the USB connection. If your code doesn’t attend to the USB connection and instead just sleeps, it will miss the command from our software that tells it to go into bootloader mode. Kevin has a nice solution for that in his code: if USB power is detected then his app just sits around and attends to the USB connection. Otherwise, he does his low-power stuff.

I’m not totally sure how you would change the code to wake up on P0_1 or P_02, but you should read the CC2511 datasheet and also make sure to change all the lines of code that have something to do with Port 1. You can usually tell if a line of code has something to do with P1 because it has the string “P1” in it, or the string “Port 1” in a nearby comment.

–David

Thanks David,

So are you saying we cannot set up interrupts on Port 0?

Also, where is this CC2511 datasheet? I’m sure It will make little sense to me but I should try…

Tim

No, I didn’t say that. I was just saying that I don’t know exactly how to set up Port 0 interrupts and I was giving you the general outline of the steps you can do to accomplish it. You would need to fill in the details.

The datasheet can be found in the Resources tab for the Wixel:
pololu.com/catalog/product/1337/resources

–David

David,

I thought I would follow up with this as I did finally get my app working. I still do not understand how exacty these interrupts work, and I’ve yet to set up an interrupt to work on the P0 bank… But that’s OK.

In the engineers code that you posted I could see comments related to not being able to get the pin to respond properly as a “Falling Edge”.

// http://ublo.ro/wixel-pm3-low-power-sleep-mode-cc2511f32/

// setting PICTL bit to 0 seems to trigger on BOTH rising and falling edges, while setting to 1 captures neither!

// select rising edge interrupt on port 1??? (we actually want falling edge)

That even when set it did not work. I had the same issue and for my needs was a pretty big flaw.

I stayed up for hours on Google and caught a blurb on a Texas Instruments Blog about needing to execute this code PICTL |= 0x02; in the proper position in the sequence when defining if it is a rising edge or falling edge. * I think it needs to be done before clearing flags if you want to have a falling edge.

Anyway… I ended up changing my code to the following and I now have a falling edge interrupt on P1_2… Just thought I’d share it…


// catch interrupts on P1INT
ISR (P1INT, 0) {
	// clearing the CPU interrupt registers
	// 1. first the general interrupt register, IRCON2
	IRCON2 &= ~0x08;				// clear IRCON2.P1IF
	// 2. followed by the P1 interrupt register, P1IFG
	P1IFG &= ~0x04;	// = P1_2
	// not related to the interrupt itself, but to the
	// sleep mode: clear SLEEP.MODE flag
	SLEEP &= ~0x03;					// clear SLEEP.MODE
	// disabling all interrupts on P1
	IEN2 &= ~0x10;					// clear IEN2.P1IE
	}

// the function that puts the system to sleep
// i've chosen PM3 as i don't need a timer wake-up event
void putToSleep () {

	//Here is some code used to setup a falling edge interrupt on P1_2.
	P1DIR &= ~0x04;     // DIRP1_2 = 0 (P1_2 is an input)
	PICTL |= 0x02;      // P1ICON = 1 (Falling edge interrupt on Port 1)

    /* Clear Port 0-1-2 Interrupt flags */
    P0IFG = ~0x01; // Clear source interrupt flag for P0
    P0IF = 0; // Clear CPU interrupt flag for P0"
    P1IFG = ~0x01; // Clear source interrupt flag for P1
    P1IF = 0; // Clear CPU interrupt flag for P1
    P2IFG = ~0x01; // Clear source interrupt flag for P2
    P2IF = 0; // Clear CPU interrupt flag for P2

	// set the interrupt enable flag on P1_0
	P1IEN |= 0x04;	// = P1_2

	// set the interrupt enable flag for the entire P1
	IEN2 |= 0x10;					// IEN2.P1IE = 1;
	// enable global interrupts
	IEN0 |= 0x80;					// IEN0.EA = 1;
	// the sleep mode i've chosen is PM3
	SLEEP |= 0x03;					// SLEEP.MODE = PM3
	// reset the counter
	SleepCount = 0;
	// idling the CPU - required in the manual
	if (SLEEP & 0x03) PCON |= 0x01;	// PCON.IDLE = 1;
	}

Hello, Tim.

I am glad you were able to get your project working, and thank you for sharing that strange detail about the interrupts on the CC2511 with us. I’m not sure why it would be like that.

–David