Do i really need to use an RTOS? (Alternatives to finite state machines?) - Page 1
Forum section: electronics / general

author: JPortici
author_rank: Super Contributor
timestamp: June 10, 2022, 01:19:35 pm
content: I have a new project that is screaming for a RTOS.It will have to use an external can controller (no, can't change MCU. external can controller it is, period.) and i'm trying to decide what to do.I exclusively use state machines, but my projects with CANbus have lead to really complex ones, where it's really a pain to change the order of operations, or add new features. Then this time i also have to add the SPI communication layer, which would mean a FSM that waits for another FSM to do its job, error handling etc..Well, I really REALLY want to simplify things and write a single, linear, big function that act as if it was the only thing running. So i need threads or something simillar.What are my choices?-Keep using a state machine-Finally give in and build the firmware over FreeRTOS (will take a while to get started as i only dabbled with it)-Something crazier, like running the canbus code as a linear function in the main while loop and run EVERYTHING else in interrupt routines set with the correct priorities, as all the rest is actually interrupt driven but that is not so flexible (what if i want to have TWO "threads"?)-Some C99 trickery that simulates pausing a thread (i saw some time ago some complex macro abortion that provided some sort of context switching somehow)I have always been reluctant to use a RTOS because most of the code should run bare metal, no libraries or anything wasting cycles, i don't know if i "can" mix my usual style of coding with threads, what are best or recommended practices etc.. and examples i can find are usually NOT helping in this regard

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 01:45:11 pm
content: Using an RTOS as an implementation technologywon'tmake implementation of FSMs easier. All it will do is add another layer of complexity, with hidden characteristics.I presume your FSMs arespecifiedin an FSM description language of whatever kind. Statecharts, CSP, and SDL are well-known techniques, but there are many others. If that isn't the case, then you should start there before implementing anything.It sounds to me as if you would benefit from using alternative design patterns to implement FSMs. If you are using if/then/else or case statements for FSMs with more than half a dozen states/events, then that willdefinitelybe the case. I've seen the results of such cancerous implementation techniques; the result wasveryugly andveryexpensive. People changed job rather than work on it!Having said that, there are some limited use cases where linear code involving yield() and waitForEvent() can be tractable.My preference:each FSM is all in a single thread or a superloop. Multiple FSMs can run in separate threads or sequentially within the superloopexternal hardware events cause an interrupt, and ISR mutates them into a message that is put in a queueFSM sucks one event from that queue and processes it to completion. Rinse and repeat.FSM can create events, which are also put in the same message queue to be processed by that FSMFSM cannot distinguish whether an message was from hardware event or softwarechoose a design pattern so that you can log events/states in real time, and when modification is required it should be obvious which line of code (i.e. state-event pair) needs changingif appropriate have multiple "worker" threads each of which takes one event from a global queue and processes it to completion. There should be less worker threads than coresor, of course, just use Hoare's CSP and an embedded multicore processor.

author: rstofer
author_rank: Super Contributor
timestamp: June 10, 2022, 01:51:37 pm
content: One of the core ideas behind using an RTOS is to simplify code. A task only runs if it is known that it is going to do something useful, there are no blocking spin loops. A task needs to know as little as where to get its input and where to send its output. It need not concern itself about other tasks.It is worth the effort to partition the code to work with an RTOS even if the RTOS doesn't do much in the way of scheduling. Getting rid of the superloop is the goal. There is no point in wasting cycles testing for something that hasn't happened.There is a ton of documentation for FreeRTOS and, more important, there are ports for various boards and chips. You just need to find something that is close to your hardware.You still have to write the bare iron peripheral code. The RTOS knows next to nothing about hardware, it relies on semaphores and queues to determine if a character has been received. The scheduler won't be looking at the USART peripheral itself.

author: brucehoult
author_rank: Super Contributor
timestamp: June 10, 2022, 01:56:27 pm
content: The C standard library _setjmp() and _longjmp() do exactly what is required to portably write C code that does cooperative threading. Each thread has its own jmp_buf as a global variable. When you want to switch from thread A to thread B you _setjmp(thread_A_jmp_buf) then _longjmp(thread_B_jmp_buf).The only thing you can't do from in C code is to set the stack pointer for a new thread. You have to either know where it is stored in the jmp_buf and poke it in after a setjmp() from the parent thread, or else use inline assembly language (or an asm function) to set the actual stack pointer register.

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 02:02:18 pm
content: Quote from: rstofer on June 10, 2022, 01:51:37 pmOf course.Sometimes it succeeds, especially where the processes/processing cannot be known at manufacture time.Sometimes it fails or is unnecessary, especially where all the processing is determined when the device is manufactured.QuoteYes, there are - buried invisibly inside the RTOSQuoteYou need to investigate alternative design patterns and implementation techniques.You also need to understand the subtle problems multiple threads can bring in their wake. One very visible one screwed up the Mars Pathfinder; fortunately they were able to (very!) remotely debug it and fix it.QuoteLook, if you want to use FreeRTOS then do so.If so, don't ask the question you asked, and don't expect it to make your life that much easier.Fundamentally RTOSs are not a magic bullet for FSMs. Proper specification, design, and implementation are more important than the specific technology used.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: June 10, 2022, 03:17:11 pm
content: What sort of microcontroller are you using?For example, FreeRTOS has been ported to the AVR's, but those are (for today's standard) quite slow 8-bit processors. They also have other disadvantages for an RTOS. They have a big bank of 32 registers, that all have to be saved and popped on each task switch, and on top of that, they have a very simple ISR architecture with only one level. They also have a limited amount of RAM that you have to divide over multiple stacks for the tasks. I would not recommend using an RTOS on an AVR.If you've got an ARM Cortex like uC you have more speed to begin with, more efficient task switching, and multiple ISR levels which all help to reduce the extra overhead of an RTOS. With 20KiB or so of RAM you've also got enough room to implement a bunch of tasks with each it's own stack and associated RAM usage.An RTOS is a quite decent way to add structure to your software and make the individual parts easier to understand, but it's not the only way.I've had quite a good result with implementing multiple state machines with function pointers.In main() there is just a simple while loop, which executes each of the state machines. You can add a timer, and for example run some (or all) of the state machines at a fixed rate, (for example 100Hz) This makes it easy to implement fixed delays in those state machines.The goal is that each function in each of the state machines does it's thing, and it changes the function pointer to another function if the state has to change. It is important that after the function has done it's thing, it returns, so main() gets to execute the next state machine.This is a very simple structure. It works good and is easy to understand. The only difficulty is that each of the states have to return quite quickly.One of the advantages of this way of cooperative multitasking is that no ISR's whatsoever are used, and they are all free. Once you start using an RTOS, it uses quite a lot of ISR time for the task switching itself, and this upsets all other ISR's, unless you have an interrupt system with multiple levels of priority.Because of this simple structure, you also do not have to use semaphores or other synchronization features around messages. Each of the state machines is free to completely format a message, for another state machine, and that statemachine only sees it after the first state machine returns to the main super loop.

author: JPortici
author_rank: Super Contributor
timestamp: June 10, 2022, 03:23:28 pm
content: Microcontroller is not important, but it's a PIC24FJ. Plenty of ram, nice architecture, supported by some commercial/free RTOS. Code/RAM usage is negligible so recource hog by the RTOS is not an issueQuote from: tggzzz on June 10, 2022, 01:45:11 pmI use FSMs to achieve multitasking because i can split operations in smaller steps so i can run all functions in the superloop as if they were running at the same timeUsing an RTOS would mean that i don't have to use FSM to split the operation in smaller steps, it's the scheduler (or a yield) that pauses the taskUnless i got the core concept wrong?EDIT:I am aware of the issues that RTOS can bring in, and the importance of a correct specification and planning ahead, which is why i'm reluctant to use it.Almost all of the code is already written. But this new hardware release aims to bring CAN support inside the board, instead of having it as an external module.Incidentally, i have only specified a "CAN Daughterboard" with a pinout simillar to the Click boards (so SPI, control signals, power). Nothing stops me to use a second microcontroller on the daugtherboard that handle only CAN and comms to the master, i could program the firmware of the daughterboard from the master if i wanted to (ICSP is already there, SPI), so firmware design is greatly simplified by dividing the load.What i want to achieve is being able to write a function like it was dumb, blocking code. Wait inside the function until a flag magically changes (the gods will make the other code execute) and do not overthink things too much, adding a line doesn't make the whole castle fall.Functions for comms are so much easier to write when you can do it like that..

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 03:35:16 pm
content: Quote from: JPortici on June 10, 2022, 03:23:28 pmAs stated there, that is a either a strange definition of FSMs or a strange thing to use FSMs to achieve.If you need preemptive multitasking, then an OS is a reasonable tool. But it isn't necessarily real time, and if task coordination is required then it isn't necessarily simpler.What are your comments on a precondition I raised earlier? "I presume your FSMs are specified in an FSM description language of whatever kind. Statecharts, CSP, and SDL are well-known techniques, but there are many others. If that isn't the case, then you should start there before implementing anything."

author: nctnico
author_rank: Super Contributor
timestamp: June 10, 2022, 03:35:57 pm
content: Quote from: rstofer on June 10, 2022, 01:51:37 pmIMHO this is a misconception. After all: how does the OS knows that something needs to be done? It has to test whether something happened!

author: emece67
author_rank: Frequent Contributor
timestamp: June 10, 2022, 03:49:35 pm
content: .

author: JPortici
author_rank: Super Contributor
timestamp: June 10, 2022, 03:52:18 pm
content: Quote from: tggzzz on June 10, 2022, 03:35:16 pmPlease see the EDIT,Re: your question no i do not use description languages on a piece of software if it's what you ask. But i do write them down on paper/whiteboard as a stetachart so i can decide the number of actions, steps, errors, flags, ...For example, i have to configure a BLE module via AT commands.There is a sequence of actions to perform. Because doing it in the dumb way holds the CPU for a noticeable time i had to split everything in small steps, something like thisCode:send_data(stateEnum_t set_previous_state, const uint8_t *str, uint8_t strLen);switch (state) {case CHECK_PRESENCE:return send_data(CHECK_PRESENCE,"AT",2);case SET_BAUD_RATE:return send_data(SET_BAUD_RATE,"AT+BAUD2",8);.......case SEND_DATA:if (txFinished) {if (strPos >= strSize) {//Finished Sending Datareturn WAIT_FOR_ANSWER;}else {//Send More DataTXBUF = txData;strPos++;return SEND_DATA;}}else {//Wait For uart TX to finishreturn SEND_DATA;}case WAIT_FOR_ANSWER:if (timeout) {//Timeout. Check content of RX bufferif (!strncmp(rxBuffer,"OK",2)) {switch(previous_state) {case CHECK_PRESENCE://Module is present. Set baud ratereturn SET_BAUD_RATE;.......}else {//Unexpected answer. Error handling.....}else {//keep waiting for timeoutreturn WAIT_FOR_ANSWER;}and that is what i want to avoid doing. I want to have a mutex so i can hold the communication channel, and write it as a dumb linear function.Implementing protocols over can, frame handling and such has proven to be much much much more complex as a series of state machines, or a very big single state machine. a dumb linear function in a worker thread is what i would do if it was done on a PC

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 04:10:35 pm
content: Quote from: JPortici on June 10, 2022, 03:52:18 pmThat confirms my suspicion.As I wrote in my first response "It sounds to me as if you would benefit from using alternative design patterns to implement FSMs. If you are using if/then/else or case statements for FSMs with more than half a dozen states/events, then that will definitely be the case. I've seen the results of such cancerous implementation techniques; the result was very ugly and very expensive. People changed job rather than work on it!"Youwillbenefit from thinking about and understanding alternative design patterns.For what causes event processing code to be executed, see my first post or Doctorandus_P's post.For implementing FSMs two common alternatives are:a 2D array of function pointers. One dimension is the state, the other is the event. When an event arrives then the function specified by the event+state is executed. Benefit: you have to explicitly fill in all array elements even if those are doNothing() or shouldNeverOccur()a variant of Doctorandus_P's post. Each different event is a different virtual method, each state is a separate class implementing/overriding the relevant virtual methods. The currentState is a Singleton instance of a one of the state classes. Benefit: hierarchical states are easy to implement, shouldNeverOccur() is only defined in the abstract superclass of all statesThose are very brief overviews of standard design patterns that are well described elsewhere.

author: emece67
author_rank: Frequent Contributor
timestamp: June 10, 2022, 04:29:14 pm
content: .

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 04:44:56 pm
content: Quote from: emece67 on June 10, 2022, 04:29:14 pmYes. With thatthe outer level of the superloop's if/the/else/case is removedthe state-dependent event processing will often devolve back to if/then/else/case statements. That's anathaema to me since it eventually metastasises into an unmaintainable messThe objective is to execute a piece of code depending on both the state and the event. I prefer such double-dispatching to be clearly visible in the implementation structure.

author: Doctorandus_P
author_rank: Super Contributor
timestamp: June 10, 2022, 05:01:44 pm
content: Quote from: emece67 on June 10, 2022, 04:29:14 pmI already wrote that !Quote from: tggzzz on June 10, 2022, 04:44:56 pmThere is no cure for sloppy programming.I like C quite a lot because it gives you freedom to do almost anything you want, but it also gives you the freedom to make a mess of things, but that is entirely upto the programmer. Sloppy programmers write bad and unmaintainable code, while better programmers spend more time about maintaining and improving the structure by regular refactoring during maintenance.I've had quite alot of experiences where it was easy to add a handful of lines of code to quickly fix a problem, but instead I spend half an hour and in the end removed a handful of lines and changed two or three other lines.Using function pointers to implement a state machine works quite good in C++, where you can use a class for each state machine. It also works good in C if you use a separate file for each state machine and make sure you only expose sensible parts though the header files."Arduino" like programming is horrible. It's quite sad that lots of people with no prior experience in programming start by learning the sloppy "arduino" way of things. sure it's easy to get a LED blinking, but after that you have to unlearn half of what you learned before you can learn some proper programming.--------------------------I never used a PIC24 myself, but assume they've got the right stuff for an RTOS.Rewriting an application to use an RTOS is a quite big task. Because tasks can be interrupted at any time, you have to be very careful around any communication between the different tasks. Throwing an RTOS at your application is not a magic wand that solves all your problems. Using an RTOS also makes debugging more difficult. When using an RTOS it's still bad form to use software delay loops, and these are usually replaced by some yield() funciton or a delay function which is part of the RTOS. And even when using an RTOS, it's still useful to use state machines to divide big tasks into manageable chunks.I have not seen much of your application, but I guess it's not going to improve much by adding an RTOS. Having knowledge and experience with an RTOS is still good to have in your toolbox though. Even small microcontrollers are starting to get multiple cores, such as the ESP32 and raspi2040. I'm not sure what became of the "propeller chip", It's sort of an 8-core (or 9?) architecture, but with weird limitations and there was no C-compiler available for it for a long time (I think there is now).Maybe it's better to keep your current application for what it is, and start using and learning with an RTOS for your next application, even if it does not directly need an RTOS.

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 06:37:05 pm
content: Quote from: Doctorandus_P on June 10, 2022, 05:01:44 pmAgreed, except sloppyness is not unique to C and people aren't always given the opportunity to do the right thing.QuoteI know my coding is going well when the number of lines in the application isreducing. People that use lines of code as a metric have problems with that!QuoteThere are many implementation techniques, most of them better than if/the/else/case spaghetti code.QuoteFrom what I've seen, I don't think an RTOS will help the OP as much as they hope.

author: hans
author_rank: Super Contributor
timestamp: June 10, 2022, 06:38:40 pm
content: A RTOS is primarily useful to the minimize the fuzz around blocking calls. The RTOS handles this for you, instead of cooperative multitasking where you would need to store intermediate state, return, and on next call resume the context flow again. This can be quite error prone since you'll need to store/restore program state manually, each time, in the right order/variant..If you look at how RTOS programs are built, you'll find that handcrafted IPC (like setting some global variables) and time-delays are also a very bad way of coding. RTOS' have stuff like signals, mutexes, mailboxes and queues for a good reason. Generally what I would write, is something like:Code:void commsTask() {initCommsChipset();osDelay(100); // 100ms chipset delayenableComms();while(1) {uint32_t sig = osSignalWait(SIG_ALL, NO_TIMEOUT);if (sig & COMMS_RX) {handlePackets();}if (sig & COMMS_TMR_HEARTBEAT) {txHeartbeat();}// etc.}}void handlePackets() {Packet pkt;while (osQueueCount() > 0) {osQueuePop(&pkt);// TODO: do stuff}}The "events" like COMMS_RX and COMMS_TMR_HEARTBEAT are a bitfield with unique bits (1, 2, 4, 8, etc). These signals are sent to the task from e.g. interrupt routines or other tasks (like a (OS)timer, or an Ethernet peripheral etc.).osDelay I would -at most- only use for initialization delays. I personally wouldn't want to have long blocking calls in RTOS threads. It kind of defeats the purpose of having a RTOS IMO. All tasks should return to osSignalWait ASAP. That way other "events" for that specific RTOS task can be handled as soon as needed.This converges a bit towards an event-driven programming based approach. Tasks only get alive when an event is generated by a "message pump". Everything is on-demand.The problem is that you can't always avoid to have some blocking calls though. For example, a I2cTxBytes with 256 bytes of data may take a while to complete. Especially if you write code a spinlock that waits for each byte to be transmitted. You could minimize the blocking time for the RTOS thread by using interrupt handling approach, and then sending a signal from IRQ back to the task once the whole packet has been transmitted... but the task context itself is still blocked on the I2cTxBytes function during that whole transaction.So RTOS is not a magic bullet for all your concurrency options. But it does help a lot, and if you plan your tasks carefully, then these conflicts for context don't have to happen that often.--A PIC24 is pretty capable to run a task. All context is put on stack. The architecture can easily store/restore it's own context. There is plenty of RAM (most chips have several kB) available. The lower end chips only run 16 MIPS, but there are also 70 or faster chips (with dozens of kB of RAM) out there that will make a low-end 32-bit MCU make a run for it's money. So no problems to foresee there.Perhaps one final suggestion: you could take a look at proto-threads.It's a cooperative multitasking system. You would need to write anything context-switching capable in a single function. Under the hood protothreads is pretty syntax for a large switch-case statement, where each case label is a particular point of progress in your "linear" piece of code. (Basically the program counter). Then each "proto"thread will resume it's latest reached case-label, check if it can do any new work since it last exitted. If Yes) it will run to the next case label. If No) It will return immediately and allow a superloop of multiple protothreads all share CPU time.You would still need to be careful in storing locals because they will be lost each time a "checkpoint" is crossed. But other than that it can be pretty useful.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 10, 2022, 06:50:33 pm
content: Another benefit of using an RTOS is potentially a better program structure than some ad-hoc solution (although you may have very well structured ad-hoc solutions too), and a structure that will be much easier to grasp for new developers taking over the project. That is a major benefit.If say you used FreeRTOS and some new developer comes along, then if they are familiar with FreeRTOS, they'll quickly figure out what the different tasks are, how they interact, etc. If you used some ad-hoc, personal solution, then the new developer is likely to scratch their head for quite a while before they can figure anything out.If you're the only one working on your project or a very small team, that's less of a problem.Another point is that a FSM-based ad-hoc scheduling is likely not to be preemptive. The benefit of a preemptive multitasking solution is that it makes some things much easier to implement, such as dealing with blocking calls. In a non-preemptive multitasking scheme, you can't have calls blocking beyond the max time allowed for a given task, so that makes implementation much trickier. An example I gave a while ago was using FatFs. It's much easier to use with a preemptive OS while not blocking execution for extended periods of time.OTOH, implementing a library such as FatFs with non-blocking calls only (more favorable for cooperative multitasking) is non-trivial and much more annoying.Just a few thoughts.

