Simple Stop Watch using Interrupts#

Particle CLI#

Installation#

%%capture
!bash <( curl -sL https://particle.io/install-cli )

# path to the particle cli. May be environment dependent.
particle_cli = "/root/bin/particle"

Utility functions#

import re
import subprocess

# regular expression to strip ansi control characters
ansi = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')

# decode byte string and strip ansi control characters
def decode_bytes(byte_string):
    if isinstance(byte_string, bytes):
        result = byte_string.decode("utf-8")
    return ansi.sub("", result)

# streamline call to the particle-cli
def particle(args):
    process = subprocess.run(["/root/bin/particle"] + args,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
    process.stdout = decode_bytes(process.stdout)
    process.stderr = decode_bytes(process.stderr)
    return process

Login to Particle#

import getpass

# prompt for username and password
username = getpass.getpass(prompt="Username: ")
password = getpass.getpass(prompt="Password: ")

# attempt login
output = particle(["login", "--username", username, "--password", password])

# report results
if output.returncode:
    print(f"Return code = {output.returncode}")
    print(output.stderr)
else:
    print(output.stdout)
Username: ··········
Password: ··········
> Successfully completed login!

Select a device#

The following cell downloads a list of all user devices and creates a list of device names. Here we choose the first name in the list for the rest of this notebook. If this is not the device to be used, then modify this cell accordingly.

devices = [line.split()[0] for line in particle(["list"]).stdout.splitlines()]
device_name = devices[0]
print(particle(["list", device_name]).stdout)
jck_argon_01 [e00fce68eaceb1faa7cf7193] (Argon) is online

Project: Simple Stop Watch#

The goal of this project is to build a simple stop watch. The project will use code previously developed for the Grove 4 digit display, and add a Grove button to control operation of the stop watch. The stop watch will start and stop with a short click of the button, and reset to zero with a long button press.

Grove Button#

The Grove Button is a momentary contact with a pull-down resistor. With a pull-down resistor, the pin value is LOW when the button is not pressed, and become HIGH when the button is depressed.

Solution 1: Using clickButton library#

Create Project#

print(particle(["project", "create", "--name", "myproject", "."]).stdout)
Initializing project in directory myproject...
> A new project has been initialized in directory myproject

Change working directory#

The Particle CLI assumes one is working in the top project directory.

%cd myproject
/content/myproject

Add relevant libraries#

print(particle(["library", "add", "Grove_4Digit_Display"]).stdout)
print(particle(["library", "add", "clickButton"]).stdout)
> Library Grove_4Digit_Display 1.0.2 has been added to the project.
> To get started using this library, run particle library view Grove_4Digit_Display to view the library documentation and sources.

> Library clickButton 1.0.9 has been added to the project.
> To get started using this library, run particle library view clickButton to view the library documentation and sources.

Create source file#

%%writefile src/myproject.ino

/* pin assignments */
#define PIN_CLK D2     /* display clock */
#define PIN_DIO D3     /* display data */
#define PIN_BTN D4     /* button */

/* display parameters */
#define DIGITS 4   /* display digits */

#include "Grove_4Digit_Display.h"
#include "clickButton.h"

/* display object */
TM1637 tm1637(PIN_CLK, PIN_DIO);

/* button object */
ClickButton button(PIN_BTN, HIGH);

/* stopwatch state */
unsigned long curr_time;
unsigned long prev_time;
unsigned long display_time;
bool running;

void setup() {
    /* setup display */
    tm1637.init();
    tm1637.set(BRIGHT_TYPICAL);
    tm1637.point(POINT_ON);

    /* setup button */
    pinMode(PIN_BTN, INPUT);
    button.debounceTime = 0;
    button.multiclickTime = 250;
    button.longClickTime = 1000;

    /* setup stopwatch */
    prev_time = millis();
    display_time = 0;
    running = FALSE;
}

void loop() {
    button.Update();
    if (button.clicks > 0) {
        running = !running;
    } else if (button.clicks < 0) {
        display_time = 0;
    }
    if (running) {
        curr_time = millis();
        display_time += curr_time - prev_time;
    } else {
        curr_time = millis();
    }
    prev_time = curr_time;
    display(display_time / 10); /* displaying 100th's of seconds */
}

void display(unsigned int number) {
    for (int i = 0; i < 4; i++) {
        int digit = DIGITS - 1 - i;
        tm1637.display(digit, number % 10);
        number /= 10;
    }
}
Overwriting src/myproject.ino

Compiling#

print(particle(["compile", "argon", "--saveTo", "myproject.bin"]).stdout)
Compiling code for argon

Including:
    src/myproject.ino
    project.properties

attempting to compile firmware
downloading binary from: /v1/binaries/5f91f89f9c09c655b52cb096
saving to: myproject.bin
Memory use:
   text	   data	    bss	    dec	    hex	filename
   6588	    108	   1112	   7808	   1e80	/workspace/target/workspace.elf

Compile succeeded.
Saved firmware to: /content/myproject/myproject.bin

Flash firmware#

print(particle(["flash", device_name, "myproject.bin"]).stdout)
Including:
    myproject.bin

attempting to flash firmware to your device jck_argon_01
Flash device OK: Update started

Flash success!

Solution 2: Interrupt Service Routine (ISR)#

The clickButton library provides an easy-to-use method of managing the button actions, with provisions for debouncing, multiple clicks, and long clicks, but testing shows the button updates when the button is released, not when it is pressed. This is not consistent with a user’s expectation that the clock should stop and start on the press of the button, not on the release.

The following cell demonstrates the use of an Interrupt Service Routine to manage the button interface. The key insight here is to respond to both the press and release of the button by specifying CHANGE in the attachInterrupt function. This makes is possible to detect a long button press to reset the stop watch display to zero.

%%writefile src/myproject.ino

/* pin assignments */
#define PIN_CLK D2     /* display clock */
#define PIN_DIO D3     /* display data */
#define PIN_BTN D4     /* button */

/* display parameters */
#define DIGITS 4   /* display digits */

#include "Grove_4Digit_Display.h"
#include "clickButton.h"

/* display object */
TM1637 tm1637(PIN_CLK, PIN_DIO);

/* stopwatch state */
unsigned long curr_time;
unsigned long prev_time;
unsigned long display_time;
volatile unsigned long btn_press_time;
volatile bool btn_is_pressed;
volatile bool running;

void setup() {
    /* setup display */
    tm1637.init();
    tm1637.set(BRIGHT_TYPICAL);
    tm1637.point(POINT_ON);

    /* setup button */
    pinMode(PIN_BTN, INPUT);
    btn_press_time = millis();
    attachInterrupt(PIN_BTN, on_btn_change, CHANGE);

    /* setup stopwatch */
    prev_time = millis();
    display_time = 0;
    running = FALSE;
}

void loop() {
    curr_time = millis();
    if (running) {
        display_time += curr_time - prev_time;
        if (btn_is_pressed && ((curr_time - btn_press_time) > 1000)) {
            running = FALSE;
            display_time = 0;
        }
    }
    prev_time = curr_time;
    display(display_time / 10); /* displaying 100th's of seconds */
}

void on_btn_change() {
    if (digitalRead(PIN_BTN)==HIGH) {
        if ((millis() - btn_press_time) > 50) {
            running = !running;
            btn_press_time = millis();
            btn_is_pressed = TRUE;
        }
    } else {
        btn_is_pressed = FALSE;   
    }
}

void display(unsigned int number) {
    for (int i = 0; i < 4; i++) {
        int digit = DIGITS - 1 - i;
        tm1637.display(digit, number % 10);
        number /= 10;
    }
}
Overwriting src/myproject.ino
print(particle(["compile", "argon", "--saveTo", "myproject.bin"]).stdout)
Compiling code for argon

Including:
    src/myproject.ino
    project.properties

attempting to compile firmware
downloading binary from: /v1/binaries/5f91c394d59546684d4c092a
saving to: myproject.bin
Memory use:
   text	   data	    bss	    dec	    hex	filename
   6604	    108	   1088	   7800	   1e78	/workspace/target/workspace.elf

Compile succeeded.
Saved firmware to: /content/myproject/myproject.bin
print(particle(["flash", device_name, "myproject.bin"]).stdout)
Including:
    myproject.bin

attempting to flash firmware to your device jck_argon_01
Flash device OK: Update started

Flash success!