51单片机+74Hc595驱动8x8LED点阵屏:从原理到代码实现(附完整工程)

51单片机-74HC595移位寄存器 74HC595移位寄存器的功能介绍,和代码实现..... 阅读详情

从零构建8x8点阵屏显示系统:深入剖析74HC595与51单片机的协同设计

最近在整理工作室的旧项目时,翻出了一个基于AT89C51的老式点阵屏显示模块。它静静地躺在那里,依然能稳定地循环显示着0到9的数字。这让我想起了很多初学者在初次接触点阵屏和串行移位寄存器时所面临的困惑——硬件连接看似复杂,代码时序让人头疼,调试过程更是充满挑战。实际上,一旦你理解了74HC595这颗“IO扩展神器”与51单片机的协作机制,构建一个稳定可靠的8x8 LED点阵显示系统,就会变得像搭积木一样直观有趣。这篇文章,我将从一个实践者的角度,带你从头开始,不仅完成硬件连接和代码编写,更深入理解每一处设计背后的考量,让你真正掌握这套经典组合的精髓。

1. 硬件架构深度解析:为何选择74HC595?

当我们面对一个8x8的LED点阵屏时,最直接的想法是:需要64个IO口来控制每一个LED的亮灭。这对于大多数微控制器,尤其是经典的51单片机系列(通常只有32个或更少的IO口)来说,是根本无法实现的。这就是我们需要引入外部芯片进行IO扩展的根本原因。

在众多扩展方案中,74HC595之所以成为经久不衰的选择,主要基于以下几个核心优势:

  • 极致的IO口节省:它采用串行输入、并行输出的工作方式。理论上,你只需要单片机提供3个IO口(数据、时钟、锁存),就能控制近乎无限多个595级联,输出海量的并行信号。在我们的8x8点阵屏场景中,用1片595驱动8位列线,已经足够。
  • 强大的驱动能力:每个输出引脚可以提供高达35mA的电流(具体请查阅你所使用型号的数据手册),这足以直接驱动LED点阵的每一列,无需额外的三极管或驱动电路,大大简化了硬件设计。
  • 内置锁存功能:这是关键所在。当串行数据一位位移入595内部的移位寄存器时,输出端保持不变。只有当你给锁存时钟一个上升沿脉冲时,移位寄存器中的数据才会被同步拷贝到输出锁存器中,并立即呈现在输出引脚上。这个特性对于点阵屏的动态扫描显示至关重要,它能有效消除鬼影,确保显示稳定。

那么,点阵屏本身呢?市面上常见的8x8模块主要有两种接法:共阴极和共阳极。理解这一点是正确设计驱动电路的前提。

特性共阴极模块共阳极模块
行线定义每一行8个LED的阴极相连每一行8个LED的阳极相连
列线定义每一列8个LED的阳极相连每一列8个LED的阴极相连
点亮逻辑对应行给低电平,对应列给高电平对应行给高电平,对应列给低电平
与595的常见接法595输出驱动列线(阳极),单片机IO直接驱动或通过三极管驱动行线(阴极)595输出驱动行线(阳极),单片机IO直接驱动或通过三极管驱动列线(阴极)

注意:在采购或使用点阵屏模块前,务必用万用表的二极管档位测量确认其类型,这是硬件连接的第一步,也是最容易出错的一步。本文后续的电路和代码将以共阴极点阵屏为例进行展开。

基于以上分析,我们的系统硬件连接框图就清晰了:

  1. 51单片机(如STC89C52RC)作为主控。
  2. 1片74HC595负责驱动点阵屏的8根列线(阳极)。
  3. 单片机的P0口(或任一8位端口)通过8个NPN型三极管(如S8050)或专用的行驱动芯片(如ULN2803),来驱动点阵屏的8根行线(阴极)。使用驱动元件是因为单片机IO口的拉电流能力通常较弱,不足以同时点亮一行中的多个LED。
  4. 连接单片机的3个IO口到74HC595的对应引脚:SER(DS, 14脚)接数据线,SRCLK(SHCP, 11脚)接移位时钟,RCLK(STCP, 12脚)接锁存时钟。

2. 核心驱动代码:逐行拆解74HC595的时序

理解了硬件,我们来看软件如何“指挥”硬件工作。驱动74HC595的核心,就是一个严格遵守其时序规则的Hc595SendByte函数。

// 定义与51单片机IO口的连接,根据你的实际电路修改
sbit SER   = P3^4; // 串行数据输入
sbit SRCLK = P3^5; // 移位寄存器时钟(上升沿移位)
sbit RCLK  = P3^6; // 存储寄存器时钟(上升沿锁存)

/**
  * @brief  向74HC595发送一个字节的数据
  * @param  dat: 要发送的8位数据
  * @retval 无
  */
void Hc595SendByte(unsigned char dat)
{
    unsigned char i;

    // 初始状态:确保时钟线处于低电平
    SRCLK = 0;
    RCLK = 0;

    // 循环8次,发送一个字节(从最高位MSB开始)
    for(i=0; i<8; i++)
    {
        // 1. 准备数据位:取dat的最高位(bit7)送到SER引脚
        SER = (dat & 0x80) ? 1 : 0; // 等价于 dat >> 7
        // 2. 产生移位时钟上升沿:将当前SER的数据位“推入”595的移位寄存器
        SRCLK = 1;
        _nop_(); // 短暂延时,确保时钟高电平被稳定识别
        _nop_();
        SRCLK = 0; // 拉低时钟,为下一次移位做准备
        // 3. 数据左移,准备下一次发送
        dat <<= 1;
    }

    // 所有8位数据都已移入内部的移位寄存器
    // 产生锁存时钟上升沿:将移位寄存器中的数据**同步**拷贝到输出锁存器
    RCLK = 1;
    _nop_();
    _nop_();
    RCLK = 0;
}

这个函数中有几个容易忽略但至关重要的细节:

  • 发送顺序:代码采用高位在先(MSB First)。即dat的第7位(二进制最高位)最先被发送到SER,最终会对应74HC595的Q7引脚(第7脚)。你需要确保点阵屏的列线连接顺序与此匹配,否则显示的字模会是镜像的。
  • _nop_()的作用:这个空操作指令用于产生一个极短的延时(约1个机器周期)。在12MHz晶振的51单片机中,大约是1微秒。它的目的是确保SRCLK和RCLK的高电平与低电平脉冲有足够的宽度,能被74HC595可靠地识别。虽然很多情况下不用_nop_()也能工作,但在高速或干扰环境下,加上它能极大提高稳定性。
  • 先移位,后锁存:整个过程中,RCLK(锁存时钟)一直保持低电平。直到8位数据全部移入完毕,才给它一个上升沿。这个设计保证了在数据移位过程中,输出端保持不变,避免了列数据在切换时产生错误的中间状态,这是实现稳定无闪烁显示的基础。

3. 动态扫描与字模设计:让数字“活”起来

单个74HC595解决了列驱动问题,但我们只有8根行线,如何同时控制64个LED呢?答案是动态扫描。其原理是利用人眼的视觉暂留效应,在极短的时间内(例如每行1-2毫秒),依次点亮每一行。只要整个扫描周期足够快(通常低于20ms),人眼就会看到一幅稳定的图像。

// 定义行选数组(共阴极,低电平有效)
// 假设P0口通过反相器或直接连接点阵屏行线(阴极),这里P0输出低电平选中某一行
unsigned char code Row_Select[8] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};
// 分别对应:选中第0行、第1行...第7行(二进制最低位为0表示选中)

// 数字0-9的字模数据(共阴极,列数据为1表示该像素点亮)
// 这是一个8x8的字体,每个数字由8个字节(8行)构成
unsigned char code Digit_Font[10][8] = {
    {0x00, 0x3E, 0x41, 0x41, 0x41, 0x41, 0x3E, 0x00}, // 0
    {0x00, 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00}, // 1
    {0x00, 0x46, 0x49, 0x49, 0x49, 0x49, 0x31, 0x00}, // 2
    {0x00, 0x22, 0x49, 0x49, 0x49, 0x49, 0x36, 0x00}, // 3
    {0x00, 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, 0x00}, // 4
    {0x00, 0x27, 0x45, 0x45, 0x45, 0x45, 0x39, 0x00}, // 5
    {0x00, 0x3C, 0x4A, 0x49, 0x49, 0x49, 0x30, 0x00}, // 6
    {0x00, 0x01, 0x01, 0x71, 0x09, 0x05, 0x03, 0x00}, // 7
    {0x00, 0x36, 0x49, 0x49, 0x49, 0x49, 0x36, 0x00}, // 8
    {0x00, 0x26, 0x49, 0x49, 0x49, 0x49, 0x3E, 0x00}  // 9
};

有了行选信号和字模数据,主程序的动态扫描循环就呼之欲出了:

void main(void)
{
    unsigned char current_digit = 0; // 当前显示的数字索引 (0-9)
    unsigned int scan_counter, row;

    while(1)
    {
        // 外层循环:控制每个数字显示的持续时间,实现数字切换
        for(scan_counter = 0; scan_counter < 500; scan_counter++)
        {
            // 内层循环:动态扫描8行
            for(row = 0; row < 8; row++)
            {
                // 1. 关闭所有显示(消隐),防止切换时的拖影
                Hc595SendByte(0x00);
                // 2. 选中当前要显示的行
                P0 = Row_Select[row];
                // 3. 发送当前数字在当前行的列数据
                Hc595SendByte(Digit_Font[current_digit][row]);
                // 4. 保持显示一小段时间(行扫描时间)
                Delay_us(1500); // 约1.5ms,调整此值可改变整体亮度
                // 5. 循环回到第1步,准备扫描下一行
            }
        }
        // 切换至下一个数字
        current_digit++;
        if(current_digit >= 10)
        {
            current_digit = 0;
        }
    }
}

提示:Delay_us(1500)是一个需要你实现的微秒级延时函数。每行显示时间约为1.5ms,那么扫描完整8行就是12ms,刷新率约为83Hz,远高于人眼能察觉闪烁的临界值(通常认为50-60Hz),因此显示效果非常稳定。

4. 工程优化与高级技巧:从“能用”到“好用”

完成基础显示后,我们可以从以下几个角度优化整个工程,使其更健壮、更灵活。

优化一:亮度均匀性与消隐 上面的基础代码中,Hc595SendByte(0x00);这行“消隐”代码至关重要。在切换行选信号之前,先关闭所有列输出,可以避免在行线切换的瞬间,由于电平变化不够陡峭(即上升/下降时间)而导致上一行的内容短暂地显示在下一行上,形成“鬼影”。这是提高显示质量的关键一步。

优化二:使用定时器中断驱动扫描 在主循环中直接使用Delay_us进行延时,会独占CPU,导致单片机无法同时处理其他任务(如按键检测、串口通信)。更专业的做法是使用定时器中断来驱动扫描。

// 假设使用定时器0,12MHz晶振,1ms中断一次
void Timer0_Init(void)
{
    TMOD &= 0xF0; // 清除T0控制位
    TMOD |= 0x01; // 设置T0为模式1(16位定时器)
    TH0 = 0xFC;   // 定时1ms的初值计算
    TL0 = 0x18;
    ET0 = 1;      // 允许T0中断
    TR0 = 1;      // 启动T0
    EA = 1;       // 开启总中断
}

volatile unsigned char timer_flag = 0;
void Timer0_ISR(void) interrupt 1
{
    static unsigned char row_index = 0;
    TH0 = 0xFC; // 重装初值
    TL0 = 0x18;

    // 执行一行扫描
    Hc595SendByte(0x00); // 消隐
    P0 = Row_Select[row_index];
    Hc595SendByte(Digit_Font[current_digit][row_index]);

    row_index++;
    if(row_index >= 8)
    {
        row_index = 0;
        timer_flag = 1; // 标记一个完整的扫描周期结束
    }
}

void main(void)
{
    Timer0_Init();
    while(1)
    {
        if(timer_flag == 1)
        {
            timer_flag = 0;
            // 这里可以安全地更新current_digit,而不会打断扫描
            // 例如,每隔100个完整扫描周期(约1.2秒)切换一次数字
            static unsigned int cycle_count = 0;
            cycle_count++;
            if(cycle_count >= 100)
            {
                cycle_count = 0;
                current_digit++;
                if(current_digit >= 10) current_digit = 0;
            }
        }
        // 主循环现在可以空出来做其他事情,比如处理按键
        // Key_Scan();
    }
}

优化三:字模提取与自定义显示 你未必需要自己计算十六进制字模。可以使用如“PCtoLCD2002”这类字模提取软件,轻松生成任意字符、汉字甚至简单图形的点阵数据。只需将软件设置为“共阴”、“逐行式”、“高位在前”等与你的代码匹配的模式,然后将生成的数组复制到你的工程中即可。这为你显示字母、符号或自定义图案打开了大门。

优化四:级联多片74HC595 如果你需要驱动更大尺寸的点阵屏(如16x16),或者希望用更少的单片机IO口控制多个8x8模块,就需要级联多片74HC595。级联时,第一片595的串行输出引脚(Q7’,第9脚)连接到第二片的数据输入(SER,第14脚),它们的SRCLK和RCLK分别并联。发送数据时,先发送距离单片机最远的那片595的数据(高位),最后发送最近的那片(低位)。在代码上,你需要一个能发送多个字节的Hc595SendBytes函数,并重新组织你的显示缓冲区。

调试这样的系统,最有效的工具就是逻辑分析仪。用它抓取SER、SRCLK、RCLK三根线上的波形,可以清晰地看到数据移位的顺序、时钟脉冲的宽度以及锁存发生的时机,任何时序问题都无处遁形。当然,在没有专业仪器的情况下,耐心地分段测试代码、使用LED或万用表检查关键引脚电平,同样是电子爱好者解决问题的经典方式。

【51单片机系列】74HC595实现对LED点阵的控制 本文是关于LED点阵的使用,使用74HC595模块实现对LED点阵的控制。 阅读详情

相关推荐

【51单片机】LED点阵屏(江科大)

LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。

2302_80796399的博客 2492

8X8X8光立方取模软件

8X8X8光立方取模软件,内有VB源代码,有了它就可以仿照写出自己的光立方取模软件了

8*8LED点阵图原理(74HC595芯片使用方法)

1.LED灯排线图 1.该线为共阳极点阵图,一行的阳极都是接在一条线上的 如果要亮第一排第一个灯,需要9号行位置为1(即5V),13号列为0(即0V)形成电流,该灯就亮。 2.故行为1000 000控制只有第一行有入口是高电压的,列为0111 1111控制只有第一列为低电压。 控制一个LED灯可以直接控制,但控制两个不在同一行的LED灯得用动态显示。 3.动态思想:我们确认好每行亮那几个LED灯,然后用1000 0000不断右移动,每次只亮一行的LED灯,然后每行变化很快(其实每次只有一行亮,但因为切换快

qq_54674051的博客 1万+

【免费下载】 探索科技魅力:8x8x8 LED立方体项目

在这个充满创新的时代,动手制作属于自己的科技产品是一种无与伦比的体验。今天,我们向您推荐一个令人惊叹的DIY项目——8x8x8 LED立方体(LED Cube)。这个项目不仅将带给你视觉上的震撼,更是一次提升技能和创造力的完美之旅。 ## 1、项目介绍 8x8x8 LED立方体是一个由64颗LED组成的3D显示装置,能播放丰富多彩的3D动画。项目灵感来源于YouTube上的一段精彩视频,经过简

gitblog_00052的博客 3万+

基于 51 单片机的 8*8 共阴极点阵屏显示爱心图案

本文详细介绍了利用 51 单片机控制 88 共阴极点阵屏显示爱心图案的原理、方法及实现过程。首先深入阐述了 88 共阴极点阵屏的工作机制,包括其内部电路结构与显示原理,接着全面展示了硬件系统的设计,涵盖 51 单片机最小系统和点阵屏驱动电路的构建,随后重点讲解了软件设计思路,包含字模提取、显示函数的编写以及相关辅助函数的作用,并给出了完整且详细注释的代码示例。通过对系统调试环节的介绍,阐述了硬件调试和软件调试的要点及可能出现问题的解决方法。

unique_23的博客 2648

单片机学习笔记————51单片机实现两片联级74HC595驱动16个LED灯

一、使用proteus绘制简单的电路图,用于后续仿真 二、编写程序 /******************************************************************************************************************** ---- @Project: LED-74HC595 ---- @File: main.c ---- @Edit: ZHQ ---- @Version: V1.0 ---- @Creation.

DJDN426611的博客 4944

英文制作光立方8X8X8详细指南-lihux(下)

此文档从国外一个DIY网站上整理得到的,花了近两天时间,来之不易,不甘独享,传至网上,供大家参考。网址是:http://www.instructables.com/id/Led-Cube-8x8x8/,在制作8X8X8光立方最好先试制一个4X4X4,积累必要的经验和技巧。可参考http://www.instructables.com/id/Led-Cube-4x4x4/,我也已整理出来了这部分的文档:英文制作光立方4X4X4详细指南,百度一下即可找到。 原本该网站是有pdf文档的,但是需要注册付费账号(not-free,3年几十刀好像),百度文库上其实也有人整理过一篇,不过不细致,很多照片都没有贴上,这里我对72个步骤逐一做了梳理,并建立了目录,方便大家查看,照片基本上95%的都贴上来了,做了相应的裁剪。虽然是全英文的,但过了英语四级的童鞋基本能看懂,部分生词我做了标注。 我将这72steps分成了4部分分别是:硬件制作、软件编写(AVR编程)、软件编写(PC编程)、软件编写(Arduino编程)。 另外,原网站上,每一步制作的下面都有很多网友的讨论,部分的比较好的我也放到了这里,但绝大多数想看的话还是登陆原网站查看,查看方法: 1. 确定你当前看的是地多少步,比如是step 43,那么你只需在浏览器中输入:http://www.instructables.com/id/Led-Cube-8x8x8/step43 回车就可查看到,其他的步对应的文章,只需修改最末尾的数字43为你想看的步即可。

C51单片机控制74HC595模块,驱动LED实现流水灯效果

我们知道单片机的IO资源十分有限,那么在有限的资源下需要实现更多功能,就需要借助外接扩展的形式实现了。我们需要控制若干个LED,来实现流水灯效果。显然单片机自身的资源是不够的。这个时候我们可以使用IO扩展来实现。本文使用的是74HC595 io扩展模块,实现一个简单的流水灯效果。有兴趣的同学,可以通过级联更多的模块,和改变数据内容,设计更多好玩的效果。

ByteBox的博客 2032

国外开源,8x8x8 LED光立方体详细制作教程、源码、3D打印文件分享-电路方案

LED光立方永远不会过时,在网上进行类似项目的研究,发现仅仅构建LED立方体的时间非常耗时,对于一些来说可能相当复杂。对于大多数这些项目来说,驱动这些LED立方体的电子电路对于入门级爱好者来说可能相当复杂,并且如果出现问题可能不容易排除故障。 在这里,我们将展示如何建立我们的单色8x8x8 LED立方体,希望入门级爱好者能够顺利完成它,没有太大的困难。建立这样一个立方体可能有一个更快的方法,但这是我们现在可以提供的最好的,而不用使用复杂的夹具来促进构建。 对于这个项目,需要具备一些基本的焊接技能,基本的电子知识,熟悉使用Arduino板。 虽然我们的立方体不是用来与任何特定的微控制器一起工作的,但是我们将使用这个指令中流行的Arduino电路板通过SPI驱动我们的立方体,只用3根信号线。 详细的制作教程整理在附件里面,源码以及3D打印的源文件也都在附件里!

51单片机驱动LED点阵屏:74HC595级联与动态扫描实战

本文详细介绍了51单片机如何通过74HC595级联技术驱动LED点阵屏,包括硬件连接、动态扫描原理及软件编程实现。重点讲解了字模提取、图形显示和多种动画效果的制作方法,为开发者提供了从基础到进阶的实战指南,帮助快速掌握LED点阵屏的控制技术。

spice的博客 997

51单片机实战:用74HC595驱动LED点阵屏显示自定义动画(附完整代码)

本文详细介绍了如何使用51单片机和74HC595移位寄存器驱动8x8 LED点阵屏,并实现自定义动画显示。内容涵盖硬件电路连接、74HC595驱动函数编写、动态扫描原理、字模提取方法以及动画帧序列设计,提供了完整的代码示例和常见问题解决方案,适合嵌入式初学者和电子爱好者动手实践。

git9versioner的博客 1111

LPProjects-LED-Cube-8x8x8:可通过智能手机控制的3D显示

LPProjects LED立方体8x8x8 可通过智能手机控制的3D显示 由卢卡·彭格(Luka Penger)创建•通过网络实时控制•通用电源5-12V DC •自定义动画和效果•沿X,Y和Z方向触摸进行绘制•可编程序列•WEB界面快速控制•开放的UDP / TCP协议•自动启动功能•32 MB的Flash动画存储•控制多个多维数据集•iPhone / iPod Touch / iPad应用程序•3D预览动画•与朋友分享动画•为您的聚会增光添彩! 使用移动设备连接到LPProjects LED Cube 8x8x8,并制作精美的动画! iOS应用程序: 与iPhone 4,iPhone 4S,iPhone 5,iPhone 6,iPhone 6 +,iPod touch,iPad兼容。 需要iOS 7.0或更高版本。 可用在: :

8x8x8光立方夜间模式测试与动画显示项目实战

本章详细讲解了开机动画与LOGO显示的实现方案,包括动画帧生成与压缩、LOGO建模与渲染、资源存储策略以及启动流程与异常处理机制。这些内容为后续章节中“多种动画模式设计与实现”奠定了坚实基础,特别是在帧管理、资源调度与交互控制方面提供了重要参考。

weixin_35750747的博客 8421

51单片机LED点阵屏(八)

LED点阵屏由若干个独立的LED组成,LED以矩阵(a×a)的形式排列,以灯珠亮灭来显示文字、图片、视频灯。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。LED点阵屏分类按颜色:单色、双色、全彩按像素:8×8,16×16等(大规模的LED点阵通常由很多个小点阵拼接而成)

m0_50562428的博客 5311

VHDL实现8x8x8光立方控制系统设计

光立方是一个三维的LED显示阵列,通常由LED灯以8x8x8的形式排列构成。由于其立体的展示效果,光立方在视觉艺术、信息显示以及互动技术领域有着广泛的应用。每一个LED点可以单独控制,通过不同的点亮组合,可以在空间中展示图形、文字或者动画。CPLD是Complex Programmable Logic Device的缩写,意为复杂可编程逻辑器件。CPLD通过高密度的查找表(LUTs)和逻辑块(logic blocks)来实现用户定义的逻辑功能。

weixin_42389113的博客 8932

8x8x8光立方

8x8x8光立方制作 该作品做出来后效果优美,有你想像不到的美丽

基于51单片机和8X8LED点阵屏(板载74HC595驱动)的普中开发板矩阵按键控制的小游戏《贪吃蛇》

51单片机 矩阵键盘按键 普中A2开发板 8X8LED点阵屏 贪吃蛇 小游戏 扫描显示 STC89C52RC

gantengsheng的博客 3560

手把手教你在Keil/IAR中移植SEGGER_RTT组件(避坑指南+性能对比)

本文详细介绍了在Keil/IAR开发环境中移植SEGGER RTT调试组件的完整流程与优化技巧。通过对比传统串口调试,SEGGER RTT凭借零硬件占用、高速传输和双向交互等优势,显著提升嵌入式开发效率。文章包含配置步骤、性能调优方案及常见问题解决方案,是嵌入式开发者必备的实用指南。

y7z8a的博客 245
上一篇: 现代雷达动目标检测(MTD)技术解析---从多普勒滤波到优化设计
下一篇: Helm Chart 依赖管理全攻略:如何优雅解决复杂应用的组件联动问题
code8
博客等级 码龄17年 65粉丝 1720原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值