Tesla: Star Wars or Tron?

tesla_light

At work the other day, I got the opportunity to teach a class on inductance and resonance in relation to electronic circuits. So of course, I thought a tesla coil would be the most fun visual instrument to instruct with.

tesla_floresent

Of course, the inexpensive kit I bought off of Amazon came with a small slayer-exciter circuit, which is a little different than the traditional tesla coil circuit, since it finds it’s own resonating frequency, but it was still impressive to watch. We even burnt a few scraps of paper and then used the spark as a plasma speaker, which was pretty fun too.

We also had a few florescent light bulbs around, both straight and round. So the question became: Star Wars light-saber duels, or Tron disc wars?

Linux – keep it simple.

Inkplate 6, A “versatile, easy-to-use, Wi-Fi-enabled e-paper display”

eink

So, my good friend, the Libre Hacker, sent me a link to a new e-paper display that is pretty cool and completely open source! It is the new Inkplate 6. It’s build on the ESP32, which means you can use the Arduino suite to program it, which is really convenient.

It’s pretty nifty, displaying at around 250 ms screen refresh rate and comes in an 800×600 resolution for grey-scale and black and white. Now I’m just waiting for someone to build the newest, open source e-reader with it….

Linux – keep it simple.

Building a Pinewood Derby Timer: Finishing the arch and adding the last of the wiring!

Well, I finally finished it! The pinewood derby timer is done. Check it out!

First, I had to finish the wiring for the lights and the sensors, and then I had to finish the arch.

Then, I had to program it. Fortunately, https://www.dfgtec.com/ has a whole section on programming the board, and what settings you should change or adjust for your needs. Super helpful site!

Here is my final code:

/*================================================================================*
Pinewood Derby Timer Version 3.00 – 31 Oct 2016
http://www.dfgtec.com/pdt

Flexible and affordable Pinewood Derby timer that interfaces with the
following software:
– PD Test/Tune/Track Utility
– Grand Prix Race Manager software

Refer to the website for setup and usage instructions.
Copyright (C) 2011-2018 David Gadberry

This work is licensed under the Creative Commons Attribution-NonCommercial-
ShareAlike 3.0 Unported License. To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to
Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
94041, USA.
*================================================================================*/

/*—————————————–*
– TIMER CONFIGURATION –
*—————————————–*/
#define NUM_LANES 4 // number of lanes

#define LED_DISPLAY 1 // Enable lane place/time displays
//#define DUAL_DISP 1 // dual displays (front/back) per lane (4 lanes max)
//#define LARGE_DISP 1 // utilize large Adafruit displays (see website)
#define SHOW_PLACE 1 // Show place mode
#define PLACE_DELAY 4 // Delay (secs) when displaying place/time
#define MIN_BRIGHT 4 // minimum display brightness (0-15)
#define MAX_BRIGHT 10 // maximum display brightness (0-15)

#define GATE_RESET 1 // Enable closing start gate to reset timer
/*—————————————–*
– END –
*—————————————–*/
#ifdef LED_DISPLAY // LED control libraries
#include “Wire.h”
#include “Adafruit_LEDBackpack.h”
#include “Adafruit_GFX.h”
#endif

/*—————————————–*
– static definitions –
*—————————————–*/
#define PDT_VERSION “3.00” // software version
#define MAX_LANE 6 // maximum number of lanes (Uno)
#define MAX_DISP 8 // maximum number of displays (Adafruit)

#define mREADY 0 // program modes
#define mRACING 1
#define mFINISH 2
#define mTEST 3

#define START_TRIP LOW // start switch trip condition
#define NULL_TIME 99.999 // null (non-finish) time
#define NUM_DIGIT 4 // timer resolution (# of decimals)
#define DISP_DIGIT 4 // total number of display digits

#define PWM_LED_ON 220
#define PWM_LED_OFF 255
#define char2int(c) (c – ‘0’)

//
// serial messages <- to timer
// -> from timer
//
#define SMSG_ACKNW ‘.’ // -> acknowledge message

#define SMSG_POWER ‘P’ // -> start-up (power on or hard reset)

#define SMSG_CGATE ‘G’ // <- check gate
#define SMSG_GOPEN ‘O’ // -> gate open

#define SMSG_RESET ‘R’ // <- reset
#define SMSG_READY ‘K’ // -> ready

#define SMSG_SOLEN ‘S’ // <- start solenoid
#define SMSG_START ‘B’ // -> race started
#define SMSG_FORCE ‘F’ // <- force end
#define SMSG_RSEND ‘Q’ // <- resend race data

#define SMSG_LMASK ‘M’ // <- mask lane
#define SMSG_UMASK ‘U’ // <- unmask all lanes

#define SMSG_GVERS ‘V’ // <- request timer version
#define SMSG_DEBUG ‘D’ // <- toggle debug on/off
#define SMSG_GNUML ‘N’ // <- request number of lanes

/*—————————————–*
– pin assignments –
*—————————————–*/
byte BRIGHT_LEV = A0; // brightness level
byte RESET_SWITCH = 8; // reset switch
byte STATUS_LED_R = 9; // status LED (red)
byte STATUS_LED_B = 11; // status LED (blue)
byte STATUS_LED_G = 10; // status LED (green)
byte START_GATE = 12; // start gate switch
byte START_SOL = 13; // start solenoid

// Display # 1 2 3 4 5 6 7 8
int DISP_ADD [MAX_DISP] = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77}; // display I2C addresses

// Lane # 1 2 3 4 5 6
byte LANE_DET [MAX_LANE] = { 2, 3, 4, 5, 6, 7}; // finish detection pins

/*—————————————–*
– global variables –
*—————————————–*/
boolean fDebug = false; // debug flag
boolean ready_first; // first pass in ready state flag
boolean finish_first; // first pass in finish state flag

unsigned long start_time; // race start time (microseconds)
unsigned long lane_time [MAX_LANE]; // lane finish time (microseconds)
int lane_place [MAX_LANE]; // lane finish place
boolean lane_mask [MAX_LANE]; // lane mask status

int serial_data; // serial data
byte mode; // current program mode

float display_level = -1.0; // display brightness level

#ifdef LARGE_DISP
unsigned char msgGateC[] = {0x6D, 0x41, 0x00, 0x0F, 0x07}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x41, 0x00, 0x3F, 0x5E}; // S=OP
unsigned char msgLight[] = {0x41, 0x41, 0x00, 0x00, 0x07}; // == L
unsigned char msgDark [] = {0x41, 0x41, 0x00, 0x00, 0x73}; // == d
#else
unsigned char msgGateC[] = {0x6D, 0x48, 0x00, 0x39, 0x38}; // S=CL
unsigned char msgGateO[] = {0x6D, 0x48, 0x00, 0x3F, 0x73}; // S=OP
unsigned char msgLight[] = {0x48, 0x48, 0x00, 0x00, 0x38}; // == L
unsigned char msgDark [] = {0x48, 0x48, 0x00, 0x00, 0x5e}; // == d
#endif
unsigned char msgDashT[] = {0x40, 0x40, 0x00, 0x40, 0x40}; // —-
unsigned char msgDashL[] = {0x00, 0x00, 0x00, 0x40, 0x00}; // –
unsigned char msgBlank[] = {0x00, 0x00, 0x00, 0x00, 0x00}; // (blank)

#ifdef LED_DISPLAY // LED display control
Adafruit_7segment disp_mat[MAX_DISP];
#endif

void initialize(boolean powerup=false);
void dbg(int, const char * msg, int val=-999);
void smsg(char msg, boolean crlf=true);
void smsg_str(const char * msg, boolean crlf=true);

/*================================================================================*
SETUP TIMER
*================================================================================*/
void setup()
{
/*—————————————–*
– hardware setup –
*—————————————–*/
pinMode(STATUS_LED_R, OUTPUT);
pinMode(STATUS_LED_B, OUTPUT);
pinMode(STATUS_LED_G, OUTPUT);
pinMode(START_SOL, OUTPUT);
pinMode(RESET_SWITCH, INPUT);
pinMode(START_GATE, INPUT);
pinMode(BRIGHT_LEV, INPUT);

digitalWrite(RESET_SWITCH, HIGH); // enable pull-up resistor
digitalWrite(START_GATE, HIGH); // enable pull-up resistor

digitalWrite(START_SOL, LOW);

#ifdef LED_DISPLAY
for (int n=0; n<MAX_DISP; n++)
{
disp_mat[n] = Adafruit_7segment();
disp_mat[n].begin(DISP_ADD[n]);
disp_mat[n].clear();
disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();
}
#endif

for (int n=0; n<MAX_LANE; n++)
{
pinMode(LANE_DET[n], INPUT);

digitalWrite(LANE_DET[n], HIGH); // enable pull-up resistor
}
set_display_brightness();

/*—————————————–*
– software setup –
*—————————————–*/
Serial.begin(9600);
smsg(SMSG_POWER);

/*—————————————–*
– check for test mode –
*—————————————–*/
if (digitalRead(RESET_SWITCH) == LOW)
{
mode = mTEST;
test_pdt_hw();
}

/*—————————————–*
– initialize timer –
*—————————————–*/
initialize(true);
unmask_all_lanes();
}
/*================================================================================*
MAIN LOOP
*================================================================================*/
void loop()
{
process_general_msgs();

switch (mode)
{
case mREADY:
timer_ready_state();
break;
case mRACING:
timer_racing_state();
break;
case mFINISH:
timer_finished_state();
break;
}
}
/*================================================================================*
TIMER READY STATE
*================================================================================*/
void timer_ready_state()
{
if (ready_first)
{
set_status_led();
clear_displays();

ready_first = false;
}

if (serial_data == int(SMSG_SOLEN)) // activate start solenoid
{
digitalWrite(START_SOL, HIGH);
smsg(SMSG_ACKNW);
}

if (digitalRead(START_GATE) == START_TRIP) // timer start
{
start_time = micros();

digitalWrite(START_SOL, LOW);

smsg(SMSG_START);
delay(100);

mode = mRACING;
}

return;
}

/*================================================================================*
TIMER RACING STATE
*================================================================================*/
void timer_racing_state()
{
int lanes_left, finish_order, lane_status[NUM_LANES];
unsigned long current_time, last_finish_time;
set_status_led();
clear_displays();

finish_order = 0;
last_finish_time = 0;

lanes_left = NUM_LANES;
for (int n=0; n<NUM_LANES; n++)
{
if (lane_mask[n]) lanes_left–;
}

while (lanes_left)
{
current_time = micros();

for (int n=0; n<NUM_LANES; n++) lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanes

for (int n=0; n<NUM_LANES; n++)
{
if (lane_time[n] == 0 && lane_status[n] == HIGH && !lane_mask[n]) // car has crossed finish line
{
lanes_left–;

lane_time[n] = current_time – start_time;

if (lane_time[n] > last_finish_time)
{
finish_order++;
last_finish_time = lane_time[n];
}
lane_place[n] = finish_order;

update_display(n, lane_place[n], lane_time[n], SHOW_PLACE);
}
}

serial_data = get_serial_data();

if (serial_data == int(SMSG_FORCE) || serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // force race to end
{
lanes_left = 0;
smsg(SMSG_ACKNW);
}
}

send_race_results();

mode = mFINISH;

return;
}
/*================================================================================*
TIMER FINISHED STATE
*================================================================================*/
void timer_finished_state()
{
if (finish_first)
{
set_status_led();
finish_first = false;
}

if (GATE_RESET && digitalRead(START_GATE) != START_TRIP) // gate closed
{
delay(500); // ignore any switch bounce

if (digitalRead(START_GATE) != START_TRIP) // gate still closed
{
initialize(); // reset timer
}
}

if (serial_data == int(SMSG_RSEND)) // resend race data
{
smsg(SMSG_ACKNW);
send_race_results();
}

set_display_brightness();
display_race_results();

return;
}
/*================================================================================*
PROCESS GENERAL SERIAL MESSAGES
*================================================================================*/
void process_general_msgs()
{
int lane;
char tmps[50];
serial_data = get_serial_data();

if (serial_data == int(SMSG_GVERS)) // get software version
{
sprintf(tmps, “vert=%s”, PDT_VERSION);
smsg_str(tmps);
}

else if (serial_data == int(SMSG_GNUML)) // get number of lanes
{
sprintf(tmps, “numl=%d”, NUM_LANES);
smsg_str(tmps);
}

else if (serial_data == int(SMSG_DEBUG)) // toggle debug
{
fDebug = !fDebug;
dbg(true, “toggle debug = “, fDebug);
}

else if (serial_data == int(SMSG_CGATE)) // check start gate
{
if (digitalRead(START_GATE) == START_TRIP) // gate open
{
smsg(SMSG_GOPEN);
}
else
{
smsg(SMSG_ACKNW);
}
}

else if (serial_data == int(SMSG_RESET) || digitalRead(RESET_SWITCH) == LOW) // timer reset
{
if (digitalRead(START_GATE) != START_TRIP) // only reset if gate closed
{
initialize();
}
else
{
smsg(SMSG_GOPEN);
}
}

else if (serial_data == int(SMSG_LMASK)) // lane mask
{
delay(100);
serial_data = get_serial_data();

lane = serial_data – 48;
if (lane >= 1 && lane <= NUM_LANES)
{
lane_mask[lane-1] = true;

dbg(fDebug, “set mask on lane = “, lane);
}
smsg(SMSG_ACKNW);
}

else if (serial_data == int(SMSG_UMASK)) // unmask all lanes
{
unmask_all_lanes();
smsg(SMSG_ACKNW);
}

return;
}
/*================================================================================*
TEST PDT FINISH DETECTION HARDWARE
*================================================================================*/
void test_pdt_hw()
{
int lane_status[NUM_LANES];
char ctmp[10];
smsg_str(“TEST MODE”);
set_status_led();
delay(2000);

/*—————————————–*
show status of lane detectors
*—————————————–*/
while(true)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_status[n] = bitRead(PIND, LANE_DET[n]); // read status of all lanes

if (lane_status[n] == HIGH)
{
update_display(n, msgDark); smsg_str(“Dark”); smsg_str(n);
}
else
{
update_display(n, msgLight); smsg_str(“Light”); smsg_str(n);
}
}

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}

