0

我正在使用 NI DAQ 卡读取力传感器数据,我能够完美地读取数据,但我很困惑如何设置所需的采样率。现在,当我检查采样率时,它会给我随机值,就像某个时间一样是500hz,有时是50000hz。

这是我用于读取力传感器数据和计算采样率的代码。

main.h file
 int Run_main(void *pUserData)
    {
        /** to visual data **/
        CAdmittanceDlg* pDlg = (CAdmittanceDlg*)pUserData;
        /** timer for calculating sampling rate **/
        performanceTest timer;
        /*** Force Sensor **/
        ForceSensor sensor1("FT8682.cal","FT_Sensor1","dev3/ai0:6",2,1.0e4);
        std::vector<double> force;
        while(1)
        {
            /*** start time  ***/
            timer.setStartTimeNow();
            /*** reading force sensor data  ***/
            force=sensor1.readForce();
            /*** visualize data in GUI  ***/
            pDlg->setCustomData(0,"force_x ",force[0]);
            pDlg->setCustomData(1,"force_y ",force[1]);
            pDlg->setCustomData(2,"force_z ",force[2]);
            pDlg->setCustomData(3,"position ",currentPosition);
            timer.increaseFrequencyCount();
            /*** end time  ***/
            pDlg->setCustomData(4,"Sampling rate ",1/timer.getElapsedTime());
        }


        return 1;
    }

//here is ForceSensor.h file
class ForceSensor
{
public:

    DAQmx board;
    Calibration *cal;

    float bias[7];
    std::vector<double> f;

    ForceSensor(std::string calibrationFile, std::string task,
        std::string device, uInt64 numberOfSamples = 1000, float64 samplingRate = 1.0e4):
    board(task, device, numberOfSamples, samplingRate),
        f(6, 0)
    {
        board.initRead();
        cal = createCalibration(calibrationFile.c_str(), 1);
        if (!cal) throw std::runtime_error(calibrationFile + " couldn't be opened");
        board.readVoltage(2); // to get reed of zeros in the first batch of samples
        board.readVoltage(1000); // read big number of samples to determine the bias

        std::copy (board.da.cbegin(), board.da.cend(), std::ostream_iterator<double>(std::cout, " "));
        std::cout << std::endl;

        Bias(cal, board.da.data());
    }

    ~ForceSensor(void){}

    const std::vector<double> & readForce(){
        auto forces = std::vector<float>(6, 0);


        board.readVoltage(2);

        ConvertToFT(cal, board.da.data(), forces.data());

        std::transform(forces.cbegin(), forces.cend(),
            f.begin(),
            [](float a){ return static_cast<double>(a);});

        return f;
    }


};

//DAQmx.h file
class DAQmx {
    float64 MaxVoltage;
    TaskHandle taskHandle;
    TaskHandle counterHandle;
    std::string taskName;
    std::string device;
    float64 samplingRate;

public:
    std::vector <float> da;
    uInt64 numberOfSamples;


    DAQmx(std::string task, std::string device, uInt64 numberOfSamples = 1000, float64 samplingRate = 1.0e4):
        taskName(task), device(device), samplingRate(samplingRate), numberOfSamples(numberOfSamples),
        da(7, 0.0f)
    {
         MaxVoltage = 10.0;
    }

    ~DAQmx()
    {
        if( taskHandle == 0 )  return;
        DAQmxStopTask(taskHandle);
        DAQmxClearTask(taskHandle);
    }

    void CheckErr(int32 error, std::string functionName = "")
    {
        char        errBuff[2048]={'\0'};

        if( DAQmxFailed(error) ){
            DAQmxGetExtendedErrorInfo(errBuff, 2048);

            if( taskHandle!=0 )  {
                DAQmxStopTask(taskHandle);
                DAQmxClearTask(taskHandle);
            }
            std::cerr << functionName << std::endl;
            throw std::runtime_error(errBuff);
        }
    }


    void initRead()
    {
        CheckErr(DAQmxCreateTask(taskName.c_str(), &taskHandle));
        CheckErr(DAQmxCreateAIVoltageChan(taskHandle, device.c_str(),"", DAQmx_Val_Diff ,-10.0,10.0,DAQmx_Val_Volts,NULL));
//        CheckErr(DAQmxCfgSampClkTiming(taskHandle, "" , samplingRate, DAQmx_Val_Rising, DAQmx_Val_FiniteSamps, numberOfSamples));
        CheckErr(DAQmxCfgSampClkTiming(taskHandle, "OnboardClock" , samplingRate, DAQmx_Val_Rising, DAQmx_Val_ContSamps, numberOfSamples*10));
        CheckErr(DAQmxSetReadRelativeTo(taskHandle, DAQmx_Val_MostRecentSamp ));
        CheckErr(DAQmxSetReadOffset(taskHandle,-1));
        CheckErr(DAQmxStartTask(taskHandle));
    }

    void initWrite()
    {
        CheckErr(DAQmxCreateTask(taskName.c_str(), &taskHandle));
        CheckErr(DAQmxCreateAOVoltageChan(taskHandle, device.c_str(),"",-10.0, 10.0, DAQmx_Val_Volts,""));
        CheckErr(DAQmxStartTask(taskHandle));
    }


    int32 readVoltage (uInt64 samples = 0, bool32 fillMode = DAQmx_Val_GroupByScanNumber) //the other option is DAQmx_Val_GroupByScanNumber
    {
        int32   read; // samples actually read
        const float64 timeOut = 10;
        if (samples == 0) samples = numberOfSamples;
        std::vector<float64> voltagesRaw(7*samples);

        CheckErr(DAQmxReadAnalogF64(taskHandle, samples, timeOut, fillMode,
                                    voltagesRaw.data(), 7*samples, &read, NULL));
//        DAQmxStopTask(taskHandle);
        if (read < samples)
            throw std::runtime_error ("DAQmx::readVoltage(): couldn't read all the samples,"
                                      "requested: "  + std::to_string(static_cast<long long>(samples)) +
                                      ", actually read: " + std::to_string(static_cast<long long>(read)));

        //we change it
        for(int axis=0;axis < 7; axis++)
        {
            double temp = 0.0;
            for(int i=0;i<read;i++)
            {
                temp += voltagesRaw[i*7+axis];
            }
            da[axis] = temp / read;
        }


        return read;
    }


    void writeVoltage(float64 value)
    {
        if (value > MaxVoltage) value = MaxVoltage;
        if (value < -MaxVoltage) value = -MaxVoltage;
        const float64 timeOut = 10;
            //A value of 0 indicates to try once to read the requested samples.
            //If all the requested samples are read, the function is successful.
            //Otherwise, the function returns a timeout error and returns the samples that were actually read.

        float64 data[1] = {value};
        int32 written;

        CheckErr(DAQmxWriteAnalogF64(taskHandle, 1, 1, timeOut, DAQmx_Val_GroupByChannel, data, &written, NULL));

        DAQmxStopTask(taskHandle);
    }

};

编辑:当我更改样本数量时,我的应用程序的吞吐量会急剧下降,在 ForceSensor.h 文件中,如果我将以下函数中的样本数从 2 更改为 100,应用程序的吞吐量会从 10kHz 降低到 500Hz。请指导我与此有关。

const std::vector<double> & readForce(){
        auto forces = std::vector<float>(6, 0);

        //changing value from 2 to 100 decrease the throughput from 10Khz to          500Hz
        board.readVoltage(2);
        ConvertToFT(cal, board.da.data(), forces.data());

        std::transform(forces.cbegin(), forces.cend(),
            f.begin(),
            [](float a){ return static_cast<double>(a);});


        return f;
    }

