单片机IO口的结构
准双向、开漏、推挽、高阻态。
实际上三极管是靠电流导通的,而MOS管是靠电压导通。
开漏输出和准双向IO的唯一区别就是开漏输出把内部的上拉电阻去掉了。
开漏输出如果要输出高电平时,T2关断,IO电平要靠外部的上拉电阻才能拉成高电平,如果没有外部上拉电阻IO电平就是一个不确定态。标准51单片机的P0口默认就是开漏输出,如果要用的时候外部需要加上拉电阻。
而强推挽输出就是有比较强的驱动能力,如图9-1中第三张小图,当内部输出一个高电平时,通过MOS管直接输出电流,没有电阻的限流,电流输出能力也比较大;如果内部输出一个低电平,那反向电流也可以很大,强推挽的一个特点就是驱动能力强。
单片机IO还有一种状态叫高阻态。通常用来做输入引脚的时候,可以将IO口设置成高阻态,高阻态引脚本身如果悬空,用万用表测量的时候可能是高可能是低,它的状态完全取决于外部输入信号的电平,高阻态引脚对GND的等效电阻很大(理论上相当于无穷大,但实际上总是有限值而非无穷大),所以称之为高阻。
上下拉电阻
上拉电阻就是将不确定的信号通过一个电阻拉到高电平,同时此电阻也起到一个限流作用,下拉就是下拉到低电平。
上拉电阻作用
-
OC门要输出高电平,必须外部加上拉电阻才能正常使用,其实OC门就相当于单片机IO的开漏输出,其原理可参照图9-1中的开漏电路。
-
加大普通IO口的驱动能力。标准51单片机的内部IO口的上拉电阻,一般都是在几十kΩ,比如STC89C52内部是20kΩ的上拉电阻,所以最大输出电流是250μA,因此外部加个上拉电阻,可以形成和内部上拉电阻的并联结构,增大高电平时电流的输出能力。
-
在电平转换电路中,比如前边讲的5V转12V的电路中,上拉电阻其实起到的是限流电阻的作用,见图3-8。
-
单片机中未使用的引脚,比如总线引脚和引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序造成什么影响,但通常会增加单片机的功耗,加上一个对VCC的上拉电阻或者一个对GND的下拉电阻后,可以有效地抵抗电磁干扰。
在进行电路设计的时候,又该如何选择合适的上、下拉电阻的阻值呢?
-
从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小。
-
从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。
-
在开漏输出时,过大的上拉电阻会导致信号上升沿变缓。这里解释一下:实际电平的变化都是需要时间的,虽然很小,但永远都达不到零,而开漏输出时上拉电阻的大小就直接影响了这个上升过程所需要的时间,如图9-2所示。想一下,如果电阻很大,而信号频率又很快,最终将导致信号还没等上升到高电平就又变为低的了,于是信号就无法正确传送了。
综合考虑各种情况,常用的上下拉电阻值大多选取在1~10kΩ之间,具体到底多大通常要根据实际需求来选取,只要在标准范围内就可以了,不一定是一个固定的值。
28BYJ-48型步进电机详解与实例
电机的分类
从用途角度可划分为驱动类电机和控制类电机。直流电机属于驱动类电机,步进电机属于控制类电机,它是将脉冲信号转换成一个转动角度的电机,在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数。
步进电机又分为反应式、永磁式和混合式三种。
- 反应式步进电机:结构简单成本低,但是动态性能差、效率低、发热大,可靠性难以保证,所以现在基本已经被淘汰了。
- 永磁式步进电机:动态性能好、输出力矩较大,但误差相对来说大一些,因其价格低而广泛应用于消费性产品。
- 混合式步进电机:综合了反应式和永磁式的优点,力矩大、动态性能好、步距角小、精度高,但结构相对来说复杂,价格也相对较高,主要应用于工业。
28BYJ-48步进电机,先介绍型号中包含的具体含义:
28——步进电机的有效最大外径是28mm。
B——表示是步进电机。
Y——表示是永磁式。
J——表示是减速型。
48——表示四相八拍。
28BYJ-48型步进电机原理详解
先来解释“4相永磁式”的概念,28BYJ-48的内部结构示意如图9-4所示。先看里圈,它上面有6个齿,分别标注为0~5,这个叫作转子,顾名思义,它是要转动的,转子的每个齿上都带有永久的磁性,是一块永磁体,这就是“永磁式”的概念。再看外圈,这个就是定子,它是保持不动的,实际上它是跟电机的外壳固定在一起的,上面有8个齿,而每个齿上都缠上了一个线圈绕组,正对着的两个齿上的绕组又是串联在一起的,也就是说正对着的两个绕组总是会同时导通或关断的,如此就形成了4相,在图中分别标注为A-B-C-D,这就是“4相”的概念。
一
假定电机的起始状态就如图9-4所示,逆时针方向转动,起始时是B相绕组的开关闭合,B相绕组导通,那么导通电流就会在正上和正下两个定子齿上产生磁性,这两个定子齿上的磁性就会对转子上的0和3号齿产生最强的吸引力,就会如图9-4所示的那样,转子的0号齿在正上、3号齿在正下而处于平衡状态;此时会发现,转子的1号齿与右上的定子齿也就是C相的一个绕组呈现一个很小的夹角,2号齿与右边的定子齿也就是D相绕组呈现一个稍微大一点的夹角,很明显这个夹角是1号齿和C绕组夹角的2倍,同理,左侧的情况也是一样的。
接下来把B相绕组断开,而使C相绕组导通,那么很明显,右上的定子齿将对转子1号齿产生最大的吸引力,而左下的定子齿将对转子4号齿,产生最大的吸引力,在这个吸引力的作用下,转子1、4号齿将对齐到右上和左下的定子齿上而保持平衡,这样,转子就转过了起始状态时1号齿和C相绕组那个夹角的角度。
再接下来断开C相绕组,导通D相绕组,过程与上述的情况完全相同,最终将使转子2、5号齿与定子D相绕组对齐,转子又转过了上述同样的角度。
很明显,当A相绕组再次导通,即完成一个B-C-D-A的四节拍操作后,转子的0、3号齿将由原来的对齐到上下两个定子齿,而变为了对齐到左上和右下的两个定子齿上,即转子转过了一个定子齿的角度。以此类推,再来一个四节拍,转子就将再转过一个齿的角度,8个四节拍以后转子将转过完整的一圈,而其中单个节拍使转子转过的角度就很容易计算出来了,即360°/(8×4)=11.25°,这个值就叫作步进角度。而上述这种工作模式就是步进电机的单四拍模式——单相绕组通电四节拍。
二
再来讲解一种具有更优性能的工作模式,那就是在单四拍的每两个节拍之间再插入一个双绕组导通的中间节拍,组成八拍模式。比如,在从B相导通到C项导通的过程中,假如一个B相和C相同时导通的节拍,这个时候,由于B、C两个绕组的定子齿对它们附近的转子齿同时产生相同的吸引力,这将导致这两个转子齿的中心线对比到B、C两个绕组的中心线上,也就是新插入的这个节拍使转子转过了上述单四拍模式中步进角度的一半,即5.625°。这样一来,就使转动精度增加了一倍,而转子转动一圈则需要8×8=64拍了。另外,新增加的这个中间节拍还会在原来单四拍的两个节拍引力之间又加了一把引力,从而可以大大增加电机的整体扭力输出,使电机更“有劲”了。
三
除了上述的单四拍和八拍的工作模式外,还有一个双四拍的工作模式——双绕组通电四节拍。其实就是把八拍模式中的两个绕组同时通电的那四拍单独拿出来,而舍弃掉单绕组通电的那四拍而已。其步进角度同单四拍是一样的,但由于它是两个绕组同时导通,所以扭矩会比单四拍模式大。
让电机转起来
再重新看一下上面的步进电机外观图和内部结构图:步进电机一共有5根引线,其中红色的是公共端,连接到5V电源,接下来的橙、黄、粉、蓝就对应了A、B、C、D相;如果要导通A相绕组,就只需将橙色线接地即可,B相则黄色接地,以此类推;再根据上述单四拍和八拍工作过程的讲解,可以得出下面的绕组控制顺序表
诚然,单片机的IO口可以直接输出0V和5V的电压,但是电流驱动能力,也就是带载能力非常有限,所以在每相的控制线上都增加一个三极管来提高驱动能力。若要使A相导通,则必须是Q2导通,此时A相也就是橙色线就相当于接地了,于是A相绕组导通,此时单片机P1口低4位应输出0b1110,即0xE;如要A、B相同时导通,就是Q2、Q3导通,P1口低4位应输出0b1100,即0xC,以此类推,可以得到下面的八拍节拍的IO控制代码数组:
unsigned char code BeatCode[8]={0xe,0xc,0xd,0x9,0xb,0x3,0x7,0x6};
启动频率就是步进电机在空载情况下能够正常启动的最高脉冲频率,如果脉冲频率高于该值,电机就不能正常启动。
#include<REG52.H>
unsigned char code BeatCode[8]={0xe,0xc,0xd,0x9,0xb,0x3,0x7,0x6};
void Delay2ms() //@12MHz
{
unsigned char i, j;
i = 24;
j = 85;
do
{
while (--j);
} while (--i);
}
void main(){
unsigned char tmp,index =0;
while(1){
tmp = P1;
tmp = tmp & 0xf0;//清零低四位
tmp = tmp | BeatCode[index];//把节拍代码写到低四位
P1= tmp;//把低四位节拍代码和高四位原值送回P1
index ++;//节拍输出索引递增
index = index & 0x07;//到8归零
Delay2ms();
}
}
转动精度与深入分析
了解“永磁式减速步进电机”中这个“减速”的概念了。
一共就有4级减速。
回头看一下电机参数表中的减速比这个参数吧——1∶64,转子转64圈,最终输出轴才会转一圈,也就是需要64×64=4096个节拍输出轴才转过一圈,2ms×4096=8192ms,8秒多才转一圈呢,是不是跟刚才的实验结果正好吻合了?4096个节拍转动一圈,那么一个节拍转动的角度——步进角度就是360/4096,看一下表中的步进角度参数5.625/64,算一下就知道这两个值相等,一切都已吻合了。
厂家给出的减速比是1∶64,不管是哪个厂家生产的电机,只要型号是28BYJ-48,其标称的减速比就都是1∶64。但实际上呢?经过拆解计算发现:真实准确的减速比并不是这个值1∶64,而是1∶63.684。得回到实际应用中,步进电机最通常的目的是控制目标转过一定的角度,通常都是在360°以内,而28BYJ-48最初的设计目的是用来控制空调的扇叶的,扇叶的活动范围是不会超过180°,所以在这种应用场合下,厂商给出一个近似的整数减速比1∶64已经足够精确了,这也是合情合理的。
#include<REG52.H>
void TurnMotor(unsigned long angle);
void main(){
TurnMotor(360*25);
while(1);
}
void Delay2ms() //@12MHz
{
unsigned char i, j;
i = 24;
j = 85;
do
{
while (--j);
} while (--i);
}
void TurnMotor(unsigned long angle){
unsigned char tmp;
unsigned char index = 0;
unsigned long beats = 0;
unsigned char code BeatCode[8]={0xe,0xc,0xd,0x9,0xb,0x3,0x7,0x6};
beats = (angle * 4096) / 360;
while(beats --){
tmp = P1;
tmp = tmp & 0xf0;
tmp = tmp | BeatCode[index];
P1 = tmp;
index ++;
index = index &0x07;
Delay2ms();
}
P1 = P1 |0x0f;
}
编写实用程序的基础
#include<REG52.H>
unsigned long beats = 0;
void StartMotor(unsigned long angle);
void main(){
EA=1;
TMOD = 0x01;
TH0 = 0x01;
TL0 = 0xcd;
ET0=1;
TR0=1;
StartMotor(360*2+180);
while(1);
}
void StartMotor(unsigned long angle){
EA=0;
beats = (angle * 4076) / 360;
EA =1;
}
void InterruptTimer0() interrupt 1{
unsigned char tmp;
static unsigned char index = 0;
unsigned char code BeatCode[8]={0xe,0xc,0xd,0x9,0xb,0x3,0x7,0x6};
TH0 = 0xf8;
TL0 = 0xcd;
if(beats != 0){
tmp = P1;
tmp = tmp & 0xf0;
tmp = tmp | BeatCode[index];
P1 = tmp;
index ++;
index = index & 0x07;
beats --;
}else{
P1 = P1 | 0x0f;
}
}
包含综合应用的实用程序
还需要修改,原理如下
#include <REG52.H>
sbit KEY_IN_1 = P1 ^ 4;
sbit KEY_IN_2 = P1 ^ 5;
sbit KEY_IN_3 = P1 ^ 6;
sbit KEY_IN_4 = P1 ^ 7;
sbit KEY_OUT_1 = P1 ^ 3;
sbit KEY_OUT_2 = P1 ^ 2;
sbit KEY_OUT_3 = P1 ^ 1;
sbit KEY_OUT_4 = P1 ^ 0;
unsigned char code KeyCodeMap[4][4] = {
// mapping table of matrix key numbers to standard keyboard key codes
{0x31, 0x32, 0x33, 0x26}, // 1,2,3,up key
{0x34, 0x35, 0x36, 0x25}, // 4,5,6,left key
{0x37, 0x38, 0x39, 0x28}, // 7,8,9,down key
{0x30, 0x1b, 0x0d, 0x27}, // 0,ESC,Enter,right key
};
unsigned char KeySta[4][4] = {
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
};
long beats = 0;
void KeyDriver();
void main()
{
EA = 1;
TMOD = 0x01;
TH0 = 0xfc;
TL0 = 0x67;
ET0 = 1;
TR0 = 1;
while (1) {
KeyDriver();
}
}
void StartMotor(unsigned long angle)
{
EA = 0;
beats = (angle * 4076) / 360;
EA = 1;
}
void StopMotor()
{
EA = 0;
beats = 0;
EA = 1;
}
void KeyAction(unsigned char keycode)
{
static bit dirMotor = 0;
if ((keycode >= 0x30) && (keycode <= 0x39)) {
if (dirMotor == 0)
StartMotor(360 * (keycode - 0x30));
else
StartMotor(-360 * (keycode - 0x30));
} else if (keycode == 0x28) {
dirMotor = 1;
} else if (keycode == 0x25) {
StartMotor(90);
} else if (keycode == 0x27) {
StartMotor(-90);
} else if (keycode == 0x18) {
StopMotor();
}
}
void KeyDriver()
{
unsigned char i, j;
static unsigned char backup[4][4] = {
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
{1, 1, 1, 1},
};
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
if (backup[i][j] != KeySta[i][j]) {
if (backup[i][j] != 0) {
KeyAction(KeyCodeMap[i][j]);
}
backup[i][j] = KeySta[i][j];
}
}
}
}
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0;
static unsigned char keybuf[4][4] = {
{0xff, 0xff, 0xff, 0xff},
{0xff, 0xff, 0xff, 0xff},
{0xff, 0xff, 0xff, 0xff},
{0xff, 0xff, 0xff, 0xff},
};
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
for (i = 0; i < 4; i++) {
if ((keybuf[keyout][i] & 0x0f) == 0x00) {
KeySta[keyout][i] = 0;
} else if ((keybuf[keyout][i] & 0x0f) == 0x0f) {
KeySta[keyout][i] = 1;
}
}
keyout++;
keyout = keyout & 0x03;
switch (keyout) {
case 0:
KEY_IN_4 = 1;
KEY_OUT_1 = 0;
break;
case 1:
KEY_IN_1 = 1;
KEY_OUT_2 = 0;
break;
case 2:
KEY_OUT_2 = 1;
KEY_OUT_3 = 0;
break;
case 3:
KEY_OUT_3 = 1;
KEY_OUT_4 = 0;
break;
default:
break;
}
}
void TurnMotor()
{
unsigned char tmp;
unsigned char index = 0;
unsigned char code BeatCode[8] = {0xe, 0xc, 0xd, 0x9, 0xb, 0x3, 0x7, 0x6};
if (beats != 0) {
if (beats > 0) {
index++;
index = index & 0x07;
beats--;
} else {
index--;
index = index & 0x07;
beats++;
}
tmp = P1;
tmp = tmp & 0xf0;
tmp = tmp | BeatCode[index];
P1 = tmp;
} else {
P1 = P1 | 0x0f;
}
}
void InterruptTimer0() interrupt 1
{
static bit div = 0;
TH0 = 0xfc;
TL0 = 0x67;
KeyScan();
div = ~div;
if (div == 1) {
TurnMotor();
}
}
蜂鸣器
蜂鸣器从结构上可分为压电式蜂鸣器和电磁式蜂鸣器。压电式为压电陶瓷片发音,电流比较小一些,电磁式蜂鸣器为线圈通电震动发音,体积比较小。
按照驱动方式分为有源蜂鸣器和无源蜂鸣器。这里的有源和无源不是指电源,而是振荡源。有源蜂鸣器内部带了振荡源。而无源蜂鸣器内部是不带振荡源的,要让它响必须给500Hz~4.5kHz之间的脉冲频率信号来驱动它才会响。、
生日快乐歌:
#include <reg51.h>
#define u8 unsigned char
#define u16 unsigned int
sbit BEEP = P2 ^ 5;
void delay_ms(u16 x)
{
u16 i, j;
for (i = 0; i < x; i++)
for (j = 0; j < 115; j++)
;
}
u8 code SONG_TONE[] = {212, 212, 190, 212, 159, 169, 212, 212, 190, 212, 142, 159, 212, 212, 106, 126, 159, 169, 190, 119, 119, 126, 159, 142, 159, 0};
u8 code SONG_LONG[] = {9, 3, 12, 12, 12, 24, 9, 3, 12, 12, 12, 24, 9, 3, 12, 12, 12, 12, 12, 9, 3, 12, 12, 12, 24, 0};
void PlayMusic()
{
u16 i = 0, j, k;
while (SONG_LONG[i] != 0 || SONG_TONE[i] != 0) {
for (j = 0; j < SONG_LONG[i] * 20; j++) {
BEEP = ~BEEP;
for (k = 0; k < SONG_TONE[i] / 3; k++)
;
}
delay_ms(10);
i++;
}
}
void main()
{
while (1) { PlayMusic(); }
}