Waking a PIC Device
from Sleep

The PIC microcontroller devices are capable of very low current operation by placing them into a dormant mode via the Sleep command. They can then draw as little as a few microAmps of current. Forget all the adverts about NanoWatt currents etc, because microcontrollers are designed to operate in the "real world", and have components around them that also take small currents, and are not just used in a laboratory with nothing else attached to them. :-)

Once a device is placed into sleep mode, there are many ways of waking it up to continue its tasks, and the list is quite long so I will not place them in this article. However, a read of the device's datasheet will clarify what can wake up a device. The two main mechanisms of waking a device from sleep are either the internal watchdog timer, timing out, or an external influence on one of the device's pins via an internal peripheral module. This article is for waking a device from sleep mode using the INT peripherals, that detect a rising or falling edge on a pin, and if the edge is correct, wake up the device. All the PIC microcontrollers have an INT peripheral, and most of the newer devices have an INT peripheral for all the I/O pins they contain, so the method is extremely efficient in operation, and very simple to implement in The Positron language. Click on the window below to view the Positron8 code lisitng for a demonstration of waking a PIC18F25K20 device from its sleep mode using its INT peripherals.

Click Here to view the Wake a Device using the INTx Peripherals, Positron8 code listing

'
' /\\\\\\\\\
' /\\\///////\\\
' \/\\\ \/\\\ /\\\ /\\\
' \/\\\\\\\\\\\/ /\\\\\ /\\\\\\\\\\ /\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\\\ /\\\\\\\\\
' \/\\\//////\\\ /\\\///\\\ \/\\\////// /\\\/////\\\ \////\\\//// \////\\\//// \////////\\\
' \/\\\ \//\\\ /\\\ \//\\\ \/\\\\\\\\\\ /\\\\\\\\\\\ \/\\\ \/\\\ /\\\\\\\\\\
' \/\\\ \//\\\ \//\\\ /\\\ \////////\\\ \//\\/////// \/\\\ /\\ \/\\\ /\\ /\\\/////\\\
' \/\\\ \//\\\ \///\\\\\/ /\\\\\\\\\\ \//\\\\\\\\\\ \//\\\\\ \//\\\\\ \//\\\\\\\\/\\
' \/// \/// \///// \////////// \////////// \///// \///// \////////\//
' Let's find out together what makes a PIC Tick!
'
' Demonstrate waking from sleep via an INT0, INT1 or INT2 event caused by PORTB.0, PORTB.1 or PORTB.2 being brought to ground.
' This is known as a falling edge, and can be changed to a rising edge via a change of the meta-macros used in the program.
'
' The SFRs and bit names are for a PIC18FX5K20 devices and will need changing for various other PIC devices.
'
' Written for the Positron8 BASIC Compiler by Les Johnson.
'

Device = 18F25K20 ' Tell the compiler what device to compile for
Declare Xtal = 64 ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Setup USART1
'

Declare HSerial1_Baud = 9600 ' Set USART1 Baud rate to 9600
Declare HRsout1_Pin = PORTC.6 ' Tell the compiler what pin is used for USART1 TX
'
' Setup the peripheral pins
'

Symbol LED_Pin = PORTC.0 ' The LED attaches to this pin
Symbol Button0_Pin = PORTB.0 ' The button attached to this pin is for an INT0 event
Symbol Button1_Pin = PORTB.1 ' The button attached to this pin is for an INT1 event
Symbol Button2_Pin = PORTB.2 ' The button attached to this pin is for an INT2 event
'
' Create some meta-macros for PIC18FX5K20 devices to make the code easier to follow and change
'------------------------------------------------------------------
' Global Interrupt meta-macros for PIC18Fx5K20 devices
'

$define Int_Global_Enable() INTCONbits_GIE = 1 ' Enable global interrupts
$define Int_Global_Disable() INTCONbits_GIE = 0 ' Disable global interrupts
$define Int_Periph_Enable() INTCONbits_PEIE = 1 ' Enable peripheral interrupts
$define Int_Periph_Disable() INTCONbits_PEIE = 0 ' Disable peripheral interrupts
'------------------------------------------------------------------
' INT0 meta-macros for PIC18Fx5K20 devices
'

$define INT0_IntFlag INTCONbits_INT0IF ' The flag for an INT0 event on pin PORTB.0
$define INT0_Enable() INTCONbits_INT0IE = 1 ' Enable the INT0 interrupt
$define INT0_Disable() INTCONbits_INT0IE = 0 ' Disable the INT0 interrupt
$define INT0_Rising() INTCON2bits_INTEDG0 = 1 ' Trigger an INT0 event on a rising edge
$define INT0_Falling() INTCON2bits_INTEDG0 = 0 ' Trigger an INT0 event on a falling edge
$
define INT0_ClearIntFlag() INT0_IntFlag = 0 ' Clear the INT0 flag
'------------------------------------------------------------------
' INT1 meta-macros for PIC18Fx5K20 devices
'

$define INT1_IntFlag INTCON3bits_INT1IF ' The flag for an INT1 event on pin PORTB.1
$define INT1_Enable() INTCON3bits_INT1IE = 1 ' Enable the INT1 interrupt
$define INT1_Disable() INTCON3bits_INT1IE = 0 ' Disable the INT1 interrupt
$define INT1_Rising() INTCON2bits_INTEDG1 = 1 ' Trigger an INT1 event on a rising edge
$define INT1_Falling() INTCON2bits_INTEDG1 = 0 ' Trigger an INT1 event on a falling edge
$define INT1_ClearIntFlag() INT1_IntFlag = 0 ' Clear the INT1 flag
'------------------------------------------------------------------
' INT2 meta-macros for PIC18Fx5K20 devices
'
$define INT2_IntFlag INTCON3bits_INT2IF ' The flag for an INT2 event on pin PORTB.2
$define INT2_Enable() INTCON3bits_INT2IE = 1 ' Enable the INT2 interrupt
$define INT2_Disable() INTCON3bits_INT2IE = 0 ' Disable the INT2 interrupt
$define INT2_Rising() INTCON2bits_INTEDG2 = 1 ' Trigger an INT2 event on a rising edge
$define INT2_Falling() INTCON2bits_INTEDG2 = 0 ' Trigger an INT2 event on a falling edge
$define INT2_ClearIntFlag() INT2_IntFlag = 0 ' Clear the INT2 flag

'------------------------------------------------------------------------------
' The main program starts here
' Place the microcontroller to sleep and wake via a falling edge on either INT0 or INT1 or INT2
' INT0 is PORTB.0 on this device, and INT1 is PORTB.1, and INT2 is PORTB.2
'

Main:
Setup() ' Setup the program and the peripherals

Do ' Create a loop
HRsoutLn "Waiting for Button Press to Wake Up"
PinLow LED_Pin ' Extinguish the LED
INT0_ClearIntFlag() ' Clear the INT0 flag before going to sleep
INT1_ClearIntFlag() ' Clear the INT1 flag before going to sleep
INT2_ClearIntFlag() ' Clear the INT2 flag before going to sleep
Sleep ' Put the microcontroller to sleep
PinHigh LED_Pin ' Illuminate the LED when the device wakes up
If INT0_IntFlag = 1 Then ' Was it an INT0 event that woke the device up?
HRsoutLn "Device Now Awake via INT0" ' Yes. So transmit to a serial terminal
Elseif INT1_IntFlag = 1 Then ' Was it an INT1 event that woke the device up?
HRsoutLn "Device Now Awake via INT1" ' Yes. So transmit to a serial terminal
Elseif INT2_IntFlag = 1 Then ' Was it an INT2 event that woke the device up?
HRsoutLn "Device Now Awake via INT2" ' Yes. So transmit to a serial terminal
EndIf
DelayMS 200 ' A delay after the device wakes up, so the LED can be seen to flash
Loop ' Do it forever

'------------------------------------------------------------------------------
' Setup the program and peripherals
' Input : None
' Output : None
' Notes : To change the edge of an INTx, change the INTx_Falling() to INTx_Rising() macros
' And disable the internal pull-up resistors and add external pull-down resistors to the pins.
'

Proc Setup()
Low PORTA ' \
Low PORTB ' | Make all pins low to begin with, so they do not draw any current
Low PORTC ' /

