STM32 et LCD2004A sans interface I2C

Récemment, j'ai commencé à étudier les contrôleurs STM32 et j'avais besoin d'une interaction avec l'écran LCD. Parmi les écrans, je n'ai trouvé que 2004A, et sans interface I2C. Il sera discuté dans cet article.



Tout d'abord, vous devez connecter l'écran au contrôleur. Nous nous connectons selon le schéma:



image



PB0 - PB7 - sorties du contrôleur.



Affectation des broches d'affichage:
1 GND ( )
2 VCC + 5
3 VEE . . 10-20 , .
4 RS : 0 – ; 1 – .
5 R/W :

0 – ;

1 – .

, .

6 EN . , «» .
7 DB0 . .
8 DB1
9 DB2
10 DB3
11 DB4 .
12 DB5
13 DB6
14 DB7
15 A (+)
16 K (-). .




Ainsi, l'écran est connecté. Il est temps d'apprendre au microcontrôleur à travailler avec lui. J'ai décidé de créer ma propre bibliothèque afin de pouvoir l'utiliser dans différents projets. Il se compose de deux fichiers - lcd_20x4.h et lcd_20x4.c



Commençons par le fichier d'en-tête.



#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_

#include "stm32f1xx.h"
#include "delay.h"


Tout d'abord, nous incluons le fichier de bibliothèque CMSIS stm32f1xx.h puisque j'ai une pierre STM32F103C8T6. Avec la prochaine inclusion, nous incluons le fichier delay.h - c'est ma bibliothèque pour travailler avec des retards basés sur la minuterie du système. Je ne le décrirai pas ici, voici son code:



Fichier Delay.h

#ifndef DELAY_DELAY_H_
#define DELAY_DELAY_H_

#include "stm32f1xx.h"

#define F_CPU 72000000UL
#define US F_CPU/1000000
#define MS F_CPU/1000
#define SYSTICK_MAX_VALUE 16777215
#define US_MAX_VALUE SYSTICK_MAX_VALUE/(US)
#define MS_MAX_VALUE SYSTICK_MAX_VALUE/(MS)


void delay_us(uint32_t us); //  233 
void delay_ms(uint32_t ms); //  233 
void delay_s(uint32_t s);

#endif /* DELAY_DELAY_H_ */




Fichier Delay.c

#include "delay.h"


/*      */

void delay_us(uint32_t us){ //  233 016 

	if (us > US_MAX_VALUE || us == 0)
		return;

	SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; //     0
	SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk; //    
	SysTick->LOAD = (US * us-1); //       
	SysTick->VAL = 0; //     SYST_CVR
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //  

	while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //    COUNFLAG   SYST_CSR

	SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;	//   COUNTFLAG
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //  

}

void delay_ms(uint32_t ms){ //  233 

	if(ms > MS_MAX_VALUE || ms ==0)
		return;

	SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
	SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
	SysTick->LOAD = (MS * ms);
	SysTick->VAL = 0;
	SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;

	while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));

	SysTick->CTRL &= ~SysTick_CTRL_COUNTFLAG_Msk;
	SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;


}

void delay_s(uint32_t s){

	for(int i=0; i<s*5;i++) delay_ms(200);
}





L'affichage 2004A est basé sur le contrôleur HITACHI HD44780. Par conséquent, regardons la fiche technique de ce contrôleur. Le tableau 6 montre le système de commandes, ainsi que les horaires de ces commandes.



image



Réécrivons les commandes nécessaires en macros dans le fichier d'en-tête:




// display commands

#define CLEAR_DISPLAY 0x1
#define RETURN_HOME 0x2
#define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift
#define DISPLAY_ON 0xC // non cursor
#define DISPLAY_OFF 0x8
#define CURSOR_SHIFT_LEFT 0x10
#define CURSOR_SHIFT_RIGHT 0x14
#define DISPLAY_SHIFT_LEFT 0x18
#define DISPLAY_SHIFT_RIGHT 0x1C
#define DATA_BUS_4BIT_PAGE0 0x28
#define DATA_BUS_4BIT_PAGE1 0x2A
#define DATA_BUS_8BIT_PAGE0 0x38
#define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS
#define SET_DDRAM_ADDRESS 0x80


