Wir sind Freunde STM32 mit LCD-Anzeige 1604 am I2C-Bus (HAL-Bibliothek)

Hallo Habr!

In diesem Artikel möchte ich über meine Erfahrungen beim Anschließen von LCD-Displays an den STM32-Mikrocontroller mithilfe der HAL-Bibliothek am I2C-Bus sprechen.

Bild

Ich werde das Display 1602 und 2004 anschließen. Beide haben einen gelöteten I2C-Adapter, der auf dem PCF8574T-Chip basiert. Das Debug-Board ist Nucleo767ZI und die Entwicklungsumgebung ist STM32CubeIDE 1.3.0.

Ich werde nicht im Detail über das Funktionsprinzip des I2C-Busses sprechen, ich rate Ihnen, hier und hier nachzuschauen .

Wir erstellen das Projekt, wählen die Debug-

Bild

Karte aus : Wir geben an, dass wir I2C1 verwenden werden. Ich werde auch UART5 verbinden, um mit der Karte zu kommunizieren. Dies ist erforderlich, um Informationen von der Karte über die Anzeigeadresse zu erhalten.

Bild

Bild

Im selben Fenster sehen Sie die Nummern der Beine, an die das Display angeschlossen ist. In meinem Fall stellte sich

Bild

Folgendes heraus: Um zu beginnen, schließen Sie nur ein Display an. Ich beginne mit 1602. Außerdem schließe ich den USB-UART CH340-Adapter an, der erfahrenen Arduin-Treibern bekannt ist, um Daten von der Karte zu empfangen.

Bild

Bitte beachten Sie, dass der Adapter RX mit TX und TX mit RX verbindet. Der Jumper am Adapter beträgt 3,3 V.

Bild

Schauen wir uns die Arbeit mit dem PCF8574T-Chip und -Display genauer an. Unten sehen Sie eine schematische Darstellung eines Moduls mit Anzeige:

Bild

Der PCF8574T-Chip ähnelt in seiner Funktionalität dem Schieberegister 74hc595 - er empfängt Bytes über die I2C-Schnittstelle und weist seinen Ausgängen die Werte des entsprechenden Bits zu (P0-P7).

Überlegen Sie, welche Stifte der Mikroschaltung mit dem Display verbunden sind und wofür sie verantwortlich sind:

  • Der Anschluss P0 der Mikroschaltung ist mit dem RS-Anschluss des Displays verbunden, der für den Empfang der Anzeigedaten (1) oder der Anzeigebetriebsanweisungen (0) verantwortlich ist;
  • Pin P1 ist mit R \ W verbunden, wenn 1 - Daten in die Anzeige schreiben, 0 - lesen;
  • Klemme P2 ist mit CS verbunden - der Klemme, deren Zustandsänderung gelesen wird;
  • Schlussfolgerung P3 - Hintergrundbeleuchtung;
  • Schlussfolgerungen P4 - P7 werden verwendet, um Daten an das Display zu übertragen.

Es können mehrere Geräte gleichzeitig an einen I2C-Bus angeschlossen werden. Um auf ein bestimmtes Gerät zugreifen zu können, hat jedes von ihnen eine eigene Adresse. Zuerst werden wir es herausfinden. Wenn die Kontakte A1, A2 und A3 auf der Adapterplatine nicht versiegelt sind, lautet die Adresse höchstwahrscheinlich 0x27, es ist jedoch besser, dies zu überprüfen. Dazu schreiben wir eine kleine Funktion, die die Adressen aller Geräte anzeigt, die an den I2C-Bus angeschlossen sind:

void I2C_Scan ()
{
	//  ,  
        HAL_StatusTypeDef res;                          
	//    
        char info[] = "Scanning I2C bus...\r\n";      
        //    UART	
        HAL_UART_Transmit(&huart5, (uint8_t*)info, strlen(info), HAL_MAX_DELAY);  
	/* &huart5 -   UART
	 * (uint8_t*)info -     
	 * strlen(info) -   
	 * HAL_MAX_DELAY - 
	 */
        //    
	for(uint16_t i = 0; i < 128; i++)              
	{
            // ,      i  
            res = HAL_I2C_IsDeviceReady(&hi2c1, i << 1, 1, HAL_MAX_DELAY);                   
	    //  , 
            if(res == HAL_OK)                              
	    {
	    	char msg[64];
	    	//   i,   ,     
                // 16 :
	    	snprintf(msg, sizeof(msg), "0x%02X", i);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
	    	//    
	    	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
	    }
	    else HAL_UART_Transmit(&huart5, (uint8_t*)".", 1, HAL_MAX_DELAY);
	}
	HAL_UART_Transmit(&huart5, (uint8_t*)"\r\n", 2, HAL_MAX_DELAY);
}

