首先,您需要将显示器连接到控制器。我们根据以下方案进行连接:
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
}