力反饋 OpenHaptics ProgGuide-HDAPI

本節目的:學習HDAPI中的常用操作
原文來源於 OpenHaptics ProgGuide 中第5節,翻譯理解水平有限,如有錯誤望指正。

HDAPI 程序典型流程

在這裏插入圖片描述
採用 PHANTOM mini
在這裏插入圖片描述

1: 觸控設備操作

對設備的操作包含獲取和設置狀態,且只能在伺服循環中通過調度程序回調的方式使用。
1. 初始化
首先對設備初始化
HHD hHD = hdInitDevice(HD_DEFAULT_DEVICE);
然後啓動力
hdEnable(HD_FORCE_OUTPUT)
當調度程序啓動時纔會真正啓動力
2. 設置當前設備
hdMakeCurrentDevice(hHD);
當多個設備使用時需要輪流設置不同的設備作爲當前設備,使其可以被操作。如果只有一個設備則無需調用
3. 設備能力(選項)
有些設備的特性是可以開關的,hdEnable() 或者 hdDisable()

2: 觸控幀

  1. 觸控幀定義了一個範圍(hdBeginFrame()和hdEndFrame()爲界),在該範圍內設備狀態保證一致。在幀的開始,更新並存儲設備狀態以在該幀中使用,幀中的所有狀態查詢使用這些數據。幀結束時,將諸如力的新狀態寫出到設備。從上一幀獲取最後信息,例如位置信息。
  2. 大多數觸控操作應該在觸控幀內運行。在幀內調用操作可確保所使用數據的一致性,因爲狀態在幀內保持不變。獲取幀外的狀態通常會返回最後一幀的狀態。在幀外設置狀態通常會導致錯誤。

3: 調度程序操作

調度程序允許在調度程序線程中運行調用。由於設備需要以非常高的速率(通常1000Hz)發送強制更新,因此調度程序管理高優先級線程。如果開發人員需要進行查詢或更改狀態,他應該在此循環中執行此操作。否則,由於狀態不斷變化,應用程序查詢或設置狀態通常是不安全的。例如,用戶不應該查詢以調度程序執行速率更改的數據,變量不應該在兩個線程之間共享。
用戶應該僅通過使用調度程序回調來訪問由觸控線程修改的變量。
回調函數通常是下面這樣
HDCallbackCode HDCALLBACK DeviceStateCallback(void *pUserData);
返回下面兩個值之一
HD_CALLBACK_DONE or HD_CALLBACK_CONTINUE
根據返回值決定回調函數是否繼續運行。
可以將回調設置爲運行一次或者多次,具體取決於回調的返回值。如果返回值請求繼續回調,則重新調度並在下一個回調時鐘週期再次運行。否則將從調度程序中刪除並被認爲是完整的,並在同步操作的情況下將控制返回到調用線程。
例子

// 客戶端數據聲明
struct DeviceDisplayState{
	HDdouble position[3];
	HDdouble force[3];
}
// 客戶端數據的使用
HDCallbackCode HDCALLBACK DeviceStateCallback(void *pUserData){
	DeviceDisplayState *pDisplayState = static_cast<DeviceDisplayState *>(pUserData);
	hdGetDoublev(HD_CURRENT_POSITION, pDisplayState->position);
	hdGetDoublev(HD_CURRENT_FORCE, pDisplayState->force);
	// 只調用一次
	return HD_CALLBACK_DONE;
}

同步/異步調用
同步調用:調用完成後返回,應用程序線程等待同步調用完成後繼續
主要用來獲取應用程序調度程序狀態的快照,例如應用程序查詢位置或者調度程序正在更改的變量或者狀態,應該使用同步調用來執行(也就是說查詢的值如果在更改,則線程等待更改完成後再返回)
例如

// 獲取當前位置
DeviceDisplayState state; 
hdScheduleSynchronous(DeviceStateCallback, &state, HD_MIN_SCHEDULER_PRIORITY);

異步調用:立即返回(可以得到連續的值)
通常管理觸覺循環,持久化表示觸覺效果,每次迭代期間回調將效果應用於設備
例如

HDCallbackCode HDCALLBACK CoulombCallback(void *data)
{
	HHD hHD = hdGetCurrentDevice();
	hdBeginFrame(hHD);
	HDdouble pos[3];
	// 循環末端得到速度
	hdGetDoublev(HD_CURRENT_POSITION,pos);
	HDdouble force[3];
	// 根據位置計算力
	forceField(pos, force);
	// 將力寫入設備
	hdSetDoublev(HD_CURRENT_FORCE, force);
	// 清除力
	hdEndFrame(hHD);
	// 每幀執行
	return HD_CALLBACK_CONTINUE;
}
hdScheduleAsynchronous(AForceSettingCallback, (void*)0, HD_DEFAULT_SCHEDULER_PRIORITY);

異步回調函數返回一個句柄,可以在將來用於對回調函數執行操作(取消調度回調,強行終止等)
例如

HDSchedulerHandle calibrationHandle = hdScheduleAsynchronous(aCallback, (void*)0, HD_MIN_SCHEDULER_PRIORITY);
hdStopScheduler();
hdUnschedule(calibrationHandle);

回調函數的優先級決定了在調度中的運行順序。優先級高的先運行,相同的隨機運行。
無論設備數量多少,都只運行一個調度程序線程。多個設備共享一個調度程序線程

4: 狀態

1. 獲取狀態
可以通過使用hdGet系列函數檢索設備狀態和其它信息,例如hdGetDoublev(), hdGetIntegerv(),這些函數都需要有效的參數以及返回地址或者數組(保證數量一樣大)
參數類型不是通用的,如果類型無效則返回HD_INVALID_INPUT_TYPE錯誤。
例如 HD_DEVICE_MODEL_TYPE 需要一個字符串而且只能被 hdGetString() 調用
CURRENT / LAST 狀態指的是當前正在查詢的幀或者上一幀的狀態。如果在幀外面調用這兩個狀態則視爲在前一幀內進行。(此時CURRENT返回上一幀,LAST返回上上幀)
例子

HDint buttonState;
HDstring vendor;
hduVector3Dd position;
HDfloat velocity[3];
HDdouble transform[16];

hdGetIntegerv(HD_CURRENT_BUTTONS,&buttonState);
hdGetString(HD_DEVICE_VENDOR,vendor);
hdGetDoublev(HD_CURRENT_POSITION,position);
hdGetFloatv(HD_CURRENT_VELOCITY,velocity);
hdGetDoublev(HD_LAST_ENDPOINT_TRANSFORM,transform);

通常應該在調度程序線程內,觸覺幀中調用獲取狀態

2. 設置狀態
設置狀態應該在觸控幀內完成,不支持笛卡爾力和 torques with motor DAC的混合
例如在HD_CURRENT_FORCE 和 HD_CURRENT_MOTOR_DAC_VALUES上調用 hdSetDoublev() 將導致錯誤
例如

HDdouble force[3] = {0.5, 0.0, 1.0};
hdSetDoublev(HD_CURRENT_FORCE,force);
HDfloat rampRate = .5;
hdSetFloatv(HD_FORCE_RAMPING_RATE,&rampRate);

注意

  • 觸覺幀結束前力不會發送到設備。
  • 兩次設置相同的狀態將會用第二個替換第一個
  • 如果累積力,可以使用一個私有變量或者重複使用 hdGet()/hdSet()

3. 狀態同步
調度程序在線程之間提供狀態同步功能。
考慮一個需要以伺服循環速率更新的狀態,從另一個線程(圖形線程)中訪問或者修改。
一個實例是動態模擬位置的更新,根據伺服線程中運動的一些運動方程。
位置頻繁發生變化,圖形重繪函數隨機地獲取這些位置並繪製。
圖形重繪期間,狀態需要保持一致,因爲圖形線程速率(60Hz)比伺服線程速率(1000Hz)低得多。
下面的例子中,位置可能會在兩次查詢時發生變化

HDCallbackCode positionUpdateCallback(void *pUserData)
{
	hduVector3Dd *position = (hduVector3Dd *)pUserData;
	hdGetDoublev(HD_CURRENT_POSITION,*position);
	return HD_CALLBACK_CONTINUE;
}
void func()
{
	hduVector3Dd position;
	hdScheduleAsynchronous(positionUpdateCallback,
	position,
	HD_DEFAULT_SCHEDULER_PRIORITY);
	hduVector3Dd pos1 = position;
	hduVector3Dd pos2 = position;
	// 大概率下是不相等的
	assert(pos1 == pos2);
}

5: 校準接口

校準允許設備保持物理位置的準確概念。例如校準之前,設備可能認爲機械臂位於工作空間中心,但實際上機械臂已經發生偏離。
1. 校準類型
編碼器硬件校準
Inkwell 校準
自動校準
2. 查詢校準
由於每種設備校準形式不同,可以通過 HD_CALIBRATION_STYLE 查詢所支持的校準類型。
對於前兩種校準,除了提示用戶將設備放入合適的位置外,還應該調用 hdUpdateCalibration() 一次。
自動校準則通過 HD_CALIBRATION_NEEDS_UPDATE 返回值進行更新,使用 hdCheckCalibration() 定期檢查並通過 hdUpdateCalibration() 進行更新。
注意 也可以通過 PHANToM Test 校準設備。
3. 校準時機
由於校準可能導致設備位置跳躍,因此可以通過一些力檢查或者禁用力來執行校準。
否則校準可能導致設備進入力感很大的位置(例如物體內部)
4. 調用校準
首先,用戶應該從支持的樣式列表中選擇校準樣式,因爲某些設備可能支持多種類型的校準。
例如

HDint supportedCalibrationStyles;
hdGetIntegerv(HD_CALIBRATION_STYLE, &supportedCalibrationStyles);
if (supportedCalibrationStyles & HD_CALIBRATION_ENCODER_RESET)
{
	calibrationStyleSupported = true;
}
if (supportedCalibrationStyles & HD_CALIBRATION_INKWELL)
{
	calibrationStyleInkwellSupported = true;
}
if (supportedCalibrationStyles & HD_CALIBRATION_AUTO)
{
	calibrationStyleAutoSupported = true;
}
// 接着定義回調函數用來檢查校準
HDCallbackCode CalibrationStatusCallback (void *pUserData)
{
	HDenum *pStatus = (HDenum *) pUserData;
	hdBeginFrame(hdGetCurrentDevice());
	*pStatus = hdCheckCalibration();
	hdEndFrame(hdGetCurrentDevice());
	return HD_CALLBACK_DONE;
}
// 例子:回調函數更新校準
HDCallbackCode UpdateCalibrationCallback (void *pUserData)
{
	HDenum *calibrationStyle = (HDint *) pUserData;
	if (hdCheckCalibration() == HD_CALIBRATION_NEEDS_UPDATE)
	{
		hdUpdateCalibration(*calibrationStyle);
	}
	return HD_CALLBACK_DONE;
}

6: 力/扭矩控制

PHANTOM 笛卡爾空間
所有 PHANToM 設備工作區間都在笛卡爾座標系中,默認時
X軸指向 PHANToM 的右側
Y軸指向 PHANToM 上方
Z軸指向 “out”, 朝向用戶
在這裏插入圖片描述
PHANTOM 關節空間
關節1,關節2和關節3是有助於PHANTOM的X,Y和Z力的基礎關節。
也就是說這三個關節決定6自由度中沿x, y, z三個直角座標軸方向的移動自由度
在這裏插入圖片描述
關節4,關節5和關節6是分別對應繞x, y, z這三個座標軸的轉動自由度。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
迪卡爾基本的力由hdSetFloatv()或者hdSetDoublev()設定,並將第一個參數設爲HD_CURRENT_FORCE

HDfloat baseForce[3];
baseForce[0] = force_x;
baseForce[1] = force_y;
baseForce[2] = force_z;
hdSetFloatv(HD_CURRENT_FORCE, baseForce);

下面說明了在6DOF設備上左右關節扭矩值的典型用法

HDdouble baseTorque[3] = {100, 250, 200}; //Base Torque in mNm
hdSetDoublev(HD_CURRENT_JOINT_TORQUE, baseTorque);
HDdouble gimbalTorque[3] = {30, 65, 0.0}; //Gimbal Torque in mNm
hdSetDoublev(HD_CURRENT_GIMBAL_TORQUE, gimbalTorque);

7: Error Reporting and Handling

// 未完待續

/* Check if an error occurred while attempting to render the force */
if (HD_DEVICE_ERROR(error = hdGetError()))
{
	if (hduIsForceError(&error))
	{
		bRenderForce = FALSE;
	}
	else if (hduIsSchedulerError(&error))
	{
		return HD_CALLBACK_DONE;
	}
}

8: Cleanup

在應用程序退出之前,應該停止調度程序並終止所有調度程序的操作。回調函數可以通過 hdUnschedule() 終止,也可以通過知道返回 HD_CALLBACK_DONE 的回調本身終止。最後禁用設備

hdStopScheduler();
hdUnschedule(scheduleCallbackHandle);
hdDisableDevice(hdGetCurrentDevice());
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章