Best way to share SPI3 between RTOS tasks - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: May 22, 2021, 07:26:28 pm
content: I am talking to several ADCs and other stuff, all on SPI3. They all have different SPI configs: different clocks, clock modes, clock speeds.It all works, so long as all the code using these devices lives in the one RTOS thread.For each device, I have something like this:adc3_init();x3=adc3_read();and thenadc4_init();x4=adc4_read();Now I need to read the various ADCs from different RTOS threads. Obviously SPI is not re-entrant. So I need a semaphore. In the old days, say Z280, there was a "test and set" instruction. You would doloop: test and set (addr)if fail then goto loopadc4_init();x4=adc4_read();addr=0;The RTOS friendly way would beloop: test and set (addr)if success then goto getitosDelay(1); // yields to RTOSgoto loopgetit: adc4_init();x4=adc4_read();addr=0; // release semaphoreI think the C ARM environment allows semaphores and the ARM CPU has an instruction for it, so this should be possible without anything to do with FreeRTOS. This shows a nice simple example of howhttps://developer.arm.com/documentation/dht0008/a/arm-synchronization-primitives/practical-uses/implementing-a-mutexHowever FreeRTOS also supports this stuff. I have been reading up on it e.g.http://khjtony.github.io/project/2015/12/22/TinyCar_Blinky.htmlandhttps://www.st.com/resource/en/user_manual/dm00105262-developing-applications-on-stm32cube-with-rtos-stmicroelectronics.pdfbut can't get my head around it. Also the FreeRTOS doc says vSemaphoreCreateBinary is obsolete and has been deprecated. I know there are clever ways involving sending interprocess messages etc (so the SPI could be in one thread and you would send requests for the data to it) but I want something really simple. There seem to be so many ways to do this.Can anyone suggest something really simple?It can't be that complicated because I am using devices which are slow relative to the SPI speed. Probably the slowest is a STLED316 display driver whose SPI runs at 650kHz, which is a few tens of us every fraction of a second. The ADCs are delta-sigma e.g. ADS1118 or MCP3550 which take milliseconds and have ~5MHz SPIs. So the duty cycle at which the SPI init+transfer fragment will be locked is tiny.This must be a common issue with RTOSes and SPI or I2C.

author: chickenHeadKnob
author_rank: Super Contributor
timestamp: May 22, 2021, 07:39:29 pm
content: Instead of semaphore guarding I would retain your single task that owns the spi, an spi server if you will that serves three message queues. You can then parcel spi throughput any way that makes sense.

author: agehall
author_rank: Frequent Contributor
timestamp: May 22, 2021, 08:20:42 pm
content: I second that idea. That is what I would go with if I needed to do that.

