STM32CubeMX | 35-使用硬件FSMC驱动TFT-LCD屏幕(MCU屏,NT35510控制器)

本篇详细的记录了如何使用STM32CubeMX配置 STM32f407ZGT6 的硬件FSMC外设驱动TFT-LCD屏幕。

1. 准备工作

硬件准备

  • 开发板
    首先需要准备一个开发板,这里我准备的是STM32F407ZGT6的开发板。

  • TFT-LCD
    开发板底板接正点原子4.3寸TFT-LCD。

2. STM32 FSMC外设概述

2.1. 什么是FSMC

FSMC全称 Flexible static memory controller,灵活的静态内存控制器,顾名思义,其主要作用是:负责向外部扩展的存储类设备提供控制信号

FSMC内存控制器支持的存储设备有:

  • Nor Flash、SRAM、PSRAM
  • Nand Flash
  • 类SRAM设备

2.2. FSMC外设的功能框图

在这里插入图片描述

2.3. 外部设备的地址映射(重点)

从FSMC的角度来看,外部的存储设备被分为几个固定大小的Bank,每个bank 256 MB

整个FSMC外设映射地址的划分如图:

2.3.1. Bank1

Bank1的地址空间为:0x6000 0000 - 0x6FFF FFFF,支持外接Nor Flash、PSRAM、SRAM等设备,还可以外接DM9000等类存储设备。

整个Bank1的地址空间被划分为四个子bank,每个子bank的大小为64MB,刚好对应FSMC外设的地址总线(FSMC_A[0:25])有26条(2^26=64MB)。

FSMC还有两条内部总线ADDR[27:26],用这两路控制片选信号,如下表:

BANK1控制时序模型

接下来讲述BANK1控制外部存储器的时序模式,BANK1又称为Nor Flash/SRAM/PSRAM控制器,后续暂且叫它SRAM控制器。

SRAM控制器支持两种控制模式:

  • 同步模式
  • 异步模式

对于异步模式,FSMC主要设置三个时序参数:

  • 地址建立时间:ADDSET
  • 数据建立时间:DATASET
  • 地址保持时间:ADDHLD

根据SRAM、PSRAM、Nor Flash的综合特点,FMC定义了四种不同的异步时序模型,如下表:

本文中控制TFT-LCD使用的就是异步ModeA时序模型

异步ModeA时序模型

模式A时序模型的优势在于:支持独立的读写时序控制。这一点对于控制TFT-LCD来说,非常符合。因为TFT-LCD在读的时候,一般比较慢,而在写入的时候一般比较快。

模式A的读操作时序如图:

模式A的写操作时序如图:

图中ADDSET和DATASET两个时序的值,后续配置的时候会详细讲述。

2.3.2. Bank2、3/4

只能外接Nand Flash设备和PC Card设备:

3. 使用STM32CubeMX生成工程

选择芯片型号

打开STM32CubeMX,打开MCU选择器:

搜索并选中芯片STM32F407ZGT6

配置时钟源

  • 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
  • 如果使用默认内部时钟(HSI),这一步可以略过;

这里我都使用外部时钟:

调试选项配置

默认没有配置下载引脚,烧录之后下载器将无法再检测到,这里我使用ST-Link,所以配置为SW选项:

配置串口

开发板板载了一个CH340换串口,连接到USART1,但是引脚不是默认引脚,需要手动修改。

接下来开始配置USART1

配置FSMC外设

本文所使用的开发板中,将TFT-LCD当做SRAM来操作,连接在FSMC的BANK1的第4个区域。
知识点:为什么TFT-LCD可以当做SRAM来控制?
因为TFT-LCD和SRAM相比,同样需要D0-D15数据线,WR、RD、CS控制线,唯一不同的就是TFT-LCD需要一条RS信号线(用于控制传输的是命令还是数据),而SRAM则需要一堆地址线,所以可以巧妙的使用任意一条地址线来当做RS信号。

FSMC配置

开发板上 TFT-LCD 的原理图如下:

通过原理图可以看出:

  • LCD D0-D15:使用了16bit:FSMC D0 - FSMC D15;
  • LCD_RS:使用FSMC A6来控制向LCD写入数据还是命令(0-命令,1-数据);
  • LCD_BL:背光控制,对应PB5;
  • LCD_CS:LCD片选信号,FMC_NE4,表示使用Bank1的Bank4子区域
  • LCD_WR :LCD写使能,FSMC_NWE;
  • LCD_RD:LCD读使能,FSMC_NOE;
  • RESET:LCD复位信号,直接与单片机复位信号接在一起;

根据这些信息,在STM32CubeMX中先配置SRAM4的基本设置:

此处如果选择LCD接口类型和SRAM类型的区别在于:
LCD接口类型只会配置用到的那一个地址引脚,而SRAM类型则会配置所有的地址引脚。

SRAM基本参数配置

首先设置基本的参数,允许读与写使用不同的模式:

SRAM时序参数配置

本文中使用的LCD控制器为NT35510控制器,找到其数据手册,查看:

其中主要的时序参数配置方法如下。

读时序配置

① HCLK

时序参数都是以HCLK的周期为单位的,在本文中HCLK=168Mhz,所以一个周期为5.95ns。

② 地址建立时间:Address setup time(ADDSET)

该时序的最大值的15个HCLK,从图中可以看出,NT35110控制器要求读的时候最小为10ns,,所以设为2即可,2x5.95=11.9ns

③ 数据持续时间:Data setup time(DATASET)

读时序比较慢,该时序的最大值为255个HCLK,从图中可以看出,NT35510控制器要求的数据建立时间最小为15ns,但因为读时序比较慢,所以设为4,4x5.95=23.8ns。

写时序配置

① HCLK

时序参数都是以HCLK的周期为单位的,在本文中HCLK=168Mhz,所以一个周期为5.95ns。

② 地址建立时间:Address setup time(ADDSET)

该时序的最大值为15个HCLK,NT35110控制器要求写的时候最小为0,,所以设为0即可

③ 数据持续时间:Data setup time(DATASET)

写时序比较快,该时序的最大值为255个HCLK,图中可以看出,NT35510控制器要求的数据建立时间最小为15ns,但因为读时序比较慢,所以设为3,3x5.95=17.85ns。

综合上述计算,配置情况如下:

配置背光引脚

配置时钟树

STM32F407ZGT6的最高主频到168M,使HCLK = 168Mhz即可:
在这里插入图片描述

生成工程设置

代码生成设置

最后设置生成独立的初始化文件:

生成代码

点击GENERATE CODE即可生成MDK-V5工程:

4. 编写TFT-LCD驱动(测试是否可以正常读写ID)

特别提醒:STM32CubeMX生成的工程默认开启了-O3优化,编写的驱动太菜了,会出问题,所以遇到玄学Bug请改为-O0优化!

封装底层发送/读取函数

LCD的底层无非就是两个API:发送命令、发送数据,(有的还需要从屏幕读取数据),接下来封装出这两(三)个底层API。

之前查看原理图的时候,表示命令或者数据的LCD_RS控制引脚接在FMC_A6上,也就是说地址数据的第6位,所以在头文件lcd-fsmc.h中先定义:

/* 通过地址线控制RS引脚 */
#define LCD_CMD_ADDR            0x6c00007E
#define LCD_DAT_ADDR            0x6c000080

接着开始封装两个(三个)底层操作函数:

① 发送命令函数:

/**
 * @brief    向LCD写入命令
 * @param    cmd 待写入命令
 * @retval   none
*/
static void lcd_write_cmd(__IO uint16_t cmd)
{
    *(uint16_t *)(LCD_CMD_ADDR) = cmd;
}

② 发送数据函数:

/**
 * @brief    向LCD写入数据
 * @param    data 待写入数据
 * @retval   none
*/
static void lcd_write_data(__IO uint16_t data)
{
    *(uint16_t *)(LCD_DAT_ADDR) = data;
}

③ 读取数据函数:

/**
 * @brief    从LCD读取数据
 * @param    none
 * @retval   读取到的数据
*/
static uint16_t lcd_read_data(void)
{
    __IO uint16_t data;
    
    data = *(uint16_t *)(LCD_DAT_ADDR);
    
    return data;
}

基于这三个底层API,还可以封装出读写LCD内部寄存器的函数:

/**
 * @brief    写LCD中的寄存器
 * @param    reg  寄存器序号
 * @param    data 要写入寄存器的值
 * @retval   none
*/
static void lcd_write_reg(__IO uint16_t reg, __IO uint16_t data)
{
    lcd_write_cmd(reg);
    lcd_write_data(data);
}

LCD控制参数结构体

为了方便驱动不同的IC,保存不同的控制参数,在lcd_fmc.h中封装如下数据类型:

/**
 * @brief    保存LCD屏幕参数
 * @param    lcd_width     LCD屏幕宽度
 * @param    lcd_height    LCD屏幕高度
 * @param    lcd_id        LCD 驱动IC ID
 * @param    lcd_direction LCD横屏显示还是竖屏显示,0-竖屏,1-横屏
 * @param    wram_cmd      开始写gram指令
 * @param    set_x_cmd     设置x坐标指令
 * @param    set_y_cmd     设置y坐标指令
*/
typedef struct lcd_params_st {
    uint16_t lcd_width;
    uint16_t lcd_height;
    uint16_t lcd_id;
    uint8_t  lcd_direction;
    uint16_t wram_cmd;
    uint16_t set_x_cmd;
    uint16_t set_y_cmd;
} lcd_params_t;

然后在头文件中声明外部变量定义,方便其他程序访问:

extern lcd_params_t lcd_params;

lcd_fsmc.c中定义此变量为全局变量:

lcd_params_t lcd_params;

LCD驱动打印日志的处理

为了方便程序开发,难免要打印一些日志,但是如果printf没有被重定向,则会导致LCD驱动卡死。为了避免这个问题,我们使用宏开关的方式来控制是否打印。

lcd_fsmc.h中定义此宏开关:

/* 使能此驱动是否打印调试日志(需要printf支持) */
#define LCD_LOG_ENABLE          1

接着可以定义一个日志打印函数:

#if LCD_LOG_ENABLE
#include <stdio.h>
#define LCD_LOG printf
#else
#define LCD_LOG(format,...)
#endif

之后所以需要打印的地方使用LCD_LOG代替printf即可。

编写LCD控制器ID读取函数

通过主动读取此控制器ID,可以自动检测出是哪种类型的控制器,然后执行不同的驱动代码:

static int lcd_read_id(void)
{
    /* 尝试执行ILI9341控制器ID的读取流程 */
    lcd_write_cmd(0XD3);				   
	lcd_params.lcd_id = lcd_read_data();
	lcd_params.lcd_id = lcd_read_data();
	lcd_params.lcd_id = lcd_read_data();				   
	lcd_params.lcd_id <<= 8;
	lcd_params.lcd_id |= lcd_read_data();
    /* 如果正常读到,则返回成功 */
    if (lcd_params.lcd_id == 0x9341) {
        return 0;
    }
    
    /* 尝试执行NT35310控制器ID的读取流程 */
    lcd_write_cmd(0XD4);				   
    lcd_params.lcd_id = lcd_read_data();
    lcd_params.lcd_id = lcd_read_data();
    lcd_params.lcd_id = lcd_read_data();
    lcd_params.lcd_id <<= 8;	 
    lcd_params.lcd_id |= lcd_read_data();
    /* 如果正常读到,则返回成功 */
    if (lcd_params.lcd_id == 0x5310) {
        return 0;
    }
    
    /* 尝试执行NT35510控制器ID的读取流程 */
    lcd_write_cmd(0XDA00);	
    lcd_params.lcd_id = lcd_read_data();
    lcd_write_cmd(0XDB00);	
    lcd_params.lcd_id = lcd_read_data();
    lcd_params.lcd_id <<= 8;	 
    lcd_write_cmd(0XDC00);	
    lcd_params.lcd_id |= lcd_read_data();
    /* 如果正常读到,则返回成功 */
    if (lcd_params.lcd_id == 0x8000) {
        lcd_params.lcd_id = 0x5510;
        return 0;
    }
   
    /* 驱动IC不支持 */
    lcd_params.lcd_id = 0;
    return -1;
}

编写LCD初始化函数

LCD初始化需要发送大量的命令和数据,本文限于篇幅,只给出读LCD 控制IC的ID的部分,用来测试LCD是否能正常读写足矣。

void lcd_init(void)
{ 	
    /* 初始化FMC接口 */
    //MX_FSMC_Init();
    
    /* 开启背光 */
    HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);
    
 	HAL_Delay(50); 
	
 	/* 读取LCD控制器IC */
    if (lcd_read_id() == -1) {
        LCD_LOG("Not Support LCD IC!\r\n");
        return;
    } else {
        LCD_LOG("LCD IC ID is:%#x\r\n", lcd_params.lcd_id);  
    }
	
	return;
}


lcd_fsmc.h中声明该函数:

void lcd_init(void);

测试是否可以正常操作LCD

main.c中包含进来头文件:

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "lcd_fsmc.h"
/* USER CODE END Includes */

然后在man函数中调用:

/* USER CODE BEGIN 2 */
printf("4.3' TFT-LCD Test By Mculover666\r\n");
lcd_init();
/* USER CODE END 2 */

编译,下载,在串口助手中查看结果:

5. 编写TFT-LCD驱动(初始化、刷屏测试)

可以正常读取ID之后,接下来的工作是:

  • 发送一堆一堆的命令,初始化屏幕;
  • 设置坐标
  • 清屏
  • 刷屏测试

① LCD开显示、关显示、LCD设置扫描方向、 LCD设置显示方向、LCD设置光标位置这些函数代码不多,需要的话请查看源码。

② 清屏函数:


static void lcd_write_ram_start(void)
{
    lcd_write_cmd(lcd_params.wram_cmd);
}

static void lcd_write_ram(uint16_t rgb_color)
{
    lcd_write_data(rgb_color);
}

void lcd_clear(uint16_t color)
{
	uint32_t index = 0;      
	uint32_t totalpoint = lcd_params.lcd_width;
    
    /* 计算得到总点数 */
	totalpoint *= lcd_params.lcd_height;
    
    /* 设置光标位置 */
	lcd_set_cursor(0x00,0x0000);
    
    /* 开始写入GRAM */
	lcd_write_ram_start();
    
    /* 写入数据到GRAM */
	for (index = 0; index < totalpoint; index++) {
		lcd_write_ram(color);
	}
}

③ 初始化函数:代码过长,请查看源码。

这三类函数实现完之后,就可以编写一个如下的刷屏测试函数:

void lcd_auto_clear(uint16_t period_ms)
{
    lcd_clear(BLACK);
    HAL_Delay(period_ms);
    lcd_clear(BLUE);
    HAL_Delay(period_ms);
    lcd_clear(GREEN);
    HAL_Delay(period_ms);
    lcd_clear(GBLUE);
    HAL_Delay(period_ms);
    lcd_clear(CYAN);
    HAL_Delay(period_ms);
    lcd_clear(GRAY);
    HAL_Delay(period_ms);
    lcd_clear(BROWN);
    HAL_Delay(period_ms);
    lcd_clear(RED);
    HAL_Delay(period_ms);
    lcd_clear(BRED);
    HAL_Delay(period_ms);
    lcd_clear(BRRED);
    HAL_Delay(period_ms);
    lcd_clear(YELLOW);
    HAL_Delay(period_ms);
    lcd_clear(WHITE);
    HAL_Delay(period_ms);
}

在main函数中调用此函数,分别给予不同的刷新频率,测试刷屏速度和效果。

6. 实现打点、画线、填充函数(重点)

打点函数

/**
 * @brief    LCD打点函数
 * @param    x_pos x方向坐标
 * @param    y_pos y方向坐标
 * @retval   none
*/
void lcd_draw_point(uint16_t x_pos, uint16_t y_pos, uint16_t color)
{
    if (x_pos > lcd_params.lcd_width || y_pos > lcd_params.lcd_height) {
        return;
    }
    
    lcd_set_cursor(x_pos, y_pos);
    lcd_write_ram_start();
    lcd_write_ram(color);
}

设置窗口函数

/**
 * @brief    LCD设置窗口
 * @param    x_pos_start x方向起始坐标
 * @param    y_pos_start y方向起始坐标
 * @param    width       窗口宽度
 * @param    height      窗口高度
 * @retval   none
 * @note     此函数执行完,坐标在窗口左上角
*/
void lcd_set_window(uint16_t x_pos_start, uint16_t y_pos_start, uint16_t width, uint16_t height)
{
    uint16_t x_pos_end, y_pos_end;

    x_pos_end = x_pos_start + width - 1;
    y_pos_end = y_pos_start + height - 1;
    
    if (x_pos_end < x_pos_start || x_pos_end > lcd_params.lcd_width) {
        return;
    }
    
    if (y_pos_end < y_pos_start || y_pos_end > lcd_params.lcd_height) {
        return;
    }
    
    if(lcd_params.lcd_id == 0x9341 || lcd_params.lcd_id == 0x5310) {
		lcd_write_cmd(lcd_params.set_x_cmd); 
		lcd_write_data(x_pos_start >> 8); 
		lcd_write_data(x_pos_start & 0xFF);	 
		lcd_write_data(x_pos_end >> 8); 
		lcd_write_data(x_pos_end & 0xFF);  
		lcd_write_cmd(lcd_params.set_y_cmd); 
		lcd_write_data(y_pos_start >> 8); 
		lcd_write_data(y_pos_start & 0xFF); 
		lcd_write_data(y_pos_end >> 8); 
		lcd_write_data(y_pos_end & 0xFF); 
	} else if (lcd_params.lcd_id == 0x5510) {
		lcd_write_cmd(lcd_params.set_x_cmd); 
        lcd_write_data(x_pos_start >> 8); 
		lcd_write_cmd(lcd_params.set_x_cmd + 1);
        lcd_write_data(x_pos_start & 0xFF);	  
		lcd_write_cmd(lcd_params.set_x_cmd + 2);
        lcd_write_data(x_pos_end >> 8);   
		lcd_write_cmd(lcd_params.set_x_cmd + 3);
        lcd_write_data(x_pos_end & 0xFF);   
		lcd_write_cmd(lcd_params.set_y_cmd);
        lcd_write_data(y_pos_start >> 8);   
		lcd_write_cmd(lcd_params.set_y_cmd + 1);
        lcd_write_data(y_pos_start&0xFF);  
		lcd_write_cmd(lcd_params.set_y_cmd + 2);
        lcd_write_data(y_pos_end >> 8);   
		lcd_write_cmd(lcd_params.set_y_cmd + 3);
        lcd_write_data(y_pos_end & 0xFF);  
	}
}

画线函数

/**
 * @brief   LCD画线函数
 * @param   x1 x方向起始坐标
 * @param   x2 x方向终止坐标
 * @param   y1 y方向起始坐标
 * @param   y2 y方向终止坐标
 * @return  none
 */
void lcd_draw_line(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
    uint16_t	i = 0;
	int16_t		delta_x = 0, delta_y = 0;
	int8_t		incx = 0, incy = 0;
	uint16_t	distance = 0;
	uint16_t    t = 0;
	uint16_t	x = 0, y = 0;
	uint16_t 	x_temp = 0, y_temp = 0;
    
    if(y1 == y2)
    {
     /* 快速画水平线 */
        lcd_set_window(x1, y1, x2, y2);
        lcd_write_ram_start();
        for(i = 0; i < x2 - x1; i++)
        {
            lcd_write_ram(color);
        }

        return;
    }
	else
	{
		/* 画斜线(Bresenham算法) */
		/* 计算两点之间在x和y方向的间距,得到画笔在x和y方向的步进值 */
		delta_x = x2 - x1;
		delta_y = y2 - y1;
		if(delta_x > 0)
		{
			//斜线(从左到右)
			incx = 1;
		}
		else if(delta_x == 0)
		{
			//垂直斜线(竖线)
			incx = 0;
		}
		else
		{
			//斜线(从右到左)
			incx = -1;
			delta_x = -delta_x;
		}
		if(delta_y > 0)
		{
			//斜线(从左到右)
			incy = 1;
		}
		else if(delta_y == 0)
		{
			//水平斜线(水平线)
			incy = 0;
		}
		else
		{
			//斜线(从右到左)
			incy = -1;
			delta_y = -delta_y;
		}			
		
		/* 计算画笔打点距离(取两个间距中的最大值) */
		if(delta_x > delta_y)
		{
			distance = delta_x;
		}
		else
		{
			distance = delta_y;
		}
		
		/* 开始打点 */
		x = x1;
		y = y1;
		//第一个点无效,所以t的次数加一
		for(t = 0; t <= distance + 1;t++)
		{
			lcd_draw_point(x, y, color);
		
			/* 判断离实际值最近的像素点 */
			x_temp += delta_x;	
			if(x_temp > distance)
			{
				//x方向越界,减去距离值,为下一次检测做准备
				x_temp -= distance;		
				//在x方向递增打点
				x += incx;
					
			}
			y_temp += delta_y;
			if(y_temp > distance)
			{
				//y方向越界,减去距离值,为下一次检测做准备
				y_temp -= distance;
				//在y方向递增打点
				y += incy;
			}
		}
	}
}

画矩形函数

/**
 * @breif	LCD画矩形
 * @param   x1 x方向起始坐标
 * @param   x2 x方向终止坐标
 * @param   y1 y方向起始坐标
 * @param   y2 y方向终止坐标
 * @param	color 颜色
 * @retval	none
 */
void lcd_draw_rect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	lcd_draw_line(x1,y1,x2,y1,color);
	lcd_draw_line(x1,y1,x1,y2,color);
	lcd_draw_line(x1,y2,x2,y2,color);
	lcd_draw_line(x2,y1,x2,y2,color);
}

画圆函数

/**
 * @breif	LCD画圆函数
 * @param   x x方向坐标
 * @param	y 方向坐标
 * @param   r 半径
 * @param	color 颜色
 * @retval	none
 */
void lcd_draw_circle(uint16_t x, uint16_t y, uint16_t r, uint16_t color)
{
	/* Bresenham画圆算法 */
	int16_t a = 0, b = r;
    int16_t d = 3 - (r << 1);		//算法决策参数
		
	/* 如果圆在屏幕可见区域外,直接退出 */
    if (x - r < 0 || x + r > lcd_params.lcd_width || y - r < 0 || y + r > lcd_params.lcd_height) {
        return;
    }
		
	/* 开始画圆 */
    while(a <= b)
    {
        lcd_draw_point(x - b, y - a, color);
        lcd_draw_point(x + b, y - a, color);
        lcd_draw_point(x - a, y + b, color);
        lcd_draw_point(x - b, y - a, color);
        lcd_draw_point(x - a, y - b, color);
        lcd_draw_point(x + b, y + a, color);
        lcd_draw_point(x + a, y - b, color);
        lcd_draw_point(x + a, y + b, color);
        lcd_draw_point(x - b, y + a, color);
        a++;

        if(d < 0)
			d += 4 * a + 6;
        else
        {
            d += 10 + 4 * (a - b);
            b--;
        }

        lcd_draw_point(x + a, y + b, color);
    }
}

矩形填充函数

/**
 * @breif	LCD填充一个矩形区域
 * @param   x1 x方向起始坐标
 * @param   x2 x方向终止坐标
 * @param   y1 y方向起始坐标
 * @param   y2 y方向终止坐标
 * @param	color 颜色
 * @retval	none
 */
void lcd_fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
    uint16_t i, j;
    uint32_t xlen = 0;
    
    xlen = x2 - x1 + 1;
    for (i = y1; i <= y2; i++) {
        lcd_set_cursor(x1, i);
        lcd_write_ram_start();
        for (j = 0; j < xlen; j++) {
            lcd_write_ram(color);
        }
    }
}

测试显示

在main函数中编写如下的代码,测试显示:

 //画线测试
lcd_clear(BLACK);
lcd_draw_line(0, lcd_params.lcd_height/2, lcd_params.lcd_width, lcd_params.lcd_height/2, BLUE);
lcd_draw_line(lcd_params.lcd_width/2, 0, lcd_params.lcd_width/2, lcd_params.lcd_height, YELLOW);
lcd_draw_line(0, 0, lcd_params.lcd_width, lcd_params.lcd_height, GREEN);
lcd_draw_line(lcd_params.lcd_width, 0, 0, lcd_params.lcd_height, RED);

//画矩形测试
lcd_draw_rect(lcd_params.lcd_width/4, lcd_params.lcd_height/4, lcd_params.lcd_width/4*3, lcd_params.lcd_height/4*3, CYAN);

//画圆测试
lcd_draw_circle(lcd_params.lcd_width/2, lcd_params.lcd_height/2, lcd_params.lcd_width/4, BRED);

编译,下载,在竖屏显示时,效果图如下:

改为横屏显示,效果如下:

7. 字符显示

这部分自己做也可以,但是用GUI可以实现更多好玩的东西,就测试到这里啦~

更多精彩文章及资源,请关注我的微信公众号:『mculover666』

猜你喜欢

转载自blog.csdn.net/Mculover666/article/details/108361830