c++interrupt-handlingatmega32

How to access class's virtual method from interrupt service routine?


I am trying to implement PWM using Timer0 for Atmega328P in C++. Indeed, I have achieved this. But, I have another related problem.

I have a PWM abstract base class that provides an interface for PWM implementation.

// mcal_pwm_base.h
#ifndef MCAL_PWM_BASE_H_
#define MCAL_PWM_BASE_H_

namespace mcal
{
    namespace pwm
    {
        class pwm_base
        {
        public:
            ~pwm_base() = default;
        
            virtual bool init() noexcept = 0;
        
            virtual void set_duty(const uint16_t duty_cycle) = 0;
        
            virtual void pwm_ISR() noexcept = 0;
        
            uint16_t get_duty() const noexcept { return pwm_duty_cycle; }
    
        protected:
            uint16_t pwm_duty_cycle;
        
            pwm_base() : pwm_duty_cycle(0U) { }
            
            pwm_base(const pwm_base&) = delete;
            const pwm_base& operator=(const pwm_base&) = delete;
        };
    }
}

#endif /* MCAL_PWM_BASE_H_ */

I want to implement PWM with timer (8 bit timer). So, I created another class which derives from pwm_base class.

// mcal_pwm_8.h
#ifndef MCAL_PWM_8_H_
#define MCAL_PWM_8_H_

#include "mcal_pwm_base.h"
#include "mcal_reg_access_dynamic.h"
#include <avr/interrupt.h>

namespace mcal
{
    namespace pwm
    {
        template<const uint8_t prescalar_val = UINT8_C(0U)>
        class pwm_8 : public mcal::pwm::pwm_base
        {
        public:
            ~pwm_8() = default;
            
            virtual bool init() noexcept
            {
                // set pwm related things
            
                return true;
            }
        
            virtual void set_duty(const uint16_t duty_cycle)
            {
                pwm_duty_cycle = duty_cycle;
            }
        
            virtual void pwm_ISR() noexcept
            {
                uint8_t compare_value =  (pwm_duty_cycle / 100) * 255;
                mcal::reg::reg_access_dynamic<uint8_t, uint8_t>::reg_set(mcal::reg::ocr0a, compare_value);
            }
        };
    }
}

ISR(TIMER0_OVF_vect)
{
    // pwm_ISR();
}


#endif /* MCAL_PWM_8_H_ */

As you can see, I want to call pwm_ISR() member function in ISR (interrupt service routine). But, I don't know how to do it. In the book, it is written that make ISR friend function of pwm_8 class. Even if I did that, how will I reach the private member variable of pwm_duty_cycle in ISR? At this point, I need your help.

And finally, this is my main function.

// main.h
#define F_CPU   16000000ULL

#include <avr/io.h>
#include <util/delay.h>

#include "mcal_reg.h"
#include "mcal_reg_access_static.h"
#include "mcal_port.h"
#include "mcal_led_port.h"
#include "mcal_pwm_8.h"

int main(void)
{       
    mcal::pwm::pwm_8<UINT8_C(5U)> myPwm;

    myPwm.init();

    myPwm.set_duty(250); // This will be applied when ISR called

    for(;;) 
    {
    }

    return 0;

}

Thank you very much beforehand.


Solution

  • It doesn't seem possible to supply user data (like a void*) to the ISR routine so you could make myPwm a global variable:

    Example:

    Header file:

    extern mcal::pwm::pwm_8<UINT8_C(5U)> myPwm;
    
    ISR(TIMER0_OVF_vect)
    {
        myPwm.pwm_ISR();
    }
    

    ... and in the .cpp file:

    mcal::pwm::pwm_8<UINT8_C(5U)> myPwm;
    

    You could also hide the global variable in a function to get lazy initialization.

    Header file:

    mcal::pwm::pwm_8<UINT8_C(5U)>& myPwm();  // now a function
    
    ISR(TIMER0_OVF_vect)
    {
        myPwm().pwm_ISR(); // calling function to get the instance
    }
    

    ... and in the .cpp file:

    mcal::pwm::pwm_8<UINT8_C(5U)>& myPwm() {
        static mcal::pwm::pwm_8<UINT8_C(5U)> instance;
        // that returns a reference to the one instance you'll use:
        return instance;
    }