Optimize my code (Sofware PWM)

Hello everyone,

I am creating a program that allows to make Software PWM.
I do this mainly to use it on attiny88, which has only 2 pins of PWM hardware.
I created a softPWM() function that corresponds to analogWrite() so that it is easy to modify a program when you want to switch from one to the other.

But I feel like my code is not optimized at all. The size must be reduced to a minimum (attiny88 has only 6kb of available space), but even without talking about that, my code is not very "clean" (my function which seems a bit useless, the over-use of if(), and surely other errors you will find).

Can you help me optimize my program?

unsigned long previousMicros;
bool period0 = true;
byte pin;
int tempOFF;
int tempON;

void setup() {
  previousMicros = micros();
  pinMode(0, OUTPUT); 
}

void loop() {
  softPWM(13, 132);// analogWrite(13, 132)



  if (period0 == true) {
    if (tempON != 0) {
      digitalWrite(pin, 1);
    }
    if (micros() - previousMicros >= tempON) {
      previousMicros = micros();
      period0 = false;
    }

  }
  else {
    digitalWrite(pin, 0);
    if (micros() - previousMicros >= tempOFF) {
      previousMicros = micros();
      period0 = true;
    }
  }
}
void softPWM(byte pin, byte value) {
  tempOFF = (255 - value) * 4;
  tempON = value * 4;
  
}

pin = ?

consider

#undef  MyHW
#ifdef MyHW
const byte pin = 13;
#else
const byte pin = 0;
#endif

int           dutyCyle = 30;
unsigned long Period = 5000;
unsigned long onPeriod  = Period * dutyCyle / 100.0;
unsigned long offPeriod = Period - onPeriod;
unsigned long period;

unsigned long usecLst;

byte state;

void softPWM (
    byte pin )
{
#ifdef MyHW
    unsigned long usec = millis ();
#else
    unsigned long usec = micros ();
#endif

    if ( (usec - usecLst) >= period)  {
        Serial.println ("timeout");
        usecLst = usec;

        if (LOW == state)
            period = onPeriod;
        else
            period = offPeriod;

        state = ! state;
        digitalWrite (pin, state);
    }
}

void loop ()
{
    softPWM (pin);
}

void setup() {
    Serial.begin (9600);

    state = LOW;
    digitalWrite (pin, state);
    pinMode      (pin, OUTPUT);
}

#undef
#ifdef
#ifndef (?)

The compiler is clever and probably do this anyway, but better to make it explicit

const int           dutyCyle = 30;
const unsigned long Period = 5000;
const unsigned long onPeriod  = Period * dutyCyle / 100.0;
const unsigned long offPeriod = Period - onPeriod;

unless you want to change the values on the fly with more complicated code ...

?
i submitted code i tested on "my hardware" but wanted to revert back to what the OP would use

Thank you for your answers !

Yes it's a bit weird, but I have no choice because otherwise there is the error "pin was not declared in scope".
If I'm not mistaken, when you leave empty it is equivalent to pin=0. This variable is modified when you call the softPWM function.
But there is surely a better way...

I don’t think it’s very appropriate.
I noticed that it takes a lot more memory, 3426 bytes instead of 1006 currently (which I hope to reduce), and 173 dynamic memory against 20 for mine.
But also, my goal is really to imitate the analogWrite function, and can be called several times in the same code with different values.

So if you can help me modify my first code, I'd prefer that :grin:, and I'll probably learn more.

I think you should make the declaration (take up memory for the variable) rather than hope "zero" lands in "pin" - then optimize the code. Have you considered bit/port manipulation for optimizing?

it's not really clear what you're trying to do.

the simplest and conventional approach is to use a PWM output

analogWrite(pin, value);    // value some # from 0-255

it seems you wrote the code to learn something. so presented code that demonstrates some ideas for you to "consider"

It's a good idea.
But how do I declare this (in an optimized way)?

If I don't use analogWrite, it's for a reason... it's that it doesn't work (except on certain pins of course).

It is certainly very interesting, but it seems too different from my original approach for me to be able to use it.

If you are trying to create a drop-in replacement for analogWrite() on pins that don't support HW PWM, the best way would be to make it a class such that you create as many objects as you need with the given pin number which gets stored in the class. You will still need to call some code to update the pin each time through the loop.

You also seem very focused on optimization which seems a bit premature. Optimizing code that doesn't work is seldom useful. Going down the class route will not yield the smallest code.

At the top of your code, near the #include<>s, identify the all-cap identifier word PIN to be the value 0 (zero) but do not end the line with a semi-colon

#define PIN 0 // #define convention is UPPER case, no semicolon (;)

Or

int PIN = 0;

more like your code, better organized, but i hope you see how awkward it is (see @blh64 's comment)


#define Period  1000

const byte    pin = 13;
unsigned long usecLst;
unsigned long period;
byte          state;

// -----------------------------------------------------------------------------
void softPWM (
    unsigned long   usec,
    byte            pin,
    byte            dutyCycle,
    byte           *pState,
    unsigned long  *pPeriod,
    unsigned long  *pUsecLst )

{
    if ((usec - *pUsecLst) >= *pPeriod)  {
        *pUsecLst = usec;
        if (LOW == *pState)
            *pPeriod = Period * dutyCycle / 100;
        else
            *pPeriod = Period * (255 - dutyCycle) / 100;

        *pState = ! *pState;
        digitalWrite (pin, state);
    }
}

void loop()
{
    unsigned long usec = micros ();

    softPWM (usec, pin, 240, &state, &period, &usecLst);
}

void setup() {
    pinMode (pin, OUTPUT);
}

My code works fine, that's not the problem.
I even tried this little sketch which makes a candle effect with a led:

unsigned long previousMicros;
unsigned long previousRMillis=0;
unsigned long intervalRandom;
bool period0 = true;
byte pin;
int tempOFF;
int tempON;

void setup() {
  previousMicros = micros();
  pinMode(13, OUTPUT); 
}

void loop() {
  if(millis() - previousRMillis >= intervalRandom) {
    previousRMillis=millis();
    softPWM(13, random(120)+135);
    intervalRandom=random(100);
  }
//----------

  if (period0 == true) {
    if (tempON != 0) {
      digitalWrite(pin, 1);
    }
    if (micros() - previousMicros >= tempON) {
      previousMicros = micros();
      period0 = false;
    }

  }
  else {
    digitalWrite(pin, 0);
    if (micros() - previousMicros >= tempOFF) {
      previousMicros = micros();
      period0 = true;
    }
  }
}
void softPWM(byte pin, byte value) {
  tempOFF = (255 - value) * 4;
  tempON = value * 4;
  
}

What does it change to put PIN in capital letters?
Can you post the complete code, because I don't quite understand how it will work.

In the meantime, my ATTINY88 card (MH-Tiny) broke down.
It's really weird, one minute before the candle code had worked very well, for example, but when I wanted to upload a new program, it didn't upload.
I tried with another PC, another USB cable but nothing changed :cry:

I will therefore (for the moment) continue the tests on arduino uno.

All caps is only a conversation convention for human recognition.

@leg2027
#define MYPINLED 0
followed by, for example,

pinMode(MYPINLED, OUTPUT); 

does not result in any memory consumption.
byte pin = 0;
reserves memory, which you seem very concerned about. (but you make no distinction between code space and data space, so...)
The #define technique has other limitations, which you can read about on the web, but will work in this limited case. A good example of the limitations, by the way, came up yesterday, here:
#define issue

When I use #define in my code, I tend to use verbose specific labels, e.g. MYPINLED, as a result. The all caps is not, AFAIK, mandated, but rather a convention to make it clear we're trying to utilize a #define, not some other variable.

If I'm not mistaken, when you leave empty it is equivalent to pin=0 .

Is an assumption. IIRC, this will be compiler-specific, though in most cases it will not be incorrect. In my student days, that would have resulted in (-1) in an assignment, unless I also put in a comment such as /* compiler dependency */ but that was a !few! years ago. Others may correct me, if the wild west compiler world has been tamed.
C

I hadn't realized a thing, I apologize. I just realized that I can't do something like this:

softPWM(10, 255); //example values
softPWM(11, 50);
softPWM(12, 255);

Only the last command will work. This means that this code can only control one PWM software at a time.
But that's no problem for what I wanted to do at first, I can control an RGB led using the 2 hardware pins and the code for the software.
But that's not very cool...

So, I better understand your remark for the use of #define.

But so if we want to control several pins with PWM software, we will need many more variables (ex: period0pin1, period0pin2, period0pin3, tempONpin1, tempONpin2...), I could however use #define instead of byte.

But I suppose we can do something much better. I have absolutely no idea how to do it. :slightly_frowning_face:

If you have an idea for me, tell me, but I would like to stick to the principle of analogWrite(pin, value) if possible.

The C/C++ standard mandates that all global variables will be initialized to 0. This is not true for locally scoped variables, such as those declared inside functions.

It will not. The compiler is clever enough to realize that this variable never changes and will insert the actual value in the call, thus optimizing away the space required for the variable. This is NOT guaranteed behavior, but happens in cases like this.