ATMEGA328 I2C/TWI und OLED-Display

Ich habe ATMEGA328P und I2C-Display (SSD1306). Ich versuche, einfach ein einzelnes Pixel mit möglichst wenig Code auf einen Bildschirm zu bringen, damit ich daraus lernen kann.

Ich konnte dies mit Himbeere mit dem folgenden Code tun:

// gcc ssd1306.c -lwiringPi -o ssd1306

#include <stdio.h>
#include <string.h>
#include <wiringPiI2C.h>

#define WIDTH 128
#define HEIGHT 64

int buffer[ WIDTH * HEIGHT / 8 ];
int i2cd;

void ssd1306_command(unsigned int c)
{
    unsigned int control = 0x00;
    wiringPiI2CWriteReg8( i2cd, control, c );
}

void ssd1306_byte(unsigned int c)
{
    unsigned int control = 0x40;
    wiringPiI2CWriteReg8( i2cd, control, c );
}

void drawPixel( int x, int y, unsigned int color )
{
    switch (color) 
    {
        case 1: // white
            buffer[x + ( y / 8 ) * WIDTH ] = 1;

            break;
        case 0: // black
            buffer[x + ( y / 8 ) * WIDTH ] = 0;
            break;
    }
}

void init()
{
    i2cd = wiringPiI2CSetup( 0x3C ); // address

    ssd1306_command(0xAE);          // 0xAE // display off
    ssd1306_command(0xD5);          // 0xD5 // set display clock division
    ssd1306_command(0x80);          // the suggested ratio 0x80
    ssd1306_command(0xA8);          // 0xA8 set multiplex
    ssd1306_command(63);            // set height
    ssd1306_command(0xD3);          // set display offset
    ssd1306_command(0x0);           // no offset
    ssd1306_command(64);            // line #0 setstartline
    ssd1306_command(0x8D);          // 0x8D // chargepump
    ssd1306_command(0x14);
    ssd1306_command(0x20);          // memory mode
    ssd1306_command(0x00);          // 0x0 act like ks0108
    ssd1306_command(161);           // segremap
    ssd1306_command(0xC8);          // comscandec
    ssd1306_command(0xDA);          // 0xDA set com pins
    ssd1306_command(0x12);
    ssd1306_command(0x81);          // 0x81 // set contract
    ssd1306_command(0xCF);
    ssd1306_command(0xD9);          // 0xd9 set pre-charge
    ssd1306_command(0xF1);
    ssd1306_command(0xDB);          // SSD1306_SETVCOMDETECT
    ssd1306_command(0x40);
    ssd1306_command(0xA4);          // 0xA4 // display all on resume
    ssd1306_command(0xA6);          // 0xA6 // normal display
    ssd1306_command(0x2E);          // deactivate scroll
    ssd1306_command(0xAF);          // --turn on oled panel
}

void renderBuffer(void)
{
    ssd1306_command(0x21);          // column address
    ssd1306_command(0);             // Column start address (0 = reset)
    ssd1306_command(127);           // Column end address (127 
    ssd1306_command(0x22);          // page address
    ssd1306_command(0x00);          // Page start address (0 = reset)
    ssd1306_command(7);             // Page end address

    int i;

    for (i = 0; i < ( 128 * 64 / 8 ); i++) 
    {
        ssd1306_byte( buffer[i] ); 
    }
}

void clearBuffer(void)
{
    memset( buffer, 0, ( 128 * 64 / 8 ) * sizeof( int ) );
}

void main() 
{
    init();
    clearBuffer();
    drawPixel( 10, 10, 1 );
    renderBuffer();
}

Bisher funktioniert alles perfekt, wenn ich versuche, dasselbe mit i2c_master.c für mein ATMEGA zu tun: https://github.com/g4lvanix/rgbtime/tree/master/firmware

#ifndef F_CPU
    #define F_CPU 8000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include "i2c_master.c"
#include "i2c_master.h"