/*—————————————–*
show status of start gate switch
*—————————————–*/
while(true)
{
if (digitalRead(START_GATE) == START_TRIP)
{
update_display(0, msgGateO);
}
else
{
update_display(0, msgGateC);
}

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
delay(1000);
break;
}
delay(100);
}

/*—————————————–*
show pattern for brightness adjustment
*—————————————–*/
while(true)
{
#ifdef LED_DISPLAY
set_display_brightness();

for (int n=0; n<NUM_LANES; n++)
{
sprintf(ctmp,”%04d”, (int)display_level);

disp_mat[n].clear();

disp_mat[n].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n].writeDigitNum(4, char2int(ctmp[3]), false);

disp_mat[n].drawColon(false);
disp_mat[n].writeDisplay();

#ifdef DUAL_DISP
disp_mat[n+4].clear();

disp_mat[n+4].writeDigitNum(0, char2int(ctmp[0]), false);
disp_mat[n+4].writeDigitNum(1, char2int(ctmp[1]), false);
disp_mat[n+4].writeDigitNum(3, char2int(ctmp[2]), false);
disp_mat[n+4].writeDigitNum(4, char2int(ctmp[3]), false);

disp_mat[n+4].drawColon(false);
disp_mat[n+4].writeDisplay();
#endif
}
#endif

