前文
汇编码、机器码、十六进制码相互转换阐述了在各种码字之间进行相互转换的方法。本文将使用此法实现一个液晶显示屏驱动。
以 宏晶
STC12C5A60S2 与 液晶显示屏
KD014QQTBN001(ST7735S) 为例。
1. 连线
首先将 STC12C5A60S2 连接
PL2303:
然后连接 KD014QQTBN001:
图中 KD014QQTBN001 的
IM2 和
IM0 引脚都接 高电平 5.0V,
SPI4W 接 低电平 地,表示 ”MCU 16-bit
Parallel“,即 ”16位 并行接口“。
同时 STC12C5A60S2 的
P2、
P0 16 位 接 KD014QQTBN001 的数据总线
DB 16 位。
P1 前 5 位 接 KD014QQTBN001 的控制线
RD、
DCX、
RESET、
CS、
WR。
2. 启动
STC 芯片的 引脚 和 地址 有如下 对应关系:
我们可以通过操作地址,以某种方式,达到控制引脚电平的目的。例如我们在
P0.0 脚接了一个 发光二极管,那么点亮即可用 汇编码:
CLR 0x80。功能是将
P0.0 脚置 低电平,在 发光二极管 上产生一个 电压 从而导通发光。
而现在我们要控制的是一个 液晶显示屏,这就需要 控制引脚电平 产生一定波形,因此必须参见 KD014QQTBN001 芯片手册。可以看到它的波形图:
我们首先根据 波形图 写出 写一个字节 和 读一个字节 的 汇编码:
2.1 写一个字节
根据时序图,可以写出 汇编码:
0x91 为
P1.1 对应
DCX,
0x94 为
P1.4 对应
WR,
0x80 为
P0 对应
DB7-0。
P0 的一个字节 在
WR 的
上升沿 写入 液晶显示屏。
也就是说,
0x80 的一个字节 在 执行
SETB 0x94 时 写入 液晶显示屏。
2.2 读一个字节
同理可以写出 汇编码:
0x90 为
P1.0 对应
RD。
在
RD 的
下降沿 读取 液晶显示屏 一个字节 到
P0。
也就是说,在 执行
CLR 0x90 时 读取 液晶显示屏 一个字节 到
0x80。
2.3 函数
我们将 上述两组指令 写成
函数 的形式:
这样 写一个字节 的功能,用一个
ACALL 指令 调用,就完成了。
它的执行方式是这样的:
ACALL 0x0003,长跳转 到
0x0009,执行 写一个字节,执行到
0x0011 然后返回。
2.4 唤醒
我们调用 写一个字节 函数 实现 唤醒
0x11 指令:
这样应该就 唤醒 了。当然前提必须是已 重置 的(
RESET 有一个 高-低-高 电平)。
还有一个命令,也是一定必须在 启动代码 中给出的。那就是 设置像素格式 命令
0x3a,它的 数据 0x06 表示我们前述的 16位 并行接口。
还有 开显示
0x29 命令,不过 好的驱动 会考虑到 使用缓存 来消除 视觉可见的 刷新,也就是在 写内存
0x2c 命令 进行时 关显示
0x28,等完成后再 开显示。
还有许多命令例如 局部刷新 也是很有用的,可以参见 KD014QQTBN001(ST7735S) 的芯片手册。
3. 驱动
液晶显示屏 KD014QQTBN001 的分辨率为 128x128。对于 16位 并行接口 模式,颜色编码如图:
3.1 时钟数
可以看到 每 3 个时钟 传送 6 个 颜色分量 也就是 2 个 像素。平均每 1 个像素 需要 1.5 个时钟。
这样,128x128 个像素需要的时钟数就是 128x128x1.5。
要 写内存 这么多的数据,是一定要通过 循环 进行的。先来看一个 小循环:
3.2 小循环
因为 液晶显示屏 像素个数一定非 0,因此写循环不会是 0 次的。所以我们可以 先执行一次循环体,然后对寄存器数值减一,减到 0 时表示这层循环完成。这样寄存器初值为 0 表示循环 1 次,...,初值为 255 表示循环 256 次。
熟悉
C 语言 的读者 可以很清楚地理解到,这就是
do 循环 的汇编码。这样做的一个好处是,充分地应用了 8位寄存器 的表数能力,使循环的最大次数增加了 1 次。
我们用 汇编码 实现循环,如图:
我们依 2.4节 的说明 在循环前 调用 写一个字节 功能 传送 写内存
0x2c 命令(
0x80 =
0x2c,
0x91 = 0),然后 在循环体中 将
NOP 指令换成 同样 调用 写一个字节 功能 传送 数据(
0xa0、
0x80 = 数据,
0x91 = 1)。
它一共循环了 0xff(256) 次,这已是 8位寄存器 表数范围的 极限 了。要进行更多次数的循环,必须用 多重循环。
3.3 大循环
芯片 STC12C5A60S2 字长是 8位 的,每个 寄存器 都是 8位,表数范围只有 0~255,太小。因此需要一个 多重循环 来表示 128x128x1.5 这个数字。
多重循环 可用 多个寄存器,甚至 还可以用 寄存器外的内存,因此循环次数是 接近 无限 的,表示 这个数字 当然是没有问题的。
如何确定循环的 层数 和 每层次数 呢?
设循环层数 n,第 i 层循环次数 ti+1,ti 为寄存器初值。那么循环总次数可按如下公式计算:
我们可以设置各层循环次数如下:
因为
大循环是多个小循环的嵌套,实现是类似的,这里就不细说。详细可以参考资源:
http://www.pudn.com/Download/item/id/3186010.html
http://download.csdn.net/detail/elecfans2csdn/9861717
这样就完成了 一个屏幕 的显示。
4. 操作系统
启动代码 运行完成后 跳转 或 将 操作系统 加载到 内存中 执行。操作系统 调用 驱动 完成显示。
4.1 代码投影
如果用 STC12C5A60S2 芯片,就只能 跳转 而 不能 将 操作系统 加载到 内存,这是由 指令集 决定了的,因为这个指令集 不能支持 “代码投影(Code Shadowing)” 技术。我们看一个指令集 是否支持代码投影 其实也不难,只要看 PC 和 MOV 指令是否指向同一片内存空间 就行了。
我们先用 跳转 到 操作系统 的方法。
4.2 分页
在 汇编程序设计 过程中,可以用到 STC12C5A60S2 芯片 指令集 支持的一种技术: “分页(Page)” 技术。分页 有很多应用,我们这里将其应用于 模块化程序设计。
我们将 2.节 的 启动、3.节 的 驱动、4.节 的 操作系统 以及我们的显示数据,分别部署在 内存 的 4 个页中:
每页首部都是跳转指令(
AJMP、
LJMP)。驱动中,居于首部的一个(也可多个)
AJMP 页内跳转指令 指向一个 驱动功能,是为 操作系统
LJMP 长跳转 准备的;然后的几个 AJMP 页内跳转指令 指向一些 读写操作,是为 驱动自己 准备的。
这个方法好处很明显。一方面,驱动的改动 不会影响到 操作系统,因为首部地址总是不动的;另一方面,操作系统的 调用 也变得简单,因为只需一个
ACALL 页内首地址 就行。
5. 实物图
用前文
汇编码、机器码、十六进制码相互转换所述方法,将本文 汇编码 生成 十六进制码,同时指定每个 页 的地址。发一张 烧写入 STC12C5A60S2 成功的实物图: