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