using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; using System.Threading; using ec = ComiLib.EtherCAT.SafeNativeMethods; namespace EtherCAT_Examples_CSharp { /// <summary> /// 시스템 초기화 /// </summary> public class CookBook_Initialize { // SystemInit() 함수를 수행하여 시스템을 초기화 합니다. // SystemInit(), Init() 함수는 Task 등을 사용한 비동기처리 예시이며, 중요한 것은 함수의 실행 순서입니다. #if true // .net 4.5 (async / await) public async void SystemInit() { IsStop = false; bool isSuccess = await Init(); AddLog(string.Format("System Initialize {0}", isSuccess ? "success" : "false")); } public async Task<bool> Init() { // Master Device를 초기화합니다. if (!await Task.Run(() => InitMasterDevice())) return false; // Config된 정보와 Scan된 정보를 비교합니다. (설정값과 실제값 비교) if (!await Task.Run(() => CheckChannel())) return false; var taskList = new List<Task<bool>>(); // 각 축의 알람을 클리어하고, 모터 구동 가능 상태로 변경합니다. axisList.ToList().ForEach(axis => taskList.Add(Task.Run(() => AxisServoOn(axis)))); var resultList = (await Task.WhenAll(taskList)).ToList(); if (resultList.Any(x => !x)) return false; // 각 축의 원점 복귀를 수행하여 원점을 설정합니다. taskList.Clear(); axisList.ToList().ForEach(axis => taskList.Add(Task.Run(() => AxisHomeReturn(axis)))); resultList = (await Task.WhenAll(taskList)).ToList(); return resultList.All(x => x); } #else // .net 4.0 public void SystemInit() { Task<bool>.Factory.StartNew(() => Init()).ContinueWith(x => AddLog(string.Format("System Initialize {0}", x.Result ? "success" : "false"))); } public bool Init() { // Master Device를 초기화합니다. if(!Task.Factory.StartNew(() => InitMasterDevice()).Result) return false; // Config된 정보와 Scan된 정보를 비교합니다. (설정값과 실제값 비교) if (!Task.Factory.StartNew(() => CheckChannel()).Result) return false; var taskList = new List<Task<bool>>(); // 각 축의 알람을 클리어하고, 모터 구동 가능 상태로 변경합니다. axisList.ToList().ForEach(axis => taskList.Add(Task.Factory.StartNew(() => AxisServoOn(axis)))); Task.Factory.ContinueWhenAll(taskList.ToArray(), r => { }); //Task.WaitAll(taskList.ToArray()); if (taskList.Exists(x => !x.Result)) return false; // 각 축의 원점 복귀를 수행하여 원점을 설정합니다. taskList.Clear(); axisList.ToList().ForEach(axis => taskList.Add(Task.Factory.StartNew(() => AxisHomeReturn(axis)))); Task.Factory.ContinueWhenAll(taskList.ToArray(), r => { }); return (!taskList.Exists(x => !x.Result)); } #endif public bool IsStop { get; set; } int netID = 0; uint slaveCount = 0; int errorCode = 0; byte[] axisList = new byte[32]; List<string> errorList = new List<string>(); CancellationTokenSource cts; #region AddLog private void AddLog(int errorCode) { if (errorCode == 0) return; Debug.WriteLine(ec.ecUtl_GetErrorString(errorCode)); } private void AddLog(string errorString) { Debug.WriteLine(errorString); } #endregion void Stop() { IsStop = true; } /// <summary> /// Master Device를 초기화합니다. /// </summary> /// <returns></returns> private bool InitMasterDevice() { //마스터 디바이스를 로드합니다. if (!DeviceLoad()) return false; // 설정 된 슬레이브 개수와 연결 된 슬레이브 개수가 동일한지 확인합니다. if(!CompareSlaveCount()) return false; // SW Version(FW, WDM, SDK)이 서로 호환되는 버전인지 확인합니다. if (!GetVersionCompResult()) { AddLog("Version compare fail"); return false; } AddLog("Version compare compt"); // 슬레이브의 Input / Output이 반대로 연결된 모듈이 있는지 확인합니다. if (!CheckReveseConnection()) { AddLog("역삽입된 모듈이 있습니다."); return false; } // Network의 alStatus를 OP로 설정합니다. if (!SetAlStateToOP()) return false; AddLog("MasterDevice Init Compt"); return true; } private bool DeviceLoad() { try { // Device를 초기화합니다. if (!ec.ecGn_LoadDevice(ref errorCode)) { AddLog(errorCode); switch (errorCode) { case 5: AddLog("Mater Device에 12V 전원이 입력되었는지 확인 바랍니다."); break; case 8: AddLog("Mater Device가 부팅되지 않았습니다. Windows의 FastBoot(빠른시작켜기)가 활성화 되어 있는 경우 비활성화 하세요"); break; } return false; } return true; } catch (BadImageFormatException) { AddLog("ecGn_LoadDevice Failed : DLL 버전(x86/x64)이 OS와 맞지 않습니다."); return false; } catch (DllNotFoundException) { AddLog("ecGn_LoadDevice Failed : DLL을 찾을 수 없습니다."); return false; } catch (Exception ex) { AddLog(string.Format("ecGn_LoadDevice Failed : Exception - {0}", ex.ToString())); return false; } } private bool CompareSlaveCount() { // Config된 slave 수를 확인합니다. // Configuration 단계에서 설정된 슬레이브의 수로 현재 연결된 슬레이브 수와는 관련이 없습니다. // Config 상세 정보 https://winoar.com/dokuwiki/platform:ethercat:1_setup:10_config:20_configuration uint cfgCount = ec.ecNet_GetCfgSlaveCount(netID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // 현재 네트워크에 연결되어 있는 slave 수를 확인합니다. uint slaveCount = ec.ecNet_ScanSlaves(netID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // 설정된 모듈 수만큼 스캔되었는지 확인합니다. if (cfgCount != slaveCount) { AddLog("현재 스캔된 슬레이브의 모듈 수와 설정된 슬레이브의 모듈 수가 다릅니다."); AddLog(string.Format("ScanSlave : {0}. CfgCount : {1}", slaveCount, cfgCount)); AddLog("전원이 들어가지 않았거나 네트워크와 연결되지 않은 슬레이브 모듈이 있는지 확인하시기 바랍니다."); AddLog("마스터를 포함한 슬레이브 모듈의 개수가 ScanSlave와 같다면 Configuration 을 재실행 하시기 바랍니다."); return false; } return true; } private bool SetAlStateToOP() { // alStatus : https://winoar.com/dokuwiki/platform:ethercat:2_info:10_alstatus ec.ecNet_SetAlState(netID, ec.EEcAlState.OP, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } AddLog("Set AlState to OP"); // Network의 alStatus가 변경되는 경우, 모든 Slave의 alStatus도 Network의 alStatus로 변경됩니다. // slave의 alStatus가 OP가 되지 않는 경우, 해당 slave를 점검하시기 바랍니다. // https://winoar.com/dokuwiki/platform:ethercat:1_setup:10_config:ts:30_safeop_failed // 모든 모듈의 alStatus가 OP가 되었는지 확인합니다. // alStatus가 OP가 아닌 slave는 정상적으로 제어되지 않습니다. ec.EEcAlState alState = ec.EEcAlState.INITIAL; Stopwatch sw = new Stopwatch(); sw.Start(); bool isSuccess = false; while (sw.ElapsedMilliseconds < 10000 && !isSuccess) { if (IsStop) { AddLog("Stop"); return false; } isSuccess = true; for (int i = 0; i < slaveCount; i++) { alState = ec.ecSlv_GetAlState_A(netID, i, ref errorCode); if (alState != ec.EEcAlState.OP || errorCode != 0) { isSuccess = false; break; } } Thread.Sleep(200); } if (!isSuccess) { for (int i = 0; i < slaveCount; i++) { alState = ec.ecSlv_GetAlState_A(netID, i, ref errorCode); if (alState != ec.EEcAlState.OP || errorCode != 0) AddLog(string.Format("슬레이브 : {0} 의 AlState를 OP로 설정하는데 실패하였습니다.", i)); } return false; } return true; } /// <summary> /// Config된 정보와 Scan된 정보 비교 (설정값과 실제값 비교) /// </summary> /// <returns></returns> public bool CheckChannel() { AddLog("CheckChannel"); // Scan 된 Axis나 IO 채널 수를 비교하려면 아래 코드를 수행합니다. // 연결된 축리스트를 확인합니다. int axisCount = ec.ecmGn_GetAxisList(netID, axisList, 32, ref errorCode); Array.Resize(ref axisList, axisCount); if (errorCode != 0) { AddLog(errorCode); return false; } if (axisCount == 0) { // 서보를 사용하는 경우에만 에러처리 합니다. AddLog("연결된 축이 없습니다."); } // config 된 DI Channel 수를 확인합니다. int totalDiCount = ec.ecdiGetNumChannels(netID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // config 된 DO Channel 수를 확인합니다. int totalDoCount = ec.ecdoGetNumChannels(netID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } #if false // 미리 정의 된 축 수나 채널수가 있는 경우, 비교하여 에러처리 할 수 있다. int definedAxisCount = 8; // 실제 설치되어 있는 서보 드라이버의 수 if (definedAxisCount != axisCount) { // axisCount 가 다른 경우 물리적으로 연결되지 않은 축이 있는지 확인합니다. // 모두 연결되어 있다면 config를 다시 실행합니다. AddLog("연결되지 않은 축이 있거나 Config 정보가 다릅니다."); return false; } int definedDiCount = 32; // 실제 설치되어 있는 di channel 수 int definedDoCount = 32; // 실제 설치되어 있는 do channel 수 if (definedDiCount != totalDiCount) { AddLog("연결되지 않은 DI Slave가 있거나 Config 정보가 다릅니다."); return false; } if (definedDoCount != totalDoCount) { AddLog("연결되지 않은 DO Slave가 있거나 Config 정보가 다릅니다."); return false; } #endif return true; } /// <summary> /// FW - WDM - DLL 간 버전 호환성을 확인 /// </summary> private bool GetVersionCompResult() { // SW Version(FW, WDM, SDK)이 서로 호환되는 버전인지 확인합니다. // 호환되지 않는 버전이 설치되어 있는 경우 오동작 할 수 있습니다. ec.TEcFileVerInfo_SDK sdkInfo = new ec.TEcFileVerInfo_SDK(); ec.TEcFileVerInfo_WDM driverInfo = new ec.TEcFileVerInfo_WDM(); ec.TEcFileVerInfo_FW fwInfo = new ec.TEcFileVerInfo_FW(); bool isSuccess = ec.ecNet_GetVerInfo(netID, ref sdkInfo, ref driverInfo, ref fwInfo, ref errorCode); if (!isSuccess) { //FW - SDK 호환성 결과 switch (sdkInfo.nFwCompResult) { case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_LOWER: AddLog("Library version is higher than the Firmware"); return false; case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_HIGHER: AddLog("Library version is lower than the Firmware"); return false; case (int)ec.EEcVerCompatResult.ecVER_MATCH: AddLog("FW-SDK : OK"); break; default: AddLog("Firmware Version is invalid"); return false; } //FW-WDM 호환성 결과 switch (driverInfo.nFwCompResult) { case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_LOWER: AddLog("Driver version is higher than the Firmware"); return false; case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_HIGHER: AddLog("Driver version is lower than the Firmware"); return false; case (int)ec.EEcVerCompatResult.ecVER_MATCH: AddLog("FW-WDM : OK"); break; default: AddLog("Firmware Version is invalid"); return false; } //SDK-WDM switch (sdkInfo.nWdmCompResult) { case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_LOWER: AddLog("Driver version is lower than the Library"); return false; case (int)ec.EEcVerCompatResult.ecVER_MISMATCH_HIGHER: AddLog("Library version is lower than the Driver"); return false; case (int)ec.EEcVerCompatResult.ecVER_MATCH: AddLog("SDK-WDM : OK"); break; default: AddLog("Driver Version is invalid"); return false; } } return isSuccess; } /// <summary> /// 슬레이브의 Inport / Outport가 반대가 삽입된 모듈을 검색합니다. /// </summary> private bool CheckReveseConnection() { if (!CanCheckReverseConnection()) return false; // 역삽입된 슬레이브의 수를 확인합니다. int scanSlaveCount = 0; int reverseConnectionCount = ec.ecNet_CheckReverseConnections(netID, ref scanSlaveCount, ref errorCode); // 정의된 슬레이브 수가 있다면, 스캔된 슬레이브의 수와 비교합니다. //if (definedSlaveCount != scanSlaveCount) //{ // // 두 변수값의 차이는 Config 되었지만 현재 연결되어 있지 않은 슬레이브의 수입니다. // AddLog(string.Format("Disconnected Slave Count = {0}", definedSlaveCount - scanSlaveCount)); //} // reverseConnectionCount 값이 0인 경우, 역삽입된 모듈이 없는 경우이므로 정상입니다. if (reverseConnectionCount == 0) { AddLog(string.Format("ReverseConnection is nothing.")); return true; } else { AddLog(string.Format("ReverseConnectionCount = {0}", reverseConnectionCount)); bool isReverseConnected = false; // 역삽입된 모듈이 있는 경우, 슬레이브별로 역삽입 여부를 확인합니다. for (ushort i = 0; i < scanSlaveCount; i++) { isReverseConnected = ec.ecSlv_IsReverseConnected_A(netID, i, ref errorCode); if (isReverseConnected) AddLog(string.Format("Check SlaveIndex {0} : ReverseConnected", i)); } return false; } } /// <summary> /// 역삽입 검출 기능을 사용할 수 있는지 확인 /// DLL : 1.5.3.2 ( FW : 1.92 / WDM : 1.5.0.6) 이상의 버전에서 사용 가능 /// </summary> private bool CanCheckReverseConnection() { ec.TEcFileVerInfo_SDK sdkInfo = new ec.TEcFileVerInfo_SDK(); ec.TEcFileVerInfo_WDM driverInfo = new ec.TEcFileVerInfo_WDM(); ec.TEcFileVerInfo_FW fwInfo = new ec.TEcFileVerInfo_FW(); //FW / Driver / Library의 버전을 확인합니다. bool isSuccess = ec.ecNet_GetVerInfo(netID, ref sdkInfo, ref driverInfo, ref fwInfo, ref errorCode); string sdkVer = string.Format("{0}{1}{2}{3}", sdkInfo.CurVer.MajorVer, sdkInfo.CurVer.MinorVer, sdkInfo.CurVer.BuildNo, sdkInfo.CurVer.RevNo); int curVer = int.Parse(sdkVer); // Library의 버전이 1.5.3.2 이하인 경우 해당 기능을 사용할 수 없습니다. if (curVer < 1532) { AddLog("CheckReverseConnection : Not Supported version"); return false; } return true; } /// <summary> /// Alarm Clear & ServoOn /// </summary> /// <param name="axisID"></param> /// <returns></returns> private bool AxisServoOn(int axisID) { // 모터의 state를 확인합니다. int motState = ec.ecmSxSt_GetMotState(netID, axisID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } if (motState != 0) { // 마지막 명령에 의해 모터의 State가 Stop 이 아닌 경우, 일부 명령이 에러처리될 수 있으므로 // Stop 명령을 먼저 수행합니다. ec.ecmSxMot_Stop(netID, axisID, 1, 1, ref errorCode); } Stopwatch sw = new Stopwatch(); if (motState == -1010) // 서보에 알람이 발생한 경우 { ec.ecmSxCtl_ResetAlm(netID, axisID, ref errorCode); // 알람 클리어 명령 실행 if (errorCode != 0) { AddLog(errorCode); return false; } sw.Start(); while (sw.ElapsedMilliseconds < 1000 && ec.ecmSxSt_GetMotState(netID, axisID, ref errorCode) == -1010) Thread.Sleep(100); motState = ec.ecmSxSt_GetMotState(netID, axisID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // 1초후에도 알람이 남아있으면, 클리어되지 않는 알람으로 간주하고 실패로 처리합니다. // 1초는 예시이며, 드라이버에 따라 알람 처리 시간은 다를 수 있습니다. if (motState == -1010) { AddLog(string.Format("Axis {0} : 클리어되지 않는 알람이 있습니다.", axisID)); return false; } } // 드라이버가 Operation Enable 상태인지 확인합니다. var isOn = ec.ecmSxCtl_GetSvon(netID, axisID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } if (!isOn) { // Operation Enable 상태가 아닌 경우, Enable 명령(서보온)을 실행합니다. ec.ecmSxCtl_SetSvon(netID, axisID, 1, ref errorCode); sw.Restart(); while (sw.ElapsedMilliseconds < 2000 && !ec.ecmSxCtl_GetSvon(netID, axisID, ref errorCode)) Thread.Sleep(100); // 2초후까지 Enable이 아닌 경우, Enable을 할 수 없는 상황으로 간주하고 실패로 처리합니다. // 2초는 예시이며, 드라이버에 따라 처리 시간은 다를 수 있습니다. isOn = ec.ecmSxCtl_GetSvon(netID, axisID, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } if (!isOn) { AddLog(string.Format("Axis {0} : ServoOn fail.", axisID)); return false; } } AddLog(string.Format("Axis {0} : ServoOn Success.", axisID)); return true; } /// <summary> /// Home Return /// </summary> /// <param name="axisID"></param> /// <returns></returns> private bool AxisHomeReturn(int axisID) { // 홈복귀 Guide // https://winoar.com/dokuwiki/platform:ethercat:70_users_guide:10_homing:start int homeMode = 114; // 홈복귀 모드를 설정합니다. ec.ecmHomeCfg_SetMode(netID, axisID, homeMode, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // 홈복귀가 완료된 후 추가 이송 거리를 설정합니다. // Offset은 옵션이므로 사용하지 않을 경우 함수를 호출하지 않습니다. double homeOffset = 0; #if true ec.ecmHomeCfg_SetOffsetEx(netID, axisID, homeOffset, false, 1, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } #else // 홈복귀 완료 후, 추가 이송 없이 위치값만 변경하는 경우 다음 함수를 사용합니다. ec.ecmHomeCfg_SetOffset(netID, axisID, homeOffset, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } #endif // 홈복귀 속도를 설정합니다. // 홈복귀 속도는 단축 이송 속도와 다르므로, 별개로 설정해야 합니다. int speedMode = 2; // 가감속 방식을 설정합니다. 0:Constant 1:Trapzoidal 2:S-Curve double workSpeed = 100000; double accel = workSpeed * 10; double decel = workSpeed * 10; double specVel = workSpeed / 10; // 1차 센서감지 후 빠지는 속도 또는 재진입 속도이며, 해당 값이 낮을 수록 센서 위치에서 가깝게 정지합니다. ec.ecmHomeCfg_SetSpeedPatt(netID, axisID, speedMode, workSpeed, accel, decel, specVel, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } int dir = 0; // 홈복귀 이송 방향을 설정합니다. 0:(-) 1:(+) ec.ecmHomeMot_MoveStart(netID, axisID, dir, ref errorCode); if (errorCode != 0) { AddLog(errorCode); return false; } // TimeOut을 설정합니다. Stopwatch sw = new Stopwatch(); sw.Start(); bool isBusy = true; while (sw.ElapsedMilliseconds < 10000 && isBusy) { if (IsStop) { AddLog("Stop"); return false; } isBusy = ec.ecmHomeSt_IsBusy(netID, axisID, ref errorCode); Thread.Sleep(100); } // isBusy가 true 인 경우, timeout 조건에 의해 while 종료 if (isBusy) { AddLog(string.Format("Axis {0} Homing Timeout", axisID)); return false; } // isBusy가 false 이면 일단 홈복귀는 종료된 상태이지만, 성공한 경우와 실패한 경우를 구분해야 합니다. // 예를 들어, 드라이버에 알람이 발생하여 정지한 경우에도 isBusy는 false로 리턴됩니다. ec.TEcmHomeSt_Flags homeFlag = new ec.TEcmHomeSt_Flags(); homeFlag.word = ec.ecmHomeSt_GetFlags(netID, axisID, ref errorCode); bool isSuccess = ((homeFlag.word >> 2) & 1) == 1; if (!isSuccess) { // 홈복귀가 실패한 경우, 대부분은 현재 축의 상태가 홈복귀 불가능 상태이며, 이는 motState로 확인 가능합니다. // MotState == 0 인 경우, Stop 명령에 의해 정지했다는 의미입니다. int motState = ec.ecmSxSt_GetMotState(netID, axisID, ref errorCode); AddLog(errorCode); } AddLog(string.Format("Axis {0} : HomeReturn {1}", axisID, isSuccess ? "success" : "fail")); return isSuccess; } } }