La suite en 2 parties (pour s affranchir des limites de 20K caractères)
sup_isr_MegaCoreX_DxCore_RAILCOM.h :
PART 1:
//******************************************************************************************************
//
// file: sup_isr_MegaCoreX_DxCore_RAILCOM.h
// purpose: DCC receive code for ATmegaX processors, such as ATmega 4808, 4809, AVR DA, AVR DB, ...
// using a MegaCoreX or DxCore board
// author: Aiko Pras + LTR
// version: 2021-09-01 V1.2.0 ap - Uses the Event system to connect the DCC input pin to a TCB timer.
// 2023-12-10 V1.2.2 add RAILCOM support LTR
//
// Result: 1. The received message is collected in the struct "dccrec.tempMessage".
// 2. After receiving a complete message, data is copied to "dccMessage.data".
// 3. The flag "dccMessage.isReady" is set.
//
// Used hardware resources:
// - DccPin: Any ATmega pin can be used to receive the DCC input signal
// Polarity of the J/K signal is not important.
// - Timer: One TCB timer. Default is TCB0, but the default can be changed by setting one of the
// #defines (DCC_USES_TIMERB1, DCC_USES_TIMERB2 or DCC_USES_TIMERB3)
// No dependancies exist with other timers, thus the TCA prescaler is NOT used.
// - Event: One of the Event channels available in ATmegaX processors.
// The software automatically selects an available channel.
// Not every pin can be connected to every Event channel. If other software has already
// claimed usage of some channels, it might be necessary to select a DCC input pin on
// another port. See also: https://github.com/MCUdude/MegaCoreX#event-system-evsys
//
// Howto: This part of the library takes advantage of the Event system peripheral that is implemented
// in the novel ATmegaX processors, and which is supported by MegaCoreX and DxCore.
// The DCC pin is used as input for an Event channel. The output of that Event channel triggers a TCB
// timer in 'Capture Frequency Measurement Mode'. If an event occurs in this Mode, the TCB
// captures the counter value (TCBn.CNT) and stores this value in TCBn.CCMP. The Interrupt flag is
// automatically cleared after the low byte of the Compare/Capture (TCBn.CCMP) register is read.
// The value in TCBn.CCMP is the number of ticks since the previous event, thus the time since the
// previous DCC signal transition. This time determines if the received DCC signal represents (half of)
// a zero or one bit.
// Since we do not want to introduce any dependency between our TCB timer and the TCA prescaler,
// TCB is used at clock speed. This requires us to use TCB in 16 bit mode. If we would have used the
// TCA prescaler, TCB could have been run in 8-bit mode, saving us roughly 8 clock cycli.
// To conform to the RCN-210 standard, we measure the times for both first as well as second half bit.
// Therefore the TCB ISR is called twice for every DCC bit.
// Since the TCB can only trigger on positive or negative edges of the input signal, the trigger
// direction is inverted after each interrupt.
//
// This code uses the Event system of MegaCoreX and DxCore:
// https://github.com/MCUdude/MegaCoreX#event-system-evsys
// https://github.com/SpenceKonde/DxCore
//
// Note: Railcom has now been implemented.
// Timer CNT + CCMP to determine the exact moment when
// the railcom data should be set. The railcom condition occured if conditions meet timings and DCC pins state.
// A UART is started to send the railcom data in RC appropriate periods according CV28 CV29 settings.
//
//
/* IMPLEMENTED BY LTR by adding new define values for RAILCOM
*/
//
//******************************************************************************************************
#include <Arduino.h>
#include <Event.h>
#include "sup_isr.h"
#include "CLASS_RAILCOM_GLOBAL.h"
//******************************************************************************************************
// 1. Declaration of external objects
//******************************************************************************************************
// The dccMessage contains the "raw" DCC packet, as received by the TCB ISR in this file
// It is instantiated in, and used by, DCC_Library.cpp
extern DccMessage dccMessage;
//RAILCOM items:
extern RAILCOM railcom;
//******************************************************************************************************
// 2. Defines that may need to be modified to accomodate certain hardware
//******************************************************************************************************
// Timer to use: TIMERB0 (TCB0), TIMERB1 (TCB1), TIMERB2 (TCB2), TIMERB3 (TCB3)
// In many cases TCB0 or TCB1 are still available for own use.
// The default is TCB0, which is available on all processor variants.
// This can be overruled by setting one of the following defines:
// #define DCC_USES_TIMERB1
// #define DCC_USES_TIMERB2
// #define DCC_USES_TIMERB3
// GPIOR (General Purpose IO Registers) are used to store global flags and temporary bytes.
// For example, in case of dccHalfBit, GPIOR saves roughly 8 clock cycli per interrupt.
// In case the selected GPIORs conflict with other libraries, change to any any free GPIOR
// or use a volatile uint8_t variable
#define dccrecState GPIOR0 // fast: saves 3 clock cycli, but requires a free GPIOR
// volatile uint8_t dccrecState; // slow, but more portable
#define tempByte GPIOR1 // fast
// volatile uint8_t tempByte; // slow
#define dccHalfBit GPIOR2 // fast
// volatile uint8_t dccHalfBit; // slow
//ADD VARIABLES FOR RAILCOM:
#define FLAG_RAILCOM_IN_CUTOUT GPIOR3 //fast
//volatile bool FLAG_RAILCOM_IN_CUTOUT //slow
//******************************************************************************************************
// 3. Defines, definitions and instantiation of local types and variables
//******************************************************************************************************
// Values for half bits from RCN 210, section 5: http://normen.railcommunity.de/RCN-210.pdf
#define ONE_BIT_MIN F_CPU / 1000000 * 52
#define ONE_BIT_MAX F_CPU / 1000000 * 64
#define ZERO_BIT_MIN F_CPU / 1000000 * 90
#define ZERO_BIT_MAX F_CPU / 1000000 * 119
//ADD RAILCOM VALUES: LTR 20231210 follow RCN-217: https://normen.railcommunity.de/RCN-217.pdf last updated on 20231123
#define RAILCOM_CUTOUT_MIN_START_TIME F_CPU/1000000 * 26 //MIN CUTOUT START TIME
#define RAILCOM_CUTOUT_MAX_START_TIME F_CPU/1000000 * 32 //MAX CUTOUT START TIME
#define RAILCOM_CH1_START_TIME F_CPU/1000000 * 75 //MIN CH1 START TIME
#define RAILCOM_CH1_MAX_START_TIME F_CPU/1000000 * (80+15) //MAX CH1 SEND START TIME
#define RAILCOM_CH1_END_TIME F_CPU/1000000 * 177 //MAX CH1 END TIME
#define RAILCOM_CH2_START_TIME F_CPU/1000000 * 193 //MIN CH2 START TIME
#define RAILCOM_CH2_MAX_START_TIME F_CPU/1000000 * (193+15) //MAX CH2 SEND START TIME
#define RAILCOM_CH2_END_TIME F_CPU/1000000 * 454 //END CH2
#define RAILCOM_CUTOUT_MAX_END_TIME F_CPU/1000000 * 488 //MAX CUTOUT TIME
// #define ZERO_BIT_MAX 65535
// Change the defines for ZERO_BIT_MAX to enable (disable) zero-bit stretching.
// To avoid integer overflow, we don't allow 10000 microseconds as maximum, but 65535 (maxint)
// For a 16Mhz clock this is equivalent to 4096 microseconds. This might work on most systems.
// For a discussion of zero bit streching see: https://github.com/littleyoda/sigrok-DCC-Protocoll/issues/4
// Possible values for dccrecState
#define WAIT_PREAMBLE (1<<0)
#define WAIT_START_BIT (1<<1)
#define WAIT_DATA (1<<2)
#define WAIT_END_BIT (1<<3)
// Possible values for dccHalfBit
#define EXPECT_ZERO (1<<0)
#define EXPECT_ONE (1<<1)
#define EXPECT_ANYTHING (1<<2)
struct {
uint8_t bitCount; // Count number of preamble bits / if we have a byte
volatile uint8_t tempMessage[MaxDccSize]; // Once we have a byte, we store it in the temp message
volatile uint8_t tempMessageSize; // Here we keep track of the size, including XOR
} dccrec; // The received DCC message is assembled here
//******************************************************************************************************
// 4. Initialise timer and event system
//******************************************************************************************************
void DccMessage::initTcb(void) {
// Step 1: Instead of calling a specific timer directly, we use alias via #defines
#if defined(DCC_USES_TIMERB0)
#define TIMER_DCC_CONTROL TCB0
#elif defined(DCC_USES_TIMERB1)
#define TIMER_DCC_CONTROL TCB1
#elif defined(DCC_USES_TIMERB2)
#define TIMER_DCC_CONTROL TCB2
#elif defined(DCC_USES_TIMERB3)
#define TIMER_DCC_CONTROL TCB3
#else
// fallback to TCB0 (every platform has it)
#define TIMER_DCC_CONTROL TCB0;
#endif
// Step 2: fill the registers. See the data sheets for details
noInterrupts();
// Clear the main timer control registers. Needed since the Arduino core creates some presets:
TIMER_DCC_CONTROL.CTRLA = 0;
TIMER_DCC_CONTROL.CTRLB = 0;
TIMER_DCC_CONTROL.EVCTRL = 0;
TIMER_DCC_CONTROL.INTCTRL = 0;
TIMER_DCC_CONTROL.CCMP = 0;
TIMER_DCC_CONTROL.CNT = 0;
TIMER_DCC_CONTROL.INTFLAGS = 0;
// Initialise the control registers:
TIMER_DCC_CONTROL.CTRLA = TCB_ENABLE_bm; // Enable the TCB peripheral, clock is CLK_PER (=F_CPU)
TIMER_DCC_CONTROL.CTRLB = TCB_CNTMODE_FRQ_gc; // Input Capture Frequency Measurement mode
TIMER_DCC_CONTROL.EVCTRL = TCB_CAPTEI_bm | TCB_FILTER_bm; // Enable input capture events and noise cancelation
TIMER_DCC_CONTROL.INTCTRL |= TCB_CAPT_bm; // Enable CAPT interrupts
interrupts();
}
void DccMessage::initEventSystem(uint8_t dccPin) {
// Note: this code uses the new Event Library of MegaCoreX, DXCore & MEGATINYCore:
noInterrupts();
Event& myEvent = Event::assign_generator_pin(dccPin);
#if defined(DCC_USES_TIMERB0)
myEvent.set_user(user::tcb0_capt);
#elif defined(DCC_USES_TIMERB1)
myEvent.set_user(user::tcb1_capt);
#elif defined(DCC_USES_TIMERB2)
myEvent.set_user(user::tcb2_capt);
#elif defined(DCC_USES_TIMERB3)
myEvent.set_user(user::tcb3_capt);
#else
// fallback to TCB0 (every platform must have it)
myEvent.set_user(user::tcb0_capt);
#endif
//start EVENTS:
myEvent.start();
interrupts();
}
//******************************************************************************************************
// 5. attach() / detach()
//******************************************************************************************************
void DccMessage::attach(uint8_t dccPin, uint8_t ackPin) {
// Initialize the local variables
_dccPin = dccPin;
tempByte = 0;
dccrecState = WAIT_PREAMBLE;
dccrec.bitCount = 0;
// initialise the global variables (the DccMessage attributes)
dccMessage.size = 0;
dccMessage.isReady = 0;
// Initialize the peripherals: (TCBx) timer and the Event system.
initTcb();
initEventSystem(dccPin);
// Initialise the DCC Acknowledgement port, which is needed in Service Mode
// If the main sketch doesn't specify this pin, the value 255 is provided as
// (invalid) default.
if (ackPin < 255) pinMode(ackPin, OUTPUT);
}
void DccMessage::detach(void) {
noInterrupts();
// Clear all TCB timer settings
// For "reboot" (jmp 0) it is crucial to set INTCTRL = 0
TIMER_DCC_CONTROL.CTRLA = 0;
TIMER_DCC_CONTROL.CTRLB = 0;
TIMER_DCC_CONTROL.EVCTRL = 0;
TIMER_DCC_CONTROL.INTCTRL = 0;
TIMER_DCC_CONTROL.CCMP = 0;
TIMER_DCC_CONTROL.CNT = 0;
TIMER_DCC_CONTROL.INTFLAGS = 0;
// Stop the Event channel interrupts();
interrupts();
}