We describe how we used a software tool called SynthOS withman off-the-shelf robot kit, to developed control algorithms, and create an RTOS to schedule and coordinate the various robot tasks. This demonstrates how writing code for SynthOS is straightforward, and SynthOS can easily adapt an RTOS to a very constrained platform.
BY IGOR SERIKOV AND JACOB HAREL, ZEIDMAN TECHNOLOGIES
At Zeidman Technologies, we wanted to create a project using our SynthOS software tool, which automatically generates an optimized real-time operating system at the push of a button. We call such an RTOS an “application specific operating system” or “ASOS.” We decided to create a multitasking robot based on an Arduino processor. The requirement was to build a robot that can move around an obstacle course while avoiding hitting walls and objects and not getting trapped in narrow sites. Furthermore, it should be able to adjust its speed for the left and right tracks independently, and if anything fails, such as one of the tracks getting stuck, it should give an indication (a beep) and shut down its power.
We started with an off-the-shelf DFRobotShop Rover V2 kit from RobotShop Distribution. The robot is shown in Figures 1 and 2. It contains the following parts:
• Main control circuit board using the Arduino UNO architecture.
• Atmel ATtmega328p microprocessor
• Battery and on-board battery charger
• Twin-motor gear box for controlling two tracks, each consisting of wheels with rubber treads
• Two encoders for determining motor position, one for each track motor
• Infrared compound eye consisting of 4 pairs of IR photo transistors and 4 IR LEDs to measure both reflected IR
• Ultrasound sensor board for generating sound and measuring reflected sound.
• Pan and tilt servo motors
• I/O expansion shield
The board uses a slightly modified Arduino UNO reference design. It incorporates an Atmel ATmega328p microprocessor. The ATmega328p is a system-on-chip with 2K SRAM and 32K flash memory running at 16MHz. The CPU uses a Harvard architecture where code and data reside in separate address spaces.
Programming the ATmega328p was done in C because this language is common for embedded systems and is supported by SynthOS. We used the following tools:
• avr-gcc, binutils-avr, and avr-libc: a GNU tool chain for Atmel chips
• avrdude: a firmware upload utility that talks to Arduino bootloader
Overview of the Project
In order to avoid hitting any objects, the robot uses an ultrasonic sensor. This sensor can detect objects at a short distance. Using the ultrasound sensor, the robot continuously scans its surroundings, turning the sensor left and right for a total of nearly 180 degrees. This wide scanning sector enables the robot to approach an obstacle at a very shallow angle. Since ultrasound behaves like light, the angle of reflection is equal to the angle of incidence. We found that the robot could not detect vertically inclined surfaces, and some curvy surfaces can cause erroneous distance readings.
To find the minimum distance from an object at which the robot will have to take action, we noted that the next time the robot detected the object is when the robot head moves back in the opposite direction after finishing the scanning sector and returning to the original position. The robot should move slowly enough to avoid hitting the object before seeing it the next time. We have calculated the relation between the robot speed and the full turn time. The real minimal distance could be a bit smaller than this because the robot would not really hit the object detected near the very edge of the sector unless it is a wall approached at a shallow angle.
When the robot detects an object that is close enough, it turns left. Moving the tracks in opposite directions allows it to make a turn with a nearly zero radius. Because the robot always turns in the same direction, it does not get stuck in narrow sites. After the robot makes a turn, it has to stop and make one complete scanning of its surrounding before moving any further.
The robot constantly monitors the speed of both tracks using the encoders. The encoder wheel has 16 sectors. Connected to an analog input, and sampled via a 10 bit A-to-D converter, the encoder emits a sine wave whenever the wheel rolls. To gauge the speed, the firmware measures the time between two adjacent half-waves. To eliminate outliers, the algorithm arranges every three consecutive samples and picks the middle one.
Motor overheating is not a concern for this project since the motor speed is not high, and if the motor gets stuck, it will be turned off by the task associated with this track. For that reason, we do not use the temperature sensing capability. The torque and speed of each of the motors are adjusted independently, while turning requires higher torque than moving straight.
The servo motors’ control circuits use pulse width modulation for determining the position where the number of pulses determines the position. It might take several pulses to reach the desired position, if the current position and the required one are far apart since every pulse turns the shaft a small angle. It is recommended to send a pulse every 20 milliseconds. To send pulses of precise width we use delays with interrupts disabled. This is acceptable since the delays are below two milliseconds.
Timers and Interrupts
The firmware uses a hardware timer for all time-related tasks via timer interrupts. The divider is set to 1024, and the CPU clock rate is 16 MHz, so the time counter register increments every 1024*1/16000000=64 microseconds. The timer generates an interrupt when the counter reaches 156, interrupting roughly every 10 milliseconds.
The ultrasonic sensor reports the moments of sending an acoustic burst and getting an echo by raising and dropping its output. The width of the pulse is equal to the round trip time for the sound to travel to and from the object. Since the time counter register increments every 64 microseconds, the measuring error is around 0.011 meter, calculated to be about 0.43 inch given the speed of sound, which is about 342 m/s at sea level.
Sometimes, the ultrasonic sensor will get no response and will set the pulse width to the maximum. Besides open space cases and curvy, inclined surfaces, this was found to be caused by objects that do not reflect sound, for example, pillows.
SynthOS allowed us to write our code in C. When one task needed to call another task, or wait for another task to complete, we inserted a special line of code that is recognized by SynthOS, called a “primitive.” We also created a simple project file to specify the parameters of each task such as the task’s priority and its frequency. SynthOS was then run on all of the task code. SynthOS created the appropriate semaphores and flags for each task and inserted the appropriate code at the appropriate points in the task code. SynthOS also created task management code to manage the tasks and their associated flags and semaphores. A generic diagram of the resulting code is given in Figure 3 with a more detailed diagram given in Figure 4. Note that each task is mostly written by us, but SynthOS inserts the code required by the ASOS into each task, and generates the ASOS that controls execution of each task.
SynthOS automated the process of creating the operating system so that we could focus on writing the tasks for the robot and its sensors. Because the output of SynthOS is in C, we had complete visibility to everything going on in the operating system. All of the tools that we normally use to compile and debug the tasks were also used to compile and debug the operating system. The operating system code could also be easily modified by hand, if necessary, giving us complete control over the code.
Developing the code with SynthOS to the specific hardware platform was fairly straightforward. First we needed to define three required routines to deal with interrupts. In our SynthOS project file (project.sop), we put the following variable settings that’s define the user functions for the specific functionality (Source Listing 1)./p>
In this implementation we had to augment a couple of standard headers because SynthOS does not yet support some of the features used by the Atmel libraries. For example, we converted variadic macros and functions that take variable numbers of arguments, like printf(), to macros and functions that take a fixed number of arguments because SynthOS does not yet support variadic macros and functions.. We created a couple of our own headers that include the standard library functions and added some customization to support for SynthOS:
• Set __ATTR_CONST__ to the empty string before including a header that uses it.
• Redefined ISR in the Atmel support library that is used in the project to be a non-variadic macro.
• Un-defined the macros sei (set interrupt) and cli (clear interrupt), and instead created two in-line functions.
After adapting the environment to work with SynthOS we started working on the system architecture. Based on the system requirements we defined the following main tasks, which are illustrated in Figure 5:
Robot software architecture
• robot: Scanning and high level control loop task to manage the scanning for obstacles using ultrasound detection and distance measurements. This task is the main control task in the system. It synchronizes with the other tasks via SynthOS primitives;
• left_motor: Left motor control loop task – manage the speed and direction of left tracks and will turn off the motor is it gets stuck;
• right_motor: Right motor control loop task – manage the speed and direction of right tracks and will turn off the motor is it gets stuck;
• drive_pan: A call task that manages the scanning and movements of the ultrasound sensor, left and right.
• print: A call task that manages all the error and messages communication via the UART port.
• ultrasonic_measure: A call task that controls the ultrasonic sensor transmit and receive, and calculates the distance to an object based on the ultrasound echo.
SynthOS made writing code for task communication simple. Using the SynthOS_wait(cond) primitive, a task can wait for any condition that can be expressed using global variables and constants. On the trigger side, nothing needs to be done at all; SynthOS automatically inserts the code that monitors the affected variables and activates waiting tasks when necessary.
A good example of using this mechanism is programming delays, which is very easy to implement. The only required variables were a tick counter which we named clock, and xyz_timer for every waiting task that holds the counter value at the delay start. Then, when we needed a delay in a task we used the following code:
xyz_timer = clock;
SynthOS_wait (clock – xyz_timer >= ticks_to_wait);
The timer interrupt routine just increments clock and SynthOS does the rest. Note that the timer interrupt routine has no specific knowledge about waiting tasks, and SynthOS has no specific knowledge about our timekeeping.
When we needed a more precise delay, we first used the SynthOS_wait() primitive to approximate the delay and a loop containing a SynthOS_sleep() primitive for the rest of the interval. Here is an example:
start = pclock ();
xyz_timer = clock;
SynthOS_wait (clock – xyz_timer >= ticks_to_wait);
while (pdiff (start, pclock ()) < ticks_to_wait * clock_divider)
The function pclock() reports the time in CPU time register increments and the function pdiff(start, end) calculates the time interval. A general diagram of interrupt handling with SynthOS is given in Figure 6.
Synchronizing tasks and interrupts using SynthOS_wait()
We are using the UART communication port only for debugging since the robot is completely autonomous (it is actually USART – universal synchronous asynchronous receiver transmitter). We followed the tradition approach of using circular input and output buffers. Again, we used SynthOS ability to track variables to synchronize tasks with the UART interrupt handlers. The interrupt handlers update buffer pointers and tasks just wait for proper conditions (Source Listing 2).
The communication and debugging are done using a custom command similar to printf(). We could not use the standard printf() function because it calls a put character routine, which is blocking. SynthOS restricts the use of blocking primitives to top level functions. Therefore we re-implemented printf() as a SynthOS call task that embeds SynthOS blocking primitives.
It is worth mentioning that in SynthOS, there is no need to declare synchronization variables as volatile unless they are modified by the interrupt handlers. This is because all control transfers are transparent to the compiler. The use of the SynthOS simple 5 primitives enabled us to create a simple and straightforward architecture that included all the RTOS functionality to lunch and synchronize the high-level tasks. The SynthOS tool also generated very efficient code to implement this system with the system using only 1K of RAM and 13.5K of flash including debug code. Taking out debug code reduced the RAM usage to 0.5K and 8.6K of flash. We ported the code to FreeRTOS, where it required 1.25K or RAM and 12.8K of flash, significantly more than our SynthOS-based solution. This can be seen in Table 1.
Comparison of FreeRTOS and SnythOS-generated ASOS
This project was a very interesting example implementation of an embedded control system using the SynthOS ASOS generation tool to create a sophisticated yet simple multi-tasking RTOS and easily integrate it into our system. This implementation shows the strength of the ASOS generated by SynthOS not only to create basic multi-tasking system architecture but also to enable system synchronization at all levels from interrupts to high-level system management. Using SynthOS allowed us to quickly come up with clean and succinct code that has a very small footprint. The code of the project can be downloaded at www.zeidman.biz/downloads.htm. A video of the final robot can be seen at http://youtu.be/HzCGSk202gY. SynthOS is available to use online completely for free at www.SynthOSonline.com.