STM32之八:IIC通信协议
目录
1. IIC协议简介
1.1 主从模式
1.2 2根通信线
2. IIC协议时序
2.1 起始条件和终止条件
2.2 应答信号
2.3 发送一个字节
2.4 接收一个字节
3. IIC读写操作
3.1 写操作
3.2 读操作
1. IIC协议简介
IIC协议是一个半双工、同步、一主多从、多主多从的串行通用数据总线。该通信模式需要2根线:SCL、SDA,即时钟线和数据线。
1.1 主从模式
IIC协议支持一主多从和多主多从,每个设备都有唯一的地址。
1.2 2根通信线
SDA:数据线,用于传输数据
SCL:时钟线,用于同步数据传输
接线时所有IIC设备的SCL连在一起,SDA连在一起,不同的设备,都是并联接在这两条线上(设备之间“线与”关系),I2C总线上的每个设备都自己一个唯一的地址,来确保不同设备之间访问的准确性。设备的SCL的SDA均要配置成开漏输出模式,SCL和SDA需要各添加一个上拉电阻,阻值一般为4.7KΩ左右。
这里补充一下,因为有看到过一个主机一个从机的情况下,可以设置为推挽输出模式,但是在一主多从,或者多主多从的情况下,推荐开漏输出,原因请看下文。
为了能理解为什么在IIC协议总线上,IO口模式需要设置为开漏输出模式,而不能使用推挽输出模式,可以看下GPIO口的硬件结构:
看输出驱动器部分,可以看到使用了两个MOS管,分别是P-MOS和N-MOS管,这些是STM32 GPIO输出模式的关键元件。在推挽输出模式下,P-MOS管和N-MOS管都工作,通过控制这些管的开关状态来实现高电平和低电平的输出。在开漏输出模式下,只有N-MOS管工作,用于输出低电平,而高电平的输出则需要通过外部上拉电阻实现。
推挽输出模式:
推挽输出结构是由两个MOS收到互补控制的信号控制。推挽输出的最大特点是可以真正能真正的输出高电平和低电平,在两种电平下都具有驱动能力。推挽输出模式中,N-MOS管和P-MOS管都工作,如果我们控制输出为0(低电平),则P-MOS管关闭,N-MOS管导通,输出低电平;若控制输出为1(高电平),则P-MOS管导通,N-MOS管关闭,输出高电平。外部上拉和下拉的作用是控制在没有输出时的IO口电平。
优点:驱动能力强,电平切换能力快(根据GPIO的波特率可用作模拟其他协议)。
缺点:多个推挽输出端口相连时,由于通路上阻抗较小电流会从IO的VDD流向另一个IO的GND,会发生短路进而对端口造成伤害。(这里就解释了为什么IIC不能使用推挽输出模式,如果多个从机都接到SDA线上,一个机器发送数据0,另一个机器发送数据1,则可能会发生短路进而毁坏IIC器件)。
开漏输出模式:
开漏输出时只有N-MOS管工作,只能输出低电平。当其输出高电平时没有驱动能力(电压会被外部阻抗拉低),需要借助外部上拉电阻完成对外驱动(通断N-MOS实现对路径上的电压控制),驱动能力取决于上拉电阻阻值。
如果我们控制输出为0(低电平),则P-MOS管关闭,N-MOS管导通,输出低电平;若控制输出为1(高电平),则P-MOS管和N-MOS管都关闭,输出指令就不会起到作用,此时I/O端口的电平就不由输出的高低电平决定,而是由I/O端口外部的上拉或者下拉决定。如果没有上拉或者下拉 IO口就处于悬空状态。
半双工:一根数据线,这根数据线既可以发送数据,也可以接收数据,但是不能同时发送和接收,所以叫做半双工通信。
代码如下:注意开漏输出模式
// PB11-->SDA
// PB10-->CLKvoid MyI2C_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出/*设置默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
2. IIC协议时序
IIC 协议基本形式:
可以看到,IIC时序在进行一次传输的时候,会有开始信号和停止信号,期间发送1byte数据,每一byte数据之后都会有一个ACK信号,下面依次讲解这些信号时序。
2.1 起始条件和终止条件
IIC需要起始信号和终止信号,SCL在高电平期间,SDA从高电平跳转到低电平表示开始信号;SDA从低电平跳转高电平表示结束信号。
/*** 函 数:I2C起始* 参 数:无* 返 回 值:无*/
void MyI2C_Start(void)
{MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/*** 函 数:I2C终止* 参 数:无* 返 回 值:无*/
void MyI2C_Stop(void)
{MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}//---------------
/*** 函 数:I2C写SCL引脚电平* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平Delay_us(10); //延时10us,防止时序频率超过要求
}/*** 函 数:I2C写SDA引脚电平* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性Delay_us(10); //延时10us,防止时序频率超过要求
}
2.2 应答信号
下面展示一个传输数据时候发送应答信号(ACK)和非应答信号(NACK)的示意 :
/*** 函 数:I2C发送应答位* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答* 返 回 值:无*/
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}/*** 函 数:I2C接收应答位* 参 数:无* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit; //定义应答位变量MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAAckBit = MyI2C_R_SDA(); //将应答位存储到变量里MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块return AckBit; //返回定义应答位变量
}
2.3 发送一个字节
注意,当时钟信号SCL为高电平的时候,数据线SDA上的信号需要保持不变。因为在SCL高电平期间发生SDA的跳变会误认为产生了起始或停止信号,从而导致数据数据传输错误。
/*** 函 数:I2C发送一个字节* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF* 返 回 值:无*/
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位{MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据}
}
2.4 接收一个字节
/*** 函 数:I2C接收一个字节* 参 数:无* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF*/
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位{MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA}return Byte; //返回接收到的一个字节数据
}/*** 函 数:I2C读SDA引脚电平* 参 数:无* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1*/
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平Delay_us(10); //延时10us,防止时序频率超过要求return BitValue; //返回SDA电平
}
如果IIC总线上传输1Byte数据0X49(01001001),其时序图如下:
3. IIC读写操作
3.1 写操作
使用IIC进行数据传输时,写操作流程如下:
1. 主机确定从机地址。IIC总线上每个设备都有唯一的地址码,用于标识和寻址。主机寻址过程是在起始信号之后进行的,起始信号之后,主机会发送7bit的从机地址+1bit的读写位(0表示写操作,1表示读操作),这也是为什么有时候看到一个器件有读地址和写地址的原因。这种寻址方式理论上最多能挂载127个从设备。目前为了制造更多的可使用从机地址数量,IIC有标准模式、快速模式和高速模式,支持10bit寻址,允许1024个从机地址。
2. 主机发送开始信号,表示开始IIC传输
3. 进行寻址,即发送从机设备地址+1bit的读写位,写操作则读写位0,例如一个从机设备地址为0x13,则发送的信号为:00011010。
4.从机回复应答信号ACK。IIC总线的从机接受到主机的信号后,会将该地址和自己的地址码进行匹配,如果匹配成功后,则相应从机会回复给主机一个应答信号ACK。
5.主机接受到应答信号,发送数据,从机应答。
6.主机发送结束信号,IIC传输结束。
整个时序图如下所示:
3.2 读操作
读操作和写操作类似,主要更改的点在于:
1. 寻址时读写标志位为1
2. 从机应答后会主动向主机发送其所要读取的数据,主机接受到信号后会向从机发送一个应答信号
3. 当主机想要结束读操作时,主机会回复一下非应答信号,生成停止信号来结束数据读取操作。
整个时序图如下:
参考:
1. 江协科技STM32
2. IIC协议 (dreamsourcelab.cn)
相关文章:
STM32之八:IIC通信协议
目录 1. IIC协议简介 1.1 主从模式 1.2 2根通信线 2. IIC协议时序 2.1 起始条件和终止条件 2.2 应答信号 2.3 发送一个字节 2.4 接收一个字节 3. IIC读写操作 3.1 写操作 3.2 读操作 1. IIC协议简介 IIC协议是一个半双工、同步、一主多从、多主多从的串行通用数据总…...
mysql的数据往hive进行上报时怎么保证数据的准确性和一致性
在将MySQL的数据往Hive进行上报时,确保数据的准确性和一致性可以通过下面一系列步骤来实现 一、准备工作 环境配置: 确保MySQL和Hive环境已经安装并配置好,且都处于可运行状态。检查Hadoop集群(Hive通常运行在Hadoop之上&#x…...
问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 #学习方法#其他#学习方法
问题:4、商业保险与政策性保险的主要不同之处是:经营主体不同、经营目标不同、承保机制不同。 参考答案如图所示...
Getx学习笔记之中间件鉴权
目录 前言 一、实现步骤 1.添加依赖 2.创建鉴权中间件 3.定义路由 4.设置初始路由 5.模拟登陆状态 二、Getx鉴权步骤总结 三、本文demo示例 四、参考文章 前言 在 Flutter 中,使用 GetX 可以很方便地实现中间件鉴权(Authentication)…...
介绍 Elasticsearch 中的 Learning to Tank - 学习排名
作者:来自 Elastic Aurlien Foucret 从 Elasticsearch 8.13 开始,我们提供了原生集成到 Elasticsearch 中的学习排名 (learning to rank - LTR) 实现。LTR 使用经过训练的机器学习 (ML) 模型为你的搜索引擎构建排名功能。通常,该模型用作第二…...
2024年计算机软考中级【硬件工程师】面试题目汇总(附答案)
硬件工程师面试题汇总分析 1、解释一下同步电路和异步电路 解题思路 同步电路和异步电路是指同步时序电路和异步时序电路。由于存储电路中触发器的动作特点不同,因此可以把时序电路分为同步时序电路和异步时序电路两种。同步时序电路所有的触发器状态的变化都是在同…...
ThinkPad改安装Windows7系统的操作步骤
ThinkPad:改安装Windows7系统的操作步骤 一、BIOS设置 1、先重新启动计算机,并按下笔记本键盘上“F1”键进入笔记本的BIOS设置界面。 2、进入BIOS设置界面后,按下键盘上“→”键将菜单移动至“Restart“项目,按下键盘上“↓”按键…...
微软Edge浏览器全解析教程
微软Edge浏览器全解析教程 微软Edge浏览器,作为微软公司精心打造的一款现代化网页浏览器,自其首次发布以来,凭借其卓越的性能、出色的用户体验和不断迭代的功能,赢得了广大用户的青睐。本文将全面解析微软Edge浏览器的各个方面&a…...
【过题记录】7.20
前两题一直在打模拟赛,有点忙,就没更 Red Playing Cards 算法:动态规划 其实这就是一个线段覆盖问题,只不过大线段能够包含小线段。 这就启发我们,对于每个大线段分别跑一个dp,合并在他内部的小线段。而后…...
Linux系统学习日记——vim操作手册
Vim编辑器是linux下的一个命令行编辑器,类似于我们windows下的记事本。 目录 打开文件 编辑 保存退出 打开文件 打开 hello.c不存在也可以打开,保存时vim会自动创建。 效果 Vim打开时,处于命令模式,即执行命令的模式&#x…...
【深度学习图片】图片清洗,只留下图像中只有一张人脸的,而且人脸是全的
环境: conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidia -ypip install onnx1.15 onnxruntime-gpu1.17pip install insightface0.7.3pip install opencv-pythonpip install gradio图片清洗,只留下图像中只有一张人脸…...
如何在 PostgreSQL 中处理海量数据的存储和检索?
🍅关注博主🎗️ 带你畅游技术世界,不错过每一次成长机会!📚领书:PostgreSQL 入门到精通.pdf 文章目录 如何在 PostgreSQL 中处理海量数据的存储和检索?一、优化表结构设计二、分区技术三、数据压…...
【中项】系统集成项目管理工程师-第2章 信息技术发展-2.2新一代信息技术及应用-2.2.1物联网与2.2.2云计算
前言:系统集成项目管理工程师专业,现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试,全称为“全国计算机与软件专业技术资格(水平)考试”&…...
Redis集群的主从复制原理-全量复制和增量复制-哨兵机制
Redis集群的主从复制原理-全量复制和增量复制-哨兵机制 作用 数据备份 这一点直观,因为现在有很多节点,每个节点都保存了原始数据的备份. 读写分离 这一点主要是当发生读写的时候,读数据的操作大部分都会进入到从节点,而写数据的操作都会进入到主节点&…...
23年阿里淘天笔试题 | 卡码网模拟
第一题 字典序最小的 01 字符串 解题思路: 模拟,统计遇到的连续的1的个数记为num,直到遇到0,如果k>num,直接将第一个1置为0,将遇到的0置为1,否则将第一个1偏置num-k个位置置为0࿰…...
【SpringBoot】单元测试之测试Service方法
测试Service方法 SpringBootTest public class UserServiceTest{ Autowired private UserService userService; Test public void findOne () throws Exception{ Assert.assertEquals("1002",userService.findOne()); } } 测试Controller接口方法 Runwith(S…...
剪辑师和小白都能用的AI解说神器,一键把短剧变解说视频-手把手教程-2024
为什么短剧、综艺、电影和电视剧需要以解说形式在抖音、快手和TikTok推广? 此类专业影视内容由于时间过长、平台用户的习惯、算法去重需求和版权问题,专业的影视综节目通常需要用解说类型的视频来不断重复的宣发剧集。具体的原因如下: 1. 视…...
我去,怎么http全变https了
项目场景: 在公司做的一个某地可视化项目。 部署采用的是前后端分离部署,图片等静态资源请求一台minio服务器。 项目平台用的是http 图片资源的服务器用的是https 问题描述 在以https请求图片资源时,图片请求成功报200。 【现象1】: 继图…...
IDEA的详细设置
《IDEA破解、配置、使用技巧与实战教程》系列文章目录 第一章 IDEA破解与HelloWorld的实战编写 第二章 IDEA的详细设置 第三章 IDEA的工程与模块管理 第四章 IDEA的常见代码模板的使用 第五章 IDEA中常用的快捷键 第六章 IDEA的断点调试(Debug) 第七章 …...
为什么Spring选择使用容器来管理对象,而不是直接使用new
为什么Spring选择使用容器来管理对象,而不是直接使用new 在Java应用程序开发中,对象的创建和管理是一项基础且关键的任务。传统上,开发者习惯于使用new关键字直接在代码中实例化对象。然而,随着应用程序规模的扩大和复杂度的增加…...
腾讯云发送短信验证码
1、在腾讯云平台中 开通短信服务 2、发送短信 2.1引用jar包 <dependency><groupId>com.tencentcloudapi</groupId><artifactId>tencentcloud-sdk-java-sms</artifactId><version>3.1.1043</version> </dependency>2.2 发送短…...
嵌入式人工智能(13-基于树莓派4B的指纹识别-AS608)
1、指纹识别模块 指纹识别是一种生物识别技术,通过分析人体指纹的纹理特征来进行身份验证。每个人的指纹纹路都是独一无二的,通过将指纹与事先存储的指纹数据库进行比对,可以确定是否为同一人。指纹识别在安全领域得到广泛应用,例…...
【Vue】`v-on` 指令详解:事件绑定与处理的全面指南
文章目录 一、v-on 指令概述缩写语法 二、v-on 的基本用法1. 绑定方法2. 内联处理器 三、v-on 指令的高级用法1. 事件修饰符.stop.prevent.capture.self.once 2. 按键修饰符.enter自定义按键修饰符 3. 系统修饰符 四、v-on 指令的实际应用1. 表单处理模板部分 (<template>…...
【Spark On Hive】—— 基于电商数据分析的项目实战
文章目录 Spark On Hive 详解一、项目配置1. 创建工程2. 配置文件3. 工程目录 二、代码实现2.1 Class SparkFactory2.2 Object SparkFactory Spark On Hive 详解 本文基于Spark重构基于Hive的电商数据分析的项目需求,在重构的同时对Spark On Hive的全流程进行详细的…...
哪种SSL证书可以快速签发保护http安全访问?
用户访问网站,经常会遇到访问http网页时,提示网站不安全或者不是私密连接的提示,因为http是使用明文传输,数据传输中可能被篡改,数据不被保护,通常需要SSL证书来给数据加密。 SSL证书的签发速度࿰…...
深入探究理解大型语言模型参数和内存需求
概述 大型语言模型 取得了显著进步。GPT-4、谷歌的 Gemini 和 Claude 3 等模型在功能和应用方面树立了新标准。这些模型不仅增强了文本生成和翻译,还在多模态处理方面开辟了新天地,将文本、图像、音频和视频输入结合起来,提供更全面的 AI 解…...
maven 私服搭建(tar+docker)
maven私服搭建 一、linux安装nexus1、工具下载 二、 docker 搭建nexus1、镜像下载创建目录2、运行nexus3、访问确认,修改默认密码,禁用匿名用户登录4、创建仓库5、创建hostd仓库6、创建Blob Stores7、创建docker私服1、创建proxy仓库2、创建hotsed本地仓…...
银行业务知识全篇(财务知识、金融业务知识)
第一部分 零售业务 1.1 储蓄业务 4 1.1.1 普通活期储蓄(本外币) 4 1.1.2 定期储蓄(本外币) 5 1.1.3 活期一本通 9 1.1.4 定期一本通 10 1.1.5 电话银行 11 1.1.6 个人支票 11 1.1.7 通信存款 13 1.1.8 其他业务规…...
解决ElasticJob项目重启ZooKeeper注册冲突以及zkCli删除目录
解决ElasticJob项目重启ZooKeeper注册冲突以及zkCli删除目录 背景 在现代化的分布式调度系统中,ElasticJob 是一个非常流行的选择。它利用 ZooKeeper 作为注册中心来管理任务分片。然而,有时在项目重启时,会遇到 ZooKeeper 注册冲突的问题&…...
【EI检索】第二届机器视觉、图像处理与影像技术国际会议(MVIPIT 2024)
一、会议信息 大会官网:www.mvipit.org 官方邮箱:mvipit163.com 会议出版:IEEE CPS 出版 会议检索:EI & Scopus 检索 会议地点:河北张家口 会议时间:2024 年 9 月 13 日-9 月 15 日 二、征稿主题…...
400网站建设办公/西地那非能提高硬度吗
或许你有做过,接过美工做好生成的Html的前台网页,开始写程序,你会知道有些简单的数据源绑定已经被美工做好了。如下: View Code <asp:DropDownList ID"DropDownList1"runat"server"><asp:ListItem …...
精美合同网站建设/最好的优化公司
DNS域名解析 简单的说就是把域名翻译成 IP 地址,但如果在浏览器直接输入IP,则跳过这个步骤。 DNS寻找解析顺序: 浏览器缓存解析 操作系统缓存解析 公共域名服务器解析(根域名服务器/Root Server,主域名服务器&…...
门户网站营销特点/杭州seo公司服务
http://www.jianshu.com/p/25e678fa43d3 转载于:https://www.cnblogs.com/mafeng/p/7146306.html...
江苏省建设厅网站/外贸推广优化公司
题意: 给出1000个数 任取三个数 求max(aiaj) xor ak 思路: 先计算出1e6个 ai aj 将这1e3个数转化为2进制建树。由于数值小于1e9,树深小于32。 将1e6个 ai aj 也转化为二进制,与trie树做抑或匹配。 每次匹配时先删除ai aj 所…...
Fastcgi做网站/软文案例300字
今天上课老师用Java实现了打地鼠游戏的界面和具体逻辑,那么我也尝试使用Android语言实现其功能。首先是打地鼠游戏的玩法1.每隔1秒或者0.5秒地鼠会出现在九宫格中的任一位置2.点击界面,如果地鼠出现的位置与点击位置相同,则认为打中地鼠。否则…...
委托做的网站版权归属/软文代写平台
Windows下vagrant up出现蓝屏的解决办法 版权声明:尊重原创喔,转载请注明 https://blog.csdn.net/lgyaxx/article/details/79333462今天在Windows 10下装了个docker for windows,结果发现问题多多,虽然说container有优势ÿ…...