FSMC控制LCD(TFTLCD:Z350IT002)显示案例
显存不一定要擦除,只要来一个地址就可以对其进行读写,而且一般的需求是不停的写入(不同的像素点给不同的值),所以是RAM(flash和E2PROM要擦除才能写入),由于FSMC没有DRAM所以我们只能选用SRAM
我们要哪个地址块,要看硬件连接的设定
LCD硬件电路图:液晶模块Z350IT002内部使用的控制芯片是:ILI9486
- D0-D15是16位数据总线接口。分别接FSMC的D0-D15。
- RST是LCD复位引脚,低电平复位。接LCD-RST(PG15)。
- RD是读控制引脚,上升沿时读数据。接FSMC-NOE(PD4)。
- WR是写控制引脚,上升沿时写数据。接FSMC-NWE(PD5)。
- RS是数据或命令选择引脚RS=1写数据,RS=0写命令。接FSMC-A10(PG0)。我们没有地址线,只有控制位,那我们的地址如何传输?在FSMC接口架构中,液晶模块通过地址总线和控制信号的协同编码实现高效的地址传输和命令写入。FSMC将液晶模块的命令和数据寄存器映射到微控制器的SRAM地址空间,例如0x6C000000-0x6FFFFFFF。使用地址线A10作为RS控制信号,通过地址编码切换命令写入和数据写入模式。访问基地址时(0x6C000000),A10为低,数据总线传输的内容被视为命令;访问偏移地址时,A10为高,数据总线内容则被当作显示数据。通过指针运算直接操作硬件寄存器,简化了命令写入过程。FSMC控制器负责地址译码、控制信号生成和总线仲裁,液晶模块通过A10状态解析总线内容。A10为0时,数据被锁存到命令寄存器;为1时,数据写入显示缓冲区。通过地址偏移量扩展,实现多寄存器统一访问接口,利用内存映射I/O机制完成硬件级抽象,提高传输效率,简化地址管理。
- CS是片选引脚,低电平有效。接FSMC-NE4(PG12)。所以其对应的STM32的地址就是bank1的第4块地址
![]()
地址映射范围:
子块编号 地址范围 对应使能引脚 引脚物理连接 Bank1-1 0x60000000-0x63FFFFFF
NE1 如PD7(具体型号可能不同) Bank1-2 0x64000000-0x67FFFFFF
NE2 如PG9 Bank1-3 0x68000000-0x6BFFFFFF
NE3 如PG10 Bank1-4 0x6C000000-0x6FFFFFFF
NE4 如PG12 - LEDA是背光电源(3.0V-3.4V)引脚。
- LEDK是背光亮度控制引脚。通过LCD-BG(PB0)来驱动MOS管Q5的导通电流。可以通过给LCD-BG输出PWM波来控制背光的亮度。占空比越大,背光就会越亮。
- YD,XL,YU,XR是触摸屏控制引脚
注意:当使用16位宽的外部存储器时,用HADDR[25:1]表示外部的FSMC_A[24:0],内部地址相当于左移了1位,所以计算地址的时候要注意。
LCD我们选择的是16位宽度的,选择地址线时,我们选择的是A10接LCD的D/CX(数据/命令引脚)。
当A10=0时,表示写命令,所以地址是:0x6C00 0000。
当A10=1时,表示写数据,所以地址是:0x6C00 0000 + 1<<11 = 0x6C00 0800
只有A10置1,其他置0,不要的数据位全置0,从右往左数1~10第11位,1后面10个0,写成16进制数就是0x6c00400(|0100|0000|0000),如果我们的地址线是直连的没有问题,但是现在的地址线不是直连的,数据宽度是8位,那我们高速系统总线上的地址线和FSMC的地址线完全是直连的模式,连到A10也是32位从右往左数的第10位,但我们现在是16位的,要操作的LCD模块显存都是16位的RGB模块,那么就有交叉连接的问题,就是A10,最后FSMC最后输出的A10来自于PHA11,以此类推PHA1连到A0,最后一位就不需要了,要表示就用高低字节的掩码来表示,关键的怎么配置A10对应的是之前原始地址的PHA11,就是1000|0000|0000,换成16进制就是0x6c00800,
如果大家不想这么麻烦就全部置1即可,反正其他内存又没用到
寄存器代码:
FSMC配置:
void FSMC_GPIO_Init(){/* 1 配置 A0-A18 地址端口的输出模式 复用推挽输出CNF:10 50MHz速度 MODE:11*//* =============MODE=============== */GPIOG->CRL |= GPIO_CRL_MODE0;/* =============CNF=============== */GPIOG->CRL |= GPIO_CRL_CNF0_1 ;GPIOG->CRL &= ~GPIO_CRL_CNF0_0;/*2 数据端口 复用推挽输出在实际应用中,即使数据线被配置为输出模式,FSMC控制器仍然能够管理数据线的方向,使其在需要时成为输入线。这种自动切换是由FSMC控制器硬件管理的,不需要软件干预。因此,即使GPIO配置为复用推挽输出,FSMC依然可以实现读取操作。*//* =============MODE=============== */GPIOD->CRL |= (GPIO_CRL_MODE0 |GPIO_CRL_MODE1);GPIOD->CRH |= (GPIO_CRH_MODE8 |GPIO_CRH_MODE9 |GPIO_CRH_MODE10 |GPIO_CRH_MODE14 |GPIO_CRH_MODE15);GPIOE->CRL |= (GPIO_CRL_MODE7);GPIOE->CRH |= (GPIO_CRH_MODE8 |GPIO_CRH_MODE9 |GPIO_CRH_MODE10 |GPIO_CRH_MODE11 |GPIO_CRH_MODE12 |GPIO_CRH_MODE13 |GPIO_CRH_MODE14 |GPIO_CRH_MODE15);/* =============CNF=============== */GPIOD->CRL |= (GPIO_CRL_CNF0_1 |GPIO_CRL_CNF1_1);GPIOD->CRL &= ~(GPIO_CRL_CNF0_0 |GPIO_CRL_CNF1_0);GPIOD->CRH |= (GPIO_CRH_CNF8_1 |GPIO_CRH_CNF9_1 |GPIO_CRH_CNF10_1 |GPIO_CRH_CNF14_1 |GPIO_CRH_CNF15_1);GPIOD->CRH &= ~(GPIO_CRH_CNF8_0 |GPIO_CRH_CNF9_0 |GPIO_CRH_CNF10_0 |GPIO_CRH_CNF14_0 |GPIO_CRH_CNF15_0);GPIOE->CRL |= (GPIO_CRL_CNF7_1);GPIOE->CRL &= ~(GPIO_CRL_CNF7_0);GPIOE->CRH |= (GPIO_CRH_CNF8_1 |GPIO_CRH_CNF9_1 |GPIO_CRH_CNF10_1 |GPIO_CRH_CNF11_1 |GPIO_CRH_CNF12_1 |GPIO_CRH_CNF13_1 |GPIO_CRH_CNF14_1 |GPIO_CRH_CNF15_1);GPIOE->CRH &= ~(GPIO_CRH_CNF8_0 |GPIO_CRH_CNF9_0 |GPIO_CRH_CNF10_0 |GPIO_CRH_CNF11_0 |GPIO_CRH_CNF12_0 |GPIO_CRH_CNF13_0 |GPIO_CRH_CNF14_0 |GPIO_CRH_CNF15_0);/* 3 其他控制端口 复用推挽输出 *//* 3.1 PD4: 读控制引脚 PD5: 写控制引脚 */GPIOD->CRL |= (GPIO_CRL_MODE4 |GPIO_CRL_MODE5);GPIOD->CRL |= (GPIO_CRL_CNF4_1 |GPIO_CRL_CNF5_1);GPIOD->CRL &= ~(GPIO_CRL_CNF4_0 |GPIO_CRL_CNF5_0);/* 3.2 PG12:NE4*/GPIOG->CRH |= (GPIO_CRH_MODE12);GPIOG->CRH |= (GPIO_CRH_CNF12_1);GPIOG->CRH &= ~(GPIO_CRH_CNF12_0);/* 3.3 背光引脚PB0:通用推挽输出*/GPIOB->CRL |= GPIO_CRL_MODE0;GPIOB->CRL &= ~GPIO_CRL_CNF0;/* 3.4 重置引脚PG15:通用推挽输出*/GPIOG->CRH |= GPIO_CRH_MODE15;GPIOG->CRH &= ~GPIO_CRH_CNF15;}
开启时钟
void FSMC_Init(){/* 1. 时钟开启 */RCC->APB2ENR |= (RCC_APB2ENR_IOPDEN |RCC_APB2ENR_IOPEEN |RCC_APB2ENR_IOPFEN |RCC_APB2ENR_IOPGEN |RCC_APB2ENR_AFIOEN);RCC->AHBENR |= RCC_AHBENR_FSMCEN;/* 2. 各个引脚的模式的配置 */Driver_FSMC_GPIO_Init();/* 3. fsmc的配置 Bank1的 4区 BCR4 *//* 3.1 存储块使能 */FSMC_Bank1->BTCR[6] |= FSMC_BCR3_MBKEN;/* 3.2 设置存储类型 00=SRAM ROM*/FSMC_Bank1->BTCR[6] &= ~FSMC_BCR3_MTYP;/* 3.3 禁止闪存访问 */FSMC_Bank1->BTCR[6] &= ~FSMC_BCR3_FACCEN;/* 3.4 地址和数据复用: 不复用 ,逻辑上是做复用,但是物理上我们还是不复用,因为就一个线0地址,1数据,*/FSMC_Bank1->BTCR[6] &= ~FSMC_BCR3_MUXEN;/* 3.5 数据总线的宽度 16位宽度=01 */FSMC_Bank1->BTCR[6] &= ~FSMC_BCR3_MWID_1;FSMC_Bank1->BTCR[6] |= FSMC_BCR3_MWID_0;/* 3.6 写使能 */;FSMC_Bank1->BTCR[6] |= FSMC_BCR3_WREN;/* 3. fsmc的 时序 *//* 3.1 地址建立时间 对同步读写来说,永远一个周期 */FSMC_Bank1->BTCR[7] &= ~FSMC_BTR3_ADDSET;/* 3.2 地址保持时间 对同步读写来说,永远一个周期 */FSMC_Bank1->BTCR[7] &= ~FSMC_BTR3_ADDHLD;/* 3.3 数据保持时间 手册不能低于55ns 我们设置1us*/FSMC_Bank1->BTCR[7] &= ~FSMC_BTR3_DATAST;
//看寄存器它是在8~15位定义的,所以左移8位,可能LCD时序上有其他要求但是1US就足够了FSMC_Bank1->BTCR[7] |= (71 << 8);}
LCD配置:
LCD.h:基础底层操作
#ifndef __INF_LCD_H
#define __INF_LCD_H#include "Driver_FSMC.h"
#include "Delay.h"
#include "math.h"
//bank1的第4个分区基地址
#define SRAM_BANK4 0x6C000000
//写命令,转换成一个指针
#define LCD_ADDR_CMD (uint16_t *)SRAM_BANK4
//写数据
#define LCD_ADDR_DATA (uint16_t *)(SRAM_BANK4 + (1 << 11))
//液晶屏的宽,这个要看你LCD的像素
#define DISPLAY_W 320
//液晶屏的高
#define DISPLAY_H 480/* 常见颜色 */
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 // 棕色
#define BRRED 0XFC07 // 棕红色
#define GRAY 0X8430 // 灰色//基本控制操作:
//1.1初始化:
void LCD_Init(void);//1.1.1复位
void LCD_Reset(void);//1.1.2开关背光
void LCD_BGOn(void);
void LCD_BGOff(void);//1.1.3初始化LCD寄存器
void LCD_ReConfig(void);//2.写命令(发出一个指令)看LCD数据手册是8位的但是我们FSMC配是16位的,强转就行
void LCD_WriteCmd(uint16_t cmd);//3.写数据(发出一个数据)
void LCD_WriteData(uint16_t cmd);//4.读数据
uint16_t LCD_ReadData(void);
//2.具体命令操作
//读取ID信息,看LCD的数据手册上的指令表
uint32_t LCD_ReadID(void);//向液晶屏发送信息,看数据手册
//底层的指令值就是对某一个像素点写入RGB数据
//有列地址和行地址的划分,即将矩阵屏划分了多个点
void LCD_SetArea(uint16_t x,uint16_t y,uint16_t w,uint16_t h);void LCD_ClearAll(uint16_t color);#endif
LCD.c基础底层操作
//基本控制操作:
//1.1初始化:
void LCD_Init(void)
{
FSMC_Init();LCD_Reset();LCD_BGOn();LCD_RegConfig();
}//1.1.1复位
void LCD_Reset(void)
{
//直接将PG15拉低
GPIOG->ODR &=~GPIO_ODR_ODR15;
//稍作延时,然后拉高
Delay_ms(100);
GPIOG->ODR |=GPIO_ODR_ODR15;
}//1.1.2开关背光
void LCD_BGOn(void)
{
//电路图是PB0高电平有效,背光点亮
GPIOB->ODR |=GPIO_ODR_ODR0;
}void LCD_BGOff(void)
{
//电路图是PB0高电平有效,背光点亮拉低
GPIOB->ODR &=~GPIO_ODR_ODR0;
}//1.1.3初始化LCD寄存器
void LCD_ReConfig(void)
{
//直接抄写LCD数据手册给的/* 1. 设置灰阶电压以调整TFT面板的伽马特性(当前输出的电压和人眼感知亮度是非线性的,校准当前输出电压服务人眼感知), 正校准。一般出厂就设置好了 */Inf_LCD_WriteCmd(0xE0);Inf_LCD_WriteData(0x00);Inf_LCD_WriteData(0x07);Inf_LCD_WriteData(0x10);Inf_LCD_WriteData(0x09);Inf_LCD_WriteData(0x17);Inf_LCD_WriteData(0x0B);Inf_LCD_WriteData(0x41);Inf_LCD_WriteData(0x89);Inf_LCD_WriteData(0x4B);Inf_LCD_WriteData(0x0A);Inf_LCD_WriteData(0x0C);Inf_LCD_WriteData(0x0E);Inf_LCD_WriteData(0x18);Inf_LCD_WriteData(0x1B);Inf_LCD_WriteData(0x0F);/* 2. 设置灰阶电压以调整TFT面板的伽马特性,负校准 */Inf_LCD_WriteCmd(0XE1);Inf_LCD_WriteData(0x00);Inf_LCD_WriteData(0x17);Inf_LCD_WriteData(0x1A);Inf_LCD_WriteData(0x04);Inf_LCD_WriteData(0x0E);Inf_LCD_WriteData(0x06);Inf_LCD_WriteData(0x2F);Inf_LCD_WriteData(0x45);Inf_LCD_WriteData(0x43);Inf_LCD_WriteData(0x02);Inf_LCD_WriteData(0x0A);Inf_LCD_WriteData(0x09);Inf_LCD_WriteData(0x32);Inf_LCD_WriteData(0x36);Inf_LCD_WriteData(0x0F);/* 3. Adjust Control 3 (F7h) *//*LCD_WriteCmd(0XF7);Inf_LCD_WriteData(0xA9);Inf_LCD_WriteData(0x51);Inf_LCD_WriteData(0x2C);Inf_LCD_WriteData(0x82);*//* DSI write DCS command, use loose packet RGB 666 *//* 4. 电源控制1*/Inf_LCD_WriteCmd(0xC0);Inf_LCD_WriteData(0x11); /* 正伽马电压 */Inf_LCD_WriteData(0x09); /* 负伽马电压 *//* 5. 电源控制2 */Inf_LCD_WriteCmd(0xC1);Inf_LCD_WriteData(0x02);Inf_LCD_WriteData(0x03);/* 6. VCOM控制 */Inf_LCD_WriteCmd(0XC5);Inf_LCD_WriteData(0x00);Inf_LCD_WriteData(0x0A);Inf_LCD_WriteData(0x80);/* 7. Frame Rate Control (In Normal Mode/Full Colors) (B1h) */Inf_LCD_WriteCmd(0xB1);Inf_LCD_WriteData(0xB0);Inf_LCD_WriteData(0x11);/* 8. Display Inversion Control (B4h) (正负电压反转,减少电磁干扰)*/Inf_LCD_WriteCmd(0xB4);Inf_LCD_WriteData(0x02);/* 9. Display Function Control (B6h) */Inf_LCD_WriteCmd(0xB6);Inf_LCD_WriteData(0x0A);Inf_LCD_WriteData(0xA2);/* 10. Entry Mode Set (B7h) */Inf_LCD_WriteCmd(0xB7);Inf_LCD_WriteData(0xc6);/* 11. HS Lanes Control (BEh) */Inf_LCD_WriteCmd(0xBE);Inf_LCD_WriteData(0x00);Inf_LCD_WriteData(0x04);/* 12. Interface Pixel Format (3Ah) */Inf_LCD_WriteCmd(0x3A);Inf_LCD_WriteData(0x55); /* 0x55 : 16 bits/pixel *//* 13. Sleep Out (11h) 关闭休眠模式 */Inf_LCD_WriteCmd(0x11);/* 14. 设置屏幕方向和RGB */Inf_LCD_WriteCmd(0x36);Inf_LCD_WriteData(0x08);Delay_ms(120);/* 14. display on */Inf_LCD_WriteCmd(0x29);}//2.写命令(发出一个指令)看LCD数据手册是8位的但是我们FSMC配是16位的,强转就行
void LCD_WriteCmd(uint16_t cmd)
{*LCD_ADDR_CMD=cmd;
}//3.写数据(发出一个数据)
void LCD_WriteData(uint16_t )data
{
*LCD_ADDR_DATA =data;
}//4.读数据
uint16_t LCD_ReadData(void)
{
//总线占用权给LCD喂数据到这个地址上
return *LCD_ADDR_DATA;
}
具体的命令:
uint32_t LCD_ReadID(void)
{
//首先发送读取ID的命令
LCD_WriteCmd(0x04);//要读取3个字节的ID数据,定义变量接收数据
uint32_t id=0;
//主要第一次读取的数据是无效的,就进行读的一个空操作
LCD_ReadData();//依次读取3个字节的数据,放置在id的相应位置
//高位的第1个字节
id |=(LCD_ReadData()& 0xff)<<16;
//第2个字节
id |=(LCD_ReadData()& 0xff)<<8;
//第3个字节
id |=(LCD_ReadData()& 0xff)<<0;}
列:
行:
//设置区域范围,给定起始点的坐标,以及宽和高
void LCD_SetArea(uint16_t x,uint16_t y,uint16_t w,uint16_t h)
{
//发送命令,先设置列范围
LCD_WriteCmd(0x2a);
//设置开始列
LCD_WriteData((x>>8)&0xff);//高8位
LCD_WriteData(x&0xff);//低8位//设置结束列
LCD_WriteData( ( (x+w-1)>>8 )&0xff);//高8位,减1为了
LCD_WriteData( (x+w-1) &0xff);//低8位//发送命令,再设置行范围
LCD_WriteCmd(0x2b);//设置开始行
LCD_WriteData((y>>8)&0xff);//高8位
LCD_WriteData(Y&0xff);//低8位//设置结束行
LCD_WriteData( ( (y+h-1)>>8 )&0xff);//高8位
LCD_WriteData( (y+h-1) &0xff);//低8位}//清屏,颜色都是16位
void LCD_ClearAll(uint16_t color)
{
//设置区域范围为全屏范围
LCD_SetArea(0,0,LCD_W,LCD_H);//向对应区域范围发送数据
LCD_WriteCmd(0x2c);//用循环遍历所有的像素点写入你想要以什么颜色进行清屏,依次写入数据
for(uint16_t i=0;i<LCD_W*LCD_H ;i++)
{
LCD_WriteData(color);
}}
用液晶显示屏输出字符:一堆像素点拼成字符,我要截取一个空间,很多行很多列,字符表示位是给1不是给0,每个字符对应的点阵,有对应的工具自行生成
#ifndef __LCDFONT_H
#define __LCDFONT_H
//其中1206指定是每个字节占用的位置为宽6高12,二维数组[][12]表示n个字符,每个字符都是用12行*1个字节表示
const unsigned char ascii_1206[][12] = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x12, 0x1C, 0x12, 0x3C, 0x00, 0x00}, /*"a",65*/{0x00, 0x03, 0x02, 0x02, 0x02, 0x0E, 0x12, 0x12, 0x12, 0x0E, 0x00, 0x00}, /*"b",66*///....由于内容过多,使用的时候可以自行填充
};
//有16行,每一行有8个点用1个字节表示,一个字符就占用16*1个字节表示,如果是24行12列的话就要用24*2(1行有12位,1个半字节用两个字节表示)个字节表示一个字符
const unsigned char ascii_1608[][16] = {{0x00, 0x00, 0x00, 0x08, 0x08, 0x18, 0x14, 0x14, 0x24, 0x3C, 0x22, 0x42, 0x42, 0xE7, 0x00, 0x00}, /*"A",33*/{0x00, 0x00, 0x00, 0x1F, 0x22, 0x22, 0x22, 0x1E, 0x22, 0x42, 0x42, 0x42, 0x22, 0x1F, 0x00, 0x00}, /*"B",34*/{0x00, 0x00, 0x00, 0x7C, 0x42, 0x42, 0x01, 0x01, 0x01, 0x01, 0x01, 0x42, 0x22, 0x1C, 0x00, 0x00}, /*"C",35*///......
};const unsigned char ascii_2412[][48] = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*" ",0*/
//......}//32行32列,1行32个位,32*32/8=128个字节
const unsigned char chinese_3232[][128]={好(0) 运(1) 来(2){0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0x80,0x01,0x00,0x04,0x80,0x81,0xFF,0x0F,0x80,0x01,0x00,0x0E,0x80,0x00,0x00,0x03,0xC0,0x10,0x00,0x01,0xFC,0x3F,0x80,0x00,0xC0,0x10,0x60,0x00,0xC0,0x10,0x60,0x00,0x40,0x18,0x60,0x00,0x60,0x18,0x60,0x00,0x60,0x18,0x60,0x00,0x20,0x18,0x60,0x18,0x20,0xE8,0xFF,0x3F,0x30,0x0C,0x60,0x00,0x30,0x0C,0x60,0x00,0x10,0x0C,0x60,0x00,0x10,0x06,0x60,0x00,0x70,0x06,0x60,0x00,0x80,0x03,0x60,0x00,0x00,0x0F,0x60,0x00,0x00,0x3D,0x60,0x00,0x80,0x31,0x60,0x00,0xC0,0x20,0x60,0x00,0x20,0x00,0x60,0x00,0x10,0x00,0x3F,0x00,0x0C,0x00,0x38,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00},/*"好",0*/
/* (32 X 32 , 宋体 )*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x60,0x00,0x00,0x03,0xC0,0xF0,0xFF,0x07,0xC0,0x01,0x00,0x00,0xC0,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x80,0xFC,0xFF,0x1F,0xFC,0x01,0x04,0x00,0xC0,0x00,0x0E,0x00,0xC0,0x00,0x06,0x00,0xC0,0x00,0x03,0x00,0xC0,0x00,0x03,0x00,0xC0,0x80,0xC1,0x00,0xC0,0x80,0x80,0x01,0xC0,0x40,0x00,0x03,0xC0,0x20,0x00,0x06,0xC0,0xF0,0xFF,0x0F,0xC0,0xF0,0x00,0x0E,0xC0,0x00,0x00,0x04,0x30,0x01,0x00,0x00,0x1C,0x06,0x00,0x00,0x0C,0x3C,0x00,0x60,0x00,0xF0,0xFF,0x1F,0x00,0x80,0xFF,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"运",1*/
/* (32 X 32 , 宋体 )*/{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x80,0x03,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x00,0x00,0x80,0x01,0x06,0xF0,0xFF,0xFF,0x0F,0x00,0x80,0x01,0x00,0x80,0x80,0x81,0x00,0x00,0x83,0x81,0x01,0x00,0x87,0xC1,0x01,0x00,0x8E,0xC1,0x00,0x00,0x8E,0x61,0x00,0x00,0x84,0x31,0x00,0x00,0x84,0x11,0x18,0xFC,0xFF,0xFF,0x3F,0x00,0xE0,0x05,0x00,0x00,0xE0,0x05,0x00,0x00,0xB0,0x09,0x00,0x00,0x98,0x19,0x00,0x00,0x98,0x11,0x00,0x00,0x8C,0x61,0x00,0x00,0x86,0xE1,0x00,0x00,0x83,0xC1,0x01,0x80,0x81,0x81,0x07,0x40,0x80,0x01,0x3E,0x30,0x80,0x01,0x1C,0x08,0x80,0x01,0x00,0x04,0x80,0x01,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00},/*"来",2*/
/* (32 X 32 , 宋体 )*/
}#endif
显示字符: 以下图的思想为例子,其他的以此类推
//上层操作接口
//显示ASCII码字符,起始位置(行列),有多少行,列就行的一半吧,因为lcdfont的文件里就是设置的行列大小关系
void LCD_WriteAsciiChar(uint16_t x,uint16_t y,uint16_t height,uint8_t c,uint16_t fontcolor,uint16_t backgroundcolor)
{
//先计算当前字符在数组中的位置
uint8_t index=c-'';//先确定区域
LCD_SetArea(x,y,height/2,heigth);//发送写入显存指令
LCD_WriteCmd(0x2c);
//遍历整个区域判断区域当前像素点是不是对应字符的笔画,到点阵里找是1就是笔画就给字符颜色,0就是非笔画就给背景颜色
//按照字节大小,到不同的数组中寻找字符对应的点阵
//如果字体的1608或者1206,都是用一个字节表示一行点阵,核心是遍历每一行每一列if(height=16||height=12){
for(uint8_t i=0;i<height;i++)
{
//根据字体大小在字库中选择当前行对应的i字节,这就是第i行的点阵uint8_t tempByte = (height ==16)?ascii_1608[index][i] :ascii_1206[index][i];//遍历当前行的每一列(每个像素点,即字节中的每一位)
for(uint8_t j=0;j<height/2;j++)
{
//每次只取最低位,因为我用工具生成时选择的
if( tempByte & 0x01)
{
//如果是1,显示字体颜色
LCD_WriteData(fontcolor);
}
//如果是0,显示背景颜色
else{LCD_WriteData(backgrountcolor);}
//右移一位
tempByte>>=1;
}}}else if (height==24){
//i为字节编号
for(uint8_t i=0;i<height;i++)
{
//根据字体大小在字库中选择当前行对应的i字节,这就是第i行的点阵
uint8_t tempByte = ascii_2412[index][i];//根据当前i的值,判断字节中遍历多少位数据(奇数的话只遍历4位)
uint8_t jcount=(i%2)?4:8;//遍历当前行的每一列(每个像素点,即字节中的每一位)
for(uint8_t j=0;j<jcount;j++)
{
//每次只取最低位,因为我用工具生成时选择的
if( tempByte & 0x01)
{
//如果是1,显示字体颜色
LCD_WriteData(fontcolor);
}
//如果是0,显示背景颜色
else{LCD_WriteData(backgrountcolor);}
//右移一位
tempByte>>=1;
}}}//如果的3216,用两个字节表示一行点阵
else if (height==32){
//i为字节编号
for(uint8_t i=0;i<height;i++)
{
//根据字体大小在字库中选择当前行对应的i字节,这就是第i行的点阵
uint8_t tempByte = ascii_2412[index][i];//遍历当前行的每一列(每个像素点,即字节中的每一位),当前字节中每一位
for(uint8_t j=0;j<8;j++)
{
//每次只取最低位,因为我用工具生成时选择的
if( tempByte & 0x01)
{
//如果是1,显示字体颜色
LCD_WriteData(fontcolor);
}
//如果是0,显示背景颜色
else{LCD_WriteData(backgrountcolor);}
//右移一位
tempByte>>=1;
}}}}
显示字符串代码:
void LCD_WriteAsciiString (uint16_t x,uint16_t y,uint16_t height,uint8_t *str,uint16_t fontcolor,uint16_t backgroundcolor)
{
uint8_t i=0;while(str[i] !='\0')
{
//判断是否遇到\n需要换行,如果没有遇到直接增加x,继续显示
if(str[i]!='\n'){
//另外判断一下是否到达当前行的末尾如果到达自动换行
if(x+height/2>LCD_W){
//换行
x=0;y+=height;}
LCD_WriteAsciiChar(x,y,height, str[i] , fontcolor,backgroundcolor);
x+=height/2;}
else{
//如果str字符内有换行符
x=0;y+=height;}
i++;
}}
显示汉字:在英文例子的本质上都是一样的
void LCD_WriteChineseChar(uint16_t x,uint16_t y,uint16_t height,uint8_t index,uint16_t fontcolor,uint16_t backgroundcolor)
{
//设置显示区域
LCD_SetArea(x,y,height,height);//发送指令
LCD_WriteCmd(0x2c);
//我给的例子里中文字体的就32*32的,所以这里height就定死32了,i能遍历的最大值也就128次了
for(uint8_t i=0;i<128;i++)
{
//根据字体大小在字库中选择当前行对应的i字节,这就是第i行的点阵
uint8_t tempByte = chinese_3232[index][i];//遍历当前行的每一列(每个像素点,即字节中的每一位),当前字节中每一位
for(uint8_t j=0;j<8;j++)
{
//每次只取最低位,因为我用工具生成时选择的
if( tempByte & 0x01)
{
//如果是1,显示字体颜色
LCD_WriteData(fontcolor);
}
//如果是0,显示背景颜色
else{LCD_WriteData(backgrountcolor);}
//右移一位
tempByte>>=1;
}}}}
}
显示图片:加载一个图片展示到液晶屏中
我用工具生成时选择的,所以低位在后,高位在前,
注意:每次取两个字节拼接成一个像素点的RGB数据(因为我们用工具生成的时候勾选的16位表示RGB,而拼接字符的像素点的RGB也是16位的(可以看宏定义的颜色))
const unsigned char gImage_logo[115200] = { /* 0X10,0X10,0X00,0XF0,0X00,0XF0,0X01,0X1B, */
0X20,0X60,0X20,0X60,0X20,0X60,0X20,0X60,0X20,0X60,0X20,0X60,0X20,0X60,0X20,0X60,
......英文过多数据我直接省略
0X94,0X0E,0X94,0X0E,0X8B,0XED,0X8B,0XAD,0X8B,0XAD,0X8B,0XAD,0X8B,0XAE,0X8B,0XCE,
};
//我们不考虑延展和压缩,直接按照原图像素展示
void LCD_Displaypicture(uint16_t x,uint16_ y)
{
//设置显示区域
LCD_SetArea(x,y,240,240);//发送指令
LCD_WriteCmd(0x2c);uint16_t len=sizeof(gImage_logo);
//两个字节,两个字节的判断所以i+=2
for(uint16_t i=0;i<len;i+=2)
{
//因为我用工具生成时选择的,所以低位在后,高位在前,每次取两个字节拼接成一个像素点的RGB数据(因为我们用工具生成的时候勾选的16位表示RGB,而字符的像素大小我们选的是8位的)
uint16_t tempByte=gImage_logo[i]<<8+gImage_logo[i+1];LCD_WriteData(p);
}}
思想总结:本质都是像素点,就是给每个像素点填不同的颜色(16位RGB)就可以展示我们想要的效果到LCD上。字符和画图代码上有差异但都为本质服务
自己手动画图:这个我后面再补更吧,毕竟前面的代码已经足够新手用的了,其实也可以将你想要的图形以图片的形式展出
//画出一个点,给定左上角起始点是坐标,以及放点的宽度
void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t height,uint16_t color)
{
//设置区域
LCD_SetArea(x,y,height,height);//发送命令
LCD_WriteCmd(0x2c);//将区域内的所有像素点涂上给定的颜色,至于液晶先扫描行还是列是LCD一开始配置时就定好了,我们不用管,默认是行扫描,它会自动换行的
for(uint16_t i=0;i<height*height;i++)
{
LCD_WriteData(color);
}
}//画线,给定起点和终点的坐标void LCD_DrawPoint(uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint16_t w,uint16_t color)
{
//计算直线方程中的k和b,y=kx+b,k=(y1-y2)/(x1-x2),b=y1-k*x1
//先考虑x1=x2的情况,一条竖线
if(x1==x2){for(uint16_t i=0;i<count ;i++)}
}