振动马达 vs 振动传感器:Arduino 该选哪一个?

Cartoon of two vibration modules on white - a square black board carrying a round black coin motor with orange waves radiating from it, beside an elongated blue board with a black cylindrical spring-switch tube, a blue trimmer and a small chip, struck by an orange impact mark - with the corner of a blue Arduino Uno cropped at the lower left

有两个完全不同的零件,共用了「振动」这两个字。振动马达是输出——一颗让你的作品自己抖起来的币型马达;振动传感器是输入——一个会察觉别的东西在抖的弹簧开关。要做震动提示、静音警报、手感回馈,你要的是马达。要做敲击警报、防盗触发、判断东西有没有被碰倒,你要的是传感器。

你是要制造震动,还是要察觉震动?

PWM 振动马达模块是输出——一颗迷你直流振动马达,已经装在它自己那块小驱动板上。一块配重偏心地锁在扁平马达的轴上,所以每转一圈都会把这块配重甩向一边,整个组件就绕着自己的安装点抖动。手机的震动,从头到尾就是这么一回事。

SW-18010P 倾斜/振动传感器模块是输入,而大多数人搜「vibration alarm sensor」时真正想要的,其实就是它。它报告的是:有东西敲了、撞了、翻了或摔了它所锁着的那件东西。它自己抖不动任何东西,也完全不知道那一下有多重——它只知道,发生过一次。

你想做的东西 该用哪一个 为什么
静音提示、穿戴式震动、按键手感回馈、机器人震动效果 振动马达模块 由引脚去驱动的输出
敲击警报、防盗触发、装在专题盒/抽屉/门/停放的摩托车上的防拆侦测 振动传感器模块 受到撞击才动作的输入
「它有没有倒下/被移动过」的看守 振动传感器模块 这个弹簧开关对倾斜也会反应
侦测到敲击 → 手腕上震一下 两个都要 一段程序里同时有输入和输出——就是下面那个作品
有多强、哪个轴向、什么角度、什么频率 GY-521 加速度计 这两个振动零件都不做测量

还有两个很靠近的邻居:透过空气传过来的声音是麦克风的活(看我们的拍手开关指南),而一个人走过房间、什么都没碰到,那是PIR 人体感应

可选的两样

ItemPriceQty
Buzzer Active Passive Buzzer 5V Electronic Sound Alarm Module Tone Piezo - Active Buzzer ModuleBuzzer Active Passive Buzzer 5V Electronic Sound Alarm Module Tone Piezo - Active Buzzer ModuleACTIVBMRM2.50
Electronic Component Set Beginner Electrical Learning Kit For Arduino Beginner Learning - ELECTRICAL STARTER KITElectronic Component Set Beginner Electrical Learning Kit For Arduino Beginner Learning - ELECTRICAL STARTER KITELECTRKRM39.95

这两样都不是下面那段程序必需的。有源蜂鸣器模块会让敲击侦测器在隔壁房间也听得见;它丝印上写着「低电平触发」,所以程序要把它的 I-O 脚拉低才会响,这一点我们那篇有源与无源蜂鸣器指南讲得很清楚。电子学习套件走的是分立元件那条路:三极管、二极管、电阻,还有一块 830 孔面包板,拿来给一颗自己拆机得来的裸振动马达配三极管和续流二极管。

卡通对比图:左边是一个方形黑色币型马达模块,接收来自被裁切的蓝色 Arduino 的青色箭头,并向外辐射橙色波纹;右边是一个细长的蓝色传感器模块,上面有一支黑色弹簧管,被一记橙色撞击击中,并把一支青色箭头送回板子
左边是输出:板子命令那颗黑色币型马达模块,震动就出来了。右边是输入:有东西撞上蓝色模块的弹簧管,一份报告就送回板子。

为什么一颗 3 V–6 V 的马达不能直接挂在 Arduino 引脚上?

VIBRAGN 模块上的那颗币型马达额定 3–6 V,电流最高到 90 mA。而 ATmega328P 的一支 I/O 引脚,绝对最大值是 40.0 mA,而 Arduino 自己建议的工作值只有它的一半,20 mA。所以单是运转电流,就已经是硅片承受得住的两倍多——这还没算上另外两件让情况更糟的事。

第一件是起动。转动中的马达会产生反电动势,抵消它自己的供电、把电流压下来;而静止的马达一点反电动势也没有,所以电源接上去的那一瞬间,唯一的限流就只剩绕组的直流电阻。因此起动和堵转会拉走运转电流的好几倍——而所谓堵转,不过就是那块配重卡到一根线上而已。

