【51单片机系列笔记四】


前言

参考资料:江协科技
参考资料:stc89c52官方手册
51单片机PWM呼吸灯/电机驱动、AD/DA-XPT2046-SPI总线、红外遥控(外部中断)学习笔记。


一、PWM呼吸灯/电机驱动

PWM(Pulse Width Modulation)是脉冲宽度调制,在具有惯性的系统(如电机、LED灯)中,通过对一系列脉冲的宽度进行调制,等效的获得所需模拟参量。
PWM重要参数:
频率 = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距 在这里插入图片描述
产生PWM方法:通过定时器定时自增,计数值与比较值比较,得到高低电平波形,不断改变比较值,得到不同的占空比。
在这里插入图片描述

PWM呼吸灯

实验现象:LED从暗->亮->暗,总共4s。定时器定时200us进中断,并自增,增加大100清零。因此,一个PWM周期为20ms。比较值从0~100,再从100~0,总共经历4s。

#include <REGX52.H>

#define LED P2_0

unsigned char count,compare,direction;

void timer0_init()
{
	TMOD &= 0XF0;
	TMOD |= 0X01;
	TR0 = 1;
	TL0 = 65336 % 256;
	TH0 = 65336 / 256;
	TF0 = 0;
	
	EA = 1;
	ET0 = 1;
}

void main()
{
	timer0_init();
	while(1)
	{
		if(count >= 100)
		{
			count = 0;
			if(direction == 0)
			{
				compare++;
				if(compare >= 100)
				{
					direction = 1;
				}
			}
			else if(direction == 1)
			{
				compare--;
				if(compare <= 0)
				{
					direction = 0;
				}
			}
		}
		
		if(count < compare)
		{
			LED = 0;
		}
		else
		{
			LED = 1;
		}
	}
}

void timer0_routine() interrupt 1
{
	TL0 = 65336 % 256;
	TH0 = 65336 / 256;
	count++;
}

PWM电机驱动

直流电机主要由永磁体(定子)、线圈(转子)和换向器组成。除直流电机外,常见的电机还有步进电机、舵机、无刷电机、空心杯电机等。
直流电机驱动方式
图1通过ULN2003芯片驱动电机,输入与输出电平相反。当输入给高电平,电机转动。
图2通过PNP三极管充当开关,驱动电机。当输入给低电平,电机转动。D1为续流二极管,主要用在有感性负载元件(电感、电机、蜂鸣器等)的电路中,保护电路。当开关元件关闭时,感性负载元件会产生反向电动势,电流通过续流二极管,形成回路。
图3通过H桥驱动,电机可以正反转。
在这里插入图片描述
实验现象:按键1~4代表4个速度等级,控制电机转动。按下的按键可以显示在数码管上。
timer0.c:定时器0定时100us进中断

#include "timer0.h"

void timer0_init()
{
	TMOD &= 0XF0;
	TMOD |= 0X01;
	TR0 = 1;
	TL0 = 65436 % 256;
	TH0 = 65436 / 256;
	TF0 = 0;
	
	EA = 1;
	ET0 = 1;
}

timer1.c:定时器1定时20ms扫描按键

#include "timer1.h"

void timer1_init(void)		//20毫秒@11.0592MHz
{
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	TL1 = 0xE0;		//设置定时初值
	TH1 = 0xB1;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	EA = 1;
	ET1 = 1;
}

key.c:通过读取前后两次的值,判断是否松开

#include "key.h"

sbit K1 = P3^1;
sbit K2 = P3^0;
sbit K3 = P3^2;
sbit K4 = P3^3;

unsigned char key;

unsigned char Key_Num()
{
	unsigned char num = 0;
	if(K1 == 0) num = 1;
	if(K2 == 0) num = 2;
	if(K3 == 0) num = 3;
	if(K4 == 0) num = 4;
	return num;
}

unsigned char Key_FinalNum()
{
	unsigned char temp = 0;
	temp = key;
	key = 0;
	return temp;
}

void timer1_routine() interrupt 3
{
	static unsigned char oldVal,newVal;
	TL1 = 0xE0;		//设置定时初值
	TH1 = 0xB1;		//设置定时初值
	
	oldVal = newVal;
	newVal = Key_Num();
	if(oldVal == 1 && newVal == 0) key = 1;
	if(oldVal == 2 && newVal == 0) key = 2;
	if(oldVal == 3 && newVal == 0) key = 3;
	if(oldVal == 4 && newVal == 0) key = 4;
}

nixie.c

#include "nixie.h"

unsigned char code SEGMENT_TABLE[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,
0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

void NXIE_DISPLAY(unsigned char location,unsigned char number)
{
	switch(location)
	{
		case 1:P2_4= 1;P2_3= 1;P2_2= 1;break;
		case 2:P2_4= 1;P2_3= 1;P2_2= 0;break;
		case 3:P2_4= 1;P2_3= 0;P2_2= 1;break;
		case 4:P2_4= 1;P2_3= 0;P2_2= 0;break;
		case 5:P2_4= 0;P2_3= 1;P2_2= 1;break;
		case 6:P2_4= 0;P2_3= 1;P2_2= 0;break;
		case 7:P2_4= 0;P2_3= 0;P2_2= 1;break;
		case 8:P2_4= 0;P2_3= 0;P2_2= 0;break;		
	}
	P0 = SEGMENT_TABLE[number];
	P0 = 0x00;
}

main.c

#include <REGX52.H>
#include "key.h"
#include "nixie.h"
#include "timer1.h"
#include "timer0.h"

#define motor P1_0

unsigned char speed;
	
void main()
{
	unsigned char key,temp;
	timer0_init();
	Timer1Init();
	while(1)
	{
		key = Key_FinalNum();
		if(key)
		{
			temp = key;
			if(key == 1) speed = 10;
			if(key == 2) speed = 50;
			if(key == 3) speed = 75;			
			if(key == 4) speed = 90;
		}	
		NXIE_DISPLAY(1,temp);
	}
}

void timer0_routine() interrupt 1
{
	static unsigned char count;
	TL0 = 65436 % 256;
	TH0 = 65436 / 256;
	count++;
	if(count < speed) motor = 1;
	else motor = 0;
}

二、AD/DA-XPT2046-SPI总线

运算放大器

运算放大器是具有很高放大倍数的放大电路单元。内部集成了差分放大器、电压放大器、功率放大器三级放大电路。
运算放大器电路的分析方法:虚短和虚断(负反馈条件下)
虚短:运算放大器的输入端之间短路,电压相等
虚断:运算放大器的输入端之间断路,电流不会流入,也不会流出
电压比较器:放大同相输入端(Vin+)和反相输入端(Vin-)之间的差值。放大倍数理论上近似无穷大。
反向比较器:运用虚短和虚断分析。虚短,输入端之间电压相等,都为GND。虚断,输入端没有电流流入。流经R1和R2的电流Vin/R1,R2的压降为Vin/R1*R2,所以Vout=0-R2/R1*Vin=-R2/R1*Vin。
在这里插入图片描述
同向放大器:虚短,输入端之间电压相等,都为Vin。虚断,输入端没有电流流入。流经R1和R2的电流Vin/R1,R2的压降为Vin/R1*R2,所以Vout=Vin+R2/R1*Vin=(1+R2/R1)*Vin。
电压跟随器:同相放大器中,R1无穷大时,左侧近似断路,Vout=Vin。
在这里插入图片描述

DA

AD(Digital to Analog):数字信号->模拟信号。
DAC0832芯片由8位输入寄存器、8位DAC寄存器、8位DA转换器及转换控制电路组成。内部有2个寄存器,可以实现双通道输入数据的同步输出。
在这里插入图片描述
T型电阻网络DA转换器
最右侧是反向放大器,输入端和输出端电压相等,为GND。Vref=IR。
电流I=2*I7=2^2*I6=2^3*I5=…=2^8*I0。
流经Rfb的电流为(D7~D0)*Vref/R/256。
在这里插入图片描述
PWM型DA转换器
在这里插入图片描述

AD

AD(Analog to Digital):模拟信号->数字信号。计算机操作的是数字信号。
AD转换一般有多个输入通道,通过多路选择开关连接到AD转换器。
ADC0809芯片是8位串行输出模数转换器(ADC)。8路模拟开关通过下方的地址锁存与译码来选择要进行转换的通道。上方是AD转换的开始、结束、时钟线,串行转换后,OE使能输出。
在这里插入图片描述
逐次逼近型AD转换器
内部含有DA转换器,通过外部输入的模拟信号和DA转换器输出的模拟信号不断比较,当两者相等时,将DA转换器输出的模拟信号输出。
在这里插入图片描述

XPT2046

在这里插入图片描述
在这里插入图片描述
XPT2046芯片可使用 SPI、SSI 和Microwire 等同步串行接口。
一次完整的转换需要24个串行同步时钟(DCLK):主机和芯片中的转换器通过DIN引脚通信,需要8个时钟周期;转换器进行模数转换并通过DOUT引脚输出数据,需要12个时钟周期。剩下的周期DOUT输出0。

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。 同步,全双工。SPI总线具有较高的数据传输速率,比I2C、UART更快。
SPI总线通信线:

  • CS片选线:用于确定与那个设备通信。
  • DCLK时钟线:用于同步传输数据。
  • DIN(MOSI主设备输出从设备输入线)
  • DOUT(MISO主设备输入从设备输出线)

XPT2046中采用的SPI总线没有使用全双工模式,而是先通过DIN引脚输入数据之后,再通过DOUT引脚输出数据。
在这里插入图片描述
实验现象:读取电位器、热敏电阻、光敏电阻的AD转换值。
spi.c

#include <REGX52.H>

sbit DIN = P3^4;
sbit CS = P3^5;
sbit DCLK = P3^6;
sbit DOUT = P3^7;

void SPI_Init()
{
	DCLK = 0;
	CS = 1;
}
	
void SPI_Start()
{
	CS = 0;
}

void SPI_Stop()
{
	CS = 1;
}

void SPI_ByteSend(unsigned char Byte)
{
	unsigned char i;
	for(i = 0;i<8;i++)
	{
		DIN = Byte & (0x80 >> i);
		DCLK = 1;
		DCLK = 0;
	}
}

unsigned char SPI_ByteReceive()
{
	unsigned char i,Byte = 0x00;
	for(i = 0;i<8;i++)
	{
		DCLK = 1;
		DCLK = 0;
		if(DOUT) Byte |= (0x80 >> i);
	}
	return Byte;
}

xpt2046.c

#include <REGX52.H>
#include "spi.h"

#define AIN0_XP_12    0x94
#define AIN1_YP_12    0xD4
#define AIN2_VBAT_12  0xA4
#define AIN3_AUX_12   0xE4

#define AIN0_XP_8    0x9c
#define AIN1_YP_8    0xDc
#define AIN2_VBAT_8  0xAc
#define AIN3_AUX_8   0xEc
unsigned int XPT2046_AD(unsigned char Command)
{
	unsigned char DataL,DataH;
	//Data初始化赋值
	unsigned int Data = 0x00;
	SPI_Init();
	SPI_Start();
	SPI_ByteSend(Command);
	DataH = SPI_ByteReceive();
	DataL = SPI_ByteReceive();
	SPI_Stop();
	if(Command & 0x08)
	{
		Data = DataH;
	}
	else
	{
		Data = (DataH << 4) | (DataH >> 4);
	}
	return Data;
}

main.c

#include <REGX52.H>
#include "xpt2046.h"
#include "LCD1602.h"
	
void main()
{
	unsigned int ADValue1,ADValue2,ADValue3;
	LCD_Init();
	LCD_ShowString(1,1,"ADJ  NTC  GR");
	while(1)
	{
		ADValue1 = XPT2046_AD(AIN0_XP_12);
		LCD_ShowNum(2,1,ADValue1,4);
		ADValue2 = XPT2046_AD(AIN1_YP_12);
		LCD_ShowNum(2,6,ADValue2,4);
		ADValue3 = XPT2046_AD(AIN2_VBAT_12);
		LCD_ShowNum(2,11,ADValue3,4);
	}	
}

三、红外遥控中断

可见光波长从长到短,可分为红橙黄绿青蓝紫。其中,波长较长的红光在650~700nm之间,波长较短的紫光在400~450nm之间。不可见的红外线波长大约在750~1000nm之间。
红外遥控是利用红外线通信的设备。由红外LED将调制后的信号发出,再由专用的红外接收头进行解调输出。
通信方式:单工,异步
红外LED波长:940nm
通信协议标准:NEC标准
图1中IN为输入的电平信号,图2中IN为输入的电平信号和38kHz的载波信号叠加后的信号。当IN为高电平时,红外LED不亮,对应接收头输出高电平;当IN为低电平时,红外LED以38kHz的频率闪烁发光,对应接收头输出低电平。
在这里插入图片描述
在这里插入图片描述
红外NEC编码协议
包含起始信号、数据信号、重复信号。信号之间是通过高低电平的持续时长来区分的。
计算信号的持续时长方法:外部中断下降沿触发,定时器开始计时,当下一次下降沿触发进入中断时,停止计时。两次下降沿的计数值即为信号的持续时长。
在这里插入图片描述
状态转移过程
接收4位字节数据时,触发下降沿才进入接收数据过程,不需要循环。
在这里插入图片描述

外部中断和寄存器
在这里插入图片描述
在这里插入图片描述
遥控器键码
在这里插入图片描述
实验现象:按下按键,显示16进制键码值。
int0.c:外部中断,下降沿触发:IT0 = 1;

#include <REGX52.H>

void Int0_Init()
{
	IT0 = 1;
	IE0 = 0;
	EA = 1;
	EX0 = 1;
	PX0 = 1;
}

timer0.c:定时器初始化,默认不开启计时,重装值清零

#include "timer0.h"

void Timer0_init()
{
	TMOD &= 0XF0;
	TMOD |= 0X01;
	TR0 = 0;
	TL0 = 0;
	TH0 = 0;
	TF0 = 0;
	
//	EA = 1;
//	ET0 = 1;
}

void Timer0_Run(unsigned char Flag)
{
	TR0 = Flag;
}

void Timer0_SetCounter(unsigned char count)
{
	TL0 = count % 256;
	TH0 = count / 256;
}

unsigned int Timer0_GetCounter()
{
	return (TH0 << 8) | TL0;
}

ir.c

#include <REGX52.H>
#include "Int0.h"
#include "Timer0.h"

unsigned char NEC_Data[4];
unsigned char index;
unsigned char State;
unsigned int NEC_Time;
unsigned char Data;
unsigned char Command;
unsigned char DataFlag;
unsigned char RepeatFlag;

void IR_NEC_Init()
{
	Int0_Init();
	Timer0_init();
}

unsigned char GetDataFlag()
{
	if(DataFlag) 
	{
		DataFlag = 0;
		return 1;
	}
	return 0;
}

unsigned char GetRepeatFlag()
{
	if(RepeatFlag) 
	{
		RepeatFlag = 0;
		return 1;
	}
	return 0;
}

unsigned char GetAddress()
{
	return Data;
}

unsigned char GetCommand()
{
	return Command;
}

void Int0_Routine() interrupt 0
{
	switch(State)
	{
		case 0:
		{
			Timer0_SetCounter(0);
			Timer0_Run(1);
			State = 1;
			break;
		}
		case 1:
		{
			NEC_Time = Timer0_GetCounter();
			Timer0_SetCounter(0);
			if(NEC_Time > 13500 - 600 && NEC_Time < 13500 + 600) 
			{
				State = 2;
			}
			else if(NEC_Time > 11250 - 600 && NEC_Time < 11250 + 600)
			{
				RepeatFlag = 1;
				Timer0_Run(0);
				State = 0;
			}
			else State = 1;
			break;
		}
		case 2:
		{
			NEC_Time = Timer0_GetCounter();
			Timer0_SetCounter(0);
			if(NEC_Time > 1120 - 500 && NEC_Time < 1120 + 500)
			{
				NEC_Data[index/8] &= ~(0x01 << (index % 8));
				index++;
			}			
			else if(NEC_Time > 2250 - 500 && NEC_Time < 2250 + 500)
			{
				NEC_Data[index/8] |= (0x01 << (index % 8));
				index++;
			}			
			else 
			{
				index = 0;
				State = 1;
			}
			if(index >= 32)
			{
				index = 0;
				if((NEC_Data[0] == ~NEC_Data[1]) && (NEC_Data[2] == ~NEC_Data[3]))
				{
					Data = NEC_Data[0];
					Command = NEC_Data[2];
					DataFlag = 1;
				}
				Timer0_Run(0);
				State = 0;
			}
			break;
		}	
	}
}

main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "Int0.h"
#include "Timer0.h"
#include "ir.h"

unsigned char num;	
void main()
{
	unsigned char Address,Command;
	LCD_Init();
	IR_NEC_Init();
	while(1)
	{
		if(GetDataFlag() | GetRepeatFlag())
		{
			Address = GetAddress();
			Command = GetCommand();
			LCD_ShowHexNum(2,1,Address,2);
			LCD_ShowHexNum(2,5,Command,2);
			if(Command == 0x16) num++;
			if(Command == 0x45) num--;
			LCD_ShowNum(2,10,num,3);
		}
	}	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值