Change PWM pulse width while running WITHOUT using Arduino AnalogWrite

Go To Last Post
22 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have found countless examples for setting PWM frequency and duty cycle settings (without using the Arduino AnalogWrite()), but I've found all of them will not change their initial settings by writing new OCR#A/B register values. How can the pulse width be varied while running ?

This topic has a solution.

Ray Pasco

Last Edited: Tue. Jun 21, 2022 - 06:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Changing OCR will change the pulse width on the fly, but the timer needs to be setup correctly and you need to take the output from the correct pin, etc.

 

Starting output with AnalogWrite() and then trying to adjust the pulse width by accessing OCR is almost certain to end with sobbing.

Brian Fairchild wrote:

It's at this point that we really do need the OP to come back and engage with us. So many questions..........

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

After the initial configuration I get exactly the frequency and pulse widths I expect. After its running, writing new values to OCRA/B have no effect at all !

 

Can you show me demo code that does get the pulse width to change after the counter is already running ?

Ray Pasco

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0



After its running, writing new values to OCRA/B have no effect at all !

That essentially makes zero sense (as such matters often do).

  • Is your output really dependent on OCRA/B for the mode you are in?
  • Are you actually executing the setting of the new reg value code (not being blocked by some wrong conditional).
  • Are you actually using different values?
  • Are you changing the values far too fast (often) to see a reaction? 

where is your code?

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
/* M328p_FastPWM_Timer2.ino   2022-06-18

####  ONCE SET, CANNOT CHANGE FREQ OR DUTY CYCLE  ####

'Secrets of Arduino PWM'  https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM#pwm-examples

First code example 'Fast PWM Mode'
"The following code fragment sets up fast PWM on pins 3 and 11 (Timer 2).  ..."
*/

static byte OCR2B_value;

int main (void) {

    pinMode(11, OUTPUT);    // OC2A D11 PB3
    pinMode( 3, OUTPUT);    // OC2B D3  PD3

    // Fast PWM Mode, Phase Correct:
    //   Clear OC2A at BOTTOM;
    //   Set OC2A on Compare Match up-counting;
    //   Clear   OC2B at BOTTOM;
    //   Set OC2B on Compare Match up-counting;
    //   Update (double-buffered) OC2A at BOTTOM;
    //            ---v          ---v
    // TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
    //                                       ---^         ---^
    // WGM2..0 = 0b011 : Fast PWM Mode, TOV flag set on Max

    // Phase Correct Fast PWM Mode:
    TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);

    TCCR2B = _BV(CS22);  // 4 usec per clock.

    OCR2A = 180;
    OCR2B = 50;    

    /*
    int ix;
    for(ix=0; ix<=180; ix++) {
        OCR2B = ix;
        delay(25);
    }
    */
}

The phase correct PWM mode is working perfectly, initially. When the FOR loop is uncommented it has no effect at all. The waveforms are shown in the attachment.

Attachment(s): 

Ray Pasco

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ray Pasco wrote:
 Can you show me demo code that does get the pulse width to change after the counter is already running

 

// 8-bit Timer/Counter0 with PWM
// ATmega328P, 16MHz clock
// Output: PD6  OC0A  (Arduino Pin 6)
#include <avr/io.h>
#define F_CPU 16000000
#include <util/delay.h>

int main(void)
{
  DDRD = (1<<DDD6);                                 // PD6 output for OC0A
  TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);   // PWM mode
  TCCR0B = (1<<CS01) | (1<<CS00);                   // clock frequency/64
  OCR0A = 25;                                       // duty cycle

  while(1)
  {
    _delay_ms(500);
    OCR0A = 127;
    _delay_ms(500);
    OCR0A = 25;
  }
}

 

The truth is more important than the facts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go on.  You just set OCnx pins for OUTPUT,  PWM mode registers to required speed, mode, ...

 

Then alter the duty cycle via OCRnx register.

 

I have no idea which speed, mode etc is set up by Arduino initialization code.

But I guess that analogWrite(pin, val) simply sets OCnx to OUTPUT and OCRnx to duty cycle.

 

Of course TCCRnx registers might get re-initialized on every analogWrite()

All Arduino code is public.  You can read it for yourself.   Or simply read the TCCRnx registers yourself.

 

Arduino functions need to be portable.  PWM will be different on SAMD21, SAM3X, STM32, ...

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

did you check WGM bits to see if you are setting the mode you desire?

something is wrong/odd with your loop  ...the loop stops and never repeats...so eventually you will not see any change, since no new ocr values are  being written.

So if it is already over, you will say nothing is changing.

I prefer using uint8_t or unitn16_t when possible.

 

 

 

 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Mon. Jun 20, 2022 - 06:30 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am specifically NOT using analogWrite.

Ray Pasco

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PWM is very simple.   4 Special Function Registers.

DDRx

TCCRnA

TCCRnB

OCRnx

 

Once you have configured the first 3 SFRs with = you can change duty-cycle by writing to the 4th reg with =

 

If you try to use |= on an Arduino you do not set the correct value.

The datasheet explains the PWM modes, COMn modes, ...

 

If you have a problem paste your actual assignment statements.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have already  posted my code.

Nowhere is the "|=" operator used.

 

I have already produced the initial PWM signals I want. I simply can't get new values of OCR2A and OCR2B to "take hold".

 

I have gotten poftamunk's code to work by substituting that code line-by-line into my program. Both OCRA and OCRB output are correct.

 

There is definitely a problem with how Arduino processes their own special "language". Or, at least, it has non-obvious side effects.

I need to pin those down and avoid them like the Plague !

Ray Pasco

Last Edited: Tue. Jun 21, 2022 - 02:06 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

I ran your program on a Uno clone.  It behaved as expected.  

Saleae are forever changing their software.  EN channel is PB3 (duty = 70.59%, freq = 488Hz).  RST channel is PD3 (duty = 19.61%).

I also re-wrote it in my own style.   And got exactly the same result.

    DDRB |= (1 << PB3);    // OC2A D11 PB3
    DDRD |= (1 << PD3);    // OC2B D3  PD3
    TCCR2A = (2 << COM2A0) | (2 << COM2B0) | (1 << WGM20); //WGM=1

    TCCR2B = (4 << CS20);  // div 64

    OCR2A = 180;           // 70% duty
    OCR2B = 50;            // 20% duty

 

Mind you,  I was expecting 16MHz div 64 to be 1.9073ms period.   The Logic Analyser reports 2.048ms.   The clone has a SMD Resonator (Arduino design is crap)

Do you really want "Phase Correct"?  "Fast PWM" seems more intuitive for most apps.

 

David.

 

Last Edited: Tue. Jun 21, 2022 - 06:29 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@OP

 

Did you miss this comment?...

 

avrcandies wrote:

something is wrong/odd with your loop  ...the loop stops and never repeats...so eventually you will not see any change, since no new ocr values are  being written.

 

The code fragment in question is this...

 

Ray Pasco wrote:

    int ix;
    for(ix=0; ix<=180; ix++) {
        OCR2B = ix;
        delay(25);
    }

 

...and you said...

 

Quote:

When the FOR loop is uncommented it has no effect at all.

 

Well it does have an effect but since it only runs for 180 x 25us = 4.7ms it's unlikely that you'll notice it as it's only about 2 cycles of your output waveform.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

The loop works fine.

At the left side of the screen duty is 70.34%, 70.59%, 0.00%, 0.39%, 0.79%, 1.18%, ..., 4.31% at the right hand side.   Yes,  there are about 12 periods per loop.

The screenshot is from

void setup()
{
    DDRB |= (1 << PB3);    // OC2A D11 PB3
    DDRD |= (1 << PD3);    // OC2B D3  PD3
    TCCR2A = (2 << COM2A0) | (2 << COM2B0) | (5 << WGM20); //WGM=3
    TCCR2B = (4 << CS20);  // div 64
    OCR2A = 180;           //70% duty
    OCR2B = 50;            //20% duty
}

void loop()
{
    int ix;
    for(ix=0; ix<=180; ix++) {
        OCR2B = ix;
        delay(25);         // ~ 12 PWM periods per loop
    }
}

 

Call me simple but I get exactly the same result from Arduino code i.e.

void setup()
{
    pinMode(11, OUTPUT);     // OC2A D11 PB3
    pinMode( 3, OUTPUT);     // OC2B D3  PD3
    analogWrite(11, 180);    // OC2A D11 PB3
    analogWrite(3, 50);      // OC2B D3  PD3
}

void loop()
{
    int ix;
    for(ix=0; ix<=180; ix++) {
        analogWrite(3, ix);
        delay(25);           // ~ 12 PWM periods per loop
    }
}

 

And this seems to be perfectly clear to a regular pot head.   And more importantly it will run on any Arduino target with PWM pins on D3, D11.

 

 

 

 

Last Edited: Tue. Jun 21, 2022 - 08:43 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

The loop works fine.

 

But you've got it inside 'loop()' which is just a hidden 'while(1)' whereas the code shown us by the OP back in #5 has it at the end of 'int main (void)' so it'll run once, main() will then exit and crash into the catchall hidden 'while (1)' that the compiler inserts.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The code in #5 is a bit of a mish-mash.    Using main() removes the Arduino Timer initialization code.   So analogWrite() and delay() will not work.

The code in #6 is regular GCC.   i.e. does not expect Arduino functions.

 

My example in #14 shows valid Arduino code.  i.e. setup() and loop()

It also shows that you can write OCR2B = val instead of analogWrite(3, val) if you have a conspiracy theory about Jewish Space Lasers.

 

I have not bothered to inspect the Arduino library code.   I guess that analogWrite() has to lookup which particular Timer and OCRnx register is mapped to Digital#3.

Execution time is not significant.

 

I would advise sticking to Arduino code.   If you find that it is not fast enough,  just say so.   Then you can forgo the portability and write directly to OCR2B.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino AnalogWrite() will produce PWM on any number of pins, so it must be doing more in software that the ATmega328 can't do in hardware. In particular, there's no promise of which timer will be used or in which mode. The same timer is very likely used for multiple purposes and so mixing Arduino and bare metal code can be dangerous. The compiler might have completely different plans, and as far as I know there is no mechanism to tell it, for example, that you're using timer 2 so hands off. All you can do is try it. If it works on your board then it works.

 

