usr_motor.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. /*
  2. * usr_gpio.c
  3. *
  4. * Created on: 2022年4月11日
  5. * Author: amos
  6. */
  7. #include "usr_motor.h"
  8. #define BOUNCE_THRESHOLD 2 // 800ms长按阈值
  9. #define LONG_PRESS_THRESHOLD 80 // 800ms长按阈值
  10. #define CONTINUOUS_INTERVAL 40 // 300ms内算连续按键
  11. #define USER_STOP_TIMER 200 // 长按2s不放,停止混匀
  12. #define FIND_OPTIC_SPEED 50 // 找光开速度
  13. #define FIND_OPTIC_BACK_SPEED 43 // 过光开回退速度
  14. #define FIND_OPTIC_BACK_GAP 2 // 过光开回退速度
  15. #define MAX_RUNCNT 4 // 找光开速度
  16. typedef enum swing_state {
  17. SWING_IDLE,
  18. SWING_START,
  19. SWING_LOOP,
  20. SWING_END,
  21. LOOP_START,
  22. LOOP_END,
  23. EMERGENCY_END,
  24. FIND_GAPNUM,
  25. FIND_HOMEGAP,
  26. SWING_ERR,
  27. } swing_state_def;
  28. typedef enum {
  29. KEY_IDLE, // 空闲状态
  30. KEY_SHORT, // 短按
  31. KEY_CONTINUE, // 连续按
  32. KEY_LONG, // 空闲状态,长按抬起,复位设备
  33. KEY_END //混匀状态,长按不释放,停止混匀
  34. } KeyState;
  35. volatile
  36. struct {
  37. KeyState state;
  38. uint32_t press_tick; // 按下时刻(10ms单位)
  39. uint32_t release_tick; // 当前释放时刻
  40. uint32_t last_release_tick; // 新增:上一次释放时刻
  41. uint32_t click_count; // 连续按键计数
  42. uint32_t last_level;
  43. uint32_t key_valid_flag; // 连续按键标志
  44. } key_ctrl;
  45. swing_state_def sw_st, last_st;
  46. unsigned int keyState = 0;
  47. unsigned int MotorOptState = 0;
  48. extern uint32_t conter100ms;
  49. uint32_t conter10ms;
  50. int32_t runLoopTime = 600;
  51. int32_t runLoopSpeed = 80;
  52. uint32_t curtimeinner;
  53. uint32_t timeinner = 6000;
  54. uint32_t swingtime = 100;
  55. uint32_t swingtimeRight = 40;
  56. uint32_t swingtimePause = 100;
  57. int32_t backOptFlag = 0;
  58. int32_t startLoopConter = 0;
  59. int32_t swingspeed = 60;
  60. int32_t stateFistin;
  61. int32_t REVORFWD;
  62. //extern uint8_t error_set(uint8_t error);
  63. uint32_t timerBuf[16];
  64. uint32_t timerconter;
  65. uint32_t intoGap;
  66. uint32_t OutGap;
  67. uint32_t lastGap;
  68. uint32_t judgeGap;
  69. uint32_t curGap;
  70. uint32_t checkPWM;
  71. MOTOR_DIR findhomedir;
  72. uint32_t intoGapFlag;
  73. uint32_t resetFromSwingFlag;
  74. uint32_t swingLoopHigtTime = 0;
  75. uint32_t swingHigStopflage = 0;
  76. uint32_t swingStoptime = 0;
  77. uint32_t startFindHometime = 0;
  78. uint32_t findHome = 0;
  79. extern void DelayMS(uint32_t ms);
  80. //冒泡法排序,排序结果有小到大
  81. void Bubble_Sort(unsigned int *Array,unsigned int n)
  82. {
  83. unsigned char i, j;
  84. unsigned int temp;
  85. for(i=0;i<n-1;i++)
  86. {
  87. for(j=i+1;j<n;j++)
  88. {
  89. if(Array[i] > Array[j])
  90. {
  91. temp = Array[i];
  92. Array[i] = Array[j];
  93. Array[j] = temp;
  94. }
  95. }
  96. }
  97. }
  98. uint32_t Read_Motor(void) {
  99. return (HAL_GPIO_ReadPin(MINI7_V2_REF_SW2_GPIO_Port,MINI7_V2_REF_SW2_Pin)) ? 1 : 0; // 检查PF5引脚值
  100. }
  101. uint32_t Read_key(void) {
  102. return (HAL_GPIO_ReadPin(MINI7_V2_KEY_GPIO_Port,MINI7_V2_KEY_Pin)) ? 1 : 0; // 检查PF5引脚值
  103. }
  104. uint32_t TIMER_GetCounter(void) {
  105. return htim3.Instance->CNT;
  106. }
  107. /**
  108. * @brief SetMotorPwm
  109. *
  110. * @param value between 0-100
  111. *
  112. * @return None
  113. *
  114. * @details
  115. */
  116. void SetMotorPwm(int dir, int32_t value) {
  117. /* Start PWM counter */
  118. unsigned int DutyCycle;
  119. if (value > 100) {
  120. value = 100;
  121. } else if (value < 0) {
  122. value = 0;
  123. DutyCycle = 0;
  124. }
  125. DutyCycle = value*10;
  126. if (dir) {
  127. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 0);
  128. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, DutyCycle);
  129. } else {
  130. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, DutyCycle);
  131. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, 0);
  132. }
  133. }
  134. /**
  135. * @brief GPIO PA/PB/PG/PH IRQ
  136. *
  137. * @param None
  138. *
  139. * @return None
  140. *
  141. * @details The PA/PB/PG/PH default IRQ, declared in startup_M031Series.s.
  142. */
  143. void KYE_IRQHandler(void) {
  144. volatile uint32_t temp;
  145. conter10ms = sysinf.conter10ms;
  146. /* To check if PB.2 interrupt occurred */
  147. {
  148. uint8_t curr_level = Read_key();
  149. if (curr_level != key_ctrl.last_level) {
  150. key_ctrl.last_level = curr_level;
  151. if (curr_level == 0) { // 下降沿(按下)
  152. key_ctrl.press_tick = conter10ms;
  153. if (key_ctrl.state == KEY_SHORT) {
  154. key_ctrl.last_release_tick =
  155. key_ctrl.release_tick; // 更新上次释放时间
  156. } else {
  157. key_ctrl.last_release_tick = 0;
  158. }
  159. } else { // 上升沿(释放)
  160. key_ctrl.release_tick = conter10ms;
  161. uint32_t press_duration = 0;
  162. if(key_ctrl.press_tick){
  163. press_duration = key_ctrl.release_tick - key_ctrl.press_tick;
  164. }
  165. key_ctrl.press_tick = 0;
  166. // 长按判定
  167. if (press_duration <= BOUNCE_THRESHOLD) {
  168. key_ctrl.state = KEY_IDLE;
  169. key_ctrl.last_release_tick = 0;
  170. } else if (press_duration >= LONG_PRESS_THRESHOLD) {
  171. key_ctrl.state = KEY_LONG;
  172. key_ctrl.last_release_tick = 0; // 更新上次释放时间
  173. key_ctrl.key_valid_flag = 1;
  174. }
  175. // 短按判定
  176. else {
  177. key_ctrl.click_count = 1;
  178. key_ctrl.state = KEY_SHORT;
  179. if ((key_ctrl.release_tick - key_ctrl.last_release_tick) <=
  180. CONTINUOUS_INTERVAL) {
  181. key_ctrl.key_valid_flag = 1;
  182. key_ctrl.click_count++;
  183. key_ctrl.state = KEY_CONTINUE;
  184. }
  185. }
  186. }
  187. }
  188. }
  189. }
  190. void check_keyShortValid(void) {
  191. volatile uint32_t temp;
  192. /* To check if PB.2 interrupt occurred */
  193. if (key_ctrl.state == KEY_SHORT) {
  194. if ((conter10ms - key_ctrl.release_tick) > CONTINUOUS_INTERVAL * 2) {
  195. key_ctrl.key_valid_flag = 1;
  196. }
  197. }
  198. if (sw_st != SWING_IDLE && key_ctrl.press_tick) {
  199. if ((conter10ms - key_ctrl.press_tick) > USER_STOP_TIMER) {
  200. key_ctrl.state = KEY_END;
  201. key_ctrl.key_valid_flag = 1;
  202. key_ctrl.press_tick = 0;
  203. }
  204. }
  205. }
  206. /**
  207. * @brief GPIO PC/PD/PE/PF IRQ
  208. *
  209. * @param None
  210. *
  211. * @return None
  212. *
  213. * @details The PC/PD/PE/PF default IRQ, declared in startup_M031Series.s.
  214. */
  215. void OPTO_IRQHandler(void) {
  216. //
  217. // volatile unsigned int temp;
  218. /* To check if PB.2 interrupt occurred */
  219. {
  220. conter10ms = sysinf.conter10ms;
  221. switch(sw_st){
  222. case FIND_GAPNUM:{
  223. if (Read_Motor()){
  224. intoGap = TIMER_GetCounter();
  225. }else{
  226. OutGap = TIMER_GetCounter();
  227. if((conter10ms - startFindHometime) > MOTOR_LOOP_TIM){
  228. if(MAX_RUNCNT > timerconter){
  229. if(OutGap > intoGap){
  230. timerBuf[timerconter++] = OutGap - intoGap;
  231. }else{
  232. timerBuf[timerconter++] = OutGap + 0x10000 - intoGap;
  233. }
  234. if(MAX_RUNCNT == timerconter){
  235. Bubble_Sort(timerBuf,MAX_RUNCNT);
  236. judgeGap = 0;
  237. if(timerBuf[0]*1.25<timerBuf[1]){
  238. judgeGap = timerBuf[0]+timerBuf[0];
  239. }else{
  240. judgeGap = timerBuf[1]+timerBuf[0];
  241. }
  242. // for(unsigned int i =0;i<(MAX_RUNCNT/2);i++){
  243. // judgeGap += timerBuf[i];
  244. // }
  245. judgeGap =judgeGap*0.625;
  246. timerBuf[timerconter++] = judgeGap ;
  247. /*判决门限为最低两个值均值的1.5倍*/
  248. }
  249. }else{
  250. if(OutGap > intoGap){
  251. curGap = OutGap - intoGap;
  252. }else{
  253. curGap = OutGap + 0x10000 - intoGap;
  254. }
  255. timerBuf[timerconter++] = curGap ;
  256. if(curGap > judgeGap){
  257. sw_st = FIND_HOMEGAP;
  258. SetMotorPwm(MOTOR_REV, 0);
  259. SetMotorPwm(MOTOR_FWD, 0);
  260. stateFistin = 1;
  261. checkPWM = checkPWM*0.8; /*降速便于复位*/
  262. findhomedir = (MOTOR_DIR)!findhomedir;
  263. }
  264. if(timerconter==sizeof(timerBuf)){
  265. SetMotorPwm(MOTOR_REV, 0);
  266. SetMotorPwm(MOTOR_FWD, 0);
  267. timerconter = 0;
  268. }
  269. }
  270. }
  271. }
  272. }
  273. break;
  274. case FIND_HOMEGAP:{
  275. /*找远点阶段,光开亮停止*/
  276. if (Read_Motor()){
  277. SetMotorPwm(MOTOR_REV, 0);
  278. SetMotorPwm(MOTOR_FWD, 0);
  279. intoGapFlag = 1;
  280. intoGap = TIMER_GetCounter();
  281. }
  282. }
  283. break;
  284. case SWING_LOOP:{
  285. /*找远点阶段,光开亮停止*/
  286. if(startLoopConter > 0){
  287. startLoopConter --;
  288. }else{
  289. if (Read_Motor()){
  290. SetMotorPwm(MOTOR_REV, 0);
  291. SetMotorPwm(MOTOR_FWD, 0);
  292. intoGapFlag = 1;
  293. intoGap = TIMER_GetCounter();
  294. }
  295. }
  296. }break;
  297. default:{
  298. SetMotorPwm(MOTOR_REV, 0);
  299. SetMotorPwm(MOTOR_FWD, 0);
  300. // sw_st = FIND_GAPNUM;
  301. // stateFistin = 1;
  302. }break;
  303. }
  304. }
  305. }
  306. /**
  307. * @brief Motor_Init
  308. *
  309. * @param init key and optic input
  310. *
  311. * @return None
  312. *
  313. * @details
  314. */
  315. void Motor_Init()
  316. {
  317. key_ctrl.last_level = 1;
  318. timeinner = sysinf.timeinner;
  319. swingspeed = sysinf.swingspeed;
  320. swingtime =sysinf.swingtime;
  321. swingtimeRight =sysinf.swingtimeRight;
  322. swingtimePause =sysinf.swingtimePause;
  323. runLoopTime = sysinf.runLoopTime;
  324. runLoopSpeed =sysinf.runLoopSpeed;
  325. }
  326. /**
  327. * @brief Motor_mix
  328. *
  329. * @param init key and optic input
  330. *
  331. * @return 0正常,1没离开光开,2,没回到光开
  332. *
  333. * @details 设置夹爪转动参数。
  334. */
  335. uint32_t Motor_mix(uint32_t time, uint32_t speed, uint32_t swing,
  336. uint32_t timeRight, uint32_t timePause) {
  337. timeinner = time;
  338. swingtime = swing;
  339. swingspeed = speed;
  340. swingtimeRight = timeRight;
  341. swingtimePause = timePause;
  342. // timeinner = time - time % (swing + timeRight);
  343. // curtimeinner = timeinner;
  344. if (speed == 0) {
  345. return 0;
  346. }
  347. curtimeinner = conter10ms;
  348. sw_st = SWING_LOOP;
  349. startLoopConter = 2;
  350. stateFistin = 1;
  351. backOptFlag = 1;
  352. findhomedir = MOTOR_REV;
  353. return 0;
  354. }
  355. /**
  356. * @brief Motor_deal_key
  357. *
  358. * @param 按键处理,短按,左右摇;连续点击,转圈摇;长按,复位;
  359. *
  360. * @return 0正常,1没离开光开,2,没回到光开
  361. *
  362. * @details 夹爪根据。
  363. */
  364. uint32_t Motor_deal_key(swing_state_def st, KeyState key) {
  365. if (st == SWING_IDLE) {
  366. switch (key) {
  367. case KEY_SHORT:
  368. curtimeinner = conter10ms;
  369. sw_st = SWING_LOOP;
  370. startLoopConter = 2;
  371. stateFistin = 1;
  372. backOptFlag = 1;
  373. findhomedir = MOTOR_REV;
  374. break;
  375. // case KEY_CONTINUE:
  376. // curtimeinner = conter10ms;
  377. // sw_st = LOOP_START;
  378. // stateFistin = 1;
  379. // break;
  380. case KEY_LONG:
  381. curtimeinner = conter10ms;
  382. sw_st = FIND_GAPNUM;
  383. stateFistin = 1;
  384. break;
  385. default:
  386. break;
  387. }
  388. }
  389. if (key == KEY_LONG || key == KEY_END ) {
  390. sw_st = FIND_GAPNUM;
  391. stateFistin = 1;
  392. resetFromSwingFlag = 0;
  393. }
  394. return 0;
  395. }
  396. void Motor_reset(void) {
  397. curtimeinner = conter10ms;
  398. sw_st = FIND_GAPNUM;
  399. stateFistin = 1;
  400. resetFromSwingFlag = 0;
  401. }
  402. /**
  403. * @brief Motor_mix_work
  404. *
  405. * @param init key and optic input
  406. *
  407. * @return 0正常,1没离开光开,2,没回到光开
  408. *
  409. * @details 夹爪根据。
  410. */
  411. uint32_t Motor_mix_work(void) {
  412. static uint32_t last10ms;
  413. static uint32_t firstINGap;
  414. static uint32_t powerOnFlag = 1;
  415. check_keyShortValid();
  416. if(powerOnFlag){
  417. powerOnFlag = 0;
  418. // Motor_Check(0);
  419. resetFromSwingFlag = 0;
  420. sw_st = FIND_GAPNUM;
  421. stateFistin = 1;
  422. }
  423. conter10ms = sysinf.conter10ms;
  424. if (key_ctrl.key_valid_flag) {
  425. key_ctrl.key_valid_flag = 0;
  426. Motor_deal_key(sw_st, key_ctrl.state);
  427. key_ctrl.state = KEY_IDLE;
  428. }
  429. if(sw_st == SWING_ERR){
  430. /*电机故障,不执行操作*/
  431. return 0;
  432. }
  433. switch (sw_st) {
  434. case SWING_IDLE: {
  435. SetMotorPwm(MOTOR_REV, 0);
  436. SetMotorPwm(MOTOR_FWD, 0);
  437. } break;
  438. case FIND_GAPNUM: {
  439. if (stateFistin) {
  440. timerconter = 0;
  441. intoGap = 0;
  442. OutGap = 0;
  443. lastGap = 0;
  444. judgeGap = 0;
  445. HAL_TIM_Base_Stop(&htim4);
  446. HAL_TIM_Base_Start(&htim4);
  447. last10ms = conter10ms;
  448. stateFistin = 0;
  449. startFindHometime = conter10ms;
  450. findhomedir = MOTOR_REV;
  451. checkPWM = swingspeed;
  452. SetMotorPwm(findhomedir, checkPWM);
  453. } else {
  454. if (((conter10ms - last10ms) > MOTOR_LOOP_TIM*4 )&&(timerconter < MAX_RUNCNT)) {
  455. /*电机不动,返回报错*/
  456. SetMotorPwm(MOTOR_REV, 0);
  457. sw_st = SWING_ERR;
  458. error_set(ERROR_MOTOR_FIALED);
  459. return 1;
  460. }
  461. }
  462. } break;
  463. case FIND_HOMEGAP: {
  464. if (stateFistin) {
  465. last10ms = conter10ms;
  466. stateFistin = 0;
  467. intoGapFlag = 0;
  468. firstINGap = 0;
  469. SetMotorPwm(findhomedir, checkPWM);
  470. } else {
  471. if (!intoGapFlag && (conter10ms - last10ms) > MOTOR_LOOP_TIM) {
  472. /*电机来回不动,返回报错*/
  473. stateFistin = 1;
  474. checkPWM = checkPWM/0.85; /*降速便于复位*/
  475. }
  476. if(intoGapFlag){
  477. if(!firstINGap){
  478. /*进入缝隙,置位,延时判读*/
  479. firstINGap = 1;
  480. last10ms = conter10ms;
  481. }else{
  482. if((conter10ms - last10ms) > MOTOR_MOVEFORM_OPTIC_TIM){
  483. if(Read_Motor()){
  484. sw_st = SWING_IDLE;
  485. stateFistin = 1;
  486. findHome = 1;
  487. }
  488. else{
  489. sw_st = FIND_HOMEGAP;
  490. stateFistin = 1;
  491. findhomedir = (MOTOR_DIR)!findhomedir;
  492. checkPWM = checkPWM*0.9; /*降速便于复位*/
  493. }
  494. }
  495. }
  496. }
  497. }
  498. } break;
  499. case LOOP_START: {
  500. if (stateFistin) {
  501. SetMotorPwm(MOTOR_REV, runLoopSpeed);
  502. last10ms = conter10ms;
  503. stateFistin = 0;
  504. } else {
  505. if ((conter10ms - last10ms) == (runLoopTime)) {
  506. SetMotorPwm(MOTOR_REV, 0);
  507. SetMotorPwm(MOTOR_FWD, 0);
  508. }
  509. if ((conter10ms - last10ms) >= (runLoopTime + swingtimePause)) {
  510. sw_st = LOOP_END;
  511. stateFistin = 1;
  512. REVORFWD = 0;
  513. }
  514. }
  515. } break;
  516. case LOOP_END: {
  517. sw_st = FIND_GAPNUM;
  518. stateFistin = 1;
  519. REVORFWD = 1;
  520. } break;
  521. case SWING_LOOP: {
  522. if (stateFistin ) {
  523. /**/
  524. if(backOptFlag){
  525. if(swingtime< (conter10ms - swingStoptime)){
  526. checkPWM = swingspeed;
  527. backOptFlag = 0;
  528. SetMotorPwm(findhomedir, checkPWM);
  529. stateFistin = 0;
  530. last10ms = conter10ms;
  531. swingHigStopflage = 0;
  532. }
  533. }else{
  534. SetMotorPwm(findhomedir, checkPWM);
  535. stateFistin = 0;
  536. last10ms = conter10ms;
  537. swingHigStopflage = 0;
  538. }
  539. intoGapFlag = 0;
  540. firstINGap = 0;
  541. } else{
  542. if (!intoGapFlag && (conter10ms - last10ms) > MOTOR_LOOP_TIM) {
  543. /*速度过底,电机回不来*/
  544. backOptFlag = 1;
  545. swingStoptime = conter10ms;
  546. findhomedir = MOTOR_REV;
  547. stateFistin = 1;
  548. }
  549. if(intoGapFlag){
  550. if(!firstINGap){
  551. /*进入缝隙,置位,延时判读*/
  552. firstINGap = 1;
  553. last10ms = conter10ms;
  554. }else{
  555. if((conter10ms - last10ms) > MOTOR_MOVEFORM_OPTIC_TIM){
  556. stateFistin = 1;
  557. if(Read_Motor()){
  558. backOptFlag = 1;
  559. swingStoptime = conter10ms;
  560. findhomedir = MOTOR_REV;
  561. }
  562. else{
  563. findhomedir = (MOTOR_DIR)!findhomedir;
  564. checkPWM = checkPWM*0.9; /*降速便于复位*/
  565. }
  566. }
  567. }
  568. }
  569. if ((conter10ms - curtimeinner) > (timeinner)){
  570. resetFromSwingFlag = 0;
  571. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 0);
  572. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 0);
  573. timerconter = 0;
  574. memset(timerBuf,0,sizeof(timerBuf));
  575. stateFistin = 1;
  576. sw_st = FIND_GAPNUM;
  577. }
  578. }
  579. } break;
  580. default:
  581. sw_st = SWING_ERR;
  582. break;
  583. }
  584. return 0;
  585. }
  586. /**
  587. * @brief Motor_mix
  588. *
  589. * @param init key and optic input
  590. *
  591. * @return 空闲返回1 其他返回0
  592. *
  593. * @details 设置夹爪转动参数。
  594. */
  595. uint32_t Motor_idle() {
  596. return(sw_st == SWING_IDLE);
  597. }
  598. uint32_t print_home_time() {
  599. if(findHome){
  600. findHome = 0;
  601. Van_Device_Printf(VAN_LOG_NOTICE,"gap:%d ",timerconter);
  602. if(timerconter > sizeof(timerBuf)){
  603. timerconter = sizeof(timerBuf);
  604. }
  605. for(unsigned int i=0;i<timerconter;i++){
  606. Van_Device_Printf(VAN_LOG_NOTICE," %d ",timerBuf[i]);
  607. }
  608. Van_Device_Printf(VAN_LOG_NOTICE,"\n");
  609. }
  610. return 0;
  611. }