Browse Source

Add README

master
Adam Pippin 4 years ago
parent
commit
c56d1aa0b2
  1. 239
      README.md

239
README.md

@ -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…
Cancel
Save