我也在使用 NI Motion 卡来控制电机,下面是我添加了 NI Motion 代码的 main.h 文件。当我在 main.h 文件中添加 NI Motion 代码时,应用程序的吞吐量会降低到 200Hz,无论我在力传感器中使用的样本数是多少。当同时使用 NI Motion 控制电机和 DAQ 读取力传感器时,有什么方法可以提高应用程序的吞吐量?

main.h
inline int Run_main(void *pUserData)
    {
        /** to visual data **/
        CAdmittanceDlg* pDlg = (CAdmittanceDlg*)pUserData;
        /** timer for calculating sampling rate **/
        performanceTest timer;
        /*** Force Sensor **/
        ForceSensor sensor1("FT8682.cal","FT_Sensor1","dev3/ai0:6",2,4.0e4);
        /*** NI Motion Card 2=board ID, 0x01=axis number**/
        NiMotion Drive(2,0x01);
        int count=0;
        sensor1.Sampling_rate_device(&sampling_rate);
        std::vector<double> force;
        timer.setStartTimeNow();
        while(x)
        {
            /*** start time  ***/

            /*** reading force sensor data  ***/
            force=sensor1.readForce();
            /*** reading current position of drive  ***/
            currentPosition = Drive.readPosition();
           /*** Actuate Drive ***/
            Drive.actuate(2047);

            enalble_motor=Drive.isEnabled();

            /*** visualize data in GUI  ***/
            pDlg->setCustomData(0,"force_x ",force[0]);
            pDlg->setCustomData(1,"force_y ",force[1]);
            pDlg->setCustomData(2,"force_z ",force[2]);
            pDlg->setCustomData(3,"position ",currentPosition);
            pDlg->setCustomData(5,"sampling rate of device ",sampling_rate);
            timer.increaseFrequencyCount();
            count++;
            if(count==1000)
            {
                pDlg->setCustomData(4,"time elapsed ",count/timer.getElapsedTime());
                count=0;
                timer.setStartTimeNow();
            }
            /*** end time  ***/
        }


        return 1;
    }

//here is NiMotion.file 
class NiMotion {

    u8  boardID;    // Board identification number
    u8  axis;       // Axis number
    u16 csr;        // Communication status register
    u16 axisStatus; // Axis status
    u16 moveComplete;
    int32 encoderCounts; //current position [counts]
    int32 encoderCountsStartPosition;
    double position; //Position in meters
    bool read_first;

public:
    NiMotion(u8 boardID = 1, u8 axis = NIMC_AXIS1): boardID(boardID), axis(axis), csr(0)
    {
        init();
    }

    ~NiMotion(){enableMotor(false);}

    void init() {
        CheckErr(flex_initialize_controller(boardID, nullptr)); // use default settings
        CheckErr(flex_read_pos_rtn(boardID, axis, &encoderCounts));
        encoderCountsStartPosition = encoderCounts;
        enableMotor(false);
        read_first=true;
        loadConfiguration();
    }

    double toCm(i32 counts){ return counts*countsToCm; }

    i32 toCounts(double Cm){ return (Cm/countsToCm); }

    double readPosition(){
//        CheckErr(flex_read_pos_rtn(boardID, axis, &encoderCounts));

        CheckErr(flex_read_encoder_rtn(boardID, axis, &encoderCounts));
        if(read_first)
        {
            encoderCountsStartPosition = encoderCounts;
            read_first=false;
        }

        encoderCounts -= encoderCountsStartPosition;
        position = encoderCounts*countsToCm;
        return position;
    }

    void enableMotor(bool state)
    {
        if (state)
            CheckErr(flex_set_inhibit_output_momo(boardID, 1 << axis, 0));
        else
            CheckErr(flex_set_inhibit_output_momo(boardID, 0, 1 << axis));
    }

