Time length of signal for a digital pin to capture it?

Hi folks. I was wondering, how long does a signal need to be on for an arduino's digital pin to capture it? I am trying to capture a signal that pulses for about 600 ns (nanoseconds), is this too short for a digital pin to read it? How do I know?

Also, does the read time vary among different arduino boards? I'm currently trying this on the Arduino Mega 2560 if that helps. If the Mega can't capture this 600 ns signal, is there another board that can, or is this out of the realms of arduino?

Thanks! :slight_smile:

600ns is about 8-9 clocks. for a digitalRead() to see it one must read at the right moment.

However the hardware supports interrupts on pins (2,3 for UNO) and with some more effort on every pin.

Check the documentation about attachInterrupt()

When the pulse arrives the interrupt will call a function and in that function you can set a variable (must be volatile) that can be checked in the main loop().

Per Section 13 of the datasheet, sensing that signal with an interrupt can be done as quickly as the next clock cycle, or if a level interrupt, or a PCINT interrupt, is used, it could be a few clock cycles. 600nS in 9.6 clock cycles, most instructions take 1,2 or 3 clock cycles.

The INT0 and INT1 interrupts can be triggered by a falling or rising edge or a low level. This is set up as indicated in the specification for the External Interrupt Control Register A – EICRA. When the INT0 or INT1 interrupts are enabled and are configured as level triggered, the interrupts will trigger as long as the pin is held low. Note that recognition of falling or rising edge interrupts on INT0 or INT1 requires the presence of an I/O clock, described in ”Clock Systems and their Distribution” on page 26. Low level interrupt on INT0 and INT1 is detected asynchronously.
This implies that this interrupt can be used for waking the part also from sleep modes other than Idle mode.
The I/O clock is halted in all sleep modes except Idle mode.
Note: Note that if a level triggered interrupt is used for wake-up from Power-down, the required level must be held long enough for the MCU to complete the wake-up to trigger the level interrupt. If the level disappears before the end of the Start-up Time, the MCU will still wake up, but no interrupt will be generated. The start-up time is defined by the SUT and CKSEL Fuses as described in ”System Clock and Clock Options” on page 26.
:
:
The External Interrupt 1 is activated by the external pin INT1 if the SREG I-flag and the corresponding interrupt mask are set. The level and edges on the external INT1 pin that activate the interrupt are defined in Table 13-1. The value on the INT1 pin is sampled before detecting edges. If edge or toggle interrupt is selected, pulses that last longer than one clock period will generate an interrupt. Shorter pulses are not guaranteed to generate an interrupt.
If low level interrupt is selected, the low level must be held until the completion of the currently executing instruction to generate an interrupt.

