Inaccurate PWM values when using SevenSeg library w/ interrupts

Hello everyone,

I’ve been lurking the forum for some time, but I’ve finally come to a point in my project that I can’t seem to solve by searching. I’ll try to be succinct, but I also want to give some background info on my project.

I’m using a Continental flex fuel sensor to measure the ethanol content of a fuel. The sensor generates a 5V PWM signal. The signal period corresponds to the ethanol percentage of the fuel (50Hz = 0%, 150Hz = 100%) and time the signal is low corresponds to the fuel temperature. I’m using an TQFP package ATMEGA328P to drive a 4-digit, seven segment display, on a PCB that I designed. Due to package restrictions, I cannot fit an LED driver chip on the board, which is why I’m using the MCU to drive the display.

There are basically two parts that need to work together in order to accomplish my task: 1.) get the PWM sensor signal to the MCU, crunch some numbers, then 2.) output the ethanol percentage to the display.

Using the pulseIn() function, I can accurately get the sensor signal to the MCU, do some math to get the percentage, and output that result through Serial.println() to verify that it behaves as expected. Rather than rig up a fuel sensor and gasoline mixture in my bedroom to test the code, I used bit-banging on an UNO with a pin set high for 4000 micros, and low for 5200 micros. This corresponds to an ethanol percentage of 58%. That all works fine; I get 58% output through the serial monitor.

When I try to display this on the 7-segment using the SevenSeg library, without interrupts, the screen flickers. I thought the solution would be easy enough by simply using the interrupts that the library employs. When I do that, the screen stops flickering, but the ethanol percentage becomes unstable. Instead of holding steady at the 58% like I expect, it fluctuates quickly from 56-60%. It seems to me that the pulseIn() function does not like the interrupts.

To get around using pulseIn(), I attempted to write my own code to process the PWM signal. Silly me didn’t realize the micros() uses a timer interrupt to generate time values, so I am getting the same results as when I used the pulseIn() function; the screen doesn’t flicker, but the value fluctuates too much.

Below is the current version of my code. As a note, I have the PWM signal going to pin 15 (using the Arduino pin numbering system). The LED segments are powered by pins 11,3,4,5,6,7,8, with the digits pins going to 2,16,13 (it’s a common anode display).

In terms of my questions, I guess I really don’t know what to ask at this point. I don’t understand completely what the SevenSeg ISR is doing, so I am struggling to find an alternative solution. Can someone offer advise on a possible solution to this? Thanks in advance!

#include <SevenSeg.h>

SevenSeg disp(11, 3, 4, 5, 6, 7, 8);
const int numOfDigits = 3;
int digitPins[numOfDigits] = {2, 16, 13};
int freq = 100;
int brightness = 100;

void setup() {
  Serial.begin(9600);
  disp.setDigitPins(numOfDigits, digitPins);
  disp.setDPPin(9) ;
  disp.setRefreshRate(freq);
  disp.setCommonAnode();
  disp.setTimer(2);
  disp.startTimer();
  disp.setDutyCycle(brightness);
  int Sensorpin = 15;
  pinMode(Sensorpin, INPUT);
}

void loop() {
  int returnValue = percent();
  disp.write(returnValue);
  Serial.println(returnValue);
}

int percent() {

  int lowEndL = 0;        //variables for PWM signal that starts during LOW part of period
  int lowStartL = 0;      //lowStartL = start time of the low signal, for a sample that was initiated during the LOW portion of the period
  int totalLowL = 0;
  int highEndL = 0;
  int highStartL = 0;
  int totalHighL = 0;
  int periodL = 0;
  
  int lowEndH = 0;        //variables for PWM signal that starts during LOW part of period
  int lowStartH = 0;      //lowStartH = start time of the low signal, for a sample that was initiated during the HIGH portion of the period
  int totalLowH = 0;
  int highEndH = 0;
  int highStartH = 0;
  int totalHighH = 0;
  int periodH = 0;
  
  int pinState = 0;
  int period = 0;
  int percent = 0;

  if (digitalRead(15) == HIGH && pinState == 0) {
    while (digitalRead(15) == HIGH) {
      lowStartH = micros();
    }
    while (digitalRead(15) == LOW) {
      lowEndH = micros();
      highStartH = lowEndH;
    }
    pinState = 1;
  }

  if (digitalRead(15) == HIGH && pinState == 1) {
    while (digitalRead(15) == HIGH) {
      highEndH = micros();
    }
    pinState = 0;
  }

  if (digitalRead(15) == LOW && pinState == 0) {
    while (digitalRead(15) == LOW) {
      highStartL = micros();
    }
    while (digitalRead(15) == HIGH) {
      highEndL = micros();
      lowStartL = highEndL;
    }
    pinState = 2;
  }

  if (digitalRead(15) == LOW && pinState == 2) {
    while (digitalRead(15) == LOW) {
      lowEndL = micros();
    }
    pinState = 0;
  }

  totalLowL = lowEndL - lowStartL;
  totalHighL = highEndL - highStartL;
  periodL = totalLowL + totalHighL;

  totalLowH = lowEndH - lowStartH;
  totalHighH = highEndH - highStartH;
  periodH = totalLowH + totalHighH;  

  if (periodH != 0){
    period = periodH;
  }
  else if (periodH == 0){
    period = periodL;
  } 

  percent = (1000000 / period) - 50;

  if (period != 0) {
    return percent; 
  }
  else {
  }
}

ISR(TIMER2_COMPA_vect)
{
  disp.interruptAction();
}

letthewookiewin7:
There are basically two parts that need to work together in order to accomplish my task: 1.) get the PWM sensor signal to the MCU, crunch some numbers, then 2.) output the ethanol percentage to the display.

You know what multiplexing is, right?
And you know that, when you’re trying to do two things (measuring and multiplexing) at the same time, and both of those things have really tight timing requirements, then something’s gotta give, right?

Wow, thanks for the help.

First STOP using pulseLn() it’s blocking and there for messes up other things.

You should use interrupts (you need two one for rising and one for falling) to find the frequency and the length of the space (the time the signal is low).

Print the results on the serial monitor and then and the LCD.

Mark

holmes4:
First STOP using pulseLn() it's blocking and there for messes up other things.

You should use interrupts (you need two one for rising and one for falling) to find the frequency and the length of the space (the time the signal is low).

Print the results on the serial monitor and then and the LCD.

Mark

I'm not using pulseIn() or pulseInLong().

Google TIL311.
Not exactly a solution to your problem, but rather a way to sidestep it entirely.