Voici maintenant le logiciel Arduino.
Pour installer ce logiciel, il faut disposer de l'environnement de développement ARDUINO, copier/coller le source ci-dessous dans un nouveau projet, puis vérifier (compiler) et charger dans la mémoire Flash d'un ATMega328 (UNO).
Le compilateur affiche 7164 bytes.
Il reste de la place, même dans un ATMega328 !
Ce programme n'est pas du tout (ou peu) optimisé, afin de rester "lisible"
C'est dommage, on perd les indentations dans ce forum.
Si vous voulez une version plus copiable (j'ai testé le copié/collé dans Arduino et ça marche), j'ai ouvert un blog avec une copie des articles rassemblés là :
http://sixtyfive.sosblog.fr/index.htm-------------- LOGICIEL ARDUINO -------------
#define VERSION "@SixtyFive V0.2r1 26/12/2012"
/*
Arduino DCC automatic train controller
From SixtyFive, 19 December 2012 12h16
This program sends pulses 0v-5v on DCC_PIN that needs to be amplified by a booster.
Works well with LMD18200 Booster
Some parts of the program are originaly from Burns and Michael Blank and are released under GNU license
This program can control 2 trains with:
- 2 pots for speed of each train
- 1 switch for forward/backward direction (both trains)
- 1 switch for light on/off
- 1 switch for Start/Stop
- 1 switch for Manual/Auto
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation;
either version 3 of the License, or (at your option) any later version.
This program 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.
*/
//Define Arduino pins
/*
0 serial
1 serial
2 Manual/Auto switch
3 Start/Stop switch
4 DCC signal (DIR pin of LMD18200)
5 DCC PWM (PWM pin of LMD18200)
6 Park 1 - Station 1
7 Park 2 - Station 2
8 Sensor 1 - Pre station 1
9 Sensor 2 - Pre station 2
10
11 LED Rouge
12 LED Jaune
13 LED Verte
Analog
0 pot throttle 1
1 pot throttle 2
2 Current sense from LMD18200
3 Thermal sense from LMD18200
4 Direction 1 (forward/backward)
5 Direction 2
*/
// Arduino Pins
#define OVER_LED 11 // on if current too high
#define MAN_LED 12 // on in manual mode, off if auto
#define START_LED 13 // on when start, off when stop
#define MAN_SW 2 // MAN/AUTO Switch Down=True, Up=False
#define START_SW 3 // Start switch Down=True, Up=False
#define SENSOR1 8 // Pre station sensor1
#define SENSOR2 9 // Pre station sensor2
#define PARK1 6 // Park sensor 1
#define PARK2 7 //Park sensor 2
#define DCC_PIN 4 // Arduino pin for DCC out
// this pin is connected to "DIRECTION" of LMD18200
#define DCC_PWM 5 // must be HIGH for signal out
// connected to "PWM in" of LMD18200
#define AN_SPEED1 A0 // analog reading for Speed Poti
#define AN_SPEED2 A1 // analog reading for Speed Poti
#define CURRENT_PIN A2 // Current sense from lmd18200
#define THERM_PIN A3 // thermal warning PIN //never used
#define dirPin1 A4 // direction forward/backward of trains Down=True, Up=False
#define dirPin2 A5 // direction On/Off of accessory (light) Down=On, Up=Off
//Timer frequency is 2MHz for ( /8 prescale from 16MHz )
#define TIMER_SHORT 0x8D // 58usec pulse length
#define TIMER_LONG 0x1B // 116usec pulse length
// ------------------- definitions for state machine -------------------
#define PREAMBLE 0
#define SEPERATOR 1
#define SENDBYTE 2
// ------------------- globals -------------------
unsigned char last_timer=TIMER_SHORT; // store last timer value
unsigned char flag=0; // used for short or long pulse
unsigned char every_second_isr = 0; // pulse up or down
unsigned char state= PREAMBLE;
unsigned char preamble_count = 16;
unsigned char outbyte = 0;
unsigned char cbit = 0x80;
boolean firststart = true; //
int maxcurrent = 0; //
int meancurrent = 0;
int current; // read from analog CURRENT_PIN
int overcurrent = 160; //ABOUT 1 A
boolean currentprotection = true; // stop the main loop if it become false
int hertz = 10;
int wait = 1000/hertz;
boolean _debug = false; // allow debugging with Serial communications
//variables for speed messure
int timespeed = 0;
// ------------------- variables for loco 1 -------------------
int locoSpeed=0;
int dir = 1;
int last_locoSpeed=0;
int last_dir;
int locoAdr=3; // this is the (fixed) address of the loco
int runtime = 0;
int stoptime = 0;
boolean stopping = false;
int locomaxspeed = 40;
int loco1parkspeed = 30;
boolean parked1 = false;
int laptime1 = 30*hertz;
// ------------------- variables for loco 2 -------------------
int STATIONTIME = hertz*10; // 100ms loop 5s = 50
int runtime2 = 0;
int stoptime2 = 0;
int locoSpeed2 = 0;
int dir2 = 1;
int last_locoSpeed2=0;
int func14 = 0;
int last_dir2;
int locoAdr2 = 1; // this is the (fixed) address of the loco
boolean stopping2 = false;
int loco2maxspeed = 120;
int loco2parkspeed = 100;
boolean parked2 = false;
int laptime2 = 30*hertz;
// ------------------- buffer for command -------------------
struct Message {
unsigned char data[7];
unsigned char len;
} ;
#define MAXMSG 5
struct Message msg[MAXMSG] = {
{ { 0xFF, 0, 0xFF, 0, 0, 0, 0}, 3}, // idle msg
{ { locoAdr, 0x3F, 0, 0, 0, 0, 0}, 4}, // Advanced operation instruction with 128 speed steps and direction (b7) in next byte
{ { locoAdr2, 0x3F, 0, 0, 0, 0, 0}, 4}, // Advanced operation instruction with 128 speed steps and direction (b7) in next byte
{ { locoAdr, 0x80, 0, 0, 0, 0, 0}, 3}, // Function group one instruction : 0x80 means light off
{ { locoAdr2, 0x90, 0, 0, 0, 0, 0}, 3} // Function group one instruction : 0x90 means light on
}; // loco msg must be filled later with speed and XOR data byte
int msgIndex=0;
int byteIndex=0;
// ------------------- Setup Timer2. -------------------
//Configures the 8-Bit Timer2 to generate an interrupt at the specified frequency.
//Returns the time load value which must be loaded into TCNT2 inside your ISR routine.
void SetupTimer2(){
//Timer2 Settings: Timer Prescaler /8, mode 0
//Timmer clock = 16MHz/8 = 2MHz oder 0,5usec
TCCR2A = 0;
TCCR2B = 0<<CS22 | 1<<CS21 | 0<<CS20;
//Timer2 Overflow Interrupt Enable
TIMSK2 = 1<<TOIE2;
//load the timer for its first cycle
TCNT2=TIMER_SHORT;
}
// ------------------- Timer2 overflow interrupt vector handler -------------------
ISR(TIMER2_OVF_vect) {
//Capture the current timer value TCTN2. This is how much error we have
//due to interrupt latency and the work in this function
//Reload the timer and correct for latency.
// for more info, see
http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/ unsigned char latency;
// for every second interupt just toggle signal
if (every_second_isr) {
digitalWrite(DCC_PIN,1);
every_second_isr = 0;
// set timer to last value
latency=TCNT2;
TCNT2=latency+last_timer;
} else { // != every second interrupt, advance bit or state
digitalWrite(DCC_PIN,0);
every_second_isr = 1;
switch(state) {
case PREAMBLE:
flag=1; // short pulse
preamble_count--;
if (preamble_count == 0) { // advance to next state
state = SEPERATOR;
// get next message
msgIndex++;
if (msgIndex >= MAXMSG) { msgIndex = 0; }
byteIndex = 0; //start msg with byte 0
}
break;
case SEPERATOR:
flag=0; // long pulse
// then advance to next state
state = SENDBYTE;
// goto next byte ...
cbit = 0x80; // send this bit next time first
outbyte = msg[msgIndex].data[byteIndex];
break;
case SENDBYTE:
if (outbyte & cbit) {
flag = 1; // send short pulse
} else {
flag = 0; // send long pulse
}
cbit = cbit >> 1;
if (cbit == 0) { // last bit sent, is there a next byte?
byteIndex++;
if (byteIndex >= msg[msgIndex].len) {
// this was already the XOR byte then advance to preamble
state = PREAMBLE;
preamble_count = 16;
} else {
// send separtor and advance to next byte
state = SEPERATOR ;
}
}
break;
}
if (flag) { // if data==1 then short pulse
latency=TCNT2;
TCNT2=latency+TIMER_SHORT;
last_timer=TIMER_SHORT;
} else { // long pulse
latency=TCNT2;
TCNT2=latency+TIMER_LONG;
last_timer=TIMER_LONG;
}
}
}
// ------------------- SETUP -------------------
void setup(void) {
Serial.begin(9600);
//Set the pins for DCC to "output".
pinMode(DCC_PIN,OUTPUT); // this is for the DCC Signal
pinMode(DCC_PWM,OUTPUT); // will be kept high when power on rail is on, PWM pin
digitalWrite(DCC_PWM,LOW); // booster (LMD18200) disabled
// set LED pins to "output"
pinMode(START_LED,OUTPUT);
pinMode(MAN_LED,OUTPUT);
pinMode(OVER_LED,OUTPUT); // this is for the DCC Signal
pinMode(THERM_PIN, INPUT);
digitalWrite(THERM_PIN,1); //enable pull up
pinMode(MAN_SW,INPUT);
digitalWrite(MAN_SW,1); //enable pull up
pinMode(START_SW,INPUT);
digitalWrite(START_SW,1); //enable pull up
pinMode(dirPin1, INPUT);
digitalWrite(dirPin1, 1); //enable pull-up resistor !!
pinMode(dirPin2, INPUT);
digitalWrite(dirPin2, 1); //enable pull-up resistor !!
pinMode(SENSOR2, INPUT);
digitalWrite(SENSOR2, 1); //enable pull-up resistor
pinMode(SENSOR1, INPUT);
digitalWrite(SENSOR1, 1); //enable pull-up resistor
pinMode(PARK1, INPUT);
digitalWrite(PARK1, 1); //enable pull-up resistor
pinMode(PARK2, INPUT);
digitalWrite(PARK2, 1); //enable pull-up resistor
// AN_SPEED1, AN_SPEED2, CURRENT_PIN are INPUT by default and don't need to be initialized
//assemble first DCC messages
read_locoSpeed();
assemble_dcc_msg();
assemble_dcc_msg2();
assemble_dcc_msglight();
assemble_dcc_msg2light();
Serial.println(VERSION);
//Start the timer
SetupTimer2();
digitalWrite(OVER_LED,LOW);
}
// ------------------- LOOP -------------------
void loop(void) {
if (currentprotection) { //do nothing if not - reset is necessary to restart
meancurrent = 0;
for (int i=0; i<wait; i++) { // wait = 200
current = analogRead(CURRENT_PIN);
if (current > maxcurrent) {
maxcurrent=current;
}
meancurrent = meancurrent+current;
}
current = meancurrent/wait;
if (_debug) {
Serial.print(" Current:");
Serial.print(current);
Serial.print(" max current:");
Serial.println(maxcurrent);
}
if (current > overcurrent) {
digitalWrite(DCC_PWM,LOW); // stop booster
digitalWrite(OVER_LED,HIGH);
currentprotection = false;
}
delay(1); // why ?
//speed messure
timespeed++;
if (!digitalRead(SENSOR2) ) {
if (timespeed > hertz*4) {
int fart = (806*hertz)/timespeed;
if (_debug) {
Serial.print(" Speed: ");
Serial.print(fart);
Serial.print(" km/h ");
}
timespeed = 0;
}
}
if ( digitalRead(START_SW) && parked1 && parked2 ) {
//if START button is off and all trains are at the parking area, stop booster
digitalWrite(DCC_PWM,LOW);
}
else if ( digitalRead(START_SW) ) {
if (_debug) {
Serial.print("Start OFF " );
}
digitalWrite(START_LED,LOW);
//if switch is in OFF state
if (!digitalRead(PARK1) || parked1) {
locoSpeed = 1;
assemble_dcc_msg();
parked1= true;
}
else {
locoSpeed = loco1parkspeed;
assemble_dcc_msg();
}
if (!digitalRead(PARK2) || parked2) {
locoSpeed2 = 1;
assemble_dcc_msg2();
parked2= true;
}
else {
locoSpeed2 = loco2parkspeed;
assemble_dcc_msg2();
}
if (_debug) {
Serial.print("Stopping :" );
Serial.print(locoSpeed);
Serial.print(" :" );
Serial.print(locoSpeed2);
}
}
else { // START switch is in ON state
if (_debug) {
Serial.print("Start ON " );
}
digitalWrite(START_LED,HIGH);
digitalWrite(DCC_PWM,HIGH); // enable Booster
parked1= false;
parked2= false;
if ( digitalRead(MAN_SW) ) { // switch is manual
if (man_loco1()) {
// some reading changed -> make new dcc message
assemble_dcc_msg();
assemble_dcc_msglight();
}
if (man_loco2()) {
// some reading changed -> make new dcc message
assemble_dcc_msg2();
assemble_dcc_msg2light();
}
digitalWrite(MAN_LED,LOW);
if (_debug) {
Serial.print(" Manual :" );
Serial.print(locoSpeed);
Serial.print(" :" );
Serial.print(locoSpeed2);
Serial.print(" dir:");
Serial.print(dir);
Serial.print(" light:");
Serial.print(msg[3].data[1], BIN);
}
if (Serial.available()) {
switch (Serial.read()) {
case '#':
_debug = !_debug;
break;
default:
break;
}
}
}
else { // switch is auto
if (auto_loco2()) {
// some reading changed -> make new dcc message
assemble_dcc_msg2();
}
if (auto_loco1()) {
// some reading changed -> make new dcc message
assemble_dcc_msg();
}
digitalWrite(MAN_LED,HIGH);
if (_debug) {
Serial.print(" Auto :" );
Serial.print(locoSpeed);
Serial.print(" :" );
Serial.print(locoSpeed2);
}
}
}
}
}
// ------------------- Read Loco Speed -------------------
boolean read_locoSpeed() {
boolean changed = false;
// read the analog input into a variable:
// limit range to 0..127
locoSpeed = (127L * analogRead(AN_SPEED1))/1023;
if (locoSpeed != last_locoSpeed) {
changed = true;
last_locoSpeed = locoSpeed;
}
dir = digitalRead(dirPin1); // direction
if (dir != last_dir) {
changed = true;
last_dir = dir;
}
dir2 = digitalRead(dirPin2); // light
if (dir2 != last_dir2) {
changed = true;
last_dir2 = dir2;
}
return changed;
}
// ------------------- AUTO LOCO1 -------------------
boolean auto_loco1() {
boolean changed = false;
// read the analog input into a variable:
// limit range to 0..127
//locoSpeed = (127L * analogRead(AN_SPEED1))/1023;
if (!digitalRead(SENSOR1)) { // pre-station
if (runtime >= laptime1) { // 30 Hertz = 300
stopping = true;
}
}
if (stopping) {
locoSpeed--;
locoSpeed--;
}
else {
locoSpeed++;
runtime++;
}
if (locoSpeed >=locomaxspeed) {
locoSpeed = locomaxspeed;
}
if (locoSpeed <=0) {
locoSpeed = 0;
stoptime++;
}
if (stoptime >= STATIONTIME) {
stopping = false;
stoptime = 0;
runtime = 0;
}
if (locoSpeed == 1) { // this would result in emergency stop
locoSpeed = 2;
}
if (locoSpeed != last_locoSpeed) {
changed = true;
last_locoSpeed = locoSpeed;
}
return changed;
}
// ------------------- AUTO LOCO2 -------------------
boolean auto_loco2() {
boolean changed = false;
// read the analog input into a variable:
// limit range to 0..127
//locoSpeed = (127L * analogRead(AN_SPEED2))/1023;
if (!digitalRead(SENSOR2)) {
if (runtime2 >= laptime2) {
stopping2 = true;
}
}
if (stopping2) {
//locoSpeed2--;
locoSpeed2--;
if (locoSpeed2 == 1) {
locoSpeed2 = 0;
}
}
else {
locoSpeed2++;
runtime2++;
}
if (locoSpeed2 >=loco2maxspeed) {
locoSpeed2 = loco2maxspeed;
}
if (locoSpeed2 <=0) {
locoSpeed2 = 0;
stoptime2++;
}
if (stoptime2 >= STATIONTIME) {
stopping2 = false;
stoptime2 = 0;
runtime2 = 0;
}
if (locoSpeed2 == 1) { // this would result in emergency stop
locoSpeed2 = 2;
}
if (locoSpeed2 != last_locoSpeed2) {
changed = true;
last_locoSpeed2 = locoSpeed2;
}
return changed;
}
// ------------------- MANUAL LOCO1 -------------------
boolean man_loco1() {
boolean changed = false;
// read the analog input into a variable:
// limit range to 0..127
locoSpeed = (127L * analogRead(AN_SPEED1))/1023;
if (locoSpeed == 1) { // this would result in emergency stop
locoSpeed = 0;
}
if (locoSpeed != last_locoSpeed) {
changed = true;
last_locoSpeed = locoSpeed;
}
dir = digitalRead(dirPin1); //direction
if (dir != last_dir) {
changed = true;
last_dir = dir;
}
dir2 = digitalRead(dirPin2);
if (dir2 != last_dir2) { //light
changed = true;
last_dir2 = dir2;
}
return changed;
}
// ------------------- MANUAL LOCO2 -------------------
boolean man_loco2() {
boolean changed = false;
// read the analog input into a variable:
// limit range to 0..127
locoSpeed2 = (127L * analogRead(AN_SPEED2))/1023;
if (locoSpeed2 == 1) { // this would result in emergency stop
locoSpeed2 = 0;
}
if (locoSpeed2 != last_locoSpeed2) {
changed = true;
last_locoSpeed2 = locoSpeed2;
}
dir = digitalRead(dirPin1);
if (dir != last_dir) { // direction
changed = true;
last_dir = dir;
}
dir2 = digitalRead(dirPin2);
if (dir2 != last_dir2) { // light
changed = true;
last_dir2 = dir2;
}
return changed;
}
// ------------------- ASSEMBLE DCC MESSAGE -------------------
void assemble_dcc_msg() {
int i;
unsigned char data, xdata;
if (locoSpeed == 1) { // this would result in emergency stop
locoSpeed = 0;
}
// direction info first
if (dir) { // forward
data = 0x80;
} else {
data = 0;
}
data |= locoSpeed;
// add XOR byte
xdata = (msg[1].data[0] ^ msg[1].data[1]) ^ data;
noInterrupts(); // make sure that only "matching" parts of the message are used in ISR
msg[1].data[2] = data;
msg[1].data[3] = xdata;
interrupts();
}
// ------------------- ASSEMBLE DCC MESSAGE2 -------------------
void assemble_dcc_msg2() {
int i;
unsigned char data, xdata;
if (locoSpeed2 == 1) { // this would result in emergency stop
locoSpeed2 = 0;
}
// direction info first
if (dir) { // forward
data = 0x80;
} else {
data = 0;
}
data |= locoSpeed2;
// add XOR byte
xdata = (msg[2].data[0] ^ msg[2].data[1]) ^ data;
noInterrupts(); // make sure that only "matching" parts of the message are used in ISR
msg[2].data[2] = data;
msg[2].data[3] = xdata;
interrupts();
}
// ------------------- ASSEMBLE DCC MESSAGELIGHT -------------------
void assemble_dcc_msglight() {
int i;
unsigned char data, xdata;
if (dir2) {
data = 0x90;
} else {
data = 0x80;
}
// add XOR byte
xdata = (msg[3].data[0]) ^ data;
noInterrupts(); // make sure that only "matching" parts of the message are used in ISR
msg[3].data[1] = data;
msg[3].data[2] = xdata;
interrupts();
}
// ------------------- ASSEMBLE DCC MESSAGE2LIGHT -------------------
void assemble_dcc_msg2light() {
int i;
unsigned char data, xdata;
if (dir2) {
data = 0x90;
} else {
data = 0x80;
}
// add XOR byte
xdata = (msg[4].data[0]) ^ data;
noInterrupts(); // make sure that only "matching" parts of the message are used in ISR
msg[4].data[1] = data;
msg[4].data[2] = xdata;
interrupts();
}
---------------- fin du logiciel -----------------