Micros() on ATTiny85 is off by a factor of 2 for me

I have a project going on where everything is working as it should, except that a routine that relies on micros is off by a factor of 2 when using 8 or 16 MHz internal, and off by a factor of 2.4 when using internal 1 MHz clock. I am assuming there is simply something I am unaware of, but can't figure out what, for the life of me.

I have stripped the code down to its barebones essentials in the block below. The idea is that I have a lookup table for a single full cycle of a sine wave and I am going to be outputting the sine wave as a series of PWM steps. So in the updatePWM() routine, it uses micros() to get the current time, compare to the last time I updated the PWM, and then updates if it is longer than the amount of time to dwell at each step. So I am just holding the PWM duty cycle constant until it's time for moving on to the next entry. The LED flashes just like I would expect, except for the fact that the amount of time to complete a cycle is wrong. Am I doing something wrong here? I will eventually have a time control to allow for full cycle times of 100 ms to 5 seconds. Any help would be greatly appreciated. Thanks!

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

// Assign pins of ATTiny85, make sure it runs at 8 MHz internal oscillator

//DigitalPin Assignments
const unsigned int pwm2 = 4; //PWM out on OC1B/PCINT4/Pin3
const unsigned int pwm1 = 1; //PWM out on OC1A/PCINT1/Pin6

//Analog Pin Assignments
const unsigned int rate = A1; //This will be an analog read of speed pot 1 on ADC1/PCINT2/Pin7

unsigned long rateTime; // Current time value compared to the previous time to see if we need to move to the next table entry.
unsigned long lastTime; // Used for keeping track of whether we move to the next entry in our sineTable or not
unsigned long maxTime = 5e6; // Max time of 5s. This doesn't seem actually achievable, but whatever.
unsigned long minTime = 1e4; // Min time of 100 ms. This doesn't seem actually achievable, but whatever.
const uint8_t tableLength = 255; //Number of entries in our tables below
uint8_t inx = 0; //Index to read out of the table for PWM1, start at the beginning
int dutyCycle;
const uint8_t OCR1C_val = 127;

// Create a table for the PWM wave. Values are for 8 bit PWM (max value 255).
// Put it in flash memory so that it doesn't eat up our dynamic SRAM

//Sine
const uint8_t sineTable[] PROGMEM = {127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,179,181,184,187,190,193,195,198,200,203,205,208,210,213,215,217,219,221,223,225,227,229,231,233,235,236,238,239,
241,242,243,245,246,247,248,249,250,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,252,252,251,251,250,249,248,247,246,245,244,243,241,240,239,237,235,234,232,230,228,226,224,222,220,218,216,214,211,209,
207,204,202,199,196,194,191,188,186,183,180,177,174,171,168,166,163,159,156,153,150,147,144,141,138,135,132,129,125,122,119,116,113,110,107,104,101,98,95,91,88,86,83,80,77,74,71,68,66,63,60,58,55,52,50,47,45,43,40,38,36,
34,32,30,28,26,24,22,20,19,17,15,14,13,11,10,9,8,7,6,5,4,3,3,2,2,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,4,5,6,7,8,9,11,12,13,15,16,18,19,21,23,25,27,29,31,33,35,37,39,41,44,46,49,51,54,56,59,61,64,67,70,73,75,78,81,84,87,90,93,
96,99,102,105,108,111,115,118,121,124};

void setup() {

  //Define pin modes
  pinMode(pwm1, OUTPUT);
  pinMode(pwm2, OUTPUT);
  pinMode(rate, INPUT);

  // Initialize pin states
  digitalWrite(pwm1,LOW);
  digitalWrite(pwm2, LOW);

  lastTime = micros(); // Get an initial value

  // Set up timer/PWM items
  PLLCSR |= (1 << PLLE); //Enable PLL

  //Wait for PLL to lock
  while ((PLLCSR & (1<<PLOCK)) == 0x00)
    {
        // Do nothing until plock bit is set
    }

  // Enable clock source bit
  PLLCSR |= (1 << PCKE);

  // Set prescaler to PCK, turn on PWM1A, and set COM1A bits to match the COM1B bits due to attiny bug
  TCCR1 = 0;
  TCCR1 |= (1 << CS10) | (1<<PWM1A) | (1 << COM1A1);
  
  // Enable OCRB output on PB4, configure compare mode and enable PWM B
  DDRB |= (1 << PB4) | (1 << PB1);
  GTCCR |= (1 << PWM1B) | (1 << COM1B1);

  // Set OCR1A and OCR1B compare values and OCR1C TOP value
  OCR1C = OCR1C_val;
  OCR1A = 20; // Give an initial PWM duty cycle
  OCR1B = 20; // Give an initial PWM duty cycle
}

void loop() {

  updatePWM();

}



//Update PWM outputs based on above settings
void updatePWM() {

  rateTime = 5e5; //Should be 0.5 seconds for a full cycle
  //Convert microsecond period into amount of time between subsequent samples
  unsigned long rateStep = round(rateTime/OCR1C_val);
  
  //Get the current time
  unsigned long currTime = micros();

  //Compare current time to last time we updated for each PWM out
  if ((currTime - lastTime) > rateStep) {
    //We have met the time threshold for PWM, so go to the next value in the table
    dutyCycle = pgm_read_byte(sineTable + inx);
    // Set both PWM's
    int mappedPWM = map(dutyCycle,0,255,0,OCR1C_val);
    OCR1B = mappedPWM;
    OCR1A = mappedPWM;

    inx += 1; //Increment the read index for PWM
    
    // Go back to the beginning of the table if we have gotten to the end
    if (inx == tableLength) {
      inx = 0;
    }
    
    lastTime = micros();
  }//End of if ((currTime - lastTime) > rateStep)

}// end of updatePWM()

It may come up - which core are you using?

I am using the avrdude attiny core, the library by David Mellis, I believe. I'm having a hard time finding the specific release, but I updated/installed it just a month ago or so.

I would start debugging by looking very carefully at the update timing.

It may simply be a case of too many machine cycles for the allotted time.

Also, did you note the warning in the timer section of the data sheet, about when in the timing cycle you are allowed to update the OCR registers? Doing so at the wrong time could easily account for a factor of two timing error.

I see you are changing the timer1. Maybe the core you are using is also using timer1 for the Micros().

You could test by using timer0 instead and leave timer1 untouched.

Or try Attinycore (available in boards manager)

I just tried with Attinycore and I get the same results, i.e., a 500 ms full cycle takes exactly 1 s.

If it were a case of too many machine cycles, I would expect that overhead to become less and less significant as I increased the full cycle time of my waveform output. However, regardless of what I set as the full cycle time for the waveform, I see it off by a factor of two right now, which suggests that there is something else at play (possibly in addition to any overhead that I might need to account for).

Another guess.
Maybe the core is setting the LSM bit in PLLCSR.
in wiring.c for attinycore it seems so at line 949.
Not that I checked all the way if this bit is actually set.
The quickest way to test would be to check the status of that bit and/or clear it first, before you set anything else.

who knows.

Or skip the core initialization by replacing setup and loop with "int main(void)". I tend to do that if I want to be sure everything is in hardware default state when I start.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

// Assign pins of ATTiny85, make sure it runs at 8 MHz internal oscillator

//DigitalPin Assignments
const unsigned int pwm2 = 4; //PWM out on OC1B/PCINT4/Pin3
const unsigned int pwm1 = 1; //PWM out on OC1A/PCINT1/Pin6

//Analog Pin Assignments
const unsigned int rate = A1; //This will be an analog read of speed pot 1 on ADC1/PCINT2/Pin7

unsigned long rateTime; // Current time value compared to the previous time to see if we need to move to the next table entry.
unsigned long lastTime; // Used for keeping track of whether we move to the next entry in our sineTable or not
unsigned long maxTime = 5e6; // Max time of 5s. This doesn't seem actually achievable, but whatever.
unsigned long minTime = 1e4; // Min time of 100 ms. This doesn't seem actually achievable, but whatever.
const uint8_t tableLength = 255; //Number of entries in our tables below
uint8_t inx = 0; //Index to read out of the table for PWM1, start at the beginning
int dutyCycle;
const uint8_t OCR1C_val = 127;

// Create a table for the PWM wave. Values are for 8 bit PWM (max value 255).
// Put it in flash memory so that it doesn't eat up our dynamic SRAM

//Sine
const uint8_t sineTable[] PROGMEM = {127, 130, 133, 136, 139, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 181, 184, 187, 190, 193, 195, 198, 200, 203, 205, 208, 210, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 236, 238, 239,
                                     241, 242, 243, 245, 246, 247, 248, 249, 250, 250, 251, 252, 252, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 253, 253, 252, 252, 251, 251, 250, 249, 248, 247, 246, 245, 244, 243, 241, 240, 239, 237, 235, 234, 232, 230, 228, 226, 224, 222, 220, 218, 216, 214, 211, 209,
                                     207, 204, 202, 199, 196, 194, 191, 188, 186, 183, 180, 177, 174, 171, 168, 166, 163, 159, 156, 153, 150, 147, 144, 141, 138, 135, 132, 129, 125, 122, 119, 116, 113, 110, 107, 104, 101, 98, 95, 91, 88, 86, 83, 80, 77, 74, 71, 68, 66, 63, 60, 58, 55, 52, 50, 47, 45, 43, 40, 38, 36,
                                     34, 32, 30, 28, 26, 24, 22, 20, 19, 17, 15, 14, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 51, 54, 56, 59, 61, 64, 67, 70, 73, 75, 78, 81, 84, 87, 90, 93,
                                     96, 99, 102, 105, 108, 111, 115, 118, 121, 124
                                    };

int main(void) {
  //Define pin modes
  pinMode(pwm1, OUTPUT);
  pinMode(pwm2, OUTPUT);
  pinMode(rate, INPUT);

  // Initialize pin states
  digitalWrite(pwm1, LOW);
  digitalWrite(pwm2, LOW);

  lastTime = micros(); // Get an initial value

  // Set up timer/PWM items
  PLLCSR |= (1 << PLLE); //Enable PLL

  //Wait for PLL to lock
  while ((PLLCSR & (1 << PLOCK)) == 0x00)
  {
    // Do nothing until plock bit is set
  }

  // Enable clock source bit
  PLLCSR |= (1 << PCKE);

  // Set prescaler to PCK, turn on PWM1A, and set COM1A bits to match the COM1B bits due to attiny bug
  TCCR1 = 0;
  TCCR1 |= (1 << CS10) | (1 << PWM1A) | (1 << COM1A1);

  // Enable OCRB output on PB4, configure compare mode and enable PWM B
  DDRB |= (1 << PB4) | (1 << PB1);
  GTCCR |= (1 << PWM1B) | (1 << COM1B1);

  // Set OCR1A and OCR1B compare values and OCR1C TOP value
  OCR1C = OCR1C_val;
  OCR1A = 20; // Give an initial PWM duty cycle
  OCR1B = 20; // Give an initial PWM duty cycle


  while (1) {
    updatePWM();
  }
}



//Update PWM outputs based on above settings
void updatePWM() {

  rateTime = 5e5; //Should be 0.5 seconds for a full cycle
  //Convert microsecond period into amount of time between subsequent samples
  unsigned long rateStep = round(rateTime / OCR1C_val);

  //Get the current time
  unsigned long currTime = micros();

  //Compare current time to last time we updated for each PWM out
  if ((currTime - lastTime) > rateStep) {
    //We have met the time threshold for PWM, so go to the next value in the table
    dutyCycle = pgm_read_byte(sineTable + inx);
    // Set both PWM's
    int mappedPWM = map(dutyCycle, 0, 255, 0, OCR1C_val);
    OCR1B = mappedPWM;
    OCR1A = mappedPWM;

    inx += 1; //Increment the read index for PWM

    // Go back to the beginning of the table if we have gotten to the end
    if (inx == tableLength) {
      inx = 0;
    }

    lastTime = micros();
  }//End of if ((currTime - lastTime) > rateStep)

}// end of updatePWM()

However micros() may be off now (even more), but at least you are in a known state and can write your own timekeeping.

Well, I found the source of the off by 2 thing. Turns out that my OCR1C value was set to 127, but based on my CS bits, the corresponding top value should be 255. That just so happens to be a factor of 2. Correcting the OCR1C has resolved the issue with my simple test code. Once I put in reads for UI controls and such, it's obvious that I need to re-work my code so that I am not polling ADC's too often and such, as that was adding significant time to a loop through main(). I appreciate all the thoughts and help!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.