2

I have a pulse going into A0 on a Nano, and want to detect the peak voltage. With low-pass filtering to slow down the input pulse I'm still reading marginally too slow.

The code overall inhibits a laser except for 50ms after a button is pressed. During that 50ms I read A0 to look for the peak voltage/laser pulse energy. The pulse is negative, several volts, and is AC-coupled into A0, pulled up to a 5V baseline.

The monitoring loop is simple:

while (millis() < (PulseStart + LaserPulseWidth)) { // monitor ADC (PbSe amplified photodiode with negative output, AC coupled and pulled up
  if (V_in <= V_old) {
    V_old = V_in;
  } // this will report the minimum voltage seen on the monitor pin.  Should be near enough proportional to the pulse energy.
  V_in = analogRead(MonitorPin);
}

Outside the monitoring loop, but in the main loop, I toggle various status LEDs etc. The resulting laser pulse energy is sent using softwareSerial to (a Raspberry Pi in) the box with the trigger button, about 12m away. softwareSerial requires interrupts, but anyway so does millis()

Oscilloscope image of input pulse

Channel 1 (yellow) is measured at A0. Channel 2 is the unfiltered output from the PbSe amplified IR photodiode, and is used to trigger the scope. Note the timebase is 100us, close to the nominal readout time of A0; I thought this would effectively average over approximately 1 division, which might be OK, but taking some rough calibration data suggests otherwise.

Here's my circuit: partial circuit/block diagram

A simplified version of the whole code follows. I've removed some status outputs (LEDs, beeps, USB serial transmssion) from the main loop and the code to set them up; the ADC read-in loop is unmodified from my running code.

#include <SoftwareSerial.h>
  
int MonitorPin = A0;
int FireButton;
int stopbutton;
int lastFireButton = LOW;
int V_in;
int V_old;
char message[8];
int V_base;
int LaserPulseWidth = 50; //in ms.  50 ms should be right for 20 Hz laser
    
SoftwareSerial mySerial = SoftwareSerial(2, 3); // RX, TX
    
void setup() {
  pinMode(pin7, INPUT_PULLUP);  // fire laser
  pinMode(pin8, INPUT_PULLUP);  // disable
  stopbutton = digitalRead(8);
  FireButton = digitalRead(7);  
  pinMode(LaserPin, OUTPUT); // Laser trigger pin
  Serial.begin(9600); // for comms to a host PC (includes debug info)
  mySerial.begin(9600); // for comms to RPi
  mySerial.println("Second serial port");
  interrupts();
}
    
void loop() {
  stopbutton = digitalRead(8);
  FireButton = digitalRead(7);
  while (stopbutton == HIGH) { // not actually used, but implemented in case we want to suppress firing (pin internally pulled up so just ground to suppress)
    lastFireButton = FireButton;
    stopbutton = digitalRead(8);
    FireButton = digitalRead(7);
    V_old = 1024; // initialise to max so anything we read is less
    
    if (FireButton != lastFireButton && FireButton == LOW) { // detect negative edge
      digitalWrite(13, true); // flash the onboard status LED on
      V_old = 1024;
      for (int i = 0; i < 10; i++) {
        V_in += analogRead(MonitorPin); // read ADC before firing to provide a baseline
      }
      V_base = V_in / 10;
      digitalWrite(LaserPin, true); // laser trigger on
      PulseStart = millis();
      while (millis() < (PulseStart + LaserPulseWidth)) { //monitor ADC (PbSe amplified photodiode with negative output, AC coupled and pulled up
        if (V_in <= V_old) {
          V_old = V_in;
        } // this will report the minimum voltage seen on the monitor pin.  Should be near enough proportional to the pulse energy.
        V_in = analogRead(MonitorPin);
      }
      digitalWrite(LaserPin, false); // laser trigger on
          
      int V_peak = V_base - V_old;
      sprintf(message, "ADC=%d", V_peak);
      mySerial.println(message);
    }
  }
}
14
  • 3
    Sidenote; to handle millis rollover, use while ( (millis()-PulseStart) < LaserPulseWidth) instead. – Gerben Feb 2 at 10:28
  • Do you have a schematic, and an oscilloscope picture of the pulse as it is, and as it arrives at the Arduino pin? Just to get some idea of what "a little too fast" is. analogRead() is quite slow by default, but can be sped up. – ocrdu Feb 2 at 10:28
  • 1
    You could you do like, say, 20 measurements, and store those in an array. Then afterward see which value was the peak value. That way your code doesn't have to do a millis call, and two comparisons, making it run a bit faster. If that's not enough, you'd have to change the prescaler of the ADC, to get a faster Analog to Digital conversion. – Gerben Feb 2 at 10:33
  • 1
    "On ATmega based boards (UNO, Nano, Mini, Mega), it takes about 100 microseconds (0.0001 s) to read an analog input". Your pulse only takes 50µs. So you need to increase the ADC speed, by changing the prescaler. – Gerben Feb 2 at 12:30
  • 1
    There's also a way to put the ADC into free-running mode, and it will sample into an array at the maximum possible speed all on its own. There's an adafruit tutorial on it, but I've never done this myself. I believe this part does not require external hardware: learn.adafruit.com/wave-shield-voice-changer/… – RDragonrydr Feb 3 at 19:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.