嵌入式硬件实战提升篇(一)-泰山派RK3566制作多功能小手机

news/2024/11/15 11:30:06/

引言:主要针对于嵌入式全栈内容的知识点汇总并对于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目录用来存放Makefilegp7101_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_clientstruct 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_opsbacklight_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  2323  24  24  25  25  26  26  2727  28  28  29  29  30  30  3131  32  32  33  33  34  34  3535  36  36  37  37  38  38  3940  41  42  43  44  45  46  4748  49  50  51  52  53  54  5556  57  58  59  60  61  62  6364  65  66  67  68  69  70  7172  73  74  75  76  77  78  7980  81  82  83  84  85  86  8788  89  90  91  92  93  94  9596  97  98  99 100 101 102 103104 105 106 107 108 109 110 111112 113 114 115 116 117 118 119120 121 122 123 124 125 126 127128 129 130 131 132 133 134 135136 137 138 139 140 141 142 143144 145 146 147 148 149 150 151152 153 154 155 156 157 158 159160 161 162 163 164 165 166 167168 169 170 171 172 173 174 175176 177 178 179 180 181 182 183184 185 186 187 188 189 190 191192 193 194 195 196 197 198 199200 201 202 203 204 205 206 207208 209 210 211 212 213 214 215216 217 218 219 220 221 222 223224 225 226 227 228 229 230 231232 233 234 235 236 237 238 239240 241 242 243 244 245 246 247248 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 code05 78 01 0105 78 01 1139 00 06 FF 77 01 00 00 1115 00 02 D1 1115 00 02 55 B0 // 80 90 b039 00 06 FF 77 01 00 00 1039 00 03 C0 63 0039 00 03 C1 09 0239 00 03 C2 37 0815 00 02 C7 00 // x-dir rotate 0:0x00,rotate 180:0x0415 00 02 CC 3839 00 11 B0 00 11 19 0C 10 06 07 0A 09 22 04 10 0E 28 30 1C39 00 11 B1 00 12 19 0D 10 04 06 07 08 23 04 12 11 28 30 1C39 00 06 FF 77 01 00 00 11 // enable  bk fun of  command 2  BK115 00 02 B0 4D15 00 02 B1 60 // 0x56  0x4a  0x5b15 00 02 B2 0715 00 02 B3 8015 00 02 B5 4715 00 02 B7 8A15 00 02 B8 2115 00 02 C1 7815 00 02 C2 7815 64 02 D0 8839 00 04 E0 00 00 0239 00 0C E1 01 A0 03 A0 02 A0 04 A0 00 44 4439 00 0D E2 00 00 00 00 00 00 00 00 00 00 00 0039 00 05 E3 00 00 33 3339 00 03 E4 44 4439 00 11 E5 01 26 A0 A0 03 28 A0 A0 05 2A A0 A0 07 2C A0 A039 00 05 E6 00 00 33 3339 00 03 E7 44 4439 00 11 E8 02 26 A0 A0 04 28 A0 A0 06 2A A0 A0 08 2C A0 A039 00 08 EB 00 01 E4 E4 44 00 4039 00 11 ED FF F7 65 4F 0B A1 CF FF FF FC 1A B0 F4 56 7F FF39 00 06 FF 77 01 00 00 0015 00 02 36 00 //U&D  Y-DIR rotate 0:0x00,rotate 180:0x1015 00 02 3A 5505 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的时钟频率为400kHzmyts@38 {                    // 定义一个i2c设备,设备地址为0x38,设备名称为mytscompatible = "my,touch"; // 表示这个设备是触摸屏设备,驱动名称为my,touchreg = <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"; // 设置输入设备的总线类型为I2Cts->input_dev->id.bustype = BUS_I2C; // 设置X轴的最大值为480input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, 480, 0, 0); // 设置Y轴的最大值为800input_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;                        // 定义一个返回值,初始化为-1struct my_touch_dev *ts = dev_id;    // 获取指向设备数据的指针u8 addr[1] = {0x02};                 // 定义一个寄存器地址数组对应数据手册TD_STATUS u8 point_data[1+6*5]={0};            // 定义一个点数据数组,预留足够空间 for 5 touch pointsu8 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;    // 获取触摸点IDMY_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待更新


http://www.ppmy.cn/news/1547187.html

相关文章

wordpress functions文件的作用及详细说明

WordPress的functions.php文件是一个非常重要的主题文件&#xff0c;它允许开发者和用户向网站添加自定义代码片段&#xff0c;从而修改网站功能或添加新内容。以下是functions.php文件的主要作用和一些详细说明&#xff1a; 1. 自定义功能添加&#xff1a; functions.php文件…

什么是python爬虫?

今天就来给大家介绍一下什么是python爬虫。 Python爬虫是一种自动化程序&#xff0c;用于在互联网上浏览和提取信息。它通过模拟人类用户访问网页的行为&#xff0c;发送HTTP请求&#xff0c;获取网页内容&#xff0c;然后解析这些内容以提取所需数据 。以下是关于Python爬虫…

初级数据结构——栈

目录 前言一、栈的基本概念二、栈的实现方式三、栈的性能分析四、栈的应用场景五、栈的变体六、出栈入栈的动态图解七、代码模版八、总结结语 前言 数据结构栈&#xff08;Stack&#xff09;是一种线性的数据结构&#xff0c;它只允许在序列的一端&#xff08;称为栈顶&#x…

第八章 利用CSS制作导航栏

8.1 水平顶部导航栏 8.1.1 简单水平导航栏的设计与实现 1. 响应式设计 介绍&#xff1a; 响应式设计是指网页能够根据不同设备屏幕尺寸和分辨率自适应调整布局和样式&#xff0c;以确保在手机、平板和桌面等设备上都能提供良好的用户体验。 CSS实现&#xff1a; media (ma…

NoSQL数据库与关系型数据库的主要区别

NoSQL数据库与关系型数据库在多个方面存在显著区别&#xff0c;以下是对这些主要区别的详细描述&#xff1a; 一、数据存储模型 关系型数据库&#xff1a;使用表格形式存储数据&#xff0c;每个表格由行和列组成&#xff0c;行表示记录&#xff0c;列表示字段。数据之间的关系…

PVE纵览-安装系统卡“Loading Driver”的快速解决方案

PVE纵览-安装系统卡“Loading Driver”的快速解决方案 文章目录 PVE纵览-安装系统卡“Loading Driver”的快速解决方案摘要通过引导参数解决PVE安装卡在“Loading Driver”问题官方解决方法 关键字&#xff1a; PVE、 显卡、 Loading、 Driver、 nomodeset 摘要 在虚拟机…

freemarker 读取template.xml ,通过response 输出文件,解决中文乱码问题

采用 try (Writer writer new OutputStreamWriter(os, “UTF-8”)) UTF-8 内容转换 public static void setResponseHeader(HttpServletResponse response, String fileName) {try {// fileName "中文.xls";try {fileName new String(fileName.getBytes(),"…

jupyter+pycharm内部直接运行

第一步&#xff1a;终端使用conda&#xff0c;切换到目标环境&#xff0c;在该虚拟环境下 下载jupyter支持包&#xff08;pip install jupyter&#xff09; 第二步&#xff1a;解决root用户不能直接运行的问题 创建配置文件 jupyter notebook --generate-config 修改…