I am reading force sensor data using NI DAQ card, I am able to read the data perfectly but I am confuse how to set the desired sampling rate.Right now when I am checking the sampling rate it is giving me random value like some time it is 500hz and some time 50000hz.
here is the code that I am using for reading force sensor data and calculating sampling rate.
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);
}
};
Edit: when I am changing the number of samples, the throughput of my application reduce drastically,in ForceSensor.h file if I change the number of sample in below function from 2 to 100, throughput of application decrease to 500Hz from 10kHz.Please guide me related to this.
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;
}
I am also using NI Motion card to control a motor, below is the main.h file in which I have add the NI Motion code. When I add the NI Motion code in main.h file throughput of the application reduces to 200Hz whatever the value I have used for number of samples in force sensor. Is there any way to increase the throughput of application when using both NI Motion for controlling motor and DAQ for reading force sensor at same time?
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());
}
}
};