Sunday, January 1, 2012

Getting PWM frequency and duty cycle with Arduino

Here is a simple code that reads a PWM signal and calculates its frequency and duty cycle in real time. The lack of an oscilloscope motivated this code.

The star of the show is the built-in 'pulseIn()' function, which gives the length of a low or high pulse. With these two measurements the calculation of duty cycle and frequency of the PWM signal is straightforward.

I have two different versions, simple and slightly longer.

The slightly longer code takes a user-specified time delay over which the maximum length is taken to help prevent truncation of a signal. For example, if an input pulse of 10 ms has already been running for 2 ms and the pulseIn function is called, it may return 8 ms. I am actually not sure if this is the way pulseIn works, but I tried both. With the default Arduino PWM frequency of approximately 490 Hz (http://arduino.cc/en/Reference/analogWrite), the slightly longer code returns 490 +/- 3 Hz, while the simple code returns 500 +/- 3 Hz. I tested this over low and high duty cycles. So it appears the slightly longer code has slightly better accuracy.

Simple code:
//Reads a PWM signal's duty cycle and frequency.
#define READ_PIN 13

#define PWM_OUTPUT 10

static double duty;
static double freq;
static long highTime = 0;
static long lowTime = 0;
static long tempPulse;

void setup(){
pinMode(READ_PIN,INPUT);
Serial.begin(9600);
analogWrite(PWM_OUTPUT,230);
}

void loop(){
readPWM(READ_PIN);
Serial.println(freq);
Serial.println(duty);
}

//Takes in reading pins and outputs pwm frequency and duty cycle.
void readPWM(int readPin){
highTime = 0;
lowTime = 0;

tempPulse = pulseIn(readPin,HIGH);
if(tempPulse>highTime){
highTime = tempPulse;
}

tempPulse = pulseIn(readPin,LOW);
if(tempPulse>lowTime){
lowTime = tempPulse;
}

freq = ((double) 1000000)/(double (lowTime+highTime));
duty = (100*(highTime/(double (lowTime+highTime))));
}


Slightly longer code:
//Reads a PWM signal's duty cycle and frequency.
#define READ_PIN 13
#define READ_DELAY 100

#define PWM_OUTPUT 10

static double duty;
static double freq;
static long highTime = 0;
static long lowTime = 0;
static long tempPulse;
static long lastSeen;

void setup(){
pinMode(READ_PIN,INPUT);
Serial.begin(9600);
analogWrite(PWM_OUTPUT,230);
}

void loop(){
readPWM(READ_PIN);
Serial.println(freq);
Serial.println(duty);
}

//Takes in reading pins and outputs pwm frequency and duty cycle.
void readPWM(int readPin){
highTime = 0;
lowTime = 0;
lastSeen = millis();
while((millis()-lastSeen)<read_delay){< div="">
tempPulse = pulseIn(readPin,HIGH);
if(tempPulse>highTime){
highTime = tempPulse;
}
}
lastSeen = millis();
while((millis()-lastSeen)<read_delay){< div="">
tempPulse = pulseIn(readPin,LOW);
if(tempPulse>lowTime){
lowTime = tempPulse;
}
}
freq = ((double) 1000000)/(double (lowTime+highTime));
duty = (100*(highTime/(double (lowTime+highTime))));
}

3 comments:

  1. hello,

    i wanna measure the pWM of a motor controller.
    do i have to connect pin 10 also?
    or only pin 13?
    i tried out the normal pulsein function. it gave me
    9,3 millsec.
    but i have to know the period cause i want to connect a NE555.

    ReplyDelete
  2. lastSeen does not name a type

    ReplyDelete
  3. Hi

    Great code. I can't compile the longer code version because I get a error about expression ,,< div="">". It is necessary to propelrly works of program?

    Thanks
    Pawel

    ReplyDelete