STM32 + DS3231 + TM1640 实时时钟数码管显示系统

一、硬件连接与系统架构

1.1 硬件连接表

模块 STM32F103C8T6引脚 说明
DS3231 RTC
SDA PB7 (I2C1_SDA) I2C数据线,需4.7kΩ上拉
SCL PB6 (I2C1_SCL) I2C时钟线,需4.7kΩ上拉
VCC 3.3V 主电源
GND GND 共地
TM1640 数码管驱动
DIN PA0 数据输入,GPIO模拟时序
SCLK PA1 时钟输入,GPIO模拟时序
VCC 5V 数码管驱动电压
GND GND 共地
4位共阴数码管
段选 TM1640输出 a,b,c,d,e,f,g,dp
位选 TM1640输出 DIG1-DIG4

1.2 系统架构

复制代码
┌─────────────┐    I2C     ┌─────────────┐
│   STM32     │◄──────────►│   DS3231    │
│  F103C8T6  │            │   RTC时钟   │
└──────┬──────┘            └─────────────┘
        │ GPIO模拟时序
        ▼
┌─────────────┐
│   TM1640    │◄─── 数码管显示
│ 驱动芯片    │    4位时间显示
└──────┬──────┘
        │
        ▼
┌─────────────┐
│  4位共阴    │
│  数码管     │
└─────────────┘

二、完整代码实现

2.1 DS3231驱动头文件(ds3231.h)

c 复制代码
#ifndef __DS3231_H
#define __DS3231_H

#include "stm32f10x.h"

// DS3231 I2C地址
#define DS3231_ADDR    0xD0    // 写地址
#define DS3231_READ    0xD1    // 读地址

// DS3231寄存器地址
#define DS3231_SECOND  0x00
#define DS3231_MINUTE  0x01
#define DS3231_HOUR    0x02
#define DS3231_DAY     0x03
#define DS3231_DATE    0x04
#define DS3231_MONTH   0x05
#define DS3231_YEAR    0x06
#define DS3231_CONTROL 0x0E
#define DS3231_STATUS  0x0F

// 时间结构体
typedef struct {
    uint8_t second;
    uint8_t minute;
    uint8_t hour;
    uint8_t day;      // 星期几 1-7
    uint8_t date;      // 日期 1-31
    uint8_t month;     // 月份 1-12
    uint8_t year;      // 年份 0-99
} RTC_TimeTypeDef;

// 函数声明
void DS3231_Init(void);
void DS3231_SetTime(RTC_TimeTypeDef *time);
void DS3231_GetTime(RTC_TimeTypeDef *time);
uint8_t DS3231_ReadTemp(void);
void DS3231_EnableAlarm(void);
void DS3231_DisableAlarm(void);

#endif /* __DS3231_H */

2.2 DS3231驱动源文件(ds3231.c)

c 复制代码
#include "ds3231.h"
#include "i2c.h"
#include "delay.h"

/**
  * @brief  DS3231初始化
  */
void DS3231_Init(void)
{
    // 初始化I2C
    I2C_Init();
    
    // 配置DS3231控制寄存器
    uint8_t ctrl_reg = 0x00;  // 禁用闹钟中断,方波输出1Hz
    I2C_WriteByte(DS3231_ADDR, DS3231_CONTROL, ctrl_reg);
    
    // 清除状态寄存器
    uint8_t status_reg = 0x00;
    I2C_WriteByte(DS3231_ADDR, DS3231_STATUS, status_reg);
    
    printf("DS3231初始化完成\r\n");
}

/**
  * @brief  BCD转十进制
  */
static uint8_t BCD2DEC(uint8_t bcd)
{
    return ((bcd >> 4) * 10) + (bcd & 0x0F);
}

/**
  * @brief  十进制转BCD
  */
static uint8_t DEC2BCD(uint8_t dec)
{
    return ((dec / 10) << 4) | (dec % 10);
}

