Adam Pippin
4 years ago
1 changed files with 239 additions and 0 deletions
@ -0,0 +1,239 @@ |
|||
# watchos |
|||
|
|||
A simple framework for developing on the M5StickC. |
|||
|
|||
## Features |
|||
|
|||
* Kernel which handles task scheduling, running, signalling, and more. |
|||
* Eeprom abstraction layer which creates an allocation table to allow multiple |
|||
modules to share storage without hardcoding offsets or addresses everywhere. |
|||
* User Input module which handles tracking button presses, debouncing, and |
|||
converting the inputs into inter-process signals to provide a unified way |
|||
for handling events. |
|||
* Interface library which provides an easy way to handle creating tiling layouts |
|||
and rotation. |
|||
* Power management library to handle screen dimming and putting the processor |
|||
into deep sleep to conserve battery while giving tasks an opportunity to |
|||
save their state. |
|||
* The base system uses less than 10% of the ESP32's storage and 5% of its RAM. |
|||
|
|||
### Kernel |
|||
|
|||
The Kernel is the core of watchos. It provides a kind of cooperative multitasking. |
|||
|
|||
Tasks can be registered and set to run on a schedule or based on interrupts. Tasks |
|||
are given the opportunity for clean startup and shutdown, to handle user input, |
|||
to handle updating the display, and more. Tasks can be paused and resumed and |
|||
exited tasks can return an exit code consumable by other processes. |
|||
|
|||
The Kernel tries to intelligently conserve power by putting the processor into |
|||
light sleep in between scheduled executions, while making use of hardware features |
|||
to wake up sooner if events of interest occur. |
|||
|
|||
### EAT |
|||
|
|||
The "EEPROM Allocation Table" a la FAT/File Allocation Table. A few bytes are |
|||
reserved at the start of flash storage to track how many bytes have been allocated |
|||
and to which module. A read/write abstraction is provided to allow modules to |
|||
read/write from their allocated space. This serves to prevent conflicts between |
|||
multiple independent pieces of code attempting to use the storage device. |
|||
|
|||
### User Input |
|||
|
|||
A simple module that tracks if either button has been pressed, handles debouncing |
|||
(not detecting another press until they have first been released), and sends |
|||
interprocess signals to other processes when the events occur. This simplifies |
|||
input handling and prevents the need for duplication of things like debounce |
|||
logic. |
|||
|
|||
### Interface |
|||
|
|||
A take on a tiling window manager supplies a simple way to lay out multiple |
|||
modules on the display in a flexible way as well as provide for adjusting the |
|||
layout for various display rotations. Each module can register as many screen |
|||
sections as it wants as well as as many nested tiles as it wants in order to |
|||
create a layout. |
|||
|
|||
### Power Management |
|||
|
|||
A simple power management library which handles tracking idle time and taking |
|||
appropriate actions when the device is no longer in use. After a short delay, |
|||
the display's backlight is dimmed. After a longer delay it will signal all |
|||
running tasks to exit (giving them a chance to save state to eeprom or quickly |
|||
finish any outstanding tasks) then the processor will be placed into deep sleep |
|||
and the display and backlight powered off. This, combined with the Kernel's |
|||
intelligent use of light sleep helps to extend the M5StickC's battery life well |
|||
beyond what is typically expected. |
|||
|
|||
### Resource Efficient |
|||
|
|||
The base system (Kernel, EAT, User Input, User Interface, Power Management) uses |
|||
only around 10% of available program flash and 5% of dynamic memory leaving |
|||
plenty of room to build on top of. |
|||
|
|||
## Getting Started |
|||
|
|||
### Your App |
|||
|
|||
Include all of the watchos files in your project, then simply initialize the |
|||
kernel and register your tasks in your `setup()` method, then call the kernel |
|||
during each invocation of your `loop()` method. For example: |
|||
|
|||
``` |
|||
#include <M5StickC.h> |
|||
#include "Kernel.h" |
|||
|
|||
int MyTask(int pid, unsigned int signal) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
void setup() |
|||
{ |
|||
M5.begin(); |
|||
Kernel_setup(); |
|||
Kernel_start(&MyTask, 5000); |
|||
} |
|||
|
|||
void loop() |
|||
{ |
|||
Kernel_tick(); |
|||
} |
|||
``` |
|||
|
|||
If your tasks make use of the EAT module, it will need to be initialized before |
|||
first use and a call to load the allocation table will need to be made on startup. |
|||
You can either add the initialization call to run once then remove from your code |
|||
later, or provide the ability to trigger initialization by, for example, holding |
|||
a button during power up. For example: |
|||
|
|||
``` |
|||
void setup() |
|||
{ |
|||
M5.begin(); |
|||
Kernel_setup(); |
|||
Kernel_start(&MyTask, 5000); |
|||
|
|||
M5.update(); |
|||
if (M5.BtnB.isPressed()) |
|||
{ |
|||
EAT_initialize(); |
|||
} |
|||
else |
|||
{ |
|||
EAT_load(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
If the side button is held during power up, the EEPROM will be reinitialized with |
|||
an empty allocation table, otherwise the existing table will be loaded (causing a |
|||
panic if no table exits or is valid). |
|||
|
|||
### Writing Your Task |
|||
|
|||
Each task method requires a simple signature: |
|||
|
|||
``` |
|||
int MyTask(int pid, unsigned int signal) |
|||
{ |
|||
[...] |
|||
} |
|||
``` |
|||
|
|||
This method takes its own process id and any pending signals as arguments, and |
|||
is expected to return an exit code as its return value. |
|||
|
|||
The id is generated each time the task is started. If you expect to start a |
|||
task multiple times, this allows you to manage keeping independent data stores |
|||
for each invocation. |
|||
|
|||
The signals are a way for the kernel and other tasks to notify you of events. |
|||
This will tell you whether your task was just started, is being requested to |
|||
shutdown, has reached its invocation interval, needs to redraw the display, etc. |
|||
|
|||
The returned exit code should always be `0` if your program expects to continue. |
|||
Any other value will signal the kernel to stop executing your task, and that value |
|||
will be available to other running tasks. |
|||
|
|||
#### Signals |
|||
|
|||
Signals are defined in `Kernel.h` and include: |
|||
|
|||
* `SIGNAL_TICK` - your task's configured run interval has elapsed |
|||
* `SIGNAL_START` - this is the first invocation of your task method and it |
|||
should perform first-startup initialization; this will only be sent once |
|||
* `SIGNAL_STOP` - your task is requested to shutdown; you *should* return a |
|||
non-zero exit code in response to this, though if your task needs to continue |
|||
running for a brief time to finish shutting down cleanly it may return 0 and |
|||
quit on a later invocation |
|||
* `SIGNAL_INPUT_A` - the main (home) button has been pressed; only delivered |
|||
once per press |
|||
* `SIGNAL_INPUT_B` - the secondary (side) button has been pressed; only delivered |
|||
once per press |
|||
* `SIGNAL_REDRAW` - the display needs to be redrawn and you should redraw |
|||
anything you have on the screen -- you should only ever perform draws in |
|||
response to this signal |
|||
|
|||
To see the full benefit of this system, you should read and understand the |
|||
further documentation on the kernel and how exactly you should use and respond |
|||
to these signals. |
|||
|
|||
##### Signal Mask |
|||
|
|||
Your application may not be interested in all of these signals. To avoid your |
|||
method being called unnecessarily, you can set a signal mask -- this tells the |
|||
kernel what signals you're interested in. The mask should include all signals |
|||
you *do* want to receive. For example: |
|||
|
|||
``` |
|||
Kernel_signal_mask(mypid, SIGNAL_START | SIGNAL_STOP | SIGNAL_TICK); |
|||
``` |
|||
|
|||
#### Example |
|||
|
|||
A simple framework for a method: |
|||
|
|||
``` |
|||
#include "Kernel.h" |
|||
|
|||
int MyTask(int pid, unsigned long signal) |
|||
{ |
|||
if (signal & SIGNAL_START) |
|||
{ |
|||
// Perform startup here |
|||
} |
|||
|
|||
if (signal & SIGNAL_STOP) |
|||
{ |
|||
// Perform shutdown here |
|||
return 255; |
|||
} |
|||
|
|||
if (signal & SIGNAL_TICK) |
|||
{ |
|||
// Your requested run interval has transpired; perform any regular |
|||
// logic here to update state. |
|||
|
|||
// If you need to update the screen, do *not* do it here, instead trigger |
|||
// a redraw so the entire screen can be cleared and redrawn: |
|||
Kernel_signal(SIGNAL_REDRAW); |
|||
// Your application will also receive this REDRAW signal and can perform its |
|||
// draws in response to that. |
|||
} |
|||
|
|||
if (signal & SIGNAL_REDRAW) |
|||
{ |
|||
// Update the screen from your state variables here. |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
``` |
|||
|
|||
## TODO: |
|||
|
|||
* Better kernel module system to support EAT as well as wifi/bluetooth/etc. |
|||
* More efficient draws: if we're using the tiling wm, no reason we need to |
|||
clear the entire screen; if we know which parts need a redraw we could |
|||
instead drawRect and only redraw those. |
Loading…
Reference in new issue