Rev 75 | Rev 77 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 75 | Rev 76 | ||
---|---|---|---|
Line 25... | Line 25... | ||
25 | /* Private includes ----------------------------------------------------------*/ |
25 | /* Private includes ----------------------------------------------------------*/ |
26 | /* USER CODE BEGIN Includes */ |
26 | /* USER CODE BEGIN Includes */ |
27 | 27 | ||
28 | #include "libPLX/plx.h" |
28 | #include "libPLX/plx.h" |
29 | #include "libPLX/displayinfo.h" |
29 | #include "libPLX/displayinfo.h" |
- | 30 | #include "libPLX/commsLib.h" |
|
30 | #include "libSerial/serialUtils.H" |
31 | #include "libSerial/serialUtils.H" |
31 | 32 | ||
32 | #include "libSmallPrintf/small_printf.h" |
33 | #include "libSmallPrintf/small_printf.h" |
33 | #include "libNMEA/nmea.h" |
34 | #include "libNMEA/nmea.h" |
34 | #include "switches.h" |
35 | #include "switches.h" |
Line 99... | Line 100... | ||
99 | context_t contexts[MAX_DISPLAYS]; |
100 | context_t contexts[MAX_DISPLAYS]; |
100 | 101 | ||
101 | /// @brief Data storage for readings |
102 | /// @brief Data storage for readings |
102 | info_t Info[MAXRDG]; |
103 | info_t Info[MAXRDG]; |
103 | 104 | ||
104 | /// \brief storage for incoming data |
- | |
105 | data_t Data; |
- | |
106 | - | ||
107 | uint32_t Latch_Timer; |
105 | uint32_t Latch_Timer; |
108 | 106 | ||
109 | // location for GPS data |
107 | // location for GPS data |
110 | Location loc; |
108 | Location loc; |
111 | 109 | ||
112 | /// @brief Time when the logged data will be sent |
110 | /// @brief Time when the logged data will be sent |
113 | uint32_t nextTickReload; |
111 | uint32_t nextTickReload; |
114 | 112 | ||
- | 113 | // data timeout |
|
- | 114 | uint32_t dataTimeout = 0; // |
|
- | 115 | ||
- | 116 | // USART buffers |
|
- | 117 | uint8_t uc1_tx_buffer[TX_USART_BUFF_SIZ]; |
|
- | 118 | uint8_t uc1_rx_buffer[RX_USART_BUFF_SIZ]; |
|
- | 119 | ||
- | 120 | uint8_t uc2_tx_buffer[TX_USART_BUFF_SIZ]; |
|
- | 121 | uint8_t uc2_rx_buffer[RX_USART_BUFF_SIZ]; |
|
- | 122 | ||
- | 123 | uint8_t uc3_tx_buffer[TX_USART_BUFF_SIZ]; |
|
- | 124 | uint8_t uc3_rx_buffer[RX_USART_BUFF_SIZ]; |
|
- | 125 | ||
- | 126 | uint8_t uc4_tx_buffer[TX_USART_BUFF_SIZ]; |
|
- | 127 | uint8_t uc4_rx_buffer[RX_USART_BUFF_SIZ]; |
|
- | 128 | ||
115 | /* USER CODE END PV */ |
129 | /* USER CODE END PV */ |
116 | 130 | ||
117 | /* Private function prototypes -----------------------------------------------*/ |
131 | /* Private function prototypes -----------------------------------------------*/ |
118 | void SystemClock_Config(void); |
132 | void SystemClock_Config(void); |
119 | static void MX_GPIO_Init(void); |
133 | static void MX_GPIO_Init(void); |
Line 135... | Line 149... | ||
135 | return cc_display(dial, suppress); |
149 | return cc_display(dial, suppress); |
136 | } |
150 | } |
137 | 151 | ||
138 | /// \note HC-05 only accepts : 9600,19200,38400,57600,115200,230400,460800 baud |
152 | /// \note HC-05 only accepts : 9600,19200,38400,57600,115200,230400,460800 baud |
139 | /// \brief Setup Bluetooth module |
153 | /// \brief Setup Bluetooth module |
140 | void initModule(usart_ctl *ctl, uint32_t baudRate) |
154 | void initModule(struct usart_ctl *ctl, uint32_t baudRate) |
141 | { |
155 | { |
142 | char initBuf[60]; |
156 | char initBuf[60]; |
143 | // switch to command mode |
157 | // switch to command mode |
144 | HAL_GPIO_WritePin(BT_RESET_GPIO_Port, BT_RESET_Pin, GPIO_PIN_SET); |
158 | HAL_GPIO_WritePin(BT_RESET_GPIO_Port, BT_RESET_Pin, GPIO_PIN_SET); |
145 | HAL_Delay(500); |
159 | HAL_Delay(500); |
Line 212... | Line 226... | ||
212 | 226 | ||
213 | /* USER CODE END PFP */ |
227 | /* USER CODE END PFP */ |
214 | 228 | ||
215 | /* Private user code ---------------------------------------------------------*/ |
229 | /* Private user code ---------------------------------------------------------*/ |
216 | /* USER CODE BEGIN 0 */ |
230 | /* USER CODE BEGIN 0 */ |
- | 231 | void libPLXcallbackSendUserData(struct usart_ctl * instance) |
|
- | 232 | { |
|
- | 233 | (void)instance; |
|
- | 234 | } |
|
- | 235 | ||
- | 236 | ||
- | 237 | void libPLXcallbackRecievedData(PLX_SensorInfo *info) |
|
- | 238 | { |
|
- | 239 | // received some data , timeout is reset |
|
- | 240 | dataTimeout = 0; |
|
- | 241 | ||
- | 242 | // search to see if the item already has a slot in the Info[] array |
|
- | 243 | // match the observation and instance: if found, update entry |
|
- | 244 | enum PLX_Observations observation = ConvPLX(info->AddrH, |
|
- | 245 | info->AddrL); |
|
- | 246 | ||
- | 247 | char instance = info->Instance; |
|
- | 248 | ||
- | 249 | int16_t data = ConvPLX(info->ReadingH, |
|
- | 250 | info->ReadingL); |
|
- | 251 | ||
- | 252 | // validate the current item, discard out of range |
|
- | 253 | if ((instance > PLX_MAX_INST) || (observation > PLX_MAX_OBS)) |
|
- | 254 | return; |
|
- | 255 | ||
- | 256 | // search for the item in the list |
|
- | 257 | int currentSlot; |
|
- | 258 | for (currentSlot = 0; currentSlot < MAXRDG; ++currentSlot) |
|
- | 259 | { |
|
- | 260 | if ((Info[currentSlot].observation.Obs == observation) && (Info[currentSlot].observation.Instance == instance)) |
|
- | 261 | break; |
|
- | 262 | } |
|
- | 263 | // fallen off the end of the list of existing items without a match, so j points at next new item |
|
- | 264 | // |
|
- | 265 | // Find an unused slot |
|
- | 266 | ||
- | 267 | if (currentSlot == MAXRDG) |
|
- | 268 | { |
|
- | 269 | int k; |
|
- | 270 | { |
|
- | 271 | for (k = 0; k < MAXRDG; ++k) |
|
- | 272 | if (!isValid(k)) |
|
- | 273 | { |
|
- | 274 | currentSlot = k; // found a spare slot |
|
- | 275 | Info[currentSlot] = nullInfo; |
|
- | 276 | break; |
|
- | 277 | } |
|
- | 278 | } |
|
- | 279 | if (k == MAXRDG) |
|
- | 280 | return; // abandon this iteration |
|
- | 281 | } |
|
217 | 282 | ||
- | 283 | // give up if we are going to fall off the end of the array |
|
- | 284 | if (currentSlot >= MAXRDG) |
|
- | 285 | return; |
|
- | 286 | ||
- | 287 | Info[currentSlot].observation.Obs = observation; |
|
- | 288 | ||
- | 289 | Info[currentSlot].observation.Instance = instance; |
|
- | 290 | Info[currentSlot].data = data; |
|
- | 291 | if (data > Info[currentSlot].Max) |
|
- | 292 | { |
|
- | 293 | Info[currentSlot].Max = data; |
|
- | 294 | } |
|
- | 295 | if (data < Info[currentSlot].Min) |
|
- | 296 | { |
|
- | 297 | Info[currentSlot].Min = data; |
|
- | 298 | } |
|
- | 299 | // take an average |
|
- | 300 | Info[currentSlot].sum += data; |
|
- | 301 | Info[currentSlot].count++; |
|
- | 302 | // note the last update time |
|
- | 303 | Info[currentSlot].lastUpdated = HAL_GetTick(); |
|
- | 304 | Info[currentSlot].updated = 1; // it has been updated |
|
- | 305 | ||
- | 306 | // scan through and invalidate all old items |
|
- | 307 | for (int i = 0; i < MAXRDG; ++i) |
|
- | 308 | { |
|
- | 309 | if (!isValid(i)) |
|
- | 310 | Info[i] = nullInfo; |
|
- | 311 | } |
|
- | 312 | } |
|
218 | /* USER CODE END 0 */ |
313 | /* USER CODE END 0 */ |
219 | 314 | ||
220 | /** |
315 | /** |
221 | * @brief The application entry point. |
316 | * @brief The application entry point. |
222 | * @retval int |
317 | * @retval int |
Line 282... | Line 377... | ||
282 | /* turn on UART4 IRQ */ |
377 | /* turn on UART4 IRQ */ |
283 | HAL_NVIC_SetPriority(UART4_IRQn, 4, 0); |
378 | HAL_NVIC_SetPriority(UART4_IRQn, 4, 0); |
284 | HAL_NVIC_EnableIRQ(UART4_IRQn); |
379 | HAL_NVIC_EnableIRQ(UART4_IRQn); |
285 | 380 | ||
286 | /* setup the USART control blocks */ |
381 | /* setup the USART control blocks */ |
287 | init_usart_ctl(&uc1, &huart1); |
382 | init_usart_ctl(&uc1, &huart1, uc1_tx_buffer, |
- | 383 | uc1_rx_buffer, |
|
- | 384 | TX_USART_BUFF_SIZ, |
|
- | 385 | TX_USART_BUFF_SIZ); |
|
288 | init_usart_ctl(&uc2, &huart2); |
386 | init_usart_ctl(&uc2, &huart2, uc2_tx_buffer, |
- | 387 | uc2_rx_buffer, |
|
- | 388 | TX_USART_BUFF_SIZ, |
|
- | 389 | TX_USART_BUFF_SIZ); |
|
289 | init_usart_ctl(&uc3, &huart3); |
390 | init_usart_ctl(&uc3, &huart3, uc3_tx_buffer, |
- | 391 | uc3_rx_buffer, |
|
- | 392 | TX_USART_BUFF_SIZ, |
|
- | 393 | TX_USART_BUFF_SIZ); |
|
290 | init_usart_ctl(&uc4, &huart4); |
394 | init_usart_ctl(&uc4, &huart4, uc4_tx_buffer, |
- | 395 | uc4_rx_buffer, |
|
- | 396 | TX_USART_BUFF_SIZ, |
|
- | 397 | TX_USART_BUFF_SIZ); |
|
291 | 398 | ||
292 | EnableSerialRxInterrupt(&uc1); |
399 | EnableSerialRxInterrupt(&uc1); |
293 | EnableSerialRxInterrupt(&uc2); |
400 | EnableSerialRxInterrupt(&uc2); |
294 | EnableSerialRxInterrupt(&uc3); |
401 | EnableSerialRxInterrupt(&uc3); |
295 | EnableSerialRxInterrupt(&uc4); |
402 | EnableSerialRxInterrupt(&uc4); |
Line 319... | Line 426... | ||
319 | 426 | ||
320 | /// @brief Time when the logged data will be sent |
427 | /// @brief Time when the logged data will be sent |
321 | 428 | ||
322 | setRmcCallback(&rmc_callback); |
429 | setRmcCallback(&rmc_callback); |
323 | 430 | ||
324 | // data timeout |
- | |
325 | uint32_t timeout = 0; // |
- | |
326 | - | ||
327 | // used in NMEA style logging |
431 | // used in NMEA style logging |
328 | uint32_t nextTick = 0; ///< time to send next |
432 | uint32_t nextTick = 0; ///< time to send next |
329 | nextTickReload = 0; |
433 | nextTickReload = 0; |
330 | uint32_t offsetTicks = 0; ///< time to print as offset in mS for each loop |
434 | uint32_t offsetTicks = 0; ///< time to print as offset in mS for each loop |
331 | 435 | ||
332 | // PLX decoder protocols |
- | |
333 | char PLXPacket = 0; |
- | |
334 | int PLXPtr = 0; |
- | |
335 | 436 | ||
336 | for (int i = 0; i < MAXRDG; ++i) |
437 | for (int i = 0; i < MAXRDG; ++i) |
337 | { |
438 | { |
338 | Info[i] = nullInfo; |
439 | Info[i] = nullInfo; |
339 | } |
440 | } |
340 | 441 | ||
341 | uint32_t resetCounter = 0; // record time at which both reset buttons were first pressed. |
442 | uint32_t resetCounter = 0; // record time at which both reset buttons were first pressed. |
342 | 443 | ||
- | 444 | resetPLX(); |
|
343 | /* USER CODE END 2 */ |
445 | /* USER CODE END 2 */ |
344 | 446 | ||
345 | /* Infinite loop */ |
447 | /* Infinite loop */ |
346 | /* USER CODE BEGIN WHILE */ |
448 | /* USER CODE BEGIN WHILE */ |
347 | while (1) |
449 | while (1) |
Line 446... | Line 548... | ||
446 | 548 | ||
447 | double cur_rdg = ConveriMFDRaw2Data((enum PLX_Observations)Observation, DisplayInfo[Observation].Units, |
549 | double cur_rdg = ConveriMFDRaw2Data((enum PLX_Observations)Observation, DisplayInfo[Observation].Units, |
448 | average); |
550 | average); |
449 | int cnt; |
551 | int cnt; |
450 | int intPart; |
552 | int intPart; |
- | 553 | // depending on digits after the decimal point, |
|
- | 554 | // choose how to format data |
|
451 | switch (DisplayInfo[Observation].DP) |
555 | switch (DisplayInfo[Observation].DP) |
452 | { |
556 | { |
453 | default: |
557 | default: |
454 | case 0: |
558 | case 0: |
455 | cnt = small_sprintf(outbuff, |
559 | cnt = small_sprintf(outbuff, |
Line 492... | Line 596... | ||
492 | sendString(&uc3, outbuff, cnt); |
596 | sendString(&uc3, outbuff, cnt); |
493 | } |
597 | } |
494 | } |
598 | } |
495 | } |
599 | } |
496 | 600 | ||
497 | // determine if we are getting any data from the interface |
- | |
498 | uint16_t cc = SerialCharsReceived(&uc1); |
- | |
499 | int chr; |
- | |
500 | if (cc == 0) |
601 | // poll data into libPLX |
501 | { |
- | |
502 | timeout++; |
602 | libPLXpollData(&uc1); |
503 | if (btConnected() && (timeout % 1000 == 0)) |
- | |
504 | { |
- | |
505 | const char msg[] = "Timeout\r\n"; |
- | |
506 | sendString(&uc3, msg, sizeof(msg)); |
- | |
507 | } |
- | |
508 | 603 | ||
509 | if (timeout > 60000) |
604 | // determine if we are getting any data from the interface |
510 | { |
- | |
511 | 605 | ||
512 | // do turn off screen |
606 | dataTimeout++; |
- | 607 | if (btConnected() && (dataTimeout % 1000 == 0)) |
|
513 | } |
608 | { |
514 | // wait for a bit if nothing came in. |
609 | const char msg[] = "Timeout\r\n"; |
515 | HAL_Delay(1); |
610 | sendString(&uc3, msg, sizeof(msg)); |
516 | } |
611 | } |
517 | 612 | ||
518 | /// process the observation list |
- | |
519 | for (chr = 0; chr < cc; chr++) |
613 | if (dataTimeout > 60000) |
520 | { |
614 | { |
521 | char c = GetCharSerial(&uc1); |
- | |
522 | 615 | ||
523 | if (c == PLX_Start) // at any time if the start byte appears, reset the pointers |
- | |
524 | { |
- | |
525 | PLXPtr = 0; // reset the pointer |
- | |
526 | PLXPacket = 1; |
- | |
527 | timeout = 0; // Reset the timer |
- | |
528 | continue; |
- | |
529 | } |
- | |
530 | if (c == PLX_Stop) |
- | |
531 | { |
- | |
532 | if (PLXPacket) |
- | |
533 | { |
- | |
534 | // we can now decode the selected parameter |
- | |
535 | int PLXNewItems = PLXPtr / sizeof(PLX_SensorInfo); // total items in last reading batch |
- | |
536 | - | ||
537 | // process items |
- | |
538 | for (int dataItem = 0; dataItem < PLXNewItems; ++dataItem) |
- | |
539 | { |
- | |
540 | // search to see if the item already has a slot in the Info[] array |
- | |
541 | // match the observation and instance: if found, update entry |
- | |
542 | enum PLX_Observations observation = ConvPLX(Data.Sensor[dataItem].AddrH, |
- | |
543 | Data.Sensor[dataItem].AddrL); |
- | |
544 | - | ||
545 | char instance = Data.Sensor[dataItem].Instance; |
- | |
546 | - | ||
547 | int16_t data = ConvPLX(Data.Sensor[dataItem].ReadingH, |
- | |
548 | Data.Sensor[dataItem].ReadingL); |
- | |
549 | // validate the current item, discard out of range |
- | |
550 | - | ||
551 | if ((instance > PLX_MAX_INST) || (observation > PLX_MAX_OBS)) |
- | |
552 | continue; |
- | |
553 | - | ||
554 | // search for the item in the list |
- | |
555 | int currentSlot; |
- | |
556 | for (currentSlot = 0; currentSlot < MAXRDG; ++currentSlot) |
- | |
557 | { |
- | |
558 | if ((Info[currentSlot].observation.Obs == observation) && (Info[currentSlot].observation.Instance == instance)) |
- | |
559 | break; |
- | |
560 | } |
- | |
561 | // fallen off the end of the list of existing items without a match, so j points at next new item |
- | |
562 | // |
- | |
563 | // Find an unused slot |
- | |
564 | - | ||
565 | if (currentSlot == MAXRDG) |
- | |
566 | { |
- | |
567 | int k; |
- | |
568 | { |
- | |
569 | for (k = 0; k < MAXRDG; ++k) |
- | |
570 | if (!isValid(k)) |
- | |
571 | { |
- | |
572 | currentSlot = k; // found a spare slot |
- | |
573 | break; |
616 | // do turn off screen |
574 | } |
- | |
575 | } |
- | |
576 | if (k == MAXRDG) |
- | |
577 | continue; // abandon this iteration |
- | |
578 | } |
- | |
579 | - | ||
580 | // give up if we are going to fall off the end of the array |
- | |
581 | if (currentSlot >= MAXRDG) |
- | |
582 | break; |
- | |
583 | - | ||
584 | Info[currentSlot].observation.Obs = observation; |
- | |
585 | - | ||
586 | Info[currentSlot].observation.Instance = instance; |
- | |
587 | Info[currentSlot].data = data; |
- | |
588 | if (data > Info[currentSlot].Max) |
- | |
589 | { |
- | |
590 | Info[currentSlot].Max = data; |
- | |
591 | } |
- | |
592 | if (data < Info[currentSlot].Min) |
- | |
593 | { |
- | |
594 | Info[currentSlot].Min = data; |
- | |
595 | } |
- | |
596 | // take an average |
- | |
597 | Info[currentSlot].sum += data; |
- | |
598 | Info[currentSlot].count++; |
- | |
599 | // note the last update time |
- | |
600 | Info[currentSlot].lastUpdated = HAL_GetTick(); |
- | |
601 | Info[currentSlot].updated = 1; // it has been updated |
- | |
602 | } |
- | |
603 | PLXPtr = 0; |
- | |
604 | PLXPacket = 0; |
- | |
605 | - | ||
606 | // scan through and invalidate all old items |
- | |
607 | for (int i = 0; i < MAXRDG; ++i) |
- | |
608 | { |
- | |
609 | if (!isValid(i)) |
- | |
610 | Info[i] = nullInfo; |
- | |
611 | } |
- | |
612 | - | ||
613 | break; // something to process |
- | |
614 | } |
- | |
615 | } |
- | |
616 | if (c > PLX_Stop) // illegal char, restart reading |
- | |
617 | { |
- | |
618 | PLXPacket = 0; |
- | |
619 | PLXPtr = 0; |
- | |
620 | continue; |
- | |
621 | } |
- | |
622 | if (PLXPacket && PLXPtr < sizeof(Data.Bytes)) |
- | |
623 | { |
- | |
624 | Data.Bytes[PLXPtr++] = c; |
- | |
625 | } |
- | |
626 | } |
617 | } |
- | 618 | // wait for a bit if nothing came in. |
|
- | 619 | HAL_Delay(1); |
|
- | 620 | } |
|
627 | 621 | ||
628 | // handle switch rotation |
622 | // handle switch rotation |
629 | for (int i = 0; i < MAX_DIALS; ++i) |
623 | for (int i = 0; i < MAX_DIALS; ++i) |
- | 624 | { |
|
- | 625 | int delta = get_dial_diff(i); |
|
- | 626 | int pos = contexts[i].knobPos; |
|
- | 627 | if (pos < 0) |
|
- | 628 | break; // dont process until we have read NVRAM for the first time . |
|
- | 629 | int start = pos; |
|
- | 630 | // move in positive direction |
|
- | 631 | while (delta > 0) |
|
630 | { |
632 | { |
631 | int delta = get_dial_diff(i); |
- | |
632 | int pos = contexts[i].knobPos; |
- | |
633 | if (pos < 0) |
- | |
634 | break; // dont process until we have read NVRAM for the first time . |
- | |
635 | int start = pos; |
- | |
636 | // move in positive direction |
- | |
637 | while (delta > 0) |
- | |
638 | { |
- | |
639 | // skip invalid items, dont count |
633 | // skip invalid items, dont count |
640 | if (pos < MAXRDG - 1) |
634 | if (pos < MAXRDG - 1) |
641 | pos++; |
635 | pos++; |
642 | else |
636 | else |
643 | pos = 0; |
637 | pos = 0; |
644 | 638 | ||
645 | if (isValid(pos)) |
639 | if (isValid(pos)) |
646 | delta--; // count a valid item |
640 | delta--; // count a valid item |
647 | - | ||
648 | // wrap |
- | |
649 | if (pos == start) |
- | |
650 | break; |
- | |
651 | } |
- | |
652 | - | ||
653 | // move in negative direction |
- | |
654 | while (delta < 0) |
- | |
655 | { |
- | |
656 | // skip invalid items, dont count |
- | |
657 | if (pos > 0) |
- | |
658 | pos--; |
- | |
659 | else |
- | |
660 | pos = MAXRDG - 1; |
- | |
661 | - | ||
662 | if (isValid(pos)) |
- | |
663 | delta++; // count a valid item |
- | |
664 | 641 | ||
665 | // wrap |
642 | // wrap |
666 | if (pos == start) |
643 | if (pos == start) |
667 | break; |
644 | break; |
668 | } |
645 | } |
669 | 646 | ||
- | 647 | // move in negative direction |
|
- | 648 | while (delta < 0) |
|
- | 649 | { |
|
670 | contexts[i].knobPos = pos; |
650 | // skip invalid items, dont count |
671 | if (pos != start) |
651 | if (pos > 0) |
- | 652 | pos--; |
|
- | 653 | else |
|
- | 654 | pos = MAXRDG - 1; |
|
- | 655 | ||
- | 656 | if (isValid(pos)) |
|
672 | contexts[i].dial_timer = DialTimeout; |
657 | delta++; // count a valid item |
- | 658 | ||
- | 659 | // wrap |
|
- | 660 | if (pos == start) |
|
- | 661 | break; |
|
673 | } |
662 | } |
674 | 663 | ||
675 | int suppress = -1; |
664 | contexts[i].knobPos = pos; |
676 | for (int i = 0; i < MAX_DISPLAYS; ++i) |
- | |
677 | { // now to display the information |
665 | if (pos != start) |
678 | suppress = DisplayCurrent(i, suppress); |
666 | contexts[i].dial_timer = DialTimeout; |
- | 667 | } |
|
679 | 668 | ||
680 | cc_check_nvram(i); |
669 | int suppress = -1; |
- | 670 | for (int i = 0; i < MAX_DISPLAYS; ++i) |
|
- | 671 | { // now to display the information |
|
- | 672 | suppress = DisplayCurrent(i, suppress); |
|
681 | } |
673 | |
682 | /* USER CODE END WHILE */ |
674 | cc_check_nvram(i); |
683 | } |
675 | } |
- | 676 | /* USER CODE END WHILE */ |
|
- | 677 | ||
684 | /* USER CODE BEGIN 3 */ |
678 | /* USER CODE BEGIN 3 */ |
685 | 679 | ||
686 | /* USER CODE END 3 */ |
680 | /* USER CODE END 3 */ |
687 | } |
681 | } |
688 | 682 |