-1

我找到了一个关于如何使用一些 DAQmx 功能的 NI 示例。这是一个简单的 C 文件,包含以下一些内容:

...
// This is a declaration/definition I think
int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);
...
// Later in the script there is actual function

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
    ...
return 0;
}

当我倾向于使用 .h 文件中定义的某些变量或函数时,ChangeDetectionCallback 函数无法识别它们。我试图将此回调函数定义为 .h 文件中的成员函数,希望现在所有函数都可以访问。这是我的 .h 内容:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "NIDAQmx.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);

private:
Ui::MainWindow *ui;
void mainLoop();
};

#endif // MAINWINDOW_H

这是我的 .c 内容:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "NIDAQmx.h"

#include <stdio.h>
#include <string.h>
#include <time.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mainLoop();
}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::mainLoop()
{
...
DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));
...    
}




int32 MainWindow::ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
...
return 0;
}

因此,我再次尝试了许多错误的方法来在头文件中定义我的回调函数,但均未成功。请帮我弄清楚这一点。这是我不清楚的错误消息:

D:\Projects\sapm3\mainwindow.cpp:37: error: cannot convert 'MainWindow::ChangeDetectionCallback' from type 'int32 (MainWindow::)(TaskHandle, int32, void*) {aka long int (MainWindow::)(void*, long int, void*)}' to type 'DAQmxSignalEventCallbackPtr {aka long int (__attribute__((__cdecl__)) *)(void*, long int, void*)}'
     DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));

这是原始代码。它触发回调函数以获取测量样本并将数据输出到控制台。我希望将采样数据写入我的成员变量并发出在对象的 .h 文件中定义的信号。

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <NIDAQmx.h>

#define DAQmxErrChk(functionCall) if( DAQmxFailed(error=(functionCall)) ) goto Error; else

static TaskHandle   taskHandle;
static uInt32       numLines;
static uInt8        cachedData[200];

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData);
void Cleanup (void);

int main(void)
{
int32       error=0;
char        errBuff[2048]={'\0'};

/*********************************************/
// DAQmx Configure Code
/*********************************************/
DAQmxErrChk (DAQmxCreateTask("",&taskHandle));
DAQmxErrChk (DAQmxCreateDIChan(taskHandle,"Dev1/port0/line0:7","",DAQmx_Val_ChanPerLine));
DAQmxErrChk (DAQmxCfgChangeDetectionTiming(taskHandle,"Dev1/port0/line0:7","Dev1/port0/line0:7",DAQmx_Val_ContSamps,1));
DAQmxErrChk (DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallback,NULL));
DAQmxErrChk (DAQmxGetTaskNumChans(taskHandle,&numLines));

/*********************************************/
// DAQmx Start Code
/*********************************************/
DAQmxErrChk (DAQmxStartTask(taskHandle));

puts("Continuously reading. Press Enter key to interrupt\n");

puts("Timestamp                 Data read   Changed Lines");

getchar();

Error:
if( DAQmxFailed(error) )
{
    DAQmxGetExtendedErrorInfo(errBuff,2048);
    Cleanup();
    printf("DAQmx Error: %s\n",errBuff);
}
printf("End of program, press Enter key to quit\n");
getchar();
return 0;
}

int32 CVICALLBACK ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID, void *callbackData)
{
int32   error=0;
uInt8   data[200]={0};
int32   numRead;
uInt32  i=0;
char    buff[512], *buffPtr;
char    errBuff[2048]={'\0'};
char    *timeStr;
time_t  currTime;

if( taskHandle ) {
    time (&currTime);
    timeStr = ctime(&currTime);
    timeStr[strlen(timeStr)-1]='\0';  // Remove trailing newline.

    /*********************************************/
    // DAQmx Read Code
    /*********************************************/
    DAQmxErrChk (DAQmxReadDigitalLines(taskHandle,1,10.0,DAQmx_Val_GroupByScanNumber,data,8,&numRead,NULL,NULL));

    if( numRead ) {
        buffPtr = buff;
        strcpy(buff, timeStr);

        strcat(buff,"  ");
        buffPtr = buff + strlen(buff);
        for(;i<numLines;++i) {
            sprintf(buffPtr,"%d",data[i]);
            buffPtr++;
        }

        strcat(buff,"    ");
        buffPtr = buff + strlen(buff);
        for(i=0;i<numLines;++i) {
            sprintf(buffPtr,"%c",data[i]==cachedData[i]?'-':'X');
            buffPtr++;
            cachedData[i] = data[i];
        }
        puts(buff);
        fflush(stdout);
    }
}
return 0;

Error:
if( DAQmxFailed(error) )
{
    DAQmxGetExtendedErrorInfo(errBuff,2048);
    Cleanup();
    printf("DAQmx Error: %s\n",errBuff);
}
return 0;
}

void Cleanup (void)
{
if( taskHandle!=0 ) 
{
    /*********************************************/
    // DAQmx Stop Code
    /*********************************************/
    DAQmxStopTask(taskHandle);
    DAQmxClearTask(taskHandle);
    taskHandle = 0;
}
}

我找到了解决问题的方法。我在文件顶部声明了一个数组变量。这样我的回调函数就可以识别它。然后,我将此数组中的数据复制到我的成员数组中。同样,我创建了一个计数器变量,并在每次回调运行时递增它。同时我在我的成员函数中循环检查这个变量,直到它达到所需的值,然后发出一个信号。这种方法真的很糟糕,我希望找到一种更智能的方式来编写它。

4

3 回答 3

1

问题是您试图传递成员函数指针而不是函数指针。您可以使用间接来使其正常工作。

在类之外,您将定义一个函数:

int32 CVICALLBACK ChangeDetectionCallbackWrapper(TaskHandle taskHandle, int32 signalID, void *callbackData) {
    MainWindow * this_ = reinterpret_cast<MainWindow*>(callbackData);
    return this_->ChangeDetectionCallback(taskHandle, signalID);
}

然后定义要调用的 MainWindow 方法,如下所示:

int32 ChangeDetectionCallback(TaskHandle taskHandle, int32 signalID);

然后像这样注册它:

DAQmxRegisterSignalEvent(taskHandle,DAQmx_Val_ChangeDetectionEvent,0,ChangeDetectionCallbackWrapper,this));

请注意,该callbackData参数用于将指针传递给周围的对象。此数据在您注册事件时传递,而不是NULL.

这是 C 库的典型模式,也是如何将其连接到 C++ 的典型方式。

于 2013-12-31T16:38:26.910 回答
0

正如我在几个小时前的评论中指出的那样,您有几种方法可以解决此问题:

1) 使用比您当前使用的库更高级别的库,您可以优先使用信号和插槽,然后在 Qt 应用程序中非常理想。

2)您可以尝试将您的类实例作为原始数据传递。这将确保回调将有一个对象可以使用。当然,这需要像往常一样将 void* 数据重新解释为所需的类型。

  • 建立属性修改器和访问器方法来设置和获取成员
  • 将回调函数声明为朋友,但通常不鼓励这样做。
  • 只需从回调中调用插槽或简单方法即可为您完成工作。
  • 使用函子来完成这项工作
  • ETC...

3)如果不太复杂,您也可以自己在项目中实现必要的逻辑。这样,您甚至可以减少依赖性。

很难说哪种方法最适合您,因为它在很大程度上取决于用例和比我们这里更多的上下文。

我可能会选择成员数据访问器和修改器方法,或者您的类中执行实际工作的专用函数,并且免费回调函数只会将 void* 转换为您的类型,并在该实例上调用该方法。

这也将确保您以后可以将回调与非 C++ 代码一起使用,因为您只需要替换它的内部实现。这也将有助于以后摆脱调用类方法的外部 C 回调。

我认为您应该查看以下 url,其中有一个相对详细的示例代码,其中还有一个针对此低级库的类似问题的回调。

NI-DAQmx C++ 封装代码

于 2013-12-31T16:47:55.907 回答
0

我们最近连接了一个用于六轴力传感器的 NI USB 模拟输入卡。连接回叫与 Vinzenz 的解决方案相同。在我们的示例中,我们只是锁定和读/写缓冲区向量来访问模拟电压值。我们的应用程序是 wxWidgets,但窗口库不需要知道回调。编译器是 VC10,程序是在 Win7 上构建的,尽管它应该可以在 Linux 上运行而无需更改。DAQmx 库不断回调并填充采样电压的原始数据数组。然后将这些平均并复制到 6D 矢量。如果需要,可以使用自定义事件生成这些事件返回到 Qt 或 wx,例如To_main_msg_evt下面的注释。我不明白的一件事是我们可以逃脱不包括CVICALLBACK它仍然有效。离开它会更好吗?如果没有,这似乎是一个更通用的解决方案。我还注意到,在卡初始化后大约三秒钟内回调不会开始。

//in ATI_force.hpp there is

int32 static Every_n_callback(  TaskHandle task_handle, 
                                int32 every_n_samples_evt_type,
                                uInt32 n_samples, 
                                void* obj_ref);

int32 Every_n_callback( TaskHandle task_handle, 
                        int32 every_n_samples_evt_type, 
                        uInt32 n_samples);


//in ATI_force.cpp in the Init() function there is

for(int i = 0; i < 6; ++i)
{
    channel_port = ports_names[i];
    channel_name = channels_names[i];

    if(still_ok)
    {
        still_ok = NI_ok(DAQmxCreateAIVoltageChan(  _task_handle, 
                                                    channel_port.c_str(), 
                                                    channel_name.c_str(),
                                                    DAQmx_Val_Cfg_Default,
                                                    -10.0, //min max volts params
                                                    10.0,
                                                    DAQmx_Val_Volts,
                                                    NULL));
    }
}
if(still_ok) 
{
    //todo what is the 1000 param ->
    //indicate continuous sampling at so many milliseconds (3rd param) 
    still_ok = NI_ok(DAQmxCfgSampClkTiming(_task_handle, "", _sample_every_ms, DAQmx_Val_Rising, DAQmx_Val_ContSamps, 1000));   
}
if(still_ok)
{
    //register the read callback Every_n_callbaint 
    int callback_every_n_samples(10); //<-effets speed of aquisition
    still_ok = NI_ok(DAQmxRegisterEveryNSamplesEvent(_task_handle, DAQmx_Val_Acquired_Into_Buffer, callback_every_n_samples, 0, Every_n_callback, this));               
}

//other useful functions

//used for the interface to the class
bool ATI_force::Read_all_channels(arma::vec6& vals_out)
{
    bool success(false);
    if(_is_initialized)
    {
        _chan_vals_mutex.lock();
        vals_out = _chan_vals;
        _chan_vals_mutex.unlock();
        success = true;
    }
    return success;
}

//the callback and its static wrapper

int32 ATI_force::Every_n_callback(TaskHandle task_handle, int32 every_n_samples_evt_type, uInt32 n_samples, void* obj_ref)
{
    ATI_force* obj_ptr(reinterpret_cast<ATI_force*>(obj_ref)); //obj_ref = "this"
    return obj_ptr->Every_n_callback(task_handle, every_n_samples_evt_type, n_samples);
}

int32 ATI_force::Every_n_callback(TaskHandle task_handle, int32 every_n_samples_evt_type, uInt32 n_samples)
{
    int32 ret(-1);
    bool still_ok(true);
    //{
    //  std::ostringstream oss;
    //  oss << "In Every_n_callback: " << std::endl;
    //  To_main_msg_evt ev(oss.str(), true);
    //  wxPostEvent(_parent, ev);
    //}
    //lock the mutex on the data and write to it 
    _num_read = 0;
    if(_is_initialized)
    {
        still_ok = NI_ok(DAQmxReadAnalogF64(_task_handle, 
                                            _num_samples, 
                                            _read_timeout_ms,  
                                            DAQmx_Val_GroupByChannel, 
                                            _data_buff.memptr(),  //writes over old vals
                                            _data_buff.size(), 
                                            &_num_read, 
                                            NULL)); //this or NULL in last param?? todo
        _chan_vals_buffer.zeros(); //zero out the values either way
        if(still_ok)
        {
            //for all six channels
            for(int j = 0; j < 6; ++j)
            {
                //average the samples
                for(int i = j*_num_samples; i < (j + 1)*_num_samples; ++i)
                {
                    _chan_vals_buffer.at(j) += _data_buff.at(i);
                }
                _chan_vals_buffer.at(j) /= static_cast<double>(_num_samples);
            }

        }
    }
    else
    {
        still_ok = false;
    }
    if(still_ok)
    {

        Condition_vals_out(_chan_vals_buffer);

        _chan_vals_mutex.lock();
        _chan_vals = _chan_vals_buffer; //this is the handoff to _chan_vals
        _chan_vals_mutex.unlock();
    }
    if(still_ok)
    {
        ret = 0;
    }
    return ret;
}

//the usage in the main form is roughly

void M_Frame::On_ati_test_btn_click(wxCommandEvent& event)
{
    if(!_ATI_force)
    {
        _ATI_force.reset(new ATI_force(this, 400.0, 50.0));
        boost::posix_time::seconds wait_time(5);
        boost::this_thread::sleep(wait_time);
    }

    double val_out(0.0);

    arma::vec6 vals_out;
    vals_out.zeros();

    if(_ATI_force->Is_initialized())
    {
        //_ATI_force->Reset_bias();
        std::cout << "_ATI_force Is Initialized." << std::endl;

        int num_reads(5);
        Stopwatch sw;
        sw.Restart();
        double total_time(0.0);
        double avg_time(0.0);

        _ATI_force->Bias_vals_out(vals_out);
        for(int i = 1; i < num_reads; ++i)
        {
            if(_ATI_force->Read_all_channels(vals_out))
            {
                std::cout << "voltages =" << vals_out << std::endl;
            }
            else
            {
                std::cout << "Read failed." << std::endl;
            }
        }
        total_time = static_cast<double>(sw.Get_elapsed_us());
        avg_time = total_time/static_cast<double>(std::max(num_reads - 1, 1));
        std::cout << "average read time = " << avg_time <<  "us" << std::endl;
    }
}

正如 Vinzenz 所指出的,这在将 C++ 类连接到 C 回调时很典型。另一个使用这种方法的库是 OpenCV。在这里,可以使用相同的模式设置鼠标回调,没有前导宏和cv::setMouseCallback 为 C 回调连接提供的C++ 包装器cvSetMouseCallback

//in .hpp

static void Mouse_handler(int event, int x, int y, int flags, void* param); //param = this
void Mouse_handler(int event, int x, int y, int flags); 

//in Template_select()

//...
cv::namedWindow(_template_select, CV_WINDOW_AUTOSIZE);
cv::imshow(_template_select, _temp_select);
cv::waitKey(30);
cv::setMouseCallback(_template_select, Mouse_handler, this);

//...
cv::destroyWindow(_template_select);

希望这些部分示例有用。

于 2015-06-22T06:58:43.130 回答