Arduino Talking Defect Detector
d. bodnar revised 1-27-2015 & 2-16-2018
Questions? email info@trainelectronics.com
|
||
Introduction The primary objectives of this project are to:
|
||
Hardware The microcontroller is an Arduino Pro Mini. These small devices are available on eBay and from other sources for $5.00 or less. The MP3 player is also available from eBay for $5.00 or less. Be sure to find the module that is almost square in shape and that supports the Arduino and serial communication.
Please note: If you have intermittent operation or excessive noise make sure that you are supplying 5 volts at 0.5 amps and have installed the 1K resistors on the TX (Arduino) to RX (DFPlayer) connection. |
||
Trackside Laser Sensors The trackside sensors are composed of a small red laser and a phototransistor. Two of these sensors are needed to determine the train's speed. It is important that the lasers beam is adjusted so that it just clears the top of the rail. If it is any higher it may not properly detect just the passing wheels and incorrectly add to the count when it encounters hanging coupler parts. The holder shown here is 3D printed (files are here is SketchUp and STL format: Arduino/DefectDetector/laser_holderX4-D.skp Arduino/DefectDetector/laser_holderX4-D.stl) and holds the two lasers and sensors 1" apart.
This overhead view shows the lasers (bottom) and the phototransistors (top). If you look carefully you can see the laser beams hitting the bottom rail.
This view is from the lasers and clearly shows the alignment of the beams as they hit the tops of the rails and the phototransistors. The plastic track has been notched to help with alignment.
The phototransistors are mounted on a small piece of circuit board. The emitters (by the tab) are at the top and the collectors are at the bottom. The back of the board shows the connections to the 3 wire cable that goes to the main circuit board. I used two servo cables to connect the photo transistors and the lasers to the circuit. Here you can see the laser beam hitting each phototransistor.
The notched plastic track allows the sensor unit to fit under the track. Other accommodations for sensor placement would be needed if other track installation are used.
|
||
The Circuit The schematic is shown here. Even though the Pro Mini version of the Arduino is shown you could easily use an Uno or other Arduino.
The library that I used for the 2 line x 16 character LCD calls for the SDA and SLC lines to be connected to A5 and A4, respectively. These connections are not on the 24 pins on the edges of the Pro Mini. The circuit board that is shown below has holes for two different placements of A4 and A5.
|
||
Circuit Board A custom circuit board has been designed.
The trace between the pins needs to be cut as shown by the yellow circle. The pins that are connected by the 1K resistor are circled in red. Also note that the back of the board improperly identifies the TX and RX pins - TX is labeled Pin1 and RX is labeled TX. These pins are properly identified on the silkscreen on top of the board. |
||
Laser Alignment
Adjust the lasers so that two 1's are shown. Once that is done the routine counts up to 100 before exiting to the normal program. Here the two lasers are properly adjusted and the blurry number in the upper right (shows 59) is rapidly counting to 100 at which point it will exit. If either beam is broken before this hits 100 the count will reset to zero.
During testing I had to bend a few of the bent wires extending from the bottom of couplers as they extended below the rail. I also had some erroneous reading caused by a car pulling along a bit of my dog's hair - yes, the laser detectors are that sensitive! Anything that extends below the rail will be counted as an axle. |
||
Planned Enhancements Since real defect detectors report their findings by radio I plan on connecting the unit to a small audio transmitter and broadcasting the report to a radio receiver. |
||
Setup and Use There are four buttons that I connected to the defector detector. They are shown here on one of my prototypes. The button on top (circled in blue) is the MENU button. The one to the right is UP or ON and the one to its left is DOWN or OFF. The button in the upper left (circled in yellow) is a reset button that is optional as Arduinos typically have one on the circuit board. I found it convenient to have it on the front of the unit during development.
When the unit is powered on it displays a few identification screens then flashes MENU = SETUP! a number of times. If you press the MENU button while this is happening you will enter the setup menus. A number of options are displayed one after the other. All are controlled by the three buttons. The button to the left is labeled on the screen as either OFF or Down depending on the menu item. The button on the right is labeled ON or UP and the center button is the MENU button that moves you from one item to the next. Mile Marker - the mile marker that is spoken has a value that can range from 000.1 miles to 999.9 miles. Each of these four digits is entered individually by pressing the UP or DOWN buttons. Press MENU when done with a digit. For example, to change the hundreds digit (the 8) to a 3 press DOWN five times. Then press MENU to move to the 10's digit. Similarly you can change the 3 in the 10's place to anything from zero to nine. Next is the 1's digit.... then the 1/10ths digit. The Defects option determines the likelihood of hearing a defect reported. In this screen it is set to 50% meaning that about 1/2 of the time a defect will be reported. Change this to a smaller number to have defects reported less frequently or increase it to have them pop up more often. Set this value to zero to have no defects reported. The scale is shown as 1:48 (O-scale). Press the UP or DOWN buttons to change the scale.
If you want to have the detector silent set this option to OFF. LCD display items such as axle count will still be shown. The next few screens allow you to turn a specific part of the detector report on or off. Press ON or OFF to activate or deactivate each. Note that you do not need to press MENU after selecting ON or OFF but will automatically be moved to the next item.
These continue to include RR Name, Defect Det, Mile Marker and Axles. When done "Ready" is displayed and the detector is set to operate. Note that all of the setting that were selected are written to memory and saved.
|
||
Software The software is a work in progress. This version reports axle count, speed, length and temperature. Reports go to the LCD display, the serial monitor and the MP3 board. The program is
fairly well commented and contains a good bit of debugging code. /* d. bodnar 1-26-15 TODO: 1. OK add menu to select scale 2. add random defect reports (select %age) 3. add 4th button to activate defect reports 4. record defect announcements U P Detector Milepost 2 8 Point 4 First Hotbox East Rail Axle 2 From End Of Train Temperature 7 3 Degrees Detector Out Hot Box on Axle 47, first hot box east side axle 23 second hot box west side axle 12 */ #include<EEPROM.h> #include <SoftwareSerial.h> #include <DFPlayer_Mini_Mp3.h> #include <Wire.h> #include <LCD.h> #include <LiquidCrystal_I2C.h> #include <OneWire.h> #include <DallasTemperature.h> #define I2C_ADDR 0x27 // <<----- Add your address here. Find it from I2C Scanner #define BACKLIGHT_PIN 3 #define En_pin 2 #define Rw_pin 1 #define Rs_pin 0 #define D4_pin 4 #define D5_pin 5 #define D6_pin 6 #define D7_pin 7 #define ONE_WIRE_BUS 2 #define TEMPERATURE_PRECISION 9 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); int numberOfDevices; // Number of temperature devices found DeviceAddress tempDeviceAddress; // We'll use this variable to store a found device address int value = 0; // float xtemp=0; byte buffer[10]; LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin); char buf[12]; const int buttonPin = 3; // the number of the pushbutton pin const int buttonPin2 = 4; // second button pin const int menuPin = A3; // analog pin to Pot 1 const int ledPin = 13; // the number of the LED pin int buttonState = 0; // variable for reading the pushbutton status int buttonState2 = 0; // same for 2nd button int buttonStateTemp = 0; // variable to temporarily remember which button was hit first int buttonState2Temp = 0; // same for 2nd button int buttonStateMenuTemp=0; int buttonStateMenu = 0; // same for menu button on A3 int buusyPin = 10;// buusyPin = 10; // sound player busy int bsy = 0; int z=0; int PTState; int Laser = 11; // laser control int PhotoTransistor = 9; int PTState2; int Laser2 = 12; // laser control int PhotoTransistor2 = 8; int PTFlag = 0; int PTFlag2 = 0; int axleCount = 0; int axleFlag = 0; long tempF=0; char tempFString(12); String thisString(12); unsigned long time=0; unsigned long axleTime=0; // time between axle hits - also used to compute train legth int reportedTime=0; unsigned long saveSpeed=0; int mute=0; // =0 if talking, =1 if muted int sayTemperature=0; // =0 if speaking temperature, =1 if muted int sayMileMarker=0; // =0 if speaking mile marker, =1 if muted int sayLength=0; // =0 if speaking lentgh, =1 if muted int saySpeed=0; // =0 if speaking speed, =1 if muted int sayAxles=0; // =0 if speaking axles, =1 if muted int sayRR=0; // =0 if speaking RR, =1 if muted int sayDD=0; // =0 if speaking Defect Detector, =1 if muted int mileMarkerW = 743; //NOTE - this is the whole # portion int mileMarkerWh=0; // high byte of mile marker whole number int mileMarkerWl=0; // low byte of mile marker whole number int mileMarkerD = 9; // NOTE - this is the decimal portion - one digit int hDigit = 0; // hundreds digit int tDigit = 0; // tens digit int oDigit = 0; // ones digit unsigned long mph = 0; // speed in MPH int scale = 96; int defectOnOff=0; // report defects- 0=never otherwise a percentage to 100 int defectFlag=0; // do defect or not based on comparison with defect ratio unsigned long mphScale = 0; unsigned long speedConstant = 361054347; // adjust to fine tune speed unsigned long lengthTime = 0; unsigned long trainLength = 0; unsigned long lengthStartTime; //***************************************SETUP************************************* void setup () { pinMode(Laser, OUTPUT); pinMode(PhotoTransistor, INPUT); pinMode(Laser2, OUTPUT); pinMode(PhotoTransistor2, INPUT); pinMode(buusyPin, INPUT); pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); pinMode(buttonPin2, INPUT); pinMode(menuPin, INPUT); Serial.begin (9600); lcd.begin (16,2); // LCD is 16x2 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);// Switch on the backlight lcd.setBacklight(HIGH); lcd.home (); // go home lcd.print(" MP3 Player"); delay(200); lcd.setCursor(0,1); // start of line 2 lcd.print(" Version 8.8 "); delay ( 500); mp3_set_serial (Serial); //set Serial for DFPlayer-mini mp3 module mp3_reset(); mp3_set_volume (30); // does not appear to make any difference mp3_reset(); delay (400); readEEPROM(); lcd.home (); // go home lcd.clear(); lcd.print("trainelectronics"); lcd.setCursor(0,1); lcd.print(" .com"); delay(1400); lcd.clear(); lcd.setCursor(0,1); lcd.print(" 2015 d. bodnar"); delay ( 1500); //Menu on power up if button held down for (int xxx=0; xxx < 10;xxx++){ buttonStateMenu = digitalRead(menuPin); delay(200); lcd.clear(); lcd.setCursor(0,(xxx % 2)) ; // switch cursor from line 0 to 1 based on xx being odd/even lcd.print(" MENU = Setup!"); if(buttonStateMenu==1){ // 1 = pressed, 0= not pressed Serial.println("MENU!!!"); readEEPROM(); // scale=96; // set once - REMOVE! lcd.clear(); lcd.print("MENU!"); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); mainMenu(); } } sensors.begin(); numberOfDevices = sensors.getDeviceCount(); if (sensors.isParasitePowerMode()) Serial.println("ON"); else Serial.println("OFF"); for(int i=0;i<numberOfDevices; i++) { if(sensors.getAddress(tempDeviceAddress, i)) { printAddress(tempDeviceAddress); sensors.setResolution(tempDeviceAddress, TEMPERATURE_PRECISION); } else{ } } digitalWrite(Laser, HIGH); digitalWrite(Laser2, HIGH); sensors.requestTemperatures(); // Send the command to get temperatures printTemperature(tempDeviceAddress); // Use a simple function to print out the data alignLasers(); // go to routine to align lasers if either is blocked at startup lcd.clear(); lcd.print("Ready"); } //.......................................LOOP................................................ void loop () { // this section computes time between first sensor hit and first hit on other sensor - to compute speed PTState = digitalRead(PhotoTransistor); PTState2 = digitalRead(PhotoTransistor2); // which is hit first? if (PTState == 0 && PTFlag == 0 && PTFlag2 == 0){ //1st sensor hit for the first time PTFlag =1; time = micros(); lengthStartTime= micros(); digitalWrite(ledPin, HIGH); // connect to beeper lcd.clear(); lcd.print("Axle Count = "); axleCount=1; lcd.setCursor(12,0); lcd.print(axleCount); axleFlag=1; // use this sensor to count axles do { delay(1); // wait for sensors to stabilize PTState = digitalRead(PhotoTransistor); } while (PTState==0); //stay here till axle clears digitalWrite(ledPin, LOW); // turn off beeper } else if(PTState2==0 && PTFlag2 == 0 && PTFlag == 0){ //2nd sensor hit for the first time PTFlag2=1; time = micros(); lengthStartTime= micros(); digitalWrite(ledPin, HIGH);// connect to beeper lcd.clear(); lcd.print("Axle Count="); axleCount=1; lcd.setCursor(12,0); lcd.print(axleCount); axleFlag=2; // use this sensor to count axles do { delay(1); // wait for sensors to stabilize PTState2 = digitalRead(PhotoTransistor2); } while (PTState2==0); //stay here till axle clears digitalWrite(ledPin, LOW); // turn off beeper } // use first hit on 2nd sensor to compute speed if(PTState ==0 && PTFlag2==1 && reportedTime==0){ saveSpeed = micros() - time; digitalWrite(Laser, LOW); //digitalWrite(Laser2, LOW); reportedTime=1;// indicates that both sensors have been hit & its OK to finalize an axle count } else if(PTState2 == 0 && PTFlag ==1 && reportedTime==0){ saveSpeed = micros() - time; // digitalWrite(Laser, LOW); digitalWrite(Laser2, LOW); reportedTime=1;// indicates that both sensors have been hit & its OK to finalize an axle count } // Routines to count axles if (axleFlag==1 && PTState ==0 ){ digitalWrite(ledPin, HIGH);// connect to beeper axleCount = axleCount++; axleTime = micros();// reset time after a hit on an axle lcd.setCursor(12,0); lcd.print(axleCount); do { delay(5); // wait for sensors to stabilize PTState = digitalRead(PhotoTransistor); } while (PTState==0); //stay here till axle clears digitalWrite(ledPin, LOW); // turn off beeper } if (axleFlag==2 && PTState2 ==0 ){ digitalWrite(ledPin, HIGH);// connect to beeper axleCount = axleCount++; axleTime = micros();// reset time after a hit on an axle lcd.setCursor(12,0); lcd.print(axleCount); do { delay(5); // wait for sensors to stabilize PTState2 = digitalRead(PhotoTransistor2); } while (PTState2==0); //stay here till axle clears digitalWrite(ledPin, LOW); // turn off beeper } // Finalize axle count after sufficient time has passed if ( reportedTime==1 && axleTime >= 1 && ( micros()-axleTime) >= 4000000){//4 sec & no axle hit =DONE! computeSpeed(); // in scale MPH speakRR(); // railroad name speakMM(); // Mile Marker speakDefects(); // Defect report speakAxles(); // # of axles speakLength(); // length of train speakSpeed(); // speed of train speakTemperature(); // ambient temperature speakOut(); // say "out" axleTime=0; //reset variables after train passes PTFlag=0; //reset variables after train passes PTFlag2=0; //reset variables after train passes axleCount=0; //reset variables after train passes reportedTime=0; //reset variables after train passes digitalWrite(Laser, HIGH); digitalWrite(Laser2, HIGH); } } //...................................... END LOOP ........................................ // DO RAILROAD NAME & DEFECT DETECTOR void speakRR(){ // delay(1000); lcd.clear(); lcd.print("Axles "); lcd.print(axleCount); if (mute ==0 && sayRR==0){ mp3_play(16); // rr name..........................1 dlayPrint(); delay(200); } if (mute ==0 && sayDD==0){ mp3_play(17); // defect detector..........................2 dlayPrint(); delay(200); } } // DO MILE MARKER 25..........................3 void speakMM(){ if (mute ==0 && sayMileMarker==0){ mp3_play(25); // mile marker dlayPrint(); delay(200); } // DO MILE MARKER number..........................4 thisString =itoa(mileMarkerW, buf,10); if (mute ==0 && sayMileMarker==0){ sayText(); mp3_play(12); // point delay(200); dlayPrint(); delay(200); thisString = itoa(mileMarkerD,buf,10); sayText(); } } // DO DEFECTS 18..........................5 void speakDefects(){ if (mute ==0){ int rndDefectReport= random(1,100); // compare this to ratio to decide on reporting defect if (rndDefectReport <= defectOnOff){ defectFlag =1; } else{ defectFlag = 0; } if(defectOnOff==0 || defectFlag == 0){ mp3_play(18); // no defects delay(1200); dlayPrint(); delay(200); } else{ // 28=North, 29=South, 30=East, 31=West randomSeed(analogRead(0)); int whichDirection = random(1,4); whichDirection = 27 + whichDirection; // choose n, s, e or west mp3_play(whichDirection); delay(1200); dlayPrint(); delay(200); long rndAxle= random(1,axleCount); mp3_play(rndAxle); delay(200); dlayPrint(); delay(200); } } } // do axle report..........................6 void speakAxles(){ if (mute ==0&& sayAxles==0){ thisString = itoa((axleCount), buf, 10); delay(200); mp3_play(22); // 22 axles dlayPrint(); delay(200); sayText(); } } //say length ..........................7 void speakLength(){ computeLength(); if (mute ==0&& sayLength==0){ delay(200); mp3_play(23); // 23 length dlayPrint(); delay(200); } if (mute ==0&& sayLength==0){ thisString = itoa((trainLength/10000), buf, 10); sayText(); delay(400); } } // do speed report..........................8 void speakSpeed(){ if (mute ==0&& saySpeed==0){ thisString = itoa(mphScale/10, buf, 10);//saveSpeed/10), buf, 10); delay(200); mp3_play(24); // speed dlayPrint(); delay(200); sayText(); } } // do temperature report ..........................9 void speakTemperature(){ if (mute ==0&& sayTemperature==0){ Serial.print("Requesting temperatures..."); sensors.requestTemperatures(); // Send the command to get temperatures Serial.println("DONE"); for(int i=0;i<numberOfDevices; i++) { if(sensors.getAddress(tempDeviceAddress, i)) { Serial.print("Temperature for device: "); Serial.println(i,DEC); printTemperature(tempDeviceAddress); // Use a simple function to print out the data } } int xtemp2 = xtemp * 100; int xtempWhole = xtemp2 / 100; int xtempDecimal = xtemp2 - (xtempWhole * 100); Serial.print(xtempWhole); // Converts tempC to Fahrenheit Serial.print("."); Serial.println(xtempDecimal); String tempFStringW = itoa((xtempWhole), buf, 10); // whole number portion String tempFStringD = itoa((xtempDecimal), buf, 10); // decimal portion // thisString = tempFStringW + "." + tempFStringD; //use this line for 1/100 deg resolution thisString = tempFStringW;// + "." + tempFStringD; //use this line for whole # degrees only(no decimal) delay(1200); Serial.println(thisString); // Converts tempC to Fahrenheit Serial.println(thisString); mp3_play(19); // 19=temperature dlayPrint(); delay(200); sayText(); // speak temperature with or without decmial degrees (see above comments) mp3_play(13); //degrees..........................10 dlayPrint(); // mp3_play(14); //fahrenheit- add these 3 lines to say "Fahrenheit" // dlayPrint(); // delay(800); } } void speakOut(){ mp3_play(20); //out..........................12 dlayPrint(); } void computeSpeed(){ mph = speedConstant / saveSpeed; //speed in real MPH mph = mph * 10; mph=mph/63; mphScale = (mph*scale); mphScale = mphScale +50; mphScale = mphScale /100; // round up } void computeLength(){ lengthTime = axleTime -lengthStartTime; trainLength = mphScale *88; // convert mph to fps (mph x 1.4666666 = fps) trainLength = trainLength / 60; trainLength = trainLength * (lengthTime / 1000); lcd.setCursor(8,0); lcd.print(" L= "); lcd.print(trainLength/10000); lcd.setCursor(0,1); lcd.print("Scale Spd= "); lcd.print(mphScale/10); } // routine to stay here till busy pin goes low once then goes high after speech item completes void dlayPrint() { int bsyflag=0; Serial.println(" "); Serial.print("buusypin "); for( z=0; z<=300 ; z++){ // was 300 bsy = digitalRead(buusyPin); Serial.print(bsy); delay(20); if (bsyflag==1 && bsy==1){ break; } if(bsy==0){ bsyflag=1; } } Serial.println(" "); Serial.println("done"); } // function to print a device address void printAddress(DeviceAddress deviceAddress) // NEEDED????????????????????????????????????????????? { for (uint8_t i = 0; i < 8; i++) { if (deviceAddress[i] < 16) Serial.print("0"); Serial.print(deviceAddress[i], HEX); } } // function to print the temperature for a device void printTemperature(DeviceAddress deviceAddress) { float tempC = sensors.getTempC(deviceAddress); xtemp = DallasTemperature::toFahrenheit(tempC); lcd.setCursor (9,0); lcd.print("t="); lcd.print(xtemp); } // routine to go into alignment if either is blocked on boot void alignLasers(){ PTState = digitalRead(PhotoTransistor); PTState2 = digitalRead(PhotoTransistor2); if (PTState == 0 || PTState2 ==0){ lcd.clear(); for (int xx=0; xx <= 100; xx++){ // 100 is delay to stay on test till good for 100 loops lcd.setCursor(0,0); // start of line 1f lcd.print("Laser Align "); lcd.setCursor(12,0); lcd.print(xx); delay(50); do{ PTState = digitalRead(PhotoTransistor); PTState2 = digitalRead(PhotoTransistor2); lcd.setCursor(0,1); lcd.print(PTState); lcd.print(" "); lcd.print(PTState2); if (PTState==0 || PTState2==0){ xx=0; lcd.setCursor(12,0); lcd.print(" "); } } while (PTState==0 || PTState2==0); //stay here till both axles clear } } } void sayText() // Say number with decimal { if (mute ==1){ Serial.println("SKIPPING!!!"); } else { int lgth = thisString.length(); for (int i=0; i <= lgth-1; i++){ int say = (thisString.substring(i,i+1)).toInt(); Serial.println(thisString.substring(i,i+1)); String isDecPoint=(thisString.substring(i,i+1)); Serial.print("saying "); Serial.print(isDecPoint ); delay(100); // needed to prevent audio "pops" at start if(isDecPoint=="."){ delay(200); mp3_play(12); delay(200); } else if (say == 0 ){ mp3_play(10); //zero } else { mp3_play(say); } dlayPrint();// delay(1200); lcd.setCursor(0,1); } } } //Main setup menu routine.................................... // left button is Down and No, right is Up and Yes void mainMenu(){ getMileMarker(); lcd.clear(); lcd.print("Setup Menu"); lcd.setCursor(0,1); lcd.print("UP & DOWN to Set"); delay(2000); getDefectOnOff(); getScale(); // Do Sound on/off =0 if talking, =1 if muted showOFFON(); lcd.print("Sound = "); lcd.setCursor(13,1); if(mute==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); if (buttonStateTemp == 0){ //0=pressed button mute = 0; // 0= talking 1=mute lcd.print("ON "); } else { mute = 1; lcd.print("OFF"); } delay(500); // Do Temperature on/off =0 if speaking temperature, =1 if muted showOFFON(); lcd.print("Temperature = "); lcd.setCursor(13,1); if(sayTemperature==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); if (buttonStateTemp == 0){ //0=pressed button sayTemperature = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayTemperature = 1; lcd.print("OFF"); } delay(500); // Do Length on/off =0 if speaking length, =1 if muted showOFFON(); lcd.print("Length = "); lcd.setCursor(13,1); if(sayLength==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); if (buttonStateTemp == 0){ //0=pressed button sayLength = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayLength = 1; lcd.print("OFF"); } delay(500); // Do Speed on/off =0 if speaking speed, =1 if muted showOFFON(); lcd.print("Speed = "); lcd.setCursor(13,1); if(saySpeed==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); ; if (buttonStateTemp == 0){ //0=pressed button saySpeed = 0; // 0= talking 1=mute lcd.print("ON "); } else { saySpeed = 1; lcd.print("OFF"); } delay(500); // Do RR name on/off =0 if speaking RR, =1 if muted showOFFON(); lcd.print("RR Name = "); lcd.setCursor(13,1); if(sayRR==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); ; if (buttonStateTemp == 0){ //0=pressed button sayRR = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayRR = 1; lcd.print("OFF"); } delay(500); // Do Defect Detector on/off =0 if speaking RR, =1 if muted showOFFON(); lcd.print("Defect Det = "); lcd.setCursor(13,1); if(sayDD==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); ; if (buttonStateTemp == 0){ //0=pressed button sayDD = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayDD = 1; lcd.print("OFF"); } delay(500); // Do mile marker on/off =0 if speaking milemarker, =1 if muted showOFFON(); lcd.print("Mile Marker = "); lcd.setCursor(13,1); if(sayMileMarker==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); ; if (buttonStateTemp == 0){ //0=pressed button sayMileMarker = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayMileMarker = 1; lcd.print("OFF"); } delay(500); // Do axles on/off =0 if speaking axles, =1 if muted showOFFON(); lcd.print("Axles = "); lcd.setCursor(13,1); if(sayAxles==0){ lcd.print("ON "); } else { lcd.print("OFF"); } getButtonPress(); lcd.setCursor(13,1); ; if (buttonStateTemp == 0){ //0=pressed button sayAxles = 0; // 0= talking 1=mute lcd.print("ON "); } else { sayAxles = 1; lcd.print("OFF"); } delay(500); writeEEPROM(); } void getDigitValues(){ Serial.print("mileMarkerW "); Serial.println(mileMarkerW); hDigit = mileMarkerW / 100; // hundreds digit tDigit = mileMarkerW - (100*hDigit); tDigit = tDigit / 10; // tens digit oDigit = mileMarkerW -hDigit*100 - tDigit*10; // ones digit Serial.print("parsed = "); Serial.print(hDigit); Serial.print(tDigit); Serial.println(oDigit); } // set defect on/off and %age ................................................................................ void getDefectOnOff(){ do { lcd.clear(); lcd.setCursor(0,1); lcd.print("Defects = "); lcd.print(defectOnOff ); lcd.setCursor(0,0); lcd.print("DOWN UP"); getButtonPress(); if (buttonStateTemp==1) { if(defectOnOff != 0){ defectOnOff =( defectOnOff-1); } } else if (buttonState2Temp==1){ if(defectOnOff <=99){ defectOnOff =( defectOnOff+1); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); writeEEPROM(); } void getScale(){ // use UP DOWN to increment / decrement 0-250 range do { lcd.clear(); lcd.setCursor(0,1); lcd.print("Scale = "); lcd.print(scale ); lcd.setCursor(0,0); lcd.print("DOWN UP"); getButtonPress(); if (buttonStateTemp==1) { if(scale != 0){ scale =( scale-1); } } else if (buttonState2Temp==1){ if(scale <=249){ scale =( scale+1); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); writeEEPROM(); } void getMileMarker(){ // 100's ..................................... do { lcd.clear(); lcd.setCursor(0,1); lcd.print("MileMarker="); thisString=itoa(mileMarkerW, buf,10);// + "." );// + itoa(mileMarkerD,buf,10)); thisString = thisString + "."; thisString = thisString + itoa(mileMarkerD,buf, 10); lcd.print(thisString );//+ "."+mileMarkerD); lcd.setCursor(0,0); lcd.print("DOWN 100's UP"); getButtonPress(); getDigitValues(); if (buttonStateTemp==1) { if(hDigit != 0){ mileMarkerW =( mileMarkerW-100); } } else if (buttonState2Temp==1){ if(hDigit !=9){ mileMarkerW =( mileMarkerW+100); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); // 10's ..................................... do { lcd.clear(); lcd.setCursor(0,1); lcd.print("MileMarker="); thisString=itoa(mileMarkerW, buf,10);// + "." );// + itoa(mileMarkerD,buf,10)); thisString = thisString + "."; thisString = thisString + itoa(mileMarkerD,buf, 10); lcd.print(thisString );//+ "."+mileMarkerD); lcd.setCursor(0,0); lcd.print("DOWN 10's UP"); getButtonPress(); getDigitValues(); if (buttonStateTemp==1) { if(tDigit != 0){ mileMarkerW =( mileMarkerW-10 ); } } else if (buttonState2Temp==1){ if(tDigit !=9){ mileMarkerW =( mileMarkerW+10 ); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); // 1's ..................................... do { lcd.clear(); lcd.setCursor(0,1); lcd.print("MileMarker="); thisString=itoa(mileMarkerW, buf,10);// + "." );// + itoa(mileMarkerD,buf,10)); thisString = thisString + "."; thisString = thisString + itoa(mileMarkerD,buf, 10); lcd.print(thisString );//+ "."+mileMarkerD); lcd.setCursor(0,0); lcd.print("DOWN 1's UP"); getButtonPress(); getDigitValues(); if (buttonStateTemp==1) { if(oDigit != 0){ mileMarkerW =( mileMarkerW-1 ); } } else if (buttonState2Temp==1){ if(oDigit !=9){ mileMarkerW =( mileMarkerW+1 ); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); // 1/10's ..................................... do { lcd.clear(); lcd.setCursor(0,1); lcd.print("MileMarker="); thisString=itoa(mileMarkerW, buf,10);// + "." );// + itoa(mileMarkerD,buf,10)); thisString = thisString + "."; thisString = thisString + itoa(mileMarkerD,buf, 10); lcd.print(thisString );//+ "."+mileMarkerD); lcd.setCursor(0,0); lcd.print("DOWN 1/10ths UP"); getButtonPress(); if (buttonStateTemp==1) { if(mileMarkerD != 0){ mileMarkerD =( mileMarkerD-1 ); } } else if (buttonState2Temp==1){ if(mileMarkerD != 9){ mileMarkerD =(mileMarkerD+1 ); } } } while (digitalRead(menuPin)==0); do { buttonStateMenuTemp = (digitalRead(menuPin));// stay here till button released } while (buttonStateMenuTemp==1); Serial.print("EEPROM-W2 "); Serial.println(mileMarkerW); Serial.print("EEPROM-D2 "); Serial.println(mileMarkerD); writeEEPROM(); } // routine to show OFF or ON or UP or DOWN on LCD void showOFFON(){ lcd.clear(); lcd.print("OFF ON"); lcd.setCursor(0,1); } // Routine to get button press events from left and right buttons and Menu button void getButtonPress(){ //stay here till button or button 1 pressed - returns buttonState or buttonState2 = 0 do{ buttonState = digitalRead(buttonPin); buttonState2 = digitalRead(buttonPin2); buttonStateMenu = digitalRead(menuPin); } while (buttonState==0 && buttonState2==0 && buttonStateMenu==0); //stay here till one is pressed buttonStateTemp=buttonState; buttonState2Temp=buttonState2; buttonStateMenuTemp=buttonStateMenu; do{ buttonState = digitalRead(buttonPin); buttonState2 = digitalRead(buttonPin2); buttonStateMenu=digitalRead(menuPin); } while (buttonState==1 || buttonState2==1 || buttonStateMenu==1); //stay here till all released lcd.setCursor(0,0); lcd.print(" "); } void writeEEPROM(){ EEPROM.write(0,sayTemperature ); EEPROM.write(1,sayMileMarker); EEPROM.write(2,sayLength); EEPROM.write(3,saySpeed); EEPROM.write(4,sayAxles); EEPROM.write(5,sayRR); EEPROM.write(6,sayDD); mileMarkerWh = mileMarkerW/256; mileMarkerWl = mileMarkerW -mileMarkerWh*256; EEPROM.write(7,mileMarkerWh); // whole # part of mile marker EEPROM.write(8,mileMarkerWl); // 1 digit decimal part of mile marker EEPROM.write(9,mileMarkerD); // 1 digit decimal part of mile marker EEPROM.write(10,scale); // scale from 0 to 250 EEPROM.write(11,defectOnOff); // =0 if never says defects otherwise a percentage to 100 } void readEEPROM(){ sayTemperature = EEPROM.read(0); sayMileMarker = EEPROM.read(1); sayLength = EEPROM.read(2); saySpeed = EEPROM.read(3); sayAxles = EEPROM.read(4); sayRR=EEPROM.read(5); sayDD=EEPROM.read(6); mileMarkerWh=EEPROM.read(7);// whole # part of mile marker - upper mileMarkerWl=EEPROM.read(8); // whole # part of mile marker - lower mileMarkerD=EEPROM.read(9); // 1 digit decimal part of mile marker mileMarkerW = mileMarkerWh *256 + mileMarkerWl; scale=EEPROM.read(10); defectOnOff=EEPROM.read(11); Serial.print("EEPROM-temp "); Serial.println(sayTemperature); Serial.print("EEPROM-mm "); Serial.println(sayMileMarker); Serial.print("EEPROM-len "); Serial.println(sayLength); Serial.print("EEPROM-sp "); Serial.println(saySpeed); Serial.print("EEPROM-ax "); Serial.println(sayAxles); Serial.print("EEPROM-rr "); Serial.println(sayRR); Serial.print("EEPROM-dd "); Serial.println(sayDD); Serial.print("EEPROM-w "); Serial.println(mileMarkerW); Serial.print("EEPROM-d "); Serial.println(mileMarkerD); } |
||
Software Notes: I needed to convert floating point values (such as mile marker) to strings to "say" them - this code helped with that conversion: Converting a floating point value to a string |
||
Audio Files The MP3 files were created in Audacity (see: http://audacity.sourceforge.net/ ) - some information on using Audacity is here: http://www.trainelectronics.com/MP3_project/#Audacity_-_Sound_Editor ). While it is out of the scope of this discussion to go into great detail about using Audacity some comments are in order. I recorded all of the sounds that I needed by making one large file (shown here). Then I individually saved each one with an appropriate name. There the number 9 segment of the file is highlighted and I used Export Selection to save it with the name "nine.mp3"
No properties need be changed. Just hit ENTER on this screen.
The audio files must be in a directory called /mp3 and named with 4 digits from 0001.mp3 through 0025.mp3 See: http://www.trainelectronics.com/Arduino/MP3Sound/TalkingTemperature/#Program_to_test_audio_files for a testing routine to verify that your sound unit and files work properly.
|
||
The 25 audio files on my micro SD card are:: 1 - one 2 - two 3 - three 4 - four 5 - five 6 - six 7 - seven 8 - eight 9 - nine 10 - zero 11 - decimal 12 - point 13 - degrees 14 - fahrenheit 15 - celsius 16 - CSX (railroad name) 17 - Defect Detector 18 - no defects 19 - temperature 20 - out 21 - defect axle # ?? 22 - axles... 23 - train length... 24 - speed... 25 - milepost |
||
"RadioActive" Defect Detector I have experimented with several inexpensive FM transmitters like the one shown below. Each of these units is designed to connect an MP3 player or cell phone to a car's FM radio so that you can listen through the car's speakers. This device was purchased from eBay for less than $5.00, delivered. See: eBay Search
The transmitter runs on either two AAA cells or a 12 volt cigarette lighter adapter that is included. A slide switch allows you to select from four different transmitting frequencies so that a locally used station can be avoided. I found that that the frequencies were a bit off. This can be dealt with by tuning the scanner / radio receiver up and down the band or by adjusting the small coil inside of the transmitter. It is circled below. Make sure you adjust it with a non-metallic wand. There is even a hole behind the label that can be used to make this adjustment with the case closed. The range is not terrific but should be more than enough for many model railroad layouts. When properly adjusted I was picking up 30-50 feet away.
Connecting to the Defect Detector Stripping the insulation reveals a braided lead and two insulated leads, one white and one red. These leads were connected to a two pin plug. The red and white wires are connected together and terminate in a 1K resistor. The braid goes to the other lead. This connector plug into the two terminals on the MP3 player that are line output (pins 4 or 5- labeled DAC_R and DAC_L) and ground. The 1K resistor can be increased to 10 or 100K should the audio be too strong and distorted. Here is my test setup. The speaker is plugged into the speaker terminals (yellow wires) and the transmitter goes to the DAC_L/DAC_R terminals and ground. |