author: nctnico
author_rank: Super Contributor
timestamp: June 10, 2022, 06:55:16 pm
content: Quote from: SiliconWizard on June 10, 2022, 06:50:33 pmThe same can be said for 'super loop' programs. Just add an extra 'loop' that is in fact a seperate process. The big advantage is that all tasks are handled sequentially so mutexes aren't needed. With an OS you'll need to have mutexes and so on which do add complexity.

author: JPortici
author_rank: Super Contributor
timestamp: June 10, 2022, 07:03:18 pm
content: I honestly fail to see how function pointers/array of function pointers are different than what i'm doing. I've read many of those threads in this very forum (and elsewhere of course) read some material but i never was able to grasp what's so different. On the next call you are calling a different function instead of a switch that select the function based on the state. I don't see how it is different, other than it's maybe clearer to write, and maybe easier to visualize. I still have to separate the operation in very small steps and think about how they interact with each other..And i don't see why what you call "arduino style", or what i call blocking code, is bad the moment you have a mechanism to make your CPU do something useful while the current function/routine/thread is blocked (or, in other words, pause the task until time has elapsed, or signal X has been received.).hans summarize what i had in mindQuoteThis state machine has become too complex for me to mantain and i don't see how changing the approach in implementing this state machine is going to solve the problem. It looks to me that it may postpone the moment in which is going to be too complex to mantain.

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 07:03:31 pm
content: Quote from: hans on June 10, 2022, 06:38:40 pmIn FSM terms, processing an event has to be atomic: it must proceed to completion before processing the next event. Failure to observe that will lead to occasional unrepeatable failures, and probably horrible "sticking plaster" code to cover the wound.The solution is simple, of course: have several concurrent blocking FSMs, each executing independently possibly in separate threads.Thus in the case you give, the packet would be passed as a message to the second FSM. At that point the first FSM's event processing would then have completed atomically, and it could start processing the next event. The second FSM would be responsible for whatever actions are necessary to transmit the packet.If the first FSM's event processing is not complete until the packet has finished transmission, then there are a number of alternatives. Firstly simply block processing new events! Secondly, add a new state "TxInProgress", which is exited when the second FSM sends a txCompleted event. Hierarchical FSMs are likely to be a benefit here. There are others.

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 07:12:17 pm
content: Quote from: JPortici on June 10, 2022, 07:03:18 pmForums are not a good source for that information, because individuals quickly write partial answers that can - at best - point you in a suitable direction.What you need is a decent textbook that someone took time and trouble to carefully construct.QuoteKey question: is the complexityinherent in the application. If so, tough!part of your FSM. If so, you might need to refactor your FSM, possibly into multiple independent FSMspart of your implementation, e.g. if-then-else-case statements. If so use a different design pattern

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 07:36:29 pm
content: Quote from: evb149 on June 10, 2022, 07:17:56 pmA quick scan of that book (Chapters 1 and 3) leads me to believe the OP would find it useful.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 10, 2022, 08:37:21 pm
content: Quote from: JPortici on June 10, 2022, 07:03:18 pmFrom your original post, I've concluded that you wanted to blend several state machines into a huge single one.On the other hand, using "function pointers" approach I understand as when each FSM is kept separate, and main loop calls each sequentially, e.g. with two FSMs:Code:void main(void) {struct fsm1_state state1 = {....};struct fsm2_state state2 = {....};fsm1_init(&state1);fsm2_init(&state2);struct shared_queue queue = {....};for (;;) {fsm1_process(&state1, &queue);fsm2_process(&state2, &queue);}}As already noted in this thread, as soon as "*_process" FSM functions cooperate (do not block for too long), that should work fine.

author: nctnico
author_rank: Super Contributor
timestamp: June 10, 2022, 09:02:20 pm
content: Quote from: tellurium on June 10, 2022, 08:37:21 pmI agree. It is better to have several small statemachines compared to having a single large one.When dealing with hardware modules (like a modem) that needs to be initialised I use a statemachine that takes care of initialisation (and monitoring whether the device is still working properly). Towards the outside the API is very small. The status is either 'OK' or 'not OK'. Furthermore there can be function calls that send or receive data. This is an easy interface to deal with from a higher level statemachine.I'm not sure whether function pointers are a good idea. In my experience this solution tends to detach the statemachine's intend a lot from the code which makes it less easy to follow. A switch-case is a much more confined solution. I'd only use function pointers is performance is an issue or when the statemachine is too large to cover with a switch case (which could be an indication that the problem should be split into multiple statemachines).

author: Doctorandus_P
author_rank: Super Contributor
timestamp: June 10, 2022, 09:18:22 pm
content: Using a big switch compared to function pointers is not very different. Function pointers are probably a bit quicker, because the pointer can just be loaded and executed, while a switch has to search through it's states, but the difference is very likely negligible.For me the biggest advantage is readability.Using function pointers removes a few layers of indentation in the source code, and with each layer of indentation you loose overview and code becomes harder to maintain.For example I've written a networks stack that sends packets over a UART with RS485 drivers.For simple applications I have a class that just formats a packet and sends it and can also receive packets, but not much more. It's just a handful of functions and ISR's.I also have a state machine layer that is optional. The state machine is used for more complex operations. It does automatic resends after variable and automatically adjusted timeouts. It can call callback functions, and those callback functions can optionally re-start the transceiver state machine, by sending a new packet and waiting for a response, etc. This state machine makes a lot of use of a timer, so I have a timer embedded in the state machine itself. The state machine looks like:Code:uint8_t TTransceiver::Run (void) {// Run Statemachine, call this repeatedly.Status |= TRANSCEIVER_STATUS_RUN;if (TimeTicks) {TimeTicks--;}while (Status & TRANSCEIVER_STATUS_RUN) {(this->*pSMState)( );// Execute the current/new state.}return Status;}At the point the function pointer is executed you have 2 levels of indentation. If it had a switch there, it would add another level of indentation, but by using a function pointer it reduces the indentation.Also, because it's just 12 lines of code surrounding the function call, it is very easy to analyze what happens around the state machine. From this code you can see that it calls states repeatedly untill theRUNflag is cleared.By using separate functions you can also use static variables in the state functions to keep track of data that only has a local context, but which must be saved in between function calls. (Although I did not use that in this statemachine).I find that keeping functions to below a page, or even below 30 lines is preferable. It makes all problems local and it adds reviewable and easy to understand structure.If there was for example a switch() with 400 lines of code in the place of the function pointer, then it becomes a lot harder to verify what the while loop actually does and if there is more code in that while loop or not. Now you see it instantly.

author: tggzzz
author_rank: Super Contributor
timestamp: June 10, 2022, 11:23:07 pm
content: It seems we've all come to a similar conclusion, whether or not it is correctMultiple cooperating FSMs are a traditional engineering solution, and in many cases are actually explicit in the specification. The FSMs can be implemented using any of the design patterns including but not limited to one FSM per RTOS thread.A key indication that an implementation technique is sound is if you can easily associate an erroneous behaviour with a specific block of code. That can easily become excessively difficult with nested if/then/else/case statements. It can be easy with one function/method for each state+event pair.

author: cv007
author_rank: Frequent Contributor
timestamp: June 11, 2022, 03:11:08 am
content: QuoteIts possible you are referring to protothreads, although its not complex but is mostly macros. Not a big fan of any macros, but it would be an easy method to produce 'tasks' with sequential code without having to create any state/switch/case directly.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 11, 2022, 06:08:09 am
content: Interrupt driven works really well. Do utilize the following:* Interrupt vectors relocated in RAM. This way, you can change states by changing the vector, making code smaller and more readable, because instead of massive switch-case handlers, you have functions, and just set a function pointer.* NVIC->STIR! Do utilize software interrupts, to demote ISR priority. For example, in priority 2 SPI handlers functions, collect data in one of the two buffers. At the last state, swap pointers between the two buffers and trigger a priority 14 processing function. Being lower priority, it will run after the SPI handler function returns. This processing function can take long time, and SPI data collection ISR functions for the next dataset keep pre-empting the processing.* What if the slow processing function needs to start doing something even slower? Just trigger priority 15 software interrupt from there.A recent software project of ours interfaces numerous sensors (of SPI and other types, some with very esoteric protocols requiring bit-banging), processes the data and outputs to the CAN. The basic structure is like this:init_sensor():init one-shot timer for a sensorset interrupt vector for the timer to sensor_init0()sensor_init0():do first initialization write, etc.set interrupt vector for the timer to sensor_init1(), re-arm timer...sensor_run0():read result Y from peripheral FIFOstart SPI read operation for register Xre-arm timer for run1if(!processing_done) error(123); // processing ISR took too longswap(p_accumulation, p_processing);NVIC->STIR = PROCESSING_ISRn;processing_done = 0;sensor_run1():read result X from peripheral FIFOstart SPI read operation for register Yre-arm timer for run0processing_isr():do_math_wizardry();can_send(...);processing_done = 1;This seems to have scaled well, despite the fact that originally what was supposed to interface with two sensor, soon started to interface with 5-6 sensors. All runs in parallel.

author: Simon
author_rank: Unknown
timestamp: June 11, 2022, 06:03:31 pm
content: I recently came up with an idea to manage my own code. I tend to use a counter to increment a time keeping variable and at set intervals run a function full of code, usually other functions so that I can break it all down into files. This is a bit of a mess. What I will trial next is to give every function an ID, the ID being it's priority. So when the counter interrupt fires a variable or several are set to all 1's. each bit is a task and any function will have a number in it that the priority variable is compared to. This way whatever order I put my functions in they will execute in their priority order, at the end of the function it's bit in the variable is reset. I suppose this means that if in one function something specific happens that means another should run sooner than the next timeout by resetting the bit for that function.When i hear about things like an RTOS I always wonder how the microcontroller can actually switch from one function to another mid function and generally think that this should not be necessary if any one function is small enough that completing it's execution is not a problem in terms of time. Real urgent things are on interrupts anyway..

author: peter-h
author_rank: Super Contributor
timestamp: June 11, 2022, 06:32:40 pm
content: I've been coding under FreeRTOS for over a year and while I have not used ~99% of its vast array of features, I would go for it every time, even for simple projects.But then I am speaking from the POV of having already got it set up (by somebody else). I reckon he spent a week on that. And I will build every future project on the same platform; it is easily fast enough. My actuarial life expectancy is about 20 yearsThe perf hit is very small. I stepped through some mutex code (I use mutexes) and the exec time was around 1us (168MHz 32F417). I have not used message queues.

author: tggzzz
author_rank: Super Contributor
timestamp: June 11, 2022, 07:07:38 pm
content: Using mutexes can become difficult to control and to prove their will never be deadlock or lovelock.Putting messages in queues makes it easier to see and understand the interactions between processes, and to measure where work is building up in the system (look at the queue length). It also allows workload to be distributed across multiple worker threads, which can be beneficial in some systems

author: tggzzz
author_rank: Super Contributor
timestamp: June 11, 2022, 07:12:26 pm
content: Quote from: Simon on June 11, 2022, 06:03:31 pmYou can't do that in C; a small amount of assembler is required to fiddle with the stack pointer and save any "hidden" context that is unknown to C.It is also worth being aware of the fundamental differences between preemptive RTOSs and cooperative non-preemptive RTOSs.

author: peter-h
author_rank: Super Contributor
timestamp: June 11, 2022, 07:59:47 pm
content: Yes, FreeRTOS contains a fair bit of assembler.The basic thing is that an RTOS does not magically deliver performance where there isn't enough. In fact I would say an RTOS cannot improve on a well designed state machine combined with interrupts.I have been coding stuff as above since c. 1980 but by ~1989 things got so unwieldy (multi channel serial comms products with protocol conversion going on on multiple channels) I wrote my own simple RTOS. It switched tasks every 10ms and had calls like Yield() for use when a task was waiting.And this is the basic thing an RTOS gives you: you can write each task like it owns the machine.Like I said, an RTOS won't magic CPU power from nowhere. Most embedded systems have a number of low priority tasks and 1 or 2 high priority ones. That lends itself to a loop or a state machine together with a timer or event ISR, but it also lends itself to an RTOS plus a timer or event ISR and you get the added benefit that your code is much easier to understand. In some cases one can use an RTOS which runs the high priority task(s) as RTOS tasks (instead of ISRs) but with a high priority, and that's quite neat also... if you can make it work.The problem is that some people build complicated edifices around an RTOS, fiddling with task priorities to make things work. These are probably working close to the CPU perf limit. Then they write a 300 page book on RTOSesAnd then you get caught when you really need to run some task ASAP but you had already set it to a low priority to make something else run fast enough. I am sure there are eloquent solutions, if you are paid by the hour and it isn't your own companyIn the end there is only one CPU. The best way to use an RTOS is by writing each task to co-operate i.e. yield when waiting for something. Then one can often write the whole project as all tasks with idle priority.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 11, 2022, 08:02:43 pm
content: Quote from: tggzzz on June 11, 2022, 07:07:38 pmYep. They're a very powerful IPC/sync mechanism that is much too often overlooked.

author: Simon
author_rank: Unknown
timestamp: June 11, 2022, 08:14:28 pm
content: well my aim is usually for simplicity so that i can understand my own code later. I here of multicore microcontrollers and think OK I guess it needs some sort of OS. I get a little confused when you have RTOS available on a fairly simple CPU like AVR or the lower power ARCM like M0(+), I mean threads, one task yielding when it is idle? Any code I have ever written is never "idles" a function is called and it runs and exits, the only waiting is for stuff where you put a while in to test a peripheral register bit usually as a matter of good practice than because you expect to wait several clock cycles for the register to be ready. Things like an ADC I write so that they fire an interrupt when done rather than wait for the ready bit to clear.It seems to me that the real embedded world has been invaded by people who want to be able to say that they are programming these microcontrollers when in actual fact they are just programmers with OS experience and cannot cope with actually working out how to manage their resources.

author: tggzzz
author_rank: Super Contributor
timestamp: June 11, 2022, 08:33:30 pm
content: Quote from: Simon on June 11, 2022, 08:14:28 pmMy objective is to be able topredictwhat will happen when the code executes. Very different, but the solution addresses both objectives.QuoteI first used a simple cooperative RTOS on a Z80, back in 1982."Yield" is a shorthand. A typical operation is more usually "waitUntilConditionOrEvent", where the wait implies yielding.The RTOS implements the yield function to look at all tasks events and conditions, and decide which yield has completed, i.e. whick task resumes.

author: Simon
author_rank: Unknown
timestamp: June 11, 2022, 08:54:42 pm
content: so you are executing code, that function running right now has control of the CPU, how does anything else decide to put that function on hold and run another? My assumption is that the code itself must be written in such a way that it is broken into small function chunks that at the end of each a decision can be made about suspending the whole stream of functions gathered into a task? It feels like writing code on an RTOS requires it to be written in a way that works with the RTOS rather than just chucking your code into an RTOS so it's quite a different way of working. My projects are usually quite simple, I don't have a situation where something can take too long, if it does I break it up nott hat that has happened yet. The only time I had to worry abut how long something took was when using arduino to talk to a hitachi style display, the arduino code is naturally some simple code using delays, the whole thing took 25ms. When I recreated the program in bare metal I wrote the screen handling around a state machine and a 100µs interrupt, so I made sure that the CPU never had to wait, it would always carry out every 100µs one half of the task of sending a character. Naturally this implementation is a bit clunky for portability which is where I came up with my very simple scheduling system.

author: gf
author_rank: Super Contributor
timestamp: June 11, 2022, 09:19:38 pm
content: Quote from: Simon on June 11, 2022, 08:54:42 pmWith preemptive multitasking, a thread can be suspended at any time by the OS, when another thread with a higher priority becomes runnable.With cooperative multitasking, a thread can only give up CPU itself (either when it waits for something, or by voluntarily yielding every now and then, even if has more work to do) in order that a re-scheduling can happen.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 11, 2022, 09:24:56 pm
content: Quote from: Simon on June 11, 2022, 08:54:42 pmSometimes it is very difficult to do so, and the only solution is to resort to RTOS.A real life example: ESP8266 chip with non-OS SDK (no RTOS). It is a wifi chip, where "tasks" decide themselves when to yield.Wifi requires periodic frame exchange to remain on the network. Now, one implements a networking code that uses TLS, and server side uses certificates that require several seconds to handshake. Wifi gets lost when crypto calculation finishes. Long story short: the only solution was to switch to RTOS, because even in the middle of the busy crypto calculation, an RTOS interrupts it and gives time to the low-level WiFi tasks that keeps a connection open.Would it be possible to keep the firmware no-OS? Theoretically, yes. By using a crypto library that sprinkles yield calls all over the code implementing coroutine-type API. I am not aware of such library. And I think it would be notoriously hard to implement one.

author: peter-h
author_rank: Super Contributor
timestamp: June 11, 2022, 10:17:12 pm
content: That is another issue. AFAIK all the "internet" type libs require an RTOS, for the reasons given. A state machine would be too complicated.Those functions (like TLS) are so complicated anyway that very few people understand them.And a lot of the code has been lifted from Linux which is an "RTOS", sort of

author: Simon
author_rank: Unknown
timestamp: June 11, 2022, 10:19:05 pm
content: Quote from: tellurium on June 11, 2022, 09:24:56 pmI think you misunderstood. I meant that to use an RTOS the code must be written in this way. The CPU can only execute what it is being told to so the code must be written in small blocks so that there is a break where the system can decide what to do next.

author: nctnico
author_rank: Super Contributor
timestamp: June 11, 2022, 10:19:24 pm
content: Quote from: peter-h on June 11, 2022, 07:59:47 pmThis is true (required) for any OS. If you are not yielding then the OS has no clue which process really needs time to do something usefull.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 06:04:37 am
content: Quote from: tellurium on June 11, 2022, 09:24:56 pmI don't think I follow. Ever heard of interrupts? What's the problem? The task which is interrupted does not need to know about the existence of interrupts, or the fact it's being interrupted. Nothing needs to be "sprinkled" in crypto processing code.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 07:05:06 am
content: Quote from: Simon on June 11, 2022, 10:19:05 pmNot true, for preemptive RTOSs where a task can be suspended betweenanytwo instructions.The RTOS contains a scheduler, whose job is to determine which task should be executing at any given instant. The scheduler isnot executedunless there is a reason to re-determine which task should be running. Hence until something happens, the current task will continue executing.Such reasons are essentially:an external hardware interrupt occurs. That can be any peripheral input occurring or output completing, including timers that can be used to "timeslice" tasks of equal priority.the currently executing process executes a system call. That can be anything including blocking and non-blocking i/o and yield().Since the hardware interrupts can occur at any point in the execution of a task,a task can be suspended between any two instructions. It it the RTOSs' responsibility to ensure the task does not detect whether or not that occurs.Incidentally, there are good theoretical and practical reasons to use the same hardware and software mechanisms to implement both mechanisms. Some processors do that, some don't.Non-preemptive RTOSs don't have all those properties.

author: peter-h
author_rank: Super Contributor
timestamp: June 12, 2022, 07:25:23 am
content: QuoteIt may be true for best performance but one can have an RTOS which chops tasks every x ms regardless. I wrote one of those in the 1980s. It didn't do a whole lot else, but it supported a Yield call, and I reckon the overall result was the same as the vastly more complicated FreeRTOS :smile:Actually I think FreeRTOS does chop tasks regardless, at every tick, but only to switch to a higher priority one.Here is my old RTOS. The company vanished in 1993 (I left in 1991).Code:.COMMENT \A SIMPLE REAL-TIME EXECUTIVE (RTX) for Z180/Z280. MUB is true for Z280.1. Supports any number of tasks (limited to 255 in some places).2. Total timer interrupt task switch time is less than 60us (Z180/6MHz).3. Each task must have its own stack.The RTX has five entry points:1.Initialisation.Entered (with JP) just once after power-up, to set-up initial PC andSP values. This entry kicks-off the RTX and never returns.2.Timer interrupt (timer tick).This is the normal entry point. At each timer tick, the RTX switchesto the next task.3.Switch to next task.This entry point can be used by the currently executing task toask the RTX to switch to the next task, e.g. if the task has nothingmore to do. On the Z180, this entry point is the same as the timertick entry point, because the timer int pushes the same things on thestack as a CALL does.On the MUB, this entry is implemented as RST 38h, for compact code.4.Suspend a task (until re-activated).This 'kills' a task. The RTX will ignore that task, until the task isre-activated with the function below.5.Re-activate a (suspended) task.This re-activates a (previously suspended) task.For each task, the RTX maintains the data area below:offsetcontentsinitial value+0affrom table+2bcfrom table+4defrom table+6hlfrom table+8ixfrom table+10iyfrom table+12sptask's sp (*before* the timer interrupt)+14pctask's entry point+16msrtask's MSR value (used with Z280 only)+18status0: task active, 1: task suspended+19reservedThe RTX uses push/pop instructions to save/reload registers because these areprobably the fastest way.The RTX loads iopage to 00h (as part of clearing the timer 0 pending int) - thisis OK because throughout the MUB ints are OFF whenever iopage<>0 and the RTXshould therefore never tick with iopage<>0.Each task's MSR is preserved, although currently all tasks run with 'ei all_ints'and preserving MSR is not really necessary; it could just be forced to 'all_ints'when each task is entered.\global REAL_RTX_SWITCHglobal RTX_ACTIVATE_ENTRYglobal RTX_GET_TASK_STATUSglobal RTX_INIT_ENTRYglobal RTX_SUSPEND_ENTRYglobal RTX_TIMER_INT_ENTRY; code (all these are RTX tasks)external BIG_REQUESTSexternal FRONT_PANELexternal LANC_OPERATIONSexternal MAIN_DATA_LOOPexternal NEW_LINKMAPSexternal ONESECexternal PROCESS_IPC_BUFSexternal PROCESS_OP_TIMEOUTSexternal PROCESS_OUT_RCBUFSexternal PROCESS_REM_PLOFNexternal SC_EAROM_UPDATEexternal SETUP_MODE; dataexternal HL_SAVEexternal PC_SAVEexternal RTX_ALL_SUSPENDEDexternal RTX_CURRENT_TASKexternal RTX_DATA_AREASexternal RTX_DATA_PTRexternal RTX_TIMERexternal MSR_SAVEexternal SP_SAVEexternal STACK_STARTINCLUDEDEFS.MUBSECTION CODE,XMUBEQUTRUE; RTX initialisation table. Terminated by a line with PC=0.; In the future, this should be modded to hold ix,iy,sp,pc,msr values only.; All tasks have equal priority and execute on a round-robin basis.; ** The task *order* (i.e. their #s) is assumed by calls to rtx_activate & rtx_suspend **; ** THEREFORE: ADD NEW TASKS ONLY AT THE *END* OF THE TABLE **; ** Dont forget to EQUate rtx_tasks in defs.mub to #tasks below (incl PC=0 task) **RTX_INITIAL_REGS:; task# v;v; AF BC DE HL IX IY SPPCMSR stat+res vDW 0 0 0 00 0 STACK_START+(1*STACK_SIZE) MAIN_DATA_LOOP ALL_INTS0 ; 0DW 0 0 0 00 0 STACK_START+(2*STACK_SIZE) LANC_OPERATIONS ALL_INTS0 ; 1DW 0 0 0 00 0 STACK_START+(3*STACK_SIZE) BIG_REQUESTSALL_INTS0 ; 2DW 0 0 0 00 0 STACK_START+(4*STACK_SIZE) SC_EAROM_UPDATE ALL_INTS0001H ; 3*DW 0 0 0 00 0 STACK_START+(5*STACK_SIZE) NEW_LINKMAPS ALL_INTS0001H ; 4*DW 0 0 0 00 0 STACK_START+(6*STACK_SIZE) SETUP_MODEALL_INTS0 ; 5DW 0 0 0 00 0 STACK_START+(7*STACK_SIZE) ONESECALL_INTS0001H ; 6*DW 0 0 0 00 0 STACK_START+(8*STACK_SIZE) FRONT_PANELALL_INTS0001H ; 7*DW 0 0 0 00 0 STACK_START+(9*STACK_SIZE) PROCESS_OP_TIMEOUTS ALL_INTS0001H ; 8*DW 0 0 0 00 0 STACK_START+(10*STACK_SIZE) PROCESS_OUT_RCBUFS ALL_INTS0001H ; 9*DW 0 0 0 00 0 STACK_START+(11*STACK_SIZE) PROCESS_IPC_BUFS ALL_INTS0001H ;10*DW 0 0 0 00 0 STACK_START+(12*STACK_SIZE) PROCESS_REM_PLOFN ALL_INTS0001H ;11*DW 0 0 0 00 0 0000; (*)the above tasks such marked run only once and suspend themselves, and are;re-activated by some event. The '0001H' value sets status=01h; this makes;the task *initially* suspended (e.g. we *dont* want to update the eeprom at;every power-up, do we?).; RTX initialisation/entry point.RTX_INIT_ENTRY:DI; Necessary ! (in case of entry with EI); Copy initial register values from ROM into RAM.LD HL, RTX_INITIAL_REGSLD DE, RTX_DATA_AREASLD BC, RTX_TASKS*RTX_DATA_SIZELDIR; Reset ptr (IX) to base+20 of the first taskLDW (RTX_DATA_PTR), RTX_DATA_AREAS+RTX_DATA_SIZELD (RTX_TIMER), RTX_TC; RTX downcounter time constantLD (RTX_CURRENT_TASK),0; Initial task # := 0; Enable timer interruptIF MUB; (no action necessary because timer 0 int is already always enabled; at the timer, and rtx1st does an EI etc)ELSEIN0A TCRSET 5, A; Timer 1 ints (RTX) ONOUT0A TCRENDIF; Set-up SP to 1st POP task's initial values and jump to 1st taskLD SP, RTX_DATA_AREASJP RTX1ST; (also does EI); This is the timer interrupt service routine which switches tasks.; At entry here (and at exit) rtx_data_ptr points to the 1st byte after the; task's data area (i.e. byte @base+20). Therefore, when we load SP from; rtx_data_ptr and start PUSHing the registers, the first word pushed (MSR); is written in the right place. Rtx_data_ptr does NOT point at base+0.RTX_TIMER_INT_ENTRY:; First, preserve the regs which we are going to corrupt, etc, while; adjusting SP back to its value *before* the timer interrupt.; All this must be done with instructions which dont modify flags.; We get here from ct0 interrupt, with AF'.IF MUBLD (RTX_TIMER), RTX_TC; Reload the down-counterEXXIOPAGE 0FEH; Clear CC bit (reset 'int pending')LD A, 11100000B; (this could be done *after* the RTX codeOUT (0E1H), A; but for some reason doing it there did notIOPAGE 0; work properly; it does not really matter)EXXEX AF, AF'; and switch back to main AFINC SP; Skip over Mode 3 Int Reason CodeINC SP; (always the same - boring)LD (HL_SAVE), HL; Save HL of interrupted taskPOP (MSR_SAVE); Save MSR of interrupted task; This entry point is used by rtx_switch, which has already loaded; HL and MSR into hl_save and msr_save, and disabled all interrupts.RTX_ES:POP HL; HL := PC of interrupted taskLD (SP_SAVE), SP; Save SP (the value *before* the timer int)ELSELD (HL_SAVE), HL; Save HL of interrupted taskPOP HL; HL := PC of interrupted taskLD (SP_SAVE), SP; Save SP of interrupted taskENDIF; HL now holds the interrupted task's PC value.; We save the interrupted task's registers by a series of PUSHes.LD SP, (RTX_DATA_PTR); Set SP to interrupted task's data areaIF MUBDEC SP; Skip over status+reserved bytesDEC SPPUSH (MSR_SAVE); Save MSRPUSH HL; Save PCPUSH (SP_SAVE); Save SP (the value *before* this interrupt)PUSH IY; Save IYPUSH IX; Save IXPUSH (HL_SAVE); Save HLELSEDEC SP; Skip over status+reserved bytesDEC SPDEC SP; Z180: 'push' a dummy value instead of MSRDEC SPPUSH HL; Save PCLD HL, (SP_SAVE)PUSH HL; Save SPPUSH IY; Save IYPUSH IX; Save IXLD HL, (HL_SAVE)PUSH HL; Save HLENDIFPUSH DE; Save DEPUSH BC; Save BCPUSH AF; Save AF; Interrupted task is now saved. Go to next task.; If next task's PC=0, this is the end of the task table and we wrap.LD B, RTX_TASKS; Max # of tasks to go throughRTX_NX:LD IX, (RTX_DATA_PTR)LD SP, IX; This loads SP just right for the POPs belowLD DE, RTX_DATA_SIZEADD IX, DE; Move rtx_data_ptr to next task's data areaLD HL, RTX_CURRENT_TASKINC (HL); Current task # ++IF MUBLD DE, (IX-6); Check if next task's PC=0LD A, DOR EELSELD A, (IX-6)OR A, (IX-5)ENDIFJR NZ, RTX_01; NZ: no, continueLD IX, RTX_DATA_AREAS+RTX_DATA_SIZE ; else reset ptr to 1st taskLD SP, RTX_DATA_AREAS; and set SP to start POPping for 1st taskLD (HL), 0; and reset 'current task #'RTX_01:LD (RTX_DATA_PTR), IXLD A, (IX-2); Now check that the newly-selected task isOR A; not suspended.JR Z, RTX1ST; Z: not suspended - continueDJNZ RTX_NX; else loop through all the tasks in the table; (If all tasks are suspended, we fall out here and the next task gets; executed anyway, which does not really matter unless one actually; relies on suspended tasks to *never* execute. Doing this properly; is more complicated).LD A, 1; Mark 'all suspended' condition detectedLD (RTX_ALL_SUSPENDED), A; Load registers for next task.; This is also the entry point for starting the RTX (enter with; SP = base of 1st task's data area)RTX1ST:POP AF; Load AFPOP BC; Load BCPOP DE; Load DEIF MUBPOP (HL_SAVE); Recover HLPOP IX; Load IXPOP IY; Load IYPOP (SP_SAVE); Recover SPPOP (PC_SAVE); Recover PCPOP HL; Recover MSRELSEPOP HLLD (HL_SAVE), HLPOP IXPOP IYPOP HLLD (SP_SAVE), HLPOP HLLD (PC_SAVE), HLENDIFLD SP, (SP_SAVE); Load SP; Clear the pending interrupt, enable ints and enter the new task.IF MUBPUSH (PC_SAVE); Put PC on stack (for RETIL to pop-off)PUSH HL; Likewise with MSRLD HL, (HL_SAVE); Load HLRETIL; Enter new taskELSELD HL, (PC_SAVE)PUSH HL; Put PC on stack (for RET to pop-off)LD HL, (HL_SAVE); Load HLEX AF, AF'IN0A TCR; Clear the pending timer 1 interrupt bitIN0A TMDR1LEX AF, AF'EI; Re-enable interruptsRET; Enter new taskENDIF; CALL this entry point to cause a switch to the next task. Done via RST 38H.; This entry also reloads the RTX tick down-counter, so that the next RTX tick; cannot occur until after a whole tick period.REAL_RTX_SWITCH:IF MUBDI; Prevent 'normal' RTX tick coming in herePUSH AFLD A, (RTX_TIMER); If rtx is NOT disabled,INC AJR Z, RRS_NLLD (RTX_TIMER), RTX_TC; then reload its down-counterRRS_NL:PUSH BCLD (HL_SAVE), HL; Save HL straight into RTX's HL save locationLD C, 0LDCTL HL, (C); Read current MSRLD A, LOR ALL_INTS; Correct for 'di' above having cleared the EI bitsLD L, ALD (MSR_SAVE), HL; Save MSR straight into RTX's MSR save locationPOP BCPOP AFJP RTX_ES; Do (nearly) as if a timer tick occuredELSEDIEX AF, AF'LD A, RTX_TC; Reload RTX downcounter time constantLD (RTX_TIMER), AEX AF, AF'JP RTX_TIMER_INT_ENTRY; Do as if timer tick occurredENDIF; CALL this entry point to suspend a task.; Enter with E = task # (0..254).; If E=255, then the task currently executing is suspended. This feature; is useful if a task does not know its own task # (i.e. 'suspend me').; ALSO, READ THE LARGE COMMENT BELOW. It effectively means that if a routine; wants to suspend itself, it must use the 'task#=255' call, NOT a 'task=own#'; call !!; Kills DE,HL.RTX_SUSPEND_ENTRY:LD D, E; Make a copy of task#INC E; If E=0FFhJR NZ, RSE_05LD A, (RTX_CURRENT_TASK) ; then use the 'current' task # insteadLD E, AINC ERSE_05:DEC EIF MUBLD A, RTX_DATA_SIZE; Index to the task's data areaMULTU A, EADDW HL, RTX_DATA_AREAS+18ELSEPUSH DELD D, RTX_DATA_SIZEMULDELD HL, RTX_DATA_AREAS+18ADD HL, DEPOP DEENDIFLD (HL), 1; and mark it 'suspended'; If entry task# = 255, then we have a 'suspend me' call, and the; task is suspended IMMEDIATELY. Normally, this is what would be; really required. However, if task#<>255, then we have a case where; one task (or int routine, etc) is suspending another task, and we; do NOT then perform a rtx_switch. Doing an rtx_switch makes sense; only on a 'suspend me' request, not on a 'suspend task n' request.INC DCALL Z, REAL_RTX_SWITCH; and switch immediately to next taskRET; Return via here when task is re-activated; CALL this entry point to re-activate a task.; Enter with E = task # (0..).; Kills AF,HL (+DE on Z180).RTX_ACTIVATE_ENTRY:IF MUBLD A, RTX_DATA_SIZE; Index to the task's data areaMULTU A, EADDW HL, RTX_DATA_AREAS+18ELSELD D, RTX_DATA_SIZEMULDELD HL, RTX_DATA_AREAS+18ADD HL, DEENDIFLD (HL), 0; and mark it 'active'RET; CALL this entry point to find out whether a task is currently activated (not; suspended).; Enter with E = task # (0..).; Returns A=00 if activated, 01 if suspended.; Kills AF,HL (+DE on Z180).RTX_GET_TASK_STATUS:IF MUBLD A, RTX_DATA_SIZE; Index to the task's data areaMULTU A, EADDW HL, RTX_DATA_AREAS+18ELSELD D, RTX_DATA_SIZEMULDELD HL, RTX_DATA_AREAS+18ADD HL, DEENDIFLD A, (HL); and get the 'status' byteRET

author: TomS_
author_rank: Frequent Contributor
timestamp: June 12, 2022, 07:29:07 am
content: Quote from: nctnico on June 10, 2022, 03:35:57 pmFreeRTOS handles this at event time, therefore the "testing" only happens once in response to an event occurring.If a task is blocked (e.g. waiting for an item to appear in a queue) then it will sit indefinitely in the blocked task list and can gather dust there as far as the scheduler is concerned.Only once something places an item in the queue does FreeRTOS check to see if a task is blocked on that queue, and if there was, it places the task into the ready queue so that it can run again.IIRC maybe the only thing that FreeRTOS continually checks in a "dumb" way is whether any tasks have been deleted so that it can remove them from the scheduler. This happens in the underlying idle loop when nothing else is going on, so by the time that is running you're not wasting CPU cycles that could have had a better use.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 12, 2022, 07:57:44 am
content: Quote from: Siwastaja on June 12, 2022, 06:04:37 amExactly, that's what an RTOS does. It installs a timer interrupt handler, which saves current tasks's registers and switches to another task.

author: Simon
author_rank: Unknown
timestamp: June 12, 2022, 10:36:19 am
content: Quote from: tggzzz on June 12, 2022, 07:05:06 amAh yes of course, i was not considering that the RTOS can do the work in a timed interrupt. That makes sense. So If I understand correctly the stack has to be saved so that the current task can be picked up again and when it is anything changed in the task that it gave way to is still unknown to the resumed task. Wow, so these must be quite isolated from each other and have enough to do that doing the swap thing is worth the overhead. I don't think my programs are there yet as if anything they are made in small chunks and talk to each other with global variables.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 11:28:56 am
content: Quote from: tellurium on June 12, 2022, 07:57:44 amI still don't follow. This is not what RTOS does. Normally either the CPU core (for example, ARM Cortex), or the C compiler does this register saving and task switching. You of course have to configure the timer interrupt but this is not difficult at all.Of course, the practical limitation is that when interrupt happens, it starts from the beginning of the handler function. From programmer viewpoint, RTOS makes it possible to have two larger functions running seemingly "concurrently" so that context is switched between the two functions.Very simplified:Function A statements:A1A2A3A4Function B statements:B1B2B3B4Interrupt based design:A1, A2, B1, B2, B3, B4, A3, A4 is easily possible.A1, A2, B1, B2, A3, B3, B4, A4 is not.With RTOS, latter is easily possible. You can write long pieces of linear code executed "in parallel", either switched by time-slicing arbitrarily, or specific yielding calls. Whether this helps or hinders completely depends on the case.Even with just interrupts, assuming you have a modern enough MCU with HW interrupt priorities (nested interrupts), you can easily build complex priority based patterns: A interrupted by B, which is interrupted by C, which triggers low priority task D which is not yet executed, then B is finished, then back to doing A, which is interrupted by E, after which A finally finishes and finally, D gets done.Now I don't see how the crypto-WiFi example requires the RTOS pattern. If it does, it's very poor design. The fact that manufacturers even advertise non-RTOS networking frameworks tells the story this is easily achievable.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 12, 2022, 11:46:13 am
content: Quote from: Siwastaja on June 12, 2022, 11:28:56 amI don't think so. C compiler certainly does not do any task switching, and it certainly does not do register saving for that. That's an OS's job.What in your opinion an RTOS does, if task switching / register saving is done by hardware or a compiler?

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 11:55:45 am
content: Quote from: tellurium on June 12, 2022, 11:46:13 amYou need to be more specific what you practically mean with task switching, and which "registers" you are talking about, clearly not about CPU registers. I have no idea what you are talking about!CPU (+ potentially compiler) does exactly what you mentioned, CPU context + general purpose register stacking, jump to the handler, context retrieval and jump back into what was interrupted, so that the interrupted part does not know anything about what happened.OS offers some additional features like separate per-task stack spaces (so that SP needs to be modified), which is great for memory protection. But in many MCU projects completely unnecessary, since sharing the stack Just Works.

author: abquke
author_rank: Regular Contributor
timestamp: June 12, 2022, 11:57:54 am
content: RTOS' are there to provide a framework so the programmer doesn't have to use semaphores directly. From what I've seen anyway.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 12:02:56 pm
content: Quote from: abquke on June 12, 2022, 11:57:54 amWTF. RTOS is exactly whatbringsyou the semaphores, which you do use "directly". Because it's an OS concept and requirement. Do you have any idea what you are talking about?I hate this kind of namedropping.In every freaking discussion about RTOS."Oh, you need an RTOS because RTOS is the only way to do flux capacitor recirculation. Without RTOS, you need to write your own space vector route planner which is at least 100 000 lines of code, so better use RTOS"I have witnessed this behavioral pattern so many times (and I'm not only referring to this forum) that if someone suggests using an RTOS for a project, I'mreallycareful and evaluate their actual skills before committing to the idea, because it's possible they are completely retarded and unable to do a simple project, because they think RTOS is a silver bullet which replaces understanding and thinking, while in reality RTOS brings many abstracted concepts you need to fundamentally understand well. Parallel programming, mutexes, semaphors etc. are not completely trivial. For really really complex and large projects, with an experienced team who knows an RTOS (and the tools and instrumentation it provides) well, I'm open for the idea, though. There it will probably help.

