学习目标

  1. 掌握I2C配置方式
  2. 掌握I2C读写操作

    学习内容

    需求

    开发板中的PCF8563的RTC时钟设置和读取。
    214.png215.png

    I2C功能配置

    055.png
    选择对应的I2C,打开。
    056.png
    057.png
    配置完成后可以查询引脚是否符合要求。

I2C编码

移植PCF8563驱动

  1. #ifndef __PCF8563_H__
  2. #define __PCF8563_H__
  3. #include "stm32f4xx.h"
  4. #include "i2c.h"
  5. #ifndef u8
  6. #define u8 uint8_t
  7. #endif
  8. #ifndef u16
  9. #define u16 uint16_t
  10. #endif
  11. // 如果定义了该宏,Alarm的相关函数才会被编译并启用,被注释掉就是禁用
  12. //#define PCF8563_ALARM_ENABLE
  13. // 如果定义了该宏,Timer的相关函数才会被编译并启用,被注释掉就是禁用
  14. //#define PCF8563_TIMER_ENABLE
  15. // 设备地址
  16. #define PCF8563_ADDR 0x51
  17. // 存储地址:时间的存储地址开始位置
  18. #define PCF8563_REG_TD 0x02
  19. // 控制寄存器2
  20. #define PCF8563_REG_CS2 0x01
  21. // I2C写操作
  22. #define I2C_WRITE(a, r, p, n) I2C1_write(a, r, p, n)
  23. // I2C读操作
  24. #define I2C_READ(a, r, p, n) I2C1_read(a, r, p, n)
  25. // 秒、分、时、星期、日、月、年、世纪
  26. // year = 2023, month = 2, day = 28, week = 4;
  27. // hour = 23, minute = 59, second = 52;
  28. typedef struct {
  29. u16 year;
  30. u8 month;
  31. u8 day;
  32. u8 week;
  33. u8 hour;
  34. u8 minute;
  35. u8 second;
  36. } Clock_t;
  37. // 警报、闹铃Alarm结构体
  38. typedef struct {
  39. u8 minute;
  40. u8 hour;
  41. u8 day;
  42. u8 weekday;
  43. } Alarm_t;
  44. typedef enum {
  45. HZ4096 = 0,
  46. HZ64 = 1,
  47. HZ1 = 2,
  48. HZ1_60 = 3,
  49. }TimerFreq;
  50. void PCF8563_init();
  51. void PCF8563_set_clock(Clock_t c);
  52. void PCF8563_get_clock(Clock_t *c);
  53. void PCF8563_set_alarm(Alarm_t alarm);
  54. void PCF8563_enable_alarm();
  55. void PCF8563_disable_alarm();
  56. void PCF8563_alarm_clear_flag();
  57. void PCF8563_set_timer(TimerFreq freq ,u8 countdown);
  58. void PCF8563_enable_timer();
  59. void PCF8563_disable_timer();
  60. void PCF8563_timer_clear_flag();
  61. #ifdef PCF8563_ALARM_ENABLE
  62. extern void PCF8563_on_alarm();
  63. #endif
  64. #ifdef PCF8563_TIMER_ENABLE
  65. extern void PCF8563_on_timer();
  66. #endif
  67. #endif
  1. #include "PCF8563.h"
  2. #include <stdio.h>
  3. #define NUMBER_TD 7
  4. void PCF8563_init() {
  5. }
  6. void PCF8563_set_clock(Clock_t c) {
  7. u8 p[NUMBER_TD]; // 用于存储时间字节数组
  8. u8 Cent; // 世纪:0代表本世纪20, 1代表下个世纪,年99->00时交替
  9. // 将数值转成一个字节表达 (BCD)
  10. // 秒:VL 1 1 1 - 0 0 0 0
  11. p[0] = ((c.second / 10) << 4) + (c.second % 10);
  12. // 分: X 1 1 1 - 0 0 0 0
  13. p[1] = ((c.minute / 10) << 4) + (c.minute % 10);
  14. // 时: X X 1 1 - 0 0 0 0
  15. p[2] = ((c.hour / 10) << 4) + (c.hour % 10);
  16. // 天: X X 1 1 - 0 0 0 0
  17. p[3] = ((c.day / 10) << 4) + (c.day % 10);
  18. // 周: X X X X - X 0 0 0
  19. p[4] = c.week;
  20. // 世纪 20xx , 21xx
  21. Cent = (c.year >= 2100 ? 1 : 0);
  22. // 月: C X X 1 - 0 0 0 0
  23. p[5] = (Cent << 7) + ((c.month / 10) << 4) + (c.month % 10);
  24. // 年: 1 1 1 1 - 0 0 0 0 2023, 2036
  25. p[6] = ((c.year % 100 / 10) << 4) + (c.year % 10);
  26. // 设置数据Write
  27. I2C_WRITE(PCF8563_ADDR, PCF8563_REG_TD, p, NUMBER_TD);
  28. }
  29. void PCF8563_get_clock(Clock_t *c) {
  30. u8 Cent; // 世纪:0代表本世纪20, 1代表下个世纪,年99->00时交替
  31. u8 p[NUMBER_TD]; // 用于存储时间字节数组
  32. // 循环读取Read: 秒、分、时、星期、日、月、年、世纪
  33. I2C_READ(PCF8563_ADDR, PCF8563_REG_TD, p, NUMBER_TD);
  34. // for(i = 0; i < NUMBER; i++){
  35. // printf("%d-> %d \n", (int)i, (int)p[i]);
  36. // }
  37. // printf("----------------\n");
  38. // 秒:VL 1 1 1 - 0 0 0 0 转成十进制
  39. c->second = ((p[0] >> 4) & 0x07) * 10 + (p[0] & 0x0F);
  40. // 分: X 1 1 1 - 0 0 0 0 转成十进制
  41. c->minute = ((p[1] >> 4) & 0x07) * 10 + (p[1] & 0x0F);
  42. // 时: X X 1 1 - 0 0 0 0 转成十进制
  43. c->hour = ((p[2] >> 4) & 0x03) * 10 + (p[2] & 0x0F);
  44. // 天: X X 1 1 - 0 0 0 0 转成十进制
  45. c->day = ((p[3] >> 4) & 0x03) * 10 + (p[3] & 0x0F);
  46. // 周: X X X X - X 0 0 0 转成十进制
  47. c->week = p[4] & 0x07;
  48. // 世纪
  49. // 月: C X X 1 - 0 0 0 0
  50. c->month = ((p[5] >> 4) & 0x01) * 10 + (p[5] & 0x0F);
  51. Cent = p[5] >> 7; // 0->20xx年 1->21xx年
  52. // 年: 1 1 1 1 - 0 0 0 0 1969, 2023
  53. c->year = ((p[6] >> 4) & 0x0F) * 10 + (p[6] & 0x0F);
  54. c->year += Cent == 0 ? 2000 : 2100;
  55. }
  56. void PCF8563_set_alarm(Alarm_t alarm){
  57. // 想将某个类型关闭掉,传一个负数
  58. // 或者多传1个字段,低4位,根据01决定是否启动对应类型 0000 0011
  59. u8 a[4];
  60. // a = 0; // 分钟
  61. // I2C_WRITE(PCF8563_ADDR, 0x09, &a, 1);
  62. //
  63. // a = 0; // 小时
  64. // I2C_WRITE(PCF8563_ADDR, 0x0A, &a, 1);
  65. // 分 M 1 1 1 - 0 0 0 0 enable->0, diabled->0x80
  66. a[0] = ((alarm.minute / 10) << 4) + (alarm.minute % 10) + 0;
  67. // 时 H x 1 1 - 0 0 0 0 enable->0, diabled->0x80
  68. a[1] = ((alarm.hour / 10) << 4) + (alarm.hour % 10) + 0;
  69. // 天 D x 1 1 - 0 0 0 0 enable->0, diabled->0x80
  70. a[2] = ((alarm.day / 10) << 4) + (alarm.day % 10) + 0;
  71. // 周 W x x x - x 0 0 0 enable->0, diabled->0x80
  72. a[3] = alarm.weekday + 0;
  73. I2C_WRITE(PCF8563_ADDR, 0x09, a, 4);
  74. }
  75. void PCF8563_enable_alarm(){
  76. u8 cs2 = 0; // 控制状态寄存器2
  77. // 配置 cs2 寄存器以启用Alarm -------------------------------------------------
  78. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  79. // printf("cs2: %d \n", (int)cs2);
  80. // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
  81. cs2 &= ~0x08;
  82. // 开启Alarm中断, AIE为1,启用Alarm闹钟
  83. cs2 |= 0x02;
  84. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  85. }
  86. void PCF8563_disable_alarm(){
  87. u8 cs2 = 0; // 控制状态寄存器2
  88. // 配置 cs2 寄存器以启用Alarm -------------------------------------------------
  89. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  90. // printf("cs2: %d \n", (int)cs2);
  91. // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
  92. cs2 &= ~0x08;
  93. // 开启Alarm中断, AIE为0,禁用Alarm闹钟
  94. cs2 &= ~0x02;
  95. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  96. }
  97. void PCF8563_alarm_clear_flag(){
  98. u8 cs2 = 0; // 控制状态寄存器2
  99. // 01H寄存器中AF位,会在Alarm触发时,被置为1.
  100. // 必须手动置为0,下一个Alarm才能触发。
  101. // 配置 cs2 寄存器
  102. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  103. // printf("cs2: %d \n", (int)cs2);
  104. // 清除Alarm标记,AF设置为0,下一次闹钟到点时,才能触发
  105. cs2 &= ~0x08;
  106. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  107. }
  108. void PCF8563_set_timer(TimerFreq freq ,u8 countdown){
  109. u8 p = 0;
  110. // 设置Timer运行频率、启用timer source clock
  111. p = 0x80 + freq; // 64Hz
  112. I2C_WRITE(PCF8563_ADDR, 0x0E, &p, 1);
  113. // 设置Timer计数值(每个周期)
  114. p = countdown;
  115. I2C_WRITE(PCF8563_ADDR, 0x0F, &p, 1);
  116. }
  117. void PCF8563_enable_timer(){
  118. u8 cs2 = 0; // 控制状态寄存器2
  119. // 通过cs2启用Timer
  120. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  121. // 开启Timer中断: TIE
  122. cs2 |= 0x01;
  123. // 清除Timer flag; TF
  124. cs2 &= ~0x04;
  125. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  126. }
  127. void PCF8563_disable_timer(){
  128. u8 cs2 = 0; // 控制状态寄存器2
  129. // 通过cs2启用Timer
  130. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  131. // 开启Timer中断: TIE
  132. cs2 &= ~0x01;
  133. // 清除Timer flag; TF
  134. cs2 &= ~0x04;
  135. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  136. }
  137. void PCF8563_timer_clear_flag(){
  138. u8 cs2 = 0; // 控制状态寄存器2
  139. // 通过cs2启用Timer
  140. I2C_READ(PCF8563_ADDR, 0x01, &cs2, 1);
  141. // 清除Timer flag; TF
  142. cs2 &= ~0x04;
  143. I2C_WRITE(PCF8563_ADDR, 0x01, &cs2, 1);
  144. }
  145. // 当中断触发时,此函数会执行(Alarm、Timer)
  146. void ext_int3_call() {
  147. u8 cs2;
  148. I2C_READ(PCF8563_ADDR, PCF8563_REG_CS2, &cs2, 1);
  149. // printf("cs2: %d \n", (int) cs2);
  150. #ifdef PCF8563_TIMER_ENABLE
  151. // Alarm Flag && AIE
  152. if((cs2 & 0x08) && (cs2 & 0x02)){
  153. // 清除Alarm标记,避免下次无法触发
  154. PCF8563_alarm_clear_flag();
  155. // 事件触发
  156. PCF8563_on_alarm();
  157. }
  158. #endif
  159. #ifdef PCF8563_TIMER_ENABLE
  160. // Timer Flag && TIE
  161. if((cs2 & 0x04) && (cs2 & 0x01)){
  162. // 清除Timer标记,避免下次无法触发
  163. PCF8563_timer_clear_flag();
  164. // 事件触发
  165. PCF8563_on_timer();
  166. }
  167. #endif
  168. }

