基于S3C44B0X的128x64单色LCD编程控制

本文讲述了作者在没有完整资料的情况下,如何通过研究和实践,成功地对一块128x64单色LCD进行编程控制。作者发现LCD模块不使用S3C44B0X的LCD控制器,而是通过I/O接口直接操作,并详细介绍了接口引脚的对应关系和驱动程序的基本流程,包括设置接口、图形模式清屏、测试代码等步骤。尽管遇到资料不准确的问题,但最终实现了LCD的控制,为后续的嵌入式开发打下基础。
摘要由CSDN通过智能技术生成


    最近忙于辞职、搬家、打扫卫生、请客吃饭、找工作等杂事,少有机会能够静下心来学习一些东西。趁着投出去的简历还没有动静的这段时间,决定开始着手一早就想做的事情,那就是LCD的编程控制。

    本来买的这块板子就带一块128x64的单色点阵LCD,不玩岂不可惜了。于是乎大概重温了一下之前做的东西(其实也就是看看自己的blog),翻出光盘上的资料,一头扎了进去。

    功夫不负苦心人,也正好这段时间比较闲(NEET就是爽啊,可惜口袋里的米……),没花几天时间就有点成果了,要不是资料不全以及资料不准确,应该会比现在进展得更快才对。

    OK,苦水就吐到这里,下面来看看实在的东西。

    首先我是第一次接触LCD的开发,可以说无处下手。应该有很多人都知道S3C44B0X是集成了LCD控制器的,所以我也自然而然的从它看起。很不幸,我看了一整天后七荤八素的,反而更糊涂了。为什么呢?我手上的LCM(LCD模块)的手册(名为SG12864J501C2,但不知为何实物上却印着 HG12864-32A3,我找到HG12864-32A3的资料却又发现它与实物不符,目前来看这个SG12864J501C2和实物还是较为接近的XD)的内容没办法和S3C44B0X文档所述LCD控制器相关内容对应上,完全是莫名其妙。

    不过没关系,做菜鸟最重要的一点就是虚心,先看配套光盘的范例程序吧。先在ADS下硬件仿真一遍,确认LCD无故障、范例程序无问题,然后认真RTFSC(Linus名言,Read The Fucking Source Code)。

    代码阅毕,首先确定了一个关键性问题。该LCM(LCD模块组)不使用S3C44B0X的LCD控制器,而是采用I/O的方式直接操作。后来我才领悟到 S3C44B0X的LCD控制器是针对LM057QC1T01这样的中高端STN、TFT LCD设计的,SG12864J501C2这种低端产品,是没有办法使用的(硬件接口不同)。

    于是问题就简单化了,全部都是I/O的工作了。翻开原理图,有图如下:


    看起来很整齐,但其实完全不是那么回事。请看LCM的各引脚说明:


    由于我并没有LCD接口板的原理图,所以无法直接得知板子上的引脚最后都是怎么接到LCM上的。于是我一边向卖家索要缺失的原理图(至今仍未拿到……)一边看范例程序来试图猜出我所需要的信息(没资料真是苦啊,感觉自己此刻的身份都不再是一个开发者而是一个黑客orz)。

    经过反复的RTFSC和推断我基本确认了:

          MPU       signal   J10-CON20    LCM
          GPD0      VD0      pin1         DB0
          GPD1      VD1      pin2         DB1
          GPD2      VD2      pin3         DB2
          GPD3      VD3      pin4         DB3
          GPC7      VD4      pin5         RS
          GPC6      VD5      pin6         R/W
          GPC5      VD6      pin7         E
          GPD7      VFRAME   pin9         DB7
          GPD4      VCLK     pin11        DB4
          GPD5      VLINE    pin12        DB5
          GPD6      VM       pin14        DB6
          nRESET    nRESET   pin15        nRST
          --        VCC      pin17        PSB

    不要被signal名字所迷惑,那名字是为高端LCM准备的,我们只要关心第一列的名字,重新组织后如下:

          MPU       J10-CON20    LCM
          GPC5      pin7         E
          GPC6      pin6         R/W
          GPC7      pin5         RS
          GPD0      pin1         DB0
          GPD1      pin2         DB1
          GPD2      pin3         DB2
          GPD3      pin4         DB3
          GPD4      pin11        DB4
          GPD5      pin12        DB5
          GPD6      pin14        DB6
          GPD7      pin9         DB7
          nRESET    pin15        nRST
          --        pin17        PSB

    能看出些名堂了吧?最重要的是PGD0~7对应DB0~7,所以我们只要访问PDATD寄存器就可以从LCM中读写数据了(大概LCM接口板子的设计者就是考虑到这一点才如此接线的)。另外很重要的就是GPC5~7,它们对应着极其重要的3个控制信号E、R/W和RS。它们的具体作用请参见LCM手册。还有就是PSB信号,接VCC暗示着该LCM始终工作在并行模式下。

    接下从程序员角度来说说该LCM的特性。

    该LCM的可编程接口完全是I/O方式的,不产生任何中断信号。此类设备的驱动程序通常采用查询的方式来处理异步事务。例如忙等待,我们需要不断的问设备芯片是否就绪,如果就绪才继续对设备进行操作,否则继续等待(自旋或睡眠等待)。但如果是中断方式的设备则可以通过设备的中断信号来唤醒,在中断处理程序中陷入设备驱动来继续对设备的操作。

    ST7920是这款LCM的核心控制芯片,LCM的可编程接口基本上都是围绕着它的。

    ST7920主要有4个寄存器,分别是IR(8bit)、DR (8bit)、AC(7bit)、BF(1bit)。其中IR只写,DR可读写,AC和BF只读。它们复用DB0~7这几根信号线来同外界通讯,而其读写模式的选择信号就是RS及R/W。要注意只有在信号E生效后,指定的传送操作才会执行。E为下降边缘触发。具体请参见LCM手册。

    ST7920芯片拥有两套指令集,基本指令集和扩展指令集。分别在两种工作方式下使用。当前芯片处于何种工作方式是由芯片内部状态RE来确定的,0x20指令可以用来切换芯片工作方式。有一点很不幸,我发现似乎没有办法能直接得到芯片的当前工作方式,如果是写驱动程序的话,大概只能用一个静态变量来存它了。

    有了这些,可以动手写程序了。这里我采用上一篇文章《简单的S3C44B0X Bootloader》中描述的Bootloader来引导该程序,只要把其中的main.c文件替换掉就行了。

    该程序大致流程如下:
       1. 设置PGIO。主要是PC5-7需初始化,始终作为输出,作为对RS、R/W、E信号的控制。
       2. 图形模式清屏。实际意义是GDRAM清零,刚刚上电的GDRAM的数据是随机的,如果不复位的话在进入图形模式后会看到花屏现象,因此需要在这之前清屏。如果你只用文字模式可以不做这一步。
       3. 测试代码。就是在屏幕上胡写胡画,看看是否OK。主要包含了位图输出和文字输出,以及图形模式和文字模式的切换。

    main.c

#define PORT(addr)      (*(volatile int *)(addr))

#define INTCON          PORT(0x1e00000)
#define INTMSK          PORT(0x1e0000c)
#define PCONC           PORT(0x1d20010)
#define PDATC           PORT(0x1d20014)
#define PCOND           PORT(0x1d2001c)
#define PDATD           PORT(0x1d20020)

/* LCM读写使能 */
#define lcm_enable()    do { PDATC |= 0x20; PDATC &= ~0x20; } while (0) /* E为下降边缘触发 */

/* MPU Port D作为输入 */
#define lcm_input()     do { PCOND = 0x0000; } while (0)        /* PD0~7=input */

/* MPU Port D作为输出 */
#define lcm_output()    do { PCOND = 0x5555; } while (0)        /* PD0~7=output */

/* 测试用单色位图数据 */
static unsigned short bmp1[] = { 0x2000, 0x5000, 0xa800, 0x7000, 0x5000 };
static unsigned short bmp2[] = { 0x9900, 0x2400, 0x5a00, 0xa500, 0x5a00, 0x2400, 0x9900 };
static unsigned short bmp3[] = { 0x2000, 0x5000, 0x8800, 0x5000, 0x2000 };

static void delay(int times)                    /* 延时函数 */
{
        volatile int i;
        for (i = 0; i < (times << 10); ++i);
}

static void lcm_wait(void)                      /* LCM忙等待函数 */
{
        lcm_input();
        PDATC &= ~0x80;                         /* RS=0 */
        PDATC |= 0x40;                          /* R/W=1 */
        lcm_enable();

        /* 使用自旋方式等待 */
        while (PDATD & 0x80) {
                delay(1);
                lcm_enable();
        }
}

static void lcm_cmd(unsigned char cmd)          /* 向LCM发送命令 */
{
        lcm_output();
        PDATC &= ~0xc0;                         /* RS=0, R/W=0 */
        PDATD = cmd;
        lcm_enable();
        lcm_wait();
}

static unsigned char lcm_read(void)             /* 从LCM读出显存数据 */
{
        lcm_input();
        PDATC |= 0xc0;                          /* RS=1, R/W=1 */
        lcm_enable();
        lcm_wait();
        return (unsigned char)(PDATD);
}

static void lcm_write(unsigned char data)       /* 写显存数据到LCM */
{
        lcm_output();
        PDATC |= 0x80;                          /* RS=1 */
        PDATC &= ~0x40;                         /* R/W=0 */
        PDATD = data;
        lcm_enable();
        lcm_wait();
}

static void lcm_tpos(int x, int y)              /* 文本输出定位(文字模式坐标系) */
{
        unsigned char pos;
        y <<= 1;
        if (y & 0x04)
                y |= 0x1;
        pos = ((y & 0x03) << 3) | (x & 0x07);
        lcm_cmd(0x80 | pos);
}

static void lcm_gpos(int x, int y)              /* 图像输出定位(图像模式坐标系) */
{
        lcm_cmd(0x80 | (y & 0x7f));
        lcm_cmd(0x80 | (x & 0x0f));
}

static void lcm_text(int clear)                 /* LCM进入文字模式 */
{
        lcm_cmd(0x30);                          /* 功能设定: 基本指令集,8位模式 */
        lcm_cmd(0x0c);                          /* 显示状态: 整体显示开,光标显示关,光标高亮关 */
        lcm_cmd(clear ? 0x01 : 0x02);           /* 文字模式复位 */
}

static void lcm_graphic(void)                   /* LCM进入图形模式 */
{
        lcm_cmd(0x32);                          /* 功能设定: 先确保扩展指令集模式为关闭,再设G位 */
        lcm_cmd(0x36);                          /* 功能设定: 扩展指令集,8位模式,图像模式开 */
}

static void lcm_print(int x, int y, const char* s)     /* LCM文字模式下在指定位置输出字符串 */
{
        lcm_tpos(x, y);
        for (; *s; s++)
                lcm_write(*s);
}

static void lcm_clear(void)                     /* LCM图形模式清屏 */
{
        int i, j;
        for (j = 0; j < 64; j++) {
                lcm_gpos(0, j);
                for (i = 0; i < 32; i++)
                        lcm_write(0);           /* 连续写零清屏 */
        }
}

static void lcm_draw(int x, int y, int w, int h, const unsigned short *p)      /* LCM图形模式下在指定坐标输出位图图像 */
{
        int i, j;
        for (; h > 0; h--, y++) {
                j = w;
                lcm_gpos(x, y);
                for (i = x; j > 0; j--, i++, p++) {
                        lcm_write(*p >> 8);
                        lcm_write(*p);
                }
        }
}

static void lcm_init(void)                      /* LCM相关初始化 */
{
        PCONC = 0xaaa555aa;                     /* PC4~9=output */
        lcm_cmd(0x34);                          /* 功能设定: 扩展指令集,8位模式,图像模式关 */
        lcm_clear();                            /* 图形模式清屏 */
}

static void init(void)
{
        INTCON = 0x7;                           /* 非向量模式,禁止IRQ,禁止FIQ */
        INTMSK = 0x07ffffff;                    /* 屏遮所有中断 */
}

void entry(void)
{
        init();

        lcm_init();                             /* 初始化LCM */

        lcm_graphic();                          /* 进入图形模式 */
        lcm_draw(0, 0, 1, 5, bmp1);             /* 绘制位图1 */
        lcm_draw(1, 3, 1, 7, bmp2);             /* 绘制位图2 */

        delay(10000);

        lcm_text(0);                            /* 进入文字模式,不清屏 */
        lcm_print(3, 0, "LCD!");
        lcm_print(2, 1, "axx1611");
        lcm_print(1, 2, "SG12864J5");
        lcm_print(0, 3, "ARM7-S3C44B0X");

        delay(10000);

        lcm_graphic();                          /* 返回图形模式 */
        lcm_draw(0, 16, 1, 5, bmp3);            /* 绘制位图3 */

        for (;;);
}

    呼,以上代码是没问题的,测试结果如图:


    最后要注意lcm_tpos这个函数,该函数所写的内容和手册的描述是不同的。这里就是我前面所说的资料不准确的地方,这段代码是我自己试出来的,在此诅咒那个写手册的人,让我浪费了不少时间。

    首先这段:
        y <<= 1;
        if (y & 0x04)
                y |= 0x1;
    可能会比较费解,但你仔细看其实就是交换y的第0位和第1位。为什么要这么做?如果按照手册上所说的话,文字模式一共4行,y = 0就是第1行,y = 1是第2行,y = 2是第3行,y = 3是第4行。可实际上根本不是那么回事,经过本人试验,y = 0和y = 3没错,y = 1和y = 2的情况是反的。所以我通过交换第0位和第1位来解决这个问题。

    然后:
        pos = ((y & 0x03) << 3) | (x & 0x07);
    也和手册描述不一致。按照手册的说法应该是:
        pos = ((y & 0x03) << 4) | (x & 0x0f);
    即y应该是第4~5位,可实际上却是第3~4位;x的值范围应该为0~15,可实际上却是0~7。我本以为x丢掉的那一位被移到了第5位上,但是试验后发现第5为必须为0,1的话什么都显示不出来……

    虽然很曲折,但总算是把这款LCD给拿下了,鼓掌……

    下一步的打算就是给这个LCD写驱动在uCLinux上跑了,我也终于有机会跳出单片机开发的范畴走向真正的嵌入式开发了。敬请关注,同时祝愿自己早日找到满意的工作……
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值