STM32模拟I2C驱动SD3078的程序
STM32模拟I2C驱动SD3078的程序。SD3078是一款内置晶振和温度补偿的高精度实时时钟芯片,采用I2C接口通信。
SD3078 基础要点
| 特性 | 描述 |
|---|---|
| I2C地址 | 写地址:0x64,读地址:0x65 |
| 数据格式 | 时间寄存器数据为BCD码,读写需转换 |
| 写保护 | 写操作前需关闭写保护(寄存器操作),完成后建议重新开启 |
| 重要提示 | I2C通信时,SCL和SDA线建议接10kΩ上拉电阻 |
软件驱动实现
基于STM32的HAL库进行模拟I2C,你可以根据实际使用的MCU调整GPIO操作。
1. 宏定义与结构体
#include "main.h" // 包含你的MCU相关头文件
// 根据你的硬件连接修改以下宏定义
#define SDA_PIN GPIO_PIN_0
#define SDA_PORT GPIOB
#define SCL_PIN GPIO_PIN_1
#define SCL_PORT GPIOB
#define SD3078_WRITE_ADDR 0x64 // 写地址
#define SD3078_READ_ADDR 0x65 // 读地址
// 时间寄存器结构体
typedef struct {
uint8_t second;
uint8_t minute;
uint8_t hour;
uint8_t week;
uint8_t date;
uint8_t month;
uint8_t year;
} SD3078_TimeTypeDef;
// 模拟I2C延时函数,调整以实现合适的时序(如5us左右)
void I2C_Delay(void)
{
for(uint32_t i = 0; i < 10; i++); // 具体延时需根据MCU主频调整
}
2. 模拟I2C基础时序
这些函数模拟了I2C总线的基本时序:
// 初始化I2C GPIO,设置为开漏输出模式并带上拉电阻
void I2C_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 使能GPIO时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
// 配置SDA和SCL为开漏输出模式
GPIO_InitStruct.Pin = SDA_PIN | SCL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SDA_PORT, &GPIO_InitStruct);
// 初始状态拉高
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
}
// 产生I2C起始信号:SCL高电平期间,SDA产生下降沿
void I2C_Start(void)
{
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
}
// 产生I2C停止信号:SCL高电平期间,SDA产生上升沿
void I2C_Stop(void)
{
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
I2C_Delay();
}
// 发送应答信号(ACK):SCL高电平期间,SDA为低电平
void I2C_Ack(void)
{
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_RESET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET); // 释放SDA
}
// 发送非应答信号(NACK):SCL高电平期间,SDA为高电平
void I2C_NAck(void)
{
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
}
// 等待应答信号,返回0表示收到ACK,1表示无ACK(超时)
uint8_t I2C_Wait_Ack(void)
{
uint16_t timeout = 0;
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET); // 释放SDA
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
// 检测SDA是否被从机拉低
while (HAL_GPIO_ReadPin(SDA_PORT, SDA_PIN) == GPIO_PIN_SET)
{
timeout++;
if(timeout > 1000) // 超时判断
{
I2C_Stop();
return 1;
}
}
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
return 0;
}
// 模拟I2C发送一个字节
void I2C_SendByte(uint8_t data)
{
for(uint8_t i = 0; i < 8; i++)
{
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
data <<= 1;
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
}
// 释放SDA,准备接收ACK
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);
}
// 模拟I2C接收一个字节
uint8_t I2C_ReadByte(void)
{
uint8_t data = 0;
// 确保SDA为输入模式或在外部被上拉
HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET); // 释放SDA
for(uint8_t i = 0; i < 8; i++)
{
data <<= 1;
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);
I2C_Delay();
if(HAL_GPIO_ReadPin(SDA_PORT, SDA_PIN) == GPIO_PIN_SET)
{
data |= 0x01;
}
HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET);
I2C_Delay();
}
return data;
}
3. SD3078专用驱动函数
这部分函数实现了SD3078的核心操作:
// 使能写操作(关闭写保护)
void SD3078_WriteEnable(void)
{
I2C_Start();
I2C_SendByte(SD3078_WRITE_ADDR);
I2C_Wait_Ack();
I2C_SendByte(0x10); // 写保护寄存器地址
I2C_Wait_Ack();
I2C_SendByte(0x00); // 写入0x00关闭写保护
I2C_Wait_Ack();
I2C_Stop();
// 额外操作确保写使效
I2C_Start();
I2C_SendByte(SD3078_WRITE_ADDR);
I2C_Wait_Ack();
I2C_SendByte(0x0F); // 控制寄存器地址
I2C_Wait_Ack();
I2C_SendByte(0x00); // 确保控制寄存器配置正确
I2C_Wait_Ack();
I2C_Stop();
}
// 禁止写操作(开启写保护)
void SD3078_WriteDisable(void)
{
I2C_Start();
I2C_SendByte(SD3078_WRITE_ADDR);
I2C_Wait_Ack();
I2C_SendByte(0x10); // 写保护寄存器地址
I2C_Wait_Ack();
I2C_SendByte(0x80); // 写入0x80开启写保护
I2C_Wait_Ack();
I2C_Stop();
}
// 十进制转BCD码
uint8_t DEC_to_BCD(uint8_t dec)
{
return ((dec / 10) << 4) | (dec % 10);
}
// BCD码转十进制
uint8_t BCD_to_DEC(uint8_t bcd)
{
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
// 设置SD3078时间
void SD3078_SetTime(SD3078_TimeTypeDef *time)
{
// 首先使能写操作
SD3078_WriteEnable();
I2C_Start();
I2C_SendByte(SD3078_WRITE_ADDR);
if(I2C_Wait_Ack())
{
I2C_Stop();
return; // 器件无应答
}
I2C_SendByte(0x00); // 起始寄存器地址
I2C_Wait_Ack();
// 依次发送秒、分、时、周、日、月、年,并转换为BCD码
I2C_SendByte(DEC_to_BCD(time->second));
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->minute));
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->hour) | 0x80); // 设置24小时制
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->week));
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->date));
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->month));
I2C_Wait_Ack();
I2C_SendByte(DEC_to_BCD(time->year));
I2C_Wait_Ack();
I2C_Stop();
// 可选:重新禁止写操作以保护时间设置
// SD3078_WriteDisable();
}
// 读取SD3078时间
void SD3078_GetTime(SD3078_TimeTypeDef *time)
{
I2C_Start();
I2C_SendByte(SD3078_WRITE_ADDR);
if(I2C_Wait_Ack())
{
I2C_Stop();
return; // 器件无应答
}
I2C_SendByte(0x00); // 起始寄存器地址
I2C_Wait_Ack();
I2C_Start(); // 重新发送起始信号
I2C_SendByte(SD3078_READ_ADDR);
I2C_Wait_Ack();
// 依次读取秒、分、时、周、日、月、年,并转换为十进制
time->second = BCD_to_DEC(I2C_ReadByte() & 0x7F); // 忽略最高位
I2C_Ack();
time->minute = BCD_to_DEC(I2C_ReadByte() & 0x7F);
I2C_Ack();
time->hour = BCD_to_DEC(I2C_ReadByte() & 0x3F); // 24小时制处理
I2C_Ack();
time->week = BCD_to_DEC(I2C_ReadByte() & 0x07); // 周只取低3位
I2C_Ack();
time->date = BCD_to_DEC(I2C_ReadByte() & 0x3F);
I2C_Ack();
time->month = BCD_to_DEC(I2C_ReadByte() & 0x1F);
I2C_Ack();
time->year = BCD_to_DEC(I2C_ReadByte());
I2C_NAck(); // 最后一个字节发送NACK
I2C_Stop();
}
4. 初始化与主函数示例
// SD3078初始化
void SD3078_Init(void)
{
I2C_Init(); // 初始化模拟I2C GPIO
// 可选:检查芯片是否正常工作
SD3078_TimeTypeDef time;
SD3078_GetTime(&time);
// 如果读取到异常时间,可以设置默认时间
if(time.year == 0 || time.month == 0 || time.date == 0)
{
SD3078_TimeTypeDef defaultTime = {
.second = 0,
.minute = 0,
.hour = 12,
.week = 1,
.date = 1,
.month = 1,
.year = 24
};
SD3078_SetTime(&defaultTime);
}
}
// 主函数使用示例
int main(void)
{
// 系统初始化
HAL_Init();
SystemClock_Config();
// SD3078初始化
SD3078_Init();
SD3078_TimeTypeDef currentTime;
// 设置时间示例
SD3078_TimeTypeDef setTime = {
.second = 30,
.minute = 15,
.hour = 9,
.week = 5,
.date = 16,
.month = 10,
.year = 25
};
SD3078_SetTime(&setTime);
while(1)
{
// 读取并显示时间
SD3078_GetTime(¤tTime);
// 在这里添加你的时间显示逻辑
// 例如:通过串口打印,或显示在LCD上
HAL_Delay(1000); // 每秒更新一次
}
}
参考代码 SD3078时钟芯片,模拟I2C读写程序 www.3dddown.com/cna/69816.html
关键问题与调试建议
-
I2C通信失败
- 检查硬件连接:确保SDA、SCL已接10kΩ上拉电阻到VCC,VCC通常为3.3V或5V。
- 确认I2C地址:写地址
0x64,读地址0x65。 - 调整时序延时:若通信不稳定,可尝试减小I2C速率(增加
I2C_Delay)。
-
时间读写异常
- 注意BCD码转换:直接读取的时间值为BCD码,需转换為十进制才能直观理解,设置时间前则需将十进制转换为BCD码。
- 检查写保护:写时间前务必关闭写保护(调用
SD3078_WriteEnable),写完后可根据需要重新开启。
-
其他注意事项
- SD3078的控制寄存器(地址
0x0F)在上电初期可能需要正确配置,具体配置值请参考数据手册。 - 若使用STM32硬件I2C,可替换模拟时序部分为HAL库的硬件I2C函数,但需注意STM32硬件I2C自身可能存在的问题。
- SD3078的控制寄存器(地址
浙公网安备 33010602011771号