没有I2C接口的STM32和LCD2004A

最近,我开始研究STM32控制器,并且需要与LCD显示器进行交互。在这些显示中,我发现只有2004A,并且没有I2C接口。他将在本文中讨论。



首先,您需要将显示器连接到控制器。我们根据以下方案进行连接:



图片



PB0-PB7-控制器输出。



显示引脚分配:
针号 信号 信号分配
1个 地线 地线(普通线)
2 VCC 电源+ 5V
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 (-). .




因此,显示器已连接。是时候教微控制器使用它了。我决定创建自己的库,以便能够在不同的项目中使用它。它由两个文件组成-lcd_20x4.h和lcd_20x4.c



让我们从头文件开始。



#ifndef LCD_LCD_20X4_2004A_LCD_20X4_H_
#define LCD_LCD_20X4_2004A_LCD_20X4_H_

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


首先,我们包含CMSIS库文件stm32f1xx.h,因为我拥有STM32F103C8T6石头。下次打开时,我们将包含文件delay.h-这是我的库,用于根据系统计时器处理延迟。我将不在这里描述它,这是它的代码:



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_ */




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





2004A显示器基于HITACHI HD44780控制器。因此,让我们看一下该控制器的数据表。表6包含命令系统以及这些命令的执行时间。



图片



让我们将必要的命令重写为头文件中的宏:




// 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端口中位的位置。注意PIN_D4。我在那里注册了10位而不是4位。第4个输出在我的控制器上不起作用。我不知道它与什么连接,但是在ODR寄存器中,即使在控制器时钟初始化开始之前,该位始终为1。我不知道这是和什么有关的,也许石头不是原始的。




//     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文件中编写函数的实现,

第一步是配置引脚以与显示器配合使用。这是通过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 ;
}


至于lcdInit()函数,这是显示初始化函数。让我们也写吧。它基于从数据表初始化显示的流程图:



图片




//---------------------  -----------------------------------------------------------
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 sendByte(字符字节,int isData)函数。让我们来编写它的实现。它基于数据表中的时序图:



图片




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


现在我们可以通过4位总线将一个字节发送到显示器。该字节可以是命令或符号。通过将isData变量传递给函数来确定。现在是时候学习如何传输字符串了。



2004A显示屏由4行20个字符组成,如标题所示。为了不使功能复杂化,我不会将切割线实现为20个字符。我们将发送一个字符串和一个字符串,以将其输出到函数。



要在屏幕上显示符号,您需要将其写入DDRAM。DDRAM寻址对应于下表:



图片




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
}


就是这样,用于显示的库已准备就绪。现在是时候使用它了。在main()函数中,我们编写:




portInit();//    

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


我们得到的结果是:



图片



总之,我将给出文件的完整列表:



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