STM32驱动ST7735\ST7789显示屏(8位并口驱动)——CUBEMX、HAL

前言

作者工作上在液晶驱动上踩了不少坑,在成功驱动成功后,写下一篇快速应用的实操文章,给自己与后来者节省时间快速开发。

所用硬件

芯片: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工作原理分解:

  1. (uint8_t)(x):将数据强制转换为8位(使用PC0-PC7共8根数据线)
  2. ((uint32_t)(uint8_t)~(x))<<16:取反后左移16位(对应BSRR的高16位清除位)
  3. |运算:组合设置位和清除位
  4. 最终效果:
  • 低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

如果帮助到你不妨给个免费的赞👍吧,我会更有动力创作内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值