# #6 增量式encoder latch z # situation #6 增量式encoder 如果position mode開啟latch z value功能,會把該值寫入control loop導致第一次latch時有很大的震動 > 1.51020開QEP_FPGA功能,剛開機時第一圈,觸碰到Z相,6064位置會自動校正為0 2.51020不開QEP_FPGA功能,Latch無法抓取Z相當下位置 >目前希望可以以第二點處理,第一點是有困難的。 Latch功能有分IO跟Ethercat方式(Z相),去存取位置 >但我們Ethercat Latch尚有問題 Ethercat Latch問題 那時與ITRI合作時測試發現 第一點6064位置會自動校正為0,這位導致PosError瞬間變大,導致位置控制會不穩 > Ethercat Latch 是 Touch Probe功能 第一點 是因為他會去把Z相當作位置0 所以就等於把Encoder轉到0就等於Z相 結論,因為method 1 (402正規設定)功能未實現齊全,控制還不穩定。~~不得已採用method 2。~~ method 2 by touch probe也是正規的,新增在2016 ver, ch12,任何mode 底下都會生效。 目前主要走method2 所以看 0x60B8, 0x60B9, 0x60BA, 0x60D0 ## 設定 > 60B8設定21 60B9,1=已啟用等待信號,2=Latch到位置存在60BA 21--> 0b10101 --> Trigger with zero impulse signal or position encoder, Enable sampling at positive edge of touch probe 1 [ERN1387](https://datasheetspdf.com/pdf-file/828328/HEIDENHAIN/ERN1387/1) ## 分析 目前任何機器,都沒有用 homing mode。 什麼模式下發生:位置模式。 速度模式 不會有問題。(所以不會影響到 control loop) 目前都先在速度模式下轉一圈,然後才切掉位置模式。 ## 大方向解法 * 不符合402 * 速度模式+QEP_FPGA_EN * 符合402 * homing mode * 速度模式+touch probe * 位置模式+touch probe CiA402 除了homing mode,本來就沒有 可以直接把6064 改變的東西,(reset 到0)。所以任何開QEP功能的方法都是不對的。 再來因為客戶不希望剛開機要做homing,可能是習慣以前手動車床的模式了。(一般情況是應該要homing才對)。所以一定是touch probe。 # code 略看 ## metod1 51020 QEP_FPGA `FPGA_QEP_EN` --> control.c `Encoder.bFPGAQepEn` 懷疑 \_IncEncoder_28377.c ## method2 `sEntryDesc0x60B8`, register data `ProbeFun` `ProbeStatus` 全部都在 objdef.c 的 `ProbeFunRun` # 問題點 我code 看起來,是它會在馬達機械角轉一圈時存值。這很可能是有bug,因為應該要看z 訊號才對。 ```c=3219 else { if (-(Get_MechAngle() - qAngelLast) > _IQ(0.9375)) { if(ProbeFun.bit.TriggerType1 == 1 ||(ProbeFun.bit.TriggerType1 == 0 && ProbeStatus.bit.PValueStor1 == 0)) { ProbeStatus.bit.NValueStor1 = ProbeStatus.bit.PValueStor1 = 1; dwPos = Get_Position(); if ((dwPos & ((1L<<DBVALUE(ENCODER_FINERESOLUTION).wData) -1)) < (1L<<(DBVALUE(ENCODER_FINERESOLUTION).wData -1))) dwPos = dwPos & (0x7FFFFFFF<<DBVALUE(ENCODER_FINERESOLUTION).wData); else dwPos = (dwPos +(1L<<DBVALUE(ENCODER_FINERESOLUTION).wData)) & (0x7FFFFFFF<<DBVALUE(ENCODER_FINERESOLUTION).wData); if (DBVALUE(ENCODER_INVERSION_ACTUAL_VALUE).wData & INVERSION_POSITION_MASK) u_lTP1NegPostion = u_lTP1PosPostion = - dwPos; else u_lTP1NegPostion = u_lTP1PosPostion = dwPos; } } else if ((Get_MechAngle() - qAngelLast) > _IQ(0.9375)) { if(ProbeFun.bit.TriggerType1 == 1 ||(ProbeFun.bit.TriggerType1 == 0 && ProbeStatus.bit.NValueStor1 == 0)) { //ProbeStatus.bit.NValueStor1 = 1; ProbeStatus.bit.NValueStor1 = ProbeStatus.bit.PValueStor1 = 1; dwPos = Get_Position(); if ((dwPos & ((1L<<DBVALUE(ENCODER_FINERESOLUTION).wData) -1)) < (1L<<(DBVALUE(ENCODER_FINERESOLUTION).wData -1))) dwPos = dwPos & (0x7FFFFFFF<<DBVALUE(ENCODER_FINERESOLUTION).wData); else dwPos = (dwPos +(1L<<DBVALUE(ENCODER_FINERESOLUTION).wData)) & (0x7FFFFFFF<<DBVALUE(ENCODER_FINERESOLUTION).wData); if (DBVALUE(ENCODER_INVERSION_ACTUAL_VALUE).wData & INVERSION_POSITION_MASK) u_lTP1PosPostion = u_lTP1NegPostion = - dwPos; else u_lTP1PosPostion = u_lTP1NegPostion = dwPos; } } } qAngelLast = Get_MechAngle(); bProbe1 = GetProbe1(); ProbeStatus.bit.ProbeEn1 = 1; } ``` mechangle 好像是以開機時的角度為準。我認為是該分兩層,開機時的underline 角度(相對角度)(給控制用的),和轉給user 用的角度。如果是abs encoder,兩者沒差別。如果是incremental, user 角度在第一次碰到z signal 才知道。 而且這個做法,值會準嗎?我是很懷疑。因為這個function 是 non-realtime的,所以偵測到的時候,時間會有差。只有abs encoder的會準。 encoder 的 Read_Encoder 和 Get_Position 是分開的。所以 Read_Encoder 會在 ISR 週期的read。但如果一次 non-realtime 經過>=2次的 ISR。encoder value 就會差很多了。 --- 但是應該至少是有存值的,所以0xb9的bit1 ,Touch probe 1 positive edge position stored,應該要=1。 ProbeStatus.bit.NValueStor1 = ProbeStatus.bit.PValueStor1 = 1; ## 解法 ~~這應該是 encoder 那裡就要有一個 latched z signal,因為只有read_encoder 有控制時間是在timer ISR裡。~~ 但是後來說,polling 的精度已經夠了。 也還是要提供一個判斷z signal 的方法,現有encoder提供的介面都不行很好的偵測。 一定是要新增 encoder 的介面,有兩個方法,function(method) or structure variable,那一個比較好?很明顯的是,用 vairable 會簡單很多。因為目前implement的inherit,是用很麻煩的address map 做的,而不是contrainer of。 polling to find edge 是在 touch probe function 裡做的。所以就是要有一個Z訊號產生前後,會變化的變數。(除非用observer mode) --> dwLoopCount 放到 parent? 但 問題就是很難產生一個 z相前後有變的變數。唯一可以看到的還是z signal 本身。而且直接使用 z signal,這也才符合 touch probe function 自己detect value chnage的特性。 如果touch probe自己讀值,且toch probe function 不在non-real time裡了話,很有可能 reading 不夠即時導致沒讀到值。 但如果在 是io 點自己去判斷rising edge,並記錄值。touch probe 就不能直接去修改這些記錄值,因為它是public to everyone,一修改就會影響到其它部分。 另一個角度是,touch probe 不知道要不要覆蓋掉目前的值,到底如何處理值,應該只有應用端的touch probe 知道。 我覺得toch probe 自己讀是對的,只是讀的不夠快了話是錯的。 或是說假設能分成兩種style,一種是自己讀(上面),第二種是源頭自己latch的。第二種的問題是,它無法確定目前的值是剛latch到的,還是上次就殘留的,因為它並沒有補捉edge。 讓源頭(producer)來補捉edge了話,producer 就要跟據consumer的數量自動生成對應數量的 edge catched 變數。consumer 拿到後就 reset edge catched。程式變得很複雜且縮限consumer 如何使用值。 # 衍生問題-無法enable 以下這個function 就是在while loop 裡的 routine 而已。 ```c=1401 void ControlWordRun() { static WORD wFlag = 1; WORD wControlWordTemp = 0; if (u_bControlWordFlag) { if (!ControlWord.bit.SwitchON && ControlWord.bit.EnableVoltage && ControlWord.bit.QuickStop) // x110 { // State Transition 2,6,8: => READY TO SWITCH ON // Event: Shutdown command recerived from host CANopenState_ReadyToSwitchOn(); if (Statusword.bit.Operationenabled) u_bShutDown = TRUE; } else if (ControlWord.bit.SwitchON && ControlWord.bit.EnableVoltage && ControlWord.bit.QuickStop && !ControlWord.bit.EnableOperation) // 0111 { // State Transition 3: READY TO SWITCH ON => SWITCHED ON // Event: Switch On command recerived from host // Action: The power section is swtiched on if it is not already switched on if (Statusword.bit.Readytoswitchon && !Statusword.bit.Switchedon) // not already switched on CANopenState_SwitchedOn(); // State Transition 5: OPERATION ENABLE => SWITCHED ON // Event: Disable Opeation command recerived from host // Action: The drive will be disabled else if (Statusword.bit.Operationenabled) // already Opeation enabled { CANopenState_SwitchedOn(); u_bDisableOper = TRUE; } } else if (ControlWord.bit.SwitchON && ControlWord.bit.EnableVoltage && ControlWord.bit.QuickStop && ControlWord.bit.EnableOperation) // this is most used. 1111 { // State Transition 3: READY TO SWITCH ON => SWITCHED ON // Event: Switch On command recerived from host // Action: The power section is swtiched on if it is not already switched on if (Statusword.bit.Readytoswitchon && !Statusword.bit.Switchedon) // not already switched on // status word xx01 CANopenState_SwitchedOn(); // State Transition 4: SWITCHED ON => OPERATION ENABLE // Event: Enable Opeation command recerived from host // Action: The drive will be enabled else if (Statusword.bit.Readytoswitchon && Statusword.bit.Switchedon && !Statusword.bit.Operationenabled) // not already Opeation enabled // status word x011 CANopenState_OperationEnable(); // State Transition 16: QUICK STOP ACTIVE => OPERATION ENABLE // Event: Enable Opeation command recerived from host. // Action: The drive will be enabled else if (Statusword.bit.Operationenabled && !Statusword.bit.Quickstop) // Quick Stop Active { if (GetClearPermit() && u_bQuickStopFlag) { u_bQuickStopFlag = FALSE; CANopenState_OperationEnable(); ClearResponseStatus(); // from stop to normal status } } } else if (!ControlWord.bit.EnableVoltage) { // State Transition 7,9,10,12: => SWITCH ON DISABLED // Event: Disable voltage command recerived from host CANopenState_SwitchOnDisabled(); if (Statusword.bit.Operationenabled) u_bDisableVolt = TRUE; } else if (ControlWord.bit.EnableVoltage && !ControlWord.bit.QuickStop) { // State Transition 7,10: => SWITCH ON DISABLED // Event: Quick stop command recerived from host if (!Statusword.bit.Operationenabled) // not already Opeation enabled CANopenState_SwitchOnDisabled(); // State Transition 11: => QUICK STOP // Event: Quick stop command recerived from host else if (Statusword.bit.Operationenabled) { // already Opeation enabled CANopenState_QuickStopActive(); // CANopenState_SwitchOnDisabled(); u_bQuickStopFlag = TRUE; } } if (ControlWord.bit.FaultReset && wFlag) // avoid mapping in ISR run, clear fault will active long time task(encoder error),make ISR time full then reset CPU { u_bCANClrError = TRUE; // State Transition 15: FAULT => READY TO SWITCH ON // Event: Fault reset command recerived from host CANopenState_SwitchOnDisabled(); wFlag = 0; } else if (!ControlWord.bit.FaultReset) wFlag = 1; if (DBVALUE(POWER_FUNTIONBIT).dwData & CAN_ENABLE) { // if state is in operation enabled, enable the motor if (ControlWord.bit.EnableOperation && ControlWord.bit.QuickStop) // (Statusword.bit.Operationenabled && Statusword.bit.Quickstop) || && wLastEnableFlag == 0 { if (GetDriveStatus()) u_wCANEnable = NULL_CTRLWORD; else u_wCANEnable = ENABLE_CTRLWORD; } else if (ControlWord.bit.EnableOperation == 0) { if (u_bDisableVolt) ExecuteFaultResponse(OFF2); else if (u_bQuickStopFlag) u_wCANEnable = NULL_CTRLWORD; else if (u_bShutDown) u_wCANEnable = NULL_CTRLWORD; else if (u_bDisableOper) u_wCANEnable = NULL_CTRLWORD; else if (GetDriveStatus()) u_wCANEnable = DISABLE_CTRLWORD; else u_wCANEnable = NULL_CTRLWORD; } if (GetControlSource() != PC_COMMAND) { if (u_bQuickStopFlag) { if (u_wQuickStopCode == SLOWBYQUICKRAMP || u_wQuickStopCode == SLOWBYCURRENTLMT || u_wQuickStopCode == SLOWBYVOLTLMT) ExecuteFaultResponse(OFF1); else if (u_wQuickStopCode == DISABLE) ExecuteFaultResponse(OFF2); else if (u_wQuickStopCode == SLOWBYSLOWRAMP) ExecuteFaultResponse(OFF3); else if (u_wQuickStopCode == SLOWBYQUICKRAMP_INSTOP || u_wQuickStopCode == SLOWBYCURRENTLMT_INSTOP || u_wQuickStopCode == SLOWBYVOLTLMT_INSTOP) ExecuteFaultResponse(STOP1); else if (u_wQuickStopCode == SLOWBYSLOWRAMP_INSTOP) ExecuteFaultResponse(STOP2); else ExecuteFaultResponse(OFF2); } if (u_bShutDown) { if (u_wShutDownCode == DISABLE_DRIVE) ExecuteFaultResponse(OFF2); else if (u_wShutDownCode == SLOWDOWN_SLOWRAMP) ExecuteFaultResponse(OFF3); else ExecuteFaultResponse(OFF2); } if (u_bDisableOper) { if (u_wDisableCode == DISABLE_DRIVE) ExecuteFaultResponse(OFF2); else if (u_wDisableCode == SLOWDOWN_SLOWRAMP) ExecuteFaultResponse(OFF3); else ExecuteFaultResponse(OFF2); } } Statusword.bit.Remote = 1; } else Statusword.bit.Remote = 0; wControlWordTemp = ControlWord.all; if (u_wOpModeAct == HOMING_MODE) // Postion Homing mode { if ((ControlWord.bit.OperationModeSpecific & 0x1) == 1) // homing operation start { wControlWordTemp |= 0x0400; // home active wControlWordTemp &= 0xFFEF; // clear active flag // } // else wControlWordTemp &= 0xFBFF; // } SetCANPosControlWord(wControlWordTemp); SetHomeStart(ControlWord.bit.HomeRun); SetNoActionEn(ControlWord.bit.Manufacturer2NoAction); u_bControlWordFlag = FALSE; } StatusWordDeal(); StatuswordBuf.all = Statusword.all; // StatuswordBuf.all = 0x1037; // for #ifndef D_DSP2812 u_wControlWord = ControlWord.all; #endif } ``` 另 ```c=1328 void CANopenState_OperationEnable() { // State Transition 4: SWITCHED ON => OPERATION ENABLE // Event: Enable operation command recerived from host // Action: The drive function is enabled Statusword.bit.Readytoswitchon = 1; Statusword.bit.Switchedon = 1; Statusword.bit.Operationenabled = 1; Statusword.bit.Fault = 0; Statusword.bit.Quickstop = 1; Statusword.bit.Switchondisabled = 0; } ``` # log 10/12 本來想要直接用probe1,但發現 `u_awInputMap` 的 `I_PROBE1` 好像沒有引出來? 其實是 `IO.c` 裡有 `SetInputMap`,然後再用這個function 和 `SetInput1Map` (index `57103` ) 去把 DI1 設定為某種功能 e.g. 我們想要的 `I_PROBE1`。 取IO 值,就是先從這個map 去讀值(pin_number),然後再 digital_read(pin_number)。 10/12 sincos, 絕對值 encoder 是fpga 來處理的,這件事已經確定。要換fpga code 也是確定的。 問題在於 sine ver 的 sandal 和 abs ver 的sandal 線路也可能不同。 > 之前他們有跟我說,SINCOS跟Endat設計的線路有所差異(大sandal, 小sandal) 這表示燒sincos 的fpga code 可能也沒用。 10/16 越走越遠,硬體搞不完。 10/19 接收不到z訊號的問題描述 encoder z訊號的路徑是 encoder --> 驅動器電路 --> FPGA --> DSP。 在 10/18 量不到 ERN1387 encoder 的Z訊號,但能量到A,B之後。更換了西門子的 encoder,馬上就量到Z訊號。 但今天DSP還是抓不到Z訊號。 首先,轉動軸,在 u20, 74LVC2G14 比較器的pin6(output) 和 pin1 (input) 都沒看到訊號。 接著,將 pin1 人工拉到 ground,DSP裡就收到z 訊號了。 所以,我們認為是在encoder port 到 u20之間的電路有問題。 --- 手動給z訊號的結果,目前是正常的,發現z signal 在FPGA裡 好像是latched的,所以只有positive edge 有用。而且沒有negative edge 的FPGA了話,就永遠做不了。 10/20 跟周勇要FPGA 的reset 方法。 10/23 因為今天測試時,只有一次剛好讀到touch probe 的值。 很可能之前說到nonrealtime 問題發生了,所以將整個function 移到 ISR 裡。 10/24 無法enable 問題。 10/25 修正touch probe 2。28377d 最小 memory 為16bit。 10/26 mode switch 不小心改到,產生bug 要考慮 unittest 的問題了。