发作品签到
专业版

基于【立创·泰山派开发板】安卓小手机项目 1045678A

工程标签

1.6k
0
0
0

简介

立创开发板-泰山派,小手机项目

简介:立创开发板-泰山派,小手机项目
立创·泰山派开发板训练营

开源协议

Public Domain

(未经作者授权,禁止转载)
创建时间:2024-02-23 14:15:39更新时间:2024-05-21 02:54:58

描述

文档描述与演示

  • 封面图:验证好的实物图;
  • 描述:完整的工程描述(简介、功能介绍、原理图说明、实物图、pcb设计说明、关键程序说明、重要物料购买、装配说明)
  • 视频:功能演示视频;
  • 附件:工程的源码或相关外壳文件等附件;

简介

采用立创开发板泰山派(RK3566 2G+16G)制作的安卓小手机,屏幕采用3.1寸MIPI液晶显示屏(480*800)



由于屏幕和泰山派线序不同,背光电流不同,所以需要额外画转接板。

泰山派项目详情 (lckfb.com)

1.功能介绍

用aosp编译的安卓版小手机。

mipi协议的液晶屏,i2c的触摸协议。

2.原理图说明

2.1接口转换电路

泰山派使用的mipi线序和我使用的屏线序不一致

2.1.1MIPI接口

2.1.1.1 3.1寸屏MIPI线序

序号3.1寸屏幕引脚功能
1LEDA背光正
2GND地线
3LEDK背光负
4NC无连接
5TE-
6GND地线
7RESET复位
8GND地线
9NC无连接
10GND地线
11IOVCC1.8-3.3V
12GND地线
13VDD电源供应2.8-3.3V
14VDD电源供应2.8-3.3V
15GND地线
16D1P数据线1 正极
17D1N数据线1 负极
18GND地线
19CLKP时钟线 正极
20CLKN时钟线 负极
21GND地线
22D0P数据线0 正极
23D0N数据线0 负极
24GND地线

2.1.1.2 泰山派MIPI接口

泰山派使用的是标准MIPI的31pin接口,但是拓展板上有些引脚并没有用上,所以做了删减。

序号3.1寸屏幕引脚功能
1VCC_3V3
2VCC_3V3
3VCC_3V3
4NC
5MIPI_DSI_RESET
6NC
7GND
8NC
9NC
10GND
11MIPI_DSI_0N
12MIPI_DSI_0P
13GND
14MIPI_DSI_CLKN
15MIPI_DSI_CLKP
16GND
17MIPI_DSI_1N
18MIPI_DSI_1P
19GND
20NC
21NC
22GND
23GND
24MIPI_DSI_VCC_LED-
25MIPI_DSI_VCC_LED-
26MIPI_DSI_VCC_LED-
27MIPI_DSI_VCC_LED-
28NC
29MIPI_DSI_VCC_LED+
30MIPI_DSI_VCC_LED+
31MIPI_DSI_VCC_LED+

2.1.2 触摸接口

3.1寸触摸屏接口使用的是i2C协议与泰山派进行通讯,除了i2C外还有两个比较重要的引脚分别是触摸复位引脚和触摸中断触发引脚

2.1.2.1 3.1寸屏触摸接口

序号3.1寸屏触摸接口功能
1GND地线
2TP_RST触摸复位引脚
3I2C1_SDA_TPi2c数据线
4I2C1_SCL_TPi2c时钟线
5TP_INT_A触摸中断引脚
6VCC_3V33V3电源引脚

2.1.2.2 泰山派触摸接口

序号接口功能
1I2C1_SCL_TPi2c时钟线
2I2C1_SDA_TPi2c数据线
3TP_INT_A触摸中断引脚
4GND地线
5VCC_3V33V3电源引脚
6TP_RST触摸复位引脚

2.2 背光电路

泰山派提供的背光电流过大需要另外设计背光电路。

拓展板设计了背光选择电路、背光驱动电路以及背光调节电路。

2.2.1 背光选择电路

第一路是由泰山派输出,第二路:是板载的背光驱动输出

通过4个0欧姆电阻进行选择

如果贴(R1和R2)不贴(R3和R4)则由泰山派背光电路供电

如果不贴(R1和R2)贴(R3和R4)则由板载背光驱动电路供电。

泰山派上的板子背光驱动电路IOUT=0.2V/R(R=(R95xR96)/(R95+R96)),最终得出IOUT = 0.2V/1.8≈110mA,但我们这款3.1寸屏幕背光电流最大只能支持25mA

所以不贴(R1和R2)贴(R3和R4)由板载背光驱动电路供电。
背光选择.png

2.2.2 背光驱动电路

背光驱动电路主要由SY7201ABC实现,SY7201ABC是一款高效率的LED驱动器,主要用于控制和调节LED灯的亮度。SY7201ABC通过提供恒定电流来确保LED发光的一致性和稳定性,从而提高了LED使用的寿命和效能。

SY7201ABC数据手册:

https://atta.szlcsc.com/upload/public/pdf/source/20160903/1472896628181.pdf

封装与引脚功能说明:

封装与引脚功能说明.png

L1 D1 C1 与SY7201ABC组成 BOOST升压电路(主要是利用电感电容的储能特性 进行升压 (电感电流不能突变,与电容电压不能突变))

R5 R6 为采样电阻与参考电压进行对比恒定输出电流,输出电流的计算公式IOUT=0.2V/R,这里为了使R能够匹配更加精准的值我们并联了两个电阻R5和R6,其中R99为NC不贴,最终的IOUT=0.2V/10Ω=20mA



R7 R8为上下拉电阻默认下拉贴R8,这里两个电阻的作用Linux系统启动到背光驱动加载需要一些时间,也就意味着驱动加载之前这个IO口是不确定的,我们根据需要在没有驱动控制的时候通过上拉或者下拉电阻来决定屏幕背光关闭还是打开。

板载背光.png

2.2.3 背光调节电路

因为泰山派没有PWM引脚引到3.1寸扩展板,但触摸接口有I2C1引到3.1寸扩展屏幕上,I2C是可以挂在多个设备的,所以为了能够实现背光调节功能,通过GP7101一颗I2C转PWM的芯片来实现PWM的调节,GP7101和触摸一起挂到I2C1下。

GP7107数据手册.pdf (szlcsc.com)



image-管脚定义.png

image-板子背光调节.png

2.3 音频接口

2.3.1 喇叭

通过两个弹簧顶针(POGO PIN)与泰山派SPKP和SPKN连接,音频驱动电路由泰山派上的RK809-5实现。

image-喇叭.png

2.3.2 麦克风

通过一个弹簧顶针(POGO PIN)与泰山派MIC连接,MIC相关的驱动电路集成在了泰山派上。

image-麦克风.png

3.实物图

拓展板实物图.png
成品实物图.png

4. PCB设计说明

4.1 叠层阻抗设计

做叠层阻抗匹配主要的目的是让信号完整性更好,一般当我们设计的板子上有高速信号存在的时候我们就需要去考虑信号的完整性。

3.1寸扩展屏幕上面有MIPI信号,MIPI属于高速信号。

4.1.1 阻抗要求

单端走线我们一般是50ohm ±10%加上我们3.1寸扩展板上面没有什么重要的单端信号所以调节可以放宽,这里比较重要的是MIPI DSI信号

MIPI DSI阻抗要求| 参数 | 数值范围 | | --- | ---- | | 走线阻抗 | 差分100ohm±10% | | 差分对内最大时延差 | <6mil | | 时钟与数据之间的等长 | <12mil | | 走线长度 | 建议不超过4个 | | 各信号所允许过孔数量 | 建议不超过4个 | | 差分对间间距 | 建议大于等于4倍MIPI线宽,至少要3倍MIPI线宽 | | MIPI与其他信号间的距离 | 建议大于等于4倍MIPI线宽,至少要3倍MIPI线宽 |

4.1.2 阻抗计算

嘉立创提供了一个阻抗计算器,在计算中可以快速的计算出所需要参数对应的板材等。

嘉立创阻抗计算 (jlc.com)



我们的板子比较简单,走线在顶层和底层就够了,内部两层直接设为GND,给到顶层走线层和底层走线层参考,所以我们计算阻抗的时候各层参考。最终得到单端6.16mil,mipi差分4.88mil,线间距8mil。

嘉立创阻抗计算.png

5. 关键程序说明

5.1 打开设备树驱动