if (digitalRead(RESET_SWITCH) == LOW) // break out of this test
{
clear_displays();
break;
}
delay(1000);
}
}
/*================================================================================*
SEND RACE RESULTS TO COMPUTER
*================================================================================*/
void send_race_results()
{
float lane_time_sec;
for (int n=0; n<NUM_LANES; n++) // send times to computer
{
lane_time_sec = (float)(lane_time[n] / 1000000.0); // elapsed time (seconds)

if (lane_time_sec == 0) // did not finish
{
lane_time_sec = NULL_TIME;
}

Serial.print(n+1);
Serial.print(” – “);
Serial.println(lane_time_sec, NUM_DIGIT); // numbers are rounded to NUM_DIGIT
// digits by println function
}

return;
}
/*================================================================================*
RACE FINISHED – DISPLAY PLACE / TIME FOR ALL LANES
*================================================================================*/
void display_race_results()
{
unsigned long now;
static boolean display_mode;
static unsigned long last_display_update = 0;
if (!SHOW_PLACE) return;

now = millis();

if (last_display_update == 0) // first cycle
{
last_display_update = now;
display_mode = false;
}

if ((now – last_display_update) > (unsigned long)(PLACE_DELAY * 1000))
{
dbg(fDebug, “display_race_results”);

for (int n=0; n<NUM_LANES; n++)
{
update_display(n, lane_place[n], lane_time[n], display_mode);
}

display_mode = !display_mode;
last_display_update = now;
}

return;
}
/*================================================================================*
SEND MESSAGE TO DISPLAY
*================================================================================*/
void update_display(int lane, unsigned char msg[])
{

#ifdef LED_DISPLAY
disp_mat[lane].clear();
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
#endif

for (int d = 0; d<=4; d++)
{
disp_mat[lane].writeDigitRaw(d, msg[d]);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(d, msg[d]);
#endif
}

disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
#endif

return;
}
/*================================================================================*
UPDATE LANE PLACE/TIME DISPLAY
*================================================================================*/
void update_display(int lane, int display_place, unsigned long display_time, int display_mode)
{
int c;
char ctime[10], cplace[4];
double display_time_sec;
boolean showdot;

// dbg(fDebug, “led: lane = “, lane);
// dbg(fDebug, “led: plce = “, display_place);
// dbg(fDebug, “led: time = “, display_time);

#ifdef LED_DISPLAY
if (display_mode)
{
if (display_place > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);

sprintf(cplace,”%1d”, display_place);
disp_mat[lane].writeDigitNum(3, char2int(cplace[0]), false);

disp_mat[lane].writeDisplay();

#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
disp_mat[lane+4].writeDigitNum(3, char2int(cplace[0]), false);
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashL);
}
}
else
{
if (display_time > 0)
{
disp_mat[lane].clear();
disp_mat[lane].drawColon(false);
#ifdef DUAL_DISP
disp_mat[lane+4].clear();
disp_mat[lane+4].drawColon(false);
#endif

display_time_sec = (double)(display_time / (double)1000000.0); // elapsed time (seconds)
dtostrf(display_time_sec, (DISP_DIGIT+1), DISP_DIGIT, ctime); // convert to string

// Serial.print(“ctime = [“); Serial.print(ctime); Serial.println(“]”);
c = 0;
for (int d = 0; d<DISP_DIGIT; d++)
{
#ifdef LARGE_DISP
showdot = false;
#else
showdot = (ctime[c + 1] == ‘.’);
#endif
disp_mat[lane].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitNum(d + int(d / 2), char2int(ctime[c]), showdot); // time
#endif

c++; if (ctime[c] == ‘.’) c++;
}

#ifdef LARGE_DISP
disp_mat[lane].writeDigitRaw(2, 16);
#ifdef DUAL_DISP
disp_mat[lane+4].writeDigitRaw(2, 16);
#endif
#endif

disp_mat[lane].writeDisplay();
#ifdef DUAL_DISP
disp_mat[lane+4].writeDisplay();
#endif
}
else // did not finish
{
update_display(lane, msgDashT);
}
}
#endif