author: abquke
author_rank: Regular Contributor
timestamp: June 12, 2022, 12:07:41 pm
content: Quote from: Siwastaja on June 12, 2022, 12:02:56 pmIf you set a bit in an ISR and then check if the bit is set in main or elsewhere, it's a semaphore. Just like data structures can be "objects" if the programmer wants to imagine them that way.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 12:14:51 pm
content: Quote from: abquke on June 12, 2022, 12:07:41 pmOK, that clarifies what you meant, thanks and sorry.But RTOS does not magically solve resource sharing at all. You have the exact same responsibility to manage the resources, setting flags or using mutex/semaphore mechanisms. And parallel programming with shared resources is notoriously difficult, bugs with rarely appearing race conditions do happen.And large linear functions which set up mutexes, and another functions waiting for mutex releases, are not easy to analyze and prove, at all.

author: abquke
author_rank: Regular Contributor
timestamp: June 12, 2022, 12:20:43 pm
content: Quote from: Siwastaja on June 12, 2022, 12:14:51 pmNope. But it does let one put a shiny sticker on it that says "RTOS" and allow inexperienced (read "cheap") programmers access to real time MCU programming.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 12:30:56 pm
content: Quote from: abquke on June 12, 2022, 12:20:43 pmThis isexactlywhat I have seen and it's a pretty dangerous pattern. Now of course if it's some trivial ILB (Industrial LED Blinker), then it does not matter. An experienced developer does it bare-metal in a week, well verified and tested. Alternatively, you can choose to use RTOS, hire ten inexperienced monkeys do it in a year, and it will finally work. And everybody's proud because NASA used RTOS in Mars, too.But what I am worried about is seeing fairly complex projects turn into chaos because no one has idea how to approach: simple bare metal approaches are close to their limits, and it's hard to find a good team. RTOS seemingly offers more, but if most RTOS developers you see are those who developed the aforementioned ILB, they won't succeed in actually challenging project. So you need either experienced bare metal team, or experienced RTOS team.And, this is unsurprising, because all the true challenges are fundamental in software design and implementation, and the RTOS can not magically solve these. Personally, I have found the more I have control over things, the better the chances of success. Thus I avoid excess layers of abstraction, and very much prefer to write nested interrupt handlers instead of threads with yielding or locking calls, but this is matter of taste; both have pretty much the same challenges, and require similar understanding.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 12, 2022, 12:32:05 pm
content: Quote from: Siwastaja on June 12, 2022, 11:55:45 amWhat you've described is interrupt handling, and yes it works exactly as you've described.It is possible to organise a program by clever use of interrupts and priorities - there is no doubt about that. In this case, however, an interrupt handler must be written in a special way. For example, it should not use infinite loops. It should return as fast as possible. It cannot keep state on a stack between calls - the state should be kept elsewhere.When a work "task" is used, usually a user-written function with its own stack is considered. With a behavior very different from an ISR. Tasks often run for a long time, or forever. For example, a task could process UART input to implement a CLI:Code:void *uart_cli_task(void *param) {char buf; // On-stack stateint len = 0;for (;;) { uart_read(); ..... } // Infinite loop!}That's what a "task" is. Of course you can implement the same using superloop. But in that case, your "tasks" cannot use infinite loops or a long-executing calls. Of course you can use interrupts to call some other "tasks" - but again, the ISRs would need to be written with care.If a firmware has many such "tasks", where any "task" can block for a long time, then using a task scheduler - which is what RTOS does, is a viable solution. One cannot directly use those "tasks" as ISRs, the architecture of the firmware should be changed.Writing a firmware in a "RTOS" way is arguably easier. Writing a firmware without an RTOS, with clever use of interrupts and priorities, require more skills and is harder - but often results in a considerably better performance.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 12, 2022, 12:47:44 pm
content: Let's have a practical example.A firmware need to create several TLS requests. Each TLS request requires a lengthy crypto calculation, let's say it executes in 3 seconds. Sometimes it needs 1 simultaneous request, sometimes 3 or 5. All requests are equal in importance:Code:void make_request(destination) {struct state state;connect(&state); // Can take a very long time. Uses external crypto library , so cannot be rewritten as FSMsend_request(&state);read_response(&state); // Can take a very long timeprocess_reponse(&state);}That's a good case for using a scheduler (RTOS): each such "task" will get its own time slice and all will progress simultaneously.Implementing that using interrupts and ISRs would be possible, but IMO more complicated.

author: Simon
author_rank: Unknown
timestamp: June 12, 2022, 12:49:53 pm
content: Quote from: Siwastaja on June 12, 2022, 12:30:56 pmIt's the arduino mentality. I started my programming in Arduino so I can't speak for life before the Arduino but I only ever did one project in Arduino as a proof of concept before converting to bare metal. More recently I became aware of code configurators and RTOSes which seem to have a buzz about them these days a bit like the arduino because people can do things without fully understanding what they are doing or at least that is the perception, everyone wants a shortcut and you will get people who will see any problem as needing the one solution they are a fan of even if it is over the top.I approach these miracle solutions with caution which is why I have asked a few questions having seen the discussion. I always ask myself, when I have to pick up this project again in 5 years how will I go about refamiliarizing myself with it. If it is too complex in it's layout will I manage it, which is why I thought up my own very small scheduling system. Not because it allows me to get better results or work faster but because it will read easier later.

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2022, 12:52:34 pm
content: Quote from: abquke on June 12, 2022, 12:07:41 pmThat's not a semaphore, it's a flag. They might be the same thing on a boat, but they're not in programming.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 12:52:53 pm
content: Quote from: tellurium on June 12, 2022, 12:32:05 pmOf course you can keep state - if the handler is one function,statickeyword inside function is enough. If the handler is multiple functions, then make the variables compilation unit (file) scope statics. With RTOS you can change the terminology so that now it's "stack" which grows downwards like stacks do, but I don't see large fundamental difference. What I do see is people overrunning stack with RTOS and then experimentally modify the task configuration to increase stack space per task, wasting a lot of memory in the process, and still not proving there actually is enough memory. I don't like it.I also don't like the "as fast as possible" rule of thumb. Doing things takes the time it does; if you can't make it in time, it's not about the coding paradigm (ISR vs. concurrent thread); both will fail, and you need to change the actual algorithm to succeed in given time. Interrupt priorities solve this problem: if something more urgent happens, it is served, and the lower-priority task execution continues.I do remember, of course, working with AVR where nesting interrupts was considered a "trick" (which I did never actually use) and hence had rules of thumbs like "keep ISRs very short, only set flags, which you check in the main loop". I don't like this pattern very much and with ARM Cortex MCUs you don't need to do this unless it feels the right way. Instead, you can just assign IRQ priorities and this seems to align pretty well with actual physical nature of things. Inductor overcurrent comparator? That's highest priority. Collecting datapoint from a sensor. That's lower. Processing all accumulated 128 samples once the previously mentioned handler has collected 'em all? That's even lower.You can of course do the same in RTOS threads / tasks, running infinite loops and waiting for events / yielding / waiting for semaphores / whatever the term. Is it easier? Not for me, I think it's of equal difficulty, just different.QuoteYou don't really need superloops. This is the superloop from a fairly complex inertial measurement + sensor fusion + processing system:Code:while(1){; //handle_uart_send_binary();}You can see I had some weird idea about adding something to the superloop and then changed my mind and commented it out. Because superloops are kinda crappy.So just keep state and trigger functions. But I do see the point, every trigger results in a certain function executing from the beginning. You see this as a limitation; I see it as a strength, because it forces you to partition the code into smaller processing units, where each function is connected to some actual trigger mechanism. This trigger mechanism can be flexible, it can be IRQ from a peripheral; it can be a software interrupt from higher priority interrupt handler, or it can be just a regular function call. But every function is triggered by something, things just "do not magically happen".And this is way way better than setting flags and checking them in superloop, although that works, too.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 01:13:28 pm
content: Quote from: Simon on June 12, 2022, 10:36:19 amNothing should be done in any interrupt, except determine the event that occurred and mutate that into a message put into a queue for the scheduler to observe, and optionally kick the scheduler into life.The task state has to be saved when a task switching occurs. What's in the task state is processor dependent and is invisible to C compilers. Typically it will include the PC, stack pointer, the condition codes, the register set including FP registers. Note that many of those can be automatically saved by hardware when the interrupt is recognised and processed.But on some processors there is avastamount of "hidden" state, e.g. any out-of-order instructions that are halfway to being completed. With the Itanium it wasthousandsof such registers and instructions beingspeculativelyexecuted, TLB spills and other cache misses - and thatsignificantlyimpacted the process switch time.All that is why process switch time in Unix/Windows/etc machines is so heavyweight, and hence the preference for cooperatively scheduled "green threads" within a single process.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 01:15:59 pm
content: Quote from: tellurium on June 12, 2022, 12:47:44 pmYeah, it's a typical example. But OTOH, it is an antithesis of what real-time systems are. If you have any real-time requirements, then is it an option to take a black box library which takes "approximately 3 seconds", time-slice it for 3-5 instances which, together, maybe takes 5*3 seconds, on average, with no idea about worst-case timing, and no idea about in which order the function calls finish? Of course not, if it does matter, you just have to dig into the library and write it for your use case, or modify it for real-time needs.Of course often this does not matter at all, but this is a general purpose computing case, and sounds like a general purpose OS, nothing to do with real-time. Which is the point: there are many reasons whyoperating systemsexist. But RTOS? All the arguments for RTOS's are for the GPOS (general purpose OS), while the real-time capabilities are pretty unimpressive.Frankly, given your problem, I would consider the following two options:* Bare metal MCU, just make make_request a low-priority task (either in superloop, or low-priority ISR), let others interrupt it. If you have five, run it five times; using FIFO request queue for example. This compromises the requirement that if the five TLS requests are pushed into the queue roughly at the same time, they won't finish roughly at the same time, but at ~3 second intervals, in the order they were pushed. But yours won't prove the worst case time or order anyway. Whichever is better is arguable. One could say that the first TLS request from 1.5 seconds ago deserves to be completed first before starting any work on the next one.* Real computer, real OS. Throw a Raspberry or some more serious industrial PC at the problem. Now it can run full Linux or BSD or whatever, with much greater capabilities and extensibility. Let a separate microcontroller handle all the real-time stuff.But yours sounds like a valid case for a cheap IoT device: can't afford real computer (price, size, power budget) and that's fine; can't afford real-time designers, and don't need real time design, so that's fine, too -> timing doesn't need to be predictable, no need for complicated software engineering, any developer does, pre-existing code does -> throw a small OS that can run on an MCU at the problem -> it just happens such OS is called "RTOS" -> utilize that in your CV, because NASA did also use RTOS on Mars.

author: tellurium
author_rank: Frequent Contributor
timestamp: June 12, 2022, 01:16:08 pm
content: Quote from: Siwastaja on June 12, 2022, 12:52:53 pmYes! Memory waste and stack overflows are very common.QuoteThat is a very nice approach indeed.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 01:25:56 pm
content: Quote from: tggzzz on June 12, 2022, 01:13:28 pmIt is not a coincidence that ARM Cortex MCUs handle saving and restoring this state, fully, so that interrupt handlers can be just plain functions. This makes programming said CPUs easy because you totallycando the stuff in the ISRs directly, instead of pushing the event messages and popping them in another scheduler. The end result is functionally the same, with two key differences:* Performance is better when you don't need to run your own scheduler / queue handling code - high-priority events can be responded to in dozends of nanoseconds* When you don't need to write any scheduler code, you won't make mistakes writing it.This results in event-driven code which is quite easy to write, understandable, easy to analyze (because each function is related to some actual trigger mechanism). Similar patterns have been popular in surprising places like web development. Instead of Javascript onClick() function, you have a C onOvercurrent(). And not some overcurrentGovernor() while(1) if(overcurrent) blah_blah task, which is the RTOS way, but does not look any cleaner but quite the opposite, and only causes performance penalty.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 01:32:43 pm
content: Quote from: Siwastaja on June 12, 2022, 12:14:51 pmRTOSs certainly aren't magic, and merely mutate the issues into a different form.As has been known sinc the 60s, mutexes and semaphores are the fundamental mechanismnecessaryfor RTOSs. But, as you imply, using "naked" mutexes/semaphores is error-prone and leads to spaghetti-like code with subtle failure modes. For most practical RTOSapplications, it is better to use one of many slightly higher level abstractions which are based on mutexes/semaphores.Such abstractions are well known and appear as functions in typical RTOSs, e.g. mailboxes, queue/buffers, fork-join and so on. Curiously one of the better sets of examples was Doug Lea's Concurrency Utilities that he created in Java, 25 years ago. Those were based on well proven real-time design strategies from other domains, and were so successful they are now incorporated into the standard Java library.https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/overview.htmlhttps://docs.oracle.com/javase/tutorial/essential/concurrency/

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 01:37:21 pm
content: Quote from: Siwastaja on June 12, 2022, 12:30:56 pmYes and no.While definitely avoiding excess levels of abstraction, the abstractions in a well-implemented RTOS enable me to ignore (and be ignorant of) many processor specific details. That enables me to concentrate my attention on solving my problem, including avoiding the traps associated with undisciplined multitasking and undisciplined interrupts.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 01:38:13 pm
content: Quote from: brucehoult on June 12, 2022, 12:52:34 pmNeatly put

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 01:48:52 pm
content: Quote from: Siwastaja on June 12, 2022, 01:25:56 pmWithout direct knowledge, I am suspicious of that statement in the more complex ARMs. Do they save TLBs and similar?QuoteCan do and should do are, of course, different things.Guaranteeing response times with multiple levels of interrupt (or task priorities for that matter) is non-trivial. Consider a lower-priority interrupt being interrupted by a higher-level interrupt. (And s/interrupt/task, of course!). Forgetting about priority inversion is what screwed up the Mars Pathfinder!QuoteEvent driven design patterns are extremely vaulable at many levels, up to and including soft realtime billing systems.It has always surprised me that softies have so much difficult thinking in those terms, and have had to introduce/codify them via strange mechanisms often involving magic words like "inversion of control". Yes, I realise IoC is an overloaded (and therefore ambiguous) term!

author: Siwastaja
author_rank: Super Contributor
timestamp: June 12, 2022, 02:56:29 pm
content: Quote from: tggzzz on June 12, 2022, 01:48:52 pmQuick Googling* would have revealed that Cortex-M CPUs do not have virtual addressing, and hence no address translation unit, and the MMUs in higher end models (M7) are only a protection unit. Of course the purpose is to keep the core simple enough, suitable for real-time microcontroller applications, so that they don't need to think about complex edge cases like TLBs during CPU design, and won't need to push that complexity to software developer, either.*)https://www.sciencedirect.com/topics/engineering/arm-cortex"Unlike the Memory Management Unit (MMU) in application processors (e.g., Cortex-A processors), the MPU does not offer address translation (i.e., it has no virtual memory support). The reason for Cortex-M processors not supporting the MMU feature is to ensure that the processor system can deal with real-time requirements: When an MMU is used for virtual memory support and when there is a Translation Lookup Buffer (TLB) miss (i.e., a logical address needs to be translated to a physical address but the address translation details are not available in the local buffer), the MMU needs to carry out a page table walk. The page table walk operation is needed to obtain the address translation information. However, because during the page table walk operation the processor might not be able to deal with interrupt requests, the use of an MMU is not ideal for real-time systems."

author: Simon
author_rank: Unknown
timestamp: June 12, 2022, 03:26:34 pm
content: Quote from: tggzzz on June 12, 2022, 01:13:28 pmThere is only one way to execute mare than one thread of code, and that is that the second code comes in an interrupt. Unless the code in the interrupt handler does the swapping of the current stack for at least the stack of something that will then decide who's stack to reload you will just have one thread oaf execution with another interrupting it until it is done. Sounds like a mess but I guess you have to make the CPU appear to be more than one somehow.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 04:33:23 pm
content: Quote from: Siwastaja on June 12, 2022, 02:56:29 pmI was thinking of the Cortex A series, which quick googling indicates are superscalar with TLB caches and branch prediction caches, both of which need to be invalidated and refilled.https://www.jblopen.com/arm-cortex-a-interrupt-latency/That article indicates interrupt latency (i.e. to first instruction in the ISR) of 0.75-3.5µs.The Cortex A series are definitely used as MCUs, e.g. inside Zync 7000 chips which also contain FPGA fabric. One of the more intriguing possibilities is that one core could run Linux and the other an RTOS, together with processing in the FPGA. Maybe that is done in the new low-end Tek scopes.There are, of course, other simpler Cortex series; the range is one of the strengths of the ARM ecosystem.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 04:45:34 pm
content: Quote from: Simon on June 12, 2022, 03:26:34 pmThat is not the standard meaning of "thread".Arguably a better way of making the "CPU appear to be more than one" is exemplified in the XMOS xCORE processors. To a very useful approximation, the RTOS's functionality is implemented in hardware.In xCORE devices an 8-"core" "tile", contains 8 sets of registers and one CPU. One instruction is executed from each core in sequence. Thus effectively there are 8 processors all executing at 1/8 the clock speed, and you normally dedicate one core to one i/o peripheral or computational task. A 32-core 4000MIPS embedded chip costs ~£20 one-off at Digikey. Inter-core comms messages use the same hardware and software abstractions as i/o through peripherals. Benefits: no interrupts, no cores, latency guaranteed by the IDE without executing code, crossing your fingers and hoping you've spotted/measured the worst case.

author: nctnico
author_rank: Super Contributor
timestamp: June 12, 2022, 08:12:51 pm
content: Quote from: Simon on June 12, 2022, 03:26:34 pmIt is not a mess; it is how every OS that supports pre-emptive multitasking works.

author: brucehoult
author_rank: Super Contributor
timestamp: June 12, 2022, 10:40:50 pm
content: Quote from: tggzzz on June 12, 2022, 04:33:23 pmI haven't actually used Zynq, but unless they've done something special those Cortex-A9s aren't going to have all that tight real-time performance.In the Microchip "PolarFire SoC" FPGAs there are five 64 bit RISC-V cores. Four of them are full strength "Linux capable" with TLB, cache, branch predictors, FPU. The 5th core is a much simpler RV64IMAC core with no branch prediction, no cache (it has ITIM and DTIM), no TLB, no FPU. It has much more predictable (but slower on average) performance, with tight real-time guarantees.

author: tggzzz
author_rank: Super Contributor
timestamp: June 12, 2022, 11:08:53 pm
content: Quote from: brucehoult on June 12, 2022, 10:40:50 pmI doubt it is necessary for there to be tight hard real time performance from the ArmA9s in the Zynq. That's the domain of the Artix 7 FPGA fabric surrounding the cores.There are many ways to skin cats, and endless shades of grey. Whether decent softcores in FPGA fabric is a good use of silicon and design time is an interesting question. The partitioning between a "high level" ArmA9 and "low level" Artix fabric seems relatively straightforward.Overall I must admit to being prejudiced against weedy softcores, but it wouldn't take much for me to reverse that opinion. Horses for courses.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 13, 2022, 06:30:22 pm
content: Quote from: JPortici on June 10, 2022, 03:52:18 pmThink in terms of data flows.There's a data flow from your program to the UART or whatever else connects to BLE. So, you create a queue that is a buffer which holds the commands until they're transmitted. When you need to configure something (or otherwise communicate to BLE), you simply put the commands into this queue. They're sitting there waiting for their turn.Meanwhile, a different process which is called from an interrupt, or otherwise, removes the data from the command queue and sends them to BLE. This process may or may not use DMA, may be simple or complex, as needed ...Another process receives responses from the BLE and either interprets them immediately or creates another data flow to be consumed by a yet different process at a slower pace.When you put this all together, the complexity won't increase as the number of commands grows. As you would say, the approach scales well, compared to the brute force approach to the state machine that you employed.When you put commands into the buffer, there's a danger that there may not be enough space. Then you may need to block. Blocking would put you back into a situation where you would need an FSM, so you need to prevent buffer overflow and associated blocking somehow. Typically, it's easy to do - either by increasing the size of the buffer (as is suitable for command flow), or by using some sort of flow control (e.g. when you need to move big amounts of raw data).

author: Siwastaja
author_rank: Super Contributor
timestamp: June 13, 2022, 06:54:17 pm
content: Quote from: NorthGuy on June 13, 2022, 06:30:22 pmThat's a good strategy, but what if you need to do decisions what to push, based on something you only know after pushing something else and getting a reply? What if the operations are more complex than just arbitrary byte stream writes - for example, involve toggling different IOs, or accessing more than one peripheral. After taking this into account, your queue becomes fancy and generic, and each element becomes a struct object with command fields, or maybe opcodes if you wish, and possibly pointers to processing/decision functions, and whatnot. At some point you are at crossroads whether this would have been easier with an OS allowing linear code flow, or just the switch-case "FSM" like the OP showed.The benefit of the switch-case solution is, even if it's not the most elegant, it is most flexible as it allows any type of code execution and decision making logic. This also makes it prone to error, though.Even with OS such as linux available, I have found myself writing switch-case FSMs and utilizing things like select(), and I definitely did not invent this paradigm; even though the alternative of spawning dozens of threads exists. But with all synchronization and resource management, it might not be any easier than the switch-case FSM.

author: nctnico
author_rank: Super Contributor
timestamp: June 13, 2022, 07:02:45 pm
content: Quote from: Siwastaja on June 13, 2022, 06:54:17 pmI agree. During an initialisation phase anything can go wrong and you might even want to switch to different kinds of initialisation sequences depending on version / model detected. A high level queue only makes sense if you have a layer in between which deals with the initialisation and monitoring of the device. The latter is a better seperation between device handling and higher layer commands.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 13, 2022, 08:43:11 pm
content: Quote from: Siwastaja on June 13, 2022, 06:54:17 pmIt depends. There are many ways to program the same thing and everything depends on details. For example, the code that parses the response will make such decision, and may push the next commands as well.Quote from: Siwastaja on June 13, 2022, 06:54:17 pmSure, for queue elements, you can use advanced data structures, or you can fill in your buffer with textual commands ready to be passed to BLE, or anything in between. Whatever you do, you shouldn't pursue the solutions which make things more complex. You rather try to make things simpler.

author: TomS_
author_rank: Frequent Contributor
timestamp: June 15, 2022, 09:37:21 am
content: Quote from: Simon on June 12, 2022, 03:26:34 pmThat is what FreeRTOS does. You end an ISR with a macro called portEND_SWITCHING_ISR and supply a parameter (flag) that indicates whether any ISR safe FreeRTOS calls have resulted in a higher priority task being made ready to run.At this point, the task contexts will be swapped such that when the ISR returns, you resume execution of a higher priority task.(I use FreeRTOS as an example because its the only one I have any direct experience with)

author: JPortici
author_rank: Super Contributor
timestamp: June 15, 2022, 11:29:36 am
content: Quote from: Siwastaja on June 13, 2022, 06:54:17 pmThanks. That's exactly why i started the thread (target scenario is more complicated than the "BLE stub of an example" i showed)

author: tggzzz
author_rank: Super Contributor
timestamp: June 15, 2022, 11:37:06 am
content: Quote from: JPortici on June 15, 2022, 11:29:36 amYou will probably find you have swapped one set of problems/advantages for a different set of problems/advantages. There is no silver-bullet: what you gain on the swings you lose on the roundabout.In particular, consider how you will debug (possibly on installed systems) deadlock, livelock, performance, and priority inheritance issues.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 15, 2022, 03:42:22 pm
content: Quote from: nctnico on June 13, 2022, 07:02:45 pmBTW, for "once at the beginning" initialization, I usually do things in a single thread sequentially, then when everything is initialized, I turn on interrupts and the multitasking starts. This is because the initialization often requires things which you don't need during normal execution and vice versa. This not only simplifies the initialization, but, more importantly, removes complexity from the multitasking part by freeing it from all the clutter related to the initialization.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 15, 2022, 05:13:19 pm
content: Quote from: NorthGuy on June 15, 2022, 03:42:22 pmYes, basically by making everything else truly interrupt-based - and this means, no "flag in ISR, check in main loop" BS - leaves you with one "main loop thread" to run long/complex linear code, without still needing an OS. One is of course much less than nearly unlimited, but it's not a bad choice, I have done it a few times and it doesn't seem to break apart with increasing complexity.One example would be a complicated mobile robot platform which did all timing-critical/sensor readout/motor control/DCDC control/FSM/control loop type things in interrupt handlers (which is a breeze in Cortex-M because of interrupt priority system, pre-empting and software interrupt priority demoting). This left a large linear loop for all that stupidly complex init stuff, and once it's done, image processing & point cloud generation of ten 3D Time-of-Flight cameras. So the init took like maybe 0.5 seconds, after which the cameras would always use whatever CPU time is left from everything else (so maybe 95%). But in retrospective, it would have been quite trivial to make the camera part interrupt based, too, for an empty while(1) "main loop", which could be then used for something else again. But making all the init a truly parallel FSM implementation - no thanks. I have done it in a later project and while it works just great (and allows arbitrary init / denit in parallel without affecting anything else), it's not as easy to write and maintain. Here, an OS would possibly help with maintainability of that init code, but the question is, is it worth bringing to a project just for this reason?

author: agehall
author_rank: Frequent Contributor
timestamp: June 15, 2022, 06:18:23 pm
content: A bit of an OT comment here:As is common in every RTOS thread I read here, there are a number of users trying to explain RTOS to people that jump to the conclusion that an RTOS is a magic bullet to make everything real time. It is not. You can still develop bad code that performs nowhere in real time on an RTOS.However, in the right hands, an RTOS can be a very powerful tool to help a decent programmer develop complex applications that execute with predictable performance (which is really what RT implies) without using complicated patterns. Essentially, the RTOS is a framework for this and imho should be seen as that. Everything else they may (or may not) provide is just convenience.I do enjoy reading the posts from the people that have obvious experience and understanding though - it is amazing how much knowledge that is floating around here.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 15, 2022, 06:23:09 pm
content: Quote from: agehall on June 15, 2022, 06:18:23 pmIt's all about perspective! I thought you just wrote total BS and was about to write a "someone's wrong on the internet!" style reply, but then again, from a certain perspective, what you wrote makes perfect sense.RT is a qualifier for the OS part! I can't stress this enough.Like a red hammer. You need something red. A bucket of red paint is most flexible and efficient way to get most of the redness. This is bare metal. But you might be used to hammers, and frankly, hammers have many advantages. But a normal hammer can't do because you need red. So red hammer it is!In level of realtime-ness:worse <--> betterOS - RTOS - bare metalThis is where inexperienced people get confused. They think RTOS is key for real time things. It's true for Windows software developer, and complete opposite to someone who has written code for PIC.If what you are used to is linux or BSD, RTOS will be total magic of being able to do timing critical things!If you are used to write bare metal microcontroller code and are fluent writing interrupt based designs, using an RTOS will only make it perform worse, timing-wise, adding timing bloat and delays, and reducing the control you have.The key is, RTOS isgood enoughfor most real-time MCU applications,and it is an OS. The latter is the reason it is used. The RT part is just the "not THAT bad" qualifier.Hope this helps people who struggle with the general idea.

author: tggzzz
author_rank: Super Contributor
timestamp: June 15, 2022, 06:31:31 pm
content: Quote from: agehall on June 15, 2022, 06:18:23 pmAgreed.The OP has stated "This state machine has become too complex for me to mantain and i don't see how changing the approach in implementing this state machine is going to solve the problem. It looks to me that [using an RTOS] may postpone the moment in which is going to be too complex to mantain".Now that might be correct. But given the OP's description, I suspect it will turn out that simplification (if any) by using an RTOS will be largely due to completely re-factoring the solution, and that a complete refactoring of the FSM could achieve similar benefits.Having said that, completely reimplementing any solution is a big risk factor, especially using unfamiliar technology. It can work, but it often fails.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 15, 2022, 06:44:20 pm
content: Yep to the above. And as is common on forums, while many of the posts were interesting, each with a kinda different angle, most were actually not answering the original question. Which probably explains agehall's reply.But people should use forums for what they are. A space for discussion. Not for getting definitive answers on personal questions. We're not here to do other people's work. We're just here to share our knowledge, experience or opinions. Now up to the OP to extract what will (or won't) help them decide.But sure thing, the "*this* is screaming for *that* solution" approach is usually not a great start, but I guess by now the OP is at least convinced that it's not necessarily screaming for anything much.

author: westfw
author_rank: Super Contributor
timestamp: June 15, 2022, 09:18:51 pm
content: The other thing that I don't think has been mentioned yet, is that while a multitasking environment is very convenient in a lot of cases, it DOES NOT follow that that means you need a "Real Time" Operating System, or even preemption, much less one of the complex commercial operating systems like FreeRTOS.You can go a long way with a much simpler "Run to Completion" cooperative scheduler - essentially the OP's "State Machines", but with the ability to suspend each SM whenever you want, instead of having to contort the code to having "single exit points."Yes, you have to be a bit careful about having any one bit of code run for "too long", but you can mostly do away with locks, which is a pretty good trade-off. (Imightstill be traumatized by the large percentage of SPRs that had "did something wrong with locking" as their root cause, back in the day.)

author: tggzzz
author_rank: Super Contributor
timestamp: June 15, 2022, 09:27:31 pm
content: Quote from: westfw on June 15, 2022, 09:18:51 pmI completely agree. IMHO such schedulers have many benefits including being simple and encouraging simplicity in the application. That was the kind of thing relevant to my earlier examples involving message queues and yield() functions in various forms.The other point to note is that softies too often think that threads are the way to store the state of computation that can block. That way leads to servers with tens of thousands of threads, which is grossly inefficient in many ways. Usually it is much better to store the state in an object, and have a worker thread pick up an event and process it in the context of that object. If you have more cores, you can have more worker threads operating in parallel.

author: brucehoult
author_rank: Super Contributor
timestamp: June 16, 2022, 01:43:19 am
content: Quote from: tggzzz on June 15, 2022, 09:27:31 pmObjects with states are much much less convenient to program that threads.There is more than one way to implement threads. You don't *have* to do it in a way that each thread is allocated a few MB of stack (or even a few KB) whether it needs it or not.The Erlang language and runtime is specifically designed for systems with huge numbers of active threads, spread over many CPUs, and has been used commercially for several decades.I worked on a system (also in telephone exchange software) that used Scheme continuations to implement one thread per in-progress phone call (from picking up the receiver and instructing the hardware to give them dial-tone, right through to eventually hanging up the call). In some places it was deployed (national operators in Belgium, Netherlands, Poland, and Indonesia, at least) peak call initiation rates were a few thousand per second with average call lengths 2 minutes, so up to several hundred thousand active threads at a time. Running on SPARC machines in the early 2000s (just looking at transitioning some production use to x86). The main code was all in compiled Chicken Scheme (some of it machine-generated from other things). A Scheme thread could call out to C code, and the C code could make callbacks to the Scheme, but you couldn't switch threads while in C code (or a callback from it).

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 16, 2022, 02:03:00 am
content: Yep. If you handle everything with states, some things will be much harder to implement. I gave the example of a file system library for instance. I implemented FAT/exFAT for some project: I was going to use FatFs, but as I explained, it's all made of "blocking" calls so it's a no-no for any kind of FSM-like cooperative multitasking. (Some suggested workarounds such as periodically calling the main FSM from within FatFs, which I also consider an ugly hack and a general no-no: I don't want "tasks" to sort of "preempt" the scheduler. Horrible. "Tasks" should "yield", but the scheduling should still be handled by the "scheduler", and nowhere else.) So I implemented my own library, with non-blocking calls as much as possible and thus a large number of internal states. (I wanted a library usable in situations for which blocking calls would be unacceptable.) It was absolutely NOT fun to implement.And preemptive multitasking in particular makes it *a lot* easier for the developer. Sure you now have synchronization issues, but if you have full control over the system you design, you don't necessarily need to use anything that is potentially hard to deal with, such as resource sharing or memory sharing (at least directly.) You can use message passing only, both for synchronization and communication between threads and the end architecture is rather straightforward to develop for. This approach doesn't necessarily work for all situations, but it does for a surprising number of them.I have developed a lot of projects using ad-hoc FSM/scheduling. But the above approach is interesting and I will certainly consider it more often in the future.

author: NorthGuy
author_rank: Super Contributor
timestamp: June 16, 2022, 03:38:41 am
content: Quote from: SiliconWizard on June 16, 2022, 02:03:00 amLook at Microsoft Windows. You already have pre-emptive multitasking, and you already have FS implemented. Yet, Windows API offers overlapped IO, and lots of people use it.

author: tggzzz
author_rank: Super Contributor
timestamp: June 16, 2022, 07:45:10 am
content: Quote from: brucehoult on June 16, 2022, 01:43:19 amEr, no.You appear to be confusing Erlang's processes (of which there arevastnumbers) with operating system threads/processes (some of which would be "worker threads" for the Erlang processes). For good performance there should be around one worker thread for each core, and that thread should be doing useful computation 100% of the time, with each Erlang process timeshared within one worker thread.QuoteI too have worked on soft real-time telecom billing systems which killed calls when the credit ran out. People who had written previous systems in C were amazed by its performance.Each call had it state in one object, which also contained a queue for that call's incoming events. When a worker thread had finished processing one call's event, it chose another call's object and processed the event in that object's queue. One worker thread per core, with a couple of cores dedicated to GC and general OS operations.

author: brucehoult
author_rank: Super Contributor
timestamp: June 16, 2022, 08:27:50 am
content: Quote from: tggzzz on June 16, 2022, 07:45:10 amI'm not confusing anything.I'm using the standard terminology of "thread" being an notionally independent locus of control flow to describe what Erlang calls a "process" in order not to confuse people unfamiliar with Erlang.Erlang's "thread" and "process" terminology is the opposite of what people are normally used to with, say, Linux processes each with a number of Green threads within them.

author: SiliconWizard
author_rank: Super Contributor
timestamp: June 16, 2022, 07:21:28 pm
content: While I don't know Erlang very well, I think it's closer to what Go also does with goroutines.

author: JPortici
author_rank: Super Contributor
timestamp: July 06, 2022, 05:05:07 pm
content: Several days later, and a week into my first real freeRTOS project, i have to say this is really fun.I have already ran into crazy bugs (like things stop working for no apparent reason, move the statement and it works again, find the reason)Currently rewriting everything to run in tasks, each block in a separate one and see if things can be aggregated (or if there will be no need, there is plenty of memory and actually many many things are wake up -> do really short thing really fast -> sleep for lots of time, 1ms tick rate seems to be almost too fast)And i've started adopting function pointers and extensive use of contexts for things that are repeated and would elsewhere called "classes" (for example, currently adapting a Lin stack that run in multiple parallel instances). I was never really keen on function pointers, mostly because they intimidated me.It's really a breath of fresh air. A lot of planning and writing and replanning and rewriting but i LOVE treating so many things as separate programs. I have no doubt that adding changes will be easier. I have also no doubt that this new experience will finally change the way i approach FSMs in general

author: newbrain
author_rank: Super Contributor
timestamp: July 07, 2022, 02:51:43 pm
content: QuoteQFT, parole sante.These are exactly the two things I (as a hobbyist, remember) find are the best advantage of using an RTOS.Different functions of the system (e.g. HMI and data processing) can be treated as completely separate - once the right priorities have been assigned and the amount of resources used verified.I am sure that if a new audio frame has arrived, the processing will happen in time - even if the GUI might slow down.And, for the same reason, changing/adding functions is easier.If resources are limited, of course, the overhead might be too much.Some personal examples:* Linear PSU - HD44780 4×20 display, 2 encoders, 2 buttons, V,I,P measurements, OCP with programmable delay, I & V DACs control etc.: with an STM042k6 (6KB RAM) I went for a superloop. Did not even try with FreeRTOS. Changing something can be done, but it needsa lotof care.* AD9834 based sine generator: on a STM32F072 FreeRTOS allows me to add both interface (parallel 480×320 TFT, one encoder) and real time (Lastly: a couple of hours to add SSB modulation) tasks without too much hassle.* iMX RT1021 SDR: Here FreeRTOS shines, the audio processing chain (FIRs, FFT, various demodulations) is served at the highest priority, UI - two encoders, some buttons, SPI 480×320 TFT - at (almost) the lowest. Other tasks might be in the middle (slow decoders such as RTTY, MORSE etc.). An FFT+waterfall display was easily added without touching the rest.I found I'm usually more comfortable with pre-emption with no time slicing.

author: brucehoult
author_rank: Super Contributor
timestamp: July 07, 2022, 11:42:42 pm
content: Quote from: newbrain on July 07, 2022, 02:51:43 pmThat's fine if you have 0 or 1 CPU-bound tasks.More than 1 and you *need* either time slicing or yield() calls in your loops.If you already have pre-emption rather than simply interrupt handlers ... i.e. an interrupt handler can cause control to return to a different (higher priority, newly unblocked) task than the one that was interrupted ... then time slicing is nothing more extra than a clock interrupt and a fairness algorithm.

author: westfw
author_rank: Super Contributor
timestamp: July 08, 2022, 12:17:51 am
content: QuoteOr other calls that implicitly yield...

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 08, 2022, 12:27:16 am
content: Well uh. A preemptive scheduler is uh... preemptive. It must be able to preempt. Sure it can do so by other means than a timer, but you still need something that preempts, externally from the tasks themselves. If tasks are solely responsible for "yielding", that's not preemptive scheduling, that's merely cooperative.Now of course with a preemptive scheduler, tasks can always "yield" earlier than their allocated time slot if they have nothing more to do (usually waiting on some event) - which is the only way of making a scheduler work at all if the number of tasks times the time slot is greater than 100%. So in that regard, the time slot is the *max* time slot for a given task. But there still needs to be an external event that can preempt. If it's not a timer, it needs to be something else, but not something that would entirely depend on each task, otherwise you're again not in the preemptive territory anymore.I haven't looked at everything FreeRTOS offered, for instance, but I've implemented a preemptive scheduler with which you can define a different (max) time slot for each task, instead of a fixed one. I haven't checked whether you could do that with FreeRTOS.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2022, 06:13:03 am
content: Quote from: newbrain on July 07, 2022, 02:51:43 pmThe feelings of you two sound pretty much similar to the enjoyment I was having when I realized, on Cortex-M, that you don't have to set some flags and sequentially process them in a superloop, but you can make a fully ISR-driven design, with pre-emption with IRQ priorities, plus software interrupts to trigger lower priority interrupts from higher ones.Everything just... works, running in parallel. Modules can be treated as "separate programs", and state machine is implemented as just functions, triggered by something - usually HW timer or other peripheral. No problem doing "long" ISRs - just make more important stuff higher priority, and pre-emption works.And it doesn't need to be a generic 1ms tick, and you don't need to write any kind of scheduler, at all, the CPU can take care of it: just set a timer peripheral for whatever delay you actually need, and make it trigger the next state function directly.The most obvious advantages for the OS would be addition of time-slicing, and the fact a trigger can bring the program flow in the middle of a function (wait for event / semaphore call). With simple ISR-based design, while long pieces of code are possible and can be pre-empted, trigger mechanisms always bring you to the start of the function. I don't think this is a bad thing, having trigger mechanisms coupled with handler functions improves readability, like in JavaScript you use a small onClick() handler instead of some massive loop where you wait-for-click.

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2022, 09:06:26 am
content: Quote from: Siwastaja on July 08, 2022, 06:13:03 amExcept that they are't actually running in parallel, and there can be "gotchas" that appear inverywell disciplined high-reliability environments. Start by understanding why the first space shuttle launch attempt (1981-4-10) was scrubbed and the software patch installed before thr first launch occurred (1981-4-12).QuoteIf and only if you get everything right. If not, livelock (for example) can occur. Understand why the Mars Pathfinder computer repeatedly reset itself, had to be remotely debugger, and a config bit changed. (That also showed the value of logging events+states in therunningsystem; don't turn debugging off!).There is a lot of well-understoodtheoryabout RTOSs, andpractical examplesof the subtle intermittent problems that do occur when the theory is ignored.Summary: no, they don't "just work" - but they mightappearto "just work".

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2022, 10:27:07 am
content: Problem of managing shared resources and proving availability of CPU resources with worst-case event rates, and limiting the rate events can happen, are common problem to OS- or bare metal design. Just the tools available and terminology are somewhat different. There is no silver bullet to it.Parallelism is notoriously difficult to get right. And in my opinion, purely event-driven code is easier to understand and get right than time-slicing linear code with a lot of mutexes. And, of course, avoiding sharing of resources by design as much as possible. FIFOs are generally a better idea than sharing a resource with mutexes, for example.Learning from past mistakes is always a good idea. For example the classic "computer overload" Apollo thing which was basically equivalent of wiring an interrupt pin to a CPU from an external source which is not proven to limit the minimum interrupt period. The takeaway is, if there is any uncertainty about the minimum period (or even signal integrity causing false edges), pattern where that IRQ is temporarily disabled to be re-enabled by timer IRQ can be used to limit worst case rate.

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2022, 10:39:29 am
content: Quote from: Siwastaja on July 08, 2022, 10:27:07 amThat's pretty much my belief.Event->interrupt->capture event put in fifo->return from interrupt. Forever loop: wait until event in fifo->process event to completion. Completion can be putting an event in the same queue or sending it to another FSM for processing.That is simple and, coupled with the half-sync-half-async design pattern, is efficient and predictable. It does require suitable events that can allow "processing event to completion", but that is usually not a major problem in real-world systems.

author: tellurium
author_rank: Frequent Contributor
timestamp: July 08, 2022, 10:47:06 am
content: Quote from: Siwastaja on July 08, 2022, 06:13:03 amI'd like to see a simple project, e.g. blinky with UART based control (e.g. to change blink intervals, blink counts, or pwm), implemented in 3 paradigms:1. superloop2. os3. ISRs with prioritiesEach approach should not use any external dependency and be as small and simple as possible.

author: tggzzz
author_rank: Super Contributor
timestamp: July 08, 2022, 02:53:12 pm
content: Quote from: tellurium on July 08, 2022, 10:47:06 amWhile that sounds like good idea, just about any approach, e.g. assembler, is sufficient for a trivial project.The problem is understanding how architectures do and don'tscaleas the number of inputs and outputs increases, the number of souces of inputs and outputs increaces, interaction between them becomes trickier, processing complexity increases, asynchronous vs synchronous APIs are encountered, extra requirements are added late in the implementation process, years elapse and people forget, new people are added to a project, etc.The value of "hello world" and "blinky" projects is that they are a first step in implementingsomethinganything in your chosen architecture, to gain confidence that you have your toolchain working from end to end. They do notand cannotshow the strengths/weaknesses of architectures.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2022, 04:07:21 pm
content: Portability is also a point. If you base your project on the premise of having an interrupt controller with pre-emptive priorized interrupts, then it won't be trivial to port it to a simpler microcontroller that does not have this feature. OS has the benefit that as long as the same OS is ported on the different platform, basic concurrency features work without any porting. Though, the hardware reality still leaks through abstractions: performance can differ a lot depending on what HW resources are available to the OS.

author: tellurium
author_rank: Frequent Contributor
timestamp: July 08, 2022, 05:20:59 pm
content: I actually see a huge value is such a project.Many people are accustomed to one/two approaches only - they just got familiar with it, and stuck to it even if another approach is better for the task. And I know that working examples is a big deal. That's one of the best learning tools in software engineering.The main goal is to show the code structure and execution flow, cause THAT is what differentiate approaches, not scalability/portability/whatever. Even implemented on a single arch, e.g. on the ubiquitous STM32 bluepill.

author: Siwastaja
author_rank: Super Contributor
timestamp: July 08, 2022, 06:17:22 pm
content: Quote from: tellurium on July 08, 2022, 05:20:59 pmIt would be a gooddemonstration, i.e., an explanation in form of code what these paradigmsactuallymean, but due to points tggzzz raised, obviously not a totally fair comparison. You can't find a "winner" that way.1 and 3 also tend to mix up to some kind of hybrid. Also note how even within 2 (the OS), there are many options, like superloop/select()/poll() vs. threads.Personally, I use all three approaches quite equally, but in different types of projects.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 08, 2022, 06:56:58 pm
content: Quote from: Siwastaja on July 08, 2022, 06:13:03 amFor tasks at the same priority level, a single timer and a binary min-heap to hold the next firing time works well. I use it frequently in Linux, for all sorts of timeouts.(For those who are unaware, given uniform random keys, such a binary min-heap only does an average ofe~ 2.7 percolations per insert/deleteMin, the data representation is a simple array, and on anX-bit arch, if you can live with say (X-N)-bit timestamps and use the lowNbits to indicate the cause, you can treat the combined value as aX-bit timestamp, i.e. no masking needed. Whenever the actual interrupt fires, you dispatch all tasks that have elapsed thus far, so the implicit ordering of theN-bit causes among same timestamps only orders the dispatching during the same interrupt, which would usually happen anyway. The only "dirty" bit is that you need to handle the case when the interrupt would fire almost instantly in the previous interrupt, to cater to the interrupt setup overhead, so there is one "critical" interrupts-disabled section for a few cycles, where you checkARM_DWT_CYCCNTand if it has not yet progressed too far, arm the next interrupt –– note that this assumes a single-shot timer with cheap next firing interval setup. But otherwise the code is simple, clean, lightweight, and concise to implement.)As mentioned by others above, this is very much co-operative time-sharing: each invocation must complete within an acceptable number of cpu cycles, and they do not really run in parallel at all. It is very much an event-driven asynchronous model, which makes things easier if you approach the development from that angle (instead of thread/task/procedural angle).Now, the only thing one really needs to be able to switch between tasks with the same privileges ("userspace threads") or coroutines, is to switch the stack and the register file. C does specifysetjmp()/longjmp(), but thejmp_bufstructure is opaque.After recent experimentation (and using terminology where the 'bottom' of the stack is where the stack is empty, and 'top' is where new data is added, even for downwards-growing stacks), I've found that if each 'stack' actually consists of register file storage before/just beyond/outside the bottom of the stack, storing the register state when the stack is not in use, it is very compatible with freestanding C/C++, and you get extremely lightweight same-privilege task switches, and you can use a pointer to the 'stack' (really, the register file storage of that stack)' as the task identifier. Evenyield()calls simplify to (store register state to the file before the bottom of the stack, and look for something else to run). It is not an RTOS, but it might be useful for bare-metal developers for cooperative multitasking and pre-emptive multitasking (if they write their own task scheduler), and also for educational purposes.You can even add additional fields there, say one describing the priority of whatever the task is doing now (with peripheral accesses bounded by setting that to "critical, please don't yield"), without disabling interrupts. Then, the scheduler noting that the task is "critical", could simply set another flag, check that the task is not stuck, and let the task progress. When the next time slice elapses, or the task resets the priority to non-critical, it auto-yields due to the scheduler-set flag.(You do need to replace some/most of newlib (the base C library you use), though. Which doesn't bother me, because I do want to replace it with something better; I have a topic about that here somewhere already.)What has stopped me thus far from even starting this as a proper project, is stack safety/collisions. I really don't have any tools (except for hardware ones, like MMU or protection registers) to ensure tasks won't overflow their stacks... I just get very unsure when having to just "trust" code to behave well

author: TC
author_rank: Contributor
timestamp: July 08, 2022, 10:10:10 pm
content: Disclaimer... I haven't read every reply to this topic. But the posted question was about coding state machines.I found this book to be an excellent resource on the topic...Practical UML Statecharts in C/C++ - Miro Samek

author: rstofer
author_rank: Super Contributor
timestamp: July 08, 2022, 10:51:18 pm
content: Quote from: Nominal Animal on July 08, 2022, 06:56:58 pmFreeRTOS can do a stack check before it dispatches a task:https://www.freertos.org/Stacks-and-stack-overflow-checking.html

author: rstofer
author_rank: Super Contributor
timestamp: July 08, 2022, 11:18:13 pm
content: Quote from: tellurium on July 08, 2022, 10:47:06 amWhile not exactly what you want, FreeRTOS comes with a LOT of ports and if the board is also 'mbed' compatible there are even more ports at mbed.org.Try to pick a chip with an ARM NVIC (Nested Vectored Interrupt Controller) peripheral. Write your interrupt handlers as simple C functions.Here is the code to set up the NVIC on an LPC1768Code:NVIC_SetVector(SPI_IRQn, (uint32_t) &spi_slave_handler);NVIC_SetPriority(SPI_IRQn, 1);NVIC_EnableIRQ(SPI_IRQn);Here is the beginning code for 'spi_slave_handler()' Note that it is an ordinary function with no special attributes.Code:void spi_slave_handler(void) {unsigned char status;unsigned char value;...

author: westfw
author_rank: Super Contributor
timestamp: July 09, 2022, 12:49:26 am
content: QuoteThe thing is, "simple projects" are really easy. Short, well-behaved "tasks" with no resource contention - no problem.It's when they get more complicated that you run into trouble.For instance, I recently have had cause to look at the "micros()" implementation on rp2040 in Arduino, using the Philhower core vs the Arduino (MBed) core.As it turns out, there's a hardware timer that counts microseconds in a 64bit register, so all the code really needs to do is return the low 32bits of that counter.The Philhower core calls the SDK function to get the 64bit time, and returns 32bits of it. That's ... fair.The MBed core has decided that "time" is a protected OS resource, and has multiple levels of "protections" to make sure there are no conflicts in accessing it. There may be additional protections to prevent conflicts between the two CPUs. As a result, micros() takes about 4 us to execute (on a 120MHz CPU)! That'sAWFUL. And yet, the problem statement "clock values should be consistent across multiple tasks" isn't an obviously awful prerequisite...

author: brucehoult
author_rank: Super Contributor
timestamp: July 09, 2022, 03:58:39 am
content: Quote from: westfw on July 09, 2022, 12:49:26 amUgh! I just tried on an Uno, calling micros() 400 times in a loop and storing the result in an unsigned long array (i.e. 4 bytes).It normally incremented by 4 us each time, but about once in 60-70 times got the same micros() result twice in a row.Getting a pointer to the array outside the loop increased the "same twice in a row" frequency to once in 16 times. Storing only the low 8 bits of micros() increased the "same twice in a row" frequency to once in 7 times.So, yeah, definitely being limited by the 16 MHz CPU and needing 4 instructions to move a 32 bit value around. The loop is taking a little less than 64 clock cycles to execute.Trying exactly the same sketch on the HiFive1 (FE310 RISC-V from 2016), also running at 16 MHz (but a 32 bit CPU), the result of micros() changes by 1 unit every call, except once in 16 calls the same value is returned twice. So the loop takes very slightly less than 1 us to execute -- 15 clock cycles in fact.With the clock changed to 256 MHz, 376 out of 400 calls to micros() return the same value as the previous call. The micros() value increments by only 23 in 400 calls, so the loop executes in 57.5 ns or 15 clock cycles.The loop calling micros() looks like:Code:2040015c: 64040993 addi s3,s0,1600 // note: s0 was already gp-2020, the address of a[]20400160: 81c18a13 addi s4,gp,-202020400164: 28f1 jal 20400240 <micros>20400166: 00aa2023 sw a0,0(s4)2040016a: 0a11 addi s4,s4,42040016c: ff3a1ce3 bne s4,s3,20400164 <loop+0x6c>And micros() looks like:Code:20400240 <micros>:20400240: b8002573 csrr a0,mcycleh20400244: b00027f3 csrr a5,mcycle20400248: b8002773 csrr a4,mcycleh2040024c: fee51ae3 bne a0,a4,20400240 <micros>20400250: 01851293 slli t0,a0,0x1820400254: 0087d313 srli t1,a5,0x820400258: 0062e533 or a0,t0,t12040025c: 8082 retSo that gets the 64 bit cycle count, divides it by 256, and returns the lower 32 bits: the hi 24 bits of the lo word, shifted right by 8, combined with the lo 8 bits of the hi word, shifted left by 24 (0x18).In total, 12 instructions in 15 cycles because the three CSR reads take 2 cycles each.My Arduino code (identical for Uno and HiFive1):Code:#define N 400unsigned long a;void setup() {pinMode(LED_BUILTIN, OUTPUT);Serial.begin(115200);delay(100);Serial.println("Starting");}int first = 1;void loop() {if (first){Serial.println("first");unsigned long *p = a;int dups = 0;for (int i=0; i<N; ++i) p = micros();for (int i=0; i<N; ++i){Serial.println((unsigned long)p);if (i>0 && p == p[i-1]){Serial.println("====");++dups;}}Serial.print("Dups = ");Serial.println(dups);first = 0;}digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)delay(1000); // wait for a seconddigitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOWdelay(1000); // wait for a second}

author: westfw
author_rank: Super Contributor
timestamp: July 09, 2022, 08:58:25 am
content: QuoteOn an Uno, the micros() function only has a resolution of 4us. So the function itself (which does 32 math on the timer0 overflow count and the timer register) is taking slightly less than 4us to execute. 60-odd clocks including the loop and store doesn't seem unreasonable.Code:unsigned long micros() {unsigned long m;uint8_t oldSREG = SREG, t;3b8: 3f b7 in r19, 0x3f ; 63cli();3ba: f8 94 clim = timer0_overflow_count;3bc: 80 91 39 01 lds r24, 0x0139 ; <timer0_overflow_count>3c0: 90 91 3a 01 lds r25, 0x013A ; <timer0_overflow_count+0x1>3c4: a0 91 3b 01 lds r26, 0x013B ; <timer0_overflow_count+0x2>3c8: b0 91 3c 01 lds r27, 0x013C ; <timer0_overflow_count+0x3>t = TCNT0;3cc: 26 b5 in r18, 0x26 ; 38if ((TIFR0 & _BV(TOV0)) && (t < 255))3ce: a8 9b sbis 0x15, 0 ; 213d0: 05 c0 rjmp .+10 ; 0x3dc <micros+0x24>3d2: 2f 3f cpi r18, 0xFF ; 2553d4: 19 f0 breq .+6 ; 0x3dc <micros+0x24>m++;3d6: 01 96 adiw r24, 0x01 ; 13d8: a1 1d adc r26, r13da: b1 1d adc r27, r1SREG = oldSREG;3dc: 3f bf out 0x3f, r19 ; 63return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());3de: ba 2f mov r27, r263e0: a9 2f mov r26, r253e2: 98 2f mov r25, r243e4: 88 27 eor r24, r243e6: bc 01 movw r22, r243e8: cd 01 movw r24, r263ea: 62 0f add r22, r183ec: 71 1d adc r23, r13ee: 81 1d adc r24, r13f0: 91 1d adc r25, r13f2: 42 e0 ldi r20, 0x02 ; 23f4: 66 0f add r22, r223f6: 77 1f adc r23, r233f8: 88 1f adc r24, r243fa: 99 1f adc r25, r253fc: 4a 95 dec r203fe: d1 f7 brne .-12 ; 0x3f4 <micros+0x3c>}400: 08 95 retFor the same functionality to take the same time on a 32bit processor with a hardware counter counting in the right units is disgraceful!

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 09, 2022, 08:59:37 am
content: Quote from: rstofer on July 08, 2022, 10:51:18 pmSo can I, but 1) how much is enough stack space, and 2) that's at only a point in the task time slice.Making hand-wavy guesses how much stack is actually needed feels.. icky. I can instrument the stack (fill with 0xdeadbeef) and provide functions to check how far the stack was meddled with. It shows that I'm used to having an MMU (and virtual memory)!

author: brucehoult
author_rank: Super Contributor
timestamp: July 09, 2022, 09:18:42 am
content: Quote from: westfw on July 09, 2022, 08:58:25 amThat's ... exactly what I said.QuoteAs I said, it needs four instructions to manipulate a 32 bit value, so yes that seems reasonable, for an 8 bit CPU at 16 MHz.QuoteDepends on the clock rate.Taking a little under 1 µs (four times faster) on a 32 bit processor (E31) that is running at the same 16 MHz as the AVR seems also fine.Taking 4 µs on a 32 bit processor running at 120 MHz would indeed be awful. 480 clock cycles. Hard to see how you could even do that.The E31, as noted, scales perfectly from 15/16 µs at 16 MHz to 15/256 µs at 256 MHz.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 09, 2022, 05:44:18 pm
content: Quote from: evb149 on July 09, 2022, 04:06:21 pmTrue. Because I usually work in a hosted (full OS with MMU and virtual memory), I didn't know GCC supports-fstack-limit-symbol=sym, which I can probably synthesize. Thanks for pointing it out! Now I have no excuses left...Welp, I think I next need to do some extensive testing to find out whether on ARM limiting the choice of stack alignments to a power of two (so that by masking the current stack pointer by 2ⁿ-1 yields the smallest allowed stack address), or reserving a register for this (via-ffixed-reg-fstack-limit-register=reg; the register then acts as both the stack limit, and a base address (minus a compile-time constant) to the task structure containing the register file), makes more sense. It definitely sounds intriguing, and the latter seems much more doable; but again, one must recompile all libraries, including HAL and the C library one uses, with those flags specified for this to work.Quote from: evb149 on July 09, 2022, 04:36:20 pmGraphvizhas become indispensable for me. The way it just eats human-readable, easily machine generated text, and spits out graphs, directed graphs, flowcharts, and so on, has made a big difference for me. I use it both in verification/debugging/unit testing, as well as in the actual planning stage. (Plus, since it is just plain text, you can easily support it in your preferred markdown language.)

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 09, 2022, 06:03:58 pm
content: Graphviz is good, but of course it's fully automated (which is a benefit), and doesn't really allow any hand placement of anything. If you want something more flexible, there is yEd:https://www.yworks.com/products/yedI use it when I need something that can be edited manually. It also supports a number of automatic placement algorithms, but you can further arrange things manually. It's free, but not open-source, though.

author: westfw
author_rank: Super Contributor
timestamp: July 10, 2022, 08:57:58 am
content: QuoteHere it is in all its ugliness!micros()->(crit)elapsed_time->(crit)slicetime->(crit)ticker_read_us->initialize/(core_crit)/update_present_time->mathCode:10004484 <micros>:unsigned long micros() {10004484: b507 push {r0, r1, r2, lr}return timer.elapsed_time().count();10004486: 4903 ldr r1, [pc, #12] ; (10004494 <micros+0x10>)10004488: 4668 mov r0, sp1000448a: f002 fc80 bl 10006d8e <mbed::TimerBase::elapsed_time() const>}1000448e: 9800 ldr r0, [sp, #0]10004490: bd0e pop {r1, r2, r3, pc}10004492: 46c0 nop ; (mov r8, r8)10004494: 20000fc8 .word 0x20000fc8---------10006d8e <mbed::TimerBase::elapsed_time() const>:10006d8e: b530 push {r4, r5, lr}10006d90: 000d movs r5, r110006d92: b085 sub sp, #2010006d94: 0004 movs r4, r010006d96: a803 add r0, sp, #1210006d98: f002 f820 bl 10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>10006d9c: 0029 movs r1, r510006d9e: 4668 mov r0, sp10006da0: f7ff ffda bl 10006d58 <mbed::TimerBase::slicetime() const>10006da4: 68a8 ldr r0, [r5, #8]10006da6: 68e9 ldr r1, [r5, #12]10006da8: 9a00 ldr r2, [sp, #0]10006daa: 9b01 ldr r3, [sp, #4]10006dac: 1812 adds r2, r2, r010006dae: 414b adcs r3, r110006db0: a803 add r0, sp, #1210006db2: 6022 str r2, [r4, #0]10006db4: 6063 str r3, [r4, #4]10006db6: f002 f817 bl 10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>10006dba: 0020 movs r0, r410006dbc: b005 add sp, #2010006dbe: bd30 pop {r4, r5, pc}-------10006d58 <mbed::TimerBase::slicetime() const>:10006d58: b537 push {r0, r1, r2, r4, r5, lr}10006d5a: 0004 movs r4, r010006d5c: a801 add r0, sp, #410006d5e: 000d movs r5, r110006d60: f002 f83c bl 10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>10006d64: 2300 movs r3, #010006d66: 2200 movs r2, #010006d68: 6022 str r2, [r4, #0]10006d6a: 6063 str r3, [r4, #4]10006d6c: 7d6b ldrb r3, [r5, #21]10006d6e: 2b00 cmp r3, #010006d70: d008 beq.n 10006d84 <mbed::TimerBase::slicetime() const+0x2c>10006d72: 6928 ldr r0, [r5, #16]10006d74: f001 fff0 bl 10008d58 <ticker_read_us>10006d78: 682a ldr r2, [r5, #0]10006d7a: 686b ldr r3, [r5, #4]10006d7c: 1a80 subs r0, r0, r210006d7e: 4199 sbcs r1, r310006d80: 6020 str r0, [r4, #0]10006d82: 6061 str r1, [r4, #4]10006d84: a801 add r0, sp, #410006d86: f002 f82f bl 10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>10006d8a: 0020 movs r0, r410006d8c: bd3e pop {r1, r2, r3, r4, r5, pc}------10008d58 <ticker_read_us>:10008d58: b570 push {r4, r5, r6, lr}10008d5a: 0004 movs r4, r010008d5c: f7ff ff2e bl 10008bbc <initialize>10008d60: f000 fc24 bl 100095ac <core_util_critical_section_enter>10008d64: 0020 movs r0, r410008d66: f7ff fe41 bl 100089ec <update_present_time>10008d6a: 6863 ldr r3, [r4, #4]10008d6c: 6a9c ldr r4, [r3, #40] ; 0x2810008d6e: 6add ldr r5, [r3, #44] ; 0x2c10008d70: f000 fc32 bl 100095d8 <core_util_critical_section_exit>10008d74: 0029 movs r1, r510008d76: 0020 movs r0, r410008d78: bd70 pop {r4, r5, r6, pc}-------100089ec <update_present_time>:100089ec: b5f7 push {r0, r1, r2, r4, r5, r6, r7, lr}100089ee: 6846 ldr r6, [r0, #4]100089f0: 0033 movs r3, r6100089f2: 3332 adds r3, #50 ; 0x32100089f4: 781c ldrb r4, [r3, #0]100089f6: 2c00 cmp r4, #0100089f8: d132 bne.n 10008a60 <update_present_time+0x74>100089fa: 6803 ldr r3, [r0, #0]100089fc: 685b ldr r3, [r3, #4]100089fe: 4798 blx r310008a00: 6a32 ldr r2, [r6, #32]10008a02: 0003 movs r3, r010008a04: 4282 cmp r2, r010008a06: d02b beq.n 10008a60 <update_present_time+0x74>10008a08: 1a82 subs r2, r0, r210008a0a: 6930 ldr r0, [r6, #16]10008a0c: 6233 str r3, [r6, #32]10008a0e: 4010 ands r0, r210008a10: 2233 movs r2, #51 ; 0x3310008a12: 56b2 ldrsb r2, [r6, r2]10008a14: 2a00 cmp r2, #010008a16: db24 blt.n 10008a62 <update_present_time+0x76>10008a18: 0021 movs r1, r410008a1a: f7f7 feb1 bl 10000780 <__aeabi_llsl>10008a1e: 2734 movs r7, #52 ; 0x3410008a20: 57f7 ldrsb r7, [r6, r7]10008a22: 0004 movs r4, r010008a24: 000d movs r5, r110008a26: 2f00 cmp r7, #010008a28: d014 beq.n 10008a54 <update_present_time+0x68>10008a2a: 2100 movs r1, #010008a2c: 6a70 ldr r0, [r6, #36] ; 0x2410008a2e: 1824 adds r4, r4, r010008a30: 414d adcs r5, r110008a32: 9400 str r4, [sp, #0]10008a34: 9501 str r5, [sp, #4]10008a36: 428f cmp r7, r110008a38: db1b blt.n 10008a72 <update_present_time+0x86>10008a3a: 003a movs r2, r710008a3c: 0020 movs r0, r410008a3e: 0029 movs r1, r510008a40: f7f7 fe92 bl 10000768 <__aeabi_llsr>10008a44: 003a movs r2, r710008a46: 0004 movs r4, r010008a48: 000d movs r5, r110008a4a: f7f7 fe99 bl 10000780 <__aeabi_llsl>10008a4e: 9b00 ldr r3, [sp, #0]10008a50: 1a18 subs r0, r3, r010008a52: 6270 str r0, [r6, #36] ; 0x2410008a54: 6ab2 ldr r2, [r6, #40] ; 0x2810008a56: 6af3 ldr r3, [r6, #44] ; 0x2c10008a58: 1912 adds r2, r2, r410008a5a: 416b adcs r3, r510008a5c: 62b2 str r2, [r6, #40] ; 0x2810008a5e: 62f3 str r3, [r6, #44] ; 0x2c10008a60: bdf7 pop {r0, r1, r2, r4, r5, r6, r7, pc}10008a62: 68b1 ldr r1, [r6, #8]10008a64: 0002 movs r2, r010008a66: 0023 movs r3, r410008a68: 0008 movs r0, r110008a6a: 0021 movs r1, r410008a6c: f7f7 ff34 bl 100008d8 <__aeabi_lmul>10008a70: e7d5 b.n 10008a1e <update_present_time+0x32>10008a72: 68f7 ldr r7, [r6, #12]10008a74: 000b movs r3, r110008a76: 9800 ldr r0, [sp, #0]10008a78: 9901 ldr r1, [sp, #4]10008a7a: 003a movs r2, r710008a7c: f7f7 ff0c bl 10000898 <__aeabi_uldivmod>10008a80: 4347 muls r7, r010008a82: 9b00 ldr r3, [sp, #0]10008a84: 0004 movs r4, r010008a86: 1bdf subs r7, r3, r710008a88: 000d movs r5, r110008a8a: 6277 str r7, [r6, #36] ; 0x2410008a8c: e7e2 b.n 10008a54 <update_present_time+0x68>-------10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>:10008ddc: b510 push {r4, lr}10008dde: 0004 movs r4, r010008de0: f000 fbe4 bl 100095ac <core_util_critical_section_enter>10008de4: 0020 movs r0, r410008de6: bd10 pop {r4, pc}Disassembly of section .text._ZN4mbed19CriticalSectionLockD2Ev:10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>:10008de8: b510 push {r4, lr}10008dea: 0004 movs r4, r010008dec: f000 fbf4 bl 100095d8 <core_util_critical_section_exit>10008df0: 0020 movs r0, r410008df2: bd10 pop {r4, pc}-------100095ac <core_util_critical_section_enter>:100095ac: b510 push {r4, lr}100095ae: f7ff f979 bl 100088a4 <hal_critical_section_enter>100095b2: 4a06 ldr r2, [pc, #24] ; (100095cc <core_util_critical_section_enter+0x20>)100095b4: 6813 ldr r3, [r2, #0]100095b6: 1c59 adds r1, r3, #1100095b8: d104 bne.n 100095c4 <core_util_critical_section_enter+0x18>100095ba: 223f movs r2, #63 ; 0x3f100095bc: 4904 ldr r1, [pc, #16] ; (100095d0 <core_util_critical_section_enter+0x24>)100095be: 4805 ldr r0, [pc, #20] ; (100095d4 <core_util_critical_section_enter+0x28>)100095c0: f7ff ff46 bl 10009450 <mbed_assert_internal>100095c4: 3301 adds r3, #1100095c6: 6013 str r3, [r2, #0]100095c8: bd10 pop {r4, pc}100095ca: 46c0 nop ; (mov r8, r8)100095cc: 2000aa4c .word 0x2000aa4c100095d0: 10013a2f .word 0x10013a2f100095d4: 10013a59 .word 0x10013a59Disassembly of section .text.core_util_critical_section_exit:100095d8 <core_util_critical_section_exit>:100095d8: 4a05 ldr r2, [pc, #20] ; (100095f0 <core_util_critical_section_exit+0x18>)100095da: b510 push {r4, lr}100095dc: 6813 ldr r3, [r2, #0]100095de: 2b00 cmp r3, #0100095e0: d005 beq.n 100095ee <core_util_critical_section_exit+0x16>100095e2: 3b01 subs r3, #1100095e4: 6013 str r3, [r2, #0]100095e6: 2b00 cmp r3, #0100095e8: d101 bne.n 100095ee <core_util_critical_section_exit+0x16>100095ea: f7ff f96f bl 100088cc <hal_critical_section_exit>100095ee: bd10 pop {r4, pc}100095f0: 2000aa4c .word 0x2000aa4c-------100088a4 <hal_critical_section_enter>:100088a4: b510 push {r4, lr}100088a6: f3ef 8010 mrs r0, PRIMASK100088aa: b672 cpsid i100088ac: 4a05 ldr r2, [pc, #20] ; (100088c4 <hal_critical_section_enter+0x20>)100088ae: 7813 ldrb r3, [r2, #0]100088b0: 2b00 cmp r3, #0100088b2: d105 bne.n 100088c0 <hal_critical_section_enter+0x1c>100088b4: 2101 movs r1, #1100088b6: 000c movs r4, r1100088b8: 4b03 ldr r3, [pc, #12] ; (100088c8 <hal_critical_section_enter+0x24>)100088ba: 4384 bics r4, r0100088bc: 701c strb r4, [r3, #0]100088be: 7011 strb r1, [r2, #0]100088c0: bd10 pop {r4, pc}100088c2: 46c0 nop ; (mov r8, r8)100088c4: 2000adff .word 0x2000adff100088c8: 2000adfa .word 0x2000adfaDisassembly of section .text.hal_critical_section_exit:100088cc <hal_critical_section_exit>:100088cc: b510 push {r4, lr}100088ce: f3ef 8210 mrs r2, PRIMASK100088d2: 2301 movs r3, #1100088d4: 4393 bics r3, r2100088d6: d004 beq.n 100088e2 <hal_critical_section_exit+0x16>100088d8: 2236 movs r2, #54 ; 0x36100088da: 4906 ldr r1, [pc, #24] ; (100088f4 <hal_critical_section_exit+0x28>)100088dc: 4806 ldr r0, [pc, #24] ; (100088f8 <hal_critical_section_exit+0x2c>)100088de: f000 fdb7 bl 10009450 <mbed_assert_internal>100088e2: 4a06 ldr r2, [pc, #24] ; (100088fc <hal_critical_section_exit+0x30>)100088e4: 7013 strb r3, [r2, #0]100088e6: 4b06 ldr r3, [pc, #24] ; (10008900 <hal_critical_section_exit+0x34>)100088e8: 781b ldrb r3, [r3, #0]100088ea: 2b00 cmp r3, #0100088ec: d000 beq.n 100088f0 <hal_critical_section_exit+0x24>100088ee: b662 cpsie i100088f0: bd10 pop {r4, pc}100088f2: 46c0 nop ; (mov r8, r8)100088f4: 10013620 .word 0x10013620100088f8: 10013651 .word 0x10013651100088fc: 2000adff .word 0x2000adff10008900: 2000adfa .word 0x2000adfa

author: brucehoult
author_rank: Super Contributor
timestamp: July 10, 2022, 09:42:37 am
content: Quote from: westfw on July 10, 2022, 08:57:58 amomg. It's worse than that!Cutting out the non-call stuff...Code:unsigned long micros() {1000448a: f002 fc80 bl 10006d8e <mbed::TimerBase::elapsed_time() const>10006d8e <mbed::TimerBase::elapsed_time() const>:10006d98: f002 f820 bl 10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>10006da0: f7ff ffda bl 10006d58 <mbed::TimerBase::slicetime() const>10006db6: f002 f817 bl 10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>10006d58 <mbed::TimerBase::slicetime() const>:10006d60: f002 f83c bl 10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>10006d74: f001 fff0 bl 10008d58 <ticker_read_us>10006d86: f002 f82f bl 10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>10008d58 <ticker_read_us>:10008d5c: f7ff ff2e bl 10008bbc <initialize>10008d60: f000 fc24 bl 100095ac <core_util_critical_section_enter>10008d66: f7ff fe41 bl 100089ec <update_present_time>10008d70: f000 fc32 bl 100095d8 <core_util_critical_section_exit>100089ec <update_present_time>:100089fa: 6803 ldr r3, [r0, #0]100089fc: 685b ldr r3, [r3, #4]100089fe: 4798 blx r310008a1a: f7f7 feb1 bl 10000780 <__aeabi_llsl>10008a40: f7f7 fe92 bl 10000768 <__aeabi_llsr>10008a4a: f7f7 fe99 bl 10000780 <__aeabi_llsl>10008a6c: f7f7 ff34 bl 100008d8 <__aeabi_lmul>10008a7c: f7f7 ff0c bl 10000898 <__aeabi_uldivmod>10008ddc <mbed::CriticalSectionLock::CriticalSectionLock()>:10008de0: f000 fbe4 bl 100095ac <core_util_critical_section_enter>10008de8 <mbed::CriticalSectionLock::~CriticalSectionLock()>:10008dec: f000 fbf4 bl 100095d8 <core_util_critical_section_exit>100095ac <core_util_critical_section_enter>:100095ae: f7ff f979 bl 100088a4 <hal_critical_section_enter>100095c0: f7ff ff46 bl 10009450 <mbed_assert_internal>100095d8 <core_util_critical_section_exit>:100095ea: f7ff f96f bl 100088cc <hal_critical_section_exit>100088a4 <hal_critical_section_enter>:100088a6: f3ef 8010 mrs r0, PRIMASK100088aa: b672 cpsid i100088cc <hal_critical_section_exit>:100088ce: f3ef 8210 mrs r2, PRIMASK100088de: f000 fdb7 bl 10009450 <mbed_assert_internal>100088ee: b662 cpsie iSo ...mbed::CriticalSectionLock::CriticalSectionLock and core_util_critical_section_enter and hal_critical_section_enter are all essentially the same thing. Same with the corresponding exit/leave.elapsed_time() puts a lock/unlock around calling slicetime()slicetime() puts a lock/unlock around calling ticker_read_us()ticker_read_us() puts a lock/unlock around calling update_present_time()update_present_time() probably spends most of its cycles in __aeabi_lmul and __aeabi_uldivmod. That may not be avoidable.It would be useful to :- have slicetime_with_lock_held() and ticker_read_us_with_lock_held() functions so you don't have to have multiple levels of lock taking. This would also enable inlining them.- NOT compile with -O0. There is far too much stuff that should simply be inlined, and too much memory traffic.But I bet __aeabi_lmul and __aeabi_uldivmod are using a significant chunk of the time anyway. The __aeabi_llsl and __aeabi_llsr are hopefully fast.BUT WHY ON EARTH DO MANUFACTURERS INSIST ON WRITING HAL CODE SO INEFFICIENTLY? Are they trying to upsell faster chips or something?Maybe they just all have new grads writing this stuff.

author: AVI-crak
author_rank: Regular Contributor
timestamp: July 10, 2022, 12:03:29 pm
content: Quote from: tggzzz on June 12, 2022, 01:32:43 pmOS without mutexes and semaphores is possible!!! It is enough to allow all tasks to be executed in a single cycle. To adjust the power of each task - use the percentage of time. For example, the task is performed 1/50 of the norm, or 1/2, or 1.0. Time is cut like spaghetti, switching is unconditional. Each task is obliged to give time, if there is nothing to do, otherwise there will be brakes. The switching speed is very fast, it allows you to eliminate many conditions, checks, and confirmations - just by allowing the task to check its state. I guarantee that simple code in a task can run much faster than a generic outer piece of code.A similar principle is implemented in Segger OS, but I don't like closed projects.https://github.com/AVI-crak/Rtos_cortex

author: DiTBho
author_rank: Super Contributor
timestamp: July 10, 2022, 12:30:42 pm
content: Quote from: Nominal Animal on July 08, 2022, 06:56:58 pmThat's the same solution I am using in hardware for managing some simple devices.Not the best, but it's the simplest solution to be implemented and tested, and it does the job!

author: brucehoult
author_rank: Super Contributor
timestamp: July 10, 2022, 01:02:12 pm
content: Quote from: DiTBho on July 10, 2022, 12:30:42 pmWhy is it not the best? What on earth else can you do (in software) that is better? And not only for tasks at the same priority level.With a suitable definition of the keys being compared, including fields for nominal priority, effective priority (highest priority task that is waiting on a mutex you hold), and adjustments for recent amount of CPU time allocated (for fairness) I don't believe there is anything better at scaling from a couple of tasks to thousands.

author: DiTBho
author_rank: Super Contributor
timestamp: July 10, 2022, 01:06:54 pm
content: Quote from: AVI-crak on July 10, 2022, 12:03:29 pmYup, it's possible. There are several ways to solve the problem, as well as constraints and complexity.Sequential consistency -> good for implementers and programmers but offers low performancetotal store ordering (aka TSO, RISC-V RVWMO) works great for programmers, hard for implementersLike IBM POWER way or like Nvidia GPUs way -> simple for implementers, Hard for programmersOther ways ... ?Yup, there are several memory consistency models(1), a wide range of memory models, as well as there is a big cliff with IBM and Nvidia called "multi-copy-atomicity"Anyway, the pipeline of machines like POWER10 and PowerPC is full of problems regarding the synchronization; RISC-V has some interesting approaches (fromWeak Memory Ordering RVWMO, to extensions likeZamandZtso), all interesting and they make life simpler for the software programmer (a nightwear for the HDL designer), but personally I am researching on "transaction memory consistency model"(2).All of this, because I prefer Mutexes and Semaphores, but also monitors if the language can give an hand on them.(1) wait, what is "memory consistency model"?Well, something that at least specifies the values that can be returned by loads during concurrency . Mutexes and Semaphores are both sensible to this.(2) wait, and what is "transaction memory consistency model?Well, it's like (1), but it also specifies what is permitted and what is forbidden(3), the order, and how loads are presented to other units. Basically it adds metadata and time-constraints to the hardware units used to implement Mutexes and Semaphores(3) wait, what?An implementation can do anything it wants under the covers, as long as the load return values satisfy RVWMO, so implementations can speculate past a lot of these rules, as long as they make sure to, e.g., squash and replay whenever the violation might actually become observableAt ISA level there are two common modeling approaches:axiomatic, which defines a set of criteria to be satisfied, andoperational, which defines a "golden holy abstract machine model" where some executions are forbidden unless producible when executing this model.I am with the "operational approach" because it offers less of gray area and obscure code this way.

author: DiTBho
author_rank: Super Contributor
timestamp: July 10, 2022, 01:37:11 pm
content: Quote from: brucehoult on July 10, 2022, 01:02:12 pmMisunderstand, I was not talking about software, but rather about hardware (FPGA project) for which I have assumed devices with the "same" priority level in order to simply the implementation.So the current approach is a very simple implementation which can potentially saturate the used FIFOs and some of the devices can also have significant latency.To overcome, I have simply over-dimension-ed everything to have the job done decently.A better approach should also consider that faster devices with long data-stream should be served with higher priority (especially if the FIFO of the other devices is empty), while slower devices can tolerate if they sometimes miss their turn.This may require preemption, which may cause priority inversion, a bug that occurs when a high priority task is indirectly preempted by a low priority task.So, you understand why last week I didn't implement preemption, and why I didn't implement different priorities.I have a new approach in mind, it will requires for sure effort and more circuits, and there are things to be careful, but if done properly as benefit it will reduce the fpga clock frequency and the size of the FIFOs.

author: brucehoult
author_rank: Super Contributor
timestamp: July 10, 2022, 02:47:48 pm
content: Quote from: DiTBho on July 10, 2022, 01:37:11 pmI know what priority inversion is, and my previous message already explains the solution to it.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: July 10, 2022, 03:28:18 pm
content: This is hella good thread guys. Learning a lot.One time i created two work fifos with different priorities. Interrupts placed tasks to be done in the proper fifos.When a task finished i checked the high priority fifo for items to process first and if none there then checked the normal priority fifo.It seemed to work fairly well.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2022, 05:10:57 pm
content: Quote from: snarkysparky on July 10, 2022, 03:28:18 pmThat can indeed be good, for several reasons. However, beginners might confuse your "task" with and RTOS task; I prefer to call what's in a FIFO a "Job" or "JobFragment".It can expand beyond interrupts enqueing items in FIFOs.Monitoring and management isgreatlysimplified by simply observing the number of items in each queue.Your execution engine can run at a single priority, thus simplifying prediction and analysis.It makes minimal presumptions about what facilities are available in the environment (hardware and software), so portability is easier.You can emulate and test the system in another environment, e.g. a workstation with excellent debugging facilties. All you have to do is create something thatpretendsto be an interrupt, and puts things in a FIFO.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 10, 2022, 06:50:03 pm
content: Without mutexes? Sure, never share any memory or other resources between tasks. Without semaphores? Uh, sure, if you have other means of synchronizing tasks, or if you don't even need to synchronize them (which is pretty rare.)Both can be "solved" using message passing only. Not that this is necessarily the most efficient in all use cases, but it works.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2022, 07:59:34 pm
content: Quote from: SiliconWizard on July 10, 2022, 06:50:03 pmNaked mutexes and shared memory are a fruitful source of errors as applications and the programming team scale and mutate over the years.A lot has been learned about good design patterns for real time and distributed programming over the decades. While the GoF patterns seem to be taught in university courses, the real time amd distributed patterns aren't. Curiously Doug Lea's selection and implementation of them is useful. Unfortinately typical embedded programmers are so hidebound they will reject them out of hand, without understanding them.Message passing has many advantages. I believe it is the High Performance Computing mob's preferred mechanism. Most real time applications are distributed, even if one project is implementing functionality on one computer.

author: SiliconWizard
author_rank: Super Contributor
timestamp: July 10, 2022, 08:24:55 pm
content: I made a whole thread about message passing a while ago. As I remember, it was an interesting discussion but got quite some "resistance" and a few misconceptions.I am increasingly resorting to message passing schemes these days. They are much, much easier to get right. Whether they yield better performance than other approaches all depends on a number of factors though, and without at least some hardware support, message passing can be inefficient (in terms of throughput/latency.)Interestingly, out of security reasons, there's a trend with large applications to distribute the work over a number of *processes* (rather than just threads). In which case, communicating between them requires some form of IPC, so that's often close to message passing.On general-purpose OSs, processes tend to be a bit heavy though. So there would surely be some benefit for intermediate execution units, some kind of lightweight processes. Some languages (usually through a runtime) and some particular OSs do offer that, but that's still relatively rare.

author: westfw
author_rank: Super Contributor
timestamp: July 10, 2022, 09:15:31 pm
content: [rp2040 MBed micros() code]QuoteI'm pretty sure that this was NOT compiled with -O0. But MBed is "modular", so that the "time" modules and the "ticker" modules and the "critical section" stuff are in separate areas (the critical Section stuff is done via a C++ constructor - are those ever in-lined?) And it's all distributed as pre-compiled library, so "link time optimization" doesn't help.QuoteThere is no "manufacturer" code in there. (I guess unless you can blame NXP for some of the original MBed architecture.) I can't even see where actual rp2040 timer registers are accessed. The rp2040 SDK (from RPi) provides a nice time_us_64() function that is about as efficient as I could ask for (and a time_us_32() function, too.)It's all MBed and its usage. That's why I brought it up in this RTOS-related discussion. An RTOS aimed at wide distribution will be designed for "portability" - "just change these few bits of code to support your particular chip and it'll all be good!" "Just use this abstractedtimeAPI and your application will run on any chip that supports MBed!"That's a danger of "an RTOS" - that somewhere someone has been a bit lazy on thinking things through, and it ends up shooting you in the foot because you weren't expecting it to be 'so bad'; you use an RTOS to avoid certain problems, and you end up with new problems... You have to pay attention.

author: tggzzz
author_rank: Super Contributor
timestamp: July 10, 2022, 09:38:21 pm
content: Quote from: SiliconWizard on July 10, 2022, 08:24:55 pmYes. In spades. That should be Writ Large, in Bold Font.QuoteAgreed, again.The classic tradeoff is between message size and processing duration. There is a tendency that if you want to maximise the processing per message, then you have to convey more context in the message, i.e. a take more time sending/receiving the message.QuoteAgain, yes in spades. This is becoming embarassingMessage passing works well at many scales, and enableseasy scalability without changing the application architecture- all the way from single core processors, through multicore processors, through many processors in the same room, to processors distributed in the cloud and owned by more than one company.All you have to do is specify an API in terms of the message contents and sequences of messages. That's how telecoms systems work, and they are arguably the biggest computing systems mankind uses.

author: tellurium
author_rank: Frequent Contributor
timestamp: July 10, 2022, 10:48:20 pm
content: Quote from: tggzzz on July 10, 2022, 09:38:21 pmI don't know about the telco system complexity, but I have some idea about the modern search/ads systems, which are incredibly complex and big. With dozens of thousands of engineers working on them non-stop. I always thought that those are the most complex/big computing systems in the world. Maybe telco systems are even bigger and more complex.

author: westfw
author_rank: Super Contributor
timestamp: July 10, 2022, 11:41:38 pm
content: Consider:Code:void show_connections() {for (conn = connection_ll_head; conn; conn = conn->next) { // Traverse our linkedlistprintf("Name: %s, dest: %s, prot %s\n", conn->name, conn->dest, conn->protocol);printf(" DataIn: %d, DataOut %d", conn->incount, conn->outcount);}}It doesn't look very dangerous, does it? But it is! Worse, "how to fix" is unclear - presumably actually maintaining the list of connections is more important than displaying it, so you don't really want to lock either the list or the individual connection data structures.It's dangerous even on a cooperative multi-tasking system, because printf() is a thing that is likely to block.

author: brucehoult
author_rank: Super Contributor
timestamp: July 11, 2022, 12:07:13 am
content: Quote from: westfw on July 10, 2022, 11:41:38 pmLock the list, traverse it while only copying each conn into a local list/array, then unlock it.Traverse your local copy, checking each conn to see if it is still valid, and if so then print it.This is a perfect example of why you want to use automatic garbage collection, whether a tracing one or reference-counting, because you don't know who will have the last reference to a conn.I'm curious what is the Rustacean way to do this (or other static ownership cult)

author: tellurium
author_rank: Frequent Contributor
timestamp: August 11, 2022, 01:26:34 pm
content: Quote from: tellurium on July 08, 2022, 10:47:06 amI actually took a stab and decided to do it, using some micro I have no prior experience with. Glanced over Mouser for an inexpensive devboard with built-in debugger, and chose an XMC 2 Go devboard,https://www.mouser.ie/ProductDetail/Infineon-Technologies/KIT_XMC_2GO_XMC1100_V1?qs=sGAEpiMZZMuqBwn8WqcFUm6zcOGmCo2HUOc17QP4cY7MLfWzpXwh5g%3D%3Dby Infineon. It it a nice tiny board with 2 LEDs and a built-in debugger.So far so good. I have read the manual, got surprised by Infineon's "universal serial" peripheral along the way, and put together the "first approach" - superloop - implementation that just blinks an LED, using only GCC, make and Jlink. All bare metal, everything hand-written and as small as possible:https://github.com/cpq/xmc1100Then I have noticed that the board heats up significantly... That was surprising. And after some time, an LED stopped blinking - apparently, it blew up! WTF? I can see only two things to blame - either my code gave too much stress to the board, or the LED did not have a resistor to limit the current. I can hardly believe in (2), but I cannot see anything wrong with the code either, it looks innocent.So there I have it, the effort ended on the very start.

author: JPortici
author_rank: Super Contributor
timestamp: August 11, 2022, 02:54:54 pm
content: Quote from: tellurium on August 11, 2022, 01:26:34 pmI -allegedly- know one of the contractors that fabricates demoboards for infineon and it wouldn't surprise me. about 10% of the boards we had produced from that contractor were defective and required rework (wrong component, wrong orientation, board defective, component not soldered realiably)

author: peter-h
author_rank: Super Contributor
timestamp: August 11, 2022, 02:55:37 pm
content: QuoteOff topic, but "ST HAL" code is auto generated. To make their software a bit less horrible, they don't make it very granular, so they do it so that the generated code checks for all kinds of config options (by reading back config register bits (!!), or by reading members of one of the countless structs they set up) and then jumps around a load of code.On the wider topic of RTOS code overhead, I have frequently traced through this stuff, and there isn't much. At 168MHz it is of the order of 1-2us (FreeRTOS). There doesn't need to be much; I wrote some simple RTOSes myself many years ago. Similarly, mutexes are 1-2us (I timed some). What takes quite a bit longer is using message queues; a lot of people regard these as some kind of "RTOS purism", and the hallmark of a professional coder, and they are atomic which is nice, and they probably are delivered in a guaranteed order, but there is a lot more code. And the space for them may well be allocated from a private heap...But the real issue here is that the software architecture needs to be appropriate for the job. Let's say you are doing high speed 3 phase motor control, and sophisticated algos too, doing ADC sampling at say 1MHz. No way will any RTOS get around fast enough for that to work. You will need a dedicated ADC subsystem, with ADC sampling driven by timer, then interrupts, and very short ISRs. Possibly, use ADC -> DMA because reading RAM is a lot faster than reading ADC registers. Then maybe have an RTOS for the background stuff (like an HTTP server for local config).

author: tggzzz
author_rank: Super Contributor
timestamp: August 11, 2022, 04:00:23 pm
content: For "crap" read slow, flaky, unmaintainable, correct, incorrect, tested, verified, validated, proven, as appropriate.Some people and companies knowingly generate crap.Some people and companies unknowingly generate crap.Some people and companies don't care if they generate crap.Some people and companies don't generate crap.When implementing something using libraries (and to a lesser extent hardware), you need to understandthe meaning of "crap"what type of people and companies created the componentsWhen designing and implementing a component, you need to defend against things not working as you think they do.