LCD驱动其实对TinyCLR并无必要,特别是在EM-STM3210E开发板上,因为该开发板上的内存太小了,片内64K,片外扩展了128K,加起来也不过172K,而我们知道针对320*240的显示大小,16bit的位图所占的大小就是150K,很显然.Net Micro Framework所提供的图形库如不加修改是很难正常运行的,不过对我们来说在LCD屏幕上显示文字信息也是值得期待的,如果修改一下图形库,在LCD上画个线、画个圆和显示个位图也绝不成问题。
和我们以前开发的驱动相比,LCD的驱动开发还是比较繁琐一些的,因为LCD的驱动代码分散在三个目录中(题外话,我觉得针对.Net Micro Framework来说,最难的驱动是网卡驱动(特别是wifi驱动)、其次是USB驱动,和它们相比,LCD驱动就是小菜了)。
和其它驱动类似,在具体写LCD驱动之前,我们先在CortexM3.h头文件里,写一个和LCD寄存器相关的结构体,以便以于操作LCD寄存器,这种做法其实也是.Net Micro Framework驱动代码的一种风格。
- struct CortexM3_LCD
- {
- //LCD /CS is CE4 - Bank 4 of NOR/SRAM Bank 1~4
- static const UINT32 c_Base = 0x6C000000;
- /****/ volatile UINT16 REG;
- /****/ volatile UINT16 RAM;
- void WriteReg(UINT8 Reg,UINT16 Value)
- {
- REG = Reg;
- RAM = Value;
- }
- UINT16 ReadReg(UINT8 Reg)
- {
- REG = Reg;
- return RAM;
- }
- void SetCursor(UINT16 x,UINT16 y)
- {
- WriteReg(32,x);
- WriteReg(33,y);
- }
- void SetPixel(UINT16 x,UINT16 y,UINT16 c)
- {
- WriteReg(32,x);
- WriteReg(33,y);
- WriteReg(34,c);
- }
- void WriteRAM_Prepare()
- {
- REG = 34;
- }
- };
首先我们在\DeviceCode\Targets\Native\CortexM3\DeviceCode目录创建LCD子目录,该驱动文件其实实现的功能很简单,归结起来就如下四个函数。
- LCD_Controller_Initialize LCD初始化
- LCD_Controller_Uninitialize
- LCD_Controller_Enable LCD使能
- LCD_GetFrameBuffer 获得LCD显示缓冲区首地址
但是对我们的ILI9320 LCD来说,LCD_Controller_Enable和LCD_GetFrameBuffer都不是必要的,从我们开发板附送的LCD示例上看,LCD显示缓冲区首地址的概念似乎是不存在的,我们直接写RAM即可,至于写入坐标是通过代码 WriteReg(32,x)和WriteReg(33,y)来控制(其实LCD驱动并不像我想象的那么简单,有很多功能需要仔细研究技术手册才能发现,不过为了简单起见,我们先按示例提供的方式来显示)。
在LCD_Controller_Initialize初始化代码中需要几个延时(我在写SysTick驱动的文章中提到了这一点),如下代码所示:
- CortexM3_LCD &LCD = CortexM3::LCD();
- HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000);
- LCD.WriteReg(227, 0x3008); // Set internal timing
- LCD.WriteReg(231, 0x0012); // Set internal timing
- LCD.WriteReg(239, 0x1231); // Set internal timing
- LCD.WriteReg(1 , 0x0100); // set SS=1:0x0100 and SM=0:0x0400 bit
- LCD.WriteReg(2 , 0x0700); // set 1 line inversion
- LCD.WriteReg(3 , 0x1030); // set GRAM write direction and BGR=1.
- //放缩 0x0000 无 0x0001 1/2 0x0003 1/4
- LCD.WriteReg(4 , 0x0000); // Resize register
- LCD.WriteReg(8 , 0x0207); // set the back porch and front porch
- LCD.WriteReg(9 , 0x0000); // set non-display area refresh cycle ISC[3:0]
- LCD.WriteReg(10 , 0x0000); // FMARK function
- //0x0000 18bit RGB接口 0x0001 16bit RGB接口 0x0002 6bit RGB接口
- LCD.WriteReg(12 , 0x0000); // RGB interface setting
- LCD.WriteReg(13 , 0x0000); // Frame marker Position
- LCD.WriteReg(15 , 0x0000); // RGB interface polarity
- /**************Power On sequence ****************/
- LCD.WriteReg(16 , 0x0000); // SAP, BT[3:0], AP, DSTB, SLP, STB
- LCD.WriteReg(17 , 0x0007); // DC1[2:0], DC0[2:0], VC[2:0]
- LCD.WriteReg(18 , 0x0000); // VREG1OUT voltage
- LCD.WriteReg(19 , 0x0000); // VDV[4:0] for VCOM amplitude
- HAL_Time_Sleep_MicroSeconds_InterruptEnabled(200000); // Delay 200 MS , Dis-charge capacitor power voltage
- LCD.WriteReg(16 , 0x1690); // SAP, BT[3:0], AP, DSTB, SLP, STB
- LCD.WriteReg(17 , 0x0227); // R11H=0x0221 at VCI=3.3V, DC1[2:0], DC0[2:0], VC[2:0]
- HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000); // Delay 50ms
- LCD.WriteReg(18 , 0x001D); // External reference voltage= Vci;
- HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000); // Delay 50ms
- LCD.WriteReg(19 , 0x0800); // R13H=1D00 when R12H=009D;VDV[4:0] for VCOM amplitude
- LCD.WriteReg(41 , 0x0014); // R29H=0013 when R12H=009D;VCM[5:0] for VCOMH
- LCD.WriteReg(43 , 0x000B); // Frame Rate = 96Hz
- HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000); // Delay 50ms
- LCD.WriteReg(32 , 0x0000); // GRAM horizontal Address
- LCD.WriteReg(33 , 0x0000); // GRAM Vertical Address
对NativeSample项目来说,在debug版本直接使用HAL_Time_Sleep_MicroSeconds_InterruptEnabled是没有问题的,但是在Release版本上面的函数前面要加一句GLOBAL_LOCK(irq)代码,也就是循环期间要关闭中断,才能正常运行。不过无论加不加这句代码,在TinyCLR项目中,Release版本也是无法正常运行的,甚至是直接用for循环也不行,不知道MDK的编译优化到底改变了些什么,有时间需要深入研究一下(当然上班之后,我也可以在RVDS上调试看看,有时候就是这样,同样的代码用MDK调试通不过,但是用RVDS就没有问题)。
接下来我们要在\DeviceCode\Drivers\Display目录下新建ILI9320子目录,在该目录的代码中我们实现文字显示(当然指西文字符,要显示汉字,在如此小的内存中我们得需要特别的技巧)。
详细的代码这里我就不贴了,不过要特别说明的是,EM-STM3210E的开发板有些问题,LCD其实是倒着装的,按开发板的测试示例来移植我们的代码,最终的显示是倒的,如下图所示:
所以我们一是要重设LCD相应的寄存器,把显存翻转过来,代码如下:
- /* 数据颜色序(BGR): 0x1000 BGR 0x0000 RGB */
- /* 扫描方向(AM): 0x0008 从右到左 0x0000 从上到下 */
- /* 数据填充方向(I/D):0x0000 0x0010 0x0020 0x0030 */
- LCD.WriteReg(3 , 0x1000 | 0x0000 ); //0x0030 正常模式
其次是修改WriteChar函数里的代码,让它适应这个变化,修改后的代码如下,注意要用LCD的宽度和高度值减去x,y的坐标。
- const UINT8* font = Font_GetGlyph( c );
- CortexM3_LCD &LCD = CortexM3::LCD();
- int cx=0,ry=0;
- for(int y = 0; y < Font_Height(); y++)
- {
- for(int x = 0; x < Font_Width(); x+=2)
- {
- cx=g_ILI9320_Config.ControllerConfig.Width-(col+x)-1;
- ry=g_ILI9320_Config.ControllerConfig.Height-(row+y)-1;
- // the font data is mirrored
- if(ILI9320_GETBIT(Font_Width() - x ,y,font,1)) LCD.SetPixel(cx, ry,0x07e0);
- else LCD.SetPixel(cx, ry,0);
- if(ILI9320_GETBIT(Font_Width() - (x+1),y,font,1)) LCD.SetPixel(cx-1, ry,0x07e0);
- else LCD.SetPixel(cx-1, ry,0);
- }
- }
修改代码后,我们的显示就已经翻转了,如下图所示:
最后我们还要在\Solutions\STM3210E\DeviceCode目录下新建子目录Display,在该目录下新建ILI9320_config.cpp文件,该文件主要完成LCD的一些参数配置,主要配置如下:
- #define ILI9320_SCREEN_WIDTH 240
- #define ILI9320_SCREEN_HEIGHT 320
- #define ILI9320_ENABLE_TFT TRUE
- #define ILI9320_ENABLE_COLOR TRUE
- #define ILI9320_PIXEL_POLARITY FALSE
- #define ILI9320_FIRST_LINE_POLARITY FALSE
- #define ILI9320_LINE_PULSE_POLARITY FALSE
- #define ILI9320_SHIFT_CLK_POLARITY FALSE
- #define ILI9320_OUTPUT_ENABLE_POLARITY FALSE
- #define ILI9320_CLK_IDLE_ENABLE TRUE
- #define ILI9320_CLK_SELECT_ENABLE TRUE
- #define ILI9320_PIXELCLOCKDIVIDER 9
- #define ILI9320_BUS_WIDTH 16
- #define ILI9320_BITS_PER_PIXEL 16
- #define ILI9320_ORIENTATION 0
这时候也许有人会问,何必这么麻烦,把一个LCD驱动分解到三个目录中,都放在一个目录行不行? 当然这是可以的,这样做的目的就是,针对不同开发板,有些代码可以方便地复用。
开发完LCD驱动,如果仅仅显示文字,不显示一个位图,总觉得缺少点什么。说干就干,我显示一个320*240的位图。
但说起来容易做起来难,一个16bit的320*240的位图要150k,先不说如何去显示它,光说它放在什么地方,就是一个头疼的地方,如果把它编写到代码中,我们的片内Flash才512k,很显然放不下,放到NandFlash中,我们没有实现文件系统,在说也没有实现UsbMassStorage功能,我们也无法把图片拷贝到NandFlash中。
幸好在开发wifi驱动过程中我实现了一个MFDeploy的插件,通过该插件可以把位图下载到NandFlash中(因为Ti提供的wifi开发板,wifi初始化时需要加载三个文件,原先是放在文件系统中的,单这样做代价高了点,不仅要实现文件系统,还要实现UsbMassStorage功能,否则文件也是没法放到NandFlash上的,所以才开发了这个插件)。至于这个插件是如何实现的,我在以后准备要写的【玩转.Net MF】系列文章中我会详细介绍的。
在下载位图之前,我们先做一张320*240的位图,保存时要做如下选择,如下图:
要选择R5G6B5,此外要勾选翻转行序(也可以不勾选,不过代码要做些调整,否则图形是倒的)。
我们的实现思路是,从NandFlash中先读一部分数据,然后再显示一部分图形,最终完成一幅图的显示,这样就避开了内容小的问题。
LCD驱动中位图显示函数LCD_BitBltEx中的代码如下:
- UINT16 * StartOfLine_src = (UINT16 *)&data[0];
- CortexM3_LCD &LCD = CortexM3::LCD();
- if(x==0 && y==0) LCD.SetCursor(g_ILI9320_Config.ControllerConfig.Width-1,g_ILI9320_Config.ControllerConfig.Height-1);
- LCD.WriteRAM_Prepare();
- for(int i=0;i
- {
- LCD.RAM = *StartOfLine_src++;
- }
NativeSample.cpp中的测试代码如下:
- BYTE bytReadData[15360];
- UINT32 index=0x00520046;
- BlockStorageDevice *device= BlockStorageList::GetFirstDevice();
- for(int y=0;y<320;y+=32)
- {
- device->Read(index,15360,bytReadData);
- LCD_BitBltEx(0,y,240,32,(UINT32 *)bytReadData);
- index+=15360;
- }
最终的效果图如下: