3

I have a UART class that has instances of Fifo class (just a rough idea as of now), but if I do that, and have an interrupt handler in a source file, there wouldn't be a way for me to access a Fifo instance or any other Uart members.

// uart.hpp
class Uart {
      Fifo fifoRx;
      // ...
};

// uart.cpp
void UART_IRQ_Handler(void) 
{
   // have no way to access the Uart class
}

In C, I would have a Uart struct in the header, and perhaps define the Fifo instance as static in a source file and access it inside the IRQ handler, but not quite sure of going around this in C++ while maintaining encapsulation.

// uart.c
static Fifo fifoRx;

void UART_IRQ_Handler(void) 
{
   // fifoRx is accessible
}

Perhaps if you want to access the Uart class itself, one way would be to set the static pointer to the instance inside the UartInit(Uart *pUart) function

// uart.c
static Uart *psUart;

void UART_IRQ_Handler(void) {
    // access UART through psUart 
}

void UartInit(Uart *pUart) {
    psUart = pUart;
    // ...
}

But how would you go about doing something similar in modern C++ while keeping encapsulation intact?

Jazzy
  • 324
  • 4
  • 15
  • Having a singleton Uart is one way. – Jeffrey May 21 '21 at 02:50
  • @Jeffrey Or rather allow `n` instances of the class, depending on how many hardware peripherals of the same kind that the MCU supports. – Lundin May 21 '21 at 09:06
  • Also unrelated to the question, you need to consider how to handle re-entrancy between the ISR and the rest of the driver. As always. Atomic access, disabling interrupts, boolean semaphores etc etc. – Lundin May 21 '21 at 09:15
  • So why can you not have a static instance of a `Uart`? `static Uart uart1 ;`, then in the ISR `uart1.fifoRx` - what is that any different that your C version? You would do the same in C with a structure; you are not really comparing like-for-like between C and C++. – Clifford May 21 '21 at 16:42
  • having a static instance of `Uart` doesn't quite sound right to me. From my understanding of the architecture and what I've read, these peripheral instances come from the user as to which/however many instances they'd need and the driver/middleware should take care of handling the requests accordingly, as opposed to hardcoding the entire peripheral's instance inside the driver – Jazzy May 22 '21 at 07:15

5 Answers5

1

I've faced the same problem and I've solved it using a somewhat dirty and complicated design. My solution is dependent on some hardware support, so it may not be applicable on your platform. My solution is developed on STM32F1 & STM32F4 uCs, which use Cortex M3 & M4 cores respectively. It should probably work on M0 too. Also, my solution uses some C++17 features, but it can be converted to C++11 and above with a little bit modification.

It's apparent that some hardware related classes, like the UART class you mentioned, are closely coupled with interrupt handlers. The uC can have multiple instances of these classes. This also requires that you need multiple instances of ISRs. Because they can't have arguments, and mapping multiple interrupt vectors to same function may cause problems if they have to determine the source of the interrupt.

Another problem about the ISRs is that their names are predetermined by the vendor supplied vector table code, sometimes written in assembly. As each vector has a hard-coded name, it's not possible to write a generalized code without some macro dark magic.

First, the ISR naming problem needs to be solved. This is the part that depends on hardware support. Cortex M3 & M4 cores gives you the ability to point different vector tables located on a different address. If the vector table is copied from FLASH to RAM, then it will possible to register any function with void ISR_func(void) signature as an ISR during runtime. M0 cores have limited vector table relocation capabilities but they still offer a way to move it to the start of RAM.

I think the details of moving the ISR vector table from FLASH to RAM is a little bit off topic for this question, but let's assume the following function is available, which resisters a function as the ISR of a given vector:

void registerISR(IRQn_Type IRQn, void (*isr)());

We can assume that the maximum number of class instances are known, as the number of UART hardware modules is known. We need the following components:

A static member array of Uart pointers, each pointing to an instance of Uart.

static constexpr unsigned MaxUarts {3};
inline static Uart* uartList[MaxUarts] {nullptr};

A function template of the ISR. These will be static member functions but in the end each instance will have its own dedicated ISR:

template <unsigned uartIndex>
void Uart::uartIsr()
{
    auto instance = uartList[uartIndex];
    instance->doSometing();
}

An static member array of function pointers, which points to all the possible instances of ISRs. This wastes some flash space due to code duplication, but shouldn't be much problem if the ISRs are small.