只需要实现 i2c_write和i2c_read,驱动就可以正常运行。

  1. /* USER CODE BEGIN WHILE */
  2. // pcf8563初始化
  3. PCF8563_init();
  4. Clock_t c;
  5. c.year = 2023;
  6. c.month = 3;
  7. c.day = 10;
  8. c.week = 2;
  9. c.hour = 23;
  10. c.minute = 59;
  11. c.second = 55;
  12. PCF8563_set_clock(c);
  13. while (1)
  14. {
  15. PCF8563_get_clock(&c);
  16. printf("%d-%d-%d %d %d:%d:%d\r\n", c.year, c.month, c.day, c.week, c.hour, c.minute,c.second);
  17. HAL_Delay(1000);
  18. /* USER CODE END WHILE */
  19. /* USER CODE BEGIN 3 */
  20. }
  21. /* USER CODE END 3 */

I2C读写实现

添加头定义

  1. /* USER CODE BEGIN Prototypes */
  2. void I2C1_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);
  3. void I2C1_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len);
  4. /* USER CODE END Prototypes */

添加c实现

  1. /* USER CODE BEGIN 1 */
  2. #include "string.h"
  3. void I2C1_write(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {
  4. uint8_t buff[len + 1];
  5. buff[0] = reg;
  6. memcpy(&buff[1], data, len);
  7. HAL_I2C_Master_Transmit(&hi2c1, addr << 1, buff, len + 1, HAL_MAX_DELAY);
  8. }
  9. void I2C1_read(uint8_t addr, uint8_t reg, uint8_t* data, uint32_t len) {
  10. HAL_I2C_Master_Transmit(&hi2c1, addr << 1, &reg, 1, HAL_MAX_DELAY);
  11. HAL_I2C_Master_Receive(&hi2c1, (addr << 1) | 0x01, data, len, HAL_MAX_DELAY);
  12. }
  13. /* USER CODE END 1 */
  • HAL库的设备地址,要的是具体的读写地址。
  • HAL库中的写操作中的数据,包含了寄存器地址。
  • HAL库中读操作,需要先写再读。

    练习题

  1. 使用HAL库读写PCF8563芯片