Finding the performance limit of an Arduino MEGA
As I think up more and more things to do with YardBot, I’ve started to reach the limits of what a normal Arduino MEGA can handle. With an 8 MHz clock speed and 2KB of RAM available, Arduinos have never been about high performance. If you look back at my system structure post, you’ll see how many things need to be controlled. So far at my prototype stage I have many of the ROS nodes set up for the following systems:
- Retrieving motor commands and setting the controller output
- Reading the motor encoder interrupts and sending the stream back to the ROS master
- Reading current and voltage sensors and streaming back to ROS
- Reading temperature and humidity sensors and streaming back to ROS
- Retrieving LED values and writing the outputs
- Retrieving beep commands and toggling the piezo buzzer
- Retrieving and streaming the IMU data from both gyro and accelerometer
All of these components are running on my original Arduino, a MEGA 1280. When I set up the DC motor for a simple open loop test, I noticed that joystick position changes weren’t accepted as quickly as they should, with some commands being dropped. This was noticeable by the fact that the motor continued to spin after dropping the deadman button or returning the stick to neutral. This is bad news for a simple DC motor test, but downright dangerous if used for a 100 lb robot.
Finding a solution means going through each block and thinking through what it does and what impact it has on the rest of the system. If you look at the list above, you’ll see that most return some data back to the ROS master. The Arduino has a limited transmit capacity. By default it operates at 57 KBps, but I’ve set it to 115 KBps in an effort to improve the situation. This helps but does not solve it. The result of the overflow is that some messages are dropped, which can cause bad response times.
Much of the code I use attempts to be non-blocking. That means there are no “delay” functions being called that hold the processor and prevent it from doing other tasks. Instead I use timers that run inside the main loop and trigger callbacks when the desired time has passed. This means that if there are any blocking segments, the timers may be delayed and performance suffers. I discovered another symptom of this when testing the piezo alert buzzer, and when requesting a 2 Hz beep, there are unequal gaps between tones. One of the potential blocking pieces is anywhere that queries an I/O pin. DigitalRead is considered an expensive operation, that is it can take a disproportionately long amount of time to complete. Adding multiple reads together like checking the emergency switch and current/voltage sensor would delay the loop and cause problems.
So the solution at the moment is to separate the functions into a second Arduino. One will operate the motors and return encoder data while the second will do the auxiliary operations like reading sensors. The motors can be considered “time critical” but sensor timing is less strict. This may change as I continue to add complexity but after learning that other mechatronics projects use multiple processors (like ArduPilot), I’m confident it will significantly improve the performance of the whole system.