• IIC/I2C总线实验


    1、I2C总线相关概念的介绍

    1> I2C总线是PHLIPS公司在八十年代初推出的一种同步串行的半双工总线,主要用于连接整体电路。

    2> I2C总线为两线制,只有两根双向信号线。一根是数据线SDA,另一根是 时钟线SCL

    3> I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。

    ​ 传感器:温湿度传感器,环境光接近传感器,心率和脉搏传感器,触摸屏传感器。

    ​ EEPROM,DS1302, ADC芯片,

    4> I2C总线上需要外接两个上拉电阻,上拉电阻的作用是再空闲态时,保持总线为高电平的状态

    2、I2C总线的硬件连接

    1> I2C是具备多主机多从机系统所需的包括总线裁决功能的高性能串行总线。

    2> 在同一时刻只能由一个主机一个从机使用I2C总线

    3> 每个接到I2C总线上的器件都有唯一的从机地址。

    4> 主机与其它器件进行数据传送时总线上发送数据的器件为发送器,总线上接收数据的器件则为接收器。

    5> 可以主动发起通信的叫做主机(Master),只能被动收发数据的叫做从机(Slave)。

    3、I2C总线的时序图分析

    时序图:随着时钟信号的变化,数据线上的数据的变化的图形就叫做时序图。

    3.1 起始信号和终止信号时序图

    1> SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;

    2> SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号;

    3> 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;终止信号产生后,总线就处于空闲态。

    3.2 数据信号时序图

    1> I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,

    ​ 只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

    2> 时钟在低电平期间,发送器向数据线上写入数据,此时允许数据线上的数据变化;

    3> 时钟在高电平期间,接收器从数据线上读取数据,此时必须保持数据线上的数据稳定;

    4> 一个时钟周期完成一个bit位数据的收发。

    3.3 应答信号时序图

    1> I2C总线采用了应答的机制,接收器收到1个字节的数据之后,必须给发送器返回应答信号。

    2> 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),在发送低位,

    ​ 每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

    3> 在第九个时钟周期的低电平期间,接收器向数据线上写入数据,

    ​ 在第9个时钟周期的高电平期间,发送器从数据线上读取数据,

    ​ 如果读到的是低电平,表示应答信号,如果读到的是高电平,表示非应答信号。

    3.4 从机地址

    1> I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

    2> 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。

    3> 总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。

    4、IIC总线的硬件连接

    5、IIC总线的通信协议

    5.1 主机给从机写一个字节的通信协议

    5.2 主机给从机发送连续的多个字节的通信协议

    5.3 主机从从机读一个字节的通信协议

    5.4 主机从从机读连续的多个字节的通信协议

    从机只能被动的收发数据,当从机给主机发送数据时,如果主机给从机发送的是应答信号,则从机会认为主机还想接收下一个字节的数据,因此从机会给主机发送下一个字节的数据。如果从机收到的是非应答信号,则从机不会发送下一个字节的数据给主机。

    6分析手册(这个略,每个芯片手册都不一样,具体解析都再代码实现中有详细写到)

    7代码实现

    1. #include "../include/iic.h"
    2. extern void printf(const char *fmt, ...);
    3. /*
    4. * 函数名 : delay_us
    5. * 函数功能:延时函数
    6. * 函数参数:无
    7. * 函数返回值:无
    8. * */
    9. void delay_us(void)
    10. {
    11. unsigned int i = 2000;
    12. while (i--)
    13. ;
    14. }
    15. /*
    16. * 函数名 : i2c_init
    17. * 函数功能: i2C总线引脚的初始化, 通用输出,推挽输出,输出速度,
    18. * 函数参数:无
    19. * 函数返回值:无
    20. * */
    21. void i2c_init(void)
    22. {
    23. // 使能GPIOF端口的时钟
    24. RCC->MP_AHB4ENSETR |= (0x1 << 5);
    25. // 设置PF14,PF15引脚为通用的输出功能
    26. GPIOF->MODER &= (~(0xF << 28));
    27. GPIOF->MODER |= (0x5 << 28);
    28. // 设置PF14, PF15引脚为推挽输出
    29. GPIOF->OTYPER &= (~(0x3 << 14));
    30. // 设置PF14, PF15引脚为高速输出
    31. GPIOF->OSPEEDR |= (0xF << 28);
    32. // 设置PF14, PF15引脚的禁止上拉和下拉
    33. GPIOF->PUPDR &= (~(0xF << 28));
    34. // 空闲状态SDA和SCL拉高
    35. I2C_SCL_H;
    36. I2C_SDA_H;
    37. }
    38. /*
    39. * 函数名:i2c_start
    40. * 函数功能:模拟i2c开始信号的时序
    41. * 函数参数:无
    42. * 函数返回值:无
    43. * */
    44. void i2c_start(void)
    45. {
    46. /*
    47. * 开始信号:时钟在高电平期间,数据线从高到低的变化
    48. * --------
    49. * SCL \
    50. * --------
    51. * ----
    52. * SDA \
    53. * --------
    54. * */
    55. SET_SDA_OUT; // 设置SDA为输出模式,确保一定为输出模式
    56. I2C_SDA_H; // SDA拉高
    57. I2C_SCL_H; // SCL拉高
    58. delay_us();
    59. I2C_SDA_L; // SDA拉低
    60. delay_us();
    61. I2C_SCL_L; // SCL拉低,I2C总线就处于占用态
    62. }
    63. /*
    64. * 函数名:i2c_stop
    65. * 函数功能:模拟i2c停止信号的时序
    66. * 函数参数:无
    67. * 函数返回值:无
    68. * */
    69. void i2c_stop(void)
    70. {
    71. /*
    72. * 停止信号 : 时钟在高电平期间,数据线从低到高的变化
    73. * ----------
    74. * SCL /
    75. * --------
    76. * --- -------
    77. * SDA X /
    78. * --- -------
    79. * 为了确保停止信号是一个上升沿,因此在时钟为低电平期间
    80. * 将数据线拉低,确保可以产生上升沿。
    81. * */
    82. SET_SDA_OUT; // 设置SDA为输出
    83. I2C_SCL_L; // SCL拉低
    84. delay_us();
    85. I2C_SDA_L; // SDA拉低
    86. delay_us();
    87. I2C_SCL_H; // SCL拉高
    88. delay_us();
    89. I2C_SDA_H; // SDA拉高
    90. }
    91. /*
    92. * 函数名: i2c_write_byte
    93. * 函数功能:主机向i2c总线上的从设备写8bits数据
    94. * 函数参数:dat : 等待发送的字节数据
    95. * 函数返回值: 无
    96. * */
    97. void i2c_write_byte(unsigned char dat)
    98. {
    99. /*
    100. * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
    101. * 时钟在高电平期间,接收器从数据线上读取数据
    102. * ---- --------
    103. * SCL \ / \
    104. * -------- --------
    105. * -------- ------------------ ---
    106. * SDA X X
    107. * -------- ------------------ ---
    108. * 先发送高位在发送低位
    109. * */
    110. unsigned int i;
    111. SET_SDA_OUT; // SDA为输出
    112. for (i = 0 ; i < 8; i++) {
    113. I2C_SCL_L; // SCL拉低
    114. delay_us();
    115. if (dat & 0x80) // 判断最高位的值
    116. I2C_SDA_H; // 向数据线上写入高电平
    117. else
    118. I2C_SDA_L; // 向数据线上写入低电平
    119. delay_us();
    120. I2C_SCL_H; // SCL拉高
    121. delay_us();
    122. delay_us(); // 等待从机将数据线上的数据读走
    123. dat <<= 1; // 将次高位的数据移动到最高位
    124. }
    125. }
    126. /*
    127. * 函数名:i2c_read_byte
    128. * 函数功能: 主机从i2c总线上的从设备读8bits数据,
    129. * 主机发送一个应答或者非应答信号给从机
    130. * 函数参数: 0 : 应答信号 1 : 非应答信号
    131. * 函数返回值:读到的有效数据
    132. *
    133. * */
    134. unsigned char i2c_read_byte(unsigned char ack)
    135. {
    136. /*
    137. * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
    138. * 时钟在高电平期间,接收器从数据线上读取数据
    139. * ---- --------
    140. * SCL \ / \
    141. * -------- --------
    142. * -------- ------------------ ---
    143. * SDA X X
    144. * -------- ------------------ ---
    145. *
    146. * 先接收高位, 在接收低位
    147. * */
    148. unsigned char dat;
    149. unsigned int i;
    150. SET_SDA_IN; // 设置SDA为输入
    151. for (i = 0; i < 8; i++) {
    152. I2C_SCL_L; // SCL拉低
    153. delay_us();
    154. delay_us(); // 等待从机向数据线上写入数据
    155. I2C_SCL_H; // SCL拉高
    156. delay_us();
    157. dat <<= 1;
    158. if (I2C_SDA_READ)
    159. dat |= 1;
    160. else
    161. dat |= 0;
    162. delay_us();
    163. }
    164. if (!ack)
    165. i2c_ack(); // 发送应答信号
    166. else
    167. i2c_nack(); // 发送非应答信号
    168. return dat;
    169. }
    170. /*
    171. 将dat中数据的左移1位写到接收数据的上边。
    172. 为什么将dat中的数据左移1位,写道接收数据的下边不可以?
    173. 假设接收的数据为0xFF,
    174. 先接收数据 在进行左移
    175. 第1次循环 0000 0001 0000 0010
    176. 第2次循环 0000 0011 0000 0110
    177. 第3次循环 0000 0111 0000 1110
    178. 第4次循环 0000 1111 0001 1110
    179. 第5次循环 0001 1111 0011 1110
    180. 第6次循环 0011 1111 0111 1110
    181. 第7次循环 0111 1111 1111 1110
    182. 第8次循环 1111 1111 1111 1110
    183. */
    184. /*
    185. * 函数名: i2c_wait_ack
    186. * 函数功能: 主机作为发送器时,等待接收器返回的应答信号
    187. * 函数参数:无
    188. * 函数返回值:
    189. * 0:接收到的应答信号
    190. * 1:接收到的非应答信号
    191. * */
    192. unsigned char i2c_wait_ack(void)
    193. {
    194. int ack;
    195. /*
    196. * 主机发送一个字节之后,从机给主机返回一个应答信号,主机接收应答信号
    197. * -----------
    198. * SCL / M:读 \
    199. * ----------------- --------
    200. * --- -------- --------------------
    201. * SDA X X
    202. * --- --------------------
    203. * 主 释 设 从机 主机
    204. * 机 放 置 向数据 读数据线
    205. * 总 SDA 线写 上的数据
    206. * 线 输 数据
    207. * 入
    208. * */
    209. I2C_SCL_L; // SCL拉低
    210. delay_us();
    211. I2C_SDA_H; // SDA拉高, 释放总线
    212. SET_SDA_IN; // 设置SDA为输入
    213. delay_us();
    214. delay_us(); // 等待从机向数据线上写入应答信号
    215. I2C_SCL_H; // SCL拉高
    216. delay_us();
    217. if (I2C_SDA_READ)
    218. ack = 1; // 非应答信号
    219. else
    220. ack = 0; // 应答信号
    221. delay_us();
    222. I2C_SCL_L;
    223. return ack;
    224. }
    225. /*
    226. * 函数名: iic_ack
    227. * 函数功能: 主机作为接收器时,给发送器发送应答信号
    228. * 函数参数:无
    229. * 函数返回值:无
    230. * */
    231. void i2c_ack(void)
    232. {
    233. /* --------
    234. * SCL / \
    235. * ------- ------
    236. * ---
    237. * SDA X
    238. * --- -------------
    239. * 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
    240. * 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
    241. * 如果读到低电平表示应答信号
    242. * */
    243. SET_SDA_OUT; // 设置SDA为输出
    244. I2C_SCL_L; // SCL拉低
    245. delay_us();
    246. I2C_SDA_L; // SDA拉低,发送的是应答信号
    247. delay_us();
    248. I2C_SCL_H; // SCL拉高
    249. delay_us();
    250. delay_us(); // 等待从机接收应答信号
    251. I2C_SCL_L; // SCL拉低
    252. }
    253. /*
    254. * 函数名: iic_nack
    255. * 函数功能: 主机作为接收器时,给发送器发送非应答信号
    256. * 函数参数:无
    257. * 函数返回值:无
    258. * */
    259. void i2c_nack(void)
    260. {
    261. /* --------
    262. * SCL / \
    263. * ------- ------
    264. * --- ---------------
    265. * SDA X
    266. * ---
    267. * 在第九个时钟周期的低电平期间,接收器向数据线写入数据,
    268. * 在第九个时钟周期的高电平期间,发送器从数据线上读取数据,
    269. * 如果读到高电平表示非应答信号
    270. * */
    271. SET_SDA_OUT; // 设置SDA为输出
    272. I2C_SCL_L; // SCL拉低
    273. delay_us();
    274. I2C_SDA_H; // SDA拉高,发送的是非应答信号
    275. delay_us();
    276. I2C_SCL_H; // SCL拉高
    277. delay_us();
    278. delay_us(); // 等待从机接收非应答信号
    279. I2C_SCL_L; // SCL拉低
    280. }

  • 相关阅读:
    什么是乌干达COC认证?乌干达COC认证是什么意思?
    混凝土基础的智能设计:VisualFoundation 12.0 Crack
    MEIS —— 前端部分基本配置
    Lambda表达式
    Swagger未授权访问漏洞
    openpyxl隐藏/删除excel某一列
    第14篇ESP32 idf wifi联网_WiFi STA 模式(连接到WIFI)LCD ST7920液晶屏显示
    VUE(0) : vue-element-admin[0] : 常用语法
    使用awescnb自定义博客园皮肤
    k8s核心概念pod 基本定义和命令
  • 原文地址:https://blog.csdn.net/a2998658795/article/details/126680964