legal contact rss
 

CatFeed Counter

Da bei uns Daheim die Katze mit mitleiderregendem Auftreten alle Femilienmitglieder dazu bringt, sie zu füttern und wir komplett den Überblick verlieren, möchte ich einen FutterZähler bauen.

Dieser soll auf einem LCD Display den aktuellen Tagesstand der Fütterungen anzeigen und um Mitternacht resetted werden.

Ich hatte als Hardware noch ein wenig Zeug "rumliegen" und arbeite seit Tagen immer mal wieder an der Umsetzung...

ChatGPT Prompt

Diese Anleitung beschreibt die vollständige Umsetzung Ihres Projekts unter Verwendung des **ESP32-S3-DevKitC-1** und eines **4-Kanal I2C Pegelwandlers**. Ziel ist es, ein **1602 I2C LCD Display** und einen **433 MHz RF-Empfänger** mit dem ESP32-S3 zu verbinden, um einen Portionenzähler zu realisieren, der bei Empfang eines bestimmten RF-Codes erhöht wird.

Benötigte Komponenten

- **ESP32-S3-DevKitC-1**
- **1602 I2C LCD Display** (5V-Version)
- **433 MHz RF-Empfänger** (z.B. RXB6 oder kompatibel)
- **4-Kanal I2C Pegelwandler** (3,3V ↔ 5V bidirektional)
- **Verbindungsdrähte**
- **USB-Kabel** (für die Verbindung zwischen ESP32-S3 und Computer)
- **Optional:** Pull-Up-Widerstände (4,7 kΩ bis 10 kΩ) für I2C-Leitungen (falls erforderlich)
- **Netzteil oder USB-Stromversorgung** (falls zusätzliche Stromversorgung benötigt wird)

Hardware-Verbindungen

- **ESP32-S3-DevKitC-1**:
  - **Versorgungsspannung:** Über USB-Kabel
  - **5V-Pin:** Versorgung des RF-Empfängers und des LCD-Displays
  - **3,3V-Pin:** Versorgung der Low-Voltage-Seite des Pegelwandlers
  - **GND-Pin:** Gemeinsame Masse für alle Komponenten

- **4-Kanal I2C Pegelwandler**:
  - **High-Voltage-Seite (HV):** Verbunden mit 5V-Geräten (RF-Empfänger, LCD-Display)
  - **Low-Voltage-Seite (LV):** Verbunden mit ESP32-S3 (3,3V Logikpegel)

2.2. Anschluss des 4-Kanal I2C Pegelwandlers

2.2.1. Spannungsversorgung des Pegelwandlers

- **HV (High Voltage) Pin:** Mit **5V-Pin** des ESP32-S3 verbinden
- **HV GND Pin:** Mit **GND-Pin** des ESP32-S3 verbinden
- **LV (Low Voltage) Pin:** Mit **3,3V-Pin** des ESP32-S3 verbinden
- **LV GND Pin:** Mit **GND-Pin** des ESP32-S3 verbinden

2.2.2. Kanäle des Pegelwandlers

Wir verwenden drei der vier verfügbaren Kanäle:

- **Kanal 1 (HV1 ↔ LV1):** RF Data Signal
- **Kanal 2 (HV2 ↔ LV2):** I2C SCL-Leitung
- **Kanal 3 (HV3 ↔ LV3):** I2C SDA-Leitung

2.3. Anschluss des RF-Empfängers

- **Data Pin des RF-Empfängers:**
  - **HV1** (High Voltage Seite Kanal 1) des Pegelwandlers anschließen
- **HV GND:** Mit **GND** des RF-Empfängers verbinden
- **VCC des RF-Empfängers:** Mit **5V-Pin** des ESP32-S3 verbinden
- **GND des RF-Empfängers:** Mit **GND-Pin** des ESP32-S3 verbinden

**Low Voltage Seite:**

- **LV1** (Low Voltage Seite Kanal 1): Mit **GPIO14** (Pin D14) des ESP32-S3 verbinden

2.4. Anschluss des LCD-Displays

- **SCL (Clock) des LCD-Displays:**
  - **HV2** (High Voltage Seite Kanal 2) des Pegelwandlers anschließen
- **SDA (Data) des LCD-Displays:**
  - **HV3** (High Voltage Seite Kanal 3) des Pegelwandlers anschließen
- **VCC des LCD-Displays:** Mit **5V-Pin** des ESP32-S3 verbinden
- **GND des LCD-Displays:** Mit **GND-Pin** des ESP32-S3 verbinden

**Low Voltage Seite:**

- **LV2** (Low Voltage Seite Kanal 2): Mit **GPIO9** (SCL, Pin D9) des ESP32-S3 verbinden
- **LV3** (Low Voltage Seite Kanal 3): Mit **GPIO8** (SDA, Pin D8) des ESP32-S3 verbinden

2.5. Zusammenfassung der Verbindungen

| Komponente       | ESP32-S3 Pin  | Pegelwandler LV | Pegelwandler HV | Externes Gerät Pin |
|------------------|---------------|-----------------|-----------------|--------------------|
| RF Data Signal   | GPIO14 (D14)  | LV1             | HV1             | Data Pin RF-Empfänger |
| I2C SCL          | GPIO9 (D9)    | LV2             | HV2             | SCL Pin LCD-Display |
| I2C SDA          | GPIO8 (D8)    | LV3             | HV3             | SDA Pin LCD-Display |

