我正在使用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());
}
}
};
答案 0 :(得分:0)
您的问题分为两部分:
在Run_main
,您有
pDlg->setCustomData(4,"Sampling rate ",1/timer.getElapsedTime());
似乎是“检查采样率”。这不是报告采样率,而是报告应用程序吞吐量。
如果您想询问驱动程序设备的采样率,请使用
int32 DllExport __CFUNC DAQmxGetSampClkRate(TaskHandle taskHandle, float64 *data);
虽然硬件始终以恒定速率对数据进行采样,但仍需将其传输到应用程序的内存中。该速率并不总是匹配 - 有时速度较慢,有时速度较快,具体取决于其他操作系统任务以及应用程序接收的CPU数量。
重要的是平均应用程序吞吐量与硬件的采样率相匹配。如果应用程序无法跟上,那么最终硬件将填充其板载和驱动程序缓冲区并返回错误。
NI-DAQmx有几种不同的方法来控制硬件如何以及何时将数据传输到应用程序。我建议探索的两个是
使用在获取N个样本时触发的callback:
int32 __CFUNC DAQmxRegisterEveryNSamplesEvent(TaskHandle task, int32 everyNsamplesEventType, uInt32 nSamples, uInt32 options, DAQmxEveryNSamplesEventCallbackPtr callbackFunction, void *callbackData);
调整how full设备的板载内存必须在将其传输到主机之前
int32 DllExport __CFUNC DAQmxSetAIDataXferMech(TaskHandle taskHandle, const char channel[], int32 data);
有关详细信息,请参阅help。
一旦你有依赖于输入的输出,你有一个控制循环,你正在实现control system。可以获取更快的输入,计算新的设定点,并且将新的设定点发送到输出,可以更好地控制系统。
操作系统是任何控制系统的基础。在频谱的高性能端是无操作系统,或“裸机”系统,如CPLD,微处理器和数字状态机。另一方面是先发制人的操作系统,如Mac,Windows和桌面Linux。在中间你会找到实时操作系统,如QNX,VxWorks和RTLinux。
每个都有不同的功能,对于像Windows这样的先发制人的操作系统,你可以期待maximum loop rate of about 1 kHz(一个输入,计算,输出周期在1毫秒)。输入,计算和输出越复杂,最大速率就越低。
在操作系统之后,I / O传输(例如USB与PCIe与以太网),I / O驱动程序和I / O硬件本身开始降低最大循环速率。最后,结束程序会增加自己的延迟。
您的问题是询问该计划。您已经选择了操作系统和I / O,因此受限于它们。
在输入方面,提高读取速度的唯一方法是提高硬件采样率并减少采集的样本数。对于给定的采样率,收集100个样本所需的时间少于收集200个样本所需的时间。计算新设定点所需的样本越少,循环速率就越快。
DAQmx有两种快速读取方法:
Hardware-timed single point。有several different ways个使用此模式。虽然Windows支持HWTSP,但使用LabVIEW RT(NI实时操作系统)可以更快地执行DAQ驱动程序和硬件。
有限的收购。 DAQmx驱动程序已针对快速重启有限任务进行了优化。由于finite task enters the stopped state一旦获取了所有样本,程序只需要重新启动任务。结合每个N样本回调,程序可以依赖驱动程序告诉它数据已准备好处理并立即重启任务以进行下一次循环迭代。
在输出侧,NI-Motion驱动程序使用packets从电机的嵌入式控制器发送和接收数据。 API已针对高性能进行了调整,但NI确实有white paper描述最佳实践。也许其中一些适用于您的情况。