目录
前言
作者工作上在液晶驱动上踩了不少坑,在成功驱动成功后,写下一篇快速应用的实操文章,给自己与后来者节省时间快速开发。
所用硬件
芯片:STM32F103RCT6
同款显示屏链接(无广):淘宝地址
液晶驱动:ST7735(ST7789区别不大,原理相同)
驱动方式:8位并口
液晶分辨率:128*160(其他分辨率自行修改代码)
硬件知识
引脚接线
首先确定屏幕接线,看明白屏幕的引脚图正确接到芯片的引脚才能正常驱动,如下为实验用的屏幕引脚图和原理图接线
简单讲解一下各自引脚的作用
BD0-7:8位并口传输数据,建议用一组连续的IO组,如PC0-7(作为颜色\指令数据传输,一次性传8位,一般驱动为RGB565,16位颜色数据,可以分两次传输一个像素的颜色)
LCD_CS:用于进行片选(一次完成指令\数据传输之间进行开启关闭)
LCD_RS:用于切换指令\数据(在初始化、显示代码中会频繁用到指令和数据切换)
LCD_WR:写指令控制
LCD_RD:读指令控制(如果没有读取需求可以不用,反正我没用到)
RESET:LCD复位,一般代码中也就初始化复位一次
LCD_LED:用于开启LCD背光,可以看到原理图上用于驱动三极管
软件部分
软件部分分为cubemx配置+驱动代码
代码分段讲解,最后会整合成完整代码放到最后,建议先看分段讲解,否则设计上稍微有些不同都有可能驱动不起来,还不知道怎么改。
cubemx引脚配置
高速晶振配置
时钟配置
引脚配置
选择STlink调试上传(自行选择自己的调试方式)
驱动代码
新建空白文件,命名为ST7735.c、ST7735.h,并添加到工程中
ST7735.h
主要内容分为分辨率宏定义、lcd结构体、IO宏定义寄存器驱动
为了提高代码驱动效率,各IO口用寄存器的形式驱动
#ifndef __ST7735_H__
#define __ST7735_H__
#include "main.h"
//默认为竖屏的分辨率
#define LCD_WIDTH 128
#define LCD_HEIGHT 160
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;
#define LCD_CS_C LCD_CS_GPIO_Port->BRR=LCD_CS_Pin
#define LCD_DC_C LCD_RS_GPIO_Port->BRR=LCD_RS_Pin
#define LCD_WRB_C LCD_WR_GPIO_Port->BRR=LCD_WR_Pin
#define LCD_RDB_C LCD_RD_GPIO_Port->BRR=LCD_RD_Pin
#define LCD_RES_C RESET_GPIO_Port->BRR=RESET_Pin
#define LCD_CS_S LCD_CS_GPIO_Port->BSRR=LCD_CS_Pin
#define LCD_DC_S LCD_RS_GPIO_Port->BSRR=LCD_RS_Pin
#define LCD_WRB_S LCD_WR_GPIO_Port->BSRR=LCD_WR_Pin
#define LCD_RDB_S LCD_RD_GPIO_Port->BSRR=LCD_RD_Pin
#define LCD_RES_S RESET_GPIO_Port->BSRR=RESET_Pin
//处理8位数据同步PC0~7传输
#define LCD_DATA(x) GPIOC->BSRR=(((uint32_t)(uint8_t)~(x))<<16)|((uint32_t)(uint8_t)(x))
#define RBG(r,g,b) (uint16_t)((r<<11)|(g<<5)|(b))
void lcd_init(void);
void lcd_set_point_color(uint8_t x_point, uint8_t y_point, uint16_t color);
void lcd_set_area_color(uint8_t x_start, uint8_t y_start, uint8_t x_end, uint8_t y_end, uint16_t color);
void lcd_set_color(uint16_t color);
void LCD_Display_Dir(uint8_t dir);
#endif
LCD_DATA工作原理分解:
- (uint8_t)(x):将数据强制转换为8位(使用PC0-PC7共8根数据线)
- ((uint32_t)(uint8_t)~(x))<<16:取反后左移16位(对应BSRR的高16位清除位)
- |运算:组合设置位和清除位
- 最终效果:
- 低16位设置对应bit为1的引脚
- 高16位清除对应bit为0的引脚
等效操作: - 当x的某bit为1时:设置对应GPIO为高电平
- 当x的某bit为0时:清除对应GPIO为低电平
例如x=0xA5(1010 0101)时: - 设置位:1010 0101(PC7,PC5,PC2,PC0置高)
- 清除位:0101 1010(PC6,PC4,PC3,PC1置低)
简而言之,STM32的BSRR寄存器实现GPIO端口一次性设置8位数据
ST7735.c
写数据、写指令、写16位颜色数据、复位函数
// 写数据: DC = 0
void LCD_WR_DATA(uint8_t data)
{
LCD_DC_S; // 拉高-数据
LCD_CS_C;
LCD_DATA(data);
LCD_WRB_C;
LCD_WRB_S;
LCD_CS_S;
}
// 写命令: DC = 1
void LCD_WR_REG(uint8_t data)
{
LCD_DC_C; // 拉低-命令
LCD_CS_C;
LCD_DATA(data);
LCD_WRB_C;
LCD_WRB_S;
LCD_CS_S;
}
// 写十六位数据
void lcd_write_data_u16(uint16_t data)
{
LCD_WRB_C;
LCD_DATA(data >> 8);
LCD_WRB_S;
LCD_WRB_C;
LCD_DATA(data & 0xff);
LCD_WRB_S;
}
// 复位
void lcd_reset()
{
LCD_RES_S; // RST引脚拉高
HAL_Delay(120); // Delay(10000); // 复位后等待120ms
LCD_RES_C; // RST引脚拉低
HAL_Delay(120); // Delay(100); // 要求大于10uS
LCD_RES_S; // RST引脚拉高
HAL_Delay(120); // Delay(30000); // 复位后等待120ms
}
额外扩展:设置屏幕方向(非必需,不用到可以不加)
// dir:方向选择 0-0度旋转,1-180度旋转,2-270度旋转,3-90度旋转
void LCD_Display_Dir(uint8_t dir)
{
lcddev.wramcmd = 0X2C;
lcddev.setxcmd = 0X2A;
lcddev.setycmd = 0X2B;
if (dir == 0 || dir == 1) // 竖屏
{
lcddev.dir = 0; // 竖屏
lcddev.width = LCD_WIDTH;
lcddev.height = LCD_HEIGHT;
if (dir == 0) // 0-0度旋转
{
LCD_WR_REG(0x36);
LCD_WR_DATA((0 << 3) | (1 << 7) | (1 << 6) | (0 << 5));
}
else // 1-180度旋转
{
LCD_WR_REG(0x36);
LCD_WR_DATA((0 << 3) | (0 << 7) | (0 << 6) | (0 << 5));
}
}
else if (dir == 2 || dir == 3)
{
lcddev.dir = 1; // 横屏
lcddev.width = LCD_HEIGHT;//此处横屏调转分辨率
lcddev.height = LCD_WIDTH;
if (dir == 2) // 2-270度旋转
{
LCD_WR_REG(0x36);
LCD_WR_DATA((0 << 3) | (0 << 7) | (1 << 6) | (1 << 5));
}
else // 3-90度旋转
{
LCD_WR_REG(0x36);
LCD_WR_DATA((0 << 3) | (1 << 7) | (0 << 6) | (1 << 5));
}
}
// 设置显示区域
LCD_WR_REG(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_REG(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初始化代码(重要,不同厂家屏幕此处要修改)
在实操使用情况都是先跟屏幕厂家拿初始化时序,不同屏幕先拿到厂家提供时序代码,再来修改使用,一般厂家会提供,自己瞎试很费劲,如果时序不对,驱动起来屏幕会花屏
一般厂家会提供如下东西,最起码提供初始化时序才能正常使用
void lcd_init(void)
{
lcd_reset(); // Reset before LCD Init.
LCD_RDB_S;
//************* Start Initial Sequence **********//
HAL_Delay(120); // ms
LCD_WR_REG(0x11); // Sleep out
HAL_Delay(120); // delay_ms 120ms
LCD_WR_REG(0xB1); // In normal mode
LCD_WR_DATA(0x00); // frame rate=85.3Hz
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x2B);
LCD_WR_REG(0xB2); // In Idle mode
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x01);
LCD_WR_REG(0xB3); // In partial mode
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x01);
LCD_WR_DATA(0x01);
LCD_WR_REG(0xB4); // DOT inversion Display Inversion Control
LCD_WR_DATA(0x03);
LCD_WR_REG(0xB9); // In normal mode
LCD_WR_DATA(0xFF);
LCD_WR_DATA(0x83);
LCD_WR_DATA(0x47);
LCD_WR_REG(0xC0); // VRH: Set the GVDD
LCD_WR_DATA(0xA2);
LCD_WR_DATA(0x02);
LCD_WR_DATA(0x84);
LCD_WR_REG(0xC1); // set VGH/ VGL
LCD_WR_DATA(0x02); //??02 VGH=16.6 VGL=-7.5 00 VGH=11.6 VGL=-7.5 06 VGH=16.6 VGL=-10
LCD_WR_REG(0xC2); // APA: adjust the operational amplifier DCA: adjust the booster Voltage
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x00);
LCD_WR_REG(0xC3); // In Idle mode (8-colors)
LCD_WR_DATA(0x8A);
LCD_WR_DATA(0x2A);
LCD_WR_REG(0xC4); // In partial mode + Full color
LCD_WR_DATA(0x8A);
LCD_WR_DATA(0xEE);
LCD_WR_REG(0xC5); // VCOM
LCD_WR_DATA(0x09);
LCD_WR_REG(0x20); // Display inversion
LCD_WR_REG(0xC7);
LCD_WR_DATA(0x10);
LCD_WR_REG(0x36); // MX, MY, RGB mode
LCD_WR_DATA(0xC0); // 08
LCD_WR_REG(0xE0);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x1C);
LCD_WR_DATA(0x1B);
LCD_WR_DATA(0x1A);
LCD_WR_DATA(0x2F);
LCD_WR_DATA(0x28);
LCD_WR_DATA(0x20);
LCD_WR_DATA(0x24);
LCD_WR_DATA(0x23);
LCD_WR_DATA(0x22);
LCD_WR_DATA(0x2A);
LCD_WR_DATA(0x36);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x05);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x10);
LCD_WR_REG(0xE1);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x1A);
LCD_WR_DATA(0x1A);
LCD_WR_DATA(0x1A);
LCD_WR_DATA(0x2E);
LCD_WR_DATA(0x27);
LCD_WR_DATA(0x21);
LCD_WR_DATA(0x24);
LCD_WR_DATA(0x24);
LCD_WR_DATA(0x22);
LCD_WR_DATA(0x2A);
LCD_WR_DATA(0x35);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x05);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x10);
LCD_WR_REG(0x35); // 65k mode
LCD_WR_DATA(0x00);
LCD_WR_REG(0x3A); // 65k mode
LCD_WR_DATA(0x05);
LCD_WR_REG(0x29); // Display on
}
写入窗口地址、描点、矩形填充、刷屏函数
// 写入屏幕地址函数
void lcd_write_address(uint8_t x_start, uint8_t y_start, uint8_t x_end, uint8_t y_end)
{
LCD_WR_REG(0x2a); // 设置列地址指令
LCD_WR_DATA(x_start >> 8);
LCD_WR_DATA(x_start & 0xff);
LCD_WR_DATA(x_end >> 8);
LCD_WR_DATA(x_end & 0xff);
LCD_WR_REG(0x2b); // 设置行地址指令
LCD_WR_DATA(y_start >> 8);
LCD_WR_DATA(y_start & 0xff);
LCD_WR_DATA(y_end >> 8);
LCD_WR_DATA(y_end & 0xff);
LCD_WR_REG(0x2c); // 写入数据指令
}
// 描点函数
void lcd_set_point_color(uint8_t x_point, uint8_t y_point, uint16_t color)
{
lcd_write_address(x_point, y_point, x_point, y_point);
LCD_DC_S;
LCD_CS_C; // 拉低-片选
lcd_write_data_u16(color);
LCD_CS_S; // 拉低-片选
}
// 区域颜色填充
void lcd_set_area_color(uint8_t x_start, uint8_t y_start, uint8_t x_end, uint8_t y_end, uint16_t color)
{
uint8_t i, j;
uint8_t x_len, y_len;
lcd_write_address(x_start, y_start, x_end, y_end);
// 计算填充区域的长度和宽度,终点坐标减起点坐标+1
x_len = x_end - x_start + 1; // 计算x坐标的长度
y_len = y_end - y_start + 1; // 计算y坐标的长度
LCD_CS_C; // 拉低-片选
LCD_DC_S; // 拉高-数据
for (i = 0; i < y_len; i++)
{
for (j = 0; j < x_len; j++)
{
LCD_WRB_C;
LCD_DATA(color >> 8);
LCD_WRB_S;
LCD_WRB_C;
LCD_DATA(color & 0xff);
LCD_WRB_S;
//以上内容等效于这个函数,但是执行次数较多,避免调用浪费时间,不用打包函数
// lcd_write_data_u16(color);
}
}
LCD_CS_S; // 拉高-片选
}
// 全屏颜色填充
void lcd_set_color(uint16_t color)
{
if(lcddev.dir==0)
{
lcd_set_area_color(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, color);
}
else
{
lcd_set_area_color(0, 0, LCD_HEIGHT-1, LCD_WIDTH-1, color);
}
}
main函数使用
使用全屏刷新和画点函数测试,效果是红绿蓝白+一条黑线间隔一秒循环
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
lcd_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
// LCD_Display_Dir(2);
while (1)
{
lcd_set_color(RBG(255,0,0));
HAL_Delay(1000);
lcd_set_color(RBG(0,255,0));
HAL_Delay(1000);
lcd_set_color(RBG(0,0,255));
HAL_Delay(1000);
lcd_set_color(RBG(255,255,255));
HAL_Delay(1000);
for(int i=0;i<128;i++)
{
lcd_set_point_color(i,100,RBG(0,0,0));
}
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
以上就是最简单的操作显示屏的代码了,如果显示屏需要显示GUI的话移植LVGL调用画点\矩形函数即可。
实操碎碎念(视频复现)
技术分享——STM32驱动8位并口屏(ST7735)
资源下载(git\百度网盘)
git下载指令
git clone https://github.com/zwalkon/LCD_8bit.git
通过网盘分享的文件:LCD_8bit.zip
链接: https://pan.baidu.com/s/1eV_7007jB58K3DkMe7mY-w 提取码: z3dq
如果帮助到你不妨给个免费的赞👍吧,我会更有动力创作内容