mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-31 11:23:44 +02:00
Compare commits
78 Commits
frightrisk
...
TrackManag
Author | SHA1 | Date | |
---|---|---|---|
|
4e177cf744 | ||
|
ff96ed1157 | ||
|
a363f37dc7 | ||
|
11d959c8d0 | ||
|
6ff5e7c7a8 | ||
|
79d6ab1c87 | ||
|
2d52d88688 | ||
|
016bc37b53 | ||
|
06e7ad5c53 | ||
|
1c78792dda | ||
|
cfcd61174d | ||
|
55561188e1 | ||
|
55196c2e7d | ||
|
c7b38170c1 | ||
|
d3b72dc4fc | ||
|
17fb921678 | ||
|
867e3b3930 | ||
|
79ef114c0d | ||
|
6d2a9e3b36 | ||
|
f0e8419fea | ||
|
8f9da49cc8 | ||
|
d7a17b10b4 | ||
|
0268304d41 | ||
|
f66f5785f5 | ||
|
6d382fa0f4 | ||
|
af0d381e45 | ||
|
4a56998553 | ||
|
ac32cd5528 | ||
|
e721457844 | ||
|
3e8649f9a1 | ||
|
6994139e57 | ||
|
7a2fd90bfc | ||
|
43bac3f78e | ||
|
cd0b8790b6 | ||
|
228553013b | ||
|
acd6e7560f | ||
|
5bdbe3895d | ||
|
bcd1335b08 | ||
|
724dea22d5 | ||
|
21d1f482cf | ||
|
9273265036 | ||
|
8522e05b13 | ||
|
1b0d700009 | ||
|
aaa3e7a83c | ||
|
ece342f037 | ||
|
f9e36e6693 | ||
|
7dd680ccd5 | ||
|
ef50665c16 | ||
|
3f283620d3 | ||
|
5adabcd1af | ||
|
2727332be3 | ||
|
49e0a0e5f5 | ||
|
78810d0e36 | ||
|
a10fca2b12 | ||
|
99c7ff6c3f | ||
|
2a87a6e997 | ||
|
dea55fec79 | ||
|
865c8dd3bd | ||
|
be186b967b | ||
|
4f2dc0934f | ||
|
75b16c9047 | ||
|
cd952c6ede | ||
|
7e3dcb8e8c | ||
|
4437f870b6 | ||
|
f7e2c0ca99 | ||
|
2890a7928b | ||
|
03372f21e2 | ||
|
678cccbd95 | ||
|
524afc6caf | ||
|
6fc223d80b | ||
|
dd9152864b | ||
|
4f781074eb | ||
|
b29b8c999e | ||
|
99e636974a | ||
|
74bbe595fc | ||
|
1afb4753ec | ||
|
a7740d652d | ||
|
8db937e985 |
80
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
80
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,80 +0,0 @@
|
||||
# Bug report GitHub issue form
|
||||
#
|
||||
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
name: Bug Report
|
||||
description: Submit a bug report
|
||||
labels:
|
||||
- Bug
|
||||
title: "Bug Report: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to submit a bug report to the DCC-EX team!
|
||||
|
||||
In order to help us to validate the bug and ascertain what's causing it, please provide as much information as possible in this form.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Please provide the version of EX-CommandStation in use.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: Please provide a clear and concise description of what the symptoms of the bug are.
|
||||
placeholder: |
|
||||
When attempting to drive a locomotive on the main track, it runs forwards, backwards, spins around, jumps up and down, blows the horn, and then stops.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce the bug
|
||||
description: Please provide the steps to reproduce the behaviour.
|
||||
placeholder: |
|
||||
1. Turn on the CommandStation and track power.
|
||||
2. Connect Engine Driver to the CommandStation.
|
||||
3. Select locomotive with address 123.
|
||||
4. Throttle up to half speed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expectation
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: Please provide a clear and concise description of what you expected to happen.
|
||||
placeholder: |
|
||||
The locomotive should accelerate smoothly to half speed in a forward direction.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, upload any screenshots here.
|
||||
|
||||
- type: textarea
|
||||
id: hardware
|
||||
attributes:
|
||||
label: Hardware in use
|
||||
description: Please provide details of hardware in use including microcontroller, motor shield, and any other relevant information.
|
||||
placeholder: |
|
||||
Elegoo Mega2560
|
||||
Arduino R3 motor shield
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: extra-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Please provide any other relevant information that could help us resolve this issue, for example a customised config.h file.
|
12
.github/ISSUE_TEMPLATE/config.yml
vendored
12
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,12 +0,0 @@
|
||||
# Configuration file for the template chooser
|
||||
#
|
||||
# This file needs to exist in the https://github.com/DCC-EX/.github repository in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: DCC-EX Discord server
|
||||
url: https://discord.gg/y2sB4Fp
|
||||
about: For the best support experience, join our Discord server
|
||||
- name: DCC-EX Contact and Support page
|
||||
url: https://dcc-ex.com/support/index.html
|
||||
about: For other support options, refer to our Contact & Support page
|
31
.github/ISSUE_TEMPLATE/documentation_update.yml
vendored
31
.github/ISSUE_TEMPLATE/documentation_update.yml
vendored
@@ -1,31 +0,0 @@
|
||||
# Documentation update GitHub issue form
|
||||
#
|
||||
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
name: Documentation Update
|
||||
description: Submit a request for documentation updates, or to report broken links or inaccuracies
|
||||
title: "[Documentation Update]: "
|
||||
labels:
|
||||
- Needs Documentation
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to submit a request for updates to our documentation.
|
||||
|
||||
This can be used for general documentation requests if information is missing or lacking, or to correct issues with our existing documentation such as broken links, or inaccurate information.
|
||||
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Documentation details
|
||||
description: Provide the details of what needs to be documented or corrected.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: page
|
||||
attributes:
|
||||
label: Page with issues
|
||||
description: If reporting broken links or inaccuracies, please provide the link to the page here.
|
||||
placeholder: https://dcc-ex.com/index.html
|
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
37
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,37 +0,0 @@
|
||||
# Feature Request GitHub issue form
|
||||
#
|
||||
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
name: Feature Request
|
||||
description: Suggest a new feature
|
||||
title: "[Feature Request]: "
|
||||
labels:
|
||||
- Enhancement
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to suggest a new feature for EX-CommandStation.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Problem/idea statement
|
||||
description: Please provide the problem you're trying to solve, or share the idea you have.
|
||||
placeholder: A clear and concise description of the problem you're trying to solve, or the idea you have. For example, I'm always frustrated when...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives or workarounds
|
||||
description: Please provide any alternatives or workarounds you currently use.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context, screenshots, or files related to the feature request here.
|
39
.github/ISSUE_TEMPLATE/support_request.yml
vendored
39
.github/ISSUE_TEMPLATE/support_request.yml
vendored
@@ -1,39 +0,0 @@
|
||||
# Support Request GitHub issue form
|
||||
#
|
||||
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
name: Support Request
|
||||
description: Request support or assistance
|
||||
title: "[Support Request]: "
|
||||
labels:
|
||||
- Support Request
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to request support or assistance with EX-CommandStation.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Please provide the version of the software in use.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Issue description
|
||||
description: Please describe the issue being encountered as accurately and detailed as possible.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: hardware
|
||||
attributes:
|
||||
label: Hardware
|
||||
description: If appropriate, please provide details of the hardware in use.
|
||||
placeholder: |
|
||||
Elegoo Mega2560
|
||||
Arduino Motor Shield R3
|
24
.github/ISSUE_TEMPLATE/to_do.yml
vendored
24
.github/ISSUE_TEMPLATE/to_do.yml
vendored
@@ -1,24 +0,0 @@
|
||||
# General To Do item GitHub issue form
|
||||
#
|
||||
# This file needs to reside in the ".github/ISSUE_TEMPLATE/" folder.
|
||||
|
||||
name: To Do
|
||||
description: Create a general To Do item
|
||||
title: "[To Do]: "
|
||||
labels:
|
||||
- To Do
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Use this template to create an issue for a general task that needs to be done.
|
||||
|
||||
This is handy for capturing ad-hoc items that don't necessarily require code to be written or updated.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Task description
|
||||
description: Provide the details of what needs to be done.
|
||||
validations:
|
||||
required: true
|
156
.github/workflows/new-items.yml
vendored
156
.github/workflows/new-items.yml
vendored
@@ -1,156 +0,0 @@
|
||||
# This workflow is to be used for all repositories to integrate with the DCC++ EX Beta Project.
|
||||
# It will add all issues and pull requests for a repository to the project, and put in the correct status.
|
||||
#
|
||||
# Ensure "REPO_LABEL" is updated with the correct label for the repo stream of work.
|
||||
name: Add Issue or Pull Request to Project
|
||||
|
||||
env:
|
||||
REPO_LABEL: ${{ secrets.PROJECT_STREAM_LABEL }}
|
||||
PROJECT_NUMBER: 7
|
||||
ORG: DCC-EX
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
pull_request_target:
|
||||
types:
|
||||
- ready_for_review
|
||||
- opened
|
||||
- review_requested
|
||||
|
||||
jobs:
|
||||
add_to_project:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add labels
|
||||
uses: andymckay/labeler@master
|
||||
with:
|
||||
add-labels: ${{ env.REPO_LABEL }}
|
||||
|
||||
- name: Generate token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@36464acb844fc53b9b8b2401da68844f6b05ebb0
|
||||
with:
|
||||
app_id: ${{ secrets.PROJECT_APP_ID }}
|
||||
private_key: ${{ secrets. PROJECT_APP_KEY }}
|
||||
|
||||
- name: Get project data
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
run: |
|
||||
gh api graphql -f query='
|
||||
query($org: String!, $number: Int!) {
|
||||
organization(login: $org){
|
||||
projectV2(number: $number) {
|
||||
id
|
||||
fields(first:20) {
|
||||
nodes {
|
||||
... on ProjectV2Field {
|
||||
id
|
||||
name
|
||||
}
|
||||
... on ProjectV2SingleSelectField {
|
||||
id
|
||||
name
|
||||
options {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f org=$ORG -F number=$PROJECT_NUMBER > project_data.json
|
||||
|
||||
echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'BACKLOG_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Backlog") |.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'TO_DO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="To Do") |.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'NEEDS_REVIEW_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="Needs Review") |.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'IN_PROGRESS_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") |.options[] | select(.name=="In Progress") |.id' project_data.json) >> $GITHUB_ENV
|
||||
|
||||
- name: Add issue to project
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
ITEM_ID: ${{ github.event.issue.node_id }}
|
||||
if: github.event_name == 'issues'
|
||||
run: |
|
||||
project_item_id="$( gh api graphql -f query='
|
||||
mutation($project:ID!, $item:ID!) {
|
||||
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
|
||||
item {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
|
||||
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
|
||||
|
||||
- name: Add PR to project
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
ITEM_ID: ${{ github.event.pull_request.node_id }}
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
project_item_id="$( gh api graphql -f query='
|
||||
mutation($project:ID!, $item:ID!) {
|
||||
addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
|
||||
item {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f item=$ITEM_ID --jq '.data.addProjectV2ItemById.item.id')"
|
||||
echo 'PROJECT_ITEM_ID='$project_item_id >> $GITHUB_ENV
|
||||
|
||||
- name: Set status - To Do
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Bug') || contains(github.event.*.labels.*.name, 'Support Request'))
|
||||
run: |
|
||||
gh api graphql -f query='
|
||||
mutation(
|
||||
$project: ID!
|
||||
$item: ID!
|
||||
$status_field: ID!
|
||||
$status_value: String!
|
||||
){
|
||||
set_status: updateProjectV2ItemFieldValue(input: {
|
||||
projectId: $project
|
||||
itemId: $item
|
||||
fieldId: $status_field
|
||||
value: {
|
||||
singleSelectOptionId: $status_value
|
||||
}
|
||||
}) {
|
||||
projectV2Item {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.TO_DO_OPTION_ID }} --silent
|
||||
|
||||
- name: Set status - Review
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
if: github.event_name == 'issues' && (contains(github.event.*.labels.*.name, 'Unit Tested') || contains(github.event.*.labels.*.name, 'Regression Tested') || contains(github.event.*.labels.*.name, 'Needs Review')) || github.event_name == 'pull_request'
|
||||
run: |
|
||||
gh api graphql -f query='
|
||||
mutation(
|
||||
$project: ID!
|
||||
$item: ID!
|
||||
$status_field: ID!
|
||||
$status_value: String!
|
||||
){
|
||||
set_status: updateProjectV2ItemFieldValue(input: {
|
||||
projectId: $project
|
||||
itemId: $item
|
||||
fieldId: $status_field
|
||||
value: {
|
||||
singleSelectOptionId: $status_value
|
||||
}
|
||||
}) {
|
||||
projectV2Item {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.NEEDS_REVIEW_OPTION_ID }} --silent
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ myFilter.cpp
|
||||
myAutomation.h
|
||||
myFilter.cpp
|
||||
myLayout.h
|
||||
.vscode/extensions.json
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "defines.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
#if defined(BIG_MEMORY) | defined(WIFI_ON) | defined(ETHERNET_ON)
|
||||
// This section of CommandDistributor is simply not relevant on a uno or similar
|
||||
@@ -119,9 +120,9 @@ void CommandDistributor::broadcastLoco(byte slot) {
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
bool main=DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool prog=DCCWaveform::progTrack.getPowerMode()==POWERMODE::ON;
|
||||
bool join=DCCWaveform::progTrackSyncMain;
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
bool join=TrackManager::isJoined();
|
||||
const FSH * reason=F("");
|
||||
char state='1';
|
||||
if (main && prog && join) reason=F(" JOIN");
|
||||
|
@@ -84,12 +84,15 @@ void setup()
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
DCC::begin(MOTOR_SHIELD_TYPE);
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
@@ -147,7 +150,7 @@ void loop()
|
||||
// Report any decrease in memory (will automatically trigger on first call)
|
||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||
|
||||
int freeNow = minimumFreeMemory();
|
||||
int freeNow = DCCTimer::getMinimumFreeMemory();
|
||||
if (freeNow < ramLowWatermark) {
|
||||
ramLowWatermark = freeNow;
|
||||
LCD(3,F("Free RAM=%5db"), ramLowWatermark);
|
||||
|
430
DCC.cpp
430
DCC.cpp
@@ -8,7 +8,7 @@
|
||||
* © 2020-2021 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -35,6 +35,8 @@
|
||||
#include "IODevice.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
// This module is responsible for converting API calls into
|
||||
// messages to be sent to the waveform generator.
|
||||
@@ -56,36 +58,26 @@ const byte FN_GROUP_4=0x08;
|
||||
const byte FN_GROUP_5=0x10;
|
||||
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::joinRelay=UNUSED_PIN;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName, MotorDriver * mainDriver, MotorDriver* progDriver) {
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
StringFormatter::send(Serial,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
|
||||
// Initialise HAL layer before reading EEprom.
|
||||
IODevice::begin();
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
EEStore::init();
|
||||
#endif
|
||||
|
||||
DCCWaveform::begin(mainDriver,progDriver);
|
||||
DCCWaveform::begin();
|
||||
}
|
||||
|
||||
void DCC::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::setThrottle( uint16_t cab, uint8_t tSpeed, bool tDirection) {
|
||||
byte speedCode = (tSpeed & 0x7F) + tDirection * 128;
|
||||
setThrottle2(cab, speedCode);
|
||||
TrackManager::setDCSignal(cab,speedCode); // in case this is a dcc track on this addr
|
||||
// retain speed for loco reminders
|
||||
updateLocoReminder(cab, speedCode );
|
||||
}
|
||||
@@ -145,12 +137,25 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||
}
|
||||
|
||||
uint8_t DCC::getThrottleSpeed(int cab) {
|
||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||
// or -1 on "loco not found"
|
||||
int8_t DCC::getThrottleSpeed(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return -1;
|
||||
return speedTable[reg].speedCode & 0x7F;
|
||||
}
|
||||
|
||||
// returns speed code byte
|
||||
// or 128 (speed 0, dir forward) on "loco not found".
|
||||
uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0)
|
||||
return 128;
|
||||
return speedTable[reg].speedCode;
|
||||
}
|
||||
|
||||
// returns direction on loco
|
||||
// or true/forward on "loco not found"
|
||||
bool DCC::getThrottleDirection(int cab) {
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg<0) return true;
|
||||
@@ -237,24 +242,39 @@ uint32_t DCC::getFunctionMap(int cab) {
|
||||
return (reg<0)?0:speedTable[reg].functions;
|
||||
}
|
||||
|
||||
void DCC::setAccessory(int address, byte number, bool activate) {
|
||||
void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
// onoff is tristate:
|
||||
// 0 => send off packet
|
||||
// 1 => send on packet
|
||||
// >1 => send both on and off packets.
|
||||
|
||||
// An accessory has an address, 4 ports and 2 gates (coils) each. That's how
|
||||
// the initial decoders were orgnized and that influenced how the DCC
|
||||
// standard was made.
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, number, activate);
|
||||
DIAG(F("DCC::setAccessory(%d,%d,%d)"), address, port, gate);
|
||||
#endif
|
||||
// use masks to detect wrong values and do nothing
|
||||
if(address != (address & 511))
|
||||
return;
|
||||
if(number != (number & 3))
|
||||
if(port != (port & 3))
|
||||
return;
|
||||
byte b[2];
|
||||
|
||||
b[0] = address % 64 + 128; // first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
b[1] = ((((address / 64) % 8) << 4) + (number % 4 << 1) + activate % 2) ^ 0xF8; // second byte is of the form 1AAACDDD, where C should be 1, and the least significant D represent activate/deactivate
|
||||
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 4); // Repeat the packet four times
|
||||
// first byte is of the form 10AAAAAA, where AAAAAA represent 6 least signifcant bits of accessory address
|
||||
// second byte is of the form 1AAACPPG, where C is 1 for on, PP the ports 0 to 3 and G the gate (coil).
|
||||
b[0] = address % 64 + 128;
|
||||
b[1] = ((((address / 64) % 8) << 4) + (port % 4 << 1) + gate % 2) ^ 0xF8;
|
||||
if (onoff != 0) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat on packet three times
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::activateEvent(address<<2|number,activate);
|
||||
RMFT2::activateEvent(address<<2|port,gate);
|
||||
#endif
|
||||
}
|
||||
if (onoff != 1) {
|
||||
b[1] &= ~0x08; // set C to 0
|
||||
DCCWaveform::mainTrack.schedulePacket(b, 2, 3); // Repeat off packet three times
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -296,14 +316,6 @@ void DCC::writeCVBitMain(int cab, int cv, byte bNum, bool bValue) {
|
||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||
}
|
||||
|
||||
void DCC::setProgTrackSyncMain(bool on) {
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,on?HIGH:LOW);
|
||||
DCCWaveform::progTrackSyncMain=on;
|
||||
}
|
||||
void DCC::setProgTrackBoost(bool on) {
|
||||
DCCWaveform::progTrackBoosted=on;
|
||||
}
|
||||
|
||||
FSH* DCC::getMotorShieldName() {
|
||||
return shieldName;
|
||||
}
|
||||
@@ -313,14 +325,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
|
||||
W0,WACK,
|
||||
V0, WACK, // validate bit is 0
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
W1,WACK,
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1, // if acked, callback(1)
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
@@ -329,7 +341,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||
ITC0, // if acked, callback(0)
|
||||
V1, WACK, // validate bit is 1
|
||||
ITC1,
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
BASELINE,
|
||||
@@ -337,7 +349,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK,
|
||||
ITC0,
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH READ_BIT_PROG[] = {
|
||||
@@ -346,7 +358,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
||||
ITC1, // if acked, callback(1)
|
||||
V0, WACK, // validate bit is zero
|
||||
ITC0, // if acked callback 0
|
||||
FAIL // bit not readable
|
||||
CALLFAIL // bit not readable
|
||||
};
|
||||
|
||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
@@ -354,7 +366,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||
// handle decoders that dont ack a write
|
||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||
FAIL // callback (-1)
|
||||
CALLFAIL // callback (-1)
|
||||
};
|
||||
|
||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
@@ -380,7 +392,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||
FAIL };
|
||||
CALLFAIL };
|
||||
|
||||
|
||||
const ackOp FLASH READ_CV_PROG[] = {
|
||||
@@ -403,7 +415,7 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||
FAIL }; // verification failed
|
||||
CALLFAIL }; // verification failed
|
||||
|
||||
|
||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
@@ -469,7 +481,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||
V0, WACK, MERGE,
|
||||
V0, WACK, MERGE,
|
||||
VB, WACK, ITCB, // verify merged byte and callback
|
||||
FAIL
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
@@ -486,7 +498,7 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK, // some decoders don't ACK writes
|
||||
VB,WACK,ITCB,
|
||||
FAIL
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
@@ -510,39 +522,39 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||
SETBYTEL, // low byte of word
|
||||
WB,WACK,
|
||||
VB,WACK,ITC1, // callback(1) means Ok
|
||||
FAIL
|
||||
CALLFAIL
|
||||
};
|
||||
|
||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
DCCACK::Setup(cv, byteValue, WRITE_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?WRITE_BIT1_PROG:WRITE_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
DCCACK::Setup(cv, byteValue, VERIFY_BYTE_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum, bitValue?VERIFY_BIT1_PROG:VERIFY_BIT0_PROG, callback);
|
||||
}
|
||||
|
||||
|
||||
void DCC::readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback) {
|
||||
if (bitNum >= 8) callback(-1);
|
||||
else ackManagerSetup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
else DCCACK::Setup(cv, bitNum,READ_BIT_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::readCV(int16_t cv, ACK_CALLBACK callback) {
|
||||
ackManagerSetup(cv, 0,READ_CV_PROG, callback);
|
||||
DCCACK::Setup(cv, 0,READ_CV_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::getLocoId(ACK_CALLBACK callback) {
|
||||
ackManagerSetup(0,0, LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(0,0, LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
@@ -551,9 +563,9 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
return;
|
||||
}
|
||||
if (id<=HIGHEST_SHORT_ADDR)
|
||||
ackManagerSetup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(id, SHORT_LOCO_ID_PROG, callback);
|
||||
else
|
||||
ackManagerSetup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||
}
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
@@ -570,8 +582,7 @@ void DCC::forgetAllLocos() { // removes all speed reminders
|
||||
byte DCC::loopStatus=0;
|
||||
|
||||
void DCC::loop() {
|
||||
DCCWaveform::loop(ackManagerProg!=NULL); // power overload checks
|
||||
ackManagerLoop(); // maintain prog track ack manager
|
||||
TrackManager::loop(); // power overload checks
|
||||
issueReminders();
|
||||
}
|
||||
|
||||
@@ -698,319 +709,6 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
DCC::LOCO DCC::speedTable[MAX_LOCOS];
|
||||
int DCC::nextLoco = 0;
|
||||
|
||||
//ACK MANAGER
|
||||
ackOp const * DCC::ackManagerProg;
|
||||
ackOp const * DCC::ackManagerProgStart;
|
||||
byte DCC::ackManagerByte;
|
||||
byte DCC::ackManagerByteVerify;
|
||||
byte DCC::ackManagerStash;
|
||||
int DCC::ackManagerWord;
|
||||
byte DCC::ackManagerRetry;
|
||||
byte DCC::ackRetry = 2;
|
||||
int16_t DCC::ackRetrySum;
|
||||
int16_t DCC::ackRetryPSum;
|
||||
int DCC::ackManagerCv;
|
||||
byte DCC::ackManagerBitNum;
|
||||
bool DCC::ackReceived;
|
||||
bool DCC::ackManagerRejoin;
|
||||
|
||||
CALLBACK_STATE DCC::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCC::ackManagerCallback;
|
||||
|
||||
void DCC::ackManagerSetup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
if (!DCCWaveform::progTrack.canMeasureCurrent()) {
|
||||
callback(-2);
|
||||
return;
|
||||
}
|
||||
|
||||
ackManagerRejoin=DCCWaveform::progTrackSyncMain;
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
setProgTrackSyncMain(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
DCCWaveform::progTrack.autoPowerOff=false;
|
||||
if (DCCWaveform::progTrack.getPowerMode() == POWERMODE::OFF) {
|
||||
DCCWaveform::progTrack.autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCC::ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
ackManagerSetup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCC::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||
}
|
||||
|
||||
void DCC::ackManagerLoop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (DCCWaveform::progTrack.getPowerMode()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(DCCWaveform::progTrack.autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
DCCWaveform::progTrack.setAckBaseline();
|
||||
callbackState=READY;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {cv1(WRITE_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { cv1(VERIFY_BYTE, ackManagerCv), cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {cv1(BIT_MANIPULATE, ackManagerCv), cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
DCCWaveform::progTrack.setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=DCCWaveform::progTrack.getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !DCCWaveform::progTrack.autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (DCCWaveform::progTrack.autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (DCCWaveform::progTrack.autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
DCCWaveform::progTrack.doAutoPowerOff();
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
setProgTrackSyncMain(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
void DCC::displayCabList(Print * stream) {
|
||||
|
||||
|
125
DCC.h
125
DCC.h
@@ -36,69 +36,28 @@
|
||||
#error short addr greater than 127 does not make sense
|
||||
#endif
|
||||
#endif
|
||||
#include "DCCACK.h"
|
||||
const uint16_t LONG_ADDR_MARKER = 0x4000;
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
FAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
enum CALLBACK_STATE : byte {
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
// Allocations with memory implications..!
|
||||
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
const byte MAX_LOCOS = 20;
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
const byte MAX_LOCOS = 30;
|
||||
#else
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
const byte MAX_LOCOS = 50;
|
||||
#else
|
||||
const byte MAX_LOCOS = 30;
|
||||
#endif
|
||||
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName, MotorDriver *mainDriver, MotorDriver *progDriver);
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||
static uint8_t getThrottleSpeed(int cab);
|
||||
static int8_t getThrottleSpeed(int cab);
|
||||
static uint8_t getThrottleSpeedByte(int cab);
|
||||
static bool getThrottleDirection(int cab);
|
||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||
@@ -108,11 +67,9 @@ public:
|
||||
static int getFn(int cab, int16_t functionNumber);
|
||||
static uint32_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int aAdd, byte aNum, bool activate);
|
||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
static void setProgTrackSyncMain(bool on); // when true, prog track becomes driveable
|
||||
static void setProgTrackBoost(bool on); // when true, special prog track current limit does not apply
|
||||
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
static void readCV(int16_t cv, ACK_CALLBACK callback);
|
||||
static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error
|
||||
@@ -132,13 +89,7 @@ public:
|
||||
static inline void setGlobalSpeedsteps(byte s) {
|
||||
globalSpeedsteps = s;
|
||||
};
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
|
||||
struct LOCO
|
||||
{
|
||||
int loco;
|
||||
@@ -148,9 +99,10 @@ public:
|
||||
};
|
||||
static LOCO speedTable[MAX_LOCOS];
|
||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
|
||||
private:
|
||||
static byte joinRelay;
|
||||
static byte loopStatus;
|
||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||
static void updateLocoReminder(int loco, byte speedCode);
|
||||
@@ -160,33 +112,9 @@ private:
|
||||
static FSH *shieldName;
|
||||
static byte globalSpeedsteps;
|
||||
|
||||
static byte cv1(byte opcode, int cv);
|
||||
static byte cv2(int cv);
|
||||
static void issueReminders();
|
||||
static void callback(int value);
|
||||
|
||||
// ACK MANAGER
|
||||
static ackOp const *ackManagerProg;
|
||||
static ackOp const *ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerBitNum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerStash;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static void ackManagerSetup(int cv, byte bitNumOrbyteValue, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerSetup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void ackManagerLoop();
|
||||
static bool checkResets( uint8_t numResets);
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// NMRA codes #
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
@@ -201,31 +129,4 @@ private:
|
||||
static const byte BIT_OFF = 0x00;
|
||||
};
|
||||
|
||||
#ifdef ARDUINO_AVR_MEGA // is using Mega 1280, define as Mega 2560 (pinouts and functionality are identical)
|
||||
#define ARDUINO_AVR_MEGA2560
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#elif defined(ARDUINO_TEENSY32)
|
||||
#define ARDUINO_TYPE "TEENSY32"
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
468
DCCACK.cpp
Normal file
468
DCCACK.cpp
Normal file
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
|
||||
unsigned int DCCACK::minAckPulseDuration = 2000; // micros
|
||||
unsigned int DCCACK::maxAckPulseDuration = 20000; // micros
|
||||
|
||||
MotorDriver * DCCACK::progDriver=NULL;
|
||||
ackOp const * DCCACK::ackManagerProg;
|
||||
ackOp const * DCCACK::ackManagerProgStart;
|
||||
byte DCCACK::ackManagerByte;
|
||||
byte DCCACK::ackManagerByteVerify;
|
||||
byte DCCACK::ackManagerStash;
|
||||
int DCCACK::ackManagerWord;
|
||||
byte DCCACK::ackManagerRetry;
|
||||
byte DCCACK::ackRetry = 2;
|
||||
int16_t DCCACK::ackRetrySum;
|
||||
int16_t DCCACK::ackRetryPSum;
|
||||
int DCCACK::ackManagerCv;
|
||||
byte DCCACK::ackManagerBitNum;
|
||||
bool DCCACK::ackReceived;
|
||||
bool DCCACK::ackManagerRejoin;
|
||||
volatile uint8_t DCCACK::numAckGaps=0;
|
||||
volatile uint8_t DCCACK::numAckSamples=0;
|
||||
uint8_t DCCACK::trailingEdgeCounter=0;
|
||||
|
||||
|
||||
unsigned int DCCACK::ackPulseDuration; // micros
|
||||
unsigned long DCCACK::ackPulseStart; // micros
|
||||
volatile bool DCCACK::ackDetected;
|
||||
unsigned long DCCACK::ackCheckStart; // millis
|
||||
volatile bool DCCACK::ackPending;
|
||||
bool DCCACK::autoPowerOff;
|
||||
int DCCACK::ackThreshold;
|
||||
int DCCACK::ackLimitmA = 50;
|
||||
int DCCACK::ackMaxCurrent;
|
||||
unsigned int DCCACK::ackCheckDuration; // millis
|
||||
|
||||
|
||||
CALLBACK_STATE DCCACK::callbackState=READY;
|
||||
|
||||
ACK_CALLBACK DCCACK::ackManagerCallback;
|
||||
|
||||
void DCCACK::Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback) {
|
||||
progDriver=TrackManager::getProgDriver();
|
||||
if (progDriver==NULL) {
|
||||
callback(-3); // we dont have a prog track!
|
||||
return;
|
||||
}
|
||||
if (!progDriver->canMeasureCurrent()) {
|
||||
callback(-2); // our prog track cant measure current
|
||||
return;
|
||||
}
|
||||
progDriver->setResetCounterPointer(&(DCCWaveform::progTrack.sentResetsSincePacket));
|
||||
|
||||
ackManagerRejoin=TrackManager::isJoined();
|
||||
if (ackManagerRejoin ) {
|
||||
// Change from JOIN must zero resets packet.
|
||||
TrackManager::setJoin(false);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
}
|
||||
|
||||
autoPowerOff=false;
|
||||
if (progDriver->getPower() == POWERMODE::OFF) {
|
||||
autoPowerOff=true; // power off afterwards
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power on"));
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
|
||||
/* TODO !!! in MotorDriver surely!
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.sentResetsSincePacket = 0;
|
||||
**/
|
||||
}
|
||||
|
||||
|
||||
ackManagerCv = cv;
|
||||
ackManagerProg = program;
|
||||
ackManagerProgStart = program;
|
||||
ackManagerRetry = ackRetry;
|
||||
ackManagerByte = byteValueOrBitnum;
|
||||
ackManagerByteVerify = byteValueOrBitnum;
|
||||
ackManagerBitNum=byteValueOrBitnum;
|
||||
ackManagerCallback = callback;
|
||||
}
|
||||
|
||||
void DCCACK::Setup(int wordval, ackOp const program[], ACK_CALLBACK callback) {
|
||||
ackManagerWord=wordval;
|
||||
Setup(0, 0, program, callback);
|
||||
}
|
||||
|
||||
const byte RESET_MIN=8; // tuning of reset counter before sending message
|
||||
|
||||
// checkRessets return true if the caller should yield back to loop and try later.
|
||||
bool DCCACK::checkResets(uint8_t numResets) {
|
||||
return DCCWaveform::progTrack.sentResetsSincePacket < numResets;
|
||||
}
|
||||
// Operations applicable to PROG track ONLY.
|
||||
// (yes I know I could have subclassed the main track but...)
|
||||
|
||||
void DCCACK::setAckBaseline() {
|
||||
int baseline=progDriver->getCurrentRaw();
|
||||
ackThreshold= baseline + progDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,progDriver->raw2mA(baseline),
|
||||
ackThreshold,progDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
}
|
||||
|
||||
void DCCACK::setAckPending() {
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
|
||||
byte DCCACK::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,progDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::loop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
|
||||
// breaks from this switch will step to next prog entry
|
||||
// returns from this switch will stay on same entry
|
||||
// (typically waiting for a reset counter or ACK waiting, or when all finished.)
|
||||
switch (opcode) {
|
||||
case BASELINE:
|
||||
if (progDriver->getPower()==POWERMODE::OVERLOAD) return;
|
||||
if (checkResets(autoPowerOff || ackManagerRejoin ? 20 : 3)) return;
|
||||
setAckBaseline();
|
||||
callbackState=AFTER_READ;
|
||||
break;
|
||||
case W0: // write 0 bit
|
||||
case W1: // write 1 bit
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("W%d cv=%d bit=%d"),opcode==W1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = WRITE_BIT | (opcode==W1 ? BIT_ON : BIT_OFF) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case WB: // write byte
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("WB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = {DCC::cv1(WRITE_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
callbackState=AFTER_WRITE;
|
||||
}
|
||||
break;
|
||||
|
||||
case VB: // Issue validate Byte packet
|
||||
{
|
||||
if (checkResets( RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("VB cv=%d value=%d"),ackManagerCv,ackManagerByte);
|
||||
byte message[] = { DCC::cv1(VERIFY_BYTE, ackManagerCv), DCC::cv2(ackManagerCv), ackManagerByte};
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case V0:
|
||||
case V1: // Issue validate bit=0 or bit=1 packet
|
||||
{
|
||||
if (checkResets(RESET_MIN)) return;
|
||||
if (Diag::ACK) DIAG(F("V%d cv=%d bit=%d"),opcode==V1, ackManagerCv,ackManagerBitNum);
|
||||
byte instruction = VERIFY_BIT | (opcode==V0?BIT_OFF:BIT_ON) | ackManagerBitNum;
|
||||
byte message[] = {DCC::cv1(BIT_MANIPULATE, ackManagerCv), DCC::cv2(ackManagerCv), instruction };
|
||||
DCCWaveform::progTrack.schedulePacket(message, sizeof(message), PROG_REPEATS);
|
||||
setAckPending();
|
||||
}
|
||||
break;
|
||||
|
||||
case WACK: // wait for ack (or absence of ack)
|
||||
{
|
||||
byte ackState=2; // keep polling
|
||||
|
||||
ackState=getAck();
|
||||
if (ackState==2) return; // keep polling
|
||||
ackReceived=ackState==1;
|
||||
break; // we have a genuine ACK result
|
||||
}
|
||||
case ITC0:
|
||||
case ITC1: // If True Callback(0 or 1) (if prevous WACK got an ACK)
|
||||
if (ackReceived) {
|
||||
callback(opcode==ITC0?0:1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB: // If True callback(byte)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCBV: // If True callback(byte) - Verify
|
||||
if (ackReceived) {
|
||||
if (ackManagerByte == ackManagerByteVerify) {
|
||||
ackRetrySum ++;
|
||||
LCD(1, F("v %d %d Sum=%d"), ackManagerCv, ackManagerByte, ackRetrySum);
|
||||
}
|
||||
callback(ackManagerByte);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case ITCB7: // If True callback(byte & 0x7F)
|
||||
if (ackReceived) {
|
||||
callback(ackManagerByte & 0x7F);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NAKFAIL: // If nack callback(-1)
|
||||
if (!ackReceived) {
|
||||
callback(-1);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case CALLFAIL: // callback(-1)
|
||||
callback(-1);
|
||||
return;
|
||||
|
||||
case BIV: // ackManagerByte initial value
|
||||
ackManagerByte = ackManagerByteVerify;
|
||||
break;
|
||||
|
||||
case STARTMERGE:
|
||||
ackManagerBitNum=7;
|
||||
ackManagerByte=0;
|
||||
break;
|
||||
|
||||
case MERGE: // Merge previous Validate zero wack response with byte value and update bit number (use for reading CV bytes)
|
||||
ackManagerByte <<= 1;
|
||||
// ackReceived means bit is zero.
|
||||
if (!ackReceived) ackManagerByte |= 1;
|
||||
ackManagerBitNum--;
|
||||
break;
|
||||
|
||||
case SETBIT:
|
||||
ackManagerProg++;
|
||||
ackManagerBitNum=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETCV:
|
||||
ackManagerProg++;
|
||||
ackManagerCv=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTE:
|
||||
ackManagerProg++;
|
||||
ackManagerByte=GETFLASH(ackManagerProg);
|
||||
break;
|
||||
|
||||
case SETBYTEH:
|
||||
ackManagerByte=highByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case SETBYTEL:
|
||||
ackManagerByte=lowByte(ackManagerWord);
|
||||
break;
|
||||
|
||||
case STASHLOCOID:
|
||||
ackManagerStash=ackManagerByte; // stash value from CV17
|
||||
break;
|
||||
|
||||
case COMBINELOCOID:
|
||||
// ackManagerStash is cv17, ackManagerByte is CV 18
|
||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||
return;
|
||||
|
||||
case ITSKIP:
|
||||
if (!ackReceived) break;
|
||||
// SKIP opcodes until SKIPTARGET found
|
||||
while (opcode!=SKIPTARGET) {
|
||||
ackManagerProg++;
|
||||
opcode=GETFLASH(ackManagerProg);
|
||||
}
|
||||
break;
|
||||
case SKIPTARGET:
|
||||
break;
|
||||
default:
|
||||
DIAG(F("!! ackOp %d FAULT!!"),opcode);
|
||||
callback( -1);
|
||||
return;
|
||||
|
||||
} // end of switch
|
||||
ackManagerProg++;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCACK::callback(int value) {
|
||||
// check for automatic retry
|
||||
if (value == -1 && ackManagerRetry > 0) {
|
||||
ackRetrySum ++;
|
||||
LCD(0, F("Retry %d %d Sum=%d"), ackManagerCv, ackManagerRetry, ackRetrySum);
|
||||
ackManagerRetry --;
|
||||
ackManagerProg = ackManagerProgStart;
|
||||
return;
|
||||
}
|
||||
|
||||
static unsigned long callbackStart;
|
||||
// We are about to leave programming mode
|
||||
// Rule 1: If we have written to a decoder we must maintain power for 100mS
|
||||
// Rule 2: If we are re-joining the main track we must power off for 30mS
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_READ:
|
||||
if (ackManagerRejoin && autoPowerOff) {
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
} else {
|
||||
callbackState=READY;
|
||||
}
|
||||
break;
|
||||
|
||||
case AFTER_WRITE: // first attempt to callback after a write operation
|
||||
if (!ackManagerRejoin && !autoPowerOff) {
|
||||
callbackState=READY;
|
||||
break;
|
||||
} // lines 906-910 added. avoid wait after write. use 1 PROG
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_100;
|
||||
if (Diag::ACK) DIAG(F("Stable 100mS"));
|
||||
break;
|
||||
|
||||
case WAITING_100: // waiting for 100mS
|
||||
if (millis()-callbackStart < 100) break;
|
||||
// stable after power maintained for 100mS
|
||||
|
||||
// If we are going to power off anyway, it doesnt matter
|
||||
// but if we will keep the power on, we must off it for 30mS
|
||||
if (autoPowerOff) callbackState=READY;
|
||||
else { // Need to cycle power off and on
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
if (Diag::ACK) DIAG(F("OFF 30mS"));
|
||||
}
|
||||
break;
|
||||
|
||||
case WAITING_30: // waiting for 30mS with power off
|
||||
if (millis()-callbackStart < 30) break;
|
||||
//power has been off for 30mS
|
||||
progDriver->setPower(POWERMODE::ON);
|
||||
callbackState=READY;
|
||||
break;
|
||||
|
||||
case READY: // ready after read, or write after power delay and off period.
|
||||
// power off if we powered it on
|
||||
if (autoPowerOff) {
|
||||
if (Diag::ACK) DIAG(F("Auto Prog power off"));
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
/* TODO
|
||||
if (MotorDriver::commonFaultPin)
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
**/
|
||||
}
|
||||
// Restore <1 JOIN> to state before BASELINE
|
||||
if (ackManagerRejoin) {
|
||||
TrackManager::setJoin(true);
|
||||
if (Diag::ACK) DIAG(F("Auto JOIN"));
|
||||
}
|
||||
|
||||
ackManagerProg=NULL; // no more steps to execute
|
||||
if (Diag::ACK) DIAG(F("Callback(%d)"),value);
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
if (!ackPending) return;
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int current=progDriver->getCurrentRawInInterrupt();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
DCCWaveform::progTrack.clearRepeats(); // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
|
156
DCCACK.h
Normal file
156
DCCACK.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* © 2021 M Steve Todd
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef DCCACK_h
|
||||
#define DCCACK_h
|
||||
|
||||
#include "MotorDriver.h"
|
||||
|
||||
typedef void (*ACK_CALLBACK)(int16_t result);
|
||||
|
||||
enum ackOp : byte
|
||||
{ // Program opcodes for the ack Manager
|
||||
BASELINE, // ensure enough resets sent before starting and obtain baseline current
|
||||
W0,
|
||||
W1, // issue write bit (0..1) packet
|
||||
WB, // issue write byte packet
|
||||
VB, // Issue validate Byte packet
|
||||
V0, // Issue validate bit=0 packet
|
||||
V1, // issue validate bit=1 packlet
|
||||
WACK, // wait for ack (or absence of ack)
|
||||
ITC1, // If True Callback(1) (if prevous WACK got an ACK)
|
||||
ITC0, // If True callback(0);
|
||||
ITCB, // If True callback(byte)
|
||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||
ITCB7, // If True callback(byte &0x7F)
|
||||
NAKFAIL, // if false callback(-1)
|
||||
CALLFAIL, // callback(-1)
|
||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||
SETBIT, // sets bit number to next prog byte
|
||||
SETCV, // sets cv number to next prog byte
|
||||
SETBYTE, // sets current byte to next prog byte
|
||||
SETBYTEH, // sets current byte to word high byte
|
||||
SETBYTEL, // sets current byte to word low byte
|
||||
STASHLOCOID, // keeps current byte value for later
|
||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||
ITSKIP, // skip to SKIPTARGET if ack true
|
||||
SKIPTARGET = 0xFF // jump to target
|
||||
};
|
||||
|
||||
enum CALLBACK_STATE : byte {
|
||||
|
||||
AFTER_READ, // Start callback sequence after something was read from the decoder
|
||||
AFTER_WRITE, // Start callback sequence after something was written to the decoder
|
||||
WAITING_100, // Waiting for 100mS of stable power
|
||||
WAITING_30, // waiting to 30ms of power off gap.
|
||||
READY, // Ready to complete callback
|
||||
};
|
||||
|
||||
|
||||
|
||||
class DCCACK {
|
||||
public:
|
||||
static byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static void checkAck(byte sentResetsSincePacket); // Interrupt time ack checker
|
||||
static inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
static inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
static inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
static void Setup(int cv, byte byteValueOrBitnum, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void Setup(int wordval, ackOp const program[], ACK_CALLBACK callback);
|
||||
static void loop();
|
||||
static bool isActive() { return ackManagerProg!=NULL;}
|
||||
static inline int16_t setAckRetry(byte retry) {
|
||||
ackRetry = retry;
|
||||
ackRetryPSum = ackRetrySum;
|
||||
ackRetrySum = 0; // reset running total
|
||||
return ackRetryPSum;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
static const byte SET_SPEED = 0x3f;
|
||||
static const byte WRITE_BYTE = 0x7C;
|
||||
static const byte VERIFY_BYTE = 0x74;
|
||||
static const byte BIT_MANIPULATE = 0x78;
|
||||
static const byte WRITE_BIT = 0xF0;
|
||||
static const byte VERIFY_BIT = 0xE0;
|
||||
static const byte BIT_ON = 0x08;
|
||||
static const byte BIT_OFF = 0x00;
|
||||
|
||||
static void setAckBaseline();
|
||||
static void setAckPending();
|
||||
static void callback(int value);
|
||||
|
||||
static const int PROG_REPEATS = 8; // repeats of programming commands (some decoders need at least 8 to be reliable)
|
||||
|
||||
// ACK management (Prog track only)
|
||||
static void checkAck();
|
||||
static bool checkResets(uint8_t numResets);
|
||||
|
||||
static volatile bool ackPending;
|
||||
static volatile bool ackDetected;
|
||||
static int ackThreshold;
|
||||
static int ackLimitmA;
|
||||
static int ackMaxCurrent;
|
||||
static unsigned long ackCheckStart; // millis
|
||||
static unsigned int ackCheckDuration; // millis
|
||||
|
||||
static unsigned int ackPulseDuration; // micros
|
||||
static unsigned long ackPulseStart; // micros
|
||||
|
||||
static unsigned int minAckPulseDuration ; // micros
|
||||
static unsigned int maxAckPulseDuration ; // micros
|
||||
static MotorDriver* progDriver;
|
||||
static volatile uint8_t numAckGaps;
|
||||
static volatile uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
static ackOp const * ackManagerProg;
|
||||
static ackOp const * ackManagerProgStart;
|
||||
static byte ackManagerByte;
|
||||
static byte ackManagerByteVerify;
|
||||
static byte ackManagerStash;
|
||||
static int ackManagerWord;
|
||||
static byte ackManagerRetry;
|
||||
static byte ackRetry;
|
||||
static int16_t ackRetrySum;
|
||||
static int16_t ackRetryPSum;
|
||||
static int ackManagerCv;
|
||||
static byte ackManagerBitNum;
|
||||
static bool ackReceived;
|
||||
static bool ackManagerRejoin;
|
||||
static bool autoPowerOff;
|
||||
static CALLBACK_STATE callbackState;
|
||||
static ACK_CALLBACK ackManagerCallback;
|
||||
|
||||
|
||||
};
|
||||
#endif
|
3
DCCEX.h
3
DCCEX.h
@@ -38,12 +38,13 @@
|
||||
#endif
|
||||
#include "LCD_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "freeMemory.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Sensors.h"
|
||||
#include "Outputs.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL.h"
|
||||
|
||||
#endif
|
||||
|
119
DCCEXParser.cpp
119
DCCEXParser.cpp
@@ -30,26 +30,20 @@
|
||||
#include "Turnouts.h"
|
||||
#include "Outputs.h"
|
||||
#include "Sensors.h"
|
||||
#include "freeMemory.h"
|
||||
#include "GITHUB_SHA.h"
|
||||
#include "version.h"
|
||||
#include "defines.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "EEStore.h"
|
||||
#include "DIAG.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
#ifdef HAS_AVR_WDT
|
||||
#include <avr/wdt.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Figure out if we have enough memory for advanced features
|
||||
//
|
||||
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
|
||||
// nope
|
||||
#else
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#endif
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
@@ -195,21 +189,7 @@ void DCCEXParser::parse(const FSH * cmd) {
|
||||
|
||||
// See documentation on DCC class for info on this section
|
||||
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||
// This function can get stings of the form "<C OMM AND>" or "C OMM AND"
|
||||
// found is true first after the leading "<" has been passed
|
||||
bool found = (com[0] != '<');
|
||||
for (byte *c=com; c[0] != '\0'; c++) {
|
||||
if (found) {
|
||||
parseOne(stream, c, ringStream);
|
||||
found=false;
|
||||
}
|
||||
if (c[0] == '<')
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
void DCCEXParser::parse(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
#ifndef DISABLE_EEPROM
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
@@ -292,20 +272,29 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE> or <a LINEARADDRESS ACTIVATE>
|
||||
case 'a': // ACCESSORY <a ADDRESS SUBADDRESS ACTIVATE [ONOFF]> or <a LINEARADDRESS ACTIVATE>
|
||||
{
|
||||
int address;
|
||||
byte subaddress;
|
||||
byte activep;
|
||||
byte onoff;
|
||||
if (params==2) { // <a LINEARADDRESS ACTIVATE>
|
||||
address=(p[0] - 1) / 4 + 1;
|
||||
subaddress=(p[0] - 1) % 4;
|
||||
activep=1;
|
||||
activep=1;
|
||||
onoff=2; // send both
|
||||
}
|
||||
else if (params==3) { // <a ADDRESS SUBADDRESS ACTIVATE>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
activep=2;
|
||||
onoff=2; // send both
|
||||
}
|
||||
else if (params==4) { // <a ADDRESS SUBADDRESS ACTIVATE ONOFF>
|
||||
address=p[0];
|
||||
subaddress=p[1];
|
||||
activep=2;
|
||||
onoff=p[3];
|
||||
}
|
||||
else break; // invalid no of parameters
|
||||
|
||||
@@ -313,12 +302,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
((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
|
||||
|| ((onoff & 0x01) != onoff) // invalid onoff 0|1
|
||||
) break;
|
||||
// Honour the configuration option (config.h) which allows the <a> command to be reversed
|
||||
#ifdef DCC_ACCESSORY_COMMAND_REVERSE
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==0,onoff);
|
||||
#else
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1);
|
||||
DCC::setAccessory(address, subaddress,p[activep]==1,onoff);
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -400,7 +390,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
{ // <R CV> -- uses verify callback
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
DCC::verifyCVByte(p[0], p[1], callback_Vbyte);
|
||||
DCC::verifyCVByte(p[0], 0, callback_Vbyte);
|
||||
return;
|
||||
}
|
||||
if (params == 3)
|
||||
@@ -443,9 +433,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
else break; // will reply <X>
|
||||
}
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
if (prog) DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -470,12 +460,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
if (main) DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
DCC::setProgTrackBoost(false); // Prog track boost mode will not outlive prog track off
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -486,18 +476,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"), DCCWaveform::mainTrack.getCurrentmA(),
|
||||
DCCWaveform::mainTrack.getMaxmA(), DCCWaveform::mainTrack.getTripmA());
|
||||
StringFormatter::send(stream, F("<a %d>\n"), DCCWaveform::mainTrack.get1024Current()); //'a' message deprecated, remove once JMRI 4.22 is available
|
||||
return;
|
||||
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
|
||||
break;
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<p%d>\n"), DCCWaveform::mainTrack.getPowerMode() == POWERMODE::ON);
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
@@ -525,6 +511,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
return;
|
||||
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
|
||||
case '#': // NUMBER OF LOCOSLOTS <#>
|
||||
StringFormatter::send(stream, F("<# %d>\n"), MAX_LOCOS);
|
||||
return;
|
||||
@@ -545,8 +536,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
#if WIFI_ON
|
||||
case '+': // Complex Wifi interface command (not usual parse)
|
||||
if (atCommandCallback && !ringStream) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
atCommandCallback((HardwareSerial *)stream,com);
|
||||
return;
|
||||
}
|
||||
@@ -599,17 +589,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else {
|
||||
const FSH *tdesc = NULL;
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (tdesc == NULL)
|
||||
tdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
tdesc);
|
||||
}
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -848,23 +835,23 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), minimumFreeMemory());
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
DCCWaveform::progTrack.setAckLimit(p[2]);
|
||||
DCCACK::setAckLimit(p[2]);
|
||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
||||
DCCWaveform::progTrack.setMinAckPulseDuration(p[2]);
|
||||
DCCACK::setMinAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
||||
DCCWaveform::progTrack.setMaxAckPulseDuration(p[2]);
|
||||
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
||||
if (p[2] >255) p[2]=3;
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCC::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
@@ -895,13 +882,17 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
DCC::setProgTrackBoost(true);
|
||||
return true;
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESET:
|
||||
{
|
||||
#ifdef HAS_AVR_WDT
|
||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
delay(50); // wait for the prescaller time to expire
|
||||
#else
|
||||
ESP.restart();
|
||||
#endif
|
||||
break; // and <X> if we didnt restart
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,6 @@ struct DCCEXParser
|
||||
|
||||
static void parse(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void parse(const FSH * cmd);
|
||||
static void parseOne(Print * stream, byte * command, RingStream * ringStream);
|
||||
static void setFilter(FILTER_CALLBACK filter);
|
||||
static void setRMFTFilter(FILTER_CALLBACK filter);
|
||||
static void setAtCommandCallback(AT_COMMAND_CALLBACK filter);
|
||||
|
95
DCCTimer.h
95
DCCTimer.h
@@ -20,6 +20,34 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* There are several different implementations of this class which the compiler will select
|
||||
according to the hardware.
|
||||
*/
|
||||
|
||||
/* This timer class is used to manage the single timer required to handle the DCC waveform.
|
||||
* All timer access comes through this class so that it can be compiled for
|
||||
* various hardware CPU types.
|
||||
*
|
||||
* DCCEX works on a single timer interrupt at a regular 58uS interval.
|
||||
* The DCCWaveform class generates the signals to the motor shield
|
||||
* based on this timer.
|
||||
*
|
||||
* If the motor drivers are BOTH configured to use the correct 2 pins for the architecture,
|
||||
* (see isPWMPin() function. )
|
||||
* then this allows us to use a hardware driven pin switching arrangement which is
|
||||
* achieved by setting the duty cycle of the NEXT clock interrupt to 0% or 100% depending on
|
||||
* the required pin state. (see setPWM())
|
||||
* This is more accurate than the software interrupt but at the expense of
|
||||
* limiting the choice of available pins.
|
||||
* Fortunately, a standard motor shield on a Mega uses pins that qualify for PWM...
|
||||
* Other shields may be jumpered to PWM pins or run directly using the software interrupt.
|
||||
*
|
||||
* Because the PWM-based waveform is effectively set half a cycle after the software version,
|
||||
* it is not acceptable to drive the two tracks on different methiods or it would cause
|
||||
* problems for <1 JOIN> etc.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DCCTimer_h
|
||||
#define DCCTimer_h
|
||||
#include "Arduino.h"
|
||||
@@ -32,11 +60,68 @@ class DCCTimer {
|
||||
static void getSimulatedMacAddress(byte mac[6]);
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
#if (defined(TEENSYDUINO) && !defined(__IMXRT1062__))
|
||||
static void read_mac(byte mac[6]);
|
||||
static void read(uint8_t word, uint8_t *mac, uint8_t offset);
|
||||
#endif
|
||||
private:
|
||||
static void clearPWM();
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
static void inline updateMinimumFreeMemoryISR(unsigned char extraBytes=0) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
|
||||
static int getMinimumFreeMemory();
|
||||
|
||||
private:
|
||||
static int freeMemory();
|
||||
static volatile int minimum_free_memory;
|
||||
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create a cpu type we can share and
|
||||
// gigure out if we have enough memory for advanced features
|
||||
// so define HAS_ENOUGH_MEMORY until proved otherwise.
|
||||
#define HAS_ENOUGH_MEMORY
|
||||
#define HAS_AVR_WDT
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#elif defined(ARDUINO_AVR_MEGA)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#elif defined(ARDUINO_TEENSY32)
|
||||
#define ARDUINO_TYPE "TEENSY32"
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#undef HAS_AVR_WDT
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#undef HAS_AVR_WDT
|
||||
#else
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN DCCTimer.h
|
||||
#endif
|
||||
#endif
|
||||
|
117
DCCTimerAVR.cpp
Normal file
117
DCCTimerAVR.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UNO or MEGA
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
|
||||
#include <avr/boot.h>
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
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
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
TCCR1A= 0;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
|
||||
#endif
|
129
DCCTimerESP.cpp
Normal file
129
DCCTimerESP.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* © 2020-2022 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on an ESP8266 and ESP32
|
||||
// On ESP32 we do not even use the functions but they are here for completeness sake
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
timer1_disable();
|
||||
|
||||
// There seem to be differnt ways to attach interrupt handler
|
||||
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
|
||||
// Let us choose the one from the API
|
||||
timer1_attachInterrupt(interruptHandler);
|
||||
|
||||
// not exactly sure of order:
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
||||
timer1_write(CLOCK_CYCLES);
|
||||
}
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::clearPWM() {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
#include "DCCTimer.h"
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
|
||||
|
||||
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler = callback;
|
||||
hw_timer_t *timer = NULL;
|
||||
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
|
||||
timerAttachInterrupt(timer, interruptHandler, true);
|
||||
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
|
||||
timerAlarmEnable(timer);
|
||||
}
|
||||
|
||||
// We do not support to use PWM to make the Waveform on ESP
|
||||
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||
return false;
|
||||
}
|
||||
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||
}
|
||||
|
||||
// Fake this as it should not be used
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
mac[0] = 0xFE;
|
||||
mac[1] = 0xBE;
|
||||
mac[2] = 0xEF;
|
||||
mac[3] = 0xC0;
|
||||
mac[4] = 0xFF;
|
||||
mac[5] = 0xEE;
|
||||
}
|
||||
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
int DCCTimer::freeMemory() {
|
||||
return ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
@@ -47,14 +47,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a UnoWifiRev3 or NanoEvery
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
|
||||
#include "DCCTimer.h"
|
||||
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;
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
#ifdef ARDUINO_ARCH_MEGAAVR
|
||||
// Arduino unoWifi Rev2 and nanoEvery architectire
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
@@ -87,132 +90,32 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
memcpy(mac,(void *) &SIGROW.SERNUM0,6); // serial number
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
}
|
||||
|
||||
#elif defined(TEENSYDUINO)
|
||||
IntervalTimer myDCCTimer;
|
||||
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// Return low memory value...
|
||||
int DCCTimer::getMinimumFreeMemory() {
|
||||
noInterrupts(); // Disable interrupts to get volatile value
|
||||
int retval = minimum_free_memory;
|
||||
interrupts();
|
||||
return retval;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
#else
|
||||
read_mac(mac);
|
||||
#endif
|
||||
}
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
void DCCTimer::read_mac(byte mac[6]) {
|
||||
read(0xe,mac,0);
|
||||
read(0xf,mac,3);
|
||||
}
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
int DCCTimer::freeMemory() {
|
||||
char top;
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Arduino nano, uno, mega etc
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
#endif
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||
TCCR1A = 0;
|
||||
ICR1 = CLOCK_CYCLES;
|
||||
TCNT1 = 0;
|
||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
// Alternative pin manipulation via PWM control.
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
return pin==TIMER1_A_PIN
|
||||
|| pin==TIMER1_B_PIN
|
||||
#ifdef TIMER1_C_PIN
|
||||
|| pin==TIMER1_C_PIN
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
if (pin==TIMER1_A_PIN) {
|
||||
TCCR1A |= _BV(COM1A1);
|
||||
OCR1A= high?1024:0;
|
||||
}
|
||||
else if (pin==TIMER1_B_PIN) {
|
||||
TCCR1A |= _BV(COM1B1);
|
||||
OCR1B= high?1024:0;
|
||||
}
|
||||
#ifdef TIMER1_C_PIN
|
||||
else if (pin==TIMER1_C_PIN) {
|
||||
TCCR1A |= _BV(COM1C1);
|
||||
OCR1C= high?1024:0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#include <avr/boot.h>
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
126
DCCTimerTEENSY.cpp
Normal file
126
DCCTimerTEENSY.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// ATTENTION: this file only compiles on a TEENSY
|
||||
// Please refer to DCCTimer.h for general comments about how this class works
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef TEENSYDUINO
|
||||
|
||||
#include "DCCTimer.h"
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
IntervalTimer myDCCTimer;
|
||||
|
||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO what are the relevant pins?
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
// Do nothing unless we implent HA
|
||||
}
|
||||
|
||||
#if defined(__IMXRT1062__) //Teensy 4.0 and Teensy 4.1
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
uint32_t m1 = HW_OCOTP_MAC1;
|
||||
uint32_t m2 = HW_OCOTP_MAC0;
|
||||
mac[0] = m1 >> 8;
|
||||
mac[1] = m1 >> 0;
|
||||
mac[2] = m2 >> 24;
|
||||
mac[3] = m2 >> 16;
|
||||
mac[4] = m2 >> 8;
|
||||
mac[5] = m2 >> 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// http://forum.pjrc.com/threads/91-teensy-3-MAC-address
|
||||
void teensyRead(uint8_t word, uint8_t *mac, uint8_t offset) {
|
||||
FTFL_FCCOB0 = 0x41; // Selects the READONCE command
|
||||
FTFL_FCCOB1 = word; // read the given word of read once area
|
||||
|
||||
// launch command and wait until complete
|
||||
FTFL_FSTAT = FTFL_FSTAT_CCIF;
|
||||
while(!(FTFL_FSTAT & FTFL_FSTAT_CCIF));
|
||||
|
||||
*(mac+offset) = FTFL_FCCOB5; // collect only the top three bytes,
|
||||
*(mac+offset+1) = FTFL_FCCOB6; // in the right orientation (big endian).
|
||||
*(mac+offset+2) = FTFL_FCCOB7; // Skip FTFL_FCCOB4 as it's always 0.
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
teensyRead(0xe,mac,0);
|
||||
teensyRead(0xf,mac,3);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
char top;
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
254
DCCWaveform.cpp
254
DCCWaveform.cpp
@@ -21,43 +21,52 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// This code is replaced entirely on an ESP32
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "DCCWaveform.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DCCACK.h"
|
||||
#include "DIAG.h"
|
||||
#include "freeMemory.h"
|
||||
|
||||
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::progTrackSyncMain=false;
|
||||
bool DCCWaveform::progTrackBoosted=false;
|
||||
int DCCWaveform::progTripValue=0;
|
||||
volatile uint8_t DCCWaveform::numAckGaps=0;
|
||||
volatile uint8_t DCCWaveform::numAckSamples=0;
|
||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||
|
||||
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);
|
||||
// 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();
|
||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
void DCCWaveform::loop(bool ackManagerActive) {
|
||||
mainTrack.checkPowerOverload(false);
|
||||
progTrack.checkPowerOverload(ackManagerActive);
|
||||
void DCCWaveform::loop() {
|
||||
// empty placemarker in case ESP32 needs something here
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
@@ -66,11 +75,11 @@ void DCCWaveform::interruptHandler() {
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
// member functions would be cleaner but have more overhead
|
||||
byte sigMain=signalTransform[mainTrack.state];
|
||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
|
||||
// Set the signal state for both tracks
|
||||
mainTrack.motorDriver->setSignal(sigMain);
|
||||
progTrack.motorDriver->setSignal(sigProg);
|
||||
TrackManager::setDCCSignal(sigMain);
|
||||
TrackManager::setPROGSignal(sigProg);
|
||||
|
||||
// Move on in the state engine
|
||||
mainTrack.state=stateTransform[mainTrack.state];
|
||||
@@ -80,7 +89,7 @@ void DCCWaveform::interruptHandler() {
|
||||
// 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();
|
||||
else DCCACK::checkAck(progTrack.sentResetsSincePacket);
|
||||
|
||||
}
|
||||
#pragma GCC push_options
|
||||
@@ -91,9 +100,6 @@ void DCCWaveform::interruptHandler() {
|
||||
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.
|
||||
|
||||
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
|
||||
|
||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
isMainTrack = isMain;
|
||||
@@ -105,106 +111,11 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
requiredPreambles = preambleBits+1;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
ackPending=false;
|
||||
}
|
||||
|
||||
POWERMODE DCCWaveform::getPowerMode() {
|
||||
return powerMode;
|
||||
}
|
||||
|
||||
void DCCWaveform::setPowerMode(POWERMODE mode) {
|
||||
powerMode = mode;
|
||||
bool ison = (mode == POWERMODE::ON);
|
||||
motorDriver->setPower( ison);
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
|
||||
void DCCWaveform::checkPowerOverload(bool ackManagerActive) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= motorDriver->getRawCurrentTripValue();
|
||||
if (!isMainTrack && !ackManagerActive && !progTrackSyncMain && !progTrackBoosted)
|
||||
tripValue=progTripValue;
|
||||
|
||||
// Trackname for diag messages later
|
||||
const FSH*trackname = isMainTrack ? F("MAIN") : F("PROG");
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=motorDriver->getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPowerMode(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (MotorDriver::commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
setPowerMode(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %S"), trackname);
|
||||
} else {
|
||||
DIAG(F("%S FAULT PIN ACTIVE - OVERLOAD"), trackname);
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPowerMode(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=motorDriver->raw2mA(lastCurrent);
|
||||
unsigned int maxmA=motorDriver->raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("%S TRACK POWER OVERLOAD current=%d max=%d offtime=%d"), trackname, mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPowerMode(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("%S TRACK POWER RESET delay=%d"), trackname, sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
const WAVE_STATE DCCWaveform::stateTransform[]={
|
||||
/* WAVE_START -> */ WAVE_PENDING,
|
||||
/* WAVE_MID_1 -> */ WAVE_START,
|
||||
/* WAVE_HIGH_0 -> */ WAVE_MID_0,
|
||||
/* WAVE_MID_0 -> */ WAVE_LOW_0,
|
||||
/* WAVE_LOW_0 -> */ WAVE_START,
|
||||
/* WAVE_PENDING (should not happen) -> */ WAVE_PENDING};
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
const bool DCCWaveform::signalTransform[]={
|
||||
/* WAVE_START -> */ HIGH,
|
||||
/* WAVE_MID_1 -> */ LOW,
|
||||
/* WAVE_HIGH_0 -> */ HIGH,
|
||||
/* WAVE_MID_0 -> */ LOW,
|
||||
/* WAVE_LOW_0 -> */ LOW,
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
@@ -216,7 +127,7 @@ void DCCWaveform::interrupt2() {
|
||||
remainingPreambles--;
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
updateMinimumFreeMemory(22);
|
||||
DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -261,8 +172,6 @@ void DCCWaveform::interrupt2() {
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
|
||||
// Wait until there is no packet pending, then make this pending
|
||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||
@@ -281,89 +190,4 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
packetPending = true;
|
||||
sentResetsSincePacket=0;
|
||||
}
|
||||
|
||||
// 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();
|
||||
ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
|
||||
if (Diag::ACK) DIAG(F("ACK baseline=%d/%dmA Threshold=%d/%dmA Duration between %uus and %uus"),
|
||||
baseline,motorDriver->raw2mA(baseline),
|
||||
ackThreshold,motorDriver->raw2mA(ackThreshold),
|
||||
minAckPulseDuration, maxAckPulseDuration);
|
||||
}
|
||||
|
||||
void DCCWaveform::setAckPending() {
|
||||
if (isMainTrack) return;
|
||||
ackMaxCurrent=0;
|
||||
ackPulseStart=0;
|
||||
ackPulseDuration=0;
|
||||
ackDetected=false;
|
||||
ackCheckStart=millis();
|
||||
numAckSamples=0;
|
||||
numAckGaps=0;
|
||||
ackPending=true; // interrupt routines will now take note
|
||||
}
|
||||
|
||||
byte DCCWaveform::getAck() {
|
||||
if (ackPending) return (2); // still waiting
|
||||
if (Diag::ACK) DIAG(F("%S after %dmS max=%d/%dmA pulse=%uuS samples=%d gaps=%d"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration,
|
||||
ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration, numAckSamples, numAckGaps);
|
||||
if (ackDetected) return (1); // Yes we had an ack
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::checkAck() {
|
||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int current=motorDriver->getCurrentRaw();
|
||||
numAckSamples++;
|
||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||
// An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
|
||||
|
||||
if (current>ackThreshold) {
|
||||
if (trailingEdgeCounter > 0) {
|
||||
numAckGaps++;
|
||||
trailingEdgeCounter = 0;
|
||||
}
|
||||
if (ackPulseStart==0) ackPulseStart=micros(); // leading edge of pulse detected
|
||||
return;
|
||||
}
|
||||
|
||||
// not in pulse
|
||||
if (ackPulseStart==0) return; // keep waiting for leading edge
|
||||
|
||||
// if we reach to this point, we have
|
||||
// detected trailing edge of pulse
|
||||
if (trailingEdgeCounter == 0) {
|
||||
ackPulseDuration=micros()-ackPulseStart;
|
||||
}
|
||||
|
||||
// but we do not trust it yet and return (which will force another
|
||||
// measurement) and first the third time around with low current
|
||||
// the ack detection will be finalized.
|
||||
if (trailingEdgeCounter < 2) {
|
||||
trailingEdgeCounter++;
|
||||
return;
|
||||
}
|
||||
trailingEdgeCounter = 0;
|
||||
|
||||
if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
|
||||
ackCheckDuration=millis()-ackCheckStart;
|
||||
ackDetected=true;
|
||||
ackPending=false;
|
||||
transmitRepeats=0; // shortcut remaining repeat packets
|
||||
return; // we have a genuine ACK result
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
#endif
|
111
DCCWaveform.h
111
DCCWaveform.h
@@ -26,106 +26,39 @@
|
||||
|
||||
#include "MotorDriver.h"
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
// to the transform array.
|
||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||
|
||||
|
||||
// NOTE: static functions are used for the overall controller, then
|
||||
// one instance is created for each track.
|
||||
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
|
||||
const byte idlePacket[] = {0xFF, 0x00, 0xFF};
|
||||
const byte resetPacket[] = {0x00, 0x00, 0x00};
|
||||
|
||||
class DCCWaveform {
|
||||
public:
|
||||
DCCWaveform( byte preambleBits, bool isMain);
|
||||
static void begin(MotorDriver * mainDriver, MotorDriver * progDriver);
|
||||
static void loop(bool ackManagerActive);
|
||||
static void begin();
|
||||
static void loop();
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
|
||||
void beginTrack();
|
||||
void setPowerMode(POWERMODE);
|
||||
POWERMODE getPowerMode();
|
||||
void checkPowerOverload(bool ackManagerActive);
|
||||
inline int get1024Current() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return (int)(lastCurrent*(long int)1024/motorDriver->getRawCurrentTripValue());
|
||||
return 0;
|
||||
}
|
||||
inline int getCurrentmA() {
|
||||
if (powerMode == POWERMODE::ON)
|
||||
return motorDriver->raw2mA(lastCurrent);
|
||||
return 0;
|
||||
}
|
||||
inline int getMaxmA() {
|
||||
if (maxmA == 0) { //only calculate this for first request, it doesn't change
|
||||
maxmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue()); //TODO: replace with actual max value or calc
|
||||
}
|
||||
return maxmA;
|
||||
}
|
||||
inline int getTripmA() {
|
||||
if (tripmA == 0) { //only calculate this for first request, it doesn't change
|
||||
tripmA = motorDriver->raw2mA(motorDriver->getRawCurrentTripValue());
|
||||
}
|
||||
return tripmA;
|
||||
}
|
||||
inline void clearRepeats() { transmitRepeats=0; }
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
volatile bool packetPending;
|
||||
volatile byte sentResetsSincePacket;
|
||||
volatile bool autoPowerOff=false;
|
||||
void setAckBaseline(); //prog track only
|
||||
void setAckPending(); //prog track only
|
||||
byte getAck(); //prog track only 0=NACK, 1=ACK 2=keep waiting
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
inline void doAutoPowerOff() {
|
||||
if (autoPowerOff) {
|
||||
setPowerMode(POWERMODE::OFF);
|
||||
autoPowerOff=false;
|
||||
}
|
||||
};
|
||||
inline bool canMeasureCurrent() {
|
||||
return motorDriver->canMeasureCurrent();
|
||||
};
|
||||
inline void setAckLimit(int mA) {
|
||||
ackLimitmA = mA;
|
||||
}
|
||||
inline void setMinAckPulseDuration(unsigned int i) {
|
||||
minAckPulseDuration = i;
|
||||
}
|
||||
inline void setMaxAckPulseDuration(unsigned int i) {
|
||||
maxAckPulseDuration = i;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// For each state of the wave nextState=stateTransform[currentState]
|
||||
static const WAVE_STATE stateTransform[6];
|
||||
|
||||
// For each state of the wave, signal pin is HIGH or LOW
|
||||
static const bool signalTransform[6];
|
||||
private:
|
||||
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
void checkAck();
|
||||
|
||||
bool isMainTrack;
|
||||
MotorDriver* motorDriver;
|
||||
// Transmission controller
|
||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte transmitLength;
|
||||
@@ -138,38 +71,6 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
int lastCurrent;
|
||||
static int progTripValue;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
// ACK management (Prog track only)
|
||||
volatile bool ackPending;
|
||||
volatile bool ackDetected;
|
||||
int ackThreshold;
|
||||
int ackLimitmA = 50;
|
||||
int ackMaxCurrent;
|
||||
unsigned long ackCheckStart; // millis
|
||||
unsigned int ackCheckDuration; // millis
|
||||
|
||||
unsigned int ackPulseDuration; // micros
|
||||
unsigned long ackPulseStart; // micros
|
||||
|
||||
unsigned int minAckPulseDuration = 2000; // micros
|
||||
unsigned int maxAckPulseDuration = 20000; // micros
|
||||
|
||||
volatile static uint8_t numAckGaps;
|
||||
volatile static uint8_t numAckSamples;
|
||||
static uint8_t trailingEdgeCounter;
|
||||
};
|
||||
#endif
|
||||
|
73
EXRAIL2.cpp
73
EXRAIL2.cpp
@@ -49,7 +49,7 @@
|
||||
#include "DCCEXParser.h"
|
||||
#include "Turnouts.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
#include "TrackManager.h"
|
||||
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
@@ -172,7 +172,7 @@ int16_t LookList::find(int16_t value) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
doSignal(sigid & (~ SERVO_SIGNAL_FLAG) & (~ACTIVE_HIGH_SIGNAL_FLAG), SIGNAL_RED);
|
||||
}
|
||||
|
||||
for (progCounter=0;; SKIPOP){
|
||||
@@ -320,23 +320,12 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
if (flag & ~TASK_FLAG) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d} "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
@@ -498,10 +487,14 @@ void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||
void RMFT2::driveLoco(byte speed) {
|
||||
if (loco<=0) return; // Prevent broadcast!
|
||||
if (diag) DIAG(F("EXRAIL drive %d %d %d"),loco,speed,forward^invert);
|
||||
if (DCCWaveform::mainTrack.getPowerMode()==POWERMODE::OFF) {
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
/* TODO.....
|
||||
power on appropriate track if DC or main if dcc
|
||||
if (TrackManager::getMainPowerMode()==POWERMODE::OFF) {
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
**********/
|
||||
|
||||
DCC::setThrottle(loco,speed, forward^invert);
|
||||
speedo=speed;
|
||||
}
|
||||
@@ -551,15 +544,7 @@ bool RMFT2::skipIfBlock() {
|
||||
|
||||
|
||||
/* static */ void RMFT2::readLocoCallback(int16_t cv) {
|
||||
if (cv & LONG_ADDR_MARKER) { // maker bit indicates long addr
|
||||
progtrackLocoId = cv ^ LONG_ADDR_MARKER; // remove marker bit to get real long addr
|
||||
if (progtrackLocoId <= HIGHEST_SHORT_ADDR ) { // out of range for long addr
|
||||
DIAG(F("Long addr %d <= %d unsupported\n"), progtrackLocoId, HIGHEST_SHORT_ADDR);
|
||||
progtrackLocoId = -1;
|
||||
}
|
||||
} else {
|
||||
progtrackLocoId=cv;
|
||||
}
|
||||
progtrackLocoId=cv;
|
||||
}
|
||||
|
||||
void RMFT2::loop() {
|
||||
@@ -705,12 +690,21 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::OFF);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_SET_TRACK:
|
||||
// operand is trackmode<<8 | track id
|
||||
// If DC/DCX use my loco for DC address
|
||||
{
|
||||
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
|
||||
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
|
||||
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_RESUME:
|
||||
pausingTask=NULL;
|
||||
driveLoco(speedo);
|
||||
@@ -862,20 +856,19 @@ void RMFT2::loop2() {
|
||||
return;
|
||||
|
||||
case OPCODE_JOIN:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true);
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_POWERON:
|
||||
DCCWaveform::mainTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
DCC::setProgTrackSyncMain(false);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
@@ -1003,7 +996,7 @@ int16_t RMFT2::getSignalSlot(VPIN id) {
|
||||
// for a LED signal it will be same as redpin
|
||||
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||
|
||||
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
|
||||
if ((sigid & ~SERVO_SIGNAL_FLAG & ~ACTIVE_HIGH_SIGNAL_FLAG)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
|
@@ -52,6 +52,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,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -107,7 +108,6 @@ class LookList {
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
|
@@ -109,6 +109,7 @@
|
||||
#undef SERVO_TURNOUT
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
@@ -211,6 +212,7 @@
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
|
@@ -262,7 +262,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
#define PARSE(msg) PRINT(msg)
|
||||
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||
@@ -285,6 +285,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
|
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* © 2022 Bruno Sanches
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
@@ -36,13 +35,8 @@ EthernetInterface * EthernetInterface::singleton=NULL;
|
||||
*/
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
if (singleton!=NULL) {
|
||||
DIAG(F("Prog Error!"));
|
||||
return;
|
||||
}
|
||||
if ((singleton=new EthernetInterface()))
|
||||
return;
|
||||
DIAG(F("Ethernet not initialized"));
|
||||
singleton=new EthernetInterface();
|
||||
if (!singleton->connected) singleton=NULL;
|
||||
};
|
||||
|
||||
|
||||
@@ -66,94 +60,73 @@ EthernetInterface::EthernetInterface()
|
||||
DIAG(F("Ethernet.begin FAILED"));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware)
|
||||
DIAG(F("Ethernet shield not detected or is a W5100"));
|
||||
|
||||
unsigned long startmilli = millis();
|
||||
while ((millis() - startmilli) < 5500) { // Loop to give time to check for cable connection
|
||||
if (Ethernet.linkStatus() == LinkON)
|
||||
break;
|
||||
DIAG(F("Ethernet waiting for link (1sec) "));
|
||||
delay(1000);
|
||||
#endif
|
||||
DIAG(F("begin OK."));
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long startmilli = millis();
|
||||
while ((millis() - startmilli) < 5500) // Loop to give time to check for cable connection
|
||||
{
|
||||
if (Ethernet.linkStatus() == LinkON)
|
||||
break;
|
||||
DIAG(F("Ethernet waiting for link (1sec) "));
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Cleanup any resources
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
EthernetInterface::~EthernetInterface() {
|
||||
delete server;
|
||||
delete outboundRing;
|
||||
if (Ethernet.linkStatus() == LinkOFF) {
|
||||
DIAG(F("Ethernet cable not connected"));
|
||||
return;
|
||||
}
|
||||
|
||||
connected=true;
|
||||
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main loop for the EthernetInterface
|
||||
*
|
||||
*/
|
||||
void EthernetInterface::loop() {
|
||||
if(!singleton || (!singleton->checkLink()))
|
||||
return;
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
if (!singleton) return;
|
||||
|
||||
switch (Ethernet.maintain())
|
||||
{
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("Ethernet Error: renewed fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Ethernet Error: rebind fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
|
||||
singleton->loop2();
|
||||
|
||||
switch (Ethernet.maintain()) {
|
||||
case 1:
|
||||
//renewed fail
|
||||
DIAG(F("Ethernet Error: renewed fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
case 3:
|
||||
//rebind fail
|
||||
DIAG(F("Ethernet Error: rebind fail"));
|
||||
singleton=NULL;
|
||||
return;
|
||||
default:
|
||||
//nothing happened
|
||||
break;
|
||||
}
|
||||
singleton->loop2();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks ethernet link cable status and detects when it connects / disconnects
|
||||
*
|
||||
* @return true when cable is connected, false otherwise
|
||||
*/
|
||||
bool EthernetInterface::checkLink() {
|
||||
if (Ethernet.linkStatus() != LinkOFF) { // check for not linkOFF instead of linkON as the W5100 does return LinkUnknown
|
||||
//if we are not connected yet, setup a new server
|
||||
if(!connected) {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
server->begin();
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
LCD(5,F("Port:%d"), IP_PORT);
|
||||
// only create a outboundRing it none exists, this may happen if the cable
|
||||
// gets disconnected and connected again
|
||||
if(!outboundRing)
|
||||
outboundRing=new RingStream(OUTBOUND_RING_SIZE);
|
||||
}
|
||||
return true;
|
||||
} else { // connected
|
||||
DIAG(F("Ethernet cable disconnected"));
|
||||
connected=false;
|
||||
//clean up any client
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) {
|
||||
if(clients[socket].connected())
|
||||
clients[socket].stop();
|
||||
}
|
||||
// tear down server
|
||||
delete server;
|
||||
server = nullptr;
|
||||
LCD(4,F("IP: None"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EthernetInterface::loop2() {
|
||||
void EthernetInterface::loop2()
|
||||
{
|
||||
// get client from the server
|
||||
EthernetClient client = server->accept();
|
||||
|
||||
|
@@ -31,7 +31,7 @@
|
||||
#include "defines.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
@@ -50,23 +50,21 @@
|
||||
|
||||
class EthernetInterface {
|
||||
|
||||
public:
|
||||
static void setup();
|
||||
static void loop();
|
||||
public:
|
||||
|
||||
static void setup();
|
||||
static void loop();
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
void loop2();
|
||||
EthernetServer * server;
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * outboundRing;
|
||||
|
||||
private:
|
||||
static EthernetInterface * singleton;
|
||||
bool connected;
|
||||
EthernetInterface();
|
||||
~EthernetInterface();
|
||||
void loop2();
|
||||
bool checkLink();
|
||||
|
||||
EthernetServer *server = nullptr;
|
||||
EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time
|
||||
// This depends on the chipset used on the Shield
|
||||
uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * outboundRing = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "a26d988"
|
||||
#define GITHUB_SHA "TM-PORTX-20220607-1"
|
||||
|
@@ -69,6 +69,10 @@ protected:
|
||||
|
||||
I2CRB requestBlock;
|
||||
FSH *_deviceName;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// workaround: Has somehow no min function for all types
|
||||
static inline T min(T a, int b) { return a < b ? a : b; };
|
||||
#endif
|
||||
};
|
||||
|
||||
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
||||
@@ -246,4 +250,4 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
||||
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
346
IO_VCNL4040.h
346
IO_VCNL4040.h
@@ -1,346 +0,0 @@
|
||||
/*
|
||||
* © 2023, Fred Decker. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The VCNL4040 is a combination IR proximity sensor (PS) and ambient light sensor
|
||||
* (ALS) with a distance sensing range limit of 20cm (about 7.5 inches) and an
|
||||
* ambient light sensing range of 0.0125 to 6553 lux. For object (train) detection
|
||||
* purposes, we are only supporting the proximity features of the sensor.
|
||||
*
|
||||
* Can set:
|
||||
*
|
||||
* led duty cycle: 40, 80, 160, 320ms (???)
|
||||
* led drive current: 200 (Affects led brightness, therefore distance and sensitivity)
|
||||
* persistence: (higher values reduce false triggers)
|
||||
*
|
||||
* The basic PS function settings, such as duty ratio, integration time,interrupt,
|
||||
* and PS enable / disable, and persistence, are handled by the register: PS_CONF1.
|
||||
* Duty ratio controls the PS response time. Integration time represents the duration
|
||||
* of the energy being received. The interrupt is triggered when the PS detection
|
||||
* levels over the high threshold level setting (register: PS_THDH) or lower than
|
||||
* low threshold (register: PS_THDL). If the interrupt function is enabled, the host
|
||||
* can react to the interrupt pin, instead of polling the PS data registers. The INT
|
||||
* flag (register:INT_Flag) indicates the type of interrupt that has been triggered,
|
||||
* depending on the interrupt settings in the configuration registers. PS persistence
|
||||
* (PS_PERS) sets up the PS INT trigger conditions, defining the amount of consecutive
|
||||
* hits required before an interrupt event occurs. The intelligent cancellation level
|
||||
* can be set on register: PS_CANC to reduce the cross talk phenomenon.
|
||||
* VCNL4040 also supports an easy to use proximity detection logic mode, that triggers
|
||||
* when the PS high threshold is exceeded and automatically resets the interrupt pin
|
||||
* when the proximity reading falls beneath the PS low threshold. This functionality
|
||||
* can be set in the register: PS_MS. A smart persistence is provided to be able to
|
||||
* prevent false PS interrupt trigger events. Descriptions of each of these parameters
|
||||
* are shown in table 1.
|
||||
*
|
||||
* REGISTER: PS_CONF1 COMMAND CODE: 0x03_L (0x03 DATA BYTE LOW)
|
||||
* Command Bit Description
|
||||
* PS_Duty 7 : 6 (0 : 0) = 1/40, (0 : 1) = 1/80, (1 : 0) = 1/160, (1 : 1) = 1/320
|
||||
* PS IRED on / off duty ratio setting
|
||||
* PS_PERS 5 : 4 (0 : 0) = 1, (0 : 1) = 2, (1 : 0) = 3, (1 : 1) = 4
|
||||
* PS interrupt persistence setting
|
||||
* PS_ IT 3 : 1 (0 : 0 : 0) = 1T, (0 : 0 : 1) = 1.5T, (0 : 1 : 0) = 2T, (0 : 1 : 1) = 2.5T
|
||||
* (1 : 0 : 0) = 3T, (1 : 0 : 1) = 3.5T, (1 : 1 : 0) = 4T, (1 : 1 : 1) = 8T
|
||||
* PS integration time setting
|
||||
* PS_SD 0 0 = PS power on, 1 = PS shut down, default = 1
|
||||
*
|
||||
* REGISTER: PS_CONF2 COMMAND CODE: 0x03_H (0x03 DATA BYTE HIGH)
|
||||
* PS_HD 3 0 = PS output is 12 bits; 1 = PS output is 16 bits
|
||||
* Reserved 2 Default = 0
|
||||
* PS_INT 1 : 0 (0 : 0) = interrupt disable, (0 : 1) = trigger when close,
|
||||
* (1 : 0)= trigger when away, (1 : 1)= trigger when close or away
|
||||
*
|
||||
* NOTE: fnd stopped here
|
||||
* the reflection of the pulse. The time between the pulse and the receipt of reflections
|
||||
* is measured and used to determine the distance to the reflecting object.
|
||||
*
|
||||
* For economy of memory and processing time, this driver includes only part of the code
|
||||
* that ST provide in their API. Also, the API code isn't very clear and it is not easy
|
||||
* to identify what operations are useful and what are not.
|
||||
* The operation shown here doesn't include any calibration, so is probably not as accurate
|
||||
* as using the full driver, but it's probably accurate enough for the purpose.
|
||||
*
|
||||
* The device driver allocates up to 3 vpins to the device. A digital read on the first pin
|
||||
* will return a value that indicates whether the object is within the threshold range (1)
|
||||
* or not (0). An analogue read on the first pin returns the last measured distance (in mm),
|
||||
* the second pin returns the signal strength, and the third pin returns detected
|
||||
* ambient light level. By default the device takes around 60ms to complete a ranging
|
||||
* operation, so we do a 100ms cycle (10 samples per second).
|
||||
*
|
||||
* The VL53L0X is initially set to respond to I2C address 0x29. If you only have one module,
|
||||
* you can use this address. However, the address can be modified by software. If
|
||||
* you select another address, that address will be written to the device and used until the device is reset.
|
||||
*
|
||||
* If you have more than one module, then you will need to specify a digital VPIN (Arduino
|
||||
* digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now,
|
||||
* when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once
|
||||
* all VL53L0X modules are turned off, the driver works through each module in turn by
|
||||
* setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address.
|
||||
* In this way, many VL53L0X modules can be connected to the one I2C bus, each one
|
||||
* using a distinct I2C address.
|
||||
*
|
||||
* WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise,
|
||||
* and the device may even reset when handled. If you're not using XSHUT, then it's
|
||||
* best to tie it to +5V.
|
||||
*
|
||||
* The driver is configured as follows:
|
||||
*
|
||||
* Single VL53L0X module:
|
||||
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold);
|
||||
* Where firstVpin is the first vpin reserved for reading the device,
|
||||
* nPins is 1, 2 or 3,
|
||||
* i2cAddress is the address of the device (normally 0x29),
|
||||
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
|
||||
* and highThreshold is the distance at which the digital vpin state is set to 0 (in mm).
|
||||
*
|
||||
* Multiple VL53L0X modules:
|
||||
* VL53L0X::create(firstVpin, nPins, i2cAddress, lowThreshold, highThreshold, xshutPin);
|
||||
* ...
|
||||
* Where firstVpin is the first vpin reserved for reading the device,
|
||||
* nPins is 1, 2 or 3,
|
||||
* i2cAddress is the address of the device (any valid address except 0x29),
|
||||
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
|
||||
* highThreshold is the distance at which the digital vpin state is set to 0 (in mm),
|
||||
* and xshutPin is the VPIN number corresponding to a digital output that is connected to the
|
||||
* XSHUT terminal on the module.
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* VL53L0X::create(4000, 3, 0x29, 200, 250);
|
||||
* Sensor::create(4000, 4000, 0); // Create a sensor
|
||||
*
|
||||
* When an object comes within 200mm of the sensor, a message
|
||||
* <Q 4000>
|
||||
* will be sent over the serial USB, and when the object moves more than 250mm from the sensor,
|
||||
* a message
|
||||
* <q 4000>
|
||||
* will be sent.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_VCNL4040_h
|
||||
#define IO_VCNL4040_h
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
class VCNL4040 : public IODevice {
|
||||
private:
|
||||
uint8_t _i2cAddress;
|
||||
uint16_t _ambient;
|
||||
uint16_t _distance;
|
||||
uint16_t _signal;
|
||||
uint16_t _onThreshold;
|
||||
uint16_t _offThreshold;
|
||||
VPIN _xshutPin;
|
||||
bool _value;
|
||||
uint8_t _nextState = 0;
|
||||
I2CRB _rb;
|
||||
uint8_t _inBuffer[12];
|
||||
uint8_t _outBuffer[2];
|
||||
// State machine states.
|
||||
enum : uint8_t {
|
||||
STATE_INIT = 0,
|
||||
STATE_CONFIGUREADDRESS = 1,
|
||||
STATE_SKIP = 2,
|
||||
STATE_CONFIGUREDEVICE = 3,
|
||||
STATE_INITIATESCAN = 4,
|
||||
STATE_CHECKSTATUS = 5,
|
||||
STATE_GETRESULTS = 6,
|
||||
STATE_DECODERESULTS = 7,
|
||||
};
|
||||
|
||||
// Register addresses
|
||||
enum : uint8_t {
|
||||
VL53L0X_REG_SYSRANGE_START=0x00,
|
||||
VL53L0X_REG_RESULT_INTERRUPT_STATUS=0x13,
|
||||
VL53L0X_REG_RESULT_RANGE_STATUS=0x14,
|
||||
VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV=0x89,
|
||||
VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS=0x8A,
|
||||
};
|
||||
const uint8_t VL53L0X_I2C_DEFAULT_ADDRESS=0x29;
|
||||
|
||||
public:
|
||||
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 3);
|
||||
_i2cAddress = i2cAddress;
|
||||
_onThreshold = onThreshold;
|
||||
_offThreshold = offThreshold;
|
||||
_xshutPin = xshutPin;
|
||||
_value = 0;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _begin() override {
|
||||
if (_xshutPin == VPIN_NONE) {
|
||||
// Check if device is already responding on the nominated address.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
// Yes, it's already on this address, so skip the address initialisation.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
} else {
|
||||
_nextState = STATE_INIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
uint8_t status;
|
||||
switch (_nextState) {
|
||||
case STATE_INIT:
|
||||
// On first entry to loop, reset this module by pulling XSHUT low. All modules
|
||||
// will be reset in turn.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
|
||||
_nextState = STATE_CONFIGUREADDRESS;
|
||||
break;
|
||||
case STATE_CONFIGUREADDRESS:
|
||||
// On second entry, set XSHUT pin high to allow the module to restart.
|
||||
// On the module, there is a diode in series with the XSHUT pin to
|
||||
// protect the low-voltage pin against +5V.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1);
|
||||
// Allow the module time to restart
|
||||
delay(10);
|
||||
// Then write the desired I2C address to the device, while this is the only
|
||||
// module responding to the default address.
|
||||
I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress);
|
||||
_nextState = STATE_SKIP;
|
||||
break;
|
||||
case STATE_SKIP:
|
||||
// Do nothing on the third entry.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
break;
|
||||
case STATE_CONFIGUREDEVICE:
|
||||
// On next entry, check if device address has been set.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
// Set 2.8V mode
|
||||
write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
|
||||
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
|
||||
} else {
|
||||
DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
case STATE_INITIATESCAN:
|
||||
// Not scanning, so initiate a scan
|
||||
_outBuffer[0] = VL53L0X_REG_SYSRANGE_START;
|
||||
_outBuffer[1] = 0x01;
|
||||
I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb);
|
||||
_nextState = STATE_CHECKSTATUS;
|
||||
break;
|
||||
case STATE_CHECKSTATUS:
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // try next time
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_value = false;
|
||||
} else
|
||||
_nextState = 2;
|
||||
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
|
||||
_nextState = STATE_GETRESULTS;
|
||||
break;
|
||||
case STATE_GETRESULTS:
|
||||
// Ranging completed. Request results
|
||||
_outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
|
||||
_nextState = 3;
|
||||
delayUntil(currentMicros + 5000); // Allow 5ms to get data
|
||||
_nextState = STATE_DECODERESULTS;
|
||||
break;
|
||||
case STATE_DECODERESULTS:
|
||||
// If I2C write still busy, return.
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // try again next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
if (!(_inBuffer[0] & 1)) return; // device still busy
|
||||
uint8_t deviceRangeStatus = ((_inBuffer[0] & 0x78) >> 3);
|
||||
if (deviceRangeStatus == 0x0b) {
|
||||
// Range status OK, so use data
|
||||
_ambient = makeuint16(_inBuffer[7], _inBuffer[6]);
|
||||
_signal = makeuint16(_inBuffer[9], _inBuffer[8]);
|
||||
_distance = makeuint16(_inBuffer[11], _inBuffer[10]);
|
||||
if (_distance <= _onThreshold)
|
||||
_value = true;
|
||||
else if (_distance > _offThreshold)
|
||||
_value = false;
|
||||
}
|
||||
}
|
||||
// Completed. Restart scan on next loop entry.
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level.
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
switch (pin) {
|
||||
case 0:
|
||||
return _distance;
|
||||
case 1:
|
||||
return _signal;
|
||||
case 2:
|
||||
return _ambient;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// For digital read, return zero for all but first pin.
|
||||
int _read(VPIN vpin) override {
|
||||
if (vpin == _firstVpin)
|
||||
return _value;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
||||
_i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
inline uint16_t makeuint16(byte lsb, byte msb) {
|
||||
return (((uint16_t)msb) << 8) | lsb;
|
||||
}
|
||||
uint8_t write_reg(uint8_t reg, uint8_t data) {
|
||||
// write byte to register
|
||||
uint8_t outBuffer[2];
|
||||
outBuffer[0] = reg;
|
||||
outBuffer[1] = data;
|
||||
return I2CManager.write(_i2cAddress, outBuffer, 2);
|
||||
}
|
||||
uint8_t read_reg(uint8_t reg) {
|
||||
// read byte from register and return value
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1);
|
||||
return _inBuffer[0];
|
||||
}
|
||||
};
|
||||
|
||||
#endif // IO_VL53L0X_h
|
2
LCN.cpp
2
LCN.cpp
@@ -43,7 +43,7 @@ void LCN::loop() {
|
||||
|
||||
while (stream->available()) {
|
||||
int ch = stream->read();
|
||||
if (ch >= '0' && ch <= '9') { // accumulate id value
|
||||
if (ch >= 0 && ch <= '9') { // accumulate id value
|
||||
id = 10 * id + ch - '0';
|
||||
}
|
||||
else if (ch == 't' || ch == 'T') { // Turnout opcodes
|
||||
|
216
MotorDriver.cpp
216
MotorDriver.cpp
@@ -25,24 +25,39 @@
|
||||
#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))
|
||||
|
||||
bool MotorDriver::usePWM=false;
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
|
||||
MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
volatile byte fakePORTA;
|
||||
volatile byte fakePORTB;
|
||||
volatile byte fakePORTC;
|
||||
|
||||
MotorDriver::MotorDriver(VPIN 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);
|
||||
IODevice::write(powerPin,LOW);// set to OUTPUT and off
|
||||
|
||||
signalPin=signal_pin;
|
||||
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
||||
pinMode(signalPin, OUTPUT);
|
||||
|
||||
|
||||
fastSignalPin.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &fakePORTC;
|
||||
}
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
dualSignal=true;
|
||||
@@ -56,8 +71,9 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
invertBrake=brake_pin < 0;
|
||||
brakePin=invertBrake ? 0-brake_pin : brake_pin;
|
||||
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
||||
pinMode(brakePin, OUTPUT);
|
||||
setBrake(false);
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
@@ -82,6 +98,12 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
||||
else
|
||||
DIAG(F("MotorDriver currentPin=A%d, senseOffset=%d, rawCurrentTripValue(relative to offset)=%d"),
|
||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
@@ -89,15 +111,15 @@ bool MotorDriver::isPWMCapable() {
|
||||
}
|
||||
|
||||
|
||||
void MotorDriver::setPower(bool on) {
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::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);
|
||||
IODevice::write(powerPin,HIGH);
|
||||
if (resetsCounterP != NULL)
|
||||
*resetsCounterP = 0;
|
||||
}
|
||||
else setLOW(fastPowerPin);
|
||||
else IODevice::write(powerPin,LOW);
|
||||
powerMode=mode;
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
@@ -114,26 +136,6 @@ void MotorDriver::setBrake(bool on) {
|
||||
else setLOW(fastBrakePin);
|
||||
}
|
||||
|
||||
void MotorDriver::setSignal( bool high) {
|
||||
if (usePWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
volatile unsigned int overflow_count=0;
|
||||
#endif
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_PIN;
|
||||
}
|
||||
@@ -148,28 +150,72 @@ bool MotorDriver::canMeasureCurrent() {
|
||||
int MotorDriver::getCurrentRaw() {
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
bool irq = disableInterrupts();
|
||||
// This function should NOT be called in an interruot so we
|
||||
// dont need to fart about saving and restoring CPU specific
|
||||
// interrupt registers.
|
||||
noInterrupts();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
enableInterrupts(irq);
|
||||
#else // Uno, Mega and all the TEENSY3* but not TEENSY4*
|
||||
unsigned char sreg_backup;
|
||||
sreg_backup = SREG; /* save interrupt enable/disable state */
|
||||
cli();
|
||||
current = analogRead(currentPin)-senseOffset;
|
||||
#if defined(ARDUINO_TEENSY32) || defined(ARDUINO_TEENSY35)|| defined(ARDUINO_TEENSY36)
|
||||
overflow_count = 0;
|
||||
#endif
|
||||
if (sreg_backup & 128) sei(); /* restore interrupt state */
|
||||
#endif // outer #
|
||||
interrupts();
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && isHIGH(fastPowerPin))
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
return (current == 0 ? -1 : -current);
|
||||
return current;
|
||||
// IMPORTANT: This function can be called in Interrupt() time within the 56uS timer
|
||||
|
||||
}
|
||||
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
else if (tSpeed >= 127) brake = 0;
|
||||
else brake = 2 * (128-tSpeed);
|
||||
DIAG(F("setDCSignal: speedcode=%d BrakePin=%d brake=%d dir=%d"),speedcode, brakePin, brake, tDir);
|
||||
analogWrite(brakePin,brake);
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
// we need to take care of that and we have to turn off interrupts during
|
||||
// that time as otherwise setDCCSignal() which is called from interrupt
|
||||
// contect can undo whatever we do here.
|
||||
if (fastSignalPin.shadowinout != NULL) {
|
||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTA"));
|
||||
noInterrupts();
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTB"));
|
||||
noInterrupts();
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
|
||||
DIAG(F("setDCSignal: HAVEPORTC"));
|
||||
noInterrupts();
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
interrupts();
|
||||
}
|
||||
} else {
|
||||
setSignal(tDir);
|
||||
}
|
||||
}
|
||||
|
||||
int MotorDriver::getCurrentRawInInterrupt() {
|
||||
|
||||
// IMPORTANT: This function must be called in Interrupt() time within the 56uS timer
|
||||
// The default analogRead takes ~100uS which is catastrphic
|
||||
// so DCCTimer has set the sample time to be much faster.
|
||||
}
|
||||
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
return analogRead(currentPin)-senseOffset;
|
||||
}
|
||||
|
||||
unsigned int MotorDriver::raw2mA( int raw) {
|
||||
return (unsigned int)(raw * senseFactor);
|
||||
@@ -190,3 +236,65 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
result.maskLOW = ~result.maskHIGH;
|
||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||
}
|
||||
|
||||
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
|
||||
// Trackname for diag messages later
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent <= tripValue) {
|
||||
setPower(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE - TOGGLED POWER on %d"), trackno);
|
||||
} else {
|
||||
DIAG(F("TRACK %d FAULT PIN ACTIVE - OVERLOAD"), trackno);
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
} else {
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %d POWER OVERLOAD current=%d max=%d offtime=%d"), trackno, mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
setPower(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %d POWER RESET delay=%d"), trackno, sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
}
|
||||
|
120
MotorDriver.h
120
MotorDriver.h
@@ -2,6 +2,7 @@
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
@@ -22,6 +23,38 @@
|
||||
#ifndef MotorDriver_h
|
||||
#define MotorDriver_h
|
||||
#include "FSH.h"
|
||||
#include "IODevice.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||
|
||||
#define TOKENPASTE(x, y) x ## y
|
||||
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define HAVE_PORTA(X) X
|
||||
#define HAVE_PORTB(X) X
|
||||
#define HAVE_PORTC(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define HAVE_PORTB(X) X
|
||||
#endif
|
||||
|
||||
// if macros not defined as pass-through we define
|
||||
// them here as someting that is valid as a
|
||||
// statement and evaluates to false.
|
||||
#ifndef HAVE_PORTA
|
||||
#define HAVE_PORTA(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTB
|
||||
#define HAVE_PORTB(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
@@ -29,63 +62,110 @@
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#endif
|
||||
|
||||
#if defined(__IMXRT1062__)
|
||||
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
struct FASTPIN {
|
||||
volatile uint32_t *inout;
|
||||
uint32_t maskHIGH;
|
||||
uint32_t maskLOW;
|
||||
uint32_t maskLOW;
|
||||
volatile uint32_t *shadowinout;
|
||||
};
|
||||
#else
|
||||
struct FASTPIN {
|
||||
volatile uint8_t *inout;
|
||||
uint8_t maskHIGH;
|
||||
uint8_t maskLOW;
|
||||
uint8_t maskLOW;
|
||||
volatile uint8_t *shadowinout;
|
||||
};
|
||||
#endif
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
|
||||
MotorDriver(VPIN power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
virtual void setPower( bool on);
|
||||
virtual void setSignal( bool high);
|
||||
virtual void setPower( POWERMODE mode);
|
||||
virtual POWERMODE getPower() { return powerMode;}
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
};
|
||||
inline void enableSignal(bool on) {
|
||||
if (on)
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
};
|
||||
virtual void setBrake( bool on);
|
||||
virtual void setDCSignal(byte speedByte);
|
||||
virtual int getCurrentRaw();
|
||||
virtual int getCurrentRawInInterrupt();
|
||||
virtual unsigned int raw2mA( int raw);
|
||||
virtual int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||
}
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
static bool usePWM;
|
||||
bool trackPWM;
|
||||
static bool usePWM; // TODO: Remove
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
inline void setResetCounterPointer(volatile byte *bp) { // load resetPacketCounter pointer
|
||||
resetsCounterP = bp;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
private:
|
||||
volatile byte *resetsCounterP = NULL; // points to the resetPacketCounter if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
}
|
||||
byte powerPin, signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastPowerPin,fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
VPIN powerPin;
|
||||
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
bool dualSignal; // true to use signalPin2
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
float senseFactor;
|
||||
int senseOffset;
|
||||
unsigned int tripMilliamps;
|
||||
int rawCurrentTripValue;
|
||||
#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41)
|
||||
static bool disableInterrupts() {
|
||||
uint32_t primask;
|
||||
__asm__ volatile("mrs %0, primask\n" : "=r" (primask)::);
|
||||
__disable_irq();
|
||||
return (primask == 0) ? true : false;
|
||||
}
|
||||
static void enableInterrupts(bool doit) {
|
||||
if (doit) __enable_irq();
|
||||
}
|
||||
#endif
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
static const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
static const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -47,14 +47,8 @@
|
||||
//
|
||||
// Arduino standard Motor Shield
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(3, 12, UNUSED_PIN, UNUSED_PIN, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, UNUSED_PIN, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// DCC-EX TI DRV8874 based motor shield
|
||||
// This motor shield has reverse sense fault pins thus the -A4 and -A5 pin values.
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 4.86, 5000, A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 4.86, 5000, A5)
|
||||
new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 2.99, 2000, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 2000, UNUSED_PIN)
|
||||
|
||||
// Pololu Motor Shield
|
||||
#define POLOLU_MOTOR_SHIELD F("POLOLU_MOTOR_SHIELD"), \
|
||||
|
@@ -3,74 +3,72 @@ Throttle Assist updates for versiuon 4.?
|
||||
Chris Harlow April 2022
|
||||
|
||||
There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations.
|
||||
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
|
||||
These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose.
|
||||
|
||||
# Turnouts
|
||||
Turnouts:
|
||||
|
||||
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than the cryptic "T123", and having a list helps the throttle UI build a suitable set of buttons.
|
||||
The conventional turnout definition commands and the ```<H>``` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons.
|
||||
|
||||
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
|
||||
```<JT>``` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state.
|
||||
e.g. response ```<jT 1 17 22 19>```
|
||||
|
||||
```<JT 17>``` requests info on turnout 17\
|
||||
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```\
|
||||
(T=thrown, C=closed)\
|
||||
or ```<jT 17 C "">``` indicating turnout description not given.\
|
||||
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
|
||||
|
||||
Note: It is still the throttles responsibility to monitor the status broadcasts.\
|
||||
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)\
|
||||
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
|
||||
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
|
||||
```<JT 17>`` requests info on turnout 17.
|
||||
e.g. response ```<jT 17 T "Coal yard exit">``` or ```<jT 17 C "Coal yard exit">```
|
||||
(T=thrown, C=closed)
|
||||
or ```<jT 17 C "">``` indicating turnout description not given.
|
||||
or ```<jT 17 X>``` indicating turnout unknown (or possibly hidden.)
|
||||
|
||||
# Automations/Routes
|
||||
Note: It is still the throttles responsibility to monitor the status broadcasts.
|
||||
(TBD I'm thinking that the existing broadcast is messy and needs cleaning up)
|
||||
However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started.
|
||||
Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands.
|
||||
|
||||
A throttle need to know which EXRAIL Automations and Routes it can show the user.
|
||||
|
||||
```<JA>``` Returns a list of Automations/Routes\
|
||||
e.g. ```<jA 13 16 23>```\
|
||||
Indicates route/automation ids.\
|
||||
Information on each route needs to be obtained by\
|
||||
```<JA 13>```\
|
||||
returns e.g. ```<jA 13 R "description">``` for a route\
|
||||
or ```<jA 13 A "description">``` for an automation.\
|
||||
or ```<jA 13 X>``` for id not found
|
||||
|
||||
## What's the difference?
|
||||
|
||||
A *Route* is just a call to an **EXRAIL ROUTE**, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
|
||||
|
||||
Thus, a route can be triggered by sending in for example ```</START 13>```.
|
||||
|
||||
An *Automation* is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
|
||||
Automations/Routes
|
||||
|
||||
A throttle need to know which EXRAIL Automations and Routes it can show the user.
|
||||
|
||||
```<JA>``` Returns a list of Automations/Routes
|
||||
e.g. ```<jA 13 16 23>```
|
||||
Indicates route/automation ids.
|
||||
Information on each route needs to be obtained by
|
||||
```<JA 13>```
|
||||
returns e.g. ```<jA 13 R "description">``` for a route
|
||||
or ```<jA 13 A "description">``` for an automation.
|
||||
or ```<jA 13 X>``` for id not found
|
||||
|
||||
Whats the difference:
|
||||
A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco.
|
||||
Thus a route can be triggered by sending in for example ```</START 13>```.
|
||||
|
||||
Thus an Automation expects a start command with a cab id\
|
||||
An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away.
|
||||
Thus an Automation expects a start command with a cab id
|
||||
e.g. ```</START 13 3>```
|
||||
|
||||
### Roster Information
|
||||
|
||||
The ```<JR>``` command requests a list of cab ids from the roster\
|
||||
e.g. responding ```<jR 3 200 6336>```\
|
||||
or <jR> for none.
|
||||
Roster Information:
|
||||
The ```<JR>``` command requests a list of cab ids from the roster.
|
||||
e.g. responding ```<jR 3 200 6336>```
|
||||
or <jR> for none.
|
||||
|
||||
Each Roster entry had a name and function map obtained by:\
|
||||
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
|
||||
Each Roster entry had a name and function map obtained by:
|
||||
```<JR 200>``` reply like ```<jR 200 "Thomas" "whistle/*bell/squeal/panic">
|
||||
|
||||
Refer to EXRAIL ROSTER command for function map format.
|
||||
Refer to EXRAIL ROSTER command for function map format.
|
||||
|
||||
### Obtaining throttle status
|
||||
|
||||
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast\
|
||||
```<l cabid slot speedbyte functionMap>```
|
||||
|
||||
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
|
||||
Obtaining throttle status.
|
||||
```<t cabid>``` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast.
|
||||
```<l cabid slot speedbyte functionMap>```
|
||||
Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled.
|
||||
|
||||
# COMMANDS TO AVOID
|
||||
|
||||
```<f cab func1 func2>``` Instead Use ```<F cab function 1/0>```\
|
||||
```<t slot cab speed dir>``` Just drop the slot number\
|
||||
```<T commands>``` other than ```<T id 0/1>```\
|
||||
```<s>```\
|
||||
COMMANDS TO AVOID
|
||||
|
||||
```<f cab func1 func2>``` Use ```<F cab function 1/0>```
|
||||
```<t slot cab speed dir>``` Just drop the slot number
|
||||
```<T commands>``` other than ```<T id 0/1>```
|
||||
```<s>```
|
||||
```<c>```
|
||||
|
||||
|
||||
|
139
Release_Notes/TrackManager.md
Normal file
139
Release_Notes/TrackManager.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# DCC++EX Track Manager
|
||||
|
||||
Chris Harlow 2022/03/23
|
||||
|
||||
**If you are only interested in a standard setup using just a DCC track and PROG track, then you DO NOT need to read the rest of this document.**
|
||||
|
||||
What follows is for advanced users interested in managing power districts and/or running DC locomotives through DCC++EX.
|
||||
|
||||
## What is the Track Manager
|
||||
Track Manger (TM from now on) is an integral part of DCC++EX software that is responsible for:
|
||||
- Managing track power state.
|
||||
- Monitoring track overloads and shorts.
|
||||
- Routing the DCC main or prog track waveforms to the correct Motor Driver and thus track.
|
||||
- Managing the JOIN feature.
|
||||
- Intercepting throttle commands to locos running on DC tracks.
|
||||
- Handling user or EXRAIL commands to switch track status.
|
||||
|
||||
In the default scenario of a single DCC track and a PROG track, the TM behaves as for the previous versions of DCC++EX so if thats what you want, you dont need to mess with it.
|
||||
|
||||
The TM is able to handle up to 8 separate track domains. Each domain requires a hardware driver to supply track voltage. A typical motor driver shield supplies two tracks, which is what we have used in the past as main and prog.
|
||||
|
||||
Unlike the previous version of DCC++EX, where the shield channel A was always the DCC main and channel B was always the DCC prog track, TM allows :
|
||||
- None, any or all the tracks can be DCC Main.
|
||||
- None or ONE track may be DCC prog at any given time.
|
||||
- Any track may be powered on or off independently of the others.
|
||||
- Any track may be disconnected from the DCC signal and used as a DC track with a given loco address. (See DC discussion later)
|
||||
|
||||
With such flexibility comes responsibility... the potential for making mistakes means taking extra care with your configuration!
|
||||
|
||||
**NOTE** TM does NOT use "zero stretching" to control your DC motor. Instead, it uses true Pulse Width Modulation (PWM) to efficiently run your loco using the same method a decoder uses to control a DCC loco's motor. DC locos can even run better on TM than they can on a normal analog throttle, especially at low speed, since it is always applying the full track voltage, albeit in pulses of varying duration.
|
||||
|
||||
## Using the Track Manager (DCC)
|
||||
TM names the tracks A to H. In a default setup, you will normally have tracks A and B where A will default to be the DCC main signal and B will be the DCC prog.
|
||||
|
||||
There is a new user command `<=>` which is used to control the TM but the `<0>` and `<1>` commands operate as before.
|
||||
|
||||
- `<=>` lists the current track settings.
|
||||
In a default setup this will normally return
|
||||
```
|
||||
<=A DCC>
|
||||
<=B PROG>
|
||||
```
|
||||
- `<=t DCC>` sets track t (A..H) to use the DCC main track. For example `<=C DCC>` sets track C. All tracks that are set to DCC will receive the same DCC signal waveform.
|
||||
- `<=t PROG>` Sets track t (A..H) to be the one and only PROG track. Any previous PROG track is turned off.
|
||||
- `<=t OFF>` turns off the track t. It will not power on with `<1>` because it will not know what signal to send.
|
||||
|
||||
In an all-DCC environment it is unlikely that you will need to do anything other than setting any additional tracks (C...H) as DCC in your `mySetup.h` file.
|
||||
|
||||
Bear in mind that a track may actually be only connected to DCC accessories such as signals and turnouts... your layout, your choice.
|
||||
|
||||
Note that when setting a track to PROG or OFF, its power is switched off automatically. (The PROG track manages power on an as-needed basis under normal circumstances.
|
||||
When setting a track to MAIN (or DC, DCX see later) the power is applied according to the most recent `<1>` or `<0>` command as being the most compatible with previous versions.
|
||||
|
||||
## using the Track Manager (DC)
|
||||
|
||||
TM allows any or all of your tracks to be individually selected as a DC track which responds to throttle commands on any given loco address. So for example if track A is set to DC address 55, then any throttle commands to loco 55 will be transmitted as DC onto track A and thus a DC loco can be driven along that track. almost exactly as if it was DCC.
|
||||
Your throttle (JMRI, EX-Webthrottle, Withrottle, Engine Driver etc etc) do not know or care that this is a DC loco so nothing needs to change.
|
||||
|
||||
For a simple Command Station setup to run just two DC tracks instead of DCC, you only need to assign DC addresses to tracks A and B. If you want DCC on track A and DC on track B, you just need to set track B to a suitable DC address.
|
||||
|
||||
The command to set a track to a DC address is as follows
|
||||
- `<=t DC a>` Sets track t (A..H) to use loco address a. e.g. <=A DC 3>
|
||||
|
||||
A simple 2 separate loop DC track, wired the traditional way in opposite directions, may be set like this to use loco addresses 1 and 2.
|
||||
```
|
||||
<=A DC 1>
|
||||
<=A DC 2>
|
||||
```
|
||||
|
||||
### Crossing between DC tracks
|
||||
|
||||
There are some slightly mind-bending issues to be addressed, especially if you want to be able to cross between two separate DC tracks or use your layout in DCC or DC mode. This is because the control of DC loco direction is relative to the TRACK and not the LOCO. (you turn a DC loco round on the track and it continues in the same geographical direction. You turn a DCC loco around and it continues to go forwards or backwards in the opposite geographical direction.)
|
||||
|
||||
Generally DC tracks are wired so that two mainline tracks are in opposite direction which makes operation easy BUT crossovers between tracks will cause shorts unless you have very complex switching arrangements.
|
||||
This is generally incompatible with DCC wiring which expects to be able to cross between tracks with impunity because they are all wired with the same polarity.
|
||||
|
||||
To get over this issue TM allows the polarity of a DC track to be swapped so that tracks wired for DCC may be switched to DC with a polarity chosen at run time according to your operations. So, for example, you may have two loops with a crossing between them. Normally you need them in opposite directions, but when you need to drive over the crossing, you need to switch one or other track so that they are at the same polarity.
|
||||
(This is a good case for using EXRAIL to help)
|
||||
|
||||
The command `<=t DCX a>` will set track t (A..H) to be DC but with reversed polarity compared with a track set to DC.
|
||||
|
||||
Its perfectly OK to cross between DC tracks by setting them to the same loco address and making sure you get the polarity right!
|
||||
|
||||
## Connecting Hardware
|
||||
Each track requires hardware to control it
|
||||
- Power on/off
|
||||
- Polarity (direction, signal etc)
|
||||
- Brake (shorts tracks together)
|
||||
- Current (analog reading)
|
||||
|
||||
The standard motor shields provide this for two separate tracks and are predictable and easy to use. However STACKING shields is not a viable way of adding more tracks because it prevents the software from gaining access to the individual track pins. Similarly, wiring all the signal pins together for example, will give you a shared DCC signal but it will eliminate any possibility of switching the track purpose at run time. So, you are going to have to understand enough to wire track drivers to various pins if you wish to extend beyond 2 tracks and take advantage of TM.
|
||||
|
||||
You will also need to consider the implications of differing electronic implementations that would cause unexpected issues when a loco moves between tracks. We know this works fine for a typical shield because we use `<1 JOIN>` quite happily but this may be different if you mix hardware types..... (NOT MY PROBLEM !)
|
||||
|
||||
The easiest way to consider the wiring is to treat each track individually (either as a separate driver or as half of a shield).
|
||||
|
||||
You will require,for each track, on the Arduino:
|
||||
- A GPIO pin (or a HAL vpin perhaps on an I2C extender, code TBA!!!) to switch power.
|
||||
- A GPIO pin to switch the signal direction
|
||||
- A GPIO pin with PWM capability to switch the Brake (you may omit this if you dont want any DC capability)
|
||||
- Optionally An Analog pin to read the current (unless your hardware cant do that, perhaps its just feeding a booster)
|
||||
- Optionally a GPIO fault pin if thats how your hardware works. (NOT recommended as you're going to run out of pins)
|
||||
|
||||
IF you have no more than 3 tracks and you can arrange for the signal pins to be one of 11,12,13 on a Mega, THEN there is a slight advantage internally and the waveform will be super-sharp.
|
||||
|
||||
**Hardware that has two signal pins still needs some code thought!!!!!!!!**
|
||||
|
||||
|
||||
## Configuring the Software
|
||||
|
||||
Configuring the software to provide more tracks is a simple extension of the existing method of customising the #define of MOTOR_SHIELD_TYPE in config.h
|
||||
Since there can be no standard setup of your wiring and hardware choices, it will be necessary to create your custom built MOTOR_SHIELD_TYPE in the manner described in MotorDrivers.h and simply continue to add more `new MotorDriver(` definitions to the list, providing all the pin numbers and electronic limits for each track. (or even shorten the list to 1)
|
||||
|
||||
## Using EXRAIL to control Track Manager
|
||||
EXRAIL has a single additional command that can be used to automate TM.
|
||||
|
||||
- `SET_TRACK(t,mode)`
|
||||
where t is the track letter A..H and mode is one of
|
||||
- `OFF` track is switched off
|
||||
- `DCC` track gets DCC signal
|
||||
- `PROG` track gets DCC prog signal
|
||||
- `DC` track is set to DC mode with the cab address of the currently executing EXRAIL sequence.
|
||||
- `DCX` as DC but with reversed polarity.
|
||||
|
||||
DC/DCX are designed so that you can be automating a DCC loco, drive it onto a separate track and switch to DC without having to know the cab address. (e.g AUTOMATION)
|
||||
If however you are just running a ROUTE you can always do something like this:
|
||||
```
|
||||
ROUTE(77,"Set track G to DC 123")
|
||||
SETLOCO(123)
|
||||
SET_TRACK(G,DC)
|
||||
DONE
|
||||
```
|
||||
|
||||
## Where and How for the Code.
|
||||
The TM code is primarily in TrackManager.cpp which is responsible for coordinating the track settings and commands.
|
||||
|
||||
Each individual track is handled by an instance of MotorDriver created from the MOTOPR_SHIELD_TYPE definition in config.h
|
||||
|
||||
Many functions formerly in the DCCWaveform code have been moved to TrackManager or MotorDriver, notably the power control and checking. This makes the code easier to follow.
|
@@ -1,346 +0,0 @@
|
||||
Version 4.1.1 Release Notes
|
||||
*************************
|
||||
|
||||
The DCC-EX Team is pleased to release CommandStation-EX v4.1.1 as a Production Release for the general public.
|
||||
This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes.
|
||||
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.1 rc13.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**New Command Station & EX-RAIL Features**
|
||||
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
|
||||
- Automatically detect and run a myFilter add-on (no need to call setFilter)
|
||||
|
||||
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
|
||||
- </RED signal_id> to turn a individual LED Signal On & Off
|
||||
- </AMBER signal_id> "
|
||||
- </GREEN signal_id> "
|
||||
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- < t cab> command to obtain current throttle setting
|
||||
|
||||
- Allow WRITE CV on PROG <W CV VALUE>
|
||||
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
|
||||
- Allow WRITE CV on PROG <W CV VALUE)
|
||||
- Change callback parameters are now optional on PROG
|
||||
|
||||
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
|
||||
|
||||
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
|
||||
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
|
||||
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
|
||||
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
|
||||
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
|
||||
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
|
||||
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
|
||||
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
|
||||
- VIRTUAL_TURNOUT definition
|
||||
|
||||
**EX-RAIL Updates**
|
||||
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
|
||||
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
|
||||
|
||||
** Other Enhancements**
|
||||
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
|
||||
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
|
||||
- Position servo can use spare servo pin as a GPIO pin.
|
||||
|
||||
**4.1.1 Bug Fixes**
|
||||
|
||||
- Preserve the turnout format
|
||||
- Parse multiple commands in one buffer string currectly
|
||||
- Fix </> command signal status in EX-RAIL
|
||||
- Read long loco addresses in EX-RAIL
|
||||
- FIX negative route IDs in WIthrottle
|
||||
|
||||
See the version.h file for notes about which of the 4.1.1 features were added/changed by point release.
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
|
||||
|
||||
**All New Major DCC++EX 4.0.0 features**
|
||||
|
||||
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
|
||||
- HAL Support for;
|
||||
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
- PCA9685 PWM (servo & signal) control modules.
|
||||
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
- MP3 sound playback via DFPlayer module.
|
||||
- HC-SR04 Ultrasonic range sensor module.
|
||||
- VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
|
||||
|
||||
**New Command Station Broadcast throttle logic**
|
||||
|
||||
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
|
||||
|
||||
**New ‘Discovered Servers’ on WiFi Throttles**
|
||||
|
||||
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
|
||||
|
||||
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
|
||||
|
||||
- Use to control your entire layout or as a separate accessory/animation controller
|
||||
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
|
||||
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
|
||||
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
|
||||
|
||||
**New EX-RAIL ‘Roster’ Feature**
|
||||
|
||||
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
|
||||
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
|
||||
- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps.
|
||||
|
||||
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
|
||||
|
||||
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
|
||||
|
||||
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
|
||||
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information).
|
||||
|
||||
**DCC++EX 4.0.0 additional product enhancements**
|
||||
|
||||
- Additional Motor Shields and Motor Board {boosters) supported
|
||||
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
|
||||
- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI
|
||||
- Automatic retry on failed ACK detection to give decoders another chance
|
||||
- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts
|
||||
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
|
||||
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
|
||||
- Turnout user names display in Engine Driver & WiThrottles
|
||||
- Output class now allows ID > 255.
|
||||
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
|
||||
- Increased use of display for showing loco decoder programming information.
|
||||
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
|
||||
- Can define border between long and short addresses
|
||||
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
|
||||
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
|
||||
**4.0.0 Bug Fixes**
|
||||
|
||||
- Compiles on Nano Every
|
||||
- Diagnostic display of ack pulses >32ms
|
||||
- Current read from wrong ADC during interrupt
|
||||
- AT(+) Command Pass Through
|
||||
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
|
||||
- One-off error in CIPSEND drop
|
||||
- Common Fault Pin Error
|
||||
- Uno Memory Utilization optimized
|
||||
|
||||
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||
|
||||
- Send function commands just once instead of repeating them 4 times
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||
|
||||
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||
|
||||
- Fix for CAB Functions greater than 127
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||
|
||||
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||
|
||||
- Reorganized files for support of 128 speed steps
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||
|
||||
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||
|
||||
- Rearranges serial newlines for the benefit of JMRI
|
||||
- Major update for efficiencies in displays (LCD, OLED)
|
||||
- Add I2C Support functions
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||
|
||||
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||
|
||||
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- Improved overload messages with raw values (relative to offset)
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||
|
||||
- Prevent compiler warning about deprecated B constants
|
||||
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- `<D RESET>` command to reboot Arduino
|
||||
- Automatic sensor offset detect
|
||||
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||
|
||||
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||
- Removed ethernet mac config and made it automatic
|
||||
- Show wifi ip and port on lcd
|
||||
- Auto load config.example.h with warning
|
||||
- Dropped example .ino files
|
||||
- Corrected .ino comments
|
||||
- Add Pololu fault pin handling
|
||||
- Waveform speed/simplicity improvements
|
||||
- Improved pin speed in waveform
|
||||
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- Analog read speed improvements
|
||||
- Drop need for DIO2 library
|
||||
- Improved current check code
|
||||
- Linear command
|
||||
- Removed need for ArduinoTimers files
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for automation in future version
|
||||
- Fixed Turnout list
|
||||
- Allow command keywords in mixed case
|
||||
- Dropped unused memstream
|
||||
- PWM pin accuracy if requirements met
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||
|
||||
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
- WiFi Startup Fixes
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
|
||||
- Command to write loco address and clear consist
|
||||
- Command will allow for consist address
|
||||
- Startup commands implemented
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
|
||||
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- Simultaneously update JMRI to handle new current meter
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
|
||||
- Add back fix for jitter
|
||||
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||
|
||||
**CommandStation-EX V3.0.0:**
|
||||
|
||||
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - `<#>` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**EX-CommandStation Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - Oregon, USA (MSteveTodd)
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**Engine Driver and JMRI Interface**
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**EX-Installer Software**
|
||||
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Colin Grabham - Central NSW, Australia (Kebbin)
|
||||
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
|
||||
- Peter Akers - Brisbane, QLD, Australia (flash62au)
|
||||
|
||||
**EX-WebThrottle**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
**Hardware / Electronics**
|
||||
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Paul Antoine, Western Australia
|
||||
- Neil McKechnie - Worcestershire, UK
|
||||
- Fred Decker - Holly Springs NC, USA
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
- Keith Ledbetter
|
||||
- Brad Van der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sam Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.1-Prod/CommandStation-EX.tar.gz)
|
@@ -1,349 +0,0 @@
|
||||
Version 4.1.2 Release Notes
|
||||
*************************
|
||||
|
||||
The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public.
|
||||
This release is a Bugfix release which fixes support for Ethernet Shields based on the W5100 chip that broke with the release of v4.1.1. This chip does not report HW and link status the way the W5200 and W5500 do, so the check routine needed to be changed.
|
||||
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**New Command Station & EX-RAIL Features**
|
||||
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
|
||||
- Automatically detect and run a myFilter add-on (no need to call setFilter)
|
||||
|
||||
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
|
||||
- </RED signal_id> to turn a individual LED Signal On & Off
|
||||
- </AMBER signal_id> "
|
||||
- </GREEN signal_id> "
|
||||
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- < t cab> command to obtain current throttle setting
|
||||
|
||||
- Allow WRITE CV on PROG <W CV VALUE>
|
||||
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
|
||||
- Allow WRITE CV on PROG <W CV VALUE)
|
||||
- Change callback parameters are now optional on PROG
|
||||
|
||||
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
|
||||
|
||||
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
|
||||
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
|
||||
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
|
||||
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
|
||||
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
|
||||
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
|
||||
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
|
||||
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
|
||||
- VIRTUAL_TURNOUT definition
|
||||
|
||||
**EX-RAIL Updates**
|
||||
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
|
||||
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
|
||||
|
||||
** Other Enhancements**
|
||||
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
|
||||
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
|
||||
- Position servo can use spare servo pin as a GPIO pin.
|
||||
|
||||
**4.1.2 Bug Fixes**
|
||||
- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips.
|
||||
|
||||
**4.1.1 Bug Fixes**
|
||||
|
||||
- Preserve the turnout format
|
||||
- Parse multiple commands in one buffer string currectly
|
||||
- Fix </> command signal status in EX-RAIL
|
||||
- Read long loco addresses in EX-RAIL
|
||||
- FIX negative route IDs in WIthrottle
|
||||
|
||||
See the version.h file for notes about which of the 4.1.2 features were added/changed by point release.
|
||||
|
||||
**Known Issues**
|
||||
|
||||
- **Wi-Fi** - Requires sending `<AT>` commands from a serial monitor if you want to switch between AP mode and STA station mode after initial setup
|
||||
- **Pololu Motor Shield** - is supported with this release, but the user may have to adjust timings to enable programming mode due to limitations in its current sensing circuitry
|
||||
|
||||
**All New Major DCC++EX 4.0.0 features**
|
||||
|
||||
- **New HAL Hardware Abstraction Layer API** that automatically detects and greatly simplifies interfacing to many predefined accessory boards for servos, signals & sensors and added I/O (digital and analog inputs and outputs, servos etc).
|
||||
- HAL Support for;
|
||||
- MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||
- PCA9685 PWM (servo & signal) control modules.
|
||||
- Analogue inputs on Arduino pins and on ADS111x I2C modules.
|
||||
- MP3 sound playback via DFPlayer module.
|
||||
- HC-SR04 Ultrasonic range sensor module.
|
||||
- VL53L0X Laser range sensor module (Time-Of-Flight).
|
||||
- A new `<D HAL SHOW>` command to list the HAL devices attached to the command station
|
||||
|
||||
**New Command Station Broadcast throttle logic**
|
||||
|
||||
- Synchronizes multiple WiThrottles and PC based JMRI Throttles for direction, speed and F-key updates
|
||||
|
||||
**New ‘Discovered Servers’ on WiFi Throttles**
|
||||
|
||||
- Our New multicast Dynamic Network Server (mDNS) enhancement allows us to display the available WiFi server connections to a DCC++EX Command Station. Selecting it allows your WiThrottle App to connect to and load Server Rosters and function keys to your throttle from the new DCC++EX Command Station Server Roster.
|
||||
|
||||
**New DCC++EX 4.0.0 with EX-RAIL Extended Railroad Automation Instruction Language**
|
||||
|
||||
- Use to control your entire layout or as a separate accessory/animation controller
|
||||
- Awesome, cleverly powerful yet simple user friendly scripting language for user built Automation & Routing scripts.
|
||||
- You can control Engines, Sensors, Turnouts, Signals, Outputs and Accessories that are entered into your new myAutomation.h file, then uploaded into the DCC++EX Command Station.
|
||||
- EX-RAIL scripts are automatically displayed as Automation {Handoff} and Route {Set} buttons on supported WiFi Throttle Apps.
|
||||
|
||||
**New EX-RAIL ‘Roster’ Feature**
|
||||
|
||||
- List and store user defined engine roster & function keys inside the command station, and automatically load them in WiFi Throttle Apps.
|
||||
- When choosing “DCC++EX” from discovered servers an Engine Driver or WiThrottle is directly connected to the Command Station.
|
||||
- The EX-RAIL ’ROSTER’ command allows all the engine numbers, names and function keys you’ve listed in your myAutomation.h file to automatically upload the Command Station's ‘Server Roster’ into your Engine Driver and WiThrottle Apps.
|
||||
|
||||
**New JMRI 4.99.2 and above specific DCC++EX 4.0 features**
|
||||
|
||||
- Enhanced JMRI DCC++ Configure Base Station pane for building and maintaining Sensor, Turnout and Output devices, or these can automatically be populated from the DCC++EX Command Station's mySetup.h file into JMRI.
|
||||
|
||||
- JMRI now supports multiple serial connected DCC++EX Command Stations, to display and track separate "Send DCC++ Command" and "DCC++ Traffic" Monitors for each Command Station at the same time.
|
||||
For example: Use an Uno DCC++EX DecoderPro Programming Station {DCC++Prg} on a desktop programming track and a second Mega DCC++EX EX-RAIL Command Station for Operations {DCC++Ops} on the layout with an additional `<JOINED>` programming spur or siding track for acquiring an engine and ‘Drive Away’ onto the mainline (see the DriveAway feature for more information).
|
||||
|
||||
**DCC++EX 4.0.0 additional product enhancements**
|
||||
|
||||
- Additional Motor Shields and Motor Board {boosters) supported
|
||||
- Additional Accessory boards supported for GPIO expansion, Sensors, Servos & Signals
|
||||
- Additional diagnostic commands like ‘D ACK RETRY’ and ‘D EXRAIL ON’ events, ‘D HAL SHOW’ devices and ‘D SERVO’ positions, and ‘D RESET’ the command station while maintaining the serial connection with JMRI
|
||||
- Automatic retry on failed ACK detection to give decoders another chance
|
||||
- New EX-RAIL ’/’ slash command allows JMRI to directly communicate with many EX-RAIL scripts
|
||||
- Turnout class revised to expand turnout capabilities and allow turnout names/descriptors to display in WiThrottle Apps.
|
||||
- Build turnouts through either or both mySetup.h and myAutomation.h files, and have them automatically passed to, and populate, JMRI Turnout Tables
|
||||
- Turnout user names display in Engine Driver & WiThrottles
|
||||
- Output class now allows ID > 255.
|
||||
- Configuration options to globally flip polarity of DCC Accessory states when driven from `<a>` command and `<T>` command.
|
||||
- Increased use of display for showing loco decoder programming information.
|
||||
- Can disable EEPROM memory code to allow room for DCC++EX 4.0 to fit on a Uno Command Station
|
||||
- Can define border between long and short addresses
|
||||
- Native non-blocking I2C drivers for AVR and Nano architectures (fallback to blocking Wire library for other platforms).
|
||||
- EEPROM layout change - deletes EEPROM contents on first start following upgrade.
|
||||
|
||||
**4.0.0 Bug Fixes**
|
||||
|
||||
- Compiles on Nano Every
|
||||
- Diagnostic display of ack pulses >32ms
|
||||
- Current read from wrong ADC during interrupt
|
||||
- AT(+) Command Pass Through
|
||||
- CiDAP WiFi Drop out and the WiThrottle F-key looping error corrected
|
||||
- One-off error in CIPSEND drop
|
||||
- Common Fault Pin Error
|
||||
- Uno Memory Utilization optimized
|
||||
|
||||
#### Summary of Release 3.1.0 key features and/or bug fixes by Point Release
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.16**
|
||||
|
||||
- Ignore CV1 bit 7 read if rejected by a non NMRA compliant decoder when identifying loco id
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.15**
|
||||
|
||||
- Send function commands just once instead of repeating them 4 times
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.14**
|
||||
|
||||
- Add feature to tolerate decoders that incorrectly have gaps in their ACK pulse
|
||||
- Provide proper track power management when joining and unjoining tracks with <1 JOIN>
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.13**
|
||||
|
||||
- Fix for CAB Functions greater than 127
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.12**
|
||||
|
||||
- Fixed clear screen issue for nanoEvery and nanoWifi
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.11**
|
||||
|
||||
- Reorganized files for support of 128 speed steps
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.10**
|
||||
|
||||
- Added Support for the Teensy 3.2, 3.5, 3.6, 4.0 and 4.1 MCUs
|
||||
- No functional change just changes to avoid complier warnings for Teensy/nanoEvery
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.9**
|
||||
|
||||
- Rearranges serial newlines for the benefit of JMRI
|
||||
- Major update for efficiencies in displays (LCD, OLED)
|
||||
- Add I2C Support functions
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.8**
|
||||
|
||||
- Wraps <* *> around DIAGS for the benefit of JMRI
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.7**
|
||||
|
||||
- Implemented support for older 28 apeed step decoders - Option to turn on 28 step speed decoders in addition to 128. If set, all locos will use 28 steps.
|
||||
- Improved overload messages with raw values (relative to offset)
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.6**
|
||||
|
||||
- Prevent compiler warning about deprecated B constants
|
||||
- Fix Bug that did not let us transmit 5 byte sized packets - 5 Byte commands like PoM (programming on main) were not being sent correctly
|
||||
- Support for Huge function numbers (DCC BinaryStateControl) - Support Functions beyond F28
|
||||
- <!> ESTOP all - New command to emergency stop all locos on the main track
|
||||
- <- [cab]> estop and forget cab/all cabs - Stop and remove loco from the CS. Stops the repeating throttle messages
|
||||
- `<D RESET>` command to reboot Arduino
|
||||
- Automatic sensor offset detect
|
||||
- Improved startup msgs from Motor Drivers (accuracy and auto sense factors)
|
||||
- Drop post-write verify - No need to double check CV writes. Writes are now even faster.
|
||||
- Allow current sense pin set to UNUSED_PIN - No need to ground an unused analog current pin. Produce startup warning and callback -2 for prog track cmds.
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.5**
|
||||
|
||||
- Fix Fn Key startup with loco ID and fix state change for F16-28
|
||||
- Removed ethernet mac config and made it automatic
|
||||
- Show wifi ip and port on lcd
|
||||
- Auto load config.example.h with warning
|
||||
- Dropped example .ino files
|
||||
- Corrected .ino comments
|
||||
- Add Pololu fault pin handling
|
||||
- Waveform speed/simplicity improvements
|
||||
- Improved pin speed in waveform
|
||||
- Portability to nanoEvery and UnoWifiRev2 CPUs
|
||||
- Analog read speed improvements
|
||||
- Drop need for DIO2 library
|
||||
- Improved current check code
|
||||
- Linear command
|
||||
- Removed need for ArduinoTimers files
|
||||
- Removed option to choose different timer
|
||||
- Added EX-RAIL hooks for automation in future version
|
||||
- Fixed Turnout list
|
||||
- Allow command keywords in mixed case
|
||||
- Dropped unused memstream
|
||||
- PWM pin accuracy if requirements met
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.4**
|
||||
|
||||
- "Drive-Away" Feature - added so that throttles like Engine Driver can allow a loco to be programmed on a usable, electrically isolated programming track and then drive off onto the main track
|
||||
- WiFi Startup Fixes
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.3**
|
||||
|
||||
- Command to write loco address and clear consist
|
||||
- Command will allow for consist address
|
||||
- Startup commands implemented
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.2:**
|
||||
|
||||
- Create new output for current in mA for `<c>` command - New current response outputs current in mA, overlimit current, and maximum board capable current
|
||||
- Simultaneously update JMRI to handle new current meter
|
||||
|
||||
**Summary of the key new features added to CommandStation-EX V3.0.1:**
|
||||
|
||||
- Add back fix for jitter
|
||||
- Add Turnouts, Outputs and Sensors to `<s>` command output
|
||||
|
||||
**CommandStation-EX V3.0.0:**
|
||||
|
||||
**Release v3.0.0 was a major rewrite if earlier versions of DCC++. The code base was re-architeced and core changes were made to the Waveform generator to reduce overhead and make better use of Arduino.** **Summary of the key new features added in Release v3.0.0 include:**
|
||||
|
||||
- **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains.
|
||||
- **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients.
|
||||
- **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently.
|
||||
- **Add LCD/OLED support** - OLED supported on Mega only
|
||||
- **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second.
|
||||
- **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers
|
||||
- **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts.
|
||||
- **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs
|
||||
- **New, simpler function command** - `<F>` command allows setting functions based on their number, not based on a code as in `<f>`
|
||||
- **Function reminders** - Function reminders are sent in addition to speed reminders
|
||||
- **Functions to F28** - All NMRA functions are now supported
|
||||
- **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24)
|
||||
- **Diagnostic `<D>` commands** - See documentation for a full list of new diagnostic commands
|
||||
- **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM
|
||||
- **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers
|
||||
- **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code
|
||||
- **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM
|
||||
- **Fix EEPROM bugs**
|
||||
- **Number of locos discovery command** - `<#>` command
|
||||
- **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega.
|
||||
- **Automatic slot management** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `<->` and `<- CAB>` commands added to release locos from memory and stop packets to the track.
|
||||
|
||||
**Key Contributors**
|
||||
|
||||
**Project Lead**
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**EX-CommandStation Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Neil McKechnie - Worcestershire, UK (NeilMck)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- M Steve Todd - Oregon, USA (MSteveTodd)
|
||||
- Scott Catalano - Pennsylvania
|
||||
- Gregor Baues - Île-de-France, France (grbba)
|
||||
|
||||
**Engine Driver and JMRI Interface**
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**EX-Installer Software**
|
||||
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
**Website and Documentation**
|
||||
|
||||
- Mani Kumar - Bangalor, India (Mani / Mani Kumar)
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
- Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting)
|
||||
- Roger Beschizza - Dorset, UK (Roger Beschizza)
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Colin Grabham - Central NSW, Australia (Kebbin)
|
||||
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
|
||||
- Peter Akers - Brisbane, QLD, Australia (flash62au)
|
||||
|
||||
**EX-WebThrottle**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
**Hardware / Electronics**
|
||||
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Paul Antoine, Western Australia
|
||||
- Neil McKechnie - Worcestershire, UK
|
||||
- Fred Decker - Holly Springs NC, USA
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
|
||||
- Larry Dribin - Release Management
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
- Keith Ledbetter
|
||||
- Brad Van der Elst
|
||||
- Andrew Pye
|
||||
- Mike Bowers
|
||||
- Randy McKenzie
|
||||
- Roberto Bravin
|
||||
- Sam Brigden
|
||||
- Alan Lautenslager
|
||||
- Martin Bafver
|
||||
- Mário André Silva
|
||||
- Anthony Kochevar
|
||||
- Gajanatha Kobbekaduwe
|
||||
- Sumner Patterson
|
||||
- Paul - Virginia, USA
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
|
372
TrackManager.cpp
Normal file
372
TrackManager.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "TrackManager.h"
|
||||
#include "FSH.h"
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
#define APPLY_BY_MODE(findmode,function) \
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
track[t]->function;
|
||||
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
|
||||
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
||||
byte TrackManager::lastTrack=0;
|
||||
bool TrackManager::progTrackSyncMain=false;
|
||||
bool TrackManager::progTrackBoosted=false;
|
||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
|
||||
|
||||
// The setup call is done this way so that the tracks can be in a list
|
||||
// from the config... the tracks default to NULL in the declaration
|
||||
void TrackManager::Setup(const FSH * shieldname,
|
||||
MotorDriver * track0, MotorDriver * track1, MotorDriver * track2,
|
||||
MotorDriver * track3, MotorDriver * track4, MotorDriver * track5,
|
||||
MotorDriver * track6, MotorDriver * track7 ) {
|
||||
addTrack(0,track0);
|
||||
addTrack(1,track1);
|
||||
addTrack(2,track2);
|
||||
addTrack(3,track3);
|
||||
addTrack(4,track4);
|
||||
addTrack(5,track5);
|
||||
addTrack(6,track6);
|
||||
addTrack(7,track7);
|
||||
|
||||
// Default the first 2 tracks (which may be null) and perform HA waveform check.
|
||||
setTrackMode(0,TRACK_MODE_MAIN);
|
||||
setTrackMode(1,TRACK_MODE_PROG);
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DIAG(F("Signal pin config: %S accuracy waveform"),
|
||||
MotorDriver::usePWM ? F("high") : F("normal") );
|
||||
DCC::begin(shieldname);
|
||||
}
|
||||
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
lastTrack=t;
|
||||
}
|
||||
}
|
||||
|
||||
// The port registers that are shadowing
|
||||
// the real port registers. These are
|
||||
// defined in Motordriver.cpp
|
||||
extern byte fakePORTA;
|
||||
extern byte fakePORTB;
|
||||
extern byte fakePORTC;
|
||||
|
||||
// setDCCSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(fakePORTA=PORTA);
|
||||
HAVE_PORTB(fakePORTB=PORTB);
|
||||
HAVE_PORTC(fakePORTC=PORTC);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=fakePORTA);
|
||||
HAVE_PORTB(PORTB=fakePORTB);
|
||||
HAVE_PORTC(PORTC=fakePORTC);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
// MotorDriver::setDCSignal handles shadowed IO port changes.
|
||||
// with interrupts turned off around the critical section
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab) continue;
|
||||
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
DIAG(F("Track=%c"),trackToSet+'A');
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
}
|
||||
} else {
|
||||
track[trackToSet]->setResetCounterPointer(NULL); // only the prog track has this pointer set
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
|
||||
// When a track is switched, we must clear any side effects of its previous
|
||||
// state, otherwise trains run away or just dont move.
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
// DC tracks need to be given speed of the throttle for that cab address
|
||||
// otherwise will not match other tracks on same cab.
|
||||
// This also needs to allow for inverted DCX
|
||||
applyDCSpeed(trackToSet);
|
||||
|
||||
}
|
||||
else {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
// 128 is speed=0 and dir=0 and then loosen brake.
|
||||
track[trackToSet]->setDCSignal(128);
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
||||
|
||||
// re-evaluate HighAccuracy mode
|
||||
// We can only do this is all main and prog tracks agree
|
||||
bool canDo=true;
|
||||
FOR_EACH_TRACK(t) {
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
} else {
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canDo) {
|
||||
// if we discover that HA mode was globally impossible
|
||||
// we must adjust the trackPWM capabilities
|
||||
FOR_EACH_TRACK(t) {
|
||||
track[t]->trackPWM=false;
|
||||
DIAG(F("Track %c trackPWM 0 (global override)"), t+'A');
|
||||
}
|
||||
DCCTimer::clearPWM(); // has to be AFTER trackPWM changes because if trackPWM==true this is undone for that track
|
||||
}
|
||||
if (MotorDriver::usePWM != canDo)
|
||||
DIAG(F("HA mode changed from %d to %d"), MotorDriver::usePWM, canDo);
|
||||
MotorDriver::usePWM=canDo;
|
||||
|
||||
|
||||
// Normal running tracks are set to the global power state
|
||||
track[trackToSet]->setPower(
|
||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
||||
mainPowerGuess : POWERMODE::OFF);
|
||||
DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (trackMode[t]==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
|
||||
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]!=NULL) {
|
||||
StringFormatter::send(stream,F("<= %c "),'A'+t);
|
||||
switch(trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
StringFormatter::send(stream,F("MAIN"));
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
StringFormatter::send(stream,F("PROG"));
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
StringFormatter::send(stream,F("OFF"));
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
StringFormatter::send(stream,F("EXT"));
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
|
||||
return setTrackMode(p[0],TRACK_MODE_OFF);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
|
||||
void TrackManager::loop() {
|
||||
DCCWaveform::loop();
|
||||
DCCACK::loop();
|
||||
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
|
||||
nextCycleTrack++;
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (trackMode[t]) {
|
||||
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_OFF:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
pinMode(joinRelay,OUTPUT);
|
||||
digitalWrite(joinRelay,LOW); // LOW is relay disengaged
|
||||
}
|
||||
}
|
||||
|
||||
void TrackManager::setJoin(bool joined) {
|
||||
progTrackSyncMain=joined;
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
||||
}
|
88
TrackManager.h
Normal file
88
TrackManager.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of Asbelos DCC API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef TrackManager_h
|
||||
#define TrackManager_h
|
||||
#include "FSH.h"
|
||||
#include "MotorDriver.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
|
||||
enum TRACK_MODE : byte {TRACK_MODE_OFF, TRACK_MODE_MAIN, TRACK_MODE_PROG,
|
||||
TRACK_MODE_DC, TRACK_MODE_DCX, TRACK_MODE_EXT};
|
||||
|
||||
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
|
||||
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
|
||||
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
|
||||
const byte TRACK_NUMBER_2=2, TRACK_NUMBER_C=2;
|
||||
const byte TRACK_NUMBER_3=3, TRACK_NUMBER_D=3;
|
||||
const byte TRACK_NUMBER_4=4, TRACK_NUMBER_E=4;
|
||||
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;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
MotorDriver * track0,
|
||||
MotorDriver * track1=NULL,
|
||||
MotorDriver * track2=NULL,
|
||||
MotorDriver * track3=NULL,
|
||||
MotorDriver * track4=NULL,
|
||||
MotorDriver * track5=NULL,
|
||||
MotorDriver * track6=NULL,
|
||||
MotorDriver * track7=NULL
|
||||
);
|
||||
|
||||
static void setDCCSignal( bool on);
|
||||
static void setCutout( bool on);
|
||||
static void setPROGSignal( bool on);
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
static void setPower2(bool progTrack,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 const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
||||
static POWERMODE getProgPower();
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
static byte nextCycleTrack;
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
static TRACK_MODE trackMode[MAX_TRACKS];
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
};
|
||||
|
||||
#endif
|
@@ -60,9 +60,9 @@ protected:
|
||||
union {
|
||||
struct {
|
||||
bool closed : 1;
|
||||
bool hidden : 1;
|
||||
bool _rfu : 1;
|
||||
uint8_t turnoutType : 5;
|
||||
bool _rfu: 2;
|
||||
bool hidden: 1;
|
||||
uint8_t turnoutType : 4;
|
||||
};
|
||||
uint8_t flags;
|
||||
};
|
||||
|
@@ -55,6 +55,8 @@
|
||||
#include "version.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define LOOPLOCOS(THROTTLECHAR, CAB) for (int loco=0;loco<MAX_MY_LOCO;loco++) \
|
||||
if ((myLocos[loco].throttle==THROTTLECHAR || '*'==THROTTLECHAR) && (CAB<0 || myLocos[loco].cab==CAB))
|
||||
@@ -127,7 +129,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{%c"), id,id,tchar);
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
@@ -165,9 +167,12 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
break;
|
||||
case 'P':
|
||||
if (cmd[1]=='P' && cmd[2]=='A' ) { //PPA power mode
|
||||
DCCWaveform::mainTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
TrackManager::setMainPower(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
/* TODO
|
||||
if (MotorDriver::commonFaultPin) // commonFaultPin prevents individual track handling
|
||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||
*/
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
}
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
@@ -218,9 +223,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),DCCWaveform::mainTrack.getPowerMode()==POWERMODE::ON);
|
||||
|
||||
// Send the roster
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
@@ -418,7 +421,10 @@ void WiThrottle::locoAction(RingStream * stream, byte* aval, char throttleChar,
|
||||
bool forward=aval[1]!='0';
|
||||
LOOPLOCOS(throttleChar, cab) {
|
||||
mostRecentCab=myLocos[loco].cab;
|
||||
DCC::setThrottle(myLocos[loco].cab, DCC::getThrottleSpeed(myLocos[loco].cab), forward);
|
||||
int8_t speed = DCC::getThrottleSpeed(myLocos[loco].cab);
|
||||
if (speed < 0) //can not find any speed for this cab
|
||||
speed = 0;
|
||||
DCC::setThrottle(myLocos[loco].cab, speed, forward);
|
||||
// setThrottle will cause a broadcast so notification will be sent
|
||||
}
|
||||
}
|
||||
@@ -564,8 +570,8 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
char addcmd[20]={'M',stashThrottleChar,'+', addrchar};
|
||||
itoa(locoid,addcmd+4,10);
|
||||
stashInstance->multithrottle(stashStream, (byte *)addcmd);
|
||||
DCCWaveform::progTrack.setPowerMode(POWERMODE::ON);
|
||||
DCC::setProgTrackSyncMain(true); // <1 JOIN> so we can drive loco away
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
#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 <avr/pgmspace.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
#include "DIAG.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
@@ -276,7 +276,6 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
checkForOK(2000, true);
|
||||
}
|
||||
}
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=0\r\n")); // turn off tcp server (to clean connections before CIPMUX=1)
|
||||
checkForOK(1000, true); // ignore result in case it already was off
|
||||
@@ -292,6 +291,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSERVER=1,%d\r\n"), port); // turn on server on port
|
||||
if (!checkForOK(1000, true)) return WIFI_DISCONNECTED;
|
||||
#endif //DONT_TOUCH_WIFI_CONF
|
||||
|
||||
StringFormatter::send(wifiStream, F("AT+CIFSR\r\n")); // Display ip addresses to the DIAG
|
||||
if (!checkForOK(1000, F("IP,\"") , true, false)) return WIFI_DISCONNECTED;
|
||||
|
@@ -23,7 +23,7 @@
|
||||
#include "FSH.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
//#include <avr/pgmspace.h>
|
||||
|
||||
enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED };
|
||||
|
||||
|
@@ -41,7 +41,6 @@ The configuration file for DCC-EX Command Station
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
|
||||
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
|
||||
// |
|
||||
// +-----------------------v
|
||||
//
|
||||
|
@@ -40,7 +40,7 @@
|
||||
// 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 (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO)) || defined(ARDUINO_AVR_NANO_EVERY)
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||
#define BIG_RAM
|
||||
#endif
|
||||
#if ENABLE_WIFI
|
||||
|
112
freeMemory.cpp
112
freeMemory.cpp
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2020 Harald Barth
|
||||
*
|
||||
* This file is part of Asbelos DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "freeMemory.h"
|
||||
|
||||
// thanks go to https://github.com/mpflaga/Arduino-MemoryFree
|
||||
#if defined(__arm__)
|
||||
extern "C" char* sbrk(int);
|
||||
#elif defined(__AVR__)
|
||||
extern char *__brkval;
|
||||
extern char *__malloc_heap_start;
|
||||
#else
|
||||
#error Unsupported board type
|
||||
#endif
|
||||
|
||||
|
||||
static volatile int minimum_free_memory = __INT_MAX__;
|
||||
|
||||
#if !defined(__IMXRT1062__)
|
||||
static inline int freeMemory() {
|
||||
char top;
|
||||
#if defined(__arm__)
|
||||
return &top - reinterpret_cast<char*>(sbrk(0));
|
||||
#elif defined(__AVR__)
|
||||
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
|
||||
#else
|
||||
#error bailed out already above
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
byte sreg_save = SREG;
|
||||
noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
|
||||
#else
|
||||
#if defined(ARDUINO_TEENSY40)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 1984;
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
static const unsigned DTCM_START = 0x20000000UL;
|
||||
static const unsigned OCRAM_START = 0x20200000UL;
|
||||
static const unsigned OCRAM_SIZE = 512;
|
||||
static const unsigned FLASH_SIZE = 7936;
|
||||
#if TEENSYDUINO>151
|
||||
extern "C" uint8_t external_psram_size;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static inline int freeMemory() {
|
||||
extern unsigned long _ebss;
|
||||
extern unsigned long _sdata;
|
||||
extern unsigned long _estack;
|
||||
const unsigned DTCM_START = 0x20000000UL;
|
||||
unsigned dtcm = (unsigned)&_estack - DTCM_START;
|
||||
unsigned stackinuse = (unsigned) &_estack - (unsigned) __builtin_frame_address(0);
|
||||
unsigned varsinuse = (unsigned)&_ebss - (unsigned)&_sdata;
|
||||
unsigned freemem = dtcm - (stackinuse + varsinuse);
|
||||
return freemem;
|
||||
}
|
||||
|
||||
// Return low memory value.
|
||||
int minimumFreeMemory() {
|
||||
//byte sreg_save = SREG;
|
||||
//noInterrupts(); // Disable interrupts
|
||||
int retval = minimum_free_memory;
|
||||
//SREG = sreg_save; // Restore interrupt state
|
||||
return retval;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
//
|
||||
// Although __brkval may go up and down as heap memory is allocated
|
||||
// and freed, this function records only the worst case encountered.
|
||||
// So even if all of the heap is freed, the reported minimum free
|
||||
// memory will not increase.
|
||||
//
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes) {
|
||||
int spare = freeMemory()-extraBytes;
|
||||
if (spare < 0) spare = 0;
|
||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||
}
|
||||
|
25
freeMemory.h
25
freeMemory.h
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020 Harald Barth
|
||||
*
|
||||
* This file is part of DCC-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef freeMemory_h
|
||||
#define freeMemory_h
|
||||
void updateMinimumFreeMemory(unsigned char extraBytes=0);
|
||||
int minimumFreeMemory();
|
||||
#endif
|
62
log2lrc.py
62
log2lrc.py
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2022 Harald Barth
|
||||
#
|
||||
# This converts serial logs with timestamps from the
|
||||
# Arduino IDE to LRC format.
|
||||
#
|
||||
# This is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# It is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Usage: log2lrc.py HH:MM:SS HH:MM:SS filename
|
||||
# logtime videotime
|
||||
# The resulting timestamps will be adjusted by logtime - videotime.
|
||||
# logtime > videotime
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
import fileinput
|
||||
import re
|
||||
|
||||
timediff = datetime.datetime.strptime('00:00:00','%H:%M:%S')
|
||||
|
||||
def convert(timestr):
|
||||
times=timestr.split('.',1) # remove fractions of second
|
||||
timestamp_obj=datetime.datetime.strptime(times[0],'%H:%M:%S')
|
||||
timestamp_obj=timestamp_obj-timediff # calculate offset
|
||||
timestr='{0:%M:%S}'.format(timestamp_obj)
|
||||
timestr='%s.%s' % (timestr, times[1][0:2]) # add fractions of second, 2 digits
|
||||
return timestr
|
||||
|
||||
def main(argv):
|
||||
global timediff
|
||||
fromtime_str=sys.argv[1]
|
||||
fromtime_obj=datetime.datetime.strptime(fromtime_str,'%H:%M:%S')
|
||||
totime_str=sys.argv[2]
|
||||
totime_obj=datetime.datetime.strptime(totime_str,'%H:%M:%S')
|
||||
sys.argv=sys.argv[2:]
|
||||
|
||||
|
||||
timediff = fromtime_obj - totime_obj
|
||||
|
||||
for line in fileinput.input():
|
||||
line = re.split('\s+', line, 1)
|
||||
if (len(line) > 1 and len(line[1]) > 1):
|
||||
l = line[1].replace('\n','')
|
||||
l = l.replace('\r','')
|
||||
l = re.sub('^-> ','',l)
|
||||
print("[%s]%s" % (convert(line[0]),l))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
|
@@ -1,69 +1,12 @@
|
||||
Version 4.0 Release Notes
|
||||
*************************
|
||||
|
||||
The DCC-EX Team is pleased to release CommandStation-EX v4.1.2 as a Production Release for the general public.
|
||||
This release is a Minor release with many significant EX-RAIL enhancements and new automation features in addition to some bug fixes.
|
||||
The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v4.0.1 to v4.1.2.
|
||||
The DCC-EX Team is pleased to release CommandStation-EX-v4.0.0 as a Production Release. Release v4.0.0 is a Major release that adds significant new product design, plus Automation features and bug fixes. The team continues improving the architecture of DCC++EX to make it more flexible and optimizing the code so as to get more performance from the Arduino (and other) microprocessors. This release includes all of the Point Releases from v3.2.0 to v3.2.0 rc13.
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**New Command Station & EX-RAIL Features**
|
||||
- ACK defaults are now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
|
||||
- Automatically detect and run a myFilter add-on (no need to call setFilter)
|
||||
|
||||
- New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
|
||||
- </RED signal_id> to turn a individual LED Signal On & Off
|
||||
- </AMBER signal_id> "
|
||||
- </GREEN signal_id> "
|
||||
- </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- < t cab> command to obtain current throttle setting
|
||||
|
||||
- Allow WRITE CV on PROG <W CV VALUE>
|
||||
- Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
|
||||
- Allow WRITE CV on PROG <W CV VALUE)
|
||||
- Change callback parameters are now optional on PROG
|
||||
|
||||
- New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
|
||||
|
||||
- New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) Scripts
|
||||
- ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
|
||||
- FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
|
||||
- FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
|
||||
- "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
- KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
- PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
|
||||
- SERVO_SIGNAL Servo signals assigned to a specific servo turnout
|
||||
- SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
|
||||
- HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
|
||||
- VIRTUAL_TURNOUT definition
|
||||
|
||||
**EX-RAIL Updates**
|
||||
- EXRAIL BROADCAST("msg") sends any message to all throttles/JMRI via serial and WiFi
|
||||
- EXRAIL POWERON turns on power to both tracks from EX-RAIL (the equivalent of sending the <1> command)
|
||||
|
||||
** Other Enhancements**
|
||||
- UNO Progmem is optimize to allow for small EXRAIL Automation scipts to run within the limited space for testing purposes.
|
||||
- PCA9685 Servo Signal board supports 'Nopoweroffleds', servo pins stay powered on after position reached, otherwise the new FADE would always turn off.
|
||||
- Position servo can use spare servo pin as a GPIO pin.
|
||||
|
||||
**4.1.2 Bug Fixes**
|
||||
- Fixed Ethernet shield W5100 support since it does not report HW or link level like the W5200 and W5500 chips.
|
||||
|
||||
**4.1.1 Bug Fixes**
|
||||
|
||||
- Preserve the turnout format
|
||||
- Parse multiple commands in one buffer string currectly
|
||||
- Fix </> command signal status in EX-RAIL
|
||||
- Read long loco addresses in EX-RAIL
|
||||
- FIX negative route IDs in WIthrottle
|
||||
|
||||
See the version.h file for notes about which of the 4.1.2 features were added/changed by point release.
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v0.0.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
||||
**Known Issues**
|
||||
|
||||
@@ -276,7 +219,7 @@ See the version.h file for notes about which of the 4.1.2 features were added/ch
|
||||
|
||||
- Fred Decker - Holly Springs, North Carolina, USA (FlightRisk)
|
||||
|
||||
**EX-CommandStation Developers**
|
||||
**CommandStation-EX Developers**
|
||||
|
||||
- Chris Harlow - Bournemouth, UK (UKBloke)
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
@@ -291,7 +234,7 @@ See the version.h file for notes about which of the 4.1.2 features were added/ch
|
||||
|
||||
- M Steve Todd
|
||||
|
||||
**EX-Installer Software**
|
||||
**exInstaller Software**
|
||||
|
||||
- Anthony W - Dayton, Ohio, USA (Dex, Dex++)
|
||||
|
||||
@@ -304,22 +247,12 @@ See the version.h file for notes about which of the 4.1.2 features were added/ch
|
||||
- Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter)
|
||||
- Kevin Smith - Rochester Hills, Michigan USA (KC Smith)
|
||||
- Colin Grabham - Central NSW, Australia (Kebbin)
|
||||
- Peter Cole - Brisbane, QLD, Australia (peteGSX)
|
||||
- Peter Akers - Brisbane, QLD, Australia (flash62au)
|
||||
|
||||
**EX-WebThrottle**
|
||||
**WebThrotle-EX**
|
||||
|
||||
- Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk)
|
||||
- Mani Kumar - Bangalor, India (Mani /Mani Kumar)
|
||||
- Matt H - Somewhere in Europe
|
||||
|
||||
**Hardware / Electronics**
|
||||
|
||||
- Harald Barth - Stockholm, Sweden (Haba)
|
||||
- Paul Antoine, Western Australia
|
||||
- Neil McKechnie - Worcestershire, UK
|
||||
- Fred Decker - Holly Springs NC, USA
|
||||
- Herb Morton - Kingwood Texas, USA (Ash++)
|
||||
|
||||
**Beta Testing / Release Management / Support**
|
||||
|
||||
@@ -343,7 +276,7 @@ See the version.h file for notes about which of the 4.1.2 features were added/ch
|
||||
|
||||
**Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.**
|
||||
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.zip)
|
||||
[CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.zip)
|
||||
|
||||
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.1.2-Prod/CommandStation-EX.tar.gz)
|
||||
[CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/releases/download/v4.0.0-Prod/CommandStation-EX.tar.gz)
|
||||
|
84
version.h
84
version.h
@@ -3,66 +3,34 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "4.1.6"
|
||||
// 4.1.6 Support DCC-EX shield
|
||||
// 4.1.5 Bugfix LCN number parsing
|
||||
// 4.1.4 Bugfix for issue #299 TurnoutDescription NULL
|
||||
// 4.1.3 Bugfix: Ethernet init order
|
||||
// 4.1.2 Bugfix: Ethernet shield W5100 does not report HW or link level
|
||||
// 4.1.1 Bugfix: preserve turnout format
|
||||
// Bugfix: parse multiple commands in one buffer string correctly (ex: <s><Q>)
|
||||
// Bugfix: </> command signal status of EX-RAIL tasks or threads
|
||||
// Bugfix: EX-RAIL read long loco addr
|
||||
// Bugfix: Add space character after version string 4.1.1 for JMRI parsing.
|
||||
// Improved display and loop time for signals make service start to be outside the DONT_TOUCH_WIFI_CONF area
|
||||
// Improve WiFi startup by making service start to be outside the DONT_TOUCH_WIFI_CONF area
|
||||
// 4.1.0 ...
|
||||
// UNO Progmem optimized to allow for small EXRAIL Automation scipts
|
||||
// 4.0.2 Command Station and EX-RAIL Ehancements & Additions:
|
||||
// New JA, JR, JT commands availabe for Throttle Developers to obtain Route, Roster and Turnout descriptions for communications
|
||||
// Change ACK defaults now set to LIMIT 50mA, MIN 2000uS, MAX 20000uS for more compatibility with non NMRA compliant decoders
|
||||
// New Commands for the Arduino IDE Serial Monitor and JMRI DCC++ Traffic Monitor
|
||||
// </RED signal_id> to turn a individual LED Signal On & Off
|
||||
// </AMBER signal_id> "
|
||||
// </GREEN signal_id> "
|
||||
// </KILL ALL> command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
// <t cab> command to obtain current throttle settings
|
||||
// Allow WRITE CV on PROG <W CV VALUE>
|
||||
// Updated CV read command <R cv>. Equivalent to <V cv 0>. Uses the verify callback.
|
||||
// Change callback parameters are now optional on PROG
|
||||
// Code: Fix weak reference to myFilter
|
||||
|
||||
#define VERSION "4.0.3"
|
||||
// 4.0.3 Track Manager additions:
|
||||
// SET_TRACK(track,mode) Functions (A-H, MAIN|PROG|DC|DCX|OFF)
|
||||
// New DC track function and DCX reverse polarity function
|
||||
// TrackManager DCC & DC up to 8 Districts Architecture
|
||||
// Automatic ALIAS(name)
|
||||
// Command Parser now accepts Underscore in Alias Names
|
||||
// 4.0.2 EXRAIL additions:
|
||||
// ACK defaults set to 50mA LIMIT, 2000uS MIN, 20000uS MAX
|
||||
// myFilter automatic detection (no need to call setFilter)
|
||||
// Improved SIGNALs startup and diagnostics
|
||||
// Incoming LCN turnout throw
|
||||
// Allow turnout ID of "0"
|
||||
// Code: struct TurnoutData to enable EEPROM in v 4.0 format
|
||||
// Broadcast jopin after DriveAway
|
||||
// Corrections to I2C code:
|
||||
// 1) I2CManager_Mega4809.h: Correct bitwise 'and' to logical 'and' - no impact.
|
||||
// 2) I2CManager_Wire.h: Ensure that error codes from Wire subsystem are passed back to caller in queueRequest().
|
||||
// New EX-RAIL Functions to use in Automation(n), ROUTE(N) & SEQUENCE(N) scripts
|
||||
// Automatically assign a name with ALIAS(name) without having to define it first
|
||||
// ALIAS now has the aility to use Low underscore in keywords e.g., MY_KEYWORD
|
||||
// ATGTE & ATLT wait for analog value, (At Greater Than or Equal and At Less Than a certain value)
|
||||
// FADE command now works for LEDs connected on PCA9685 Servo/Signal board Output vpins
|
||||
// FORGET Forgets the current loco in DCC reminder tables saving memory and wasted packets sent to the track
|
||||
// "IF" signal detection with IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
// KILLALL command to stop all tasks, and Diagnostic messages when KILL is used
|
||||
// PARSE <> commands in EXRAIL allows sending of DCC-EX commands from EX-RAIL
|
||||
// SERVO_SIGNAL Servo signals assigned to a specific servo turnout
|
||||
// SIGNALH High-On signal pins (Arduino normally handles active LOW signals. This allows for active HIGH)
|
||||
// HIDDEN turnouts (hide a REAL turnout and create a VIRTUAL turnout to handle actions that happen BEFORE a turnout is thrown)
|
||||
// VIRTUAL_TURNOUT definition
|
||||
// README.md: removed misleading "folder/subforlders" (#218)
|
||||
// README.md: fix dead link to rewrite (#217) in notes/rewrite.md
|
||||
// 4.0.1 Additional EXRAIL updates
|
||||
// FIX negative route ids in WIthrottle problem.
|
||||
// IFRED(signal_id), IFAMBER(signal_id), IFGREEN(signal_id)
|
||||
// </RED signal_id> </AMBER signal_id> </GREEN signal_id> commands
|
||||
// <t cab> command to obtain current throttle settings
|
||||
// JA, JR, JT commands to obtain route, roster and turnout descriptions
|
||||
// HIDDEN turnouts
|
||||
// PARSE <> commands in EXRAIL
|
||||
// VIRTUAL_TURNOUT
|
||||
// </KILL ALL> and KILLALL command to stop all tasks.
|
||||
// FORGET forgets the current loco in DCC reminder tables.
|
||||
// Servo signals (SERVO_SIGNAL)
|
||||
// High-On signal pins (SIGNALH)
|
||||
// Wait for analog value (ATGTE, ATLT)
|
||||
// 4.0.1 Small EXRAIL updates
|
||||
// EXRAIL BROADCAST("msg")
|
||||
// EXRAIL POWERON (only turns on MAIN)
|
||||
// Remove optional EXRAIL/ENDEXRAIL from myAutomation.example.h (#215)
|
||||
// Use "startup sequence" to describe the initial instructions
|
||||
// Add description of display scroll modes
|
||||
// GetLocoCallback() restructured for better readability and put broadcastPower() at right place
|
||||
// 4.0.0 Major Production Release with New Functional and non-functional changes.
|
||||
// EXRAIL POWERON
|
||||
// 4.0.0 Major functional and non-functional changes.
|
||||
// Engine Driver "DriveAway" feature enhancement
|
||||
// 'Discovered Server' multicast Dynamic Network Server (mDNS) displays available WiFi connections to a DCC++EX Command Station
|
||||
// New EX-RAIL "Extended Railroad Automation Instruction Language" automation capability.
|
||||
|
Reference in New Issue
Block a user