单片机/MCU论坛
直播中

jf_1137202360

9年用户 1528经验值
擅长:嵌入式技术
私信 关注
[文章]

【HD-G2UL-EVM开发板体验】掌上游戏机之二 —— 显示部分(任意画点,艺术图片)

前言
   前面我们掌上游戏机之一完成了USB手柄输入设备按键的采集,现在我们来实现显示部分。由于开发板默认没有带LCD,所以曲线救国,我们通过网口使用RTP实时推流H264PC上显示,使用电脑作为显示器。
参见https://bbs.elecfans.com/jishu_2324285_1_1.html;https://bbs.elecfans.com/jishu_2 ... hu_2324514_1_1.html
这一篇我们来实现通过上述方式模拟的LCD的任意写点。

过程
   整体过程如下,画点实际就是写RGB显存, 需要刷新显示时将RGB显存转为YUV,然后通过x264编码为H264裸流,通过RTP推流到PC端,PC端通过VLC播放器显示。
所以需要三个模块,分别是
Dis显示模块,负责实现写点接口,RGBYUV接口
H264编码模块,负责将YUV缓存转为H264裸流
Rtp模块,负责将H264裸流实时推流到PC
分别对应以下源码
dis.c
引用: #include
#include
#include
#include
#include
#include
#include "x264.h"
#include "rtp.h"
#include "dis.h"

uint8_t s_rgb_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3];        /**< 显示RGB缓存    */
uint8_t s_yuv_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3/2];      /**< 显示I420缓存   */
uint8_t s_h264_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3*10];    /**< 显示I420缓存   */

uint8_t s_flush_u8 = 0;

static void rgb_to_yuv420p(uint8_t* yuv420p, uint8_t* rgb, int width, int height)
{
        if (yuv420p == NULL || rgb == NULL)
                return;
        int frameSize = width*height;
        int chromaSize = frameSize / 4;

        int yIndex = 0;
        int uIndex = frameSize;
        int vIndex = frameSize + chromaSize;

        int R, G, B, Y, U, V;
        for (int i = 0; i < height; i++)
        {
                for (int j = 0; j < width; j++)
                {
                        B = rgb[(i * width + j) * 3 + 0];
                        G = rgb[(i * width + j) * 3 + 1];
                        R = rgb[(i * width + j) * 3 + 2];

                        //RGB to YUV
                        Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
                        U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
                        V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

                        yuv420p[yIndex++] = (unsigned char)((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
                        if (i % 2 == 0 && j % 2 == 0)
                        {
                                yuv420p[uIndex++] = (unsigned char)((U < 0) ? 0 : ((U > 255) ? 255 : U));
                                yuv420p[vIndex++] = (unsigned char)((V < 0) ? 0 : ((V > 255) ? 255 : V));
                        }
                }
        }
}

static void* dis_llopback(void* arg)
{
    static int s_h264_len_i = -1;
        while(1)
        {
                if(s_flush_u8 != 0)
                {
                        s_flush_u8 = 0;
                        rgb_to_yuv420p(s_yuv_buffer_au8, s_rgb_buffer_au8, DIS_WIDTH, DIS_HEIGHT);
                        memset(s_h264_buffer_au8,0,sizeof(s_h264_buffer_au8));
                        s_h264_len_i = x264_yuv420p2h264(s_yuv_buffer_au8,s_h264_buffer_au8,sizeof(s_h264_buffer_au8),DIS_WIDTH,DIS_HEIGHT);
                        printf("flush frame len=%dn",s_h264_len_i);
                }
                if(s_h264_len_i > 0)
                {
                        uint8_t* tmp = malloc(s_h264_len_i+1);
                        memcpy(tmp+1,s_h264_buffer_au8,s_h264_len_i);  /**< H264 FU-A分包时会往前写一个字节,改写原始值,所以这里需要临时开辟空间用来发送  */
                        rtp_send_h264(tmp+1, s_h264_len_i);
                        free(tmp);
                        /* 帧率控制 */
                        usleep(100);
                }
        }
}

void dis_set_pixel(int x,int y, uint8_t r,uint8_t g,uint8_t b)
{
    s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 0] = b;  /**< r和b反 */
    s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 1] = g;
    s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 2] = r;
}

void dis_flush(void)
{
    s_flush_u8 = 1;
}

int dis_init(char* ip,int port)
{
    rtp_h264_init(ip,port);
        pthread_t id;
        /* 创建函数线程,并且指定函数线程要执行的函数 */
        pthread_create(&id,NULL,dis_llopback,0);
}
dis.h
引用: #ifndef DIS_H
#define DIS_H

#define DIS_WIDTH 1024
#define DIS_HEIGHT 1024

void dis_set_pixel(int x,int y, uint8_t r,uint8_t g,uint8_t b);
int dis_init(char* ip,int port);
void dis_flush(void);
#endif
rtp.c
引用: #include
#include
#include
#include
#include
#include
#include
#include

#define MAX_RTP_PKT_LENGTH (1400)
#define TS_INC (90000/25)

static RtpSession *session;
static uint32_t user_ts=0;

static void forward_h264frame(RtpSession * session, uint8_t * buffer, int len,uint32_t userts)
{
    unsigned char NALU = buffer[0];
    uint32_t valid_len = len;

    if (len <= MAX_RTP_PKT_LENGTH) {
        /*
            RTP Header + NALU Header + NALU Data; (不包括startcode)
            传入的buffer从NALU Header开始
            RTP Header由rtp_session_send_with_ts添加
        */
        rtp_session_send_with_ts(session, buffer,len,userts);
        //printf("send slice len=%drn",len);
    } else {
        /* FU-A *
         * FU indicator  FU header     DATA
         *                ^
         *               NALU Header   NALU Data
         */
        int packetnum = valid_len / (MAX_RTP_PKT_LENGTH-1);  /* FU-A分片要多加一个字节 */
        if (valid_len % (MAX_RTP_PKT_LENGTH-1) != 0) {
            packetnum += 1;
        }
        int i = 0;
        int pos = -1;
        while (i < packetnum) {
            if (i < packetnum - 1) {
                buffer[pos] = (NALU & 0x60) | 28;
                buffer[pos+1] = (NALU & 0x1f);
                if (0 == i) {
                    buffer[pos+1] |= 0x80;
                }
                rtp_session_send_with_ts(session, &buffer[pos],
                MAX_RTP_PKT_LENGTH, userts);
                                //printf("send slice offset=%d,len=%drn",pos,MAX_RTP_PKT_LENGTH);
            } else {
                int iSendLen = len - pos - 1;
                buffer[pos] = (NALU & 0x60) | 28;
                buffer[pos+1] = (NALU & 0x1f);
                buffer[pos+1] |= 0x40;
                rtp_session_send_with_ts(session, &buffer[pos],
                        iSendLen, userts);
                                //printf("send slice offset=%d,len=%drn",pos,iSendLen);
            }
            pos += MAX_RTP_PKT_LENGTH-2;  /*有效长度MAX_RTP_PKT_LENGTH-1 在往前预留一个字节存储FU indicator */
            ++i;
        }
    }
}

static uint32_t h264isslice(uint8_t* buffer)
{
    if((buffer[0]==0)&&(buffer[1]==0))
    {
        if(buffer[2]==1)
        {
            return 1;
        }
        else if(buffer[2]==0)
        {
            if(buffer[3]==1)
            {
                return 2;
            }
            else
            {
                return 0;
            }
        }
        else
        {
            return 0;
        }
    }
    else
    {
        return 0;
    }
}