**Spannungsversorgung:

- **3,3V (ESP32-S3):** LV Pin Pegelwandler
- **5V (ESP32-S3):** HV Pin Pegelwandler, VCC RF-Empfänger, VCC LCD-Display
- **GND:** Alle GND-Pins miteinander verbinden (ESP32-S3, Pegelwandler, RF-Empfänger, LCD-Display)

Programmierung mit MicroPython

Benötigte Bibliotheken

- **`lcd_api.py`**: Basis-Klasse für die LCD-Ansteuerung.
- **`esp32_i2c_lcd.py`**: Anpassung der LCD-Ansteuerung für den ESP32-S3.
- **`main.py`**: Hauptprogramm.

Erstellung der Skripte

#### **4.2.1. `lcd_api.py`**

```python
# lcd_api.py

import time

class LcdApi:
    # Implementierung der LCD-API-Klasse
    # (Inhalt entsprechend Ihrer bisherigen Version)
    # ...
```

#### **4.2.2. `esp32_i2c_lcd.py`**

```python
# esp32_i2c_lcd.py

from lcd_api import LcdApi
from machine import I2C
import time

class I2cLcd(LcdApi):
    # Implementierung der I2C-LCD-Klasse für den ESP32
    # (Anpassungen entsprechend Ihrer Hardware)
    # ...
```

`main.py`**

```python
# main.py

from machine import Pin, I2C
import network
import utime
import _thread
from esp32_i2c_lcd import I2cLcd
import ntptime

# WLAN-Konfiguration
SSID = 'Ihr_WLAN_SSID'
PASSWORD = 'Ihr_WLAN_Passwort'

# I2C-Konfiguration
I2C_ADDR = 0x27
I2C_NUM_ROWS = 2
I2C_NUM_COLS = 16

# Initialisierung des I2C-Busses
i2c = I2C(1, scl=Pin(9), sda=Pin(8), freq=100000)  # Frequenz auf 100 kHz reduziert

# LCD initialisieren
lcd = I2cLcd(i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS)

# RF-Empfänger an GPIO14
rf_pin = Pin(14, Pin.IN)

# WLAN-Verbindung herstellen
def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)
    max_wait = 15
    while max_wait > 0:
        if wlan.isconnected():
            print('WLAN verbunden:', wlan.ifconfig())
            break
        max_wait -= 1
        print('Warten auf WLAN-Verbindung...')
        utime.sleep(1)
    if not wlan.isconnected():
        print('Konnte keine WLAN-Verbindung herstellen.')
        return False
    return True

# WLAN verbinden
if not connect_wifi():
    raise SystemExit

# Zeit synchronisieren
try:
    ntptime.settime()
    print('Zeit synchronisiert:', utime.localtime())
except Exception as e:
    print('Konnte Zeit nicht synchronisieren:', e)

# Portion-Zähler
portion_counter = 0

# Display aktualisieren
def update_display():
    lcd.clear()
    lcd.putstr("Portionen: {}".format(portion_counter))

# RF-Signal dekodieren
def decode_rf_signal():
    import micropython
    micropython.alloc_emergency_exception_buf(100)

    CODE_TO_MATCH = 0xED2C78
    timings = []
    last_time = utime.ticks_us()

    def pin_callback(pin):
        nonlocal last_time, timings
        current_time = utime.ticks_us()
        duration = utime.ticks_diff(current_time, last_time)
        last_time = current_time
        level = pin.value()
        timings.append((duration, level))

    rf_pin.irq(trigger=Pin.IRQ_RISING | Pin.IRQ_FALLING, handler=pin_callback)

    while True:
        if len(timings) > 100:
            rf_pin.irq(handler=None)
            code = parse_timings(timings)
            timings = []
            rf_pin.irq(handler=pin_callback)
            if code == CODE_TO_MATCH:
                print("Code empfangen:", hex(code))
                increment_counter()
        utime.sleep_ms(100)

def parse_timings(timings):
    bits = []
    for duration, level in timings:
        if 200 < duration < 700:
            bits.append(0)
        elif 700 <= duration < 1200:
            bits.append(1)
        else:
            return None
    if len(bits) >= 24:
        code = 0
        for bit in bits[:24]:
            code = (code << 1) | bit
        return code
    return None

def increment_counter():
    global portion_counter
    portion_counter += 1
    update_display()

# Zähler um Mitternacht zurücksetzen
def reset_counter_at_midnight():
    global portion_counter
    while True:
        current_time = utime.localtime()
        if current_time[3] == 0 and current_time[4] == 0:
            portion_counter = 0
            update_display()
            print('Portion-Zähler um Mitternacht zurückgesetzt')
            utime.sleep(60)
        utime.sleep(30)

# Threads starten
_thread.start_new_thread(decode_rf_signal, ())
_thread.start_new_thread(reset_counter_at_midnight, ())

# Initiales Display-Update
update_display()

# Hauptschleife
while True:
    utime.sleep(1)
```