硬件驱动——I.MX6ULL裸机启动(9)(RGBLCD相关设置)
一.RGBLCD相关定义和概念
1.LCD全称是liquid crystal display,就是液晶显示器.
是现在最常用到的显示器,手机,电脑,各种人机交互设备等基本上都用到了LCD,最常见就是手机和电脑显示器。我们这里所提及的LCD不包括触摸功能,触摸功能是通过另外的设备实现的,LCD在这里只是输出设备。
LCD的原理:LCD的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过TFT上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目标。
2.分辨率:
LCD显示器,有720P,1080P,2K,4K等LCD显示器分辨率。LCD显示器都是由一个一个的像素点组成,像素点就类似一个灯(在OLED显示器中,像素点就是一个小灯),这个小灯是RGB灯,也就是由R(红色),G(绿色)和B(蓝色)这三种颜色组成的,而RGB就是光的三原色。1080P的意思就是一个LCD屏幕上的像素数量是1920*1080个,也就是这个屏幕一列1080个像素点,一共1920列。
上图是1080P显示器的像素示意图,X轴就是LCD显示器的横轴,Y轴就是显示器的竖轴。图中的小方块就是像素点,一共有1920*1080 = 2073600个像素点。左上角的A点是第一个像素点,右下角的C点就是最后一个像素点。在LCD尺寸不变的情况下,分辨率越高越清晰。同样的,分辨率不变的情况下,LCD尺寸越小越清晰。
LCD显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的LCD就越好。衡量一款LCD的好坏,分辨率只是其中的一个参数,还有色彩还原程度,色彩偏离,亮度,可视角度,屏幕刷新率等其它参数。
3.像素格式
一个像素点就相当于一个RGB小灯,通过控制R,G,B这三种颜色的亮度就可以显示出各种各样的色彩。一般一个R,G,B这三部分分别使用8bit的数据,那么一个像素点就是24bit,也就是说一个像素点3个字节,这种像素格式成为RGB888。如果再加入8bit的透明通道的话一个像素点就是32bit,也就是4个字节,这种像素格式称为RGB8888。
如上图,一个像素点是4个字节,其中bit31~bit24是透明通道,bit23~bit16是red通道,bit15~bit8是green通道,bit7~bit0是blue通道。所以红色对应的值就是0x00FF0000,蓝色对应的值就是0x000000FF,绿色对应的值是0x0000FF00
4.LCD硬件接口
LCD屏幕或者说显示器有很多种接口,比如在显示器上常见的VGA,HDMI,DP等等,但是IMX6ULL开发板不支持这些接口。该开发板支持RGB接口的LCD,RGBLCD接口的信号线如下表:
R,G,B这24根是数据线,其他的是控制线
5.LCD时间参数
如果将LCD显示一帧图像的过程想象成绘画,那么在现实化的过程中就是和用一只“笔”在不同的像素点画上不同的颜色。这根笔按照从左到右,从上到下的顺序扫描每一个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假设一个LCD的分辨率为1024*600,那么其扫描如图:
HSYNC是水平同步信号,也叫做行同步信号,当产生此信号的话表示开始显示新的一行了,所以此信号都是在图的最左边。
VSYNC信号是垂直同步信号,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图的左上角。
6.LCD的时序
上图就是RGBLCD的行显示时序,其中有几个重要参数:
(1)HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,通过查阅手册可以知道此信号是低电平有效还是高电平有效。
(2)HSPW:有些地方也叫做thp,是HSYNC信号宽度,也就是HSYNC信号持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为CLK
(3)HBP:有些地方叫做thb,术语叫做行同步信号后肩
(4)HOZVAL:有些地方叫做thf,显示一行数据所需的时间,假设屏幕分辨率为1024*600,那么HOZVAL就是1024
(5)HFP:有些地方叫做thf,术语叫做行同步信号前肩
当HSYNC信号发出以后,需要等待HSPW+HBP个CLK时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待HFP个CLK时间才能发出下一个HSYNC信号,所以显示一行所需要的时间就是:HSPW+HBP+HOZAL+HFP
上图就是RGBLCD的帧显示时序,我们来分析一下其中的几个参数:
(1)VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查手册可以知道此信号低电平有效还是高电平有效
(2)VSPW:也可称为tvp,是VSYNC信号宽度,也就是VSYNC信号持续时间,单位为一行的时间
(3)VBP:也可称为tvb,术语是帧同步信号后肩,单位为一行的时间
(4)LINE:也可称为tvd,显示一帧有效数据所需的时间,假设屏幕分辨率为1024*600,那么LINE就是600行的时间
(5)VFP:也可称为tvf,术语是帧同步信号前肩,单位为一行的时间
显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP个行时间,最终的计算公式:为T = (VSPW+VBP+LINE+VFP)*(HSPW+HBP+HOZVAL+HFP)
7.像素时钟
我们用的显示屏的分辨率是800*480,其时间参数如下:
显示一帧图像所需要的时钟数是:
(VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)= (3 + 32 + 480 + 13) * (48 + 88 + 800 + 40) =。显示一帧图像需要515328个时钟数,那么显示60帧就是:515328 * 60 = 30,919,680≈31M,所以像素时钟就是 31MHz。
这里的31MHz其实指的就是IMX这片Soc的LCD控制器的工作频率。
图示1的位置是一个选择器,相当于是选择eLCDIF的时钟源。时钟源选择PPL5。配置PLL5,此后的2,3位置是两个分频器,在经过一个选择器最终到达eLCDIF外设,eLCDIF外设,eLCDIF外设的实际时钟名为LCDIF1_CLK_ROOT。
这里其实主要问题是凑几个数,最终使LCDIF1_CLK_ROOT的值恰好是31MHz。我们的计划是这样的:选通PLL5作为时钟源是肯定的,这里先把PLL5的倍频因子设置为42,那么从PLL5输出的频率就是1008MHz,之后在2这个位置4分频,再在3这个位置8分频,那么结果为1008/4/8=31.5
MHz,最后再把这四个位置选通。
注意: CCM_ANALOG_PLL_VIDEOn还有两个域需要设置,POST_DIV_SLECT(bit20:19),是个分频器,我们需要设置为1分频; ENABLE(bit13),用于使能PLL5,需要置1来使能PLL5。此外CCM_ANALOG_MISC2n寄存器里还有一个分频器VIDEO_DIV(bit 31:30),需要设置为1分频。
8.显存问题
关于显存的问题,如果采用 ARGB8888 格式的话一个像素需要 4 个字节的内存来存放像素
数据,那么 1024*600 分辨率就需要 800*480*4=38400B≈1.5MB 内存。但是 RGB LCD 内部是没有内存的,所以就需要在开发板上的 DDR3 中分出一段内存作为 RGB LCD 屏幕的显存,我们如果要在屏幕上显示什么图像的话直接操作这部分显存即可。 eLCDIF控制器有两个寄存器LCDIF_CUR_BUF和LCDIF_NEXT_BUF。LCDIF_CUR_BUF故名思意就是当前缓冲,我们只需把这个寄存设置为上面所说的1.5MB的首地址,那么eLCDIF就会自动把数组里面的值按照RGB888的格式显示到LCD上。 LCDIF_NEXT_BUF呢?就是下一帧的意思,就是当前的数据显示完以后自动切换到LCDIF_NEXT_BUF。一般来说GPU负责渲染,渲染的其实是下一帧,也就是说GPU渲染的总是下一帧图像,一次类推。当然也可以把这两个寄存器设置为同一个地址,那么修改的同时就直接显示出来了,但这样做连续高速变化的图像时可能会有撕裂的情况发生。
LCD裸机驱动的初始化流程为:初始化IO引脚---->初始化像素时钟----->初始化eLCDIF
二.相关代码
#include "lcd.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "gpio.h"
#include "delay.h"void init_lcd_io(void)
{/*1.引脚初始化*/IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA04_LCDIF_DATA04,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA05_LCDIF_DATA05,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA06_LCDIF_DATA06,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA07_LCDIF_DATA07,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA08_LCDIF_DATA08,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA09_LCDIF_DATA09,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA10_LCDIF_DATA10,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA11_LCDIF_DATA11,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA12_LCDIF_DATA12,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA13_LCDIF_DATA13,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA14_LCDIF_DATA14,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA15_LCDIF_DATA15,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA16_LCDIF_DATA16,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA17_LCDIF_DATA17,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA18_LCDIF_DATA18,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA19_LCDIF_DATA19,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA20_LCDIF_DATA20,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA21_LCDIF_DATA21,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA22_LCDIF_DATA22,0);IOMUXC_SetPinMux(IOMUXC_LCD_DATA23_LCDIF_DATA23,0);IOMUXC_SetPinMux(IOMUXC_LCD_CLK_LCDIF_CLK,0);IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0);IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0);IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0);IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08,0);/*设置电气属性*//**bit 16:0 HYS 关闭*bit [15:14]: 0 默认 22k 上拉*bit [13]: 0 pull 功能*bit [12]: 0 pull/keeper 使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度 100MHz*bit [5:3]: 111 驱动能力为 R0/7*bit [0]: 1 高转换率*/IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA02_LCDIF_DATA02,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA03_LCDIF_DATA03,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA04_LCDIF_DATA04,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA05_LCDIF_DATA05,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA06_LCDIF_DATA06,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA07_LCDIF_DATA07,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA08_LCDIF_DATA08,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA09_LCDIF_DATA09,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA10_LCDIF_DATA10,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA11_LCDIF_DATA11,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA12_LCDIF_DATA12,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA13_LCDIF_DATA13,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA14_LCDIF_DATA14,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA15_LCDIF_DATA15,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA16_LCDIF_DATA16,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA17_LCDIF_DATA17,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA18_LCDIF_DATA18,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA19_LCDIF_DATA19,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA20_LCDIF_DATA20,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA21_LCDIF_DATA21,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA22_LCDIF_DATA22,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_DATA23_LCDIF_DATA23,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0xB9);IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0xB9);IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08,0xB9);struct GPIO_Type_t t = {.direction = gpio_output,.defalut_value = 1};init_gpio(GPIO1,8,&t);write_gpio(GPIO1,8,1);
}void init_lcd_clk(void)
{CCM_ANALOG->PLL_VIDEO |= (1 << 16);CCM_ANALOG->PLL_VIDEO |= (1 << 12);CCM_ANALOG->PLL_VIDEO_NUM = 0;CCM_ANALOG->PLL_VIDEO_DENOM = 1;//PLL_AUDIO 42分频 1分频 24 * 42 = 1008CCM_ANALOG->PLL_VIDEO &= ~(0x7F << 0);CCM_ANALOG->PLL_VIDEO |= (42 << 0);CCM_ANALOG->PLL_VIDEO &= ~(3 << 19);CCM_ANALOG->PLL_VIDEO |= (2 << 19);CCM_ANALOG->PLL_VIDEO &= ~(1 << 12);CCM_ANALOG->PLL_VIDEO |= (1 << 13);while((CCM_ANALOG->PLL_VIDEO & (1 << 31)) == 0);CCM_ANALOG->PLL_VIDEO &= ~(1 << 16);//PLL5作为时钟unsigned int t;t = CCM->CSCDR2;t &= ~(7 << 15);t |= (2 << 15);t &= ~(7 << 12); t |= (3 << 12); //设置分频 4 1008 / 4 = 252t &= ~(7 << 9); //时钟from divided pre-muxed LCDIF1 clockCCM->CSCDR2 = t;CCM->CBCMR |= (7 << 23); //设置分频 8 252 / 8 = 31.5}void reset_lcd(void)
{LCDIF->CTRL |= (1 << 31);delayms(20);LCDIF->CTRL &= ~(3 << 30);
}struct tftlcd_t lcd_dev;
void lcd_clear(unsigned int color)
{unsigned int (*p)[800] = (unsigned int (*)[800])lcd_dev.frame_buffer; //指向帧缓存首地址int i,j; //缓冲区总长度for(i = 0; i < lcd_dev.height; ++i){for(j = 0; j < lcd_dev.width;++j){p[i][j] = color;}}}void init_lcd(void)
{init_lcd_io();reset_lcd();init_lcd_clk();lcd_dev.width = 800;lcd_dev.height = 480;lcd_dev.hspw = 48;lcd_dev.hbp = 88;lcd_dev.hfp = 40;lcd_dev.vspw = 3;lcd_dev.vbp = 32;lcd_dev.vfp = 13;lcd_dev.pix_size = 4;lcd_dev.frame_buffer = LCD_FRAMEBUFFER_ADDR;lcd_dev.fore_color = 0x0000FF00;lcd_dev.back_color = 0x00FFFFFF;LCDIF->CTRL |= (1 << 19) | (1 << 17) | (3 << 10) | (3 << 8) | (1 << 5);LCDIF->CTRL1 = (7 << 16);LCDIF->TRANSFER_COUNT = (lcd_dev.height << 16) | (lcd_dev.width);LCDIF->CUR_BUF = lcd_dev.frame_buffer;LCDIF->NEXT_BUF = lcd_dev.frame_buffer;LCDIF->VDCTRL0 = (1 << 28) | (1 << 24) | (1 << 21) | (1 << 20) | (lcd_dev.vspw << 0);LCDIF->VDCTRL1 = lcd_dev.height + lcd_dev.vspw + lcd_dev.vfp + lcd_dev.vbp;LCDIF->VDCTRL2 = (lcd_dev.hspw << 18) | (lcd_dev.hspw + lcd_dev.hfp + lcd_dev.hbp + lcd_dev.width);LCDIF->VDCTRL3 = ((lcd_dev.hspw + lcd_dev.hbp) << 16) | (lcd_dev.vspw + lcd_dev.vbp);LCDIF->VDCTRL4 = (1 << 18) | (lcd_dev.width);LCDIF->CTRL |= (1 << 0);delayms(20);
}void lcd_drawpoint(int x, int y, unsigned int color)
{unsigned int *p = (unsigned int *)lcd_dev.frame_buffer;if(x >= lcd_dev.width || y >= lcd_dev.height){return;}*(p + lcd_dev.width * y + x) = color;
}
#ifndef __LCD_H__
#define __LCD_H__
extern void init_lcd(void);
extern void init_lcd_clk(void);
extern void init_lcd_io(void);
extern void reset_lcd(void);
extern void lcd_clear(unsigned int color);
extern void lcd_drawpoint(int x, int y, unsigned int color);
struct tftlcd_t
{unsigned short height;unsigned short width;unsigned short pix_size; //每像素所占字节数unsigned short vspw; //帧同步信号宽度unsigned short vbp; //帧同步信号后肩unsigned short vfp; //帧同步信号前肩unsigned short hspw; //行同步信号宽度unsigned short hbp; //行同步信号后肩unsigned short hfp; //行同步信号前肩unsigned int frame_buffer;unsigned int fore_color;unsigned int back_color;};
extern struct tftlcd_t lcd_dev;
#define LCD_FRAMEBUFFER_ADDR (0x89000000)
#define COLOR(r, g, b) ((r << 16) | (g << 8) | (b << 0))
#endif
#include "beep.h"
#include "led.h"
#include "key.h"
#include "core_ca7.h"
#include "interrupt.h"
#include "clock.h"
#include "epit.h"
#include "gpt.h"
#include "delay.h"
#include "uart.h"
#include "stdio.h"
#include "i2c.h"
#include "string.h"
#include "lm75.h"
#include "adc.h"
#include "adxl345.h"
#include "spi.h"
#include "lcd.h"
#include "framebuffer.h"
int main(void)
{init_clock();system_interrupt_init();init_beep();init_led();
// init_key();
// init_epit1();init_gpt1();init_uart1();init_i2c1();init_adc1_channle1();init_spi3();init_adxl345();init_lcd();lcd_clear(0x00FFFFFF);// lcd_draw_rectangle(100,100,300,400);// lcd_fill(100,100,300,400,0x00FF0000);// char *s = "Hello World!";// lcd_show_string(400,240,12 * strlen(s),24,24,s);// s = "China";// lcd_show_string(100,100,12 * strlen(s),24,24,s);while(1){// delayms(500);// lcd_clear(0x00FF0000);// delayms(500);// lcd_clear(0x0000FF00);// delayms(500);// lcd_clear(0x000000FF);char buffer[32];float f;f = lm75_get_temperature();int k = f * 10;int m = k / 10;int n = k % 10;sprintf(buffer,"%d.%d ",m,n);lcd_show_string(100,100,16 * strlen(buffer),32,32,buffer);}return 0;
}