    bool isEnabled(){
        u16 home = 0;
        CheckErr(flex_read_home_input_status_rtn(boardID, &home));
        return (home & (1 << axis));
    }

//    void resetPosition()
//    {
////        CheckErr(flex_reset_encoder(boardID, NIMC_ENCODER1, 0, 0xFF));
//        CheckErr(flex_reset_pos(boardID, NIMC_AXIS1, 0, 0, 0xFF));
//    }

    void actuate(double positionCommand)
    {
        int32 positionCommandCounts = toCounts(positionCommand);
//        CheckErr(flex_load_dac(boardID, NIMC_DAC1, std::lround(positionCommand), 0xFF));
//        CheckErr(flex_load_dac(boardID, NIMC_DAC2, std::lround(positionCommand), 0xFF));
//        CheckErr(flex_load_dac(boardID, NIMC_DAC3, std::lround(positionCommand), 0xFF));
        // CheckErr(flex_load_dac(boardID, NIMC_DAC1, (positionCommand), 0xFF));
         CheckErr(flex_load_target_pos(boardID, axis, (positionCommand), 0xFF));
         CheckErr(flex_start(2, axis, 0));
         //CheckErr(flex_load_target_pos (2, 0x01, 2047, 0xFF));
//        CheckErr(flex_load_dac(boardID, NIMC_DAC3, 10000, 0xFF));

//        std::cout << PositionCotroller(desiredPositionCounts, encoderCounts) << std::endl;
//        std::this_thread::sleep_for(cycle);
    }

    void moveToPosition(double desiredPosition, double P, double I, double D)
    {
       /* int32 desiredPositionCounts = toCounts(desiredPosition);
        std::chrono::milliseconds cycle(100);
        PIDController PositionCotroller = PIDController(P, I, D, 0.1);
        while((encoderCounts - desiredPositionCounts)*(encoderCounts - desiredPositionCounts) > 100){
            CheckErr(flex_load_dac(boardID, NIMC_DAC1, PositionCotroller(desiredPositionCounts, encoderCounts), 0xFF));
            std::cout << PositionCotroller(desiredPositionCounts, encoderCounts) << std::endl;
            std::this_thread::sleep_for(cycle);*/
       // }
    }

     void loadConfiguration()
    {
       // Set the velocity for the move (in counts/sec)
    CheckErr(flex_load_velocity(boardID, axis, 10000, 0xFF));
    // Set the acceleration for the move (in counts/sec^2)
    CheckErr(flex_load_acceleration(boardID, axis, NIMC_ACCELERATION, 100000, 0xFF));


    // Set the deceleration for the move (in counts/sec^2)
    CheckErr(flex_load_acceleration(boardID, axis, NIMC_DECELERATION, 100000, 0xFF));

    // Set the jerk (s-curve value) for the move (in sample periods)
    CheckErr(flex_load_scurve_time(boardID, axis, 100, 0xFF));


    // Set the operation mode to velocity
    CheckErr(flex_set_op_mode(boardID, axis, NIMC_RELATIVE_POSITION));

    }
    void CheckErr(int32 error){
        if(error !=0 ) {
            u32 sizeOfArray;                        //Size of error description
            u16 descriptionType = NIMC_ERROR_ONLY;  //The type of description to be printed
            u16 commandID = 0;
            u16 resourceID = 0;
            sizeOfArray = 0;
            flex_get_error_description(descriptionType, error, commandID, resourceID, NULL, &sizeOfArray );
            // NULL means that we want to get the size of the message, not message itself

            sizeOfArray++;
            std::unique_ptr<i8[]> errorDescription(new i8[sizeOfArray]);

            flex_get_error_description(descriptionType, error, commandID, resourceID, errorDescription.get(), &sizeOfArray);
            throw std::runtime_error(errorDescription.get());
        }
    }
};
4

1 回答 1

0

你的问题有两个部分:

  1. “我对我观察到的采样率感到惊讶。”
  2. “我想让我的控制回路速度更快。”

1. 采样率与应用程序吞吐量

在你的Run_main,你有

pDlg->setCustomData(4,"Sampling rate ",1/timer.getElapsedTime());

这似乎是您“检查采样率”的方式。那不是报告采样率,而是报告应用程序吞吐量。

查询采样率

如果您想向驱动程序询问设备的采样率,请使用

int32 DllExport __CFUNC DAQmxGetSampClkRate(TaskHandle taskHandle, float64 *data);

了解应用程序吞吐量

虽然硬件始终以恒定速率对数据进行采样,但仍需要将其传输到应用程序的内存中。该速率并不总是匹配的 - 有时它更慢,有时它更快,这取决于其他操作系统任务和应用程序接收多少 CPU。

重要的是平均应用程序吞吐量与硬件的采样率相匹配。如果应用程序无法跟上,最终硬件将填充其板载和驱动程序缓冲区并返回错误。

调整应用程序吞吐量

NI-DAQmx 有几种不同的方法来控制硬件将数据传输到应用程序的方式和时间。我推荐探索的两个是

  • 使用在获取 N 个样本时触发的回调

    int32 __CFUNC  DAQmxRegisterEveryNSamplesEvent(TaskHandle task, int32 everyNsamplesEventType, uInt32 nSamples, uInt32 options, DAQmxEveryNSamplesEventCallbackPtr callbackFunction, void *callbackData);
    
  • 在将设备的板载内存传输到主机之前调整设备的板载内存必须有多满

    int32 DllExport __CFUNC DAQmxSetAIDataXferMech(TaskHandle taskHandle, const char channel[], int32 data);
    

有关更多详细信息,请参阅帮助

2. 实施有效的控制回路

一旦你有了依赖于输入的输出,你就有了一个控制回路,并且你正在实现一个控制系统。获取输入的速度越快,计算出的新设定点越快,并将新的设定点发送到输出端,系统的控制就越好。

基线

操作系统是任何控制系统的基础。在频谱的高性能端是无操作系统系统或“裸机”系统,如 CPLD、微处理器和数字状态机。另一方面是抢占式操作系统,如 Mac、Windows 和桌面 Linux。在中间,您会发现实时操作系统,例如 QNX、VxWorks 和 RTLinux。

每个都有不同的功能,对于像 Windows 这样的抢先操作系统,您可以预期最大循环速率约为 1 kHz(一个输入、计算、输出周期在 1 ms 内)。输入、计算和输出越复杂,最大速率越低。

在操作系统之后,I/O 传输(例如 USB 与 PCIe 与以太网)、I/O 驱动程序和 I/O 硬件本身开始降低最大循环速率。最后,最终程序添加了自己的延迟。

您的问题是询问该程序。您已经选择了您的操作系统和 I/O,因此您会受到它们的限制。

在抢占式操作系统上调整控制回路

输入端,加快读取速度的唯一方法是提高硬件采样率并减少采集的样本数量。对于给定的采样率,收集 100 个样本所需的时间少于收集 200 个样本所需的时间。计算新设定点所需的样本越少,循环速率可以运行得越快。

DAQmx 有两种快速读取方法:

  1. 硬件定时单点。有几种不同的方法可以使用此模式。虽然 Windows 支持 HWTSP,但 DAQ 驱动程序和硬件在 LabVIEW RT(NI Real-Time OS)下运行得更快。

  2. 有限的收购。DAQmx 驱动程序已针对快速重启有限任务进行了优化。由于有限任务在获取所有样本后进入停止状态,因此程序只需重新启动任务。结合 every-N-samples-callback,程序可以依靠驱动程序告诉它数据已准备好处理并立即重新启动任务以进行下一次循环迭代。

输出端,NI-Motion 驱动程序使用数据包从电机的嵌入式控制器发送和接收数据。API 已经针对高性能进行了调整,但 NI 确实有一份描述最佳实践的白皮书。也许其中一些适用于您的情况。

于 2015-01-28T21:46:30.513 回答