]> mj.ucw.cz Git - home-hw.git/blob - aircon/firmware/main.c
da9df476d2e6403e1fc47cf0d726e2e5f47285d1
[home-hw.git] / aircon / firmware / main.c
1 /*
2  *      Air Conditioning Controller
3  *
4  *      (c) 2019 Martin Mareš <mj@ucw.cz>
5  */
6
7 #include "util.h"
8 #include "ds18b20.h"
9 #include "modbus.h"
10 #include "registers.h"
11
12 #include <libopencm3/cm3/nvic.h>
13 #include <libopencm3/cm3/systick.h>
14 #include <libopencm3/stm32/rcc.h>
15 #include <libopencm3/stm32/gpio.h>
16 #include <libopencm3/stm32/timer.h>
17 #include <libopencm3/stm32/usart.h>
18
19 #include <string.h>
20
21 static void rc_init(void);
22 static bool rc_send(char key);
23
24 static void clock_init(void)
25 {
26         rcc_clock_setup_in_hse_8mhz_out_72mhz();
27
28         rcc_periph_clock_enable(RCC_GPIOA);
29         rcc_periph_clock_enable(RCC_GPIOB);
30         rcc_periph_clock_enable(RCC_GPIOC);
31         rcc_periph_clock_enable(RCC_USART1);
32         rcc_periph_clock_enable(RCC_USART3);
33         rcc_periph_clock_enable(RCC_TIM1);
34         rcc_periph_clock_enable(RCC_TIM2);
35         rcc_periph_clock_enable(RCC_TIM3);
36         rcc_periph_clock_enable(RCC_TIM4);
37         rcc_periph_clock_enable(RCC_DMA1);
38
39         rcc_periph_reset_pulse(RST_GPIOA);
40         rcc_periph_reset_pulse(RST_GPIOB);
41         rcc_periph_reset_pulse(RST_GPIOC);
42         rcc_periph_reset_pulse(RST_USART1);
43         rcc_periph_reset_pulse(RST_USART3);
44         rcc_periph_reset_pulse(RST_TIM1);
45         rcc_periph_reset_pulse(RST_TIM2);
46         rcc_periph_reset_pulse(RST_TIM3);
47         rcc_periph_reset_pulse(RST_TIM4);
48 }
49
50 static void gpio_init(void)
51 {
52         // Switch JTAG off to free up pins
53         gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
54
55         // PA6 = RS485 TX enable
56         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6);
57         gpio_clear(GPIOA, GPIO6);
58
59         // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
60
61         // PA9 = TXD1 for debugging console
62         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
63
64         // PA10 = RXD1 for debugging console
65         gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
66
67         // PB0 = bypass LED*
68         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
69         gpio_set(GPIOB, GPIO0);
70
71         // PB1 = MODBUS frame LED*
72         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO1);
73         gpio_set(GPIOB, GPIO1);
74
75         // PB6 = TIM4_CH1 fan control opto-coupler
76         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
77
78         // PB7 = TIM4_CH2 IR LED
79         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
80
81         // PB10 = TXD3 for RS485
82         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
83
84         // PB11 = RXD3 for RS485
85         gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
86
87         // PC13 = BluePill LED
88         gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
89         gpio_clear(GPIOC, GPIO13);
90
91         // PC15 = bypass opto-coupler
92         gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
93         gpio_clear(GPIOC, GPIO15);
94 }
95
96 static volatile u32 ms_ticks;
97
98 void sys_tick_handler(void)
99 {
100         ms_ticks++;
101 }
102
103 static void tick_init(void)
104 {
105         systick_set_frequency(1000, 72000000);
106         systick_counter_enable();
107         systick_interrupt_enable();
108 }
109
110 #if 0
111 static void delay_ms(uint ms)
112 {
113         u32 start_ticks = ms_ticks;
114         while (ms_ticks - start_ticks < ms)
115                 ;
116 }
117 #endif
118
119 static void usart_init(void)
120 {
121         usart_set_baudrate(USART1, 115200);
122         usart_set_databits(USART1, 8);
123         usart_set_stopbits(USART1, USART_STOPBITS_1);
124         usart_set_mode(USART1, USART_MODE_TX_RX);
125         usart_set_parity(USART1, USART_PARITY_NONE);
126         usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
127
128         usart_enable(USART1);
129 }
130
131 // TIM4 will run on CPU clock, it will overflow with frequency 38 kHz (IR modulation frequency)
132 #define T4_CYCLE ((CPU_CLOCK_MHZ * 1000000 + 37999) / 38000)
133
134 static byte bypass_active;
135 static byte fan_pwm;
136
137 static void show_temperature(void)
138 {
139         debug_putc('#');
140         for (uint i=0; ds_sensors[i].address[0]; i++) {
141                 debug_putc(' ');
142                 int t = ds_sensors[i].current_temp;
143                 if (t == DS_TEMP_UNKNOWN)
144                         debug_puts("---.---");
145                 else
146                         debug_printf("%3d.%03d", t / 1000, t % 1000);
147         }
148         debug_printf(" %d", bypass_active);
149         debug_printf(" %d", fan_pwm);
150         debug_puts("\r\n");
151 }
152
153 static void pwm_init(void)
154 {
155         timer_set_prescaler(TIM4, 0);
156         timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
157         timer_disable_preload(TIM4);
158         timer_set_period(TIM4, T4_CYCLE - 1);
159
160         // 50% PWM for the IR LED
161         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);   // will be TIM_OCM_PWM1 when transmitting
162         timer_set_oc_value(TIM4, TIM_OC2, T4_CYCLE / 2);
163         timer_set_oc_polarity_high(TIM4, TIM_OC2);
164         timer_enable_oc_output(TIM4, TIM_OC2);
165
166         // PWM for controlling fan
167         timer_set_oc_mode(TIM4, TIM_OC1, TIM_OCM_PWM1);
168         timer_set_oc_value(TIM4, TIM_OC1, 0);
169         timer_set_oc_polarity_high(TIM4, TIM_OC1);
170         timer_enable_oc_output(TIM4, TIM_OC1);
171
172         timer_enable_counter(TIM4);
173 }
174
175 int main(void)
176 {
177         clock_init();
178         gpio_init();
179         tick_init();
180         usart_init();
181         pwm_init();
182         rc_init();
183
184         debug_puts("Hello, world! This is Aircon Controller speaking.\n");
185
186         ds_init();
187         modbus_init();
188
189         u32 last_show_temp = 0;
190         u32 last_ds_step = 0;
191
192         for (;;) {
193                 if (ms_ticks - last_ds_step >= 100) {
194                         debug_led_toggle();
195                         ds_step();
196                         last_ds_step = ms_ticks;
197                 }
198
199                 modbus_loop();
200
201 #if 0
202                 if (usart_get_flag(USART1, USART_SR_RXNE)) {
203                         uint ch = usart_recv(USART1);
204                         if (ch == 'B') {
205                                 bypass_active = 1;
206                                 gpio_set(GPIOC, GPIO15);        // opto-coupler
207                                 gpio_clear(GPIOB, GPIO0);       // LED
208                         } else if (ch == 'b') {
209                                 bypass_active = 0;
210                                 gpio_clear(GPIOC, GPIO15);      // opto-coupler
211                                 gpio_set(GPIOB, GPIO0);         // LED
212                         } else if (ch >= '0' && ch <= '9') {
213                                 fan_pwm = 3*(ch - '0') + 1;
214                                 /*
215                                  *      ch      pwm       %
216                                  *      0         1       0
217                                  *      1         4       0
218                                  *      2         7      18
219                                  *      3        10      31
220                                  *      4        13      44
221                                  *      5        16      57
222                                  *      6        19      71
223                                  *      7        22      84
224                                  *      8        25      97
225                                  *      9        28     100
226                                  *
227                                  *      % = pwm*4.389 - 12.723
228                                  */
229                                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
230                         } else {
231                                 rc_send(ch);
232                         }
233                 }
234 #endif
235
236                 if (ms_ticks - last_show_temp >= 5000) {
237                         show_temperature();
238                         last_show_temp = ms_ticks;
239                 }
240
241                 wait_for_interrupt();
242         }
243
244         return 0;
245 }
246
247 /*** Infra-red remote control transmitter ***/
248
249 enum rc_keys {
250         RC_AUTO,
251         RC_TEMP_UP,
252         RC_FUNC,
253         RC_HI,
254         RC_TIMER,
255         RC_MID,
256         RC_TEMP_DOWN,
257         RC_SLEEP,
258         RC_LOW,
259         RC_POWER,
260         RC_MAX
261 };
262
263 static const char * const rc_patterns[RC_MAX] = {
264         [RC_AUTO]       = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*A*B*B*A*B*A*A*B*B*A*A*B*A*B*B*A*$",
265         [RC_TEMP_UP]    = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*A*A*B*A*B*A*A*B*B*B*A*B*A*B*B*A*$",
266         [RC_FUNC]       = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*B*A*B*B*B*A*A*B*A*B*A*A*A*B*B*A*$",
267         [RC_HI]         = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*B*A*A*B*B*A*A*B*A*B*B*A*A*B*B*A*$",
268         [RC_TIMER]      = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*A*B*B*B*B*A*A*B*B*A*A*A*A*B*B*A*$",
269         [RC_MID]        = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*B*B*A*B*B*A*A*B*A*A*B*A*A*B*B*A*$",
270         [RC_TEMP_DOWN]  = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*B*A*B*A*B*A*A*B*A*B*A*B*A*B*B*A*$",
271         [RC_SLEEP]      = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*B*B*B*B*B*A*A*B*A*A*A*A*A*B*B*A*$",
272         [RC_LOW]        = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*A*B*A*B*B*A*A*B*B*A*B*A*A*B*B*A*$",
273         [RC_POWER]      = "^#*A*A*A*A*A*A*A*B*B*B*B*B*B*B*B*A*A*A*B*B*B*A*A*B*B*B*A*A*A*B*B*A*$",
274 };
275
276 static const char rc_keys[] = "aufhtmdslp";
277
278 static void rc_init(void)
279 {
280         // TIM1 runs at 1 MHz and it is used for timing of RC pulses
281         timer_set_prescaler(TIM1, CPU_CLOCK_MHZ - 1);
282         timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
283         timer_update_on_overflow(TIM1);
284         timer_disable_preload(TIM1);
285         timer_one_shot_mode(TIM1);
286         timer_enable_irq(TIM1, TIM_DIER_UIE);
287         nvic_enable_irq(NVIC_TIM1_UP_IRQ);
288 }
289
290 static volatile const char *rc_pattern_pos;
291 static volatile char rc_pending;
292
293 void tim1_up_isr(void)
294 {
295         if (TIM_SR(TIM1) & TIM_SR_UIF) {
296                 TIM_SR(TIM1) &= ~TIM_SR_UIF;
297
298                 if (!rc_pattern_pos)    // Just to be sure
299                         return;
300
301                 bool val;       // 1=pulse, 0=break
302                 uint duration;  // in μs
303
304                 switch (*rc_pattern_pos++) {
305                         case '^':
306                                 val = 1;
307                                 duration = 9032;
308                                 break;
309                         case '#':
310                                 val = 0;
311                                 duration = 4457;
312                                 break;
313                         case '*':
314                                 val = 1;
315                                 duration = 619;
316                                 break;
317                         case 'A':
318                                 val = 0;
319                                 duration = 514;
320                                 break;
321                         case 'B':
322                                 val = 0;
323                                 duration = 1617;
324                                 break;
325                         case '$':
326                                 val = 0;
327                                 duration = 10000;
328                                 break;
329                         default:
330                                 // End of transmission
331                                 gpio_set(GPIOC, GPIO13);
332                                 rc_pattern_pos = NULL;
333                                 rc_pending = 0;
334                                 return;
335                 }
336
337                 if (val)
338                         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1);
339                 else
340                         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);
341
342                 timer_set_period(TIM1, duration - 1);
343                 timer_generate_event(TIM1, TIM_EGR_UG);
344                 timer_enable_counter(TIM1);
345         }
346 }
347
348 static bool rc_send(char key)
349 {
350         if (rc_pending)
351                 return false;
352         if (!key)
353                 return false;
354
355         const char *s = strchr(rc_keys, key);
356         if (!s)
357                 return false;
358         rc_pending = key;
359         rc_pattern_pos = rc_patterns[s - rc_keys];
360         debug_printf("RC sending: %c\n", key);
361
362         gpio_clear(GPIOC, GPIO13);
363
364         timer_set_period(TIM1, 1);
365         timer_generate_event(TIM1, TIM_EGR_UG);
366         timer_enable_counter(TIM1);
367         return true;
368 }
369
370 /*** Modbus callbacks ***/
371
372 bool modbus_check_discrete_input(u16 addr UNUSED)
373 {
374         return false;
375 }
376
377 bool modbus_get_discrete_input(u16 addr UNUSED)
378 {
379         return false;
380 }
381
382 bool modbus_check_coil(u16 addr)
383 {
384         return addr < AIRCON_COIL_MAX;
385 }
386
387 bool modbus_get_coil(u16 addr)
388 {
389         switch (addr) {
390         case AIRCON_COIL_EXCHANGER_BYPASS:
391                 return bypass_active;
392         default:
393                 return false;
394         }
395 }
396
397 void modbus_set_coil(u16 addr, bool value)
398 {
399         switch (addr) {
400         case AIRCON_COIL_EXCHANGER_BYPASS:
401                 bypass_active = value;
402                 if (bypass_active) {
403                         gpio_set(GPIOC, GPIO15);        // opto-coupler
404                         gpio_clear(GPIOB, GPIO0);       // LED
405                 } else {
406                         gpio_clear(GPIOC, GPIO15);      // opto-coupler
407                         gpio_set(GPIOB, GPIO0);         // LED
408                 }
409                 break;
410         default:
411                 ;
412         }
413 }
414
415 #define AIRCON_IREG_DS_ID_MAX (AIRCON_IREG_DS_ID_BASE + 3*DS_NUM_SENSORS)
416
417 bool modbus_check_input_register(u16 addr)
418 {
419         return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_BASE);
420 }
421
422 static const byte temp_sensor_addrs[][8] = {
423         { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
424         { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
425         { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
426         { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
427         { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
428 };
429
430 u16 modbus_get_input_register(u16 addr)
431 {
432         if (addr <= AIRCON_IREG_TEMP_MIXED) {
433                 byte i = 0;
434                 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address + 1, temp_sensor_addrs[addr], 6))
435                         i++;
436                 if (i >= DS_NUM_SENSORS)
437                         return 0x8000;
438                 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
439                         return 0x8000;
440                 return ((ds_sensors[i].current_temp + 5) / 10) & 0xffff;
441         } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
442                 byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 3;
443                 byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 3;
444                 return get_u16_be(ds_sensors[i].address + 2*j + 1);
445         } else {
446                 return 0;
447         }
448 }
449
450 bool modbus_check_holding_register(u16 addr)
451 {
452         return addr < AIRCON_HREG_MAX;
453 }
454
455 u16 modbus_get_holding_register(u16 addr)
456 {
457         switch (addr) {
458         case AIRCON_HREG_EXCHANGER_FAN:
459                 return fan_pwm;
460         case AIRCON_HREG_REMOTE_CONTROL:
461                 return rc_pending;
462         default:
463                 return 0;
464         }
465 }
466
467 void modbus_set_holding_register(u16 addr, u16 value)
468 {
469         switch (addr) {
470         case AIRCON_HREG_EXCHANGER_FAN:
471                 fan_pwm = MIN(value, 255);
472                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
473                 break;
474         case AIRCON_HREG_REMOTE_CONTROL:
475                 if (!rc_send(value))
476                         modbus_slave_error();
477                 break;
478         default:
479                 ;
480         }
481 }
482
483 void modbus_ready_hook(void)
484 {
485         // Frame LED off
486         gpio_set(GPIOB, GPIO1);
487 }
488
489 void modbus_frame_start_hook(void)
490 {
491         // Frame LED on
492         gpio_clear(GPIOB, GPIO1);
493 }
494
495 const char * const modbus_id_strings[MODBUS_ID_MAX] = {
496         [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
497         [MODBUS_ID_PRODUCT_CODE] = "42",
498         [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
499         [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
500         [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
501         [MODBUS_ID_USER_APP_NAME] = NULL,
502 };