1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 18:33:44 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Harald Barth
edb12d69d6 surpress gcc warnings 2020-11-14 22:08:57 +01:00
Harald Barth
086c78f58f Display class 2020-11-14 22:06:58 +01:00
157 changed files with 4228 additions and 23461 deletions

1
.gitattributes vendored
View File

@@ -1,3 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
*.svg -text

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
github: DCC-EX
patreon: dccex

View File

@@ -1,14 +0,0 @@
name: Label sponsors
on:
pull_request:
types: [opened]
issues:
types: [opened]
jobs:
build:
name: is-sponsor-label
runs-on: ubuntu-latest
steps:
- uses: JasonEtco/is-sponsor-label-action@v1.2.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -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)

View File

@@ -1,35 +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@v8 # You can change this to use a specific version
with:
add: 'GITHUB_SHA.h'
message: 'Committing a SHA'
commit: --amend
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged

14
.gitignore vendored
View File

@@ -6,16 +6,4 @@ Release/*
.gcc-flags.json
.pio/
.vscode/
config.h
.vscode/*
# mySetup.h
mySetup.cpp
myHal.cpp
# myAutomation.h
myFilter.cpp
# myAutomation.h
# myLayout.h
my*.h
!my*.example.h
.vscode/extensions.json
.vscode/extensions.json
config.h

View File

@@ -3,8 +3,5 @@
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

194
ATMEGA2560/Timer.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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

View File

@@ -1,9 +1,6 @@
/*
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -19,220 +16,16 @@
* 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 "CommandDistributor.h"
#include "SerialManager.h"
#include "WiThrottle.h"
#include "DIAG.h"
#include "defines.h"
#include "DCCWaveform.h"
#include "DCC.h"
#include "TrackManager.h"
#include "StringFormatter.h"
// variables to hold clock time
int16_t lastclocktime;
int8_t lastclockrate;
DCCEXParser * CommandDistributor::parser=0;
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
// use a buffer to allow broadcast
StringBuffer * CommandDistributor::broadcastBufferWriter=new StringBuffer();
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
broadcastBufferWriter->flush();
StringFormatter::send(broadcastBufferWriter, msg...);
broadcastToClients(type);
}
#else
// on a single USB connection config, write direct to Serial and ignore flush/shove
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
(void)type; //shut up compiler warning
StringFormatter::send(&USB_SERIAL, msg...);
}
#endif
#ifdef CD_HANDLE_RING
// wifi or ethernet ring streams with multiple client types
RingStream * CommandDistributor::ring=0;
CommandDistributor::clientType CommandDistributor::clients[8]={
NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE,NONE_TYPE};
// Parse is called by Withrottle or Ethernet interface to determine which
// protocol the client is using and call the appropriate part of dcc++Ex
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream) {
if (Diag::WIFI && Diag::CMD)
DIAG(F("Parse C=%d T=%d B=%s"),clientId, clients[clientId], buffer);
ring=stream;
// First check if the client is not known
// yet and in that case determinine type
// NOTE: First character of transmission determines if this
// client is using the DCC++ protocol where all commands start
// with '<'
if (clients[clientId] == NONE_TYPE) {
if (buffer[0] == '<')
clients[clientId]=COMMAND_TYPE;
else
clients[clientId]=WITHROTTLE_TYPE;
}
// mark buffer that is sent to parser
ring->mark(clientId);
// When type is known, send the string
// to the right parser
if (clients[clientId] == COMMAND_TYPE) {
DCCEXParser::parse(stream, buffer, ring);
} else if (clients[clientId] == WITHROTTLE_TYPE) {
WiThrottle::getThrottle(clientId)->parse(ring, buffer);
}
if (ring->peekTargetMark()!=RingStream::NO_CLIENT) {
// The commit call will either write the length bytes
// OR rollback to the mark because the reply is empty
// or the command generated more output than fits in
// the buffer
if (!ring->commit()) {
DIAG(F("OUTBOUND FULL processing cmd:%s"),buffer);
}
} else {
DIAG(F("CD parse: was alredy committed")); //XXX Could have been committed by broadcastClient?!
void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * streamer) {
if (buffer[0] == '<') {
if (!parser) parser = new DCCEXParser();
parser->parse(streamer, buffer, true); // tell JMRI parser that ACKS are blocking because we can't handle the async
}
}
void CommandDistributor::forget(byte clientId) {
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
clients[clientId]=NONE_TYPE;
}
#endif
// This will not be called on a uno
void CommandDistributor::broadcastToClients(clientType type) {
byte rememberClient;
(void)rememberClient; // shut up compiler warning
// Broadcast to Serials
if (type==COMMAND_TYPE) SerialManager::broadcast(broadcastBufferWriter->getString());
#ifdef CD_HANDLE_RING
// If we are broadcasting from a wifi/eth process we need to complete its output
// before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client.
if (ring) {
if ((rememberClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
//DIAG(F("CD precommit client %d"), rememberClient);
ring->commit();
}
// loop through ring clients
for (byte clientId=0; clientId<sizeof(clients); clientId++) {
if (clients[clientId]==type) {
//DIAG(F("CD mark client %d"), clientId);
ring->mark(clientId);
ring->print(broadcastBufferWriter->getString());
//DIAG(F("CD commit client %d"), clientId);
ring->commit();
}
}
// at this point ring is committed (NO_CLIENT) either from
// 4 or 13 lines above.
if (rememberClient != RingStream::NO_CLIENT) {
//DIAG(F("CD postmark client %d"), rememberClient);
ring->mark(rememberClient);
}
}
#endif
}
// Public broadcast functions below
void CommandDistributor::broadcastSensor(int16_t id, bool on ) {
broadcastReply(COMMAND_TYPE, F("<%c %d>\n"), on?'Q':'q', id);
}
void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
// For DCC++ classic compatibility, state reported to JMRI is 1 for thrown and 0 for closed;
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<H %d %d>\n"),id, !isClosed);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PTA%c%d\n"), isClosed?'2':'4', id);
#endif
}
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// The JMRI clock command is of the form : PFT65871<;>4
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
// The string below contains serial and Withrottle protocols which should
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
#endif
}
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
// opt - case 1 save the latest time if changed
// case 2 broadcast the time when requested
// case 3 display latest time
switch (opt)
{
case 1:
if (clocktime != lastclocktime){
if (Diag::CMD) {
DIAG(F("Clock Command Received"));
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
}
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
// look for an event for this time
RMFT2::clockEvent(clocktime,1);
// Now tell everyone else what the time is.
CommandDistributor::broadcastClockTime(clocktime, clockrate);
lastclocktime = clocktime;
lastclockrate = clockrate;
}
return;
case 2:
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
return;
}
}
int16_t CommandDistributor::retClockTime() {
return lastclocktime;
}
void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
#ifdef CD_HANDLE_RING
WiThrottle::markForBroadcast(sp->loco);
#endif
}
void CommandDistributor::broadcastPower() {
bool main=TrackManager::getMainPower()==POWERMODE::ON;
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
bool join=TrackManager::isJoined();
const FSH * reason=F("");
char state='1';
if (main && prog && join) reason=F(" JOIN");
else if (main && prog);
else if (main) reason=F(" MAIN");
else if (prog) reason=F(" PROG");
else state='0';
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0');
#endif
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
}
void CommandDistributor::broadcastText(const FSH * msg) {
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
#endif
else WiThrottle::getThrottle(clientId)->parse(streamer, buffer);
}

View File

@@ -1,9 +1,6 @@
/*
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
*
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
@@ -23,37 +20,13 @@
#define CommandDistributor_h
#include "DCCEXParser.h"
#include "RingStream.h"
#include "StringBuffer.h"
#include "defines.h"
#include "EXRAIL2.h"
#if WIFI_ON | ETHERNET_ON
// Command Distributor must handle a RingStream of clients
#define CD_HANDLE_RING
#endif
class CommandDistributor {
private:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter;
#ifdef CD_HANDLE_RING
static RingStream * ring;
static clientType clients[8];
#endif
public :
static void parse(byte clientId,byte* buffer, RingStream * ring);
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastClockTime(int16_t time, int8_t rate);
static void setClockTime(int16_t time, int8_t rate, byte opt);
static int16_t retClockTime();
static void broadcastPower();
static void broadcastText(const FSH * msg);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
static void parse(byte clientId,byte* buffer, RingStream * streamer);
private:
static DCCEXParser * parser;
};
#endif

View File

@@ -1,69 +1,21 @@
////////////////////////////////////////////////////////////////////////////////////
// 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.
// 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.
//
// 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.
//
// 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"
#ifndef MOTOR_SHIELD_TYPE
#error Your config.h must include a MOTOR_SHIELD_TYPE definition. If you see this warning in spite not having a config.h, you have a buggy preprocessor and must copy config.example.h to config.h
#endif
#else
#warning config.h not found. Using defaults from config.example.h
#include "config.example.h"
#endif
/*
* © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton
* 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 "config.h"
#include "DCCEX.h"
#include "Display_Implementation.h"
#ifdef CPU_TYPE_ERROR
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
#endif
#ifdef WIFI_WARNING
#warning You have defined that you want WiFi but your hardware has not enough memory to do that, so WiFi DISABLED
#endif
#ifdef ETHERNET_WARNING
#warning You have defined that you want Ethernet but your hardware has not enough memory to do that, so Ethernet DISABLED
#endif
#ifdef EXRAIL_WARNING
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
#endif
// Create a serial command parser for the USB connection,
// This supports JMRI or manual diagnostics and commands
// to be issued from the USB serial console.
DCCEXParser serialParser;
void setup()
{
@@ -71,63 +23,37 @@ void setup()
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
SerialManager::init();
Serial.begin(115200);
DIAG(F("DCC++ EX v%S"),F(VERSION));
CONDITIONAL_LCD_START {
// This block is ignored if LCD not in use
LCD(0,F("DCC++ EX v%S"),F(VERSION));
LCD(1,F("Starting"));
}
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
DISPLAY_START (
// This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION));
LCD(1,F("Lic GPLv3"));
);
// Responsibility 2: Start all the communications before the DCC engine
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
// Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32
#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
#else
// ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
#endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// 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
TrackManager::Setup(MOTOR_SHIELD_TYPE);
// Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin();
// 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
// Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h.
// This can be used to create turnouts, outputs, sensors etc. through the normal text commands.
#if __has_include ( "mySetup.h")
#define SETUP(cmd) DCCEXParser::parse(F(cmd))
#include "mySetup.h"
#undef SETUP
#endif
#if defined(LCN_SERIAL)
LCN_SERIAL.begin(115200);
LCN::init(LCN_SERIAL);
#endif
LCD(3, F("Ready"));
CommandDistributor::broadcastPower();
DCC::begin(MOTOR_SHIELD_TYPE);
LCD(1,F("Ready"));
}
void loop()
@@ -139,42 +65,27 @@ void loop()
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
SerialManager::loop();
serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic
#ifndef ARDUINO_ARCH_ESP32
// Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON
WifiInterface::loop();
#endif //WIFI_ON
#else //ARDUINO_ARCH_ESP32
#ifndef WIFI_TASK_ON_CORE0
WifiESP::loop();
#endif
#endif //ARDUINO_ARCH_ESP32
#if ETHERNET_ON
EthernetInterface::loop();
#endif
RMFT::loop(); // ignored if no automation
LCDDisplay::loop(); // ignored if LCD not in use
// 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
#if defined(LCN_SERIAL)
LCN::loop();
#endif
// Display refresh
DisplayInterface::loop();
// Handle/update IO devices.
IODevice::loop();
Sensor::checkAll(); // Update and print changes
// Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
int freeNow = DCCTimer::getMinimumFreeMemory();
if (freeNow < ramLowWatermark) {
int freeNow = freeMemory();
if (freeNow < ramLowWatermark)
{
ramLowWatermark = freeNow;
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
LCD(2,F("Free RAM=%5db"), ramLowWatermark);
}
#endif
}

778
DCC.cpp

File diff suppressed because it is too large Load Diff

144
DCC.h
View File

@@ -1,10 +1,5 @@
/*
* © 2021 Mike S
* © 2021 Fred Decker
* © 2021 Herb Morton
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -26,70 +21,82 @@
#include <Arduino.h>
#include "MotorDriver.h"
#include "MotorDrivers.h"
#include "FSH.h"
#include "defines.h"
#ifndef HIGHEST_SHORT_ADDR
#define HIGHEST_SHORT_ADDR 127
#else
#if HIGHEST_SHORT_ADDR > 127
#error short addr greater than 127 does not make sense
#endif
#endif
#include "DCCACK.h"
const uint16_t LONG_ADDR_MARKER = 0x4000;
typedef void (*ACK_CALLBACK)(int result);
enum ackOp
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
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
STASHLOCOID, // keeps current byte value for later
COMBINELOCOID, // combines current value with stashed value and returns it
ITSKIP, // skip to SKIPTARGET if ack true
SKIPTARGET = 0xFF // jump to target
};
// Allocations with memory implications..!
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
#if defined(HAS_ENOUGH_MEMORY)
const byte MAX_LOCOS = 50;
#ifdef ARDUINO_AVR_UNO
const byte MAX_LOCOS = 20;
#else
const byte MAX_LOCOS = 30;
const byte MAX_LOCOS = 50;
#endif
class DCC
{
public:
static void begin(const FSH * motorShieldName);
static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1);
static void loop();
// Public DCC API functions
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
static int8_t getThrottleSpeed(int cab);
static uint8_t getThrottleSpeedByte(int cab);
static uint8_t getThrottleSpeed(int cab);
static bool getThrottleDirection(int cab);
static void writeCVByteMain(int cab, int cv, byte bValue);
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
static void setFunction(int cab, byte fByte, byte eByte);
static bool setFn(int cab, int16_t functionNumber, bool on);
static void changeFn(int cab, int16_t functionNumber);
static int getFn(int cab, int16_t functionNumber);
static uint32_t getFunctionMap(int cab);
static void updateGroupflags(byte &flags, int16_t functionNumber);
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
static void setFn(int cab, byte functionNumber, bool on);
static int changeFn(int cab, byte functionNumber, bool pressed);
static int getFn(int cab, byte functionNumber);
static void updateGroupflags(byte &flags, int functionNumber);
static void setAccessory(int aAdd, byte aNum, bool activate);
static bool writeTextPacket(byte *b, int nBytes);
// 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 setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
static void getLocoId(ACK_CALLBACK callback);
static void setLocoId(int id,ACK_CALLBACK callback);
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
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, 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
{
int loco;
@@ -97,26 +104,34 @@ public:
byte groupFlags;
unsigned long functions;
};
static LOCO speedTable[MAX_LOCOS];
static int lookupSpeedTable(int locoId, bool autoCreate=true);
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
private:
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 lastLocoReminder;
static int highestUsedReg;
static FSH *shieldName;
static byte globalSpeedsteps;
static int nextLoco;
static __FlashStringHelper *shieldName;
static LOCO speedTable[MAX_LOCOS];
static byte cv1(byte opcode, int cv);
static byte cv2(int cv);
static int lookupSpeedTable(int locoId);
static void issueReminders();
static void callback(int value);
// ACK MANAGER
static ackOp const *ackManagerProg;
static byte ackManagerByte;
static byte ackManagerBitNum;
static int ackManagerCv;
static byte ackManagerStash;
static bool ackReceived;
static ACK_CALLBACK ackManagerCallback;
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 #
static const byte SET_SPEED = 0x3f;
static const byte WRITE_BYTE_MAIN = 0xEC;
@@ -130,4 +145,23 @@ private:
static const byte BIT_OFF = 0x00;
};
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
#define ARDUINO_AVR_MEGA2560
#endif
#if defined(ARDUINO_AVR_UNO)
#define ARDUINO_TYPE "UNO"
#elif defined(ARDUINO_AVR_NANO)
#define ARDUINO_TYPE "NANO"
#elif defined(ARDUINO_AVR_MEGA2560)
#define ARDUINO_TYPE "MEGA"
#elif defined(ARDUINO_ARCH_MEGAAVR)
#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
#endif

View File

@@ -1,469 +0,0 @@
/*
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DCCACK.h"
#include "DIAG.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "TrackManager.h"
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
MotorDriver * DCCACK::progDriver=NULL;
ackOp const * DCCACK::ackManagerProg;
ackOp const * DCCACK::ackManagerProgStart;
byte DCCACK::ackManagerByte;
byte DCCACK::ackManagerByteVerify;
byte DCCACK::ackManagerStash;
int DCCACK::ackManagerWord;
byte DCCACK::ackManagerRetry;
byte DCCACK::ackRetry = 2;
int16_t DCCACK::ackRetrySum;
int16_t DCCACK::ackRetryPSum;
int DCCACK::ackManagerCv;
byte DCCACK::ackManagerBitNum;
bool DCCACK::ackReceived;
bool DCCACK::ackManagerRejoin;
volatile uint8_t DCCACK::numAckGaps=0;
volatile uint8_t DCCACK::numAckSamples=0;
uint8_t DCCACK::trailingEdgeCounter=0;
unsigned int DCCACK::ackPulseDuration; // micros
unsigned long DCCACK::ackPulseStart; // micros
volatile bool DCCACK::ackDetected;
unsigned long DCCACK::ackCheckStart; // millis
volatile bool DCCACK::ackPending;
bool DCCACK::autoPowerOff;
int DCCACK::ackThreshold;
int DCCACK::ackLimitmA = 50;
int DCCACK::ackMaxCurrent;
unsigned int DCCACK::ackCheckDuration; // millis
CALLBACK_STATE DCCACK::callbackState=READY;
ACK_CALLBACK DCCACK::ackManagerCallback;
void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
ackManagerRejoin=TrackManager::isJoined();
if (ackManagerRejoin) {
// Change from JOIN must zero resets packet.
TrackManager::setJoin(false);
DCCWaveform::progTrack.clearResets();
}
progDriver=TrackManager::getProgDriver();
if (progDriver==NULL) {
TrackManager::setJoin(ackManagerRejoin);
callback(-3); // we dont have a prog track!
return;
}
if (!progDriver->canMeasureCurrent()) {
TrackManager::setJoin(ackManagerRejoin);
callback(-2); // our prog track cant measure current
return;
}
autoPowerOff=false;
if (progDriver->getPower() == POWERMODE::OFF) {
autoPowerOff=true; // power off afterwards
if (Diag::ACK) DIAG(F("Auto Prog power on"));
progDriver->setPower(POWERMODE::ON);
/* TODO !!! in MotorDriver surely!
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
DCCWaveform::progTrack.clearResets();
**/
}
ackManagerCv = cv;
ackManagerProg = program;
ackManagerProgStart = program;
ackManagerRetry = ackRetry;
ackManagerByte = byteValueOrBitnum;
ackManagerByteVerify = byteValueOrBitnum;
ackManagerBitNum=byteValueOrBitnum;
ackManagerCallback = callback;
}
void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
ackManagerWord=wordval;
Setup(0, 0, program, 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 DCCACK::checkResets(uint8_t numResets) {
return DCCWaveform::progTrack.getResets() < numResets;
}
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
void DCCACK::setAckBaseline() {
int baseline=progDriver->getCurrentRaw();
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
baseline,progDriver->raw2mA(baseline),
ackThreshold,progDriver->raw2mA(ackThreshold),
minAckPulseDuration, maxAckPulseDuration);
}
void DCCACK::setAckPending() {
ackMaxCurrent=0;
ackPulseStart=0;
ackPulseDuration=0;
ackDetected=false;
ackCheckStart=millis();
numAckSamples=0;
numAckGaps=0;
ackPending=true; // interrupt routines will now take note
}
byte DCCACK::getAck() {
if (ackPending) return (2); // still waiting
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
if (ackDetected) return (1); // Yes we had an ack
return(0); // pending set off but not detected means no ACK.
}
void DCCACK::loop() {
while (ackManagerProg) {
byte opcode=GETFLASH(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.)
switch (opcode) {
case BASELINE:
if (progDriver->getPower()==POWERMODE::OVERLOAD) return;
if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
setAckBaseline();
callbackState=AFTER_READ;
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);
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
callbackState=AFTER_WRITE;
}
break;
case WB: // write byte
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
callbackState=AFTER_WRITE;
}
break;
case VB: // Issue validate Byte packet
{
if (checkResets( RESET_MIN)) return;
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
}
break;
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);
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
setAckPending();
}
break;
case WACK: // wait for ack (or absence of ack)
{
byte ackState=2; // keep polling
ackState=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);
return;
}
break;
case ITCB: // If True callback(byte)
if (ackReceived) {
callback(ackManagerByte);
return;
}
break;
case ITCBV: // If True callback(byte) - Verify
if (ackReceived) {
if (ackManagerByte == ackManagerByteVerify) {
ackRetrySum ++;
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
}
callback(ackManagerByte);
return;
}
break;
case ITCB7: // If True callback(byte & 0x7F)
if (ackReceived) {
callback(ackManagerByte & 0x7F);
return;
}
break;
case NAKFAIL: // If nack callback(-1)
if (!ackReceived) {
callback(-1);
return;
}
break;
case CALLFAIL: // callback(-1)
callback(-1);
return;
case BIV: // ackManagerByte initial value
ackManagerByte = ackManagerByteVerify;
break;
case STARTMERGE:
ackManagerBitNum=7;
ackManagerByte=0;
break;
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
ackManagerByte <<= 1;
// ackReceived means bit is zero.
if (!ackReceived) ackManagerByte |= 1;
ackManagerBitNum--;
break;
case SETBIT:
ackManagerProg++;
ackManagerBitNum=GETFLASH(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);
break;
case STASHLOCOID:
ackManagerStash=ackManagerByte; // stash value from CV17
break;
case COMBINELOCOID:
// ackManagerStash is cv17, ackManagerByte is CV 18
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
return;
case ITSKIP:
if (!ackReceived) break;
// SKIP opcodes until SKIPTARGET found
while (opcode!=SKIPTARGET) {
ackManagerProg++;
opcode=GETFLASH(ackManagerProg);
}
break;
case SKIPTARGET:
break;
default:
DIAG(F("!! ackOp %d FAULT!!"),opcode);
callback( -1);
return;
} // end of switch
ackManagerProg++;
}
}
void DCCACK::callback(int value) {
// check for automatic retry
if (value == -1 && ackManagerRetry > 0) {
ackRetrySum ++;
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
ackManagerRetry --;
ackManagerProg = ackManagerProgStart;
return;
}
static unsigned long callbackStart;
// We are about to leave programming mode
// Rule 1: If we have written to a decoder we must maintain power for 100mS
// Rule 2: If we are re-joining the main track we must power off for 30mS
switch (callbackState) {
case AFTER_READ:
if (ackManagerRejoin && autoPowerOff) {
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
} else {
callbackState=READY;
}
break;
case AFTER_WRITE: // first attempt to callback after a write operation
if (!ackManagerRejoin && !autoPowerOff) {
callbackState=READY;
break;
} // lines 906-910 added. avoid wait after write. use 1 PROG
callbackStart=millis();
callbackState=WAITING_100;
if (Diag::ACK) DIAG(F("Stable 100mS"));
break;
case WAITING_100: // waiting for 100mS
if (millis()-callbackStart < 100) break;
// stable after power maintained for 100mS
// If we are going to power off anyway, it doesnt matter
// but if we will keep the power on, we must off it for 30mS
if (autoPowerOff) callbackState=READY;
else { // Need to cycle power off and on
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
if (Diag::ACK) DIAG(F("OFF 30mS"));
}
break;
case WAITING_30: // waiting for 30mS with power off
if (millis()-callbackStart < 30) break;
//power has been off for 30mS
progDriver->setPower(POWERMODE::ON);
callbackState=READY;
break;
case READY: // ready after read, or write after power delay and off period.
// power off if we powered it on
if (autoPowerOff) {
if (Diag::ACK) DIAG(F("Auto Prog power off"));
progDriver->setPower(POWERMODE::OFF);
/* TODO
if (MotorDriver::commonFaultPin)
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
**/
}
// Restore <1 JOIN> to state before BASELINE
if (ackManagerRejoin) {
TrackManager::setJoin(true);
if (Diag::ACK) DIAG(F("Auto JOIN"));
}
ackManagerProg=NULL; // no more steps to execute
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
(ackManagerCallback)( value);
}
}
void DCCACK::checkAck(byte sentResetsSincePacket) {
if (!ackPending) return;
// 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=progDriver->getCurrentRaw(true); // true means "from interrupt"
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
if (current>ackThreshold) {
if (trailingEdgeCounter > 0) {
numAckGaps++;
trailingEdgeCounter = 0;
}
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
// not in pulse
if (ackPulseStart==0) return; // keep waiting for leading edge
// if we reach to this point, we have
// detected trailing edge of pulse
if (trailingEdgeCounter == 0) {
ackPulseDuration=micros()-ackPulseStart;
}
// but we do not trust it yet and return (which will force another
// measurement) and first the third time around with low current
// the ack detection will be finalized.
if (trailingEdgeCounter < 2) {
trailingEdgeCounter++;
return;
}
trailingEdgeCounter = 0;
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;
DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets
return; // we have a genuine ACK result
}
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
}

156
DCCACK.h
View File

@@ -1,156 +0,0 @@
/*
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2022 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DCCACK_h
#define DCCACK_h
#include "MotorDriver.h"
typedef void (*ACK_CALLBACK)(int16_t result);
enum ackOp : byte
{ // Program opcodes for the ack Manager
BASELINE, // ensure enough resets sent before starting and obtain baseline current
W0,
W1, // issue write bit (0..1) packet
WB, // issue write byte packet
VB, // Issue validate Byte packet
V0, // Issue validate bit=0 packet
V1, // issue validate bit=1 packlet
WACK, // wait for ack (or absence of ack)
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
ITC0, // If True callback(0);
ITCB, // If True callback(byte)
ITCBV, // If True callback(byte) - end of Verify Byte
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
CALLFAIL, // callback(-1)
BIV, // Set ackManagerByte to initial value for Verify retry
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
SKIPTARGET = 0xFF // jump to target
};
enum CALLBACK_STATE : byte {
AFTER_READ, // Start callback sequence after something was read from the decoder
AFTER_WRITE, // Start callback sequence after something was written to the decoder
WAITING_100, // Waiting for 100mS of stable power
WAITING_30, // waiting to 30ms of power off gap.
READY, // Ready to complete callback
};
class DCCACK {
public:
static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker
static inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
static inline void setMinAckPulseDuration(unsigned int i) {
minAckPulseDuration = i;
}
static inline void setMaxAckPulseDuration(unsigned int i) {
maxAckPulseDuration = i;
}
static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback);
static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback);
static void loop();
static bool isActive() { return ackManagerProg!=NULL;}
static inline int16_t setAckRetry(byte retry) {
ackRetry = retry;
ackRetryPSum = ackRetrySum;
ackRetrySum = 0; // reset running total
return ackRetryPSum;
};
private:
static const byte SET_SPEED = 0x3f;
static const byte WRITE_BYTE = 0x7C;
static const byte VERIFY_BYTE = 0x74;
static const byte BIT_MANIPULATE = 0x78;
static const byte WRITE_BIT = 0xF0;
static const byte VERIFY_BIT = 0xE0;
static const byte BIT_ON = 0x08;
static const byte BIT_OFF = 0x00;
static void setAckBaseline();
static void setAckPending();
static void callback(int value);
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
// ACK management (Prog track only)
static void checkAck();
static bool checkResets(uint8_t numResets);
static volatile bool ackPending;
static volatile bool ackDetected;
static int ackThreshold;
static int ackLimitmA;
static int ackMaxCurrent;
static unsigned long ackCheckStart; // millis
static unsigned int ackCheckDuration; // millis
static unsigned int ackPulseDuration; // micros
static unsigned long ackPulseStart; // micros
static unsigned int minAckPulseDuration ; // micros
static unsigned int maxAckPulseDuration ; // micros
static MotorDriver* progDriver;
static volatile uint8_t numAckGaps;
static volatile uint8_t numAckSamples;
static uint8_t trailingEdgeCounter;
static ackOp const * ackManagerProg;
static ackOp const * ackManagerProgStart;
static byte ackManagerByte;
static byte ackManagerByteVerify;
static byte ackManagerStash;
static int ackManagerWord;
static byte ackManagerRetry;
static byte ackRetry;
static int16_t ackRetrySum;
static int16_t ackRetryPSum;
static int ackManagerCv;
static byte ackManagerBitNum;
static bool ackReceived;
static bool ackManagerRejoin;
static bool autoPowerOff;
static CALLBACK_STATE callbackState;
static ACK_CALLBACK ackManagerCallback;
};
#endif

44
DCCEX.h
View File

@@ -1,25 +1,3 @@
/*
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// This include is intended to visually simplify the .ino for the end users.
// If there were any #ifdefs required they are much better handled in here.
@@ -30,25 +8,11 @@
#include "DCC.h"
#include "DIAG.h"
#include "DCCEXParser.h"
#include "SerialManager.h"
#include "version.h"
#ifndef ARDUINO_ARCH_ESP32
#include "WifiInterface.h"
#else
#include "WifiESP32.h"
#endif
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#endif
#include "Display_Implementation.h"
#include "LCN.h"
#include "IODevice.h"
#include "Turnouts.h"
#include "Sensors.h"
#include "Outputs.h"
#include "CommandDistributor.h"
#include "TrackManager.h"
#include "DCCTimer.h"
#include "EXRAIL.h"
#include "LCD_Implementation.h"
#include "freeMemory.h"
#include <Arduino.h>
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,5 @@
/*
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of Asbelos DCC API
*
@@ -22,57 +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 (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]);
typedef void (*AT_COMMAND_CALLBACK)(const byte * command);
struct DCCEXParser
{
static void parse(Print * stream, byte * command, RingStream * ringStream);
static void parse(const FSH * cmd);
static void parseOne(Print * stream, byte * command, RingStream * ringStream);
DCCEXParser();
void loop(Stream & stream);
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 int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
static const int MAX_BUFFER=50; // longest command sent in
byte bufferLength=0;
bool inCommandPayload=false;
byte buffer[MAX_BUFFER+2];
int splitValues( int result[MAX_PARAMS], const byte * command);
static bool parseT(Print * stream, int16_t params, int16_t p[]);
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(Print * stream, int16_t params, int16_t p[]);
static bool parsef(Print * stream, int16_t params, int16_t p[]);
static 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];
static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream);
static void callback_W(int16_t result);
static void callback_W4(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 bool funcmap(int16_t cab, byte value, byte fstart, byte fstop);
static void sendFlashList(Print * stream,const int16_t flashList[]);
static void funcmap(int cab, byte value, byte fstart, byte fstop);
};

View File

@@ -1,235 +0,0 @@
/*
* © 2021-2022, Harald Barth.
*
* This file is part of DCC-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 defined(ARDUINO_ARCH_ESP32)
#include "defines.h"
#include "DIAG.h"
#include "DCCRMT.h"
#include "DCCTimer.h"
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
#include "soc/gpio_sig_map.h"
// Number of bits resulting out of X bytes of DCC payload data
// Each byte has one bit extra and at the end we have one EOF marker
#define DATA_LEN(X) ((X)*9+1)
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
#error wrong IDF version
#endif
void setDCCBit1(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_1_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_1_HALFPERIOD;
}
void setDCCBit0(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD;
}
// special long zero to trigger scope
void setDCCBit0Long(rmt_item32_t* item) {
item->level0 = 1;
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
item->level1 = 0;
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
}
void setEOT(rmt_item32_t* item) {
item->val = 0;
}
// This is an array that contains the this pointers
// to all uses channel objects. This is used to determine
// which of the channels was triggering the ISR as there
// is only ONE common ISR routine for all channels.
RMTChannel *channelHandle[8] = { 0 };
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
RMTChannel *tt = channelHandle[channel];
if (tt) tt->RMTinterrupt();
if (channel == 0)
DCCTimer::updateMinimumFreeMemoryISR(0);
}
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
byte ch;
byte plen;
if (isMain) {
ch = 0;
plen = PREAMBLE_BITS_MAIN;
} else {
ch = 2;
plen = PREAMBLE_BITS_PROG;
}
// preamble
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
for (byte n=0; n<plen; n++)
setDCCBit1(preamble + n); // preamble bits
#ifdef SCOPE
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
#else
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
#endif
setEOT(preamble + plen + 1); // EOT marker
// idle
idleLen = 28;
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
if (isMain) {
for (byte n=0; n<8; n++) // 0 to 7
setDCCBit1(idle + n);
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
setDCCBit0(idle + n);
for (byte n=18; n<26; n++) // 18 to 25
setDCCBit1(idle + n);
} else {
for (byte n=0; n<26; n++) // all zero
setDCCBit0(idle + n);
}
setDCCBit1(idle + 26); // end bit
setEOT(idle + 27); // EOT marker
// data: max packet size today is 5 + checksum
maxDataLen = DATA_LEN(MAX_PACKET_SIZE+1); // plus checksum
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
rmt_config_t config;
// Configure the RMT channel for TX
bzero(&config, sizeof(rmt_config_t));
config.rmt_mode = RMT_MODE_TX;
config.channel = channel = (rmt_channel_t)ch;
config.clk_div = RMT_CLOCK_DIVIDER;
config.gpio_num = (gpio_num_t)pins.pin;
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
// number of bits needed is 22preamble + start +
// 11*9 + extrazero + EOT = 124
// 2 mem block of 64 RMT items should be enough
ESP_ERROR_CHECK(rmt_config(&config));
addPin(pins.invpin, true);
/*
// test: config another gpio pin
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
*/
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
// DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
channelHandle[channel] = this; // used by interrupt
rmt_register_tx_end_callback(interrupt, 0);
rmt_set_tx_intr_en(channel, true);
DIAG(F("Channel %d DCC signal for %s start"), config.channel, isMain ? "MAIN" : "PROG");
// send one bit to kickstart the signal, remaining data will come from the
// packet queue. We intentionally do not wait for the RMT TX complete here.
//rmt_write_items(channel, preamble, preambleLen, false);
RMTprefill();
dataReady = false;
}
void RMTChannel::RMTprefill() {
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
}
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=0) {
//int RMTChannel::RMTfillData(dccPacket packet) {
// dataReady: Signals to then interrupt routine. It is set when
// we have data in the channel buffer which can be copied out
// to the HW. dataRepeat on the other hand signals back to
// the caller of this function if the data has been sent enough
// times (0 to 3 means 1 to 4 times in total).
if (dataRepeat > 0) // we have still old work to do
return dataRepeat;
if (dataReady == true) // the packet is not copied out yet
return 1000;
if (DATA_LEN(byteCount) > maxDataLen) { // this would overun our allocated memory for data
DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), byteCount, maxDataLen);
return -1; // something very broken, can not convert packet
}
// convert bytes to RMT stream of "bits"
byte bitcounter = 0;
for(byte n=0; n<byteCount; n++) {
for(byte bit=0; bit<8; bit++) {
if (buffer[n] & transmitMask[bit])
setDCCBit1(data + bitcounter++);
else
setDCCBit0(data + bitcounter++);
}
setDCCBit0(data + bitcounter++); // zero at end of each byte
}
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter;
dataReady = true;
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
return 0;
}
void IRAM_ATTR RMTChannel::RMTinterrupt() {
//no rmt_tx_start(channel,true) as we run in loop mode
//preamble is always loaded at beginning of buffer
packetCounter++;
if (!dataReady && dataRepeat == 0) { // we did run empty
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
return; // nothing to do about that
}
// take care of incoming data
if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
dataReady = false;
}
if (dataRepeat > 0) // if a repeat count was specified, work on that
dataRepeat--;
}
bool RMTChannel::addPin(byte pin, bool inverted) {
if (pin == UNUSED_PIN)
return true;
gpio_num_t gpioNum = (gpio_num_t)(pin);
esp_err_t err;
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
err = gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
if (err != ESP_OK) return false;
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX+channel, inverted, 0);
if (err != ESP_OK) return false;
return true;
}
bool RMTChannel::addPin(pinpair pins) {
return addPin(pins.pin) && addPin(pins.invpin, true);
}
#endif //ESP32

View File

@@ -1,66 +0,0 @@
/*
* © 2021-2022, Harald Barth.
*
* This file is part of DCC-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 defined(ARDUINO_ARCH_ESP32)
#pragma once
#include <Arduino.h>
#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "soc/rmt_struct.h"
#include "MotorDriver.h" // for class pinpair
// make calculations easy and set up for microseconds
#define RMT_CLOCK_DIVIDER 80
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
#define DCC_0_HALFPERIOD 100 //8000
class RMTChannel {
public:
RMTChannel(pinpair pins, bool isMain);
bool addPin(byte pin, bool inverted=0);
bool addPin(pinpair pins);
void IRAM_ATTR RMTinterrupt();
void RMTprefill();
//int RMTfillData(dccPacket packet);
int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
inline bool busy() {
if (dataRepeat > 0) // we have still old work to do
return true;
return dataReady;
};
inline uint32_t packetCount() { return packetCounter; };
private:
rmt_channel_t channel;
// 3 types of data to send, preamble and then idle or data
// if this is prog track, idle will contain reset instead
rmt_item32_t *idle;
byte idleLen;
rmt_item32_t *preamble;
byte preambleLen;
rmt_item32_t *data;
byte dataLen;
byte maxDataLen;
uint32_t packetCounter = 0;
// flags
volatile bool dataReady = false; // do we have real data available or send idle
volatile byte dataRepeat = 0;
};
#endif //ESP32

View File

@@ -1,132 +0,0 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* 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/>.
*/
/* There are several different implementations of this class which the compiler will select
according to the hardware.
*/
/* 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.
*
*/
#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);
static void clearPWM();
// 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.
//
static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0)
__attribute__((always_inline)) {
int spare = freeMemory()-extraBytes;
if (spare < 0) spare = 0;
if (spare < minimum_free_memory) minimum_free_memory = spare;
};
static int getMinimumFreeMemory();
static void reset();
private:
static int freeMemory();
static volatile int minimum_free_memory;
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency
static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
#else
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
#endif
};
// Class ADCee implements caching of the ADC value for platforms which
// have a too slow ADC read to wait for. On these platforms the ADC is
// scanned continiously in the background from an ISR. On such
// architectures that use the analog read during DCC waveform with
// specially configured ADC, for example AVR, init must be called
// PRIOR to the start of the waveform. It returns the current value so
// that an offset can be initialized.
class ADCee {
public:
// init does add the pin to the list of scanned pins (if this
// platform's implementation scans pins) and returns the first
// read value. It is called before the regular scan is started.
static int init(uint8_t pin);
// read does read the pin value from the scanned cache or directly
// if this is a platform that does not scan. fromISR is a hint if
// it was called from ISR because for some implementations that
// makes a difference.
static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return
static int16_t ADCmax();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
// bit array of used pins (max 16)
static uint16_t usedpins;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;
// ids to scan (new way)
static byte *idarr;
// friend so that we can call scan() and begin()
friend class DCCWaveform;
};
#endif

View File

@@ -1,234 +0,0 @@
/*
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 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/>.
*/
// ATTENTION: this file only compiles on a UNO or MEGA
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_AVR
#include <avr/boot.h>
#include <avr/wdt.h>
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
// 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();
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
}
void DCCTimer::clearPWM() {
TCCR1A= 0;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
for (byte i=0; i<6; i++) {
// take the fist 3 and last 3 of the serial.
// the first 5 of 8 are at 0x0E to 0x013
// the last 3 of 8 are at 0x15 to 0x017
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
}
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
extern char *__brkval;
extern char *__malloc_heap_start;
int DCCTimer::freeMemory() {
char top;
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}
void DCCTimer::reset() {
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
}
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define NUM_ADC_INPUTS 16
#else
#define NUM_ADC_INPUTS 8
#endif
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
byte *ADCee::idarr = NULL;
static bool ADCusesHighPort = false;
/*
* Register a new pin to be scanned
* Returns current reading of pin and
* stores that as well
*/
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
byte n;
if (id >= NUM_ADC_INPUTS)
return -1023;
if (id > 7)
ADCusesHighPort = true;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL) {
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker
analogvals[n] = -32768; // 16 bit int min value
idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value
for (n=0 ; n <= NUM_ADC_INPUTS; n++)
idarr[n] = 255; // set 255 as end of array marker
}
analogvals[id] = value; // store before enable by idarr[n]
for (n=0 ; n <= NUM_ADC_INPUTS; n++) {
if (idarr[n] == 255) {
idarr[n] = id;
break;
}
}
return value;
}
int16_t ADCee::ADCmax() {
return 1023;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
(void)fromISR; // AVR does ignore this arg
uint8_t id = pin - A0;
int a;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
noInterrupts();
a = analogvals[id];
interrupts();
return a;
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static byte num = 0; // index into id array
static bool waiting = false;
if (waiting) {
// look if we have a result
byte low, high;
if (bit_is_set(ADCSRA, ADSC))
return; // no result, continue to wait
// found value
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
analogvals[idarr[num]] = (high << 8) | low;
waiting = false;
}
if (!waiting) {
// cycle around in-use analogue pins
num++;
if (idarr[num] == 255)
num = 0;
// start new ADC aquire on id
#if defined(ADCSRB) && defined(MUX5)
if (ADCusesHighPort) { // if we ever have started to use high pins)
if (idarr[num] > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit
else
bitClear(ADCSRB, MUX5);
}
#endif
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX
bitSet(ADCSRA, ADSC); // start conversion
waiting = true;
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
// Set up ADC for free running mode
ADMUX=(1<<REFS0); //select AVCC as reference. We set MUX later
ADCSRA = (1<<ADEN)|(1 << ADPS2); // ADPS2 means divisor 32 and 16Mhz/32=500kHz.
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
interrupts();
}
#endif

View File

@@ -1,178 +0,0 @@
/*
* © 2020-2022 Harald Barth
*
* 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/>.
*/
// ATTENTION: this file only compiles on an ESP8266 and ESP32
// On ESP32 we do not even use the functions but they are here for completeness sake
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_ESP8266
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
timer1_disable();
// There seem to be differnt ways to attach interrupt handler
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
// Let us choose the one from the API
timer1_attachInterrupt(interruptHandler);
// not exactly sure of order:
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
timer1_write(CLOCK_CYCLES);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
void IRAM_ATTR DCCTimer::clearPWM() {
}
// Fake this as it should not be used
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
mac[0] = 0xFE;
mac[1] = 0xBE;
mac[2] = 0xEF;
mac[3] = 0xC0;
mac[4] = 0xFF;
mac[5] = 0xEE;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
int DCCTimer::freeMemory() {
return ESP.getFreeHeap();
}
#endif
////////////////////////////////////////////////////////////////////////
#ifdef ARDUINO_ARCH_ESP32
#include <driver/adc.h>
#include <soc/sens_reg.h>
#include <soc/sens_struct.h>
#undef ADC_INPUT_MAX_VALUE
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
int IRAM_ATTR local_adc1_get_raw(int channel) {
uint16_t adc_value;
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
while (SENS.sar_slave_addr1.meas_status != 0);
SENS.sar_meas_start1.meas1_start_sar = 0;
SENS.sar_meas_start1.meas1_start_sar = 1;
while (SENS.sar_meas_start1.meas1_done_sar == 0);
adc_value = SENS.sar_meas_start1.meas1_data_sar;
return adc_value;
}
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
// This should not be called on ESP32 so disable it
return;
interruptHandler = callback;
hw_timer_t *timer = NULL;
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
timerAttachInterrupt(timer, interruptHandler, true);
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
timerAlarmEnable(timer);
}
// We do not support to use PWM to make the Waveform on ESP
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
return false;
}
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
}
void IRAM_ATTR DCCTimer::clearPWM() {
}
// Fake this as it should not be used
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
mac[0] = 0xFE;
mac[1] = 0xBE;
mac[2] = 0xEF;
mac[3] = 0xC0;
mac[4] = 0xFF;
mac[5] = 0xEE;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
int DCCTimer::freeMemory() {
return ESP.getFreeHeap();
}
void DCCTimer::reset() {
ESP.restart();
}
int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(pinToADC1Channel(pin),ADC_ATTEN_DB_11);
return adc1_get_raw(pinToADC1Channel(pin));
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
return local_adc1_get_raw(pinToADC1Channel(pin));
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
}
#endif //ESP32

View File

@@ -1,155 +0,0 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 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.
*
*/
// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_MEGAAVR
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
extern char *__brkval;
extern char *__malloc_heap_start;
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; // Clear interrupt request flag
interruptHandler();
}
bool DCCTimer::isPWMPin(byte pin) {
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
(void) pin;
(void) high;
// TODO what are the relevant pins?
}
void DCCTimer::clearPWM() {
// Do nothing unless we implent HA
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
mac[0] &= 0xFE;
mac[0] |= 0x02;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = minimum_free_memory;
interrupts();
return retval;
}
extern char *__brkval;
extern char *__malloc_heap_start;
int DCCTimer::freeMemory() {
char top;
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}
void DCCTimer::reset() {
CPU_CCP=0xD8;
WDT.CTRLA=0x4;
while(true){}
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -1,277 +0,0 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 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/>.
*/
// ATTENTION: this file only compiles on a SAMD21 based board
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_SAMD
#include "DCCTimer.h"
#include <wiring_private.h>
INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// Timer setup - setup clock sources first
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);
// Assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway
// for "normal accuracy" DCC waveform generation. For high accuracy we're going to need
// to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and output
// pins for each TCC are only available on certain pins
TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync
// Set the frequency
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val);
TCC0->PER.reg = CLOCK_CYCLES * 2;
while (TCC0->SYNCBUSY.bit.PER);
// Start the timer
TCC0->CTRLA.bit.ENABLE = 1;
while (TCC0->SYNCBUSY.bit.ENABLE);
// Set the interrupt condition, priority and enable it in the NVIC
TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow
int USBprio = NVIC_GetPriority((IRQn_Type) USB_IRQn); // Fetch the USB priority
NVIC_SetPriority((IRQn_Type)TCC0_IRQn, USBprio); // Match the USB priority
// NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority
NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt
interrupts();
}
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
// copied from rf24 branch
void TCC0_Handler() {
if(TCC0->INTFLAG.bit.OVF) {
TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
void TCC1_Handler() {
if(TCC1->INTFLAG.bit.OVF) {
TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
void TCC2_Handler() {
if(TCC2->INTFLAG.bit.OVF) {
TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag
interruptHandler();
}
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
return false;
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO: High Accuracy mode is not supported as yet, and may never need to be
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
return;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
int DCCTimer::freeMemory() {
char top;
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
}
void DCCTimer::reset() {
__disable_irq();
NVIC_SystemReset();
while(true) {};
}
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
int ADCee::init(uint8_t pin) {
uint id = pin - A0;
int value = 0;
if (id > NUM_ADC_INPUTS)
return -1023;
// Permanently configure SAMD IO MUX for that pin
pinPeripheral(pin, PIO_ANALOG);
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// Wait for the conversion to be ready
while (ADC->INTFLAG.bit.RESRDY == 0); // Waiting for conversion to complete
// Read the value
value = ADC->RESULT.reg;
if (analogvals == NULL)
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
return value;
}
int16_t ADCee::ADCmax() {
return 4095;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static uint id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
if (waiting) {
// look if we have a result
if (ADC->INTFLAG.bit.RESRDY == 0)
return; // no result, continue to wait
// found value
analogvals[id] = ADC->RESULT.reg;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[id + A0].ulADCChannelNumber; // Selection for the positive ADC input
// Start conversion
ADC->SWTRIG.bit.START = 1;
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
// Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS,
// and we need sub-58uS. This code sets it to a read speed of around 5-6uS, and enables
// 12-bit mode
// Reconfigure ADC
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
interrupts();
}
#endif

View File

@@ -1,310 +0,0 @@
/*
* © 2023 Neil McKechnie
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 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/>.
*/
// ATTENTION: this file only compiles on a STM32 based boards
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef ARDUINO_ARCH_STM32
#include "DCCTimer.h"
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10, Tx=PB6 -- CN10 pins 33 and 17 - F446RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
#else
#warning Serial1 not defined
#endif
INTERRUPT_CALLBACK interruptHandler=0;
// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
// Also, timer #3 will do hardware pulses on pin D12. This gives
// accurate timing, independent of the latency of interrupt handling.
// We only need to interrupt on one of these (TIM2), the other will just generate
// pulses.
HardwareTimer timer(TIM2);
HardwareTimer timerAux(TIM3);
// Timer IRQ handler
void Timer_Handler() {
interruptHandler();
}
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
timer.pause();
timerAux.pause();
timer.setPrescaleFactor(1);
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timer.attachInterrupt(Timer_Handler);
timer.refresh();
timerAux.setPrescaleFactor(1);
timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timerAux.refresh();
timer.resume();
timerAux.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
// Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
// Enable the appropriate timer channel.
switch (pin) {
case 12:
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
return true;
case 13:
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
return true;
default:
return false;
}
}
void DCCTimer::setPWM(byte pin, bool high) {
// Set the timer so that, at the next counter overflow, the requested
// pin state is activated automatically before the interrupt code runs.
// TIM2 is timer, TIM3 is timerAux.
switch (pin) {
case 12:
if (high)
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
else
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
break;
case 13:
if (high)
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
else
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
break;
}
}
void DCCTimer::clearPWM() {
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;
mac[0] = m1 >> 8;
mac[1] = m1 >> 0;
mac[2] = m2 >> 24;
mac[3] = m2 >> 16;
mac[4] = m2 >> 8;
mac[5] = m2 >> 0;
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
int DCCTimer::freeMemory() {
char top;
return (int)(&top - reinterpret_cast<char *>(sbrk(0)));
}
void DCCTimer::reset() {
__disable_irq();
NVIC_SystemReset();
while(true) {};
}
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
uint id = pin - A0;
int value = 0;
PinName stmpin = digitalPin[analogInputPin[id]];
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
GPIO_TypeDef * gpioBase;
// Port config - find which port we're on and power it up
switch(stmgpio) {
case 0x00:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
gpioBase = GPIOA;
break;
case 0x01:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB
gpioBase = GPIOB;
break;
case 0x02:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC;
break;
}
// Set pin mux mode to analog input
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
// Set the sampling rate for that analog input
if (adcchan < 10)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
// Read the inital ADC value for this analog input
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
value = ADC1->DR; // Read value from register
if (analogvals == NULL)
{
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
}
analogvals[id] = value; // Store sampled value
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
usedpins |= (1 << id); // This pin is now ready
return value;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - A0;
// Was this pin initialised yet?
if ((usedpins & (1<<id) ) == 0)
return -1023;
// We do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
return analogvals[id];
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static uint id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
if (waiting) {
// look if we have a result
if (!(ADC1->SR & (1 << 1)))
return; // no result, continue to wait
// found value
analogvals[id] = ADC1->DR;
// advance at least one track
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
//ADC1 config sequence
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC1->CR2 &= ~(1 << 1); //Single conversion
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->CR2 |= (1 << 0); // Switch on ADC1
interrupts();
}
#endif

View File

@@ -1,171 +0,0 @@
/*
* © 2022 Paul M Antoine
* © 2021 Mike S
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 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/>.
*/
// ATTENTION: this file only compiles on a TEENSY
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#ifdef TEENSYDUINO
#include "DCCTimer.h"
INTERRUPT_CALLBACK interruptHandler=0;
IntervalTimer myDCCTimer;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
}
bool DCCTimer::isPWMPin(byte pin) {
//Teensy: digitalPinHasPWM, todo
(void) pin;
return false; // TODO what are the relevant pins?
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO what are the relevant pins?
(void) pin;
(void) high;
}
void DCCTimer::clearPWM() {
// Do nothing unless we implent HA
}
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
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
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
void teensyRead(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.
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
teensyRead(0xe,mac,0);
teensyRead(0xf,mac,3);
}
#endif
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
#if !defined(__IMXRT1062__)
int DCCTimer::freeMemory() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
#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
int DCCTimer::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;
}
#endif
void DCCTimer::reset() {
// found at https://forum.pjrc.com/threads/59935-Reboot-Teensy-programmatically
SCB_AIRCR = 0x05FA0004;
}
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
return analogRead(pin);
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int current;
if (!fromISR) noInterrupts();
current = analogRead(pin);
if (!fromISR) interrupts();
return current;
}
/*
* Scan function that is called from interrupt
*/
void ADCee::scan() {
}
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -1,12 +1,8 @@
/*
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of CommandStation-EX
* 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
@@ -21,80 +17,69 @@
* 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_ARCH_ESP32
// This code is replaced entirely on an ESP32
#include <Arduino.h>
#include "DCCWaveform.h"
#include "TrackManager.h"
#include "DCCTimer.h"
#include "DCCACK.h"
#include "DIAG.h"
const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
bool DCCWaveform::progTrackSyncMain=false;
bool DCCWaveform::progTrackBoosted=false;
VirtualTimer * DCCWaveform::interruptTimer=NULL;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
mainTrack.motorDriver=mainDriver;
progTrack.motorDriver=progDriver;
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
const byte resetPacket[] = {0x00, 0x00, 0x00};
// For each state of the wave nextState=stateTransform[currentState]
const WAVE_STATE 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 signalTransform[]={
/* WAVE_START -> */ HIGH,
/* WAVE_MID_1 -> */ LOW,
/* WAVE_HIGH_0 -> */ HIGH,
/* WAVE_MID_0 -> */ LOW,
/* WAVE_LOW_0 -> */ LOW,
/* WAVE_PENDING (should not happen) -> */ LOW};
void DCCWaveform::begin() {
DCCTimer::begin(DCCWaveform::interruptHandler);
mainTrack.setPowerMode(POWERMODE::OFF);
progTrack.setPowerMode(POWERMODE::OFF);
switch (timerNumber) {
case 1: interruptTimer= &TimerA; break;
case 2: interruptTimer= &TimerB; break;
#ifndef ARDUINO_AVR_UNO
case 3: interruptTimer= &TimerC; break;
#endif
default:
DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber);
return;
}
interruptTimer->initialize();
interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
interruptTimer->attachInterrupt(interruptHandler);
interruptTimer->start();
}
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
interruptTimer->start();
DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET"));
}
void DCCWaveform::loop() {
// empty placemarker in case ESP32 needs something here
mainTrack.checkPowerOverload();
progTrack.checkPowerOverload();
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
// 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=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
// Set the signal state for both tracks
TrackManager::setDCCSignal(sigMain);
TrackManager::setPROGSignal(sigProg);
// Refresh the values in the ADCee object buffering the values of the ADC HW
ADCee::scan();
// 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 DCCACK::checkAck(progTrack.getResets());
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();
}
#pragma GCC pop_options
// An instance of this class handles the DCC transmissions for one track. (main or prog)
// Interrupts are marshalled via the statics.
@@ -102,40 +87,148 @@ void DCCWaveform::interruptHandler() {
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
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;
sampleDelay = 0;
lastSampleTaken = millis();
ackPending=false;
}
POWERMODE DCCWaveform::getPowerMode() {
return powerMode;
}
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() {
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 && !ackPending && !progTrackSyncMain && !progTrackBoosted)
tripValue=progTripValue;
switch (powerMode) {
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
lastCurrent = motorDriver->getCurrentRaw();
if (lastCurrent <= tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
else
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
} else {
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;
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPowerMode(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
// 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.
DCCTimer::updateMinimumFreeMemoryISR(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
@@ -155,126 +248,106 @@ 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;
clearResets();
sentResetsSincePacket=0;
}
else {
// Fortunately reset and idle packets are the same length
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
transmitLength = sizeof(idlePacket);
transmitRepeats = 0;
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
if (sentResetsSincePacket<250) sentResetsSincePacket++;
}
}
}
}
#pragma GCC pop_options
// 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;
packetPending = true;
clearResets();
sentResetsSincePacket=0;
}
bool DCCWaveform::getPacketPending() {
return packetPending;
int DCCWaveform::getLastCurrent() {
return lastCurrent;
}
#endif
#ifdef ARDUINO_ARCH_ESP32
#include "DCCWaveform.h"
#include "DCCACK.h"
// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...)
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
RMTChannel *DCCWaveform::rmtMainChannel = NULL;
RMTChannel *DCCWaveform::rmtProgChannel = NULL;
DCCWaveform::DCCWaveform(byte preambleBits, bool isMain) {
isMainTrack = isMain;
requiredPreambles = preambleBits;
void DCCWaveform::setAckBaseline() {
if (isMainTrack) return;
int baseline = motorDriver->getCurrentRaw();
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA threshold=%d/%dmA"),
baseline,motorDriver->raw2mA(baseline),
ackThreshold,motorDriver->raw2mA(ackThreshold));
}
void DCCWaveform::begin() {
for(const auto& md: TrackManager::getMainDrivers()) {
pinpair p = md->getSignalPin();
if(rmtMainChannel) {
//DIAG(F("added pins %d %d to MAIN channel"), p.pin, p.invpin);
rmtMainChannel->addPin(p); // add pin to existing main channel
} else {
//DIAG(F("new MAIN channel with pins %d %d"), p.pin, p.invpin);
rmtMainChannel = new RMTChannel(p, true); /* create new main channel */
void DCCWaveform::setAckPending() {
if (isMainTrack) return;
ackMaxCurrent=0;
ackPulseStart=0;
ackPulseDuration=0;
ackDetected=false;
ackCheckStart=millis();
ackPending=true; // interrupt routines will now take note
}
byte DCCWaveform::getAck() {
if (ackPending) return (2); // still waiting
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.
}
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;
}
}
MotorDriver *md = TrackManager::getProgDriver();
if (md) {
pinpair p = md->getSignalPin();
if (rmtProgChannel) {
//DIAG(F("added pins %d %d to PROG channel"), p.pin, p.invpin);
rmtProgChannel->addPin(p); // add pin to existing prog channel
} else {
//DIAG(F("new PROGchannel with pins %d %d"), p.pin, p.invpin);
rmtProgChannel = new RMTChannel(p, false);
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 (lastCurrent>ackThreshold) {
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
return;
}
}
// not in pulse
if (ackPulseStart==0) return; // keep waiting for leading edge
// detected trailing edge of pulse
ackPulseDuration=micros()-ackPulseStart;
if (ackPulseDuration>=MIN_ACK_PULSE_DURATION && ackPulseDuration<=MAX_ACK_PULSE_DURATION) {
ackCheckDuration=millis()-ackCheckStart;
ackDetected=true;
ackPending=false;
transmitRepeats=0; // shortcut remaining repeat packets
return; // we have a genuine ACK result
}
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
}
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
byte checksum = 0;
for (byte 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;
// The resets will be zero not only now but as well repeats packets into the future
clearResets(repeats+1);
{
int ret;
do {
if(isMainTrack) {
if (rmtMainChannel != NULL)
ret = rmtMainChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
} else {
if (rmtProgChannel != NULL)
ret = rmtProgChannel->RMTfillData(pendingPacket, pendingLength, pendingRepeats);
}
} while(ret > 0);
}
}
bool DCCWaveform::getPacketPending() {
if(isMainTrack) {
if (rmtMainChannel == NULL)
return true;
return rmtMainChannel->busy();
} else {
if (rmtProgChannel == NULL)
return true;
return rmtProgChannel->busy();
}
}
void IRAM_ATTR DCCWaveform::loop() {
DCCACK::checkAck(progTrack.getResets());
}
#endif

View File

@@ -1,12 +1,8 @@
/*
* © 2021 M Steve Todd
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of CommandStation-EX
* 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
@@ -23,87 +19,115 @@
*/
#ifndef DCCWaveform_h
#define DCCWaveform_h
#include "MotorDriver.h"
#ifdef ARDUINO_ARCH_ESP32
#include "DCCRMT.h"
#include "TrackManager.h"
#endif
#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 { OFF, ON, OVERLOAD };
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
const byte resetPacket[] = {0x00, 0x00, 0x00};
class DCCWaveform {
public:
DCCWaveform( byte preambleBits, bool isMain);
static void begin();
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber);
static void setDiagnosticSlowWave(bool slow);
static void loop();
static DCCWaveform mainTrack;
static DCCWaveform progTrack;
inline void clearRepeats() { transmitRepeats=0; }
#ifndef ARDUINO_ARCH_ESP32
inline void clearResets() { sentResetsSincePacket=0; }
inline byte getResets() { return sentResetsSincePacket; }
#else
// extrafudge is added when we know that the resets will first come extrafudge packets in the future
inline void clearResets(byte extrafudge=0) {
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return;
resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
resetPacketBase += extrafudge;
};
inline byte getResets() {
if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0;
uint32_t packetcount = isMainTrack ?
rmtMainChannel->packetCount() : rmtProgChannel->packetCount();
uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic.
if (count > UINT32_MAX/2) // we are in the extrafudge area
return 0;
if (count > 255) // cap to 255
return 255;
return count; // all special cases handled above
};
#endif
void beginTrack();
void setPowerMode(POWERMODE);
POWERMODE getPowerMode();
void checkPowerOverload();
int getLastCurrent();
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
bool getPacketPending();
private:
#ifndef ARDUINO_ARCH_ESP32
volatile bool packetPending;
volatile byte sentResetsSincePacket;
#else
volatile uint32_t resetPacketBase;
#endif
volatile bool autoPowerOff=false;
void setAckBaseline(); //prog track only
void setAckPending(); //prog track only
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
inline void doAutoPowerOff() {
if (autoPowerOff) {
setPowerMode(POWERMODE::OFF);
autoPowerOff=false;
}
};
inline void setAckLimit(int mA) {
ackLimitmA = mA;
}
private:
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;
#ifdef ARDUINO_ARCH_ESP32
static RMTChannel *rmtMainChannel;
static RMTChannel *rmtProgChannel;
#endif
int lastCurrent;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
// ACK management (Prog track only)
volatile bool ackPending;
volatile bool ackDetected;
int ackThreshold;
int ackLimitmA = 60;
int ackMaxCurrent;
unsigned long ackCheckStart; // millis
unsigned int ackCheckDuration; // millis
unsigned int ackPulseDuration; // micros
unsigned long ackPulseStart; // micros
};
#endif

8
DIAG.h
View File

@@ -1,9 +1,7 @@
/*
* © 2021 Fred Decker
* © 2020 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
* 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
@@ -20,9 +18,7 @@
*/
#ifndef DIAG_h
#define DIAG_h
#include "StringFormatter.h"
#define DIAG StringFormatter::diag
#define LCD StringFormatter::lcd
#define SCREEN StringFormatter::lcd2
#endif

View File

@@ -1,7 +1,7 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
* © 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
@@ -16,181 +16,25 @@
* You should have received a copy of the GNU General Public License
* 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 noMoreRowsToDisplay 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).
*/
#include <Arduino.h>
#include "Display.h"
#include "DIAG.h"
// Constructor - allocates device driver.
Display::Display(DisplayDevice *deviceDriver) {
_deviceDriver = deviceDriver;
// Get device dimensions in characters (e.g. 16x2).
numCharacterColumns = _deviceDriver->getNumCols();
numCharacterRows = _deviceDriver->getNumRows();;
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
topRow = ROW_INITIAL; // loop2 will fill from row 0
addDisplay(0); // Add this display as display number 0
};
void Display::begin() {
_deviceDriver->begin();
_deviceDriver->clearNative();
}
void Display::_clear() {
_deviceDriver->clearNative();
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
rowBuffer[row][0] = '\0';
topRow = ROW_INITIAL; // loop2 will fill from row 0
}
void Display::_setRow(uint8_t line) {
hotRow = line;
hotCol = 0;
rowBuffer[hotRow][0] = 0; // Clear existing text
}
size_t Display::_write(uint8_t b) {
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
rowBuffer[hotRow][hotCol] = b;
hotCol++;
rowBuffer[hotRow][hotCol] = 0;
return 1;
}
// Refresh screen completely (will block until complete). Used
// during start-up.
void Display::_refresh() {
loop2(true);
}
// On normal loop entries, loop will only make one output request on each
// entry, to avoid blocking while waiting for the I2C.
void Display::_displayLoop() {
// If output device is busy, don't do anything on this loop
// This avoids blocking while waiting for the device to complete.
if (!_deviceDriver->isBusy()) loop2(false);
}
Display *Display::loop2(bool force) {
unsigned long currentMillis = millis();
if (!force) {
// See if we're in the time between updates
if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME)
return NULL;
} else {
// force full screen update from the beginning.
rowFirst = ROW_INITIAL;
rowNext = ROW_INITIAL;
bufferPointer = 0;
noMoreRowsToDisplay = false;
slot = 0;
}
do {
if (bufferPointer == 0) {
// Find a line of data to write to the screen.
if (rowFirst == ROW_INITIAL) rowFirst = rowNext;
if (findNextNonBlankRow()) {
// Non-blank line found, so copy it (including terminator)
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
buffer[i] = rowBuffer[rowNext][i];
} else {
// No non-blank lines left, so draw a blank line
buffer[0] = 0;
}
_deviceDriver->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) {
_deviceDriver->writeNative(ch);
bufferPointer++;
} else {
_deviceDriver->writeNative(' ');
}
if (++charIndex >= MAX_CHARACTER_COLS) {
// Screen slot completed, move to next slot on screen
bufferPointer = 0;
slot++;
if (slot >= numCharacterRows) {
// Last slot on screen written, reset ready for next screen update.
#if SCROLLMODE==2
if (!noMoreRowsToDisplay) {
// On next refresh, restart one row on from previous start.
rowNext = rowFirst;
findNextNonBlankRow();
}
#endif
noMoreRowsToDisplay = false;
slot = 0;
rowFirst = ROW_INITIAL;
lastScrollTime = currentMillis;
return NULL;
}
}
}
} while (force);
return NULL;
}
bool Display::findNextNonBlankRow() {
while (!noMoreRowsToDisplay) {
if (rowNext == ROW_INITIAL)
rowNext = 0;
void Display::displayIP(byte ip[4]) {
if (ip != NULL)
LCD(6,F("%d.%d.%d.%d"),ip[0],ip[1],ip[2],ip[3]);
else
rowNext = rowNext + 1;
if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL;
#if SCROLLMODE == 1
// Finished if we've looped back to start
if (rowNext == ROW_INITIAL) {
noMoreRowsToDisplay = true;
return false;
}
#else
// Finished if we're back to the first one shown
if (rowNext == rowFirst) {
noMoreRowsToDisplay = true;
return false;
}
#endif
if (rowBuffer[rowNext][0] != 0) {
// Found non-blank row
return true;
}
}
return false;
}
LCD(6,F("NO IP"));
}
void Display::displayIP(char *ip) {
if (ip != NULL)
LCD(6,F("%s"),ip);
else
LCD(6,F("NO IP"));
}
void Display::displayPort(int i) {
if (i > 0)
LCD(7,F(":%d"),i);
else
LCD(7,F("none"));
}

View File

@@ -1,8 +1,6 @@
/*
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
*
* This file is part of CommandStation-EX
*
* © 2020, Harald Barth.
*
* 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
@@ -18,59 +16,14 @@
*/
#ifndef Display_h
#define Display_h
#include <Arduino.h>
#include "defines.h"
#include "DisplayInterface.h"
// Allow maximum message length to be overridden from config.h
#if !defined(MAX_MSG_SIZE)
#define MAX_MSG_SIZE 20
#endif
class Display {
public:
void displayIP(byte[4]);
void displayIP(char *);
void displayPort(int);
// Set default scroll mode (overridable in config.h)
#if !defined(SCROLLMODE)
#define SCROLLMODE 1
#endif
// This class is created in Display_Implementation.h
class Display : public DisplayInterface {
public:
Display(DisplayDevice *deviceDriver);
static const int MAX_CHARACTER_ROWS = 8;
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
static const uint8_t ROW_INITIAL = 255;
private:
DisplayDevice *_deviceDriver;
unsigned long lastScrollTime = 0;
uint8_t hotRow = 0;
uint8_t hotCol = 0;
uint8_t topRow = 0;
uint8_t slot = 0;
uint8_t rowFirst = ROW_INITIAL;
uint8_t rowNext = ROW_INITIAL;
uint8_t charIndex = 0;
char buffer[MAX_CHARACTER_COLS + 1];
char* bufferPointer = 0;
bool noMoreRowsToDisplay = false;
uint16_t numCharacterRows;
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
public:
void begin() override;
void _clear() override;
void _setRow(uint8_t line) override;
size_t _write(uint8_t b) override;
void _refresh() override;
void _displayLoop() override;
Display *loop2(bool force);
bool findNextNonBlankRow();
private:
};
#endif

View File

@@ -1,27 +0,0 @@
/*
* © 2021 Neil McKechnie
* © 2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "DisplayInterface.h"
// Start of chain of display handlers.
DisplayInterface *DisplayInterface::_displayHandler = NULL;
uint8_t DisplayInterface::_selectedDisplayNo = 255;

View File

@@ -1,99 +0,0 @@
/*
* © 2021 Neil McKechnie
* © 2021 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DisplayInterface_h
#define DisplayInterface_h
#include <Arduino.h>
// Definition of base class for displays. The base class does nothing.
class DisplayInterface : public Print {
protected:
static DisplayInterface *_displayHandler;
static uint8_t _selectedDisplayNo; // Nothing selected.
DisplayInterface *_nextHandler = NULL;
uint8_t _displayNo = 0;
public:
// Add display object to list of displays
void addDisplay(uint8_t displayNo) {
_nextHandler = _displayHandler;
_displayHandler = this;
_displayNo = displayNo;
}
static DisplayInterface *getDisplayHandler() {
return _displayHandler;
}
uint8_t getDisplayNo() {
return _displayNo;
}
// The next functions are to provide compatibility with calls to the LCD function
// which does not specify a display number. These always apply to display '0'.
static void refresh() { refresh(0); };
static void setRow(uint8_t line) { setRow(0, line); };
static void clear() { clear(0); };
// Additional functions to support multiple displays. These perform a
// multicast to all displays that match the selected displayNo.
// Display number zero is the default one.
static void setRow(uint8_t displayNo, uint8_t line) {
_selectedDisplayNo = displayNo;
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) {
if (displayNo == p->_displayNo) p->_setRow(line);
}
}
size_t write (uint8_t c) override {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (_selectedDisplayNo == p->_displayNo) p->_write(c);
return _displayHandler ? 1 : 0;
}
static void clear(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_clear();
}
static void refresh(uint8_t displayNo) {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
if (displayNo == p->_displayNo) p->_refresh();
}
static void loop() {
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
p->_displayLoop();
};
// The following are overridden within the specific device class
virtual void begin() {};
virtual size_t _write(uint8_t c) { (void)c; return 0; };
virtual void _setRow(uint8_t line) { (void)line; }
virtual void _clear() {}
virtual void _refresh() {}
virtual void _displayLoop() {}
};
class DisplayDevice {
public:
virtual bool begin() { return true; }
virtual void clearNative() = 0;
virtual void setRowNative(uint8_t line) = 0;
virtual size_t writeNative(uint8_t c) = 0;
virtual bool isBusy() = 0;
virtual uint16_t getNumRows() = 0;
virtual uint16_t getNumCols() = 0;
};
#endif

View File

@@ -1,60 +0,0 @@
/*
* © 2021, Chris Harlow, 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/>.
*/
////////////////////////////////////////////////////////////////////////////////////
// This implementation is designed to be #included ONLY ONCE in the .ino
//
// 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.
////////////////////////////////////////////////////////////////////////////////////
#ifndef LCD_Implementation_h
#define LCD_Implementation_h
#include "DisplayInterface.h"
#include "SSD1306Ascii.h"
#include "LiquidCrystal_I2C.h"
// Implement the Display shim class as a singleton.
// The DisplayInterface class implements a display handler with no code (null device);
// The Display class sub-classes DisplayInterface to provide the common display code;
// Then Display class talks to the specific device type classes:
// SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers;
// LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'.
#if defined(OLED_DRIVER)
#define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \
t->begin(); \
xxx; \
t->refresh(); \
}
#elif defined(LCD_DRIVER)
#define DISPLAY_START(xxx) { \
DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \
t->begin(); \
xxx; \
t->refresh();}
#else
#define DISPLAY_START(xxx) {}
#endif
#endif // LCD_Implementation_h

View File

@@ -1,12 +1,9 @@
/*
* © 2021 Neil McKechnie
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2013-2016 Gregg E. Berman
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
* © 2020, Harald Barth.
*
* This file is part of CommandStation-EX
* 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
@@ -21,92 +18,89 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include "defines.h"
#ifndef DISABLE_EEPROM
#include "EEStore.h"
#include "DIAG.h"
#include "Outputs.h"
#include "Sensors.h"
#include "Turnouts.h"
#include "Sensors.h"
#include "Outputs.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_SAMC)
#if defined(ARDUINO_ARCH_SAMD)
ExternalEEPROM EEPROM;
#endif
void EEStore::init() {
#if defined(ARDUINO_ARCH_SAMC)
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three
// A pins grounded (0b1010000 = 0x50)
void EEStore::init(){
#if defined(ARDUINO_ARCH_SAMD)
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three A pins grounded (0b1010000 = 0x50)
#endif
eeStore = (EEStore *)calloc(1, sizeof(EEStore));
eeStore=(EEStore *)calloc(1,sizeof(EEStore));
EEPROM.get(0,eeStore->data); // get eeStore data
EEPROM.get(0, eeStore->data); // get eeStore data
if(strncmp(eeStore->data.id,EESTORE_ID,sizeof(EESTORE_ID))!=0){ // check to see that eeStore contains valid DCC++ ID
sprintf(eeStore->data.id,EESTORE_ID); // if not, create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
eeStore->data.nTurnouts=0;
eeStore->data.nSensors=0;
eeStore->data.nOutputs=0;
EEPROM.put(0,eeStore->data);
}
// check to see that eeStore contains valid DCC++ ID
if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) {
// if not, create blank eeStore structure (no
// turnouts, no sensors) and save it back to EEPROM
strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0);
eeStore->data.nTurnouts = 0;
eeStore->data.nSensors = 0;
eeStore->data.nOutputs = 0;
EEPROM.put(0, eeStore->data);
}
reset(); // set memory pointer to first free EEPROM space
Turnout::load(); // load turnout definitions
Sensor::load(); // load sensor definitions
Output::load(); // load output definitions
reset(); // set memory pointer to first free EEPROM space
Turnout::load(); // load turnout definitions
Sensor::load(); // load sensor definitions
Output::load(); // load output definitions
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::clear() {
sprintf(eeStore->data.id,
EESTORE_ID); // create blank eeStore structure (no turnouts, no
// sensors) and save it back to EEPROM
eeStore->data.nTurnouts = 0;
eeStore->data.nSensors = 0;
eeStore->data.nOutputs = 0;
EEPROM.put(0, eeStore->data);
void EEStore::clear(){
sprintf(eeStore->data.id,EESTORE_ID); // create blank eeStore structure (no turnouts, no sensors) and save it back to EEPROM
eeStore->data.nTurnouts=0;
eeStore->data.nSensors=0;
eeStore->data.nOutputs=0;
EEPROM.put(0,eeStore->data);
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::store() {
reset();
Turnout::store();
Sensor::store();
Output::store();
EEPROM.put(0, eeStore->data);
DIAG(F("EEPROM used: %d/%d bytes"), EEStore::pointer(), EEPROM.length());
void EEStore::store(){
reset();
Turnout::store();
Sensor::store();
Output::store();
EEPROM.put(0,eeStore->data);
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::advance(int n) { eeAddress += n; }
void EEStore::advance(int n){
eeAddress+=n;
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::reset() { eeAddress = sizeof(EEStore); }
void EEStore::reset(){
eeAddress=sizeof(EEStore);
}
///////////////////////////////////////////////////////////////////////////////
int EEStore::pointer() { return (eeAddress); }
int EEStore::pointer(){
return(eeAddress);
}
///////////////////////////////////////////////////////////////////////////////
void EEStore::dump(int num) {
byte b = 0;
DIAG(F("Addr 0x char"));
for (int n = 0; n < num; n++) {
EEPROM.get(n, b);
DIAG(F("%d %x %c"), n, b, isprint(b) ? b : ' ');
}
byte b;
DIAG(F("\nAddr 0x char\n"));
for (int n=0 ; n<num; n++) {
EEPROM.get(n, b);
DIAG(F("%d %x %c\n"),n,b,isprint(b) ? b : ' ');
}
}
///////////////////////////////////////////////////////////////////////////////
EEStore *EEStore::eeStore = NULL;
int EEStore::eeAddress = 0;
#endif
EEStore *EEStore::eeStore=NULL;
int EEStore::eeAddress=0;

View File

@@ -1,45 +1,22 @@
/*
* © 2021 Neil McKechnie
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020 Chris Harlow
* All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef DISABLE_EEPROM
#ifndef EEStore_h
#define EEStore_h
#include <Arduino.h>
#if defined(ARDUINO_ARCH_SAMC)
#if defined(ARDUINO_ARCH_SAMD)
#include <SparkFun_External_EEPROM.h>
extern ExternalEEPROM EEPROM;
#else
#include <EEPROM.h>
#endif
#define EESTORE_ID "DCC++1"
#define EESTORE_ID "DCC++"
struct EEStoreData{
char id[sizeof(EESTORE_ID)];
uint16_t nTurnouts;
uint16_t nSensors;
uint16_t nOutputs;
int nTurnouts;
int nSensors;
int nOutputs;
};
struct EEStore{
@@ -56,4 +33,3 @@ struct EEStore{
};
#endif
#endif // DISABLE_EEPROM

View File

@@ -1,61 +0,0 @@
/*
* © 2022 Harald Barth
* 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/>.
*/
#ifdef ARDUINO_ARCH_ESP32
#include <Arduino.h>
#include "ESP32-fixes.h"
#include "esp32-hal.h"
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
static int cnt_channel = LEDC_CHANNELS;
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] != 0) {
ledcSetup(pin_to_channel[pin], frequency, 8);
}
}
}
void DCCEXanalogWrite(uint8_t pin, int value) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
#endif

View File

@@ -1,23 +0,0 @@
#ifndef EXRAIL_H
#define EXRAIL_H
#if defined(EXRAIL_ACTIVE)
#include "EXRAIL2.h"
class RMFT {
public:
static void inline begin() {RMFT2::begin();}
static void inline loop() {RMFT2::loop();}
};
#include "EXRAILMacros.h"
#else
// Dummy RMFT
class RMFT {
public:
static void inline begin() {}
static void inline loop() {}
};
#endif
#endif

File diff suppressed because it is too large Load Diff

207
EXRAIL2.h
View File

@@ -1,207 +0,0 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2023 Harald Barth
* 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 EXRAIL2_H
#define EXRAIL2_H
#include "FSH.h"
#include "IODevice.h"
#include "Turnouts.h"
// The following are the operation codes (or instructions) for a kind of virtual machine.
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
// In cases where more than one parameter is required, the first parameter is followed by one
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
// searching easier as a parameter can never be confused with an opcode.
//
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
OPCODE_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
OPCODE_ENDIF,OPCODE_ELSE,
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
OPCODE_PRINT,OPCODE_DCCACTIVATE,
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
OPCODE_ROSTER,OPCODE_KILLALL,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
// see skipIfBlock()
IF_TYPE_OPCODES, // do not move this...
OPCODE_IFRED,OPCODE_IFAMBER,OPCODE_IFGREEN,
OPCODE_IFGTE,OPCODE_IFLT,
OPCODE_IFTIMEOUT,
OPCODE_IF,OPCODE_IFNOT,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
};
// Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd.
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcn,
thrunge_lcd, // Must be last!!
};
// Flag bits for status of hardware and TPL
static const byte SECTION_FLAG = 0x80;
static const byte LATCH_FLAG = 0x40;
static const byte TASK_FLAG = 0x20;
static const byte SPARE_FLAG = 0x10;
static const byte SIGNAL_MASK = 0x0C;
static const byte SIGNAL_RED = 0x08;
static const byte SIGNAL_AMBER = 0x0C;
static const byte SIGNAL_GREEN = 0x04;
static const byte MAX_STACK_DEPTH=4;
static const short MAX_FLAGS=256;
#define FLAGOVERFLOW(x) x>=MAX_FLAGS
class LookList {
public:
LookList(int16_t size);
void add(int16_t lookup, int16_t result);
int16_t find(int16_t value);
private:
int16_t m_size;
int16_t m_loaded;
int16_t * m_lookupArray;
int16_t * m_resultArray;
};
class RMFT2 {
public:
static void begin();
static void loop();
RMFT2(int progCounter);
RMFT2(int route, uint16_t cab);
~RMFT2();
static void readLocoCallback(int16_t cv);
static void createNewTask(int route, uint16_t cab);
static void turnoutEvent(int16_t id, bool closed);
static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change);
static void clockEvent(int16_t clocktime, bool change);
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
static const int16_t SIGNAL_ID_MASK=0x0FFF;
// Throttle Info Access functions built by exrail macros
static const byte rosterNameCount;
static const int16_t HIGHFLASH routeIdList[];
static const int16_t HIGHFLASH automationIdList[];
static const int16_t HIGHFLASH rosterIdList[];
static const FSH * getRouteDescription(int16_t id);
static char getRouteType(int16_t id);
static const FSH * getTurnoutDescription(int16_t id);
static const FSH * getRosterName(int16_t id);
static const FSH * getRosterFunctions(int16_t id);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
static void streamFlags(Print* stream);
static bool setFlag(VPIN id,byte onMask, byte OffMask=0);
static bool getFlag(VPIN id,byte mask);
static int16_t progtrackLocoId;
static void doSignal(int16_t id,char rag);
static bool isSignal(int16_t id,char rag);
static int16_t getSignalSlot(int16_t id);
static void setTurnoutHiddenState(Turnout * t);
static LookList* LookListLoader(OPCODE op1,
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
void driveLoco(byte speedo);
bool readSensor(uint16_t sensorId);
bool skipIfBlock();
bool readLoco();
void loop2();
void kill(const FSH * reason=NULL,int operand=0);
void printMessage(uint16_t id); // Built by RMFTMacros.h
void printMessage2(const FSH * msg);
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
uint16_t getOperand(byte n);
static bool diag;
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static LookList * sequenceLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
static LookList * onActivateLookup;
static LookList * onDeactivateLookup;
static LookList * onRedLookup;
static LookList * onAmberLookup;
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain
int progCounter; // Byte offset of next route opcode in ROUTES table
unsigned long delayStart; // Used by opcodes that must be recalled before completing
unsigned long delayTime;
union {
unsigned long waitAfter; // Used by OPCODE_AFTER
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
};
bool timeoutFlag;
byte taskId;
uint16_t loco;
bool forward;
bool invert;
byte speedo;
int onEventStartPosition;
byte stackDepth;
int callStack[MAX_STACK_DEPTH];
};
#endif

View File

@@ -1,264 +0,0 @@
/*
* © 2020-2022 Chris Harlow. All rights reserved.
* © 2023 Harald Barth
*
* 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/>.
*/
// This file cleans and resets the RMFT2 Macros.
// It is used between passes to reduce complexity in RMFT2Macros.h
// DO NOT add an include guard to this file.
// Undefine all RMFT macros
#undef ACTIVATE
#undef ACTIVATEL
#undef AFTER
#undef ALIAS
#undef AMBER
#undef ANOUT
#undef AT
#undef ATGTE
#undef ATLT
#undef ATTIMEOUT
#undef AUTOMATION
#undef AUTOSTART
#undef BROADCAST
#undef CALL
#undef CLOSE
#undef DCC_SIGNAL
#undef DEACTIVATE
#undef DEACTIVATEL
#undef DELAY
#undef DELAYMINS
#undef DELAYRANDOM
#undef DONE
#undef DRIVE
#undef ELSE
#undef ENDEXRAIL
#undef ENDIF
#undef ENDTASK
#undef ESTOP
#undef EXRAIL
#undef FADE
#undef FOFF
#undef FOLLOW
#undef FON
#undef FORGET
#undef FREE
#undef FWD
#undef GREEN
#undef HAL
#undef IF
#undef IFAMBER
#undef IFCLOSED
#undef IFGREEN
#undef IFGTE
#undef IFLOCO
#undef IFLT
#undef IFNOT
#undef IFRANDOM
#undef IFRED
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
#undef KILLALL
#undef LATCH
#undef LCD
#undef SCREEN
#undef LCN
#undef MOVETT
#undef ONACTIVATE
#undef ONACTIVATEL
#undef ONAMBER
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONGREEN
#undef ONRED
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
#undef PAUSE
#undef PIN_TURNOUT
#undef PRINT
#undef POM
#undef POWEROFF
#undef POWERON
#undef READ_LOCO
#undef RED
#undef RESERVE
#undef RESET
#undef RESUME
#undef RETURN
#undef REV
#undef ROSTER
#undef ROUTE
#undef SENDLOCO
#undef SEQUENCE
#undef SERIAL
#undef SERIAL1
#undef SERIAL2
#undef SERIAL3
#undef SERIAL4
#undef SERIAL5
#undef SERIAL6
#undef SERVO
#undef SERVO2
#undef SERVO_TURNOUT
#undef SERVO_SIGNAL
#undef SET
#undef SET_TRACK
#undef SETLOCO
#undef SIGNAL
#undef SIGNALH
#undef SPEED
#undef START
#undef STOP
#undef THROW
#undef TURNOUT
#undef UNJOIN
#undef UNLATCH
#undef VIRTUAL_SIGNAL
#undef VIRTUAL_TURNOUT
#undef WAITFOR
#undef XFOFF
#undef XFON
#ifndef RMFT2_UNDEF_ONLY
#define ACTIVATE(addr,subaddr)
#define ACTIVATEL(addr)
#define AFTER(sensor_id)
#define ALIAS(name,value...)
#define AMBER(signal_id)
#define ANOUT(vpin,value,param1,param2)
#define AT(sensor_id)
#define ATGTE(sensor_id,value)
#define ATLT(sensor_id,value)
#define ATTIMEOUT(sensor_id,timeout_ms)
#define AUTOMATION(id,description)
#define AUTOSTART
#define BROADCAST(msg)
#define CALL(route)
#define CLOSE(id)
#define DCC_SIGNAL(id,add,subaddr)
#define DEACTIVATE(addr,subaddr)
#define DEACTIVATEL(addr)
#define DELAY(mindelay)
#define DELAYMINS(mindelay)
#define DELAYRANDOM(mindelay,maxdelay)
#define DONE
#define DRIVE(analogpin)
#define ELSE
#define ENDEXRAIL
#define ENDIF
#define ENDTASK
#define ESTOP
#define EXRAIL
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
#define FON(func)
#define FORGET
#define FREE(blockid)
#define FWD(speed)
#define GREEN(signal_id)
#define HAL(haltype,params...)
#define IF(sensor_id)
#define IFAMBER(signal_id)
#define IFCLOSED(turnout_id)
#define IFGREEN(signal_id)
#define IFGTE(sensor_id,value)
#define IFLOCO(loco_id)
#define IFLT(sensor_id,value)
#define IFNOT(sensor_id)
#define IFRANDOM(percent)
#define IFRED(signal_id)
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
#define LATCH(sensor_id)
#define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg)
#define MOVETT(id,steps,activity)
#define ONACTIVATE(addr,subaddr)
#define ONACTIVATEL(linear)
#define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
#define PARSE(msg)
#define POM(cv,value)
#define POWEROFF
#define POWERON
#define READ_LOCO
#define RED(signal_id)
#define RESERVE(blockid)
#define RESET(pin)
#define RESUME
#define RETURN
#define REV(speed)
#define ROUTE(id,description)
#define ROSTER(cab,name,funcmap...)
#define SENDLOCO(cab,route)
#define SEQUENCE(id)
#define SERIAL(msg)
#define SERIAL1(msg)
#define SERIAL2(msg)
#define SERIAL3(msg)
#define SERIAL4(msg)
#define SERIAL5(msg)
#define SERIAL6(msg)
#define SERVO(id,position,profile)
#define SERVO2(id,position,duration)
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin)
#define SET_TRACK(track,mode)
#define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed)
#define START(route)
#define STOP
#define THROW(id)
#define TURNOUT(id,addr,subaddr,description...)
#define UNJOIN
#define UNLATCH(sensor_id)
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin)
#define XFOFF(cab,func)
#define XFON(cab,func)
#endif

View File

@@ -1,385 +0,0 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2023 Harald Barth
* 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 EXRAILMacros_H
#define EXRAILMacros_H
// remove normal code LCD & SERIAL macros (will be restored later)
#undef LCD
#undef SERIAL
// This file will include and build the EXRAIL script and associated helper tricks.
// It does this by including myAutomation.h several times, each with a set of macros to
// extract the relevant parts.
// The entire automation script is contained within a byte array RMFT2::RouteCode[]
// made up of opcode and parameter pairs.
// ech opcode is a 1 byte operation plus 2 byte operand.
// The array is normally built using the macros below as this makes it easier
// to manage the cases where:
// - padding must be applied to ensure the correct alignment of the next instruction
// - large parameters must be split up
// - multiple parameters aligned correctly
// - a single macro requires multiple operations
// Descriptive texts for routes and animations are created in a sepaerate function which
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
// PRINT(msg), LCD(row,msg) and SCREEN(display,row,msg) are implemented in a separate pass to create
// a getMessageText(id) function.
// CAUTION: The macros below are multiple passed over myAutomation.h
// helper macro for turnout descriptions, creates NULL for missing description
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
// helper macro for turnout description as HIDDEN
#define HIDDEN "\x01"
// helper macro to strip leading zeros off time inputs
// (10#mins)%100)
#define STRIP_ZERO(value) 10##value%100
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
void exrailHalSetup() {
#include "myAutomation.h"
}
// Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h"
#undef ROUTE
#define ROUTE(id, description) id,
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
0};
// Pass 3 Create route descriptions:
#undef ROUTE
#define ROUTE(id, description) case id: return F(description);
#undef AUTOMATION
#define AUTOMATION(id, description) case id: return F(description);
const FSH * RMFT2::getRouteDescription(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass 4... Create Text sending functions
#include "EXRAIL2MacroReset.h"
const int StringMacroTracker1=__COUNTER__;
#define THRUNGE(msg,mode) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=mode;\
break;\
}
#undef BROADCAST
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
#undef PARSE
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
#undef PRINT
#define PRINT(msg) THRUNGE(msg,thrunge_print)
#undef LCN
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
#undef SERIAL
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
#undef SERIAL1
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
#undef SERIAL2
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
#undef SERIAL3
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
#undef SERIAL4
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
#undef SERIAL5
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
#undef SERIAL6
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
#undef LCD
#define LCD(id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=thrunge_lcd; \
lcdid=id;\
break;\
}
#undef SCREEN
#define SCREEN(display,id,msg) \
case (__COUNTER__ - StringMacroTracker1) : {\
static const char HIGHFLASH thrunge[]=msg;\
strfar=(uint32_t)GETFARPTR(thrunge);\
tmode=(thrunger)(thrunge_lcd+display); \
lcdid=id;\
break;\
}
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
uint32_t strfar=0;
byte lcdid=0;
switch(id) {
#include "myAutomation.h"
default: break ;
}
if (strfar) thrungeString(strfar,tmode,lcdid);
}
// Pass 5: Turnout descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) O_DESC(id,description)
#undef VIRTUAL_TURNOUT
#define VIRTUAL_TURNOUT(id,description...) O_DESC(id,description)
const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
switch (turnoutid) {
#include "myAutomation.h"
default:break;
}
return NULL;
}
// Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) +1
const byte RMFT2::rosterNameCount=0
#include "myAutomation.h"
;
// Pass 6: Roster IDs
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
0};
// Pass 7: Roster names getter
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) case cabid: return F(name);
const FSH * RMFT2::getRosterName(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass to get roster functions
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) case cabid: return F("" funcmap);
const FSH * RMFT2::getRosterFunctions(int16_t id) {
switch(id) {
#include "myAutomation.h"
default: break;
}
return F("");
}
// Pass 8 Signal definitions
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) redpin,redpin,amberpin,greenpin,
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) redpin | RMFT2::ACTIVE_HIGH_SIGNAL_FLAG,redpin,amberpin,greenpin,
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
#undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) id,0,0,0,
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
#include "EXRAIL2MacroReset.h"
// Define internal helper macros.
// Everything we generate here has to be compile-time evaluated to
// a constant.
#define V(val) (byte)(((int16_t)(val))&0x00FF),(byte)(((int16_t)(val)>>8)&0x00FF)
// Define macros for route code creation
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define ALIAS(name,value...)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define AUTOSTART OPCODE_AUTOSTART,0,0,
#define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
#define DCC_SIGNAL(id,add,subaddr)
#define DONE OPCODE_ENDTASK,0,0,
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
#define ELSE OPCODE_ELSE,0,0,
#define ENDEXRAIL
#define ENDIF OPCODE_ENDIF,0,0,
#define ENDTASK OPCODE_ENDTASK,0,0,
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func),
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
#define FON(func) OPCODE_FON,V(func),
#define FORGET OPCODE_FORGET,0,0,
#define FREE(blockid) OPCODE_FREE,V(blockid),
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
#define IFRED(signal_id) OPCODE_IFRED,V(signal_id),
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#define POWEROFF OPCODE_POWEROFF,0,0,
#define POWERON OPCODE_POWERON,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
#define PARSE(msg) PRINT(msg)
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
#define RED(signal_id) OPCODE_RED,V(signal_id),
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
#define RESET(pin) OPCODE_RESET,V(pin),
#define RESUME OPCODE_RESUME,0,0,
#define RETURN OPCODE_RETURN,0,0,
#define REV(speed) OPCODE_REV,V(speed),
#define ROSTER(cabid,name,funcmap...)
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define SERIAL(msg) PRINT(msg)
#define SERIAL1(msg) PRINT(msg)
#define SERIAL2(msg) PRINT(msg)
#define SERIAL3(msg) PRINT(msg)
#define SERIAL4(msg) PRINT(msg)
#define SERIAL5(msg) PRINT(msg)
#define SERIAL6(msg) PRINT(msg)
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define SET(pin) OPCODE_SET,V(pin),
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define UNJOIN OPCODE_UNJOIN,0,0,
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
// Build RouteCode
const int StringMacroTracker2=__COUNTER__;
const HIGHFLASH byte RMFT2::RouteCode[] = {
#include "myAutomation.h"
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
// Restore normal code LCD & SERIAL macro
#undef LCD
#define LCD StringFormatter::lcd
#undef SCREEN
#define SCREEN StringFormatter::lcd2
#undef SERIAL
#define SERIAL 0x0
#endif

View File

@@ -1,10 +1,5 @@
/*
* © 2022 Bruno Sanches
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX/CommandStation-EX
*
@@ -22,13 +17,10 @@
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "defines.h"
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#include "DIAG.h"
#include "CommandDistributor.h"
#include "WiThrottle.h"
#include "DCCTimer.h"
EthernetInterface * EthernetInterface::singleton=NULL;
/**
@@ -37,13 +29,8 @@ EthernetInterface * EthernetInterface::singleton=NULL;
*/
void EthernetInterface::setup()
{
if (singleton!=NULL) {
DIAG(F("Prog Error!"));
return;
}
if ((singleton=new EthernetInterface()))
return;
DIAG(F("Ethernet not initialized"));
singleton=new EthernetInterface();
if (!singleton->connected) singleton=NULL;
};
@@ -55,46 +42,37 @@ void EthernetInterface::setup()
*/
EthernetInterface::EthernetInterface()
{
byte mac[6];
DCCTimer::getSimulatedMacAddress(mac);
connected=false;
byte mac[]=MAC_ADDRESS;
DIAG(F("\n+++++ Ethernet Setup "));
connected=false;
#ifdef IP_ADDRESS
Ethernet.begin(mac, IP_ADDRESS);
#else
if (Ethernet.begin(mac) == 0)
{
DIAG(F("Ethernet.begin FAILED"));
DIAG(F("begin FAILED\n"));
return;
}
#endif
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("Ethernet shield not found or W5100"));
DIAG(F("begin OK."));
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
DIAG(F("shield not found\n"));
return;
}
unsigned long startmilli = millis();
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
if (Ethernet.linkStatus() == LinkON)
break;
DIAG(F("Ethernet waiting for link (1sec) "));
delay(1000);
if (Ethernet.linkStatus() == LinkOFF) {
DIAG(F("cable not connected\n"));
return;
}
// now we either do have link of we have a W5100
// where we do not know if we have link. That's
// the reason to now run checkLink.
// CheckLinks sets up outboundRing if it does
// not exist yet as well.
checkLink();
}
connected=true;
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
/**
* @brief Cleanup any resources
*
* @return none
*/
EthernetInterface::~EthernetInterface() {
delete server;
delete outboundRing;
server = new EthernetServer(LISTEN_PORT); // Ethernet Server listening on default port LISTEN_PORT
server->begin();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), LISTEN_PORT);
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
/**
@@ -103,80 +81,40 @@ EthernetInterface::~EthernetInterface() {
*/
void EthernetInterface::loop()
{
if (!singleton || (!singleton->checkLink()))
return;
if (!singleton) return;
switch (Ethernet.maintain()) {
switch (Ethernet.maintain())
{
case 1:
//renewed fail
DIAG(F("Ethernet Error: renewed fail"));
DIAG(F("\nEthernet Error: renewed fail\n"));
singleton=NULL;
return;
case 3:
//rebind fail
DIAG(F("Ethernet Error: rebind fail"));
DIAG(F("Ethernet Error: rebind fail\n"));
singleton=NULL;
return;
default:
//nothing happened
break;
}
singleton->loop2();
}
/**
* @brief Checks ethernet link cable status and detects when it connects / disconnects
*
* @return true when cable is connected, false otherwise
*/
bool EthernetInterface::checkLink() {
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
//if we are not connected yet, setup a new server
if(!connected) {
DIAG(F("Ethernet cable connected"));
connected=true;
#ifdef IP_ADDRESS
setLocalIP(IP_ADDRESS); // for static IP, set it again
#endif
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
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);
// only create a outboundRing it none exists, this may happen if the cable
// gets disconnected and connected again
if(!outboundRing)
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
}
return true;
} else { // connected
DIAG(F("Ethernet cable disconnected"));
connected=false;
//clean up any client
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
if(clients[socket].connected())
clients[socket].stop();
}
// tear down server
delete server;
server = nullptr;
LCD(4,F("IP: None"));
}
return false;
}
void EthernetInterface::loop2() {
if (!outboundRing) { // no idea to call loop2() if we can't handle outgoing data in it
if (Diag::ETHERNET) DIAG(F("No outboundRing"));
return;
}
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 "));
if (Diag::ETHERNET) DIAG(F("\nEthernet: New client "));
byte socket;
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
{
@@ -184,12 +122,12 @@ void EthernetInterface::loop2() {
{
// 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);
if (Diag::ETHERNET) DIAG(F("%d\n"),socket);
clients[socket] = client;
break;
}
}
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW"));
if (socket==MAX_SOCK_NUM) DIAG(F("new Ethernet OVERFLOW\n"));
}
// check for incoming data from all possible clients
@@ -199,13 +137,15 @@ void EthernetInterface::loop2() {
int available=clients[socket].available();
if (available > 0) {
if (Diag::ETHERNET) DIAG(F("Ethernet: available socket=%d,avail=%d"), socket, available);
if (Diag::ETHERNET) DIAG(F("\nEthernet: available socket=%d,avail=%d,count="), 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);
if (Diag::ETHERNET) DIAG(F("%d:%e\n"), 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.
}
}
@@ -215,22 +155,19 @@ void EthernetInterface::loop2() {
for (int socket = 0; socket<MAX_SOCK_NUM; socket++) {
if (clients[socket] && !clients[socket].connected()) {
clients[socket].stop();
CommandDistributor::forget(socket);
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
if (Diag::ETHERNET) DIAG(F("\nEthernet: disconnect %d \n"), socket);
}
}
WiThrottle::loop(outboundRing);
// handle at most 1 outbound transmission
int socketOut=outboundRing->read();
if (socketOut >= MAX_SOCK_NUM) {
DIAG(F("Ethernet outboundRing socket=%d error"), socketOut);
} else if (socketOut >= 0) {
if (socketOut>=0) {
int count=outboundRing->count();
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d"), socketOut,count);
if (Diag::ETHERNET) DIAG(F("Ethernet reply socket=%d, count=:%d\n"), socketOut,count);
for(;count>0;count--) clients[socketOut].write(outboundRing->read());
clients[socketOut].flush(); //maybe
}
}
}
#endif

View File

@@ -1,10 +1,5 @@
/*
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* All rights reserved.
* © 2020,Gregor Baues, Chris Harlow. All rights reserved.
*
* This file is part of DCC-EX/CommandStation-EX
*
@@ -28,23 +23,32 @@
#ifndef EthernetInterface_h
#define EthernetInterface_h
#include "defines.h"
#include "DCCEXParser.h"
#include "MemStream.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 <avr/pgmspace.h>
#include <Ethernet.h>
#include "RingStream.h"
/* 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 Network Configuration
*
*/
#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
// this one is not used elsewhere and corresponds to your network layout
#define LISTEN_PORT 2560 // default listen port for the server
#define MAX_ETH_BUFFER 512
#define OUTBOUND_RING_SIZE 2048
@@ -56,16 +60,15 @@ class EthernetInterface {
static void loop();
private:
static EthernetInterface * singleton;
bool connected;
EthernetInterface();
~EthernetInterface();
void loop2();
bool checkLink();
EthernetServer * server = NULL;
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 = NULL;
RingStream * outboundRing;
};
#endif

93
FSH.h
View File

@@ -1,93 +0,0 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* © 2021 Fred Decker
* 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 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.
* pgm_read_word_near use GETFLASHW instead.
*
* Also:
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
* GETHIGHFLASH,GETHIGHFLASHW to access them
*
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_AVR
// AVR devices have flash memory mapped differently
// progmem can be accessed by _near functions or _far
typedef __FlashStringHelper FSH;
#define FLASH PROGMEM
#define GETFLASH(addr) pgm_read_byte_near(addr)
#define STRCPY_P strcpy_P
#define STRCMP_P strcmp_P
#define STRNCPY_P strncpy_P
#define STRNCMP_P strncmp_P
#define STRLEN_P strlen_P
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
// AVR_MEGA memory deliberately placed at end of link may need _far functions
#define HIGHFLASH __attribute__((section(".fini2")))
#define GETFARPTR(data) pgm_get_far_address(data)
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
#else
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
// as there is no progmem above 32kb anyway.
#define HIGHFLASH PROGMEM
#define GETFARPTR(data) ((uint32_t)(data))
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
#endif
#else
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
#ifdef F
#undef F
#endif
#ifdef FLASH
#undef FLASH
#endif
#define F(str) (str)
typedef char FSH;
#define FLASH
#define HIGHFLASH
#define GETFARPTR(data) ((uint32_t)(data))
#define GETFLASH(addr) (*(const byte *)(addr))
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
#define STRCPY_P strcpy
#define STRCMP_P strcmp
#define STRNCPY_P strncpy
#define STRNCMP_P strncmp
#define STRLEN_P strlen
#endif
#endif

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202302121935Z"
#define GITHUB_SHA "9db6d36"

View File

@@ -1,361 +0,0 @@
/*
* © 2023, Neil McKechnie
* © 2022 Paul M Antoine
* 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 "I2CManager.h"
#include "DIAG.h"
// Include target-specific portions of I2CManager class
#if defined(I2C_USE_WIRE)
#include "I2CManager_Wire.h"
#elif defined(ARDUINO_ARCH_AVR)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_AVR.h" // Uno/Nano/Mega2560
#elif defined(ARDUINO_ARCH_MEGAAVR)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_Mega4809.h" // NanoEvery/UnoWifi
#elif defined(ARDUINO_ARCH_SAMD)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later
#elif defined(ARDUINO_ARCH_STM32)
#include "I2CManager_NonBlocking.h"
#include "I2CManager_STM32.h" // STM32F411RE for now... more later
#else
#define I2C_USE_WIRE
#include "I2CManager_Wire.h" // Other platforms
#endif
// Helper function for listing device types
static const FSH * guessI2CDeviceType(uint8_t address) {
if (address >= 0x20 && address <= 0x26)
return F("GPIO Expander");
else if (address == 0x27)
return F("GPIO Expander or LCD Display");
else if (address == 0x29)
return F("Time-of-flight sensor");
else if (address >= 0x3c && address <= 0x3d)
return F("OLED Display");
else if (address >= 0x48 && address <= 0x4f)
return F("Analogue Inputs or PWM");
else if (address >= 0x40 && address <= 0x4f)
return F("PWM");
else if (address >= 0x50 && address <= 0x5f)
return F("EEPROM");
else if (address == 0x68)
return F("Real-time clock");
else if (address >= 0x70 && address <= 0x77)
return F("I2C Mux");
else
return F("?");
}
// If not already initialised, initialise I2C
void I2CManagerClass::begin(void) {
if (!_beginCompleted) {
_beginCompleted = true;
_initialise();
#if defined(I2C_USE_WIRE)
DIAG(F("I2CManager: Using Wire library"));
#endif
// Check for short-circuits on I2C
if (!digitalRead(SDA))
DIAG(F("WARNING: Possible short-circuit on I2C SDA line"));
if (!digitalRead(SCL))
DIAG(F("WARNING: Possible short-circuit on I2C SCL line"));
// Probe and list devices. Use standard mode
// (clock speed 100kHz) for best device compatibility.
_setClock(100000);
unsigned long originalTimeout = _timeout;
setTimeout(1000); // use 1ms timeout for probes
#if defined(I2C_EXTENDED_ADDRESS)
// First count the multiplexers and switch off all subbuses
_muxCount = 0;
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK)
_muxCount++;
}
#endif
// Enumerate devices that are visible
bool found = false;
for (uint8_t addr=0x08; addr<0x78; addr++) {
if (exists(addr)) {
found = true;
DIAG(F("I2C Device found at 0x%x, %S?"), addr, guessI2CDeviceType(addr));
}
}
#if defined(I2C_EXTENDED_ADDRESS)
// Enumerate all I2C devices that are connected via multiplexer,
// i.e. that respond when only one multiplexer has one subBus enabled
// and the device doesn't respond when the mux subBus is disabled.
// If any probes time out, then assume that the subbus is dead and
// don't do any more on that subbus.
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo;
if (exists(muxAddr)) {
// Select Mux Subbus
for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) {
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
for (uint8_t addr=0x08; addr<0x78; addr++) {
uint8_t status = checkAddress(addr);
if (status == I2C_STATUS_OK) {
// De-select subbus
muxSelectSubBus({(I2CMux)muxNo, SubBus_None});
if (!exists(addr)) {
// Device responds when subbus selected but not when
// subbus disabled - ergo it must be on subbus!
found = true;
DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,0x%x}, %S?"),
muxNo, subBus, addr, guessI2CDeviceType(addr));
}
// Re-select subbus
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
} else if (status == I2C_STATUS_TIMEOUT) {
// Bus stuck, skip to next one.
break;
}
}
}
// Deselect all subBuses for this mux. Otherwise its devices will continue to
// respond when other muxes are being probed.
I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux
}
}
#endif
if (!found) DIAG(F("No I2C Devices found"));
_setClock(_clockSpeed);
setTimeout(originalTimeout); // set timeout back to original
}
}
// 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;
DIAG(F("I2C clock speed set to %l Hz"), _clockSpeed);
}
_setClock(_clockSpeed);
}
// Force clock speed to that specified.
void I2CManagerClass::forceClock(uint32_t speed) {
_clockSpeed = speed;
_clockSpeedFixed = true;
_setClock(_clockSpeed);
DIAG(F("I2C clock speed forced to %l Hz"), _clockSpeed);
}
// Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code.
// Suppress retries. If it doesn't respond first time it's out of the running.
uint8_t I2CManagerClass::checkAddress(I2CAddress address) {
I2CRB rb;
rb.setWriteParams(address, NULL, 0);
rb.suppressRetries(true);
queueRequest(&rb);
return rb.wait();
}
/***************************************************************************
* Write a transmission to I2C using a list of data (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::write(I2CAddress address, uint8_t 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);
}
/***************************************************************************
* Initiate a write to an I2C device (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) {
I2CRB req;
uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req);
return finishRB(&req, status);
}
/***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * data, uint8_t dataLen) {
I2CRB req;
uint8_t status = write_P(i2cAddress, data, dataLen, &req);
return finishRB(&req, status);
}
/***************************************************************************
* Initiate a write (optional) followed by a read from the I2C device (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen)
{
I2CRB req;
uint8_t status = read(i2cAddress, readBuffer, readLen, writeBuffer, writeLen, &req);
return finishRB(&req, status);
}
/***************************************************************************
* Overload of read() to allow command to be specified as a series of bytes (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::read(I2CAddress 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);
}
/***************************************************************************
* Finish off request block by posting status, etc. (blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::finishRB(I2CRB *rb, uint8_t status) {
if ((status == I2C_STATUS_OK) && rb)
status = rb->wait();
return status;
}
/***************************************************************************
* Get a message corresponding to the error status
***************************************************************************/
const FSH *I2CManagerClass::getErrorMessage(uint8_t status) {
switch (status) {
case I2C_STATUS_OK: return F("OK");
case I2C_STATUS_TRUNCATED: return F("Transmission truncated");
case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)");
case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)");
case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error");
case I2C_STATUS_TIMEOUT: return F("I2C bus timeout");
case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost");
case I2C_STATUS_BUS_ERROR: return F("I2C bus error");
case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error");
case I2C_STATUS_PENDING: return F("Request pending");
default: return F("Error code not recognised");
}
}
/***************************************************************************
* Declare singleton class instance.
***************************************************************************/
I2CManagerClass I2CManager = I2CManagerClass();
// Buffer for conversion of I2CAddress to char*.
/* static */ char I2CAddress::addressBuffer[30];
/////////////////////////////////////////////////////////////////////////////
// Helper functions associated with I2C Request Block
/////////////////////////////////////////////////////////////////////////////
/***************************************************************************
* Block waiting for request to complete, and return completion status.
* Timeout monitoring is performed in the I2CManager.loop() function.
***************************************************************************/
uint8_t I2CRB::wait() {
while (status==I2C_STATUS_PENDING) {
I2CManager.loop();
};
return status;
}
/***************************************************************************
* Check whether request is still in progress.
* Timeout monitoring is performed in the I2CManager.loop() function.
***************************************************************************/
bool I2CRB::isBusy() {
if (status==I2C_STATUS_PENDING) {
I2CManager.loop();
return true;
} else
return false;
}
/***************************************************************************
* Helper functions to fill the I2CRequest structure with parameters.
***************************************************************************/
void I2CRB::setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen) {
this->i2cAddress = i2cAddress;
this->writeLen = 0;
this->readBuffer = readBuffer;
this->readLen = readLen;
this->operation = OPERATION_READ;
this->status = I2C_STATUS_OK;
}
void I2CRB::setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen) {
this->i2cAddress = i2cAddress;
this->writeBuffer = writeBuffer;
this->writeLen = writeLen;
this->readBuffer = readBuffer;
this->readLen = readLen;
this->operation = OPERATION_REQUEST;
this->status = I2C_STATUS_OK;
}
void I2CRB::setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) {
this->i2cAddress = i2cAddress;
this->writeBuffer = writeBuffer;
this->writeLen = writeLen;
this->readLen = 0;
this->operation = OPERATION_SEND;
this->status = I2C_STATUS_OK;
}
void I2CRB::suppressRetries(bool suppress) {
if (suppress)
this->operation |= OPERATION_NORETRY;
else
this->operation &= ~OPERATION_NORETRY;
}
// Helper function for converting a uint8_t to four characters (e.g. 0x23).
void I2CAddress::toHex(const uint8_t value, char *buffer) {
char *ptr = buffer;
// Just display hex value, two digits.
*ptr++ = '0';
*ptr++ = 'x';
uint8_t bits = (value >> 4) & 0xf;
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
bits = value & 0xf;
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
}
#if !defined(I2C_EXTENDED_ADDRESS)
/* static */ bool I2CAddress::_addressWarningDone = false;
#endif

View File

@@ -1,580 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
* © 2022 Paul M Antoine
*
* 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 <inttypes.h>
#include "FSH.h"
#include "defines.h"
#include "DIAG.h"
/*
* Manager for I2C communications. For portability, it allows use
* of the Wire class, but also has a native implementation for AVR
* which supports non-blocking queued I/O requests.
*
* 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.
*
* Non-blocking requests are issued by creating an I2C Request Block
* (I2CRB) which is then added to the I2C manager's queue. The
* application refers to this block to check for completion of the
* operation, and for reading completion status.
*
* Examples:
* I2CRB rb;
* uint8_t status = I2CManager.write(address, buffer, sizeof(buffer), &rb);
* ...
* if (!rb.isBusy()) {
* status = rb.status;
* // Repeat write
* I2CManager.queueRequest(&rb);
* ...
* status = rb.wait(); // Wait for completion and read status
* }
* ...
* I2CRB rb2;
* outbuffer[0] = 12; // Register number in I2C device to be read
* rb2.setRequestParams(address, inBuffer, 1, outBuffer, 1);
* status = I2CManager.queueRequest(&rb2);
* if (status == I2C_STATUS_OK) {
* status = rb2.wait();
* if (status == I2C_STATUS_OK) {
* registerValue = inBuffer[0];
* }
* }
* ...
*
* Synchronous (blocking) calls are also possible, e.g.
* status = I2CManager.write(address, buffer, sizeof(buffer));
*
* When using non-blocking requests, neither the I2CRB nor the input or output
* buffers should be modified until the I2CRB is complete (not busy).
*
* Timeout monitoring is possible, but requires that the following call is made
* reasonably frequently in the program's loop() function:
* I2CManager.loop();
* So that the application doesn't need to do this explicitly, this call is performed
* from the I2CRB::isBusy() or I2CRB::wait() functions.
*
*/
/*
* Future enhancement possibility:
*
* I2C Multiplexer (e.g. TCA9547, TCA9548)
*
* A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27
* to are limited to 8 on a bus. By adding a multiplexer, the limit becomes 8 for each of the multiplexer's 8 sub-buses, i.e. 64.
* And a single I2C bus can have up to 8 multiplexers, giving up to 64 sub-buses and, in theory, up to 512 I/O extenders; that's
* as many as 8192 input/output pins!
* Secondly, the capacitance of the bus is an electrical limiting factor of the length of the bus, speed and number of devices.
* The multiplexer isolates each sub-bus from the others, and so reduces the capacitance of the bus. For example, with one
* multiplexer and 64 GPIO extenders, only 9 devices are connected to the bus at any time (multiplexer plus 8 extenders).
* Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be
* put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions.
*
* Changes required: Increase the size of the I2CAddress field in the IODevice class from uint8_t to uint16_t.
* The most significant byte would contain a '1' bit flag, the multiplexer number (0-7) and bus number (0-7). Then, when performing
* an I2C operation, the I2CManager would check this byte and, if zero, do what it currently does. If the byte is non-zero, then
* that means the device is connected via a multiplexer so the I2C transaction should be preceded by a select command issued to the
* relevant multiplexer.
*
* Non-interrupting I2C:
*
* Non-blocking I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state
* machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function.
* The speed at which I2C operations can be performed then becomes highly dependent on the frequency that
* the loop() function is called, and may be adequate under some circumstances.
* The advantage of NOT using interrupts is that the impact of I2C upon the DCC waveform (when accurate timing mode isn't in use)
* becomes almost zero.
*
*/
// Maximum number of retries on an I2C operation.
// A value of zero will disable retries.
// Maximum value is 254 (unsigned byte counter)
// Note that timeout failures are not retried, but any timeout
// configured applies to each try separately.
#define MAX_I2C_RETRIES 2
// Add following line to config.h to enable Wire library instead of native I2C drivers
//#define I2C_USE_WIRE
// Add following line to config.h to disable the use of interrupts by the native I2C drivers.
//#define I2C_NO_INTERRUPTS
// Default to use interrupts within the native I2C drivers.
#ifndef I2C_NO_INTERRUPTS
#define I2C_USE_INTERRUPTS
#endif
// I2C Extended Address support I2C Multiplexers and allows various properties to be
// associated with an I2C address such as the MUX and SubBus. In the future, this
// may be extended to include multiple buses, and other features.
// Uncomment to enable extended address.
//
// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc,
// it should be cast to (int) to ensure that the address value is passed rather than
// the struct.
//#define I2C_EXTENDED_ADDRESS
/////////////////////////////////////////////////////////////////////////////////////
// Extended I2C Address type to facilitate extended I2C addresses including
// I2C multiplexer support.
/////////////////////////////////////////////////////////////////////////////////////
// Currently only one bus supported, and one instance of I2CManager to handle it.
enum I2CBus : uint8_t {
I2CBus_0 = 0,
};
// Currently I2CAddress supports one I2C bus, with up to eight
// multipexers (MUX) attached. Each MUX can have up to eight sub-buses.
enum I2CMux : uint8_t {
I2CMux_0 = 0,
I2CMux_1 = 1,
I2CMux_2 = 2,
I2CMux_3 = 3,
I2CMux_4 = 4,
I2CMux_5 = 5,
I2CMux_6 = 6,
I2CMux_7 = 7,
I2CMux_None = 255, // Address doesn't need mux switching
};
enum I2CSubBus : uint8_t {
SubBus_0 = 0, // Enable individual sub-buses...
SubBus_1 = 1,
#if !defined(I2CMUX_PCA9542)
SubBus_2 = 2,
SubBus_3 = 3,
#if !defined(I2CMUX_PCA9544)
SubBus_4 = 4,
SubBus_5 = 5,
SubBus_6 = 6,
SubBus_7 = 7,
#endif
#endif
SubBus_No, // Number of subbuses (highest + 1)
SubBus_None = 254, // Disable all sub-buses on selected mux
SubBus_All = 255, // Enable all sub-buses
};
// Type to hold I2C address
#if defined(I2C_EXTENDED_ADDRESS)
// First MUX address (they range between 0x70-0x77).
#define I2C_MUX_BASE_ADDRESS 0x70
// Currently I2C address supports one I2C bus, with up to eight
// multiplexers (MUX) attached. Each MUX can have up to eight sub-buses.
// This structure could be extended in the future (if there is a need)
// to support 10-bit I2C addresses, different I2C clock speed for each
// sub-bus, multiple I2C buses, and other features not yet thought of.
struct I2CAddress {
private:
// Fields
I2CBus _busNumber;
I2CMux _muxNumber;
I2CSubBus _subBus;
uint8_t _deviceAddress;
static char addressBuffer[];
public:
// Constructors
// For I2CAddress "{I2CBus_0, Mux_0, SubBus_0, 0x23}" syntax.
I2CAddress(const I2CBus busNumber, const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) {
_busNumber = busNumber;
_muxNumber = muxNumber;
_subBus = subBus;
_deviceAddress = deviceAddress;
}
// Basic constructor
I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {}
// For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax.
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) :
I2CAddress(I2CBus_0, muxNumber, subBus, deviceAddress) {}
// For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70)
I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) :
I2CAddress(I2CMux_0, subBus, deviceAddress) {}
// Conversion from uint8_t to I2CAddress
// For I2CAddress in form "0x23"
// (device assumed to be on the main I2C bus).
I2CAddress(const uint8_t deviceAddress) :
I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {}
// Conversion from uint8_t to I2CAddress
// For I2CAddress in form "{I2CBus_1, 0x23}"
// (device not connected via multiplexer).
I2CAddress(const I2CBus bus, const uint8_t deviceAddress) :
I2CAddress(bus, I2CMux_None, SubBus_None, deviceAddress) {}
// For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector)
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) :
I2CAddress(muxNumber, subBus, 0x00) {}
// For I2CAddress in form "{i2cAddress, deviceAddress}"
// where deviceAddress is to be on the same subbus as i2cAddress.
I2CAddress(I2CAddress firstAddress, uint8_t newDeviceAddress) :
I2CAddress(firstAddress._muxNumber, firstAddress._subBus, newDeviceAddress) {}
// Conversion operator from I2CAddress to uint8_t
// For "uint8_t address = i2cAddress;" syntax
// (device assumed to be on the main I2C bus or on a currently selected subbus.
operator uint8_t () const { return _deviceAddress; }
// Conversion from I2CAddress to char* (uses static storage so only
// one conversion can be done at a time). So don't call it twice in a
// single DIAG statement for example.
const char* toString() {
char *ptr = addressBuffer;
if (_muxNumber != I2CMux_None) {
strcpy_P(ptr, (const char*)F("{I2CMux_"));
ptr += 8;
*ptr++ = '0' + _muxNumber;
strcpy_P(ptr, (const char*)F(",Subbus_"));
ptr += 8;
if (_subBus == SubBus_None) {
strcpy_P(ptr, (const char*)F("None"));
ptr += 4;
} else if (_subBus == SubBus_All) {
strcpy_P(ptr, (const char*)F("All"));
ptr += 3;
} else
*ptr++ = '0' + _subBus;
*ptr++ = ',';
}
toHex(_deviceAddress, ptr);
ptr += 4;
if (_muxNumber != I2CMux_None)
*ptr++ = '}';
*ptr = 0; // terminate string
return addressBuffer;
}
// Comparison operator
int operator == (I2CAddress &a) const {
if (_deviceAddress != a._deviceAddress)
return false; // Different device address so no match
if (_muxNumber == I2CMux_None || a._muxNumber == I2CMux_None)
return true; // Same device address, one or other on main bus
if (_subBus == SubBus_None || a._subBus == SubBus_None)
return true; // Same device address, one or other on main bus
if (_muxNumber != a._muxNumber)
return false; // Connected to a subbus on a different mux
if (_subBus != a._subBus)
return false; // different subbus
return true; // Same address on same mux and same subbus
}
// Field accessors
I2CMux muxNumber() { return _muxNumber; }
I2CSubBus subBus() { return _subBus; }
uint8_t deviceAddress() { return _deviceAddress; }
private:
// Helper function for converting byte to four-character hex string (e.g. 0x23).
void toHex(const uint8_t value, char *buffer);
};
#else
struct I2CAddress {
private:
uint8_t _deviceAddress;
static char addressBuffer[];
public:
// Constructors
I2CAddress(const uint8_t deviceAddress) {
_deviceAddress = deviceAddress;
}
I2CAddress(I2CMux, I2CSubBus, const uint8_t deviceAddress) {
addressWarning();
_deviceAddress = deviceAddress;
}
I2CAddress(I2CSubBus, const uint8_t deviceAddress) {
addressWarning();
_deviceAddress = deviceAddress;
}
// Basic constructor
I2CAddress() : I2CAddress(0) {}
// Conversion operator from I2CAddress to uint8_t
// For "uint8_t address = i2cAddress;" syntax
operator uint8_t () const { return _deviceAddress; }
// Conversion from I2CAddress to char* (uses static storage so only
// one conversion can be done at a time). So don't call it twice in a
// single DIAG statement for example.
const char* toString () {
char *ptr = addressBuffer;
// Just display hex value, two digits.
toHex(_deviceAddress, ptr);
ptr += 4;
*ptr = 0; // terminate string
return addressBuffer;
}
// Comparison operator
int operator == (I2CAddress &a) const {
if (_deviceAddress != a._deviceAddress)
return false; // Different device address so no match
return true; // Same address on same mux and same subbus
}
private:
// Helper function for converting byte to four-character hex string (e.g. 0x23).
void toHex(const uint8_t value, char *buffer);
void addressWarning() {
if (!_addressWarningDone) {
DIAG(F("WARNIING: Extended I2C address used but not supported in this configuration"));
_addressWarningDone = true;
}
}
static bool _addressWarningDone;
};
#endif // I2C_EXTENDED_ADDRESS
// Status codes for I2CRB structures.
enum : uint8_t {
// Codes used by Wire and by native drivers
I2C_STATUS_OK=0,
I2C_STATUS_TRUNCATED=1,
I2C_STATUS_NEGATIVE_ACKNOWLEDGE=2,
I2C_STATUS_TRANSMIT_ERROR=3,
I2C_STATUS_TIMEOUT=5,
// Code used by Wire only
I2C_STATUS_OTHER_TWI_ERROR=4, // catch-all error
// Codes used by native drivers only
I2C_STATUS_ARBITRATION_LOST=6,
I2C_STATUS_BUS_ERROR=7,
I2C_STATUS_UNEXPECTED_ERROR=8,
I2C_STATUS_PENDING=253,
};
// Status codes for the state machine (not returned to caller).
enum : uint8_t {
I2C_STATE_ACTIVE=253,
I2C_STATE_FREE=254,
I2C_STATE_CLOSING=255,
I2C_STATE_COMPLETED=252,
};
typedef enum : uint8_t
{
OPERATION_READ = 1,
OPERATION_REQUEST = 2,
OPERATION_SEND = 3,
OPERATION_SEND_P = 4,
OPERATION_NORETRY = 0x80, // OR with operation to suppress retries.
OPERATION_MASK = 0x7f, // mask for extracting the operation code
} OperationEnum;
// Default I2C frequency
#ifndef I2C_FREQ
#define I2C_FREQ 400000L
#endif
// Class defining a request context for an I2C operation.
class I2CRB {
public:
volatile uint8_t status; // Completion status, or pending flag (updated from IRC)
volatile uint8_t nBytes; // Number of bytes read (updated from IRC)
inline I2CRB() { status = I2C_STATUS_OK; };
uint8_t wait();
bool isBusy();
void setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen);
void setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen);
void setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen);
void suppressRetries(bool suppress);
uint8_t writeLen;
uint8_t readLen;
uint8_t operation;
I2CAddress i2cAddress;
uint8_t *readBuffer;
const uint8_t *writeBuffer;
#if !defined(I2C_USE_WIRE)
I2CRB *nextRequest; // Used by non-blocking devices for I2CRB queue management.
#endif
};
// I2C Manager
class I2CManagerClass {
public:
// 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);
// setTimeout sets the timout value for I2C transactions (milliseconds).
void setTimeout(unsigned long);
// Check if specified I2C address is responding.
uint8_t checkAddress(I2CAddress address);
inline bool exists(I2CAddress address) {
return checkAddress(address)==I2C_STATUS_OK;
}
// Select/deselect Mux Sub-Bus (if using legacy addresses, just checks address)
// E.g. muxSelectSubBus({I2CMux_0, SubBus_3});
uint8_t muxSelectSubBus(I2CAddress address) {
return checkAddress(address);
}
// Write a complete transmission to I2C from an array in RAM
uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size);
uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
// Write a complete transmission to I2C from an array in Flash
uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size);
uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
// Write a transmission to I2C from a list of bytes.
uint8_t write(I2CAddress address, uint8_t nBytes, ...);
// Write a command from an array in RAM and read response
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0);
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb);
// Write a command from an arbitrary list of bytes and read response
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
uint8_t writeSize, ...);
void queueRequest(I2CRB *req);
// Function to abort long-running operations.
void checkForTimeout();
// Loop method
void loop();
// Expand error codes into text. Note that they are in flash so
// need to be printed using FSH.
static const FSH *getErrorMessage(uint8_t status);
private:
bool _beginCompleted = false;
bool _clockSpeedFixed = false;
uint8_t retryCounter; // Count of retries
// Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD
// and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds
// will not be useful. The speed can be overridden by I2CManager::forceClock().
uint32_t _clockSpeed = I2C_FREQ;
// Default timeout 100ms on I2C request block completion.
// A full 32-byte transmission takes about 8ms at 100kHz,
// so this value allows lots of headroom.
// It can be modified by calling I2CManager.setTimeout() function.
// When retries are enabled, the timeout applies to each
// try, and failure from timeout does not get retried.
// A value of 0 means disable timeout monitoring.
unsigned long _timeout = 100000UL;
// Finish off request block by waiting for completion and posting status.
uint8_t finishRB(I2CRB *rb, uint8_t status);
void _initialise();
void _setClock(unsigned long);
#if defined(I2C_EXTENDED_ADDRESS)
// Count of I2C multiplexers found when initialising. If there is only one
// MUX then the subbus does not need de-selecting after use; however, if there
// are two or more, then the subbus must be deselected to avoid multiple
// sub-bus legs on different multiplexers being accessible simultaneously.
private:
uint8_t _muxCount = 0;
public:
uint8_t getMuxCount() { return _muxCount; }
#endif
#if !defined(I2C_USE_WIRE)
// I2CRB structs are queued on the following two links.
// If there are no requests, both are NULL.
// If there is only one request, then queueHead and queueTail both point to it.
// Otherwise, queueHead is the pointer to the first request in the queue and
// queueTail is the pointer to the last request in the queue.
// Within the queue, each request's nextRequest field points to the
// next request, or NULL.
// Mark volatile as they are updated by IRC and read/written elsewhere.
private:
I2CRB * volatile queueHead = NULL;
I2CRB * volatile queueTail = NULL;
// State is set to I2C_STATE_FREE when the interrupt handler has finished
// the current request and is ready to complete.
uint8_t state = I2C_STATE_FREE;
// CompletionStatus may be set by the interrupt handler at any time but is
// not written to the I2CRB until the state is I2C_STATE_FREE.
uint8_t completionStatus = I2C_STATUS_OK;
uint8_t overallStatus = I2C_STATUS_OK;
I2CRB * currentRequest = NULL;
uint8_t txCount = 0;
uint8_t rxCount = 0;
uint8_t bytesToSend = 0;
uint8_t bytesToReceive = 0;
uint8_t operation = 0;
unsigned long startTime = 0;
uint8_t muxPhase = 0;
uint8_t muxAddress = 0;
uint8_t muxData[1];
uint8_t deviceAddress;
const uint8_t *sendBuffer;
uint8_t *receiveBuffer;
volatile uint32_t pendingClockSpeed = 0;
void startTransaction();
// Low-level hardware manipulation functions.
void I2C_init();
void I2C_setClock(unsigned long i2cClockSpeed);
void I2C_handleInterrupt();
void I2C_sendStart();
void I2C_sendStop();
void I2C_close();
public:
// handleInterrupt needs to be public to be called from the ISR function!
void handleInterrupt();
#endif
};
// Pointer to class instance (Note: if there is more than one bus, each will have
// its own instance of I2CManager, selected by the queueRequest function from
// the I2CBus field within the request block's I2CAddress).
extern I2CManagerClass I2CManager;
#endif

View File

@@ -1,230 +0,0 @@
/*
* © 2023, 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_AVR_H
#define I2CMANAGER_AVR_H
#include <Arduino.h>
#include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
#include <avr/io.h>
#include <avr/interrupt.h>
/****************************************************************************
TWI State codes
****************************************************************************/
// General TWI Master staus codes
#define TWI_START 0x08 // START has been transmitted
#define TWI_REP_START 0x10 // Repeated START has been transmitted
#define TWI_ARB_LOST 0x38 // Arbitration lost
// TWI Master Transmitter staus codes
#define TWI_MTX_ADR_ACK 0x18 // SLA+W has been tramsmitted and ACK received
#define TWI_MTX_ADR_NACK 0x20 // SLA+W has been tramsmitted and NACK received
#define TWI_MTX_DATA_ACK 0x28 // Data byte has been tramsmitted and ACK received
#define TWI_MTX_DATA_NACK 0x30 // Data byte has been tramsmitted and NACK received
// TWI Master Receiver staus codes
#define TWI_MRX_ADR_ACK 0x40 // SLA+R has been tramsmitted and ACK received
#define TWI_MRX_ADR_NACK 0x48 // SLA+R has been tramsmitted and NACK received
#define TWI_MRX_DATA_ACK 0x50 // Data byte has been received and ACK tramsmitted
#define TWI_MRX_DATA_NACK 0x58 // Data byte has been received and NACK tramsmitted
// TWI Miscellaneous status codes
#define TWI_NO_STATE 0xF8 // No relevant state information available
#define TWI_BUS_ERROR 0x00 // Bus error due to an illegal START or STOP condition
#define TWI_TWBR ((F_CPU / I2C_FREQ) - 16) / 2 // TWI Bit rate Register setting.
#if defined(I2C_USE_INTERRUPTS)
#define ENABLE_TWI_INTERRUPT (1<<TWIE)
#else
#define ENABLE_TWI_INTERRUPT 0
#endif
/***************************************************************************
* Set I2C clock speed register.
***************************************************************************/
void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) {
unsigned long temp = ((F_CPU / i2cClockSpeed) - 16) / 2;
for (uint8_t preScaler = 0; preScaler<=3; preScaler++) {
if (temp <= 255) {
TWBR = temp;
TWSR = (TWSR & 0xfc) | preScaler;
return;
} else
temp /= 4;
}
// Set slowest speed ~= 500 bits/sec
TWBR = 255;
TWSR |= 0x03;
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
TWSR = 0;
TWBR = TWI_TWBR; // Set bit rate register (Baudrate). Defined in header file.
TWDR = 0xFF; // Default content = SDA released.
TWCR = (1<<TWINT); // Clear interrupt flag
pinMode(SDA, INPUT_PULLUP);
pinMode(SCL, INPUT_PULLUP);
}
/***************************************************************************
* Initiate a start bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
rxCount = 0;
txCount = 0;
// We may have already triggered a stop bit in the same run as this. To avoid
// clearing that bit before the stop bit has been sent, we can either wait for
// it to complete or we can OR the bit onto the existing bits.
TWCR |= (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO); // Send Stop
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
// disable TWI
TWCR = (1<<TWINT); // clear any interrupt and stop twi.
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler or,
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
if (!(TWCR & (1<<TWINT))) return; // Nothing to do.
uint8_t twsr = TWSR & 0xF8;
// Main I2C interrupt handler, used for the device communications.
// The following variables are used:
// bytesToSend, bytesToReceive (R/W)
// txCount, rxCount (W)
// deviceAddress (R)
// sendBuffer, receiveBuffer (R)
// operation (R)
// state, completionStatus (W)
//
// Cases are ordered so that the most frequently used ones are tested first.
switch (twsr) {
case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received
case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received
if (bytesToSend) { // Send first.
if (operation == OPERATION_SEND_P)
TWDR = GETFLASH(sendBuffer + (txCount++));
else
TWDR = sendBuffer[txCount++];
bytesToSend--;
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
} else if (bytesToReceive) { // All sent, anything to receive?
// Don't need to wait for stop, as the interface won't send the start until
// any in-progress stop condition from previous interrupts has been sent.
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTA); // Send Start
} else {
// Nothing left to send or receive
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATE_COMPLETED;
}
break;
case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted
if (bytesToReceive > 0) {
receiveBuffer[rxCount++] = TWDR;
bytesToReceive--;
}
/* fallthrough */
case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received
if (bytesToReceive <= 1) {
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception
} else {
// send ack
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
}
break;
case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted
if (bytesToReceive > 0) {
receiveBuffer[rxCount++] = TWDR;
bytesToReceive--;
}
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
state = I2C_STATE_COMPLETED;
break;
case TWI_START: // START has been transmitted
case TWI_REP_START: // Repeated START has been transmitted
// Set up address and R/W
if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend))
TWDR = (deviceAddress << 1) | 1; // SLA+R
else
TWDR = (deviceAddress << 1) | 0; // SLA+W
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
break;
case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received
case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received
case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
break;
case TWI_ARB_LOST: // Arbitration lost
// Restart transaction from start.
I2C_sendStart();
break;
case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition
default:
TWDR = 0xff; // Default condition = SDA released
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
completionStatus = I2C_STATUS_TRANSMIT_ERROR;
state = I2C_STATE_COMPLETED;
}
}
#if defined(I2C_USE_INTERRUPTS)
ISR(TWI_vect) {
I2CManager.handleInterrupt();
}
#endif
#endif /* I2CMANAGER_AVR_H */

View File

@@ -1,159 +0,0 @@
/*
* © 2023, 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_MEGA4809_H
#define I2CMANAGER_MEGA4809_H
#include <Arduino.h>
#include "I2CManager.h"
/***************************************************************************
* Set I2C clock speed register.
***************************************************************************/
void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) {
uint16_t t_rise;
if (i2cClockSpeed < 200000)
t_rise = 1000;
else if (i2cClockSpeed < 800000)
t_rise = 300;
else
t_rise = 120;
if (t_rise == 120)
TWI0.CTRLA |= TWI_FMPEN_bm;
else
TWI0.CTRLA &= ~TWI_FMPEN_bm;
uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000
* t_rise / 1000 - 10) / 2;
if (baud > 255) baud = 255; // ~30kHz
TWI0.MBAUD = (uint8_t)baud;
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
pinMode(PIN_WIRE_SDA, INPUT_PULLUP);
pinMode(PIN_WIRE_SCL, INPUT_PULLUP);
PORTMUX.TWISPIROUTEA |= TWI_MUX;
I2C_setClock(I2C_FREQ);
#if defined(I2C_USE_INTERRUPTS)
TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm;
#else
TWI0.MCTRLA = TWI_ENABLE_bm;
#endif
TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
}
/***************************************************************************
* Initiate a start bit for transmission, followed by address and R/W
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
txCount = 0;
rxCount = 0;
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
else
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 0;
}
/***************************************************************************
* Initiate a stop bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C
TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc;
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler.
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
uint8_t currentStatus = TWI0.MSTATUS;
if (currentStatus & TWI_ARBLOST_bm) {
// Arbitration lost, restart
TWI0.MSTATUS = currentStatus; // clear all flags
I2C_sendStart(); // Reinitiate request
} else if (currentStatus & TWI_BUSERR_bm) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
TWI0.MSTATUS = currentStatus; // clear all flags
} else if (currentStatus & TWI_WIF_bm) {
// Master write completed
if (currentStatus & TWI_RXACK_bm) {
// Nacked, send stop.
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) {
// Acked, so send next byte (don't need to use GETFLASH)
TWI0.MDATA = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
TWI0.MADDR = (deviceAddress << 1) | 1;
} else {
// No more data to send/receive. Initiate a STOP condition.
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
state = I2C_STATE_COMPLETED;
}
} else if (currentStatus & TWI_RIF_bm) {
// Master read completed without errors
if (bytesToReceive) {
receiveBuffer[rxCount++] = TWI0.MDATA; // Store received byte
bytesToReceive--;
}
if (bytesToReceive) {
// More bytes to receive, issue ack and start another read
TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc;
} else {
// Transaction finished, issue NACK and STOP.
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
state = I2C_STATE_COMPLETED;
}
}
}
/***************************************************************************
* Interrupt handler.
***************************************************************************/
ISR(TWI0_TWIM_vect) {
I2CManager.handleInterrupt();
}
#endif

View File

@@ -1,382 +0,0 @@
/*
* © 2023, Neil McKechnie
* © 2022 Paul M Antoine
* 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_NONBLOCKING_H
#define I2CMANAGER_NONBLOCKING_H
#include <Arduino.h>
#include "I2CManager.h"
// Support for atomic isolation (i.e. a block with interrupts disabled).
// E.g.
// ATOMIC_BLOCK() {
// doSomethingWithInterruptsDisabled();
// }
// This has the advantage over simple noInterrupts/Interrupts that the
// original interrupt state is restored when the block finishes.
//
// (This should really be defined in an include file somewhere more global, so
// it can replace use of noInterrupts/interrupts in other parts of DCC-EX.
//
static inline uint8_t _deferInterrupts(void) {
noInterrupts();
return 1;
}
static inline void _conditionalEnableInterrupts(bool *wasEnabled) {
if (*wasEnabled) interrupts();
}
#define ATOMIC_BLOCK(x) \
for (bool _int_saved __attribute__((__cleanup__(_conditionalEnableInterrupts))) \
=_getInterruptState(),_ToDo=_deferInterrupts(); _ToDo; _ToDo=0)
#if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc.
static inline bool _getInterruptState(void) {
return bitRead(SREG, SREG_I); // true if enabled, false if disabled
}
#elif defined(__arm__) // STM32, SAMD, Teensy
static inline bool _getInterruptState( void ) {
uint32_t reg;
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
return !(reg & 1); // true if interrupts enabled, false otherwise
}
#else
#warning "ATOMIC_BLOCK() not defined for this target type, I2C interrupts disabled"
#define ATOMIC_BLOCK(x) // expand to nothing.
#ifdef I2C_USE_INTERRUPTS
#undef I2C_USE_INTERRUPTS
#endif
#endif
// This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here
// to get intellisense to work correctly.
#if defined(I2C_USE_WIRE)
#undef I2C_USE_WIRE
#endif
enum MuxPhase: uint8_t {
MuxPhase_OFF = 0,
MuxPhase_PROLOG,
MuxPhase_PAYLOAD,
MuxPhase_EPILOG,
} ;
/***************************************************************************
* Initialise the I2CManagerAsync class.
***************************************************************************/
void I2CManagerClass::_initialise()
{
queueHead = queueTail = NULL;
state = I2C_STATE_FREE;
I2C_init();
_setClock(_clockSpeed);
}
/***************************************************************************
* Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast)
* on Arduino. Mega4809 supports 1000000 (Fast+) too.
* This function saves the desired clock speed and the startTransaction
* function acts on it before a new transaction, to avoid speed changes
* during an I2C transaction.
***************************************************************************/
void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
pendingClockSpeed = i2cClockSpeed;
}
/***************************************************************************
* Start an I2C transaction, if the I2C interface is free and
* there is a queued request to be processed.
* If there's an I2C clock speed change pending, then implement it before
* starting the operation.
***************************************************************************/
void I2CManagerClass::startTransaction() {
ATOMIC_BLOCK() {
if ((state == I2C_STATE_FREE) && (queueHead != NULL)) {
state = I2C_STATE_ACTIVE;
completionStatus = I2C_STATUS_OK;
// Check for pending clock speed change
if (pendingClockSpeed) {
// We're about to start a new I2C transaction, so set clock now.
I2C_setClock(pendingClockSpeed);
pendingClockSpeed = 0;
}
startTime = micros();
currentRequest = queueHead;
rxCount = txCount = 0;
// Start the I2C process going.
#if defined(I2C_EXTENDED_ADDRESS)
I2CMux muxNumber = currentRequest->i2cAddress.muxNumber();
if (muxNumber != I2CMux_None) {
muxPhase = MuxPhase_PROLOG;
uint8_t subBus = currentRequest->i2cAddress.subBus();
muxData[0] = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
#if defined(I2CMUX_PCA9547)
0x08 | subBus;
#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544)
0x04 | subBus; // NB Only 2 or 4 subbuses respectively
#else
// Default behaviour for most MUXs is to use a mask
// with a bit set for the subBus to be enabled
1 << subBus;
#endif
deviceAddress = I2C_MUX_BASE_ADDRESS + muxNumber;
sendBuffer = &muxData[0];
bytesToSend = 1;
bytesToReceive = 0;
operation = OPERATION_SEND;
} else {
// Send/receive payload for device only.
muxPhase = MuxPhase_OFF;
deviceAddress = currentRequest->i2cAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
}
#else
deviceAddress = currentRequest->i2cAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
#endif
I2C_sendStart();
}
}
}
/***************************************************************************
* Function to queue a request block and initiate operations.
***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) {
req->status = I2C_STATUS_PENDING;
req->nextRequest = NULL;
ATOMIC_BLOCK() {
if (!queueTail)
queueHead = queueTail = req; // Only item on queue
else
queueTail = queueTail->nextRequest = req; // Add to end
startTransaction();
}
}
/***************************************************************************
* Initiate a write to an I2C device (non-blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) {
// Make sure previous request has completed.
req->wait();
req->setWriteParams(i2cAddress, writeBuffer, writeLen);
queueRequest(req);
return I2C_STATUS_OK;
}
/***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) {
// Make sure previous request has completed.
req->wait();
req->setWriteParams(i2cAddress, writeBuffer, writeLen);
req->operation = OPERATION_SEND_P;
queueRequest(req);
return I2C_STATUS_OK;
}
/***************************************************************************
* Initiate a read from the I2C device, optionally preceded by a write
* (non-blocking operation)
***************************************************************************/
uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req)
{
// Make sure previous request has completed.
req->wait();
req->setRequestParams(i2cAddress, readBuffer, readLen, writeBuffer, writeLen);
queueRequest(req);
return I2C_STATUS_OK;
}
/***************************************************************************
* Set I2C timeout value in microseconds. The timeout applies to the entire
* I2CRB request, e.g. where a write+read is performed, the timer is not
* reset before the read.
***************************************************************************/
void I2CManagerClass::setTimeout(unsigned long value) {
_timeout = value;
};
/***************************************************************************
* checkForTimeout() function, called from isBusy() and wait() to cancel
* requests that are taking too long to complete. Such faults
* may be caused by an I2C wire short for example.
***************************************************************************/
void I2CManagerClass::checkForTimeout() {
ATOMIC_BLOCK() {
I2CRB *t = queueHead;
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
// Check for timeout
unsigned long elapsed = micros() - startTime;
if (elapsed > _timeout) {
#ifdef DIAG_IO
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
#endif
// Excessive time. Dequeue request
queueHead = t->nextRequest;
if (!queueHead) queueTail = NULL;
currentRequest = NULL;
bytesToReceive = bytesToSend = 0;
// Post request as timed out.
t->status = I2C_STATUS_TIMEOUT;
// Reset TWI interface so it is able to continue
// Try close and init, not entirely satisfactory but sort of works...
I2C_close(); // Shutdown and restart twi interface
// If SDA is stuck low, issue up to 9 clock pulses to attempt to free it.
pinMode(SCL, INPUT_PULLUP);
pinMode(SDA, INPUT_PULLUP);
for (int i=0; !digitalRead(SDA) && i<9; i++) {
digitalWrite(SCL, 0);
pinMode(SCL, OUTPUT); // Force clock low
delayMicroseconds(10); // ... for 5us
pinMode(SCL, INPUT_PULLUP); // ... then high
delayMicroseconds(10); // ... for 5us (100kHz Clock)
}
// Whether that's succeeded or not, now try reinitialising.
I2C_init();
_setClock(_clockSpeed);
state = I2C_STATE_FREE;
// Initiate next queued request if any.
startTransaction();
}
}
}
}
/***************************************************************************
* Loop function, for general background work
***************************************************************************/
void I2CManagerClass::loop() {
#if !defined(I2C_USE_INTERRUPTS)
handleInterrupt();
#endif
// Call function to monitor for stuck I2C operations.
checkForTimeout();
}
/***************************************************************************
* Interupt handler. Call I2C state machine, and dequeue request
* if completed.
***************************************************************************/
void I2CManagerClass::handleInterrupt() {
// Update hardware state machine
I2C_handleInterrupt();
// Check if current request has completed. If there's a current request
// and state isn't active then state contains the completion status of the request.
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
// Operation has completed.
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|| currentRequest->operation & OPERATION_NORETRY)
{
// Status is OK, or has failed and retry count exceeded, or retries disabled.
#if defined(I2C_EXTENDED_ADDRESS)
if (muxPhase == MuxPhase_PROLOG ) {
overallStatus = completionStatus;
uint8_t rbAddress = currentRequest->i2cAddress.deviceAddress();
if (completionStatus == I2C_STATUS_OK && rbAddress != 0) {
// Mux request OK, start handling application request.
muxPhase = MuxPhase_PAYLOAD;
deviceAddress = rbAddress;
sendBuffer = currentRequest->writeBuffer;
bytesToSend = currentRequest->writeLen;
receiveBuffer = currentRequest->readBuffer;
bytesToReceive = currentRequest->readLen;
operation = currentRequest->operation & OPERATION_MASK;
state = I2C_STATE_ACTIVE;
I2C_sendStart();
return;
}
} else if (muxPhase == MuxPhase_PAYLOAD) {
// Application request completed, now send epilogue to mux
overallStatus = completionStatus;
currentRequest->nBytes = rxCount; // Save number of bytes read into rb
if (_muxCount == 1) {
// Only one MUX, don't need to deselect subbus
muxPhase = MuxPhase_OFF;
} else {
muxPhase = MuxPhase_EPILOG;
deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber();
muxData[0] = 0x00;
sendBuffer = &muxData[0];
bytesToSend = 1;
bytesToReceive = 0;
operation = OPERATION_SEND;
state = I2C_STATE_ACTIVE;
I2C_sendStart();
return;
}
} else if (muxPhase == MuxPhase_EPILOG) {
// Epilog finished, ignore completionStatus
muxPhase = MuxPhase_OFF;
} else
overallStatus = completionStatus;
#else
overallStatus = completionStatus;
currentRequest->nBytes = rxCount;
#endif
// Remove completed request from head of queue
I2CRB * t = queueHead;
if (t == currentRequest) {
queueHead = t->nextRequest;
if (!queueHead) queueTail = queueHead;
t->status = overallStatus;
// I2C state machine is now free for next request
currentRequest = NULL;
state = I2C_STATE_FREE;
}
retryCounter = 0;
} else {
// Status is failed and retry permitted.
// Retry previous request.
state = I2C_STATE_FREE;
}
}
if (state == I2C_STATE_FREE && queueHead != NULL) {
// Allow any pending interrupts before starting the next request.
//interrupts();
// Start next request
I2CManager.startTransaction();
}
}
#endif

View File