第二件是停止。绕组是一个电感,而电感里的电流没办法瞬间改变,所以一切断供电,正在崩塌的磁场就会把那个节点的电压往上推,一直推到有东西导通为止。让 GPIO 引脚去当那个「有东西」,是很不划算的——而这正是绝大多数「我的板子一直重启」马达故事背后的元凶。

VIBRAGN 模块把这三件事全部挡在你的 Arduino 引脚之外,所以它引出来的是三支标着 IN、VCC、GND 的弯针,而不是两条马达线。币型马达和排针之间那一小堆零件就是一个驱动电路:一颗装在三脚 SOT-23 封装里的三极管,加上喂它基极、以及在它不该导通时把基极按住的那几颗电阻。VCC 和 GND 从 5 V 电源轨把马达电流带过来;IN 提供的只是零点几毫安的基极电流——那是一道指令,不是动力。真正做到这件事的是布线本身:运转电流、起动冲击、断电尖峰,全部在三极管的另一边、也就是 5 V 轨与地之间兜圈子,没有一条路能回到你接上 IN 的那支引脚。那支引脚碰到的只有基极,而且中间还隔着一颗电阻。

不过负载只是被搬走了,并没有消失,所以电源轨要算一算。USB 端口给整块板子 500 mA,Uno 自己的逻辑电路先吃掉其中几十毫安,所以一个 90 mA 的模块大约占掉这份预算的五分之一,很宽裕。麻烦是从第二颗马达、一个舵机,或者一米 LED 灯带来共用同一支 5 V 引脚开始的——到那时候,限制就不再是 I/O 引脚,而是电源轨和它的稳压器了。那种情况就给它们独立的 5 V 供电,然后把两边的地接在一起,因为 IN 是一个相对于地量出来的电压,两个从来没有碰过面的电源,等于没有共同的零点可以量。

卡通示意图:一个方形黑色币型马达模块由三条路径连到被裁切的蓝色 Arduino,其中一条是细如发丝、标着羽毛图示的青色信号路径,另外两条是标着哑铃图示的粗橙色电流路径,橙色回路穿过黑色币型马达和板子上那颗小小的黑色三极管
一条细如发丝的信号线,两条粗重的电流线。马达的电流是经过模块自己的三极管,从 5 V 电源轨取的;控制脚上只走一丝丝基极电流。

这就是为什么对 IN 用 analogWrite() 是安全的,而且 PWM 给你的东西比一支开关引脚多。三极管每秒把自己完全导通、完全关断上百次;马达的惯量和电感会把这些脉冲平均掉,所以在 5 V 轨上用 200/255 的占空比,马达感受到的大约就是 3.9 V,再扣掉三极管上那零点几伏。占空比调低,震得更轻,还是同一颗零件。

下限是机械上的。币型马达标称是 3 V 的零件,典型起动电压约 2.3 V,因为竖着放的时候,它第一圈必须把偏心配重从轴的上方硬撑过去。换算到 5 V 轨,大概是 255 里的 120;低于这个值,一颗停着的马达可能就杵在那里不动,不过一旦转起来,占空比再低一点它也照转。所以要「踢」它一下——满占空比大约 30 ms,然后掉到你要的安静档位。下面那段程序每一次震动都是这样开始的,这也就是为什么你可以把 BUZZ_DUTY 调到很含蓄的程度,而马达不会干脆罢工。

亲眼看一颗币型马达被板子驱动起来,比再写一段文字有用:

从旧手机里拆出来那颗只有两条线的裸马达,完全没有这些保护,所以那三个零件要你自己做:一颗三极管来扛电流,一颗算好阻值的基极电阻,让引脚只出一两毫安而不是马达那九十毫安,再加一颗反向跨接在马达两端的续流二极管,让崩塌的磁场把电流从二极管兜回去,而不是在三极管上打火。这三样在电子学习套件里都有。另一条路是现成的驱动板,例如 DRV8833 模块,它还多给你反转。我们那篇 MOSFET 模块指南讲的是同一套低边开关的想法。这里别用 L298N:它内部大约要吃掉两伏,而那已经是一颗 3 V 马达大半的供电了。

为什么传感器只响一下,而不是一直保持 HIGH?

