I could use a passive low-pass filter and analog read, but then the impedances need to be factored in and there will always be compromise between ripple and response time. An active filter would help with the impedance issue, but adds complexity.
I was wondering if it would be better to just read the on/off time directly from the digital input. The period of oscillation here is only 100us. I need a resolution of at least 1us. The Arduino function pulseIn() can only go down to 10us. Is there some fancy way to achieve the resolution I'm aiming for here?
I plan to implement this on an ATTiny85-20pu. I only need to sample the duty cycle every 20ms or so.
It's a pity about using ATTiny85 - it doesn't have the input capture time-stamp capability of timer 1 on a 328
TheMemberFormerlyKnownAsAWOL:
It's a pity about using ATTiny85 - it doesn't have the input capture time-stamp capability of timer 1 on a 328
I was hoping to avoid the 328. I'm trying to make my circuit as small and cheap as possible. And seeing as I only need 3 inputs and 2 outputs, the 85 is perfect in every other way. Perhaps there's a way to make it work with the 85 or there's another 8 pin chip better suited.
The 85 is the only 8-pin AVR chip.
You can check the ATtiny84a or 841 - a few more pins but still pretty small. These do have input capture function, on a 16-bit timer (the 85 only has 8-bit timers), so 1 µs on 100µs is easy to accomplish.
wvmarle:
The 85 is the only 8-pin AVR chip.
You can check the ATtiny84a or 841 - a few more pins but still pretty small. These do have input capture function, on a 16-bit timer (the 85 only has 8-bit timers), so 1 µs on 100µs is easy to accomplish.
Can someone briefly explain what factors limit the timers?
I understand an 8 bit timer could only count to 256, but that should be plenty of bits if I'm only counting as high as 100µs with a resolution of 1µs. I assumed the clock frequency would be the limiting factor here. The ATTiny85 has a 20-8MHz clock, so it should (in my head at least) be able to count by increments as small as 125ns times the number of clock cycles needed to increment the timer by 1. Does it take more than 8 clock cycles to increment the timer? I see how 125ns is too small to count to 100µs before overflowing the timer. But then a prescaler could just be set.
From the Datasheet on the ATTINY85
...maximum timer/counter clock frequency equal to system clock frequency (fCLK_I/O). Alternatively,
one of four taps from the prescaler can be used as a clock source. The prescaled clock has a frequency of either
fCLK_I/O/8, fCLK_I/O/64, fCLK_I/O/256, or fCLK_I/O/1024.
If you want microsecond resolution, you won't be using pulse in or digitalRead.
TheMemberFormerlyKnownAsAWOL:
If you want microsecond resolution, you won't be using pulse in or digitalRead.
I had assumed that before I asked my question, hence
GustavoMcSavy:
Is there some fancy way to achieve the resolution I'm aiming for here?
You'll be using port manipulation.
Sometimes called "direct port manipulation".
It's not "fancy", it's mainstream but relatively inflexible (from an Arduino POV)
I've used port manipulation for outputs, but I haven't used it on an input yet. Are port commands flexible enough to make this work? It's perfectly acceptable if it has to make the rest of the code wait.
I'd appreciate an outline of the procedure here, maybe some pseudocode.
You might consider some form of assembly language routine to monitor the port pin. That way you don't need the timer capability and can really trim the code to the bare bones to minimize the overhead and error.
You can create a sketch with a .ino (C++) and .S (assembler) and call the measurement routine from C++ as required.
Your resolution is going to be dependent on the clock cycle time setting of the processor. In this example for a 2560 the clock runs at 16MHz so the cycle time is 62.5nS; the loop in this example requires 6 cycles or 375nS so you could, in theory, get sub-uS resolution.
C code for a 2560 example that uses pin 11 as the input (pin 9 is used to generate a test PWM of 490Hz for testing);
const byte pinIn = 11;
const byte pinPWM = 9;
extern "C"
{
int Test(void);
}
void setup()
{
pinMode( pinIn, INPUT );
pinMode( pinPWM, OUTPUT );
analogWrite( pinPWM, 50 );
Serial.begin(9600);
}//setup
void loop()
{
delay(100);
Serial.println( Test() );
}//loop
And the assembly portion:
#define __SFR_OFFSET 0
#include "avr/io.h"
.global Test
Test:
cli
clr r24
clr r25
wait_while_high:
;if pin is high now, wait for it to go low
sbic PINB,5
rjmp wait_while_high
wait_while_low:
;with pin low, wait for it to go high and then begin timing
sbis PINB,5
rjmp wait_while_low
time_high:
sbis PINB,5 ;[2]
rjmp time_done
;
adiw r24,1 ;[2]
rjmp time_high ;[2]
time_done:
sei
ret
A few concessions for simplicity:
- the code blocks interrupts while the timing is happening
- the code waits to time a full high-level cycle; there is no safety in the event the signal is missing or at 100% duty
- the code returns the number of loops counted. Converting that to a duty cycle is up to you
- the code doesn't measure the period of the cycle, only the high-time; it assumes you know the input is 10kHz
If you find it useful add "protection" code as you see fit to prevent lock-ups/freezes.
You can use pin change interrupts as well; typically takes 2-3 clock cycles to get into the ISR. Here read the timer and the pin state, then figure out what happened to it. Fast enough for 1 us resolution.
At 20 MHz the clock ticks at 50 ns intervals, timers can count single clock cycles (when set to no prescaler). So that's your resolution.
Time to enter an ISR is about 20(!) CK and slightly less to leave the ISR. If the first instruction in the ISR is reading the TCNT register, the jitter will be at most 4 CK provided other interrupts are disabled and the previous interrupt had enough time to finish.
What is minimal pulse width? Can you busy-wait for the pulse duration for SW measuring or do you need to use interrupts to free CPU time?
There are other 8 pin AVRs. ATTiny13A is a simpler but cheaper, ATTiny102 has small memory but otherwise interesting features. And there are the new AVRs from Microchip with plenty of peripherals. IIRC some (all?) have timers that can be configured for pulse width measuring directly.
GustavoMcSavy:
I could use a passive low-pass filter and analog read
Is it possible to just directly grab the duty cycle settings from whatever is producing the pwm?
Southpark:
Is it possible to just directly grab the duty cycle settings from whatever is producing the pwm?
Nope. I'm making a motor controller adapter that converts PWM to servo control signals. It's intended to work with any PWM source without modification. 10kHz is actually just the maximum I chose based on the controller I'm making this for. No reason it shouldn't also work with others, just gotta measure the low time too.
Blackfin:
You might consider some form of assembly language routine to monitor the port pin. That way you don't need the timer capability and can really trim the code to the bare bones to minimize the overhead and error.
You can create a sketch with a .ino (C++) and .S (assembler) and call the measurement routine from C++ as required.
This is very helpful, thankyou. I never knew I could combine assembler and C++together on the same program.
Smajdalf:
Time to enter an ISR is about 20(!) CK and slightly less to leave the ISR. If the first instruction in the ISR is reading the TCNT register, the jitter will be at most 4 CK provided other interrupts are disabled and the previous interrupt had enough time to finish.
What is minimal pulse width? Can you busy-wait for the pulse duration for SW measuring or do you need to use interrupts to free CPU time?
Reading the PWM is the controllers #1 and only priority 95% of the time, and there's only one of them. I have no problem with the waiting for the pulse duration, or even 10 whole ms. The shortest pulse should be 1us based on the controller I'm designing this for. I really only need 1us resolution too, so I should be able to tolerate 4 clock cycles at 8MHz. Probably even more since I can take a bunch of readings and average them. It doesn't even have to be perfect. I don't much care if the servo is at 0 degrees or 1.8 degrees (if the servo can even tell, haha), but extra accuracy is always nice.
GustavoMcSavy:
Nope. I'm making a motor controller adapter that converts PWM to servo control signals. It's intended to work with any PWM source without modification. 10kHz is actually just the maximum I chose based on the controller I'm making this for. No reason it shouldn't also work with others, just gotta measure the low time too.
Thanks for those details. Maybe could also try (if not done already) ----- PWM signal, going to two interrupt pins. One interrupt pin for detecting rising edge. The other pin for detecting falling edge. Just grab the absolute difference in time between those two detected edges.
And whenever interrupts for those pins need to be temporarily disabled, could just disable them using a Pin Change Mask Register.
The 1us pulse is very difficult requirement. You will surely miss such pulse when using interrupts. You cannot even use the input capture feature because it can detect only rising or falling edge, not both. I think the best (only?) way is to use some clever SW trick. Or an external HW to make it easier.
EDIT:
Timer/Counter B of ATTiny202 may be used for frequency and pulse width measurement. Consider it.
Smajdalf:
The 1us pulse is very difficult requirement. You will surely miss such pulse when using interrupts. You cannot even use the input capture feature because it can detect only rising or falling edge, not both. I think the best (only?) way is to use some clever SW trick. Or an external HW to make it easier.
EDIT:
Timer/Counter B of ATTiny202 may be used for frequency and pulse width measurement. Consider it.
I considered using an external crystal oscillator and a binary counter with a PISO shift register (if the counter doesn't have a serial output), but that kinda makes it not as cheap, small, and simple as I was hoping. The ATTiny202 has 16bit timers, input capture and it's cheaper! It doesn't come in a through hole package, but I wanted to make this on a PCB anyway. Just makes programming it harder. The 202 looks promising.