写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.03.30
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始51单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习51单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。不再另外购买视频中的普中开发板了。
原理图如下
视频中的都用这个开发板来实现,如果有资源就利用起来。
仔细看了看:开发板的晶振为:11.0592Mhz;12Mhz晶振是用来给CH340G芯片外置晶振;
下图是实物图
引用
51单片机入门教程-2020版 程序全程纯手打 从零开始入门
还参考了下图中的书籍:
手把手教你学51单片机(C语言版)
STC89C52手册
解答和科普
一、
1.1测试中断的执行
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "I2C.h"
#include "PCF8591.h"
#include "Init.h"
unsigned char Num;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
Nixie_OFF();
DianZhengGuan();
IT0=1; //下降沿触发
IE0=0; //中断标志位
EX0=1; //打开中断
EA=1; //打开所有中断
PX0=1; //优先级
while(1)
{
LCD_ShowNum(2,1,Num,3);
}
}
void Into_Routine(void) interrupt 0
{
Num++;
}
实验现象:
外部中断0低电平触发
外部中断0下降沿触发
1.2红外解码
如何把东西解码出来,包括读取时间值进行计时,还有中断。
状态机:定义一个变量表示当前的状态,0表示空闲状态,然后当他收到下降沿之后,把定时器打开,开始计时,再把状态转为1状态,寻找Start或者Repeat头部的这一个信号,1状态再来个下降沿的话,继续判断状态,判断中间的时间,如果是起始信号,就把状态转化为2状态,2状态定义为开始解码1012总共32,如果是重发,就把重发标志位置1,还有变量缓存区用来存取,还会置为正确是否。
1.Timer0
#include <REGX52.H>
void Timer0_Init(void) //1毫秒@11.0592MHz
{
// TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
void Timer0_SetCounter(unsigned char Value)
{
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_Getcounter(void)
{
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
```c
#ifndef __TIMER0_H
#define __TIMER0_H
void Timer0_Init(void) ;
void Timer0_SetCounter(unsigned char Value);
unsigned int Timer0_Getcounter(void);
void Timer0_Run(unsigned char Flag);
#endif
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "I2C.h"
#include "PCF8591.h"
#include "Init.h"
#include "INT0.h"
#include "Timer0.h"
#include "IR.h"
unsigned int Time;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"A");
Nixie_OFF();
DianZhengGuan();
Timer0_Init();
Timer0_SetCounter(0);
Timer0_Run(1);
Delay(1);
Time=Timer0_Getcounter();
while(1)
{
LCD_ShowNum(2,1,Time,3);
}
}
实验现象:
测试遥控的进入
#include <REGX52.H>
#include "Timer0.h"
#include "INT0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4]; //缓存
unsigned char IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepearFlag;
unsigned char IR_Address; //Data又要接受数据又要输出数据 变搜变输出,可能会出错
unsigned char IR_Command;
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
void Into_Routine(void) interrupt 0
{
if(IR_State==0)
{
P1=0x00;
Timer0_SetCounter(0);
Timer0_Run(1);
IR_State=1;
}
else if(IR_State==1)
{
IR_Time=Timer0_Getcounter();
Timer0_SetCounter(0);
if(IR_Time>12442-500&& IR_Time<12442+500) //13500
{
IR_State=2;
}
else if(IR_Time>10368-500&& IR_Time<10368+500)
{
IR_RepearFlag=1;
Timer0_Run(0);
IR_State=0;
}
else
{
IR_State=1;
}
}
}
遥控能进入的话,会产生中断,进入状态0,然后会让LED灯全亮。
实验现象:
测试遥控灯
二、红外遥控接受
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "I2C.h"
#include "PCF8591.h"
#include "Init.h"
#include "INT0.h"
#include "Timer0.h"
#include "IR.h"
unsigned int Time;
unsigned char Address;
unsigned char Command;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"LED");
Nixie_OFF();
DianZhengGuan();
IR_Init();
while(1)
{
if(IR_GetDataFlag())
{
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,6,Command,2);
}
if(Command==0x16)
{
P1=0x00;
}
}
}
#include <REGX52.H>
void Timer0_Init(void) //1毫秒@11.0592MHz
{
// TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
void Timer0_SetCounter(unsigned char Value)
{
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_GetCounter(void)
{
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
#include <REGX52.H>
#include "Timer0.h"
#include "INT0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];
unsigned char IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
/**
* @brief 红外遥控初始化
* @param 无
* @retval 无
*/
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到连发帧标志位
* @param 无
* @retval 是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到的地址数据
* @param 无
* @retval 收到的地址数据
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 红外遥控获取收到的命令数据
* @param 无
* @retval 收到的命令数据
*/
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
//外部中断0中断函数,下降沿触发执行
void Int0_Routine(void) interrupt 0
{
if(IR_State==0) //状态0,空闲状态
{
Timer0_SetCounter(0); //定时计数器清0
Timer0_Run(1); //定时器启动
IR_State=1; //置状态为1
}
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0
//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
if(IR_Time>12442-500 && IR_Time<12442+500)
{
IR_State=2; //置状态为2
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
else if(IR_Time>10368-500 && IR_Time<10368+500)
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
else //接收出错
{
IR_State=1; //置状态为1
}
}
else if(IR_State==2) //状态2,接收数据
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0
//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
if(IR_Time>1032-500 && IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2074-500 && IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
else //接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
if(IR_pData>=32) //如果接收到了32位数据
{
IR_pData=0; //数据位置指针清0
if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //数据验证
{
IR_Address=IR_Data[0]; //转存数据
IR_Command=IR_Data[2];
IR_DataFlag=1; //置收到连发帧标志位为1
}
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}
实验现象:
红外遥控显示键码值并控制LED
2.3遥控键码值
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "I2C.h"
#include "PCF8591.h"
#include "Init.h"
#include "INT0.h"
#include "Timer0.h"
#include "IR.h"
unsigned char Num;
unsigned char Address;
unsigned char Command;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"ADDR CMD NUM");
LCD_ShowString(2,1,"00 00 000");
Nixie_OFF();
DianZhengGuan();
IR_Init();
while(1)
{
if(IR_GetDataFlag()||IR_GetRepeatFlag())
{
Address=IR_GetAddress();
Command=IR_GetCommand();
LCD_ShowHexNum(2,1,Address,2);
LCD_ShowHexNum(2,7,Command,2);
if(Command==IR_VOL_X)
{
Num--;
}
if(Command==IR_VOL_Z)
{
Num++;
}
LCD_ShowNum(2,12,Num,3);
}
}
}
实验现象:
红外遥控键码值控制值增加和减少
三、红外遥控直流电机
1.Motor.c
#include <REGX52.H>
#include "Timer1.h"
sbit Moter= P1^7;
unsigned char Counter,Compare;
void Motor_Init(void)
{
Timer1_Init();
}
void Motor_SetSpeed(unsigned char Speed)
{
Compare=Speed;
}
void Timer1_Routine() interrupt 3 //跳转到这里,触发中断
{
TL1 = 0xA4; //设置定时初值
TH1 = 0xFF; //设置定时初值
Counter++; //定时器定时递增
Counter%=100; //周期清零
if(Counter<Compare) //比较
{
Moter=1; //给1转
}
else
{
Moter=0;
}
}
#ifndef __MOTOR_H
#define __MOTOR_H
void Motor_Init(void);
void Motor_SetSpeed(unsigned char Speed);
#endif
2.Timer1.c
#include <REGX52.H>
void Timer1_Init(void) //100us @11.0592MHz
{
// TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL0 = 0xA4; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF0标志
TR1 = 1; //定时器0开始计时
ET1=1;
EA=1;
PT1=0;
}
/*
void Timer1_Routine() interrupt 3 //跳转到这里,触发中断
{
static unsigned int T0Count; //Tcount为计数值 static只有本函数使用
TL1 = 0x66; //重新设置定时初值
TH1 = 0xFC; //重新设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
P1_0=~P1_0;
}
}
*/
#ifndef __TIMER1_H
#define __TIMER1_H
void Timer1_Init(void) ;
#endif
3.IR.c
#include <REGX52.H>
#include "Timer0.h"
#include "INT0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];
unsigned char IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
/**
* @brief 红外遥控初始化
* @param 无
* @retval 无
*/
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到连发帧标志位
* @param 无
* @retval 是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到的地址数据
* @param 无
* @retval 收到的地址数据
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 红外遥控获取收到的命令数据
* @param 无
* @retval 收到的命令数据
*/
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
//外部中断0中断函数,下降沿触发执行
void Int0_Routine(void) interrupt 0
{
if(IR_State==0) //状态0,空闲状态
{
Timer0_SetCounter(0); //定时计数器清0
Timer0_Run(1); //定时器启动
IR_State=1; //置状态为1
}
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0
//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
if(IR_Time>12442-500 && IR_Time<12442+500)
{
IR_State=2; //置状态为2
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
else if(IR_Time>10368-500 && IR_Time<10368+500)
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
else //接收出错
{
IR_State=1; //置状态为1
}
}
else if(IR_State==2) //状态2,接收数据
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0
//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
if(IR_Time>1032-500 && IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2074-500 && IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
else //接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
if(IR_pData>=32) //如果接收到了32位数据
{
IR_pData=0; //数据位置指针清0
if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3])) //数据验证
{
IR_Address=IR_Data[0]; //转存数据
IR_Command=IR_Data[2];
IR_DataFlag=1; //置收到连发帧标志位为1
}
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}
#ifndef __IR_H__
#define __IR_H__
#define IR_CH_X 0x45
#define IR_CH 0x46
#define IR_CH_Z 0x47
#define IR_PREV 0x44
#define IR_NEXT 0x40
#define IR_PAUSE 0x43
#define IR_VOL_X 0x07
#define IR_VOL_Z 0x15
#define IR_EQ 0xD9
#define IR_0 0x16
#define IR_100+ 0x19
#define IR_200+ 0x0d
#define IR_1 0x0C
#define IR_2 0x18
#define IR_3 0x5E
#define IR_4 0x08
#define IR_5 0x1C
#define IR_6 0x5A
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4A
void IR_Init(void);
unsigned char IR_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);
#endif
4.main.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Timer1.h"
#include "Nixie.h"
#include "Init.h"
#include "IR.h"
#include "Motor.h"
unsigned char KeyNum,Speed,Command;
void main()
{
DianZhengGuan();
Motor_Init();
IR_Init();
while(1)
{
// KeyNum=Key();
// if(KeyNum==1)
// {
// Speed++;
// Speed%=4;
//
// if(Speed==0){Motor_SetSpeed(0);}
// if(Speed==1){Motor_SetSpeed(50);}
// if(Speed==2){Motor_SetSpeed(75);}
// if(Speed==3){Motor_SetSpeed(100);}
//
// Nixie(1,Speed); //数码管静态显示会锁存
// }
if(IR_GetDataFlag())
{
Command=IR_GetCommand();
if(Command==IR_0) {Speed=0;}
if(Command==IR_1) {Speed=1;}
if(Command==IR_2) {Speed=2;}
if(Command==IR_3) {Speed=3;}
if(Speed==0){Motor_SetSpeed(0);}
if(Speed==1){Motor_SetSpeed(50);}
if(Speed==2){Motor_SetSpeed(75);}
if(Speed==3){Motor_SetSpeed(100);}
Nixie(1,Speed); //数码管静态显示会锁存
}
}
}
5.INT0
#include <REGX52.H>
void Int0_Init(void)
{
IT0=1; //下降沿触发
IE0=0; //中断标志位
EX0=1; //打开中断
EA=1; //打开所有中断
PX0=1; //优先级
}
#ifndef __INT0_H__
#define __INT0_H__
void Int0_Init(void);
#endif
实验现象:
红外控制直流电机
总结
本节课主要学了红外遥控了,对定时器写入和写出的,还有解码的功能,最重要的是这个解码过程,设置了3个状态,每个状态执行分别执行不同的功能,识别出开始的信号和设置很多个标志位,通过操作进行定时器的开始,进而解码出开始和连发,进而分辨出是发送的是信号0还是信号1,最后读取值进入数组中,拆分为4个unsigned char类型,分别存入最后变量地址和数据中,并根据数据Command进行有关的操作。
2、最后进行了实验:红外遥控直流电机,根据读到的Command,相当于读取按键值,然后执行相关的功能,完成本次实验。