In this installment we will look at an example project based on BeRTOS. The goal is to learn a bit about how processes works and at the same time play around with the drivers provided by BeRTOS.
For new readers, BeRTOS is a real-time operating system for small embedded systems based on AVR, Arm7 or Arm Cortex M3. It provides a minimal kernel and a set of modules adding functionality to the API. This way, the system can be configured to be small, but at the same time, the API provided is rich enough for the task at hand.
The benefit of using an real-time operating system, as opposed to bare metal, in a small embedded system is, in my opinion, the access to processes and timers. This means that you get out of the classical monolithic main loop approach that is commonly used in smaller systems. Distributing the functionality over processes correctly means that you can scale your software. The time aspect also means that you know that things get done on time.
To create a process in BeRTOS, you must create a stack, and a “main” method for the process. The stack is setup using the PROC_DEFINE_STACK macro, and the main method is expected to have a void f(void) signature.
PROC_DEFINE_STACK(commStack, KERN_MINSTACKSIZE);
void commMain(void) {…}
The KERN_MINSTACKSIZE is a define that is usually safe to use, however, you should be aware of it when using systems with very tight memory restrictions or implementing processes with a high level of stack usage, i.e. deep calling trees or many variables allocated on stack.
In the system’s entry-point main function, you must initialize the kernel, register the process and give it a priority.
void main(void)
{
proc_init()
Process *pComm = proc_new(commMain, NULL, sizeof(commStack), commStack);
proc_setPri(pComm, -5);
…
The call to proc_init setups the kernel. The proc_new call instantiates the process from a method and a stack. The second argument, NULL, can be used to pass data to the process in question. The data is accessed through the proc_currentUserData method, from inside the process.
Notice that the process is already running after the proc_new call. The proc_setPri call then assigns the process with a new priority, to allow the kernel scheduler to prioritize it alongside other processes of the system.
It is also worth noticing that the entry-point main function of the whole system is considered to be a process. So do not forget to use it to implement some functionality as well.
The second half of using a real-time system is the time awareness. This is handled by the timer module of BeRTOS. Here, it is possible to delay until a given time, read the time, convert ticks to and from ms, etc. Using this toolkit, you can syncronize a thread to, for example, take samples at a given rate with minimal jitter.
The nature of the problem of delaying means that there are many ways to wait. One can busy wait, i.e. run a loop until the given time has passed, but one can also wait for a timer to emit an event, i.e. relinquish the processor to other processes while waiting. What is the best solution? It all depends on the task at hand.