mipi相关的设备树在tspi-rk3566-dsi-v10.dtsi中,这里面包含mipi相关的所有设备树。我们通过tspi-rk3566-user-v10.dts中使用头文件去包含tspi-rk3566-dsi-v10.dtsi来决定是否使用mipi屏幕,所以我们使用前需打开。

//【开/关】mipi 显示屏幕配置,用户可以基于此复制自己的屏幕,注意EDP与MIPI屏幕互斥,因为共用了VOP如果需要同显自行修改
#include "tspi-rk3566-dsi-v10.dtsi"

5.2 GP7101背光驱动

调试屏幕我们一般会先把背光点亮如果使用的是泰山派的背光电路那直接使用代码里面默认的背光PWM驱动就行,但为了保护屏幕背光我们选择的是扩展板上的板载背光电路给3.1寸屏幕背光供电,扩展板板载背光电路PWM脚是通过GP7101 i2C转PWM芯片实现。所以我们需要编写一个GP7101驱动。

5.2.1 配置I2C1设备树

从原理图中可知GP7101和触摸共同挂在道I2C下,从数据手册中我们可以得知GP7101的I2C地址是0XB0,0xB0是包含了读写位的所以我们实际填写中还需要右移一位最终地址为0X58。

GP7101-I2C地址.png

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
    };
};

5.2.2 创建驱动

一般背光驱动都放在/kernel/drivers/video/backlight目录下,所以我们在此路径下创建一个my_gp7101_bl目录用来存放Makefilegp7101_bl.c文件。

5.2.3 编写Makefile

my_gp7101_bl/Makefile中把gp7101_bl.c编译到内核中,当然也可以选择obj-m编译成模块。

obj-y   += gp7101_bl.o

要想my_gp7101_bl下的Makefile生效还需要在上一层目录的Makefile中添加my_gp7101_bl目录,所以我们需要在backlight目录下Makefile中加入:

obj-y += my_gp7101_bl/

5.2.4 gp7101_bl.c驱动

5.2.4.1 I2C驱动框架

代码附件

驱动中的结构体

因为驱动过程中会有很多参数,我们不可能创建全局变量去保存他们,在linux驱动中一般都是通过创建一个结构体来保存驱动相关的参数,所以这里我创建一个gp7101_backlight_data结构体。

/* 背光控制器设备数据结构 */
struct gp7101_backlight_data {
    /* 指向一个i2c_client结构体的指针*/
    struct i2c_client *client;
    /*......其他成员后面有用到再添加........*/
};

probe函数

当驱动中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; // 返回成功
}

5.2.4.2 devm_backlight_device_register函数

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 结构的指针,这个结构包含了背光设备的属性,如最大亮度、当前亮度等。
5.2.4.3 gp7101_backlight_ops结构体

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,
};

5.2.4.4 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;  // 返回成功
}

5.2.4.5 i2c_write

i2c_write函数直接把我们前面写的触摸驱动I2C写函数拷贝过来。

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表示失败
}

5.2.4.6 注释backlight

因为我们之前的背光驱动也是用的"backlight"节点,为了不去修改上层我们自己写的驱动也是用的"backlight"节点所以两个节点会冲突,所以我们在tspi-rk3566-dsi-v10.dtsi中把之前的屏蔽掉留下我们自己写的驱动。

屏蔽原有背光设备树节点。

/ {

    /*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>;
        /*省略*/
    };
};

5.2.5完整代码

见附件

5.3 屏参调试

设备树在tspi-rk3566-dsi-v10.dtsi中,适配3.1寸触摸屏只需修改以下几个参数,其他保持默认即可。

  • 修改lanes数
  • 配置初始化序列
  • 配置屏幕时序

5.3.1 修改lanes数

3.1寸屏幕硬件上只用了2lanes的差分对,设备树中默认配置的是4lanes所以我们需要把lanes修改为2

dsi,lanes  = <4>;
改为
dsi,lanes  = <2>;

5.3.2 配置初始化序列

初始化序列是参考3.1寸屏幕厂商给的修改过来的,每款屏幕所使用的屏幕参都有些许不同。

5.3.2.1 序列格式

瑞芯微的初始化序列格式如下,这个格式很重要哟这是,因为有些屏厂会给其他格式的参考代码,但是内容都是一样的,我们要理解了这写格式以后可以可以根据其他参考来修改。

