We had a project to take an off-the-shelf robot kit, develop control algorithms, and use SynthOS to create a real-time application specific operating system (ASOS) to schedule and coordinate the various robot tasks. The requirement was to build a robot that can move around an obstacle course while avoiding walls and other objects and not getting trapped in narrow places. We also wanted it to 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 own power. We were happy with the results.
The second phase of the project involved adding wireless communication to the robot to enable remote communication for debug, monitoring, and control. In this article, we talk about the unique issues in the development of a ZigBee device driver in particular and with the development of drivers for communication devices in general using SynthOS.
What is ZigBee?
We chose ZigBee because it is an open standard that is used worldwide and has found significant use in the Internet of Things. ZigBee is particularly focused on the requirements of sensors for use in consumer, commercial, and industrial applications. It is supported by the ZigBee Alliance, an open, non-profit association with about 400 members driving the development of the ZigBee standards.
ZigBee is a low power, low cost wireless mesh network standard, operating in the 2.4MHz unlicensed spectrum. The raw, over the air data rate is 250 Kbit/s per channel in the 2.4GHz band, and the indoor effective transition distance is 10-20 m depending on the environment contraction materials. ZigBee devices are required to conform to IEEE 802.15.4-2003 Low-Rate Personal Area Network (LP-WPAN). This standard specifies the physical layer Media Access Control and the data link layer.
The particular implementation of ZigBee that we chose is XBee, which is the brand name from Digi International for a family of form-factor -ompatible radio modules based on the 802.15.4-2003 standard designed for point-to-point and star communications at over-the-air baud rates of 250 Kbit/s. We chose XBee because of the low cost and ease of ordering from Digi. The particular module that we chose was the XBee ZB low power ZigBee Module with integrated wire antenna, product #XB24-Z7WIT-004, shown in figure 3.
The hardware platform
The hardware platform for the project was an off-the-shelf DFRobot Rover v2 kit from RobotShop Distribution Inc. The Rover uses a Tamiya twin motor gearbox and the Tamiya track and wheel set. An on-board buzzer that can generate a variety of tones is used for simple notifications such as failure modes. A rechargeable 3.7V LiPo battery is included and can be recharged using the onboard charger.
SynthOS has a built-in capability to support drivers for hardware peripherals. Its inherent capability to run multiple tasks in parallel enables a user to build a system that monitors multiple devices and activate the appropriate tasks in response to inputs. SynthOS supports two basic schedulers for the system kernel, round robin and the priority-based, as illustrated in figure 4. The definition of the architecture is static and cannot be changed at runtime, and at this time we can support only one of them and not a combination of them. It is defined using the scheduler directive in the configuration file. SynthOS internal inter-task communication will activate the appropriate task to service the request. In general for embedded systems, interrupt mechanisms are the preferred method for monitoring external sensors and inputs, but a timer interrupt with polling can often be as effective.
The software solution
First we needed to define three required routines to deal with interrupts. In the SynthOS project file (project.sop), we set the following parameters to define the user-created interrupt functions that SynthOS would need to utilise when it synthesised interrupt service routines:
Starting with SynthOS version 1.7 and up, variadic macros (macros that take a variable number of parameters) are supported. This means we can use the Atmel libraries for interrupt support without any modification. Another improvement available in version 1.7 is the support for the setMaskAndSleep() function call that sets the interrupt and puts the processor into low-power sleep mode when there is no active task in the task queue. Once an external device or a timer triggers an interrupt, the processor will wake up from the sleep mode and service the interrupt. If the interrupt service routine changes the status of any of the tasks, by satisfying the condition of a SynthOS_wait() statement, the ASOS will put this task into the task execution queue and it will be executed. The following code shows a processor-specific sleep mode routine, assembly wrapped in C code, and the setMaskAndSleep() function that SynthOS uses to call it.
The architecture for the XBee driver is simple and straightforward as shown in figure 5. The two main tasks that are supported are transmit and receive tasks. Both of them are SynthOS Call Tasks that can be called from any SynthOS Loop Task. In the robot implementation, the main robot, left_motor and right_motor Loop Tasks can send and receive data via XBee by calling the xbee_request() task for transmitted data and the xbee_receive() task for receiving data. Using the SynthOS_call() primitive to invoke these tasks will execute the transmit or receive task and halt the execution on the calling task until the called task is completed. The higher-level calling task can monitor a trigger that is activated once communication is established with the base station and the channel is open.
The transmit and receive tasks can be called from multiple Loop Tasks simultaneously but those calls are going to be serialised, and each transition is going to be handled independently until it is complete. This architecture enables multiple higher level Loop Tasks to use the same driver and to communicate through the single XBee hardware communication device. Unfortunately this architecture is not optimised—transmit and receive tasks may have to wait for previous task instances to complete execution even if they are ready to execute. One way to improve on this basic architecture without many changes is to run the xbee_receive() and xbee_request() tasks as Loop Tasks and communicate to the rest of the system via shared memory. An even more efficient architecture using non-blocking task execution is describe later in the article. SynthOS will manage all the context switching between the high-level tasks and the driver communication tasks and synchronise the information being sent and received.
SynthOS enables two basic ways to implement a device driver in a multi-tasking environment. The first is a blocking model, where one task calls the driver and halts execution of the calling task until the operation of the called task is complete. This can be used for critical timing drivers that will use a large percentage of the CPU power. The blocking model is common in drivers that write to memory and have a strict timing requirements. The other method is a non-blocking model where one task will call a driver task to run concurrently. The calling task will continue its execution in parallel with the device driver, and at a later point in the execution the driver task will communicate back to the calling task and terminate. The non-blocking model is very common in interfacing with slower devices like sensors, analogue to digital converters, and communication interfaces.
SynthOS has a build in support for both those blocking and non-blocking execution, and the infrastructure code to support both of them is created automatically by using the basic SynthOS primitives. For the blocking method the basic primitive to use is SynthOS_call(). This will start the device driver as a new task and in turn suspend the execution of the calling task. The calling task will restart its execution once the driver has completed its operation and exits.
// create data
// wait for the driver
The limitation of the blocking method becomes clear when multiple tasks are trying to access the same peripheral through the same device driver. Once the first task calls the driver, all other tasks that will call this driver will stop execution until the device driver completes execution. This will introduce inefficiencies in the system when all requests are serialised, and most of the time is spent waiting on the blocking device driver to execute. One way to avoid this is to check that the device manager task is free, using the SynthOS_check() primitive, which will return a value of true if the task is not executing.
Non-blocking calls are key for creating an efficient multi-tasking architecture. This method enables a task to call another task and allow both of them to execute in parallel. This will minimise the time tasks are waiting for execution by other tasks and can significantly increase CPU utilisation.
To support non-blocking task execution, SynthOS uses the SynthOS_start() primitive. One task calls the driver and the calling task will continue executing. The calling task can continue operation, ignoring the called task until it needs to call it again. If the calling task gets to a point that it needs to ensure the device driver completed its execution, the main task can use SynthOS_wait() primitive to synchronise with the driver execution. To enable waiting on a specific instance of the transmit or receive tasks, you can create a lookup table and wait on a specific entry in this table rather than waiting for all tasks to complete.
The XBee code
Figure 6 shows the high-level structure of the code. The main application code can call the receive task xbee_receive() when it is expecting a message or xbee_request() when it is sending a command. If the application needs to continue execution while waiting for the XBee communication, SynthOS_start() can be used to call this tasks. The XBee module will response via interrupt when new data is received or the transmit buffer is empty. If new data is available, the interrupt will activate the receive function uart_receive_byte(). Once the data is read the receive call task will be notified and the interrupt released.
The same mechanism is used for the transmit side. The interrupt is asserted once the output buffer is empty. This will call the uart_transmit_byte() function that will load the buffer, notify the call task xbee_request(), and release the interrupt.
In this article we have described the development of a specific XBee driver for our Arduino robot using SynthOS. We also have described some of the important considerations for developing device drivers in general. We found that SynthOS handled the inter-task communication and scheduling with little need for us to understand the details of the implementation. The software took two weeks to develop and debug and the entire system fit in 4.5K bytes of flash memory and used only 273B of RAM during execution, making it extremely efficient.
About the authors
Jacob Harel is the Vice President of Business Development/Product Management at Zeidman Technologies. Jacob spent the early years of his career at Applied Materials and as a senior consultant for a variety of semiconductor fabrication equipment companies. His previous position before joining Zeidman technologies was Senior VP of Product Operations at Luidia, a leading provider of interactive technology (Luidia was sold to PNF Korea in 2013). He is the named inventor on several patents. He holds a BA in Computer Science and Economics from Tel Aviv University.
Igor Serikov is the Lead Engineer at Zeidman Technologies. He has devoted his career to the development of computer algorithms and software development tools. He has been a consultant to companies including Sygate, Symantec, NTT Docomo, Wyse Technology, and Ricoh Systems. Igor has a Bachelor's degree in computer science from National Taras Shevchenko University of Kyiv.