• ARM_day8:温湿度数据采集应用


    1、IIC通信过程

    主机发送起始信号、主机发送8位(7位从机地址+1位传送方向(0W,1R))、从机应答、发数据、应答、数据传输完,主机发送停止信号

    2、起始信号和终止信号

    SCL时钟线,SDA数据线

    SCL高电平,SDA由高到低——起始信号

    SCL高电平,SDA由低到高——终止信号

    均由主机发出

    3、应答信号和非应答信号

    8位数据+1位应答位——一帧9位

    8位数据传输完,第九个时钟周期,数据线(SDA)低电平,接收方还想接收数据,回复应答信号

                                                           数据线(SDA)高电平,接收方不想接收数据,回复非应信号

    4、数据传输时机

    时钟信号(SCL)高电平,数据线(SDA)稳定时,读取

    时钟信号(SCL)低电平,数据线(SDA)高电平或低电平,写入

    5、IIC从机选择及读写选择

    传输信号包括地址信号、数据信号

    起始信号后必须跟一个8位数据(7位从机地址+1位传送方向位(R/W))(0——W、1——R)

    6、IIC读写时序

    主机向从机发送:

    主机发送起始信号、主机发送8位(7位从机地址+1位写标志)、从机应答、主机发送8位从机寄存器地址、从机应答、主机发送8位数据、从机应答、主机发起终止信号

    主机读取从机数据:

    主机发送起始信号、主机发送8位(7位从机地址+1位写标志)、从机应答、主机发起重复起始信号、主机发送8位(7位从机地址+1位读标志)、从机应答、从机发送8位数据、主机非应答信号、主机发起终止信号

    实验:I2C读取温湿度传感器数据

    1、SCL时钟线——PF14,SDA数据线——PF15        引脚连接

            I2C总线引脚初始化:(PF14、PF15共同初始化)使能GPIOF外设时钟、设输出功能、推挽输出、输出速度、上拉下拉、空闲状态下的SCL和SDA状态(拉高)——起始信号要求SCL高,SDA从高到低

    2、模拟I2C开始信号时序

            SDA数据线保持输出状态  PF15输出;(PF15管脚设为输出)

            空闲状态SCL、SDA拉高(起始信号要求SCL高,SDA从高到低);(选用ODR(设置)、BSRR(复位)、BRR(清空)使输出高低电平)

            延迟一段时间,保持稳定后,拉低SDA数据线完成起始信号;

            延迟一段时间,拉低SCL时钟线,才能进行写入数据;

    3、主机向从机写数据(高位到低位)

            SDA数据线保持输出状态  PF15输出;(PF15管脚设为输出)

            时钟线拉低,才能写数据;

            循环发送数据8位(0-7),延迟时间,保持时钟线稳定,开始发送数据;(要发送的数据 dat & 0x80(10000000),判断其真假,真则拉高数据线为高电平1,否则拉低数据线为低电平0)

            每次发送一位数据,延时后拉高时钟线(SCL),接收器才能读数据;

            延时等待接收器接收数据,再延时后将数据左移一位再&0x80进行循环

    4、计算温湿度

            湿度:hem=125*测量值/65536-6

            温度:tem=175.72*测量值/65536-46.85

    程序代码:

    main.c:

    1. #include "si7006.h"
    2. extern void printf(const char* fmt, ...);
    3. int main()
    4. {
    5. //si7006初始化
    6. si7006_init();
    7. unsigned short hum;
    8. short tem;
    9. while(1)
    10. {
    11. //读取温湿度
    12. hum=si70006_read_hum();
    13. tem=si70006_read_tem();
    14. //计算温湿度数据
    15. hum=hum*125/65536-6;
    16. tem=tem*175.72/65536-46.85;
    17. printf("hum:%d\n",hum);
    18. printf("tem:%d\n",tem);
    19. delay_ms(1000);
    20. //湿度大于65开启马达
    21. if(hum>65)
    22. {
    23. GPIOF->ODR |= (0x1<<6);
    24. }
    25. else if(hum<=60) //湿度小于60关闭马达
    26. {
    27. GPIOF->ODR &= (~(0x1<<6));
    28. }
    29. //温度大于25开启风扇
    30. if(tem>=25)
    31. {
    32. GPIOE->ODR |= (0x1<<9);
    33. }
    34. else if(tem<25) //湿度小于25关闭风扇
    35. {
    36. GPIOE->ODR &= (~(0x1<<9));
    37. }
    38. }
    39. return 0;
    40. }

    si7006.h:

    1. #ifndef __SI7006_H__
    2. #define __SI7006_H__
    3. #include "iic.h"
    4. void delay_ms(int ms);
    5. void si7006_init();
    6. unsigned short si70006_read_hum();
    7. short si70006_read_tem();
    8. #endif

    si7006.c:

    1. #include "si7006.h"
    2. extern void printf(const char* fmt, ...);
    3. void delay_ms(int ms)
    4. {
    5. int i,j;
    6. for(i=0;i
    7. {
    8. for(j=0;j<2000;j++)
    9. {
    10. }
    11. }
    12. }
    13. void si7006_init()
    14. {
    15. //发起起始信号
    16. i2c_init();//I2C总线引脚初始化
    17. i2c_start();//模拟i2c开始信号的时序
    18. //发送7bit从机地址和写标志位 0x80
    19. i2c_write_byte(0x40<<1|0);//主机向从机写8bit数据
    20. //等待从机应答
    21. i2c_wait_ack();//等待接收器应答
    22. //发送寄存器地址 0XE6
    23. i2c_write_byte(0xE6);
    24. //等待从机应答
    25. i2c_wait_ack();//等待接收器应答
    26. //向从机发送数据 0x3A
    27. i2c_write_byte(0x3A);
    28. //等待从机应答
    29. i2c_wait_ack();
    30. //发送终止信号
    31. i2c_stop();
    32. }
    33. unsigned short si70006_read_hum()//湿度读取
    34. {
    35. unsigned char hum_l,hum_h;
    36. unsigned short hum;
    37. //主机发送起始信号
    38. i2c_init();
    39. i2c_start();
    40. //主机发送7bit从机地址+1bit写标志
    41. i2c_write_byte(0x40<<1|0);
    42. //等待从机应答
    43. i2c_wait_ack();
    44. //主机发送8bit寄存器地址
    45. i2c_write_byte(0xE5);
    46. //等待从机应答
    47. i2c_wait_ack();
    48. //主机发起重复起始信号
    49. i2c_start();
    50. //主机发送7bit从机地址+1bit 读 0x81
    51. i2c_write_byte(0x40<<1|1);
    52. //等待从机应答
    53. i2c_wait_ack();
    54. //延时等待从机测量数据
    55. delay_ms(100);
    56. //读取湿度的高8bit数据 hum_h
    57. //发送应答信号
    58. hum_h=i2c_read_byte(0);
    59. //读取湿度的低8位数据 hum_l
    60. //发送非应答信号
    61. hum_l=i2c_read_byte(1);
    62. //发送终止信号
    63. //将读取到的数据的低8位和高8位合成一个完整数据
    64. hum=hum_h<<8 | hum_l;
    65. return hum;
    66. }
    67. short si70006_read_tem()//温度读取
    68. {
    69. unsigned char tem_l,tem_h;
    70. unsigned short tem;
    71. //主机发送起始信号
    72. i2c_init();
    73. i2c_start();
    74. //主机发送7bit从机地址+1bit写标志
    75. i2c_write_byte(0x40<<1|0);
    76. //等待从机应答
    77. i2c_wait_ack();
    78. //主机发送8bit寄存器地址
    79. i2c_write_byte(0xE3);
    80. //等待从机应答
    81. i2c_wait_ack();
    82. //主机发起重复起始信号
    83. i2c_start();
    84. //主机发送7bit从机地址+1bit 读 0x81
    85. i2c_write_byte(0x40<<1|1);
    86. //等待从机应答
    87. i2c_wait_ack();
    88. //延时等待从机测量数据
    89. delay_ms(100);
    90. //读取温度的高8bit数据 tem_h
    91. //发送应答信号
    92. tem_h=i2c_read_byte(0);
    93. //读取温度的低8位数据 tem_l
    94. //发送非应答信号
    95. tem_l=i2c_read_byte(1);
    96. //发送终止信号
    97. //将读取到的数据的低8位和高8位合成一个完整数据
    98. tem=tem_h<<8 | tem_l;
    99. return tem;
    100. }

    iic.h:

    1. #ifndef __IIC_H__
    2. #define __IIC_H__
    3. #include "stm32mp1xx_gpio.h"
    4. #include "stm32mp1xx_rcc.h"
    5. /* 通过程序模拟实现I2C总线的时序和协议
    6. * GPIOF ---> AHB4
    7. * I2C1_SCL ---> PF14
    8. * I2C1_SDA ---> PF15
    9. *
    10. * */
    11. #define SET_SDA_OUT do{GPIOF->MODER &= (~(0x3 << 30)); \
    12. GPIOF->MODER |= (0x1 << 30);}while(0)
    13. #define SET_SDA_IN do{GPIOF->MODER &= (~(0x3 << 30));}while(0)
    14. #define I2C_SCL_H do{GPIOF->BSRR |= (0x1 << 14);}while(0)
    15. #define I2C_SCL_L do{GPIOF->BRR |= (0x1 << 14);}while(0)
    16. #define I2C_SDA_H do{GPIOF->BSRR |= (0x1 << 15);}while(0)
    17. #define I2C_SDA_L do{GPIOF->BRR |= (0x1 << 15);}while(0)
    18. #define I2C_SDA_READ (GPIOF->IDR & (0x1 << 15))
    19. void delay_us(void);//微秒延时
    20. void delay(int ms);
    21. void i2c_init(void);//初始化
    22. void i2c_start(void);//起始信号
    23. void i2c_stop(void);//终止信号
    24. void i2c_write_byte(unsigned char dat);//写一个字节数据
    25. unsigned char i2c_read_byte(unsigned char ack);//读取一个字节数据
    26. unsigned char i2c_wait_ack(void); //等待应答信号
    27. void i2c_ack(void);//发送应答信号
    28. void i2c_nack(void);//发送非应答信号
    29. #endif

    iic.c:

    1. #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. * 函数名 : i2c_init
    16. * 函数功能: i2C总线引脚的初始化, 通用输出,推挽输出,输出速度,
    17. * 函数参数:无
    18. * 函数返回值:无
    19. * */
    20. void i2c_init(void)
    21. {
    22. // 使能GPIOF端口的时钟
    23. RCC->MP_AHB4ENSETR |= (0x1 << 5);
    24. //使能风扇的时钟
    25. RCC->MP_APB2ENSETR |= 0x1;
    26. //使能马达的时钟
    27. RCC->MP_APB2ENSETR |= (0x1<<3);
    28. // 设置PF14,PF15引脚为通用的输出功能
    29. GPIOF->MODER &= (~(0xF << 28));
    30. GPIOF->MODER |= (0x5 << 28);
    31. //设置PE9为输出
    32. GPIOE->MODER &= (~(0x3<<18));
    33. GPIOE->MODER |= (0x1<<18);
    34. //设置PF6为输出
    35. GPIOF->MODER &= (~(0x3<<12));
    36. GPIOF->MODER |= (0x1<<12);
    37. // 设置PF14, PF15引脚为推挽输出
    38. GPIOF->OTYPER &= (~(0x3 << 14));
    39. //设置PE9为推挽输出
    40. GPIOE->OTYPER &= (~(0x1<<9));
    41. //设置PF6为推挽输出
    42. GPIOF->OTYPER &= (~(0x1<<6));
    43. // 设置PF14, PF15引脚为高速输出
    44. GPIOF->OSPEEDR |= (0xF << 28);
    45. //设置PE9为低速输出
    46. GPIOE->OSPEEDR &= (~(0x3<<18));
    47. //设置PF6为低速输出
    48. GPIOF->OSPEEDR &= (~(0x3<<12));
    49. // 设置PF14, PF15引脚的禁止上拉和下拉
    50. GPIOF->PUPDR &= (~(0xF << 28));
    51. //设置PE9没有上拉下拉电阻
    52. GPIOE->PUPDR &= (~(0x3<<18));
    53. //设置PF6没有上拉下拉电阻
    54. GPIOF->PUPDR &= (~(0x3<<12));
    55. // 空闲状态SDA和SCL拉高
    56. I2C_SCL_H;
    57. I2C_SDA_H;
    58. }
    59. /*
    60. * 函数名:i2c_start
    61. * 函数功能:模拟i2c开始信号的时序
    62. * 函数参数:无
    63. * 函数返回值:无
    64. * */
    65. void i2c_start(void)
    66. {
    67. /*
    68. * 开始信号:时钟在高电平期间,数据线从高到低的变化
    69. * --------
    70. * SCL \
    71. * --------
    72. * ----
    73. * SDA \
    74. * --------
    75. * */
    76. //确保SDA是输出状态 PF15输出
    77. SET_SDA_OUT;
    78. // 空闲状态SDA和SCL拉高
    79. I2C_SCL_H;
    80. I2C_SDA_H;
    81. delay_us();//延时等待一段时间
    82. I2C_SDA_L;//数据线拉低
    83. delay_us();//延时等待一段时间
    84. I2C_SCL_L;//时钟线拉低,让总线处于占用状态
    85. }
    86. /*
    87. * 函数名:i2c_stop
    88. * 函数功能:模拟i2c停止信号的时序
    89. * 函数参数:无
    90. * 函数返回值:无
    91. * */
    92. void i2c_stop(void)
    93. {
    94. /*
    95. * 停止信号 : 时钟在高电平期间,数据线从低到高的变化
    96. * ----------
    97. * SCL /
    98. * --------
    99. * --- -------
    100. * SDA X /
    101. * --- -------
    102. * */
    103. //确保SDA是输出状态 PF15输出
    104. SET_SDA_OUT;
    105. //时钟线拉低
    106. I2C_SCL_L;//为了修改数据线的电平
    107. delay_us();//延时等待一段时间
    108. I2C_SDA_L;//数据线拉低
    109. delay_us();//延时等待一段时间
    110. //时钟线拉高
    111. I2C_SCL_H;
    112. delay_us();//延时等待一段时间
    113. I2C_SDA_H;//数据线拉高
    114. }
    115. /*
    116. * 函数名: i2c_write_byte
    117. * 函数功能:主机向i2c总线上的从设备写8bits数据
    118. * 函数参数:dat : 等待发送的字节数据
    119. * 函数返回值: 无
    120. * */
    121. void i2c_write_byte(unsigned char dat)
    122. {
    123. /*
    124. * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
    125. * 时钟在高电平期间,接收器从数据线上读取数据
    126. * ---- --------
    127. * SCL \ / \
    128. * -------- --------
    129. * -------- ------------------ ---
    130. * SDA X X
    131. * -------- ------------------ ---
    132. *
    133. * 先发送高位在发送低位
    134. * */
    135. //确保SDA是输出状态 PF15输出
    136. SET_SDA_OUT;
    137. unsigned int i;
    138. for(i=0;i<8;i++)
    139. {
    140. //时钟线拉低
    141. I2C_SCL_L;
    142. delay_us();//延时
    143. //0X3A->0011 1010 0X80->10000000
    144. if(dat&0X80)//最高位为1
    145. {
    146. //发送1
    147. I2C_SDA_H;
    148. }
    149. else //最高位为0
    150. {
    151. I2C_SDA_L;//发送0
    152. }
    153. delay_us();//延时
    154. //时钟线拉高,接收器接收
    155. I2C_SCL_H;
    156. delay_us();//延时,用于等待接收器接收数据
    157. delay_us();//延时
    158. //将数据左移一位,让原来第6位变为第7位
    159. dat = dat<<1;
    160. }
    161. }
    162. /*
    163. * 函数名:i2c_read_byte
    164. * 函数功能: 主机从i2c总线上的从设备读8bits数据,
    165. * 主机发送一个应答或者非应答信号
    166. * 函数参数: 0 : 应答信号 1 : 非应答信号
    167. * 函数返回值:读到的有效数据
    168. *
    169. * */
    170. unsigned char i2c_read_byte(unsigned char ack)
    171. {
    172. /*
    173. * 数据信号:时钟在低电平期间,发送器向数据线上写入数据
    174. * 时钟在高电平期间,接收器从数据线上读取数据
    175. * ---- --------
    176. * SCL \ / \
    177. * -------- --------
    178. * -------- ------------------ ---
    179. * SDA X X
    180. * -------- ------------------ ---
    181. *
    182. * 先接收高位, 在接收低位
    183. * */
    184. unsigned int i;
    185. unsigned char dat;//保存接受的数据
    186. //将数据线设置为输入
    187. SET_SDA_IN;
    188. for(i=0;i<8;i++)
    189. {
    190. //先把时钟线拉低,等一段时间,保证发送器发送完毕数据
    191. I2C_SCL_L;
    192. delay_us();
    193. delay_us();//保证发送器发送完数据
    194. //时钟线拉高,读取数据
    195. I2C_SCL_H;
    196. delay_us();
    197. dat=dat<<1;//数值左移 一定要先左移在赋值,不然数据会溢出
    198. if(I2C_SDA_READ)//pf15管脚得到了一个高电平输入
    199. {
    200. dat |=1; //0000 0110
    201. }
    202. else
    203. {
    204. dat &=(~0X1);
    205. }
    206. delay_us();
    207. }
    208. if(ack)
    209. {
    210. i2c_nack();//发送非应答信号,不再接收下一次数据
    211. }
    212. else
    213. {
    214. i2c_ack();//发送应答信号
    215. }
    216. return dat;//将读取到的数据返回
    217. }
    218. /*
    219. * 函数名: i2c_wait_ack
    220. * 函数功能: 主机作为发送器时,等待接收器返回的应答信号
    221. * 函数参数:无
    222. * 函数返回值:
    223. * 0:接收到的应答信号
    224. * 1:接收到的非应答信号
    225. * */
    226. unsigned char i2c_wait_ack(void)
    227. {
    228. /*
    229. * 主机发送一个字节之后,从机给主机返回一个应答信号
    230. *
    231. * -----------
    232. * SCL / M:读 \
    233. * ------------- --------
    234. * --- ---- --------------------
    235. * SDA X X
    236. * --- --------------------
    237. * 主 释 从机 主机
    238. * 机 放 向数据 读数据线
    239. * 总 线写 上的数据
    240. * 线 数据
    241. * */
    242. //时钟线拉低,接收器可以发送信号
    243. I2C_SCL_L;
    244. I2C_SDA_H;//先把数据线拉高,当接收器回应应答信号时,数据线会拉低
    245. delay_us();
    246. SET_SDA_IN;//设置数据线为输入
    247. delay_us();//等待从机响应
    248. delay_us();
    249. I2C_SCL_H;//用于读取数据线数据
    250. if(I2C_SDA_READ)//PF15得到一个高电平输入,收到非应答信号
    251. return 1;
    252. I2C_SCL_L;//时钟线拉低,让数据线处于占用状态
    253. return 0;
    254. }
    255. /*
    256. * 函数名: iic_ack
    257. * 函数功能: 主机作为接收器时,给发送器发送应答信号
    258. * 函数参数:无
    259. * 函数返回值:无
    260. * */
    261. void i2c_ack(void)
    262. {
    263. /* --------
    264. * SCL / \
    265. * ------- ------
    266. * ---
    267. * SDA X
    268. * --- -------------
    269. * */
    270. //保证数据线是输出
    271. SET_SDA_OUT;
    272. I2C_SCL_L;//拉低时钟线
    273. delay_us();
    274. I2C_SDA_L;//数据线拉低,表示应答信号
    275. delay_us();
    276. I2C_SCL_H;//时钟线拉高,等待发送器读取应答信号
    277. delay_us();//让从机读取我们当前的回应
    278. delay_us();
    279. I2C_SCL_L;//数据线处于占用状态,发送器发送下一次数据
    280. }
    281. /*
    282. * 函数名: iic_nack
    283. * 函数功能: 主机作为接收器时,给发送器发送非应答信号
    284. * 函数参数:无
    285. * 函数返回值:无
    286. * */
    287. void i2c_nack(void)
    288. {
    289. /* --------
    290. * SCL / \
    291. * ------- ------
    292. * --- ---------------
    293. * SDA X
    294. * ---
    295. * */
    296. //保证数据线是输出
    297. SET_SDA_OUT;
    298. I2C_SCL_L;//拉低时钟线
    299. delay_us();
    300. I2C_SDA_H;//数据线拉高,表示非应答信号
    301. delay_us();
    302. I2C_SCL_H;//时钟线拉高,等待发送器读取应答信号
    303. delay_us();
    304. delay_us();
    305. I2C_SCL_L;//数据线处于占用状态,发送器发送下一次数据
    306. }

    运行结果:

  • 相关阅读:
    [剑指 Offer 11]旋转数组的最小数字
    QreryRunner类和ResultSetHandler
    2022级大学新生-电脑推荐
    Android_Monkey_测试执行策略及标准
    python毕业设计项目源码选题(19)篮球、足球、羽毛球等运动场地预约系统毕业设计毕设作品开题报告开题答辩PPT
    NPDP|什么样的产品经理可以被称为优秀?
    Java 数据结构篇-实现单链表核心API
    【百日刷题计划 第十一天】——熟悉函数,递归及递推 函数,递归及递推基础题
    【微服务】Day12(搜索功能、Quartz)
    UVM寄存器模型:reg adapter实现和集成
  • 原文地址:https://blog.csdn.net/2301_81513928/article/details/137885260