Pololu Robotics & Electronics
Menu
My account Comments or questions? About Pololu Contact Ordering information Distributors

Reading WORTIME0/1


#1

Hello,
I am trying to read WORTIME IWORTIME0/WORTIME1) after returning from a PM1/2 mode, but have no luck doing so whatsoever.
I need my wixel to sleep for around 5 minutes, then wake to receive a packet from another wixel.
I would have thought that the following would suffice to read WORTIME after waking from PM1/2 and before starting the HSXOSC:

temp = WORTIME0;
//wait for a positive edge
while(temp == WORTIME0);
temp0 = WORTIME0;
temp1 = WORTIME1;
time_slept = (temp1 <<8) ;
time_slept != temp0;

Essentially, I am waking the wixel every 10 seconds within a loop to allow the RCOSC to be recalibrated against the XOSC to give me some stability as the RCOSC tends to drift too much with temperature. But I need to know how long the wixel was asleep for, so I can correctly set the getMs() register so the rest of the code can function easily. To within at least 100ms or so. Not to big an ask, I would have thought.

The result of time_slept is always 0, even though the while(temp == WORTIME0) obviously does not go into an infinite loop. temp0 and temp1 are set to values the first time after waking, but every time afterwards, they are set to 0. It is not making sense to me why this is the case.

If anyone has any ideas, I would appreciate them.
Cheers


#2

Hello.

I am sorry you are having trouble reading from the Wixel’s WORTIME registers. The last line of code you posted will have no effect because you are just comparing two variables with the != operator and you are not saving the result of that comparison. I think you meant to use the |= operator. If that does not help, could you post your full code?

–David


#3

Hi David,
Yes, as you pointed out, I had a typo in the last line of the code I posted, apologies. I am using the |= operator. This is not the source of the issue. Before each loop, I print out the the value of temp0 and temp1. The actual code that does the calculation is as follows:

			temp = WORTIME0;
			while(temp == WORTIME0);
			tmp0 = WORTIME0;
			tmp1 = WORTIME1;
			// Switch back to high speed      
			boardClockInit(); 
			slept_time = tmp0;
			slept_time |= tmp1 << 8; 

As you can see, I do not do anything with the tmp0/1 variables until after the HSXOSC is running again. This does not appear to be the problem here.
I am really trying to understand why each subsequent read of the WORTIME registers returns 0 into these variables. And the initial read is inconsistent. Rarely what I would expect, often only one value is set. Yet, the temp = WORTIME0, and the while(temp == WORTIME0) are obviously working fine.
Cheers


#4

Further, I have created a much smaller program to try to determine exactly what is going on. Originally, I was displaying WORTIME0 and WORTIME1 BEFORE the sleep had occurred, using a printf_fast call. This is obviously wrong, as the code was not necessarily waiting for the positive edge prior to reading WORTIME.

So, I have changed this to only display the value of WORTIME AFTER coming out of PM2, after two positive edges, just to make sure, and AFTER the call to boardClockInit(). I capture WORTIME0 and WORTIME1 into tmp0 and tmp1 PRIOR to boardClockInit(), and display these values immediately after displaying WORTIME0 and WORTIME1 after boardClockInit().

In this case, the value of WORTIME0 and tmp0 is always 1, and the value of WORTIME1 and tmp1 is always 0.

Perhaps the problem is in the version of SDK I am using, and something has been fixed in the SDK since this version? It appears to be v3.1.0


#5

I have also tried moving the storage of WORTIME into the ST ISR. The code looks like this.

ISR(ST, 1)
{
	// Clear IRCON.STIF (Sleep Timer CPU interrupt flag)
	IRCON &= 0x7F;
	// Clear WORIRQ.EVENT0_FLAG (Sleep Timer peripheral interrupt flag)
	// This is required for the CC111xFx/CC251xFx only!
	WORIRQ &= 0xFE;
	// Store WORTIME for later calcuation in goToSleep
	temp = WORTIME0;
	while(temp == WORTIME0);
	tmp0 = WORTIME0;
	tmp1 = WORTIME1;
   
	SLEEP &= 0xFC; // Not required when resuming from PM0; Clear SLEEP.MODE[1:0]
}

The problem still persists. The tmp0 and tmp1 uint8 variables are both still 0, but the while loop exits appropriately. It just does not make sense. There obviously must be some value in WORTIME0 and WORTIME1, otherwise the while(temp == WORTIME0) would stay in an endless loop.

I really need a way to determine how long the wixel was in PM1/2, down to at least 100ms, so that I can accurately align communications between two devices. Any suggestions would be gratefully appreciated.

TI forums state that the code I am using should do this, accurate to a maximum 3ms drift. But I simply cannot make it work.


#6

Hello.

I just noticed that there is a discrepancy in the register addresses for WOREVT1 and WORTIME0. Here is what the CC2511 datasheet shows:

I believe the datasheet is wrong, and that they accidentally flipped the addresses of WOREVT1 and WORTIME0 when writing that table. The Wixel SDK’s cc2511_map.h and Keil’s cc2510.h both use addresses that make more sense, putting WOREVT1 at 0xA4 and WORTIME0 at 0xA5. Our radio_mac library, which is used in many Wixel applications, uses WOREVT1, so we probably are using the right address for that register. Just in case there is something odd with those registers, you might want to start reading the values of WOREVT0 and WOREVT1 during your debugging.

Given that you are waiting for WORTIME0 to increment, and you are saying it has a value of zero after that, I wonder if there is some error in how you are transferring that information out of the Wixel.

I recommend simplifying your code to the simplest possible thing that should work but doesn’t. It should just go to sleep, wait for some time, then wake up and send some register values out on the UART using the Wixel SDK’s UART library. It should not define additional ISRs. If you cannot find the problem when running that simple code, please post the complete program here along with the expected results and the actual results you are seeing.

–David


#7

Thanks David,
But that is what I have done. Reduced the code to something very simple. I can reduce it some more, but I don’t think it is an issue with transferring information out of the wixel.
The reason I don’t see this as the issue is that the code is:

	temp = WORTIME0;
	while(temp == WORTIME0);
	tmp0 = WORTIME0;
	tmp1 = WORTIME1;

The tmp0 and tmp1 variables (which are unsigned char) SHOULD be set to WORTIME0 and WORTIME1.
I then use these to calculate the value of slept_time (uint16) using

			slept_time = tmp0;
			slept_time |= tmp1 << 8; 

and then print out tmp0, tmp1, and slept_time using a printf_fast call, as well as update other timers with the slept_time value. The tmp0, tmp1, and slept_time values all return 0, while a lot of others of similar types print out fine in a printf_fast. Also, NONE of the updated timer values show any increment or change based on slept_time, as expected if the value is indeed 0.

When I get a chance, I will reduce the code even further as you suggest. But I do not believe this is a printf_fast issue. BTW, I am sending these out the USB serial port to a PuTTY terminal, not the UART, but that should not be an issue.
Cheers


#8

Hi David,
Sorry it has taken so long. I am still none the wiser about why WORTIME cannot be read correctly.
I have an example code that can be run on a wixel to show the issue. Do I post it here, or somehow attach it? Haven’t worked out the editor here as yet.
Cheers


#9

Hello.

