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);
}
}