记录一下:在嵌入式实验中,通过实现模拟格力空调的红外信号实现了使用单片机遥控空调的效果。

红外信号传输原理

红外线的通讯原理

红外光是以特定的频率脉冲形式发射,接收端收到到信号后,按照约定的协议进行解码,完成数据传输,在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段,NEC协议的频率就是38KHZ。 这个以特定的频率发射其实就可以理解为点灯,不要被复杂的词汇难住了,就是控制灯的闪烁频率(亮灭),和刚学单片机完成闪光灯一样的意思,只不过是灯换了一种类型,都是灯。 接收端的原理: 接收端的芯片对这个红外光比较敏感,可以根据有没有光输出高低电平,如果发送端的闪烁频率是有规律的,接收端收到后输出的高电平和低电平也是有规律对应的,这样发送端和接收端只要约定好,那就可以做数据传输了。

红外接收原理

NEC协议

NEC协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的mini遥控器、电视机、投影仪几乎都是NEC协议。 像格力空调、美的空调这些设备使用的就是其他协议格式,不是NEC协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。

NEC格式完成一次数据传输的完整格式
tasks-2024-05-21-19-54-53

引导码: 由9ms的高电平+4.5ms的低电平组成。
4个字节的数据: 用户码+用户反码+数据码+数据反码。 这里的反码可以用来校验数据是否传输正确,有没有丢包。

重点: NEC协议传输数据位的时候,0和1的区分是依靠收到的高、低电平的持续时间来进行区分的—这是解码关键。
标准间隔时间:0.56ms
收到数据位0: 0.56ms
收到位1: 1.68ms

所以,收到一个数据位的完整时间表示方法是这样的:
收到数据位0: 0.56m低电平+ 0.56ms的高电平
收到数据位1: 0.56ms低电平+1.68ms的高电平

红外线接收头模块输出电平的原理: 红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。

红外发送原理

嵌入式模拟格力空调遥控器-2024-05-21-20-02-16
首先,需要理解是,发送信号时,我们需要发送的是方波,而不是一条直线,在上图中可以看出来,如果我们要发送一个数据”1”,那么我们就要先发送560μs的高电平和1680μs的低电平,而要发送这个高电平就需要使用38khz的频率去交替发送高低电位,持续560μs,才可以正确发射红外光。而低电平就会比较简单。只要保持0电位即可。

交替发送高电平=> 发射红外光
直接发送低电平=> 不发射红外光

原理如上:
下面上代码:

源码

接受模块

初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Remote_Init(void)
{
RCC->APB1ENR |= 1 << 2; // TIM4 时钟使能
RCC->APB2ENR |= 1 << 4; // 使能PORTC时钟

GPIOC->CRH &= 0XFFFF0FFF; // PC11 输入
GPIOC->CRH |= 0X00008000; // 上拉输入
// GPIOC->ODR|=1<<11;//PC11 上拉

TIM4->ARR = 10000; // 设定计数器自动重装值 最大10ms溢出
TIM4->PSC = 71; // 预分频器,1M的计数频率,1us加1.

TIM4->DIER |= 1 << 0; // 允许更新中断
TIM4->CR1 |= 0x01;// 使能定时器4

// MY_NVIC_Init(1, 3, TIM4_IRQChannel, 2); // 抢占1,子优先级3,组2

// 设置外部中断
Ex_NVIC_Config(GPIO_C, 11, 3); // 全频段触发
MY_NVIC_Init(0, 2, EXTI15_10_IRQChannel, 2);
}

这一部分设置PCin(11)为红外接受的端口。然后设置了外部中断来作为监听信息的变化,如果产生了高低电平的变化,都会调用EXTI15_10_IRQHandler()函数。然后初始化了TIM4定时器,用来记录时间的长短。
启用关闭红外接受功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 打开红外接收
*/
void Remote_ON(void)
{
// 禁用 EXTI 第11线
EXTI->IMR |= (1 << 11); // 禁用中断掩码寄存器上的第11位
EXTI->EMR |= (1 << 11); // 禁用事件掩码寄存器上的第11位
}

/**
* 关闭红外接收
*/
void Remote_OFF(void)
{
// 禁用 EXTI 第11线
EXTI->IMR &= ~(1 << 11); // 禁用中断掩码寄存器上的第11位
EXTI->EMR &= ~(1 << 11); // 禁用事件掩码寄存器上的第11位
}

在单片机上,如果同时存在红外发射和红外接受,就可能会导致,你发射信号然后自己接收到,于是就会有每发一个信号就中断接受一次信号,导致数据的延时出现问题。所以在每次发送红外信息时,需要关闭红外接受功能。
测量电平时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
函数功能: 测量高电平持续的时间
*/
u32 Infrared_GetTime_H(void)
{
TIM4->CNT = 0;
while (RDATA)
{
} // 等待高电平结束
return TIM4->CNT;
}

/*
函数功能: 测量低电平持续的时间
*/
u32 Infrared_GetTime_L(void)
{
TIM4->CNT = 0;
while (!RDATA)
{
} // 等待高电平结束
return TIM4->CNT;
}

通过这个功能,来判断是否符合560和1680的时间。
具体的接受功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
void other_remote_get()
{
time = Infrared_GetTime_L();

if (time < 7000 || time > 10000)
return; // 标准时间: 9000us

times_low[0] = time;
time = Infrared_GetTime_H();

// 得到高电平时间
if (time < 3000 || time > 5500)
return; // 标准时间4500us

times_high[0] = time;
// printf("高电平时间为%d\n",time);
for (i = 0; i < 35; i++)
{
time = Infrared_GetTime_L();
// 得到低电平时间
times_low[1 + i] = time;
if (time < 400 || time > 800)
return;
// 标准时间: 560us
time = Infrared_GetTime_H(); // 得到高电平时间
times_high[1 + i] = time;
if (time > 1400 && time < 1800) // 数据1 1680us
{
datas[i] = 1;
}
else if (time > 400 && time < 700) // 数据0 560us
{
datas[i] = 0;
}
else
return;
}

// 第一段数据结束

time = Infrared_GetTime_L();
// 得到低电平时间
if (time < 400 || time > 700)
return; // 标准时间: 600us
times_low[36] = time;
time = Infrared_GetTime_H();
// 得到高电平时间

if (time < 6000 || time > 13000)
return; // 标准时间4500us
times_high[36] = time;
for (i = 0; i < 32; i++)
{
time = Infrared_GetTime_L();
// printf("32位数据码之%d,时间为%d\n",i+1,time);
times_low[37 + i] = time;
// 得到低电平时间
if (time < 400 || time > 800)
return;

// 标准时间: 560us

time = Infrared_GetTime_H(); // 得到高电平时间
times_high[37 + i] = time;
if (time > 1300 && time < 1900) // 数据1 1680us
{
datas[35 + i] = 1;
}
else if (time > 400 && time < 700) // 数据0 560us
{
datas[35 + i] = 0;
}
else
return;
}

for (i = 0; i < 67; i++)
{
printf("%d,", datas[i]);
}
printf("\n");
printf("低电平时间\n");
for (i = 0; i < 69; i++)
{
printf("第%d个数据:%d\n", i + 1, times_low[i]);
}
printf("\n");
printf("高电平时间\n");
for (i = 0; i < 69; i++)
{
printf("第%d个数据:%d\n", i + 1, times_high[i]);
}
printf("\n");
}

这一部分我做了debug和输出,将我获得到的空调红外信号输出出来,好让自己进行模拟。

发射模块

初始化部分

1
2
3
4
5
6
7
8
{
//**硬件初始化
RCC->APB2ENR |= 1 << 4; // 使能PORTC时钟

GPIOC->CRH &= 0xFFF0FFFF;
GPIOC->CRH |= 0x00030000; // PC.12推挽输出
GPIOC->ODR |= 0x00001000; // PC.12输出高 //端口输出高?
}

这一部分很好理解,只是把对应的红外发射端口初始化了而已。
发送高低电位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Send_H_delay(int time)
{
int i;
for (i = 0; i < time; i++)
{
IR_SEND = 1;
delay_us(13);
IR_SEND = 0;
delay_us(13);
}
}
void Send_L_delay(int time)
{

int i;
for (i = 0; i < time; i++)
{

IR_SEND = 0;
delay_us(26);
}
}

这里的延时就是考虑了38khz的频率,在38khz的频率下,每26μs就会产生一次电平变化,因此我只要发射高电平的时候每13μs交替一次即可。然后这里的time就是作为次数,用次数*26=持续的时间。
发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 void TR_SendData()
{
// 引导码
Send_H_delay(338);

Send_L_delay(170);

// 数据
for (t = 0; t < 35; t++)
{

Send_H_delay(22);

if (data[t])
{
Send_L_delay(60);
}
else
{
Send_L_delay(22);
}
}
// 连接码
// 引导码
Send_H_delay(22);

Send_L_delay(770);
// 第二段数据
for (t = 0; t < 32; t++)
{
Send_H_delay(22);

if (data[35 + t])
{
Send_L_delay(60);
}
else
{
Send_L_delay(22);
}
}
// 发送结束码
Send_H_delay(22);
Send_L_delay(22);
}

这一块为什么这么发送就需要看下面的内容了,因为他不是采用标准的NEC格式,而是使用格力空调的自有格式

格力空调编码

格力空调遥控器(YB0F2)红外码组成如下,按解码顺序排列

起始码(S)+35位数据码+连接码(C)+32位数据码

1、各种编码的电平宽度:

数据码由“0”“1”组成:

0的电平宽度为:600us低电平+600us高电平,

1的电平宽度为:600us低电平+1600us高电平

起始码S电平宽度为:9000us低电平+4500us高电平

连接码C电平宽度为:600us低电平+20000us高电平

2、数据码的形成机制

前35位数据码形成如下图所示:
嵌入式模拟格力空调遥控器-2024-05-21-21-06-13

后32位数据码形成如下图所示:

嵌入式模拟格力空调遥控器-2024-05-21-21-07-12

嵌入式模拟格力空调遥控器-2024-05-21-21-08-17
上表中,大于两位的数据都是逆序递增的,各数据的意义如下:

校验码形成:

校验码 = (模式 – 1) + (温度– 16) + 5 + 左右扫风 + 换气 + 节能 - 开关
之后取二进制后四位,再逆序;

以上
知道这些功能后就基本可以实现对格力空调的遥控功能。下面附上我的代码。
下载链接