-
Notifications
You must be signed in to change notification settings - Fork 3
Features
The Loom library strives to be your one stop shop for embedded device rapid prototyping and design. Detailed below is a comprehensive list of features Loom offers, as well as a usage overview, stability, and links to our comprehensive documentation.
While there will always be variation in functionality from device to device, there are some operations which are inherent to any embedded system, and for those we have created Sub-Managers, to take as much guesswork as possible out of the process of managing them.
The Loom_Interrupt_Manager
is used to manage interrupts of a variety of types (e.g. external, RTC alarm, internal timer, internal stopwatch).
- HIGH
- LOW
- RISING
- FALLING
- CHANGE (rising or falling)
Note: If the device is asleep, only HIGH and LOW can be detected.
Interrupts need to be associated with an interrupt service routine (ISR). An ISR is a basic function that runs immediately when the interrupt happens, temporarily jumping from whatever the program was currently executing, before returning to what it was doing. As such, you want to keep your ISRs as simple as possible.
Things to generally avoid in ISRs (see Check Flag ISRs if you want to use the following):
- Serial prints
- Delays
- Function calls
The interrupt manager supports two types of ISRs, immediate and flag-based, the difference lying in when they are run.
Immediate: The ISR will run in its entirety immediately upon the interrupt triggering
Check Flag: When the interrupt triggers, a default ISR will set a flag indicating that the ISR you provided should be run when run_pending_ISRs()
is called (your ISR is known as an ISR bottom half). Use this if the execution of your ISR can wait until the next iteration of loop()
; if it is relatively long; or if you want to use commands like Serial prints, delay, or function calls. These ISRs are similar to setting a flag in an immediate ISR and running code in an if (flag) { … }
, either approach is acceptable.
In the case of a basic external interrupt (just a HIGH or LOW signal), the ISR does not need any content, the following would suffice:
void wakeISR() {}
However, you may want to detach the interrupt to prevent it from triggering a few times (though that would not impact program behavior)
void wakeISR() {
detachInterrupt(digitalPinToInterrupt(WAKE_PIN));
// where WAKE_PIN is the pin the interrupt is connected to
}
If your interrupt comes from an RTC, you need to detach the interrupt to prevent it from triggering repeatedly (as the RTC outputs a squarewave to the interrupt pin), essentially freezing the program
void wakeISR_RTC() {
detachInterrupt(digitalPinToInterrupt(RTC_PIN));
// where RTC_PIN is the pin the interrupt is connected to
}
If you have multiple sources of interrupts you will likely want to use flags (boolean variables) in the ISRs so that the rest of the program can be behave according to which interrupt fired.
For example, if you had both an RTC and a button that could trigger interrupts and the behavior to respond to each is different, you might use the following:
volatile alarmFlag = false;
void RTC_ISR() {
detachInterrupt(digitalPinToInterrupt(RTC_PIN));
alarmFlag = true;
}
volatile buttonFlag = false;
void buttonISR() {
detachInterrupt(digitalPinToInterrupt(BUTTON_PIN));
buttonFlag = true;
}
setup()
{
...
// register interrupts
...
}
loop()
{
...
if (alarmFlag) {
...
// Handle wake from alarm functionality
...
alarmFlag = false;
// reconnect alarm interrupt
}
if (buttonFlag) {
...
// Handle wake from button functionality
...
buttonFlag = false;
// reconnect button interrupt
}
...
}
If all the interrupts are intended to trigger the same behavior, the flags are not always necessary.
These methods are used for most associating ISRs with interrupts, and various common functionality. External interrupts can be implemented completely with the following. RTC and other interrupt will also make use of these methods.
Register an ISR to an interrupt pin and its configuration.
void register_ISR(byte pin, ISRFuncPtr ISR, byte signal_type, ISR_Type run_type);
pin: Which pin to connect the interrupt on ISR: ISR function (Null if no interrupt linked) signal_type: Low, High, Change, Falling, Rising run_type: Whether the interrupt runs immediately, else sets flag to check and runs ISR when flag checked
**Note: ** If your ISR detaches the interrupt with detachInterrupt
you will need to re-register the ISR for it to be triggered again.
Example:
This is not a complete example, intended only to show how you would register an ISR with an interrupt.
void alarmISR() {
detachInterrupt(digitalPinToInterrupt(ALARM_PIN));
alarmFlag = true;
}
void buttonISR() {
buttonFlag = true;
}
...
setup() {
...
Manager.InterruptManager().register_ISR(6, alarmISR, LOW, ISR_Type::IMMEDIATE);
Manager.InterruptManager().register_ISR(10, buttonISR, HIGH, ISR_Type::CHECK_FLAG);
...
}
Run any waiting ISRs. This applys to bottom half ISRs (those specified as ISR_Type::CHECK_FLAG
). If you don't call this method, any CHECK_FLAG
ISRs will not run. It also applies to some timer ISRs.
Manager.InterruptManager().run_pending_ISRs();
Restores pin to default ISR, disables interrupt.
// Remove ISR associated with pin 6
Manager.InterruptManager().unregister(6);
Detaches then reattacheds interrupt according to settings. Used to clear pending interrupts.
// Reset interrupt pin 6
Manager.InterruptManager().interrupt_reset(6);
Use these methods to set RTC alarms. Requires RTC hardware connected and a LoomRTC
module linked (done automatically if using LoomManager
, or set_RTC_module(LoomRTC*)
if manual).
Set RTC alarm an amount of time from now.
bool RTC_alarm_duration(TimeSpan duration);
bool RTC_alarm_duration(uint days, uint hours, uint minutes, uint seconds);
Example:
// duration of 300 seconds with TimeSpan
InterruptManager().RTC_alarm_duration(TimeSpan(300));
// or
// duration of 5 minutes
InterruptManager().RTC_alarm_duration(0, 0, 5, 0);
Set RTC alarm for a specific time.
bool RTC_alarm_at(DateTime future_time);
bool RTC_alarm_at(uint hour, uint minute, uint second);
Example:
// DateTime time
// Alarm at time specified by DateTime object `time`
InterruptManager().RTC_alarm_at(time);
// or
// Alarm at 5:30 pm
InterruptManager().RTC_alarm_at(17, 30, 0);
Set RTC alarm an amount of time from last alarm time.
bool RTC_alarm_duration_from_last(TimeSpan duration);
bool RTC_alarm_duration_from_last(uint days, uint hours, uint minutes, uint seconds);
Example:
// Alarm 300 seconds from last alarm time with TimeSpan
InterruptManager().RTC_alarm_duration_from_last(TimeSpan(300));
// or
// Alarm 5 minutes after last alarm
InterruptManager().RTC_alarm_duration_from_last(0, 0, 5, 0);
See the Interrupt Manager documentation for complete list of available methods.
The Loom_Sleep_Manager
is used to put the board into a low power or off state. It will also power off any hardware with its own power off functionality. In order to wake from sleep or turn back on, you must have an interrupt (or interrupts) setup to wake the device from sleep, see the Interrupt Manager section for details on doing this.
The board will go into low power mode and continue from the same location upon waking.
Manager.SleepManager.sleep();
// program will continue here upon waking
Important note: At this point, only the STANDBY mode is supported.
Note: see Example ISRs for the type of ISR to link to associate with an interrupt that wakes the device
The board will turn completely off, saving important information to flash or or SD, if you have information that you want to persist, make sure it is saved, because variables will be reset upon restart. The program will restart from setup()
upon waking. This operation assumes that you are using an OPEnS Low Power board to facilitate turning the device completely off and restarting with external interrupts / RTC.
Manager.SleepManager.powerDown();
// program will not run anything after powerDown if successful
// will instead restart at setup()
See the Sleep Manager documentation for complete list of available methods.
The multiplexer is a hardware shield (stacks on Arduino Feather) that allows you to easily attach several I^2^C sensors to your Loom device, rather than having to wire them all individually. The multiplexer has a corresponding LoomModule
that automates the management of any sensors currently plugged into it (can automatically detect new sensors and instantiate sensor modules to handle them).
While you can call the methods of the Loom_Multiplexer
module directly, you can generally just let the LoomManager
manage the multiplexer as well. In addition to typical LoomModule
methods, the multiplexer allows operations such as the following:
This returns a Json object listing the sensors currently plugged in.
// JsonObject sensorList
// Loom_Multiplexer mux
mux.get_sensor_list(sensorList);
// or
// LoomManager Manager
Manager.Multiplexer.get_sensor_list(sensorList);
This will force the multiplexer to update the sensor list, regardless of normal update interval, if any (note that automatic updating of sensors can be enabled by setting dynamic_list
to true
. The minimum time between updates is update_period
)
mux.refresh_sensors();
// or
Manager.Multiplexer.refresh_sensors();
You can get a pointer to a sensor plugged into a port of the multiplexer with:
int index = 0; // 0 to (number of multiplexer ports)-1, normally 8
LoomI2CSensor* sensor = mux.get_sensor(index);
// or
LoomI2CSensor* sensor = Manager.Multiplexer.get_sensor(index);
See the Multiplexer documentation for complete list of available methods.