当前课程知识点:ARM微控制器与嵌入式系统 > 第五章 ARM微控制器的各种外设 > 5.7.3 ARM微控制器外设:I2C寄存器与编程 > 5.7.3 ARM微控制器外设:I2C寄存器与编程
各位同学大家好
我是清华大学工程物理系的曾鸣老师
我们来继续我们
ARM微控制器与嵌入式系统的MOOC之旅
我们在这几个单元里呢
一直在学习I2C这样一种同步串行通讯
这是一种比较麻烦的通讯协议
也是我们这门课里应该来讲
学的最麻烦 最复杂的一类通讯
我们花了两个单元的时间来讲它的原理
它的思想
它的电气特性
它的数据帧
那么总体上来讲
回过头来看 会发现
它里头有UART异步串行通讯的影子
也有SPI同步串行通讯的影子
但是我们学了它们好像上了一个台阶
有了数据帧的概念
有了像这样几个积木组成的这样一个
时序的这个单元的概念
那么展开了讲日后大家
如果把这个I2C学习好了
日后大家去看USB
或者以太网通讯
特别是USB
你去看它里头那个数据帧的结构的时候
你会发现
哇 理解起来好像很容易
那么它们技术发展就是这么一脉相承的
所以这也是为什么
USB叫Universal Serial Bus
跟我们这样一个I2C的名字
都是一脉相承的这样一种关系
所以呢
我们学通讯到这里就告一个段落了
但是我们这门课的本质
我们这门课一直给大家讲的一个精神
是我们学的东西
要带着兴趣我们把它给玩起来
所以在这个单元里
我来给大家讲如何把I2C编程玩起来
玩一个比较好玩的功能
那么在我们所拿到的这样一个开发板里
我们的I2C接口
接了一个非常有意思的芯片电路
就是数字加速度传感器
那么数字加速度传感器
说起来好像还比较学术
Accelerometer是什么东西
感受加速度
那我给大家换一个说法就是运动传感器
也就是我们现在手机上大家用的摇一摇啊
翻个面啊
倾斜角度啊
这样一类的物理传感器
我们用了它的一个I2C接口的sensor
传感器芯片
也就是Freescale的MMA这个8551这样一个芯片
那么这个芯片它接在我们这个板子上
用了其中一个I2C的接口
那么看看这张电路图
大家读一读图就会发现
芯片抽象成了这样一个黄色小方块
接的两个除了共地之外
接的两个时序引脚
从PORT上来讲就是PTE
PORTE的24和25这两个引脚
它们的第5号复用功能
分别是I2C的SCL时钟和I2C的SDA数据
那么我们怎么编程把它给用起来呢
这就涉及到了I2C的通讯协议
包括我们刚才讲的拔高内容里头的
这样一个芯片内部是有若干个寄存器的
我们需要对它内部地址进行读和写操作
来如何编程实现
那么所有同学都会说分三个步骤对吧
第一个步骤配置好时钟打开
第二个步骤把引脚配置给I2C模块用
第三个步骤学习一下I2C的寄存器
把曾老师刚才讲的那些理论
那些时序怎么转换成语言
转换成封装的函数给实现出来对吧
那么第一个步骤非常简单
前两个步骤我都讲快一点
如何把时钟打开
还是System integration Module里头
有两个寄存器我们要涉及
一个是在SIM的SCGC4这个寄存器里头
我们可以找到I2C0和I2C1我们这个芯片里
有两个I2C模块
它们分别都可以用一个比特
控制时钟的开关
那么第二个就是不要忘记了
我们现在用的这个引脚是PORTE的引脚
我们要记得把PORTE的时钟也打开
具体的语句我就不给大家展开讲
那么打开时钟之后背后一定要有个概念
就是我们这个芯片每一个模块
都在一个共同的时钟Tree的驱动下
一个Clock 一个Clock像心跳一样的工作
那么如果我们看我们芯片手册的
第三章关于Clock的distribution
时钟树的解释
专门会有一个部分提到I2C0和I2C1
我们这个芯片里头这两个I2C模块
使用的参考时钟源都是Bus Clock
所以在上电默认没有启动锁相环的时候
我们的锁频环的这个片内晶振产生的
这个时钟是20.97兆
Bus Clock是它的一半
所以我们分频的基础频率
就是20.97兆除以2
这个大家要弄清楚
那么如果大家用的是别家的
别的厂商的ARM芯片
你们也去看对应的这样一种芯片手册
一定能找到你所使用I2C模块的时钟源
这一点大家不要忘
为什么呢
因为一到通讯肯定有速度
肯定有波特率
后面的寄存器设置会用到它
所以这个单元加起来就这么两句话
打开时钟非常简单 对吧
那么第二个步骤呢
就是把我们的引脚配置为给I2C使用
这个大家做了很多次
这次格外简单
为什么呢
因为I2C除了地线
它就只有两根数据线
所以我们就是两句话
PORTE的PCR24
和PORTE的PCR25
指定为对应的第5号功能
就是给I2C用
那么这件事就做完了
那么最后一个步骤相对麻烦一点
也是我们这个单元里学习的难点或者重点
就是I2C的寄存器的编程
那么I2C在我们用的KL25这款芯片里头
有两个模块 I2C0、I2C1
所以是同样的电路
同样的寄存器一式两份
每一个模块对应有12个寄存器
在400的这个地址段
可以帮助我们来对I2C进行配置
我们对寄存器的使用
可以完成对于电路
这样一个框图功能内的功能的设置
包括对于这些数据的按照时序如何出去
如何按照刚才我们所讲的时序收进来
框图我就不展开讲了
那么这有12个寄存器里头
在这门课单元
读一个加速度传感器大家放心
我们只用深入掌握其中四个寄存器使用
就能把一个I2C模块给初步的用起来
但是更多寄存器我鼓励大家
自己去看芯片手册对吧
那么这四个寄存器分别就是
I2C_F Frequency也就是baudrate
我们这个速度的频率的设置寄存器
还有I2C的这个C1控制的Control Register
还有I2C的S
Status Register和I2C的D
大家会发现我们用过的
每一个通讯模块的寄存器基本上都是这几个
所以编程的思想和步骤很接近
在I2C里头唯一的一个区别是什么呢
就是在上个单元我们讲的
它有一个数据帧的结构
里头涉及到主机与从设备当中彼此的交互
所以它在编程实践上会稍微有一点不一样
我们先来看这个Frequency第一个寄存器
设置我们I2C通讯模块的时钟频率
那么同样它就是跟我们以前
设波特率那寄存器一样
把一个八比特寄存器分成了两段
这两段各是一个值
所以呢
我们的基础频率20.97兆的
那个一半除以这两个值的乘积
就是我们I2C模块的
最终一个比特位宽的那个时钟频率
那么这个时钟频率大家做过
上一个单元里头
对于屏幕那个OLED点阵屏的控制以后
就会有概念
我的频率必须跟我通讯的对象设备协商好
是它能接受的
那我们现在要跟这么一个加速度传感器
这样一个运动传感器通讯
我们是不是应该去看一看它的芯片手册呢
所以详细的芯片手册下一节课
我带着大家看
现在大家跟着我一起去看
这个芯片手册的时候
直接翻到这个第八页里
你会发现有一张这个电气特性表
里头有一个它的I2C这个接口的通讯频率
和Maximum最大值 是多少啊
是400K赫兹
所以我们选一组分频因子
使我们I2C模块
工作在比这个低的频率
就可以确保跟这个芯片正确的通讯了
所以比如说我们现在红框里
在这个芯片手册的表里
选了一组值来设置我们这个ARM芯片
让它是20.97兆再除以1乘以80
得到一个100多K的通讯速率
这样我们就可以以一个比较稳妥的速度
跟加速度传感器进行通讯
当然它离这个芯片能够承受的最高速率
还差那么一点点
有兴趣的同学
你可以把速度再改高一点看一看
那么这就是对于这个频率寄存器的控制
那么我们涉及到的第二个寄存器呢
就是C1控制寄存器
控制寄存器里头有非常多的比特
我们可能会用到
包括我标为绿色的这几个比特
它们有的比如说最高位控制I2C
这个模块的Enable和Disable
比如第五个比特控制这个I2C模块
是工作在Master模式还是Slave模式
然后第四个比特控制这个I2C模块
是控制在发送状态还是接收状态
那么学过前面的电气特性
大家应该深刻理解的是
所谓的工作在发送状态
就是由主机来控制数据线
负责数据线的是否往下拽产生零电平
而所谓控制在接收状态
是指主机松开对于数据线的控制
而交由设备来控制
也就是我们刚才通讯里白色的部分
而主机这个时候是读取数据线上的值
然后第二个比特
Restart这个比特呢
指可以做写操作
每写一次产生一个
我们刚才说到的重启信号
也就是重复发送一个起始信号
那么I2C的特殊性
就如我这段代码所示
实际上呢
我们通过一系列的宏
把这样对于寄存器
这些比特的操作进行了封装
来实现我们I2C通讯协议里头
刚才说的在上一个单元里所讲的
起始位 停止位
重复起始位
发送ACK 发送NACK
以及进入接收模式
把控制权交给从设备等等
这样一系列的操作
所以也就是说I2C的通讯
我们不再简单的是对寄存器
完成一次配置就不管了
而是得封装成这些宏
再对这些寄存器的操作
或者以这种宏的形式完成我们刚才
时序图里所讲的那样一个通讯协议
那么I2C的第三个寄存器是数据寄存器
也就是I2C_D
数据寄存器在UART
在SPI里头我们都学过
它是一个寄存器
同时对应读和写两种不同操作
这个概念大家已经有了
那么在I2C的数据寄存器
I2C_D里头
它也有它的特殊性
那么讲了一大段英文
Transmit model和Receive Model分别怎么样
我们把它简述从芯片手册上
翻译成中文是这么几个意思
对于这个寄存器的写操作
会发起一笔新的I2C通讯
也就是主机开始向外
一个比特一个比特的发出通讯
也就是产生这个数据出去
那么对于I2C_D的这个数据寄存器的读
总是读到的是I2C上一次已经接收到的字节
而这个读操作本身会启动一次新的读取操作
那么这句话什么意思呢
就意味着我们待会编写读写函数
对I2C进行编程的时候
我们每一次写操作把数据写进去
就发起了一个字节的写
在I2C的时序里
读操作我们应该先做一次Dummy读取
也就是空读取
来把上一次存在里头的数据读出来不要
同时通知这个模块产生一次新的读取时序
接下来下一次读取
才会读到我们
在未来这八个比特时钟周期里
真正读到我们要接收的值
这是这句话背后的含义
第三件事呢
是由于我们I2C的通讯协议刚才已经讲了
它是有规范 有约束的
所以每一笔通讯
每一笔独立的通讯
在起始位
我们刚才说对这个
控制寄存器的比特进行读写
产生起始位之后
我们第一次操作一定是通过
对于I2C_D的这个寄存器的写操作
写入一个从机的地址
加上一个比特读写方向
然后这一个比特写完了以后
我们之后做的读取
或者是写入
就是对这个数据寄存器操作
必须与我们写的这第一个字节的方向一致
也就是我们写的这第一个字节
七个比特地址后面
跟的是读 还是跟的是写
我们后面对数据寄存器的操作
必须跟它相应的是读或者是写
如果两者不匹配
这个模块就无法正确的返回数据
无法正确的工作
I2C的通讯就不会成功
因为我们的程序编错了
所以就这么三个要点
写 发起写的时序
读 应该先预读一次
然后等待数据收到了
再第二次读
才会读到真正的数据
然后读第一次写入的字节
决定了我们的设备地址和读写方向
之后对寄存器的读写
必须与第一个字节的
读写标志位方向一致
符合I2C的通讯协议
这是几个要点
那么我们回过头来看看
刚才讲到的数据帧
也就是所说的这个意思
第一个字节是地址加读写
后面的读和写的方向
必须与第一个字节一致才符合协议
最后是I2C的Status这个状态寄存器
状态寄存器里有很多标志位
实际上在我们这个编程里
真正用到的是比特1
也就是I2CIF中断标志位
跟我们做SPI通讯的时候一样
这个中断标志位
其实在启用了中断的时候
可以作为中断的标志位
但是在我们做查询模式的时候
它也是我们每一次I2C的通讯
完成一个字节的时候的一个标志位
所以我们真正的通讯流程是
每当发送或者接受一个字节后
这个标志位都会置1
那么在进行I2C通讯时
我们每一次读写I2C_D这个寄存器以后
都应该查询一下这个标志位
然后再来进行下一次操作
而对这个标志位做写1
注意大家这个寄存器的图里写的
W1C Write once to clear
写1会清楚这个标志位
在查询模式里我们需要通过
清除标志位便于下一次查询
在中断模式里我们需要在中断函数里
去清这个标志位
所以这样一件事情
我们也定义成了这样一句宏语句
叫做I2CWait
也就是每一次的
无论读或者写的字节操作完了以后
我们会反复查询这一位
直到它发生改变
并且再把它清除再做下一次操作
这样完成
保证我们的时序
是等待I2C内八个比特Clock的时序
一个字节一个字节的完成通讯
那么我们回过头来
再看我们对于函数的封装
我们会发现在前面的经验上
我们首先I2C模块会封装出一个Init函数
就是这么三段关于时钟的打开
引脚的配置以及I2C的一些初始配置
那么对于读写我们现在控制的
是一个MMA8510这样一个加速度传感器
它内部是有内部地址的
我们在下一个单元会给大家再详细讲
所以封装成它的写函数和读函数
我用这个伪代码给大家写出来
大家自己去写
是不是对于写操作
我们就变成了I2C的三个步骤操作
起始位刚才有宏定义 对吧
然后写一个字节
这个字节必须是这个芯片的子地址
加上一个写的标志位
然后查一下ACK
芯片是否回应
再写入一个寄存器
写入一个铺位号
写入一个子地址
然后查一下芯片有没有ACK
再写入我要给这个寄存器写入的值
再查一下芯片的ACK
再Stop停止位
完成一次完整的操作
那么读操作比刚才要麻烦一点
因为这是一个有子地址的I2C设备
所以就跟我们前面在上个单元讲的
高阶的内容一样
那么我们完成一次读操作要分为两段
首先是一次写操作
写入芯片的地址和一个写的标志位
紧接着呢写入一个字节
这个字节是我们要写的那个子地址
也就是芯片内部寄存器的地址
哪个铺位我要进行读
然后这个操作完成了以后
芯片已经知道主机
要从我这儿读取设备
而且主机是要从我这儿
哪个寄存器读取设备
芯片准备好了对吧
这个时候主机发起一次Restart
然后再做一次新的通讯
这个新的通讯首先是一个读操作
向芯片写入一个芯片的地址
跟一个读的比特位
因为这个比特位是读
接下来的操作就是读取了
由芯片乖乖的把刚才设定那个
寄存器的值的内容
向主机返回一个字节的内容
然后主机给ACK通讯完成
这样两笔通讯中间有一个Restart
完成一次完整的对于这个芯片内
某个寄存器的读取
所以这两个函数的封装
我们用伪代码给大家写清楚它的步骤
它的起始 它的写字节
它的ACK 它的Restart
它的读字节 它的停止位
大家尝试用刚才那些代码 那些宏
可以自己把它写出来
那么如果你能写出这两个函数
也就完成了对于I2C里头
相对麻烦的
有内部地址结构的读写函数的封装
它可以用于这个加速度传感器
也可以用于别的I2C设备
你就真正掌握它了
下一个单元我来讲如何把这个I2C芯片
真正的有效值给读出来
和它的原理
-1.1 课程概览
--1.1 课程概览
-1.2 如何学好嵌入式系统
-2.1 计算机的基本概念、发展历史
-2.2 从晶体管到CPU
-2.3 概念CPU、微控制器MCU和嵌入式系统
-2.4 八卦计算机史
-2.5 不同领域、不同系列的嵌入式系统
-2.6 ARM历史与MKL25Z128 MCU
--2.6 ARM历史与MKL25Z128 MCU【习题】
-3.1 CPU的基本结构和运行机制
-3.2.1 堆栈的概念
--3.2.1 堆栈的概念【习题】
-3.2.2 堆栈的概念-头脑体操
-3.3.1 ARM的体系结构
--3.3.1 ARM的体系结构【习题】
-3.3.2 ARM的体系结构-头脑体操
-3.4.1 中断的概念和机制
-3.4.2 中断子程的概念和编程
-3.5 复位、时钟、存储器和总线
--3.5 复位、时钟、存储器和总线【习题】
-3.6 小结:MCU的总体结构和程序运行机制
--3.6 小结:MCU的总体结构和程序运行机制【习题】
-4.1 第一种外设:IO
-4.2 IO外设的编程实操-点亮LED
-4.3 IO外设的进阶知识
-4.4 嵌入式开发的基本概念与工具链
-4.5 嵌入式开发的进阶知识
-4.6 嵌入式开发中的C语言(上)
--4.6 嵌入式开发中的C语言(上)【习题】
-4.7 嵌入式开发中的C语言(下)
--4.7 嵌入式开发中的C语言(下)【习题】
-E0.1 实验零 开发板的初步认识与工具链的安装
-E0.2 实验零 体验一个例程的编译与下载
-E0.3 实验零 编写第一个程序:点亮核心板LED
-E1 实验一 点灯秘籍
-5.1 ARM微控制器外设学习概述
-5.2.1 ARM微控制器外设:通讯
-5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)
--5.2.2 ARM微控制器外设:异步串行通讯UART的原理(上)【习题】
-5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器的外设:异步串行通讯UART的原理(下)
--5.2.3 ARM微控制器外设:异步串行通讯UART的原理(下)【习题】
-5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
--5.2.4 ARM微控制器外设:RS-232串口与USB虚拟串口
-5.2.5 ARM微控制器外设:UART的寄存器编程(上)
--5.2.5 ARM微控制器外设:UART的寄存器编程(上)
-5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)
--5.2.6 ARM微控制器外设:UART的寄存器编程(下)【习题】
-E2 实验二 UART编程实操
-5.3.1 ARM微控制器外设:IO的中断编程(上)
-5.3.2 ARM微控制器外设:IO的中断编程(下)
-5.4.1 ARM微控制器外设:定时器的原理
-5.4.2 ARM微控制器外设:定时器的编程
--5.4.2 ARM微控制器外设:定时器的编程【习题】
-E3 实验三 定时器中断编程实操
-5.5.1 ARM微控制器外设:PWM的原理
-5.5.2 ARM微控制器外设:PWM寄存器与编程
-5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
--5.5.3 ARM微控制器外设:PWM编程实例—电子音乐
-E4 实验四 数码管显示编程实操
-5.6.1 ARM微控制器外设:SPI通讯简介
--5.6.1 ARM微控制器外设:SPI通讯简介【习题】
-5.6.2 ARM微控制器外设:SPI寄存器与编程
-5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
--5.6.3 ARM微控制器外设:SPI编程实例—OLED显示屏驱动
-5.7.1 ARM微控制器外设:I2C通讯简介
-5.7.2 ARM微控制器外设:I2C的通讯协议
-5.7.3 ARM微控制器外设:I2C寄存器与编程
--5.7.3 ARM微控制器外设:I2C寄存器与编程【习题】
-5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
--5.7.4 ARM微控制器外设:I2C编程实例—加速度传感器
-5.8.1 ARM微控制器外设:ADC简介
-5.8.2 ARM微控制器外设:ADC基础
-5.8.3 ARM微控制器外设:ADC寄存器与编程
-E5 实验五 ADC编程实操
-E6 挑战实验
--E6 挑战实验
-6.1 嵌入式系统的接口与设计
-6.2 嵌入式系统的实例