I2C软实现基于GD32F407VE的天空星的配置
I2C软实现基于GD32F407VE的天空星的配置
一、I2C协议基础
1.1 什么是I2C?
I2C(Inter-Integrated Circuit)是一种通用的串行通信总线协议,仅使用两根线(时钟线SCL和数据线SDA)就能实现多个设备间的通信。在GD32F407VE天空星开发板上,我们可以通过软件模拟的方式实现I2C通信。
1.2 I2C通信模型
想象一个课堂场景:
- 主设备 = 老师
- 从设备 = 学生
- SCL = 老师的手拍节拍
- SDA = 老师和学生对话的通道
通信流程示例:
- 老师敲黑板:“开始上课!”(起始信号)
- 老师点名:“张三,回答这个问题”(设备地址)
- 张三举手:“到!”(ACK响应)
- 老师和张三对话(数据传输)
- 老师敲桌子:“下课!”(停止信号)
二、硬件连接
2.1 天空星开发板引脚定义
#ifndef __I2C0_SOFT_H__
#define __I2C0_SOFT_H__
#include "gd32f4xx.h"
#include "systick.h"
// PB6 - SCL
#define I2C0S_SCL_RCU RCU_GPIOB
#define I2C0S_SCL_PORT GPIOB
#define I2C0S_SCL_PIN GPIO_PIN_6
// PB7 - SDA
#define I2C0S_SDA_RCU RCU_GPIOB
#define I2C0S_SDA_PORT GPIOB
#define I2C0S_SDA_PIN GPIO_PIN_7#define FAST 1 // 0 标准 100k 1 快速 400kvoid I2C0_soft_init();/* 写数据到I2C设备指定寄存器 */
uint8_t I2C0_soft_write(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len);/* 从I2C设备指定寄存器读取数据 */
uint8_t I2C0_soft_read(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len);#endif
2.2 原理图连接
天空星开发板 PCF8563模块
PB6(SCL) --- SCL
PB7(SDA) --- SDA
3.3V --- VCC
GND --- GND
注意:SCL和SDA线需要接4.7kΩ上拉电阻到3.3V!
三、软件实现详解
3.1 引脚初始化
void I2C0_soft_init() {// SCL SDA 开漏输出 电平使用默认的GPIO_output(I2C0S_SCL_RCU, I2C0S_SCL_PORT, I2C0S_SCL_PIN, GPIO_OTYPE_OD, 2); // 2 默认电平,配置完不做电平处理GPIO_output(I2C0S_SDA_RCU, I2C0S_SDA_PORT, I2C0S_SDA_PIN, GPIO_OTYPE_OD, 2); // 2 默认电平,配置完不做电平处理
}
为什么用开漏输出?
- 支持"线与"功能,多个设备可以共享总线
- 配合上拉电阻实现高电平
- 避免总线冲突
3.2 时序控制宏
//设置引脚的高低电平
#define I2C0S_SDA(bit) gpio_bit_write(I2C0S_SDA_PORT, I2C0S_SDA_PIN, (bit) ? SET : RESET)
#define I2C0S_SCL(bit) gpio_bit_write(I2C0S_SCL_PORT, I2C0S_SCL_PIN, (bit) ? SET : RESET)// 标准模式 100Kbits/s, 快速模式 400Kbits/s
// 100Kbits/s -> 100 000 bit / 1 000 000us -> 1bit/10us 一个高一个低才是1bit
// 400Kbits/s -> 400 000 bit / 1 000 000us -> 4bit/10us -> 每1bit大概需要2.5us
#if !FAST#define I2C0S_DELAY() delay_1us(5) // 标准模式
#else#define I2C0S_DELAY() delay_1us(1) // 快速模式
#endif
#define I2C0S_SDA_STATE() gpio_input_bit_get(I2C0S_SDA_PORT, I2C0S_SDA_PIN) // 读取SDA电平
3.3 基本信号实现
起始信号
// 开始信号
static void start() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}
停止信号
// 停止信号
static void stop() {I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时
}
发送字节
// 设备地址,寄存器地址,数据
static void send(uint8_t dat) {for(uint8_t i = 0; i < 8; i++) {// 判断最高位为1还是0, 如果为1, 条件就为真if (dat & 0x80) I2C0S_SDA(1);else I2C0S_SDA(0);dat = dat << 1; // 左移移位I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时 }
}
接收字节
// 接收1个字节
static uint8_t recv() {uint8_t dat = 0;I2C0S_SDA(1); // SDA 拉高for(uint8_t i = 0; i < 8; i++) {I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时dat <<= 1; // 左移一位,留一个位置放数据,不处理,这个位置(第0位)为0if (SET == I2C0S_SDA_STATE()) dat |= 1; // 是|=1, 不是=1I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时}return dat;
}
应答控制
// 发送ack响应
static void send_ack() {I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}
// 发送nack响应
static void send_nack() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}// 等待响应,返回值 > 0, 出现异常
static uint8_t wait_ack() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高, 控制权交给从设备I2C0S_DELAY();// 延时// ============ 控制权交给从设备,MCU读数据线// if (RESET == gpio_input_bit_get(I2C0S_SDA_PORT, I2C0S_SDA_PIN)) { // 低电平应答成功if (RESET == I2C0S_SDA_STATE()) {I2C0S_SCL(0);// SCL 拉低, 控制权交给主设备I2C0S_DELAY();// 延时return 0;} else {// 高电平应答失败stop();return 1;}
}
四、完整的读写函数
4.1 写操作函数
/*********************************************************** @brief 写数据到I2C设备指定寄存器* @param addr 设备地址 (写地址的前7位) 0x51写地址 (addr << 1) | 0 0xA2* @param reg 寄存器地址* @param dat 字节数组* @param len 数据长度* @return 0成功,1 设备不存在, 2 寄存器不存在, 3 数据无响应**********************************************************/
uint8_t I2C0_soft_write(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len) {// 开始start();// 发: 设备地址send(addr << 1); // 往左移,传过来是往右移的// 等待响应if (wait_ack() > 0) return 1;// 发: 寄存器地址send(reg);// 等待响应if (wait_ack() > 0) return 2;// 循环发送数据for(uint32_t i = 0; i < len; i++) {send(dat[i]);if (wait_ack() > 0) return 3;}// 停止stop();return 0;
}
4.2 读操作函数
/*********************************************************** @brief 从I2C设备指定寄存器读取数据* @param addr 设备地址 (写地址的前7位) 0x51写地址 (addr << 1) | 0 0xA2读地址 (addr << 1) | 1 0xA3* @param reg 寄存器地址* @param dat 字节数组* @param len 数据长度* @return 0成功,1 写设备不存在, 2 寄存器不存在, 3 读设备不存在* @return**********************************************************/
uint8_t I2C0_soft_read(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len) {// 开始start();// 发: 设备地址, 写send(addr << 1); // 往左移,传过来是往右移的// 等待响应if (wait_ack() > 0) return 1;// 发: 寄存器地址send(reg);// 等待响应if (wait_ack() > 0) return 2;// 开始start();// 发: 设备地址, 读send((addr << 1) | 1); // 地址不一样// 等待响应if (wait_ack() > 0) return 3;// 循环接收数据for(uint32_t i = 0; i < len; i++) {dat[i] = recv(); // 接收if (i == len - 1) send_nack(); // 最后1个数据,发空响应else send_ack(); // 非最后1个数据,发响应}// 停止stop();return 0;
}
五、PCF8563时钟芯片应用
5.1 PCF8563设备信息
// 设备地址
#define PCF8563_DEV_ADDR (0xa2 >> 1)
// 存储地址(寄存器地址): 时间(秒)存储地址
#define PCF8563_REG_SECOND 0x02typedef struct {u16 year; u8 month;u8 day;u8 weekday;u8 hour;u8 minute;u8 second;
} Clock_t;
5.2 BCD码转换宏
// 十进制转BCD码
#define WRITE_BCD(val) ((val / 10) << 4) + (val%10) // 没有分号,宏定义
// BCD码转十进制
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0f)
5.3 PCF8563驱动函数
// 设置时间
void PCF8563_set_clock(Clock_t temp) {u8 p[7] = {0}; // 写完整 年月日 星期几 时分秒// 秒的寄存器地址为: 0x02// 秒: 第0~3位记录个位,第4~6位记录十位// 十位 个位p[0] = WRITE_BCD(temp.second);// 分: 第0~3位,保存个数,第4到6位,保存十位p[1] = WRITE_BCD(temp.minute);// 时:第0~3位,保存个数,第4到5位,保存十位p[2] = WRITE_BCD(temp.hour);// 日:第0~3位,保存个数,第4到5位,保存十位p[3] = WRITE_BCD(temp.day);// 周:第0~2位,保存个数p[4] = temp.weekday;// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xxp[5] = WRITE_BCD(temp.month);// 月的第7位if (temp.year >= 2100) { // 第7位置1p[5] |= (1 << 7);} // 第7位置0,不处理就是0// 年:第0~3位,保存个数,第4到7位,保存十位// 2025 ===> 25 p[6] = WRITE_BCD(temp.year%100);I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}// 获取时间
void PCF8563_get_clock(Clock_t *temp) {u8 p[7] = {0}; u8 flag;// 读时间I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);// 10进制 用 16进制表示 低4位放个位 高4位放10位// 秒: 第0~3位记录个位,第4~6位记录十位temp->second = READ_BCD(p[0]);// 分: 第0~3位,保存个数,第4到6位,保存十位temp->minute = READ_BCD(p[1]);// 时:第0~3位,保存个数,第4到5位,保存十位temp->hour = READ_BCD(p[2]);// 日:第0~3位,保存个数,第4到5位,保存十位temp->day = READ_BCD(p[3]);// 周:第0~2位,保存个数temp->weekday = p[4]; // 如果是星期日,是0// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xx// 处理第7位// 取出第7位flag = p[5] >> 7;// 第7位置0, 月的第7位,是年的标志位,不是月的有效数据p[5] &= ~(1 << 7);temp->month = READ_BCD(p[5]);// 年:第0~3位,保存个数,第4到7位,保存十位temp->year = READ_BCD(p[6]);if (flag == 1) temp->year += 2100;else temp->year += 2000;
}
六、完整应用示例
main主函数:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "USART0.h"
#include "bsp_pcf8563.h"void USART0_on_recv(uint8_t* data, uint32_t len) {printf("recv[%d]:%s\n", len, data);
}int main(void) {// 配置全局优先级分组规则nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);// 初始化系统嘀嗒定时器systick_config();// 初始化USARTUSART0_init();PCF8563_init(); // PCF8563 初始化Clock_t temp = {2025, 8, 11, 1, 23, 59, 54}; // 结构体变量PCF8563_set_clock(temp); // 设置时间while(1) {PCF8563_get_clock(&temp); // 获取时间printf("%02d-%02d-%02d\n", (int)temp.year, (int)temp.month, (int)temp.day);printf("weekday: %02d\n", (int)temp.weekday);printf("%02d:%02d:%02d\n", (int)temp.hour, (int)temp.minute, (int)temp.second);delay_1ms(1000);}return 0;
}
i2c软实现函数:
I2C0_soft.h
#ifndef __I2C0_SOFT_H__
#define __I2C0_SOFT_H__#include "gd32f4xx.h"
#include "systick.h"// PB6 - SCL
#define I2C0S_SCL_RCU RCU_GPIOB
#define I2C0S_SCL_PORT GPIOB
#define I2C0S_SCL_PIN GPIO_PIN_6
// PB7 - SDA
#define I2C0S_SDA_RCU RCU_GPIOB
#define I2C0S_SDA_PORT GPIOB
#define I2C0S_SDA_PIN GPIO_PIN_7#define FAST 1 // 0 标准 1 快速void I2C0_soft_init();/* 写数据到I2C设备指定寄存器 */
uint8_t I2C0_soft_write(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len);/* 从I2C设备指定寄存器读取数据 */
uint8_t I2C0_soft_read(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len);#endif
I2C0_soft.c
#include "I2C0_soft.h"
#include "gpio_cfg.h"
#define I2C0S_SDA(bit) gpio_bit_write(I2C0S_SDA_PORT, I2C0S_SDA_PIN, (bit) ? SET : RESET)
#define I2C0S_SCL(bit) gpio_bit_write(I2C0S_SCL_PORT, I2C0S_SCL_PIN, (bit) ? SET : RESET)// 标准模式 100Kbits/s, 快速模式 400Kbits/s
// 100Kbits/s -> 100 000 bit / 1 000 000us -> 1bit/10us 一个高一个低才是1bit
// 400Kbits/s -> 400 000 bit / 1 000 000us -> 4bit/10us -> 每1bit大概需要2.5us
#if !FAST#define I2C0S_DELAY() delay_1us(5) // 标准模式
#else#define I2C0S_DELAY() delay_1us(1) // 快速模式
#endif#define I2C0S_SDA_STATE() gpio_input_bit_get(I2C0S_SDA_PORT, I2C0S_SDA_PIN) // 读取SDA电平// 开始信号
static void start() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}// 设备地址,寄存器地址,数据
static void send(uint8_t dat) {for(uint8_t i = 0; i < 8; i++) {// 判断最高位为1还是0, 如果为1, 条件就为真if (dat & 0x80) I2C0S_SDA(1);else I2C0S_SDA(0);dat = dat << 1; // 左移移位I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时 }
}// 停止信号
static void stop() {I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时
}// 接收1个字节
static uint8_t recv() {uint8_t dat = 0;I2C0S_SDA(1); // SDA 拉高for(uint8_t i = 0; i < 8; i++) {I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时dat <<= 1; // 左移一位,留一个位置放数据,不处理,这个位置(第0位)为0if (SET == I2C0S_SDA_STATE()) dat |= 1; // 是|=1, 不是=1I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时}return dat;
}// 发送ack响应
static void send_ack() {I2C0S_SDA(0); // SDA 拉低I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}
// 发送nack响应
static void send_nack() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高I2C0S_DELAY();// 延时I2C0S_SCL(0);// SCL 拉低I2C0S_DELAY();// 延时
}// 等待响应,返回值 > 0, 出现异常
static uint8_t wait_ack() {I2C0S_SDA(1); // SDA 拉高I2C0S_DELAY();// 延时I2C0S_SCL(1);// SCL 拉高, 控制权交给从设备I2C0S_DELAY();// 延时// ============ 控制权交给从设备,MCU读数据线// if (RESET == gpio_input_bit_get(I2C0S_SDA_PORT, I2C0S_SDA_PIN)) { // 低电平应答成功if (RESET == I2C0S_SDA_STATE()) {I2C0S_SCL(0);// SCL 拉低, 控制权交给主设备I2C0S_DELAY();// 延时return 0;} else {// 高电平应答失败stop();return 1;}
}void I2C0_soft_init() {// SCL SDA 开漏输出 电平使用默认的GPIO_output(I2C0S_SCL_RCU, I2C0S_SCL_PORT, I2C0S_SCL_PIN, GPIO_OTYPE_OD, 2); // 2 默认电平,配置完不做电平处理GPIO_output(I2C0S_SDA_RCU, I2C0S_SDA_PORT, I2C0S_SDA_PIN, GPIO_OTYPE_OD, 2); // 2 默认电平,配置完不做电平处理
}/*********************************************************** @brief 写数据到I2C设备指定寄存器* @param addr 设备地址 (写地址的前7位) 0x51写地址 (addr << 1) | 0 0xA2* @param reg 寄存器地址* @param dat 字节数组* @param len 数据长度* @return 0成功,1 设备不存在, 2 寄存器不存在, 3 数据无响应**********************************************************/
uint8_t I2C0_soft_write(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len) {// 开始start();// 发: 设备地址send(addr << 1); // 往左移,传过来是往右移的// 等待响应if (wait_ack() > 0) return 1;// 发: 寄存器地址send(reg);// 等待响应if (wait_ack() > 0) return 2;// 循环发送数据for(uint32_t i = 0; i < len; i++) {send(dat[i]);if (wait_ack() > 0) return 3;}// 停止stop();return 0;
}/*********************************************************** @brief 从I2C设备指定寄存器读取数据* @param addr 设备地址 (写地址的前7位) 0x51写地址 (addr << 1) | 0 0xA2读地址 (addr << 1) | 1 0xA3* @param reg 寄存器地址* @param dat 字节数组* @param len 数据长度* @return 0成功,1 写设备不存在, 2 寄存器不存在, 3 读设备不存在* @return**********************************************************/
uint8_t I2C0_soft_read(uint8_t addr, uint8_t reg, uint8_t* dat, uint32_t len) {// 开始start();// 发: 设备地址, 写send(addr << 1); // 往左移,传过来是往右移的// 等待响应if (wait_ack() > 0) return 1;// 发: 寄存器地址send(reg);// 等待响应if (wait_ack() > 0) return 2;// 开始start();// 发: 设备地址, 读send((addr << 1) | 1); // 地址不一样// 等待响应if (wait_ack() > 0) return 3;// 循环接收数据for(uint32_t i = 0; i < len; i++) {dat[i] = recv(); // 接收if (i == len - 1) send_nack(); // 最后1个数据,发空响应else send_ack(); // 非最后1个数据,发响应}// 停止stop();return 0;
}
pcf8563的库函数实现:
bsp_pcf8563.h :
#ifndef __BSP_PCF8563_H__
#define __BSP_PCF8563_H__#include "gd32f4xx.h"
#include "I2C.h"#define I2C_WriteNbyte(a,b,c,d) I2C0_write(a,b,c,d)
#define I2C_ReadNbyte(a,b,c,d) I2C0_read(a,b,c,d)#ifndef u8
#define u8 uint8_t
#endif#ifndef u16
#define u16 uint16_t
#endif#ifndef u32
#define u32 uint32_t
#endif/*
1. 如果 PCF8563_USE_ALARM 开关为1,闹钟中断开关
void PCF8563_on_alarm() {} 必须在合适位置定义
2. 如果 PCF8563_USE_TIMER 开关为1,定时器中断开关
void PCF8563_on_timer() {} 必须在合适位置定义
*/#define PCF8563_USE_ALARM 0
#define PCF8563_USE_TIMER 0// 函数声明
void PCF8563_on_alarm();
void PCF8563_on_timer();#define WRITE_BCD(val) ((val / 10) << 4) + (val%10) // 没有分号,宏定义
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0f)// 设备地址
#define PCF8563_DEV_ADDR (0xa2 >> 1)
// 存储地址(寄存器地址): 时间(秒)存储地址
#define PCF8563_REG_SECOND 0x02typedef struct {u16 year; u8 month;u8 day;u8 weekday;u8 hour;u8 minute;u8 second;
} Clock_t;// PCF8563初始化
void PCF8563_init();// 设置时间
void PCF8563_set_clock(Clock_t temp);// 获取时间
void PCF8563_get_clock(Clock_t *temp);//=============================闹钟
typedef struct {// 设置分\时\天\周,如果为-1,禁用此项char minute ;char hour ;char day ;char weekday;
} Alarm_t;// 设置闹钟
void PCF8563_set_alarm(Alarm_t alarm);
// 启用闹钟
void PCF8563_enable_alarm();
// 禁用闹钟Alarm
void PCF8563_disable_alarm();
// 清理闹钟标记
void PCF8563_alarm_clear_flag();//=============================定时器
// 国产芯片的HZ1有问题,不要使用,建议使用HZ64
typedef enum { HZ4096 = 0, HZ64 = 1, HZ1 = 2, HZ1_60 = 3} TimerFreq;// 启动定时器
void PCF8563_enable_timer();
// 禁用定时器
void PCF8563_disable_timer();
// 清除定时器标志位
void PCF8563_clear_timer();
// 设置定时器,参数1:时钟频率 参数2:倒计时计算值,时间为:参数2/参数1
void PCF8563_set_timer(TimerFreq freq, u8 countdown);#endif
bsp_pcf8563.c的库函数实现
#include "bsp_pcf8563.h"// PCF8563初始化
void PCF8563_init() {I2C0_init();
}// 设置时间
void PCF8563_set_clock(Clock_t temp) {u8 p[7] = {0}; // 写完整 年月日 星期几 时分秒// 秒的寄存器地址为: 0x02// 秒: 第0~3位记录个位,第4~6位记录十位// 十位 个位p[0] = WRITE_BCD(temp.second);// 分: 第0~3位,保存个数,第4到6位,保存十位p[1] = WRITE_BCD(temp.minute);// 时:第0~3位,保存个数,第4到5位,保存十位p[2] = WRITE_BCD(temp.hour);// 日:第0~3位,保存个数,第4到5位,保存十位p[3] = WRITE_BCD(temp.day);// 周:第0~2位,保存个数p[4] = temp.weekday;// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xxp[5] = WRITE_BCD(temp.month);// 月的第7位if (temp.year >= 2100) { // 第7位置1p[5] |= (1 << 7);} // 第7位置0,不处理就是0// 年:第0~3位,保存个数,第4到7位,保存十位// 2025 ===> 25 p[6] = WRITE_BCD(temp.year%100);I2C_WriteNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);
}// 获取时间
void PCF8563_get_clock(Clock_t *temp) {u8 p[7] = {0}; u8 flag;// 读时间I2C_ReadNbyte(PCF8563_DEV_ADDR, PCF8563_REG_SECOND, p, 7);// 10进制 用 16进制表示 低4位放个位 高4位放10位// 秒: 第0~3位记录个位,第4~6位记录十位temp->second = READ_BCD(p[0]);// 分: 第0~3位,保存个数,第4到6位,保存十位temp->minute = READ_BCD(p[1]);// 时:第0~3位,保存个数,第4到5位,保存十位temp->hour = READ_BCD(p[2]);// 日:第0~3位,保存个数,第4到5位,保存十位temp->day = READ_BCD(p[3]);// 周:第0~2位,保存个数temp->weekday = p[4]; // 如果是星期日,是0// 月_世纪: 第0~3位记录个位,第4位记录十位,第7位为0,世纪数为20xx,为1,世纪数为21xx// 处理第7位// 取出第7位flag = p[5] >> 7;// 第7位置0, 月的第7位,是年的标志位,不是月的有效数据p[5] &= ~(1 << 7);temp->month = READ_BCD(p[5]);// 年:第0~3位,保存个数,第4到7位,保存十位temp->year = READ_BCD(p[6]);if (flag == 1) temp->year += 2100;else temp->year += 2000;
}// 设置闹钟
void PCF8563_set_alarm(Alarm_t alarm) {u8 p[4] = {0};//===================2.1 闹钟时间设置 寄存器地址 0x09// 分: 第0~3位,记录个数, 第4~6位记录十位, 第7位:置0启动, 置1禁用if (alarm.minute != -1) p[0] = WRITE_BCD(alarm.minute);else p[0] = 0x80;// 时: 第0~3位,记录个数, 第4~5位记录十位, 第7位:置0启动, 置1禁用p[1] = alarm.hour != -1 ? WRITE_BCD(alarm.hour) : 0x80 ;// 日: 第0~3位,记录个数, 第4~5位记录十位, 第7位:置0启动, 置1禁用p[2] = alarm.day != -1 ? WRITE_BCD(alarm.day) : 0x80 ;// 周: 第0~2位,记录个数, 第7位:置0启动, 置1禁用p[3] = alarm.weekday != -1 ? alarm.weekday : 0x80 ;I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x09, p, 4);
}// 启用闹钟
void PCF8563_enable_alarm() {u8 cfg;//===================2.2 闹钟开启 寄存器地址 0x01//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位 第3位:置0清除标志位,置1维持不变cfg &= ~(1 << 3);//c) 在原来配置基础上,启动闹钟,第1位:置0禁用,置1启动cfg |= (1 << 1); //d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 禁用闹钟Alarm
void PCF8563_disable_alarm() {u8 cfg;//===================2.2 闹钟 寄存器地址 0x01//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位 第3位:置0清除标志位,置1维持不变cfg &= ~(1 << 3);//c) 在原来配置基础上,禁用闹钟,第1位:置0禁用,置1启动cfg &= ~(1 << 1); //d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 清理闹钟标记
void PCF8563_alarm_clear_flag() {u8 cfg;// 清除闹钟的标志位,才能重复触发闹钟//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位 第3位:置0清除标志位,置1维持不变cfg &= ~(1 << 3); //c) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 启动定时器
void PCF8563_enable_timer() {u8 cfg;//============2 定时器开启 寄存器地址 0x01//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位,第2位:置0清除标志位,置1维持不变 cfg &= ~(1 << 2);//c) 在原来配置基础上,启动定时器,第0位:置0禁用,置1启用 cfg |= (1 << 0);//d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 禁用定时器
void PCF8563_disable_timer() {u8 cfg;//============2 定时器开启 寄存器地址 0x01//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位,第2位:置0清除标志位,置1维持不变 cfg &= ~(1 << 2);//c) 在原来配置基础上,启动定时器,第0位:置0禁用,置1启用 cfg &= ~(1 << 0);//d) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 清除定时器标志位
void PCF8563_clear_timer() {u8 cfg;// 清除标志位,才能重复触发//a) 读原来的配置(不要乱改配置,只改自己的位,其它维持不变)I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);//b) 在原来配置的基础上,清除标志位,第2位:置0清除标志位,置1维持不变 cfg &= ~(1 << 2);//c) 重新写入配置I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);
}// 设置定时器,参数1:时钟频率 参数2:倒计时计算值,时间为:参数2/参数1
void PCF8563_set_timer(TimerFreq freq, u8 countdown) {u8 p[2] = {0}; //============1 定时器设置 寄存器地址 0x0e//a) 时钟频率 不建议用 1hz,有bug// 第7位为0,定时器禁用,第七位为1,定时器启用p[0] = freq + 0x80; // 64hz//b) 计数值(0~255) ===》时间为: 计数值/时钟频率 p[1] = countdown; // 时间间隔:countdown / freq I2C_WriteNbyte(PCF8563_DEV_ADDR, 0x0e, p, 2);}void int3_callback() { // 外部中断3的回调u8 cfg;// 读取原来的配置I2C_ReadNbyte(PCF8563_DEV_ADDR, 0x01, &cfg, 1);// 如果是闹钟,第1位和第3位是1 if ((cfg & 0x02) && (cfg & 0x08)) {// 清理闹钟标记PCF8563_alarm_clear_flag();// 调用#if PCF8563_USE_ALARMPCF8563_on_alarm();#endif }// 如果是定时器,第0位和第2位是1 if ((cfg & 0x01) && (cfg & 0x04)) {// 清除定时器标志位PCF8563_clear_timer();// 调用#if PCF8563_USE_TIMERPCF8563_on_timer();#endif }
}
七、调试技巧
7.1 逻辑分析仪使用
使用逻辑分析仪可以直观查看I2C时序:
-
连接方式:
- CH0接SCL(PB6)
- CH1接SDA(PB7)
- GND接开发板GND
-
软件设置:
- 采样率:4MHz以上
- 触发方式:SCL下降沿
- 解码协议:I2C
7.2 常见问题解决
-
无应答信号:
- 检查设备地址是否正确
- 确认上拉电阻已连接
- 检查设备电源
-
时序错误:
- 调整延时参数
- 确认时钟频率设置
-
数据错误:
- 检查BCD码转换
- 验证寄存器地址