蓝色模块上那颗 SW-18010P,是一根弹簧,而不是「会测量」那种意义上的传感器:一圈细弹簧包住一支中心针,一起封在黑色的小管子里。静止时弹簧圈是撑开的,整个零件就是断路——厂家给的数值是断开时超过 10 MΩ,闭合时低于 30 Ω。一记敲击把弹簧圈甩向一边碰到中心针,规格书把这次接通称为 2 ms 的导通时间。然后弹簧弹回来,电路又断开。

所以这个零件是一个开关:每被撞一下,从任何方向,它闭合大约两千分之一秒,而且没有任何东西会把它按住。轻敲一下,你得到的是一串毫秒级的闪烁,不是一个可以慢慢读的电平。缓慢的倾斜也是同一种方式被抓到的——弹簧圈在重力作用下靠上了中心针。

板上那颗 LM393 比较器做的是把这串东西整理干净,而不是去测量它。开关和一颗电阻组成分压,蓝色可调电阻设定比较器的参考电压,而比较器的输出是集电极开路的。它的输出三极管只能把 DO 拉到地,或者放手让板上的上拉电阻把它抬起来——它自己从来不会主动输出 HIGH。这两个状态哪一个是静止态,取决于弹簧开关接在比较器的哪一个输入端;在这块模块上,DO 静止时是 HIGH,每一次闭合都会在接触持续的那段时间里把它狠狠拉低,所以受到撞击时 DO 指示灯是闪一下,而不是就此亮着不放。

那颗可调电阻和第四支脚,能做的事都比看起来窄。可调电阻是拿一个门槛去对一个只有两种电平的信号,所以它设定的是比较器的余量,而不是把轻敲和重敲分出等级——拧过头它会让整块板子完全没反应,这也正是「模块好像坏了」最常见的原因。用最快的方法调它:让板子静止不动,转动螺丝直到 DO 指示灯熄灭并且保持熄灭,然后敲一下桌面——指示灯应该每敲一下闪一次。如果它静止时就一直亮着,或者你怎么敲它都不闪,那就是往某一个方向调过头了,退回四分之一圈。AO 也好不到哪里去。一根不是断就是通的管子,中间没有任何东西可以「模拟」,所以那支脚只会在静止时停在接近满量程的位置,接触发生时掉到底——就是同一个数字答案换了件慢一点的衣服,这也是为什么卖这块模块的人都会注明 AO 不可用。让它空着别接。

DO 静止时到底是哪一边,这件事值得写进程序里,而不是写进你的假设里,因为同一颗弹簧开关会出现在两种接法都有的板子上。下面那段程序自己把这件事定下来:它在开机时读一次引脚的静止电平,然后把任何偏离这个电平的情况当成一次敲击,所以两种接法它都对,一行都不用改。同一类模块在工作台上的实测:

怎样让一次敲击只算成一次事件?

振动马达模块和 SW-18010P 传感器模块引出来的都是公针,而 Arduino Uno 上是母排针,所以 20 cm 的公对母杜邦线就能把整个作品接完:不用面包板,也不用焊。

模块引脚 Arduino Uno 引脚 备注
马达 VCC 5V 马达电流从这里来,不是从 D9
马达 GND GND 和其他所有东西共地
马达 IN D9 必须是 PWM 引脚(3、5、6、9、10、11)
传感器 VCC 5V 模块 3.3–5 V 都能用
传感器 GND GND
传感器 DO D2 数字输出——程序盯着的那支脚,也是 Uno 的 INT0,将来想用中断就用它
传感器 AO 空着不接

接下来,先把两个模块固定好,再来评价它们。币型马达拿在空中,配重是对着空气在甩,你什么都感觉不到;用模块上那两个金属化安装孔锁到盒盖上,它会让整个盒子嗡嗡震。传感器是同一条规则的反面——它只接收得到它自己那块板子接收到的东西,所以一个用胶带贴在泡棉上、或者吊在杜邦线上晃的模块,会漏掉一颗锁死的模块抓得到的敲击。能分开的话就把两个装远一点,因为马达摇那个传感器,摇得和你的指关节一样实在,而下面那段程序把这件事看得很认真。

再来是软件那一半的问题。一记指节敲击会让弹簧抖动,所以引脚在几十毫秒里看到的是一堆散落的 2 ms 脉冲,一个单纯的计数器会把一次敲击报成十几次。把一次事件定义成:一次跳变,接着一段装聋的时间——在第一次读到与静止值不同的那一刻记下时间,然后在闭锁期结束之前不再理会这支引脚。

这个闭锁期要卡在两个数字之间。它必须撑得比一次撞击的余振久,又要短过你希望被分开计数的两次敲击之间的间隔,而 250 ms 舒舒服服地落在两者中间。这背后的道理,和我们那篇按钮开关指南里的按键消抖是同一个,只是窗口拉长了,因为弹簧余振的时间比锅仔片长。

还有一个陷阱是这一对零件独有的。你刚刚起动的那颗马达,正在摇着传感器所在的同一张桌子,所以一个在震动还没停的时候就重新待命的侦测器,会听见它自己、再触发一次,然后永远震下去——在一段天真的程序里把 BUZZ_MS 拉到比 LOCKOUT_MS 长,你看到的就正好是这个。两行就能把它关掉:马达在转的时候干脆不去看那支引脚,而且闭锁计时从马达停下来的那一刻重新起算,而不是从敲击到来的那一刻。这样恢复时间就诚实地等于震动时间加闭锁时间——这里是 150 ms 加 250 ms,也就是大约每秒可以认真敲两下。

剩下的靠两个习惯撑住。量出静止电平,而不是假设它是 HIGH,这样接法相反的板子程序也照样活;还有用 millis() 而不是 delay() 来计震动的时间,因为 delay 期间程序是聋的,落在里面的每一次敲击都丢了。这也是为什么 DO 放在 D2:它是 Uno 的 INT0 脚,等你的循环将来变重到会漏掉 2 ms 的脉冲,你可以把侦测搬进中断里,一根线都不用改。

/*
  敲进来,震出去 - MakerHub
  SW-18010P 传感器模块: VCC->5V, GND->GND, DO->D2  (AO 空着不接)
  PWM 振动马达模块: VCC->5V, GND->GND, IN->D9  (D9 必须是 PWM 引脚)
*/

const uint8_t SENSOR_PIN = 2;   // 传感器模块的 DO
const uint8_t MOTOR_PIN  = 9;   // 马达模块的 IN

const uint8_t  KICK_DUTY  = 255;  // 起步那一下给满占空比,把偏心配重踢动
const uint16_t KICK_MS    = 30;   // 这一踢持续多久
const uint8_t  BUZZ_DUTY  = 200;  // 踢完之后的 0-255。5V 轨上 200 平均约 3.9V
const uint16_t BUZZ_MS    = 150;  // 一次提示震动的总长度,含起步那一踢
const uint16_t LOCKOUT_MS = 250;  // 一次敲击算一次事件: 这段时间内的脉冲一律不看

int      idleLevel;               // 什么都没发生时 DO 读到的电平
uint32_t lastKnockMs;
uint32_t buzzStartedMs = 0;
bool     buzzing       = false;
uint32_t knockCount    = 0;

void setup() {
  Serial.begin(9600);
  pinMode(SENSOR_PIN, INPUT);
  pinMode(MOTOR_PIN, OUTPUT);
  analogWrite(MOTOR_PIN, 0);

  // 量出静止电平,而不是假设它。这一刻别去碰板子。
  delay(200);
  idleLevel = digitalRead(SENSOR_PIN);
  Serial.print(F("DO rests at: "));
  Serial.println(idleLevel == HIGH ? F("HIGH") : F("LOW"));

  // 一开机就待命;无符号数相减会正确回绕。
  lastKnockMs = millis() - LOCKOUT_MS;
}

void loop() {
  uint32_t now = millis();

  // 输入: 把一串毫秒级的接触脉冲收成一次事件 - 而且自己的马达在转的时候
  // 一律不看,否则这个警报会听见它自己。
  if (!buzzing && digitalRead(SENSOR_PIN) != idleLevel && (now - lastKnockMs) >= LOCKOUT_MS) {
    lastKnockMs = now;
    knockCount++;
    Serial.print(F("Knock #"));
    Serial.println(knockCount);

    analogWrite(MOTOR_PIN, KICK_DUTY);
    buzzStartedMs = now;
    buzzing = true;
  }

  // 输出: 踢一下、落到设定值、停 - 全部靠时钟走,所以这里没有一行会卡住循环。
  if (buzzing) {
    if ((now - buzzStartedMs) >= BUZZ_MS) {
      analogWrite(MOTOR_PIN, 0);
      buzzing = false;
      // 刚才是我们自己的马达在摇那个传感器。所以在这里重新起算装聋的
      // 那段时间,从震动停下来的这一刻算起。
      lastKnockMs = now;
    } else if ((now - buzzStartedMs) >= KICK_MS) {
      analogWrite(MOTOR_PIN, BUZZ_DUTY);
    }
  }
}

用 9600 baud 打开 Serial Monitor,在传感器附近敲敲桌子:每敲一下,一行带编号的输出,加一次短震动。如果一下还是算成两次,就把 LOCKOUT_MS 调大;如果两次敲击被并成一次,就调小。想让提示是听得见而不是感觉得到,就把一个有源蜂鸣器模块接到另一支引脚上并把它拉低驱动,这一段我们那篇有源与无源蜂鸣器指南讲得很清楚。

卡通时序图:上面一条深蓝色波形显示一记橙色撞击产生一串又密又乱的窄高脉冲,下面一条深蓝色波形则在它下方显示一个干净的宽方块,方块右侧延伸出一段淡青色的闭锁期阴影带
同一次敲击:上面是引脚看到的样子,下面是你的程序应该报出来的样子。淡色那一段是闭锁窗口,期间每一个后续的接触脉冲都被刻意忽略。

这两个零件都告诉不了你什么?

振动马达模块和 SW-18010P 传感器模块,都不测量任何东西。马达完全没有回授——你设一个占空比,然后祈祷。SW-18010P 回答的是「有」或「没有」,而且连这一点都不是打包票的:厂家给的是一百次开关动作里 98% 的导通率,所以大约每五十次轻碰会漏掉一次。这里靠安装赢过靠程序,因为一块用胶带贴在软表面上的板子,根本接收不到那一下冲击。

如果你的专题需要一个数字——多重、沿哪个轴、什么角度、什么频率——那答案是加速度计。搭载 MPU-6050 的 GY-521 模块透过 I²C 回传三轴加速度和三轴角速度,我们那篇 GY-521 倾角教程会带你读到实时数值;ADXL345 那一类零件是另一个常见答案。

我们常看到的错误

照着那个共用的字去下单。「振动模块」不是一个零件,而是两个工作完全相反、搜索结果又长得几乎一模一样的零件。先决定你的程序是要「说」还是要「问」,再去下单。

把拆机得来的裸振动马达直接接到引脚上。从旧手机里拆出来的那颗两线马达,看起来就是我们模块上那片圆盘,但它到手时完全没有那些让模块安全的电子零件。给它一颗三极管、一颗基极电阻和一颗续流二极管,或者给它一块驱动板。

去轮询一个电平,而不是去抓一个脉冲。会失败的写法是 if (digitalRead(DO) == LOW) { alarm(); } delay(100);——2 ms 的接触对上 100 ms 的盲区,所以大多数敲击都发生在程序睡着的时候。把 delay 删掉却不加闭锁期,结果反过来:一次余振被算成十几次。要盯的是跳变,然后闭锁。

常见问题

振动马达和振动传感器有什么分别?

振动马达是输出:一颗带偏心配重的币型马达,让你的作品抖起来,用来做震动提示。振动传感器是输入:一个弹簧开关,在别的东西摇它的时候报告一声,用来做敲击警报和防拆侦测。

振动马达可以直接接到 Arduino 引脚上吗?

裸马达不行:它的电流最高到 90 mA,而 ATmega328P 引脚的绝对最大值是 40 mA,另外还有更大的起动冲击和断电时的电感尖峰。PWM 振动马达模块之所以安全,是因为板上的三极管把那份电流从 5 V 电源轨扛走了,留给 IN 的只有一个信号。

为什么我敲一下,振动传感器却触发好多次?

因为 SW-18010P 是一根弹簧,每次接触闭合约 2 ms,而一次撞击会让它抖好几下。要数跳变而不是数电平,并且在每一次被接受的敲击之后,把这支引脚忽略大约 250 ms 的闭锁时间。

振动传感器的 DO 脚静止时是 HIGH 还是 LOW?

在这块模块上,DO 静止时是 HIGH,弹簧接触持续多久就被拉低多久。LM393 的集电极开路输出只会对着板上的上拉电阻往下拉,所以两个状态里哪一个是静止态,取决于比较器的输入是怎么接的,而这一族的板子两种接法都存在。在开机时读一次引脚的静止电平并以它为准来比较,别把 HIGH 写死在程序里。

振动传感器可以测出震动有多强吗?

不行。它是一个只给「有/没有」的撞击侦测器,而它的模拟脚也不是一个强度读数。要力度、轴向、角度或频率,你需要的是加速度计,例如 GY-521 板上的 MPU-6050。

最后更新:2026 年 8 月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *