Author Topic: non static C++ class objects with interrupt routines  (Read 3193 times)

0 Members and 5 Guests are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
non static C++ class objects with interrupt routines
« on: May 29, 2026, 08:03:49 am »
I have tried to created my classes so that they are created statically (outside of main()) but where this involves hardware it does mean that I can't do anything at instantiation and need to then run an init method of the class at startup to set things up where the registers need accessing.

But this is a pain and sometimes I just want to instantiate in main so that the hardware can be setup and not have to write everything out twice.

So I thought well I'll just do that, then it hits me, if I instantiate in main then no interrupt handler can see the interrupt method of a class. So for example an encoder for user machination of the UI relies on interrupts, and I took it one step further and put UI management stuff in there to make sure the UI is responsive.

So now what?

Well I had a swell idea, what if I create a statically defined pointer for each interrupt routine to the function I want to run put into it, this pointer can then be called in the interrupt routine so that the class method is called.

will this work? is it nuts? or even how everyone has been doing it for ages anyhow?
 

Offline gerbay

  • Regular Contributor
  • *
  • Posts: 84
  • Country: tr
Re: non static C++ class objects with interrupt routines
« Reply #1 on: May 29, 2026, 10:35:58 am »
This is a commonly used method. Similar methods are used to point "threads" to class instances. Essentially, you pass the `this` pointer and call your dynamic method through it.
 

Offline m k

  • Super Contributor
  • ***
  • Posts: 3407
  • Country: fi
Re: non static C++ class objects with interrupt routines
« Reply #2 on: May 29, 2026, 12:41:35 pm »
How you connect to old and already there dynamic interrupt routine chain.
You ask where it is.

How your program receive mouse movements.
It connects to mouse movement event chain.
Advance-Aneng-Appa-AVO-Beckman-Danbridge-Data Precision-Data Tech-Fluke-General Radio-H. W. Sullivan-Heathkit-HP-Kaise-Kyoritsu-Leeds & Northrup-Mastech-OR-X-REO-Schneider-Simpson-Sinclair-Tektronix-Tokyo Rikosha-Topward-Triplett-Tritron-YFE
(plus work shop of the world unknowns)
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1049
Re: non static C++ class objects with interrupt routines
« Reply #3 on: May 29, 2026, 05:33:10 pm »
One example-
https://godbolt.org/z/3zrxfbs7o
edit- the 3 relevant files are example.cpp (main), Cortex-M0plus.hpp (Nvic), and I2c.hpp (constructor sets irq function).

The CPU::Nvic class can set an interrupt function, which either puts a static function address directly into the ram vector table, or for a class pointer (Isr class with virtual isr function) it places the Isr* into a table and puts a static 'dispatch' function address into the vector table. When an irq fires- if the class isr function was static it is directly called, when an interrupt fires for a class which needs the class object the CPU::Nvic::dispatch function is called which figures out the current irq number, looks up the Isr* in the table and calls Isr->isr.

This  method allows you to simply provide the CPU::Nvic::setFunction with a function to use for an irq (either static or class, setFunction is overloaded so same function usage for either). The above example has i2c in use where all the details are taken care of with a single object definition. There are other ways to handle this problem, but this is 'cleaner' to use as you do not have to come up with object names and/or possibly keep track of object pointers in the class for a class common static isr function.

Quote
Well I had a swell idea, what if I create a statically defined pointer for each interrupt routine to the function I want to run put into it, this pointer can then be called in the interrupt routine so that the class method is called.
Remember there is an unseen 'argument' when calling a (non-static) class method, which is why there is an abstract Isr class in use for the above example. The example also uses a ram vector table, filled at runtime, but if a flash vector table is in use then one option is to come up with object names at compile time and can do something such as this-

Code: [Select]
//....somewhere
class Some { public: void isr(){ /* isr code */ } };
extern Some some; //we need a name to use in the handler code (object declared here, defined somewhere else, name must match)
extern "C" void SOME_HANDLER(){ some.isr(); } //override a C predefined weak handler name, call the class method

