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:
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.
- const unsigned char CY8CMBR3102-SX1I_configuration[128] = {
- 0x01u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x80u, 0x7Fu, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x80u,
- 0x05u, 0x00u, 0x00u, 0x02u, 0x00u, 0x02u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x1Eu, 0x00u, 0x00u,
- 0x00u, 0x1Eu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x0Fu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x05u, 0x03u, 0x01u, 0x58u,
- 0x00u, 0x37u, 0x06u, 0x00u, 0x00u, 0x0Au, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u,
- 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x08u, 0x16u
- };
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):
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ą.
- void configCY8CMBR3102()
- {
- int writeerr = 0;
- //Write dummy data
- Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
- Wire.write(REGMAP_ORIGIN); // sends Offset byte
- Wire.write(00);
- Wire.endTransmission(); // stop transmitting
- Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
- Wire.write(REGMAP_ORIGIN); // sends Offset byte
- Wire.write(00);
- Wire.endTransmission(); // stop transmitting
- //Arduino function can send only 31 bytes of data
- //So whole frame is send in chunks
- //from [0] to [30]
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(REGMAP_ORIGIN); //0
- size_t sendedSize = Wire.write(&configData[0],31);
- Wire.endTransmission();
- writeerr = Wire.getWriteError();
- if(writeerr == 0) {
- Serial.print("First packet sended\n");
- }
- //from [31] to [61]
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(BUTTON_LBR); //31
- sendedSize = Wire.write(&configData[31],31);
- Wire.endTransmission();
- writeerr = Wire.getWriteError();
- if(writeerr == 0) {
- Serial.print("Second packet sended\n");
- }
- //from [62] to [92]
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(BUZZER_CFG); //62
- sendedSize = Wire.write(&configData[62],31);
- Wire.endTransmission();
- writeerr = Wire.getWriteError();
- if(writeerr == 0) {
- Serial.print("Third packet sended\n");
- }
- //from [93] to [123]
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(SLIDER_CFG); //93
- sendedSize = Wire.write(&configData[93],31);
- Wire.endTransmission();
- writeerr = Wire.getWriteError();
- if(writeerr == 0) {
- Serial.print("Fourth packet sended\n");
- }
- //from [124] to [127]
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(0x7C); //124
- sendedSize = Wire.write(&configData[124],4);
- Wire.endTransmission();
- writeerr = Wire.getWriteError();
- if(writeerr == 0) {
- Serial.print("Fifth packet sended\n");
- }
- /*
- Write 0x02 to 0x86
- Info from datasheet
- The device calculates a CRC checksum over the configuration data in this register map and
- compares the result with the content of CONFIG_CRC. If the two values match, the device saves
- the configuration and the CRC checksum to nonvolatile memory.
- */
- Wire.beginTransmission(SLAVE_ADDR); // transmit to device #0x37
- Wire.write(CTRL_CMD);
- Wire.write(SAVE_CHECK_CRC);
- Wire.endTransmission(); // stop transmitting
- delay(200);
- //Reset
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(CTRL_CMD);
- Wire.write(SW_RESET);
- Wire.endTransmission(); // stop transmitting
- delay(200);
- }
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:
- #define BUFFER_LENGTH 32
- size_t TwoWire::write(uint8_t data)
- {
- if(transmitting){
- // in master transmitter mode
- // don't bother if buffer is full
- if(txBufferLength >= BUFFER_LENGTH){
- setWriteError();
- return 0;
- }
- // put byte in tx buffer
- txBuffer[txBufferIndex] = data;
- ++txBufferIndex;
- // update amount in buffer
- txBufferLength = txBufferIndex;
- }else{
- // in slave send mode
- // reply to master
- twi_transmit(&data, 1);
- }
- return 1;
- }
- size_t TwoWire::write(const uint8_t *data, size_t quantity)
- {
- if(transmitting){
- // in master transmitter mode
- for(size_t i = 0; i < quantity; ++i){
- write(data[i]); //brak sprawdzenia zwracanej wartości, petla wykonuje się do końca
- }
- }else{
- // in slave send mode
- // reply to master
- twi_transmit(data, quantity);
- }
- return quantity;
- }
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.
- int writeerr = Wire.getWriteError();
- Serial.print(writeerr, DEC);
Funkcja setup:
- void setup()
- {
- pinMode(ArduinoUnoInternalLed, OUTPUT); //To blink if needed
- pinMode(InterruptPin, INPUT);
- attachInterrupt(digitalPinToInterrupt(InterruptPin), HostInterrruptISR, FALLING);
- //Both to know when btn was pressed and released
- //attachInterrupt(digitalPinToInterrupt(InterruptPin), HostInterrruptISR, CHANGE);
- Wire.begin();
- Serial.begin(115200); // start serial for output
- //For single chip is need to be call only once.
- //If do not change configuration
- configCY8CMBR3102();
- Serial.print("Device configurated\n");
- }
Obsługa przerwania:
- void HostInterrruptISR() {
- readflag=1;
- }
W obsłudze przerwania od pinu ustawiam jedynie flagę informującą o otrzymaniu przerwania.
Pętla główna:
- void loop()
- {
- if(readflag) {
- readflag=0;
- ReadAndDisplaySensorStatus();
- }
- }
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:
- void ReadAndDisplaySensorStatus()
- {
- char readedData[2] = {0x00};
- Wire.beginTransmission(SLAVE_ADDR);
- Wire.write(BUTTON_STATUS);
- Wire.endTransmission();
- int i=0;
- Wire.requestFrom(SLAVE_ADDR, 2); //2 bytes for whole BUTTON_STATUS
- while(Wire.available())
- {
- readedData[i] = Wire.read();
- Serial.print("Hex: 0x");
- Serial.print(readedData[i], HEX);
- Serial.print("\n");
- i++;
- }
- Wire.endTransmission();
- DisplaySensorStatus(&readedData[0]);
- }
- void DisplaySensorStatus(char *c)
- {
- if((c[0] & 0x00) != 0)
- {
- Serial.print("BTN 0 TOUCHED \n");
- }
- //If second btn is used instead of interrupt
- if((c[0] & 0x01) != 0)
- {
- Serial.print("BTN 1 TOUCHED \n");
- }
- }
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.
- void loop()
- {
- ReadAndDisplaySensorStatus();
- delay(50);
- }
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.