正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.5

 前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》第 8.1 章

《正点原子资料_A盘/02开发板原理图/IMX6ULL_MINI_V2.2(Mini底板原理图).pdf》

  • 资料盘 开发板资料链接: https://pan.baidu.com/s/1j5Jzbdx9i-g0cWIi3wf2XA 提取码:ag1u


正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第6.5讲” 的读书笔记。第6.5讲 I.MX6U 芯片的启动方式。

0. I.MX6U 启动方式详解

I.MX6U 支持多种启动方式以及启动设备,比如可以从 SD/MMC,NAND Flash,QSPIFlash 等启动。用户可以根据实际情况,选择合适的启动设备。不同的启动方式,其启动方式和启动要求也不一样,比如上一张的从 SD 卡启动就需要在 bin 文件前面添加一个数据头,其它的启动设备也是需要这个数据头的。本章我们就来学习一下 I.MX6U 的启动方式,以及不同设备启动的要求。

1. 启动方式的选择

BOOT 的处理过程是发生在 I.MX6U 芯片上电以后,芯片会根据 BOOT_MODE[1:0] 的设备来选择 BOOT 方式。BOOT_MODE[1:0] 的值是可以改变的,有两种方式,一种是改写 eFUSE (熔丝),一种是修改相应的 GPIO 高低电平。第一种修改 eFUSE 的方式只能修改一次,后面就不能再修改了,所以我们不使用。我们用的是通过修改 BOOT_MODE[1:0] GPIO 对应的高低电平来选择启动方式,所有的开发板都使用的这种方式,I.MX6U 有一个 BOOT_MODE1 引脚和 BOOT_MODE0 引脚,这两个引脚对应 BOOT_MODE[1:0] 。I.MX6U -ALPHA/Mini 开发板的这两个引脚的电路原理图如下图所示:

其中 BOOT_MODE1 和 BOOT_MODE0 在芯片内部是有 100KΩ 下拉电阻的,所以默认是0。 BOOT_MODE1 和 BOOT_MODE0 这两个引脚我们也接到了底板上的拨码开关上,这样我们就可以通过拨码开关来控制  BOOT_MODE1 和 BOOT_MODE0 的高低电平。以  BOOT_MODE1 为例,当我们把 BOOT_CFG 的第一个开关拨到 “ON” 位置时,就相当于 BOOT_MODE1 引脚通过 R88 这个 10K 电阻接到了 3.3V 电源,芯片内部的 BOOT_MODE1 又是 100K 下拉电阻接地,因此 BOOT_MODE1 的电压就是 100(10+100)*3.3v=3V,这个就是高电平,因此 BOOT_CFG 的8个开关拨到“ON” 就是高电平,拨到“OFF”就是低电平。

而 I.MX6U 有四个 BOOT 模式,这四个 BOOT 模式由 BOOT_MODE[1:0] 来控制,也就是 BOOT_MODE1 和 BOOT_MODE0 这两个IO,BOOT 模式的配置如下表所示:

BOOT_MODE[1:0]BOOT类型
00从 FUSE 启动
01串行下载
10内部BOOT模式
11保留

在 表 9.1.1 中,我们只用到第二种和第三种BOOT方式。

1.1 串行下载

当 BOOT_MODE1 为0, BOOT_MODE0=1的时候使能此模式,串行下载的意思就是可以通过USB或UART将代码下载到板子上的外置存储设置中,我们可以使用 OTG1 这个 USB 口向开发板上的 SD/EMMC ,NAND 等存储设备下载代码。我们需要将 BOOT_MODE1 拨到 “OFF”,将 BOOT_MODE0 拨到“ON”。这个下载需要用到NXP提供的一个软件,一般用来最终量产的时候将代码烧写到外置的存储设备总的,我们后面讲解如何使用。

1.2 内部BOOT方式

当 BOOT_MODE1 为1, BOOT_MODE0=0的时候使能此模式,在此模式下,芯片会执行内部 bootROM 代码,这段 bootROM 代码会进行硬件初始化(一部分外设),然后从 boot 设备(也就是存放代码的设备,比如 SD/EMMC,NAND)中将代码拷贝复制到RAM中,一般是放在DDR中。

2. BOOT ROM 初始化内容

当我们设置 BOOT 模式为 “内部BOOT模式”以后,I.MX6U 的把内部 boot ROM 代码就是执行,这个 boot ROM 代码会做什么处理呢?首先肯定是初始化时钟,boot ROM 设置的系统是中国如下图所示:

在上图中 BT_FREQ 模式为0,可以看到,boot ROM 会将 I.MX6U 的内核时钟设置为 396MHz,也就是主频为 396MHz。System PLL=528Mhz,USB PLL=480MHz,AHB=132MHz,IPG=66MHz。关于I.MX6U的系统时钟,我们后面会详细讲解。

内部 boot ROM 为了加快执行速度回打开 MMU 和 Cache:

  • 下载镜像的时候 L1 ICache 会打开,
  • 验证镜像的时候 L1 DCache,L2 Cache 和 MMU 都会打开。
  • 一旦镜像验证完成,boot ROM 就会关闭 L1 DCache,L2Cache 和 MMU 

中断向量偏移会被设置到 boot ROM 的起始位置,当 boot ROM 启动了用户代码以后就可以重新设置中断向量偏移了。一般是重新设置到我们用户代码开始的地方,关于中断的内容后面会详细讲解。

3. 启动设备

当 BOO_MODE 设置为内部BOOT模式以后,可以从一下设备中启动:

  1. 接到 EIM 接口的 CS0 上的 16 位 NOR Flash。
  2. 接到 EIM 接口的 CS0 上的 OneNAND Flash。
  3. 接到 GPMI 接口上的 MLC/SLC NAND Flash,NAND Flash 页大小支持 2KByte, 4KByte 和 8Kbyte,8位宽。
  4. Quard SPI Flash
  5. 接到 USDHC 接口上的 SD/MMC/eSD/SDXC/eMMC等设备。
  6. SPI接口的 EEPROM

这些设备启动如何选择呢? I.MX6U 同样提供了 eFUSE 和 GPIO 配置两种,eFUSE 就不讲解了。我们重点看如何通过 GPIO 来选择启动设备,因为所有的 I.MX6U 开发板都是通过 GPIO 来配置启动设备的。正如启动模式由 BOOT_MODE[1:0]来选择一样,启动设备是通过 BOOT_CFG1[7:0] ,BOOT_CFG2[7:0] 和 BOOT_CFG4[7:0] 这 24 个配置IO,这24个配置IO刚好对应着 LCD 的24根数据线 LCD_DATA0 ~ LCD_DATA23,当启动完成以后这个24个IO就可以额作为LCD的数据线使用。这24个数据线和 BOOT_MODE1, BOOT_MODE0 共同组成了 I.MX6U 的启动选择引脚,如图 9.3.1 所示:

通过 图 9.3.1 中的 26 个启动IO即可实现 I.MX6U 从不同设备启动,BOOT_MODE1 和 BOOT_MODE0 已经讲述过了。看到这24个 IO 是不是头大?调整这24个IO的高低电平得多复杂啊?起始不然,虽然有24个IO,但是实际需要调整的只有那几个IO,其它的IO全部下拉接地即可,也就是设置为0.打开 I.MX6U-ALPHA/Mini的开发板核心板电路原理图,这24个IO的默认配置如下图所示:

可以从"正点原子 I.MX6U ALPHA"开发板原理图中看出,"正点原子 I.MX6U ALPHA"开发板的 LCD_DATA0~LCD_DATA23 大部分 IO 都接地了,只有几个 IO 拉高,尤其是 BOOT_CFG4[7:0] 这8个IO都有 10 K 电阻下拉接地,所以我们压根就不需要去关心 BOOT_CFG4[7:0]。我们需要中断关注的只剩下了 BOOT_CFG2[7:0] 和 BOOT_CFG1[7:0] 这16个IO。这16个配置IO的含义在原理图的左侧已经贴出来了,如下图所示。