#define  SSD1306_ADDRESS 0x3C

void initDisplay()
{
    i2c_start(SSD1306_ADDRESS);

    i2c_write(0xAE);          // 0xAE // display off
    i2c_write(0xD5);          // 0xD5 // set display clock division
    i2c_write(0x80);          // the suggested ratio 0x80
    i2c_write(0xA8);          // 0xA8 set multiplex
    i2c_write(63);            // set height
    i2c_write(0xD3);          // set display offset
    i2c_write(0x0);           // no offset
    i2c_write(64);            // line #0 setstartline
    i2c_write(0x8D);          // 0x8D // chargepump
    i2c_write(0x14);
    i2c_write(0x20);          // memory mode
    i2c_write(0x00);          // 0x0 act like ks0108
    i2c_write(161);           // segremap
    i2c_write(0xC8);          // comscandec
    i2c_write(0xDA);          // 0xDA set com pins
    i2c_write(0x12);
    i2c_write(0x81);          // 0x81 // set contract
    i2c_write(0xCF);
    i2c_write(0xD9);          // 0xd9 set pre-charge
    i2c_write(0xF1);
    i2c_write(0xDB);          // SSD1306_SETVCOMDETECT
    i2c_write(0x40);
    i2c_write(0xA4);          // 0xA4 // display all on resume
    i2c_write(0xA6);          // 0xA6 // normal display
    i2c_write(0x2E);          // deactivate scroll
    i2c_write(0xAF);          // --turn on oled panel

    i2c_stop();
}

void drawPixel()
{
    i2c_start( SSD1306_ADDRESS );

    i2c_write(0x21);          // column address
    i2c_write(0);             // Column start address (0 = reset)
    i2c_write(127);           // Column end address (127 
    i2c_write(0x22);          // page address
    i2c_write(0x00);          // Page start address (0 = reset)
    i2c_write(7);             // Page end address

    int i;

    int z=0;

    for ( i = 0; i < ( 128 * 64 / 8 ); i++ ) 
    {
        if ( z == 0 )
        {
            i2c_write( 0xff ); 
            z = 1;
        }
        else
        {
            i2c_write( 0x00 ); 
            z = 0;
        }
    }

    i2c_stop();
}

int main(void){

    i2c_init();
    initDisplay();
    drawPixel();

    return 0;
}

aber das Pixel wird nicht gezeichnet..

Ich verwende die folgenden Sicherungseinstellungen (ich verwende 8 MHz intern) lfuse:w:0xe2:m -U hfuse:w:0xd9:m

Ich habe folgende Hardwarekonfiguration:

-ATMEGA328P connected to a 4.0v power source
-ADC5 (SCL) connected to OLED's SCL (with additional line to VCC with 15k resistor in between) 
-ADC4 (SDA) connected to OLED's SDA (with additional line to VCC with 15k resistor in between)

Ich habe auch versucht, Widerstände und die "zusätzliche vcc-Verbindung" zu entfernen, und sie genauso angeschlossen wie Himbeere, aber es hat keinen Unterschied gemacht. Irgendwelche Hinweise was ich falsch mache? Ich stecke seit Tagen daran fest. Danke!

Weitere Informationen:

(a) Bitte liefern Sie ein Foto der H/W.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

(b) Bitte liefern Sie das Datenblatt für das OLED-Modul.

https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

(c) Haben Sie gemessen, ob das OLED-Modul I2C-Pullups eingebaut und aktiviert hat, oder sind Ihre 15k-Pullups die einzigen auf dem Bus?

Ich glaube, es gibt keine eingebauten Klimmzüge. Hier ein Zitat aus dem Datenblatt:

Both the data line (SDA) and the clock line (SCL) should be pulled up by external resistors

(d) Haben Sie Zugang zu einem Oszilloskop und Erfahrung damit?

Leider habe ich kein Oszilloskop...

(e) Wenn ja, können Sie Spuren bereitstellen, die den Anstieg und Abfall einer kleinen Stichprobe beider I2C-Signale zeigen?

-

(f) Obwohl in diesem Stadium weniger nützlich als ein Oszilloskop, haben Sie Zugang zu einem Logikanalysator, auch zu einem billigen?

Leider habe ich auch keinen Logikanalysator..

Ich würde damit beginnen, den I2C-Bus auf Ihrem "nicht funktionierenden" ATMega328-Setup auf Hardwareebene zu überprüfen : (a) Bitte liefern Sie ein Foto der Hardware. (b) Bitte liefern Sie das Datenblatt für das OLED-Modul. (c) Haben Sie gemessen, ob das OLED-Modul I2C-Pullups eingebaut und aktiviert hat, oder sind Ihre 15k-Pullups die einzigen auf dem Bus? (d) Haben Sie Zugang zu einem Oszilloskop und Erfahrung damit? (e) Wenn ja, können Sie Spuren bereitstellen, die den Anstieg und Abfall einer kleinen Stichprobe beider I2C-Signale zeigen? (f) Obwohl in diesem Stadium weniger nützlich als ein Oszilloskop, haben Sie Zugang zu einem Logikanalysator, auch zu einem billigen?
@SamGibson Vielen Dank für die Antwort! Ich habe meine Frage mit den von Ihnen angeforderten zusätzlichen Informationen aktualisiert.
Danke für das Update. Leider ist dieser Link das Datenblatt für den Controller , nicht das Modul - dieser Controller wird auf verschiedenen Modulen verwendet und kann uns nicht sagen, ob sich I2C-Widerstände auf dem Modul befinden . Ich habe auf der Adafruit-Website nachgesehen (basierend auf der URL für dieses Datenblatt), aber ich habe kein OLED-I2C-Modul mit der gleichen Pinbelegung gefunden . Daher kann ich keine Informationen zu dem Modul überprüfen , da es noch nicht identifiziert ist. Ohne diese Informationen, noch einen Scope oder LA kann ich nur vorschlagen, Klimmzüge mit niedrigeren Werten auszuprobieren, zB 5k6 oder sogar 2k2. (FYI, das RPi hat 1k8 I2C-Klimmzüge an Bord.)
[Fortsetzung] Ohne Scope oder LA, um sein Verhalten zu sehen, wird es für Sie schwierig sein, den I2C-Betrieb auf der Basisebene zu bestätigen. Der Grund für meinen Vorschlag, I2C-Klimmzüge niedriger zu machen, ist, dass Ihre 15k ziemlich hoch sind. Da Ihre Kabel kurz sind (geringe Buskapazität), könnten 15 k funktionieren (abhängig von den genauen Slave-Anforderungen), aber es ist höher, als ich verwenden würde. Wie ich bereits erwähnt habe, verfügt RPi über integrierte 1k8-I2C-Pullups, was erklären könnte, warum Ihr RPi-Setup immer noch ohne externe Pullups funktionierte (obwohl das Modul selbst möglicherweise auch Pullups hat). Wenn mir noch etwas einfällt, werde ich einen weiteren Kommentar hinzufügen.
@SamGibson vielen Dank für die Antwort. Ich werde versuchen, Klimmzüge auf niedrigerem Niveau zu verwenden, wie Sie vorgeschlagen haben. Es tut mir leid, ich bin ein bisschen verloren auf Modul vs. Controller. Soweit ich verstanden habe, ist SSD1306 eine kleinere Komponente, die an dem chinesischen "Modul" angebracht ist, das ich habe, richtig? Ich habe bei ebay nachgesehen, aber ich kann keinen Link zu Datenblättern zu diesen finden. Wenn es einen Hinweis gibt, heißt es "0.96" I2C IIC SPI Serial 128X64 White OLED", danke für deine Zeit!
"SSD1306 ist eine kleinere Komponente, die an dem chinesischen 'Modul' angebracht ist, das ich habe, richtig?" Ja. Komponenten wie Pull-up-Widerstände hätten dem Modul hinzugefügt werden können und würden daher nicht im Datenblatt für den SSD1306- Controller erwähnt . Persönlich würde ich einfach den PCB-Traces des Moduls von den SCL- und SDA-Pins folgen, um zu sehen, ob I2C-Pull-ups hinzugefügt wurden. Bei einer schnellen Suche habe ich bei Ebay mehrere Module gefunden, die zu diesem "Titel" passen. Die meisten hatten jedoch einen (teilweise Kauderwelsch) Kommentar, dass seine I2C-Schnittstelle keine ACKs sendet und daher eine spezielle I2C-Bibliothek benötigt - aber Links zu dieser Bibliothek sind tot :-(
@SamGibson oh verdammt, sieht so aus, als würde es ohne Oszilloskop / Logikanalysator wie ein Schießen im Dunkeln sein. Ich glaube, ich könnte die fehlende Bestätigung des Slaves ignorieren, aber ehrlich gesagt verstehe ich nicht, wie der Code in der I2C-Bibliothek funktioniert. Ich denke, ich habe keine andere Wahl, als meinen eigenen Code zu studieren und zu schreiben, um den Prozess gründlich zu verstehen.
@SamGibson (Fortsetzung) Ich werde versuchen, ein einfacheres Gerät (MPU-6050) anzuschließen und versuchen, es zum Laufen zu bringen. Ich versuche im Grunde, TWI dazu zu bringen, mit jedem Sensor zu arbeiten, aber der ultimative Ruhm wäre dieses Display gewesen. Wenn ich ein anderes Gerät zum Laufen bringe, ist es einfacher, dies ohne zusätzliche Tools weiter zu debuggen, und ich werde mich mit den Ergebnissen bei Ihnen melden, da ich mir jetzt nicht ganz sicher bin, ob die von mir verwendete I2C-Bibliothek funktioniert Code an erster Stelle, selbst für eine nicht defekte I2C-Implementierung. Danke!
@SamGibson Ich bin halbwegs dabei, die OLED mit ATMEGA zum Laufen zu bringen. Das Problem, mit dem ich jetzt konfrontiert bin, ist, dass ich aus irgendeinem Grund ein sehr seltsames und starkes Flackern bekomme, obwohl ich verschiedene Werte für die Pull-Ons ausprobiert habe, aber das hat nichts geändert. Das Flackern tritt sogar auf, wenn ich nichts „zeichne“. Alle Befehle wie Kontrast ändern usw. funktionieren einwandfrei. Irgendwelche Hinweise, warum dies passieren könnte? Danke!
Ich empfehle, sich einen Logic Sniffer zu besorgen – er hat mir beim Debuggen der I2C-Kommunikation viel erspart. Es ist der billigste richtige Logikanalysator, den ich finden konnte.

Antworten (3)

Das erste Byte, das in einer I2C-Transaktion übertragen wird, ist die Slave-Adresse (7 Bit) plus das einzelne Lese-/Schreibbit. Wenn die Slave-Adresse also 0x3c ist, wird als erstes Byte Folgendes übertragen:

0x78  // for starting a write transaction
0x79  // for starting a read transaction

Das ist 0x3c um 1 Bit verschoben plus - für Lesetransaktionen - das gesetzte LSB.

Die Raspberry-Bibliothek scheint dies automatisch zu handhaben. Die ATMEGA328P-Bibliothek scheint zu erfordern, dass Sie dies manuell tun.

Verwenden Sie also 0x78als Adresse und versuchen Sie es erneut.

Aktualisieren

Es gibt zusätzliche Probleme in Ihrem Code: Sie müssen dem Display-Controller angeben, ob Sie Befehle (und Befehlsparameter) oder Daten senden. Grundsätzlich müssen Sie 0x80 vor jedem Befehlsbyte und 0x40 vor dem Senden von Daten voranstellen. Sobald Sie 0x40 verwenden, werden alle Bytes bis zur Stoppbedingung als Datenbytes behandelt. Siehe Abbildung 8-7 und Kapitel 8.1.5.2 lit. 5 im Datenblatt.

Deshalb gibt es ssd1306_commandund ssd1306_byteim Raspberry-Code. (Offensichtlich funktioniert auch 0x00 anstelle von 0x80.) Sie müssen also 0x80 vor jedem Initialisierungsbyte sowie vor den Adressbefehlen beim Rendern eines Puffers hinzufügen. Und Sie müssen 0x40 hinzufügen, bevor Sie das erste Datenbyte senden.

Beachten Sie, dass Sie auch bei den Transaktionen einen Unterschied haben. (Eine Transaktion beginnt mit der START-Bedingung und endet mit der STOP-Bedingung.) Im Raspberry-Code wird jedes Befehlsbyte und jedes Datenbyte in einer separaten Transaktion gesendet. Im ATMEGA328P-Code kombinieren Sie es zu weit weniger, aber größeren Transaktionen. Beide Ansätze funktionieren. Letzteres ist effizienter. Dies kann für einige geringfügige Unterschiede wie 0x00 vs. 0x80 relevant sein.

Nur als Referenz: Sie finden meinen Referenzcode für das OLED-Display in Swift und C# . Es ist Testcode für Wirekite – eine Open-Source-Lösung zum Anschließen von I/Os einschließlich I2C an Ihren Mac oder PC mit einem kostengünstigen Teensy-Board und einem USB-Kabel. Der anwendungsspezifische Code wird dann auf Ihrem Mac oder PC ausgeführt; der Code auf dem Teensy steht fest.

Ich bin mir nicht ganz sicher, und da ich noch nicht das Privileg habe, einen Kommentar abzugeben,

Denken Sie nicht, dass Sie die TWI-Kommunikation nach jedem Befehl starten oder neu starten oder einen Stopp senden und einen Neustart veranlassen sollten?

Wenn das Ihre eigene Bibliothek ist, könnten Sie den TWI- oder I2C-Statuscode aus dem TWSR-Register lesen, und das würde Ihnen beim Debuggen dieses Problems helfen. Sie können wirklich überprüfen, welcher Teil Ihrer Anweisungen fehlschlägt, indem Sie mit dem im Datenblatt angegebenen Statuscode vergleichen.

EDIT: Keine Vermutungen mehr, der erste Teil der Antwort ist falsch. Nein, Sie brauchen kein Start- und Stoppbit, nachdem Sie gelesen und geschrieben haben. Um also einen Befehl an Ihren Chip zu senden, müssen Sie 16-Bit-Daten senden, von denen die ersten 8 Bits von MSB bestimmen, ob Sie die Daten oder den Befehl senden. Machst du das?

Danke für die Antwort! Ich habe erwartet, dass die Bibliothek es automatisch handhabt, lassen Sie mich das überprüfen, danke.
@ 0x29a fügen Sie Ihre i2c_write-Funktion auch in die Frage selbst ein. Es könnte Menschen helfen, Ihnen zu helfen.
Ich habe einen Link zur Bibliothek angehängt, damit der Beitrag nicht zu lang wird, danke)
@ 0x29a Sie müssen Folgendes tun
@ 0x29a Sie müssen Ihren Code ändern und eine Funktion erstellen, die zuerst ein Startbyte sendet, dann die Slave-Adresse schreibt, dann ein Byte schreibt, um anzuzeigen, ob das nächste Byte Befehl/Daten ist, dann den 'Befehl oder die Daten' schreiben und dann senden das Stoppbyte.

Der einfachste Weg zur Portierung besteht darin, eine eigene Version von WiringPiI2CWriteReg8 und die entsprechende Setup-Funktion zu schreiben.

Kompilieren Sie neu und Sie sollten bereit sein zu gehen.