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?