前言
最近无聊翻Linux内核源码,发现了个好玩的东西:tinydrm
驱动里合并了panel-mipi-dbi
模块。从名字上来看,这个模块是用来驱动MIPI-DBI
屏幕用的。又翻阅了下notro的GitHub,找到了模块的使用说明,并且成功点亮了一块ST7789V 240*240的屏幕。
亮点
这个模块大体结构和博主这篇文章中介绍的模块没有太大的区别,唯一不同的地方就是将初始化序列
独立了出来,作为一个单独的固件
文件,模块在加载的过程中,读取固件中的初始化序列并发送给屏幕,屏幕就能正常工作了。
这样做的好处有很多:
- 不需要针对每一个屏幕编写一个单独的驱动
- 内核更新后不需要重新编译驱动模块
- 可以很方便的生成新固件以支持新屏幕
平台
博主使用的开发板是Orange Pi Zero
,系统是Armbian
。
准备
首先你需要确保自己使用的内核为5.18.x
:panel-mipi-dbi
模块是5.18.x版本中被并入主线的。
(博主为了使用这个模块,专门编译了5.18的内核,真是为了一碟醋包了顿饺子啊! )
(P.S. 树莓派在5.15.x版本中就并入了这个模块,所以使用树莓派时不需要更新到5.18内核。)
下载工具
git clone https://github.com/notro/panel-mipi-dbi.git
下载完毕后,我们需要使用里面的mipi-dbi-cmd
工具。
该工具是一个Python脚本,下面博主贴出脚本的内容(如果您无法下载工具,可以从这里复制粘贴保存为mipi-dbi-cmd
)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: CC0-1.0
#
# Written in 2022 by Noralf Trønnes <noralf@tronnes.org>
#
# To the extent possible under law, the author(s) have dedicated all copyright and related and
# neighboring rights to this software to the public domain worldwide. This software is
# distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along with this software.
# If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
from __future__ import print_function
import argparse
import sys
def hexstr(buf):
return ' '.join('{:02x}'.format(x) for x in buf)
def parse_binary(buf):
if len(buf) < 18:
raise ValueError('file too short, len=%d' % len(buf))
if buf[:15] != b'MIPI DBI\x00\x00\x00\x00\x00\x00\x00':
raise ValueError('wrong magic: %s' % hexstr(buf[:15]))
if buf[15] != 1:
raise ValueError('wrong version: %d' % (buf[15]))
result = ''
cmds = buf[16:]
i = 0
while i < len(cmds):
try:
pos = i
cmd = cmds[i]
i += 1
num_params = cmds[i]
i += 1
params = cmds[i:i+num_params]
if len(params) != num_params:
raise IndexError()
if cmd == 0x00 and num_params == 1:
s = 'delay %d\n' % params[0]
else:
s = 'command 0x%02x' % cmd
if params:
s += ' '
s += ' '.join('0x{:02x}'.format(x) for x in params)
s += '\n'
except IndexError:
raise ValueError('malformed file at offset %d: %s' % (pos + 16, hexstr(cmds[pos:])))
i += num_params
result += s
return result
def print_file(fn):
with open(args.fw_file, mode='rb') as f:
fw = f.read()
s = parse_binary(bytearray(fw))
print(s)
def parse_values(parts):
vals = []
for x in parts:
try:
val = int(x, 0)
except ValueError:
raise ValueError('not a number: %s' % x)
if val < 0 or val > 255:
raise ValueError('value out of bounds: %s (%d)' % (hex(val), val))
vals.append(val)
return vals
def make_file(fw_file, input_file):
with open(input_file, mode='r') as f:
lines = f.readlines()
buf = bytearray()
buf.extend(b'MIPI DBI\x00\x00\x00\x00\x00\x00\x00') # magic
buf.append(1) # version
for idx, line in enumerate(lines):
# strip off comments and skip empty lines
comment_idx = line.find('#')
if comment_idx >= 0:
line = line[:comment_idx]
line = line.strip()
if not line:
continue
try:
parts = line.split()
if parts[0] == 'command':
vals = parse_values(parts[1:])
buf.append(vals[0])
num_params = len(vals) - 1
buf.append(num_params)
if num_params:
buf.extend(vals[1:])
elif parts[0] == 'delay':
vals = parse_values(parts[1:])
if len(vals) != 1:
raise ValueError('delay takes exactly one argument')
buf.append(0x00)
buf.append(1)
buf.append(vals[0])
else:
raise ValueError('unknown keyword: %s' % parts[0])
except ValueError as e:
raise ValueError('error: %s\nline %d: %s' % (e, idx + 1, line))
with open(fw_file, mode='wb') as f:
f.write(buf)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='MIPI DBI Linux driver firmware tool')
parser.add_argument('fw_file', help='firmware binary file')
parser.add_argument('input', nargs='?', help='Input commands file')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
parser.add_argument('-d', '--debug', action='store_true', help='Print exception callstack')
args = parser.parse_args()
try:
if args.input:
make_file(args.fw_file, args.input)
if args.verbose:
print_file(args.fw_file)
else:
print_file(args.fw_file)
except Exception as e:
if args.debug:
raise
print(e, file=sys.stderr)
sys.exit(1)
注意
如果执行时出现env: “python”: no such file or directory
,您需要将脚本中的第一行换成#!/usr/bin/env python3
。
编写配置文件
编写配置文件(st7789v.txt
)如下:
command 0x3A 0x05
command 0xB2 0x0C 0x0C 0x00 0x33 0x33
command 0xB7 0x35
command 0xBB 0x19
command 0xC0 0x2C
command 0xC2 0x01
command 0xC3 0x12
command 0xC4 0x20
command 0xC6 0x0F
command 0xD0 0xA4 0xA1
command 0xE0 0xD0 0x04 0x0D 0x11 0x13 0x2B 0x3F 0x54 0x4C 0x18 0x0D 0x0B 0x1F 0x23
command 0xE1 0xD0 0x04 0x0C 0x11 0x13 0x2C 0x3F 0x44 0x51 0x2F 0x1F 0x1F 0x20 0x23
command 0x21
command 0x36 0x00
command 0x11
delay 20
command 0x29
解释
每一行都代表一条初始化命令。
command表示命令开头,紧跟着第一个参数是DCS命令,后面的其他参数是该命令的数据。
转换示例:
WriteComm(0xB2);
WriteData(0x1F);
WriteData(0x1F);
WriteData(0x00);
WriteData(0x33);
WriteData(0x33);
转换为如下:
command 0xB2 0x1F 0x1F 0x00 0x33 0x33
delay命令用于延时,一般在发送slpout命令和set display on命令后需要延时一段时间,具体参考屏幕手册/初始化代码。
生成
./mipi-dbi-cmd panel-mipi-dbi-spi.bin st7789v.txt
生成完毕后,将panel-mipi-dbi-spi.bin
复制到/lib/firmware
下。
设备树
编写设备树overlay文件(panel-mipi-dbi.dts
)如下:
/dts-v1/;
/plugin/;
/ {
compatible = "allwinner,sun8i-h3";
fragment@0 {
target = <&spi1>; // spi总线
__overlay__ {
/* needed to avoid dtc warning */
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
panel: panel@0 {
compatible = "panel-mipi-dbi-spi";
reg = <0>;
spi-max-frequency = <32000000>;
width-mm = <20>;
height-mm = <20>;
reset-gpios = <&pio 0 3 0>; // reset引脚
dc-gpios = <&pio 0 2 0>; // dc引脚
write-only;
timing: panel-timing {
hactive = <240>; // 横向分辨率
vactive = <240>; // 纵向分辨率
hback-porch = <0>;
vback-porch = <0>;
clock-frequency = <0>;
hfront-porch = <0>;
hsync-len = <0>;
vfront-porch = <0>;
vsync-len = <0>;
};
};
};
};
};
根据自己的需求修改里面的一些配置项。
应用
sudo armbian-add-overlay panel-mipi-dbi.dts
reboot
如果没有问题的话,重启后屏幕上就能显示命令行了。