图 9.3.3 看着是不是也很头大,BOOT_CFG1[7:0]和BOOT_CFG2[7:0]这16个IO还能不嗯呢再减少哪?可以,打开 I.MX6U ALPHA/Mini 开发板的底板原理图,底板上设备选择拨码开关原理图如下:

在图 9.3.4 中,除了 BOOT_MODE1 和 BOOT_MODE0 必须印出来,LCD_DATA3~LC_DATA7,LCD_DATA11 这 6个IO也被印出来,可以通过拨码开关来设置其对应的高低电平,拨码开关到 “ON” 就是1,拨码开关到 “OFF” 就是0.齐总 LCD_DATA11 就是 BOOT_CFG2[3],LCD_DATA3~LCD_DATA7 就是 BOOT_CFG1[3]~BOOT_CFG1[7],这6个IO的配置的含义如下表:

BOOT_CFG引脚对应LCD引脚含义
BOOT_CFG2[3]LCD_DATA11为0时SDHC1上的SD/EMMC启动,为1时从SDH2上的SD/EMMC启动。
BOOT_CFG1[3]LCD_DATA3当从SD/EMMC启动的时候设置启动速度,当从NAND启动的时候设置ANND数量。
BOOT_CFG1[4]LCD_DATA4

BOOT_CFG1[7:4]: (高位在前,低位在后)
0000 NOR/OneNAND(EIM)启动

0001 QSPI启动

0011 SPI启动

010x SD/eSD/SDXC启动

011x MMC/eMMC启动

1xxx NAND Flash启动

BOOT_CFG1[5]LCD_DATA5
BOOT_CFG1[6]LCD_DATA6
BOOT_CFG1[7]LCD_DATA7

根据表 9.3.1 中 BOOT IO 含义,I.MX6U-ALPHA/Mini 开发板从SD卡,EMMC,NAND 启动的时候拨码开关各个位置配置方式如下表所示

12345678启动设备
01xxxxxx串行下载,可以通过USB烧写镜像文件
10000010SD卡启动
10100110EMMC启动
10001001NAND FLAHS启动

我们再“第八章 汇编LED实验”中,最终的可执行问价 led.bin 烧写到了 SD 卡里面,然后从SD卡启动,其拨码开关就是根据表 9.3.1 来设置的,通过上面的讲解酒味道为什么拨码开关要这么设置了。

4. 镜像烧写

注意!本小节会分析 bin 文件添加的头部信息,但是在笔者写本教程的时候关于I.MX 系列的SOC头部信息的资料很少,基本智能参考NXP的官方资料,而官方资料有些地方讲解的又不是很详细。所以本节有些部分是笔者根据NXP的官方 u-boot.imx 文件的头部信息反推出来的,因此难免有错误的地方,还望大家谅解!如有发现错误之处,欢迎大家在 www.openedv.com 论坛
上留言。

前面我们设置好 BOOT 以后就能从指定的设备启动了,但是你的设备里面得有代码啊,在第八章我们使用 imxdownload 这个软件将 led.bin 烧写到 SD 卡中。imxdownload 会在 led.bin 前面添加一些头信息,重新生成一个叫做 load.imx 的文件,最终烧写的是 load.imx。那么肯能就有人问:imxdownload 是如何将 led.bin 打包成 load.imx的。

学习STM32的时候我们可以直接将编译生成的.bin文件烧写到STM32内部的Flash里面,但是 I.MX6U 不能直接烧写编译生成的 .bin文件,我们需要再.bin文件前面添加一些头信息构成满足 I.MX6U 需求的最终可烧写文件,I.MX6U的最终可烧写文件注册如下:

  1. Image vector rable,简称 IVT,IVT 里面包含了一系列地址信息,这些地址信息在ROM里按照固定的地址存放着。
  2. Boot data,启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少等等。
  3. Device configuration data,简称 DCD ,设别配置信息,重点是 DDR3 的初始化配置。
  4. 用户代码可执行文件,比如 led.bin 

可以看出最终烧写到 I.MX6U 中的程序其组成为 IVT+Boot data + DCD + .bin 。所以第8章中的 imxdownload 所生成的 load.imx 就是在 led.bin 前面加上 IVT + Boot data + DCD 。内部 boot ROM 会将 load.imx 拷贝到DDR中,用户代码前面又有 3KByte的 IVT+Boot Data + DCD数据,下面会讲一下为什么是 3KByte,因此 load.imx 在DDR中的起始地址就是 0x8780_0000 - 3072 = 0x877F_F400。

4.1 IVT 和 Boot Data 数据

load.imx 最前面就是 IVT 和 Boot Data ,IVT 包含了镜像程序的入口点,指向 DCD指针和一些用作其他用途的指针。内部 boot ROM 要求 IVT 应该放到指定的位置,不同的启动设备位置不同,而 IVT 在整个 load.imx 的最前面,起始就相当于要求 load.imx 在烧写的时候应该烧写到存储设备的指定为止去。整个位置都是相对于存储初设备的起始地址的偏移,如图 9.4.1 所示:

以 SD/EMMC 为例,IVT偏移为 1KByte,IVT+Boot Data+DCD总大小为4KByte - 1KByte = 3KByte。假设 SD/EMMC 每个扇区为 512 字节,那么 load.imx 应该从第三个山城区开始烧写,前两个扇区要流出来。load.imx 从第3KBbyte开始才是真正的 .bin 文件。那么 IVT 里究竟存放着什么东西呢?IVT里存放的内容如下表 9.4.1.2 所示

从 图 9.4.1.2 可以看到,第一个存放的是 head (头),header 的给是如 图 9.4.1.3 所示:

图9.4.3 中,Tag位1字节长度,固定为 0xD1,Length是两个字节,保存着IVT 长度,位大端格式,也就是高字节保存在低内存中。最有的Version是一个字节,为 0x40 或者 0x41。

Boot Data 的数据格式如图 9.4.1.3 所示

实际情况是不是这样的呢?我们用 winhex 打开 load.imx 一看便知,winhex 可以直接查看一个文件的二进制格式数据。用winhex打开 load.imx 如下图所示

图 9.1.4.1 是我们截取的 load.imx 的一部分内容,从地址 0x0000_0000 ~ 0x0000_025F,共608字节的数据。气门将钱44个字节按照4个字节一组组的组合在一起就是,0x402000D1, 0x8780000, 0x00000000, 0x877FF42C,0x877FF420,0x00000000,0x0000000,0x877FF000,0x00200000,0x00000000。这44个字节的内容就是 IVT和Boot Data的数据,按照图 9.4.1.2 和图 9.4.1.4 所示的 IVT 和 Boot Data 所示的格式对应起来如表 9.4.1.1 所示:

4.2 DCD 数据

复位以后,I.MX6U 片内所有寄存器就会复位为默认值,但是这些默认值往往不是我们想要的值,而且有些外设在我们必须在使用之前初始化它。为此 I.MX6U 提出了一个 DCD (device Config Data) 的概念,和 IVT , Boot Data 一样,DCD也是添加到 load.imx 里面的,紧跟在 IVT 和 Boot Data 后面,IVT里面也指定了DCD的位置。DCD起始就是 I.MX6U 寄存器地址和对应配置的集合信息,boot ROM 会使用这些寄存器地址和配置集合来初始化相应的寄存器,比如开启某些外设时钟,初始化DDR等等。DCD去不不能超过 1768 Byte,DCD区域的结构如 9.4.2.1 所示

DCD 的 header 和 IVT 的 header 类似,结构如图 9.4.2.2 所示
中 Tag 是单字节,固定为 0XD2, Length 为两个字节,表示 DCD 区域的大小,包含 header,同样是大端模式, Version 是单字节,固定为 0X40 或者 0X41。

图 9.4.2.1 中的 CMD 就是要初始化的寄存器地址和相应的寄存器值, 结构如图 9.4.2.3 所示:

图 9.4.2.3 中 Tag 为一个字节,固定为 0XCC。 Length 是两个字节,包含写入的命令数据长度,包含 header,同样是大端模式。 Parameter 为一个字节,这个字节的每个位含义如图 9.4.2.4 所示