The OP did eventually share their code, so we know that except for pinMode() and delay(), they did not mix Arduino and bare metal code.

 

Having said all of that,  I have used AnalogWrite() many times and found it to work reliably, though in those cases the uC was not over tasked, so the extra overhead above setting up a timer and using the assigned output pin was never a problem. If the OP has a lot of other things going on and would like PWM entirely in hardware, then they need to stick entirely to bare metal and not use any Arduino timing functions including delay(), at least not without risk of something not working as expected.

Brian Fairchild wrote:

It's at this point that we really do need the OP to come back and engage with us. So many questions..........

Last Edited: Tue. Jun 21, 2022 - 01:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

A Uno uses Timer0 for system timing e.g. millis(), micros(), delay()

It sets up all 3 Timers for PWM.

 

Which is why you can happily use Timer1, Timer2 safely for other purposes.   Just assign the new known values to the SFRs with =

Avoid analogWrite() on those particular pins.

Don't touch Timer0 unless you can afford to lose system time. 

 

Yes,  the default analogWrite() uses 488Hz Phase Correct PWM.   Exactly like my example in #14.

 

We don't know what the OP actually wants to achieve.

If he knows it will always be ATmega328P it is pretty simple to set up the registers on Timer2  e.g. #14

 

eugene the jeep wrote:
Starting output with AnalogWrite() and then trying to adjust the pulse width by accessing OCR is almost certain to end with sobbing.

That should work just fine.   But I would choose one or the other.

 

David.

Last Edited: Tue. Jun 21, 2022 - 01:47 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for all the useful code and tips.

 

To sum it up, these are the important things I've learned and discovered while using the Arduino IDE:

 

The |= operator works fine for register assignments.

 

Bare metal (AVR) code and Arduino "predefined statements" may be mixed freely. How about that ?! It just works.

 

The most common GCC #define's are automatically included.

No need for statements such as #include <util/delay.h>. It's magic ! They are already in effect.

 

The IDE freely accepts either Arduino void setup()/void loop() or GCC void main() program structure.

 

Arduino's delay() function uses timer0 while AVR _delay() uses software cycle counting.

The corollary is that Arduino documentation is not great, to say the very least.

 

I think the most important idea I've take away from this is that "how-to" article authors consistantly miss the fact that newbies should see register setup code such as

    TCCR2A |= (0<<WGM22) | (1<<WGM21) | (1<<WGM20);   // WGM22:0  = 0b011 Fast PWM Mode.

                                                                                                     // Update of OCR2A/B output pins at BOTTOM.

    // or:

 

    TCCR2A | = (0b011<< WGM20) :   // WGM22:0  Fast PWM Mode.

                                                            // Update of OCR2A/B output pins at BOTTOM.

and

    TCCR2A = 0; // Reset any previously set '1' bits.

                // Register forced to '0' after hard resets.
    TCCR2A |= _BV(COM2A1);   // COM2A1:0 = 0b10 Clear OC2A on Compare Match,
                             // Set at BOTTOM. (Ard D11, AVR PB3)
    TCCR2A |= _BV(COM2B1);   // COM2B1:0 = 0b10 Clear OC2B on Compare Match,
                             // Set at BOTTOM. (Ard D3,  AVR PD3)

Some OCD is not required, but, its certainly useful for avoiding mistakes. I've been busy cross referencing Arduino pins such as "3", meaning "Arduino D3",  the default AVR output digital function pins and pin names like "OC2A".

 

 

Ray Pasco

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ray Pasco wrote:

...

The |= operator works fine for register assignments.

...
    TCCR2A = 0; // Reset any previously set '1' bits.

                // Register forced to '0' after hard resets.
    TCCR2A |= _BV(COM2A1);   // COM2A1:0 = 0b10 Clear OC2A on Compare Match,
                             // Set at BOTTOM. (Ard D11, AVR PB3)
    TCCR2A |= _BV(COM2B1);   // COM2B1:0 = 0b10 Clear OC2B on Compare Match,
                             // Set at BOTTOM. (Ard D3,  AVR PD3)

Yes.  If you initialize to 0 first !!

 

You end up with an LDI, STS, ORI, STS, ORI, STS when a single LDI, STS would have given you a known assignment.

And some  registers can use OUT

 

Yes,  there are occasions when |= and &=~ are appropriate.  e.g. DDRB, PORTB, ...

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Arduino AnalogWrite() will produce PWM on any number of pins, so it must be doing more in software that the ATmega328 can't do in hardware.

Um.  Nope.  AnalogWrite() precisely works on the ATmega328 pins that support PWM in hardware.  Two pins for each timer.  Timer0 is also "leveraged" to provide the millis() clock tick.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I stand corrected. Thank you.

Brian Fairchild wrote:

It's at this point that we really do need the OP to come back and engage with us. So many questions..........