c51单片机程序100例AD转换时,读取数据时dat初值为啥是0啊

51单片机ad转换问题_百度知道
51单片机ad转换问题
用51单片机at89s52与adc0804进行ad转换
按照郭天祥那本书做
用滑动变阻器模拟电压,通过转换后在数显管显示0x00~0xff之间的数
可是最后结果是无论如何调 数显管都是输出00 包括进行仿真也是
在线等答案
#include&reg52.h&
#include&intri...
我有更好的答案
首先,不知道你的原理图中的 VCC的标号是怎么画的,我记得那个标号一般是GND的,你在确认一下其次,‘P3=0x11;
//为了验证是不是我初始设值的问题...’,这里P3作为输入不建议做输出使用,P1口闲着可以随便用然后,数码管动态显示应该预留足够的显示时间,程序中一次while循环一次AD采集用到4个'delay (10) ', 数码管显示 有10个{delay (2)+delay (2)}也就是说一个周期只有不到一半的时间在显示,那么做出来的效果数码管肯定不会太亮,建议适当增加数码管的显示时间(高级应用也可考虑用定时器中断定时更新数码显示)还有就是‘uchar temp3;
//temp3用作暂时储存P0...’,这里没必要保留P0的数据,你在退出显示的时候已经把位选关了最后,不知道你是按照什么样的步骤软件仿真的,建议使用断点观察配合单步调试来查找问题,可以先不考虑数码管显示,AD采集数据正常之后再考虑显示,一步一步来分块排查解决问题
采纳率:87%
……P3=0x11;
//为了验证是不是我初始设值的问题,这里随便打的数字--不可乱打字呀!--为了使用 P3 输入,必须写:P3 = 0xFF;adrd=1;delay (10);adrd=0;delay (10);adval=P3;--这里,倒是可以随意的打上数字,如:--P3=0x11;
--这么打,数码管,就应该显示 11。--如果能显示出来 11,说明 ADC 部分,有错。--如果不能显示出来 11,说明显示部分,有错。……
显示出来的是00 就是不知道adc部分哪里有问题前面那个注释是不小心打错了
哦,我前面写的,有些纰漏。……adval=P3;--这里,倒是可以随意的打上数字,如:--adval = 0x11;
******这里改一下******--这么打,数码管,就应该显示 11。--如果能显示出来 11,说明 ADC 部分,有错。--如果不能显示出来 11,说明显示部分,有错。……这么改,就是不用 ADC 了,直接,把 11,当做 ADC 的结果,送给变量。再试试看。
adc似乎有输出了,但是无论怎么调滑动变阻器第一位数字稳定显示0或1第二位数字就一直在0和1之间变化会不会是芯片的问题?顺便问一问 如果我数字地和模拟地不是按照最后一点相连 会不会影响输出?
--adval = 0x11;
******这里改一下******---------------------------这里改成多少,就应该显示多少,和 ADC 无关了。你如果能随意显示,那肯定就是 ADC 有错了。电路,是主要原因。自己焊接的,有些错误、虚焊,难免。这个就只能自己检查了。--数字地和模拟地不是按照最后一点相连 会不会影响输出?连上,即可。影响,不会很大。不会有《第二位数字就一直在0和1之间变化》这么大。
本回答被网友采纳
你用的是不是TX-1C开发板?参考的这本书?
书是……有点坑……板自己看着电路图焊的大侠看过那本书的话,应该会熟悉点吧?
看是看过,不过记不太清了,你自己焊的电路板做数字实验应该没问题,但是这个是模数转换,模拟量很可能因为硬件的选材和焊接工艺受到影响,原因不好找啊
adc似乎有输出了,但是无论怎么调滑动变阻器第一位数字稳定显示0或1第二位数字就一直在0和1之间变化会不会是芯片的问题?顺便问一问 如果我数字地和模拟地不是按照最后一点相连 会不会影响输出?
会的,数字地和模拟地是要分开的,因为数字的变化频率比模拟快,会产生抖动,而模拟地的抖动达到一定程度又会对数字造成影响,甚至可能导致数字的01不分。
为您推荐:
其他类似问题
您可能关注的内容
51单片机的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。51单片机课程设计--AD转换_百度文库
您的浏览器Javascript被禁用,需开启后体验完整功能,
享专业文档下载特权
&赠共享文档下载特权
&10W篇文档免费专享
&每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
51单片机课程设计--AD转换
&&51单片机课程设计--AD转换
阅读已结束,下载本文需要
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
加入VIP
还剩14页未读,
定制HR最喜欢的简历
你可能喜欢51单片机综合学习系统之 AD模数转换实验篇
51单片机综合学习系统之 AD模数转换实验篇 《电子制作》2008年8月 站长原创,如需引用请注明出处
&&&&大家好,通过以前的学习,我们已经对51单片机综合学习系统的使用方法及学习方式有所了解与熟悉,学会了使用SPI总线的基本知识,体会到了综合学习系统的易用性与易学性,这一期我们将一起学习AD模数转换的基本原理与应用实例。
&&&&先看一下我们将要使用的51单片机综合学习系统能完成哪些实验与产品开发工作:分别有流水灯,数码管显示,液晶显示,按键开关,蜂鸣器奏乐,继电器控制,IIC总线,SPI总线,PS/2实验,AD模数转换,光耦实验,串口通信,红外线遥控,无线遥控,温度传感,步进电机控制等等。主体系统如图1所示,其配套书本教程《单片机快速入门》如图2所示。
图1 51单片机综合学习系统主机部分图片
图2 51单片机综合学习系统配套书本教程――《单片机快速入门》
&&&&上图是我们将要使用的51单片机综合学习系统硬件平台,如图1所示,本期实验我们用到了综合系统主机、板载的ADC0832 模数转换芯片,综合系统其它功能模块原理与使用详见前几期《电子制作》杂志及后期连载教程介绍。
&&&&在工业控制和智能化仪表中,通常由微型计算机进行实时控制及实时数据处理。计算机所加工的信息总是数字量,而被控制或被测量的有关参量往往是连续变化的模拟量,如温度、速度、压力等等,与此对应的电信号是模拟信号。模拟量的存储和处理比较困难,不适合作为远距离传输且易受干扰。在一般的工业应用系统中传感器把非电量的模拟信号变成与之对应的模拟信号,然后经模拟(Analog)到数字(Digital)转换电路将模拟信号转成对应的数字信号送微机处理。这就是一个完整的信号链,模拟到数字的转换过程就是我们经常接触到的ADC(Analog to Digital Convert)电路。
模-数转换(ADC)简介
模-数转换原理
&&&&ADC的转换原理根据ADC的电路形式有所不同。&&ADC电路通常由两部分组成,它们是:采样、保持电路和量化、编码电路。其中量化、编码电路是最核心的部件,任何ADC转换电路都必须包含这种电路。&ADC电路的形式很多,通常可以并为两类:
&&&&间接法:它是将采样-保持的模拟信号先转换成与模拟量成正比的时间或频率,然后再把它转换为数字量。这种通常是采用时钟脉冲计数器,它又被称为计数器式。它的工作特点是:工作速度低,转换精度高,抗干扰能力强。
&&&&直接法:通过基准电压与采样-保持信号进行比较,从而转换为数字量。它的工作特点是:工作速度高,转换精度容易保证。&
&&&&模―数转换的过程有四个阶段,即采样、保持、量化和编码。
&&&&采样是将连续时间信号变成离散时间信号的过程。经过采样,时间连续、数值连续的模拟信号就变成了时间离散、数值连续的信号,称为采样信号。采样电路相当于一个模拟开关,模拟开关周期性地工作。理论上,每个周期内,模拟开关的闭合时间趋近于0。在模拟开关闭合的时刻(采样时刻),我们就“采”到模拟信号的一个“样本”。
&&&&量化是将连续数值信号变成离散数值信号的过程。理论上,经过量化,我们就可以将时间离散、数值连续的采样信号变成时间离散、数值离散的数字信号。
&&&&我们知道,在电路中,数字量通常用二进制代码表示。因此,量化电路的后面有一个编码电路,将数字信号的数值转换成二进制代码。
&&&&然而,量化和编码总是需要一定时间才能完成,所以,量化电路的前面还要有一个保持电路。保持是将时间离散、数值连续的信号变成时间连续、数值离散信号的过程。在量化和编码期间,保持电路相当于一个恒压源,它将采样时刻的信号电压“保持”在量化器的输入端。虽然逻辑上保持器是一个独立的单元,但是,工程上保持器总是与采样器做在一起。两者合称采样保持器。
八位串行A/D转换器ADC0832简介
&&&&ADC0832 是美国国家半导体公司生产的一种8 位分辨率、双通道A/D转换芯片。由于它体积小,兼容性强,性价比高而深受单片机爱好者及企业欢迎,其目前已经有很高的普及率。ADC083X是市面上常见的串行模―数转换器件系列。ADC0831、ADC0832、ADC0834、ADC0838是具有多路转换开关的8位串行I/O模―数转换器,转换速度较高(转换时间32uS),单电源供电,功耗低(15mW),适用于各种便携式智能仪表。本章以ADC0832为例,介绍其使用方法。
&&&&ADC0832是8脚双列直插式双通道A/D转换器,能分别对两路模拟信号实现模―数转换,可以用在单端输入方式和差分方式下工作。ADC0832采用串行通信方式,通过DI 数据输入端进行通道选择、数据采集及数据传送。8位的分辨率(较高分辨可达256级),可以适应一般的模拟量转换要求。其内部电源输入与参考电压的复用,使得芯片的模拟电压输入在0~5V之间。具有双数据输出可作为数据校验,以减少数据误差,转换速度快且稳定性能强。独立的芯片使能输入,使多器件挂接和处理器控制变的更加方便。
&&&&ADC0832 具有以下特点:
? 8位分辨率;
? 双通道A/D转换;
? 输入输出电平与TTL/CMOS相兼容;
? 5V电源供电时输入电压在0~5V之间;
? 工作频率为250KHZ,转换时间为32μS;
? 一般功耗仅为15mW;
? 8P、14P―DIP(双列直插)、PICC 多种封装;
? 商用级芯片温宽为0°C to +70°C,工业级芯片温宽为-40°C to +85°C;
图3 ADC0832引脚图
芯片接口说明:
? CS_ 片选使能,低电平芯片使能。
? CH0 模拟输入通道0,或作为IN+/-使用。
? CH1 模拟输入通道1,或作为IN+/-使用。
? GND 芯片参考零电位(地)。
? DI 数据信号输入,选择通道控制。
? DO 数据信号输出,转换数据输出。
? CLK 芯片时钟输入。
? Vcc/REF 电源输入及参考电压输入(复用)
ADC0832的工作原理:
&&&&正常情况下ADC0832 与单片机的接口应为4条数据线,分别是CS、CLK、DO、DI。但由于DO端与DI端在通信时并未同时使用并与单片机的接口是双向的,所以在I/O口资源紧张时可以将DO和DI并联在一根数据线上使用。当ADC0832未工作时其CS输入端应为高电平,此时芯片禁用,CLK 和DO/DI 的电平可任意。当要进行A/D转换时,须先将CS使能端置于低电平并且保持低电平直到转换完全结束。此时芯片开始转换工作,同时由处理器向芯片时钟(CLK)输入端输入时钟脉冲,DO/DI端则使用DI端输入通道功能选择的数据信号。在一个时钟脉冲的下沉之前DI端必须是高电平,表示启始信号。在第二、三个脉冲下沉之前DI端应输入两位数据用于选择通道功能。
工作方式说明
单端输入方式
表1:通道地址设置表
&&&&如表1所示,当此两位数据为“1”、“0”时,只对CH0 进行单通道转换。当2位数据为“1”、“1”时,只对CH1进行单通道转换。当两位数据为“0”、“0”时,将CH0作为正输入端IN+,CH1作为负输入端IN-进行输入。当两位数据为“0”、“1”时,将CH0作为负输入端IN-,CH1 作为正输入端IN+进行输入。到第三个脉冲的下降之后DI端的输入电平就失去输入作用,此后DO/DI端则开始利用数据输出DO进行转换数据的读取。从第4个脉冲下降沿开始由DO端输出转换数据较高位Data7,随后每一个脉冲的下降沿DO端输出下一位数据。直到第11个脉冲时发出最低位数据Data0,一个字节的数据输出完成。也正是从此位开始输出下一个相反字节的数据,即从第11个字节的下降沿输出Data0。随后输出8位数据,到第19 个脉冲时数据输出完成,也标志着一次A/D转换的结束。较后将CS置高电平禁用芯片,直接将转换后的数据进行处理就可以了。时序说明请参照图4。
&&&&作为单通道模拟信号输入时ADC0832的输入电压是0―5V且8位分辨率时的电压精度为19.53mV,即(5/256)V。如果作为由IN+与IN-输入的输入时,可是将电压值设定在某一个较大范围之内,从而提高转换的宽度。但值得注意的是,在进行IN+与IN-的输入时,如果IN-的电压大于IN+的电压则转换后的数据结果始终为00H。
ADC0832的工作时序
图4 ADC0832工作时序
ADC0832软硬件设计实例
&&&&通过以上的理论学习之后,对模―数转换应该有了一定的了解,接下来就根据上文的指导,对ADC0832进行实际应用,以加深印象。本实例功能是将通道1上采样到的电压显示在LED数码管上,通过改变通道1的输入电压变化,观察输出读数。本实例调试前要先将功能选择开关调到ADC0832位置上,如图5,图6所示。
图5 ADC0832实验演示图
图6 ADC0832实验演示图
硬件原理图
图7 硬件原理图
程序流程图
图8 软件流程图
????????????&&&&相信看到这里,你应该可以理解我们是如何利用单片机来进行模数转换的处理了,你也可以根据自己的需要来写些AD模数转换相关的应用程序,如数字温度计,湿度传感应用,压力传感应用等等。由于篇幅有限,读者朋友可以通过网站或电子邮件一起交流与学习。在下几期中,我们将陆续介绍51单片机综合学习系统的其它功能原理与应用。
以上部分内容转载于网上,如有涉及到版权问题,请即通知本人删除 浙ICP备号
联系地址:浙江省杭州市西湖科技园西园七路3号4层
邮政编码:310011&Email:
电话总机:0 &&产品咨询:转分机1 &&技术支持:转分机2 &&传真:转分机3 &&手机:
技术QQ熊工:
技术QQ徐工:
杭州晶控电子有限公司 版权所有
COPYRIGHT2003――2011 HANGZHOU KinCony ELECTRONICS CO.,LTD All
rights reserved23043 条评论分享收藏感谢收起int main(void) //一个main函数 搞定
P0 = 0xxx;
linux:驱动程序:
#include&linux/init.h&
#include&linux/module.h&
#include&linux/fs.h&
//file_operatios
#include&linux/device.h& //class_create/device_create
#include&linux/slab.h&
#include&asm/io.h&
#include&asm/uaccess.h&
#include"led.h"
struct s5pv210_device *s5pv210_dev;
volatile unsigned long *gpc0con = NULL;
volatile unsigned long *gpc0dat = NULL;
static int led_open(struct inode *inode, struct file *file)
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
/*初始化GPC0_3,GPC0_4引脚功能为输出功能*/
*gpc0con &= ~((0xf&&12)|(0xf&&16));
*gpc0con |= ((0x1&&12)|(0x1&&16));
static int led_close(struct inode *inode, struct file *file)
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
//iounmap(S5PV210_PA_GPC0CON);
static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
//write(fd, &val, 4)
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
unsigned int val;
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
/*获取用户空间数据*/
ret = copy_from_user(&val,buf,count);
printk(KERN_ERR"copy data from user failed!\n");
return -ENODATA;
printk(KERN_INFO"copy data from user: val=%d\n",val);
*gpc0dat |= ((0x1&&3)|(0x1&&4));
*gpc0dat &= ~((0x1&&3)|(0x1&&4));
return ret?0:count;
static struct file_operations led_fops={
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_close,
static int __init led_init(void)
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
s5pv210_dev = kmalloc(sizeof(struct s5pv210_device),GFP_KERNEL);
if(s5pv210_dev==NULL)
printk(KERN_INFO"no memory malloc for s5pv210_dev!\n");
return -ENOMEM;
led_major = register_chrdev(0,"led",&led_fops);
if(led_major&0)
printk(KERN_INFO"register major failed!\n");
ret = -EINVAL;
goto err1;
/*创建设备文件*/
s5pv210_dev-&led_class = class_create(THIS_MODULE,"led_class");
if (IS_ERR(s5pv210_dev-&led_class)) {
printk(KERN_ERR "class_create() failed for led_class\n");
ret = -EINVAL;
goto err2;
s5pv210_dev-&led_device = device_create(s5pv210_dev-&led_class,NULL,MKDEV(led_major,0),NULL,"led");
if (IS_ERR(s5pv210_dev-&led_device)) {
printk(KERN_ERR "device_create failed for led_device\n");
ret = -ENODEV;
goto err3;
/*将物理地址映射为虚拟地址*/
gpc0con = ioremap(S5PV210_PA_GPC0CON,8);
if(gpc0con==NULL)
printk(KERN_INFO"ioremap failed!\n");
ret = -ENOMEM;
goto err4;
gpc0dat = gpc0con + 1;
device_destroy(s5pv210_dev-&led_class,MKDEV(led_major,0));
class_destroy(s5pv210_dev-&led_class);
unregister_chrdev(led_major,"led");
kfree(s5pv210_dev);
return ret;
static void __exit led_exit(void)
printk(KERN_INFO"%s()-%d\n", __func__, __LINE__);
unregister_chrdev(led_major,"led");
device_destroy(s5pv210_dev-&led_class,MKDEV(led_major,0));
class_destroy(s5pv210_dev-&led_class);
iounmap(gpc0con);
kfree(s5pv210_dev);
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
应用程序:#include &sys/types.h&
#include &sys/stat.h&
#include &fcntl.h&
#include &stdio.h&
#include &errno.h&
#include &stdlib.h&
#include &unistd.h&
#include &string.h&
** ./led_test on
** ./led_test off
int main(int argc, char **argv)
int val =0;
fd = open("/dev/led", O_RDWR);
perror("open failed!\n");
if(strcmp(argv[1], "on")==0)
if(write(fd, &val, 4)!=4)
perror("write failed!\n");
close(fd);
android:不知道大家清不清楚android与linux之间的关系。android是基于linux内核的,linux操作系统的5大组件:驱动,内存管理,文件系统,进程管理,网络套接字。android是基于linux kernel上的,大家平时只看到了app。app是java语言的,其实每运行一个java应用程序,实际上是fork一个linux应用程序。android四大组件,activity,service,Broadcast Receiver,Content Provider。这是android的主要框架,java应用开发是基于这开发的。android平台是基于linux 内核的。在linux运行之后才建立起java世界。直接上代码:上面的linux的驱动在android是一样的,适用于android。驱动ok之后是应用层了,也就是应用程序。我下面就是最直接的应用,不包含任何android框架性的东西,大家可以直接看到,应用app-&jni-&linux驱动这三层调用关系。下面是jni代码是c++;#define LOG_TAG "led-jni-log"
#include &utils/Log.h&
#include "jni.h"
#include &sys/types.h&
#include &sys/stat.h&
#include &fcntl.h&
#include &stdio.h&
#include &errno.h&
#include &unistd.h&
#include &stdlib.h&
static int fd = -1;
jint open_led(JNIEnv *env, jobject thiz)
LOGD("$$$%s\n", __FUNCTION__);
fd = open("/dev/led1", O_RDWR);
if(fd & 0)
LOGE("open : %s\n", strerror(errno));
return -1;
jint led_on(JNIEnv *env, jobject thiz)
LOGD("$$$%s\n", __FUNCTION__);
int val = 1;
jint ret = 0;
ret = write(fd, &val, 4);
if(ret != 4)
LOGE("write : %s\n", strerror(errno));
return -1;
jint led_off(JNIEnv *env, jobject thiz)
LOGD("$$$%s\n", __FUNCTION__);
int val = 0;
jint ret = 0;
ret = write(fd, &val, 4);
if(ret != 4)
LOGE("write : %s\n", strerror(errno));
return -1;
jint close_led(JNIEnv *env, jobject thiz)
LOGD("$$$%s\n", __FUNCTION__);
if(fd & 0)
close(fd);
static JNINativeMethod myMethods[] ={
{"openDev", "()I", (void *)open_led},
{"onDev", "()I", (void *)led_on},
{"offDev", "()I", (void *)led_off},
{"closeDev", "()I", (void *)close_led},
jint JNI_OnLoad(JavaVM * vm, void * reserved)
JNIEnv *env = NULL;
jint ret = -1;
ret = vm-&GetEnv((void **)&env, JNI_VERSION_1_4);
if(ret & 0)
LOGE("GetEnv error\n");
return -1;
jclass myclz = env-&FindClass("com/ledtest/LedActivity");
if(myclz == NULL)
LOGE("FindClass error\n");
return -1;
ret = env-&RegisterNatives(myclz, myMethods, sizeof(myMethods)/sizeof(myMethods[0]));
if(ret & 0)
LOGE("RegisterNatives error\n");
return -1;
return JNI_VERSION_1_4;
然后是java app:package com.ledtest;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class LedActivity extends Activity {
final String TAG = "LedActivity";
public Button btn_on = null;
public Button btn_off = null;
public void init() {
btn_on = (Button) this.findViewById(R.id.btn1);
btn_on.setOnClickListener(clickListener);
btn_off = (Button) this.findViewById(R.id.btn2);
btn_off.setOnClickListener(clickListener);
OnClickListener clickListener = new OnClickListener() {
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1:
Log.d(TAG, "led on in app");
case R.id.btn2:
Log.d(TAG, "led off in app");
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_led);
openDev();
public boolean onCreateOptionsMenu(Menu menu) {
// I this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_led, menu);
return true;
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
closeDev();
System.loadLibrary("led_jni");
public native int openDev();
public native int onDev();
public native int offDev();
public native int closeDev();
框架是这样的:最上层是java ,点开app,点击一下一个button,点亮了LED,调用onDev();onDev调用c++的led_on,然后led_on是调用驱动的led_write,明白不?这样说吧:linux 驱动是最下层的,驱动嘛。然后是c++层,c++包装一下是给java调用的,然后到java层。下面是android比价经典的一张框架图。linux在最下面。lib 是动态库。然后是JNI,然后是Framework(android框架),然后是大家喜闻乐见的app。赞同 53572 条评论分享收藏感谢收起51单片机AD转换的问题,求大神解答。_百度知道
51单片机AD转换的问题,求大神解答。
用的是XPT2046;
这一段数据读取的代码:
sbit DOUT=P1^1;
uint SPI_Read(void)
uint i, dat=0;
for(i=0; i&12; i++)
//接收12位数据
dat &&= 1;
dat |= DOUT;
我有更好的答案
就是个语法dat&&=1
dat=dat&&1;dat|=DOUT
==& dat=dat|DOUT
采纳率:35%
为您推荐:
其他类似问题
您可能关注的内容
长高的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。}

我要回帖

更多关于 单片机初值计算 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信