Adding Waking from Deep Sleep to Circuit Python - BitKnitting/wakey_circuitpython GitHub Wiki
- CP = Circuit Python
- samd21 datasheet
- The micropython documentation has an excellent section on writing a module. If you are following along, you'll want to understand/follow the steps outlined.
- Follow Dan Halbert's "Build CircuitPython" tutorial. This gets a fork of the CircuitPython source on our local machine.
- Debugging the SAMD21 with GDB
I am using Adafruit's ItsyBitsy m0 Express for my project. It runs on a battery. For the majority of the time, the CP code sits around. Every once and awhile - about twice a day - a motion detector sets off an interrupt. The CP code does a few things, then goes back to waiting around. Frankly, a pretty easy (potentially boring) job. Perfect for the ItsyBitsy... unfortunately ...
While it is sitting there, the code quickly gobbles up the battery's energy. Its as if the code is constantly running on a treadmill, going nowhere, but depleting all energy. This means I have to recharge, and recharge, and recharge, and recharge....the battery. Recharging is work I want to avoid. But ... challenges bring opportunities ...
Write a CP module extension in C that exposes a method that can put the Itsy Bitsy's SamD21G18A chip into standby (aka deep sleep) mode. By doing so, the code gobbles up far less energy. Far less energy consumption means I don't have to charge the battery as often.
Our goal is to get the following CP to work:
import board
import wakey
wakey.zzz(board.D12)
The zzz function will config the EIC to wake up when an interrupt happens on D12. To do this, we must:
- Write C code to call C Functions from a CP module. (e.g.: wakey.c )
- Write a C Function within the C code that initializes / configures the SamD21G18A to go into standby mode and wake up when an external interrupt occurs. For example, a motion detector detects a motion and changes the voltage on one of the GPIO pins. (e.g.: config_EIC in wakey_helpers.c )
- Wrap the C Function within the C "goo".
- Stuff the C "goo" with the function into the CP build.
- Use the function from within CP.
- Be kind.
I want to start with the latest stable release. This is CircuitPython 3.0.1 (tag = 3.0.1, commit = 182a8d7)
- Make a repos folder on my Mac and cd into it.
$ $ git clone --recursive -j8 https://github.com/adafruit/circuitpython.git
$ cd circuitpython
-
$ git checkout tags/3.0.1
After doing this and attempting to build, I got:
$ make BOARD=itsybitsy_m0_express DEBUG=1
Use make V=1, make V=2 or set BUILD_VERBOSE similarly in your environment to increase build verbosity.
/bin/sh: tools/gen_usb_descriptor.py: Permission denied
make: *** [autogen_usb_descriptor.intermediate] Error 126
To overcome this, I:
$ git fetch https://github.com/adafruit/circuitpython.git pull/1123/head:pr_112
From https://github.com/adafruit/circuitpython
* [new ref] refs/pull/1123/head -> pr_112
$ git checkout pr_112
At this point, I had modified the Makefile and mpconfigport.h files:
error: Your local changes to the following files would be overwritten by checkout:
ports/atmel-samd/Makefile
ports/atmel-samd/mpconfigport.h
Please commit your changes or stash them before you switch branches.
So I stashed them... I mention this here because I hadn't used git stash
before.
- Loosely follow the micropython documentation.
See wakey.c
See wakey_helpers.c. The underlying C code for the wakey.zzz(board.D) method:
- Sets up the GPIO pin as: 1) input 2) pull down (our trigger will be rising from LOW to HIGH) 3) pin mux mapped to the pin being used for an external interrupt.
- Sets up the EIC (External Interrupt Controller Peripheral). If you are like me when I started learning this stuff, certainly the SamD21 data sheet has "all you need to know" in engineer-speak lingo. I also found this YouTube video on the EIC to be helpful. Setting up the EIC consists of:
- Setting the external interrupt channel's EIC->WAKEUP.reg bit to 1. This way, the CPU knows we want to respond to the interrupt when the CPU is in standby mode.
- Configuring the voltage change we want the interrupt to trigger on. I chose when the voltage goes from LOW to HIGH.
- Enabling the external interrupt channel bit in the INTENSET register.
### The Callback
* startup_samd21.c identifies as ```*pfnEIC_Handler = (void *)EIC_Handler```
* EIC_Handler function is defined in ```peripherals/samd/samd21/external_interrupts.c```
* EIC_Handler calls the ```external_interrupt_handler``` which is a different c source than the step above. This ```external_interrupts.c``` is located one folder up at ```peripherals/samd/external_interrupts.c```
* The ```external_interrupt_handler``` needs to be modified to call back our call back function, e.g.:
void external_interrupt_handler(uint8_t channel) { uint8_t handler = channel_handler[channel]; if (handler == EIC_HANDLER_PULSEIN) { pulsein_interrupt_handler(channel); } else if (handler == EIC_HANDLER_INCREMENTAL_ENCODER) { incrementalencoder_interrupt_handler(channel); } else if (handler == EIC_HANDLER_WAKEY) { wakey_interrupt_handler(channel); } EIC->INTFLAG.reg = (1 << channel) << EIC_INTFLAG_EXTINT_Pos; }
## Put the SamD21 into standby mode
void time_to_sleep(void){ __DSB(); // Complete any pending buffer writes. SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __WFI(); }
## Handle Wake Up
When the CPU wakes up, CP will get a call to ```EIC_Handler```. Earlier, we had tied in our callback function:
// Callback void wakey_interrupt_handler(uint8_t channel) {
volatile uint32_t flags = hri_eic_read_INTFLAG_reg(EIC) & hri_eic_read_INTEN_reg(EIC); hri_eic_clear_INTFLAG_reg(EIC, flags); }