(返回目录)
Ndless SDK 系列教程——LCD操作
本节内容:
自己操作nspire的显示屏!
在以前的帖子中,我们已经看到了如何使用nio(nspireio2.h)库进行字符的输出,不过其能力有限,也不能输出中文。要想拥有更高的自由度,在屏幕上显示更加复杂的图形,我们就要自己实现一些LCD函数。 本节大多数例子都是基于黑白机的,大家稍作变通就可以用上彩屏机。
Ndless SDK 为我们提供了一个宏:SCREEN_BASE_ADDRESS。这就是nspire当前的显存首地址。通过指针操作,我们就可以对nspire的显存进行修改,比如如下代码(来自NdlessSDK _Samples文件夹,有改动,可在附件中找到): - #include <os.h>
- int main(void) {
- if (!has_colors) return 0;
- lcd_incolor();
- volatile unsigned char *scr_base = SCREEN_BASE_ADDRESS;
- volatile unsigned char *ptr;
- unsigned scr_size = SCREEN_BYTES_SIZE; // SCREEN_BYTES_SIZE为显存大小
- for (ptr = scr_base; ptr < scr_base + scr_size / 3; ptr += 2)
- *(volatile unsigned short*)ptr = 0b1111100000000000;
- for (; ptr < scr_base + scr_size * 2 / 3; ptr += 2)
- *(volatile unsigned short*)ptr = 0b0000011111100000;
- for (; ptr < scr_base + scr_size; ptr += 2)
- *(volatile unsigned short*)ptr = 0b0000000000011111;
- wait_key_pressed();
- return 0;
- }
复制代码CX/CM运行编译后的程序,效果如图:
我们来好好理解一下这段代码: 首先是解释一下16位彩屏的颜色问题。16位彩色一共可以显示2^16(65536)种颜色,因此可以用unsigned short来表示一个像素的颜色。关于颜色的组成,可以这样看:0bRRRRRGGGGGGBBBBB。即用二进制表示时,前5位为红色的值,中间6位为绿色(人眼对绿色更敏感),剩下的5位则是蓝色。实际上我们都更加习惯于使用使用0~255来表示一个像素里的一个颜色,所以我们可以这样写一个宏: - #define RGB16(r,g,b) (((unsigned short)(r>>3))<<11 | ((unsigned short)(g>>2))<<5 | ((unsigned short)(b>>3)))
复制代码这样就可以使用RGB16(255,0,0)来表示红色。
每次都用SCREEN_BASE_ADDRESS会觉得很麻烦,于是我们可以简单的封装一个函数: - void setpixel( int x , int y , unsigned short color )
- {
- volatile unsigned char * ptr = SCREEN_BASE_ADDRESS;
- ptr += y*320*sizeof(short) + x*sizeof(short);
- *(volatile unsigned short*)ptr = color;
- }
复制代码现在我们就可以利用这个setpixel来操作显示屏了!比如画一条水平蓝线。 - #include <os.h>
- #define RGB16(r,g,b) (((unsigned short)(r>>3))<<11 | ((unsigned short)(g>>2))<<5 | ((unsigned short)(b>>3)))
- int main(void)
- {
- if (!has_colors) return 0;//防止黑白机运行此程序
- lcd_incolor();
- int i;
- for( i=0 ; i<320 ; i++ )
- {
- setpixel( i , 100 , RGB16(0,0,255) );
- }
- wait_key_pressed();
- return 0;
- }
复制代码对于黑白机,可以用unsigned char来表示两个像素,这里我们给出描点函数,原理可以自己理解。 - void setpixel(int x , int y , unsigned char color )
- {
- if( x < 0 || x >= 320 || y < 0 || y >= 240 )
- return ;
- volatile unsigned char * p = SCREEN_BASE_ADDRESS + (x >> 1) + ( y << 7 ) + (y << 5 );
- *p = ( x & 1) ? ((*p & 0xf0 ) | color ) : (( *p & 0x0f ) | ( color << 4 ));
- }
复制代码在使用这些函数时,务必要注意小心访问越界,否则会有不可意料的后果(通常情况是重启)。 为了规避风险,可以在描点之前加上检查(如上一段代码)。
下面提供几个用于黑白机的显示函数,部分改变后就可以用上彩屏机。 - void allclr ()
- { memset(SCREEN_BASE_ADDRESS,0xff,SCREEN_BYTES_SIZE);}
- void allfill (unsigned char color)
- {
- //绝对不适用于彩屏机,彩屏机必须逐点描颜色!!
- unsigned char c = (color<<4)&(color);
- memset(SCREEN_BASE_ADDRESS,c,SCREEN_BYTES_SIZE);
- }
复制代码下面再来个画线函数,里面有注释,就自己看吧: - void line (int x1 , int y1 , int x2 , int y2 , unsigned char color)
- {
- float i ;
- if( x1 == x2 ) //此时,斜率不存在
- {
- if( y1 > y2 ) { i = y1 ; y1 = y2 ; y2 = i ;}
- for( ; y1 <= y2 ; y1 += 1)
- setpixel (x1 , y1 , color );
- return ;
- }
- float k , b ;
- k = ((float)y2 - (float)y1)/((float)x2 - (float)x1); //计算斜率
- b = (float)y1 - k * (float)x1 ; //计算截距
- if( _abs(k) <= 1 ) //当斜率小于1时,直线比较平缓,以x方向逐个画点
- {
- if( x1 > x2 ) //交换x,y的值,如果需要的话
- {
- i = y1 ; y1 = y2 ; y2 = i ;
- i = x1 ; x1 = x2 ; x2 = i ;
- }
- for( ; x1 <= x2 ; x1 +=1 )
- setpixel ( x1 , k * x1 + b , color);
- return ;
- }
- else //当斜率大于1时,直线比较陡峭,以y方向逐个画点
- {
- if( y1 > y2 ) //交换x,y的值,如果需要的话
- {
- i = y1 ; y1 = y2 ; y2 = i ;
- i = x1 ; x1 = x2 ; x2 = i ;
- }
- for( ; y1 <= y2 ; y1 +=1 )
- setpixel (( y1 - b ) / k , y1 , color);
- return ;
- }
- }
复制代码 再有,画一个实心圆也是很简单的,可以用点到圆心的距离小于半径得到。- void fillcircle(int x0,int y0,int r0,unsigned char color)
- {
- int x, y;
- int xMax, yMax, yInit;
-
- x = x0 -r0; if( x < 0 ) x = 0;
- yInit = y0 - r0; if( yInit < 0 ) yInit = 0;
- xMax = x0 + r0; if( xMax > 320) xMax = 320;
- yMax = y0 + r0; if( yMax > 240 ) yMax = 240;
- for( ; x <= xMax; ++x )
- {
- for( y = yInit; y <= yMax; ++y )
- {
- if( abs( (x-x0)*(x-x0) + (y-y0)*(y-y0 ) ) < r0*r0 )
- setpixel(x, y, color);
- }
- }
- }(CPASS)
复制代码好了,有了以上这些函数,我们可以来点有意思的!
还记得Wudy在9860GII的C解释器(WSC&FVM)里做的演示程序吗? 我们来在nspire上做一下那个BALL吧。(来源于WSC&FVM V1.2Sample,有改动) - #include "os.h"
- #define BALL_RADIUS 10
- #define X_MAX 320
- #define Y_MAX 240
- #define LOST -0.95
- float x, y, vx, vy, ax, ay;
- void update();
- void fillcircle(int x0,int y0,int r0,unsigned char color);
- void allclr ();
- void setpixel(int x , int y , unsigned char color );
- int main()
- {
- lcd_ingray();
- x = 10; y = 10;
- vx = 2; vy = 0;
- ax = 0; ay = 0.2;
- while( !isKeyPressed(KEY_NSPIRE_ESC) )
- {
- allclr();
- fillcircle(x, y, BALL_RADIUS ,0 );
- sleep(30);
- if( isKeyPressed(KEY_NSPIRE_UP ) )
- vy -= 0.5;
- if( isKeyPressed(KEY_NSPIRE_DOWN ) )
- vy += 0.5;
- if( isKeyPressed(KEY_NSPIRE_LEFT ) )
- vx -= 0.5;
- if( isKeyPressed(KEY_NSPIRE_RIGHT ) )
- vx += 0.5;
- update();
- }
- return 0;
- }
- void update()
- {
- vx += ax; vy += ay;
- x += vx*0.75; y += vy*0.75;
- if( x > X_MAX - BALL_RADIUS + 1 )
- {
- x = X_MAX - BALL_RADIUS; vx *= LOST;
- }
- else if( x < BALL_RADIUS - 1 )
- {
- x = BALL_RADIUS; vx *= LOST;
- }
- if( y > Y_MAX - BALL_RADIUS + 1 )
- {
- y = Y_MAX - BALL_RADIUS; vy *= LOST;
- }
- else if( y < BALL_RADIUS - 1 )
- {
- y = BALL_RADIUS; vy *= LOST;
- }
- }
- void fillcircle(int x0,int y0,int r0,unsigned char color)
- {
- int x, y;
- int xMax, yMax, yInit;
-
- x = x0 -r0; if( x < 0 ) x = 0;
- yInit = y0 - r0; if( yInit < 0 ) yInit = 0;
- xMax = x0 + r0; if( xMax > 320) xMax = 320;
- yMax = y0 + r0; if( yMax > 240 ) yMax = 240;
- for( ; x <= xMax; ++x )
- {
- for( y = yInit; y <= yMax; ++y )
- {
- if( abs( (x-x0)*(x-x0) + (y-y0)*(y-y0 ) ) < r0*r0 )
- setpixel(x, y, color);
- }
- }
- }
- void allclr ()
- { memset(SCREEN_BASE_ADDRESS,0xff,SCREEN_BYTES_SIZE);}
- void setpixel(int x , int y , unsigned char color )
- {
- if( x < 0 || x >= 320 || y < 0 || y >= 240 )
- return ;
- volatile unsigned char * p = SCREEN_BASE_ADDRESS + (x >> 1) + ( y << 7 ) + (y << 5 );
- *p = ( x & 1) ? ((*p & 0xf0 ) | color ) : (( *p & 0x0f ) | ( color << 4 ));
- }
复制代码经模拟器检验运行良好(球有闪烁现象),黑白机实际运行时存在严重BUG(显示位置偏移),这个问题到最后在解决。 大家也可以拿WSC&FVM里的SPRING.c做实验。
下面我们来研究下字符(串)的描绘。 在ndless程序的源代码中,几乎都存在着char.h或者类似的文件,里面有一个超大的二位数组,那些其实就是字符的图片。(char.h见本帖附件) 这些数组(图片)的结构类似于9860的图片数据,想做更多了解的可以找diameter的老帖:“9860 SDK 教程”
为了解码这些图片,我们编写以下函数: - void writegraph(int x, int y , int width,int height,char * pimage,char cl_fg,char cl_bg)
- {
- int i,j,k,pixel,rx=0,ry=0;
- unsigned char p;
- int iwidth = width/8 + (width % 8 ? 1:0);
- for (i=0;i<height;++i,pimage+=iwidth)
- {
- ry = y+i;
- if (ry>=240) return;
- else if (ry<0) continue;
- for (j=0;j<iwidth;++j)
- {
- p = pimage[j];
- for (k=0;k<8;++k)
- {
- rx = x+(8-k)+8*j;
- pixel = p % 2;
- p>>=1;
- if (pixel) setpixel (rx-1,ry,cl_fg);
- else setpixel (rx-1,ry,cl_bg);
- }
- }
- }
- }
- void DrawAsciiChar (int x , int y , char c , int cl_fg, int cl_bg)
- {
- writegraph ( x , y ,8 , 12 , (char *)charMap_ascii [(unsigned char)c] , cl_fg , cl_bg );
- }
复制代码搞定,现在就可以在屏幕的任意位置输出一个字符了。 要输出字符串,连续输出字符即可。 - void DrawAsciiString (int x , int y , char *string,int cl_fg,int cl_bg)
- {
- while(1)
- {
- DrawAsciiChar (x , y , *string , cl_fg , cl_bg );
- string ++; x += 8 ;
- if(*string == '\0' || x > 320 )return;
- }
- }
复制代码不同char.h的字符输出方式可能不一样,比如NIO库的字符输出方式就比较非常态。大家可以自己找NIO库的源代码来研究下。
英文字符输出的问题解决了,下面就是汉字的描绘了。 汉字很多,所以不能像ascii字符一样用数组就解决,所以我们外包一个文件,名为HZK16.tns。 先写两个函数,用来打开汉字库和关闭汉字库。 - FILE * open_hzk ()
- {
- return fopen("/documents/HZK16.tns","rb");
- }(CPASS)
- void close_hzk (FILE* file_hzk)
- {
- fclose(file_hzk);
- }
复制代码然后就是输出中文字符串。 - void PrintChStr(FILE *HZK , int x,int y,char *str,unsigned char cl_fg,unsigned char cl_bg)
- {
- unsigned char c1,c2,mat[32];
- while(*str)
- {
- if(x>=304)
- {
- x=0;y+=16;
- }
- c1=*str++;
- if( !(c1&0x80) )
- {
- DrawAsciiChar (x,y+2,c1,cl_fg,cl_bg);
- x+=8;
- continue;
- }
- c2=*str++;
- fseek(HZK,(94*(c1-0xa1)+(c2-0xa1))*32,SEEK_SET);
- fread(mat,32,1, HZK);
- writegraph (x,y,16,16,(char *)mat, cl_fg,cl_bg);
- x+=16;
- }
- }
复制代码我们来试试看 - int main(void)
- {
- lcd_ingray();
- FILE *hzk = open_hzk()
- if( hzk==NULL )
- return -1;
- PrintChStr(hzk,0,0,"世界,你好!",0x0,0xf);
- close_hzk(hzk);
- wait_key_pressed();
- return 0;
- }
复制代码中文描绘顺利解决,效果如图:
(貌似少了清空屏幕……)
值得一提的是,这种方法读取汉字库必须保证字符串是ANSI编码,而且据我所知部分日语和繁体中文(ANSI,如“電”)都会死,所以我写的nNovel暂不支持这些。 要想使得输出的汉字更好看,你可以找nNovel的源代码,里面有灰度字库的输出方式。 在教程文件整合包里的char.h,提供了3中不同的ascii字符:点阵大字符,点阵小字符,灰度大字符。
前面所提供的所有函数都是基于直接对显存的的操作,因此你会看到绘图的过程(虽然很短暂)。而实际上在复杂的绘图中,我们更加不希望别人看到这一过程。所以,我们可以在空闲的内存中分配一块,在这块内存里绘图,绘图完毕后用memcpy函数送到显存里去。这样还可以避免出现黑白机运行BALL时的显示位置偏移的BUG。 下面我们试试这个方法来处理ball.c。 - #include "os.h"
- #define BALL_RADIUS 10
- #define X_MAX 320
- #define Y_MAX 240
- #define LOST -0.95
- float x, y, vx, vy, ax, ay;
- void update();
- void fillcircle(char*VRAM,int x0,int y0,int r0,unsigned char color);
- void allclr (char*VRAM);
- void setpixel(char*VRAM,int x , int y , unsigned char color );
- char *init_VRAM()
- {
- return (char*)malloc(SCREEN_BYTES_SIZE);
- }
- void Close_VRAM(char *VRAM)
- {
- free(VRAM);
- }
- void PutDisp(char * VRAM)
- { memcpy(SCREEN_BASE_ADDRESS,VRAM,SCREEN_BYTES_SIZE);}
- int main()
- {
- lcd_ingray();
- char *VRAM=init_VRAM();
- x = 10; y = 10;
- vx = 2; vy = 0;
- ax = 0; ay = 0.2;
- while( !isKeyPressed(KEY_NSPIRE_ESC) )
- {
- allclr(VRAM);
- fillcircle(VRAM ,x, y, BALL_RADIUS ,0 );
- PutDisp(VRAM);
- sleep(20);
- if( isKeyPressed(KEY_NSPIRE_UP ) )
- vy -= 0.5;
- if( isKeyPressed(KEY_NSPIRE_DOWN ) )
- vy += 0.5;
- if( isKeyPressed(KEY_NSPIRE_LEFT ) )
- vx -= 0.5;
- if( isKeyPressed(KEY_NSPIRE_RIGHT ) )
- vx += 0.5;
- update();
- }
- Close_VRAM(VRAM);
- return 0;
- }
- void update()
- {
- vx += ax; vy += ay;
- x += vx*0.75; y += vy*0.75;
- if( x > X_MAX - BALL_RADIUS + 1 )
- {
- x = X_MAX - BALL_RADIUS; vx *= LOST;
- }
- else if( x < BALL_RADIUS - 1 )
- {
- x = BALL_RADIUS; vx *= LOST;
- }
- if( y > Y_MAX - BALL_RADIUS + 1 )
- {
- y = Y_MAX - BALL_RADIUS; vy *= LOST;
- }
- else if( y < BALL_RADIUS - 1 )
- {
- y = BALL_RADIUS; vy *= LOST;
- }
- }
- void fillcircle(char*VRAM ,int x0,int y0,int r0,unsigned char color)
- {
- int x, y;
- int xMax, yMax, yInit;
-
- x = x0 -r0; if( x < 0 ) x = 0;
- yInit = y0 - r0; if( yInit < 0 ) yInit = 0;
- xMax = x0 + r0; if( xMax > 320) xMax = 320;
- yMax = y0 + r0; if( yMax > 240 ) yMax = 240;
- for( ; x <= xMax; ++x )
- {
- for( y = yInit; y <= yMax; ++y )
- {
- if( abs( (x-x0)*(x-x0) + (y-y0)*(y-y0 ) ) < r0*r0 )
- setpixel(VRAM,x, y, color);
- }
- }
- }
- void allclr (char*VRAM)
- { memset(VRAM,0xff,SCREEN_BYTES_SIZE);}
- void setpixel(char*VRAM ,int x , int y , unsigned char color )
- {
- if( x < 0 || x >= 320 || y < 0 || y >= 240 )
- return ;
- volatile char * p = VRAM + (x >> 1) + ( y << 7 ) + (y << 5 );
- *p = ( x & 1) ? ((*p & 0xf0 ) | color ) : (( *p & 0x0f ) | ( color << 4 ));
- }
复制代码好了,在教程文件整合包中的mylib文件夹内,免费送上了一个我自己用的,黑白机的LCD库,都是基于上述的“虚拟显存”方案。所有文件没有版权问题,大家拿了随便用~ 还有,整合包里面的文件不要直接编译,否则会出现多个main函数的错误。
写的略仓促,稍微有点乱,麻烦大家了……
如果当中出现错误,也希望大家能指出。有任何意见和建议,可以在下面回帖。
|