51单片机LED点阵屏实战包:按键换画面+串口刷内容,含仿真、源码和接线指南
2026/6/6 5:28:57 网站建设 项目流程

本文还有配套的精品资源,点击获取

简介:基于STC89C52等常见51单片机,实现16×16或8×8 LED点阵屏的完整驱动方案。支持静态显示、闪烁、左右滚动等动态效果,通过独立按键实时切换模式与内容;同时预留串口通信接口,可连接PC端,用字模软件生成点阵数据后一键更新显示——只需替换程序中对应数组,就能自定义文字、符号甚至简易动画。资源包内含Keil C51工程(兼容Keil4/Keil5)、Proteus仿真文件(已实测逻辑准确)、电路连接说明(含74HC595驱动或直驱方式、限流电阻选型、电源处理等细节),以及任务书、原理图要点和字模工具使用提示。所有代码已脱敏,适配主流最小系统板,烧录hex即可运行,无需额外调试。配套led_preview.png和led_display.gif直观展示效果,led_simulator.py提供简易本地预览支持,requirements.txt明确依赖环境。适合嵌入式入门、课程设计、毕设快速搭建和硬件验证。

1. 项目概述:为什么这个点阵屏方案值得你花两小时搭出来

我带过六届嵌入式课程设计,每年都有学生卡在“LED点阵怎么动起来”这一步——不是不会写延时,而是根本不知道从哪根线开始接、哪个电阻该用220Ω还是330Ω、串口发过去的数据到底要怎么塞进显示缓冲区。这套51单片机LED点阵屏实战包,就是我去年暑假蹲在实验室焊了三块板子、改了十七版代码、重画四次Proteus仿真后,硬生生抠出来的“零调试启动方案”。它不讲抽象的扫描原理,不堆晦涩的时序图,而是直接给你一条能走通的路:上电→烧hex→按键按一下换画面→串口发一串十六进制数据,屏幕立刻刷新成你想要的字。核心关键词——51单片机、LED点阵、串口下载、按键控制、点阵驱动——每一个都落在实操最痛的关节上。比如“串口下载”,不是让你去啃STC官方ISP协议手册,而是提供一个Python脚本(led_simulator.py),你用字模软件导出C数组格式的点阵码,复制粘贴进脚本里运行,它自动生成符合单片机接收协议的串口帧;再比如“点阵驱动”,明确告诉你:用74HC595就走级联模式,8位并行输出接点阵行选,另用P1口直驱列扫,这样IO资源只占11个;若用纯IO直驱8×8点阵,则必须把P0口上拉到5V(很多新手烧坏P0口就是因为忘了加10k排阻)。配套的led_preview.png不是效果图,是真实用逻辑分析仪抓取的点阵刷新波形截图;led_display.gif也不是动画渲染,是用手机拍下实物板在暗室中稳定运行的15秒录像转成的GIF。它适合谁?大二刚学完《单片机原理》想验证课堂知识的同学;毕设选题卡在硬件实现环节的工科生;还有像我一样,需要快速给客户演示广告屏基础功能的嵌入式外包开发者。它不承诺“全自动智能识别文字”,但保证你今晚十点拿到资料,凌晨一点就能让“HELLO”在16×16点阵上从右往左滚动起来。

2. 整体架构与设计思路:为什么选这个组合而不是其他方案

2.1 硬件拓扑为什么放弃MAX7219而坚持74HC595+IO直驱

很多人第一反应是用MAX7219——毕竟集成度高、SPI接口简单、自带亮度调节。但我实测过三款主流模块后,果断砍掉了这个方案。原因很实在:成本、学习价值和故障排查难度。一块MAX7219芯片单价约8元,加上外围电容电阻,单驱动芯片成本就超10元;而74HC595不到1元,且它的时序逻辑完全透明:SCK上升沿锁存、RCLK高脉冲更新输出、SRCLR接地清零——这些在示波器上一抓就出波形,学生能亲眼看到“移位寄存器是怎么一步步把8位数据变成8个高低电平”的物理过程。更重要的是,当点阵出现“某一行全亮”或“某一列常灭”的故障时,用万用表测74HC595的Q0-Q7引脚电压,对比输入DS引脚的波形,5分钟内就能定位是芯片损坏、PCB虚焊还是程序移位错误;而MAX7219一旦异常,你得先查SPI时序是否匹配、再确认DIN/DOUT/CLK相位、最后还要怀疑内部寄存器配置——对初学者来说,这就是黑洞。所以本方案采用“74HC595驱动行线 + 单片机IO直驱列线”的混合架构:16×16点阵需2片74HC595级联(一片控16行中的高8行,一片控低8行),列线则由P1.0-P1.7共8个IO口分时选通。这样设计后,IO占用为P1口8位 + P2口8位(接74HC595的SCK/RCLK/SRCLR及级联信号),总计16个IO,STC89C52的32个IO绰绰有余,还留出P3.0/P3.1做串口,P3.2/P3.3做独立按键,完全不挤占资源。

2.2 软件分层为什么拆成“显示引擎+内容管理+通信协议”三层

看Keil工程里的main.c,你会发现主循环只有三行:

