3
\$\begingroup\$

I have been developing a bare metal control application on dsPIC33EP256MC506. My application consists of infinite loop in the background and three foreground "application" interrupt service routines (isr). Besides those application isrs there is also let's say system isr which services the SPI end of transaction interrupt requests. In each SPI interrupt new status of the remote digital inputs (state of contactors) is being read. This information is then used for the calculations (logic expressions determining when to close or open individual contactors) done in the background loop.

My problem is that I am not sure how to ensure that during one pass through the background loop the state of the remote digital inputs will be consistent. Better saying I have been looking for a mechanism how to avoid following situation

enter image description here

Can anybody recommend me a simple and robust solution for this kind of problem?.

EDIT:

Below given answers inspired me to following possible solution. I will define SPI driver:

enter image description here

The update function will be executed in the background loop and it will do following

if(new_data_ready){
    new_data_ready = false;
    transferDataToMirror();
}
startTransaction();

The SPI end of transaction interrupt will be serviced in following simple manner:

new_data_ready = true;

The client's code from the SPI driver point of view will access to the digital_ inputs_mirror via getInputsState function call. The digital_ inputs_mirror will be updated in synchronous manner in the background loop via transferDataToMirror() which will retrieve data from the SPI peripheral registers.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ set a flag at the end of the SPI transaction to denote there is new data available. The background test can test this and do what is required. \$\endgroup\$
    – Kartman
    Commented Feb 5, 2021 at 11:58
  • \$\begingroup\$ Use "protected objects" if your language supports them. \$\endgroup\$
    – user16324
    Commented Feb 5, 2021 at 14:11
  • \$\begingroup\$ If your compiler doesn't come with instruction re-ordering (somewhat unlikely for bare metal), then you can use a simple bool flag. See this: electronics.stackexchange.com/a/409570/6102 \$\endgroup\$
    – Lundin
    Commented Feb 5, 2021 at 15:26
  • \$\begingroup\$ L3sek, this sounds an awful lot like most of what I've done for decades. I developed code for scientific and commercial instrumentation most of my life. Much of it "bare metal" and almost all of it where I wrote all of the O/S code, state machines, etc., as required. But there's insufficient information for me to get a clear bead on what you are doing, despite having written so much text above. Can you be more specific and list each and every one of your inputs, their purposes and estimated frequencies, processing and est. time needed, and outputs and purposes? Disclose all that you can? \$\endgroup\$
    – jonk
    Commented Feb 5, 2021 at 20:13
  • \$\begingroup\$ If you find yourself wanting to do threading, that's generally a sign that your project has outgrown the "bare metal" stage. \$\endgroup\$
    – Mark
    Commented Feb 5, 2021 at 21:28

2 Answers 2

4
\$\begingroup\$

Pretty simply: don't directly work on the data the ISR modifies. Instead, in an atomic operation, copy over the potentially volatile variables from the ISR-modified location to your loop state.

Alternatively, if there's more data than you can copy atomically, you'll need to teach your ISR how to write into a ring buffer, and your main loop how to read from one, so that you're never modifying a piece of data that's still being used.

Remark: What you do sounds like 100% an application of a RTOS. These very slim pieces of operating systems are available for your CPU, too (promise! If you can run compiled C on it, someone has ported a small RTOS to it), and you should be using them, exactly because task synchronization is hard and it's a good idea to not do task juggling yourself. It's not any less "bare metal" (you can still write nearly exactly the same code if you want), you just get primitives for executing tasks, exchanging data etc. I don't know your MCU, but look into ChibiOS (if you want something really small) or FreeRTOS (if you want something small with a large user base).

