thiagoralves / OpenPLC_Editor

OpenPLC Editor - IDE capable of creating programs for the OpenPLC Runtime
GNU General Public License v2.0
430 stars 207 forks source link

More flexible PWM_Controller #97

Open CloudHead84 opened 1 year ago

CloudHead84 commented 1 year ago

Hi Thiago,

i just realized that the call of a single pwm_controller function block will trigger the creation of all possible PWM_Instances. So all possible pwm pins are reserved for pwm, even if only 1 channel/pin is used.

void init_pwm()
{
    // Initialize PWM pins
    #if defined(__SAM3X8E__) || defined(__SAMD21G18A__)
        PWM_Instance[0] = new SAMD_PWM(PWM_CHANNEL_0_PIN, PWM_DEFAULT_FREQ, 0);
        PWM_Instance[1] = new SAMD_PWM(PWM_CHANNEL_1_PIN, PWM_DEFAULT_FREQ, 0);
etc.....

I did some reading and realised that if a pin is dedicated to a PWM_Instance, it cannot be used for something else. So i think something like this is not needed:

    for (int i = 0; i < NUM_ANALOG_OUTPUT; i++)
    {
        for (int j = 0; j < NUM_OF_PWM_PINS; j++)
        {
            if (pinMask_AOUT[i] == pins[j])
            {
                pinMask_AOUT[i] = 255; //disable pin
            }
        }
    }

I then played around and ended up with this solution without the void init_pwm() function

uint8_t set_hardware_pwm(uint8_t ch, float freq, float duty)
{
    if (ch >= NUM_OF_PWM_PINS)
    {
        return 0;
    }

    uint8_t pwm_pin = pins[ch];

    // Initialize the PWM instance if not already done
    if (PWM_Instance[ch] == nullptr)
    {
        PWM_Instance[ch] = new ESP32_FAST_PWM(pwm_pin, freq, duty, timers[ch], PWM_resolution);
        // Disable the pin for other uses as before
    }

    if (PWM_Instance[ch]->setPWM(pwm_pin, freq, duty))
    {
        return 1;
    }

    return 0;
}

With this, only the pins/channels that are used at the PWM-Controller instances are reserved for PWM. All other unused potential pwm pins can be used as digital/analog input/output.

I have carried out tests with different constellations on my ESP32 board. So with different numbers of pwm pins and digital IOs. It seems to work.

Wanna also give it a try?

CloudHead84 commented 1 year ago

This is the ESP32.cpp

#include <stdlib.h>
extern "C" {
 #include "openplc.h"
}
#include "Arduino.h"
#include "../examples/Baremetal/defines.h"

#include "ESP32_FastPWM.h"

//OpenPLC HAL for ESP32 boards

/******************PINOUT CONFIGURATION**************************
Digital In:  17, 18, 19, 21, 22, 23, 27, 32 (%IX0.0 - %IX0.7)
             33                             (%IX1.0 - %IX1.0)
Digital Out: 01, 02, 03, 04, 05, 12, 13, 14 (%QX0.0 - %QX0.7)
             15, 16                         (%QX1.0 - %QX1.1)
Analog In:   34, 35, 36, 39                 (%IW0 - %IW2)
Analog Out:  25, 26                         (%QW0 - %QW1)
*****************************************************************/

//Create the I/O pin masks
uint8_t pinMask_DIN[] = {PINMASK_DIN};
uint8_t pinMask_AIN[] = {PINMASK_AIN};
uint8_t pinMask_DOUT[] = {PINMASK_DOUT};
uint8_t pinMask_AOUT[] = {PINMASK_AOUT};

    #define NUM_OF_PWM_PINS       8 //only 8 pins possible with independent frequency

    #define PWM_CHANNEL_0_PIN     4
    #define PWM_CHANNEL_1_PIN     13
    #define PWM_CHANNEL_2_PIN     18
    #define PWM_CHANNEL_3_PIN     19
    #define PWM_CHANNEL_4_PIN     21
    #define PWM_CHANNEL_5_PIN     22
    #define PWM_CHANNEL_6_PIN     32
    #define PWM_CHANNEL_7_PIN     33

    #define TIMER_CHANNEL_0       0
    #define TIMER_CHANNEL_1       2
    #define TIMER_CHANNEL_2       4
    #define TIMER_CHANNEL_3       6
    #define TIMER_CHANNEL_4       8
    #define TIMER_CHANNEL_5       10
    #define TIMER_CHANNEL_6       12
    #define TIMER_CHANNEL_7       14

    float frequency = 1000.0f;
    float dutyCycle = 0.0f;
    int PWM_resolution = 10;    

    ESP32_FAST_PWM *PWM_Instance[NUM_OF_PWM_PINS];

    const uint8_t pins[] = {PWM_CHANNEL_0_PIN, PWM_CHANNEL_1_PIN, PWM_CHANNEL_2_PIN, PWM_CHANNEL_3_PIN,
                            PWM_CHANNEL_4_PIN, PWM_CHANNEL_5_PIN, PWM_CHANNEL_6_PIN, PWM_CHANNEL_7_PIN};

    const uint8_t timers[] = {TIMER_CHANNEL_0, TIMER_CHANNEL_1, TIMER_CHANNEL_2, TIMER_CHANNEL_3,
                              TIMER_CHANNEL_4, TIMER_CHANNEL_5, TIMER_CHANNEL_6, TIMER_CHANNEL_7};

extern "C" uint8_t set_hardware_pwm(uint8_t, float, float); //this call is required for the C-based PWM block on the Editor

bool pwm_initialized = false;

void hardwareInit()
{
    for (int i = 0; i < NUM_DISCRETE_INPUT; i++)
    {
        pinMode(pinMask_DIN[i], INPUT);
    }

    for (int i = 0; i < NUM_ANALOG_INPUT; i++)
    {
        pinMode(pinMask_AIN[i], INPUT);
    }

    for (int i = 0; i < NUM_DISCRETE_OUTPUT; i++)
    {
        pinMode(pinMask_DOUT[i], OUTPUT);
    }

    for (int i = 0; i < NUM_ANALOG_OUTPUT; i++)
    {
        pinMode(pinMask_AOUT[i], OUTPUT);
    }
}

uint8_t set_hardware_pwm(uint8_t ch, float freq, float duty)
{
    if (ch >= NUM_OF_PWM_PINS)
    {
        return 0;
    }

    uint8_t pwm_pin = pins[ch];

    // Initialize the PWM instance if not already done
    if (PWM_Instance[ch] == nullptr)
    {
        PWM_Instance[ch] = new ESP32_FAST_PWM(pwm_pin, freq, duty, timers[ch], PWM_resolution);
        // Disable the pin for other uses as before
    }

    if (PWM_Instance[ch]->setPWM(pwm_pin, freq, duty))
    {
        return 1;
    }

    return 0;
}

void updateInputBuffers()
{
    for (int i = 0; i < NUM_DISCRETE_INPUT; i++)
    {
        if (bool_input[i/8][i%8] != NULL) 
            *bool_input[i/8][i%8] = digitalRead(pinMask_DIN[i]);
    }

    for (int i = 0; i < NUM_ANALOG_INPUT; i++)
    {
        if (int_input[i] != NULL)
            *int_input[i] = (analogRead(pinMask_AIN[i]) * 16);
    }
}

void updateOutputBuffers()
{
    for (int i = 0; i < NUM_DISCRETE_OUTPUT; i++)
    {
        if (bool_output[i/8][i%8] != NULL) 
            digitalWrite(pinMask_DOUT[i], *bool_output[i/8][i%8]);
    }
    for (int i = 0; i < NUM_ANALOG_OUTPUT; i++)
    {
        if (int_output[i] != NULL) 
            dacWrite(pinMask_AOUT[i], (*int_output[i] / 256));
    }
}
rhysperry111 commented 8 months ago

Inadvertently working on this while adding support for the ESP32-S3. Moving over to LEDC from FastPWM. I'm getting issues though because in order to use LEDC's auto-timer allocation, esp32:esp32 >3.0.0 needs to be used, and when switching over to the development branch for that core I just get drowned in compile errors when trying to build a program from within OpenPLC

https://github.com/rhysperry111/OpenPLC_Editor

CloudHead84 commented 8 months ago

Esp32_fastPWM is already using ledc timers... Also all other boards are using the fastPWM libs. Would be a shame to deviate from that. Do you need to use this auto-timer allocation?

My example in the comments is working with a esp32 wroom board. Did you try it with the S3?

rhysperry111 commented 8 months ago

Might be more appropriate to add comments to the PR I just opened: #106

Esp32_fastPWM is already using ledc timers...

Internally yes, but looking at the GitHub repos it seems that the maintainer of ESP32_fastPWM no longer intends to maintain it. This wouldn't be aproblem if it worked properly, however I found that (at least on my board) it was not able to do a duty cycle of 0% or 100%, which was extremely inconvenient.

Do you need to use this auto-timer allocation?

It helps a lot because it means that we are no longer statically defining mappings of timers and channels. It means that you can use all timers your board has built in, not just the ones defined in esp32.cpp. This in turn allows for the channel fed to the PWM_CONTROLLER block to simply correlate to a pin on your board, instead of ones statically defined in esp32.cpp

Did you try it with the S3?

I was able to verify that there were issues with FastPWM on the S3, however have not been able to debug my LEDC implementation yet because of some funky versioning problems. Went into more detail in the draft PR

rhysperry111 commented 6 months ago

Managed to get all the code on my end sorted out, and if you're still looking for a better PWM_CONTROLLER implementation I'd appreciate some testing of the code in my PR. It likely won't merge until 3.0.0 of arduino-esp32 is released, however as far as I've observed it's pretty stable (why I'd appreciate some testing)

Breaking change from a code side is that the CHANNEL of the PWM_CONTROLLER block now directly correlates to the pin you want to do PWM on.