Homemade PWM value reader code

NOTE: I'm using ATTiny85 which does not have interrupt pins, so I'm attempting to measure the timing of a PWM signal using my own non-blocking code.

Preamble
I'm using an ATtiny85 connected to some microphone circuitry which outputs an envelope of an audio signal, which works really well for sensing different types of audio such as "any sound in the room" or "double clap" etc. I would like to provide a reference value to compare the analog input with, so that I can trigger sounds above a certain level. And I want this level to be remotely controlled, so I can tweak it over wifi. As my project already uses an ESP32 running Tasmota, PWM is the only option for getting this value into my ATtiny85.

The PWM signal
Using Tasmota I am able to output a 40Hz PWM signal with a "range" of 255. The period is therefore 1/40s = 0.025s. To measure the PWM signal I am simply measuring the number of microseconds that the signal is high and updating an "interval" value which represents that measurement. See code below.

PWM signal calculations and scope measurements
When I set the PWM value to 1 (1/255 duty cycle), the theoretical interval in microseconds should be 1 / 40 / 255 = 98us. My scope shows the value to be 97us, which is good. When I set the PWM value to 10, the interval in my code should theoretically measure 980us, my scope measures 975us. Seems like the PWM signal is doing what I expect.

The Problem (my code?)
When I output the interval value (this is the amount of time that the signal is high) to serial monitor, the value seems to flip between the expected result and another value.

  • In the case of PWM 1 (signal goes high for ~98us) the other value is not far off, and this problem doesn't happen as often
  • In the case of PWM 10 (signal goes high for ~980us) the other value is very different, but problem not as often
  • In the case of PWM 100 (signal goes high for ~9.8ms) the other value is very different AND it happens very often, almost every other "reading"
  • In the case of PWM 254 (signal goes high for 24.8ms) the other value is very far off, but it doesn't flip to that value as regularly.

This is an example of PWM 10 (I expect interval to be around 974, so most values are good here:

  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 972  calibration: 19
  interval: 976  calibration: 19
  interval: 972  calibration: 19
  interval: 968  calibration: 19
  interval: 968  calibration: 19
  interval: 972  calibration: 19
  interval: 976  calibration: 19
  interval: 980  calibration: 20
  interval: 976  calibration: 19
  interval: 972  calibration: 19
  interval: 980  calibration: 20
  interval: 976  calibration: 19
  interval: 972  calibration: 19
  interval: 972  calibration: 19
  interval: 28  calibration: 0
  interval: 972  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 80  calibration: 1
  interval: 972  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 976  calibration: 19
  interval: 136  calibration: 2
  interval: 976  calibration: 19
  interval: 976  calibration: 19

Here's PWM 100. As you can see, the interval 9752 looks about right, but the wrong value (~5800) is very frequent:

  interval: 9752  calibration: 199
  interval: 5780  calibration: 118
  interval: 9752  calibration: 199
  interval: 5800  calibration: 118
  interval: 9756  calibration: 199
  interval: 5820  calibration: 119
  interval: 9752  calibration: 199
  interval: 5848  calibration: 119
  interval: 9752  calibration: 199
  interval: 5864  calibration: 120
  interval: 9756  calibration: 199

Code below:

#define audiopin A0
#define pwmPin 2
bool pwm = 0;
unsigned long interval = 12549; // set interval to half the maximum PWM interval
unsigned long previousMicros = 0;
int calibration = 0;

void setup()
{
  pinMode(audiopin, INPUT);
  pinMode(0, INPUT);
  Serial.begin(9600);
}

void loop()
{
  // PWM decode -- only do stuff if signal changes
  if (!pwm) {
    pwm = digitalRead(pwmPin);
    if (pwm) {
      // pwm went high, start timer
      previousMicros = micros();
    }
  } else {
    pwm = digitalRead(pwmPin);
    // pwm previously high
    if (!pwm) {
      // pwm went low, get measurement from timer
      interval = micros() - previousMicros;
      calibration = (int) (interval / 48.82);
    }
  }
}

Any clues?

Maybe while its writing to the monitor its not doing a very good job doing the measurements?

Maybe you need to set up an RC servo with a pointer and use that as a gauge? (Sending a non-blocking pulse to it.)

-jim lee

I am only taking a SWAG but it appears you are getting interrupted by background tasks which are part of the arduino system. Interrupts are occuring from other parts such as the serial interface. A circuit diagram may help.

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