Porting lwIP to UC/OS-II
For this year Eurobot, the CVRA is building two different robots, and also doing some computer vision. While this sounds nice, it also comes with added complexity : The two robots have to communicate with the two computer vision boards, and with each other, using the communication architecture below (Debra-4 being our main robot with SCARA arms and Nastya-2 our holonomic platform).
Developping so much communication between embedded systems can be quite a headache, especially when it comes to debugging and trying to take a peek at what the messages are. It would be really nice if we could spare the time of writing the communication stack and debug tools and jump straight to the application. This is where IP (either TCP/IP or UDP/IP) becomes useful : It take cares of packet routing, differenciation using ports numbers and we also have fantastic debugs tools available, like Wireshark.
lwIP is a small implementation of the IP protocol, whose main focus is to avoid relying on specific OS constructs and keeping a low memory footprint, which makes it perfect for embedded systems. In addition to TCP/IP and UDP/IP, it provides PPPoS, which will be really useful to us, as we don’t have Ethernet connectivity on the FPGA boards.
As you can see on the architecture schematic, both of our FPGA boards will be running a shiny NIOS-II softcore processor, which in turn will be running the UC/OS-II operating system. While softcores have been in use at the club for four years now, we did not have any kind of OS on it, only a simple, homebrew and buggy round robin scheduler, which really needed o get replaced by something cleaner. At first we were hesitating between two operating systems, UC/OS-II and FreeRTOS. The second appealed to us due to its Open Source License, and a greater feature set than UC/OS-II. On the other hand, UC/OS-II was widely used, was extensively documented and had previously been sent to Mars by the NASA (which is pretty cool). What finally decided towards UC/OS-II was the fact that there was a commercial port of it on our target platform and we wanted to avoid the time expense of porting an RTOS ourselves.
Now that we know what operating system will run our lwIP port, we can start searching for some documentation about the porting process.
The main sources of information regarding the process of porting lwIP to a new system are the Porting guide,
doc/sys_arch.txt in the lwIP source tree and finally, the comments in the source itself.
After a bit of reading, we know that we will have to provide three files :
cc.hThis file is responsible to provide various typedef’s and informations about the target compiler / CPU.
sys_arch.hThis files includes all the necessary files and defines the
sys_mbox_ttypes for system semaphores and mailboxes.
sys_arch.cThis files defines a set of functions used by lwIP to create tasks, semaphores and mailboxes. It also needs to provides a few utilitary functions to get the system’s time.
This file is pretty straightforward to implement : it only contains types and simple macros. The types can be defined as follows.
Please note that this code snippet defines lwIP types using
stdint.h types, which may not be available on your target platform.
A better way of doing this would be to use UC/OS-II types.
After defining basic types, we have to provide compiler hints about structure packing. In this application we are using GCC, and searching around the wiki and mailing list gives us the correct implementation.
We have to provide two macros to output diagnostic messages. Those macros are specific to your target board/platform, and not to UC/OS-II, so you should adapt my implementation to suit your needs.
Finally, we need to provide lwIP some to way to guarantee atomicity of a piece of code. In this port I am using the “lightweight” synchronization mechanism, which means we disable interrupts while in an atomic code block. To implement this, we need a macro to disable interrupts and another macro to return the interrupts to their previous state, which is saved in a stack variable, using a third macro. We can find information about how to obtain atomicity in the UC/OS-II reference manual.
Semaphores are a standard construct used in multithreading programming to implement synchronization between tasks or to guarantee exclusive access to a shared resource. They can be either binary or counting (lwip accept both).
Once again the UC/OS-II reference manual comes in handy to learn how they are implemented and how to use them, which is fairly easy (an example is provided). The only pitfall is that lwIP requires a way to mark semaphores as being ‘invalid’. I don’t know what this is used for, but we need to implement it so…
For semaphores you only need to provide one thing : the definition of the semaphore type.
I used a structure containing a pointer to an
OS_EVENT, which is the ‘real’ semaphore and a validity flag, which is simply an integer.
Put the following in
Now we have to provide implementations of semaphores functions.
The complete list of functions that you need to implement can be found in
The first function that we need to provide is the one used to create a new sempahore.
It takes the semaphore’s initial value as a parameter.
The second function that we need to provide is less trivial : it has to wait until the semaphore becomes available or a given timeout is reached.
The return value is either the time we waited on the semaphore or
SYS_ARCH_TIMEOUT if there was a timeout.
We also need to convert the timeout value, since lwIP’s timeouts are always in microseconds where UC/OS-II’s timeouts are alway given in scheduler ticks.
So far I have yet to implement the measurement of the waited time.
I have an idea about how to do it, but I have still to test it (Update: in 2015 we switched to another OS so it won’t be done).
The next function we need to provide is the opposite of the previous one : it realises the post operation, while the previous implemented the pend operation. Here again, we can use an example from the reference manual.
We then implement two functions to test if a sempahore is valid and to mark it as invalid. As UC/OS-II does not provide a standard way to implement it, we had to add a validity flag to the semaphore definition, which is used to implement those two functions.
The last step to implement about semaphores is the deletion of a semaphore.
A mailbox is a software construct used in multithreaded programming to exchange data between two tasks. The UC/OS-II manual defines two types of mailboxes :
- Mailboxes A mailbox is a variable in which a task can put a value to be read by another task. Read and write access to it is atomic, and a task can pend on it (wait for it to receive something).
- Mail queues A mail queue is similar to a mail box, the main difference being that only one value at a time can be put in a mailbox, while a mail queue can store several messages and deliver them in a FIFO fashion.
Note: After reading lwIP manual, it is clear that what UC/OS-II calls a mail queue is called a mailbox in lwIP.
Another important problem is that lwIP requires the mailing queue implementation to be able to exit immediatly if the queue is already full, which is not supported by UC/OS-II. To implement this behaviour, we use a counting semaphore which stores the number of available slots in the mail queue. If a thread wants to write to the queue, it must first acquire the semaphore, then it can safely write to the queue. When a tasks reads an item from the queue, it will post to the semaphore to tell every waiting task that a slot is now available in the queue. The last feature we need to add to the UC/OS-II queues is a validity flag, similar to the one used for the semaphores. In summary the structure looks like this :
First of all, let’s implement mail queue creation. We need to create a queue, but also a counting semaphore that will be initialized to the queue size.
We can then implement blocking posting to the queue. To post in a queue, we have to wait on the associated semaphore to have an available slot. For this first function, we will do a blocking semaphore wait.
After that, let’s proceed to the non-blocking posting to the queue. This is where our semaphore gets useful, as we can ask UC/OS-II “if this semaphore is immediately available, take it, otherwise return an error code”, which we cannot do with queues.
Ok, writing to the queue is now implemented, we should proceed to implementing blocking and non blocking reading of the queue.
Both of them will also operate on the semaphore, but they should do it in reverse order than the writing block : they start by reading from the queue, then posting to the semaphore, which ensures
that the semaphore will never have more counts than free slots in the queue.
Let’s start with the blocking variant, which have a timeout.
It should return the time the task had to wait for a message to arrive, or
SYS_ARCH_TIMEOUT in case of timeout.
The non-blocking version is shorter, but similar:
Finally we write the functions to mark a queue as invalid, and to delete it.
The completed port can be found on https://www.github.com/cvra/lwip_ucos2, along with other projects we developped at the CVRA.
A demo application using the port can also be found on https://www.github.com/antoinealb/lwip_test.
Our port of lwIP is based on original work by http://geocities.com/michaelanburaj/, and was modified to suit recent versions of UC/OS-II and lwIP.