//....somewhere else
Some some; //object definition here
int main(){
    while(1){}
    }

Or... you could also do something similar to the linked example using the flash vector table, but you do need an abstract class

Code: [Select]
class Isr {
    public:
    virtual void isr() = 0;
    };               

Isr* handlers[32];

class Some : Isr {
    virtual void isr(){ /* code */ }
    public:
Some(u8 handle){ handlers[handle] = this; }  //not a great idea using the u8 handle here, but this is just an example to specify which table entry needed for this instance 
};

//overriding each weak C handler function (vector table in flash, and only 2 shown here)
extern "C" void NAMED_HANDLER_16(){ if( handlers[16] ) handlers[16]->isr(); }
extern "C" void NAMED_HANDLER_31(){ if( handlers[31] ) handlers[31]->isr(); }

//2 instances, different peripheral/vector table entry
Some some1{ 16 };
Some some2{ 31 };

But, now what to do with classes with static functions. They cannot inherit/use the abstract Isr class, which is why I ended up with my linked example which allows for either type when using the ram vector table. Also note that a lambda doesn't work for a void(*)() function pointer as you would need to capture the 'this' to call the class method, and there is no conversion from a captured lambda to a basic function pointer (later C++ standards may have something available to get that job done, but most likely not lightweight).
« Last Edit: May 31, 2026, 12:20:53 pm by cv007 »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #4 on: June 01, 2026, 11:08:18 am »
I thought about going directly to the interrupt table but that will just be messy and I am not prepared for that level of tinkering yet.

I am creating pointers of the class type. The initializer function for the class assigns assigns the object address (this) to the pointer. then in each interrupt routine I place the call through the pointer.
 

Offline Tation

  • Frequent Contributor
  • **
  • Posts: 275
  • Country: pt
Re: non static C++ class objects with interrupt routines
« Reply #5 on: June 01, 2026, 02:25:48 pm »
Maybe this can be of help:
Code: [Select]
#include <functional>
#include <cstdio>

template<class T>
std::function<void(void)> callback(T* object, void (T::*method)(void)) {
  // build and return a lambda that executes the bounded method
  return [=]{ (object->*method)(); };
}

class Peripheral {
public:
    int n;
    Peripheral (int n) : n(n) {};
    void inc (void) { ++n; };
};

int main (void) {
    Peripheral the_peripheral{5};

    std::function<void(void)> the_isr;
   
    the_isr = callback(&the_peripheral, &Peripheral::inc);

    the_isr();  // calls a bounded method

    printf("%i\n", the_peripheral.n); // prints 6
}

Here template function callback() creates a function from method inc() of object the_peripheral of class Peripheral, such function is stored into variable the_isr of type std::function<void(void)>. Then such variable is "called", updating the original object the_peripheral.

With this mechanism, you can use as the ISR a function that, simply, executes the_isr(). The ISR does not change, the vector does not change, the_isr does. There is overhead reading variable the_isr from the ISR and calling it, but I have found it to be a single memory read (or two) and a branch (on ARM with the ARM compiler) when you enable enough compiler optimizations.

Note that here the_isr is a local variable, but it can be an array, it may be internal to the class, it may be a member of another class managing interrupts system wide,… there are many options.

With this approach I have implemented classes to manage GPIO (each object is a single pin, and you can define an "ISR" to be called for every single pin, code manages transparently pin number and port), software timers, serial and I2C.

With little modifications you can also pass parameters to the_isr().

Also note that you can store in the_isr normal (unbounded) functions not needing pointers:
Code: [Select]
void my_function (void) {
...
}

...

    the_isr = my_function;
« Last Edit: June 01, 2026, 03:32:00 pm by Tation »
 
The following users thanked this post: Dazed_N_Confused

Offline Pseudobyte

  • Frequent Contributor
  • **
  • Posts: 354
  • Country: us
  • Embedded Systems Engineer / PCB Designer
Re: non static C++ class objects with interrupt routines
« Reply #6 on: June 01, 2026, 07:02:07 pm »
I will offer up another method for accomplishing this:

Simple run time ISR registration + static object entry functions

Run time ISR registration:

Code: [Select]
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

typedef enum {
ISR_DMA1_STREAM1,
ISR_DMA1_STREAM2,
ISR_DMA1_STREAM3,
ISR_DMA1_STREAM4,
ISR_DMA1_STREAM5,
ISR_USART1,
ISR_USART2,
ISR_USART3,
ISR_RTC_ALARM,
ISR_RTC_WKUP,
ISR_DMA2_STREAM0,
ISR_DMA2_STREAM1,
ISR_DMA2_STREAM2,
ISR_DMA2_STREAM3,
ISR_USART6,
ISR_SPI5,
        __ISR_MAX__
} ISR_t;

    typedef struct {
        void(*callback)(void*);
        void *args;
    } ISR_callback_t;

    void isr_init_callbacks(void);
void isr_callback_register(ISR_t isr, void(*callback)(void *), void* args);

#ifdef __cplusplus
}
#endif

Code: [Select]
#include "isr.h"

#define NULL 0

void __attribute__((naked, noreturn)) default_callback(void* args)
{
for (;;) ;
}

static ISR_callback_t s_callbacks[__ISR_MAX__];


void isr_init_callbacks(void)
{
    for (int i = 0; i < __ISR_MAX__; ++i) {
        s_callbacks[i].callback = default_callback;
        s_callbacks[i].args = nullptr;
    }
}


void isr_callback_register(ISR_t isr, void(*callback)(void*), void *args)
{
if (isr < __ISR_MAX__)
{
s_callbacks[isr].callback = callback;
s_callbacks[isr].args = args;
}
}

static void isr_cb_handler(ISR_t isr)
{
if (isr < __ISR_MAX__) {
        if (s_callbacks[isr].callback) {
            s_callbacks[isr].callback(s_callbacks[isr].args);
        }
    }
}

void DMA1_Stream1_IRQHandler()
{
isr_cb_handler(ISR_DMA1_STREAM1);
}

void DMA1_Stream2_IRQHandler()
{
isr_cb_handler(ISR_DMA1_STREAM2);
}

void DMA1_Stream3_IRQHandler()
{
isr_cb_handler(ISR_DMA1_STREAM3);
}

void DMA1_Stream4_IRQHandler()
{
isr_cb_handler(ISR_DMA1_STREAM4);
}

void DMA1_Stream5_IRQHandler()
{
isr_cb_handler(ISR_DMA1_STREAM5);
}

void DMA2_Stream0_IRQHandler()
{
isr_cb_handler(ISR_DMA2_STREAM0);
}

void DMA2_Stream1_IRQHandler()
{
isr_cb_handler(ISR_DMA2_STREAM1);
}

void DMA2_Stream2_IRQHandler()
{
isr_cb_handler(ISR_DMA2_STREAM2);
}

void DMA2_Stream3_IRQHandler()
{
isr_cb_handler(ISR_DMA2_STREAM3);
}

void USART1_IRQHandler()
{
isr_cb_handler(ISR_USART1);
}

void USART2_IRQHandler()
{
isr_cb_handler(ISR_USART2);
}

void USART3_IRQHandler()
{
isr_cb_handler(ISR_USART3);
}

void USART6_IRQHandler()
{
isr_cb_handler(ISR_USART6);
}

void SPI5_IRQHandler()
{
isr_cb_handler(ISR_SPI5);
}

void RTC_Alarm_IRQHandler()
{
isr_cb_handler(ISR_RTC_ALARM);
}

void RTC_WKUP_IRQHandler()
{
isr_cb_handler(ISR_RTC_WKUP);
}

Object Entry + Init:

Code: [Select]
#include "isr.h"

class Test
{
public:
    Test()
    {
        isr_callback_register(ISR_USART1, uart_isr_handler_entry, this);
    }
   
private:
    void uart_isr_handler();

