Introduction
In my previous post, I detailed the the hardware used to create the finishline tracker. If you missed it, here’s a recap of the key hardware components that make up the finish line tracker:
- Adafruit VCNL4040 Proximity and Lux sensor – measures proximity and light levels and is used to detect cars passing over it
- Adafruit PCA9548 8-Channel I2C Multiplexer – allows multiple I2C boards with the same address to exist on the same bus
- Arduino Uno – used to read in physical user input
- 64×32 RGB LED Matrix panel – display the race details and results
- Adafruit RGB Matrix Shield – allows easy connection of the LED panel to the Metro M4
- Adafruit Metro Mini 328 V2 – tracks the race time for each lane
- Adafruit Metro M4 – main controller
In this post, I’ll explain how to integrate and use all this hardware together effectively.
Development environment
Architecture
The firmware is broken down so that the Metro M4 does most of the control of the tracker. Four Metro Mini 328s with four VCNL4040s are used to capture the most accurate timing for finish times for each car. Communication to these boards is done over I2C. An Arduino Uno is used to capture user inputs like race state changes and racer participation. Communication to this board is done over serial communication.
Microcontroller breakdown
Timer boards
The Metro Mini 328 timer boards are designed with simplicity in mind. They handle two interrupt signals: one to mark the start of the race, and another to mark when a car crosses the finish line. This straightforward design ensures highly accurate race timing.
Previously, using the M4 for timing introduced issues. The M4 had to cycle through four lanes to determine results, leading to inaccuracies in close races due to the loop iteration determining the finish order instead of the actual crossing times.
To avoid using millis()
within an interrupt handler, I set start and end flags that were checked within the main loop.
void loop() {
if (startFlag) { // Check if the start flag is set
startTime = millis(); // get the start time
startFlag = false;
reportTime = false;
elapsedTime = 0;
Serial.println("start flag triggered");
}
if (endFlag) { // check if the end flag is set
if (reportTime == false) {
elapsedTime = millis() - startTime;
reportTime = true;
Serial.print("Elapsed Time: ");
Serial.println(elapsedTime);
}
Serial.println("end flag triggered");
endFlag=false;
}
}
Communication with the Metro Mini 328 boards is handled over I2C. These boards are configured as slaves and respond to requests for elapsed times as unsigned long values. If the race hasn’t started or is still running, the board returns zero. Once the race is complete and the end flag is set, the board returns the elapsed time.
finishline sensor firmware repo
Input board
The hardware’s goal is to operate independently (as well as over USB), allowing users to select participating lanes and switch between race states. This requires a total of eight inputs: four toggle switch inputs to indicate lane participation and four momentary switches to change the race state.
The race has three states the user can try to set: setup, ready, and race. Users can switch between these states using three triggers. Racer participation is updated via the toggle switches only when needed, so an additional “setup racers” switch is required.
The main controller communicates this information via serial communication by sending an ‘R’ message. This board responds with a single byte, representing the state of the inputs, with high as 1 and low as 0.
void loop() {
// Check if there's a request from the serial
if (Serial.available() > 0) {
char command = Serial.read();
if (command == 'R') { // If command is 'R', respond with the input states
byte inputState = readInputs();
Serial.write(inputState); // Send states as a byte
}
Serial.flush();
}
delay(10); // Small delay to prevent overwhelming the serial buffer
}
finishline input firmware repo
Main controller
Communicating with other boards
The main controller communicates with the timer boards and proximity sensors via I2C, utilizing an I2C multiplexer. It sets the proximity sensors to trigger when their value exceeds 5 units. The interrupt output from the proximity board is connected to the end interrupt on the timer board, while one output from the main controller is connected to the four start interrupts on the timer boards.
Communication with the input board is handled through serial communication, using “R” commands to retrieve a byte representation of the eight inputs. The bit format of these bytes is predetermined and requires prior knowledge to interpret correctly.
The display is managed by an Adafruit Protomatter class. The screen layout is designed to show a general message at the top and divide the bottom into four sections, each providing information about a specific lane. Due to pixel limitations, the display shows only the finish position and participation status.
State machine
The main controller operates with a state machine consisting of five states: setup, ready, countdown, race, and finish.
State Descriptions:
Finish: View race results. Transition back to setup to start a new race.
Setup: Set racer participation and transition to the ready state.
Ready: Transition back to setup or proceed to start the race.
Countdown: Counts down to the start of the race. If manually triggering the race, enter this state; if using a start gate with a switch, transition directly to the race state. Cannot transition to any other state and automatically transitions to the race state.
Race: The race is active. Can transition back to setup. Once the race completes, it automatically transitions to the finish state.
Communicating through USB
You can also control the hardware and access additional information via the USB port. The main controller reads messages from the serial port in JSON format.
Message Format:
- State Control:
- Set the
"method"
key to"setup"
,"ready"
, or"race"
to switch between states.
- Set the
- Configure Participants:
- Set the
"method"
key to"config"
and include an"inRace"
key with an array of four integers (0s and 1s), where0
means not participating and1
means participating.
- Set the
Response:
Whenever a valid JSON string is sent to the main controller, it responds with a JSON string containing information about the current state of the race. This includes:
- Race message
- Racer participants
- Finish positions
- Status
- Finish times
To obtain board-level information, set the "method"
key to "board"
.
finishline tracker firmware repo
Debugging, testing, and optimization
A significant portion of the time spent debugging and testing the hardware focused on three main areas: power, JSON objects, and speed.
Power Issues
Incorrect power supply to the boards caused numerous problems, including errors in I2C and serial communication. These errors led to corrupted data, which resulted in inaccurate race times that didn’t make sense. Proper error handling helped identify some issues, but consistent results required a steady voltage supply to all boards and tying their grounds together.
JSON Issues
Malformed JSON strings were a major hurdle. Initially, I used static JSON documents, thinking it was efficient to share them instead of recreating them each time. However, this approach caused JSON strings to be cut off or contain errors. By creating new JSON objects for each iteration, these issues were resolved.
Speed Constraints
Experimenting with different board configurations revealed that the main speed bottleneck was the multiplexer channel switching time. Each switch took 10ms, which was too long for cycling through the four lanes. I considered sending the four sensor interrupt signals directly to the main controller, but it ran out of available GPIO pins for interrupts.
Conclusion
As I previously mentioned, the hardware setup could be simplified by using an Adafruit Grand Central M4 Express. This board has all the necessary inputs and could be coded in a way that could significantly reduce my timing concerns using a single board. However, part of this project’s journey was to give myself a reason to learn new skills. By keeping the input and timer boards separate from the main board, I gained valuable experience with I2C communication, serial communication, and working across multiple boards.
Stay tuned for my next post, where I’ll dive into a Python application that interacts with the finish line tracker hardware!
Leave a Reply