środa, 22 grudnia 2021

[46] STM32 - Discovery - PAJ7620

W tym poście chciałbym opisać sposób obsługi czujnika PAJ7620 do odczytywania gestów.


[Źródło: https://innovatorsguru.com/paj7620u2-arduino-gesture-sensor/]

Podłączenie:


Czujnik można podłączyć przez 5 pinów:

  • VCC - zasilanie 2,8 do 3.3V.
  • GND - masa.
  • SDA - I2C linia danych.
  • SCL - I2C linia zegara.
  • INT - opcjonalnie do podłączenia. Na pinie pojawia się sygnał niski, który informuje o odczytaniu jednego z dostępnych dla tego czujnika gestów.

Testy Arduino:


Do szybkich testów czujnika wykorzystałem platformę Arduino z układem Arduino Uno oraz biblioteką RavEng PAJ7620 (link github)

Podłączenie PAJ7620 do Arduino Uno:

  • VCC - 3V3
  • GND - GND
  • SDA - A4
  • SCL - A5
  • INT - Pin 2

Podczas instalacji czujnika należy pamiętać o odpowiednich parametrach mechanicznych które można znaleźć w dokumentacji. W przypadku błędnej instalacji np. przez umieszczenie białego obiektu zbyt blisko czujnika jego działanie może zostać zaburzone lub nawet całkowicie zahamowane.

Skorzystałem z przykładu obsługującego przerwanie:

  1. #include "RevEng_PAJ7620.h"
  2.  
  3. #define INTERRUPT_PIN 2                    
  4. volatile bool isr = false;                
  5.  
  6. RevEng_PAJ7620 sensor = RevEng_PAJ7620();
  7.  
  8. void setup()
  9. {
  10.   pinMode(INTERRUPT_PIN, INPUT);
  11.  
  12.   Serial.begin(115200);
  13.   Serial.println("PAJ7620 Test Demo: Recognize 9 gestures using interrupt callback.");
  14.  
  15.   if( !sensor.begin() )                     // Returns 0 if sensor connect fail
  16.   {
  17.     Serial.print("PAJ7620 I2C error - halting");
  18.     while(true) {}
  19.   }
  20.  
  21.   Serial.println("PAJ7620 Init OK.");
  22.   Serial.println("Please input your gestures:");
  23.  
  24.   attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interruptRoutine, FALLING);
  25. }
  26.  
  27. void loop()
  28. {
  29.   Gesture gesture;                
  30.   if (isr == true) {
  31.     isr = false;                    
  32.     gesture = sensor.readGesture();
  33.  
  34.     switch (gesture) {
  35.       case GES_FORWARD: {
  36.           Serial.print(" GES_FORWARD");
  37.           break;
  38.       }
  39.       case GES_BACKWARD: {
  40.           Serial.print(" GES_BACKWARD");
  41.           break;
  42.       }
  43.       case GES_LEFT: {
  44.           Serial.print(" GES_LEFT");
  45.           break;
  46.       }
  47.       case GES_RIGHT: {
  48.           Serial.print(" GES_RIGHT");
  49.           break;
  50.       }
  51.       case GES_UP: {
  52.           Serial.print(" GES_UP");
  53.           break;
  54.       }
  55.       case GES_DOWN: {
  56.           Serial.print(" GES_DOWN");
  57.           break;
  58.       }
  59.       case GES_CLOCKWISE: {
  60.           Serial.print(" GES_CLOCKWISE");
  61.           break;
  62.       }
  63.       case GES_ANTICLOCKWISE: {
  64.           Serial.print(" GES_ANTICLOCKWISE");
  65.           break;
  66.       }
  67.       case GES_WAVE: {
  68.           Serial.print(" GES_WAVE");
  69.           break;
  70.       }
  71.       case GES_NONE: {
  72.           Serial.print(" GES_NONE");
  73.           break;
  74.         }
  75.     }
  76.  
  77.     Serial.print(", Gesture Code: ");
  78.     Serial.println(gesture);
  79.  
  80.     if (isr == true) {
  81.       Serial.println(" --> Interrupt during event processing");
  82.     }
  83.   }
  84. }
  85.  
  86. // Called then interrupt pin is set high
  87. void interruptRoutine()
  88. {
  89.   isr = true;
  90.   Serial.print("Interrupt! -- ");
  91. }

Powyższy przykład działa w oparciu o odebrania przerwania z pinu, które jest generowane sygnałem niski gdy czujnik rozpozna jeden z 9 gestów. Na tej podstawie w oknie terminala wyświetlana będzie informacja o tym, który gest został odebrany. 

Może się zdarzyć, że po którymś włączeniu zasilania, bez programowania kontrolera czujnik PAJ nie będzie działał, mimo iż komunikacja z układem będzie wyglądała na poprawną. Prawdopodobnie wynika to z czasu potrzebnego na inicjalizację czujnika przed wysyłaniem mu danych. Rozwiązałem to przez dodanie opóźnienia przed jego uruchomieniem. Wartość 500 - 1000 ms jest wystarczająca.

  1. Serial.println("PAJ7620 Test Demo: Recognize 9 gestures using interrupt callback.");
  2. delay(1000);
  3. if( !sensor.begin() )  
  4. //...

Dodatkowo dołożyłem opóźnienie podczas inicjalizacji czujnika w bibliotece RavEng:

  1. uint8_t RevEng_PAJ7620::begin(TwoWire *chosenWireHandle)
  2. {
  3.   // Reasonable timing delay values to make algorithm insensitive to
  4.   //  hand entry and exit moves before and after detecting a gesture
  5.   gestureEntryTime = 0;
  6.   gestureExitTime = 200;
  7.  
  8.   wireHandle = chosenWireHandle;      // Save selected I2C bus for our use
  9.  
  10.   delayMicroseconds(700);               // Wait 700us for PAJ7620U2 to stabilize
  11.                                       // Reason: see v0.8 of 7620 documentation
  12.   wireHandle->begin();                // Start the I2C bus via wire library
  13.  
  14.  
  15.   /* There's two register banks (0 & 1) to be selected between.
  16.    * BANK0 is where most data collection operations happen, so it's default.
  17.    * Selecting the bank is done here twice for a reason. When the 7620 turns
  18.    *  on, the I2C bus is sleeping. When you first read/write to the bus
  19.    *  the 7620 wakes up, but it sometimes misses that first message.
  20.    * Running the 7620 on an arduino with the USB power, a single call here
  21.    *  usually works, but as soon as you use an external power bus it often
  22.    *  fails to properly initialize and begin returns an error.
  23.    */
  24.   selectRegisterBank(BANK0);          // This is done twice on purpose
  25.   selectRegisterBank(BANK0);          // Default operations on BANK0
  26.   //Modyfikacja
  27.   delayMicroseconds(200);
  28.   Serial.println("selectRegisterBank_Bank0");
  29.   selectRegisterBank(BANK0);
  30.   //Koniec modyfikacji
  31.  
  32.   if( !isPAJ7620UDevice() ) {
  33.     return 0;                         // Return false - wrong device found
  34.   }
  35.  
  36.   initializeDeviceSettings();         // Set registers up
  37.   setGestureMode();                   // Specifically set to gesture mode
  38.  
  39.   return 1;
  40. }

W przypadku pierwszego uruchomienia czujnika może wystąpić błędne działanie układu, wynikające (prawdopodobnie) ze zmian w rejestrach konfiguracyjnych. W takim przypadku warto odłączyć zasilanie układu na chwilę po czym ponownie uruchomić płytkę.

Programowanie:


Poniżej znajduje się zmodyfikowany projekt z biblioteki dla Arduino opisanej powyżej.

Według mnie najrozsądniejszym sposobem komunikacji z czytnikiem jest oczekiwanie na przerwanie wygenerowane przez zmianę stanu na pinie INT. Oszczędza to trochę czasu i kłopotu potrzebnego na niepotrzebne odpytywanie czujnika. 

W obsłudze przerwania podobnie jak w przypadku dla Arduino ustawiana będzie jedynie flaga informująca o konieczności odczytania danych z czujnika.

Wartości jakie muszą być wgrane do rejestrów w celu poprawnej konfiguracji można znaleźć pod tym linkiem https://github.com/acrandal/RevEng_PAJ7620/wiki w dokumencie PAJ7620U2 Datasheet: v0.8.pdf.

Inicjalizacja interfejsu I2C:

  1. static void MX_I2C1_Init(void)
  2. {
  3.   /* USER CODE BEGIN I2C1_Init 0 */
  4.   /* USER CODE END I2C1_Init 0 */
  5.  
  6.   /* USER CODE BEGIN I2C1_Init 1 */
  7.   /* USER CODE END I2C1_Init 1 */
  8.   hi2c1.Instance = I2C1;
  9.   hi2c1.Init.ClockSpeed = 100000;
  10.   hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  11.   hi2c1.Init.OwnAddress1 = 0;
  12.   hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  13.   hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  14.   hi2c1.Init.OwnAddress2 = 0;
  15.   hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  16.   hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  17.   if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  18.   {
  19.     Error_Handler();
  20.   }
  21.   /* USER CODE BEGIN I2C1_Init 2 */
  22.   /* USER CODE END I2C1_Init 2 */
  23. }

Inicjalizacja czujnika:

  1. uint8_t PAJ7620_Init(void)
  2. {
  3.     HAL_StatusTypeDef opResoult = 0;
  4.     uint8_t readData1 = 0;
  5.     uint8_t readData2 = 0;
  6.  
  7.     if(PAJ7620_SelectBank(0) != 0) { return 1; }
  8.     if(PAJ7620_SelectBank(0) != 0) { return 2; }
  9.  
  10.     if(PAJ7620_ReadRegister(0,1, &readData1) != 0) { return 3; }
  11.     if(PAJ7620_ReadRegister(1,1, &readData2) != 0) { return 4; }
  12.  
  13.     if(PAJ7620_CheckDataId(readData1, readData2)) {
  14.         return 5;
  15.     }
  16.  
  17.     for(uint16_t i = 0; i<(sizeof(initRegisterArray_test)/sizeof(initRegisterArray_test[0])); i++)
  18.     {
  19.         if(PAJ7620_WriteRegister((initRegisterArray_test[i] & 0xFF00 ) >> 8, (initRegisterArray_test[i] & 0x00FF)) != 0) {
  20.             return 5;
  21.         }
  22.     }
  23.  
  24.     if(PAJ7620_SelectBank(0) != 0) { return 6; }
  25.     if(opResoult != 0) { return opResoult; }
  26.  
  27.     for(uint16_t i = 0; i<(sizeof(setGestureModeRegisterArray_test)/sizeof(setGestureModeRegisterArray_test[0])); i++)
  28.     {
  29.         if(PAJ7620_WriteRegister((setGestureModeRegisterArray_test[i] & 0xFF00 ) >> 8, (setGestureModeRegisterArray_test[i] & 0x00FF)) != 0) {
  30.             return 5;
  31.         }
  32.     }
  33.  
  34.     if(PAJ7620_WriteRegister(PAJ7620_REGISTER_BANK_SEL, PAJ7620_BANK0) != 0) { return 6; }
  35.     if(opResoult != 0) { return opResoult; }
  36.  
  37.     return 0;
  38. }

Odczytanie danych:

  1. uint16_t PAJ7620_CheckGesture(void)
  2. {
  3.     uint8_t readedGestureVal = 0;
  4.     uint16_t retVal = 0;
  5.  
  6.     if(PAJ7620_GetGesturesReg0(&readedGestureVal) != HAL_OK) {
  7.         return 0xFF;
  8.     }
  9.  
  10.     #ifdef ENABLE_PAJ7620_DEBUG
  11.     int len = 0;
  12.     char array[30] = {0x00};
  13.     len = sprintf(array, "xxx %u\r\n", readedGestureVal);
  14.     HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  15.     #endif
  16.  
  17.     if(readedGestureVal != 0)
  18.     {
  19.         retVal = readedGestureVal;
  20.  
  21.         if((readedGestureVal & GES_UP_FLAG) == GES_UP_FLAG) {
  22.             #ifdef ENABLE_PAJ7620_DEBUG
  23.             int len = sprintf(array, "GES_UP_FLAG\r\n");
  24.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  25.             #endif
  26.         }
  27.  
  28.         if((readedGestureVal & GES_DOWN_FLAG) == GES_DOWN_FLAG) {
  29.             #ifdef ENABLE_PAJ7620_DEBUG
  30.             int len = sprintf(array, "GES_DOWN_FLAG\r\n");
  31.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  32.             #endif
  33.         }
  34.  
  35.         if((readedGestureVal & GES_LEFT_FLAG) == GES_LEFT_FLAG) {
  36.             #ifdef ENABLE_PAJ7620_DEBUG
  37.             int len = sprintf(array, "GES_LEFT_FLAG\r\n");
  38.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  39.             #endif
  40.         }
  41.  
  42.         if((readedGestureVal & GES_RIGHT_FLAG) == GES_RIGHT_FLAG) {
  43.             #ifdef ENABLE_PAJ7620_DEBUG
  44.             int len = sprintf(array, "GES_RIGHT_FLAG\r\n");
  45.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  46.             #endif
  47.         }
  48.  
  49.         if((readedGestureVal & GES_FORWARD_FLAG) == GES_FORWARD_FLAG) {
  50.             #ifdef ENABLE_PAJ7620_DEBUG
  51.             int len = sprintf(array, "GES_FORWARD_FLAG\r\n");
  52.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  53.             #endif
  54.         }
  55.  
  56.         if((readedGestureVal & GES_BACKWARD_FLAG) == GES_BACKWARD_FLAG) {
  57.             #ifdef ENABLE_PAJ7620_DEBUG
  58.             int len = sprintf(array, "GES_BACKWARD_FLAG\r\n");
  59.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  60.             #endif
  61.         }
  62.  
  63.         if((readedGestureVal & GES_CLOCKWISE_FLAG) == GES_CLOCKWISE_FLAG) {
  64.             #ifdef ENABLE_PAJ7620_DEBUG
  65.             int len = sprintf(array, "GES_CLOCKWISE_FLAG\r\n");
  66.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  67.             #endif
  68.         }
  69.  
  70.         if((readedGestureVal & GES_ANTI_CLOCKWISE_FLAG) == GES_ANTI_CLOCKWISE_FLAG) {
  71.             #ifdef ENABLE_PAJ7620_DEBUG
  72.             int len = sprintf(array, "GES_ANTI_CLOCKWISE_FLAG\r\n");
  73.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  74.             #endif
  75.         }
  76.     }
  77.  
  78.     if(PAJ7620_GetGesturesReg1(&readedGestureVal) != HAL_OK) {
  79.         return 0xFF;
  80.     }
  81.  
  82.     if(readedGestureVal != 0)
  83.     {
  84.         retVal |= readedGestureVal << 8;
  85.  
  86.         if((readedGestureVal & GES_WAVE_FLAG) == GES_WAVE_FLAG) {
  87.             #ifdef ENABLE_PAJ7620_DEBUG
  88.             int len = sprintf(array, "GES_WAVE_FLAG\r\n");
  89.             HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  90.             #endif
  91.         }
  92.     }
  93.  
  94.     return retVal;
  95. }

Powyższa funkcja wywoływana jest w pętli. Następuje odczyt z dwóch rejestrów przechowujących odczytane dane o zarejestrowanym geście. Powyższe instrukcję warunkowe sprawdzają każdy z możliwych gestów, ponieważ w zależności od częstotliwości sprawdzania może nastąpić rejestracja większej ilości gestów.

Drugi sposób polega na sprawdzaniu czy zostało wywołane przerwanie, informujące o odczytanym geście. 

  1. volatile uint8_t PAJ7620_InterruptEnable = 0;
  2.  
  3. void GPIO_Init(void)
  4. {
  5.   GPIO_InitTypeDef GPIO_InitStruct = {0};
  6.  
  7.   __HAL_RCC_GPIOB_CLK_ENABLE();
  8.  
  9.   /*Configure GPIO pin : PB3 */
  10.   GPIO_InitStruct.Pin = GPIO_PIN_3;
  11.   GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  12.   GPIO_InitStruct.Pull = GPIO_NOPULL;
  13.   HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  14.  
  15.   /* EXTI interrupt init*/
  16.   HAL_NVIC_SetPriority(EXTI3_IRQn, 0, 0);
  17.   HAL_NVIC_EnableIRQ(EXTI3_IRQn);
  18. }
  19.  
  20. uint16_t PAJ7620_CheckGesture_Interrupt(void)
  21. {
  22.     if(PAJ7620_InterruptEnable == 1) {
  23.         PAJ7620_InterruptEnable = 0;
  24.         return PAJ7620_CheckGesture();
  25.     }
  26.     return 0;
  27. }

Sprawdzanie gestu następuje w pętli głównej programu.

Istnieją jeszcze dwa tryby pracy Corners oraz Cursor. Pierwszy z nich zwraca informację o położeniu obiektu w czterech rogach czujnika oraz na środku. Drugi natomiast zwraca pozycję X,Y obiektu w zasięgu czujnika.

Wywołanie funkcji wygląda następująco:

  1. //...
  2. PAJ7620_Init();
  3. PAJ7620_setCursorMode();
  4.  
  5. while (1)
  6. {
  7.     if(!PAJ7620_CheckIfCursorInView())
  8.     {
  9.         uint16_t cursor_x = PAJ7620_getCursorX();
  10.         uint16_t cursor_y = PAJ7620_getCursorY();
  11.  
  12.         int len = 0;
  13.         char array[30] = {0x00};
  14.         len = sprintf(array, "X:%u Y:%u\r\n", cursor_x, cursor_y);
  15.         HAL_UART_Transmit(&huart2, (uint8_t *)&array[0], len, 100);
  16.     }
  17. }

Bibliotekę można pobrać z dysku Google pod tym linkiem.