What does it mean that digitalRead() has to read it at the right moment? Does it mean that digitalRead() takes place at whenever it is ready to take place, and once it reads the input (regardless of whether it's LOW or HIGH), it moves forward and when the 600ns signal comes in, the digitalRead() may have already been executed, thus missing the actual signal itself?

Thanks for the fast responses! I did some more reading and gave it a shot, incorporating it into my current code:

int pin = 21; //*added (pin 21 is int.2 on Arduino Mega, right?)
volatile int state = LOW; //*added


void setup()          
{
  Serial.begin(9600);     
                          
  pinMode(22, INPUT); // digital DB00
  pinMode(23, INPUT); // digital DB01
  pinMode(24, INPUT); // digital DB02
  pinMode(25, INPUT); // digital DB03
  pinMode(26, INPUT); // digital DB04
  pinMode(27, INPUT); // digital DB05
  pinMode(28, INPUT); // digital DB06
  pinMode(29, INPUT); // digital DB07
  pinMode(30, INPUT); // digital DB08
  pinMode(31, INPUT); // digital DB09
  pinMode(32, INPUT); // digital DB10
  pinMode(33, INPUT); // digital DB11
  pinMode(34, INPUT); // digital DB12
  pinMode(35, INPUT); // digital DB13
  pinMode(36, INPUT); // digital DB14
  pinMode(37, INPUT); // digital DB15
  
  pinMode(38, OUTPUT); // CPU_Ready     digital 
  pinMode(39, OUTPUT); // Data_Hold     digital 
  pinMode(40, INPUT); // Cycle_Request  digital 

  pinMode(pin, INPUT); //*added
  attachInterrupt(digitalPinToInterrupt(pin), xyz, RISING); //*added

  digitalWrite(38, HIGH); // Tells signal processor that it can start sending data
  
}

void xyz ()                   //*added
{                             //*added
    state = !state;           //*added
                              //*added
}                             //*added
  

void loop()                      
{

if (digitalRead(pin) > 0) // Cycle_Request (pulses for about 600ns from LDV) //*modified
   { 
    digitalWrite(39, HIGH); // Data_Hold

    Serial.print(digitalRead(22)); // DB00
    Serial.print(digitalRead(23)); // DB01
    Serial.print(digitalRead(24)); // DB02
    Serial.print(digitalRead(25)); // DB03
    Serial.print(digitalRead(26)); // DB04
    Serial.print(digitalRead(27)); // DB05
    Serial.print(digitalRead(28)); // DB06
    Serial.print(digitalRead(29)); // DB07
    Serial.print(digitalRead(30)); // DB08
    Serial.print(digitalRead(31)); // DB09
    Serial.print(digitalRead(32)); // DB10
    Serial.print(digitalRead(33)); // DB11
    Serial.print(digitalRead(34)); // DB12
    Serial.print(digitalRead(35)); // DB13
    Serial.print(digitalRead(36)); // DB14 
    Serial.println(digitalRead(37)); // DB15 

    digitalWrite(39, LOW); // Data_Hold done and ready for next set
        
   }
   
   else
   {
   }

}

The basic logic of this code (might look familiar, been working it for a while) is that it reads data sent to the 16 data pins (for data bits 00-15). Also, there are three signal lines that manage this flow.

  1. CPU_Ready (pin 38) is an outgoing signal to the signal processor to let it know it's ready to receive data (I just write to that pin as HIGH).
  2. Data_Hold (pin 39) tells the signal processor to hold the data, until it is fully read, before proceeding with the next string of data to the 16 data lines.
  3. Cycle_Request (pin 40) is the one that "listens" for the 600ns signal from the signal processor. But it is temporarily disabled with "//" as I incorporated the attachInterrupt() set for pin 21 (int.2 on the Mega).

The changes I made to the code to incorporate the attachInterrupt() is commented with an asterisk*. What do you folks think? I don't really understand the types of ISR to be used here (AttachInterrupt() uses "blink") but I just stuck an arbitrary "xyz" in there.

Would this work? Am I applying it correctly?

Does it mean that digitalRead() takes place at whenever it is ready to take place, and once it reads the input (regardless of whether it's LOW or HIGH), it moves forward and when the 600ns signal comes in, the digitalRead() may have already been executed, thus missing the actual signal itself?

Exactly. That's why an interrupt would be used in this case.

I'll defer to more knowledgeable software folks on the rest.

Without fully understanding all your code it seems like you are missing something. Your ISR toggles the state variable but this is not used anywhere else so seems pointless.

Usually your ISR would set state=1 then your main code would contain

if (state==1) {
   state=0;
   // a pulse occurred, do something useful here
}

The ISR is executed "magically" when the hardware detects a rising edge so setting state "remembers" the pulse until your main program is ready to deal with it. Because the ISR is executed out of sequence many of the normal functions dont work properly so its best to do only the minimum amount - like setting a flag or incrementing a counter- then rely on the main program to respond in some way.

This thread Faster Analog Read? - Frequently-Asked Questions - Arduino Forum which contains this link Arduino Reference - Arduino Reference have more detail on the issues involved in trying to sample shorter pulses.

refactored your code a bit,
read it and try to understand

  • the use of the interrupt
  • the for loops and
  • the use of const int for symbolic names (no need for comment when you use good names)

I have no mega nearby to test it,

const int PULSE_PIN= 21; //*added (pin 21 is int.2 on Arduino Mega, right?)

volatile int state = LOW;

const int CPU_READY=38;
const int DATA_HOLD=39;
const int CYCLE_REQUEST=40;

void setup()
{
  Serial.begin(9600);          // <<<<<<<<<<<<  use 115200 for higher speed!
  Serial.print("Start ");
  Serial.println(__FILE__);

  for (int pin = 22; pin <= 37; pin++)
  {
    pinMode(pin, INPUT);
  }
  pinMode(CPU_READY, OUTPUT);
  pinMode(DATA_HOLD, OUTPUT);
  pinMode(CYCLE_REQUEST, INPUT);

  pinMode(PULSE_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(PULSE_PIN), catchPulse, RISING);
  digitalWrite(CPU_READY, HIGH); // Tells signal processor that it can start sending data

}

void catchPulse()
{
  state = 1;
}


void loop()
{
  if (state == 1)
  {
    state = 0; // reset the interrupt flag

    digitalWrite(DATA_HOLD, HIGH); // Data_Hold
    for (int pin = 22; pin <= 37; pin++)
    {
      Serial.print(digitalRead(pin));
    }
    digitalWrite(DATA_HOLD, LOW); // Data_Hold done and ready for next set
  }
  else
  {
    Serial.print(millis());
    Serial.print('\t');
    Serial.println("No interrupt occured");
  }

  // simulate doing other stuf by delay
  delay(random(10000));
}

You can make the pulse longer (one shot monostable), can't you?

I appreciate the responses! And actually I have no control over the length of the pulse; I'm reading data from an "ancient" data acquisition system and the pulse seems factory-set at 600ns

robtillaart, thanks for reworking the code! I loaded your code into the Mega I'm working with and connected it to the data acquisition system (laser doppler velocimetry, LDV) that I'm trying to read the 600 ns pulse from. Here's my results (from the Serial Monitor):

Start LDV_6b_Mega.ino
0	No interrupt occured
6807	No interrupt occured
12056	No interrupt occured
12130	No interrupt occured
15789	No interrupt occured
24719	No interrupt occured
25992	No interrupt occured
33537	No interrupt occured
34415	No interrupt occured
42338	No interrupt occured
50048	No interrupt occured
54489	No interrupt occured
62654	No interrupt occured

It'll go on forever, but basically that's what it says. It looks like the interrupt isn't detected (or maybe it just was never triggered by the data acquisition system?).

Also, I thought I saw something in the data acquisition manuals about 9600 baud (might be for something else though) but I guess nothing specifically for this portion of the system. I guess I just stuck with 9600 as a "default" value, would bringing it up to 115200 simply mean the data collection would occur faster? Would it help with detecting the interrupt due to the pulse signal?

If you want to detect a pulse from time to time you can make the pulse longer (by a simple circuit) and detect it easily. Tell us what the pulse does actually represent and what you want to measure (frequency of the pulses, or you just count the pulses, etc) and we may help with some advice..

Is the pulse connected to the right pin?
Did you connect the grounds of the Arduino and the pulse giving device?

Yep, the pulse is connected to the right pin (pin 21) and all the grounds are connected to Arduino ground (I used the GND pins after digital pin 53 on the Mega)

How do I lengthen this pulse with a circuit? Here's the parts of the manuals that pertain to this logic specifically.

  1. Pin out specs and what they send
  2. Logic flow order on the pins

These are manuals for an interface card that is missing, for a Laser Doppler Velocimetry (LDV) system, that I'm trying to replace with an arduino. The first PDF discusses the 16 data pins and 3 signal pins, and what each pin represents. I can take the 0/1's of each pins, and follow the steps to calculate my velocity.

For the second PDF, it discusses two data transfer modes (Polled Port Mode, and DMA Mode). My current effort with the arduino is based on the Polled Port Mode, see page B-4 for the transfer logic steps. Let me know if this helps!

Also, side question, page B-6 onward lists sample assembly programs. I have the programming experience of an infant, so I'm not sure how to make sense of this. Could I do something with these samples as an easier alternative to the arduino? Just curious!

You shouldn't have to lengthen a 600ns pulse to detect it with a Mega running at 16MHz. It's possible to detect a pulse of that width with simple polling. With an edge triggered interrupt there's no way you'd miss it.

Something is wrong.

With an edge triggered interrupt there's no way you'd miss it.
Unless it is not connected well?

Please post the complete schematics (handdrawn is ok) how you connected the device,

Unless it is not connected well?

I thought that went under the category of "something is wrong". :slight_smile:

Sorry it took a while to get this, the drawing of the schematic took a while..


Arduino interface

This is to help visualize the schematic. The Arduino Mega 2560 is on the top, and a breadboard is on the bottom. The center of the breadboard is a 26-pin male connector, that the ribbon cable shown connects to. The other end of the ribbon cable connects to the output of the signal processor of the LDV (laser doppler velocimetry) system.

The two green LED's are only there to light up if CPU_Ready is on (code sets it to always on) and Data_Hold (code turns this pin on to let signal processor know to hold the data so the code can read it, then the code turns this pin off to let sig processor know to send next set of data).


Schematic

In the upper half of this schematic, in black pen, I captured the portion of the pins on the Arduino Mega that I am using, relative to the Mega's orientation. The square boxes are the pins, identified by the respective pin numbers, 5V, or GND.

In the bottom half of this schematic, marked with blue highlighter, is the 26-pin male connector seen from the previous picture. Within the ribbon cable, there are 16 data bits lines and 3 signal lines (see previous post with links to PDF that lists out where each pin is).

All input pins (data pins and Cycle_Request) have a 10k Ohm pull-down resistor that connects the line to ground. I also had current-limiting resistors for each pin with a pull-down to protect the arduino from accidental shorts, but removed it to simplify.

Previously, pin 40 was used for Cycle_Request, but now it's pin 21 where Mega's interrupt is located.


Does this help? What do you folks think? Would definitely help to have a fresh set of eyes on this :slight_smile:

I oftentimes use state machines with interrupts, then the ISRs can be real short sometimes only one line long (the state change line). It is good programming to keep ISRs short especially when there are multiple interrupts, shorter ISRs have a lower probability of having a interrupt trip "interrupting" your original. interrupt.

For your application it looks like all you need is one interrupt, I sketched out a state machine below for to look at or use.

Hope this helps,
wade

volatile uint8_t machineState;

//  all your set up code

switch (machineState) {

case (ISR_NOT_TRIGGERED)
    //  nothing to do
    break; 
case (ISR_TRIGGERED)  //  The interrupt has been triggeres read the data in
//    if (digitalRead(pin) > 0) // Cycle_Request (pulses for about 600ns from LDV) //*modified
//    The above line if (digitalRead(pin) > 0) ... is not needed as you would not
//    be here if the pin had not been high.
  machineState = ISR_NOT_TRIGGERED; //  Then the next time through you won't 
                                    //  get in this switch.  This is very easy to forget
    {
      digitalWrite(39, HIGH); // Data_Hold

      Serial.print(digitalRead(22)); // DB00
      Serial.print(digitalRead(23)); // DB01
      Serial.print(digitalRead(24)); // DB02
      Serial.print(digitalRead(25)); // DB03
      Serial.print(digitalRead(26)); // DB04
      Serial.print(digitalRead(27)); // DB05
      Serial.print(digitalRead(28)); // DB06
      Serial.print(digitalRead(29)); // DB07
      Serial.print(digitalRead(30)); // DB08
      Serial.print(digitalRead(31)); // DB09
      Serial.print(digitalRead(32)); // DB10
      Serial.print(digitalRead(33)); // DB11
      Serial.print(digitalRead(34)); // DB12
      Serial.print(digitalRead(35)); // DB13
      Serial.print(digitalRead(36)); // DB14
      Serial.println(digitalRead(37)); // DB15
      digitalWrite(39, LOW); // Data_Hold done and ready for next set

    }

    //  I don't know if there are other states.
} //  End of switch(machineState)

void catchPulse(void){  //  This is you ISR you can call what you want.
  machineState = ISR_TRIGGERED;
  //  A nice short and sweet ISR.
}

wade, thanks for your example code. I was wondering, could you explain it a little bit? I'm having a hard time understanding the flow of it, especially because I'm used to void setup(), void loop(), etc

I also downloaded the Finite State Machine (FSM) library and put it in my libraries, but not really sure if that's the right path to go on nor how to really use it within my application

What makes you think that the sending unit is even sending out these pulses?
How often should they occur? Why?
Can you detect the pulses (without the Arduino)?

What is the stand by voltage of the signal, and what is its peak during pulse?

According to the manual for this system, the signal processor should be sending the pulse out on that CYCLE_REQUEST line, every time there's a new set of 16 bits ready for collecting. This lets the data acquisition (arduino Mega, but previously to an old ISA card (missing, hence replaced with arduino) in a desktop) know when to read the 16 bits.

I know it can send out pulses. I've hooked up that specific pin (pin 21, aka the CYCLE_REQUEST line, aka the interrupt) to an oscilloscope. Certain settings within the signal processor (such as turning it on) can trigger the collection of a set of 16 bits. When this is triggered, the oscilloscope reflects the jump on the interrupt pin connected to the CYCLE_REQUEST line. So I know it "can" be triggered to send out pulses on this line.

I don't know the exact stand-by and peak voltages of this pulse; I've treated it as logical 0s/1s. I've measured the other pins via a multimeter/voltmeter when they're HIGH and they're around 3.74 volts. I don't think I can measure the peak of this pin simply because 600ns isn't long enough to get an accurate reading on my multimeter. But if the other pins (data pins, and signal pins) seem to be high at 3.74v, it may be reasonable to suspect the same for this CYCLE_REQUEST line too.

So I know I can detect a pulse IF it is sent (via oscilloscope showing the 600ns pulse triggered during initialization). But I don't know if it's being triggered when data is sent. Perhaps it is a signal processor issue...?