mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
22 Commits
v3.0.11
...
NetworkInt
Author | SHA1 | Date | |
---|---|---|---|
|
8fa6ded079 | ||
|
dda7bc7277 | ||
|
edc38169dc | ||
|
34fe1256e7 | ||
|
6d7d01b1b5 | ||
|
0eb902f169 | ||
|
b41fb4b46b | ||
|
fe274cd059 | ||
|
003cb95591 | ||
|
2e09c9965d | ||
|
0144ff7ee4 | ||
|
1b624cdfdb | ||
|
09ae1e7ed1 | ||
|
a355cc74a0 | ||
|
77f0b99f8d | ||
|
5888e21090 | ||
|
1948773684 | ||
|
9b5e6c447f | ||
|
c1e4727ee8 | ||
|
d00864cbf4 | ||
|
c9c5f6a67b | ||
|
740ba6859a |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,3 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
*.svg -text
|
||||
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Install Python Wheel
|
||||
run: pip install wheel
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install -U platformio
|
||||
run: pip install -U https://github.com/platformio/platformio/archive/v4.2.1.zip
|
||||
- name: Copy generic config over
|
||||
run: cp config.example.h config.h
|
||||
- name: Compile Command Station (AVR)
|
||||
|
34
.github/workflows/sha.yml
vendored
34
.github/workflows/sha.yml
vendored
@@ -1,34 +0,0 @@
|
||||
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
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,6 +6,4 @@ Release/*
|
||||
.gcc-flags.json
|
||||
.pio/
|
||||
.vscode/
|
||||
config.h
|
||||
.vscode/extensions.json
|
||||
mySetup.h
|
||||
config.h
|
194
ATMEGA2560/Timer.h
Normal file
194
ATMEGA2560/Timer.h
Normal file
@@ -0,0 +1,194 @@
|
||||
#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
|
208
ATMEGA328/Timer.h
Normal file
208
ATMEGA328/Timer.h
Normal file
@@ -0,0 +1,208 @@
|
||||
#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
|
131
ATMEGA4809/Timer.h
Normal file
131
ATMEGA4809/Timer.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#ifndef ATMEGA328Timer_h
|
||||
#define ATMEGA328Timer_h
|
||||
|
||||
#include "../VirtualTimer.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// We only define behavior for timer 0 (TCA0), because TCB0 is very limited in functionality.
|
||||
|
||||
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:
|
||||
timer_resolution = 65536;
|
||||
break;
|
||||
}
|
||||
this->timer_num = timer_num;
|
||||
lastMicroseconds = 0;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setPeriod(unsigned long microseconds) {
|
||||
if(microseconds == lastMicroseconds)
|
||||
return;
|
||||
lastMicroseconds = microseconds;
|
||||
const unsigned long cycles = (F_CPU / 1000000) * microseconds;
|
||||
|
||||
switch(timer_num) {
|
||||
case 0:
|
||||
if (cycles < timer_resolution) {
|
||||
clockSelectBits = 0x0;
|
||||
pwmPeriod = cycles;
|
||||
} else
|
||||
if (cycles < timer_resolution * 2) {
|
||||
clockSelectBits = 0x1;
|
||||
pwmPeriod = cycles / 8;
|
||||
} else
|
||||
if (cycles < timer_resolution * 4) {
|
||||
clockSelectBits = 0x2;
|
||||
pwmPeriod = cycles / 32;
|
||||
} else
|
||||
if (cycles < timer_resolution * 8) {
|
||||
clockSelectBits = 0x3;
|
||||
pwmPeriod = cycles / 64;
|
||||
} else
|
||||
if (cycles < timer_resolution * 64) {
|
||||
clockSelectBits = 0x5;
|
||||
pwmPeriod = cycles / 128;
|
||||
} else
|
||||
if (cycles < timer_resolution * 256) {
|
||||
clockSelectBits = 0x6;
|
||||
pwmPeriod = cycles / 256;
|
||||
} else
|
||||
if (cycles < timer_resolution * 1024) {
|
||||
clockSelectBits = 0x7;
|
||||
pwmPeriod = cycles / 1024;
|
||||
} else {
|
||||
clockSelectBits = 0x7;
|
||||
pwmPeriod = timer_resolution - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
TCA0.SINGLE.PER = pwmPeriod;
|
||||
TCA0.SINGLE.CTRLA = clockSelectBits << 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
bitSet(TCA0.SINGLE.CTRLA, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
void stop() {
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
bitClear(TCA0.SINGLE.CTRLA, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void attachInterrupt(void (*isr)()) {
|
||||
isrCallback = isr;
|
||||
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
TCA0.SINGLE.INTCTRL = 0x1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void detachInterrupt() {
|
||||
switch (timer_num)
|
||||
{
|
||||
case 0:
|
||||
TCA0.SINGLE.INTCTRL = 0x0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
extern Timer TimerA;
|
||||
|
||||
#endif
|
129
ATSAMC21G/Timer.h
Normal file
129
ATSAMC21G/Timer.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef ATSAMC21Timer_h
|
||||
#define ATSAMC21Timer_h
|
||||
|
||||
#include "../VirtualTimer.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class Timer : public VirtualTimer
|
||||
{
|
||||
private:
|
||||
int pwmPeriod;
|
||||
unsigned long timer_resolution;
|
||||
unsigned long lastMicroseconds;
|
||||
public:
|
||||
void (*isrCallback)();
|
||||
Tcc* timer;
|
||||
|
||||
Timer(Tcc* timer) {
|
||||
this->timer = timer;
|
||||
if(timer == TCC0 || timer == TCC1) {
|
||||
timer_resolution = 16777216;
|
||||
} else {
|
||||
timer_resolution = 65536;
|
||||
}
|
||||
lastMicroseconds = 0;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
if(timer == TCC0 || timer == TCC1) {
|
||||
MCLK->APBCMASK.bit.TCC0_ = 1;
|
||||
MCLK->APBCMASK.bit.TCC1_ = 1;
|
||||
GCLK->GENCTRL[4].reg = ( GCLK_GENCTRL_DIV(2) | GCLK_GENCTRL_SRC_DPLL96M | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_OE );
|
||||
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 4) & 1); // Wait for synchronization
|
||||
GCLK->PCHCTRL[28].reg = ( GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(4) ); // 28 = TCC0_TCC1
|
||||
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 4) & 1); // Wait for synchronization
|
||||
}
|
||||
else if (timer == TCC2) {
|
||||
MCLK->APBCMASK.bit.TCC2_ = 1;
|
||||
GCLK->GENCTRL[5].reg = ( GCLK_GENCTRL_DIV(2) | GCLK_GENCTRL_SRC_DPLL96M | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_OE );
|
||||
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 5) & 1); // Wait for synchronization
|
||||
GCLK->PCHCTRL[29].reg = ( GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(5) ); // 29 = TCC2
|
||||
while ((GCLK->SYNCBUSY.bit.GENCTRL >> 5) & 1); // Wait for synchronization
|
||||
}
|
||||
|
||||
timer->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
|
||||
while (timer->SYNCBUSY.bit.WAVE); // Wait for synchronization
|
||||
|
||||
}
|
||||
void setPeriod(unsigned long microseconds) {
|
||||
if(microseconds == lastMicroseconds)
|
||||
return;
|
||||
lastMicroseconds = microseconds;
|
||||
|
||||
const unsigned long cycles = F_CPU / 1000000 * microseconds; // cycles corresponds to how many clock ticks per microsecond times number of microseconds we want
|
||||
timer->CTRLA.bit.PRESCALER = 0;
|
||||
if(cycles < timer_resolution) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
|
||||
pwmPeriod = cycles;
|
||||
} else
|
||||
if(cycles < timer_resolution * 2) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV2_Val);
|
||||
pwmPeriod = cycles / 2;
|
||||
} else
|
||||
if(cycles < timer_resolution * 4) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV4_Val);
|
||||
pwmPeriod = cycles / 4;
|
||||
} else
|
||||
if(cycles < timer_resolution * 8) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV8_Val);
|
||||
pwmPeriod = cycles / 8;
|
||||
} else
|
||||
if(cycles < timer_resolution * 16) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV16_Val);
|
||||
pwmPeriod = cycles / 16;
|
||||
} else
|
||||
if(cycles < timer_resolution * 64) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV64_Val);
|
||||
pwmPeriod = cycles / 64;
|
||||
} else
|
||||
if(cycles < timer_resolution * 1024) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1024_Val);
|
||||
pwmPeriod = cycles / 1024;
|
||||
}
|
||||
timer->PER.reg = pwmPeriod;
|
||||
while (timer->SYNCBUSY.bit.PER);
|
||||
}
|
||||
void start() {
|
||||
timer->CTRLA.bit.ENABLE = 1; // Turn on the output
|
||||
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
|
||||
}
|
||||
void stop() {
|
||||
timer->CTRLA.bit.ENABLE = 0; // Turn on the output
|
||||
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
|
||||
}
|
||||
|
||||
void attachInterrupt(void (*isr)()) {
|
||||
isrCallback = isr; // Store the interrupt callback function
|
||||
timer->INTENSET.reg = TCC_INTENSET_OVF; // Set the interrupt to occur on overflow
|
||||
|
||||
if(timer == TCC0) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC0_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
else if(timer == TCC1) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC1_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
else if(timer == TCC2) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC2_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
}
|
||||
|
||||
void detachInterrupt() {
|
||||
if(timer == TCC0) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC0_IRQn); // Disable the interrupt
|
||||
}
|
||||
else if(timer == TCC1) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC1_IRQn); // Disable the interrupt
|
||||
}
|
||||
else if(timer == TCC2) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC2_IRQn); // Disable the interrupt
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern Timer TimerA;
|
||||
extern Timer TimerB;
|
||||
extern Timer TimerC;
|
||||
|
||||
|
||||
|
||||
#endif // ATSAMC21Timer_h
|
144
ATSAMD21G/Timer.h
Normal file
144
ATSAMD21G/Timer.h
Normal file
@@ -0,0 +1,144 @@
|
||||
#ifndef ATSAMD21GTimer_h
|
||||
#define ATSAMD21GTimer_h
|
||||
|
||||
#include "../VirtualTimer.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class Timer : public VirtualTimer
|
||||
{
|
||||
private:
|
||||
int pwmPeriod;
|
||||
unsigned long timer_resolution;
|
||||
unsigned long lastMicroseconds;
|
||||
public:
|
||||
void (*isrCallback)();
|
||||
Tcc* timer;
|
||||
|
||||
Timer(Tcc* timer) {
|
||||
this->timer = timer;
|
||||
if(timer == TCC0 || timer == TCC1) {
|
||||
timer_resolution = 16777216;
|
||||
} else {
|
||||
timer_resolution = 65536;
|
||||
}
|
||||
lastMicroseconds = 0;
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
if(timer == TCC0 || timer == TCC1) {
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
|
||||
GCLK_GENDIV_ID(4); // Apply to GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
|
||||
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
|
||||
GCLK_GENCTRL_ID(4); // Select GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
|
||||
4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
|
||||
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
}
|
||||
else if (timer == TCC2) {
|
||||
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1
|
||||
GCLK_GENDIV_ID(5); // Apply to GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK
|
||||
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
|
||||
GCLK_GENCTRL_ID(5); // Select GCLK4
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
|
||||
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock
|
||||
5 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4
|
||||
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed GCLK to TCC0/1
|
||||
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
|
||||
}
|
||||
|
||||
|
||||
timer->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
|
||||
while (timer->SYNCBUSY.bit.WAVE); // Wait for synchronization
|
||||
|
||||
}
|
||||
void setPeriod(unsigned long microseconds) {
|
||||
if(microseconds == lastMicroseconds)
|
||||
return;
|
||||
lastMicroseconds = microseconds;
|
||||
|
||||
const unsigned long cycles = F_CPU / 1000000 * microseconds; // cycles corresponds to how many clock ticks per microsecond times number of microseconds we want
|
||||
if(cycles < timer_resolution) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
|
||||
pwmPeriod = cycles;
|
||||
} else
|
||||
if(cycles < timer_resolution * 2) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV2_Val);
|
||||
pwmPeriod = cycles / 2;
|
||||
} else
|
||||
if(cycles < timer_resolution * 4) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV4_Val);
|
||||
pwmPeriod = cycles / 4;
|
||||
} else
|
||||
if(cycles < timer_resolution * 8) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV8_Val);
|
||||
pwmPeriod = cycles / 8;
|
||||
} else
|
||||
if(cycles < timer_resolution * 16) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV16_Val);
|
||||
pwmPeriod = cycles / 16;
|
||||
} else
|
||||
if(cycles < timer_resolution * 64) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV64_Val);
|
||||
pwmPeriod = cycles / 64;
|
||||
} else
|
||||
if(cycles < timer_resolution * 1024) {
|
||||
timer->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1024_Val);
|
||||
pwmPeriod = cycles / 1024;
|
||||
}
|
||||
timer->PER.reg = pwmPeriod;
|
||||
while (timer->SYNCBUSY.bit.PER);
|
||||
}
|
||||
void start() {
|
||||
timer->CTRLA.bit.ENABLE = 1; // Turn on the output
|
||||
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
|
||||
}
|
||||
void stop() {
|
||||
timer->CTRLA.bit.ENABLE = 0; // Turn on the output
|
||||
while (timer->SYNCBUSY.bit.ENABLE); // Wait for synchronization
|
||||
}
|
||||
|
||||
void attachInterrupt(void (*isr)()) {
|
||||
isrCallback = isr; // Store the interrupt callback function
|
||||
timer->INTENSET.reg = TCC_INTENSET_OVF; // Set the interrupt to occur on overflow
|
||||
|
||||
if(timer == TCC0) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC0_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
else if(timer == TCC1) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC1_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
else if(timer == TCC2) {
|
||||
NVIC_EnableIRQ((IRQn_Type) TCC2_IRQn); // Enable the interrupt (clock is still off)
|
||||
}
|
||||
}
|
||||
|
||||
void detachInterrupt() {
|
||||
if(timer == TCC0) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC0_IRQn); // Disable the interrupt
|
||||
}
|
||||
else if(timer == TCC1) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC1_IRQn); // Disable the interrupt
|
||||
}
|
||||
else if(timer == TCC2) {
|
||||
NVIC_DisableIRQ((IRQn_Type) TCC2_IRQn); // Disable the interrupt
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern Timer TimerA;
|
||||
extern Timer TimerB;
|
||||
extern Timer TimerC;
|
||||
|
||||
|
||||
|
||||
#endif
|
112
AnalogReadFast.h
Normal file
112
AnalogReadFast.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
int inline analogReadFast(uint8_t ADCpin)
|
||||
{ ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
int CTRLBoriginal = ADC->CTRLB.reg;
|
||||
int AVGCTRLoriginal = ADC->AVGCTRL.reg;
|
||||
int SAMPCTRLoriginal = ADC->SAMPCTRL.reg;
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64; // divide Clock by 64
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
|
||||
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization
|
||||
|
||||
int adc = analogRead(ADCpin);
|
||||
|
||||
ADC->CTRLB.reg = CTRLBoriginal;
|
||||
ADC->AVGCTRL.reg = AVGCTRLoriginal;
|
||||
ADC->SAMPCTRL.reg = SAMPCTRLoriginal;
|
||||
|
||||
return adc;
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO_ARCH_SAMC)
|
||||
|
||||
int inline analogReadFast(uint8_t ADCpin)
|
||||
{
|
||||
Adc* ADC;
|
||||
if ( (g_APinDescription[ADCpin].ulPeripheralAttribute & PER_ATTR_ADC_MASK) == PER_ATTR_ADC_STD ) {
|
||||
ADC = ADC0;
|
||||
} else {
|
||||
ADC = ADC1;
|
||||
}
|
||||
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->SYNCBUSY.bit.ENABLE == 1 ); // wait for synchronization
|
||||
|
||||
int CTRLBoriginal = ADC->CTRLB.reg;
|
||||
int AVGCTRLoriginal = ADC->AVGCTRL.reg;
|
||||
int SAMPCTRLoriginal = ADC->SAMPCTRL.reg;
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64; // divide Clock by 64
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0
|
||||
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while(ADC->SYNCBUSY.bit.ENABLE == 1); // wait for synchronization
|
||||
|
||||
int adc = analogRead(ADCpin);
|
||||
|
||||
ADC->CTRLB.reg = CTRLBoriginal;
|
||||
ADC->AVGCTRL.reg = AVGCTRLoriginal;
|
||||
ADC->SAMPCTRL.reg = SAMPCTRLoriginal;
|
||||
|
||||
return adc;
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_AVR_NANO_EVERY)
|
||||
|
||||
int inline analogReadFast(uint8_t ADCpin)
|
||||
{ byte ADC0CTRLCoriginal = ADC0.CTRLC;
|
||||
ADC0.CTRLC = (ADC0CTRLCoriginal & 0b00110000) + 0b01000011;
|
||||
int adc = analogRead(ADCpin);
|
||||
ADC0.CTRLC = ADC0CTRLCoriginal;
|
||||
return adc;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int inline analogReadFast(uint8_t ADCpin)
|
||||
{ byte ADCSRAoriginal = ADCSRA;
|
||||
ADCSRA = (ADCSRA & B11111000) | 4;
|
||||
int adc = analogRead(ADCpin);
|
||||
ADCSRA = ADCSRAoriginal;
|
||||
return adc;
|
||||
}
|
||||
#endif
|
||||
#endif // COMMANDSTATION_DCC_ANALOGREADFAST_H_
|
24
ArduinoTimers.h
Normal file
24
ArduinoTimers.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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(SAMC21)
|
||||
#include "ATSAMC21G/Timer.h"
|
||||
#elif defined(ARDUINO_SAMD_ZERO)
|
||||
#include "ATSAMD21G/Timer.h"
|
||||
#elif defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#include "ATMEGA2560/Timer.h"
|
||||
#elif defined(ARDUINO_AVR_UNO)
|
||||
#include "ATMEGA328/Timer.h"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#include "ATMEGA4809/Timer.h"
|
||||
#else
|
||||
#error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags."
|
||||
#endif
|
||||
|
||||
#endif
|
121
ArduinoUniqueID.cpp
Normal file
121
ArduinoUniqueID.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues, Luiz Henrique Cassettari. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
#include "ArduinoUniqueID.h"
|
||||
|
||||
ArduinoUniqueID::ArduinoUniqueID()
|
||||
{
|
||||
#if defined(ARDUINO_ARCH_AVR)
|
||||
for (size_t i = 0; i < UniqueIDsize; i++)
|
||||
{
|
||||
id[i] = boot_signature_byte_get(0x0E + i + (UniqueIDsize == 9 && i > 5 ? 1 : 0));
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
uint32_t chipid = ESP.getChipId();
|
||||
id[0] = 0;
|
||||
id[1] = 0;
|
||||
id[2] = 0;
|
||||
id[3] = 0;
|
||||
id[4] = chipid >> 24;
|
||||
id[5] = chipid >> 16;
|
||||
id[6] = chipid >> 8;
|
||||
id[7] = chipid;
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
uint64_t chipid = ESP.getEfuseMac();
|
||||
id[0] = 0;
|
||||
id[1] = 0;
|
||||
id[2] = chipid;
|
||||
id[3] = chipid >> 8;
|
||||
id[4] = chipid >> 16;
|
||||
id[5] = chipid >> 24;
|
||||
id[6] = chipid >> 32;
|
||||
id[7] = chipid >> 40;
|
||||
|
||||
#elif defined(ARDUINO_ARCH_SAM)
|
||||
unsigned int status ;
|
||||
/* Send the Start Read unique Identifier command (STUI) by writing the Flash Command Register with the STUI command.*/
|
||||
EFC1->EEFC_FCR = (0x5A << 24) | EFC_FCMD_STUI;
|
||||
do
|
||||
{
|
||||
status = EFC1->EEFC_FSR ;
|
||||
} while ( (status & EEFC_FSR_FRDY) == EEFC_FSR_FRDY ) ;
|
||||
|
||||
/* The Unique Identifier is located in the first 128 bits of the Flash memory mapping. So, at the address 0x400000-0x400003. */
|
||||
uint32_t pdwUniqueID[4];
|
||||
pdwUniqueID[0] = *(uint32_t *)IFLASH1_ADDR;
|
||||
pdwUniqueID[1] = *(uint32_t *)(IFLASH1_ADDR + 4);
|
||||
pdwUniqueID[2] = *(uint32_t *)(IFLASH1_ADDR + 8);
|
||||
pdwUniqueID[3] = *(uint32_t *)(IFLASH1_ADDR + 12);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
id[i*4+0] = (uint8_t)(pdwUniqueID[i] >> 24);
|
||||
id[i*4+1] = (uint8_t)(pdwUniqueID[i] >> 16);
|
||||
id[i*4+2] = (uint8_t)(pdwUniqueID[i] >> 8);
|
||||
id[i*4+3] = (uint8_t)(pdwUniqueID[i] >> 0);
|
||||
}
|
||||
|
||||
/* To stop the Unique Identifier mode, the user needs to send the Stop Read unique Identifier
|
||||
command (SPUI) by writing the Flash Command Register with the SPUI command. */
|
||||
EFC1->EEFC_FCR = (0x5A << 24) | EFC_FCMD_SPUI ;
|
||||
|
||||
/* When the Stop read Unique Unique Identifier command (SPUI) has been performed, the
|
||||
FRDY bit in the Flash Programming Status Register (EEFC_FSR) rises. */
|
||||
do
|
||||
{
|
||||
status = EFC1->EEFC_FSR ;
|
||||
} while ( (status & EEFC_FSR_FRDY) != EEFC_FSR_FRDY );
|
||||
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
|
||||
// from section 9.3.3 of the datasheet
|
||||
#define SERIAL_NUMBER_WORD_0 *(volatile uint32_t*)(0x0080A00C)
|
||||
#define SERIAL_NUMBER_WORD_1 *(volatile uint32_t*)(0x0080A040)
|
||||
#define SERIAL_NUMBER_WORD_2 *(volatile uint32_t*)(0x0080A044)
|
||||
#define SERIAL_NUMBER_WORD_3 *(volatile uint32_t*)(0x0080A048)
|
||||
|
||||
uint32_t pdwUniqueID[4];
|
||||
pdwUniqueID[0] = SERIAL_NUMBER_WORD_0;
|
||||
pdwUniqueID[1] = SERIAL_NUMBER_WORD_1;
|
||||
pdwUniqueID[2] = SERIAL_NUMBER_WORD_2;
|
||||
pdwUniqueID[3] = SERIAL_NUMBER_WORD_3;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
id[i*4+0] = (uint8_t)(pdwUniqueID[i] >> 24);
|
||||
id[i*4+1] = (uint8_t)(pdwUniqueID[i] >> 16);
|
||||
id[i*4+2] = (uint8_t)(pdwUniqueID[i] >> 8);
|
||||
id[i*4+3] = (uint8_t)(pdwUniqueID[i] >> 0);
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
uint32_t pdwUniqueID[3];
|
||||
pdwUniqueID[0] = HAL_GetUIDw0();
|
||||
pdwUniqueID[1] = HAL_GetUIDw1();
|
||||
pdwUniqueID[2] = HAL_GetUIDw2();
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
id[i*4+0] = (uint8_t)(pdwUniqueID[i] >> 24);
|
||||
id[i*4+1] = (uint8_t)(pdwUniqueID[i] >> 16);
|
||||
id[i*4+2] = (uint8_t)(pdwUniqueID[i] >> 8);
|
||||
id[i*4+3] = (uint8_t)(pdwUniqueID[i] >> 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ArduinoUniqueID _UniqueID;
|
104
ArduinoUniqueID.h
Normal file
104
ArduinoUniqueID.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues, Luiz Henrique Cassettari. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _ARDUINO_UNIQUE_ID_H_
|
||||
#define _ARDUINO_UNIQUE_ID_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_AVR)
|
||||
#include <avr/boot.h>
|
||||
#ifndef SIGRD
|
||||
#define SIGRD 5
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#elif defined(ARDUINO_ARCH_SAM)
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#else
|
||||
#error "ArduinoUniqueID only works on AVR, SAM, SAMD, STM32 and ESP Architecture"
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_AVR)
|
||||
|
||||
#if defined(__AVR_ATmega328PB__)
|
||||
#define UniqueIDsize 10
|
||||
#else
|
||||
#define UniqueIDsize 9
|
||||
#endif
|
||||
|
||||
#define UniqueIDbuffer UniqueIDsize
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define UniqueIDsize 4
|
||||
#define UniqueIDbuffer 8
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define UniqueIDsize 6
|
||||
#define UniqueIDbuffer 8
|
||||
#elif defined(ARDUINO_ARCH_SAM)
|
||||
#define UniqueIDsize 16
|
||||
#define UniqueIDbuffer 16
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define UniqueIDsize 16
|
||||
#define UniqueIDbuffer 16
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define UniqueIDsize 12
|
||||
#define UniqueIDbuffer 12
|
||||
#endif
|
||||
|
||||
#define UniqueID8 (_UniqueID.id + UniqueIDbuffer - 8)
|
||||
#define UniqueID (_UniqueID.id + UniqueIDbuffer - UniqueIDsize)
|
||||
|
||||
#define UniqueIDdump(stream) \
|
||||
{ \
|
||||
stream.print("UniqueID: "); \
|
||||
for (size_t i = 0; i < UniqueIDsize; i++) \
|
||||
{ \
|
||||
if (UniqueID[i] < 0x10) \
|
||||
stream.print("0"); \
|
||||
stream.print(UniqueID[i], HEX); \
|
||||
stream.print(" "); \
|
||||
} \
|
||||
stream.println(); \
|
||||
}
|
||||
|
||||
#define UniqueID8dump(stream) \
|
||||
{ \
|
||||
stream.print("UniqueID: "); \
|
||||
for (size_t i = 0; i < 8; i++) \
|
||||
{ \
|
||||
if (UniqueID8[i] < 0x10) \
|
||||
stream.print("0"); \
|
||||
stream.print(UniqueID8[i], HEX); \
|
||||
stream.print(" "); \
|
||||
} \
|
||||
stream.println(); \
|
||||
}
|
||||
|
||||
class ArduinoUniqueID
|
||||
{
|
||||
public:
|
||||
ArduinoUniqueID();
|
||||
uint8_t id[UniqueIDbuffer];
|
||||
};
|
||||
|
||||
extern ArduinoUniqueID _UniqueID;
|
||||
|
||||
#endif
|
@@ -1,62 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for considering contributing to our project. Here is a guide for how to get started and and a list of our conventions. We will also walk you through the Github command line and Desktop commands necessary to download the code, make changes, and get it included in a next version of the sofware.
|
||||
|
||||
Before contributing to this repository, please first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Find us on our website at https://dcc-ex.com, on our Discord https://discord.gg/y2sB4Fp or on Trainboard: https://www.trainboard.com/highball/index.php?threads/dcc-update-project-2020.130071/
|
||||
|
||||
# Development Environment
|
||||
|
||||
We recommend using PlatformIO IDE for VSCode. If you haven't yet used it, it is an easy to learn and easy to use IDE that really shines for embedded development and the Arduino based hardware we use. For more information go to https://platformio.org/
|
||||
|
||||
* Download and install the latest version of the Arduino IDE
|
||||
* Download and install the latest version of Visual Studio Code from Microsoft
|
||||
* Run VSCode and click on the "extensions" icon on the left. Install "PlatformIO IDE for VSCode" and the "Arduino Framework" support
|
||||
|
||||
If you don't see C/C++ Installed in the list, install that too. We also recomment installing the Gitlens extension to make working with Git and GitHub even easier.
|
||||
|
||||
You may ask if you can use the Arduino IDE, Visual Studio, or even a text editor and the answer is "of course" if you know what you are doing. Since you are just changing text files, you can use whatever you like as long as your commits and pull requests can be merged in GitHub. However, it will be much easier to follow our coding standards if you have an IDE that can automatically format things for you.
|
||||
|
||||
# Coding Style Guidelines
|
||||
|
||||
We have adopted the Google style guidlines. In particular please make sure to adhere to these standards:
|
||||
|
||||
1. All header files should have #define guards to prevent multiple inclusion.
|
||||
2. Use Unix style line endings
|
||||
3. We indent using two spaces (soft tabs)
|
||||
4. Braces
|
||||
|
||||
For more information just check our code or read https://google.github.io/styleguide/cppguide.html#C++_Version
|
||||
|
||||
## Using the Repository
|
||||
|
||||
1. Clone the repository on your local machine
|
||||
2. Create a working branch using the format "username-featurename" ex: "git branch -b frightrisk-turnouts"
|
||||
3. Commit offen, ex: "git add ." and then "git commit -m "description of your changes"
|
||||
4. Push your changes to our repository "git push"
|
||||
5. When you are ready, issue a pull request for your changes to be merged into the main branch
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Be Nice
|
||||
|
||||
### Enforcement
|
||||
|
||||
Contributors who do not follow the be nice rule in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
The DCC-EX Team has several projects and sub teams where you can help donate your epertise. See the sections below for the project or projects you are interested in.
|
||||
|
||||
### Development
|
||||
### Documentation
|
||||
### WebThrottle-EX
|
||||
### Web Support
|
||||
### Organization/Coordination
|
||||
|
||||
Links to external documentation goes here XXX
|
@@ -25,7 +25,7 @@ DCCEXParser * CommandDistributor::parser=0;
|
||||
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
|
||||
if (buffer[0] == '<') {
|
||||
if (!parser) parser = new DCCEXParser();
|
||||
parser->parse(streamer, buffer, streamer);
|
||||
parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
|
||||
}
|
||||
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
|
||||
}
|
||||
|
@@ -1,49 +1,15 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// DCC-EX CommandStation-EX Please see https://DCC-EX.com
|
||||
// © 2020, Chris Harlow. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM.
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#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 "config.h"
|
||||
#include "DCCEX.h"
|
||||
|
||||
// Create a serial command parser for the USB connection,
|
||||
@@ -51,6 +17,21 @@
|
||||
// to be issued from the USB serial console.
|
||||
DCCEXParser serialParser;
|
||||
|
||||
// (0) Declare NetworkInterfaces
|
||||
NetworkInterface nwi1;
|
||||
NetworkInterface nwi2;
|
||||
// (0) Declared NetworkInterfaces
|
||||
|
||||
// (1) Start NetworkInterface - HTTP callback
|
||||
void httpRequestHandler(ParsedRequest *req, Client* client) {
|
||||
DIAG(F("\nParsed Request:"));
|
||||
DIAG(F("\nMethod: [%s]"), req->method);
|
||||
DIAG(F("\nURI: [%s]"), req->uri);
|
||||
DIAG(F("\nHTTP version: [%s]"), req->version);
|
||||
DIAG(F("\nParameter count:[%d]\n"), *req->paramCount);
|
||||
}
|
||||
// (1) End NetworkInterface - HTTP callback
|
||||
|
||||
void setup()
|
||||
{
|
||||
// The main sketch has responsibilities during setup()
|
||||
@@ -58,9 +39,10 @@ void setup()
|
||||
// Responsibility 1: Start the usb connection for diagnostics
|
||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||
Serial.begin(115200);
|
||||
DIAG(F("DCC++ EX v%S"),F(VERSION));
|
||||
|
||||
CONDITIONAL_LCD_START {
|
||||
// This block is still executed for DIAGS if LCD not in use
|
||||
// This block is ignored if LCD not in use
|
||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||
LCD(1,F("Starting"));
|
||||
}
|
||||
@@ -68,13 +50,9 @@ 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, WIFI_CHANNEL);
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT);
|
||||
#endif // WIFI_ON
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// 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
|
||||
@@ -82,23 +60,39 @@ 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)
|
||||
RMFT::begin();
|
||||
#endif
|
||||
|
||||
#if __has_include ( "mySetup.h")
|
||||
#define SETUP(cmd) serialParser.parse(F(cmd))
|
||||
#include "mySetup.h"
|
||||
#undef SETUP
|
||||
#endif
|
||||
// (2) Start NetworkInterface - The original WifiInterface is still there but disabled
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN_SERIAL.begin(115200);
|
||||
LCN::init(LCN_SERIAL);
|
||||
#endif
|
||||
DIAG(F("\nFree RAM before network init: [%d]\n"),freeMemory());
|
||||
DIAG(F("\nNetwork Setup In Progress ...\n\n"));
|
||||
|
||||
// WIFI, TCP on Port 2560, Wifi (ssid/password) has been configured permanetly already on the esp. If
|
||||
// the connection fails will go into AP mode
|
||||
// wifi.setup(WIFI);
|
||||
|
||||
// New connection on known ssid / password combo / port can be added as a last parameter other wise the default of 2560
|
||||
// will be used. If it passes the connection will be stored as permanent default. If fails will go into AP mode.
|
||||
// wifi.init(WIFI, TCP, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME));
|
||||
// wifi.init(WIFI, TCP, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME, 2323)
|
||||
// wifi.init
|
||||
|
||||
|
||||
// nwi1.setup(ETHERNET, UDPR); // ETHERNET/UDP on Port 2560
|
||||
// nwi2.setup(ETHERNET, UDPR, 8888); // ETHERNET/UDP on Port 8888
|
||||
// nwi1.setup(ETHERNET, TCP); // ETHERNET/TCP on Port 2560
|
||||
nwi2.setup(ETHERNET, TCP, 23); // ETHERNET/TCP on Port 23 for the CLI
|
||||
// nwi1.setup(ETHERNET, TCP, 8888); // ETHERNET/TCP on Port 8888
|
||||
nwi2.setup(WIFI, TCP); // WIFI/TCP on Port 2560
|
||||
// nwi1.setHttpCallback(httpRequestHandler); // HTTP callback
|
||||
|
||||
DIAG(F("\nNetwork Setup done ...\n"));
|
||||
DIAG(F("\nFree RAM after network init: [%d]\n"),freeMemory());
|
||||
|
||||
// (2) End starting NetworkInterface
|
||||
|
||||
LCD(1,F("Ready"));
|
||||
}
|
||||
@@ -118,27 +112,23 @@ void loop()
|
||||
#if WIFI_ON
|
||||
WifiInterface::loop();
|
||||
#endif
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::loop();
|
||||
#endif
|
||||
|
||||
#if defined(RMFT_ACTIVE)
|
||||
RMFT::loop();
|
||||
#endif
|
||||
|
||||
#if defined(LCN_SERIAL)
|
||||
LCN::loop();
|
||||
#endif
|
||||
// (3) Start Loop NetworkInterface
|
||||
NetworkInterface::loop();
|
||||
// (3) End Loop NetworkInterface
|
||||
|
||||
LCDDisplay::loop(); // ignored if LCD not in use
|
||||
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
// Optionally report any decrease in memory (will automatically trigger on first call)
|
||||
#if ENABLE_FREE_MEM_WARNING
|
||||
static int ramLowWatermark = 32767; // replaced on first loop
|
||||
|
||||
int freeNow = minimumFreeMemory();
|
||||
int freeNow = freeMemory();
|
||||
if (freeNow < ramLowWatermark)
|
||||
{
|
||||
ramLowWatermark = freeNow;
|
||||
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
363
DCC.cpp
363
DCC.cpp
@@ -17,13 +17,12 @@
|
||||
* 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.
|
||||
@@ -44,27 +43,17 @@ const byte FN_GROUP_3=0x04;
|
||||
const byte FN_GROUP_4=0x08;
|
||||
const byte FN_GROUP_5=0x10;
|
||||
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::joinRelay=UNUSED_PIN;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
__FlashStringHelper* DCC::shieldName=NULL;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) {
|
||||
shieldName=(__FlashStringHelper*)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);
|
||||
}
|
||||
|
||||
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
DCCWaveform::begin(mainDriver,progDriver, timerNumber);
|
||||
}
|
||||
|
||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||
@@ -78,45 +67,19 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||
|
||||
uint8_t b[4];
|
||||
uint8_t nB = 0;
|
||||
// DIAG(F("setSpeedInternal %d %x"),cab,speedCode);
|
||||
// DIAG(F("\nsetSpeedInternal %d %x"),cab,speedCode);
|
||||
|
||||
if (cab > 127)
|
||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
b[nB++] = lowByte(cab);
|
||||
|
||||
if (globalSpeedsteps <= 28) {
|
||||
|
||||
uint8_t speed128 = speedCode & 0x7F;
|
||||
uint8_t speed28;
|
||||
uint8_t code28;
|
||||
|
||||
if (speed128 == 0 || speed128 == 1) { // stop or emergency stop
|
||||
code28 = speed128;
|
||||
} else {
|
||||
speed28= (speed128*10+36)/46; // convert 2-127 to 1-28
|
||||
/*
|
||||
if (globalSpeedsteps <= 14) // Don't want to do 14 steps, to get F0 there is ugly
|
||||
code28 = (speed28+3)/2 | (Value of F0); // convert 1-28 to DCC 14 step speed code
|
||||
else
|
||||
*/
|
||||
code28 = (speed28+3)/2 | ( (speed28 & 1) ? 0 : 0b00010000 ); // convert 1-28 to DCC 28 step speed code
|
||||
}
|
||||
// Construct command byte from:
|
||||
// command speed direction
|
||||
b[nB++] = 0b01000000 | code28 | ((speedCode & 0x80) ? 0b00100000 : 0);
|
||||
|
||||
} else { // 128 speedsteps
|
||||
|
||||
b[nB++] = SET_SPEED; // 128-step speed control byte
|
||||
b[nB++] = speedCode; // for encoding see setThrottle
|
||||
|
||||
}
|
||||
b[nB++] = SET_SPEED; // 128-step speed control byte
|
||||
b[nB++] = speedCode; // for encoding see setThrottle
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||
// DIAG(F("\nsetFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||
byte b[4];
|
||||
byte nB = 0;
|
||||
|
||||
@@ -143,28 +106,7 @@ bool DCC::getThrottleDirection(int cab) {
|
||||
|
||||
// Set function to value on or off
|
||||
void DCC::setFn( int cab, byte functionNumber, bool on) {
|
||||
if (cab<=0 ) return;
|
||||
|
||||
if (functionNumber>28) {
|
||||
//non reminding advanced binary bit set
|
||||
byte b[5];
|
||||
byte nB = 0;
|
||||
if (cab > 127)
|
||||
b[nB++] = highByte(cab) | 0xC0; // convert train number into a two-byte address
|
||||
b[nB++] = lowByte(cab);
|
||||
if (functionNumber <= 127) {
|
||||
b[nB++] = 0b11011101; // Binary State Control Instruction short form
|
||||
b[nB++] = functionNumber | (on ? 0x80 : 0);
|
||||
}
|
||||
else {
|
||||
b[nB++] = 0b11000000; // Binary State Control Instruction long form
|
||||
b[nB++] = (functionNumber & 0x7F) | (on ? 0x80 : 0); // low order bits and state flag
|
||||
b[nB++] = functionNumber >>8 ; // high order bits
|
||||
}
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cab<=0 || functionNumber>28) return;
|
||||
int reg = lookupSpeedTable(cab);
|
||||
if (reg<0) return;
|
||||
|
||||
@@ -205,9 +147,9 @@ int DCC::changeFn( int cab, byte functionNumber, bool pressed) {
|
||||
} else {
|
||||
// toggle function on press, ignore release
|
||||
if (pressed) {
|
||||
speedTable[reg].functions ^= funcmask;
|
||||
speedTable[reg].functions ^= funcmask;
|
||||
}
|
||||
funcstate = (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||
funcstate = speedTable[reg].functions & funcmask;
|
||||
}
|
||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||
return funcstate;
|
||||
@@ -248,10 +190,6 @@ void DCC::setAccessory(int address, byte number, bool activate) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||
}
|
||||
|
||||
//
|
||||
// writeCVByteMain: Write a byte with PoM on main. This writes
|
||||
// the 5 byte sized packet to implement this DCC function
|
||||
//
|
||||
void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||
byte b[5];
|
||||
byte nB = 0;
|
||||
@@ -266,10 +204,6 @@ void DCC::writeCVByteMain(int cab, int cv, byte bValue) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
}
|
||||
|
||||
//
|
||||
// writeCVBitMain: Write a bit of a byte with PoM on main. This writes
|
||||
// the 5 byte sized packet to implement this DCC function
|
||||
//
|
||||
void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
byte b[5];
|
||||
byte nB = 0;
|
||||
@@ -288,25 +222,24 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
}
|
||||
|
||||
void DCC::setProgTrackSyncMain(bool on) {
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
FSH* DCC::getMotorShieldName() {
|
||||
__FlashStringHelper* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
|
||||
const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||
const ackOp PROGMEM WRITE_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
W0,WACK,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
const ackOp PROGMEM WRITE_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
W1,WACK,
|
||||
V1, WACK, // validate bit is 1
|
||||
@@ -314,7 +247,7 @@ const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
const ackOp PROGMEM VERIFY_BIT0_PROG[] = {
|
||||
BASELINE,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC0, // if acked, callback(0)
|
||||
@@ -322,7 +255,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
ITC1,
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
const ackOp PROGMEM VERIFY_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
@@ -331,7 +264,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH READ_BIT_PROG[] = {
|
||||
const ackOp PROGMEM READ_BIT_PROG[] = {
|
||||
BASELINE,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
@@ -340,15 +273,15 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
||||
FAIL // bit not readable
|
||||
};
|
||||
|
||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
const ackOp PROGMEM WRITE_BYTE_PROG[] = {
|
||||
BASELINE,
|
||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||
// handle decoders that dont ack a write
|
||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||
WB,WACK, // Write
|
||||
VB,WACK, // validate byte
|
||||
ITC1, // if ok callback (1)
|
||||
FAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
const ackOp PROGMEM VERIFY_BYTE_PROG[] = {
|
||||
BASELINE,
|
||||
VB,WACK, // validate byte
|
||||
ITCB, // if ok callback value
|
||||
@@ -373,7 +306,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
FAIL };
|
||||
|
||||
|
||||
const ackOp FLASH READ_CV_PROG[] = {
|
||||
const ackOp PROGMEM 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
|
||||
@@ -396,33 +329,12 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
FAIL }; // verification failed
|
||||
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
const ackOp PROGMEM LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV, (ackOp)1,
|
||||
SETBIT, (ackOp)7,
|
||||
V0,WACK,NAKFAIL, // test CV 1 bit 7 is a zero... NAK means no loco found
|
||||
|
||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||
SETBYTE, (ackOp)0,
|
||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||
SETBYTE, (ackOp)128,
|
||||
VB, WACK, ITSKIP, // ignore consist if cv19 is 128 (no consist, direction bit set)
|
||||
STARTMERGE, // Setup to read cv 19
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB7, // return 7 bits only, No_ACK means CV19 not supported so ignore it
|
||||
|
||||
SKIPTARGET, // continue here if CV 19 is zero or fails all validation
|
||||
SETCV,(ackOp)29,
|
||||
SETBIT,(ackOp)5,
|
||||
V0, WACK, ITSKIP, // Skip to SKIPTARGET if bit 5 of CV29 is zero
|
||||
|
||||
V1, WACK, NAKFAIL, // fast fail if no loco on track
|
||||
// Long locoid
|
||||
SETCV, (ackOp)17, // CV 17 is part of locoid
|
||||
STARTMERGE,
|
||||
@@ -454,7 +366,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
SKIPTARGET,
|
||||
SETCV, (ackOp)1,
|
||||
STARTMERGE,
|
||||
SETBIT, (ackOp)6, // skip over first bit as we know its a zero
|
||||
V0, WACK, MERGE, // read and merge bit 1 etc
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
@@ -466,106 +378,61 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore dedcoder without cv19 support
|
||||
// Turn off long address flag
|
||||
SETCV,(ackOp)29,
|
||||
SETBIT,(ackOp)5,
|
||||
W0,WACK,
|
||||
V0,WACK,NAKFAIL,
|
||||
SETCV, (ackOp)1,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK, // some decoders don't ACK writes
|
||||
VB,WACK,ITCB,
|
||||
FAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
BASELINE,
|
||||
// Clear consist CV 19
|
||||
SETCV,(ackOp)19,
|
||||
SETBYTE, (ackOp)0,
|
||||
WB,WACK, // ignore decoder without cv19 support
|
||||
// Turn on long address flag cv29 bit 5
|
||||
SETCV,(ackOp)29,
|
||||
SETBIT,(ackOp)5,
|
||||
W1,WACK,
|
||||
V1,WACK,NAKFAIL,
|
||||
// Store high byte of address in cv 17
|
||||
SETCV, (ackOp)17,
|
||||
SETBYTEH, // high byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,NAKFAIL,
|
||||
// store
|
||||
SETCV, (ackOp)18,
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,ITC1, // callback(1) means Ok
|
||||
FAIL
|
||||
};
|
||||
// On the following prog-track functions blocking defaults to false.
|
||||
// blocking=true forces the API to block, waiting for the response and invoke the callback BEFORE returning.
|
||||
// During that wait, other parts of the system will be unresponsive.
|
||||
// blocking =false means the callback will be called some time after the API returns (typically a few tenths of a second)
|
||||
// but that would be very inconvenient in a Wifi situaltion where the stream becomes
|
||||
// unuavailable immediately after the API rerturns.
|
||||
|
||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
void DCC::writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
|
||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
|
||||
void DCC::writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
void DCC::verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking) {
|
||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
|
||||
void DCC::verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
|
||||
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||
void DCC::readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
|
||||
void DCC::readCV(int cv, ACK_CALLBACK callback, bool blocking) {
|
||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
|
||||
void DCC::getLocoId(ACK_CALLBACK callback, bool blocking) {
|
||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback, blocking);
|
||||
}
|
||||
|
||||
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
if (id<1 || id>10239) { //0x27FF according to standard
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
if (id<=127)
|
||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
else
|
||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg>=0) speedTable[reg].loco=0;
|
||||
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||
}
|
||||
void DCC::forgetAllLocos() { // removes all speed reminders
|
||||
setThrottle2(0,1); // ESTOP all locos still on track
|
||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||
for (int i=0;i<MAX_LOCOS;i++) speedTable[i].loco=0;
|
||||
}
|
||||
|
||||
byte DCC::loopStatus=0;
|
||||
|
||||
void DCC::loop() {
|
||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||
ackManagerLoop(); // maintain prog track ack manager
|
||||
DCCWaveform::loop(); // power overload checks
|
||||
ackManagerLoop(false); // maintain prog track ack manager
|
||||
issueReminders();
|
||||
}
|
||||
|
||||
@@ -593,7 +460,7 @@ bool DCC::issueReminder(int reg) {
|
||||
|
||||
switch (loopStatus) {
|
||||
case 0:
|
||||
// DIAG(F("Reminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||
// DIAG(F("\nReminder %d speed %d"),loco,speedTable[reg].speedCode);
|
||||
setThrottle2(loco, speedTable[reg].speedCode);
|
||||
break;
|
||||
case 1: // remind function group 1 (F0-F4)
|
||||
@@ -651,7 +518,7 @@ int DCC::lookupSpeedTable(int locoId) {
|
||||
}
|
||||
if (reg == MAX_LOCOS) reg = firstEmpty;
|
||||
if (reg >= MAX_LOCOS) {
|
||||
DIAG(F("Too many locos"));
|
||||
DIAG(F("\nToo many locos\n"));
|
||||
return -1;
|
||||
}
|
||||
if (reg==firstEmpty){
|
||||
@@ -685,65 +552,58 @@ int DCC::nextLoco = 0;
|
||||
ackOp const * DCC::ackManagerProg;
|
||||
byte DCC::ackManagerByte;
|
||||
byte DCC::ackManagerStash;
|
||||
int DCC::ackManagerWord;
|
||||
int DCC::ackManagerCv;
|
||||
int DCC::ackManagerCv;
|
||||
byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
bool DCC::ackManagerRejoin;
|
||||
|
||||
ACK_CALLBACK DCC::ackManagerCallback;
|
||||
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback, bool blocking) {
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
if (blocking) ackManagerLoop(blocking);
|
||||
}
|
||||
|
||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
ackManagerProg = program;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCC::checkResets(uint8_t numResets) {
|
||||
bool DCC::checkResets(bool blocking, uint8_t numResets) {
|
||||
if (blocking) {
|
||||
// must block waiting for restest to be issued
|
||||
while(DCCWaveform::progTrack.sentResetsSincePacket < numResets);
|
||||
return false; // caller need not yield
|
||||
}
|
||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||
}
|
||||
|
||||
void DCC::ackManagerLoop() {
|
||||
void DCC::ackManagerLoop(bool blocking) {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
byte opcode=pgm_read_byte_near(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
// if blocking then we must ONLY return AFTER callback issued
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
setProgTrackSyncMain(false);
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
if (Diag::ACK) DIAG(F("\nAuto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
DCCWaveform::progTrack.autoPowerOff=true;
|
||||
return;
|
||||
if (!blocking) return;
|
||||
}
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
||||
if (checkResets(blocking, DCCWaveform::progTrack.autoPowerOff ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
if (checkResets(blocking, RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("\nW%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
@@ -753,8 +613,8 @@ void DCC::ackManagerLoop() {
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
if (checkResets(blocking, RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("\nWB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
@@ -763,8 +623,8 @@ void DCC::ackManagerLoop() {
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
if (checkResets(blocking, RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("\nVB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
@@ -774,8 +634,8 @@ void DCC::ackManagerLoop() {
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
if (checkResets(blocking, RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("\nV%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
@@ -786,43 +646,44 @@ void DCC::ackManagerLoop() {
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=DCCWaveform::progTrack.getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
if (blocking) {
|
||||
while(ackState==2) ackState=DCCWaveform::progTrack.getAck();
|
||||
}
|
||||
else {
|
||||
ackState=DCCWaveform::progTrack.getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
}
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
ackManagerProg = NULL; // all done now
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
ackManagerProg = NULL; // all done now
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
ackManagerProg = NULL; // all done now
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAIL: // callback(-1)
|
||||
callback(-1);
|
||||
ackManagerProg = NULL;
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case STARTMERGE:
|
||||
@@ -839,25 +700,12 @@ void DCC::ackManagerLoop() {
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
ackManagerBitNum=pgm_read_byte_near(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
ackManagerCv=pgm_read_byte_near(ackManagerProg);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
@@ -866,6 +714,7 @@ void DCC::ackManagerLoop() {
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
ackManagerProg=NULL;
|
||||
callback( ackManagerByte + ((ackManagerStash - 192) << 8));
|
||||
return;
|
||||
|
||||
@@ -874,13 +723,14 @@ void DCC::ackManagerLoop() {
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
opcode=pgm_read_byte_near(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
DIAG(F("\n!! ackOp %d FAULT!!"),opcode);
|
||||
ackManagerProg=NULL;
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
@@ -889,16 +739,11 @@ void DCC::ackManagerLoop() {
|
||||
}
|
||||
}
|
||||
void DCC::callback(int value) {
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
if (Diag::ACK) DIAG(F("\nAuto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
}
|
||||
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
setProgTrackSyncMain(ackManagerRejoin);
|
||||
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
if (Diag::ACK) DIAG(F("\nCallback(%d)\n"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
|
||||
@@ -908,10 +753,10 @@ void DCC::callback(int value) {
|
||||
for (int reg = 0; reg < MAX_LOCOS; reg++) {
|
||||
if (speedTable[reg].loco>0) {
|
||||
used ++;
|
||||
StringFormatter::send(stream,F("cab=%d, speed=%d, dir=%c \n"),
|
||||
StringFormatter::send(stream,F("\ncab=%d, speed=%d, dir=%c "),
|
||||
speedTable[reg].loco, speedTable[reg].speedCode & 0x7f,(speedTable[reg].speedCode & 0x80) ? 'F':'R');
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("Used=%d, max=%d\n"),used,MAX_LOCOS);
|
||||
StringFormatter::send(stream,F("\nUsed=%d, max=%d\n"),used,MAX_LOCOS);
|
||||
|
||||
}
|
||||
|
59
DCC.h
59
DCC.h
@@ -21,11 +21,10 @@
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "MotorDrivers.h"
|
||||
#include "FSH.h"
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
typedef void (*ACK_CALLBACK)(int result);
|
||||
|
||||
enum ackOp : byte
|
||||
enum ackOp
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
@@ -38,16 +37,12 @@ enum ackOp : byte
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
@@ -65,8 +60,7 @@ const byte MAX_LOCOS = 50;
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
@@ -86,25 +80,21 @@ public:
|
||||
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
static void readCV(int16_t cv, ACK_CALLBACK callback);
|
||||
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
||||
static void writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
||||
static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||
static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback);
|
||||
static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback);
|
||||
static void readCV(int cv, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void readCVBit(int cv, byte bitNum, ACK_CALLBACK callback, bool blocking = false); // -1 for error
|
||||
static void writeCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void writeCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void verifyCVByte(int cv, byte byteValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
static void verifyCVBit(int cv, byte bitNum, bool bitValue, ACK_CALLBACK callback, bool blocking = false);
|
||||
|
||||
static void getLocoId(ACK_CALLBACK callback);
|
||||
static void setLocoId(int id,ACK_CALLBACK callback);
|
||||
static void getLocoId(ACK_CALLBACK callback, bool blocking = false);
|
||||
|
||||
// Enhanced API functions
|
||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||
static void forgetAllLocos(); // removes all speed reminders
|
||||
static void displayCabList(Print *stream);
|
||||
|
||||
static FSH *getMotorShieldName();
|
||||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
};
|
||||
static __FlashStringHelper *getMotorShieldName();
|
||||
|
||||
private:
|
||||
struct LOCO
|
||||
@@ -114,15 +104,13 @@ private:
|
||||
byte groupFlags;
|
||||
unsigned long functions;
|
||||
};
|
||||
static byte joinRelay;
|
||||
static byte loopStatus;
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
static void updateLocoReminder(int loco, byte speedCode);
|
||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
||||
static bool issueReminder(int reg);
|
||||
static int nextLoco;
|
||||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
static __FlashStringHelper *shieldName;
|
||||
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static byte cv1(byte opcode, int cv);
|
||||
@@ -136,15 +124,12 @@ private:
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerLoop();
|
||||
static bool checkResets( uint8_t numResets);
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback, bool blocking);
|
||||
static void ackManagerLoop(bool blocking);
|
||||
static bool checkResets(bool blocking, uint8_t numResets);
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// NMRA codes #
|
||||
@@ -171,17 +156,9 @@ private:
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#elif defined(ARDUINO_TEENSY32)
|
||||
#define ARDUINO_TYPE "TEENSY32"
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#define ARDUINO_TYPE "UNOWIFIR2"
|
||||
#elif defined(ARDUINO_SAMD_ZERO)
|
||||
#define ARDUINO_TYPE "FireBoxMK1"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#endif
|
||||
|
11
DCCEX.h
11
DCCEX.h
@@ -10,16 +10,9 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "version.h"
|
||||
#include "WifiInterface.h"
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#endif
|
||||
#include "NetworkInterface.h"
|
||||
#include "LCD_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "freeMemory.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#include "RMFT.h"
|
||||
#define RMFT_ACTIVE
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
481
DCCEXParser.cpp
481
DCCEXParser.cpp
@@ -28,41 +28,34 @@
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include <avr/wdt.h>
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_JOIN = -30750;
|
||||
const int16_t HASH_KEYWORD_CABS = -11981;
|
||||
const int16_t HASH_KEYWORD_RAM = 25982;
|
||||
const int16_t HASH_KEYWORD_CMD = 9962;
|
||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||
const int16_t HASH_KEYWORD_ACK = 3113;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_DCC = 6436;
|
||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
||||
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
||||
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||
const int16_t HASH_KEYWORD_MAX = 16244;
|
||||
const int16_t HASH_KEYWORD_MIN = 15978;
|
||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||
const int HASH_KEYWORD_PROG = -29718;
|
||||
const int HASH_KEYWORD_MAIN = 11339;
|
||||
const int HASH_KEYWORD_JOIN = -30750;
|
||||
const int HASH_KEYWORD_CABS = -11981;
|
||||
const int HASH_KEYWORD_RAM = 25982;
|
||||
const int HASH_KEYWORD_CMD = 9962;
|
||||
const int HASH_KEYWORD_WIT = 31594;
|
||||
const int HASH_KEYWORD_WIFI = -5583;
|
||||
const int HASH_KEYWORD_ACK = 3113;
|
||||
const int HASH_KEYWORD_ON = 2657;
|
||||
const int HASH_KEYWORD_DCC = 6436;
|
||||
const int HASH_KEYWORD_SLOW = -17209;
|
||||
const int HASH_KEYWORD_PROGBOOST = -6353;
|
||||
const int HASH_KEYWORD_EEPROM = -7168;
|
||||
const int HASH_KEYWORD_LIMIT = 27413;
|
||||
const int HASH_KEYWORD_NET = 21503;
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
int DCCEXParser::stashP[MAX_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
|
||||
Print *DCCEXParser::stashStream = NULL;
|
||||
RingStream *DCCEXParser::stashRingStream = NULL;
|
||||
byte DCCEXParser::stashTarget=0;
|
||||
|
||||
// This is a JMRI command parser, one instance per incoming stream
|
||||
// It doesnt know how the string got here, nor how it gets back.
|
||||
@@ -74,7 +67,7 @@ DCCEXParser::DCCEXParser() {}
|
||||
void DCCEXParser::flush()
|
||||
{
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Buffer flush"));
|
||||
DIAG(F("\nBuffer flush"));
|
||||
bufferLength = 0;
|
||||
inCommandPayload = false;
|
||||
}
|
||||
@@ -97,7 +90,7 @@ void DCCEXParser::loop(Stream &stream)
|
||||
else if (ch == '>')
|
||||
{
|
||||
buffer[bufferLength] = '\0';
|
||||
parse(&stream, buffer, NULL); // Parse this (No ringStream for serial)
|
||||
parse(&stream, buffer, false); // Parse this allowing async responses
|
||||
inCommandPayload = false;
|
||||
break;
|
||||
}
|
||||
@@ -106,22 +99,21 @@ void DCCEXParser::loop(Stream &stream)
|
||||
buffer[bufferLength++] = ch;
|
||||
}
|
||||
}
|
||||
Sensor::checkAll(&stream); // Update and print changes
|
||||
}
|
||||
|
||||
int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||
int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd)
|
||||
{
|
||||
byte state = 1;
|
||||
byte parameterCount = 0;
|
||||
int16_t runningValue = 0;
|
||||
int runningValue = 0;
|
||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||
bool signNegative = false;
|
||||
|
||||
// clear all parameters in case not enough found
|
||||
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||
for (int i = 0; i < MAX_PARAMS; i++)
|
||||
result[i] = 0;
|
||||
|
||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||
while (parameterCount < MAX_PARAMS)
|
||||
{
|
||||
byte hot = *remainingCmd;
|
||||
|
||||
@@ -150,7 +142,6 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
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
|
||||
@@ -168,97 +159,24 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||
return parameterCount;
|
||||
}
|
||||
|
||||
int16_t DCCEXParser::splitHexValues(int16_t result[MAX_COMMAND_PARAMS], const byte *cmd)
|
||||
{
|
||||
byte state = 1;
|
||||
byte parameterCount = 0;
|
||||
int16_t runningValue = 0;
|
||||
const byte *remainingCmd = cmd + 1; // skips the opcode
|
||||
|
||||
// clear all parameters in case not enough found
|
||||
for (int16_t i = 0; i < MAX_COMMAND_PARAMS; i++)
|
||||
result[i] = 0;
|
||||
|
||||
while (parameterCount < MAX_COMMAND_PARAMS)
|
||||
{
|
||||
byte hot = *remainingCmd;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
|
||||
case 1: // skipping spaces before a param
|
||||
if (hot == ' ')
|
||||
break;
|
||||
if (hot == '\0' || hot == '>')
|
||||
return parameterCount;
|
||||
state = 2;
|
||||
continue;
|
||||
|
||||
case 2: // checking first hex digit
|
||||
runningValue = 0;
|
||||
state = 3;
|
||||
continue;
|
||||
|
||||
case 3: // building a parameter
|
||||
if (hot >= '0' && hot <= '9')
|
||||
{
|
||||
runningValue = 16 * runningValue + (hot - '0');
|
||||
break;
|
||||
}
|
||||
if (hot >= 'A' && hot <= 'F')
|
||||
{
|
||||
runningValue = 16 * runningValue + 10 + (hot - 'A');
|
||||
break;
|
||||
}
|
||||
if (hot >= 'a' && hot <= 'f')
|
||||
{
|
||||
runningValue = 16 * runningValue + 10 + (hot - 'a');
|
||||
break;
|
||||
}
|
||||
if (hot==' ' || hot=='>' || hot=='\0') {
|
||||
result[parameterCount] = runningValue;
|
||||
parameterCount++;
|
||||
state = 1;
|
||||
continue;
|
||||
}
|
||||
return -1; // invalid hex digit
|
||||
}
|
||||
remainingCmd++;
|
||||
}
|
||||
return parameterCount;
|
||||
}
|
||||
|
||||
FILTER_CALLBACK DCCEXParser::filterCallback = 0;
|
||||
FILTER_CALLBACK DCCEXParser::filterRMFTCallback = 0;
|
||||
AT_COMMAND_CALLBACK DCCEXParser::atCommandCallback = 0;
|
||||
void DCCEXParser::setFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterCallback = filter;
|
||||
}
|
||||
void DCCEXParser::setRMFTFilter(FILTER_CALLBACK filter)
|
||||
{
|
||||
filterRMFTCallback = filter;
|
||||
}
|
||||
void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
||||
{
|
||||
atCommandCallback = callback;
|
||||
}
|
||||
|
||||
// Parse an F() string
|
||||
void DCCEXParser::parse(const FSH * cmd) {
|
||||
int size=strlen_P((char *)cmd)+1;
|
||||
char buffer[size];
|
||||
strcpy_P(buffer,(char *)cmd);
|
||||
parse(&Serial,(byte *)buffer,NULL);
|
||||
}
|
||||
|
||||
// See documentation on DCC class for info on this section
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
void DCCEXParser::parse(Print *stream, byte *com, bool blocking)
|
||||
{
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
if (Diag::CMD)
|
||||
DIAG(F("PARSING:%s"), com);
|
||||
int16_t p[MAX_COMMAND_PARAMS];
|
||||
DIAG(F("\nPARSING:%s\n"), com);
|
||||
int p[MAX_PARAMS];
|
||||
while (com[0] == '<' || com[0] == ' ')
|
||||
com++; // strip off any number of < or spaces
|
||||
byte params = splitValues(p, com);
|
||||
@@ -266,8 +184,6 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
if (filterCallback)
|
||||
filterCallback(stream, opcode, params, p);
|
||||
if (filterRMFTCallback && opcode!='\0')
|
||||
filterRMFTCallback(stream, opcode, params, p);
|
||||
|
||||
// Functions return from this switch if complete, break from switch implies error <X> to send
|
||||
switch (opcode)
|
||||
@@ -276,9 +192,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return; // filterCallback asked us to ignore
|
||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||
{
|
||||
int16_t cab;
|
||||
int16_t tspeed;
|
||||
int16_t direction;
|
||||
int cab;
|
||||
int tspeed;
|
||||
int direction;
|
||||
|
||||
if (params == 4)
|
||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||
@@ -295,8 +211,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
else
|
||||
break;
|
||||
|
||||
// Convert DCC-EX protocol speed steps where
|
||||
// -1=emergency stop, 0-126 as speeds
|
||||
// Convert JMRI bizarre -1=emergency stop, 0-126 as speeds
|
||||
// to DCC 0=stop, 1= emergency stop, 2-127 speeds
|
||||
if (tspeed > 126 || tspeed < -1)
|
||||
break; // invalid JMRI speed code
|
||||
@@ -312,9 +227,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
DCC::setThrottle(cab, tspeed, direction);
|
||||
if (params == 4)
|
||||
StringFormatter::send(stream, F("<T %d %d %d>\n"), p[0], p[2], p[3]);
|
||||
StringFormatter::send(stream, F("<T %d %d %d>"), p[0], p[2], p[3]);
|
||||
else
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return;
|
||||
}
|
||||
case 'f': // FUNCTION <f CAB BYTE1 [BYTE2]>
|
||||
@@ -322,33 +237,12 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
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);
|
||||
}
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
if (p[2] != (p[2] & 1))
|
||||
return;
|
||||
DCC::setAccessory(p[0], p[1], p[2] == 1);
|
||||
return;
|
||||
|
||||
|
||||
case 'T': // TURNOUT <T ...>
|
||||
if (parseT(stream, params, p))
|
||||
return;
|
||||
@@ -372,66 +266,48 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||
return;
|
||||
|
||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||
// Re-parse the command using a hex-only splitter
|
||||
params=splitHexValues(p,com)-1; // drop REG
|
||||
if (params<1) break;
|
||||
{
|
||||
byte packet[params];
|
||||
for (int i=0;i<params;i++) {
|
||||
packet[i]=(byte)p[i+1];
|
||||
if (Diag::CMD) DIAG(F("packet[%d]=%d (0x%x)"), i, packet[i], packet[i]);
|
||||
}
|
||||
(opcode=='M'?DCCWaveform::mainTrack:DCCWaveform::progTrack).schedulePacket(packet,params,3);
|
||||
}
|
||||
return;
|
||||
|
||||
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||
DCC::setLocoId(p[0],callback_Wloco);
|
||||
else // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::writeCVByte(p[0], p[1], callback_W, blocking);
|
||||
return;
|
||||
|
||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||
if (params == 2)
|
||||
{ // <V CV VALUE>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
|
||||
DCC::verifyCVByte(p[0], p[1], callback_Vbyte, blocking);
|
||||
return;
|
||||
}
|
||||
if (params == 3)
|
||||
{
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit);
|
||||
DCC::verifyCVBit(p[0], p[1], p[2], callback_Vbit, blocking);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B, blocking);
|
||||
return;
|
||||
|
||||
case 'R': // READ CV ON PROG
|
||||
if (params == 3)
|
||||
{ // <R CV CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::readCV(p[0], callback_R);
|
||||
DCC::readCV(p[0], callback_R, blocking);
|
||||
return;
|
||||
}
|
||||
if (params == 0)
|
||||
{ // <R> New read loco id
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
if (!stashCallback(stream, p))
|
||||
break;
|
||||
DCC::getLocoId(callback_Rloco);
|
||||
DCC::getLocoId(callback_Rloco, blocking);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -443,28 +319,27 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
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 ||
|
||||
(MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling
|
||||
if (params == 0)
|
||||
{
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c>\n"), opcode);
|
||||
StringFormatter::send(stream, F("<p%c>"), opcode);
|
||||
return;
|
||||
}
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_MAIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
StringFormatter::send(stream, F("<p%c MAIN>\n"), opcode);
|
||||
StringFormatter::send(stream, F("<p%c MAIN>"), opcode);
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_PROG:
|
||||
DCCWaveform::progTrack.setPowerMode(mode);
|
||||
if (mode == POWERMODE::OFF)
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
StringFormatter::send(stream, F("<p%c PROG>\n"), opcode);
|
||||
StringFormatter::send(stream, F("<p%c PROG>"), opcode);
|
||||
return;
|
||||
case HASH_KEYWORD_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(mode);
|
||||
@@ -472,48 +347,43 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
if (mode == POWERMODE::ON)
|
||||
{
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
StringFormatter::send(stream, F("<p1 JOIN>\n"), opcode);
|
||||
StringFormatter::send(stream, F("<p1 JOIN>"), opcode);
|
||||
}
|
||||
else
|
||||
StringFormatter::send(stream, F("<p0>\n"));
|
||||
StringFormatter::send(stream, F("<p0>"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
return;
|
||||
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||
case 'c': // READ CURRENT <c>
|
||||
StringFormatter::send(stream, F("<a %d>"), DCCWaveform::mainTrack.getLastCurrent());
|
||||
return;
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
Sensor::checkAll();
|
||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||
{
|
||||
StringFormatter::send(stream, F("<%c %d>"), tt->active ? 'Q' : 'q', tt->data.snum);
|
||||
}
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
StringFormatter::send(stream, F("<p%d>"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
// TODO Send stats of speed reminders table
|
||||
return;
|
||||
// TODO send status of turnouts etc etc
|
||||
return;
|
||||
|
||||
case 'E': // STORE EPROM <E>
|
||||
EEStore::store();
|
||||
StringFormatter::send(stream, F("<e %d %d %d>\n"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
||||
StringFormatter::send(stream, F("<e %d %d %d>"), EEStore::eeStore->data.nTurnouts, EEStore::eeStore->data.nSensors, EEStore::eeStore->data.nOutputs);
|
||||
return;
|
||||
|
||||
case 'e': // CLEAR EPROM <e>
|
||||
EEStore::clear();
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return;
|
||||
|
||||
case ' ': // < >
|
||||
@@ -526,13 +396,7 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
|
||||
return;
|
||||
|
||||
case '-': // Forget Loco <- [cab]>
|
||||
if (params > 1 || p[0]<0) break;
|
||||
if (p[0]==0) DCC::forgetAllLocos();
|
||||
else DCC::forgetLoco(p[0]);
|
||||
StringFormatter::send(stream, F("<# %d>"), MAX_LOCOS);
|
||||
return;
|
||||
|
||||
case 'F': // New command to call the new Loco Function API <F cab func 1|0>
|
||||
@@ -543,60 +407,53 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
if (atCommandCallback) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
atCommandCallback(com);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||
DIAG(F("\nOpcode=%c params=%d\n"), opcode, params);
|
||||
for (int i = 0; i < params; i++)
|
||||
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
|
||||
DIAG(F("p[%d]=%d (0x%x)\n"), i, p[i], p[i]);
|
||||
break;
|
||||
|
||||
} // end of opcode switch
|
||||
|
||||
// Any fallout here sends an <X>
|
||||
StringFormatter::send(stream, F("<X>\n"));
|
||||
StringFormatter::send(stream, F("<X>"));
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
bool DCCEXParser::parseZ(Print *stream, int params, int p[])
|
||||
{
|
||||
|
||||
switch (params)
|
||||
{
|
||||
|
||||
|
||||
case 2: // <Z ID ACTIVATE>
|
||||
{
|
||||
Output *o = Output::get(p[0]);
|
||||
if (o == NULL)
|
||||
return false;
|
||||
o->activate(p[1]);
|
||||
StringFormatter::send(stream, F("<Y %d %d>\n"), p[0], p[1]);
|
||||
StringFormatter::send(stream, F("<Y %d %d>"), p[0], p[1]);
|
||||
}
|
||||
return true;
|
||||
|
||||
case 3: // <Z ID PIN INVERT>
|
||||
if (!Output::create(p[0], p[1], p[2], 1))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
Output::create(p[0], p[1], p[2], 1);
|
||||
return true;
|
||||
|
||||
case 1: // <Z ID>
|
||||
if (!Output::remove(p[0]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
return true;
|
||||
return Output::remove(p[0]);
|
||||
|
||||
case 0: // <Z> list Output definitions
|
||||
case 0: // <Z>
|
||||
{
|
||||
bool gotone = false;
|
||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||
{
|
||||
gotone = true;
|
||||
StringFormatter::send(stream, F("<Y %d %d %d %d>\n"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
|
||||
StringFormatter::send(stream, F("<Y %d %d %d %d>"), tt->data.id, tt->data.pin, tt->data.iFlag, tt->data.oStatus);
|
||||
}
|
||||
return gotone;
|
||||
}
|
||||
@@ -606,7 +463,7 @@ bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
}
|
||||
|
||||
//===================================
|
||||
bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
|
||||
bool DCCEXParser::parsef(Print *stream, int params, int p[])
|
||||
{
|
||||
// JMRI sends this info in DCC message format but it's not exactly
|
||||
// convenient for other processing
|
||||
@@ -638,9 +495,9 @@ bool DCCEXParser::parsef(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
}
|
||||
|
||||
void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
void DCCEXParser::funcmap(int cab, byte value, byte fstart, byte fstop)
|
||||
{
|
||||
for (int16_t i = fstart; i <= fstop; i++)
|
||||
for (int i = fstart; i <= fstop; i++)
|
||||
{
|
||||
DCC::setFn(cab, i, value & 1);
|
||||
value >>= 1;
|
||||
@@ -648,18 +505,17 @@ void DCCEXParser::funcmap(int16_t cab, byte value, byte fstart, byte fstop)
|
||||
}
|
||||
|
||||
//===================================
|
||||
bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
bool DCCEXParser::parseT(Print *stream, int params, int p[])
|
||||
{
|
||||
switch (params)
|
||||
{
|
||||
case 0: // <T> list turnout definitions
|
||||
case 0: // <T> show all turnouts
|
||||
{
|
||||
bool gotOne = false;
|
||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||
{
|
||||
gotOne = true;
|
||||
StringFormatter::send(stream, F("<H %d %d %d %d>\n"), tt->data.id, tt->data.address,
|
||||
tt->data.subAddress, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
|
||||
}
|
||||
return gotOne; // will <X> if none found
|
||||
}
|
||||
@@ -667,7 +523,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
case 1: // <T id> delete turnout
|
||||
if (!Turnout::remove(p[0]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return true;
|
||||
|
||||
case 2: // <T id 0|1> activate turnout
|
||||
@@ -676,14 +532,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
if (!tt)
|
||||
return false;
|
||||
tt->activate(p[1]);
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||
StringFormatter::send(stream, F("<H %d %d>"), tt->data.id, tt->data.tStatus & STATUS_ACTIVE);
|
||||
}
|
||||
return true;
|
||||
|
||||
case 3: // <T id addr subaddr> define turnout
|
||||
if (!Turnout::create(p[0], p[1], p[2]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
StringFormatter::send(stream, F("<O>"));
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -691,29 +547,24 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||
bool DCCEXParser::parseS(Print *stream, int params, int p[])
|
||||
{
|
||||
|
||||
switch (params)
|
||||
{
|
||||
case 3: // <S id pin pullup> create sensor. pullUp indicator (0=LOW/1=HIGH)
|
||||
if (!Sensor::create(p[0], p[1], p[2]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
Sensor::create(p[0], p[1], p[2]);
|
||||
return true;
|
||||
|
||||
case 1: // S id> remove sensor
|
||||
if (!Sensor::remove(p[0]))
|
||||
return false;
|
||||
StringFormatter::send(stream, F("<O>\n"));
|
||||
return true;
|
||||
if (Sensor::remove(p[0]))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case 0: // <S> list sensor definitions
|
||||
if (Sensor::firstSensor == NULL)
|
||||
return false;
|
||||
case 0: // <S> lit sensor states
|
||||
for (Sensor *tt = Sensor::firstSensor; tt != NULL; tt = tt->nextSensor)
|
||||
{
|
||||
StringFormatter::send(stream, F("<Q %d %d %d>\n"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
||||
StringFormatter::send(stream, F("<Q %d %d %d>"), tt->data.snum, tt->data.pin, tt->data.pullUp);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -723,7 +574,7 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
bool DCCEXParser::parseD(Print *stream, int params, int p[])
|
||||
{
|
||||
if (params == 0)
|
||||
return false;
|
||||
@@ -735,25 +586,15 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory());
|
||||
StringFormatter::send(stream, F("\nFree memory=%d\n"), freeMemory());
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX] Value>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||
StringFormatter::send(stream, F("Ack limit=%dmA\n"), p[2]);
|
||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
||||
StringFormatter::send(stream, F("Ack min=%dus\n"), p[2]);
|
||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
||||
StringFormatter::send(stream, F("Ack max=%dus\n"), p[2]);
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF>
|
||||
if (params >= 2 && p[1] == HASH_KEYWORD_LIMIT) {
|
||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||
StringFormatter::send(stream, F("\nAck limit=%dmA\n"), p[2]);
|
||||
} else
|
||||
Diag::ACK = onOff;
|
||||
}
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||
@@ -764,43 +605,26 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
Diag::WIFI = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF>
|
||||
Diag::ETHERNET = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
|
||||
Diag::WITHROTTLE = onOff;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_LCN: // <D LCN ON/OFF>
|
||||
Diag::LCN = onOff;
|
||||
|
||||
case HASH_KEYWORD_DCC:
|
||||
DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESET:
|
||||
{
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
break; // and <X> if we didnt restart
|
||||
}
|
||||
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
if (params >= 2)
|
||||
case HASH_KEYWORD_EEPROM:
|
||||
if (params >= 1)
|
||||
EEStore::dump(p[1]);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
StringFormatter::send(stream, F("28 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
StringFormatter::send(stream, F("128 Speedsteps"));
|
||||
return true;
|
||||
case HASH_KEYWORD_NET:
|
||||
_nLogLevel = p[1];
|
||||
return true;
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
@@ -809,70 +633,45 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
}
|
||||
|
||||
// CALLBACKS must be static
|
||||
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||
bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS])
|
||||
{
|
||||
if (stashBusy )
|
||||
return false;
|
||||
stashBusy = true;
|
||||
stashStream = stream;
|
||||
stashRingStream=ringStream;
|
||||
if (ringStream) stashTarget= ringStream->peekTargetMark();
|
||||
memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0]));
|
||||
memcpy(stashP, p, MAX_PARAMS * sizeof(p[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
Print * DCCEXParser::getAsyncReplyStream() {
|
||||
if (stashRingStream) {
|
||||
stashRingStream->mark(stashTarget);
|
||||
return stashRingStream;
|
||||
}
|
||||
return stashStream;
|
||||
void DCCEXParser::callback_W(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::commitAsyncReplyStream() {
|
||||
if (stashRingStream) stashRingStream->commit();
|
||||
stashBusy = false;
|
||||
void DCCEXParser::callback_B(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d %d>"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
|
||||
stashBusy = false;
|
||||
}
|
||||
void DCCEXParser::callback_Vbit(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<v %d %d %d>"), stashP[0], stashP[1], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
void DCCEXParser::callback_Vbyte(int result)
|
||||
{
|
||||
StringFormatter::send(stashStream, F("<v %d %d>"), stashP[0], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_W(int16_t result)
|
||||
void DCCEXParser::callback_R(int result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r%d|%d|%d %d>\n"), stashP[2], stashP[3], stashP[0], result == 1 ? stashP[1] : -1);
|
||||
commitAsyncReplyStream();
|
||||
StringFormatter::send(stashStream, F("<r%d|%d|%d %d>"), stashP[1], stashP[2], stashP[0], result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_B(int16_t result)
|
||||
void DCCEXParser::callback_Rloco(int result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(),
|
||||
F("<r%d|%d|%d %d %d>\n"), stashP[3], stashP[4], stashP[0], stashP[1], result == 1 ? stashP[2] : -1);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
void DCCEXParser::callback_Vbit(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d %d>\n"), stashP[0], stashP[1], result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
void DCCEXParser::callback_Vbyte(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<v %d %d>\n"), stashP[0], result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_R(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<r%d|%d|%d %d>\n"), stashP[1], stashP[2], stashP[0], result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_Rloco(int16_t result)
|
||||
{
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<r %d>\n"), result);
|
||||
commitAsyncReplyStream();
|
||||
}
|
||||
|
||||
void DCCEXParser::callback_Wloco(int16_t result)
|
||||
{
|
||||
if (result==1) result=stashP[0]; // pick up original requested id from command
|
||||
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
|
||||
commitAsyncReplyStream();
|
||||
StringFormatter::send(stashStream, F("<r %d>"), result);
|
||||
stashBusy = false;
|
||||
}
|
||||
|
@@ -19,60 +19,49 @@
|
||||
#ifndef DCCEXParser_h
|
||||
#define DCCEXParser_h
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
#include "RingStream.h"
|
||||
|
||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
|
||||
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
|
||||
|
||||
struct DCCEXParser
|
||||
{
|
||||
DCCEXParser();
|
||||
void loop(Stream & stream);
|
||||
void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
void parse(const FSH * cmd);
|
||||
void parse(Print * stream, byte * command, bool blocking);
|
||||
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_COMMAND_PARAMS=10; // Must not exceed this
|
||||
static const int MAX_PARAMS=10; // Must not exceed this
|
||||
|
||||
private:
|
||||
|
||||
static const int16_t MAX_BUFFER=50; // longest command sent in
|
||||
static const int MAX_BUFFER=50; // longest command sent in
|
||||
byte bufferLength=0;
|
||||
bool inCommandPayload=false;
|
||||
byte buffer[MAX_BUFFER+2];
|
||||
int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
int16_t splitHexValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command);
|
||||
int splitValues( int result[MAX_PARAMS], const byte * command);
|
||||
|
||||
bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
bool parseT(Print * stream, int params, int p[]);
|
||||
bool parseZ(Print * stream, int params, int p[]);
|
||||
bool parseS(Print * stream, int params, int p[]);
|
||||
bool parsef(Print * stream, int params, int p[]);
|
||||
bool parseD(Print * stream, int params, int p[]);
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
|
||||
static bool stashBusy;
|
||||
static byte stashTarget;
|
||||
static Print * stashStream;
|
||||
static RingStream * stashRingStream;
|
||||
|
||||
static int16_t stashP[MAX_COMMAND_PARAMS];
|
||||
bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
|
||||
static void callback_W(int16_t result);
|
||||
static void callback_B(int16_t result);
|
||||
static void callback_R(int16_t result);
|
||||
static void callback_Rloco(int16_t result);
|
||||
static void callback_Wloco(int16_t result);
|
||||
static void callback_Vbit(int16_t result);
|
||||
static void callback_Vbyte(int16_t result);
|
||||
static bool stashBusy;
|
||||
|
||||
static Print * stashStream;
|
||||
static int stashP[MAX_PARAMS];
|
||||
bool stashCallback(Print * stream, int p[MAX_PARAMS]);
|
||||
static void callback_W(int result);
|
||||
static void callback_B(int result);
|
||||
static void callback_R(int result);
|
||||
static void callback_Rloco(int result);
|
||||
static void callback_Vbit(int result);
|
||||
static void callback_Vbyte(int result);
|
||||
static FILTER_CALLBACK filterCallback;
|
||||
static FILTER_CALLBACK filterRMFTCallback;
|
||||
static AT_COMMAND_CALLBACK atCommandCallback;
|
||||
static void funcmap(int16_t cab, byte value, byte fstart, byte fstop);
|
||||
static void funcmap(int cab, byte value, byte fstart, byte fstop);
|
||||
|
||||
};
|
||||
|
||||
|
207
DCCTimer.cpp
207
DCCTimer.cpp
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* © 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
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
#elif defined(TEENSYDUINO)
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
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]) {
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
#else
|
||||
read_mac(mac);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
void DCCTimer::read_mac(byte mac[6]) {
|
||||
read(0xe,mac,0);
|
||||
read(0xf,mac,3);
|
||||
}
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
#endif
|
||||
|
||||
#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);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
20
DCCTimer.h
20
DCCTimer.h
@@ -1,20 +0,0 @@
|
||||
#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);
|
||||
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||
static void read_mac(byte mac[6]);
|
||||
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||
#endif
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
230
DCCWaveform.cpp
230
DCCWaveform.cpp
@@ -17,64 +17,67 @@
|
||||
* 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"
|
||||
#include "freeMemory.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);
|
||||
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
VirtualTimer * DCCWaveform::interruptTimer=NULL;
|
||||
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
|
||||
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);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
&& (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
// Only use PWM if both pins are PWM capable. Otherwise JOIN does not work
|
||||
MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable();
|
||||
if (MotorDriver::usePWM)
|
||||
DIAG(F("Signal pin config: high accuracy waveform"));
|
||||
else
|
||||
DIAG(F("Signal pin config: normal accuracy waveform"));
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
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"));
|
||||
}
|
||||
|
||||
void DCCWaveform::loop(bool ackManagerActive) {
|
||||
mainTrack.checkPowerOverload(false);
|
||||
progTrack.checkPowerOverload(ackManagerActive);
|
||||
void DCCWaveform::loop() {
|
||||
mainTrack.checkPowerOverload();
|
||||
progTrack.checkPowerOverload();
|
||||
}
|
||||
|
||||
|
||||
// static //
|
||||
void DCCWaveform::interruptHandler() {
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
// 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();
|
||||
bool mainCall2 = mainTrack.interrupt1();
|
||||
bool progCall2 = progTrack.interrupt1();
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -89,12 +92,13 @@ 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 = WAVE_START;
|
||||
state = 0;
|
||||
// The +1 below is to allow the preamble generator to create the stop bit
|
||||
// for the previous packet.
|
||||
// fpr the previous packet.
|
||||
requiredPreambles = preambleBits+1;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
@@ -108,17 +112,24 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
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();
|
||||
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
|
||||
if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
|
||||
tripValue=progTripValue;
|
||||
|
||||
switch (powerMode) {
|
||||
@@ -127,26 +138,8 @@ void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
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("*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||
} else {
|
||||
DIAG(F("*** %S FAULT PIN ACTIVE - OVERLOAD ***"), isMainTrack ? F("MAIN") : F("PROG"));
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = motorDriver->getCurrentRaw();
|
||||
if (lastCurrent <= tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
@@ -156,9 +149,9 @@ void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
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("*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
@@ -169,48 +162,73 @@ void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPowerMode(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("*** %S TRACK POWER RESET delay=%d ***"), 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};
|
||||
|
||||
// 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};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 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 state = 2;
|
||||
break;
|
||||
case 2: // 116us after case 0
|
||||
setSignal(LOW);
|
||||
state = 3;
|
||||
break;
|
||||
case 3: // finished sending zero bit
|
||||
state = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// ACK check is prog track only and will only be checked if
|
||||
// this is not case(0) which needs relatively expensive packet change code to be called.
|
||||
if (ackPending) checkAck();
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void DCCWaveform::setSignal(bool high) {
|
||||
if (progTrackSyncMain) {
|
||||
if (!isMainTrack) return; // ignore PROG track waveform while in sync
|
||||
// set both tracks to same signal
|
||||
motorDriver->setSignal(high);
|
||||
progTrack.motorDriver->setSignal(high);
|
||||
return;
|
||||
}
|
||||
motorDriver->setSignal(high);
|
||||
}
|
||||
|
||||
void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
// set state WAVE_MID_1 for a 1=bit
|
||||
// or WAVE_HIGH_0 for a 0 bit.
|
||||
// set currentBit to be the next bit to be sent.
|
||||
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
currentBit = true;
|
||||
remainingPreambles--;
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
updateMinimumFreeMemory(22);
|
||||
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
|
||||
state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0;
|
||||
currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
|
||||
bits_sent++;
|
||||
|
||||
// If this is the last bit of a byte, prepare for the next byte
|
||||
@@ -230,10 +248,7 @@ void DCCWaveform::interrupt2() {
|
||||
}
|
||||
else if (packetPending) {
|
||||
// Copy pending packet to transmit packet
|
||||
// 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));
|
||||
|
||||
for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
@@ -254,15 +269,14 @@ void DCCWaveform::interrupt2() {
|
||||
|
||||
// Wait until there is no packet pending, then make this pending
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||
if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
|
||||
while (packetPending);
|
||||
|
||||
byte checksum = 0;
|
||||
for (byte b = 0; b < byteCount; b++) {
|
||||
for (int b = 0; b < byteCount; b++) {
|
||||
checksum ^= buffer[b];
|
||||
pendingPacket[b] = buffer[b];
|
||||
}
|
||||
// buffer is MAX_PACKET_SIZE but pendingPacket is one bigger
|
||||
pendingPacket[byteCount] = checksum;
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
@@ -270,17 +284,20 @@ 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("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %dus and %dus"),
|
||||
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA threshold=%d/%dmA"),
|
||||
baseline,motorDriver->raw2mA(baseline),
|
||||
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
ackThreshold,motorDriver->raw2mA(ackThreshold));
|
||||
}
|
||||
|
||||
void DCCWaveform::setAckPending() {
|
||||
@@ -295,7 +312,7 @@ void DCCWaveform::setAckPending() {
|
||||
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
if (Diag::ACK) DIAG(F("\nACK-%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("OK"):F("FAIL"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
@@ -303,17 +320,18 @@ 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;
|
||||
}
|
||||
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
|
||||
// An ACK is a pulse lasting between MIN_ACK_PULSE_DURATION and MAX_ACK_PULSE_DURATION uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (lastCurrent>ackThreshold) {
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
@@ -324,7 +342,7 @@ void DCCWaveform::checkAck() {
|
||||
// detected trailing edge of pulse
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
if (ackPulseDuration>=MIN_ACK_PULSE_DURATION && ackPulseDuration<=MAX_ACK_PULSE_DURATION) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
|
@@ -19,29 +19,30 @@
|
||||
*/
|
||||
#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;
|
||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// Ack time thresholds. Unit: microseconds
|
||||
const int MIN_ACK_PULSE_DURATION = 2000;
|
||||
const int MAX_ACK_PULSE_DURATION = 8500;
|
||||
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
|
||||
// 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 : byte { OFF, ON, OVERLOAD };
|
||||
enum class POWERMODE { OFF, ON, OVERLOAD };
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
@@ -49,37 +50,17 @@ const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
class DCCWaveform {
|
||||
public:
|
||||
DCCWaveform( byte preambleBits, bool isMain);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||
static void loop(bool ackManagerActive);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
|
||||
static void setDiagnosticSlowWave(bool slow);
|
||||
static void loop();
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
|
||||
void beginTrack();
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
void checkPowerOverload(bool ackManagerActive);
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||
return 0;
|
||||
}
|
||||
inline int getCurrentmA() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return motorDriver->raw2mA(lastCurrent);
|
||||
return 0;
|
||||
}
|
||||
inline int getMaxmA() {
|
||||
if (maxmA == 0) { //only calculate this for first request, it doesn't change
|
||||
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
|
||||
}
|
||||
return maxmA;
|
||||
}
|
||||
inline int getTripmA() {
|
||||
if (tripmA == 0) { //only calculate this for first request, it doesn't change
|
||||
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
|
||||
}
|
||||
return tripmA;
|
||||
}
|
||||
void checkPowerOverload();
|
||||
int getLastCurrent();
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
@@ -95,49 +76,36 @@ class DCCWaveform {
|
||||
autoPowerOff=false;
|
||||
}
|
||||
};
|
||||
inline bool canMeasureCurrent() {
|
||||
return motorDriver->canMeasureCurrent();
|
||||
};
|
||||
inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
// 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 VirtualTimer * interruptTimer;
|
||||
static void interruptHandler();
|
||||
bool interrupt1();
|
||||
void interrupt2();
|
||||
void checkAck();
|
||||
void setSignal(bool high);
|
||||
|
||||
bool isMainTrack;
|
||||
MotorDriver* motorDriver;
|
||||
// Transmission controller
|
||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte transmitPacket[MAX_PACKET_SIZE]; // packet being transmitted
|
||||
byte transmitLength;
|
||||
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
|
||||
WAVE_STATE state; // wave generator state machine
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte state; // wave generator state machine
|
||||
|
||||
byte pendingPacket[MAX_PACKET_SIZE];
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
int lastCurrent;
|
||||
static int progTripValue;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
int lastCurrent;
|
||||
|
||||
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
@@ -160,9 +128,6 @@ class DCCWaveform {
|
||||
|
||||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 2000; // micros
|
||||
unsigned int maxAckPulseDuration = 8500; // micros
|
||||
|
||||
};
|
||||
#endif
|
||||
|
2
DIAG.h
2
DIAG.h
@@ -18,8 +18,8 @@
|
||||
*/
|
||||
#ifndef DIAG_h
|
||||
#define DIAG_h
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define DIAG StringFormatter::diag
|
||||
#define LCD StringFormatter::lcd
|
||||
#endif
|
||||
|
24
EEStore.cpp
24
EEStore.cpp
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* © 2013-2016 Gregg E. Berman
|
||||
* © 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/>.
|
||||
*/
|
||||
#include "EEStore.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
@@ -94,10 +74,10 @@ int EEStore::pointer(){
|
||||
|
||||
void EEStore::dump(int num) {
|
||||
byte b;
|
||||
DIAG(F("Addr 0x char"));
|
||||
DIAG(F("\nAddr 0x char\n"));
|
||||
for (int n=0 ; n<num; n++) {
|
||||
EEPROM.get(n, b);
|
||||
DIAG(F("%d %x %c"),n,b,isprint(b) ? b : ' ');
|
||||
DIAG(F("%d %x %c\n"),n,b,isascii(b) ? b : ' ');
|
||||
}
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#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;
|
||||
/**
|
||||
* @brief Setup Ethernet Connection
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
singleton=new EthernetInterface();
|
||||
if (!singleton->connected) singleton=NULL;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Aquire IP Address from DHCP and start server
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
EthernetInterface::EthernetInterface()
|
||||
{
|
||||
byte mac[6];
|
||||
DCCTimer::getSimulatedMacAddress(mac);
|
||||
connected=false;
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
Ethernet.begin(mac, IP_ADDRESS);
|
||||
#else
|
||||
if (Ethernet.begin(mac) == 0)
|
||||
{
|
||||
DIAG(F("Ethernet.begin FAILED"));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
DIAG(F("begin OK."));
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found"));
|
||||
return;
|
||||
}
|
||||
if (Ethernet.linkStatus() == LinkOFF) {
|
||||
DIAG(F("Ethernet cable not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
connected=true;
|
||||
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
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"), IP_PORT);
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main loop for the EthernetInterface
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
if (!singleton) return;
|
||||
|
||||
switch (Ethernet.maintain())
|
||||
{
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("Ethernet Error: renewed fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Ethernet Error: rebind fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
|
||||
singleton->loop2();
|
||||
|
||||
}
|
||||
|
||||
void EthernetInterface::loop2()
|
||||
{
|
||||
// get client from the server
|
||||
EthernetClient client = server->accept();
|
||||
|
||||
// check for new client
|
||||
if (client)
|
||||
{
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: New client "));
|
||||
byte socket;
|
||||
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (!clients[socket])
|
||||
{
|
||||
// On accept() the EthernetServer doesn't track the client anymore
|
||||
// so we store it in our client array
|
||||
if (Diag::ETHERNET) DIAG(F("Socket %d"),socket);
|
||||
clients[socket] = client;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW"));
|
||||
}
|
||||
|
||||
// check for incoming data from all possible clients
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (clients[socket]) {
|
||||
|
||||
int available=clients[socket].available();
|
||||
if (available > 0) {
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: available socket=%d,avail=%d"), socket, available);
|
||||
// read bytes from a client
|
||||
int count = clients[socket].read(buffer, MAX_ETH_BUFFER);
|
||||
buffer[count] = '\0'; // terminate the string properly
|
||||
if (Diag::ETHERNET) DIAG(F(",count=%d:%e"), socket,buffer);
|
||||
// execute with data going directly back
|
||||
outboundRing->mark(socket);
|
||||
CommandDistributor::parse(socket,buffer,outboundRing);
|
||||
outboundRing->commit();
|
||||
return; // limit the amount of processing that takes place within 1 loop() cycle.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop any clients which disconnect
|
||||
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
|
||||
if (clients[socket] && !clients[socket].connected()) {
|
||||
clients[socket].stop();
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
}
|
||||
|
||||
// handle at most 1 outbound transmission
|
||||
int socketOut=outboundRing->read();
|
||||
if (socketOut>=0) {
|
||||
int count=outboundRing->count();
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
|
||||
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
|
||||
clients[socketOut].flush(); //maybe
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX/CommandStation-EX
|
||||
*
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Ethernet Interface added by Gregor Baues
|
||||
*/
|
||||
|
||||
#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 <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
#else
|
||||
#include "Ethernet.h"
|
||||
#endif
|
||||
#include "RingStream.h"
|
||||
|
||||
/**
|
||||
* @brief Network Configuration
|
||||
*
|
||||
*/
|
||||
|
||||
#define MAX_ETH_BUFFER 512
|
||||
#define OUTBOUND_RING_SIZE 2048
|
||||
|
||||
class EthernetInterface {
|
||||
|
||||
public:
|
||||
|
||||
static void setup();
|
||||
static void loop();
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
void loop2();
|
||||
EthernetServer * server;
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * outboundRing;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
122
EthernetSetup.cpp
Normal file
122
EthernetSetup.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
#include "NetworkSetup.h"
|
||||
#include "EthernetSetup.h"
|
||||
|
||||
byte EthernetSetup::setup()
|
||||
{
|
||||
INFO(F("Initialize MAC Address ..."));
|
||||
NetworkSetup::genMacAddress();
|
||||
|
||||
INFO(F("Initialize Ethernet with DHCP"));
|
||||
if (Ethernet.begin(mac) == 0)
|
||||
{
|
||||
WARN(F("Failed to configure Ethernet using DHCP ... Trying with fixed IP"));
|
||||
Ethernet.begin(mac, IPAddress(IP_ADDRESS)); // default ip address
|
||||
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
{
|
||||
ERR(F("Ethernet shield was not found. Sorry, can't run without hardware. :("));
|
||||
return 0;
|
||||
};
|
||||
if (Ethernet.linkStatus() == LinkOFF)
|
||||
{
|
||||
ERR(F("Ethernet cable is not connected."));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
maxConnections = MAX_SOCK_NUM;
|
||||
|
||||
if (Ethernet.hardwareStatus() == EthernetW5100)
|
||||
{
|
||||
INFO(F("W5100 Ethernet controller detected."));
|
||||
maxConnections = 4; // Max supported officaly by the W5100 but i have been running over 8 as well. Perf has to be evaluated though comparing 4 vs. 8 connections
|
||||
}
|
||||
else if (Ethernet.hardwareStatus() == EthernetW5200)
|
||||
{
|
||||
INFO(F("W5200 Ethernet controller detected."));
|
||||
maxConnections = 8;
|
||||
}
|
||||
else if (Ethernet.hardwareStatus() == EthernetW5500)
|
||||
{
|
||||
INFO(F("W5500 Ethernet controller detected."));
|
||||
maxConnections = 8;
|
||||
}
|
||||
|
||||
INFO(F("Network Protocol: [%s]"), protocol ? "UDP" : "TCP");
|
||||
switch (protocol)
|
||||
{
|
||||
case UDPR:
|
||||
{
|
||||
udp = new EthernetUDP();
|
||||
byte udpState = udp->begin(port);
|
||||
if (udpState)
|
||||
{
|
||||
TRC(F("UDP status: %d"), udpState);
|
||||
maxConnections = 1; // there is only one UDP object listening for incomming data
|
||||
connected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR(F("UDP failed to start"));
|
||||
connected = false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
case TCP:
|
||||
{
|
||||
server = new EthernetServer(port);
|
||||
server->begin();
|
||||
connected = true;
|
||||
break;
|
||||
};
|
||||
case MQTT:
|
||||
{
|
||||
// do the MQTT setup stuff ...
|
||||
};
|
||||
default:
|
||||
{
|
||||
ERR(F("\nUnkown Ethernet protocol; Setup failed"));
|
||||
connected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (connected)
|
||||
{
|
||||
ip = Ethernet.localIP();
|
||||
NetworkSetup::printMacAddress(NetworkSetup::mac);
|
||||
INFO(F("Local IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]);
|
||||
INFO(F("Listening on port: [%d]"), port);
|
||||
dnsip = Ethernet.dnsServerIP();
|
||||
INFO(F("DNS server IP address: [%d.%d.%d.%d] "), dnsip[0], dnsip[1], dnsip[2], dnsip[3]);
|
||||
INFO(F("Number of connections: [%d]"), maxConnections);
|
||||
return true;
|
||||
}
|
||||
return false; // something went wrong
|
||||
}
|
||||
|
||||
EthernetSetup::EthernetSetup() {}
|
||||
EthernetSetup::EthernetSetup(uint16_t p, protocolType pt ) { port = p; protocol = pt; }
|
||||
EthernetSetup::~EthernetSetup() {}
|
||||
|
48
EthernetSetup.h
Normal file
48
EthernetSetup.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef EthernetSetup_h
|
||||
#define EthernetSetup_h
|
||||
|
||||
#include <Ethernet.h>
|
||||
#include "NetworkSetup.h"
|
||||
|
||||
class EthernetSetup: public NetworkSetup {
|
||||
|
||||
private:
|
||||
|
||||
EthernetServer* server = 0;
|
||||
EthernetUDP* udp = 0;
|
||||
|
||||
public:
|
||||
|
||||
byte setup(); // sets the TCP server or UDP udp object; returns 1 if the connection was successfull 0 otherwise
|
||||
EthernetServer *getTCPServer() {
|
||||
return server;
|
||||
}
|
||||
EthernetUDP *getUDPServer() {
|
||||
return udp;
|
||||
}
|
||||
|
||||
EthernetSetup();
|
||||
EthernetSetup(uint16_t port, protocolType protocol);
|
||||
~EthernetSetup();
|
||||
};
|
||||
|
||||
#endif
|
30
FSH.h
30
FSH.h
@@ -1,30 +0,0 @@
|
||||
#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 "e7e8e84"
|
||||
#define GITHUB_SHA "9db6d36"
|
||||
|
432
HttpRequest.cpp
Normal file
432
HttpRequest.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
/*
|
||||
* © 2012 Francisco G. Paletta, © 2020 Gregor Baues. 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 "Arduino.h"
|
||||
#include "HttpRequest.h"
|
||||
#include "NetworkInterface.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
// public interface to the parsed request
|
||||
// static ParsedRequest req;
|
||||
|
||||
HttpRequest::HttpRequest()
|
||||
{
|
||||
resetRequest();
|
||||
req.method = method;
|
||||
req.uri = uri;
|
||||
req.version = version;
|
||||
req.paramCount = ¶mCount;
|
||||
/**
|
||||
* @todo add list of parameters
|
||||
*
|
||||
*/
|
||||
}
|
||||
|
||||
ParsedRequest HttpRequest::getParsedRequest()
|
||||
{
|
||||
return req;
|
||||
}
|
||||
|
||||
void HttpRequest::resetRequest()
|
||||
{
|
||||
|
||||
freeParamMem(firstParam);
|
||||
freeCookieMem(firstCookie);
|
||||
|
||||
parseStatus = HTTP_PARSE_INIT;
|
||||
method[0] = '\0';
|
||||
uri[0] = '\0';
|
||||
version[0] = '\0';
|
||||
firstParam = NULL;
|
||||
firstCookie = NULL;
|
||||
paramCount = 0;
|
||||
cookieCount = 0;
|
||||
tmpParamName[0] = '\0';
|
||||
tmpParamValue[0] = '\0';
|
||||
tmpAttribName[0] = '\0';
|
||||
tmpAttribValue[0] = '\0';
|
||||
tmpCookieName[0] = '\0';
|
||||
tmpCookieValue[0] = '\0';
|
||||
dataBlockLength = 0;
|
||||
dataCount = 0;
|
||||
}
|
||||
|
||||
void HttpRequest::freeParamMem(Params *paramNode)
|
||||
{
|
||||
if (paramNode != NULL)
|
||||
{
|
||||
freeParamMem(paramNode->next);
|
||||
delete paramNode;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::freeCookieMem(Cookies *cookieNode)
|
||||
{
|
||||
if (cookieNode != NULL)
|
||||
{
|
||||
freeCookieMem(cookieNode->next);
|
||||
delete cookieNode;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpRequest::parseRequest(char c)
|
||||
{
|
||||
|
||||
char cStr[2];
|
||||
cStr[0] = c;
|
||||
cStr[1] = '\0';
|
||||
switch (parseStatus)
|
||||
{
|
||||
|
||||
case HTTP_METHOD:
|
||||
if (c == ' ')
|
||||
parseStatus = HTTP_URI;
|
||||
else if (strlen(method) < HTTP_REQ_METHOD_LENGTH - 1)
|
||||
strcat(method, cStr);
|
||||
break;
|
||||
|
||||
case HTTP_URI:
|
||||
// DIAG(F("HTTP_URI: %c\n"), c);
|
||||
if (c == ' ')
|
||||
parseStatus = HTTP_VERSION;
|
||||
else if (c == '?')
|
||||
parseStatus = HTTP_GET_NAME;
|
||||
else if (strlen(uri) < HTTP_REQ_URI_LENGTH - 1)
|
||||
strcat(uri, cStr);
|
||||
break;
|
||||
|
||||
case HTTP_GET_NAME:
|
||||
// DIAG(F("HTTP_GET_NAME: %c\n"), c);
|
||||
if (c == ' ')
|
||||
parseStatus = HTTP_VERSION;
|
||||
else if (c != '=')
|
||||
{
|
||||
if (strlen(tmpParamName) < HTTP_REQ_PARAM_NAME_LENGTH - 1)
|
||||
strcat(tmpParamName, cStr);
|
||||
}
|
||||
else
|
||||
parseStatus = HTTP_GET_VALUE;
|
||||
break;
|
||||
|
||||
case HTTP_GET_VALUE:
|
||||
// DIAG(F("HTTP_GET_VALUE: %c\n"), c);
|
||||
if (c == '&')
|
||||
{
|
||||
addParam();
|
||||
parseStatus = HTTP_GET_NAME;
|
||||
}
|
||||
else if (c == ' ')
|
||||
{
|
||||
addParam();
|
||||
parseStatus = HTTP_VERSION;
|
||||
}
|
||||
else if (strlen(tmpParamValue) < HTTP_REQ_PARAM_VALUE_LENGTH - 1)
|
||||
strcat(tmpParamValue, cStr);
|
||||
break;
|
||||
|
||||
case HTTP_VERSION:
|
||||
// DIAG(F("HTTP_VERSION: %c\n"), c);
|
||||
if (c == '\n')
|
||||
parseStatus = HTTP_NEW_LINE;
|
||||
else if (c != '\r' && strlen(version) < HTTP_REQ_VERSION_LENGTH - 1)
|
||||
strcat(version, cStr);
|
||||
break;
|
||||
|
||||
case HTTP_NEW_LINE:
|
||||
// DIAG(F("HTTP_NEW_LINE: %c\n"), c);
|
||||
if (c != '\r' && c != '\n')
|
||||
{
|
||||
parseStatus = HTTP_ATTRIB_NAME;
|
||||
tmpAttribName[0] = '\0';
|
||||
tmpAttribValue[0] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strcmp(method, "POST") == 0 && dataBlockLength > 0)
|
||||
parseStatus = HTTP_POST_NAME;
|
||||
else
|
||||
// DIAG(F("HTTP_REQUEST_END: %c\n"), c);
|
||||
parseStatus = HTTP_REQUEST_END;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_ATTRIB_NAME:
|
||||
// DIAG(F("HTTP_ATTRIB_NAME: %c\n"), c);
|
||||
if (c == '\n')
|
||||
parseStatus = HTTP_NEW_LINE;
|
||||
else if (c != ':' && c != ' ' && c != '\r')
|
||||
{
|
||||
if (strlen(tmpAttribName) < HTTP_REQ_ATTRIB_NAME_LENGTH - 1)
|
||||
strcat(tmpAttribName, cStr);
|
||||
}
|
||||
else if (c == ' ')
|
||||
{
|
||||
if (strcmp(tmpAttribName, "Cookie") == 0)
|
||||
parseStatus = HTTP_COOKIE_NAME;
|
||||
else
|
||||
parseStatus = HTTP_ATTRIB_VALUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_ATTRIB_VALUE:
|
||||
// DIAG(F("HTTP_ATTRIB_VALUE: %c\n"), c);
|
||||
if (c == '\n')
|
||||
{
|
||||
addAttrib();
|
||||
parseStatus = HTTP_NEW_LINE;
|
||||
}
|
||||
else if (c != '\r')
|
||||
if (strlen(tmpAttribValue) < HTTP_REQ_ATTRIB_VALUE_LENGTH - 1)
|
||||
strcat(tmpAttribValue, cStr);
|
||||
break;
|
||||
|
||||
case HTTP_POST_NAME:
|
||||
dataCount++;
|
||||
if (c != '=')
|
||||
{
|
||||
if (strlen(tmpParamName) < HTTP_REQ_PARAM_NAME_LENGTH - 1)
|
||||
strcat(tmpParamName, cStr);
|
||||
}
|
||||
else
|
||||
parseStatus = HTTP_POST_VALUE;
|
||||
if (dataCount > dataBlockLength)
|
||||
{
|
||||
addParam();
|
||||
// DIAG(F("HTTP_REQUEST_END: %c\n"), c);
|
||||
parseStatus = HTTP_REQUEST_END;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_POST_VALUE:
|
||||
dataCount++;
|
||||
if (c == '&')
|
||||
{
|
||||
addParam();
|
||||
parseStatus = HTTP_POST_NAME;
|
||||
}
|
||||
else if (strlen(tmpParamValue) < HTTP_REQ_PARAM_VALUE_LENGTH - 1)
|
||||
strcat(tmpParamValue, cStr);
|
||||
if (dataCount > dataBlockLength)
|
||||
{
|
||||
addParam();
|
||||
// DIAG(F("HTTP_REQUEST_END: %c\n"), c);
|
||||
parseStatus = HTTP_REQUEST_END;
|
||||
}
|
||||
break;
|
||||
|
||||
case HTTP_COOKIE_NAME:
|
||||
if (c == '\n')
|
||||
parseStatus = HTTP_NEW_LINE;
|
||||
else if (c != '=')
|
||||
{
|
||||
if (c != ' ' && strlen(tmpCookieName) < HTTP_REQ_COOKIE_NAME_LENGTH - 1)
|
||||
strcat(tmpCookieName, cStr);
|
||||
}
|
||||
else
|
||||
parseStatus = HTTP_COOKIE_VALUE;
|
||||
break;
|
||||
|
||||
case HTTP_COOKIE_VALUE:
|
||||
if (c == ';')
|
||||
{
|
||||
addCookie();
|
||||
parseStatus = HTTP_COOKIE_NAME;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
addCookie();
|
||||
parseStatus = HTTP_NEW_LINE;
|
||||
}
|
||||
else if (c != '\r' && c != ' ' && strlen(tmpCookieValue) < HTTP_REQ_COOKIE_VALUE_LENGTH - 1)
|
||||
strcat(tmpCookieValue, cStr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpRequest::endOfRequest()
|
||||
{
|
||||
if (parseStatus == HTTP_REQUEST_END)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void HttpRequest::addParam()
|
||||
{
|
||||
|
||||
Params **cursor;
|
||||
|
||||
cursor = &firstParam;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
if (strcmp((*cursor)->name, tmpParamName) == 0)
|
||||
break;
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
if ((*cursor) == NULL)
|
||||
{
|
||||
// DIAG(F("New Param: %s\n"), tmpParamName);
|
||||
(*cursor) = new Params;
|
||||
strcpy((*cursor)->name, tmpParamName);
|
||||
strcpy((*cursor)->value, tmpParamValue);
|
||||
(*cursor)->next = NULL;
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
tmpParamName[0] = '\0';
|
||||
tmpParamValue[0] = '\0';
|
||||
}
|
||||
|
||||
void HttpRequest::addCookie()
|
||||
{
|
||||
|
||||
Cookies **cursor;
|
||||
|
||||
cursor = &firstCookie;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
if (strcmp((*cursor)->name, tmpCookieName) == 0)
|
||||
break;
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
if ((*cursor) == NULL)
|
||||
{
|
||||
(*cursor) = new Cookies;
|
||||
strcpy((*cursor)->name, tmpCookieName);
|
||||
strcpy((*cursor)->value, tmpCookieValue);
|
||||
(*cursor)->next = NULL;
|
||||
cookieCount++;
|
||||
}
|
||||
|
||||
tmpCookieName[0] = '\0';
|
||||
tmpCookieValue[0] = '\0';
|
||||
}
|
||||
|
||||
void HttpRequest::addAttrib()
|
||||
{
|
||||
|
||||
if (strcmp(tmpAttribName, "Content-Length") == 0)
|
||||
dataBlockLength = atoi(tmpAttribValue);
|
||||
|
||||
tmpAttribName[0] = '\0';
|
||||
tmpAttribValue[0] = '\0';
|
||||
}
|
||||
|
||||
uint8_t HttpRequest::getParam(uint8_t paramNum, char *name, char *value)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
|
||||
Params **cursor;
|
||||
|
||||
cursor = &firstParam;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
i++;
|
||||
if (i == paramNum)
|
||||
{
|
||||
strcpy(name, (*cursor)->name);
|
||||
strcpy(value, (*cursor)->value);
|
||||
break;
|
||||
}
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
Params* HttpRequest::getParam(uint8_t paramNum)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
|
||||
Params **cursor;
|
||||
|
||||
cursor = &firstParam;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
i++;
|
||||
if (i == paramNum)
|
||||
{
|
||||
break;
|
||||
}
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
return *cursor;
|
||||
}
|
||||
|
||||
uint8_t HttpRequest::getParam(char *name, char *value)
|
||||
{
|
||||
|
||||
uint8_t pos = 0, i = 0;
|
||||
Params **cursor;
|
||||
|
||||
cursor = &firstParam;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
i++;
|
||||
if (strcmp((*cursor)->name, name) == 0)
|
||||
{
|
||||
strcpy(value, (*cursor)->value);
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
uint8_t HttpRequest::getCookie(uint8_t cookieNum, char *name, char *value)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
|
||||
Cookies **cursor;
|
||||
|
||||
cursor = &firstCookie;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
i++;
|
||||
if (i == cookieNum)
|
||||
{
|
||||
strcpy(name, (*cursor)->name);
|
||||
strcpy(value, (*cursor)->value);
|
||||
break;
|
||||
}
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
uint8_t HttpRequest::getCookie(char *name, char *value)
|
||||
{
|
||||
|
||||
uint8_t pos = 0, i = 0;
|
||||
Cookies **cursor;
|
||||
|
||||
cursor = &firstCookie;
|
||||
while ((*cursor) != NULL)
|
||||
{
|
||||
i++;
|
||||
if (strcmp((*cursor)->name, name) == 0)
|
||||
{
|
||||
strcpy(value, (*cursor)->value);
|
||||
pos = i;
|
||||
break;
|
||||
}
|
||||
cursor = &((*cursor)->next);
|
||||
}
|
||||
return pos;
|
||||
}
|
123
HttpRequest.h
Normal file
123
HttpRequest.h
Normal file
@@ -0,0 +1,123 @@
|
||||
|
||||
/*
|
||||
* © 2012 Francisco G. Paletta, © 2020 Gregor Baues. 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/>.
|
||||
*/
|
||||
|
||||
#ifndef HttpRequest_h
|
||||
#define HttpRequest_h
|
||||
|
||||
#include "Ethernet.h"
|
||||
|
||||
// Buffer lengths
|
||||
#define HTTP_REQ_METHOD_LENGTH 10 //10 is enough
|
||||
#define HTTP_REQ_URI_LENGTH 32 //adjust if you have long path/file names
|
||||
#define HTTP_REQ_VERSION_LENGTH 10 //10 is enough
|
||||
#define HTTP_REQ_PARAM_NAME_LENGTH 16 //adjust to meet your needs
|
||||
#define HTTP_REQ_PARAM_VALUE_LENGTH 16 //adjust to meet your needs
|
||||
#define HTTP_REQ_ATTRIB_NAME_LENGTH 16 //enough to track attribute name
|
||||
#define HTTP_REQ_ATTRIB_VALUE_LENGTH 16 //enough to track "Content-Length" value
|
||||
#define HTTP_REQ_COOKIE_NAME_LENGTH 10 //adjust to meet your needs
|
||||
#define HTTP_REQ_COOKIE_VALUE_LENGTH 16 //adjust to meet your needs
|
||||
|
||||
// Parsing status
|
||||
#define HTTP_PARSE_INIT 0 //Initial Parser Status
|
||||
#define HTTP_METHOD 0 //Parse the Method: GET POST UPDATE etc
|
||||
#define HTTP_URI 1 //Parse the URI
|
||||
#define HTTP_GET_NAME 11 //Parse the GET parameter NAME
|
||||
#define HTTP_GET_VALUE 12 //Parse the GET parameter VALUE
|
||||
#define HTTP_VERSION 2 //Parse the version: HTTP1.1
|
||||
#define HTTP_NEW_LINE 3 //Starts reading a new line
|
||||
#define HTTP_ATTRIB_NAME 41 //Read the attibutes NAME
|
||||
#define HTTP_ATTRIB_VALUE 42 //Read the attribute VALUE
|
||||
#define HTTP_POST_NAME 51 //Read the POST parameter NAME
|
||||
#define HTTP_POST_VALUE 52 //Read the POST paramenter VALUE
|
||||
#define HTTP_COOKIE_NAME 61 //Read the COOKIE NAME
|
||||
#define HTTP_COOKIE_VALUE 62 //Read the COOKIE VALUE
|
||||
#define HTTP_REQUEST_END 99 //Finished reading the HTTP Request
|
||||
|
||||
// returned to the callback
|
||||
struct ParsedRequest {
|
||||
char* method;
|
||||
char* uri;
|
||||
char* version;
|
||||
uint8_t* paramCount;
|
||||
// uint8_t (*getByIndex)(int, char*, char*);
|
||||
// uint8_t (*getByName)(char*, char*);
|
||||
};
|
||||
|
||||
struct Params
|
||||
{
|
||||
char name[HTTP_REQ_PARAM_NAME_LENGTH];
|
||||
char value[HTTP_REQ_PARAM_VALUE_LENGTH];
|
||||
Params *next;
|
||||
};
|
||||
|
||||
class HttpRequest
|
||||
{
|
||||
private:
|
||||
|
||||
// no Cookies
|
||||
struct Cookies
|
||||
{
|
||||
char name[HTTP_REQ_COOKIE_NAME_LENGTH];
|
||||
char value[HTTP_REQ_COOKIE_VALUE_LENGTH];
|
||||
Cookies *next;
|
||||
};
|
||||
|
||||
uint8_t parseStatus;
|
||||
Params *firstParam;
|
||||
Cookies *firstCookie;
|
||||
|
||||
char tmpParamName[HTTP_REQ_PARAM_NAME_LENGTH];
|
||||
char tmpParamValue[HTTP_REQ_PARAM_VALUE_LENGTH];
|
||||
char tmpAttribName[HTTP_REQ_ATTRIB_NAME_LENGTH];
|
||||
char tmpAttribValue[HTTP_REQ_ATTRIB_NAME_LENGTH];
|
||||
char tmpCookieName[HTTP_REQ_COOKIE_NAME_LENGTH]; // no use
|
||||
char tmpCookieValue[HTTP_REQ_COOKIE_VALUE_LENGTH]; // no use
|
||||
|
||||
uint16_t dataBlockLength, dataCount;
|
||||
|
||||
void addParam();
|
||||
void addAttrib();
|
||||
void addCookie(); // no use
|
||||
void freeParamMem(Params *paramNode);
|
||||
void freeCookieMem(Cookies *cookieNode);
|
||||
|
||||
char method[HTTP_REQ_METHOD_LENGTH]; // user
|
||||
char uri[HTTP_REQ_URI_LENGTH]; // user
|
||||
char version[HTTP_REQ_VERSION_LENGTH]; // user
|
||||
|
||||
uint8_t paramCount; // user
|
||||
uint8_t cookieCount; // no use - no cookie support
|
||||
|
||||
uint8_t getParam(uint8_t paramNum, char *name, char *value); // user
|
||||
uint8_t getParam(char *name, char *value); // user
|
||||
uint8_t getCookie(uint8_t cookieNum, char *name, char *value); // no use
|
||||
uint8_t getCookie(char *name, char *value); // no use
|
||||
|
||||
ParsedRequest req;
|
||||
|
||||
public:
|
||||
HttpRequest();
|
||||
void resetRequest();
|
||||
void parseRequest(char c);
|
||||
bool endOfRequest();
|
||||
ParsedRequest getParsedRequest();
|
||||
Params* getParam(uint8_t paramNum);
|
||||
void (* callback)(ParsedRequest *req, Client *client);
|
||||
};
|
||||
|
||||
#endif
|
129
I2CManager.cpp
129
I2CManager.cpp
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <Wire.h>
|
||||
#include "I2CManager.h"
|
||||
|
||||
// If not already initialised, initialise I2C (wire).
|
||||
void I2CManagerClass::begin(void) {
|
||||
if (!_beginCompleted) {
|
||||
Wire.begin();
|
||||
_beginCompleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set clock speed to the lowest requested one. If none requested,
|
||||
// the Wire default is 100kHz.
|
||||
void I2CManagerClass::setClock(uint32_t speed) {
|
||||
if (speed < _clockSpeed && !_clockSpeedFixed) {
|
||||
_clockSpeed = speed;
|
||||
Wire.setClock(_clockSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
// Force clock speed to that specified. It can then only
|
||||
// be overridden by calling Wire.setClock directly.
|
||||
void I2CManagerClass::forceClock(uint32_t speed) {
|
||||
if (!_clockSpeedFixed) {
|
||||
_clockSpeed = speed;
|
||||
_clockSpeedFixed = true;
|
||||
Wire.setClock(_clockSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if specified I2C address is responding.
|
||||
// Returns 0 if OK, or error code.
|
||||
uint8_t I2CManagerClass::checkAddress(uint8_t address) {
|
||||
begin();
|
||||
Wire.beginTransmission(address);
|
||||
return Wire.endTransmission();
|
||||
}
|
||||
|
||||
bool I2CManagerClass::exists(uint8_t address) {
|
||||
return checkAddress(address)==0;
|
||||
}
|
||||
|
||||
// Write a complete transmission to I2C using a supplied buffer of data
|
||||
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size) {
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(buffer, size);
|
||||
return Wire.endTransmission();
|
||||
}
|
||||
|
||||
// Write a complete transmission to I2C using a supplied buffer of data in Flash
|
||||
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size) {
|
||||
uint8_t ramBuffer[size];
|
||||
memcpy_P(ramBuffer, buffer, size);
|
||||
return write(address, ramBuffer, size);
|
||||
}
|
||||
|
||||
|
||||
// Write a complete transmission to I2C using a list of data
|
||||
uint8_t I2CManagerClass::write(uint8_t address, int nBytes, ...) {
|
||||
uint8_t buffer[nBytes];
|
||||
va_list args;
|
||||
va_start(args, nBytes);
|
||||
for (uint8_t i=0; i<nBytes; i++)
|
||||
buffer[i] = va_arg(args, int);
|
||||
va_end(args);
|
||||
return write(address, buffer, nBytes);
|
||||
}
|
||||
|
||||
// Write a command and read response, returns number of bytes received.
|
||||
// Different modules use different ways of accessing registers:
|
||||
// PCF8574 I/O expander justs needs the address (no data);
|
||||
// PCA9685 needs a two byte command to select the register(s) to be read;
|
||||
// MCP23016 needs a one-byte command to select the register.
|
||||
// Some devices use 8-bit registers exclusively and some have 16-bit registers.
|
||||
// Therefore the following function is general purpose, to apply to any
|
||||
// type of I2C device.
|
||||
//
|
||||
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t writeBuffer[], uint8_t writeSize) {
|
||||
if (writeSize > 0) {
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(writeBuffer, writeSize);
|
||||
Wire.endTransmission(false); // Don't free bus yet
|
||||
}
|
||||
Wire.requestFrom(address, readSize);
|
||||
uint8_t nBytes = 0;
|
||||
while (Wire.available() && nBytes < readSize)
|
||||
readBuffer[nBytes++] = Wire.read();
|
||||
return nBytes;
|
||||
}
|
||||
|
||||
// Overload of read() to allow command to be specified as a series of bytes.
|
||||
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t writeSize, ...) {
|
||||
va_list args;
|
||||
// Copy the series of bytes into an array.
|
||||
va_start(args, writeSize);
|
||||
uint8_t writeBuffer[writeSize];
|
||||
for (uint8_t i=0; i<writeSize; i++)
|
||||
writeBuffer[i] = va_arg(args, int);
|
||||
va_end(args);
|
||||
return read(address, readBuffer, readSize, writeBuffer, writeSize);
|
||||
}
|
||||
|
||||
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize) {
|
||||
return read(address, readBuffer, readSize, NULL, 0);
|
||||
}
|
||||
|
||||
I2CManagerClass I2CManager = I2CManagerClass();
|
76
I2CManager.h
76
I2CManager.h
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef I2CManager_h
|
||||
#define I2CManager_h
|
||||
|
||||
#include "FSH.h"
|
||||
|
||||
/*
|
||||
* Helper class to manage access to the I2C 'Wire' subsystem.
|
||||
*
|
||||
* Helps to avoid calling Wire.begin() multiple times (which is not)
|
||||
* entirely benign as it reinitialises).
|
||||
*
|
||||
* Also helps to avoid the Wire clock from being set, by another device
|
||||
* driver, to a speed which is higher than a device supports.
|
||||
*
|
||||
* Thirdly, it provides a convenient way to check whether there is a
|
||||
* device on a particular I2C address.
|
||||
*/
|
||||
|
||||
class I2CManagerClass {
|
||||
|
||||
public:
|
||||
|
||||
I2CManagerClass() {}
|
||||
|
||||
// If not already initialised, initialise I2C (wire).
|
||||
void begin(void);
|
||||
// Set clock speed to the lowest requested one.
|
||||
void setClock(uint32_t speed);
|
||||
// Force clock speed
|
||||
void forceClock(uint32_t speed);
|
||||
// Check if specified I2C address is responding.
|
||||
uint8_t checkAddress(uint8_t address);
|
||||
bool exists(uint8_t address);
|
||||
// Write a complete transmission to I2C from an array in RAM
|
||||
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||
// Write a complete transmission to I2C from an array in Flash
|
||||
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||
// Write a transmission to I2C from a list of bytes.
|
||||
uint8_t write(uint8_t address, int nBytes, ...);
|
||||
// Write a command from an array in RAM and read response
|
||||
uint8_t read(uint8_t address, uint8_t writeBuffer[], uint8_t writeSize,
|
||||
uint8_t readBuffer[], uint8_t readSize);
|
||||
// Write a command from an arbitrary list of bytes and read response
|
||||
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t writeSize, ...);
|
||||
// Write a null command and read the response.
|
||||
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize);
|
||||
|
||||
private:
|
||||
bool _beginCompleted = false;
|
||||
bool _clockSpeedFixed = false;
|
||||
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
|
||||
};
|
||||
|
||||
extern I2CManagerClass I2CManager;
|
||||
|
||||
#endif
|
191
LCDDisplay.cpp
191
LCDDisplay.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
@@ -17,146 +17,63 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// CAUTION: the device dependent parts of this class are created in the .ini
|
||||
// using LCD_Implementation.h
|
||||
|
||||
/* The strategy for drawing the screen is as follows.
|
||||
* 1) There are up to eight rows of text to be displayed.
|
||||
* 2) Blank rows of text are ignored.
|
||||
* 3) If there are more non-blank rows than screen lines,
|
||||
* then all of the rows are displayed, with the rest of the
|
||||
* screen being blank.
|
||||
* 4) If there are fewer non-blank rows than screen lines,
|
||||
* then a scrolling strategy is adopted so that, on each screen
|
||||
* refresh, a different subset of the rows is presented.
|
||||
* 5) On each entry into loop2(), a single operation is sent to the
|
||||
* screen; this may be a position command or a character for
|
||||
* display. This spreads the onerous work of updating the screen
|
||||
* and ensures that other loop() functions in the application are
|
||||
* not held up significantly. The exception to this is when
|
||||
* the loop2() function is called with force=true, where
|
||||
* a screen update is executed to completion. This is normally
|
||||
* only done during start-up.
|
||||
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
|
||||
* in the config.h.
|
||||
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||
* #define SCROLLMODE 1 is by page (alternate between pages),
|
||||
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||
|
||||
*/
|
||||
|
||||
// CAUTION: the device dependent parts of this class are created in the .ini using LCD_Implementation.h
|
||||
#include "LCDDisplay.h"
|
||||
|
||||
void LCDDisplay::clear() {
|
||||
clearNative();
|
||||
for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0';
|
||||
topRow = -1; // loop2 will fill from row 0
|
||||
}
|
||||
void LCDDisplay::clear() {
|
||||
clearNative();
|
||||
for (byte row=0;row<MAX_LCD_ROWS; row++) rowBuffer[row][0]='\0';
|
||||
topRow=-1; // loop2 will fill from row 0
|
||||
}
|
||||
|
||||
void LCDDisplay::setRow(byte line) {
|
||||
hotRow = line;
|
||||
hotCol = 0;
|
||||
}
|
||||
void LCDDisplay::setRow(byte line) {
|
||||
hotRow=line;
|
||||
hotCol=0;
|
||||
}
|
||||
|
||||
size_t LCDDisplay::write(uint8_t b) {
|
||||
if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1;
|
||||
rowBuffer[hotRow][hotCol] = b;
|
||||
hotCol++;
|
||||
rowBuffer[hotRow][hotCol] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LCDDisplay::loop() {
|
||||
if (!lcdDisplay) return;
|
||||
lcdDisplay->loop2(false);
|
||||
}
|
||||
|
||||
LCDDisplay *LCDDisplay::loop2(bool force) {
|
||||
if (!lcdDisplay) return NULL;
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (!force) {
|
||||
// See if we're in the time between updates
|
||||
if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME)
|
||||
return NULL;
|
||||
} else {
|
||||
// force full screen update from the beginning.
|
||||
rowFirst = -1;
|
||||
rowNext = 0;
|
||||
bufferPointer = 0;
|
||||
done = false;
|
||||
slot = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
if (bufferPointer == 0) {
|
||||
// Find a line of data to write to the screen.
|
||||
if (rowFirst < 0) rowFirst = rowNext;
|
||||
skipBlankRows();
|
||||
if (!done) {
|
||||
// Non-blank line found, so copy it.
|
||||
for (uint8_t i = 0; i < sizeof(buffer); i++)
|
||||
buffer[i] = rowBuffer[rowNext][i];
|
||||
} else
|
||||
buffer[0] = '\0'; // Empty line
|
||||
setRowNative(slot); // Set position for display
|
||||
charIndex = 0;
|
||||
bufferPointer = &buffer[0];
|
||||
|
||||
} else {
|
||||
|
||||
// Write next character, or a space to erase current position.
|
||||
char ch = *bufferPointer;
|
||||
if (ch) {
|
||||
writeNative(ch);
|
||||
bufferPointer++;
|
||||
} else
|
||||
writeNative(' ');
|
||||
|
||||
if (++charIndex >= MAX_LCD_COLS) {
|
||||
// Screen slot completed, move to next slot on screen
|
||||
slot++;
|
||||
bufferPointer = 0;
|
||||
if (!done) {
|
||||
moveToNextRow();
|
||||
skipBlankRows();
|
||||
}
|
||||
if (hotRow>=MAX_LCD_ROWS || hotCol>=MAX_LCD_COLS) return -1;
|
||||
rowBuffer[hotRow][hotCol]=b;
|
||||
hotCol++;
|
||||
rowBuffer[hotRow][hotCol]=0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LCDDisplay::loop() {
|
||||
if (!lcdDisplay) return;
|
||||
lcdDisplay->loop2(false);
|
||||
}
|
||||
|
||||
LCDDisplay* LCDDisplay::loop2(bool force) {
|
||||
if ((!force) && (millis() - lastScrollTime)< LCD_SCROLL_TIME) return NULL;
|
||||
lastScrollTime=millis();
|
||||
clearNative();
|
||||
int rowFirst=nextFilledRow();
|
||||
if (rowFirst<0)return NULL; // No filled rows
|
||||
setRowNative(0);
|
||||
writeNative(rowBuffer[rowFirst]);
|
||||
for (int slot=1;slot<lcdRows;slot++) {
|
||||
int rowNext=nextFilledRow();
|
||||
if (rowNext==rowFirst){
|
||||
// we have wrapped around and not filled the screen
|
||||
topRow=-1; // start again at first row next time.
|
||||
break;
|
||||
}
|
||||
setRowNative(slot);
|
||||
writeNative(rowBuffer[rowNext]);
|
||||
}
|
||||
displayNative();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (slot >= lcdRows) {
|
||||
// Last slot finished, reset ready for next screen update.
|
||||
#if SCROLLMODE==2
|
||||
if (!done) {
|
||||
// On next refresh, restart one row on from previous start.
|
||||
rowNext = rowFirst;
|
||||
moveToNextRow();
|
||||
skipBlankRows();
|
||||
}
|
||||
#endif
|
||||
done = false;
|
||||
slot = 0;
|
||||
rowFirst = -1;
|
||||
lastScrollTime = currentMillis;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} while (force);
|
||||
int LCDDisplay::nextFilledRow() {
|
||||
for (int rx=1;rx<=MAX_LCD_ROWS;rx++) {
|
||||
topRow++;
|
||||
topRow %= MAX_LCD_ROWS;
|
||||
if (rowBuffer[topRow][0]) return topRow;
|
||||
}
|
||||
return -1; // No slots filled
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void LCDDisplay::moveToNextRow() {
|
||||
rowNext = (rowNext + 1) % MAX_LCD_ROWS;
|
||||
#if SCROLLMODE == 1
|
||||
// Finished if we've looped back to row 0
|
||||
if (rowNext == 0) done = true;
|
||||
#else
|
||||
// Finished if we're back to the first one shown
|
||||
if (rowNext == rowFirst) done = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void LCDDisplay::skipBlankRows() {
|
||||
while (!done && rowBuffer[rowNext][0] == 0)
|
||||
moveToNextRow();
|
||||
}
|
||||
|
||||
|
||||
|
87
LCDDisplay.h
87
LCDDisplay.h
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
@@ -20,61 +20,44 @@
|
||||
#define LCDDisplay_h
|
||||
#include <Arduino.h>
|
||||
|
||||
#if __has_include ( "config.h")
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
// Allow maximum message length to be overridden from config.h
|
||||
#if !defined(MAX_MSG_SIZE)
|
||||
#define MAX_MSG_SIZE 16
|
||||
#endif
|
||||
|
||||
// This class is created in LCDisplay_Implementation.h
|
||||
|
||||
class LCDDisplay : public Print {
|
||||
public:
|
||||
static const int MAX_LCD_ROWS = 8;
|
||||
static const int MAX_LCD_COLS = MAX_MSG_SIZE;
|
||||
static const long LCD_SCROLL_TIME = 3000; // 3 seconds
|
||||
|
||||
static LCDDisplay* lcdDisplay;
|
||||
LCDDisplay();
|
||||
void interfake(int p1, int p2, int p3);
|
||||
public:
|
||||
static const int MAX_LCD_ROWS=8;
|
||||
static const int MAX_LCD_COLS=16;
|
||||
static const long LCD_SCROLL_TIME=3000; // 3 seconds
|
||||
|
||||
static LCDDisplay* lcdDisplay;
|
||||
LCDDisplay();
|
||||
void interfake(int p1, int p2, int p3);
|
||||
|
||||
// Internally handled functions
|
||||
static void loop();
|
||||
LCDDisplay* loop2(bool force);
|
||||
void setRow(byte line);
|
||||
void clear();
|
||||
|
||||
virtual size_t write(uint8_t b);
|
||||
using Print::write;
|
||||
|
||||
private:
|
||||
void moveToNextRow();
|
||||
void skipBlankRows();
|
||||
|
||||
// Relay functions to the live driver
|
||||
void clearNative();
|
||||
void displayNative();
|
||||
void setRowNative(byte line);
|
||||
void writeNative(char b);
|
||||
|
||||
unsigned long lastScrollTime = 0;
|
||||
int8_t hotRow = 0;
|
||||
int8_t hotCol = 0;
|
||||
int8_t topRow = 0;
|
||||
uint8_t lcdRows;
|
||||
uint8_t lcdCols;
|
||||
int8_t slot = 0;
|
||||
int8_t rowFirst = -1;
|
||||
int8_t rowNext = 0;
|
||||
int8_t charIndex = 0;
|
||||
char buffer[MAX_LCD_COLS + 1];
|
||||
char* bufferPointer = 0;
|
||||
bool done = false;
|
||||
|
||||
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
|
||||
// Internally handled functions
|
||||
static void loop();
|
||||
LCDDisplay* loop2(bool force);
|
||||
void setRow(byte line);
|
||||
void clear();
|
||||
|
||||
virtual size_t write(uint8_t b);
|
||||
using Print::write;
|
||||
|
||||
private:
|
||||
int nextFilledRow();
|
||||
|
||||
// Relay functions to the live driver
|
||||
void clearNative();
|
||||
void displayNative();
|
||||
void setRowNative(byte line);
|
||||
void writeNative(char * b);
|
||||
|
||||
unsigned long lastScrollTime=0;
|
||||
int hotRow=0;
|
||||
int hotCol=0;
|
||||
int topRow=0;
|
||||
int lcdRows;
|
||||
void renderRow(byte row);
|
||||
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS+1];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -22,11 +22,10 @@
|
||||
//
|
||||
// It will create a driver implemntation and a shim class implementation.
|
||||
// This means that other classes can reference the shim without knowing
|
||||
// which library is involved.
|
||||
// which libraray is involved.
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef LCD_Implementation_h
|
||||
#define LCD_Implementation_h
|
||||
#include "config.h"
|
||||
#include <Wire.h>
|
||||
#include "LCDDisplay.h"
|
||||
|
||||
@@ -49,7 +48,8 @@ LCDDisplay * LCDDisplay::lcdDisplay=0;
|
||||
|
||||
#else
|
||||
#include "LCD_NONE.h"
|
||||
#define CONDITIONAL_LCD_START if (true) /* NO LCD CONFIG, but do the LCD macros to get DIAGS */
|
||||
#define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */
|
||||
#endif
|
||||
|
||||
|
||||
#endif // LCD_Implementation_h
|
||||
|
||||
|
12
LCD_LCD.h
12
LCD_LCD.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -16,7 +16,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
#include <LiquidCrystal_I2C.h>
|
||||
LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows
|
||||
// DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER
|
||||
LCDDisplay::LCDDisplay() {
|
||||
@@ -28,6 +28,10 @@
|
||||
}
|
||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; lcdRows=p3; }
|
||||
void LCDDisplay::clearNative() {LCDDriver.clear();}
|
||||
void LCDDisplay::setRowNative(byte row) { LCDDriver.setCursor(0, row); }
|
||||
void LCDDisplay::writeNative(char b){ LCDDriver.write(b); }
|
||||
void LCDDisplay::setRowNative(byte row) {
|
||||
LCDDriver.setCursor(0, row);
|
||||
LCDDriver.print(F(" "));
|
||||
LCDDriver.setCursor(0, row);
|
||||
}
|
||||
void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); }
|
||||
void LCDDisplay::displayNative() { LCDDriver.display(); }
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -22,6 +22,6 @@
|
||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;}
|
||||
void LCDDisplay::setRowNative(byte row) { (void)row;}
|
||||
void LCDDisplay::clearNative() {}
|
||||
void LCDDisplay::writeNative(char b){ (void)b;} //
|
||||
void LCDDisplay::writeNative(char * b){ (void)b;} //
|
||||
void LCDDisplay::displayNative(){}
|
||||
|
||||
|
80
LCD_OLED.h
80
LCD_OLED.h
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* © 2020, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
@@ -17,57 +17,41 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// OLED Implementation of LCDDisplay class
|
||||
// Note: this file is optionally included by LCD_Implementation.h
|
||||
// It is NOT a .cpp file to prevent it being compiled and demanding libraries
|
||||
// even when not needed.
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "SSD1306Ascii.h"
|
||||
SSD1306AsciiWire LCDDriver;
|
||||
|
||||
// OLED Implementation of LCDDisplay class
|
||||
// Note: this file is optionally included by LCD_Implenentation.h
|
||||
// It is NOT a .cpp file to prevent it being compiled and demanding libraraies even when not needed.
|
||||
|
||||
#include <Adafruit_SSD1306.h>
|
||||
Adafruit_SSD1306 LCDDriver(OLED_DRIVER);
|
||||
|
||||
// DEVICE SPECIFIC LCDDisplay Implementation for OLED
|
||||
|
||||
LCDDisplay::LCDDisplay() {
|
||||
// Scan for device on 0x3c and 0x3d.
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(400000L); // Set max supported I2C speed
|
||||
for (byte address = 0x3c; address <= 0x3d; address++) {
|
||||
if (I2CManager.exists(address)) {
|
||||
// Device found
|
||||
DIAG(F("OLED display found at 0x%x"), address);
|
||||
interfake(OLED_DRIVER, 0);
|
||||
const DevType *devType;
|
||||
if (lcdCols == 132)
|
||||
devType = &SH1106_128x64; // Actually 132x64 but treated as 128x64
|
||||
else if (lcdCols == 128 && lcdRows == 4)
|
||||
devType = &Adafruit128x32;
|
||||
else
|
||||
devType = &Adafruit128x64;
|
||||
LCDDriver.begin(devType, address);
|
||||
lcdDisplay = this;
|
||||
LCDDriver.setFont(System5x7); // Normal 1:1 pixel scale, 8 bits high
|
||||
if(LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3C) || LCDDriver.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
|
||||
DIAG(F("\nOLED display found"));
|
||||
delay(2000); // painful Adafruit splash pants!
|
||||
lcdDisplay=this;
|
||||
LCDDriver.setTextSize(1); // Normal 1:1 pixel scale
|
||||
LCDDriver.setTextColor(SSD1306_WHITE); // Draw white text
|
||||
interfake(OLED_DRIVER,0);
|
||||
clear();
|
||||
return;
|
||||
return;
|
||||
}
|
||||
DIAG(F("\nOLED display not found\n"));
|
||||
}
|
||||
}
|
||||
DIAG(F("OLED display not found"));
|
||||
}
|
||||
|
||||
void LCDDisplay::interfake(int p1, int p2, int p3) {
|
||||
lcdCols = p1;
|
||||
lcdRows = p2 / 8;
|
||||
(void)p3;
|
||||
}
|
||||
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; lcdRows=p2/8; (void)p3;}
|
||||
|
||||
void LCDDisplay::clearNative() { LCDDriver.clear(); }
|
||||
void LCDDisplay::clearNative() {LCDDriver.clearDisplay();}
|
||||
|
||||
void LCDDisplay::setRowNative(byte row) {
|
||||
// Positions text write to start of row 1..n
|
||||
int y = row;
|
||||
LCDDriver.setCursor(0, y);
|
||||
}
|
||||
|
||||
void LCDDisplay::writeNative(char b) { LCDDriver.write(b); }
|
||||
|
||||
void LCDDisplay::displayNative() {}
|
||||
void LCDDisplay::setRowNative(byte row) {
|
||||
// Positions text write to start of row 1..n and clears previous text
|
||||
int y=8*row;
|
||||
LCDDriver.fillRect(0, y, LCDDriver.width(), 8, SSD1306_BLACK);
|
||||
LCDDriver.setCursor(0, y);
|
||||
}
|
||||
|
||||
void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); }
|
||||
|
||||
void LCDDisplay::displayNative() { LCDDriver.display(); }
|
||||
|
||||
|
74
LCN.cpp
74
LCN.cpp
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "LCN.h"
|
||||
#include "DIAG.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
|
||||
int LCN::id = 0;
|
||||
Stream * LCN::stream=NULL;
|
||||
bool LCN::firstLoop=true;
|
||||
|
||||
void LCN::init(Stream & lcnstream) {
|
||||
stream=&lcnstream;
|
||||
DIAG(F("LCN connection setup"));
|
||||
}
|
||||
|
||||
|
||||
// Inbound LCN traffic is postfix notation... nnnX where nnn is an id, X is the opcode
|
||||
void LCN::loop() {
|
||||
if (!stream) return;
|
||||
if (firstLoop) {
|
||||
firstLoop=false;
|
||||
stream->println('X');
|
||||
return;
|
||||
}
|
||||
|
||||
while (stream->available()) {
|
||||
int ch = stream->read();
|
||||
if (ch >= 0 && ch <= '9') { // accumulate id value
|
||||
id = 10 * id + ch - '0';
|
||||
}
|
||||
else if (ch == 't' || ch == 'T') { // Turnout opcodes
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
Turnout * tt = Turnout::get(id);
|
||||
if (!tt) Turnout::create(id, LCN_TURNOUT_ADDRESS, 0);
|
||||
if (ch == 't') tt->data.tStatus |= STATUS_ACTIVE;
|
||||
else tt->data.tStatus &= ~STATUS_ACTIVE;
|
||||
Turnout::turnoutlistHash++; // signals ED update of turnout data
|
||||
id = 0;
|
||||
}
|
||||
else if (ch == 'S' || ch == 's') {
|
||||
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
|
||||
Sensor * ss = Sensor::get(id);
|
||||
if (!ss) ss = Sensor::create(id, 255,0); // impossible pin
|
||||
ss->active = ch == 'S';
|
||||
id = 0;
|
||||
}
|
||||
else id = 0; // ignore any other garbage from LCN
|
||||
}
|
||||
}
|
||||
|
||||
void LCN::send(char opcode, int id, bool state) {
|
||||
if (stream) {
|
||||
StringFormatter::send(stream,F("%c/%d/%d"), opcode, id , state);
|
||||
if (Diag::LCN) DIAG(F("LCN OUT %c/%d/%d"), opcode, id , state);
|
||||
}
|
||||
}
|
16
LCN.h
16
LCN.h
@@ -1,16 +0,0 @@
|
||||
#ifndef LCN_h
|
||||
#define LCN_h
|
||||
#include <Arduino.h>
|
||||
|
||||
class LCN {
|
||||
public:
|
||||
static void init(Stream & lcnstream);
|
||||
static void loop();
|
||||
static void send(char opcode, int id, bool state);
|
||||
private :
|
||||
static bool firstLoop;
|
||||
static Stream * stream;
|
||||
static int id;
|
||||
};
|
||||
|
||||
#endif
|
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation-EX. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
#include "I2CManager.h"
|
||||
|
||||
// When the display powers up, it is configured as follows:
|
||||
//
|
||||
// 1. Display clear
|
||||
// 2. Function set:
|
||||
// DL = 1; 8-bit interface data
|
||||
// N = 0; 1-line display
|
||||
// F = 0; 5x8 dot character font
|
||||
// 3. Display on/off control:
|
||||
// D = 0; Display off
|
||||
// C = 0; Cursor off
|
||||
// B = 0; Blinking off
|
||||
// 4. Entry mode set:
|
||||
// I/D = 1; Increment by 1
|
||||
// S = 0; No shift
|
||||
//
|
||||
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||
// can't assume that its in that state when a sketch starts (and the
|
||||
// LiquidCrystal constructor is called).
|
||||
|
||||
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols,
|
||||
uint8_t lcd_rows) {
|
||||
_Addr = lcd_Addr;
|
||||
_cols = lcd_cols;
|
||||
_rows = lcd_rows;
|
||||
_backlightval = LCD_NOBACKLIGHT;
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::init() { init_priv(); }
|
||||
|
||||
void LiquidCrystal_I2C::init_priv() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
|
||||
|
||||
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
begin(_cols, _rows);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines) {
|
||||
if (lines > 1) {
|
||||
_displayfunction |= LCD_2LINE;
|
||||
}
|
||||
_numlines = lines;
|
||||
(void)cols; // Suppress compiler warning.
|
||||
|
||||
// according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||
// before sending commands. Arduino can turn on way befer 4.5V so we'll allow
|
||||
// 100 milliseconds after pulling both RS and R/W and backlight pin low
|
||||
expanderWrite(
|
||||
_backlightval); // reset expander and turn backlight off (Bit 8 =1)
|
||||
delay(100);
|
||||
|
||||
// put the LCD into 4 bit mode
|
||||
// this is according to the hitachi HD44780 datasheet
|
||||
// figure 24, pg 46
|
||||
|
||||
// we start in 8bit mode, try to set 4 bit mode
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// second try
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
|
||||
// third go!
|
||||
write4bits(0x03 << 4);
|
||||
delayMicroseconds(150);
|
||||
|
||||
// finally, set to 4-bit interface
|
||||
write4bits(0x02 << 4);
|
||||
|
||||
// set # lines, font size, etc.
|
||||
command(LCD_FUNCTIONSET | _displayfunction);
|
||||
|
||||
// turn the display on with no cursor or blinking default
|
||||
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
display();
|
||||
|
||||
// clear it off
|
||||
clear();
|
||||
|
||||
// Initialize to default text direction (for roman languages)
|
||||
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||
|
||||
// set the entry mode
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
|
||||
setCursor(0, 0);
|
||||
}
|
||||
|
||||
/********** high level commands, for the user! */
|
||||
void LiquidCrystal_I2C::clear() {
|
||||
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes 1.52ms
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row) {
|
||||
int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
|
||||
if (row > _numlines) {
|
||||
row = _numlines - 1; // we count rows starting w/0
|
||||
}
|
||||
command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
|
||||
}
|
||||
|
||||
// Turn the display on/off (quickly)
|
||||
void LiquidCrystal_I2C::noDisplay() {
|
||||
_displaycontrol &= ~LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::display() {
|
||||
_displaycontrol |= LCD_DISPLAYON;
|
||||
command(LCD_DISPLAYCONTROL | _displaycontrol);
|
||||
}
|
||||
|
||||
// Turn the (optional) backlight off/on
|
||||
void LiquidCrystal_I2C::noBacklight(void) {
|
||||
_backlightval = LCD_NOBACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::backlight(void) {
|
||||
_backlightval = LCD_BACKLIGHT;
|
||||
expanderWrite(0);
|
||||
}
|
||||
|
||||
size_t LiquidCrystal_I2C::write(uint8_t value) {
|
||||
send(value, Rs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*********** mid level commands, for sending data/cmds */
|
||||
|
||||
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||
send(value, 0);
|
||||
}
|
||||
|
||||
/************ low level data pushing commands **********/
|
||||
|
||||
/* According to the NXP Datasheet for the PCF8574 section 8.2:
|
||||
* "The master (microcontroller) sends the START condition and slave address
|
||||
* setting the last bit of the address byte to logic 0 for the write mode.
|
||||
* The PCF8574/74A acknowledges and the master then sends the data byte for
|
||||
* P7 to P0 to the port register. As the clock line goes HIGH, the 8-bit
|
||||
* data is presented on the port lines after it has been acknowledged by the
|
||||
* PCF8574/74A. [...] The master can then send a STOP or ReSTART condition
|
||||
* or continue sending data. The number of data bytes that can be sent
|
||||
* successively is not limited and the previous data is overwritten every
|
||||
* time a data byte has been sent and acknowledged."
|
||||
*
|
||||
* This driver takes advantage of this by sending multiple data bytes in succession
|
||||
* within a single I2C transmission. With a fast clock rate of 400kHz, the time
|
||||
* between successive updates of the PCF8574 outputs will be at least 2.5us. With
|
||||
* the default clock rate of 100kHz the time between updates will be at least 10us.
|
||||
*
|
||||
* The LCD controller HD44780, according to its datasheet, needs nominally 37us
|
||||
* (up to 50us) to execute a command (i.e. write to gdram, reposition, etc.). Each
|
||||
* command is sent in a separate I2C transmission here. The time taken to end a
|
||||
* transmission and start another one is a stop bit, a start bit, 8 address bits,
|
||||
* an ack, 8 data bits and another ack; this is at least 20 bits, i.e. >50us
|
||||
* at 400kHz and >200us at 100kHz. Therefore, we don't need additional delay.
|
||||
*
|
||||
* Similarly, the Enable must be set/reset for at least 450ns. This is
|
||||
* well within the I2C clock cycle time of 2.5us at 400kHz. Data is clocked in
|
||||
* to the HD44780 on the trailing edge of the Enable pin, so we set the Enable
|
||||
* as we present the data, then in the next byte we reset Enable without changing
|
||||
* the data.
|
||||
*/
|
||||
|
||||
// write either command or data (8 bits) to the HD44780 LCD controller as
|
||||
// a single I2C transmission.
|
||||
void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||
mode |= _backlightval;
|
||||
uint8_t highnib = (value & 0xf0) | mode;
|
||||
uint8_t lownib = ((value << 4) & 0xf0) | mode;
|
||||
// Send both nibbles
|
||||
byte buffer[] = {(byte)(highnib|En), highnib, (byte)(lownib|En), lownib};
|
||||
I2CManager.write(_Addr, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
// write 4 bits to the HD44780 LCD controller.
|
||||
void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||
uint8_t _data = value | _backlightval;
|
||||
// Enable must be set/reset for at least 450ns. This is well within the
|
||||
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
|
||||
// HD44780 on the trailing edge of the Enable pin.
|
||||
byte buffer[] = {(byte)(_data|En), _data};
|
||||
I2CManager.write(_Addr, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
// write a byte to the PCF8574 I2C interface. We don't need to set
|
||||
// the enable pin for this.
|
||||
void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
|
||||
I2CManager.write(_Addr, 1, value | _backlightval);
|
||||
}
|
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* Based on the work by DFRobot, Frank de Brabander and Marco Schwartz.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LiquidCrystal_I2C_h
|
||||
#define LiquidCrystal_I2C_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// commands
|
||||
#define LCD_CLEARDISPLAY 0x01
|
||||
#define LCD_RETURNHOME 0x02
|
||||
#define LCD_ENTRYMODESET 0x04
|
||||
#define LCD_DISPLAYCONTROL 0x08
|
||||
#define LCD_CURSORSHIFT 0x10
|
||||
#define LCD_FUNCTIONSET 0x20
|
||||
#define LCD_SETCGRAMADDR 0x40
|
||||
#define LCD_SETDDRAMADDR 0x80
|
||||
|
||||
// flags for display entry mode
|
||||
#define LCD_ENTRYRIGHT 0x00
|
||||
#define LCD_ENTRYLEFT 0x02
|
||||
#define LCD_ENTRYSHIFTINCREMENT 0x01
|
||||
#define LCD_ENTRYSHIFTDECREMENT 0x00
|
||||
|
||||
// flags for display on/off control
|
||||
#define LCD_DISPLAYON 0x04
|
||||
#define LCD_DISPLAYOFF 0x00
|
||||
#define LCD_CURSORON 0x02
|
||||
#define LCD_CURSOROFF 0x00
|
||||
#define LCD_BLINKON 0x01
|
||||
#define LCD_BLINKOFF 0x00
|
||||
|
||||
// flags for display/cursor shift
|
||||
#define LCD_DISPLAYMOVE 0x08
|
||||
#define LCD_CURSORMOVE 0x00
|
||||
#define LCD_MOVERIGHT 0x04
|
||||
#define LCD_MOVELEFT 0x00
|
||||
|
||||
// flags for function set
|
||||
#define LCD_8BITMODE 0x10
|
||||
#define LCD_4BITMODE 0x00
|
||||
#define LCD_2LINE 0x08
|
||||
#define LCD_1LINE 0x00
|
||||
#define LCD_5x10DOTS 0x04
|
||||
#define LCD_5x8DOTS 0x00
|
||||
|
||||
// flags for backlight control
|
||||
#define LCD_BACKLIGHT 0x08
|
||||
#define LCD_NOBACKLIGHT 0x00
|
||||
|
||||
#define En 0b00000100 // Enable bit
|
||||
#define Rw 0b00000010 // Read/Write bit
|
||||
#define Rs 0b00000001 // Register select bit
|
||||
|
||||
class LiquidCrystal_I2C : public Print {
|
||||
public:
|
||||
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||
void begin(uint8_t cols, uint8_t rows);
|
||||
void clear();
|
||||
void noDisplay();
|
||||
void display();
|
||||
void noBacklight();
|
||||
void backlight();
|
||||
|
||||
void setCursor(uint8_t, uint8_t);
|
||||
virtual size_t write(uint8_t);
|
||||
void command(uint8_t);
|
||||
void init();
|
||||
|
||||
private:
|
||||
void init_priv();
|
||||
void send(uint8_t, uint8_t);
|
||||
void write4bits(uint8_t);
|
||||
void expanderWrite(uint8_t);
|
||||
uint8_t _Addr;
|
||||
uint8_t _displayfunction;
|
||||
uint8_t _displaycontrol;
|
||||
uint8_t _displaymode;
|
||||
uint8_t _numlines;
|
||||
uint8_t _cols;
|
||||
uint8_t _rows;
|
||||
uint8_t _backlightval;
|
||||
};
|
||||
|
||||
#endif
|
98
MemStream.cpp
Normal file
98
MemStream.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
|
||||
(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
Normal file
78
MemStream.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
|
||||
(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
|
178
MotorDriver.cpp
178
MotorDriver.cpp
@@ -18,153 +18,62 @@
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "AnalogReadFast.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))
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define WritePin digitalWrite
|
||||
#define ReadPin digitalRead
|
||||
#else
|
||||
// use the DIO2 libraray for much faster pin access
|
||||
#define GPIO2_PREFER_SPEED 1
|
||||
#include <DIO2.h> // use IDE menu Tools..Manage Libraries to locate and install DIO2
|
||||
#define WritePin digitalWrite2
|
||||
#define ReadPin digitalRead2
|
||||
#endif
|
||||
|
||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||
powerPin=power_pin;
|
||||
getFastPin(F("POWER"),powerPin,fastPowerPin);
|
||||
powerPin=power_pin;
|
||||
signalPin=signal_pin;
|
||||
signalPin2=signal_pin2;
|
||||
brakePin=brake_pin;
|
||||
currentPin=current_pin;
|
||||
senseFactor=sense_factor;
|
||||
faultPin=fault_pin;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
|
||||
pinMode(powerPin, OUTPUT);
|
||||
|
||||
signalPin=signal_pin;
|
||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||
pinMode(brakePin, OUTPUT);
|
||||
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;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
pinMode(currentPin, INPUT);
|
||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (currentPin==UNUSED_PIN)
|
||||
DIAG(F("MotorDriver ** WARNING ** No current or short detection"));
|
||||
else
|
||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurentTripValue(relative to offset)=%d"),
|
||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||
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 (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);
|
||||
}
|
||||
else setLOW(fastPowerPin);
|
||||
WritePin(powerPin, on ? HIGH : LOW);
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
// voltage from the motor bride one needs to do a
|
||||
// setBrake(false).
|
||||
// If the brakePin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake) and setBrake does
|
||||
// compensate for that.
|
||||
//
|
||||
void MotorDriver::setBrake(bool on) {
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (on ^ invertBrake) setHIGH(fastBrakePin);
|
||||
else setLOW(fastBrakePin);
|
||||
void MotorDriver::setBrake( bool on) {
|
||||
WritePin(brakePin, on ? HIGH : LOW);
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
WritePin(signalPin, high ? HIGH : LOW);
|
||||
if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH);
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
volatile unsigned int overflow_count=0;
|
||||
#endif
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_PIN;
|
||||
}
|
||||
/*
|
||||
* 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, create a little and return -1 in that case.
|
||||
*
|
||||
* senseOffset handles the case where a shield returns values above or below
|
||||
* a central value depending on direction.
|
||||
*/
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
bool irq = disableInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
enableInterrupts(irq);
|
||||
#elif defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
unsigned char sreg_backup;
|
||||
sreg_backup = SREG; /* save interrupt enable/disable state */
|
||||
cli();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
overflow_count = 0;
|
||||
SREG = sreg_backup; /* restore interrupt state */
|
||||
#else
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
#endif
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
return current;
|
||||
if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH)
|
||||
return (int)(32000/senseFactor);
|
||||
|
||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
// so analogReadFast is used here. (-2uS)
|
||||
return analogReadFast(currentPin);
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
@@ -173,16 +82,3 @@ 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("MotorDriver %S Pin=%d,"),type,pin);
|
||||
(void) type; // avoid compiler warning if diag not used above.
|
||||
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"),port, result.inout,input,result.maskHIGH);
|
||||
}
|
||||
|
@@ -18,32 +18,11 @@
|
||||
*/
|
||||
#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
|
||||
|
||||
#if defined(__IMXRT1062__)
|
||||
struct FASTPIN {
|
||||
volatile uint32_t *inout;
|
||||
uint32_t maskHIGH;
|
||||
uint32_t maskLOW;
|
||||
};
|
||||
#else
|
||||
struct FASTPIN {
|
||||
volatile uint8_t *inout;
|
||||
uint8_t maskHIGH;
|
||||
uint8_t maskLOW;
|
||||
};
|
||||
#endif
|
||||
|
||||
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);
|
||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
virtual void setPower( bool on);
|
||||
virtual void setSignal( bool high);
|
||||
virtual void setBrake( bool on);
|
||||
@@ -51,38 +30,15 @@ class MotorDriver {
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
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;
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
|
||||
private:
|
||||
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
|
||||
byte powerPin, signalPin, signalPin2, brakePin,currentPin,faultPin;
|
||||
float senseFactor;
|
||||
int senseOffset;
|
||||
unsigned int tripMilliamps;
|
||||
int rawCurrentTripValue;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
static bool disableInterrupts() {
|
||||
uint32_t primask;
|
||||
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
|
||||
__disable_irq();
|
||||
return (primask == 0) ? true : false;
|
||||
}
|
||||
static void enableInterrupts(bool doit) {
|
||||
if (doit) __enable_irq();
|
||||
}
|
||||
#endif
|
||||
const byte UNUSED_PIN = 255;
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -1,6 +1,8 @@
|
||||
#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.
|
||||
@@ -11,36 +13,20 @@
|
||||
// similar to those defined here, WITHOUT editing this file. You can put your
|
||||
// custom defines in config.h.
|
||||
|
||||
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
const byte UNUSED_PIN = 255;
|
||||
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
|
||||
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, byte brake_pin, byte current_pin,
|
||||
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
//
|
||||
// If the brakePin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake)
|
||||
//
|
||||
|
||||
// Arduino standard Motor Shield
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A2, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// 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, 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
|
||||
// via the power pins we above use 9 and 10 as power pins and 4 as "inverted brake" which in this
|
||||
// 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, 12)
|
||||
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
new MotorDriver(4, 7, UNUSED_PIN, 9, A0, 18, 3000, 12), \
|
||||
new MotorDriver(2, 8, UNUSED_PIN, 10, A1, 18, 3000, UNUSED_PIN)
|
||||
|
||||
// Firebox Mk1
|
||||
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
|
||||
@@ -57,9 +43,4 @@
|
||||
new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog
|
||||
#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \
|
||||
new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
#endif
|
||||
|
64
NetworkConfig.h
Normal file
64
NetworkConfig.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* © 2020, Gregor Baues. 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/>.
|
||||
*/
|
||||
|
||||
/* some generated mac addresses as EthernetShields don't have one by default in HW.
|
||||
* Sometimes they come on a sticker on the EthernetShield then use this address otherwise
|
||||
* just choose one from below or generate one yourself. Only condition is that there is no
|
||||
* other device on your network with the same Mac address.
|
||||
*
|
||||
* 52:b8:8a:8e:ce:21
|
||||
* e3:e9:73:e1:db:0d
|
||||
* 54:2b:13:52:ac:0c
|
||||
* c2:d8:d4:7d:7c:cb
|
||||
* 86:cf:fa:9f:07:79
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Build configuration
|
||||
*
|
||||
*/
|
||||
|
||||
#define DCCEX_ENABLED // uncomment to enable CS-EX integration; Commented will operate as standalone and only echo commands as replies
|
||||
|
||||
/**
|
||||
* @brief Network operational configuration
|
||||
*
|
||||
*/
|
||||
#define LISTEN_PORT 2560 // default listen port for the server
|
||||
#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 IP_ADDRESS 10, 0, 0, 101 // Just in case we don't get an adress from DHCP try a static one;
|
||||
|
||||
/**
|
||||
* @brief NetworkInterface configuration
|
||||
*
|
||||
*/
|
||||
#define MAX_INTERFACES 4 // Consume too much memeory beyond in general not more than 2 should be required
|
||||
#define MAX_SOCK_NUM 8 // Maximum number of sockets allowed for any WizNet based EthernetShield. The W5100 only supports 4
|
||||
#define MAX_WIFI_SOCK 5 // ESP8266 doesn't support more than 5 connections
|
||||
#define MAX_ETH_BUFFER 128 // maximum length we read in one go from a TCP packet.
|
||||
#define MAX_OVERFLOW MAX_ETH_BUFFER / 2 // length of the overflow buffer to be used for a given connection.
|
||||
#define MAX_JMRI_CMD MAX_ETH_BUFFER / 2 // MAX Length of a JMRI Command
|
||||
#define OUTBOUND_RING_SIZE 2048
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @todo - MAC address automation
|
||||
* @todo - Wifi setup process in case no permanent setup yet done
|
||||
* @todo - RingBuffer hack to be reviewed
|
||||
*
|
||||
*/
|
36
NetworkDiag.cpp
Normal file
36
NetworkDiag.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// #include "DIAG.h"
|
||||
// #include "MemoryFree.h"
|
||||
// #include "NetworkDiag.h"
|
||||
|
||||
|
||||
int _dMem = 0;
|
||||
int _cMem = 0;
|
||||
byte _nLogLevel = 0; // (Third digit)
|
||||
byte _nInfoLevel = 1; // (Second digit)runtime level of the details of the messages displayed; 1 = only the diag messagges, 2 - incl time / file / line / freemem information - TBD
|
||||
byte _dOutput = 1; // (First digit) where the diag messages shall be send; 1 = Serial, 2 = future CLI on port 23 - TBD (Hundreds)
|
||||
|
||||
// e.g. 124 Send up to TRACE with full info to Serial or 211 send basic INFO level messages to the CLI
|
||||
|
||||
// nLogLevel 0 to 5 send to serial according to the log level 0 = SILENT, 1 = INFO, 2 = WARNING, 3 = ERROR, 4 = TRACE, 5 = DEBUG
|
||||
// nLoglevel 10 to 15 send to network client (10) + according to the log level 0 = SILENT, 1 = INFO, 2 = WARNING, 3 = ERROR, 4 = TRACE, 5 = DEBUG
|
124
NetworkDiag.h
Normal file
124
NetworkDiag.h
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef NetworkDiag_h
|
||||
#define NetworkDiag_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "freeMemory.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#include "TransportProcessor.h"
|
||||
|
||||
#define EH_DW(code) \
|
||||
do \
|
||||
{ \
|
||||
code \
|
||||
} while (0) //wraps in a do while(0) so that the syntax is correct.
|
||||
|
||||
|
||||
// enable/ disbale the loglevel at runtime everything up to the compile time level will be shown the rest not !
|
||||
#define EH_IFLL(LL,code) if(_nLogLevel >= LL){code}
|
||||
#define EH_IFIL(IL,code) if(_nInfoLevel >= IL){code}
|
||||
|
||||
#ifndef DEBUG
|
||||
#define DEBUG
|
||||
#ifndef LOGLEVEL
|
||||
#define LOGLEVEL 4 // compile time level by default up to error can be overridden at compiletime with a -D flag in build_flags (PIO) extra.comiler.flags on Arduino IDE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class NetworkDiag: public StringFormatter {
|
||||
|
||||
public:
|
||||
|
||||
static void setDiagOut(Connection *c) {
|
||||
if ( c->client->connected() ) {
|
||||
diagSerial = c->client;
|
||||
}
|
||||
}
|
||||
|
||||
static void resetDiagOut() {
|
||||
diagSerial = &Serial;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#define NDIAG NetworkDiag::diag
|
||||
|
||||
extern int _dMem;
|
||||
extern int _cMem;
|
||||
extern byte _nLogLevel; // runtime level set by <D NET #level >; by default set to 0 i.e. silent excpet for network startup at level 3 to get the init mesages shown
|
||||
extern byte _nInfoLevel; // runtime lvel of the details of the messages displayed - TBD
|
||||
extern byte _dOutput; // where the diag messages shall be send; 1 = Serial, 2 = future CLI on port 23 - TBD
|
||||
|
||||
#define LOGV_DEBUG 5
|
||||
#define LOGV_TRACE 4
|
||||
#define LOGV_ERROR 3
|
||||
#define LOGV_WARN 2
|
||||
#define LOGV_INFO 1
|
||||
#define LOGV_SILENT 0
|
||||
|
||||
#ifdef LOGLEVEL
|
||||
#if LOGLEVEL == LOGV_SILENT
|
||||
#define INFO(message...)
|
||||
#define WARN(message...)
|
||||
#define ERR(message...)
|
||||
#define TRC(message...)
|
||||
#define DBG(message...)
|
||||
#endif
|
||||
#if LOGLEVEL == LOGV_INFO
|
||||
#define INFO(message...) EH_DW(EH_IFLL(LOGV_INFO, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[INF]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define WARN(message...)
|
||||
#define ERR(message...)
|
||||
#define TRC(message...)
|
||||
#define DBG(message...)
|
||||
#endif
|
||||
#if LOGLEVEL == LOGV_WARN
|
||||
#define INFO(message...) EH_DW(EH_IFLL(LOGV_INFO, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[INF]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define WARN(message...) EH_DW(EH_IFLL(LOGV_ERROR, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[WRN]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define ERR(message...)
|
||||
#define TRC(message...)
|
||||
#define DBG(message...)
|
||||
#endif
|
||||
#if LOGLEVEL == LOGV_ERROR
|
||||
#define INFO(message...) EH_DW(EH_IFLL(LOGV_INFO, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[INF]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define WARN(message...) EH_DW(EH_IFLL(LOGV_WARN, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[WRN]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define ERR(message...) EH_DW(EH_IFLL(LOGV_ERROR, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[ERR]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define TRC(message...)
|
||||
#define DBG(message...)
|
||||
#endif
|
||||
#if LOGLEVEL == LOGV_TRACE
|
||||
#define INFO(message...) EH_DW(EH_IFLL(LOGV_INFO, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[INF]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define WARN(message...) EH_DW(EH_IFLL(LOGV_WARN, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[WRN]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define ERR(message...) EH_DW(EH_IFLL(LOGV_ERROR, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[ERR]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define TRC(message...) EH_DW(EH_IFLL(LOGV_TRACE, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[TRC]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define DBG(message...)
|
||||
#endif
|
||||
#if LOGLEVEL >= LOGV_DEBUG
|
||||
#define INFO(message...) EH_DW(EH_IFLL(LOGV_INFO, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[INF]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define WARN(message...) EH_DW(EH_IFLL(LOGV_WARN, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[WRN]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define ERR(message...) EH_DW(EH_IFLL(LOGV_ERROR, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[ERR]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define TRC(message...) EH_DW(EH_IFLL(LOGV_TRACE, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[TRC]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#define DBG(message...) EH_DW(EH_IFLL(LOGV_DEBUG, _cMem = freeMemory(); _dMem = _cMem - _dMem; NDIAG(F("::[DBG]:%d:%d:%s:%d : "), _cMem, _dMem, __FILE__, __LINE__); _dMem = _cMem; NDIAG(message); NDIAG(F("\n")); ))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
172
NetworkInterface.cpp
Normal file
172
NetworkInterface.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
#include "NetworkSetup.h"
|
||||
#include "NetworkInterface.h"
|
||||
#include "Transport.h"
|
||||
#include "EthernetSetup.h"
|
||||
#include "WifiSetup.h"
|
||||
|
||||
Transport<WiFiServer, WiFiClient, WiFiUDP> *wifiTransport;
|
||||
Transport<EthernetServer, EthernetClient, EthernetUDP> *ethernetTransport;
|
||||
|
||||
DCCNetwork _dccNet;
|
||||
|
||||
void DCCNetwork::loop()
|
||||
{
|
||||
for (byte i = 0; i < _tCounter; i++)
|
||||
{
|
||||
|
||||
Transport<EthernetServer, EthernetClient, EthernetUDP> *e;
|
||||
Transport<WiFiServer, WiFiClient, WiFiUDP> *w;
|
||||
|
||||
switch (_t[i])
|
||||
{
|
||||
case ETHERNET:
|
||||
{
|
||||
e = (Transport<EthernetServer, EthernetClient, EthernetUDP> *)transports[i];
|
||||
e->loop();
|
||||
break;
|
||||
}
|
||||
case WIFI:
|
||||
{
|
||||
w = (Transport<WiFiServer, WiFiClient, WiFiUDP> *)transports[i];
|
||||
w->loop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte DCCNetwork::add(AbstractTransport *t, transportType transport)
|
||||
{
|
||||
if (_tCounter != MAX_INTERFACES)
|
||||
{
|
||||
_t[_tCounter] = transport;
|
||||
transports[_tCounter] = t; // add to array of network interfaces returns the index + 1 if added
|
||||
_tCounter++; // if max intefaces is reached returns 0 for too many ...
|
||||
return _tCounter; // normally a delete shall not be necessary as all is setup at the beginning and shall not change over a session
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkInterface::setup(transportType transport, protocolType protocol, uint16_t port)
|
||||
{
|
||||
bool ok = false;
|
||||
_nLogLevel = 4; // set the log level to ERROR during setup to get proper information
|
||||
|
||||
NetworkSetup::setDeviceId();
|
||||
|
||||
INFO(F("[%s] Transport Setup In Progress ..."), transport ? "Ethernet" : "Wifi");
|
||||
|
||||
// configure the Transport and get Ethernet/Wifi server up and running
|
||||
|
||||
t = transport;
|
||||
switch (transport)
|
||||
{
|
||||
case WIFI:
|
||||
{
|
||||
WifiSetup wSetup(port, protocol);
|
||||
if (wSetup.setup())
|
||||
{
|
||||
wifiTransport = new Transport<WiFiServer, WiFiClient, WiFiUDP>;
|
||||
wifiTransport->id = _dccNet.add(wifiTransport, transport);
|
||||
wifiTransport->server = wSetup.getTCPServer();
|
||||
wifiTransport->port = port;
|
||||
wifiTransport->protocol = protocol;
|
||||
wifiTransport->transport = transport;
|
||||
wifiTransport->udp = wSetup.getUDPServer(); // 0 if TCP is used
|
||||
wifiTransport->maxConnections = wSetup.maxConnections;
|
||||
ok = wifiTransport->setup(this);
|
||||
DBG(F("Interface [%x] bound to transport id [%d:%x]"), this, wifiTransport->id, wifiTransport);
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
case ETHERNET:
|
||||
{
|
||||
EthernetSetup eSetup(port, protocol);
|
||||
if( eSetup.setup() ) {
|
||||
ethernetTransport = new Transport<EthernetServer, EthernetClient, EthernetUDP>;
|
||||
ethernetTransport->id = _dccNet.add(ethernetTransport, transport);
|
||||
ethernetTransport->server = eSetup.getTCPServer(); // 0 if UDP is used
|
||||
ethernetTransport->port = port;
|
||||
ethernetTransport->protocol = protocol;
|
||||
ethernetTransport->transport = transport;
|
||||
ethernetTransport->udp = eSetup.getUDPServer(); // 0 if TCP is used
|
||||
ethernetTransport->maxConnections = eSetup.maxConnections; // that has been determined during the ethernet/wifi setup
|
||||
ok = ethernetTransport->setup(this); // start the transport i.e. setup all the client connections; We don't need the setup object anymore from here on
|
||||
DBG(F("Interface [%x] bound to transport id [%d:%x]"), this, ethernetTransport->id, ethernetTransport);
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
default:
|
||||
{
|
||||
ERR(F("ERROR: Unknown Transport")); // Something went wrong
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
INFO(F("[%s] Transport %s ..."), transport ? "Ethernet" : "Wifi", ok ? "OK" : "Failed");
|
||||
// _nLogLevel = 0; // set loging back to silent;
|
||||
}
|
||||
|
||||
void NetworkInterface::setup(transportType tt, protocolType pt)
|
||||
{
|
||||
NetworkInterface::setup(tt, pt, LISTEN_PORT);
|
||||
}
|
||||
|
||||
void NetworkInterface::setup(transportType tt)
|
||||
{
|
||||
NetworkInterface::setup(tt, TCP, LISTEN_PORT);
|
||||
}
|
||||
|
||||
void NetworkInterface::setup()
|
||||
{
|
||||
NetworkInterface::setup(ETHERNET, TCP, LISTEN_PORT);
|
||||
}
|
||||
|
||||
void NetworkInterface::loop()
|
||||
{
|
||||
// loop over all the transports in
|
||||
_dccNet.loop();
|
||||
|
||||
}
|
||||
|
||||
void NetworkInterface::setHttpCallback(HttpCallback callback)
|
||||
{
|
||||
this->httpCallback = callback;
|
||||
}
|
||||
|
||||
HttpCallback NetworkInterface::getHttpCallback()
|
||||
{
|
||||
return this->httpCallback;
|
||||
}
|
||||
|
||||
NetworkInterface::NetworkInterface() {}
|
||||
NetworkInterface::~NetworkInterface() {}
|
94
NetworkInterface.h
Normal file
94
NetworkInterface.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef NetworkInterface_h
|
||||
#define NetworkInterface_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkConfig.h"
|
||||
#include "HttpRequest.h"
|
||||
|
||||
typedef enum protocolType {
|
||||
TCP,
|
||||
UDPR, // UDP clashes with a class name in the network stack
|
||||
MQTT
|
||||
} protocolType;
|
||||
|
||||
typedef enum transportType {
|
||||
WIFI, // using an AT (Version >= V1.7) command enabled ESP8266 not to be used in conjunction with the WifiInterface though! not tested for conflicts
|
||||
ETHERNET // using the EthernetShield
|
||||
} transportType;
|
||||
|
||||
using HttpCallback = void(*)(ParsedRequest *req, Client *client);
|
||||
|
||||
/**
|
||||
* @brief Abstract parent class of the templated ( Ethernet or Wifi ) class
|
||||
* Instances of Transports are hold through this in an array in DCCNetwork which describes and
|
||||
* actually manages the available transports.
|
||||
*/
|
||||
struct AbstractTransport {
|
||||
void loop(){};
|
||||
virtual ~AbstractTransport(){};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Core class holding and running the instantiated Transports
|
||||
* initalized through the NetworkInterface. The number of transports is
|
||||
* limited by MAX_INTERFACES
|
||||
*
|
||||
*/
|
||||
class DCCNetwork {
|
||||
private:
|
||||
byte _tCounter = 0;
|
||||
transportType _t[MAX_INTERFACES];
|
||||
public:
|
||||
AbstractTransport *transports[MAX_INTERFACES];
|
||||
|
||||
byte add(AbstractTransport* t, transportType _t);
|
||||
void loop();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Main entry point and provider of callbacks. Sole responsibility is to create
|
||||
* the transport endpoints and loop over them for processing
|
||||
*
|
||||
*/
|
||||
class NetworkInterface
|
||||
{
|
||||
private:
|
||||
HttpCallback httpCallback;
|
||||
transportType t;
|
||||
|
||||
public:
|
||||
|
||||
void setHttpCallback(HttpCallback callback);
|
||||
HttpCallback getHttpCallback();
|
||||
void setup(transportType t, protocolType p, uint16_t port); // specific port nummber
|
||||
void setup(transportType t, protocolType p); // uses default port number
|
||||
void setup(transportType t); // defaults for protocol/port
|
||||
|
||||
void setup(); // defaults for all as above plus CABLE (i.e. using EthernetShield ) as default
|
||||
static void loop();
|
||||
|
||||
NetworkInterface();
|
||||
~NetworkInterface();
|
||||
};
|
||||
|
||||
#endif
|
76
NetworkSetup.cpp
Normal file
76
NetworkSetup.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ArduinoUniqueID.h"
|
||||
#include "NetworkSetup.h"
|
||||
#include "NetworkDiag.h"
|
||||
|
||||
bool NetworkSetup::deviceIdSet = false;
|
||||
bool NetworkSetup::macAddressSet = false;
|
||||
char NetworkSetup::_deviceId[MAXDEVICEID] = {0};
|
||||
uint8_t NetworkSetup::mac[6] = MAC_ADDRESS; // default MacAddress
|
||||
uint8_t NetworkSetup::apWifiMacAddress[6] = MAC_ADDRESS; // default MacAddress
|
||||
uint8_t NetworkSetup::stWifiMacAddress[6] = MAC_ADDRESS; // default MacAddress
|
||||
|
||||
static void array_to_string(byte array[], unsigned int len, char buffer[])
|
||||
{
|
||||
for (unsigned int i = 0; i < len; i++)
|
||||
{
|
||||
byte nib1 = (array[i] >> 4) & 0x0F;
|
||||
byte nib2 = (array[i] >> 0) & 0x0F;
|
||||
buffer[i * 2 + 0] = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
|
||||
buffer[i * 2 + 1] = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
|
||||
}
|
||||
buffer[len * 2] = '\0';
|
||||
}
|
||||
|
||||
void NetworkSetup::setDeviceId()
|
||||
{
|
||||
array_to_string(UniqueID, UniqueIDsize, _deviceId);
|
||||
DBG(F("Unique device ID: %s\n"), _deviceId);
|
||||
deviceIdSet = true;
|
||||
}
|
||||
|
||||
void NetworkSetup::printMacAddress(uint8_t a[]) {
|
||||
INFO(F("MAC Address: [%x:%x:%x:%x:%x:%x]"),a[0],a[1],a[2],a[3],a[4],a[5]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief generates Mac Addresses for Ethernet and WiFi
|
||||
*
|
||||
*/
|
||||
void NetworkSetup::genMacAddress() {
|
||||
|
||||
if (!deviceIdSet) NetworkSetup::setDeviceId();
|
||||
if (!macAddressSet) {
|
||||
for (byte i = 0; i < 6; i++)
|
||||
{
|
||||
mac[i] = UniqueID[i];
|
||||
};
|
||||
}
|
||||
macAddressSet = true;
|
||||
}
|
||||
|
||||
NetworkSetup::NetworkSetup() {}
|
||||
NetworkSetup::~NetworkSetup() {}
|
||||
|
||||
|
||||
// WiFi.apMacAddress(apWifiMacAddress);
|
||||
// WiFi.macAddress(stWifiMacAddress);
|
62
NetworkSetup.h
Normal file
62
NetworkSetup.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef NetworkSetup_h
|
||||
#define NetworkSetup_h
|
||||
|
||||
#include <Ethernet.h>
|
||||
|
||||
#include "NetworkConfig.h"
|
||||
#include "NetworkInterface.h"
|
||||
|
||||
#define MAXDEVICEID 20
|
||||
|
||||
class NetworkSetup
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
static char _deviceId[MAXDEVICEID];
|
||||
static bool macAddressSet;
|
||||
static bool deviceIdSet;
|
||||
|
||||
public:
|
||||
IPAddress dnsip;
|
||||
IPAddress ip;
|
||||
static uint8_t mac[6]; // Default if not set automatically for EthernetShield
|
||||
static uint8_t apWifiMacAddress[6]; // for the WiFi AP
|
||||
static uint8_t stWifiMacAddress[6]; // for the normal WiFi connection
|
||||
|
||||
uint8_t maxConnections;
|
||||
bool connected; // semantics is that the server has successfullt started or not; client connections will be started in the Transport object
|
||||
protocolType protocol;
|
||||
uint16_t port = LISTEN_PORT; // Default port
|
||||
|
||||
static void setDeviceId();
|
||||
char *getDeviceId() {
|
||||
return _deviceId;
|
||||
}
|
||||
static void genMacAddress();
|
||||
static void printMacAddress(uint8_t a[]);
|
||||
|
||||
NetworkSetup();
|
||||
~NetworkSetup();
|
||||
};
|
||||
|
||||
#endif
|
@@ -83,19 +83,14 @@ the state of any outputs being monitored or controlled by a separate interface o
|
||||
|
||||
#include "Outputs.h"
|
||||
#include "EEStore.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// print all output states to stream
|
||||
void Output::printAll(Print *stream){
|
||||
for (Output *tt = Output::firstOutput; tt != NULL; tt = tt->nextOutput)
|
||||
StringFormatter::send(stream, F("<Y %d %d>\n"), tt->data.id, tt->data.oStatus);
|
||||
} // Output::printAll
|
||||
|
||||
void Output::activate(int s){
|
||||
data.oStatus=(s>0); // if s>0, set status to active, else inactive
|
||||
digitalWrite(data.pin,data.oStatus ^ bitRead(data.iFlag,0)); // set state of output pin to HIGH or LOW depending on whether bit zero of iFlag is set to 0 (ACTIVE=HIGH) or 1 (ACTIVE=LOW)
|
||||
if(num>0)
|
||||
EEPROM.put(num,data.oStatus);
|
||||
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -39,7 +39,6 @@ class Output{
|
||||
static Output *firstOutput;
|
||||
struct OutputData data;
|
||||
Output *nextOutput;
|
||||
static void printAll(Print *);
|
||||
private:
|
||||
int num; // Chris has no idea what this is all about!
|
||||
|
||||
|
@@ -23,9 +23,9 @@
|
||||
* BSD license, all text above must be included in any redistribution
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include "PWMServoDriver.h"
|
||||
#include "DIAG.h"
|
||||
#include "I2CManager.h"
|
||||
|
||||
|
||||
// REGISTER ADDRESSES
|
||||
@@ -40,7 +40,6 @@ const byte MODE1_RESTART=0x80; /**< Restart enabled */
|
||||
const byte PCA9685_I2C_ADDRESS=0x40; /** First PCA9685 I2C Slave Address */
|
||||
const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
|
||||
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
|
||||
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
|
||||
|
||||
/*!
|
||||
* @brief Sets the PWM frequency for a chip to 50Hz for servos
|
||||
@@ -53,20 +52,19 @@ bool PWMServoDriver::setup(int board) {
|
||||
if (board>3 || (failFlags & (1<<board))) return false;
|
||||
if (setupFlags & (1<<board)) return true;
|
||||
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(MAX_I2C_SPEED);
|
||||
|
||||
Wire.begin();
|
||||
uint8_t i2caddr=PCA9685_I2C_ADDRESS + board;
|
||||
|
||||
// Test if device is available
|
||||
byte error = I2CManager.checkAddress(i2caddr);
|
||||
if (error) {
|
||||
DIAG(F("I2C Servo device 0x%x Not Found %d"),i2caddr, error);
|
||||
// Terst if device is available
|
||||
Wire.beginTransmission(i2caddr);
|
||||
byte error = Wire.endTransmission();
|
||||
if (error!=0) {
|
||||
DIAG(F("\nI2C Servo device 0x%x Not Found %d\n"),i2caddr, error);
|
||||
failFlags|=1<<board;
|
||||
return false;
|
||||
}
|
||||
|
||||
//DIAG(F("PWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
|
||||
//DIAG(F("\nPWMServoDriver::setup %x prescale=%d"),i2caddr,PRESCALE_50HZ);
|
||||
writeRegister(i2caddr,PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
|
||||
writeRegister(i2caddr,PCA9685_PRESCALE, PRESCALE_50HZ);
|
||||
writeRegister(i2caddr,PCA9685_MODE1,MODE1_AI);
|
||||
@@ -83,15 +81,22 @@ void PWMServoDriver::setServo(byte servoNum, uint16_t value) {
|
||||
int pin=servoNum%16;
|
||||
|
||||
if (setup(board)) {
|
||||
DIAG(F("SetServo %d %d"),servoNum,value);
|
||||
uint8_t buffer[] = {(uint8_t)(PCA9685_FIRST_SERVO + 4 * pin), // 4 registers per pin
|
||||
0, 0, (uint8_t)(value & 0xff), (uint8_t)(value >> 8)};
|
||||
if (value == 4095) buffer[2] = 0x10; // Full on
|
||||
byte error=I2CManager.write(PCA9685_I2C_ADDRESS + board, buffer, sizeof(buffer));
|
||||
if (error!=0) DIAG(F("SetServo error %d"),error);
|
||||
DIAG(F("\nSetServo %d %d\n"),servoNum,value);
|
||||
Wire.beginTransmission(PCA9685_I2C_ADDRESS + board);
|
||||
Wire.write(PCA9685_FIRST_SERVO + 4 * pin); // 4 registers per pin
|
||||
Wire.write(0);
|
||||
Wire.write(0);
|
||||
Wire.write(value);
|
||||
Wire.write(value >> 8);
|
||||
byte error=Wire.endTransmission();
|
||||
if (error!=0) DIAG(F("\nSetServo error %d\n"),error);
|
||||
}
|
||||
}
|
||||
|
||||
void PWMServoDriver::writeRegister(uint8_t i2caddr,uint8_t hardwareRegister, uint8_t d) {
|
||||
I2CManager.write(i2caddr, 2, hardwareRegister, d);
|
||||
Wire.beginTransmission(i2caddr);
|
||||
Wire.write(hardwareRegister);
|
||||
Wire.write(d);
|
||||
Wire.endTransmission();
|
||||
delay(5); // allow registers to settle before continuing
|
||||
}
|
||||
|
80
README.md
80
README.md
@@ -1,60 +1,52 @@
|
||||
# What is DCC++ EX?
|
||||
DCC++ EX is the organization maintaining several codebases that together represent a fully open source DCC system. Currently, this includes the following:
|
||||
# What's DCC++ EX
|
||||
------------
|
||||
|
||||
* [CommandStation-EX](https://github.com/DCC-EX/CommandStation-EX/releases) - the latest take on the DCC++ command station for controlling your trains. Runs on an Arduino board, and includes advanced features such as a WiThrottle server implementation, turnout operation, general purpose inputs and outputs (I/O), and JMRI integration.
|
||||
* [exWebThrottle](https://github.com/DCC-EX/exWebThrottle) - a simple web based controller for your DCC++ command station.
|
||||
* [BaseStation-installer](https://github.com/DCC-EX/BaseStation-Installer) - an installer executable that takes care of downloading and installing DCC++ firmware onto your hardware setup.
|
||||
* [BaseStation-Classic](https://github.com/DCC-EX/BaseStation-Classic) - the original DCC++ software, packaged in a stable release. No active development, bug fixes only.
|
||||
DCC++ EX is an open-source hardware and software system for the operation of DCC-equipped model railroads. It expands on the work of Gregg E. Berman's original DCC++ (which can be found here in the BaseStation-Classic repository)
|
||||
|
||||
A basic DCC++ EX hardware setup can use easy to find, widely avalable Arduino boards that you can assemble yourself.
|
||||
The system consists of two parts, the DCC++ EX Command Station and one of many front end controllers. These controllers can be hardware controllers (called CABs or Throttles), software applications like JMRI, phone apps like Engine Driver, or our exWebThrottle that is a simple application you run in a browser like a web page and control your model trains.
|
||||
|
||||
Both CommandStation-EX and BaseStation-Classic support much of the NMRA Digital Command Control (DCC) [standards](http://www.nmra.org/dcc-working-group "NMRA DCC Working Group"), including:
|
||||
The DCC++ EX Command Station consists of an Arduino micro controller fitted with an Arduino Motor Shield (or other supported motor controllers) that can be connected directly to the tracks of a model railroad.
|
||||
|
||||
* simultaneous control of multiple locomotives
|
||||
* 2-byte and 4-byte locomotive addressing
|
||||
* 128-step speed throttling
|
||||
* Activate/de-activate all accessory function addresses 0-2048
|
||||
* Control of all cab functions F0-F28
|
||||
* Main Track: Write configuration variable bytes and set/clear specific configuration variable (CV) bits (aka Programming on Main or POM)
|
||||
* Programming Track: Same as the main track with the addition of reading configuration variable bytes
|
||||
# What’s in this Repository
|
||||
-------------------------
|
||||
|
||||
# What’s in this Repository?
|
||||
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders. More information about the sketch can be found in the included PDF file.
|
||||
|
||||
This repository, CommandStation-EX, contains a complete DCC++ EX Commmand Station sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. All sketch files are in the folder named CommandStation-EX and its subforlders.
|
||||
To utilize this sketch, you can use the following methods:
|
||||
|
||||
To utilize this sketch, you can use the following:
|
||||
* our automated installer
|
||||
* download a zip file from this repository (green Code button above) and unzip it
|
||||
* use git clone on this repository
|
||||
|
||||
1. (beginner) our [automated installer](https://github.com/DCC-EX/BaseStation-Installer)
|
||||
2. (intermediate) download the latest version from the [releases page](https://github.com/DCC-EX/CommandStation-EX/releases)
|
||||
3. (advanced) use git clone on this repository
|
||||
|
||||
Not using the installer? Open the file "CommandStation-EX.ino" in the
|
||||
With the manual methods you unzip or git clone to the Arduino IDE
|
||||
projects folder and then open the file "CommandStation-EX.ino" in the
|
||||
Arduino IDE. Please do not rename the folder containing the sketch
|
||||
code, nor add any files in that folder. The Arduino IDE relies on the
|
||||
structure and name of the folder to properly display and compile the
|
||||
code. Rename or copy config.example.h to config.h. If you do not have
|
||||
the standard setup, you must edit config.h according to the help texts
|
||||
in config.h.
|
||||
code. If you do not run the installer, you have to copy
|
||||
config.example.h to config.h. If you do not have the standard config
|
||||
you edit config.h according to the help texts in config.h.
|
||||
|
||||
## What's new in CommandStation-EX?
|
||||
The latest production release of the Master branch is 3.0.1:
|
||||
|
||||
* WiThrottle server built in. Connect Engine Driver or WiThrottle clients directly to your Command Station
|
||||
* WiFi and Ethernet shield support
|
||||
* No more jumpers or soldering!
|
||||
* Direct support for all the most popular motor control boards
|
||||
* I2C Display support
|
||||
* Improved short circuit detection and automatic reset from an overload
|
||||
* Current reading, sensing and ACK detection settings in milliAmps instead of just pin readings
|
||||
* Improved adherence to the NMRA DCC specification
|
||||
* Complete support for all the old commands and front ends like JMRI
|
||||
* Railcom cutout (beta)
|
||||
* Simpler, modular, faster code with an API Library for developers for easy expansion
|
||||
* New features and functions in JMRI
|
||||
* Automation (coming soon)
|
||||
* Supports the Arduino Uno, Arduino Mega, and Arduino Nano
|
||||
* Built-in configuration for both the original Arduino Motor Shield, Pololu MC33926 Motor Shield, LMD18200, and BTS7960B
|
||||
* Built-in configuration and support of Ethernet Shields and the ESP82266 WiFi module (networking for use with Mega only).
|
||||
|
||||
NOTE: DCC-EX is a major rewrite to the code. We started over and rebuilt it from the ground up! For what that means to you, click [HERE](notes/rewrite.md).
|
||||
For more information on the overall DCC++ EX system, please follow the links in the PDF file.
|
||||
|
||||
# More information
|
||||
You can learn more at the [DCC++ EX website](https://dcc-ex.com/)
|
||||
Detailed diagrams showing pin mappings and required jumpers for the Motor Shields can be found in the Documentation Repository
|
||||
|
||||
- November 14, 2020
|
||||
The Master branch contains all of the Command Station functionality showed in the DCC-EX YouTube channel.
|
||||
|
||||
# How to find more information
|
||||
--------------------------
|
||||
|
||||
[DCC++ EX WEB Page](https://dcc-ex.github.io "DCC++ EX WEB Page")
|
||||
|
||||
[The DCC++ EX Discord and live support](https://discord.gg/y2sB4Fp "The DCC++ EX Discord Server")
|
||||
|
||||
[TrainBoard DCC++ Forum](https://www.trainboard.com/highball/index.php?forums/dcc.177/ "TrainBoard DCC++ Forum")
|
||||
|
||||
-May 2020
|
||||
!!
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 65 KiB |
Binary file not shown.
@@ -1,93 +0,0 @@
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward.
|
||||
|
||||
**Known Bugs:**
|
||||
- **Consisting through JMRI** - currently does not work in this release. You may use the <M> command to do this manually.
|
||||
- **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode.
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
- **<W addr> command to write loco address and clear consist**
|
||||
- **<R> command will allow for consist address**
|
||||
- **Startup commands implemented**
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
- **Create new output for current in mA for ``<c>`` command** - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- **Simultaneously update JMRI to handle new current meter**
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
- **Add back fix for jitter**
|
||||
- **Add Turnouts, Outputs and Sensors to ```<s>``` command output**
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.0:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - ```<#>``` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
|
||||
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**CommandStation-EX Developers**
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - - Engine Driver and JMRI Interface
|
||||
- Scott Catalanno - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**exInstaller Software**
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - (KCSmith)
|
||||
|
||||
**WebThrotle-EX**
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H -
|
||||
|
||||
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
- Larry Dribin - Release Management
|
||||
- Keith Ledbetter
|
||||
- BradVan der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sim Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
@@ -1,23 +0,0 @@
|
||||
# CommandStation-EX Release Notes
|
||||
|
||||
## v3.0.0
|
||||
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - ```<F>``` command allows setting functions based on their number, not based on a code as in ```<f>```
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them
|
||||
- **Diagnostic ```<D>``` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Support for more decoders** - Support for 20 (Uno) or 50 (Mega) mobile decoders, number automaticlaly recognized by JMRI.
|
||||
- **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. ```<!>``` command added to release locos from memory.
|
@@ -75,15 +75,9 @@ void RingStream::mark(uint8_t b) {
|
||||
_count=0;
|
||||
}
|
||||
|
||||
// peekTargetMark is used by the parser stash routines to know which client
|
||||
// to send a callback response to some time later.
|
||||
uint8_t RingStream::peekTargetMark() {
|
||||
return _buffer[_mark];
|
||||
}
|
||||
|
||||
bool RingStream::commit() {
|
||||
if (_overflow) {
|
||||
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||
DIAG(F("\nRingStream(%d) commit(%d) OVERFLOW\n"),_len, _count);
|
||||
// just throw it away
|
||||
_pos_write=_mark;
|
||||
_overflow=false;
|
||||
@@ -103,3 +97,24 @@ bool RingStream::commit() {
|
||||
_buffer[_mark]=lowByte(_count);
|
||||
return true; // commit worked
|
||||
}
|
||||
|
||||
//grbba
|
||||
byte *RingStream::getBuffer() {
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
void RingStream::printStream() {
|
||||
DIAG(F(" _len %d _pos_write %d _pos_read %d _overflow %d _mark %d _count %d\n"), _len, _pos_write, _pos_read, _overflow, _mark, _count);
|
||||
};
|
||||
|
||||
void RingStream::resetStream()
|
||||
{
|
||||
memset(_buffer, 0, _len);
|
||||
_pos_write=0;
|
||||
_pos_read=0;
|
||||
_buffer[0]=0;
|
||||
_overflow=false;
|
||||
_mark=0;
|
||||
_count=0;
|
||||
}
|
||||
//grbba
|
11
RingStream.h
11
RingStream.h
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DIAG.h"
|
||||
|
||||
class RingStream : public Print {
|
||||
|
||||
@@ -33,8 +34,13 @@ class RingStream : public Print {
|
||||
int freeSpace();
|
||||
void mark(uint8_t b);
|
||||
bool commit();
|
||||
uint8_t peekTargetMark();
|
||||
|
||||
|
||||
// grbba
|
||||
byte *getBuffer();
|
||||
void resetStream();
|
||||
void printStream();
|
||||
// grbba
|
||||
|
||||
private:
|
||||
int _len;
|
||||
int _pos_write;
|
||||
@@ -43,6 +49,7 @@ class RingStream : public Print {
|
||||
int _mark;
|
||||
int _count;
|
||||
byte * _buffer;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
108
SSD1306Ascii.cpp
108
SSD1306Ascii.cpp
@@ -1,108 +0,0 @@
|
||||
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||
* Modifications (C) 2021 Neil McKechnie
|
||||
*
|
||||
* This Library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with the Arduino SSD1306Ascii Library. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "SSD1306Ascii.h"
|
||||
#include "I2CManager.h"
|
||||
#include "FSH.h"
|
||||
|
||||
|
||||
// Maximum number of bytes we can send per transmission is 32.
|
||||
const uint8_t FLASH SSD1306AsciiWire::blankPixels[32] =
|
||||
{0x40, // First byte specifies data mode
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
|
||||
//==============================================================================
|
||||
// SSD1306AsciiWire Method Definitions
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::clear() {
|
||||
clear(0, displayWidth() - 1, 0, displayRows() - 1);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::clear(uint8_t columnStart, uint8_t columnEnd,
|
||||
uint8_t rowStart, uint8_t rowEnd) {
|
||||
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire
|
||||
// Ensure only rows on display will be cleared.
|
||||
if (rowEnd >= displayRows()) rowEnd = displayRows() - 1;
|
||||
for (uint8_t r = rowStart; r <= rowEnd; r++) {
|
||||
setCursor(columnStart, r); // Position at start of row to be erased
|
||||
for (uint8_t c = columnStart; c <= columnEnd; c += maxBytes-1) {
|
||||
uint8_t len = min((uint8_t)(columnEnd-c+1), maxBytes-1) + 1;
|
||||
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write up to 31 blank columns
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) {
|
||||
m_i2cAddr = i2cAddr;
|
||||
m_col = 0;
|
||||
m_row = 0;
|
||||
#ifdef __AVR__
|
||||
const uint8_t* table = (const uint8_t*)pgm_read_word(&dev->initcmds);
|
||||
#else // __AVR__
|
||||
const uint8_t* table = dev->initcmds;
|
||||
#endif // __AVR
|
||||
uint8_t size = readFontByte(&dev->initSize);
|
||||
m_displayWidth = readFontByte(&dev->lcdWidth);
|
||||
m_displayHeight = readFontByte(&dev->lcdHeight);
|
||||
m_colOffset = readFontByte(&dev->colOffset);
|
||||
I2CManager.write_P(m_i2cAddr, table, size);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::setContrast(uint8_t value) {
|
||||
I2CManager.write(m_i2cAddr, 2,
|
||||
0x00, // Set to command mode
|
||||
SSD1306_SETCONTRAST, value);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::setCursor(uint8_t col, uint8_t row) {
|
||||
if (row < displayRows() && col < m_displayWidth) {
|
||||
m_row = row;
|
||||
m_col = col + m_colOffset;
|
||||
I2CManager.write(m_i2cAddr, 4,
|
||||
0x00, // Set to command mode
|
||||
SSD1306_SETLOWCOLUMN | (col & 0XF),
|
||||
SSD1306_SETHIGHCOLUMN | (col >> 4),
|
||||
SSD1306_SETSTARTPAGE | m_row);
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
void SSD1306AsciiWire::setFont(const uint8_t* font) {
|
||||
m_font = font;
|
||||
m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR);
|
||||
m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
size_t SSD1306AsciiWire::write(uint8_t ch) {
|
||||
const uint8_t* base = m_font + FONT_WIDTH_TABLE;
|
||||
|
||||
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
|
||||
return 0;
|
||||
ch -= m_fontFirstChar;
|
||||
base += fontWidth * ch;
|
||||
uint8_t buffer[1+fontWidth+letterSpacing];
|
||||
buffer[0] = 0x40; // set SSD1306 controller to data mode
|
||||
uint8_t bufferPos = 1;
|
||||
// Copy character pixel columns
|
||||
for (uint8_t i = 0; i < fontWidth; i++)
|
||||
buffer[bufferPos++] = readFontByte(base++);
|
||||
// Add blank pixels between letters
|
||||
for (uint8_t i = 0; i < letterSpacing; i++)
|
||||
buffer[bufferPos++] = 0;
|
||||
// Write the data to I2C display
|
||||
I2CManager.write(m_i2cAddr, buffer, bufferPos);
|
||||
return 1;
|
||||
}
|
@@ -1,97 +0,0 @@
|
||||
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||
* Modifications (C) 2021 Neil McKechnie
|
||||
*
|
||||
* This Library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this software. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SSD1306Ascii_h
|
||||
#define SSD1306Ascii_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "SSD1306font.h"
|
||||
#include "SSD1306init.h"
|
||||
|
||||
class SSD1306AsciiWire : public Print {
|
||||
public:
|
||||
using Print::write;
|
||||
SSD1306AsciiWire() {}
|
||||
// Initialize the display controller.
|
||||
void begin(const DevType* dev, uint8_t i2cAddr);
|
||||
// Clear the display and set the cursor to (0, 0).
|
||||
void clear();
|
||||
// Clear a region of the display.
|
||||
void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1);
|
||||
// The current column in pixels.
|
||||
inline uint8_t col() const { return m_col; }
|
||||
// The display hight in pixels.
|
||||
inline uint8_t displayHeight() const { return m_displayHeight; }
|
||||
// The display height in rows with eight pixels to a row.
|
||||
inline uint8_t displayRows() const { return m_displayHeight / 8; }
|
||||
// The display width in pixels.
|
||||
inline uint8_t displayWidth() const { return m_displayWidth; }
|
||||
// Set the cursor position to (0, 0).
|
||||
inline void home() { setCursor(0, 0); }
|
||||
// Initialize the display controller.
|
||||
void init(const DevType* dev);
|
||||
// the current row number with eight pixels to a row.
|
||||
inline uint8_t row() const { return m_row; }
|
||||
/**
|
||||
* @brief Set the display contrast.
|
||||
*
|
||||
* @param[in] value The contrast level in th range 0 to 255.
|
||||
*/
|
||||
void setContrast(uint8_t value);
|
||||
/**
|
||||
* @brief Set the cursor position.
|
||||
*
|
||||
* @param[in] col The column number in pixels.
|
||||
* @param[in] row the row number in eight pixel rows.
|
||||
*/
|
||||
void setCursor(uint8_t col, uint8_t row);
|
||||
/**
|
||||
* @brief Set the current font.
|
||||
*
|
||||
* @param[in] font Pointer to a font table.
|
||||
*/
|
||||
void setFont(const uint8_t* font);
|
||||
/**
|
||||
* @brief Display a character.
|
||||
*
|
||||
* @param[in] c The character to display.
|
||||
* @return one for success else zero.
|
||||
*/
|
||||
size_t write(uint8_t c);
|
||||
|
||||
private:
|
||||
uint8_t m_col; // Cursor column.
|
||||
uint8_t m_row; // Cursor RAM row.
|
||||
uint8_t m_displayWidth; // Display width.
|
||||
uint8_t m_displayHeight; // Display height.
|
||||
uint8_t m_colOffset; // Column offset RAM to SEG.
|
||||
const uint8_t* m_font = NULL; // Current font.
|
||||
|
||||
// Only fixed size 5x7 fonts in a 6x8 cell are supported.
|
||||
const uint8_t fontWidth = 5;
|
||||
const uint8_t fontHeight = 7;
|
||||
const uint8_t letterSpacing = 1;
|
||||
uint8_t m_fontFirstChar;
|
||||
uint8_t m_fontCharCount;
|
||||
|
||||
uint8_t m_i2cAddr;
|
||||
|
||||
static const uint8_t blankPixels[];
|
||||
};
|
||||
|
||||
#endif // SSD1306Ascii_h
|
180
SSD1306font.h
180
SSD1306font.h
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* System5x7
|
||||
*
|
||||
*
|
||||
* File Name : System5x7.h
|
||||
* Date : 28 Oct 2008
|
||||
* Font size in bytes : 470
|
||||
* Font width : 5
|
||||
* Font height : 7
|
||||
* Font first char : 32
|
||||
* Font last char : 127
|
||||
* Font used chars : 94
|
||||
*
|
||||
* The font data are defined as
|
||||
*
|
||||
* struct _FONT_ {
|
||||
* uint16_t font_Size_in_Bytes_over_all_included_Size_it_self;
|
||||
* uint8_t font_Width_in_Pixel_for_fixed_drawing;
|
||||
* uint8_t font_Height_in_Pixel_for_all_characters;
|
||||
* unit8_t font_First_Char;
|
||||
* uint8_t font_Char_Count;
|
||||
*
|
||||
* uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1];
|
||||
* // for each character the separate width in pixels,
|
||||
* // characters < 128 have an implicit virtual right empty row
|
||||
*
|
||||
* uint8_t font_data[];
|
||||
* // bit field of all characters
|
||||
*/
|
||||
|
||||
#ifndef SSD1306font_H
|
||||
#define SSD1306font_H
|
||||
|
||||
#define SYSTEM5x7_WIDTH 5
|
||||
#define SYSTEM5x7_HEIGHT 7
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
/** declare a font for AVR. */
|
||||
#define GLCDFONTDECL(_n) static const uint8_t __attribute__((progmem)) _n[]
|
||||
#define readFontByte(addr) pgm_read_byte(addr)
|
||||
#else // __AVR__
|
||||
/** declare a font. */
|
||||
#define GLCDFONTDECL(_n) static const uint8_t _n[]
|
||||
/** Fake read from flash. */
|
||||
#define readFontByte(addr) (*(const unsigned char *)(addr))
|
||||
#endif // __AVR__
|
||||
//------------------------------------------------------------------------------
|
||||
// Font Indices
|
||||
/** No longer used Big Endian length field. Now indicates font type.
|
||||
*
|
||||
* 00 00 (fixed width font with 1 padding pixel on right and below)
|
||||
*
|
||||
* 00 01 (fixed width font with no padding pixels)
|
||||
*/
|
||||
#define FONT_LENGTH 0
|
||||
/** Maximum character width. */
|
||||
#define FONT_WIDTH 2
|
||||
/** Font hight in pixels */
|
||||
#define FONT_HEIGHT 3
|
||||
/** Ascii value of first character */
|
||||
#define FONT_FIRST_CHAR 4
|
||||
/** count of characters in font. */
|
||||
#define FONT_CHAR_COUNT 5
|
||||
/** Offset to width table. */
|
||||
#define FONT_WIDTH_TABLE 6
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
GLCDFONTDECL(System5x7) = {
|
||||
0x0, 0x0, // size of zero indicates fixed width font,
|
||||
0x05, // width
|
||||
0x07, // height
|
||||
0x20, // first char
|
||||
0x61, // char count
|
||||
|
||||
// Fixed width; char width table not used !!!!
|
||||
|
||||
// font data
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // (space)
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||
0x08, 0x2A, 0x1C, 0x2A, 0x08, // *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||
0x00, 0x50, 0x30, 0x00, 0x00, // ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||
0x00, 0x56, 0x36, 0x00, 0x00, // ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||
0x7F, 0x09, 0x09, 0x01, 0x01, // F
|
||||
0x3E, 0x41, 0x41, 0x51, 0x32, // G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||
0x7F, 0x02, 0x04, 0x02, 0x7F, // M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||
0x7F, 0x20, 0x18, 0x20, 0x7F, // W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||
0x03, 0x04, 0x78, 0x04, 0x03, // Y
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||
0x00, 0x00, 0x7F, 0x41, 0x41, // [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, // "\"
|
||||
0x41, 0x41, 0x7F, 0x00, 0x00, // ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, // ^
|
||||
0x40, 0x40, 0x40, 0x40, 0x40, // _
|
||||
0x00, 0x01, 0x02, 0x04, 0x00, // `
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, // a
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, // b
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, // c
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, // d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, // e
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, // f
|
||||
0x08, 0x14, 0x54, 0x54, 0x3C, // g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, // h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, // i
|
||||
0x20, 0x40, 0x44, 0x3D, 0x00, // j
|
||||
0x00, 0x7F, 0x10, 0x28, 0x44, // k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, // l
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, // m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, // n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, // o
|
||||
0x7C, 0x14, 0x14, 0x14, 0x08, // p
|
||||
0x08, 0x14, 0x14, 0x18, 0x7C, // q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, // r
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, // s
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||
0x0C, 0x50, 0x50, 0x50, 0x3C, // y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, // z
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, // {
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, // |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, // }
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
|
||||
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
|
||||
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol
|
||||
|
||||
};
|
||||
|
||||
#endif
|
207
SSD1306init.h
207
SSD1306init.h
@@ -1,207 +0,0 @@
|
||||
/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman
|
||||
* Modifications (C) 2021 Neil McKechnie
|
||||
*
|
||||
* This Library is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with the Arduino SSD1306Ascii Library. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @file SSD1306init.h
|
||||
* @brief Display controller initialization commands.
|
||||
*/
|
||||
#ifndef SSD1306init_h
|
||||
#define SSD1306init_h
|
||||
//------------------------------------------------------------------------------
|
||||
#ifndef __AVR__
|
||||
/** Handle AVR flash addressing. */
|
||||
#define MEM_TYPE
|
||||
#else // __AVR__
|
||||
#define MEM_TYPE __attribute__ ((progmem))
|
||||
#endif // __AVR__
|
||||
//------------------------------------------------------------------------------
|
||||
/** Set Lower Column Start Address for Page Addressing Mode. */
|
||||
#define SSD1306_SETLOWCOLUMN 0x00
|
||||
/** Set Higher Column Start Address for Page Addressing Mode. */
|
||||
#define SSD1306_SETHIGHCOLUMN 0x10
|
||||
/** Set Memory Addressing Mode. */
|
||||
#define SSD1306_MEMORYMODE 0x20
|
||||
/** Set display RAM display start line register from 0 - 63. */
|
||||
#define SSD1306_SETSTARTLINE 0x40
|
||||
/** Set Display Contrast to one of 256 steps. */
|
||||
#define SSD1306_SETCONTRAST 0x81
|
||||
/** Enable or disable charge pump. Follow with 0X14 enable, 0X10 disable. */
|
||||
#define SSD1306_CHARGEPUMP 0x8D
|
||||
/** Set Segment Re-map between data column and the segment driver. */
|
||||
#define SSD1306_SEGREMAP 0xA0
|
||||
/** Resume display from GRAM content. */
|
||||
#define SSD1306_DISPLAYALLON_RESUME 0xA4
|
||||
/** Force display on regardless of GRAM content. */
|
||||
#define SSD1306_DISPLAYALLON 0xA5
|
||||
/** Set Normal Display. */
|
||||
#define SSD1306_NORMALDISPLAY 0xA6
|
||||
/** Set Inverse Display. */
|
||||
#define SSD1306_INVERTDISPLAY 0xA7
|
||||
/** Set Multiplex Ratio from 16 to 63. */
|
||||
#define SSD1306_SETMULTIPLEX 0xA8
|
||||
/** Set Display off. */
|
||||
#define SSD1306_DISPLAYOFF 0xAE
|
||||
/** Set Display on. */
|
||||
#define SSD1306_DISPLAYON 0xAF
|
||||
/**Set GDDRAM Page Start Address. */
|
||||
#define SSD1306_SETSTARTPAGE 0XB0
|
||||
/** Set COM output scan direction normal. */
|
||||
#define SSD1306_COMSCANINC 0xC0
|
||||
/** Set COM output scan direction reversed. */
|
||||
#define SSD1306_COMSCANDEC 0xC8
|
||||
/** Set Display Offset. */
|
||||
#define SSD1306_SETDISPLAYOFFSET 0xD3
|
||||
/** Sets COM signals pin configuration to match the OLED panel layout. */
|
||||
#define SSD1306_SETCOMPINS 0xDA
|
||||
/** This command adjusts the VCOMH regulator output. */
|
||||
#define SSD1306_SETVCOMDETECT 0xDB
|
||||
/** Set Display Clock Divide Ratio/ Oscillator Frequency. */
|
||||
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
|
||||
/** Set Pre-charge Period */
|
||||
#define SSD1306_SETPRECHARGE 0xD9
|
||||
/** Deactivate scroll */
|
||||
#define SSD1306_DEACTIVATE_SCROLL 0x2E
|
||||
/** No Operation Command. */
|
||||
#define SSD1306_NOP 0XE3
|
||||
//------------------------------------------------------------------------------
|
||||
/** Set Pump voltage value: (30H~33H) 6.4, 7.4, 8.0 (POR), 9.0. */
|
||||
#define SH1106_SET_PUMP_VOLTAGE 0X30
|
||||
/** First byte of set charge pump mode */
|
||||
#define SH1106_SET_PUMP_MODE 0XAD
|
||||
/** Second byte charge pump on. */
|
||||
#define SH1106_PUMP_ON 0X8B
|
||||
/** Second byte charge pump off. */
|
||||
#define SH1106_PUMP_OFF 0X8A
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* @struct DevType
|
||||
* @brief Device initialization structure.
|
||||
*/
|
||||
struct DevType {
|
||||
/**
|
||||
* Pointer to initialization command bytes.
|
||||
*/
|
||||
const uint8_t* initcmds;
|
||||
/**
|
||||
* Number of initialization bytes.
|
||||
*/
|
||||
const uint8_t initSize;
|
||||
/**
|
||||
* Width of the diaplay in pixels.
|
||||
*/
|
||||
const uint8_t lcdWidth;
|
||||
/**
|
||||
* Height of the display in pixels.
|
||||
*/
|
||||
const uint8_t lcdHeight;
|
||||
/**
|
||||
* Column offset RAM to display. Used to pick start column of SH1106.
|
||||
*/
|
||||
const uint8_t colOffset;
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
// this section is based on https://github.com/adafruit/Adafruit_SSD1306
|
||||
/** Initialization commands for a 128x32 SSD1306 oled display. */
|
||||
static const uint8_t MEM_TYPE Adafruit128x32init[] = {
|
||||
// Init sequence for Adafruit 128x32 OLED module
|
||||
0x00, // Set to command mode
|
||||
SSD1306_DISPLAYOFF,
|
||||
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
||||
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
||||
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
||||
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
||||
SSD1306_MEMORYMODE, 0x02, // page mode
|
||||
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
||||
SSD1306_COMSCANDEC, // column scan direction reversed
|
||||
SSD1306_SETCOMPINS, 0x02, // sequential COM pins, disable remap
|
||||
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
||||
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
||||
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
||||
SSD1306_DISPLAYALLON_RESUME,
|
||||
SSD1306_NORMALDISPLAY,
|
||||
SSD1306_DISPLAYON
|
||||
};
|
||||
/** Initialize a 128x32 SSD1306 oled display. */
|
||||
static const DevType MEM_TYPE Adafruit128x32 = {
|
||||
Adafruit128x32init,
|
||||
sizeof(Adafruit128x32init),
|
||||
128,
|
||||
32,
|
||||
0
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
// This section is based on https://github.com/adafruit/Adafruit_SSD1306
|
||||
/** Initialization commands for a 128x64 SSD1306 oled display. */
|
||||
static const uint8_t MEM_TYPE Adafruit128x64init[] = {
|
||||
// Init sequence for Adafruit 128x64 OLED module
|
||||
0x00, // Set to command mode
|
||||
SSD1306_DISPLAYOFF,
|
||||
SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80
|
||||
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
||||
SSD1306_SETDISPLAYOFFSET, 0x0, // no offset
|
||||
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||
SSD1306_CHARGEPUMP, 0x14, // internal vcc
|
||||
SSD1306_MEMORYMODE, 0x02, // page mode
|
||||
SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0
|
||||
SSD1306_COMSCANDEC, // column scan direction reversed
|
||||
SSD1306_SETCOMPINS, 0x12, // alt COM pins, disable remap
|
||||
SSD1306_SETCONTRAST, 0x7F, // contrast level 127
|
||||
SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15)
|
||||
SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level
|
||||
SSD1306_DISPLAYALLON_RESUME,
|
||||
SSD1306_NORMALDISPLAY,
|
||||
SSD1306_DISPLAYON
|
||||
};
|
||||
/** Initialize a 128x64 oled display. */
|
||||
static const DevType MEM_TYPE Adafruit128x64 = {
|
||||
Adafruit128x64init,
|
||||
sizeof(Adafruit128x64init),
|
||||
128,
|
||||
64,
|
||||
0
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
// This section is based on https://github.com/stanleyhuangyc/MultiLCD
|
||||
/** Initialization commands for a 128x64 SH1106 oled display. */
|
||||
static const uint8_t MEM_TYPE SH1106_128x64init[] = {
|
||||
0x00, // Set to command mode
|
||||
SSD1306_DISPLAYOFF,
|
||||
SSD1306_SETSTARTPAGE | 0X0, // set page address
|
||||
SSD1306_SETCONTRAST, 0x80, // 128
|
||||
SSD1306_SEGREMAP | 0X1, // set segment remap
|
||||
SSD1306_NORMALDISPLAY, // normal / reverse
|
||||
SSD1306_SETMULTIPLEX, 0x3F, // ratio 64
|
||||
SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable
|
||||
SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts
|
||||
SSD1306_COMSCANDEC, // Com scan direction
|
||||
SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset
|
||||
SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division
|
||||
SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period
|
||||
SSD1306_SETCOMPINS, 0X12, // set COM pins
|
||||
SSD1306_SETVCOMDETECT, 0x40, // set vcomh
|
||||
SSD1306_DISPLAYON
|
||||
};
|
||||
/** Initialize a 128x64 oled SH1106 display. */
|
||||
static const DevType MEM_TYPE SH1106_128x64 = {
|
||||
SH1106_128x64init,
|
||||
sizeof(SH1106_128x64init),
|
||||
128,
|
||||
64,
|
||||
2 // SH1106 is a 132x64 controller. Use middle 128 columns.
|
||||
};
|
||||
#endif // SSD1306init_h
|
63
Sensors.cpp
63
Sensors.cpp
@@ -65,61 +65,26 @@ decide to ignore the <q ID> return and only react to <Q ID> triggers.
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#include "Sensors.h"
|
||||
#include "EEStore.h"
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// checks one defined sensors and prints _changed_ sensor state
|
||||
// to stream unless stream is NULL in which case only internal
|
||||
// state is updated. Then advances to next sensor which will
|
||||
// be checked att next invocation.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::checkAll(Print *stream){
|
||||
|
||||
if (firstSensor == NULL) return;
|
||||
if (readingSensor == NULL) readingSensor=firstSensor;
|
||||
|
||||
bool sensorstate = digitalRead(readingSensor->data.pin);
|
||||
|
||||
if (!sensorstate == readingSensor->active) { // active==true means sensorstate=0/false so sensor unchanged
|
||||
// no change
|
||||
if (readingSensor->latchdelay != 0) {
|
||||
// enable if you want to debug contact jitter
|
||||
//if (stream != NULL) StringFormatter::send(stream, F("JITTER %d %d\n"),
|
||||
// readingSensor->latchdelay, readingSensor->data.snum);
|
||||
readingSensor->latchdelay=0; // reset
|
||||
}
|
||||
} else if (readingSensor->latchdelay < 127) { // byte, max 255, good value unknown yet
|
||||
// change but first increase anti-jitter counter
|
||||
readingSensor->latchdelay++;
|
||||
} else {
|
||||
// make the change
|
||||
readingSensor->active = !sensorstate;
|
||||
readingSensor->latchdelay=0; // reset
|
||||
if (stream != NULL) StringFormatter::send(stream, F("<%c %d>\n"), readingSensor->active ? 'Q' : 'q', readingSensor->data.snum);
|
||||
}
|
||||
|
||||
readingSensor=readingSensor->nextSensor;
|
||||
} // Sensor::checkAll
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// prints all sensor states to stream
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void Sensor::printAll(Print *stream){
|
||||
|
||||
void Sensor::checkAll(){
|
||||
|
||||
for(Sensor * tt=firstSensor;tt!=NULL;tt=tt->nextSensor){
|
||||
if (stream != NULL)
|
||||
StringFormatter::send(stream, F("<%c %d>\n"), tt->active ? 'Q' : 'q', tt->data.snum);
|
||||
tt->signal=tt->signal*(1.0-SENSOR_DECAY)+digitalRead(tt->data.pin)*SENSOR_DECAY;
|
||||
|
||||
if(!tt->active && tt->signal<0.5){
|
||||
tt->active=true;
|
||||
} else if(tt->active && tt->signal>0.9){
|
||||
tt->active=false;
|
||||
}
|
||||
} // loop over all sensors
|
||||
} // Sensor::printAll
|
||||
|
||||
} // Sensor::checkAll
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -143,7 +108,7 @@ Sensor *Sensor::create(int snum, int pin, int pullUp){
|
||||
tt->data.pin=pin;
|
||||
tt->data.pullUp=(pullUp==0?LOW:HIGH);
|
||||
tt->active=false;
|
||||
tt->latchdelay=0;
|
||||
tt->signal=1;
|
||||
pinMode(pin,INPUT); // set mode to input
|
||||
digitalWrite(pin,pullUp); // don't use Arduino's internal pull-up resistors for external infrared sensors --- each sensor must have its own 1K external pull-up resistor
|
||||
|
||||
@@ -172,7 +137,6 @@ bool Sensor::remove(int n){
|
||||
else
|
||||
pp->nextSensor=tt->nextSensor;
|
||||
|
||||
if (readingSensor==tt) readingSensor=tt->nextSensor;
|
||||
free(tt);
|
||||
|
||||
return true;
|
||||
@@ -210,4 +174,3 @@ void Sensor::store(){
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Sensor *Sensor::firstSensor=NULL;
|
||||
Sensor *Sensor::readingSensor=NULL;
|
||||
|
@@ -31,18 +31,16 @@ struct SensorData {
|
||||
|
||||
struct Sensor{
|
||||
static Sensor *firstSensor;
|
||||
static Sensor *readingSensor;
|
||||
SensorData data;
|
||||
boolean active;
|
||||
byte latchdelay;
|
||||
float signal;
|
||||
Sensor *nextSensor;
|
||||
static void load();
|
||||
static void store();
|
||||
static Sensor *create(int, int, int);
|
||||
static Sensor* get(int);
|
||||
static bool remove(int);
|
||||
static void checkAll(Print *);
|
||||
static void printAll(Print *);
|
||||
static void checkAll();
|
||||
}; // Sensor
|
||||
|
||||
#endif
|
||||
|
@@ -22,9 +22,13 @@
|
||||
#if defined(ARDUINO_ARCH_SAMD)
|
||||
// Some processors use a gcc compiler that renames va_list!!!
|
||||
#include <cstdarg>
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
#else
|
||||
Print * StringFormatter::diagSerial= &SerialUSB;
|
||||
|
||||
#elif defined(ARDUINO_ARCH_AVR)
|
||||
Print * StringFormatter::diagSerial= &Serial;
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
Print * StringFormatter::diagSerial=&Serial;
|
||||
#define __FlashStringHelper char
|
||||
#endif
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
@@ -33,27 +37,23 @@ bool Diag::ACK=false;
|
||||
bool Diag::CMD=false;
|
||||
bool Diag::WIFI=false;
|
||||
bool Diag::WITHROTTLE=false;
|
||||
bool Diag::ETHERNET=false;
|
||||
bool Diag::LCN=false;
|
||||
|
||||
|
||||
void StringFormatter::diag( const FSH* input...) {
|
||||
if (!diagSerial) return;
|
||||
diagSerial->print(F("<* "));
|
||||
void StringFormatter::diag( const __FlashStringHelper* input...) {
|
||||
if (!diagSerial) return;
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(diagSerial,input,args);
|
||||
diagSerial->print(F(" *>\n"));
|
||||
}
|
||||
|
||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) {
|
||||
va_list args;
|
||||
|
||||
// Issue the LCD as a diag first
|
||||
send(diagSerial,F("<* LCD%d:"),row);
|
||||
diag(F("\nLCD%d:"),row);
|
||||
va_start(args, input);
|
||||
send2(diagSerial,input,args);
|
||||
send(diagSerial,F(" *>\n"));
|
||||
diag(F("\n"));
|
||||
|
||||
if (!LCDDisplay::lcdDisplay) return;
|
||||
LCDDisplay::lcdDisplay->setRow(row);
|
||||
@@ -61,25 +61,25 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
send2(LCDDisplay::lcdDisplay,input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::send(Print * stream, const FSH* input...) {
|
||||
void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) {
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(stream,input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::send(Print & stream, const FSH* input...) {
|
||||
void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) {
|
||||
va_list args;
|
||||
va_start(args, input);
|
||||
send2(&stream,input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
void StringFormatter::send2(Print * stream,const __FlashStringHelper* 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=GETFLASH(flash+i);
|
||||
char c=pgm_read_byte_near(flash+i);
|
||||
if (c=='\0') return;
|
||||
if(c!='%') { stream->print(c); continue; }
|
||||
|
||||
@@ -90,14 +90,14 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
|
||||
formatContinues=false;
|
||||
i++;
|
||||
c=GETFLASH(flash+i);
|
||||
c=pgm_read_byte_near(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 FSH*)va_arg(args, char*)); break;
|
||||
case 'S': stream->print((const FSH*)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 '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;
|
||||
@@ -137,12 +137,12 @@ void StringFormatter::printEscapes(Print * stream,char * input) {
|
||||
}
|
||||
}
|
||||
|
||||
void StringFormatter::printEscapes(Print * stream, const FSH * input) {
|
||||
void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) {
|
||||
|
||||
if (!stream) return;
|
||||
char* flash=(char*)input;
|
||||
for(int i=0; ; ++i) {
|
||||
char c=GETFLASH(flash+i);
|
||||
char c=pgm_read_byte_near(flash+i);
|
||||
printEscape(stream,c);
|
||||
if (c=='\0') return;
|
||||
}
|
||||
@@ -185,5 +185,4 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
|
||||
}
|
||||
if (!formatLeft) stream->print(value, DEC);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -19,10 +19,12 @@
|
||||
#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"
|
||||
@@ -32,31 +34,28 @@ class Diag {
|
||||
static bool CMD;
|
||||
static bool WIFI;
|
||||
static bool WITHROTTLE;
|
||||
static bool ETHERNET;
|
||||
static bool LCN;
|
||||
|
||||
};
|
||||
|
||||
class StringFormatter
|
||||
{
|
||||
public:
|
||||
static void send(Print * serial, const FSH* input...);
|
||||
static void send(Print & serial, const FSH* input...);
|
||||
static void send(Print * serial, const __FlashStringHelper* input...);
|
||||
static void send(Print & serial, const __FlashStringHelper* input...);
|
||||
|
||||
static void printEscapes(Print * serial,char * input);
|
||||
static void printEscapes(Print * serial,const FSH* input);
|
||||
static void printEscapes(Print * serial,const __FlashStringHelper* input);
|
||||
static void printEscape(Print * serial, char c);
|
||||
|
||||
// DIAG support
|
||||
static Print * diagSerial;
|
||||
static void diag( const FSH* input...);
|
||||
static void lcd(byte row, const FSH* input...);
|
||||
static void diag( const __FlashStringHelper* input...);
|
||||
static void lcd(byte row, const __FlashStringHelper* input...);
|
||||
static void printEscapes(char * input);
|
||||
static void printEscape( char c);
|
||||
|
||||
private:
|
||||
static void send2(Print * serial, const FSH* input,va_list args);
|
||||
static void send2(Print * serial, const __FlashStringHelper* input,va_list args);
|
||||
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
||||
|
||||
};
|
||||
#endif
|
||||
#endif
|
94
Timer.cpp
Normal file
94
Timer.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// This file is copied from https://github.com/davidcutting42/ArduinoTimers
|
||||
// All Credit to David Cutting
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO)
|
||||
|
||||
#if defined(SAMC21)
|
||||
#include "ATSAMC21G/Timer.h"
|
||||
#else
|
||||
#include "ATSAMD21G/Timer.h"
|
||||
#endif
|
||||
|
||||
Timer TimerA(TCC0);
|
||||
Timer TimerB(TCC1);
|
||||
Timer TimerC(TCC2);
|
||||
|
||||
void TCC0_Handler() {
|
||||
if(TCC0->INTFLAG.bit.OVF) {
|
||||
TCC0->INTFLAG.bit.OVF = 1;
|
||||
TimerA.isrCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC1_Handler() {
|
||||
if(TCC1->INTFLAG.bit.OVF) {
|
||||
TCC1->INTFLAG.bit.OVF = 1;
|
||||
TimerB.isrCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void TCC2_Handler() {
|
||||
if(TCC2->INTFLAG.bit.OVF) {
|
||||
TCC2->INTFLAG.bit.OVF = 1;
|
||||
TimerC.isrCallback();
|
||||
}
|
||||
}
|
||||
#elif 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();
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
|
||||
#include "ATMEGA4809/Timer.h"
|
||||
|
||||
Timer TimerA(0);
|
||||
|
||||
ISR(TCA0_OVF_vect) {
|
||||
TimerA.isrCallback();
|
||||
}
|
||||
|
||||
#endif
|
189
Transport.cpp
Normal file
189
Transport.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
#include "NetworkInterface.h"
|
||||
#include "Transport.h"
|
||||
|
||||
#ifdef DCCEX_ENABLED
|
||||
#include "RingStream.h"
|
||||
#endif
|
||||
|
||||
extern bool diagNetwork;
|
||||
extern uint8_t diagNetworkClient;
|
||||
|
||||
template<class S, class C, class U>
|
||||
bool Transport<S,C,U>::setup(NetworkInterface *nw) {
|
||||
t = new TransportProcessor();
|
||||
|
||||
if (protocol == TCP) {
|
||||
connectionPool(server); // server should have started here so create the connection pool only for TCP though
|
||||
t->udp = 0;
|
||||
} else {
|
||||
connectionPool(udp);
|
||||
t->udp = udp;
|
||||
}
|
||||
t->nwi = nw; // The TransportProcessor needs to know which Interface he is connected to
|
||||
connected = true; // server & clients which will recieve/send data have all e setup and are available
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class S, class C, class U>
|
||||
void Transport<S,C,U>::loop() {
|
||||
switch (protocol)
|
||||
{
|
||||
case UDPR:
|
||||
{
|
||||
udpHandler(udp);
|
||||
break;
|
||||
};
|
||||
case TCP:
|
||||
{
|
||||
DBG(F("Transport: %s"), this->transport == WIFI ? "WIFI" : "ETHERNET");
|
||||
tcpSessionHandler(server);
|
||||
};
|
||||
case MQTT:
|
||||
{
|
||||
// MQTT
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template<class S, class C, class U>
|
||||
void Transport<S, C, U>::connectionPool(S *server)
|
||||
{
|
||||
for (int i = 0; i < Transport::maxConnections; i++)
|
||||
{
|
||||
clients[i] = server->accept();
|
||||
connections[i].client = &clients[i];
|
||||
memset(connections[i].overflow, 0, MAX_OVERFLOW);
|
||||
connections[i].id = i;
|
||||
TRC(F("TCP Connection pool: [%d:%x]"), i, connections[i].client);
|
||||
}
|
||||
}
|
||||
template<class S, class C, class U>
|
||||
void Transport<S, C, U>::connectionPool(U *udp)
|
||||
{
|
||||
for (int i = 0; i < Transport::maxConnections; i++)
|
||||
{
|
||||
// clients[i] = server->accept();
|
||||
// connections[i].client = &clients[i];
|
||||
memset(connections[i].overflow, 0, MAX_OVERFLOW);
|
||||
connections[i].id = i;
|
||||
|
||||
TRC(F("UDP Connection pool: [%d:%x]"), i, udp);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @todo implement UDP properly
|
||||
*
|
||||
* @tparam S
|
||||
* @tparam C
|
||||
* @tparam U
|
||||
*/
|
||||
|
||||
template<class S, class C, class U>
|
||||
void Transport<S, C, U>::udpHandler(U* udp)
|
||||
{
|
||||
int packetSize = udp->parsePacket();
|
||||
if (packetSize > 0)
|
||||
{
|
||||
TRC(F("Received packet of size:[%d]"), packetSize);
|
||||
IPAddress remote = udp->remoteIP();
|
||||
char portBuffer[6];
|
||||
TRC(F("From: [%d.%d.%d.%d: %s]"), remote[0], remote[1], remote[2], remote[3], utoa(udp->remotePort(), portBuffer, 10)); // DIAG has issues with unsigend int's so go through utoa
|
||||
|
||||
udp->read(t->buffer, MAX_ETH_BUFFER);
|
||||
t->buffer[packetSize] = 0; // terminate buffer
|
||||
t->readStream(&connections[0], false ); // there is only one connection for UDP; reading into the buffer has been done
|
||||
|
||||
memset(t->buffer, 0, MAX_ETH_BUFFER); // reset PacktBuffer
|
||||
return;
|
||||
|
||||
// send the reply
|
||||
// udp.beginPacket(udp.remoteIP(), udp.remotePort());
|
||||
// parse(&udp, (byte *)buffer, true); //////////// Put into the TransportProcessor Attn the default udp TX buffer on ethernet is 24 on wifi its 256 ??
|
||||
// udp.endPacket();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief As tcpHandler but this time the connections are kept open (thus creating a statefull session) as long as the client doesn't disconnect. A connection
|
||||
* pool has been setup beforehand and determines the number of available sessions depending on the network hardware. Commands crossing packet boundaries will be captured
|
||||
*
|
||||
*/
|
||||
template<class S, class C, class U>
|
||||
void Transport<S,C,U>::tcpSessionHandler(S* server)
|
||||
{
|
||||
// get client from the server
|
||||
C client = server->accept();
|
||||
|
||||
// check for new client
|
||||
if (client)
|
||||
{
|
||||
for (byte i = 0; i < maxConnections; i++)
|
||||
{
|
||||
if (!clients[i])
|
||||
{
|
||||
// On accept() the EthernetServer doesn't track the client anymore
|
||||
// so we store it in our client array
|
||||
clients[i] = client;
|
||||
INFO(F("New Client: [%d:%x]"), i, clients[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for incoming data from all possible clients
|
||||
for (byte i = 0; i < maxConnections; i++)
|
||||
{
|
||||
if (clients[i] && clients[i].available() > 0)
|
||||
{
|
||||
t->readStream(&connections[i], true);
|
||||
}
|
||||
// stop any clients which disconnect
|
||||
for (byte i = 0; i < maxConnections; i++)
|
||||
{
|
||||
if (clients[i] && !clients[i].connected())
|
||||
{
|
||||
INFO(F("Disconnect client #%d"), i);
|
||||
clients[i].stop();
|
||||
connections[i].isProtocolDefined = false;
|
||||
if (diagNetworkClient == i && diagNetwork)
|
||||
{
|
||||
diagNetwork = false;
|
||||
NetworkDiag::resetDiagOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class S, class C, class U>
|
||||
Transport<S,C,U>::Transport(){}
|
||||
template<class S, class C, class U>
|
||||
Transport<S,C,U>::~Transport(){}
|
||||
|
||||
// explicitly instatiate to get the relevant copies for ethernet / wifi build @compile time
|
||||
template class Transport<EthernetServer,EthernetClient,EthernetUDP>;
|
||||
template class Transport<WiFiServer, WiFiClient, WiFiUDP>;
|
97
Transport.h
Normal file
97
Transport.h
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef Transport_h
|
||||
#define Transport_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Ethernet.h>
|
||||
#include <WiFiEspAT.h>
|
||||
|
||||
#include "NetworkConfig.h"
|
||||
#include "NetworkInterface.h"
|
||||
#include "TransportProcessor.h"
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DCCEX, // if char[0] = < opening bracket the client should be a JMRI / DCC EX client_h
|
||||
WITHROTTLE, //
|
||||
HTTP, // If char[0] = G || P || D; if P then char [1] = U || O || A
|
||||
N_DIAG, // '#' send form a telnet client as FIRST message will then reroute all DIAG messages to that client whilst being able to send jmri type commands
|
||||
UNKNOWN_PROTOCOL
|
||||
} appProtocol;
|
||||
|
||||
// Needed forward declarations
|
||||
struct Connection;
|
||||
class TransportProcessor;
|
||||
|
||||
using appProtocolCallback = void (*)(Connection* c, TransportProcessor* t);
|
||||
|
||||
struct Connection
|
||||
{
|
||||
uint8_t id; // initalized when the pool is setup
|
||||
Client *client; // idem
|
||||
char overflow[MAX_OVERFLOW]; // idem
|
||||
appProtocol p; // dynamically determined upon message reception; first message wins
|
||||
char delimiter = '\0'; // idem
|
||||
bool isProtocolDefined = false; // idem
|
||||
appProtocolCallback appProtocolHandler; // idem
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
template <class S, class C, class U> class Transport: public AbstractTransport
|
||||
{
|
||||
|
||||
private:
|
||||
C clients[MAX_SOCK_NUM]; // Client objects created by the connectionPool
|
||||
Connection connections[MAX_SOCK_NUM]; // All the connections build by the connectionPool
|
||||
bool connected = false;
|
||||
TransportProcessor* t; // pointer to the object which handles the incomming/outgoing flow
|
||||
|
||||
void udpHandler(U* udp); // Reads from a Udp socket - todo add incomming queue for processing when the flow is faster than we can process commands
|
||||
void tcpSessionHandler(S* server); // tcpSessionHandler -> connections are maintained open until close by the client
|
||||
void connectionPool(S* server); // allocates the Sockets at setup time and creates the Connections
|
||||
void connectionPool(U* udp); // allocates the UDP Sockets at setup time and creates the Connection
|
||||
|
||||
public:
|
||||
|
||||
uint8_t id;
|
||||
uint16_t port;
|
||||
uint8_t protocol; // TCP or UDP
|
||||
uint8_t transport; // WIFI or ETHERNET
|
||||
S* server; // WiFiServer or EthernetServer
|
||||
U* udp; // UDP socket object
|
||||
uint8_t maxConnections; // number of supported connections depending on the network equipment use
|
||||
|
||||
bool setup(NetworkInterface* nwi); // we get the callbacks from the NetworkInterface
|
||||
void loop();
|
||||
|
||||
bool isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
Transport<S,C,U>();
|
||||
~Transport<S,C,U>();
|
||||
|
||||
};
|
||||
|
||||
#endif // !Transport_h
|
512
TransportProcessor.cpp
Normal file
512
TransportProcessor.cpp
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
#include "NetworkInterface.h"
|
||||
#include "HttpRequest.h"
|
||||
#include "TransportProcessor.h"
|
||||
|
||||
#ifdef DCCEX_ENABLED
|
||||
|
||||
#include "DCCEXParser.h"
|
||||
#include "WiThrottle.h"
|
||||
#include "MemStream.h"
|
||||
|
||||
DCCEXParser dccParser;
|
||||
|
||||
#endif
|
||||
|
||||
HttpRequest httpReq;
|
||||
|
||||
uint16_t _rseq[MAX_SOCK_NUM] = {0}; // sequence number for packets recieved per connection
|
||||
uint16_t _sseq[MAX_SOCK_NUM] = {0}; // sequence number for replies send per connection
|
||||
uint16_t _pNum = 0; // number of total packets recieved
|
||||
uint64_t _tPayload = 0; // number of total bytes recieved
|
||||
unsigned int _nCmds = 0; // total number of commands processed
|
||||
|
||||
char protocolName[5][11] = {"JMRI", "WITHROTTLE", "HTTP", "DIAG", "UNKNOWN"}; // change for Progmem
|
||||
|
||||
bool diagNetwork = false; // if true diag data will be send to the connected telnet client
|
||||
uint8_t diagNetworkClient = 0; // client id for diag output
|
||||
|
||||
#ifdef DCCEX_ENABLED
|
||||
|
||||
void dumpRingStreamBuffer(byte *b, int len)
|
||||
{
|
||||
TRC(F("RingStream buffer length [%d] out of [%d] bytes"), strlen((char *)b), len);
|
||||
TRC(F("%e"), b);
|
||||
}
|
||||
|
||||
RingStream streamer(512); // buffer into which to feed the commands for handling; there will not be an immediate reply
|
||||
// as this is async written to another RingStream i.e. we have to see where in the loop we
|
||||
// generate the replies.
|
||||
|
||||
void sendWiThrottleToDCC(Connection *c, TransportProcessor *t, bool blocking)
|
||||
{
|
||||
|
||||
byte *_buffer = streamer.getBuffer();
|
||||
memset(_buffer, 0, 512); // clear out the _buffer
|
||||
WiThrottle *wt = WiThrottle::getThrottle(c->id); // get a throttle for the Connection; will be created if it doesn't exist
|
||||
|
||||
TRC(F("WiThrottle [%x:%x] parsing: [%e]"), wt, _buffer, t->command);
|
||||
|
||||
wt->parse(&streamer, (byte *)t->command); // get the response; not all commands will produce a reply
|
||||
if (streamer.count() != -1)
|
||||
{
|
||||
dumpRingStreamBuffer(_buffer, 512);
|
||||
TRC(F("UDP %x"), t->udp);
|
||||
|
||||
if ( t->udp != 0) {
|
||||
TRC(F("Sending UDP WiThrottle response ..."));
|
||||
t->udp->beginPacket(t->udp->remoteIP(), t->udp->remotePort());
|
||||
t->udp->write(_buffer, strlen((char *)_buffer));
|
||||
t->udp->endPacket();
|
||||
} else if (c->client->connected()) {
|
||||
c->client->write(_buffer, strlen((char *)_buffer));
|
||||
}
|
||||
}
|
||||
streamer.resetStream();
|
||||
}
|
||||
|
||||
void sendJmriToDCC(Connection *c, TransportProcessor *t, bool blocking)
|
||||
{
|
||||
MemStream streamer((byte *)t->command, MAX_ETH_BUFFER, MAX_ETH_BUFFER, true);
|
||||
|
||||
DBG(F("DCC parsing: [%e]"), t->command);
|
||||
// as we use buffer for recv and send we have to reset the write position
|
||||
streamer.setBufferContentPosition(0, 0);
|
||||
dccParser.parse(&streamer, (byte *)t->command, true); // set to true to that the execution in DCC is sync
|
||||
|
||||
if (streamer.available() == 0)
|
||||
{
|
||||
DBG(F("No response"));
|
||||
}
|
||||
else
|
||||
{
|
||||
t->command[streamer.available()] = '\0'; // mark end of buffer, so it can be used as a string later
|
||||
DBG(F("Response: %s"), t->command);
|
||||
if (c->client->connected())
|
||||
{
|
||||
c->client->write((byte *)t->command, streamer.available());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sending a reply by using the StringFormatter (this will result in every byte send individually which may/will create an important Network overhead).
|
||||
* Here we hook back into the DCC code for actually processing the command using a DCCParser. Alternatively we could use MemeStream in order to build the entiere reply
|
||||
* before ending it.
|
||||
*
|
||||
* @param stream Actually the Client to whom to send the reply. As Clients implement Print this is working
|
||||
* @param t TransportProcessor used for accessing the buffers to be send
|
||||
* @param blocking if set to true will instruct the DCC code to not use the async callback functions
|
||||
*/
|
||||
|
||||
void sendToDCC(Connection *c, TransportProcessor *t, bool blocking)
|
||||
{
|
||||
|
||||
switch (c->p)
|
||||
{
|
||||
case WITHROTTLE:
|
||||
{
|
||||
sendWiThrottleToDCC(c, t, blocking);
|
||||
break;
|
||||
}
|
||||
case DCCEX:
|
||||
{
|
||||
sendJmriToDCC(c, t, blocking);
|
||||
break;
|
||||
}
|
||||
case N_DIAG:
|
||||
case HTTP:
|
||||
case UNKNOWN_PROTOCOL:
|
||||
{
|
||||
// we shall never get here they should have been caught before
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* @brief Sending a reply without going through the StringFormatter. Sends the repy in one go
|
||||
*
|
||||
* @param client Client who send the command to which the reply shall be send
|
||||
* @param command Command initaliy recieved to be echoed back
|
||||
*/
|
||||
void sendReply(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
byte reply[MAX_ETH_BUFFER];
|
||||
byte *response;
|
||||
char *number;
|
||||
char *command = t->command;
|
||||
char seqNumber[6];
|
||||
int i = 0;
|
||||
|
||||
memset(reply, 0, MAX_ETH_BUFFER); // reset reply
|
||||
|
||||
// This expects messages to be send with a trailing sequence number <R 1 1 1:0>
|
||||
// as of my stress test program to verify the arrival of messages
|
||||
|
||||
number = strrchr(command, ':'); // replace the int after the last ':' if number != 0
|
||||
if (number != 0)
|
||||
{
|
||||
while (&command[i] != number)
|
||||
{ // copy command into the reply upto the last ':'
|
||||
reply[i] = command[i];
|
||||
i++;
|
||||
}
|
||||
strcat((char *)reply, ":");
|
||||
itoa(_sseq[c->id], seqNumber, 10);
|
||||
strcat((char *)reply, seqNumber);
|
||||
strcat((char *)reply, ">");
|
||||
response = reply;
|
||||
}
|
||||
else
|
||||
{
|
||||
response = (byte *)command;
|
||||
}
|
||||
|
||||
DBG(F("Response: [%e]"), (char *)response);
|
||||
if (c->client->connected())
|
||||
{
|
||||
c->client->write(response, strlen((char *)response));
|
||||
_sseq[c->id]++;
|
||||
DBG(F("Send"));
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief creates a HttpRequest object for the user callback. Some conditions apply esp reagrding the length of the items in the Request
|
||||
* can be found in @file HttpRequest.h
|
||||
*
|
||||
* @param client Client object from whom we receievd the data
|
||||
* @param c id of the Client object
|
||||
*/
|
||||
void httpProcessor(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
|
||||
if (httpReq.callback == 0)
|
||||
return; // no callback i.e. nothing to do
|
||||
/**
|
||||
* @todo look for jmri formatted uris and execute those if there is no callback. If no command found ignore and
|
||||
* ev. send a 401 error back
|
||||
*/
|
||||
uint8_t i, l = 0;
|
||||
ParsedRequest preq;
|
||||
l = strlen((char *)t->buffer);
|
||||
for (i = 0; i < l; i++)
|
||||
{
|
||||
httpReq.parseRequest((char)t->buffer[i]);
|
||||
}
|
||||
if (httpReq.endOfRequest())
|
||||
{
|
||||
preq = httpReq.getParsedRequest();
|
||||
httpReq.callback(&preq, c->client);
|
||||
httpReq.resetRequest();
|
||||
} // else do nothing and continue with the next packet
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the App Protocol. The detection id done upon the very first message recieved. The client will then be bound to that protocol. Its very brittle
|
||||
* as e.g. The N message as first message for WiThrottle is not a requirement by the protocol; If any client talking Withrottle doesn't implement this the detection
|
||||
* will default to JMRI. For HTTP we base this only on a subset of th HTTP verbs which can be used.
|
||||
*
|
||||
* @param a First character of the recieved buffer upon first connection
|
||||
* @param b Second character of the recieved buffer upon first connection
|
||||
* @return appProtocol
|
||||
*/
|
||||
appProtocol setAppProtocol(char a, char b, Connection *c)
|
||||
{
|
||||
appProtocol p;
|
||||
switch (a)
|
||||
{
|
||||
case 'G': // GET
|
||||
case 'C': // CONNECT
|
||||
case 'O': // OPTIONS
|
||||
case 'T': // TRACE
|
||||
{
|
||||
p = HTTP;
|
||||
break;
|
||||
}
|
||||
case 'D': // DELETE or D plux hex value
|
||||
{
|
||||
if (b == 'E')
|
||||
{
|
||||
p = HTTP;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = WITHROTTLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'P':
|
||||
{
|
||||
if (b == 'T' || b == 'R')
|
||||
{
|
||||
p = WITHROTTLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = HTTP; // PUT / PATCH / POST
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'H':
|
||||
{
|
||||
if (b == 'U')
|
||||
{
|
||||
p = WITHROTTLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = HTTP; // HEAD
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'M':
|
||||
case '*':
|
||||
case 'R':
|
||||
case 'Q': // That doesn't make sense as it's the Q or close on app level
|
||||
case 'N':
|
||||
{
|
||||
p = WITHROTTLE;
|
||||
break;
|
||||
}
|
||||
case '<':
|
||||
{
|
||||
p = DCCEX;
|
||||
break;
|
||||
}
|
||||
case '#':
|
||||
{
|
||||
p = DCCEX;
|
||||
INFO(F("\nDiagnostics routed to network client"));
|
||||
NetworkDiag::setDiagOut(c);
|
||||
diagNetwork = true;
|
||||
diagNetworkClient = c->id;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// here we don't know
|
||||
p = UNKNOWN_PROTOCOL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
INFO(F("Client speaks: [%s]"), protocolName[p]);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses the buffer to extract commands to be executed
|
||||
*
|
||||
*/
|
||||
void processStream(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
uint8_t i, j, k, l = 0;
|
||||
uint8_t *_buffer = t->buffer;
|
||||
|
||||
DBG(F("Buffer: [%e]"), _buffer);
|
||||
memset(t->command, 0, MAX_JMRI_CMD); // clear out the command
|
||||
|
||||
// copy overflow into the command
|
||||
if ((i = strlen(c->overflow)) != 0)
|
||||
{
|
||||
// DBG(F("Copy overflow to command: %e"), c->overflow);
|
||||
strncpy(t->command, c->overflow, i);
|
||||
k = i;
|
||||
}
|
||||
// reset the overflow
|
||||
memset(c->overflow, 0, MAX_OVERFLOW);
|
||||
|
||||
// check if there is again an overflow and copy if needed
|
||||
if ((i = strlen((char *)_buffer)) == MAX_ETH_BUFFER - 1)
|
||||
{
|
||||
// DBG(F("Possible overflow situation detected: %d "), i);
|
||||
j = i;
|
||||
while (_buffer[i] != c->delimiter)
|
||||
{
|
||||
i--;
|
||||
}
|
||||
i++; // start of the buffer to copy
|
||||
l = i;
|
||||
k = j - i; // length to copy
|
||||
|
||||
for (j = 0; j < k; j++, i++)
|
||||
{
|
||||
c->overflow[j] = _buffer[i];
|
||||
// DBG(F("%d %d %d %c"),k,j,i, buffer[i]);
|
||||
}
|
||||
_buffer[l] = '\0'; // terminate buffer just after the last '>'
|
||||
// DBG(F("New buffer: [%s] New overflow: [%s]"), (char*) buffer, c->overflow );
|
||||
}
|
||||
// breakup the buffer using its changed length
|
||||
i = 0;
|
||||
k = strlen(t->command); // current length of the command buffer telling us where to start copy in
|
||||
l = strlen((char *)_buffer);
|
||||
// DBG(F("Command buffer cid[%d]: [%s]:[%d:%d:%d:%x]"), c->id, t->command, i, l, k, c->delimiter );
|
||||
unsigned long _startT = micros();
|
||||
_nCmds = 0;
|
||||
while (i < l)
|
||||
{
|
||||
// DBG(F("l: %d - k: %d - i: %d - %c"), l, k, i, _buffer[i]);
|
||||
t->command[k] = _buffer[i];
|
||||
if (_buffer[i] == c->delimiter)
|
||||
{ // closing bracket need to fix if there is none before an opening bracket ?
|
||||
|
||||
t->command[k + 1] = '\0';
|
||||
|
||||
DBG(F("Command: [%d:%e]"), _rseq[c->id], t->command);
|
||||
#ifdef DCCEX_ENABLED
|
||||
|
||||
sendToDCC(c, t, true); // send the command into the parser and replies back to the client
|
||||
#else
|
||||
sendReply(c, t); // standalone version without CS-EX integration
|
||||
#endif
|
||||
_rseq[c->id]++;
|
||||
_nCmds++;
|
||||
j = 0;
|
||||
k = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
k++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
unsigned long _endT = micros();
|
||||
char time[10] = {0};
|
||||
ultoa(_endT - _startT, time, 10);
|
||||
INFO(F("[%d] Commands processed in [%s]uS\n"), _nCmds, time);
|
||||
}
|
||||
|
||||
void echoProcessor(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
byte reply[MAX_ETH_BUFFER];
|
||||
|
||||
memset(reply, 0, MAX_ETH_BUFFER);
|
||||
sprintf((char *)reply, "ERROR: malformed content in [%s]\n", t->buffer);
|
||||
if (c->client->connected())
|
||||
{
|
||||
c->client->write(reply, strlen((char *)reply));
|
||||
_sseq[c->id]++;
|
||||
c->isProtocolDefined = false; // reset the protocol to not defined so that we can recover the next time
|
||||
}
|
||||
}
|
||||
void jmriProcessor(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
DBG(F("Processing JMRI ..."));
|
||||
processStream(c, t);
|
||||
}
|
||||
void withrottleProcessor(Connection *c, TransportProcessor *t)
|
||||
{
|
||||
DBG(F("Processing WiThrottle ..."));
|
||||
processStream(c, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads what is available on the incomming TCP stream and hands it over to the protocol handler.
|
||||
*
|
||||
* @param c Pointer to the connection struct contining relevant information handling the data from that connection
|
||||
*/
|
||||
|
||||
void TransportProcessor::readStream(Connection *c, bool read)
|
||||
{
|
||||
int count = 0;
|
||||
// read bytes from a TCP client if required
|
||||
if (read) {
|
||||
int len = c->client->read(buffer, MAX_ETH_BUFFER - 1); // count is the amount of data ready for reading, -1 if there is no data, 0 is the connection has been closed
|
||||
buffer[len] = 0;
|
||||
count = len;
|
||||
} else {
|
||||
count = strlen((char *)buffer);
|
||||
}
|
||||
|
||||
// figure out which protocol
|
||||
|
||||
if (!c->isProtocolDefined)
|
||||
{
|
||||
c->p = setAppProtocol(buffer[0], buffer[1], c);
|
||||
c->isProtocolDefined = true;
|
||||
switch (c->p)
|
||||
{
|
||||
case N_DIAG:
|
||||
case DCCEX:
|
||||
{
|
||||
c->delimiter = '>';
|
||||
c->appProtocolHandler = (appProtocolCallback)jmriProcessor;
|
||||
break;
|
||||
}
|
||||
case WITHROTTLE:
|
||||
{
|
||||
c->delimiter = '\n';
|
||||
c->appProtocolHandler = (appProtocolCallback)withrottleProcessor;
|
||||
break;
|
||||
}
|
||||
case HTTP:
|
||||
{
|
||||
c->appProtocolHandler = (appProtocolCallback)httpProcessor;
|
||||
httpReq.callback = nwi->getHttpCallback();
|
||||
break;
|
||||
}
|
||||
case UNKNOWN_PROTOCOL:
|
||||
{
|
||||
INFO(F("Requests will not be handeled and packet echoed back"));
|
||||
c->appProtocolHandler = (appProtocolCallback)echoProcessor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_pNum++;
|
||||
_tPayload = _tPayload + count;
|
||||
#ifdef DCCEX_ENABLED
|
||||
INFO(F("Client #[%d] received packet #[%d] of size:[%d/%d]"), c->id, _pNum, count, _tPayload);
|
||||
#else
|
||||
IPAddress remote = c->client->remoteIP();
|
||||
INFO(F("Client #[%d] Received packet #[%d] of size:[%d] from [%d.%d.%d.%d]"), c->id, _pNum, count, remote[0], remote[1], remote[2], remote[3]);
|
||||
#endif
|
||||
buffer[count] = '\0'; // terminate the string properly
|
||||
INFO(F("Packet: [%e]"), buffer);
|
||||
|
||||
// chop the buffer into CS / WiThrottle commands || assemble command across buffer read boundaries
|
||||
c->appProtocolHandler(c, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sending a reply by using the StringFormatter (this will result in every byte send individually which may/will create an important Network overhead).
|
||||
* Here we hook back into the DCC code for actually processing the command using a DCCParser. Alternatively we could use MemeStream in order to build the entiere reply
|
||||
* before ending it (cf. Scratch pad below)
|
||||
*
|
||||
* @param stream Actually the Client to whom to send the reply. As Clients implement Print this is working
|
||||
* @param command The reply to be send ( echo as in sendReply() )
|
||||
* @param blocking if set to true will instruct the DCC code to not use the async callback functions
|
||||
*/
|
||||
void parse(Print *stream, byte *command, bool blocking)
|
||||
{
|
||||
DBG(F("DCC parsing: [%e]"), command);
|
||||
// echo back (as mock parser )
|
||||
StringFormatter::send(stream, F("reply to: %s"), command);
|
||||
}
|
||||
|
||||
|
55
TransportProcessor.h
Normal file
55
TransportProcessor.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef TransportProcessor_h
|
||||
#define TransportProcessor_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Ethernet.h>
|
||||
#include <WiFiEspAT.h>
|
||||
|
||||
#include "Transport.h"
|
||||
#include "NetworkConfig.h"
|
||||
#include "NetworkInterface.h"
|
||||
|
||||
#ifdef DCCEX_ENABLED
|
||||
#include "RingStream.h"
|
||||
#endif
|
||||
|
||||
class TransportProcessor
|
||||
{
|
||||
private:
|
||||
#ifdef DCCEX_ENABLED
|
||||
void sendToDCC(Connection *c, TransportProcessor* t, bool blocking);
|
||||
#endif
|
||||
|
||||
|
||||
public:
|
||||
UDP *udp; // need to carry the single UDP server instance over to the processor for sending packest
|
||||
NetworkInterface *nwi;
|
||||
uint8_t buffer[MAX_ETH_BUFFER];
|
||||
char command[MAX_JMRI_CMD];
|
||||
|
||||
void readStream(Connection *c, bool read); // process incomming packets and processes them; if read = false the buffer has already been filled
|
||||
|
||||
TransportProcessor(){};
|
||||
~TransportProcessor(){};
|
||||
};
|
||||
|
||||
#endif // !Transport_h
|
20
Turnouts.cpp
20
Turnouts.cpp
@@ -21,20 +21,13 @@
|
||||
#include "Turnouts.h"
|
||||
#include "EEStore.h"
|
||||
#include "PWMServoDriver.h"
|
||||
#include "StringFormatter.h"
|
||||
#ifdef EESTOREDEBUG
|
||||
#include "DIAG.h"
|
||||
#endif
|
||||
|
||||
// print all turnout states to stream
|
||||
void Turnout::printAll(Print *stream){
|
||||
for (Turnout *tt = Turnout::firstTurnout; tt != NULL; tt = tt->nextTurnout)
|
||||
StringFormatter::send(stream, F("<H %d %d>\n"), tt->data.id, (tt->data.tStatus & STATUS_ACTIVE)!=0);
|
||||
} // Turnout::printAll
|
||||
|
||||
bool Turnout::activate(int n,bool state){
|
||||
#ifdef EESTOREDEBUG
|
||||
DIAG(F("Turnout::activate(%d,%d)"),n,state);
|
||||
DIAG(F("\nTurnout::activate(%d,%d)\n"),n,state);
|
||||
#endif
|
||||
Turnout * tt=get(n);
|
||||
if (tt==NULL) return false;
|
||||
@@ -53,13 +46,8 @@ bool Turnout::isActive(int n){
|
||||
// activate is virtual here so that it can be overridden by a non-DCC turnout mechanism
|
||||
void Turnout::activate(bool state) {
|
||||
#ifdef EESTOREDEBUG
|
||||
DIAG(F("Turnout::activate(%d)"),state);
|
||||
DIAG(F("\nTurnout::activate(%d)\n"),state);
|
||||
#endif
|
||||
if (data.address==LCN_TURNOUT_ADDRESS) {
|
||||
// A LCN turnout is transmitted to the LCN master.
|
||||
LCN::send('T',data.id,state);
|
||||
return; // The tStatus will be updated by a message from the LCN master, later.
|
||||
}
|
||||
if (state)
|
||||
data.tStatus|=STATUS_ACTIVE;
|
||||
else
|
||||
@@ -170,9 +158,9 @@ Turnout *Turnout::create(int id){
|
||||
#ifdef EESTOREDEBUG
|
||||
void Turnout::print(Turnout *tt) {
|
||||
if (tt->data.tStatus & STATUS_PWM )
|
||||
DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE);
|
||||
DIAG(F("Turnout %d ZeroAngle %d MoveAngle %d Status %d\n"),tt->data.id, tt->data.inactiveAngle, tt->data.moveAngle,tt->data.tStatus & STATUS_ACTIVE);
|
||||
else
|
||||
DIAG(F("Turnout %d Addr %d Subaddr %d Status %d"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE);
|
||||
DIAG(F("Turnout %d Addr %d Subaddr %d Status %d\n"),tt->data.id, tt->data.address, tt->data.subAddress,tt->data.tStatus & STATUS_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@@ -21,12 +21,11 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "DCC.h"
|
||||
#include "LCN.h"
|
||||
|
||||
const byte STATUS_ACTIVE=0x80; // Flag as activated
|
||||
const byte STATUS_PWM=0x40; // Flag as a PWM turnout
|
||||
const byte STATUS_PWMPIN=0x3F; // PWM pin 0-63
|
||||
const int LCN_TURNOUT_ADDRESS=-1; // spoof dcc address -1 indicates a LCN turnout
|
||||
|
||||
struct TurnoutData {
|
||||
int id;
|
||||
uint8_t tStatus; // has STATUS_ACTIVE, STATUS_PWM, STATUS_PWMPIN
|
||||
@@ -50,7 +49,6 @@ class Turnout {
|
||||
static Turnout *create(int id , byte pin , int activeAngle, int inactiveAngle);
|
||||
static Turnout *create(int id);
|
||||
void activate(bool state);
|
||||
static void printAll(Print *);
|
||||
#ifdef EESTOREDEBUG
|
||||
void print(Turnout *tt);
|
||||
#endif
|
||||
|
21
VirtualTimer.h
Normal file
21
VirtualTimer.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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
|
@@ -76,7 +76,7 @@ bool WiThrottle::areYouUsingThrottle(int cab) {
|
||||
// One instance of WiThrottle per connected client, so we know what the locos are
|
||||
|
||||
WiThrottle::WiThrottle( int wificlientid) {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l Creating new WiThrottle for client %d"),millis(),wificlientid);
|
||||
if (Diag::WITHROTTLE) DIAG(F("\n%l Creating new WiThrottle for client %d\n"),millis(),wificlientid);
|
||||
nextThrottle=firstThrottle;
|
||||
firstThrottle= this;
|
||||
clientid=wificlientid;
|
||||
@@ -104,7 +104,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
byte * cmd=cmdx;
|
||||
|
||||
heartBeat=millis();
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||
if (Diag::WITHROTTLE) DIAG(F("\n%l WiThrottle(%d)<-[%e]\n"),millis(),clientid,cmd);
|
||||
|
||||
if (initSent) {
|
||||
// Send power state if different than last sent
|
||||
@@ -133,8 +133,6 @@ 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
|
||||
}
|
||||
@@ -184,7 +182,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||
}
|
||||
}
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit\n"),millis(),clientid);
|
||||
delete this;
|
||||
break;
|
||||
}
|
||||
@@ -207,7 +205,6 @@ int WiThrottle::getLocoId(byte * cmd) {
|
||||
if (cmd[0]!='L' && cmd[0]!='S') return 0; // should not match any locos
|
||||
return getInt(cmd+1);
|
||||
}
|
||||
|
||||
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
char throttleChar=cmd[1];
|
||||
int locoid=getLocoId(cmd+3); // -1 for *
|
||||
@@ -215,20 +212,9 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
while(*aval !=';' && *aval !='\0') aval++;
|
||||
if (*aval) aval+=2; // skip ;>
|
||||
|
||||
// DIAG(F("Multithrottle aval=%c cab=%d"), aval[0],locoid);
|
||||
// DIAG(F("\nMultithrottle aval=%c cab=%d"), aval[0],locoid);
|
||||
switch(cmd[2]) {
|
||||
case '+': // add loco request
|
||||
if (cmd[3]=='*') {
|
||||
// M+* means get loco from prog track, then join tracks ready to drive away
|
||||
// Stash the things the callback will need later
|
||||
stashStream= stream;
|
||||
stashClient=stream->peekTargetMark();
|
||||
stashThrottleChar=throttleChar;
|
||||
stashInstance=this;
|
||||
// ask DCC to call us back when the loco id has been read
|
||||
DCC::getLocoId(getLocoCallback); // will remove any previous join
|
||||
return; // return nothing in stream as response is sent later in the callback
|
||||
}
|
||||
//return error if address zero requested
|
||||
if (locoid==0) {
|
||||
StringFormatter::send(stream, F("HMAddress '0' not supported!\n"), cmd[3] ,locoid);
|
||||
@@ -248,7 +234,7 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
//Get known Fn states from DCC
|
||||
for(int fKey=0; fKey<=28; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c<;>F%d%d\n"),throttleChar,cmd[3],fstate,fKey);
|
||||
}
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>V%d\n"), throttleChar, cmd[3], locoid, DCCToWiTSpeed(DCC::getThrottleSpeed(locoid)));
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>R%d\n"), throttleChar, cmd[3], locoid, DCC::getThrottleDirection(locoid));
|
||||
@@ -272,14 +258,14 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
|
||||
void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar, int cab){
|
||||
// Note cab=-1 for all cabs in the consist called throttleChar.
|
||||
// DIAG(F("Loco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
||||
// DIAG(F("\nLoco Action aval=%c%c throttleChar=%c, cab=%d"), aval[0],aval[1],throttleChar, cab);
|
||||
switch (aval[0]) {
|
||||
case 'V': // Vspeed
|
||||
{
|
||||
int witSpeed=getInt(aval+1);
|
||||
byte locospeed=WiTToDCCSpeed(getInt(aval+1));
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
DCC::setThrottle(myLocos[loco].cab, WiTToDCCSpeed(witSpeed), DCC::getThrottleDirection(myLocos[loco].cab));
|
||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, witSpeed);
|
||||
DCC::setThrottle(myLocos[loco].cab, locospeed, DCC::getThrottleDirection(myLocos[loco].cab));
|
||||
StringFormatter::send(stream,F("M%cA%c%d<;>V%d\n"), throttleChar, LorS(myLocos[loco].cab), myLocos[loco].cab, locospeed);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -366,10 +352,10 @@ void WiThrottle::loop(RingStream * stream) {
|
||||
void WiThrottle::checkHeartbeat() {
|
||||
// if eStop time passed... eStop any locos still assigned to this client and then drop the connection
|
||||
if(heartBeatEnable && (millis()-heartBeat > ESTOP_SECONDS*1000)) {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) eStop(%ds) timeout, drop connection"), millis(), clientid, ESTOP_SECONDS);
|
||||
if (Diag::WITHROTTLE) DIAG(F("\n\n%l WiThrottle(%d) eStop(%ds) timeout, drop connection\n"), millis(), clientid, ESTOP_SECONDS);
|
||||
LOOPLOCOS('*', -1) {
|
||||
if (myLocos[loco].throttle!='\0') {
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d"),millis(),myLocos[loco].cab);
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l eStopping cab %d\n"),millis(),myLocos[loco].cab);
|
||||
DCC::setThrottle(myLocos[loco].cab, 1, DCC::getThrottleDirection(myLocos[loco].cab)); // speed 1 is eStop
|
||||
}
|
||||
}
|
||||
@@ -379,24 +365,4 @@ void WiThrottle::checkHeartbeat() {
|
||||
|
||||
char WiThrottle::LorS(int cab) {
|
||||
return (cab<127)?'S':'L';
|
||||
}
|
||||
|
||||
// Drive Away feature. Callback handling
|
||||
|
||||
RingStream * WiThrottle::stashStream;
|
||||
WiThrottle * WiThrottle::stashInstance;
|
||||
byte WiThrottle::stashClient;
|
||||
char WiThrottle::stashThrottleChar;
|
||||
|
||||
void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
stashStream->mark(stashClient);
|
||||
if (locoid<0) StringFormatter::send(stashStream,F("HMNo loco found on prog track\n"));
|
||||
else {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+',LorS(locoid) };
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||
}
|
||||
stashStream->commit();
|
||||
}
|
||||
}
|
||||
|
10
WiThrottle.h
10
WiThrottle.h
@@ -60,14 +60,6 @@ class WiThrottle {
|
||||
void multithrottle(RingStream * stream, byte * cmd);
|
||||
void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab);
|
||||
void accessory(RingStream *, byte* cmd);
|
||||
void checkHeartbeat();
|
||||
|
||||
// callback stuff to support prog track acquire
|
||||
static RingStream * stashStream;
|
||||
static WiThrottle * stashInstance;
|
||||
static byte stashClient;
|
||||
static char stashThrottleChar;
|
||||
static void getLocoCallback(int16_t locoid);
|
||||
|
||||
void checkHeartbeat();
|
||||
};
|
||||
#endif
|
||||
|
@@ -1,23 +1,3 @@
|
||||
/*
|
||||
* © 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"
|
||||
@@ -64,7 +44,7 @@ void WifiInboundHandler::loop1() {
|
||||
|
||||
|
||||
if (pendingCipsend) {
|
||||
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||
if (Diag::WIFI) DIAG( F("\nWiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||
pendingCipsend=false;
|
||||
return;
|
||||
@@ -75,11 +55,11 @@ void WifiInboundHandler::loop1() {
|
||||
int clientId=inboundRing->read();
|
||||
if (clientId>=0) {
|
||||
int count=inboundRing->count();
|
||||
if (Diag::WIFI) DIAG(F("Wifi EXEC: %d %d:"),clientId,count);
|
||||
if (Diag::WIFI) DIAG(F("\nWifi EXEC: %d %d:"),clientId,count);
|
||||
byte cmd[count+1];
|
||||
for (int i=0;i<count;i++) cmd[i]=inboundRing->read();
|
||||
cmd[count]=0;
|
||||
if (Diag::WIFI) DIAG(F("%e"),cmd);
|
||||
if (Diag::WIFI) DIAG(F("%e\n"),cmd);
|
||||
|
||||
outboundRing->mark(clientId); // remember start of outbound data
|
||||
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||
@@ -193,11 +173,11 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||
loopState=ANYTHING;
|
||||
break;
|
||||
}
|
||||
if (Diag::WIFI) DIAG(F("Wifi inbound data(%d:%d):"),runningClientId,dataLength);
|
||||
if (Diag::WIFI) DIAG(F("\nWifi inbound data(%d:%d):"),runningClientId,dataLength);
|
||||
if (inboundRing->freeSpace()<=(dataLength+1)) {
|
||||
// This input would overflow the inbound ring, ignore it
|
||||
loopState=IPD_IGNORE_DATA;
|
||||
if (Diag::WIFI) DIAG(F("Wifi OVERFLOW IGNORING:"));
|
||||
if (Diag::WIFI) DIAG(F("\nWifi OVERFLOW IGNORING:"));
|
||||
break;
|
||||
}
|
||||
inboundRing->mark(runningClientId);
|
||||
@@ -243,10 +223,8 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||
|
||||
void WifiInboundHandler::purgeCurrentCIPSEND() {
|
||||
// A CIPSEND was sent but errored... or the client closed just toss it away
|
||||
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d"),clientPendingCIPSEND,currentReplySize);
|
||||
if (Diag::WIFI) DIAG(F("Wifi: DROPPING CIPSEND=%d,%d\n"),clientPendingCIPSEND,currentReplySize);
|
||||
for (int i=0;i<=currentReplySize;i++) outboundRing->read();
|
||||
pendingCipsend=false;
|
||||
clientPendingCIPSEND=-1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -15,12 +15,12 @@ class WifiInboundHandler {
|
||||
static WifiInboundHandler * singleton;
|
||||
|
||||
|
||||
enum INBOUND_STATE : byte {
|
||||
enum INBOUND_STATE {
|
||||
INBOUND_BUSY, // keep calling in loop()
|
||||
INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND
|
||||
};
|
||||
|
||||
enum LOOP_STATE : byte {
|
||||
enum LOOP_STATE {
|
||||
ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc...
|
||||
SKIPTOEND, // skip to newline
|
||||
|
||||
@@ -37,6 +37,9 @@ class WifiInboundHandler {
|
||||
|
||||
GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED
|
||||
GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED
|
||||
|
||||
//grbba removed following remark from Chris
|
||||
// GOT_CLIENT_ID3 // clientid prefix to CONNECTED / CLOSED
|
||||
};
|
||||
|
||||
|
||||
|
@@ -17,34 +17,29 @@
|
||||
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 "WifiInterface.h" /* config.h and defines.h included here */
|
||||
#include <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#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;
|
||||
|
||||
#ifndef WIFI_CONNECT_TIMEOUT
|
||||
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
|
||||
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
|
||||
// or ot will cause issues with the following commands.
|
||||
#define WIFI_CONNECT_TIMEOUT 16000
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Figure out number of serial ports depending on hardware
|
||||
//
|
||||
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define NUM_SERIAL 0
|
||||
#endif
|
||||
|
||||
@@ -57,13 +52,12 @@ Stream * WifiInterface::wifiStream;
|
||||
#endif
|
||||
|
||||
bool WifiInterface::setup(long serial_link_speed,
|
||||
const FSH *wifiESSID,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel) {
|
||||
const __FlashStringHelper *wifiESSID,
|
||||
const __FlashStringHelper *wifiPassword,
|
||||
const __FlashStringHelper *hostname,
|
||||
const int port) {
|
||||
|
||||
wifiSerialState wifiUp = WIFI_NOAT;
|
||||
bool wifiUp = false;
|
||||
|
||||
#if NUM_SERIAL == 0
|
||||
// no warning about unused parameters.
|
||||
@@ -72,247 +66,191 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
(void) wifiPassword;
|
||||
(void) hostname;
|
||||
(void) port;
|
||||
(void) channel;
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 0
|
||||
Serial1.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
#if NUM_SERIAL > 1
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
if (!wifiUp)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if NUM_SERIAL > 2
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
if (!wifiUp)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (wifiUp == WIFI_NOAT) // here and still not AT commands found
|
||||
return false;
|
||||
DCCEXParser::setAtCommandCallback(ATCommand);
|
||||
|
||||
DCCEXParser::setAtCommandCallback(ATCommand);
|
||||
// CAUTION... ONLY CALL THIS ONCE
|
||||
WifiInboundHandler::setup(wifiStream);
|
||||
if (wifiUp == WIFI_CONNECTED)
|
||||
connected = true;
|
||||
else
|
||||
connected = false;
|
||||
return connected;
|
||||
// CAUTION... ONLY CALL THIS ONCE
|
||||
WifiInboundHandler::setup(wifiStream);
|
||||
|
||||
return wifiUp;
|
||||
}
|
||||
|
||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
wifiSerialState wifiState;
|
||||
bool WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
|
||||
wifiStream = &setupStream;
|
||||
|
||||
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
|
||||
DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry);
|
||||
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
DIAG(F("++ Wifi Setup NO AT ++"));
|
||||
return wifiState;
|
||||
}
|
||||
connected = setup2( SSid, password, hostname, port);
|
||||
|
||||
if (wifiState == WIFI_CONNECTED) {
|
||||
if (connected) {
|
||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||
checkForOK(200, true);
|
||||
checkForOK(200, OK_SEARCH, true);
|
||||
}
|
||||
|
||||
|
||||
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
||||
return wifiState;
|
||||
DIAG(F("\n++ Wifi Setup %S ++\n"), connected ? F("OK") : F("FAILED"));
|
||||
return connected;
|
||||
}
|
||||
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#endif
|
||||
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
bool WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password,
|
||||
const __FlashStringHelper* hostname, int port) {
|
||||
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,F("+IPD"), true)) {
|
||||
DIAG(F("Preconfigured Wifi already running with data waiting"));
|
||||
return WIFI_CONNECTED;
|
||||
if (checkForOK(200,IPD_SEARCH, 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 true;
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
|
||||
if(!checkForOK(200, true))
|
||||
return WIFI_NOAT; // No AT compatible WiFi module here
|
||||
if(!checkForOK(200, OK_SEARCH, true))
|
||||
return false; // 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, true); // Makes this visible on the console
|
||||
checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console
|
||||
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, true, false); // Makes this visible on the console
|
||||
checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console
|
||||
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
|
||||
#else
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client
|
||||
checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
checkForOK(1000, OK_SEARCH, 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 password.
|
||||
if (strncmp_P("Your network ",(const char*)password,13) == 0) {
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
|
||||
const char *yourNetwork = "Your network ";
|
||||
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;
|
||||
}
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
} else {
|
||||
// SSID was configured, so we assume station (client) mode.
|
||||
if (oldCmd) {
|
||||
|
||||
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
|
||||
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(16000, OK_SEARCH, true);
|
||||
}
|
||||
DIAG(F("\n**\n"));
|
||||
|
||||
} else {
|
||||
// later version supports CWJAP_CUR
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client
|
||||
checkForOK(2000, true); // dont care if not supported
|
||||
checkForOK(2000, OK_SEARCH, true); // dont care if not supported
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true);
|
||||
if (SSid) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password);
|
||||
ipOK = checkForOK(20000, OK_SEARCH, true);
|
||||
}
|
||||
}
|
||||
delay(8000); // give a preconfigured ES8266 a chance to connect to a router
|
||||
|
||||
if (ipOK) {
|
||||
// But we really only have the ESSID and password correct
|
||||
// Let's check for IP (via DHCP)
|
||||
// Let's check for IP
|
||||
ipOK = false;
|
||||
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))
|
||||
if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false))
|
||||
if (!checkForOK(1000, (const char*) 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+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
|
||||
StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint.
|
||||
checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change"
|
||||
|
||||
// Figure out MAC addr
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n"));
|
||||
// looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7"
|
||||
if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) {
|
||||
if (checkForOK(5000, (const char*) 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'};
|
||||
|
||||
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("Warning: Setting AP SSID and password failed")); // but issue warning
|
||||
if (oldCmd) {
|
||||
while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE
|
||||
|
||||
if (!oldCmd) {
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail);
|
||||
checkForOK(16000, OK_SEARCH, true); // can 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
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct
|
||||
checkForOK(2000, true);
|
||||
checkForOK(2000, OK_SEARCH, true);
|
||||
}
|
||||
}
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||
checkForOK(1000, true); // ignore result in case it already was off
|
||||
checkForOK(10000, OK_SEARCH, 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, true)) return WIFI_DISCONNECTED;
|
||||
if (!checkForOK(10000, OK_SEARCH, true)) return false;
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
if (!checkForOK(10000, OK_SEARCH, true)) return false;
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||
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"),port);
|
||||
if (!checkForOK(10000, OK_SEARCH, true, false)) return false;
|
||||
DIAG(F("\nPORT=%d\n"),port);
|
||||
|
||||
return WIFI_CONNECTED;
|
||||
return true;
|
||||
}
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
||||
// This function is used to allow users to enter <+ commands> through the DCCEXParser
|
||||
@@ -324,42 +262,38 @@ void WifiInterface::ATCommand(const byte * command) {
|
||||
command++;
|
||||
if (*command=='X') {
|
||||
connected = true;
|
||||
DIAG(F("++++++ Wifi Connction forced on ++++++++"));
|
||||
DIAG(F("\n++++++ Wifi Connction forced on ++++++++\n"));
|
||||
}
|
||||
else {
|
||||
StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command);
|
||||
checkForOK(10000, true);
|
||||
checkForOK(10000, OK_SEARCH, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) {
|
||||
unsigned long startTime = millis();
|
||||
char *locator = (char *)waitfor;
|
||||
DIAG(F("Wifi Check: [%E]"), waitfor);
|
||||
char const *locator = waitfor;
|
||||
DIAG(F("\nWifi Check: [%E]"), waitfor);
|
||||
while ( millis() - startTime < timeout) {
|
||||
while (wifiStream->available()) {
|
||||
int ch = wifiStream->read();
|
||||
if (echo) {
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else StringFormatter::diagSerial->print((char)ch);
|
||||
else DIAG(F("%c"), ch);
|
||||
}
|
||||
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||
if (ch == GETFLASH(locator)) {
|
||||
if (ch != pgm_read_byte_near(locator)) locator = waitfor;
|
||||
if (ch == pgm_read_byte_near(locator)) {
|
||||
locator++;
|
||||
if (!GETFLASH(locator)) {
|
||||
DIAG(F("Found in %dms"), millis() - startTime);
|
||||
if (!pgm_read_byte_near(locator)) {
|
||||
DIAG(F("\nFound in %dms"), millis() - startTime);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DIAG(F("TIMEOUT after %dms"), timeout);
|
||||
DIAG(F("\nTIMEOUT after %dms\n"), timeout);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -369,5 +303,3 @@ void WifiInterface::loop() {
|
||||
WifiInboundHandler::loop();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -19,35 +19,35 @@
|
||||
*/
|
||||
#ifndef WifiInterface_h
|
||||
#define WifiInterface_h
|
||||
#include "FSH.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
|
||||
|
||||
class WifiInterface
|
||||
{
|
||||
|
||||
public:
|
||||
static bool setup(long serial_link_speed,
|
||||
const FSH *wifiESSID,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const __FlashStringHelper *wifiESSID,
|
||||
const __FlashStringHelper *wifiPassword,
|
||||
const __FlashStringHelper *hostname,
|
||||
const int port = 2560);
|
||||
static void loop();
|
||||
static void ATCommand(const byte *command);
|
||||
|
||||
|
||||
private:
|
||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
static bool setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password,
|
||||
const __FlashStringHelper *hostname, int port);
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
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 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 bool connected;
|
||||
static bool closeAfter;
|
||||
static byte loopstate;
|
||||
static int datalength;
|
||||
static int connectionId;
|
||||
static unsigned long loopTimeoutStart;
|
||||
};
|
||||
#endif
|
||||
|
134
WifiSetup.cpp
Normal file
134
WifiSetup.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "NetworkDiag.h"
|
||||
#include "NetworkSetup.h"
|
||||
#include "WifiSetup.h"
|
||||
|
||||
void reverseArray(uint8_t arr[], int start, int end);
|
||||
|
||||
bool WifiSetup::setup() {
|
||||
/**
|
||||
* @todo setup using SoftwareSerial or any other Hardware Serial port on the mega (i.e. 2 or 3);
|
||||
*
|
||||
*/
|
||||
Serial1.begin(AT_BAUD_RATE);
|
||||
WiFi.init(Serial1);
|
||||
|
||||
maxConnections = MAX_WIFI_SOCK;
|
||||
|
||||
if (WiFi.status() == WL_NO_MODULE)
|
||||
{
|
||||
ERR(F("Communication with WiFi module failed!"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
INFO(F("Waiting for connection to WiFi "));
|
||||
while (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
delay(1000);
|
||||
DBG(F("."));
|
||||
}
|
||||
|
||||
INFO(F("Network Protocol: [%s]"), protocol ? "UDP" : "TCP");
|
||||
INFO(F("Initialize MAC Addresses ... "));
|
||||
WiFi.apMacAddress(apWifiMacAddress);
|
||||
reverseArray(apWifiMacAddress, 0, 5); // the MAc is provided in reverse order ...
|
||||
WiFi.macAddress(stWifiMacAddress);
|
||||
reverseArray(stWifiMacAddress, 0, 5);
|
||||
|
||||
// Setup the protocol handler
|
||||
switch (protocol)
|
||||
{
|
||||
case UDPR:
|
||||
{
|
||||
connected = false;
|
||||
udp = new WiFiUDP();
|
||||
byte udpState = udp->begin(port);
|
||||
if (udpState)
|
||||
{
|
||||
TRC(F("UDP status: %d"), udpState);
|
||||
maxConnections = 1; // there is only one UDP object listening for incomming data
|
||||
connected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR(F("UDP failed to start"));
|
||||
connected = false;
|
||||
}
|
||||
break;
|
||||
};
|
||||
case TCP:
|
||||
{
|
||||
server = new WiFiServer(port);
|
||||
server->begin(MAX_WIFI_SOCK, 240);
|
||||
if(server->status()) {
|
||||
connected = true;
|
||||
|
||||
} else {
|
||||
ERR(F("\nWiFi server failed to start"));
|
||||
connected = false;
|
||||
} // Connection pool not used for WiFi
|
||||
break;
|
||||
};
|
||||
case MQTT: {
|
||||
// do the MQTT setup stuff here
|
||||
};
|
||||
default:
|
||||
{
|
||||
ERR(F("Unkown Ethernet protocol; Setup failed"));
|
||||
connected = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (connected)
|
||||
{
|
||||
ip = WiFi.localIP();
|
||||
NetworkSetup::printMacAddress(apWifiMacAddress);
|
||||
NetworkSetup::printMacAddress(stWifiMacAddress);
|
||||
|
||||
INFO(F("Local IP address: [%d.%d.%d.%d]"), ip[0], ip[1], ip[2], ip[3]);
|
||||
INFO(F("Listening on port: [%d]"), port);
|
||||
dnsip = WiFi.dnsServer1();
|
||||
INFO(F("DNS server IP address: [%d.%d.%d.%d] "), dnsip[0], dnsip[1], dnsip[2], dnsip[3]);
|
||||
INFO(F("Number of connections: [%d]"), maxConnections);
|
||||
return true;
|
||||
}
|
||||
return false; // something went wrong
|
||||
|
||||
};
|
||||
|
||||
void reverseArray(uint8_t arr[], int start, int end)
|
||||
{
|
||||
while (start < end)
|
||||
{
|
||||
uint8_t temp = arr[start];
|
||||
arr[start] = arr[end];
|
||||
arr[end] = temp;
|
||||
start++;
|
||||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
WifiSetup::WifiSetup() {}
|
||||
WifiSetup::WifiSetup(uint16_t p, protocolType pt ) { port = p; protocol = pt; }
|
||||
WifiSetup::~WifiSetup() {}
|
60
WifiSetup.h
Normal file
60
WifiSetup.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* © 2020 Gregor Baues. 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.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* See the GNU General Public License for more details <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WifiSetup_h
|
||||
#define WifiSetup_h
|
||||
|
||||
#include "NetworkSetup.h"
|
||||
#include "WiFiEspAT.h"
|
||||
|
||||
// Emulate Serial1 on pins 6/7 if not present
|
||||
#if defined(ARDUINO_ARCH_AVR) && !defined(HAVE_HWSERIAL1)
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial Serial1(6, 7); // RX, TX
|
||||
#define AT_BAUD_RATE 9600
|
||||
#else
|
||||
#define AT_BAUD_RATE 115200
|
||||
#endif
|
||||
|
||||
|
||||
class WifiSetup: public NetworkSetup {
|
||||
|
||||
private:
|
||||
|
||||
WiFiServer* server;
|
||||
WiFiUDP* udp;
|
||||
|
||||
public:
|
||||
|
||||
bool setup();
|
||||
|
||||
WiFiUDP* getUDPServer() {
|
||||
return udp;
|
||||
}
|
||||
|
||||
WiFiServer* getTCPServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
WifiSetup();
|
||||
WifiSetup(uint16_t port, protocolType protocol);
|
||||
~WifiSetup();
|
||||
};
|
||||
|
||||
#endif
|
@@ -1,9 +1,10 @@
|
||||
/**********************************************************************
|
||||
|
||||
config.h
|
||||
Config.h
|
||||
COPYRIGHT (c) 2013-2016 Gregg E. Berman
|
||||
COPYRIGHT (c) 2020 Fred Decker
|
||||
|
||||
The configuration file for DCC-EX Command Station
|
||||
The configuration file for DCC++ EX Command Station
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
@@ -20,12 +21,12 @@ The configuration file for DCC-EX Command Station
|
||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// |
|
||||
// +-----------------------v
|
||||
//
|
||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The IP port to talk to a WIFI or Ethernet shield.
|
||||
@@ -43,48 +44,9 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// DEFINE WiFi Parameters (only in effect if WIFI is on)
|
||||
//
|
||||
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
|
||||
// the <+> commands and this sketch will not change anything over
|
||||
// AT commands and the other WIFI_* defines below do not have any effect.
|
||||
//#define DONT_TOUCH_WIFI_CONF
|
||||
//
|
||||
// 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 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"
|
||||
//
|
||||
// WIFI_PASSWORD is the network password for your home network or if
|
||||
// you want to change the password from default AP mode password
|
||||
// to the AP password you want.
|
||||
// Your password may not conain ``"'' (double quote, ASCII 0x22).
|
||||
#define WIFI_PASSWORD "Your network passwd"
|
||||
//
|
||||
// WIFI_HOSTNAME: You probably don't need to change this
|
||||
#define WIFI_HOSTNAME "dccex"
|
||||
//
|
||||
// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented,
|
||||
// WiFi will be enabled (Mega only). The default channel is set to "1" whether
|
||||
// this line exists or not. If you need to use an alternate channel (we recommend
|
||||
// using only 1,6, or 11) you may change it here.
|
||||
#define WIFI_CHANNEL 1
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
|
||||
// is not for Wifi. You will then need the Arduino Ethernet library as well
|
||||
//
|
||||
//#define ENABLE_ETHERNET true
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -92,6 +54,15 @@ 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
|
||||
//
|
||||
// NOTE: This is not used with ESP8266 WiFi modules.
|
||||
//
|
||||
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -105,10 +76,11 @@ The configuration file for DCC-EX Command Station
|
||||
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
|
||||
// #define LCD_DRIVER 0x3F,16,2
|
||||
|
||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||
// Also 132x64 I2C SH1106 devices.
|
||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||
// This will not work on a UNO due to memory constraints
|
||||
// #define OLED_DRIVER 128,32
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// Enable warning as memory gets depleted
|
||||
#define ENABLE_FREE_MEM_WARNING false
|
||||
|
18
defines.h
18
defines.h
@@ -21,27 +21,15 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// 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) || defined(TEENSYDUINO))
|
||||
#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
|
||||
|
||||
#if ENABLE_ETHERNET && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
||||
#define ETHERNET_ON true
|
||||
#else
|
||||
#define ETHERNET_ON false
|
||||
#endif
|
||||
|
||||
#if WIFI_ON && ETHERNET_ON
|
||||
#error Command Station does not support WIFI and ETHERNET at the same time.
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This defines the speed at which the Arduino will communicate with the ESP8266 module.
|
||||
|
152
examples/advanced/CommandStation-EX.ino
Normal file
152
examples/advanced/CommandStation-EX.ino
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* © 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);
|
||||
}
|
||||
}
|
29
examples/basic/CommandStation-EX.ino
Normal file
29
examples/basic/CommandStation-EX.ino
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* © 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);
|
||||
}
|
73
examples/basicWifi/CommandStation-EX.ino
Normal file
73
examples/basicWifi/CommandStation-EX.ino
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* © 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();
|
||||
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* © 2020, Harald Barth
|
||||
* © 2021, Neil McKechnie
|
||||
*
|
||||
* This file is part of Asbelos DCC-EX
|
||||
*
|
||||
@@ -18,7 +17,6 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "freeMemory.h"
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
@@ -32,80 +30,13 @@ extern char *__malloc_heap_start;
|
||||
#endif
|
||||
|
||||
|
||||
static volatile int minimum_free_memory = __INT_MAX__;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
int freeMemory() {
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
#error bailed out already above
|
||||
#error bailed out alredy above
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
byte sreg_save = SREG;
|
||||
noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
//byte sreg_save = SREG;
|
||||
//noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
//SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user