return;
}
/*================================================================================*
CLEAR LANE PLACE/TIME DISPLAYS
*================================================================================*/
void clear_displays()
{
dbg(fDebug, “led: CLEAR”);

for (int n=0; n<NUM_LANES; n++)
{
if (mode == mRACING || mode == mTEST)
{
update_display(n, msgBlank); // racing
}
else
{
update_display(n, msgDashT); // ready
}
}

return;
}
/*================================================================================*
SET LANE DISPLAY BRIGHTNESS
*================================================================================*/
void set_display_brightness()
{
float new_level;

#ifdef LED_DISPLAY
new_level = long(1023 – analogRead(BRIGHT_LEV)) / 1023.0F * 15.0F;
new_level = min(new_level, (float)MAX_BRIGHT);
new_level = max(new_level, (float)MIN_BRIGHT);

if (fabs(new_level – display_level) > 0.3F) // deadband to prevent flickering
{ // between levels
dbg(fDebug, “led: BRIGHT”);

display_level = new_level;

for (int n=0; n<NUM_LANES; n++)
{
disp_mat[n].setBrightness((int)display_level);
#ifdef DUAL_DISP
disp_mat[n+4].setBrightness((int)display_level);
#endif
}
}
#endif

return;
}
/*================================================================================*
SET TIMER STATUS LED
*================================================================================*/
void set_status_led()
{
int r_lev, b_lev, g_lev;

dbg(fDebug, “status led = “, mode);

r_lev = PWM_LED_OFF;
b_lev = PWM_LED_OFF;
g_lev = PWM_LED_OFF;

if (mode == mREADY) // blue
{
b_lev = PWM_LED_ON;
}
else if (mode == mRACING) // green
{
g_lev = PWM_LED_ON;
}
else if (mode == mFINISH) // red
{
r_lev = PWM_LED_ON;
}
else if (mode == mTEST) // yellow
{
r_lev = PWM_LED_ON;
g_lev = PWM_LED_ON;
}

analogWrite(STATUS_LED_R, r_lev);
analogWrite(STATUS_LED_B, b_lev);
analogWrite(STATUS_LED_G, g_lev);

return;
}
/*================================================================================*
READ SERIAL DATA FROM COMPUTER
*================================================================================*/
int get_serial_data()
{
int data = 0;

if (Serial.available() > 0)
{
data = Serial.read();
dbg(fDebug, “ser rec = “, data);
}

return data;
}
/*================================================================================*
INITIALIZE TIMER
*================================================================================*/
void initialize(boolean powerup)
{
for (int n=0; n<NUM_LANES; n++)
{
lane_time[n] = 0;
lane_place[n] = 0;
}

start_time = 0;
set_status_led();
digitalWrite(START_SOL, LOW);

// if power up and gate is open -> goto FINISH state
if (powerup && digitalRead(START_GATE) == START_TRIP)
{
mode = mFINISH;
}
else
{
mode = mREADY;

smsg(SMSG_READY);
delay(100);
}
Serial.flush();

ready_first = true;
finish_first = true;

return;
}
/*================================================================================*
UNMASK ALL LANES
*================================================================================*/
void unmask_all_lanes()
{
dbg(fDebug, “unmask all lanes”);

for (int n=0; n<NUM_LANES; n++)
{
lane_mask[n] = false;
}

return;
}
/*================================================================================*
SEND DEBUG TO COMPUTER
*================================================================================*/
void dbg(int flag, const char * msg, int val)
{
char tmps[50];
if (!flag) return;

smsg_str(“dbg: “, false);
smsg_str(msg, false);

if (val != -999)
{
sprintf(tmps, “%d”, val);
smsg_str(tmps);
}
else
{
smsg_str(“”);
}

return;
}
/*================================================================================*
SEND SERIAL MESSAGE (CHAR) TO COMPUTER
*================================================================================*/
void smsg(char msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}

return;
}
/*================================================================================*
SEND SERIAL MESSAGE (STRING) TO COMPUTER
*================================================================================*/
void smsg_str(const char * msg, boolean crlf)
{
if (crlf)
{
Serial.println(msg);
}
else
{
Serial.print(msg);
}

return;
}

A little long for posting here, but I wanted to post any changes I had made in case it helps someone else! Now the timers work, alternating between places and time per track/car! This was a really fun project, and I highly recommend it to anyone looking for a fun electronics project to work on.

I might still dress it up a bit, but it is fully functional as is, now.

Linux – keep it simple.

Building a Pinewood Derby Timer: Wiring in the displays

Now that I have the displays mounted on the pinewood derby timer, it was time to wire them in. I still don’t have lights or sensors in yet, but I thought I could just hook up the displays for now. It seemed to work well!

I am using the schematics per the https://www.dfgtec.com/ website. Fortunately, the creator of the schematics, David Gadberry was kind enough to give it a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License, so guys like me can tinker with it!

Linux – keep it simple.

Building a Pinewood Derby Timer: 3D printing some display covers

Well, I decided to print a few display covers to go around the LED displays. This covers up the unsightly hole that I cut from the box, and seems to “class up” the displays a bit. I like the way they turned out. If I could post files here, I’d post the gcode and openCad files, but the free version of WordPress doesn’t work well that way.

Either way, took me only two tries to find a style I liked, and a couple hours total to complete this part of the project.

Linux – keep it simple.

Building a Pinewood Derby Timer: The Box for the “Arch”

As I have been constructing this Pinewood Derby Timer, I needed some sort of box or enclosure to make the top of the arch. So I took apart a junk piece of equipment and cut out holes for the LED displays to go into it. I also drilled some holes for the LED’s to shine out the bottom and some holes for the Arduino card USB interface and power adapter plug on the side.

The box was originally black, and I tried to paint it white. Unfortunately, all the paint peeled, and I ended up grinding it down to bare metal. I’m not sure if I should paint it or not now. I guess I’ll have to see the end result.

