当前位置: 首页 > news >正文

I2C软实现基于GD32F407VE的天空星的配置

I2C软实现基于GD32F407VE的天空星的配置

一、I2C协议基础

1.1 什么是I2C?

I2C(Inter-Integrated Circuit)是一种通用的串行通信总线协议,仅使用两根线(时钟线SCL数据线SDA)就能实现多个设备间的通信。在GD32F407VE天空星开发板上,我们可以通过软件模拟的方式实现I2C通信。

1.2 I2C通信模型

想象一个课堂场景

  • 主设备 = 老师
  • 从设备 = 学生
  • SCL = 老师的手拍节拍
  • SDA = 老师和学生对话的通道

通信流程示例:

  1. 老师敲黑板:“开始上课!”(起始信号)
  2. 老师点名:“张三,回答这个问题”(设备地址)
  3. 张三举手:“到!”(ACK响应)
  4. 老师和张三对话(数据传输)
  5. 老师敲桌子:“下课!”(停止信号)

二、硬件连接

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时序:

  1. 连接方式

    • CH0接SCL(PB6)
    • CH1接SDA(PB7)
    • GND接开发板GND
  2. 软件设置

    • 采样率:4MHz以上
    • 触发方式:SCL下降沿
    • 解码协议:I2C

7.2 常见问题解决

  1. 无应答信号

    • 检查设备地址是否正确
    • 确认上拉电阻已连接
    • 检查设备电源
  2. 时序错误

    • 调整延时参数
    • 确认时钟频率设置
  3. 数据错误

    • 检查BCD码转换
    • 验证寄存器地址
http://www.dtcms.com/a/487551.html

相关文章:

  • 温州如何进行网站推广网站后台如何用代码上传视频
  • 深入浅出FastAPI:现代Python Web开发的利器
  • 月球矩阵日志:Swift 6.2 主线程隔离抉择(下)
  • 石家庄网站建设推广报价电商运营包括哪些方面
  • 相亲网站排名前十名wordpress支付通道
  • 网站被黑了定制app开发
  • Asp.net WebAPI 中使用一般处理程序处理跨域问题
  • Python的插件机制
  • 对接印度股票指数API完整指南:从入门到实战
  • 差分操作正确性证明
  • 广西建设厅网证件查询郑州seo外包平台
  • 做个支付网站多少钱南通注册公司
  • 免费网站推广优化建设网站 法律责任
  • SpringAI 本地调用 Ollama
  • python 视频播放网站开发wordpress 预订插件
  • 公司网站维护重庆平台
  • 第5篇:自定义序列化器与反序列化器:突破默认逻辑
  • NSSCTF 理想国
  • 极速迁移:GitLab项目无缝转移实战手册
  • 视频解析转换耗时—OpenCV优化摸索路
  • 自己电脑做网站谁有网站推荐一下好
  • 组织架构树形选择组件使用说明(Vue3 + UniApp)
  • 响应式网站开发步骤去哪里学习建设网站
  • 网站建设与管理用什么软件有哪些内容南京外包公司
  • 电子商务网站规划的原则江苏省城乡建设网站
  • 项目学习总结:CAN总线、摄像头、STM32概述
  • Linux中在字符串中查找指定字符的第一次出现位置的汇编实现
  • 官方网站撰写策划书分布式移动网站开发技术
  • OpenPI源码分析
  • 智能化早诊:深度学习如何改变阿尔茨海默病的诊断未来