当前位置: 首页 > news >正文

Arduino电子骰子实战:从伪随机数生成到多路LED控制

1. 项目概述与核心价值

做嵌入式开发或者物联网项目,随机数生成是一个绕不开的基础功能。你可能觉得它很简单,不就是让单片机“随便”给个数吗?但真到了项目里,比如做个抽奖机、游戏道具,或者像我这次做的电子骰子,你会发现“真随机”和“看起来随机”完全是两码事,里面门道不少。这个基于Arduino的电子骰子项目,就是一个绝佳的切入点,它把抽象的随机数算法和直观的硬件交互(按钮、LED)结合在了一起。

这个项目的核心目标很明确:模拟一个六面骰子。用户按下一个按钮,系统生成一个1到6之间的随机数,然后通过点亮7个LED(排列成骰子点数图案)来显示结果。听起来简单,对吧?但正是这种简单的目标,能让我们聚焦于几个关键的技术点:如何用微控制器产生“够随机”的数,如何高效地驱动多个LED形成不同图案,以及如何设计稳定可靠的按钮检测逻辑。对于初学者来说,这是从点亮一个LED到实现完整人机交互的完美台阶;对于有经验的开发者,这也是一个反思和优化随机数质量、电路设计以及代码结构的好案例。

我选择Arduino平台,是因为它的生态足够友好,硬件抽象层让我们不用太操心底层寄存器,可以快速把想法变成现实。但我会在过程中穿插讲解这些“方便”背后的原理,比如random()函数是怎么工作的,直接驱动LED和用移位寄存器驱动有什么区别。最终,你得到的不仅是一个会闪的骰子,更是一套可以复用到其他项目中的关于随机数生成与多路LED控制的实战经验。

2. 核心思路与方案选型解析

2.1 为什么是“伪随机”以及如何让它“更随机”

首先要破除一个迷思:对于Arduino这类没有专用硬件随机数发生器(RNG)的微控制器,我们通常生成的都是“伪随机数”。它依赖于一个称为“种子”的初始值,通过一个确定的数学公式产生一串看起来随机、但可重现的数列。如果每次上电都用同一个种子,那么生成的随机数序列将完全一样。

Arduino的randomSeed()函数就是用来设置这个种子的。那么,种子从哪里来?一个经典且有效的做法是读取一个未连接的模拟引脚(如A0)的电压值。由于引脚悬空,读取到的值是环境电磁噪声,具有不确定性,可以作为不错的随机种子。这是本项目确保每次掷骰子序列不同的关键。

// 初始化随机数种子 randomSeed(analogRead(A0));

注意事项analogRead()在引脚完全悬空时,读数可能在0-1023间剧烈跳动,但某些情况下也可能徘徊在某值附近。为增加熵值,可以连续读取多次并进行某种运算(如累加或异或)。更进阶的做法是结合多个噪声源,比如读取内部温度传感器(如果支持)的末几位,或者统计程序启动到首次用户按键之间的微秒数差值。

2.2 LED显示方案:直接驱动 vs. 移位寄存器

骰子的点数图案需要控制7个LED。最直观的方法是用Arduino的7个数字I/O口直接驱动。这种方法简单明了,易于理解,适合初学者验证概念。本项目的初始设计也采用了这种方式。

但它的缺点也很明显:大量占用宝贵的I/O资源。一个Arduino Uno只有14个数字I/O,这就占了一半。如果项目还需要连接其他传感器、显示屏或通信模块,引脚立刻捉襟见肘。

更优化的方案是使用移位寄存器,如经典的74HC595。只需要占用Arduino的3个引脚(数据、时钟、锁存),就可以串联控制几乎无限多个LED(理论上)。数据被一位一位地送入寄存器,然后同时输出到8个引脚上,极大地节省了主控资源。这对于未来扩展(比如做两个骰子,需要14个LED)至关重要。

方案选型建议:对于纯粹的学习和验证,直接驱动完全没问题。但如果你的目标是做一个更完整、可扩展的作品,或者希望深入学习嵌入式系统中资源管理的思路,我强烈建议在理解直接驱动后,立刻尝试用74HC595重构电路。这会是技能上的一次重要升级。

2.3 按钮防抖:从“能用”到“稳定”

机械按钮在按下和弹起的瞬间,金属触点会发生物理抖动,会产生一系列快速的开闭信号,微控制器会误判为多次按压。如果不处理,按一次按钮可能会触发多次随机数生成,体验极差。

软件防抖是成本最低的解决方案。其核心逻辑是:在检测到引脚电平变化(按下)后,不立即响应,而是延迟一段时间(通常10-50毫秒),再次读取引脚状态。如果状态依然是按下,则确认为一次有效按压。

if (digitalRead(buttonPin) == LOW) { // 假设按下为低电平 delay(50); // 延时去抖 if (digitalRead(buttonPin) == LOW) { // 确认按下,执行核心操作 rollTheDice(); // 等待按钮释放(可附加释放去抖) while(digitalRead(buttonPin) == LOW); delay(50); } }

实操心得delay()函数在防抖时虽然简单,但会阻塞整个程序。在复杂的、需要同时处理多任务的项目中,这不可接受。更优的方法是使用状态机非阻塞式计时(利用millis()函数)。但对于骰子这个单一交互的项目,阻塞式防抖足够简单有效。先实现功能,再优化架构,这是学习过程中的合理路径。

3. 硬件电路搭建与核心细节

3.1 元器件清单与选型依据

一份清晰的物料清单是成功的第一步。以下是核心元器件及其选型理由:

元器件规格/型号数量选型理由与注意事项
主控板Arduino Uno R31生态最完善,资料最多,USB供电编程方便。兼容板亦可。
LED5mm 红色散光7颜色一致性好。务必注意:不同颜色LED正向压降不同(红/黄约1.8-2.2V,蓝/白约3.0-3.4V),影响限流电阻计算。
限流电阻220Ω 或 330Ω 1/4W7保护LED和Arduino引脚。计算:电阻 = (电源电压 - LED压降) / 期望电流。Arduino引脚安全电流约20mA,取5-15mA即可很亮。以5V电源、红色LED(2V、10mA)为例:R = (5-2)/0.01 = 300Ω,取330Ω标准值。
按钮6x6mm 轻触开关1四脚按键,内部两两相通,接线时注意对角线为同一组触点。
上拉电阻10kΩ1用于按钮。接在按钮与VCC之间,确保未按下时引脚为确定的高电平。注意:Arduino引脚可内部上拉,代码中设置pinMode(pin, INPUT_PULLUP)即可省略此电阻,此时按钮另一端应接地。
面包板与杜邦线-若干用于原型搭建。建议使用不同颜色线区分配电(红-VCC,黑-GND)和信号。
电源USB线或9V电池1开发时用USB,做成独立作品可考虑电池供电。

注意:LED是有极性的!长脚为正(阳极),短脚为负(阴极)。接反不会亮,但通常不会损坏。焊接或插接前务必确认。

3.2 电路连接详解与原理图解读

我们采用直接驱动方案进行连接。理解这个连接图,是读懂一切嵌入式项目硬件的基础。

核心连接逻辑:

  1. 电源回路:所有元件的GND(地)连接到Arduino的GND引脚,形成公共参考点。
  2. LED驱动回路:每个LED的阳极(正极)通过一个限流电阻,连接到Arduino的一个数字I/O引脚(如2-8)。LED的阴极(负极)直接连接到GND。这种连接方式称为“低端驱动”或“灌电流”,即Arduino引脚输出高电平(+5V)时,电流从引脚流出,经电阻、LED流入GND,LED点亮。
  3. 按钮输入回路:按钮一端接GND,另一端接Arduino的数字引脚(如9)。同时,该引脚通过一个10kΩ上拉电阻连接到VCC(+5V)。未按下时,引脚被电阻拉高到VCC,读取为HIGH;按下时,引脚直接与GND接通,读取为LOW。这种配置称为“上拉电阻+下拉触发”。

具体接线表(基于Arduino Uno):

Arduino 引脚连接至说明
数字引脚 2LED1 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 3LED2 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 4LED3 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 5LED4 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 6LED5 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 7LED6 阳极 (通过220Ω电阻)控制骰子图案的一个LED
数字引脚 8LED7 阳极 (通过220Ω电阻)控制骰子图案的中心LED
数字引脚 9按钮一脚 (另一脚接GND)检测按钮按下,需启用内部上拉
5V按钮电路的上拉电阻 (如使用外部电阻)提供高电平
GND所有LED阴极、按钮一脚公共接地

电路原理要点

  • 限流电阻不可省:没有电阻,LED会试图从Arduino引脚抽取过大电流,可能永久损坏引脚或LED本身。220Ω电阻在5V下提供约(5-2)/220≈13.6mA电流,安全且明亮。
  • 上拉电阻的作用:它确保了输入引脚在不被按钮拉低时,有一个确定的、干净的高电平状态,防止引脚悬空(浮空)时因电磁干扰产生不可预测的HIGH/LOW抖动,导致误触发。

3.3 布局与焊接实操技巧

在面包板上搭建时,建议按功能分区:左边集中布置LED和电阻,右边布置按钮和上拉电阻,中间是Arduino。电源总线(红、蓝条)充分利用起来,为VCC和GND提供主干道。

如果打算做成永久性的作品,焊接是下一步。焊接核心技巧

  1. 先规划后焊接:在洞洞板或PCB上先摆放好所有元件,用记号笔标记位置,确保LED排列成骰子点阵的方形。
  2. 先矮后高:先焊接电阻、IC座等矮元件,再焊接LED、按钮等高的元件。
  3. LED焊接要快:LED对高温敏感,焊接每个引脚时间不要超过3秒,避免烫坏芯片。可以使用散热夹或镊子夹住引脚根部帮助散热。
  4. 电源走线加粗:VCC和GND的走线可以并联焊锡或用导线加粗,以减少电阻,保证供电稳定。

4. 软件程序深度剖析与实现

程序是项目的灵魂。我们将代码拆解成几个逻辑模块,并逐行解释。

4.1 引脚定义与初始化

// 定义LED引脚,对应骰子上的位置 // 假设布局如下: // [1] [2] [3] // [4] [5] [6] // [7] // 中心点单独控制 const int ledPins[] = {2, 3, 4, 5, 6, 7, 8}; const int ledCount = 7; const int buttonPin = 9; // 按钮连接引脚 void setup() { // 初始化所有LED引脚为输出模式 for (int i = 0; i < ledCount; i++) { pinMode(ledPins[i], OUTPUT); digitalWrite(ledPins[i], LOW); // 初始状态熄灭 } // 初始化按钮引脚为输入模式,并启用内部上拉电阻 pinMode(buttonPin, INPUT_PULLUP); // 初始化随机数种子 // 读取未连接的模拟引脚A0的噪声作为种子 randomSeed(analogRead(A0)); // 可选的:启动后所有LED快速闪烁一次,表示系统就绪 for (int i = 0; i < ledCount; i++) { digitalWrite(ledPins[i], HIGH); } delay(200); for (int i = 0; i < ledCount; i++) { digitalWrite(ledPins[i], LOW); } }

代码解读

  • 使用数组ledPins管理所有LED引脚,便于用循环统一操作,提高代码可维护性。如果想改变引脚分配,只需修改这个数组。
  • INPUT_PULLUP是Arduino提供的便利功能,省去了外部上拉电阻。此时,按钮的另一端必须接地。
  • randomSeed(analogRead(A0))是保证随机性的关键。每次上电,A0引脚上的模拟噪声都不同,从而产生不同的随机数序列起点。

4.2 骰子点数与LED映射算法

如何将1-6的数字映射到7个LED的点亮图案?最直接的方法是用一个二维数组来定义。

// 定义一个二维数组,每一行代表一个点数(1-6),每一列代表一个LED(顺序与ledPins[]对应) // 1表示点亮,0表示熄灭 const byte dicePatterns[6][7] = { {0, 0, 0, 0, 1, 0, 0}, // 点数1: 只点亮中心LED (索引4,假设为布局中心) {1, 0, 0, 0, 0, 0, 1}, // 点数2: 点亮左上和右下 {1, 0, 0, 0, 1, 0, 1}, // 点数3: 点数2 + 中心 {1, 1, 0, 0, 0, 1, 1}, // 点数4: 点亮四个角 {1, 1, 0, 0, 1, 1, 1}, // 点数5: 点数4 + 中心 {1, 1, 1, 1, 0, 1, 1} // 点数6: 点亮所有边(假设中间列为2,5,8,这里需根据实际布局调整) }; // 注意:以上数组需要根据你实际焊接的LED物理位置进行调整!

映射逻辑的建立

  1. 在纸上画出你的7个LED的物理布局图(例如,3x3矩阵去掉两个角)。
  2. 为每个LED编号(0-6),并记录其对应的Arduino引脚。
  3. 对照真实的骰子,画出点数1到6对应的LED点亮图。
  4. 将点亮图转化为0/1数组,填入dicePatterns

这是项目中最需要耐心和细心的一步。一个清晰的映射表是后续所有功能正确运行的基础。

4.3 主循环:状态检测与显示控制

void loop() { // 检测按钮是否被按下(低电平有效,因为启用了内部上拉) if (digitalRead(buttonPin) == LOW) { // 步骤1:软件防抖 delay(50); // 等待抖动过去 if (digitalRead(buttonPin) == LOW) { // 确认按下 // 步骤2:播放一个“滚动”动画,增加趣味性 playRollingAnimation(); // 步骤3:生成随机点数并显示 int diceNumber = random(1, 7); // 生成1到6之间的随机数 showNumber(diceNumber); // 步骤4:等待按钮释放(防止按住不放连续触发) while (digitalRead(buttonPin) == LOW) { // 可以在这里添加一个“保持显示”或微小延时,避免忙等待完全占用CPU delay(10); } delay(50); // 释放去抖 } } // 主循环可以在这里添加其他低优先级任务,如呼吸灯效果、待机省电等 }

关键点分析

  • random(1, 7)random(min, max)函数生成minmax-1之间的整数。所以这里是1-6。
  • playRollingAnimation()showNumber()是我们接下来要实现的函数。
  • 按钮释放检测和去抖同样重要,确保了单次按压只触发一次动作。

4.4 核心功能函数实现

动画函数:让LED快速随机闪烁,模拟骰子滚动。

void playRollingAnimation() { int animationDuration = 500; // 动画总时长500毫秒 int startTime = millis(); // 记录动画开始时间 while (millis() - startTime < animationDuration) { // 快速随机点亮部分LED for (int i = 0; i < ledCount; i++) { digitalWrite(ledPins[i], random(2)); // random(2)生成0或1 } delay(50); // 控制动画帧率 } // 动画结束,关闭所有LED allLEDsOff(); }

显示函数:根据点数点亮对应图案。

void showNumber(int number) { // 参数检查,确保输入在1-6之间 if (number < 1 || number > 6) { return; // 或者用默认图案(如全部点亮)表示错误 } // 先关闭所有LED allLEDsOff(); // 根据映射表点亮对应的LED int patternIndex = number - 1; // 数组索引从0开始 for (int i = 0; i < ledCount; i++) { if (dicePatterns[patternIndex][i] == 1) { digitalWrite(ledPins[i], HIGH); } } } void allLEDsOff() { for (int i = 0; i < ledCount; i++) { digitalWrite(ledPins[i], LOW); } }

代码优化思考:目前的playRollingAnimation使用了delay(50),在动画期间会阻塞程序。更流畅的非阻塞式动画可以利用millis()来定时切换LED状态,这样在“滚动”期间,按钮检测依然能响应(虽然在这个项目里需求不强)。这是从基础向进阶迈进时可以尝试的挑战。

5. 功能扩展与进阶优化思路

基础功能实现后,我们可以从多个维度让这个电子骰子变得更智能、更专业。

5.1 增加声音反馈

使用一个无源蜂鸣器或小型扬声器,配合tone()函数,可以在按钮按下时发出“嘀”声提示,在显示结果时播放一个简短的音阶,体验立刻提升一个档次。

连接:蜂鸣器正极接一个数字引脚(如10),负极接GND。注意电流,太大需加三极管驱动。代码:在playRollingAnimation中调用tone(10, 1000)播放滚动音,在showNumber后调用tone(10, 800, 200)播放结果音。

5.2 实现“双击”或“长按”功能

通过更精细的按钮状态检测(状态机),可以区分单击、双击和长按。例如:

  • 单击:掷一次骰子。
  • 双击:连续掷两次骰子并显示总和(适合飞行棋等游戏)。
  • 长按:进入“模式切换”,比如在6面骰子和20面骰子(用于桌游)之间切换。

这需要对loop()中的按钮检测逻辑进行重构,使用millis()来计时,并记录按下、释放的时间点。

5.3 使用移位寄存器重构电路

如前所述,使用74HC595可以解放大量I/O口。接线变为:

  • Arduino Pin 11 -> 74HC595 DS (数据)
  • Arduino Pin 12 -> 74HC595 STCP (锁存)
  • Arduino Pin 13 -> 74HC595 SHCP (时钟)
  • 74HC595的8个输出Q0-Q7连接7个LED(加一个限流电阻),剩余一个可接蜂鸣器或备用。

软件上,你需要学习使用shiftOut()函数来串行输出数据。显示函数showNumber的逻辑不变,只是将直接digitalWrite改为计算一个字节(byte)的值,然后通过shiftOut发送出去。

5.4 添加电池盒与电源开关,做成独立作品

找一个合适的盒子(如塑料收纳盒),将Arduino、面包板(或洞洞板)、电池盒(推荐4节AA电池盒,提供6V)安装进去。在盒子上开孔固定按钮和LED,让LED透出。增加一个拨动开关控制总电源。这样,一个无需连接电脑、可随身携带的电子骰子就完成了。

电源注意事项:电池电压(6V)高于Arduino Uno的推荐输入电压(5V)。虽然Uno板载稳压芯片可以处理,但长期使用可能发热。更稳妥的方案是使用3节AA电池(4.5V)或一个9V电池配合一个降压模块到5V。

6. 常见问题排查与调试实录

即使按照步骤操作,你也可能会遇到一些问题。这里是我在制作和教学中遇到的一些典型情况及其解决方法。

6.1 LED相关问题

问题1:LED完全不亮。

  • 检查顺序
    1. 电源:用万用表测量Arduino的5V和GND之间是否有5V电压?USB线是否插好?
    2. 回路:LED和电阻是否串联在引脚和GND之间?用万用表通断档检查。
    3. 极性:LED是否接反?调换LED两脚试试。
    4. 电阻值:电阻是否太大(如10kΩ)?计算一下电流是否微乎其微。尝试换一个220Ω电阻。
    5. 代码:确认setup()中设置了引脚为OUTPUT,并且loop()或显示函数里有输出HIGH的逻辑。可以用一个最简单的Blink程序单独测试这个引脚。

问题2:LED亮度很暗。

  • 原因:限流电阻过大,或LED本身质量/型号问题。
  • 解决:减小限流电阻(如从1kΩ换为220Ω),但不要低于计算的安全值。确保使用的是5V电源,如果使用3.3V系统(如某些开发板),亮度本身就会降低。

问题3:部分LED点亮图案错误。

  • 原因dicePatterns映射数组与实际的LED物理连接顺序不匹配。
  • 调试:写一个测试程序,按顺序(从ledPins[0]ledPins[6])逐个点亮LED,记录每个位置对应的物理LED。根据这个记录,修正dicePatterns数组中的0和1。

6.2 按钮相关问题

问题1:按钮不灵敏,或需要按很多次。

  • 原因:接触不良,或防抖延时设置过长/过短。
  • 解决:检查按钮焊接/插接是否牢固。调整delay(50)中的数值,从20ms到100ms尝试。

问题2:程序自己不断触发“掷骰子”,仿佛按钮一直被按下。

  • 原因1(使用外部上拉电阻时):上拉电阻未接或虚焊,导致引脚浮空。或者按钮接错线,将引脚直接接到了GND。
  • 原因2(使用INPUT_PULLUP时):按钮另一端没有接GND,而是接在了VCC上,形成了常低。
  • 解决:用万用表测量按钮未按下时,输入引脚对GND的电压。应该是接近5V(高电平)。如果是0V或不确定的电压,检查接线。

6.3 随机数相关问题

问题:每次重启后,掷出的前几个数字序列感觉有规律。

  • 原因analogRead(A0)作为种子可能熵源不足。如果A0引脚悬空但受到稳定干扰,或者电路上电过程电压稳定太快,可能导致初始读数变化不大。
  • 解决
    1. 增强熵源:将A0引脚通过一个1MΩ以上的大电阻接地(弱下拉),同时让它悬空。这样噪声更明显。
    2. 混合熵源randomSeed(analogRead(A0) + millis())millis()是系统运行时间,每次上电也不同。
    3. 多次采样
      long seed = 0; for (int i=0; i<10; i++) { seed += analogRead(A0); delay(1); } randomSeed(seed);

6.4 程序逻辑问题

问题:动画播放不流畅,或者按钮响应卡顿。

  • 原因:大量使用了delay()函数,导致程序阻塞。
  • 优化方向:学习使用状态机和基于millis()非阻塞定时。将动画的每一帧、按钮的按下/释放状态都定义为状态,在loop()中根据当前时间和状态进行切换,而不是用delay()等待。这是嵌入式编程中提升系统响应能力的关键技能。

这个项目从电路原理到代码编写,从基础功能到进阶优化,覆盖了嵌入式入门所需的核心技能点。它像一把钥匙,打开了一扇门,门后是更广阔的物联网和智能硬件世界。当你看到自己制作的骰子随着按键亮起随机的图案时,那种将代码逻辑转化为物理世界交互的成就感,正是驱动我们不断探索的动力。希望你在实现它的过程中,不仅收获了作品,更理解了背后每一个设计选择的缘由。

http://www.cnnetsun.cn/news/2737112.html

相关文章:

  • Oracle 19c静默安装踩坑实录:从“安装失败”到“完美启动”的7个关键检查点
  • 如何快速掌握CloudBeaver:云端数据库管理的终极指南
  • 从网页到电子书:WebToEpub如何解决网络阅读的三大痛点
  • 鸿蒙Flutter实战:MethodChannel桥接获取OHOS文件目录
  • 旧手机座充改造USB充电器:开关电源原理与DIY实战
  • 手把手教你用C语言实现Modbus RTU主机,从协议解析到代码调试(避坑指南)
  • 非公度边缘拓扑态:从体边对应到准周期边缘态的理论突破
  • 脑器官模块化系统与神经AI数字孪生技术解析
  • Python 爬虫实战:贝壳找房房源数据爬取与房价趋势分析
  • 一台服务器跑多个MongoDB?保姆级教程教你配置多实例,榨干服务器资源
  • 华为设备BGP邻居建立失败?手把手教你排查EBGP多跳与更新源配置问题
  • 3个步骤实现AI驱动的UE5场景自动化:UE5-MCP技术深度解析
  • B站缓存视频转换:5分钟学会m4s转MP4的终极方案
  • 三步揭秘SUSFS4KSU-Module:内核级Root隐藏的终极实战指南
  • 鸿蒙 PC 移植记:将微软的 `edit` 轻量级终端编辑器带到 OpenHarmony
  • 复旦大学LaTeX论文模板fduthesis:快速完成学术写作的终极指南
  • K8s 环境下大模型分布式训练的网络带宽优化:针对推理服务冷热备方案
  • 告别模糊:KVM GPU直通后Windows虚拟机分辨率上不去?试试这3个排查思路
  • 别再傻傻分不清了!一文搞懂GS1的GPC和UNSPSC分类标准到底怎么用
  • 告别重复造轮子:用SFUD库让你的STM32项目轻松兼容多种SPI Flash
  • STM32H743硬件FPU加速1024点FFT工程:含定时器精准测时与串口实时结果输出
  • 2026年适配维普降AIGC平台横评:亲测8款工具,将AIGC特征彻底弱化淡化
  • 告别付费OCR!手把手教你用LayoutLMv3+Python免费搞定PDF文字识别(附完整代码)
  • 从‘你好世界’到‘签名世界’:手把手用Python实现Schnorr签名(附完整代码)
  • 告别命令行恐惧:用ChatGPT+Python脚本,5分钟搞定网络拓扑自动规划
  • 塔机障碍物远距离超声测距方法与识别机理解析方案【附仿真】
  • Gemma-4-E2B-it-litert-lm iOS部署:在iPhone上运行私有AI模型的10个技巧
  • 解决Obsidian多端同步难题!打造 Obsidian 多设备同步与 AI 工作流
  • 如何免费实现高效离线OCR文字识别?Umi-OCR终极指南
  • 技术模拟的“四诊仪”,为何永远无法触及中医的灵魂?