8962开发板学习笔记2_玩转OLED_2 一种菜单的实现方法
[复制链接]
沉睡了一个多月,终于要浮出水面来透透气了,期间也开始过一些比较大的项目,但都没能进行到最后,后来还是回到这个OLED上来继续转它。 这个菜单的方案是很早就在构思,不过一直没能实现,大学毕业后做了快三年的硬件,做的最多的就是电源,基本上跟编程不沾边,C语言都快还给老师了。这个程序是我对照着书本写出来的(主要是回忆语法),也有些地方是从别处粘贴过来的,也是粘贴语法,自己修改变量。所以可能会有些不规范,还请大家见谅。呵呵,这也应该是我以前总是分析代码的原因。看着能明白,但是要是自己写就想不起来了。 好了不多说废话了,中断返回,切入主程序。 这个方案的核心就是存放菜单数据的结构体,其中包含菜单项的数据,所有对菜单的操作都是用该结构体内的数据来定位的。该结构体如下: typedef struct _MenuType{ unsigned char Befo_Menu; unsigned char Next_Menu; unsigned char Prev_Menu; unsigned char Child_Menu; char *Menu_Text; signed char CU_Line; void (*Menufuction)(); } MenuType; 该结构体内变量的作用应该比较容易看出来的吧,采用的是英文单词,较长的单词取前四个字母。呵呵,十年前从FoxBase里学来的这招。多个单词的取每个单词的头字母大写。 Befo_Menu,前一菜单项的索引。该菜单项是存放在一个结构体数组里面的,最多可以有255项,当然,定义为其他数据类型可以有更多的项。本来这里是想用指针来着,运行时动态分配内存,就可以在运行时根据情况增加和减少菜单项了。不过后来考虑到微控制器的资源,还是选择了这个比较简单的。 Next_Menu,下一个菜单项的索引;Prev_Menu,上级菜单项的索引;Child_Menu,子菜单项的索引。 Menu_Text,菜单上显示的文字。保存在一个字符串数组里。本来想采用指针数组,这里来保存指针索引的,后来在网上看到了一个通用菜单生成器的软件,可以生成通用菜单结构数据。所以就偷了个懒。那个通用菜单跟我这个菜单数据结构定义差不多,应该是殊途同归吧。当然,人家已经编写成了通用函数了,我这个距离通用还有一段距离。 CU_Line,保存当前菜单项在屏幕中的位置。是为了从下级菜单返回时能够还原原来屏幕内容而不是从第一项开始显示。为了这个功能付出的代价是非常大的,这样就决定了在运行时会修改CU_Line的值,则该菜单数组必须保存在内存中而不是只Flash中。内存的开销会增加不少。 Menufuction,该菜单对应的功能函数,这才是这个菜单中最有用的数据呢,没有它,这个菜单只是个花瓶。 菜单显示算法: 程序中有一个Menu_Index变量,指向了当前显示突出显示的菜单项在数组中的索引。根据这个值和CU_Line值来开始显示菜单。 程序刚开始运行时,光标(即反相显示区域)位于数组中第一个菜单项的位置(注意,还有第0个呢,那是个永不显示的菜单,不过可以用来做菜单的Title,在每个菜单的上方显示该菜单的隶属菜单项。 此时,所有菜单项的CU_line都为0,索引程序要为第一次出现的菜单项编制CU_line。从第一个菜单项开始编制,一直到该级菜单结束或者屏幕容纳的最后菜单项。不能在屏幕中显示的菜单项CU_line一律为0。 编制好之后开始送显示数据。从Menu_Index开始轮询所有菜单项的CU_Line,直到最后一项,即该菜单项的Next_Menu为Menu_index。所有CU_line不为0的菜单项都将送到第CU_line行来显示,为0的忽略。 当光标移动到屏幕外的时候,即当前的CU_Line为0或者最大CU_Line加1时会重新安排菜单显示位置。按键事件中除了改变Menu_index的值外还会改变CU_line的值(加1或者减1),从而造成显示越界。当显示程序发现显示越界后会重新整理显示数据,从而实现菜单的滚动。 菜单显示程序如下,关于屏幕刷新还有待考虑,目前屏幕刷新太频繁了,明显有闪烁感 - :
void Menu_Display() { signed char i,j; RIT128x96x4Clear(); Menu_Write_Text(Menu_Unit[Menu_Unit[Menu_Index].Prev_Menu].Menu_Text,0,1); if (Menu_Unit[Menu_Index].CU_Line==9) { if (Menu_Index>Menu_Unit[Menu_Index].Befo_Menu) { for (i=9,j=Menu_Unit[Menu_Index].Next_Menu;i--;) { Menu_Unit[j].CU_Line=i; j=Menu_Unit[j].Befo_Menu; } } else { for (i=1,j=Menu_Unit[Menu_Index].Next_Menu;i<=8;i++) { Menu_Unit[j].CU_Line=i; j=Menu_Unit[j].Next_Menu; } for (;j!=Menu_Index;) { Menu_Unit[j].CU_Line=0; j=Menu_Unit[j].Next_Menu; } } } else if (Menu_Unit[Menu_Index].CU_Line==0) { if(Menu_Index<Menu_Unit[Menu_Index].Next_Menu) { for (i=1,j=Menu_Index;i<=8;i++) { Menu_Unit[j].CU_Line=i; j=Menu_Unit[j].Next_Menu; } Menu_Unit[j].CU_Line=0; } else { for(i=9,j=Menu_Index;--i;) { Menu_Unit[j].CU_Line=i; j=Menu_Unit[j].Befo_Menu; } for(;j!=Menu_Index;) { Menu_Unit[j].CU_Line=0; j=Menu_Unit[j].Befo_Menu; } } } Menu_Write_Text(Menu_Unit[Menu_Index].Menu_Text,Menu_Unit[Menu_Index].CU_Line,0); for (i=Menu_Unit[Menu_Index].Next_Menu;Menu_Unit [i ] .Befo_Menu!=Menu_Unit[Menu_Index].Befo_Menu;) { if (Menu_Unit[i ].CU_Line != 0) { Menu_Write_Text(Menu_Unit[i ].Menu_Text,Menu_Unit[i].CU_Line,1); } i=Menu_Unit[i].Next_Menu; }
P_Function=P_Next_Function; P_Next_Function=0; }
void Menu_Write_Text(char *Menu_Text,unsigned char Menu_Line,unsigned char Disp_Mode) { if(Disp_Mode) RIT128x96x4StringDraw(Menu_Text,20,(Menu_Line+1)*8,15); else RIT128x96x4StringDraw_Inverse(Menu_Text,20,(Menu_Line+1)*8,15); }
复制代码 该程序中本来有注释的,但是在后来调试修改过程中,很多地方已经改变了意思但是没有同步修改注释,为了不给大家造成误导,我先将注释删除,稍后上传整理后的工程包。 函数Menu_Write_Text中有一个RIT128x96x4StringDraw_Inverse函数这是我修改的一个用来反相显示文字的函数。根据RIT128x96x4StringDraw来修改的,只增加了一个语句(红色字体部分)。
- void
RIT128x96x4StringDraw_Inverse(char *pcStr, unsigned long ulX, unsigned long ulY, unsigned char ucLevel) { unsigned long ulIdx1, ulIdx2; unsigned char ucTemp;
// // Check the arguments. // ASSERT(ulX < 128); ASSERT((ulX & 1) == 0); ASSERT(ulY < 96); ASSERT(ucLevel < 16);
// // Setup a window starting at the specified column and row, ending // at the right edge of the display and 8 rows down (single character row). // g_pucBuffer[0] = 0x15; g_pucBuffer[1] = ulX / 2; g_pucBuffer[2] = 63; RITWriteCommand(g_pucBuffer, 3); g_pucBuffer[0] = 0x75; g_pucBuffer[1] = ulY; g_pucBuffer[2] = ulY + 7; RITWriteCommand(g_pucBuffer, 3); RITWriteCommand(g_pucRIT128x96x4VerticalInc, sizeof(g_pucRIT128x96x4VerticalInc));
// // Loop while there are more characters in the string. // while(*pcStr != 0) { // // Get a working copy of the current character and convert to an // index into the character bit-map array. // ucTemp = *pcStr++ & 0x7f; if(ucTemp < ' ') { ucTemp = 0; } else { ucTemp -= ' '; }
// // Build and display the character buffer. // for(ulIdx1 = 0; ulIdx1 < 6; ulIdx1 += 2) { // // Convert two columns of 1-bit font data into a single data // byte column of 4-bit font data. // for(ulIdx2 = 0; ulIdx2 < 8; ulIdx2++) { g_pucBuffer[ulIdx2] = 0; if(g_pucFont[ucTemp][ulIdx1] & (1 << ulIdx2)) { g_pucBuffer[ulIdx2] = (ucLevel << 4) & 0xf0; } if((ulIdx1 < 4) && (g_pucFont[ucTemp][ulIdx1 + 1] & (1 << ulIdx2))) { g_pucBuffer[ulIdx2] |= (ucLevel << 0) & 0x0f; } g_pucBuffer[ulIdx2]=~g_pucBuffer[ulIdx2]; }
// // Send this byte column to the display. // RITWriteData(g_pucBuffer, 8); ulX += 2;
// // Return if the right side of the display has been reached. // if(ulX == 128) { return; } } } }
复制代码好了,菜单能够显示了,下面就要让它动起来了。这样就需要进行按键处理了。对于按键处理,我比较崇尚中断处理,这样就不会影响正在运行的程序了,我比较喜欢一种我自创的“全中断键盘”。即所有键盘处理过程全部由中断来完成。 该“全中断键盘”需要至少一个外部中断和一个定时器,在这里我使用的是Systick。实现方法是:当检测到GPIOE中断(8962开发板上方向键连接的IO,采用下降沿触发)后,关闭GPIOE中断,但不清除中断标志,同时使能Systick定时器,然后退出中断处理程序。等Systick中断到来后,在Systick中断中关闭Systick(注意不是关闭Systick中断),读取GPIOE中断标志判断按键,读取键值并处理后(也可以只读取键值,处理交给中断返回后的其他函数),清除GPIOE中断标志并使能GPIOE中断。这两个中断函数如下:
- void IntGPIOe(void)
{ IntDisable(INT_GPIOE); SysTickEnable(); }
void IntSysTick(void) { long My_IntStatus; SysTickDisable(); My_IntStatus=GPIOPinIntStatus(GPIO_PORTE_BASE,true); My_IntStatus&=0x0000000f; // GPIOPinWrite(GPIO_PORTF_BASE,0x01,0x01); switch(My_IntStatus) { case 1: Menu_Index=Menu_Unit[Menu_Index].Befo_Menu; if (Menu_Unit[Menu_Index].CU_Line==0) Menu_Unit[Menu_Index].CU_Line=Menu_Unit[Menu_Unit[Menu_Index].Next_Menu].CU_Line - 1; if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display; break; case 2: Menu_Index=Menu_Unit[Menu_Index].Next_Menu; if (Menu_Unit[Menu_Index].CU_Line==0) Menu_Unit[Menu_Index].CU_Line=Menu_Unit[Menu_Unit[Menu_Index].Next_Menu].CU_Line + 1; if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display; break; case 4: if (Menu_Unit[Menu_Index].Prev_Menu !=0) {Menu_Index=Menu_Unit[Menu_Index].Prev_Menu; if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;} break; case 8: if (Menu_Unit[Menu_Index].Child_Menu !=0) {Menu_Index=Menu_Unit[Menu_Index].Child_Menu; if (P_Function==0) P_Function=Menu_Display; else P_Next_Function=Menu_Display;} break; default :break; } GPIOPinIntClear(GPIO_PORTE_BASE,0x0f); IntEnable(INT_GPIOE); }
复制代码中断程序和主程序如何连接,就要靠上面函数中的P_Function和P_Next_Function两个变量了。这两个变量是指针型变量,指向当前需要在主程序中执行的程序。在主程序中有一个无限循环。 while(1) {if (P_Function !=0) P_Function(); } 在这里,如果P_Function不为0,则表示中断发生时正在执行其它函数,则要将需要执行的函数指针写入下一个需要执行的函数指针位置,即P_Next_Function,等正在执行的函数执行完毕后,再由其将P_Next_Function中的值写入P_Function并清零P_Next_Function,安排下一个在主程序中执行的函数。这样在每一个函数中都要加入处理这两个函数指针的内容,没有等待执行的任务的时候,则给其赋0。
好了,关于这个菜单就先说到这里吧,不过这个菜单还有些问题没有解决掉,主要就是采用Systick消抖方面,我将Systick定时值增加到100mS还是会出现连击现象。目前关于Systick的资料不多,不知哪位对这方面比较了解还请指点一下。
|