/**
  * @brief  设置时间
  */
void DS3231_SetTime(RTC_TimeTypeDef *time)
{
    I2C_Start();
    I2C_SendByte(DS3231_ADDR);
    I2C_WaitAck();
    I2C_SendByte(DS3231_SECOND);
    I2C_WaitAck();
    
    I2C_SendByte(DEC2BCD(time->second));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->minute));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->hour));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->day));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->date));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->month));
    I2C_WaitAck();
    I2C_SendByte(DEC2BCD(time->year));
    I2C_WaitAck();
    
    I2C_Stop();
}

/**
  * @brief  获取时间
  */
void DS3231_GetTime(RTC_TimeTypeDef *time)
{
    I2C_Start();
    I2C_SendByte(DS3231_ADDR);
    I2C_WaitAck();
    I2C_SendByte(DS3231_SECOND);
    I2C_WaitAck();
    
    I2C_Start();
    I2C_SendByte(DS3231_READ);
    I2C_WaitAck();
    
    time->second = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->minute = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->hour = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->day = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->date = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->month = BCD2DEC(I2C_ReadByte());
    I2C_SendAck();
    time->year = BCD2DEC(I2C_ReadByte());
    I2C_SendNotAck();
    
    I2C_Stop();
}

/**
  * @brief  读取温度
  */
uint8_t DS3231_ReadTemp(void)
{
    uint8_t temp;
    
    I2C_Start();
    I2C_SendByte(DS3231_ADDR);
    I2C_WaitAck();
    I2C_SendByte(0x11);  // 温度寄存器
    I2C_WaitAck();
    
    I2C_Start();
    I2C_SendByte(DS3231_READ);
    I2C_WaitAck();
    temp = I2C_ReadByte();
    I2C_SendNotAck();
    I2C_Stop();
    
    return temp;
}

2.3 TM1640驱动头文件(tm1640.h)

c 复制代码
#ifndef __TM1640_H
#define __TM1640_H

#include "stm32f10x.h"

// TM1640命令定义
#define TM1640_CMD_DATA_AUTO  0x40  // 自动地址增加模式
#define TM1640_CMD_DATA_FIXED 0x44  // 固定地址模式
#define TM1640_CMD_DISPLAY   0x88  // 显示控制命令
#define TM1640_CMD_ADDRESS  0xC0  // 显示地址命令

// 显示亮度级别
typedef enum {
    BRIGHTNESS_1_16 = 0x00,  // 1/16亮度
    BRIGHTNESS_2_16 = 0x01,  // 2/16亮度
    BRIGHTNESS_4_16 = 0x02,  // 4/16亮度
    BRIGHTNESS_10_16 = 0x03, // 10/16亮度
    BRIGHTNESS_11_16 = 0x04, // 11/16亮度
    BRIGHTNESS_12_16 = 0x05, // 12/16亮度
    BRIGHTNESS_13_16 = 0x06, // 13/16亮度
    BRIGHTNESS_14_16 = 0x07  // 14/16亮度
} BrightnessLevel;

// 数码管段码定义(共阴极)
#define SEG_0   0x3F  // 0
#define SEG_1   0x06  // 1
#define SEG_2   0x5B  // 2
#define SEG_3   0x4F  // 3
#define SEG_4   0x66  // 4
#define SEG_5   0x6D  // 5
#define SEG_6   0x7D  // 6
#define SEG_7   0x07  // 7
#define SEG_8   0x7F  // 8
#define SEG_9   0x6F  // 9
#define SEG_A   0x77  // A
#define SEG_B   0x7C  // b
#define SEG_C   0x39  // C
#define SEG_D   0x5E  // d
#define SEG_E   0x79  // E
#define SEG_F   0x71  // F
#define SEG_OFF 0x00  // 熄灭
#define SEG_DOT 0x80  // 小数点

// 函数声明
void TM1640_Init(void);
void TM1640_WriteCommand(uint8_t cmd);
void TM1640_WriteData(uint8_t addr, uint8_t data);
void TM1640_DisplayNumber(uint8_t pos, uint8_t num, uint8_t dot);
void TM1640_DisplayTime(uint8_t hour, uint8_t minute);
void TM1640_ClearAll(void);
void TM1640_SetBrightness(BrightnessLevel brightness);

#endif /* __TM1640_H */

2.4 TM1640驱动源文件(tm1640.c)

c 复制代码
#include "tm1640.h"
#include "delay.h"

// GPIO定义
#define TM1640_DIN_PORT    GPIOA
#define TM1640_DIN_PIN     GPIO_Pin_0
#define TM1640_SCLK_PORT   GPIOA
#define TM1640_SCLK_PIN    GPIO_Pin_1

/**
  * @brief  TM1640延时函数
  */
static void TM1640_Delay(void)
{
    uint8_t i;
    for(i = 0; i < 10; i++);
}

/**
  * @brief  TM1640初始化
  */
void TM1640_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置GPIO为推挽输出
    GPIO_InitStructure.GPIO_Pin = TM1640_DIN_PIN | TM1640_SCLK_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(TM1640_DIN_PORT, &GPIO_InitStructure);
    
    // 初始状态
    GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
    GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
    
    // 设置自动地址增加模式
    TM1640_WriteCommand(TM1640_CMD_DATA_AUTO);
    
    // 设置亮度为中等
    TM1640_SetBrightness(BRIGHTNESS_10_16);
    
    // 清屏
    TM1640_ClearAll();
}

/**
  * @brief  写命令
  */
void TM1640_WriteCommand(uint8_t cmd)
{
    uint8_t i;
    
    GPIO_ResetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
    TM1640_Delay();
    
    for(i = 0; i < 8; i++)
    {
        GPIO_ResetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        if(cmd & 0x01)
            GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        else
            GPIO_ResetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        TM1640_Delay();
        
        GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        TM1640_Delay();
        
        cmd >>= 1;
    }
    
    GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
    GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
}

/**
  * @brief  写数据
  */
void TM1640_WriteData(uint8_t addr, uint8_t data)
{
    uint8_t i;
    
    // 发送地址命令
    GPIO_ResetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
    TM1640_Delay();
    
    for(i = 0; i < 8; i++)
    {
        GPIO_ResetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        if((TM1640_CMD_ADDRESS | addr) & (1 << i))
            GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        else
            GPIO_ResetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        TM1640_Delay();
        
        GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        TM1640_Delay();
    }
    
    // 发送数据
    for(i = 0; i < 8; i++)
    {
        GPIO_ResetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        if(data & (1 << i))
            GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        else
            GPIO_ResetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
        TM1640_Delay();
        
        GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
        TM1640_Delay();
    }
    
    GPIO_SetBits(TM1640_SCLK_PORT, TM1640_SCLK_PIN);
    GPIO_SetBits(TM1640_DIN_PORT, TM1640_DIN_PIN);
}

/**
  * @brief  显示数字
  */
void TM1640_DisplayNumber(uint8_t pos, uint8_t num, uint8_t dot)
{
    uint8_t seg_data;
    
    // 数字段码映射
    static const uint8_t seg_table[] = {
        SEG_0, SEG_1, SEG_2, SEG_3, SEG_4, SEG_5, SEG_6, SEG_7,
        SEG_8, SEG_9, SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F
    };
    
    seg_data = seg_table[num % 16];
    if(dot) seg_data |= SEG_DOT;  // 添加小数点
    
    TM1640_WriteData(pos, seg_data);
}

/**
  * @brief  显示时间(HH:MM格式)
  */
void TM1640_DisplayTime(uint8_t hour, uint8_t minute)
{
    // 第1位:小时十位
    TM1640_DisplayNumber(0, hour / 10, 0);
    
    // 第2位:小时个位
    TM1640_DisplayNumber(1, hour % 10, 1);  // 带小数点作为冒号
    
    // 第3位:分钟十位
    TM1640_DisplayNumber(2, minute / 10, 0);
    
    // 第4位:分钟个位
    TM1640_DisplayNumber(3, minute % 10, 0);
}

/**
  * @brief  清屏
  */
void TM1640_ClearAll(void)
{
    uint8_t i;
    for(i = 0; i < 16; i++)
    {
        TM1640_WriteData(i, SEG_OFF);
    }
}

/**
  * @brief  设置亮度
  */
void TM1640_SetBrightness(BrightnessLevel brightness)
{
    TM1640_WriteCommand(TM1640_CMD_DISPLAY | brightness | 0x01);  // 开启显示
}

2.5 主程序(main.c)

c 复制代码
#include "stm32f10x.h"
#include "ds3231.h"
#include "tm1640.h"
#include "delay.h"
#include "usart.h"

RTC_TimeTypeDef current_time;

int main(void)
{
    // 系统初始化
    SystemInit();
    Delay_Init();
    USART_Init(115200);
    
    printf("STM32 + DS3231 + TM1640 实时时钟系统\r\n");
    
    // 初始化DS3231
    DS3231_Init();
    
    // 初始化TM1640
    TM1640_Init();
    
    // 设置初始时间(首次使用时取消注释)
    /*
    current_time.second = 0;
    current_time.minute = 30;
    current_time.hour = 14;
    current_time.day = 1;      // 星期一
    current_time.date = 15;
    current_time.month = 12;
    current_time.year = 23;
    DS3231_SetTime(&current_time);
    printf("时间已设置\r\n");
    */
    
    uint8_t last_second = 0;
    uint8_t display_mode = 0;  // 0:显示时间, 1:显示温度
    
    while(1)
    {
        // 获取当前时间
        DS3231_GetTime(&current_time);
        
        // 每秒更新显示
        if(current_time.second != last_second)
        {
            last_second = current_time.second;
            
            // 切换显示模式(每30秒切换一次)
            if(current_time.second % 30 == 0)
            {
                display_mode = !display_mode;
            }
            
            if(display_mode == 0)
            {
                // 显示时间 HH:MM
                TM1640_DisplayTime(current_time.hour, current_time.minute);
                printf("时间: %02d:%02d:%02d\r\n", 
                       current_time.hour, current_time.minute, current_time.second);
            }
            else
            {
                // 显示温度
                uint8_t temp = DS3231_ReadTemp();
                TM1640_DisplayNumber(0, temp / 10, 0);
                TM1640_DisplayNumber(1, temp % 10, 1);  // 带小数点
                TM1640_DisplayNumber(2, 12, 0);  // 显示"C"
                TM1640_DisplayNumber(3, 0, 0);   // 清空最后一位
                printf("温度: %d°C\r\n", temp);
            }
        }
        
        Delay_ms(100);  // 100ms刷新一次
    }
}

三、I2C底层驱动(i2c.c)

c 复制代码
#include "i2c.h"
#include "delay.h"

/**
  * @brief  I2C初始化
  */
void I2C_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7);  // 拉高总线
}

/**
  * @brief  I2C起始信号
  */
void I2C_Start(void)
{
    SDA_OUT();
    SCL_HIGH();
    SDA_HIGH();
    Delay_us(4);
    SDA_LOW();
    Delay_us(4);
    SCL_LOW();
}

/**
  * @brief  I2C停止信号
  */
void I2C_Stop(void)
{
    SDA_OUT();
    SCL_LOW();
    SDA_LOW();
    Delay_us(4);
    SCL_HIGH();
    SDA_HIGH();
    Delay_us(4);
}

/**
  * @brief  等待应答
  */
uint8_t I2C_WaitAck(void)
{
    uint8_t timeout = 0;
    
    SDA_IN();
    SCL_HIGH();
    Delay_us(1);
    
    while(SDA_READ())
    {
        if(++timeout > 250)
        {
            I2C_Stop();
            return 1;
        }
    }
    
    SCL_LOW();
    return 0;
}

/**
  * @brief  发送字节
  */
void I2C_SendByte(uint8_t byte)
{
    uint8_t i;
    SDA_OUT();
    
    for(i = 0; i < 8; i++)
    {
        SCL_LOW();
        if(byte & 0x80)
            SDA_HIGH();
        else
            SDA_LOW();
        byte <<= 1;
        Delay_us(2);
        SCL_HIGH();
        Delay_us(2);
    }
    SCL_LOW();
}

/**
  * @brief  读取字节
  */
uint8_t I2C_ReadByte(void)
{
    uint8_t i, byte = 0;
    SDA_IN();
    
    for(i = 0; i < 8; i++)
    {
        SCL_LOW();
        Delay_us(2);
        SCL_HIGH();
        byte <<= 1;
        if(SDA_READ())
            byte |= 0x01;
        Delay_us(1);
    }
    SCL_LOW();
    return byte;
}

void I2C_SendAck(void)
{
    SDA_OUT();
    SCL_LOW();
    SDA_LOW();
    Delay_us(2);
    SCL_HIGH();
    Delay_us(2);
    SCL_LOW();
}

void I2C_SendNotAck(void)
{
    SDA_OUT();
    SCL_LOW();
    SDA_HIGH();
    Delay_us(2);
    SCL_HIGH();
    Delay_us(2);
    SCL_LOW();
}

四、关键调试要点

4.1 常见问题排查

现象 原因 解决方法
数码管不亮 TM1640时序错误 检查DIN和SCLK时序,增加延时
显示乱码 段码定义错误 确认是共阴还是共阳数码管
时间不走 DS3231未初始化 检查I2C地址和寄存器配置
温度读数异常 读取温度寄存器错误 确认温度寄存器地址为0x11

4.2 优化建议

  1. 省电优化:在无操作时关闭数码管显示,或降低亮度
  2. 按键设置:添加按键用于调整时间和设置闹钟
  3. 掉电保护:DS3231自带电池,断电后时间继续走
  4. 显示特效:添加冒号闪烁效果,增强用户体验

参考代码 STM32+DS3231+TM1640驱动数码管显示数据 www.youwenfan.com/contentcsu/56169.html

五、总结

这套系统实现了:

  • 高精度计时:DS3231精度±2ppm,年误差仅±1分钟
  • 稳定显示:TM1640驱动4位数码管,亮度可调
  • 低功耗:DS3231休眠电流仅200nA
  • 易于扩展:可添加温度传感器、蜂鸣器等外设
相关推荐
小懒懒️2 小时前
嵌入式常见通信协议学习——UART
stm32·uart·通信协议
zjxtxdy2 小时前
STM32开发
stm32·单片机·fpga开发
BT-BOX2 小时前
STM32简易数字电流表仿真_LCD1602显示
stm32·电流测量·lcd1602显示·电流表
集和诚JHCTECH3 小时前
BRAV-7120加持,让有毒有害气体无处遁形
大数据·人工智能·嵌入式硬件
LCG元3 小时前
STM32实战:基于STM32F103的I2C通信(AT24Cxx EEPROM读写)
stm32·单片机·嵌入式硬件
徐某人..3 小时前
基于i.MX6ULL平台的智能网关系统开发
arm开发·c++·单片机·qt·物联网·学习·arm
星恒讯工业路由器5 小时前
MCU+WiFi与CPU+WiFi模块区别
单片机·嵌入式硬件
LCMICRO-133108477465 小时前
长芯微LD7940完全P2P替代AD7940,是一款14位、逐次逼近型模数转换器(ADC)
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·模数转换器adc
进击的小头6 小时前
20_第20篇:嵌入式外设驱动开发基础:寄存器级开发与库函数开发对比实战
arm开发·驱动开发·单片机