【.Net Micro Framework PortingKit – 13】LCD驱动开发

LCD驱动其实对TinyCLR并没有必要,特别是在EM-STM3210E开发板上,由于该开发板上的内存过小了,片内64K,片外扩展了128K,加起来也不过172K,而咱们知道针对320*240的显示大小,16bit的位图所占的大小就是150K,很显然.Net Micro Framework所提供的图形库如不加修改是很难正常运行的,不过对咱们来讲在LCD屏幕上显示文字信息也是值得期待的,若是修改一下图形库,在LCD上画个线、画个圆和显示个位图也毫不成问题。c++

和咱们之前开发的驱动相比,LCD的驱动开发仍是比较繁琐一些的,由于LCD的驱动代码分散在三个目录中(题外话,我以为针对.Net Micro Framework来讲,最难的驱动是网卡驱动(特别是wifi驱动)、其次是USB驱动,和它们相比,LCD驱动就是小菜了)。ide

和其它驱动相似,在具体写LCD驱动以前,咱们先在CortexM3.h头文件里,写一个和LCD寄存器相关的结构体,以便以于操做LCD寄存器,这种作法其实也是.Net Micro Framework驱动代码的一种风格。函数

  
  
  
  
  1. struct CortexM3_LCD  
  2.  
  3. {  
  4.  
  5.   //LCD /CS is CE4 - Bank 4 of NOR/SRAM Bank 1~4  
  6.  
  7.   static const UINT32 c_Base = 0x6C000000;  
  8.  
  9.     
  10.  
  11.   /****/ volatile UINT16 REG;  
  12.  
  13.   /****/ volatile UINT16 RAM;  
  14.  
  15.   void WriteReg(UINT8 Reg,UINT16 Value)  
  16.  
  17.   {  
  18.  
  19.       REG = Reg;   
  20.  
  21.       RAM = Value;  
  22.  
  23.   }  
  24.  
  25.   UINT16 ReadReg(UINT8 Reg)  
  26.  
  27.   {  
  28.  
  29.       REG = Reg;   
  30.  
  31.       return RAM;  
  32.  
  33.   }  
  34.  
  35.   void SetCursor(UINT16 x,UINT16 y)  
  36.  
  37.   {  
  38.  
  39.       WriteReg(32,x);  
  40.  
  41.       WriteReg(33,y);  
  42.  
  43.   }  
  44.  
  45.   void SetPixel(UINT16 x,UINT16 y,UINT16 c)  
  46.  
  47.   {  
  48.  
  49.       WriteReg(32,x);  
  50.  
  51.       WriteReg(33,y);  
  52.  
  53.       WriteReg(34,c);  
  54.  
  55.   }  
  56.  
  57.   void WriteRAM_Prepare()  
  58.  
  59.   {  
  60.  
  61.       REG = 34;   
  62.  
  63.   }     
  64.  
  65. };  
  66.  

首先咱们在\DeviceCode\Targets\Native\CortexM3\DeviceCode目录建立LCD子目录,该驱动文件其实实现的功能很简单,归结起来就以下四个函数。测试

 

  
  
  
  
  1. LCD_Controller_Initialize     LCD初始化  
  2.  
  3. LCD_Controller_Uninitialize     
  4.  
  5. LCD_Controller_Enable       LCD使能  
  6.  
  7. LCD_GetFrameBuffer         得到LCD显示缓冲区首地址  

可是对咱们的ILI9320 LCD来讲,LCD_Controller_Enable和LCD_GetFrameBuffer都不是必要的,从咱们开发板附送的LCD示例上看,LCD显示缓冲区首地址的概念彷佛是不存在的,咱们直接写RAM便可,至于写入坐标是经过代码  WriteReg(32,x)和WriteReg(33,y)来控制(其实LCD驱动并不像我想象的那么简单,有不少功能须要仔细研究技术手册才能发现,不过为了简单起见,咱们先按示例提供的方式来显示)。优化

在LCD_Controller_Initialize初始化代码中须要几个延时(我在写SysTick驱动的文章中提到了这一点),以下代码所示:this

  
  
  
  
  1. CortexM3_LCD &LCD = CortexM3::LCD();  
  2.  
  3.      HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000);  
  4.  
  5.      LCD.WriteReg(227, 0x3008);   // Set internal timing  
  6.  
  7.     LCD.WriteReg(231, 0x0012);   // Set internal timing  
  8.  
  9.     LCD.WriteReg(239, 0x1231);   // Set internal timing  
  10.  
  11.     LCD.WriteReg(1  , 0x0100);   // set SS=1:0x0100 and SM=0:0x0400 bit  
  12.  
  13.     LCD.WriteReg(2  , 0x0700);   // set 1 line inversion  
  14.  
  15.     LCD.WriteReg(3  , 0x1030);   // set GRAM write direction and BGR=1.  
  16.  
  17.     //放缩 0x0000 无 0x0001 1/2 0x0003 1/4  
  18.  
  19.     LCD.WriteReg(4  , 0x0000);   // Resize register  
  20.  
  21.     LCD.WriteReg(8  , 0x0207);   // set the back porch and front porch  
  22.  
  23.     LCD.WriteReg(9  , 0x0000);   // set non-display area refresh cycle ISC[3:0]  
  24.  
  25.     LCD.WriteReg(10 , 0x0000);   // FMARK function  
  26.  
  27.     //0x0000 18bit RGB接口 0x0001 16bit RGB接口  0x0002 6bit RGB接口   
  28.  
  29.     LCD.WriteReg(12 , 0x0000);   // RGB interface setting  
  30.  
  31.     LCD.WriteReg(13 , 0x0000);   // Frame marker Position  
  32.  
  33.     LCD.WriteReg(15 , 0x0000);   // RGB interface polarity  
  34.  
  35.     /**************Power On sequence ****************/  
  36.  
  37.     LCD.WriteReg(16 , 0x0000);   // SAP, BT[3:0], AP, DSTB, SLP, STB  
  38.  
  39.     LCD.WriteReg(17 , 0x0007);   // DC1[2:0], DC0[2:0], VC[2:0]  
  40.  
  41.     LCD.WriteReg(18 , 0x0000);   // VREG1OUT voltage  
  42.  
  43.     LCD.WriteReg(19 , 0x0000);   // VDV[4:0] for VCOM amplitude  
  44.  
  45.     HAL_Time_Sleep_MicroSeconds_InterruptEnabled(200000);     // Delay 200 MS , Dis-charge capacitor power voltage  
  46.  
  47.      LCD.WriteReg(16 , 0x1690);   // SAP, BT[3:0], AP, DSTB, SLP, STB  
  48.  
  49.     LCD.WriteReg(17 , 0x0227);   // R11H=0x0221 at VCI=3.3V, DC1[2:0], DC0[2:0], VC[2:0]  
  50.  
  51.      HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000);      // Delay 50ms  
  52.  
  53.      LCD.WriteReg(18 , 0x001D);   // External reference voltageVci;  
  54.  
  55.     HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000);      // Delay 50ms  
  56.  
  57.     LCD.WriteReg(19 , 0x0800);   // R13H=1D00 when R12H=009D;VDV[4:0] for VCOM amplitude      
  58.  
  59.     LCD.WriteReg(41 , 0x0014);   // R29H=0013 when R12H=009D;VCM[5:0] for VCOMH  
  60.  
  61.     LCD.WriteReg(43 , 0x000B);   // Frame Rate = 96Hz 
  62.  
  63.     HAL_Time_Sleep_MicroSeconds_InterruptEnabled(50000);      // Delay 50ms  
  64.  
  65.     LCD.WriteReg(32 , 0x0000);   // GRAM horizontal Address  
  66.  
  67.     LCD.WriteReg(33 , 0x0000);   // GRAM Vertical Address  
  68.  

对NativeSample项目来讲,在debug版本直接使用HAL_Time_Sleep_MicroSeconds_InterruptEnabled是没有问题的,可是在Release版本上面的函数前面要加一句GLOBAL_LOCK(irq)代码,也就是循环期间要关闭中断,才能正常运行。不过不管加不加这句代码,在TinyCLR项目中,Release版本也是没法正常运行的,甚至是直接用for循环也不行,不知道MDK的编译优化到底改变了些什么,有时间须要深刻研究一下(固然上班以后,我也能够在RVDS上调试看看,有时候就是这样,一样的代码用MDK调试通不过,可是用RVDS就没有问题)。spa

接下来咱们要在\DeviceCode\Drivers\Display目录下新建ILI9320子目录,在该目录的代码中咱们实现文字显示(固然指西文字符,要显示汉字,在如此小的内存中咱们得须要特别的技巧)。插件

详细的代码这里我就不贴了,不过要特别说明的是,EM-STM3210E的开发板有些问题,LCD实际上是倒着装的,按开发板的测试示例来移植咱们的代码,最终的显示是倒的,以下图所示:debug

 


 

因此咱们一是要重设LCD相应的寄存器,把显存翻转过来,代码以下:3d

  
  
  
  
  1. /* 数据颜色序(BGR): 0x1000 BGR  0x0000 RGB */ 
  2.  
  3.      /* 扫描方向(AM): 0x0008 从右到左  0x0000 从上到下 */ 
  4.  
  5.      /* 数据填充方向(I/D):0x0000  0x0010  0x0020  0x0030 */ 
  6.  
  7.     LCD.WriteReg(3  , 0x1000 | 0x0000 ); //0x0030 正常模式  
  8.  

其次是修改WriteChar函数里的代码,让它适应这个变化,修改后的代码以下,注意要用LCD的宽度和高度值减去x,y的坐标。

  
  
  
  
  1. const UINT8* font = Font_GetGlyph( c );  
  2.  
  3.     CortexM3_LCD &LCD = CortexM3::LCD();  
  4.  
  5.      int cx=0,ry=0;  
  6.  
  7.     for(int y = 0; y < Font_Height(); y++)  
  8.  
  9.     {  
  10.  
  11.         for(int x = 0; x < Font_Width(); x+=2)  
  12.  
  13.         {       
  14.  
  15.             cx=g_ILI9320_Config.ControllerConfig.Width-(col+x)-1;  
  16.  
  17.                 ry=g_ILI9320_Config.ControllerConfig.Height-(row+y)-1;  
  18.  
  19.             // the font data is mirrored  
  20.  
  21.             if(ILI9320_GETBIT(Font_Width() -  x   ,y,font,1)) LCD.SetPixel(cx, ry,0x07e0);  
  22.  
  23.                 else LCD.SetPixel(cx, ry,0);     
  24.  
  25.                   
  26.  
  27.             if(ILI9320_GETBIT(Font_Width() - (x+1),y,font,1)) LCD.SetPixel(cx-1, ry,0x07e0);  
  28.  
  29.                 else LCD.SetPixel(cx-1, ry,0);  
  30.  
  31.         }  
  32.  
  33.     }  
  34.  

修改代码后,咱们的显示就已经翻转了,以下图所示:

 

 

最后咱们还要在\Solutions\STM3210E\DeviceCode目录下新建子目录Display,在该目录下新建ILI9320_config.cpp文件,该文件主要完成LCD的一些参数配置,主要配置以下:

  
  
  
  
  1. #define ILI9320_SCREEN_WIDTH              240  
  2.  
  3. #define ILI9320_SCREEN_HEIGHT             320  
  4.  
  5. #define ILI9320_ENABLE_TFT                TRUE  
  6.  
  7. #define ILI9320_ENABLE_COLOR              TRUE  
  8.  
  9. #define ILI9320_PIXEL_POLARITY            FALSE  
  10.  
  11. #define ILI9320_FIRST_LINE_POLARITY       FALSE  
  12.  
  13. #define ILI9320_LINE_PULSE_POLARITY       FALSE  
  14.  
  15. #define ILI9320_SHIFT_CLK_POLARITY        FALSE  
  16.  
  17. #define ILI9320_OUTPUT_ENABLE_POLARITY    FALSE  
  18.  
  19. #define ILI9320_CLK_IDLE_ENABLE           TRUE  
  20.  
  21. #define ILI9320_CLK_SELECT_ENABLE         TRUE  
  22.  
  23.    
  24.  
  25. #define ILI9320_PIXELCLOCKDIVIDER     9  
  26.  
  27. #define ILI9320_BUS_WIDTH             16  
  28.  
  29. #define ILI9320_BITS_PER_PIXEL        16  
  30.  
  31. #define ILI9320_ORIENTATION           0        
  32.  

这时候也许有人会问,何须这么麻烦,把一个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中的代码以下:

  
  
  
  
  1. UINT16 * StartOfLine_src = (UINT16 *)&data[0];  
  2.  
  3.     CortexM3_LCD &LCD = CortexM3::LCD();    
  4.  
  5.     if(x==0 && y==0) LCD.SetCursor(g_ILI9320_Config.ControllerConfig.Width-1,g_ILI9320_Config.ControllerConfig.Height-1);      
  6.  
  7.      LCD.WriteRAM_Prepare();       
  8.  
  9.      for(int i=0;i<width*height;i++)  
  10.  
  11.      {  
  12.  
  13.         LCD.RAM = *StartOfLine_src++;      
  14.  
  15.      }      
  16.  

NativeSample.cpp中的测试代码以下:

  
  
  
  
  1. BYTE bytReadData[15360];  
  2.  
  3.      UINT32 index=0x00520046;  
  4.  
  5.      BlockStorageDevice *device= BlockStorageList::GetFirstDevice();   
  6.  
  7.     for(int y=0;y<320;y+=32)  
  8.  
  9.     {  
  10.  
  11.        device->Read(index,15360,bytReadData);  
  12.  
  13.         LCD_BitBltEx(0,y,240,32,(UINT32 *)bytReadData);  
  14.  
  15.         index+=15360;  
  16.  
  17.     }  
  18.  

最终的效果图以下: