步进电机与蜂鸣器

单片机I/O口的结构

单片机的I/O口结构有四种状态:

  • 准双向I/O口
  • 开漏输出
  • 推挽输出
  • 高阻态

准双向I/O口

图9-1-1 准双向IO口结构示意图
准双向IO口

开漏输出

图9-1-2 开漏输出结构示意图
开漏输出和准双向I/O的唯一区别,就是开漏输出把内部的上拉电阻去掉了。
开漏输出如果要输出高电平时,T2关断,I/O口电平要靠外部的上拉电阻才能拉成高电平,如果没有外部的上拉电阻I/O口电平就是一个不确定态。
标准的51单片机的P0口默认就是开漏输出,如果要用的时候外部需要加上上拉电阻。

强推挽输出

9-1-3 强推挽输出结构示意图
强推挽输出就是由比较强的驱动力,如图,当内部输出一个高电平时,通过MOS管直接输出电流,没有电阻的限流,电流输出能力也比较大;如果内部输出一个低电平时,那反向电流也可以很大,强推挽的一个特点就是驱动能力很强。

高阻态

单片机I/O口还有一种状态叫高阻态,通常用来做输入引脚的时候,可以将I/O口设置成高阻态,高阻态引脚本身如果悬空,用万用表测量的时候可能是高可能是低,它的状态完全取决于外部输入信号的电平,高阻态引脚对GND的等效电阻很大(理论上相当于无穷大,但是实际上总是有限值而非无穷大),所以称之为高阻。

上下拉电阻

上拉电阻就是将不确定的信号通过一个电阻拉到高电平,同时此电阻也起到一个限流作用,下拉就是下拉低电平。
例如I/O口设置为开漏输出高电平或者是高阻态时,默认的电平就是不确定的,外部经一个电阻接到VCC,也就是上拉电阻,那么相应的引脚就是高电平;经过一个电阻到GND,也就是下拉电阻,那么相应的引脚就是一个低电平。

上下拉电阻的应用

上拉电阻的应用有很多,最常用的四种:

  • OC门要输出高电平,必须外部加上拉电阻才能正常使用,其实OC门就相当于单片机I/O口的开漏输出
  • 加大普通I/O口的驱动能力。标准51单片机的内部I/O口的上拉电阻,一般都是在加几万欧姆,比如STC89C52内部是20K欧姆的上拉电阻,所以最大输出电流是250uA,因此外部加个上拉电阻,可以形成和内部上拉电阻的并联结构,增大高电平时电流的输出
  • 在电平转换电路中,例如5V转12V的电路中,上拉电阻其实起到的是限流电阻的作用
  • 单片机中未使用的引脚,比如总线引脚和引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序造成什么影响,但是通常会增加单片机的功耗,加上一个对VCC的上拉电阻或一个对GND的下拉电阻后,可以有效的抵抗电磁干扰。

上下拉电阻的阻值的选择

  • 从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小
  • 从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大
  • 在开漏输出时,过大的上拉电阻会导致信号上升沿变缓。实际电平的变化都是需要时间的,虽然很小,但永远都达不到零,而开漏输出时上拉电阻的大小就直接影响了这个上升过程所需要的时间。如果电阻很大,而信号频率又很快,最终导致信号还没等上升到高电平就又变低了,于是信号就无法正确传送了。
    图9-2 上拉电阻阻值对波形的影响
    常用的上下拉电阻值多选在 1 ~ 10k 欧姆,具体到底多大通常要根据实际需求来选取,只要在标准范围内就可以了,不一定是一个固定的值。

28BYJ-48型步进电机详解与实例

电机的分类

电机的分类方式有很多,从用途角度可划分为驱动类电机和控制类电机。
直流电机属于驱动类电机,这种电机是将电能转换为机械能,主要应用在电钻、小车轮子、电风扇、洗衣机等设备。
步进电机属于控制类电机,它是将脉冲信号转换成一个转动角度的电机,在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,主要应用在自动化仪表、机器人、自动生产流水线、空调扇叶转动等设备。

步进电机又分为反应式、永磁式和混合式三种

  • 反应式步进电机:结构简单成本低,但是动态性能差、效率低、发热大,可靠性难以保证,所以现在基本上被淘汰了
  • 永磁式步进电机:动态性能好、输出力矩较大,但误差相对来说大一些,因其价格低而广泛应用于消费性产品
  • 混合式步进电机:综合了反应式和永磁式的优点,力矩大、动态性能好、步距角小、精度高,但结构相对复杂,价格也相对较高,主要用于工业

28BYJ-48步进电机,型号包含的含义:
28--步进电机的有效最大外径[ 直径 ]是28mm
B--表示是步进电机
Y--表示是永磁性
J--表示是减速型
48--表示四相八拍

28BYJ-48型步进电机原理详解

图9-3 步进电机外观
28BYJ-48是4相永磁式减速步进电机,其外观如上图。
图9-4 步进电机内部结构示意图
上图为28BYJ-48的内部结构示意图。
先看里圈,它上面有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号转子齿和定子齿夹角的2倍,同理,对侧也是一样。
接下来把B相绕组断开,而使C相绕组导通,则1号转子齿和4号转子齿对齐到了右上和左下的定子齿上而保持平衡。
再断开C相绕组,导通D相绕组,则2号转子齿和5号转子齿与D相的定子齿对其,转子又转动了一个角度。
很明显,当A相绕组再次导通,即完成了一个B-C-D-A的四节拍操作后,转子的0号齿和3号齿将由原来对齐B相定子齿变为对齐C相定子齿,即转子转过了一个定子齿的角度。以此类推,再来一个四节拍,转子就将再转过一个齿的角度,8个四节拍以后,转子将转过完整的一圈,而其中单个节拍使转子转过的角度就很容易计算出来,即360° / (8 × 4) = 11.25°,这个值就叫做步进角度。而上述这种工作模式就是步进电机的单四节拍模式--单相绕组通电四节拍。

还有一种性能更佳的工作模式,那就是再单四拍的每两个节拍之间再插入一个双绕组导通的中间节拍,组成八拍模式。比如,在从B相导通到C相导通的过程中,假如一个B相和C相同时导通的节拍,这个时候,由于B、C两个绕组的定子齿对它们附近的转子齿同时产生相同的吸引力,这将导致这两个转子齿的中心线对比到B、C两个绕组的中心线上,也就是新插入的这个节拍使转子转过了上述单四拍模式中步进角度的一半,即5.625°。这样一来,就使扭转精度增加了一倍,而转子转动一圈则需要8 × 8 = 64拍了。另外,新增加的这个中间节拍还会在原来单四拍的两个节拍吸引力之间又加了一把引力,从而可以大大增加电机的整体扭力输出,使电机更“有劲”了。

除了上述的单四拍和八拍的工作模式外,还有一个双四拍的工作模式--双绕组通电四节拍。其实就是把八拍模式中的两个绕组同时通电的那四拍单独拿出来,而舍弃单绕组通电的那四拍而已。其步进角度同单四拍是一样的,但由于它是两个绕组同时导通,所以扭矩会比单四拍模式大。

八拍模式是这类4相步进电机的最佳工作模式,能最大限度的发挥电机的各项性能,也是绝大多数实际工程中所选择的模式。

让电机转起来

步进电机一共有5根引线,其中红色的是公共端,连接到5V电源上,接下来的橙、黄、粉、蓝就对应了A、B、C、D相;如果要导线通A相绕组,就只需将橙色线接地即可,B相则是黄色接地,以此类推;再根据上述单四拍和八拍工作过程的讲解,可以得到以下顺序表
表9-1 八拍模式绕组控制顺序表
在上表中:
1拍:A相导通
2拍:A相和B相导通
3拍:B相导通
4拍:B相和C相导通
5拍:C相导通
6拍:C相和D相导通
7拍:D相导通
8拍:D相和A相导通
图9-5 显示译码与步进电机的选择跳线
用跳线帽来控制单片机的I/O连接的是步进电机还是译码器

  • 如果要使用步进电机,则用跳线帽将I/O口和MC口相连
  • 如果要显示部分,则用跳线帽将I/O口和ADDR口相连
  • 如果要兼得,则用跳线帽将I/O口和ADDR口相连,再将MC口连接到单片机无需用的I/O口即可

图9-6 步进电机控制电路
单片机的I/O口可以直接输出0V和5V的电压,但是电流驱动能力,也就是带载能力非常有限,所以每相的控制线上都增加一个[[第四篇 硬件基础知识#^Transistor|三极管]]来提高驱动能力。若要使A相导通,则必须是Q2导通,此时A相也就是橙色线就相当于接地了,于是A相绕组导通,此时单片机P1口低4位应输出0b1110,即0xE;如果要A、B相同时导通,就是Q2、Q3导通,P1口低四位应输出0b1100,即0xC,以此类推,可以得到下面的八拍节拍的I/O口控制代码数组:
unsigned char code BeatCode[8] = { 0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6 };
循环将这个数组的值传送到P1口,那多久送一次数据呢?
这个时间由步进电机的启动频率决定的。
启动频率就是步进电机在空载情况下能够正常启动的最高脉冲频率,如果脉冲频率高于该值,电机就不能正常启动。
表9-2 28BYJ-48步进电机参数表
上表中给出的参数是 >= 550,单位是P.P.S,即每秒脉冲数,这里的意思就是说:电机保证在每秒给出550个步进脉冲的情况下可以正常启动。
换算成节拍持续时间就是 1s/550 = 1.8ms,为了让电机能够启动,控制节拍刷新时间大于1.8ms就可以了

代码

/*
@file      motor.c
@brief     51单片机步进电机篇之让电机转动
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-12
@history   2025-08-12 EricsT - 新建文件
*/

#include <reg52.h>

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

void delay();

void main(void)
{
	unsigned char tmp;//暂存值
	unsigned char index = 0;//索引

	while (1)
	{
		tmp = P1;//存储P1的值
		tmp = tmp & 0xF0;//清空tmp的低四位
		tmp = tmp | BeatCode[index];//写入tmp的低四位
		P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作
		index++;//索引+1
		index &= 0x07;//到8归0
		delay();//延时
	}
}

void delay(void)
{
	unsigned int i = 200;
	while (i--);
}

转动精度与深入分析——1
9-7步进电机内部齿轮示意图
转动精度与深入分析——2

电机转动90度

/*
@file      motor.c
@brief     51单片机步进电机篇之电机转动90度
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-14
@history   2025-08-14 EricsT - 新建文件
*/

#include <reg52.h>

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

void delay();

void main(void)
{
	unsigned int i = 0;
	unsigned char tmp;//暂存值
	unsigned char index = 0;//索引

	for (i = 0; i < 1024; ++i)//90 / (360 / 4096) = 1024
	{
		tmp = P1;//存储P1的值
		tmp = tmp & 0xF0;//清空tmp的低四位
		tmp = tmp | BeatCode[index];//写入tmp的低四位
		P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作
		index++;//索引+1
		index &= 0x07;//到8归0
		delay();//延时
	}

	P1 = P1 & 0x0F;
	while (1);
}

void delay(void)
{
	unsigned int i = 200;
	while (i--);
}

电机转动任意角度

/*
@file      motor.c
@brief     51单片机步进电机篇之电机转动任意角度[角度可人为改变]
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-14
@history   2025-08-14 EricsT - 新建文件
*/

#include <reg52.h>

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

void delay();
 void Motor(unsigned long iCalc);

void main(void)
{
	Motor(360);
	while (1);
}

void delay(void)
{
	unsigned int i = 200;
	while (i--);
}

void Motor(unsigned long iCalc)
{
	unsigned long i = 0;
	unsigned long iNum = iCalc * 4076 / 360;
	unsigned char tmp;//暂存值
	unsigned char index = 0;//索引

	for (i = 0; i < iNum; ++i)
	{
		tmp = P1;//存储P1的值
		tmp = tmp & 0xF0;//清空tmp的低四位
		tmp = tmp | BeatCode[index];//写入tmp的低四位
		P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作
		index++;//索引+1
		index &= 0x07;//到8归0
		delay();//延时
	}

	P1 = P1 & 0x0F;
}

定时器实现电机转动任意角度

/*
@file      motor.c
@brief     51单片机步进电机篇之电机转动任意角度[角度可人为改变][定时器实现]
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-18
@history   2025-08-18 EricsT - 新建文件
*/

#include <reg52.h>

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

unsigned long beats;
void Motor(unsigned long iCalc);

void main(void)
{
	EA = 1;//打开总使能

	TMOD = 1;//定时器0工作在模式1

	TH0 = 0xF8;
	TL0 = 0xCD;//设置初始值,2ms

	ET0 = 1;//定时器0中断使能
	TR0 = 1;//打开定时器0

	beats = 0;
	Motor(180);
	while (1);
}

void Motor(unsigned long iCalc)
{
	EA = 0;//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
	beats = iCalc * 4076 / 360;
	EA = 1;
}

void InterruptTime0() interrupt 1
{
	unsigned long i = 0;
	unsigned char tmp;//暂存值
	static unsigned char index = 0;//索引

	TH0 = 0xF8;
	TL0 = 0xCD;//设置初始值,2ms

	if (!beats)
	{
		P1 = P1 & 0x0F;
		return;
	}

	
	tmp = P1;//存储P1的值
	tmp = tmp & 0xF0;//清空tmp的低四位
	tmp = tmp | BeatCode[index];//写入tmp的低四位
	P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作
	index++;//索引+1
	index &= 0x07;//到8归0
	beats--;	
}

按键控制电机-基础

/*
@file      motor.c
@brief     51单片机步进电机篇之按键控制电机转动--基础
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-18
@history   2025-08-18 EricsT - 新建文件
*/

#include <reg52.h>

sbit KEY_IN_1 = P2 ^ 4;
sbit KEY_IN_2 = P2 ^ 5;
sbit KEY_IN_3 = P2 ^ 6;
sbit KEY_IN_4 = P2 ^ 7;

sbit KEY_OUT_1 = P2 ^ 3;
sbit KEY_OUT_2 = P2 ^ 2;
sbit KEY_OUT_3 = P2 ^ 1;
sbit KEY_OUT_4 = P2 ^ 0;

unsigned char keyStatu[4][4] = 
{
	{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }, 
	{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }
};

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

unsigned long beats;
void Motor(unsigned long iCalc);

void main(void)
{
	unsigned char i, j;
	unsigned char backup[4][4] = 
	{
		{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }, 
		{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }
	};

	EA = 1;//打开总使能

	TMOD = 1;//定时器0工作在模式1

	TH0 = 0xFC;
	TL0 = 0x67;//设置定时器0初值,1ms

	ET0 = 1;//定时器0中断使能
	TR0 = 1;//打开定时器0

	beats = 0;
	
	while (1)
	{
		for (i = 0; i < 4; ++i)	
		{
			for (j = 0; j < 4; ++j)
			{//扫描按键状态
				if (backup[i][j] != keyStatu[i][j])//按键状态改变
				{
					if (0 != backup[i][j])
					{
						Motor(90);//电机转动
					}
					backup[i][j] = keyStatu[i][j];//更新按键状态
				}
			}
		}
	}

	while (1);
}

void Motor(unsigned long iCalc)
{
	EA = 0;//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
	beats = iCalc * 4076 / 360;
	EA = 1;
}

//电机中断
void MotorInterrupt()
{
	unsigned long i = 0;
	unsigned char tmp;//暂存值
	static unsigned char index = 0;//索引

	if (!beats)
	{
		P1 = P1 & 0x0F;
		return;
	}

	tmp = P1;//存储P1的值
	tmp = tmp & 0xF0;//清空tmp的低四位
	tmp = tmp | BeatCode[index];//写入tmp的低四位
	P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作
	index++;//索引+1
	index &= 0x07;//到8归0
	beats--;
}

//按键中断
void KeyInterrupt()
{
	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 (0x00 == (keyBuf[keyOut][i] & 0x0F))//稳定
			keyStatu[keyOut][i] = 0;
		else if (0x0F == (keyBuf[keyOut][i] & 0x0F))//稳定
			keyStatu[keyOut][i] = 1;			
		//不稳定
	}

	keyOut++;
	if (keyOut > 3)//只有四组按键,循环扫描
		keyOut = 0;

	switch (keyOut)//更新按键组
	{
		case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
		case 1: KEY_OUT_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 interruptTimer0 (void) interrupt 1
{
	static char flag = 0;  //电机中断的标志
	TH0 = 0xFC;
	TL0 = 0x67;//设置初始值,1ms

	KeyInterrupt();

	if (flag)
		MotorInterrupt();//电机2ms一中断
	
	flag++;	

	if (2 == flag)
		flag = 0;
}

按键控制电机-进阶

/*
@file      motor.c
@brief     51单片机步进电机篇之按键控制电机转动--进阶 //数字键1~9控制电机转动1~9圈,上为正方向,下为反方向,左固定正向转动90圈,右固定反向转动90圈
@author    EricsT (EricsT@163.com)
@version   v1.0.0
@date      2025-08-18
@history   2025-08-18 EricsT - 新建文件
*/

#include <reg52.h>

sbit KEY_IN_1 = P2 ^ 4;
sbit KEY_IN_2 = P2 ^ 5;
sbit KEY_IN_3 = P2 ^ 6;
sbit KEY_IN_4 = P2 ^ 7;

sbit KEY_OUT_1 = P2 ^ 3;
sbit KEY_OUT_2 = P2 ^ 2;
sbit KEY_OUT_3 = P2 ^ 1;
sbit KEY_OUT_4 = P2 ^ 0;

unsigned char keyStatu[4][4] = 
{
	{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }, 
	{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }
};

unsigned char code KeyValue[4][4] = 
{
	{ 1, 2, 3, 'u' }, 	  //1,2,3,上
	{ 4, 5, 6, 'l'},	//4,5,6,左
	{ 7, 8, 9, 'd' }, 	 //7,8,9,下
	{ 0, 'e', 'n', 'r'} //0,esc,enter,右
};//按键值

unsigned char code BeatCode[8] = { 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x03,0x07, 0x06 };//步进电机i/o口控制节拍

unsigned long beats;
void Motor(unsigned long iCalc);
void Key(int i, int j);

bit swapFlag;

void main(void)
{
	unsigned char i, j;
	unsigned char backup[4][4] = 
	{
		{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }, 
		{ 1, 1, 1, 1 }, { 1, 1, 1, 1 }
	};

	swapFlag = 1;

	EA = 1;//打开总使能

	TMOD = 1;//定时器0工作在模式1

	TH0 = 0xFC;
	TL0 = 0x67;//设置定时器0初值,1ms

	ET0 = 1;//定时器0中断使能
	TR0 = 1;//打开定时器0

	beats = 0;
	
	while (1)
	{
		for (i = 0; i < 4; ++i)	
		{
			for (j = 0; j < 4; ++j)
			{//扫描按键状态
				if (backup[i][j] != keyStatu[i][j])//按键状态改变
				{
					if (0 != backup[i][j])
					{
						Key(i, j);
					}
					backup[i][j] = keyStatu[i][j];//更新按键状态
				}
			}
		}
	}

	while (1);
}

void Key(int i, int j)
{
	if ((KeyValue[i][j] >= 0) && (KeyValue[i][j] <= 9))//发动机转动相应的圈数
	{
		Motor(360 * KeyValue[i][j]);
		return;
	}

	if ('u' == KeyValue[i][j])//设置发动机方向为正转
	{
		swapFlag = 1;
		return;
	}

	if ('d' == KeyValue[i][j])//设置发动机方向为反转
	{
		swapFlag = 0;
		return;
	}

	if ('e' == KeyValue[i][j])//发动机停止转动
	{
		swapFlag = 1;
		//P1 = P1 & 0x0F;
		beats = 0;
		return;
	}

	if ('r' == KeyValue[i][j])//固定反转90度
	{
		swapFlag = 0;
		Motor(90);
		return;
	}

	if ('l' == KeyValue[i][j])//固定正转90度
	{
		swapFlag = 1;
		Motor(90);
		return;
	}
		
}

void Motor(unsigned long iCalc)
{
	EA = 0;//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
	beats = iCalc * 4076 / 360;
	EA = 1;
}

//电机中断
void MotorInterrupt()
{
	unsigned long i = 0;
	unsigned char tmp;//暂存值
	static unsigned char index = 0;//索引

	if (!beats)
	{
		P1 = P1 & 0x0F;
		return;
	}

	tmp = P1;//存储P1的值
	tmp = tmp & 0xF0;//清空tmp的低四位
	tmp = tmp | BeatCode[index];//写入tmp的低四位
	P1 = tmp;//将tmp的值赋值给P1,即上述操作是在对P1进行操作

	if (swapFlag)
	{
		index++;//索引+1
		index &= 0x07;//到8归0
	}
	else
	{
		if (0 == index)
			index = 8;
		index--;//索引-1
	}

	beats--;	
}

//按键中断
void KeyInterrupt()
{
	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 (0x00 == (keyBuf[keyOut][i] & 0x0F))//稳定
			keyStatu[keyOut][i] = 0;
		else if (0x0F == (keyBuf[keyOut][i] & 0x0F))//稳定
			keyStatu[keyOut][i] = 1;			
		//不稳定
	}

	keyOut++;
	if (keyOut > 3)//只有四组按键,循环扫描
		keyOut = 0;

	switch (keyOut)//更新按键组
	{
		case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
		case 1: KEY_OUT_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 interruptTimer0 (void) interrupt 1
{
	static char flag = 0;  //电机中断的标志
	TH0 = 0xFC;
	TL0 = 0x67;//设置初始值,1ms

	KeyInterrupt();

	if (flag)
		MotorInterrupt();//电机2ms一中断
	
	flag++;	

	if (2 == flag)
		flag = 0;
}

蜂鸣器

蜂鸣器从结构上可以分为压电式蜂鸣器和电磁式蜂鸣器。
压电式为压电陶瓷片发音,电流比较小一些。
电磁式蜂鸣器为线圈通电震动发音,体积比较小。

按照驱动方式分为有源蜂鸣器和无源蜂鸣器。
这里的有源和无源并不是指电源,而是指振荡源。
图9-8蜂鸣器电路原理图
有源蜂鸣器内部带了一个振荡器源,如图,给了BUZZ引脚一个低电平,蜂鸣器就会直接响。
无源蜂鸣器内部是不带振荡源的,要让它响必须给一个500Hz ~ 4.5kHz之间的脉冲频率信号来驱动它才会响。
有源蜂鸣器往往比无源蜂鸣器贵一些,因为里边多了振荡电路,驱动发音也简单,靠电平就可以驱动
无源蜂鸣器声音频率可以控制,而音阶与频率又有确定的对应关系,因此就可以做出音阶的效果,可以用来制作出简单的音乐曲目,例如生日歌等。

如上电路图,蜂鸣器电流依然相对较大,因此需要用三极管驱动,并且加上了一个100欧姆的电阻作为限流电阻。此外还加上了一个D4二极管,这个二极管称为续流二极管。
蜂鸣器是感性器件,当三极管导通给蜂鸣器供电时,就会有导通电流流过蜂鸣器。
电感的一个特点就是电流不能突变,导通时电流逐渐加大的,这点没问题,但关断时,经“电源-三极管-蜂鸣器-地”这条回路就截断了,过不了任何电流了,那么储存的电流往哪儿去嘞?就是经过D4和蜂鸣器自身的环路来消耗掉了,从而避免了关断时,由于电感电流造成的反向冲击。持续关断时的电流,这就是续流二极管名称的由来。
蜂鸣器经常用于计算机、打印机、万用表这些设备上做提示音,提示音一般也很简单,就是简单发出一个声音即可。

posted @ 2025-08-19 00:00  EricsT  阅读(99)  评论(0)    收藏  举报