I also am not sure if I’ll just put some sort of “face” on it, or fake front to cover it up. The holes didn’t cut perfectly rectangular, so I want to cover that up somehow. I’ll have to think on it. Maybe I can use the ol’ 3D printer to come up with something.

Linux – keep it simple.

Building a Pinewood Derby Timer: Setting up the LED displays

So one of the driving components (pun intended) of the derby timer is the LED displays. I went with the smaller ones from Adafruit, as we wouldn’t need a second power supply if we go this route, and I’d like to keep the unit as simple as possible.

You have to solder the displays to the backer board, and then you have to solder jumper pins across them to serialize each display. If you don’t do this part, your displays all show the same thing! I found that out the hard way! So be sure to solder those little jumpers. For convenience, the PDT website: https://www.dfgtec.com/pdt

has a reference table:

PDT Lane # PDT Lane # I2C Address # Jumpers
A0 A1 A2
1 1 F 0
2 2 F 1 x
3 3 F 2 x
4 4 F 3 x x
5 1 B 4 x
6 2 B 5 x x
3 B 6 x x
4 B 7 x x x

Key: – = open, x = shorted; F = front, B = back

This is really handy. I really appreciate all the work they put into this, and it is fun to build along with a guide that covers about 95% of the process!

Linux – keep it simple.

Building a Pinewood Derby Timer: Getting the schematics and parts list…

A local church group that I attend needed some help with an Awana project. They use a pinewood derby track for racing cars, and they needed a timer built for it. I don’t attend the Awana club usually, and I was not present for the races last time they held them, but I guess they borrowed a track and timer for those races. Now they bought their own track, and need a timer for it.

I priced the times out online, you can buy some nice pre-made ones for about $350-$400. That’s a lot of money. After a little research, I found we could make one for about $85! So, here I am, building a timer.

I found a lot of reference material out there for making your own timer, but the best by far was here: https://www.dfgtec.com/pdt 

It is a great and informative website that uses an Ardiuno Uno powered configuration with several parts from Adafruit and elsewhere. They have done all the heavy lifting on the build and, best of all, their schematics were open source! So, I went with it.

5

They sold some of the parts on their website, and others you had to get elsewhere, but it was very clean and informative. They sold this PDT shield for the Arduino, and then you have to solder all the parts onto it, which was pretty fun to do, as the shield made everything a lot easier to maintain.

Now I just have to finish putting it together!

Linux – keep it simple.

Damp dryers…

image

Somewhat of a break from the norm, but today the dryer quit drying. The clothes stayed damp, and there was no heat inside the dryer when you opened the door.

At first, I disassembled the dryer, expecting to find a problem with with the controls or main electronic boards. However, using a multi meter revealed that the controls and circuit board were outputting 120 volts to the heating element.

Further investigation showed that the heating element wire had 120 volts at the plug, and all the sensors seemed to be working. It was at this point that I decided to check resistance on the heating element wire itself. What I found was “OL” for over limit, meaning no connection between the terminals.

After disassembled, the problem was very obvious, as the heating element wire had broken in the middle, making for no connection. A simple repair, hooking the wire together again, I’m only wondering how long that repair will last. But, this got it working again, and gives me time to order another heating element.

Hopefully they can ship one to Alaska.

Linux – keep it simple.

Home Photo Server, Part 4: Battery Backup

download

One of the problems that I have with a local server is that we frequently have power “blips”. I don’t want to say outages, because the power is on about 99% of the time. The issue that we have is occasional “brown outs” where the power dips, but doesn’t drop out about once a month. We also have noticed about every other week it seems that the power will blink for just a moment.

Usually about three or four times a year the power will actually go out for more than an hour. It’s not uncommon for a minute long outage on a regular basis though. The worst outage we had was about 3 years ago, when the power was out at our house for 4 days! It was winter time, and I had to run the generator to keep the boiler going. Needless to say, we are prepared around here for the power to go out.

And that’s why I wanted my computer to be prepared as well. Anytime you are using something as a server that you want to make sure it is still running, you really should have it on a backup. So, I purchased an APC BackUPS 1350. By the specs, and it’s own digital display, it should keep my server and monitor running for 15 minutes during an outage.

That’s handy, but I wanted to take it one step further. I’m at work during the week day, and at church on Sunday, so I may not be around when the power goes out. If that happens, having it run for 15 minutes and then die really didn’t help me much. Fortunately, I found some great articles, and installed apcaccess to help me automatically control everything. First I added the EPEL repository, and then installed apcaccess:

