Using an embedded (RT)Os shall depend on the requirements set to the software.
Surely one can make main-loop software till he dies, but sometimes it's necessary to think about task switching based systems too.
E.g. you have a safety relevant software with watchdogs, fixed cycle times and error handling. Communication interface is CAN.
Hard requirements are: 10ms software cycles, be sensitive to bus faults, use a watchdog which will reset the µC in case of cycle failure; you're only allowed to trigger the watchdog once per window time.
We assume the worst case:
CAN Bus-Off occurs, due to safety requirements the customer wants you to fully re-initialize the CAN controller in the MCU. Say you chose a Microchip PIC, these guys can take up to 160ms for a full re-initialization!
Case 1: You programmed the firmware as a main-loop variant.
You will hang up the entire software cycle and the watchdog will trigger for sure. Your MCU will be reset, internal diagnostics will write "watchdog reset" among other error codes into the EEPROM. Even worse: if bus off remains and the start up was not done correctly this might repeat endlessly until the bus off is cleared....
=> Customer in panic, software team in panic.
Case 2: You programmed the firmware task based.
The CAN driver will start a re-initialization, the scheduler puts the CAN driver to background, rest of the software runs like normal, but being aware that the CAN bus went offline. No reset, safe state reached. Everyone happy...
These are non-fictional scenarios! I happened to see a lot of stuff during my career....
You can get at least one obvious conclusion out of this: if at least ONE module hangs in a main-loop program, your entire programm either will be off (by time) or hanging completely.
Though, task based systems are not free of disadvantages either. You need to plan in additional resources for your scheduler, the heartbeat timer interrupt (that is basically what re-arranges all your tasks execution order) and eventually some more function calls compared to your main-loop variant to get the tasks interact with each other.
Writing a task based OS from scratch is not much of black magic. If you know how to save a context with your MCU half of the way is done. E.g. get the OSEK/VDX specification (it's free), read the requirements and after three days you should've the system up and running.
The fun stuff comes after you got it up and running: FINE TUNING.
First you need to figure out the ideal heart beat. If your scheduler gets called way too often nothing will ever finish (or worse: on some MCUs it can disturb internal peripherals), if it's too slow your whole timings will be off and the system becomes far from being a "real-time" os.
Second is your scheduling algorithm. It need to fit your needs, but also need to be as small as possible. You can't necessarly call an algorithm each 100uS which takes up more than 100uS. You would leave the interrupt routine and immediately will be in it again -> system is dead.
Thus it is necessary to know your MCUs latencies for interrupts and general code execution (clocking speed is very important her, i.e. to calculate the time for the routine if you don't have an oscilloscope at hand)
As for the discussion about global variables... It should be avoid where possible. It leads to pretty bad spaghetti code and maintenance can be a nightmare. (Fun fact: I had to find a bug in an unknown code. A lot of global vars and what kind of unspeakable stuff. In the end I re-arranged the code and DELETED approx 3000 Lines (!!!) and it worked as before and even better! Though I was not allowed to commit the code "changes were too huge"
)
But, you can't avoid them if you use a configurable tasked based OS for example. You need to define your Alarms, Events, Task anywhere after all.. If you use Matlab for code generation, well it almost solely uses global variables throught the code. Though you don't need to maintain the resulting C-code here! Anyhow that's a matter you always have to discuss with customers... Most write "No external variables shall exists" in their requirements... but on the other hand they want you to develop a high reliable software and in case of a configurable task based OS you need to use external variables.
As for the buffer and parameter discussions, usually you should write your code in modules. Even in ANSI-C you can come through with the getter/setter approach; more over it's recommended.
So instead of
Uart.h
extern uint8 buffer[];
Uart.c
uint8 buffer[MAX_BUFF_ELEM] = { 0 };
you should better do something like
extern FUNC( void, OS_CODE) Uart_ReadBuffer(
VAR(uint16, AUTOMATIC) offset,
VAR(uint16, AUTOMATIC ) length,
P2VAR(uint8, AUTOMATIC, AUTOMATIC ) PtrDataBuffer
);
extern FUNC( void, OS_CODE) Uart_WriteBuffer(
VAR(uint16, AUTOMATIC ) length,
P2CONST(uint8, AUTOMATIC, AUTOMATIC ) PtrDataBuffer
);
Uart.c
typedef struct S_UART {
uint8 buffer[MAX_BUF_ELEM];
} UartType;
_STATIC_ UartType Uart;
FUNC( void, OS_CODE) Uart_ReadBuffer(
VAR(uint16, AUTOMATIC) offset,
VAR(uint16, AUTOMATIC ) length,
P2VAR(uint8, AUTOMATIC, AUTOMATIC ) PtrDataBuffer
) {
/* write internal buffer to given external pointer here */
VAR( uint8, AUTOMATIC ) iter_i;
VAR( uint8, AUTOMATIC ) iter_j;
iter_j = 0;
for (iter_i = offset; (iter_i < (offset+length)) && (iter_i < MAX_BUF_ELEM); ++iter_i ) {
PtrDataBuffer[iter_j] = Uart.buffer[iter_i];
++iter_j;
}
}
FUNC( void, OS_CODE) Uart_WriteBuffer(
VAR(uint16, AUTOMATIC ) length,
P2CONST(uint8, AUTOMATIC, AUTOMATIC ) PtrDataBuffer
) {
/* fill internal buffer here */
}
(Don't get confused, I'm using the AutoSAR compiler abstraction annotation)
You could even go further and do not use a buffer at all! Just using interrupts and pass the call through the layers.
Say you have the UART driver as lowest layer, an interrupt comes in, it calls the UART-Interface which is above the UART driver. this UART-Interface passes the lower layer HW-buffer forth to another layer or to the final destination. So you might have some call stacks more in the set up (initiated by the interrupt routine), but you save the RAM for the buffer, and the data gets "injected directly" into the destination module; therefore you don't need polling anymore. Read the AutoSAR CAN communication layer specification if you want that principle in finer details
(In case of UART, you might look in the AutoSAR SPI specification too, there's another buffer principle better suited for serial communications (but more complex)).
Anyhow... big
...and I seem to have drifted away from the main topic?!