Let me start off with saying coding is most definitely my weak link and this project has me scratching my head a bit.
We have a Pinewood Derby track at work that is used a once a year. A event looked forward to by all. Long story short I have inherited the care and maintenance of the track which was originally was built by others. When it was handed to me a few years ago the computer was missing. I quickly figured out the timer is Arduino Uno based and communicates with a PC via Processing 3, developed by Bill V. at Minimal Hardware Designs.
https://billvsderbytimers.weebly.com/minimal-hw-designs.html
The issue now is we use the track in more of a drag racing style than the Boy Scout style it currently is. I remember that Bill V"s site also offered drag racing timers. The one I'm interested in is the one for RC car and could very easily modified to suit our needs. It uses an Arduino Mega, has all the bells and whistles we wish to have. The program provides the outputs for a drag racing tree, the reaction time/separate start. Via Processing 3 it will display status on the computer screen same as the Boy Scout timer (which we also connect to an LCD projector) and you can even connect a printer for time slips! Very cool! All that, but it is missing one feature, an end of track display, where everyone's eyes will be as the race ends.
My first thought was that I can add this via I2C LED displays and a couple of standard LED's to show the winning lane. Simple Right? it probably would be, the issue is the Processing software is connected via the USB port which is also the only I2C port the Arduino Mega has.
Any ideas anyone?
As the INO file is quite long, so I have added it as a attachment
Thanks,
Steve
The winner of the race is indicated by blinking the green "GO" light for the winning lane on the Xmas tree lights at the start of the track (I assume they're at the track start). The winner light blinks for 15 seconds (you can adjust the blink time). The PC software should show the race times.
Mirror the "GO" lights at the end of the track.
Lane 1 is pin 32
Lane 2 is pin 33
The one who has the most fun, wins!
Yes that would be easy enough. I also wish to display, for each lane, the elapsed time at the end of the track and maybe the miles per hour with an LED win light on top. I have used the I2C displays from Spark Fun for other projects. They are very easy to use and work great and I still have some.
https://www.sparkfun.com/products/16916
The major issue with that is the Processing 3 is hogging up the serial port that is also the I2C port.
OK. The code currently uses the PC program (the Processing 3 program) for all display. The Ardunio sketch is a sensor. That's a change in the circuit from sensor to sensor + display. Fine.
I don't know enough about Arduino Mega I2C or any I2C conflict with the serial port to comment on that. SparkFun doesn't address how to handle the Serial port conflict?
You might consider adding a second serial port?
Use Multiple Serial Ports on the Arduino Mega | Arduino Documentation
Then change the sketch to use that port instead of Serial and free the Serial port for SparkFun.
The one who has the most fun, wins!
Hi @stevenr8062,
RE: it probably would be, the issue is the Processing software is connected via the USB port which is also the only I2C port the Arduino Mega has.
It is probably just me getting confused, but the two different schematics I found for the Arduino Mega at
https://content.arduino.cc/assets/MEGA2560_Rev3e_sch.pdf
and
https://www.arduino.cc/en/uploads/Main/arduino-mega-schematic.pdf
seem to show the USB finds its way to 2560 chip pins 2 & 3 of the 2560, also known as PE0 and PE1
I2C uses D20 D21, according to https://docs.arduino.cc/learn/communication/wire/
which according to the schematic are the chip pins 43 and 44, also labelled PD0 and PD1, and SDA and SCL
------------------
Perhaps the USB port you have in mind is not the one on the Mega board, or some other confusion.
Sorry, if it is a red herring, but I am hoping it is helpful.
Best wishes, Dave
@stevenr8062 How is the serial port also the I2C port? Totally different hardware!
First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, 360, fairly knowledge in PC plus numerous MPU's & MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.
Ok perhaps I'm not seeing the problem clearly. So as mentioned early on programing is my weak link. So I did do some research and must have misunderstood the connections. So I guess the question how do I keep the data going were it should go when both use the serial monitor and the serial begin command? Second what pins do I connect the new I2C devices? Below is the communication portion of the program.
Opps! I mean to say: Below is the communication setup portion of the program.
After thought... I guess should also mention the Spark Fun I2C calls the print() function to display messages. So if I add the print() for displays, will it confuse the communication to the computer? I guess this the part that I don't understand.
Below is the first part of the main loop dealing with serial monitor.
//======================================== MAIN LOOP ============================================
void loop() {
//CHECK & PROCESS INCOMING SERIAL DATA
if (Serial.available() > 0) { //Check if serial data
serial_data = Serial.readStringUntil('\n'); //And get it if so
Serial.flush();
if (serial_data.indexOf('R') >= 0) { //Check for Reset request from PC
timer_reset(); //Call subroutine to reset the timer
}
if (serial_data.indexOf('S') >= 0) { //Check for race start request from PC
if (trackstate == Staged) { //Check if vehicles are staged
Set_Dialin_Delays(); //Call subroutine to set dial-in delays
trackstate = Countdown; //Set race state to Countdown mode
if (Lane_Usage==1 || Lane_Usage==3) Ln1_State = Countdown; //Set Lane1 state to Countdown state if used
if (Lane_Usage==2 || Lane_Usage==3) Ln2_State = Countdown; //Set Lane2 state to Countdown state if used
Serial.println(SMsg3); //Send Countdown state message to PC
}
}
if (serial_data.indexOf('H') >= 0) { //Check for hold request from PC
Hold_Flag = true; //Set Hold flag
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf('C') >= 0) { //Check for continue request from PC
Hold_Flag = false; //Clear Hold flag
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("LT") >= 0) { //Check for Xmas tree lamp test request from PC
Lamptest(); //Call Xmas Tree Lamp Test subroutine
Serial.println("@"); //Send Acknowledge message to PC
}
if (serial_data.indexOf("M0") >= 0) { //Check for Xmas Tree "Std" mode request from PC
XTmode = 0; //Set Xmas Tree to Std Mode if true
Serial.println("@"); //Send Acknowledge message to PC
}
if (serial_data.indexOf("M1") >= 0) { //Check for Xmas Tree "Pro" mode request from PC
XTmode = 1; //Set Xmas Tree to Std Mode if true
Serial.println("@"); //Send Acknowledge message to PC
}
if (serial_data.indexOf("M2") >= 0) { //Check for Xmas Tree "Outlaw" mode request from PC
XTmode = 2; //Set Xmas Tree to Std Mode if true
Serial.println("@"); //Send Acknowledge message to PC
}
if (serial_data.indexOf("A1") >= 0) { //Check for timer auto-start request from PC
Autostart_Flag = true; //Set timer to Auto-start mode
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("A0") >= 0) { //Check for timer manual-start request from PC
Autostart_Flag = false; //Set timer to Manual-start mode
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("P1") >= 0) { //Check for single racer on Lane1 request from PC
Lane_Usage = 1; //Set to single racer on Lane 1 mode
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("P2") >= 0) { //Check for single racer on Lane2 request from PC
Lane_Usage = 2; //Set to single racer on Lane 2 mode
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("P3") >= 0) { //Check for dual racer request from PC
Lane_Usage = 3; //Set to dual racer mode
Serial.println("@"); //Send acknowledge message to PC
}
if (serial_data.indexOf("D1:") >= 0) { //Check for receipt of Lane1 dial-in time from PC
//Input string format: D1:xxxxx where xxxxx is Lane1 dial-in time in milliseconds
//Valid range: 00000 - 09999 (0.0 to 9.999 seconds)
if (serial_data.length() == 8) { //Check integrity of input string
Str1 = serial_data.substring(3, 8); //Extract Lane1 dial-in time
Dialin1 = Str1.toInt(); //And convert to integer
Serial.println("@"); //Send acknowledge message to PC
}
}
if (serial_data.indexOf("D2:") >= 0) { //Check for receipt of Lane2 dial-in time from PC
//Input string format: D2:xxxxx where xxxxx is Lane2 dial-in time in milliseconds
//Valid range: 00000 - 09999 (0.0 to 9.999 seconds)
if (serial_data.length() == 8) { //Check integrity of input string
Str1 = serial_data.substring(3, 8); //Extract Lane2 dial-in time
Dialin2 = Str1.toInt(); //And convert to integer
Serial.println("@"); //Send acknowledge message to PC
}
}
//max_duration
if (serial_data.indexOf("TO:") >= 0) { //Check for receipt of timer timeout duration from PC
//Input string format: TO:xx where xx is the timeout duration in seconds
//Valid range: 05 - 30
if (serial_data.length() == 5) { //Check integrity of input string
Str1 = serial_data.substring(3, 5); //Extract timeout duration in seconds
max_duration = Str1.toInt() * 1000; //And convert to milliseconds
Serial.println("@"); //Send acknowledge message to PC
}
}
}
Ok lets say I don't care about the status of the displays or want to send status to the serial monitor and lets see if I have the basic concept of adding the displays to the program correct.
- include the wire.h library
- include the SparkFun_Alphanumeric_Display.h library
- in the setup loop wire.begin() //to join the I2C bus
- in the main loop find the final timer results for lane1 and lane 2
Lane 1if (!Fin1_Flag) { //Check & skip if Lane1 already crossed finish lineif (digitalRead(Fin_Sense1)==trip) { //Is Car over Lane1 Finish Line?Fin_time[0] = Clk_Time; //Yes...get timeLn1_State = Finished; //Set Lane1 state to finishedFin1_Flag = true; //Set Lane1 finished sensed flagdigitalWrite(XtreeGrn1,HIGH); //Turn off Lane1 Green Go Indicator}Lane 2if (!Fin2_Flag) { //Check & skip if Lane2 already crossed finish lineif (digitalRead(Fin_Sense2)==trip) { //Is Car over Lane2 Finish Line?Fin_time[1] = Clk_Time; //Yes...get timeLn2_State = Finished; //Set Lane2 state to finishedFin2_Flag = true; //Set Lane2 finished sensed flagdigitalWrite(XtreeGrn2,HIGH); //Turn off Lane2 Green Go Indicator}
- display.print(Fin_time[0] = Clk_Time) // for lane one display
- display.print(Fin_time[1] = Clk_Time) // for lane two display
I have not found a good example of how use to the addressing of the displays. The default address of the displays 0x70 and can be changed via jumpers. So assuming lane one is 0x70 and lane 2 is 0x71 what else do I need to add? Also how to blank the displays during the timer/track reset subroutine?
Open to suggestions so I may better learn to use I2C devices. The spark fun examples are not very clear on how to do this. What other library examples might I use give some insight?
Thank you everyone
Steve
Wow! what time it? lol.
OK, did some more digging. To solve the serial port issues I decided to go go with an Arduino Due. It has a second serial port and is about the same price as the Mega. Had a long talk with ChatGPT and a direction. As I suspected it is way more involved that I thought, but at least it is a start. The concept is to add a end of track display with forward and aft displays. Also a win light via a standard LED. Also as expected it did not verify as is. Got some work to do to integrate it into the original code. Below is what I come up with:
@stevenr8062 In the future, please read the HELP to paste the code. It is also a good idea to do a Tools/Auto Format before the copy as well. Can't find the Help, do a page find for Code
First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, 360, fairly knowledge in PC plus numerous MPU's & MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
My personal scorecard is now 1 PC hardware fix (circa 1982), 1 open source fix (at age 82), and 2 zero day bugs in a major OS.
My apologies. Was up quite late working this project and should have waited until today to post when i could pay attention to detail.
let's give it another go, hope it is not considered to long.
//Reset Variables and Displays: //The resetTrack() function resets the timing and speed variables (guardToFinishTimeLane1, guardToFinishTimeLane2, speedLane1, speedLane2) to zero and clears the alphanumeric displays. // //Track Reset Subroutine: //Call resetTrack() whenever the race is restarted. This subroutine handles clearing displays, resetting race variables, and printing a reset message to Serial1. // //Display Reset: //The resetDisplays() function is called within resetTrack() to reset each of the displays, showing "----" as a placeholder when the displays are cleared. // //How It Works: //Before Each Race: When the track reset subroutine is called (via resetTrack()), all timing data is cleared, and the displays are reset to show placeholder. //After Each Race: The elapsed time and speed are displayed, followed by a delay, allowing the user to read the data before the next reset. //Testing: //You can call resetTrack() at any point in your code, such as after a race, or tie it to a physical button or other track reset mechanisms. // #include <Wire.h> #include <SparkFun_Alphanumeric_Display.h> // Initialize the displays for each lane and position HT16K33 displayLane1Start; // Display at the start line for lane 1 HT16K33 displayLane1Down; // Display down the track for lane 1 HT16K33 displayLane2Start; // Display at the start line for lane 2 HT16K33 displayLane2Down; // Display down the track for lane 2 // Track-related variables float guardToFinishTimeLane1 = 0; float guardToFinishTimeLane2 = 0; float speedLane1 = 0; float speedLane2 = 0; void setup() { Serial.begin(9600); // Communication with Serial Monitor via Serial1 Serial1.begin(9600); // Debugging on Serial1 Wire.begin(); // Initialize each display with its unique I2C address displayLane1Start.begin(0x70); // Lane 1 Start Line Display displayLane1Down.begin(0x71); // Lane 1 Down Track Display displayLane2Start.begin(0x72); // Lane 2 Start Line Display displayLane2Down.begin(0x73); // Lane 2 Down Track Display // Clear displays at startup resetDisplays(); } void loop() { // Simulate race completion with example guard-to-finish times for each lane guardToFinishTimeLane1 = 0.421; // Example elapsed time for lane 1 in seconds guardToFinishTimeLane2 = 0.438; // Example elapsed time for lane 2 in seconds // Calculate speed (using 1.4 feet distance for trap sensor) speedLane1 = calculateSpeed(guardToFinishTimeLane1); speedLane2 = calculateSpeed(guardToFinishTimeLane2); // Display the elapsed time on all displays displayElapsedTime(guardToFinishTimeLane1, guardToFinishTimeLane2); // Introduce a readable delay before displaying speed delay(2000); // Display the speed on all displays displaySpeed(speedLane1, speedLane2); // Introduce a readable delay before the next race delay(2000); // Simulate track reset resetTrack(); } // Function to calculate speed based on elapsed time between guard and finish sensor (distance = 1.4 feet) float calculateSpeed(float elapsedTime) { float distance = 1.4; // Distance in feet return (distance / elapsedTime) * 0.681818; // Speed in mph (conversion factor) } // Function to display the elapsed time on the displays void displayElapsedTime(float time1, float time2) { // Display Lane 1 elapsed time displayLane1Start.clear(); displayLane1Down.clear(); displayLane1Start.displayText(String(time1, 3).c_str(), 0); // Show elapsed time with 3 decimal places displayLane1Down.displayText(String(time1, 3).c_str(), 0); // Display Lane 2 elapsed time displayLane2Start.clear(); displayLane2Down.clear(); displayLane2Start.displayText(String(time2, 3).c_str(), 0); displayLane2Down.displayText(String(time2, 3).c_str(), 0); // Report the elapsed times to Serial1 for verification Serial1.print("Lane 1 Elapsed Time: "); Serial1.print(time1, 3); Serial1.println(" seconds"); Serial1.print("Lane 2 Elapsed Time: "); Serial1.print(time2, 3); Serial1.println(" seconds"); } // Function to display the speed on the displays void displaySpeed(float speed1, float speed2) { // Display Lane 1 speed displayLane1Start.clear(); displayLane1Down.clear(); displayLane1Start.displayText(String(speed1, 1).c_str(), 0); // Show speed with 1 decimal place displayLane1Down.displayText(String(speed1, 1).c_str(), 0); // Display Lane 2 speed displayLane2Start.clear(); displayLane2Down.clear(); displayLane2Start.displayText(String(speed2, 1).c_str(), 0); displayLane2Down.displayText(String(speed2, 1).c_str(), 0); // Report the speeds to Serial1 for verification Serial1.print("Lane 1 Speed: "); Serial1.print(speed1, 1); Serial1.println(" mph"); Serial1.print("Lane 2 Speed: "); Serial1.print(speed2, 1); Serial1.println(" mph"); } // Function to reset the track, displays, and variables void resetTrack() { // Reset track-related variables guardToFinishTimeLane1 = 0; guardToFinishTimeLane2 = 0; speedLane1 = 0; speedLane2 = 0; // Clear the displays resetDisplays(); Serial1.println("Track reset complete."); } // Function to clear the alphanumeric displays void resetDisplays() { // Clear Lane 1 displays displayLane1Start.clear(); displayLane1Down.clear(); displayLane1Start.displayText("----", 0); displayLane1Down.displayText("----", 0); // Clear Lane 2 displays displayLane2Start.clear(); displayLane2Down.clear(); displayLane2Start.displayText("----", 0); displayLane2Down.displayText("----", 0); }
Ok, perhaps a little review is in order. when first started this project it had been a couple of years since the first version of our track. Our needs and the way we use the have changed greatly since then, we do more of drag racing style race event, rather than a Boy Scout style were everybody races everybody. It was also the time since I had worked with Arduino programming at all. I began with the RC car timer that is Arduino Mega based and communicates with a PC via Processing 3, developed by Bill V. at Minimal Hardware Designs. It has all the bells and whistles we wish to have, except one an end of track display (see my first post).
https://billvsderbytimers.weebly.com/minimal-hw-designs.html
The thought was it would easy enough with I2C displays and a couple LED's for lane win lights. It turns out that wiring it is the easy part, but the code is much involve than could have imaged. Unfortunately for me the code is my weak link. I happy to report that with the help of friends and the use of Chat GPT, I am improving my coding skills. My advice here is, if truly wish to improve your coding skills, do not use these kind of assets as crutch and slow down take the time to learn what is before you.
I had not worked with but a couple of very simple I2C projects and now have a slightly better understanding on how to implement them. First problem was was that when adding changes the reports to the serial monitor conflicted with the reports to Processing 3. Second was a bit confused on how the Mega, and other similar boards handle I2C. Admittedly, I am still working on that. I am sure there is a way to avoid these conflicts, but for now the solution for was to change boards and use the Arduino Due. Going this route allows Processing 3 to report to Serial and I2C to report to Serial1.
The displays I have use used are from Spark Fun and can found here:
https://www.sparkfun.com/products/16916
This created the next issue. The library examples provided are a bit vague. When trying to verify the code using:
SFE_Alphanumeric display;
or in my case:
SFE_Alphanumeric displayLane1Start;
SFE_Alphanumeric displayLane1Down;
SFE_Alphanumeric displayLane2Start;
SFE_Alphanumeric displayLane2Down;
IDE reported class errors.
The fix was:
HT16K33 displayLane1Start;
HT16K33 displayLane1Down;
HT16K33 displayLane2Start;
HT16K33 displayLane2Down;
The next issue was to deal with scaling down the track size. The original code is designed for a 132 foot track. The new track when it arrives will be 35 foot. Speed is measured from the trap sensors to the finish line sensors. A change in the way speed calculated is needed.To adjust the speed calculation for a different spacing between the trap and finish line sensors, you need to modify the equation used to calculate speed based on the new distance.
Current Formula:
The current formula is:
S=3600Finish Time−Trap TimeS = \frac{3600}{\text{Finish Time} - \text{Trap Time}}S=Finish Time−Trap Time3600
This formula assumes the distance between the trap and finish line sensors is 5.28 feet, simplifying the calculations to convert the time difference directly into miles per hour.
Adjusting for a New Distance:
If you move the trap closer to the finish line, say to a new distance DDD feet, you'll need to update the formula to account for this change:
- New Formula:
The speed SSS in miles per hour can be calculated as:
S=D×3600Finish Time−Trap TimeS = \frac{D \times 3600}{\text{Finish Time} - \text{Trap Time}}S=Finish Time−Trap TimeD×3600
Here, DDD is the new distance in feet between the trap and the finish line.
- Example Calculation:
- Let’s say you move the trap 3 feet away from the finish line:
S=3×3600Finish Time−Trap TimeS = \frac{3 \times 3600}{\text{Finish Time} - \text{Trap Time}}S=Finish Time−Trap Time3×3600
In the end the spacing will be:
Updated Sensor Positions:
- 6-Foot Sensor: Adjusted to 1.59 feet from the start line.
- 66 Ft Sensor: This will be modified to a position that is halfway on the new 35-foot track, i.e., 17.5 feet.
- The trap sensor: The trap sensor's primary role is to calculate the speed of the car over a known distance. The original code calculates speed over the 5.28-foot segment before the finish line; this needs to be updated to reflect the 1.4-foot segment.
The exact calculated positions are given above, your mileage my vary.
As this post is getting a bit long I will end it here. As the code is a bit please se the attachments.
The first is all the changes to add the display and second is that file blended with with the original RC timer file. The blended file did verify, but is yet untested.