得力991CN计算器-LCD屏幕逆向

去年在海鲜市场低价收了2台得力991CN plus计算器(仿的卡西欧991Plus),功能上没啥好说的,目前基本上也用不到计算器了;
屏幕还不错,显示区域约2.4英寸,分辨率192 * 63 + 顶部20个图标,最近有时间分享下调试过程。


拆开壳,就可以看到焊接在主板上的屏线接口,左侧是屏驱动芯片内部电荷泵使用的电容,用于产生液晶偏转所需的几路电压(V0, V1, V2...),屏供电为1.5V(2节锌银纽扣电池并联)。
右侧就是数字通信接口了,有13条左右,这就排除了SPI,I2C这种串行接口了,推测多半是i8080或者m6800这类并口,具体是哪一种得上逻辑分析仪观察数据波形。
排线上从左到右为1脚到30脚,FPC上数字是我后加的,这里约定线序方便后面讨论。

区分80并口和6800并口:

8080并口使用RD/WR两个引脚分别作为读写时钟,6800并口读写信号在一个引脚上(/WR),单独一个锁存信号(E)控制信号的读取/写入。
单从引脚数量上无法区分,都是5根控制线+8位或16位数据线,从逻辑分析仪波形上,如果看到某一根线上一直有时钟信号,那么就是6800并口了;
反之如果看到有2根线上都有时钟波形,那就是8080并口。

区分数据线和控制线:

找几个类似分辨率的FSTN驱动芯片(带控制器的)手册,都会发现其数据线和控制线都是各自集中,分布在两侧,
不会出现数据线和控制线混杂排布的情况,因此我们只要找到其中一类引脚,就能确定周边引脚类型了。

找RESET线

reset线有非常明显的特性,初始化屏幕时短暂高低跳变,随后长时间保持固定的电平(通常是高电平)。
下图是按17~29脚顺序排布的数据波形图,可以看到28号脚就符合RESET脚的特性,把它标记为复位信号脚。

再结合之前类似驱动IC的引脚排布顺序,仅这一步我们就确定了数据线和控制线的分布情况。
25,26,27,28,29为控制线,17,18,19,20,21,22,23,24为数据线。


找CS线

cs为通信总使能信号,在下图中可以看到,抓取到的波形中时钟密集区域(传输数据)的部分,29号脚都是低电平且处在控制线附近,那么将其标记为片选脚。

RW/WR

25,26脚上,都能看到密集的时钟信号,因此可以确定这块屏使用的是i8080接口,那么下面只要确定哪个是RW,哪个是WR了。

在屏复位后,通常会有清屏动作将LCM内部的GRAM填充0,那么数据线会放0,表现为长低电平(下图的蓝色方框区域),WR高低跳变,锁存数据。
这里就能确定26号脚是WR信号了,那么同样有时钟信号的25号脚就是RD。

RS

经过上面一步,只剩下27号脚了,根据i80接口定义,RS脚为高电平时传输的是数据,低电平时传输的是命令,
下图中的红色区域,27号脚为高电平,与上面的WR时钟和数据线全低区域对应,那么RS就是27号引脚。

8位数据线

8位数据线一般为顺序排布,因此会有下图的两种情况:

这里使用逻辑分析仪的数据解码功能,添加一个并口解码器,将时钟设置为WR信号,假定17~24D0~D7,进行数据解码。


可以看到下面概览图展示了屏上电后的4个过程:1:复位信号区,2:首次数据读写区,3:延时区,4:二次数据读写区。

放大屏幕硬复位后,首次数据读写区域可以看到有以下步骤组成:

  • 主机读屏幕命令(状态寄存器),屏幕返回了0x00
  • 主机向屏幕写入5次命令(配置寄存器)0xf8 0x00 0x2e 0x81 0x2a
  • 一段时间的延时操作

    网上搜罗一些192*64的FSTN驱动IC的数据手册,屏幕分辨率可以在计算器开机后,调高对比度(这样像素点几乎全亮)大致数一下。

    经过一番查找,在ST7565PIST3020的数据手册中找到相同的控制指令,且看指令描述为设置参考电压,电源控制,读状态这类初始化时才需要操作的寄存器,那么证明假设的引脚顺序是正确的。
    如果没有找到相同的,或有相同的指令但看描述信息不像是初始化时候该做的,那么就反过来假定17~24D7~D0,再次进行数据解码并查找类似的手册进行分析。

    也可以观察显存读写的指令,很容易就找到类似的了。

引脚定义

结合计算器原始电路板上元件和逻辑分析后数字接口定义,得到以下引脚定义:

序号 定义 序号 定义 序号 定义
1 NC 11 V3 21 D3
2 V0 12 C4P 22 D2
3 C1P 13 C4N 23 D1
4 C1N 14 V4 24 D0
5 V1 15 VDD 25 RD
6 C2P 16 VSS 26 WR
7 C2N 17 D7 27 RS
8 V2 18 D6 28 RESET
9 C3P 19 D5 29 CS
10 C3N 20 D4 30 NC

驱动IC:和ST7565P,IST3020相兼容的控制器
分辨率:192*63 + 顶部一行20个图标
电压:VDD和VIO在原计算器板子上测量得1.5V,推测应该是1.8V,!!! 3.3V电压会导致显示异常

驱动代码

由于不同的MCU所使用的SDK并不相同,这里给出共通的伪代码逻辑,具体的延时和i80接口通信需要自行实现。

// 复位时序
// reset start
DATA = RD = WR = RS = RST = CS = 0
delay_ms(20)
DATA = RD = WR = RS = RST = CS = 1
delay_ms(13)
RST = 0
delay_ms(63)
RST = 1
delay_ms(13)
// reset done
// i80接口读写预定义
/**
 * @brief read data from i80 bus
 * @param flag CS_TAKE, CS_RELEASE or CS_KEEP
 * @param dc CMD or DATA which wants to read
 * @param data ptr to store read data(type of 'uint8_t *')
 * @param size unit: byte
 * */
static void i8080_read(uint8_t flag, uint8_t dc, void *data, uint32_t size);

/**
 * @param flag CS_TAKE, CS_RELEASE or CS_KEEP
 * @param dc 1:data, 0:cmd
 * @param buffer which data send out
 * @param size size of buffer in bytes
 * */
static void i8080_write(uint8_t flag, uint8_t dc, const void *buffer, uint32_t size);
static void i8080_write_ex(uint8_t flag, uint8_t dc, uint8_t data, uint32_t size);
// 发送初始化序列
const char *sequence01 = "\x88\x88\x88\x88\x93\xE3\xF8\x00\x2E\x81\x2A";
i8080_write(CS_TAKE, CMD, sequence01, 11);
delay_ms(465);

// 配置屏显示模式
/*
0x81 // Set the V0 output voltage electronic volume register
0x25 // Electronic volume value, only low 6bit(0x00~0x3F)
0x84 // Enter partial display mode
0x3F // Set partial display bias
0x83 // Exit partial display mode
0x10 // Set col address high
0x00 // Set col address low
0x32 // set the number of line reversal drive line, only low 4bit
0xA0 // Sets the display RAM address SEG output correspondence, 0xA0:normal, 0xA1:reverse
0xC8 // Select COM output scan direction [C0:normal direction, C8:reverse direction]
0xA4 // Select normal/entire display ON, 0xA4:normal display, 0xA5 entire display ON
0xAF // LCD display ON/OFF, 0xAE:OFF, 0xAF:ON
*/
const char *sequence12 = "\x81\x25\x84\x3F\x83\x10\x00\x32\xA0\xC8\xA4\xAF";
i8080_write(CS_KEEP, CMD, sequence12, 12);
delay_ms(13);

// 清屏
const char *page_addr = "\xB0\x10\x00";
i8080_write(CS_KEEP, CMD, page_addr, 3);
i8080_write_ex(CS_RELEASE, DATA, 0x00, 1536);
delay_ms(1);

显存组织方式

对于不同的屏显示模式配置(见上文代码),屏控制器读取显存的方向有所不同,这里给出的为上文代码配置下的显存读写方向。

屏幕显存横向分成8页,每页192字节,共1536字节。

其中每页的显示方式为纵向扫描,低位在前。

第0页所有的Bit0都被顶部的图标显示区占用了,由于图标只有20个,因此第0页大部分字节的Bit0位都是无效的,Bit0有效的字节位置见下面的ICON_LUT
由于图标区占用了一行显存,因此这个屏使用192*64的控制器,但实际能显示的点阵区域为192*63

static const uint8_t ICON_LUT[20] = {
    // 反显S 反显A 正显M x输入框 根号
    7, 15, 23, 31, 47,
    // 反显D 反显R 反显G  FIX浮点数固定显示小数部分 SCI浮点数科学计数
    55, 63, 71, 79, 87,
    // 大写英文E符号 i提示符 复数虚部符号 空心向下箭头 实心左箭头
    95, 103, 111, 127, 135,
    // 实心下箭头 实心上箭头 实心右箭头 暂停 太阳能图标
    143, 151, 159, 175, 183
};

屏幕打点函数,framebuffer为全屏缓存(1536字节)。

/**
 * @brief 绘制像素点
 * @note y轴为0时,x轴坐标值表示点亮对应的图标
 * @param x x轴坐标(0~191)
 * @param y y轴坐标(0~63)
 * @param color 1: 黑色,0: 白色
 * */
static void draw_pixel(uint32_t x, uint32_t y, uint8_t color) {
    uint32_t index = (y >> 3) * 192  + x;
    uint32_t shift = (y & 0x7);
    framebuffer[index] &= ~((uint8_t)1 << shift);
    framebuffer[index] |= ((uint8_t)color << shift);
}
/**
 * @brief i8080 write
 * @param pos not used
 * @param buffer which data send out, shuold be const uint8_t *
 * @param size size of buffer in bytes
 * */
rt_size_t lcd_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) {
    const uint8_t *frame = (const uint8_t *)buffer;

    uint32_t i, column;
    uint8_t page_buffer[3];
    // 1, 31, 32, 32, 32, 32, 32
    // 地址自增模式有点问题,现每传1字节换一次列地址
    i8080_write(CS_TAKE, CMD, nop_operation, 1);

    for(i = 0; i < GRAM_PAGES; i++) {
        page_buffer[0] = (0xB0 + i);
        // write page once
        for(column = 0; column < GRAM_COLUMN; column++) {
            // write column once
            page_buffer[1] = (0x10 | ((column >> 4) & 0xF));
            page_buffer[2] = (column & 0xF);
            i8080_write(CS_KEEP, CMD, page_buffer, 3);
            i8080_write(CS_KEEP, DATA, (frame + column), 1);
        }
        frame += GRAM_COLUMN;
    }

    i8080_write(CS_RELEASE, CMD, nop_operation, 1);

    return size;
}
posted @ 2022-06-10 21:54  Yanye  阅读(867)  评论(0编辑  收藏  举报