diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index a1ff739..4a7c5c6 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -71,6 +71,7 @@ void setup() SerialManager::init(); DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com")); + DIAG(F("Platform: %s"), F(ARDUINO_TYPE)); // PMA - temporary CONDITIONAL_LCD_START { // This block is still executed for DIAGS if LCD not in use @@ -89,7 +90,7 @@ void setup() EthernetInterface::setup(); #endif // ETHERNET_ON -// Initialise HAL layer before reading EEprom or setting up MotorDrivers + // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); // Responsibility 3: Start the DCC engine. @@ -106,17 +107,16 @@ void setup() // Invoke any DCC++EX commands in the form "SETUP("xxxx");"" found in optional file mySetup.h. // This can be used to create turnouts, outputs, sensors etc. through the normal text commands. #if __has_include ( "mySetup.h") - #define SETUP(cmd) DCCEXParser::parse(F(cmd)) - #include "mySetup.h" - #undef SETUP + #define SETUP(cmd) DCCEXParser::parse(F(cmd)) + #include "mySetup.h" + #undef SETUP #endif #if defined(LCN_SERIAL) LCN_SERIAL.begin(115200); LCN::init(LCN_SERIAL); #endif - - LCD(3,F("Ready")); + LCD(3, F("Ready")); CommandDistributor::broadcastPower(); } diff --git a/DCC.cpp b/DCC.cpp index 31ff0be..71a09c7 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -62,8 +62,11 @@ byte DCC::globalSpeedsteps=128; void DCC::begin(const FSH * motorShieldName) { shieldName=(FSH *)motorShieldName; +#if defined(ARDUINO_ARCH_SAMD) + StringFormatter::send(SerialUSB,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); +#else StringFormatter::send(Serial,F("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); - +#endif #ifndef DISABLE_EEPROM // Load stuff from EEprom (void)EEPROM; // tell compiler not to warn this is unused diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 14b6bfe..f60a783 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -905,7 +905,9 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[]) wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms delay(50); // wait for the prescaller time to expire #else +#ifdef ARDUINO_ARCH_ESP ESP.restart(); +#endif #endif break; // and if we didnt restart } diff --git a/DCCTimerMEGAAVR.cpp b/DCCTimerMEGAAVR.cpp index 521e7f7..ce32ec5 100644 --- a/DCCTimerMEGAAVR.cpp +++ b/DCCTimerMEGAAVR.cpp @@ -75,7 +75,7 @@ extern char *__malloc_heap_start; // ISR called by timer interrupt every 58uS ISR(TCB0_INT_vect){ - TCB0.INTFLAGS = TCB_CAPT_bm; + TCB0.INTFLAGS = TCB_CAPT_bm; // Clear interrupt request flag interruptHandler(); } diff --git a/DCCTimerSAMD.cpp b/DCCTimerSAMD.cpp new file mode 100644 index 0000000..88603e6 --- /dev/null +++ b/DCCTimerSAMD.cpp @@ -0,0 +1,167 @@ +/* + * © 2021 Mike S + * © 2021 Harald Barth + * © 2021 Fred Decker + * © 2021 Chris Harlow + * © 2021 David Cutting + * © 2022 Paul M. Antoine + * 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 . + */ + +// ATTENTION: this file only compiles on a TEENSY +// Please refer to DCCTimer.h for general comments about how this class works +// This is to avoid repetition and duplication. +#ifdef ARDUINO_ARCH_SAMD + +#include "FSH.h" //PMA temp debug +#include "DIAG.h" //PMA temp debug +#include "DCCTimer.h" + +INTERRUPT_CALLBACK interruptHandler=0; + +void DCCTimer::begin(INTERRUPT_CALLBACK callback) { + interruptHandler=callback; + noInterrupts(); + + // PMA - Set up ADC to do faster reads... default for Arduino Zero platform configs is 436uS, + // and we need sub-100uS. This code sets it to a read speed of around 21uS, and enables 12-bit + ADC->CTRLA.bit.ENABLE = 0; // disable ADC + while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization + + ADC->CTRLB.reg &= 0b1111100011111111; // mask PRESCALER bits + ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 64 + ADC_CTRLB_RESSEL_10BIT; // Result on 10 bits default, 12 bits possible + + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time + ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0 + ADC->SAMPCTRL.reg = 0x00; // sampling Time Length = 0 + + ADC->CTRLA.bit.ENABLE = 1; // enable ADC + while(ADC->STATUS.bit.SYNCBUSY == 1); // wait for synchronization + + // PMA - actual timer setup goo + // Setup clock sources first + REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide 48MHz by 1 + GCLK_GENDIV_ID(4); // Apply to GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_GENCTRL = GCLK_GENCTRL_GENEN | // Enable GCLK + GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source + GCLK_GENCTRL_ID(4); // Select GCLK4 + while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization + + REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable generic clock + 4 << GCLK_CLKCTRL_GEN_Pos | // Apply to GCLK4 + GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK to TCC0/1 + while (GCLK->STATUS.bit.SYNCBUSY); + + // PMA - assume we're using TCC0... as we're bit-bashing the DCC waveform output pins anyway + // for "normal accuracy" DCC waveform generation. For high accuracy we're going to need + // to a good deal more. The TCC waveform output pins are mux'd on the SAMD, and OP pins + // for each TCC are only available on certain pins + TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Select NPWM as waveform + while (TCC0->SYNCBUSY.bit.WAVE); // Wait for sync + + // PMA - set the frequency + TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER(TCC_CTRLA_PRESCALER_DIV1_Val); + TCC0->PER.reg = CLOCK_CYCLES * 2; + while (TCC0->SYNCBUSY.bit.PER); + + // PMA - start it + TCC0->CTRLA.bit.ENABLE = 1; + while (TCC0->SYNCBUSY.bit.ENABLE); + + // PMA - set interrupt condition, priority and enable + TCC0->INTENSET.reg = TCC_INTENSET_OVF; // Only interrupt on overflow + NVIC_SetPriority((IRQn_Type)TCC0_IRQn, 0); // Make this highest priority + NVIC_EnableIRQ((IRQn_Type)TCC0_IRQn); // Enable the interrupt + interrupts(); +} + +// PMA - Timer IRQ handlers replace the dummy handlers (cortex_handlers) +// copied from rf24 branch +// TODO: test +void TCC0_Handler() { + if(TCC0->INTFLAG.bit.OVF) { + TCC0->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC1_Handler() { + if(TCC1->INTFLAG.bit.OVF) { + TCC1->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + +void TCC2_Handler() { + if(TCC2->INTFLAG.bit.OVF) { + TCC2->INTFLAG.bit.OVF = 1; // writing a 1 clears the flag + interruptHandler(); + } +} + + +bool DCCTimer::isPWMPin(byte pin) { + //TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM, + // there's no support yet for High Accuracy, so for now return false + // return digitalPinHasPWM(pin); + return false; +} + +void DCCTimer::setPWM(byte pin, bool high) { + // TODO: what are the relevant pins? + (void) pin; + (void) high; +} + +void DCCTimer::getSimulatedMacAddress(byte mac[6]) { + volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C; + volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040; +// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044; +// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048; + + volatile uint32_t m1 = *serno1; + volatile uint32_t m2 = *serno2; + mac[0] = m1 >> 8; + mac[1] = m1 >> 0; + mac[2] = m2 >> 24; + mac[3] = m2 >> 16; + mac[4] = m2 >> 8; + mac[5] = m2 >> 0; +} + +volatile int DCCTimer::minimum_free_memory=__INT_MAX__; + +// Return low memory value... +int DCCTimer::getMinimumFreeMemory() { + noInterrupts(); // Disable interrupts to get volatile value + int retval = freeMemory(); + interrupts(); + return retval; +} + +extern "C" char* sbrk(int incr); + +int DCCTimer::freeMemory() { + char top; + return (int)(&top - reinterpret_cast(sbrk(0))); +} + +#endif \ No newline at end of file diff --git a/EEStore.cpp b/EEStore.cpp index a441f00..89577aa 100644 --- a/EEStore.cpp +++ b/EEStore.cpp @@ -31,12 +31,12 @@ #include "Sensors.h" #include "Turnouts.h" -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMC) ExternalEEPROM EEPROM; #endif void EEStore::init() { -#if defined(ARDUINO_ARCH_SAMD) +#if defined(ARDUINO_ARCH_SAMC) EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three // A pins grounded (0b1010000 = 0x50) #endif diff --git a/I2CManager.h b/I2CManager.h index 1163261..67fd1ad 100644 --- a/I2CManager.h +++ b/I2CManager.h @@ -111,10 +111,10 @@ */ // Uncomment following line to enable Wire library instead of native I2C drivers -//#define I2C_USE_WIRE +#define I2C_USE_WIRE // Uncomment following line to disable the use of interrupts by the native I2C drivers. -//#define I2C_NO_INTERRUPTS +#define I2C_NO_INTERRUPTS // Default to use interrupts within the native I2C drivers. #ifndef I2C_NO_INTERRUPTS diff --git a/I2CManager_SAMD.h b/I2CManager_SAMD.h new file mode 100644 index 0000000..aeabd6c --- /dev/null +++ b/I2CManager_SAMD.h @@ -0,0 +1,188 @@ +/* + * © 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_SAMD_H +#define I2CMANAGER_SAMD_H + +#include +#include "I2CManager.h" + +//#include +//#include + +#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO) +// PMA - IRQ handler, based on SERCOM3 being used for I2C, as per Arduino Zero & Sparkfun SAMD21 +// TODO: test +void SERCOM3_Handler() { + I2CManagerClass::handleInterrupt(); +} +#endif + +/*************************************************************************** + * Set I2C clock speed register. + ***************************************************************************/ +void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) { + unsigned long temp = ((F_CPU / i2cClockSpeed) - 16) / 2; + for (uint8_t preScaler = 0; preScaler<=3; preScaler++) { + if (temp <= 255) { + TWBR = temp; + TWSR = (TWSR & 0xfc) | preScaler; + return; + } else + temp /= 4; + } + // Set slowest speed ~= 500 bits/sec + TWBR = 255; + TWSR |= 0x03; +} + +/*************************************************************************** + * Initialise I2C registers. + ***************************************************************************/ +void I2CManagerClass::I2C_init() +{ + // PMA - broadly we do the following + initialise the clock + initialise the NVIC + software reset the I2C for the sercom + set master mode + do we need smart mode and quick command?? + configure interrupt handlers + enable interrupts + set default baud rate + set SDA/SCL pins as outputs and enable pullups +} + +/*************************************************************************** + * Initiate a start bit for transmission. + ***************************************************************************/ +void I2CManagerClass::I2C_sendStart() { + bytesToSend = currentRequest->writeLen; + bytesToReceive = currentRequest->readLen; + // We may have initiated a stop bit before this without waiting for it. + // Wait for stop bit to be sent before sending start. + while (TWCR & (1<writeBuffer + (txCount++)); + else + TWDR = currentRequest->writeBuffer[txCount++]; + bytesToSend--; + TWCR = (1< 0) { + currentRequest->readBuffer[rxCount++] = TWDR; + bytesToReceive--; + } + /* fallthrough */ + case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received + if (bytesToReceive <= 1) { + TWCR = (1< 0) { + currentRequest->readBuffer[rxCount++] = TWDR; + bytesToReceive--; + } + TWCR = (1<i2cAddress << 1) | 1; // SLA+R + else + TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W + TWCR = (1<