author: Tagli
author_rank: Contributor
timestamp: May 22, 2021, 08:40:56 pm
content: Same here, I have implemented it in that way. Messages carry requests from clients to SPI Manager task.This is copy & paste from some C++ code but the same logic can be applied in C too.Code:struct SpiRequest {FeedbackChannel* client; // This can be a task handlevoid* source; // for MOSIvoid* destination; // for MISOuint16_t xferCount; // number of 8 or 16 bit transfershw::Spi::Settings setting; // SPI mode and data format (bits)const hw::Pin* csPin;};One can queue pointers instead of the whole struct, if the client task can guarantee the life time of the struct. The SPI Manager can give a feedback using task notification when the SPI transaction is completed.

author: peter-h
author_rank: Super Contributor
timestamp: May 22, 2021, 09:00:00 pm
content: OK, I can see that is elegant, but I am pretty much on the edge with C. I don't really fully get pointers, for example, so use arraysStructures, and passing them as a parameter, are another thing I need to think about and find examples. And every example I see with the RTOS stuff is really complicated. I am learning, and have written quite a lot of C code lately, but it is all taking time. And because I am working on this mostly alone I need to fully understand what I am doing.There must be a simple C function for "test and set" of a semaphore (which was previously obtained with another function), and then a function for releasing it.On most calls to the SPI, the semaphore will be free, because of the very low duty cycle. Well, except for the MCP3550 whose conversion takes ~100ms and on that I just do an osDelay(120), for complicated reasons to do with the conversion status stupidly not being available over SPI, so one would have to reprogram the SPI IN pin to be a GPIO. So on that one the semaphore will be busy for 120ms, but I can choose when that device is accessed via other means so it doesn't affect anything. Also one yields to the RTOS for that whole wait anyway. This is an issue with most delta-sigma ADCs but as I say one yields to the RTOS for the conversion time.I could implement my own semaphore using a global variable but would need the equivalent of DI/EI around it, which is tacky.

author: peter-h
author_rank: Super Contributor
timestamp: May 23, 2021, 08:39:12 am
content: Digging around some RTOS and LWIP code I found thisSemaphoreHandle_t SPI3_Mutex = NULL;while (osMutexAcquire(SPI3_Mutex,osWaitForever)==osErrorResource){osDelay(1);}... do SPI3 stuffosMutexRelease(SPI3_Mutex);Perhaps the Mutex delay parameter is the same thing as osDelay i.e.SemaphoreHandle_t SPI3_Mutex = NULL;while (osMutexAcquire(SPI3_Mutex,1)==osErrorResource) {}... do SPI3 stuffosMutexRelease(SPI3_Mutex);The above is wrong because osMutexAcquire can return other codes e.g. osErrorTimeout, but perhaps the idea is right. I think it should beSemaphoreHandle_t SPI3_Mutex = NULL;while (osMutexAcquire(SPI3_Mutex,osWaitForever)!=osOK){osDelay(1);}... do SPI3 stuffosMutexRelease(SPI3_Mutex);There seem to be other ways e.g. usingvSemaphoreCreateBinary(SPI3_Mutex); // obtain itxSemaphoreTake( SPI3_Mutex, 0 ); // set it to a known stateI seem to have got it working withosMutexId_t SPI3_Mutex;in main.cSPI3_Mutex = osMutexNew(NULL);and then before each SPI3 accesswhile (osMutexAcquire(SPI3_Mutex,osWaitForever)!=osOK){osDelay(1);}and after itosMutexRelease(SPI3_Mutex);

author: nctnico
author_rank: Super Contributor
timestamp: May 23, 2021, 10:24:16 am
content: Quote from: chickenHeadKnob on May 22, 2021, 07:39:29 pmBut that would incur the overhead & complexity of a queue system which doesn't add any functionality to the project. IMHO using mutexes is a much more simple solution; I would make it using mutexes.The way peter-h is describing in the post above seems good enough to me. Bonus points for using mutex waits which can trigger on a mutex becoming unlocked so the osDelay() function doesn't need to be called. Random Google find:https://www.freertos.org/FreeRTOS_Support_Forum_Archive/December_2007/freertos_waiting_on_semaphores_-_indefinitely_1895550.htmlNote that people seem to mix then terms mutexes and semaphores so read carefully on what they actually mean (=expected functionality) by using the words.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: May 23, 2021, 10:42:23 am
content: Queues definitely help clean up things especially if you have eeprom writes going on for example. You don't want all your tasks that use eeprom to be slowed down by the eeprom writes which might have to wait for a page erase to finish

author: nctnico
author_rank: Super Contributor
timestamp: May 23, 2021, 01:24:35 pm
content: Quote from: JOEBOBSICLE on May 23, 2021, 10:42:23 amOne way or another you'll have to wait for the page erase to finish before a task which wants to write the eeprom can continue; that task also has to take action in case the write to the eeprom fails. Having a queue doesn't change that or make things easier. The whole point of having an OS is that you can wait for things to finish. If that isn't 'possible' then your threading model is wrong and you need to start an extra thread for a sub-task which can't wait for writing to an eeprom (for example). You really need to be careful not to add extra layers which don't add any functionality. It is like painting a wall red before painting it green.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 23, 2021, 05:21:15 pm
content: Quote from: chickenHeadKnob on May 22, 2021, 07:39:29 pmAlso second this.Multi-threading is great, so long as you don't abuse it.When accessing a given hardware resource, the best approach is usually a single "worker thread". Here, a single task handling all accesses to SPI3. Just use some kind of messaging from other tasks to start and retrieve the result of a given conversion. It's by far the cleanest and also easiest to debug approach.Likewise, when dealing with UIs, it's a common approach to use a single thread for the UI, and communicate with it from other threads, letting it handle all accesses to UI resources.

author: peter-h
author_rank: Super Contributor
timestamp: May 23, 2021, 06:43:45 pm
content: " Bonus points for using mutex waits which can trigger on a mutex becoming unlocked so the osDelay() function doesn't need to be called"If thiswhile (osMutexAcquire(g_SPI3_Mutex,osWaitForever)!=osOK){osDelay(1); // yield to RTOS}equivalent toosMutexAcquire(g_SPI3_Mutex,osWaitForever);It should be, because while "waiting for ever" in the 2nd case, what else is it going to do other than run other RTOS tasks?I have it working apparently ok, but need to do more testing.That 2007 article mentions functions like xSemaphoreCreateMutex but they don't seem to be supported anymore.I already see this defined:#define INCLUDE_vTaskSuspend 1

author: Silenos
author_rank: Regular Contributor
timestamp: May 23, 2021, 09:19:28 pm
content: It the display thing fire-and-forget write only slave?

author: peter-h
author_rank: Super Contributor
timestamp: May 24, 2021, 02:09:55 am
content: The STLED316 is one-way comms, yes.There is an interesting issue with SPI in that the ST generated SPI code seems to wait until a transfer is complete before returning to the caller. It is a common mode with SPI ADCs that while you shift out a command you are shifting in the result of a previous conversion, so you don't want to just load the SPI TX and forget it, like you would with a UART. Maybe that is why they did that. But the other issue with SPI is that you have to implement the /CS and for that you either need a blocking transmit or an "end of" interrupt. The auto CS feature is not usable with multiple slaves on the same SPI.There is a non-blocking "transmit with interrupt", not currently used, presumably intended for the above mode. It is trivial to not enable the interrupt but I would need to implement /CS=1 somehow, and do it before the mutex is unlocked.In overall timing terms just hanging on the TX this is not a big deal, and the RTOS can switch out anyway.

author: TomS_
author_rank: Frequent Contributor
timestamp: May 24, 2021, 05:12:57 am
content: Quote from: peter-h on May 22, 2021, 07:26:28 pmUse xSemaphoreCreateBinary instead.I tend to find the FreeRTOS docs and their examples reasonably good, and the community on their forum is very helpful. There are also videos of YouTube explaining a lot of the features etc that might help, e.g.https://youtu.be/grXuVMttVuU

author: peter-h
author_rank: Super Contributor
timestamp: May 24, 2021, 08:17:27 am
content: What is wrong withosMutexAcquire(g_SPI3_Mutex,osWaitForever);I am struggling to work out the differences. I think the former comes from an ST wrapper for FreeRTOS.

author: newbrain
author_rank: Super Contributor
timestamp: May 24, 2021, 08:23:56 am
content: Quote from: peter-h on May 23, 2021, 08:39:12 ampeter-h, a word of caution:You are mixing the CMSIS RTOS abstraction layer API (os... stuff) with the native FreeRTOS API (x... v... etc.).You should stick with only one (I use the native one), as some concepts, calls and properties might not map completely.As said above,FreeRTOSdocsare quite good, and thetutorial book, though a bit old, still gives a lot of insight and handy suggestions.That said, about the problem at hand, the main solutions have already been discussed:Server task with input queue (one is enough - unless you want to have different priorities)Use of Mutex/SemaphoreI tend to prefer the second one, as it does give more or less the same freedom and has less overhead.Usually the access to the peripheral is guarded with a mutex - only one user at the time.Internally the driver uses DMA or interrupt (e.g.: I need to send large amount of data to an SPI display, so I use DMA, for my I2C needs interrupts are fine) and a semaphore or, in FreeROTS, a cheaper direct task notification - tasks not depending on the peripheral run (mostly) unaffected.

author: tggzzz
author_rank: Super Contributor
timestamp: May 24, 2021, 08:33:25 am
content: Quote from: peter-h on May 22, 2021, 09:00:00 pmThere is no such function in C. Until very recently C presumed that code was single-threaded, and that multithread/processor stuff was part of the library. Unfortunately, if you think about it, those two statements conflict with each other! Many people didn't think about it, so (remarkably) it was necessary for Hans Boehm to write his paper "Threads Cannot be Implemented as a Library"https://www.hpl.hp.com/techreports/2004/HPL-2004-209.pdfGiven your current knowledge of C, you will subtly fail to implement multithreaded code correctly, which will lead to intermittent errors.Your best approach is, as others have suggested, to have three tasks and send messages between them using the RTOS facilities. Debugging will be greatly simplified since you can log the messages to find out what each task is doing.

author: peter-h
author_rank: Super Contributor
timestamp: May 24, 2021, 08:45:16 am
content: "You are mixing the CMSIS RTOS abstraction layer API (os... stuff) with the native FreeRTOS API (x... v... etc.)."I am now using only (in main(), before RTOS starts)osMutexId_t g_SPI3_Mutex;g_SPI3_Mutex = osMutexNew(NULL);and then around each hardware bitosMutexAcquire(g_SPI3_Mutex,osWaitForever);...osMutexRelease(g_SPI3_Mutex);" you will subtly fail to implement multithreaded code correctly, which will lead to intermittent errors"Which areas do you think are at risk? I am very familiar with low level stuff in assembler, having written megabytes of it, even writing a simple RTOS from scratch (Z280) which worked totally faultlessly. Static variables are not thread-safe of course, etc. Functions which keep vars on the stack are ok.On this project, the obviously not re-entrant stuff is the hardware, which is why I want the mutexes.I've read K&R (re-read it, cover to cover, yet again) and sure there are no C functions for creating mutexes etc, but it must obviously be possible with asm inserts, because every modern CPU must have a test and set instruction. FreeRTOS makes fairly extensive use of asm, I found.

author: newbrain
author_rank: Super Contributor
timestamp: May 24, 2021, 09:07:14 am
content: Quote from: peter-h on May 24, 2021, 08:45:16 amK&R, as good as it is, covers (in its 2nd edition) a 32 years old standard.Do you yourself a favour, and find something updated for at least C99 (I have no suggestions to give, sorry, I never read a C book, and only use the standards now).

author: tggzzz
author_rank: Super Contributor
timestamp: May 24, 2021, 09:51:54 am
content: Quote from: newbrain on May 24, 2021, 09:07:14 amIf the OP is planning on rolling his own threading primitives, he should start with C14, whichfinallyintroduces the fundamental concept of memory models!

author: gf
author_rank: Super Contributor
timestamp: May 24, 2021, 10:33:00 am
content: Quote from: peter-h on May 22, 2021, 09:00:00 pmDepends on the C version. At least the C11 standard defines theAtomic operations library. Prior to C11, some compilers did provide compiler-specific extensions (e.g. the__sync_...()built-ins of gcc -- but most of these functions are considered full barriers, not distinguishing the different memory ordering constraints available in the atomic operations library).

author: peter-h
author_rank: Super Contributor
timestamp: May 24, 2021, 10:57:19 am
content: What I am doing is much more simple than that. It is very ordinary stuff, with very separate tasks running under the RTOS, inter-communicating hardly at all.If I can do something very simply, e.g. a global boolean saying "got some data for you", which is cleared when the data is picked up, that's how I do it. And my code is fairly basic stuff e.g. processing NMEA data, extracting date/time, coordinates etc, converting it to ARINC429...

author: Silenos
author_rank: Regular Contributor
timestamp: May 24, 2021, 11:09:39 am
content: Quote from: peter-h on May 24, 2021, 02:09:55 amFor "transmit only" you have to either wait for end of transaction, or don't wait, but have the transaction queued in case multiple tasks happen to query for transmit.For simplicity and "just move on" sake I would just spinlock the writes-only to the adcs/display. Plus put a server/arbiter which runs the adcs in continous conversion mode (or timer triggered), reads the results and puts read-only values in shared interface.

author: newbrain
author_rank: Super Contributor
timestamp: May 24, 2021, 11:29:30 am
content: Quote from: peter-h on May 24, 2021, 02:09:55 amThe HAL provides both blocking and non-blocking (in DMA and IRQ versions) API for most of the peripherals.IIRC, the latest one allows to register callbacks for various events (but I should check).A SW CS (the HW based one in ST IP is a joke) can, as you say, be asserted before calling the transfer function, and de-asserted in the ISR when everything has been transmitted - just make sure not to base it on the TX empty flag, the reference manual describe how to check that the last bit has left the shift register.If you implement a driver along the lines I suggested above, CS handling might be part of it.

author: tggzzz
author_rank: Super Contributor
timestamp: May 24, 2021, 02:01:47 pm
content: Quote from: peter-h on May 24, 2021, 08:45:16 amThere are many many subtle traps with C. I suggest you start with the Boehm reference I gave you.QuoteI've done similar things, since we are of the same vintageI've been writing C since, I think 1983 when there were precisely two books on C.Hence my knowing where the dragons lie in wait.QuoteK&R was sufficient in the 80s, particularly since it noted what C avoided specifying. Since then C has expanded enormously in both size and ambition - and that has brought many problems that language lawyers love debating.My favourite was the mid-90s debate over whether it should be possible or impossible to cast away constness". Yes enables debuggers, no enables optimisations.The best technique is to use the RTOS primitives, especially message queues or mailboxes.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: May 24, 2021, 06:49:09 pm
content: Quote from: nctnico on May 23, 2021, 01:24:35 pmSome tasks don't care if an eeprom write fails, if it fails then it's best to let the eeprom task sort it out right? If they do care then you could pass in a write back queue handle and have the writing task wait on an error code to be written back.A queue definitely makes it easier, your eeprom task will only run when it has writes to perform. You can also make it a lot more efficient by having it run only every few seconds such that you can collect up eeprom changes before doing any writes which reduces eeprom wearingThe layers thing isn't true though, sometimes abstractions improve performance. Abstraction can reduce duplicate code, for example you probably don't want two spi drivers with one for each spi peripheral.Imagine if your eeprom writes need to store a page of memory. That would mean every task needs a stack that's big enough to hold a page of memory. That's certainly not efficient use of memory.

author: nctnico
author_rank: Super Contributor
timestamp: May 24, 2021, 08:00:06 pm
content: Quote from: JOEBOBSICLE on May 24, 2021, 06:49:09 pmThe problem is that lower layers can't sort out how to deal with errors. Especially where it comes to recovery. So in the end: what is the difference between waiting for the hardware to become available compared to waiting until a message in a queue has been processed and an answer comes back? If you want to do the latter in a non-blocking way (which was the argument for using queues) then your process will no longer be stateless; it will be in a state where it is waiting for input or waiting for the eeprom to be finished (in which case it likely can't accept input). In the end you are just shoving the problem around. It is much better to put the queue at the input of the process which can do something meaningfull in case of a failure (or do some kind of caching) rather than dividing processes further down.Just having a function write_eeprom(area_id, offset, data, length) which blocks until the data has been written or the write failed makes life a whole easier than having to do this with messaging queues in between.QuoteOfcourse not but that is not the problem at hand. You want to share the hardware + driver layer. Just look at how the Linux kernel shares SPI and I2C ports; the IOCTL, read / writes will block until the hardware becomes available. No queueing or caching in the driver layer because it doesn't have the knowledge on how to do this effectively. The higher layers have the intelligence to know what kind of device they are talking to and how the data is organised.

author: peter-h
author_rank: Super Contributor
timestamp: May 25, 2021, 10:39:50 am
content: "Some tasks don't care if an eeprom write fails"You cannot do anything about a hardware failure, in an embedded system. Well, you can put up some error indication so the duff chip can be changed."You want to share the hardware + driver layer"I think, in a low duty cycle sharing situation, a mutex is really efficient. Also if you have SPI running at say 5MHz (or better still one running to a serial FLASH at 20-40MHz) performanceislikely to be an issue and you don't want to add some tens of us of overhead. Most of the time the mutex will come back "free". If you have a message queue, you still need to arbitrate the requests, and how do you do that? It has to be more of a perf hit AFAICT.Re my level of C I will defend the desire to use a language in a simple way, and limit myself to software architecture which works well with thatI have been involved on projects containing C since ~1985 and practically all bugs were due to pointers. Also practically all bugs in PC software were due to pointers, especially in the early days. C is a great language and remains a great language even if you use it in a simple way.I've put in the mutex as described and it has been running all night. Admittedly I don't yet have multiple RTOS tasks accessing the same device; that will be the next test.One thing I did do was to reorganise the SPI mode init so that a global variable maintains a value corresponding to the currently initialised device, and when you call that func (say readadc()) it checks if the current SPI init is correct for that device, and if not, initialises the SPI for that device, and updates the global variable. That keeps the mutexed piece of code all together.The bigger gotchas with SPI sharing I wrote about some time ago. The 32F4 SPI controller does funny stuff when you change the clock mode or speed, and needs some minimum time to sort itself out between devices. I never got to the bottom of this in terms of any definition, except on the STLED316 whose SPI runs at 650kHz and that init func needs a lot of delay. An attempt to deal with it, which someone suggested, was to transmit some 0x00 after the initialisation, before talking to the device, but it didn't help.

author: tggzzz
author_rank: Super Contributor
timestamp: May 25, 2021, 04:20:50 pm
content: Quote from: peter-h on May 25, 2021, 10:39:50 amWhich is more important, efficiency or correctness?As someone once said, "if I had know that my program could contain errors, I could have made it 100 times faster".QuoteVery sensible. I attempt that and choose tools that aid it rather than hinder it.QuoteAs Einstein said, "you should make thing as simple as possible, but no simpler".The language and processor architecture have moved a long way since then, and not toward simplicity.QuoteOld engineering aphorism that software people have all but forgotten: you can't test quality into a product, you have to design it in.How are you going to write a (repeatable) test that will detect a, say, 1 in 10000 chance of an asynchronous race condition causing a bug that you hadn't thought could exist?I hope you realise that "early" C standards explicitly exclude asynchronism guarantees and cannot be used to write a portable threading library. K&R are quite explicit about that. C14 is the first with a memory model.Why have an RTOS and not use its beneficial features?

author: mikerj
author_rank: Super Contributor
timestamp: May 25, 2021, 04:23:13 pm
content: I'd use a mutex every single time for this situation. Lower resource usage and almost certainly lower execution time than a queue.

author: Tagli
author_rank: Contributor
timestamp: May 25, 2021, 05:03:12 pm
content: AFAIK, semaphores & mutexes in FreeRTOS are implemented using queues. This line is taken fromsemphr.h:Code:#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )Of course, sending a zero-length message isn't the same thing as passing some data around. But I still prefer queues, as they logically make more sense for me. It's easier to think in terms ofclients & servers.

author: nctnico
author_rank: Super Contributor
timestamp: May 25, 2021, 05:04:41 pm
content: Quote from: tggzzz on May 25, 2021, 04:20:50 pmWho cares about the C standard where it comes to memory and OS specifics? Memory management is the job of the OS. The OS is supposed to provide working mutexes taking into account the shortcomings / features of the platform it is running on. If you read the article from Boehm you linked to earlier very carefully you will notice that his statement is just a hyperbole to grasp your attention. The conclusion of the article is that you can actually have synchronisation from a C library but this must support the underlying hardware which also involves interaction with the OS. If that wheren't the case then none of the computers on this planet would be useful.Whether mutexes come from a library or an OS or both is just semantics. Meh. As a programmer you don't care.

author: peter-h
author_rank: Super Contributor
timestamp: May 25, 2021, 05:40:31 pm
content: Yes - I don't understand why a mutex should be less than 100% reliable - assuming it is correctly implemented initscode.I stepped through the mutex code and didn't think there was much there. Maybe of the order of 10us, at 168MHz. One could time it, of course. Here ya go:6 seconds, and that is with ETH and about 5 other RTOS tasks running. So, around 1-2us for each call.

author: tggzzz
author_rank: Super Contributor
timestamp: May 25, 2021, 06:34:05 pm
content: Quote from: nctnico on May 25, 2021, 05:04:41 pmRemember the OP's question as to whether there was aC functionfor mutexes etc. There is not and cannot be in the versions of C he is referring to. It is, of course, entirely possible to use assembler to implement a non-portable mutex function that can be called from C. But that is not a C function.Over the decades I've seen far too many peoplethinkthey have correctly implemented multithreading functions in C, but in reality they haven't understood thelackof guarantees given by C. That's especially true when the compiler is told to optimise the shit out of the code.Boehm isn't a fool and merely wanted to get the (very important) points over to disbelievers. It was a significant influence in getting the later C standards to address the issues - so it was necessary and successful.

author: peter-h
author_rank: Super Contributor
timestamp: May 25, 2021, 06:42:29 pm
content: "they haven't understood the lack of guarantees given by C"Can you give an actual example?All I can think of is stuff like creating hidden temporary variables which are static (not on the stack) but that would be incredibly dumb since it would also prevent recursion which is nothing to do with multithreading.

author: tggzzz
author_rank: Super Contributor
timestamp: May 25, 2021, 06:49:31 pm
content: Quote from: peter-h on May 25, 2021, 05:40:31 pmI wasn't considering whether the mutex itself was reliable. The question is whether the entire application is reliable when all parts are executing. It is easy (and common) for bits of a system using mutexes to work in isolation, but when all are bolted together "unpredicted" behaviour emerges.Once that happens it can be difficult to examine an application's entrails to determine what has happened and what caused it to happen. Contrast that with message queues where it is easy to observe the flow of control (i.e. messages in queues), either with a debugger or by looking at the queue length.Tried and trusted real-time architectures, both hardware and software (at multiple abstraction levels), are based around the concepts of events and states, in the form of finite state machines. Event maps on to message, and state map onto task/thread local variable.Logging events and state transitions gives a direct insight into what is happening at the system level.

author: tggzzz
author_rank: Super Contributor
timestamp: May 25, 2021, 07:01:45 pm
content: Quote from: peter-h on May 25, 2021, 06:42:29 pmStart by considering the volatile keyword; it does less than many people think when the compiler re-orders variable accesses and/or eliminates "redundant" code.Googling will find many examples of "surprising behaviour", where the language lawyers respond with reasons why they are right and the poor average developer doesn't understand the tool they are using. Usually the language lawyers make full use of the lack of guarantees in C. While correct, that isn't helpful, in my opinion.

author: peter-h
author_rank: Super Contributor
timestamp: May 25, 2021, 09:19:21 pm
content: This is all generic talk. I see no actual examples of anything. People saying they know something but aren't saying what it is.Can anyone post an example of C code which breaks when multi-threaded, explaining why it broke?

author: tggzzz
author_rank: Super Contributor
timestamp: May 25, 2021, 10:50:42 pm
content: Quote from: peter-h on May 25, 2021, 09:19:21 pmIt is a wideranging problem, so a that is unsurprising. If a specific example is posted then people will concentrate on the details and not see the larger picture.If you want specific examples, google for them. I have neither the time nor energy to give you full explanations and examples - but I can point you in a suitable direction for you to get yourself up to speed.

author: chickenHeadKnob
author_rank: Super Contributor
timestamp: May 26, 2021, 12:40:53 am
content: Quote from: peter-h on May 25, 2021, 06:42:29 pmslightly divergent from the on-going discussion: I found a C compiler that had exactly that obscure bug, caused me much misery. The QNX (nee QUNIX) operating system originally had its own home grown C compiler before switching to Watcom C around 1990. When I confronted the QNX peeps over the phone about it the guy told me yeah sometimes if your math expression is very long or complicated he would spill a register into a temporary static variable. BUT THAT MAKES THE CODE NON RE-ENTRANT!. Pissed me off something fierce. It didn't seem to bother the QNX people since the compiler only did this rarely, and not all code in a multi-threaded system needs to be re-entrant. After that I always scanned the linker symbol tables looking for this generated symbol.

author: peter-h
author_rank: Super Contributor
timestamp: May 26, 2021, 06:45:38 am
content: That's horrible, but 1990 is early days and there were all kinds of weird compilers around then. My then business paid over £1000 for an IAR one (Z180) which was incredibly slow in some of the runtime functions.I am talking about current GNU tools. Surely they can't be doing that, 30 years later?The only code which I need to be re-entrant are the bits in the device access functions, up to where the mutex is obtained.Interestingly one also needs a mutex around any funcs which write GPIO pins - because those are read-modify-write operations; I am not sure if they are atomic at the low level. Example:

author: newbrain
author_rank: Super Contributor
timestamp: May 26, 2021, 07:22:53 am
content: Quote from: peter-h on May 26, 2021, 06:45:38 amThey are using the BSRR register, so they are fine without a mutex: it's a single atomic write and the rest of the wrapper function is safe too.EtA: The BSRR can be used to build a generic function able to set and reset an arbitrary number of pins in an atomic (interrupt and thread safe) way:Code:static inline void GpioWrite(GPIO_TypeDef* GPIOx, uint32_t mask, uint32_t value){GPIOx->BSRR = (mask << 16) | (mask & value);}This works, as setting bits takes priority on resetting them, according to the RMs for all families I used (but check with yours).