Arduino I/O深入理解

作者:hardihuang   hardihuang   

想通过按键开关得到一个信号,然后控制一些别的东西
用的一段代码的片段:

pinMode(8,INPUT);
digitalWrite(8,HIGH);

问题1:我把8号引脚定义为输入引脚了,然后再把他定义为高电平,
总觉得我不是让它输入了吗,再digitalWrite不就输出了又,总觉得有点奇怪啊,
这个digitalWrite电气上是怎样的一个过程?

问题2:开关一头接地,一头接8号引脚,开关一按,8号引脚成功读出了低电平;
如果反过来,8号引脚digitalWrite为LOW,然后通过开关给8号接入个高电平,
8号又成功读出了高电平。。。

为什么这个输入引脚本来自己是有电压的(或高或低),却又被输入信号所决定?
貌似和问题一类似。read和write是怎么个过程? 实名反对拿STM32来讲解Arduino。STM32的IO口结构跟Arduino的AVR还是有区别的。根据STM32的数据手册来学习AVR会造成误导。引用@李赧郎 自己的一句话:

玩arduino的还是要老老实实的看单片机手册、学习硬件原理的,不看手册、不看原理上来就用自然会有问题。

首先要明确IO口的几种常见模式。对于数字IO而言,常见的模式有:推挽输出、开漏输出、浮空输入(高阻输入)、上拉输入、下拉输入。STM32支持上述的所有模式,但很多单片机并不是支持上述所有模式。比如Arduino采用的AVR单片机就不支持开漏输出和下拉输入。

第二要明确“输入阻抗”和“输出阻抗”的概念。例如当IO口输出一个高电平时,IO口内部并非像机械开关那样把一根线直接插到了电源正极上,IO引脚和电源正极之间其实还有电阻的存在。当这个电阻很小的时候,我们称之为“强”,当这个电阻较大时,我们称之为“弱”。这个电阻的一端当然接的是IO引脚,另一端如果接到电源正极,则成为上拉电阻,如果接地,则称为下拉电阻。

先说输出模式。对于Arduino,用pinMode将IO口设为OUTPUT的时候,其实IO的状态为“强推挽”,也就是说设为高电平时,IO口对电源正极的电阻比较小(强上拉),设为低电平时IO口对地的电阻也比较小(强下拉),这样IO口就具备了较强的驱动能力。其实也没有强到哪里去,大概几十毫安,能点亮LED而已。这里顺便提一下常见的51单片机,它的IO口总是接通了一个上拉电阻,这个上电阻比较大,所以称为弱上拉,所以51单片机的拉电流驱动能力(IO引脚高电平时电流从IO引脚流向外部电路的能力)比较弱,大概只有100μA左右,这通常只能让LED发出很微弱的光,所以51单片机IO口点亮LED的方式通常为灌电流(电流从外部电路流入IO引脚)。

再说输入模式。对于Arduino,用pinMode将IO口设为INPUT的时候,其实IO的状态为浮空输入,浮空输入也称高阻输入,也就是说输入阻抗非常高。理想状态下,可以认为输入阻抗是无穷大的,大到就像这个引脚断路了一样。就像一个浮在空中的金属丝一样,没有连上任何电路,你让它的电压是多少,它的电压就是多少。这样做是有意义的,因为只有输入阻抗足够大,才能接收到微弱的信号。如果输入阻抗不够大,比如输入端跟地之间有一个1kΩ的电阻,那微弱的输入信号很可能就被直接拉到0V,检测不出来了。但是浮空输入并非在任何情况下都是最好的选择,比如题主在接开关时。开关一头接IO口,一头接地。按下时把IO口拉到0V,读取,低电平,完全符合预期,赞!但是如果开关没有按下,读取,IO这时实际上没有接到任何地方,处于浮空状态,IO口上其实没有电压,或者说可能是任何的电压,这时读取IO口就会发现读到的值是不一定的。有时高有时低,用手摸一下那个引脚,都会使得读到的值抖动,傻眼了,咋办?

这时就需要上拉电阻来救场了,准确地说是弱上拉。Arduino的AVR单片机内置了上拉电阻,只要通过程序打开就可以,开启上拉电阻后,IO口会通过一个比较大的电阻(比如100kΩ)接到电源正极,尽管是比较大的电阻,但这个电阻仍然远远小于IO口浮空的输入阻抗,所以这个电阻就可以在IO口没有外部输入时把IO口的电平可靠维持在一个比较高的水平,读出的也都是高电平了。

这个时候就可以回答题主的问题了。

问题1问的是为什么可以对一个输入引脚执行write操作。答案就是,当对一个已经设定为输入状态的IO口digitalWrite为HIGH时,就会启动这个引脚的上拉电阻。没有为什么,Arduino在实现digitalWrite这个函数时就是这么写的。很扯淡对吧?这其实是一个历史遗留问题,Arduino在早期的版本中并未封装对上拉电阻的操作,在那个时候,你的这种开关电路只能在外部连接一个上拉电阻。在Arduino 1.0之后,才在其库中支持了上拉电阻。其实更好的方法是修改pinMode函数的实现,将INPUT分为INPUT_FLOATING和INPUT_PULLUP。但这就意味着之前所有玩家们开发的代码都需要修改后才能运行,于是Arduino就想出了这么一个歪招,用digitalWrite实现了上拉。需要注意的是,AVR不具备内置的下拉电阻,所以对已经设为INPUT的引脚digitalWrite为LOW,是没有任何效果的。

问题2是为什么可以对一个已经有电压的引脚再接入别的电压,从而改变其电压,而不会造成短路。答案就是4个字——“输出阻抗”。刚才说了在输入引脚上的上拉电阻是个弱上拉,所以当你把弱上拉到高电平的引脚接地时,就相当于把电源正极和地之间连接了一个很大的电阻(例如100kΩ),假设弱上拉电阻是100kΩ,根据欧姆定律,只有50μA的电流从中通过,完全不会带来伤害。但是如果你把设为输出状态的引脚设为高电平后再接地,情况就很不一样了。由于输出高电平时是强上拉的,直接接地的电流可能在50mA以上,超过了输入状态接地电流的1000倍。这其实就是所谓的短路了,所以一定不要这样做,一个引脚长时间短路是可能造成这个引脚甚至整个单片机损坏的。

其实我认为这个题目并不需要贴图解释,因为Arduino本身的设计初衷就是避免用户接触IO口内部结构这种底层细节。但是为了纠正其他知友的错误,我还是从真正的AVR数据手册里截了一张简化版的IO口等效电路图,其中的Rpu就是那个传说中的上拉电阻。


我拿STM32的管脚结构图改改示意一下,都差不多的。
能看到输入和输出是2条线,我打红圈的就是你模式控制的开光,上下开光是互斥的,一个打开另一个就关闭。
所以当你选择输入时,上面的线路就打开,外面的信号就可以通过TTL肖特基触发器进入单片机内部的输入数据寄存器,然后被你的程序判断是高还是低。
此时,你写digitalWrite(8,HIGH);,只是把一个高电压写到了输出数据寄存器,因为输出控制是关闭的,信号没有输出出去,所以不会和输入信号冲突。

另一种情况,如果管脚是输出模式,图中的P-mos或N-mos就要打开了,这时你如果强制管脚到一个不对的电压上是会损坏管脚的。比如管脚输出低,但被强行接到电源上。所以输入脚都一般穿个电阻来限流。

玩arduino的还是要老老实实的看单片机手册、学习硬件原理的,不看手册、不看原理上来就用自然会有问题。 Description
Write a HIGH or a LOW value to a digital pin.

If the pin has been configured as an OUTPUT with pinMode(), its voltage will be set to the corresponding value: 5V (or 3.3V on 3.3V boards) for HIGH, 0V (ground) for LOW.

如果引脚被设置为INPUT,digitalWrite()会激活输入引脚的上拉电阻。

If the pin is configured as an INPUT, digitalWrite() will enable (HIGH) or disable (LOW) the internal pullup on the input pin. It is recommended to set the pinMode() to INPUT_PULLUP to enable the internal pull-up resistor. See the digital pins tutorial for more information.

激活上拉电阻的重要应用之一便是检测开关是否按下。开关两端接pin和GND。当开关闭合时,digitalRead()==LOW。当开关断开时,无电流通过上拉电阻,无压降,digitalRead()==HIGH。

原理图如图片下半部分所示,debouncing capacitor用于减少虚碰造成的波动。因为ATmega芯片内置上拉电阻,实际接线按照上半部分接即可,省时省力。

为什么要上拉呢?如果不上拉,开关断开时pin处于悬空状态(i.e. tristate)。digitalRead()无法准确反映开关的状态。


如果没有将pinMode() 设置为OUTPUT,然后将一个LED连接至pin。当调用digitalWrite(HIGH)时,LED会很暗淡。因为如果没有明确的设置pinMode(),digitalWrite()将默认启用内部上拉电阻,这个电阻有很强的限流作用。

NOTE: If you do not set the pinMode() to OUTPUT, and connect an LED to a pin, when calling digitalWrite(HIGH), the LED may appear dim. Without explicitly setting pinMode(), digitalWrite() will have enabled the internal pull-up resistor, which acts like a large current-limiting resistor.

ATmega General Digital I/O 原理图

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注