@@ -1,247 +0,0 @@
/*
* © 2022 Paul M Antoine
* © 2023, 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_SAMD_H
#define I2CMANAGER_SAMD_H
#include <Arduino.h>
#include "I2CManager.h"
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO)
void SERCOM3_Handler() {
I2CManager.handleInterrupt();
}
#endif
// Assume SERCOM3 for now - default I2C bus on Arduino Zero and variants of same
Sercom *s = SERCOM3;
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
int t_rise;
if (i2cClockSpeed < 200000L) {
i2cClockSpeed = 100000L; // NB: this overrides a "force clock" of lower than 100KHz!
t_rise = 1000;
} else if (i2cClockSpeed < 800000L) {
i2cClockSpeed = 400000L;
t_rise = 300;
} else if (i2cClockSpeed < 1200000L) {
i2cClockSpeed = 1000000L;
t_rise = 120;
} else {
i2cClockSpeed = 100000L;
t_rise = 1000;
}
// Wait while the bus is busy
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
// Disable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 0 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Calculate baudrate - using a rise time appropriate for the speed
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / (2 * i2cClockSpeed) - 5 - (((SystemCoreClock / 1000000) * t_rise) / (2 * 1000));
// Enable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 1 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Setting bus idle mode and wait for sync
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
//Setting clock
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID(GCM_SERCOM3_CORE) | // Generic Clock 0 (SERCOM3)
GCLK_CLKCTRL_GEN_GCLK0 | // Generic Clock Generator 0 is source
GCLK_CLKCTRL_CLKEN ;
/* Wait for peripheral clock synchronization */
while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
// Software reset the SERCOM
s->I2CM.CTRLA.bit.SWRST = 1;
//Wait both bits Software Reset from CTRLA and SYNCBUSY are equal to 0
while(s->I2CM.CTRLA.bit.SWRST || s->I2CM.SYNCBUSY.bit.SWRST);
// Set master mode and enable SCL Clock Stretch mode (stretch after ACK bit)
s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* |
SERCOM_I2CM_CTRLA_SCLSM*/ ;
// Enable Smart mode (but not Quick Command)
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_EnableIRQ(SERCOM3_IRQn);
NVIC_SetPriority (SERCOM3_IRQn, SERCOM_NVIC_PRIORITY); // Match default SERCOM priorities
// NVIC_SetPriority (SERCOM3_IRQn, 0); // Set highest priority
// Enable all interrupts
s->I2CM.INTENSET.reg = SERCOM_I2CM_INTENSET_MB | SERCOM_I2CM_INTENSET_SB | SERCOM_I2CM_INTENSET_ERROR;
#endif
// Calculate baudrate and set default rate for now
s->I2CM.BAUD.bit.BAUD = SystemCoreClock / ( 2 * I2C_FREQ) - 7 / (2 * 1000);
// Enable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 1 ;
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
// Setting bus idle mode and wait for sync
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
// Set SDA/SCL pins as outputs and enable pullups, at present we assume these are
// the default ones for SERCOM3 (see assumption above)
pinPeripheral(PIN_WIRE_SDA, g_APinDescription[PIN_WIRE_SDA].ulPinType);
pinPeripheral(PIN_WIRE_SCL, g_APinDescription[PIN_WIRE_SCL].ulPinType);
// Enable the SCL and SDA pins on the sercom: includes increased driver strength,
// pull-up resistors and pin multiplexer
PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg =
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg =
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
}
/***************************************************************************
* Initiate a start bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
txCount = 0;
rxCount = 0;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start and address with read flag (1) or'd in
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
}
else {
// Send start and address with write flag (0) or'd in
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1ul) | 0;
}
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->I2CM.CTRLB.bit.CMD = 3; // Stop condition
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
I2C_sendStop();
// Disable the I2C master mode and wait for sync
s->I2CM.CTRLA.bit.ENABLE = 0 ;
// Wait for up to 500us only.
unsigned long startTime = micros();
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) {
if (micros() - startTime >= 500UL) break;
}
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler or,
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
if (s->I2CM.STATUS.bit.ARBLOST) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->I2CM.STATUS.bit.BUSERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (s->I2CM.INTFLAG.bit.MB) {
// Master write completed
if (s->I2CM.STATUS.bit.RXNACK) {
// Nacked, send stop.
I2C_sendStop();
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED; // Completed with error
} else if (bytesToSend) {
// Acked, so send next byte
s->I2CM.DATA.bit.DATA = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// No more data to send/receive. Initiate a STOP condition
I2C_sendStop();
state = I2C_STATE_COMPLETED; // Completed OK
}
} else if (s->I2CM.INTFLAG.bit.SB) {
// Master read completed without errors
if (bytesToReceive == 1) {
s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED; // Completed OK
} else if (bytesToReceive) {
s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
bytesToReceive--;
}
}
}
#endif /* I2CMANAGER_SAMD_H */

View File

@@ -1,312 +0,0 @@
/*
* © 2022-23 Paul M Antoine
* © 2023, 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_STM32_H
#define I2CMANAGER_STM32_H
#include <Arduino.h>
#include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
void I2C1_IRQHandler() {
I2CManager.handleInterrupt();
}
#endif
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
I2C_TypeDef *s = I2C1;
#define I2C_IRQn I2C1_EV_IRQn
#define I2C_BUSFREQ 16
// I2C SR1 Status Register #1 bit definitions for convenience
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
// #define I2C_SR1_TIMEOUT (1<<14) // Timeout of Tlow error
// #define I2C_SR1_PECERR (1<<12) // PEC error in reception
// #define I2C_SR1_OVR (1<<11) // Overrun/Underrun error
// #define I2C_SR1_AF (1<<10) // Acknowledge failure
// #define I2C_SR1_ARLO (1<<9) // Arbitration lost (master mode)
// #define I2C_SR1_BERR (1<<8) // Bus error (misplaced start or stop condition)
// #define I2C_SR1_TxE (1<<7) // Data register empty on transmit
// #define I2C_SR1_RxNE (1<<6) // Data register not empty on receive
// #define I2C_SR1_STOPF (1<<4) // Stop detection (slave mode)
// #define I2C_SR1_ADD10 (1<<3) // 10 bit header sent
// #define I2C_SR1_BTF (1<<2) // Byte transfer finished - data transfer done
// #define I2C_SR1_ADDR (1<<1) // Address sent (master) or matched (slave)
// #define I2C_SR1_SB (1<<0) // Start bit (master mode) 1=start condition generated
// I2C CR1 Control Register #1 bit definitions for convenience
// #define I2C_CR1_SWRST (1<<15) // Software reset - places peripheral under reset
// #define I2C_CR1_ALERT (1<<13) // SMBus alert assertion
// #define I2C_CR1_PEC (1<<12) // Packet Error Checking transfer in progress
// #define I2C_CR1_POS (1<<11) // Acknowledge/PEC Postion (for data reception in PEC mode)
// #define I2C_CR1_ACK (1<<10) // Acknowledge enable - ACK returned after byte is received (address or data)
// #define I2C_CR1_STOP (1<<9) // STOP generated
// #define I2C_CR1_START (1<<8) // START generated
// #define I2C_CR1_NOSTRETCH (1<<7) // Clock stretching disable (slave mode)
// #define I2C_CR1_ENGC (1<<6) // General call (broadcast) enable (address 00h is ACKed)
// #define I2C_CR1_ENPEC (1<<5) // PEC Enable
// #define I2C_CR1_ENARP (1<<4) // ARP enable (SMBus)
// #define I2C_CR1_SMBTYPE (1<<3) // SMBus type, 1=host, 0=device
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
uint16_t t_rise;
uint32_t ccr_freq;
if (i2cClockSpeed < 200000L) {
// i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
else if (i2cClockSpeed < 800000L)
{
i2cClockSpeed = 400000L;
t_rise = 0x06; // (300ns / 62.5ns) + 1;
// } else if (i2cClockSpeed < 1200000L) {
// i2cClockSpeed = 1000000L;
// t_rise = 120;
}
else
{
i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
// Enable the I2C master mode
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
// Software reset the I2C peripheral
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
// Release reset
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Calculate baudrate - using a rise time appropriate for the speed
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = (uint16_t)ccr_freq;
// Configure the rise time register
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
}
/***************************************************************************
* Initialise I2C registers.
***************************************************************************/
void I2CManagerClass::I2C_init()
{
//Setting up the clocks
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
// Standard I2C pins are SCL on PB8 and SDA on PB9
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
// Alt Function High register routing pins PB8 and PB9 for I2C1:
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
// Software reset the I2C peripheral
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Program the peripheral input clock in CR2 Register in order to generate correct timings
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C_IRQn);
// CR2 Interrupt Settings
// Bit 15-13: reserved
// Bit 12: LAST - DMA last transfer
// Bit 11: DMAEN - DMA enable
// Bit 10: ITBUFEN - Buffer interrupt enable
// Bit 9: ITEVTEN - Event interrupt enable
// Bit 8: ITERREN - Error interrupt enable
// Bit 7-6: reserved
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
s->CR2 |= 0x0300; // Enable Event and Error interrupts
#endif
// Calculate baudrate and set default rate for now
// Configure the Clock Control Register for 100KHz SCL frequency
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = 0x0050;
// Configure the rise time register - max allowed in 1000ns
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
// Setting bus idle mode and wait for sync
}
/***************************************************************************
* Initiate a start bit for transmission.
***************************************************************************/
void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
rxCount = txCount = 0;
uint8_t temp;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start for read operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
// Special case for 1 byte reads!
if (bytesToReceive == 1)
{
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
else
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
else {
// Send start for write operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
/***************************************************************************
* Close I2C down
***************************************************************************/
void I2CManagerClass::I2C_close() {
I2C_sendStop();
// Disable the I2C master mode and wait for sync
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
// Should never happen, but wait for up to 500us only.
unsigned long startTime = micros();
while ((s->CR1 && I2C_CR1_PE) != 0) {
if (micros() - startTime >= 500UL) break;
}
}
/***************************************************************************
* Main state machine for I2C, called from interrupt handler or,
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
if (s->SR1 && I2C_SR1_ARLO) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->SR1 && I2C_SR1_BERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
} else if (s->SR1 && I2C_SR1_TXE) {
// Master write completed
if (s->SR1 && (1<<10)) {
// Nacked, send stop.
I2C_sendStop();
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
} else if (bytesToSend) {
// Acked, so send next byte
s->DR = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// Check both TxE/BTF == 1 before generating stop
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
// No more data to send/receive. Initiate a STOP condition and finish
I2C_sendStop();
state = I2C_STATE_COMPLETED;
}
} else if (s->SR1 && I2C_SR1_RXNE) {
// Master read completed without errors
if (bytesToReceive == 1) {
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED;
} else if (bytesToReceive) {
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
}
}
#endif /* I2CMANAGER_STM32_H */

View File

@@ -1,226 +0,0 @@
/*
* © 2023, 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_WIRE_H
#define I2CMANAGER_WIRE_H
#include <Arduino.h>
#include <Wire.h>
#include "I2CManager.h"
// This module is only compiled if I2C_USE_WIRE is defined, so define it here
// to get intellisense to work correctly.
#if !defined(I2C_USE_WIRE)
#define I2C_USE_WIRE
#endif
// Older versions of Wire don't have setWireTimeout function. AVR does.
#ifdef ARDUINO_ARCH_AVR
#define WIRE_HAS_TIMEOUT
#endif
/***************************************************************************
* Initialise I2C interface software
***************************************************************************/
void I2CManagerClass::_initialise() {
Wire.begin();
#if defined(WIRE_HAS_TIMEOUT)
Wire.setWireTimeout(_timeout, true);
#endif
}
/***************************************************************************
* Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast)
* on Arduino. Mega4809 supports 1000000 (Fast+) too.
***************************************************************************/
void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
Wire.setClock(i2cClockSpeed);
}
/***************************************************************************
* Set I2C timeout value in microseconds. The timeout applies to each
* Wire call separately, i.e. in a write+read, the timer is reset before the
* read is started.
***************************************************************************/
void I2CManagerClass::setTimeout(unsigned long value) {
_timeout = value;
#if defined(WIRE_HAS_TIMEOUT)
Wire.setWireTimeout(value, true);
#endif
}
/********************************************************
* Helper function for I2C Multiplexer operations
********************************************************/
#ifdef I2C_EXTENDED_ADDRESS
static uint8_t muxSelect(I2CAddress address) {
// Select MUX sub bus.
I2CMux muxNo = address.muxNumber();
I2CSubBus subBus = address.subBus();
if (muxNo != I2CMux_None) {
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
uint8_t data = (subBus == SubBus_All) ? 0xff :
(subBus == SubBus_None) ? 0x00 :
(1 << subBus);
Wire.write(&data, 1);
return Wire.endTransmission(true); // have to release I2C bus for it to work
}
return I2C_STATUS_OK;
}
#endif
/***************************************************************************
* Initiate a write to an I2C device (blocking operation on Wire)
***************************************************************************/
uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
uint8_t status, muxStatus;
uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block.
do {
status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None)
muxStatus = muxSelect(address);
#endif
// Only send new transaction if address is non-zero.
if (muxStatus == I2C_STATUS_OK && address != 0) {
Wire.beginTransmission(address);
if (size > 0) Wire.write(buffer, size);
status = Wire.endTransmission();
}
#ifdef I2C_EXTENDED_ADDRESS
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK
&& address.deviceAddress() != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!(status == I2C_STATUS_OK
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->status = status;
return I2C_STATUS_OK;
}
/***************************************************************************
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire)
***************************************************************************/
uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
uint8_t ramBuffer[size];
const uint8_t *p1 = buffer;
for (uint8_t i=0; i<size; i++)
ramBuffer[i] = GETFLASH(p1++);
return write(address, ramBuffer, size, rb);
}
/***************************************************************************
* Initiate a write (optional) followed by a read from the I2C device (blocking operation on Wire)
* If fewer than the number of requested bytes are received, status is I2C_STATUS_TRUNCATED.
***************************************************************************/
uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb)
{
uint8_t status, muxStatus;
uint8_t nBytes = 0;
uint8_t retryCount = 0;
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
// in the request block.
do {
status = muxStatus = I2C_STATUS_OK;
#ifdef I2C_EXTENDED_ADDRESS
if (address.muxNumber() != I2CMux_None) {
muxStatus = muxSelect(address);
}
#endif
// Only start new transaction if address is non-zero.
if (muxStatus == I2C_STATUS_OK && address != 0) {
if (writeSize > 0) {
Wire.beginTransmission(address);
Wire.write(writeBuffer, writeSize);
status = Wire.endTransmission(false); // Don't free bus yet
}
if (status == I2C_STATUS_OK) {
#ifdef WIRE_HAS_TIMEOUT
Wire.clearWireTimeoutFlag();
Wire.requestFrom(address, (size_t)readSize);
if (!Wire.getWireTimeoutFlag()) {
while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read();
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
} else {
status = I2C_STATUS_TIMEOUT;
}
#else
Wire.requestFrom(address, (size_t)readSize);
while (Wire.available() && nBytes < readSize)
readBuffer[nBytes++] = Wire.read();
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
#endif
}
}
#ifdef I2C_EXTENDED_ADDRESS
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK && address != 0 && address.muxNumber() != I2CMux_None) {
muxSelect({address.muxNumber(), SubBus_None});
}
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
#endif
} while (!((status == I2C_STATUS_OK)
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
rb->nBytes = nBytes;
rb->status = status;
return I2C_STATUS_OK;
}
/***************************************************************************
* Function to queue a request block and initiate operations.
*
* For the Wire version, this executes synchronously.
* The read/write/write_P functions return I2C_STATUS_OK always, and the
* completion status of the operation is in the request block, as for
* the non-blocking version.
***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) {
switch (req->operation & OPERATION_MASK) {
case OPERATION_READ:
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
break;
case OPERATION_SEND:
write(req->i2cAddress, req->writeBuffer, req->writeLen, req);
break;
case OPERATION_SEND_P:
write_P(req->i2cAddress, req->writeBuffer, req->writeLen, req);
break;
case OPERATION_REQUEST:
read(req->i2cAddress, req->readBuffer, req->readLen, req->writeBuffer, req->writeLen, req);
break;
}
}
/***************************************************************************
* Loop function, for general background work
***************************************************************************/
void I2CManagerClass::loop() {}
#endif

View File

@@ -1,567 +0,0 @@
/*
* © 2021 Neil McKechnie
* © 2021 Harald Barth
* All rights reserved.
*
* This file is part of DCC++EX 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 <Arduino.h>
#include "IODevice.h"
#include "DIAG.h"
#include "FSH.h"
#include "IO_MCP23017.h"
#include "DCCTimer.h"
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
#define USE_FAST_IO
#endif
// Link to halSetup function. If not defined, the function reference will be NULL.
extern __attribute__((weak)) void halSetup();
extern __attribute__((weak)) void exrailHalSetup();
//==================================================================================================================
// Static methods
//------------------------------------------------------------------------------------------------------------------
// Static functions
// Static method to initialise the IODevice subsystem.
#if !defined(IO_NO_HAL)
// Create any standard device instances that may be required, such as the Arduino pins
// and PCA9685.
void IODevice::begin() {
// Initialise the IO subsystem defaults
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
// Call user's halSetup() function (if defined in the build in myHal.cpp).
// The contents will depend on the user's system hardware configuration.
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
// This is done early so that the subsequent defaults will detect an overlap and not
// create something that conflicts with the user's vpin definitions.
if (halSetup)
halSetup();
// include any HAL devices defined in exrail.
if (exrailHalSetup)
exrailHalSetup();
// Predefine two PCA9685 modules 0x40-0x41
// Allocates 32 pins 100-131
PCA9685::create(100, 16, 0x40);
PCA9685::create(116, 16, 0x41);
// Predefine two MCP23017 module 0x20/0x21
// Allocates 32 pins 164-195
MCP23017::create(164, 16, 0x20);
MCP23017::create(180, 16, 0x21);
}
// reset() function to reinitialise all devices
void IODevice::reset() {
unsigned long currentMicros = micros();
for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) {
dev->_deviceState = DEVSTATE_DORMANT;
// First ensure that _loop isn't delaying
dev->delayUntil(currentMicros);
// Then invoke _begin to restart driver
dev->_begin();
}
}
// Overarching static loop() method for the IODevice subsystem. Works through the
// list of installed devices and calls their individual _loop() method.
// Devices may or may not implement this, but if they do it is useful for things like animations
// or flashing LEDs.
// The current value of micros() is passed as a parameter, so the called loop function
// doesn't need to invoke it.
void IODevice::loop() {
unsigned long currentMicros = micros();
IODevice *lastLoopDevice = _nextLoopDevice; // So we know when to stop...
// Loop through devices until we find one ready to be serviced.
do {
if (!_nextLoopDevice) _nextLoopDevice = _firstDevice;
if (_nextLoopDevice) {
if (_nextLoopDevice->_deviceState != DEVSTATE_FAILED
&& ((long)(currentMicros - _nextLoopDevice->_nextEntryTime)) >= 0) {
// Found one ready to run, so invoke its _loop method.
_nextLoopDevice->_nextEntryTime = currentMicros;
_nextLoopDevice->_loop(currentMicros);
_nextLoopDevice = _nextLoopDevice->_nextDevice;
break;
}
// Not this one, move to next one
_nextLoopDevice = _nextLoopDevice->_nextDevice;
}
} while (_nextLoopDevice != lastLoopDevice); // Stop looking when we've done all.
// Report loop time if diags enabled
#if defined(DIAG_LOOPTIMES)
unsigned long diagMicros = micros();
static unsigned long lastMicros = 0;
// Measure time since HAL's loop() method started.
unsigned long halElapsed = diagMicros - currentMicros;
// Measure time between loop() method entries (excluding this diagnostic).
unsigned long elapsed = diagMicros - lastMicros;
static unsigned long maxElapsed = 0, maxHalElapsed = 0;
static unsigned long lastOutputTime = 0;
static unsigned long halTotal = 0, total = 0;
static unsigned long count = 0;
const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec
// Ignore long loop counts while message is still outputting (~3 milliseconds)
if (currentMicros - lastOutputTime > 3000UL) {
if (elapsed > maxElapsed) maxElapsed = elapsed;
if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed;
halTotal += halElapsed;
total += elapsed;
count++;
}
if (diagMicros - lastOutputTime > interval) {
if (lastOutputTime > 0)
DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"),
total/count, maxElapsed, halTotal/count, maxHalElapsed);
maxElapsed = maxHalElapsed = total = halTotal = count = 0;
lastOutputTime = diagMicros;
}
// Read microsecond count after calculations, so they aren't
// included in the overall timings.
lastMicros = micros();
#endif
}
// Display a list of all the devices on the diagnostic stream.
void IODevice::DumpAll() {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
dev->_display();
}
}
// Determine if the specified vpin is allocated to a device.
bool IODevice::exists(VPIN vpin) {
return findDevice(vpin) != NULL;
}
// check whether the pin supports notification. If so, then regular _read calls are not required.
bool IODevice::hasCallback(VPIN vpin) {
IODevice *dev = findDevice(vpin);
if (!dev) return false;
return dev->_hasCallback;
}
// Display (to diagnostics) details of the device.
void IODevice::_display() {
DIAG(F("Unknown device Vpins:%d-%d %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
// Find device associated with nominated Vpin and pass configuration values on to it.
// Return false if not found.
bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
IODevice *dev = findDevice(vpin);
if (dev) return dev->_configure(vpin, configType, paramCount, params);
#ifdef DIAG_IO
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin);
#endif
return false;
}
// Read value from virtual pin.
int IODevice::read(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_read(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
#endif
return false;
}
// Read analogue value from virtual pin.
int IODevice::readAnalogue(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_readAnalogue(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
#endif
return -1023;
}
int IODevice::configureAnalogIn(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->owns(vpin))
return dev->_configureAnalogIn(vpin);
}
#ifdef DIAG_IO
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
#endif
return -1023;
}
// Write value to virtual pin(s). If multiple devices are allocated the same pin
// then only the first one found will be used.
void IODevice::write(VPIN vpin, int value) {
IODevice *dev = findDevice(vpin);
if (dev) {
dev->_write(vpin, value);
return;
}
#ifdef DIAG_IO
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
#endif
}
// Write analogue value to virtual pin(s). If multiple devices are allocated
// the same pin then only the first one found will be used.
//
// The significance of param1 and param2 may vary from device to device.
// For servo controllers, param1 is the profile of the transition and param2
// the duration, i.e. the time that the operation is to be animated over
// in deciseconds (0-3276 sec)
//
void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) {
IODevice *dev = findDevice(vpin);
if (dev) {
dev->_writeAnalogue(vpin, value, param1, param2);
return;
}
#ifdef DIAG_IO
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
#endif
}
// isBusy, when called for a device pin is always a digital output or analogue output,
// returns input feedback state of the pin, i.e. whether the pin is busy performing
// an animation or fade over a period of time.
bool IODevice::isBusy(VPIN vpin) {
IODevice *dev = findDevice(vpin);
if (dev)
return dev->_read(vpin);
else
return false;
}
void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
if (pinNumber >= 0)
pinMode(pinNumber, INPUT_PULLUP);
_gpioInterruptPin = pinNumber;
}
// Helper function to add a new device to the device chain. If
// slaveDevice is NULL then the device is added to the end of the chain.
// Otherwise, the chain is searched for slaveDevice and the new device linked
// in front of it (to support filter devices that share the same VPIN range
// as the devices they control). If slaveDevice isn't found, then the
// device is linked to the end of the chain.
void IODevice::addDevice(IODevice *newDevice, IODevice *slaveDevice /* = NULL */) {
if (slaveDevice == _firstDevice) {
newDevice->_nextDevice = _firstDevice;
_firstDevice = newDevice;
} else {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (dev->_nextDevice == slaveDevice || dev->_nextDevice == NULL) {
// Link new device between dev and slaveDevice (or at end of chain)
newDevice->_nextDevice = dev->_nextDevice;
dev->_nextDevice = newDevice;
break;
}
}
}
newDevice->_begin();
}
// Private helper function to locate a device by VPIN. Returns NULL if not found.
// This is performance-critical, so minimises the calculation and function calls necessary.
IODevice *IODevice::findDevice(VPIN vpin) {
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
VPIN firstVpin = dev->_firstVpin;
if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins)
return dev;
}
return NULL;
}
// Instance helper function for filter devices (layered over others). Looks for
// a device that is further down the chain than the current device.
IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) {
VPIN firstVpin = dev->_firstVpin;
if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins)
return dev;
}
return NULL;
}
// Private helper function to check for vpin overlap. Run during setup only.
// returns true if pins DONT overlap with existing device
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
#ifdef DIAG_IO
DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString());
#endif
VPIN lastPin=firstPin+nPins-1;
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
if (nPins > 0 && dev->_nPins > 0) {
// check for pin range overlaps (verbose but compiler will fix that)
VPIN firstDevPin=dev->_firstVpin;
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
if (!noOverlap) {
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
firstPin, lastPin);
return false;
}
}
// Check for overlapping I2C address
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
return false;
}
}
return true; // no overlaps... OK to go on with constructor
}
//==================================================================================================================
// Static data
//------------------------------------------------------------------------------------------------------------------
// Chain of callback blocks (identifying registered callback functions for state changes)
IONotifyCallback *IONotifyCallback::first = 0;
// Start and end of chain of devices.
IODevice *IODevice::_firstDevice = 0;
// Reference to next device to be called on _loop() method.
IODevice *IODevice::_nextLoopDevice = 0;
//==================================================================================================================
// Instance members
//------------------------------------------------------------------------------------------------------------------
// Method to check whether the id corresponds to this device
bool IODevice::owns(VPIN id) {
return (id >= _firstVpin && id < _firstVpin + _nPins);
}
#else // !defined(IO_NO_HAL)
// Minimal implementations of public HAL interface, to support Arduino pin I/O and nothing more.
void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
#ifdef DIAG_IO
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
#endif
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
return true;
}
void IODevice::write(VPIN vpin, int value) {
if (vpin >= NUM_DIGITAL_PINS) return;
digitalWrite(vpin, value);
pinMode(vpin, OUTPUT);
}
void IODevice::writeAnalogue(VPIN, int, uint8_t, uint16_t) {}
bool IODevice::isBusy(VPIN) { return false; }
bool IODevice::hasCallback(VPIN) { return false; }
int IODevice::read(VPIN vpin) {
if (vpin >= NUM_DIGITAL_PINS) return 0;
return !digitalRead(vpin); // Return inverted state (5v=0, 0v=1)
}
int IODevice::readAnalogue(VPIN vpin) {
return ADCee::read(vpin);
}
int IODevice::configureAnalogIn(VPIN vpin) {
return ADCee::init(vpin);
}
void IODevice::loop() {}
void IODevice::DumpAll() {
DIAG(F("NO HAL CONFIGURED!"));
}
bool IODevice::exists(VPIN vpin) { return (vpin > 2 && vpin < NUM_DIGITAL_PINS); }
void IODevice::setGPIOInterruptPin(int16_t) {}
// Chain of callback blocks (identifying registered callback functions for state changes)
// Not used in IO_NO_HAL but must be declared.
IONotifyCallback *IONotifyCallback::first = 0;
#endif // IO_NO_HAL
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
ArduinoPins::ArduinoPins(VPIN firstVpin, int nPins) {
_firstVpin = firstVpin;
_nPins = nPins;
int arrayLen = (_nPins+7)/8;
_pinPullups = (uint8_t *)calloc(3, arrayLen);
_pinModes = (&_pinPullups[0]) + arrayLen;
_pinInUse = (&_pinPullups[0]) + 2*arrayLen;
for (int i=0; i<arrayLen; i++) {
_pinPullups[i] = 0xff; // default to pullup on, for inputs
_pinModes[i] = 0;
_pinInUse[i] = 0;
}
}
// Device-specific pin configuration. Configure should be called infrequently so simplify
// code by using the standard pinMode function.
bool ArduinoPins::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_INPUT) return false;
if (paramCount != 1) return false;
bool pullup = params[0];
int pin = vpin;
#ifdef DIAG_IO
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, pullup);
#endif
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
_pinModes[index] &= ~mask; // set to input mode
if (pullup) {
_pinPullups[index] |= mask;
pinMode(pin, INPUT_PULLUP);
} else {
_pinPullups[index] &= ~mask;
pinMode(pin, INPUT);
}
_pinInUse[index] |= mask;
return true;
}
// Device-specific write function.
void ArduinoPins::_write(VPIN vpin, int value) {
int pin = vpin;
#ifdef DIAG_IO
DIAG(F("Arduino Write Pin:%d Val:%d"), pin, value);
#endif
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
// First update the output state, then set into write mode if not already.
fastWriteDigital(pin, value);
if (!(_pinModes[index] & mask)) {
// Currently in read mode, change to write mode
_pinModes[index] |= mask;
// Since mode changes should be infrequent, use standard pinMode function
pinMode(pin, OUTPUT);
_pinInUse[index] |= mask;
}
}
// Device-specific read function (digital input).
int ArduinoPins::_read(VPIN vpin) {
int pin = vpin;
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
if ((_pinModes[index] | ~_pinInUse[index]) & mask) {
// Currently in write mode or not initialised, change to read mode
_pinModes[index] &= ~mask;
// Since mode changes should be infrequent, use standard pinMode function
if (_pinPullups[index] & mask)
pinMode(pin, INPUT_PULLUP);
else
pinMode(pin, INPUT);
_pinInUse[index] |= mask;
}
int value = !fastReadDigital(pin); // Invert (5v=0, 0v=1)
#ifdef DIAG_IO
//DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
#endif
return value;
}
// Device-specific readAnalogue function (analogue input)
int ArduinoPins::_readAnalogue(VPIN vpin) {
if (vpin > 255) return -1023;
uint8_t pin = vpin;
int value = ADCee::read(pin);
#ifdef DIAG_IO
DIAG(F("Arduino Read Pin:%d Value:%d"), pin, value);
#endif
return value;
}
int ArduinoPins::_configureAnalogIn(VPIN vpin) {
if (vpin > 255) return -1023;
uint8_t pin = vpin;
uint8_t mask = 1 << ((pin-_firstVpin) % 8);
uint8_t index = (pin-_firstVpin) / 8;
if (_pinModes[index] & mask) {
// Currently in write mode, change to read mode
_pinModes[index] &= ~mask;
// Since mode changes should be infrequent, use standard pinMode function
if (_pinPullups[index] & mask)
pinMode(pin, INPUT_PULLUP);
else
pinMode(pin, INPUT);
}
int value = ADCee::init(pin);
#ifdef DIAG_IO
DIAG(F("configureAnalogIn Pin:%d Value:%d"), pin, value);
#endif
return value;
}
void ArduinoPins::_display() {
DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void ArduinoPins::fastWriteDigital(uint8_t pin, uint8_t value) {
#if defined(USE_FAST_IO)
if (pin >= NUM_DIGITAL_PINS) return;
uint8_t mask = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *outPortAdr = portOutputRegister(port);
noInterrupts();
if (value)
*outPortAdr |= mask;
else
*outPortAdr &= ~mask;
interrupts();
#else
digitalWrite(pin, value);
#endif
}
bool ArduinoPins::fastReadDigital(uint8_t pin) {
#if defined(USE_FAST_IO)
if (pin >= NUM_DIGITAL_PINS) return false;
uint8_t mask = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *inPortAdr = portInputRegister(port);
// read input
bool result = (*inPortAdr & mask) != 0;
#else
bool result = digitalRead(pin);
#endif
return result;
}

View File

@@ -1,478 +0,0 @@
/*
* © 2023, Paul Antoine, Discord user @ADUBOURG
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 iodevice_h
#define iodevice_h
// Define symbol DIAG_IO to enable diagnostic output
//#define DIAG_IO Y
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
//#define DIAG_LOOPTIMES
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#define IO_NO_HAL
#endif
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
// animation has completed. This switches off the servo motor, preventing
// the continuous buzz sometimes found on servos, and reducing the
// power consumption of the servo when inactive.
// It is recommended to enable this, unless it causes you problems.
#define IO_SWITCH_OFF_SERVO
#include "DIAG.h"
#include "FSH.h"
#include "I2CManager.h"
#include "inttypes.h"
typedef uint16_t VPIN;
// Limit VPIN number to max 32767. Above this number, printing often gives negative values.
// This should be enough for 99% of users.
#define VPIN_MAX 32767
#define VPIN_NONE 65535
/*
* Callback support for state change notification from an IODevice subclass to a
* handler, e.g. Sensor object handling.
*/
class IONotifyCallback {
public:
typedef void IONotifyCallbackFunction(VPIN vpin, int value);
static void add(IONotifyCallbackFunction *function) {
IONotifyCallback *blk = new IONotifyCallback(function);
if (first) blk->next = first;
first = blk;
}
static void invokeAll(VPIN vpin, int value) {
for (IONotifyCallback *blk = first; blk != NULL; blk = blk->next)
blk->invoke(vpin, value);
}
static bool hasCallback() {
return first != NULL;
}
private:
IONotifyCallback(IONotifyCallbackFunction *function) { invoke = function; };
IONotifyCallback *next = 0;
IONotifyCallbackFunction *invoke = 0;
static IONotifyCallback *first;
};
/*
* IODevice class
*
* This class is the basis of the Hardware Abstraction Layer (HAL) for
* the DCC++EX Command Station. All device classes derive from this.
*
*/
class IODevice {
public:
// Parameter values to identify type of call to IODevice::configure.
typedef enum : uint8_t {
CONFIGURE_INPUT = 1,
CONFIGURE_SERVO = 2,
CONFIGURE_OUTPUT = 3,
CONFIGURE_ANALOGOUTPUT = 4,
CONFIGURE_ANALOGINPUT = 5,
} ConfigTypeEnum;
typedef enum : uint8_t {
DEVSTATE_DORMANT = 0,
DEVSTATE_PROBING = 1,
DEVSTATE_INITIALISING = 2,
DEVSTATE_NORMAL = 3,
DEVSTATE_SCANNING = 4,
DEVSTATE_FAILED = 5,
} DeviceStateEnum;
// Static functions to find the device and invoke its member functions
// begin is invoked to create any standard IODevice subclass instances.
// Also, the _begin method of any existing instances is called from here.
static void begin();
// reset function to invoke all driver's _begin() methods again, to
// reset the state of the devices and reinitialise.
static void reset();
// configure is used invoke an IODevice instance's _configure method
static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]);
// User-friendly function for configuring an input pin.
inline static bool configureInput(VPIN vpin, bool pullupEnable) {
int params[] = {pullupEnable};
return IODevice::configure(vpin, CONFIGURE_INPUT, 1, params);
}
// User-friendly function for configuring a servo pin.
inline static bool configureServo(VPIN vpin, uint16_t activePosition, uint16_t inactivePosition, uint8_t profile=0, uint16_t duration=0, uint8_t initialState=0) {
int params[] = {(int)activePosition, (int)inactivePosition, profile, (int)duration, initialState};
return IODevice::configure(vpin, CONFIGURE_SERVO, 5, params);
}
// write invokes the IODevice instance's _write method.
static void write(VPIN vpin, int value);
// write invokes the IODevice instance's _writeAnalogue method (not applicable for digital outputs)
static void writeAnalogue(VPIN vpin, int value, uint8_t profile=0, uint16_t duration=0);
// isBusy returns true if the device is currently in an animation of some sort, e.g. is changing
// the output over a period of time.
static bool isBusy(VPIN vpin);
// check whether the pin supports notification. If so, then regular _read calls are not required.
static bool hasCallback(VPIN vpin);
// read invokes the IODevice instance's _read method.
static int read(VPIN vpin);
// read invokes the IODevice instance's _readAnalogue method.
static int readAnalogue(VPIN vpin);
static int configureAnalogIn(VPIN vpin);
// loop invokes the IODevice instance's _loop method.
static void loop();
static void DumpAll();
// exists checks whether there is a device owning the specified vpin
static bool exists(VPIN vpin);
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
// Without the shared interrupt, input states are scanned periodically to detect changes on
// GPIO extender pins. If a shared interrupt pin is configured, then input states are scanned
// only when the shared interrupt pin is pulled low. The external GPIO module releases the pin
// once the GPIO port concerned has been read.
void setGPIOInterruptPin(int16_t pinNumber);
// Method to check if pins will overlap before creating new device.
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
// pin range.
IODevice *findDeviceFollowing(VPIN vpin);
// Method to write new state (optionally implemented within device class)
virtual void _write(VPIN vpin, int value) {
(void)vpin; (void)value;
};
// Method to write an 'analogue' value (optionally implemented within device class)
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) {
(void)vpin; (void)value; (void) param1; (void)param2;
};
// Method to read digital pin state (optionally implemented within device class)
virtual int _read(VPIN vpin) {
(void)vpin;
return 0;
};
// Method to read analogue pin state (optionally implemented within device class)
virtual int _readAnalogue(VPIN vpin) {
(void)vpin;
return 0;
};
protected:
// Constructor
IODevice(VPIN firstVpin=0, int nPins=0) {
_firstVpin = firstVpin;
_nPins = nPins;
_nextEntryTime = 0;
_I2CAddress=0;
}
// Method to perform initialisation of the device (optionally implemented within device class)
virtual void _begin() {}
// Method to check whether the vpin corresponds to this device
bool owns(VPIN vpin);
// Method to configure device (optionally implemented within device class)
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
return false;
};
virtual int _configureAnalogIn(VPIN vpin) {
(void)vpin;
return 0;
};
// Method to perform updates on an ongoing basis (optionally implemented within device class)
virtual void _loop(unsigned long currentMicros) {
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
};
// Method for displaying info on DIAG output (optionally implemented within device class)
virtual void _display();
// Destructor
virtual ~IODevice() {};
// Non-virtual function
void delayUntil(unsigned long futureMicrosCount) {
_nextEntryTime = futureMicrosCount;
}
// Common object fields.
VPIN _firstVpin;
int _nPins;
I2CAddress _I2CAddress;
// Flag whether the device supports callbacks.
bool _hasCallback = false;
// Pin number of interrupt pin for GPIO extender devices. The extender module will pull this
// pin low if an input changes state.
int16_t _gpioInterruptPin = -1;
// Static support function for subclass creation
static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL);
// Method to find device handling Vpin
static IODevice *findDevice(VPIN vpin);
// Current state of device
DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
private:
IODevice *_nextDevice = 0;
unsigned long _nextEntryTime;
static IODevice *_firstDevice;
static IODevice *_nextLoopDevice;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for PCA9685 16-channel PWM module.
*/
class PCA9685 : public IODevice {
public:
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50);
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
private:
// Constructor
PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency);
// Device-specific initialisation
void _begin() override;
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
// Device-specific write functions.
void _write(VPIN vpin, int value) override;
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override;
int _read(VPIN vpin) override; // returns the digital state or busy status of the device
void _loop(unsigned long currentMicros) override;
void updatePosition(uint8_t pin);
void writeDevice(uint8_t pin, int value);
void _display() override;
struct ServoData {
uint16_t activePosition : 12; // Config parameter
uint16_t inactivePosition : 12; // Config parameter
uint16_t currentPosition : 12;
uint16_t fromPosition : 12;
uint16_t toPosition : 12;
uint8_t profile; // Config parameter
uint16_t stepNumber; // Index of current step (starting from 0)
uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
uint8_t currentProfile; // profile being used for current animation.
uint16_t duration; // time (tenths of a second) for animation to complete.
}; // 14 bytes per element, i.e. per pin in use
struct ServoData *_servoData [16];
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
static const uint8_t FLASH _bounceProfile[30];
const unsigned int refreshInterval = 50; // refresh every 50ms
// structures for setting up non-blocking writes to servo controller
I2CRB requestBlock;
uint8_t outputBuffer[5];
uint8_t prescaler; // clock prescaler for setting PWM frequency
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for DCC accessory decoder.
*/
class DCCAccessoryDecoder: public IODevice {
public:
static void create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
private:
// Constructor
DCCAccessoryDecoder(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress);
// Device-specific write function.
void _begin() override;
void _write(VPIN vpin, int value) override;
void _display() override;
int _packedAddress;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for arduino input/output pins.
*/
class ArduinoPins: public IODevice {
public:
static void create(VPIN firstVpin, int nPins) {
addDevice(new ArduinoPins(firstVpin, nPins));
}
static void fastWriteDigital(uint8_t pin, uint8_t value);
static bool fastReadDigital(uint8_t pin);
private:
// Constructor
ArduinoPins(VPIN firstVpin, int nPins);
// Device-specific pin configuration
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
// Device-specific write function.
void _write(VPIN vpin, int value) override;
// Device-specific read functions.
int _read(VPIN vpin) override;
int _readAnalogue(VPIN vpin) override;
int _configureAnalogIn(VPIN vpin) override;
void _display() override;
uint8_t *_pinPullups;
uint8_t *_pinModes; // each bit is 1 for output, 0 for input
uint8_t *_pinInUse;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-Turntable.
*/
class EXTurntable : public IODevice {
public:
static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
// Constructor
EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
enum ActivityNumber : uint8_t {
Turn = 0, // Rotate turntable, maintain phase
Turn_PInvert = 1, // Rotate turntable, invert phase
Home = 2, // Initiate homing
Calibrate = 3, // Initiate calibration sequence
LED_On = 4, // Turn LED on
LED_Slow = 5, // Set LED to a slow blink
LED_Fast = 6, // Set LED to a fast blink
LED_Off = 7, // Turn LED off
Acc_On = 8, // Turn accessory pin on
Acc_Off = 9, // Turn accessory pin off
};
private:
// Device-specific write function.
void _begin() override;
void _loop(unsigned long currentMicros) override;
int _read(VPIN vpin) override;
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
void _display() override;
uint8_t _stepperStatus;
};
/////////////////////////////////////////////////////////////////////////////////////////////////////
// IODevice framework for invoking user-written functions.
// To use, define a function that you want to be regularly
// invoked, and then create an instance of UserAddin.
// For example, you can show the status, on screen 3, of the first eight
// locos in the speed table:
//
// void updateLocoScreen() {
// for (int i=0; i<8; i++) {
// if (DCC::speedTable[i].loco > 0) {
// int speed = DCC::speedTable[i].speedCode;
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
// speed & 0x7f, speed & 0x80 ? 'R' : 'F');
// }
// }
// }
//
// void halSetup() {
// ...
// UserAddin(updateLocoScreen, 1000); // Update every 1000ms
// ...
// }
//
class UserAddin : public IODevice {
private:
void (*_invokeUserFunction)();
int _delay; // milliseconds
public:
UserAddin(void (*func)(), int delay) {
_invokeUserFunction = func;
_delay = delay;
addDevice(this);
}
// userFunction has no return value, no parameter. delay is in milliseconds.
static void create(void (*userFunction)(), int delay) {
new UserAddin(userFunction, delay);
}
protected:
void _begin() { _display(); }
void _loop(unsigned long currentMicros) override {
_invokeUserFunction();
// _loop won't be called again until _delay ms have elapsed.
delayUntil(currentMicros + _delay * 1000UL);
}
void _display() override {
DIAG(F("UserAddin run every %dms"), _delay);
}
};
#include "IO_MCP23008.h"
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#include "IO_PCF8575.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
#endif // iodevice_h

View File

@@ -1,169 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 io_analogueinputs_h
#define io_analogueinputs_h
// Uncomment following line to slow the scan cycle down to 1second ADC samples, with
// diagnostic output of scanned values.
//#define IO_ANALOGUE_SLOW
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/**********************************************************************************************
* ADS111x class for I2C-connected analogue input modules ADS1113, ADS1114 and ADS1115.
*
* ADS1113 and ADS1114 are restricted to 1 input. ADS1115 has a multiplexer which allows
* any of four input pins to be read by its ADC.
*
* The driver polls the device in accordance with the constant 'scanInterval' below. On first loop
* entry, the multiplexer is set to pin A0 and the ADC is triggered. On second and subsequent
* entries, the analogue value is read from the conversion register and then the multiplexer and
* ADC are set up to read the next pin.
*
* The ADS111x is set up as follows:
* Single-shot scan
* Data rate 128 samples/sec (7.8ms/sample, but scanned every 10ms)
* Comparator off
* Gain FSR=6.144V
* The gain means that the maximum input voltage of 5V (when Vss=5V) gives a reading
* of 32767*(5.0/6.144) = 26666.
*
* A device is configured by the following:
* ADS111x::create(firstVpin, nPins, i2cAddress);
* for example
* ADS111x::create(300, 1, 0x48); // single-input ADS1113
* ADS111x::create(300, 4, 0x48); // four-input ADS1115
*
* Note: The device is simple and does not need initial configuration, so it should recover from
* temporary loss of communications or power.
**********************************************************************************************/
class ADS111x: public IODevice {
public:
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress);
}
private:
ADS111x(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
_firstVpin = firstVpin;
_nPins = (nPins > 4) ? 4 : nPins;
_I2CAddress = i2cAddress;
_currentPin = 0;
for (int8_t i=0; i<_nPins; i++)
_value[i] = -1;
addDevice(this);
}
void _begin() {
// Initialise I2C
I2CManager.begin();
// ADS111x support high-speed I2C (4.3MHz) but that requires special
// processing. So stick to fast mode (400kHz maximum).
I2CManager.setClock(400000);
// Initialise ADS device
if (I2CManager.exists(_I2CAddress)) {
_nextState = STATE_STARTSCAN;
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("ADS111x device not found, I2C:%s"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
// Check that previous non-blocking write has completed, if not then wait
uint8_t status = _i2crb.status;
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything.
if (status == I2C_STATUS_OK) {
switch (_nextState) {
case STATE_STARTSCAN:
// Configure ADC and multiplexer for next scan. See ADS111x datasheet for details
// of configuration register settings.
_outBuffer[0] = 0x01; // Config register address
_outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n
_outBuffer[2] = 0xA3; // 250 samples/sec, comparator off
// Write command, without waiting for completion.
I2CManager.write(_I2CAddress, _outBuffer, 3, &_i2crb);
delayUntil(currentMicros + scanInterval);
_nextState = STATE_STARTREAD;
break;
case STATE_STARTREAD:
// Reading the pin value
_outBuffer[0] = 0x00; // Conversion register address
I2CManager.read(_I2CAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register
_nextState = STATE_GETVALUE;
break;
case STATE_GETVALUE:
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
#ifdef IO_ANALOGUE_SLOW
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
#endif
// Move to next pin
if (++_currentPin >= _nPins) _currentPin = 0;
_nextState = STATE_STARTSCAN;
break;
default:
break;
}
} else { // error status
DIAG(F("ADS111x I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED;
}
}
int _readAnalogue(VPIN vpin) override {
int pin = vpin - _firstVpin;
return _value[pin];
}
void _display() override {
DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
// ADC conversion rate is 250SPS, or 4ms per conversion. Set the period between updates to 10ms.
// This is enough to allow the conversion to reliably complete in time.
#ifndef IO_ANALOGUE_SLOW
const unsigned long scanInterval = 10000UL; // Period between successive ADC scans in microseconds.
#else
const unsigned long scanInterval = 1000000UL; // Period between successive ADC scans in microseconds.
#endif
enum : uint8_t {
STATE_STARTSCAN,
STATE_STARTREAD,
STATE_GETVALUE,
};
uint16_t _value[4];
uint8_t _outBuffer[3];
uint8_t _inBuffer[2];
uint8_t _currentPin; // ADC pin currently being scanned
I2CRB _i2crb;
uint8_t _nextState;
};
#endif // io_analogueinputs_h

View File

@@ -1,68 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 "DCC.h"
#include "IODevice.h"
#include "DIAG.h"
#include "defines.h"
#define PACKEDADDRESS(addr, subaddr) (((addr) << 2) + (subaddr))
#define ADDRESS(packedaddr) ((packedaddr) >> 2)
#define SUBADDRESS(packedaddr) ((packedaddr) % 4)
void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) {
if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress);
}
// Constructors
DCCAccessoryDecoder::DCCAccessoryDecoder(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) {
_firstVpin = vpin;
_nPins = nPins;
_packedAddress = PACKEDADDRESS(DCCAddress, DCCSubaddress);
addDevice(this);
}
void DCCAccessoryDecoder::_begin() {
#if defined(DIAG_IO)
_display();
#endif
}
// Device-specific write function. State 1=closed, 0=thrown. Adjust for RCN-213 compliance
void DCCAccessoryDecoder::_write(VPIN id, int state) {
int packedAddress = _packedAddress + id - _firstVpin;
#if defined(HAL_ACCESSORY_COMMAND_REVERSE)
state = !state;
#ifdef DIAG_IO
DIAG(F("DCC Write Linear Address:%d State:%d (inverted)"), packedAddress, state);
#endif
#else
#ifdef DIAG_IO
DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state);
#endif
#endif
DCC::setAccessory(ADDRESS(packedAddress), SUBADDRESS(packedAddress), state);
}
void DCCAccessoryDecoder::_display() {
int endAddress = _packedAddress + _nPins - 1;
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
}

View File

@@ -1,297 +0,0 @@
/*
* © 2022, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
* amplifier, so it only needs a power supply and a speaker.
*
* This driver allows the device to be controlled through IODevice::write() and
* IODevice::writeAnalogue() calls.
*
* The driver is configured as follows:
*
* DFPlayer::create(firstVpin, nPins, Serialn);
*
* Where firstVpin is the first vpin reserved for reading the device,
* nPins is the number of pins to be allocated (max 5)
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
*
* Example:
* In mySetup function within mySetup.cpp:
* DFPlayer::create(3500, 5, Serial1);
*
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
the first file will be played by setting pin 3500, the second by setting pin 3501 etc.;
* Writing a digital value of 0 to any pin will stop the player;
* Reading a digital value from any pin will return true(1) if the player is playing, false(0) otherwise.
*
* From EX-RAIL, the following commands may be used:
* SET(3500) -- starts playing the first file (file 1) on the SD card
* SET(3501) -- starts playing the second file (file 2) on the SD card
* etc.
* RESET(3500) -- stops all playing on the player
* WAITFOR(3500) -- wait for the file currently being played by the player to complete
* SERVO(3500,2,Instant) -- plays file 2 at current volume
* SERVO(3501,20,Instant) -- Sets the volume to 20
*
* NB The DFPlayer's serial lines are not 5V safe, so connecting the Arduino TX directly
* to the DFPlayer's RX terminal will cause lots of noise over the speaker, or worse.
* A 1k resistor in series with the module's RX terminal will alleviate this.
*
* Files on the SD card are numbered according to their order in the directory on the
* card (as listed by the DIR command in Windows). This may not match the order of the files
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
* files be copied into an empty SDcard in the desired order, one at a time.
*/
#ifndef IO_DFPlayer_h
#define IO_DFPlayer_h
#include "IODevice.h"
class DFPlayer : public IODevice {
private:
const uint8_t MAXVOLUME=30;
HardwareSerial *_serial;
bool _playing = false;
uint8_t _inputIndex = 0;
unsigned long _commandSendTime; // Allows timeout processing
uint8_t _lastVolumeLevel = MAXVOLUME;
// When two commands are sent in quick succession, the device sometimes
// fails to execute one. A delay is required between successive commands.
// This could be implemented by buffering commands and outputting them
// from the loop() function, but it would somewhat complicate the
// driver. A simpler solution is to output a number of NUL pad characters
// between successive command strings if there isn't sufficient elapsed time
// between them. At 9600 baud, each pad character takes approximately
// 1ms to complete. Experiments indicate that the minimum number of pads
// for reliable operation is 17. This gives 17.7ms between the end of one
// command and the beginning of the next, or 28ms between successive commands
// being completed. I've allowed 20 characters, which is almost 21ms.
const int numPadCharacters = 20; // Number of pad characters between commands
public:
static void create(VPIN firstVpin, int nPins, HardwareSerial &serial) {
if (checkNoOverlap(firstVpin,nPins)) new DFPlayer(firstVpin, nPins, serial);
}
protected:
// Constructor
DFPlayer(VPIN firstVpin, int nPins, HardwareSerial &serial) :
IODevice(firstVpin, nPins),
_serial(&serial)
{
addDevice(this);
}
void _begin() override {
_serial->begin(9600, SERIAL_8N1); // 9600baud, no parity, 1 stop bit
// Flush any data in input queue
while (_serial->available()) _serial->read();
_deviceState = DEVSTATE_INITIALISING;
// Send a query to the device to see if it responds
sendPacket(0x42);
_commandSendTime = micros();
}
void _loop(unsigned long currentMicros) override {
// Check for incoming data on _serial, and update busy flag accordingly.
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
while (_serial->available()) {
int c = _serial->read();
if (c == 0x7E && _inputIndex == 0)
_inputIndex = 1;
else if ((c==0xFF && _inputIndex==1)
|| (c==0x3D && _inputIndex==3)
|| (_inputIndex >=4 && _inputIndex <= 8))
_inputIndex++;
else if (c==0x06 && _inputIndex==2) {
// Valid message prefix, so consider the device online
if (_deviceState==DEVSTATE_INITIALISING) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
}
_inputIndex++;
} else if (c==0xEF && _inputIndex==9) {
// End of play
if (_playing) {
#ifdef DIAG_IO
DIAG(F("DFPlayer: Finished"));
#endif
_playing = false;
}
_inputIndex = 0;
} else
_inputIndex = 0; // Unrecognised character sequence, start again!
}
// Check if the initial prompt to device has timed out. Allow 5 seconds
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
DIAG(F("DFPlayer device not responding on serial port"));
_deviceState = DEVSTATE_FAILED;
}
delayUntil(currentMicros + 10000); // Only enter every 10ms
}
// Write with value 1 starts playing a song. The relative pin number is the file number.
// Write with value 0 stops playing.
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
if (value) {
// Value 1, start playing
#ifdef DIAG_IO
DIAG(F("DFPlayer: Play %d"), pin+1);
#endif
sendPacket(0x03, pin+1);
_playing = true;
} else {
// Value 0, stop playing
#ifdef DIAG_IO
DIAG(F("DFPlayer: Stop"));
#endif
sendPacket(0x16);
_playing = false;
}
}
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
// Volume may be specified as second parameter to writeAnalogue.
// If value is zero, the player stops playing.
// WriteAnalogue on second pin sets the output volume.
// If starting a new file and setting volume, then avoid a short burst of loud noise by
// the following strategy:
// - If the volume is increasing, start playing the song before setting the volume,
// - If the volume is decreasing, decrease it and then start playing.
//
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
uint8_t pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume);
#endif
// Validate parameter.
if (volume > MAXVOLUME) volume = MAXVOLUME;
if (pin == 0) {
// Play track
if (value > 0) {
if (volume != 0) {
if (volume <= _lastVolumeLevel)
sendPacket(0x06, volume); // Set volume before starting
sendPacket(0x03, value); // Play track
_playing = true;
if (volume > _lastVolumeLevel)
sendPacket(0x06, volume); // Set volume after starting
_lastVolumeLevel = volume;
} else {
// Volume not changed, just play
sendPacket(0x03, value);
_playing = true;
}
} else {
sendPacket(0x16); // Stop play
_playing = false;
}
} else if (pin == 1) {
// Set volume (0-30)
sendPacket(0x06, value);
_lastVolumeLevel = volume;
}
}
// A read on any pin indicates whether the player is still playing.
int _read(VPIN) override {
return _playing;
}
void _display() override {
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
private:
// 7E FF 06 0F 00 01 01 xx xx EF
// 0 -> 7E is start code
// 1 -> FF is version
// 2 -> 06 is length
// 3 -> 0F is command
// 4 -> 00 is no receive
// 5~6 -> 01 01 is argument
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
// 9 -> EF is end code
void sendPacket(uint8_t command, uint16_t arg = 0)
{
unsigned long currentMillis = millis();
uint8_t out[] = { 0x7E,
0xFF,
06,
command,
00,
static_cast<uint8_t>(arg >> 8),
static_cast<uint8_t>(arg & 0x00ff),
00,
00,
0xEF };
setChecksum(out);
// Check how long since the last command was sent.
// Each character takes approx 1ms at 9600 baud
unsigned long minimumGap = numPadCharacters + sizeof(out);
if (currentMillis - _commandSendTime < minimumGap) {
// Output some pad characters to add an
// artificial delay between commands
for (int i=0; i<numPadCharacters; i++)
_serial->write((uint8_t)0);
}
// Now output the command
_serial->write(out, sizeof(out));
_commandSendTime = currentMillis;
}
uint16_t calcChecksum(uint8_t* packet)
{
uint16_t sum = 0;
for (int i = 1; i < 7; i++)
{
sum += packet[i];
}
return -sum;
}
void setChecksum(uint8_t* out)
{
uint16_t sum = calcChecksum(out);
out[7] = (sum >> 8);
out[8] = (sum & 0xff);
}
};
#endif // IO_DFPlayer_h

View File

@@ -1,125 +0,0 @@
/*
* © 2022, Colin Murdoch. 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/>.
*/
/*
* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
*
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
*
*
*/
#ifndef IO_EXFastclock_h
#define IO_EXFastclock_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "EXRAIL2.h"
#include "CommandDistributor.h"
bool FAST_CLOCK_EXISTS = true;
class EXFastClock : public IODevice {
public:
// Constructor
EXFastClock(I2CAddress i2cAddress){
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(I2CAddress i2cAddress) {
DIAG(F("Checking for Clock"));
// Start by assuming we will find the clock
// Check if specified I2C address is responding (blocking operation)
// Returns I2C_STATUS_OK (0) if OK, or error code.
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
DIAG(F("Clock check result - %d"), _checkforclock);
// XXXX change thistosave2 bytes
if (_checkforclock == 0) {
FAST_CLOCK_EXISTS = true;
//DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
new EXFastClock(i2cAddress);
}
else {
FAST_CLOCK_EXISTS = false;
//DIAG(F("No Fast Clock found"));
LCD(6,F("CLOCK NOT FOUND"));
}
}
private:
// Initialisation of Fastclock
void _begin() override {
if (FAST_CLOCK_EXISTS == true) {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
_deviceState = DEVSTATE_NORMAL;
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
//LCD(6,F("CLOCK NOT FOUND"));
DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString());
}
}
}
// Processing loop to obtain clock time
void _loop(unsigned long currentMicros) override{
if (FAST_CLOCK_EXISTS==true) {
uint8_t readBuffer[3];
byte a,b;
#ifdef EXRAIL_ACTIVE
I2CManager.read(_I2CAddress, readBuffer, 3);
// XXXX change this to save a few bytes
a = readBuffer[0];
b = readBuffer[1];
//_clocktime = (a << 8) + b;
//_clockrate = readBuffer[2];
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
// Clock interval is 60/ clockspeed i.e 60/b seconds
delayUntil(currentMicros + ((60/b) * 1000000));
#endif
}
}
// Display EX-FastClock device driver info.
void _display() override {
DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

View File

@@ -1,263 +0,0 @@
/*
* © 2022, Peter Cole. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
* This device driver will configure the device on startup, along with
* interacting with the device for all input/output duties.
*
* To create EX-IOExpander devices, these are defined in myHal.cpp:
* (Note the device driver is included by default)
*
* void halSetup() {
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
* EXIOExpander::create(800, 18, 0x65);
* }
*
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
* device in use. There is no way for the device driver to sanity check pins are used for the
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
*/
#ifndef IO_EX_IOEXPANDER_H
#define IO_EX_IOEXPANDER_H
#include "I2CManager.h"
#include "DIAG.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-IOExpander.
*/
class EXIOExpander : public IODevice {
public:
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
}
private:
// Constructor
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_i2cAddress = i2cAddress;
addDevice(this);
}
void _begin() {
// Initialise EX-IOExander device
I2CManager.begin();
if (I2CManager.exists(_i2cAddress)) {
_command4Buffer[0] = EXIOINIT;
_command4Buffer[1] = _nPins;
_command4Buffer[2] = _firstVpin & 0xFF;
_command4Buffer[3] = _firstVpin >> 8;
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4);
if (_receive3Buffer[0] == EXIOPINS) {
_numDigitalPins = _receive3Buffer[1];
_numAnaloguePins = _receive3Buffer[2];
_digitalPinBytes = (_numDigitalPins + 7)/8;
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
_analoguePinBytes = _numAnaloguePins * 2;
_analogueInputStates = (byte*) calloc(_analoguePinBytes, 1);
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
} else {
DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _i2cAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
// We now need to retrieve the analogue pin map
_command1Buffer[0] = EXIOINITA;
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1);
// Attempt to get version, if we don't get it, we don't care, don't go offline
_command1Buffer[0] = EXIOVER;
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
_I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
if (paramCount != 1) return false;
int pin = vpin - _firstVpin;
if (configType == CONFIGURE_INPUT) {
bool pullup = params[0];
_digitalOutBuffer[0] = EXIODPUP;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = pullup;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
if (_command1Buffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
return false;
}
} else {
return false;
}
}
// Analogue input pin configuration, used to enable on EX-IOExpander device
int _configureAnalogIn(VPIN vpin) override {
int pin = vpin - _firstVpin;
_command2Buffer[0] = EXIOENAN;
_command2Buffer[1] = pin;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2);
if (_command1Buffer[0] == EXIORDY) {
return true;
} else {
DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin);
return false;
}
return true;
}
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
void _loop(unsigned long currentMicros) override {
(void)currentMicros; // remove warning
if (_deviceState == DEVSTATE_FAILED) return;
_command1Buffer[0] = EXIORDD;
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1);
_command1Buffer[0] = EXIORDAN;
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
}
// Obtain the correct analogue input value
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
uint8_t _pinLSBByte;
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
if (_analoguePinMap[aPin] == pin) {
_pinLSBByte = aPin * 2;
}
}
uint8_t _pinMSBByte = _pinLSBByte + 1;
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
}
// Obtain the correct digital input value
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
uint8_t pinByte = pin / 8;
bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8);
return value;
}
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
_digitalOutBuffer[0] = EXIOWRD;
_digitalOutBuffer[1] = pin;
_digitalOutBuffer[2] = value;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
if (_command1Buffer[0] != EXIORDY) {
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
}
}
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
_servoBuffer[0] = EXIOWRAN;
_servoBuffer[1] = pin;
_servoBuffer[2] = value & 0xFF;
_servoBuffer[3] = value >> 8;
_servoBuffer[4] = profile;
_servoBuffer[5] = duration & 0xFF;
_servoBuffer[6] = duration >> 8;
I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7);
if (_command1Buffer[0] != EXIORDY) {
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
}
}
void _display() override {
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"),
_i2cAddress.toString(), _majorVer, _minorVer, _patchVer,
(int)_firstVpin, (int)_firstVpin+_nPins-1,
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
I2CAddress _i2cAddress;
uint8_t _numDigitalPins = 0;
uint8_t _numAnaloguePins = 0;
byte _digitalOutBuffer[3];
uint8_t _versionBuffer[3];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
byte* _digitalInputStates;
byte* _analogueInputStates;
uint8_t _digitalPinBytes = 0;
uint8_t _analoguePinBytes = 0;
byte _command1Buffer[1];
byte _command2Buffer[2];
byte _command4Buffer[4];
byte _receive3Buffer[3];
byte _servoBuffer[7];
uint8_t* _analoguePinMap;
// EX-IOExpander protocol flags
enum {
EXIOINIT = 0xE0, // Flag to initialise setup procedure
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
EXIOVER = 0xE3, // Flag to get version
EXIORDAN = 0xE4, // Flag to read an analogue input
EXIOWRD = 0xE5, // Flag for digital write
EXIORDD = 0xE6, // Flag to read digital input
EXIOENAN = 0xE7, // Flag to enable an analogue pin
EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings
EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers
EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM)
EXIOERR = 0xEF, // Flag we've received an error
};
};
#endif

View File

@@ -1,121 +0,0 @@
/*
* © 2021, Peter Cole. 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/>.
*/
/*
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
*
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
*
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
* <D TT vpin steps activity> in the serial console
* MOVETT(vpin, steps, activity) in EX-RAIL
* Refer to the documentation for further information including the valid activities.
*/
#ifndef IO_EXTurntable_h
#define IO_EXTurntable_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
new EXTurntable(firstVpin, nPins, I2CAddress);
}
// Constructor
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
addDevice(this);
}
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
}
}
// Processing loop to obtain status of stepper
// 0 = finished moving and in correct position
// 1 = still moving
void EXTurntable::_loop(unsigned long currentMicros) {
uint8_t readBuffer[1];
I2CManager.read(_I2CAddress, readBuffer, 1);
_stepperStatus = readBuffer[0];
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
}
// Read returns status as obtained in our loop.
// Return false if our status value is invalid.
int EXTurntable::_read(VPIN vpin) {
if (_deviceState == DEVSTATE_FAILED) return 0;
// DIAG(F("_read status: %d"), _stepperStatus);
if (_stepperStatus > 1) {
return false;
} else {
return _stepperStatus;
}
}
// writeAnalogue to send the steps and activity to Turntable-EX.
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
// value contains the steps, bit shifted to MSB + LSB.
// activity contains the activity flag as per this list:
//
// Turn = 0, // Rotate turntable, maintain phase
// Turn_PInvert = 1, // Rotate turntable, invert phase
// Home = 2, // Initiate homing
// Calibrate = 3, // Initiate calibration sequence
// LED_On = 4, // Turn LED on
// LED_Slow = 5, // Set LED to a slow blink
// LED_Fast = 6, // Set LED to a fast blink
// LED_Off = 7, // Turn LED off
// Acc_On = 8, // Turn accessory pin on
// Acc_Off = 9 // Turn accessory pin off
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
if (_deviceState == DEVSTATE_FAILED) return;
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
vpin, value, activity, duration);
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
#endif
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
}
// Display Turnetable-EX device driver info.
void EXTurntable::_display() {
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
#endif

View File

@@ -1,165 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* To declare a device instance,
* IO_ExampleSerial myDevice(1000, 10, Serial3, 9600);
* or to create programmatically,
* IO_ExampleSerial::create(1000, 10, Serial3, 9600);
*
* (uses VPINs 1000-1009, talke on Serial 3 at 9600 baud.)
*
* See IO_ExampleSerial.cpp for the protocol used over the serial line.
*
*/
#ifndef IO_EXAMPLESERIAL_H
#define IO_EXAMPLESERIAL_H
#include "IODevice.h"
class IO_ExampleSerial : public IODevice {
private:
// Here we define the device-specific variables.
HardwareSerial *_serial;
uint8_t _inputState = 0;
int _inputIndex = 0;
int _inputValue = 0;
uint16_t *_pinValues; // Pointer to block of memory containing pin values
unsigned long _baud;
public:
// Static function to handle "IO_ExampleSerial::create(...)" calls.
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
}
protected:
// Constructor. This should initialise variables etc. but not call other objects yet
// (e.g. Serial, I2CManager, and other parts of the CS functionality).
// defer those until the _begin() function. The 'addDevice' call is required unless
// the device is not to be added (e.g. because of incorrect parameters).
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
_firstVpin = firstVpin;
_nPins = nPins;
_pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
_baud = baud;
// Save reference to serial port driver
_serial = serial;
addDevice(this);
}
// Device-specific initialisation
void _begin() override {
_serial->begin(_baud);
#if defined(DIAG_IO)
_display();
#endif
// Send a few # characters to the output
for (uint8_t i=0; i<3; i++)
_serial->write('#');
}
// Device-specific write function. Write a string in the form "#Wm,n#"
// where m is the vpin number, and n is the value.
void _write(VPIN vpin, int value) {
int pin = vpin -_firstVpin;
#ifdef DIAG_IO
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
#endif
// Send a command string over the serial line
_serial->print('#');
_serial->print('W');
_serial->print(pin);
_serial->print(',');
_serial->print(value);
_serial->println('#');
DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value);
}
// Device-specific read function.
int _read(VPIN vpin) {
// Return a value for the specified vpin.
int result = _pinValues[vpin-_firstVpin];
return result;
}
// Loop function to do background scanning of the input port. State
// machine parses the incoming command as it is received. Command
// is in the form "#Nm,n#" where m is the index and n is the value.
void _loop(unsigned long currentMicros) {
(void)currentMicros; // Suppress compiler warnings
if (_serial->available()) {
// Input data available to read. Read a character.
char c = _serial->read();
switch (_inputState) {
case 0: // Waiting for start of command
if (c == '#') // Start of command received.
_inputState = 1;
break;
case 1: // Expecting command character
if (c == 'N') { // 'Notify' character received
_inputState = 2;
_inputValue = _inputIndex = 0;
} else
_inputState = 0; // Unexpected char, reset
break;
case 2: // reading first parameter (index)
if (isdigit(c))
_inputIndex = _inputIndex * 10 + (c-'0');
else if (c==',')
_inputState = 3;
else
_inputState = 0; // Unexpected char, reset
break;
case 3: // reading reading second parameter (value)
if (isdigit(c))
_inputValue = _inputValue * 10 - (c-'0');
else if (c=='#') { // End of command
// Complete command received, do something with it.
DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
if (_inputIndex >= 0 && _inputIndex < _nPins) { // Store value
_pinValues[_inputIndex] = _inputValue;
}
_inputState = 0; // Done, start again.
} else
_inputState = 0; // Unexpected char, reset
break;
}
}
}
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
// Here we display the current values held for the pins.
void _display() {
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
(int)_firstVpin+_nPins-1);
for (int i=0; i<_nPins; i++)
DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]);
}
};
#endif // IO_EXAMPLESERIAL_H

View File

@@ -1,141 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 device driver monitors the state of turnout objects and writes updates,
* on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte)
* EEPROM device connected via I2C.
*
* When the device is restarted, it repositions the turnouts in accordance
* with the last saved position.
*
* To create a device instance,
* IO_ExternalEEPROM::create(0, 0, i2cAddress);
*
*
*/
#ifndef IO_EXTERNALEEPROM_H
#define IO_EXTERNALEEPROM_H
#include "IODevice.h"
#include "I2CManager.h"
#include "Turnouts.h"
class ExternalEEPROM : public IODevice {
private:
// Here we define the device-specific variables.
int _sizeInKBytes = 128;
Turnout *_turnout = 0;
int _lastTurnoutHash = 0;
I2CRB _rb;
uint8_t _buffer[32]; // 32 is max for Wire write
public:
// Static function to handle "IO_ExampleSerial::create(...)" calls.
static void create(I2CAddress i2cAddress, int sizeInKBytes) {
if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes);
}
protected:
// Constructor.
ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) {
_I2CAddress = i2cAddress;
_sizeInKBytes = sizeInKBytes;
// Set up I2C structures.
_rb.setWriteParams(_I2CAddress, _buffer, 32);
addDevice(this);
}
// Device-specific initialisation
void _begin() override {
I2CManager.begin();
I2CManager.setClock(1000000); // Max supported speed
if (I2CManager.exists(_I2CAddress)) {
// Initialise or read contents of EEPROM
// and set turnout states accordingly.
// Read 32 bytes from address 0x0000.
I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0);
// Dump data
DIAG(F("EEPROM First 32 bytes:"));
for (int i=0; i<32; i+=8)
DIAG(F("%d: %x %x %x %x %x %x %x %x"),
i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3],
_buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]);
#if defined(DIAG_IO)
_display();
#endif
} else {
DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Loop function to do background scanning of the turnouts
void _loop(unsigned long currentMicros) {
(void)currentMicros; // Suppress compiler warnings
if (_rb.isBusy()) return; // Can't do anything until previous request has completed.
if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) {
// Device not responding, probably still writing data, so requeue request
I2CManager.queueRequest(&_rb);
return;
}
if (_lastTurnoutHash != Turnout::turnoutlistHash) {
_lastTurnoutHash = Turnout::turnoutlistHash;
// Turnout list has changed, so pointer held from last run may be invalid
_turnout = 0; // Start at the beginning of the list again.
//#if defined(DIAG_IO)
DIAG(F("Turnout Hash Changed!"));
//#endif
}
// Locate next turnout, or first one if there is no current one.
if (_turnout)
_turnout = _turnout->next();
else
_turnout = Turnout::first();
// Retrieve turnout state
int turnoutID = _turnout->getId();
int turnoutState = _turnout->isThrown();
(void)turnoutID; // Suppress compiler warning
(void)turnoutState; // Suppress compiler warning
// TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed.
// TODO: If it has, then initiate a write of the updated state to EEPROM
delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256
}
// Display information about the device.
void _display() {
DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(),
_deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F(""));
}
};
#endif // IO_EXTERNALEEPROM_H

View File

@@ -1,250 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 IO_GPIOBASE_H
#define IO_GPIOBASE_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
// GPIOBase is defined as a class template. This allows it to be instantiated by
// subclasses with different types, according to the number of pins on the GPIO module.
// For example, GPIOBase<uint8_t> for 8 pins, GPIOBase<uint16_t> for 16 pins etc.
// A module with up to 64 pins can be handled in this way (uint64_t).
template <class T>
class GPIOBase : public IODevice {
protected:
// Constructor
GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin);
// Device-specific initialisation
void _begin() override;
// Device-specific pin configuration function.
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
// Pin write function.
void _write(VPIN vpin, int value) override;
// Pin read function.
int _read(VPIN vpin) override;
void _display() override;
void _loop(unsigned long currentMicros) override;
// Data fields
// Allocate enough space for all input pins
T _portInputState; // 1=high (inactive), 0=low (activated)
T _portOutputState; // 1 =high, 0=low
T _portMode; // 0=input, 1=output
T _portPullup; // 0=nopullup, 1=pullup
T _portInUse; // 0=not in use, 1=in use
// Target interval between refreshes of each input port
static const int _portTickTime = 4000; // 4ms
// Virtual functions for interfacing with I2C GPIO Device
virtual void _writeGpioPort() = 0;
virtual void _readGpioPort(bool immediate=true) = 0;
virtual void _writePullups() {};
virtual void _writePortModes() {};
virtual void _setupDevice() {};
virtual void _processCompletion(uint8_t status) {
(void)status; // Suppress compiler warning
};
I2CRB requestBlock;
FSH *_deviceName;
};
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
// file as the class declaration (above). Otherwise it won't compile!
// Constructor
template <class T>
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) :
IODevice(firstVpin, nPins)
{
if (_nPins > (int)sizeof(T)*8) _nPins = sizeof(T)*8; // Ensure nPins is consistent with the number of bits in T
_deviceName = deviceName;
_I2CAddress = i2cAddress;
_gpioInterruptPin = interruptPin;
_hasCallback = true;
// Add device to list of devices.
addDevice(this);
_portMode = 0; // default to input mode
_portPullup = -1; // default to pullup enabled
_portInputState = -1; // default to all inputs high (inactive)
_portInUse = 0; // No ports in use initially.
}
template <class T>
void GPIOBase<T>::_begin() {
// Configure pin used for GPIO extender notification of change (if allocated)
if (_gpioInterruptPin >= 0)
pinMode(_gpioInterruptPin, INPUT_PULLUP);
I2CManager.begin();
I2CManager.setClock(400000);
if (I2CManager.exists(_I2CAddress)) {
#if defined(DIAG_IO)
_display();
#endif
_setupDevice();
_deviceState = DEVSTATE_NORMAL;
} else {
DIAG(F("%S I2C:%s Device not detected"), _deviceName, _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
// Configuration parameters for inputs:
// params[0]: enable pullup
template <class T>
bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_INPUT) return false;
if (paramCount == 0 || paramCount > 1) return false;
bool pullup = params[0];
int pin = vpin - _firstVpin;
#ifdef DIAG_IO
DIAG(F("%S I2C:%s Config Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, pullup);
#endif
uint16_t mask = 1 << pin;
if (pullup)
_portPullup |= mask;
else
_portPullup &= ~mask;
// Mark that port has been accessed
_portInUse |= mask;
// Set input mode
_portMode &= ~mask;
// Call subclass's virtual function to write to device
_writePortModes();
_writePullups();
// Port change will be notified on next loop entry.
return true;
}
// Periodically read the input port
template <class T>
void GPIOBase<T>::_loop(unsigned long currentMicros) {
T lastPortStates = _portInputState;
if (_deviceState == DEVSTATE_SCANNING && !requestBlock.isBusy()) {
uint8_t status = requestBlock.status;
if (status == I2C_STATUS_OK) {
_deviceState = DEVSTATE_NORMAL;
} else {
_deviceState = DEVSTATE_FAILED;
DIAG(F("%S I2C:%s Error:%d %S"), _deviceName, _I2CAddress.toString(), status,
I2CManager.getErrorMessage(status));
}
_processCompletion(status);
// Set unused pin and write mode pin value to 1
_portInputState |= ~_portInUse | _portMode;
// Scan for changes in input states and invoke callback (if present)
T differences = lastPortStates ^ _portInputState;
if (differences && IONotifyCallback::hasCallback()) {
// Scan for differences bit by bit
T mask = 1;
for (int pin=0; pin<_nPins; pin++) {
if (differences & mask) {
// Change detected.
IONotifyCallback::invokeAll(_firstVpin+pin, (_portInputState & mask) == 0);
}
mask <<= 1;
}
}
#ifdef DIAG_IO
if (differences)
DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
#endif
}
// Check if interrupt configured. If not, or if it is active (pulled down), then
// initiate a scan.
if (_gpioInterruptPin < 0 || !digitalRead(_gpioInterruptPin)) {
// TODO: Could suppress reads if there are no pins configured as inputs!
// Read input
if (_deviceState == DEVSTATE_NORMAL) {
_readGpioPort(false); // Initiate non-blocking read
_deviceState= DEVSTATE_SCANNING;
}
}
// Delay next entry until tick elapsed.
delayUntil(currentMicros + _portTickTime);
}
template <class T>
void GPIOBase<T>::_display() {
DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(),
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
template <class T>
void GPIOBase<T>::_write(VPIN vpin, int value) {
int pin = vpin - _firstVpin;
T mask = 1 << pin;
#ifdef DIAG_IO
DIAG(F("%S I2C:%s Write Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, value);
#endif
// Set port mode output if currently not output mode
if (!(_portMode & mask)) {
_portInUse |= mask;
_portMode |= mask;
_writePortModes();
}
// Update port output state
if (value)
_portOutputState |= mask;
else
_portOutputState &= ~mask;
// Call subclass's virtual function to write to device.
return _writeGpioPort();
}
template <class T>
int GPIOBase<T>::_read(VPIN vpin) {
int pin = vpin - _firstVpin;
T mask = 1 << pin;
// Set port mode to input if currently output or first use
if ((_portMode | ~_portInUse) & mask) {
_portMode &= ~mask;
_portInUse |= mask;
_writePullups();
_writePortModes();
// Port won't have been read yet, so read it now.
_readGpioPort();
// Set unused pin and write mode pin value to 1
_portInputState |= ~_portInUse | _portMode;
#ifdef DIAG_IO
DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
#endif
}
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
}
#endif

View File

@@ -1,250 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 driver provides a more immediate interface into the OLED display
* than the one installed through the config.h file. When an LCD(...) call
* is made, the text is output immediately to the specified display line,
* without waiting for the next 2.5 second refresh. However, if the line
* specified is off the screen then the text in the bottom line will be
* overwritten. There is however a special case that if line 255 is specified,
* the existing text will scroll up and the new line added to the bottom
* line of the screen.
*
* To install, use the following command in myHal.cpp:
*
* HALDisplay<OLED>::create(address, width, height);
*
* where address is the I2C address of the OLED display (0x3c or 0x3d),
* width is the width in pixels, and height is the height in pixels.
*
* Valid width and height are 128x32 (SSD1306 controller),
* 128x64 (SSD1306) and 132x64 (SH1106). The driver uses
* a 5x7 character set in a 6x8 pixel cell.
*
* OR
*
* HALDisplay<LiquidCrystal>::create(address, width, height);
*
* where address is the I2C address of the LCD display (0x27 typically),
* width is the width in characters (16 or 20 typically),
* and height is the height in characters (2 or 4 typically).
*/
#ifndef IO_HALDisplay_H
#define IO_HALDisplay_H
#include "IODevice.h"
#include "DisplayInterface.h"
#include "SSD1306Ascii.h"
#include "LiquidCrystal_I2C.h"
#include "version.h"
typedef SSD1306AsciiWire OLED;
typedef LiquidCrystal_I2C LiquidCrystal;
template <class T>
class HALDisplay : public IODevice, public DisplayInterface {
private:
// Here we define the device-specific variables.
uint8_t _height; // in pixels
uint8_t _width; // in pixels
T *_displayDriver;
uint8_t _rowNo = 0; // Row number being written by caller
uint8_t _colNo = 0; // Position in line being written by caller
uint8_t _numRows;
uint8_t _numCols;
char *_buffer = NULL;
uint8_t *_rowGeneration = NULL;
uint8_t *_lastRowGeneration = NULL;
uint8_t _rowNoToScreen = 0;
uint8_t _charPosToScreen = 0;
DisplayInterface *_nextDisplay = NULL;
public:
// Static function to handle "HALDisplay::create(...)" calls.
static void create(I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height);
}
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height);
}
protected:
// Constructor
HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
_displayDriver = new T(i2cAddress, width, height);
_I2CAddress = i2cAddress;
_width = width;
_height = height;
_numCols = _displayDriver->getNumCols();
_numRows = _displayDriver->getNumRows();
_charPosToScreen = _numCols;
// Allocate arrays
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
// Fill buffer with spaces
memset(_buffer, ' ', _numCols*_numRows);
_displayDriver->clearNative();
// Add device to list of HAL devices (not necessary but allows
// status to be displayed using <D HAL SHOW> and device to be
// reinitialised using <D HAL RESET>).
IODevice::addDevice(this);
// Also add this display to list of display handlers
DisplayInterface::addDisplay(displayNo);
// Is this the main display?
if (displayNo == 0) {
// Set first two lines on screen
this->setRow(displayNo, 0);
print(F("DCC-EX v"));
print(F(VERSION));
setRow(displayNo, 1);
print(F("Lic GPLv3"));
}
}
void screenUpdate() {
// Loop through the buffer and if a row has changed
// (rowGeneration[row] is changed) then start writing the
// characters from the buffer, one character per entry,
// to the screen until that row has been refreshed.
// First check if the OLED driver is still busy from a previous
// call. If so, don't to anything until the next entry.
if (!_displayDriver->isBusy()) {
// Check if we've just done the end of a row
if (_charPosToScreen >= _numCols) {
// Move to next line
if (++_rowNoToScreen >= _numRows)
_rowNoToScreen = 0; // Wrap to first row
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
// Row content has changed, so start outputting it
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
_displayDriver->setRowNative(_rowNoToScreen);
_charPosToScreen = 0; // Prepare to output first character on next entry
} else {
// Row not changed, don't bother writing it.
}
} else {
// output character at current position
_displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
}
}
return;
}
/////////////////////////////////////////////////
// IODevice Class Member Overrides
/////////////////////////////////////////////////
// Device-specific initialisation
void _begin() override {
// Initialise device
if (_displayDriver->begin()) {
_display();
// Force all rows to be redrawn
for (uint8_t row=0; row<_numRows; row++)
_rowGeneration[row]++;
// Start with top line (looks better).
// The numbers will wrap round on the first loop2 entry.
_rowNoToScreen = _numRows;
_charPosToScreen = _numCols;
}
}
void _loop(unsigned long) override {
screenUpdate();
}
// Display information about the device.
void _display() {
DIAG(F("HALDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString());
}
/////////////////////////////////////////////////
// DisplayInterface functions
//
/////////////////////////////////////////////////
public:
void _displayLoop() override {
screenUpdate();
}
// Position on nominated line number (0 to number of lines -1)
// Clear the line in the buffer ready for updating
// The displayNo referenced here is remembered and any following
// calls to write() will be directed to that display.
void _setRow(byte line) override {
if (line == 255) {
// LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") -
// scroll the contents of the buffer and put the new line
// at the bottom of the screen
for (int row=1; row<_numRows; row++) {
strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols);
_rowGeneration[row-1]++;
}
line = _numRows-1;
} else if (line >= _numRows)
line = _numRows - 1; // Overwrite bottom line.
_rowNo = line;
// Fill line with blanks
for (_colNo = 0; _colNo < _numCols; _colNo++)
_buffer[_rowNo*_numCols+_colNo] = ' ';
_colNo = 0;
// Mark that the buffer has been touched. It will be
// sent to the screen on the next loop entry, by which time
// the line should have been written to the buffer.
_rowGeneration[_rowNo]++;
}
// Write one character to the screen referenced in the last setRow() call.
virtual size_t _write(uint8_t c) override {
// Write character to buffer (if there's space)
if (_colNo < _numCols) {
_buffer[_rowNo*_numCols+_colNo++] = c;
}
return 1;
}
// Write blanks to all of the screen buffer
void _clear() {
// Clear buffer
memset(_buffer, ' ', _numCols*_numRows);
_colNo = 0;
_rowNo = 0;
}
};
#endif // IO_HALDisplay_H

View File

@@ -1,189 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* The HC-SR04 module has an ultrasonic transmitter (40kHz) and a receiver.
* It is operated through two signal pins. When the transmit pin is set to 1
* for 10us, on the falling edge the transmitter sends a short transmission of
* 8 pulses (like a sonar 'ping'). This is reflected off objects and received
* by the receiver. A pulse is sent on the receive pin whose length is equal
* to the delay between the transmission of the pulse and the detection of
* its echo. The distance of the reflecting object is calculated by halving
* the time (to allow for the out and back distance), then multiplying by the
* speed of sound (assumed to be constant).
*
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
* the length of the received pulse. If the calculated distance is less than
* the threshold, the output state returned by a read() call changes to 1. If
* the distance is greater than the threshold plus a hysteresis margin, the
* output changes to 0. The device also supports readAnalogue(), which returns
* the measured distance in cm, or 32767 if the distance exceeds the
* offThreshold.
*
* It might be thought that the measurement would be more reliable if interrupts
* were disabled while the pulse is being timed. However, this would affect
* other functions in the CS so the measurement is being performed with
* interrupts enabled. Also, we could use an interrupt pin in the Arduino for
* the timing, but the same consideration applies. In any case, the DCC
* interrupt occurs once every 58us, so any IRC code is much faster than that.
* And 58us corresponds to 1cm in the calculation, so the effect of
* interrupts is negligible.
*
* Note: The timing accuracy required for measuring the pulse length means that
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
* provide the required accuracy.
*/
#ifndef IO_HCSR04_H
#define IO_HCSR04_H
#include "IODevice.h"
class HCSR04 : public IODevice {
private:
// pins must be arduino GPIO pins, not extender pins or HAL pins.
int _trigPin = -1;
int _echoPin = -1;
// Thresholds for setting active state in cm.
uint8_t _onThreshold; // cm
uint8_t _offThreshold; // cm
// Last measured distance in cm.
uint16_t _distance;
// Active=1/inactive=0 state
uint8_t _value = 0;
// Factor for calculating the distance (cm) from echo time (ms).
// Based on a speed of sound of 345 metres/second.
const uint16_t factor = 58; // ms/cm
public:
// Static create function provides alternative way to create object
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
if (checkNoOverlap(vpin))
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
}
protected:
// Constructor perfroms static initialisation of the device object
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
_firstVpin = vpin;
_nPins = 1;
_trigPin = trigPin;
_echoPin = echoPin;
_onThreshold = onThreshold;
_offThreshold = offThreshold;
addDevice(this);
}
// _begin function called to perform dynamic initialisation of the device
void _begin() override {
pinMode(_trigPin, OUTPUT);
pinMode(_echoPin, INPUT);
ArduinoPins::fastWriteDigital(_trigPin, 0);
#if defined(DIAG_IO)
_display();
#endif
}
// _read function - just return _value (calculated in _loop).
int _read(VPIN vpin) override {
(void)vpin; // avoid compiler warning
return _value;
}
int _readAnalogue(VPIN vpin) override {
(void)vpin; // avoid compiler warning
return _distance;
}
// _loop function - read HC-SR04 once every 50 milliseconds.
void _loop(unsigned long currentMicros) override {
read_HCSR04device();
// Delay next loop entry until 50ms have elapsed.
delayUntil(currentMicros + 50000UL);
}
void _display() override {
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
}
private:
// This polls the HC-SR04 device by sending a pulse and measuring the duration of
// the pulse observed on the receive pin. In order to be kind to the rest of the CS
// software, no interrupts are used and interrupts are not disabled. The pulse duration
// is measured in a loop, using the micros() function. Therefore, interrupts from other
// sources may affect the result. However, interrupts response code in CS typically takes
// much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm
// in the HC-SR04.
// To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
// greater than the offThreshold.
//
void read_HCSR04device() {
// uint16 enough to time up to 65ms
uint16_t startTime, waitTime = 0, currentTime, maxTime;
// If receive pin is still set on from previous call, abort the read.
if (ArduinoPins::fastReadDigital(_echoPin))
return;
// Send 10us pulse to trigger transmitter
ArduinoPins::fastWriteDigital(_trigPin, 1);
delayMicroseconds(10);
ArduinoPins::fastWriteDigital(_trigPin, 0);
// Wait for receive pin to be set
startTime = currentTime = micros();
maxTime = factor * _offThreshold * 2;
while (!ArduinoPins::fastReadDigital(_echoPin)) {
// lastTime = currentTime;
currentTime = micros();
waitTime = currentTime - startTime;
if (waitTime > maxTime) {
// Timeout waiting for pulse start, abort the read
return;
}
}
// Wait for receive pin to reset, and measure length of pulse
startTime = currentTime = micros();
maxTime = factor * _offThreshold;
while (ArduinoPins::fastReadDigital(_echoPin)) {
currentTime = micros();
waitTime = currentTime - startTime;
// If pulse is too long then set return value to zero,
// and finish without waiting for end of pulse.
if (waitTime > maxTime) {
// Pulse length longer than maxTime, reset value.
_value = 0;
_distance = 32767;
return;
}
}
// Check if pulse length is below threshold, if so set value.
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance);
_distance = waitTime / factor; // in centimetres
if (_distance < _onThreshold)
_value = 1;
}
};
#endif //IO_HCSR04_H

View File

@@ -1,101 +0,0 @@
/*
* © 2022 Paul M Antoine
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 IO_MCP23008_H
#define IO_MCP23008_H
#include "IO_GPIOBase.h"
class MCP23008 : public GPIOBase<uint8_t> {
public:
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin);
}
private:
// Constructor
MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) {
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_GPIO;
}
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState);
}
void _writePullups() override {
// Set pullups only for in-use pins. This prevents pullup being set for a pin that
// is intended for use as an output but hasn't been written to yet.
I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup & _portInUse);
}
void _writePortModes() override {
// Write 0 to IODIR for in-use pins that are outputs, 1 for others.
uint8_t temp = ~(_portMode & _portInUse);
I2CManager.write(_I2CAddress, 2, REG_IODIR, temp);
// Enable interrupt-on-change for in-use pins that are inputs (_portMode=0)
temp = ~_portMode & _portInUse;
I2CManager.write(_I2CAddress, 2, REG_INTCON, 0x00);
I2CManager.write(_I2CAddress, 2, REG_GPINTEN, temp);
}
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer;
I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO);
_portInputState = buffer | _portMode;
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = inputBuffer[0] | _portMode;
else
_portInputState = 0xff;
}
void _setupDevice() override {
// IOCON is set ODR=1 (open drain shared interrupt pin), INTPOL=0 (active-Low)
I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x04);
_writePortModes();
_writePullups();
_writeGpioPort();
}
uint8_t inputBuffer[1];
uint8_t outputBuffer[1];
enum {
// Register definitions for MCP23008
REG_IODIR=0x00,
REG_GPINTEN=0x02,
REG_INTCON=0x04,
REG_IOCON=0x05,
REG_GPPU=0x06,
REG_GPIO=0x09,
};
};
#endif

View File

@@ -1,111 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 io_mcp23017_h
#define io_mcp23017_h
#include "IO_GPIOBase.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for MCP23017 16-bit I/O expander.
*/
class MCP23017 : public GPIOBase<uint16_t> {
public:
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin);
}
private:
// Constructor
MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin)
{
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_GPIOA;
}
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
}
void _writePullups() override {
// Set pullups only for in-use pins. This prevents pullup being set for a pin that
// is intended for use as an output but hasn't been written to yet.
uint16_t temp = _portPullup & _portInUse;
I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8);
}
void _writePortModes() override {
// Write 0 to IODIR for in-use pins that are outputs, 1 for others.
uint16_t temp = ~(_portMode & _portInUse);
I2CManager.write(_I2CAddress, 3, REG_IODIRA, temp, temp>>8);
// Enable interrupt for in-use pins which are inputs (_portMode=0)
temp = ~_portMode & _portInUse;
I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00);
I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8);
}
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA);
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode;
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
else
_portInputState = 0xffff;
}
void _setupDevice() override {
// IOCON is set MIRROR=1, ODR=1 (open drain shared interrupt pin)
I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x44);
_writePortModes();
_writePullups();
_writeGpioPort();
}
uint8_t inputBuffer[2];
uint8_t outputBuffer[1];
enum {
REG_IODIRA = 0x00,
REG_IODIRB = 0x01,
REG_GPINTENA = 0x04,
REG_GPINTENB = 0x05,
REG_INTCONA = 0x08,
REG_INTCONB = 0x09,
REG_IOCON = 0x0A,
REG_GPPUA = 0x0C,
REG_GPPUB = 0x0D,
REG_GPIOA = 0x12,
REG_GPIOB = 0x13,
};
};
#endif

View File

@@ -1,279 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
// REGISTER ADDRESSES
static const byte PCA9685_MODE1=0x00; // Mode Register
static const byte PCA9685_FIRST_SERVO=0x06; /** low byte first servo register ON*/
static const byte PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */
// MODE1 bits
static const byte MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */
static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */
static const byte MODE1_RESTART=0x80; /**< Restart enabled */
static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
// Predeclare helper function
static void writeRegister(byte address, byte reg, byte value);
// Create device driver instance.
void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency);
}
// Configure a port on the PCA9685.
bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false;
#if DIAG_IO >= 3
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]);
#endif
int8_t pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
s = _servoData[pin];
if (!s) return false; // Check for failed memory allocation
}
s->activePosition = params[0];
s->inactivePosition = params[1];
s->profile = params[2];
s->duration = params[3];
int state = params[4];
if (state != -1) {
// Position servo to initial state
_writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition, 0, 0);
}
return true;
}
// Constructor
PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin;
_nPins = (nPins > 16) ? 16 : nPins;
_I2CAddress = i2cAddress;
// Calculate prescaler value for PWM clock
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
// To save RAM, space for servo configuration is not allocated unless a pin is used.
// Initialise the pointers to NULL.
for (int i=0; i<_nPins; i++)
_servoData[i] = NULL;
addDevice(this);
// Initialise structure used for setting pulse rate
requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer));
}
// Device-specific initialisation
void PCA9685::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C
// In reality, other devices including the Arduino will limit
// the clock speed to a lower rate.
// Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow
// the PWM oscillator to get running. However, we don't do any specific wait, as there's
// plenty of other stuff to do before we will send a command.
#if defined(DIAG_IO)
_display();
#endif
} else
_deviceState = DEVSTATE_FAILED;
}
// Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void PCA9685::_write(VPIN vpin, int value) {
#if DIAG_IO >= 3
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
#endif
int pin = vpin - _firstVpin;
if (value) value = 1;
struct ServoData *s = _servoData[pin];
if (s != NULL) {
// Use configured parameters
_writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
} else {
/* simulate digital pin on PWM */
_writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
}
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
// Profile is as follows:
// Bit 7: 0=Set PWM to 0% to power off servo motor when finished
// 1=Keep PWM pulses on (better when using PWM to drive an LED)
// Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds)
// 1 (Fast) Move servo in 0.5 seconds
// 2 (Medium) Move servo in 1.0 seconds
// 3 (Slow) Move servo in 2.0 seconds
// 4 (Bounce) Servo 'bounces' at extremes.
//
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#if DIAG_IO >= 3
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
// Servo pin not configured, so configure now using defaults
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
if (s == NULL) return; // Check for memory allocation failure
s->activePosition = 4095;
s->inactivePosition = 0;
s->currentPosition = value;
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
}
// Animated profile. Initiate the appropriate action.
s->currentProfile = profile;
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
profileValue==Slow ? 40 : // 2.0 seconds
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
s->stepNumber = 0;
s->toPosition = value;
s->fromPosition = s->currentPosition;
}
// _read returns true if the device is currently in executing an animation,
// changing the output over a period of time.
int PCA9685::_read(VPIN vpin) {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL)
return false; // No structure means no animation!
else
return (s->stepNumber < s->numSteps);
}
void PCA9685::_loop(unsigned long currentMicros) {
for (int pin=0; pin<_nPins; pin++) {
updatePosition(pin);
}
delayUntil(currentMicros + refreshInterval * 1000UL);
}
// Private function to reposition servo
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
void PCA9685::updatePosition(uint8_t pin) {
struct ServoData *s = _servoData[pin];
if (s == NULL) return; // No pin configuration/state data
if (s->numSteps == 0) return; // No animation in progress
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
// Go straight to end of sequence, output final position.
s->stepNumber = s->numSteps-1;
}
if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo
s->stepNumber++;
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
} else {
// All other profiles - calculate step by linear interpolation between from and to positions.
s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition);
}
// Send servo command
writeDevice(pin, s->currentPosition);
} else if (s->stepNumber < s->numSteps + _catchupSteps) {
// We've finished animation, wait a little to allow servo to catch up
s->stepNumber++;
} else if (s->stepNumber == s->numSteps + _catchupSteps
&& s->currentPosition != 0) {
#ifdef IO_SWITCH_OFF_SERVO
if ((s->currentProfile & NoPowerOff) == 0) {
// Wait has finished, so switch off PWM to prevent annoying servo buzz
writeDevice(pin, 0);
}
#endif
s->numSteps = 0; // Done now.
}
}
// writeDevice takes a pin in range 0 to _nPins-1 within the device, and a value
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void PCA9685::writeDevice(uint8_t pin, int value) {
#ifdef DIAG_IO
DIAG(F("PCA9685 I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif
// Wait for previous request to complete
uint8_t status = requestBlock.wait();
if (status != I2C_STATUS_OK) {
_deviceState = DEVSTATE_FAILED;
DIAG(F("PCA9685 I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
} else {
// Set up new request.
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
outputBuffer[1] = 0;
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
outputBuffer[3] = value & 0xff;
outputBuffer[4] = value >> 8;
I2CManager.queueRequest(&requestBlock);
}
}
// Display details of this device.
void PCA9685::_display() {
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
// Internal helper function for this device
static void writeRegister(byte address, byte reg, byte value) {
I2CManager.write(address, 2, reg, value);
}
// Profile for a bouncing signal or turnout
// The profile below is in the range 0-100% and should be combined with the desired limits
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
const uint8_t FLASH PCA9685::_bounceProfile[30] =
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};

View File

@@ -1,170 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 driver performs the basic interface between the HAL and an
* I2C-connected PCA9685 16-channel PWM module. When requested, it
* commands the device to set the PWM mark-to-period ratio accordingly.
* The call to IODevice::writeAnalogue(vpin, value) specifies the
* desired value in the range 0-4095 (0=0% and 4095=100%).
*
* This driver can be used for simple servo control by writing values between
* about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250
* for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length).
* A value of zero will switch off the servo. To create the device, use
* the following syntax:
*
* PCA9685_basic::create(vpin, npins, i2caddress);
*
* For LED control, a value of 0 is fully off, and 4095 is fully on. It is
* recommended, to reduce flicker of LEDs, that the frequency be configured
* to a value higher than the default of 50Hz. To do this, create the device
* as follows, for a frequency of 200Hz.:
*
* PCA9685_basic::create(vpin, npins, i2caddress, 200);
*
*/
#ifndef PCA9685_BASIC_H
#define PCA9685_BASIC_H
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
/*
* IODevice subclass for PCA9685 16-channel PWM module.
*/
class PCA9685pwm : public IODevice {
public:
// Create device driver instance.
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency);
}
private:
// structures for setting up non-blocking writes to PWM controller
I2CRB requestBlock;
uint8_t outputBuffer[5];
uint16_t prescaler;
// REGISTER ADDRESSES
const uint8_t PCA9685_MODE1=0x00; // Mode Register
const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/
const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */
// MODE1 bits
const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */
const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */
const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */
const uint32_t FREQUENCY_OSCILLATOR=25000000; /** 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
// Constructor
PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
_firstVpin = firstVpin;
_nPins = (nPins>16) ? 16 : nPins;
_I2CAddress = i2cAddress;
if (frequency > 1526) frequency = 1526;
else if (frequency < 24) frequency = 24;
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
addDevice(this);
// Initialise structure used for setting pulse rate
requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer));
}
// Device-specific initialisation
void _begin() override {
I2CManager.begin();
I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C
// In reality, other devices including the Arduino will limit
// the clock speed to a lower rate.
// Initialise I/O module here.
if (I2CManager.exists(_I2CAddress)) {
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
// In theory, we should wait 500us before sending any other commands to each device, to allow
// the PWM oscillator to get running. However, we don't do any specific wait, as there's
// plenty of other stuff to do before we will send a command.
#if defined(DIAG_IO)
_display();
#endif
} else
_deviceState = DEVSTATE_FAILED;
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
//
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
(void)param1; (void)param2; // suppress compiler warning
#if DIAG_IO >= 3
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
writeDevice(pin, value);
}
// Display details of this device.
void _display() override {
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
// writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
void writeDevice(uint8_t pin, int value) {
#if DIAG_IO >= 3
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
#endif
// Wait for previous request to complete
uint8_t status = requestBlock.wait();
if (status != I2C_STATUS_OK) {
_deviceState = DEVSTATE_FAILED;
DIAG(F("PCA9685pwm I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
} else {
// Set up new request.
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
outputBuffer[1] = 0;
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
outputBuffer[3] = value & 0xff;
outputBuffer[4] = value >> 8;
I2CManager.queueRequest(&requestBlock);
}
}
// Internal helper function for this device
static void writeRegister(I2CAddress address, uint8_t reg, uint8_t value) {
I2CManager.write(address, 2, reg, value);
}
};
#endif

View File

@@ -1,104 +0,0 @@
/*
* © 2022 Paul M Antoine
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* The PCF8574 is a simple device; it only has one register. The device
* input/output mode and pullup are configured through this, and the
* output state is written and the input state read through it too.
*
* This is accomplished by having a weak resistor in series with the output,
* and a read-back of the other end of the resistor. As an output, the
* pin state is set to 1 or 0, and the output voltage goes to +5V or 0V
* (through the weak resistor).
*
* In order to use the pin as an input, the output is written as
* a '1' in order to pull up the resistor. Therefore the input will be
* 1 unless the pin is pulled down externally, in which case it will be 0.
*
* As a consequence of this approach, it is not possible to use the device for
* inputs without pullups.
*/
#ifndef IO_PCF8574_H
#define IO_PCF8574_H
#include "IO_GPIOBase.h"
class PCF8574 : public GPIOBase<uint8_t> {
public:
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin);
}
private:
PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin)
{
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
}
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
// The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
// Unused pins are driven '0'.
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse);
}
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
// Therefore, writing '1' in _writePortModes is enough to set the module to input mode
// and enable pull-up.
void _writePullups() override { }
void _writePortModes() override {
_writeGpioPort();
}
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
// When not in immediate mode, it initiates a request using the request block and returns.
// When the request completes, _processCompletion finishes the operation.
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[1];
I2CManager.read(_I2CAddress, buffer, 1);
_portInputState = buffer[0] | _portMode;
} else {
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = inputBuffer[0] | _portMode;
else
_portInputState = 0xff;
}
// Set up device ports
void _setupDevice() override {
_writePortModes();
}
uint8_t inputBuffer[1];
};
#endif

View File

@@ -1,109 +0,0 @@
/*
* © 2023, Paul Antoine, and Discord user @ADUBOURG
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* The PCF8575 is a simple device; it only has one register. The device
* input/output mode and pullup are configured through this, and the
* output state is written and the input state read through it too.
*
* This is accomplished by having a weak resistor in series with the output,
* and a read-back of the other end of the resistor. As an output, the
* pin state is set to 1 or 0, and the output voltage goes to +5V or 0V
* (through the weak resistor).
*
* In order to use the pin as an input, the output is written as
* a '1' in order to pull up the resistor. Therefore the input will be
* 1 unless the pin is pulled down externally, in which case it will be 0.
*
* As a consequence of this approach, it is not possible to use the device for
* inputs without pullups.
*/
#ifndef IO_PCF8575_H
#define IO_PCF8575_H
#include "IO_GPIOBase.h"
#include "FSH.h"
class PCF8575 : public GPIOBase<uint16_t> {
public:
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin);
}
private:
PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin)
{
requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer));
}
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
// The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
// Unused pins are driven '0'.
void _writeGpioPort() override {
uint16_t bits = (_portOutputState | ~_portMode) & _portInUse;
I2CManager.write(_I2CAddress, 2, bits, bits>>8);
}
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
// Therefore, writing '1' in _writePortModes is enough to set the module to input mode
// and enable pull-up.
void _writePullups() override { }
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
void _writePortModes() override {
_writeGpioPort();
}
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
// When not in immediate mode, it initiates a request using the request block and returns.
// When the request completes, _processCompletion finishes the operation.
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2);
_portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode;
} else {
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
else
_portInputState = 0xffff;
}
// Set up device ports
void _setupDevice() override {
_writePortModes();
_writeGpioPort();
_writePullups();
}
uint8_t inputBuffer[2];
};
#endif

View File

@@ -1,126 +0,0 @@
/*
* © 2022, Peter Cole. All rights reserved.
*
* This file is part of EX-CommandStation
*
* 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/>.
*/
/*
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
*
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
*
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
* can be tested in EX-RAIL with:
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
*
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
*
* Refer to the documentation for further information including the valid activities and examples.
*/
#ifndef IO_ROTARYENCODER_H
#define IO_ROTARYENCODER_H
#include "EXRAIL2.h"
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
class RotaryEncoder : public IODevice {
public:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
}
private:
// Initiate the device
void _begin() {
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
byte _getVersion[1] = {RE_VER};
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
_buffer[0] = RE_OP;
I2CManager.write(_I2CAddress, _buffer, 1);
#ifdef DIAG_IO
_display();
#endif
} else {
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
I2CManager.read(_I2CAddress, _buffer, 1);
_position = _buffer[0];
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin,1);
} else {
RMFT2::changeEvent(_firstVpin,0);
}
#endif
delayUntil(currentMicros + 100000);
}
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
byte _feedbackBuffer[2] = {RE_OP, value};
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
void _display() override {
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _buffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
enum {
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
RE_OP = 0xA1, // Flag for normal operation
};
};
#endif

View File

@@ -1,33 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 "IO_Servo.h"
#include "FSH.h"
// Profile for a bouncing signal or turnout
// The profile below is in the range 0-100% and should be combined with the desired limits
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
//
// Note: This has been put into its own .CPP file to ensure that duplicates aren't created
// if the IO_Servo.h library is #include'd in multiple source files.
//
const uint8_t FLASH Servo::_bounceProfile[30] =
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};

View File

@@ -1,298 +0,0 @@
/*
* © 2023, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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 device is a layered device which is designed to sit on top of another
* device. The underlying device class is expected to accept writeAnalogue calls
* which will normally cause some physical movement of something. The device may be a servo,
* a motor or some other kind of positioner, and the something might be a turnout,
* a semaphore signal or something else. One user has used this capability for
* moving a figure along the platform on their layout!
*
* Example of use:
* In myHal.cpp,
*
* #include "IO_Servo.h"
* ...
* PCA9685::create(100,16,0x40); // First create the hardware interface device
* Servo::create(300,16,100); // Then create the higher level device which
* // references pins 100-115 or a subset of them.
*
* Then any reference to pins 300-315 will cause the servo driver to send output
* PWM commands to the corresponding PCA9685 driver pins 100-115. The PCA9685 driver may
* be substituted with any other driver which provides analogue output
* capability, e.g. EX-IOExpander devices, as long as they are capable of interpreting
* the writeAnalogue() function calls.
*/
#include "IODevice.h"
#ifndef IO_SERVO_H
#define IO_SERVO_H
#include "I2CManager.h"
#include "DIAG.h"
class Servo : IODevice {
public:
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
// Create device driver instance.
static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin=VPIN_NONE) {
new Servo(firstVpin, nPins, firstSlavePin);
}
private:
VPIN _firstSlavePin;
IODevice *_slaveDevice = NULL;
struct ServoData {
uint16_t activePosition : 12; // Config parameter
uint16_t inactivePosition : 12; // Config parameter
uint16_t currentPosition : 12;
uint16_t fromPosition : 12;
uint16_t toPosition : 12;
uint8_t profile; // Config parameter
uint16_t stepNumber; // Index of current step (starting from 0)
uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
uint8_t currentProfile; // profile being used for current animation.
uint16_t duration; // time (tenths of a second) for animation to complete.
}; // 14 bytes per element, i.e. per pin in use
struct ServoData *_servoData [16];
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
static const uint8_t FLASH _bounceProfile[30];
const unsigned int refreshInterval = 50; // refresh every 50ms
// Configure a port on the Servo.
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
if (_deviceState == DEVSTATE_FAILED) return false;
if (configType != CONFIGURE_SERVO) return false;
if (paramCount != 5) return false;
#ifdef DIAG_IO
DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
vpin, params[0], params[1], params[2], params[3], params[4]);
#endif
int8_t pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
s = _servoData[pin];
if (!s) return false; // Check for failed memory allocation
}
s->activePosition = params[0];
s->inactivePosition = params[1];
s->profile = params[2];
s->duration = params[3];
int state = params[4];
if (state != -1) {
// Position servo to initial state
writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition);
}
return true;
}
// Constructor
Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin = VPIN_NONE) {
_firstVpin = firstVpin;
_nPins = (nPins > 16) ? 16 : nPins;
if (firstSlavePin == VPIN_NONE)
_firstSlavePin = firstVpin;
else
_firstSlavePin = firstSlavePin;
// To save RAM, space for servo configuration is not allocated unless a pin is used.
// Initialise the pointers to NULL.
for (int i=0; i<_nPins; i++)
_servoData[i] = NULL;
// Get reference to slave device.
_slaveDevice = findDevice(_firstSlavePin);
if (!_slaveDevice) {
DIAG(F("Servo: Slave device not found on pins %d-%d"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
DIAG(F("Servo: Slave device does not cover all pins %d-%d"),
_firstSlavePin, _firstSlavePin+_nPins-1);
_deviceState = DEVSTATE_FAILED;
}
addDevice(this, _slaveDevice); // Link device ahead of slave device to intercept requests
}
// Device-specific initialisation
void _begin() override {
#if defined(DIAG_IO)
_display();
#endif
}
// Device-specific write function, invoked from IODevice::write().
// For this function, the configured profile is used.
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value);
#endif
int pin = vpin - _firstVpin;
if (value) value = 1;
struct ServoData *s = _servoData[pin];
if (s != NULL) {
// Use configured parameters
writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
} else {
/* simulate digital pin on PWM */
writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
}
}
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
// Profile is as follows:
// Bit 7: 0=Set output to 0% to power off servo motor when finished
// 1=Keep output at final position (better with LEDs, which will stay lit)
// Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds)
// 1 (Fast) Move servo in 0.5 seconds
// 2 (Medium) Move servo in 1.0 seconds
// 3 (Slow) Move servo in 2.0 seconds
// 4 (Bounce) Servo 'bounces' at extremes.
// Duration is in deciseconds (tenths of a second) and defaults to 0.
//
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
#ifdef DIAG_IO
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
#endif
if (_deviceState == DEVSTATE_FAILED) return;
int pin = vpin - _firstVpin;
if (value > 4095) value = 4095;
else if (value < 0) value = 0;
struct ServoData *s = _servoData[pin];
if (s == NULL) {
// Servo pin not configured, so configure now using defaults
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
if (s == NULL) return; // Check for memory allocation failure
s->activePosition = 4095;
s->inactivePosition = 0;
s->currentPosition = value;
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
}
// Animated profile. Initiate the appropriate action.
s->currentProfile = profile;
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
profileValue==Slow ? 40 : // 2.0 seconds
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
s->stepNumber = 0;
s->toPosition = value;
s->fromPosition = s->currentPosition;
}
// _read returns true if the device is currently in executing an animation,
// changing the output over a period of time.
int _read(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
int pin = vpin - _firstVpin;
struct ServoData *s = _servoData[pin];
if (s == NULL)
return false; // No structure means no animation!
else
return (s->stepNumber < s->numSteps);
}
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return;
for (int pin=0; pin<_nPins; pin++) {
updatePosition(pin);
}
delayUntil(currentMicros + refreshInterval * 1000UL);
}
// Private function to reposition servo
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
void updatePosition(uint8_t pin) {
struct ServoData *s = _servoData[pin];
if (s == NULL) return; // No pin configuration/state data
if (s->numSteps == 0) return; // No animation in progress
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
// Go straight to end of sequence, output final position.
s->stepNumber = s->numSteps-1;
}
if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo
s->stepNumber++;
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash
uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
} else {
// All other profiles - calculate step by linear interpolation between from and to positions.
s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition);
}
// Send servo command to output driver
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, s->currentPosition);
} else if (s->stepNumber < s->numSteps + _catchupSteps) {
// We've finished animation, wait a little to allow servo to catch up
s->stepNumber++;
} else if (s->stepNumber == s->numSteps + _catchupSteps
&& s->currentPosition != 0) {
#ifdef IO_SWITCH_OFF_SERVO
if ((s->currentProfile & NoPowerOff) == 0) {
// Wait has finished, so switch off output driver to avoid servo buzz.
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, 0);
}
#endif
s->numSteps = 0; // Done now.
}
}
// Display details of this device.
void _display() override {
DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"),
(int)_firstVpin, (int)_firstVpin+_nPins-1,
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
};
#endif

