这里的优化会介绍两种模式,GPIO口模拟程序,FSMC模块模式.
FSMC模式只有在STM32大容量模式的主控才有的,优化起来效果确实很是最好的,因为当你根据液晶屏ic配置好fsmc的时候,液晶屏的寄存器和GRAM就会被映射到系统的4G内存空间的某一块区域,我们往指定地址写数据或读数据,FSMC就会自动帮你把数据送到液晶屏控制器上面了,所以这里面我们省去了对GPIO管脚和接口的操作(...这个确实很强大,因为你可以花更多的时间来进行运算,每个点都是通过指令来产生的,产生指令也是需要时间的,同样,模拟IO口时序也是需要时间的,把这部分时间剩下来,是非常可观的),至于一些人不理解FSMC其实也不是很打紧啦,你就把GRAM和液晶屏寄存器想象成一个完整的外置SRAM就可以了,因为他们的时序都是同一个原理的,而FSMC就是会自动帮你模拟各种IO口操作,我把它简单的理解成(你写的io口操作程序,他用硬件来实现了).
相信大家都有看到别人移植好的demo程序,里面最开始以来就是一个speedDEMO,这个是测试每秒钟可以通过计算可以画多少个点,里面包含了随即填充矩阵函数.
分数越高,代表着你刷频也就是越快,当然,跟着我的脚步走,可以快到把刷这个字去掉(不严格意义上);
在做优化的时候,我们得提前做好准备,因为优化更多代表着是结构性的破坏,可读性的疯狂下降,因为从最底层的函数开始一层一层封装上去,使得函数可以很容易的去理解调用,我们优化就是拆开这些函数调用,并把一些有关硬件层的驱动进行修改,对算法进行更新.
举个函数调用的例子,填充矩阵:
这个比较大家可看可不看,涉及到一些汇编的知识.略过对后面的理解没有问题.
- 1 void fill_Rect(int v, int h, int x, int y)
- 2 {
- 3 for (i
- 4 {
- 5 for (j
- 6 {
- 7 Darw_Point(a,b);
- 8 }
- 9 }
- 10 }
- 11
- 12 void Darw_Point(int x,int y)
- 13 {
- 14 Set_LCD_reg();
- 15 Set_LCD_gram();
- 16 }
- 17
- 18 void Set_LCD_reg()
- 19 {
- 20 /* 写入寄存器 */
- 21 }
- 22
- 23 void Set_LCD_gram()
- 24 {
- 25 /* 写入显存 */
- 26 }
复制代码
这是我随便写的一个函数,我们差不多都是这样子调用一个写点的函数把,通过计算可得,假设我们要绘制一个100*100的矩阵,
那么就要调用10000次
Darw_Point函数,10000次Set_LCD_reg函数,10000Set_LCD_gram()函数,而每次调用一个函数的时候,要包括如下过程:
1.把要传递的参数压入堆栈中
2.把上一层程序的返回地址压入堆栈中
3.对程序的运行指针进行偏移操作,指向调用程序的入口地址
4.执行程序时候如果有需要要进行堆栈保护
5.执行程序完毕,堆栈要进行释放,还原给调用它的函数使用
6.弹出返回地址
7.返回,并继续执行上层程序.
而真正的,我们执行程序的时候,嵌套了那么多层的调用,执行的只有一个寄存器赋值,所以系统更多时候是在疯狂的进行压栈和出栈活动,
如果我们把这些时间都去掉,
效率就是呈现几何倍数增长,这种是毋庸置疑的.
出了通过对函数进行拆解,算法和硬件的结合也是非常重要的,像你画个矩形,一个通过点来填充,通过直线来填充,甚至,
为什么你没有想到通过矩阵来填充呢?有时候想法
就是这样,这在很多IC上是可以很轻松的实现的,前提是你得通读datasheet.
具体的我通过函数来为大家进行一一讲解:
这是优化后的填充点函数:
- /*********************************************************************已经最优化
- *
- * LCD_L0_SetPixelIndex
- *
- * Purpose:
- * Writes 1 pixel into the display.
- */
- void LCD_L0_SetPixelIndex(int x, int y, int ColorIndex)
- {
- /* 填充x轴坐标和y轴坐标 */
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0200;
- *(__IO uint16_t *)(Bank4_LCD_D) = y;
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0201;
- *(__IO uint16_t *)(Bank4_LCD_D) = 399 - x;
- /* 写显存前准备 */
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0202;
- /* 写入数据 */
- *(__IO uint16_t *)(Bank4_LCD_D) = ColorIndex;
- }
复制代码
与优化前的进行对比:这里的的数据全部改成对地址进行操作(BANk4_LCD_D和C都是被映射了的内存地址).模拟IO口可能会有差别
大家在进行写函数的时候,对管脚进行配置都是直接调用库函数的setbit或者resetbit来进行的,我们可以直接查询库函数,对寄存器进行操作:
库函数大家可以字节查询源代码,最后修改后demo代码如下:
- #ifdef HARDWARE_PLATFORM_ALI
- //0~15 作为数据线
- #define LCD_DATA_BUS GPIO_Pin_All
- //片选信号
- // #define LCD_CS_LOW GPIO_ResetBits(GPIOC, GPIO_Pin_9)
- // #define LCD_CS_HIGH GPIO_SetBits(GPIOC, GPIO_Pin_9)
- // Ver.HXH
- #define LCD_CS_LOW GPIOC->BRR = GPIO_Pin_9;
- #define LCD_CS_HIGH GPIOC->BSRR = GPIO_Pin_9;
- //数据/命令
- // #define LCD_RS_LOW GPIO_ResetBits(GPIOC, GPIO_Pin_8)
- // #define LCD_RS_HIGH GPIO_SetBits(GPIOC, GPIO_Pin_8)
- //Ver.HXH
- #define LCD_RS_LOW GPIOC->BRR = GPIO_Pin_8;
- #define LCD_RS_HIGH GPIOC->BSRR = GPIO_Pin_8;
- //End.HXH
- //写数据
- // #define LCD_WR_LOW GPIO_ResetBits(GPIOC, GPIO_Pin_7)
- // #define LCD_WR_HIGH GPIO_SetBits(GPIOC, GPIO_Pin_7)
- //Ver.HXH
- #define LCD_WR_LOW GPIOC->BRR = GPIO_Pin_7;
- #define LCD_WR_HIGH GPIOC->BSRR = GPIO_Pin_7;
- //End.HXH
- //读数据
- // #define LCD_RD_LOW GPIO_ResetBits(GPIOC, GPIO_Pin_6)
- // #define LCD_RD_HIGH GPIO_SetBits(GPIOC, GPIO_Pin_6)
- //Ver.HXH
- #define LCD_RD_LOW GPIOC->BRR = GPIO_Pin_6;
- #define LCD_RD_HIGH GPIOC->BSRR = GPIO_Pin_6;
- //End.HXH
- //PB0~15,作为数据线
- #define DATAOUT(x) GPIOB->ODR=x; //数据输出
- #define DATAIN GPIOB->IDR; //数据输入
- #endif
复制代码
把对管脚置高置底进行的操作完全跟改成寄存器操作,当然,你也可以改成对寄存器指针进行操作,不过效率是一样的,因为define的效果是复制,所以,通过观察源代码:
- 00127 #define GPIO_Pin_0 ((uint16_t)0x0001)
- 00128 #define GPIO_Pin_1 ((uint16_t)0x0002)
- 00129 #define GPIO_Pin_2 ((uint16_t)0x0004)
- 00130 #define GPIO_Pin_3 ((uint16_t)0x0008)
- 00131 #define GPIO_Pin_4 ((uint16_t)0x0010)
复制代码
所以,当程序在编译的时候,也是把地址进行简单的拷贝,所以这部分功夫是可以完全剩下来的.
接下来是关于填充矩阵的函数操作:
- /*********************************************************************
- *
- * LCD_L0_FillRect
- */
- void LCD_L0_FillRect(int x0, int y0, int x1, int y1)
- {
- /* 最后修改.2011.7.23 */
- for (; y0 <= y1; y0++)
- {
- int x;
- /* 填充x轴坐标和y轴坐标 */
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0200;
- *(__IO uint16_t *)(Bank4_LCD_D) = y0;
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0201;
- *(__IO uint16_t *)(Bank4_LCD_D) = 399 - x0;
- /* 写显存前 准备 */
- *(__IO uint16_t *)(Bank4_LCD_C) = 0x0202;
- /* 开始写入显存 */
- x=x0;
- for (;x0 <= x1; x0++)
- {
- *(__IO uint16_t *)(Bank4_LCD_D) = LCD_COLORINDEX;
- }
- x0=x;
- }
- // /* 最后修改.2011.7.26 */
- // u32 n;
- // /* 设定窗口 */
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0210;
- // *(__IO uint16_t *)(Bank4_LCD_D) = y0;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0211;
- // *(__IO uint16_t *)(Bank4_LCD_D) = y1;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0212;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 399-x1;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0213;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 399-x0;
- // /* 设定开始位置 */
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0200;
- // *(__IO uint16_t *)(Bank4_LCD_D) = y0;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0201;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 399-x0;
- // /* 进行填充 */
- // n = (u32)(y1-y0+1)*(x1-x0+1);
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0202;
- // while (n--)
- // {
- // *(__IO uint16_t *)(Bank4_LCD_D) = LCD_COLORINDEX;
- // }
- // /* 恢复窗口 */
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0210;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 0x0000;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0211;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 0x00EF;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0212;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 0x0000;
- // *(__IO uint16_t *)(Bank4_LCD_C) = 0x0213;
- // *(__IO uint16_t *)(Bank4_LCD_D) = 0x018f;
- }
复制代码
这里有关于两种填充方式,都是避开函数操作,被注释掉的是对窗口进行操作的,而没被注释掉的是对线条进行填充.
对线条进行操作的相信大家应该非常了解了,这里详细解释下对窗口进行操作的一些细节:
窗口:也就是可以进行填充的区域,液晶驱动里面,每个物理像素对应的坐标已经是固定的,但是窗口可以不固定,窗口就是可以进行填充的区域,你如果要在窗口外面
进行填充,是无法进行的,同样的,当你填充到窗口边缘的时候,会自动跳转到下一行进行填充,只要你设定的点正确,那么整个你设定的窗口区域都会被填充完毕,这段期间
你要做的知识单纯的填充数据,不需要进行设定点的操作,也不需要换行,这样子屏幕填充矩阵操作看起来效果就不会有刷屏的感觉了.
填充行:对行进行填充,只需要在换行的时候进行坐标切换,我用整个函数,慢了30万个点每秒把.
在优化的时候,我只是抛砖引玉的给大家介绍下怎么用什么样的思路进行优化,细节性的东西还是要大家好好去琢磨的.
初稿到这边就差不多结束了,后面会陆续补充,只要大家想知道都可以直接进行联系.