    static void uart_isr_handler_entry(void *args)
    {
        if (args) {
            static_cast<Test*>(args)->uart_isr_handler();
        }
    }
};


“They Don’t Think It Be Like It Is, But It Do”
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1049
Re: non static C++ class objects with interrupt routines
« Reply #7 on: June 02, 2026, 03:01:59 am »
Here is a simplified comparison between using the std::function and using an abstract Isr class-
https://godbolt.org/z/oaEK73Gac
(change the define in line 24 to select)

I modified the std::function example posted by Tation to match the abstract Isr class example. Both are using the Default_Handler which would not require a ram vector table. All irq's would use the Default_Handler so all would lookup a function to call based on the active irq.

I'm not sure what std::function is doing (I have not used before), but it does appear to be more code than the abstract Isr class version (233 asm lines vs 101 asm lines, but may be misleading). Getting/storing/using the captured value in a lambda takes some work/code so not unexpected.

In either case you can get the constructor to setup everything so you can have a single object definition and all is taken care of. My first linked example is more complete, where priorities can be set, nvic irq enabled, etc., and also allows for direct (ram) vector table entries for anything that does not need a class object.

A class could be all static but still have the virtual isr function non-static (so can inherit the abstract Isr class) which would eliminate the need for the ram vector table as all irq's would then use the Isr* array (although a little slower). I think that would be ok if everything else in the class was static.

I also deal with callbacks as a separate thing- if a class needs to use callbacks it can have a function to set them and let the isr code deal with them as needed. This way you are not left without an irq handler for the class as it will always have one-
void isr(){ ... if( callback_) callback_(); ... }
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #8 on: June 03, 2026, 02:20:32 pm »
So I've started creating a static class type pointer that is given the address of the object (this) when it is constructed. Well it seems to work but now that I have things constructed in main when I hit a hard fault I can't access the data with a j-link debugger.
 

Offline m k

  • Super Contributor
  • ***
  • Posts: 3407
  • Country: fi
Re: non static C++ class objects with interrupt routines
« Reply #9 on: June 03, 2026, 03:13:36 pm »
Is it a global pointer?
Advance-Aneng-Appa-AVO-Beckman-Danbridge-Data Precision-Data Tech-Fluke-General Radio-H. W. Sullivan-Heathkit-HP-Kaise-Kyoritsu-Leeds & Northrup-Mastech-OR-X-REO-Schneider-Simpson-Sinclair-Tektronix-Tokyo Rikosha-Topward-Triplett-Tritron-YFE
(plus work shop of the world unknowns)
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1049
Re: non static C++ class objects with interrupt routines
« Reply #10 on: June 03, 2026, 04:59:51 pm »
Assuming 'in main' means inside the main() function- I don't use the debugger, but not difficult to imagine it will have a hard time figuring out a class that is on the stack. There are times to make a class live on the stack for temporary/optimizing purposes, but if you start putting them in main it gets hard to understand the generated asm code when there is a need to figure out what is going on. You also start using stack space where you begin to lose the overall picture of ram usage as it is unaccounted for with compile time stats.
 

Offline m k

  • Super Contributor
  • ***
  • Posts: 3407
  • Country: fi
Re: non static C++ class objects with interrupt routines
« Reply #11 on: June 03, 2026, 08:14:10 pm »
How to access a shared interrupt.

Exclusive is simple, there's an interrupt vector and that's it, case closed.

Shared one is not much more complex, the same vector is still there and does the same operation.
Sharing happens so that original vector address becomes an exit jump of a new interrupt routine and actual interrupt vector changes to entry point of the new interrupt routine, so finally all interrupt routines are chained and original is the last one.

Sharing also means that there must be extra info of what interrupt is requested, if not then all possibilities must be scanned every time.
It's also possible that many interrupts are pending, then there must be a way to say when all of them are served.

Next level could be a table for active services and their priorities.
Final level could be how to keep all of that fast enough for smooth operation.

