scmRTOS - small RTOS for mbed

09 Sep 2010

I know there were FreeRTOS ports done before but they used an external compiler and I personally find FreeRTOS big and confusing. I looked around and found something called scmRTOS. It makes quite heavy use of C++ OOP and templates so I was a bit skeptical at first, but looks like it's used quite sensibly - to make most of the OS configuration resolve at compile-time. No RTTI or exceptions are used, and the generated code looks quite compact.

There was a port for Cortex-M3 (STM32, but chip-specific bits were nicely isolated), so I could make it compile with the online compiler pretty quickly. And it does seem to work - I have three processes running which communicate using "event flags" (binary semaphores) and happily toggle the leds. The docs are somewhat Runglish-y, but at least the are some available!

Check it out: scmRTOS_test

If you want to use the code for your own program, don't forget to change at least the process count in scmRTOS_config.h.

I'm still learning the code myself but if you have any questions, ask away - I can at least cross-check with the Russian docs.

10 Sep 2010

Hi Igor

I think this is what I have been waiting for. I want my mbed to be doing lots of things at once; reading I/O, serial comms to modem, temperature sensors, internet.

I have a particularly time sensitive part of my code which is a power monitor, where I am sampling current and voltage at set frequency. I do this at the moment using the mbed Timer (timer 3 in 1768). Most of the rest of my code runs in the main thread and I use critical sections where necessary for timings more important than the sampling (eg. I2C and one-wire comms). I have been waiting for a scheduler to think about writing it more efficiently.

I understand that the scmRTOS uses the system tick timer for task switching. Could I still run my sampling code on the mbed Timer outside of the RTOS, and if so how would I give the mbed Timer priority over the system tick timer (if indeed possible/sensible). Or should I just be looking to make everyting a task in the RTOS? I notice there seemed to be some inherent priority ordering of tasks in the code.

Thanks
Daniel

 

10 Sep 2010

How do task priorities get sorted out? This looks fantastic. Thanks!

10 Sep 2010 . Edited: 10 Sep 2010

You specify the priorities when declaring the processes, e.g.

typedef OS::process<OS::pr0, 300> TProc1;

pr0 is the priority. My test project uses descending order, so pr0 is lowest priority. One peculiarity of scmRTOS is that no two process can have the same priority, so you need to watch it carefully.

 

12 Sep 2010

Hi Igor

I've been having a play and discovered (not surprisingly) that setting stack size for the processes is critical - too small and you get a hard fault (or garbage in stack variables). Do you have any ideas about how to set stack size or is it going to be a process of trial and error?

Thanks
Daniel

12 Sep 2010

The process' stack is placed in the class member Stack (see definition of process template in OS_Kernel.h). What you can do is change the constructor to fill it with a known value and then at some point during execution walk the processes and see how many of those value still remain. Not very convenient but reliable.

As for determining the initial value, you could try the following:

  1. Every function needs to save a couple of registers - usually around 4-6 but could be up to 31. Registers are 4 bytes each.
  2. Then it needs space for local variables. To be safe you can add up sizes of all variables, though compiler might reuse stack slots for variables with non-intersecting live ranges.
  3. If you call any other functions from the process' main entry, add their sizes too.
15 Sep 2010

Hi Igor

Thanks for the suggestions, I will try that once I have sorted out the RAM usage of the networking stack. My first impressions of scmRTOS are very good, and should lend a new lease of life to my mbed programming. I'll post back here with progress.

Regards
Daniel

15 Sep 2010

Here's a sample implementation of the idea.

16 Sep 2010 . Edited: 16 Sep 2010

hey igor will the mbed device library work with it? ie serial/can/ethernet

16 Sep 2010
user avatar Igor Skochinsky wrote:
Here's a sample implementation of the idea

спасибо игорь, the other thing I was thinking of doing was putting these stacks in the AHB RAM to save on memory (not AHB0 because that is being used by the networking stack). Is that a good idea?

user avatar Jason Engelman wrote:
will the mbed device library work with it? ie serial/can/ethernet

Hi Jason, I have had one process reading AnalogIn objects and another process posting to the internet using the HTTPClient, so I would say yes to your question. The RTOS supports critical sections and mutexes so you can avoid the issues associated with multiple threads. Not tried any serial yet (but I will be).

Regards
Daniel

 

16 Sep 2010

It looks very promising! I too found FreeRTOS to big and confusing, this is just the "easy RTOS" I was looking for. Thanks!

21 Sep 2010

Hi Igor

I've had more of a play, I implemented the used stack size code but I have come across a problem not related to stack size (I think).

With only two processes (#define scmRTOS_PROCESS_COUNT 2) and the main.cpp here (RtosTest), I think there is a surprising result (or maybe it is not surprising and I shouldn't be attempting this).

The idea is that a global variable is updated by the sys tick handler, and then printed out by the two identical processes. Only the second process prints it correctly. Is there an explanation for this?

Initialising ...

2000
[1] 8589934592002
[2] 2001

4000
[1] 17184164151298
[2] 4002

I reduced the problem to this simple example, but I came across it because I want to access member variables of classes from different processes, and the values were not made available correctly.

Thanks
Daniel

 

 

 

21 Sep 2010

It seems that somehow the first process gets a skewed value (8589934592002 = 0x7D000000002, and 0x7D0 is 2000). I'm not sure how that happens but it will probably disappear if you use some kind of synchronization to access the variable, e.g. a mutex. You could also try sending a message with the counter value from the timer function instead of using a global variable.

21 Sep 2010 . Edited: 21 Sep 2010

Hi Igor

I tried putting a critical section "TCritSect cs;" (rather than a mutex) in front of the Proc1 and Proc2 Exec printf method and it made absolutely no difference. The tick update hook is already called from a critical section due to the "scmRTOS_ISRW_TYPE ISR;" definiton in OS_INTERRUPT void OS::SysTick_Handler().

Any suggestions about how I might debug this? It should still be safe to access global variables in a critical section?

Regards

Daniel

 

21 Sep 2010

Hm yeah, critical section is rather overkill but should have solved the problem... I need to think about it a bit more.

24 Sep 2010

It probably isn't your problem, but in general with variables that can be updated out of the view of the compiler you should mark them 'volatile'.  Otherwise the compiler will believe it's OK to keep copies in registers.

For variables with greater than function scope, the compiler won't keep copies across calls to other functions, so you shouldn't be hitting it here, but it's worth thinking about.

24 Sep 2010

Hi Mike

Thanks for the comment. I made the global variable volatile but the problem didn't go away.

Regards
Daniel

02 Oct 2010 . Edited: 03 Oct 2010
user avatar Igor Skochinsky wrote:
I need to think about it a bit more

Hi Igor

I've had more of a play with this, because I reproduced something similar using a simple round robin scheduler instead. It looks like the problem is with printf, rather than access to the variable itself.

I used hexview to demonstrate that the memory values associated with the count variable were the same no matter which task it was accessed from (you'd really expect that). I then modified an itoa code sample for long long and got the right result from all the tasks.

This only leaves the printf as the culprit. I found this note about using printf when the stack is being reorganised; not sure it is entirely relevant and not sure the workaround helps (because I already tried a critical section), but I think the moral of the story is to avoid printf for now.

Regards
Daniel

PS I tried formatting the long long with the stream output rather that using a printf and it still messed up, so I'm not sure it's exactly printf that is at fault (because integers and strings print ok). I think it is ok to use printf except for long long (64 bit) and float/double, so for those I've written my own "to string" conversions.

 

03 Oct 2010

Hi, I am using the mbed device in my final year project to form the brains of a remote controlled helicopter. I am going to be using ScmRTOS in the project as well. I am just getting started and am wondering how I would go about setting up a process to be set to active when a serial RX interrupt is asserted. Before using ScemRTOS I could just use the attach function to call a function on the interrupt but I am just wandering how this will tie in with the RTOS?

 

Thanks,

Greg

03 Oct 2010
user avatar jason berry wrote:
how this will tie in with the RTOS?

You can have processes (with their priorities) and you can still have interrupt service routines (ISRs). According to the manual, you should mark ISRs so that the RTOS knows about them. Have a look in the documentation about the TISR_Wrapper class.

By the way, so far I merged some code of mine making extensive use of mbed Timer object events, and it doesn't seem to affect the processes that are running. I think this is because the mbed interrupt events disable further interrupt processing, and I'm not using any inter process communication at the moment.

Regards
Daniel

03 Oct 2010

user avatar Daniel Peter wrote:
user avatar jason berry wrote:
how this will tie in with the RTOS?

You can have processes (with their priorities) and you can still have interrupt service routines (ISRs). According to the manual, you should mark ISRs so that the RTOS knows about them. Have a look in the documentation about the TISR_Wrapper class.

By the way, so far I merged some code of mine making extensive use of mbed Timer object events, and it doesn't seem to affect the processes that are running. I think this is because the mbed interrupt events disable further interrupt processing, and I'm not using any inter process communication at the moment.

Regards
Daniel

Hi again and thanks for the quick response.

Ok I have read the bulk of the manual over the course of the weekend and with regards to what you suggested,
I have taken another look and found this:

 

#pragma vector = TIMERB0_VECTOR
 __interrupt void Timer_B_ISR()
 {
 OS::TISR_Wrapper ISR;
 ... // some code
Timer_B_Ovf.SignalISR();
}
so, if i define a function at global scope like the one above, attach it to the serial interrupt, and get the function to signal a teventflag for example, is this sufficient?

Also I am getting another strange little error,

when trying to add another process, as in below, I get the following error:

"Namespace "OS" has no member "pr3" (E135)" in file "/scmRTOS_test/main.cpp"

typedef OS::process<OS::pr3, 300> TProc4;

 

Thanks for your help,

Greg

03 Oct 2010 . Edited: 03 Oct 2010
user avatar jason berry wrote:
when trying to add another process, as in below, I get the following error

 Hi Greg, I'm going to refer you back to the post by Igor right at the top ...

user avatar Igor Skochinsky wrote:

 

If you want to use the code for your own program, don't forget to change at least the process count in scmRTOS_config.h.

With regards to your question about the use of an event flag, this goes to the heart of using an RTOS. Imagine you are reading serial data from a GPS (for example) and want to buffer the data until you see a new line character, and then parse it. In an RTOS you'd do the following:

You'd have your serial interrupt handler write characters into a buffer (one of the process safe objects provided by the OS). You'd only signal the event flag when you received your new line character.

Now, you have a process waiting (blocked) on the event flag. When it is signalled, that process empties the buffer and parses it as far as the new line character. With the buffer object being process safe, you should be able to empty it in your process at the same time as the buffer being filled up by the serial interrupt. If you used your own buffer rather than a process safe one, you could protect it with a mutex.

Your process which parses isn't continually polling for a new line character, it only becomes active on the event being signalled; that is the beauty of the RTOS approach.

With what I've just described, you are not calling signal on the event flag every time you enter the ISR. You are only signalling the event flag when something of interest to a process blocked on it has happened.

Hope that helps.

Regards
Daniel

03 Oct 2010

Thanks for that Daniel and yes it certainly is helpful. Yup I forgot about the process count. In fact what you have just described is exactly what I want to do (except that the serial data is coming from my pc. I am in the middle of converting a program I have written into something compatible with the RTOS. This is the first time I have tried coding with any OS let alone a RTOS.

 

So just to make sure I understand what you are saying...

I create a function that is called when a RX interrupt occurs, store new characters in a process safe object like a channel, check for a newline character (which is how I was checking anyway) and signal a waiting process if a newline is found. The waiting process could then deal with the message in the channel and flush it.

 

thanks a million.

 

03 Oct 2010

Hi Greg

Yep, you've got it, now you just need to code it! The beauty of dealing with the channel in a process is that it can be done at a low priority, providing you've got enough buffer space.

Don't forget that TISR_Wrapper at the start of your ISR function; that should make sure your access to the channel is process safe, if I understand the documentation correctly.

Try to make the ISR code as short as possible, because it's blocking other things from happening.

I'd keep on eye on your stack sizes (which increase the more nested interrupts you have). I've implemented the UsedStackSize code from the forum as mentioned by Igor above. It certainly gave me an opportunity to look into the code a bit more, but if you want I'll give you the changes needed. And watch out for printf garbage (also mentioned above).

Good luck!
Daniel

03 Oct 2010

Cheers Daniel,

If I may, I would just like to show you what I have so far for the ISR code for the serial interrupt.

my channel that I am using to store the received command is called RX_chars. The function below gets attached to the serial object - pc in main().

The function is not working as expected however as it seems to send a signal for a single received command. Followed by the program crashing. I know the program stops functioning as I have a LED toggling as one of the processes. This stops occurring.

#ifndef SERIAL_H
#define SERIAL_H

#include <processes.h>

Serial pc(USBTX, USBRX); // tx, rx


void signal_serial() //signal RTOS event that RXinterrupt has been asserted
{
    char temp = 0;
    
    OS::TISRW ISR;
    while(pc.readable())
    {
        temp = pc.getc();
        if(temp !='\n')
        RX_chars.push(temp);
        else if(temp == '\n')
        {
            RX_chars.push('\0');
            RX_flag.Signal();
        }
    }   
   
}


#endif
I think I am doing something wrong in my interrupt ISR as shown above.

For clarity, I have included the program which, when it is finished will parse the command and deal with it.

#pragma once
#ifndef COMMAND_HANDLER_H
#define COMMAND_HANDLER_H
#include 



template<> OS_PROCESS void Command_handler::Exec() //serial stream handling process
{
    char RX_Command[100] = "";
    for(;;)
    {
       RX_flag.Wait(); //suspend and wait for a command
       RX_chars.read(RX_Command, RX_chars.get_count());
       RX_chars.flush(); //flush the channel 
       pc.printf("Command received was: %s \n", RX_Command);         
           
        
    }
    
}  


#endif

Thanks again.

Greg

04 Oct 2010

Hi Greg

I coded up a simple example (push only not pop) of what you are doing, and got a lock up as well. I only got the lock up when calling push on the channel; if I commented out that line, then there was no problem.

I then looked into the OS source code, and could see that in the channel template the default type for the size of the channel buffer (the S you see in the template) is "byte". However, I'd set my channel size to 256, which would overflow that.

I redefined my channel definition to be OS::channel<char, 256, int> RxBuffer; and the code then worked. Note that the third parameter int is not documented in the version 2 documentation.

Did you set your channel buffer size to be greater than 255 by any chance?

Note also that if you don't have space in the channel, then a push should block - not a good idea in an ISR. Therefore you should probably throw away the characters under those circumstances (ie. test for whether there is free space before pushing), and maybe set a flag somewhere else to indicate you're getting a buffer overflow.

Regards
Daniel

04 Oct 2010

Hi Daniel,

thanks for the very insightful reply. I am afraid my problem was something I should never have brought to you in the first place. The problem was due to the printf statement that I had in my process, something you ahve mentioned already. Removing this sorted the problem. What you have mentioned about the channel size though is useful to know though.

I did not pay too much thought to my process though as I was fairly certain that I had made a mistake in the serial function that was called upon an interrupt.

 

Apologies, I hope I did not waste too much of your time.

Greg

04 Oct 2010

Hi Greg

I'm happy to help since I'm learning about this at the same time, and I want to use it for similar things (in my case I'm going to hook up a mobile phone thru the serial port for text messaging).

The problem I mention above about printf shouldn't block a process, its about the printf output being corrupted when printing 64 bit integers or floats/doubles (so watch out for that).

The problem I think you mention in your last post is perhaps something different, and relates to a problem with reading and writing to the same serial port using different processes (or interrrupt handlers). There's a lot of discussion on this thread here. You shouldn't be trying to write to the stream at the same time as getting characters from it (unless the re-entrancy problem is fixed in the beta compiler). You should arrange mutually exclusive access to the serial port, which is what the RTOS is for!

Regards
Daniel

 

04 Oct 2010

Thanks Daniel,

I shall definitely take your advice about using the Mutex. One  more question for you... I noticed in the v2 manual on page 41 it mentions that program control must not leave the process' root function.

"Program control flow must not leave process’s root function, otherwise, since this function was not called in ordinary manner, on function exit the program control flow will transfer to undefined place and program will crash;"

Do you know if it is still safe to call a function from the exec() root function? I have been doing just that and it seems fine. Or does the above quote just mean that there is only one function allowed per process?

04 Oct 2010 . Edited: 04 Oct 2010

Hi Greg

I think what it means is that the Exec() function must never return ie. must always have an infinite loop inside which you do the work of the process. It doesn't mean that you can't call other functions from within that infinite loop.

So the following code is fine as you have discovered:

template<> OS_PROCESS void TProc0::Exec() {
    while (true) {
        DoWork(); 
        Sleep(5000);
    }
}


But if you were to try the following, you'd have undefined behaviour:

template<> OS_PROCESS void TProc0::Exec() {
    DoWork();
}

In fact, try it. The compiler complains about Exec() "Function declared with "noreturn" does return".

Regards
Daniel

PS Good work reading the manual by the way, I've only dipped into it!