[包类型][发送延时][数据长度][MIPI屏初始化数据*n个]
例如:
39 00 06 FF 77 01 00 00 11
15 00 02 E0 00
05 00 01 28

5.3.2.2 包类型

上面例子中的39、15、05都是包类型除了这三个当然还有其他的类型,可以在Mipi-DSI-specification-v1-3.pdf手册中60页中查看更多详细的命令,我们平时最常见的就是这三个所以就只讲这三个的用法,整个mipi是非常非常复杂的我们这里还是实用为主,如果大家感兴趣可以自行深入(这篇文章不错推荐感兴趣的小伙伴可以看看https://blog.csdn.net/u013606261/article/details/112535270)

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
];

代码详见kernel.zip中的代码

5.3.3 配置屏幕时序

屏幕时序一般根据数据手册和厂家给的参考得来,以下是针对3.1寸屏幕修改好的参数。

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>;
    };
};

5.3.4 完整代码

见附件

5.4 触摸驱动

5.4.1 配置i2c1设备树

从原理图中可知GP7101和触摸共同挂在道I2C下,所以引用&i2c1并添加一个我们自己定义的myts@38触摸节点。

&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个引脚,有效电平为高电平
    };
   /****省略****/
};

5.4.2 创建驱动

一般触摸都放在/kernel/drivers/input/touchscreen目录下,所以我们在此路径下创建一个my_touch目录用来存放Makefilemy_touch.c文件。

5.4.3 编写Makefile

touchscreen/Makefile中把my_touch.c编译到内核中,当然也可以选择obj-m编译成模块。

obj-y   += my_touch.o

5.4.4 my_touch.c驱动

5.4.4.1 I2C驱动框架

和前面一样,这就是一个框架结构,这里就不过多赘述了。

5.4.4.2 驱动中的结构体

因为驱动过程中会有很多参数,我们不可能创建全局变量去保存他们,在linux驱动中一般都是通过创建一个结构体来保存驱动相关的参数,所以这里我创建一个my_touch_dev结构体。

// 定义一个表示触摸设备的结构体
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; // 触摸设备的中断号
};

5.4.4.3 probe

当驱动中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
}

5.4.4.4 中断

读取触摸数据有很多方法比如轮询但是这样效率太低了,所以我们这里通过中断方式实现触摸数据读取,当屏幕被触控时,屏幕会通过irq引脚输出中断信号。

devm_request_threaded_irq(&(client->dev), ts->irq,   // 请求线程化中断
                NULL, my_touch_irq_handler,          // 中断服务函数
                IRQF_TRIGGER_FALLING | IRQF_ONESHOT, // 中断触发方式为下降沿触发,且只触发一次
                client->name, ts);

5.4.4.5 中断线程服务函数

上面中断以后当屏幕被触摸会触发中断线程服务函数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;
}

5.4.4.6 TD_STATUS

读取数据从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));

1.4.4.6 TD_STATUS.png

TD_STATUS寄存器中的[3:0]位存储的是触摸点数据,所以我们通过下面语句读取到当前有多少点被按下。

touch_num = point_data[0]&0x0f;     // 获取触摸点的数量

TD_STATUS2.png

5.4.4.7 TOUCHn_X寄存器

TOUCHn_XH寄存器的[7:6]位是事件标志位数,所以我们通过touch_data右移6位来获取[7:6]位值。通过判定这个标志位我们可以知道当前触摸状态。

值类型:

  • 触摸:00b
  • 抬起:01b
  • 长按:10b
  • 保留:11b
event_flag = touch_data[0] >> 6;

TOUCHn_XH寄存器的[3:0]位是x坐标的高[11:8]位数,要想获取完整的x坐标值需要把TOUCHn_XH的[3:0]位和TOUCHn_XL的[7:0]位进行组合。

input_x  = ((touch_data[0]&0x0f)<<8) | touch_data[1];

TOUCHn_X.png

5.4.4.8 TOUCHn_Y寄存器

TOUCHn_YH寄存器的[7:4]位是触摸ID,通过右移4位获取到触摸id。

touch_id = touch_data[2] >> 4;    // 获取触摸点ID

TOUCHn_YH寄存器的[3:0]位是y坐标的高[11:8]位数,要想获取完整的x坐标值需要把TOUCHn_YH的[3:0]位和TOUCHn_YL的[7:0]位进行组合。