Handling complex stuff that includes interrupts is not easy.
Back in the day of DOS it wasn't in anyway clear that DOS is not re-entrant, means that next DOS call is illegal if previous one is still active, the system worked because software was not multitasking.
Now there are things like thread safe, you are not allowed to do all things all the time, or maybe you are, but result is unknown.

Priorities may be also problematic, there's no point in sorting them all the time.
One way is to have an initial priority and the current one.
Every round decreases all current priorities and only lowest ones are served, here low value is high priority.
After priority is served its current value is changed to initial value, that way all are finally served.
Advance-Aneng-Appa-AVO-Beckman-Danbridge-Data Precision-Data Tech-Fluke-General Radio-H. W. Sullivan-Heathkit-HP-Kaise-Kyoritsu-Leeds & Northrup-Mastech-OR-X-REO-Schneider-Simpson-Sinclair-Tektronix-Tokyo Rikosha-Topward-Triplett-Tritron-YFE
(plus work shop of the world unknowns)
 

Offline julian1

  • Frequent Contributor
  • **
  • Posts: 881
  • Country: au
Re: non static C++ class objects with interrupt routines
« Reply #12 on: June 03, 2026, 09:55:04 pm »
The difficulty in c++ is all the variations in the stack setup necessary to call a method - versus using C when one can just pass around a function and void *ctx context pointer.  This is because each call type is different - depending on whether it is a static method/class, a normal method, a virtual method, or virtual function implementing a prototype from multiple inherited base class instances.

boost::function now stl::function is the standard way to elide over all the possible call variations.
https://www.boost.org/doc/libs/latest/doc/html/function.html

But it is kind of a big heavy complicated thing, so it is also common to just use a static class instance, or else some variation with a static C-like function to handle the dispatch.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #13 on: June 04, 2026, 07:49:25 am »
Assuming 'in main' means inside the main() function- I don't use the debugger, but not difficult to imagine it will have a hard time figuring out a class that is on the stack. There are times to make a class live on the stack for temporary/optimizing purposes, but if you start putting them in main it gets hard to understand the generated asm code when there is a need to figure out what is going on. You also start using stack space where you begin to lose the overall picture of ram usage as it is unaccounted for with compile time stats.

so basically I should go back to statically creating class objects and in the case of peripherals initializing them in main so that registers can be updated which they won't on a static allocation?
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: de
Re: non static C++ class objects with interrupt routines
« Reply #14 on: June 04, 2026, 09:22:09 am »
Assuming 'in main' means inside the main() function- I don't use the debugger, but not difficult to imagine it will have a hard time figuring out a class that is on the stack. There are times to make a class live on the stack for temporary/optimizing purposes, but if you start putting them in main it gets hard to understand the generated asm code when there is a need to figure out what is going on. You also start using stack space where you begin to lose the overall picture of ram usage as it is unaccounted for with compile time stats.

so basically I should go back to statically creating class objects and in the case of peripherals initializing them in main so that registers can be updated which they won't on a static allocation?

An object defined inside main() does not need to live on the stack if you make it  static.
By doing so, the constructor also won't run before main() is invoked (which seems to be what you want).
But of course, it is by nature still not visible outside main().

Code: [Select]
class C {
    ...
};

int main() {
    static C c;
    ...
}
« Last Edit: June 04, 2026, 09:23:51 am by gf »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #15 on: June 04, 2026, 10:20:05 am »
I think I'll go back to blank constructors and an init function
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4495
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: non static C++ class objects with interrupt routines
« Reply #16 on: June 04, 2026, 10:33:07 am »
I don't understand, this smells like a classis observer pattern?
https://refactoring.guru/design-patterns/observer

During your constructor your subscribe your class callback to an interrupt manager class.
You can make the interrupt manager class a singleton.

This is literally what the NVIC does in hardware, call the function written in the subscriber table :)
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #17 on: June 04, 2026, 11:22:03 am »
Yea that bit is not a problem, it's the whole non static classes that is an issue. I seem to have a problem with the code somewhere and it is easier to work through as static objects.

I had a whole stack up of classes inheriting each other - marvelous, made life so "easy", now a minefield to debug maybe? so instead I am creating each object statically, running it's init function in main which passes pointers to other class objects needed.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: de
Re: non static C++ class objects with interrupt routines
« Reply #18 on: June 04, 2026, 11:52:03 am »
so instead I am creating each object statically, running it's init function in main which passes pointers to other class objects needed.

"Statically" can still be either
a) outside a function (-> constructor runs before main() is invoked)
b) inside a function (-> constructor runs when the function is called for the first time)

EDIT: Static member variabes of a class behave like (a)
« Last Edit: June 04, 2026, 11:55:36 am by gf »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18835
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: non static C++ class objects with interrupt routines
« Reply #19 on: June 04, 2026, 01:30:14 pm »
so how is b) achieved?

I've now stopped my program from crashing it was an error in my code.
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: de
Re: non static C++ class objects with interrupt routines
« Reply #20 on: June 04, 2026, 03:24:08 pm »
so how is b) achieved?

Just define it static inside a function.

Code: [Select]
class C {
    ...
};

int main() {
    static C c;
    ...
}
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17529
  • Country: fr
Re: non static C++ class objects with interrupt routines
« Reply #21 on: June 04, 2026, 05:50:05 pm »
I didn't get for certain how you usually declare your objects. Do you mean you declare them in main() as local variables? (Which would be consistent with what you said otherwise)
If so, the inconvenince you noticed is not the only problem: they are also allocated on the stack, which is not necessarily what you want, or ideal. One problem with that is that you don't know how much RAM they take up at compile time. Another is that the risk of overflow if you also allocate stuff dynamically (on the heap) increases.

Statically allocating objects in your case is usually much saner. Now if you rely on constructors for initializing more than just memory (members), for instance, for setting up peripherals, the issue with static objects is that you basically have no control over the order in which they are created, meaning the order in which the constructors will be called. Constructors of statically allocated object are called before main() is entered, but the order is not defined. You can mitigate it by including child objects as members of a parent object, so that the order of constructor calls can be controlled, but that can become nasty.

That's why an init() method, called explicitely, when you have to initialize hardware resources is the easiest way to control that.

If you still want the benefit of both appraoches, you can use placement new, which allows both to control when constructors are called and where the actual object is placed in memory, which is particularly handy if you have to deal with RAM split into several regions (sections) and control in which section you want a given object to sit. You can begin here for the basics: https://www.geeksforgeeks.org/cpp/placement-new-operator-cpp/


 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1049
Re: non static C++ class objects with interrupt routines
« Reply #22 on: June 04, 2026, 07:48:08 pm »
I am a little lost on what your problem is. Provide a simple/minimal example for us such as-
https://godbolt.org/z/xqMaeK87o
Showing examples of code will most likely be better than you trying to describe it all in words.


Quote
Just define it static inside a function.
You will also start to get guard_aquire/release code, which in main would be a one time thing and ultimately unnecessary there since no return from main, but harmless (but also extra generated code to stumble over when trying to figure things out).

Quote
Constructors of statically allocated object are called before main() is entered, but the order is not defined
For each source file, they are done in the order of creation, so for example from my first linked example-

                //i2c on A9/A10
                I2c i2c{ MCU::I2c1_A9A10 };
                //STS35 using i2c
                STS35 sts35{ i2c };

the i2c object constructor (global) will be run before the sts35 object constructor (global) is run. The order could change if the following is done, but this is your own doing-

                extern I2c i2c;
                //STS35 using i2c
                STS35 sts35{ i2c };
                //i2c on A9/A10
                I2c i2c{ MCU::I2c1_A9A10 };
 
and the danger here is that sts35 is going to be used before i2c is constructed. But in this case since sts35 is not using i2c until one of its methods is explicitly called this is fine (it only needs the address of the I2c object, which is available before i2c is constructed). If you wanted sts35 to actually use i2c in the constructor you simply have to put the i2c creation before sts35, but in this case since this i2c uses irq's, I simply would not do it as I would not want any irq code running in the global constructors (I disable irq's in startup code so that no irq can run while doing startup init, so if I do something like setup a timer with irq that fires sooner than I expect, it will have to wait until startup init is done).

I don't find it that hard to think about what a constructor does, and what its dependencies may be. I also use header-only for classes so all my class objects are defined in the main source file where I can also control object creation order if/when needed. My first linked example has one source file, and all the rest is headers (in use, there is also a startup.cpp source file, not shown).

Quote
That's why an init() method, called explicitely, when you have to initialize hardware resources is the easiest way to control that.
Nothing wrong with that approach, but it kind of leaves a bit of the C++ advantage unused. Just a little bit of thought is required to make sure you are not getting into trouble. It is quite nice to be able to have a single object definition take care of everything and not have to split up initialization into separate pieces of code. The first linked example has led, sw, uart, i2c, sts35, systick all done with a single line each, all are setup in global constructors including the use of irq's for i2c. I cannot forget to do any more required init as its all done. I have no need to deal with setting up pins for uart/i2c as enough info is provided so it is handled in the constructor. If I want to change the i2c pins used or peripheral instance it is a matter of changing the single object definition line as needed and let the constructor deal with the details (which peripheral instance, which pins, which irq is used, etc.).
 

Offline gf

  • Super Contributor
  • ***
  • Posts: 1799
  • Country: de
Re: non static C++ class objects with interrupt routines
« Reply #23 on: June 04, 2026, 07:59:06 pm »
they are also allocated on the stack

Local (block-scope) variables can still have static storage class; then they are not allocated on the stack.

Quote
Constructors of statically allocated object are called before main() is entered

This applies to global variables or static member variables of a class.

However, local (block-scope) static variables are are dynamically initialized the very first time execution control passes through their declaration--so you can control their initialization order.
[ If the control flow never reaches a specific static local declaration, its constructor is never called. ]
 

Offline cv007

  • Super Contributor
  • ***
  • Posts: 1049
Re: non static C++ class objects with interrupt routines
« Reply #24 on: June 05, 2026, 03:24:32 am »
A little aside with initialization- I allow for the use of method chaining in my classes, if it makes sense or could make sense to use (no space consumed if not used, so essentially free to implement). In those cases the constructor may not be designed to setup everything since there would be many ways to setup the peripheral. But it would still be nice to init the peripheral where the class object was created with no need to do init later inside a function such as main.

As a very simple example, the GpioPin class in the first linked example will setup a pin determined by the constructor arguments, and if it is an output the pin will be set to 'off' before the output mode is set ('off' depends on invert- if LOWISON was specified then off becomes high), so that an output pin by default is off (whatever that may be). But, lets say I have an led and I would like it to be on before main with no need to explicitly turn it on inside main. Could add more constructor arguments to set initial state, or could use the method chaining already in place-

                //pin A5, output, high is on (default)
                GpioPin led{ led.PA5, led.OUTPUT }; //led is all setup, but is also off

I would like to use method chaining to also set the led on, so do this instead-

                auto led = GpioPin( led.PA5, led.OUTPUT ).on(); //on() returns *this, so led is assigned to the GpioPin object

looks ok, and may appear to work (led will be on), but that led GpioPin object was on the stack and at the end of the global constructors that stack space was recovered so that led GpioPin object no longer exists, not good (led reference now refers to somewhere on the stack which no longer is valid).

But this can be done-

                GpioPin led{ led.PA5, led.OUTPUT }; //pin is setup
                static auto& unused_ = led.on(); //(name unimportant as we will not use it)

since order creation is maintained we have the led setup before we use one of its methods and since the static reference takes no space it is zero cost and we get the peripheral setup with two lines that live next to each other, and is all done in the the global constructor code.

A better example would be the Tim67 class where one can do something similar by using its various methods-period/prescale/periodHz/on/irqOn/callback to get the peripheral setup as required in two consecutive lines, and will be completed when you get to main.

« Last Edit: June 05, 2026, 03:27:33 am by cv007 »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf