0

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.

BHOS
  • 91
  • 7

2 Answers2

1

You can create global callback object (visible to the ISR) that should register the actual action to be taken. Generally the callable needs to be visible to the ISR, i.e. it needs to live int ISR's scope.

noexcept suggests you're on C++11 or higher. If not, you'll need to fall back to boost or do some more typing...


volatile std::function<void()> pwmCallback; //even better: make it static for the PWM class
//can also by a function object...depends what you need.

ISR(TIMER0_OVF_vect)
{
    if (pwmCallback) {
        pwmCallback();
    }

}

int main()
{
    mcal::pwm::pwm_8<UINT8_C(5U)> myPwm;
    pwmCallback = [&myPwm](){myPwm.pwm_ISR();}

    myPwm.init();

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

    for(;;) 
    {
    }

    return 0;
}

Also, make sure the callback does not outlive the pwm object (not covered here). Another option is make your pwm a global object.

alagner
  • 3,448
  • 1
  • 13
  • 25
1

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;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • I have used the first solution, but the second one was very interesting. Thank you again. – BHOS Jan 09 '22 at 21:41
  • 1
    @BHOS Glad it helped and you're wecome! If you have more than one global variable and a potential dependecy between them, I recommend the second version, but with one only, the first should be fine. – Ted Lyngmo Jan 09 '22 at 21:49