]> mj.ucw.cz Git - home-hw.git/blob - aircon/firmware/main.c
Aircon: Better main loop
[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 (usart_get_flag(USART1, USART_SR_RXNE)) {
202                         uint ch = usart_recv(USART1);
203                         if (ch == 'B') {
204                                 bypass_active = 1;
205                                 gpio_set(GPIOC, GPIO15);        // opto-coupler
206                                 gpio_clear(GPIOB, GPIO0);       // LED
207                         } else if (ch == 'b') {
208                                 bypass_active = 0;
209                                 gpio_clear(GPIOC, GPIO15);      // opto-coupler
210                                 gpio_set(GPIOB, GPIO0);         // LED
211                         } else if (ch >= '0' && ch <= '9') {
212                                 fan_pwm = 3*(ch - '0') + 1;
213                                 /*
214                                  *      ch      pwm       %
215                                  *      0         1       0
216                                  *      1         4       0
217                                  *      2         7      18
218                                  *      3        10      31
219                                  *      4        13      44
220                                  *      5        16      57
221                                  *      6        19      71
222                                  *      7        22      84
223                                  *      8        25      97
224                                  *      9        28     100
225                                  *
226                                  *      % = pwm*4.389 - 12.723
227                                  */
228                                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
229                         } else {
230                                 rc_send(ch);
231                         }
232                 }
233
234                 if (ms_ticks - last_show_temp >= 5000) {
235                         show_temperature();
236                         last_show_temp = ms_ticks;
237                 }
238
239                 wait_for_interrupt();
240         }
241
242         return 0;
243 }
244
245 /*** Infra-red remote control transmitter ***/
246
247 enum rc_keys {
248         RC_AUTO,
249         RC_TEMP_UP,
250         RC_FUNC,
251         RC_HI,
252         RC_TIMER,
253         RC_MID,
254         RC_TEMP_DOWN,
255         RC_SLEEP,
256         RC_LOW,
257         RC_POWER,
258         RC_MAX
259 };
260
261 static const char * const rc_patterns[RC_MAX] = {
262         [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*$",
263         [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*$",
264         [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*$",
265         [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*$",
266         [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*$",
267         [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*$",
268         [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*$",
269         [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*$",
270         [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*$",
271         [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*$",
272 };
273
274 static const char rc_keys[] = "aufhtmdslp";
275
276 static void rc_init(void)
277 {
278         // TIM1 runs at 1 MHz and it is used for timing of RC pulses
279         timer_set_prescaler(TIM1, CPU_CLOCK_MHZ - 1);
280         timer_set_mode(TIM1, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
281         timer_update_on_overflow(TIM1);
282         timer_disable_preload(TIM1);
283         timer_one_shot_mode(TIM1);
284         timer_enable_irq(TIM1, TIM_DIER_UIE);
285         nvic_enable_irq(NVIC_TIM1_UP_IRQ);
286 }
287
288 static volatile const char *rc_pattern_pos;
289 static volatile char rc_pending;
290
291 void tim1_up_isr(void)
292 {
293         if (TIM_SR(TIM1) & TIM_SR_UIF) {
294                 TIM_SR(TIM1) &= ~TIM_SR_UIF;
295
296                 if (!rc_pattern_pos)    // Just to be sure
297                         return;
298
299                 bool val;       // 1=pulse, 0=break
300                 uint duration;  // in μs
301
302                 switch (*rc_pattern_pos++) {
303                         case '^':
304                                 val = 1;
305                                 duration = 9032;
306                                 break;
307                         case '#':
308                                 val = 0;
309                                 duration = 4457;
310                                 break;
311                         case '*':
312                                 val = 1;
313                                 duration = 619;
314                                 break;
315                         case 'A':
316                                 val = 0;
317                                 duration = 514;
318                                 break;
319                         case 'B':
320                                 val = 0;
321                                 duration = 1617;
322                                 break;
323                         case '$':
324                                 val = 0;
325                                 duration = 10000;
326                                 break;
327                         default:
328                                 // End of transmission
329                                 gpio_set(GPIOC, GPIO13);
330                                 rc_pattern_pos = NULL;
331                                 rc_pending = 0;
332                                 return;
333                 }
334
335                 if (val)
336                         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1);
337                 else
338                         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);
339
340                 timer_set_period(TIM1, duration - 1);
341                 timer_generate_event(TIM1, TIM_EGR_UG);
342                 timer_enable_counter(TIM1);
343         }
344 }
345
346 static bool rc_send(char key)
347 {
348         if (rc_pending)
349                 return false;
350
351         const char *s = strchr(rc_keys, key);
352         if (!s)
353                 return false;
354         rc_pending = key;
355         rc_pattern_pos = rc_patterns[s - rc_keys];
356         debug_printf("RC sending: %c", key);
357
358         gpio_clear(GPIOC, GPIO13);
359
360         timer_set_period(TIM1, 1);
361         timer_generate_event(TIM1, TIM_EGR_UG);
362         timer_enable_counter(TIM1);
363         return true;
364 }
365
366 /*** Modbus callbacks ***/
367
368 bool modbus_check_discrete_input(u16 addr UNUSED)
369 {
370         return false;
371 }
372
373 bool modbus_get_discrete_input(u16 addr UNUSED)
374 {
375         return false;
376 }
377
378 bool modbus_check_coil(u16 addr)
379 {
380         return addr < AIRCON_COIL_MAX;
381 }
382
383 bool modbus_get_coil(u16 addr)
384 {
385         switch (addr) {
386         case AIRCON_COIL_EXCHANGER_BYPASS:
387                 return bypass_active;
388         default:
389                 return false;
390         }
391 }
392
393 void modbus_set_coil(u16 addr, bool value)
394 {
395         switch (addr) {
396         case AIRCON_COIL_EXCHANGER_BYPASS:
397                 bypass_active = value;
398                 if (bypass_active) {
399                         gpio_set(GPIOC, GPIO15);        // opto-coupler
400                         gpio_clear(GPIOB, GPIO0);       // LED
401                 } else {
402                         gpio_clear(GPIOC, GPIO15);      // opto-coupler
403                         gpio_set(GPIOB, GPIO0);         // LED
404                 }
405                 break;
406         default:
407                 ;
408         }
409 }
410
411 #define AIRCON_IREG_DS_ID_MAX (AIRCON_IREG_DS_ID_BASE + 3*DS_NUM_SENSORS)
412
413 bool modbus_check_input_register(u16 addr)
414 {
415         return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_BASE);
416 }
417
418 static const byte temp_sensor_addrs[][8] = {
419         { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
420         { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
421         { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
422         { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
423         { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
424 };
425
426 u16 modbus_get_input_register(u16 addr)
427 {
428         if (addr <= AIRCON_IREG_TEMP_MIXED) {
429                 byte i = 0;
430                 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address + 1, temp_sensor_addrs[addr], 6))
431                         i++;
432                 if (i >= DS_NUM_SENSORS)
433                         return 0x8000;
434                 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
435                         return 0x8000;
436                 return ((ds_sensors[i].current_temp + 5) / 10) & 0xffff;
437         } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
438                 byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 3;
439                 byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 3;
440                 return get_u16_be(ds_sensors[i].address + 2*j + 1);
441         } else {
442                 return 0;
443         }
444 }
445
446 bool modbus_check_holding_register(u16 addr)
447 {
448         return addr < AIRCON_HREG_MAX;
449 }
450
451 u16 modbus_get_holding_register(u16 addr)
452 {
453         switch (addr) {
454         case AIRCON_HREG_EXCHANGER_FAN:
455                 return fan_pwm;
456         case AIRCON_HREG_REMOTE_CONTROL:
457                 return rc_pending;
458         default:
459                 return 0;
460         }
461 }
462
463 void modbus_set_holding_register(u16 addr, u16 value)
464 {
465         switch (addr) {
466         case AIRCON_HREG_EXCHANGER_FAN:
467                 fan_pwm = MIN(value, 255);
468                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
469                 break;
470         case AIRCON_HREG_REMOTE_CONTROL:
471                 if (!rc_send(value))
472                         modbus_slave_error();
473                 break;
474         default:
475                 ;
476         }
477 }
478
479 void modbus_ready_hook(void)
480 {
481         // Frame LED off
482         gpio_set(GPIOB, GPIO1);
483 }
484
485 void modbus_frame_start_hook(void)
486 {
487         // Frame LED on
488         gpio_clear(GPIOB, GPIO1);
489 }
490
491 const char * const modbus_id_strings[MODBUS_ID_MAX] = {
492         [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
493         [MODBUS_ID_PRODUCT_CODE] = "42",
494         [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
495         [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
496         [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
497         [MODBUS_ID_USER_APP_NAME] = NULL,
498 };