Nous sommes amis STM32 avec écran LCD 1604 sur bus I2C (bibliothèque HAL)

Bonjour, Habr!

Dans cet article, je voudrais parler de mon expérience de connexion d'écrans LCD au microcontrôleur STM32 à l'aide de la bibliothèque HAL sur le bus I2C.

image

Je connecterai les écrans 1602 et 2004. Ils ont tous deux un adaptateur I2C soudé basé sur la puce PCF8574T. La carte de débogage sera Nucleo767ZI et l'environnement de développement sera STM32CubeIDE 1.3.0.

Je ne parlerai pas en détail du principe de fonctionnement du bus I2C, je vous conseille de regarder ici et ici .

Nous créons le projet, sélectionnez la carte de débogage: Nous

image

indiquons que nous utiliserons I2C1. Je vais également connecter UART5 pour communiquer avec la carte, cela est nécessaire pour recevoir des informations de la carte sur l'adresse d'affichage.

image

image

Dans la même fenêtre, vous pouvez voir les numéros des jambes auxquelles l'écran est connecté, dans mon cas, il s'est avéré comme ceci:

image

Pour commencer, connectez un seul écran, je commencerai par 1602. De plus, je connecterai l'adaptateur USB-UART CH340, connu des pilotes Arduin expérimentés, pour recevoir des données de la carte.

image

Veuillez noter que l'adaptateur connecte RX à TX et TX à RX, le cavalier sur l'adaptateur est de 3,3 V

image

Voyons de plus près comment travailler avec la puce et l'affichage PCF8574T. Ci-dessous, un schéma de principe d'un module avec écran:

image

La puce PCF8574T est similaire en fonction au registre à décalage 74hc595 - elle reçoit des octets via l'interface I2C et affecte les valeurs du bit correspondant à ses sorties (P0-P7).

Considérez quelles broches du microcircuit sont connectées à l'écran et de quoi elles sont responsables:

  • La borne P0 du microcircuit est connectée à la borne RS de l'affichage, qui est chargée de recevoir les données d'affichage (1) ou les instructions de fonctionnement d'affichage (0);
  • La broche P1 est connectée à R \ W, si 1 - écrire des données sur l'affichage, 0 - lire;
  • La borne P2 est connectée à CS - la borne dont le changement d'état est en cours de lecture;
  • Conclusion P3 - contrôle du rétroéclairage;
  • Les conclusions P4 - P7 sont utilisées pour transmettre des données à l'écran.

Plusieurs appareils peuvent être connectés à un bus I2C en même temps. Afin de pouvoir accéder à un appareil spécifique, chacun d'eux a sa propre adresse, nous allons d'abord le découvrir. Si les contacts A1, A2 et A3 sur la carte adaptateur ne sont pas scellés, l'adresse sera probablement 0x27, mais il vaut mieux vérifier. Pour ce faire, nous allons écrire une petite fonction qui montrera les adresses de tous les appareils connectés au bus I2C:

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);
}

Cette fonction interroge toutes les adresses de 0 à 127 et si une réponse est reçue de cette adresse, elle envoie le numéro d'adresse sous forme hexadécimale à UART.

Pour communiquer avec le conseil, j'utilise le programme Termite. Par défaut, la vitesse UART du microcontrôleur est définie sur 115200, vous devez définir la même chose dans les termites. Nous appelons la fonction dans le corps principal du programme, flashons la carte et nous connectons en termite à notre microcontrôleur: les

image

points montrent toutes les adresses dont la réponse n'a pas été reçue. L'adresse sur mon écran est 0x26, car j'ai soudé le cavalier A0. Maintenant, connectez le deuxième écran en parallèle au premier et voyez ce que le programme donnera:

image

Nous avons deux adresses: 0x26 (affichage 1602) et 0x27 (affichage 2004). Maintenant, comment travailler avec l'écran. Le microcontrôleur envoie un octet d'adresse et tous les appareils connectés au bus le vérifient avec le leur. S'il correspond, le module commence la communication avec le microcontrôleur. Tout d'abord, vous devez configurer l'affichage: d'où ira le nombre de caractères et dans quelle direction, comment se comportera le curseur, etc. Après cela, il sera déjà possible de transmettre des informations à afficher à l'écran. La particularité est que nous ne pouvons utiliser que 4 bits pour transmettre des informations, c'est-à-dire les données doivent être divisées en deux parties. Les données sont stockées dans les bits hauts (4-7), et les bits bas sont utilisés pour indiquer si le rétroéclairage s'allumera (3 bits), si les données viennent pour la sortie ou pour afficher les paramètres (sortie RS, 0 bits) et 2 bits, par le changement dont la lecture a lieu, c'est-à-diree pour envoyer 1 octet de données, vous devez envoyer 4 octets - le 1er octet contiendra 4 bits d'information, le 2e bit à l'état 1, le 2e octet est une répétition du 1er, seul le 2e bit à l'état 0. Les 3e et 4e octets sont similaires, ils contiennent seulement seconde moitié des données. Cela semble un peu incompréhensible, je vais vous montrer un exemple:

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);
}

Prenons-le dans l'ordre. Au début, il y a des variables qui stockent l'adresse d'affichage et des bits de paramètres qui doivent être envoyés à chaque fois avec les données. Dans la fonction d'envoi, nous vérifions tout d'abord s'il y a un module à l'adresse enregistrée. En cas de réception du message HAL_OK, nous commençons à former des octets pour l'envoi. Au début de l'octet que nous enverrons, il est nécessaire de diviser en deux parties, d'écrire les deux sur les bits hauts. Supposons que nous voulons que l'affichage affiche le caractère 's', dans le système binaire, il est 1110011 ( calculatrice) En utilisant l'opération logique et nous écrivons = 01110000 dans la variable, c'est-à-dire écrire uniquement les bits les plus significatifs. Les bits de poids faible sont décalés vers la gauche de 4 caractères au début, puis sont écrits dans la variable lo = 00110000. Ensuite, nous formons un tableau de 4 octets qui contient des informations sur le caractère à afficher. Maintenant, nous attribuons des bits de configuration (0-3 bits) aux octets existants. Après cela, nous envoyons l'octet d'adresse et 4 octets d'informations à l'écran à l'aide de la fonction HAL_I2C_Master_Transmit ();

Mais ne vous précipitez pas pour télécharger le programme, car au début, vous devez définir les paramètres d'affichage. Le site a un excellent tableau traduit avec des commandes pour personnaliser l'affichage. Après l'avoir vérifié avec la documentation, je suis arrivé aux paramètres suivants qui sont optimaux pour moi:

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

Nous plaçons ces commandes avant le début d'une boucle infinie, afin que les paramètres soient envoyés une fois avant de commencer le travail (comme la configuration void en arduinki). La fonction I2C_send, en plus de l'octet, vous oblige à spécifier si les paramètres d'affichage ou les données seront envoyés. Si le deuxième argument de la fonction est 0, alors les paramètres et si 1, alors les données.

Et la dernière touche - vous avez besoin d'une fonction qui enverra un délai caractère par caractère. Tout est assez simple ici:

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

Après avoir rassemblé toutes ces fonctions ensemble, vous pouvez écrire:

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

image

Eh bien, nous avons compris l'écran 1602, maintenant 2004. La différence entre eux est minime, même ce code fonctionnera bien. Toute la différence se résume à organiser les adresses des cellules sur l'écran. Dans les deux affichages, la mémoire contient 80 cellules, dans l'affichage 1602, les 16 premières cellules sont responsables de la première ligne et les cellules 40 à 56 sont responsables de la deuxième ligne. Les cellules mémoire restantes ne sont pas affichées, donc si vous envoyez 17 caractères à l'affichage, la dernière ne sera pas transférée sur la deuxième ligne, et sera enregistré dans une cellule mémoire qui n'a pas de sortie à l'écran. Un peu plus clairement, la mémoire est organisée comme suit:

image

Pour utiliser le saut de ligne, j'ai utilisé la commande I2C_send (0b11000000,0); , il va juste à 40 cellules. L'affichage 2004 est plus intéressant.

La première ligne correspond aux cellules 1 à 20
La deuxième ligne correspond aux cellules 40 à 60
La troisième rangée - cellules de 21 à 40
La quatrième rangée - cellules de 60 à 80,
c.-à - d. si vous envoyez une commande

LCD_SendString("___________________1___________________2___________________3___________________4");

Nous obtenons ce qui suit:

image

Pour organiser les transitions entre les lignes, vous devez déplacer manuellement le curseur vers la cellule de mémoire souhaitée, ou vous pouvez compléter par programme la fonction. Jusqu'à présent, je me suis installé sur la version manuelle:

  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");

Résultat:

image

c'est probablement tout avec ces affichages, liens utiles grâce auxquels j'ai pu tout comprendre:

  1. Le code ressemblait beaucoup ici
  2. Les tableaux pour la configuration de l'affichage ont regardé ici
  3. La procédure a été consultée ici

Programme et fiche technique

PS: n'oubliez pas de régler à l'avance la luminosité de l'écran.

All Articles