在进行嵌入式系统设计时,我们经常需要用单片机(Microcontroller 微控制器或称为MCU)的通用输入/输出(GPIO)引脚来驱动LED、继电器等外围设备。老wu还记得大学时写的第一份代码就是通过51单片机来控制LED灯的闪烁。不知现在的大学课程里还保留有51单片机点灯编程实验否,还是已经不再玩51,入门就是STM32这样更高级些的单片机了?
简单贴一下通过51单片机来控制LED灯的闪烁的C代码(ps: 这不是老wu上学时写的第一份代码哈,存有代码的硬盘已经找不到了,一起消失的还有许多美好的记忆瞬间
)
#include // 包含8051特殊功能寄存器声明头文件sbit LED = P2^0; // 定义LED连接到P2.0引脚void Delay(void); // 延时函数声明void main(void){ while(1) // 无限循环 { LED = 0; // LED点亮(低电平有效) Delay(); // 延时 LED = 1; // LED熄灭(高电平) Delay(); // 延时 }}// 软件延时函数void Delay(void){ int j; int i; for(i=0; i10; i++) { for(j=0; j10000; j++) { // 空循环产生延时 } }}因为是人生中的第一份单片机代码,程序逻辑怎么简单就怎么来:
1. 头文件包含: #include 包含了8051系列单片机的寄存器定义
2. 引脚定义: sbit LED = P2^0; 将P2.0引脚定义为LED控制引脚
3. 主函数逻辑:
使用无限循环while(1)
LED = 0 点亮LED(采用低电平点亮LED)
LED = 1 熄灭LED
通过Delay()函数控制闪烁频率
4. 延时函数: 使用双重for循环产生软件延时,程序在这里空跑,纯粹消耗指令时间,更好的做法是使用定时器产生中断来控制LED的闪烁,期间MCU可以去处理其他的逻辑运算
这段古老的51单片机代码,其程序逻辑也适用于更现代更高级的STM32单片机,只是STM32在初始化时会更麻烦些,要使能GPIO的时钟,设置GPIO的输出模式等。
在程序逻辑方面,51单片机与STM32单片机并没有区别,但在硬件电路方面,倒是有些讲究的,特别是你平时用的自己买的STM32开发板做实验,而到了学校实验考核时,切换到学校古董级的51单片机开发板,这两种芯片间硬件层面的差异,往往让只顾着写代码的同学会有些摸不着头脑。
[img][/img]
单就用GPIO来点亮LED的电路来说,一个常见的问题是,驱动电路应该设计成高电平点亮还是低电平点亮?
高电平逻辑驱动:MCU引脚输出高电平(’H’)时,电流从MCU流向LED,点亮LED。
低电平逻辑驱动:MCU引脚输出低电平(’L’)时,电流从电源经LED流向MCU,点亮LED。
直观上看,这两种方式似乎没有区别,至少对于STM32这样的单片机来说是没有区别的,但对于51单片机来说,他们在电流驱动能力上却并不相同。
8051的GPIO是“准双向IO”
经典的8051单片机(特指P1, P2, P3口)并没有真正的推挽结构。它的I/O口被称为准双向口(Quasi-bidirectional Port)。其内部结构是非对称的:
一个弱上拉电阻(Weak Pull-up Resistor)。
一个强下拉N-MOS管(Strong Pull-down N-MOS)。
当向51单片机的GPIO端口对应的引脚写入“1”(高电平)时:下管N-MOS截止,此时仅靠微弱的内部上拉电阻将引脚电平拉高。这个“上拉”的动作非常无力,只能提供很小的电流。最大输出电流通常仅有约 0.1-0.3mA(毫安),这是由于其内部采用弱上拉电阻设计(典型值约 20kΩ-50kΩ)。
计算可得最大输出电流:
实际电流因型号和温度略有浮动,但普遍低于 0.5mA,微弱的内部上拉电阻完全无法提供驱动LED所需的毫安级电流,会导致LED非常暗甚至点不亮。
当向51单片机GPIO端口的引脚写入“0”(低电平)时:下管N-MOS导通,强力地将引脚电平“拉”向地。这个“拉”的动作非常有力,可以“灌入”较大的电流,毫安级别,点亮LED灯没问题,但也不是非常强悍的水平,STC89C51的5V单片机的P0口的灌电流最大为 12 mA ,其他I/0口的灌电流最大为 6 mA 。 STC89LE51的3V单片机的P0口的灌电流最大为 8 mA ,其他II0口的灌电流最大为 4 mA 。
可见51单片机的灌电流并不是非常强悍,仅能直接驱动小功率负载(如低亮度 LED、小尺寸数码管),如果要驱动继电器、蜂鸣器等需外接三极管或 MOS 管放大电流。
STM32的“推挽”模式具有强劲的驱动电流
STM32 GPIO 推挽模式电流流向图STM32的GPIO在配置为输出时,最常用的就是“推挽(Push-pull)”模式。其名称生动地描述了它的内部结构:由一个P-MOS管(上管)和N-MOS管(下管)组成。
推(Push):当需要输出高电平时,上管P-MOS导通,下管N-MOS截止。P-MOS管像一只手,主动、强力地将引脚电压“推”向电源(VDD),能够提供(Source)较大的电流。
拉(Pull):当需要输出低电平时,下管N-MOS导通,上管P-MOS截止。N-MOS管像另一只手,主动、强力地将引脚电压“拉”向地(GND),能够吸纳(Sink)较大的电流。
STM32得益于半导体工艺的进步,能够为每个引脚都配备功能更强、更直观的真推挽结构。无论是输出高电平还是低电平,都有一个强大的晶体管在主动工作。能提供和吸纳几乎同等级别的电流,驱动能力强劲且均衡。
如STM32 Datasheet中电流规格表所示,GPIO向外推的电流和向里灌的电流最大都是25mA,都能让LED获得足够的亮度。
为何有时“灌电流”能力更强?经典8051刚出来时,当时的工艺还不能实现高密度的晶体管集成,或者说在硅片上光刻晶体管会比较贵,所以当时设计经典的8051的智慧在于用更少的晶体管实现了一种巧妙的、能读能写的“准双向”口,这在当年是极具成本效益的创新。而大多数CMOS逻辑IC的输出级都采用“推挽式”(Push-Pull)结构,由一个P沟道MOSFET和一个N沟道MOSFET组成。
当输出高电平时,上方的P-MOS管导通,将引脚“推”向电源电压(Vcc)。
当输出低电平时,下方的N-MOS管导通,将引脚“拉”向地(GND)。
关键在于,由于半导体物理特性的差异,N沟道MOSFET的导通电阻通常比P沟道MOSFET的更小。
在由外向里“灌”(Sinking)电流时,内部压降更小,允许通过的电流更大。
在由里向外“推”(Sourcing)电流时,内部压降更大,限制了其输出电流的能力。
简而言之,经典8051单片机为了节约晶体管,采用的“弱上拉电阻”+“强下拉N-MOS管”的方式,造成8051的驱动电流能力不对称,点亮LED需要用“灌电流”的方式。
由于意法半导体自有晶圆厂的优秀工艺,STM32单片机的“灌电流”和“推电流”的能力是一致的,都是最大25mA,而一些CMOS 逻辑IC,比如小厂的74HC04逻辑IC,有可能“灌电流”的方式比“推电流”的方式驱动能力更强。(ps:采购买到杂牌IC的苦大家应该都懂的)
如何确认IC的驱动能力?作为工程师,我们不能只凭经验。有两种方法可以确定一个IC的具体驱动能力:
查阅数据手册(Datasheet):数据手册是金标准。你需要关注电气特性中的V_OH(高电平输出电压)和V_OL(低电平输出电压)参数。这些参数通常会在特定的输出电流(I_OH和I_OL)条件下给出。通过这些值,你可以估算出IC在高电平和低电平输出时的等效导通电阻,并了解其最大灌/推电流能力。切记,不要超过“绝对最大额定值”(Absolute Maximum Ratings)中规定的电流限制。
使用SPICE模型进行仿真:对于更精确的分析,可以寻找芯片制造商提供的SPICE模型。通过在仿真软件中搭建测试电路,可以直观地得到IC的完整I-V特性曲线,从而为设计提供精确的数据支持。
[/ol]实践中的注意事项基于以上分析,我们在进行微控制器外围电路设计时,应注意以下几点:
优先使用低电平逻辑驱动:对于需要较大电流或期望获得最佳性能的负载(如高亮度LED、光耦等),应优先设计为低电平有效,即采用“灌电流”的方式。
驱动大功率负载:MCU的GPIO引脚电流能力有限(通常在几mA到几十mA,STM32 GPIO最大的电流位25mA)。当需要驱动电机、大功率继电器等负载时,绝不能直接连接。必须使用外部驱动电路,如MOSFET或专用的驱动IC,来放大电流。此时,MCU引脚同样可以低电平驱动外部N-MOSFET的栅极,这通常比驱动P-MOSFET更简单高效。
仔细阅读数据手册:不同型号、不同系列的MCU,其GPIO的驱动能力千差万别。设计前务必仔细阅读目标器件的数据手册,明确其I_OL和I_OH的规格。
让采购千万别买杂牌IC。最后,祝大家好运,设计时能避免踩坑,同时不被采购买到的杂牌IC坑到,以上
#include // 包含8051特殊功能寄存器声明头文件 sbit LED = P2^0; // 定义LED连接到P2.0引脚 void Delay(void); // 延时函数声明 void main(void) { while(1) // 无限循环 { LED = 0; // LED点亮(低电平有效) Delay(); // 延时 LED = 1; // LED熄灭(高电平) Delay(); // 延时 } } // 软件延时函数 void Delay(void) { int j; int i; for(i=0; i#include // 包含8051特殊功能寄存器声明头文件 sbit LED = P2^0; // 定义LED连接到P2.0引脚 void Delay(void); // 延时函数声明 void main(void) { while(1) // 无限循环 { LED = 0; // LED点亮(低电平有效) Delay(); // 延时 LED = 1; // LED熄灭(高电平) Delay(); // 延时 } } // 软件延时函数 void Delay(void) { int j; int i; for(i=0; i |