【智能家居项目】裸机版本——设备子系统(LED Display 风扇)

🐱作者:一只大喵咪1201
🐱专栏:《智能家居项目》
🔥格言:你只管努力,剩下的交给时间!
图

图
输入子系统中目前仅实现了按键输入,剩下的网络输入和标准输入在以后会逐步实现,今天先来实现设备子系统,包含LED设备(GPIO控制),风扇设备,OLED设备。

目录

  • 🍛设计思路
  • 🍛LED设备
    • 🍚设备层
    • 🍚内核抽象层
    • 🍚芯片抽象层
    • 🍚硬件操作
    • 🍚单元测试
  • 🍛显示设备
    • 🍚管理及设备层
    • 🍚内核抽象层
    • 🍚芯片抽象层及硬件操作
    • 🍚单元测试
  • 🍛风扇设备
    • 🍚设备层
    • 🍚内核抽象层
    • 🍚芯片抽象层
    • 🍚硬件操作
    • 🍚单元测试

🍛设计思路

不同内核下是访问设备的方式是不同的:

  • 裸机里怎么访问设备?

对于ST芯片可以使用HAL库访问设备,对于一些国产芯片可以使用厂家自己封装的库,甚至自己编写代码直接访问寄存器来访问设备。

  • FreeRTOS怎么访问设备?

FreeRTOS中并没有驱动程序框架,它访问设备时方法和裸机一样。

  • RT-Thread怎么访问设备?

RT-Thread可以使用两种方法访问设备:

  1. 像裸机一样访问
  2. 使用RT-Thread的驱动程序框架

图
如上图便是RT-Thread驱动程序框架的示意图,所谓驱动框架就是事先定义好的接口函数,无论是访问设备还是添加新设备,直接调用这些接口函数即可。

所以我们要做的就是实现这些接口函数好让应用层去直接调用,这样做的好处是,无论硬件怎么修改,驱动程序的接口并不会改变,上层的应用程序也就不需要改变。

tu
如上图所示是RT-Thread驱动框架下“I/O设备管理”的接口,应用程序通过标准的接口来访问设备:

  • rt_device_find:查找设备
  • rt_device_open:打开设备
  • rt_device_read:从设备中读取数据
  • rt_device_write:向设备中写数据
  • Linux下怎么访问设备?

Linux系统中,和驱动程序严格分离开,应用程序无法直接读写寄存器,应用程序必须通过驱动程序访问设备,应用程序使用的接口只有open/read/write/ioctl等。

图
如上图所示,APP就是应用程序,在Linux下一切皆文件,所以设备在Linux内核中都被抽象成了一个文件,所以应用层必须通过系统调用接口通过文件系统来访问设备。


图
再来看我们要实现的设备子系统,目前暂时将其分为两层,虚线之上写出统一的应程序接口,虚线之下根据不同的系统,调用不同的函数,这样一来就统一了设备的访问方式。

对于开发应用程序的人,他不关心LED使用哪个GPIO引脚,GPIO是输出高还是低来控制LED,open的是什么设备、read/write的又是什么设备,也不用去看原理图,芯片手册,以及研究HAL库,RT-Thread,或者Linux的驱动函数怎么调用。

甚至不用理解我们抽象出来的设备结构体,只需要关心结构体中的InitContral两个函数指针怎么使用即可,其他的一概不用关心,所以非常有必要提供更高层的API接口。

以LCD的使用为例,可以分为三层:

图
如上图,其中设备驱动层中的驱动程序负责提供像素操作的功能,但是显示什么字符、显示多大、在哪显示,不用关心。

文字/图像显示层中包含库函数/函数功能,提供显示字符、显示图片功能的接口,但是显示什么字符、显示多大、在哪显示,不用关心。

应用程序则通过库函数来显示字符以及图片,并且决定显示什么字符,显示在哪里,显示多大等问题,但是并不用关心驱动程序。

  • 驱动只提供功能,不提供策略,应用程序只提供策略,不提供功能的实现。
  • 不同的层要各司其职不要越界,实现不同专业知识的隔离。