图 9.4.2.4 中的 bytes 表示是目标位置宽度,单位为 byte,可以选择 1、 2、和 4 字节。 flags 是命令控制标志位。

图 9.4.2.3 中的 Address 和 Vlalue/Mask 就是要初始化的寄存器地址和相应的寄存器值,注意采用的是大端模式! DCD 结构就分析到这里,在分析 IVT 的时候我们就已经说过了, DCD 数据是从图 9.4.1.4 的 0X2C 地址开始的。根据我们分析的 DCD 结构可以得到 load.imx 的 DCD 数据如表 9.4.2.1 所示:

从 表 9.4.2.1 中可以看出,DCD 里面初始化配置主要包括三个方面

  1. 设置CCGR0~CCGR6这个7个外设时钟寄存器,默认打开所有外设时钟
  2. 配置DDR3所用的所有IO
  3. 设置MMDC控制器,初始化DDR3

I.MX6U的启动过程我们就讲解到这里,本章我们详细的讲解了 I.MX6U 的启动模式,启动设备类型,和镜像烧写过程。总结一下,我们编译出来的 .bin 文件不能拿直接烧写到SD卡中,需要再.bin文件前面加上 IVT, Boot DATA和DCD这三个数据块。这三个数据块是有指定格式的,播我们必须按照格式填写,然后将其放到.bin文件前面,最终和策划给你的才是我们可以直接烧写到SD卡中的文件。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web安全---CSRF漏洞/OWASP-CSRFTester的使用

what 跨站请求伪造 Cross Site Request Forgery how 攻击者诱骗点击恶意网页,盗用(伪造)受害者的身份,以受害者的名义向服务器发送恶意请求,而这种恶意请求在服务端看起来是正常请求 CSRF&&XSS区别 他们最本质区别就…

什么是中间件?中间件有哪些?

什么是中间件? 中间件(Middleware)是指在客户端和服务器之间的一层软件组件,用于处理请求和响应的过程。 中间件是指介于两个不同系统之间的软件组件,它可以在两个系统之间传递、处理、转换数据,以达到协…

ArcGIS基础:便捷分享图层包和地图包

1、分享图层包: 首先,选中要分享的数据,右键创建图层包,修改保存路径。 找到项目描述那一栏,将摘要、标签、描述都填写分享图层包的相关内容。 一切设置好之后,点击右上角的【分析】按钮。 点击分析之后…

linux下安装anaconda

顺手点个关注吧,谢谢! 一、下载安装包 https://repo.anaconda.com/archive/ 或者使用命令 wget https://repo.anaconda.com/archive/Anaconda3-5.3.1-Linux-x86_64.sh2.赋予权限并安装 # 给文件执行权限 chmod 777 Anaconda3-5.3.1-Linux-x86_64.sh# 执…

高频面试题:在浏览器搜索框中输入一个URL的完整请求过程?

相信很多小伙伴在校招或者社招面试中都遇到过这个问题 面试官:小伙子,了解 在浏览器搜索框中输入一个URL的完整请求过程吗?详细说说我:eeemm,不太清出具体的过程。整体过程应该是HTTP请求的过程。 如果在面试中不能很…

FinClip :可以让小程序脱离微信环境最快运行在自有App中

🧐 什么是FinClip? FinClip,一个可以让小程序脱离微信环境,最快运行在自有App中的神器。它支持在iPhone、Android、Windows、Linux、macOS、统信等平台下的应用中运行小程序,这意味着,无论是移动端、PC端&…

uniapp自定义返回事件(封装)

uniapp自定义返回事件 在我们使用uniapp时,我们导航栏一般都是自定义的,比如用uview框架的导航栏,那么返回事件通常会遇到以下几个问题 返回事件前需要做一些额外的处理 h5项目刷新页面后返回失效 返回按钮点击后到指定页面 如果只是监听返…

技术团队的管理方法和日常总结建议

管理学家德鲁克有言“管理是一种实践,其本质不在于知,而在于行,其验证不在于逻辑,而在于成果,其唯一的权威就是成就” ,因此管理重实践看效果,但如果管理实践有理论依凭,那么实践起来…

新媒体运营-----短视频运营-----PR视频剪辑----视频调色

新媒体运营-----短视频运营-----PR视频剪辑-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/138079659 文章目录 1. Lumetri调色,明暗对比度2. Lumetri调色,创意与矢量示波器2.1 创意2.2 矢量示波器 3. L…

滑块验证码破解----Java使用opencv后端破解滑块验证

使用技术:Java SpringBootopenCV 在windows上首先需要下载opencv进行安装,先去官网:Releases - OpenCV 下载这个windows版本的安装包 下载后直接安装解压就行,然后需要,然后找到安装位置里的这个文件: 你下载的是什么版本的,这里的数字就是多少,比如我下载4.5.3版本那么这…

分治策略 --- 快排归并

目录 分治-快排 一、颜色分类 二、排序数组 三、数组中的第K个最大元素 四、库存管理 分治-归并 一、排序数组 二、交易逆序对的总数 三、计算右侧小于当前元素的个数 四、翻转对 分治是一种思想,也就是将大问题分解成小问题,一直分到小问题可…

2024年云仓酒庄新动态合肥北京双城开课,持续培训助力酒业发展

云仓酒庄合肥北京双城开课,持续培训助力酒业发展 随着国内酒类市场的不断繁荣与消费需求的日益增长,云仓酒庄作为行业内的稳扎稳打之一的企业,始终致力于提升服务质量和品牌影响力。近日,云仓酒庄在合肥与北京两地同时开设培训课…

qt学习篇---C++基础学习

本学习笔记学习下面视频总结,感兴趣可以去学习。讲的很详细 【北京迅为】嵌入式学习之QT学习篇_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1tp4y1i7EJ/?spm_id_from333.337.search-card.all.click&vd_source8827cc0da16223b9f2ad8ae7111de9e2 目录 C…

JavaScript数组方法练习

第一个练习 ● 计算所有账户的存款总额 const bankDepositSum accounts //定义一个变量.flatMap(acc > acc.movements) //使用flatmap将所有的存款提取,然后组成一个新的数组.filter(mov > mov > 0) //使用filter方法提取所有的正值.reduce((sum, c…

【前端】VUE项目创建

在所需文件夹中打开cmd命令行窗口,输入vue ui 进入web可视化界面选择创建新项目 根据需求依次完成下列选择,下列是参考配置,完成后点击创建项目即可 最终显示完成

upload-labs后续(14-)

图片马 在图片中加入木马,然后上传让浏览器解析,达到上传木马的目的 制作图片马:可以在notepad或者winhex中打开文件,在文件头中加入图片的文件头,再在文件末尾加入木马即可。 图片文件头如下: 1.Png图片…

B站广告推广开户最低需要多少钱?

在竞争激烈的互联网营销环境中,哔哩哔哩(B站)作为深受年轻人喜爱的视频分享平台,其广告价值不容小觑。对于希望在B站上进行广告推广的企业或个人而言,了解开户流程及成本,选择专业的服务团队至关重要。介绍…

AI图书推荐:ChatGPT写论文的流程与策略

论文一直是任何学术学位的顶峰。它展示了学生在研究领域的兴趣和专业知识。撰写论文也是一个学习经验,为学术工作以及专业研究角色做好准备。但是,论文工作总是艰苦的,通常是充满乐趣和创造性的,但有时也是乏味和无聊的。生成式人…

kerberos-hive-dbeaver问题总结

一、kerberos安装windows客户端 1、官方下载地址 http://web.mit.edu/kerberos/dist/ 2、环境变量配置 下载msi安装包,无需重启计算机,调整环境变量在jdk的前面,尽量靠前,因为jdk也带了kinit、klist等命令 C:\Program Files\…

Kotlin泛型之 循环引用泛型(A的泛型是B的子类,B的泛型是A的子类)

IDE(编辑器)报错 循环引用泛型是我起的名字&#xff0c;不知道官方的名字是什么。这个问题是我在定义Android 的MVP时提出来的。具体是什么样的呢&#xff1f;我们看一下我的基础的MVP定义&#xff1a; interface IPresenter<V> { fun getView(): V }interface IVie…
最新文章