diff --git a/.gitignore b/.gitignore index e5404bb..d768dbf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ Release/* .gcc-flags.json .pio/ .vscode/ -config.h \ No newline at end of file +config.h +.vscode/extensions.json +mySetup.h diff --git a/ATMEGA2560/Timer.h b/ATMEGA2560/Timer.h deleted file mode 100644 index 2568c9e..0000000 --- a/ATMEGA2560/Timer.h +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef ATMEGA2560Timer_h -#define ATMEGA2560Timer_h - -#include "../VirtualTimer.h" -#include - -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 \ No newline at end of file diff --git a/ATMEGA328/Timer.h b/ATMEGA328/Timer.h deleted file mode 100644 index 6204953..0000000 --- a/ATMEGA328/Timer.h +++ /dev/null @@ -1,208 +0,0 @@ -#ifndef ATMEGA328Timer_h -#define ATMEGA328Timer_h - -#include "../VirtualTimer.h" -#include - -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 \ No newline at end of file diff --git a/AnalogReadFast.h b/AnalogReadFast.h deleted file mode 100644 index cc474ee..0000000 --- a/AnalogReadFast.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * AnalogReadFast.h - * - * Copyright (C) 2016 Albert van Dalen http://www.avdweb.nl - * - * This file is part of CommandStation. - * - * CommandStation is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * CommandStation is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with CommandStation. If not, see . - */ - -#ifndef COMMANDSTATION_DCC_ANALOGREADFAST_H_ -#define COMMANDSTATION_DCC_ANALOGREADFAST_H_ - -#include - -int inline analogReadFast(uint8_t ADCpin); - -int inline analogReadFast(uint8_t ADCpin) -{ byte ADCSRAoriginal = ADCSRA; - ADCSRA = (ADCSRA & B11111000) | 4; - int adc = analogRead(ADCpin); - ADCSRA = ADCSRAoriginal; - return adc; -} - -#endif // COMMANDSTATION_DCC_ANALOGREADFAST_H_ \ No newline at end of file diff --git a/ArduinoTimers.h b/ArduinoTimers.h deleted file mode 100644 index c0acd30..0000000 --- a/ArduinoTimers.h +++ /dev/null @@ -1,18 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit and copyright David Cutting -// The files included below come from the same source. -// This library had been included with the DCC code to avoid issues with -// library management for inexperienced users. "It just works (TM)" - -#ifndef ArduinoTimers_h -#define ArduinoTimers_h - -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - #include "ATMEGA2560/Timer.h" -#elif defined(ARDUINO_AVR_UNO) - #include "ATMEGA328/Timer.h" -#else - #error "Cannot compile - ArduinoTimers library does not support your board, or you are missing compatible build flags." -#endif - -#endif diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index c0c3a1d..0e282d1 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -1,15 +1,49 @@ //////////////////////////////////////////////////////////////////////////////////// -// © 2020, Chris Harlow. All rights reserved. +// DCC-EX CommandStation-EX Please see https://DCC-EX.com // -// This file is a demonstattion of setting up a DCC-EX -// Command station with optional support for direct connection of WiThrottle devices -// such as "Engine Driver". If you contriol your layout through JMRI -// then DON'T connect throttles to this wifi, connect them to JMRI. +// This file is the main sketch for the Command Station. +// +// CONFIGURATION: +// Configuration is normally performed by editing a file called config.h. +// This file is NOT shipped with the code so that if you pull a later version +// of the code, your configuration will not be overwritten. // -// THE WIFI FEATURE IS NOT SUPPORTED ON ARDUINO DEVICES WITH ONLY 2KB RAM. +// If you used the automatic installer program, config.h will have been created automatically. +// +// To obtain a starting copy of config.h please copy the file config.example.h which is +// shipped with the code and may be updated as new features are added. +// +// If config.h is not found, config.example.h will be used with all defaults. //////////////////////////////////////////////////////////////////////////////////// -#include "config.h" +#if __has_include ( "config.h") + #include "config.h" +#else + #warning config.h not found. Using defaults from config.example.h + #include "config.example.h" +#endif + + +/* + * © 2020,2021 Chris Harlow, Harald Barth, David Cutting, + * Fred Decker, Gregor Baues, Anthony W - Dayton All rights reserved. + * + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + + #include "DCCEX.h" // Create a serial command parser for the USB connection, @@ -24,10 +58,9 @@ void setup() // Responsibility 1: Start the usb connection for diagnostics // This is normally Serial but uses SerialUSB on a SAMD processor Serial.begin(115200); - DIAG(F("DCC++ EX v%S"),F(VERSION)); CONDITIONAL_LCD_START { - // This block is ignored if LCD not in use + // This block is still executed for DIAGS if LCD not in use LCD(0,F("DCC++ EX v%S"),F(VERSION)); LCD(1,F("Starting")); } @@ -35,7 +68,7 @@ void setup() // Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi #if WIFI_ON - WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT); + WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL); #endif // WIFI_ON #if ETHERNET_ON @@ -49,9 +82,7 @@ void setup() // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - + DCC::begin(MOTOR_SHIELD_TYPE); #if defined(RMFT_ACTIVE) diff --git a/DCC.cpp b/DCC.cpp index b658fce..799964c 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -17,12 +17,13 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ +#include "DIAG.h" #include "DCC.h" #include "DCCWaveform.h" -#include "DIAG.h" #include "EEStore.h" #include "GITHUB_SHA.h" #include "version.h" +#include "FSH.h" // This module is responsible for converting API calls into // messages to be sent to the waveform generator. @@ -43,17 +44,24 @@ const byte FN_GROUP_3=0x04; const byte FN_GROUP_4=0x08; const byte FN_GROUP_5=0x10; -__FlashStringHelper* DCC::shieldName=NULL; +FSH* DCC::shieldName=NULL; +byte DCC::joinRelay=UNUSED_PIN; -void DCC::begin(const __FlashStringHelper* motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, byte timerNumber) { - shieldName=(__FlashStringHelper*)motorShieldName; +void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver, + byte joinRelayPin) { + shieldName=(FSH *)motorShieldName; DIAG(F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); + joinRelay=joinRelayPin; + if (joinRelay!=UNUSED_PIN) { + pinMode(joinRelay,OUTPUT); + digitalWrite(joinRelay,LOW); // high is relay disengaged + } // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused EEStore::init(); - DCCWaveform::begin(mainDriver,progDriver, timerNumber); + DCCWaveform::begin(mainDriver,progDriver); } void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) { @@ -222,24 +230,25 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) { } void DCC::setProgTrackSyncMain(bool on) { + if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW); DCCWaveform::progTrackSyncMain=on; } void DCC::setProgTrackBoost(bool on) { DCCWaveform::progTrackBoosted=on; } -__FlashStringHelper* DCC::getMotorShieldName() { +FSH* DCC::getMotorShieldName() { return shieldName; } -const ackOp PROGMEM WRITE_BIT0_PROG[] = { +const ackOp FLASH WRITE_BIT0_PROG[] = { BASELINE, W0,WACK, V0, WACK, // validate bit is 0 ITC1, // if acked, callback(1) FAIL // callback (-1) }; -const ackOp PROGMEM WRITE_BIT1_PROG[] = { +const ackOp FLASH WRITE_BIT1_PROG[] = { BASELINE, W1,WACK, V1, WACK, // validate bit is 1 @@ -247,7 +256,7 @@ const ackOp PROGMEM WRITE_BIT1_PROG[] = { FAIL // callback (-1) }; -const ackOp PROGMEM VERIFY_BIT0_PROG[] = { +const ackOp FLASH VERIFY_BIT0_PROG[] = { BASELINE, V0, WACK, // validate bit is 0 ITC0, // if acked, callback(0) @@ -255,7 +264,7 @@ const ackOp PROGMEM VERIFY_BIT0_PROG[] = { ITC1, FAIL // callback (-1) }; -const ackOp PROGMEM VERIFY_BIT1_PROG[] = { +const ackOp FLASH VERIFY_BIT1_PROG[] = { BASELINE, V1, WACK, // validate bit is 1 ITC1, // if acked, callback(1) @@ -264,7 +273,7 @@ const ackOp PROGMEM VERIFY_BIT1_PROG[] = { FAIL // callback (-1) }; -const ackOp PROGMEM READ_BIT_PROG[] = { +const ackOp FLASH READ_BIT_PROG[] = { BASELINE, V1, WACK, // validate bit is 1 ITC1, // if acked, callback(1) @@ -273,7 +282,7 @@ const ackOp PROGMEM READ_BIT_PROG[] = { FAIL // bit not readable }; -const ackOp PROGMEM WRITE_BYTE_PROG[] = { +const ackOp FLASH WRITE_BYTE_PROG[] = { BASELINE, WB,WACK, // Write VB,WACK, // validate byte @@ -281,7 +290,7 @@ const ackOp PROGMEM WRITE_BYTE_PROG[] = { FAIL // callback (-1) }; -const ackOp PROGMEM VERIFY_BYTE_PROG[] = { +const ackOp FLASH VERIFY_BYTE_PROG[] = { BASELINE, VB,WACK, // validate byte ITCB, // if ok callback value @@ -306,7 +315,7 @@ const ackOp PROGMEM VERIFY_BYTE_PROG[] = { FAIL }; -const ackOp PROGMEM READ_CV_PROG[] = { +const ackOp FLASH READ_CV_PROG[] = { BASELINE, STARTMERGE, //clear bit and byte values ready for merge pass // each bit is validated against 0 and the result inverted in MERGE @@ -329,7 +338,7 @@ const ackOp PROGMEM READ_CV_PROG[] = { FAIL }; // verification failed -const ackOp PROGMEM LOCO_ID_PROG[] = { +const ackOp FLASH LOCO_ID_PROG[] = { BASELINE, SETCV, (ackOp)1, SETBIT, (ackOp)7, @@ -399,7 +408,7 @@ const ackOp PROGMEM LOCO_ID_PROG[] = { FAIL }; -const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = { +const ackOp FLASH SHORT_LOCO_ID_PROG[] = { BASELINE, SETCV,(ackOp)19, SETBYTE, (ackOp)0, @@ -415,7 +424,7 @@ const ackOp PROGMEM SHORT_LOCO_ID_PROG[] = { FAIL }; -const ackOp PROGMEM LONG_LOCO_ID_PROG[] = { +const ackOp FLASH LONG_LOCO_ID_PROG[] = { BASELINE, // Clear consist CV 19 SETCV,(ackOp)19, @@ -501,7 +510,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders byte DCC::loopStatus=0; void DCC::loop() { - DCCWaveform::loop(); // power overload checks + DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks ackManagerLoop(false); // maintain prog track ack manager issueReminders(); } @@ -659,7 +668,7 @@ bool DCC::checkResets(bool blocking, uint8_t numResets) { void DCC::ackManagerLoop(bool blocking) { while (ackManagerProg) { - byte opcode=pgm_read_byte_near(ackManagerProg); + byte opcode=GETFLASH(ackManagerProg); // breaks from this switch will step to next prog entry // returns from this switch will stay on same entry @@ -786,17 +795,17 @@ void DCC::ackManagerLoop(bool blocking) { case SETBIT: ackManagerProg++; - ackManagerBitNum=pgm_read_byte_near(ackManagerProg); + ackManagerBitNum=GETFLASH(ackManagerProg); break; case SETCV: ackManagerProg++; - ackManagerCv=pgm_read_byte_near(ackManagerProg); + ackManagerCv=GETFLASH(ackManagerProg); break; case SETBYTE: ackManagerProg++; - ackManagerByte=pgm_read_byte_near(ackManagerProg); + ackManagerByte=GETFLASH(ackManagerProg); break; case SETBYTEH: @@ -822,9 +831,7 @@ void DCC::ackManagerLoop(bool blocking) { // SKIP opcodes until SKIPTARGET found while (opcode!=SKIPTARGET) { ackManagerProg++; - opcode=pgm_read_byte_near(ackManagerProg); - // Jump over second byte of any 2-byte opcodes. - if (opcode==SETBIT || opcode==SETBYTE || opcode==SETCV) ackManagerProg++; + opcode=GETFLASH(ackManagerProg); } break; case SKIPTARGET: diff --git a/DCC.h b/DCC.h index d8a1b6b..f1c4aa0 100644 --- a/DCC.h +++ b/DCC.h @@ -21,10 +21,10 @@ #include #include "MotorDriver.h" #include "MotorDrivers.h" - +#include "FSH.h" typedef void (*ACK_CALLBACK)(int result); -enum ackOp +enum ackOp : byte { // Program opcodes for the ack Manager BASELINE, // ensure enough resets sent before starting and obtain baseline current W0, @@ -64,7 +64,8 @@ const byte MAX_LOCOS = 50; class DCC { public: - static void begin(const __FlashStringHelper *motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, byte timerNumber = 1); + static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver, + byte joinRelayPin=UNUSED_PIN); static void loop(); // Public DCC API functions @@ -99,7 +100,7 @@ public: static void forgetAllLocos(); // removes all speed reminders static void displayCabList(Print *stream); - static __FlashStringHelper *getMotorShieldName(); + static FSH *getMotorShieldName(); private: struct LOCO @@ -109,13 +110,14 @@ private: byte groupFlags; unsigned long functions; }; + static byte joinRelay; static byte loopStatus; static void setThrottle2(uint16_t cab, uint8_t speedCode); static void updateLocoReminder(int loco, byte speedCode); static void setFunctionInternal(int cab, byte fByte, byte eByte); static bool issueReminder(int reg); static int nextLoco; - static __FlashStringHelper *shieldName; + static FSH *shieldName; static LOCO speedTable[MAX_LOCOS]; static byte cv1(byte opcode, int cv); @@ -162,6 +164,8 @@ private: #define ARDUINO_TYPE "NANO" #elif defined(ARDUINO_AVR_MEGA2560) #define ARDUINO_TYPE "MEGA" +#elif defined(ARDUINO_ARCH_MEGAAVR) +#define ARDUINO_TYPE "MEGAAVR" #else #error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560 #endif diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 010cf8b..9b996e7 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -52,7 +52,7 @@ const int HASH_KEYWORD_ETHERNET = -30767; const int HASH_KEYWORD_MAX = 16244; const int HASH_KEYWORD_MIN = 15978; -int DCCEXParser::stashP[MAX_PARAMS]; +int DCCEXParser::stashP[MAX_COMMAND_PARAMS]; bool DCCEXParser::stashBusy; Print *DCCEXParser::stashStream = NULL; @@ -102,7 +102,7 @@ void DCCEXParser::loop(Stream &stream) Sensor::checkAll(&stream); // Update and print changes } -int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) +int DCCEXParser::splitValues(int result[MAX_COMMAND_PARAMS], const byte *cmd) { byte state = 1; byte parameterCount = 0; @@ -111,10 +111,10 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) bool signNegative = false; // clear all parameters in case not enough found - for (int i = 0; i < MAX_PARAMS; i++) + for (int i = 0; i < MAX_COMMAND_PARAMS; i++) result[i] = 0; - while (parameterCount < MAX_PARAMS) + while (parameterCount < MAX_COMMAND_PARAMS) { byte hot = *remainingCmd; @@ -143,6 +143,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) runningValue = 10 * runningValue + (hot - '0'); break; } + if (hot >= 'a' && hot <= 'z') hot=hot-'a'+'A'; // uppercase a..z if (hot >= 'A' && hot <= 'Z') { // Since JMRI got modified to send keywords in some rare cases, we need this @@ -160,7 +161,7 @@ int DCCEXParser::splitValues(int result[MAX_PARAMS], const byte *cmd) return parameterCount; } -int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd) +int DCCEXParser::splitHexValues(int result[MAX_COMMAND_PARAMS], const byte *cmd) { byte state = 1; byte parameterCount = 0; @@ -168,10 +169,10 @@ int DCCEXParser::splitHexValues(int result[MAX_PARAMS], const byte *cmd) const byte *remainingCmd = cmd + 1; // skips the opcode // clear all parameters in case not enough found - for (int i = 0; i < MAX_PARAMS; i++) + for (int i = 0; i < MAX_COMMAND_PARAMS; i++) result[i] = 0; - while (parameterCount < MAX_PARAMS) + while (parameterCount < MAX_COMMAND_PARAMS) { byte hot = *remainingCmd; @@ -237,7 +238,7 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback) } // Parse an F() string -void DCCEXParser::parse(const __FlashStringHelper * cmd) { +void DCCEXParser::parse(const FSH * cmd) { int size=strlen_P((char *)cmd)+1; char buffer[size]; strcpy_P(buffer,(char *)cmd); @@ -250,7 +251,7 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) (void)EEPROM; // tell compiler not to warn this is unused if (Diag::CMD) DIAG(F("\nPARSING:%s\n"), com); - int p[MAX_PARAMS]; + int p[MAX_COMMAND_PARAMS]; while (com[0] == '<' || com[0] == ' ') com++; // strip off any number of < or spaces byte params = splitValues(p, com); @@ -314,12 +315,33 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) return; break; - case 'a': // ACCESSORY - if (p[2] != (p[2] & 1)) - return; - DCC::setAccessory(p[0], p[1], p[2] == 1); + case 'a': // ACCESSORY or + { + int address; + byte subaddress; + byte activep; + if (params==2) { // + address=(p[0] - 1) / 4 + 1; + subaddress=(p[0] - 1) % 4; + activep=1; + } + else if (params==3) { // + address=p[0]; + subaddress=p[1]; + activep=2; + } + else break; // invalid no of parameters + + if ( + ((address & 0x01FF) != address) // invalid address (limit 9 bits ) + || ((subaddress & 0x03) != subaddress) // invalid subaddress (limit 2 bits ) + || ((p[activep] & 0x01) != p[activep]) // invalid activate 0|1 + ) break; + + DCC::setAccessory(address, subaddress,p[activep]==1); + } return; - + case 'T': // TURNOUT if (parseT(stream, params, p)) return; @@ -414,7 +436,8 @@ void DCCEXParser::parse(Print *stream, byte *com, bool blocking) { POWERMODE mode = opcode == '1' ? POWERMODE::ON : POWERMODE::OFF; DCC::setProgTrackSyncMain(false); // Only <1 JOIN> will set this on, all others set it off - if (params == 0) + if (params == 0 || + (MotorDriver::commonFaultPin && p[0] != HASH_KEYWORD_JOIN)) // commonFaultPin prevents individual track handling { DCCWaveform::mainTrack.setPowerMode(mode); DCCWaveform::progTrack.setPowerMode(mode); @@ -732,10 +755,6 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) Diag::WITHROTTLE = onOff; return true; - case HASH_KEYWORD_DCC: - DCCWaveform::setDiagnosticSlowWave(params >= 1 && p[1] == HASH_KEYWORD_SLOW); - return true; - case HASH_KEYWORD_PROGBOOST: DCC::setProgTrackBoost(true); return true; @@ -752,13 +771,13 @@ bool DCCEXParser::parseD(Print *stream, int params, int p[]) } // CALLBACKS must be static -bool DCCEXParser::stashCallback(Print *stream, int p[MAX_PARAMS]) +bool DCCEXParser::stashCallback(Print *stream, int p[MAX_COMMAND_PARAMS]) { if (stashBusy ) return false; stashBusy = true; stashStream = stream; - memcpy(stashP, p, MAX_PARAMS * sizeof(p[0])); + memcpy(stashP, p, MAX_COMMAND_PARAMS * sizeof(p[0])); return true; } void DCCEXParser::callback_W(int result) diff --git a/DCCEXParser.h b/DCCEXParser.h index c9197de..c1dfd13 100644 --- a/DCCEXParser.h +++ b/DCCEXParser.h @@ -19,6 +19,7 @@ #ifndef DCCEXParser_h #define DCCEXParser_h #include +#include "FSH.h" typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int p[]); typedef void (*AT_COMMAND_CALLBACK)(const byte * command); @@ -28,12 +29,12 @@ struct DCCEXParser DCCEXParser(); void loop(Stream & stream); void parse(Print * stream, byte * command, bool blocking); - void parse(const __FlashStringHelper * cmd); + void parse(const FSH * cmd); void flush(); static void setFilter(FILTER_CALLBACK filter); static void setRMFTFilter(FILTER_CALLBACK filter); static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); - static const int MAX_PARAMS=10; // Must not exceed this + static const int MAX_COMMAND_PARAMS=10; // Must not exceed this private: @@ -41,8 +42,8 @@ struct DCCEXParser byte bufferLength=0; bool inCommandPayload=false; byte buffer[MAX_BUFFER+2]; - int splitValues( int result[MAX_PARAMS], const byte * command); - int splitHexValues( int result[MAX_PARAMS], const byte * command); + int splitValues( int result[MAX_COMMAND_PARAMS], const byte * command); + int splitHexValues( int result[MAX_COMMAND_PARAMS], const byte * command); bool parseT(Print * stream, int params, int p[]); bool parseZ(Print * stream, int params, int p[]); @@ -54,8 +55,8 @@ struct DCCEXParser static bool stashBusy; static Print * stashStream; - static int stashP[MAX_PARAMS]; - bool stashCallback(Print * stream, int p[MAX_PARAMS]); + static int stashP[MAX_COMMAND_PARAMS]; + bool stashCallback(Print * stream, int p[MAX_COMMAND_PARAMS]); static void callback_W(int result); static void callback_B(int result); static void callback_R(int result); diff --git a/DCCTimer.cpp b/DCCTimer.cpp new file mode 100644 index 0000000..46cd2ea --- /dev/null +++ b/DCCTimer.cpp @@ -0,0 +1,145 @@ +/* + * © 2021, Chris Harlow & David Cutting. All rights reserved. + * + * This file is part of Asbelos DCC API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + + +/* This timer class is used to manage the single timer required to handle the DCC waveform. + * All timer access comes through this class so that it can be compiled for + * various hardware CPU types. + * + * DCCEX works on a single timer interrupt at a regular 58uS interval. + * The DCCWaveform class generates the signals to the motor shield + * based on this timer. + * + * If the motor drivers are BOTH configured to use the correct 2 pins for the architecture, + * (see isPWMPin() function. ) + * then this allows us to use a hardware driven pin switching arrangement which is + * achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on + * the required pin state. (see setPWM()) + * This is more accurate than the software interrupt but at the expense of + * limiting the choice of available pins. + * Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM... + * Other shields may be jumpered to PWM pins or run directly using the software interrupt. + * + * Because the PWM-based waveform is effectively set half a cycle after the software version, + * it is not acceptable to drive the two tracks on different methiods or it would cause + * problems for <1 JOIN> etc. + * + */ + +#include "DCCTimer.h" +const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle +const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1; + +INTERRUPT_CALLBACK interruptHandler=0; + +#ifdef ARDUINO_ARCH_MEGAAVR + // Arduino unoWifi Rev2 and nanoEvery architectire + + void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time + TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled + TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us + TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset + TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag + TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt + TCB0.CNT = 0; + TCB0.CTRLA |= TCB_ENABLE_bm; // start + interrupts(); + } + + // ISR called by timer interrupt every 58uS + ISR(TCB0_INT_vect){ + TCB0.INTFLAGS = TCB_CAPT_bm; + interruptHandler(); + } + + bool DCCTimer::isPWMPin(byte pin) { + return false; // TODO what are the relevant pins? + } + + void DCCTimer::setPWM(byte pin, bool high) { + // TODO what are the relevant pins? + } + + void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number + } + +#else + // Arduino nano, uno, mega etc +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + #define TIMER1_A_PIN 11 + #define TIMER1_B_PIN 12 + #define TIMER1_C_PIN 13 +#else + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 +#endif + + void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time + TCCR1A = 0; + ICR1 = CLOCK_CYCLES; + TCNT1 = 0; + TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1 + TIMSK1 = _BV(TOIE1); // Enable Software interrupt + interrupts(); + } + +// ISR called by timer interrupt every 58uS + ISR(TIMER1_OVF_vect){ interruptHandler(); } + +// Alternative pin manipulation via PWM control. + bool DCCTimer::isPWMPin(byte pin) { + return pin==TIMER1_A_PIN + || pin==TIMER1_B_PIN + #ifdef TIMER1_C_PIN + || pin==TIMER1_C_PIN + #endif + ; + } + + void DCCTimer::setPWM(byte pin, bool high) { + if (pin==TIMER1_A_PIN) { + TCCR1A |= _BV(COM1A1); + OCR1A= high?1024:0; + } + else if (pin==TIMER1_B_PIN) { + TCCR1A |= _BV(COM1B1); + OCR1B= high?1024:0; + } + #ifdef TIMER1_C_PIN + else if (pin==TIMER1_C_PIN) { + TCCR1A |= _BV(COM1C1); + OCR1C= high?1024:0; + } + #endif + } + + + #include + void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + for (byte i=0; i<6; i++) mac[i]=boot_signature_byte_get(0x0E + i); + } + +#endif diff --git a/DCCTimer.h b/DCCTimer.h new file mode 100644 index 0000000..dfe9ef2 --- /dev/null +++ b/DCCTimer.h @@ -0,0 +1,16 @@ +#ifndef DCCTimer_h +#define DCCTimer_h +#include "Arduino.h" + +typedef void (*INTERRUPT_CALLBACK)(); + +class DCCTimer { + public: + static void begin(INTERRUPT_CALLBACK interrupt); + static void getSimulatedMacAddress(byte mac[6]); + static bool isPWMPin(byte pin); + static void setPWM(byte pin, bool high); + private: +}; + +#endif diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index 9eb7022..c1e0269 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -17,13 +17,13 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ + #pragma GCC optimize ("-O3") #include #include "DCCWaveform.h" +#include "DCCTimer.h" #include "DIAG.h" -const int NORMAL_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle -const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512; DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true); DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); @@ -31,53 +31,51 @@ DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false); bool DCCWaveform::progTrackSyncMain=false; bool DCCWaveform::progTrackBoosted=false; -VirtualTimer * DCCWaveform::interruptTimer=NULL; +int DCCWaveform::progTripValue=0; -void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) { +void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) { mainTrack.motorDriver=mainDriver; progTrack.motorDriver=progDriver; - + progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static mainTrack.setPowerMode(POWERMODE::OFF); progTrack.setPowerMode(POWERMODE::OFF); - switch (timerNumber) { - case 1: interruptTimer= &TimerA; break; - case 2: interruptTimer= &TimerB; break; -#ifndef ARDUINO_AVR_UNO - case 3: interruptTimer= &TimerC; break; -#endif - default: - DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid. DCC will not work.*** \n\n"), timerNumber); - return; - } - interruptTimer->initialize(); - interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle - interruptTimer->attachInterrupt(interruptHandler); - interruptTimer->start(); -} -void DCCWaveform::setDiagnosticSlowWave(bool slow) { - interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME); - interruptTimer->start(); - DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); + // Fault pin config for odd motor boards (example pololu) + MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin()) + && (mainDriver->getFaultPin() != UNUSED_PIN)); + // Only use PWM if both pins are PWM capable. Otherwise JOIN does not work + MotorDriver::usePWM= mainDriver->isPWMCapable() && progDriver->isPWMCapable(); + if (MotorDriver::usePWM) + DIAG(F("\nWaveform using PWM pins for accuracy.")); + else + DIAG(F("\nWaveform accuracy limited by signal pin configuration.")); + DCCTimer::begin(DCCWaveform::interruptHandler); } -void DCCWaveform::loop() { - mainTrack.checkPowerOverload(); - progTrack.checkPowerOverload(); +void DCCWaveform::loop(bool ackManagerActive) { + mainTrack.checkPowerOverload(false); + progTrack.checkPowerOverload(ackManagerActive); } - -// static // void DCCWaveform::interruptHandler() { // call the timer edge sensitive actions for progtrack and maintrack - bool mainCall2 = mainTrack.interrupt1(); - bool progCall2 = progTrack.interrupt1(); + // member functions would be cleaner but have more overhead + byte sigMain=signalTransform[mainTrack.state]; + byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state]; + + // Set the signal state for both tracks + mainTrack.motorDriver->setSignal(sigMain); + progTrack.motorDriver->setSignal(sigProg); + + // Move on in the state engine + mainTrack.state=stateTransform[mainTrack.state]; + progTrack.state=stateTransform[progTrack.state]; + + + // WAVE_PENDING means we dont yet know what the next bit is + if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2(); + if (progTrack.state==WAVE_PENDING) progTrack.interrupt2(); + else if (progTrack.ackPending) progTrack.checkAck(); - // call (if necessary) the procs to get the current bits - // these must complete within 50microsecs of the interrupt - // but they are only called ONCE PER BIT TRANSMITTED - // after the rising edge of the signal - if (mainCall2) mainTrack.interrupt2(); - if (progCall2) progTrack.interrupt2(); } @@ -92,13 +90,12 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) { - // establish appropriate pins isMainTrack = isMain; packetPending = false; memcpy(transmitPacket, idlePacket, sizeof(idlePacket)); - state = 0; + state = WAVE_START; // The +1 below is to allow the preamble generator to create the stop bit - // fpr the previous packet. + // for the previous packet. requiredPreambles = preambleBits+1; bytes_sent = 0; bits_sent = 0; @@ -112,24 +109,17 @@ POWERMODE DCCWaveform::getPowerMode() { } void DCCWaveform::setPowerMode(POWERMODE mode) { - - // Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away. - if (!interruptTimer) return; - powerMode = mode; bool ison = (mode == POWERMODE::ON); motorDriver->setPower( ison); } -void DCCWaveform::checkPowerOverload() { - - static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static - +void DCCWaveform::checkPowerOverload(bool ackManagerActive) { if (millis() - lastSampleTaken < sampleDelay) return; lastSampleTaken = millis(); int tripValue= motorDriver->getRawCurrentTripValue(); - if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted) + if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted) tripValue=progTripValue; switch (powerMode) { @@ -138,8 +128,26 @@ void DCCWaveform::checkPowerOverload() { break; case POWERMODE::ON: // Check current - lastCurrent = motorDriver->getCurrentRaw(); - if (lastCurrent <= tripValue) { + lastCurrent=motorDriver->getCurrentRaw(); + if (lastCurrent < 0) { + // We have a fault pin condition to take care of + lastCurrent = -lastCurrent; + setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again + if (MotorDriver::commonFaultPin) { + if (lastCurrent <= tripValue) { + setPowerMode(POWERMODE::ON); // maybe other track + } + // Write this after the fact as we want to turn on as fast as possible + // because we don't know which output actually triggered the fault pin + DIAG(F("\n*** COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S ***\n"), isMainTrack ? F("MAIN") : F("PROG")); + } else { + DIAG(F("\n*** %S FAULT PIN ACTIVE - OVERLOAD ***\n"), isMainTrack ? F("MAIN") : F("PROG")); + if (lastCurrent < tripValue) { + lastCurrent = tripValue; // exaggerate + } + } + } + if (lastCurrent < tripValue) { sampleDelay = POWER_SAMPLE_ON_WAIT; if(power_good_counter<100) power_good_counter++; @@ -149,9 +157,9 @@ void DCCWaveform::checkPowerOverload() { setPowerMode(POWERMODE::OVERLOAD); unsigned int mA=motorDriver->raw2mA(lastCurrent); unsigned int maxmA=motorDriver->raw2mA(tripValue); - DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait); power_good_counter=0; sampleDelay = power_sample_overload_wait; + DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d offtime=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, sampleDelay); if (power_sample_overload_wait >= 10000) power_sample_overload_wait = 10000; else @@ -162,77 +170,45 @@ void DCCWaveform::checkPowerOverload() { // Try setting it back on after the OVERLOAD_WAIT setPowerMode(POWERMODE::ON); sampleDelay = POWER_SAMPLE_ON_WAIT; + // Debug code.... + DIAG(F("\n*** %S TRACK POWER RESET delay=%d ***\n"), isMainTrack ? F("MAIN") : F("PROG"), sampleDelay); break; default: sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning. } } +// For each state of the wave nextState=stateTransform[currentState] +const WAVE_STATE DCCWaveform::stateTransform[]={ + /* WAVE_START -> */ WAVE_PENDING, + /* WAVE_MID_1 -> */ WAVE_START, + /* WAVE_HIGH_0 -> */ WAVE_MID_0, + /* WAVE_MID_0 -> */ WAVE_LOW_0, + /* WAVE_LOW_0 -> */ WAVE_START, + /* WAVE_PENDING (should not happen) -> */ WAVE_PENDING}; - - - - -// process time-edge sensitive part of interrupt -// return true if second level required -bool DCCWaveform::interrupt1() { - // NOTE: this must consume transmission buffers even if the power is off - // otherwise can cause hangs in main loop waiting for the pendingBuffer. - switch (state) { - case 0: // start of bit transmission - setSignal(HIGH); - state = 1; - return true; // must call interrupt2 to set currentBit - - case 1: // 58us after case 0 - if (currentBit) { - setSignal(LOW); - state = 0; - } - else { - setSignal(HIGH); // jitter prevention - state = 2; - } - break; - case 2: // 116us after case 0 - setSignal(LOW); - state = 3; - break; - case 3: // finished sending zero bit - setSignal(LOW); // jitter prevention - state = 0; - break; - } - - // ACK check is prog track only and will only be checked if - // this is not case(0) which needs relatively expensive packet change code to be called. - if (ackPending) checkAck(); - - return false; - -} - -void DCCWaveform::setSignal(bool high) { - if (progTrackSyncMain) { - if (!isMainTrack) return; // ignore PROG track waveform while in sync - // set both tracks to same signal - motorDriver->setSignal(high); - progTrack.motorDriver->setSignal(high); - return; - } - motorDriver->setSignal(high); -} - +// For each state of the wave, signal pin is HIGH or LOW +const bool DCCWaveform::signalTransform[]={ + /* WAVE_START -> */ HIGH, + /* WAVE_MID_1 -> */ LOW, + /* WAVE_HIGH_0 -> */ HIGH, + /* WAVE_MID_0 -> */ LOW, + /* WAVE_LOW_0 -> */ LOW, + /* WAVE_PENDING (should not happen) -> */ LOW}; + void DCCWaveform::interrupt2() { - // set currentBit to be the next bit to be sent. + // calculate the next bit to be sent: + // set state WAVE_MID_1 for a 1=bit + // or WAVE_HIGH_0 for a 0 bit. if (remainingPreambles > 0 ) { - currentBit = true; + state=WAVE_MID_1; // switch state to trigger LOW on next interrupt remainingPreambles--; return; } + // Wave has gone HIGH but what happens next depends on the bit to be transmitted // beware OF 9-BIT MASK generating a zero to start each byte - currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent]; + state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0; bits_sent++; // If this is the last bit of a byte, prepare for the next byte @@ -252,7 +228,10 @@ void DCCWaveform::interrupt2() { } else if (packetPending) { // Copy pending packet to transmit packet - for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b]; + // a fixed length memcpy is faster than a variable length loop for these small lengths + // for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b]; + memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket)); + transmitLength = pendingLength; transmitRepeats = pendingRepeats; packetPending = false; @@ -277,7 +256,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea while (packetPending); byte checksum = 0; - for (int b = 0; b < byteCount; b++) { + for (byte b = 0; b < byteCount; b++) { checksum ^= buffer[b]; pendingPacket[b] = buffer[b]; } @@ -288,16 +267,12 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea sentResetsSincePacket=0; } -int DCCWaveform::getLastCurrent() { - return lastCurrent; -} - // Operations applicable to PROG track ONLY. // (yes I know I could have subclassed the main track but...) void DCCWaveform::setAckBaseline() { if (isMainTrack) return; - int baseline = motorDriver->getCurrentRaw(); + int baseline=motorDriver->getCurrentRaw(); ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA); if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"), baseline,motorDriver->raw2mA(baseline), @@ -325,18 +300,17 @@ byte DCCWaveform::getAck() { void DCCWaveform::checkAck() { // This function operates in interrupt() time so must be fast and can't DIAG - if (sentResetsSincePacket > 6) { //ACK timeout ackCheckDuration=millis()-ackCheckStart; ackPending = false; return; } - lastCurrent=motorDriver->getCurrentRaw(); - if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent; + int current=motorDriver->getCurrentRaw(); + if (current > ackMaxCurrent) ackMaxCurrent=current; // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba) - if (lastCurrent>ackThreshold) { + if (current>ackThreshold) { if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected return; } diff --git a/DCCWaveform.h b/DCCWaveform.h index 7908e02..aad63ae 100644 --- a/DCCWaveform.h +++ b/DCCWaveform.h @@ -20,7 +20,6 @@ #ifndef DCCWaveform_h #define DCCWaveform_h #include "MotorDriver.h" -#include "ArduinoTimers.h" // Wait times for power management. Unit: milliseconds const int POWER_SAMPLE_ON_WAIT = 100; @@ -30,15 +29,18 @@ const int POWER_SAMPLE_OVERLOAD_WAIT = 20; // Number of preamble bits. const int PREAMBLE_BITS_MAIN = 16; const int PREAMBLE_BITS_PROG = 22; +const byte MAX_PACKET_SIZE = 5; // NMRA standard exrtended packets + +// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic +// to the transform array. +enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5}; - -const byte MAX_PACKET_SIZE = 12; // NOTE: static functions are used for the overall controller, then // one instance is created for each track. -enum class POWERMODE { OFF, ON, OVERLOAD }; +enum class POWERMODE : byte { OFF, ON, OVERLOAD }; const byte idlePacket[] = {0xFF, 0x00, 0xFF}; const byte resetPacket[] = {0x00, 0x00, 0x00}; @@ -46,17 +48,15 @@ const byte resetPacket[] = {0x00, 0x00, 0x00}; class DCCWaveform { public: DCCWaveform( byte preambleBits, bool isMain); - static void begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber); - static void setDiagnosticSlowWave(bool slow); - static void loop(); + static void begin(MotorDriver * mainDriver, MotorDriver * progDriver); + static void loop(bool ackManagerActive); static DCCWaveform mainTrack; static DCCWaveform progTrack; void beginTrack(); void setPowerMode(POWERMODE); POWERMODE getPowerMode(); - void checkPowerOverload(); - int getLastCurrent(); + void checkPowerOverload(bool ackManagerActive); inline int get1024Current() { if (powerMode == POWERMODE::ON) return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue()); @@ -105,12 +105,16 @@ class DCCWaveform { } private: - static VirtualTimer * interruptTimer; + +// For each state of the wave nextState=stateTransform[currentState] + static const WAVE_STATE stateTransform[6]; + +// For each state of the wave, signal pin is HIGH or LOW + static const bool signalTransform[6]; + static void interruptHandler(); - bool interrupt1(); void interrupt2(); void checkAck(); - void setSignal(bool high); bool isMainTrack; MotorDriver* motorDriver; @@ -120,15 +124,14 @@ class DCCWaveform { byte transmitRepeats; // remaining repeats of transmission byte remainingPreambles; byte requiredPreambles; - bool currentBit; // bit to be transmitted byte bits_sent; // 0-8 (yes 9 bits) sent for current byte byte bytes_sent; // number of bytes sent from transmitPacket - byte state; // wave generator state machine - + WAVE_STATE state; // wave generator state machine byte pendingPacket[MAX_PACKET_SIZE]; byte pendingLength; byte pendingRepeats; - int lastCurrent; + int lastCurrent; + static int progTripValue; int maxmA; int tripmA; diff --git a/DIAG.h b/DIAG.h index 6d16f78..1457796 100644 --- a/DIAG.h +++ b/DIAG.h @@ -18,6 +18,7 @@ */ #ifndef DIAG_h #define DIAG_h + #include "StringFormatter.h" #define DIAG StringFormatter::diag #define LCD StringFormatter::lcd diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index ebc21c4..38a0b24 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -17,13 +17,18 @@ * along with CommandStation. If not, see . * */ - -#include "config.h" -#include "defines.h" // This should be changed to DCCEX.h when possible +#if __has_include ( "config.h") + #include "config.h" +#else + #warning config.h not found. Using defaults from config.example.h + #include "config.example.h" +#endif +#include "defines.h" #if ETHERNET_ON == true #include "EthernetInterface.h" #include "DIAG.h" #include "CommandDistributor.h" +#include "DCCTimer.h" EthernetInterface * EthernetInterface::singleton=NULL; /** @@ -45,10 +50,15 @@ void EthernetInterface::setup() */ EthernetInterface::EthernetInterface() { - byte mac[]=MAC_ADDRESS; + byte mac[6]; + DCCTimer::getSimulatedMacAddress(mac); + DIAG(F("\n+++++ Ethernet Setup. Simulatd mac=")); + for (byte i=0;ibegin(); LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); - LCD(5,F("Port:%d"), LISTEN_PORT); + LCD(5,F("Port:%d"), IP_PORT); outboundRing=new RingStream(OUTBOUND_RING_SIZE); } diff --git a/EthernetInterface.h b/EthernetInterface.h index 7b4c2f8..87bb8b6 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -22,9 +22,13 @@ #ifndef EthernetInterface_h #define EthernetInterface_h - +#if __has_include ( "config.h") + #include "config.h" +#else + #warning config.h not found. Using defaults from config.example.h + #include "config.example.h" +#endif #include "DCCEXParser.h" -#include "MemStream.h" #include #include #include @@ -34,11 +38,7 @@ * @brief Network Configuration * */ -#ifndef MAC_ADDRESS -#error define MAC_ADDRESS in config.h -#endif -#define LISTEN_PORT 2560 // default listen port for the server #define MAX_ETH_BUFFER 512 #define OUTBOUND_RING_SIZE 2048 diff --git a/FSH.h b/FSH.h new file mode 100644 index 0000000..c6d787e --- /dev/null +++ b/FSH.h @@ -0,0 +1,30 @@ +#ifndef FSH_h +#define FSH_h + +/* This is an architecture support file to manage the differences + * between the nano/uno.mega and the later nanoEvery, unoWifiRev2 etc + * + * IMPORTANT: + * To maintain portability the main code should NOT contain ANY references + * to the following: + * + * __FlashStringHelper Use FSH instead. + * PROGMEM use FLASH instead + * pgm_read_byte_near use GETFLASH instead. + * + */ +#include +#if defined(ARDUINO_ARCH_MEGAAVR) +#ifdef F + #undef F +#endif +#define F(str) (str) +typedef char FSH; +#define GETFLASH(addr) (*(const unsigned char *)(addr)) +#define FLASH +#else +typedef __FlashStringHelper FSH; +#define GETFLASH(addr) pgm_read_byte_near(addr) +#define FLASH PROGMEM +#endif +#endif diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 096d2bb..70430b9 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "62d1f46" +#define GITHUB_SHA "fbb9841" diff --git a/I2CManager.cpp b/I2CManager.cpp new file mode 100644 index 0000000..676f2ce --- /dev/null +++ b/I2CManager.cpp @@ -0,0 +1,57 @@ +/* + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#include "I2CManager.h" + +// If not already initialised, initialise I2C (wire). +void I2CManagerClass::begin(void) { + if (!_beginCompleted) { + Wire.begin(); + _beginCompleted = true; + } +} + +// Set clock speed to the lowest requested one. If none requested, +// the Wire default is 100kHz. +void I2CManagerClass::setClock(uint32_t speed) { + if (speed < _clockSpeed && !_clockSpeedFixed) { + _clockSpeed = speed; + Wire.setClock(_clockSpeed); + } +} + +// Force clock speed to that specified. It can then only +// be overridden by calling Wire.setClock directly. +void I2CManagerClass::forceClock(uint32_t speed) { + if (!_clockSpeedFixed) { + _clockSpeed = speed; + _clockSpeedFixed = true; + Wire.setClock(_clockSpeed); + } +} + +// Check if specified I2C address is responding. +// Returns 0 if OK, or error code. +uint8_t I2CManagerClass::exists(uint8_t address) { + begin(); + Wire.beginTransmission(address); + return Wire.endTransmission(); +} + +I2CManagerClass I2CManager = I2CManagerClass(); \ No newline at end of file diff --git a/I2CManager.h b/I2CManager.h new file mode 100644 index 0000000..98dcfd6 --- /dev/null +++ b/I2CManager.h @@ -0,0 +1,61 @@ +/* + * © 2021, Neil McKechnie. All rights reserved. + * + * This file is part of CommandStation-EX + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ + +#ifndef I2CManager_h +#define I2CManager_h + +#include + +/* + * Helper class to manage access to the I2C 'Wire' subsystem. + * + * Helps to avoid calling Wire.begin() multiple times (which is not) + * entirely benign as it reinitialises). + * + * Also helps to avoid the Wire clock from being set, by another device + * driver, to a speed which is higher than a device supports. + * + * Thirdly, it provides a convenient way to check whether there is a + * device on a particular I2C address. + */ + +class I2CManagerClass { + +public: + + I2CManagerClass() {} + + // If not already initialised, initialise I2C (wire). + void begin(void); + // Set clock speed to the lowest requested one. + void setClock(uint32_t speed); + // Force clock speed + void forceClock(uint32_t speed); + // Check if specified I2C address is responding. + uint8_t exists(uint8_t address); + +private: + bool _beginCompleted = false; + bool _clockSpeedFixed = false; + uint32_t _clockSpeed = 1000000L; // 1MHz max on Arduino. +}; + +extern I2CManagerClass I2CManager; + +#endif \ No newline at end of file diff --git a/LCDDisplay.cpp b/LCDDisplay.cpp index e031f10..c559c76 100644 --- a/LCDDisplay.cpp +++ b/LCDDisplay.cpp @@ -1,6 +1,6 @@ /* - * © 2020, Chris Harlow. All rights reserved. - * + * © 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 @@ -17,63 +17,145 @@ * along with CommandStation. If not, see . */ -// CAUTION: the device dependent parts of this class are created in the .ini using LCD_Implementation.h +// CAUTION: the device dependent parts of this class are created in the .ini +// using LCD_Implementation.h + +/* The strategy for drawing the screen is as follows. + * 1) There are up to eight rows of text to be displayed. + * 2) Blank rows of text are ignored. + * 3) If there are more non-blank rows than screen lines, + * then all of the rows are displayed, with the rest of the + * screen being blank. + * 4) If there are fewer non-blank rows than screen lines, + * then a scrolling strategy is adopted so that, on each screen + * refresh, a different subset of the rows is presented. + * 5) On each entry into loop2(), a single operation is sent to the + * screen; this may be a position command or a character for + * display. This spreads the onerous work of updating the screen + * and ensures that other loop() functions in the application are + * not held up significantly. The exception to this is when + * the loop2() function is called with force=true, where + * a screen update is executed to completion. This is normally + * only done during start-up. + * The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2 + * in the config.h. + * #define SCROLLMODE 0 is scroll continuous (fill screen if poss), + * #define SCROLLMODE 1 is by page (alternate between pages), + * #define SCROLLMODE 2 is by row (move up 1 row at a time). + + */ + #include "LCDDisplay.h" - void LCDDisplay::clear() { - clearNative(); - for (byte row=0;row=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= 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) { + unsigned long currentMillis = millis(); + + if (!force) { + // See if we're in the time between updates + if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME) + return NULL; + } else { + // force full screen update from the beginning. + rowFirst = -1; + rowNext = 0; + bufferPointer = 0; + done = false; + slot = 0; + } + + do { + if (bufferPointer == 0) { + // Find a line of data to write to the screen. + if (rowFirst < 0) rowFirst = rowNext; + skipBlankRows(); + if (!done) { + // Non-blank line found, so copy it. + for (uint8_t i = 0; i < sizeof(buffer); i++) + buffer[i] = rowBuffer[rowNext][i]; + } else + buffer[0] = '\0'; // Empty line + setRowNative(slot); // Set position for display + charIndex = 0; + bufferPointer = &buffer[0]; + + } else { + + // Write next character, or a space to erase current position. + char ch = *bufferPointer; + if (ch) { + writeNative(ch); + bufferPointer++; + } else + writeNative(' '); + + if (++charIndex >= MAX_LCD_COLS) { + // Screen slot completed, move to next slot on screen + slot++; + bufferPointer = 0; + if (!done) { + moveToNextRow(); + skipBlankRows(); + } } - 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 - } + if (slot >= lcdRows) { + // Last slot finished, reset ready for next screen update. +#if SCROLLMODE==2 + if (!done) { + // On next refresh, restart one row on from previous start. + rowNext = rowFirst; + moveToNextRow(); + skipBlankRows(); + } +#endif + done = false; + slot = 0; + rowFirst = -1; + lastScrollTime = currentMillis; + return NULL; + } + } + } while (force); - - + return NULL; +} + +void LCDDisplay::moveToNextRow() { + rowNext = (rowNext + 1) % MAX_LCD_ROWS; +#if SCROLLMODE == 1 + // Finished if we've looped back to row 0 + if (rowNext == 0) done = true; +#else + // Finished if we're back to the first one shown + if (rowNext == rowFirst) done = true; +#endif +} + +void LCDDisplay::skipBlankRows() { + while (!done && rowBuffer[rowNext][0] == 0) + moveToNextRow(); +} \ No newline at end of file diff --git a/LCDDisplay.h b/LCDDisplay.h index 9abcca3..e75fd31 100644 --- a/LCDDisplay.h +++ b/LCDDisplay.h @@ -1,6 +1,6 @@ /* - * © 2020, Chris Harlow. All rights reserved. - * + * © 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 @@ -20,44 +20,61 @@ #define LCDDisplay_h #include +#if __has_include ( "config.h") + #include "config.h" +#endif + +// Allow maximum message length to be overridden from config.h +#if !defined(MAX_MSG_SIZE) +#define MAX_MSG_SIZE 16 +#endif + // This class is created in LCDisplay_Implementation.h class LCDDisplay : public Print { + public: + static const int MAX_LCD_ROWS = 8; + static const int MAX_LCD_COLS = MAX_MSG_SIZE; + static const long LCD_SCROLL_TIME = 3000; // 3 seconds - 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); + 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]; + // Internally handled functions + static void loop(); + LCDDisplay* loop2(bool force); + void setRow(byte line); + void clear(); + + virtual size_t write(uint8_t b); + using Print::write; + + private: + void moveToNextRow(); + void skipBlankRows(); + + // Relay functions to the live driver + void clearNative(); + void displayNative(); + void setRowNative(byte line); + void writeNative(char b); + + unsigned long lastScrollTime = 0; + int8_t hotRow = 0; + int8_t hotCol = 0; + int8_t topRow = 0; + uint8_t lcdRows; + uint8_t lcdCols; + int8_t slot = 0; + int8_t rowFirst = -1; + int8_t rowNext = 0; + int8_t charIndex = 0; + char buffer[MAX_LCD_COLS + 1]; + char* bufferPointer = 0; + bool done = false; + + char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1]; }; #endif diff --git a/LCD_Implementation.h b/LCD_Implementation.h index 45e0429..9dcae24 100644 --- a/LCD_Implementation.h +++ b/LCD_Implementation.h @@ -1,5 +1,5 @@ /* - * © 2020, Chris Harlow. All rights reserved. + * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -22,12 +22,11 @@ // // 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. +// which library is involved. //////////////////////////////////////////////////////////////////////////////////// #ifndef LCD_Implementation_h #define LCD_Implementation_h -#include "config.h" #include #include "LCDDisplay.h" @@ -50,7 +49,7 @@ LCDDisplay * LCDDisplay::lcdDisplay=0; #else #include "LCD_NONE.h" - #define CONDITIONAL_LCD_START if (false) /* NO LCD CONFIG */ + #define CONDITIONAL_LCD_START if (true) /* NO LCD CONFIG, but do the LCD macros to get DIAGS */ #endif #endif // LCD_Implementation_h diff --git a/LCD_LCD.h b/LCD_LCD.h index 6f88ddc..2f30c97 100644 --- a/LCD_LCD.h +++ b/LCD_LCD.h @@ -1,5 +1,5 @@ /* - * © 2020, Chris Harlow. All rights reserved. + * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with CommandStation. If not, see . */ - #include + #include "LiquidCrystal_I2C.h" LiquidCrystal_I2C LCDDriver(LCD_DRIVER); // set the LCD address, cols, rows // DEVICE SPECIFIC LCDDisplay Implementation for LCD_DRIVER LCDDisplay::LCDDisplay() { @@ -28,10 +28,6 @@ } 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::setRowNative(byte row) { LCDDriver.setCursor(0, row); } + void LCDDisplay::writeNative(char b){ LCDDriver.write(b); } void LCDDisplay::displayNative() { LCDDriver.display(); } diff --git a/LCD_NONE.h b/LCD_NONE.h index 65e05e4..a76ade4 100644 --- a/LCD_NONE.h +++ b/LCD_NONE.h @@ -1,5 +1,5 @@ /* - * © 2020, Chris Harlow. All rights reserved. + * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. * * This file is part of CommandStation-EX * @@ -22,6 +22,6 @@ void LCDDisplay::interfake(int p1, int p2, int p3) {(void)p1; (void)p2; (void)p3;} void LCDDisplay::setRowNative(byte row) { (void)row;} void LCDDisplay::clearNative() {} - void LCDDisplay::writeNative(char * b){ (void)b;} // + void LCDDisplay::writeNative(char b){ (void)b;} // void LCDDisplay::displayNative(){} diff --git a/LCD_OLED.h b/LCD_OLED.h index efa2bc7..4931997 100644 --- a/LCD_OLED.h +++ b/LCD_OLED.h @@ -1,6 +1,6 @@ /* - * © 2020, Chris Harlow. All rights reserved. - * + * © 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 @@ -17,41 +17,59 @@ * along with CommandStation. If not, see . */ -// 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 LCDDriver(OLED_DRIVER); - +// OLED Implementation of LCDDisplay class +// Note: this file is optionally included by LCD_Implementation.h +// It is NOT a .cpp file to prevent it being compiled and demanding libraries +// even when not needed. + +#include "I2CManager.h" +#include "SSD1306Ascii.h" +#include "Wire.h" +SSD1306AsciiWire LCDDriver; + // 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); + // Scan for device on 0x3c and 0x3d. + I2CManager.begin(); + I2CManager.setClock(400000L); // Set max supported I2C speed + for (byte address = 0x3c; address <= 0x3d; address++) { + byte error = I2CManager.exists(address); + if (!error) { + // Device found + DIAG(F("\nOLED display found at 0x%x"), address); + interfake(OLED_DRIVER, 0); + const DevType *devType; + if (lcdCols == 132) + devType = &SH1106_128x64; // Actually 132x64 but treated as 128x64 + else if (lcdCols == 128 && lcdRows == 4) + devType = &Adafruit128x32; + else + devType = &Adafruit128x64; + LCDDriver.begin(devType, address); + lcdDisplay = this; + LCDDriver.setFont(System5x7); // Normal 1:1 pixel scale, 8 bits high clear(); - return; - } - DIAG(F("\nOLED display not found\n")); + 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::interfake(int p1, int p2, int p3) { + lcdCols = p1; + lcdRows = p2 / 8; + (void)p3; +} - void LCDDisplay::clearNative() {LCDDriver.clearDisplay();} +void LCDDisplay::clearNative() { LCDDriver.clear(); } - 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(); } - +void LCDDisplay::setRowNative(byte row) { + // Positions text write to start of row 1..n and clears previous text + int y = row; + LCDDriver.setCursor(0, y); +} + +void LCDDisplay::writeNative(char b) { LCDDriver.write(b); } + +void LCDDisplay::displayNative() {} diff --git a/LiquidCrystal_I2C.cpp b/LiquidCrystal_I2C.cpp new file mode 100644 index 0000000..751892a --- /dev/null +++ b/LiquidCrystal_I2C.cpp @@ -0,0 +1,245 @@ +/* + * © 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 . + */ + +#include "LiquidCrystal_I2C.h" +#include "I2CManager.h" + +#include +#if defined(ARDUINO) && ARDUINO >= 100 + +#include "Arduino.h" + +#define printIIC(args) Wire.write(args) +inline size_t LiquidCrystal_I2C::write(uint8_t value) { + send(value, Rs); + return 1; +} + +#else +#include "WProgram.h" + +#define printIIC(args) Wire.send(args) +inline void LiquidCrystal_I2C::write(uint8_t value) { send(value, Rs); } + +#endif +#include "Wire.h" + +// When the display powers up, it is configured as follows: +// +// 1. Display clear +// 2. Function set: +// DL = 1; 8-bit interface data +// N = 0; 1-line display +// F = 0; 5x8 dot character font +// 3. Display on/off control: +// D = 0; Display off +// C = 0; Cursor off +// B = 0; Blinking off +// 4. Entry mode set: +// I/D = 1; Increment by 1 +// S = 0; No shift +// +// Note, however, that resetting the Arduino doesn't reset the LCD, so we +// can't assume that its in that state when a sketch starts (and the +// LiquidCrystal constructor is called). + +LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols, + uint8_t lcd_rows) { + _Addr = lcd_Addr; + _cols = lcd_cols; + _rows = lcd_rows; + _backlightval = LCD_NOBACKLIGHT; +} + +void LiquidCrystal_I2C::init() { init_priv(); } + +void LiquidCrystal_I2C::init_priv() { + I2CManager.begin(); + I2CManager.setClock(100000L); // PCF8574 is limited to 100kHz. + + _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; + begin(_cols, _rows); +} + +void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines) { + if (lines > 1) { + _displayfunction |= LCD_2LINE; + } + _numlines = lines; + (void)cols; // Suppress compiler warning. + + // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! + // according to datasheet, we need at least 40ms after power rises above 2.7V + // before sending commands. Arduino can turn on way befer 4.5V so we'll allow + // 100 milliseconds after pulling both RS and R/W and backlight pin low + expanderWrite( + _backlightval); // reset expander and turn backlight off (Bit 8 =1) + delay(100); + + // put the LCD into 4 bit mode + // this is according to the hitachi HD44780 datasheet + // figure 24, pg 46 + + // we start in 8bit mode, try to set 4 bit mode + write4bits(0x03 << 4); + delayMicroseconds(4500); // wait min 4.1ms + + // second try + write4bits(0x03 << 4); + delayMicroseconds(4500); // wait min 4.1ms + + // third go! + write4bits(0x03 << 4); + delayMicroseconds(150); + + // finally, set to 4-bit interface + write4bits(0x02 << 4); + + // set # lines, font size, etc. + command(LCD_FUNCTIONSET | _displayfunction); + + // turn the display on with no cursor or blinking default + _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; + display(); + + // clear it off + clear(); + + // Initialize to default text direction (for roman languages) + _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; + + // set the entry mode + command(LCD_ENTRYMODESET | _displaymode); + + setCursor(0, 0); +} + +/********** high level commands, for the user! */ +void LiquidCrystal_I2C::clear() { + command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero + delayMicroseconds(2000); // this command takes 1.52ms +} + +void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row) { + int row_offsets[] = {0x00, 0x40, 0x14, 0x54}; + if (row > _numlines) { + row = _numlines - 1; // we count rows starting w/0 + } + command(LCD_SETDDRAMADDR | (col + row_offsets[row])); +} + +// Turn the display on/off (quickly) +void LiquidCrystal_I2C::noDisplay() { + _displaycontrol &= ~LCD_DISPLAYON; + command(LCD_DISPLAYCONTROL | _displaycontrol); +} + +void LiquidCrystal_I2C::display() { + _displaycontrol |= LCD_DISPLAYON; + command(LCD_DISPLAYCONTROL | _displaycontrol); +} + +// Turn the (optional) backlight off/on +void LiquidCrystal_I2C::noBacklight(void) { + _backlightval = LCD_NOBACKLIGHT; + expanderWrite(0); +} + +void LiquidCrystal_I2C::backlight(void) { + _backlightval = LCD_BACKLIGHT; + expanderWrite(0); +} + +void LiquidCrystal_I2C::setBacklight(uint8_t new_val) { + if (new_val) { + backlight(); // turn backlight on + } else { + noBacklight(); // turn backlight off + } +} + +void LiquidCrystal_I2C::printstr(const char c[]) { + // This function is not identical to the function used for "real" I2C displays + // it's here so the user sketch doesn't have to be changed + print(c); +} + +/*********** 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. + */ + +// write either command or data (8 bits) to the HD44780 as +// a single I2C transmission. +void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { + uint8_t highnib = value & 0xf0; + uint8_t lownib = (value << 4) & 0xf0; + // Send both nibbles + Wire.beginTransmission(_Addr); + write4bits(highnib | mode, true); + write4bits(lownib | mode, true); + Wire.endTransmission(); +} + +// write 4 bits to the HD44780 interface. If inTransmission is false +// then the nibble will be sent in its own I2C transmission. +void LiquidCrystal_I2C::write4bits(uint8_t value, bool inTransmission) { + int _data = (int)value | _backlightval; + if (!inTransmission) Wire.beginTransmission(_Addr); + // 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. + printIIC(_data | En); + printIIC(_data); + if (!inTransmission) Wire.endTransmission(); +} + +// write a byte to the PCF8574 I2C interface +void LiquidCrystal_I2C::expanderWrite(uint8_t value) { + int _data = (int)value | _backlightval; + Wire.beginTransmission(_Addr); + printIIC(_data); + Wire.endTransmission(); +} \ No newline at end of file diff --git a/LiquidCrystal_I2C.h b/LiquidCrystal_I2C.h new file mode 100644 index 0000000..4f69bf5 --- /dev/null +++ b/LiquidCrystal_I2C.h @@ -0,0 +1,114 @@ +/* + * © 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 . + */ + +#ifndef LiquidCrystal_I2C_h +#define LiquidCrystal_I2C_h + +#include +#include "Print.h" +#include + +// commands +#define LCD_CLEARDISPLAY 0x01 +#define LCD_RETURNHOME 0x02 +#define LCD_ENTRYMODESET 0x04 +#define LCD_DISPLAYCONTROL 0x08 +#define LCD_CURSORSHIFT 0x10 +#define LCD_FUNCTIONSET 0x20 +#define LCD_SETCGRAMADDR 0x40 +#define LCD_SETDDRAMADDR 0x80 + +// flags for display entry mode +#define LCD_ENTRYRIGHT 0x00 +#define LCD_ENTRYLEFT 0x02 +#define LCD_ENTRYSHIFTINCREMENT 0x01 +#define LCD_ENTRYSHIFTDECREMENT 0x00 + +// flags for display on/off control +#define LCD_DISPLAYON 0x04 +#define LCD_DISPLAYOFF 0x00 +#define LCD_CURSORON 0x02 +#define LCD_CURSOROFF 0x00 +#define LCD_BLINKON 0x01 +#define LCD_BLINKOFF 0x00 + +// flags for display/cursor shift +#define LCD_DISPLAYMOVE 0x08 +#define LCD_CURSORMOVE 0x00 +#define LCD_MOVERIGHT 0x04 +#define LCD_MOVELEFT 0x00 + +// flags for function set +#define LCD_8BITMODE 0x10 +#define LCD_4BITMODE 0x00 +#define LCD_2LINE 0x08 +#define LCD_1LINE 0x00 +#define LCD_5x10DOTS 0x04 +#define LCD_5x8DOTS 0x00 + +// flags for backlight control +#define LCD_BACKLIGHT 0x08 +#define LCD_NOBACKLIGHT 0x00 + +#define En B00000100 // Enable bit +#define Rw B00000010 // Read/Write bit +#define Rs B00000001 // Register select bit + +class LiquidCrystal_I2C : public Print { +public: + LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); + void begin(uint8_t cols, uint8_t rows); + void clear(); + void noDisplay(); + void display(); + void noBacklight(); + void backlight(); + + void setCursor(uint8_t, uint8_t); +#if defined(ARDUINO) && ARDUINO >= 100 + virtual size_t write(uint8_t); +#else + virtual void write(uint8_t); +#endif + void command(uint8_t); + void init(); + void oled_init(); + +////compatibility API function aliases +void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight() +void printstr(const char[]); + +private: + void init_priv(); + void send(uint8_t, uint8_t); + void write4bits(uint8_t, bool inTransmission=false); + void expanderWrite(uint8_t); + void pulseEnable(uint8_t); + uint8_t _Addr; + uint8_t _displayfunction; + uint8_t _displaycontrol; + uint8_t _displaymode; + uint8_t _numlines; + uint8_t _cols; + uint8_t _rows; + uint8_t _backlightval; +}; + +#endif diff --git a/MemStream.cpp b/MemStream.cpp deleted file mode 100644 index bbb7695..0000000 --- a/MemStream.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - - (c) 2015 Ingo Fischer - buffer serial device - based on Arduino SoftwareSerial - - Constructor warning messages fixed by Chris Harlow. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#include "MemStream.h" - -MemStream::MemStream(uint8_t *buffer, const uint16_t len, uint16_t content_len, bool allowWrite) -:_buffer(buffer),_len(len), _buffer_overflow(false), _pos_read(0), _allowWrite(allowWrite) -{ - if (content_len==0) memset(_buffer, 0, _len); - _pos_write=(content_len>len)? len: content_len; -} - -size_t MemStream::write(uint8_t byte) { - if (! _allowWrite) return -1; - if (_pos_write >= _len) { - _buffer_overflow = true; - return 0; - } - _buffer[_pos_write] = byte; - ++_pos_write; - return 1; -} - -void MemStream::flush() { - memset(_buffer, 0, _len); - _pos_write = 0; - _pos_read = 0; -} - -int MemStream::read() { - if (_pos_read >= _len) { - _buffer_overflow = true; - return -1; - } - if (_pos_read >= _pos_write) { - return -1; - } - return _buffer[_pos_read++]; -} - -int MemStream::peek() { - if (_pos_read >= _len) { - _buffer_overflow = true; - return -1; - } - if (_pos_read >= _pos_write) { - return -1; - } - return _buffer[_pos_read+1]; -} - -int MemStream::available() { - int ret=_pos_write-_pos_read; - if (ret<0) ret=0; - return ret; -} - -void MemStream::setBufferContent(uint8_t *buffer, uint16_t content_len) { - memset(_buffer, 0, _len); - memcpy(_buffer, buffer, content_len); - _buffer_overflow=false; - _pos_write=content_len; - _pos_read=0; -} - -void MemStream::setBufferContentFromProgmem(uint8_t *buffer, uint16_t content_len) { - memset(_buffer, 0, _len); - memcpy_P(_buffer, buffer, content_len); - _buffer_overflow=false; - _pos_write=content_len; - _pos_read=0; -} - -void MemStream::setBufferContentPosition(uint16_t read_pos, uint16_t write_pos) { - _pos_write=write_pos; - _pos_read=read_pos; -} diff --git a/MemStream.h b/MemStream.h deleted file mode 100644 index 61ef6bb..0000000 --- a/MemStream.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - - (c) 2015 Ingo FIscher - buffer serial device - based on Arduino SoftwareSerial - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -*/ - -#ifndef MemStream_h -#define MemStream_h - -#include -#if defined(ARDUINO_ARCH_MEGAAVR) -#include -#else -#include -#endif - -#include - -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 diff --git a/MotorDriver.cpp b/MotorDriver.cpp index ae832e9..c9681d6 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -18,50 +18,73 @@ */ #include #include "MotorDriver.h" -#include "AnalogReadFast.h" +#include "DCCTimer.h" #include "DIAG.h" +#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH +#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW +#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH) +#define isLOW(fastpin) (!isHIGH(fastpin)) - -#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_ARCH_MEGAAVR) - #define WritePin digitalWrite - #define ReadPin digitalRead -#else - // use the DIO2 libraray for much faster pin access - #define GPIO2_PREFER_SPEED 1 - #include // use IDE menu Tools..Manage Libraries to locate and install DIO2 - #define WritePin digitalWrite2 - #define ReadPin digitalRead2 -#endif - +bool MotorDriver::usePWM=false; +bool MotorDriver::commonFaultPin=false; + MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) { powerPin=power_pin; + getFastPin(F("POWER"),powerPin,fastPowerPin); + pinMode(powerPin, OUTPUT); + signalPin=signal_pin; + getFastPin(F("SIG"),signalPin,fastSignalPin); + pinMode(signalPin, OUTPUT); + signalPin2=signal_pin2; + if (signalPin2!=UNUSED_PIN) { + dualSignal=true; + getFastPin(F("SIG2"),signalPin2,fastSignalPin2); + pinMode(signalPin2, OUTPUT); + } + else dualSignal=false; + brakePin=brake_pin; + if (brake_pin!=UNUSED_PIN){ + invertBrake=brake_pin < 0; + brakePin=invertBrake ? 0-brake_pin : brake_pin; + getFastPin(F("BRAKE"),brakePin,fastBrakePin); + pinMode(brakePin, OUTPUT); + setBrake(false); + } + else brakePin=UNUSED_PIN; + currentPin=current_pin; - senseFactor=sense_factor; + pinMode(currentPin, INPUT); + faultPin=fault_pin; + if (faultPin != UNUSED_PIN) { + getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin); + pinMode(faultPin, INPUT); + } + + senseFactor=sense_factor; tripMilliamps=trip_milliamps; rawCurrentTripValue=(int)(trip_milliamps / sense_factor); - pinMode(powerPin, OUTPUT); - pinMode(brakePin < 0 ? -brakePin : brakePin, OUTPUT); - setBrake(false); - pinMode(signalPin, OUTPUT); - if (signalPin2 != UNUSED_PIN) pinMode(signalPin2, OUTPUT); - pinMode(currentPin, INPUT); - if (faultPin != UNUSED_PIN) pinMode(faultPin, INPUT); } +bool MotorDriver::isPWMCapable() { + return (!dualSignal) && DCCTimer::isPWMPin(signalPin); +} + + void MotorDriver::setPower(bool on) { - if (brakePin == -4 && on) { + if (on) { // toggle brake before turning power on - resets overcurrent error // on the Pololu board if brake is wired to ^D2. setBrake(true); setBrake(false); + setHIGH(fastPowerPin); } - WritePin(powerPin, on ? HIGH : LOW); + else setLOW(fastPowerPin); } // setBrake applies brake if on == true. So to get @@ -73,30 +96,40 @@ void MotorDriver::setPower(bool on) { // compensate for that. // void MotorDriver::setBrake(bool on) { - bool state = on; - byte pin = brakePin; - if (brakePin < 0) { - pin=-pin; - state=!state; - } - WritePin(pin, state ? HIGH : LOW); - //DIAG(F("BrakePin: %d is %d\n"), pin, ReadPin(pin)); + if (brakePin == UNUSED_PIN) return; + if (on ^ invertBrake) setHIGH(fastBrakePin); + else setLOW(fastBrakePin); } void MotorDriver::setSignal( bool high) { - WritePin(signalPin, high ? HIGH : LOW); - if (signalPin2 != UNUSED_PIN) WritePin(signalPin2, high ? LOW : HIGH); + if (usePWM) { + DCCTimer::setPWM(signalPin,high); + } + else { + if (high) { + setHIGH(fastSignalPin); + if (dualSignal) setLOW(fastSignalPin2); + } + else { + setLOW(fastSignalPin); + if (dualSignal) setHIGH(fastSignalPin2); + } + } } - +/* + * Return the current reading as pin reading 0 to 1023. If the fault + * pin is activated return a negative current to show active fault pin. + * As there is no -0, ceat a little and return -1 in that case. + */ int MotorDriver::getCurrentRaw() { - if (faultPin != UNUSED_PIN && ReadPin(faultPin) == LOW && ReadPin(powerPin) == HIGH) - return (int)(32000/senseFactor); - + int current = analogRead(currentPin); + if (faultPin != UNUSED_PIN && isLOW(fastFaultPin) && isHIGH(fastPowerPin)) + return (current == 0 ? -1 : -current); + return current; // IMPORTANT: This function can be called in Interrupt() time within the 56uS timer // The default analogRead takes ~100uS which is catastrphic - // so analogReadFast is used here. (-2uS) - return analogReadFast(currentPin); + // so DCCTimer has set the sample time to be much faster. } unsigned int MotorDriver::raw2mA( int raw) { @@ -105,3 +138,15 @@ unsigned int MotorDriver::raw2mA( int raw) { int MotorDriver::mA2raw( unsigned int mA) { return (int)(mA / senseFactor); } + +void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) { + DIAG(F("\nMotorDriver %S Pin=%d,"),type,pin); + uint8_t port = digitalPinToPort(pin); + if (input) + result.inout = portInputRegister(port); + else + result.inout = portOutputRegister(port); + result.maskHIGH = digitalPinToBitMask(pin); + result.maskLOW = ~result.maskHIGH; + DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x\n"),port, result.inout,input,result.maskHIGH); +} diff --git a/MotorDriver.h b/MotorDriver.h index 3f67080..b47ae95 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -18,12 +18,20 @@ */ #ifndef MotorDriver_h #define MotorDriver_h +#include "FSH.h" + // Virtualised Motor shield 1-track hardware Interface #ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h #define UNUSED_PIN 127 // inside int8_t #endif +struct FASTPIN { + volatile uint8_t *inout; + uint8_t maskHIGH; + uint8_t maskLOW; +}; + class MotorDriver { public: MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin, byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin); @@ -34,12 +42,24 @@ class MotorDriver { virtual unsigned int raw2mA( int raw); virtual int mA2raw( unsigned int mA); inline int getRawCurrentTripValue() { - return rawCurrentTripValue; + return rawCurrentTripValue; } - + bool isPWMCapable(); + static bool usePWM; + static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs + inline byte getFaultPin() { + return faultPin; + } + private: - byte powerPin, signalPin, signalPin2, currentPin, faultPin; - int8_t brakePin; // negative means pin is inverted + void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result); + void getFastPin(const FSH* type,int pin, FASTPIN & result) { + getFastPin(type, pin, 0, result); + } + byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin; + FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin; + bool dualSignal; // true to use signalPin2 + bool invertBrake; // brake pin passed as negative means pin is inverted float senseFactor; unsigned int tripMilliamps; int rawCurrentTripValue; diff --git a/MotorDrivers.h b/MotorDrivers.h index ef1e2cf..54b5cc0 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -1,8 +1,6 @@ #ifndef MotorDrivers_h #define MotorDrivers_h -#if defined(ARDUINO_ARCH_MEGAAVR) #include -#endif // *** PLEASE NOTE *** THIS FILE IS **NOT** INTENDED TO BE EDITED WHEN CONFIGURING A SYSTEM. // It will be overwritten if the library is updated. @@ -31,8 +29,8 @@ // Pololu Motor Shield #define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \ - new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \ - new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, UNUSED_PIN) + new MotorDriver( 9, 7, UNUSED_PIN, -4, A0, 18, 3000, 12), \ + new MotorDriver(10, 8, UNUSED_PIN, UNUSED_PIN, A1, 18, 3000, 12) // // Actually, on the Pololu MC33926 shield the enable lines are tied together on pin 4 and the // pins 9 and 10 work as "inverted brake" but as we turn on and off the tracks individually @@ -40,8 +38,8 @@ // version of the code always will be high. That means this config is not usable for generating // a railcom cuotout in the future. For that one must wire the second ^D2 to pin 2 and define // the motor driver like this: -// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12) -// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, UNUSED_PIN) +// new MotorDriver(4, 7, UNUSED_PIN, -9, A0, 18, 3000, 12) +// new MotorDriver(2, 8, UNUSED_PIN, -10, A1, 18, 3000, 12) // See Pololu dial_mc33926_shield_schematic.pdf and truth table on page 17 of the MC33926 data sheet. // Firebox Mk1 @@ -59,4 +57,9 @@ new MotorDriver(10, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \ new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) +// IBT_2 Motor Board for Main and Arduino Motor Shield for Prog +#define IBT_2_WITH_ARDUINO F("IBT_2_WITH_ARDUINO_SHIELD"), \ + new MotorDriver(4, 5, 6, UNUSED_PIN, A5, 41.54, 5000, UNUSED_PIN), \ + new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN) + #endif diff --git a/PWMServoDriver.cpp b/PWMServoDriver.cpp index ef5b794..fa261e7 100644 --- a/PWMServoDriver.cpp +++ b/PWMServoDriver.cpp @@ -26,6 +26,7 @@ #include #include "PWMServoDriver.h" #include "DIAG.h" +#include "I2CManager.h" // REGISTER ADDRESSES @@ -40,6 +41,7 @@ const byte MODE1_RESTART=0x80; /**< Restart enabled */ const byte PCA9685_I2C_ADDRESS=0x40; /** First PCA9685 I2C Slave Address */ const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */ const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1); +const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed /*! * @brief Sets the PWM frequency for a chip to 50Hz for servos @@ -52,13 +54,14 @@ bool PWMServoDriver::setup(int board) { if (board>3 || (failFlags & (1<. + */ +#include "SSD1306Ascii.h" + +//============================================================================== +// SSD1306Ascii Method Definitions + +//------------------------------------------------------------------------------ +void SSD1306Ascii::clear() { + clear(0, displayWidth() - 1, 0, displayRows() - 1); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1) { + // Ensure only rows on display will be cleared. + if (r1 >= displayRows()) r1 = displayRows() - 1; + + for (uint8_t r = r0; r <= r1; r++) { + setCursor(c0, r); + for (uint8_t c = c0; c <= c1; c++) ssd1306WriteRamBuf(0); + } + setCursor(c0, r0); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::init(const DevType* dev) { + m_col = 0; + m_row = 0; +#ifdef __AVR__ + const uint8_t* table = (const uint8_t*)pgm_read_word(&dev->initcmds); +#else // __AVR__ + const uint8_t* table = dev->initcmds; +#endif // __AVR + uint8_t size = readFontByte(&dev->initSize); + m_displayWidth = readFontByte(&dev->lcdWidth); + m_displayHeight = readFontByte(&dev->lcdHeight); + m_colOffset = readFontByte(&dev->colOffset); + for (uint8_t i = 0; i < size; i++) { + ssd1306WriteCmd(readFontByte(table + i)); + } + clear(); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::setCol(uint8_t col) { + if (col < m_displayWidth) { + m_col = col; + col += m_colOffset; + ssd1306WriteCmd(SSD1306_SETLOWCOLUMN | (col & 0XF)); + ssd1306WriteCmd(SSD1306_SETHIGHCOLUMN | (col >> 4)); + } +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::setContrast(uint8_t value) { + ssd1306WriteCmd(SSD1306_SETCONTRAST); + ssd1306WriteCmd(value); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::setCursor(uint8_t col, uint8_t row) { + setCol(col); + setRow(row); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::setFont(const uint8_t* font) { + m_font = font; + m_fontFirstChar = readFontByte(m_font + FONT_FIRST_CHAR); + m_fontCharCount = readFontByte(m_font + FONT_CHAR_COUNT); +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::setRow(uint8_t row) { + if (row < displayRows()) { + m_row = row; + ssd1306WriteCmd(SSD1306_SETSTARTPAGE | m_row); + } +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::ssd1306WriteRam(uint8_t c) { + if (m_col < m_displayWidth) { + writeDisplay(c, SSD1306_MODE_RAM); + m_col++; + } +} +//------------------------------------------------------------------------------ +void SSD1306Ascii::ssd1306WriteRamBuf(uint8_t c) { + if (m_col < m_displayWidth) { + writeDisplay(c, SSD1306_MODE_RAM_BUF); + m_col++; + } +} +//------------------------------------------------------------------------------ +size_t SSD1306Ascii::write(uint8_t ch) { + if (!m_font) { + return 0; + } + const uint8_t* base = m_font + FONT_WIDTH_TABLE; + + if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount)) + return 0; + ch -= m_fontFirstChar; + base += fontWidth * ch; + for (uint8_t c = 0; c < fontWidth; c++) { + uint8_t b = readFontByte(base + c); + ssd1306WriteRamBuf(b); + } + for (uint8_t i = 0; i < letterSpacing; i++) { + ssd1306WriteRamBuf(0); + } + flushDisplay(); + return 1; +} + +//============================================================================= +// SSD1306AsciiWire method definitions + +#define m_oledWire Wire + +void SSD1306AsciiWire::begin(const DevType* dev, uint8_t i2cAddr) { +#if OPTIMIZE_I2C + m_nData = 0; +#endif // OPTIMIZE_I2C + m_i2cAddr = i2cAddr; + init(dev); +} + +//------------------------------------------------------------------------------ +void SSD1306AsciiWire::writeDisplay(uint8_t b, uint8_t mode) { +#if OPTIMIZE_I2C + if (m_nData > 16 || (m_nData && mode == SSD1306_MODE_CMD)) { + m_oledWire.endTransmission(); + m_nData = 0; + } + if (m_nData == 0) { + m_oledWire.beginTransmission(m_i2cAddr); + m_oledWire.write(mode == SSD1306_MODE_CMD ? 0X00 : 0X40); + } + m_oledWire.write(b); + if (mode == SSD1306_MODE_RAM_BUF) { + m_nData++; + } else { + m_oledWire.endTransmission(); + m_nData = 0; + } +#else // OPTIMIZE_I2C + m_oledWire.beginTransmission(m_i2cAddr); + m_oledWire.write(mode == SSD1306_MODE_CMD ? 0X00 : 0X40); + m_oledWire.write(b); + m_oledWire.endTransmission(); +#endif // OPTIMIZE_I2C +} + +//------------------------------------------------------------------------------ +void SSD1306AsciiWire::flushDisplay() { +#if OPTIMIZE_I2C + if (m_nData) { + m_oledWire.endTransmission(); + m_nData = 0; + } +#endif // OPTIMIZE_I2C +} +//------------------------------------------------------------------------------ diff --git a/SSD1306Ascii.h b/SSD1306Ascii.h new file mode 100644 index 0000000..2b4961f --- /dev/null +++ b/SSD1306Ascii.h @@ -0,0 +1,234 @@ +/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman + * Modifications (C) 2021 Neil McKechnie + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SSD1306Ascii Library. If not, see + * . + */ +/** + * @file SSD1306AsciiWire.h + * @brief Class for I2C displays using Wire. + */ +#ifndef SSD1306AsciiWire_h +#define SSD1306AsciiWire_h + +#include + +#include "Arduino.h" +#include "LCDDisplay.h" +#include "SSD1306font.h" +#include "SSD1306init.h" + +//------------------------------------------------------------------------------ +/** SSD1306Ascii version basis */ +#define SDD1306_ASCII_VERSION 1.3.0 +//------------------------------------------------------------------------------ +// Configuration options. + +/** Use larger faster I2C code. */ +#define OPTIMIZE_I2C 1 + +//------------------------------------------------------------------------------ +// Values for writeDisplay() mode parameter. +/** Write to Command register. */ +#define SSD1306_MODE_CMD 0 +/** Write one byte to display RAM. */ +#define SSD1306_MODE_RAM 1 +/** Write to display RAM with possible buffering. */ +#define SSD1306_MODE_RAM_BUF 2 +//------------------------------------------------------------------------------ + +/** + * @class SSD1306Ascii + * @brief SSD1306 base class + */ +class SSD1306Ascii : public Print { + public: + using Print::write; + SSD1306Ascii() {} + /** + * @brief Clear the display and set the cursor to (0, 0). + */ + void clear(); + /** + * @brief Clear a region of the display. + * + * @param[in] c0 Starting column. + * @param[in] c1 Ending column. + * @param[in] r0 Starting row; + * @param[in] r1 Ending row; + * @note The final cursor position will be (c0, r0). + */ + void clear(uint8_t c0, uint8_t c1, uint8_t r0, uint8_t r1); + /** + * @brief Clear a field of n fieldWidth() characters. + * + * @param[in] col Field start column. + * + * @param[in] row Field start row. + * + * @param[in] n Number of characters in the field. + * + */ + void clearField(uint8_t col, uint8_t row, uint8_t n); + /** + * @brief Clear the display to the end of the current line. + * @note The number of rows cleared will be determined by the height + * of the current font. + * @note The cursor will be returned to the original position. + */ + void clearToEOL(); + /** + * @return The current column in pixels. + */ + inline uint8_t col() const { return m_col; } + /** + * @return The display hight in pixels. + */ + inline uint8_t displayHeight() const { return m_displayHeight; } + /** + * @brief Set display to normal or 180 degree remap mode. + * + * @param[in] mode true for normal mode, false for remap mode. + * + * @note Adafruit and many ebay displays use remap mode. + * Use normal mode to rotate these displays 180 degrees. + */ + void displayRemap(bool mode); + /** + * @return The display height in rows with eight pixels to a row. + */ + inline uint8_t displayRows() const { return m_displayHeight / 8; } + /** + * @return The display width in pixels. + */ + inline uint8_t displayWidth() const { return m_displayWidth; } + /** + * @brief Set the cursor position to (0, 0). + */ + inline void home() { setCursor(0, 0); } + /** + * @brief Initialize the display controller. + * + * @param[in] dev A display initialization structure. + */ + void init(const DevType* dev); + /** + * @return the current row number with eight pixels to a row. + */ + inline uint8_t row() const { return m_row; } + /** + * @brief Set the current column number. + * + * @param[in] col The desired column number in pixels. + */ + void setCol(uint8_t col); + /** + * @brief Set the display contrast. + * + * @param[in] value The contrast level in th range 0 to 255. + */ + void setContrast(uint8_t value); + /** + * @brief Set the cursor position. + * + * @param[in] col The column number in pixels. + * @param[in] row the row number in eight pixel rows. + */ + void setCursor(uint8_t col, uint8_t row); + /** + * @brief Set the current font. + * + * @param[in] font Pointer to a font table. + */ + void setFont(const uint8_t* font); + /** + * @brief Set the current row number. + * + * @param[in] row the row number in eight pixel rows. + */ + void setRow(uint8_t row); + /** + * @brief Write a command byte to the display controller. + * + * @param[in] c The command byte. + * @note The byte will immediately be sent to the controller. + */ + inline void ssd1306WriteCmd(uint8_t c) { writeDisplay(c, SSD1306_MODE_CMD); } + /** + * @brief Write a byte to RAM in the display controller. + * + * @param[in] c The data byte. + * @note The byte will immediately be sent to the controller. + */ + void ssd1306WriteRam(uint8_t c); + /** + * @brief Write a byte to RAM in the display controller. + * + * @param[in] c The data byte. + * @note The byte may be buffered until a call to ssd1306WriteCmd + * or ssd1306WriteRam or flushDisplay. + */ + void ssd1306WriteRamBuf(uint8_t c); + /** + * @brief Display a character. + * + * @param[in] c The character to display. + * @return one for success else zero. + */ + size_t write(uint8_t c); + + protected: + virtual void writeDisplay(uint8_t b, uint8_t mode) = 0; + virtual void flushDisplay() = 0; + uint8_t m_col; // Cursor column. + uint8_t m_row; // Cursor RAM row. + uint8_t m_displayWidth; // Display width. + uint8_t m_displayHeight; // Display height. + uint8_t m_colOffset; // Column offset RAM to SEG. + const uint8_t* m_font = nullptr; // Current font. + + // Only fixed size 5x7 fonts in a 6x8 cell are supported. + const int fontWidth = 5; + const int fontHeight = 7; + const uint8_t letterSpacing = 1; + uint8_t m_fontFirstChar; + uint8_t m_fontCharCount; +}; + +/** + * @class SSD1306AsciiWire + * @brief Class for I2C displays using Wire. + */ +class SSD1306AsciiWire : public SSD1306Ascii { + public: + /** + * @brief Initialize the display controller. + * + * @param[in] dev A device initialization structure. + * @param[in] i2cAddr The I2C address of the display controller. + */ + void begin(const DevType* dev, uint8_t i2cAddr); + + protected: + void writeDisplay(uint8_t b, uint8_t mode); + void flushDisplay(); + + protected: + uint8_t m_i2cAddr; +#if OPTIMIZE_I2C + uint8_t m_nData; +#endif // OPTIMIZE_I2C +}; + +#endif // SSD1306AsciiWire_h diff --git a/SSD1306font.h b/SSD1306font.h new file mode 100644 index 0000000..fd98800 --- /dev/null +++ b/SSD1306font.h @@ -0,0 +1,180 @@ +/* + * + * System5x7 + * + * + * File Name : System5x7.h + * Date : 28 Oct 2008 + * Font size in bytes : 470 + * Font width : 5 + * Font height : 7 + * Font first char : 32 + * Font last char : 127 + * Font used chars : 94 + * + * The font data are defined as + * + * struct _FONT_ { + * uint16_t font_Size_in_Bytes_over_all_included_Size_it_self; + * uint8_t font_Width_in_Pixel_for_fixed_drawing; + * uint8_t font_Height_in_Pixel_for_all_characters; + * unit8_t font_First_Char; + * uint8_t font_Char_Count; + * + * uint8_t font_Char_Widths[font_Last_Char - font_First_Char +1]; + * // for each character the separate width in pixels, + * // characters < 128 have an implicit virtual right empty row + * + * uint8_t font_data[]; + * // bit field of all characters + */ + +#ifndef SSD1306font_H +#define SSD1306font_H + +#define SYSTEM5x7_WIDTH 5 +#define SYSTEM5x7_HEIGHT 7 + +#ifdef __AVR__ +#include +/** declare a font for AVR. */ +#define GLCDFONTDECL(_n) static const uint8_t __attribute__((progmem)) _n[] +#define readFontByte(addr) pgm_read_byte(addr) +#else // __AVR__ +/** declare a font. */ +#define GLCDFONTDECL(_n) static const uint8_t _n[] +/** Fake read from flash. */ +#define readFontByte(addr) (*(const unsigned char *)(addr)) +#endif // __AVR__ +//------------------------------------------------------------------------------ +// Font Indices +/** No longer used Big Endian length field. Now indicates font type. + * + * 00 00 (fixed width font with 1 padding pixel on right and below) + * + * 00 01 (fixed width font with no padding pixels) + */ +#define FONT_LENGTH 0 +/** Maximum character width. */ +#define FONT_WIDTH 2 +/** Font hight in pixels */ +#define FONT_HEIGHT 3 +/** Ascii value of first character */ +#define FONT_FIRST_CHAR 4 +/** count of characters in font. */ +#define FONT_CHAR_COUNT 5 +/** Offset to width table. */ +#define FONT_WIDTH_TABLE 6 +//------------------------------------------------------------------------------ + +GLCDFONTDECL(System5x7) = { + 0x0, 0x0, // size of zero indicates fixed width font, + 0x05, // width + 0x07, // height + 0x20, // first char + 0x61, // char count + + // Fixed width; char width table not used !!!! + + // font data + 0x00, 0x00, 0x00, 0x00, 0x00, // (space) + 0x00, 0x00, 0x5F, 0x00, 0x00, // ! + 0x00, 0x07, 0x00, 0x07, 0x00, // " + 0x14, 0x7F, 0x14, 0x7F, 0x14, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ + 0x23, 0x13, 0x08, 0x64, 0x62, // % + 0x36, 0x49, 0x55, 0x22, 0x50, // & + 0x00, 0x05, 0x03, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, // ) + 0x08, 0x2A, 0x1C, 0x2A, 0x08, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, // + + 0x00, 0x50, 0x30, 0x00, 0x00, // , + 0x08, 0x08, 0x08, 0x08, 0x08, // - + 0x00, 0x60, 0x60, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, // / + 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 + 0x00, 0x42, 0x7F, 0x40, 0x00, // 1 + 0x42, 0x61, 0x51, 0x49, 0x46, // 2 + 0x21, 0x41, 0x45, 0x4B, 0x31, // 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 + 0x27, 0x45, 0x45, 0x45, 0x39, // 5 + 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6 + 0x01, 0x71, 0x09, 0x05, 0x03, // 7 + 0x36, 0x49, 0x49, 0x49, 0x36, // 8 + 0x06, 0x49, 0x49, 0x29, 0x1E, // 9 + 0x00, 0x36, 0x36, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, // ; + 0x00, 0x08, 0x14, 0x22, 0x41, // < + 0x14, 0x14, 0x14, 0x14, 0x14, // = + 0x41, 0x22, 0x14, 0x08, 0x00, // > + 0x02, 0x01, 0x51, 0x09, 0x06, // ? + 0x32, 0x49, 0x79, 0x41, 0x3E, // @ + 0x7E, 0x11, 0x11, 0x11, 0x7E, // A + 0x7F, 0x49, 0x49, 0x49, 0x36, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, // C + 0x7F, 0x41, 0x41, 0x22, 0x1C, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, // E + 0x7F, 0x09, 0x09, 0x01, 0x01, // F + 0x3E, 0x41, 0x41, 0x51, 0x32, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, // I + 0x20, 0x40, 0x41, 0x3F, 0x01, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, // K + 0x7F, 0x40, 0x40, 0x40, 0x40, // L + 0x7F, 0x02, 0x04, 0x02, 0x7F, // M + 0x7F, 0x04, 0x08, 0x10, 0x7F, // N + 0x3E, 0x41, 0x41, 0x41, 0x3E, // O + 0x7F, 0x09, 0x09, 0x09, 0x06, // P + 0x3E, 0x41, 0x51, 0x21, 0x5E, // Q + 0x7F, 0x09, 0x19, 0x29, 0x46, // R + 0x46, 0x49, 0x49, 0x49, 0x31, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, // V + 0x7F, 0x20, 0x18, 0x20, 0x7F, // W + 0x63, 0x14, 0x08, 0x14, 0x63, // X + 0x03, 0x04, 0x78, 0x04, 0x03, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, // Z + 0x00, 0x00, 0x7F, 0x41, 0x41, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, // "\" + 0x41, 0x41, 0x7F, 0x00, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, // _ + 0x00, 0x01, 0x02, 0x04, 0x00, // ` + 0x20, 0x54, 0x54, 0x54, 0x78, // a + 0x7F, 0x48, 0x44, 0x44, 0x38, // b + 0x38, 0x44, 0x44, 0x44, 0x20, // c + 0x38, 0x44, 0x44, 0x48, 0x7F, // d + 0x38, 0x54, 0x54, 0x54, 0x18, // e + 0x08, 0x7E, 0x09, 0x01, 0x02, // f + 0x08, 0x14, 0x54, 0x54, 0x3C, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, // j + 0x00, 0x7F, 0x10, 0x28, 0x44, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, // l + 0x7C, 0x04, 0x18, 0x04, 0x78, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, // n + 0x38, 0x44, 0x44, 0x44, 0x38, // o + 0x7C, 0x14, 0x14, 0x14, 0x08, // p + 0x08, 0x14, 0x14, 0x18, 0x7C, // q + 0x7C, 0x08, 0x04, 0x04, 0x08, // r + 0x48, 0x54, 0x54, 0x54, 0x20, // s + 0x04, 0x3F, 0x44, 0x40, 0x20, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, // w + 0x44, 0x28, 0x10, 0x28, 0x44, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, // z + 0x00, 0x08, 0x36, 0x41, 0x00, // { + 0x00, 0x00, 0x7F, 0x00, 0x00, // | + 0x00, 0x41, 0x36, 0x08, 0x00, // } + 0x08, 0x08, 0x2A, 0x1C, 0x08, // -> + 0x08, 0x1C, 0x2A, 0x08, 0x08, // <- + 0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol + +}; + +#endif diff --git a/SSD1306init.h b/SSD1306init.h new file mode 100644 index 0000000..fb99deb --- /dev/null +++ b/SSD1306init.h @@ -0,0 +1,204 @@ +/* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman + * Modifications (C) 2021 Neil McKechnie + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with the Arduino SSD1306Ascii Library. If not, see + * . + */ +/** + * @file SSD1306init.h + * @brief Display controller initialization commands. + */ +#ifndef SSD1306init_h +#define SSD1306init_h +//------------------------------------------------------------------------------ +#ifndef __AVR__ +/** Handle AVR flash addressing. */ +#define MEM_TYPE +#else // __AVR__ +#define MEM_TYPE __attribute__ ((progmem)) +#endif // __AVR__ +//------------------------------------------------------------------------------ +/** Set Lower Column Start Address for Page Addressing Mode. */ +#define SSD1306_SETLOWCOLUMN 0x00 +/** Set Higher Column Start Address for Page Addressing Mode. */ +#define SSD1306_SETHIGHCOLUMN 0x10 +/** Set Memory Addressing Mode. */ +#define SSD1306_MEMORYMODE 0x20 +/** Set display RAM display start line register from 0 - 63. */ +#define SSD1306_SETSTARTLINE 0x40 +/** Set Display Contrast to one of 256 steps. */ +#define SSD1306_SETCONTRAST 0x81 +/** Enable or disable charge pump. Follow with 0X14 enable, 0X10 disable. */ +#define SSD1306_CHARGEPUMP 0x8D +/** Set Segment Re-map between data column and the segment driver. */ +#define SSD1306_SEGREMAP 0xA0 +/** Resume display from GRAM content. */ +#define SSD1306_DISPLAYALLON_RESUME 0xA4 +/** Force display on regardless of GRAM content. */ +#define SSD1306_DISPLAYALLON 0xA5 +/** Set Normal Display. */ +#define SSD1306_NORMALDISPLAY 0xA6 +/** Set Inverse Display. */ +#define SSD1306_INVERTDISPLAY 0xA7 +/** Set Multiplex Ratio from 16 to 63. */ +#define SSD1306_SETMULTIPLEX 0xA8 +/** Set Display off. */ +#define SSD1306_DISPLAYOFF 0xAE +/** Set Display on. */ +#define SSD1306_DISPLAYON 0xAF +/**Set GDDRAM Page Start Address. */ +#define SSD1306_SETSTARTPAGE 0XB0 +/** Set COM output scan direction normal. */ +#define SSD1306_COMSCANINC 0xC0 +/** Set COM output scan direction reversed. */ +#define SSD1306_COMSCANDEC 0xC8 +/** Set Display Offset. */ +#define SSD1306_SETDISPLAYOFFSET 0xD3 +/** Sets COM signals pin configuration to match the OLED panel layout. */ +#define SSD1306_SETCOMPINS 0xDA +/** This command adjusts the VCOMH regulator output. */ +#define SSD1306_SETVCOMDETECT 0xDB +/** Set Display Clock Divide Ratio/ Oscillator Frequency. */ +#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 +/** Set Pre-charge Period */ +#define SSD1306_SETPRECHARGE 0xD9 +/** Deactivate scroll */ +#define SSD1306_DEACTIVATE_SCROLL 0x2E +/** No Operation Command. */ +#define SSD1306_NOP 0XE3 +//------------------------------------------------------------------------------ +/** Set Pump voltage value: (30H~33H) 6.4, 7.4, 8.0 (POR), 9.0. */ +#define SH1106_SET_PUMP_VOLTAGE 0X30 +/** First byte of set charge pump mode */ +#define SH1106_SET_PUMP_MODE 0XAD +/** Second byte charge pump on. */ +#define SH1106_PUMP_ON 0X8B +/** Second byte charge pump off. */ +#define SH1106_PUMP_OFF 0X8A +//------------------------------------------------------------------------------ +/** + * @struct DevType + * @brief Device initialization structure. + */ +struct DevType { + /** + * Pointer to initialization command bytes. + */ + const uint8_t* initcmds; + /** + * Number of initialization bytes. + */ + const uint8_t initSize; + /** + * Width of the diaplay in pixels. + */ + const uint8_t lcdWidth; + /** + * Height of the display in pixels. + */ + const uint8_t lcdHeight; + /** + * Column offset RAM to display. Used to pick start column of SH1106. + */ + const uint8_t colOffset; +}; +//------------------------------------------------------------------------------ +// this section is based on https://github.com/adafruit/Adafruit_SSD1306 +/** Initialization commands for a 128x32 SSD1306 oled display. */ +static const uint8_t MEM_TYPE Adafruit128x32init[] = { + // Init sequence for Adafruit 128x32 OLED module + SSD1306_DISPLAYOFF, + SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80 + SSD1306_SETMULTIPLEX, 0x1F, // ratio 32 + SSD1306_SETDISPLAYOFFSET, 0x0, // no offset + SSD1306_SETSTARTLINE | 0x0, // line #0 + SSD1306_CHARGEPUMP, 0x14, // internal vcc + SSD1306_MEMORYMODE, 0x02, // page mode + SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0 + SSD1306_COMSCANDEC, // column scan direction reversed + SSD1306_SETCOMPINS, 0x02, // sequential COM pins, disable remap + SSD1306_SETCONTRAST, 0x7F, // contrast level 127 + SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15) + SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level + SSD1306_DISPLAYALLON_RESUME, + SSD1306_NORMALDISPLAY, + SSD1306_DISPLAYON +}; +/** Initialize a 128x32 SSD1306 oled display. */ +static const DevType MEM_TYPE Adafruit128x32 = { + Adafruit128x32init, + sizeof(Adafruit128x32init), + 128, + 32, + 0 +}; +//------------------------------------------------------------------------------ +// This section is based on https://github.com/adafruit/Adafruit_SSD1306 +/** Initialization commands for a 128x64 SSD1306 oled display. */ +static const uint8_t MEM_TYPE Adafruit128x64init[] = { + // Init sequence for Adafruit 128x64 OLED module + SSD1306_DISPLAYOFF, + SSD1306_SETDISPLAYCLOCKDIV, 0x80, // the suggested ratio 0x80 + SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 + SSD1306_SETDISPLAYOFFSET, 0x0, // no offset + SSD1306_SETSTARTLINE | 0x0, // line #0 + SSD1306_CHARGEPUMP, 0x14, // internal vcc + SSD1306_MEMORYMODE, 0x02, // page mode + SSD1306_SEGREMAP | 0x1, // column 127 mapped to SEG0 + SSD1306_COMSCANDEC, // column scan direction reversed + SSD1306_SETCOMPINS, 0x12, // alt COM pins, disable remap + SSD1306_SETCONTRAST, 0x7F, // contrast level 127 + SSD1306_SETPRECHARGE, 0xF1, // pre-charge period (1, 15) + SSD1306_SETVCOMDETECT, 0x40, // vcomh regulator level + SSD1306_DISPLAYALLON_RESUME, + SSD1306_NORMALDISPLAY, + SSD1306_DISPLAYON +}; +/** Initialize a 128x64 oled display. */ +static const DevType MEM_TYPE Adafruit128x64 = { + Adafruit128x64init, + sizeof(Adafruit128x64init), + 128, + 64, + 0 +}; +//------------------------------------------------------------------------------ +// This section is based on https://github.com/stanleyhuangyc/MultiLCD +/** Initialization commands for a 128x64 SH1106 oled display. */ +static const uint8_t MEM_TYPE SH1106_128x64init[] = { + SSD1306_DISPLAYOFF, + SSD1306_SETSTARTPAGE | 0X0, // set page address + SSD1306_SETCONTRAST, 0x80, // 128 + SSD1306_SEGREMAP | 0X1, // set segment remap + SSD1306_NORMALDISPLAY, // normal / reverse + SSD1306_SETMULTIPLEX, 0x3F, // ratio 64 + SH1106_SET_PUMP_MODE, SH1106_PUMP_ON, // set charge pump enable + SH1106_SET_PUMP_VOLTAGE | 0X2, // 8.0 volts + SSD1306_COMSCANDEC, // Com scan direction + SSD1306_SETDISPLAYOFFSET, 0X00, // set display offset + SSD1306_SETDISPLAYCLOCKDIV, 0X80, // set osc division + SSD1306_SETPRECHARGE, 0X1F, // set pre-charge period + SSD1306_SETCOMPINS, 0X12, // set COM pins + SSD1306_SETVCOMDETECT, 0x40, // set vcomh + SSD1306_DISPLAYON +}; +/** Initialize a 128x64 oled SH1106 display. */ +static const DevType MEM_TYPE SH1106_128x64 = { + SH1106_128x64init, + sizeof(SH1106_128x64init), + 128, + 64, + 2 // SH1106 is a 132x64 controller. Use middle 128 columns. +}; +#endif // SSD1306init_h diff --git a/StringFormatter.cpp b/StringFormatter.cpp index a8ab5cf..204e0df 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -22,13 +22,9 @@ #if defined(ARDUINO_ARCH_SAMD) // Some processors use a gcc compiler that renames va_list!!! #include - Print * StringFormatter::diagSerial= &SerialUSB; - -#elif defined(ARDUINO_ARCH_AVR) - Print * StringFormatter::diagSerial= &Serial; -#elif defined(ARDUINO_ARCH_MEGAAVR) + Print * StringFormatter::diagSerial= &SerialUSB; +#else Print * StringFormatter::diagSerial=&Serial; - #define __FlashStringHelper char #endif #include "LCDDisplay.h" @@ -40,14 +36,14 @@ bool Diag::WITHROTTLE=false; bool Diag::ETHERNET=false; -void StringFormatter::diag( const __FlashStringHelper* input...) { +void StringFormatter::diag( const FSH* input...) { if (!diagSerial) return; va_list args; va_start(args, input); send2(diagSerial,input,args); } -void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { +void StringFormatter::lcd(byte row, const FSH* input...) { va_list args; // Issue the LCD as a diag first @@ -62,25 +58,25 @@ void StringFormatter::lcd(byte row, const __FlashStringHelper* input...) { send2(LCDDisplay::lcdDisplay,input,args); } -void StringFormatter::send(Print * stream, const __FlashStringHelper* input...) { +void StringFormatter::send(Print * stream, const FSH* input...) { va_list args; va_start(args, input); send2(stream,input,args); } -void StringFormatter::send(Print & stream, const __FlashStringHelper* input...) { +void StringFormatter::send(Print & stream, const FSH* input...) { va_list args; va_start(args, input); send2(&stream,input,args); } -void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va_list args) { +void StringFormatter::send2(Print * stream,const FSH* format, va_list args) { // thanks to Jan Turoň https://arduino.stackexchange.com/questions/56517/formatting-strings-in-arduino-for-output char* flash=(char*)format; for(int i=0; ; ++i) { - char c=pgm_read_byte_near(flash+i); + char c=GETFLASH(flash+i); if (c=='\0') return; if(c!='%') { stream->print(c); continue; } @@ -91,14 +87,14 @@ void StringFormatter::send2(Print * stream,const __FlashStringHelper* format, va formatContinues=false; i++; - c=pgm_read_byte_near(flash+i); + c=GETFLASH(flash+i); switch(c) { case '%': stream->print('%'); break; case 'c': stream->print((char) va_arg(args, int)); break; case 's': stream->print(va_arg(args, char*)); break; case 'e': printEscapes(stream,va_arg(args, char*)); break; - case 'E': printEscapes(stream,(const __FlashStringHelper*)va_arg(args, char*)); break; - case 'S': stream->print((const __FlashStringHelper*)va_arg(args, char*)); break; + case 'E': printEscapes(stream,(const FSH*)va_arg(args, char*)); break; + case 'S': stream->print((const FSH*)va_arg(args, char*)); break; case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break; case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break; case 'b': stream->print(va_arg(args, int), BIN); break; @@ -138,12 +134,12 @@ void StringFormatter::printEscapes(Print * stream,char * input) { } } -void StringFormatter::printEscapes(Print * stream, const __FlashStringHelper * input) { +void StringFormatter::printEscapes(Print * stream, const FSH * input) { if (!stream) return; char* flash=(char*)input; for(int i=0; ; ++i) { - char c=pgm_read_byte_near(flash+i); + char c=GETFLASH(flash+i); printEscape(stream,c); if (c=='\0') return; } diff --git a/StringFormatter.h b/StringFormatter.h index 5be513f..a3ba7fd 100644 --- a/StringFormatter.h +++ b/StringFormatter.h @@ -19,12 +19,10 @@ #ifndef StringFormatter_h #define StringFormatter_h #include - +#include "FSH.h" #if defined(ARDUINO_ARCH_SAMD) // Some processors use a gcc compiler that renames va_list!!! #include -#elif defined(ARDUINO_ARCH_MEGAAVR) - #define __FlashStringHelper char #endif #include "LCDDisplay.h" @@ -41,22 +39,22 @@ class Diag { class StringFormatter { public: - static void send(Print * serial, const __FlashStringHelper* input...); - static void send(Print & serial, const __FlashStringHelper* input...); + static void send(Print * serial, const FSH* input...); + static void send(Print & serial, const FSH* input...); static void printEscapes(Print * serial,char * input); - static void printEscapes(Print * serial,const __FlashStringHelper* input); + static void printEscapes(Print * serial,const FSH* input); static void printEscape(Print * serial, char c); // DIAG support static Print * diagSerial; - static void diag( const __FlashStringHelper* input...); - static void lcd(byte row, const __FlashStringHelper* input...); + static void diag( const FSH* input...); + static void lcd(byte row, const FSH* input...); static void printEscapes(char * input); static void printEscape( char c); private: - static void send2(Print * serial, const __FlashStringHelper* input,va_list args); + static void send2(Print * serial, const FSH* input,va_list args); static void printPadded(Print* stream, long value, byte width, bool formatLeft); }; diff --git a/Timer.cpp b/Timer.cpp deleted file mode 100644 index 10673ec..0000000 --- a/Timer.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit to David Cutting - -#include - -#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) - -#include "ATMEGA2560/Timer.h" - -Timer TimerA(1); -Timer TimerB(3); -Timer TimerC(4); -Timer TimerD(5); - -ISR(TIMER1_OVF_vect) -{ - TimerA.isrCallback(); -} - -ISR(TIMER3_OVF_vect) -{ - TimerB.isrCallback(); -} - -ISR(TIMER4_OVF_vect) -{ - TimerC.isrCallback(); -} - -ISR(TIMER5_OVF_vect) -{ - TimerD.isrCallback(); -} - -#elif defined(ARDUINO_AVR_UNO) // Todo: add other 328 boards for compatibility - -#include "ATMEGA328/Timer.h" - -Timer TimerA(1); -Timer TimerB(2); - -ISR(TIMER1_OVF_vect) -{ - TimerA.isrCallback(); -} - -ISR(TIMER2_OVF_vect) -{ - TimerB.isrCallback(); -} - -#endif diff --git a/VirtualTimer.h b/VirtualTimer.h deleted file mode 100644 index 5e3832f..0000000 --- a/VirtualTimer.h +++ /dev/null @@ -1,21 +0,0 @@ -// This file is copied from https://github.com/davidcutting42/ArduinoTimers -// All Credit to David Cutting - -#ifndef VirtualTimer_h -#define VirtualTimer_h - -class VirtualTimer -{ -public: - virtual void initialize() = 0; - virtual void setPeriod(unsigned long microseconds) = 0; - virtual void start() = 0; - virtual void stop() = 0; - - virtual void attachInterrupt(void (*isr)()) = 0; - virtual void detachInterrupt() = 0; -private: - -}; - -#endif diff --git a/WiThrottle.cpp b/WiThrottle.cpp index d9b9ec7..62b638d 100644 --- a/WiThrottle.cpp +++ b/WiThrottle.cpp @@ -133,6 +133,8 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) { case 'P': if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); + if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling + DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF); StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); lastPowerState = (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON); //remember power state sent for comparison later } diff --git a/WifiInboundHandler.cpp b/WifiInboundHandler.cpp index 2463921..02d3e0a 100644 --- a/WifiInboundHandler.cpp +++ b/WifiInboundHandler.cpp @@ -1,3 +1,23 @@ +/* + * © 2020, Chris Harlow. All rights reserved. + * © 2020, Harald Barth. + * + * This file is part of Asbelos DCC API + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * It is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with CommandStation. If not, see . + */ +#ifndef ARDUINO_AVR_UNO_WIFI_REV2 #include #include "WifiInboundHandler.h" #include "RingStream.h" @@ -228,3 +248,5 @@ void WifiInboundHandler::purgeCurrentCIPSEND() { pendingCipsend=false; clientPendingCIPSEND=-1; } + +#endif diff --git a/WifiInboundHandler.h b/WifiInboundHandler.h index c2c8865..8a25d9c 100644 --- a/WifiInboundHandler.h +++ b/WifiInboundHandler.h @@ -15,12 +15,12 @@ class WifiInboundHandler { static WifiInboundHandler * singleton; - enum INBOUND_STATE { + enum INBOUND_STATE : byte { INBOUND_BUSY, // keep calling in loop() INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND }; - enum LOOP_STATE { + enum LOOP_STATE : byte { ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc... SKIPTOEND, // skip to newline diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 45531fb..1eb79f2 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -17,7 +17,8 @@ You should have received a copy of the GNU General Public License along with CommandStation. If not, see . */ - +#ifndef ARDUINO_AVR_UNO_WIFI_REV2 +// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture #include "WifiInterface.h" /* config.h included there */ #include #include "DIAG.h" @@ -25,25 +26,25 @@ #include "WifiInboundHandler.h" -const char PROGMEM READY_SEARCH[] = "\r\nready\r\n"; -const char PROGMEM OK_SEARCH[] = "\r\nOK\r\n"; -const char PROGMEM END_DETAIL_SEARCH[] = "@ 1000"; -const char PROGMEM SEND_OK_SEARCH[] = "\r\nSEND OK\r\n"; -const char PROGMEM IPD_SEARCH[] = "+IPD"; + + + const unsigned long LOOP_TIMEOUT = 2000; bool WifiInterface::connected = false; Stream * WifiInterface::wifiStream; #ifndef WIFI_CONNECT_TIMEOUT // Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4. -#define WIFI_CONNECT_TIMEOUT 14000 +// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that +// or ot will cause issues with the following commands. +#define WIFI_CONNECT_TIMEOUT 16000 #endif //////////////////////////////////////////////////////////////////////////////// // // Figure out number of serial ports depending on hardware // -#if defined(ARDUINO_AVR_UNO) +#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) #define NUM_SERIAL 0 #endif @@ -56,10 +57,11 @@ Stream * WifiInterface::wifiStream; #endif bool WifiInterface::setup(long serial_link_speed, - const __FlashStringHelper *wifiESSID, - const __FlashStringHelper *wifiPassword, - const __FlashStringHelper *hostname, - const int port) { + const FSH *wifiESSID, + const FSH *wifiPassword, + const FSH *hostname, + const int port, + const byte channel) { wifiSerialState wifiUp = WIFI_NOAT; @@ -70,11 +72,12 @@ bool WifiInterface::setup(long serial_link_speed, (void) wifiPassword; (void) hostname; (void) port; + (void) channel; #endif #if NUM_SERIAL > 0 Serial1.begin(serial_link_speed); - wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port); + wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel); #endif // Other serials are tried, depending on hardware. @@ -82,7 +85,7 @@ bool WifiInterface::setup(long serial_link_speed, if (wifiUp == WIFI_NOAT) { Serial2.begin(serial_link_speed); - wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port); + wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel); } #endif @@ -90,7 +93,7 @@ bool WifiInterface::setup(long serial_link_speed, if (wifiUp == WIFI_NOAT) { Serial3.begin(serial_link_speed); - wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port); + wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel); } #endif @@ -107,8 +110,8 @@ bool WifiInterface::setup(long serial_link_speed, return connected; } -wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringHelper* SSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port) { +wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password, + const FSH* hostname, int port, byte channel) { wifiSerialState wifiState; static uint8_t ntry = 0; ntry++; @@ -117,7 +120,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH DIAG(F("\n++ Wifi Setup Try %d ++\n"), ntry); - wifiState = setup2( SSid, password, hostname, port); + wifiState = setup2( SSid, password, hostname, port, channel); if (wifiState == WIFI_NOAT) { DIAG(F("\n++ Wifi Setup NO AT ++\n")); @@ -126,7 +129,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH if (wifiState == WIFI_CONNECTED) { StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo - checkForOK(200, OK_SEARCH, true); + checkForOK(200, true); } @@ -139,146 +142,171 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const __FlashStringH #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-parameter" #endif -wifiSerialState WifiInterface::setup2(const __FlashStringHelper* SSid, const __FlashStringHelper* password, - const __FlashStringHelper* hostname, int port) { +wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, + const FSH* hostname, int port, byte channel) { bool ipOK = false; bool oldCmd = false; char macAddress[17]; // mac address extraction - + // First check... Restarting the Arduino does not restart the ES. // There may alrerady be a connection with data in the pipeline. // If there is, just shortcut the setup and continue to read the data as normal. - if (checkForOK(200,IPD_SEARCH, true)) { + if (checkForOK(200,F("+IPD"), true)) { DIAG(F("\nPreconfigured Wifi already running with data waiting\n")); - // loopstate=4; // carry on from correct place... or not as the case may be return WIFI_CONNECTED; } StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT? - if(!checkForOK(200, OK_SEARCH, true)) + if(!checkForOK(200, true)) return WIFI_NOAT; // No AT compatible WiFi module here StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening - checkForOK(2000, OK_SEARCH, true); // Makes this visible on the console + checkForOK(2000, true); // Makes this visible on the console // Display the AT version information StringFormatter::send(wifiStream, F("AT+GMR\r\n")); - checkForOK(2000, OK_SEARCH, true, false); // Makes this visible on the console + checkForOK(2000, true, false); // Makes this visible on the console #ifdef DONT_TOUCH_WIFI_CONF DIAG(F("\nDONT_TOUCH_WIFI_CONF was set: Using existing config\n")); #else StringFormatter::send(wifiStream, F("AT+CWMODE=1\r\n")); // configure as "station" = WiFi client - checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" + checkForOK(1000, true); // Not always OK, sometimes "no change" + + // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME + StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); + if (checkForOK(2000, true)) { + oldCmd=true; + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + } - // If the source code looks unconfigured, check if the - // ESP8266 is preconfigured. We check the first 13 chars - // of the SSid. const char *yourNetwork = "Your network "; - if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || ((const char *)SSid)[0] == '\0') { - delay(8000); // give a preconfigured ES8266 a chance to connect to a router - // typical connect time approx 7 seconds - StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); - if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) - if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) - ipOK = true; - } else { // Should this really be "else" here /haba - - if (!ipOK) { - - // Older ES versions have AT+CWJAP, newer ones have AT+CWJAP_CUR and AT+CWHOSTNAME - StringFormatter::send(wifiStream, F("AT+CWJAP?\r\n")); - if (checkForOK(2000, OK_SEARCH, true)) { - oldCmd=true; - while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) { + if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + // If the source code looks unconfigured, check if the + // ESP8266 is preconfigured in station mode. + // We check the first 13 chars of the SSid and the password + // give a preconfigured ES8266 a chance to connect to a router + // typical connect time approx 7 seconds + delay(8000); + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + if (checkForOK(5000, F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, F("0.0.0.0"), true,false)) + ipOK = true; + } + } else { + // SSID was configured, so we assume station (client) mode. + if (oldCmd) { // AT command early version supports CWJAP/CWSAP - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); - ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true); - } - DIAG(F("\n**\n")); - + StringFormatter::send(wifiStream, F("AT+CWJAP=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true); } else { // later version supports CWJAP_CUR - StringFormatter::send(wifiStream, F("AT+CWHOSTNAME=\"%S\"\r\n"), hostname); // Set Host name for Wifi Client - checkForOK(2000, OK_SEARCH, true); // dont care if not supported + checkForOK(2000, true); // dont care if not supported - if (SSid) { - StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); - ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true); - } + StringFormatter::send(wifiStream, F("AT+CWJAP_CUR=\"%S\",\"%S\"\r\n"), SSid, password); + ipOK = checkForOK(WIFI_CONNECT_TIMEOUT, true); } if (ipOK) { // But we really only have the ESSID and password correct - // Let's check for IP + // Let's check for IP (via DHCP) ipOK = false; StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); - if (checkForOK(5000, (const char*) F("+CIFSR:STAIP"), true,false)) - if (!checkForOK(1000, (const char*) F("0.0.0.0"), true,false)) + if (checkForOK(5000, F("+CIFSR:STAIP"), true,false)) + if (!checkForOK(1000, F("0.0.0.0"), true,false)) ipOK = true; } - } } if (!ipOK) { // If we have not managed to get this going in station mode, go for AP mode - StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); // configure as AccessPoint. - checkForOK(1000, OK_SEARCH, true); // Not always OK, sometimes "no change" +// StringFormatter::send(wifiStream, F("AT+RST\r\n")); +// checkForOK(1000, true); // Not always OK, sometimes "no change" + + int i=0; + do { + // configure as AccessPoint. Try really hard as this is the + // last way out to get any Wifi connectivity. + StringFormatter::send(wifiStream, F("AT+CWMODE=2\r\n")); + } while (!checkForOK(1000+i*500, true) && i++<10); + + while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE // Figure out MAC addr - StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); + StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // not TOMATO // looking fpr mac addr eg +CIFSR:APMAC,"be:dd:c2:5c:6b:b7" - if (checkForOK(5000, (const char*) F("+CIFSR:APMAC,\""), true,false)) { + if (checkForOK(5000, F("+CIFSR:APMAC,\""), true,false)) { // Copy 17 byte mac address for (int i=0; i<17;i++) { while(!wifiStream->available()); macAddress[i]=wifiStream->read(); StringFormatter::printEscape(macAddress[i]); } + } else { + memset(macAddress,'f',sizeof(macAddress)); } char macTail[]={macAddress[9],macAddress[10],macAddress[12],macAddress[13],macAddress[15],macAddress[16],'\0'}; - if (oldCmd) { - while (wifiStream->available()) StringFormatter::printEscape( wifiStream->read()); /// THIS IS A DIAG IN DISGUISE + checkForOK(1000, true, false); // suck up remainder of AT+CIFSR + + i=0; + do { + if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { + // unconfigured + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"), + oldCmd ? "" : "_CUR", macTail, macTail, channel); + } else { + // password configured by user + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", + macTail, password, channel); + } + } while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok + if (i >= 2) + DIAG(F("\nWarning: Setting AP SSID and password failed\n")); // but issue warning - int i=0; - do { - if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) { - // unconfigured - StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); - } else { - // password configured by user - StringFormatter::send(wifiStream, F("AT+CWSAP=\"DCCEX_%s\",\"%s\",1,4\r\n"), macTail, password); - } - } while (i++<2 && !checkForOK(WIFI_CONNECT_TIMEOUT, OK_SEARCH, true)); // do twice if necessary but ignore failure as AP mode may still be ok - } else { - - StringFormatter::send(wifiStream, F("AT+CWSAP_CUR=\"DCCEX_%s\",\"PASS_%s\",1,4\r\n"), macTail, macTail); - checkForOK(20000, OK_SEARCH, true); // can ignore failure as SSid mode may still be ok - + if (!oldCmd) { StringFormatter::send(wifiStream, F("AT+CIPRECVMODE=0\r\n"), port); // make sure transfer mode is correct - checkForOK(2000, OK_SEARCH, true); + checkForOK(2000, true); } } StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1) - checkForOK(1000, OK_SEARCH, true); // ignore result in case it already was off + checkForOK(1000, true); // ignore result in case it already was off StringFormatter::send(wifiStream, F("AT+CIPMUX=1\r\n")); // configure for multiple connections - if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED; + if (!checkForOK(1000, true)) return WIFI_DISCONNECTED; StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port - if (!checkForOK(1000, OK_SEARCH, true)) return WIFI_DISCONNECTED; + if (!checkForOK(1000, true)) return WIFI_DISCONNECTED; #endif //DONT_TOUCH_WIFI_CONF StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG - if (!checkForOK(1000, OK_SEARCH, true, false)) return WIFI_DISCONNECTED; - DIAG(F("\nPORT=%d\n"),port); + if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED; + // Copy the IP address + { + const byte MAX_IP_LENGTH=15; + char ipString[MAX_IP_LENGTH+1]; + ipString[MAX_IP_LENGTH]='\0'; // protection against missing " character on end. + for(byte ipLen=0;ipLenavailable()); + int ipChar=wifiStream->read(); + StringFormatter::printEscape(ipChar); + if (ipChar=='"') { + ipString[ipLen]='\0'; + break; + } + ipString[ipLen]=ipChar; + } + LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this + } + // suck up anything after the IP. + if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED; + LCD(5,F("PORT=%d\n"),port); return WIFI_CONNECTED; } @@ -300,15 +328,19 @@ void WifiInterface::ATCommand(const byte * command) { } else { StringFormatter:: send(wifiStream, F("AT+%s\r\n"), command); - checkForOK(10000, OK_SEARCH, true); + checkForOK(10000, true); } } -bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor, bool echo, bool escapeEcho) { +bool WifiInterface::checkForOK( const unsigned int timeout, bool echo, bool escapeEcho) { + return checkForOK(timeout,F("\r\nOK\r\n"),echo,escapeEcho); +} + +bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor, bool echo, bool escapeEcho) { unsigned long startTime = millis(); - char const *locator = waitfor; + char *locator = (char *)waitfor; DIAG(F("\nWifi Check: [%E]"), waitfor); while ( millis() - startTime < timeout) { while (wifiStream->available()) { @@ -317,10 +349,10 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const char * waitfor if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE else DIAG(F("%c"), ch); } - if (ch != pgm_read_byte_near(locator)) locator = waitfor; - if (ch == pgm_read_byte_near(locator)) { + if (ch != GETFLASH(locator)) locator = (char *)waitfor; + if (ch == GETFLASH(locator)) { locator++; - if (!pgm_read_byte_near(locator)) { + if (!GETFLASH(locator)) { DIAG(F("\nFound in %dms"), millis() - startTime); return true; } @@ -337,3 +369,5 @@ void WifiInterface::loop() { WifiInboundHandler::loop(); } } + +#endif diff --git a/WifiInterface.h b/WifiInterface.h index 390a85f..19f8a3a 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -19,7 +19,7 @@ */ #ifndef WifiInterface_h #define WifiInterface_h -#include "config.h" +#include "FSH.h" #include "DCCEXParser.h" #include #include @@ -31,26 +31,23 @@ class WifiInterface public: static bool setup(long serial_link_speed, - const __FlashStringHelper *wifiESSID, - const __FlashStringHelper *wifiPassword, - const __FlashStringHelper *hostname, - const int port = 2560); + const FSH *wifiESSID, + const FSH *wifiPassword, + const FSH *hostname, + const int port, + const byte channel); static void loop(); static void ATCommand(const byte *command); - + private: - static wifiSerialState setup(Stream &setupStream, const __FlashStringHelper *SSSid, const __FlashStringHelper *password, - const __FlashStringHelper *hostname, int port); + static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password, + const FSH *hostname, int port, byte channel); static Stream *wifiStream; static DCCEXParser parser; - static wifiSerialState setup2(const __FlashStringHelper *SSSid, const __FlashStringHelper *password, - const __FlashStringHelper *hostname, int port); - static bool checkForOK(const unsigned int timeout, const char *waitfor, bool echo, bool escapeEcho = true); + static wifiSerialState setup2(const FSH *SSSid, const FSH *password, + const FSH *hostname, int port, byte channel); + static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true); + static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true); static bool connected; - static bool closeAfter; - static byte loopstate; - static int datalength; - static int connectionId; - static unsigned long loopTimeoutStart; }; #endif diff --git a/config.example.h b/config.example.h index b588e50..f528b3e 100644 --- a/config.example.h +++ b/config.example.h @@ -1,10 +1,9 @@ /********************************************************************** -Config.h -COPYRIGHT (c) 2013-2016 Gregg E. Berman +config.h COPYRIGHT (c) 2020 Fred Decker -The configuration file for DCC++ EX Command Station +The configuration file for DCC-EX Command Station **********************************************************************/ @@ -21,12 +20,12 @@ The configuration file for DCC++ EX Command Station // POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track) // FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection) // FIREBOX_MK1 : The Firebox MK1 -// FIREBOX_MK1S : The Firebox MK1S +// FIREBOX_MK1S : The Firebox MK1S +// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN // | // +-----------------------v // #define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD - ///////////////////////////////////////////////////////////////////////////////////// // // The IP port to talk to a WIFI or Ethernet shield. @@ -52,10 +51,14 @@ The configuration file for DCC++ EX Command Station // WIFI_SSID is the network name IF you want to use your existing home network. // Do NOT change this if you want to use the WiFi in Access Point (AP) mode. // -// If you do NOT set the WIFI_SSID, the WiFi chip will first try -// to connect to the previously configured network and if that fails -// fall back to Access Point mode. The SSID of the AP will be -// automatically set to DCCEX_*. +// If you do NOT set the WIFI_SSID and do NOT set the WIFI_PASSWORD, +// then the WiFi chip will first try to connect to the previously +// configured network and if that fails fall back to Access Point mode. +// The SSID of the AP will be automatically set to DCCEX_*. +// If you DO set the WIFI_SSID then the WiFi chip will try to connect +// to that (home) network in station (client) mode. If a WIFI_PASSWORD +// is set (recommended), that password will be used for AP mode. +// The AP mode password must be at least 8 characters long. // // Your SSID may not conain ``"'' (double quote, ASCII 0x22). #define WIFI_SSID "Your network name" @@ -69,12 +72,11 @@ The configuration file for DCC++ EX Command Station // WIFI_HOSTNAME: You probably don't need to change this #define WIFI_HOSTNAME "dccex" // -///////////////////////////////////////////////////////////////////////////////////// -// -// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need -// to set this if you have an extremely slow Wifi router. -// -//#define WIFI_CONNECT_TIMEOUT 14000 +// WIFI_CHANNEL: If the line "#define ENABLE_WIFI true" is uncommented, +// WiFi will be enabled (Mega only). The default channel is set to "1" whether +// this line exists or not. If you need to use an alternate channel (we recommend +// using only 1,6, or 11) you may change it here. +#define WIFI_CHANNEL 1 ///////////////////////////////////////////////////////////////////////////////////// // @@ -90,26 +92,6 @@ The configuration file for DCC++ EX Command Station // //#define IP_ADDRESS { 192, 168, 1, 200 } -///////////////////////////////////////////////////////////////////////////////////// -// -// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE -// -// Uncomment to use with Ethernet Shields -// -// Ethernet Shields do not have have a MAC address in hardware. There may be one on -// a sticker on the Shield that you should use. Otherwise choose one of the ones below -// Be certain that no other device on your network has this same MAC address! -// -// 52:b8:8a:8e:ce:21 -// e3:e9:73:e1:db:0d -// 54:2b:13:52:ac:0c - -// NOTE: This is not used with ESP8266 WiFi modules. - -//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above - -// -// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF } ///////////////////////////////////////////////////////////////////////////////////// // @@ -123,8 +105,9 @@ The configuration file for DCC++ EX Command Station // define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows // #define LCD_DRIVER 0x3F,16,2 -//OR define OLED_DRIVER width,height in pixels (address auto detected) -// This will not work on a UNO due to memory constraints +//OR define OLED_DRIVER width,height in pixels (address auto detected) +// 128x32 or 128x64 I2C SSD1306-based devices are supported. +// Also 132x64 I2C SH1106 devices. // #define OLED_DRIVER 128,32 ///////////////////////////////////////////////////////////////////////////////////// diff --git a/defines.h b/defines.h index 6db42e1..478667e 100644 --- a/defines.h +++ b/defines.h @@ -21,10 +21,13 @@ //////////////////////////////////////////////////////////////////////////////// // // WIFI_ON: All prereqs for running with WIFI are met -// +// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed. #if ENABLE_WIFI && (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO)) #define WIFI_ON true +#ifndef WIFI_CHANNEL +#define WIFI_CHANNEL 1 +#endif #else #define WIFI_ON false #endif diff --git a/examples/advanced/CommandStation-EX.ino b/examples/advanced/CommandStation-EX.ino deleted file mode 100644 index b7d10c0..0000000 --- a/examples/advanced/CommandStation-EX.ino +++ /dev/null @@ -1,152 +0,0 @@ -/* - * © 2020, Chris Harlow. All rights reserved. - * - * This file is a demonstattion of calling the DCC-EX API - */ - - - -#include "DCCEX.h" - -#ifdef ARDUINO_AVR_UNO - #include - SoftwareSerial Serial1(15,16); // YOU must get these pins correct to use Wifi on a UNO - #define WIFI_BAUD 9600 -#else - #define WIFI_BAUD 115200 -#endif - -// this code is here to demonstrate use of the DCC API and other techniques - -// myFilter is an example of an OPTIONAL command filter used to intercept < > commands from -// the usb or wifi streamm. It demonstrates how a command may be intercepted -// or even a new command created without having to break open the API library code. -// The filter is permitted to use or modify the parameter list before passing it on to -// the standard parser. By setting the opcode to 0, the standard parser will -// just ignore the command on the assumption that you have already handled it. -// -// The filter must be enabled by calling the DCC EXParser::setFilter method, see use in setup(). - -void myComandFilter(Print * stream, byte & opcode, byte & paramCount, int p[]) { - (void)stream; // avoid compiler warning if we don't access this parameter - switch (opcode) { - case '!': // Create a bespoke new command to clear all loco reminders or specific locos e.g - if (paramCount==0) DCC::forgetAllLocos(); - else for (int i=0;iThis is my HTTP filter responding.
")); -} - -// Callback functions are necessary if you call any API that must wait for a response from the -// programming track. The API must return immediately otherwise other loop() functions would be blocked. -// Your callback function will be invoked when the data arrives from the prog track. -// See the DCC:getLocoId example in the setup function. - - -void myCallback(int result) { - DIAG(F("\n getting Loco Id callback result=%d"),result); -} - - -// Create a serial command parser... This is OPTIONAL if you don't need to handle JMRI type commands -// from the Serial port. -DCCEXParser serialParser; - - -// Try monitoring the memory -#include "freeMemory.h" -int ramLowWatermark=32767; // This figure gets overwritten dynamically in loop() - -void setup() { - - // The main sketch has responsibilities during setup() - - // Responsibility 1: Start the usb connection for diagnostics and possible JMRI input - // DIAGSERAL is normally Serial but uses SerialUSB on a SAMD processor - DIAGSERIAL.begin(115200); - while(!DIAGSERIAL); - - // Responsibility 2: Start the DCC engine. - // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) - // Standard supported devices have pre-configured macros but custome hardware installations require - // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. - - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - - DCC::begin(STANDARD_MOTOR_SHIELD); - - // Responsibility 3: **Optionally** Start the WiFi interface if required. - // NOTE: On a Uno you will have to provide a SoftwareSerial - // configured for the pins connected to the Wifi card - // and a 9600 baud rate. - // setup(serial, F(router name) or NULL, F(router password), F(hostname), F(AcessPoint name) or NULL , port) - // (port 3532 is 0xDCC decimal.) - - - Serial1.begin(WIFI_BAUD); - WifiInterface::setup(Serial1, F("BTHub5-M6PT"), F("49de8d4862"),F("DCCEX"),3532); - - // Optionally tell the Wifi parser to use my http filter. - // This will intercept http commands from Wifi. - WifiInterface::setHTTPCallback(myHttpFilter); - - // This is just for demonstration purposes - DIAG(F("\n===== DCCEX demonstrating DCC::getLocoId() call ==========\n")); - DCC::getLocoId(myCallback); // myCallback will be called with the result - DIAG(F("\n===== DCC::getLocoId has returned, but the callback wont be executed until we are in loop() ======\n")); - - // Optionally tell the command parser to use my example filter. - // This will intercept JMRI commands from both USB and Wifi - DCCEXParser::setFilter(myComandFilter); - - - DIAG(F("\nReady for JMRI commands\n")); - -} - -void loop() { - // The main sketch has responsibilities during loop() - - // Responsibility 1: Handle DCC background processes - // (loco reminders and power checks) - DCC::loop(); - - // Responsibility 2: handle any incoming commands on USB connection - serialParser.loop(DIAGSERIAL); - - // Responsibility 3: Optionally handle any incoming WiFi traffic - WifiInterface::loop(); - - // Your additional loop code - - // Optionally report any decrease in memory (will automatically trigger on first call) - int freeNow=freeMemory(); - if (freeNow commands coming from keyboard or JMRI on thr USB connection. -DCCEXParser serialParser; - -void setup() { - - // Responsibility 1: Start the usb connection for diagnostics and possible JMRI input - Serial.begin(115200); - - // Responsibility 2: Start the DCC engine with information about your Motor Shield. - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorDriverss.h - DCC::begin(STANDARD_MOTOR_SHIELD); -} - -void loop() { - // Responsibility 1: Handle DCC background processes (loco reminders and power checks) - DCC::loop(); - - // Responsibility 2: handle any incoming commands on USB connection - serialParser.loop(Serial); -} diff --git a/examples/basicWifi/CommandStation-EX.ino b/examples/basicWifi/CommandStation-EX.ino deleted file mode 100644 index f4a091e..0000000 --- a/examples/basicWifi/CommandStation-EX.ino +++ /dev/null @@ -1,73 +0,0 @@ -/* - * © 2020, Chris Harlow. All rights reserved. - * - * This file is a demonstattion of setting up a DCC-EX - * Command station to support direct connection of WiThrottle devices - * such as "Engine Driver". If you contriol your layout through JMRI - * then DON'T connect throttles to this wifi, connect them to JMRI. - * - * This is just 3 statements longer than the basic setup. - * - * THIS SETUP DOES NOT APPLY TO ARDUINO UNO WITH ONLY A SINGLE SERIAL PORT. - * REFER TO SEPARATE EXAMPLE. - */ - -#include "DCCEX.h" - - - -// Create a serial command parser... Enables certain diagnostics and commands -// to be issued from the USB serial console -// This is NOT intended for JMRI.... - -DCCEXParser serialParser; - -void setup() { - - // The main sketch has responsibilities during setup() - - // Responsibility 1: Start the usb connection for diagnostics - // This is normally Serial but uses SerialUSB on a SAMD processor - - Serial.begin(115200); - - // Responsibility 3: Start the DCC engine. - // Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s) - // Standard supported devices have pre-configured macros but custome hardware installations require - // detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic. - - // STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h - - // Optionally a Timer number (1..4) may be passed to DCC::begin to override the default Timer1 used for the - // waveform generation. e.g. DCC::begin(STANDARD_MOTOR_SHIELD,2); to use timer 2 - - DCC::begin(STANDARD_MOTOR_SHIELD); - - // Start the WiFi interface. - // NOTE: References to Serial1 are for the serial port used to connect - // your wifi chip/shield. - - - Serial1.begin(115200); // BAUD rate of your Wifi chip/shield - WifiInterface::setup(Serial1, - F("BTHub5-M6PT"), // Router name - F("49de8d4862"), // Router password - F("DCCEX"), // Hostname (ignored by some wifi chip firmware) - 3532); // port (3532 is 0xDCC) - -} - -void loop() { - // The main sketch has responsibilities during loop() - - // Responsibility 1: Handle DCC background processes - // (loco reminders and power checks) - DCC::loop(); - - // Responsibility 2: handle any incoming commands on USB connection - serialParser.loop(Serial); - - // Responsibility 3: Optionally handle any incoming WiFi traffic - WifiInterface::loop(); - -} diff --git a/objdump.bat b/objdump.bat index af30cb5..94e19e8 100644 --- a/objdump.bat +++ b/objdump.bat @@ -10,5 +10,7 @@ ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt -notepad %TMP%\OBJDUMP_%a%.txt +ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt +avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt +%TMP%\OBJDUMP_%a%.txt EXIT diff --git a/platformio.ini b/platformio.ini index e0a0ad8..c898f00 100644 --- a/platformio.ini +++ b/platformio.ini @@ -36,10 +36,6 @@ lib_deps = DIO2 arduino-libraries/Ethernet SPI - marcoschwartz/LiquidCrystal_I2C - Adafruit/Adafruit_BusIO - Adafruit/Adafruit_SSD1306 - Adafruit/Adafruit-GFX-Library monitor_speed = 115200 monitor_flags = --echo @@ -52,7 +48,6 @@ lib_deps = DIO2 arduino-libraries/Ethernet SPI - marcoschwartz/LiquidCrystal_I2C monitor_speed = 115200 monitor_flags = --echo @@ -65,9 +60,9 @@ lib_deps = DIO2 arduino-libraries/Ethernet SPI - marcoschwartz/LiquidCrystal_I2C monitor_speed = 115200 monitor_flags = --echo +build_flags = "-DF_CPU=16000000L -DARDUINO=10813 -DARDUINO_AVR_UNO_WIFI_DEV_ED -DARDUINO_ARCH_AVR -DESP_CH_UART -DESP_CH_UART_BR=19200"g [env:uno] platform = atmelavr @@ -78,6 +73,5 @@ lib_deps = DIO2 arduino-libraries/Ethernet SPI - marcoschwartz/LiquidCrystal_I2C monitor_speed = 115200 monitor_flags = --echo diff --git a/version.h b/version.h index e47b1ec..4a13848 100644 --- a/version.h +++ b/version.h @@ -3,8 +3,9 @@ #include "StringFormatter.h" -// const char VERSION[] PROGMEM ="0.2.0"; -#define VERSION "3.0.3" +#define VERSION "3.0.4" +// 3.0.4 Includes: +// Wifi startup bugfixes // 3.0.3 Includes: // command to write loco address and clear consist // command will allow for consist address