第六节:综合项目

泰山派第六期:综合项目


2024.4.19 13:55学习

第一部分 前言

相信如果有80/90/00的同学,有可能看过《魔幻手机》,里面的傻妞是很多人小时候梦寐以求的,那今天我们就来圆梦,自己制作一款桌面AI小手机,我们做的这个手机长宽高比例是82x50x17,看起有点微胖所以我们叫它"胖妞手机"。
学习完本套项目,对个人综合能力的提高非常有帮助。项目综合了前面学习的内容,并结合初学者对知识的接受程度,由浅入深的,带着大家学习一个硬件设计、焊接调试、驱动编写、系统定制、结构设计等一整个完整的项目。

第二部分 硬件设计

一、电路原理分析

胖妞手机硬件,主要由泰山派以及一个屏幕扩展板构成,我们这里主要是来分析这个扩展板的设计原理以及思路,扩展板主要由接口转换电路、背光电路、音频电路构成。

1、接口转换电路

为什么需要接口电路呢?
主要是因为,我们选的3.1寸屏幕,它的mipi接口和触摸接口和我们泰山派的mipi和触摸接口都不一样,所以我们需要通过设计扩展板,使信号线能够对应。

注意:接口原理图设计时,要特别注意线序正反,需要根据FPC的封装与实际接插情况来定。切勿画反。

买的是大显伟业的3.1寸mipi,问淘宝要资料
这里需要看一下屏幕规格书。

1、泰山派的mipi_dsi和touch与3.1寸屏幕的mipi_dsi和touch,线序不一样,pin数量也不一样,不能直接相连。
所以需要画转接板
泰山派的mipi_dsi和touch接转接板
3.1寸屏幕的mipi_dsi和touch接转接板
在转接板上设计接口转换电路,将两者的线序按顺序理清,接好。
2、
3.1寸屏幕的mipi_dsi是24pin(0.5mm),其中有一对背光线,两对数据线差分(0 1lan),一对时钟线差分,一根复位线,电源和地线
泰山派的mipi_dsi是标准31Pin(0.3mm),其中有一对背光线,四对数据线差分(0 1 2 3lan)(从0开始用),一对时钟线差分,一根复位线,电源和地线
因为3.1寸屏幕的mipi_dsi用不到泰山派的mipi_dsi所有的 pin,所以泰山派mipi_dsi接到转接板时,有一些pin需要删掉,但注意, 泰山派的mipi_dsi是标准31Pin,泰山派在转接板的接口也应该是31pin,这样两者之间就可以通过31pin的FPC相连。
(先看屏幕的资料,再来对用泰山派的引脚)
3、
3.1寸屏幕的最大背光电流是25mA,泰山派背光电路最大提供110mA的电流
所以直接用泰山派给屏幕供电,可能有烧屏幕或者屏幕发烫的风险。
所以转接板上单独设计背光电路,给屏幕供电,不用泰山派给屏幕供电。

1.1 泰山派mipi接口

泰山派的MIPI接口是标准31PIN的,但因为3.1寸屏幕是24pin,用不到泰山派mipi_dsi的所有引脚,所以这里设计转接板时做了删减。泰山派mipi_dsi可使用31pin 0.3mm的FPC与转接板的31PIN 0.5mmFPC座子直接相连。
需要注意的是MIPI_DSI_VCC_LED+和MIPI_DSI_VCC_LED-背光引脚,此背光是由泰山派上的板载背光电路输出,他的输出电流是110mA,我们3.1寸屏幕最大能承受的驱动电流是25mA,所以泰山派上的板载背光电路输出不适合直接接到3.1寸屏幕的FPC上。
在这里插入图片描述

1.2 3.1寸屏幕的MIPI接口

3.1寸屏幕的分辨率为480x800,使用的是MIPI DSI接口,屏幕排线为24个引脚 0.5mm,其中4、5、9脚为空不需要接。
在这里插入图片描述
在这里插入图片描述

1.3 泰山派的触摸touch接口

泰山派触摸接口以及功能如下所示
在这里插入图片描述

1.4 3.1寸屏的触摸touch接口

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

2、背光电路

背光电路主要由、背光选择电路、背光驱动电路以及背光调节电路构成。

2.1 背光选择电路

背光电路分为两路:第一路是由泰山派输出,第二路:是板载的背光驱动输出。
两者通过4个0欧姆电阻进行选择。
如果贴(R103和R104)不贴(R105和R106)则由泰山派背光电路供电。
不贴(R103和R104)贴(R105和R106)则由板载背光驱动电路供电。

前面章节中,我们已经计算过了,泰山派上的背光驱动电路IOUT=0.2V/R(R=(R95xR96)/(R95+R96)),最终得出IOUT = 0.2V/1.8≈110mA,但我们这款3.1寸屏幕背光电流最大只能支持25mA
如果直接接到屏幕上有烧屏幕或者屏幕发烫的风险
实际测试中,使用泰山派背光供电会出现屏幕非常发烫情况,所以我们默认不贴(R103和R104)贴(R105和R106)使用板载背光驱动供电。

3.1寸屏幕背光二极管就串了几个,所以电流小
10寸的屏幕,串的多,并的也多,所以电流是140多mA

在这里插入图片描述

2.2 板载背光电路!!!需要研究一下

背光驱动电路主要由SY7201ABC实现,SY7201ABC是一款高效率的LED驱动器,主要用于控制和调节LED灯的亮度。SY7201ABC通过提供恒定电流来确保LED发光的一致性和稳定性,从而提高了LED使用的寿命和效能。
需下载SY7201ABC芯片的数据手册!
泰山派背光输出也是用这个芯片。
主要特性:

  • 输入电压范围:2.5V至30V直流。(宽电压输入)
  • 输出电流能力:单通道最大可提供4A电流,双通道则能提供8A总电流。(好像只能一个通道吧)
  • PWM调光功能:支持通过PWM信号进行调光,调节范围宽。
  • 恒流模式:确保在LED负载变化时,输出电流保持恒定。(恒流输出)
  • 过压保护和短路保护:内置的保护功能,可在输入电压过高或发生短路时保护芯片不受损害。
  • 小尺寸封装:采用SOT-23封装形式,适合于多种PCB板设计
    封装与引脚功能说明:
    在这里插入图片描述
    设计的背光驱动电路如下图
    在这里插入图片描述
    说明:
    L15 D7 C255 与SY7201ABC芯片组成 BOOST升压电路(主要是利用电感电容的储能特性 进行升压 (电感电流不能突变,与电容电压不能突变))
    R99 R100 为采样电阻与参考电压进行对比,恒定输出电流,输出电流的计算公式IOUT=0.2V/R,这里为了使R能够匹配更加精准的值,我们并联了两个电阻R99和R100,其中R99为NC不贴,最终的IOUT=0.2V/10Ω=20mA
    R102 R101为上下拉电阻,默认下拉,所以贴R101,这里两个电阻的作用是Linux系统启动到背光驱动加载需要一些时间,也就意味着驱动加载之前这个IO口是不确定的,我们根据需要在没有驱动控制的时候通过上拉或者下拉电阻来决定屏幕背光关闭还是打开。

这里,到内核kernel中才加载背光的驱动,才会点亮屏幕
也可以将背光驱动写在boot中
程序先进入boot,再进入kernel

2.3 背光调节电路!!!

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

数据手册是中文的

在这里插入图片描述

2.4 PD诱骗12V电路

因为之前设计的转接板中PD诱骗12V电路,只能诱骗出9V,所以这里再研究研究,加上这个电路。

2024.5.7
打印出来的板子,焊接好后,PD还是只能诱骗出9V
很奇怪,明明看了数据手册,电路不太可能出问题
想起来自已用的“小米11充电器和数据线”,有时候手机充电时,线一折,充电会断掉。
于是换了一根数据线,诱骗成功!!!
指示灯亮起

忽然想到一个问题
不加12V诱骗的时候,typec给泰山派供电,泰山派3v3给板载背光电路供电
加12v供电的时候,typec给泰山派供电,此时泰山派3v3给板载背光电路供电3v3
12V诱骗给泰山派供电时,板载背光电路应该也设计为输入由泰山派3v3获得
即,12V诱骗出来后,只给泰山派供电,泰山派的3v3永远给背光电路供电
注:查看一下教程里的板载背光电路由哪里供电。
(查看后,3V3经转接板的31pin座,由泰山派提供)

之前的PD诱骗电路,明明cfg1接了24k电阻,但还是诱骗出9V
9V经10k电阻接LED,led只有0.9ma的电流,连一点微微亮也没有
(10k时 LED灯亮度也还行)
CH224K的PG接LED的负极,PG低电平有效
(一般低电平有效指,输入低电平,某芯片开始工作)
(但PG是开漏输出,这里的低电平有效是什么意思?)
PG的低电平有效,应该是CH224K正常工作时,PG输出低电平。
阅读了CH224K的数据手册,感觉电路没什么问题
可能是布局布线不好,或者GND焊接不行?
使用同样的电路,重新设计,板子到手后,先调调PD。

参考原理图
在这里插入图片描述

诱骗成功后,PG拉低

实际电路图
在这里插入图片描述

2.5 串口电路

还没用过串口,所以也加上这个电路。
记得看看CH343G的数据手册
看看能不能把电源断开
泰山派有供电时,插入uart的typec,才可以使用串口。

UART的VBUS,只给CH343G供电
不与泰山派的供电电路联系

在这里插入图片描述

3、音频接口

3.1 喇叭

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

3.2 麦克风

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

二、原理图与PCB设计

在了解了3.1寸胖妞手机扩展板硬件电路后,接下来进行原理图与PCB设计环节
原理图设计部分包含了元器件选型、元器件搜索以及原理图整理的内容;
PCB设计部分包含边框结构设计、叠层设计、阻抗匹配、规则设置、模块化布局、PCB走线与设计检查、PCB生产与打样等内容。

1、原理图设计

1.1 快捷键

有部分是自己按照AD的习惯,改的快捷键
有部分是立创EDA自带的
常用:
ctrl+鼠标滚轮:放大缩小
ctrl+W:放置导线(空格改变拐角的方向)
shift+F:搜索元器件,并放置
shift+N:放置网络标签
alt+T:放置文本
`:删除
右键:取消当前命令
按住ctrl+右键点中对象并拖动:复制对象(器件 文本)
x:镜像
y:镜像

多引脚放置导线和网络标签时,结合ctrl cv使用,更快

不常用:
P+C:放置NC

1.2 分配位号

在设计原理图时,进行元器件的复制删除等操作,可能会导致元器件的位号不连续
虽然不连续,不影响PCB的布局布线以及最后的焊接。
但是为了设计的完整性,进行重新分配位号的操作。
先清除位号(也可以直接重新分配位号)
在这里插入图片描述
在这里插入图片描述

也可在原理图图页上,右键,清除位号
会跳到上面的第二张图

分配位号
在这里插入图片描述

1.3 检查封装

使用AD时,一般需要在“封装管理器”中为元器件分配封装。
在立创EDA中,器件符号和封装一般是绑定的,不怎么需要改。
但是放置电阻、电容、电感时,默认时0603的封装
为了好焊接一点,可在封装管理器中将0603的封装改为0805的封装。
在“工具”中找到“封装管理器”
在这里插入图片描述
加粗样式
在设计PCB时,应该检查所有的封装是否正确。

本项目中,pogopin弹簧顶针改为了插件,封装为“TESTPOINIT-TH_Z2-1.5-8-2.5”的封装
高8mm,插入PCB的部分直径为0.8mm(本项目中8mm是否合适?)
10.4元50个,5元运费,有点贵

1.4 最终原理图

按照前面的原理图分析,可将电路图画为如下形式
需再补充PD诱骗电路和串口电路。
教程部分

在这里插入图片描述
另加部分
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1.5 原理图DRC检查

原理图也需要进行DRC检查
因为在设计原理图时,可能产生有些引脚断连的情况。

2、PCB设计(写的全面一点,可作为设计PCB的一般步骤)

2.0 快捷键设置

这是AD软件的快捷键
按照AD软件的快捷键改变立创EDA的快捷键

菜单栏上时,Ctrl+鼠标左键,设置快捷键
F2:交互式布线(走线命令)
P+L:放置线条
F3:放置过孔
F4:放置铜皮
T+G+R:重铺选中的铜皮
F6:在矩形区域内排列器件
F9:交换两个器件的位置(先按F9,再依次点两个器件)
1:通过x/y轴进行位移(字母上面的数字)
2:线选(字母上面的数字)
3:框选(字母上面的数字)
4:移动命令(字母上面的数字)
7:挖铜命令(字母上面的数字)(整板地铜添加完成后,此命令用的多,修铜用)
6:差分走线(字母上面的数字)(需要设置某对线为差分线)
ALT+F1:修整铜皮边缘(按下后,铜皮出现小圆点,点中,重铺)(铺铜时,出现尖刺,使用该命令修改之)(好像可以补铜)(拖点也行,以点为起点和终点再次铺铜也行)
4:左对齐
8:顶对齐
6:右对齐
2:底对齐
AC:垂直中心对齐(系统自带快捷键)
AV:水平中心对齐(系统自带快捷键)
F5:颜色开关(系统自带快捷键)
F11:菜单栏开关命令(系统自带快捷键)
7:水平分布
9:垂直分布
ˋ:删除按键(注意:设置快捷键时,要设置为 ALT + ˋ
ALT+F3:补一块铜皮

V+B:板子反转
L:左键选中元器件,L换层
ctrl+shift+鼠标滚轮:切换不同层

ALT+鼠标左键:高亮显示(Ctrl+鼠标左键?)
U+M:多根走线,选中后

X,Y:镜像
shift+左键:多选
P+L:放置线
shift+空格:改变线改变方向的模式

E+A:特殊粘贴

2.1 原理图生成PCB

导入变更前,需要检查封装是否正确
设计选项卡,从原理图导入变更
需确定导入的元器件
第一次导入时,一般全部导入

2.2 板框结构设计

在公司实际的项目中,结构工程师会先输出结构图给我们,我们再基于结构工程师的结构要求,来设计PCB的板框形状以及器件布局等,最后在和结构工程师共同敲定最终的布局。
当然我们平时DIY没有结构要求,基本上大家都是随心所欲画板框和布局的。
但因为泰山派胖妞手机3.1扩展板后期需要与泰山派、屏幕、喇叭、外壳其它配件等配合,所以我们对结构有严格的要求。所以在我们开始PCB设计的时候,先要把结构定下来。
结构图我们已经根据外壳和装配情况提前设计好了,大家直接下载导入就行,如果你不想使用我做好的结构你也可以自己手动去调整,比如装配3.1寸屏幕的时候FPC放在什么位置合适,定位孔放在什么位置刚好可以和泰山对上,这些需要你自己去测量。
一般用CAD软件画板框结构,保存为DXF格式的文件
立创EDA中,文件,导入,DXF
选择要导入的DXF文件即可
在这里插入图片描述
更改完成后
在这里插入图片描述
选中板框,右键,可添加“圆角”
添加2mm的圆角
在这里插入图片描述
以上应该添加“锁定”,全选中,在“属性”中,或者“右键”,锁定。

2.3 叠层确定

根据电路的规模、电路板的尺寸、电磁兼容(EMC)的要求,确定PCB的层数
以4层板为例
叠层基本规则:
1、元件面、焊接面为完整的地平面(屏蔽)
2、尽可能无相邻平行布线层(相邻两层垂直布线)
3、所有信号层尽可能与地平面相邻(最好有一个地层相邻)
4、关键信号与地层相邻,不跨分割区(需控阻抗的信号,不跨两个铜皮,若跨越,会造成阻抗突变)

选用4层板
sin01(顶层) (关键信号,放置主要元器件)
GND02 (相邻地平面)(负片)
PWR03 (正片OR负片)(电源较少时,可设置负片,放置闭合线条分割)(电源较多时,可正片,手动铺铜连接,最后再铺地铜)
sin04(底层)

操作:
找到铺铜管理器
在这里插入图片描述

设置完成后,查看一下内两层的网络,一般默认都是GND

内层需要设置内缩,保证不向外产生电磁辐射。
在20H时,可以抑制70%的磁通泄漏
在100H时,可以抑制98%的磁通泄漏
综合衡量,20H就基本满足大多数要求。

20H原则:
H指电源层和地层的距离,电源层比地层内缩20H。
一般GND层内缩20mil,PWR层内缩40mil。

AD中
负片层内缩可在层叠管理器中设置
Name左侧的# -> 打开“Pullback distance” -> 打开后直接设置即可
设置完成之后,白色虚线为板框,蓝色粗线就是内缩的距离。可以将内缩距离调大,查看变化。

立创EDA中,为内电层设置网络,然后设置网络规则,完成“内缩”。

2.4 阻抗计算

两层板没有阻抗一说,4层板要阻抗匹配。

阻抗匹配,使信号传输过程中,不产生反射,可以降低传输损耗、使信号完整。
低速信号,不进行阻抗匹配,有点反射影响不大。
高速信号,阻抗不匹配时,对信号质量影响很大。

一般、介质厚度、线距(指差分线)越大,阻抗值越大。
介电常数、铜厚、线宽、阻焊厚度越大,阻抗值越小。
上述可以改变的参数只有线距线宽
阻抗匹配的简单理解,就是设计线宽和线距,以满足阻抗要求,降低信号的反射,提高信号完整性。

-> 增加介质厚度,提高阻抗
-> 增加线宽,减小阻抗
-> 减小铜厚,增大阻抗
-> 增加介电常数,减小阻抗。通常FR4板介电常数为3.9-4.5。
-> 刷一遍阻焊,使单端线的阻抗下降2om,使差分线的阻抗下降8om。刷两遍,下降值为一遍时的两倍。刷三遍时,不再变化。

外层 20mil过1A
内层 40mil过1A

微带线的概念是只有一个参考平面的传输线,带状线是有两个参考平面。

不同信号,不同器件要求的目标阻抗都不一样。
一般情况下
USB2.0需要90Ω阻抗
HDMI、USB3.0、MIPI、100兆端口、千兆端口和其他控制,通常为100Ω阻抗
RS422为120Ω阻抗
单端走线50Ω阻抗
串口一般不需要阻抗匹配。

百度:
1、PP片(Prepreg)是PCB的薄片绝缘材料。
PP片在被层压前为半固化片(半固化,顾名思义),又称为预浸材料,主要用于多层印制板的内层导电图形的粘合材料绝缘材料
在PP片被层压后,半固化的环氧树脂被挤压开来,开始流动并凝固,将多层电路板粘合在一起,并形成一层可靠的绝缘体。
PP片的介电常数一般为4.2
2、Core则是制作印制板的基础材料。
Core又称之为芯板,具有一定的硬度及厚度,并且双面包铜。
芯板一般是环氧树脂加上填充剂以及玻璃纤维做出的复合材料
耐燃等级为FR-4
(FR-4是一种耐燃材料等级的代号,所代表的意思是树脂材料经过燃烧状态必须能够自行熄灭的一种材料规格,它不是一种材料名称,而是一种材料等级)
通常FR4板介电常数为3.9-4.5。
所以,多层板其实就是Core与PP片压合而成的。
3、两者的区别:
a、PP片在PCB中属于一种材料,使用前的材质为半固态,类似于纸板,使用后的材质坚硬,类似于铜板;
b、PP片类似于粘合剂+绝缘体;而Core则是PCB的基础材料,两种是完全不同的功能作用;
c、PP片能够卷曲,而Core无法弯曲;
d、PP片不导电,而Core两面均有铜层,是印制板的导电介质。

在本项目中,需要进行阻抗匹配的有
1、mipi座子(100om)
差分:
0LAN:0N 0P
1LAN:1N 0P
CLK:CLKN CLKP
单端:
RESET
2、TYPEC座子(90om)
差分:
DP DN (PD)
DP DN (UART)
3、SPK(视频中只计算了50om和100om 所以暂定100om)
差分:
SPKP SPKN

mipi属于高速信号,但在这里速率比较低
高分辨率mipi屏幕,速度会更快
为了设计完整性,也对mipi进行阻抗匹配
有单端阻抗和差分阻抗之分
mipi的reset是一根线,需要控单端阻抗
在这里插入图片描述

使用嘉立创的**阻抗计算神器**。
选用层压结构为
在这里插入图片描述
设置线距为8mil
在这里插入图片描述
计算结果为
在这里插入图片描述

2.5 网络类设计

设计网络类,方便分类布线
一般在DDR中,将“地址线”分一类,“数据线”分一类。
PWR设置为一类
方便地址线数据线等长时,查看每根线的长度
方便设置不同的规则

本项目中,没有DDR,只设置PWR的类。
在这里插入图片描述

2.6 差分对设计

通过差分对管理器,将差分对组合到一起,方便进行差分对布线。
组合好后,使用”差分对布线“功能,可将两根线同时引出。
在这里插入图片描述
共设置了6对差分线
MIPI_DSI_CLK
MIPI_DSI_0
MIPI_DSI_1
SPK
PD
UART

差分线内部需要”等长调节“。
同一器件的各对差分线之间,需要”差分对等长调节“。
比如,mipi的三对差分之间需要”差分对等长调节“。
差分线等长调节完成后,若空间允许,需要进行”包地“处理,同时地线上(尽量)均匀打上过地孔。

2.6 规则设计
2.6.1 导线线宽

1、默认线宽
mipi的单端阻抗要求线宽6.16mil,这里不再区分,干脆将所有的默认线宽修改为6.16mil
在这里插入图片描述
2、PWR线宽
PWR线宽设置为如图。
布线时,按tab,可以修改线宽。
暂时这样设置,实际布线时,可重新进行调整
在这里插入图片描述

默认线宽为6.16mil
当布电源线时,线宽会优先满足PWR的要求。
其他规则同理。

2.6.2 导线线距

信号线的线距需要满足3W原则,实际是2W。
在这里插入图片描述

2.6.3 差分对

1、 90om
在这里插入图片描述
2、 100om
在这里插入图片描述

2.6.4 过孔尺寸

一般情况下,选用12/24mil的过孔尺寸就行。
BGA时,可减小过孔尺寸,方便扇孔,可选用8/16mil。
注意不要超过加工工艺的最低过孔尺寸要求。
在这里插入图片描述

2.6.5 内电层

前面在叠层确定的时候说过,内层需要内缩处理,以减小对外的电磁辐射。
将内电层GND02赋予GND的网络。
将内电层PWR03赋予VCC_3V3的网络。

本设计中有12V、VCC_3V3、5V(可忽略)
VCC_3V3网络较多,PD诱骗出来12V后,直接接入pogopin,网络较少。
所以内电层赋予VCC_3V3网络
当电源较多时,可将PWR03设为信号层,再其中进行铺铜连接
所有的电源网络铺铜完毕后,剩余空间可铺地铜。

在这里插入图片描述
在这里插入图片描述

2.6.6 铺铜

一般默认即可

2.6.7 助焊阻焊扩展

一般默认即可

2.6 规则生效

前面我们只是设置了规则,但是有些规则并不会生效,要让规则生效我们还需要在网络规则中去应用到指定的规则。

AD中,设计规则中,设计完成一个规则,同时可以选择此规则应用在哪个网络中。
在这里插入图片描述
对比着规则管理中,新建了哪些规则,然后生效。

内电层生效后
GND02
在这里插入图片描述

PWR03
在这里插入图片描述

2.7 PCB布局

右侧“过滤”中将“元件属性”进行隐藏
左侧“网络”中将飞线隐藏
更简洁,方便布局
1、
按照原理图的各框图,左键框选,shift+x(立创EDA快捷键)进行交叉选择
跳转到PCB设计
按F6,在矩形区域内排列器件。
对所有的器件,进行模块化分布。

2、
将固定器件,放到文档层标记的位置
放置好后,将其锁定。

3、
其余的电路,需进行模块化布局,使器件间的导线最短

在这里插入图片描述

2.8 PCB布线

F2:交互式布线(走线命令)
P+L:放置线条
F3:放置过孔
F4:放置铜皮
6:差分走线(字母上面的数字)(需要设置某对线为差分线)

先走重要的线
布线时,忽略和推挤好用
先连线,最后修线
两对差分线之间,四倍间距,三倍也可
差分线,空间允许时,包地处理,打过地孔,过地孔对齐

打孔占位,特别是地孔
mipi线属高速线,高速线布线时少打过孔

布线时,横平竖直,顶层横着走线,底层竖着走线
一个面,一个布线方向,立交桥,布线空间大,不需要打过多的过孔(一般不超过4个)。

问题
mipi的多个lan,是否需要长度一致

应该需要长度一致吧,各mipi信号同时传输
将最长的一对线,尽可能缩短,然后将其他短的线,进行绕长,去对齐最长的一对线。
一对差分线之间,等长调节
各对差分线之间,差分对等长调节
应该是两个不一样的功能。

2.9 整理

1-5不用严格按照步骤来操作。

重建内电层

2.9.1 泪滴
2.9.2 铺地铜
2.9.3 缝合孔
2.9.4 放置禁布区(去除天线效应)
2.9.5 DRC检查
2.9.6 丝印调整

丝印尽量朝向同一方向
常用丝印规格
5-30
6-45
有丝印很丑,泰山派和梁山派都没有丝印
手机等高密度板子,应该也不设置丝印
可看原理图焊接调试

2.9.7 标注
2.9.8 生成可制造文件

一般导出bom,gerber和坐标文件
bom是物料清单,购买器件。
gerber用于PCB下单。
坐标文件用于SMT。
在这里插入图片描述

三、免费PCB打样

1 优惠券领取

2 生成可制造文件

仅打样PCB,使用gerber文件即可

3 下单

在这里插入图片描述

3.1 基本信息

在这里插入图片描述

  • 板材类别:选择FR-4,另外FPC板材为柔性PCB、铝基板常用于做灯板、铜基板散热性较好、高频板用于设计制作多阻抗和信号要求较高的板子;
  • 板子尺寸:默认会自动识别出来的,没有识别的话也可手动填写;
  • 板子数量:免费打样数量为5片,如果多打需要自费;
  • 板子层数:嘉立创现在支持1-6层的免费打样,板子设计是两层板,这里选择4层(因为我们这个板速率不高你直接用2层来做性价比更高)。
  • 产品类型:选择工业/消费/其他类电子产品,航空和医疗板精度设计要求较高;
  • 确认生产稿:如果是批量生产那必须要确认生产稿,避免生产文件有误影响板子功能,免费打样则选择不需要生产稿即可。
3.2 PCB工艺

PCB工艺选项里面内容较多,仅需关注下图中框选出来的几个选项:

  • 拼板款数:在进行批量打样时常将多个PCB拼在一个板子上生产,这样成本更低。由于目前仅做免费打样,拼板数量应为1,出货方式为单片;
  • 板子厚度:默认板子厚度为1.6mm,无特殊要求建议是默认1.6mm ,选择其他厚度和颜色匹配时容易选到冷门工艺会额外添加工艺费;
  • 需要阻抗:选择我们前面计算出来的层叠结构(文件中已有层压顺序即可)(因为我们这个板速率不高你直接用2层来做性价比更高)
  • 阻焊颜色:即板子的颜色,嘉立创支持七种不同的阻焊颜色,其中绿色的生产周期最快,最快48小时内发货,其他颜色最快是72小时发货,可结合自身喜好和板子的着急程度选择合适的阻焊颜色。(除绿色外,其他颜色的沉金板为特殊工艺,要加钱)
    在这里插入图片描述
    在这里插入图片描述

无铅喷锡要加钱

3.3 个性化服务

个性化服务没有特殊需求选默认即可。
在这里插入图片描述
在这里插入图片描述

3.4 交期

交期与所选颜色有关,嘉立创最快支持12小时加急,但需额外支付加急费。若无特殊需求,选用默认交期即可。

在这里插入图片描述

3.5 SMT贴片与激光钢网

如需选择工厂代焊接元器件,则在此选择需要SMT贴片,工厂生产PCB后,会将元器件一同焊接好寄出
SMT属于收费服务,若不需要则选择不需要即可。
钢网是用于SMT贴片刷锡膏用的,如果自己有贴片机,可在生产PCB时选择开钢网回来自己进行贴片焊接。
在这里插入图片描述

SMT贴片较贵
200-300
注:
1、
下单PCB时勾选需要SMT贴片(基本上用券后免费)
2、
然后再下单SMT(较贵)
完成1、2后才能完成SMT贴片。

3.6开票与支付

嘉立创是支持开票的,下单前需填写开票税号,免费打样无需开票,开票信息填写个人即可。在确认订单方式推荐选择“系统自动扣款并确认”选项,避免个人疏忽忘记确认订单影响生产。

费用为零时,自动确认即可
有费用时,预存账户没钱时,手动确认
手动确认订单更有仪式感。

在这里插入图片描述

3.7 发货与快递

在发货页面建议选择电子收据,发货方式可选不同交期是否一起发后,填写收货地址,下单联系人和技术联系人都可以填自己的联系方式,快递根据地区不同会由不同的快递显示,选择里面一个免费的快递即可。

在这里插入图片描述

3.8 使用优惠券下单

填写完成后,右边可以看见总价
在这里插入图片描述

没有特殊工艺时
二层板20
四层板50
使用优惠券,会免费
这里选用了“无铅喷锡”,总价在50的基础上加了30多
使用优惠券后,仍有需自己支付30
按理说,无铅喷锡也可以免费打样

在这里插入图片描述

四、PCB焊接

四个FPC座子,和TYPEC接口比较难焊接,特别是0.3mm 31pin的座子。
第一次焊接,使用了好几个座子,都没有焊接成功。
在B站搜“FPC座子焊接”,找到一个视频,找到了焊接方法。
具体方法如下:
1、
需使用加热台(自己之前复刻了开源硬件平台的加热台项目,没有加热台,买一个“鹿仙子LED灯珠拆焊”即可,但是不能控温),使用电烙铁很难焊成功,尤其是自己的电烙铁很廉价。
2、
需要焊锡膏,自己买的低温焊锡膏(138度融化,在加热台上,需要160以上,可能加热台检测的温度不准),凯利顺牌子,其他也行,但是不要维修佬。
3、
裸PCB,先焊接难焊的部分。
以FPC座子为例。
先将锡膏涂到要焊接的焊盘上,小焊盘,非常少的锡膏就行,然后加热PCB,使焊锡膏融化。
融化后,多余的锡膏会变成球状,使用镊子,将球状锡膏小心地从焊盘上移除。(或者一口气吹除,当PCB上有其它器件时,吹气会吹掉其他器件,这个时候就不要吹)
仔细检查,若有个别焊盘焊锡较少,需要再补充一些锡膏,再用镊子移除多余锡膏。
然后拿下PCB,待锡膏冷却。
将需要焊接的FPC,放到上面加了锡膏的对应位置。
重新加热PCB,锡膏化了之后,用镊子小心地调整位置(最好是加热前放好,这里不要调整)
用镊子轻轻地按压器件,使引脚包裹融化的锡膏,并接触到相应的焊盘
拿下PCB,冷却。
冷却后,用万用表检查各线路是否正确连通。

该方法可以完成0.3mm 31pin的FPC座子焊接。

2024.23 18.04

1 焊接工具

需要用到的焊接工具有:电烙铁、焊锡丝、锡膏、镊子、吸锡带、助焊剂、高温海绵、吸锡器、松香、洗板水、焊台、热风枪等,其中电烙铁、焊锡丝、这四项为必备工具,其余几个有最好,没有也可以。
电烙铁选择常规烙铁就行,有条件的可以选择焊台,升温快且稳定。烙铁加热时请勿触摸金属位置,避免烫伤,头发绑好避免烫到,桌面保持整洁。

在这里插入图片描述
焊锡建议是选用无铅焊锡,焊锡时较好比较好上锡。一般左手拿焊锡,右手拿烙铁,如果惯用左手的话反之。
在这里插入图片描述
高温海绵在使用前先用水浸湿,然后拧干后使用,海绵上不能沾太多水分,避免烙铁头高温沾水后急速降温损坏烙铁头。

2 焊接辅助工具

AD和嘉立创EDA有交互式BOM
电脑网页可打开,辅助焊接

3 本转接板焊接注意事项

3.1 NC不要贴

原理图中为NC的器件不要贴,比如电源背光选择电路,如果贴上了以后,扩展板上的背光电源会和泰山派上的电源串联起来,轻者损坏背光电路,重者烧坏开发板。
背光只选择板载背光电路给mipi屏幕供电。

mipi屏幕的背光供电有两路
一路是泰山派上的板载背光电路,通过31pin端口给mipi屏幕供电
一路是转接板的板载背光电路,通过**SY7201ABC(LED驱动芯片)**将泰山派31pin端口的3V3变成mipi屏幕的供电。(GP7101将触摸IIC转化为PWM,主动控制背光的强弱)
通过0欧电阻选择转接板的板载背光电路

在这里插入图片描述
原理图中为NC的器件不要贴,PWM背光上下拉电路默认上拉或者下来就行,FB反馈只要贴一个,两个都贴10Ω最终得到的电流就是40mA了超过了屏幕背光要求所以会发烫。
在这里插入图片描述

3.2 肉眼检查

焊接完成后不要立刻上电,先肉眼观察是否存在连锡、虚焊、锡珠、短路等情况。一般在工厂有飞针测试每个网络都测过去,但是你测量短路不肯能每个都去测量,一般只会选择测量电源等重要网络,所以看的时候仔细一点假如I2C_DSI_VCC_LED+网络短到其他GPIO上这种,你是没办法测试出来的。不然烧板子就麻烦了。
如果有耐心最好还去检查一下自己的原理图和PCB,因为FPC线序画反、网络错位、网络名字不对等问题太常见了,基本新手必犯。
在这里插入图片描述

3.3 测量短路

焊接完成后不要立刻上电,必须测量短路,使用万用表蜂鸣档测量VCC_3V3和GND,I2C_DSI_VCC_LED+和I2C_DSI_VCC_LED-,MIPI_DSI_VCC_LED+和MIPI_DSI_VCC_LED- 这几组网路是否有短路情况,没有才可以上电。

3.4 测量断路

断路会导致你的板子没办法正常点亮,因为断路带来的影响比前面小很多,个人经验是上电后没有达到预期效果才会去判断是否有断路情况,判断短路可以通过万用表蜂鸣档位测量整条线路是否导通,GPIO引脚也可以通过测量内阻的方式排查是否导通。

3.5 测试验证

排查完所有问题后给泰山派烧入固件,然后断电再接入扩展板。
因为第一次测试,你不确定问题是出在你自己编译的固件还是硬件,所以推荐首次测试使用后面附件中提供的固件进行测试,排除硬件问题以后再开始调试自己的代码。
在这里插入图片描述
正常情况屏幕会被点亮并进入系统、进入系统后可以正常触摸,如果用我们提供的固件依然没有办法正常工作就需要再返回检查上面步骤了。

第三部分 软件设计

调试触摸驱动时的操作
安卓,使用adb查看日志时
可以查看特定关键词的日志
在这里插入图片描述
以上是查看“触摸中断是否触发”
(程序中,应该也需要加“打印出日志”的程序,才可以被看到)
(MY_DEBUG是一个打印日志的函数 1:55:16)
在这里插入图片描述
"getevent"检查数据上报
在这里插入图片描述

泰山派连接ADB,在shell中输入命令,可直接进入loader,不用按板子上的两个按键
在这里插入图片描述

进入安卓系统,设置中,连按三次版本号,进入开发者模式,可打开触控点(显示指针位置)

修改驱动时,将驱动编译进内核,然后编译内核,只烧录boot
编译为内核较慢
编译为模块,然后push比较快

一、驱动开发

3.1mipi屏幕和触摸的驱动
屏幕背光驱动(触摸I2C1控制GP7101从而控制屏幕背光)
开机logo

1、打开设备树驱动

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

tspi-rk3566-dsi-v10.dtsi文件是各种mipi屏幕驱动的文件,需要在设备树文件中包含
若新加GP7101背光驱动,需要新建C和H文件后,在tspi-rk3566-dsi-v10.dtsi文件中新建设备树节点

user中包含mipi的dsi驱动文件,mipi的dsi驱动文件又新建了GP7101设备树节点,GP7101设备树节点中指向I2C地址。
2024.4.26 认识的不深,所以总结的不太好,继续往下看,加深理解

在这里插入图片描述

2、GP7101背光驱动

使用vscode时,bear工具可以实现代码跳转功能
具体再查查怎么实现
需要搭建环境
一种情况是 vscode+ssh到ubuntu

调试屏幕时,我们一般会先把背光点亮。
如果屏幕使用的是泰山派的背光电路(tspi的31pin接口通过转接板给屏幕供电),那直接使用代码里面默认的背光PWM驱动就行。
但为了保护屏幕(屏幕耐受25ma电流,泰山派最大100多ma,背光电路最大提供20ma),背光我们选择的是扩展板上的板载背光电路给3.1寸屏幕背光供电
扩展板板载背光电路PWM脚是通过GP7101 i2C转PWM芯片实现。所以我们需要编写一个GP7101驱动。
创建驱动的一般步骤

2.1 配置I2C1的设备树

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

图中是“写数据”的步骤,开始信号后,需要I2C发送一个地址(设备地址+写位)。
即 I2C中使用的是带读写位的地址,真正的设备地址是读写位地址右移一位后的地址。

在这里插入图片描述
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  
    };  
};
2.2 创建驱动

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

2.3 编写makefile文件

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

obj-y   += gp7101_bl.o

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

不一定像下方这样写
因为是目录,所以下面命令后面是一个斜杠(/)

 obj-y += my_gp7101_bl/

有点理解了
多层makefile套用
my_gp7101_bl下的Makefile负责将gp7101_bl.c编译到“内核”或“模块”。
上层目录(backlight目录下)的Makefile负责“执行my_gp7101_bl下的Makefile”。

2.4 编写gp7101_bl.c驱动

只是一个最简单的背光驱动
休眠 低功耗等功能还需要完善

2.4.1 I2C驱动框架

前面“触摸驱动章节”的时候,我们就学过了I2C驱动框架,所以直接把触摸I2C驱动框架复制过来修改一下,如果你没有看前面章节也没有关系,这个框架结构会用就行。

#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");
2.4.2 驱动中的结构体

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

/* 背光控制器设备数据结构 */
struct gp7101_backlight_data {
    /* 指向一个i2c_client结构体的指针*/
    struct i2c_client *client;
    /*......其他成员后面有用到再添加........*/
};
2.4.3 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; // 返回成功
}

2.4.4 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_client 或 struct platform_device。
  • name:背光设备的名称。
  • parent:背光设备的父设备,通常与 dev 参数相同。
  • devdata:私有数据,会被传递给背光操作函数。
  • ops:指向 backlight_ops 结构的指针,这个结构定义了背光设备的行为,包括设置亮度、获取亮度等操作。
  • props:指向 backlight_properties 结构的指针,这个结构包含了背光设备的属性,如最大亮度、当前亮度等。
2.4.5 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,
};
2.4.6 gp7101_backlight_set函数(背光底层函数)

这就是我们更新背光的核心函数了,每次背光被改动的时候系统都会回调这个函数,在函数中我们通过I2C1去写GP7101实现修改背光。
GP7101两种操作方法第一种是8位PWM,第二种是16位数PWM,刚好我们背光是从0~255所以,我们就选择8位PWM,八位PWM模式需要写寄存器0x03。
在这里插入图片描述

I2C写入时,先发送待写入的“设备地址”,相应的设备应答后,再发送该设备的“寄存器地址”,往某一具体的寄存器写入数据,从而设备进行底层操作

/* 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;  // 返回成功
}
2.4.7 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表示失败
}

2.4.8 注释backlight(注释tspi默认的背光驱动 防止冲突)

因为我们之前的背光驱动也是用的"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>;
        /*省略*/
    }}
2.5 完整GP7101驱动

文件放在E盘了,屏幕数据手册文档中 屏幕背光补丁

3、屏参调试

3.1 tspi-rk3566-dsi-v10.dtsi配置

tspi-rk3566-dsi-v10.dtsi大多数设备树都是已经定义好的(要想了解详细参数以及使用方法可以看前面章节)我们这次适配3.1寸触摸屏只需修改以下几个参数,其他保持默认即可。

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

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

dsi,lanes  = <4>;
改为
dsi,lanes  = <2>;
3.1.2 配置初始化序列

初始化序列是参考3.1寸屏幕厂商给的修改过来的,每款屏幕所使用的屏幕参都有些许不同,如果还不了解屏参调试的同学可以参考第五章节MIPI屏幕调试。

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                
];
3.1.3 配置屏幕时序

屏幕时序一般根据数据手册和厂家给的参考得来,以下是针对3.1寸屏幕修改好的参数。如果还不了解屏参调试的同学可以参考第五章节MIPI屏幕调试。

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>;
    };
};
3.2 完整代码补丁

放在E盘 3.1大显补丁

4、触摸驱动

4.1 配置I2C1设备树

从原理图中可知,GP7101(I2C转PWM芯片)和触摸共同挂在道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个引脚,有效电平为高电平
    };
   /****省略****/
};

4.2 创建驱动

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

4.3 编写makefile

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

obj-y   += my_touch.o

注:
与my_touch.c同目录的makefile,决定编译my_touch.c到内核还是到模块。
my_touch.c的上级目录的makefile决定是否编译my_touch.c。

4.4 my_touch.c驱动
4.4.1 I2C驱动框架

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

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");
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; // 触摸设备的中断号
};
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
}

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

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

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

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

在这里插入图片描述

4.4.7 TOUCHn_XH寄存器

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

在这里插入图片描述

4.4.8 TOUCHn_YH寄存器

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坐标

在这里插入图片描述

4.4.9 上报数据

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

4.5 完整代码补丁

放在E盘,触摸驱动补丁

5、开机logo更换

5.1 素材

找一张你想使用的开机logo图片,最好图片大小尺寸和屏幕的尺寸一样,比如我们这里随便找一张图片用来做测试。选择保存图片到本地。
在这里插入图片描述

5.2 BMP

一般图片保存到本地以后,大多都是png或者jpeg的格式,开机logo使用的是bmp格式,所以我们需要进一次图片格式转换,如果你有会用PS等工具的话,可以使用他们调整一下图片大小以及旋转方向,最后输出bmp,如果你懒,那也没有关系,转bmp的在线工具有很多,这里推进一个在线转换:https://onlineconvertfree.com/zh/convert-format/png-to-bmp/

5.3 替换

把转换完的logo代替kernel目录下的logo.bmp和logo_kernel.bmp。
在这里插入图片描述

5.4 生效

logo代替完成以后,要生效,还需要重新编译内核并更新内核,如果编译过程中出现logo相关报错,一般都是logo格式不对,重新生成bmp图片后在尝试编译。

5.5 图片格式报错

图片格式不对会导致错误
在这里插入图片描述

二、系统开发

1 Android系统

1.1 修改系统信息

使用我们提供的sdk编译出来后的Android11系统固件进入关于系统中,默认设备名称和型号都是rk3566_tspi,大家开发自己的产品时候,一般希望客户看到的是自己的商品名称,我们可以通用修改rk3566_tspi.mk实现。
路径:SDK/device/rockchip/rk356x/rk3566_tspi/rk3566_tspi.mk

PRODUCT_MODEL := rk3566_tspi
修改为:
PRODUCT_MODEL := my_fat_girl_phone

在这里插入图片描述

1.2 修改时区

Android11系统在联网的情况下会自动去同步时间,但是如果时区不是中国时区自动同步以后会有时差,所以我们需要把系统默认的时区修改成中国时区。
路径:SDK/device/rockchip/rk356x/rk3566_tspi/rk3566_tspi.mk

# 添加时区为亚洲上海
PRODUCT_PROPERTY_OVERRIDES += persist.sys.timezone=Asia/Shanghai
1.3 修改屏幕密度

屏幕密度决定了Android11系统UI的缩放布局以及选择的素材控件等,举个例子如果你屏幕是2K的分辨率,那么你密度就可以调大一点这个图标才协调,如果你用的是这次的3.1寸屏密度大了一个桌面就只能放下几个图标。
路径:SDK/device/rockchip/rk356x/rk3566_tspi/rk3566_tspi.mk

# 修改密度这里是240
PRODUCT_PROPERTY_OVERRIDES += ro.sf.lcd_density=240

在这里插入图片描述

1.4 预制apk(默认应用的apk装系统时自动安装)

泰山派开发板下载系统以后,如果有需要用到的apk,还需要自己手动安装,假如有100W个胖妞手机都需要用到这个apk应用,如果我们每个都去手动安装那基本上是不现实的,我们可以把需要安装的应用内置到系统sdk中,我们装系统的时候自动安装。

SDK/device/rockchip/rk356x/rk3566_tspi
preinstall             //不可卸载应用
preinstall_del         //可卸载卸载,恢复出厂设置复原应用
preinstall_del_forever //可卸载应用,恢复出厂设置不会复原

创建了一个preinstall目录并放入了一个yingyongbao.apk(应用宝)
在这里插入图片描述
编译以后会自动生成Android.mkpreinstall.mk
在这里插入图片描述
out目录下生成我们预制的应用
在这里插入图片描述

1.5 旋转屏幕

3.1寸屏幕默认是竖屏显示,想要修改成默认横屏需要调整3个地方,修改logo方向修改触摸方向修改android系统方向

1.5.1 修改logo方向

logo方向不是必须的,如果感觉logo是竖屏,系统是横屏不接受可以参考上面logo制作方法,使用图片编辑工具把logo图片旋转90度。
在这里插入图片描述

1.5.2 修改触摸方向

修改/kernel/drivers/input/touchscreen/my_touch/mytouch.c

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

还没有修改系统方向呢

重新编译内核并烧录
在这里插入图片描述

1.5.3 修改系统方向???
1.6 修改开机动画

开机显示完logo以后会进入系统之前会显示一段开机动画,默认的android系统开机动画是一个android的logo在闪光,我们可以自定自己的开机动画来代替默认开机动画。

1.6.1 创建文件夹

创建开机动画文件夹命名为bootanimation,并在文件夹中创建两个文件夹分别为part0,part1和一个文档desc.txt。
在这里插入图片描述
文件说明:

  • partN:用于存放动画图片,里面存放着多张连续的图片,开机会去播放里面的图片。如果part0播放完了,接着就来播放part1,以此类推N代表多个part,我们这里就演示两个。
  • desc.txt:描述图片信息以及显示的方式
800 480 10
p 1 0 part0
p 0 0 part1

第一行:800 480 10分别表示800*480分辨率和一秒钟播放10张图片。
第二行:p 1 0 part0分别表示p播放、1播放循环次数为一次,播放完后间隔0秒重复播放或播放下一个part。part0指目录。
第三行:p 0 0 part1分别表示p播放、0播放一直循环播放,播放完后间隔0秒重复播放或播放下一个part。part1指目录。

1.6.2 part目录

这里以part0目录为例,part1大家举一反三,part中对图片的命名格式有明确要求,如果不按照要求来系统无法识别。规则如下:

  • 建议使用png格式图片
  • 图片的序列是连续的
  • 如果图片个数小于10张图片的命名规则是01.png到09.png
  • 如果图片个数小于100张图片的命名规则是001.png到099.png
  • 如果图片个数小于1000张图片的命名规则是0001.png到0999.png

下面素材中提供的图片有117张所以命名就是0001.png到0117.png
在这里插入图片描述

1.6.3 打包压缩资源

打包这里大家一定要非常注意,不是直接压缩bootanimation目录,这样压缩后会多一层bootanimation目录导致系统无法识别,需要进入bootanimation目录选择所有文件压缩。压缩等级改为仅存储。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.6.4 修改SDK

把上面准备好的bootanimation.zip资源文件复制到SDK/device/rockchip/rk356x目录下。
在这里插入图片描述
打开SDK/device/rockchip/rk356x/device.mk文件在末尾追加下面代码。

PRODUCT_COPY_FILES += $(LOCAL_PATH)/bootanimation.zip:system/media/bootanimation.zip

追加后的效果(看最后一行)
在这里插入图片描述

1.6.5 快速验证

每次制作完成logo后,需要看效果,都要重新编译然后烧录非常浪费时间,我们可以通用adb命令直接把制作好的bootanimation.zip动画资源push到泰山派开发板中,重启验证修改后的效果。

adb root && adb remount && adb push Z:\tspi\Android11_20231007\PublicVersion\device\rockchip\rk356x\bootanimation.zip /system/media/bootanimation.zip

注意语法

1.6.6 制作动画短片(可跳过)

ps导入一个视频,并把视频转成一帧帧图片资源。演示中的所有资源文件在文末附件中提供,如果不制作自己的logo可以直接跳过用现成的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面导出以后命名是到图层的,为了满足我们前面说的规则,这里写了一个脚本自动转换。

import os
import shutil

# 源文件夹路径
src_folder = './hztest/'

# 目标文件夹路径
dest_folder = './out/'

# 重命名函数
def rename_files(src_folder, dest_folder):
    # 确保目标文件夹存在
    if not os.path.exists(dest_folder):
        os.makedirs(dest_folder)

    # 遍历源文件夹中的文件
    for filename in sorted(os.listdir(src_folder)):
        # 提取文件名和扩展名
        name, ext = os.path.splitext(filename)
        
        # 提取图层号
        layer_num = int(name.split(' ')[-1])
        
        # 根据图层号重命名文件
        if layer_num < 10:
            new_filename = f"000{layer_num}{ext}"
        elif layer_num < 100:
            new_filename = f"00{layer_num}{ext}"
        else:
            new_filename = f"0{layer_num}{ext}"
        
        # 拷贝文件并重命名
        shutil.copy(os.path.join(src_folder, filename), os.path.join(dest_folder, new_filename))
        print(f"Renamed {filename} to {new_filename}")

# 调用重命名函数
rename_files(src_folder, dest_folder)

在这里插入图片描述

1.7 编译生效

Android11直接使用make编译上面的修改都不会生效,要生效需要installclean以后在编译,千万不要clean,clean以后要编译大半天,installclean只需1-5分钟就好了。

source build/envsetup.sh && lunch rk3566_tspi-userdebug && make installclean -j88 && make -j88 && ./mkimage.sh

在这里插入图片描述
编译完成以后需要重新烧录固件
在这里插入图片描述

1.8 效果演示

在这里插入图片描述

1.9 完整补丁

胖妞手机安卓代码补丁-默认横屏

2 Ubuntu系统

2.1 QT AI 桌面助手
2.2 安装PYQt5
2.3 安装PYQt5-tools
2.4 安装QT Designer
2.5 vscode适配
2.6 常用控件使用
2.7 机器人UI实现
2.8 关键词唤醒
2.9接入AI大模型

第四部分 外壳

1 模型文件

手机外壳由“顶壳”和“底壳”组成。
因为转接板上新加了“12VPD供电”和"UART串口",增加的type-c增加了板的高度,导致了mini转接板和泰山派之间的距离增加,于是跟据官方提供的3D文件,使用SW重新设计外壳。并根据新的距离重新确定pogopin和贴片螺母柱。
在SW中评估二板的距离,选一个整数的距离,去某宝看是否能找到该距离的贴片螺母柱,再根据该距离找一个弹簧压缩范围包含该距离的pogopin。选型结束。
因为两板间增加了距离,所以需要重新设计官方的3D文件。
需要注意的是,设计完成后主芯片的散热片位置的外壳去掉
将“胖妞手机”logo去掉,命名为“new hope”,希望重新购买的泰山派能够让自己好好学懂linux。

1.1 顶壳

图中三个按键下方,高出来一块,触碰到泰山派上的三个按键。
这个设计不错,省去了买导光柱的成本
在这里插入图片描述

1.2 底壳

在这里插入图片描述

2 3D外壳下单

2.1 上传模型

下单助手,进行3D打印下单
在这里插入图片描述
在这里插入图片描述

2.2 材料选择

泰山派满载运行时温度60-70度,底壳靠近屏幕,且有转接板与泰山派相隔,所以底壳选用树脂。
在这里插入图片描述
顶壳靠近泰山派,选用热变形温度更高的树脂
在这里插入图片描述
结合30元3D打印券,综合考虑后,提交订单。
在这里插入图片描述

2.3 快递

选择好地址和下单联系人后,选择最便宜的EMS快递
在这里插入图片描述

2.4 使用优惠券下单

在这里插入图片描述
下单后,选择“接受风险”,等待审核通过后,支付费用,等待快递到来。

3 组装

3.1 开发板

3.2 屏幕扩展板

3.3 喇叭

3.4 上壳

3.5 下壳

3.6 螺丝固定

3.7 底板

第五部分 附件

默认横屏的固件

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Truffle是以太坊智能合约开发的常用框架之一。它提供了一系列工具和库,可以帮助我们更加高效地开发、测试和部署智能合约。下面,我将简单介绍一下Truffle的基本用法。 Truffle的安装 首先,我们需要在本地安装Truffle。在命令行窗口中输入以下命令: ``` npm install -g truffle ``` 如果你在国内使用npm速度很慢,可以考虑使用cnpm(淘宝镜像): ``` npm install -g cnpm --registry=https://registry.npm.taobao.org cnpm install -g truffle ``` Truffle的使用 Truffle提供了一系列命令行工具,可以帮助我们进行合约的编译、测试和部署等操作。下面,我列举几个常用的命令。 1. 初始化项目 ``` truffle init ``` 这个命令会在当前目录下创建一个新的Truffle项目。Truffle项目结构如下: ``` contracts/ // 合约文件目录 migrations/ // 部署脚本目录 test/ // 测试文件目录 truffle-config.js // Truffle配置文件 ``` 2. 编译合约 ``` truffle compile ``` 这个命令会编译contracts目录下的所有合约文件,并将编译结果保存在build/contracts目录下。 3. 部署合约 ``` truffle migrate ``` 这个命令会执行migrations目录下的所有部署脚本。部署脚本是以数字命名的JavaScript文件,Truffle会按照数字顺序执行所有脚本。每个脚本都需要定义一个合约对象,并使用module.exports导出。 4. 运行测试 ``` truffle test ``` 这个命令会运行test目录下的所有测试文件,并输出测试结果。 除了以上命令,Truffle还提供了很多其他的功能,比如调试工具、交互式控制台等。在实际开发中,你可以根据需要选择使用。 总结 Truffle是一个强大的以太坊智能合约开发框架,可以帮助我们更加高效地开发、测试和部署智能合约。在使用Truffle时,我们需要熟悉它的基本用法,并根据实际需求选择使用不同的功能。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值