SoftPWM update limitations

i'm using arduino to dimmer 9 led strips with different output pins after an FFT audio analysis gives results at 60Hz = 16ms. FFT is performed by a PC and sent to arduino using a RF24 module

I started the project using SoftPWM on pins D2->D9 & A4 with a lower frequency update but now i'm having problems increasing the frequency update. I have already wired 6 copies of that board and i'm looking for a software solution

To diagnose the problem i performed a test sending a pwm value and zero at a certain frequency, if the frequency is high you can only see a lower emitted power

If I set the pwm once I have a stable signal (led intensity) If I set the pwm every ~10ms (10ms on @ certain pwm, 10ms at pwm=0) I have an unstable intensity, repeating the test on the pin 6 with a normal analogWrite i don't have the problem. With 10ms i clearly see it blinking at a random frequency (~20Hz=50ms), with 15ms I only see it random fluctuating

I'm using arduino Pro mini - 5V - 2kB Without signals arduino performs 16k cycles/second (most without changing anything), with a 10ms signal it runs at 14k cycles/second so it seems not a load problem.

Can someone help me regarding this problem?

  • Should I change library
  • Should I move to a lower level to rewrite a more performing library
  • Should I use only 6 std pwm, draw again the board, print them again, weld all components again, and test it again?

Thanks!

I would use one of these

I already have 6 identical PCB with the current configuration, i prefer to try a software modification first because I don't have remaining space and I must draw and print the PCB again....

Can I manually set pins using timed interrupt?

I already have 6 identical PCB with the current configuration,

Sorry I don't understand exactly what you mean. Do you mean 6 Arduino's each with six PWM outputs?
If so that would make it 36 PWM outputs would it not?

Using the boards I linked to, you can reduce that to one Arduino and two boards, that's 32 PWM outputs from the board and six on the Arduino. Would that not save you space? As the boards are set and forget, just like the internal PWM generators then there is no software overhead when they are running.

Can I manually set pins using timed interrupt?

I don't understand that either. Manually implies doing something to set them. You can change the pins programmatically but the big problem is that the interrupts have to go off at 256 times the final PWM rate to ensure the number of available duty cycles. This is basically what the software PWM libiary does so you don't get any improvement in performance over the libiary.

Where you could get some improvement is if you had fewer duty cycle widths available, like say 64, then the interrupts would only have to run at 64 times the final PWM rate.

Currently I have 6 arduinos with 9 PWM each. They receive informations with an RF24 module and they are quite far so I can't use a single module.

Currently I don't have a CPU-power problem, since it works well in stationary condition (PWM set, no modification), and also in a frequently changing situation (PWM updated every 10ms) the CPU load is not high (16k cycles with stationary, 14k cycles/s changing every 10ms)

I can consume more cpu, but I need the color to be more stable when I change the pin. In the library I read something intresting

void SoftPWMBegin(uint8_t defaultPolarity)
{
// We can tweak the number of PWM period by changing the prescalar
// and the OCR - we'll default to ck/8 (CS21 set) and OCR=128.
// This gives 1024 cycles between interrupts. And the ISR consumes ~200 cycles, so
// we are looking at about 20 - 30% of CPU time spent in the ISR.
// At these settings on a 16 MHz part, we will get a PWM period of
// approximately 60Hz (~16ms).

Can't I use more CPU (i.e. 50%) to have a more frequent PWM period (120Hz) and to wait the end of a PWM cycle before loading the new values?

I'm not fully understanding the library, but my feeling is that when I set the PWM with SoftPWMSet something break the current PWM cycle, restarting it a "random" position causing the instability.

Thanks for help

PS: I could also pack the library and hardcode it since I know I always have that 9 outputs used, to make it faster. But I want to know your opinion, to know if it could help.

I'm not fully understanding the library, but my feeling is that when I set the PWM with SoftPWMSet something break the current PWM cycle, restarting it a "random" position causing the instability.

Well it will have to do something like that if you think about it, because if it has gone past the place where you are now telling it to go to ( in time ) it will have to reset the waveform. But you can tell exactly what it does by looking at the code.

Here I am, full of tests and doubts.

Attached you can find a file with performed tests i'll explan below.

I first tested the performance of three differents IRS codes: the result can be seen in the attached image

First, predict code

ISR(TIMER2_COMPA_vect){ // version 1 = predict
	static uint8_t cnt = 0;
	if (cnt++ == 0) {
		for (int i = 0; i < 9; i++) pwm[i].currentPWM = 255; // 0 with next increase
	}
	for (int i = 0; i < 9; i++){
    if ( (++ pwm[i].currentPWM < pwm[i].pwmvalue) && !pwm[i].currentState) { *pwm[i].outport |= pwm[i].pinmask; pwm[i].currentState = true; }
    else if ( (pwm[i].currentPWM >= pwm[i].pwmvalue) && pwm[i].currentState ) { *pwm[i].outport &= ~(pwm[i].pinmask); pwm[i].currentState = false; }
  }
}

Second, simple code (similar to the one used with SoftPWM

ISR(TIMER2_COMPA_vect){ // version 2 = simple
  static uint8_t cnt = 0;
  if (cnt++ == 0) {
    for (int i = 0; i < 9; i++) {
      if ( pwm[i].pwmvalue) *pwm[i].outport |= pwm[i].pinmask; 
    }
  }
  for (int i = 0; i < 9; i++){
      if ( cnt == pwm[i].pwmvalue) { *pwm[i].outport &= ~(pwm[i].pinmask); }
  }
}

Third, a plain text version of 2

ISR(TIMER2_COMPA_vect){ // version 3 = plain
  static uint8_t cnt = 0;
  static uint8_t v[9] = {0,0,0,0,0,0,0,0,0};
  if (++cnt == 0) {
    if (pwm[0].pwmvalue) *pwm[0].outport |= pwm[0].pinmask;
    if (pwm[1].pwmvalue) *pwm[1].outport |= pwm[1].pinmask;
    if (pwm[2].pwmvalue) *pwm[2].outport |= pwm[2].pinmask;
    if (pwm[3].pwmvalue) *pwm[3].outport |= pwm[3].pinmask;
    if (pwm[4].pwmvalue) *pwm[4].outport |= pwm[4].pinmask;
    if (pwm[5].pwmvalue) *pwm[5].outport |= pwm[5].pinmask;
    if (pwm[6].pwmvalue) *pwm[6].outport |= pwm[6].pinmask;
    if (pwm[7].pwmvalue) *pwm[7].outport |= pwm[7].pinmask;
    if (pwm[8].pwmvalue) *pwm[8].outport |= pwm[8].pinmask;
  }

  if ( cnt == pwm[0].pwmvalue) { *pwm[0].outport &= ~(pwm[0].pinmask); }
  if ( cnt == pwm[1].pwmvalue) { *pwm[1].outport &= ~(pwm[1].pinmask); }
  if ( cnt == pwm[2].pwmvalue) { *pwm[2].outport &= ~(pwm[2].pinmask); }
  if ( cnt == pwm[3].pwmvalue) { *pwm[3].outport &= ~(pwm[3].pinmask); }
  if ( cnt == pwm[4].pwmvalue) { *pwm[4].outport &= ~(pwm[4].pinmask); }
  if ( cnt == pwm[5].pwmvalue) { *pwm[5].outport &= ~(pwm[5].pinmask); }
  if ( cnt == pwm[6].pwmvalue) { *pwm[6].outport &= ~(pwm[6].pinmask); }
  if ( cnt == pwm[7].pwmvalue) { *pwm[7].outport &= ~(pwm[7].pinmask); }
  if ( cnt == pwm[8].pwmvalue) { *pwm[8].outport &= ~(pwm[8].pinmask); }
}

When I increase the speed (400Hz) the problem is reduced, but still present. Having high frequency is also not good for mosftet for a temperature reason

I tested making a copy of the pwm Values but with the same results as before

ISR(TIMER2_COMPA_vect){ // version 4 = copy
  static uint8_t cnt = 0;
  static uint8_t v[9] = {0,0,0,0,0,0,0,0,0};
  if (++cnt == 0) {
    v[0] = pwm[0].pwmvalue;
    v[1] = pwm[1].pwmvalue;
    v[2] = pwm[2].pwmvalue;
    v[3] = pwm[3].pwmvalue;
    v[4] = pwm[4].pwmvalue;
    v[5] = pwm[5].pwmvalue;
    v[6] = pwm[6].pwmvalue;
    v[7] = pwm[7].pwmvalue;
    v[8] = pwm[8].pwmvalue;
    
    if (v[0]) *pwm[0].outport |= pwm[0].pinmask;
    if (v[1]) *pwm[1].outport |= pwm[1].pinmask;
    if (v[2]) *pwm[2].outport |= pwm[2].pinmask;
    if (v[3]) *pwm[3].outport |= pwm[3].pinmask;
    if (v[4]) *pwm[4].outport |= pwm[4].pinmask;
    if (v[5]) *pwm[5].outport |= pwm[5].pinmask;
    if (v[6]) *pwm[6].outport |= pwm[6].pinmask;
    if (v[7]) *pwm[7].outport |= pwm[7].pinmask;
    if (v[8]) *pwm[8].outport |= pwm[8].pinmask;
  }

  if ( cnt == v[0]) { *pwm[0].outport &= ~(pwm[0].pinmask); }
  if ( cnt == v[1]) { *pwm[1].outport &= ~(pwm[1].pinmask); }
  if ( cnt == v[2]) { *pwm[2].outport &= ~(pwm[2].pinmask); }
  if ( cnt == v[3]) { *pwm[3].outport &= ~(pwm[3].pinmask); }
  if ( cnt == v[4]) { *pwm[4].outport &= ~(pwm[4].pinmask); }
  if ( cnt == v[5]) { *pwm[5].outport &= ~(pwm[5].pinmask); }
  if ( cnt == v[6]) { *pwm[6].outport &= ~(pwm[6].pinmask); }
  if ( cnt == v[7]) { *pwm[7].outport &= ~(pwm[7].pinmask); }
  if ( cnt == v[8]) { *pwm[8].outport &= ~(pwm[8].pinmask); }
}

Can you understand where is the problem? If i move the blinking speed to 2000ms i see a stable dimmed light...

testPWM.ino (6.43 KB)

graph.jpg

I also tested the IRQ frequency this way:

volatile unsigned long minTime, maxTime;
//inside IRS
  static unsigned long last = micros();
  unsigned long now = micros();
  unsigned long delta = now-last;
  last = now;
  if (delta<minTime) minTime = delta;
  if (delta>maxTime) maxTime = delta;

getting values between 52 and 80 uS in with and without settings pwm intensity (no differences) so the irq call is performed well.

I'm probably getting the point, the version 2-3 is affected by a bug if values changes during the cycle because I switch off the led only on == and i could miss a time.

Version 4 flickers because there is a sort of alias (random sampling a very fast function, oscillating faster than the cycle)

I'm going to optimize the code to reduce the problem considering that updated arrives usually fast, suggests are appreciated

There is a technique called "bit-angle modulation" which can be more CPU efficient than traditional PWM. This would of course be a major re-write of the library code. Using BAM, all channels switch on/off together, and the intervals are, for example: 4ms, then 2ms, then 1ms, 500us, 250us, 125us, 62us, 31us, then back to 4ms and so on. If channel N is set to 193 for example, which is 0b11000001, then that channel would be on during the 4ms, 2ms and 31us periods and off during the other periods.

The problem is not a SoftPWM limitation but a physical one. SoftPWM have a PWM cycle at 60Hz, that makes impossible to update out faster than 17mS and that explain why I see low frequency trying to change the state with every 10 ms.

Hardcoding the IRS I can reach a much faster PWM cycle, keeping the same PWM frequency I have roughly 60% less of CPU used. With a full CPU load and latest version of the code I can reach ~600Hz PWM cycle. I decided to use a ~300PWM cycle to have enough CPU to perform other tasks (mainly handle RF24 to receive packets @ 60Hz and, if needed, forward them too other bases)

I also tried to calculate in a more accurate way the "remaining time" to the end of the pwm cycle and to adjust the next cycle according to the calculated values. The problem is that it tends to be resource consuming without a real benefit to the visual effect