Can anyone please sanity check this simple triggered capture script?

Post general discussions on using our drivers to write your own software here
Post Reply
nsavulkin
Newbie
Posts: 0
Joined: Thu Jun 27, 2024 7:28 pm

Can anyone please sanity check this simple triggered capture script?

Post by nsavulkin »

I'm trying to automate a simple task of recording a segment of fixed length after a trigger so I can record large number of segments when collecting data for my research. In particular I have an antenna which aptures electromagnetic emanations of HDMI cable. At the start of each transfered frame there is a sharp voltage peak exceeding 1.5 volts I use as a trigger. Can anyone take a brief look at the code and tell me if the way I use API is sound with what I want to achieve as I had no experience with using picoscope API beforehand.

My implementtion in Rust with expected behaviour in comments is below.

Code: Select all


unsafe extern "C" fn callback(handle: i16, status: PicoStatus, flag: *mut ::std::os::raw::c_void) {
    *(flag as *mut _ as *mut bool) = true;
}


fn main() {

    /*
    First I have to connect to the picoscope (model is 5203).
    I use ps5000OpenUnit call which provides me a handle uniquely identifying connected device to the API.
    */
    let mut handle: i16 = 0;
    unsafe {
        // Connect to the oscilloscope, model 5203
        assert_eq!(ps5000OpenUnit(&mut handle), PicoOk);
    }

    /*
    ETS stands for equivalent time sampling.
    It is used to aggregate multiple cycles of repeating signal in order to improve approximated signal.
    I disable it as in this case signal is not repeating due to occasional noise spikes, etc
    */
    unsafe {
        // Disable ETS as I need unprocessed signal
        assert_eq!(ps5000SetEts(handle, ETSOff, 0, 0, null_mut()), PicoOk);
    }

    /*
    I only use channel A to which antenna is connected. Therefore I disable channel B. 
    I know that 2v range is sufficent for my task as this is the range PicoScope 7 client chooses when range is set to auto.
    */
    unsafe {
        assert_eq!(ps5000SetChannel(handle, ChannelA, 1, 1, R2V), PicoOk);
        assert_eq!(ps5000SetChannel(handle, ChannelB, 0, 1, R2V), PicoOk);

    }

    /*
    Set simple trigger to fire when abnormally high (1.5v) pulse is detected on A channel. 
    The trigger fires on rising edge of the pulse.
    The triger will not timeout and wait indefinetly because auto_trigger_ms is set to 0.
    */
    unsafe {
        assert_eq!(ps5000SetSimpleTrigger(handle, 
                                          1, 
                                          ChannelA, 
                                          mv_to_adc(1500, R2V) as i16, 
                                          Rising, 
                                          0, 
                                          0), 
                   PicoOk);
    }

    /*
    I need to sample at 125Mhz which is 125_000_000Hz. 
    I need to record at least one full frame.
    I can only record one full frame as when using PicoScope 7 with required samping rate in SR priority mode I only had
    2 waveforms of data which only had 2 voltage spikes meaning there was only one full frame. Assuming PicoScope 7 
    doesnt reduce buffer size unless forced to its the hardware limit I cannot overcome.
    At 60 fps, 1 frame takes 1/60 s. 
    125_000_000 / 60 = 2083333.3
    Since I cannot have fractional samples and that fps is not percisely 60 but fluctuates I round up number of samples 
    to 2083400 in hopes this is sufficent to record whole frame while still allowed by hardware limitations.
    */
    let mut buffer: Vec = vec![0;2083400];

    unsafe {
        assert_eq!(ps5000SetDataBuffer(handle, ChannelA, buffer.as_mut_ptr(), 2083400), PicoOk);
    }

    /*
    I do not fully understand what a timebase is.
    The way I understood, function ps5000GetTimebase takes sample rate encoded as timebase value as stated in programing guide
    as well as required number of samples. It then returns PicoOk if this combination of sampling rate and number samples is within 
    hardware limits. Otherwise it returns one of the error codes.
    In either case it sets sampling rate and number samples parameters passed as pointers to the closest permissible values.
    I assume oversampling refers to subdividing each sample into multiple samples and averaging them. Therefore, due to 
    already tight situation with buffer size I set it to one, which means samples wont be subdivided.
    */
    let mut sampling_rate: i32 = 0;
    let mut number_samples: i32 = 0;

    unsafe {
        ps5000GetTimebase(
            handle,
            3, // period of 8ns which means signal has frequency of 125Mhz
            2083400,
            &mut sampling_rate,
            1, // no oversampling since otherwise I wont be able to store whole HDMI frame
            &mut number_samples,
            0
        );
    }

    /*
    Here I call RunBlock requesting the capture to begin with the triger, that is no samples before trigger would be stored.
    I pass user defined callback which upon capture complete will be called on the parameter passed via p_parameter.
    here, my callback simply sets flag to true.
    */
    let mut flag: bool = false;
    unsafe {
        ps5000RunBlock(
            handle,
            0, // begin at the trigger, do not sample before
            2083400, // sample whole frame after trigger
            3,
            1,
            null_mut(),
            0,
            Some(callback),
            &mut flag as *mut bool as *mut _
        );
    }

    // poll untill capture complete
    while !flag {
        ;
    }


    /*
    Call to ps5000GetValues dumps everything to the buffer passed earlier on. 
    Downsample ratio allows to downsample signal stored in oscilloscope memory before transfering it to the PC.
    By setting mode to None I choose not to downsample and get each sample recorded.
    Samples returned will be populated by the actual number of samples recorded. I do not know why it may differ, but I take it's asanity check.
    */
    let mut samples_returned: u32 = 0;
    unsafe {
        assert_eq!(ps5000GetValues(handle,
                        0,
                        &mut samples_returned,
                        1,
                        RatioModeNone,
                        0,
                        null_mut()), PicoOk);
    }


    /*
    At the end, I gracefully disconnect from the picoscope using call to ps5000CloseUnit.
    */
    unsafe {
        assert_eq!(ps5000CloseUnit(handle), PicoOk);
    }
}

