本文未完,更新中........2020.04.05
先上图,有图有真相:
文章结构:
实验平台
3.5寸ILI9486的特点
编写初始化代码
编写点读、写函数
英文字符显示代码
汉字显示代码
汉字字库的使用
一、实验平台搭建
屏幕:
- 材料器件:信泰微 3.5寸TFT_ILI9486驱动
- 选择理由:1:接口兼容正点原子的TFT34针; 2:够便宜,才43元入手的, 但买的时候没留意是不带触摸, 可惜
板子:
- 材料器件:魔女的开发板 STM32F429IGT6
- 选择理由:1:内置CMSIS-DAP下载器、虚拟串口,接线方便; 2:价钱实,这是硬道理
软件:
- Keil 5.27, 近来Keil又升级了,进入界面漂亮了,
- 没用库函数,没用HAL,全寄存器,并详细注释 ;用寄存器操作,查错除BUG就是速度
二、3.5寸驱动ILI9486的特点
这货最大的特点: 省钱! 所以3.5寸就它最大存量了. 比原子哥的3.5寸便宜一半以上, 但原子哥的代码中没它影子. 咱造一个呗!
参数上的特点 , 懒得百度了, 你也别管了, 做开发用啥的不是用, 来来去去就是写寄存器, 读寄存器。最直接的就是提取厂商的代码再根据项目优化, 别造轮子.
编写初始化代码
共需要初始化3个小部份
1:GPIO的初始化,因为板子、屏幕接口都和正点原子的一样,没必要造轮子,将在正点原子的代码基础上修改完善。
- 引脚有两种: 背光引脚推挽输出、FMS引脚使用复用功能
- 使用了魔女开发板团队提供的函数GPIOSet()对GPIO的初始化,比较简洁,函数会使能引脚时钟,并可设置引脚复用功能,就如下面只用了3行代码,就完成了屏幕所需21个引脚的初始化,和指定了引脚复用FMC功能。
// 控制背光引脚,推挽输出
GPIOSet(GPIOB, PIN5, G_MODE_OUT , G_OTYPE_PP , G_OSPEED_50M , G_PUPD_UP , 0);
// GPIOD中需要的引脚, 使用FMC复用功能
GPIOSet(GPIOD, PIN0|PIN1|PIN4|PIN5|PIN7|PIN8|PIN9|PIN10|PIN13|PIN14|PIN15, G_MODE_AF , G_OTYPE_PP , G_OSPEED_100M , G_PUPD_UP , G_AF_FMC);
// GPIOE中需要的引脚, 使用FMC复用功能
GPIOSet (GPIOE, PIN7|PIN8|PIN9|PIN10|PIN11|PIN12|PIN13|PIN14|PIN15, G_MODE_AF , G_OTYPE_PP , G_OSPEED_100M , G_PUPD_UP , G_AF_FMC );
2:FMS的初始化,这个没啥好说的,如果想要理解FMC的原理,可以翻翻正点原子和野火的书,会比PDF详尽很多。
//bank1有NE1~4,每一个有一个BCR+TCR,所以总共八个寄存器。
//这里我们使用NE1 ,也就对应BTCR[0],[1]。
FMC_Bank1->BTCR[0]=0X00000000;
FMC_Bank1->BTCR[1]=0X00000000;
FMC_Bank1E->BWTR[0]=0X00000000;
//操作BCR寄存器 使用异步模式
FMC_Bank1->BTCR[0]|=1<<12; //存储器写使能
FMC_Bank1->BTCR[0]|=1<<14; //读写使用不同的时序
FMC_Bank1->BTCR[0]|=1<<4; //存储器数据宽度为16bit
//操作BTR寄存器
//读时序控制寄存器
FMC_Bank1->BTCR[1]|=0<<28; //模式A
FMC_Bank1->BTCR[1]|=0XF<<0; //地址建立时间(ADDSET)为15个HCLK 1/192M=5.2ns*15=78ns
//因为液晶驱动IC的读数据的时候,速度不能太快,尤其是个别奇葩芯片。
FMC_Bank1->BTCR[1]|=70<<8; //数据保存时间(DATAST)为60个HCLK =5.2*70=360ns
//写时序控制寄存器
FMC_Bank1E->BWTR[0]|=0<<28; //模式A
FMC_Bank1E->BWTR[0]|=15<<0; //地址建立时间(ADDSET)为15个HCLK=78ns
//10个HCLK(HCLK=180M),某些液晶驱动IC的写信号脉宽,最少也得50ns。
FMC_Bank1E->BWTR[0]|=15<<8; //数据保存时间(DATAST)为5.2ns*15个HCLK=78ns
//使能BANK1,区域1
FMC_Bank1->BTCR[0]|=1<<0; //使能BANK1,区域1
初始化了GPIO和FMC,不着急初始化屏幕芯片ILI9486, 先测试一下能否读取屏幕型号,以判断连接和之前的初始化是否正确。
//判断LCD TFT 9486是否连通:尝试ILI9486 ID的读取
delay_ms(10); // delay 50 ms
LCD_REG = 0XD3 ;
xLCD.ID = LCD_RAM; // dummy read
xLCD.ID = LCD_RAM; // 读到0X00
xLCD.ID = LCD_RAM; // 读取93
xLCD.ID <<=8;
xLCD.ID |=LCD_RAM; // 读取41
if(xLCD.ID!=0x9486) // 判断是否连通
{
printf("错误: 没检测到TFT_3.5寸; 型号值读取:0x%X; 将放弃本屏幕的使用!\r", xLCD.ID);
xLCD.InitOK = 0;
return;
}
其中:LCD_REG是在h文件中的宏定义, 使用指针直接读写内存中的值,这个内存段是FMC使用的区域,可理解显存区域。
#define LCD_REG (*(u16 *)(0x6007FFFE)) // (*(u16 *)(0x60000000 | 0x0007FFFE))
#define LCD_RAM (*(u16 *)(0x6008000E)) // (*(u16 *)((0x60000000 | 0x0007FFFE)+16)))
还有一个xLCD,是一个结构体,习惯了每个外设的c文件都初始化一个独立的结构体,存放设备自己数据
//LCD重要参数集
struct
{
u8 InitOK;
u16 width; //LCD 宽度
u16 height; //LCD 高度
u16 ID; //LCD ID
u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
u16 wramcmd; //开始写gram指令
u16 setxcmd; //设置x坐标指令
u16 setycmd; //设置y坐标指令
}xLCD;
回到判断连接的思路上,语句if(xLCD.ID!=0x9486)进行判断,如果读取的值不是0x9486, 就退出,不再继续9486芯片的配置。
3:屏幕芯片ILI9486的初始化、配置
这部分按照厂家的步骤进行,几乎都是复制+粘贴了。
//************* Start Initial Sequence **********//
LCD_REG = 0XF9;
LCD_RAM = 0x00;
LCD_RAM = 0x08;
LCD_REG = 0xC0;
LCD_RAM = 0x19;//VREG1OUT POSITIVE
LCD_RAM = 0x1a;//VREG2OUT NEGATIVE
LCD_REG = 0xC1;
LCD_RAM = 0x45;//VGH,VGL VGH>=14V.
LCD_RAM = 0x00;
LCD_REG = 0xC2;
LCD_RAM = 0x33;
LCD_REG = 0XC5;
LCD_RAM = 0x00;
LCD_RAM = 0x28;//VCM_REG[7:0]. <=0X80.
LCD_REG = 0xB1;
LCD_RAM = 0xA0;//0XB0 =70HZ, <=0XB0.0xA0=62HZ
LCD_RAM = 0x11;
LCD_REG = 0xB4;
LCD_RAM = 0x02; //2 DOT FRAME MODE,F<=70HZ.
LCD_REG = 0xB6;
LCD_RAM = 0x00;
LCD_RAM = 0x42;//0 GS SS SM ISC[3:0];
LCD_RAM = 0x3B;
LCD_REG = 0xB7;
LCD_RAM = 0x07;
LCD_REG = 0xE0;
LCD_RAM = 0x1F;
LCD_RAM = 0x25;
LCD_RAM = 0x22;
LCD_RAM = 0x0B;
LCD_RAM = 0x06;
LCD_RAM = 0x0A;
LCD_RAM = 0x4E;
LCD_RAM = 0xC6;
LCD_RAM = 0x39;
LCD_RAM = 0x00;
LCD_RAM = 0x00;
LCD_RAM = 0x00;
LCD_RAM = 0x00;
LCD_RAM = 0x00;
LCD_RAM = 0x00;
LCD_REG = 0XE1;
LCD_RAM = 0x1F;
LCD_RAM = 0x3F;
LCD_RAM = 0x3F;
LCD_RAM = 0x0F;
LCD_RAM = 0x1F;
LCD_RAM = 0x0F;
LCD_RAM = 0x46;
LCD_RAM = 0x49;
LCD_RAM = 0x31;
LCD_RAM = 0x05;
LCD_RAM = 0x09;
LCD_RAM = 0x03;
LCD_RAM = 0x1C;
LCD_RAM = 0x1A;
LCD_RAM = 0x00;
LCD_REG = 0XF1;
LCD_RAM = 0x36;
LCD_RAM = 0x04;
LCD_RAM = 0x00;
LCD_RAM = 0x3C;
LCD_RAM = 0x0F;
LCD_RAM = 0x0F;
LCD_RAM = 0xA4;
LCD_RAM = 0x02;
LCD_REG = 0XF2;
LCD_RAM = 0x18;
LCD_RAM = 0xA3;
LCD_RAM = 0x12;
LCD_RAM = 0x02;
LCD_RAM = 0x32;
LCD_RAM = 0x12;
LCD_RAM = 0xFF;
LCD_RAM = 0x32;
LCD_RAM = 0x00;
LCD_REG = 0XF4;
LCD_RAM = 0x40;
LCD_RAM = 0x00;
LCD_RAM = 0x08;
LCD_RAM = 0x91;
LCD_RAM = 0x04;
LCD_REG = 0XF8;
LCD_RAM = 0x21;
LCD_RAM = 0x04;
LCD_REG = 0x36;
LCD_RAM = 0x48;
LCD_REG = 0x3A;
LCD_RAM = 0x55;
LCD_REG = 0x11;
delay_ms(20);
LCD_REG = 0x29;
//重新配置写时序控制寄存器的时序
FMC_Bank1E->BWTR[0]&=~(0XF<<0); // 地址建立时间(ADDSET)清零
FMC_Bank1E->BWTR[0]&=~(0XF<<8); // 数据保存时间清零
FMC_Bank1E->BWTR[0]|=4<<0; // 地址建立时间(ADDSET)为4个HCLK =21ns
FMC_Bank1E->BWTR[0]|=4<<8; // 数据保存时间(DATAST)为5.2ns*4个HCLK=21ns
SetDir(1); // 横屏 1,3 竖屏0,3
ScanDir(3); // 默认扫描方向
LCD_On();
上面代码的最后,有两处要注意:重新配置FMC的一些时序, 设置了屏幕的显示方向,其中的3个小函数,可以完整代码中查看。
未完,待续.....
读写操作已优化,文件、函数结构也已优化,直接调用即可。
c文件可不用修改,主要是按h文件的函数声明调用。
注意字库的使用,本代码使用的是w25q128, GBK字库起始地址:0x00200000
字库
显示ASCII字符
显示汉字