Inhaltsverzeichnis:

I2C-Bus für ATtiny und ATmega - Gunook
I2C-Bus für ATtiny und ATmega - Gunook

Video: I2C-Bus für ATtiny und ATmega - Gunook

Video: I2C-Bus für ATtiny und ATmega - Gunook
Video: I2C in 8 Minuten verstehen! | #EdisTechlab #i2c #arduino 2024, Juli
Anonim
I2C-Bus für ATtiny und ATmega
I2C-Bus für ATtiny und ATmega

Ich liebe die Atmel AVR-Mikrocontroller! Seit dem Aufbau des in diesem Instructable beschriebenen Ghetto-Entwicklungssystems hatte ich viel Spaß beim Experimentieren mit dem AVR ATtiny2313 und dem ATmega168 insbesondere. Ich ging sogar so weit, ein Instructable über die Verwendung von Schaltern als Eingaben zu schreiben, und erweiterte das Konzept des Ghetto-Entwicklungssystems auf CPLDs. Während eines kürzlich durchgeführten Projekts brauchte ich mehrere Schalter zum Einstellen von Steuerwerten. Die AVRs hatten nicht genug I/O-Pins, also musste ich mir etwas einfallen lassen. Ich hätte ein komplexes Eingabesystem mit Tastatur und Display ausprobieren können, aber dem ATtiny2313 wären die Ressourcen ausgegangen. Glücklicherweise hat Atmel einen Weg gefunden, dieses Problem zu umgehen, indem es eine Schnittstelle integriert hat, die mit einer einfachen Zweidrahtschnittstelle mit zusätzlichen Chips (wie Speicher- oder I/O-Ports) verbunden werden kann. Das ist richtig, indem wir nur zwei I/O-Pins an einem AVR verwenden, können wir auf viele zusätzliche I/O-Pins und auch auf andere Ressourcen zugreifen. Diese zweiadrige Schnittstelle ist offiziell als Inter-Integrated Circuit Bus oder einfach als I2C-Bus bekannt und wurde von NXP erfunden, als es noch Philips Semiconductors war. Wenn Sie dieses Instructable lesen, haben Sie wahrscheinlich vom I2C-Bus gehört und ihn möglicherweise sogar auf einem PIC oder einem anderen Mikrocontroller verwendet. Obwohl es konzeptionell sehr einfach ist und von Hardwareressourcen auf den AVRs unterstützt wird, sind dennoch Softwaretreiber erforderlich, um den I2C-Bus zu verwenden. Atmel bietet Anwendungshinweise (siehe die Ressourcen später in diesem Instructable), aber diese sind unvollständig und zeigen keine Beispiele über die Kommunikation mit einem anderen AVR-Gerät hinaus AVRs. Stattdessen stelle ich erweiterte Versionen der Atmel-Treiber für ATtiny2313- und ATmega168-Geräte zur Verfügung, erkläre die Anforderungen und Einschränkungen, die bei deren Verwendung gelten, und zeige Ihnen Arbeitsbeispiele von I2C-Geräten. Nachdem Sie dieses Instructable durchgearbeitet haben, können Sie den I2C-Bus erfolgreich in Ihren AVR-Projekten verwenden. Natürlich können Sie die Treiber für tiny oder MEGA ignorieren, wenn Sie nur an einem von ihnen interessiert sind. Für diejenigen, die mehr über den I2C-Bus erfahren möchten, werde ich Links zu entsprechendem Material bereitstellen.

Schritt 1: Was ist das ganze I2C-Zeug überhaupt?

Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?
Was ist das ganze I2C-Zeug überhaupt?

