poniedziałek, 17 października 2022

[41] Arduino - CY8CMBR3102

W tym poście chciałbym opisać sposób obsługi CY8CMBR3102. Czyli układu służącego do obsługi przycisków dotykowych. Komunikacja odbywa się przez I2C.


Do wykonania tego projektu udało mi się znaleźć przykład dla innego układu pod tym linkiem

Opis układu:


Układ CY8CMBR3102 jest dwukanałowym czujnikiem kontrolerem czujnikiem pojemnościowym, komunikującym się przez interfejs I2C. Może zostać skonfigurowany do działania jako czujnik pojemnościowy (proximiy sensor) lub przycisk pojemnościowy (touch). Czyli będzie działał na zbliżenie lub na wciśnięcie.

Poniżej znajduje się opis pinów układu.

[Źródło: Datasheet]

Podłączenie:


Podłączenie wykonałem zgodnie z dokumentacją producenta:

[Źródło: AN900071 - CY8CMBR3XXX

Należy tutaj pamiętać, że pin VCC należy odłączyć od zasilania VDD. Dotyczy to sytuacji w której zasilanie układu jest wyższe od 1,89V. Korzystając z Arduino całość podłączyłem do zasilania 3,3V. Maksymalne dopuszczalne zasilanie układu to 5,5V. 

Zmodyfikowany schemat wygląda następująco:


Rezystor 560 Ohm podłączony szeregowo z czujnikiem pojemnościowym, ma za zadanie zredukowanie ilości szumu jaki jest przekazywany do układu. 

EZ-Click 2.0:


W celu ustawienia układu należy do niego wysłać przygotowany pakiet konfiguracyjny, który jest generowany przez program EZ-Click.


Układ posiada dwa konfigurowane wyjścia. W moim przypadku tylko jeden z nich podłączony jest jako przycisk. Czyli CS0/PS0. Drugi z nich jest skonfigurowany do wyzwalania przerwania z informacją, że przycisk został wciśnięty. 

W celu uruchomiania czujnika zbliżeniowego należy zwiększyć wartość Number of proximiy sensors, co wyświetli dodatkowe pole nad Buttons z jego konfiguracją. 

Wyjście z informacją o wciśnięciu przycisku należy skonfigurować w zakładce Global Configuration. 


Pozostałe dwie zakładki nie będą wykorzystywane, ponieważ układ należałoby podłączyć przez odpowiedni programator. 

Dla tak przygotowanego projektu należy uruchomić przycisk Generate Config File (Ctrl + G). Wygenerowane dane są zapisane w pliku <nazwa_projektu>.h. 

  1. const unsigned char CY8CMBR3102-SX1I_configuration[128] = {
  2.     0x01u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  3.     0x00u, 0x00u, 0x00u, 0x00u, 0x80u, 0x7Fu, 0x00u, 0x00u,
  4.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  5.     0x00u, 0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u,
  6.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x80u,
  7.     0x05u, 0x00u, 0x00u, 0x02u, 0x00u, 0x02u, 0x00u, 0x00u,
  8.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x1Eu, 0x00u, 0x00u,
  9.     0x00u, 0x1Eu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  10.     0x00u, 0x0Fu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  11.     0x00u, 0x00u, 0x00u, 0x00u, 0x05u, 0x03u, 0x01u, 0x58u,
  12.     0x00u, 0x37u, 0x06u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u,
  13.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  14.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  15.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  16.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
  17.     0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x08u, 0x16u
  18. };

Konfiguracja:


Załączony wyżej plik konfiguracyjny należy przesłać do układu. Po czym dla pewności wykonać jego reset. Po przesłaniu danych z programu EZ-Click jego działanie pozostaje niezmienne do czasu zmiany konfiguracji.

Dane z informacją, który przycisk został wciśnięty należy pobrać z odpowiedniego rejestru (0xAA):


Dla układu dwukanałowego, interesujące dane są zapisane na bitach 0 oraz 1. Dla opisywanej konfiguracji tylko dla bitu 0.

Gdy przycisk jest aktywny wartość w bitu w rejestrze jest ustawiona na 1. 

Jeśli wykorzystujemy konfigurację jednoprzyciskową, to wystarczy korzystać tylko z informacji pobranej ze stanu CS1, który jest podłączony jako przerwanie. 

Program Arduino:


Na samym początku przejdę przez funkcję konfiguracyjną.

  1. void configCY8CMBR3102()
  2. {
  3.   int writeerr = 0;
  4.  
  5.   //Write dummy data
  6.   Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
  7.   Wire.write(REGMAP_ORIGIN);          // sends Offset byte
  8.   Wire.write(00);
  9.   Wire.endTransmission();    // stop transmitting
  10.  
  11.   Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
  12.   Wire.write(REGMAP_ORIGIN);          // sends Offset byte
  13.   Wire.write(00);
  14.   Wire.endTransmission();    // stop transmitting
  15.  
  16.   //Arduino function can send only 31 bytes of data
  17.   //So whole frame is send in chunks
  18.   //from [0] to [30]
  19.   Wire.beginTransmission(SLAVE_ADDR);
  20.   Wire.write(REGMAP_ORIGIN);      //0    
  21.   size_t sendedSize = Wire.write(&configData[0],31);        
  22.   Wire.endTransmission();  
  23.  
  24.   writeerr = Wire.getWriteError();
  25.  
  26.   if(writeerr == 0) {
  27.      Serial.print("First packet sended\n");
  28.   }
  29.  
  30.   //from [31] to [61]
  31.   Wire.beginTransmission(SLAVE_ADDR);
  32.   Wire.write(BUTTON_LBR);         //31
  33.   sendedSize = Wire.write(&configData[31],31);
  34.   Wire.endTransmission();
  35.  
  36.   writeerr = Wire.getWriteError();
  37.  
  38.   if(writeerr == 0) {
  39.      Serial.print("Second packet sended\n");
  40.   }
  41.  
  42.   //from [62] to [92]
  43.   Wire.beginTransmission(SLAVE_ADDR);
  44.   Wire.write(BUZZER_CFG);          //62
  45.   sendedSize = Wire.write(&configData[62],31);
  46.   Wire.endTransmission();
  47.  
  48.   writeerr = Wire.getWriteError();
  49.  
  50.   if(writeerr == 0) {
  51.      Serial.print("Third packet sended\n");
  52.   }
  53.  
  54.   //from [93] to [123]
  55.   Wire.beginTransmission(SLAVE_ADDR);
  56.   Wire.write(SLIDER_CFG);         //93
  57.   sendedSize = Wire.write(&configData[93],31);
  58.   Wire.endTransmission();
  59.  
  60.   writeerr = Wire.getWriteError();
  61.  
  62.   if(writeerr == 0) {
  63.      Serial.print("Fourth packet sended\n");
  64.   }
  65.  
  66.   //from [124] to [127]
  67.   Wire.beginTransmission(SLAVE_ADDR);
  68.   Wire.write(0x7C);         //124
  69.   sendedSize = Wire.write(&configData[124],4);        
  70.   Wire.endTransmission();    
  71.  
  72.   writeerr = Wire.getWriteError();
  73.  
  74.   if(writeerr == 0) {
  75.      Serial.print("Fifth packet sended\n");
  76.   }
  77.  
  78.   /*
  79.   Write 0x02 to 0x86
  80.   Info from datasheet
  81.   The device calculates a CRC checksum over the configuration data in this register map and
  82.   compares the result with the content of CONFIG_CRC. If the two values match, the device saves
  83.   the configuration and the CRC checksum to nonvolatile memory.
  84.   */
  85.   Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
  86.   Wire.write(CTRL_CMD);
  87.   Wire.write(SAVE_CHECK_CRC);
  88.   Wire.endTransmission();    // stop transmitting
  89.  
  90.   delay(200);
  91.  
  92.   //Reset
  93.   Wire.beginTransmission(SLAVE_ADDR);
  94.   Wire.write(CTRL_CMD);
  95.   Wire.write(SW_RESET);
  96.   Wire.endTransmission();    // stop transmitting
  97.  
  98.   delay(200);
  99. }

Na samym początku wysyłamy puste bajty w celu wybudzenia układu i rozpoczęcia transmisji. W kolejnych krokach przesyłamy paczki konfiguracyjne do układu. Spowodowane jest to ograniczeniem funkcji write:

  1. #define BUFFER_LENGTH 32
  2.  
  3. size_t TwoWire::write(uint8_t data)
  4. {
  5.   if(transmitting){
  6.   // in master transmitter mode
  7.     // don't bother if buffer is full
  8.     if(txBufferLength >= BUFFER_LENGTH){
  9.       setWriteError();
  10.       return 0;
  11.     }
  12.     // put byte in tx buffer
  13.     txBuffer[txBufferIndex] = data;
  14.     ++txBufferIndex;
  15.     // update amount in buffer  
  16.     txBufferLength = txBufferIndex;
  17.   }else{
  18.   // in slave send mode
  19.     // reply to master
  20.     twi_transmit(&data, 1);
  21.   }
  22.   return 1;
  23. }
  24.  
  25. size_t TwoWire::write(const uint8_t *data, size_t quantity)
  26. {
  27.   if(transmitting){
  28.   // in master transmitter mode
  29.     for(size_t i = 0; i < quantity; ++i){
  30.       write(data[i]); //brak sprawdzenia zwracanej wartości, petla wykonuje się do końca
  31.     }
  32.   }else{
  33.   // in slave send mode
  34.     // reply to master
  35.     twi_transmit(data, quantity);
  36.   }
  37.   return quantity;
  38. }

Jak widać powyżej funkcja write wyśle maksymalnie tyle danych ile zostało zostało umieszczone w buforze, który jest ograniczony domyślnie przez wartość BUFFER_LENGTH. Można ją oczywiście zwiększyć w bibliotece, natomiast tylko do przesłania danych konfiguracyjnych nie będzie to optymalne rozwiązanie. Niestety powyższa funckja zwróci informacje, że zostały wysłane wszystkie bajty, ponieważ nie ma sprawdzania zwracanej wartości z funkcji write w pętli for. Dopiero pobranie wartości z funkcji getWriteError() da informację o błędzie w transmisji.

  1.   int writeerr = Wire.getWriteError();
  2.   Serial.print(writeerr, DEC);

Funkcja setup:

  1. void setup()
  2. {
  3.   pinMode(ArduinoUnoInternalLed, OUTPUT); //To blink if needed
  4.   pinMode(InterruptPin, INPUT);    
  5.   attachInterrupt(digitalPinToInterrupt(InterruptPin), HostInterrruptISR, FALLING);  
  6. //Both to know when btn was pressed and released
  7.   //attachInterrupt(digitalPinToInterrupt(InterruptPin), HostInterrruptISR, CHANGE);  

  8.   Wire.begin();  
  9.    
  10.   Serial.begin(115200);  // start serial for output
  11.  
  12.   //For single chip is need to be call only once.
  13.   //If do not change configuration
  14.   configCY8CMBR3102();
  15.  
  16.   Serial.print("Device configurated\n");
  17. }

Obsługa przerwania:

  1. void HostInterrruptISR() {
  2.   readflag=1;
  3. }

W obsłudze przerwania od pinu ustawiam jedynie flagę informującą o otrzymaniu przerwania.

Pętla główna:

  1. void loop()
  2. {
  3.  if(readflag) {
  4.    readflag=0;
  5.    ReadAndDisplaySensorStatus();
  6.  }
  7. }

Pętla sprawdza czy flaga została ustawiona. Jeśli tak to kasuje flagę i przechodzę do odczytania danych z układu.

Obsługa kliknięcia przycisku:

  1. void ReadAndDisplaySensorStatus()
  2. {
  3.   char readedData[2] = {0x00};
  4.  
  5.   Wire.beginTransmission(SLAVE_ADDR);
  6.   Wire.write(BUTTON_STATUS);        
  7.   Wire.endTransmission();
  8.  
  9.   int i=0;
  10.   Wire.requestFrom(SLAVE_ADDR, 2); //2 bytes for whole BUTTON_STATUS
  11.   while(Wire.available())  
  12.   {
  13.     readedData[i] = Wire.read();
  14.     Serial.print("Hex: 0x");
  15.     Serial.print(readedData[i], HEX);
  16.     Serial.print("\n");
  17.     i++;
  18.   }
  19.   Wire.endTransmission();
  20.      
  21.   DisplaySensorStatus(&readedData[0]);
  22. }
  23.  
  24. void DisplaySensorStatus(char *c)
  25. {
  26.   if((c[0] & 0x00) != 0)
  27.   {
  28.     Serial.print("BTN 0 TOUCHED \n");
  29.   }
  30.  
  31.   //If second btn is used instead of interrupt
  32.   if((c[0] & 0x01) != 0)
  33.   {
  34.     Serial.print("BTN 1 TOUCHED \n");
  35.   }
  36. }

W związku z tym, że do układu jest podłączony tylko jeden przycisk. Działanie może opierać się tylko na sprawdzaniu przerwania. Ponieważ jeśli został kliknięty przycisk, to tylko jeden. 

Gdy chcemy korzystać z dwóch przycisków wtedy należy po prostu częściej odpytywać, zamiast czekać na przerwanie. Ponieważ już nie ma wolnego pinu pozwalającego na obsługę przerwania.

  1. void loop()
  2. {
  3.   ReadAndDisplaySensorStatus();
  4.   delay(50);
  5. }

Przez czas wciśnięcia przycisku funkcja będzie zwracać informację o wciśnięciu.

Działanie układu testowałem z przyciskiem naklejonym do szyby 2mm. Całość działała bez problemu.

Pliki do projektu można pobrać z dysku Google pod tym linkiem.