PinMode(Button0_Pin, Input_Pullup) ' Make the INT0 pin an input and enable its internal pull-up resistor
PinMode(Button1_Pin, Input_Pullup) ' Make the INT1 pin an input and enable its internal pull-up resistor
PinMode(Button2_Pin, Input_Pullup) ' Make the INT2 pin an input and enable its internal pull-up resistor
INT0_Falling() ' Trigger an INT0 event on a falling edge
INT1_Falling() ' Trigger an INT1 event on a falling edge
INT2_Falling() ' Trigger an INT2 event on a falling edge
INT0_ClearIntFlag() ' Clear the INT0 flag
INT1_ClearIntFlag() ' Clear the INT1 flag
INT2_ClearIntFlag() ' Clear the INT2 flag
INT0_Enable() ' Enable the INT0 peripheral
INT1_Enable() ' Enable the INT1 peripheral
INT2_Enable() ' Enable the INT2 peripheral
EndProc

'------------------------------------------------------------------------------
' Setup the fuses for the 4xPLL with an external crystal on a PIC18F25K20 device

' This makes a 16MHz crystal, operate the device at 64MHz
'

Config_Start
FOSC = HSPLL ' HS oscillator, PLL enabled and under software control
STVREN = Off ' Reset on stack overflow/underflow disabled
WDTEN = Off ' WDT disabled (control is placed on SWDTEN bit)
FCMEN = Off ' Fail-Safe Clock Monitor disabled
IESO = Off ' Two-Speed Start-up disabled
WDTPS = 128 ' Watchdog is 1:128
BOREN = Off ' Brown-out Reset disabled in hardware and software
BORV = 18 ' VBOR set to 1.8 V nominal
MCLRE = On ' MCLR pin enabled, RE3 input pin disabled
HFOFST = Off ' The system clock is held Off until the HF-INTOSC is stable
LPT1OSC = Off ' T1 operates in standard power mode
PBADEN = Off ' PORTB<4:0> pins are configured as digital I/O on Reset
CCP2MX = PORTC ' CCP2 input/output is multiplexed with RC1
LVP = Off ' Single-Supply ICSP disabled
DEBUG = Off ' Background debugger disabled. RB6 and RB7 configured as general purpose I/O pins
XINST = Off ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
CP0 = Off ' Block 0 (000800-001FFF) not code-protected
CP1 = Off ' Block 1 (002000-003FFF) not code-protected
CPB = Off ' Boot block (000000-0007FF) not code-protected
CPD = Off ' Data EEPROM not code-protected
WRT0 = Off ' Block 0 (000800-001FFF) not write-protected
WRT1 = Off ' Block 1 (002000-003FFF) not write-protected
WRTB = Off ' Boot block (000000-0007FF) not write-protected
WRTC = Off ' Configuration registers (300000-3000FF) not write-protected
WRTD = Off ' Data EEPROM not write-protected
EBTR0 = Off ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
EBTR1 = Off ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
EBTRB = Off ' Boot block (000000-0007FF) not protected from table reads executed in other blocks
Config_End

As can be seen in the Positron8 code listing above, the Positron language makes things look very clear and is easily understood. Just like the INT events are when written in a clear language.

The
meta-macros in the code listing are preprocessor macros and are included so that their contents can be easily changed for different PIC devices that use different SFRs (Special Function Registers) and bits for their peripherals. Or more meta-macros can be added for devices with more INT event peripherals. Meaning the main code does not need to be changed as much, because it will still use the meta-macros with their names unchanged. The INT mechanism uses the same methodology on all PIC microcontrollers, so the demo program above will work on them when the SFRs are changed to suit the device being used, if required.

Note that even though the INT peripherals have SFR flags and bit names that reference the word "Interrupt", global interrupts are not required to be enabled when they are used, but if interrupts are enabled, they will fire an interrupt event when triggered. So if your program is using interrupts, remember that one will be triggered, and always trap what peripheral caused the interrupt event within the interrupt handler, so that another peripheral's interrupt is not triggered by an INT event.

Shown below is the above demonstration code listing operating in the Proteus simulator. The simulated circuit will flash the LED when a button is pressed and the device is woken up from its Sleep, and transmit to a serial terminal what INT event caused the device to wake up, depending on what INT pin the button is attached too. With simple changes to the code, the device can wake up from all sorts of external peripherals altering a pin's state high or low, and the program will know what peripheral woke it up and operate the specific task for the specific peripheral.

The demonstration Positron8 source code can be downloaded from here: Wake_From_Sleep_Via_INTx.zip. The zip file also includes the Proteus simulator project file.