Goal
Estimate the bandwidth of your photodetector + front-end (pull-up or TIA) by measuring its 10–90% rise time and 90–10% fall time on Red Pitaya, then using:
Bandwidth≈tr0.35
You’ll generate a clean digital edge (using RP DIO), capture a short high-resolution waveform on CH1, locate the 10% and 90% thresholds, and compute tr/tf automatically.
What you’ll build
- Edge source: Use Red Pitaya DIO0_P to create a sharp rising/falling transition (e.g., drive an LED, LED driver, or a test node in your photodetector setup).
- Sensing path: Your photodetector output is connected to Red Pitaya IN1 (CH1).
- Automation: An Arduino (Wi-Fi) script sends SCPI commands to RP to create the edge, perform a single high-speed acquisition, and print rise and fall times to the Serial Monitor in nanoseconds.
If you prefer your own LED driver or an external square source, keep it—just trigger around your transition and call the same acquisition routine.
Hardware
- Red Pitaya (SCPI server running)
- Photodetector front-end (photodiode + pull-up or TIA) → output → RP CH1
- Optional: Use RP DIO0_P to drive an LED (through a current-limit resistor) or to toggle a node in your circuit
- Common GND between everything
Safety & integrity
- Keep leads short (reduce ringing/strays).
- Choose LV/HV input range appropriately (avoid clipping).
- If you drive an LED from DIO, use a series resistor and respect DIO current limits, or buffer it.
Network setup
Create
arduino_secrets.h
next to your sketch:#define SECRET_SSID "YourSSID" #define SECRET_PASS "YourPassword" #define SECRET_RP_IP "192.168.1.42" // or "rp-xxxxxx.local" #define SECRET_RP_PORT 5025 // use 5000 on older images
Make sure the Red Pitaya SCPI server is running (via the web UI → Development → SCPI → Run, or SSH with
systemctl start redpitaya_scpi
).Wiring
- Photodetector output → Red Pitaya IN1 (CH1) (BNC or header depending on model)
- (Optional) Red Pitaya DIO0_P → your optical/electrical stimulus path (e.g., LED + resistor to 3.3 V, or a logic input to your optical modulator)
- GND common across everything
If you’re probing a fast node, keep loops tight and use a proper ground reference.
Wifi Communication
This is the code we will use for the exercise. Paste it into a new sketch, add arduino_secrets.h, and flash.
/* UNO R4 WiFi → Red Pitaya (SCPI) LEDResponseRiseFallWifi: toggle GPIO LED, capture photodiode on fast ADC CH1, and compute 10–90% rise and 90–10% fall times. */ #include "wifiSCPI.h" #include "arduino_secrets.h" // WiFi + Red Pitaya settings WifiSCPI rp; // ADC sample period (125 MS/s) const float SAMPLE_PERIOD = 1.0f/125e6f; // Convert block string "{v1,v2,...}" to an array of floats. // Returns number of samples written (<=maxN). int parseBlock(const String& blk, float* arr, int maxN){ int l = blk.indexOf('{'); int r = blk.lastIndexOf('}'); if(l < 0 || r <= l) return 0; int n = 0, st = l+1; while(n < maxN){ int k = blk.indexOf(',', st); String t = (k == -1) ? blk.substring(st, r) : blk.substring(st, k); t.trim(); if(!t.length()) break; arr[n++] = t.toFloat(); if(k == -1) break; st = k + 1; } return n; } float edgeTime(bool rising){ const int N = 500; // number of samples to request float s[N]; rp.scpi("ACQ:RST"); rp.scpi("ACQ:DEC:Factor 1"); rp.scpi("ACQ:DATA:FORMAT VOLTS"); rp.scpi("ACQ:DATA:UNITS ASCII"); rp.scpi("ACQ:TRig:DLY 0"); // Set initial level then start acquisition rp.scpi(String("DIG:PIN DIO0_P,") + (rising?"0":"1")); rp.scpi("ACQ:START"); delayMicroseconds(1); // Toggle LED to create edge and trigger capture rp.scpi(String("DIG:PIN DIO0_P,") + (rising?"1":"0")); rp.scpi("ACQ:TRig NOW"); while(rp.scpiLine("ACQ:TRig:FILL?") != "1") delay(1); String blk = rp.scpiBlock("ACQ:SOUR1:DATA? 0, " + String(N-1)); int ns = parseBlock(blk, s, N); if(ns < 2) return NAN; // Determine min and max float vmin = s[0], vmax = s[0]; for(int i=1;i<ns;i++){ if(s[i] < vmin) vmin = s[i]; if(s[i] > vmax) vmax = s[i]; } float th10 = vmin + 0.1f*(vmax - vmin); float th90 = vmin + 0.9f*(vmax - vmin); int i10=-1, i90=-1; if(rising){ for(int i=0;i<ns;i++){ if(i10==-1 && s[i] >= th10) i10 = i; if(s[i] >= th90){ i90 = i; break; } } if(i10==-1 || i90==-1) return NAN; return (i90 - i10) * SAMPLE_PERIOD; // seconds }else{ for(int i=0;i<ns;i++){ if(i90==-1 && s[i] <= th90) i90 = i; if(s[i] <= th10){ i10 = i; break; } } if(i10==-1 || i90==-1) return NAN; return (i10 - i90) * SAMPLE_PERIOD; // seconds } } void setup(){ Serial.begin(115200); delay(200); if(!rp.begin(SECRET_SSID, SECRET_PASS, SECRET_RP_IP, SECRET_RP_PORT)){ Serial.println(F("Connection failed")); while(true){} } rp.scpi("DIG:PIN:DIR DIO0_P,OUT"); Serial.println(F("Measuring rise and fall times...")); } void loop(){ float tr = edgeTime(true); float tf = edgeTime(false); Serial.print(F("Rise time (10-90%): ")); Serial.print(tr*1e9,1); Serial.println(F(" ns")); Serial.print(F("Fall time (90-10%): ")); Serial.print(tf*1e9,1); Serial.println(F(" ns")); delay(1000); }
UART Communication
/* Arduino → Red Pitaya (SCPI via UART) led_response_rise_fall: toggle GPIO LED, capture photodiode on fast ADC CH1, compute 10–90% rise and 90–10% fall times. */ #include <Arduino.h> #include "SCPI_RP.h" #if defined(ARDUINO_ARCH_AVR) #include <SoftwareSerial.h> SoftwareSerial uart(8, 9); // RX, TX pins for UART #endif scpi_rp::SCPIRedPitaya rp; const float SAMPLE_PERIOD = 1.0f/125e6f; // 125 MS/s float edgeTime(bool rising){ const uint32_t N = 500; float s[N]; rp.acq.control.reset(); rp.acq.settings.decimationFactor(1); rp.acq.trigger.delay(0); rp.dio.state(scpi_rp::DIO_0_P, rising?false:true); rp.acq.control.start(); delayMicroseconds(1); rp.dio.state(scpi_rp::DIO_0_P, rising?true:false); rp.acq.trigger.trigger(scpi_rp::ACQ_TRIG_NOW); bool filled=false; while(!filled){ rp.acq.trigger.fillQ(&filled); } bool last=false; float v=0; uint32_t idx=0; while(!last && idx<N){ rp.acq.data.dataStartSizeQ(scpi_rp::ACQ_CH_1, 0, N, &v, &last); s[idx++] = v; } uint32_t ns = idx; if(ns < 2) return NAN; float vmin=s[0], vmax=s[0]; for(uint32_t i=1;i<ns;i++){ if(s[i]<vmin) vmin=s[i]; if(s[i]>vmax) vmax=s[i]; } float th10 = vmin + 0.1f*(vmax-vmin); float th90 = vmin + 0.9f*(vmax-vmin); int i10=-1, i90=-1; if(rising){ for(uint32_t i=0;i<ns;i++){ if(i10==-1 && s[i]>=th10) i10=i; if(s[i]>=th90){ i90=i; break; } } if(i10==-1 || i90==-1) return NAN; return (i90 - i10)*SAMPLE_PERIOD; }else{ for(uint32_t i=0;i<ns;i++){ if(i90==-1 && s[i]<=th90) i90=i; if(s[i]<=th10){ i10=i; break; } } if(i10==-1 || i90==-1) return NAN; return (i10 - i90)*SAMPLE_PERIOD; } } void setup(){ Serial.begin(115200); #if defined(ARDUINO_ARCH_AVR) uart.begin(RED_PITAYA_UART_RATE); rp.initStream(&uart); #elif defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM) Serial1.begin(RED_PITAYA_UART_RATE); rp.initStream(&Serial1); #endif rp.dio.reset(); rp.dio.dir(scpi_rp::DIO_0_P, scpi_rp::OUT); Serial.println(F("Measuring rise and fall times...")); } void loop(){ float tr = edgeTime(true); float tf = edgeTime(false); Serial.print(F("Rise time (10-90%): ")); Serial.print(tr*1e9,1); Serial.println(F(" ns")); Serial.print(F("Fall time (90-10%): ")); Serial.print(tf*1e9,1); Serial.println(F(" ns")); delay(1000); }
Note on SCPI variants (only if needed on your RP image):
- If
ACQ:DEC:Factor 1
is not recognized, tryACQ:DEC 1
.
- If
ACQ:DATA:FORMAT VOLTS
/ACQ:DATA:UNITS ASCII
are not recognized, the typical pair is: ACQ:DATA:FORMAT ASCII
ACQ:DATA:UNITS VOLTS
- If
ACQ:TRig:*
is rejected, useACQ:TRIG:*
(case/spelling).
- If
ACQ:SOUR1:DATA? 0, N-1
is not supported, useACQ:SOUR1:DATA?
(no indices).
Keep the rest of the flow identical.
Run it
- Power your setup; ensure SCPI server is running on Red Pitaya.
- Flash the sketch; open Serial Monitor @ 115200.
- You’ll see a line printed each second with rise and fall times in ns.
The edge is created by toggling DIO0_P. Your photodetector output is sampled on IN1 at maximum rate (125 MS/s), a small block is parsed, and thresholds (10%/90%) are applied to compute the timings.
Analyze your results
- Convert trt_rtr (seconds) to bandwidth:
- Compare rise vs. fall: large asymmetry suggests device physics, saturation, or circuit asymmetry.
- If you change pull-up R, detector bias, or use a TIA, repeat and observe how trt_rtr and the estimated BW shift.
Tuning knobs
- Sample window (N): Increase if you need a wider view (e.g.,
const int N = 1000;
).
- Time base: For slower edges, increase decimation (if you change to
ACQ:DEC K
) and updateSAMPLE_PERIOD
accordingly:
Ts=DEC/125e6T_s = \text{DEC} / 125\text{e6}Ts=DEC/125e6.
- Thresholding: 10–90% is robust. If noise is present, consider median smoothing on
s[]
before threshold detection.
Troubleshooting
- No data / timeout: Confirm SCPI server is running; try port 5000; ensure Wi-Fi SSID/PASS/IP are correct.
- SCPI errors: If a command is not recognized, use the alternative forms listed above.
- Flat-line or clipping: Check your IN1 range (use LV for small signals, HV for larger) and physical wiring.
- No edge detected: Verify DIO0_P is configured as OUT (done in
setup()
), and that your photodetector actually “sees” that toggle (e.g., LED path, optical alignment).