Der I2C-Bus ist eine einfache Zweidrahtverbindung, die mehrere Geräte miteinander verbinden und ihnen den Datenaustausch ermöglichen kann. In seiner einfachsten Form gibt es ein Master-Gerät, das mit mehreren Slave-Geräten kommuniziert. Alle Geräte werden parallel an die beiden Adern des I2C-Busses angeschlossen. Die beiden Drähte sind als SCL und SDA bekannt. SCL ist die Taktleitung und wird vom Master-Gerät gesteuert. SDA ist die bidirektionale Datenleitung. Um Daten zu übertragen, sendet der Master eine Slave-Adresse kombiniert mit einem Ein-Bit-Lese-/Schreib-Flag. Wird ein Schreiben gewünscht, sendet der Master weiterhin Daten an den adressierten Slave. Wenn ein Lesen angefordert wird, antwortet der Slave mit Daten. Um Transaktionen zu koordinieren, werden die SCL- und SDA-Leitungen vom Master und vom Slave manipuliert, um verschiedene Bedingungen zu signalisieren. Dazu gehören START, STOP, ACK (Bestätigung) und NAK (keine Bestätigung). Die Einzelheiten dieser Bedingungen werden von den Fahrern behandelt. Die wahren Geeks unter Ihnen können alle Details in den Links am Ende dieses Instructable erfahren. Die elektrischen Anforderungen sind ziemlich einfach. Der Master und die Slaves müssen für Vcc den gleichen Pegel verwenden, die Masse muss verbunden sein und die SCL- und SDA-Leitungen müssen auf Vcc gezogen werden. Der Wert der Pull-Up-Widerstände wird durch eine Berechnung anhand der Gesamtkapazität auf dem Bus genau bestimmt, kann aber praktisch jeden Wert zwischen 1,8K und 10K annehmen. Ich beginne mit 5.1K und verwende niedrigere Werte, bis es funktioniert. Dies ist normalerweise kein Problem, es sei denn, Sie haben viele Geräte oder lange Kabellängen zwischen den Geräten. Die nominale Datenrate auf dem I2C-Bus beträgt 100 Kbit/s. Raten von 400Kbits/Sekunde, 1Mbits/Sekunde und darüber hinaus sind ebenfalls möglich, werden aber von den Treibern in diesem Instructable nicht unterstützt. Alle I2C-Geräte arbeiten mit 100 Kbit/Sekunde. Der ATtiny2313 und der ATmega168 implementieren den I2C-Bus jeweils unterschiedlich. ATtiny2313 verwendet die Hardware der Universal Serial Interface (USI), die auch für den SPI-Bus verwendet werden kann. ATmega168 verfügt über dedizierte Hardware für den I2C-Bus, die als Two Wire Interface (TWI) bekannt ist. Sobald die Treiber geschrieben sind, sind diese Unterschiede für den Benutzer größtenteils transparent. Ein wesentlicher Unterschied liegt in der Software: Der ATmega168 I2C-Treiber ist Interrupt-gesteuert, der des ATtiny2313 nicht. Dies bedeutet, dass ein ATmega168-Programm nicht auf I2C-Datenübertragungen warten muss, sondern nur warten muss, bevor eine weitere Übertragung initiiert wird oder bis Daten von einem Lesevorgang eintreffen. Die folgenden Beispiele und Diskussionen sollen dies verdeutlichen. I2C-Adressen sind 7 Bit lang, sodass bis zu 127 Geräte am Bus sein können, wenn jedes eine eindeutige Adresse hat. Wie in der Abbildung gezeigt, wird diese 7-Bit-Adresse um ein Bit nach links verschoben und das niedrigstwertige Bit wird verwendet, um ein Lesen oder Schreiben des Geräts an der Adresse zu kennzeichnen. Somit ist die komplette Slave-Adresse ein 8-Bit-Byte. Die tatsächliche Adresse wird teilweise intern im Gerät bestimmt und kann nicht geändert werden (4 höchstwertige Bits) und teilweise durch Bits bestimmt, die mit Gerätepins verbunden sein können (3 niedrigstwertige Bits), die hoch oder niedrig gesetzt werden können eine bestimmte Adresse. Klingt verwirrend, aber ein Beispiel soll dies verdeutlichen. Das Datenblatt PCA8574A zeigt, dass die vier höchstwertigen Bits der I2C-Adresse immer 0111 sein werden. Die nächsten drei Bits werden durch die Einstellungen an den Pins AD0, AD1 und AD2 bestimmt. Diese Pins können mit Masse oder mit der positiven Spannungsversorgung (5 Volt) verbunden werden, um 0 bzw. 1 darzustellen. Der Bereich der möglichen Adressen ist also 38 bis 3F hexadezimal, wie in der anderen Abbildung aus dem PCA8574-Datenblatt gezeigt. Durch Ändern der Adressbiteinstellungen können sich also bis zu 8 PCA8574As gleichzeitig am I2C-Bus befinden. Jeder antwortet nur auf seine spezifische Slave-Adresse. Wenn noch mehr I/O-Ports benötigt werden, kann der PCA8574 verwendet werden. Der einzige Unterschied zwischen dem PCA8574 und dem PCA8574A besteht darin, dass der I2C-Slave-Adressbereich des PCA8574 20 bis 27 hexadezimal beträgt. Die Bestimmung der Adresse eines bestimmten Geräts kann verwirrend sein, da einige Datenblätter das Lese-/Schreibbit als Teil des die Anschrift. Lesen Sie das Datenblatt sorgfältig durch und beachten Sie, dass die Slave-Adresse 7 Bit lang ist. Das Lese-/Schreibbit sollte separat behandelt werden. Auch hier hilft ein Beispiel. Das Datenblatt für das 24C16 EEPROM, mit dem wir experimentieren, besagt, dass die ersten (höchsten) vier Bits der Slave-Adresse 1010 sind. Die nächsten drei Bits können durch A0, A1 und A2 bestimmt werden; Beachten Sie jedoch, dass das Datenblatt auch 24C01 bis 24C08 abdeckt, bei denen es sich um kleinere EEPROMs handelt. Die Abbildung aus dem Datenblatt zeigt, dass die Einstellungen dieser Adressbits mit zunehmender Größe ignoriert werden und beim 24C16 komplett ignoriert werden. Das heißt, die letzten drei Bits spielen keine Rolle und der 24C16 verwendet wirklich alle I2C-Slave-Adressen 50 bis 57 hexadezimal. Der Bereich der Slave-Adressen adressiert tatsächlich verschiedene Abschnitte innerhalb des 24C16. Die ersten 256 Bytes befinden sich bei Adresse 50h, die nächsten 256 bei 51h und so weiter bis zu den letzten 256 bei 57h - insgesamt also 2KByte. Da die Adresse des PCF8570-RAMs, mit dem wir auch experimentieren, in diesem Bereich liegt, können der 24C16 und der PCF8570 nicht zusammen verwendet werden.

Schritt 2: Bestellen Sie einige I2C-Geräte