# yum install epel-release

# yum install apcupsd

Then I navigated to the apc directory to change my settings…

# cd /etc/apcupsd/

# nano apcupsd.conf

This is a bit lengthy, but here is my apcupsd.conf file, without all the comment lines:

## apcupsd.conf v1.1 ##
#
# for apcupsd release 3.14.14 (31 May 2016) – redhat
#
# “apcupsd” POSIX config file

#
# Note that the apcupsd daemon must be restarted in order for changes to
# this configuration file to become active.
#

#
# ========= General configuration parameters ============
#

UPSNAME SRVRAPC
UPSCABLE usb
UPSTYPE usb
DEVICE
POLLTIME 60
LOCKFILE /var/lock
SCRIPTDIR /etc/apcupsd
PWRFAILDIR /etc/apcupsd
NOLOGINDIR /etc
#
# ======== Configuration parameters used during power failures ==========
#
ONBATTERYDELAY 6
BATTERYLEVEL 20
MINUTES 5
TIMEOUT 0
ANNOY 60
ANNOYDELAY 60
NOLOGON disable
KILLDELAY 0
#
# ==== Configuration statements for Network Information Server ====
#
NETSERVER on
NISIP 127.0.0.1
NISPORT 3551
EVENTSFILE /var/log/apcupsd.events
EVENTSFILEMAX 10
#
# ========== Configuration statements used if sharing =============
# a UPS with more than one machine
UPSCLASS standalone
UPSMODE disable
#
# ===== Configuration statements to control apcupsd system logging ========
#
STATTIME 0
STATFILE /var/log/apcupsd.status
LOGSTATS off
DATATIME 0
#FACILITY DAEMON
#
# ========== Configuration statements used in updating the UPS EPROM =========
#
SELFTEST 336

The important parts being that I set MINUTES to 5, meaning that if the UPS believes that it only has 5 minutes of power left at the current usage rate, it will initiate a computer shutdown command to the server computer. Likewise, if the BATTERYLEVEL drops below 20%, it will also initiate a shutdown command (which, theoretically, would be the 3 minute mark). I suppose you don’t have to set both, but I wanted to make sure under either condition, it shut down the server computer gracefully.

A really funny parameter is NETSERVER on and NISIP 127.0.0.1. At first I turned these off, but then found that the USB connection to the UPS didn’t work unless they were on and set to the local interface. So be sure you turn those on if you are using one of these as well.

Be sure to start the service and make sure it’s enabled:

# systemctl stop apcupsd
# systemctl start apcupsd
# systemctl enable apcupsd

After all of that, now I can just type apcaccess to see the UPS status:

APC : 001,036,0880
DATE : 2019-07-01 09:13:37 -0800
HOSTNAME : localhost.localdomain
VERSION : 3.14.14 (31 May 2016) redhat
UPSNAME : SRVRAPC
CABLE : USB Cable
DRIVER : USB UPS Driver
UPSMODE : Stand Alone
STARTTIME: 2019-06-27 14:34:23 -0800
MODEL : Back-UPS NS 1350M2
STATUS : ONLINE
LINEV : 121.0 Volts
LOADPCT : 34.0 Percent
BCHARGE : 100.0 Percent
TIMELEFT : 16.3 Minutes
MBATTCHG : 20 Percent
MINTIMEL : 5 Minutes
MAXTIME : 0 Seconds
SENSE : Medium
LOTRANS : 88.0 Volts
HITRANS : 139.0 Volts
ALARMDEL : No alarm
BATTV : 27.3 Volts
LASTXFER : No transfers since turnon
NUMXFERS : 0
TONBATT : 0 Seconds
CUMONBATT: 0 Seconds
XOFFBATT : N/A
SELFTEST : NO
STATFLAG : 0x05000008
SERIALNO : 3B1907X23168
BATTDATE : 2019-02-13
NOMINV : 120 Volts
NOMBATTV : 24.0 Volts
NOMPOWER : 810 Watts
FIRMWARE : 954.e3 .D USB FW:e3
END APC : 2019-07-01 09:14:06 -0800

I might play with the high/low transfer, it seems a bit to wide of a spectrum to me, but we’ll see how it does. Either way, I feel a lot safer now in terms of power problems for my server.

Linux – keep it simple.