嵌入式硬件实战提升篇(一)-泰山派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目录用来存放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.mzph.cn/pingmian/60613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

玩转ChatGPT:文献阅读 v2.0

一、写在前面 好久不更新咯。 因为最近ChatGPT更新了不少功能&#xff08;水一篇刷存在感&#xff09;&#xff1a; 上线ChatGPT-4o模型&#xff0c;说推理能力还不错&#xff1b;上线联网功能&#xff0c;类似Kimi那种。 所以呢&#xff0c;用它来读文献就挺舒服的了。例如…

游戏引擎中LOD渲染技术

一.LOD(Level Of Detail) 为了降低GPU渲染压力,根据摄像机距离模型距离将面数较高的模型替换为面数较低的模型. LOD LOD0(distance<10) LOD1(distance<20) LOD2(distance<30) 故通常引擎中MetaMesh是由一个或多个LOD模型构成. MetaMesh mesh mesh.lod1 mesh.lod…

高性能分布式缓存Redis-分布式锁与布隆过滤器

一、分布式锁 我们先来看一下本地锁 在并发编程中&#xff0c;我们通过锁&#xff0c;来避免由于竞争而造成的数据不一致问题。通常&#xff0c;我们以 synchronized 、Lock 来使用它&#xff08;单机情况&#xff09; 来看这段代码 Autowired RedisTemplate<String,Str…

在启动 Spring Boot 项目时,报找不到 slf4j 的错误

而且 tomcat 的启动信息不知道为什么输出出来了 问 AI 得到的解决方案&#xff1a; 将 pom.xml 中的如下配置替换成这样&#xff0c;排除这个插件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring - boot - starter - …

C/C++ YUV 文件叠加自定义符号

一、前言 需要在图片文件上叠加文字&#xff0c;但是要在4M内存开发板上实现&#xff0c;实际内存不足1M&#xff0c;怎么实现&#xff1f;这个问题在网上查找的解决方案都需要使用第三方库文件&#xff0c;下载文字图像库&#xff0c;但是此开发板不能承受住这么大的内存&…

Android Studio 学习——整体框架和概念

一、创建新项目 选择File-->New-->New Project 这里可以随便选&#xff0c;我一般选择Empty Activity&#xff0c;不同的选择&#xff0c;只是界面不同而已。然后静静的等待安装就可以了 二、框架结构 1&#xff09;manifests manifests文件是一个XML文件&#xff0c;…

「JVM详解」

JVM JVM概述 基本介绍 JVM&#xff1a;全称 Java Virtual Machine&#xff0c;即 Java 虚拟机&#xff0c;一种规范&#xff0c;本身是一个虚拟计算机&#xff0c;直接和操作系统进行交互&#xff0c;与硬件不直接交互&#xff0c;而操作系统可以帮我们完成和硬件进行交互的…

Serverless架构在实时数据处理中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 Serverless架构在实时数据处理中的应用 Serverless架构在实时数据处理中的应用 Serverless架构在实时数据处理中的应用 引言 Ser…

【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏

Android 标题栏 参考 Android Studio版本 配置gradle镜像 阿里云 Android使用 android:theme 显示标题栏 添加依赖 dependencies {implementation("androidx.appcompat:appcompat:1.6.1")implementation("com.google.android.material:material:1.9.0")…

pytorch量化训练

训练时量化&#xff08;Quantization-aware Training, QAT&#xff09;是一种在模型训练过程中&#xff0c;通过模拟低精度量化效应来增强模型对量化操作的鲁棒性的技术。与后训练量化不同&#xff0c;QAT 允许模型在训练过程中考虑到量化引入的误差&#xff0c;从而在实际部署…

使用Java绘制图片边框,解决微信小程序map组件中marker与label层级关系问题,label增加外边框后显示不能置与marker上面

今天上线的时候发现系统不同显示好像不一样&#xff0c;苹果手机打开的时候是正常的&#xff0c;但是一旦用安卓手机打开就会出现label不置顶的情况。尝试了很多种办法&#xff0c;也在官方查看了map相关的文档&#xff0c;发现并没有给label设置zIndex的属性&#xff0c;只看到…

Redisson的可重入锁

初始状态&#xff1a; 表示系统或资源在没有线程持有锁的情况下的状态&#xff0c;任何线程都可以尝试获取锁。 线程 1 获得锁&#xff1a; 线程 1 首次获取了锁并进入受保护的代码区域。 线程 1 再次请求锁&#xff1a; 在持有锁的情况下&#xff0c;线程 1 再次请求锁&a…

三周精通FastAPI:37 包含 WSGI - Flask,Django,Pyramid 以及其它

官方文档&#xff1a;https://fastapi.tiangolo.com/zh/advanced/wsgi/ 包含 WSGI - Flask&#xff0c;Django&#xff0c;其它 您可以挂载多个 WSGI 应用&#xff0c;正如您在 Sub Applications - Mounts, Behind a Proxy 中所看到的那样。 为此, 您可以使用 WSGIMiddlewar…

Swagger UI

Swagger UI 是一个开源工具&#xff0c;用于可视化、构建和交互式地探索 RESTful API。 它是 Swagger 生态系统的一部分&#xff0c;Swagger 是一套用于描述、生成、调用和可视化 RESTful Web 服务的工具和规范。 Swagger UI 可以自动生成 API 文档&#xff0c;并提供一个交互…

thinkphp6 --数据库操作 增删改查

一、数据库连接配置 如果是本地测试&#xff0c;它会优先读取 .env 配置&#xff0c;然后再读取 database.php 的配置&#xff1b; 如果禁用了 .env 配置&#xff0c;则会读取数据库连接的默认配置&#xff1a; # .env文件&#xff0c;部署服务器&#xff0c;请禁用我 我们可以…

【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-最大的数

CL13 最大的数(20 分) 输入一个有 n 个无重复元素的整数数组 a&#xff0c;输出数组中最大的数。提示&#xff1a;如使用排序库函数 sort()&#xff0c;需要包含头文件#include 。输入&#xff1a; 第一行是一个正整数 n(2<n<20)&#xff1b; 第二行包含 n 个不重复的整…

让Git走代理

有时候idea提交代码或者从github拉取代码&#xff0c;一直报错超时或者:Recv failure: Connection was reset,下面记录一下怎么让git走代理从而访问到github。 1.打开梯子 2.打开网络和Internet设置 3.设置代理 记住这个地址和端口 4.打开git bash终端 输入以下内容 git c…

vivo 游戏中心包体积优化方案与实践

作者&#xff1a;来自 vivo 互联网大前端团队- Ke Jie 介绍 App 包体积优化的必要性&#xff0c;游戏中心 App 在实际优化过程中的有效措施&#xff0c;包括一些优化建议以及优化思路。 一、包体积优化的必要性 安装包大小与下载转化率的关系大致是成反比的&#xff0c;即安装…

Struts扫盲

Struts扫盲 这里的struts是struts1。以本文记录我的那些复习JavaEE的痛苦并快乐的晚上 Struts是什么 框架的概念想必大家都清楚&#xff0c;框架即“半成品代码”&#xff0c;是为了简化开发而设计的。一个项目有许多分层&#xff0c;拿一个MVC架构的Web应用来说&#xff0c;有…

【AiPPT-注册/登录安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被机器执行自动化程序攻击&#xff0c;存在如下风险&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露&#xff0c;不符合国家等级保护的要求。短信盗刷带来的拒绝服务风险 &#xff0c;造成用户无法登陆、注册&#xff0c;大量收到垃圾短信的…