-
Notifications
You must be signed in to change notification settings - Fork 6
/
midea-ir.c
328 lines (288 loc) · 8.12 KB
/
midea-ir.c
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#include "midea-ir.h"
#include <stdio.h>
#include "espressif/esp_misc.h"
#include "esp/interrupts.h"
#include "esp/gpio.h"
#include "esp/timer.h"
/**
* Midea Air conditioner protocol consists of 3 data bytes.
* After each data byte follows its inverted byte (0 an 1 are switched). It
* provides errors checking on the receiver side.
*
* Each 6 total bytes follows with additional repeat of the same 6 bytes to
* provide even more errors checking. (except move deflector command)
*/
/**
* Bits encoding.
*
* T is 21 38000kHz pulses.
*
* Bit 0 is encoded as 1T of high level and 1T of low level.
* Bit 1 is encoded as 1T of high level and 3T of low level.
*
* Start condition: 8T of hight level and 8T of low level.
* Stop bit: 6 bytes follow with one "0" stop bit.
*/
/**
* Data packet (3 bytes):
* [1010 0010] [ffff ssss] [ttttcccc]
*
* 1010 0010 - (0xB2) is a constant
*
* ffff - Fan control
* 1011 - automatic or 0
* 1001 - low speed
* 0101 - medium speed
* 0011 - high speed
* 0001 - off (or when fan speed is irrelevant)
*
* ssss - State control
* 1111 - on
* 1011 - off
*
* tttt - Temperature control (see table below)
* 0000 - 17 Celsius
* ...
* 1011 - 30 Celsius
* 1110 - off
*
* cccc - Command
* 0000 - cool
* 1100 - heat
* 1000 - automatic
* 0100 - fan
*/
//#define DEBUG_PRINT
#define TEMP_LOW 17
#define TEMP_HIGH 30
#define RAW_DATA_PACKET_SIZE 6 // each byte is sent two times
typedef struct
{
uint8_t magic; // 0xB2 always
uint8_t state : 4;
uint8_t fan : 4;
uint8_t command : 4;
uint8_t temp : 4;
} DataPacket;
// Table to convert temperature in Celsius to a strange Midea AirCon values
const static uint8_t temperature_table[] = {
0b0000, // 17 C
0b0001, // 18 C
0b0011, // 19 C
0b0010, // 20 C
0b0110, // 21 C
0b0111, // 22 C
0b0101, // 23 C
0b0100, // 24 C
0b1100, // 25 C
0b1101, // 26 C
0b1001, // 27 C
0b1000, // 28 C
0b1010, // 29 C
0b1011 // 30 C
// 0b1110 // off
};
// Table to convert fan level
const static uint8_t fan_table[] = {
0b1011, // 0
0b1001, // 1
0b0101, // 2
0b0011, // 3
};
/**
* Implementation of pulses processing.
*
* In order to generate carrier frequency and data pulses single hardware timer
* is used. It ticks with frequency double the carrier frequency in order to
* generate rising and falling edge of carrier wave.
*
* The minimum data pulses is T which is 21 pulses of carrier frequency.
* To minimize code in the interrupt handler the stream of data pulses is
* prepared. When interrupt handlers sees pulse value "1" it needs to generate
* 21 pulses of 38kHz. When interrupt handler sees pulse value "0" it is being
* silent for time of 21 periods of 38kHz.
*
* ________ _ _ _
* signal: _| |________| |___| |_| | ... (without carrier)
* meaning: "start condition" "1" "0"
* pulses: 11111111 00000000 1 000 1 0 1
*/
#define PULSES_CAPACITY 29 // 8T + 8T + (4T * 8 * 6) + 8T
#define SUB_PULSES_PER_PULSE 42 // (high + low) * 21
typedef struct
{
uint8_t pin_number;
uint8_t pulses[PULSES_CAPACITY]; // pulses to spit out
uint8_t pulses_size;
uint8_t repeat_count; // how many times to repeat
uint8_t current_pulse; // current processing pulse
uint8_t current_sub_pulse; // 38000 kHz pulse
} IrState;
static volatile IrState ir_state;
static void timer_interrupt_handler(void)
{
// get current pulse value
bool pulse_val = ir_state.pulses[ir_state.current_pulse/8]
& (1<<(ir_state.current_pulse%8));
if (ir_state.current_sub_pulse < SUB_PULSES_PER_PULSE) {
if (!(ir_state.current_sub_pulse % 2) && pulse_val) {
gpio_write(ir_state.pin_number, true);
} else {
gpio_write(ir_state.pin_number, false);
}
ir_state.current_sub_pulse++;
} else { // pulse is finished
ir_state.current_sub_pulse = 0;
ir_state.current_pulse++;
if (ir_state.current_pulse >= ir_state.pulses_size) {
ir_state.repeat_count--;
if (ir_state.repeat_count) {
ir_state.current_pulse = 0;
ir_state.current_sub_pulse = 0;
} else {
timer_set_run(FRC1, false);
}
}
}
}
static inline void pack_data(MideaIR *ir, DataPacket *data)
{
data->magic = 0xB2;
if (ir->enabled) {
if (ir->mode == MODE_AUTO) {
data->fan = 0b0001; // for auto mode fan must be 0b0001
} else {
data->fan = fan_table[ir->fan_level];
}
data->state = 0b1111; // on
data->command = ir->mode;
if (ir->mode == MODE_FAN) {
data->temp = 0b1110;
} else {
if (ir->temperature >= TEMP_LOW && ir->temperature <= TEMP_HIGH) {
data->temp = temperature_table[ir->temperature - TEMP_LOW];
} else {
data->temp = 0b0100;
}
}
} else {
data->fan = 0b0111;
data->state = 0b1011; // off
data->command = 0b0000;
data->temp = 0b1110;
}
}
void midea_ir_init(MideaIR *ir, const uint8_t pin_number)
{
ir_state.pin_number = pin_number;
ir_state.repeat_count = 0; // indicates IDLE state
ir->temperature = 24;
ir->enabled = false;
ir->mode = MODE_AUTO;
ir->fan_level = 0;
gpio_enable(ir_state.pin_number, GPIO_OUTPUT);
gpio_write(ir_state.pin_number, false);
_xt_isr_attach(INUM_TIMER_FRC1, timer_interrupt_handler);
timer_set_frequency(FRC1, 38000 * 2); // two iterrupts per period
timer_set_interrupts(FRC1, true);
}
#ifdef DEBUG_PRINT
static inline void print_bit(bool bit)
{
if (bit) {
printf("1");
} else {
printf("0");
}
}
#endif
static inline void init_buff()
{
ir_state.current_pulse = 0;
ir_state.current_sub_pulse = 0;
for (uint8_t i = 0; i < PULSES_CAPACITY; i++) {
ir_state.pulses[i] = 0;
}
}
static inline void add_start()
{
ir_state.pulses[0] = 0b11111111;
ir_state.pulses[1] = 0b00000000;
ir_state.current_pulse = 8 * 2;
}
static inline void add_bit(bool bit)
{
// add 1 to the pulses
ir_state.pulses[ir_state.current_pulse/8] |=
(1<<(ir_state.current_pulse%8));
ir_state.current_pulse++;
if (bit) {
ir_state.current_pulse += 3; // bit 1 -> pulses 1000
} else {
ir_state.current_pulse++; // bit 0 -> pulses 10
}
}
static inline void add_stop()
{
add_bit(true);
ir_state.current_pulse += 8;
}
static inline void start(const uint8_t repeat)
{
ir_state.pulses_size= ir_state.current_pulse;
ir_state.current_pulse = 0;
ir_state.current_sub_pulse = 0;
ir_state.repeat_count = repeat;
timer_set_run(FRC1, true);
}
/* For each byte in src add two bytes in dst (normal and bit-wise inverted)
*/
static inline void add_complementary_bytes(const uint8_t *src, uint8_t *dst)
{
for (int i = 0; i < 3; i++) {
*dst = *src;
dst++;
*dst = ~(*src);
dst++;
src++;
}
}
static inline void send_ir_data(const uint8_t data[RAW_DATA_PACKET_SIZE],
const uint8_t repeat)
{
init_buff();
add_start();
#ifdef DEBUG_PRINT
printf("Data: ");
#endif
for (int b = 0; b < RAW_DATA_PACKET_SIZE; b++) {
uint8_t v = data[b];
for (uint8_t i = 0; i < 8; i++) {
#ifdef DEBUG_PRINT
print_bit(v & (1<<7));
#endif
add_bit(v & (1<<7));
v <<= 1;
}
}
#ifdef DEBUG_PRINT
printf("\n");
#endif
add_stop();
start(repeat);
}
void midea_ir_send(MideaIR *ir)
{
DataPacket packet;
pack_data(ir, &packet);
uint8_t data[RAW_DATA_PACKET_SIZE];
add_complementary_bytes((uint8_t*)&packet, data);
send_ir_data(data, 2);
}
void midea_ir_move_deflector(MideaIR *ir)
{
uint8_t data[RAW_DATA_PACKET_SIZE/2] = {0xB2, 0x0F, 0xE0};
uint8_t raw_data[RAW_DATA_PACKET_SIZE];
add_complementary_bytes(data, raw_data);
send_ir_data(raw_data, 1);
}