news 2026/10/20 18:10:19

51单片机驱动16x16点阵屏:动态扫描与595级联实战详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机驱动16x16点阵屏:动态扫描与595级联实战详解

做51单片机的小项目,点阵屏应用是个绕不开的坎。一块16x16点阵屏,就是由256颗LED组成的“灯阵”,看着不起眼,却能把单片机最核心的三样基本功全串起来:引脚不够时怎么扩展、时序怎么精确控制、图像怎么动态刷新。很多朋友做数码管秒表、呼吸灯都很顺手,一换到点阵屏就懵,最常问的就是:这么多灯,单片机那二十来个脚根本不够插,到底怎么点起来?

这篇文章我就按自己做项目的完整思路来写,从点阵屏为什么必须“扫描”开始,到硬件方案选型、595驱动代码、字模提取、实际调试踩坑,最后再聊聊滚动显示和接传感器扩展的事。如果你正在做课程设计、电子竞赛练手,或者单纯想把51的基础功打扎实,这篇应该能让你少走很多弯路。

1. 先搞懂点阵屏的显示本质:为什么非扫不可

1.1 点阵屏其实就是“行列交叉的灯阵”

一块8x8点阵屏,内部是8根行线和8根列线交叉出来的64个节点,每个节点焊一颗LED。行线和列线从模块两侧引出,一共16个引脚。点亮某个LED的条件很简单:让所在的行和列分别处于正确的电平。比如“行高列低”,那这一颗LED就导通发光;同一行的其他LED因为列没被选中并不亮,同一列的其他LED因为行没被选中也不亮。

这个“行列交叉选点”的结构,是点阵显示器的核心。它最大的意义在于省引脚:64颗LED只需要16条线,256颗LED只需要32条线。如果按一颗LED两条腿来接,16x16就是512条线,别说51单片机,换成上百引脚的芯片也一样头皮发麻。所以矩阵结构不是可选项,而是唯一解。

16x16点阵屏通常由4块8x8模块拼成。拼的时候要注意行列编号的连续性,左边两块、右边两块,行线并在一起,列线按顺序引出。我见过不少人因为拼装时把列线顺序搞反,最后显示出来的汉字左右颠倒,检查半天才发现是硬件接线问题。

1.2 一次只亮一行:动态扫描的由来

既然行列交叉能唯一确定一颗LED,那最直接的想法是:把要亮的LED全部“同时”点亮。但16x16一共256颗LED,假设每颗只需要1mA电流,全亮时就是256mA;如果每颗5mA,全亮就是1.28A。这还只是16x16的小屏,再大一点的32x16、64x32,电流直接奔着好几安培去,普通面包板和杜邦线根本扛不住。

更麻烦的是引脚。要“同时”点亮全部LED,理论上要给每一颗灯都准备独立驱动路径,这跟直接把每条线引出来没什么区别,矩阵结构就失去意义了。所以工程上采用的办法叫“动态扫描”:把一整幅画面拆成一行一行,某一时刻只点亮一行,快速扫完16行,利用人眼的视觉暂留效应,看起来就是一整幅画面常亮。

这个思路在生活里到处都是。电影也是这么干的:一秒放24帧静态画面,看起来就是连续运动。点阵屏的刷新率只要超过人眼能感知的闪烁频率,肉眼就分辨不出“每一行轮流亮”的真相。

扫描频率有个经验值:低于60Hz会觉得闪,80到120Hz比较稳,超过120Hz肉眼已经完全无感。51单片机做16x16点阵屏,跑到100Hz以上很轻松,不用太担心性能不够。

1.3 扫描的具体节奏:选行、送数据、保持、换行

动态扫描看着玄乎,拆开就四步:

  1. 选中第N行,只给这一行通电;
  2. 同时把这一行16列的数据送到列驱动电路;
  3. 保持一小段时间,让这一行LED稳定发光;
  4. 关闭第N行,切换到第N+1行,重复上面的过程。

这里有一个容易被忽略的细节:换行之前先“关掉”当前行,再切换数据。如果不关行直接换数据,上一行的残影还没消失,下一行的数据已经上来了,画面上就会出现重影或者“鬼影”。后面写代码时,专门用OE引脚做“消隐”,就是为了解决这个事。

扫描方向也有讲究。可以“行扫描、列送数据”,也可以“列扫描、行送数据”,本质是对称的。一般项目都选行扫描,因为行数比列数少得多(16行,每行16列),切换次数更少,列数据并行送入的宽度也更合理。选行扫描还有一个好处:行选通信号可以用译码器来生成,比如3根IO线选8行,省引脚。

1.4 为什么必须用“锁存器”而不是直接拉IO

理解了扫描原理,下一个问题就是:16列数据怎么送?如果直接用单片机P0口和P2口,16个引脚还不够,而且P2口还得留着扩展别的功能,再往上做32x16、64x32大屏更是直接没戏。所以得用“串转并”的移位寄存器,把数据一位一位送进去,凑齐一整行后一次性输出。

这里最常用的芯片是74HC595。它的内部结构分两块:一块是移位寄存器,一块是输出锁存器。数据在时钟脉冲作用下一位一位往移位寄存器里挪,但此时输出引脚并不会跟着变;只有当锁存信号到来,移位寄存器里的数据才整体搬到输出锁存器,QA到QH这8个引脚才会更新。

这个“先移位、后锁存”的设计非常关键。如果不锁存,数据在移位过程中会一节一节地从输出脚冒出来,眼睛看到的就是一长串花屏乱闪。锁存器相当于一个“缓存墙”:里面的数据随便怎么折腾,外面的人看不见,等数据排整齐了“啪”一声放行,画面才整体更新。这也是为什么595在点阵屏项目里几乎是标配。

2. 硬件方案怎么选:从“裸IO”到“双595”

2.1 三种常见驱动方案的取舍

做点阵屏驱动,方案五花八门,我列个表对比一下,直观很多:

方案占用IO数最大规模优点缺点
单片机IO直接驱动8~16个8x8勉强,16x16吃力逻辑最简单,不用额外芯片引脚不够、电流不够、扩展性差
列用595 + 行用138译码器6~8个16x16合适引脚省,行选效率高16行要两片138,再大要重新换方案
行列都用595级联4~5个64x32以内都行结构统一、扩展方便、思路通用数据要串行移入,对时序要求略高
专用驱动芯片(MAX7219等)3个大屏更好代码简单、自带扫描学不到扫描原理,工程味太浓

我推荐大家上手直接用“行列都用595级联”这个方案。原因有几个:一是逻辑统一,行数据和列数据都走同一条串行链路,写代码特别顺手;二是扩展大屏时只需要继续级联595,代码框架不用推倒重来;三是我个人觉得,做学习项目最重要的不是把灯点亮,而是把“串转并”和“动态扫描”这两个底层机制吃透,用595正好能同时练到这两点。

2.2 74HC595的引脚与级联方法

74HC595是8位串行输入、并行输出的移位寄存器,常用引脚就几个:

  • DS(14脚):串行数据输入;
  • SH_CP(11脚):移位时钟,上升沿时把DS上的数据移入寄存器;
  • ST_CP(12脚):锁存时钟,上升沿时把移位寄存器内容送到输出锁存器;
  • OE(13脚):输出使能,低电平有效,高电平时输出引脚呈高阻态;
  • Q7'(9脚):级联输出,把第8位数据送出去,接到下一片595的DS。

级联时记住一个顺序:前一片的Q7'接后一片的DS。比如两片595级联,先送后一片的数据(也就是先送高字节),再送前一片的数据(低字节),这样16位列数据就能一次性锁存输出。

我刚开始做的时候总搞混送数顺序,结果就是显示内容左右错位。后来总结成一句话:先送远端的,再送近端的。跟排队进门一个道理,先让后面的人往里走,最后才是门口的人。

2.3 行驱动与列驱动:电流方向和限流电阻

硬件上有一件事必须想清楚:LED的电流从哪里来、到哪里去。以“行阳列阴”的接法为例,行线接LED阳极,列线接LED阴极。点亮条件是“行线为高、列线为低”,电流从行驱动流进LED,再从列驱动流到地。

16x16屏每一行最多同时亮16颗LED,如果每颗LED平均电流5mA,一行就是80mA。这个电流不能全指望595扛,因为595是低压CMOS芯片,单脚输出电流有限,全体引脚同时输出时总电流很容易超限,芯片会发热甚至损坏。所以行选通这一侧一般要加驱动放大,常见做法是用ULN2803这类的达林顿管阵列,或者用PNP三极管做行电源开关。

列这一侧为什么可以交给595呢?因为595是灌电流工作,也就是输出低电平让电流流进芯片内部。单脚灌电流能力比拉电流强不少,但也不能无限灌,16列同时亮时总电流还是要注意。稳妥的做法是每列串联一个限流电阻,把单颗LED电流控制在3到5mA。

限流电阻的计算要结合动态扫描的占空比来算。静态点亮时,红色LED压降大约1.8到2V,5V供电,如果目标电流10mA,电阻就是(5-2)/0.01=300Ω。动态扫描时每行只有1/16的时间在亮,平均亮度只有峰值的1/16,所以要让峰值电流稍微大一点来补偿平均亮度。经验值一般取峰值20mA左右,电阻用(5-2)/0.02=150Ω,实际我用220Ω比较多,亮度足够而且对LED寿命友好。

还有一个小细节:限流电阻尽量放在“列”这一侧,也就是LED阴极到地的那一路。为什么?如果放在行侧,同一个行电阻要承担这一行所有LED的电流,亮灯数量变化时行电压会被拉偏,画面亮度就不均匀。而放在列侧,每一列电流被独立限定,和另外几颗灯亮不亮没关系。

3. 代码实现:从显示缓冲区到一帧画面

3.1 程序框架:字模、底层驱动、扫描循环

写代码之前先把程序拆成三层,这样后面调试会非常有条理:

第一层是字模数据。16x16点阵的汉字,每个字需要32字节,每一行2个字节,左边8位对应左半屏,右边8位对应右半屏。字模本质就是“把画面变成0和1”,1表示这个点要亮,0表示不亮。

第二层是595的底层驱动。这一层只干一件事:把一串二进制数据移位到595里,再锁存输出。不关心这些数据代表什么画面。

第三层是扫描主循环。这一层负责“选第几行、送这一行的16位列数据、保持一小段时间、切下一行”。

分层的好处是,改画面只需要动字模数组,换屏幕规模只需要改底层驱动,扫描逻辑几乎不用动。很多初学者一上来就把所有代码揉在一起,后续加功能就痛苦了。

先看引脚定义和底层驱动代码:

sbit ROW_DAT = P1^0; // 行595数据 sbit COL_DAT = P1^1; // 列595数据 sbit SH_CLK = P1^2; // 共用移位时钟 sbit LATCH = P1^3; // 共用锁存时钟 sbit OE = P1^4; // 输出使能,低有效,接PWM可调亮度 // 同时向行、列595移入一个bit void ShiftOneBit(bit rowbit, bit colbit) { ROW_DAT = rowbit; COL_DAT = colbit; SH_CLK = 0; SH_CLK = 1; // 上升沿,行和列同步移入 }

然后是一整帧的写入函数。这里用的是“行595 + 列595都级联成16位”的结构,所以每次写一行,要移入16个bit的行掩码和16个bit的列数据:

// 写一行:rowmask里只有1位为1,对应要选通的行;colmask是16位列数据 void WriteOneRow(unsigned int rowmask, unsigned int colmask) { unsigned char i; OE = 1; // 先消隐,防止切换行时出现鬼影 for (i = 0; i < 16; i++) { ShiftOneBit((bit)(rowmask & 0x8000), (bit)(colmask & 0x8000)); rowmask <<= 1; colmask <<= 1; } LATCH = 0; LATCH = 1; // 锁存:行和列数据同时输出 OE = 0; // 打开输出,这一行开始发光 }

这段代码里最核心的点有两个。一是“先消隐、后换行”,OE拉高期间输出引脚全部高阻,此时移位和锁存都不会在屏上产生乱码,等数据稳定后再拉低OE,干净利落。二是行和列共用同一个移位时钟和锁存时钟,每次脉冲同时移入一个行bit和一个列bit,16个脉冲后两边寄存器都满了,一次锁存搞定。

3.2 扫描主循环与字模数据

字模数组用一维数组连续存放,每个汉字占32字节。扫描的时候按行号取数据:

unsigned char code Font[32] = { // 这里放16x16汉字的32字节字模 // 假设取模结果是:1表示亮点 }; void DisplayFrame(unsigned char *font) { unsigned char row; unsigned int rowmask; unsigned int colmask; for (row = 0; row < 16; row++) { rowmask = 1 << row; // 第row行选通,高电平有效 // 取当前行的两个字节,左字节在高8位,右字节在低8位 colmask = ((unsigned int)font[row * 2] << 8) | font[row * 2 + 1]; colmask = ~colmask; // 列低电平点亮,所以取反 WriteOneRow(rowmask, colmask); DelayShort(); // 行驻留时间,约0.2到0.5ms } } void main() { while (1) { DisplayFrame(Font); } }

这里必须解释一下“取反”。前面接法是“行阳列阴”,字模里1表示亮点,硬件要求列线输出低电平让LED导通,所以列数据在送给595之前要按位取反,0变成1,1变成0。如果你手里的模块是“行阴列阳”,那取反的位置就换到行掩码上,或者直接不取反,改成“列高电平点亮”。这个极性关系是很多新手最容易卡住的地方,建议画个电路图捋一遍再写代码。

3.3 刷新率估算:为什么120Hz不难

刷新率是最能给人信心的一项指标。12MHz晶振的51单片机,机器周期1us,一次移位操作大概几个机器周期。16bit数据加锁存操作,一行大约几十us。再加上行驻留时间,假设每行0.5ms,16行就是8ms,算下来刷新率约125Hz。这个频率已经超过人眼感知上限,不会闪。

如果发现画面闪,优先检查两件事:行驻留时间是不是太长,以及有没有做消隐。把驻留时间从1ms降到0.3ms,刷新率能翻三倍。但也不能无脑降,驻留时间太短,LED平均亮度会不够,画面发暗。实际调试时,我习惯把驻留时间做成一个可调参数,一边改一边看亮度,找到一个“不闪且够亮”的平衡点。

3.4 用定时器中断做刷新更稳

主循环里直接写扫描的缺点是:一旦主循环里有别的耗时操作,比如温度传感器转换、按键消抖、串口发送,扫描节奏就会被打断,画面开始闪。成熟的做法是把扫描搬进定时器中断,比如每0.5ms进一次中断,每次刷一行,16次中断刷完一帧。主循环只管改字模缓冲区的数据,中断管刷新,两不相干。

这个思路我从点阵屏开始养成,后来做OLED、LCD驱动,全是一套逻辑。点阵屏项目看着简单,却天然逼着你养成“刷新和业务分离”的好习惯。

4. 实操:从模块接线到第一帧画面

4.1 拿到点阵屏第一件事:测极性

很多商家卖的8x8点阵模块压根不标引脚顺序,别指望规格书。拿到手先做一件事:万用表二极管档,红表笔固定一个引脚,黑表笔轮流扫其他引脚,看哪颗LED会亮。亮的那次,红表笔所在极大概率是某一行或某一列的正极公共端,再逐个引脚试,把16个引脚对应的行列关系全测出来。

这个方法听起来慢,实际十几分钟就能搞定。我用标签纸把每个引脚编号贴好,画一张“行线-列线对应表”,后续接线、调试全靠这张表。跳过这步直接猜,后面要是显示乱码,你会在硬件和软件之间来回折腾一整天。

4.2 拼16x16模块的行列编号

4块8x8拼成16x16,我习惯按“左上、右上、左下、右下”的方式摆放。行线方面,左上的行1到行8、左下的行1到行8,合并成16行;列线方面,左上的列1到列8接列1到列8,右上的列1到列8接列9到列16。这样拼出来的坐标,和字模数组里的字节顺序是对应的,调试时脑内映射最直观。

还有一种常见拼法是“左右并排”,也就是8x32。这种适合做跑马灯和长幅滚动字幕,但显示16x16汉字时坐标换算会绕一点。新手还是从2x2拼接开始。

4.3 基础测试:全亮、单点、对角线

代码写完之后别直接上汉字,先跑三个基础测试。

第一个是全亮测试:把所有行列全部扫描一遍,理论上16x16所有LED都该亮。通过这个测试确认没有坏点、没有接错线。第二是单点测试:固定点亮某一个坐标,比如(第3行,第5列),确认软件里的坐标和屏幕的实际位置一致。第三是对角线测试:让左上到右下的对角线逐一点亮,看位序是否反向。

这三个测试全部通过之后,再显示字模,基本一次成功。如果直接跳过这些测试去调汉字,一旦出问题,你根本不知道是接线错了、坐标反了、还是字模取错了,排查范围巨大。

4.4 字模提取与字节序调整

16x16像素的汉字,取模软件都能生成32字节数据。取模时要特别注意设置:逐行式、横向取模、每行两个字节。生成的数据一般是1表示亮点,偶尔软件会默认“逆向取模”,也就是把字节内的位顺序反过来,这时显示出来就是左右镜像,需要把每个字节按位反转一下。

我不建议盲目相信取模软件的默认设置。取模完成后先在纸上画一个简单的测试图形,比如右上角一个方块,然后把取模结果和期望输出对照一遍,再烧到屏幕上看。先确认“坐标映射”正确,再放大字模数据,效率高得多。

仿真软件做点阵屏逻辑验证也很方便。595模型、点阵屏模型都挺成熟,可以先在仿真里跑通扫描和字模,再上真实硬件。不过仿真里看到的LED亮度只是示意,真实的亮度和电流参数还是得以实物为准。

5. 常见问题排查与避坑实录

5.1 故障速查表

我在点阵屏项目里遇到过的问题,整理成一张表,基本覆盖80%的坑:

现象可能原因解决办法
完全不亮595的OE悬空或接高电平OE必须接低电平或由MCU拉低
画面闪烁行驻留时间太长、刷新率低缩短驻留时间到0.3ms左右
只有一行亮行掩码移位方向反了检查移位方向和位顺序
显示内容整体错位595级联送数顺序反了先送远端595,后送近端595
左右镜像字模字节内位序反了反转每个字节的位顺序
上下颠倒行掩码和行号对应反了调整行选通顺序
重影/鬼影换行前没有消隐输出数据前拉高OE
亮度不均匀行驻留时间不一致统一每行的延时
某一行特别暗行驱动能力不足换ULN2803或加大行电源
屏幕上有雪花点锁存时序不对先移位彻底,再锁存输出

5.2 三个容易被忽视的隐性坑

第一坑:595电源旁没有去耦电容。动态扫描的瞬时电流很大,电源线上噪声会把数据时序搞乱。我的习惯是每片595的VCC和GND之间放一个104电容,尽量靠近芯片引脚。面包板上原件稀稀拉拉摆着无所谓,但线路一长、电流一起,这个电容能救你很多次。

第二坑:把595的OE直接接地,不做软件消隐。这样硬件简单了,但每次切换行时,旧数据会和新数据短暂重叠,晚上关灯看特别明显。把OE接到一个IO口上,哪怕不用PWM,单纯用来“关输出-移位-锁存-开输出”,画面干净程度完全不一样。

第三坑:扫描延时用的是随便一个Delay函数,而且特别不均匀。如果每次延时长短不一,行与行之间的亮度就差很多。尤其注意进入中断、读传感器这类代码在扫描循环里出现时,某一行的驻留时间会被拉得特别长,那一行就会特别亮。这也是为什么我推荐用定时器中断固定扫描节奏。

6. 往更大屏和更多玩法延伸

6.1 滚动显示和动画的思路

16x16屏显示一个字之后,下一步肯定想做滚动字幕。滚动最笨的办法是每帧重新搬数据:把字模数组整体左移一位,再复制到显示缓冲区。16x16这么小,拷贝256字节的代价几乎可以忽略,所以这种做法完全可行。

但如果你想跑32x16甚至64x32的大屏,搬移数据就会拖慢刷新。更高效的做法是“加一个偏移量”,显示位置由偏移量决定,扫描时从字模数组里按偏移量取数据,而不是把数据移到固定位置。比如左移一列,不是搬数据,而是让“显示窗口”左移一位,每次扫描时读取的字节起始位置跟着offset走。

这个“窗口偏移”的思想在嵌入式显示里特别常用,OLED滚动、LCD翻页、LED条屏跑马灯,核心都是这一套。点阵屏玩明白了,这些项目上手会非常快。

6.2 接按键、传感器和红外遥控

点阵屏显示内容只是第一步,加上交互才好玩。比如按键切屏:两个按键做上一页下一页;温度传感器数据显示当前温度;红外遥控控制动画播放。

做这些扩展时有个原则:传感器延时操作不能阻塞中断里的扫描。我那会儿犯过的错是,主循环里直接调用温度传感器读取函数,等待转换的几百毫秒期间,扫描循环被卡住,画面直接闪成雪花。解决办法就是用中断刷新,主循环里只管改缓冲区数据,把耗时操作放到主循环里慢慢做。

6.3 我的体会

点阵屏这个项目,工程量不大,但把嵌入式显示领域的核心机制都串起来了。我在实际调试中体会最深的一点是:屏幕上的任何异常,永远先怀疑时序,再怀疑接线,最后才怀疑芯片。很多人一上来就怀疑“是不是芯片坏了”,结果换了好几片595问题还在,最后发现是消隐没做或者锁存信号反了。

如果真的想把这个项目吃透,建议做两件事:一是亲手把8x8模块的行列关系测一遍,二是把595的移位、锁存时序用示波器或者逻辑分析仪看一遍。看完这两个东西,你再看任何点阵屏、OLED、LCD的驱动代码,会发现全是同一套道理。这块屏做完,后面很多路就顺了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 18:08:42

GEE位运算实战:遥感QA波段云掩膜高效提取指南

1. 项目概述&#xff1a;位运算不是“位”于天边的玄学&#xff0c;而是GEE里最常被低估的像素级手术刀在Google Earth Engine&#xff08;GEE&#xff09;里写过image.gt(0).selfMask()的人不少&#xff0c;但真正把image.select(B5).and(image.select(B4))当日常操作来用的&a…

作者头像 李华
网站建设 2026/10/20 18:08:23

开源Agent五层地图:从模型底座到应用交互的完整解剖

2026年刚开工那阵儿&#xff0c;我给自己定了一个还算疯狂的目标&#xff1a;把过去两年冒出来的Agent开源项目尽可能翻一遍。代码托管平台上每天都会多出几十个新仓库&#xff0c;标题带着agent、bot、copilot、assistant的字样&#xff0c;单看简介个个都能颠覆世界。但等我真…

作者头像 李华
网站建设 2026/10/20 18:08:02

危化园区高风险作业三维安全半径动态校验系统实战解析

在危化品园区干过安全管理的人都有体会&#xff0c;高风险作业的管控&#xff0c;最难的不是制度&#xff0c;而是“摸不着”的风险边界。动火、受限空间、高处作业这些场景&#xff0c;过去我们靠什么判断风险范围&#xff1f;监护人站在警戒线外&#xff0c;用肉眼估个大概&a…

作者头像 李华
网站建设 2026/10/20 18:07:41

专科生论文降AIGC率:千笔与锐智AI实测对比

又到了毕业季和期末季&#xff0c;后台私信里问得最多的就是两类降AIGC软件怎么选。专科生的论文要求虽然不像本科和研究生那么卷&#xff0c;但学校对AIGC检测率的红线一点没松&#xff0c;不少同学拿着千笔降AIGC助手和锐智AI的截图来问我到底哪个更稳。这两款我都实际掏钱测…

作者头像 李华
网站建设 2026/10/20 18:07:18

Locust压测实战:命令行参数详解与分布式压测高阶指南

上周给一个内部服务做上线前的容量评估&#xff0c;我直接在命令行敲了几条 locust 命令&#xff0c;几分钟后拿到了 P95、RPS 和失败率数据。同事问我为什么不用现成的压测平台&#xff0c;我说&#xff1a;因为整个压测链路就藏在 Python 脚本里&#xff0c;环境有 Python 就…

作者头像 李华
网站建设 2026/10/20 18:05:00

AI可复现性挑战:从随机种子到环境锁定的工程实践

在深度学习圈子里&#xff0c;可复现性这几年已经从一个“论文附录里带个随机种子就行”的小事&#xff0c;变成了一整类让人头疼的工程挑战。很多项目跑第一遍时效果很好&#xff0c;换一台机器、换一个版本、甚至只是隔了一周再跑&#xff0c;指标就开始漂。有人把这类问题统…

作者头像 李华