Microcontroller ESP32 mit Micropython, erste Gehversuche

AntiX ist darauf ausgelegt, mit minimalen Systemressourcen zu laufen. Es benötigt nur wenige hundert MB RAM und kann auf älteren Computern mit begrenzter Leistung betrieben werden
Antworten
Benutzeravatar
asp
Site Admin
Beiträge: 64
Registriert: 22 Feb 2025, 19:44
Wohnort: Lohn-Ammannsegg
Kontaktdaten:

Microcontroller ESP32 mit Micropython, erste Gehversuche

Beitrag von asp »

Ich möchte ein ESP32-Board von einem Laptop aus programmieren. Auf meinem Laptop ist das Linux antiX installiert.
Screenshot_2025-10-21_11-44-02.jpg
Screenshot_2025-10-21_11-44-02.jpg (20.76 KiB) 34619 mal betrachtet
pipx und esptool installieren

Code: Alles auswählen

sudo apt install pipx
pipx ensurepath
pipx install esptool
Danach habe ich im Terminal folgende Befehle eingegeben

Code: Alles auswählen

source ~/.bashrc
esptool.py --chip esp32 --port /dev/ttyUSB0 erase_flash #Flash löschen
esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 ESP32_GENERIC_C2-20250911-v1.26.1.bin # und neu flashen
Zum Programmieren mit Micropython verwende ich Thonny


Hier das Pinlayout von meinem ESP32
ESP32-USB-Pin.jpg
ESP32-USB-Pin.jpg (93.91 KiB) 34641 mal betrachtet
Jetzt setzen wir mal einen Ausgang:

Code: Alles auswählen

import machine
pin12 = machine.Pin(12, machine.Pin.OUT)
pin12.value(1)
Nach diesem Befehl sollte D12 Log1 sein.
Und so einen Eingang:
Dazu ziehe ich D13 mit einem Widerstand auf 3.3V, damit der Eingang einen definierten Level hat.

Code: Alles auswählen

pin13 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
print(pin13.value())
Im Thonny-Terminal wird eine Log1 zurückgelesen und wenn man D13 gegen GND zieht, erkennt der Eingang eine Log0
Benutzeravatar
asp
Site Admin
Beiträge: 64
Registriert: 22 Feb 2025, 19:44
Wohnort: Lohn-Ammannsegg
Kontaktdaten:

Re: Microcontroller ESP32 mit IOPi-Plus Board

Beitrag von asp »

Screenshot_2025-10-21_15-04-07.jpg
Screenshot_2025-10-21_15-04-07.jpg (14.59 KiB) 34406 mal betrachtet
Jetzt habe ich noch ein IO-Pi-Plus - Board angeschlossen (3.3V, 5V, GND, scl und sda)
Bei meinem ESP32 Board ist scl an Pin22 und sda an Pin21 und mit folgenden Befehlen kann man den Bus scannen:

Code: Alles auswählen

i2c = machine.I2C(sda=machine.Pin(21), scl=machine.Pin(22))
i2c.scan()
Bei mir wird [32, 33] ausgegeben. Dies dezimal 32 entspricht hex 0x20 und dezimal 33 entspricht hex 0x21.

Zum IOPi-Plus Board, dieses hat zwei ICs MCP23017:
Jeder Chip hat 16 I/O-Pins (GPIOA0–7 und GPIOB0–7) und Register für Richtung (IODIRx), Ausgabe (OLATx / GPIOx) usw.

Code: Alles auswählen

Register    Adr Port A    Adr Port B
IODIR       0x00             0x01
GPIO        0x12             0x13
OLAT        0x14             0x15
Bei mir habe ich eine LED mit Vorwiderstand an bus1, Ausgang1 angeschlossen
Mit den folgenden Befehlen bringt man die LED zum Leuchten:

Code: Alles auswählen

i2c.writeto_mem(0x21, 0x00, b'\x00')  # alle 8 Pins auf Port A als Output
i2c.writeto_mem(0x21, 0x01, b'\x00')  # alle 8 Pins auf Port B als Output (optional)
i2c.writeto_mem(0x21, 0x12, b'\x01')  # Bit 0 = 1 => GPIOA0 = High
und so wird die LED wieder ausgeschaltet.

Code: Alles auswählen

i2c.writeto_mem(0x21, 0x12, b'\x00')
Wenn die LED im Rythmus von 1 Sekunde blinken soll
geben wir im Thonny-Terminal folgendes ein:

Code: Alles auswählen

import time
while True:
        i2c.writeto_mem(0x21, 0x12, b'\x01') # Ausgang1 von bus1 setzen
        time.sleep(0.5) # eine halbe Sekunde warten        
        i2c.writeto_mem(0x21, 0x12, b'\x00') # Ausgang1 von bus1 zurücksetzen
        time.sleep(0.5) # und nochmals eine halbe Sekunde warten
Benutzeravatar
asp
Site Admin
Beiträge: 64
Registriert: 22 Feb 2025, 19:44
Wohnort: Lohn-Ammannsegg
Kontaktdaten:

Re: Microcontroller ESP32 mit Micropython, Programm abspeichern

Beitrag von asp »

Wenn alles funktioniert, kann man seine Programme auf dem nicht flüchtigen Speicher abspeichern.

Screenshot_2025-10-21_15-58-46.jpg
Screenshot_2025-10-21_15-58-46.jpg (45.79 KiB) 34341 mal betrachtet
https://www.fambach.net/esp32-nuetzliche-infos/

Der ESP32 hat auf dem Board einen Flash-Speicher integriert
Dort kann man Programme fix abspeichern. Hier zum Beispiel ein Programm, welches die interne LED zum Blinken bringt.

Code: Alles auswählen

from machine import Pin
import time

led = Pin(2, Pin.OUT)  # Built-in LED auf GPIO2

while True:
    led.on()
    time.sleep(1)
    led.off()
    time.sleep(1)

Obiges Programm einfach auf dem ESP32 unter z.B. blink-esp32-LED-intern.py abspeichern (Shift-Ctrl-S auf ESP32) und dann mit grünem Pfeil oben bei Thonny ausführen.

Nach einem Reset oder Spannungsausfall Startet das Programm blink-esp32-LED-intern.py nicht mehr neu, man muss den Dateimanager von Thonny öffnen und das Programm neu ins RAM laden.

Startprogramm main.py
Wenn man möchte, dass ein Programm beim Booten automatisch startet, dann muss man dieses als main.py ins Flash laden.
Benutzeravatar
asp
Site Admin
Beiträge: 64
Registriert: 22 Feb 2025, 19:44
Wohnort: Lohn-Ammannsegg
Kontaktdaten:

Re: Microcontroller ESP32 mit Micropython, Klassenprogrammierung

Beitrag von asp »

Vorteile der Klassenprogrammierung
Bei grossen Programmen oder um die Lesbarkeit zu verbessern ist es empfehlenswert mit Klasse zu arbeiten.

Beispielklasse für IOPiPlus
Wenn man das folgende Programm als iopi_plus_class.py speichert

Code: Alles auswählen

from machine import I2C, Pin
import time

class IOPiPlus:
    def __init__(self, i2c_bus=0, sda_pin=21, scl_pin=22):
        """
        IO-Pi-Plus Klasse für ESP32 mit beiden Chips
        """
        self.i2c = I2C(i2c_bus, sda=Pin(sda_pin), scl=Pin(scl_pin), freq=100000)
        
        # Beide Chip-Adressen
        self.chip1_addr = 0x20  # Erster MCP23017
        self.chip2_addr = 0x21  # Zweiter MCP23017
        
        # Register Adressen für MCP23017
        self.REGISTERS = {
            'IODIRA': 0x00, 'IODIRB': 0x01,   # I/O direction
            'GPIOA': 0x12, 'GPIOB': 0x13,     # GPIO pins
            'GPPUA': 0x0C, 'GPPUB': 0x0D,     # Pull-up
            'OLATA': 0x14, 'OLATB': 0x15      # Output latch
        }
        
        # Initialisiere beide Chips
        self._init_chip(self.chip1_addr)
        self._init_chip(self.chip2_addr)
        
        print("IO-Pi-Plus initialisiert mit 2 Chips (32 I/O Pins)")
        print(f"Chip 1: 0x{self.chip1_addr:02x}, Chip 2: 0x{self.chip2_addr:02x}")
    
    def _init_chip(self, address):
        """Initialisiert einen MCP23017 Chip"""
        self._write_byte(address, self.REGISTERS['IODIRA'], 0xFF)  # Alle Pins Input
        self._write_byte(address, self.REGISTERS['IODIRB'], 0xFF)  # Alle Pins Input
    
    def _write_byte(self, chip_addr, reg, value):
        """Schreibt ein Byte in ein Register"""
        try:
            self.i2c.writeto_mem(chip_addr, reg, bytes([value]))
            return True
        except OSError:
            print(f"Fehler beim Schreiben an Chip 0x{chip_addr:02x}, Register 0x{reg:02x}")
            return False
    
    def _read_byte(self, chip_addr, reg):
        """Liest ein Byte aus einem Register"""
        try:
            result = self.i2c.readfrom_mem(chip_addr, reg, 1)
            return result[0]
        except OSError:
            print(f"Fehler beim Lesen von Chip 0x{chip_addr:02x}, Register 0x{reg:02x}")
            return 0
    
    def get_chip_and_pin(self, pin):
        """
        Bestimmt Chip und internen Pin für Pin 1-32
        Returns: (chip_address, bank, bit_position)
        """
        if pin < 1 or pin > 32:
            raise ValueError("Pin muss zwischen 1 und 32 sein")
        
        if pin <= 16:
            chip_addr = self.chip1_addr
            internal_pin = pin
        else:
            chip_addr = self.chip2_addr
            internal_pin = pin - 16
        
        if internal_pin <= 8:
            bank = 'A'
            bit_pos = internal_pin - 1
        else:
            bank = 'B'
            bit_pos = internal_pin - 9
        
        return chip_addr, bank, bit_pos
    
    def set_pin_direction(self, pin, direction):
        """
        Setzt Pin Richtung (Input/Output)
        Args:
            pin: 1-32
            direction: 0 = Output, 1 = Input
        """
        chip_addr, bank, bit_pos = self.get_chip_and_pin(pin)
        
        reg = self.REGISTERS[f'IODIR{bank}']
        current = self._read_byte(chip_addr, reg)
        
        if direction == 1:  # Input
            new_value = current | (1 << bit_pos)
        else:  # Output
            new_value = current & ~(1 << bit_pos)
            
        self._write_byte(chip_addr, reg, new_value)
    
    def set_pin_pullup(self, pin, enable):
        """
        Aktiviert/Deaktiviert Pull-up Widerstand
        Args:
            pin: 1-32
            enable: True = Pull-up aktiv, False = deaktiviert
        """
        chip_addr, bank, bit_pos = self.get_chip_and_pin(pin)
        
        reg = self.REGISTERS[f'GPPU{bank}']
        current = self._read_byte(chip_addr, reg)
        
        if enable:
            new_value = current | (1 << bit_pos)
        else:
            new_value = current & ~(1 << bit_pos)
            
        self._write_byte(chip_addr, reg, new_value)
    
    def write_pin(self, pin, value):
        """
        Schreibt einen Wert auf einen Output-Pin
        Args:
            pin: 1-32
            value: 0 = LOW, 1 = HIGH
        """
        chip_addr, bank, bit_pos = self.get_chip_and_pin(pin)
        
        reg = self.REGISTERS[f'GPIO{bank}']
        current = self._read_byte(chip_addr, reg)
        
        if value:
            new_value = current | (1 << bit_pos)
        else:
            new_value = current & ~(1 << bit_pos)
            
        self._write_byte(chip_addr, reg, new_value)
    
    def read_pin(self, pin):
        """
        Liest einen Input-Pin
        Args:
            pin: 1-32
        Returns:
            0 = LOW, 1 = HIGH
        """
        chip_addr, bank, bit_pos = self.get_chip_and_pin(pin)
        
        reg = self.REGISTERS[f'GPIO{bank}']
        value = self._read_byte(chip_addr, reg)
        
        return (value >> bit_pos) & 1
    
    def write_port(self, chip_num, port, value):
        """
        Schreibt einen Wert auf einen ganzen Port (8 Pins)
        Args:
            chip_num: 1 oder 2
            port: 'A' oder 'B'
            value: 8-bit Wert (0-255)
        """
        chip_addr = self.chip1_addr if chip_num == 1 else self.chip2_addr
        reg = self.REGISTERS[f'GPIO{port}']
        self._write_byte(chip_addr, reg, value)
    
    def read_port(self, chip_num, port):
        """
        Liest einen ganzen Port (8 Pins)
        Args:
            chip_num: 1 oder 2
            port: 'A' oder 'B'
        Returns:
            8-bit Wert (0-255)
        """
        chip_addr = self.chip1_addr if chip_num == 1 else self.chip2_addr
        reg = self.REGISTERS[f'GPIO{port}']
        return self._read_byte(chip_addr, reg)
kann man z.B. mit io.write_pin(17, 1) einfach den Ausgang 1 auf Port 2 setzen, im folgenden ein Beispiel, wie die Klasse aufgerufen wird:

Code: Alles auswählen

from iopi_plus_class import IOPiPlus
from machine import Pin, PWM
import time

led = Pin(2, Pin.OUT) # Built-in LED auf GPIO2
io = IOPiPlus() # Klasse Instanzieren
print("IO-Pi-Plus erfolgreich initialisiert")

try:
    
    # Warte kurz damit alles bereit ist
    time.sleep(0.1)
    
    # Pin 17 konfigurieren
    io.set_pin_direction(17, 0)  # Output
    print("Pin 17 als Output konfiguriert")
    
    # Sicherheitshalber erstmal ausschalten
    io.write_pin(17, 0)
    time.sleep(0.1)
    
    print("Starte Blink-Programm...")
    
    while True:
        io.write_pin(17, 1)
        led.on() # on-Board (ESP32) LED ON
        print("✓ LED intern/extern AN")
        time.sleep(0.5)
        
        io.write_pin(17, 0)
        led.off() # on-Board (ESP32) LED OFF
        print("✗ LED intern/extern AUS")
        time.sleep(0.5)
        
except Exception as e:
    print(f"Fehler: {e}")
    # Programm läuft weiter aber ohne Fehler

Wünsche viel Vergnügen beim Programmieren!
Antworten