For embedded real-time applications, it’s crucial to have a reliable time base. When using MATLAB/Simulink in external mode, the generated real-time code runs at the desired rate using the specified solver, but this isn’t the case with Python. Code deployed in a while loop typically runs as fast as possible, and controlling the execution rates requires additional measures. In this blog, I’ll cover the core approaches available with the Quanser Python API (also applicable to MATLAB and C++) to achieve accurate timing.

Let’s consider the following case – you have to develop an application that writes motor and steering angle commands, and reads an IMU at 100 Hz, corresponding to a sample time of 10 milliseconds.

Sleeping/Waiting

The simplest approach is to put the thread to sleep waiting for a certain duration. The time module in Python provides the sleep() method, allowing you to sleep for a specified duration in milliseconds. If the computation time of your code is around 6ms, you can sleep for the remaining 4ms, and achieve a general 10ms loop rate. More generally, the computation time may vary slightly, but you can estimate it at each iteration, and sleep for the remainder. This is demonstrated below in Timing_using_Time_methods.py.

The issue with this is fundamental to the approach itself. The sleep() function has overhead, and these small errors propagate over time, leading to your read/write application falling behind. That’s not ideal, but never fear, Quanser APIs offer a timing solution that’s far superior.

Task-based IO

The Quanser Python APIs provide task-based IO that handle timing precisely using hardware clocks. When you create a reading task, it spawns a new thread in the background and a buffer. Data you wish to read is dropped into the buffer at the rate you request. You can read the data from the buffer, and subsequently flush it. The background task adding data to the buffer is blocking, and automatically waits until the overall sample time has elapsed. This is demonstrated below in Timing_using_HIL_tasks.py. Remember to start the task before trying to read, as well as stop and delete the tasks in the end.

Vision applications

When using OpenCV, the cv2 module provides the waitKey() method, which behaves similar to the time module’s sleep() method. It has similar time drift errors to the sleep method though, making it problematic for real-time vision applications. Instead, use the VideoCapture and Video3D modules provided with the Quanser APIs, which include blocking read operations. They provide new images at the requested frame rate, and you can set the waitKey() method’s sleep duration to 1ms to avoid waiting. Note that you can’t completely remove the waitKey() method, as your images won’t be displayed without it.

Summary

You can find more information on task-based IO by browsing our Python Hardware documentation as well as the Video APIs by browsing the Python Multimedia documentation. Check out our GitHub page for the HIL SDK. Lastly, feel free to contact us about integrating our hardware devices with your real-time control and robotics implementations in Python.

Thanks for reading, I’ll catch you next time.
Murtaza