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(&currentTime);
        
        // 在这里添加你的时间显示逻辑
        // 例如:通过串口打印,或显示在LCD上
        
        HAL_Delay(1000); // 每秒更新一次
    }
}

参考代码 SD3078时钟芯片,模拟I2C读写程序 www.3dddown.com/cna/69816.html

关键问题与调试建议

  1. I2C通信失败

    • 检查硬件连接:确保SDA、SCL已接10kΩ上拉电阻到VCC,VCC通常为3.3V或5V。
    • 确认I2C地址:写地址0x64,读地址0x65
    • 调整时序延时:若通信不稳定,可尝试减小I2C速率(增加I2C_Delay)。
  2. 时间读写异常

    • 注意BCD码转换:直接读取的时间值为BCD码,需转换為十进制才能直观理解,设置时间前则需将十进制转换为BCD码。
    • 检查写保护:写时间前务必关闭写保护(调用SD3078_WriteEnable),写完后可根据需要重新开启。
  3. 其他注意事项

    • SD3078的控制寄存器(地址0x0F)在上电初期可能需要正确配置,具体配置值请参考数据手册。
    • 若使用STM32硬件I2C,可替换模拟时序部分为HAL库的硬件I2C函数,但需注意STM32硬件I2C自身可能存在的问题。
posted @ 2025-12-16 10:32  kang_ms  阅读(3)  评论(0)    收藏  举报