CHRT¶
The main idea is to combine multiple capacitive timekeepers of different sizes into a Cascaded Hierarchical Remanence Timekeeper (CHRT). The various tiers of the CHRT can be used to minimize cold-boot time and energy consumption, and maximize resolution and timekeeping range at run-time. The tiers are linked together, from smallest to largest, so that a depleted tier can automatically activate the next tier, therefore increasing the total timing range, whilst maintaining the best possible resolution. For short time intervals, the smaller tiers provide higher resolution and consume less energy. The larger tiers are used to time longer intervals, but have lower resolution and need more charging energy. To be able to use the CHRT, the cascaded tiers have to be pre-charged at each reboot. Our hardware abstraction layer, can be configured to minimize energy consumption depending on the needs of an application and the expectations of energy availability of the environment, by specifying how many tiers should be pre-charged at each reboot.
Programmers and intermittent kernel designers can choose to be either conservative or adaptive with tier recharging. Adaptive timekeeper provisioning is useful when energy environments and application behavior are somewhat predictable or continuous (for example, solar environments). The basic idea of adaptive tier recharge is to choose only the smallest tier that can still satisfy the timing requirements. The adaptive method saves energy since overcharging the timekeeper when the kernel is only timing a short outage is wasteful. When the reboot frequency is variable the kernel may choose to be conservative in timekeeper provisioning (tier recharging), as retrieved times are more likely to be outside the timekeeping range of the current target tier. For better robustness against variable reboot frequency, the user can request the CHRT to charge more than just the current target tier. Specifically, the two function parameters KL and KR are used to tell the CHRT software layer to charge all tiers in [Tx−KL,Tx+KR], given that Tx is the current target tier. For instance, if KL = KR = 1, tiers Tx−1, Tx and Tx+1 would be recharged on reboot, and the discharge would start from tier Tx−1, and continue with the larger tiers in a cascaded fashion. The parameters KL and KR control the trade-off between timekeeping robustness and energy consumption, as charging more tiers requires more energy. In particular, KR has a higher impact on energy consumption, due to larger tiers needing more charging energy.
Raw API¶
The raw CHRT interface is a hardware abstraction layer (HAL) of the underlying timekeeping hardware functionality, to be used for low-level control of the CHRT. It is mostly intended as a building block for more advanced timekeeping duties to be exposed by the runtime or kernel that has knowledge of the user tasks and operations, but can be used at the application level as well by the user.
Upon reboot first chrt_init()
has to be called followed by
chrt_get_time()
to get the elapsed time of the power failure that was just
recovered from. Next, the CHRT needs to be recharged using chrt_charge()
.
-
uint8_t
chrt_init
(uint8_t start_tier, uint8_t KR, uint8_t KL, bool fix_tiers)¶ Initialize the CHRT.
Configure I/O and initializes non-volatile variables such as calibration constants on first boot after programming.
Smaller values of KR and KL minimize energy consumption, as fewer tiers are recharged each time. Larger values minimize timekeeping errors when timed intervals vary more quickly.
- Return
TK_SUCCESS
- Parameters
start_tier
: first CHRT tier to be charged on first bootKR
: number of additional tiers larger than the current one to rechargeKL
: number of additional tiers smaller than the current one to rechargefix_tiers
: whether to charge the same tier (start_tier
) throughout the whole application (only for debugging)
-
uint8_t
chrt_charge
()¶ Charge the CHRT.
Depending on the last on-off period, the current (optimal) tier will change when the last measurement was near the limits of the current tier. This combined with the margins specified in chrt_init() ensure that the CHRT follows changes in energy availability.
- Return
TK_SUCCESS
-
uint8_t
chrt_get_time
(uint16_t *resolution, uint16_t *time, bool cmp_chr_time)¶ Retrieve time from the CHRT.
Depending on the margin specified in chrt_init(), this function samples the tiers to retrieve a notion of time.
To obtain time in millisecond, multiply time by resolution.
- Return
TK_SUCCESS
, orTK_ERROR
when operating outside of the CHRT optimal timing range- Parameters
resolution
: measurement resolution in mstime
: time elapsed since last rechargecmp_chr_time
: when true compensates the timing output for charging times
High-level API¶
The high-level CHRT interface enhances raw functionalities to provide
higher-level timekeeping tools to be used in real-world batteryless
applications for intermittent devices. Fundamentally, this is implemented
combining CHRT functionalities with an on-board MCU digital timer to maintain
an always-available system time. The system time is incremented at each reboot
using the raw chrt_get_time()
function. When queried during on-time, the
system time is combined with timing information retrieved from the digital
timer running in the background. The system time is used to generate
timestamps and to set expiration timers for data and functions.
-
void
tptime_init
(void)¶ Initialize timekeeping subsystem.
This function inctrements system time at each reboot, querying the CHRT, and recharges the CHRT as well. It must be called at the beginning of the
main()
.Note: The timekeeping subsystem uses an MCU timer with a specific clock configuration, take this into account when designing your application. For the MSP430FR59xx, it is timer TA0 with SMCLK at 0.25 MHz
-
uint32_t
tptime_get_timestamp
(void)¶ Get timestamp (current time) in milliseconds.
- Return
system time in milliseconds
-
int8_t
tptime_set_expiration
(void *tag, uint32_t expiration)¶ Set expiration time, in milliseconds, for an arbitraty object.
The expiration time is relative to the current system time (as would be returned by
tptime_get_timestamp()
). An object can be a variable, an array, a function or any other arbitrary void pointer. The expiration status of the given object can be checked withtptime_has_expired()
.Note: This function uses
malloc()
internally. Expiration times are not deallocated unless the user explicitely callstptime_clear_expiration()
.- Return
TPTIME_OK
orTPTIME_MEM_ERROR
- Parameters
tag
: void pointer to object to set expiration time forexpiration
: expiration time, in milliseconds, relative to current time
-
int8_t
tptime_clear_expiration
(void *tag)¶ Clear expiration time.
An expiration time previously set with
tptime_set_expiration()
can be deallocated using this function.- Return
TPTIME_OK
orTPTIME_OBJ_NOT_FOUND
- Parameters
tag
: void pointer to object to clear expiration time of
-
int8_t
tptime_has_expired
(void *tag)¶ Check if given object has expired.
- Return
TPTIME_OBJ_EXPIRED
,TPTIME_OBJ_VALID
orTPTIME_OBJ_NOT_FOUND
- Parameters
tag
: void pointer to object to check expiration of
Fine-tuning CHRT behavior¶
To tune the CHRT behavior when using the high-level API, you can tweak the
following parameters in tptime.h
. Refer to chrt_init()
in the
raw API for the meaning of each.
-
TPTIME_CHRT_START_TIER
¶ First CHRT tier to be recharged on first boot.
-
TPTIME_CHRT_MARGIN_UP
¶ Number of additional tiers, larger than the current one, to recharge.
-
TPTIME_CHRT_MARGIN_DOWN
¶ Number of additional tiers, smaller than the current one, to recharge.
-
TPTIME_CHRT_FIX_TIERS
¶ Whether to charge the same tier throughout the whole application.
Calibration¶
Ideally, the RC circuit discharge model, t = −RC ln(V/V0), could be used to estimate elapsed time, t. In actuality, capacitance C and resistance R never match their nominal values, and other parasitic capacitors and resistors are spread through the circuit. To resolve this issue, a software calibration routine, to be performed before CHRT deployment, was devised and implemented, aiming for a better precision and accuracy of the timekeeper. During calibration, all the tiers of the CHRT are repeatedly charged and discharged, and their discharging profile is sampled over time to obtain a realistic physical model for each tier. This way, an interpolated version of the RC circuit discharge model is built and stored in the form of a lookup table. At run-time, the voltage across the target tier is used to look up the table and retrieve the corresponding elapsed time.
This process consists of two steps starting with the charge/discharge time calibration, followed by accuracy calibration.
Charge/Discharge time calibration¶
To calibrate the charging/discharging times please follow the process described below.
Connect Botoks to a debugger capable of debugging the MSP430 line of products. Set the target voltage to 2.2V to avoid back-feeding. Then power Botox using the auxiliary power connector on the PCB. The voltage provided needs to be between 3.4 and 5V.
Program the calib-charge-discharge application using Uniflash.
$ ./flash.sh bin/calib-charge-discharge.out
Connect to the programmers UART interface (Usually the higher of the two COM ports) with your favorite monitor software such as picocom and monitor the output.
$ picocom /dev/ttyACM1 -b 19200 --imap lfcrlf
Using the output modify the constants listed below in ‘chrt.h’
To reach optimal accuracy the charge and discharge constants might need to manually tuned.
-
group
CHRT_CHRCONF
Sets the delay required to fully charge a specific tier. Defined in loops of
DELAY_CYCLES
-
group
CHRT_CHRCMP
Sets the optionally applied charge time compensation for tier 0. (Charge time is only significant to tiers 0 and 1) Defined in ms.
-
group
CHRT_DISCONF
Sets the delay required to fully discharge a specific tier. Defined in loops of
DELAY_CYCLES
Accuracy calibration¶
The accuracy calibration process is automated in the calibrate.py script. This scripts programs the accuracy calibration application, monitors the output, creates a calibration file and then fits a RC curve using MATLAB on this calibration data. The process is repeated for each tier. When the RC constants for each tier is determined, lut.py is called to create a look-up table (lut.c) based on the previously determined RC constants. Upon completion of the program the new lut.c can replace the provided lut.c.
From within the scripts folder:
$ python calibrate.py
$ mv -f lut.c ../libs/chrt/src/
To reach optimal accuracy the constants compensating for tier transitions might need to manually tuned. These constants are listed below.
-
group
CHRT_CASCONF
These constants define the total time of the tier used as compensation when sampling multiple tiers.
Examples¶
Below some sample applications are listed to showcase the usage of the raw API and the high-level API.
High-level API¶
This example utilizes the high level API to check the if variable foo_data has expired. Is simulates intermittency with a software reset and a delay.
#include <chrt.h>
#include <chrt_calib.h>
#include <msp430.h>
#include <mspbase.h>
#include <mspprintf.h>
#include <platform.h>
#include <stdint.h>
#include <tptime.h>
#define CLEAR_EXPIRATION 0
__nv uint16_t foo_data = 0xF00;
int main(void) {
msp_watchdog_disable();
/* Initialize and configure pins. */
msp_gpio_init_all_ports();
msp_gpio_unlock();
/* Change clock frequency to 8 MHz for MCLK and SMCLK. */
msp_clock_set_mclk(CLK_8_MHZ, SMCLK_8_MHZ);
msp_uart_open();
msp_gpio_dir_out(DBG_PORT, DEBUG_POWER_PIN);
msp_gpio_spike(DBG_PORT, DEBUG_POWER_PIN);
/* 10ms delay. */
__delay_cycles(80000);
/* Initialize TPTime. */
tptime_init();
/* Check for expiration of variable. */
int8_t result = tptime_has_expired(&foo_data);
if (result == TPTIME_OBJ_EXPIRED) {
msp_printf("Pre-set: Variable 'foo_data' has expired.\n");
#if CLEAR_EXPIRATION
tptime_clear_expiration(&foo_data);
#endif
} else if (result == TPTIME_OBJ_NOT_FOUND) {
msp_printf("Pre-set: Variable 'foo_data' not found.\n");
}
/* Print timestamp. */
uint32_t timestamp = tptime_get_timestamp();
msp_printf("System time: %l\n", timestamp);
/* Set expiration for variable (expires in 50ms). */
tptime_set_expiration(&foo_data, 50);
/* Check for expiration of variable. */
result = tptime_has_expired(&foo_data);
if (result == TPTIME_OBJ_EXPIRED) {
msp_printf("Post-set: Variable 'foo_data' has expired.\n");
#if CLEAR_EXPIRATION
tptime_clear_expiration(&foo_data);
#endif
} else if (result == TPTIME_OBJ_NOT_FOUND) {
msp_printf("Post-set: Variable 'foo_data' not found.\n");
}
/* Reset the MCU simulating intermittency. */
WDTCTL = 0xDEAD;
return 0;
}
Raw API¶
This example leverages the low level interface to measure time. Please note that In this example the charging time of the CHRT is not compensated for.
#include <chrt.h>
#include <chrt_calib.h>
#include <msp430.h>
#include <mspbase.h>
#include <mspprintf.h>
#include <platform.h>
#include <stdint.h>
int main(void) {
msp_watchdog_disable();
/* Initialize and configure pins. */
msp_gpio_init_all_ports();
msp_gpio_unlock();
/* Change clock frequency to 8 MHz for MCLK and SMCLK. */
msp_clock_set_mclk(CLK_8_MHZ, SMCLK_8_MHZ);
msp_uart_open();
msp_gpio_dir_out(DBG_PORT, DEBUG_POWER_PIN);
msp_gpio_spike(DBG_PORT, DEBUG_POWER_PIN);
/* 10ms delay. */
__delay_cycles(80000);
/* Initialize Raw API. */
chrt_init(0, N_TIERS - 1, N_TIERS - 1, true);
/* Retrieve the time. (charging time not compensated). */
uint16_t time_meas, resolution;
chrt_get_time(&resolution, &time_meas, false);
/* Charge the CHRT */
chrt_charge();
uint32_t time_meas_ms = (uint32_t)resolution * time_meas;
msp_printf("Measured time: %l, Resolution: %u, Raw time: %u\n", time_meas_ms,
resolution, time_meas);
/* Reset the MCU simulating intermittency. */
WDTCTL = 0xDEAD;
return 0;
}