Vous devez maintenant configurer les broches du contrôleur pour qu'elles fonctionnent avec l'écran. Déterminez la position des bits dans le port ODR du contrôleur. Faites attention à PIN_D4. J'ai le 10ème bit enregistré ici au lieu de 4. La 4ème sortie ne fonctionne pas sur mon contrôleur. Je ne sais pas à quoi il est connecté, mais dans le registre ODR, ce bit est toujours un, même avant le début de l'initialisation de l'horloge du contrôleur. Je ne sais pas à quoi il est lié, peut-être que la pierre n’est pas originale.




//     ODR
#define PIN_RS 0x1
#define PIN_EN 0x2
#define PIN_D7 0x80
#define PIN_D6 0x40
#define PIN_D5 0x20
#define PIN_D4 0x400


Ensuite, nous configurons les registres de contrôle pour les sorties. J'ai décidé de le faire sous la forme de macros de préprocesseur:




#define     LCD_PORT               	GPIOB
#define	LCD_ODR 				LCD_PORT->ODR

#define     LCD_PIN_RS()     		LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \
							LCD_PORT->CRL |= GPIO_CRL_MODE0;    // PB0   -,  50 

#define     LCD_PIN_EN()            LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\
						 LCD_PORT->CRL |= GPIO_CRL_MODE1;        // PB1

#define     LCD_PIN_D7()            LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\
						 LCD_PORT->CRL |= GPIO_CRL_MODE7;          // PB7

#define     LCD_PIN_D6()            LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\
						 LCD_PORT->CRL |= GPIO_CRL_MODE6;       // PB6

#define     LCD_PIN_D5()            LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\
						 LCD_PORT->CRL |= GPIO_CRL_MODE5;         // PB5

#define     LCD_PIN_D4()            LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\
						 LCD_PORT->CRH |= GPIO_CRH_MODE10;         // PB10

#define     LCD_PIN_MASK   (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011    


A la fin du fichier d'en-tête, nous définissons les fonctions pour travailler avec l'affichage:




void portInit(void); //     
void sendByte(char byte, int isData);
void lcdInit(void); //  
void sendStr(char *str, int row ); //  

#endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */


Nous avons terminé avec le fichier d'en-tête. Écrivons maintenant l'implémentation des fonctions dans le fichier lcd_20x4.c

La première étape est de configurer les broches pour qu'elles fonctionnent avec l'affichage. Ceci est fait par la fonction void portInit (void):




void portInit(void){
//----------------------  ----------------------------------------------------

	if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
	else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
	else return;

//---------------------    LCD-----------------------------------------------------

		LCD_PIN_RS();//    
		LCD_PIN_EN();
		LCD_PIN_D7();
		LCD_PIN_D6();
		LCD_PIN_D5();
		LCD_PIN_D4();

		lcdInit(); //   

	return ;
}


Quant à la fonction lcdInit (), c'est la fonction d'initialisation de l'affichage. Écrivons-le aussi. Il est basé sur un organigramme d'initialisation d'un affichage à partir d'une feuille de données:



image




//---------------------  -----------------------------------------------------------
void lcdInit(void){



			delay_ms(15); //    

			sendByte(0x33, 0); //      0011
			delay_us(100);

			sendByte(0x32, 0); //      00110010
			delay_us(40);

			sendByte(DATA_BUS_4BIT_PAGE0, 0); //   4 
			delay_us(40);
			sendByte(DISPLAY_OFF, 0); //  
			delay_us(40);
			sendByte(CLEAR_DISPLAY, 0); //  
			delay_ms(2);
			sendByte(ENTRY_MODE_SET, 0); //      
			delay_us(40);
			sendByte(DISPLAY_ON, 0);//     
			delay_us(40);


	return ;
}


La fonction d'initialisation utilise la fonction void sendByte (char byte, int isData). Écrivons sa mise en œuvre. Il est basé sur un chronogramme d'une fiche technique:



image




void sendByte(char byte, int isData){

	//   
	LCD_ODR &= ~LCD_PIN_MASK;

	if(isData == 1) LCD_ODR |= PIN_RS; //    RS
		else LCD_ODR &= ~(PIN_RS);		   //   RS
      
	LCD_ODR |= PIN_EN; //   E

	//     

	if(byte & 0x80) LCD_ODR |= PIN_D7;
	if(byte & 0x40) LCD_ODR |= PIN_D6;
	if(byte & 0x20) LCD_ODR |= PIN_D5;
	if(byte & 0x10) LCD_ODR |= PIN_D4;
	
	LCD_ODR &= ~PIN_EN; //   

	LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);//     RS

     	LCD_ODR |= PIN_EN;//   E

	//     
	if(byte & 0x8) LCD_ODR |= PIN_D7;
	if(byte & 0x4) LCD_ODR |= PIN_D6;
	if(byte & 0x2) LCD_ODR |= PIN_D5;
	if(byte & 0x1) LCD_ODR |= PIN_D4;
	
	LCD_ODR &= ~(PIN_EN);//   
	delay_us(40);


	return;
}


Nous pouvons maintenant envoyer un octet à l'écran sur un bus 4 bits. Cet octet peut être une commande ou un symbole. Il est déterminé en passant la variable isData à la fonction. Il est temps d'apprendre à transférer des chaînes.



L'affichage 2004A se compose de 4 lignes de 20 caractères comme reflété dans le titre. Afin de ne pas compliquer la fonction, je n'implémenterai pas de lignes de découpe à 20 caractères. Nous enverrons une chaîne de caractères et une chaîne dans laquelle la sortie vers la fonction.



Pour afficher le symbole à l'écran, vous devez l'écrire dans la DDRAM. L'adressage DDRAM correspond au tableau:



image




void sendStr(char *str, int row ){

	char start_address;

	switch (row) {

	case 1:
		start_address = 0x0; // 1 
		break;

	case 2:
		start_address = 0x40; // 2 
		break;

	case 3:
		start_address = 0x14; // 3 
		break;

	case 4:
		start_address = 0x54; // 4 
		break;

	}

	sendByte((start_address |= SET_DDRAM_ADDRESS), 0); //         DDRAM

	delay_ms(4);
	while(*str != '\0'){//     

		sendByte(*str, 1);
		str++;
		

	}// while
}


Voilà, la bibliothèque pour l'affichage est prête. Il est maintenant temps de l'utiliser. Dans la fonction main (), nous écrivons:




portInit();//    

	sendStr("    HELLO, HABR", 1);
	sendStr("     powered by", 2);
	sendStr("   STM32F103C8T6", 3);
	sendStr("Nibiru", 4);


Et nous obtenons le résultat:



image



En conclusion, je vais donner une liste complète des fichiers:



lcd_20x4.h

#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_

#include "stm32f1xx.h"
#include "delay.h"

// display commands

#define CLEAR_DISPLAY 0x1
#define RETURN_HOME 0x2
#define ENTRY_MODE_SET 0x6 // mode cursor shift rihgt, display non shift
#define DISPLAY_ON 0xC // non cursor
#define DISPLAY_OFF 0x8
#define CURSOR_SHIFT_LEFT 0x10
#define CURSOR_SHIFT_RIGHT 0x14
#define DISPLAY_SHIFT_LEFT 0x18
#define DISPLAY_SHIFT_RIGHT 0x1C
#define DATA_BUS_4BIT_PAGE0 0x28
#define DATA_BUS_4BIT_PAGE1 0x2A
#define DATA_BUS_8BIT_PAGE0 0x38
#define SET_CGRAM_ADDRESS 0x40 // usage address |= SET_CGRAM_ADDRESS
#define SET_DDRAM_ADDRESS 0x80


//     ODR
#define PIN_RS 0x1
#define PIN_EN 0x2
#define PIN_D7 0x80
#define PIN_D6 0x40
#define PIN_D5 0x20
#define PIN_D4 0x400



#define     LCD_PORT               	GPIOB
#define		LCD_ODR 				LCD_PORT->ODR

#define     LCD_PIN_RS()     		LCD_PORT->CRL &= ~GPIO_CRL_CNF0; \
									LCD_PORT->CRL |= GPIO_CRL_MODE0;    // PB0   -,  50 

#define     LCD_PIN_EN()            LCD_PORT->CRL &= ~GPIO_CRL_CNF1;\
									LCD_PORT->CRL |= GPIO_CRL_MODE1;        // PB1

#define     LCD_PIN_D7()            LCD_PORT->CRL &= ~GPIO_CRL_CNF7;\
									LCD_PORT->CRL |= GPIO_CRL_MODE7;          // PB7

#define     LCD_PIN_D6()            LCD_PORT->CRL &= ~GPIO_CRL_CNF6;\
									LCD_PORT->CRL |= GPIO_CRL_MODE6;       // PB6

#define     LCD_PIN_D5()            LCD_PORT->CRL &= ~GPIO_CRL_CNF5;\
									LCD_PORT->CRL |= GPIO_CRL_MODE5;         // PB5

#define     LCD_PIN_D4()            LCD_PORT->CRH &= ~GPIO_CRH_CNF10;\
									LCD_PORT->CRH |= GPIO_CRH_MODE10;         // PB10

#define     LCD_PIN_MASK   (PIN_RS | PIN_EN | PIN_D7 | PIN_D6 | PIN_D5 | PIN_D4) // 0b0000000011110011    

void portInit(void); //     
void sendByte(char byte, int isData);
void lcdInit(void); //  
void sendStr(char *str, int row ); //  

#endif /* LCD_LCD_20X4_2004A_LCD_20X4_H_ */




lcd_20x4.c

#include "lcd_20x4.h"

//     LCD

void sendByte(char byte, int isData){

	//   
	LCD_ODR &= ~LCD_PIN_MASK;

	if(isData == 1) LCD_ODR |= PIN_RS; //    RS
		else LCD_ODR &= ~(PIN_RS);		   //   RS

	//     
	if(byte & 0x80) LCD_ODR |= PIN_D7;
	if(byte & 0x40) LCD_ODR |= PIN_D6;
	if(byte & 0x20) LCD_ODR |= PIN_D5;
	if(byte & 0x10) LCD_ODR |= PIN_D4;

	//   E
	LCD_ODR |= PIN_EN;
	LCD_ODR &= ~PIN_EN; //   

	//     RS

	LCD_ODR &= ~(LCD_PIN_MASK & ~PIN_RS);

	//     
	if(byte & 0x8) LCD_ODR |= PIN_D7;
	if(byte & 0x4) LCD_ODR |= PIN_D6;
	if(byte & 0x2) LCD_ODR |= PIN_D5;
	if(byte & 0x1) LCD_ODR |= PIN_D4;

	//   E

	LCD_ODR |= PIN_EN;
	//delay_us(10);

	//   

	LCD_ODR &= ~(PIN_EN);
	delay_us(40);


	return;
}

//               50 

void portInit(void){

//----------------------  ----------------------------------------------------

	if(LCD_PORT == GPIOB) RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
	else if (LCD_PORT == GPIOA) RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
	else return;

//---------------------    LCD-----------------------------------------------------

		LCD_PIN_RS();
		LCD_PIN_EN();
		LCD_PIN_D7();
		LCD_PIN_D6();
		LCD_PIN_D5();
		LCD_PIN_D4();

		lcdInit();

	return ;
}

//---------------------  -----------------------------------------------------------
void lcdInit(void){

			delay_ms(15); //    

			sendByte(0x33, 0); //      0011
			delay_us(100);

			sendByte(0x32, 0); //      00110010
			delay_us(40);

			sendByte(DATA_BUS_4BIT_PAGE0, 0); //   4 
			delay_us(40);
			sendByte(DISPLAY_OFF, 0); //  
			delay_us(40);
			sendByte(CLEAR_DISPLAY, 0); //  
			delay_ms(2);
			sendByte(ENTRY_MODE_SET, 0); //      
			delay_us(40);
			sendByte(DISPLAY_ON, 0);//     
			delay_us(40);


	return ;
}

void sendStr(char *str, int row ){

	char start_address;

	switch (row) {

	case 1:
		start_address = 0x0; // 1 
		break;

	case 2:
		start_address = 0x40; // 2 
		break;

	case 3:
		start_address = 0x14; // 3 
		break;

	case 4:
		start_address = 0x54; // 4 
		break;

	}

	sendByte((start_address |= SET_DDRAM_ADDRESS), 0); //         DDRAM

	delay_ms(4);
	while(*str != '\0'){

		sendByte(*str, 1);
		str++;
		//delay_ms(100);

	}// while
}






All Articles