🚀write in front🚀
🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝💬本系列哔哩哔哩江科大51单片机的视频为主以及自己的总结梳理📚
🚀Projeet source code🚀
💾工程代码放在了本人的Gitee仓库:iPickCan (iPickCan) - Gitee.com
前言:
本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记,在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。
引用:
51单片机学习笔记-15 红外遥控_定时器发送红外信号-CSDN博客
单片机学习笔记---红外遥控&红外遥控电机调速(完结篇)_红外遥控红外遥控电机调速完结篇-CSDN博客
如何学习单片机——从51过渡到STM32_现在学单片机学哪个-CSDN博客
红外遥控另一种思路(在外部中断处理函数中延时等待):
51单片机学习笔记13 红外遥控接收_单片机红外接收解析方法-CSDN博客
51单片机 - 红外遥控NEC协议_c51 红外遥控-CSDN博客
国产USB逻辑分析仪开箱体验_usb 1.0协议分析仪 采样频率-CSDN博客
正文:
0. 🌿概述
在淘宝上购买了江协科技51单片机开发板套件(普中科技STC51单片机A2型号),就上在上一篇博文里说的自己计划学习下江协科技51单片机开发教程,通过STC51单片机这种MCU这种贴近于裸机的开发来增加对于系统硬件层面知识的了解和掌握。
术语和缩略语
缩写 | 全称 | 说明 |
AD | Analog to Data | 模拟到数字,将模拟信号转换为计算机可操作的数字信号 |
1. 🚀 红外遥控协议介绍
1.1 红外遥控器
红外遥控是利用红外光进行通信的设备,由 红外LED 将调制后的信号发出,由专用的红外接收头进行解调输出。
- 通信方式:单工,异步
- 红外LED波长:940nm(还有一种850nm的可以微微看见红光)
- 通信协议标准:NEC标准
对于红外发射电路来说,要发送的信号应该调制在一定的载频上,才能使得红外接收模块从自然界中识别出遥控器所发出的信号。要实现这个目的,本质上可以采用方式一进行硬件调制,此时输出的红外LED在IN输出高电平时不导通,而在IN输出低电平时,以38kHz的频率闪烁,从而有区别于自然光(如太阳光中的红外光)。软件调制则是提前将IN调制好再进行发送。
而红外接收时,则要完成一系列负载的解调、滤波工作,这些器件集成在红外接收头中,无需关心。最后输出的信号OUT与原来发送的信号IN一致。如下图所示:
注意若没有接收到信号,接收端默认输出高电平;接收到信号才输出低电平。行业内默认空闲信号高电平。
- 空闲状态:红外LED不亮,接收头输出高电平
- 发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平
- 发送高电平:红外LED不亮,接收头输出高电平
1.2 NEC编码协议
上面介绍了最底层的物理层,已经集成好所以不需要过多关心。对于要发送的数据来说,红外遥控器主要采用NEC编码。
可以看到上述一个完整的数据帧包括4个字节的数据(反码主要用于数据验证),并且由以下三个部分组成:
- Start起始位:红色部分。
- DATA数据部分:蓝色部分。低位在前,高位在后。 注意发送0/1的区别主要在于高电平持续时间,并且在这一部分已经发送完成所有的32位信息。最后一个下降沿发送完成后,持续560us拉高结束。
- Repeat重复标志:绿色部分。如果按下按键不松手,就会重复发送该标志。最后的下降沿完成后,也是持续560us的低电平,然后拉高结束。
- 32位的数据信息是4个字节,第一个字节是地址用来区分不同的遥控器,第二个字节是地址的反码,通过使用反码可以进行一定的校验,接收器接收到到前两个自己的地址码和地址码的反码后将其中一个地址码取反和另一个进行比较可以进行一定的校验;然后是第三个字节是数据码,然后第4个字节是数据码的反码。
- 起始码的长度是,低电平9ms,然后是高电平4.5ms,这个时间比后面的数据码的一位的高低电平的us秒时间要长。
上述32位数据中,后发送的16位“命令”,指的就是遥控器的键码值,如下图:
江协科技老师使用示波器抓取到的红外遥控器按键按下时,红外接收头输出的电平信号的波形,可以配合NEC红外遥控的编码使用(NEC红外遥控发送一个字节的数据时LSB优先,低位先发送)
- 🌾NEC红外编码每个下降沿既是前一个电平周期的结束,又是下一个电平周期的开始。
- 🌾在NEC红外编码的最后一个数据位条件会多出来一个下降沿和上升沿(时间时560us),原因时为NEC红外遥控数据位/条件位都是先低电平然后高电平,为了结束最后一个高电平就必须给一个下降沿,然后再返回到Idel空闲状态(上升沿),所以NEC红外遥控编码的最后要给数据位/条件位总是会多出来一个(低电平边沿)。
1.3 外部中断
由于红外遥控发送信号的速度很快(几十ms),如果采用单片机像扫描按键轮询检测红外遥控信号,很可能会遗漏信号,此时就需要 外部中断 实时的采集信号。回顾之前所学,STC89C52有4个外部中断,但是单片机只引出了2个外部中断(P3.2/P3.3)。这些外部中断有两种触发方式:下降沿触发和低电平触发。根据NEC编码协议,显然需要 下降沿触发。
// 中断后,就执行下面这些中断子程序
void Int0_Routine(void) interrupt 0 { 函数体 }
void Timer0_Routine(void) interrupt 1 { 函数体 }
void Int1_Routine(void) interrupt 2 { 函数体 }
void Timer1_Rountine(void) interrupt 3 { 函数体 }
void UART_Routine(void) interrupt 4 { 函数体 }
void Timer2_Routine(void) interrupt 5 { 函数体 }
void Int2_Routine(void) interrupt 6 { 函数体 }
- 但是单片机只引出了2个外部中断(P3.2/P3.3)
- 这些外部中断有两种触发方式:下降沿触发和低电平触发。根据NEC编码协议,显然需要 下降沿触发
2 🚀实验:红外遥控基本应用
需求:使用红外遥控发射信号,并将接收到的结果显示在LCD液晶屏上:
- LCD第一行显示“ADDR COMD NUM”。
- LCD第二行依次显示红外遥控的地址码、键码、音量按键(VOL+/VOL-)计数变量num。对于这个num,按下VOL+加1,按下VOL-则减一,要是一直按着不松手,那就快速增加/减小。
下面是初始化外部中断的一个小参考:
- IT0:外部中断源类型选择位,IT0=0 外部中断0为低电平触发,IT0=1 外部中断0为下降沿触发。
- IE0:外部中断源0清秀园中断标志。IE0=1外部中断0向CPU请求中断,当CPU响应外部中断时,有硬件清零 IE0。
整个程序使用了状态机。注意和FPGA中的单片机不太一样的是,所有的判断过程都是在外部中断(下降沿)来临时产生的,所以系统在执行判断语句的时候,目前的波形早就过去了。简单一句话,FPGA所有的信号等着你判断完才运行,所以要注意实时性;单片机中实际信号在你判断的时候已经过去了,有错位。并且状态机是在外部中断0的中断函数中完成的,这个中断函数放在HS0038.c文件中。
红外遥控状态机
2.1 实验1-外部中断下降沿触发
实验1-外部中断下降沿触发。测试STC89C52单片机外部中断源0下降沿触发方式的实验效果。在main.c中设置如下:
- 外部中断源0的中断触发方式为电平下降沿触发方式。
- 因为在我们使用的开发板上外部中断源0引脚 INT0/P3.2 同时也接在独立按键 KEY3 上,所以我们可以用按下独立按键KEY3来测试外部中断0。
- 我们在外部中断0的中断服务函数中,每次进入中断服务函数时对全局变量值Num进行加1,然后再main.c中循环打印Num的值就可以直到是否进入到了中断服务函数和进入中断服务函数的次数。
实验1源码如下:
#include <REGX52.H>
#include <INTRINS.H>
#include "delay.h"
#include "LCD1602.h"
unsigned char Num;
void main()
{
LCD_Init();
LCD_ShowString(1, 1, "A");
IT0 = 1; //外部中断源0选择下降沿触发
EX0 = 1; //外部中断源0,中断使能
EA = 1; //全局中断,中断使能
PX0 = 1; //外部中断源0,中断高优先级,可以中断别的低优先级的中断
while(1)
{
LCD_ShowNum(2, 1, Num, 3);
}
}
//外部中断源0的中断服务函数
void Int0_Routien(void) interrupt 0
{
Num++;
}
实验1实验结果:
- 每次按下独立按键KEY3(P3.2引脚)时产生一次电平下降沿,触发一次外部中断源0,进入外部中断0中断服务函数 Num的值增加1,按3次独立按键KEY3,Num的值增加到3。
- 因为IT0=1选择外部中断源0的中断触发方式为下降沿触发,所以按着独立按键KEY3不松手时(一直为低电平)并不会触发外部中断源0。
2.2 实验2-外部中断低电平触发
实验2-外部中断低电平触发。测试STC89C52单片机外部中断源0低电平触发方式的实验效果。在main.c中设置如下:
- 外部中断源0的中断触发方式为低电平触发方式。
- 因为在我们使用的开发板上外部中断源0引脚 INT0/P3.2 同时也接在独立按键 KEY3 上,所以我们可以用按下独立按键KEY3来测试外部中断0。
- 我们在外部中断0的中断服务函数中,每次进入中断服务函数时对全局变量值Num进行加1,然后再main.c中循环打印Num的值就可以直到是否进入到了中断服务函数和进入中断服务函数的次数。
实验2源码如下:
#include <REGX52.H>
#include <INTRINS.H>
#include "delay.h"
#include "LCD1602.h"
unsigned char Num;
void main()
{
LCD_Init();
LCD_ShowString(1, 1, "A");
IT0 = 0; //外部中断源0, IT0=0选择低电平触发中断方式
IE0 = 0;
EX0 = 1; //外部中断源0,中断使能
EA = 1; //全局中断,中断使能
PX0 = 1; //外部中断源0,中断高优先级,可以中断别的低优先级的中断
while(1)
{
LCD_ShowNum(2, 1, Num, 3);
}
}
//外部中断源0的中断服务函数
void Int0_Routien(void) interrupt 0
{
Num++;
}
实验2实验结果:
- 因为IT0=1选择外部中断源0的中断触发方式为低电平触发,所以按着独立按键KEY3不松手时(一直为低电平)会一直触发外部中断源0的中断,并进入外部中断0的中断服务函数 Num的值不断增长。
3. 红外遥控器解码-读取遥控器键码和重传信号
红外遥控器解码-读取遥控器键码和重传信号。当按下红外遥控器时在LCD1602屏幕上显示按键的遥控器的按键值,当按下红外遥控器按键不松手时实现数值的不断增长
Int0.c
#include <REGX52.H>
#include <INTRINS.H>
#include "int0.h"
/**
* @brief 外部中断源0,中断初始化
* @param 无
* @retval 无
*/
void Int0_Init(void)
{
IT0 = 1; //外部中断源0选择下降沿触发
IE0 = 0; //外部中断源0,请求标志位
PX0 = 1; //外部中断源0,中断高优先级,可以中断别的低优先级的中断
EX0 = 1; //外部中断源0,中断使能
EA = 1; //全局中断,中断使能
}
/**
* @brief 外部中断源0,中断服务函数模版
* @param 无
* @retval 无
*/
//void int0_route(void_) interrupt 0
//{
//}
Int0.h
#ifndef __INT0_H__
#define __INT0_H__
void Int0_Init(void);
#endif
Timer0.c
#include <REGX52.H>
#include "timer0.h"
/**
* @brief 定时器0初始化函数, 100微秒@11.0592MHz
* @param 无
* @retval 无
*/
void Timer0_Init()
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x00; //设置定时初值
TH0 = 0x00; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //不启动定时器0
}
/**
* @brief 设置定时器0计数器初始值
* @param 无
* @retval 无
*/
void Timer0_SetCounter(unsigned int Counter)
{
TL0 = Counter % 256; //设置定时初值
TH0 = Counter / 256; //设置定时初值
}
/**
* @brief 设置定时器0计数器初始值
* @param 无
* @retval 无
*/
unsigned int Timer0_GetCounter(void)
{
unsigned int Counter;
Counter = (TH0 << 8) | TL0;
return Counter;
}
/**
* @brief 设置定时器0计数器开始计数
* @param 无
* @retval 无
*/
void Timer0_Run(void)
{
TR0 = 1;
}
/**
* @brief 设置定时器0计数器停止计数
* @param 无
* @retval 无
*/
void Timer0_Stop(void)
{
TR0 = 0;
}
Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init();
void Timer_Routine(void);
void Timer0_Stop(void);
void Timer0_Run(void);
void Timer0_SetCounter(unsigned int Counter);
unsigned int Timer0_GetCounter(void);
#endif
IR.c
#include <REGX52.H>
#include <INTRINS.H>
#include "IR.h"
#include "Timer0.h"
#include "Int0.h"
#include "LCD1602.h"
unsigned int IR_Time0;
unsigned char IR_State;
unsigned char IR_CommendFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Data[4];
unsigned char IR_P;
unsigned char IR_Address;
unsigned char IR_Commend;
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
unsigned char IR_GetCommendFlag(void)
{
if(IR_CommendFlag)
{
IR_CommendFlag = 0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag = 0;
return 1;
}
return 0;
}
unsigned char IR_GetCommend(void)
{
return IR_Commend;
}
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 外部中断0中断服务函数中处理红外遥控器触发下降沿中断
* @param 无
* @retval 无
*/
void Int0_Routine(void) interrupt 0
{
/*
IR_State:0, 空闲状态
IR_State:1,搜寻状态,搜寻Start起始条件或者Repeat条件
IR_State:2,数据接收状态
*/
if(IR_State == 0) //状态机0空闲状态,收到下降沿中断转移到1状态
{
Timer0_Stop();
Timer0_SetCounter(0);
Timer0_Run();
IR_State = 1;
}
else if(IR_State == 1) //状态机1空闲状态,再次收到下降沿检查时起始条件或者Repeat条件
{
IR_Time0 = Timer0_GetCounter();
Timer0_SetCounter(0);
//12100<= x && x<=12900
if((IR_Time0 <= 12500+400) && (IR_Time0 >= 12500-400)){ //起始条件,跳转到2状态
IR_State = 2;
//P1_0 = 0;
//P1_0 = 1;
}
//10068<= x && x<=10768
else if((IR_Time0 <= 10368+400) && (IR_Time0 >= 10368-400)){ //Repeat条件,跳转到0状态
IR_RepeatFlag = 1;
IR_State = 0;
}
else{ //错误处理,,跳转到1状态
IR_State = 1;
}
}
else if(IR_State == 2) //状态机2状态,等待下降沿读取数据
{
IR_Time0 = Timer0_GetCounter();
Timer0_SetCounter(0);
if((IR_Time0 <= 1037+300) && (IR_Time0 >= 1037-300)){
IR_Data[IR_P/8] &= ~(0x01 << (IR_P%8));
IR_P++;
}
else if((IR_Time0 <= 2083+300) && (IR_Time0 >= 2083-300)){
IR_Data[IR_P/8] |= (0x01 << (IR_P%8));
IR_P++;
}
else{
IR_State = 1;
IR_P = 0;
}
if(IR_P >= 32)
{
IR_P = 0;
if(IR_Data[0] == ~IR_Data[1] && IR_Data[2] == ~IR_Data[3]){
IR_CommendFlag = 1;
IR_State = 0;
IR_Address = IR_Data[0];
IR_Commend = IR_Data[2];
}
else{
IR_State = 1;
}
}
}
}
IR.h
#ifndef __IR_H__
#define __IR_H__
#define IR_VOL_ADD 0x09
#define IR_VOL_MINIM 0x15
void IR_Init(void);
unsigned char IR_GetCommendFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetCommend(void);
unsigned char IR_GetAddress(void);
#endif
main.c
#include <REGX52.H>
#include <INTRINS.H>
#include "delay.h"
#include "LCD1602.h"
#include "IR.h"
unsigned char Num;
unsigned char ADDR;
unsigned char CMD;
extern unsigned char IR_State;
extern unsigned char IR_P;
extern unsigned char IR_CommendFlag;
void main()
{
LCD_Init();
LCD_ShowString(1, 1, "ADDR CMD Num");
IR_Init();
while(1)
{
if(IR_GetCommendFlag() || IR_GetRepeatFlag())
{
CMD = IR_GetCommend();
ADDR = IR_GetAddress();
LCD_ShowHexNum(2,1,ADDR,4);
LCD_ShowHexNum(2,7,CMD,2);
if(IR_VOL_ADD == CMD){
Num++;
}
else if(IR_VOL_MINIM == CMD){
Num--;
}
LCD_ShowHexNum(2,10,Num,2);
}
}
}
3.2 实验效果
实验效果如下图所示,当按下红外遥控器时在LCD1602屏幕上显示按键的遥控器的按键值,当按下红外遥控器按键不松手时实现数值的不断增长。
4. 红外遥控器解码-控制直流电机调速
红外遥控器解码,然后根据接收到的红外遥控器的键码值进行直流电动机的调速。本格实验就是在前一个PWM直流电动机调速的基础上,加上红外遥控器控制速度的部分就可以了。