input_y  = ((touch_data[2]&0x0f)<<8) | touch_data[3]; // 计算Y坐标

TOUCHn_Y.png

5.4.4.9 上报数据

数据到以后调用input接口函数进行数据上报,详细的函数使用方法大家看代码里面的注释。

5.4.5 完整代码

见附件

6.重要物料购买

名称链接备注
底壳https://www.jlc-3dp.cn/swhorder/#/placeOrder自行打印,附件有吴工给的
顶壳https://www.jlc-3dp.cn/swhorder/#/placeOrder自行打印,附件有吴工给的
屏幕拓展板本项目自行打印,可以删除中间2层,2层板可以选颜色。(* ̄︶ ̄)
屏幕https://item.taobao.com/item.htm?abbucket=9&id=602291522189&ns=1&skuId=4647073176206&spm=a21n57.1.item.4.5dee523cydFHkZ3.1寸触摸显示屏
喇叭https://detail.tmall.com/item.htm?_u=u1q56pn38e7c&id=657102318771&spm=a1z09.2.0.0.1bf62e8doEbR852809带线
泰山派https://lckfb.com/project/detail/lctspi-2g-16g?param=baseInfo
0.3间距31PIN同方向MIPI屏排线https://item.taobao.com/item.htm?abbucket=9&id=618662306027&ns=1&priceTId=2147815617144889472891128e184c&sku_properties=1627207:381564526;-2:-2&spm=a21n57.1.item.5.17db523coJA0gY31pin-同向-50mm。越短越好
0.5间距6PIN同向触摸屏排线https://item.taobao.com/item.htm?abbucket=9&id=671892913989&ns=1&priceTId=2147815617144891221501036e184c&spm=a21n57.1.item.54.1bdc523cw6mm7D&skuId=50092948220910.5mmFPC软排线6pin。同向。6CM

7.装配说明

第一步

第一步把3.1寸屏幕和扩展板组装起来,3.1寸屏幕排线和触摸排线插入到FPC座中扣紧,接着翻转平板使用双面胶固定屏幕和扩展板,注意折叠的时候温柔一点别把屏幕排线扯断了。

image.png

第二步

把屏幕装入到底壳中,可以通用喇叭座子来判定屏幕和底壳的方向,屏幕完全装入以后是和外壳同高的且间隙非常小的。

image.png
image.png

第三步

通过0.3间距31PIN同方向MIPI屏排线和0.5间距6PIN同向触摸排线连接泰山派和3.1寸屏幕扩展板,这排线尽量是越短越好。

image.png

第四步

折叠排线把泰山派装入到底壳中,折叠的时候不要像折纸一样压太紧,尽量折个圆角,折死了信号不好而且容易断。

image.png

第五步

把喇叭装到顶壳的喇叭插槽中,喇叭默认是不带插座头的,这个需要自己买回来焊接,或者直接把喇叭焊接到扩展板板上也可以。

第六步

盖上顶壳使用镊子把喇叭线插入到座子中并确保外壳没有夹到线

第七步

盖上顶壳把落实拧紧,不用太用力感觉螺丝已经拧到底即可,因为是塑料件,太用力容易导致外壳变形损坏等,这里只需要装3课螺丝,另外一个孔被屏幕排线挡住所以不需要装螺丝,螺丝尺寸为M212(2mm12mm)

image.png

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

附件

序号文件名称下载次数
1
屏幕资料.zip
3
2
胖妞手机3.1寸扩展板结构图.dxf
1
3
Android_All_Config.cfg
3
4
3.1寸大显MIPI补丁.zip
1
5
3.1寸屏幕背光驱动补丁.zip
1
6
3.1寸屏幕触摸驱动补丁.zip
1
7
boot.img
1
8
Mipi-DSI-specification-v1-3.pdf
1
9
胖妞手机安卓版代码补丁(默认横屏)官方.zip
3
10
胖妞手机安卓版代码补丁(默认竖屏)官方.zip
2
11
演示视频.mp4
0
12
顶壳.STEP
1
13
顶壳.STL
1
14
底壳.STEP
1
15
底壳.STL
1
16
关键代码文件kernel.zip
3
克隆工程
添加到专辑
0
0
分享
侵权投诉

工程成员

评论

全部评论(1)
按时间排序|按热度排序
粉丝0|获赞0
相关工程
暂无相关工程

底部导航