1.硬件连接
在硬件上,TFTLCD 模块与战舰 STM32F103 的 IO 口对应关系如下:
LCD_BL(背光控制)对应 PB0;
LCD_CS 对应 PG12 即 FSMC_NE4;
LCD _RS 对应 PG0 即 FSMC_A10;
LCD _WR 对应 PD5 即 FSMC_NWE;
LCD _RD 对应 PD4 即 FSMC_NOE;
LCD _D[15:0]则直接连接在 FSMC_D15~FSMC_D0;
2.代码 :LCD结构体
//LCD地址结构体
typedef struct
{
vu16 LCD_REG;
vu16 LCD_RAM;
} LCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11 A10作为数据命令区分线
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE ((u32)(0x6C000000 | 0x000007FE))
#define LCD ((LCD_TypeDef *) LCD_BASE)
LCD_BASE,须根据外部电路的连接来确定,如Bank1.sector4就是从地址0X6C000000开 始,而0X000007FE,则是A10的偏移量。以A10为例,7FE换成二进制为:111 1111 1110 ,而16位数据时,地址右移一位对齐,对应到地址引脚,就是:A10:A0=011 1111 1111, 此时A10是0,但是如果16位地址再加1(对应到8位地址是加2,即7FE+0X02),那么: A10:A0=100 0000 0000,此时A10就是1了,即实现了对RS的0和1的控制。
我们将这个地址强制转换为LCD_TypeDef结构体地址,那么可以得到LCD->LCD_REG的 地址就是0X6C00,07FE,对应A10的状态为0(即RS=0),而LCD-> LCD_RAM的地址就是 0X6C00,0800(结构体地址自增),对应A10的状态为1(即RS=1),从而实现对RS的控制。
关于vu16,实际上就是u16,但前面加上了volatile volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。 例如: volatile int i=10; int j = i; ... int k = i; volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
3.代码 :_lcd_dev结构体
//LCD重要参数集
typedef struct
{
u16 width; //LCD 宽度
u16 height; //LCD 高度
u16 id; //LCD ID
u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
u16 wramcmd; //开始写gram指令
u16 setxcmd; //设置x坐标指令
u16 setycmd; //设置y坐标指令
} _lcd_dev;
//LCD参数
extern _lcd_dev lcddev; //管理LCD重要参数
lcddev结构体参数的赋值,基本上都是在LCD_Display_Dir函数完成
//设置LCD显示方向
//dir:0,竖屏;1,横屏
void LCD_Display_Dir(u8 dir)
{
lcddev.dir = dir; //竖屏/横屏
if (dir == 0) //竖屏
{
lcddev.width = 240;
lcddev.height = 320;
if (lcddev.id == 0x5510)
{
lcddev.wramcmd = 0X2C00;
lcddev.setxcmd = 0X2A00;
lcddev.setycmd = 0X2B00;
lcddev.width = 480;
lcddev.height = 800;
}
else if (lcddev.id == 0X1963)
{
lcddev.wramcmd = 0X2C; //设置写入GRAM的指令
lcddev.setxcmd = 0X2B; //设置写X坐标指令
lcddev.setycmd = 0X2A; //设置写Y坐标指令
lcddev.width = 480; //设置宽度480
lcddev.height = 800; //设置高度800
}
else //其他IC, 包括: 9341 / 5310 / 7789等IC
{
lcddev.wramcmd = 0X2C;
lcddev.setxcmd = 0X2A;
lcddev.setycmd = 0X2B;
}
if (lcddev.id == 0X5310) //如果是5310 则表示是 320*480分辨率
{
lcddev.width = 320;
lcddev.height = 480;
}
}
else //横屏
{
lcddev.width = 320;
lcddev.height = 240;
if (lcddev.id == 0x5510)
{
lcddev.wramcmd = 0X2C00;
lcddev.setxcmd = 0X2A00;
lcddev.setycmd = 0X2B00;
lcddev.width = 800;
lcddev.height = 480;
}
else if (lcddev.id == 0X1963)
{
lcddev.wramcmd = 0X2C; //设置写入GRAM的指令
lcddev.setxcmd = 0X2A; //设置写X坐标指令
lcddev.setycmd = 0X2B; //设置写Y坐标指令
lcddev.width = 800; //设置宽度800
lcddev.height = 480; //设置高度480
}
else //其他IC, 包括: 9341 / 5310 / 7789等IC
{
lcddev.wramcmd = 0X2C;
lcddev.setxcmd = 0X2A;
lcddev.setycmd = 0X2B;
}
if (lcddev.id == 0X5310) //如果是5310 则表示是 320*480分辨率
{
lcddev.width = 480;
lcddev.height = 320;
}
}
LCD_Scan_Dir(DFT_SCAN_DIR); //默认扫描方向
}
4.底层接口函数
7个底层接口函数:
1,写寄存器值函数 :void LCD_WR_REG(u16 regval)
2,写数据函数:void LCD_WR_DATA(u16 data)
3,读数据函数:u16 LCD_RD_DATA(void)
4,写寄存器内容函数: void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue)
5,读寄存器内容函数: u16 LCD_ReadReg(u16 LCD_Reg)
6,开始写GRAM函数: void LCD_WriteRAM_Prepare(void)
7,写GRAM函数: void LCD_WriteRAM(u16 RGB_Code)
1.写寄存器值
写入地址之后FSMC会硬件去实现
//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(u16 regval)
{
LCD->LCD_REG=regval; //写入要写的寄存器序号
}
2.写LCD数据
//写LCD数据
//data:要写入的值
void LCD_WR_DATA(u16 data)
{
LCD->LCD_RAM=data;
}
3.
//读LCD数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{
vu16 ram; //防止被优化
ram=LCD->LCD_RAM;
return ram;
}
4.
//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
{
LCD->LCD_REG = LCD_Reg; //写入要写的寄存器序号
LCD->LCD_RAM = LCD_RegValue;//写入数据
}
5.
//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(u16 LCD_Reg)
{
LCD_WR_REG(LCD_Reg); //写入要读的寄存器序号
delay_us(5);
return LCD_RD_DATA(); //返回读到的值
}
6.
//开始写GRAM
void LCD_WriteRAM_Prepare(void)
{
LCD->LCD_REG = lcddev.wramcmd;
}
7.
//LCD写GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code)
{
LCD->LCD_RAM = RGB_Code; //写十六位GRAM
}
5. 第二层--LCD函数
1.初始化
LCD初始化函数伪代码:
//LCD初始化
void LCD_Init(void)
{
初始化GPIO;
初始化FSMC; //Mini板不需要
读取LCD ID;
printf(“LCD ID:%x\r\n”,lcddev.id);//打印LCD ID,用到了串口1
//所以必须初始化串口1,否则黑屏
根据不同的ID执行LCD初始化代码;
LCD_Display_Dir(0); //默认为竖屏
LCD_LED=1; //点亮背光
LCD_Clear(WHITE); //清屏
}
2.
//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
if(lcddev.id==0X9341||lcddev.id==0X5310)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(Xpos>>8);
LCD_WR_DATA(Xpos&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_DATA(Ypos&0XFF);
}else if(lcddev.id==XXXX) //根据不同的LCD型号,执行不同的代码
{
……//省略部分代码
}
}
3.
//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
LCD->LCD_RAM=POINT_COLOR; //非Mini板的操作方式
}
4.LCD读点函数:u16 LCD_ReadPoint(u16 x,u16 y) 1,非Mini板的读点函数代码(FSMC方式,适合战舰、精英、探索者F4板) 2,Mini板的读点函数代码(GPIO方式,适合Mini板)
//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
u16 LCD_ReadPoint(u16 x, u16 y)
{
u16 r, g, b;
if (x >= lcddev.width || y >= lcddev.height)return 0; //超过了范围,直接返回
LCD_SetCursor(x, y);
if (lcddev.id == 0X5510) //5510 发送读GRAM指令
{
LCD_WR_REG(0X2E00);
}
else //其他IC(9341/5310/1963/7789)发送读GRAM指令
{
LCD_WR_REG(0X2E);
}
r = LCD_RD_DATA(); //假读
if (lcddev.id == 0X1963) //对1963来说,是真读
{
return r; //1963直接读就可以
}
r = LCD_RD_DATA(); //实际坐标颜色
//9341/5310/5510/7789 要分2次读出
b = LCD_RD_DATA();
g = r & 0XFF; //对于 9341/5310/5510/7789, 第一次读取的是RG的值,R在前,G在后,各占8位
g <<= 8;
return (((r >> 11) << 11) | ((g >> 10) << 5) | (b >> 11)); // 9341/5310/5510/7789 需要公式转换一下
}
5.叠加方式会覆盖颜色,非叠加方式不会
//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{
u8 temp,t1,t;
u16 y0=y;
u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字体一个字符对应点阵集所占的字节数
num=num-' '; //得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
for(t=0;t<csize;t++)
{
if(size==12)temp=asc2_1206[num][t]; //调用1206字体
else if(size==16)temp=asc2_1608[num][t]; //调用1608字体
else if(size==24)temp=asc2_2412[num][t]; //调用2412字体
else return; //没有的字库
for(t1=0;t1<8;t1++)
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
temp<<=1;
y++;
if(y>=lcddev.height)return; //超区域了
if((y-y0)==size)
{
y=y0;
x++;
if(x>=lcddev.width)return; //超区域了
break;
}
}
}
}