How to plan/structure interrupt-based code? - Page 1
Forum section: electronics / general

author: petemate
author_rank: Regular Contributor
timestamp: September 18, 2020, 07:10:53 pm
content: Hi guys, I have done simple microcontroller code before(think "everthing in one main loop), but for this new project I'm doing, I need to handle a "control loop" as well as LED indicators, ADC sampling and serial communication. As far as I understand, you use interrupts and/or timers to make sure that your relevant code is executed at the right time(or rather, at a certain repetition). I am looking for guides or principles regarding how you structure this principle of controlling "several things at once".The project is about making an analog(+/- 10V programming voltrage) PSU controlable from a digital(USB/serial) input. I would need to be able to send commands via a virtual com port to set voltage and read back information in a SCPI kinda way. E.g. "set:volt 10" would then set the output voltage of the PSU to 10V. "output on/off", etc. Sending these commands should then set the analog programming voltage to whatever and turn on the PSU through the analog on/off pin. The ADC should then read the voltage(there is a 0..10V monitor voltage as well) and adjust the outptut to that it is a close as possible to the actual voltage desired. In addition, are also a number of open collector outputs from the PSU that can inform about over-temperature, CC, CV, etc. I'd like to "scan" these as well.I produced a small PCB with the required hardware, based on an STM32F103 mcu. Now i need to get going with the structure of the code. How do I plan the "interrupt layout" to have all these functions running "simultaneously"? Its kind of a "structure" or "principles of programming" kind of question, but I hope someone can give me some pointers.Thanks!

author: GromBeestje
author_rank: Frequent Contributor
timestamp: September 18, 2020, 07:28:39 pm
content: My approach would be something like this: do as little as possible in the interrupt. rather set a flag that triggers something in the main loop. Have timers set a flag it is time to do something, have the ADC values written to a variable and set a flag the data is ready.Make sure you declare the variables you set inside an interrupt as volatile.

author: petemate
author_rank: Regular Contributor
timestamp: September 18, 2020, 08:42:15 pm
content: Quote from: GromBeestje on September 18, 2020, 07:28:39 pmThanks for your input! I wasn't even aware of the volatile declaration: I read this blog post and its clear that I need to take this into account.https://barrgroup.com/embedded-systems/how-to/c-volatile-keywordI'll try to keep the interrupts as short as possible and use flags instead.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 18, 2020, 08:47:47 pm
content: I would put the control loop inside a timer interrupt - read ADC, decide on the adjustments, do them, exit ISR.Everything else doesn't seem to be time-critical and can wait, so do it all in the main loop in a round-robin fashion.

author: TomS_
author_rank: Frequent Contributor
timestamp: September 18, 2020, 09:08:52 pm
content: If jitter is a concern, you may need to do your work in the ISR. Using the ISR to set a flag and then have an action performed in the main loop will not be deterministic, and jitter will vary depending on where are are in the main loop when the flash is set.Or you have to go to a lot of effort to have your main loop tasks yield when various flags have been set to allow a higher priority task to run, but there will still be an amount of jitter involved.So you might split the work between the ISR and the main loop, e.g.: ISR starts an ADC conversion and sets a flag, main loop can then wait for the conversion and process the result in time - but at least you captured the result with absolutely minimal jitter.

author: ggchab
author_rank: Frequent Contributor
timestamp: September 18, 2020, 10:28:37 pm
content: I have done something very similar with an 8 bit PIC (18F25K22).The only thing that is easier via interrupts is sending/receiving characters on the serial port via buffers that can be read/written from the application. If you need delays, you may also use a timer that set flags, decrements counters, ... via interrupts. This can be seen as "services" offered to the application.Time critical operations may also be handled via interrupts (ex.: detecting power cut to save parameters in eeprom).Your main loop should be as simple as possible (ex.: calling a function per task). Each task should be well disciplined and keep CPU a minimum of time. Each task may divide itself in small sub-tasks and only one of them is executed when the function is called.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 18, 2020, 10:31:51 pm
content: petemate, the approach or pattern or paradigm is calledevent-driven programming.If you've done ordinary Arduino sketches, that one isimperative programming.The difference is that in imperative programming, you the programmer are in complete control of what the microcontroller does at any point in time; in event-driven programming, you write code thatreactsto different events. With hardware interrupts (and e.g. POSIX signals in Linux/Mac/BSD OSes), those events are usually different functions, and while the event (function) is being executed, nothing else happens. The event can occur anywhere except in critical sections (where interrupts, or POSIX signals in Linux/Mac/BSD OSes, are blocked). Because of this, it is crucial that the event handler does notsleepor dilly-dally.Exactly how to plan or structure your code, depends on whether you have BOTH interrupt (event-based) actions AND continuously-running "work".Let's say you have a simple sketch that generates a pulse stream, say for LEDs or some such, and some buttons that start, stop, and change the stream being output.For the three buttons, you could install interrupt functions (with software debouncing), that simply changestatic volatile boolorstatic volatile intflags. (Remember, Arduino uses GNU C++.staticin the file scope means the variable or function it is only visible in this file scope; you can omit it if it weirds you out.)The loop() function would generate the next pulse, except that first it would check the flags, and change its operating mode accordingly. (The loop() function would basically be the iterative part of a simplefinite state machine.)Okay, but why would anyone use an interrupt for that, instead of just checking the button states at the beginning?Because with the interrupt approach, you can detect keypresses much shorter than the duration of a single iteration of the loop.Essentially, this sketch would have a continuously running pulse generator, which regularly checks certain volatile flags that modify its operation. Those flags are updated by the interrupt functions.There is one nasty detail: Interrupts can be interrupted by higher-priority ones, too – you can usually change the interrupt priorities, though – and "atomicity" is an issue. What "atomicity" means, is that when changing some variable, it may actually require more than one underlying machine instruction, and if interrupted in mid-modification, with the interrupt handling function seeing a partially modified value. Ifthatalso modifies the value, and execution returns to the earlier interrupt which only modifies the rest of the variable value, the value becomes corrupted.If the interrupt only examines the flags, and does not change their values, there can be no problem. In the case where interrupt functions only set a flag, and normal (loop()) code only reads the flags, you do not need to worry about atomicity. The atomicity issues only occur when both normal code and interrupt code can modify the same variable.Different environments provide different tools to work with this. The Arduino environment providesATOMIC_BLOCK(ATOMIC_RESTORESTATE) {critical operations}macro when<util/atomic.h>is included. The GCC compiler provides__atomic_built-in functions (functions provided by the GCC compiler, not by any library or environment, for a given hardware architecture). In pseudocode, the typical sequence isCode:Let intsEnabled = (state of interrupts enabled status flag)If intsEnabled:Disable_InterruptsEnd IfCritical operationsIf intsEnabled:Enable_InterruptsEnd If(This is what the Arduino macro on AVRs does, expanding into a single-iterationforloop.)The idea is that interrupts are disabled for the duration of the atomic or critical operations, and if enabled before, re-enabled afterwards. (If these critical operations happen to be part of a larger set of critical operations, interrupts could be already disabled – even if we are in an interrupt handler. That's the reason why simply disabling and re-enabling interrupts may not always work the way intended!)The GCC atomic operations use simple hardware instructions for variable sizes that are known to be atomic on that hardware, and some other code for larger sizes.Regardless of which environment – or even bare-metal – you use, the above concepts do apply. Only the details vary between environments (hardware and libraries).

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 18, 2020, 10:40:59 pm
content: To OP: There are several ways to solve this, so it depends on the details of your application. For a clean software design, you would make use of knowledge from distributed and event-oriented systems.The following two things are especially important:You should separate the different layers of abstraction from each other. In this instance, you have two layers: the technical MCU stuff (interrupt handling) and the control logic layer (your actual event handling). Don't mix those two but decouple interrupt handling from event handling. That's important for maintainability, understandability, and also correctness.Do your event handlers modify shared system state that is prone to race conditions? If yes, you have to care for mutual exclusion, either via queues, temporary interrupt disablement or via a specific mutex algorithm. Interrupt disablement is not necessarily a good idea because the system can become unresponsive.I personally lean towards a queue. This would entail not to call the event handler directly from your interrupt handler but to put the event into a queue. This is an extremely fast operation, and the interrupt handler can exit very quickly. Then, your main loop executes the events that are stored in the queue. This way, it cannot happen that multiple events are executed simultaneously, the system is automatically free from race conditions provided that you have correct resource management w.r.t. your MCU state and you have cared for mutual exclusion on the queue itself. However, this approach increases LATENCY and it's not necessarily ok for you. I don't know your particular parameters, so it depends on your situation.

author: petemate
author_rank: Regular Contributor
timestamp: September 18, 2020, 11:32:46 pm
content: Quote from: NorthGuy on September 18, 2020, 08:47:47 pmThank you for your reply! A agree that the control loop is to be controlled at a specific instance, so that should definitely be interrupt-based. As pointed out elsewhere, jitter could become an issue if it is not done this way.I would like some LEDs to do various flashing patterns(e.g. a fast blinking red LED as an alarm indication. How would I handle the blinking of the LED? Should that also be interrupt based?I don't know yet how the USB com port thingie works, bud i'd assume there'd be some functions for this. Maybe an interrupt every time a buffer is full?Quote from: TomS_ on September 18, 2020, 09:08:52 pmThanks for replying! Good point on jitter if the control loop is not run in an ISR.Quote from: ggchab on September 18, 2020, 10:28:37 pmThank you for your input. I will definitely try to do a highly structured code and avoid a messy main loop.Quote from: Nominal Animal on September 18, 2020, 10:31:51 pmThank you very much for your writeup. Getting the lingo right is important to me and its good to know what I'm actually asking aboutI read a little bit about atomic operations and static variables and while i think I have it covered, its good to know about potential issues. Thanks again!Quote from: Fixpoint on September 18, 2020, 10:40:59 pmThanks for your suggestion! I like the idea about a queue, but I'm afraid the overhead and work involved would drag me away from the actual task of programming this thingI'd like to keep it as simple as possible, as I'm already in over my head

author: Renate
author_rank: Super Contributor
timestamp: September 18, 2020, 11:38:05 pm
content: If what you're doing isn't rocket science, one approach is to have your main loop be:Code:for(;;) sleep();Just handle everything in your interrupt routines.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 19, 2020, 12:03:16 am
content: Quote from: petemate on September 18, 2020, 11:32:46 pmThe overhead is almost non-existent, that's exactly one of the advantages of this approach. There is only potentially higher latency, but overhead is basically 0. The latency could be increased because an incoming event has to wait until its predecessor has finished, but that is probably anyway what you want.In its simplest implementation, the queue would just be an array together with an index into the array, so you don't have to implement complicated things.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 19, 2020, 12:51:03 am
content: Quote from: petemate on September 18, 2020, 11:32:46 pmSure, why not. Consider the following almost-standard C (it needs usleep() or Sleep() to sleep for a fraction of a second between "interrupts", but one should be provided by your OS):Code:#define _POSIX_C_SOURCE 200112L#define _DEFAULT_SOURCE#include <stdlib.h>#include <stdint.h> /* This is available even in freestanding environments! */#include <stdio.h>/* Hack to implement sleep_ms(milliseconds) on different OSes */#if defined(_WIN32) || defined(_WIN64)#include <synchapi.h>static void sleep_ms(unsigned long ms) { Sleep(ms); }#else#include <unistd.h>static void sleep_ms(unsigned long ms) { usleep(ms * 1000L); }#endif/* Pulse train macros */#define PULSE_END() 0#define PULSE(ontime, offtime) (((offtime) & 15) | (((ontime) & 15) << 4))#define PULSE_OFFVAL(value) ((value) & 15)#define PULSE_ONVAL(value) (((value) >> 4) & 15)/* Pulse train variables */static volatile uint_fast8_t pulse_mode = 0; /* 0 = stopped, 1 = on, 2 = off */static volatile uint_fast8_t pulse_left = 0; /* Ticks left in current state */static volatile const uint_fast8_t *pulse_next = NULL;static void pulse_interrupt(void){/* No pulse train? */if (!pulse_mode || !pulse_next)return;/* Current state still active? */if (pulse_left--)return;/* Which transition? */if (pulse_mode == 1) {/* On -> Off */fprintf(stderr, "\r- ");pulse_left = PULSE_OFFVAL(*pulse_next);pulse_mode = 2;pulse_next++;} elseif (*pulse_next) {/* Off -> On */fprintf(stderr, "\rX ");pulse_left = PULSE_ONVAL(*pulse_next);pulse_mode = 1;} else {/* End of pulse train. */fprintf(stderr, "\r \n");pulse_left = 0;pulse_mode = 0;pulse_next = NULL;}}static const uint_fast8_t pwm[] = {/* X---- XX--- XXX-- XXXX- XXX-- XX--- XXXX- */PULSE(1, 4), PULSE(2, 3), PULSE(3, 2), PULSE(4, 1), PULSE(3, 2), PULSE(2, 3), PULSE(4, 1), PULSE_END()};int main(void){/* Set up pulse train */pulse_next = NULL;pulse_mode = 2;pulse_left = 0;pulse_next = pwm;/* Simulate interrupts every 200ms = 0.2s until pulse train is complete. */while (pulse_mode) {pulse_interrupt();sleep_ms(200);}return EXIT_SUCCESS;}Thepwm[]array specifies the pulse train, one byte per pulse. A zero byte ends the pulse train. High nibble defines the duration of the ON phase, and the low nibble the duration of the OFF phase. Thus, you can also specify pulse trains as strings, like "\xGH\xIJ\xKL\0", where G, I, and K are hexadecimal digits specifying the durations of the three ON pulses, H and J the intervals between the pulses, and L the duration after the last pulse before the pulse train is complete.To stop an ongoing pulse train, dopulse_next = NULL; pulse_mode = 0; pulse_left = 0;To start, restart, or switch to a new pulse train, dopulse_next = NULL; pulse_mode = 2; pulse_left = 0; pulse_next =pwm;wherepwmis an array of pulse definitions, or a pointer to the next pulse.If you look at the function, you'll see that clearingpulse_nextensures the interrupt does nothing. Do note that you can only do this safely in your "main" code (non-interrupt code), or in an interrupt that cannot interrupt the pulse interrupt; you may need to check the interrupt priorities to make sure.Here, the event is a timer "tick", a regularly occurring event. In the handler, we use thepulse_leftvariable to tell us how many ticks we have left until nextpulse edge. When it is zero when the handler is called, we handle the next edge. There are three such edges: off to on, on to off, and end of pulse train.As a finite state machine, it really just has five different states.The main() just simulates the interrupt. Feel free to change the state changefprintf(stderr, )prints (if you switch to standard output, remember to addfflush(stdout);so that your C library outputs the data immediately, instead of caching until the next newline).For those wondering why I posted this, I'm hoping the real-world example gives the OP an intuitive "feel" of event-driven programming, in the form of a simple C text-mode application; at the same time providing a simple "test bench pattern" of "simulating" interrupt-driven C programming in easy standard/hosted C environment.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: September 19, 2020, 02:31:29 am
content: Note that a power supply doesn't need much, or a very reliable, sample rate, so you could very well do all of this in the main() loop -- no interrupts needed:Code:sample_rate_timer_reset();while (1) {adc_start_acquisition_on_channel(current_index);command_buffer(serial_getc());if (command_received()) {process_command();}if (adc_done() && sample_rate_timer()) {sample_rate_timer_reset();adc_readings = adc_new_value();current_index++;if (current_index >= (sizeof(adc_readings) / sizeof(adc_readings)) {current_index = 0;update_display();}}}This loop has a worst case execution time of [command processing time]. If this is always shorter than the time to receive a serial character, you won't miss anything. Or if it's hardware buffered (like a 16550 UART), and you're guaranteed to never have a command so long that that buffer overflows, same idea.Presumably, the command processing will be very brief in the average case (receiving single characters?), and only a little longer when actually doing something. So the sample rate and jitter won't bethatnasty, and you are unlikely (or guaranteed not) to miss serial characters.Missing characters during command execution might still be acceptable behavior, anyway. Or with flow control (Xon/Xoff or hardware), this is intended behavior! (Flow control means the host machine stops sending characters until the client is ready to receive more. Xon/Xoff uses special ASCII characters inline, so can't be used with a binary control set.)Now, if you want to use it as an arb gen (setting a new setpoint on potentially every sample time), you'll want a more stable sample rate, and it's probably a good idea to trigger the DAC update from an interrupt (maybe from a timer).Note that I went right to discussing the execution time of the loop. That's the real-time dependency here. If you use interrupts, you can have all the slow and noncritical stuff running in main(), and not really care about this; but you still need to do your due diligence -- and in different and sometimes surprising ways. Namely, thread safety: shared variables need to be taggedvolatile(which tells the compiler to be proactive when reading/writing that memory location), and access needs to beatomicso that the interrupt is never reading or writing a value when main() is using it.In that case, a typical application would be using buffered serial I/O, and probably a timer or ADC interrupt to do the signal stuff. With buffering, atomic access is handled by the driver interface, and you can use non-blocking functions to read and write (that is, they execute faster than it takes to twiddle the actual serial characters). Don't put command execution into the interrupts: the interrupt should return quickly. Instead, set a flag and let main() deal with it. (This can even be expanded into an event system, though that would be complete overkill for a project like this!)Tim

author: emece67
author_rank: Frequent Contributor
timestamp: September 19, 2020, 11:35:19 am
content: .

author: Renate
author_rank: Super Contributor
timestamp: September 19, 2020, 12:04:46 pm
content: Quote from: emece67 on September 19, 2020, 11:35:19 amJust to clarify, I agree, one should never use a non-interruptable sleep.When I mentioned sleep above, I meant specifically the AVR8 instruction SLEEP which (in one mode) means, go to idle and wait for interrupt.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: September 19, 2020, 01:37:10 pm
content: I always make a 1ms interrupt for program timers.Timer_X_handler // 1 ms timouts{if(LedTimer)LedTimer--;if(CheckSerialTimer)CheckSerialTimer--;***DoOneMsLoop = 1;}To use a timer just set it to the desired value in ms.Then in codeIf(LedTimer)LedTimerTimingFlag = 1;else if(LedTimerTimingFlag){LedTimerTimingFlag = 0;// do whatever you want to do when timer expires}use a que for serial communications with RX interrupt.If any task can't be interrupted give its ISR higher priority.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 19, 2020, 02:22:22 pm
content: Quote from: petemate on September 18, 2020, 11:32:46 pmPlanning MCU timing is very much like planning your day.For example, you need to give your mother medicine every second hour. You set an alarm. When it goes off, you quit doing what you were doing and go give the medicine to your mother. That's the same as interrupt - an event triggers the interrupt, CPU stops execution, enters ISR (interrupt service routine) and runs it ahead of other code. This ensures that the routine gets executed as close to the event as possible. The same as you quitting all affairs and running to give the medicine.But not all actions are urgent. For example, you need to pay electric bill, which is important, but not urgent. Imagine you set an alarm which rings when the electric bill arrives, and as soon as it goes off you quit everything, disable your medicine alarm (so that it wouldn't interfere with paying the electric bill) and go pay your bill. You probably wouldn't do it this, would you? Similarly, you don't need interrupts to handle slow events, you just check them in a loop:Code:while (1) {if (time_to_switch_LED()) {switch_LED();}if (USB_packed_arrived()) {process_USB_packet();}...}It takes time to get through the loop, but since your actions don't require urgency, it doesn't matter.Things are different when you want to save power and sleep. Just as you must use alarm clock to wake you up in the morning, you must use interrupts to wake CPU from sleep.

author: Renate
author_rank: Super Contributor
timestamp: September 19, 2020, 03:32:02 pm
content: Quote from: NorthGuy on September 19, 2020, 02:22:22 pmTrue, but why wouldn't you use interrupts?If your application isn't rocket science, handle everything in ISRs, go into idle in the main loop.If you don't re-enable interrupts in your ISRs then you get builtin mutual exclusion.What's in most ISRs? Mostly push a byte on a queue, decide if something needs to be done and push some bytes on someone else's queue.This can all be done quickly.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 19, 2020, 04:16:07 pm
content: Quote from: Renate on September 19, 2020, 03:32:02 pmSimpler structure. Less work. No data travelling across threads. Often better latency for the interrupts you do need.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 19, 2020, 05:15:48 pm
content: Quote from: NorthGuy on September 19, 2020, 04:16:07 pmHm? Interrupts have nothing to do with threads.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 19, 2020, 06:54:29 pm
content: Quote from: Fixpoint on September 19, 2020, 05:15:48 pmISR runs as a different thread. It preemts the execution, saves context, executes, restores context and exits.Hence, if you want to share data between the "main" and the ISR, you may need to take measures to preserve data integrity as the data travel back and forth. If you put the same code in the "main", everything is in the same thread and you don't need to do anything special.

author: Kleinstein
author_rank: Super Contributor
timestamp: September 19, 2020, 07:29:12 pm
content: Interrupts are a bit similar to multiple threads, though they are not exactly the same. So many things from multi threaded programs also apply the ISR programming.The main point for an event driven system is that every ISR/job is executed in time. So the maximum delay for each ISR is an important parameter to look at when planing. Even if not critical an ISR should usually be finished at least before the next interrupt of the same type comes.One can often leave one job to the main program. If power consumption is critical this job is often going into power saving mode when there is idle time.There is little use of only setting flags in an ISR - quite often the HW does this anyway to signal an ISR - so in this case one could use the HW flags directly. Anyway putting the job to an extra SW handled scheduler only makes things more complicated. With interrupt priorities one can usually do all the jobs in the ISRs. In rare cases one may have to alow nested interrupts. Moving the actual work to the main program does not get the jobs done faster - so it usually does not help.An important point is usually to avoid any longer time active waiting more than a few cycles. If there is a delay needed don't be shy using another timer and interrupt to call for the next interrupt in a few µs if needed. This way one may also split longer ISRs into 2 smaller ones if needed. Though the interrupts give some kind of preemptive multitasking, one should program like having cooperative multi-tasking. So be nice to the other ISRs and don't waste CPU time.The data flow and interactions are an important point - so make sure to not use things like half updated data entries. This can be simple if there are no nested interrupts, but it can be quite complicated with nested interrupts.

author: Renate
author_rank: Super Contributor
timestamp: September 19, 2020, 07:32:54 pm
content: Quote from: NorthGuy on September 19, 2020, 06:54:29 pmBut that's the point, I don't want to share any data with main because main is a zero.I can even flag my ISRs with "bare" so that they don't save any registers.Quote from: Kleinstein on September 19, 2020, 07:29:12 pmThis ^.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 19, 2020, 07:35:59 pm
content: Quote from: NorthGuy on September 19, 2020, 06:54:29 pmNo, this is just nonsense. Please! When a CPU receives an interrupt signal on one of its pins, it reacts by pushing the instruction pointer to the stack and setting the instruction pointer to the address of the respective interrupt handler routine. After the routine has finished, the instruction pointer is restored from the stack. That's all.A thread is a completely different thing, and many widely available microcontrollers don't support them. And even if the hardware supports them, you still need operating-system like software support (which, by the way, would be overkill for 99% of embedded applications).QuotePlease don't write stuff like this, in this generality it is wrong on a very fundamental level and people (beginners) could actually believe you and do that in their code.When your "main" function is running, an interrupt handler could be called which could invalidate many state invariants that are assumed by "main". The consequence would be a crash or undefined behavior of your application.The only situation where this cannot happen is when ALL interrupts have been disabled. However, then there is no guarantee anymore that hardware events are handled in time because the software has to do polling. Even a little change in system behavior would then destroy all the implicit timing invariants. So, from a design perspective polling is unsafe and impairs maintainability and correctness.By the way, The OP wanted to know how to do a GOOD design, not a bad one.Quote from: KleinsteinPlease, let's be correct and precise here and let us not confuse things. Beginners will not learn anything by that. After all, this is an engineering forum.

author: Renate
author_rank: Super Contributor
timestamp: September 19, 2020, 08:00:58 pm
content: Erm, I didn't mean "bare", I meant "naked".Code:ISR(USB_COM_vect, ISR_NAKED){// blah, blah, blahreti();}If you look at your ISRs you'll see that most of it is push/pop.Edit: I didn't mention reti() before.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 19, 2020, 08:39:04 pm
content: Quote from: Renate on September 19, 2020, 07:32:54 pmYou don't need to save registers only if you're not preemting anything useful. This means that all of your interrupts are on the same level and do not preemt each other. You certainly can do this, but then your fast interrupts (such as ADC in the OP's case) would have to wait until you're finished with the slow stuff.It's better to use interrupts for fast things (such as ADC in OP's case) and the main thread is doing the slow stuff without affecting the latency of the fast interrupts.If you really want interrupts for everything, you need a CPU with multi-level interrupts and you need to assign priorities to ensure that your fast interrupts preempt the slow ones. This will yield about the same performance as if you had all the stuff in the main loop, but you need to do extra work setting all the interrupts and priorities without any benefits.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 19, 2020, 09:09:37 pm
content: Quote from: Fixpoint on September 19, 2020, 07:35:59 pmBasically. The context needs to be saved or everything blows. But these are details. What is important is that the interrupt can happen at any time and can preempt your execution. For example, in the ISR you have:Code:a = 1;and in the main thread:Code:// cannot have both "a" and "b" set to one or the bridge blowsif (a == 0) {b = 1;}There may be interference between these like this:- a is fetched, compared to 0, and since it is zero, the "if" is taken.- interrupt happens and ISR is invoked- ISR executes "a = 1"- ISR returns- The main thread excutes "b = 1". Now both a and b are set to 1 which causes an explosion.This is certainly very silly example, but nonetheless it shows that you need to watch for such situation.In contrast, if both of these statements are in the main thread:Code:// one place:a = 0;...// another place:// cannot have both "a" and "b" set to one or the bridge blowsif (a == 0) {b = 1;}there's no danger for such interference.Quote from: Fixpoint on September 19, 2020, 07:35:59 pmInterrupts only operate on certain variables, and you need to understand the consequences of using such variables in the main thread (see an example above). Other variables (which are not used by the interrupts) will not suddenly change just because an interrupt happens.This is basically the point I'm trying to make. If you have less interrupts which affect less variables, it is less work for you looking for possible interference.

author: Renate
author_rank: Super Contributor
timestamp: September 19, 2020, 09:15:26 pm
content: Quote from: NorthGuy on September 19, 2020, 09:09:37 pmIf you don't have a main which affects variables, it is less work for you looking for possible interference.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 19, 2020, 09:33:06 pm
content: Quote from: NorthGuy on September 19, 2020, 09:09:37 pmWell, while we're at it, let's use the correct terminology: the thing we are talking about is called "race conditions", not "interference". Race conditions not only concern variables. Since we are talking about embedded systems, we also have to consider hardware state, not just software state. Usually, an interrupt handler will change the hardware state in some way. In the simplest case, it could be some flags (usually harmless, but sometimes dangerous), and in more complicated scenarios, some configuration of the MCU (pins, ADC, UART, USB, etc etc etc). That's why interrupts are often disabled as long as an ISR is running: the ISR has to do things that must not be interrupted, otherwise the semantics of the code would be undefined.But as I said: even if you disable ALL interrupts and do everything from main by software polling, you have a very grave problem in your design because of the implicit temporal couplings. Your invariants can (and will) be destroyed by software changes (random errors or even crashes are guaranteed) or sometimes even temperature changes because the oscillator could be sensitive to temperature, and then the software polling is not guaranteed to work anymore because the timing is off.Replacing interrupts by polling introduces a pretty serious design flaw because of the implicit temporal couplings. Polling may be simpler than using interrupts, but this simplicity makes the system fragile and hard to maintain.

author: petemate
author_rank: Regular Contributor
timestamp: September 19, 2020, 09:34:51 pm
content: Thank you all for your comments and suggestions! They have given me a lot to think about. I am still waiting for the last few parts, but I think I got enough info to start experimenting and my board is already up and running(only one mistake: Forgot to pull BOOT0).I'll report back in due time, when I have some working code. I'm sure I'll have more questions for you throughout the project

author: uer166
author_rank: Super Contributor
timestamp: September 21, 2020, 04:26:28 am
content: Quote from: Fixpoint on September 19, 2020, 09:33:06 pmYou have temporal couplings between different interrupts and interrupts <-> main() context anyway. A polling-only architecture (a cooperative scheduler) is deterministic and safe if the actual application is written with requisite care (again as it would be anyway with interrupts).https://www.safetty.net/download/pont_pttes_2001.pdf

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 21, 2020, 09:45:00 am
content: Quote from: uer166 on September 21, 2020, 04:26:28 amThis depends on how many flaws there are your software design. In many cases, it can be avoided. An obvious exception is when your application requires such extremely tight timing that you just cannot decouple things because the timing would be influenced by that.QuoteThat is a null statement. "Your system will work if you do everything right." Aha.But seriously:(a) From the viewpoint of determinism and indeterminism, a cooperative scheduler is a very problematic thing, because it only has very little control about scheduling (that's what "cooperative" means), and a scheduler that only has little control isn't a great help when it comes to achieving safety and determinism. To be exact: a cooperative scheduler only has control over the ORDER of tasks, but not over the TIMING of tasks. But timing is a fundamental and crucial property of safety-critical systems. For example, if your ESP (in a car) is not timed exactly right down to a millisecond, then the driver might experience a surprise. So, a cooperative scheduler is actually an old, outdated, primitive and deeply flawed design that only works because the rest of the system is designed with these flaws in mind.Because systems with cooperative schedulers are extremely unreliable and prone to crashing, such schedulers were banned from non-embedded systems during the 90s. It is ironic that safety-criticial embedded systems, of all things, still use them. I am pretty sure this is purely because of the huge code base and because a redesign is not deemed feasible, but not because it has anything to do with a reliable, solid software design.Once, I myself worked for a large corporation in a team that was building a new operating system for cars. Because it was a new system, the scheduler was built from the ground up. For starters, in order to achieve safety, it wasn't a cooperative design. (It wasn't my design, by the way. The scheduler was already there when I joined the team.)(b) Let us assume you have a really deterministic scheduler. Keep in mind that it does not nothing more than -- well, deterministic scheduling. If you use such a scheduler, ok, then your scheduling is determinstic. But that does still NOT mean that the *system itself* is deterministic. Each of your tasks needs to be deterministic, too. And this goes even beyond the timing aspect. That's not just academic blabla, it is very real.(c) Finally: Determinism neither means that your system works nor that it is safe nor that it is maintainable. It just means that its semantics is defined in a specific way (which I surely shouldn't go into at this point), but that does not equate to "simple" or "easy". It could still be very complex and broken at 1000 dangerous places without anybody knowing it.

author: mac.6
author_rank: Regular Contributor
timestamp: September 21, 2020, 10:47:51 am
content: Quite frankly, unless you are in a very thigh (space/timing) environment on a puny platform, you don't want to reinvent the wheel and code a scheduler by yourself.Go directly to a rtos, yes it has some drawback but at least your productivity will increase and you won't end up debugging non core code.

author: newbrain
author_rank: Super Contributor
timestamp: September 21, 2020, 10:49:27 am
content: Nobody did it (explicitly, as yet, but Fixpoint's posts are quite the hint...see mac.6's post), so I'll throw this in the arena:Consider using a RTOS:The overhead, if you don't ned to squeeze the last drop of performance, is quite OK.There's no need to reinvent, write and debug a scheduler (and many RTOSes provide a choice of schedulers) or synchronization primitives.They are quite easy to learn.They give a greater flexibility in adding and removing tasks.The general approach is:An HW event happens (e.g. char received by the UART, ADC conversion ready, the user turned an encoder, a DMA has finished)An interrupt routine just caches the data (if needed), signals the the relevant task and exits, this limits the race and data sharing issues.The task is scheduled according to its priority (e.g. higher for input tasks, lower for presenting data on a display etc.).Having done it both ways for my projects, I'm starting to appreciate an RTOS (FreeRTOS in my case) more and more.I wrote the code in my PSU in the superloop plus ISR way.It works and is readable (if I say so), but a bit inflexible and hard to modify.For the code for a DDS based generator (AD9834+TFT LCD+encoders+DACs+UART, so quite similar in scope) I used FreeRTOS, on an STM32F072.It's clearer, easier to maintain and much more flexible, well worth the initial learning effort and the overhead.For an SDR I'm working on, I would never have considered going bare metal.

author: greenpossum
author_rank: Frequent Contributor
timestamp: September 21, 2020, 10:52:23 am
content: Well it goes like this:1. First you have toJust a moment, I have an incoming phone call, back in a sec...

author: dietert1
author_rank: Super Contributor
timestamp: September 21, 2020, 11:10:03 am
content: I agree the best recommendation to plan/structure an embedded project with real-time requirements is to use a real-time operating system. The developers of those libraries thought a lot about all problems mentioned above and learning to use a library means learning a basic, standard approach. No need to invent anything.The STM32 is strong enough to handle the overhead of using a predefined library. Beginning a project with optimization is a really bad idea.Regards, Dieter

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 21, 2020, 12:08:49 pm
content: Quote from: dietert1 on September 21, 2020, 11:10:03 amThat is music for my ears.

author: Howardlong
author_rank: Super Contributor
timestamp: September 21, 2020, 12:38:09 pm
content: Regarding using ISRs just to set flags, depending on the architecture, that might already be supported without even the need for an ISR. It's very common in PICs for example to poll a peripherals' xxxIF flag without the need for an ISR.Whether or not to use an ISR for a given task is dependent on how timely a response is required.Whether or not to use an RTOS is dependent on how complex the application is. Most of the time, I find it's simply not justified in deeply embedded systems. But if you start adding touch screen UIs etc, with other features such as networking, that's probably a good time to consider one. There's no such thing as a free lunch though, RTOSes by their very nature force you to structure code in certain ways, and there is usually some overhead associated with RTOSes in terms of performance and memory use.

author: Renate
author_rank: Super Contributor
timestamp: September 21, 2020, 01:48:53 pm
content: Well, the obvious solution is to throw some gigaherz at it and use Windows.Unrelated aside: Worst case preamble/postamble for AVR8 ISR is a total of 38 instructions.Code:1f 92 push r10f 92 push r00f b6 in r0, 0x3f0f 92 push r011 24 eor r1, r12f 93 push r183f 93 push r194f 93 push r205f 93 push r216f 93 push r227f 93 push r238f 93 push r249f 93 push r25af 93 push r26bf 93 push r27cf 93 push r28df 93 push r29ef 93 push r30ff 93 push r31ff 91 pop r31ef 91 pop r30df 91 pop r29cf 91 pop r28bf 91 pop r27af 91 pop r269f 91 pop r258f 91 pop r247f 91 pop r236f 91 pop r225f 91 pop r214f 91 pop r203f 91 pop r192f 91 pop r180f 90 pop r00f be out 0x3f, r00f 90 pop r01f 90 pop r118 95 reti

author: dietert1
author_rank: Super Contributor
timestamp: September 21, 2020, 02:35:43 pm
content: In a 32 bit arm processor there are instructions for push and pop multiple registers. The interrupt context switch in a STM32 will be a fraction of a microsecond. No need for gigahertz.Regards, Dieter

author: Renate
author_rank: Super Contributor
timestamp: September 21, 2020, 02:50:26 pm
content: Quote from: dietert1 on September 21, 2020, 02:35:43 pmIn the Arm FIQ interrupt there is register bank switching too.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 21, 2020, 03:26:37 pm
content: Quote from: dietert1 on September 21, 2020, 02:35:43 pmNot that it matters, but the STM/LDM instructions certainly don't execute in a single clock and their execution time increases proportionally with the number of registers being saved/loaded.

author: Howardlong
author_rank: Super Contributor
timestamp: September 21, 2020, 03:33:46 pm
content: Quote from: NorthGuy on September 21, 2020, 03:26:37 pmAdd to that, ARM is, by design, solidly register based. That's a key point of its architecture, do as much as possible on the core without having to refer to lower speed off-core memory. Therefore, potentially you have a lot of context to save, and latency suffers as a result. The STM/LDM only saves you multiple instruction fetches, it doesn't save you the time to save/load to off-core memory.In some MIPS implementations (and possibly some ARM implementations, I don't know), there are shadow register sets on the core, allowing instant context switching for the most demanding of scenarios.

author: rstofer
author_rank: Super Contributor
timestamp: September 21, 2020, 04:04:58 pm
content: Research the GCC function attribute 'naked'. If a function has this attribute, there will be no prolog or epilog code generated. It's all on you! But if all you are doing is setting a flag, you can do a wee bit of assembly code to save a register, set the register to point to the flag and then stuff a '1' in the flag. Restore the register and return.Here's another idea that comes from early gaming: If you use a superloop, you can have a loop counter, say 0..3. Every time through the loop with a particular counter value, certain functions are executed, others are skipped. Important functions might get called on every pass and so on.The right way to do this stuff is with an RTOS like FreeRTOS which has ports for all popular processors.Depending on what needs to be done and how quick, consider a PSoC 6 with dual cores - use the M4 core for the mainline stuff and use the M0 core for the interrupt driven and IO stuff. Yes, there a FreeRTOS port for the PSoC 6. There are a bunch of videos as well. Just Google for 'PSoC 6 videos'

author: emece67
author_rank: Frequent Contributor
timestamp: September 21, 2020, 04:13:00 pm
content: .

author: Howardlong
author_rank: Super Contributor
timestamp: September 21, 2020, 05:29:20 pm
content: I'm going to strongly disagree with those calling for RTOS as a blanket approach. As I mentioned, in deeply embedded systems it's rare to need one, and I'd say they usually get in the way in most of those cases.There's definitely a time and a place for RTOSes, but, if you don't care much about resources, power consumption, BOM cost, development cost, maintenance cost, upskilling etc, then whatever floats your boat on your blinky project ;-)I've just always found that approach disappointing and defeatist, it makes me wonder what else might be being overlooked. :-(There's certainly absolutely nothing in the OP's specification that justifies an RTOS, unless they want something extra to have to learn. If someone came to me with that spec and said they'd be using an RTOS, I'd be asking for some justification. Edit: think over-engineering.

author: rstofer
author_rank: Super Contributor
timestamp: September 21, 2020, 05:40:39 pm
content: One thing an RTOS provides, as a side benefit, is modularizing the code. Each task stands alone - it takes inputs from queues/semaphonres and delivers outputs to pins or other queues/semaphores. But the entire process is a building block wrapped in a task.There's certainly nothing wrong with avoiding the RTOS in favor of some other scheme, probably a superloop although it does seem wasteful to use a bunch of cycles to test whether some function should be called based, perhaps, on a flag. Considering the prolog and epilog code, we don't want to call a function unless we already know it has work to do.The first thing to get straight is the required response time to external events.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: September 21, 2020, 05:54:22 pm
content: Quote from: Howardlong on September 21, 2020, 05:29:20 pmI disagree. An RTOS doesn't "get in the way" if you use it correctly, nor does it necessarily consume a lot of resources/power/cost or what have you.Sure, if your "RTOS" is something like embedded Linux, then I'd agree that can be a resource hog, but a small RTOS running on something like a Cortex-M MCU can be very small and still get the job done.As an example, my RTOS takes up 8-15K of flash (depending on how it's configured) and about 4K of RAM (excluding task stacks). That may be a lot if you're developing for a PIC MCU with 2K of flash and 256 bytes of RAM, but on the MCU I typically use (2M flash/512K RAM), it's nothing.An RTOS makes sense when your embedded application has multiple pieces that interact in complex ways. Yes, you can implement such a system without an RTOS, but maintaining and extending such a system becomes difficult when any change has the potential to throw off timing and synchronization between the various parts of the system.

author: NivagSwerdna
author_rank: Super Contributor
timestamp: September 21, 2020, 06:12:29 pm
content: This is one of those questions where you get loads of replies since it is such a common pattern. I see loads of wisdom earlier in the thread but I can't resist my 2c worth...Have interrupts for things that need to happen in a timely fashion or that signal completion of a long running task...The obvious interrupts in your case are :Serial receive (so you don't saturate the receive buffer). In the ISR just push bytes onto a ring bufferRegular timer expiry for use in flashing LEDs and/or de-bouncing push buttonsADC Completion (if conversions are slow relative to clock)Everything else can stay in main in a while forever loop.Sounds like a fun project

author: Kleinstein
author_rank: Super Contributor
timestamp: September 21, 2020, 06:41:21 pm
content: How bad the overhead from an RTOS is depend on the tasks to do. If there are short but very frequently called jobs, a RTOS can be a hard no-go slowing things down up to an order of magnitude. Some tasks can be short but run quite often (e.g. a software implemented DDS that takes only a few cycles at a time).An RTOS can help a lot if one has tasks to do that the OS already provides (e.g. buffered UART or similar).There are different ways to program many concurrent jobs to be done with the µC: RTOS, all in ISRs, partially in main loop, separate task scheduling, all in main loop. It depends on the job which system works good and which may not work at all. If in doubt one may have to evaluate different possible solutions and than decide which path is the best match - here may be different criteria for best: lowest power, readable code, resources , estimated development time, potential for failure. Sometimes it help to look at the problem from a different side, so the time spend on a alternative concept is not really lost.In addition to the pure software side modern µCs also have special hardware functions like special periphery, DMAs and event systems that can do quite some jobs (e.g. buffered UART) mainly in hardware, that needed coding by hand on older systems. So it may not be just the SW, but also planing to use HW resources. DMAs can help, but they could also make data access and sometimes the needed run time tricky.

author: Kalvin
author_rank: Super Contributor
timestamp: September 21, 2020, 07:33:27 pm
content: Quote from: Fixpoint on September 21, 2020, 09:45:00 amI would say that it is very well possible to design a very deterministic and maintainable system using a simple, co-operative, time-triggered scheduler:https://en.wikipedia.org/wiki/Time-triggered_architectureAlso, your statements that the co-operative schedulers are somehow particularly unreliable and prone to crashing, is not valid either. Probably you have had a bad experience using a poorly implemented scheduler and poor general system architecture, but do not make a general statement based on your bad experience.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 21, 2020, 08:05:55 pm
content: Quote from: Kalvin on September 21, 2020, 07:33:27 pmThat's not an opinion but follows directly from the definitions. I already explained it in great detail, but ok, I'll say it again: a cooperative scheduler does not control timing and thus there are no timing guarantees whatsoever. It's even worse: not only are there no timing guarantees, there are also no liveness guarantees. That is actually pretty catastrophic in a safety-critical environment. Case closed. Yes, there is nothing more to say.Your link to the WP article is not meaningful here because time-triggered systems and cooperative schedulers are different things, they are not synonyms.QuoteI have never worked with cooperative schedulers, at least not in a professional environment. My only contact with them was with Windows 3.1, and back then I was a kid and neither knew nor cared.

author: voltsandjolts
author_rank: Unknown
timestamp: September 22, 2020, 02:26:59 pm
content: Quote from: Fixpoint on September 21, 2020, 08:05:55 pmI have worked on several commercial products with extensive safety requirements that have used, in effect, what you would describe as co-operative scheduler / co-routines / round-robin.Simple, robust, with a small code base that makes for easier third-party safety assessment.No embedded OS, no RTOS, no preemptive scheduling. Adding any of these does not guarantee timing or 'liveness' either, it does guarantee more headaches for safety assessment.Of course there are more complex systems which require these features but it is a case of 'horses for courses'.

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 22, 2020, 04:38:51 pm
content: Quote from: voltsandjolts on September 22, 2020, 02:26:59 pmOf course you have, why not? By the way, I once wrote working software using BASIC. What does that prove? That BASIC works? Of course it does, but that's beside the point.QuoteA safety assessment may appear "easier" when there are no abstract concepts to understand and analyze, but that's a deceptive simplicity. I am aware that the industry has been doing it this way for decades, but that is actually sad. (And as we now, from time to time we see a spectacular and deadly failure where it definitely could have been avoided if modern design techniques had been applied.)How good is the engineering process really when it uses outdated methods from 50 years ago while in the meantime far better methods have become available? Shouldn't we have a look at the new methods and use them to make our products safer to use? Especially when the product is simple because then it is also simpler to try out the new stuff?Please keep in mind that I am not (only) talking theory, I am talking about things that have been in practical use for quite some time now (also in my own professional work, see above). And so, I can be pretty sure that what I am talking about shouldn't cause any headaches for any safety engineer who is only halfway serious about his job.So, the way this works is basically to design safety into the product, which we may call *systemic* safety engineering in contrast to *case-based* safety engineering.When we have a modern scheduler at our disposal, we can make use of its safety and liveness guarantees. This part of the software has to be tested and analyzed once per platform (until a new version is released, of course). The actual tasks of the application -- be it ESP of a car or whatever -- then can be plugged into this safety architecture using the well-defined interfaces.Then, the magic happens when we define safety and liveness properties for those tasks. Those properties can then be formally analyzed using the well-known methods like model checking, static program analysis etc. So, the whole system gets broken down into *architecturally independent* well-analyzable components. Safety assessment gets *way* easier and the results become better both because it is easier and because of support from the formal side.The interfacing between the components is part of the underlying system architecture and, as I said, needs to be analyzed only once per platform. Using formal methods, it may even be possible to automatically derive a test suite.In total, our products become safer becasue more defects are found, and I think that is what we should want. However, for this to work, we must accept that the world is evolving and that new, better methods become available. Cooperative scheduling, for that matter, does not help this process but impairs it, that's why I don't want it in a safety-criticial system.

author: Howardlong
author_rank: Super Contributor
timestamp: September 22, 2020, 06:38:02 pm
content: Quote from: Fixpoint on September 22, 2020, 04:38:51 pm"Modern design techniques" like the 737-MAX *cough*?

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 22, 2020, 07:02:53 pm
content: Quote from: Howardlong on September 22, 2020, 06:38:02 pmAs far as I know, Boeing used century-old techniques for that, called negligence, deception, and subterfuge.

author: Renate
author_rank: Super Contributor
timestamp: September 22, 2020, 07:11:06 pm
content: Quote from: Fixpoint on September 22, 2020, 07:02:53 pmNot being able to flag an easily identifiable sensor failure while on the tarmac certainly counts as that."Oh, you wanted to know if your AoA sensors were totally screwed? You have to pay extra for that light bulb."

author: Howardlong
author_rank: Super Contributor
timestamp: September 22, 2020, 08:04:44 pm
content: Quote from: Fixpoint on September 22, 2020, 07:02:53 pmI was being deliberately obtuse, because you were discounting everything in favour of your dogmatic approach that only your modern design techniques should ever be used. In the Boeing case, they are using a modern software hack to fiddle a proven very reliable decades old design.My point is around the premise of keeping things simple. Putting layer upon layer of abstraction and code means inevitably you have less and less visibility and understanding of the system as a whole, and how it is likely to behave.Once you're relying only on empirical results, and ignore the details of what's going on under the hood, you increase risk, you don't reduce it. I'd also say that this abstraction encourages people to use software outside of design criteria, because all to often not all use cases are specified.There's a time and a place for many methods of achieving the same goal. Some are decades old. I am very sceptical when anyone states there's only one way to achieve something in all cases, discounting all other options, particularly when it must be good because it's modern. I bet the Adobe Flash and Perl developers thought that at one time too.As I mentioned before, I can't see the point of introducing an RTOS for the OP's relatively simple requirements, that to me seems like over engineering, and unnecessarily adds complexity and cost in terms of development and maintenance effort, and certainly more time to upskill for the OP. If you happen to have a standard that says you must use an RTOS, and you have the skills to develop and maintain it, then go right ahead.

author: dietert1
author_rank: Super Contributor
timestamp: September 22, 2020, 09:39:07 pm
content: Looking into an open source RTOS was meant as an invitation for a beginner to learn certain things and as a path to results. Software engineers need to learn how to use a given library and its API properly. There is no excuse.Regards, Dieter

author: Fixpoint
author_rank: Regular Contributor
timestamp: September 22, 2020, 10:10:40 pm
content: Quote from: Howardlong on September 22, 2020, 08:04:44 pmThe belief that you could have such an "understanding of a system as a whole" is the archetypical engineering hubris. Yes, if you have 1000 LOC, then that may work. But (a) applications don't stay that tiny and (b) in many domains, such tiny applications don't even exist. Systems often grow so large and complex that no person could ever have an understanding of the whole system. Even with a small system, say tens of thousands of LOC, you won't have this understanding. Imagine now we have a big system with millions of LOC.Many components are being contributed by third parties, people join the team, people leave team, etc etc -- no-one knows the "system" and no-one can know. Let's just accept the reality that systems are complex. We cannot talk their complexity away and deceive ourselves by thinking that we "keep it simple". No, we don't. We just add couplings, and couplings make systems *more* complex (you can also model that mathematically, if you are so inclined). Only if we are not familiar with software design techniques, we might find adding couplings simple and intuitive, but that's just because it's the only thing we know.In the case of the scheduling stuff, the couplings are introduced because each and every task has to keep the timing of the whole system in mind in order not to destroy it. That is wrong on so many levels, I don't even know where to begin.QuoteIf I opened the code for a USB driver, I could only understand it if I sat down for a year and digged deep into the USB specification. Of course, I cannot do that because I have a different job to do. So, I have to accept that I do not know the details and *nobody* knows, maybe not even the guys who wrote the driver because also their team consists of more than one person and the USB spec is huge.And empirical results are a corner stone of software development, it's called testing.QuoteWell, the only thing I can do right now is tell you that I am talking about things that have been the basis of the IT and software industry since the 1990s (of course really only the fundamentals, many other things have changed since then). You don't increase risk but instead manage complexity. Managing complexity means to *reduce* risk. That doesn't mean that software always works. But if you did it differently, it would never work because it's too big to "just work".QuoteI am not sure what you are referring to.QuoteWhat I say leaves much room for interpretation, I didn't give specifics. I'm leaving everything open. The only thing I don't want to see -- yes, I don't -- is code that looks as if the last 50 years of reason hadn't happened. And that is all too often the case when I look at embedded code. It's just a big hack with vicious couplings everywhere, no design, no architecture, sometimes they don't even care for formatting and readability. That cannot be good for anyone, especially not the users.If you want something official, have a look into ISO 26262, for instance. They want you to use the methods that I am talking about. And not because they are "modern" but because they are important -- or at least could be if they were being used.

author: Howardlong
author_rank: Super Contributor
timestamp: September 22, 2020, 10:20:55 pm
content: Quote from: dietert1 on September 22, 2020, 09:39:07 pmThat;s right, but even when it's open source, how often would a programmer dig into the guts to see how it works? Typically libraries and APIs don't give anywhere near enough details to know how they'll behave in different instances, or how they scale for example.I'm thinking typically of things like dynamic memory usage for example, or, in the case of web based technologies, dependencies on external resources that might not always be there, or indeed work.That's not to say we shouldn't use third party APIs or libraries, but unquestioningly treating them as just a building block without understanding their behaviour in a given use case is a significant risk. It's rarely a free lunch, that takes time to analyse. You need to weigh that up against rolling your own.One last point on an RTOS, almost certainly it won't mean you don't need to understand how interrupts work, you'll still need to have an understanding of that, and write those ISRs. The difference is you'll need to write them in the way that's compatible with the RTOS: more work!

author: Howardlong
author_rank: Super Contributor
timestamp: September 22, 2020, 10:46:55 pm
content: Quote from: Fixpoint on September 22, 2020, 10:10:40 pmIn the case of the OP's project, and in many others, it is definitely the case that the entire system can be understood, and there of plenty of other designs just like that one.That's not always the case, but that should be the aim, to understand the system as a whole.Regarding the USB driver example, that's exactly what I've had to do to make it perform, such is the state of certain vendors' offerings. But you're right, I shouldn't have to do that, but it happens.I think you're looking at this in an overly dogmatic way, with only one tool in your toolbox. I, on the other hand, am saying it's horses for courses, one should aim to keep things simple, and not over complicate things, and in the case of the OP's task, I don't believe an RTOS is relevant.I wasn't discounting empirical measurements, if you think that then you're projecting. I was saying that the better understanding you have of a system, the better the solution you'll provide: if you're relying on empirical results only, you're already behind the curve of someone who has a deeper understanding.I didn't bring up the scheduler thing. IMHO, there's no need for a scheduler in the OP's system at all, it's quite a simple design, it's unnecessary in my opinion.If you want to boast about vintage, I was brought up on paper tape and germanium transistors in the 70s if that helps!You see I agree that systems should be well architected, but to do that you need to have a good understanding of what you're trying to achieve. If you don't then of course you'll end up with the systems you refer to. There is a reality check here, and that's that there's such a thing as a sales team, and if they're going to get a big sale you can be sure you'll have to hack something on, and that technical debt will rarely go away. The majority of projects I work on for customers aren't the Utopian green field designs you're yearning for.You're welcome to disagree, I just think your one-size-fits all approach is over prescriptive. By being so dismissive of concepts like keeping it simple, it's almost as if you're trying to make life hard for yourself by adding complexity.

author: emece67
author_rank: Frequent Contributor
timestamp: September 22, 2020, 10:58:53 pm
content: .

author: NorthGuy
author_rank: Super Contributor
timestamp: September 23, 2020, 12:52:04 am
content: Quote from: Fixpoint on September 22, 2020, 10:10:40 pmThat is true. Therefore it is necessary to do something about the complexity. Simplicity has always been a hallmark of good engineering. One old programmer said:"Fools ignore complexity, professionals deal with complexity, geniuses remove complexity."Yet, the modern computer science believes that the way to manage complexity is to add more complexity (Just as economic science believes that the solution to the debt problem is more debt, or as a drug addict believes that the solution to his problem is more drugs). This is obviously not true. The best way to deal with complexity, is to reduce it as much as it can be done (but no more than that).The modern computer science took a wrong turn. Instead of researching how to improve quality of the software being build, the computer science concentrates on the programming process itself and try to create better rules and mechanisms. Hence ever-increasing complexity of the software. Software is getting bloated beyond comprehension, buggy, and unmanageable. By now I lost all hope and I'm convinced that it isn't going to change on my life time.So, what to do about that? Go with the flow and drag everything into your project whether you need it or not (such as blinking LEDs with RTOS)? Or, may be a newbie who faces his first project should try to avoid extra complexity. Then, perhaps, years later, when he meets real complexity, he will have some skills to deal with it, or, perhaps, even reduce it.

author: Howardlong
author_rank: Super Contributor
timestamp: September 23, 2020, 09:09:45 am
content: Quote from: emece67 on September 22, 2020, 10:58:53 pmI'd agree with that in general, especially regarding vendor hardware drivers and HAL libraries. They tend to try to be all things to all men generic approaches... they're inefficient, and can have a very short shelf life when they no longer support newer silicon. The worst offenders IME are code generators. My preference is the bare metal approach when targeting trivial hardware like I2C, SPI etc. For USB and networking, you pretty much have to use vendor libraries, but they frequently need tweaking. Code generators also have very short shelf lives, and don't lend themselves well to tweaking.My only thought regarding using an RTOS "in case" is that you can't design for unknown requirements. It's not great practice IMHO to over engineer, it costs money and time unnecessarily. If it's for education, sure, fill your boots, but I'd add that this won't preclude the OP from having to implement interrupts. Furthermore, one problem of over engineering like this is that you can make a rod for your own back: IME it definitely encourages scope creep.

author: JojoS
author_rank: Contributor
timestamp: September 23, 2020, 09:32:10 am
content: How about using MBed-os? Its a RTOS without RTMeans, that you can configure if you want RTOS stuff included or to use it in the bare-metal variant.With RTOS you have the preemptive scheduler, but at the cost of more RAM/Flash required. So for simple stuff as the intended project, an event based approch is supported by an EventQueue. Of course with the disavantages of a cooperative system, but for managing user inputs and system events it will work also.Mbed works also a lot with callbacks, and you can throw an event callback into the queue. This avoids to have large dispatcher switch cases for decoding the events.USBDevices are supported (F1 has been added recently) also, e.g. you can you a USBSerial class and that will simplify a lot. Another advantage of Mbed-os is that you have an API that is available for a lot of targets, so when the F103 becomes to small, you can simply switch to e.g. a F401/F411. ST has attached importance on pin compatibilty, so it maybe that you can use the other processor without changing the layout (have not checked the named types).

author: emece67
author_rank: Frequent Contributor
timestamp: September 23, 2020, 12:01:41 pm
content: .

author: Cerebus
author_rank: Super Contributor
timestamp: September 23, 2020, 01:38:23 pm
content: Quote from: emece67 on September 23, 2020, 12:01:41 pmJust for the sake of comparison: ChibiOS on an STM32G474, full RTOS, variable frequency blinky in a thread, frequency set from main thread using the timer library for a quadrature encoder, SPI, an 8 digit seven segment driver atop that, serial port driver, streams library, printf library, quite a few 'debug' printfs - 13kb. (Just kicking the tyres on ChibiOS to see if it's worth adopting/persevering with.)

author: dietert1
author_rank: Super Contributor
timestamp: September 23, 2020, 02:10:43 pm
content: The STM "UM1722 - User manual - Developing Applications on STM32Cube with RTOS" is a good introduction.On the first page (P. 5) they explain that a typical FreeRTOS kernel will have "4K to 9K bytes".Regards, Dieter

author: voltsandjolts
author_rank: Unknown
timestamp: September 24, 2020, 08:18:28 am
content: Quote from: Fixpoint on September 22, 2020, 04:38:51 pmWhat does that prove, you ask.It demonstrates that an industrial explosives detonation system that includes microcontrollers using co-operative scheduling techniques passes a rigorous third-party safety assessment by a well-known defense contractor subsiduary which specialises in missile electronics.

author: Simon
author_rank: Unknown
timestamp: September 25, 2020, 10:44:34 am
content: setup a timer with an interrupt, every time it fires you increment a counter variable. You main loop will check the counter variable and when a certain number is reached you run a function with your periodic tasks. Doing it this way means that you can have several batches of tasks at different intervals. This means that timing can be accurately achieved for things like changing LED's and reading buttons without time wasting delays.Things like ADC's can run on interrupts. You can write code that will scan through the ADC inputs repeatedly putting the values into variables. Using arrays helps a lot as you can do things like increment a variable that selects the channel and that variable is the index number of your array so the exact same code can run on all channel round in a loop. Interrupts will also help with oversampling as rather than wait around for the ADC to do several conversions it can use interrupts to call on the CPU when needed but otherwise leave the CPU to run your code. Same sort of thing with comms stuff, set up variables that act as buffers so you put data into them and leave the transmit stuff to work with interrupts or you check a variable flag that tells you a new message has arrived.

author: krish2487
author_rank: Frequent Contributor
timestamp: September 25, 2020, 01:56:18 pm
content: https://en.wikipedia.org/wiki/Law_of_the_instrumentTl;Dr - When you have a hammer, everything looks like a nail.Granted, parts of the quoted message will not be relevant, but in essence that is what your argument is.One magic bullet to solve every issue.As engineers, we are meant to keep our mind open about having different tools at our disposal for solving different problems.Co-operative scheduler isnt certainly perfect, neither is a preemptive RTOS nor will some futuristic solution based on learnings of today and tomorrow.They are all different approaches to solve a problem.Quote from: Fixpoint on September 22, 2020, 04:38:51 pmWhat you are advocating is here deferring responsibility of the said code. Shifting the onus of understanding a system to depending on the RTOS / scheduler's vendor to do a good job. What if they screw up? You are asking to assume that the eventual inherent complexity of system will inevitable be far to greater than any one individual can understand, so throw the biggest, most complex, totally not required RTOS / scheduler / framework at it - today. Sorry, thats is just poor optimization. In fact premature optimization. Even in cases where the system is far too complex right at inception, there will always be either1. people2. documentationto handle the said complexity.For someone who looks like a huge proponent of determinism, your statements about systems and processess and engineering are anything but.Massive amounts of money has been sunk over decades by various companies to do just this, document stuff.Are companies implementing an appropriate system to document stuff - maybe.But it is not because tools dont exist, it is due to the people involved.Quote

author: Cerebus
author_rank: Super Contributor
timestamp: September 25, 2020, 02:51:15 pm
content: Quote from: Simon on September 25, 2020, 10:44:34 amSorry Simon, I'm going to pick on you as an example of "The naïvety of the self-taught programmer".Consider the situation where you do as you say above. Your C code for the main loop might look like this:Code:volatile int counter;int compare_value;...while (counter != compare_value) // Wait for the interrupt routine to set 'counter' to our trigger value{// a little bit of housekeeping code here, not shown}// task code follows on hereThe compiled machine level pseudocode for the main loop would look a bit like this:Code:again:MOV A, counter// danger point starts hereMOV B, compare_valueCMP A, B // danger point ends with the finished execution of this instructionJE do_task// a little bit of housekeeping code here, not shownJMP againdo_task:// task code follows on hereIf the interrupt happens in the region covered by the two 'danger point' comments, the value of 'counter' assetby the interrupt routine, and the value of 'counter' asusedby the main loop are different - the main loop doesn't 'see' the updated counter because it has already loaded the now out-of-date value into a register. So your main loop will not execute 'do_task' for at least one more main loop wait cycle, or will miss the trigger entirely if there's another counter incrementing interrupt happens before the loop has time to run around again (say because there's some housekeeping code at the top of the loop as here, or the increment interrupts are very frequent). So you end up with timing jitter, or worse, you miss your event entirely.This kind of error is very common in naïvely written code that uses interrupts. There are strategies for avoiding this class of errors thatoughtto be known to every computer scientist on the planet, but they're not known by the vast majority of people who've taught themselves to program. Names for some of these strategies include semaphores, 'wait-free locks', mutexes and so on.This isone exampleof a whole class of synchronization problems. Other classes go by names like 'deadlock', 'priority inversion' and so on.There's obviously not space here to fire up a tutorial, either on how to dodge this problem, or on the wider class of synchronization problems that can be caused by interrupt code or indeed any code that shares values with another piece of code where the execution order of those pieces of code is either unpredictable or can happen in parallel. They can fill entire textbooks.If someone wants to write this kind of code, and to have it operate reliably and correctly, theyhaveto learn about these hazards and learn about the techniques for avoiding them. These problems can be very subtle, and you're unlikely to spot them, or invent solutions on your own -they occupied a significant part of early computer science research and are still anon-goingarea of research. That implies either going to the right classes, or picking up the right computer science textbooks and reading them. You'll usually find this stuff in computer science textbooks designed to teach the fundamentals of Operating Systems, as solving this class of problems is an intrinsic part of building an Operating System. You won't find it in typical books on embedded programming.

author: Simon
author_rank: Unknown
timestamp: September 25, 2020, 04:00:39 pm
content: I use this method where missing a loop would not even matter. I also tend to do a modulus operation and if the remainder is 0 go with the task list so that if i miss one opportunity I take the next one as it works on any multiple. Things like lighting up LED's, measuring how long a button has been pressed for for de-bouncing purposes, things like that, not timing critical stuff and of course a little thought needs to go into how long the task list could take and to ensure enough overhead.

author: Simon
author_rank: Unknown
timestamp: September 25, 2020, 04:05:44 pm
content: Your interpretation is also not quite what I meant. The counter variable is incremented on each interrupt. All the loop does is test the counter value or it's modulus and as soon as one of the tests passes the appropriate sub routine is called.There is the risk that if the routine takes too long then slots will be missed but like I say this is not meant for mission critical timing. Other interrupt driven tasks like ADC reads can run around these task lists. Again this is not a timing controlled system, you have no idea when the ADC value was produced you just know that it is the latest available.

author: Simon
author_rank: Unknown
timestamp: September 25, 2020, 04:11:52 pm
content: Looking at the OP requirements all the interrupt timing he really needs is around comms. ie interrupt says there is a message byte, empty register into correct array position and await new byte, in the mean time keep taking whatever external measurements are required and put the latest values into the data packet when a message is sent back.

author: thinkfat
author_rank: Unknown
timestamp: September 25, 2020, 04:27:47 pm
content: When using interrupts, I typically have one interrupt that just drives a counter variable and derived from that counter I will iterate over a timer list with callback functions from my mainloop context. That serves my housekeeping needs and all tasks that just need to be run at a defined point of time in the future.Peripheral interrupts are structured such that only the minimum necessary actions are taken. I treat interrupt handlers as atomic so they define the minimum response time of the system. Therefore, the time spent in atomic context should be as short as possible. For example, a value will be read from an ADC or a UART fifo and stored in a buffer, a flag will be set and a timer added so that a mainloop function can do the processing of the values.I will also keep the amount of data structures touched from interrupt context small. That will limit the amount of headache considerably.

author: Cerebus
author_rank: Super Contributor
timestamp: September 25, 2020, 04:46:44 pm
content: Quote from: Simon on September 25, 2020, 04:11:52 pmI think you're missing the subtleties here, which is kind of the point of my long post. There's every opportunity with this kind of producer/consumer process to lose notifications (and therefore characters) unless you properly synchronise access to the array, the reader index for that array and the writer index for that array.Exactly the same kind of race condition is possible here - consumer reads the read/write index into a register, interrupt (producer) updates it in memory, consumer uses the stale register value after the interrupt returns. You cannot avoid these errors without using interlocks such as read-modify-write instructions or higher level structures such as semaphores or mutexes based on them.

author: Simon
author_rank: Unknown
timestamp: September 25, 2020, 05:09:11 pm
content: Of course you use interlocks. You need to know that the multi-byte message is complete. You will have flags for the fact that you have already looked at the message in the array and that a new reception has started. that remains in place until the entire new message is received at which point you take the data you want from the array and set the flag as message read.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 25, 2020, 05:15:55 pm
content: Quote from: Cerebus on September 25, 2020, 02:51:15 pmI cannot agree with your assesment. If the interrupt happens right after the line marked "danger point ends with the finished execution of this instruction" (or in any other place for that matter), these bad things will happen anyway - you get jitter because you may wait for almost a full cycle to pass, and you're doomed if the counter increments twice during the loop. There's no defined "danger" zone and the pseudo-assembler code is irrelevant to the problem.Hopefully, if you put things in the main loop, you understand that these things must be able to tolerate jitter. Also, you probably will combat double ticks with:Code:while ((counter - compare_value) < 0) // Wait for the interrupt routine to set 'counter' to our trigger valueinstead ofCode:while (counter != compare_value) // Wait for the interrupt routine to set 'counter' to our trigger valueThere are other things to look at, such as making the counter "volatile", making sure the counter variable in not too short, or taking precautions if it the counter variable is too long and requires non-atomic load, or mis-understanding differences between signed and unsigned comparisons. The possibility of such mistakes doesn't mean that Simon's approach is invalid or too naive.

author: Cerebus
author_rank: Super Contributor
timestamp: September 25, 2020, 05:50:09 pm
content: Quote from: NorthGuy on September 25, 2020, 05:15:55 pmSomebody didn't look at the original code very carefully, to have missed the 'volatile' there. But 'volatile' won't magically protect you from interrupts happening between instructions in mainline code. 'volatile' simply says to the compiler "Beware, this memory location may change at times that you, the compiler, don't expect because it is also accessed by code that is outside your data flow analysis.".If you want a step-by-step, cycle-by-cycle analysis of the vulnerabilities here then you're not going to get it. This was a simple throwawayexampleof where a race condition between mainline code and interrupt code can catch you out, not a worked example of how to do it right, but 'look it can go wrong here'.If you think the pseudo-assembler is irrelevant, you don't understand the problem - specifically that a register load that is made before an interrupt, which changes the loaded value and then used, stale, after the interrupt returns. By the way, the simple fix for this specific, simplistic, example is a disable_interrupts()/enable_interrupts() pair around the critical section.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 25, 2020, 06:51:40 pm
content: Quote from: Cerebus on September 25, 2020, 05:50:09 pmSee for yourself:Assume the counter changes from "compare_value-1" to "compare_value" during the interrupt:Code:--- if interrupt happens here we go to do_task immediatelyMOV A, counter--- if interrupt happens here we go for another loop// danger point starts hereMOV B, compare_value--- if interrupt happens here we go for another loopCMP A, B // danger point ends with the finished execution of this instruction--- if interrupt happens here we go for another loopJE do_task--- if interrupt happens here we go for another loop<other instruction here>--- if interrupt happens here we go for another loopAs you can see, there's a point where the behaviour changes. If interrupt happens earlier (meaning that the time has come to do the task) the code does the task. If interrupt happens later (meaning this is not yet time), the code goes for another loop. There's no harm done either way. There's no danger zone, and absolutely nothing changes when the code leaves the imaginary danger zone.If you add interrupt enable/disable to remove the imaginary danger zone, the behaviour doesn't change a bit:Code:--- if interrupt happens here we go to do_task immediatelyDI--- interrupt cannot happen hereMOV A, counter--- interrupt cannot happen hereMOV B, compare_value--- interrupt cannot happen hereCMP A, B // danger point ends with the finished execution of this instruction--- interrupt cannot happen hereEI--- the interrupt will happen here if the interrupt condition arose when it was disabled--- if interrupt happens here we go for another loopJE do_task--- if interrupt happens here we go for another loop<other instruction here>--- if interrupt happens here we go for another loop

author: emece67
author_rank: Frequent Contributor
timestamp: September 25, 2020, 07:15:59 pm
content: .

author: Cerebus
author_rank: Super Contributor
timestamp: September 25, 2020, 07:48:55 pm
content: Quote from: NorthGuy on September 25, 2020, 06:51:40 pmYou've merely proved what I asserted in the original post:Quote from: Cerebus on September 25, 2020, 02:51:15 pmI don't really see how that helps, but thanks for proving what I asserted.Yes, the hazard here, if the task and housekeeping code is simple, is in the mainline code executing late or exhibiting jitter, or in the extreme case, if the housekeeping or task is lengthy, missing an interrupt. This all may be trivial or it may be catastrophic depending on the application, on what's in the task or the housekeeping code. It is a simplecontrivedexample, reduced to the bare essentials. The choice of an equals comparison was deliberate to make it as simple an example as possible and make it clear how a single missed interrupt could cause the code to fail - it isdeliberatelyfragile for epistemological purposes. The whole thing exists merely to demonstrate that there is a hazard there that while a shared value is held in a register that it can be outdated before it is used.The point is to illustrate that critical sections exist and they are not immediately obvious or intuitive, nothing more, nothing less.The overall point stands, code with parallel execution/interrupts is tricky, with non-obvious hazards, and a sensible person will go and learn how to use the standard tools for handling this.

author: NorthGuy
author_rank: Super Contributor
timestamp: September 25, 2020, 08:03:05 pm
content: Quote from: Cerebus on September 25, 2020, 07:48:55 pmNo, you need to give me more credit than that. I have proved that isolating your supposedly critical section with disable/enable interrupts doesn't change the behaviour in a slightest. Which means that this wasn't a critical section at all, and there was no "danger point" in the pseudo-assembler code you have posted.

author: Cerebus
author_rank: Super Contributor
timestamp: September 25, 2020, 08:20:03 pm
content: Quote from: NorthGuy on September 25, 2020, 08:03:05 pmYou haven't because you haven't been listening, but I'm quite happy to leave you with the belief that you did. Hopefully those I was trying to help have, or will, see the point.

author: emece67
author_rank: Frequent Contributor
timestamp: September 26, 2020, 09:53:15 am
content: .

author: thinkfat
author_rank: Unknown
timestamp: September 26, 2020, 10:40:07 am
content: You're all making this much more of a problem than it actually is. For such simple requirements with only one mainloop and no threads, all you need is one counter variable, one timer interrupt and a simple timer list sorted by earliest expiration time. Keep in mind that the counter will only monotonically increase and the only requirement for a lock free implementation is that read and write access to the counter is atomic. Any 32bit CPU can do that.The modulo trick is superfluous. Subtracting two unsigned values and comparing 'smaller or equal to zero' is enough to catch a late timer.

author: Kalvin
author_rank: Super Contributor
timestamp: September 26, 2020, 11:31:19 am
content: For advanced users:If the system requirements allow, it is also possible to place all the application logic into the timer interrupt handler (foreground process). Thus, the system will run at the timer interrupt rate. The application logic could be then implemented as s simple, time-triggered, co-operative state machine. The main() function would just initialize the system, start the timer and enter infinite loop doing nothing (putting the CPU into a sleep, waiting for the next timer interrupt). The timer interrupt will wake the CPU and the application will be run one step at a time at each timer interrupt. After the execution of the interrupt handler has completed, the CPU will enter back into the sleep. Quite simple, deterministic approach.Of course, it is possible to perform some non-critical background processing in the main() function, too. In this case, it is necessary to implement some guards (like enable/disable interrupts) when transferring data between the foreground process and the background process. Enabling/disabling the interrupts may introduce some jitter to the system response, so that need to be analyzer carefully if some jitter in the system response cannot be tolerated.It may not be possible to use standard printf() for the debugging purposes in the foreground process AND the background process use the printf(), though. However, if the background process is not used, or the background process does not use printf(), the application code running in the interrupt handler should be able to use printf(). Please check your implementation of the printf().Here is a good reference for the time-triggered, co-operative systems: ‘Patterns for Time-Triggered Embedded Systems’ by Michael J. Ponthttps://www.safetty.net/publications/pttesThe book is freely available. Although the book uses the 8051 as an example, the principles are equally applicable to all microcontrollers from Attiny, Arduino to SMT32.An addendum to this book is also freely available with an updated task scheduler using LPC1769 (ARM Cortex-M3 based MCU):https://www.researchgate.net/publication/311614300_The_Engineering_of_Reliable_Embedded_SystemsEdit: Modified the wording regarding the addendum and Cortex M3, as suggested by Simon and emece67 below.Edit2: Fixed a typo.

author: uer166
author_rank: Super Contributor
timestamp: September 29, 2020, 08:42:11 pm
content: Quote from: Kalvin on September 26, 2020, 11:31:19 amAnd the hybrid scheduler described in that book can get around some of the limitations of the cooperative one, with some tradeoff regarding safety guarantees.

author: emece67
author_rank: Frequent Contributor
timestamp: September 29, 2020, 08:50:06 pm
content: .

author: Simon
author_rank: Unknown
timestamp: September 30, 2020, 06:23:51 am
content: Quote from: emece67 on September 29, 2020, 08:50:06 pmI'd call it an addendum. The "new" book is a mere 400 pages, the old one 1'000. Maybe he just rewrote the examples.

author: Kalvin
author_rank: Super Contributor
timestamp: September 30, 2020, 12:48:26 pm
content: Thanks Simon and emece67, I edited & updated my wording in my original post.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 02, 2020, 06:52:51 am
content: Kalvin's strategy is very good, and I wouldn't call it "advanced"; quite the opposite, it's easy to do.My take is, ignore the general advice of "keep ISRs short and make them set flags; process flags in main()". This may work in some cases, but is generally undeterministic, hard to analyze, and forces much more shared data between tasks than would be strictly necessary.For example, if you follow the typical advice and program an UART interrupt, set a flag in ISR, and poll for that flag in the main loop, you could have just polled the peripheral interrupt flag to begin with, without writing or enabling the ISR in the first place. Or, you could use DMA to stream the data to memory, directly, and process it at a convenient time. But if youneedto do it when the event happens, why not do it in the ISR, completely? Only if you have two separable actions, a timing-critical one, and a irrelevant-timing heavy one,thenit makes sense to set flags and continue processing in main(). But this isn't any kind ofdefaultcase. And even then, it would be more deterministic and easier to follow to demote the interrupt priority by creating a lower-priority software interrupt that continues processing.Interrupt priorities and pre-empting interrupts make it easy to run time- or event-triggered tasks right whenever needed.It's a good idea to run all code that needs any sort of realtime deterministic behavior in the interrupts. And contrary to the usual poor advice, there is no reason to make the interrupt handlers extremely short. Say you get a new ADC datapoint, and need to do some math upon it, to then decide whether to turn an IO on or off. Do all that in the ISR. Maybe your ISR takes worst case 1ms but you need to react to a comparator event in 10µs? Write a higher-priority ISR for the comparator event!Maybe you have a state machine, run in ISR, triggered by SPI transfers from a sensor, and get a full data packet after the 17th state? At that state, generate a lower-priority software interrupt which takes over processing the full dataset. Now your longer processing function is out of the way, but you still have better determinism and safety against code changes compared to if you just set a flag and looked at it at some random place in your already long main loop.If you go this way, the main() ends up doing the initialization and then either remains in empty while(1) loop, or does some procedural logic where timing and jitter is completely irrelevant, as long as it gets enough CPU time. Which is quite easy to calculate from ISR repetition rates and worst-case processing times.

author: mac.6
author_rank: Regular Contributor
timestamp: October 02, 2020, 08:58:27 am
content: Quote from: Cerebus on September 23, 2020, 01:38:23 pmChibiOS is nice but tend to growth into a full framework suite and is handicapped by it's dual GPL/proprietary licensing scheme (worst thing to do imo).

author: NorthGuy
author_rank: Super Contributor
timestamp: October 02, 2020, 01:56:23 pm
content: Quote from: Siwastaja on October 02, 2020, 06:52:51 amMoreover, you can piggy-back even more processing to that ISR. Typically, ADC readings come at regular intervals, so after you process your ADC reading and do everything what you need to do to react on it, you're still inside ISR. At this point you can be certain that you have some time until the next ADC reading comes. So, while still within ISR, you can do some things which should be done regularly, such as advance your "time" counter, or do some other housekeeping. You only need to be careful not to take too much time. You need to be done by the time the next interrupt happens, and you need to leave some time to let the main code run.The things piggy-backed to a regular interrupt will have less jitter than they would have if you placed them in a lower-priority ISR or in main. If you don't have interrupt priorities, piggy-backing improves timing considerably.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 02, 2020, 02:14:59 pm
content: Yes, an ADC would be easy to configure to either free-run with pre-determined sampling and conversion times, or conversion triggered from a timer peripheral, resulting in stable and predictable timebase interrupt (give or take a few clock cycles of jitter if internal signals pass clock domains). Two flies with one stone.You can further divide the ADC interrupt rate down by incrementing a counter and only acting everyninterrupts. Process data, and if(++count > 123) { do_more(); count = 0;} Even fancier, if you have five different housekeeping tasks to do with regular intervals and doing them all at once would take too long before the next interrupt, just use modulo-5 of the count to decide which housekeeping function to call.A practical example would be an ADC converting at 10ksmps, and the interrupt handler having ten different "slots" for 1kHz timebase stuff, each of which must be finished in significantly less than 0.1ms.It's very wise to add some kind of rudimentary monitoring; for example reset a counter at the start of ISR, read at the end of ISR, store maximum value. Assert and fail with error message if it ever exceeds say 50% of the available time.You can obviously bring a full-fledged RTOS to do the same but if you are constrained by processing power, an RTOS won't magically make your CPU faster.

author: NorthGuy
author_rank: Super Contributor
timestamp: October 02, 2020, 04:20:15 pm
content: Quote from: Siwastaja on October 02, 2020, 02:14:59 pmUSB 2.0 works that way. They have frames (1 ms each for Full Speed). Each frame starts from the Start of Frame (SOF) packet, which you can regard as the beginning of the interrupt.The devices may create so called "interrupt end-points". For each interrupt end-point the device requests that it must be transacted every n-th frame. The controller packs these requests and schedules the interrupt end-points to be transacted according to the requests of their respective devices.Once the interrupt end-points are processed, the controller transacts all other end-points (such as bulk end-points), and does so until the next SOF. That's the similar to how the "main" loop is executed in MCUs - the timing is vague. Some of the bulk end-points may not get a chance to get transacted, so they get postponed until the next frame. Or vice versa, if there's enough time in the current frame, some end-points may be transacted many times in a row.Hence, the strange names - "interrupt" and "bulk" end-points, which is puzzling for a person who first starts working with USB.

author: voltsandjolts
author_rank: Unknown
timestamp: October 02, 2020, 05:18:53 pm
content: Apologies in advance if this is slightly off topic, but there was some discussion earlier on RTOS.This is quite an interesting read about space qualified VxWorks, RTEMS and Sylix.https://arstechnica.com/features/2020/10/the-space-operating-systems-booting-up-where-no-one-has-gone-before/

author: Howardlong
author_rank: Super Contributor
timestamp: October 02, 2020, 06:58:06 pm
content: Quote from: voltsandjolts on October 02, 2020, 05:18:53 pmUnlike many other areas of engineering, in aerospace, there's often bias towards systems with proven heritage as a means to reduce risk, and even when something new is placed in a payload, it's often complemented with similar functionality bearing heritage. This also helps mitigate against common mode failure.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 03, 2020, 06:24:48 am
content: Aerospace has massive budgeting, long timeframes, and large teams consisting of bright minds. They can deal with complexity.For the rest of us, simplicity should be #1 consideration.

author: thinkfat
author_rank: Unknown
timestamp: October 03, 2020, 07:01:27 am
content: Quote from: Siwastaja on October 03, 2020, 06:24:48 amAerospace is also extremely certification-driven. Believe me, they will haggle for every if() in the code because for traceability it will have to have a formal requirement, a justification and a test case.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 03, 2020, 07:06:26 am
content: Yes, and if everyifin the code has to be certified, the only solution to keep the development going is to bring on 1000000 lines of code that has passed the paperwork once, maybe decades ago, so that it doesnotneed to be looked at again.This results in increased complexity and compromised safety, but the process is happy.Put in enough resources and such disgrace can be made safe enough.Try to replicate such process in a different setting, thinking it's "robust", and it will be a disaster.

author: thinkfat
author_rank: Unknown
timestamp: October 03, 2020, 07:57:46 am
content: The 737MAX disaster is a consequence of this. You can call it greed, but once you understand the cost involved to get an aircraft certified, you kind of understand that Boeing desperately wanted the new aircraft to appear on paper like the model it evolved from.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: October 03, 2020, 11:44:03 am
content: Using an RTOS is easy, I don't understand the aversion to them seen here.If you're trying to cut bread, use a bread knife. If you have multiple independent tasks or multiple interrupts then your RTOS is your bread knife.You can configure an RTOS to reduce it's flash consumption down to kilobytes. When you start doing global interrupt disables then you're basically doing what an RTOS does but you're potentially disabling interrupts for far too long because you actually need a mutex and not a nuke.

author: Simon
author_rank: Unknown
timestamp: October 03, 2020, 11:44:42 am
content: Quote from: thinkfat on October 03, 2020, 07:57:46 amThat was not even about the code. I would have thought that the code that was new had to be signed off. What they played games with was getting the plane to "look" like the existing version by implementing software that they did not tell the pilots about. The cost saving here was to the airlines who would then place orders because they did not have to retrain the pilots.I think how this is handled is also about the capabilities of the chip and it's speed. So I swap my 8 bit AVR running at 16MHz for a 32 bit ARM that runs at 48MHz. So to start with it is 3 times the raw speed, secondly it is 32 bit so handles all of those 16 bit variables and operations much, much faster. As I tried to explain it to my MD, we could take an ARM and run it at a fraction of the speed we now run AVR and achieve the same performance at a lower frequency CPU running on a lower voltage and passing EMC more easily.If you have a slow chip you will want short interrupt routines, even servicing the ADC and PWM means dealing in 16 bit operations on an 8 bit architecture. ARM on the other hand already makes interrupts more long winded because it can with the performance. You don't get an event interrupt on ARM, you get a peripheral interrupt. You then have to check which flag is set. Sounds simple? I wrote some interrupt driven SPI code and fell into several holes before it worked as I worked out how to on the one hand call any of the required bits of code for the various flags whilst ensuring I called the right ones and did not end up with faulty code. The resulting interrupt routine was far from short and I had to think very hard about which is the most used bit of code so that I put flag checks and functions in such an order to make execution as fast as possible most of the time. But you can do this on a chip that is 3 times faster and executes more efficiently.I think there are two sides to this argument. I sense that the OP just wanted a basic system that had timing for human inputs and outputs. Even with a simple system that runs say every 10ms to sense buttons, turn on LED's or update displays that is fast and even if you missed a cycle a 20ms update to everything is not a problem.What a lot of people seem to be talking about is mission critical safety applications that of course are entirely different and where you will pay per line of compiled assembly or machine code to have your code audited and as soon as you recompile have to do the same again.

author: Howardlong
author_rank: Super Contributor
timestamp: October 03, 2020, 06:22:08 pm
content: Quote from: JOEBOBSICLE on October 03, 2020, 11:44:03 amIt's about using the right tool for the job. What's the point of delivering a square peg when you have a round hole?Not all targets have kilobytes of flash.Some applications are very sensitive about power consumption.Many applications I deal with have very tight latency and timing requirements, often unsuitable for the generic nature of an RTOS.Not all applications demand or need an RTOS.Interrupts and RTOSes aren't the same thing, I'm not sure what that comment was about: if you're not aware of how your system is behaving at that level, RTOS or not, then you have a bigger problem than whether to use an RTOS or not. Using an RTOS doesn't absolve you of needing to write interrupt handlers.It's horses for courses, and for most of my applications an RTOS is simply unnecessary. If an application includes a GUI, network and/or USB, sure, I can see justification for an RTOS. Most of my projects don't, they're typically functionally simpler and much more deeply embedded. That's not to say others' projects don't typically warrant an RTOS. I just don't see why the OP's project warrants an RTOS, except as an academic exercise.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: October 03, 2020, 07:19:56 pm
content: If your project starts having multiple interrupts and you don't have an RTOS then you're entering the "assuming variables are atomic" and having no prioritization in place. I have seen this code all over the place as an embedded contractor. Quite frankly a lot of people don't understand thread safety.If you have tight timing deadlines then you can move interrupts to be higher priority than your normal RTOS level interrupts.I don't understand why you think using an RTOS would add problems. You can keep doing what you're doing right now but have the convenience of having low priority background tasks too. Having tight timing deadlines is actually quite frankly a reason to use an RTOS.If whatever you're doing can't afford the cost of a context switch ( last time I measured this it was around 5 microseconds on a 100mhz cortex M4) then I worry that you're doing something in software that should be done in hardware.The OP should use an RTOS, it really does make sense in 2020.

author: Howardlong
author_rank: Super Contributor
timestamp: October 03, 2020, 07:55:17 pm
content: Quote from: JOEBOBSICLE on October 03, 2020, 07:19:56 pmOh dear :-(5us is pretty pathetic TBH.But please go right ahead and put RTOSes on your blinkies ;-)

author: Simon
author_rank: Unknown
timestamp: October 03, 2020, 09:12:01 pm
content: The ultimate decisions around interrupt priority and latency is a feature of the chip and the ability to prioritize one interrupt over another is a feature of the chip not the RTOS. If you chip does not allow you to prioritize interrupts then you have to work closely with the architecture.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 04, 2020, 06:28:39 am
content: JOEBOBSICLE seems to be forcing a square peg into a round hole (while thinking it's theotherswho are doing that).5us context switch is totally unacceptable in many applications. An RTOS cannot replace a classical interrupt-based system. It has completely different purpose. RTOSes are for higher-level control systems. Simple bare metal interrupt-based systems are used to control electronics. This usually isn't about having several parallel computation tasks and guaranteeing they get a certain CPU time %. If you are doing that, time-multiplexing calculation in many interrupts, and sharing a lot of data between there threads, you likely want an OS.But, for example, if you need to build a 3-phase inverter motor controller with current sensing, you definitely do not use an RTOS on the firmware. You initialize the PWM generation timer, calculate FOC algorithm in the timer interrupt, and possibly have another set of interrupts for overcurrent, desaturation, DC bus overvoltage events etc., all of which can be triggered in some hundreds nanoseconds, not 5µs.Sure, both approaches can coexist (a more sophisticated inverter could use an RTOSandinterrupts, or even opt for two-chip solution where another microcontroller runs bare metal and another runs a full-fledged OS, RT or not), but then you need to understand and be able to do both. If the total complexity doesn't require an RTOS, then it's just extra weight. This would be the OP's case.I also fail to see how an RTOS magically makes variables atomic. The programmer needs to remember to use specific atomic constructs, just like in bare-metal.All ARM Cortex microcontrollers implement interrupt priorities; it's very simple to use; one LoC of initialization, and one LoC to set the priority for the interrupt.Finally, as JOE himself says, "Quite frankly a lot of people don't understand thread safety." I wholeheartedly agree. The problem doesn't go away with RTOS; quite the opposite, much of the problem iscreatedby the OS, you bring in mutexes, possibilities for deadlocks, thread synchronization, all that pile of complicated shit many of us fail at. Of course, the problem originates from the hardware (and the OS abstracts it to lookdifferent) so not using an OS isn't a magical solution either, but a simple interrupt-based bare-metal system is easier to understand as long as it really stays simple enough not to warrant the added abstraction. This would be the case for the OP.The idea of something being "modern" and having to be used only because of that is dangerous. Use the right tool for the job.

author: dietert1
author_rank: Super Contributor
timestamp: October 04, 2020, 08:40:59 am
content: There is a difference being a specialist and publish an RTOS framework and being a specialist writing smart comments in this forum. Especially for a beginner in embedded trying to understand one of the published frameworks is the recommended approach. All those smart comments here won't get him/her anywhere. A stupid idea to think one can do those things better than the specialists who spent their whole career thinking about concurrency, thread safety, priorities and the like.Regards, Dieter

author: Simon
author_rank: Unknown
timestamp: October 04, 2020, 08:48:03 am
content: Quote from: dietert1 on October 04, 2020, 08:40:59 amThe sad thing is that this thread has simply been a chest beating match way out of the scope of what the OP asked for. They asked to do something I myself pondered at one time and have only just started to do. It's not complicated but that important first step out of code that runs dumbly in a loop making poor use of the interrupt resources to something that can handle things in a semiautomatic way. Not hard, I have done it a couple of times myself.Instead of some help on first steps they have gotten cautionary advice fit only for people trying to design something like a bloody ballistic missile system..... What he wants to do is simple, it's a question I once asked our subcontractor at work about how he did his. Once he explained the basic principles I was able to get something up and running.

author: Jan Audio
author_rank: Frequent Contributor
timestamp: October 04, 2020, 01:35:12 pm
content: Indeed you all scared the new member.Curious if his PSU is working...Always fun these topics.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 04, 2020, 01:44:13 pm
content: It never stops amusing me how certain people can't resist posting "this thread is crap with no content" comments, do they think their such comments lead the way? It's a handful of people, they never seem to give meaningful contributions. Oftentimes, there is some good discussion if you give it a chance, it's just that the people with limited attention span seem to think that all the others have the same attention span problem as well and will "run away" if they see a message longer than 100 words, let alone something that considers different viewpoints instead of directly answerring a question. These people never learn anything, just keep complaining everything is TLDR or "offtopic" even when it isn't.If you have nothing to say, just do not post. Lead the way; if you feel the content is of wrong type, provide the right content type, instead of complaining about it.

author: NorthGuy
author_rank: Super Contributor
timestamp: October 04, 2020, 01:47:09 pm
content: Quote from: Jan Audio on October 04, 2020, 01:35:12 pmIn the beginning, the OP seemed to want to take every advise given. Perhaps now he realized the futility of this idea and started thinking on his own. If this is the case, this thread was a great success.

author: Simon
author_rank: Unknown
timestamp: October 04, 2020, 03:58:34 pm
content: Quote from: NorthGuy on October 04, 2020, 01:47:09 pmYes and no, what I would have learnt is don't bother asking as you will be told several ridiculously complicated ways of doing it and warned about the end of the world when nothing like that will happen.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 04, 2020, 04:31:26 pm
content: I agree that OP's case is quite simple. Run some UI code / command parsing / reporting in main while(1). If you have some PI(D) feedback loops for voltage/current, run them in the ADC conversion finished ISR. If you have safety events, run the associated shutdown signals in high-priority interrupts. There are some traps regarding synchronization and shared state: for example, if your main code enables power, and your safety ISR disables the power, need to make sure "enable power" commands are ignored (for some time? until acknowledged?) in the main code, and that there is no short time window where this ignoring won't work. You need to think about it, but it's not going to be a big thing.The best way to learn is to pick some method and implement it using it. Such simple project can succeed in many different ways. Then you get the first-hand experience and can think if you could do it otherwise.

author: Simon
author_rank: Unknown
timestamp: October 04, 2020, 04:33:32 pm
content: Precisely, trying to write something that first time is fit for a missile control system is doomed to fail.

author: thinkfat
author_rank: Unknown
timestamp: October 04, 2020, 08:11:07 pm
content: If the OP reads this thread carefully, and is able to think for himself (which I assume), he'll find a lot of useful information and potential traps and pitfalls in here. He'll also find that, even here, nothing that is written on the Internet is to be taken at face value

author: Fixpoint
author_rank: Regular Contributor
timestamp: October 13, 2020, 09:38:15 am
content: Quote from: NorthGuy on September 23, 2020, 12:52:04 amActually, that is a ridiculous myth. Just have a look at real-world systems like an oscilloscope or an RF spectrum analyzer. The electrical engineering is complicated and requires years of education and much intelligence. Many people cannot even design a multimeter themselves because they will soon discover that the "simple" principles are very hard in practice. And mechanical or chemical engineering? Same story. How "simple" is it to build a rocket?And now, please try to build a self-stabilizing quad copter yourself, including designing and implementing the software and the differential equations. Have fun with your "simplicity".QuoteWell, that is only half of the picture. There are two different kinds of complexity, namely intrinsic complexity and accidental complexity. You can only take away accidental complexity, but not intrinsic complexity. That's the one you simply have to deal with, and if you can't, you'll create a mess.QuoteIt doesn't seem that you know what you are talking about here, neither regarding the theoretical side of CS nor the practical side.QuoteNo, it doesn't. Just no. Just have a look into CS what people are doing there. BTW, programming isn't CS, in case that's what you're thinking, just like laying bricks isn't civil engineering.CS creates neither complexity nor simplicity. CS is concerned with developing mathematical models and mathematical tools for *dealing with* complexity *in general*. It is up to you whether you want to ignore this or not, but that's how it is.QuoteYes. That's the result of bad engineering, i.e. misled product managament and bad software development. You are mixing up CS and development. What you are doing is like saying "mathematics took a wrong turn" when your circuit isn't working. No, that's not the result of bad mathematics but of bad engineering.

author: Siwastaja
author_rank: Super Contributor
timestamp: October 13, 2020, 10:33:52 am
content: Having the mindset of achieving simplicity is exactly important because many problems are inherently so complex.They are complexevenif you do them the simplest way possible. Such projects have a real risk of blowing up in complexity until they become nearly impossible to finish unless the team understands how to simplify.For example, a self-stabilizing quad-copter optimally requires the workload of about 4-5 (technical engineer) team members and about a year or two of development time. But getting to this requires the mindset of simplification, understanding which parts are necessary which are not. You can easily get lost by applying modern software industry practices, for example, and you definitely can hire 200 trained monkeys fresh out of CS class to design a middleware for your quadcopter communication bus. This is not unseen; such projects just don't ever work out so the end consumers won't hear about them, but those "in the industry" will.So being able to simplify will be one of the major deciding factors whether the project will be successful or not. The exception are fundamentally very simple projects which can finally succeed even with a lot of bloat. Many modern information technology services are such examples; think about selling a train ticket online, something a hacker write over a weekend, and a large software house can spend years and tens of millions to pull off badly.

author: Fixpoint
author_rank: Regular Contributor
timestamp: October 13, 2020, 11:20:17 am
content: Quote from: Siwastaja on October 13, 2020, 10:33:52 amYes. But that's not the point. The point is that we understand that this has nothing to do with computer science. What you are describing is a (rather classic) management error. Someone made a wrong management decision for whatever reason -- there are very many very different reasons why someone could act like that. Anyway, CS is not concerned with business administration. Also, it is not concerned with the question whether you should design a communication bus at all.In simple terms (we want it simple, right?), computer science is concerned with(a) algorithms,(b) data structures,(c) both formal and semi-formal meta-models thereof.For example, software development is an application of all of that.QuoteNo. Just think for a minute. The interfacing with all the third-party systems ALONE will take much time and many people. And you didn't even design the routing algorithms etc. Let alone the user interface. Etc etc etc. It's a very complex system.

author: NorthGuy
author_rank: Super Contributor
timestamp: October 13, 2020, 04:59:50 pm
content: Quote from: Fixpoint on October 13, 2020, 09:38:15 amIf a rocket is more complex than a LED blinker is not a reason to make the rocket more complex as it already is. Nor is it a reason to make LED blinker as complex as a rocket.Quote from: Fixpoint on October 13, 2020, 09:38:15 amMathematics can take a wrong turn too. If you destroy the underlying principles, for example if you decide that no proof is needed for theorems, the mathematics will become completely useless within few years. We have seen similar things in the past. After the destruction of Roman empire, the whole science got derailed and moved nearly nowhere for over a thousand years.Of course, the mathematical parts of the computer science, such as sorting algorithms, are Ok, they existed long ago. But the computer science also deals with computer languages, programming paradigms, general methods and more. For example, why do you think RTOS is necessary in all your projects? You probably went to a college and you were taught this in one (or many) of your courses. Did they give you a mathematical proof of that? No? Then this has nothing to do with mathematics, but still a part of the computer science. Does this make you a bad engineer? Of course not.

author: Fixpoint
author_rank: Regular Contributor
timestamp: October 13, 2020, 05:58:01 pm
content: Quote from: NorthGuy on October 13, 2020, 04:59:50 pmUm ... if we take away the proofs, then it's just not mathematics anymore. That would not be a "wrong turn" but just nonsense.QuoteWell, yes, CS "deals" with that, but let's be more precise: In computer science, we don't *create* programming languages but *study* them. CS is not an engineering discipline, so nothing is built there, only studied.CS develops principles that are fundamental *for* programming languages. There are some computer scientists (persons!) who create programming languages, but don't confuse those guys with the academic discipline *itself*.There are research programming languages (like Prolog or many functional languages, for instance) that were created at universities. They were designed for study, not for practical use. In practice, research languages are of limited use, and that's ok, because that's not what they are made for.QuoteI don't and I never said that. What I say is that IF you need a scheduler, THEN it should be preemptive. But if you don't need a scheduler, then so be it. In general, you do NOT need to use an RTOS.QuoteI was never taught anything about RTOS, I learned about that the hard way -- on the fly in projects. My opinions don't come from the lecture halls but from my experience in real-world projects.

author: NorthGuy
author_rank: Super Contributor
timestamp: October 13, 2020, 09:45:33 pm
content: Quote from: Fixpoint on October 13, 2020, 05:58:01 pmThe science not only studies things, but also teaches. The students spread around, some of them become engineers, others post on the forums, write blogs, or whatnot. So, the science has a strong influence on our life.

author: Fixpoint
author_rank: Regular Contributor
timestamp: October 14, 2020, 09:22:36 am
content: Quote from: NorthGuy on October 13, 2020, 09:45:33 pmWell ... *people* teach. Not science teaches, but people teach. And those people can do what they want. They have different mindsets, different understandings, etc. So, some will teach good stuff, while others will teach crap.I observed that people who write blogs or make YouTube videos most often do not rely on academic knowledge but on their own (mis)understandings and will "teach" their audience pretty ridiculous stuff. Actually, it would be nice if those people had deeper knowledge and understanding of things, but ...Additionally, at universities, there is a very simple yet fundamental problem. Professors (and their assistants) have to teach many subjects that are far removed from their own specialty. For example, a CS professor who has specialized in logic might have to teach an introduction to software engineering at some point. With high probability, the lecture will be a disaster, but that's how it is. So, students might be taught wrong things because of the system. But that has nothing to do with CS itself.You have the same problem in electrical engineering. When a professor of electrical engineering has to do a course about software engineering, it will likewise be a disaster with high probability. Simply because an EE professor is a specialist for EE, not software. But again, that's the academic system. It always assumes that a professor is some kind of genius who understands everything perfectly and can teach everything perfectly.During my academic time, I myself was forced to teach a certain course that I was not a specialist in. I did my best, but I think other guys might have done a better job. That's how it is. Unfortunately.