mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-28 18:03:45 +02:00
Compare commits
74 Commits
v5.5.37-De
...
nanoEvery2
Author | SHA1 | Date | |
---|---|---|---|
|
39cc9aecfc | ||
|
803c581825 | ||
|
d377468006 | ||
|
37fd5fe893 | ||
|
edeaad4a9d | ||
|
5edac75a9e | ||
|
947ef1d146 | ||
|
9368a69e30 | ||
|
8240a24193 | ||
|
d4be9a8a6c | ||
|
5a9d5d1f96 | ||
|
cae30f5faf | ||
|
919b975adc | ||
|
7e37580466 | ||
|
79d991db00 | ||
|
7d72624232 | ||
|
a8bd530451 | ||
|
5c5a2e924c | ||
|
46e0fcdc54 | ||
|
ba99de17bf | ||
|
75ab2ab533 | ||
|
9d5f579847 | ||
|
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 |
34
.github/workflows/sha.yml
vendored
Normal file
34
.github/workflows/sha.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
if: github.ref == 'refs/heads/master'
|
||||
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
|
||||
.pio/
|
||||
.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
|
||||
// Command station with optional support for 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 file is the main sketch for the Command Station.
|
||||
//
|
||||
// CONFIGURATION:
|
||||
// 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"
|
||||
|
||||
// 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
|
||||
|
||||
#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), IP_PORT, WIFI_CHANNEL);
|
||||
#endif // WIFI_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
|
||||
|
||||
// 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);
|
||||
|
||||
#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
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DIAG.h"
|
||||
#include "EEStore.h"
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "FSH.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// 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_5=0x10;
|
||||
|
||||
__FlashStringHelper* DCC::shieldName=NULL;
|
||||
FSH* DCC::shieldName=NULL;
|
||||
|
||||
void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
||||
shieldName=(__FlashStringHelper*)motorShieldName;
|
||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
DIAG(F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
|
||||
// Load stuff from EEprom
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
EEStore::init();
|
||||
|
||||
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
|
||||
DCCWaveform::begin(mainDriver,progDriver);
|
||||
}
|
||||
|
||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||
@@ -228,18 +229,18 @@ void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
__FlashStringHelper* DCC::getMotorShieldName() {
|
||||
FSH* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
|
||||
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
|
||||
const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
W0,WACK,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp PROGMEM WRITE_BIT1_PROG[] = {
|
||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
W1,WACK,
|
||||
V1, WACK, // validate bit is 1
|
||||
@@ -247,7 +248,7 @@ const ackOp PROGMEM WRITE_BIT1_PROG[] = {
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
|
||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC0, // if acked, callback(0)
|
||||
@@ -255,7 +256,7 @@ const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
|
||||
ITC1,
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
|
||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
@@ -264,7 +265,7 @@ const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp PROGMEM READ_BIT_PROG[] = {
|
||||
const ackOp FLASH READ_BIT_PROG[] = {
|
||||
BASELINE,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
@@ -273,7 +274,7 @@ const ackOp PROGMEM READ_BIT_PROG[] = {
|
||||
FAIL // bit not readable
|
||||
};
|
||||
|
||||
const ackOp PROGMEM WRITE_BYTE_PROG[] = {
|
||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
BASELINE,
|
||||
WB,WACK, // Write
|
||||
VB,WACK, // validate byte
|
||||
@@ -281,7 +282,7 @@ const ackOp PROGMEM WRITE_BYTE_PROG[] = {
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
BASELINE,
|
||||
VB,WACK, // validate byte
|
||||
ITCB, // if ok callback value
|
||||
@@ -306,7 +307,7 @@ const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
|
||||
FAIL };
|
||||
|
||||
|
||||
const ackOp PROGMEM READ_CV_PROG[] = {
|
||||
const ackOp FLASH READ_CV_PROG[] = {
|
||||
BASELINE,
|
||||
STARTMERGE, //clear bit and byte values ready for merge pass
|
||||
// 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
|
||||
|
||||
|
||||
const ackOp PROGMEM LOCO_ID_PROG[] = {
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV, (ackOp)1,
|
||||
SETBIT, (ackOp)7,
|
||||
@@ -399,7 +400,7 @@ const ackOp PROGMEM LOCO_ID_PROG[] = {
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTE, (ackOp)0,
|
||||
@@ -415,7 +416,7 @@ const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = {
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp PROGMEM LONG_LOCO_ID_PROG[] = {
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
// Clear consist CV 19
|
||||
SETCV,(ackOp)19,
|
||||
@@ -659,7 +660,7 @@ bool DCC::checkResets(bool blocking, uint8_t numResets) {
|
||||
|
||||
void DCC::ackManagerLoop(bool blocking) {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=pgm_read_byte_near(ackManagerProg);
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
@@ -786,17 +787,17 @@ void DCC::ackManagerLoop(bool blocking) {
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=pgm_read_byte_near(ackManagerProg);
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=pgm_read_byte_near(ackManagerProg);
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=pgm_read_byte_near(ackManagerProg);
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
@@ -822,9 +823,7 @@ void DCC::ackManagerLoop(bool blocking) {
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=pgm_read_byte_near(ackManagerProg);
|
||||
// Jump over second byte of any 2-byte opcodes.
|
||||
if (opcode==SETBIT || opcode==SETBYTE || opcode==SETCV) ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
|
12
DCC.h
12
DCC.h
@@ -21,10 +21,10 @@
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "MotorDrivers.h"
|
||||
|
||||
#include "FSH.h"
|
||||
typedef void (*ACK_CALLBACK)(int result);
|
||||
|
||||
enum ackOp
|
||||
enum ackOp : byte
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
@@ -64,7 +64,7 @@ const byte MAX_LOCOS = 50;
|
||||
class DCC
|
||||
{
|
||||
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();
|
||||
|
||||
// Public DCC API functions
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print *stream);
|
||||
|
||||
static __FlashStringHelper *getMotorShieldName();
|
||||
static FSH *getMotorShieldName();
|
||||
|
||||
private:
|
||||
struct LOCO
|
||||
@@ -115,7 +115,7 @@ private:
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||
static bool issueReminder(int reg);
|
||||
static int nextLoco;
|
||||
static __FlashStringHelper *shieldName;
|
||||
static FSH *shieldName;
|
||||
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static byte cv1(byte opcode, int cv);
|
||||
@@ -162,6 +162,8 @@ private:
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#endif
|
||||
|
@@ -52,7 +52,7 @@ const int HASH_KEYWORD_ETHERNET = -30767;
|
||||
const int HASH_KEYWORD_MAX = 16244;
|
||||
const int HASH_KEYWORD_MIN = 15978;
|
||||
|
||||
int DCCEXParser::stashP[MAX_PARAMS];
|
||||
int DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
|
||||
Print *DCCEXParser::stashStream = NULL;
|
||||
@@ -102,7 +102,7 @@ void DCCEXParser::loop(Stream &stream)
|
||||
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 parameterCount = 0;
|
||||
@@ -111,10 +111,10 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
||||
bool signNegative = false;
|
||||
|
||||
// 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;
|
||||
|
||||
while (parameterCount < MAX_PARAMS)
|
||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||
{
|
||||
byte hot = *remainingCmd;
|
||||
|
||||
@@ -143,6 +143,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
||||
runningValue = 10 * runningValue + (hot - '0');
|
||||
break;
|
||||
}
|
||||
if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z
|
||||
if (hot >= 'A' && hot <= 'Z')
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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 parameterCount = 0;
|
||||
@@ -168,10 +169,10 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd)
|
||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||
|
||||
// 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;
|
||||
|
||||
while (parameterCount < MAX_PARAMS)
|
||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||
{
|
||||
byte hot = *remainingCmd;
|
||||
|
||||
@@ -237,7 +238,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
||||
}
|
||||
|
||||
// Parse an F() string
|
||||
void DCCEXParser::parse(const __FlashStringHelper * cmd) {
|
||||
void DCCEXParser::parse(const FSH * cmd) {
|
||||
int size=strlen_P((char *)cmd)+1;
|
||||
char buffer[size];
|
||||
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
|
||||
if (Diag::CMD)
|
||||
DIAG(F("\nPARSING:%s\n"), com);
|
||||
int p[MAX_PARAMS];
|
||||
int p[MAX_COMMAND_PARAMS];
|
||||
while (com[0] == '<' || com[0] == ' ')
|
||||
com++; // strip off any number of < or spaces
|
||||
byte params = splitValues(p, com);
|
||||
@@ -314,12 +315,33 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
if (p[2] != (p[2] & 1))
|
||||
return;
|
||||
DCC::setAccessory(p[0], p[1], p[2] == 1);
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
|
||||
{
|
||||
int address;
|
||||
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;
|
||||
|
||||
|
||||
case 'T': // TURNOUT <T ...>
|
||||
if (parseT(stream, params, p))
|
||||
return;
|
||||
@@ -414,7 +436,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||
{
|
||||
POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF;
|
||||
DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off
|
||||
if (params == 0)
|
||||
if (params == 0 ||
|
||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
||||
{
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
@@ -732,10 +755,6 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
||||
Diag::WITHROTTLE = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_DCC:
|
||||
DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
@@ -752,13 +771,13 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
||||
}
|
||||
|
||||
// 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 )
|
||||
return false;
|
||||
stashBusy = true;
|
||||
stashStream = stream;
|
||||
memcpy(stashP, p, MAX_PARAMS * sizeof(p[0]));
|
||||
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
|
||||
return true;
|
||||
}
|
||||
void DCCEXParser::callback_W(int result)
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#ifndef DCCEXParser_h
|
||||
#define DCCEXParser_h
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
|
||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||
@@ -28,12 +29,12 @@ struct DCCEXParser
|
||||
DCCEXParser();
|
||||
void loop(Stream & stream);
|
||||
void parse(Print * stream, byte * command, bool blocking);
|
||||
void parse(const __FlashStringHelper * cmd);
|
||||
void parse(const FSH * cmd);
|
||||
void flush();
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_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:
|
||||
|
||||
@@ -41,8 +42,8 @@ struct DCCEXParser
|
||||
byte bufferLength=0;
|
||||
bool inCommandPayload=false;
|
||||
byte buffer[MAX_BUFFER+2];
|
||||
int splitValues( int result[MAX_PARAMS], const byte * command);
|
||||
int splitHexValues( int result[MAX_PARAMS], const byte * command);
|
||||
int splitValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
int splitHexValues( int result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
|
||||
bool parseT(Print * stream, int params, int p[]);
|
||||
bool parseZ(Print * stream, int params, int p[]);
|
||||
@@ -54,8 +55,8 @@ struct DCCEXParser
|
||||
static bool stashBusy;
|
||||
|
||||
static Print * stashStream;
|
||||
static int stashP[MAX_PARAMS];
|
||||
bool stashCallback(Print * stream, int p[MAX_PARAMS]);
|
||||
static int stashP[MAX_COMMAND_PARAMS];
|
||||
bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS]);
|
||||
static void callback_W(int result);
|
||||
static void callback_B(int result);
|
||||
static void callback_R(int result);
|
||||
|
145
DCCTimer.cpp
Normal file
145
DCCTimer.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* © 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.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
}
|
||||
|
||||
#else
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
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(); }
|
||||
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return pin==TIMER1_A_PIN
|
||||
|| pin==TIMER1_B_PIN
|
||||
#ifdef TIMER1_C_PIN
|
||||
|| pin==TIMER1_C_PIN
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#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
|
16
DCCTimer.h
Normal file
16
DCCTimer.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#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]);
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
199
DCCWaveform.cpp
199
DCCWaveform.cpp
@@ -17,13 +17,13 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma GCC optimize ("-O3")
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.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::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
@@ -31,33 +31,19 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=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;
|
||||
progTrack.motorDriver=progDriver;
|
||||
|
||||
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||
mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
progTrack.setPowerMode(POWERMODE::OFF);
|
||||
switch (timerNumber) {
|
||||
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"));
|
||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||
MotorDriver::commonFaultPin = (mainDriver->getFaultPin() == progDriver->getFaultPin());
|
||||
if (MotorDriver::usePWM) DIAG(F("\nWaveform using PWM pins for accuracy."));
|
||||
else DIAG(F("\nWaveform accuracy limited by signal pin configuration."));
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
void DCCWaveform::loop() {
|
||||
@@ -65,19 +51,26 @@ void DCCWaveform::loop() {
|
||||
progTrack.checkPowerOverload();
|
||||
}
|
||||
|
||||
|
||||
// static //
|
||||
void DCCWaveform::interruptHandler() {
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
bool mainCall2 = mainTrack.interrupt1();
|
||||
bool progCall2 = progTrack.interrupt1();
|
||||
// member functions would be cleaner but have more overhead
|
||||
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 +85,12 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
|
||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
// establish appropriate pins
|
||||
isMainTrack = isMain;
|
||||
packetPending = false;
|
||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||
state = 0;
|
||||
state = WAVE_START;
|
||||
// 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;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
@@ -112,10 +104,6 @@ POWERMODE DCCWaveform::getPowerMode() {
|
||||
}
|
||||
|
||||
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;
|
||||
bool ison = (mode == POWERMODE::ON);
|
||||
motorDriver->setPower( ison);
|
||||
@@ -123,9 +111,6 @@ void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
|
||||
|
||||
void DCCWaveform::checkPowerOverload() {
|
||||
|
||||
static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static
|
||||
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||
@@ -138,8 +123,26 @@ void DCCWaveform::checkPowerOverload() {
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent = motorDriver->getCurrentRaw();
|
||||
if (lastCurrent <= tripValue) {
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (MotorDriver::commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
setPowerMode(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("\n*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***\n"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||
} else {
|
||||
DIAG(F("\n*** %S FAULT PIN ACTIVE - OVERLOAD ***\n"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
@@ -149,9 +152,9 @@ void DCCWaveform::checkPowerOverload() {
|
||||
setPowerMode(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
||||
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;
|
||||
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)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
@@ -162,77 +165,45 @@ void DCCWaveform::checkPowerOverload() {
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPowerMode(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("\n*** %S TRACK POWER RESET delay=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay);
|
||||
break;
|
||||
default:
|
||||
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};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// process time-edge sensitive part of interrupt
|
||||
// return true if second level required
|
||||
bool DCCWaveform::interrupt1() {
|
||||
// NOTE: this must consume transmission buffers even if the power is off
|
||||
// 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);
|
||||
}
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool DCCWaveform::signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
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 ) {
|
||||
currentBit = true;
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
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
|
||||
currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
|
||||
state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0;
|
||||
bits_sent++;
|
||||
|
||||
// If this is the last bit of a byte, prepare for the next byte
|
||||
@@ -252,7 +223,10 @@ void DCCWaveform::interrupt2() {
|
||||
}
|
||||
else if (packetPending) {
|
||||
// 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;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
@@ -277,7 +251,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
while (packetPending);
|
||||
|
||||
byte checksum = 0;
|
||||
for (int b = 0; b < byteCount; b++) {
|
||||
for (byte b = 0; b < byteCount; b++) {
|
||||
checksum ^= buffer[b];
|
||||
pendingPacket[b] = buffer[b];
|
||||
}
|
||||
@@ -288,16 +262,12 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
int DCCWaveform::getLastCurrent() {
|
||||
return lastCurrent;
|
||||
}
|
||||
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
|
||||
void DCCWaveform::setAckBaseline() {
|
||||
if (isMainTrack) return;
|
||||
int baseline = motorDriver->getCurrentRaw();
|
||||
int baseline=motorDriver->getCurrentRaw();
|
||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
|
||||
baseline,motorDriver->raw2mA(baseline),
|
||||
@@ -325,18 +295,17 @@ byte DCCWaveform::getAck() {
|
||||
|
||||
void DCCWaveform::checkAck() {
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// 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
|
||||
return;
|
||||
}
|
||||
|
@@ -20,7 +20,6 @@
|
||||
#ifndef DCCWaveform_h
|
||||
#define DCCWaveform_h
|
||||
#include "MotorDriver.h"
|
||||
#include "ArduinoTimers.h"
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
@@ -30,15 +29,18 @@ const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
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
|
||||
// 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 resetPacket[] = {0x00, 0x00, 0x00};
|
||||
@@ -46,8 +48,7 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
class DCCWaveform {
|
||||
public:
|
||||
DCCWaveform( byte preambleBits, bool isMain);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
|
||||
static void setDiagnosticSlowWave(bool slow);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||
static void loop();
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
@@ -56,7 +57,6 @@ class DCCWaveform {
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
void checkPowerOverload();
|
||||
int getLastCurrent();
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||
@@ -105,12 +105,16 @@ class DCCWaveform {
|
||||
}
|
||||
|
||||
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();
|
||||
bool interrupt1();
|
||||
void interrupt2();
|
||||
void checkAck();
|
||||
void setSignal(bool high);
|
||||
|
||||
bool isMainTrack;
|
||||
MotorDriver* motorDriver;
|
||||
@@ -120,15 +124,14 @@ class DCCWaveform {
|
||||
byte transmitRepeats; // remaining repeats of transmission
|
||||
byte remainingPreambles;
|
||||
byte requiredPreambles;
|
||||
bool currentBit; // bit to be transmitted
|
||||
byte bits_sent; // 0-8 (yes 9 bits) sent for current byte
|
||||
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 pendingLength;
|
||||
byte pendingRepeats;
|
||||
int lastCurrent;
|
||||
int lastCurrent;
|
||||
static int progTripValue;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
|
1
DIAG.h
1
DIAG.h
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
#ifndef DIAG_h
|
||||
#define DIAG_h
|
||||
|
||||
#include "StringFormatter.h"
|
||||
#define DIAG StringFormatter::diag
|
||||
#define LCD StringFormatter::lcd
|
||||
|
@@ -17,13 +17,18 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "defines.h" // This should be changed to DCCEX.h when possible
|
||||
#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 "defines.h"
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#include "DIAG.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
/**
|
||||
@@ -45,10 +50,15 @@ void EthernetInterface::setup()
|
||||
*/
|
||||
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
|
||||
Ethernet.begin(mac, IP_ADDRESS);
|
||||
@@ -73,11 +83,11 @@ EthernetInterface::EthernetInterface()
|
||||
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
server = new EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), LISTEN_PORT);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
|
@@ -22,9 +22,13 @@
|
||||
|
||||
#ifndef 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 "MemStream.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <Ethernet.h>
|
||||
@@ -34,11 +38,7 @@
|
||||
* @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 MAX_ETH_BUFFER 512
|
||||
#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 "edeaad4"
|
||||
|
@@ -27,7 +27,6 @@
|
||||
|
||||
#ifndef LCD_Implementation_h
|
||||
#define LCD_Implementation_h
|
||||
#include "config.h"
|
||||
#include <Wire.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
|
123
MotorDriver.cpp
123
MotorDriver.cpp
@@ -18,50 +18,73 @@
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "AnalogReadFast.h"
|
||||
#include "DCCTimer.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
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
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) {
|
||||
powerPin=power_pin;
|
||||
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||
pinMode(powerPin, OUTPUT);
|
||||
|
||||
signalPin=signal_pin;
|
||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||
pinMode(signalPin, OUTPUT);
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
dualSignal=true;
|
||||
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
||||
pinMode(signalPin2, OUTPUT);
|
||||
}
|
||||
else dualSignal=false;
|
||||
|
||||
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;
|
||||
senseFactor=sense_factor;
|
||||
pinMode(currentPin, INPUT);
|
||||
|
||||
faultPin=fault_pin;
|
||||
if (faultPin != UNUSED_PIN) {
|
||||
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
|
||||
pinMode(faultPin, INPUT);
|
||||
}
|
||||
|
||||
senseFactor=sense_factor;
|
||||
tripMilliamps=trip_milliamps;
|
||||
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);
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
|
||||
void MotorDriver::setPower(bool on) {
|
||||
if (brakePin == -4 && on) {
|
||||
if (on) {
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
setBrake(true);
|
||||
setBrake(false);
|
||||
setHIGH(fastPowerPin);
|
||||
}
|
||||
WritePin(powerPin, on ? HIGH : LOW);
|
||||
else setLOW(fastPowerPin);
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
@@ -73,30 +96,40 @@ void MotorDriver::setPower(bool on) {
|
||||
// compensate for that.
|
||||
//
|
||||
void MotorDriver::setBrake(bool on) {
|
||||
bool state = on;
|
||||
byte pin = brakePin;
|
||||
if (brakePin < 0) {
|
||||
pin=-pin;
|
||||
state=!state;
|
||||
}
|
||||
WritePin(pin, state ? HIGH : LOW);
|
||||
//DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin));
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (on ^ invertBrake) setHIGH(fastBrakePin);
|
||||
else setLOW(fastBrakePin);
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
WritePin(signalPin, high ? HIGH : LOW);
|
||||
if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH);
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return the current reading as pin reading 0 to 1023. If the fault
|
||||
* pin is activated return a negative current to show active fault pin.
|
||||
* As there is no -0, ceat a little and return -1 in that case.
|
||||
*/
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH)
|
||||
return (int)(32000/senseFactor);
|
||||
|
||||
int current = analogRead(currentPin);
|
||||
if (faultPin != UNUSED_PIN && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
return current;
|
||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so analogReadFast is used here. (-2uS)
|
||||
return analogReadFast(currentPin);
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
@@ -105,3 +138,15 @@ unsigned int MotorDriver::raw2mA( int raw) {
|
||||
int MotorDriver::mA2raw( unsigned int mA) {
|
||||
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
|
||||
#define MotorDriver_h
|
||||
#include "FSH.h"
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
struct FASTPIN {
|
||||
volatile uint8_t *inout;
|
||||
uint8_t maskHIGH;
|
||||
uint8_t maskLOW;
|
||||
};
|
||||
|
||||
class MotorDriver {
|
||||
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);
|
||||
@@ -34,12 +42,24 @@ class MotorDriver {
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
|
||||
bool isPWMCapable();
|
||||
static bool usePWM;
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
|
||||
private:
|
||||
byte powerPin, signalPin, signalPin2, currentPin, faultPin;
|
||||
int8_t brakePin; // negative means pin is inverted
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
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;
|
||||
unsigned int tripMilliamps;
|
||||
int rawCurrentTripValue;
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#ifndef MotorDrivers_h
|
||||
#define MotorDrivers_h
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
// *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM.
|
||||
// It will be overwritten if the library is updated.
|
||||
@@ -31,8 +29,8 @@
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
|
||||
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, UNUSED_PIN)
|
||||
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
|
||||
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, 12)
|
||||
//
|
||||
// Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the
|
||||
// pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually
|
||||
@@ -40,8 +38,8 @@
|
||||
// version of the code always will be high. That means this config is not usable for generating
|
||||
// a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define
|
||||
// the motor driver like this:
|
||||
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
|
||||
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, UNUSED_PIN)
|
||||
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
|
||||
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12)
|
||||
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
||||
|
||||
// Firebox Mk1
|
||||
|
@@ -22,13 +22,9 @@
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
|
||||
#elif defined(ARDUINO_ARCH_AVR)
|
||||
Print * StringFormatter::diagSerial= &Serial;
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
#else
|
||||
Print * StringFormatter::diagSerial=&Serial;
|
||||
#define __FlashStringHelper char
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
@@ -40,14 +36,14 @@ bool Diag::WITHROTTLE=false;
|
||||
bool Diag::ETHERNET=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const __FlashStringHelper* input...) {
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
if (!diagSerial) return;
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(diagSerial,input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
|
||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
va_list args;
|
||||
|
||||
// Issue the LCD as a diag first
|
||||
@@ -62,25 +58,25 @@ void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
|
||||
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_start(args, input);
|
||||
send2(stream,input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) {
|
||||
void StringFormatter::send(Print & stream, const FSH* input...) {
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
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
|
||||
|
||||
char* flash=(char*)format;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=pgm_read_byte_near(flash+i);
|
||||
char c=GETFLASH(flash+i);
|
||||
if (c=='\0') return;
|
||||
if(c!='%') { stream->print(c); continue; }
|
||||
|
||||
@@ -91,14 +87,14 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va
|
||||
|
||||
formatContinues=false;
|
||||
i++;
|
||||
c=pgm_read_byte_near(flash+i);
|
||||
c=GETFLASH(flash+i);
|
||||
switch(c) {
|
||||
case '%': stream->print('%'); break;
|
||||
case 'c': stream->print((char) va_arg(args, int)); break;
|
||||
case 's': stream->print(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 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break;
|
||||
case 'E': printEscapes(stream,(const FSH*)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 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); 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;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=pgm_read_byte_near(flash+i);
|
||||
char c=GETFLASH(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
}
|
||||
|
@@ -19,12 +19,10 @@
|
||||
#ifndef StringFormatter_h
|
||||
#define StringFormatter_h
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "FSH.h"
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define __FlashStringHelper char
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
@@ -41,22 +39,22 @@ class Diag {
|
||||
class StringFormatter
|
||||
{
|
||||
public:
|
||||
static void send(Print * serial, const __FlashStringHelper* input...);
|
||||
static void send(Print & serial, const __FlashStringHelper* input...);
|
||||
static void send(Print * serial, const FSH* input...);
|
||||
static void send(Print & serial, const FSH* 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);
|
||||
|
||||
// DIAG support
|
||||
static Print * diagSerial;
|
||||
static void diag( const __FlashStringHelper* input...);
|
||||
static void lcd(byte row, const __FlashStringHelper* input...);
|
||||
static void diag( const FSH* input...);
|
||||
static void lcd(byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
static void printEscape( char c);
|
||||
|
||||
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);
|
||||
|
||||
};
|
||||
|
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
|
@@ -133,6 +133,8 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
case 'P':
|
||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||
lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later
|
||||
}
|
||||
|
@@ -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 "WifiInboundHandler.h"
|
||||
#include "RingStream.h"
|
||||
@@ -228,3 +248,5 @@ void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||
pendingCipsend=false;
|
||||
clientPendingCIPSEND=-1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -15,12 +15,12 @@ class WifiInboundHandler {
|
||||
static WifiInboundHandler * singleton;
|
||||
|
||||
|
||||
enum INBOUND_STATE {
|
||||
enum INBOUND_STATE : byte {
|
||||
INBOUND_BUSY, // keep calling in loop()
|
||||
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...
|
||||
SKIPTOEND, // skip to newline
|
||||
|
||||
|
@@ -17,7 +17,8 @@
|
||||
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
|
||||
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
||||
#include "WifiInterface.h" /* config.h included there */
|
||||
#include <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
@@ -25,11 +26,9 @@
|
||||
|
||||
#include "WifiInboundHandler.h"
|
||||
|
||||
const char PROGMEM READY_SEARCH[] = "\r\nready\r\n";
|
||||
const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n";
|
||||
const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000";
|
||||
const char PROGMEM SEND_OK_SEARCH[] = "\r\nSEND OK\r\n";
|
||||
const char PROGMEM IPD_SEARCH[] = "+IPD";
|
||||
|
||||
|
||||
|
||||
const unsigned long LOOP_TIMEOUT = 2000;
|
||||
bool WifiInterface::connected = false;
|
||||
Stream * WifiInterface::wifiStream;
|
||||
@@ -43,7 +42,7 @@ Stream * WifiInterface::wifiStream;
|
||||
//
|
||||
// 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
|
||||
#endif
|
||||
|
||||
@@ -56,10 +55,11 @@ Stream * WifiInterface::wifiStream;
|
||||
#endif
|
||||
|
||||
bool WifiInterface::setup(long serial_link_speed,
|
||||
const __FlashStringHelper *wifiESSID,
|
||||
const __FlashStringHelper *wifiPassword,
|
||||
const __FlashStringHelper *hostname,
|
||||
const int port) {
|
||||
const FSH *wifiESSID,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel) {
|
||||
|
||||
wifiSerialState wifiUp = WIFI_NOAT;
|
||||
|
||||
@@ -74,7 +74,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
|
||||
#if NUM_SERIAL > 0
|
||||
Serial1.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
@@ -82,7 +82,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -90,7 +90,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -107,8 +107,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
return connected;
|
||||
}
|
||||
|
||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
wifiSerialState wifiState;
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
@@ -117,7 +117,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
|
||||
|
||||
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
|
||||
|
||||
wifiState = setup2( SSid, password, hostname, port);
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
DIAG(F("\n++ Wifi Setup NO AT ++\n"));
|
||||
@@ -126,7 +126,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
|
||||
|
||||
if (wifiState == WIFI_CONNECTED) {
|
||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||
checkForOK(200, OK_SEARCH, true);
|
||||
checkForOK(200, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,146 +139,171 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#endif
|
||||
wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
bool ipOK = false;
|
||||
bool oldCmd = false;
|
||||
|
||||
char macAddress[17]; // mac address extraction
|
||||
|
||||
|
||||
// First check... Restarting the Arduino does not restart the ES.
|
||||
// There may alrerady be a connection with data in the pipeline.
|
||||
// If there is, just shortcut the setup and continue to read the data as normal.
|
||||
if (checkForOK(200,IPD_SEARCH, true)) {
|
||||
if (checkForOK(200,F("+IPD"), true)) {
|
||||
DIAG(F("\nPreconfigured Wifi already running with data waiting\n"));
|
||||
// loopstate=4; // carry on from correct place... or not as the case may be
|
||||
return WIFI_CONNECTED;
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
||||
if(!checkForOK(200, OK_SEARCH, true))
|
||||
if(!checkForOK(200, true))
|
||||
return WIFI_NOAT; // No AT compatible WiFi module here
|
||||
|
||||
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
|
||||
checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console
|
||||
checkForOK(2000, true); // Makes this visible on the console
|
||||
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
|
||||
checkForOK(2000, true, false); // Makes this visible on the console
|
||||
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
DIAG(F("\nDONT_TOUCH_WIFI_CONF was set: Using existing config\n"));
|
||||
#else
|
||||
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, true); // Not always OK, sometimes "no change"
|
||||
|
||||
// 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, true)) {
|
||||
oldCmd=true;
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
}
|
||||
|
||||
// If the source code looks unconfigured, check if the
|
||||
// ESP8266 is preconfigured. We check the first 13 chars
|
||||
// of the SSid.
|
||||
const char *yourNetwork = "Your network ";
|
||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || ((const char *)SSid)[0] == '\0') {
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
// typical connect time approx 7 seconds
|
||||
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 { // 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
|
||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// If the source code looks unconfigured, check if the
|
||||
// ESP8266 is preconfigured in station mode.
|
||||
// We check the first 13 chars of the SSid and the password
|
||||
|
||||
// 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, F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, 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
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
||||
}
|
||||
DIAG(F("\n**\n"));
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
} else {
|
||||
// later version supports CWJAP_CUR
|
||||
|
||||
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, true); // dont care if not supported
|
||||
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true);
|
||||
}
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
}
|
||||
|
||||
if (ipOK) {
|
||||
// But we really only have the ESSID and password correct
|
||||
// Let's check for IP
|
||||
// Let's check for IP (via DHCP)
|
||||
ipOK = false;
|
||||
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))
|
||||
if (checkForOK(5000, F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipOK) {
|
||||
// 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.
|
||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||
// StringFormatter::send(wifiStream, F("AT+RST\r\n"));
|
||||
// checkForOK(1000, 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, true) && i++<10);
|
||||
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
|
||||
// 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"
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) {
|
||||
if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) {
|
||||
// Copy 17 byte mac address
|
||||
for (int i=0; i<17;i++) {
|
||||
while(!wifiStream->available());
|
||||
macAddress[i]=wifiStream->read();
|
||||
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'};
|
||||
|
||||
if (oldCmd) {
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
checkForOK(1000, true, false); // suck up remainder of AT+CIFSR
|
||||
|
||||
i=0;
|
||||
do {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
}
|
||||
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||
if (i >= 2)
|
||||
DIAG(F("\nWarning: Setting AP SSID and password failed\n")); // but issue warning
|
||||
|
||||
int i=0;
|
||||
do {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"%s\",1,4\r\n"), 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
|
||||
} else {
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
||||
checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok
|
||||
|
||||
if (!oldCmd) {
|
||||
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
||||
checkForOK(2000, OK_SEARCH, true);
|
||||
checkForOK(2000, true);
|
||||
}
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||
checkForOK(1000, OK_SEARCH, true); // ignore result in case it already was off
|
||||
checkForOK(1000, true); // ignore result in case it already was off
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections
|
||||
if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
|
||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||
if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED;
|
||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||
if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED;
|
||||
DIAG(F("\nPORT=%d\n"),port);
|
||||
if (!checkForOK(1000, 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, true, false)) return WIFI_DISCONNECTED;
|
||||
LCD(5,F("PORT=%d\n"),port);
|
||||
|
||||
return WIFI_CONNECTED;
|
||||
}
|
||||
@@ -300,15 +325,19 @@ void WifiInterface::ATCommand(const byte * command) {
|
||||
}
|
||||
else {
|
||||
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
||||
checkForOK(10000, OK_SEARCH, true);
|
||||
checkForOK(10000, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
|
||||
bool WifiInterface::checkForOK( const unsigned int timeout, bool echo, bool escapeEcho) {
|
||||
return checkForOK(timeout,F("\r\nOK\r\n"),echo,escapeEcho);
|
||||
}
|
||||
|
||||
bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, bool echo, bool escapeEcho) {
|
||||
unsigned long startTime = millis();
|
||||
char const *locator = waitfor;
|
||||
char *locator = (char *)waitfor;
|
||||
DIAG(F("\nWifi Check: [%E]"), waitfor);
|
||||
while ( millis() - startTime < timeout) {
|
||||
while (wifiStream->available()) {
|
||||
@@ -317,10 +346,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else DIAG(F("%c"), ch);
|
||||
}
|
||||
if (ch != pgm_read_byte_near(locator)) locator = waitfor;
|
||||
if (ch == pgm_read_byte_near(locator)) {
|
||||
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||
if (ch == GETFLASH(locator)) {
|
||||
locator++;
|
||||
if (!pgm_read_byte_near(locator)) {
|
||||
if (!GETFLASH(locator)) {
|
||||
DIAG(F("\nFound in %dms"), millis() - startTime);
|
||||
return true;
|
||||
}
|
||||
@@ -337,3 +366,5 @@ void WifiInterface::loop() {
|
||||
WifiInboundHandler::loop();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
#ifndef WifiInterface_h
|
||||
#define WifiInterface_h
|
||||
#include "config.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
@@ -31,26 +31,23 @@ class WifiInterface
|
||||
|
||||
public:
|
||||
static bool setup(long serial_link_speed,
|
||||
const __FlashStringHelper *wifiESSID,
|
||||
const __FlashStringHelper *wifiPassword,
|
||||
const __FlashStringHelper *hostname,
|
||||
const int port = 2560);
|
||||
const FSH *wifiESSID,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
static void loop();
|
||||
static void ATCommand(const byte *command);
|
||||
|
||||
|
||||
private:
|
||||
static wifiSerialState setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
||||
const __FlashStringHelper *hostname, int port);
|
||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static wifiSerialState setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
||||
const __FlashStringHelper *hostname, int port);
|
||||
static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true);
|
||||
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
|
||||
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||
static bool connected;
|
||||
static bool closeAfter;
|
||||
static byte loopstate;
|
||||
static int datalength;
|
||||
static int connectionId;
|
||||
static unsigned long loopTimeoutStart;
|
||||
};
|
||||
#endif
|
||||
|
@@ -1,10 +1,9 @@
|
||||
/**********************************************************************
|
||||
|
||||
Config.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
config.h
|
||||
COPYRIGHT (c) 2020 Fred Decker
|
||||
|
||||
The configuration file for DCC++ EX Command Station
|
||||
The configuration file for DCC-EX Command Station
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
@@ -52,10 +51,14 @@ The configuration file for DCC++ EX Command Station
|
||||
// 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.
|
||||
//
|
||||
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
|
||||
// to connect to the previously configured network and if that fails
|
||||
// fall back to Access Point mode. The SSID of the AP will be
|
||||
// automatically set to DCCEX_*.
|
||||
// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD,
|
||||
// then the WiFi chip will first try to connect to the previously
|
||||
// configured network and if that fails fall back to Access Point mode.
|
||||
// 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).
|
||||
#define WIFI_SSID "Your network name"
|
||||
@@ -69,6 +72,9 @@ The configuration file for DCC++ EX Command Station
|
||||
// WIFI_HOSTNAME: You probably don't need to change this
|
||||
#define WIFI_HOSTNAME "dccex"
|
||||
//
|
||||
// WIFI_CHANNEL:
|
||||
#define WIFI_CHANNEL 1
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
|
||||
@@ -90,26 +96,6 @@ The configuration file for DCC++ EX Command Station
|
||||
//
|
||||
//#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 }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
@@ -21,10 +21,13 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WIFI_ON: All prereqs for running with WIFI are met
|
||||
//
|
||||
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
||||
|
||||
#if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO))
|
||||
#define WIFI_ON true
|
||||
#ifndef WIFI_CHANNEL
|
||||
#define WIFI_CHANNEL 1
|
||||
#endif
|
||||
#else
|
||||
#define WIFI_ON false
|
||||
#endif
|
||||
|
@@ -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
|
||||
ECHO ++++++++++++++++++++++++++++++++++ >>%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
|
||||
EXIT
|
||||
|
@@ -68,6 +68,7 @@ lib_deps =
|
||||
marcoschwartz/LiquidCrystal_I2C
|
||||
monitor_speed = 115200
|
||||
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]
|
||||
platform = atmelavr
|
||||
|
@@ -3,8 +3,9 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// const char VERSION[] PROGMEM ="0.2.0";
|
||||
#define VERSION "3.0.3"
|
||||
#define VERSION "3.0.4"
|
||||
// 3.0.4 Includes:
|
||||
// Wifi startup bugfixes
|
||||
// 3.0.3 Includes:
|
||||
// <W addr> command to write loco address and clear consist
|
||||
// <R> command will allow for consist address
|
||||
|
Reference in New Issue
Block a user