嵌入式硬件实战提升篇(一)-泰山派RK3566制作多功能小手机
引言:主要针对于嵌入式全栈内容的知识点汇总并对于linux等相关驱动知识点进行串联,用大家参考学习,并用到了嘉立创提供的泰山派RK3566作为学习的主控。
实物演示如下所示:
目录
一、硬件设计
1.转接电路
2.背光电路
3.音频接口
4.原理图设计
5.PCB设计
二、软件开发
1.驱动开发
1.1.设备树驱动改写
1.2.GP7101背光驱动设备树编写以及配置i2c1设备树
1.3.屏幕参数设配
1.4.触摸屏驱动
2.Android系统开发
三、外壳制作
一、硬件设计
我们的主控用的是RK3566由于主板的 MIPI 接口 为 31 PIN,而我们选型的屏幕是 24 PIN 的 DSI 所以得设计一个转接板,并且转接板主要由接口转换电路、背光电路、音频电路构成。
其中,3.1寸屏幕的分辨率为 480x800,使用的是MIPI DSI接口,屏幕排线为24个引脚,其中4、5、9脚为空不需要接,接下来我们对所选型的屏幕进行分析。
1.转接电路
如上为LCD屏幕的数据手册,如下我们对此屏幕设计的转接电路图。
如下为CTP电容式触摸屏的数据手册,如下我们对此屏幕设计的转接电路图。
如下为CTP电容式触摸屏的数据手册,如下我们对此触摸屏幕设计的转接电路图。
2.背光电路
值得注意的是,他的输出电流是110mA我们3.1寸屏幕最大能承受的驱动电流是25mA所以不适合直接接到3.1寸屏幕的FPC上。
板子背光驱动电路IOUT=0.2V/R(R=(R95xR96)/(R95+R96)),最终得出IOUT = 0.2V/1.8≈110mA
驱动电路利用SY7201ABC,SY7201ABC 提供恒流输出,确保LED模块的电流稳定。这对于LED的亮度控制至关重要,因为LED的亮度与电流成正比,恒定的电流能够保证LED的亮度一致,避免因电流波动导致的亮度不均匀或闪烁问题。如下为SY7201ABC的数据手册。
针对上述引脚我们需要认识一下, LX:电感连接引脚,控制电流的转换。
GND:地引脚,电路的参考点。
FB:反馈引脚,用于设定输出电流(亮度)。
EN/PWM:使能引脚和调光控制引脚,使用 PWM 信号调节亮度。
OVP:过压保护引脚,防止电压过高对电路和 LED 模块造成损害。
IN:输入电压引脚,连接电源并使用电容去噪。
其次这里可以看出是BOOST升压电路,对于BOOST升压电路如果不了解可以回看专栏【嵌入式硬件知识汇总】
因此,下述原理图中的,IN与LX就不再赘述,OVP主要用于过压保护引脚,FB通过反馈电压来控制驱动电流。连接一个外部电阻(R1)将 FB 引脚与 GND 连接,用来设定输出电流。也就是L out = 0.2V/R100 = 0.2/10 = 20mA 其中R99为NC不贴,如果电流还是未达到阈值可以考虑。最后EN/PWM引脚,R102 R101为上下拉电阻默认下拉贴R101,我们根据需要在没有驱动控制的时候通过上拉或者下拉电阻来决定屏幕背光关闭还是打开。
最后因为泰山派没有PWM引脚引到3.1寸扩展板,但触摸接口有I2C1引到3.1寸扩展屏幕上,I2C是可以挂在多个设备的,所以为了能够实现背光调节功能,我们通过GP7101一颗I2C转PWM的芯片来实现PWM的调节,GP7101和触摸一起挂到I2C1下,这样板载背光的EN/PWM也能体现出来作用。
3.音频接口
喇叭
通过两个弹簧顶针(POGO PIN)与泰山派SPKP和SPKN连接,音频驱动电路由泰山派上的RK809-5实现。
麦克风
通过一个弹簧顶针(POGO PIN)与泰山派MIC连接,MIC相关的驱动电路集成在了泰山派上。
4.原理图设计
5.PCB设计
MIPI的差分对属于高速信号需要特殊处理,在布线中选择差分对布线来出MIPI的差分线,并且没对走线中进行各地处理,差分对的误差需要在合理范围之内所以需要在布线中选择差分对长度调节来走蛇形线使差分在合理范围之内,如下为PCB视图:
具体顶层底层如下所示:
二、软件开发
1.驱动开发
1.1.设备树驱动改写
mipi相关的设备树在tspi-rk3566-dsi-v10.dtsi
中,这里面包含mipi相关的所有设备树。我们通过tspi-rk3566-user-v10.dts
中使用头文件去包含tspi-rk3566-dsi-v10.dtsi
来决定是否使用mipi屏幕
具体代码段如下:
/dts-v1/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/display/media-bus-format.h>
#include <dt-bindings/pinctrl/rockchip.h>
#include "rk3566.dtsi"
#include <dt-bindings/display/rockchip_vop.h>
//tspi核心配置层,这里是几乎后期不需要怎么改动
#include "tspi-rk3566-core-v10.dtsi"
//【开/关】EDP 显示屏幕配置,用户可以基于此复制自己的屏幕,注意EDP与MIPI屏幕互斥,因为共用了VOP如果需要同显自行修改
// #include "tspi-rk3566-edp-v10.dtsi"
//【开/关】mipi 显示屏幕配置,用户可以基于此复制自己的屏幕,注意EDP与MIPI屏幕互斥,因为共用了VOP如果需要同显自行修改
// #include "tspi-rk3566-dsi-v10.dtsi"
//【开/关】HDMI 显示屏幕配置,里面内容几乎可以不用动,如果不需要hdmi显示直接注释掉即可
#include "tspi-rk3566-hdmi-v10.dtsi"
//【开/关】摄像头 目前视频的是ov5659
#include "tspi-rk3566-csi-v10.dtsi"
//【开/关】网口 扩展板上使用的是千兆网,不接扩展板情况下可以关闭
// #include "tspi-rk3566-gmac1-v10.dtsi"
//【开/关】下方是用户定义层,所有用户修改理论上在此下方修改就好了
/ {
model = "lckfb tspi V10 Board";
compatible = "lckfb,tspi-v10", "rockchip,rk3566";
rk_headset: rk-headset {
compatible = "rockchip_headset";
headset_gpio = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&hp_det>;
};
leds: leds {
compatible = "gpio-leds";
rgb_led_r: rgb-led-r {
gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_LOW>;
linux,default-trigger = "timer";
linux,delay-reg = <0>; // 延时注册
linux,blink-delay-on = <500>; // 打开时间
linux,blink-delay-off = <500>; // 关闭时间
};
rgb_led_g: rgb-led-g {
gpios = <&gpio1 RK_PB1 GPIO_ACTIVE_LOW>;
linux,default-trigger = "timer";
linux,delay-reg = <100>; // 延时注册
linux,blink-delay-on = <1000>;
linux,blink-delay-off = <1000>;
};
rgb_led_b: rgb-led-b {
gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_LOW>;
linux,default-trigger = "timer";
linux,delay-reg = <100>; // 延时注册
linux,blink-delay-on = <1500>;
linux,blink-delay-off = <1500>;
};
};
};
&pinctrl {
headphone {
hp_det: hp-det {
rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
//用户三色灯
&leds {
status = "okay";
};
//耳机插入检测,不使用扩展板情况需关闭,否则默认会检测到耳机插入
&rk_headset {
status = "disabled";
};
//用户串口3
&uart3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart3m1_xfer>;
};
//用户I2C2
&i2c2 {
status = "okay";
/*添加你的I2C设备参考
gt1x: gt1x@14 {
compatible = "goodix,gt1x";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&touch_gpio>;
goodix,rst-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
goodix,irq-gpio = <&gpio0 RK_PB5 IRQ_TYPE_LEVEL_LOW>;
};*/
};
&i2c3 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3m1_xfer>;
/*添加你的I2C设备参考
gt1x: gt1x@14 {
compatible = "goodix,gt1x";
reg = <0x14>;
pinctrl-names = "default";
pinctrl-0 = <&touch_gpio>;
goodix,rst-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
goodix,irq-gpio = <&gpio0 RK_PB5 IRQ_TYPE_LEVEL_LOW>;
};*/
};
&spi3 {
status = "okay";
max-freq = <48000000>;
dma-names = "tx","rx";
pinctrl-names = "default", "high_speed";
pinctrl-0 = <&spi3m1_cs0 &spi3m1_pins>;
pinctrl-1 = <&spi3m1_cs0 &spi3m1_pins_hs>;
spi_test@10 {
compatible ="rockchip,spi_test_bus1_cs0";
reg = <0>;
spi-max-frequency = <24000000>;
status = "okay";
};
};
&pwm8 {
status = "okay";
};
&pwm9 {
status = "okay";
};
&pwm14 {
status = "okay";
};
//pwd 15遥控器
&pwm15 {
status = "okay";
compatible = "rockchip,remotectl-pwm";
remote_pwm_id = <3>;
handle_cpu_id = <1>;
remote_support_psci = <0>;
pinctrl-names = "default";
pinctrl-0 = <&pwm15m0_pins>;
//用户自定方法:adb设置输出日志并通过dmesg确定usercode=address与key_table=command
//echo 1 > sys/module/rockchip_pwm_remotectl/parameters/code_print
//键值可在 include/dt-bindings/input/linux-event-codes.h 中查找
ir_key1 {
rockchip,usercode = <0xff00>;
rockchip,key_table =
<0xf2 KEY_MENU>,
<0xe9 KEY_BACK>,
<0xe3 KEY_ENTER>,
<0xe7 KEY_UP>,
<0xad KEY_DOWN>,
<0xf7 KEY_LEFT>,
<0xa5 KEY_RIGHT>,
<0xba KEY_1>,
<0xb9 KEY_2>,
<0xb8 KEY_3>,
<0xbb KEY_4>,
<0xbf KEY_5>,
<0xbc KEY_6>,
<0xf8 KEY_7>,
<0xea KEY_8>,
<0xf6 KEY_9>,
<0xe6 KEY_0>;
};
};
把下述位置打开即可
1.2.GP7101背光驱动设备树编写以及配置i2c1设备树
从原理图中可知GP7101和触摸共同挂在道I2C下,从数据手册中我们可以得知GP7101的I2C地址是0XB0,0xB0是包含了读写位的所以我们实际填写中还需要右移一位最终地址为0X58。
在tspi-rk3566-dsi-v10.dtsi
中添加GP7101相关设备树驱动,首先引用I2C1并往设备树I2C1节点中添加GP7101子节点并指定I2C地址、最大背光,默认背光等。
&i2c1 { // 引用名为i2c1的节点
status = "okay"; // 状态为"okay",表示此节点是可用和配置正确的
GP7101@58 { // 定义一个子节点,名字为GP7101,地址为58
compatible = "gp7101-backlight"; // 该节点与"gp7101-backlight"兼容,
reg = <0x58>; // GP7101地址0x58
max-brightness-levels = <255>; // 背光亮度的最大级别是255
default-brightness-level = <100>; // 默认的背光亮度级别是100
};
};
一般背光驱动都放在/kernel/drivers/video/backlight
目录下,所以我们在此路径下创建一个my_gp7101_bl
目录用来存放Makefile
和gp7101_bl.c
文件,编译利用 obj-y += gp7101_bl.o 即可。
接下来就是 gp7101_bl.c驱动编写 ,如下为IIC驱动框架。
#include "linux/stddef.h"
#include <linux/kernel.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/input/mt.h>
#include <linux/random.h>
#if 1
#define MY_DEBUG(fmt,arg...) printk("gp7101_bl:%s %d "fmt"",__FUNCTION__,__LINE__,##arg);
#else
#define MY_DEBUG(fmt,arg...)
#endif
#define BACKLIGHT_NAME "gp7101-backlight"
static int gp7101_bl_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
MY_DEBUG("locat");
return 0;
}
static int gp7101_bl_remove(struct i2c_client *client)
{
MY_DEBUG("locat");
return 0;
}
static const struct of_device_id gp7101_bl_of_match[] = {
{ .compatible = BACKLIGHT_NAME, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, gp7101_bl_of_match);
static struct i2c_driver gp7101_bl_driver = {
.probe = gp7101_bl_probe,
.remove = gp7101_bl_remove,
.driver = {
.name = BACKLIGHT_NAME,
.of_match_table = of_match_ptr(gp7101_bl_of_match),
},
};
static int __init my_init(void)
{
MY_DEBUG("locat");
return i2c_add_driver(&gp7101_bl_driver);
}
static void __exit my_exit(void)
{
MY_DEBUG("locat");
i2c_del_driver(&gp7101_bl_driver);
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My touch driver");
MODULE_AUTHOR("wucaicheng@qq.com");
因为驱动过程中会有很多参数,我们不可能创建全局变量去保存他们,在linux驱动中一般都是通过创建一个结构体来保存驱动相关的参数,所以这里我创建一个gp7101_backlight_data
结构体。
/* 背光控制器设备数据结构 */
struct gp7101_backlight_data {
/* 指向一个i2c_client结构体的指针*/
struct i2c_client *client;
/*......其他成员后面有用到再添加........*/
};
当驱动中of_match_table = of_match_ptr(gp7101_bl_of_match)
和设备树匹配成功以后会执行探针函数,探针函数中我们会去初始化驱动。
// gp7101_bl_probe - 探测函数,当I2C总线上的设备与驱动匹配时会被调用
static int gp7101_bl_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct backlight_device *bl; // backlight_device结构用于表示背光设备
struct gp7101_backlight_data *data; // 自定义的背光数据结构
struct backlight_properties props; // 背光设备的属性
struct device_node *np = client->dev.of_node; // 设备树中的节点
MY_DEBUG("locat"); // 打印调试信息
// 为背光数据结构动态分配内存
data = devm_kzalloc(&client->dev, sizeof(struct gp7101_backlight_data), GFP_KERNEL);
if (data == NULL){
dev_err(&client->dev, "Alloc GFP_KERNEL memory failed."); // 内存分配失败,打印错误信息
return -ENOMEM; // 返回内存分配错误码
}
// 初始化背光属性结构
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW; // 设置背光类型为原始类型
props.max_brightness = 255; // 设置最大亮度为255
// 从设备树中读取最大亮度级别
of_property_read_u32(np, "max-brightness-levels", &props.max_brightness);
// 从设备树中读取默认亮度级别
of_property_read_u32(np, "default-brightness-level", &props.brightness);
// 确保亮度值在有效范围内
if(props.max_brightness>255 || props.max_brightness<0){
props.max_brightness = 255;
}
if(props.brightness>props.max_brightness || props.brightness<0){
props.brightness = props.max_brightness;
}
// 注册背光设备
bl = devm_backlight_device_register(&client->dev, "backlight", &client->dev, data, &gp7101_backlight_ops,&props);
if (IS_ERR(bl)) {
dev_err(&client->dev, "failed to register backlight device\n"); // 注册失败,打印错误信息
return PTR_ERR(bl); // 返回错误码
}
data->client = client; // 保存i2c_client指针
i2c_set_clientdata(client, data); // 设置i2c_client的客户端数据
MY_DEBUG("max_brightness:%d brightness:%d",props.max_brightness, props.brightness); // 打印最大亮度和当前亮度
backlight_update_status(bl); // 更新背光设备的状态
return 0; // 返回成功
}
devm_backlight_device_register
这个函数非常重要,他是 Linux 内核中用于动态注册背光设备的一个函数。前缀带devm
的一般都会在设备被销毁时自动释放相关资源,无需手动调用 backlight_device_unregister。
这个函数的主要作用是创建并注册一个 backlight_device
实例,这个实例代表了系统中的一个背光设备。背光设备通常用于控制显示屏的亮度。函数原型如下:
struct backlight_device *devm_backlight_device_register(
struct device *dev, const char *name, struct device *parent,
void *devdata, const struct backlight_ops *ops,
const struct backlight_properties *props);
参数说明:
-
dev
:指向父设备的指针,通常是一个struct i2c_client
或struct platform_device
。 -
name
:背光设备的名称。 -
parent
:背光设备的父设备,通常与dev
参数相同。 -
devdata
:私有数据,会被传递给背光操作函数。 -
ops
:指向backlight_ops
结构的指针,这个结构定义了背光设备的行为,包括设置亮度、获取亮度等操作。 -
props
:指向backlight_properties
结构的指针,这个结构包含了背光设备的属性,如最大亮度、当前亮度等。
ops
参数非常重要,因为我们就是通过这个参数指向的结构成员中的函数去实现获取背光更新背光的。函数的原型如下:
struct backlight_ops {
unsigned int options;
#define BL_CORE_SUSPENDRESUME (1 << 0)
/* Notify the backlight driver some property has changed */
int (*update_status)(struct backlight_device *);
/* Return the current backlight brightness (accounting for power,
fb_blank etc.) */
int (*get_brightness)(struct backlight_device *);
/* Check if given framebuffer device is the one bound to this backlight;
return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */
int (*check_fb)(struct backlight_device *, struct fb_info *);
};
通过backlight_ops
定义了一个名为gp7101_backlight_ops
的backlight_ops
结构体实例,并且只初始化了.update_status
成员,它指向了一个名为gp7101_backlight_set
的函数,这个函数负责更新背光设备的亮度状态。
static struct backlight_ops gp7101_backlight_ops = {
.update_status = gp7101_backlight_set,
};
这就是我们更新背光的核心函数了,每次背光被改动的时候系统都会回调这个函数,在函数中我们通过I2C1去写GP7101实现修改背光。 GP7101两种操作方法第一种是8位PWM,第二种是16位数PWM,刚好我们背光是从0~255所以,我们就选择8位PWM,八位PWM模式需要写寄存器0x03。
/* I2C 背光控制器寄存器定义 */
#define BACKLIGHT_REG_CTRL_8 0x03
#define BACKLIGHT_REG_CTRL_16 0x02
/* 设置背光亮度 */
static int gp7101_backlight_set(struct backlight_device *bl)
{
struct gp7101_backlight_data *data = bl_get_data(bl); // 获取背光数据结构指针
struct i2c_client *client = data->client; // 获取I2C设备指针
u8 addr[1] = {BACKLIGHT_REG_CTRL_8}; // 定义I2C地址数组
u8 buf[1] = {bl->props.brightness}; // 定义数据缓冲区,用于存储背光亮度值
MY_DEBUG("pwm:%d", bl->props.brightness); // 输出背光亮度值
// 将背光亮度值写入设备
i2c_write(client, addr, sizeof(addr), buf, sizeof(buf));
return 0; // 返回成功
}
i2c_write函数
s32 i2c_write(struct i2c_client *client, u8 *addr, u8 addr_len, u8 *buf, s32 len)
{
struct i2c_msg msg; // 定义i2c消息结构,用于传输数据
s32 ret = -1; // 初始化返回值为-1,表示失败
u8 *temp_buf; // 定义临时缓冲区指针
msg.flags = !I2C_M_RD; // 标志位,表示写操作
msg.addr = client->addr; // 设备地址
msg.len = len + addr_len; // 写入数据的总长度(地址长度+数据长度)
// 分配临时缓冲区
temp_buf = kzalloc(msg.len, GFP_KERNEL);
if (!temp_buf) {
goto error; // 如果分配失败,跳转到错误处理
}
// 装填地址到临时缓冲区
memcpy(temp_buf, addr, addr_len);
// 装填数据到临时缓冲区(紧随地址之后)
memcpy(temp_buf + addr_len, buf, len);
msg.buf = temp_buf; // 设置消息的缓冲区为临时缓冲区
// 发送消息并写入数据
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret == 1) {
kfree(temp_buf); // 释放临时缓冲区
return 0; // 如果消息成功传输,返回0表示成功
}
error:
// 如果写入失败,打印错误信息
if (addr_len == 2) {
MY_DEBUG("I2C Write: 0x%04X, %d bytes failed, errcode: %d! Process reset.", (((u16)(addr[0] << 8)) | addr[1]), len, ret);
} else {
MY_DEBUG("I2C Write: 0x%02X, %d bytes failed, errcode: %d! Process reset.", addr[0], len, ret);
}
if (temp_buf) {
kfree(temp_buf); // 释放临时缓冲区
}
return -1; // 返回-1表示失败
}
屏蔽原有背光设备树节点。
/ {
/*backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm5 0 25000 0>;
brightness-levels = <
0 20 20 21 21 22 22 23
23 24 24 25 25 26 26 27
27 28 28 29 29 30 30 31
31 32 32 33 33 34 34 35
35 36 36 37 37 38 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71
72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87
88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103
104 105 106 107 108 109 110 111
112 113 114 115 116 117 118 119
120 121 122 123 124 125 126 127
128 129 130 131 132 133 134 135
136 137 138 139 140 141 142 143
144 145 146 147 148 149 150 151
152 153 154 155 156 157 158 159
160 161 162 163 164 165 166 167
168 169 170 171 172 173 174 175
176 177 178 179 180 181 182 183
184 185 186 187 188 189 190 191
192 193 194 195 196 197 198 199
200 201 202 203 204 205 206 207
208 209 210 211 212 213 214 215
216 217 218 219 220 221 222 223
224 225 226 227 228 229 230 231
232 233 234 235 236 237 238 239
240 241 242 243 244 245 246 247
248 249 250 251 252 253 254 255
>;
default-brightness-level = <255>;
};*/
};
在dsi1中也需要屏蔽掉否则找不到引用节点编译时候会报错。
&dsi1 {
status = "okay";
rockchip,lane-rate = <1000>;
dsi1_panel: panel@0 {
/*省略*/
// backlight = <&backlight>;
/*省略*/
};
};
1.3.屏幕参数设配
tspi-rk3566-dsi-v10.dtsi配置
-
修改lanes数
3.1寸屏幕硬件上只用了2lanes的差分对,设备树中默认配置的是4lanes所以我们需要把lanes修改为2。
dsi,lanes = <4>;
改为
dsi,lanes = <2>;
-
配置初始化序列
panel-init-sequence = [
// init code
05 78 01 01
05 78 01 11
39 00 06 FF 77 01 00 00 11
15 00 02 D1 11
15 00 02 55 B0 // 80 90 b0
39 00 06 FF 77 01 00 00 10
39 00 03 C0 63 00
39 00 03 C1 09 02
39 00 03 C2 37 08
15 00 02 C7 00 // x-dir rotate 0:0x00,rotate 180:0x04
15 00 02 CC 38
39 00 11 B0 00 11 19 0C 10 06 07 0A 09 22 04 10 0E 28 30 1C
39 00 11 B1 00 12 19 0D 10 04 06 07 08 23 04 12 11 28 30 1C
39 00 06 FF 77 01 00 00 11 // enable bk fun of command 2 BK1
15 00 02 B0 4D
15 00 02 B1 60 // 0x56 0x4a 0x5b
15 00 02 B2 07
15 00 02 B3 80
15 00 02 B5 47
15 00 02 B7 8A
15 00 02 B8 21
15 00 02 C1 78
15 00 02 C2 78
15 64 02 D0 88
39 00 04 E0 00 00 02
39 00 0C E1 01 A0 03 A0 02 A0 04 A0 00 44 44
39 00 0D E2 00 00 00 00 00 00 00 00 00 00 00 00
39 00 05 E3 00 00 33 33
39 00 03 E4 44 44
39 00 11 E5 01 26 A0 A0 03 28 A0 A0 05 2A A0 A0 07 2C A0 A0
39 00 05 E6 00 00 33 33
39 00 03 E7 44 44
39 00 11 E8 02 26 A0 A0 04 28 A0 A0 06 2A A0 A0 08 2C A0 A0
39 00 08 EB 00 01 E4 E4 44 00 40
39 00 11 ED FF F7 65 4F 0B A1 CF FF FF FC 1A B0 F4 56 7F FF
39 00 06 FF 77 01 00 00 00
15 00 02 36 00 //U&D Y-DIR rotate 0:0x00,rotate 180:0x10
15 00 02 3A 55
05 78 01 11
05 14 01 29
];
-
配置屏幕时序
disp_timings1: display-timings {
native-mode = <&dsi1_timing0>;
dsi1_timing0: timing0 {
clock-frequency = <27000000>;
hactive = <480>; //与 LCDTiming.LCDH 对应
vactive = <800>; //与 LCDTiming.LCDV 对应
hfront-porch = <32>; //与 LCDTiming.HFPD 对应
hsync-len = <4>; //与 LCDTiming.HSPW 对应
hback-porch = <32>; //与 LCDTiming.HBPD 对应
vfront-porch = <9>; //与 LCDTiming.VEPD 对应
vsync-len = <4>; //与 LCDTiming.VsPW 对应
vback-porch = <3>; //与 LCDTiming.VBPD 对应
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
};
};
1.4.触摸屏驱动
配置i2c1设备树
&i2c1 {
status = "okay"; // 表示这个i2c1设备是可用的
clock-frequency = <400000>; // 设置i2c1的时钟频率为400kHz
myts@38 { // 定义一个i2c设备,设备地址为0x38,设备名称为myts
compatible = "my,touch"; // 表示这个设备是触摸屏设备,驱动名称为my,touch
reg = <0x38>; // i2c设备地址
tp-size = <89>; // 触摸屏的大小
max-x = <480>; // 触摸屏支持的最大X坐标值
max-y = <800>; // 触摸屏支持的最大Y坐标值
touch-gpio = <&gpio1 RK_PA0 IRQ_TYPE_LEVEL_LOW>; // 触摸屏的触摸中断引脚,连接到gpio1的第0个引脚,触发方式为低电平触发
reset-gpio = <&gpio1 RK_PA1 GPIO_ACTIVE_HIGH>; // 触摸屏的复位引脚,连接到gpio1的第1个引脚,有效电平为高电平
};
/****省略****/
};
也是利用 obj-y += my_touch.o 去编译。
my_touch.c驱动
static int my_touch_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
return 0;
}
static int my_touch_ts_remove(struct i2c_client *client)
{
MY_DEBUG("locat");
return 0;
}
static const struct of_device_id my_touch_of_match[] = {
{ .compatible = "my,touch", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_touch_of_match);
static struct i2c_driver my_touch_ts_driver = {
.probe = my_touch_ts_probe,
.remove = my_touch_ts_remove,
.driver = {
.name = "my-touch",
.of_match_table = of_match_ptr(my_touch_of_match),
},
};
static int __init my_ts_init(void)
{
MY_DEBUG("locat");
return i2c_add_driver(&my_touch_ts_driver);
}
static void __exit my_ts_exit(void)
{
MY_DEBUG("locat");
i2c_del_driver(&my_touch_ts_driver);
}
module_init(my_ts_init);
module_exit(my_ts_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My touch driver");
MODULE_AUTHOR("wucaicheng@qq.com");
驱动中的结构体
// 定义一个表示触摸设备的结构体
struct my_touch_dev {
struct i2c_client *client; // 指向与触摸设备通信的 I2C 客户端结构体的指针
struct input_dev *input_dev; // 指向与输入设备关联的 input_dev 结构体的指针,用于处理输入事件
int rst_pin; // 触摸设备的复位引脚编号
int irq_pin; // 触摸设备的中断引脚编号
u32 abs_x_max; // 触摸设备在 X 轴上的最大绝对值
u32 abs_y_max; // 触摸设备在 Y 轴上的最大绝对值
int irq; // 触摸设备的中断号
};
当驱动中of_match_table = of_match_ptr(my_touch_of_match)
和设备树匹配成功以后会执行探针函数,探针函数中我们会去初始化驱动。
static int my_touch_ts_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret; // 定义一个返回值变量
struct my_touch_dev *ts; // 定义一个结构体指针,用来指向my_touch_dev结构体
struct device_node *np = client->dev.of_node; // 获取设备节点
// 打印调试信息
MY_DEBUG("locat"); // 调用MY_DEBUG函数打印调试信息,此处打印"locat"
ts = devm_kzalloc(&client->dev, sizeof(*ts), GFP_KERNEL); // 使用devm_kzalloc分配内存,减少内存申请操作
if (ts == NULL){ // 检查内存分配是否成功
dev_err(&client->dev, "Alloc GFP_KERNEL memory failed."); // 内存分配失败,打印错误信息
return -ENOMEM; // 返回内存申请错误的码
}
ts->client = client; // 触摸屏设备的客户端指针指向i2c_client结构体
i2c_set_clientdata(client, ts); // 将my_touch_dev结构体的指针设置为i2c客户端的数据
// 从设备树中读取触摸屏的最大X和Y值
if (of_property_read_u32(np, "max-x", &ts->abs_x_max)) {
dev_err(&client->dev, "no max-x defined\n"); // 如果读取最大X值失败,打印错误信息
return -EINVAL; // 返回参数无效的错误码
}
MY_DEBUG("abs_x_max:%d",ts->abs_x_max); // 打印X值
if (of_property_read_u32(np, "max-y", &ts->abs_y_max)) {
dev_err(&client->dev, "no max-y defined\n"); // 如果读取最大Y值失败,打印错误信息
return -EINVAL; // 返回参数无效的错误码
}
MY_DEBUG("abs_x_max:%d",ts->abs_y_max); // 打印Y值
// 获取并请求复位GPIO管脚
ts->rst_pin = of_get_named_gpio(np, "reset-gpio", 0); // 从设备树中获取复位管脚
ret = devm_gpio_request(&client->dev,ts->rst_pin,"my touch touch gpio"); // 请求使用复位管脚
if (ret < 0){ // 如果请求失败
dev_err(&client->dev, "gpio request failed."); // 打印错误信息
return -ENOMEM; // 返回内存申请错误的码
}
ts->irq_pin = of_get_named_gpio(np, "touch-gpio", 0); // 从设备树中获取中断管脚
ret = devm_gpio_request_one(&client->dev, ts->irq_pin, // 请求使用中断管脚
GPIOF_IN, "my touch touch gpio");
if (ret < 0)
return ret; // 如果请求失败,直接返回错误码
// 复位触摸屏设备
gpio_direction_output(ts->rst_pin,0); // 设置复位管脚输出低电平
msleep(20); // 等待20毫秒
gpio_direction_output(ts->irq_pin,0); // 设置中断管脚输出低电平
msleep(2); // 等待2毫秒
gpio_direction_output(ts->rst_pin,1); // 设置复位管脚输出高电平
msleep(6); // 等待6毫秒
gpio_direction_output(ts->irq_pin, 0); // 设置中断管脚输出低电平
msleep(50); // 等待50毫秒
// 申请中断服务
ts->irq = gpio_to_irq(ts->irq_pin); // 将GPIO管脚转换为中断号
if(ts->irq){ // 检查中断号是否有效
ret = devm_request_threaded_irq(&(client->dev), ts->irq, // 请求线程化中断
NULL, my_touch_irq_handler, // 中断服务函数
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, // 中断触发方式为下降沿触发,且只触发一次
client->name, ts);
if (ret != 0) {
MY_DEBUG("Cannot allocate ts INT!ERRNO:%d\n", ret); // 如果中断请求失败,打印错误信息
return ret; // 返回错误码
}
}
// 使用devm_input_allocate_device分配输入设备对象
ts->input_dev = devm_input_allocate_device(&client->dev);
if (!ts->input_dev) { // 检查输入设备对象是否分配成功
dev_err(&client->dev, "Failed to allocate input device.\n"); // 打印错误信息
return -ENOMEM; // 返回内存申请错误的码
}
// 设置输入设备的名称
ts->input_dev->name = "my touch screen";
// 设置输入设备的总线类型为I2C
ts->input_dev->id.bustype = BUS_I2C;
// 设置X轴的最大值为480
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, 480, 0, 0);
// 设置Y轴的最大值为800
input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, 800, 0, 0);
// 初始化5个多点触摸槽位,直接模式
ret = input_mt_init_slots(ts->input_dev, 5, INPUT_MT_DIRECT);
if (ret) {
dev_err(&client->dev, "Input mt init error\n"); // 打印错误信息
return ret; // 返回错误码
}
// 注册输入设备
ret = input_register_device(ts->input_dev); // 注册输入设备
if (ret)
return ret; // 返回错误码
// 读取版本号
gt9271_read_version(client);
return 0; // 如果一切顺利,返回0
}
读取触摸数据有很多方法比如轮询但是这样效率太低了,所以我们这里通过中断方式实现触摸数据读取,当屏幕被触控时,屏幕会通过irq引脚输出中断信号。
devm_request_threaded_irq(&(client->dev), ts->irq, // 请求线程化中断
NULL, my_touch_irq_handler, // 中断服务函数
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, // 中断触发方式为下降沿触发,且只触发一次
client->name, ts);
上面中断以后当屏幕被触摸会触发中断线程服务函数my_touch_irq_handler
,在这个函数里面我们通过i2c去读取屏幕相关的参数。
static irqreturn_t my_touch_irq_handler(int irq, void *dev_id)
{
s32 ret = -1; // 定义一个返回值,初始化为-1
struct my_touch_dev *ts = dev_id; // 获取指向设备数据的指针
u8 addr[1] = {0x02}; // 定义一个寄存器地址数组对应数据手册TD_STATUS
u8 point_data[1+6*5]={0}; // 定义一个点数据数组,预留足够空间 for 5 touch points
u8 touch_num = 0; // 定义一个变量来存储触摸点的数量
u8 *touch_data; // 定义一个指针用于指向点数据
int i = 0; // 定义一个循环变量
int event_flag, touch_id, input_x, input_y; // 定义一些用于存储事件信息的变量
MY_DEBUG("irq"); // 打印中断信息
ret = my_touch_i2c_read(ts->client, addr, sizeof(addr), point_data, sizeof(point_data)); // 尝试读取触摸屏设备的数据
if (ret < 0){ // 如果读取失败
MY_DEBUG("I2C write end_cmd error!"); // 打印错误信息
}
touch_num = point_data[0]&0x0f; // 获取触摸点的数量
MY_DEBUG("touch_num:%d",touch_num); // 打印触摸点数量
// 遍历触摸点数据
for(i=0; i<5; i++){
// 获取触摸点数据
touch_data = &point_data[1+6*i];
/*
解析触摸点的事件标志位
00b: 按下
01b: 抬起
10b: 接触
11b: 保留
*/
event_flag = touch_data[0] >> 6;
if(event_flag == 0x03)continue; // 如果事件标志位不是按下或抬起,则跳过此循环
touch_id = touch_data[2] >> 4; // 获取触摸点ID
MY_DEBUG("i:%d touch_id:%d event_flag:%d",i,touch_id,event_flag); // 打印调试信息
input_x = ((touch_data[0]&0x0f)<<8) | touch_data[1]; // 计算X坐标
input_y = ((touch_data[2]&0x0f)<<8) | touch_data[3]; // 计算Y坐标
// MY_SWAP(input_x,input_y); // 如果需要交换X和Y坐标,可以取消注释此行
MY_DEBUG("i:%d,x:%d,y:%d",i,input_x,input_y); // 打印调试信息
// 设置输入设备的触摸槽位
input_mt_slot(ts->input_dev, touch_id);
if(event_flag == 0){ // 如果是按下
// 上报按下事件和坐标
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); // 设置为按下状态
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, 480-input_x); // 报告X坐标
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); // 报告Y坐标
}else if (event_flag == 2){ // 如果是长按
// 直接上报数据
input_report_abs(ts->input_dev, ABS_MT_POSITION_X, 480-input_x); // 报告X坐标
input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); // 报告Y坐标
else if(event_flag == 1){ // 如果是触摸抬起
// 上报事件
input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, false); // 设置为抬起状态
}
}
// 报告输入设备的指针仿真信息
input_mt_report_pointer_emulation(ts->input_dev, true);
// 同步输入事件
input_sync(ts->input_dev);
// 返回IRQ_HANDLED,表示中断已经被处理
return IRQ_HANDLED;
}
读取数据从TD_STATUS开始,一个触摸点包含6个数据分别是TOUCH1_XH、TOUCH1_XL、TOUCH1_YH、TOUCH1_YL、TOUCH1_WEIGHT,共计支持5点触控,所以连续读取的长度为1(TD_STATUS)+6(数据)*5。
ret = my_touch_i2c_read(ts->client, addr, sizeof(addr), point_data, sizeof(point_data));
2.Android系统开发
修改屏幕密度
# 修改密度这里是240 PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=240
系统添加屏幕旋转
diff --git a/rk3566_tspi/BoardConfig.mk b/rk3566_tspi/BoardConfig.mk index 4f702c3..bbad87a 100755 --- a/rk3566_tspi/BoardConfig.mk +++ b/rk3566_tspi/BoardConfig.mk @@ -37,3 +37,6 @@ ifeq ($(strip $(BOARD_USES_AB_IMAGE)), true) include device/rockchip/common/BoardConfig_AB.mk TARGET_RECOVERY_FSTAB := device/rockchip/rk356x/rk3566_tspi/recovery.fstab_AB endif +TARGET_RECOVERY_DEFAULT_ROTATION := ROTATION_RIGHT +SF_PRIMARY_DISPLAY_ORIENTATION := 90
修改触摸方向
diff --git a/my_touch.c b/my_touch.c index 9acab2d..608914a 100755 --- a/my_touch.c +++ b/my_touch.c @@ -143,7 +143,7 @@ static irqreturn_t my_touch_irq_handler(int irq, void *dev_id) input_x = ((touch_data[0]&0x0f)<<8) | touch_data[1]; input_y = ((touch_data[2]&0x0f)<<8) | touch_data[3]; - // MY_SWAP(input_x,input_y); + MY_SWAP(input_x,input_y); MY_DEBUG("i:%d,x:%d,y:%d",i,input_x,input_y); // 设定输入设备的触摸槽位 input_mt_slot(ts->input_dev, touch_id); @@ -151,11 +151,11 @@ static irqreturn_t my_touch_irq_handler(int irq, void *dev_id) if(event_flag == 0){ // 如果是按下上报按下和坐标 input_mt_report_slot_state(ts->input_dev, MT_TOOL_FINGER, true); - input_report_abs(ts->input_dev, ABS_MT_POSITION_X, 480-input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); }else if (event_flag == 2){ // 如果是长按直接上报数据 - input_report_abs(ts->input_dev, ABS_MT_POSITION_X, 480-input_x); + input_report_abs(ts->input_dev, ABS_MT_POSITION_X, input_x); input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, input_y); }else if(event_flag == 1){ // 触摸抬起,上报事件 @@ -277,8 +277,8 @@ static int my_touch_ts_probe(struct i2c_client *client, /*设置触摸 x 和 y 的最大值*/ // 设置输入设备的绝对位置参数 - input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, 480, 0, 0); - input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, 800, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, 800, 0, 0); + input_set_abs_params(ts->input_dev, ABS_MT_POSITION_Y, 0, 480, 0, 0); // 初始化多点触摸设备的槽位 ret = input_mt_init_slots(ts->input_dev, 5, INPUT_MT_DIRECT);
三、外壳制作
2024.11.11.15.04待更新