必要知识
本实验用的是240*320屏幕
LCD的驱动原理:
LCD屏(MCU接口)驱动的核心是:驱动LCD驱动芯片
LCD驱动基本知识:
1,8080时序,LCD驱动芯片一般使用8080时序控制,实现数据写入/读取
2,初始化序列(数组),屏厂提供,用于初始化特定屏幕,不同屏幕厂家不完全相同!
3,画点函数、读点函数(非必需),基于这两个函数可以实现各种绘图功能!
LCD驱动的一般过程
8080时序简介
LCD驱动芯片
LCD基本驱动实现
1,确定IO连接关系
LCD模块原理图、开发板液晶接口原理图
2,初始化IO口
初始化连接LCD的各个IO口
初始化FSMC外设
可选,某些芯片是没有FSMC外设,MINI板是没有的
3,编写读写接口函数
lcd_wr_data、lcd_wr_regno、lcd_write_reg、lcd_rd_data
4,编写LCD初始化函数
编写lcd_init函数,完成初始化序列配置,点亮背光等
5,编写LCD画和读点函数
编写lcd_draw_point函数,实现LCD任意位置画点
通过LCD驱动的一般过程和基本驱动实现可知 lcd.c 怎么样去配置
1、写数据 lcd_wr_data、写命令 lcd_wr_regno、写寄存器 lcd_write_reg、
读数据 lcd_rd_data
/* 8080 写数据 */
void lcd_wr_data (uint16_t data)
{
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_DATA_OUT(data); /* 数据 */
LCD_WR(0); /* WR低电平 */
LCD_WR(1); /* WR高电平 */
LCD_CS(1); /* 释放片选 */
}
/* 8080 写命令 */
void lcd_wr_regno(uint16_t regno)
{
LCD_RS(0); /* RS=0,表示写寄存器 */
LCD_CS(0); /* 选中 */
LCD_DATA_OUT(regno);/* 命令 */
LCD_WR(0); /* WR低电平 */
LCD_WR(1); /* WR高电平 */
LCD_CS(1); /* 释放片选 */
}
/* 往寄存器写值 */
void lcd_write_reg(uint16_t regno, uint16_t data)
{
lcd_wr_regno(regno); /* 写入要写的寄存器序号 */
lcd_wr_data(data); /* 写入数据 */
}
uint16_t lcd_rd_data(void)
{
uint16_t ram; /* 定义变量 */
DATA_IN_MODE(); /* 设置数据输入 */
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_RD(0); /* RD低电平 */
ram = LCD_DATA_IN; /* 读取数据 */
LCD_RD(1); /* RD高电平 */
LCD_CS(1); /* 释放片选 */
DATA_OUT_MODE(); /* 设置数据输出 */
return ram;
2、初始化LCD:主要是发送初始化序列、对LCD控制结构体赋值、设置扫描函数、点亮背光以及设置lcd_clear函数
3、实现LCD的画点和读点函数
在实现画点函数之前我们要先做 1、准备写GRAM 2、设置坐标
/* 准备写GRAM */
void lcd_write_ram_prepare(void)
{
lcd_wr_regno(lcddev.wramcmd);
}
/* 设置坐标 */
void lcd_set_cursor(uint16_t x, uint16_t y)
{
lcd_wr_regno(lcddev.setxcmd);
lcd_wr_data(x >> 8);
lcd_wr_data(x & 0xFF);
lcd_wr_regno(lcddev.setycmd);
lcd_wr_data(y >> 8);
lcd_wr_data(y & 0xFF);
}
/* 画点 */
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
lcd_set_cursor(x, y);
lcd_write_ram_prepare();
lcd_wr_data(color);
}
/* 读点 */
uint16_t lcd_read_point (uint16_t x, uint16_t y)
{
uint16_t r = 0, g = 0, b = 0; /* 定义变量 */
lcd_set_cursor(x, y); /* 设置坐标 */
lcd_wr_regno(0X2E); /* 发读点命令 */
r = lcd_rd_data(); /* 假读 */
r = lcd_rd_data(); /* 读rg */
b = lcd_rd_data(); /* 读b */
g = r & 0XFF; /* 得到g值 */
return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
}
源码
lcd.c
#include "stdlib.h"
#include "./BSP/LCD/lcd.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LCD/lcd_ex.c"
#include "./BSP/LCD/lcdfont.h"
/* LCD的画笔颜色和背景色 */
uint32_t g_point_color = 0XF800; /* 画笔颜色 */
uint32_t g_back_color = 0XFFFF; /* 背景色 */
/* 管理LCD重要参数 */
_lcd_dev lcddev;
/* 8080 写数据 */
void lcd_wr_data (uint16_t data)
{
LCD_RS(1); /* 操作数据 */
LCD_CS(0); /* 选中 */
LCD_DATA_OUT(data); /* 数据 */
LCD_WR(0); /* WR低电平 */
LCD_WR(1); /* WR高电平 */
LCD_CS(1); /* 释放片选 */
}
/* 8080 写命令 */
void lcd_wr_regno(uint16_t regno)
{
LCD_RS(0); /* RS=0,表示写寄存器 */
LCD_CS(0); /* 选中 */
LCD_DATA_OUT(regno);/* 命令 */
LCD_WR(0); /* WR低电平 */
LCD_WR(1); /* WR高电平 */
LCD_CS(1); /* 释放片选 */
}
/* 往寄存器写值 */
void lcd_write_reg(uint16_t regno, uint16_t data)
{
lcd_wr_regno(regno); /* 写入要写的寄存器序号 */
lcd_wr_data(data); /* 写入数据 */
}
static void lcd_opt_delay(uint32_t i)
{
while(i--);
}
/* LCD读数据 */
uint16_t lcd_rd_data(void)
{
volatile uint16_t ram; /* 防止被优化 */
GPIO_InitTypeDef gpio_init_struct;
/* LCD_DATA 引脚模式设置, 上拉输入, 准备接收数据 */
gpio_init_struct.Pin = LCD_DATA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_INPUT;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LCD_DATA_GPIO_PORT, &gpio_init_struct);
LCD_RS(1); /* RS=1,表示操作数据 */
LCD_CS(0);
LCD_RD(0);
lcd_opt_delay(2);
ram = LCD_DATA_IN; /* 读取数据 */
LCD_RD(1);
LCD_CS(1);
/* LCD_DATA 引脚模式设置, 推挽输出, 恢复输出状态 */
gpio_init_struct.Pin = LCD_DATA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
gpio_init_struct.Pull = GPIO_PULLUP;
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LCD_DATA_GPIO_PORT, &gpio_init_struct);
return ram;
}
/* 准备写GRAM */
void lcd_write_ram_prepare(void)
{
lcd_wr_regno(lcddev.wramcmd);
}
/* 设置坐标 */
void lcd_set_cursor(uint16_t x, uint16_t y)
{
lcd_wr_regno(lcddev.setxcmd);
lcd_wr_data(x >> 8);
lcd_wr_data(x & 0xFF);
lcd_wr_regno(lcddev.setycmd);
lcd_wr_data(y >> 8);
lcd_wr_data(y & 0xFF);
}
/* 画点 */
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
lcd_set_cursor(x, y);
lcd_write_ram_prepare();
lcd_wr_data(color);
}
/* 读点 */
uint16_t lcd_read_point (uint16_t x, uint16_t y)
{
uint16_t r = 0, g = 0, b = 0; /* 定义变量 */
lcd_set_cursor(x, y); /* 设置坐标 */
lcd_wr_regno(0X2E); /* 发读点命令 */
r = lcd_rd_data(); /* 假读 */
r = lcd_rd_data(); /* 读rg */
b = lcd_rd_data(); /* 读b */
g = r & 0XFF; /* 得到g值 */
return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
}
void lcd_show_char(uint16_t x, uint16_t y, char chr, uint8_t size, uint8_t mode, uint16_t color)
{
uint8_t t, t1, temp;
uint8_t *pfont = NULL;
uint8_t csize = (size / 8+((size % 8) ? 1 : 0)) * (size / 2);
chr = chr - ' ';
uint16_t y0 = y;
switch(size)
{
case 12:
pfont = (uint8_t *)asc2_1206[chr]; /* 调用1206字体 */
break;
case 16:
pfont = (uint8_t *)asc2_1608[chr]; /* 调用1608字体 */
break;
case 24:
pfont = (uint8_t *)asc2_2412[chr]; /* 调用2412字体 */
break;
case 32:
pfont = (uint8_t *)asc2_3216[chr]; /* 调用3216字体 */
break;
default:
return ;
}
//字符显示代码
for (t = 0; t < csize; t++)
{
temp = pfont[t];
for (t1 = 0; t1 < 8; t1++)
{
if (temp & 0x80)
{
lcd_draw_point(x, y, color);
}
else if (mode == 0)
{
lcd_draw_point(x, y, g_back_color);
}
temp <<= 1;
y++;
if ((y - y0) == size)
{
y = y0;
x++;
break;
}
}
}
}
void lcd_clear(uint16_t color)
{
uint32_t index = 0;
uint32_t totalpoint = lcddev.width;
totalpoint *= lcddev.height; /* 得到总点数 */
lcd_set_cursor(0, 0); /* 设置光标位置 */
lcd_write_ram_prepare(); /* 开始写入GRAM */
LCD_RS(1); /* RS=1,表示写数据 */
LCD_CS(0);
for (index = 0; index < totalpoint; index++)
{
LCD_DATA_OUT(color); /* 写入要写的数据 */
LCD_WR(0);
LCD_WR(1);
}
LCD_CS(1);
}
/* 初始化LCD */
void lcd_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LCD_BL_GPIO_CLK_ENABLE(); /* LCD_BL脚时钟使能 */
LCD_CS_GPIO_CLK_ENABLE(); /* LCD_CS脚时钟使能 */
LCD_WR_GPIO_CLK_ENABLE(); /* LCD_WR脚时钟使能 */
LCD_RD_GPIO_CLK_ENABLE(); /* LCD_RD脚时钟使能 */
LCD_RS_GPIO_CLK_ENABLE(); /* LCD_RS脚时钟使能 */
LCD_DATA_GPIO_CLK_ENABLE(); /* LCD_DATA脚时钟使能 */
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_SWJ_NOJTAG(); /* 禁止JTAG, 使能SWD, 释放PB3,PB4两个引脚做普通IO用 */
gpio_init_struct.Pin = LCD_BL_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽复用 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LCD_BL_GPIO_PORT, &gpio_init_struct); /* LCD_BL引脚模式设置(推挽输出) */
gpio_init_struct.Pin = LCD_CS_GPIO_PIN;
HAL_GPIO_Init(LCD_CS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_CS引脚 */
gpio_init_struct.Pin = LCD_WR_GPIO_PIN;
HAL_GPIO_Init(LCD_WR_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_WR引脚 */
gpio_init_struct.Pin = LCD_RD_GPIO_PIN;
HAL_GPIO_Init(LCD_RD_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RD引脚 */
gpio_init_struct.Pin = LCD_RS_GPIO_PIN;
HAL_GPIO_Init(LCD_RS_GPIO_PORT, &gpio_init_struct); /* 初始化LCD_RS引脚 */
gpio_init_struct.Pin = LCD_DATA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
HAL_GPIO_Init(LCD_DATA_GPIO_PORT, &gpio_init_struct); /* LCD_DATA引脚模式设置 */
LCD_WR(1); /* WR 默认高电平 */
LCD_RD(1); /* RD 默认高电平 */
LCD_CS(1); /* CS 默认高电平 */
LCD_RS(1); /* RS 默认高电平 */
LCD_DATA_OUT(0XFFFF); /* DATA 默认高电平 */
/* 读取ID */
lcd_wr_regno(0xD3);
lcddev.id = lcd_rd_data(); /* 假读 */
lcddev.id = lcd_rd_data(); /* 00 */
lcddev.id = lcd_rd_data(); /* 93 */
lcddev.id <<= 8;
lcddev.id |= lcd_rd_data(); /* 41 */
printf("lcddev_id:%#x \r\n", lcddev.id);
/* 完成初始化序列 */
if (lcddev.id == 0x9341)
lcd_ex_ili9341_reginit();
else
lcd_ex_st7789_reginit();
/* 对LCD控制结构体赋值 */
lcddev.width = 240;
lcddev.height = 320;
lcddev.setxcmd = 0x2A;
lcddev.setycmd = 0x2B;
lcddev.wramcmd = 0x2C;
lcd_wr_regno(lcddev.setxcmd);
lcd_wr_data(0);
lcd_wr_data(0);
lcd_wr_data((lcddev.width - 1) >> 8);
lcd_wr_data((lcddev.width - 1) & 0XFF);
lcd_wr_regno(lcddev.setycmd);
lcd_wr_data(0);
lcd_wr_data(0);
lcd_wr_data((lcddev.height - 1) >> 8);
lcd_wr_data((lcddev.height - 1) & 0XFF);
/* 设置扫描方向 */
lcd_write_reg(0x36, 1 << 3);
/* 点亮背光 */
LCD_BL(1);
/* lcd_clear */
lcd_clear(0xFFFF);
}
lcd.h
#ifndef __LCD_H
#define __LCD_H
#include "stdlib.h"
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* LCD RST/WR/RD/BL/CS/RS 引脚 定义
* LCD_D0~D15,由于引脚太多,就不在这里定义了,直接在lcd_init里面修改.所以在移植的时候,除了改
* 这6个IO口, 还得改LCD_Init里面的D0~D15所在的IO口.
*/
#define LCD_BL_GPIO_PORT GPIOC
#define LCD_BL_GPIO_PIN GPIO_PIN_10
#define LCD_BL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
#define LCD_WR_GPIO_PORT GPIOC
#define LCD_WR_GPIO_PIN GPIO_PIN_7
#define LCD_WR_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
#define LCD_RD_GPIO_PORT GPIOC
#define LCD_RD_GPIO_PIN GPIO_PIN_6
#define LCD_RD_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
#define LCD_CS_GPIO_PORT GPIOC
#define LCD_CS_GPIO_PIN GPIO_PIN_9
#define LCD_CS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
#define LCD_RS_GPIO_PORT GPIOC
#define LCD_RS_GPIO_PIN GPIO_PIN_8
#define LCD_RS_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
#define LCD_DATA_GPIO_PORT GPIOB
#define LCD_DATA_GPIO_PIN GPIO_PIN_All /* 16个IO都用到 */
#define LCD_DATA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* 所在IO口时钟使能 */
/* LCD 端口控制函数定义 */
/* 利用操作寄存器的方式控制IO引脚提高屏幕的刷新率 */
#define LCD_BL(x) LCD_BL_GPIO_PORT->BSRR = LCD_BL_GPIO_PIN << (16 * (!x)) /* 设置BL引脚 */
#define LCD_WR(x) LCD_WR_GPIO_PORT->BSRR = LCD_WR_GPIO_PIN << (16 * (!x)) /* 设置WR引脚 */
#define LCD_RD(x) LCD_RD_GPIO_PORT->BSRR = LCD_RD_GPIO_PIN << (16 * (!x)) /* 设置RD引脚 */
#define LCD_CS(x) LCD_CS_GPIO_PORT->BSRR = LCD_CS_GPIO_PIN << (16 * (!x)) /* 设置CS引脚 */
#define LCD_RS(x) LCD_RS_GPIO_PORT->BSRR = LCD_RS_GPIO_PIN << (16 * (!x)) /* 设置RS引脚 */
#define LCD_DATA_OUT(x) LCD_DATA_GPIO_PORT->ODR = x /* 写B0~B15引脚 */
#define LCD_DATA_IN LCD_DATA_GPIO_PORT->IDR /* 读B0~B15引脚 */
/* 以下是使用HAL库方式控制IO引脚 */
///* LCD背光控制 */
//#define LCD_BL(x) do{ x ? \
// HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_SET) : \
// HAL_GPIO_WritePin(LCD_BL_GPIO_PORT, LCD_BL_GPIO_PIN, GPIO_PIN_RESET); \
// }while(0)
//#define LCD_WR(x) do{ x ? \
// HAL_GPIO_WritePin(LCD_WR_GPIO_PORT, LCD_WR_GPIO_PIN, GPIO_PIN_SET) : \
// HAL_GPIO_WritePin(LCD_WR_GPIO_PORT, LCD_WR_GPIO_PIN, GPIO_PIN_RESET); \
// }while(0) /* 设置WR引脚 */
//#define LCD_RD(x) do{ x ? \
// HAL_GPIO_WritePin(LCD_RD_GPIO_PORT, LCD_RD_GPIO_PIN, GPIO_PIN_SET) : \
// HAL_GPIO_WritePin(LCD_RD_GPIO_PORT, LCD_RD_GPIO_PIN, GPIO_PIN_RESET); \
// }while(0) /* 设置RD引脚 */
//#define LCD_CS(x) do{ x ? \
// HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_GPIO_PIN, GPIO_PIN_SET) : \
// HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_GPIO_PIN, GPIO_PIN_RESET); \
// }while(0) /* 设置CS引脚 */
//#define LCD_RS(x) do{ x ? \
// HAL_GPIO_WritePin(LCD_RS_GPIO_PORT, LCD_RS_GPIO_PIN, GPIO_PIN_SET) : \
// HAL_GPIO_WritePin(LCD_RS_GPIO_PORT, LCD_RS_GPIO_PIN, GPIO_PIN_RESET); \
// }while(0) /* 设置RS引脚 */
/******************************************************************************************/
/* LCD重要参数集 */
typedef struct
{
uint16_t width; /* LCD 宽度 */
uint16_t height; /* LCD 高度 */
uint16_t id; /* LCD ID */
uint8_t dir; /* 横屏还是竖屏控制:0,竖屏;1,横屏。 */
uint16_t wramcmd; /* 开始写gram指令 */
uint16_t setxcmd; /* 设置x坐标指令 */
uint16_t setycmd; /* 设置y坐标指令 */
} _lcd_dev;
/* LCD参数 */
extern _lcd_dev lcddev; /* 管理LCD重要参数 */
/* 常用画笔颜色 */
#define WHITE 0xFFFF /* 白色 */
#define BLACK 0x0000 /* 黑色 */
#define RED 0xF800 /* 红色 */
#define GREEN 0x07E0 /* 绿色 */
#define BLUE 0x001F /* 蓝色 */
#define MAGENTA 0XF81F /* 品红色/紫红色 = BLUE + RED */
#define YELLOW 0XFFE0 /* 黄色 = GREEN + RED */
#define CYAN 0X07FF /* 青色 = GREEN + BLUE */
/* 非常用颜色 */
#define BROWN 0XBC40 /* 棕色 */
#define BRRED 0XFC07 /* 棕红色 */
#define GRAY 0X8430 /* 灰色 */
#define DARKBLUE 0X01CF /* 深蓝色 */
#define LIGHTBLUE 0X7D7C /* 浅蓝色 */
#define GRAYBLUE 0X5458 /* 灰蓝色 */
#define LIGHTGREEN 0X841F /* 浅绿色 */
#define LGRAY 0XC618 /* 浅灰色(PANNEL),窗体背景色 */
#define LGRAYBLUE 0XA651 /* 浅灰蓝色(中间层颜色) */
#define LBBLUE 0X2B12 /* 浅棕蓝色(选择条目的反色) */
/* 函数申明 */
void lcd_init(void); /* lcd初始化函数 */
void lcd_wr_data (uint16_t data); /* 8080写数据 */
void lcd_wr_regno(volatile uint16_t regno); /* 8080写命令 */
void lcd_write_reg(uint16_t regno, uint16_t data); /* 往寄存器写值 */
uint16_t lcd_rd_data(void); /* 8080读数据 */
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color);
uint16_t lcd_read_point (uint16_t x, uint16_t y);
void lcd_show_char(uint16_t x, uint16_t y, char chr, uint8_t size, uint8_t mode, uint16_t color);
void lcd_clear(uint16_t color);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
int main(void)
{
uint16_t point_value = 0;
uint8_t x = 0;
HAL_Init(); /* HAL库初始化 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init();
lcd_draw_point(0, 0, RED);
lcd_draw_point(0, 1, RED);
lcd_draw_point(100, 100, RED);
/* 0xF800 */
point_value = lcd_read_point(100, 100);
printf("point_value:%#x \r\n",point_value);
lcd_show_char(0, 0, 'A', 16, 1, RED);
lcd_show_char(100, 100, 'B', 24, 1, RED);
while (1)
{
switch (x)
{
case 0:
lcd_clear(WHITE);
break;
case 1:
lcd_clear(BLACK);
break;
case 2:
lcd_clear(BLUE);
break;
case 3:
lcd_clear(RED);
break;
case 4:
lcd_clear(MAGENTA);
break;
case 5:
lcd_clear(GREEN);
break;
case 6:
lcd_clear(CYAN);
break;
case 7:
lcd_clear(YELLOW);
break;
case 8:
lcd_clear(BRRED);
break;
case 9:
lcd_clear(GRAY);
break;
case 10:
lcd_clear(LGRAY);
break;
case 11:
lcd_clear(BROWN);
break;
}
x++;
if (x == 12)
x = 0;
LED0_TOGGLE(); /* 红灯闪烁 */
delay_ms(1000);
}
}