Digital write speed/frequency affected by analog read values

Hi all. I’d like to quickly start by saying I am not a programmer. I’ve never taken a class or read a book so please excuse my poor methods, organization, and lack of comments. My code below was developed somewhat of necessity for a problem at work.

Goal: Use digital write to generate my own “PWM” output at a lower frequency to increase resolution (lower frequency means longer on/off time and more discrete states than at the standard 490Hz of the analog write function). Ultimately, I would like 10-bit resolution @ 200Hz. This output is then passed through an RC filter to another analog device with 10-bit resolution. (I used this guide for high-speed digital writing: http://www.instructables.com/id/Arduino-is-Slow-and-how-to-fix-it/?ALLSTEPS)

What happens: On an oscilloscope, pulse width is fine but the frequency is approximately 216Hz when a 10k analog input pot is at 0v, 226Hz from 0.005v to 4.995v, and 240Hz when input is at 5v.

What I’ve checked:

  • Removed code that’s not shown. This code and the larger, original program behave the same way
  • Changed the on and off time of the digital output to finite values rather than a variable
  • Used a previous version of my program to verify this problem isn’t present using analog write and the same device (Mega)
  • Changed the output pin from D2 to D11.

Any ideas? Unless I’m missing something, the analog read actually has no impact on digital write…there’s no code that should be connecting the two. Does analog read generate different amounts of load on the CPU when the input is at 0v, 0<x<5v, and 5v?

int Loadpin = 0;            // Physical pin number of the Load potentiometer
int Loadval = 0;            // Raw 10-bit value of the Load potentiometer analog pin
float Loadpct = 0;          // Scaled value of the Load pin (0.0 - 100.0%)
int Loadint = 0;            // Converted integer value of the float "Loadpct"
int Load = 0;               // Load value aftering being descretized into 5% increments
 
const long screenrefresh = 150;
unsigned long previousMillis = 0;
const long PWMperiod = 4095;
unsigned long previousMicros = 0;
 
void setup() {
Serial.begin(115200);
}
 
void loop() {
unsigned long currentMillis = millis();
unsigned long currentMicros = micros();
Loadval = analogRead(Loadpin);          // read the Load potentiometer input
Loadpct = Loadval/1023.0*100.0;         // Scale the value from 0-1023 to 0.0-100.0
Loadint = Loadpct;                      // Convert float back to integer for if/else statement
 
if (Loadint >= 0 && Loadint <7)
{Load = 5;}
else if (Loadint >= 7 && Loadint <12)
{Load = 10;}
else if (Loadint >= 12 && Loadint <17)
{Load = 15;}
else if (Loadint >= 17 && Loadint <22)
{Load = 20;}
else if (Loadint >= 22 && Loadint <27)
{Load = 25;}
else if (Loadint >= 27 && Loadint <32)
{Load = 30;}
else if (Loadint >= 32 && Loadint <37)
{Load = 35;}
else if (Loadint >= 37 && Loadint <42)
{Load = 40;}
else if (Loadint >= 42 && Loadint <47)
{Load = 45;}
else if (Loadint >= 47 && Loadint <52)
{Load = 50;}
else if (Loadint >= 52 && Loadint <57)
{Load = 55;}
else if (Loadint >= 57 && Loadint <62)
{Load = 60;}
else if (Loadint >= 62 && Loadint <67)
{Load = 65;}
else if (Loadint >= 67 && Loadint <72)
{Load = 70;}
else if (Loadint >= 72 && Loadint <77)
{Load = 75;}
else if (Loadint >= 77 && Loadint <82)
{Load = 80;}
else if (Loadint >= 82 && Loadint <87)
{Load = 85;}
else if (Loadint >= 87 && Loadint <92)
{Load = 90;}
else if (Loadint >= 92 && Loadint <97)
{Load = 95;}
else
{Load = 100;}
 
  if (currentMicros - previousMicros >= 4096) {
    PORTB |= _BV(PB5);
    previousMicros = currentMicros;
  }
  if (currentMicros - previousMicros >= 2048) {
  PORTB &= ~_BV(PB5);
}
 
Serial.print(Load);
Serial.print(" | ");
Serial.println(Load);
}

Thank you very much for any assistance!

analogRead() is fairly slow - the ADC conversion process is not instantaneous.

Why not use Timer 1 to generate the PWM with the 16-bit hardware timer? If you take over the timer, you can get 16-bit PWM - either full 16 bit, or counting up to a number between 1 and 2^16 (ie, allowing exact control over the frequency), and it will be done in the background like analogWrite() so your code doesn't throw off the timing. You can do it directly by manipulating the registers, and I think someone has a Timer1 library that provides wrappers around some of that functionality. Take a look at chapter 16 of the datasheet, pg 112 through 132.

Thank you DrAzzy, I will definitely look into that. As I'm not yet familiar with that method, do you foresee a limit to how many digital writes I can make referencing Timer 1? I should have mentioned that I currently have this single digital output, but my larger program has 12 and someday as many as 26 would be great. If I use Timer 1 as a counter I should be able to use a simple "if" statement to compare the current value to preset values to trigger the outputs, correct?

Thanks again!
Dylan

Here is some code I wrote for using Timer1 for PWM. You can get 16-bit resolution at 244 Hz and 10-bit resolution at 15624 Hz.

/*
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);
}

// ***************  EXAMPLE CODE, REPLACE WITH YOUR OWN SKETCH ************
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() {
}

Thanks a lot johnwasser, I think your sample code and DrAzzy's response are plenty to get me going in the right direction. Appreciate the comments and all those who took the time to read!

Dylan