12-bit analogWrite on breadboard atmega328

Is it possible to get 12-bit PWM on an Atmega328 breadboard?

I'm used to using analogWriteResolution on Teensy boards and now I need higher resolution on my atmega328 based board....

Google "12-bit PWM on a Atmega328" for several hits on the subject.

Here’s an example using Timer 1 in WGM15 mode:

const uint8_t pinPWM = 10;      //pin for OC1B

void setup() 
{    
    pinMode( pinPWM, OUTPUT );
    digitalWrite( pinPWM, 0 );
    
    TCCR1A = _BV(COM1B1) | _BV(WGM11) | _BV(WGM10);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);       // prescaler /8 gives period of ~2.048mS
    OCR1A = 0x0fff;     //period

    OCR1B = 0x0;        //~0% duty
    
}//setup

void loop() 
{
    for( int16_t i=0; i<0x1000; i++ )
    {
        //if you want true 0% DC, digitalWrite( pinPWM, 0) and disconnect OC1B from pin
        //if you want true 100% DC, digitalWrite( pinPWM, 1) and disconnect OC1B from pin
        //if you don't need true 0% or true 100% can omit checks and digitalWrite...
        if( i == 0 || i == 0x0fff )
        {            
            digitalWrite( pinPWM, (i==0) ? LOW:HIGH );
            TCCR1A &= ~_BV(COM1B1);            
        }
        else
        {
            TCCR1A |= _BV(COM1B1);            
            OCR1B = i;
            
        }//else
        
        delay(5);        
        
    }//for

}//loop

Here is another example of using Timer1 to get higher resolution PWM:

/*
  Demonstration of 16-bit PWM on Timer1
  Written by John Wasser
  
  PWM16Begin(): Set up Timer1 for PWM.
  PWM16EnableA(): Start the PWM output on Pin 9
  PWM16EnableB(): Start the PWM output on Pin 10
  PWM16A(unsigned int value): Set the PWM value for Pin 9.
  PWM16B(unsigned int value): Set the PWM value for Pin 10.
*/


// Set 'TOP' for PWM resolution.  Assumes 16 MHz clock.
// const unsigned int TOP = 0xFFFF; // 16-bit resolution.   244 Hz PWM
// const unsigned int TOP = 0x7FFF; // 15-bit resolution.   488 Hz PWM
// const unsigned int TOP = 0x3FFF; // 14-bit resolution.   976 Hz PWM
// const unsigned int TOP = 0x1FFF; // 13-bit resolution.  1953 Hz PWM
const unsigned int TOP = 0x0FFF; // 12-bit resolution.  3906 Hz PWM
// const unsigned int TOP = 0x07FF; // 11-bit resolution.  7812 Hz PWM
// const unsigned int TOP = 0x03FF; // 10-bit resolution. 15624 Hz PWM

void PWM16Begin()
{
  // Stop Timer/Counter1
  TCCR1A = 0;  // Timer/Counter1 Control Register A
  TCCR1B = 0;  // Timer/Counter1 Control Register B
  TIMSK1 = 0;   // Timer/Counter1 Interrupt Mask Register
  TIFR1 = 0;   // Timer/Counter1 Interrupt Flag Register
  ICR1 = TOP;
  OCR1A = 0;  // Default to 0% PWM
  OCR1B = 0;  // Default to 0% PWM


  // Set clock prescale to 1 for maximum PWM frequency
  TCCR1B |= (1 << CS10);


  // Set to Timer/Counter1 to Waveform Generation Mode 14: Fast PWM with TOP set by ICR1
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << WGM12) ;
}


void PWM16EnableA()
{
  // Enable Fast PWM on Pin 9: Set OC1A at BOTTOM and clear OC1A on OCR1A compare
  TCCR1A |= (1 << COM1A1);
  pinMode(9, OUTPUT);
}


void PWM16EnableB()
{
  // Enable Fast PWM on Pin 10: Set OC1B at BOTTOM and clear OC1B on OCR1B compare
  TCCR1A |= (1 << COM1B1);
  pinMode(10, OUTPUT);
}


inline void PWM16A(unsigned int PWMValue)
{
  OCR1A = constrain(PWMValue, 0, TOP);
}


inline void PWM16B(unsigned int PWMValue)
{
  OCR1B = constrain(PWMValue, 0, TOP);
}




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


  // On the Arduino UNO T1A is Pin 9 and T1B is Pin 10


  //  PWM16A(0);  // Set initial PWM value for Pin 9
  //  PWM16EnableA();  // Turn PWM on for Pin 9


  PWM16B(0);  // Set initial PWM value for Pin 10
  PWM16EnableB();  // Turn PWM on for Pin 10
}


void loop()
{
  int analogValue = analogRead(A0);
  unsigned int PWMValue = map(analogValue, 0, 1023, 0, TOP);
  PWM16B(PWMValue);  // Update the PWM at the end of the current cycle
}

Only timer1 has more than an 8bit counter (its 16 bits.). That probably means you could get two 16bit pwms, or only one 12bit.

westfw:
Only timer1 has more than an 8bit counter (its 16 bits.). That probably means you could get two 16bit pwms, or only one 12bit.

If you use WGM 10 (FastPWM mode that uses ICR1 for TOP) you can use both OCR1A and OCR1B for PWM. There is also WGM 8 (Phase and Frequency Correct PWM that uses ICR1 for TOP) but that gives you half the frequency: 1953 Hz.

Thanks for the quick replies....I tried the code provided by johnwasser but it doesn't seem to be working.

I'm verifying in two ways....first is to plug directly into my Clearpath servo and then read it's measured duty cycle.

Second is with my multi-meter that has a duty cycle function.

I'm hard coding a 50% output but reading 0.

FYI...I'm trying to run this code on an atmega328 breadboard without oscillator. It just occurred to me that this info may be relevant.

ajlapp:

Quote from: ajlappI'm hard coding a 50% output but reading 0.

FYI...I'm trying to run this code on an atmega328 breadboard without oscillator. It just occurred to me that this info may be relevant.

The breadboard using the internal 8 MHz oscillator should work exactly the same as an Arduino UNO except the PWM frequency will be half (1953 Hz.).

When you say "hard coding 50%", what value are you passing?

Your sketch should look like this for 50% PWM on Pin 10 output:

void setup()
{
  PWM16Begin();

  //  PWM16A(TOP/2);  // Set 50% PWM value for Pin 9
  //  PWM16EnableA();  // Turn PWM on for Pin 9

  PWM16B(TOP/2);  // Set 50% PWM value for Pin 10
  PWM16EnableB();  // Turn PWM on for Pin 10
}

I was sending 2056...

Solved…

I was using pin 9 of the micro which is actually pin5 in Arduino. I need pin 13 of the micro. :o

Works great. Thanks!!