在实现设备子系统的时候,使用面向对象的思想,对于每一种设备,抽象出一个结构体,结构体里有设备相关的函数指针,不同设备,不强求统一,不强求用一个结构体类型来支持所有设备。

编写函数时要注意:

  • 头文件:这些函数是面向应用程序开发者,假设他们对硬件一无所知。
  • C文件:函数内部,再根据不同系统、不同芯片,调用其他函数。

🍛LED设备

图
如上图所示,我们把LED设备分为4层,接下来就是一层一层来实现。

🍚设备层

LED设备的功能:

  • LED的开和关
  • 设置LED的颜色
  • 设置LED的亮度

虽然本喵使用的LED灯并不支持设置颜色和直接设置亮度,但是有一些高级的LED灯是支持的,这里我们为了以后更好的维护和扩展,所以在抽象LED设备结构体的时候都考虑上:

图
如上图所示,抽象出来的LEDDevice结构体就是用来描述LED设备的,包含LED编号,初始化和控制方法,以及设置颜色和亮度的方法,还需要在LED的设备层提供一个获取LED设备的方法。

图
如上图所示,因为LED设备只有三个,所以通过一个全局的LEDDevice数组来管理LED设备,对每一个LED设备进行了初始化,实现了LED设备的初始化函数以及控制函数,为了达到分层的目的,在函数内部调用内核抽象层的初始化和控制函数。

至于设置颜色和亮度的函数并没有实现,因为本喵使用的LED灯并不支持这两种功能,还实现了一个获取LED设备的函数,在上层使用访问LED设备的时候,必须先调用该函数获取LED设备。

🍚内核抽象层

图
如上图,无论是访问LED设备还是控制LED设备,甚至初始化LED设备,在内核抽象都有不同的方式,这里定义一个配置文件,用来控制是使用哪种方式:

图

如上图所示,用到哪种内核就定义相应的标识符常量。

图
如上图所示头文件,提供了内核抽象层对LED设备初始化和控制的函数声明。

图
如上图所示内核抽象层源文件,提供了LED设备初始化和控制函数的实现,通过宏开关来控制调用对应内核下的LED初始化和控制函数。

其中对于RT-ThreadLinux下的LED设备初始化和控制函数本喵在这里并不会实现,这里仅仅是为了给大家演示本喵的代码架构写的。

  • 对于裸机和FreeRTOS,LED设备的初始化和控制函数都是一样的。

🍚芯片抽象层

图
如上图,在真正访问芯片的时候,不同类型的芯片支持不同的库,同样可以通过宏开关来控制使用哪种库来访问芯片:

#define CONFIG_SUPPORT_HAL 		0x10
//#define CONFIG_SUPPORT_OTHER 	0x20

将这部分代码加入到config.h中去。

图
如上图头文件代码,提供了芯片抽象层LED设备初始化和控制的函数声明。

图

如上图源文件代码,提供了芯片抽象层LED设备初始化和控制函数的实现,对于HAL库,调用对应的HAL库对应的初始化和控制函数,对应其他库则调用其他方式。

🍚硬件操作

本喵使用的是ST的芯片,所以使用HAL库来操作硬件:

图
如上图所示头文件,提供了LED设备的GPIO信息以及初始化和控制的函数声明。

图
如上图所示源文件,提供了初始化和控制函数的实现,在控制函数中,switch通过LEDDevice结构体中的LED编号操作具体的LED设备。

🍚单元测试

LED设备从设备层到操作硬件共五层已经实现了,下面就进行单元测试,看是否符合预期:

图
如上图所示LED设备的测试函数,其中LED点亮和熄灭中间的延时,为了应用层和HAL库的解耦,本喵只是通过循环进行了一个大概的循环,有兴趣的小伙伴可以按照五层的框架,在最底层调用HAL_Delay()自己实现一下。

图
如上图,白蓝绿三个灯在不停闪烁。

🍛显示设备

为了让这里的代码更加容易维护和扩展,适用于多种类型的显示设备,本喵通过LCD显示的原理来抽象出一个具有普适性的显示设备结构体,虽然本喵最终用的是OLED屏幕,但是仍然可以用这个结构。

LCD显示原理:

图

什么是LCD?就是多行多列的像素点:

  • 对于黑白屏幕(单色屏幕),这些像素点只有2个状态:点亮、熄灭。
  • 对于彩色屏幕,这些像素点有颜色:可以用RGB三原色来表示。

怎么控制LCD上每个像素的状态?

  • 通过显存,就是一块内存,也被称为FrameBuffer
  • 每个像素在显存上都有对应的数据,会在屏幕上对应位置显示。
  • 对于黑白屏(单色屏),每个像素点在显存里有对应的1位数据,只有两种状态,表示点亮和熄灭。
  • 对于彩色屏,每个像素点在显存里有几个字节(可能是1字节、2字节、4字节),可以表示多种颜色。
  • BPP:Bits Per Pixel,用来表示每个像素点有多少位数据。

LCD可能自带显存,也可能不带有显存(要使用LCD的话,就需要在系统内存中分配显存),有三种类型的LCD。

  1. LCD含有显存, CPU通过I2C协议访问显示

图
如上图,很多I支持I2C、SPI接口的屏幕,本身是含有显存的,要在LCD上显示文字、图片,就需要网显存里写入数据,程序通过I2C接口写显存,LCD控制器就会让LCD屏幕显示相应内容。

  1. LCD没有显存, LCD控制器从内存得到数据

图
如上图,很多TFT LCD本身是没有显存的,那么数据保存在系统内存里分配一块空间,这块RAM中的内存就可以看作是显存。设置好LCD控制器后,它就会自动从这个显存取出数据、发送给LCD,我们只需要写数据到RAM中的显存即可。

  1. LCD含有显存, CPU可以直接访问

如
如上图,有些LCD含有显存,并且CPU可以直接访问显存,不需要I2C灯协议,就像访问一般内存一样直接访问显存,我们只需要写数据到显存即。


对于软件来说,这3种LCD都有显存,第1种无法直接写显存;第2、3种可以直接写显存,能否让第1种LCD,能否也直接写显存?可以:

  • 在系统内存RAM中分配另一个显存FrameBuffer
  • 应用程序直接写这个显存。
  • 设备层等下层再通过I2C将RAM显存中的数据发送到LCD自带的显存中。

图
如上图,同样按照五层来实现程序。

🍚管理及设备层

在设备层要抽象出一个显示设备的结构体,根据前面的分析以及LED设备的经验,该结构体包含:

  • 初始化函数,用来初始化显示设备。
  • 在RAM中都有显存,通过显存的起始地址,显示设备的分辨率,以及bpp来描述显存。
  • 对于第一种LCD,还需要一个Flush函数,把RAM显存中的数据刷到LCD显存中去。

图

如上图头文件中代码,抽象出了显示设备的结构体,用这个结构体可以描述多种类型的显示设备,包括LCD和OLED,这些显示设备可以用链表管理起来。

  • void* FBBase:显示设备的显存。

图
如上图所示源文件,创建了一个静态的全局显示设备链表,用这个链表来管理所有显示设备,提供了向链表中注册新设备的函数,以及从链表中获取设备的函数。

  • 由于pDisplayDevice链表头指针被static修饰了,就只能在当前源文件访问和操作这个链表。
  • 使用__修饰的函数表明该函数会在其他位置被调用,而且要实现的功能相同。

此时设备层的宏观描述已经有了,接下来就是创建具体的显示设备了,这里创建的是OLED屏。

图

如上图所示,创建一个全局的DisplayDevice变量用来实例化OLED设备,根据OLED的特性填充该结构体变量,其中RAM中的显存就是一个全局的数组,用static修饰的初始化和刷新显存的函数初始化结构体中的方法。

对于点亮(X , Y)处像素的函数本喵单独讲解一下:

图

如上图所示坐标系便是显存抽象出来的坐标,其中左上角的坐标原点就是RAM显存数组的首地址,本喵使用的OLED是128*64的,所以可以分为8页,128列。

现在要点亮坐标(x, y)处的像素点,所以要计算出该像素点位于RAM显存中的哪个位置,然后在程序中向该位置写入数据即可。

共有64行像素点,每8行分为一页,所以(x, y)所在页为page = y / 8,由于RAM中的显存是一个数组,它并不存在页的概念,所以可以计算出(x,y)对应那个字节在数组中的下标是page*128 + x,地址就是FrameBuffer + page * 128 + x由于一个字节有8个bit,而每一个像素点用一个bit,所以(x, y)所用那个bit在该字节中是第y % 8个bit。

图

如上图代码所示,在将(x, y)坐标所对应的显存位置计算出来后,根据dwColor颜色值来决定是点亮该像素点函数熄灭,或者是用哪种颜色点亮。

  • OLED不支持显示不同颜色,所以dwColor的值不为0则表示点亮,为0则表示熄灭。

图

如上图所示代码便是OLED设备剩下的代码,在添加OLED显示设备的时候用到了static修饰的g_DisplayDevice全局变量,该变量同样只能在该源文件内使用。

AddDisplayDeviceOLED函数中调用的DisplayDeviceRegister注册函数是属于display_device.h文件中的,所以需要包含该头文件。

display_device中需要有添加显示设备的函数AddDisplayDevices,在该函数中势必会调用oled_device.h中的AddDisplayDeviceOLED函数,所以要包含该头文件。

  • 此时就产生了互相包含的场景,display_device中包含oled_device.holed_device中包含display_device.h

为了避免互相包含,再创建一个dispaly_system.c文件来管理这两个导致互相包含的函数。

tu
如上图所示,在这里实现AddDisplayDevices更加合适,而获取设备的方法也是放在这里更合适。


图
如上图所示便是这些文件中的内容概述和调用关系,这些所有内容构成了整个设备层。

🍚内核抽象层

内核抽象层需要实现的只有显示设备的初始化已经将数据刷新到OLED自带的显存中的Flush函数。

图
如上图代码,同样为了方便或者和维护,对于不同的内核,调用不同的方式,本喵使用的是裸机,所以需要调用芯片抽象层对应的函数。

🍚芯片抽象层及硬件操作

芯片抽象层同样是分为支持HAL库和支持其他库,只是需要实现初始化函数和CAL_OLEDDeviceFlush函数即可。

图

如上图所示,对于HAL库,在初始化的时候直接初始化I2C协议用到的引脚,以及调用OLED屏幕的初始化函数,这里部分内容本喵在I2C通信协议 | OLED屏一文中有详细讲解,这里本喵就直接使用了。

CAL_OLEDDeviceFlush函数中,对于HAL库,调用OLED_Copy拷贝函数将数据从RAM显存中搬运到OLED自带的显存中,该拷贝函数本喵没有实现过,这里实现一下:

图

如上图,将RAM显存中的数据分八次写入OLED自带的显存中,每次写入一个page的数据,也就是128字节。

🍚单元测试

显示设备的五层实现以后,同样需要进单元测试:

图

如上图所示,在测试显示设备的时候,先添加所有显示设备(本喵这里只有一个OLED显示设备),然后从设备管理列表中获取名为OLED的设备,如果不存在则打印错误信息。

获取成功后,先调用显示设备的初始化函数进行初始化,然后清屏,就是将RAM中的显存全部清0,再调用显示设备中的SetPixel设置像素函数在OLED屏幕上画一个十字。

图
如上图所示,在OLED屏幕上显示了一个十字。

🍛风扇设备

图
如上图所示,在本喵的开发板上有一个电机模块,上面有一个风扇,可以控制它顺时针转,逆时针转,停止。

图
如上图所示是电机接口模块示意图,风扇的控制非常简单,只需要改变PC6PE6也就是INBINA口的电平状态就可以,真值表如下:

风扇状态INAINB
顺时针旋转01
逆时针旋转10
停止00
停止11

其中0表示该接口输入低电平,1表示该接口输出高电平。风扇设备分为四层,本喵就不详细讲解了,直接上代码。

🍚设备层

图
如上图所示头文件代码,定义了描述风扇设备的结构体,包含风扇的速度属性,初始化函数,设置速度的函数,当前速度属性是多余的,本喵这里并不会控制速度,包括设置速度函数也只是控制风扇的启停和正反转。

如果使用PWM波来控制电风扇就可以控制其速度,所以为了以后方便扩展维护,本喵就保留了速度这个属性。

图
如上图所示是源文件,创建了一个全局的风扇设备变量来实例化,并进行了初始化,还提供了获取风扇设备的函数,不再多说。

🍚内核抽象层

图

如上图所示,对于不同的内核调用相应层的函数即可,不再讲解。

🍚芯片抽象层

图

如上图代码,在初始化函数中,对于HAL库,由于在使用CubeMX创建工程的已经配置好了引脚,所以这里不用再调用HAL库的初始化函数了,直接返回0即可,表示初始化成功,对于设置速度的函数则需要调用HAL库中的设置速度函数。

🍚硬件操作

图
如上图代码所示,根据iSpeed的不同情况,对照风扇的真值表设置INAINB引脚的电平状态即可。

🍚单元测试

图

如上图所示测试代码,让风扇顺时针转2秒,停止2秒,逆时针转2秒,停止2秒,如此循环。

为了实现应用层的独立,延时函数使用的是KAL_Delay,其最在芯片抽象层仍然是封装的HAL_Delay

图
如上图代码所示,实现了延时函数,头文件自己加就行,本喵不在讲解。

图
如上图所示是风扇转起来的状态,符合我们的要求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/93581.shtml

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

相关文章

【网络安全-sqlmap】sqlmap以及几款自动化sql注入工具的详细使用过程,超详细,SQL注入【5】

一,sqlmap 工具的详细使用 kali系统自带这个工具,无需安装直接sqlmap 后面接参数使用 Windows上参照以下方法安装即可 1-1 工具下载 1-1-1 sqlmap下载 sqlmap 工具下载地址: GitHub - sqlmapproject/sqlmap: Automatic SQL injection a…

Unity实现设计模式——状态模式

Unity实现设计模式——状态模式 状态模式最核心的设计思路就是将对象的状态抽象出一个接口,然后根据它的不同状态封装其行为,这样就可以实现状态和行为的绑定,最终实现对象和状态的有效解耦。 在实际开发中一般用到FSM有限状态机的实现&…

【JavaEE】JavaScript webAPI的基本知识

JavaScript Web API 文章目录 JavaScript Web APIwebAPI背景DOMDOM树 获取元素querySelectorquerySelectorAll 事件初识键盘事件onkeydownonkeypressonkeyup 操作元素获取/修改元素内容1.innerText2.innerHTML 获取/修改元素属性获取/修改表单元素属性获取/修改样式属性行内样式…

AI智能问答系统源码/AI绘画商业系统/支持GPT联网提问/支持Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于国外很火的ChatGPT进行开发的AI智能问答系统和AI绘画系统。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT?小编这里写一个详细图…

TinyWebServer学习笔记-MySQL数据库连接

为什么要用数据库连接池? 我们的网站允许用户注册,在没有池的情况下,假设只有一个人,那么流程就是,用户点击注册,通过socket将用户的账号和密码发送到服务器,然后就需要创建MySQL数据库连接&am…

【Linux】文件权限详解

🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的…

使用Docker部署ElasticSearch7+ELK(附带ES操作操作命令集)

ElasticSearch 7ELK 程序安装Docker安装下载ES镜像提前创建挂载文件夹添加配置文件创建并启动容器可能出现的异常安装IK分词使用ElasticHD客户端工具(目前使用发现无法做增删改)安装Kibana 软件包安装安装ElasticSearch(需要JDK1.8)安装IK(下…

【时区】Flink JDBC 和CDC时间字段时区 测试及时间基准

关联文章: 各种时间类型和timezone关系浅析 一、测试目的和值 1. 测试一般的数据库不含time zone的类型的时区。 mysql timestamp(3) 类型postgres timestamp(3) 类型sqlserver datetime2(3) 类型oracle类型 TIMESTAMP(3) 类型 在以下测试之中均为ts字段 2.测试CDC中元数据…

【数据结构】堆的应用-----TopK问题

目录 一、前言 二、Top-k问题 💦解法一:暴力排序 💦解法二:建立N个数的堆 💦解法三:建立K个数的堆(最优解) 三、完整代码和视图 四、共勉 一、前言 在之前的文章中&#xff…

【Kubernetes】当K8s出现问题时,我们可以从哪些方面排查出

前言 kubernetes,简称K8s,是用8代替名字中间的8个字符“ubernete”而成的缩写。是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kub…

python中dir()和help()的作用

在 Python 中,dir() 和 help() 是两个常用的内置函数,用于获取对象的属性和方法信息以及提供帮助文档。 dir(object) 函数返回一个包含对象 object 的属性和方法名称的列表。如果没有提供参数,则返回当前作用域内的所有名称。例如&#xff0…

DAMA-DMBOK2重点知识整理CDGA/CDGP——第14章 大数据与数据科学

目录 一、分值分布 二、重点知识梳理 1、引言 1.1 业务驱动因素 1.2 原则 1.3 基本理念 2、活动 2.1 定义大数据战略和业务需求 2.2 选择数据源 2.3 获得和接收数据源 2.4 制定数据假设和方法 2.5 集成和调整数据进行分析 2.6 使用模型探索数据 2.7 部署和监控 …

Linux CentOS7 vim多文件与多窗口操作

窗口是可视化的分割区域。Windows中窗口的概念与linux中基本相同。连接xshell就是在Windows中新建一个窗口。而vim打开一个文件默认创建一个窗口。同时,Vim打开一个文件也就会建立一个缓冲区,打开多个文件就会创建多个缓冲区。 本文讨论vim中打开多个文…

【单片机】14-I2C通信之EEPROM

1.EEPROM概念 1.EEPROM 1.1 一些概念 (1)一些概念:ROM【只读存储器---硬盘】,RAM【随机访问存储器--内存】,PROM【可编程的ROM】,EPROM【可擦除ROM】,EEPROM【电可擦除ROM】 1.2 为什么需要EE…

应用大五人格测试,来做个人职业规划

应用大五人格测试做职业规划,一方面能让大学生清楚知晓自己的性格特征,置身适合怎样的职业,让他们结合自己的兴趣爱好职业走向,进一步了解自己适合做的职业规划。另一方面可以帮助他们结合职业规划来安排人生,一步一个…

Transformer学习-self-attention

这里写自定义目录标题 Self-attentionMulti-head self-attention用self-attention解决其他问题 Self-attention 用Wq、Wk、Wv分别乘输入向量得到q、k、v向量 用每个q向量乘所有的k向量得到对应项的attention,即用每项的query向量去匹配所有的key向量,得…

【进阶C语言】自定义类型

本节内容大致目录如下: 1.结构体 2.位段 3.枚举 4.联合(共用体) 以上都是C语言中的自定义类型,可以根据我们的需要去定义。 一、结构体 一些基础知识在初阶C语言的时候已经介绍过,在这里粗略概括;重…

代码随想录算法训练营第五十五天 | 动态规划 part 12 | 300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

目录 300.最长递增子序列思路代码 674. 最长连续递增序列思路代码 718. 最长重复子数组思路代码 300.最长递增子序列 Leetcode 思路 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度递推公式:if (nums[i] > nums[j]) dp[i] max(dp[i], dp[j] 1)初…

面试总结之Spring篇

一、AOP 1、什么是AOP 1.1、概述 AOP(Aspect-Oriented Programming):面向切面编程,即把一些业务逻辑中的相同代码抽取出来,让业务逻辑更加简练清爽 如果要CRUD写一堆业务,可如何实现业务代码前后进行打印…

计算机竞赛 深度学习驾驶行为状态检测系统(疲劳 抽烟 喝水 玩手机) - opencv python

文章目录 1 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习的驾…