飞利浦 RC-MM 协议

image-20250930105841612

image-20250930105924180

1
2
3
4
00 = 0
01 = 1
10 = 2
11 = 3

飞利浦RC-5协议

3010红外格式说明

3010

IRReader波形数据

image-20251212112938887

从IRReader波形数据的波形可以看出,3010的相关定义

帧定义

一帧有15位( 2位起始位 + 1位控制位 + 5位地址码 + 6位命令码 + 1位停止位)

位定义

逻辑值 低电平时长 高电平时长 总周期 (上升沿到上升沿) 解码依据
0 880 µs 880 µs 1.76 ms 测量到脉冲间隔较短
1 1.8 ms 880 µs 2.68 ms 测量到脉冲间隔较长

数据规则

1.起始位规则:一帧的开头两个位(起始位)是“11”还是“10”,由数据码(D5-D0)的值决定。这是一种将数据码高位信息“隐藏”在起始位中的巧妙设计。

  • 规则A:如果数据码 ≤ 0x3F(即≤63),则起始位固定为 “11”
  • 规则B:如果数据码 > 0x3F,则:在发送数据码部分时,只发送其低6位(舍弃最高位)。同时,将起始位设置为 “10”
  • 目的:此规则等效于将数据码的最高位“移动”到了起始位中发送,使得6位数据码能表示大于63的数值,扩展了指令容量。

2.奇偶判断位功能:帧中的一个特殊位,用于区分是持续按键还是松开后重新按键(该位在重新按键时会翻转)。它不用于错误校验,而是用于按键状态机判断。

3.地址码:固定部分。每个按键都一样

4.命令码:就是3010格式的数据码,每个按键不一样,只取低6位

5.停止码:固定部分。每个按键都一样

代码编写

在奉加微芯片中

获取红外码值get_ir_key_code函数和红外发送handle函数如下

首先按下按键后会在app_key_press中触发ir_sending_handle

ir_sending_handle在执行判断 if( get_ir_key_code(key_index, temp_infrated_code) != 1)的时候同时将码值通过指针的方式给到临时数组temp_infrated_code中,最后往下执行程序的时候将temp_infrated_code数据传入ir_data_encode

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
int8 get_ir_key_code( uint8 key_id, uint8* remap_ir_data )
{
uint8 index = 0;

if( remap_ir_data == NULL )
{
return -1;
}

// ! get key id index
if( (IR_TEST_MODE_SUPPORT == TRUE) && (flag_ir_test_mode == TRUE) )
{
for( index = 0; index < sizeof( app_ir_test_keyCode )/ sizeof(app_ir_test_keyCode[0]); index++ )
{
if( key_id == app_ir_test_keyCode[ index ].ir_key_index )
{
break;
}
}

if ((index < 0) || (index >= sizeof( app_ir_test_keyCode )/ sizeof(app_ir_test_keyCode[0])))
{
LOG("Index out of range: %d\n", index);
return -1;
}
// ! use code
remap_ir_data[0] = IR_TM_USER_CODE;
// ! use inverse code
remap_ir_data[1] = IR_TM_USER_CODE_INV;
// ! command code
remap_ir_data[2] = app_ir_test_keyCode[ index ].ir_key_value;
// ! command inverse code
remap_ir_data[3] = 0XFF - remap_ir_data[2];
return 1;
}

else
{
for( index = 0; index < sizeof( app_ir_keyCode )/ sizeof(app_ir_keyCode[0]); index++ )
{
if( key_id == app_ir_keyCode[ index ].ir_key_index )
{
break;
}
}
if ( (index < 0) || (index >= sizeof( app_ir_keyCode )/ sizeof(app_ir_keyCode[0])))
{
LOG("Index out of range: %d\n", index);
return -1;
}
// ! use code
remap_ir_data[0] = LOCAL_CUSTOMER_CODE;
// ! use inverse code
remap_ir_data[1] = 0XFE;
// ! command code
remap_ir_data[2] = app_ir_keyCode[ index ].ir_key_value;
// ! command inverse code
remap_ir_data[3] = 0XFF - remap_ir_data[2];
return 1;
}

}

void ir_sending_handle(uint8 key_index)
{
uint8 temp_infrated_code[4] = { 0x00 };
hal_watchdog_feed();
// ! send checking last ir status
ir_sending_stop_handle(0);

// ! send checking wheather ir in learn
if( get_ir_learn_mode() == 1 )
{
IR_SEND_LOG("ir in learn mode\r\n");
return;
}

IR_SEND_LOG("ir sending\n");

if( get_ir_key_code(key_index, temp_infrated_code) != 1)
{
IR_SEND_LOG("key id error\r\n");
return;
}

if( Ir_Send_Parm.ir_ctx_cfg.ir_md == IR_MODE_1 )
{
hal_gpio_write(Ir_Send_Parm.ir_ctx_cfg.ir_control_pin, 1);
}

// !ir wave data update
ir_data_encode(temp_infrated_code, Ir_Send_Parm.ir_38khz_wave_buff );
Ir_Send_Parm.send_waveform_index = 0;
Ir_Send_Parm.switch_flag = 0;
Ir_Send_Parm.ir_sending_work = true;
Ir_Send_Parm.notify_ir_stop_flag = true;
ir_timer_init();
// !pwm config
pwm_ch_t ir_pwm_config;
hal_pwm_module_init();
ir_pwm_config.pwmN = PWM_CH0;
ir_pwm_config.pwmPin = Ir_Send_Parm.ir_ctx_cfg.ir_sending_pin;
ir_pwm_config.cmpVal = IR_SENDING_38KHZ_COM_VALUE;
ir_pwm_config.cntTopVal = IR_SENDING_38KHZ_TOP_VALUE;
ir_pwm_config.pwmMode = PWM_CNT_UP;
ir_pwm_config.pwmPolarity = PWM_POLARITY_RISING;
ir_pwm_config.pwmDiv = PWM_CLK_DIV_4;
hal_pwm_ch_start( ir_pwm_config );
hal_timer_set( IR_SENDING_TIMER_ID, Ir_Send_Parm.ir_38khz_wave_buff[0] );
}

我们继续看在得到红外数据并传入编码函数ir_data_encode后,是如何对十六进制的红外数据进行编码位38kHz的波形发送的

下面是ir_data_encoded的函数实现

在上面我们通过将temp_infrated_code数组传入get_ir_key_code,将红外数据存储到temp_infrated_code数组中,并将temp_infrated_code作为get_ir_key_code的参数Origin_Ir_data,需要注意的是这里没有进行数据的复制,而只是将temp_infrated_code的地址传入编码函数当中

所以实际上,我们在app_cfg上写的十六进制的红外用户码和红外数据码,最后是到了ir_data_encode的一个for循环中进行处理

这里for循环处理的目的是将十六进制的数据的每一位进行0和1的编码处理,具体是通过if(Origin_Ir_data[j] & 0x01)这一句位与运算实现0和1的判读和编码的,并且采用低位优先(LSB First)的传输方式,在对最低位进行编码后, Origin_Ir_data[j] >>= 1这句代码会将当前字节右移一位,这样,原来的次低位就变成了新的最低位,等待下一次循环进行判断。这个过程就像我们从左到右阅读一样,程序从字节的最低位开始,逐个比特进行处理

i < 8决定了处理的位数位8位,即一个字节,j < 4决定了处理的一帧的数据为4个字节

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
void ir_data_encode( uint8* Origin_Ir_data, uint32* encode_Ir_data )
{
if( Origin_Ir_data == NULL || encode_Ir_data == NULL )
{
return;
}

// !ir wave data update
uint8 id = 0;
encode_Ir_data[id++] = IR_38KHZ_START_CARRIER;
encode_Ir_data[id++] = IR_38KHZ_START_NO_CARRIER;

for( uint8 j =0; j < 4; j++ )
{
for( uint8 i = 0; i < 8; i++ )
{
if( Origin_Ir_data[j] & 0x01 )
{
encode_Ir_data[id++] = IR_38KHZ_HIGH_LEVEL_CARRIER;
encode_Ir_data[id++] = IR_38KHZ_HIGH_LEVEL_NO_CARRIER;
}
else
{
encode_Ir_data[id++] = IR_38KHZ_LOW_LEVEL_CARRIER;
encode_Ir_data[id++] = IR_38KHZ_LOW_LEVEL_NO_CARRIER;
}

Origin_Ir_data[j] >>= 1;
}
}

// !stop code
encode_Ir_data[id++] = IR_38KHZ_STOP_CARRIER;
encode_Ir_data[id++] = IR_38KHZ_STOP_NO_CARRIER;
//9ms carrier + 2.25ms null carrier + 560us carrier +97.94ms null carrier-->repeate code
encode_Ir_data[id++] = IR_38KHZ_REPEATED_CARRIER_1;
encode_Ir_data[id++] = IR_38KHZ_REPEATED_NO_CARRIER_1;
encode_Ir_data[id++] = IR_38KHZ_REPEATED_CARRIER_2;
encode_Ir_data[id++] = IR_38KHZ_REPEATED_NO_CARRIER_2;
g_ir_data_count = IR_SENDING_38KHZ_BITS_INDEX + 1;
g_ir_is_repeart = 1;
return;
}

接下来我们需要将红外格式由6121格式改为3010格式,我们使用游程编码(Run-Length Encoding)对RC5(3010)红外格式进行编码

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// 定义 RC5 的位总数:2 Start + 1 Toggle + 5 Address + 6 Command = 14 bits
#define RC5_TOTAL_BITS 14

// 全局或静态变量用来存储 Toggle 位(每次按键按下取反,这里先默认写0,你需要根据业务逻辑修改)
static uint8_t g_rc5_toggle_bit = 0;

void ir_data_encode( uint8* Origin_Ir_data, uint32* encode_Ir_data )
{
if( Origin_Ir_data == NULL || encode_Ir_data == NULL )
{
return;
}

uint8_t raw_bits[RC5_TOTAL_BITS];
uint8_t half_bits_stream[RC5_TOTAL_BITS * 2]; // 存储曼彻斯特编码后的电平流
uint32_t id = 0;
int bit_index = 0;

// --------------------------------------------------------
// 第一步:构建原始位数组 (Start, Toggle, Address, Command)
// --------------------------------------------------------
// 1. 起始位 S1, S2 (RC5固定为 1, 1)
raw_bits[bit_index++] = 1;
// raw_bits[bit_index++] = 1;
if (Origin_Ir_data[1] > 0x3F)
{
raw_bits[bit_index++] = 0;

}
else
{
raw_bits[bit_index++] = 1;

}
// 2. 控制位 Toggle (需外部维护,每次新按键翻转)
// 如果你有外部逻辑维护 toggle,请传入;这里暂时使用默认值
raw_bits[bit_index++] = g_rc5_toggle_bit & 0x01;

// 3. 客户码/地址码 (Address) - 5位
// 注意:你的原代码是 Origin_Ir_data[0] 低位在前 (LSB First)
for(int i = 0; i < 5; i++)
{
int bit_position = (5 -1 - i) % 8; //从第五位读取到最低位
raw_bits[bit_index++] = (Origin_Ir_data[0] >> bit_position) & 0x01;
}

for(int i = 0; i < 6; i++)
{
int bit_position = (5 - i) % 8; //从第六位读取到最低位
raw_bits[bit_index++] = (Origin_Ir_data[1] >> bit_position) & 0x01;
}

// --------------------------------------------------------
// 第二步:曼彻斯特编码展开 (生成电平流)
// 规则:逻辑 1 -> 低(0), 高(1) ; 逻辑 0 -> 高(1), 低(0)
// --------------------------------------------------------
for(int i = 0; i < RC5_TOTAL_BITS; i++)
{
if(raw_bits[i] == 1)
{
half_bits_stream[i*2] = 0;
half_bits_stream[i*2 + 1] = 1;
}
else
{
half_bits_stream[i*2] = 1;
half_bits_stream[i*2 + 1] = 0;
}
}

// --------------------------------------------------------
// 第三步:RLE 游程编码 (计算脉冲时间)
// --------------------------------------------------------

// RC5 特殊处理:起始位 S1 是逻辑 "1" (0->1)。
// 前半段的 0 与空闲状态重合,不可见。发送从 S1 的后半段(1)开始。
// 所以我们从 stream[1] 开始统计。

uint8_t current_level = half_bits_stream[1]; // S1 的后半段肯定是高电平
uint8_t count = 1; // 当前已累积了 1 个 880us

uint8_t change_count = 1; //

// 从 stream[2] 开始遍历到最后
for(int k = 2; k < RC5_TOTAL_BITS * 2; k++)
{
if (half_bits_stream[k] == current_level)
{
// 电平没变(例如 1 -> 1),时间累加
count++;
}
else
{
change_count++; // 电平变化次数增加

// 电平变了(例如 1 -> 0),先把之前累积的时间写入数组
if (count == 1)
{
encode_Ir_data[id++] = IR_38KHZ_Manchester_880;
}
else
{
// count 必然是 2 (RC5最多连续两个半位同电平)
encode_Ir_data[id++] = IR_38KHZ_Manchester_1760;
}

// 重置计数器,更新当前电平
current_level = half_bits_stream[k];
count = 1;
}
}

LOG("RC5 Change Count: %d\n", change_count);

if (change_count % 2 == 0) {
// 偶数
encode_Ir_data[id++] = 85000 +880 ;
}
else
{
// 奇数,补一个半位
// 循环结束后,别忘了写入最后一段累积的时间
if (count == 1)
{
encode_Ir_data[id++] = IR_38KHZ_Manchester_880;
}
else
{
encode_Ir_data[id++] = IR_38KHZ_Manchester_1760;
}
encode_Ir_data[id++] = 85000 ;
}

//重复发送数据码
g_ir_data_count = IR_SENDING_38KHZ_BITS_INDEX + 1;
g_ir_is_repeart = 0;
return;
}