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.

_images/chrt.svg

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 boot

  • KR: number of additional tiers larger than the current one to recharge

  • KL: number of additional tiers smaller than the current one to recharge

  • fix_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, or TK_ERROR when operating outside of the CHRT optimal timing range

Parameters
  • resolution: measurement resolution in ms

  • time: time elapsed since last recharge

  • cmp_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 with tptime_has_expired().

Note: This function uses malloc() internally. Expiration times are not deallocated unless the user explicitely calls tptime_clear_expiration().

Return

TPTIME_OK or TPTIME_MEM_ERROR

Parameters
  • tag: void pointer to object to set expiration time for

  • expiration: 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 or TPTIME_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 or TPTIME_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.

  1. 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.

  2. Program the calib-charge-discharge application using Uniflash.

    $ ./flash.sh  bin/calib-charge-discharge.out
    
  3. 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
    
  4. 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

Defines

TIER_CHARGE_TIME_0
TIER_CHARGE_TIME_1
TIER_CHARGE_TIME_2
TIER_CHARGE_TIME_3
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.

Defines

TIER_CHARGE_TIME_0_MS
TIER_CHARGE_TIME_1_MS
TIER_CHARGE_TIME_2_MS
TIER_CHARGE_TIME_3_MS
group CHRT_DISCONF

Sets the delay required to fully discharge a specific tier. Defined in loops of DELAY_CYCLES

Defines

TIER_DISCHARGE_TIME_0
TIER_DISCHARGE_TIME_1
TIER_DISCHARGE_TIME_2
TIER_DISCHARGE_TIME_3

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.

Defines

TIER_TOTAL_TIME_0

Defined in the resolution of tier 1.

TIER_TOTAL_TIME_1

Defined in the resolution of tier 2.

TIER_TOTAL_TIME_2

Defined in the resolution of tier 3.

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;
}