[Warning: Long post]
The Purdue VEX Robotics College Challenge team BLRS would like to introduce one of our biggest achievements this year. Our history as a CS team means that our club membership is almost 75% Computer Science majors itching to write code for the extended 60-second college Autonomous Period. However, neither of the available programming options (easyC or ROBOTC) satisfied us, as the choices were limited at the time to a drag-and-drop language with a slow runtime library or an interpreted bytecode system that wasn’t true C.
We created the Purdue Robotics Operating System (PROS) to move beyond these limitations and allow us to leverage our advantage. Its features include:
- Develop on Windows, Linux, or Mac OS X using an Eclipse-based environment with one-click download and integrated source control for collaboration between our eight primary programmers
- Unparalleled runtime library written from scratch that takes full advantage of the Cortex Microcontroller’s power (microsecond-resolution timers, full priority-based multitasking, interrupt control)
- Direct compilation to native C allows use of structures, pointers, standard I/O, dynamic memory allocation, and functions familiar to CS students
- Use of standard tools (GCC), coding standards (ISO C99), and function names (libc, Arduino) minimizes learning curve
- Direct access to hardware allows interfacing the Cortex Microcontroller with a wide variety of third-party sensors to fully utilize the rules of the College Division
https://vexforum.com/gallery/files/3/8/6/1/4/pros_development.jpg
While benchmarks aren’t always the best way to describe our system’s performance, these timing and benchmark results should provide a general idea of our new system. Some of these benchmarks were taken from jpearman’s benchmarking thread. The latest versions of PROS (1r09), easyC (4.1.0.5), and ROBOTC (3.60) were tested. All tests, unless otherwise noted, were performed using a direct USB tether connection to a joystick and a VEX Programming Kit wired to the computer.
Test 1: Toggle digital output
The most accurate way to get an indication of how long something takes on the Cortex is to set an external digital port to “1”, perform the operation, set the digital port back to “0”, and measure the length of the resulting pulse with an oscilloscope. We measured how long each environment takes to simply toggle the pin on and off.
void operatorControl() {
// Naive method of toggling pins
pinMode(1, OUTPUT);
while (1) {
digitalWrite(1, HIGH);
digitalWrite(1, LOW);
}
}
Results:
- ROBOTC: 3 us
- easyC: 1.5 us
- PROS: 1.15 us (1.3x easyC and 2.61x ROBOTC)
For actually toggling a pin in real-world scenarios, PROS features PWM drivers using the Timer units on the Cortex Microcontroller that run without any intervention on the user’s part. These timers can go as fast as 27.8 ns, but in reality only a 0.8 us period can be achieved before the signal gets garbled by capacitive load.
Test 2: Set all motors to the value of joystick channel 1
This test does not actually measure the responsiveness of each environment to operator control! As jpearman explains, motor ports update every 15 ms in easyC or ROBOTC, and the servo control signal updates every 18 ms, so the command will only reach the motor when one of these occurs.
void operatorControl() {
// Set all motors to joystick 1
pinMode(1, OUTPUT);
while (1) {
int8_t value;
digitalWrite(1, HIGH);
value = joystickGetAnalog(1, 1);
for (int i = 1; i <= 10; i++)
motorSet(i, value);
digitalWrite(1, LOW);
}
}
Results:
- ROBOTC: 124 us
- easyC: 70 us
- PROS: 18 us (3.89x easyC and 6.89x ROBOTC)
PROS also allows synchronization with the motor update to the slave processor, as the nearly jitter-free timing system can easily be synchronized to update motors just before communication occurs. This reduces the worst-case motor update delay from 34 ms to 18 ms, giving PROS an edge in the real-world department too.
Test 3: Send a string to the debug console
To send the string “Hello\n” (6 characters) to the VEXnet system at 230400 baud, it takes 260 us. EasyC always takes this long to send a string. RobotC and PROS only take this long if many characters have been sent quickly and their internal output buffers are full.
void operatorControl() {
// Send a string
pinMode(1, OUTPUT);
while (1) {
digitalWrite(1, HIGH);
printf("Hello\n");
digitalWrite(1, LOW);
}
}
Results:
- ROBOTC: 12 us
- easyC: 260 us
- PROS: 12 us (21.67x easyC and 1.0x ROBOTC)
Now for our own benchmarks which we believe really demonstrate the efficiency improvements of the library and threading system of PROS.
Test 4: Serial port torture test
We programmed our vision system to send tracking updates (30 frames/second, 24 bytes/update) to the Cortex serial port UART1. A simple reader program in each language reads in the packets and counts the number of drops (packets with failed checksums).
typedef struct {
// Packet information
} Packet;
void operatorControl() {
// Serial port torture test
usartInit(uart1, 115200, SERIAL_8N1);
int failures = 0; Packet pkt;
for (int i = 0; i < 255; i++) {
// readPacket() returns 1 if checksum OK and 0 if checksum failed
if (!readPacket(uart1, &pkt))
failures++;
}
printf("Drops: %d\n", failures);
while (1);
}
Results:
- ROBOTC: 200/255 correct (78%)
- easyC: 225/255 correct (88%)
- PROS: 255/255 correct (100%)
Our high-speed vision system would clearly be much more difficult without PROS.
Test 5: IME blaster test
We hooked up two Integrated Motor Encoders (IMEs) to the Cortex I2C port and requested updates as fast as possible. ROBOTC and easyC poll the IMEs in the background (update rates shown in this post); PROS requires updating in a user thread. While this might seem like a hassle, it means that if a loose cable disrupts communication, only that thread will lock and the PROS kernel (and field control) will keep working. We have not had a single IME-related full lock on PROS.
User-controlled updates of IMEs also increases flexibility by allowing other I2C sensors to share the bus with VEX Motor Encoders. This gives us another port to interface third-party hardware directly to the Cortex.
void operatorControl() {
// Poll an IME as quickly as possible
long int x;
pinMode(1, OUTPUT);
imeInitializeAll();
while (1) {
digitalWrite(1, HIGH);
imeGet(IME_ADDR_MIN, &x);
digitalWrite(1, LOW);
}
}
Results:
- ROBOTC: 2 ms
- easyC: 20 ms
- PROS: 500 us (40x easyC and 4x ROBOTC)
Even if another code mistake (null pointer access, stack overflow, watchdog timer) occurs, PROS will stop all motors and automatically reboot for fail-safe behavior that has saved many VEX parts.
Test 6: Analog sampling
We attached a VEX Line Tracker to analog port 1 and timed both the average and worst case sampling time. Uniform rate sampling with low jitter is one of the most crucial parts of making a good PID controller and performing Digital Signal Processing on analog inputs.
void operatorControl() {
// Read an analog port
// NOT equal to the analog "Sampling Rate"; PROS samples internally at 50 KSPS, easyC at 26 KSPS, ROBOTC unknown
pinMode(1, OUTPUT);
while (1) {
digitalWrite(1, HIGH);
analogRead(1);
digitalWrite(1, LOW);
}
}
Results (average case):
- ROBOTC: 6.64 us
- easyC: 2.54 us
- PROS: 1.11 us (2.29x easyC and 5.98x ROBOTC)
Results (worst case):
- ROBOTC: 66 us (9.94x average case)
- easyC: 1.4 ms (551x average case)
- PROS: 4.5 us (4.05x average case)
Signal processing like a Kalman filter has been tested to run much more consistently on PROS.
Test 7: Threaded workload
Three threads were started in each language - one thread toggles a motor forward and reverse every 20ms, one thread prints text to the screen once a second, and one thread toggles a pin high and low with 1ms in between. We measured the maximum length of a high pulse in a 16,384 pulse batch to get an idea of threading performance and jitter. EasyC has no threads, so we used RegisterRepeatingTimer instead.
void printData(void *ignore) {
// Print data every second
while (1) {
printf("Hello\n");
delay(1000);
}
}
void flipMotors(void *ignore) {
// Toggle motors every 20ms
while (1) {
motorSet(2, 127);
delay(20);
motorSet(2, -127);
delay(20);
}
}
void operatorControl() {
// Jitter test with threaded workload
taskCreateSimple(flipMotors);
taskCreateSimple(printData);
// Flip pin again
pinMode(1, OUTPUT);
while (1) {
digitalWrite(1, HIGH);
delay(1);
digitalWrite(1, LOW);
delay(1);
}
}
Results:
- ROBOTC: 1.83 ms (83% long)
- easyC: 2.42 ms (142% long)
- PROS: 1.01 ms (1% long)
PROS is capable of handling threaded workloads with ease.
I could explain much more, but there’s a 10K post limit so I can’t do it now. If you have more questions, feel free to stop by the BLRS booth (college division) at Worlds. Do you think that PROS could forever change VEX programming? We believe it can.