2024-01-23 09:56 AM
Hopefully this is more of a sensible non-noob question, it relates to interrupt handling, in my case a simple USART receive interrupt.
I can see how the CubeMX tool is managing the .c file, its created an interrupt handler, for me to insert my code, thats easy. I can detect the interrupt and spit out a short string in response, so its working - all good.
The question I have is, what can I do inside that ISR safely. Do I need to preserve any registers. My aim is to throw anything I receive into a ring buffer of some form, with a view to building a received packet of data. At some point I will need to pick up each completed packet from a list. In order to do that I will possibly need to allocate some memory, and it would be useful if I do not have to put the whole implementation of that ISR in the file that is being auto-generated, so would be handy to also be able to call a function where I can contain the implementation of the ISR in its own compilation unit.
One other question is that of controlling interrupts. At some point, I would have a packet of data in a buffer somewhere that I would access from the main loop of the code. In order for me to safely access that packet from the receive buffer, I would need to first, stop/pause/prevent the next interrupt from interrupting me. On x86 architecture this is quite tricky as you have to rely on the atomic behaviour of specific instructions, and prevent the interrupt in a way that, should an interrupt occur while getting the data safely from the buffer, the interrupt controller will hold/queue that interrupt until the interrupts are re-enabled.
What is the right semantics for doing this on the STM32 platform?
As ever, any help at all much appreciated.
Gerry
2024-01-24 10:42 AM
> Can I ask how you located that document, so I can try and find the same one for the chip I am using please?
Personally, I just google "stm32f429 reference manual" and it typically comes up.
It should also be listed on the part page. Navigate to Documentation and then to Reference Manuals
https://www.st.com/en/microcontrollers-microprocessors/stm32f429zi.html#
> I don't suppose you know where I might get an example of how do enable interrupts and implement the ISR without the HAL?
This one is linked quite a bit, but uses DMA.
https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx
I don't know of a non-DMA based one, but I'm sure they are out there. If I find one later I'll link it here.
2024-01-24 12:46 PM
My app is bare metal, no RTOS, no threads and I'm trying to stay away from locks, mutexes, etc. It was googling for thread safe and non locking FIFO queues that helped me figure out how to write a bare metal queue without threads or mutexes or overly complicated synchronization code that would works for my simple single producer (the ISR), single consumer (the main app) application.
I went through the same steep learning curve you're on. In fact, I'm still on it. But the more I read the reference manual for my MCU and the more actual coding and testing I do, and with help from this forum, the simpler my code gets. Hopefully, it's more robust also. Having said that ,there's always some simple gotcha that takes me days to figure out but that's just part of the process.
2024-01-24 04:57 PM
Yeah understood, this forum has really helped. The reference manual is a revelation! So pleanty of reading and experimenting to do.
Having been through the various iterations, clearly I can see why people stay away from the HAL, at least for the UART stuff, the LL (low level I am guessing) drivers seem to be a good place to start.
I have managed to remove the HAL stuff and now just using the LL helpers and some direct register access, I can now receive characters properly and populate a buffer, on \n I can transfer the contents of that buffer to a second buffer and flag that to the main processing loop, it can then dump to console, so that at least is working.
I am still struggling with the IDLE interrupt, I only get one of those every other character I receive. No idea why that is, but I am wondering if its because of the relatively high speed I am running UART3 at (115200). Need to try and slow it down and see what happens.
In any case, progress of sorts, but boy its painful...
Gerry
2024-01-24 07:42 PM - edited 2024-01-24 07:42 PM
Take it for what it's worth, but I would strongly suggest either using HAL or using direct register access, but not using LL. The LL (low layer) library makes everything more difficult and obfuscated. Not only do you need to know what is happening and what needs to happen at the register level, you also have to understand terms that the LL library devs made up. I'm sure it was well intentioned, but it comes off as a useless obfuscation of register access.
Register-level coding using the CMSIS defines is quite clean and readable. And it will still be readable and usable 20 years from now because the register names and access will never change. (HAL and LL are likely to change and require updates in the future.)
Anyhow, good luck. The STM32 hardware is quite powerful.
2024-01-24 07:58 PM
Fair enough, that seems like reasonable advice. One of the tihngs I noticed about the LL interface is, it abstracts away the lower level differences between parts. For example in my case, clearing/acknowledging the receipt of the IDLE interrupt involves reading the ST register followed by the RD register. on the ARM7 parts, you have to clear a specific flag, so it would seem that there is some level of code portability to be gained by using those abstractions.
But for learning, and translating what the ref guide says, probably direct register access will be the easiest to understand.
Gerry
2024-01-29 05:59 AM
Just wanted to post an update, I finally got this working, in the end I ended up using HAL DMA for reading and writing, I implemented is (really really) simple slot based circular buffer, sends and receives are on a message by message basis, I got everything I needed to finally understand how the HAL likes to work for DMA based sends and receives from this video. As with all these things, its pretty simple when you know how.
There is still problem that accessing the circular buffer would not pass the basic sniff test in a multi-threaded environment, there is, as far as I can perceive still edge cases that could go wrong when accessing the variables that control the cirular buffer head/teal, if interrupted mid-stream. In practice though, so long as your reader process is able to to read and process content out of the buffer, faster than your incoming data stream can out data in, then you can basically ignore that possibility. I have yet to have a good sense of the performance of the microcontroller because I am not yet able to measure it (my Agilent digital scope let out the magic smoke a year or so ago, still on the list to fix), but my incoming data stream is only at 9600baud, I am simple ceiving a messge, doing a crc check, wrapping in a header and second CRC and sending out over the USB serial at 115200baud so my ring buffer is barely exercised, the MCU has pleanty of horse power.
Anyway, please see link to video. If you want to read serial data with IDLE detection this is how you do it.
Thanks to everyone who answered my questions on this thread.
https://www.youtube.com/watch?v=RpDOHqoVNTs
2024-01-30 09:36 AM
As long as there is only 1 "writer" and 1 "reader", and if the IRQ only modifies the "head" pointer (writing data), and the non-IRQ code only modifies the "tail" pointer (reading data) there should be no race conditions or edge cases and it should be RTOS compatible. The non-IRQ code needs to only update the "tail" pointer when it is done - no intermediate values. Do all manipulations in a temporary copy then write when done. Tilen Majerle has an example on his github site https://github.com/MaJerle/lwrb
2024-01-30 04:32 PM
Hi Bob,
I understand the microcontroller is basically single threaded, and an interrupt is simply a suspension of what is currently processing and a crude context switch (i.e call an ISR function), I get that, but the point I am making above is, your C code generally translates to a larger number of machine code instructions. Forgive the fact this example is in x86 code (same thing applies), that is a simple function to get the next slot in a simple 16-slot ring buffer. The vast majority of these instructions are NOT atomic, and certainly the registers can easily be re-used. So if you take that example code, it could very easily be that in your main loop, you are executing this code, and the line where you are setting eax register to the value contained in the memory variable MainBuf_tail. An intterupt arrives so your program stops immediately after that instruction line, and the CPU calls your ISR. In your ISR code you do some other work that involves using the eax register, so its value is changed. Upon the ISR completion, your codo resumes at the next instruction in the code below where ecx is being set. The line after that compares ecx with eax, but eax now has a different value in it then it did before the ISR was executed. At that point, at best this code will give the wrong result, at worst it could crash.
So it does depend on the CPU, but as a general rule, updating a pointer (which is in a ram memory location) will almost always have temporary variables, if of those are just registers like shown in this example.
All CPU's that are thread capable, or multi-code, have some very specific and well documented instructions that are gaurunteed to be atomic, basically uninterruptible, these are typically register-to-register value exchange or register -memory-to-register operations. On CPU architectures that are not inherently design with multi-core in mind, will have some well defined way of safely executing ISR's, generally this means the ISR should save all and any registers its going to mess with, typically on the stack, and then when complete restores the register values before returning from the ISR.
I was asking about the ARM architechture because I do not know what is recommended, and in C, with quite a large number of registers at the disposal of libraries and so on, its not clear what registers should be saved/restored on entry/exit of the ISR.
Of course, people say, do as little as possible in the ISR, that makes timings better but also reduces the risk of the type of problem I mention above, but thats relying on hope rather than absolutes, which I would never do when programming for any professional development project.
When I figure out how to drive the debugger properly, for my application I will do it by reviewing the disassembly, work out what registers are changed and save/restore these.
For some background, back in the DOS/Early Windows era I developed a lot of pre-emptive background processes on x86, and that was a good time to learn that if I wanted my programs to not crash and be 100% reliable, even when threading was not a thing in DOS, interrupts on timers, serial and network events very much were, so you really had to pay attention to the state the CPU was in at all time.
It would seem reasonable to assume that the ARM core does not have some magical "save and restore all registers automatically when entering an ISR and restore them all back on exit"... I mean it might, and would be nice, but I have not found any documentation either way, which is why I was asking the question.
Hope that makes some sense.
2024-01-30 04:41 PM
On further update, reading another article, its suggesting the ARM cortex does not its self save anything, appart from the stack and frame and instruction pointer registers, it appears to be left upto the C compiler to implement the prolog/epilog to save/restore registers. The ABI used by the compiler seems to dictate which registers are/are not saved/restored. So the answer would appear to be - consult the "C" compiler...
Fair enough...
2024-01-30 06:29 PM - edited 2024-01-30 07:16 PM
> It would seem reasonable to assume that the ARM core does not have some magical "save and restore all registers automatically when entering an ISR and restore them all back on exit"
The Cortex-M architecture with its NVIC interrupt controller is relatively new. They took lessons from most things you've mentioned and the result is very efficient and elegant. Yes, it saves and restores the important registers automatically. In short, interrupt handling works mostly intuitively. It does what you want for small embedded systems, and in the way you want it. Yes there is an ABI (few variants for different FPUs), provided by ARM.
> I have not found any documentation either way, which is why I was asking the question
Come on, Mr. gerrysweeney. The documentation is abound, and now the chatgpt.
Please use your time, read the Programmer's manuals, play with the code in debugger. There are some books, but who reads (and writes) books these days. Enjoy the simplicity, power and elegance of Cortex-M before it got messed with ugly complications - TZ, security and so on ))
Also, interesting reading is on memory accesses ordering, memory barriers, "atomic" support in C and C++ (stdatomic.h and std::atomic). And the use of "volatile" /* old folks who believe they knew all about volatile, are up to surprise!*/