ArcheryClock-Arduino/ArcheryClock-Arduino.ino

447 lines
14 KiB
C++

//Copyright (C) 2010-2014 Henk Jegers
//Copyright (C) 2019 giuliof (GOLEM)
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//for questions email to info@archeryclock.com
// version MEGA01 Initial version
// version MEGA01_1 Adding robustness for receiving ABCD detailes which is implemented in AC 232. Using the ABCD details information is not yet implemented in MEGA01_1 Needs archeryclock version 2320 or newer.
// version MEGA01_2 Added end number information. Needs Archeryclock2401 or newer. (older archeryclock versions don't sent the end number information to arduine)
// version MEGA02_1 Changes because of a change in protocoll for AC 241 (nr of archers and E and F shooters) and added functionality: emergency stop, add archers E and F. Changes for signal robustness. (0 (0000) is sent as 12 (1100) to prevent decoding issues when 3 times 0 is sent (000000000000)
// #define GOLEM_PANEL
// Uncomment to enable nRF24 module
// #define HAS_WIFI
#include "utils.h"
#ifdef HAS_WIFI
#include <SPI.h>
#include <RF24.h>
#endif
/* * * * * * * * * * NRF24 Radio Module * * * * * * * * * */
#ifdef HAS_WIFI
uint64_t device_address = 0x0110104334LL;
//Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 40 (CE) & 41 (CSN)
RF24 radio(40, 41);
#endif
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Commands data structures
enum PanelSide_t
{
LEFT = 0,
RIGHT = 1
};
enum BuzzerStatus_t
{
BUZZER_OFF,
BUZZER_ON
};
struct cfg_t
{
PanelSide_t PanelSide : 1;
BuzzerStatus_t BuzzerStatus : 1;
uint8_t abcd : 3; // unclear meaning
uint8_t competition : 3; // unclear meaning
uint8_t GroupsNumber : 6;
uint8_t : 0;
} cfg;
struct trafficvalue_t
{
uint8_t cmd : 3; // < 0b111
BuzzerStatus_t buzzer : 1;
uint8_t TrafficLightLeft : 3;
uint8_t TrafficLightRight : 3;
uint8_t ArcherGroups : 6;
//uint8_t : 0;
};
struct statevalue_t
{
uint8_t cmd : 4; // = 0b1111
uint8_t abcd : 3; // unclear meaning
uint8_t competition : 3; // unclear meaning
uint8_t : 3;
uint8_t GroupsNumber : 6;
//uint8_t : 0;
};
struct digitsvalue_t
{
uint8_t cmd : 1;
PanelSide_t side : 1;
uint8_t sideOverride : 1;
uint8_t minutes : 1;
uint8_t rDigit : 4;
uint8_t mDigit : 4;
uint8_t lDigit : 4;
};
union ReceivedCommand_t {
int raw;
trafficvalue_t t;
statevalue_t s;
digitsvalue_t d;
} ReceivedCommand;
#ifndef GOLEM_PANEL
#define DOTS_bm _BV(7)
#else
#define DOTS_PIN 23
#endif
#define ABCD_PORT A
#define LEFTDIGIT_PORT K
#define MIDDIGIT_PORT L
#define RIGHTDIGIT_PORT C
#define TRAFLIGHT_PORT F
#define HORNP_PIN 7
#define REDP_PIN 6
#define ORANGEP_PIN 5
#define GREENP_PIN 4
#ifndef GOLEM_PANEL
#define A_PIN 25
#define B_PIN 24
#define C_PIN 23
#define D_PIN 22
#define notA_PIN 29
#define notB_PIN 28
#define notC_PIN 27
#define notD_PIN 26
#else
#define A_PIN 22
#define B_PIN 24
#define C_PIN 26
#define D_PIN 27
#define AC_PIN 25
#define BD_PIN 28
#endif
#define LR_PIN 10
#define MUTE_PIN 9
#define BRIGHT_PIN 8
// "translation" for seven segment
#define CHAR_P 15
#define CHAR_DASH 10
#ifndef GOLEM_PANEL
const uint8_t segment[16] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40, 0x00, 0x3f, 0x00, 0x00, 0x73}; //0,1,2,3,4,5,6,7,8,9,-, , , , ,P
#else
const uint8_t segment[16] = {0xbe, 0x82, 0x6e, 0xea, 0xd2, 0xf8, 0xfc, 0x8a, 0xfe, 0xfa, 0x40, 0x0, 0xbe, 0x0, 0x0, 0x5e}; //0,1,2,3,4,5,6,7,8,9,-, , , , ,P
#endif
boolean dataAvailable(char &digit)
{
if (Serial1.available())
{
digit = Serial1.read(); // Read serial buffer
return 1;
}
else if (Serial.available())
{
digit = Serial.read(); // Read serial buffer
return 1;
}
return 0;
}
boolean parseCommand(ReceivedCommand_t &ReceivedCommand)
{
// A command is received
char digit;
#ifdef HAS_WIFI
// Parse command from nRF packets
if (radio.available())
{
char inputString[6] = {0};
// Variable for the received timestamp
radio.read(&inputString, 5);
ReceivedCommand.raw = atoi(inputString);
return 1;
}
#endif
if (dataAvailable(digit))
{
/* ** Buffer to store received char **
* Data is an integer string terminated with newline '\n'
* */
static String inputString = "";
// Check if string terminator
if (digit == '\r')
{
// Make string conversion
ReceivedCommand.raw = inputString.toInt();
inputString = "";
// Now decode command and apply to the panel
// Refer to documentation for command structure
}
else // if (char) digit != '\n'
{
inputString += digit;
}
return 1;
}
return 0;
}
void setup()
{
pinMode(LR_PIN, INPUT_PULLUP);
pinMode(MUTE_PIN, INPUT_PULLUP);
pinMode(BRIGHT_PIN, INPUT_PULLUP);
CATDDR(ABCD_PORT) = 0xFF;
CATDDR(LEFTDIGIT_PORT) = 0xFF;
CATDDR(MIDDIGIT_PORT) = 0xFF;
CATDDR(RIGHTDIGIT_PORT) = 0xFF;
CATDDR(TRAFLIGHT_PORT) = 0xFF;
pinMode(REDP_PIN, OUTPUT);
pinMode(ORANGEP_PIN, OUTPUT);
pinMode(GREENP_PIN, OUTPUT);
pinMode(HORNP_PIN, OUTPUT);
// Load system configuration
cfg.BuzzerStatus = digitalRead(MUTE_PIN) ? BUZZER_ON : BUZZER_OFF;
cfg.PanelSide = digitalRead(LR_PIN) ? LEFT : RIGHT;
// Communication w/PC for debug or other puroposes
Serial.begin(9600);
Serial.println("Arduino Serial Link");
Serial.println("connected!");
// Communication w/XBEE
Serial1.begin(9600);
#ifdef HAS_WIFI
// Communication with nRF24
radio.begin();
// Set the PA Level low to prevent power supply related issues since this is a
// getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
radio.setPALevel(RF24_PA_LOW);
// Setup for the USB module
radio.setDataRate(RF24_2MBPS);
radio.setCRCLength(RF24_CRC_16);
radio.setRetries(15, 15);
radio.setChannel(40);
radio.setPayloadSize(32);
radio.openReadingPipe(1, device_address);
// Start the radio listening for data
radio.startListening();
#endif
}
void loop()
{
if (parseCommand(ReceivedCommand))
{
// Parsing of DIGITS command
if (ReceivedCommand.d.cmd == 0b0)
{
// Parse command only if is same side as this panel (or comamnd says to override)
if (ReceivedCommand.d.sideOverride || (ReceivedCommand.d.side == cfg.PanelSide))
{
CATPORT(MIDDIGIT_PORT) = segment[ReceivedCommand.d.mDigit];
CATPORT(RIGHTDIGIT_PORT) = segment[ReceivedCommand.d.rDigit];
// Turn on dot between minutes and seconds only if count is in minutes and its digit is a representable value (not dash)
#ifndef GOLEM_PANEL
if (ReceivedCommand.d.lDigit != CHAR_DASH && ReceivedCommand.d.minutes)
CATPORT(LEFTDIGIT_PORT) = segment[ReceivedCommand.d.lDigit] | DOTS_bm;
else
CATPORT(LEFTDIGIT_PORT) = segment[ReceivedCommand.d.lDigit];
#else
CATPORT(LEFTDIGIT_PORT) = segment[ReceivedCommand.d.lDigit];
#endif
#ifdef GOLEM_PANEL
if (ReceivedCommand.d.lDigit != CHAR_DASH && ReceivedCommand.d.minutes)
digitalWrite(DOTS_PIN, HIGH);
else
digitalWrite(DOTS_PIN, LOW);
#endif
}
}
// Parsing of TRAFFIC command
else if (ReceivedCommand.t.cmd == 0b001)
{
// Set buzzer depending on mute switch
if (cfg.BuzzerStatus == BUZZER_ON)
{
digitalWrite(HORNP_PIN, ReceivedCommand.t.buzzer);
}
// Set Traffic lights depending on side switch
if (cfg.PanelSide == RIGHT)
{
digitalWrite(GREENP_PIN, ReceivedCommand.t.TrafficLightRight & _BV(0)); // green right side
digitalWrite(ORANGEP_PIN, ReceivedCommand.t.TrafficLightRight & _BV(1)); // orange right side
digitalWrite(REDP_PIN, ReceivedCommand.t.TrafficLightRight & _BV(2)); // red right side
#ifndef GOLEM_PANEL
CATPORT(TRAFLIGHT_PORT) = (ReceivedCommand.t.TrafficLightRight & _BV(0) ? 0b1001 << 2 : 0) |
(ReceivedCommand.t.TrafficLightRight & _BV(1) ? 0b1001 << 1 : 0) |
(ReceivedCommand.t.TrafficLightRight & _BV(2) ? 0b1001 << 0 : 0);
#else
CATPORT(TRAFLIGHT_PORT) = (ReceivedCommand.t.TrafficLightRight & _BV(0) ? 0b11 << 6 : 0) |
(ReceivedCommand.t.TrafficLightRight & _BV(1) ? 0b1001 << 2 : 0) |
(ReceivedCommand.t.TrafficLightRight & _BV(2) ? 0b11 << 3 : 0);
#endif
}
else
{
digitalWrite(GREENP_PIN, ReceivedCommand.t.TrafficLightLeft & _BV(0)); // green left side
digitalWrite(ORANGEP_PIN, ReceivedCommand.t.TrafficLightLeft & _BV(1)); // orange left side
digitalWrite(REDP_PIN, ReceivedCommand.t.TrafficLightLeft & _BV(2)); // red left side
#ifndef GOLEM_PANEL
CATPORT(TRAFLIGHT_PORT) = (ReceivedCommand.t.TrafficLightLeft & _BV(0) ? 0b1001 << 2 : 0) |
(ReceivedCommand.t.TrafficLightLeft & _BV(1) ? 0b1001 << 1 : 0) |
(ReceivedCommand.t.TrafficLightLeft & _BV(2) ? 0b1001 << 0 : 0);
#else
CATPORT(TRAFLIGHT_PORT) = (ReceivedCommand.t.TrafficLightLeft & _BV(0) ? 0b11 << 6 : 0) |
(ReceivedCommand.t.TrafficLightLeft & _BV(1) ? 0b1001 << 2 : 0) |
(ReceivedCommand.t.TrafficLightLeft & _BV(2) ? 0b11 << 3 : 0);
#endif
}
// Set ABCD(EF)
digitalWrite(A_PIN, ReceivedCommand.t.ArcherGroups & _BV(0)); //A
digitalWrite(B_PIN, ReceivedCommand.t.ArcherGroups & _BV(1)); //B
digitalWrite(C_PIN, ReceivedCommand.t.ArcherGroups & _BV(2)); //C
digitalWrite(D_PIN, ReceivedCommand.t.ArcherGroups & _BV(3)); //D
#ifdef GOLEM_PANEL
digitalWrite(AC_PIN, ReceivedCommand.t.ArcherGroups & (_BV(0) | _BV(2))); //AC
digitalWrite(BD_PIN, ReceivedCommand.t.ArcherGroups & (_BV(1) | _BV(3))); //BD
#endif
// EF not implemented in this hardware
/*
digitalWrite(6, (((statevalue >> 7) & B00000111) != 2) and (trafficvalue & 0x4000)); //E
digitalWrite(7, (((statevalue >> 7) & B00000111) != 2) and (trafficvalue & 0x8000)); //F
digitalWrite(A15, (((statevalue >> 7) & B00000111) == 2) and (trafficvalue & 0x4000)); //green right arrow for fita finals
digitalWrite(39, (((statevalue >> 7) & B00000111) == 2) and (trafficvalue & 0x8000)); //green left arrow for fita finals
*/
// Set not{A,B,C,D,(E,F)}; EF not implemented in this hardware
// Note: GOLEM panel does not have not{A,B,C,D}
#ifndef GOLEM_PANEL
if (cfg.abcd != 6) {
// not A is on if archer group A is not selected
digitalWrite(notA_PIN, !(ReceivedCommand.t.ArcherGroups & _BV(0)));
// not B ~ not D are on if groups are effectively present and are not selected
if (cfg.competition != 2 && cfg.competition != 4)
{
digitalWrite(notB_PIN, cfg.GroupsNumber > 0 && !(ReceivedCommand.t.ArcherGroups & _BV(1)));
digitalWrite(notC_PIN, cfg.GroupsNumber > 1 && !(ReceivedCommand.t.ArcherGroups & _BV(2)));
digitalWrite(notD_PIN, cfg.GroupsNumber > 2 && !(ReceivedCommand.t.ArcherGroups & _BV(3)));
}
else
{
digitalWrite(notB_PIN, LOW);
digitalWrite(notC_PIN, LOW);
digitalWrite(notD_PIN, LOW);
}
}
else
{
digitalWrite(notA_PIN, ReceivedCommand.t.buzzer == BUZZER_ON);
digitalWrite(notB_PIN, ReceivedCommand.t.buzzer == BUZZER_ON);
digitalWrite(notC_PIN, ReceivedCommand.t.buzzer == BUZZER_ON);
digitalWrite(notD_PIN, ReceivedCommand.t.buzzer == BUZZER_ON);
}
#endif
}
// Parsing of STATE command
else if (ReceivedCommand.s.cmd == 0b1111)
{
cfg.abcd = ReceivedCommand.s.abcd;
cfg.competition = ReceivedCommand.s.competition;
cfg.GroupsNumber = ReceivedCommand.s.GroupsNumber;
}
}
// This feature is not implemented in this hardware
/*
lefend = ((endnrvalue >> 4) & B00001111);
midend = ((endnrvalue >> 8) & B00001111);
rigend = ((endnrvalue >> 12) & B00001111);
if (lefend == 15)
{
comend = lefend;
}
else
{
comend = midend;
};
digitalWrite(24, ((segment[comend] & 0x001)) ? HIGH : LOW); //left combined end digit segment A
digitalWrite(26, ((segment[comend] & 0x002)) ? HIGH : LOW); //left combined end digit segment B
digitalWrite(28, ((segment[comend] & 0x004)) ? HIGH : LOW); //left combined end digit segment C
digitalWrite(30, ((segment[comend] & 0x008)) ? HIGH : LOW); //left combined end digit segment D
digitalWrite(32, ((segment[comend] & 0x010)) ? HIGH : LOW); //left combined end digit segment E
digitalWrite(34, ((segment[comend] & 0x020)) ? HIGH : LOW); //left combined end digit segment F
digitalWrite(36, ((segment[comend] & 0x040)) ? HIGH : LOW); //left combined end digit segment G
digitalWrite(40, ((segment[rigend] & 0x001)) ? HIGH : LOW); //right end digit segment A
digitalWrite(42, ((segment[rigend] & 0x002)) ? HIGH : LOW); //right end digit segment B
digitalWrite(44, ((segment[rigend] & 0x004)) ? HIGH : LOW); //right end digit segment C
digitalWrite(46, ((segment[rigend] & 0x008)) ? HIGH : LOW); //right end digit segment D
digitalWrite(48, ((segment[rigend] & 0x010)) ? HIGH : LOW); //right end digit segment E
digitalWrite(50, ((segment[rigend] & 0x020)) ? HIGH : LOW); //right end digit segment F
digitalWrite(52, ((segment[rigend] & 0x040)) ? HIGH : LOW); //right end digit segment G
*/
// This feature is not implemented in this hardware
/*
//Read buttons
buttonvalue = 0;
// if (digitalRead(A4)){buttonvalue=5;};
// if (digitalRead(A3)){buttonvalue=4;};
// if (digitalRead(A0)){buttonvalue=1;}; //next
// if (digitalRead(A1)){buttonvalue=2;};
// if (digitalRead(A2)){buttonvalue=3;};
if (loop1 == 0)
{
if (buttonvalue != remember1)
{
Serial.print(buttonvalue);
delay(3);
remember1 = buttonvalue;
loop1 = 15;
//digitalWrite(2, 1);
};
}
else
{
delay(10);
//if (loop1>7){blinkr=1;};
loop1--;
};
*/
}