mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 02:43:45 +02:00
Compare commits
54 Commits
v5.5.15-De
...
wifiRev2
Author | SHA1 | Date | |
---|---|---|---|
|
9a9715ccbc | ||
|
43e7c18743 | ||
|
f09eee25dd | ||
|
6f70bec67e | ||
|
6737785388 | ||
|
c1a8206667 | ||
|
61931bf40a | ||
|
c21bb6053a | ||
|
4c9182d95f | ||
|
fd4d454463 | ||
|
3b74e16dd1 | ||
|
f120a1e43d | ||
|
a9a6b56654 | ||
|
f687625bfa | ||
|
7bffe0bd1d | ||
|
446beff20a | ||
|
f796f23d7b | ||
|
514bb31cdd | ||
|
653c421400 | ||
|
9dd210fa14 | ||
|
13757c8c57 | ||
|
dc36cbee0c | ||
|
4bf44f0051 | ||
|
b6847419fc | ||
|
0d8f45efad | ||
|
13dec796c1 | ||
|
d577606ee9 | ||
|
aba937f42f | ||
|
6958f029b7 | ||
|
1b19b61ebd | ||
|
7df07b03e4 | ||
|
4e6f79589a | ||
|
b8d61fb839 | ||
|
7092f7de33 | ||
|
13593ecf4f | ||
|
a4b63013ba | ||
|
13e516f8b2 | ||
|
cbb039c02f | ||
|
8a9feaef22 | ||
|
032b36ab45 | ||
|
9399aca63d | ||
|
3dede9eabe | ||
|
ff81b4d1b4 | ||
|
cb0d2bcdc5 | ||
|
740dcc7db4 | ||
|
1bc27a40e8 | ||
|
ba873fb8bc | ||
|
e7c76bf806 | ||
|
bdab5d0ff7 | ||
|
7d888e9aa9 | ||
|
74c22c83fc | ||
|
b0388bfc67 | ||
|
d96c919fee | ||
|
eececa322a |
33
.github/workflows/sha.yml
vendored
Normal file
33
.github/workflows/sha.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: SHA
|
||||||
|
|
||||||
|
# Run this workflow ever time code is pushed to a branch
|
||||||
|
# other than `main` in your repository
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Set the job key. The key is displayed as the job name
|
||||||
|
# when a job name is not provided
|
||||||
|
sha:
|
||||||
|
# Name the Job
|
||||||
|
name: Commit SHA
|
||||||
|
# Set the type of machine to run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Checks out a copy of your repository on the ubuntu-latest machine
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Create SHA File
|
||||||
|
run: |
|
||||||
|
sha=$(git rev-parse --short "$GITHUB_SHA")
|
||||||
|
echo "#define GITHUB_SHA \"$sha\"" > GITHUB_SHA.h
|
||||||
|
|
||||||
|
- uses: EndBug/add-and-commit@v4 # You can change this to use a specific version
|
||||||
|
with:
|
||||||
|
add: 'GITHUB_SHA.h'
|
||||||
|
message: 'Committing a SHA'
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ Release/*
|
|||||||
.gcc-flags.json
|
.gcc-flags.json
|
||||||
.pio/
|
.pio/
|
||||||
.vscode/
|
.vscode/
|
||||||
config.h
|
config.h
|
||||||
|
.vscode/extensions.json
|
||||||
|
@@ -1,194 +0,0 @@
|
|||||||
#ifndef ATMEGA2560Timer_h
|
|
||||||
#define ATMEGA2560Timer_h
|
|
||||||
|
|
||||||
#include "../VirtualTimer.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
class Timer : public VirtualTimer {
|
|
||||||
private:
|
|
||||||
int pwmPeriod;
|
|
||||||
unsigned long timer_resolution;
|
|
||||||
unsigned char clockSelectBits;
|
|
||||||
int timer_num;
|
|
||||||
unsigned long lastMicroseconds;
|
|
||||||
public:
|
|
||||||
void (*isrCallback)();
|
|
||||||
Timer(int timer_num) {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
case 3:
|
|
||||||
case 4:
|
|
||||||
case 5:
|
|
||||||
timer_resolution = 65536;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this->timer_num = timer_num;
|
|
||||||
lastMicroseconds = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initialize() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12);
|
|
||||||
TCCR1A = _BV(WGM11);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
TCCR3B = _BV(WGM33) | _BV(WGM32);
|
|
||||||
TCCR3A = _BV(WGM31);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
TCCR4B = _BV(WGM43) | _BV(WGM42);
|
|
||||||
TCCR4A = _BV(WGM41);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
TCCR5B = _BV(WGM53) | _BV(WGM52);
|
|
||||||
TCCR5A = _BV(WGM51);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPeriod(unsigned long microseconds) {
|
|
||||||
if(microseconds == lastMicroseconds)
|
|
||||||
return;
|
|
||||||
lastMicroseconds = microseconds;
|
|
||||||
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
|
|
||||||
if (cycles < timer_resolution) {
|
|
||||||
clockSelectBits = 1 << 0;
|
|
||||||
pwmPeriod = cycles;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 8) {
|
|
||||||
clockSelectBits = 1 << 1;
|
|
||||||
pwmPeriod = cycles / 8;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 64) {
|
|
||||||
clockSelectBits = (1 << 0) | (1 << 1);
|
|
||||||
pwmPeriod = cycles / 64;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 256) {
|
|
||||||
clockSelectBits = 1 << 2;
|
|
||||||
pwmPeriod = cycles / 256;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 1024) {
|
|
||||||
clockSelectBits = (1 << 2) | (1 << 0);
|
|
||||||
pwmPeriod = cycles / 1024;
|
|
||||||
} else {
|
|
||||||
clockSelectBits = (1 << 2) | (1 << 0);
|
|
||||||
pwmPeriod = timer_resolution - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
ICR1 = pwmPeriod;
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
ICR3 = pwmPeriod;
|
|
||||||
TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
ICR4 = pwmPeriod;
|
|
||||||
TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
ICR5 = pwmPeriod;
|
|
||||||
TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void start() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
TCCR1B = 0;
|
|
||||||
TCNT1 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
TCCR3B = 0;
|
|
||||||
TCNT3 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR3B = _BV(WGM33) | _BV(WGM32) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
TCCR4B = 0;
|
|
||||||
TCNT4 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR4B = _BV(WGM43) | _BV(WGM42) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
TCCR5B = 0;
|
|
||||||
TCNT5 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR5B = _BV(WGM53) | _BV(WGM52) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
void stop() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
TCCR3B = _BV(WGM33) | _BV(WGM32);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
TCCR4B = _BV(WGM43) | _BV(WGM42);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
TCCR5B = _BV(WGM53) | _BV(WGM52);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void attachInterrupt(void (*isr)()) {
|
|
||||||
isrCallback = isr;
|
|
||||||
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
TIMSK1 = _BV(TOIE1);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
TIMSK3 = _BV(TOIE3);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
TIMSK4 = _BV(TOIE4);
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
TIMSK5 = _BV(TOIE5);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void detachInterrupt() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
TIMSK1 = 0;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
TIMSK3 = 0;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
TIMSK4 = 0;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
TIMSK5 = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Timer TimerA;
|
|
||||||
extern Timer TimerB;
|
|
||||||
extern Timer TimerC;
|
|
||||||
extern Timer TimerD;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,208 +0,0 @@
|
|||||||
#ifndef ATMEGA328Timer_h
|
|
||||||
#define ATMEGA328Timer_h
|
|
||||||
|
|
||||||
#include "../VirtualTimer.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
class Timer : public VirtualTimer {
|
|
||||||
private:
|
|
||||||
int pwmPeriod;
|
|
||||||
unsigned long timer_resolution;
|
|
||||||
unsigned char clockSelectBits;
|
|
||||||
int timer_num;
|
|
||||||
unsigned long lastMicroseconds;
|
|
||||||
public:
|
|
||||||
void (*isrCallback)();
|
|
||||||
Timer(int timer_num) {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
//case 0:
|
|
||||||
case 2:
|
|
||||||
timer_resolution = 256;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
timer_resolution = 65536;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this->timer_num = timer_num;
|
|
||||||
lastMicroseconds = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void initialize() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// TCCR0B = _BV(WGM02);
|
|
||||||
// TCCR0A = _BV(WGM00) | _BV(WGM01);
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12);
|
|
||||||
TCCR1A = _BV(WGM11);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TCCR2B = _BV(WGM22);
|
|
||||||
TCCR2A = _BV(WGM20) | _BV(WGM21);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPeriod(unsigned long microseconds) {
|
|
||||||
if(microseconds == lastMicroseconds)
|
|
||||||
return;
|
|
||||||
lastMicroseconds = microseconds;
|
|
||||||
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
|
|
||||||
|
|
||||||
switch(timer_num) {
|
|
||||||
case 2:
|
|
||||||
if (cycles < timer_resolution) {
|
|
||||||
clockSelectBits = 1 << 0;
|
|
||||||
pwmPeriod = cycles;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 8) {
|
|
||||||
clockSelectBits = 1 << 1;
|
|
||||||
pwmPeriod = cycles / 8;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 32) {
|
|
||||||
clockSelectBits = 1 << 0 | 1 << 1;
|
|
||||||
pwmPeriod = cycles / 32;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 64) {
|
|
||||||
clockSelectBits = 1 << 2;
|
|
||||||
pwmPeriod = cycles / 64;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 128) {
|
|
||||||
clockSelectBits = 1 << 2 | 1 << 0;
|
|
||||||
pwmPeriod = cycles / 128;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 256) {
|
|
||||||
clockSelectBits = 1 << 2 | 1 << 1;
|
|
||||||
pwmPeriod = cycles / 256;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 1024) {
|
|
||||||
clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0;
|
|
||||||
pwmPeriod = cycles / 1024;
|
|
||||||
} else {
|
|
||||||
clockSelectBits = 1 << 2 | 1 << 1 | 1 << 0;
|
|
||||||
pwmPeriod = timer_resolution - 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
//case 0:
|
|
||||||
case 1:
|
|
||||||
if (cycles < timer_resolution) {
|
|
||||||
clockSelectBits = 1 << 0;
|
|
||||||
pwmPeriod = cycles;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 8) {
|
|
||||||
clockSelectBits = 1 << 1;
|
|
||||||
pwmPeriod = cycles / 8;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 64) {
|
|
||||||
clockSelectBits = (1 << 0) | (1 << 1);
|
|
||||||
pwmPeriod = cycles / 64;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 256) {
|
|
||||||
clockSelectBits = 1 << 2;
|
|
||||||
pwmPeriod = cycles / 256;
|
|
||||||
} else
|
|
||||||
if (cycles < timer_resolution * 1024) {
|
|
||||||
clockSelectBits = (1 << 2) | (1 << 0);
|
|
||||||
pwmPeriod = cycles / 1024;
|
|
||||||
} else {
|
|
||||||
clockSelectBits = (1 << 2) | (1 << 0);
|
|
||||||
pwmPeriod = timer_resolution - 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// OCR0A = pwmPeriod;
|
|
||||||
// TCCR0B = _BV(WGM02) | clockSelectBits;
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
ICR1 = pwmPeriod;
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
OCR2A = pwmPeriod;
|
|
||||||
TCCR2B = _BV(WGM22) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
void start() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// TCCR0B = 0;
|
|
||||||
// TCNT0 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
// TCCR0B = _BV(WGM02) | clockSelectBits;
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
TCCR1B = 0;
|
|
||||||
TCNT1 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TCCR2B = 0;
|
|
||||||
TCNT2 = 0; // TODO: does this cause an undesired interrupt?
|
|
||||||
TCCR2B = _BV(WGM22) | clockSelectBits;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
void stop() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// TCCR0B = _BV(WGM02);
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
TCCR1B = _BV(WGM13) | _BV(WGM12);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TCCR2B = _BV(WGM22);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void attachInterrupt(void (*isr)()) {
|
|
||||||
isrCallback = isr;
|
|
||||||
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// TIMSK0 = _BV(TOIE0);
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
TIMSK1 = _BV(TOIE1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TIMSK2 = _BV(TOIE2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void detachInterrupt() {
|
|
||||||
switch (timer_num)
|
|
||||||
{
|
|
||||||
// case 0:
|
|
||||||
// TIMSK0 = 0;
|
|
||||||
// break;
|
|
||||||
case 1:
|
|
||||||
TIMSK1 = 0;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
TIMSK2 = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
extern Timer TimerA;
|
|
||||||
extern Timer TimerB;
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* AnalogReadFast.h
|
|
||||||
*
|
|
||||||
* Copyright (C) 2016 Albert van Dalen http://www.avdweb.nl
|
|
||||||
*
|
|
||||||
* This file is part of CommandStation.
|
|
||||||
*
|
|
||||||
* CommandStation 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.
|
|
||||||
*
|
|
||||||
* CommandStation 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef COMMANDSTATION_DCC_ANALOGREADFAST_H_
|
|
||||||
#define COMMANDSTATION_DCC_ANALOGREADFAST_H_
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
int inline analogReadFast(uint8_t ADCpin);
|
|
||||||
|
|
||||||
int inline analogReadFast(uint8_t ADCpin)
|
|
||||||
{ byte ADCSRAoriginal = ADCSRA;
|
|
||||||
ADCSRA = (ADCSRA & B11111000) | 4;
|
|
||||||
int adc = analogRead(ADCpin);
|
|
||||||
ADCSRA = ADCSRAoriginal;
|
|
||||||
return adc;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // COMMANDSTATION_DCC_ANALOGREADFAST_H_
|
|
@@ -1,18 +0,0 @@
|
|||||||
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
|
|
||||||
// All Credit and copyright David Cutting
|
|
||||||
// The files included below come from the same source.
|
|
||||||
// This library had been included with the DCC code to avoid issues with
|
|
||||||
// library management for inexperienced users. "It just works (TM)"
|
|
||||||
|
|
||||||
#ifndef ArduinoTimers_h
|
|
||||||
#define ArduinoTimers_h
|
|
||||||
|
|
||||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
|
||||||
#include "ATMEGA2560/Timer.h"
|
|
||||||
#elif defined(ARDUINO_AVR_UNO)
|
|
||||||
#include "ATMEGA328/Timer.h"
|
|
||||||
#else
|
|
||||||
#error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags."
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,15 +1,49 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
// © 2020, Chris Harlow. All rights reserved.
|
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
||||||
//
|
//
|
||||||
// This file is a demonstattion of setting up a DCC-EX
|
// This file is the main sketch for the Command Station.
|
||||||
// Command station with optional support for direct connection of WiThrottle devices
|
//
|
||||||
// such as "Engine Driver". If you contriol your layout through JMRI
|
// CONFIGURATION:
|
||||||
// then DON'T connect throttles to this wifi, connect them to JMRI.
|
// Configuration is normally performed by editing a file called config.h.
|
||||||
|
// This file is NOT shipped with the code so that if you pull a later version
|
||||||
|
// of the code, your configuration will not be overwritten.
|
||||||
//
|
//
|
||||||
// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM.
|
// If you used the automatic installer program, config.h will have been created automatically.
|
||||||
|
//
|
||||||
|
// To obtain a starting copy of config.h please copy the file config.example.h which is
|
||||||
|
// shipped with the code and may be updated as new features are added.
|
||||||
|
//
|
||||||
|
// If config.h is not found, config.example.h will be used with all defaults.
|
||||||
////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#include "config.h"
|
#if __has_include ( "config.h")
|
||||||
|
#include "config.h"
|
||||||
|
#else
|
||||||
|
#warning config.h not found. Using defaults from config.example.h
|
||||||
|
#include "config.example.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* © 2020,2021 Chris Harlow, Harald Barth, David Cutting,
|
||||||
|
* Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This 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.
|
||||||
|
*
|
||||||
|
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include "DCCEX.h"
|
#include "DCCEX.h"
|
||||||
|
|
||||||
// Create a serial command parser for the USB connection,
|
// Create a serial command parser for the USB connection,
|
||||||
@@ -35,7 +69,7 @@ void setup()
|
|||||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||||
|
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT);
|
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), 2560);
|
||||||
#endif // WIFI_ON
|
#endif // WIFI_ON
|
||||||
|
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
@@ -49,9 +83,7 @@ void setup()
|
|||||||
|
|
||||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||||
|
|
||||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
|
||||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
|
||||||
|
|
||||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||||
|
|
||||||
#if defined(RMFT_ACTIVE)
|
#if defined(RMFT_ACTIVE)
|
||||||
|
47
DCC.cpp
47
DCC.cpp
@@ -17,12 +17,13 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include "DIAG.h"
|
||||||
#include "DCC.h"
|
#include "DCC.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DIAG.h"
|
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "GITHUB_SHA.h"
|
#include "GITHUB_SHA.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
// This module is responsible for converting API calls into
|
// This module is responsible for converting API calls into
|
||||||
// messages to be sent to the waveform generator.
|
// messages to be sent to the waveform generator.
|
||||||
@@ -43,17 +44,17 @@ const byte FN_GROUP_3=0x04;
|
|||||||
const byte FN_GROUP_4=0x08;
|
const byte FN_GROUP_4=0x08;
|
||||||
const byte FN_GROUP_5=0x10;
|
const byte FN_GROUP_5=0x10;
|
||||||
|
|
||||||
__FlashStringHelper* DCC::shieldName=NULL;
|
FSH* DCC::shieldName=NULL;
|
||||||
|
|
||||||
void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||||
shieldName=(__FlashStringHelper*)motorShieldName;
|
shieldName=(FSH *)motorShieldName;
|
||||||
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||||
|
|
||||||
// Load stuff from EEprom
|
// Load stuff from EEprom
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
EEStore::init();
|
EEStore::init();
|
||||||
|
|
||||||
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
|
DCCWaveform::begin(mainDriver,progDriver);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||||
@@ -228,18 +229,18 @@ void DCC::setProgTrackBoost(bool on) {
|
|||||||
DCCWaveform::progTrackBoosted=on;
|
DCCWaveform::progTrackBoosted=on;
|
||||||
}
|
}
|
||||||
|
|
||||||
__FlashStringHelper* DCC::getMotorShieldName() {
|
FSH* DCC::getMotorShieldName() {
|
||||||
return shieldName;
|
return shieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
|
const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
W0,WACK,
|
W0,WACK,
|
||||||
V0, WACK, // validate bit is 0
|
V0, WACK, // validate bit is 0
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp PROGMEM WRITE_BIT1_PROG[] = {
|
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
W1,WACK,
|
W1,WACK,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
@@ -247,7 +248,7 @@ const ackOp PROGMEM WRITE_BIT1_PROG[] = {
|
|||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
|
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V0, WACK, // validate bit is 0
|
V0, WACK, // validate bit is 0
|
||||||
ITC0, // if acked, callback(0)
|
ITC0, // if acked, callback(0)
|
||||||
@@ -255,7 +256,7 @@ const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
|
|||||||
ITC1,
|
ITC1,
|
||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
|
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
@@ -264,7 +265,7 @@ const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
|
|||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM READ_BIT_PROG[] = {
|
const ackOp FLASH READ_BIT_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
@@ -273,7 +274,7 @@ const ackOp PROGMEM READ_BIT_PROG[] = {
|
|||||||
FAIL // bit not readable
|
FAIL // bit not readable
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM WRITE_BYTE_PROG[] = {
|
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
WB,WACK, // Write
|
WB,WACK, // Write
|
||||||
VB,WACK, // validate byte
|
VB,WACK, // validate byte
|
||||||
@@ -281,7 +282,7 @@ const ackOp PROGMEM WRITE_BYTE_PROG[] = {
|
|||||||
FAIL // callback (-1)
|
FAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
|
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
VB,WACK, // validate byte
|
VB,WACK, // validate byte
|
||||||
ITCB, // if ok callback value
|
ITCB, // if ok callback value
|
||||||
@@ -306,7 +307,7 @@ const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
|
|||||||
FAIL };
|
FAIL };
|
||||||
|
|
||||||
|
|
||||||
const ackOp PROGMEM READ_CV_PROG[] = {
|
const ackOp FLASH READ_CV_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
STARTMERGE, //clear bit and byte values ready for merge pass
|
STARTMERGE, //clear bit and byte values ready for merge pass
|
||||||
// each bit is validated against 0 and the result inverted in MERGE
|
// each bit is validated against 0 and the result inverted in MERGE
|
||||||
@@ -329,7 +330,7 @@ const ackOp PROGMEM READ_CV_PROG[] = {
|
|||||||
FAIL }; // verification failed
|
FAIL }; // verification failed
|
||||||
|
|
||||||
|
|
||||||
const ackOp PROGMEM LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV, (ackOp)1,
|
SETCV, (ackOp)1,
|
||||||
SETBIT, (ackOp)7,
|
SETBIT, (ackOp)7,
|
||||||
@@ -399,7 +400,7 @@ const ackOp PROGMEM LOCO_ID_PROG[] = {
|
|||||||
FAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
|
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
@@ -415,7 +416,7 @@ const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
|
|||||||
FAIL
|
FAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp PROGMEM LONG_LOCO_ID_PROG[] = {
|
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
// Clear consist CV 19
|
// Clear consist CV 19
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
@@ -659,7 +660,7 @@ bool DCC::checkResets(bool blocking, uint8_t numResets) {
|
|||||||
|
|
||||||
void DCC::ackManagerLoop(bool blocking) {
|
void DCC::ackManagerLoop(bool blocking) {
|
||||||
while (ackManagerProg) {
|
while (ackManagerProg) {
|
||||||
byte opcode=pgm_read_byte_near(ackManagerProg);
|
byte opcode=GETFLASH(ackManagerProg);
|
||||||
|
|
||||||
// breaks from this switch will step to next prog entry
|
// breaks from this switch will step to next prog entry
|
||||||
// returns from this switch will stay on same entry
|
// returns from this switch will stay on same entry
|
||||||
@@ -786,17 +787,17 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
|
|
||||||
case SETBIT:
|
case SETBIT:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerBitNum=pgm_read_byte_near(ackManagerProg);
|
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETCV:
|
case SETCV:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerCv=pgm_read_byte_near(ackManagerProg);
|
ackManagerCv=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBYTE:
|
case SETBYTE:
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
ackManagerByte=pgm_read_byte_near(ackManagerProg);
|
ackManagerByte=GETFLASH(ackManagerProg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SETBYTEH:
|
case SETBYTEH:
|
||||||
@@ -822,9 +823,7 @@ void DCC::ackManagerLoop(bool blocking) {
|
|||||||
// SKIP opcodes until SKIPTARGET found
|
// SKIP opcodes until SKIPTARGET found
|
||||||
while (opcode!=SKIPTARGET) {
|
while (opcode!=SKIPTARGET) {
|
||||||
ackManagerProg++;
|
ackManagerProg++;
|
||||||
opcode=pgm_read_byte_near(ackManagerProg);
|
opcode=GETFLASH(ackManagerProg);
|
||||||
// Jump over second byte of any 2-byte opcodes.
|
|
||||||
if (opcode==SETBIT || opcode==SETBYTE || opcode==SETCV) ackManagerProg++;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SKIPTARGET:
|
case SKIPTARGET:
|
||||||
|
12
DCC.h
12
DCC.h
@@ -21,10 +21,10 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "MotorDrivers.h"
|
#include "MotorDrivers.h"
|
||||||
|
#include "FSH.h"
|
||||||
typedef void (*ACK_CALLBACK)(int result);
|
typedef void (*ACK_CALLBACK)(int result);
|
||||||
|
|
||||||
enum ackOp
|
enum ackOp : byte
|
||||||
{ // Program opcodes for the ack Manager
|
{ // Program opcodes for the ack Manager
|
||||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||||
W0,
|
W0,
|
||||||
@@ -64,7 +64,7 @@ const byte MAX_LOCOS = 50;
|
|||||||
class DCC
|
class DCC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
|
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||||
static void loop();
|
static void loop();
|
||||||
|
|
||||||
// Public DCC API functions
|
// Public DCC API functions
|
||||||
@@ -99,7 +99,7 @@ public:
|
|||||||
static void forgetAllLocos(); // removes all speed reminders
|
static void forgetAllLocos(); // removes all speed reminders
|
||||||
static void displayCabList(Print *stream);
|
static void displayCabList(Print *stream);
|
||||||
|
|
||||||
static __FlashStringHelper *getMotorShieldName();
|
static FSH *getMotorShieldName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LOCO
|
struct LOCO
|
||||||
@@ -115,7 +115,7 @@ private:
|
|||||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||||
static bool issueReminder(int reg);
|
static bool issueReminder(int reg);
|
||||||
static int nextLoco;
|
static int nextLoco;
|
||||||
static __FlashStringHelper *shieldName;
|
static FSH *shieldName;
|
||||||
|
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
static byte cv1(byte opcode, int cv);
|
static byte cv1(byte opcode, int cv);
|
||||||
@@ -162,6 +162,8 @@ private:
|
|||||||
#define ARDUINO_TYPE "NANO"
|
#define ARDUINO_TYPE "NANO"
|
||||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||||
#define ARDUINO_TYPE "MEGA"
|
#define ARDUINO_TYPE "MEGA"
|
||||||
|
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||||
|
#define ARDUINO_TYPE "MEGAAVR"
|
||||||
#else
|
#else
|
||||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||||
#endif
|
#endif
|
||||||
|
6
DCCEX.h
6
DCCEX.h
@@ -9,7 +9,11 @@
|
|||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "WifiInterface.h"
|
#ifdef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
|
#include "WifiInterfaceRev2.h"
|
||||||
|
#else
|
||||||
|
#include "WifiInterface.h"
|
||||||
|
#endif
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
#endif
|
#endif
|
||||||
|
@@ -52,7 +52,7 @@ const int HASH_KEYWORD_ETHERNET = -30767;
|
|||||||
const int HASH_KEYWORD_MAX = 16244;
|
const int HASH_KEYWORD_MAX = 16244;
|
||||||
const int HASH_KEYWORD_MIN = 15978;
|
const int HASH_KEYWORD_MIN = 15978;
|
||||||
|
|
||||||
int DCCEXParser::stashP[MAX_PARAMS];
|
int DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
|
|
||||||
Print *DCCEXParser::stashStream = NULL;
|
Print *DCCEXParser::stashStream = NULL;
|
||||||
@@ -102,7 +102,7 @@ void DCCEXParser::loop(Stream &stream)
|
|||||||
Sensor::checkAll(&stream); // Update and print changes
|
Sensor::checkAll(&stream); // Update and print changes
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||||
{
|
{
|
||||||
byte state = 1;
|
byte state = 1;
|
||||||
byte parameterCount = 0;
|
byte parameterCount = 0;
|
||||||
@@ -111,10 +111,10 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
|||||||
bool signNegative = false;
|
bool signNegative = false;
|
||||||
|
|
||||||
// clear all parameters in case not enough found
|
// clear all parameters in case not enough found
|
||||||
for (int i = 0; i < MAX_PARAMS; i++)
|
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||||
result[i] = 0;
|
result[i] = 0;
|
||||||
|
|
||||||
while (parameterCount < MAX_PARAMS)
|
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||||
{
|
{
|
||||||
byte hot = *remainingCmd;
|
byte hot = *remainingCmd;
|
||||||
|
|
||||||
@@ -143,6 +143,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
|||||||
runningValue = 10 * runningValue + (hot - '0');
|
runningValue = 10 * runningValue + (hot - '0');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
|
||||||
if (hot >= 'A' && hot <= 'Z')
|
if (hot >= 'A' && hot <= 'Z')
|
||||||
{
|
{
|
||||||
// Since JMRI got modified to send keywords in some rare cases, we need this
|
// Since JMRI got modified to send keywords in some rare cases, we need this
|
||||||
@@ -160,7 +161,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
|||||||
return parameterCount;
|
return parameterCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
|
int DCCEXParser::splitHexValues(int result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||||
{
|
{
|
||||||
byte state = 1;
|
byte state = 1;
|
||||||
byte parameterCount = 0;
|
byte parameterCount = 0;
|
||||||
@@ -168,10 +169,10 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
|
|||||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||||
|
|
||||||
// clear all parameters in case not enough found
|
// clear all parameters in case not enough found
|
||||||
for (int i = 0; i < MAX_PARAMS; i++)
|
for (int i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||||
result[i] = 0;
|
result[i] = 0;
|
||||||
|
|
||||||
while (parameterCount < MAX_PARAMS)
|
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||||
{
|
{
|
||||||
byte hot = *remainingCmd;
|
byte hot = *remainingCmd;
|
||||||
|
|
||||||
@@ -237,7 +238,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse an F() string
|
// Parse an F() string
|
||||||
void DCCEXParser::parse(const __FlashStringHelper * cmd) {
|
void DCCEXParser::parse(const FSH * cmd) {
|
||||||
int size=strlen_P((char *)cmd)+1;
|
int size=strlen_P((char *)cmd)+1;
|
||||||
char buffer[size];
|
char buffer[size];
|
||||||
strcpy_P(buffer,(char *)cmd);
|
strcpy_P(buffer,(char *)cmd);
|
||||||
@@ -250,7 +251,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("\nPARSING:%s\n"), com);
|
DIAG(F("\nPARSING:%s\n"), com);
|
||||||
int p[MAX_PARAMS];
|
int p[MAX_COMMAND_PARAMS];
|
||||||
while (com[0] == '<' || com[0] == ' ')
|
while (com[0] == '<' || com[0] == ' ')
|
||||||
com++; // strip off any number of < or spaces
|
com++; // strip off any number of < or spaces
|
||||||
byte params = splitValues(p, com);
|
byte params = splitValues(p, com);
|
||||||
@@ -314,12 +315,33 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
|||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
|
||||||
if (p[2] != (p[2] & 1))
|
{
|
||||||
return;
|
int address;
|
||||||
DCC::setAccessory(p[0], p[1], p[2] == 1);
|
byte subaddress;
|
||||||
|
byte activep;
|
||||||
|
if (params==2) { // <a LINEARADDRESS ACTIVATE>
|
||||||
|
address=(p[0] - 1) / 4 + 1;
|
||||||
|
subaddress=(p[0] - 1) % 4;
|
||||||
|
activep=1;
|
||||||
|
}
|
||||||
|
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||||
|
address=p[0];
|
||||||
|
subaddress=p[1];
|
||||||
|
activep=2;
|
||||||
|
}
|
||||||
|
else break; // invalid no of parameters
|
||||||
|
|
||||||
|
if (
|
||||||
|
((address & 0x01FF) != address) // invalid address (limit 9 bits )
|
||||||
|
|| ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits )
|
||||||
|
|| ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1
|
||||||
|
) break;
|
||||||
|
|
||||||
|
DCC::setAccessory(address, subaddress,p[activep]==1);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'T': // TURNOUT <T ...>
|
case 'T': // TURNOUT <T ...>
|
||||||
if (parseT(stream, params, p))
|
if (parseT(stream, params, p))
|
||||||
return;
|
return;
|
||||||
@@ -732,10 +754,6 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
|||||||
Diag::WITHROTTLE = onOff;
|
Diag::WITHROTTLE = onOff;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HASH_KEYWORD_DCC:
|
|
||||||
DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_PROGBOOST:
|
case HASH_KEYWORD_PROGBOOST:
|
||||||
DCC::setProgTrackBoost(true);
|
DCC::setProgTrackBoost(true);
|
||||||
return true;
|
return true;
|
||||||
@@ -752,13 +770,13 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CALLBACKS must be static
|
// CALLBACKS must be static
|
||||||
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS])
|
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS])
|
||||||
{
|
{
|
||||||
if (stashBusy )
|
if (stashBusy )
|
||||||
return false;
|
return false;
|
||||||
stashBusy = true;
|
stashBusy = true;
|
||||||
stashStream = stream;
|
stashStream = stream;
|
||||||
memcpy(stashP, p, MAX_PARAMS * sizeof(p[0]));
|
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void DCCEXParser::callback_W(int result)
|
void DCCEXParser::callback_W(int result)
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
#ifndef DCCEXParser_h
|
#ifndef DCCEXParser_h
|
||||||
#define DCCEXParser_h
|
#define DCCEXParser_h
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
||||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||||
@@ -28,12 +29,12 @@ struct DCCEXParser
|
|||||||
DCCEXParser();
|
DCCEXParser();
|
||||||
void loop(Stream & stream);
|
void loop(Stream & stream);
|
||||||
void parse(Print * stream, byte * command, bool blocking);
|
void parse(Print * stream, byte * command, bool blocking);
|
||||||
void parse(const __FlashStringHelper * cmd);
|
void parse(const FSH * cmd);
|
||||||
void flush();
|
void flush();
|
||||||
static void setFilter(FILTER_CALLBACK filter);
|
static void setFilter(FILTER_CALLBACK filter);
|
||||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||||
static const int MAX_PARAMS=10; // Must not exceed this
|
static const int MAX_COMMAND_PARAMS=10; // Must not exceed this
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@@ -41,8 +42,8 @@ struct DCCEXParser
|
|||||||
byte bufferLength=0;
|
byte bufferLength=0;
|
||||||
bool inCommandPayload=false;
|
bool inCommandPayload=false;
|
||||||
byte buffer[MAX_BUFFER+2];
|
byte buffer[MAX_BUFFER+2];
|
||||||
int splitValues( int result[MAX_PARAMS], const byte * command);
|
int splitValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
int splitHexValues( int result[MAX_PARAMS], const byte * command);
|
int splitHexValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
||||||
|
|
||||||
bool parseT(Print * stream, int params, int p[]);
|
bool parseT(Print * stream, int params, int p[]);
|
||||||
bool parseZ(Print * stream, int params, int p[]);
|
bool parseZ(Print * stream, int params, int p[]);
|
||||||
@@ -54,8 +55,8 @@ struct DCCEXParser
|
|||||||
static bool stashBusy;
|
static bool stashBusy;
|
||||||
|
|
||||||
static Print * stashStream;
|
static Print * stashStream;
|
||||||
static int stashP[MAX_PARAMS];
|
static int stashP[MAX_COMMAND_PARAMS];
|
||||||
bool stashCallback(Print * stream, int p[MAX_PARAMS]);
|
bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS]);
|
||||||
static void callback_W(int result);
|
static void callback_W(int result);
|
||||||
static void callback_B(int result);
|
static void callback_B(int result);
|
||||||
static void callback_R(int result);
|
static void callback_R(int result);
|
||||||
|
84
DCCTimer.cpp
Normal file
84
DCCTimer.cpp
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Chris Harlow & David Cutting. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of Asbelos DCC API
|
||||||
|
*
|
||||||
|
* This 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.
|
||||||
|
*
|
||||||
|
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||||
|
* All timer access comes through this class so that it can be compiled for
|
||||||
|
* various hardware CPU types.
|
||||||
|
*
|
||||||
|
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||||
|
* The DCCWaveform class generates the signals to the motor shield
|
||||||
|
* based on this timer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "DCCTimer.h"
|
||||||
|
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||||
|
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||||
|
|
||||||
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
|
|
||||||
|
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||||
|
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
interruptHandler=callback;
|
||||||
|
noInterrupts();
|
||||||
|
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||||
|
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||||
|
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||||
|
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
||||||
|
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||||
|
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||||
|
TCB0.CNT = 0;
|
||||||
|
TCB0.CTRLA |= TCB_ENABLE_bm; // start
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISR called by timer interrupt every 58uS
|
||||||
|
ISR(TCB0_INT_vect){
|
||||||
|
TCB0.INTFLAGS = TCB_CAPT_bm;
|
||||||
|
interruptHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
|
memcpy(mac,&SIGROW.SERNUM0,6); // serial number
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
// Arduino nano, uno, mega etc
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
interruptHandler=callback;
|
||||||
|
noInterrupts();
|
||||||
|
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||||
|
TCCR1A = 0;
|
||||||
|
ICR1 = CLOCK_CYCLES;
|
||||||
|
TCNT1 = 0;
|
||||||
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISR called by timer interrupt every 58uS
|
||||||
|
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||||
|
|
||||||
|
#include <avr/boot.h>
|
||||||
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
|
for (byte i=0; i<6; i++) mac[i]=boot_signature_byte_get(0x0E + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
14
DCCTimer.h
Normal file
14
DCCTimer.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef DCCTimer_h
|
||||||
|
#define DCCTimer_h
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
typedef void (*INTERRUPT_CALLBACK)();
|
||||||
|
|
||||||
|
class DCCTimer {
|
||||||
|
public:
|
||||||
|
static void begin(INTERRUPT_CALLBACK interrupt);
|
||||||
|
static void getSimulatedMacAddress(byte mac[6]);
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
182
DCCWaveform.cpp
182
DCCWaveform.cpp
@@ -17,13 +17,13 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#pragma GCC optimize ("-O3")
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
|
||||||
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;
|
|
||||||
|
|
||||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||||
@@ -31,33 +31,15 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
|||||||
|
|
||||||
bool DCCWaveform::progTrackSyncMain=false;
|
bool DCCWaveform::progTrackSyncMain=false;
|
||||||
bool DCCWaveform::progTrackBoosted=false;
|
bool DCCWaveform::progTrackBoosted=false;
|
||||||
VirtualTimer * DCCWaveform::interruptTimer=NULL;
|
int DCCWaveform::progTripValue=0;
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
|
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
mainTrack.motorDriver=mainDriver;
|
mainTrack.motorDriver=mainDriver;
|
||||||
progTrack.motorDriver=progDriver;
|
progTrack.motorDriver=progDriver;
|
||||||
|
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||||
progTrack.setPowerMode(POWERMODE::OFF);
|
progTrack.setPowerMode(POWERMODE::OFF);
|
||||||
switch (timerNumber) {
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
case 1: interruptTimer= &TimerA; break;
|
|
||||||
case 2: interruptTimer= &TimerB; break;
|
|
||||||
#ifndef ARDUINO_AVR_UNO
|
|
||||||
case 3: interruptTimer= &TimerC; break;
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
interruptTimer->initialize();
|
|
||||||
interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
|
|
||||||
interruptTimer->attachInterrupt(interruptHandler);
|
|
||||||
interruptTimer->start();
|
|
||||||
}
|
|
||||||
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
|
|
||||||
interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
|
|
||||||
interruptTimer->start();
|
|
||||||
DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::loop() {
|
void DCCWaveform::loop() {
|
||||||
@@ -65,19 +47,26 @@ void DCCWaveform::loop() {
|
|||||||
progTrack.checkPowerOverload();
|
progTrack.checkPowerOverload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// static //
|
|
||||||
void DCCWaveform::interruptHandler() {
|
void DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
bool mainCall2 = mainTrack.interrupt1();
|
// member functions would be cleaner but have more overhead
|
||||||
bool progCall2 = progTrack.interrupt1();
|
byte sigMain=signalTransform[mainTrack.state];
|
||||||
|
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
|
// Set the signal state for both tracks
|
||||||
|
mainTrack.motorDriver->setSignal(sigMain);
|
||||||
|
progTrack.motorDriver->setSignal(sigProg);
|
||||||
|
|
||||||
|
// Move on in the state engine
|
||||||
|
mainTrack.state=stateTransform[mainTrack.state];
|
||||||
|
progTrack.state=stateTransform[progTrack.state];
|
||||||
|
|
||||||
|
|
||||||
|
// WAVE_PENDING means we dont yet know what the next bit is
|
||||||
|
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||||
|
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||||
|
else if (progTrack.ackPending) progTrack.checkAck();
|
||||||
|
|
||||||
// call (if necessary) the procs to get the current bits
|
|
||||||
// these must complete within 50microsecs of the interrupt
|
|
||||||
// but they are only called ONCE PER BIT TRANSMITTED
|
|
||||||
// after the rising edge of the signal
|
|
||||||
if (mainCall2) mainTrack.interrupt2();
|
|
||||||
if (progCall2) progTrack.interrupt2();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -92,13 +81,12 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
|||||||
|
|
||||||
|
|
||||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
// establish appropriate pins
|
|
||||||
isMainTrack = isMain;
|
isMainTrack = isMain;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||||
state = 0;
|
state = WAVE_START;
|
||||||
// The +1 below is to allow the preamble generator to create the stop bit
|
// The +1 below is to allow the preamble generator to create the stop bit
|
||||||
// fpr the previous packet.
|
// for the previous packet.
|
||||||
requiredPreambles = preambleBits+1;
|
requiredPreambles = preambleBits+1;
|
||||||
bytes_sent = 0;
|
bytes_sent = 0;
|
||||||
bits_sent = 0;
|
bits_sent = 0;
|
||||||
@@ -112,20 +100,13 @@ POWERMODE DCCWaveform::getPowerMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||||
|
|
||||||
// Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away.
|
|
||||||
if (!interruptTimer) return;
|
|
||||||
|
|
||||||
powerMode = mode;
|
powerMode = mode;
|
||||||
bool ison = (mode == POWERMODE::ON);
|
bool ison = (mode == POWERMODE::ON);
|
||||||
motorDriver->setPower( ison);
|
motorDriver->setPower( ison);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DCCWaveform::checkPowerOverload() {
|
void DCCWaveform::checkPowerOverload() {
|
||||||
|
|
||||||
static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static
|
|
||||||
|
|
||||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||||
lastSampleTaken = millis();
|
lastSampleTaken = millis();
|
||||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||||
@@ -138,7 +119,12 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
break;
|
break;
|
||||||
case POWERMODE::ON:
|
case POWERMODE::ON:
|
||||||
// Check current
|
// Check current
|
||||||
lastCurrent = motorDriver->getCurrentRaw();
|
lastCurrent=motorDriver->getCurrentRaw();
|
||||||
|
if (lastCurrent < 0) {
|
||||||
|
// We have a fault pin condition to take care of
|
||||||
|
DIAG(F("\n*** %S FAULT PIN ACTIVE TOGGLE POWER ON THIS OR BOTH TRACKS ***\n"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||||
|
lastCurrent = -lastCurrent;
|
||||||
|
}
|
||||||
if (lastCurrent <= tripValue) {
|
if (lastCurrent <= tripValue) {
|
||||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||||
if(power_good_counter<100)
|
if(power_good_counter<100)
|
||||||
@@ -149,9 +135,9 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
setPowerMode(POWERMODE::OVERLOAD);
|
setPowerMode(POWERMODE::OVERLOAD);
|
||||||
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
||||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||||
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait);
|
|
||||||
power_good_counter=0;
|
power_good_counter=0;
|
||||||
sampleDelay = power_sample_overload_wait;
|
sampleDelay = power_sample_overload_wait;
|
||||||
|
DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
|
||||||
if (power_sample_overload_wait >= 10000)
|
if (power_sample_overload_wait >= 10000)
|
||||||
power_sample_overload_wait = 10000;
|
power_sample_overload_wait = 10000;
|
||||||
else
|
else
|
||||||
@@ -162,77 +148,45 @@ void DCCWaveform::checkPowerOverload() {
|
|||||||
// Try setting it back on after the OVERLOAD_WAIT
|
// Try setting it back on after the OVERLOAD_WAIT
|
||||||
setPowerMode(POWERMODE::ON);
|
setPowerMode(POWERMODE::ON);
|
||||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||||
|
// Debug code....
|
||||||
|
DIAG(F("\n*** %S TRACK POWER RESET delay=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// For each state of the wave nextState=stateTransform[currentState]
|
||||||
|
const WAVE_STATE DCCWaveform::stateTransform[]={
|
||||||
|
/* WAVE_START -> */ WAVE_PENDING,
|
||||||
|
/* WAVE_MID_1 -> */ WAVE_START,
|
||||||
|
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||||
|
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||||
|
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||||
|
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||||
|
|
||||||
|
// For each state of the wave, signal pin is HIGH or LOW
|
||||||
|
const bool DCCWaveform::signalTransform[]={
|
||||||
|
/* WAVE_START -> */ HIGH,
|
||||||
|
/* WAVE_MID_1 -> */ LOW,
|
||||||
// process time-edge sensitive part of interrupt
|
/* WAVE_HIGH_0 -> */ HIGH,
|
||||||
// return true if second level required
|
/* WAVE_MID_0 -> */ LOW,
|
||||||
bool DCCWaveform::interrupt1() {
|
/* WAVE_LOW_0 -> */ LOW,
|
||||||
// NOTE: this must consume transmission buffers even if the power is off
|
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||||
// otherwise can cause hangs in main loop waiting for the pendingBuffer.
|
|
||||||
switch (state) {
|
|
||||||
case 0: // start of bit transmission
|
|
||||||
setSignal(HIGH);
|
|
||||||
state = 1;
|
|
||||||
return true; // must call interrupt2 to set currentBit
|
|
||||||
|
|
||||||
case 1: // 58us after case 0
|
|
||||||
if (currentBit) {
|
|
||||||
setSignal(LOW);
|
|
||||||
state = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setSignal(HIGH); // jitter prevention
|
|
||||||
state = 2;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2: // 116us after case 0
|
|
||||||
setSignal(LOW);
|
|
||||||
state = 3;
|
|
||||||
break;
|
|
||||||
case 3: // finished sending zero bit
|
|
||||||
setSignal(LOW); // jitter prevention
|
|
||||||
state = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACK check is prog track only and will only be checked if
|
|
||||||
// this is not case(0) which needs relatively expensive packet change code to be called.
|
|
||||||
if (ackPending) checkAck();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCWaveform::setSignal(bool high) {
|
|
||||||
if (progTrackSyncMain) {
|
|
||||||
if (!isMainTrack) return; // ignore PROG track waveform while in sync
|
|
||||||
// set both tracks to same signal
|
|
||||||
motorDriver->setSignal(high);
|
|
||||||
progTrack.motorDriver->setSignal(high);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
motorDriver->setSignal(high);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DCCWaveform::interrupt2() {
|
void DCCWaveform::interrupt2() {
|
||||||
// set currentBit to be the next bit to be sent.
|
// calculate the next bit to be sent:
|
||||||
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
|
// or WAVE_HIGH_0 for a 0 bit.
|
||||||
|
|
||||||
if (remainingPreambles > 0 ) {
|
if (remainingPreambles > 0 ) {
|
||||||
currentBit = true;
|
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||||
remainingPreambles--;
|
remainingPreambles--;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wave has gone HIGH but what happens next depends on the bit to be transmitted
|
||||||
// beware OF 9-BIT MASK generating a zero to start each byte
|
// beware OF 9-BIT MASK generating a zero to start each byte
|
||||||
currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
|
state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0;
|
||||||
bits_sent++;
|
bits_sent++;
|
||||||
|
|
||||||
// If this is the last bit of a byte, prepare for the next byte
|
// If this is the last bit of a byte, prepare for the next byte
|
||||||
@@ -252,7 +206,10 @@ void DCCWaveform::interrupt2() {
|
|||||||
}
|
}
|
||||||
else if (packetPending) {
|
else if (packetPending) {
|
||||||
// Copy pending packet to transmit packet
|
// Copy pending packet to transmit packet
|
||||||
for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||||
|
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||||
|
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||||
|
|
||||||
transmitLength = pendingLength;
|
transmitLength = pendingLength;
|
||||||
transmitRepeats = pendingRepeats;
|
transmitRepeats = pendingRepeats;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
@@ -277,7 +234,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||||||
while (packetPending);
|
while (packetPending);
|
||||||
|
|
||||||
byte checksum = 0;
|
byte checksum = 0;
|
||||||
for (int b = 0; b < byteCount; b++) {
|
for (byte b = 0; b < byteCount; b++) {
|
||||||
checksum ^= buffer[b];
|
checksum ^= buffer[b];
|
||||||
pendingPacket[b] = buffer[b];
|
pendingPacket[b] = buffer[b];
|
||||||
}
|
}
|
||||||
@@ -288,16 +245,12 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCCWaveform::getLastCurrent() {
|
|
||||||
return lastCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operations applicable to PROG track ONLY.
|
// Operations applicable to PROG track ONLY.
|
||||||
// (yes I know I could have subclassed the main track but...)
|
// (yes I know I could have subclassed the main track but...)
|
||||||
|
|
||||||
void DCCWaveform::setAckBaseline() {
|
void DCCWaveform::setAckBaseline() {
|
||||||
if (isMainTrack) return;
|
if (isMainTrack) return;
|
||||||
int baseline = motorDriver->getCurrentRaw();
|
int baseline=motorDriver->getCurrentRaw();
|
||||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||||
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
|
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
|
||||||
baseline,motorDriver->raw2mA(baseline),
|
baseline,motorDriver->raw2mA(baseline),
|
||||||
@@ -325,18 +278,17 @@ byte DCCWaveform::getAck() {
|
|||||||
|
|
||||||
void DCCWaveform::checkAck() {
|
void DCCWaveform::checkAck() {
|
||||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||||
|
|
||||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||||
ackCheckDuration=millis()-ackCheckStart;
|
ackCheckDuration=millis()-ackCheckStart;
|
||||||
ackPending = false;
|
ackPending = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCurrent=motorDriver->getCurrentRaw();
|
int current=motorDriver->getCurrentRaw();
|
||||||
if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
|
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||||
|
|
||||||
if (lastCurrent>ackThreshold) {
|
if (current>ackThreshold) {
|
||||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
#ifndef DCCWaveform_h
|
#ifndef DCCWaveform_h
|
||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "ArduinoTimers.h"
|
|
||||||
|
|
||||||
// Wait times for power management. Unit: milliseconds
|
// Wait times for power management. Unit: milliseconds
|
||||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||||
@@ -30,15 +29,18 @@ const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
|||||||
// Number of preamble bits.
|
// Number of preamble bits.
|
||||||
const int PREAMBLE_BITS_MAIN = 16;
|
const int PREAMBLE_BITS_MAIN = 16;
|
||||||
const int PREAMBLE_BITS_PROG = 22;
|
const int PREAMBLE_BITS_PROG = 22;
|
||||||
|
const byte MAX_PACKET_SIZE = 5; // NMRA standard exrtended packets
|
||||||
|
|
||||||
|
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||||
|
// to the transform array.
|
||||||
|
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const byte MAX_PACKET_SIZE = 12;
|
|
||||||
// NOTE: static functions are used for the overall controller, then
|
// NOTE: static functions are used for the overall controller, then
|
||||||
// one instance is created for each track.
|
// one instance is created for each track.
|
||||||
|
|
||||||
|
|
||||||
enum class POWERMODE { OFF, ON, OVERLOAD };
|
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||||
|
|
||||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||||
@@ -46,8 +48,7 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
|
|||||||
class DCCWaveform {
|
class DCCWaveform {
|
||||||
public:
|
public:
|
||||||
DCCWaveform( byte preambleBits, bool isMain);
|
DCCWaveform( byte preambleBits, bool isMain);
|
||||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
|
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||||
static void setDiagnosticSlowWave(bool slow);
|
|
||||||
static void loop();
|
static void loop();
|
||||||
static DCCWaveform mainTrack;
|
static DCCWaveform mainTrack;
|
||||||
static DCCWaveform progTrack;
|
static DCCWaveform progTrack;
|
||||||
@@ -56,7 +57,6 @@ class DCCWaveform {
|
|||||||
void setPowerMode(POWERMODE);
|
void setPowerMode(POWERMODE);
|
||||||
POWERMODE getPowerMode();
|
POWERMODE getPowerMode();
|
||||||
void checkPowerOverload();
|
void checkPowerOverload();
|
||||||
int getLastCurrent();
|
|
||||||
inline int get1024Current() {
|
inline int get1024Current() {
|
||||||
if (powerMode == POWERMODE::ON)
|
if (powerMode == POWERMODE::ON)
|
||||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||||
@@ -105,12 +105,16 @@ class DCCWaveform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static VirtualTimer * interruptTimer;
|
|
||||||
|
// For each state of the wave nextState=stateTransform[currentState]
|
||||||
|
static const WAVE_STATE stateTransform[6];
|
||||||
|
|
||||||
|
// For each state of the wave, signal pin is HIGH or LOW
|
||||||
|
static const bool signalTransform[6];
|
||||||
|
|
||||||
static void interruptHandler();
|
static void interruptHandler();
|
||||||
bool interrupt1();
|
|
||||||
void interrupt2();
|
void interrupt2();
|
||||||
void checkAck();
|
void checkAck();
|
||||||
void setSignal(bool high);
|
|
||||||
|
|
||||||
bool isMainTrack;
|
bool isMainTrack;
|
||||||
MotorDriver* motorDriver;
|
MotorDriver* motorDriver;
|
||||||
@@ -120,15 +124,14 @@ class DCCWaveform {
|
|||||||
byte transmitRepeats; // remaining repeats of transmission
|
byte transmitRepeats; // remaining repeats of transmission
|
||||||
byte remainingPreambles;
|
byte remainingPreambles;
|
||||||
byte requiredPreambles;
|
byte requiredPreambles;
|
||||||
bool currentBit; // bit to be transmitted
|
|
||||||
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
|
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
|
||||||
byte bytes_sent; // number of bytes sent from transmitPacket
|
byte bytes_sent; // number of bytes sent from transmitPacket
|
||||||
byte state; // wave generator state machine
|
WAVE_STATE state; // wave generator state machine
|
||||||
|
|
||||||
byte pendingPacket[MAX_PACKET_SIZE];
|
byte pendingPacket[MAX_PACKET_SIZE];
|
||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
byte pendingRepeats;
|
||||||
int lastCurrent;
|
int lastCurrent;
|
||||||
|
static int progTripValue;
|
||||||
int maxmA;
|
int maxmA;
|
||||||
int tripmA;
|
int tripmA;
|
||||||
|
|
||||||
|
1
DIAG.h
1
DIAG.h
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef DIAG_h
|
#ifndef DIAG_h
|
||||||
#define DIAG_h
|
#define DIAG_h
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#define DIAG StringFormatter::diag
|
#define DIAG StringFormatter::diag
|
||||||
#define LCD StringFormatter::lcd
|
#define LCD StringFormatter::lcd
|
||||||
|
@@ -17,13 +17,18 @@
|
|||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
#if __has_include ( "config.h")
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "defines.h" // This should be changed to DCCEX.h when possible
|
#else
|
||||||
|
#warning config.h not found. Using defaults from config.example.h
|
||||||
|
#include "config.example.h"
|
||||||
|
#endif
|
||||||
|
#include "defines.h"
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "DCCTimer.h"
|
||||||
|
|
||||||
EthernetInterface * EthernetInterface::singleton=NULL;
|
EthernetInterface * EthernetInterface::singleton=NULL;
|
||||||
/**
|
/**
|
||||||
@@ -45,10 +50,15 @@ void EthernetInterface::setup()
|
|||||||
*/
|
*/
|
||||||
EthernetInterface::EthernetInterface()
|
EthernetInterface::EthernetInterface()
|
||||||
{
|
{
|
||||||
byte mac[]=MAC_ADDRESS;
|
byte mac[6];
|
||||||
|
DCCTimer::getSimulatedMacAddress(mac);
|
||||||
|
DIAG(F("\n+++++ Ethernet Setup. Simulatd mac="));
|
||||||
|
for (byte i=0;i<sizeof(mac); i++) {
|
||||||
|
DIAG(F("%x:"),mac[i]);
|
||||||
|
}
|
||||||
|
DIAG(F("\n"));
|
||||||
|
|
||||||
DIAG(F("\n+++++ Ethernet Setup "));
|
connected=false;
|
||||||
connected=false;
|
|
||||||
|
|
||||||
#ifdef IP_ADDRESS
|
#ifdef IP_ADDRESS
|
||||||
Ethernet.begin(mac, IP_ADDRESS);
|
Ethernet.begin(mac, IP_ADDRESS);
|
||||||
|
@@ -22,9 +22,13 @@
|
|||||||
|
|
||||||
#ifndef EthernetInterface_h
|
#ifndef EthernetInterface_h
|
||||||
#define EthernetInterface_h
|
#define EthernetInterface_h
|
||||||
|
#if __has_include ( "config.h")
|
||||||
|
#include "config.h"
|
||||||
|
#else
|
||||||
|
#warning config.h not found. Using defaults from config.example.h
|
||||||
|
#include "config.example.h"
|
||||||
|
#endif
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "MemStream.h"
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include <Ethernet.h>
|
#include <Ethernet.h>
|
||||||
@@ -34,11 +38,8 @@
|
|||||||
* @brief Network Configuration
|
* @brief Network Configuration
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#ifndef MAC_ADDRESS
|
|
||||||
#error define MAC_ADDRESS in config.h
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LISTEN_PORT 2560 // default listen port for the server
|
#define LISTEN_PORT 2560 // standard listen port for the server
|
||||||
#define MAX_ETH_BUFFER 512
|
#define MAX_ETH_BUFFER 512
|
||||||
#define OUTBOUND_RING_SIZE 2048
|
#define OUTBOUND_RING_SIZE 2048
|
||||||
|
|
||||||
|
30
FSH.h
Normal file
30
FSH.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef FSH_h
|
||||||
|
#define FSH_h
|
||||||
|
|
||||||
|
/* This is an architecture support file to manage the differences
|
||||||
|
* between the nano/uno.mega and the later nanoEvery, unoWifiRev2 etc
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* To maintain portability the main code should NOT contain ANY references
|
||||||
|
* to the following:
|
||||||
|
*
|
||||||
|
* __FlashStringHelper Use FSH instead.
|
||||||
|
* PROGMEM use FLASH instead
|
||||||
|
* pgm_read_byte_near use GETFLASH instead.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <Arduino.h>
|
||||||
|
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||||
|
#ifdef F
|
||||||
|
#undef F
|
||||||
|
#endif
|
||||||
|
#define F(str) (str)
|
||||||
|
typedef char FSH;
|
||||||
|
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
||||||
|
#define FLASH
|
||||||
|
#else
|
||||||
|
typedef __FlashStringHelper FSH;
|
||||||
|
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||||
|
#define FLASH PROGMEM
|
||||||
|
#endif
|
||||||
|
#endif
|
@@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "9db6d36"
|
#define GITHUB_SHA "43e7c18"
|
||||||
|
@@ -27,7 +27,6 @@
|
|||||||
|
|
||||||
#ifndef LCD_Implementation_h
|
#ifndef LCD_Implementation_h
|
||||||
#define LCD_Implementation_h
|
#define LCD_Implementation_h
|
||||||
#include "config.h"
|
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "LCDDisplay.h"
|
#include "LCDDisplay.h"
|
||||||
|
|
||||||
|
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
(c) 2015 Ingo Fischer
|
|
||||||
buffer serial device
|
|
||||||
based on Arduino SoftwareSerial
|
|
||||||
|
|
||||||
Constructor warning messages fixed by Chris Harlow.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library 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
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "MemStream.h"
|
|
||||||
|
|
||||||
MemStream::MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len, bool allowWrite)
|
|
||||||
:_buffer(buffer),_len(len), _buffer_overflow(false), _pos_read(0), _allowWrite(allowWrite)
|
|
||||||
{
|
|
||||||
if (content_len==0) memset(_buffer, 0, _len);
|
|
||||||
_pos_write=(content_len>len)? len: content_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t MemStream::write(uint8_t byte) {
|
|
||||||
if (! _allowWrite) return -1;
|
|
||||||
if (_pos_write >= _len) {
|
|
||||||
_buffer_overflow = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
_buffer[_pos_write] = byte;
|
|
||||||
++_pos_write;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemStream::flush() {
|
|
||||||
memset(_buffer, 0, _len);
|
|
||||||
_pos_write = 0;
|
|
||||||
_pos_read = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int MemStream::read() {
|
|
||||||
if (_pos_read >= _len) {
|
|
||||||
_buffer_overflow = true;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (_pos_read >= _pos_write) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _buffer[_pos_read++];
|
|
||||||
}
|
|
||||||
|
|
||||||
int MemStream::peek() {
|
|
||||||
if (_pos_read >= _len) {
|
|
||||||
_buffer_overflow = true;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (_pos_read >= _pos_write) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return _buffer[_pos_read+1];
|
|
||||||
}
|
|
||||||
|
|
||||||
int MemStream::available() {
|
|
||||||
int ret=_pos_write-_pos_read;
|
|
||||||
if (ret<0) ret=0;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemStream::setBufferContent(uint8_t *buffer, uint16_t content_len) {
|
|
||||||
memset(_buffer, 0, _len);
|
|
||||||
memcpy(_buffer, buffer, content_len);
|
|
||||||
_buffer_overflow=false;
|
|
||||||
_pos_write=content_len;
|
|
||||||
_pos_read=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemStream::setBufferContentFromProgmem(uint8_t *buffer, uint16_t content_len) {
|
|
||||||
memset(_buffer, 0, _len);
|
|
||||||
memcpy_P(_buffer, buffer, content_len);
|
|
||||||
_buffer_overflow=false;
|
|
||||||
_pos_write=content_len;
|
|
||||||
_pos_read=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemStream::setBufferContentPosition(uint16_t read_pos, uint16_t write_pos) {
|
|
||||||
_pos_write=write_pos;
|
|
||||||
_pos_read=read_pos;
|
|
||||||
}
|
|
78
MemStream.h
78
MemStream.h
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
(c) 2015 Ingo FIscher
|
|
||||||
buffer serial device
|
|
||||||
based on Arduino SoftwareSerial
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library 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
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MemStream_h
|
|
||||||
#define MemStream_h
|
|
||||||
|
|
||||||
#include <inttypes.h>
|
|
||||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
#include <Arduino.h>
|
|
||||||
#else
|
|
||||||
#include <Stream.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
|
|
||||||
class MemStream : public Stream
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
uint8_t *_buffer;
|
|
||||||
const uint16_t _len;
|
|
||||||
bool _buffer_overflow;
|
|
||||||
uint16_t _pos_read;
|
|
||||||
uint16_t _pos_write;
|
|
||||||
bool _allowWrite;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// public methods
|
|
||||||
MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len = 0, bool allowWrite = true);
|
|
||||||
~MemStream() {}
|
|
||||||
|
|
||||||
operator const uint8_t *() const { return _buffer; }
|
|
||||||
operator const char *() const { return (const char *)_buffer; }
|
|
||||||
|
|
||||||
uint16_t current_length() const { return _pos_write; }
|
|
||||||
|
|
||||||
bool listen() { return true; }
|
|
||||||
void end() {}
|
|
||||||
bool isListening() { return true; }
|
|
||||||
bool overflow()
|
|
||||||
{
|
|
||||||
bool ret = _buffer_overflow;
|
|
||||||
_buffer_overflow = false;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
int peek();
|
|
||||||
|
|
||||||
virtual size_t write(uint8_t byte);
|
|
||||||
virtual int read();
|
|
||||||
virtual int available();
|
|
||||||
virtual void flush();
|
|
||||||
|
|
||||||
void setBufferContent(uint8_t *buffer, uint16_t content_len);
|
|
||||||
void setBufferContentFromProgmem(uint8_t *buffer, uint16_t content_len);
|
|
||||||
void setBufferContentPosition(uint16_t read_pos, uint16_t write_pos);
|
|
||||||
|
|
||||||
using Print::write;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
104
MotorDriver.cpp
104
MotorDriver.cpp
@@ -18,50 +18,65 @@
|
|||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "AnalogReadFast.h"
|
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
|
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||||
|
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||||
|
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||||
|
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||||
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
#define WritePin digitalWrite
|
|
||||||
#define ReadPin digitalRead
|
|
||||||
#else
|
|
||||||
// use the DIO2 libraray for much faster pin access
|
|
||||||
#define GPIO2_PREFER_SPEED 1
|
|
||||||
#include <DIO2.h> // use IDE menu Tools..Manage Libraries to locate and install DIO2
|
|
||||||
#define WritePin digitalWrite2
|
|
||||||
#define ReadPin digitalRead2
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||||
powerPin=power_pin;
|
powerPin=power_pin;
|
||||||
|
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||||
|
pinMode(powerPin, OUTPUT);
|
||||||
|
|
||||||
signalPin=signal_pin;
|
signalPin=signal_pin;
|
||||||
|
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||||
|
pinMode(signalPin, OUTPUT);
|
||||||
|
|
||||||
signalPin2=signal_pin2;
|
signalPin2=signal_pin2;
|
||||||
|
if (signalPin2!=UNUSED_PIN) {
|
||||||
|
dualSignal=true;
|
||||||
|
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
||||||
|
pinMode(signalPin2, OUTPUT);
|
||||||
|
}
|
||||||
|
else dualSignal=false;
|
||||||
|
|
||||||
brakePin=brake_pin;
|
brakePin=brake_pin;
|
||||||
|
if (brake_pin!=UNUSED_PIN){
|
||||||
|
invertBrake=brake_pin < 0;
|
||||||
|
brakePin=invertBrake ? 0-brake_pin : brake_pin;
|
||||||
|
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||||
|
pinMode(brakePin, OUTPUT);
|
||||||
|
setBrake(false);
|
||||||
|
}
|
||||||
|
else brakePin=UNUSED_PIN;
|
||||||
|
|
||||||
currentPin=current_pin;
|
currentPin=current_pin;
|
||||||
senseFactor=sense_factor;
|
pinMode(currentPin, INPUT);
|
||||||
|
|
||||||
faultPin=fault_pin;
|
faultPin=fault_pin;
|
||||||
|
if (faultPin != UNUSED_PIN) {
|
||||||
|
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
|
||||||
|
pinMode(faultPin, INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
senseFactor=sense_factor;
|
||||||
tripMilliamps=trip_milliamps;
|
tripMilliamps=trip_milliamps;
|
||||||
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
||||||
pinMode(powerPin, OUTPUT);
|
|
||||||
pinMode(brakePin < 0 ? -brakePin : brakePin, OUTPUT);
|
|
||||||
setBrake(false);
|
|
||||||
pinMode(signalPin, OUTPUT);
|
|
||||||
if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT);
|
|
||||||
pinMode(currentPin, INPUT);
|
|
||||||
if (faultPin != UNUSED_PIN) pinMode(faultPin, INPUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MotorDriver::setPower(bool on) {
|
void MotorDriver::setPower(bool on) {
|
||||||
if (brakePin == -4 && on) {
|
if (on) {
|
||||||
// toggle brake before turning power on - resets overcurrent error
|
// toggle brake before turning power on - resets overcurrent error
|
||||||
// on the Pololu board if brake is wired to ^D2.
|
// on the Pololu board if brake is wired to ^D2.
|
||||||
setBrake(true);
|
setBrake(true);
|
||||||
setBrake(false);
|
setBrake(false);
|
||||||
|
setHIGH(fastPowerPin);
|
||||||
}
|
}
|
||||||
WritePin(powerPin, on ? HIGH : LOW);
|
else setLOW(fastPowerPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// setBrake applies brake if on == true. So to get
|
// setBrake applies brake if on == true. So to get
|
||||||
@@ -73,30 +88,31 @@ void MotorDriver::setPower(bool on) {
|
|||||||
// compensate for that.
|
// compensate for that.
|
||||||
//
|
//
|
||||||
void MotorDriver::setBrake(bool on) {
|
void MotorDriver::setBrake(bool on) {
|
||||||
bool state = on;
|
if (brakePin == UNUSED_PIN) return;
|
||||||
byte pin = brakePin;
|
if (on ^ invertBrake) setHIGH(fastBrakePin);
|
||||||
if (brakePin < 0) {
|
else setLOW(fastBrakePin);
|
||||||
pin=-pin;
|
|
||||||
state=!state;
|
|
||||||
}
|
|
||||||
WritePin(pin, state ? HIGH : LOW);
|
|
||||||
//DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MotorDriver::setSignal( bool high) {
|
void MotorDriver::setSignal( bool high) {
|
||||||
WritePin(signalPin, high ? HIGH : LOW);
|
if (high) {
|
||||||
if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH);
|
setHIGH(fastSignalPin);
|
||||||
|
if (dualSignal) setLOW(fastSignalPin2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setLOW(fastSignalPin);
|
||||||
|
if (dualSignal) setHIGH(fastSignalPin2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int MotorDriver::getCurrentRaw() {
|
int MotorDriver::getCurrentRaw() {
|
||||||
if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH)
|
int current = analogRead(currentPin);
|
||||||
return (int)(32000/senseFactor);
|
if (faultPin != UNUSED_PIN && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||||
|
return -current;
|
||||||
|
return current;
|
||||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||||
// The default analogRead takes ~100uS which is catastrphic
|
// The default analogRead takes ~100uS which is catastrphic
|
||||||
// so analogReadFast is used here. (-2uS)
|
// so DCCTimer has set the sample time to be much faster.
|
||||||
return analogReadFast(currentPin);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int MotorDriver::raw2mA( int raw) {
|
unsigned int MotorDriver::raw2mA( int raw) {
|
||||||
@@ -105,3 +121,15 @@ unsigned int MotorDriver::raw2mA( int raw) {
|
|||||||
int MotorDriver::mA2raw( unsigned int mA) {
|
int MotorDriver::mA2raw( unsigned int mA) {
|
||||||
return (int)(mA / senseFactor);
|
return (int)(mA / senseFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||||
|
DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin);
|
||||||
|
uint8_t port = digitalPinToPort(pin);
|
||||||
|
if (input)
|
||||||
|
result.inout = portInputRegister(port);
|
||||||
|
else
|
||||||
|
result.inout = portOutputRegister(port);
|
||||||
|
result.maskHIGH = digitalPinToBitMask(pin);
|
||||||
|
result.maskLOW = ~result.maskHIGH;
|
||||||
|
DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x\n"),port, result.inout,input,result.maskHIGH);
|
||||||
|
}
|
||||||
|
@@ -18,12 +18,20 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef MotorDriver_h
|
#ifndef MotorDriver_h
|
||||||
#define MotorDriver_h
|
#define MotorDriver_h
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
// Virtualised Motor shield 1-track hardware Interface
|
// Virtualised Motor shield 1-track hardware Interface
|
||||||
|
|
||||||
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
||||||
#define UNUSED_PIN 127 // inside int8_t
|
#define UNUSED_PIN 127 // inside int8_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct FASTPIN {
|
||||||
|
volatile uint8_t *inout;
|
||||||
|
uint8_t maskHIGH;
|
||||||
|
uint8_t maskLOW;
|
||||||
|
};
|
||||||
|
|
||||||
class MotorDriver {
|
class MotorDriver {
|
||||||
public:
|
public:
|
||||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||||
@@ -34,12 +42,18 @@ class MotorDriver {
|
|||||||
virtual unsigned int raw2mA( int raw);
|
virtual unsigned int raw2mA( int raw);
|
||||||
virtual int mA2raw( unsigned int mA);
|
virtual int mA2raw( unsigned int mA);
|
||||||
inline int getRawCurrentTripValue() {
|
inline int getRawCurrentTripValue() {
|
||||||
return rawCurrentTripValue;
|
return rawCurrentTripValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
byte powerPin, signalPin, signalPin2, currentPin, faultPin;
|
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||||
int8_t brakePin; // negative means pin is inverted
|
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||||
|
getFastPin(type, pin, 0, result);
|
||||||
|
}
|
||||||
|
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||||
|
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||||
|
bool dualSignal; // true to use signalPin2
|
||||||
|
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||||
float senseFactor;
|
float senseFactor;
|
||||||
unsigned int tripMilliamps;
|
unsigned int tripMilliamps;
|
||||||
int rawCurrentTripValue;
|
int rawCurrentTripValue;
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
#ifndef MotorDrivers_h
|
#ifndef MotorDrivers_h
|
||||||
#define MotorDrivers_h
|
#define MotorDrivers_h
|
||||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
|
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
|
||||||
// It will be overwritten if the library is updated.
|
// It will be overwritten if the library is updated.
|
||||||
|
@@ -22,13 +22,9 @@
|
|||||||
#if defined(ARDUINO_ARCH_SAMD)
|
#if defined(ARDUINO_ARCH_SAMD)
|
||||||
// Some processors use a gcc compiler that renames va_list!!!
|
// Some processors use a gcc compiler that renames va_list!!!
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||||
|
#else
|
||||||
#elif defined(ARDUINO_ARCH_AVR)
|
|
||||||
Print * StringFormatter::diagSerial= &Serial;
|
|
||||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
Print * StringFormatter::diagSerial=&Serial;
|
Print * StringFormatter::diagSerial=&Serial;
|
||||||
#define __FlashStringHelper char
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "LCDDisplay.h"
|
#include "LCDDisplay.h"
|
||||||
@@ -40,14 +36,14 @@ bool Diag::WITHROTTLE=false;
|
|||||||
bool Diag::ETHERNET=false;
|
bool Diag::ETHERNET=false;
|
||||||
|
|
||||||
|
|
||||||
void StringFormatter::diag( const __FlashStringHelper* input...) {
|
void StringFormatter::diag( const FSH* input...) {
|
||||||
if (!diagSerial) return;
|
if (!diagSerial) return;
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(diagSerial,input,args);
|
send2(diagSerial,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
|
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
// Issue the LCD as a diag first
|
// Issue the LCD as a diag first
|
||||||
@@ -62,25 +58,25 @@ void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
|
|||||||
send2(LCDDisplay::lcdDisplay,input,args);
|
send2(LCDDisplay::lcdDisplay,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) {
|
void StringFormatter::send(Print * stream, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(stream,input,args);
|
send2(stream,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) {
|
void StringFormatter::send(Print & stream, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(&stream,input,args);
|
send2(&stream,input,args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) {
|
void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||||
|
|
||||||
// thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output
|
// thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output
|
||||||
|
|
||||||
char* flash=(char*)format;
|
char* flash=(char*)format;
|
||||||
for(int i=0; ; ++i) {
|
for(int i=0; ; ++i) {
|
||||||
char c=pgm_read_byte_near(flash+i);
|
char c=GETFLASH(flash+i);
|
||||||
if (c=='\0') return;
|
if (c=='\0') return;
|
||||||
if(c!='%') { stream->print(c); continue; }
|
if(c!='%') { stream->print(c); continue; }
|
||||||
|
|
||||||
@@ -91,14 +87,14 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va
|
|||||||
|
|
||||||
formatContinues=false;
|
formatContinues=false;
|
||||||
i++;
|
i++;
|
||||||
c=pgm_read_byte_near(flash+i);
|
c=GETFLASH(flash+i);
|
||||||
switch(c) {
|
switch(c) {
|
||||||
case '%': stream->print('%'); break;
|
case '%': stream->print('%'); break;
|
||||||
case 'c': stream->print((char) va_arg(args, int)); break;
|
case 'c': stream->print((char) va_arg(args, int)); break;
|
||||||
case 's': stream->print(va_arg(args, char*)); break;
|
case 's': stream->print(va_arg(args, char*)); break;
|
||||||
case 'e': printEscapes(stream,va_arg(args, char*)); break;
|
case 'e': printEscapes(stream,va_arg(args, char*)); break;
|
||||||
case 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break;
|
case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break;
|
||||||
case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break;
|
case 'S': stream->print((const FSH*)va_arg(args, char*)); break;
|
||||||
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
||||||
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
||||||
case 'b': stream->print(va_arg(args, int), BIN); break;
|
case 'b': stream->print(va_arg(args, int), BIN); break;
|
||||||
@@ -138,12 +134,12 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) {
|
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||||
|
|
||||||
if (!stream) return;
|
if (!stream) return;
|
||||||
char* flash=(char*)input;
|
char* flash=(char*)input;
|
||||||
for(int i=0; ; ++i) {
|
for(int i=0; ; ++i) {
|
||||||
char c=pgm_read_byte_near(flash+i);
|
char c=GETFLASH(flash+i);
|
||||||
printEscape(stream,c);
|
printEscape(stream,c);
|
||||||
if (c=='\0') return;
|
if (c=='\0') return;
|
||||||
}
|
}
|
||||||
|
@@ -19,12 +19,10 @@
|
|||||||
#ifndef StringFormatter_h
|
#ifndef StringFormatter_h
|
||||||
#define StringFormatter_h
|
#define StringFormatter_h
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "FSH.h"
|
||||||
#if defined(ARDUINO_ARCH_SAMD)
|
#if defined(ARDUINO_ARCH_SAMD)
|
||||||
// Some processors use a gcc compiler that renames va_list!!!
|
// Some processors use a gcc compiler that renames va_list!!!
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
|
||||||
#define __FlashStringHelper char
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "LCDDisplay.h"
|
#include "LCDDisplay.h"
|
||||||
@@ -41,22 +39,22 @@ class Diag {
|
|||||||
class StringFormatter
|
class StringFormatter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void send(Print * serial, const __FlashStringHelper* input...);
|
static void send(Print * serial, const FSH* input...);
|
||||||
static void send(Print & serial, const __FlashStringHelper* input...);
|
static void send(Print & serial, const FSH* input...);
|
||||||
|
|
||||||
static void printEscapes(Print * serial,char * input);
|
static void printEscapes(Print * serial,char * input);
|
||||||
static void printEscapes(Print * serial,const __FlashStringHelper* input);
|
static void printEscapes(Print * serial,const FSH* input);
|
||||||
static void printEscape(Print * serial, char c);
|
static void printEscape(Print * serial, char c);
|
||||||
|
|
||||||
// DIAG support
|
// DIAG support
|
||||||
static Print * diagSerial;
|
static Print * diagSerial;
|
||||||
static void diag( const __FlashStringHelper* input...);
|
static void diag( const FSH* input...);
|
||||||
static void lcd(byte row, const __FlashStringHelper* input...);
|
static void lcd(byte row, const FSH* input...);
|
||||||
static void printEscapes(char * input);
|
static void printEscapes(char * input);
|
||||||
static void printEscape( char c);
|
static void printEscape( char c);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void send2(Print * serial, const __FlashStringHelper* input,va_list args);
|
static void send2(Print * serial, const FSH* input,va_list args);
|
||||||
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
52
Timer.cpp
52
Timer.cpp
@@ -1,52 +0,0 @@
|
|||||||
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
|
|
||||||
// All Credit to David Cutting
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
|
||||||
|
|
||||||
#include "ATMEGA2560/Timer.h"
|
|
||||||
|
|
||||||
Timer TimerA(1);
|
|
||||||
Timer TimerB(3);
|
|
||||||
Timer TimerC(4);
|
|
||||||
Timer TimerD(5);
|
|
||||||
|
|
||||||
ISR(TIMER1_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerA.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
ISR(TIMER3_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerB.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
ISR(TIMER4_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerC.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
ISR(TIMER5_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerD.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(ARDUINO_AVR_UNO) // Todo: add other 328 boards for compatibility
|
|
||||||
|
|
||||||
#include "ATMEGA328/Timer.h"
|
|
||||||
|
|
||||||
Timer TimerA(1);
|
|
||||||
Timer TimerB(2);
|
|
||||||
|
|
||||||
ISR(TIMER1_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerA.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
ISR(TIMER2_OVF_vect)
|
|
||||||
{
|
|
||||||
TimerB.isrCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,21 +0,0 @@
|
|||||||
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
|
|
||||||
// All Credit to David Cutting
|
|
||||||
|
|
||||||
#ifndef VirtualTimer_h
|
|
||||||
#define VirtualTimer_h
|
|
||||||
|
|
||||||
class VirtualTimer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void initialize() = 0;
|
|
||||||
virtual void setPeriod(unsigned long microseconds) = 0;
|
|
||||||
virtual void start() = 0;
|
|
||||||
virtual void stop() = 0;
|
|
||||||
|
|
||||||
virtual void attachInterrupt(void (*isr)()) = 0;
|
|
||||||
virtual void detachInterrupt() = 0;
|
|
||||||
private:
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@@ -1,3 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* © 2020, Chris Harlow. All rights reserved.
|
||||||
|
* © 2020, Harald Barth.
|
||||||
|
*
|
||||||
|
* This file is part of Asbelos DCC API
|
||||||
|
*
|
||||||
|
* This 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.
|
||||||
|
*
|
||||||
|
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "WifiInboundHandler.h"
|
#include "WifiInboundHandler.h"
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
@@ -228,3 +248,5 @@ void WifiInboundHandler::purgeCurrentCIPSEND() {
|
|||||||
pendingCipsend=false;
|
pendingCipsend=false;
|
||||||
clientPendingCIPSEND=-1;
|
clientPendingCIPSEND=-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@@ -15,12 +15,12 @@ class WifiInboundHandler {
|
|||||||
static WifiInboundHandler * singleton;
|
static WifiInboundHandler * singleton;
|
||||||
|
|
||||||
|
|
||||||
enum INBOUND_STATE {
|
enum INBOUND_STATE : byte {
|
||||||
INBOUND_BUSY, // keep calling in loop()
|
INBOUND_BUSY, // keep calling in loop()
|
||||||
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
|
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
|
||||||
};
|
};
|
||||||
|
|
||||||
enum LOOP_STATE {
|
enum LOOP_STATE : byte {
|
||||||
ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc...
|
ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc...
|
||||||
SKIPTOEND, // skip to newline
|
SKIPTOEND, // skip to newline
|
||||||
|
|
||||||
|
@@ -17,7 +17,8 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
|
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
||||||
#include "WifiInterface.h" /* config.h included there */
|
#include "WifiInterface.h" /* config.h included there */
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
@@ -25,11 +26,11 @@
|
|||||||
|
|
||||||
#include "WifiInboundHandler.h"
|
#include "WifiInboundHandler.h"
|
||||||
|
|
||||||
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
|
const char FLASH READY_SEARCH[] = "\r\nready\r\n";
|
||||||
const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n";
|
const char FLASH OK_SEARCH[] = "\r\nOK\r\n";
|
||||||
const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000";
|
const char FLASH END_DETAIL_SEARCH[] = "@ 1000";
|
||||||
const char PROGMEM SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
|
const char FLASH SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
|
||||||
const char PROGMEM IPD_SEARCH[] = "+IPD";
|
const char FLASH IPD_SEARCH[] = "+IPD";
|
||||||
const unsigned long LOOP_TIMEOUT = 2000;
|
const unsigned long LOOP_TIMEOUT = 2000;
|
||||||
bool WifiInterface::connected = false;
|
bool WifiInterface::connected = false;
|
||||||
Stream * WifiInterface::wifiStream;
|
Stream * WifiInterface::wifiStream;
|
||||||
@@ -43,7 +44,7 @@ Stream * WifiInterface::wifiStream;
|
|||||||
//
|
//
|
||||||
// Figure out number of serial ports depending on hardware
|
// Figure out number of serial ports depending on hardware
|
||||||
//
|
//
|
||||||
#if defined(ARDUINO_AVR_UNO)
|
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
|
||||||
#define NUM_SERIAL 0
|
#define NUM_SERIAL 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -56,9 +57,9 @@ Stream * WifiInterface::wifiStream;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool WifiInterface::setup(long serial_link_speed,
|
bool WifiInterface::setup(long serial_link_speed,
|
||||||
const __FlashStringHelper *wifiESSID,
|
const FSH *wifiESSID,
|
||||||
const __FlashStringHelper *wifiPassword,
|
const FSH *wifiPassword,
|
||||||
const __FlashStringHelper *hostname,
|
const FSH *hostname,
|
||||||
const int port) {
|
const int port) {
|
||||||
|
|
||||||
wifiSerialState wifiUp = WIFI_NOAT;
|
wifiSerialState wifiUp = WIFI_NOAT;
|
||||||
@@ -107,8 +108,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
|||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||||
const __FlashStringHelper* hostname, int port) {
|
const FSH* hostname, int port) {
|
||||||
wifiSerialState wifiState;
|
wifiSerialState wifiState;
|
||||||
static uint8_t ntry = 0;
|
static uint8_t ntry = 0;
|
||||||
ntry++;
|
ntry++;
|
||||||
@@ -139,8 +140,8 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
|
|||||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||||
#endif
|
#endif
|
||||||
wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||||
const __FlashStringHelper* hostname, int port) {
|
const FSH* hostname, int port) {
|
||||||
bool ipOK = false;
|
bool ipOK = false;
|
||||||
bool oldCmd = false;
|
bool oldCmd = false;
|
||||||
|
|
||||||
@@ -172,66 +173,71 @@ wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __F
|
|||||||
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
||||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||||
|
|
||||||
// If the source code looks unconfigured, check if the
|
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
||||||
// ESP8266 is preconfigured. We check the first 13 chars
|
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
||||||
// of the SSid.
|
if (checkForOK(2000, OK_SEARCH, true)) {
|
||||||
|
oldCmd=true;
|
||||||
|
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||||
|
}
|
||||||
|
|
||||||
const char *yourNetwork = "Your network ";
|
const char *yourNetwork = "Your network ";
|
||||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || ((const char *)SSid)[0] == '\0') {
|
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
||||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||||
// typical connect time approx 7 seconds
|
// If the source code looks unconfigured, check if the
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
// ESP8266 is preconfigured in station mode.
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
// We check the first 13 chars of the SSid and the password
|
||||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
|
||||||
ipOK = true;
|
|
||||||
} else { // Should this really be "else" here /haba
|
|
||||||
|
|
||||||
if (!ipOK) {
|
|
||||||
|
|
||||||
// Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n"));
|
|
||||||
if (checkForOK(2000, OK_SEARCH, true)) {
|
|
||||||
oldCmd=true;
|
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
|
||||||
|
|
||||||
|
// give a preconfigured ES8266 a chance to connect to a router
|
||||||
|
// typical connect time approx 7 seconds
|
||||||
|
delay(8000);
|
||||||
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||||
|
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||||
|
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||||
|
ipOK = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SSID was configured, so we assume station (client) mode.
|
||||||
|
if (oldCmd) {
|
||||||
// AT command early version supports CWJAP/CWSAP
|
// AT command early version supports CWJAP/CWSAP
|
||||||
if (SSid) {
|
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
||||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
|
||||||
}
|
|
||||||
DIAG(F("\n**\n"));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// later version supports CWJAP_CUR
|
// later version supports CWJAP_CUR
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||||
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
||||||
|
|
||||||
if (SSid) {
|
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
||||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ipOK) {
|
if (ipOK) {
|
||||||
// But we really only have the ESSID and password correct
|
// But we really only have the ESSID and password correct
|
||||||
// Let's check for IP
|
// Let's check for IP (via DHCP)
|
||||||
ipOK = false;
|
ipOK = false;
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||||
ipOK = true;
|
ipOK = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ipOK) {
|
if (!ipOK) {
|
||||||
// If we have not managed to get this going in station mode, go for AP mode
|
// If we have not managed to get this going in station mode, go for AP mode
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint.
|
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
||||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
// checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||||
|
|
||||||
|
int i=0;
|
||||||
|
do {
|
||||||
|
// configure as AccessPoint. Try really hard as this is the
|
||||||
|
// last way out to get any Wifi connectivity.
|
||||||
|
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n"));
|
||||||
|
} while (!checkForOK(1000+i*500, OK_SEARCH, true) && i++<10);
|
||||||
|
|
||||||
|
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||||
|
|
||||||
// Figure out MAC addr
|
// Figure out MAC addr
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
|
||||||
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
||||||
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
||||||
// Copy 17 byte mac address
|
// Copy 17 byte mac address
|
||||||
@@ -240,27 +246,27 @@ wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __F
|
|||||||
macAddress[i]=wifiStream->read();
|
macAddress[i]=wifiStream->read();
|
||||||
StringFormatter::printEscape(macAddress[i]);
|
StringFormatter::printEscape(macAddress[i]);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
memset(macAddress,'f',sizeof(macAddress));
|
||||||
}
|
}
|
||||||
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'};
|
||||||
|
|
||||||
if (oldCmd) {
|
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
|
||||||
|
|
||||||
int i=0;
|
i=0;
|
||||||
do {
|
do {
|
||||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||||
// unconfigured
|
// unconfigured
|
||||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), oldCmd ? "" : "_CUR", macTail, macTail);
|
||||||
} else {
|
} else {
|
||||||
// password configured by user
|
// password configured by user
|
||||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"%s\",1,4\r\n"), macTail, password);
|
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",1,4\r\n"), oldCmd ? "" : "_CUR", macTail, password);
|
||||||
}
|
}
|
||||||
} while (i++<2 && !checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok
|
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||||
} else {
|
if (i >= 2)
|
||||||
|
DIAG(F("\nWarning: Setting AP SSID and password failed\n")); // but issue warning
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
if (!oldCmd) {
|
||||||
checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok
|
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
||||||
checkForOK(2000, OK_SEARCH, true);
|
checkForOK(2000, OK_SEARCH, true);
|
||||||
}
|
}
|
||||||
@@ -277,8 +283,27 @@ wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __F
|
|||||||
#endif //DONT_TOUCH_WIFI_CONF
|
#endif //DONT_TOUCH_WIFI_CONF
|
||||||
|
|
||||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||||
|
if (!checkForOK(1000, (const char *)F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
||||||
|
// Copy the IP address
|
||||||
|
{
|
||||||
|
const byte MAX_IP_LENGTH=15;
|
||||||
|
char ipString[MAX_IP_LENGTH+1];
|
||||||
|
ipString[MAX_IP_LENGTH]='\0'; // protection against missing " character on end.
|
||||||
|
for(byte ipLen=0;ipLen<MAX_IP_LENGTH;ipLen++) {
|
||||||
|
while(!wifiStream->available());
|
||||||
|
int ipChar=wifiStream->read();
|
||||||
|
StringFormatter::printEscape(ipChar);
|
||||||
|
if (ipChar=='"') {
|
||||||
|
ipString[ipLen]='\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ipString[ipLen]=ipChar;
|
||||||
|
}
|
||||||
|
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
|
||||||
|
}
|
||||||
|
// suck up anything after the IP.
|
||||||
if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED;
|
||||||
DIAG(F("\nPORT=%d\n"),port);
|
LCD(5,F("PORT=%d\n"),port);
|
||||||
|
|
||||||
return WIFI_CONNECTED;
|
return WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
@@ -317,10 +342,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
|
|||||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||||
else DIAG(F("%c"), ch);
|
else DIAG(F("%c"), ch);
|
||||||
}
|
}
|
||||||
if (ch != pgm_read_byte_near(locator)) locator = waitfor;
|
if (ch != GETFLASH(locator)) locator = waitfor;
|
||||||
if (ch == pgm_read_byte_near(locator)) {
|
if (ch == GETFLASH(locator)) {
|
||||||
locator++;
|
locator++;
|
||||||
if (!pgm_read_byte_near(locator)) {
|
if (!GETFLASH(locator)) {
|
||||||
DIAG(F("\nFound in %dms"), millis() - startTime);
|
DIAG(F("\nFound in %dms"), millis() - startTime);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -337,3 +362,5 @@ void WifiInterface::loop() {
|
|||||||
WifiInboundHandler::loop();
|
WifiInboundHandler::loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef WifiInterface_h
|
#ifndef WifiInterface_h
|
||||||
#define WifiInterface_h
|
#define WifiInterface_h
|
||||||
#include "config.h"
|
#include "FSH.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
@@ -31,20 +31,20 @@ class WifiInterface
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static bool setup(long serial_link_speed,
|
static bool setup(long serial_link_speed,
|
||||||
const __FlashStringHelper *wifiESSID,
|
const FSH *wifiESSID,
|
||||||
const __FlashStringHelper *wifiPassword,
|
const FSH *wifiPassword,
|
||||||
const __FlashStringHelper *hostname,
|
const FSH *hostname,
|
||||||
const int port = 2560);
|
const int port = 2560);
|
||||||
static void loop();
|
static void loop();
|
||||||
static void ATCommand(const byte *command);
|
static void ATCommand(const byte *command);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static wifiSerialState setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||||
const __FlashStringHelper *hostname, int port);
|
const FSH *hostname, int port);
|
||||||
static Stream *wifiStream;
|
static Stream *wifiStream;
|
||||||
static DCCEXParser parser;
|
static DCCEXParser parser;
|
||||||
static wifiSerialState setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||||
const __FlashStringHelper *hostname, int port);
|
const FSH *hostname, int port);
|
||||||
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
||||||
static bool connected;
|
static bool connected;
|
||||||
static bool closeAfter;
|
static bool closeAfter;
|
||||||
|
103
WifiInterfaceRev2.cpp
Normal file
103
WifiInterfaceRev2.cpp
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Chris Harlow. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-EX/CommandStation-EX
|
||||||
|
*
|
||||||
|
* This 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.
|
||||||
|
*
|
||||||
|
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifdef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
|
// This code is ONLY compiled on a unoWifiRev2 processor which uses a different architecture
|
||||||
|
|
||||||
|
#include "WifiInterfaceRev2.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <WiFiNINA.h>
|
||||||
|
|
||||||
|
|
||||||
|
WiFiServer WifiInterface::server(2560);
|
||||||
|
bool WifiInterface::connected=false;
|
||||||
|
/**
|
||||||
|
* @brief Setup Wifi Connection
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool WifiInterface::setup(long serial_link_speed,
|
||||||
|
const FSH *wifiESSID,
|
||||||
|
const FSH *wifiPassword,
|
||||||
|
const FSH *hostname,
|
||||||
|
const int port) {
|
||||||
|
(void)serial_link_speed;
|
||||||
|
(void)port; // obsolete
|
||||||
|
(void)hostname; // To be implemented
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_NO_MODULE) {
|
||||||
|
DIAG(F("Wifi- hardware failed\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DIAG(F("Wifi Firmware=%s expected=%S"),WiFi.firmwareVersion(),F(WIFI_FIRMWARE_LATEST_VERSION));
|
||||||
|
|
||||||
|
|
||||||
|
int status = WL_IDLE_STATUS;
|
||||||
|
int attempts = 4;
|
||||||
|
while (status != WL_CONNECTED) {
|
||||||
|
if (attempts-- <= 0) {
|
||||||
|
DIAG(F("\nFAILED - No Wifi\n"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DIAG(F("\nAttempting to connect to %s\n"),wifiESSID);
|
||||||
|
status = WiFi.begin(wifiESSID, wifiPassword);
|
||||||
|
// wait 10 seconds for connection:
|
||||||
|
delay(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.begin(); // start the server on port 2560
|
||||||
|
|
||||||
|
IPAddress ip = WiFi.localIP();
|
||||||
|
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||||
|
LCD(5,F("Port:2560"));
|
||||||
|
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Main loop for the WifiInterfaceRev2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void WifiInterface::loop()
|
||||||
|
{
|
||||||
|
WiFiClient client = server.available(); // listen for incoming clients
|
||||||
|
if (client)
|
||||||
|
{
|
||||||
|
// read bytes from a client
|
||||||
|
byte buffer[MAX_NINA_BUFFER];
|
||||||
|
int count = client.read(buffer, MAX_NINA_BUFFER-1);
|
||||||
|
buffer[count] = '\0'; // terminate the string properly
|
||||||
|
if (Diag::WIFI) DIAG(F("WIFI:%e\n"), buffer);
|
||||||
|
// TEMPORARY - Assume all clients are client 1, this will confuse WiThrottle!
|
||||||
|
outboundRing->mark(1);
|
||||||
|
// TEMPORARY - Assume all clients are client 1, this will confuse WiThrottle!
|
||||||
|
CommandDistributor::parse(1,buffer,outboundRing);
|
||||||
|
outboundRing->commit();
|
||||||
|
int socketOut=outboundRing->read();
|
||||||
|
if (socketOut>=0) {
|
||||||
|
int count=outboundRing->count();
|
||||||
|
if (Diag::WIFI) DIAG(F("Wifi Reply count=:%d\n"), count);
|
||||||
|
for(;count>0;count--) client.write(outboundRing->read());
|
||||||
|
client.flush(); //maybe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
44
WifiInterfaceRev2.h
Normal file
44
WifiInterfaceRev2.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Chris Harlow. All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This 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.
|
||||||
|
*
|
||||||
|
* It 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 CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#ifndef WifiInterface_h
|
||||||
|
#define WifiInterface_h
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <WiFiNINA.h>
|
||||||
|
#include "FSH.h"
|
||||||
|
#include "RingStream.h"
|
||||||
|
|
||||||
|
#define MAX_NINA_BUFFER 512
|
||||||
|
#define OUTBOUND_RING_SIZE 2048
|
||||||
|
|
||||||
|
class WifiInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool setup(long serial_link_speed, // ignored
|
||||||
|
const FSH *wifiESSID,
|
||||||
|
const FSH *wifiPassword,
|
||||||
|
const FSH *hostname,
|
||||||
|
const int port = 2560); // ignored
|
||||||
|
static void loop();
|
||||||
|
private:
|
||||||
|
static WiFiServer server;
|
||||||
|
static bool connected;
|
||||||
|
static RingStream * outboundRing;
|
||||||
|
};
|
||||||
|
#endif
|
@@ -1,10 +1,9 @@
|
|||||||
/**********************************************************************
|
/**********************************************************************
|
||||||
|
|
||||||
Config.h
|
config.h
|
||||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
|
||||||
COPYRIGHT (c) 2020 Fred Decker
|
COPYRIGHT (c) 2020 Fred Decker
|
||||||
|
|
||||||
The configuration file for DCC++ EX Command Station
|
The configuration file for DCC-EX Command Station
|
||||||
|
|
||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
@@ -27,12 +26,6 @@ The configuration file for DCC++ EX Command Station
|
|||||||
//
|
//
|
||||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// The IP port to talk to a WIFI or Ethernet shield.
|
|
||||||
//
|
|
||||||
#define IP_PORT 2560
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// NOTE: Only supported on Arduino Mega
|
// NOTE: Only supported on Arduino Mega
|
||||||
@@ -52,10 +45,14 @@ The configuration file for DCC++ EX Command Station
|
|||||||
// WIFI_SSID is the network name IF you want to use your existing home network.
|
// WIFI_SSID is the network name IF you want to use your existing home network.
|
||||||
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
|
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
|
||||||
//
|
//
|
||||||
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
|
// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD,
|
||||||
// to connect to the previously configured network and if that fails
|
// then the WiFi chip will first try to connect to the previously
|
||||||
// fall back to Access Point mode. The SSID of the AP will be
|
// configured network and if that fails fall back to Access Point mode.
|
||||||
// automatically set to DCCEX_*.
|
// The SSID of the AP will be automatically set to DCCEX_*.
|
||||||
|
// If you DO set the WIFI_SSID then the WiFi chip will try to connect
|
||||||
|
// to that (home) network in station (client) mode. If a WIFI_PASSWORD
|
||||||
|
// is set (recommended), that password will be used for AP mode.
|
||||||
|
// The AP mode password must be at least 8 characters long.
|
||||||
//
|
//
|
||||||
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
|
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
|
||||||
#define WIFI_SSID "Your network name"
|
#define WIFI_SSID "Your network name"
|
||||||
@@ -90,26 +87,6 @@ The configuration file for DCC++ EX Command Station
|
|||||||
//
|
//
|
||||||
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
//#define IP_ADDRESS { 192, 168, 1, 200 }
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
|
|
||||||
//
|
|
||||||
// Uncomment to use with Ethernet Shields
|
|
||||||
//
|
|
||||||
// Ethernet Shields do not have have a MAC address in hardware. There may be one on
|
|
||||||
// a sticker on the Shield that you should use. Otherwise choose one of the ones below
|
|
||||||
// Be certain that no other device on your network has this same MAC address!
|
|
||||||
//
|
|
||||||
// 52:b8:8a:8e:ce:21
|
|
||||||
// e3:e9:73:e1:db:0d
|
|
||||||
// 54:2b:13:52:ac:0c
|
|
||||||
|
|
||||||
// NOTE: This is not used with ESP8266 WiFi modules.
|
|
||||||
|
|
||||||
//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
|
|
||||||
|
|
||||||
//
|
|
||||||
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
@@ -1,152 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is a demonstattion of calling the DCC-EX API
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#include "DCCEX.h"
|
|
||||||
|
|
||||||
#ifdef ARDUINO_AVR_UNO
|
|
||||||
#include <SoftwareSerial.h>
|
|
||||||
SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO
|
|
||||||
#define WIFI_BAUD 9600
|
|
||||||
#else
|
|
||||||
#define WIFI_BAUD 115200
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// this code is here to demonstrate use of the DCC API and other techniques
|
|
||||||
|
|
||||||
// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from
|
|
||||||
// the usb or wifi streamm. It demonstrates how a command may be intercepted
|
|
||||||
// or even a new command created without having to break open the API library code.
|
|
||||||
// The filter is permitted to use or modify the parameter list before passing it on to
|
|
||||||
// the standard parser. By setting the opcode to 0, the standard parser will
|
|
||||||
// just ignore the command on the assumption that you have already handled it.
|
|
||||||
//
|
|
||||||
// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup().
|
|
||||||
|
|
||||||
void myComandFilter(Print * stream, byte & opcode, byte & paramCount, int p[]) {
|
|
||||||
(void)stream; // avoid compiler warning if we don't access this parameter
|
|
||||||
switch (opcode) {
|
|
||||||
case '!': // Create a bespoke new command to clear all loco reminders <!> or specific locos e.g <! 3 4 99>
|
|
||||||
if (paramCount==0) DCC::forgetAllLocos();
|
|
||||||
else for (int i=0;i<paramCount;i++) DCC::forgetLoco(p[i]);
|
|
||||||
opcode=0; // tell parser to ignore this command as we have done it already
|
|
||||||
break;
|
|
||||||
default: // drop through and parser will use the command unaltered.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// This is an OPTIONAL example of a HTTP filter...
|
|
||||||
// If you have configured wifi and an HTTP request is received on the Wifi connection
|
|
||||||
// it will normally be rejected 404 Not Found.
|
|
||||||
|
|
||||||
// If you wish to handle HTTP requests, you can create a filter and ask the WifiInterface to
|
|
||||||
// call your code for each detected http request.
|
|
||||||
|
|
||||||
void myHttpFilter(Print * stream, byte * cmd) {
|
|
||||||
(void)cmd; // Avoid compiler warning because this example doesnt use this parameter
|
|
||||||
|
|
||||||
// BEWARE - As soon as you start responding, the cmd buffer is trashed!
|
|
||||||
// You must get everything you need from it before using StringFormatter::send!
|
|
||||||
|
|
||||||
StringFormatter::send(stream,F("HTTP/1.1 200 OK\nContent-Type: text/html\nConnnection: close\n\n"));
|
|
||||||
StringFormatter::send(stream,F("<html><body>This is my HTTP filter responding.<br/></body></html>"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback functions are necessary if you call any API that must wait for a response from the
|
|
||||||
// programming track. The API must return immediately otherwise other loop() functions would be blocked.
|
|
||||||
// Your callback function will be invoked when the data arrives from the prog track.
|
|
||||||
// See the DCC:getLocoId example in the setup function.
|
|
||||||
|
|
||||||
|
|
||||||
void myCallback(int result) {
|
|
||||||
DIAG(F("\n getting Loco Id callback result=%d"),result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Create a serial command parser... This is OPTIONAL if you don't need to handle JMRI type commands
|
|
||||||
// from the Serial port.
|
|
||||||
DCCEXParser serialParser;
|
|
||||||
|
|
||||||
|
|
||||||
// Try monitoring the memory
|
|
||||||
#include "freeMemory.h"
|
|
||||||
int ramLowWatermark=32767; // This figure gets overwritten dynamically in loop()
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
|
|
||||||
// The main sketch has responsibilities during setup()
|
|
||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics and possible JMRI input
|
|
||||||
// DIAGSERAL is normally Serial but uses SerialUSB on a SAMD processor
|
|
||||||
DIAGSERIAL.begin(115200);
|
|
||||||
while(!DIAGSERIAL);
|
|
||||||
|
|
||||||
// Responsibility 2: Start the DCC engine.
|
|
||||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
|
||||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
|
||||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
|
||||||
|
|
||||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
|
||||||
|
|
||||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
|
||||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
|
||||||
|
|
||||||
DCC::begin(STANDARD_MOTOR_SHIELD);
|
|
||||||
|
|
||||||
// Responsibility 3: **Optionally** Start the WiFi interface if required.
|
|
||||||
// NOTE: On a Uno you will have to provide a SoftwareSerial
|
|
||||||
// configured for the pins connected to the Wifi card
|
|
||||||
// and a 9600 baud rate.
|
|
||||||
// setup(serial, F(router name) or NULL, F(router password), F(hostname), F(AcessPoint name) or NULL , port)
|
|
||||||
// (port 3532 is 0xDCC decimal.)
|
|
||||||
|
|
||||||
|
|
||||||
Serial1.begin(WIFI_BAUD);
|
|
||||||
WifiInterface::setup(Serial1, F("BTHub5-M6PT"), F("49de8d4862"),F("DCCEX"),3532);
|
|
||||||
|
|
||||||
// Optionally tell the Wifi parser to use my http filter.
|
|
||||||
// This will intercept http commands from Wifi.
|
|
||||||
WifiInterface::setHTTPCallback(myHttpFilter);
|
|
||||||
|
|
||||||
// This is just for demonstration purposes
|
|
||||||
DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n"));
|
|
||||||
DCC::getLocoId(myCallback); // myCallback will be called with the result
|
|
||||||
DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n"));
|
|
||||||
|
|
||||||
// Optionally tell the command parser to use my example filter.
|
|
||||||
// This will intercept JMRI commands from both USB and Wifi
|
|
||||||
DCCEXParser::setFilter(myComandFilter);
|
|
||||||
|
|
||||||
|
|
||||||
DIAG(F("\nReady for JMRI commands\n"));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// The main sketch has responsibilities during loop()
|
|
||||||
|
|
||||||
// Responsibility 1: Handle DCC background processes
|
|
||||||
// (loco reminders and power checks)
|
|
||||||
DCC::loop();
|
|
||||||
|
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
|
||||||
serialParser.loop(DIAGSERIAL);
|
|
||||||
|
|
||||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
|
||||||
WifiInterface::loop();
|
|
||||||
|
|
||||||
// Your additional loop code
|
|
||||||
|
|
||||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
|
||||||
int freeNow=freeMemory();
|
|
||||||
if (freeNow<ramLowWatermark) {
|
|
||||||
ramLowWatermark=freeNow;
|
|
||||||
DIAG(F("\nFree RAM=%d\n"),ramLowWatermark);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
|
||||||
*
|
|
||||||
* This is a basic, no frills DCC-EX example of a DCC++ compatible setup.
|
|
||||||
* There are more advanced examples in the examples folder i
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "DCCEX.h"
|
|
||||||
|
|
||||||
// Create parser for <> commands coming from keyboard or JMRI on thr USB connection.
|
|
||||||
DCCEXParser serialParser;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics and possible JMRI input
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// Responsibility 2: Start the DCC engine with information about your Motor Shield.
|
|
||||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorDriverss.h
|
|
||||||
DCC::begin(STANDARD_MOTOR_SHIELD);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// Responsibility 1: Handle DCC background processes (loco reminders and power checks)
|
|
||||||
DCC::loop();
|
|
||||||
|
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
|
||||||
serialParser.loop(Serial);
|
|
||||||
}
|
|
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* © 2020, Chris Harlow. All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is a demonstattion of setting up a DCC-EX
|
|
||||||
* Command station to support direct connection of WiThrottle devices
|
|
||||||
* such as "Engine Driver". If you contriol your layout through JMRI
|
|
||||||
* then DON'T connect throttles to this wifi, connect them to JMRI.
|
|
||||||
*
|
|
||||||
* This is just 3 statements longer than the basic setup.
|
|
||||||
*
|
|
||||||
* THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT.
|
|
||||||
* REFER TO SEPARATE EXAMPLE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "DCCEX.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create a serial command parser... Enables certain diagnostics and commands
|
|
||||||
// to be issued from the USB serial console
|
|
||||||
// This is NOT intended for JMRI....
|
|
||||||
|
|
||||||
DCCEXParser serialParser;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
|
|
||||||
// The main sketch has responsibilities during setup()
|
|
||||||
|
|
||||||
// Responsibility 1: Start the usb connection for diagnostics
|
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
|
||||||
|
|
||||||
Serial.begin(115200);
|
|
||||||
|
|
||||||
// Responsibility 3: Start the DCC engine.
|
|
||||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
|
||||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
|
||||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
|
||||||
|
|
||||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
|
||||||
|
|
||||||
// Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the
|
|
||||||
// waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2
|
|
||||||
|
|
||||||
DCC::begin(STANDARD_MOTOR_SHIELD);
|
|
||||||
|
|
||||||
// Start the WiFi interface.
|
|
||||||
// NOTE: References to Serial1 are for the serial port used to connect
|
|
||||||
// your wifi chip/shield.
|
|
||||||
|
|
||||||
|
|
||||||
Serial1.begin(115200); // BAUD rate of your Wifi chip/shield
|
|
||||||
WifiInterface::setup(Serial1,
|
|
||||||
F("BTHub5-M6PT"), // Router name
|
|
||||||
F("49de8d4862"), // Router password
|
|
||||||
F("DCCEX"), // Hostname (ignored by some wifi chip firmware)
|
|
||||||
3532); // port (3532 is 0xDCC)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
// The main sketch has responsibilities during loop()
|
|
||||||
|
|
||||||
// Responsibility 1: Handle DCC background processes
|
|
||||||
// (loco reminders and power checks)
|
|
||||||
DCC::loop();
|
|
||||||
|
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
|
||||||
serialParser.loop(Serial);
|
|
||||||
|
|
||||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
|
||||||
WifiInterface::loop();
|
|
||||||
|
|
||||||
}
|
|
@@ -10,5 +10,7 @@ ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
|||||||
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||||
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||||
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt
|
||||||
|
ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt
|
||||||
|
avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt
|
||||||
notepad %TMP%\OBJDUMP_%a%.txt
|
notepad %TMP%\OBJDUMP_%a%.txt
|
||||||
EXIT
|
EXIT
|
||||||
|
@@ -68,6 +68,7 @@ lib_deps =
|
|||||||
marcoschwartz/LiquidCrystal_I2C
|
marcoschwartz/LiquidCrystal_I2C
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_flags = --echo
|
monitor_flags = --echo
|
||||||
|
build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g
|
||||||
|
|
||||||
[env:uno]
|
[env:uno]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
|
@@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
// const char VERSION[] PROGMEM ="0.2.0";
|
#define VERSION "3.0.4"
|
||||||
#define VERSION "3.0.3"
|
// 3.0.4 Includes:
|
||||||
|
// Wifi startup bugfixes
|
||||||
// 3.0.3 Includes:
|
// 3.0.3 Includes:
|
||||||
// <W addr> command to write loco address and clear consist
|
// <W addr> command to write loco address and clear consist
|
||||||
// <R> command will allow for consist address
|
// <R> command will allow for consist address
|
||||||
|
Reference in New Issue
Block a user