\$\endgroup\$
9
  • \$\begingroup\$ Well, he didn't say his target. However from about the Cortex-M3 up freertos is viable if you have the memory. Good luck with a PIC12 though :D \$\endgroup\$ Commented Feb 5, 2021 at 13:00
  • \$\begingroup\$ Huh, nothing wrong with using FreeRTOS on a cortex-m0. Yeah, smaller PICs will be challenging :) But, then again, all questions by this asker go through lengths to forget to mention the actual microcontroller family used, but just mention that it runs C, so one can't be sure. \$\endgroup\$ Commented Feb 5, 2021 at 13:26
  • \$\begingroup\$ @LorenzoMarcantonio by the way, you might really find ChibiOS/NIL refreshing if memory footprint is important to you :) \$\endgroup\$ Commented Feb 5, 2021 at 13:29
  • \$\begingroup\$ @LorenzoMarcantonio github.com/ChibiOS/ChibiOS/tree/master/demos/AVR/… (admittedly uses an AVR with 512 B of RAM instead of a PIC12 with 256 B) \$\endgroup\$ Commented Feb 5, 2021 at 13:36
  • \$\begingroup\$ @MarcusMüller thank you very much for your response. I have attempted to summarize how I have understood your idea in my original post. \$\endgroup\$
    – L3sek
    Commented Feb 5, 2021 at 15:05
3
\$\begingroup\$

There is a standard solution for this: the ISR always has priority over user code so the conflict can happen only in one direction. I use shadow variables so that the user copy is updated only when it's safe to do so. In pseudo-C:

volatile int io_status_isr;
int io_status;

void interrupt isr()
{
    /* acquire stuff from whatever */
    io_status_isr = stuff;
}

main()
{
    while (1) {
        disable_IRQ();
        io_status = io_status_isr;
        enable_IRQ();

        /* NEVER use io_status_isr here! only io_status */
    }
}

This is the basic pattern; you can avoid disabling the irq if your µc has atomic moves (few have) but usually copying a couple of variable doesn't hurt the realtime performance. Remember to use the volatile keyword to access variables used in ISR, otherwise the compiler could optimize stuff away!

\$\endgroup\$
9
  • \$\begingroup\$ I think these days, most MCUs have atomic moves (at least on a word level, and that's 32 bit for a lot of modern ones; cortex-M does specificially have "exclusive load/store" instructions, especially!) I'd say disabling ISRs is a relatively problematic solution (why use an ISR if it's not always allowed to interrupt? This can lead to dangerous conditions in control systems, but might be OK in soft-realtime systems like PC computers), and would be a last resort, imho. Especially since, as said, there's atomic moves on most platforms these days, so you'd only need that for larger pieces of data, \$\endgroup\$ Commented Feb 5, 2021 at 12:04
  • \$\begingroup\$ where copying actually takes some time, and thus you're disabling your IRQ for a relatively long duration. A lockless double buffered/ring buffered approach seems wiser here. (especially since double buffering is just "using the shadow state in a different manner", not requiring any additional resources) \$\endgroup\$ Commented Feb 5, 2021 at 12:07
  • \$\begingroup\$ He was asking for a 'simple' solution and no specific target environment. In hard realtime you should measure the actual irqless time to be sure. Also he's using a SPI which is a relatively slow peripheral but we don't know if it has a FIFO or DMA attached. For a word or two in my experience the shadow approach is fine 99% of the times. If he was, for example, generating µs pulses with irq driven timers I agree that disabling irqs wouldn't be a viable choice \$\endgroup\$ Commented Feb 5, 2021 at 12:19
  • 1
    \$\begingroup\$ I honestly think we're saying the same thing here! the atomic move only makes sense when you need to move a single word; if you need to do more than that, double buffering works fine without (as long as memory ordering is intact, but I wouldn't know a single MCU core that wouldn't guarantee that) \$\endgroup\$ Commented Feb 5, 2021 at 13:39
  • 1
    \$\begingroup\$ Notably you should never disable the global interrupt mask for this, but rather the specific hardware peripheral interrupt, in this case an SPI one. \$\endgroup\$
    – Lundin
    Commented Feb 5, 2021 at 15:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.