static uint32_t h264getslicelen(uint8_t* buffer,uint32_t len,uint8_t* headlen)
{
    uint8_t type;
    uint32_t offset = 0;
    uint32_t isslice = 0;
    isslice = h264isslice(buffer);
    if(isslice==1)
    {
        offset=3;
                *headlen = 3;
    }
    else if(isslice==2)
    {
        offset=4;
                *headlen = 4;
    }
    else
    {
        return 0;
    }
    uint32_t i;
    for(i=offset;i     {
        if((buffer[i+0]==0)&&(buffer[i+1]==0))
        {
            if(buffer[i+2]==1)
            {
                return i;
            }
            else if(buffer[i+2]==0)
            {
                if(buffer[i+3]==1)
                {
                    return i;
                }
            }
        }
    }
    return i;
}

int rtp_h264_init(char* ip,int port)
{
        int i;
        char *ssrc;
        int clockslide=0;
        int jitter=0;
        ortp_init();
        ortp_scheduler_init();
        ortp_set_log_level_mask(ORTP_ERROR);
        session=rtp_session_new(RTP_SESSION_SENDONLY);       
        rtp_session_set_scheduling_mode(session,1);
        rtp_session_set_blocking_mode(session,1);
        rtp_session_set_connected_mode(session,TRUE);
        rtp_session_set_remote_addr(session,ip,port);
        rtp_session_set_payload_type(session,96);
        ssrc=getenv("SSRC");
        if (ssrc!=NULL)
    {
                printf("using SSRC=%i.n",atoi(ssrc));
                rtp_session_set_ssrc(session,atoi(ssrc));
        printf("rtp_session_set_ssrc %sn",ssrc);
        }
}

int rtp_h264_deinit(void)
{
    rtp_session_destroy(session);
        ortp_exit();
        ortp_global_stats_display();
}

int rtp_send_h264(uint8_t* src, uint32_t len)
{
        uint8_t* src_buff = src;
        uint32_t framelen = 0;
        uint32_t s_sendlen_u32 = 0;
        uint8_t headlen;
        while(1)
        {
        if((framelen = h264getslicelen(src_buff,len-s_sendlen_u32,&headlen)) == 0)
        {
            /* 无有效数据 */
            break;
        }
        forward_h264frame(session,src_buff+headlen,framelen-headlen,user_ts);
                user_ts+=framelen-headlen;
        src_buff += framelen;
        s_sendlen_u32+=framelen;
        if(s_sendlen_u32 >= len)
        {
            break;
        }
        }
        return s_sendlen_u32;
}
rtp.h
引用: #ifndef RTP_H
#define RTP_H

int rtp_h264_init(char* ip,int port);
int rtp_h264_deinit(void);
int rtp_send_h264(uint8_t* src, uint32_t len);

#endif
x264.c
引用: #include
#include
#include
#include
#include
#include

int x264_yuv420p2h264(uint8_t* src,uint8_t* dst, uint32_t dstlen,uint32_t width,uint32_t height)
{
    x264_param_t param;
    x264_picture_t pic;
    x264_picture_t pic_out;
    x264_t *h;
    x264_nal_t *nal;
    int i_frame = 0;
    int i_frame_size;
    int i_nal;
    int outlen = 0;
    /* Get default params for preset/tuning */
    if( x264_param_default_preset( ¶m, "medium", NULL ) < 0 )
    {
        printf("x264_param_default_preset errn");
        return -1;
    }
   
    /* Configure non-default params */
    param.i_bitdepth = 8;
    param.i_csp = X264_CSP_I420;
    param.i_width  = width;
    param.i_height = height;
    param.b_vfr_input = 0;
    param.b_repeat_headers = 1;
    param.b_annexb = 1;
    param.i_log_level = X264_LOG_ERROR;
   
    /* Apply profile restrictions. */
    if( x264_param_apply_profile( ¶m, "high" ) < 0 )
    {
        printf("x264_param_apply_profile errn");
        return -1;
    }


    if( x264_picture_alloc( &pic, param.i_csp, param.i_width, param.i_height ) < 0 )
    {
        printf("x264_picture_alloc errn");
        return -1;
    }

    h = x264_encoder_open( ¶m );
    if( !h )
    {
        x264_picture_clean( &pic );
        printf("x264_encoder_open errn");
        return -1;
    }

    int luma_size = width * height;
    int chroma_size = luma_size / 4;
    i_frame++;
    memcpy(pic.img.plane[0],src,luma_size);
    memcpy(pic.img.plane[1],src+luma_size,chroma_size);
    memcpy(pic.img.plane[2],src+luma_size+chroma_size,chroma_size);
    pic.i_pts = i_frame;
    i_frame_size = x264_encoder_encode( h, &nal, &i_nal, &pic, &pic_out );
    if( i_frame_size < 0 )
    {
        printf("x264_encoder_encode err %dn",i_frame_size);
        x264_encoder_close( h );
        x264_picture_clean( &pic );
        return -1;
    }
    else if( i_frame_size )
    {
        printf("x264_encoder_encode ok %dn",i_frame_size);
        if((outlen + i_frame_size) <= dstlen)
        {
            memcpy(dst+outlen,nal->p_payload,i_frame_size);
            outlen += i_frame_size;
        }
        else
        {
            printf("outlen=%d,i_frame_size=%d,>%dn",outlen,i_frame_size,dstlen);
        }
    }
    /* Flush delayed frames */
    while( x264_encoder_delayed_frames( h ) )
    {
        i_frame_size = x264_encoder_encode( h, &nal, &i_nal, NULL, &pic_out );
        if( i_frame_size < 0 )
        {
            printf("x264_encoder_encode err %dn",i_frame_size);
            x264_encoder_close( h );
            x264_picture_clean( &pic );
            return -1;
        }
        else if( i_frame_size )
        {
            if((outlen + i_frame_size) <= dstlen)
            {
                memcpy(dst+outlen,nal->p_payload,i_frame_size);
                outlen += i_frame_size;
            }
            else
            {
                printf("outlen=%d,i_frame_size=%d,>%dn",outlen,i_frame_size,dstlen);
            }
        }
    }

    x264_encoder_close( h );
    x264_picture_clean( &pic );

    return outlen;
}
x264.h
引用: #ifndef X264_H
#define X264_H

int x264_yuv420p2h264(uint8_t* src,uint8_t* dst, uint32_t dstlen,uint32_t width,uint32_t height);

#endif

测试
main.c中代码,实现RGB刷频,和通过函数运算绘各种艺术图片
引用: #include
#include
#include
#include
#include
#include

#include "dis.h"


#define DIM 1024
#define DM1 (DIM-1)
#define _sq(x) ((x)*(x)) // square
#define _cb(x) abs((x)*(x)*(x)) // absolute value of cube
#define _cr(x) (unsigned char)(pow((x),1.0/3.0)) // cube root


unsigned char RD1(int i,int j)
{
  return (char)(_sq(cos(atan2(j-512,i-512)/2))*255);
}

unsigned char GR1(int i,int j)
{
  return (char)(_sq(cos(atan2(j-512,i-512)/2-2*acos(-1)/3))*255);
}

unsigned char BL1(int i,int j)
{
  return (char)(_sq(cos(atan2(j-512,i-512)/2+2*acos(-1)/3))*255);
}

int draw1(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD1(i,j)&255,GR1(i,j)&255,BL1(i,j)&255);
        }
    }
}


unsigned char RD2(int i,int j)
{
  #define r(n)(rand()%n)
  static char c[1024][1024];
  return!c[j]?c[j]=!r(999)?r(256):RD2((i+r(2))%1024,(j+r(2))%1024):c[j];
}

unsigned char GR2(int i,int j)
{
  static char c[1024][1024];
  return!c[j]?c[j]=!r(999)?r(256):GR2((i+r(2))%1024,(j+r(2))%1024):c[j];
}

unsigned char BL2(int i,int j)
{
  static char c[1024][1024];
  return!c[j]?c[j]=!r(999)?r(256):BL2((i+r(2))%1024,(j+r(2))%1024):c[j];
}

int draw2(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD2(i,j)&255,GR2(i,j)&255,BL2(i,j)&255);
        }
    }
}

unsigned char RD3(int i,int j)
{
  float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
  return log(k)*47;
}

unsigned char GR3(int i,int j)
{
  float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
  return log(k)*47;
}

unsigned char BL3(int i,int j)
{
  float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
  return 128-log(k)*23;
}

int draw3(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD3(i,j)&255,GR3(i,j)&255,BL3(i,j)&255);
        }
    }
}

unsigned char RD4(int i,int j)
{
  double a=0,b=0,c,d,n=0;
  while((c=a*a)+(d=b*b)<4&&n++<880)
  {b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
  return 255*pow((n-80)/800,3.);
}

unsigned char GR4(int i,int j)
{
  double a=0,b=0,c,d,n=0;
  while((c=a*a)+(d=b*b)<4&&n++<880)
  {b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
  return 255*pow((n-80)/800,.7);
}

unsigned char BL4(int i,int j)
{
  double a=0,b=0,c,d,n=0;
  while((c=a*a)+(d=b*b)<4&&n++<880)
  {b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
  return 255*pow((n-80)/800,.5);
}

int draw4(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD4(i,j)&255,GR4(i,j)&255,BL4(i,j)&255);
        }
    }
}


unsigned char RD5(int i,int j)
{
  static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}

unsigned char GR5(int i,int j)
{
  static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}

unsigned char BL5(int i,int j)
{
  static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}

int draw5(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD5(i,j)&255,GR5(i,j)&255,BL5(i,j)&255);
        }
    }
}

unsigned char RD6(int i,int j)
{
  float s=3./(j+99);
  float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
  return ((int)((i+DIM)*s+y)%2+(int)((DIM*2-i)*s+y)%2)*127;
}

unsigned char GR6(int i,int j)
{
  float s=3./(j+99);
  float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
  return ((int)(5*((i+DIM)*s+y))%2+(int)(5*((DIM*2-i)*s+y))%2)*127;
}

unsigned char BL6(int i,int j)
{
  float s=3./(j+99);
  float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
  return ((int)(29*((i+DIM)*s+y))%2+(int)(29*((DIM*2-i)*s+y))%2)*127;
}

int draw6(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD6(i,j)&255,GR6(i,j)&255,BL6(i,j)&255);
        }
    }
}

unsigned char BL7(int i,int j);
unsigned char RD7(int i,int j)
{
    return(i+j)?256-(BL7(i,j))/2:0;
}

unsigned char GR7(int i,int j)
{
    return RD7(i,j);
}

unsigned char BL7(int i,int j)
{
    static int m[DIM][DIM],e,x,y,d,c[4],f,n;
    if(i+j<1)
    {
        for(d=DIM*DIM;d;d--)
        {
            m[d%DIM][d/DIM]=d%6?0:rand()%2000?1:255;
        }
        for(n=1;n;n++)
        {
            x=rand()%DIM;
            y=rand()%DIM;
            if(m[x][y]==1)
            {
                f=1;
                for(d=0;d<4;d++)
                {
                    c[d]=m[(x+DIM+(d==0)-(d==2))%DIM][(y+DIM+(d==1)-(d==3))%DIM];
                    f=f                 }
                if(f>2)
                {
                    m[x][y]=f-1;
                }
                else
                {
                    ++e;
                    e%=4;
                    d=e;
                    if(!c[e])
                    {
                        m[x][y]=0;
                        m[(x+DIM+(d==0)-(d==2))%DIM][(y+DIM+(d==1)-(d==3))%DIM]=1;
                    }
                }
            }
        }
    }
    return m[j];
}

int draw7(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD7(i,j)&255,GR7(i,j)&255,BL7(i,j)&255);
        }
    }
}

unsigned char BL8(int i,int j);
unsigned char RD8(int i,int j)
{
    return BL8(i,j)*(DIM-i)/DIM;
}

unsigned char GR8(int i,int j)
{
    return BL8(i,j)*(DIM-j/2)/DIM;
}

unsigned char BL8(int i,int j)
{
    static float c[DIM][DIM];
    if(i+j<1)
    {
        float a=0,b,k,r,x;
        int e,o;
        for(;a         {
            for(b=0;b             {
                r=a*1.6/DIM+2.4;x=1.0001*b/DIM;
                for(k=0;k                 {
                    x=r*x*(1-x);
                    if(k>DIM/2)
                    {
                        e=a;
                        o=(DM1*x);
                        c[e][o]+=0.01;
                    }
                }
            }
        }
    }
    return c[j]>255?255:c[j]*i/DIM;
}


int draw8(void)
{
    for(int j=0;j     {
        for(int i=0;i         {
            dis_set_pixel(j,i,RD8(i,j)&255,GR8(i,j)&255,BL8(i,j)&255);
        }
    }
}

int main(int argc, char* argv[])
{
    uint8_t r= 0x00;
    uint8_t g= 0x00;
    uint8_t b= 0xFF;
    if(argc != 3)
    {
        printf("usage:./lcdtest ip port ie:./lcdtest 192.168.1.101 5004 n");
    }
    dis_init(argv[1],atoi(argv[2]));
    while(1)
    {
#if 0
        /* RGB */
        for(int i=0;i         {
            for(int j=0;j             {
                dis_set_pixel(i,j, 0xFF, 0, 0);
            }
        }
        dis_flush();
        sleep(2);

        for(int i=0;i         {
            for(int j=0;j             {
                dis_set_pixel(i,j, 0, 0xFF, 0);
            }
        }
        dis_flush();
        sleep(2);

        for(int i=0;i         {
            for(int j=0;j             {
                dis_set_pixel(i,j, 0, 0, 0xFF);
            }
        }
        dis_flush();
        sleep(2);

        /* PIC */

        draw1();
        dis_flush();
        sleep(5);
#endif
#if 1
        draw2();
        dis_flush();
        sleep(5);
        draw3();
        dis_flush();
        sleep(5);
        draw4();
        dis_flush();
        sleep(5);
        draw5();
        dis_flush();
        sleep(5);
        draw6();
        dis_flush();
        sleep(5);
        draw7();
        dis_flush();
        sleep(5);
        draw8();
        dis_flush();
        sleep(20);
#endif
    }
}







      
编译
aarch64-linux-gnu-gcc main.c dis/*.c -lx264 -lm -lpthread -ldl -lortp -L/home/lhj/opt/x264/board/lib -I/home/lhj/opt/x264/board/include -L/home/lhj/opt/board/lib -I/home/lhj/opt/board/include -I./dis -o lcdtest
lcdtest导入到开发板
chmod + x lcdtest
运行
./lcdtest 192.168.1.101:5004
VLC打开内容为如下的sdp文件
引用: m=video 5004 RTP/AVP 96

a=rtpmap:96 H264/90000

a=framerate:25

c=IN IP4 192.168.1.101
效果如下
RGB刷频
图片1.png
图片2.png
图片3.png

艺术图片
图片4.png
图片5.png
图片6.png
图片7.png
总结
   以上我们实现了任意的画点,完成了掌上游戏机的两个重点部分,输入和显示,下一篇就开始正式的NES模拟器移植了。

更多回帖

×
20
完善资料,
赚取积分