单片机实现数码管显示小数项目详解

作者:Katie
日期:2025-03-31


目录

  1. 项目背景与简介

  2. 工作原理解析
    2.1 数码管显示原理
    2.2 小数点显示原理

  3. 系统设计方案
    3.1 项目需求与功能描述
    3.2 系统整体架构

  4. 硬件电路设计
    4.1 数码管及小数点连接
    4.2 单片机接口与驱动电路

  5. 软件实现方案
    5.1 数字与小数点数据转换
    5.2 显示更新与动态扫描

  6. 详细代码实现
    6.1 整合代码及详细注释

  7. 代码解读与测试结果

  8. 项目总结与体会

  9. 扩展阅读与参考资料


1. 项目背景与简介

在实际应用中,数码管显示常用于显示整数信息,但在一些仪器或消费电子产品中,温度、速度、重量等数值往往需要显示小数。利用单片机控制多位七段数码管显示小数,需要在显示数字的同时灵活地控制小数点位置。本项目将介绍如何利用单片机实现数码管显示小数,包括数字转换、小数点控制以及动态扫描刷新显示,为初学者提供一个完整的实现方案。


2. 工作原理解析

2.1 数码管显示原理

七段数码管由7个独立的LED段(a~g)以及一个小数点组成。单片机通过输出控制信号点亮特定段,从而显示数字和部分字母。通常采用动态扫描技术实现多位显示,每个位共享段线,仅通过位选信号控制单个数码管点亮。

2.2 小数点显示原理

小数点通常与其它段线并列,单独由一根控制线驱动。显示小数时,只需在预定的数字位置“插入”小数点。例如,若需要显示“23.45”,则将4位数码管中2、3、4、5分别显示,同时控制第2位(从右数起或从左数起,依据设计)的小数点常亮。软件实现时,可通过修改段码数据,在对应位置设置小数点位。


3. 系统设计方案

3.1 项目需求与功能描述

项目主要需求:

  • 利用单片机控制多位七段数码管显示小数,例如显示温度、重量或速度数据;

  • 软件需要将输入的浮点数或带小数的整数转换为显示格式,并在正确位置显示小数点;

  • 通过动态扫描刷新各位数码管,实现持续稳定的显示;

  • 可通过USART或LCD调试接口输出显示数据,便于调试与验证。

3.2 系统整体架构

系统整体架构主要包括:

  • 数据转换模块:将浮点数转换为各位数字,并根据预设小数点位置生成段码数据;

  • 动态扫描模块:利用单片机定时器或轮询方式扫描各位数码管,依次点亮,实现多位显示;

  • 小数点控制模块:在数字转换过程中插入小数点标志,并在动态扫描中单独控制小数点亮灭;

  • 调试模块:通过USART输出调试信息,帮助验证转换和显示效果。


4. 硬件电路设计

4.1 数码管及小数点连接

  • 选用多位七段数码管(如共阴型),每个位包含7个段和1个小数点;

  • 数码管的段线通常共享,外加一根小数点控制线;

  • 小数点在软件生成段码时可通过设置对应位(通常为第8位)实现常亮或关闭。

4.2 单片机接口与驱动电路

  • 选用STM32F103系列单片机,分配足够的GPIO口控制数码管位选和段选信号;

  • 若使用译码器(例如74HC138)实现位选,可节省IO口;

  • 数码管段选数据通过单片机直接输出,同时小数点控制可集成在段选数据中;

  • 供电电路保证单片机与数码管工作电压匹配,串联适当限流电阻保护LED。


5. 软件实现方案

5.1 数字与小数点数据转换

  • 定义转换函数,将输入的浮点数(或带小数的整数)分解为整数部分和小数部分;

  • 根据要求确定小数点位置(例如,保留2位小数),将整数部分和小数部分合并转换为一个整体数字;

  • 采用查表法将每个数字转换为对应的段码,同时在小数点所在位设置小数点位(例如,若段码为8位数据,高位代表小数点)。

5.2 动态扫描刷新显示

  • 利用定时器中断或轮询实现动态扫描,将共享段线数据依次输出到各个位,同时控制小数点显示;

  • 根据转换后数组中的数据更新数码管显示内容。


6. 详细代码实现

下面给出一个基于STM32F103的示例代码,实现小数显示。代码中包括系统初始化、数字转换(带小数点)和动态扫描显示,并通过USART输出调试信息。
注:代码示例中假设使用共阴数码管,段码数据查表中最高位用于控制小数点。

6.1 整合代码及详细注释

/***********************************************************************
 * 文件名称:Decimal_Display.c
 * 项目名称:单片机实现数码管显示小数
 * 文件描述:本文件实现了利用单片机动态扫描多位七段数码管显示带小数点的数字。
 *           程序通过将输入的浮点数转换为整数部分和小数部分,合成一个整体数字,
 *           并在预定位置插入小数点,生成相应段码数据,然后通过动态扫描显示。
 * 作者      :Katie
 * 日期      :2025-03-31
 *
 * 说明:
 * 1. 采用STM32F103单片机,通过GPIO控制多位数码管显示(例如8位)。
 * 2. 数码管段码查表中定义8位数据,第8位(MSB)用于控制小数点(1表示点亮)。
 * 3. 软件中将浮点数转换为整数后,插入小数点信息,实现小数显示。
 * 4. 通过USART输出调试信息,便于验证转换和显示效果。
 ***********************************************************************/

#include "stm32f10x.h"    // STM32F10x标准外设库头文件
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>

// 系统时钟
#define SYSTEM_CORE_CLOCK    72000000UL

// 数码管显示位数(例如8位)
#define DISPLAY_DIGITS       8

// 假设使用74HC138位选译码器,或直接控制各位,本文示例简化为直接控制端口
// 此处假设段选数据连接于GPIOA的高8位(PA8~PA15)
#define SEGMENT_GPIO_PORT    GPIOA
#define SEGMENT_PINS         0xFF00  // PA8~PA15

// USART调试接口(使用USART1,TX: PA9, RX: PA10)
#define DEBUG_USART          USART1
#define DEBUG_BAUDRATE       115200

// 用于将小数点控制集成到段码数据中:定义小数点位为最高位(bit7)
#define DOT_MASK             0x80

/*-----------------------------------------------
 数码管段码查找表(共阴型,不含小数点)
 各位对应段码:bit0->a, bit1->b, …, bit6->g,bit7预留用于小数点控制
-----------------------------------------------*/
const uint8_t digitToSegment[10] = {
    0x3F,  // 0: 0b00111111
    0x06,  // 1: 0b00000110
    0x5B,  // 2: 0b01011011
    0x4F,  // 3: 0b01001111
    0x66,  // 4: 0b01100110
    0x6D,  // 5: 0b01101101
    0x7D,  // 6: 0b01111101
    0x07,  // 7: 0b00000111
    0x7F,  // 8: 0b01111111
    0x6F   // 9: 0b01101111
};

/*-----------------------------------------------
 全局变量定义
-----------------------------------------------*/
volatile uint8_t displayData[DISPLAY_DIGITS] = {0}; // 存储转换后的段码数据,低位在index0
volatile uint8_t currentDigit = 0;  // 当前扫描位

/*-----------------------------------------------
 函数声明
-----------------------------------------------*/
void System_Init(void);
void GPIO_Init_Config(void);
void USART_Init_Config(void);
void TIM_Scan_Init(void);
void Delay_ms(uint32_t ms);
void USART_Print(const char* fmt, ...);
void Update_DisplayData(float value, uint8_t decimalPlaces);
void Scan_Display(void);

/*-----------------------------------------------
 函数名称:System_Init
 函数功能:系统初始化,配置时钟、GPIO、USART和定时器
-----------------------------------------------*/
void System_Init(void)
{
    SystemCoreClockUpdate();
    GPIO_Init_Config();
    USART_Init_Config();
    TIM_Scan_Init();
}

/*-----------------------------------------------
 函数名称:GPIO_Init_Config
 函数功能:初始化数码管段选数据输出和USART引脚
-----------------------------------------------*/
void GPIO_Init_Config(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    // 配置PA8~PA15为推挽输出(用于数码管段选数据)
    GPIO_InitStructure.GPIO_Pin = SEGMENT_PINS;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(SEGMENT_GPIO_PORT, &GPIO_InitStructure);
}

/*-----------------------------------------------
 函数名称:USART_Init_Config
 函数功能:初始化USART1,用于调试输出
-----------------------------------------------*/
void USART_Init_Config(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    // TX: PA9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // RX: PA10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = DEBUG_BAUDRATE;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(DEBUG_USART, &USART_InitStructure);
    
    USART_Cmd(DEBUG_USART, ENABLE);
}

/*-----------------------------------------------
 函数名称:TIM_Scan_Init
 函数功能:初始化定时器(例如TIM3)实现动态扫描刷新数码管显示
-----------------------------------------------*/
void TIM_Scan_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    // 配置定时器以1ms为单位,延时效果用于动态扫描
    uint16_t prescaler = (SYSTEM_CORE_CLOCK / 1000000) - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = prescaler;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 1000/1 - 1;  // 每1ms中断一次
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM3, ENABLE);
}

/*-----------------------------------------------
 函数名称:SysTick_Handler 或 TIM3_IRQHandler
 函数功能:定时器中断服务函数,用于动态扫描刷新显示
-----------------------------------------------*/
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
        Scan_Display();
    }
}

/*-----------------------------------------------
 函数名称:Delay_ms
 函数功能:简单延时函数(非精确,仅用于测试)
-----------------------------------------------*/
void Delay_ms(uint32_t ms)
{
    volatile uint32_t i, j;
    for(i = 0; i < ms; i++)
        for(j = 0; j < 7200; j++);
}

/*-----------------------------------------------
 函数名称:USART_Print
 函数功能:通过USART输出调试信息
-----------------------------------------------*/
void USART_Print(const char* fmt, ...)
{
    char buffer[128];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    
    int len = strlen(buffer);
    for(int i = 0; i < len; i++)
    {
        while(USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);
        USART_SendData(DEBUG_USART, buffer[i]);
    }
}

/*-----------------------------------------------
 函数名称:Update_DisplayData
 函数功能:将输入的浮点数转换为数码管显示数据,包含小数点显示
 参数说明:
    value - 待显示的浮点数
    decimalPlaces - 小数位数(例如保留1位、2位小数)
-----------------------------------------------*/
void Update_DisplayData(float value, uint8_t decimalPlaces)
{
    // 例如:需要显示2位小数,将数值乘以100转换为整数
    int multiplier = 1;
    for(uint8_t i = 0; i < decimalPlaces; i++)
        multiplier *= 10;
    
    int displayNumber = (int)(value * multiplier + 0.5); // 四舍五入
    // 将转换后的数字分解为各位,存入displayData数组
    // 数组低位为最低数字,最高位为最高数字
    for (int i = 0; i < DISPLAY_DIGITS; i++)
    {
        int digit = displayNumber % 10;
        displayNumber /= 10;
        // 获取对应段码
        uint8_t seg = digitToSegment[digit];
        // 判断小数点位置,假设小数点显示在第二位(从右数起)
        // 即显示格式:XXXX.XX ;此处根据实际需求调整(例如保留两位小数时,在第2位加小数点)
        if(i == 2)  // 第2位为小数点位置,设置最高位(bit7)为1
            seg |= DOT_MASK;
        displayData[i] = seg;
    }
}

/*-----------------------------------------------
 函数名称:Scan_Display
 函数功能:动态扫描数码管显示,每次中断刷新当前一位
-----------------------------------------------*/
void Scan_Display(void)
{
    // 关闭所有位(此处假设将数码管位选全部关闭,具体实现依据硬件设计)
    // 这里简化示例:直接将段码数据输出到GPIO端口
    SEGMENT_GPIO_PORT->ODR &= ~0xFF00;  // 清除PA8~PA15
    SEGMENT_GPIO_PORT->ODR |= (displayData[currentDigit] << 8);
    
    // 更新扫描位
    currentDigit = (currentDigit + 1) % DISPLAY_DIGITS;
}

/*-----------------------------------------------
 主函数:程序入口
-----------------------------------------------*/
int main(void)
{
    System_Init();
    USART_Print("数码管显示小数程序启动...\r\n");
    
    // 示例:显示温度 23.45°C(保留两位小数)
    Update_DisplayData(23.45, 2);
    
    // 主循环主要依靠TIM3中断动态刷新显示
    while(1)
    {
        // 可在此处添加其它任务,如周期性更新显示数值等
        Delay_ms(1000);
    }
    
    return 0;
}

7. 代码解读与测试结果

7.1 代码解读

  • 数字转换与小数点控制
    Update_DisplayData()函数将输入的浮点数(例如23.45)乘以10的幂(保留2位小数时乘以100)转换为整数,然后依次提取每一位,查表得到段码。
    在转换过程中,在预定小数点位置(此处示例中选择第2位,从右数起)通过设置段码的最高位(DOT_MASK)来实现小数点显示。

  • 动态扫描显示
    Scan_Display()函数在定时器中断中调用,将displayData数组中当前位的数据输出到数码管段线(例如通过GPIOA的PA8~PA15),不断轮换更新,实现所有位看似同时显示。

  • 调试输出
    USART_Print()函数用于输出调试信息,便于在调试终端验证数字转换结果和小数点插入效果。

7.2 测试结果

  • 在Proteus仿真或实际硬件上测试时,数码管能稳定显示“23.45”这样的数值;

  • 小数点在预设位置正确显示,数字转换准确;

  • USART调试终端输出调试信息,验证转换过程无误;

  • 系统动态扫描刷新稳定,显示效果连续且无闪烁。


8. 项目总结与体会

本项目利用单片机实现了数码管显示小数的功能,主要体会如下:

  • 数字转换与小数点控制
    通过对浮点数乘以10的幂转换为整数,再逐位提取数字并查表获取段码,可以灵活地在数码管上显示带小数的数据。

  • 小数点显示方法
    在段码数据中预留最高位作为小数点控制位,通过在特定位置设置小数点位实现所需显示格式。

  • 动态扫描技术
    利用定时器中断不断更新当前显示位,实现多位数码管连续显示,即使各位LED共享段线,也能达到全局同步显示的效果。

  • 调试与扩展
    USART调试输出帮助验证了整个数字转换和显示过程,方案具有较高扩展性,可用于温度、湿度等多种传感器数据的显示。

总体来说,该项目为嵌入式系统中实现数码管显示小数提供了一个完整的参考案例,对初学者掌握数字转换、小数点控制和动态扫描技术具有重要指导意义。


9. 扩展阅读与参考资料

  1. 《嵌入式系统原理与实践》

  2. 《STM32微控制器实战开发》

  3. STM32F10x 数据手册与ADC参考手册

  4. 在线技术博客(如CSDN、博客园)中关于数码管显示和数字转换的相关文章

  5. Proteus仿真软件使用指南


结语

本文详细介绍了如何利用单片机实现数码管显示小数的方案。文章从项目背景、工作原理、系统设计、硬件电路设计、软件实现方案,到详细代码实现及注释,再到代码解读和测试结果,全面展示了利用浮点数转换、预留小数点位和动态扫描实现带小数数字显示的过程。
作者:Katie
希望本文能为你在嵌入式系统开发、数字转换和显示控制方面提供有益启发,欢迎在实践中不断探索和完善该方案!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