Ndless SDK 系列教程——LCD操作
(返回目录)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)里做的演示程序吗?http://www.cncalc.org/forum.php?mod=viewthread&tid=7572我们来在nspire上做一下那个BALL吧。(来源于WSC&FVM V1.2Sample,有改动)#include "os.h"
#define BALL_RADIUS10
#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;
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;
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_RADIUS10
#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函数的错误。
写的略仓促,稍微有点乱,麻烦大家了……
如果当中出现错误,也希望大家能指出。有任何意见和建议,可以在下面回帖。
支持LZ!! 本帖最后由 Zentauit 于 2014-3-3 23:01 编辑
来个SPRING.c和Wudy的解释器效果差不多,不过用了触摸板效果比较好。
暂时不兼容ClickPad等无触摸板机型。#include <os.h>
#include "graph.h"
#include "touchpad.h"
#define X_MAX 320
#define Y_MAX 240
#define BALL_RADIUS 10
#define LOST -0.95
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 = 319;
yMax = y0 + r0; if( yMax >= 240 ) yMax = 239;
for( ; x <= xMax; ++x )
{
for( y = yInit; y <= yMax; ++y )
{
if( abs( (x-x0)*(x-x0) + (y-y0)*(y-y0 ) ) <r0*r0 )
DrawPoint_VRAM(VRAM,x, y, color);
}
}
}
int main(void) {
char *VRAM = init_VRAM();
AllClr_VRAM(VRAM);
lcd_ingray();
PutDisp_DDVRAM(VRAM);
initTP();
int mx, my, bx, by; // m = mouse, b = ball
float vx, vy;
char msg;
mx = bx = X_MAX/2; my = by = 30;
vx = vy = 0;
int last_x=mx,last_y=my,curr_x=mx,curr_y=my,touching=0;
while( !isKeyPressed( KEY_NSPIRE_ESC ) )
{
readTP();
if (isTPTouched())
{
if(!touching)
{
curr_x = ((tpreport->x)*320)/tpinfo->width;
curr_y = 240-(((tpreport->y)*240)/tpinfo->height);
touching=1;
}
mx=last_x+((tpreport->x)*320)/tpinfo->width-curr_x;
my=last_y+240-(((tpreport->y)*240)/tpinfo->height)-curr_y;
}
else if(touching)
{
last_x = mx;//((tpreport->x)*320)/tpinfo->width;
last_y = my;//240-((tpreport->x)*320)/tpinfo->width;
touching=0;
}
float fx = mx - bx;
float fy = my - by + 60;
vx += (fx / 40);
vy += (fy / 40);
vx *= 0.98; vy *= 0.98;
bx += vx; by += vy;
AllClr_VRAM(VRAM);
sprintf(msg,"Ball Information:X %4d|Y %4d|VX %4d|VY %4d|",bx,by,(int)vx,(int)vy);
DrawMiniString_VRAM(VRAM,0,0,msg,BLACK,WHITE);
fillcircle( VRAM , bx, by, BALL_RADIUS ,BLACK );
DrawLine_VRAM( VRAM, mx-1, my, mx+1, my, BLACK );
DrawLine_VRAM( VRAM, mx, my-1, mx, my+1, BLACK );
DrawLine_VRAM( VRAM, mx, my, bx, by, BLACK );
PutDisp_DDVRAM( VRAM );
sleep(30);
}
Close_VRAM(VRAM);
endTP();
return 0;
}
页:
[1]