Skip to content

Stopwatch

Joachim Stolberg edited this page Jan 27, 2023 · 4 revisions

stopuhr

Example of a stopwatch with displays for the last 5 results and the top 5 results.

The program is divided into 4 files:

sw_init.c with configuration parameters

// Standard libs
import "sys/os.c"
import "sys/stdlib.asm"
import "sys/string.asm"
import "lib/seg14.c"
import "lib/eeprom.c"
import "lib/techage.c"

// Configuration
const STRT = 0;   // button
const STOP = 1;   // button
const SEG1 = 8;   // tenth
const SEG2 = 9;   // seconds
const SEG3 = 10;  // seconds
const SEG4 = 11;  // minutes
const SEG5 = 12;  // minutes
const LCD1 = 13;  // last 5
const LCD2 = 14;  // top 5

const MIN_DT = 100;  // Minimum time: 1 min
const MAX_DT = 1800; // Maximum time: 3 min

The ports do not need to be adjusted if the I/O modules are configured as shown in the picture. Only the min/max timings have to be adjusted to your needs.

sw_time.c with timing functions

static var str[18];

func format_time(s, time) {
  var tenth;
  var sec;
  var min;

  tenth = time mod 10;
  sec = (time / 10) mod 60;
  min = (time / 600) mod 60;

  s[0] = 48 + min / 10;
  s[1] = 48 + min mod 10;
  s[2] = ':';
  s[3] = 48 + sec / 10;
  s[4] = 48 + sec mod 10;
  s[5] = '.';
  s[6] = 48 + tenth;
  s[7] = 0;
  strpack(s);

  return s;
}

func seg14_output_time(s) {
  seg14_putchar(SEG5, s[0] >> 8);
  seg14_putchar(SEG4, s[0] & 0xFF);
  seg14_putchar(SEG3, s[1] & 0xFF);
  seg14_putchar(SEG2, s[2] >> 8);
  seg14_putchar(SEG1, s[3]);
}

func gen_string(name, time) {
  strcpy(str, "<"); // left aligned
  strcat(str, name);
  str[8] = 0; // cut to long names
  strcat(str, ": ");
  strcat(str, time);
  return str;
}

sw_top5.c with the top 5 result calculation functions

const ELE_SIZE = 20;
const TBL_SIZE = 100; // 5 * ELE_SIZE

static var top5[TBL_SIZE];

static func get_idx(dt) {
  var idx = 0xFFFF;
  var i;

  for(i = 0; i < TBL_SIZE; i = i + ELE_SIZE) {
    if(top5[i] == 0xFFFF) {
      return i; // free entry
    }
    if(top5[i] == dt) {
      return 0xFFFF; // already available
    }
    if(top5[i] > dt) {
      dt = top5[i];
      idx = i;
    }
  }
  return idx;
}

func init_top5(port) {
  var i;

  clear_screen(port);

  for(i = 0; i < TBL_SIZE; i = i + ELE_SIZE) {
    e2p_read_block(i, &top5[i], ELE_SIZE);
  }
}

func store_top5() {
  var i;

  for(i = 0; i < TBL_SIZE; i = i + ELE_SIZE) {
    e2p_write_block(i, &top5[i], ELE_SIZE);
  }
}

func output_top5(port) {
  var line = 1;
  var i;

  for(i = 0; i < 5; i++) {
    if(top5[i * ELE_SIZE] != 0xFFFF) {
      write_line(port, line, &top5[i * ELE_SIZE + 1]);
      line++;
    }
  }
}

func add_to_top5(s, dt) {
  var idx = get_idx(dt);
  if(idx != 0xFFFF) {
    top5[idx] = dt;
    strcpy(&top5[idx + 1], s);
    store_top5();
  }
}

sw_main.c with the init and loop functions

// Application
import "sw_init.c"
import "sw_top5.c"
import "sw_time.c"

const WAITFOR_START = 0;
const WAITFOR_STOP  = 1;

var sName1[10];
var sName2[10];
var sTime[8];
var Time;
var State;
var Cnt;

func start() {
  var time;

  if(input(STRT) == 1) {
    request_data(STRT, 144, "", sName1);
    request_data(STRT, 149, "", &time);
    return time;
  }
  return 0;
}

func stop() {
  var time;

  if(input(STOP) == 1) {
    request_data(STOP, 144, "", sName2);
    request_data(STOP, 149, "", &time);
    return time;
  }
  return 0;
}

func init() {
  init_top5(LCD2);
  output_top5(LCD2);
  seg14_output_time("00:00.0");
  State = WAITFOR_START;
  Cnt = 0;
}

func loop() {
  var t;
  var time;
  var dt;
  var s;
  var port;

  port = get_next_inp_port();
  if(port != 0xffff) {
    if(State == WAITFOR_START) {
      t = start();
      if(t > 0) {
        Time = t;
        State = WAITFOR_STOP;
      }
    } else if(State == WAITFOR_STOP) {
      t = stop();
      if(t > 0) {
        dt = t - Time;
        if(dt > MIN_DT) {
          if(strcmp(sName1, sName2) == 0) {
            format_time(sTime, dt);
            s = gen_string(sName1, sTime);
            append_line(LCD1, s);
            seg14_output_time(sTime);
            add_to_top5(s, dt);
            output_top5(LCD2);
            State = WAITFOR_START;
          }
        }
      }
    }
  }
  if(State == WAITFOR_STOP) {
    if((get_time() - Time) > MAX_DT) {
      State = WAITFOR_START;
      seg14_output_time("00:00.0");
    } else {
      Cnt++;
      if(Cnt > 10) {
        Cnt = 0;
        dt = get_time() - Time;
        format_time(sTime, dt);
        seg14_output_time(sTime);
      }
    }
  }
}

Configuration of the I/O Ports

Input Module (#0)

screenshot_20230127_181702

I/O Module (#8)

screenshot_20230127_181711

Building Instructions

The stopwatch has a start and a stop button. The start button can be placed far away (The controller's map block does not need to be loaded for the clock to start, the timings are stored in the TA4 buttons).

Note: Both buttons must be configured with the open-end wrench (Techage Info Tool) so that they only send a "1" and no on/off sequence.

The stopwatch needs one I/O module, one Input module, and two techage XL displays.

The Beduino controller must be equipped with EEPROM chip to be able to store the top 5 result.

It uses the Beduino 14-Segment displays.

References: