Preparation
This guide is meant to get you started writing your own tasks. It is valid only for version 0.91 of Femto OS. Please select the correct version first, for names, codes and descriptions made be significantly different between the versions.
I assume you already where able to build the toolchain, flash the prebuild binaries and compile the examples from the command line. This is all described in the Readme file that goes with the distribution. Having done that, this is the place to continue and build your first application.
The preparations you have to make, is to make sure you have a working development board
with a mounted and supported microcontroller. Connect the leds and switches to the correct
ports. Leds usually must be
connected to PORTB
, except for the tiny's: 43u 261,461,861 for these
must be connected to PORTA
. The switches usually must be
connected to PORTD
, except for the tiny's: 43u 25,45,85,261,461,861, wich have
them on PORTB
and the tiny's: 24,44,84 which have them on PORTA
.
More info in the FemtoOS_0.91/
directory, look for the file that matches your device.
Note that the latter tiny's are so small they share the led- and switchport, which makes
experimenting a little hard. Most examples will not run out of the box.
Hello World, part 1.
I start by explaining the Hello World Application, and after that, we will make some modifications
and see how the OS behaves. Please note this example only runs as described in the latest
version (Femto OS v0.91) of the distribution. All examples consist of two files, in this case code_TestHelloWorld.c
and config_application.h
. The latter name is identical for all examples so that they
can be included automatically, and contains, well, configuration parameters.
Since we use the my compiler script, the first is fixed too, and will contain our code.
Code files must include femtoos_code.h
to
make all OS functions available, and nothing more.
Now you will usually need some variables, say for example an integer and a
string. For the moment, it is important to make sure NO initialization is put in the
.data
section; which is a waste anyway. So we initialize the integer to zero,
and make sure the string is put into flash. Thus up to this moment our code_TestHelloWorld.c
looks like:
static Tuint08 speed = 0;
const Tchar HelloWorldStr[] PROGMEM = "Hello World.";
Following misra, i redefined all types uint8_t, unsigned char
etc to Tuint08, Tchar
. Their definitions can be found in femtoos_globals.h
,
but they are pretty straightforward. You can of course keep using standard types if you like.
Further, we want to define a name for our first task. All tasks in Femto OS have name literals, and
the preprocessor gives each task a number (0..15) depending on its use. Since you do not know this
number, you refer to the task by name always. Naming is done inside the config_application.h
file,
from which we show the relevant part:
/* TASK NAMES ============================================================== */
/* ========================================================================= */
#define CN_00 Display
Note that all configuration parameters are described on the Code | Config page on this site. From now on, we can use the name "Display" if we want to refer to that task. Ok, now we have defined the name, let's define some code. We want to let task display the ascii values from the words "Hello World" on the leds. Here it is:
void appLoop_Display(void)
{ speed = 64;
while (true)
{ Tuint08 uiCharCount;
Tchar cChar;
for (uiCharCount = 0; uiCharCount<12; uiCharCount++)
{ cChar = portFlashReadByte(Tchar, HelloWorldStr[uiCharCount]);
devLedPORT = ~cChar;
taskDelayFromNow( ((Tuint16)speed)*4 + 64 );
devLedPORT = 0xFF;
taskDelayFromNow(64); }
taskDelayFromNow(2000); } }
#endif
We see a couple of things. The code itself is packed within a preprocessor check to see if the taskname is defined.
This is just to make sure we do not include code, when the name is not defined. It is not stricly necessary.
Then we see that the task is called appLoop_Display()
. This is convention. All user defined
code is called from the OS using the app
prefix. The appLoop
stands for the
main loop of the task, and the name you have defined before is simply added to that, thus we get
appLoop_Display
. Femto OS calls that function after the context has been setup, and
that function should NEVER return. To be certain, the rest is packed in a while(true)
loop. Then we see an character counter variable and character variable, followed by a loop
to read the characters. Nothing special.
To read the characters from flash, we make use of a port function (all portable functions used
by the OS are packed in the femtoos_port.h
and femtoos_port.c
files)
and we can make use of that too. Here we use:
to read one character in a 'typed' way. Subsequently we write the value of the character to devLedPORT
,
a device independent way to write to the port whith the leds connected. (Not all devices have for example PORTA
available for output. Every device has a seperate file where these definitions are given. For example the
Atmel tiny861 has the file femtoos_ATtiny861.asm
.
After we have written the chacter to the outside world, we want to pause for a while.
To that end we call taskDelayFromNow(128)
(speed variable set to 64 at this point).
This is an OS method that may only be
called from within a task (hence the 'task
' prefix), and will delay the task for 128 ticks.
The whole API of Femto OS is described on the site as well: Code | API.
The time
one tick takes depens on many variables, but for now, please accept we have set it to 1ms.
This call will lead to a context switch, but since we do not yet have other tasks, the OS
will start waiting in the idle mode for the requested time. However, before we can make use
of the call, we must configure it to be used.
/* API INCLUSIONS ========================================================== */
/* ========================================================================= */
/* Standard functions ------------------------------------------------------ */
#define includeTaskYield cfgFalse
#define includeTaskDelayFromNow cfgTrue
#define includeTaskDelayFromWake cfgFalse
#define includeGenSuspend cfgFalse
This is a general policy. Any function (or group of functions) you want to use must be switched on in the config file. This is to make sure the preprocessor only includes the required functionality in the OS. Observe the other includes to be false. (Use cfgFalse and cfgTrue instead of plain 'true' and 'false'. This enables further quality checking by the preprocessor.)
Although this may seem quite complete, only a few things are missing. First, we usually
must initialize the hardware before use, and second, we must tell the OS how
much stack must be reserved for the task. Standard initizalition, such as cleaning the
memory, resetting the watchdog etc is done for you by the OS, but if you want to
use some ports, they must be initialized. Femto OS calls a special function,
named appBoot()
just once, so this is ideal for our situation:
{ devLedDRR = 0xFF;
devLedPORT = 0xFF;
devSwitchDRR = 0x00; }
As you can see, de define the ledPORT to be outputs, and clear all leds, and we define the switches to be input. Of course, we must make sure that Femto OS indeed calls this method:
/* EXTERNAL CALLS ========================================================== */
/* ========================================================================= */
#define callAppTick00 cfgFalse
#define callAppTick08 cfgFalse
#define callAppTick16 cfgFalse
#define callAppTickSleep cfgFalse
#define callAppEnterIdle cfgFalse
#define callAppEnterSleep cfgFalse
#define callAppExitSleep cfgFalse
#define callAppBoot cfgTrue
#define callAppInit cfgFalse
#define callAppBark cfgFalse
As you can see, there a lot more hooks possible, but for now, we only need a call to appBoot()
.
The last thing we need to do is to define the use of memory, in this example,
only the stack.
/* STACK SIZES ============================================================= */
/* ========================================================================= */
#define StackSafety 4
#define StackSizeOS 24
#define StackSizeISR 0
#define StackSizeShared 0
#define StackSizeOverride 46
The StackSizeOverride
is used to define the taskspace for all tasks. And since we
have only one task this will do fine. (Defining overrides is possible for all parameters
that must be defined on a task by task basis.) Furthermore, we see the definition of the stackspace
for the OS, and a StackSafety
. We will explain that lateron.
What we have done up to this moment should result in runable code. Of course, we did not
discuss every tiny bit of it, and there is also second task in the code which i will
adress in a moment. But, out of the box, this should run. So go to your commandline
(cygwin if you are on windows),
change to your FemtoOS_0.91/IDE directory and type: ./script/compile HelloWorld attiny861
and substitute your device. Subsequently flash it, by providing the device and comport of your
development board.
text data bss dec hex device project
1780 0 78 1858 742 attiny861 HelloWorld
ruud@wolf IDE $ ./script/flash /dev/ttyS1 stk500v2
At compiling and flashing, parameters need only to be specified once. If the operation was succesfull the
script stores the last parameters, so you can skip them. Get the full syntax of the commands by typing
help
as parameter. If the leds are very slow, then you are probably running on a speed
lower than 8MHz. This typically happens when your device does not have a clock prescaler,
(atmega 8,16,32,64,128,8515,8535). You need to fuse it to make it run on 8MHz:
This command will activate the internal clock and set it to 8MHz (other bits on the fuse are left alone). If all is well this should be your result.
Hello World, part 2.
For one task, we don't really need an OS, so we must at least define a second. Lets read the value of a switch, which controlles the speed at which the letter are displayed.
void appLoop_Speed(void)
{ Tuint08 button = devSwitchPIN & 01;
Tuint08 lastbutton = button;
while (true)
{ button = devSwitchPIN & 01;
if (button != lastbutton) { speed += 64; }
taskDelayFromNow(100); } }
#endif
The idea of this piece of code is first to read the current setting of the switch, which is assumed to be the deactivated mode, and subsequently wait for a change in the setting. If that change happens, we increase the speed, and wait 100 ms to suppress bouncing.
Up to this point the code ran like this, out of the box, but now, you must make the first changes. The taskname has already be defined, but we must make sure the task is really included into the code. There go to the config file and make adjustments to go from:
/* INCLUDE TASKS =========================================================== */
/* ========================================================================= */
#define TaskInclude_Display cfgStartRunning
#define TaskInclude_Speed cfgExclude
to (changed values are bold and cursive):
/* INCLUDE TASKS =========================================================== */
/* ========================================================================= */
#define TaskInclude_Display cfgStartRunning
#define TaskInclude_Speed cfgStartRunning
When a task is set to cfgExclude, the code is not only not executed, but also not incorporated in the executable. After you have made the adjustments, let's try it:
text data bss dec hex device project
1942 0 128 2070 816 attiny861 HelloWorld
ruud@wolf IDE $ ./script/flash
When all is well, you should have two tasks running, and be able to adjust the speed by pressing the first button.
Hello World, fun part.
What we have done, could be done with any OS, so now it is time to have some fun. Basically there are two ways we could continue this demo. First we are going to downscale the current application, after that we will restore it, and add extra functionality. Also, from now on, i cannot guarantee complete succes, since what we are about to do, may differ between te different systems.
Let we first try to reduce the use of the stack on one of the tasks, to see what happens. We can estimate this by the following what happens when a context save occurs:
- 2 bytes to store the return address after
taskDelayFromNow(100)
- 2 bytes for a possible tick interrupt
- 2 bytes to store the return address after the internal
portSaveContext()
- -2 bytes for the removal of the OS return adress
- 32 bytes for the registers on the context
- 1 byte for the status on the context
since we have a very simple function we do not expect any stackuse there. Thus this adds up to 37 bytes. At this moment we have defined it to 46 bytes, so lets reduce it to 38 and see what happens:
/* STACK SIZES ============================================================= */
/* ========================================================================= */
#define StackSafety 4
#define StackSizeOS 24
#define StackSizeISR 0
#define StackSizeOverride cfgOverrideNon
#define StackSize_Display 46
#define StackSize_Speed 38
Note that we must deactivate the override value (which takes precedence when defined) and also define the stacksize parameters for all tasks. When we compile this task we see that it will not succeed any more:
text data bss dec hex device project
FAIL 0 0 0 0 attiny861 HelloWorld
The question is, how do we find out what has happened? It is alwasy possible to inspect the
file FemtoOS_0.91/Maincode/binaries/compile_results
for more details, but we can also try the 'info' switch first:
text data bss dec hex device project
FAIL 0 0 0 0 attiny861 HelloWorld
Function ===================== Stack === Regtable Individual registers use (changed) ====
Label ==== Name ========================================
Task 0 Display
Task 1 Speed
Errors and warnings === (see Maincode/binaries/compile_results for more details) =============
femtoos_check.h:983:4: error: #error "Parameter 'StackSize on task CN_01' is too small."
As you can see, the preprocessor calculated that the amount of stack space
will not be sufficient to run this task. Since the preprocessor is not able to expand
macro's inside its own messages, it cannot couple the name of the task to CN_01
,
but this is easily seen in the configuration file.
Now what has happened? Maybe you have noticed the present of the StackSafety
parameter in the
config file, which was set to 4 bytes. This parameter is our live saver. What happens is that, 4 bytes
before the real border of stack is crossed, the OS will break off the task and report an
error. Note, that the AVR has no memory protection unit, so this protection is not absolute.
The preprocessor calculates that use of the savety area in the stack is unavoidable, and
therefore produces an error.
Only when the OS itself runs, this check is performed too. Most of the cases, these error
get caught before the stack really overflows. When the task overruns its stackspace before
a context switch happens, you are still out of luck. But this tool helps you fine-tuning
and gaurding the stack use.
Try to set the StackSafety
to two,
/* STACK SIZES ============================================================= */
/* ========================================================================= */
#define StackSafety 2
#define StackSizeOS 24
#define StackSizeISR 0
#define StackSizeOverride cfgOverrideNon
#define StackSize_Display 46
#define StackSize_Speed 38
and compile again with the info switch:
text data bss dec hex device project
1976 0 120 2096 830 attiny861 HelloWorld
Function ================ Stack === Regtable Individual registers use (changed) ====
HelloWorldStr: 0 ( 0) ........
appLoop_Display: 4 ( 4) xx.x.... r31 r30 r25 r24 r17 r16
appLoop_Speed: 4 ( 4) .x.x.... r25 r24 r17
Label ==== Name ========================================
Task 0 Display
Task 1 Speed
Address Fields ======================================
0x0060 xOS
0x0078 Stack00
0x00a6 Stack01
0x00cd tcb00
0x00d1 uxTickCount
0x00d3 uiOsStatus
0x00d4 tcb01
Address Size = Unit ==============================
0x0060 0x006c ./femtoos_shared.o
0x00cc 0x0001 ./code_TestHelloWorld.o
0x00cd 0x000b ./femtoos_shared.o
Errors and warnings === (see Maincode/binaries/compile_results for more details) ========
and see that the compilation now is a succes. The other information that is presented in this result
is discussed below. You see a lot more information now. It becomes clear that task number zero corresponds to the Display
task and one to the speed task. (The information can also be found
in the FemtoOS_0.91/
file.)
Furthermore, we also learn where the variables are stored in RAM and which registers are used in the
tasks. This information will be utizled below.
Hello World, the party.
What if we really do not have more stack space available than say 30 bytes or so. Is it still possible to run the task? We certainly do not want to spend nearly 40 bytes just to read a switch! It looks like this is not possible given the calculation of the stack size above. These where all minimal values. However, there still is a way out.
If we have a look at the assembly (found in FemtoOS_0.91/
),
we can see by inspection that most of the registers of the device are not used at all:
752: 16 b3 in r17, 0x16 ; 22
754: 11 70 andi r17, 0x01 ; 1
756: 86 b3 in r24, 0x16 ; 22
758: 81 70 andi r24, 0x01 ; 1
75a: 81 17 cp r24, r17
75c: 29 f0 breq .+10 ; 0x678 <appLoop_Speed+0x16>
75e: 80 91 d4 00 lds r24, 0x00D4
762: 80 5c subi r24, 0xC0 ; 192
764: 80 93 d4 00 sts 0x00D4, r24
768: 84 e6 ldi r24, 0x64 ; 100
76a: 90 e0 ldi r25, 0x00 ; 0
76c: a9 de rcall .-686 ; 0x3ea <taskDelayFromNow>
76e: f3 cf rjmp .-26 ; 0x756 <appLoop_Speed+0x4>
so the question arises, why do we put them all on the context at a context switch? What a waste! Let us take some registers out. Femto OS has the possiblity to save only those registers which are needed, but per block of four registers. So we define in the configuration file:
/* REGISTER USE ============================================================ */
/* ========================================================================= */
#define RegisterUse_Display registersAll
#define RegisterUse_Speed r16r17r18r19 | r24r25r26r27
which registers can be used is defined in FemtoOS_0.91/
Maybe you have noted that this information was also presented by the compile script using the info switch. Please be warned that this is only an indication. This script is not guaranteed to be correct. Inspect the code yourself.
OK, so now we reduced the context by 24 registers, so we must be able to reduce the stack by the same
amount. Set StackSafety
to zero as well.
/* STACK SIZES ============================================================= */
/* ========================================================================= */
#define StackSafety 0
#define StackSizeOS 24
#define StackSizeISR 0
#define StackSizeOverride cfgOverrideNon
#define StackSize_Display 46
#define StackSize_Speed 12
Compile and flash and see that indeed we have a working application, with a context size of only 12 bytes! The total amount of RAM has decreased below the 100 bytes. Of course this kind of optimization should be done at the very end of your development cycle, because the choice of the registers by gcc cannot be predicted and can change even with the most minor change of your code.
What would hapen if we did not do our homework well, and forgot to save some registers that were used after all? We can try that by setting:
/* REGISTER USE ============================================================ */
/* ========================================================================= */
#define RegisterUse_Display registersAll
#define RegisterUse_Speed r24r25r26r27
So, effectively we forgot to save one register only. After you have compiled and run this example
you will notice that immediately an error condition is displayed (recognized by the running led) which
displays the code 0x04
, 0x00
, 0x41
. This tells us
(see the Code | Errors page)
-
0x04
: illegal register use, a change in a register was detected that was not saved, -
0x00
: that the error was detected in the system itself, -
0x41
: that offending task was number one (least significant nibble) and the highest ranked offending register was in group 4 (most significant nibble). Group 0 is r0-r3, group 1, r4-r7 etc.
After displaying this information 16 times, the system terminates the offending task, and the other tasks (one in our case) is continued. "Hello World" will be display as before, but the device will not respond to the switch any more.
Here we part?
You explore the optimization possibilities some more and redude this application even further. When you have reached the figures given on the front page:
text data bss dec hex device project
960 0 48 1008 3f0 attiny861 HelloWorld
you are close the the optimum and a master in code reduce.
I was only able to show you a small part of the Femto OS and all its possibilities to reduce code size and ram usage. The best way to continue is to start building your own extra tasks in the Hello World application (do not forget to restore stack size and register use to their larger values!) and learn while you are using the OS. Have a look at the examples too, where it is shown how Femto OS calls can be used.
When you are comfortable with the syntax, you can define your own files.
At the bottom of (starting at around line 2800) FemtoOS_0.91/
a configuration template is listed, which can be used as a starting point
for your own application.
And, of course, if you have additional questions, post them on the sf forum or email me.
Contact: info@femtoos.org | CC-BY License: Ruud Vlaming. |