Diese Funktion fragt alle Adressen von 0 bis 127 ab. Wenn eine Antwort von dieser Adresse empfangen wird, sendet sie die Adressnummer in hexadezimaler Form an UART.

Um mit dem Board zu kommunizieren, verwende ich das Termite-Programm. Standardmäßig ist die UART-Geschwindigkeit des Mikrocontrollers auf 115200 eingestellt, Sie müssen dies auch für Termiten einstellen. Wir rufen die Funktion im Hauptteil des Programms auf, flashen die Karte und verbinden Termiten mit unserem Mikrocontroller:

Bild

Punkte zeigen alle Adressen an, von denen die Antwort nicht empfangen wurde. Die Adresse auf meinem Display ist 0x26, da ich den Jumper A0 gelötet habe. Schließen Sie nun die zweite Anzeige parallel zur ersten an und sehen Sie, was das Programm geben wird:

Bild

Wir haben zwei Adressen: 0x26 (Anzeige 1602) und 0x27 (Anzeige 2004). Nun erfahren Sie, wie Sie mit dem Display arbeiten. Der Mikrocontroller sendet ein Adressbyte, und alle an den Bus angeschlossenen Geräte überprüfen es mit ihrem eigenen. Wenn es übereinstimmt, beginnt das Modul mit der Kommunikation mit dem Mikrocontroller. Zunächst müssen Sie die Anzeige konfigurieren: Woher kommt die Anzahl der Zeichen und in welche Richtung, wie verhält sich der Cursor usw. Danach ist es bereits möglich, Informationen zur Anzeige an die Anzeige zu übertragen. Die Besonderheit ist, dass wir nur 4 Bits verwenden können, um Informationen zu übertragen, d.h. Daten müssen in zwei Teile geteilt werden. Die Daten werden in den High-Bits (4-7) gespeichert, und die Low-Bits werden verwendet, um anzuzeigen, ob die Hintergrundbeleuchtung eingeschaltet wird (3 Bits), ob Daten zur Ausgabe kommen oder Anzeigeeinstellungen (RS-Ausgang, 0 Bits) und 2 Bits. durch deren Änderung das Lesen stattfindet, d.h.e Um 1 Byte Daten zu senden, müssen Sie 4 Bytes senden - das 1. Byte enthält 4 Informationsbits, das 2. Bit den Zustand 1, das 2. Byte eine Wiederholung des 1., nur das 2. Bit den Zustand 0. Das 3. und 4. Byte sind ähnlich, nur sie enthalten zweite Hälfte der Daten. Es klingt ein wenig unverständlich, ich zeige Ihnen ein Beispiel:

void I2C_send(uint8_t data, uint8_t flags)
{
	HAL_StatusTypeDef res;
	 //     
        for(;;) {
                // ,      lcd_addr                                                                                        
	        res = HAL_I2C_IsDeviceReady(&hi2c1, LCD_ADDR, 1, HAL_MAX_DELAY);               
	         //  ,     
                if(res == HAL_OK) break;                                                  
	    }
        //    1111 0000      0  3,    4  7
	uint8_t up = data & 0xF0;   
        //   ,  data   4                	
        uint8_t lo = (data << 4) & 0xF0;          
                                          
	uint8_t data_arr[4];
         // 4-7   ,  0-3   
	data_arr[0] = up|flags|BACKLIGHT|PIN_EN; 
         //  ,       0  
	data_arr[1] = up|flags|BACKLIGHT;         
	data_arr[2] = lo|flags|BACKLIGHT|PIN_EN;
	data_arr[3] = lo|flags|BACKLIGHT;

	HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_arr, sizeof(data_arr), HAL_MAX_DELAY);
	HAL_Delay(LCD_DELAY_MS);
}

Nehmen wir es in Ordnung. Am Anfang gibt es Variablen, die die Anzeigeadresse speichern, und Einstellungsbits, die jedes Mal zusammen mit den Daten gesendet werden müssen. In der Sendefunktion prüfen wir zunächst, ob sich an der aufgezeichneten Adresse ein Modul befindet. Im Falle des Empfangs der HAL_OK-Nachricht beginnen wir, Bytes zum Senden zu bilden. Am Anfang des Bytes, das wir senden, ist es notwendig, in zwei Teile zu teilen und beide in die hohen Bits zu schreiben. Angenommen, die Anzeige soll die Zeichen 's' anzeigen, im Binärsystem ist es 1110011 ( Taschenrechner)) Unter Verwendung der logischen Operation & schreiben wir = 01110000 in die Variable, d.h. schreibe nur die höchstwertigen Bits. Die niederwertigen Bits werden zu Beginn um 4 Zeichen nach links verschoben und dann in die Variable lo = 00110000 geschrieben. Als nächstes bilden wir ein Array von 4 Bytes, das Informationen über das anzuzeigende Zeichen enthält. Jetzt weisen wir vorhandenen Bytes Konfigurationsbits (0-3 Bits) zu. Danach senden wir das Adressbyte und 4 Informationsbytes mit der Funktion HAL_I2C_Master_Transmit () an die Anzeige.

Beeilen Sie sich jedoch nicht, das Programm herunterzuladen, da Sie zu Beginn die Anzeigeeinstellungen vornehmen müssen. Die Site verfügt über eine ausgezeichnete übersetzte Tabelle mit Befehlen zum Anpassen der Anzeige. Nachdem ich es mit der Dokumentation überprüft hatte, kam ich zu den folgenden Einstellungen, die für mich optimal sind:

  I2C_send(0b00110000,0);   // 8  
  I2C_send(0b00000010,0);   //     
  I2C_send(0b00001100,0);   //   ,  
  I2C_send(0b00000001,0);   //  

Wir platzieren diese Befehle vor dem Beginn einer Endlosschleife, so dass die Einstellungen vor Arbeitsbeginn einmal gesendet werden (wie das Void-Setup in Arduinki). Für die Funktion I2C_send müssen Sie zusätzlich zum Byte angeben, ob Anzeigeeinstellungen oder Daten gesendet werden sollen. Wenn das zweite Argument der Funktion 0 ist, dann die Einstellungen und wenn 1, dann die Daten.

Und die letzte Berührung - Sie benötigen eine Funktion, die Zeichen für Zeichen eine Frist sendet. Hier ist alles ziemlich einfach:

void LCD_SendString(char *str)
{
    // *char    
    //    
	while(*str) 
        {                                   
                //    
		I2C_send((uint8_t)(*str), 1); 
                //     1               
                str++;                                     
        }
}

Nachdem Sie alle diese Funktionen zusammengestellt haben, können Sie schreiben:

  LCD_SendString("  Hello");
  I2C_send(0b11000000,0);   //  
  LCD_SendString("    Habr");

Bild

Nun, wir haben das 1602-Display jetzt 2004 herausgefunden. Der Unterschied zwischen ihnen ist minimal, selbst dieser Code wird gut funktionieren. Der Unterschied besteht darin, die Adressen der Zellen auf dem Display zu organisieren. In beiden Anzeigen enthält der Speicher 80 Zellen, in der Anzeige 1602 sind die ersten 16 Zellen für die erste Zeile und die Zellen 40 bis 56 für die zweite Zeile verantwortlich. Die verbleibenden Speicherzellen werden nicht angezeigt. Wenn Sie also 17 Zeichen an die Anzeige senden, wird die letzte nicht übertragen in der zweiten Zeile und wird in einer Speicherzelle aufgezeichnet, die keinen Ausgang zur Anzeige hat. Etwas klarer ist der Speicher wie folgt organisiert:

Bild

Um den Zeilenvorschub zu verwenden, habe ich den Befehl I2C_send (0b11000000,0) verwendet; geht es nur zu 40 Zellen. Das 2004er Display ist interessanter.

Die erste Reihe besteht aus den Zellen 1 bis 20,
die zweite Reihe aus den Zellen 40 bis 60
Die dritte Reihe - Zellen von 21 bis 40
Die vierte Reihe - Zellen von 60 bis 80,
d.h. wenn Sie einen Befehl senden

LCD_SendString("___________________1___________________2___________________3___________________4");

Wir erhalten Folgendes:

Bild

Um Übergänge zwischen Zeilen zu organisieren, müssen Sie den Cursor manuell auf die gewünschte Speicherzelle bewegen, oder Sie können die Funktion programmgesteuert ergänzen. Ich habe mich bisher für die manuelle Version entschieden:

  I2C_send(0b10000000,0);   //   1 
  LCD_SendString("  Hello Habr");
  I2C_send(0b11000000,0);   //   2 
  LCD_SendString(" STM32 + LCD 1602");
  I2C_send(0b10010100,0);   //   3 
  LCD_SendString(" +LCD 2004A");
  I2C_send(0b11010100,0);   //   4 
  LCD_SendString(" library HAL");

Ergebnis:

Bild

Das ist wahrscheinlich alles mit diesen Displays, nützlichen Links, dank derer ich alles herausfinden konnte:

  1. Der Code sah hier sehr gut aus
  2. Tabellen zur Anzeigekonfiguration finden Sie hier
  3. Das Verfahren wurde hier angeschaut

Programm- und Datenblatt

PS: Vergessen Sie nicht, die Helligkeit des Displays im Voraus anzupassen.

All Articles