无线手持二维码识别项目是中山大学电子与信息工程学院(微电子学院)的工程应用训练课程的设计要求。项目是基于STM32F103开发的,开源代码:stm32f103zet6-qrcode-detect,bilibili:基于STM32F1的无线手持二维码识别项目。CSDN总目录:基于STM32F103的二维码识别项目。项目移植了Zbar库进行二维码识别,而非使用二维码识别模块。
此篇文章讲述TFTLCD相关的设计。
一、 TFT-LCD概述
TFT-LCD(Thin Film Transistor)液晶显示屏是薄膜晶体管型液晶显示屏,也就是“真彩”(TFT)。TFT液晶为每个像素都设有一个半导体开关,每个像素都可以通过点脉冲直接控制,因而每个节点都相对独立,并可以连续控制,不仅提高了显示屏的反应速度,同时可以精确控制显示色阶。市面上的TFT-LCD有不同的芯片驱动类型,如:ILI9341 / ILI9325 / RM68042 等等。
博主使用的是ILI9341驱动的TFTLCD,使用的是SPI的方式传输数据。
二、 RGB565
举例帮助理解:
0xF800,对应二进制1111 1000 0000 0000,高5位都是1,其他位都是0,全是红色分量,没有另外两种颜色的分量,因此0xF800表示的就是纯红色。
0x07D0,对应二进制0000 0111 1110 0000,中6位都是1,其他位都是0,全是绿色分量,没有另外两种颜色的分量,因此0xF800表示的就是纯绿色。
0x001F,对应二进制0000 0000 0001 1111,后5位都是1,其他位都是0,全是蓝色分量,没有另外两种颜色的分量,因此0x001F表示的就是纯蓝色。
三、 关键指令
所有指令和大部分操作参数都是8位的,但是它们整体是16位的,只是高8位无效而已,另外,RGB565的颜色数据也是16位的,而且,颜色数据是主要的数据源,所以我们在使用DMA时,可以将操作的数据位数设置成16位的,从而实现统一操作。
现在我们就能明白为什么代码中有 &0XFF 的操作,因为我们需要的是低8位。
color=GPIOF->IDR&0XFF; //读数据
下面具体介绍一些常用指令。
1. 0xD3指令
顺序列:这一列说明的是发指令还是读参数;
控制列:表示发送当前数据时,各控制位需要处于什么样的状态,例如第一行发送指令时,RS要置0,RD要置1,WR是有效的并且会在上升沿时写入;
各位描述列:指令对应的控制字。
HEX列:各位描述列对应的十六进制数。
注意:发指令时RD置1,在WR的上升沿写入;读参数时WR置1,在RD的上升沿读出;
2. 0x36指令
这个指令用来控制GRAM自增方向。这里的三种关键控制位MY,MX,MV,决定了GRAM自增的方向,也就是LCD的扫描方向。这个指令很重要。正常我们如果想要往LCD上刷个像素点,就设置该点的坐标,然后刷上颜色值。如果是想要刷一个区域的点最基础的方式就只能是设置一下坐标,刷一个点,再设置一下坐标,再刷一个点……如此循环往复,一次刷一个点,每次都要设置坐标。
GRAM自增指令就能让我们只用设置一个整体区域的开始和结束坐标,然后发送颜色数据时,GRAM坐标就会自动增长。这也是我们实现批量发送颜色数据的重要基础,我们可以通过DMA将数据批量发送出去,LCD收到数据时,就会按照设置好的坐标来刷新屏幕。
MY,MX,MV不同的设置带来的后果如下图。
我们来看代码,可以看到代码封装为了一个 LCD_direction 函数,所以使用者能直接改变 direction 的值而无需关心底层实现。注释有写出BGR,MY,MX,MV的取值,对应上表可以很清晰地看出表达的意思。
void LCD_direction(u8 direction)
{
lcddev.setxcmd=0x2A;
lcddev.setycmd=0x2B;
lcddev.wramcmd=0x2C;
switch(direction){
case 0:
lcddev.width=LCD_W;
lcddev.height=LCD_H;
LCD_WriteReg(0x36,(1<<3)|(0<<6)|(0<<7));//BGR==1,MY==0,MX==0,MV==0
break;
case 1:
lcddev.width=LCD_H;
lcddev.height=LCD_W;
LCD_WriteReg(0x36,(1<<3)|(0<<7)|(1<<6)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
break;
case 2:
lcddev.width=LCD_W;
lcddev.height=LCD_H;
LCD_WriteReg(0x36,(1<<3)|(1<<6)|(1<<7));//BGR==1,MY==0,MX==0,MV==0
break;
case 3:
lcddev.width=LCD_H;
lcddev.height=LCD_W;
LCD_WriteReg(0x36,(1<<3)|(1<<7)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
break;
default:break;
}
}
3. 0x2A指令
设置x的开始和x的结束坐标
4. 0x2B指令
设置y的开始和y的结束坐标
我们来看代码,lcddev.setxcmd 就是设置 x 坐标的,lcddev.setycmd 是设置 y 坐标的。封装好了后直接通过 LCD_SetWindows 函数的 xStar 和 yStar传入进行修改,达到了设置窗口位置的效果。
void LCD_SetWindows(u16 xStar, u16 yStar,u16 xEnd,u16 yEnd)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(xStar>>8);
LCD_WR_DATA(0x00FF&xStar);
LCD_WR_DATA(xEnd>>8);
LCD_WR_DATA(0x00FF&xEnd);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(yStar>>8);
LCD_WR_DATA(0x00FF&yStar);
LCD_WR_DATA(yEnd>>8);
LCD_WR_DATA(0x00FF&yEnd);
LCD_WriteRAM_Prepare(); //开始写入GRAM
}
5. 0x2C指令
GRAM是否自增是这个指令决定的,上面的0x36设置的是自增的方向。
6. 0x2E指令
四、 核心程序
当你买了一块TFTLCD屏幕后,跟商家要一下程序例程即可,一般来说商家都会给你封装成易于使用的函数,无需你自己手动进行海量的底层编写。所以我们只需要管上层应用即可。
我们来看二维码识别时核心的部分:如何实现摄像头的内容显示到TFTLCD上:
if(ov_sta)//有帧中断更新
{
LCD_direction(1);
LCD_SetWindows(0,0,320-1,240-1);
OV7670_RRST=0; //开始复位读指针
OV7670_RCK_L;
OV7670_RCK_H;
OV7670_RCK_L;
OV7670_RRST=1; //复位读指针结束
OV7670_RCK_H;
u32 a = 0;
for(j=0;j<76800;j++) //此种方式需清楚TFT内部显示方向控制寄存器值 速度较快
{
OV7670_RCK_L;
color=GPIOF->IDR&0XFF; //读数据
OV7670_RCK_H;
color<<=8;
OV7670_RCK_L;
color|=GPIOF->IDR&0XFF; //读数据
OV7670_RCK_H;
//二值化
if(color > 0x4500)
{
color = 0xffff;
colorQR = 0xff;
}
else
{
color = 0x0000;
colorQR = 0x00;
}
// 检查是否在范围内
if ((j <= 239) || (j >= 320 && ((j - 320) % 320 <= 239))) {
// 在范围内,执行你的操作
data_buf[a] = colorQR;
a++;
Lcd_WriteData_16Bit(color);
}
else {Lcd_WriteData_16Bit(0xffff);}//白色
}
}
}
检查是否在范围内的部分是我为了减轻RAM的负担,让处理二维码的区域变为240×240。二值化的部分去掉就是正常的视频显示。
具体代码见开源链接的 lcd.c,SPI.c