Jetzt, da Sie ein wenig über den I2C-Bus Bescheid wissen und ihn verwenden möchten, bestellen Sie doch gleich einige I2C-Geräte zum Experimentieren, damit sie unterwegs zu Ihnen sind, während Sie die Software vorbereiten? O Interface Expander (mein Favorit), ein Static Ram und ein EEPROM. Es gibt noch viel mehr, aber diese sind ein guter Anfang. Die AVR-Prozessoren, die wir verwenden werden, sind der ATtiny2313 und der Atmega168 (verwendet in Arduino). Wenn Sie neu in diesen sind, dann werfen Sie einen Blick auf dieses großartige Instructable, um mehr über sie zu erfahren und Ihr Ghetto-Entwicklungssystem aufzubauen. Das Schema des ATmega168 im vorliegenden Instructable zeigt, wie das Ghetto-Entwicklungssystem für diesen Prozessor implementiert wird. Das Parallelportkabel ist das gleiche wie beim ATtiny2313. (Ich habe die USB-Version des Ghetto-Entwicklungssystems nicht ausprobiert, daher bin ich mir nicht sicher, wie auf den I2C-Bus zugegriffen wird. Gleiches für das Arduino.) Hier sind Digikey-Teilenummern. Port Expander: IC I2C I/O EXPANDER 568-4236-5-NDRam:IC SRAM 256X8 W/I2C 568-1071-5-NDEEPROM:IC EEPROM SERIAL 16K CAT24C16LI-G-ND

Schritt 3: I2C-Treiber

Hier die Beschreibungen der Treiberfunktionen für den I2C-Bus. Diese wurden mit den Atmel Apps Notes für den Anfang entwickelt. Ohne sie als Basis hätte ich das nicht geschafft. Die Entwicklung erfolgte mit WinAVR und dem gcc C-Compiler. Taktratenbeschränkungen werden unten für jeden Prozessor beschrieben. Da ich nicht in der Lage bin, alle möglichen Prozessor-Flavor/Taktraten-Kombinationen zu testen, halte ich mich an das, was ich tatsächlich testen kann, und versuche, die Einschränkungen und Einschränkungen aufzuzeigen. Hier sind die Treiberfunktionen und deren Verwendung. Bitte schauen Sie sich die Beispiele an, um mehr Details und die verwendeten Funktionen in vollständigen Programmen zu sehen. Wenn Sie mit anderen Geschwindigkeiten arbeiten möchten, müssen Sie Konstanten in den Treibern anpassen. Senden Sie mir eine E-Mail, wenn Sie dabei Hilfe benötigen. Sie können auch einige Hinweise aus den Notizen zu den Atmel-Apps in den Links im Ressourcenschritt abrufen. USI_TWI_Master_Initialise()Diese Funktion initialisiert die USI-Hardware für den Betrieb im I2C-Modus. Rufen Sie es einmal zu Beginn Ihres Programms auf. Sie gibt void zurück und es sind keine Argumente vorhanden. USI_TWI_Get_State_Info()Diese Funktion gibt I2C-Fehlerinformationen zurück und wird verwendet, wenn während einer I2C-Transaktion ein Fehler aufgetreten ist. Da diese Funktion nur einen Fehlercode zurückliefert, verwende ich die Funktion TWI_Act_On_Failure_In_Last_Transmission(TWIerrorMsg) um eine Fehler-LED zu blinken. Die Fehlercodes sind in USI_TWI_Master.h definiert. So rufen Sie es auf:TWI_Act_On_Failure_In_Last_Transmission(USI_TWI_Get_State_Info())USI_TWI_Start_Read_Write()Diese Funktion wird verwendet, um einzelne Bytes in I2C-Geräte zu lesen und zu schreiben. Es wird auch verwendet, um mehrere Bytes zu schreiben. Zur Verwendung dieser Funktion sind 6 Schritte erforderlich.1) Deklarieren Sie in Ihrem Programm einen Nachrichtenpuffer, der die Slave-Adresse und das zu sendende oder zu empfangende Datenbyte enthält. unsigned char messageBuf (MESSAGEBUF_SIZE);2)Legen Sie die Slave-Adresse als erstes Byte in den Puffer. Verschieben Sie es ein Bit nach links und ODER im Read/Write-Bit. Beachten Sie, dass das Read/Write-Bit für einen Read 1 und für einen Write 0 ist. Dieses Beispiel ist für einen Read. messageBuf(0) = (TWI_targetSlaveAddress<<TWI_ADR_BITS) | (TRUE<<TWI_READ_BIT); 3) Beim Schreiben das zu schreibende Byte an der nächsten Stelle im Puffer ablegen. 4) Rufen Sie die Funktion USI_TWI_Start_Read_Write mit dem Nachrichtenpuffer und der Nachrichtengröße als Argumente auf.temp = USI_TWI_Start_Read_Write(messageBuf, 2);5)Die Der zurückgegebene Wert (in diesem Fall temp) kann getestet werden, um festzustellen, ob ein Fehler aufgetreten ist. Wenn dies der Fall ist, wird es wie oben beschrieben gehandhabt. Siehe Beispiele in den Programmen.6)Wenn ein Read angefordert wurde, befindet sich das gelesene Byte an der zweiten Stelle im Puffer. Wenn mehrere Bytes geschrieben werden sollen (z. B. in ein Speichergerät), kann dieselbe Routine verwendet werden. Das Einrichten des Puffers und das Aufrufen der Routine unterscheiden sich geringfügig. Das zweite Byte im Puffer ist die Startspeicheradresse, in die geschrieben werden soll. Die zu schreibenden Daten befinden sich in nachfolgenden Bytes. Die Nachrichtengröße ist die Größe, die alle gültigen Daten enthält. Wenn also 6 Byte geschrieben werden sollen, beträgt die Nachrichtengröße 8 (Slave-Adresse + Speicheradresse + 6 Byte Daten). eine Art Erinnerung. Die Verwendung dieser Routine ist der vorherigen Routine mit zwei Ausnahmen sehr ähnlich. Die Einstellung des Read/Write-Bits spielt keine Rolle. Der Aufruf dieser Routine führt immer zu einem Read-Vorgang. Die MessageSize sollte 2 plus die Anzahl der zu lesenden Bytes betragen. Wenn keine Fehler aufgetreten sind, werden die Daten ab der zweiten Position im Puffer gespeichert. Für den ATmega168:Clock Requirement:The Treiber sind für eine Taktrate von 4MHz für ATmega168 ausgelegt. Der Beispielcode zeigt, wie Sie diese Taktrate einstellen. Wenn Sie mit anderen Geschwindigkeiten arbeiten möchten, müssen Sie Konstanten in den Treibern anpassen. Senden Sie mir eine E-Mail, wenn Sie dies tun müssen. TWI_Master_Initialise()Diese Funktion initialisiert die TWI-Hardware für den Betrieb im I2C-Modus. Rufen Sie es einmal zu Beginn Ihres Programms auf. Es gibt void zurück und es gibt keine Argumente. Stellen Sie sicher, dass Sie Interrupts aktivieren, indem Sie nach der Initialisierung swi() aufrufen. TWI_Get_State_Info()Diese Funktion gibt I2C-Fehlerinformationen zurück und wird verwendet, wenn während einer I2C-Transaktion ein Fehler aufgetreten ist. Da diese Funktion nur einen Fehlercode zurückliefert, verwende ich die Funktion TWI_Act_On_Failure_In_Last_Transmission(TWIerrorMsg) um eine Fehler-LED zu blinken. Die Fehlercodes sind in TWI_Master.h definiert, werden jedoch zur Signalisierung an einer Fehler-LED modifiziert. Details finden Sie im Beispielcode. So rufen Sie es auf:TWI_Act_On_Failure_In_Last_Transmission(TWI_Get_State_Info())Beachten Sie, dass die Fehlerprüfung durchgeführt wird, indem sichergestellt wird, dass die I2C-Transaktion abgeschlossen ist (Funktion unten beschrieben) und dann ein Bit im globalen Statuswort getestet wird. TWI_Start_Read_Write()TWI_Start_Random_Read()These zwei Funktionen funktionieren mit wenigen Ausnahmen wie die entsprechenden oben beschriebenen Funktionen. Sie geben keine Fehlerwerte zurück. Gelesene Daten werden nicht in den Puffer übertragen. Dies geschieht mit der nachfolgend beschriebenen Funktion. Beim Aufruf von TWI_Start_Random_Read sollte die MessageSize die Anzahl der angeforderten Datenbytes plus eins sein, nicht zwei. Der I2C-Treiber für den ATmega168 ist Interrupt-gesteuert. Das heißt, die I2C-Transaktionen werden gestartet und dann unabhängig ausgeführt, während die Hauptroutine weiterläuft. Wenn die Hauptroutine Daten von einer von ihr gestarteten I2C-Transaktion benötigt, muss sie prüfen, ob die Daten verfügbar sind. Bei der Fehlerprüfung verhält es sich ähnlich. Die Hauptroutine muss sicherstellen, dass die I2C-Transaktion abgeschlossen ist, bevor sie auf Fehler überprüft. Die nächsten beiden Funktionen werden für diese Zwecke verwendet. TWI_Transceiver_Busy() Rufen Sie diese Funktion auf, um zu sehen, ob eine I2C-Transaktion abgeschlossen ist, bevor Sie auf Fehler prüfen. Die Beispielprogramme zeigen dies. TWI_Read_Data_From_Buffer() Rufen Sie diese Funktion auf, um Daten aus dem Empfangspuffer des I2C-Treibers in den Nachrichtenpuffer zu übertragen. Diese Funktion stellt sicher, dass die I2C-Transaktion abgeschlossen ist, bevor die Daten übertragen werden. Während von dieser Funktion ein Wert zurückgegeben wird, finde ich das direkte Überprüfen des Fehlerbits zuverlässiger. So rufen Sie es auf. Die Nachrichtengröße sollte um eins größer sein als die gewünschte Anzahl von Datenbits. Die Daten befinden sich in messageBuf ab dem zweiten Standort.temp = TWI_Read_Data_From_Buffer(messageBuf, messageSize);

Schritt 4: Lass uns bauen

Lass uns bauen!
Lass uns bauen!
Lass uns bauen!
Lass uns bauen!
Lass uns bauen!
Lass uns bauen!
Lass uns bauen!
Lass uns bauen!

Laden Sie zunächst die Datei I2C Schematics.zip herunter. Möglicherweise möchten Sie in Ihrem Arbeitsbereich einen I2C-Ordner erstellen, der die Schaltpläne und die Beispielprogrammdateien enthält. Entpacken Sie die Schaltpläne in dieses Verzeichnis. Sie finden einen Ordner namens I2C Schematics. Öffnen Sie die Datei namens tiny I2C.pdf. Dieses Schema zeigt das ATtiny2313 Ghetto Development System und den PCA8574A I/O Port Expander (mit dem großen gestrichelten Kästchen drumherum). Die Port-Expander-Schaltung ist auf einem Steckbrett aufgebaut. Schauen Sie sich die Fotos an, um zu sehen, wie diese Schaltungen aussehen. Sie sind wirklich ziemlich einfach. Der ATtiny2313-Teil des Schaltplans ist nur das Ghetto-Entwicklungssystem mit drei Blinklichtern (LED1, 2 und 3, plus R4, 5 und 6) und einem daran angeschlossenen Druckknopf (S1) plus einem zusätzliches Detail. Dieses Detail ist das Hinzufügen von Jumpern (JP4, 5 und 6), die entfernt werden können, um den Anschluss der I2C-Bus-SCL- und SDA-Leitungen zu ermöglichen. Zur Programmierung müssen die Jumper gesetzt und dann entfernt werden, damit SCL und SDA verbunden werden können. Die Fotos zeigen die Jumper an Ort und Stelle und entfernt. Die Platzierung dieser Jumper liegt bei Ihnen, Sie müssen sie nur in Ihr Ghetto-Entwicklungssystem stecken, wenn Sie den I2C-Bus verwenden möchten. Zur Programmierung muss der I2C-Bus abgeklemmt und die Jumper gesetzt werden. Beachten Sie, dass Sie sich wirklich nur um JP4 und JP6 für den I2C-Bus kümmern müssen. Setzen Sie JP5 ein, wenn Sie denken, dass Sie jemals den SPI-Bus verwenden möchten. Das Breadboarding des PCA8574A I/O Port Expander ist sehr einfach. Stellen Sie Vcc (+5 Volt) und Gnd (Masse) Anschlüsse bereit und verbinden Sie AD0, 1 und 2 mit Masse (macht die I2C Slave Adresse 38 hex). Dann 4 Blinker und 4 DIP-Schalter anschließen. (Wenn Sie keine DIP-Schalter haben, können Sie einfach Drähte verwenden. An Masse binden oder schweben lassen, um das Signal ein- oder auszuschalten.) Schließlich verbinden Sie die Pullup-Widerstände (R11 und 12) von SDA und SCL mit Vcc. Diese werden als 3,3K angezeigt, aber jeder Wert von 1,8K bis 5,1K sollte funktionieren (vielleicht bis zu 10K, aber das habe ich nicht ausprobiert). Sobald Sie den ATtiny2313 programmiert haben, können Sie die Jumper entfernen und SDA und SCL zum Testen anschließen. Jetzt für den ATmega168. Der einzige Nachteil hierbei ist, dass Sie möglicherweise kein Ghetto-Entwicklungssystem für diesen Prozessor erstellt haben. Wenn dies der Fall ist, zeigt Ihnen der von mir bereitgestellte Schaltplan (MEGA I2C.pdf) wie. Dies ist nur eine Permutation der ATtiny2313-Version. Wenn Sie im Voraus planen, können Sie sicherstellen, dass Ihr Programmierkabel zu beiden Systemen passt. Der Hauptunterschied ist die Zugabe von C2 und C3. Siehe die Bilder für die Platzierung dieser, sie sollten sehr nah am Chip sein; einer von ihnen ist tatsächlich unter dem Chip. Diese helfen insbesondere dabei, Rauschen aus dem Analog-Digital-Wandler herauszuhalten. Sie müssen die Jumper nicht stecken, es sei denn, Sie möchten den SPI-Bus verwenden, da sie für den I2C-Bus auf diesem Chip nicht benötigt werden. Beachten Sie, dass das Steckbrett PCA8754A unverändert bleibt. Sie schließen einfach SDA und SCL an und schon kann es losgehen! Einfach, oder?

Schritt 5: Lassen Sie uns programmieren und testen

Lassen Sie uns codieren und testen!
Lassen Sie uns codieren und testen!
Lassen Sie uns codieren und testen!
Lassen Sie uns codieren und testen!
Lassen Sie uns codieren und testen!
Lassen Sie uns codieren und testen!

Es ist an der Zeit, die Treiber und die Beispielprogramme zu erstellen. Wir beginnen mit dem ATtiny2313 und dem PCA8574A Steckbrett, das wir gerade gebaut haben. Laden Sie die Datei I2C.zip in Ihr I2C-Arbeitsverzeichnis herunter und entpacken Sie diese. Sie haben einen neuen Ordner namens I2C. Darin finden Sie USI I2C (für ATtiny2313) und TWI I2C (für ATmega168). In USI I2C finden Sie den Ordner I_O Port. Dieser Ordner enthält den Code für unser erstes Beispielprogramm und die USI I2C-Treiber. Kompilieren und laden Sie den Code mit WinAVR in den ATtiny2313. Atmen Sie tief ein und schalten Sie den Strom ein. Folgendes ist zu erwarten: Beim Einschalten blinkt LED 1 an Port PD6 des ATtiny2313 zweimal. Nichts anderes passiert, bis Sie die Taste (S1) drücken. Jedes Mal, wenn die Taste gedrückt wird, werden die Schalter gelesen und ihre Einstellung wird auf den LEDs angezeigt, die an den PCA8574A angeschlossen sind. Ändern Sie den Wert der Schalter, drücken Sie die Taste und die LEDs sollten sich ändern. Machen Sie so weiter, bis Sie den Nervenkitzel überwunden haben, zu sehen, wie es funktioniert. Wenn (Gott bewahre!) die Dinge nicht wie erwartet funktionieren, überprüfen Sie sorgfältig Ihre Verkabelung. I2C-Fehler werden durch Blinken von LED3 (PD4) signalisiert und bedeuten wahrscheinlich, dass Sie überprüfen müssen, ob SDA und SCL mit den richtigen Pins verbunden und richtig hochgezogen sind. Wenn die Dinge immer noch nicht funktionieren, lesen Sie den Rest dieses Abschnitts, um mehr über das Debuggen zu erfahren. Gehen Sie nun zurück und sehen wir uns den Code an. Öffnen Sie die Datei USI_I2C_Port.c. Dies ist der Code für das Beispielprogramm. (USI_TWI_Master.c und USI_TWI_Master.h enthalten die Treiber - Sie können sie ignorieren, es sei denn, Sie sind neugierig.) Verwenden Sie das Beispiel, um Ihre eigenen I2C-Anwendungen zu führen. Meistens zeigt Ihnen das Programm, wie Sie die I2C-Treiber initialisieren und verwenden, einschließlich der Einstellungen die Slave-Adresse und den Rest des Nachrichtenpuffers hoch und die Daten daraus holen. Sie werden auch sehen, wie ich die Taste entprelle und die while-Schleife einrichte. Einige Details des Programms sind erwähnenswert. Beachten Sie, dass die Daten von den Switches invertiert werden, bevor sie in die LEDs des Port Expanders geschrieben werden. Beachten Sie auch, dass die Eingangsports am Port Expander als High geschrieben werden müssen, damit sie ordnungsgemäß funktionieren. Diese Details sind im Datenblatt PCA8574A beschrieben. Lesen Sie die Datenblätter immer sorgfältig durch! Interessanter ist die Verwendung von bedingtem Debugging. Am Anfang der Programmdatei steht die Anweisung //#define DEBUG und im gesamten Code sind #ifdef DEBUG-Anweisungen verstreut. Solange DEBUG nicht definiert ist (die beiden Schrägstriche machen die Zeile zu einem Kommentar und verhindern, dass sie definiert wird), wird der Code innerhalb der Anweisungen #ifdef bis #endif nicht kompiliert. Aber wenn die Dinge nicht wie erwartet funktionieren, kompilieren und laden Sie den Code mit #define DEBUG unkommentiert neu. Sie erhalten viel mehr Blinken auf den LEDs, die Sie decodieren können, um die Ausführung Ihres Programms zu verfolgen und Ihnen zu helfen, genau zu finden, wo etwas schief geht. Tatsächlich empfehle ich Ihnen, dies zu versuchen, nur um zu sehen, was passiert. Was Sie sehen werden, ist, dass LED 2 (auf PD5) blinkt, während die Ausführung des Programms fortschreitet. Der von den Schaltern gelesene Wert blinkt auf LED 1 (PD6), bevor er auf den Port-Expander-LEDs angezeigt wird. Sie sollten in der Lage sein, das Programm während seiner Ausführung mit Hilfe dieser LEDs zu verfolgen. Als nächstes werden wir mit dem ATmega168 arbeiten; Überspringen Sie diesen Abschnitt, wenn Sie nur am ATtiny2313 interessiert sind. Immer noch bei mir? Gut. Wechseln Sie in den Ordner TWI_I2C, ändern Sie Ihr Arbeitsverzeichnis in IO_Port und kompilieren und laden Sie TWI_I2C_Port.c in den ATmega168. Trennen Sie die SDA- und SCL-Leitungen vom ATtiny2313 und verbinden Sie sie mit ATmega168. Schließen Sie Strom und Masse an und schalten Sie ein. Die Bedienung sollte gleich sein! Spielen Sie, bis der Nervenkitzel nachlässt, dann schauen wir uns den Code an. Öffnen Sie TWI_I2C_Port.c. Der Code ist mit Ausnahme der Fehlerbehandlung und der Anpassung von Interrupt-gesteuerten Treibern nahezu identisch. Hier sind die Unterschiede: Beachten Sie, dass die Uhr auf 4 MHz eingestellt sein muss, damit der I2C-Bus ordnungsgemäß funktioniert. Das sei(); -Anweisung schaltet Interrupts nach der Initialisierung der I2C-Treiber ein. Um auf Fehler zu prüfen, wird ein bestimmtes Statusbit getestet. Beim Lesen muss die Funktion TWI_Read_Data_From_Buffer aufgerufen werden, um die gelesenen Daten in den Nachrichtenpuffer zu übertragen. Während eines Schreibvorgangs muss while (TWI_Transceiver_Busy()) verwendet werden, um sicherzustellen, dass die Übertragung abgeschlossen ist, bevor auf Fehler überprüft wird. Diese letzten beiden Funktionen sind oben in der Beschreibung der Treiber beschrieben. Ansonsten ist der Code so ziemlich der gleiche wie beim ATtiny2313. DEBUG funktioniert genauso, wenn Sie damit experimentieren möchten.

Schritt 6: Verwenden des I2C-Speichers

Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher
Verwenden von I2C-Speicher

Nachdem wir nun gelernt haben, den I2C-Bus zum Lesen und Schreiben eines I/O-Port-Expanders zu verwenden, gehen wir zur Verwendung von I2C-Speichern über, sowohl RAM als auch EEPROM. Der Hauptunterschied besteht darin, dass mit einem einzigen I2C-Befehl mehrere Bytes in Speicher gelesen oder geschrieben werden können. Um sich auf diese Experimente vorzubereiten, müssen wir die Hardware leicht modifizieren und ein paar neue Schaltkreise auf dem Steckbrett bauen. Behalten Sie die Port-Expander-Schaltung bei, da wir sie verwenden werden, um einige Speicherwerte anzuzeigen. Entfernen Sie die DIP-Schalter vom PCA8574A und setzen Sie Blinklichter auf diese Pins. Wenn Sie nicht genügend Blinklichter haben, verschieben Sie die auf P4 bis P7 auf P0 bis P3. (Die anzuzeigenden Werte sind klein genug.) Sehen Sie sich nun den Schaltplan I2C Ram.pdf an und schließen Sie den PCF8570 auf dem Steckbrett an. Schauen Sie sich auch das Bild an. Achten Sie darauf, Pin 7 an Vcc zu binden. Führen Sie Kabel für SDA und SCL vom PCA8574A aus. Es sind keine zusätzlichen Pull-Up-Widerstände erforderlich. Wenn Sie auch am EEPROM interessiert sind, bauen Sie diese Schaltung auch mit I2C EEPROM.pdf für den 24C16 auf, aber seien Sie gewarnt, dass das Beispiel den ATmega168 verwendet. Diese Schaltung ist wirklich einfach. Wie oben diskutiert, sollten die Adressbits ignoriert werden. Einfach Strom und Masse anschließen. Verbinden Sie SDA und SCL noch nicht, da wir noch nicht mit dem Ram experimentiert haben. Wir beginnen unsere Speicherexperimente mit dem ATtiny2313, der mit dem PCA8574A Port Expander und dem PCF8570 Ram verbunden ist. Das Programm schreibt einige Zahlen in den RAM, liest sie dann zurück und zeigt sie auf dem Port Expander an. Ändern Sie Ihr Arbeitsverzeichnis in RAM unter USI I2C. Verwenden Sie die Make-Datei, um USI_I2C_RAM.c zu kompilieren und herunterzuladen. Beachten Sie, dass die I2C-Treiberdateien mit denen identisch sind, die wir zuvor verwendet haben. Schließen Sie die Stromversorgung an und Sie sollten ein einzelnes Blinken auf LED 1 (PD6) sehen. Die Daten werden in die ersten 4 Byte des Speichers geschrieben. Drücken Sie die Taste und zwei Bytes werden zurückgelesen und angezeigt. Sie sollten eine LED am Port Expander (P0) sehen, eine Pause von zwei Sekunden und dann zwei LEDs (P0 und P1). Weitere zwei Sekunden Pause und die LEDs sollten ausgehen. Drücken Sie die Taste erneut, um die Sequenz von vorne zu beginnen. Das Debuggen ähnelt der oben beschriebenen Methode. Werfen wir einen Blick auf den Code. Öffnen Sie USI_I2C_RAM.c. Es sollte dem vorherigen Code ziemlich ähnlich sein. Die Hauptunterschiede sind die Details des Lesens und Schreibens von Speicher. Sehen Sie sich an, wie der Nachrichtenpuffer vor dem Aufruf geladen wird, der tatsächlich den Schreibvorgang durchführt. Das erste Byte ist die Slave-Adresse mit entsprechend gesetztem Lese-/Schreibbit. Aber das nächste Byte ist die Speicheradresse, an der mit dem Schreiben von Daten begonnen wird. Dann kommen die eigentlichen Datenbytes, die ab der angegebenen Adresse sequentiell in den Speicher geladen werden. Als Nachrichtengröße geben wir 6 an. Wir beginnen also bei Adresse 00 zu schreiben und schreiben die Werte 01, 03, 02 und 06 in die Speicherplätze 00 bis 03. Um die Daten aus dem Speicher zurückzulesen, müssen wir die Funktion USI_TWI_Start_Random_Read verwenden. Der Nachrichtenpuffer erhält im ersten Byte die Slave-Adresse und im zweiten Byte die Startadresse. Rufen Sie dann die Funktion auf, wobei die Nachrichtengröße auf die Anzahl der zu lesenden Bytes plus 2 eingestellt ist. Beachten Sie, dass das Lese-/Schreibbit keine Rolle spielt, da unabhängig davon gelesen wird. Die zurückgegebenen Daten beginnen an der zweiten Stelle im Nachrichtenpuffer. Sobald die Daten eingelesen sind, werden sie für die Anzeige auf dem Port Expander invertiert und mit einer Pause zwischen den Werten byteweise in diesen geschrieben. Schließlich werden die Port-Expander-LEDs ausgeschaltet. Die Schreibvorgänge an den Port Expander sind identisch mit denen in den vorherigen Beispielen. Zum Spaß können Sie die #define DEBUG-Anweisung wie oben auskommentieren und viele blinkende LEDs sehen. Nach einem weiteren erfolgreichen Experiment vor Aufregung gespült, gehen wir zum ATmega168 und einem EEPROM über. Ändern Sie Ihr Arbeitsverzeichnis in EEPROM unter TWI I2C. Verwenden Sie die Make-Datei, um TWI_I2C_EEPROM.c zu kompilieren und herunterzuladen. Beachten Sie, dass die I2C-Treiberdateien mit denen identisch sind, die wir zuvor für den PCA8574A verwendet haben. Um das Programm zu testen, trennen Sie den ATtiny2313 und schließen Sie den ATmega168. Lassen Sie den I2C-Bus an den Ram angeschlossen und schalten Sie ihn ein. Die Ergebnisse sind anders, da wir jetzt mehr Daten schreiben und lesen. LED 1 am PD7 sollte bei der Initialisierung blinken. Drücken Sie die Taste und die Daten werden aus dem Speicher zurückgelesen und angezeigt. Die LEDs am PCA8574 sollten in der folgenden Reihenfolge blinken: P1, P0 & P2, (alle aus), P0 & P1, P1 & P2. Schließlich sollten die Port-LEDs alle erlöschen. Drücken Sie die Taste erneut, um dies zu wiederholen. Oh, aber warten Sie, sagen Sie. Ist dieses Programm nicht für das EEPROM? Da wir auf ein Speichergerät mit derselben I2C-Adresse zugreifen, funktioniert dasselbe Programm sowohl für den Ram als auch für das EEPROM. Schalten Sie das Gerät aus und verschieben Sie SDA und SCL vom RAM in das EEPROM und führen Sie das Programm erneut aus. Es sollte genau gleich funktionieren. Beachten Sie, dass EEPROM und Ram nicht gleichzeitig an den I2C-Bus angeschlossen werden können, da sie dieselbe Adresse haben. (Die Schlauen unter euch ziehen vielleicht in Erwägung, die programmierbaren Adressbits auf dem Ram zu ändern, aber das wird immer noch nicht funktionieren. Der 24C16 verwendet den gesamten Adressblock, der für den Ram programmiert werden kann.)OK, schauen wir uns dieses letzte Programm an. Öffnen Sie TWI_I2C_EEPROM.c. Als erstes fällt auf, dass ich angegeben habe, wie das komplette 24C16-EEPROM zu adressieren ist. Es kann in 256-Byte-Chunks an 8 verschiedenen I2C-Slave-Adressen zugegriffen werden. Sehen Sie, wie MEMORY_ADDR als Startadresse bei 50 hexadezimal definiert ist; Deshalb hat der Ram funktioniert. Wenn Sie auf andere Blöcke des 24C16 zugreifen möchten, verwenden Sie die anderen Adressen wie ich angegeben habe. Sehen Sie sich an, wie ich das Schreiben in den Speicher einrichte. Zuerst wird die Slave-Adresse mit gesetztem Lese-/Schreibbit in den Puffer geschrieben, dann die Startadresse 00, dann 16 Byte Daten. Die Funktion TWI_Start_Read_Write wird aufgerufen, um die Daten (wie zuvor) mit der Nachrichtengröße 18 zu schreiben. Wenn die Schaltfläche gedrückt wird, verwenden wir TWI_Start_Random_Read und TWI_Read_Data_From_Buffer, um die Daten zurückzulesen. Jedes dritte Byte wird auf den Port Expander LEDs angezeigt. Schließlich werden die LEDs ausgeschaltet, um auf den nächsten Tastendruck zu warten. Sie fragen sich vielleicht, warum ich 16 Byte geschrieben habe. Wenn Sie das Datenblatt aufmerksam lesen, werden Sie feststellen, dass der 24C16 immer dann einen Schreibzyklus durchführt, wenn er 16 Byte empfängt, auch wenn mehr Byte gesendet werden. Das schien also eine schöne Nummer zu sein. Wenn Sie diesen Wert erhöhen möchten, müssen Sie die Größe von MESSAGEBUF_SIZE ändern. Sie müssen auch den Wert TWI_BUFFER_SIZE in TWI_Master.h ändern. Dies liegt daran, dass der Treiber die Daten aus dem Nachrichtenpuffer kopiert, um sie von der Interrupt-Service-Routine zu verwenden. Herzliche Glückwünsche! Jetzt können Sie den I2C-Bus in Ihren eigenen Projekten verwenden!

Schritt 7: Webressourcen

Hier sind die Links zu den Datenblättern der für die Experimente verwendeten Teile. Sie sollten sich diese unbedingt besorgen, wenn Sie nichts anderes bekommen. Port ExpanderRamEEPROMAls Schöpfer von I2C bietet NXP (Philips) eine Menge großartiger Dinge. (Sie verwenden gerne eckige Klammern in ihren URLs, daher kann ich sie hier nicht richtig einfügen. Entschuldigung.] Um zum I2C-Bereich zu gelangen, wählen Sie Schnittstelle aus der Produktliste. Sie können dann auf ihre I2C-Site gelangen und Zugriff auf alle angebotenen Datenblätter und App-Hinweise. Die Beschreibung des I2C-Busses und insbesondere die technischen Details finden Sie hier. Holen Sie sich die ATtiny2313- und ATmega168-Datenblätter (Datenbücher?) von Atmel. Die Atmel-Anwendungshinweise finden Sie hier. Sehen Sie sich AVR310 und AVR315 an. Besorgen Sie sich auch den Code. Schauen Sie hier nach viel mehr I2C-Zeug.

Schritt 8: Hinweise für Geeks

Für den wahren Geek, der die Details wissen möchte, sind hier einige Dinge zu beachten, wenn Sie sich die Atmel Apps Notes und den Treibercode ansehen: - Die Methode zur Adressierung und Steuerung eines I2C-Geräts ist nicht Teil der Spezifikation! Abgesehen von der Slave-Adresse und dem Lese-/Schreibbit werden Befehle, Modi usw. nicht angegeben und sind spezifisch für ein bestimmtes Gerät. Um dies deutlich zu machen, beachten Sie, dass das im Atmel-Beispiel verwendete Schema nur für dieses Beispiel gilt und ziemlich vom Standard abweicht. - Die USI-Implementierung unterscheidet sich in einigen wichtigen Punkten von der TWI-Implementierung. + Bei USI erfolgt die Taktung durch Software; bei TWI wird es von einem Bitratengenerator bereitgestellt. + Die USI-Methode verwendet keine Interrupts; der TWI tut es. Dies macht einen gewissen Sinn, da die Mega-Familie (mit TWI) viele andere Dinge tun könnte und nicht durch I2C-Transfers belastet werden sollte. Eine Interrupt-gesteuerte Version für USI ist sicherlich möglich, es ist nur in diesem Instructable nicht implementiert. + Die USI-Hardware ist nicht für I2C optimiert und kann nur 8-Bit-Übertragungen verarbeiten. Dies bedeutet, dass zwei Übertragungen erforderlich sind, um das neunte Bit zu senden (entweder NACK oder ACK). Die TWI-Hardware übernimmt dies automatisch. Dies macht die Implementierung des USI-Treibers etwas komplizierter. + Die Fehlererkennung für das TWI wird in Hardware gehandhabt. Die USI erfordert die Handhabung in Software, was die Dinge etwas verkompliziert. + Die TWI-Hardware steuert die Konfiguration des Ports direkt. Die USI-Hardware erfordert, dass die Portbits konfiguriert werden, bevor der Port verwendet werden kann. Sie werden dies in der Master_Initialize-Routine für die USI sehen. Atmel behauptet, dass es möglich ist, AVR-Port-Pullups für die I2C-Bus-Pullups zu verwenden. Ich habe keinen Weg gefunden, diesen Ansatz zum Laufen zu bringen. Die Verwendung von zwei externen Widerständen scheint ein ziemlich einfaches Schema zu sein, daher habe ich nicht viel Zeit damit verbracht.

Empfohlen: