1. 从“开关”到“感知”:为什么AD/DA是51单片机项目质的飞跃
玩51单片机的朋友,从点亮第一个LED,到让数码管显示数字,再到用按键控制流水灯,这个过程充满了成就感。但不知道你有没有过这样的感觉:我们好像一直在和“开关”打交道。灯亮或灭,是1和0;按键按下或松开,也是1和0。单片机处理的是纯粹的数字世界,非黑即白。
然而,真实世界是连续的、模拟的。房间的温度从20度缓慢升到25度,这不是一个跳变,而是一个平滑的过程;麦克风采集的声音信号,其电压高低起伏,连绵不绝;调节一个电位器旋钮,电阻值也是连续变化的。这些连续变化的物理量,我们称之为模拟量。要让51单片机这个“数字大脑”去理解、处理这些模拟量,就必须借助一座桥梁——这就是模数转换器(Analog-to-Digital Converter, ADC)。反过来,当单片机计算出一个结果(比如一个数字量),想要去控制一个模拟设备(比如让电机平滑加速,或者让喇叭发出特定音调的声音),就需要数模转换器(Digital-to-Analog Converter, DAC)将数字信号变回模拟信号。
所以,当你开始接触51单片机的AD/DA,标志着你从“控制开关”迈入了“感知与控制真实世界”的新阶段。你的项目不再局限于灯和蜂鸣器,而是可以去做温湿度监控、声音采集、光线感应、电机调速等更复杂、更有趣的应用。这绝对是51单片机学习路上一个关键的里程碑。本文,我将以一个老电子爱好者的视角,带你彻底搞懂51单片机如何玩转AD/DA,从原理、选型、电路到代码,全程避坑,让你能真正把这项技能用起来。
2. AD/DA的核心原理:不只是“转换”那么简单
在深入代码和电路之前,我们必须把原理吃透。很多人觉得AD/DA就是单片机的一个“功能”,调用一下库函数就行,这其实是个误解。对于经典的89C51/52这类单片机,它们内部并没有集成ADC或DAC,所有的转换都需要依靠外部芯片来完成。单片机与这些芯片通过某种方式“对话”,获取或发送数据。因此,理解它们如何“对话”,比记住某个芯片的引脚更重要。
2.1 ADC:如何把连续的电压“拍成”离散的数字照片
想象你要用数码相机给一个连续运动的物体拍照。相机会在按下快门的一瞬间,捕捉到物体的一个静止画面。ADC的工作就类似于此,它在一个特定时刻,对输入的模拟电压进行“采样”,并将这个瞬间的电压值转换成一个对应的数字量。这个过程有两个关键参数:分辨率和采样率。
分辨率,通常用位数表示,比如8位、10位、12位。它决定了这张“数字照片”的精细程度。一个8位的ADC,意味着它能把参考电压(比如5V)分成 2^8 = 256 个等级。每个等级对应的电压值是 5V / 256 ≈ 0.0195V(19.5mV)。如果输入电压是1.5V,ADC转换出的数字值就是 1.5V / 0.0195V ≈ 77(取整)。显然,位数越高,能区分的电压变化就越细微,测量就越精确。常见的ADC0804是8位,ADC0832是8位(但通过差分输入可提高精度),而像ADS1115这类16位ADC,精度就非常高,适用于精密测量。
采样率,指每秒能完成多少次这样的“拍照”。根据奈奎斯特采样定理,要完整还原一个信号,采样率必须至少是信号最高频率的两倍。如果你要采集音频信号(人耳能听到的最高频率约20kHz),那么ADC的采样率至少需要40kHz以上。对于温度、光照这种变化缓慢的信号,几Hz的采样率就足够了。51单片机通常通过I/O口模拟时序或SPI总线来控制外部ADC,其极限采样率受限于单片机操作外部芯片的速度。
ADC的转换结果,是一个数字量。对于8位ADC,结果范围是0-255;对于10位ADC,是0-1023。这个数字值(N)和实际电压(Vin)的关系是:N = (Vin / Vref) * (2^n - 1)。其中,Vref是ADC的参考电压,n是位数。这是所有ADC计算的基础公式。
2.2 DAC:如何用数字阶梯“拼凑”出平滑曲线
DAC是ADC的逆过程。它接收一个数字量(比如8位的0-255),然后输出一个对应的模拟电压。最简单的一种DAC实现方式是权电阻网络型,但更常用的是R-2R梯形电阻网络,因为它对电阻精度要求相对较低。
你可以把DAC想象成一个有很多级的数字电位器。输入的数字量决定了接通哪一级,从而在输出端产生一个对应的电压。一个8位DAC,同样能把参考电压分成256级。当你让输入的数字量从0线性增加到255,输出的电压就会像一个阶梯一样,从0V一步步爬到接近Vref。
但是,这个“阶梯”是离散的,不是真正平滑的曲线。如果直接用这个阶梯信号去驱动喇叭,会听到严重的失真和噪声。因此,在实际应用中,DAC输出之后通常会连接一个低通滤波器(通常是一个简单的RC电路),把高频的阶梯毛刺滤掉,让输出波形变得平滑。这就是为什么音频DAC芯片(如PCM5102)内部或外部必须要有滤波电路。
对于51单片机项目,常用的DAC芯片有DAC0832(8位并行输入)、DAC5571(8位I2C接口)等。同样,很多51单片机内部也没有DAC,需要外接。
注意:市面上有些教程会用PWM(脉冲宽度调制)来模拟DAC功能。其原理是通过调整一个数字方波信号中高电平所占的时间比例(占空比),再经过低通滤波,得到一个平均电压值。这种方法成本低,但精度、稳定性和响应速度通常不如专用的DAC芯片,适用于要求不高的场合,如LED调光、电机简易调速等。
3. 经典外设芯片实战:以ADC0804和DAC0832为例
理论懂了,我们来看实战。我会选择两款非常经典、资料极多的芯片:ADC0804(AD)和DAC0832(DA)。通过它们,你能掌握51单片机与外部AD/DA芯片通信的典型模式。
3.1 ADC0804:并行接口的8位ADC
ADC0804是一个8位分辨率的逐次逼近型ADC,采用并行数据输出,可以直接与51单片机的P0口连接(需要上拉电阻)。它的接口时序相对简单,适合初学者理解ADC的工作流程。
电路连接要点:
- 模拟输入:待测电压接在Vin(+)和Vin(-)之间。如果测量单端对地电压,Vin(-)接地,电压接Vin(+)。
- 参考电压:Vref/2引脚决定了ADC的满量程电压。通常,如果你希望输入电压范围是0-5V,就将Vref/2悬空(芯片内部会提供约2.5V的参考)。如果你想自定义量程,比如0-2V,可以在Vref/2引脚接一个1V的电压(因为Vref = 2 * Vref/2)。
- 时钟:ADC0804需要工作时钟。可以用外部RC电路在CLK IN和CLK R引脚之间搭建(典型频率约640kHz),也可以由单片机提供时钟信号。
- 数据与控制线:
- DB0-DB7:接单片机P0口(P0口需接10k上拉电阻排)。
CS(片选):低电平有效,接一个单片机I/O口(如P2.0)。RD(读信号)和WR(写信号):低电平有效,分别接单片机I/O口(如P2.1, P2.2)。INTR(转换结束标志):输出,转换完成时变低电平,接单片机I/O口(如P2.3)以供查询或接中断引脚。
单片机操作流程(查询方式):
- 单片机拉低
CS和WR,启动一次转换。 - 延时一小段时间(等待转换启动),然后拉高
WR。 - 循环查询
INTR引脚是否为低电平。 - 当
INTR变低,表示转换完成。单片机拉低CS和RD。 - 此时,ADC0804的数据线DB0-DB7上出现有效数据,单片机从P0口读取该数据。
- 读取完成后,拉高
RD和CS。
C代码示例(Keil环境):
#include <reg52.h> #include <intrins.h> sbit ADC_CS = P2^0; sbit ADC_RD = P2^1; sbit ADC_WR = P2^2; sbit ADC_INTR = P2^3; #define ADC_DATA P0 // P0口需接上拉电阻 unsigned char Read_ADC0804() { unsigned char adc_value; ADC_CS = 0; // 选中芯片 ADC_WR = 0; // 启动转换 _nop_(); _nop_(); // 短暂延时,确保信号稳定 ADC_WR = 1; // 结束启动信号 while(ADC_INTR == 1); // 等待转换完成(INTR变低) ADC_RD = 0; // 准备读取数据 _nop_(); _nop_(); // 短暂延时,等待数据稳定 adc_value = ADC_DATA; // 读取数据 ADC_RD = 1; ADC_CS = 1; // 释放芯片 return adc_value; } void main() { unsigned char val; while(1) { val = Read_ADC0804(); // 此时val是0-255的数字,对应0-Vref的电压 // 可以将val送去显示,或进行其他处理 // ... 你的处理代码 ... } }实操心得:ADC0804的
INTR信号线非常关键。如果程序卡在while(ADC_INTR == 1);这里出不来,首先检查INTR引脚的上拉电阻(通常10k)是否接了,以及电路连接是否正确。另外,启动转换(WR)和读取(RD)之间的延时要足够,确保芯片有时间完成转换,否则读到的可能是上一次的数据或乱码。
3.2 DAC0832:并行接口的8位DAC
DAC0832是与ADC0804同时代的经典8位DAC,同样采用并行输入。它内部有两级输入锁存器,允许单片机快速写入数据,而DAC的输出可以独立于数据总线变化,设计更灵活。
电路连接与工作模式:DAC0832有三种工作模式,最常用的是直通模式,这种方式下,数据一旦写入就立即转换输出,控制最简单。
- 数据线:DI0-DI7接单片机P0口(需上拉)。
- 控制线(直通模式接法):
CS、WR1、WR2、XFER全部接地。这意味着两个锁存器都处于“直通”状态。ILE(输入锁存使能)接高电平(VCC)。
- 输出:DAC0832输出的是电流(Iout1和Iout2)。要得到电压,必须外接一个运算放大器构成电流-电压转换电路(即一个反相比例放大器)。这是最容易出错的地方!很多人连了芯片发现没输出,就是因为少了这个运放电路。
- 典型接法:Iout1接运放反相输入端,Iout2接地(或接运放同相输入端)。运放输出端与反相输入端之间接一个反馈电阻Rfb。DAC0832内部已有一个Rfb(约15kΩ),可以就用它,也可以外接。最终输出电压 Vout = - (数字量/256) * Vref。
单片机操作(直通模式):在直通模式下,操作极其简单:单片机只需要像操作普通锁存器一样,把数据送到P0口,数据就会立刻进入DAC并开始转换。因为控制线都固定了,所以没有复杂的时序。
C代码示例:
#include <reg52.h> #define DAC_DATA P0 // P0口接DAC0832数据线,需上拉 void Output_DAC0832(unsigned char dac_value) { DAC_DATA = dac_value; // 直接写入数据,转换立即开始 // 无需其他控制信号 } void main() { unsigned char i; while(1) { // 示例:产生一个三角波 for(i=0; i<255; i++) { Output_DAC0832(i); // 这里需要加一个延时,控制波形频率 DelayMs(1); // 假设有一个毫秒延时函数 } for(i=255; i>0; i--) { Output_DAC0832(i); DelayMs(1); } } }踩坑实录:DAC0832输出的是电流!这是我当年犯的第一个错误。直接拿万用表测输出引脚,电压几乎不变。必须按照数据手册,老老实实搭建运放电路(常用LM358、TL082等),将电流转换为电压。运放的正负电源一定要接对,通常需要±12V或±5V,单电源运放电路设计会复杂一些。另外,产生的波形有台阶是正常的,这是数字量的本质,后续需要滤波才能平滑。
4. 更现代的方案:串行接口ADC/DAC与集成模块
虽然ADC0804和DAC0832是学习原理的绝佳教材,但在实际项目中,它们已逐渐被更小巧、接口更简单的串行芯片取代。对于51单片机,I2C和SPI接口的ADC/DAC芯片是更好的选择,它们占用I/O口少(通常只需2-4根线),封装小,精度可选范围广。
4.1 使用I2C接口的ADC:以PCF8591为例
PCF8591是一个集成了4路模拟输入(可配置为单端或差分)和1路模拟输出(DAC)的芯片!它通过I2C总线与单片机通信,仅需两根线(SDA, SCL)。对于需要同时采集多路信号并偶尔输出模拟量的项目,它是一颗“神器”。
电路连接:
SDA,SCL:分别接51单片机的两个I/O口(需软件模拟I2C时序),并各自接一个4.7kΩ上拉电阻到VCC。A0-A2:地址选择引脚,接地或接VCC,用于设置I2C从机地址,允许多个器件挂在同一条总线上。AIN0-AIN3:模拟输入通道。AOUT:模拟输出(DAC输出)。EXT,OSC:内部振荡器引脚,通常悬空使用内部振荡器即可。
软件操作要点:51单片机通常没有硬件I2C,需要编写“软件模拟I2C”的代码,包括I2C_Start(),I2C_Stop(),I2C_SendByte(),I2C_RecvByte()等函数。这是必须掌握的基本功。
操作PCF8591的流程是:
- 发送起始信号,发送设备地址(写模式)。
- 发送控制字节,用于选择输入通道、是否启用模拟输出、设置增益等。
- 如果是读ADC,需要再发送一个起始信号和读地址,然后读取两个字节的数据(其中第二个字节是有效的转换结果)。
- 如果是写DAC,则在发送控制字节后,直接发送要输出的数据字节。
经验技巧:PCF8591的ADC转换需要时间。在发送读命令后,最好延时一小段时间(几十到几百微秒,具体看芯片手册)再读取数据,否则可能读到的是上一次的转换结果或无效数据。另外,它的DAC输出在电源上电后是未知状态,最好在程序初始化时就写入一个默认值(比如0)。
4.2 使用SPI接口的DAC:以MCP4921为例
MCP4921是Microchip公司的一款12位SPI接口DAC,精度比8位DAC高很多。SPI接口速度比I2C快,适合需要快速更新输出的场合。
电路连接:
CS(片选)、SCK(时钟)、SDI(数据输入):接51单片机的三个I/O口。LDAC:加载DAC输入,通常可以直接接地,这样数据在传输完成后立即更新到输出;如果接一个单片机I/O,可以实现多个DAC同步更新输出。VOUT:电压输出,无需外接运放(芯片内部集成),这是很大的优点。
软件操作:需要编写软件模拟SPI的函数。向MCP4921写入数据需要16位(两个字节)。这16位中包含了配置位(如是否缓冲、输出增益选择、是否关断输出)和12位的数据位。具体格式需要查阅数据手册。
代码逻辑示例:
void Write_MCP4921(unsigned int data) { unsigned short command_data; // 假设配置为:缓冲关闭,增益1x,输出开启 command_data = 0x3000 | (data & 0x0FFF); // 组合配置位和12位数据 MCP4921_CS = 0; SPI_WriteByte((unsigned char)(command_data >> 8)); // 发送高8位 SPI_WriteByte((unsigned char)(command_data & 0xFF)); // 发送低8位 MCP4921_CS = 1; }4.3 直接使用集成模块:规避硬件设计的烦恼
对于初学者或快速原型开发,直接使用现成的AD/DA模块是最高效的选择。某宝上几块钱就能买到PCF8591模块、ADS1115模块(16位高精度ADC)、MCP4725模块(12位I2C DAC)等。这些模块已经帮你做好了外围电路(电源滤波、参考源、电平转换、接口引出),你只需要用杜邦线连接单片机的I/O口,然后调用厂家或社区提供的驱动程序即可。
这样做的好处是:
- 省时省力:无需自己画原理图、计算电阻电容、焊接运放。
- 降低风险:模块经过验证,避免了因自己电路设计不当导致的芯片损坏或工作不稳定。
- 便于调试:模块通常带有指示灯,输入输出接口清晰,方便用万用表、示波器测量。
需要注意的坑:
- 电平兼容:确保模块的工作电压(通常是3.3V或5V)与你的51单片机系统一致。如果不一致,需要电平转换,否则可能无法通信或损坏芯片。
- 驱动程序:模块卖家提供的代码质量参差不齐。最好自己根据芯片数据手册,理解时序后编写或调试驱动,这样出问题了自己能排查。
- 电源噪声:即使使用模块,也要注意给模拟部分(ADC的参考电压、输入信号)提供干净的电源,数字地和模拟地之间用磁珠或0欧电阻单点连接,可以减少数字开关噪声对模拟信号的干扰。
5. 项目实战:打造一个简易数字电压表与信号发生器
现在,我们把前面学的知识串起来,做一个综合性的小项目:用51单片机、一个ADC模块和一个DAC模块,制作一个简易数字电压表,同时能产生简单的波形信号。这个项目能让你全面体验AD/DA的采集、处理和输出过程。
系统设计:
- 单片机:STC89C52RC(或其他51内核单片机)。
- ADC模块:选用PCF8591模块(兼顾ADC和DAC,但这里我们先只用其ADC功能)或单独的ADS1115模块(精度更高)。
- DAC模块:选用MCP4725模块(I2C接口,12位)或直接用PCF8591的DAC输出(8位)。
- 显示:使用一个OLED显示屏(I2C接口)来显示测量的电压值和生成的波形参数。
- 输入:用一个电位器作为被测电压源,旋动电位器改变输入ADC的电压。
- 输出:DAC的输出接一个示波器(或电脑声卡配合音频软件)来观察波形。
功能规划:
- 模式一:电压表。单片机循环读取ADC的值,根据公式
电压 = (ADC值 / 最大分辨率) * 参考电压计算出实际电压,并显示在OLED上。 - 模式二:信号发生器。单片机内部计算一个波形数据表(如正弦波、方波、三角波的离散点),循环地将表中的数据写入DAC,从而在输出端产生对应的模拟波形。通过按键可以切换波形种类和调整频率。
核心代码结构:
// 伪代码,展示逻辑 void main() { I2C_Init(); // 初始化软件I2C OLED_Init(); // 初始化OLED Key_Init(); // 初始化按键 while(1) { key = ScanKey(); if(key == MODE_KEY) { mode = !mode; // 切换电压表/信号发生器模式 } if(mode == VOLTMETER_MODE) { // 电压表模式 adc_value = Read_PCF8591_ADC(0); // 读取通道0 voltage = (adc_value / 255.0) * 5.0; // 假设Vref=5V OLED_ShowVoltage(voltage); } else { // 信号发生器模式 if(key == WAVE_KEY) { wave_type = (wave_type + 1) % 3; // 循环切换正弦、方波、三角 } if(key == FREQ_UP_KEY) { freq += 1; } if(key == FREQ_DOWN_KEY) { freq -= 1; } // 根据wave_type和freq,计算当前应输出的DAC值 dac_value = Get_Wave_Value(wave_type, freq, system_tick); Write_MCP4725_DAC(dac_value); // 输出 OLED_ShowWaveInfo(wave_type, freq); } DelayMs(10); // 主循环延时 } }调试与优化经验:
- ADC读数跳变:这是最常见的问题。首先,确保模拟输入信号本身是稳定的(可以用万用表测量电位器中间引脚电压是否稳定)。其次,在软件上可以对ADC进行软件滤波。最简单有效的方法是多次采样取平均值。例如,连续采样10次,去掉最大最小值,剩下的取平均,能极大抑制随机干扰。
#define SAMPLE_TIMES 10 unsigned int Get_Filtered_ADC() { unsigned int i, sum = 0; unsigned char min_val = 255, max_val = 0, val; for(i=0; i<SAMPLE_TIMES+2; i++) { // 多采两个用于剔除 val = Read_ADC(); sum += val; if(val < min_val) min_val = val; if(val > max_val) max_val = val; } sum = sum - min_val - max_val; // 剔除最大最小值 return sum / SAMPLE_TIMES; } - DAC输出波形毛刺大:除了在输出端加硬件RC低通滤波器,在软件上可以尝试在改变DAC输出值之间加入极短的延时,避免数据变化过快。对于51单片机,如果DAC更新速率接近其软件模拟I2C/SPI的极限,可能会因为时序紧张导致通信错误,产生毛刺。适当降低更新频率或优化通信函数会有改善。
- 系统干扰:如果发现测量值随单片机其他操作(如刷新显示屏)有明显跳动,可能是电源噪声或地线噪声。尝试在模拟电源入口处增加一个π型滤波电路(电容-电感-电容),并将数字地和模拟地在电源入口处单点连接。
6. 深入进阶:精度、速度与系统设计考量
当你完成了基础项目后,如果想做得更专业,就需要考虑以下更深层次的问题。这些是区分“玩具”项目和“实用”项目的关键。
6.1 如何提高测量精度?
- 选用高分辨率ADC:这是最直接的方法。从8位的ADC0804升级到12位的ADS1015或16位的ADS1115,理论精度可以从约20mV提高到1mV甚至0.25mV(在5V量程下)。
- 提供精密参考电压:ADC的精度极度依赖参考电压(Vref)的稳定性。单片机本身的5V电源通常噪声较大。使用独立的基准电压源芯片,如TL431、REF5025,可以提供非常稳定、精确的2.5V或4.096V参考电压,能大幅提升ADC的转换精度。
- 优化PCB布局与布线:
- 模拟与数字分区:将板上电路明确划分为模拟区域和数字区域。
- 独立走线:模拟信号线(特别是高阻抗的传感器信号线)要远离数字信号线(时钟、数据总线)、电源线,避免耦合噪声。
- 接地艺术:采用“单点接地”或“星型接地”。所有模拟器件的地最终汇集到一点,所有数字器件的地汇集到另一点,然后用一根较粗的线或磁珠将这两点连接起来。切忌形成“地环路”。
- 软件校准:即使硬件很完美,也存在零点偏移和增益误差。可以通过软件校准来消除。方法是在已知输入电压(如0V和满量程电压)时,读取ADC的原始值,计算出实际的转换系数和偏移量,保存在EEPROM中,每次测量时进行修正。
6.2 如何提高转换与输出速度?
- 选择高速接口芯片:对于需要高速采样的应用(如音频),并行接口的ADC/DAC天生比串行(I2C/SPI)快,但占用引脚多。也可以选择高速SPI接口的芯片。
- 优化单片机代码:
- 用汇编语言或高度优化的C语言编写ADC读取和DAC写入的核心时序函数。
- 避免在数据转换循环中进行复杂的数学运算(如浮点数乘除)或函数调用。
- 使用查表法代替实时计算。例如,对于正弦波输出,可以预先计算好一个周期的采样点数组,DAC输出时直接顺序查表,速度极快。
- 使用中断而非查询:对于ADC,使用
INTR中断通知转换完成,比循环查询更能及时响应,释放CPU资源去做其他事。 - 考虑升级单片机:如果51单片机的速度成为瓶颈,可以考虑换用运行速度更快的增强型51内核单片机(如STC8系列),或者直接升级到ARM Cortex-M0/M3内核的32位单片机,其主频和外设性能远超传统51。
6.3 模拟前端与后端电路设计
一个完整的模拟信号处理链路,单片机夹在中间,前后都需要精心设计。
模拟前端(AFE, 在ADC之前):
- 传感器接口:很多传感器输出信号微弱(如热电偶的mV信号)或带有共模干扰(如工业现场的4-20mA电流环)。需要设计仪表放大器电路来放大微弱信号,或使用隔离变送器来处理高压、高共模电压信号。
- 抗混叠滤波器:如果被测信号中含有高于ADC采样率一半的频率成分,会发生“混叠”失真,导致测量错误。必须在ADC输入端加入一个低通滤波器(抗混叠滤波器),其截止频率略低于采样率的一半,将高频噪声滤除。
- 限幅保护:用二极管钳位电路或TVS管,防止输入电压超过ADC允许的范围,烧毁芯片。
模拟后端(在DAC之后):
- 驱动能力:DAC或运放的输出电流有限,如果直接驱动低阻抗负载(如扬声器),会导致输出电压下降、失真。需要增加功率放大电路(如运放加晶体管扩流,或专用的音频功放芯片)。
- 滤波:如前所述,DAC输出的阶梯波需要重构滤波器(通常也是低通滤波器)来平滑。对于音频应用,这个滤波器设计非常关键,影响音质。
- 直流偏置:有些应用需要双极性输出(如-5V到+5V),而DAC通常是单电源供电(0-Vref)。这时需要在运放电路中加入电平移位电路,将单极性输出转换为双极性。
从点亮一个LED,到让单片机读懂世界的温度、声音,再到控制一个电机平滑转动,AD/DA技术就是打开这扇大门的钥匙。它没有想象中那么神秘,核心就是“转换”二字,但围绕这两个字展开的,是精度、速度、抗干扰、电源完整性等一系列扎实的硬件知识和软件技巧。我建议你不要停留在看和想,一定要动手。哪怕最开始只是用模块搭一个测量电池电压的小装置,或者让蜂鸣器发出不同音调(用PWM模拟DAC),在调试和解决问题的过程中获得的经验,远比读十篇文章来得深刻。当你第一次在示波器上看到自己用51单片机产生的干净正弦波时,那种成就感是无与伦比的。这条路有坑,但踩过去,就是坦途。