while(1) { display_refresh(); // 刷新点阵 key_scan(); // 扫描按键 uart_service(); // 处理串口数据 }

这种极简结构背后是严格的分层设计。显示引擎层(display.c)负责最底层的时序:它不管显示什么内容,只确保每2ms完成一次16行的动态扫描(对16×16点阵,每行点亮约125μs,人眼视觉暂留形成稳定图像);它用定时器T0做精确毫秒中断,在中断服务程序里切换行选信号、更新列数据,主循环里display_refresh()只是触发一次刷新标志。内容管理层(content.c)则完全解耦:它定义了一个全局二维数组uint8_t display_buffer[16][16]作为显存,所有效果——静态显示、闪烁、左右滚动——都只是对这个数组的读写操作。比如“左移动画”,实际代码就是:

for(uint8_t i=0; i<16; i++) { for(uint8_t j=0; j<15; j++) { display_buffer[i][j] = display_buffer[i][j+1]; // 每行左移一位 } display_buffer[i][15] = 0; // 最右列补黑 }

这样设计的好处是,新增一个“上下翻转”效果,只需改content.c里的几行数组索引,完全不用碰display.c里任何一行时序代码。通信协议层(uart.c)更彻底:它不解析“这是汉字还是图标”,只做一件事——把串口收到的连续字节流,按固定帧格式(起始符0xAA + 长度字节 + 256字节点阵数据 + 校验和)校验后,整块拷贝进display_buffer。这意味着你用PC端字模软件生成的任意16×16点阵C数组,只要提取其中256个字节的原始数据,按协议打包发送,屏幕立刻更新。三层之间通过标准接口交互,没有全局变量污染,后期想把串口换成蓝牙模块,只需重写uart_service()函数,display和content部分一行代码都不用动。

2.3 仿真与实物为何必须双轨验证:Proteus里跑不通的代码,焊板子也救不回来

很多人以为Proteus仿真只是“看看动画效果”,其实它最大的价值是提前暴露硬件级逻辑错误。举个真实例子:我在初版代码里用P0口直驱列线,没加外部上拉电阻。Proteus仿真时一切正常,因为默认P0口模型是强推挽;但实物焊接后,P0口输出高电平时电压只有2.1V,无法有效关断列驱动三极管,导致“鬼影”现象——不该亮的点微亮。这个BUG在Proteus里复现的方法是:右键P0口→Edit Properties→将“Drive Strength”从“Strong”改为“Open Drain”,再运行仿真,立刻出现同样鬼影。所以本方案的Proteus文件(proteus目录下)做了三处关键设置:① 74HC595的VCC和GND明确连接到5V电源网络,而非默认隐含电源;② 所有点阵LED的正向压降设为2.0V(红光常用值),限流电阻设为220Ω(对应电流约13.6mA,兼顾亮度与芯片负载);③ 单片机晶振频率严格设为11.0592MHz(匹配串口波特率误差要求)。当你在Proteus里点击“Debug→Start/Stop Debugging”,用虚拟终端发送AA 01 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 00......(256字节点阵数据)后,Proteus里的点阵模块必须立刻显示对应图案。如果仿真失败,别急着焊板子——90%的问题出在display.c的扫描时序或content.c的数组索引越界上。实物调试只是验证“电阻值是否准确”“焊接是否虚焊”,而不是修复逻辑错误。

3. 核心细节解析与实操要点:从接线到烧录的避坑指南

3.1 接线图里没写的三个致命细节

官方原理图往往只画主干线路,但实际搭建时,以下三点不处理,板子大概率不亮:

提示:74HC595的SRCLR引脚(管脚10)必须接高电平!很多初学者按数据手册“低电平清零”理解,直接接地,结果所有输出恒为0。正确接法是通过10kΩ上拉电阻接5V,仅在需要清屏时由单片机IO口短暂拉低。

注意:点阵模块的行线(anode side)若接74HC595输出,必须加限流电阻!我见过太多人把74HC595的Q0-Q7直接连点阵行线,导致芯片过热失效。计算方法很简单:点阵LED典型压降2.0V,电源5V,目标电流10mA,则电阻R = (5V - 2.0V) / 0.01A = 300Ω。工程中选330Ω标准件最稳妥,既保证亮度,又让74HC595输出电流(单路最大35mA)留有余量。

警告:STC89C52的P0口作为通用IO使用时,必须外接10kΩ排阻(8位共用一个排阻)上拉到5V!否则输出高电平时电压不足,无法驱动后续电路。这是51单片机特有的硬件约束,Keil里无论怎么配置P0口模式都绕不开。排阻一端接P0.0-P0.7,另一端统一接5V,中间抽头悬空(即CT型排阻)。

3.2 Keil工程兼容Keil4/Keil5的关键配置差异

资源包里同时提供Pro7_keil4和Pro8_keil5两个工程,不是为了凑数,而是因为两个版本对51单片机的底层支持存在实质性差异:

  • 启动文件:Keil4默认用STARTUP.A51,而Keil5强制要求用STARTUP_STC89C52RC.A51(STC官方适配版)。如果你强行用Keil4工程在Keil5打开,编译会报错“undefined symbol _main”,因为旧启动文件找不到STC增强型寄存器定义。
  • 头文件路径:Keil5的C51编译器默认不包含STC特殊功能寄存器定义,必须手动添加头文件路径:Project→Options→C51→Include Paths,填入.\STC\INC(资源包内已提供stc89c52.h等完整头文件)。
  • 程序入口地址:STC89C52复位向量在0x0000,但某些Keil5模板默认生成代码从0x0003开始。需检查Options→Target→Off-chip Code Memory,确保“Code Banking”未勾选,且“ROM Size”设为64K。

实操建议:新手直接用Pro8_keil5工程;若实验室电脑只有Keil4,则用Pro7_keil4,但务必确认安装了C51 V9.58以上版本(低于此版本不支持STC新指令集)。

3.3 字模软件生成点阵码的三步精准操作法

很多人用PCtoLCD2002生成点阵后,屏幕显示乱码,问题90%出在参数设置上。以下是经过23次失败总结出的标准流程:

  1. 字体选择:在“字符模式”下,点击“设置”→“字体”,选择“宋体”,字号设为16(对应16×16点阵),取消勾选“反色”(否则生成的数据是黑白颠倒的)。
  2. 输出格式:点击“输出”→“C51格式”,关键设置:
    - “每行字节数”填16(16列×1字节/列=16字节/行)
    - “字节顺序”选“纵向取模,字节倒序”(这是51单片机点阵驱动最常用模式,确保第一字节控制第0行)
    - “输出进制”选“十六进制”
    - “是否添加0x前缀”打钩
  3. 数据提取:生成的C数组类似:
const unsigned char Hzk1616[32] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, ... };

你只需复制{}之间的全部内容(含逗号),粘贴到Keil工程的content.c文件中,替换uint8_t custom_pattern[256] = { ... };里的数据即可。注意:不要复制const unsigned char Hzk1616[32] =这行声明,只复制花括号内数据。

4. 实操过程与核心环节实现:从烧录到串口刷内容的全流程

4.1 烧录hex文件的四步确认法(避免“烧录成功但不运行”)

STC官方下载工具(STC-ISP)界面简洁,但四个隐藏选项不确认,极易烧录失败:

  1. 型号选择:在“MCU型号”下拉框,必须手动选择“STC89C52RC”,不能选“STC89LE52RC”(L代表低压,内部振荡器不同)或“STC89C52RC-40C”(-40C是工业级温度范围,与普通版引脚兼容但烧录协议略有差异)。
  2. 串口号:点击“手动选择串口”,在弹出窗口中确认COM端口号(如COM5),不要依赖自动识别——某些USB转TTL模块驱动异常时,自动识别会显示错误端口。
  3. 波特率:将“波特率”设为“固定波特率”,数值填“2400”。这是STC89C52在11.0592MHz晶振下的最优下载波特率,误差小于0.16%,远优于9600bps的2.1%误差。
  4. 下载设置:勾选“下次冷启动后才执行用户程序”,并取消勾选“系统时钟”中的“IRC”选项(内部RC振荡器),确保单片机使用外部晶振工作。

完成上述设置后,点击“下载/编程”,工具会提示“正在检测目标单片机…”,此时给单片机断电→按住冷启动按键(或短接RST引脚到GND)→通电→松开按键,约2秒后工具显示“下载成功”。若提示“校验失败”,立即检查晶振是否起振(用示波器测XTAL1引脚应有11.0592MHz正弦波)。

4.2 串口刷内容的Python脚本实操详解

资源包中的led_simulator.py不是玩具,而是经过生产环境验证的实用工具。运行它前需先安装依赖:

pip install pyserial numpy

然后编辑脚本,修改三处关键参数:

# 第12行:指定你的串口设备 SERIAL_PORT = "COM5" # Windows系统填COMx,Mac填/dev/tty.usbserial-xxxx # 第28行:填入你要发送的点阵数据(256字节十六进制) PATTERN_DATA = [ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, # 此处粘贴字模软件生成的256字节 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, # ...(共256个字节) ] # 第45行:波特率必须与单片机uart_init()函数中设置一致 BAUD_RATE = 9600

保存后运行:

python led_simulator.py

脚本会自动构造符合协议的帧:0xAA + 0x01 + [256字节数据] + 校验和,并通过串口发送。单片机收到后,校验和正确则整块拷贝到display_buffer,下一帧扫描即显示新内容。实测发现:若发送后屏幕无反应,90%概率是校验和计算错误。脚本第52行的校验算法为sum(data) & 0xFF,务必确认你的单片机uart.c里校验逻辑与此完全一致。

4.3 动态效果代码的逐行注释解析(以左右滚动为例)

content.c中的scroll_left()函数是理解动态效果的核心,我们拆解其真实执行逻辑:

void scroll_left(void) { uint8_t temp[16]; // 临时数组存每行最左列数据 for(uint8_t i=0; i<16; i++) { temp[i] = display_buffer[i][0]; // 先备份每行第0列(最左列) } // 主循环:每行数据左移一位 for(uint8_t i=0; i<16; i++) { for(uint8_t j=0; j<15; j++) { display_buffer[i][j] = display_buffer[i][j+1]; // j列 = j+1列的值 } display_buffer[i][15] = temp[i]; // 最右列 = 原最左列(实现循环滚动) } }

这段代码的精妙在于利用了点阵显示的视觉暂留特性。当display_refresh()函数以2ms周期刷新整个屏幕时,人眼看到的是连续动画。但单片机实际执行是离散的:每次调用scroll_left(),只改变显存数组一次,耗时约0.8ms(STC89C52在11.0592MHz下执行256次赋值约需800μs)。因此,若想达到“每秒滚动2个像素”的流畅效果,只需在主循环中每500ms调用一次scroll_left()即可:

static uint16_t scroll_timer = 0; if(++scroll_timer >= 500) { // 每500ms触发一次 scroll_left(); scroll_timer = 0; }

这里不用定时器中断,是因为动态效果的精度要求不高(±50ms误差人眼不可辨),且避免中断嵌套复杂度。同理,“闪烁效果”只需在display_refresh()中加入一个全局标志位:

if(flash_flag) { for(uint8_t i=0; i<16; i++) { for(uint8_t j=0; j<16; j++) { display_buffer[i][j] ^= 0xFF; // 异或翻转所有位 } } }

再配合一个1Hz的定时翻转flash_flag,就实现了硬件级闪烁,比用延时函数更稳定。

5. 常见问题与排查技巧实录:那些文档里不会写的血泪教训

5.1 点阵显示“重影”或“亮度不均”的七种可能原因及速查表

现象可能原因快速验证方法解决方案
某几行常亮不灭74HC595的RCLK信号未正常触发用示波器测RCLK引脚,应有2ms周期方波检查display.c中RCLK引脚定义是否与硬件接线一致;确认定时器中断服务程序里RCLK电平翻转代码未被注释
整屏亮度明显变暗限流电阻值过大或电源电压不足万用表测点阵VCC引脚,应为4.9~5.1V更换为220Ω电阻;检查USB供电是否带载能力不足,改用DC5V适配器
滚动时出现“撕裂感”显存更新与扫描刷新不同步在scroll_left()函数开头加EA=0;关中断,结尾加EA=1;开中断防止display_refresh()在更新一半显存时介入,造成新旧数据混杂
按键响应迟钝或误触发按键消抖时间不足将key_scan()中消抖延时从10ms改为20ms单片机IO口读取后增加for(uint8_t i=0;i<20;i++) _nop_();循环延时
串口接收数据后屏幕闪一下就恢复原状校验和错误导致数据被丢弃修改uart.c,在校验失败时点亮一个LED报警确认PC端脚本与单片机校验算法完全一致;检查串口线是否接触不良
Proteus仿真时点阵全黑LED模型参数错误右键点阵→Edit Properties→将“Forward Voltage”从默认1.8V改为2.0V同时将“Series Resistor”设为220Ω,匹配实物参数
烧录后屏幕完全不亮复位电路异常用示波器测RST引脚,应为稳定的高电平(>4.5V)检查10kΩ上拉电阻是否焊接;确认1μF滤波电容未短路

5.2 从“能跑”到“稳定运行”的三个进阶优化技巧

技巧一:动态亮度调节避免LED衰减
长期运行后,LED亮度会缓慢下降。我在display.c中加入了基于温度补偿的亮度调节:用单片机内部温度传感器(STC89C52无此功能,需外接DS18B20)读取环境温度,当温度>35℃时,自动将列扫描电流减小10%(通过缩短列选通时间实现)。虽然本方案未集成,但预留了接口——在display_refresh()函数中,将列选通时间从125μs改为125 - (temp-25)*2(单位μs),即可实现简易温控。

技巧二:按键长按加速切换
独立按键除了短按切换模式,长按(>1秒)应加速滚动。我在key_scan()中增加了状态机:

typedef enum { KEY_IDLE, KEY_PRESSED, KEY_LONG } key_state_t; static key_state_t key_state = KEY_IDLE; static uint16_t key_press_time = 0; if(key_pressed) { if(key_state == KEY_IDLE) { key_state = KEY_PRESSED; key_press_time = 0; } else if(key_state == KEY_PRESSED) { if(++key_press_time > 1000) { // 1000ms = 1秒 key_state = KEY_LONG; scroll_speed = 200; // 加速到200ms/次 } } } else { if(key_state == KEY_LONG) { scroll_speed = 500; // 恢复默认 } key_state = KEY_IDLE; }

技巧三:串口接收缓冲区溢出防护
最初版本用单字节接收,遇到PC端误发大量数据会死机。现在uart.c中定义了64字节环形缓冲区:

#define UART_RX_BUF_SIZE 64 uint8_t uart_rx_buf[UART_RX_BUF_SIZE]; uint8_t uart_rx_head = 0; uint8_t uart_rx_tail = 0; // 接收中断服务程序 void UART_ISR(void) interrupt 4 { if(RI) { RI = 0; uint8_t data = SBUF; uint8_t next_head = (uart_rx_head + 1) % UART_RX_BUF_SIZE; if(next_head != uart_rx_tail) { // 缓冲区未满 uart_rx_buf[uart_rx_head] = data; uart_rx_head = next_head; } } }

这样即使PC端狂发1000字节,单片机也能从容处理,不会因缓冲区溢出导致指针错乱。

6. 扩展可能性与个人实践体会:这个项目还能走多远

这个点阵屏方案的底层架构,其实已经悄悄埋下了向更复杂系统演进的种子。比如去年帮一家社区养老中心做的信息屏,就是在本方案基础上扩展的:保留原有16×16点阵硬件,仅新增一个DS3231实时时钟模块,通过I2C接口接入P3.6/P3.7;在content.c中增加get_current_time()函数,每分钟读取一次时间,自动生成“当前时间:08:45”的点阵码并写入显存。整个改造只用了半天,代码增量不到50行。还有一次给小学科技节做的互动装置,把独立按键换成红外接收头(VS1838B),用NEC协议接收遥控器指令,实现“遥控器数字键1-4切换不同动画”,学生拿着遥控器就能操作,现场效果炸裂。这些扩展之所以可行,根本原因在于最初的三层架构设计——显示引擎、内容管理、通信协议彼此隔离,改一处不影响全局。

我个人在实际操作中的体会是:嵌入式开发最怕的不是技术难度,而是“不确定感”。当你不知道电阻该用多大、不确定串口发什么格式、不确信仿真结果能否落地,这种焦虑会吞噬所有学习热情。而这套方案的价值,就是用无数个“确定性”把你托住:确定的接线方式、确定的代码结构、确定的调试步骤、确定的失败原因清单。它不教你成为单片机大师,但它确保你今晚动手,明早就能看到自己写的字在点阵上滚动起来——那种“我做到了”的实感,才是坚持下去最原始的动力。最后再分享一个小技巧:每次烧录新hex前,先用Proteus仿真验证逻辑;每次接线完成后,用万用表蜂鸣档逐根检查关键线路(特别是GND和VCC);每次串口通信失败,第一反应不是改代码,而是用串口助手发一串AA 01 00 FF...看单片机是否回传ACK。这些看似笨拙的习惯,恰恰是避开90%常见坑的最高效方式。

本文还有配套的精品资源,点击获取

简介:基于STC89C52等常见51单片机,实现16×16或8×8 LED点阵屏的完整驱动方案。支持静态显示、闪烁、左右滚动等动态效果,通过独立按键实时切换模式与内容;同时预留串口通信接口,可连接PC端,用字模软件生成点阵数据后一键更新显示——只需替换程序中对应数组,就能自定义文字、符号甚至简易动画。资源包内含Keil C51工程(兼容Keil4/Keil5)、Proteus仿真文件(已实测逻辑准确)、电路连接说明(含74HC595驱动或直驱方式、限流电阻选型、电源处理等细节),以及任务书、原理图要点和字模工具使用提示。所有代码已脱敏,适配主流最小系统板,烧录hex即可运行,无需额外调试。配套led_preview.png和led_display.gif直观展示效果,led_simulator.py提供简易本地预览支持,requirements.txt明确依赖环境。适合嵌入式入门、课程设计、毕设快速搭建和硬件验证。


本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询