My goal was to send a square wave pulse of varying frequency into the input capture pin and detect the edge to edge time so that I could reconstruct the generated wave accurately. I had some limited success doing this with an 8 bit wave but once I migrated to 16 bit to get the extra precision and range, the Input Capture is now spitting out random crap, sometimes hanging on a particular deltaT before floating around to completely different ones. It's weird. I'm wondering if it has something to do with the prescaler but I'll start by just posting the code. It should be noted that the pulse generator portion of the code on timer5 acts quite nicely and creates a stable wave with deltaT decrimenting steadily and predictably. However when ICP4 is monitored for the same sort of step-wise declining number, I don't see that at all. Instead I'll see a thousand 1450s followed by a bunch of junk, a few 800s, 930s... basically seemingly random behaviour with no regularity. What am I doing wrong?
On further inspection, the code appears to work when a separate Tiny85 is the source, however there is an unacceptable amount of anomalous spikes in the reconstructed signal. I believe that the input capture code technically functions but is poorly optimized to detect the source signal for some reason. I am obviously missing something regarding how the timer should be set up.
Sorry to re-post guys but I'm completely stumped on this. Completely. I paid a programmer to figure it out and even he's got nothing for me. Getting bizarre fluctuating readings from this code when I shouldn't be. The generated signal is clean. I've seen that both on my scope and on the arduino plotter. But the input capture just isn't working and I can't rule out whether it's the code or the board or something else entirely. I've been reading example code and other references all week long and can't figure out why this is happening. Any help would be immensely appreciated.
It’s probably not the cause of your problems, but...
Don’t reset the timer.
Remember the previous capture value, do a unsigned 16-bit subtraction to work out the difference, and hence elapsed count.
You’re throwing away half the benefit of input capture (precise interrupt latency independent timing) if you reset to zero each time.
How much interrupt latency was there between the interrupt and you setting the timer register to zero? You’ve no idea...
Not familiar with the Tiny, but I have used 16 bit capture on an Uno. For that you have to read and write the 16 bit timer values as two 8 bit bytes in a specific order. The high byte is latched when you read the low byte, and the latched high byte is written when you write the low byte.
Is there a similar latching scheme on the Tiny?
I actually checked the compiler output for the Uno, and it’s not stupid. It actually knows about the latch and generates correct sequence. Doubly impressive as writing is the reverse order of register access to reading. So I can just read and write the timer and capture register and not worry about it.
But maybe the Tiny doesn’t have latching, or the compiler is naff? Then, if writing the timer writes high byte first, but the low byte is about to roll over (through say 0x12FF to 0x1300), and it does that before the CPU writeS the low byte, your reset timer ends up as 0x0100 and not 0x0000.
Edit: Actually, from your capture registers, it appears you may be using a Mega? Your “a separate Tiny85” comment threw me...
Still no experience of the Mega, but the same comments apply with regard to 16 bit timer registers. Reading the datasheet now to try and see how that uP works.
Edit again: Still can’t work out exactly which chip you are using. ATmega328PB?
I did discover Megas do seem to have latching.
Since you can specify only a rising or a falling edge (there is no “change” option as with external interrupts ), you have to constantly swap the edge you are looking for.
PCBBC: Sorry for misleading you. I am using a mega2560 and i read the documentation and it does latch so you don't need special 16 bit handling to read the ICR. However your comment about resetting TCNT may be a lead I can follow. I will try that and see if it helps. However if you have "functioning" input capture code from another application, would you mind if I saw that? If all else fails, I'd like to start with working code and morph it over to my application 1 step at a time.
6v6gt: Thank you, that is a fine example but to be honest, I found his work very hard to follow. So much code to accomplish a basic task, I got lost in it and it became hard to debug. I was expecting an example with <10 lines in it. It should take only that many to set up the timer and a couple more for a basic ISR and loop. It should take 10 lines or less to set up any capture timer to do basic timing I think. With all the formal coding convention and extra bulk, I could not use that example and sought others.
OK. The example was a bit heavy weight, but if you look at the following example "Measuring a duty cycle using the input capture unit", it is closer to what you appear to be attempting.
There is some complexity, because he is using very short ticks, so needs to maintain a 32 bit counter, handling overflows from the 16bit counter, to prevent wrap around. You are using large prescaler values so the wrap around of a 16bit counter may not be a problem.
It is not clear how you are processing the times captured by the input capture register, such that you can remark about spurious results. How are these results being made visible to you ?
Also, if you are to accurately reproduce a waveform, you need to trigger the capture alternately on a rising edge and a falling edge. It appears the you are looking at only the falling edges:
Yes that's correct. I also noticed other coding examples were measuring on rising and falling edges but I could not understand why. I know the frequency can change between cycles but from rising-rising or falling-falling, you get your pulse width and I am not interested in duty (in my case duty is mechanically fixed) so I only care about frequency.
I am transmitting the data via serial so I have a suspicion that the serial communications are part of the problem but to what degree I cannot say. I am currently diagnosing that separately.
I'll share what I have, but I'm not sure it will help. It's using count mode, not capture. Sorry.
I've previosuly used capture mode on 8-bit PIC hardware though. It's identical in concept.
Here's my Uno code...
/*
* Audi CANBUS RFSL/V-Signal Injector
*
* Author: Stuart McConnachie
* Target: Arduino UNO with MCP2515 CANBUS Shield
*
* Injects a special direction and speed CAN packet for RNS-E 193 units operating in A6 mode.
* This is of use where the RFSL (C2) and V-Signal (B3) pins are not present (later 193 models).
* Note that this requires a special firmware to receive the packet: https://rnse.pcbbc.co.uk
*
* Acorn firmware supports a 1 byte CAN packet with the reverse indicator.
* Beech firmware also supports a 3 byte packet with reverse AND speed pulse count.
*/
//CANBUS Library: https://github.com/sandeepmistry/arduino-CAN
#include <CAN.h>
#define DELAY_TIME 100
uint32_t delayPacket;
uint8_t sendCount;
void setup()
{
// Input from reverse gear
pinMode(7, OUTPUT);
digitalWrite(7, 0);
// Input from reverse gear
pinMode(6, INPUT);
// Configure T1 to count V-Signal pulses on pin 5
pinMode(5, INPUT);
TCCR1A = 0x00;
TCCR1B = 0x07;
TCNT1 = 0;
// Start debug output
Serial.begin(9600);
while (!Serial);
Serial.println("Audi CANBUS RFSL/V-Signal Injector");
// Start the CAN bus at 100 kbps
if (!CAN.begin(100E3))
{
Serial.println("Starting CAN failed!");
while (1);
}
// Enable rx
CAN.filter(0x271,0x7FF);
// register the receive callback
CAN.onReceive(onReceive);
// Start delay between sending CAN packets
delayPacket = millis();
}
void loop()
{
// Send a CAN packet every 100ms
if (millis() - delayPacket >= DELAY_TIME && sendCount > 0)
{
// Get V-Signal pulse count
uint16_t count = TCNT1;
uint8_t packet[3];
// Get the reverse gear state
packet[0] = digitalRead(6) ? 'R' : 0x00;
// Set the V-Signal pulse count
packet[1] = count >> 0;
packet[2] = count >> 8;
// Send the custom CAN direction and pulse count packet
CAN.beginPacket(0x351);
CAN.write(packet, 3);
CAN.endPacket();
// Time for next packet
delayPacket += DELAY_TIME;
sendCount--;
}
}
void onReceive(int packetSize)
{
// Receive ignition status packet
if (CAN.packetId() == 0x271)
{
uint8_t packet[8];
uint8_t i = 0;
while (CAN.available())
{
packet[i++] = CAN.read();
}
// Ignition on?
bool ignitionOn = packet[0] >= 0x02;
digitalWrite(7, ignitionOn);
if (ignitionOn)
{
sendCount = 8;
}
}
}
Yes that's correct. I also noticed other coding examples were measuring on rising and falling edges but I could not understand why. I know the frequency can change between cycles but from rising-rising or falling-falling, you get your pulse width and I am not interested in duty (in my case duty is mechanically fixed) so I only care about frequency.
You are correct. If you are only interested in frequency and not duty, you only need to look at one edge. Although ideally it needs to be the correct edge in anything other than steady state (unless they duty is always 50%).
I am transmitting the data via serial so I have a suspicion that the serial communications are part of the problem but to what degree I cannot say. I am currently diagnosing that separately.
I would capture in the ISR, do the subtraction, save that value, set a flag, then in main pump it out via serial.
Basically write the simplest program you can to exercise the hardware.
Of course I may be teaching you to suck eggs here...
That is yet another way to accomplish the same goal and is not off the table for me if IC fails to yield. I will keep it in mind.
I should mention as well that I have activated the overflow interrupts to monitor if and when I get overflows. I do get them more often than I'm comfortable with so I may not be handling things correctly in the interrupt. This is why I had been resetting the counter but as you both indicated this should not be necessary and I believe you are right about that. However it is disconcerting that in addition to the noisy random fluctuations in my detection I am also getting overflows, it makes me think that the capture unit is not resetting the counter properly and this may be contributing to the wild swings in my graph.
I had been using a combination of the built-in plotter (tracking ICR4) as well as a 3rd party plotter tracking the same. They are not identical in quality, owing perhaps to differences in the COM handling but otherwise they are similar in the sense that the wave is distorted beyond recognition when it goes through the IC code. The generated wave on the other hand is clean and predictable.
Gahhhrrrlic:
Yes that's correct. I also noticed other coding examples were measuring on rising and falling edges but I could not understand why. I know the frequency can change between cycles but from rising-rising or falling-falling, you get your pulse width and I am not interested in duty (in my case duty is mechanically fixed) so I only care about frequency.
. . .
OK. So you are interested only in frequency. In your OP, however, that was not clear because you said that you wanted to reconstruct the incoming wave accurately. If the duty cycle is fixed (say 50%), then you have to determine the mark/space intervals based on the frequency.
Gahhhrrrlic:
. . .
I am transmitting the data via serial so I have a suspicion that the serial communications are part of the problem but to what degree I cannot say. I am currently diagnosing that separately.
There is no code in the sketch you supplied in the OP for transmitting data by a serial connection, so it is not possible to assess what impact this could have. Further, you do not appear to be using the data obtained from the input capture at all in that sketch. You obtain currentTime (derived from ICR4) in the ISR but never use it.
That may have been my oversight when posting the code. The code was far messier (owing to the countless tests I had done to trial-and-error my way to a working solution. I must have blown away the print statements.
It was nothing really. All I did was print currentTime either in the ISR or in the loop. I tried both to see what would happen. The PC application handles all the math (which has been verified correct) and then prints RPM from the deltas in the times being sent to it.
However, even when printing ICR4 directly from the Arduino side, I get a mess of numbers. Nothing resembling the simple curve the simulation code is supposed to produce. Somehow events are getting missed on detection or some overflow issue is causing time deltas to spill into the next 2 bytes and produce a different number maybe.
I am also uncertain as to my prescalers. I figure choosing the largest possible prescaler would prevent the timer from ever overflowing before an event can stop and reset it. This is my reasoning but I am still seeing overflows, which doesn't make any sense. If it takes 1 uS at the most for an edge to occur and it takes 100 uS for the timer to reach its max value, there should be no way for the timer to EVER overflow. Hence my choice of prescaler, but the data says otherwise.
EDIT: Just occurred to me that the OP code doesn't show max prescaler but rest assured I have tested with 1024 and it didn't fix the problem. It only made it slightly less bad so instead of a million overflows I'm getting 100 for example.
Damnit. That's because at times I wanted to print the generated signal to verify that it was still clean. However when I commend out ICR5, which should have been the case here, it still messes up.
// Pulse generator on pin 46 sent to Input Capture Pin 49
int ledPin = 13;
int count = 0;
unsigned int lastTickTime = 0;
volatile uint16_t currentTime;
volatile bool sendTime = false;
volatile bool printSignal = 0;
ISR(TIMER4_CAPT_vect) // PULSE DETECTED! (interrupt automatically triggered, not called by main program)
{
currentTime = ICR4;
// TCNT4 = 0; // Removed at the recommendation of forums
}
ISR (TIMER1_OVF_vect) {
Serial.println("Ovr");
}
void setup()
{
cli();
Serial.begin(9600, SERIAL_8N1); // Open the serial library @ 9600bps
// Initialize Timer1
TCCR4A = 0;
TCCR4B = 0;
TCCR4C = 0;
TCCR4B |= (1 << ICNC4); // Enable denoiser
TCCR4B &= ~(1 << ICES4); // Capture on falling edge
// Prescaler 1024 (101)
// Prescaler 256 (100)
TCCR4B |= (1 << CS42); // 1
TCCR4B &= ~(1 << CS41); // 0
TCCR4B |= (1 << CS40); // 1
TCNT4 = 0;
TIFR4 = (1<<ICF4);
TIMSK4 |= (1 << ICIE4) | (1 << TOIE4);
sei();
}
// Set up the timer for the simulated ticks
void setupSimulationTimer()
{
DDRL |= 1 << DDL3; // Set D46 as an output pin
TCCR5A = 0; // Clear TCCR A & B bits
TCCR5B = 0;
TCCR5A = (1 << COM5A1); // Non-inverting output
TCCR5B = (1 << WGM53) | (1 << CS50) | (1 << CS52); // Phase & Freq correct PWM,
ICR5 = 10000; // 8 RPM
OCR5A = 2500; // 25% duty... arbitrary
}
int i = 0;
void loop()
{
// SIMULATION CODE
// currentTime = ICR5; //for printing the generated signal values
if(i > 1) {
ICR5-=1;
OCR5A = ICR5 * 0.25;
i=0;
if(ICR5 < 36) {
ICR5 = 10000;
OCR5A = 2500;
}
}
i++;
// END SIMULATION CODE
cli();
Serial.println(currentTime);
sei();
}
I see two issues with your code. I don't have a Mega to test with, but I put a pulse generator on one UNO and the ICP reader on another. With the two different units, I can get the accurate reading of the signal.
One issue I found is that the pulse generator was in a dual sloped pwm mode, which led to overflow issues on the read side. I also think that the generator is better structured to change the values in an overflow interrupt rather than each pass of loop. My generator code looks like this using Timer1.