mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-31 03:13:45 +02:00
Compare commits
42 Commits
v5.1.6-Dev
...
v5.1.12-De
Author | SHA1 | Date | |
---|---|---|---|
|
ea4f90d5fc | ||
|
1181fd855d | ||
|
a092e06a6f | ||
|
68fd56e7fc | ||
|
370dae0ab8 | ||
|
bef4b2ec35 | ||
|
fe618d0b85 | ||
|
2ff1619ad1 | ||
|
7afd4443d6 | ||
|
52cfc18754 | ||
|
ed0cfee091 | ||
|
25bbfa4c68 | ||
|
2a46b96083 | ||
|
17c004aecf | ||
|
9e3ae21bb8 | ||
|
624656ebc9 | ||
|
5a7f278b1e | ||
|
9333beda49 | ||
|
aacb980dc8 | ||
|
11b9fd4ef5 | ||
|
d07718be8c | ||
|
d57b5ba537 | ||
|
dab02ec659 | ||
|
6ad5326f1d | ||
|
39e1363ce0 | ||
|
c9d4f5e94d | ||
|
8052090e0f | ||
|
dfe3e9d42c | ||
|
5d810a620b | ||
|
550ad58c4d | ||
|
7a305e179c | ||
|
8437b0e7aa | ||
|
46289fa78c | ||
|
b3cafd126e | ||
|
c55fa9f9d2 | ||
|
210d96a3e3 | ||
|
42f3c7c128 | ||
|
6cd7002e91 | ||
|
085762e800 | ||
|
2db2b0ecc6 | ||
|
fd58a749ef | ||
|
3bddf4dfd1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@ myFilter.cpp
|
||||
my*.h
|
||||
!my*.example.h
|
||||
compile_commands.json
|
||||
newcode.txt.old
|
||||
UserAddin.txt
|
||||
|
@@ -269,6 +269,6 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr);
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@ public :
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastRaw(clientType type,char * msg);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
|
||||
|
172
DCCEXParser.cpp
172
DCCEXParser.cpp
@@ -122,7 +122,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==INT16_MAX) break; \
|
||||
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_G='G';
|
||||
const int16_t HASH_KEYWORD_H='H';
|
||||
const int16_t HASH_KEYWORD_I='I';
|
||||
const int16_t HASH_KEYWORD_O='O';
|
||||
const int16_t HASH_KEYWORD_P='P';
|
||||
@@ -552,69 +553,131 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
bool singletrack=false;
|
||||
//byte t=0;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
//DIAG(F("Processing track - %d "), t);
|
||||
if (TrackManager::isProg(t)) {
|
||||
main = false;
|
||||
prog = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
main=true;
|
||||
prog=false;
|
||||
}
|
||||
singletrack=true;
|
||||
if (main) TrackManager::setTrackPower(false, false, POWERMODE::ON, t);
|
||||
if (prog) TrackManager::setTrackPower(true, false, POWERMODE::ON, t);
|
||||
|
||||
StringFormatter::send(stream, F("<1 %c>\n"), t+'A');
|
||||
//CommandDistributor::broadcastPower();
|
||||
//TrackManager::streamTrackState(NULL,t);
|
||||
return;
|
||||
}
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
if (!singletrack) {
|
||||
TrackManager::setJoin(join);
|
||||
if (join) TrackManager::setJoinPower(POWERMODE::ON);
|
||||
else {
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
{
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
}
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool singletrack=false;
|
||||
//byte t=0;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
//DIAG(F("Processing track - %d "), t);
|
||||
if (TrackManager::isProg(t)) {
|
||||
main = false;
|
||||
prog = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
main=true;
|
||||
prog=false;
|
||||
}
|
||||
singletrack=true;
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setTrackPower(false, false, POWERMODE::OFF, t);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(true, false, POWERMODE::OFF, t);
|
||||
}
|
||||
StringFormatter::send(stream, F("<0 %c>\n"), t+'A');
|
||||
//CommandDistributor::broadcastPower();
|
||||
//TrackManager::streamTrackState(NULL, t);
|
||||
return;
|
||||
}
|
||||
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
if (!singletrack) {
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
@@ -812,13 +875,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
const FSH *tpdesc = NULL;
|
||||
for (uint8_t p = 0; p < posCount; p++) {
|
||||
StringFormatter::send(stream, F("<jP"));
|
||||
int16_t value = tto->getPositionValue(p);
|
||||
int16_t angle = tto->getPositionAngle(p);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
|
||||
#endif
|
||||
if (tpdesc == NULL) tpdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %d %d %d \"%S\""), id, p, value, angle, tpdesc);
|
||||
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
}
|
||||
}
|
||||
@@ -1058,7 +1120,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
break;
|
||||
return true;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
|
@@ -125,8 +125,13 @@ private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
#if defined (ARDUINO_ARCH_STM32)
|
||||
// bit array of used pins (max 32)
|
||||
static uint32_t usedpins;
|
||||
#else
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
#endif
|
||||
static uint8_t highestPin;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
static int *analogvals;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022-23 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021, 2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
|
||||
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
||||
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||
@@ -154,13 +154,28 @@ HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
|
||||
// On STM32F4xx models that have them, Timers 6 and 7 have no PWM output capability,
|
||||
// so are good choices for general timer duties - they are used for tone and servo
|
||||
// in stm32duino so we shall usurp those as DCC-EX doesn't use tone or servo libs.
|
||||
// NB: the F401, F410 and F411 do **not** have Timer 6 or 7, so we use Timer 11
|
||||
#ifndef DCC_EX_TIMER
|
||||
#if defined(TIM6)
|
||||
#define DCC_EX_TIMER TIM6
|
||||
#elif defined(TIM7)
|
||||
#define DCC_EX_TIMER TIM7
|
||||
#elif defined(TIM11)
|
||||
#define DCC_EX_TIMER TIM11
|
||||
#else
|
||||
#warning This STM32F4XX variant does not have Timers 6,7 or 11!!
|
||||
#endif
|
||||
#endif // ifndef DCC_EX_TIMER
|
||||
|
||||
HardwareTimer dcctimer(DCC_EX_TIMER);
|
||||
void DCCTimer_Handler() __attribute__((interrupt));
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer11_Handler() {
|
||||
void DCCTimer_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
@@ -168,22 +183,24 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
timer.pause();
|
||||
timer.setPrescaleFactor(1);
|
||||
dcctimer.pause();
|
||||
dcctimer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timer.attachInterrupt(Timer11_Handler);
|
||||
timer.refresh();
|
||||
timer.resume();
|
||||
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
// dcctimer.attachInterrupt(Timer11_Handler);
|
||||
dcctimer.attachInterrupt(DCCTimer_Handler);
|
||||
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
|
||||
dcctimer.refresh();
|
||||
dcctimer.resume();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
//TODO: STM32 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);
|
||||
(void) pin;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -235,22 +252,91 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||
#endif
|
||||
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
|
||||
#define NUM_ADC_INPUTS 16
|
||||
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
// TODO: rationalise the size of these... could really use sparse arrays etc.
|
||||
static HardwareTimer * pin_timer[100] = {0};
|
||||
static uint32_t channel_frequency[100] = {0};
|
||||
static uint32_t pin_channel[100] = {0};
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
// Using the HardwareTimer library API included in stm32duino core to handle PWM duties
|
||||
// TODO: in order to use the HA code above which Neil kindly wrote, we may have to do something more
|
||||
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
|
||||
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
|
||||
// work well... then work backwards to integrate with HA mode if we can.
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
|
||||
{
|
||||
if (pin_timer[pin] == NULL) {
|
||||
// Automatically retrieve TIM instance and channel associated to pin
|
||||
// This is used to be compatible with all STM32 series automatically.
|
||||
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
|
||||
if (Instance == NULL) {
|
||||
// We shouldn't get here (famous last words) as it ought to have been caught by brakeCanPWM()!
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d has no PWM function!"), pin);
|
||||
return;
|
||||
}
|
||||
pin_channel[pin] = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
|
||||
// HardwareTimer is not destructed when setup function is finished.
|
||||
pin_timer[pin] = new HardwareTimer(Instance);
|
||||
// Configure and start PWM
|
||||
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
|
||||
if (pin_timer[pin] != NULL)
|
||||
{
|
||||
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Frequency change request
|
||||
if (frequency != channel_frequency[pin])
|
||||
{
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
||||
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
||||
}
|
||||
}
|
||||
channel_frequency[pin] = frequency;
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
// Calculate percentage duty cycle from value given
|
||||
uint32_t duty_cycle = (value * 100 / 256) + 1;
|
||||
if (pin_timer[pin] != NULL) {
|
||||
// if (duty_cycle == 100)
|
||||
// {
|
||||
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
|
||||
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
|
||||
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||
// }
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||
}
|
||||
|
||||
|
||||
// Now we can handle more ADCs, maybe this works!
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint32_t ADCee::usedpins = 0; // Max of 32 ADC input channels!
|
||||
uint8_t ADCee::highestPin = 0; // Highest pin to scan
|
||||
int * ADCee::analogvals = NULL; // Array of analog values last captured
|
||||
uint32_t * analogchans = NULL; // Array of channel numbers to be scanned
|
||||
// bool adc1configured = false;
|
||||
ADC_TypeDef * * adcchans = NULL; // Array to capture which ADC is each input channel on
|
||||
|
||||
int16_t ADCee::ADCmax()
|
||||
{
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
@@ -261,11 +347,33 @@ int ADCee::init(uint8_t pin) {
|
||||
return -1024; // some silly value as error
|
||||
|
||||
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC input channel
|
||||
ADC_TypeDef *adc = (ADC_TypeDef *)pinmap_find_peripheral(stmpin, PinMap_ADC); // find which ADC this pin is on ADC1/2/3 etc.
|
||||
int adcnum = 1;
|
||||
if (adc == ADC1)
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC1"), pin);
|
||||
// Checking for ADC2 and ADC3 being defined helps cater for more variants later
|
||||
#if defined(ADC2)
|
||||
else if (adc == ADC2)
|
||||
{
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC2"), pin);
|
||||
adcnum = 2;
|
||||
}
|
||||
#endif
|
||||
#if defined(ADC3)
|
||||
else if (adc == ADC3)
|
||||
{
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC3"), pin);
|
||||
adcnum = 3;
|
||||
}
|
||||
#endif
|
||||
else DIAG(F("ADCee::init(): found pin %d on unknown ADC!"), pin);
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
// Port config - find which port we're on and power it up
|
||||
GPIO_TypeDef *gpioBase;
|
||||
|
||||
switch (stmgpio)
|
||||
{
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
@@ -278,6 +386,20 @@ int ADCee::init(uint8_t pin) {
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
case 0x03:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Power up PORTD
|
||||
gpioBase = GPIOD;
|
||||
break;
|
||||
case 0x04:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; //Power up PORTE
|
||||
gpioBase = GPIOE;
|
||||
break;
|
||||
#if defined(GPIOF)
|
||||
case 0x05:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Power up PORTF
|
||||
gpioBase = GPIOF;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return -1023; // some silly value as error
|
||||
}
|
||||
@@ -293,31 +415,33 @@ int ADCee::init(uint8_t pin) {
|
||||
if (adcchan > 18)
|
||||
return -1022; // silly value as error
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
adc->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = adc->DR; // Read value from register
|
||||
|
||||
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||
if (id > 15) { // today we have not enough bits in the mask to support more
|
||||
return -1021;
|
||||
}
|
||||
// if (id > 15) { // today we have not enough bits in the mask to support more
|
||||
// return -1021;
|
||||
// }
|
||||
|
||||
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
|
||||
if (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
|
||||
}
|
||||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
adcchans[id] = adc; // Keep track of which ADC this channel is on
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||
|
||||
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
|
||||
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -344,13 +468,16 @@ void ADCee::scan() {
|
||||
static uint8_t id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
static ADC_TypeDef *adc;
|
||||
|
||||
if (waiting) {
|
||||
adc = adcchans[id];
|
||||
if (waiting)
|
||||
{
|
||||
// look if we have a result
|
||||
if (!(ADC1->SR & (1 << 1)))
|
||||
if (!(adc->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
analogvals[id] = adc->DR;
|
||||
// advance at least one track
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
@@ -369,9 +496,10 @@ void ADCee::scan() {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// start new ADC aquire on id
|
||||
adc = adcchans[id];
|
||||
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
|
||||
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
@@ -392,19 +520,83 @@ void ADCee::scan() {
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
// Disable the DMA controller for ADC1
|
||||
ADC1->CR2 &= ~ADC_CR2_DMA;
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
// Wait for ADC1 to become ready (calibration complete)
|
||||
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
#if defined(ADC2)
|
||||
// Enable the ADC2 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
|
||||
|
||||
// Initialize ADC2
|
||||
ADC2->CR1 = 0; // Disable all channels
|
||||
ADC2->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC2->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC2->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC2 to become ready (calibration complete)
|
||||
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
#if defined(ADC3)
|
||||
// Enable the ADC3 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
|
||||
|
||||
// Initialize ADC3
|
||||
ADC3->CR1 = 0; // Disable all channels
|
||||
ADC3->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC3->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC3->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC3 to become ready (calibration complete)
|
||||
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
|
14
EXRAIL2.cpp
14
EXRAIL2.cpp
@@ -780,6 +780,20 @@ void RMFT2::loop2() {
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_SET_POWER:
|
||||
// operand is TRACK_POWER , trackid
|
||||
//byte thistrack=getOperand(1);
|
||||
switch (operand) {
|
||||
case TRACK_POWER_0:
|
||||
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::OFF, getOperand(1));
|
||||
break;
|
||||
case TRACK_POWER_1:
|
||||
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::ON, getOperand(1));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OPCODE_SET_TRACK:
|
||||
// operand is trackmode<<8 | track id
|
||||
|
@@ -59,7 +59,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_SET_TRACK,OPCODE_SET_POWER,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
|
@@ -138,6 +138,7 @@
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SET_POWER
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
@@ -275,6 +276,7 @@
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
|
@@ -63,6 +63,11 @@
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -407,11 +412,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202308302157Z"
|
||||
#define GITHUB_SHA "devel-202309241855Z"
|
||||
|
@@ -37,9 +37,11 @@
|
||||
* I2C bus, or more than one I2C bus on the STM32 architecture
|
||||
*****************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||
#if defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|
||||
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|
||||
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
|
||||
// and Nucleo-144variants
|
||||
// and Nucleo-144 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
|
||||
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
|
||||
@@ -115,35 +117,46 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Disable the I2C device, as TRISE can only be programmed whilst disabled
|
||||
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
if (i2cClockSpeed > 100000L)
|
||||
if (i2cClockSpeed > 100000UL)
|
||||
{
|
||||
if (i2cClockSpeed > 400000L)
|
||||
i2cClockSpeed = 400000L;
|
||||
// if (i2cClockSpeed > 400000L)
|
||||
// i2cClockSpeed = 400000L;
|
||||
|
||||
t_rise = 300; // nanoseconds
|
||||
}
|
||||
else
|
||||
{
|
||||
i2cClockSpeed = 100000L;
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 1000; // nanoseconds
|
||||
}
|
||||
// Configure the rise time register
|
||||
s->TRISE = (t_rise / (1000 / i2c_MHz)) + 1;
|
||||
// Configure the rise time register - max allowed tRISE is 1000ns,
|
||||
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
|
||||
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle (use 2:1)
|
||||
// Bit 11-0: FREQR
|
||||
if (i2cClockSpeed > 100000L) {
|
||||
// In fast mode, I2C period is 3 * CCR * TPCLK1.
|
||||
//APB1clk1 / 3 / i2cClockSpeed = 38, but that results in 306KHz not 400!
|
||||
ccr_freq = 30; // So 30 gives 396KHz or so!
|
||||
s->CCR = (uint16_t)(ccr_freq | 0x8000); // We need Fast Mode set
|
||||
} else {
|
||||
// In standard mode, I2C period is 2 * CCR * TPCLK1
|
||||
ccr_freq = (APB1clk1 / 2 / i2cClockSpeed); // Should be 225 for 45Mhz APB1 clock
|
||||
s->CCR |= (uint16_t)ccr_freq;
|
||||
}
|
||||
// if (i2cClockSpeed > 400000UL) {
|
||||
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
|
||||
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
|
||||
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
|
||||
// } else {
|
||||
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
|
||||
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
|
||||
// if (i2cClockSpeed > 100000UL)
|
||||
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
|
||||
// }
|
||||
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
@@ -157,6 +170,7 @@ void I2CManagerClass::I2C_init()
|
||||
// Query the clockspeed from the STM32 HAL layer
|
||||
APB1clk1 = HAL_RCC_GetPCLK1Freq();
|
||||
i2c_MHz = APB1clk1 / 1000000UL;
|
||||
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
|
||||
// Enable clocks
|
||||
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
|
||||
// Reset the I2C1 peripheral to initial state
|
||||
@@ -179,6 +193,7 @@ void I2CManagerClass::I2C_init()
|
||||
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
||||
|
||||
// Software reset the I2C peripheral
|
||||
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
@@ -189,6 +204,7 @@ void I2CManagerClass::I2C_init()
|
||||
// Set I2C peripheral clock frequency
|
||||
// s->CR2 |= I2C_PERIPH_CLK;
|
||||
s->CR2 |= i2c_MHz;
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
|
||||
|
||||
// set own address to 00 - not used in master mode
|
||||
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
|
||||
@@ -212,6 +228,7 @@ void I2CManagerClass::I2C_init()
|
||||
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
|
||||
#endif
|
||||
|
||||
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
|
||||
// Calculate baudrate and set default rate for now
|
||||
// Configure the Clock Control Register for 100KHz SCL frequency
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
@@ -219,12 +236,14 @@ void I2CManagerClass::I2C_init()
|
||||
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
|
||||
// s->CCR = I2C_PERIPH_CLK * 5;
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // i2c_MHz * 5;
|
||||
// s->CCR = i2c_MHz * 5;
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
|
||||
|
||||
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
// s->TRISE = I2C_PERIPH_CLK + 1; // 1000 ns / 50 ns = 20 + 1 = 21
|
||||
s->TRISE = i2c_MHz + 1;
|
||||
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
|
||||
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
|
@@ -409,11 +409,12 @@ private:
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
int _read(VPIN vpin) override;
|
||||
void _broadcastStatus (VPIN vpin, uint8_t status);
|
||||
void _broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity);
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
||||
void _display() override;
|
||||
uint8_t _stepperStatus;
|
||||
uint8_t _previousStatus;
|
||||
uint8_t _currentActivity;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@@ -72,7 +72,9 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||
_stepperStatus = readBuffer[0];
|
||||
if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes
|
||||
_broadcastStatus(_firstVpin, _stepperStatus);
|
||||
if ( _currentActivity < 4) {
|
||||
_broadcastStatus(_firstVpin, _stepperStatus, _currentActivity);
|
||||
}
|
||||
_previousStatus = _stepperStatus;
|
||||
}
|
||||
delayUntil(currentMicros + 100000); // Wait 100ms before checking again
|
||||
@@ -90,11 +92,13 @@ int EXTurntable::_read(VPIN vpin) {
|
||||
}
|
||||
|
||||
// If a status change has occurred for a turntable object, broadcast it
|
||||
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status) {
|
||||
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) {
|
||||
Turntable *tto = Turntable::getByVpin(vpin);
|
||||
if (tto) {
|
||||
tto->setMoving(status);
|
||||
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
|
||||
if (activity < 4) {
|
||||
tto->setMoving(status);
|
||||
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,9 +128,10 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
_previousStatus = _stepperStatus;
|
||||
_broadcastStatus(vpin, _stepperStatus); // Broadcast when the rotation starts
|
||||
_currentActivity = activity;
|
||||
_broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts
|
||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||
}
|
||||
|
||||
|
@@ -33,17 +33,16 @@ public:
|
||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
PCA9555(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_INPUT_P0;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
|
@@ -34,6 +34,11 @@ unsigned long MotorDriver::globalOverloadStart = 0;
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
volatile portreg_t shadowPORTD;
|
||||
volatile portreg_t shadowPORTE;
|
||||
volatile portreg_t shadowPORTF;
|
||||
#endif
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
|
||||
@@ -68,6 +73,21 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
|
||||
DIAG(F("Found PORTD pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTD;
|
||||
}
|
||||
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
|
||||
DIAG(F("Found PORTE pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTE;
|
||||
}
|
||||
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
|
||||
DIAG(F("Found PORTF pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTF;
|
||||
}
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
@@ -91,6 +111,21 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTC;
|
||||
}
|
||||
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
|
||||
DIAG(F("Found PORTD pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTD;
|
||||
}
|
||||
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
|
||||
DIAG(F("Found PORTE pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTE;
|
||||
}
|
||||
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
|
||||
DIAG(F("Found PORTF pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTF;
|
||||
}
|
||||
}
|
||||
else dualSignal=false;
|
||||
|
||||
@@ -279,7 +314,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||
#pragma GCC pop_options
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
#ifdef VARIABLE_TONES
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
@@ -330,7 +365,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
{
|
||||
int f = 131;
|
||||
#ifdef VARIABLE_TONES
|
||||
@@ -348,7 +383,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
@@ -372,6 +407,24 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
interrupts();
|
||||
} else {
|
||||
noInterrupts();
|
||||
setSignal(tDir);
|
||||
@@ -393,6 +446,13 @@ void MotorDriver::throttleInrush(bool on) {
|
||||
} else {
|
||||
ledcDetachPin(brakePin);
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
} else {
|
||||
pinMode(brakePin, OUTPUT);
|
||||
}
|
||||
#else
|
||||
if(on){
|
||||
switch(brakePin) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
@@ -60,6 +60,16 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#define HAVE_PORTB(X) X
|
||||
#define PORTC GPIOC->ODR
|
||||
#define HAVE_PORTC(X) X
|
||||
#define PORTD GPIOD->ODR
|
||||
#define HAVE_PORTD(X) X
|
||||
#if defined(GPIOE)
|
||||
#define PORTE GPIOE->ODR
|
||||
#define HAVE_PORTE(X) X
|
||||
#endif
|
||||
#if defined(GPIOF)
|
||||
#define PORTF GPIOF->ODR
|
||||
#define HAVE_PORTF(X) X
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// if macros not defined as pass-through we define
|
||||
@@ -74,6 +84,15 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTD
|
||||
#define HAVE_PORTD(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTE
|
||||
#define HAVE_PORTE(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTF
|
||||
#define HAVE_PORTF(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
@@ -110,6 +129,9 @@ struct FASTPIN {
|
||||
extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
extern volatile portreg_t shadowPORTD;
|
||||
extern volatile portreg_t shadowPORTE;
|
||||
extern volatile portreg_t shadowPORTF;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||
|
||||
@@ -163,16 +185,16 @@ class MotorDriver {
|
||||
unsigned int raw2mA( int raw);
|
||||
unsigned int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
|
||||
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
|
||||
return true;
|
||||
#else
|
||||
#ifdef digitalPinToTimer
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
return (brakePin != UNUSED_PIN); // This was just (true) but we probably do need to check for UNUSED_PIN!
|
||||
#elif defined(__arm__)
|
||||
// On ARM we can use digitalPinHasPWM
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinHasPWM(brakePin)));
|
||||
#elif defined(digitalPinToTimer)
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||
#else
|
||||
return (brakePin<14 && brakePin >1);
|
||||
#endif //digitalPinToTimer
|
||||
#endif //ESP32/ARM
|
||||
#endif
|
||||
}
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
|
158
TrackManager.cpp
158
TrackManager.cpp
@@ -26,7 +26,8 @@
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include"CommandDistributor.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "DCCEXParser.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
@@ -154,10 +155,16 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
@@ -172,10 +179,16 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
@@ -319,6 +332,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
FOR_EACH_TRACK(t)
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
@@ -353,32 +367,36 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
// null stream means send to commandDistributor for broadcast
|
||||
if (track[t]==NULL) return;
|
||||
auto format=F("");
|
||||
bool pstate = TrackManager::isPowerOn(t);
|
||||
|
||||
switch(track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
format=F("<= %c MAIN>\n");
|
||||
if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");}
|
||||
break;
|
||||
#ifndef DISABLE_PROG
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");}
|
||||
break;
|
||||
#endif
|
||||
case TRACK_MODE_NONE:
|
||||
format=F("<= %c NONE>\n");
|
||||
if (pstate) {format=F("<= %c NONE ON>\n");} else {format=F("<= %c NONE OFF>\n");}
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
if (pstate) {format=F("<= %c EXT ON>\n");} else {format=F("<= %c EXT OFF>\n");}
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
format=F("<= %c DC %d>\n");
|
||||
if (pstate) {format=F("<= %c DC %d ON>\n");} else {format=F("<= %c DC %d OFF>\n");}
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
format=F("<= %c DCX %d>\n");
|
||||
if (pstate) {format=F("<= %c DCX %d ON>\n");} else {format=F("<= %c DCX %d OFF>\n");}
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
||||
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
||||
|
||||
if (stream) StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
|
||||
else CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
|
||||
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
@@ -412,49 +430,70 @@ std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
}
|
||||
#endif
|
||||
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
TrackManager::setTrackPower(setProg, setJoin, mode, t);
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack) {
|
||||
|
||||
//DIAG(F("SetTrackPower Processing Track %d"), thistrack);
|
||||
MotorDriver * driver=track[thistrack];
|
||||
if (!driver) return;
|
||||
|
||||
switch (track[thistrack]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
//DIAG(F("Processing track - %d setProg %d"), thistrack, setProg);
|
||||
if (setProg || setJoin) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(thistrack); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg && !setJoin) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
// It reports the first track only, as main, regardless of track settings.
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
}
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
@@ -526,3 +565,32 @@ bool TrackManager::isPowerOn(byte t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrackManager::isProg(byte t) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::returnMode(byte t) {
|
||||
return (track[t]->getMode());
|
||||
}
|
||||
|
||||
int16_t TrackManager::returnDCAddr(byte t) {
|
||||
return (trackDCAddr[t]);
|
||||
}
|
||||
|
||||
const char* TrackManager::getModeName(byte Mode) {
|
||||
|
||||
//DIAG(F("PowerMode %d"), Mode);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case 1: return "NONE";
|
||||
case 2: return "MAIN";
|
||||
case 4: return "PROG";
|
||||
case 8: return "DC";
|
||||
case 16: return "DCX";
|
||||
case 32: return "EXT";
|
||||
default: return "----";
|
||||
}
|
||||
}
|
||||
|
@@ -39,6 +39,10 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
@@ -60,10 +64,14 @@ class TrackManager {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
#endif
|
||||
static void setPower2(bool progTrack,POWERMODE mode);
|
||||
|
||||
static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode);
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,false,mode);}
|
||||
static void setJoinPower(POWERMODE mode) {setPower2(false,true,mode);}
|
||||
static void setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack);
|
||||
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
@@ -77,9 +85,14 @@ class TrackManager {
|
||||
static void sampleCurrent();
|
||||
static void reportGauges(Print* stream);
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportPowerChange(Print* stream, byte thistrack);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
static bool isProg(byte t);
|
||||
static byte returnMode(byte t);
|
||||
static int16_t returnDCAddr(byte t);
|
||||
static const char* getModeName(byte Mode);
|
||||
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
|
@@ -24,6 +24,7 @@
|
||||
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
|
@@ -31,7 +31,6 @@ include_dir = .
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
; monitor_filters = time
|
||||
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -60,7 +59,7 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -72,7 +71,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
platform = atmelavr
|
||||
@@ -84,7 +83,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DIO_NO_HAL
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
platform = atmelavr
|
||||
@@ -108,7 +107,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = ; -DDIAG_LOOPTIMES
|
||||
build_flags =
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -190,10 +189,75 @@ platform = ststm32
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Experimental - no reason this should not work, but not
|
||||
; tested as yet
|
||||
;
|
||||
[env:Nucleo-F401RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f401re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F13ZH has variant files
|
||||
; but NOT the nucleo_f413zh.json file which needs to be
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F413ZH]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f413zh
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F446ZE needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F446ZE]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f446ze
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F412ZG needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F412ZG]
|
||||
; platform = ststm32
|
||||
; board = blah_f412zg
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
; [env:Nucleo-F429ZI]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f429zi
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; arduino-libraries/Ethernet @ ^2.0.1
|
||||
; stm32duino/STM32Ethernet @ ^1.3.0
|
||||
; stm32duino/STM32duino LwIP @ ^2.1.2
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
@@ -232,5 +296,4 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
|
||||
lib_ignore =
|
||||
|
16
version.h
16
version.h
@@ -3,7 +3,21 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.1.6"
|
||||
#define VERSION "5.1.12"
|
||||
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
|
||||
// Added EXRAIL SET_POWER(track, ON/OFF)
|
||||
// Fixed a problem whereby <1 MAIN> also powered on PROG track
|
||||
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
|
||||
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
|
||||
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
|
||||
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
|
||||
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
|
||||
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
|
||||
// - ARM architecture brakeCanPWM now uses digitalPinHasPWM()
|
||||
// - STM32F4xx shadowpin extensions to handle pins on ports D, E and F
|
||||
// 5.1.9 - Fixed IO_PCA9555'h to work with PCA9548 mux, tested OK
|
||||
// 5.1.8 - STM32Fxx ADCee extension to support ADCs #2 and #3
|
||||
// 5.1.7 - Fix turntable broadcasts for non-movement activities and <JP> result
|
||||
// 5.1.6 - STM32F4xx native I2C driver added
|
||||
// 5.1.5 - Added turntable object and EXRAIL commands
|
||||
// - <I ...>, <JO ...>, <JP ...> - turntable commands
|
||||
|
Reference in New Issue
Block a user