得力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~24
为D0~D7
,进行数据解码。
可以看到下面概览图展示了屏上电后的4个过程:1:复位信号区,2:首次数据读写区,3:延时区,4:二次数据读写区。
放大屏幕硬复位后,首次数据读写区域可以看到有以下步骤组成:
- 主机读屏幕命令(状态寄存器),屏幕返回了
0x00
- 主机向屏幕写入5次命令(配置寄存器)
0xf8 0x00 0x2e 0x81 0x2a
- 一段时间的延时操作
网上搜罗一些192*64的FSTN驱动IC的数据手册,屏幕分辨率可以在计算器开机后,调高对比度(这样像素点几乎全亮)大致数一下。
经过一番查找,在ST7565P
和IST3020
的数据手册中找到相同的控制指令,且看指令描述为设置参考电压
,电源控制
,读状态
这类初始化时才需要操作的寄存器,那么证明假设的引脚顺序是正确的。
如果没有找到相同的,或有相同的指令但看描述信息不像是初始化时候该做的,那么就反过来假定17~24
为D7~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;
}