using isrPointer = void (*)();
inline static const isrPointer uartHandlers[MaxUarts] {
    &uartIsr<0>,
    &uartIsr<1>,
    &uartIsr<2>
}

Finally, the ctor of the Uart needs to register itself to the static array of Uart instances and it must also register the ISR to the vector table

Uart::Uart(/* Arguments, possibly the instance no */) {
    instanceNo = instanceNoAssigner++; // One can use a static counter
    uartList[instanceNo] = this;
    registerISR(this->getIrqNo(), uartHandlers[instanceNo])
}

So the Uart class header should look like this:

class Uart {
public:
    Uart(/* Arguments */);
private:
    inline static uint8_t instanceNoAssigner {0};
    
    void doSomething(); // Just a place holder
    IRQn_Type getIrqNo(); // Implementation depends on HW

    template <unsigned uartIndex>
        static void uartIsr();

    static constexpr unsigned MaxUarts {3};
    inline static Uart* uartList[MaxUarts] {nullptr};

    using isrPointer = void (*)();
    inline static const isrPointer uartHandlers[MaxUarts] {
        &uartIsr<0>,
        &uartIsr<1>,
        &uartIsr<2>
    }
}

template <unsigned uartIndex>
Uart::uartIsr()
{
    auto instance = uartList[uartIndex];
    instance->doSometing();
}

It has been a long post. I hope I didn't make too many errors as I ported it from one of my real-wold projects.

Update: Example vector table relocation and ISR registration code for STM32F407.

Some modifications are needed in linker script file. Vendor supplied linker script already has the .isr_vector section (obviously). I just added start & end markers.

/* The startup code into "ROM" Rom type memory */
.isr_vector :
{
  . = ALIGN(4);
  _svector = .;
  KEEP(*(.isr_vector)) /* Startup code */
  . = ALIGN(4);
  _evector = .;
} >ROM

I also created a new section at the beginning of RAM. For Cortex M3 & M4, vector table can be anywhere (with some alignment requirements). But for Cortex M0, the start of the RAM is the only alternative location, as they lack VTOR register.

/* Relocated ISR Vector Table */
.isr_vector_ram 0x20000000 :
{
  . = ALIGN(4);
  KEEP(*(.isr_vector_ram))
  . = ALIGN(4);
} >RAM

And finally, these are the two functions I use for relocation (called once during initialization) & registration:

extern uint32_t _svector;
static constexpr size_t VectorTableSize {106}; // words
uint32_t __attribute__((section(".isr_vector_ram"))) vectorTable[VectorTableSize];

void relocateVectorTable()
{
    // Copy IRQ vector table to RAM @ 0x20'000'000
    uint32_t *ptr = &_svector;
    for (size_t i = 0; i < VectorTableSize; ++i) {
        vectorTable[i] = ptr[i];
    }

    // Switch to new table
    __disable_irq();
    SCB->VTOR = 0x20'000'000ULL;
    __DSB();
    __enable_irq();
}

void registerISR(IRQn_Type IRQn, void (*isr)())
{
    uint32_t absAdr = reinterpret_cast<uint32_t>(isr);
    __disable_irq();
    vectorTable[IRQn + 16] = absAdr;
    __DSB();
    __enable_irq();
}

Somehow, GCC is clever enough to automatically make the least significant bit of raw function address 1, to indicate Thumb instructions.

Tagli
  • 2,412
  • 2
  • 11
  • 14
  • 1
    It you find yourself writing overhead bloat layers like this, you should seriously consider porting to C. C++ shouldn't be an obstacle and burden preventing trivial embedded programming. However, it shouldn't be a problem in most tool chains to keep an ISR as a static member. Mostly depends on how the vector table is made. – Lundin May 21 '21 at 09:05
  • The problem is trying to write library-like generalized code, which can be reused. I agree that it causes some bloat (probably not as much as ST's HAL), but I think the advantage of re-usability overweight its cons. Before the idea of runtime ISR registration came to my mind, I was using macros to generate multiple ISR functions. It was a lot more uglier than function templates. – Tagli May 21 '21 at 09:40
  • You can write re-usable code just fine without any of this. The only problem is how the vector table is defined and how to link your static member to it. Run-time registration isn't something I'd recommend for any purpose. – Lundin May 21 '21 at 10:07
  • @Lundin If you can do it in C, you can do it in C++ just the same - this "bloat" (or rather complexity, it is probably not that large) is caused by the programmer not the language. – Clifford May 21 '21 at 16:45
  • @Lundin how would you propose a re-usable code as a 'better' alternative? – Jazzy May 22 '21 at 06:46
  • @Tagli thanks for the detailed response. I read through it, and wanna clariffy a couple of things: 1) the idea behind copying from flash to RAM is to be able to use 'custom' ISR (`uartIsr()`)as opposed to what the HW would invoke, which is why you're registering? 2) how are you really going about registering the ISR? – Jazzy May 22 '21 at 07:11
  • @Jazzy , for STM32, names of ISR functions are provided in a vendor supplied assembly code. To bind them, you normally need to define a function with the same name, like `USART1_IRQHandler`, unless you modify that vendor supplied file. I tried to avoid copy & paste of ISR functions (see @Clifford 's answer). Also, I didn't want ISRs to be bound unless I create the class instance. I will add an example code for ISR registration for STM32F407. – Tagli May 22 '21 at 07:27
  • Yes, I understand you basically override the weak ISR functions by defining them inside your application but I'm still a bit uncertain of being able invoke `uartIsr()` in your example... – Jazzy May 22 '21 at 07:36
  • Actually, normal definition of `void USART1_IRQHandler()` function overrides the weak default handlers. When an interrupt fires, Cortex M core fetches the address of the ISR directly from the vector table - which is more or less an array of function pointers - and jumps into that function. As they (vector table) normally reside at the beginning of the flash, functions are bound during compile time and can't be rebound unless you copy them into RAM. – Tagli May 22 '21 at 07:53
  • @Tagli yes that's what I meant; the IRQ handlers would override the weak default handler – Jazzy May 22 '21 at 16:54
  • Just for clarification: `registerISR()` doesn't cause link-time overriding of weak functions. It replaces the ISR functions during run-time. BTW, `relocateVectorTable()` doesn't break link-time registered ISR functions. So, when using vector table relocation, you don't need to register every ISR using `registerISR()`. You can continue to use the classical ISR definition & registration and mix two methods. – Tagli May 22 '21 at 17:05
0

You could try one of the following mechanism

  1. Introduce a public static member Fifo fifoRx
    // uart.h
    class Uart {
        public:
          static Fifo fifoRx;
          // ...
    
    };
    // Initialize static member of Uart Class
    Fifo Uart::fifoRx = {0};
    
    // uart.cpp
    void UART_IRQ_Handler(void) 
    {
       // directly access static member
       Uart::fifoRx.<memeber_access>
    }
  1. Option 2. Introduce a private static member and use a static getter/setter member functions to access/modify the fifoRx
    class Uart {
          static Fifo fifoRx;
          // ...
         public:
          // Add the getter, setter static functions here
          //eg: static <data_type> get_fifo_front(), 
          //eg: static add_element_to_fifo(<data_type> data)
          //eg: static remove_element_from_fifo_front()
             
    };
    
    // uart.cpp
    void UART_IRQ_Handler(void) 
    {
       // access using static member function 
       //Uart::get_fifo_front()
    }
    
    // Initialize static member of Uart Class
    Fifo Uart::fifoRx = {0};
Anish
  • 31
  • 1
  • thanks. what if I want to keep `Fifo` as a part of the class instead and not really a free member variable? Also, how to access any other member functions of `Uart` inside the interrupt handler? – Jazzy May 21 '21 at 04:04
  • Not a great solution if you have more then one `Uart` instance which is likely. – Clifford May 21 '21 at 16:47
0

Your C version and C++ version are not really comparable. In C you could have a Uart structure containing a Fifo structure - that would be comparable. There is no reason why what you are doing in C won't work in C++ (i.e. your C code is also valid C++ code).

The real question you should ask is why have you introduced the Uart class if you did not need such a thing in your C code. The answer to that rather determines an acceptable C++ solution.

Either way if your C solution is acceptable, the same solution can be applied to the C++:

static Uart uart1 ;

void UART1_IRQ_Handler(void) 
{
   ...
   uart1.fifoRx.put( UART1_DR ) ;
   ...
}  

However, you are then likely to have duplicate ISR code for each UART you support. For performance that might be desirable, but a more object oriented approach would be to have the ISR body as part of the class:

class Uart 
{
    public:
      Uart( UART uart ) : m_uart(uart) { ... } ;
      void isr()
      { 
        ...
          m_fifoRx.put( m_uart.DR ) ;
        ...
      }

    private:
      Fifo m_fifoRx;
      UART m_uart ;
};

static Uart uart1( UART1 ) ;
static Uart uart2( UART2 ) ;
void UART1_IRQ_Handler(void) 
{
    uart1.isr() ;
}

void UART2_IRQ_Handler(void) 
{
    uart2.isr() ;
}

In that way you only maintain the one ISR handling code and it applies to all supported UARTS, which is presumably the purpose of doing it this way?

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • In some cases, your ISR pre-definition can cause problems. Suppose that I have 3 SPIs on my uC. I create a SpiManager class, which acts like a gateway for a single SPI using interrupts. If I plan to use 2 SpiManagers only, all 3 ISRs are pre-defined and I can't use the 3rd SPI ISR with another method, like a low level non-OOP code which uses that SPI directly. Of course one can simply delete them from the code. But I would prefer them not bound unless I create an SpiManager object. It's easier not to add/remove them to/from code. – Tagli May 21 '21 at 19:19
  • @Tagli : I am not sure what your point is. Nothing requires anyone to specify _all_ available UARTS or whatever. Whatever your point is it seems very specific to your design. In any case the above is illustrative only, it is not _real code_, and not even the way I'd necessarily do it - that would require more code, a specific rather than hypothetical platform, and would cover aspects irrelevant to the question. The solution to the "problems" you refer to nor doubt are all solvable but would require a separate more concrete and specific question to address. – Clifford May 22 '21 at 06:36
  • @Clifford I see where the confusion is coming from. I didn't mean the first code snippet to be a C code - it's a C++ hence `class`; I was just showing how I imagined of doing in C++ but there are challenges regarding encapsulation, whereas in C, I'd define a static `Fifo` where could be used inside the IRQ handler. Apologies – Jazzy May 22 '21 at 06:53
  • what you proposed is fairly similar to what I did in my C code, no? except you're defining static instances of Uart inside the source file and using them inside the IRQ handler, but shouldn't the user/application itself be the one deciding how many UART instances they really need to use? – Jazzy May 22 '21 at 06:55
0

You can make a static variable inside the class and add interrupt routines as a friend members.

your uart.hpp would look like:

#define NUM_UART 3
enum class uartCh {uart1=0, uart2, uart3};
extern "C" void UART1_IRQHandler ();
extern "C" void UART2_IRQHandler ();
extern "C" void UART3_IRQHandler ();

class uart
{
public:
  static uart* meUart[NUM_UART];

  void initInterrupt(const uartCh a_ch) 
  {
    /*do your initialization*/
    int chmem = static_cast<int>(a_ch);
    if (chmem >= NUM_UART) return;
    meUart[chmem] = this;
  }
  void myUARTHandler()
  {
  /*do something with your struct*/
  }
  
  friend void UART1_IRQHandler();
  friend void UART2_IRQHandler();
  friend void UART3_IRQHandler();
}

because you have static array of ptrs (static uart*), you need to allocate them in the cpp file. So your main.cpp is:

uart *uart::meUart[NUM_UART]{nullptr};
constexpr int UART1 = static_cast<int>(uartCh::uart1);
constexpr int UART2 = static_cast<int>(uartCh::uart2);
constexpr int UART3 = static_cast<int>(uartCh::uart3);
int main ()
{
  uart myUartClass;
  myUartClass.initInterrupt(uartCh::uart1);
}


extern "C" void UART1_IRQHandler ()
{
  uart::meUart[UART1]->myUARTHandler();
}

extern "C" void UART2_IRQHandler ()
{
  uart::meUart[UART2]->myUARTHandler();
}

you can also make a template out of that and avoid sending enum parameter to initialization function, but you get the idea.

Gossamer
  • 309
  • 2
  • 16
-1

You could declare the interrupt handler as a friend function so the handler could access private members of the class.

// uart.h
class Uart {
      Fifo fifoRx;
      friend void UART_IRQ_Handler(void) 
      // ...
};
// uart.cpp
void UART_IRQ_Handler(void) 
{
   // have no way to access the Uart class
}
GandhiGandhi
  • 1,029
  • 6
  • 10