简介:压缩包内含基于Altera现场可编程门阵列驱动红绿蓝三原色接口液晶屏的完整工程示例,面向使用Quartus开发环境的硬件工程师与嵌入式学习者,无论是消费电子还是工业显示面板,均可借助该示例快速验证驱动方案;特别适合需要实现二十四位真彩色显示、像素时钟与行场同步信号控制的场景。工程覆盖从寄存器传输级设计、逻辑综合、适配布线到生成可下载比特流的全过程,包含七个Verilog源文件、Quartus编译中间文件(cdb、hdb、ddb、rdb)、工程配置文件(qsf、qpf)以及最终下载用的sof文件,另附txt说明文档与html报告;共一百二十六个文件,压缩包仅3.43MB,结构清晰便于按需查阅。目前已有1719人学习或下载,数据表明其实用性获得一定认可。借助该工程,可以系统理解RGB液晶屏驱动的整体架构,掌握颜色空间转换、帧缓冲、时钟同步等核心逻辑的实现方法;同时可通过工程目录映射Quartus各个处理阶段的产物,为后续设计类似显示控制系统提供扎实的参考模板。
1. tft_rgb_colorbar.rar 是什么:一条色带看穿整个 RGB 链路
拿到 tft_rgb_colorbar.rar 这类包,你手里基本上就是一套 TFT 屏调试用的“标准测试图案 + 读写工程”。RGB colorbar 说白了就是一张把红、绿、蓝三通道和灰阶全部铺开的测试图,常见做法是压缩包里放着 BMP 格式的色带图、读图脚本,以及一个极简的刷屏例程。它的价值不在于好看,而在于如果屏幕点不亮、偏色、刷新错位,这张图能直接告诉你坏在像素格式、窗口坐标、扫描方向还是 Gamma 上,比对着白屏盲猜快得多。适合刚接手 MIPI/SPI 屏驱动、做板级验证的工程师,也适合在换屏后快速确认整个显示链路是否正常。
2. 读懂 colorbar 的编码逻辑:先弄明白你在往屏幕里写什么
2.1 色带里的三类信息:灰阶、分通道、渐变
RGB colorbar 不是随便画几条彩色竖条就完事的。标准做法是横向排布,从左到右分成几组信息区:灰阶块区、R/G/B 分通道渐变区、以及灰阶渐变区。灰阶块用来验证亮度层次,分通道渐变用来验证每个数据位有没有被搞错,灰阶渐变则用来暴露传输过程中的丢位和 Gamma 畸变。
在动手刷屏之前,先把这张图想象成一个像素数组,每一个像素都是一个 (R, G, B) 三元组。TFT 屏控制器的核心工作就是把这个三元组按照你配置的像素格式(RGB565 还是 RGB888)、扫描方向、窗口起始坐标,写进对应坐标的显存里。colorbar 之所以比普通照片好用,是因为它的像素值是有明确规律的——你可以推算出任何一个坐标点的期望 RGB,然后用读回或截图去比对,偏差一眼就能定位是哪个环节出了问题。
2.2 生成一张分辨率精确匹配的 colorbar:Python 脚本
我一般会用 Python Pillow 库生成测试图,而不是直接从网上下现成的。原因很简单:普通图片的分辨率很少精确等于你屏的物理分辨率,强行拉伸会让色带边缘出现插值混色,导致你以为驱动有问题,其实是一张图坏了事。
from PIL import Image, ImageDraw # 目标分辨率:改成你的屏实际分辨率 W, H = 1080, 2280 img = Image.new("RGB", (W, H), (0, 0, 0)) draw = ImageDraw.Draw(img) # 1. 左侧 1/4:8 级灰阶跳变块 block_h = H // 8 for i in range(8): g = i * 255 // 7 draw.rectangle([0, i*block_h, W//4, (i+1)*block_h], fill=(g, g, g)) # 2. 中间 3/4:R/G/B 分通道渐变,每通道宽度各占 1/4 seg_w = W // 4 # 左侧第一段:R 从 0 渐变到 255 for x in range(seg_w): r = int(x / (seg_w - 1) * 255) draw.line([(W//4 + x, 0), (W//4 + x, H)], fill=(r, 0, 0)) # 第二段:G 渐变 for x in range(seg_w): g = int(x / (seg_w - 1) * 255) draw.line([(W//4 + seg_w + x, 0), (W//4 + seg_w + x, H)], fill=(0, g, 0)) # 第三段:B 渐变 for x in range(seg_w): b = int(x / (seg_w - 1) * 255) draw.line([(W//4 + 2*seg_w + x, 0), (W//4 + 2*seg_w + x, H)], fill=(0, 0, b)) # 3. 最右侧 1/4:灰阶渐变,从 0 到 255 线性渐变 for x in range(seg_w): g = int(x / (seg_w - 1) * 255) draw.line([(W//4*3 + x, 0), (W//4*3 + x, H)], fill=(g, g, g)) img.save("colorbar.bmp", "BMP")这里每个像素点的 RGB 值都是可预测的。比如图里坐标(W//4 + 100, 任意y)处的颜色应该是一个亮红色,R 约为100/(seg_w-1)*255。在做屏端读回验证时,这些已知值就是比对基准。参数里需要改的是W和H,别用 1920x1080 的逻辑分辨率换算,要用屏显存里真实的行列数,否则窗口设置和读回坐标会全部错位。
2.3 三个必调参数:色带数、灰阶步长、渐变/跳变
色带数量不是越多越好。做灰阶跳变块时我通常用 8 级或 16 级,8 级适合快速看个大概,16 级能分辨出暗部压缩问题,再往上比如 32 级,如果驱动 IC 的 Gamma 没调好,相邻两块几乎分不清,反而不容易肉眼判断。
灰阶步长的选择要结合屏的 bit 深度。如果你的 TFT 驱动 IC 支持 8bit(RGB888),g = i * 255 // 7就很自然;如果屏工作在 RGB565 模式下,R 和 B 只有 5bit,G 只有 6bit,你把灰阶步长设为 1,写进去后相邻色阶可能完全一样,因为你把细节丢在了格式转换时。所以调试早期优先用大步长,比如每级差 32,先确认链路通,再回来验灰阶精度。
渐变和跳变的选择取决于你要解决什么问题。跳变色带用于确认像素写入位置和扫描方向,因为边界是硬切,错位现象非常明显。渐变色带则用来检查数据位传输是否丢位——如果某个数据位被拉低,渐变带中间会出现规则的横向细条纹。判断经验是:跳变块出问题先查坐标和窗口,渐变块出问题先查像素格式和引线。
3. 把色带写进 TFT 的最小工程:文件、驱动参数与刷屏命令
3.1 文件格式:为什么选 BMP 不选 JPEG/PNG
colorbar 的载体格式直接决定刷屏工程的复杂度。我们做嵌入式屏调试,最省事的是 24bit BMP。BMP 格式没有任何压缩,像素数据按行顺序保存,解析时只需要跳过一个 54 字节的文件头,剩下就是像素数组。JPEG 是压缩格式,解码需要 JPEG 库,且是有损压缩;PNG 虽然无损,但用 zlib 压缩,解码也要带 inflate 逻辑,在 MCU 上跑纯粹是给自己找麻烦。
BMP 头里只需要关心三个关键偏移量,用十六进制编辑器就能确认:
| 偏移 | 长度 | 含义 | colorbar.bmp 常见值 |
|---|---|---|---|
| 0x00 | 2字节 | 文件标记 | 0x4D42 (BM) |
| 0x0A | 4字节 | 像素数据起始偏移 | 54(无调色板时) |
| 0x12 | 4字节 | 图像宽度 | 与屏宽一致 |
| 0x16 | 4字节 | 图像高度(可为负值) | 正数为自底向上 |
| 0x1C | 2字节 | 位深 | 24 |
这里有个容易翻车的点:BMP 默认行序是从最后一行开始存的,也就是说文件里第一行像素是图像最底部的行。如果你的刷屏代码按文件顺序直接送数据,且驱动配置的扫描方向是从上到下,整个画面会上下镜像。我通常写一个小工具把 BMP 行序翻转为自顶向下,或者直接在刷屏时把 Y 坐标反过来,两种做法都可以,但一定要在代码里固定下来,避免后续换图时又踩一次。
3.2 从松散的图片文件到紧凑的像素数组:转换脚本
24bit BMP 里每个像素占 3 字节,顺序是 B、G、R,文件里有调色板或者行对齐填充时需要处理。我们用 Pillow 生成时不会有多余内容,但从别处拿来的 BMP 建议做一次归一化:
from PIL import Image import numpy as np img = Image.open("raw_input.bmp").convert("RGB") # 如果宽度不是 4 的倍数,BMP 会每行末尾补 0 到 4 字节对齐 # 这里统一转成 numpy 数组,去掉行 padding 的影响 arr = np.array(img) # 形状 (H, W, 3),通道顺序为 R, G, B # 翻转行序:BMP 存储是自底向上,转成自顶向下 arr = np.flipud(arr) # 打平成 24bit 像素流,注意转成 BGR 字节序 flat = arr[:, :, ::-1].flatten() # RGB -> BGR # 把 RGB 三个字节打包成 int,方便后续通过 SPI/MIPI 发送 packed = np.packbits(flat.reshape(-1, 8), axis=1) print(f"像素字节数: {flat.nbytes}")转换脚本的逻辑就是把图像数据变成和驱动 IC 显存布局完全一致的字节流。为什么要在电脑上先转好而不是在 MCU 里转?因为 MCU 内存小,RGB888 每像素 3 字节,一张 1080x2280 的图裸数据要 6MB 多,绝大多数 MCU 放不下。常见做法是只把转换结果做成 C 数组,刷屏时逐段读取或直接从外部 Flash 流式发送。
如果屏是 RGB565,那转换脚本就要多做一步:丢弃 R 和 B 的低 3 位、G 的低 2 位,然后打包成 2 字节。这一步能在 PC 上做就绝不在 MCU 上做,因为你每省一个 MCU 周期,刷屏速度就能快一截。
3.3 写窗口与显存填充:两条刷屏命令就够
无论你的 TFT 驱动 IC 是 JD9365DA-H3 还是更老的 ST7789,刷色带最小的命令集就三个:设置列地址范围、设置行地址范围、开始写显存。以 MIPI DSI 加常见驱动 IC 为例,命令序列如下:
// 0x2A: Column Address Set, 列地址范围 uint8_t col_addr[] = {0x2A, (uint8_t)(x_start >> 8), (uint8_t)(x_start & 0xFF), (uint8_t)(x_end >> 8), (uint8_t)(x_end & 0xFF)}; spi_send_cmd(col_addr, sizeof(col_addr)); // 0x2B: Page Address Set, 行地址范围 uint8_t page_addr[] = {0x2B, (uint8_t)(y_start >> 8), (uint8_t)(y_start & 0xFF), (uint8_t)(y_end >> 8), (uint8_t)(y_end & 0xFF)}; spi_send_cmd(page_addr, sizeof(page_addr)); // 0x2C: Memory Write, 开始连续写入像素数据 uint8_t write_cmd[] = {0x2C}; spi_send_cmd(write_cmd, sizeof(write_cmd)); // 之后开始一个像素一个像素地送 RGB 数据 for (int i = 0; i < pixel_count; i++) { spi_send_data(pixel_stream + i * bytes_per_pixel, bytes_per_pixel); }这段代码的逻辑很简单:先圈定你要写的矩形区域,然后连续灌数据。驱动 IC 会按你设置的扫描方向自动换行,你不用关心换行逻辑。但这里有两个参数必须和驱动的“扫描方向”寄存器(MADCTL, 0x36)配合起来。
如果你设置窗口为x_start=0, x_end=W-1, y_start=0, y_end=H-1,但 MADCTL 配置成右旋 90 度,那么你看到的图像是侧躺的。调试 colorbar 时先别管旋转问题,把 MADCTL 设成 0,也就是不旋转不镜像,然后再确认坐标方向,这样定位问题最简单。
3.4 先回答驱动 IC 两个问题:RGB565 还是 RGB888、扫描方向是哪个
RGB colorbar 是验证像素格式最好的工具,但前提是你先确认驱动 IC 当前被配置成什么格式。这一点怎么确认?看驱动 IC 的 Data Format Select 寄存器,名字大致是0x3A(DCS 标准)或各家自定义的COLMOD。常见取值:0x3A = 0x70表示 RGB666(18bit),0x3A = 0x60表示 RGB666 通过 24bit 接口传输,0x3A = 0x50表示 RGB565,0x3A = 0x77表示 RGB888。
// 设置像素格式: 这里以 RGB888 为例 uint8_t pixfmt[] = {0x3A, 0x77}; spi_send_cmd(pixfmt, sizeof(pixfmt));和你期望的像素格式不一致时,刷出来是什么效果?如果 colorbar 在电脑上看是 RGB888 三通道各 8bit,但屏工作在 RGB565,那么亮色区域会出现明显色偏,红色渐变带上会有细密噪点式条纹,因为低 3bit 被驱动 IC 直接扔掉了。所以刷屏之前确认两件事:一是你生成的图是什么格式,二是驱动 IC 的0x3A配置成什么。两边的 bit 深度必须匹配,否则后面所有偏色排查都是白费功夫。
扫描方向寄存器我习惯一上来就写死成0x00,也就是默认自上而下、从左到右。等 colorbar 能完整显示且方向正确后再去调旋转,这样能减少变量。记住:一个时刻只改一个参数,colorbar 才能发挥出它的定位价值。
4. python 读取图片 rgb 值与屏端读回:用数据代替肉眼判断
4.1 用 PIL 读取期望色值:getpixel 性能不够时就 load
肉眼判断 colorbar 偏色只能分辨“有没有问题”,不能量化“差了多少”。这时候就需要把期望图像的 RGB 值和实际显示结果做逐点比对。Python 端我一般用 Pillow 的getpixel,但如果你要扫全屏几千个点,getpixel太慢,更聪明的做法是用load()把像素数组一次性拉进内存:
from PIL import Image img = Image.open("colorbar.bmp").convert("RGB") # 转成像素访问对象,比 getpixel 快很多 pixels = img.load() w, h = img.size # 在屏幕上选几个关键坐标做采样,比如每个色带的中线 probe_points = [ (w // 8, h // 2), # 灰阶跳变区中点 (w // 4 + 50, h // 2), # R 渐变区 (w // 2 + 50, h // 2), # G 渐变区 (w * 3 // 4 + 50, h // 2), # B 渐变区 (w * 7 // 8, h // 2), # 灰阶渐变区 ] for pt in probe_points: r, g, b = pixels[pt] print(f"坐标 {pt}: R={r} G={g} B={b}")如果你在屏端读取到的 RGB 和这里的期望值相差超过 ±5,就要小心了。±5 以内的差异通常是背光或 Gamma 引入的,属于正常范围;差几十以上,基本是像素格式、通道顺序或者数据位传输的问题。这个方法同样可以用来读一张真实照片拍下来的屏幕画面——把照片裁剪成和 colorbar 同样分辨率,再对同一坐标取色,比对逻辑完全一样。
4.2 驱动读回:0x2E GRAM Read 的假读时序与坐标陷阱
比拍照更可靠的办法,是直接通过驱动 IC 的读回命令把显存内容抓出来。标准 DCS 命令里有一个 0x2E(Memory Read),配合地址设置命令就能把指定坐标的 RGB 值读回。听起来很直接,但有三个坑等着你踩。
第一个坑是假读。很多驱动 IC(包括 JD9365DA-H3 这类常见型号)在发送 Memory Read 命令之后,需要你额外读一次数据,丢掉第一个字节,后面才是有效数据。不同 IC 假读的字节数还不一样,有的要 1 字节,有的要 2 字节,这个只能查 datasheet,没有统一规律。
第二个坑是坐标陷阱。读回前必须重新设置 Column Address 和 Page Address,而且有些 IC 读回时地址是自增的,读到行尾后会翻到下一行。如果你只设置了一次窗口然后连续读,出来的是整块区域的数据,而不是你想要的单点。所以要读单个坐标,就得每次重新设置一次窗口,读 3 字节(RGB888 各占 1 字节)就停。
第三个坑是读回命令对传输模式有要求。SPI 接口的屏读回通常要求在命令后切换总线方向,MIPI DSI 接口则要看 IC 是否支持 DSI 读回,很多 DSI 屏只支持写,读回只在诊断模式下通过 I2C 或 SPI 完成。如果你发现读回命令发出去后总线没有响应,先别怀疑时序,去查一下这款屏的读回通道到底走的是哪条路线。
// 读回单点像素, 假设是 SPI 接口 RGB888 uint8_t cmd[] = {0x2A, (uint8_t)(x >> 8), (uint8_t)(x & 0xFF), (uint8_t)(x >> 8), (uint8_t)(x & 0xFF)}; spi_send_cmd(cmd, sizeof(cmd)); cmd[0] = 0x2B; cmd[1] = (uint8_t)(y >> 8); cmd[2] = (uint8_t)(y & 0xFF); cmd[3] = (uint8_t)(y >> 8); cmd[4] = (uint8_t)(y & 0xFF); spi_send_cmd(cmd, sizeof(cmd)); uint8_t read_cmd[] = {0x2E}; spi_send_cmd(read_cmd, sizeof(read_cmd)); uint8_t dummy, r, g, b; spi_receive_data(&dummy, 1); // 假读,丢弃 spi_receive_data(&r, 1); spi_receive_data(&g, 1); spi_receive_data(&b, 1); printf("RGB(%d,%d) = %02X %02X %02X\n", x, y, r, g, b);这段代码每次只读一个点。如果你要扫全屏,可以扩大窗口一次性读一大块,但要注意缓冲区和传输速度,一次读太多数据超过 MCU 接收能力会翻车。读回值和期望值做逐字节比较,如果R和B的值互换,说明颜色通道顺序不是 RGB 而是 BGR;如果低几位全是 0,说明驱动 IC 内部被配置成了 RGB565 而你在往它送 RGB888。
4.3 动态照明联动:把 colorbar 色值同步推给 RGB 外设灯光
colorbar 还有一个常被忽略的用途——如果系统里有氛围灯、RGB 外设灯光这类设备,它可以作为统一的颜色标定基准。做动态照明方案时,常见做法是让 MCU 解析 colorbar 的色值,再通过 PWM 或 I2C 驱动外接的 RGB LED,确保屏幕和灯光显示同一颜色,而不是各调各的。
// 从 colorbar 的 R 渐变区取一个点, 推给 RGB LED uint16_t r_val = spi_read_reg(0x2E, x, y); // 假读省略 uint8_t led_pwm[3] = { r_val, 0, 0 }; i2c_write_led(0x18, led_pwm, 3); // 驱动 RGB LED 模块这种做法在做联动灯光时很有用,屏幕和灯光的白平衡如果能对齐,视觉上会协调很多。调试技巧是:先把 colorbar 的灰阶块作为基准,屏幕显示某种灰阶时,把 LED 的 PWM 调到肉眼看起来亮度一致,然后记录下这个 PWM 表。后续做动态照明时,查表就能让总体灯光跟随屏幕内容,不需要每次调光都用色度计去测。
5. TFT 彩屏 colorbar 避坑指南:现象、原因、解决
5.1 色带只显示上半屏,下半屏还是上一帧残留
现象:刷 colorbar 后,只有上半部分更新了,下半部分保持着上一次的图像或者黑屏。
原因:这是窗口寄存器配置错误,或者驱动 IC 的行地址设置超出了实际分辨率。常见翻车点是初始化时把0x2B(Page Address Set)设成了比实际屏高多一行,或者屏的 POR 值里SS(Source Driver Shift)和NL(Number of Lines)没配对。
解决:把 Column Address 和 Page Address 的终值改成分辨率-1,如果你屏是 2280 行,行地址最大值就写0x08EB(2280 的十六进制)。还有一个细节:部分驱动 IC 的 GRAM 地址是反的,初始化时通过 MADCTL 的ROW_SCAN和COL_SCAN位来纠正,这个位如果设置错,出现的就是显示区域错位,而不是简单的上下镜像。
5.2 整条色带偏红或偏蓝,但灰阶块看起来正常
现象:RGB 三个渐变区的颜色明显不对,R 渐变区算出的是红色没错,但亮度和期望值差很远;G 和 B 区也异常。灰阶块却大致正常。
原因:这是颜色通道顺序问题。很多驱动 IC 支持 RGB 和 BGR 两种模式,寄存器通常叫BGR(在0x36MADCTL 里面)。如果你的数据是按 RGB 顺序发的,但 IC 被设成 BGR,则红蓝互换。灰阶块因为 RGB 三个通道值相等,所以看不出问题。
解决:改0x36寄存器的BGR位,或者改发送字节序,只改一处,不要两个都改。改完用 R 渐变区中点做验证,期望是(255,0,0),读回如果是(0,0,255),说明修正生效。
5.3 渐变色带出现规律性横纹,像“搓衣板”
现象:纯色渐变区不是平滑过渡,而是每隔几行出现一条更亮或更暗的横纹。跳变灰阶块没有这个问题。
原因:多数是像素格式错配后的数据位丢失。比如你架构里的 LCDC 配置成 RGB666 但发的是 RGB888 数据,故障率高的地方在低位数据线上悬空或短路。如果渐变带上有固定间隔的跳变,数一下行间距,往往能算出是哪一根数据线的问题。
解决:先用0x3A确认当前像素格式,再对照电路图,重点量悬空的数据线。软件上可以改用低 bit 深度的 RGB565 模式再刷一次,如果横纹消失,说明数据高位正常、低位线有问题,为主板硬件故障而非驱动配置错误。
5.4 色带暗部糊成一团,深灰和黑完全分不开
现象:灰阶块区域靠左边的几块看起来全是黑的,亮的部分正常。
原因:这是两个常见原因叠加:一是背光亮度偏低导致暗部对比度不足,二是驱动 IC 的 Gamma 寄存器没调过,默认曲线把暗部压缩了。colorbar 本身没有问题,问题在显示链路末端的 D/A 转换曲线。
解决:先调背光 PWM 占空比,排除亮度因素。然后查驱动 IC 的 Positive/Negative Gamma 寄存器组,通常是0xE0到0xE7一组,找厂商给出的推荐值写进去,不要自己裸调 Gamma,容易越调越花。确认 Gamma 无误后,再用 16 级灰阶色带重新验证暗部层次。
5.5 纯色区域的边缘出现彩色毛边
现象:colorbar 里灰色的跳变块边缘出现红绿蓝杂色,像是被描了一圈边。
原因:这个一般不在驱动 IC 本身,而在屏的像素排列和扫描时序。如果是旋转屏,颜色子像素排列从 RGB 条纹变成了 PenTile 或其他排列方式,子像素渲染逻辑不对就会出现彩色边缘。
解决:先确认 MADCTL 的旋转方向,每个方向对应的子像素排列可能不同。如果是接口时序造成的采样偏移,检查 pixel clock 和行同步参数,在屏规格书的时序表里找到tHBP和tVBP的推荐区间。付费经验是:这种错位问题最容易被怀疑成驱动代码,实际往往出在初始化时序参数配置表里。
6. colorbar 的进阶玩法:用固定色块做坏点定位与灰阶响应
6.1 坏点扫描:固定坐标原色像素对比
常规色带图是按区域铺整体渐变的,但你做产线屏检时,需要另一种形态的 colorbar:每个像素都是固定颜色,比如 R=255 全屏红色,再切全绿、全蓝、全白、全黑。把这些纯色帧按顺序循环播放,人眼或自动光学检测设备逐帧拍照,任何一个不亮的点都会在某些帧暴露出来。
坏点定位的思路是在拍照后做像素减法:期望图像是纯色,实际拍到的图中,某个点的像素值和周围差异超过阈值,就记为可疑坏点。
import cv2, numpy as np img = cv2.imread("screen_white.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 每个像素与周围 3x3 均值做差, 差异过大就是坏点候选 blur = cv2.GaussianBlur(gray, (3, 3), 0) diff = cv2.absdiff(gray, blur) bad = np.argwhere(diff > 40) print("坏点坐标列表:", bad[:50])阈值 40 是个经验值,实际还要配合光圈和曝光来校准。如果整屏所有像素灰度值整体偏低,说明曝光不足,先调相机参数再判坏点,否则会误报一片。
6.2 灰阶响应时间:用照片序列测拖影
常规 dynamic 应用场景里,屏的响应速度比色准更影响体验。你可以在 colorbar 里加入渐变的灰阶块,快速从 0 跳到 255 再跳回来,用高速相机连拍几十帧,观察中间帧的亮度是否符合预期过渡。没有高速相机时,用手机慢动作视频逐帧提取也行,虽然精度差一些,但判断严重拖影足够了。
这个方法的优点是全部复用已有工程:生成图、刷屏代码、Python 读帧脚本都是调试中已经写好的东西,不用额外引入仪器。我自己的习惯是每次调完驱动,先把纯色循环和灰阶跳变各跑一遍,再进入上层应用开发,避免屏幕问题掩盖 UI 问题。这条流程帮我在后来遇到显示异常时能立刻分清是驱动层问题还是应用层数据问题,少翻了很多次车——希望帮到你。
本文还有配套的精品资源,点击获取