Nach vielen misslungen Versuchen, eine I2C-Kommunikation mit einem Eeprom aufzunehmen, hatte ich nach einer anderen Lösung gesucht, Parameter dauerhaft sichern zu können.
Ich sag's gleich vorneweg: Diese Präsentation meiner Lösung wird nicht jedem Pythonisten gefallen, es ist auch nicht meine Absicht, eine pythonistische Sprachkultur der besonderen Art zu pflegen. Man mag meinen Stil annehmen oder auch nicht, ich jedenfalls komme damit gut zurecht.
In meinem Projekt wird eine standardmäßige SPI-Schnittstelle bereits von dem Touchpanel der von mir gewählten Monitoreinheit verwendet, ohne dass ich auf diese Schnittstelle mit meinem eigenen Pythonscript einen Einfluss ausüben kann. In meinem Projekt sollte auch noch eine weitere SPI-Schnittstelle mit einem AD-Wandler aufgebaut werden, weshalb ich der grundsätzlichen Idee einer I2C-Kommunikation mit einem Eeprom zur Parameter-Speicherung positiv gegenüber stand.
Da ich als Newbie noch nicht abschätzen konnte, ob ich die zweite integrierte SPI-Schnittstelle dann vielleicht doch noch anderweitig werde verwenden müssen, hatte ich mich entschlossen, den AD-Wandler MCP3204 mit einer handgestrickten SPI-Schnittstelle zu betreiben, so wie es Erik Bartmann in seinem Buch "Die elektronische Welt mit Raspberry Pi entdecken" vor vielen Jahren beschrieben hatte. Erik Bartmann setzte mit seiner Beschreibung auf eine damals übliche Bibliothek auf. Aus bestimmten Gründen habe ich es aber vorgezogen, auf die pigpio-Bibliothek aufzusetzen und seine Methode mit Erfolg entsprechend umgesetzt.
Danach sollte es mit der I2C-Kommunikation zur Einbindung eines Eeproms weitergehen. Das hat ohne Erfolg sehr viel Zeit gekostet, während dessen ich die pigpio-Bibliothek gründlich studieren konnte. Es lohnt sich, Einblicke in das Innere der Bibliothek zu nehmen. Man wird staunen. Vor wenigen Tagen hatte ich dann beschlossen, die I2C-Kommunikation mit einem Eeprom nicht mehr betreiben zu wollen. Das aufmerksame Studium des Datenblattes zu einem SPI-Eeprom brachte mich dann zu der Erkenntnis, dass diese Art der Kommunikation der Lösungsweg für mein Problem sein könnte. Besonders der Satz:
QuoteIt may also interface with microcontrollers that do not have a built-in SPI port by using discrete I/O lines programmed properly in software to match the SPI protocol.
hatte es mir angetan. Erik Bartmann war schon einmal als Problemlöser in die Bresche geprungen, er sollte es auch dieses Mal wieder tun.
# * ----------------------------------------------------------------------------------------------
# * EEParamUtils.py
# * ----------------------------------------------------------------------------------------------
#
# *** created 2024-06-14 Batucada
#
# *** revised 2024-06-16 Revision -1- EEPStorage
# 2024-06-17 Revision -2- Test EEprom
#
#
# 2024-06-17 23:53
# * ----------------------------------------------------------------------------------------------
import pigpio
from pigpio import HIGH, LOW
from time import sleep
_TK_ = 0.000010 # eine Halbperiode bei 50 kHz
# * ==============================================================================================
# *** class EEPStorage
# *
# * generell liegt das Timing des SLCK-Taktes bei den beiden Funktionen readByte und sendByte
# * die Vorbelegung der Pins erfolgt in der main-Modul, da sich das Eeprom den SPI-Bus mit
# * dem AD-Wandler teilt
# * ==============================================================================================
class EEPStorage():
def __init__(self, sclk, mosi, miso, csen, wp, parent=None):
self.pi = pigpio.pi()
self.SCLK = sclk
self.MOSI = mosi
self.MISO = miso
self.CSEN = csen
self.WP = wp # write protect wird z.Zt. nicht verwendet und steht auf HIGH
# * ----------------------------------------------------------------------------------------------
# *** schreibe 1 Byte in das Eeprom
# * ----------------------------------------------------------------------------------------------
def sendByte(self, data):
data = data
for i in range(8): # Data-Byte versenden
self.pi.write(self.MOSI, (data & 0x80) > 0) # Bit an Position 7
data <<= 1 # Bitfolge links schieben
sleep(_TK_)
self.pi.write(self.SCLK, HIGH) # steigende Flanke serieller Takt
sleep(_TK_)
self.pi.write(self.SCLK, LOW) # fallende Flanke serieller Takt
# * ----------------------------------------------------------------------------------------------
# *** lese 1 Byte aus dem Eeprom
# * ----------------------------------------------------------------------------------------------
def readByte(self):
data = 0
for i in range(8): # Byte einlesen
sleep(_TK_)
data <<= 1 # Bitfolge 1 Position nach links
self.pi.write(self.SCLK, HIGH) # steigende Flanke serieller Takt
if (self.pi.read(self.MISO)):
data |= 0x01
sleep(_TK_)
self.pi.write(self.SCLK, LOW) # fallende Flanke serieller Takt
return data
# * ----------------------------------------------------------------------------------------------
# *** Lese das Status Register: >> RDSR <<
# * ----------------------------------------------------------------------------------------------
def readStatusRegister(self):
self.pi.write(self.CSEN, LOW) # CS für EEprom aktivieren
self.sendByte(0b00000101) # Instruktion >> RDSR <<
status = self.readByte() # Status-Byte empfangen
self.pi.write(self.CSEN, HIGH) # Zugriff auf EEprom beendet
return status
# * ----------------------------------------------------------------------------------------------
# *** das WRITE-ENABLE-Latch (WEL) setzen: >> WREN <<
# * zurück setzen: >> WRDI <<
# * ----------------------------------------------------------------------------------------------
def affectWEL(self, enable=True):
if enable:
command = 0b00000110 # Instruktion >> WREN <<
else:
command = 0b00000100 # Instruktion >> WRDI <<
self.pi.write(self.CSEN, LOW) # CS für EEprom aktivieren
self.sendByte(command) # Instruktion senden
self.pi.write(self.CSEN, HIGH) # Zugriff auf EEprom beendet
# * ----------------------------------------------------------------------------------------------
# *** das Status-Register überschreiben: >> WRSR <<
# * ----------------------------------------------------------------------------------------------
def setBPMatrix(self, BP1=True, BP0=True):
self.pi.write(self.CSEN, LOW) # CS für EEprom aktivieren
self.sendByte(0b00000001) # Instruktion >> WRSR <<
status = 0b00000000
if BP1:
status |= 0b00001000
if BP0:
status |= 0b00000100
self.sendByte(status) # BP1 und BP0
self.pi.write(self.CSEN, HIGH) # Zugriff auf EEprom beendet
# * ----------------------------------------------------------------------------------------------
# *** die READ-Sequenz >> READ <<
# *
# * liest 'count' Bytes aus 'data_list' ab der Adresse 'index' ins Eeprom
# * es gibt keine Begrenzung hinsichtlich einer Seitengröße
# * die einzige Begrenzung ist die Anzahl auch 256 Byte.
# * ----------------------------------------------------------------------------------------------
def readSequence(self, index, count, data_list):
self.pi.write(self.CSEN, LOW) # CS für EEprom aktivieren
self.sendByte(0b00000011) # Set Eeprom mode for reading
self.sendByte(index) # Set address to start reading from
for i in range(count):
byte = self.readByte() # Read a byte from Eeprom
data_list[index + i] = byte # Byte in der angegebenen Liste speichern
self.pi.write(self.CSEN, HIGH) # Zugriff auf EEprom beendet
# * ----------------------------------------------------------------------------------------------
# *** die WRITE-Sequenz
# *
# * schreibt 'count' Bytes aus 'data_list' ab der Adresse 'index' ins Eeprom
# * es gibt keine Begrenzung hinsichtlich einer Seitengröße
# * die einzige Begrenzung ist die Anzahl auf 256 Byte.
# * ----------------------------------------------------------------------------------------------
def writeSequence(self, index, count, data_list):
page_size = 16
currindex = index
lastindex = index + count
while currindex < lastindex:
# -----------------------------------------
# Start Ablauf(1)
wipcount = 0
wip = self.readStatusRegister() & 0b00000001
while wip != 0:
wipcount += 1
if wipcount > 8:
raise Exception("Write process taking too long")
sleep(0.1) # kurze Pause
wip = self.readStatusRegister() & 0b00000001
self.affectWEL(True)
# -----------------------------------------
# Start Ablauf(2)
self.pi.write(self.CSEN, LOW) # CS für EEprom aktivieren
self.sendByte(0b00000010) # WRITE-Instruktion senden
self.sendByte(currindex) # aktuellen Adresse senden
byteCount = currindex % page_size # obere Kante des Byte-Fensters bestimmen
quantity = lastindex - currindex # untere Kante des Byte-Fensters
if quantity > page_size:
quantity = page_size
while byteCount < quantity:
self.sendByte(data_list[currindex]) # Senden der Daten
currindex += 1
byteCount += 1
self.pi.write(self.CSEN, HIGH) # Zugriff auf Eeprom beenden
Display More
Das Modul ist nicht unbedingt nur auf ein bestimmtes Eeprom anwendbar und kann jederzeit leicht angepasst werden. Diesem Modul liegt ein Eeprom zugrunde, das 256 Byte speichern kann. Jedes Byte kann für sich einzeln geschrieben und gelesen werden. Es können aber auch 256 Byte in einem Durchgang gelesen werden. Das gilt im besonderen Verständnis auch für das Schreiben: da aber der interne Schreibvorgang auf jeweils 16 Byte pro Speicherseite limitiert ist, wird durch die Funktion "writeSequence" eine automatische Aufteilung vorgenommen, die diese Restriktion umgeht, ohne dabei nach außen sichtbar zu werden.
Alle Funktionen, die das Datenblatt zu dem Eeprom des Typs 25AA020A hergibt, sind in dem Modul implementiert.
Solange auf dem SPI-Bus nur das Eeprom zuhause wäre, müssten keine weiteren Maßnahmen zur Busarbitrierung getroffen werden. In meinem Falle ist jedoch ein AD-Wandler mit von der Partie. Wichtig ist, dass die Ausführung der Funktion "writeSequence" ohne Unterbrechung erfolgen muss, weil sonst der interne Schreibprozess nicht erfolgreich angestoßen wird. Neben der Arbitrierung, die ich nach dem Prinzip der Anmeldung und der daraufhin erteilten Freigabe durchführe, der eigentliche Zugriff auf das Eeprom durch Semaphore gekapselt wird.