For completness here are the binds I have defined (they were mostly just rewriting output of bindgen)

Code: Select all

pub fn mv_to_adc(mv: i16, pr: PicoRange) -> i16 {
    static INPUT_RANGE: [u16; 12] = [ 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000];
    return ((mv as i32 * 32512) / INPUT_RANGE[pr as i32 as usize] as i32) as i16;
}

pub type ps5000BlockReady = Option<
    unsafe extern "C" fn(handle: i16, status: PicoStatus, p_parameter: *mut ::std::os::raw::c_void),
>;



#[derive(PartialEq, Debug, Copy, Clone)]
#[repr(C)]
pub enum PicoStatus {
    PicoOk = 0,
    PicoInvalidHandle = 12,
    PicoNullParameter = 22,
    PicoInvalidInfo = 41,
    PicoInfoUnavailable = 42,
    PicoOsNotSupported = 11,
    PicoOpenOperationInProgress = 5,
    PicoEepromCorrupt = 10,
    PicoKernelDriverTooOld = 9,
    PicoFwFail = 4,
    PicoMaxUnitsOpened = 1,
    PicoNotFound = 3,
    PicoNotResponding = 7,
    PicoUserCallback = 35,
    PicoInvalidParameter = 13,
    PicoInvalidChannel = 16,
    PicoInvalidVoltageRange = 15,
    PicoDriverFunction = 67,
    PicoInvalidTimebase = 14,
    PicoTooManySamples = 29,
    PicoSegmentOutOfRange = 38,
    PicoInvalidTriggerChannel = 17,
    PicoInvalidConditionChannel = 18,
    PicoConfigFail = 8,
    PicoWarningAuxOutputConflict = 52,
    PicoWarningExtThresholdConflict = 51,
    PicoTriggerError = 44,
    PicoNoSamplesAvailable = 37,
    PicoDeviceSampling = 36,
    PicoDataNotAvailable = 24,
    PicoStartindexInvalid = 40,
    PicoInvalidSampleratio = 64,
    PicoInvalidCall = 60,
    PicoMemory = 45,

}

#[derive(Copy, Clone)]
#[repr(C)]
pub enum PicoInfo {
    PicoDriverVersion = 0,
    PicoUsbVersion = 1,
    PicoHardwareVersion = 2,
    PicoVariantInfo = 3,
    PicoBatchAndSerial = 4,
    PicoCalDate = 5,
    PicoKernelVersion = 6
}


#[repr(C)]
pub enum PicoETSMode {
    ETSOff = 0,
    ETSFast = 1,
    ETSSlow = 2,
    ETSModesMax = 3
}

#[repr(C)]
pub enum PicoChannel {
    ChannelA = 0,
    ChannelB = 1
}

#[repr(C)]
pub enum PicoRange {
    R10MV = 0,
    R20MV = 1,
    R50MV = 2,
    R100MV = 3,
    R200MV = 4,
    R500MV = 5,
    R1V = 6,
    R2V = 7,
    R5V = 8,
    R10V = 9,
    R20V = 10,
    R50V = 11,
    RMaxRanges = 12,

}


#[repr(C)]
pub enum ThresholdDirection {
    Above = 0,
    Below = 1,
    Rising = 2,
    Falling = 3,
    RisingOrFalling = 4
}

#[repr(C)]
pub enum RatioMode {
    RatioModeNone = 0,
    RatioModeAggregate = 1,
    RatioModeDecimate = 2,
    RatioModeAverage = 4,
    RatioModeDistribution = 8
}





#[link_name = "ps5000.lib"]
extern "C" {

    pub fn ps5000OpenUnit(p_handle: *mut i16) -> PicoStatus;

    pub fn ps5000CloseUnit(handle: i16) -> PicoStatus;

    pub fn ps5000GetUnitInfo(
        handle: i16,
        buf: *mut u8,
        buf_len: i16,
        required_len: *mut i16,
        info: PicoInfo,
    ) -> PicoStatus;

    pub fn ps5000SetEts(
        handle: i16,
        mode: PicoETSMode,
        ets_cycles: i16,
        ets_interleave: i16,
        sample_time_picoseconds: *mut i32,
    ) -> PicoStatus;

    pub fn ps5000SetChannel(
        handle: i16,
        channel: PicoChannel,
        enabled: i16,
        dc: i16,
        range: PicoRange,
    ) -> PicoStatus;


    pub fn ps5000SetSimpleTrigger(
        handle: i16,
        enable: i16,
        source: PicoChannel,
        threshold: i16,
        direction: ThresholdDirection,
        delay: u32,
        auto_trigger_ms: i16,
    ) -> PicoStatus;

    pub fn ps5000SetDataBuffer(
        handle: i16,
        channel: PicoChannel,
        buffer: *mut i16,
        buffer_len: i32,
    ) -> PicoStatus;

    pub fn ps5000GetTimebase(
        handle: i16,
        timebase: u32,
        no_samples: i32,
        time_interval_nanoseconds: *mut i32,
        oversample: i16,
        max_samples: *mut i32,
        segment_index: u16,
    ) -> PicoStatus;

    pub fn ps5000RunBlock(
        handle: i16,
        no_of_pre_trigger_samples: i32,
        no_of_post_trigger_samples: i32,
        timebase: u32,
        oversample: i16,
        time_indisposed_ms: *mut i32,
        segment_index: u16,
        lp_ready: ps5000BlockReady,
        p_parameter: *mut ::std::os::raw::c_void,
    ) -> PicoStatus;

    pub fn ps5000GetValues(
        handle: i16,
        start_index: u32,
        no_of_samples: *mut u32,
        down_sample_ratio: u32,
        down_sample_ratio_mode: RatioMode,
        segment_index: u16,
        overflow: *mut i16,
    ) -> PicoStatus;
}
NeilH
PICO STAFF
PICO STAFF
Posts: 298
Joined: Tue Jul 18, 2017 8:28 am

Re: Can anyone please sanity check this simple triggered capture script?

Post by NeilH »

Hi

From looking through the code is seems pretty sensible and like it should work fine.
Have you tried running it?
Neil
Technical Support Engineer
Post Reply