前言
参考资料:江协科技
参考资料: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);
}
}
}