Initial commit

This commit is contained in:
jnoack 2018-07-06 23:56:33 +02:00
parent 060ba3f2e8
commit ccdfaa56d3
4 changed files with 19894 additions and 0 deletions

BIN
SpeedclockNano.fzz Normal file

Binary file not shown.

19477
fonts.h Normal file

File diff suppressed because it is too large Load diff

71
speedclock.h Normal file
View file

@ -0,0 +1,71 @@
#ifndef Remote_Control_transceiver_H
#define Remote_Control_transceiver_H
//-------------- defines fpr the radio devices NRF24 ---------------------------------------------------------
#define STATION_SEL 4 // this 4 for Nano
typedef enum {BASESTATION = 0, TOPSTATION} radio_type_e;
#define RF24_CNS 7 // this is 7 for the Nano, D4 for the ESP
#define RF24_CE 8 // this is 8 for the Nano, D3 for the ESP
//--------------- define the structure and type of data that sender and receiver will exchange ----------------
typedef struct transcv_struct{
unsigned long topstationtime; // the top station sends its time (millis()) continously to the base station
unsigned long topbuttonpressedtime; // the top station sends the time in millis() when the button was pressed - this is already the calculated time
}transcv_s;
#define STOPBUTTON_IN D2 // this is the input for the button
#define STOPBUTTON_PRESSED HIGH // this the signal level the top button will be at as soon as pressed
#define MIN_DELAY_BETWEEN_PRESSED_MS 1000 // this defines the time in milliseconds before the button is expected to be pressed again. We do this to avaoid keybouncing
#define MIN_DELAY_BETWEEN_SEND_MS 1000 // this defines the time in milliseconds before the next set of data will be send to the base station - except the button was pressed.
#define STARTBUTTON_IN D4 // start button
#define STARTBUTTON_PRESSED LOW
#define CHANCELBUTTON_IN D2 // chancle button
#define CHANCELBUTTON_PRESSED LOW
#define FAILSTARTBUTTON_IN D3 // fail start button
#define FAILSTARTBUTTON_PRESSED LOW
#define WARN_LED A1 // yellow warn LED
#define WARN_LED_ON HIGH
#define WARN_LED_OFF LOW
#define FAIL_LED A3 // red fail LED
#define FAIL_LED_ON HIGH
#define FAIL_LED_OFF LOW
#define READY_LED A2 // green ready LED
#define READY_LED_ON HIGH
#define READY_LED_OFF LOW
#define RUN_LED A0 // blue run LED
#define RUN_LED_ON HIGH
#define RUN_LED_OFF LOW
typedef enum {TIMER_INIT = 0, TIMER_READY, TIMER_STARTED, TIMER_RUNNING , TIMER_CHANCELED, TIMER_STOPPED, TIMER_TIMEDOUT, TIMER_FAIL, TIMER_WAIT} timer_state_e;
// READY_LED, WARN_LED, RUN_LED, FAIL_LED
const float LEDStates[][3] =
{
TIMER_INIT = {READY_LED_OFF, RUN_LED_OFF, FAIL_LED_OFF},
TIMER_READY = {READY_LED_ON, RUN_LED_OFF, FAIL_LED_OFF},
TIMER_STARTED = {READY_LED_ON, RUN_LED_ON, FAIL_LED_OFF},
TIMER_RUNNING = {READY_LED_OFF, RUN_LED_ON, FAIL_LED_OFF},
TIMER_CHANCELED = {READY_LED_OFF, RUN_LED_OFF, FAIL_LED_ON},
TIMER_STOPPED = {READY_LED_ON, RUN_LED_ON, FAIL_LED_OFF},
TIMER_TIMEDOUT = {READY_LED_OFF, RUN_LED_ON, FAIL_LED_ON},
TIMER_FAIL = {READY_LED_OFF, RUN_LED_OFF, FAIL_LED_ON}
}
#define MAX_DIFFERENCE_OFFSET_MS 10 // 0,001sec is the maximum offset we allow between the current offset and the mean offset. if it is more - restart offset calculation
#define REQUIRED_NUMBER_MEANVALS 100 // we need at least this number of meanvalues to be ready to start a run
#define STARTSEQ_LENGTH_MS = 3100 // the length of the start sequence from the time the button was pressed ... includes the 3 tones
#define STARTSEQ_STARTPAUSE_MS = 1000
#define STARTSEQ_TONEPAUSE_MS = 800
#define STARTSEQ_TON_1_2_LENGTH_MS = 200
#define STARTSEQ_TON_3_LENGTH_MS = 100
#endif

346
speedclock.ino Normal file
View file

@ -0,0 +1,346 @@
#include <Arduino.h>
#include <U8g2lib.h>
//#include "fonts.h"
#include <SPI.h>
#include "RF24.h"
#include "speedclock.h"
// internal defines for the OLED display ...
U8G2_SSD1306_128X64_NONAME_1_SW_I2C display(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // All Boards without Reset of the Display
/****************** User Config for NRF24***************************/
/*** Set this radio as radio number RADIO0 or RADIO1 ***/
radio_type_e radioNumber = BASESTATION; //---> TOPSTATION has the button connected
/* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */
RF24 radio(RF24_CNS,RF24_CE);
/**********************************************************/
byte addresses[][12] = {"top_station","basestation"}; // Radio pipe addresses for the 2 nodes to communicate.
unsigned long counter_time_offset = 0; // number of used values for the mean value calculation
unsigned long sum_time_offset = 0; // sum of offset values
unsigned long mean_time_offset = 0; // mean value for the offset
unsigned long current_time_offset = 0; // current offset ...
unsigned long running_time_offset = 0; // offset that will be used for this run ...
unsigned long start_time = 0; // if the timer is running this is that start time ...
unsigned long run_time = 0; // if the timer is running this is that start time ...
boolean warn_during_run = false; // will be set to true if there is a warning during the run - usually an offset sync error
timer_state_e timer_state = TIMER_INIT; // timer needs to be initialized ...
// timer is ready to be started ...
// will be true if the timer was started
// will be true if the timer is running and the stopbutton was pressed at the TOPSTATION
// will be true if the timeout of the timer is reached
// will be true if the chancel button was pressed - e.g. in case of a early start
transcv_s radio_data;
void setup(){
Serial.begin(115200);
// this is the top button - will be pressed by the speed climber as soon she/he reaches the top ...
pinMode(STOPBUTTON_IN, INPUT);
pinMode(STARTBUTTON_IN, INPUT);
pinMode(CHANCELBUTTON_IN, INPUT);
pinMode(FAILSTARTBUTTON_IN, INPUT);
pinMode(WARN_LED, OUTPUT);
pinMode(FAIL_LED, OUTPUT);
pinMode(READY_LED, OUTPUT);
// Get the station type (base or top) as set by the station select pin - BASESTATION is default
pinMode(STATION_SEL, INPUT);
Serial.print(F(" The station select pin (pin "));
Serial.print(STATION_SEL);
Serial.print(F(") is set to level: "));
Serial.println(RADIO_SEL);
if(digitalRead(RADIO_SEL) == HIGH){
stationNumber = TOPSTATION;
Serial.print(F("The level of the station select pin makes the current node set to the TOPSTATION."));
}
else{
Serial.print(F("The level of the station select pin makes the current node set to the BASESTATION"));
}
// Setup and configure the NRF radio
// radio setup ...
radio.begin();
radio.setRetries(15, 15); //the first is the time between reties in multiple of 250ms, the second is the numer of attempts
if(stationNumber == TOPSTATION){
radio.openWritingPipe(addresses[1]); // Both radios listen on the same pipes by default, but opposite addresses
radio.openReadingPipe(1,addresses[0]); // Open a reading pipe on address 0, pipe 1
radio.stopListening(); // top station will never receive data.
}else{
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]);
}
radio.startListening(); // Start listening
radio_data.topstationtime = millis(); // set the current milli second count
radio_data.topbuttonpressedtime = 0; // set the time the button was pressed last time to 0
//initialise OLED and display Welcome Message ...
display.begin();
}
void loop(void) {
/****************** Code for the TOPSTATION is here - the stop button is connected to the top station ***************************/
if (stationNumber == TOPSTATION){ // Radio is the top station and sends continously its time and the time the stop button was pressed.
bool topbuttonwaspressed = false;
// check for pressed button ...
if( (millis() - radio_data.topbuttonpressedtime) > MIN_DELAY_BETWEEN_PRESSED_MS){
// ignore if the button was "pressed" a few millis before - this is keybouncing and would give a false result and if the button is pressed for a longer time that would effect the time as well
if(digitalRead(STOPBUTTON_IN, STOPBUTTON_PRESSED)){
// button was pressed - store the time
radio_data.topbuttonpressedtime = millis();
topbuttonwaspressed = true;
}
}
// if the button was not pressed only each few second data will be send to BASESTATION ...
if(topbuttonwaspressed || ((millis()-radio_data.topstationtime) >= MIN_DELAY_BETWEEN_SEND_MS)){
// store current millis to be send as reference ...
radio_data.topstationtime = millis(); // set the current milli second count
// send data ...
if (!radio.write(&radio_data,sizeof(radio_data) )){ // Send the counter variable to the other radio
Serial.println(F("Failed to send data to BASESSTATION ... will retry"));
}
}
}
/****************** Code for the BASESTATION is here - the display and the start button is connected here. All caclulation will be done here ***************************/
if ( stationNumber == BASESTATION ) {
// read data from TOP_STATION ...
if( radio.available()){
// check if radio data is available - if so read the data
radio.read( &radio_data, sizeof(radio_data) ); // Read the data the TOPSTATION sent
current_time_offset = radio_data.topstationtime - millis(); // the offset between TOP_STATION and BASESTATION
Serial.print(F(" Current time on client in millis: "));
Serial.println(radio_data.topstationtime);
Serial.print(F(" Button was pressed last time on client in millis: "));
Serial.println(radio_data.topbuttonpressedtime);
}
// offset calculation ... only needed if the variation is bigger than allowed or not enough values available already ...
// check current offset of the TOP_STATIOn and the BASESTATION if more than allowed ...
if(abs(current_time_offset - mean_time_offset) < MAX_DIFFERENCE_OFFSET_MS){
// the offset is in range - check if we have already enough values of if we need to add more ...
if(counter_time_offset < REQUIRED_NUMBER_MEANVALS){
//add the next value to meanvalue calculation ...
sum_time_offset = sum_time_offset + current_time_offset;
counter_time_offset++;
mean_time_offset = sum_time_offset/counter_time_offset;
Serial.print(F("Offset calulation. We already have "))
Serial.print(counter_time_offset)
Serial.print(F(" of "));
Serial.print(REQUIRED_NUMBER_MEANVALS);
Serial.print(F(" values used for offset calculation. Mean value of offset based on that is: "));
Serial.println(mean_time_offset);
}
} else {
// the current offset is out of range so we need to restart the mean calculation and set the timer to unready state ...
Serial.print(F("Will restart offset calculation because the variation of the current offset: "));
Serial.print(current_time_offset);
Serial.print(F(" is more than the allowed: "));
Serial.print(MAX_DIFFERENCE_OFFSET_MS);
Serial.print(F(" compared to the mean offset: "));
Serial.println(mean_time_offset);
counter_time_offset = 0;
sum_time_offset = 0;
mean_time_offset = 0;
}
// set LEDs
set_state_LEDs(timer_state, warn_during_run );
switch(timer_state){
case TIMER_INIT:
// check if we are ready ...
if(counter_time_offset > REQUIRED_NUMBER_MEANVALS){
// check if offset is OK - if not .. set state back to INIT
timer_state = TIMER_READY;
}
break;
case TIMER_READY:
warn_during_run = false;
if(counter_time_offset < REQUIRED_NUMBER_MEANVALS){
// check if offset is OK - if not .. set state back to INIT
timer_state = TIMER_INIT;
}
else{
// check if the FALSESTATE button is pressed - somebody is ready to run ...
if(digitalRead(FAILSTARTBUTTON_IN, FAILSTARTBUTTON_PRESSED)){
//wait a few milliseconds to prevent keybouncing - this is a very simplistic method here
delay(300);
//read again and check if still active ...
if(digitalRead(FAILSTARTBUTTON_IN, FAILSTARTBUTTON_PRESSED)){
// check if the start button was pressed ... there is at least still someone waiting for the run .
if(digitalRead(STARTBUTTON_IN, STARTBUTTON_PRESSED)){
timer_state = TIMER_STARTED;
}
}
}
}
break;
case TIMER_STARTED:
//enable the interrupt for the FALSESTART button and start the StartSequence - no interruption possible here anymore, except the FALSESTART button was released to early
//initialize the start countdown here ... if done set to RUNNING ... maybe check fail start already here ... ?? or in running state ... or extra state ...
startSequence();
case TIMER_FAILCHECK:
//check for fail start in this phase - interrupt is active, this state can only be reached from STARTED state ...
if(start_time - millis() >= STARTOK_TOLERANCETIME_MS)
{
timer_state = TIMER_RUNNING;
}
break;
case TIMER_RUNNING:
if(counter_time_offset < REQUIRED_NUMBER_MEANVALS){
// check if offset is still OK - if not .. set state to TIMER_RUNNING
warn_during_run = true;
}
if(digitalRead(CHANCELBUTTON_IN, CHANCELBUTTON_PRESSED)){
timer_state = TIMER_CHANCELED;
}
if((radio_data.topbuttonpressedtime - running_time_offset) > millis()){
timer_state = TIMER_STOPPED;
}
break;
case TIMER_STOPPED:
//calculate the run_time and switch to WAIT
timer_state = TIMER_WAIT;
break;
case TIMER_FAIL:
//fail start case ....
timer_state = TIMER_WAIT;
break;
case TIMER_CHANCELED:
// what to do in chancel mode ?
timer_state = TIMER_WAIT;
break;
case TIMER_TIMEDOUT:
//
timer_state = TIMER_WAIT;
break;
case TIMER_WAIT:
// wait until the start button was pressed to go ahead
if(digitalRead(STARTBUTTON_IN, STARTBUTTON_PRESSED)){
timer_state = TIMER_READY;
}
break;
}
int ypos = 64-42/2;
snprintf ( temp_string, sizeof(temp_string),"%d.%1d", int(client_data.temperature), int(abs(client_data.temperature - int(client_data.temperature))*10 + 0.5));
Serial.println(temp_string);
display.setFontPosCenter();
display.setFont(u8g2_font_logisoso34_tn);
int xpos = (128-display.getStrWidth(temp_string))/2 - 10;
display.firstPage();
do {
display.setFont(u8g2_font_logisoso34_tn);
display.setCursor(xpos,ypos);
display.print(temp_string);
display.setCursor(xpos + display.getStrWidth(temp_string)+ 5,ypos-15);
display.setFont(u8g2_font_ncenB12_tr);
display.print(F("°C"));
} while ( display.nextPage() );
}
/////////////////////
if( radio.available(&pipeNo)){
while( radio.available(&pipeNo)){ // Read all available payloads
radio.read( &control_data, sizeof(control_data) );
}
radio.stopListening(); // First, stop listening so we can talk
Serial.print(F("Got remote data counter: "));
Serial.print(control_data.counter);
Serial.print(F(" time: "));
Serial.println(control_data.time);
radio_data.counter = control_data.counter + 1; // Ack payloads are much more efficient than switching to transmit mode to respond to a call
radio_data.time = control_data.time;
Serial.print(F("Loaded next response "));
Serial.println(radio_data.counter);
if( radio.write(&radio_data, sizeof(radio_data) )){
Serial.println(F("Sending failed.")); // If no ack response, sending failed
//add error handling here if remote does not recieve the data anymore
}
radio.startListening();
}
}
}
//####################### HELPER FUNCTIONS ###########################
void set_state_LEDs(timer_state_e state, boolean warn)
{
// set the LEDS corresponding to the state of the timer ... as long as the system is not waiting for input ...
if(TIMER_WAIT != state){
digitalWrite(READY_LED, LEDStates[state][0]};
digitalWrite(RUN_LED, LEDStates[state][1]};
digitalWrite(FAIL_LED, LEDStates[state][2]};
if(warn == true){
digitalWrite(WARN_LED, WARN_LED_ON};
}
else
{
digitalWrite(WARN_LED, WARN_LED_OFF};
}
}
}
void startSequence(void)
{
// first - enable the interrupt
// set the startime - this is the current time plus the length of this sequence
start_time = millis() + STARTSEQ_LENGTH_MS;
running_time_offset = mean_time_offset;
// this is sequence of usually three tones after a wait time 1sec , in between the tones there is also a delay of 1 sec. Each tone is 200ms seconds long, except the last
delay(STARTSEQ_STARTPAUSE_MS);
// first tone
delay(STARTSEQ_TONEPAUSE_MS);
//second tone
delay(STARTSEQ_TONEPAUSE_MS);
//third tone
// disable the interrupt and than