I would recommend posting it here. You can copy and paste it into your post, then select it, and click the “Preformatted Text” button to format it properly (or type ``` on the lines above and below the code).

–David


#10

Hi David,
Test program is below.
Cheers

#include <board.h>
#include <usb.h>
#include <usb_com.h>
#include <gpio.h>
#include <adc.h>
#include "time.h"

#include <radio_registers.h>
#include <radio_mac.h>
#include <random.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <uart1.h>


static volatile BIT usb_connected;		// indicates DTR set on USB.  Meaning a terminal program is connected via USB for debug.

// temporary storage of WORTIME, used in goToSleep to calculate slept_time
unsigned char temp;
unsigned char tmp0 = 0;
unsigned char tmp1 = 0;



// Initialization of source buffers and DMA descriptor for the DMA transfer during PM2 sleep.
unsigned char XDATA PM2_BUF[7] = {0x06,0x06,0x06,0x06,0x06,0x06,0x04};
//unsigned char XDATA PM3_BUF[7] = {0x07,0x07,0x07,0x07,0x07,0x07,0x04};
unsigned char XDATA dmaDesc[8] = {0x00,0x00,0xDF,0xBE,0x00,0x07,0x20,0x42};


// forward prototypes
// prototype for doServices function.
int doServices(uint8 bWithProtocol);

/* time functions.  Replaces the time library. 
Note:  These functions and variables are taken direct from the time library, and have been slightly modified
and added to.  This is preferable to modifying the time library source code directly.
*/
PDATA volatile uint32 timeMs;

ISR(T4, 0)
{
    timeMs++;
	T4CC0 ^= 1; // If we do this, then on average the interrupts will occur precisely 1.000 ms apart.
}

uint32 getMs()
{
    uint8 oldT4IE = T4IE;   // store state of timer 4 interrupt (active/inactive?)
    uint32 time;
    T4IE = 0;               // disable timer 4 interrupt
    time = timeMs;          // copy millisecond timer count into a safe variable
    T4IE = oldT4IE;         // restore timer 4 interrupt to its original state
    return time;            // return timer count copy
}

/* addMs - This is an added function to allow the timeMs variable to be added to
We use this in the code to append the sleep time to the timeMs variable so we can keep track of
precisely how long it has been since the last packet was received, even during sleep.
*/
void addMs(uint32 addendum)
{
	uint8 oldT4IE = T4IE;	// store state of timer 4 interrupt (active/inactive?)
	T4IE = 0;				// disable timer 4 interrupt
	timeMs += addendum; 	// add addendum to timeMs
	T4IE = oldT4IE;			// restore timer 4 interrupt to it's original state
}

void timeInit()
{
	// set the timer tick interval
    T4CC0 = 187;
    T4IE = 1;     // Enable Timer 4 interrupt.  (IEN1.T4IE=1)

    // DIV=111: 1:128 prescaler
    // START=1: Start the timer
    // OVFIM=1: Enable the overflow interrupt.
    // MODE=10: Modulo
    T4CTL = 0b11111010;

    EA = 1; // Globally enable interrupts (IEN0.EA=1).
}

void delayMs(uint16 milliseconds)
{
    // TODO: make this more accurate.
    // A great way would be to use the compare feature of Timer 4 and then
    // wait for the right number of compare events to happen, but then we
    // can't use that channel for PWM in the future.
    while(milliseconds--)
    {
        delayMicroseconds(250);
        delayMicroseconds(250);
        delayMicroseconds(250);
        delayMicroseconds(249); // there's some overhead, so only delay by 249 here
    }
}

/* end time functions ***********************************************************/


void uartEnable() {
    U1UCR &= ~0x40; //Hardware Flow Control (CTS/RTS) Off.  We always want it off.
    U1CSR |= 0x40; // Recevier enable
}

#define waitDoingServicesInterruptible(wait_time, break_flag, bProtocolServices) \
  do { \
	XDATA uint32 start_wait; \
	start_wait = getMs(); \
	while ((getMs() - start_wait) < wait_time) { \
		doServices(bProtocolServices); \
		if(break_flag) break; \
		delayMs(20); \
	} \
  } while (0)

// macro to wait a specified number of milliseconds, whilst processing services.
#define waitDoingServices(wait_time, bProtocolServices) \
  do { \
	XDATA uint32 start_wait; \
	start_wait = getMs(); \
	while ((getMs() - start_wait) < wait_time) { \
		doServices(bProtocolServices); \
		delayMs(20); \
	} \
  } while (0)



/** Functions *****************************************************************/
/* the functions that puts the system to sleep (PM2) and configures sleep timer to
wake it again in 250 seconds.*/
void makeAllOutputs(BIT value)
{
	//we only make the P1_ports low, and not P1_2 or P1_3
    int i;
    for (i=10;i <= 17; i++)
	{
		setDigitalOutput(i, value);
    }
}

void sleepInit(void)
{
   WORIRQ  |= (1<<4); // Enable Event0 interrupt  
}

ISR(ST, 1)
{
	// Clear IRCON.STIF (Sleep Timer CPU interrupt flag)
	IRCON &= 0x7F;
	//IRCON &= 0x3F;
	// Clear WORIRQ.EVENT0_FLAG (Sleep Timer peripheral interrupt flag)
	// This is required for the CC111xFx/CC251xFx only!
	WORIRQ &= 0xFE;
	// Store WORTIME for later calcuation in goToSleep
	temp = WORTIME0;
	while(temp == WORTIME0);
	tmp0 = WORTIME0;
	tmp1 = WORTIME1;
   
	SLEEP &= 0xFC; // Not required when resuming from PM0; Clear SLEEP.MODE[1:0]
}

void switchToRCOSC(void)
{
   // Power up [HS RCOSC] (SLEEP.OSC_PD = 0)
   SLEEP &= ~0x04;
   // Wait until [HS RCOSC] is stable (SLEEP.HFRC_STB = 1)
   while ( ! (SLEEP & 0x20) );
   // Switch system clock source to HS RCOSC (CLKCON.OSC = 1),
   // and set max CPU clock speed (CLKCON.CLKSPD = 1).
   CLKCON = (CLKCON & ~0x07) | 0x40 | 0x01;
   // Wait until system clock source has actually changed (CLKCON.OSC = 1)
   while ( !(CLKCON & 0x40) );
   // Power down [HS XOSC] (SLEEP.OSC_PD = 1)
   SLEEP |= 0x04;
}


void goToSleep (uint16 seconds) {
	//uint16 sleep_time = 0;
	unsigned short sleep_time = 0;
	unsigned short this_sleep_time = 10;
	uint32 sleep_time_ms = 0;
	uint32 last_wake_time = 0;
	uint32 last_sleep_time = 0;
	uint16 slept_time = 0;
	//initialise sleep library
	sleepInit();

    // The wixel docs note that any high output pins consume ~30uA
    makeAllOutputs(LOW);
	while(usb_connected && (usbComTxAvailable() < 128)) {
		usbComService();
	}
	//calculate the time we will sleep in total.
	sleep_time_ms = (seconds * 1000);
	sleep_time = (unsigned short)(sleep_time_ms/1000);
	printf_fast("seconds = %u, sleep_time_ms = %lu, sleep_time = %u\r\n", seconds, sleep_time_ms, sleep_time); 
	// we wake up every 10 seconds to recalibrate the RCOSC.  
	//The first time may be less than 10 seconds, in case we calculate value that is not wholey divisible by 10.
	while(sleep_time > 0)		
	{
		if( sleep_time % 10 == 0)
		{
			this_sleep_time = 10;
			if (sleep_time <=10)
				break;
		}
		else
		{
			this_sleep_time = sleep_time % 10;
		}
		sleep_time -= this_sleep_time;
		if (this_sleep_time < 1 || this_sleep_time > 10 || sleep_time > seconds) {
			printf_fast("Invalid this_sleep_time, or sleep_time too short.  Not sleeping.");
			return;
		}
		while(usb_connected && (usbComTxAvailable() < 128)) {
			usbComService();
		}

		if(!usb_connected)
		{
			unsigned char storedDescHigh, storedDescLow;
			BIT	storedDma0Armed;
			unsigned char storedIEN0, storedIEN1, storedIEN2;
			disableUsbPullup();
			usbDeviceState = USB_STATE_DETACHED;
			// disable the USB module
			SLEEP &= ~(1<<7); // Disable the USB module (SLEEP.USB_EN = 0).
			// sleep power mode 2 is incompatible with USB - as USB registers lose state in this mode.

			// set Sleep Timer to the lowest resolution (1 second)      
			WORCTRL |= 0x03;
			last_sleep_time = getMs();
			// must be using RC OSC before going to PM2
			//switchToRCOSC();
			
			// Following DMA code is a workaround for a bug described in Design Note
			// DN106 section 4.1.4 where there is a small chance that the sleep mode
			// bits are faulty set to a value other than zero and this prevents the
			// processor from waking up correctly (appears to hang)
			
			// Store current DMA channel 0 descriptor and abort any ongoing transfers,
			// if the channel is in use.
			storedDescHigh = DMA0CFGH;
			storedDescLow = DMA0CFGL;
			storedDma0Armed = DMAARM & 0x01;
			DMAARM |= 0x81; // Abort transfers on DMA Channel 0; Set ABORT and DMAARM0
			// Update descriptor with correct source.
			dmaDesc[0] = ((unsigned int)& PM2_BUF) >> 8;
			dmaDesc[1] = (unsigned int)& PM2_BUF;
			// Associate the descriptor with DMA channel 0 and arm the DMA channel
			DMA0CFGH = ((unsigned int)&dmaDesc) >> 8;
			DMA0CFGL = (unsigned int)&dmaDesc;
			DMAARM = 0x01; // Arm Channel 0; DMAARM0
			
			// save enabled interrupts
			storedIEN0 = IEN0;
			storedIEN1 = IEN1;
			storedIEN2 = IEN2; 
			
			// make sure interrupts aren't completely disabled
			// and enable sleep timer interrupt
			IEN0 |= 0xA0; // Set EA and STIE bits
			 
			// then disable all interrupts except the sleep timer
			IEN0 &= 0xA0;
			IEN1 &= ~0x3F;
			IEN2 &= ~0x3F;
			
			WORCTRL |= 0x04; // Reset Sleep Timer, set resolution to 1 clock cycle
			temp = WORTIME0;
			while(temp == WORTIME0); // Wait until a positive 32 kHz edge
			temp = WORTIME0;
			while(temp == WORTIME0); // Wait until a positive 32 kHz edge
			WOREVT1 = this_sleep_time >> 8; // Set EVENT0, high byte
			WOREVT0 = this_sleep_time; // Set EVENT0, low byte
			MEMCTR |= 0x02;  // Flash cache must be disabled.
			SLEEP = (SLEEP & 0xFC) | 0x06; // PM2, disable USB, power down other oscillators
			
			__asm nop __endasm; 
			__asm nop __endasm; 
			__asm nop __endasm;
			
			if (SLEEP & 0x03) {
				__asm mov 0xD7,#0x01 __endasm; // DMAREQ = 0x01;
				__asm nop __endasm;            // Needed to perfectly align the DMA transfer.
				__asm orl 0x87,#0x01 __endasm; // PCON |= 0x01;
				__asm nop __endasm;      
			}
			// restore enabled interrupts
			IEN0 = storedIEN0;
			IEN1 = storedIEN1;
			IEN2 = storedIEN2; 
			// restore DMA descriptor
			DMA0CFGH = storedDescHigh;
			DMA0CFGL = storedDescLow;
			if (storedDma0Armed)
				DMAARM |= 0x01; // Set DMA0ARM
	   
			// Switch back to high speed
			boardClockInit();
			last_wake_time = getMs();

		} else {
			// set Sleep Timer to the lowest resolution (1 second)      
			WORCTRL |= 0x03; // WOR_RES[1:0]
			// make sure interrupts aren't completely disabled
			// and enable sleep timer interrupt
			IEN0 |= 0xA0; // Set EA and STIE bits
			last_sleep_time = getMs();
		   
			WORCTRL |= 0x04; // Reset Sleep Timer; WOR_RESET, and set resolution to 1 clock period
			temp = WORTIME0;
			while(temp == WORTIME0); // Wait until a positive 32 kHz edge
			temp = WORTIME0;
			while(temp == WORTIME0); // Wait until a positive 32 kHz edge

			WOREVT1 = this_sleep_time >> 8; // Set EVENT0, high byte
			WOREVT0 = this_sleep_time; // Set EVENT0, low byte

			// Set SLEEP.MODE according to PM1

			SLEEP = (SLEEP & 0xFC) | 0x01; // SLEEP.MODE[1:0]
			// Apply three NOPs to allow the corresponding interrupt blocking to take
			// effect, before verifying the SLEEP.MODE bits below. Note that all
			// interrupts are blocked when SLEEP.MODE ? 0, thus the time between
			// setting SLEEP.MODE ? 0, and asserting PCON.IDLE should be as short as
			// possible. If an interrupt occurs before the NOPs have completed, then
			// the enabled ISR shall clear the SLEEP.MODE bits, according to the code
			// in Figure 7.

			__asm nop __endasm;
			__asm nop __endasm;
			__asm nop __endasm;

			// If no interrupt was executed in between the above NOPs, then all
			// interrupts are effectively blocked when reaching this code position.
			// If the SLEEP.MODE bits have been cleared at this point, which means
			// that an ISR has indeed executed in between the above NOPs, then the
			// application will not enter PM{1 – 3} !
	   
			if (SLEEP & 0x03) // SLEEP.MODE[1:0]
			{
				// Set PCON.IDLE to enter the selected PM, e.g. PM1.
				PCON |= 0x01;
				// The SoC is now in PM and will only wake up upon Sleep Timer interrupt
				// or external Port interrupt.
				__asm nop __endasm;    
			}
			// Switch back to high speed      
			boardClockInit(); 
			last_wake_time = getMs();
		}
		printf_fast("tmp0 is %hu, tmp1 is %hu\r\n", tmp0, tmp1);
		slept_time = tmp1 << 8;
		slept_time |= tmp0; 
		printf_fast("slept_time is %u\r\n", slept_time);
		addMs(slept_time);
	}
}

void updateLeds()
{
	LED_GREEN( usb_connected);
}

// This is called by printf and printPacket.
void putchar(char c)
{
//	uart1TxSendByte(c);
	if (usb_connected && (usbComTxAvailable() > 0))
		usbComTxSendByte(c);
}


// process each of the services we need to be on top of.
// if bWithProtocol is true, also check for commands on both USB and UART
int doServices(uint8 bWithProtocol)
{
	boardService();
	updateLeds();
	usbComService();
	return 1;
}


// you can uncomment this if you want a glowing yellow LED when a terminal program is connected
// to the USB.  I got sick of it.
// LineStateChangeCallback - sets the yellow LED to the state of DTR on the USB, whenever it changes.
void LineStateChangeCallback(uint8 state)
{
	LED_YELLOW(state & ACM_CONTROL_LINE_DTR);
	usb_connected = state & ACM_CONTROL_LINE_DTR;
}


void main()
{   
	uint8 i = 0;
	uint16 rpt_pkt=0;
	XDATA uint32 tmp_ms = 0;
	systemInit();
	//initialise the USB port
	usbInit();
	//initialise sleep library
	sleepInit();
	// implement the USB line State Changed callback function.  
	usbComRequestLineStateChangeNotification(LineStateChangeCallback);
	setPort1PullType(LOW);
	setDigitalInput(12,PULLED);
	//initialise Anlogue Input 0
	P0INP = 0x1;
	// turn Red LED on to let people know we have started and have power.
	LED_RED(1);
	//delay for 30 seconds to get putty up.
//	waitDoingServices(10000,0,1);
	waitDoingServices(30000,1);
	LED_RED(0);
	printf_fast("Starting\r\n");
	
	// Open the UART and set it up for comms to HM-10
	// MAIN LOOP
	// initialise the Radio Regisers
	while (1)
	{
		uint8 savedPICTL = PICTL;
		while(uart1TxAvailable() < 255)
			doServices(1);
		// turn the wixel LEDS off
		LED_RED(0);
		LED_YELLOW(0);
		LED_GREEN(0);
		// sleep for around 300s
		printf_fast("%lu - sleeping for %u\r\n", getMs(), 300);
		goToSleep(300);   //
		// Enable suspend detection and disable any other weird features.
		USBPOW = 1;
		// Without this, we USBCIF.SUSPENDIF will not get set (the datasheet is incomplete).
		USBCIE = 0b0111;
		printf_fast("%lu - awake!\r\n", getMs());
		LED_RED(1);
		waitDoingServices(2000,1);
	}
}

#11

Hello.

I could not run your code because the call to usbComRequestLineStateChangeNotification is not part of our Wixel SDK and you didn’t specify which third-party Wixel SDK you are using. I think it would be better if you could write your program using the latest commit in our version of the SDK so that there is less chance for problems to be caused by SDK modifications, and we can just focus on the code in one file instead of worrying about all of the code in the SDK.

I also think that your code is still far too complicated for something that is just supposed to demonstrate that WORTIME0 and WORTIME1 cannot be read correctly. You are doing lots of stuff to turn the USB connection on and off, so I think it would be better to just take out all of the USB code and do all communication with a UART. If the bug that you are working around using DMA only has a very small chance of happening, it would be better to remove all the complex DMA code that attempts to work around the bug for the sake of this test. You shouldn’t need to redefine time-keeping functions from the Wixel SDK, or use preprocessor macros like waitDoingServicesInterruptible to generate lots of code. I don’t know why you are sleeping in two different ways depending on the state of your usb_connected flag. I’m not sure why you are reading WORTIME0 and WORTIME1 in the sleep timer ISR instead of reading it in the main loop: there is a chance that the ISR is not even running, or it is running at the wrong time.

A while ago, I posted some code for putting the Wixel into power mode 2, based on section 12.1.3 of the datasheet:

SLEEP = (SLEEP & ~3) | 2;    // SLEEP.MODE = 2 : Selects Power Mode 2 (PM2).
__asm nop __endasm; __asm nop __endasm; __asm nop __endasm;
if (SLEEP & 3)
{
    PCON |= 1;    // PCON.IDLE = 1 : Actually go to sleep.
}

I’d suggest that you write a new, simple program that uses the UART library to listen for a certain command character, and when it gets that character, it sets up the sleep timer, goes to sleep using the code above, wakes up, and then uses printf to send the values of WORTIME0 and WORTIME1 to the UART, and then goes back to listening for commands. You can use one of the LEDs to indicate whether you are sleeping or not, so you can verify that going to sleep and waking up at the right time really worked.

–David


#12

Hi David,
Whilst I take some of your points, I have the following comments.

  1. Not knowing the reason why putting the CC2511 into PM1 when connected to the USB and PM2 when not, is not a good reason to criticise the code. Apologies if you did not mean it in that way. I can only use a USB terminal for debugging, as the UARTs are already occupied in the rest of the application, and I thought that providing the full sleep code to you, rather than potentially causing the WORTIME reads to function inadvertently was the best way to appraise you of the code itself.
  2. The methods of putting the wixel to sleep, though complex, are required. As per TI’s DN106. Failure to use these methods results (in the case of the CC2511) in the SoC intermittently getting into a state where it never returns to active mode. I was unable to make the code consistently and reliably wake, without intermittently having to be power cycled, without this additional code. Yes, it may wake 10-1000 times before this state happens, but it will happen without the “complex” code.

Yes I did forget that I was using a modified SDK. I am happy to provide the SDK link for you. It was merged with the latest commit of your SDK a few weeks ago, so there is little difference in the underlying SDK rather than a few extra functions. At some point in time I will be making this code stand alone. The work is based on this original third party SDK, and if that is the source of the issue, I would still require that the problem be solved in that SDK until I make the code stand alone.

Thanks for your thoughts and assistance.
I will do what I can to comply with your request, but will still require the USB for debugging at my end until I can procure a 3.3v USB/UART adaptor.
Cheers


#13

I looked into it a bit and came up with the following code to demonstrate how to put the Wixel to sleep and measure the time spent sleeping. I’ve tested it and it seems to work fine. I think the comments in the code should explain pretty well how it works, but let me know if you have any questions. If you want to try it out, you might consider using the Wixel SDK’s usb_serial app to turn an extra Wixel into a USB-to-serial adapter.

// Pins:
// - P1_5: Early wake-up input
// - P1_6: TX
// - P1_7: RX

#include <wixel.h>
#include <uart1.h>
#include <stdio.h>
#include <stdint.h>

// Sleep timer interrupt: This interrupt needs to be enabled so that we can wake
// up the CPU with the sleep timer, but we don't want to actually do anything
// in the ISR.  Just clear the interrupt flag so that the interrupt is not
// constantly happening and preventing the CPU from going to sleep.
ISR(ST, 1)
{
    STIF = 0;
}

// Port 1 interrupt: Just clear the interrupt flag.
ISR(P1INT, 1)
{
    P1IFG = 0;
    P1IF = 0;
}

void waitForSleepTimerChange()
{
    uint8_t temp = WORTIME0;
    while (temp == WORTIME0);
}

// Goes to sleep and returns the time spent sleeping, in milliseconds.
uint16_t testWortime()
{
    uint16_t sleepTime;
    uint16_t timeSlept;

    // WOR_RES = 1: Configure the sleep timer resolution to be 1 ms.
    WORCTRL = 0b00000001;

    // Configure the Event 0 time period to be 2000 ms.  When WORTIME1/0 reaches
    // WOREVT1/0, Event 0 happens and WORTIME1/0 get reset to 0.
    sleepTime = 2000;
    WOREVT1 = sleepTime >> 8 & 0xFF;
    WOREVT0 = sleepTime & 0xFF;

    // Reset the sleep timer to 0 so that the next Event 0 will happen in a
    // predictable amount of time from now.  We have to wait for two rising
    // edges to make sure the timer has had enough time to actually reset.
    WORCTRL |= 0x04;
    waitForSleepTimerChange();
    waitForSleepTimerChange();

    // Clear the Event 0 flag and unmask Event 0.  We also need to enable the
    // sleep timer interrupt so that Event 0 will wake up the CPU.
    WORIRQ = 0b00010000;
    STIE = 1;

    LED_RED(0);  // For debugging.

    // Now follow the procedure from section 12.1.3 of the datasheet to go to
    // sleep.
    SLEEP = (SLEEP & ~3) | 1;    // Selects Power Mode 1 (PM1).
    __asm nop __endasm; __asm nop __endasm; __asm nop __endasm;
    if (SLEEP & 3)
    {
        PCON |= 1;    // PCON.IDLE = 1 : Actually go to sleep.
    }

    LED_RED(1);  // For debugging.

    // Wait for the high-speed crystal oscillator to be stable
    // (SLEEP.XOSC_STB=1), so we don't try to continue running our program at
    // the wrong speed
    while (!(SLEEP & (1 << 6)));

    // Disable interrupts and wait for a rising edge on WORTIME0 so we can read
    // the state of our timer without worrying about an Event 0 happening as we
    // do that.
    EA = 0;
    waitForSleepTimerChange();

    // Record the time that we spent sleeping.
    timeSlept = WORTIME0 | (WORTIME1 << 8);
    if (WORIRQ & 1)  // check EVENT0_FLAG
    {
        timeSlept += WOREVT0 | (WOREVT1 << 8);
    }

    // Re-enable interrupts.
    EA = 1;

    return timeSlept;
}

void updateLeds()
{
    LED_GREEN(1);
    LED_YELLOW(getMs() >> 8 & 1);
    LED_RED(1);
}

void putchar(char c)
{
    uart1TxSendByte(c);
}

void commandService()
{
    uint8_t c;

    if (!uart1RxAvailable() || uart1TxAvailable() < 16) { return; }

    c = uart1RxReceiveByte();

    if (c == 'g')
    {
        uint16_t result = testWortime();
        printf("r: %d\r\n", result);
        WORIRQ = 0;
    }
}

void wakeupPinInit()
{
    // Enable P1_5 as a pulled-up input.
    setDigitalInput(15, PULLED);

    // Enable a falling edge interrupt on P1_5 so we can wake up the CPU before
    // the sleep timer does.
    PICTL |= (1 << 1); // P1ICON = 1: Falling edge on Port 1 inputs gives interrupt.
    P1IEN |= (1 << 5); // Enable P1_5 interrupt
    IEN2 |= (1 << 4);  // P1IE = 1: enable Port 1 interrupts
}

void main()
{
    systemInit();
    uart1Init();
    uart1SetBaudRate(9600);
    wakeupPinInit();

    while(1)
    {
        boardService();
        updateLeds();
        commandService();
    }
}

Now that I am more familiar with the sleep timer, I can point out what might be the main issue with your code. The sleep timer interrupt will only fire as a result of Event 0 happening, and Event 0 also clears the sleep timer count (WORTIME1/0), so that could be why those registers were always zero when you tried to read them in the ST interrupt. You need to look at the EVENT0_FLAG bit to see if an Event 0 has happened since you went to sleep, and if it has, then add the Event 0 time. To test that the code for that is actually correct, you need a way to wake up the Wixel early before the sleep timer wakes it up.

By the way, I used PM1 in my code because I noticed the Wixel was sometimes not waking up from PM2, probably due to that CC2511 issue you mentioned. I did test PM2 and found that if the Wixel does wake up, the time measurement code seems to work too.

–David