View File

@@ -1,348 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX 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/>.
*/
/*
* The VL53L0X Time-Of-Flight sensor operates by sending a short laser pulse and detecting
* the reflection of the pulse. The time between the pulse and the receipt of reflections
* is measured and used to determine the distance to the reflecting object.
*
* For economy of memory and processing time, this driver includes only part of the code
* that ST provide in their API. Also, the API code isn't very clear and it is not easy
* to identify what operations are useful and what are not.
* The operation shown here doesn't include any calibration, so is probably not as accurate
* as using the full driver, but it's probably accurate enough for the purpose.
*
* The device driver allocates up to 3 vpins to the device. A digital read on the first pin
* will return a value that indicates whether the object is within the threshold range (1)
* or not (0). An analogue read on the first pin returns the last measured distance (in mm),
* the second pin returns the signal strength, and the third pin returns detected
* ambient light level. By default the device takes around 60ms to complete a ranging
* operation, so we do a 100ms cycle (10 samples per second).
*
* The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module,
* you can use this address. However, the address can be modified by software. If
* you select another address, that address will be written to the device and used until the device is reset.
*
* If you have more than one module, then you will need to specify a digital VPIN (Arduino
* digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now,
* when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once
* all VL53L0X modules are turned off, the driver works through each module in turn,
* setting XSHUT to HIGH to turn that module on, then writing that module's desired I2C address.
* In this way, many VL53L0X modules can be connected to the one I2C bus, each one
* using a distinct I2C address. The process is described in ST Microelectronics application
* note AN4846.
*
* WARNING: If the device's XSHUT pin is not connected, then it may be prone to noise,
* and the device may reset spontaneously or when handled and the device will stop responding
* on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor
* (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example)
* include a pull-up on the module, but others don't.
*
* The driver is configured as follows:
*
* Single VL53L0X module:
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold);
* Where firstVpin is the first vpin reserved for reading the device,
* nPins is 1, 2 or 3,
* i2cAddress is the address of the device (normally 0x29),
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
* and highThreshold is the distance at which the digital vpin state is set to 0 (in mm).
*
* Multiple VL53L0X modules:
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold, xshutPin);
* ...
* Where firstVpin is the first vpin reserved for reading the device,
* nPins is 1, 2 or 3,
* i2cAddress is the address of the device (any valid address except 0x29),
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
* highThreshold is the distance at which the digital vpin state is set to 0 (in mm),
* and xshutPin is the VPIN number corresponding to a digital output that is connected to the
* XSHUT terminal on the module. The digital output may be an Arduino pin or an
* I/O extender pin.
*
* Example:
* In mySetup function within mySetup.cpp:
* VL53L0X::create(4000, 3, 0x29, 200, 250);
* Sensor::create(4000, 4000, 0); // Create a sensor
*
* When an object comes within 200mm of the sensor, a message
* <Q 4000>
* will be sent over the serial USB, and when the object moves more than 250mm from the sensor,
* a message
* <q 4000>
* will be sent.
*
*/
#ifndef IO_VL53L0X_h
#define IO_VL53L0X_h
#include "IODevice.h"
class VL53L0X : public IODevice {
private:
uint16_t _ambient;
uint16_t _distance;
uint16_t _signal;
uint16_t _onThreshold;
uint16_t _offThreshold;
VPIN _xshutPin;
bool _value;
uint8_t _nextState = STATE_INIT;
I2CRB _rb;
uint8_t _inBuffer[12];
uint8_t _outBuffer[2];
static bool _addressConfigInProgress;
// State machine states.
enum : uint8_t {
STATE_INIT,
STATE_RESTARTMODULE,
STATE_CONFIGUREADDRESS,
STATE_CONFIGUREDEVICE,
STATE_INITIATESCAN,
STATE_CHECKSTATUS,
STATE_GETRESULTS,
STATE_DECODERESULTS,
STATE_FAILED,
};
// Register addresses
enum : uint8_t {
VL53L0X_REG_SYSRANGE_START=0x00,
VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13,
VL53L0X_REG_RESULT_RANGE_STATUS=0x14,
VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV=0x89,
VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A,
};
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
public:
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
}
protected:
VL53L0X(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
_firstVpin = firstVpin;
_nPins = (nPins > 3) ? 3 : nPins;
_I2CAddress = i2cAddress;
_onThreshold = onThreshold;
_offThreshold = offThreshold;
_xshutPin = xshutPin;
_value = 0;
addDevice(this);
}
void _begin() override {
// If there's only one device, then the XSHUT pin need not be connected. However,
// the device will not respond on its default address if it has
// already been changed. Therefore, we skip the address configuration if the
// desired address is already responding on the I2C bus.
_nextState = STATE_INIT;
_addressConfigInProgress = false;
}
void _loop(unsigned long currentMicros) override {
uint8_t status;
switch (_nextState) {
case STATE_INIT:
if (I2CManager.exists(_I2CAddress)) {
// Device already present on the nominated address, so skip the address initialisation.
_nextState = STATE_CONFIGUREDEVICE;
} else {
// On first entry to loop, reset this module by pulling XSHUT low. Each module
// will be addressed in turn, until all are in the reset state.
// If no XSHUT pin is configured, then only one device is supported.
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
_nextState = STATE_RESTARTMODULE;
delayUntil(currentMicros+10000);
}
break;
case STATE_RESTARTMODULE:
// On second entry, set XSHUT pin high to allow this module to restart.
// I've observed that the device tends to randomly reset if the XSHUT
// pin is set high from a 5V arduino, even through a pullup resistor.
// Assume that there will be a pull-up on the XSHUT pin to +2.8V as
// recommended in the device datasheet. Then we only need to
// turn our output pin high-impedence (by making it an input) and the
// on-board pullup will do its job.
// Ensure XSHUT is set for only one module at a time by using a
// shared flag accessible to all device instances.
if (!_addressConfigInProgress) {
_addressConfigInProgress = true;
// Configure XSHUT pin (if connected) to bring the module out of sleep mode.
if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, false);
// Allow the module time to restart
delayUntil(currentMicros+10000);
_nextState = STATE_CONFIGUREADDRESS;
}
break;
case STATE_CONFIGUREADDRESS:
// Then write the desired I2C address to the device, while this is the only
// module responding to the default address.
{
#if defined(I2C_EXTENDED_ADDRESS)
// Add subbus reference for desired address to the device default address.
I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS};
status = I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress());
#else
status = I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress);
#endif
if (status != I2C_STATUS_OK) {
reportError(status);
}
}
delayUntil(currentMicros+10000);
_nextState = STATE_CONFIGUREDEVICE;
break;
case STATE_CONFIGUREDEVICE:
// Allow next VL53L0X device to be configured
_addressConfigInProgress = false;
// Now check if device address has been set.
if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO
_display();
#endif
// Set 2.8V mode
status = write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
if (status != I2C_STATUS_OK) {
reportError(status);
} else
_nextState = STATE_INITIATESCAN;
} else {
DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
_nextState = STATE_FAILED;
}
break;
case STATE_INITIATESCAN:
// Not scanning, so initiate a scan
_outBuffer[0] = VL53L0X_REG_SYSRANGE_START;
_outBuffer[1] = 0x01;
I2CManager.write(_I2CAddress, _outBuffer, 2, &_rb);
_nextState = STATE_CHECKSTATUS;
break;
case STATE_CHECKSTATUS:
status = _rb.status;
if (status == I2C_STATUS_PENDING) return; // try next time
if (status != I2C_STATUS_OK) {
reportError(status);
} else
_nextState = STATE_GETRESULTS;
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
break;
case STATE_GETRESULTS:
// Ranging completed. Request results
_outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
delayUntil(currentMicros + 5000); // Allow 5ms to get data
_nextState = STATE_DECODERESULTS;
break;
case STATE_DECODERESULTS:
// If I2C write still busy, return.
status = _rb.status;
if (status == I2C_STATUS_PENDING) return; // try again next time
if (status == I2C_STATUS_OK) {
if (!(_inBuffer[0] & 1)) return; // device still busy
uint8_t deviceRangeStatus = ((_inBuffer[0] & 0x78) >> 3);
if (deviceRangeStatus == 0x0b) {
// Range status OK, so use data
_ambient = makeuint16(_inBuffer[7], _inBuffer[6]);
_signal = makeuint16(_inBuffer[9], _inBuffer[8]);
_distance = makeuint16(_inBuffer[11], _inBuffer[10]);
if (_distance <= _onThreshold)
_value = true;
else if (_distance > _offThreshold)
_value = false;
}
// Completed. Restart scan on next loop entry.
_nextState = STATE_INITIATESCAN;
} else {
reportError(status);
}
break;
case STATE_FAILED:
// Do nothing.
delayUntil(currentMicros+1000000UL);
break;
default:
break;
}
}
// Function to report a failed I2C operation.
void reportError(uint8_t status) {
DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
_deviceState = DEVSTATE_FAILED;
_value = false;
}
// For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level.
int _readAnalogue(VPIN vpin) override {
int pin = vpin - _firstVpin;
switch (pin) {
case 0:
return _distance;
case 1:
return _signal;
case 2:
return _ambient;
default:
return -1;
}
}
// For digital read, return zero for all but first pin.
int _read(VPIN vpin) override {
if (vpin == _firstVpin)
return _value;
else
return 0;
}
void _display() override {
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
}
private:
inline uint16_t makeuint16(byte lsb, byte msb) {
return (((uint16_t)msb) << 8) | lsb;
}
uint8_t write_reg(uint8_t reg, uint8_t data) {
// write byte to register
uint8_t outBuffer[2];
outBuffer[0] = reg;
outBuffer[1] = data;
return I2CManager.write(_I2CAddress, outBuffer, 2);
}
uint8_t read_reg(uint8_t reg) {
// read byte from register and return value
I2CManager.read(_I2CAddress, _inBuffer, 1, &reg, 1);
return _inBuffer[0];
}
};
bool VL53L0X::_addressConfigInProgress = false;
#endif // IO_VL53L0X_h

View File

@@ -1,172 +0,0 @@
/*
* © 2022, Chris Harlow. All rights reserved.
* Based on original by: Robin Simonds, Beagle Bay Inc
*
* This file is part of DCC-EX 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 IO_duinoNodes_h
#define IO_duinoNodes_h
#include <Arduino.h>
#include "defines.h"
#include "IODevice.h"
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
class IO_duinoNodes : public IODevice {
public:
IO_duinoNodes(VPIN firstVpin, int nPins,
byte clockPin, byte latchPin, byte dataPin,
const byte* pinmap) :
IODevice(firstVpin, nPins) {
_latchPin=latchPin;
_clockPin=clockPin;
_dataPin=dataPin;
_pinMap=pinmap;
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
_pinValues=(byte*) calloc(_nShiftBytes,1);
// Connect to HAL so my _write, _read and _loop will be called as required.
IODevice::addDevice(this);
}
// Called by HAL to start handling this device
void _begin() override {
_deviceState = DEVSTATE_NORMAL;
pinMode(_latchPin,OUTPUT);
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
}
// loop called by HAL supervisor
void _loop(unsigned long currentMicros) override {
if (_pinMap) _loopInput(currentMicros);
else if (_xmitPending) _loopOutput();
}
void _loopInput(unsigned long currentMicros) {
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
_prevMicros=currentMicros;
//set latch to HIGH to freeze & store parallel data
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
delayMicroseconds(1);
//set latch to LOW to enable the data to be transmitted serially
ArduinoPins::fastWriteDigital(_latchPin, LOW);
// stream in the bitmap using mapping order provided at constructor
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
byte newByte=0;
for (int xmitBit=0;xmitBit<8; xmitBit++) {
ArduinoPins::fastWriteDigital(_clockPin, LOW);
delayMicroseconds(1);
bool data = ArduinoPins::fastReadDigital(_dataPin);
byte map=_pinMap[xmitBit];
if (data) newByte |= map;
else newByte &= ~map;
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
delayMicroseconds(1);
}
_pinValues[xmitByte]=newByte;
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
}
}
void _loopOutput() {
// stream out the bitmap (highest pin first)
_xmitPending=false;
ArduinoPins::fastWriteDigital(_latchPin, LOW);
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
ArduinoPins::fastWriteDigital(_clockPin,LOW);
}
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
}
int _read(VPIN vpin) override {
int pin=vpin - _firstVpin;
bool b=DN_GET_BIT(pin);
return b?1:0;
}
void _write(VPIN vpin, int value) override {
int pin = vpin - _firstVpin;
bool oldval=DN_GET_BIT(pin);
bool newval=value!=0;
if (newval==oldval) return; // no change
if (newval) DN_SET_BIT(pin);
else DN_CLR_BIT(pin);
_xmitPending=true; // shift register will be sent on next _loop()
}
void _display() override {
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
_pinMap?F("IN"):F("OUT"),
(int)_firstVpin,
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
}
private:
static const unsigned long POLL_MICROS=100000; // 10 / S
unsigned long _prevMicros;
int _nShiftBytes=0;
VPIN _latchPin,_clockPin,_dataPin;
byte* _pinValues;
bool _xmitPending; // Only relevant in output mode
const byte* _pinMap; // NULL in output mode
};
class IO_DNIN8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0,7,6,5,1,2,3,4
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNIN8K {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
}
};
class IO_DNOU8 {
public:
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
{
if (IODevice::checkNoOverlap(firstVpin,nPins))
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
}
};
#endif

79
LCDDisplay.cpp Normal file
View File

@@ -0,0 +1,79 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// 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::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 ((!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;
}
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
}

63
LCDDisplay.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef LCDDisplay_h
#define LCDDisplay_h
#include <Arduino.h>
// 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=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:
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

55
LCD_Implementation.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////////
// This implementation is designed to be #included ONLY ONCE in the .ino
//
// It will create a driver implemntation and a shim class implementation.
// This means that other classes can reference the shim without knowing
// which libraray is involved.
////////////////////////////////////////////////////////////////////////////////////
#include "config.h"
#include <Wire.h>
#include "LCDDisplay.h"
LCDDisplay * LCDDisplay::lcdDisplay=0;
// Implement the LCDDisplay shim class as a singleton.
// Notice that the LCDDisplay class declaration (LCDDisplay.h) is independent of the library
// but the implementation is compiled here with dependencies on LCDDriver which is
// specific to the library in use.
// Thats the workaround to the drivers not all implementing a common interface.
#if defined(OLED_DRIVER)
#include "LCD_OLED.h"
#define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true))
#elif defined(LCD_DRIVER)
#include "LCD_LCD.h"
#define CONDITIONAL_LCD_START for (LCDDisplay * dummy=new LCDDisplay();dummy!=NULL; dummy=dummy->loop2(true))
#else
#include "LCD_NONE.h"
#define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */
#endif

37
LCD_LCD.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows
// DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER
LCDDisplay::LCDDisplay() {
lcdDisplay=this;
LCDDriver.init();
LCDDriver.backlight();
interfake(LCD_DRIVER);
clear();
}
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);
LCDDriver.print(F(" "));
LCDDriver.setCursor(0, row);
}
void LCDDisplay::writeNative(char * b){ LCDDriver.print(b); }
void LCDDisplay::displayNative() { LCDDriver.display(); }

View File

@@ -1,7 +1,5 @@
/*
* © 2021 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
@@ -18,19 +16,12 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#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
// dummy LCD shim to keep linker happy
LCDDisplay::LCDDisplay() {}
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::displayNative(){}

57
LCD_OLED.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
// 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() {
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;
}
DIAG(F("\nOLED display not found\n"));
}
void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; lcdRows=p2/8; (void)p3;}
void LCDDisplay::clearNative() {LCDDriver.clearDisplay();}
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(); }

76
LCN.cpp
View File

@@ -1,76 +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);
if (!Turnout::exists(id)) LCNTurnout::create(id);
Turnout::setClosedStateOnly(id,ch=='t');
id = 0;
}
else if (ch == 'y' || ch == 'Y') { // Turnout opcodes
if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch);
Turnout::setClosed(id,ch=='y');
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, VPIN_NONE, 0); // impossible pin
ss->setState(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);
}
}

View File

@@ -1,224 +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 "DIAG.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(I2CAddress lcd_Addr, uint8_t lcd_cols,
uint8_t lcd_rows) {
_Addr = lcd_Addr;
lcdRows = lcd_rows; // Number of character rows (typically 2 or 4).
lcdCols = lcd_cols; // Number of character columns (typically 16 or 20)
_backlightval = 0;
}
bool LiquidCrystal_I2C::begin() {
I2CManager.begin();
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
if (I2CManager.exists(_Addr)) {
DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString());
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
backlight();
} else {
DIAG(F("LCD not found on I2C:%s"), _Addr.toString());
return false;
}
if (lcdRows > 1) {
_displayfunction |= LCD_2LINE;
}
// according to datasheet, we need at least 40ms after power rises above 2.7V
// before sending commands. Arduino can turn on way before 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);
delayMicroseconds(5000); // wait min 4.1ms
// second try
write4bits(0x03);
delayMicroseconds(5000); // wait min 4.1ms
// third go!
write4bits(0x03);
delayMicroseconds(5000);
// finally, set to 4-bit interface
write4bits(0x02);
// 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();
// Initialize to default text direction (for roman languages)
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
// set the entry mode
command(LCD_ENTRYMODESET | _displaymode);
return true;
}
/********** high level commands, for the user! */
void LiquidCrystal_I2C::clearNative() {
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
delayMicroseconds(2000); // this command takes 1.52ms but allow plenty
}
void LiquidCrystal_I2C::setRowNative(byte row) {
uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
if (row >= lcdRows) {
row = lcdRows - 1; // we count rows starting w/0
}
command(LCD_SETDDRAMADDR | (row_offsets[row]));
}
void LiquidCrystal_I2C::display() {
_displaycontrol |= LCD_DISPLAYON;
command(LCD_DISPLAYCONTROL | _displaycontrol);
}
// Turn the (optional) backlight off/on
void LiquidCrystal_I2C::noBacklight(void) {
_backlightval &= ~LCD_BACKLIGHT;
expanderWrite(0);
}
void LiquidCrystal_I2C::backlight(void) {
_backlightval = LCD_BACKLIGHT;
expanderWrite(0);
}
size_t LiquidCrystal_I2C::writeNative(uint8_t value) {
send(value, Rs);
return 1;
}
bool LiquidCrystal_I2C::isBusy() {
return rb.isBusy();
}
/*********** 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 >> 4) & 0x0f) << BACKPACK_DATA_BITS) | mode;
uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode;
// Send both nibbles
uint8_t len = 0;
rb.wait();
outputBuffer[len++] = highnib|En;
outputBuffer[len++] = highnib;
outputBuffer[len++] = lownib|En;
outputBuffer[len++] = lownib;
I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
}
// write 4 data bits to the HD44780 LCD controller.
void LiquidCrystal_I2C::write4bits(uint8_t value) {
uint8_t _data = ((value & 0x0f) << BACKPACK_DATA_BITS) | _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.
uint8_t len = 0;
rb.wait();
outputBuffer[len++] = _data|En;
outputBuffer[len++] = _data;
I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
}
// 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) {
rb.wait();
outputBuffer[0] = value | _backlightval;
I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously
}

View File

@@ -1,99 +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>
#include "Display.h"
#include "I2CManager.h"
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#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_CURSOROFF 0x00
#define LCD_BLINKOFF 0x00
// flags for function set
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x8DOTS 0x00
// Bit mapping onto PCF8574 port
#define BACKPACK_Rs_BIT 0
#define BACKPACK_Rw_BIT 1
#define BACKPACK_En_BIT 2
#define BACKPACK_BACKLIGHT_BIT 3
#define BACKPACK_DATA_BITS 4 // Bits 4-7
// Equivalent mask bits
#define LCD_BACKLIGHT (1 << BACKPACK_BACKLIGHT_BIT) // Backlight enable
#define En (1 << BACKPACK_En_BIT) // Enable bit
#define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit
#define Rs (1 << BACKPACK_Rs_BIT) // Register select bit
class LiquidCrystal_I2C : public DisplayDevice {
public:
LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
bool begin() override;
void clearNative() override;
void setRowNative(byte line) override;
size_t writeNative(uint8_t c) override;
// I/O is synchronous, so if this is called we're not busy!
bool isBusy() override;
void display();
void noBacklight();
void backlight();
void command(uint8_t);
uint16_t getNumCols() { return lcdCols; }
uint16_t getNumRows() { return lcdRows; }
private:
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
void expanderWrite(uint8_t);
uint8_t lcdCols=0, lcdRows=0;
I2CAddress _Addr;
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _backlightval = 0;
uint8_t outputBuffer[4];
I2CRB rb;
};
#endif

98
MemStream.cpp Normal file
View 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
View 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

View File

@@ -1,12 +1,7 @@
/*
* © 2022 Paul M Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
* 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
@@ -23,135 +18,50 @@
*/
#include <Arduino.h>
#include "MotorDriver.h"
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "AnalogReadFast.h"
#include "DIAG.h"
#if defined(ARDUINO_ARCH_ESP32)
#include "ESP32-fixes.h"
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_ARCH_MEGAAVR)
#define WritePin digitalWrite
#define ReadPin digitalRead
#else
// use the DIO2 libraray for much faster pin access
#define GPIO2_PREFER_SPEED 1
#include <DIO2.h> // use IDE menu Tools..Manage Libraries to locate and install DIO2
#define WritePin digitalWrite2
#define ReadPin digitalRead2
#endif
bool MotorDriver::commonFaultPin=false;
volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC;
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
powerPin=power_pin;
invertPower=power_pin < 0;
if (invertPower) {
powerPin = 0-power_pin;
IODevice::write(powerPin,HIGH);// set to OUTPUT and off
} else {
powerPin = power_pin;
IODevice::write(powerPin,LOW);// set to OUTPUT and off
}
signalPin=signal_pin;
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
fastSignalPin.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTA;
}
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
DIAG(F("Found PORTB pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTB;
}
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
DIAG(F("Found PORTC pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTC;
}
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);
// if brake is used for railcom cutout we need to do PORTX register trick here as well
pinMode(brakePin, OUTPUT);
setBrake(true); // start with brake on in case we hace DC stuff going on
}
else brakePin=UNUSED_PIN;
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
senseOffset = ADCee::init(currentPin);
}
senseFactor=sense_factor;
faultPin=fault_pin;
if (faultPin != UNUSED_PIN) {
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
pinMode(faultPin, INPUT);
}
// This conversion performed at compile time so the remainder of the code never needs
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps;
rawCurrentTripValue=mA2raw(trip_milliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
// can reach the trip value. So independent of the current, the
// short circuit protection would never trip. So we adjust the
// trip value so that it is tiggered when the ADC reports it's
// maximum value instead.
// DIAG(F("Changing short detection value from %d to %d mA"),
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
}
if (currentPin==UNUSED_PIN)
DIAG(F("** WARNING ** No current or short detection"));
else {
DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"),
currentPin-A0, senseOffset,rawCurrentTripValue);
// self testing diagnostic for the non-float converters... may be removed when happy
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
}
// prepare values for current detection
sampleDelay = 0;
lastSampleTaken = millis();
progTripValue = mA2raw(TRIP_CURRENT_PROG);
rawCurrentTripValue=(int)(trip_milliamps / sense_factor);
pinMode(powerPin, OUTPUT);
pinMode(brakePin < 0 ? -brakePin : brakePin, OUTPUT);
setBrake(false);
pinMode(signalPin, OUTPUT);
if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT);
pinMode(currentPin, INPUT);
if (faultPin != UNUSED_PIN) pinMode(faultPin, INPUT);
}
bool MotorDriver::isPWMCapable() {
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
}
void MotorDriver::setPower(POWERMODE mode) {
bool on=mode==POWERMODE::ON;
if (on) {
IODevice::write(powerPin,invertPower ? LOW : HIGH);
if (isProgTrack)
DCCWaveform::progTrack.clearResets();
void MotorDriver::setPower(bool on) {
if (brakePin == -4 && on) {
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
setBrake(true);
setBrake(false);
}
else {
IODevice::write(powerPin,invertPower ? HIGH : LOW);
}
powerMode=mode;
WritePin(powerPin, on ? HIGH : LOW);
}
// setBrake applies brake if on == true. So to get
@@ -162,231 +72,36 @@ void MotorDriver::setPower(POWERMODE mode) {
// (HIGH == release brake) and setBrake does
// compensate for that.
//
void MotorDriver::setBrake(bool on, bool interruptContext) {
if (brakePin == UNUSED_PIN) return;
if (!interruptContext) {noInterrupts();}
if (on ^ invertBrake)
setHIGH(fastBrakePin);
else
setLOW(fastBrakePin);
if (!interruptContext) {interrupts();}
}
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, cheat 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.
*
* Bool fromISR should be adjusted dependent how function is called
*/
int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR;
if (currentPin==UNUSED_PIN) return 0;
int current;
current = ADCee::read(currentPin, fromISR)-senseOffset;
if (current<0) current=0-current;
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
return (current == 0 ? -1 : -current);
return current;
}
#ifdef ANALOG_READ_INTERRUPT
/*
* This should only be called in interrupt context
* Copies current value from HW to cached value in
* Motordriver.
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
bool MotorDriver::sampleCurrentFromHW() {
byte low, high;
//if (!bit_is_set(ADCSRA, ADIF))
if (bit_is_set(ADCSRA, ADSC))
return false;
// if ((ADMUX & mask) != (currentPin - A0))
// return false;
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
sampleCurrent = (high << 8) | low;
sampleCurrentTimestamp = millis();
return true;
}
void MotorDriver::startCurrentFromHW() {
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
const byte mask = 7;
#else
const byte mask = 31;
#endif
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
}
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
249, 392, 440, 494,
523, 587, 659, 698,
494, 440, 392, 249,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
#if defined(ARDUINO_AVR_UNO)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
#endif
// spedcoode is a dcc speed & direction
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
byte brake;
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
void MotorDriver::setBrake(bool on) {
bool state = on;
byte pin = brakePin;
if (brakePin < 0) {
pin=-pin;
state=!state;
}
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
if (tSpeed <= 1) brake = 255;
else if (tSpeed >= 127) brake = 0;
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32)
DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
#endif
//DIAG(F("DCSignal %d"), speedcode);
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
noInterrupts();
HAVE_PORTA(shadowPORTA=PORTA);
setSignal(tDir);
HAVE_PORTA(PORTA=shadowPORTA);
interrupts();
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
noInterrupts();
HAVE_PORTB(shadowPORTB=PORTB);
setSignal(tDir);
HAVE_PORTB(PORTB=shadowPORTB);
interrupts();
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
noInterrupts();
HAVE_PORTC(shadowPORTC=PORTC);
setSignal(tDir);
HAVE_PORTC(PORTC=shadowPORTC);
interrupts();
} else {
noInterrupts();
setSignal(tDir);
interrupts();
}
WritePin(pin, state ? HIGH : LOW);
//DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin));
}
void MotorDriver::setSignal( bool high) {
WritePin(signalPin, high ? HIGH : LOW);
if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH);
}
int MotorDriver::getCurrentRaw() {
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 analogReadFast is used here. (-2uS)
return analogReadFast(currentPin);
}
unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
return (unsigned int)(raw * senseFactor);
}
unsigned int MotorDriver::mA2raw( unsigned int mA) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
return (int32_t)mA * senseScale / senseFactorInternal;
}
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.
#if defined(ARDUINO_ARCH_SAMD)
PortGroup *port = digitalPinToPort(pin);
#elif defined(ARDUINO_ARCH_STM32)
GPIO_TypeDef *port = digitalPinToPort(pin);
#else
uint8_t port = digitalPinToPort(pin);
#endif
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);
}
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
// Trackname for diag messages later
switch (powerMode) {
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (commonFaultPin) {
if (lastCurrent < tripValue) {
setPower(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: POWERTOGGLE TRACK %c"), trackno + 'A');
} else {
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
}
}
if (lastCurrent < tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
else
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
} else {
setPower(POWERMODE::OVERLOAD);
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
}
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPower(POWERMODE::ON);
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
int MotorDriver::mA2raw( unsigned int mA) {
return (int)(mA / senseFactor);
}

View File

@@ -1,12 +1,7 @@
/*
* © 2022 Paul M Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020 Chris Harlow
* © 2022 Harald Barth
* All rights reserved.
* © 2020, Chris Harlow. All rights reserved.
*
* This file is part of CommandStation-EX
* 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
@@ -23,215 +18,30 @@
*/
#ifndef MotorDriver_h
#define MotorDriver_h
#include "FSH.h"
#include "IODevice.h"
#include "DCCTimer.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))
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
#define HAVE_PORTA(X) X
#define HAVE_PORTB(X) X
#define HAVE_PORTC(X) X
#endif
#if defined(ARDUINO_AVR_UNO)
#define HAVE_PORTB(X) X
#endif
#if defined(ARDUINO_ARCH_SAMD)
#define PORTA REG_PORT_OUT0
#define HAVE_PORTA(X) X
#define PORTB REG_PORT_OUT1
#define HAVE_PORTB(X) X
#endif
#if defined(ARDUINO_ARCH_STM32)
#define PORTA GPIOA->ODR
#define HAVE_PORTA(X) X
#define PORTB GPIOB->ODR
#define HAVE_PORTB(X) X
#define PORTC GPIOC->ODR
#define HAVE_PORTC(X) X
#endif
// if macros not defined as pass-through we define
// them here as someting that is valid as a
// statement and evaluates to false.
#ifndef HAVE_PORTA
#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTB
#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTC
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
// 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
class pinpair {
public:
pinpair(byte p1, byte p2) {
pin = p1;
invpin = p2;
};
byte pin = UNUSED_PIN;
byte invpin = UNUSED_PIN;
};
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
typedef uint32_t portreg_t;
#else
typedef uint8_t portreg_t;
#endif
struct FASTPIN {
volatile portreg_t *inout;
portreg_t maskHIGH;
portreg_t maskLOW;
volatile portreg_t *shadowinout;
};
// The port registers that are shadowing
// the real port registers. These are
// defined in Motordriver.cpp
extern volatile portreg_t shadowPORTA;
extern volatile portreg_t shadowPORTB;
extern volatile portreg_t shadowPORTC;
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
class MotorDriver {
public:
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
void setPower( POWERMODE mode);
POWERMODE getPower() { return powerMode;}
// as the port registers can be shadowed to get syncronized DCC signals
// we need to take care of that and we have to turn off interrupts if
// we setSignal() or setBrake() or setPower() during that time as
// otherwise the call from interrupt context can undo whatever we do
// from outside interrupt
void setBrake( bool on, bool interruptContext=false);
__attribute__((always_inline)) inline void setSignal( bool high) {
if (trackPWM) {
DCCTimer::setPWM(signalPin,high);
}
else {
if (high) {
setHIGH(fastSignalPin);
if (dualSignal) setLOW(fastSignalPin2);
}
else {
setLOW(fastSignalPin);
if (dualSignal) setHIGH(fastSignalPin2);
}
}
};
inline void enableSignal(bool on) {
if (on)
pinMode(signalPin, OUTPUT);
else
pinMode(signalPin, INPUT);
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
#elif defined(ARDUINO_ARCH_ESP32)
ledcDetachPin(brakePin);
#else
setDCSignal(128);
#endif
};
int getCurrentRaw(bool fromISR=false);
unsigned int raw2mA( int raw);
unsigned int mA2raw( unsigned int mA);
inline bool brakeCanPWM() {
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
return true;
#else
#ifdef digitalPinToTimer
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
#else
return (brakePin<14 && brakePin >1);
#endif //digitalPinToTimer
#endif //ESP32/ARM
}
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t 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);
virtual int getCurrentRaw();
virtual unsigned int raw2mA( int raw);
virtual int mA2raw( unsigned int mA);
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
return rawCurrentTripValue;
}
bool isPWMCapable();
bool canMeasureCurrent();
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte getFaultPin() {
return faultPin;
}
inline void makeProgTrack(bool on) { // let this output know it's a prog track.
isProgTrack = on;
}
void checkPowerOverload(bool useProgLimit, byte trackno);
#ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
private:
bool isProgTrack = false; // tells us if this is a prog track
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);
}
VPIN powerPin;
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
bool dualSignal; // true to use signalPin2
bool invertBrake; // brake pin passed as negative means pin is inverted
bool invertPower; // power pin passed as negative means pin is inverted
// Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale
//
// senseScale is chosen as 256 to give enough scale for 2 decimal place
// raw->mA conversion with an ultra fast optimised integer multiplication
int senseFactorInternal; // set to senseFactor * senseScale
static const int senseScale=256;
int senseOffset;
byte powerPin, signalPin, signalPin2, currentPin, faultPin;
int8_t brakePin; // negative means pin is inverted
float senseFactor;
unsigned int tripMilliamps;
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
int progTripValue;
int lastCurrent;
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
#endif
int maxmA;
int tripmA;
// Wait times for power management. Unit: milliseconds
static const int POWER_SAMPLE_ON_WAIT = 100;
static const int POWER_SAMPLE_OFF_WAIT = 1000;
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
};
#endif

View File

@@ -1,30 +1,8 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
* (c) 2020 Anthony W - Dayton. 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 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.
@@ -39,61 +17,22 @@
#define UNUSED_PIN 127 // inside int8_t
#endif
// The MotorDriver definition is:
//
// MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin,
// float senseFactor, unsigned int tripMilliamps, byte faultPin);
//
// power_pin: Turns the board on/off. Often called ENABLE or PWM on the shield
// signal_pin: Where the DCC signal goes in. Often called DIR on the shield
// signal_pin2: Inverse of signal_pin. A few shields need this as well, can be replace by hardware inverter
// brake_pin: When tuned on, brake is set - output shortened (*)
// current_pin: Current sense voltage pin from shield to ADC
// senseFactor: Relation between volts on current_pin and actual output current
// tripMilliamps: Short circuit trip limit in milliampere, max 32767 (32.767A)
// faultPin: Some shields have a pin to to report a fault condition to the uCPU. High when fault occurs
//
// (*) If the brake_pin is negative that means the sense
// 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, used on different architectures:
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
// to tweak both these
// Arduino standard Motor Shield
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#elif defined(ARDUINO_ARCH_ESP32)
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN)
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
#endif
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)
// 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)
new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \
new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, UNUSED_PIN)
//
// 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
@@ -101,21 +40,10 @@
// 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)
// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12)
// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, UNUSED_PIN)
// See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet.
// Pololu Dual TB9051FTG Motor Shield
// This is the shield without modifications. Unfortunately the TB9051FTG driver chip on
// the shield makes short delays when direction is switched. That means that the chip
// can NOT provide a standard conformant DCC signal independent how hard we try. If your
// Decoders tolerate that signal, use it by all mean but it is not recommended. Without
// modifications it uses the following pins below which means no HA waveform and no
// RailCom on an Arduino Mega 2560 but the DCC signal is broken anyway.
#define POLOLU_TB9051FTG F("POLOLU_TB9051FTG"), \
new MotorDriver(2, 7, UNUSED_PIN, -9, A0, 10, 2500, 6), \
new MotorDriver(4, 8, UNUSED_PIN, -10, A1, 10, 2500, 12)
// Firebox Mk1
#define FIREBOX_MK1 F("FIREBOX_MK1"), \
new MotorDriver(3, 6, 7, UNUSED_PIN, A5, 9.766, 5500, UNUSED_PIN), \
@@ -128,61 +56,7 @@
// FunduMoto Motor Shield
#define FUNDUMOTO_SHIELD F("FUNDUMOTO_SHIELD"), \
new MotorDriver(10, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
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, 1500, UNUSED_PIN)
// YFROBOT Motor Shield (V3.1)
#define YFROBOT_MOTOR_SHIELD F("YFROBOT_MOTOR_SHIELD"), \
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// Makeblock ORION UNO like sized board with integrated motor driver
// This is like an Uno with H-bridge and RJ12 contacts instead of pin rows.
// No current sense. Barrel connector max 12V, Vmotor max 15V. 1.1A polyfuse as output protection.
// Main is marked M1 and near RJ12 #5
// Prog is marked M2 and near RJ12 #4
// For details see
// http://docs.makeblock.com/diy-platform/en/electronic-modules/main-control-boards/makeblock-orion.html
#define ORION_UNO_INTEGRATED_SHIELD F("ORION_UNO_INTEGRATED_SHIELD"), \
new MotorDriver(6, 7, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN), \
new MotorDriver(5, 4, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 1.0, 1100, UNUSED_PIN)
// This is an example how to setup a motor shield definition for a motor shield connected
// to an NANO EVERY board. You have to make the connectons from the shield to the board
// as in this example or adjust the values yourself.
#define NANOEVERY_EXAMPLE F("NANOEVERY_EXAMPLE"), \
new MotorDriver(5, 6, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 1500, UNUSED_PIN),\
new MotorDriver(9, 10, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 1500, UNUSED_PIN)
// This is an example how to stack two standard motor shields. The upper shield
// needs pins 3 8 9 11 12 13 A0 A1 disconnected from the lower shield and
// jumpered instead like this: 2-3 6-8 7-9 4-13 5-11 10-12 A0-A4 A1-A5
// Pin assigment table:
// 2 Enable C jumpered
// 3 Enable A direct
// 4 Dir D jumpered
// 5 Enable D jumpered
// 6 Brake D jumpered
// 7 Brake C jumpered
// 8 Brake B direct
// 9 Brake A direct
// 10 Dir C jumpered
// 11 Enable B direct
// 12 Dir A direct
// 13 Dir B direct
// A0 Sense A direct
// A1 Sense B direct
// A4 Sense C jumpered
// A5 Sense D jumpered
//
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
//
#endif

Some files were not shown because too many files have changed in this diff Show More