]> mj.ucw.cz Git - home-hw.git/blob - aircon/firmware/main.c
556ec89440533c052af0d7a8f239ec26e036fcc3
[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
11 #include <libopencm3/cm3/nvic.h>
12 #include <libopencm3/cm3/systick.h>
13 #include <libopencm3/stm32/rcc.h>
14 #include <libopencm3/stm32/gpio.h>
15 #include <libopencm3/stm32/timer.h>
16 #include <libopencm3/stm32/usart.h>
17
18 #include <string.h>
19
20 static void clock_setup(void)
21 {
22         rcc_clock_setup_in_hse_8mhz_out_72mhz();
23
24         rcc_periph_clock_enable(RCC_GPIOA);
25         rcc_periph_clock_enable(RCC_GPIOB);
26         rcc_periph_clock_enable(RCC_GPIOC);
27         rcc_periph_clock_enable(RCC_USART1);
28         rcc_periph_clock_enable(RCC_USART3);
29         rcc_periph_clock_enable(RCC_TIM1);
30         rcc_periph_clock_enable(RCC_TIM2);
31         rcc_periph_clock_enable(RCC_TIM3);
32         rcc_periph_clock_enable(RCC_TIM4);
33         rcc_periph_clock_enable(RCC_DMA1);
34
35         rcc_periph_reset_pulse(RST_GPIOA);
36         rcc_periph_reset_pulse(RST_GPIOB);
37         rcc_periph_reset_pulse(RST_GPIOC);
38         rcc_periph_reset_pulse(RST_USART1);
39         rcc_periph_reset_pulse(RST_USART3);
40         rcc_periph_reset_pulse(RST_TIM1);
41         rcc_periph_reset_pulse(RST_TIM2);
42         rcc_periph_reset_pulse(RST_TIM3);
43         rcc_periph_reset_pulse(RST_TIM4);
44 }
45
46 static void gpio_setup(void)
47 {
48         // Switch JTAG off to free up pins
49         gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
50
51         // PA6 = RS485 TX enable
52         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6);
53         gpio_clear(GPIOA, GPIO6);
54
55         // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
56
57         // PA9 = TXD1 for debugging console
58         gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
59
60         // PA10 = RXD1 for debugging console
61         gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
62
63         // PB0 = bypass LED*
64         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
65         gpio_set(GPIOB, GPIO0);
66
67         // PB1 = MODBUS frame LED*
68         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO1);
69         gpio_set(GPIOB, GPIO1);
70
71         // PB6 = TIM4_CH1 fan control opto-coupler
72         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
73
74         // PB7 = TIM4_CH2 IR LED
75         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
76
77         // PB10 = TXD3 for RS485
78         gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
79
80         // PB11 = RXD3 for RS485
81         gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
82
83         // PC13 = BluePill LED
84         gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
85         gpio_clear(GPIOC, GPIO13);
86
87         // PC15 = bypass opto-coupler
88         gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
89         gpio_clear(GPIOC, GPIO15);
90 }
91
92 static volatile u32 ms_ticks;
93
94 void sys_tick_handler(void)
95 {
96         ms_ticks++;
97 }
98
99 static void tick_setup(void)
100 {
101         systick_set_frequency(1000, 72000000);
102         systick_counter_enable();
103         systick_interrupt_enable();
104 }
105
106 static void delay_ms(uint ms)
107 {
108         u32 start_ticks = ms_ticks;
109         while (ms_ticks - start_ticks < ms)
110                 ;
111 }
112
113 static void usart_setup(void)
114 {
115         usart_set_baudrate(USART1, 115200);
116         usart_set_databits(USART1, 8);
117         usart_set_stopbits(USART1, USART_STOPBITS_1);
118         usart_set_mode(USART1, USART_MODE_TX_RX);
119         usart_set_parity(USART1, USART_PARITY_NONE);
120         usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
121
122         usart_enable(USART1);
123 }
124
125 // TIM4 will run on CPU clock, it will overflow with frequency 38 kHz (IR modulation frequency)
126 #define T4_CYCLE ((CPU_CLOCK_MHZ * 1000000 + 37999) / 38000)
127
128 static byte bypass_active;
129 static byte fan_pwm;
130
131 static void show_temperature(void)
132 {
133         debug_putc('#');
134         for (uint i=0; ds_sensors[i].address[0]; i++) {
135                 debug_putc(' ');
136                 int t = ds_sensors[i].current_temp;
137                 if (t == DS_TEMP_UNKNOWN)
138                         debug_puts("---.---");
139                 else
140                         debug_printf("%3d.%03d", t / 1000, t % 1000);
141         }
142         debug_printf(" %d", bypass_active);
143         debug_printf(" %d", fan_pwm);
144         debug_puts("\r\n");
145 }
146
147 static void pwm_init(void)
148 {
149         timer_set_prescaler(TIM4, 0);
150         timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
151         timer_disable_preload(TIM4);
152         timer_set_period(TIM4, T4_CYCLE - 1);
153
154         // 50% PWM for the IR LED
155         timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);   // will be TIM_OCM_PWM1 when transmitting
156         // timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1);      // FIXME
157         timer_set_oc_value(TIM4, TIM_OC2, T4_CYCLE / 2);
158         timer_set_oc_polarity_high(TIM4, TIM_OC2);
159         timer_enable_oc_output(TIM4, TIM_OC2);
160
161         // PWM for controlling fan
162         timer_set_oc_mode(TIM4, TIM_OC1, TIM_OCM_PWM1);
163         timer_set_oc_value(TIM4, TIM_OC1, 0);
164         timer_set_oc_polarity_high(TIM4, TIM_OC1);
165         timer_enable_oc_output(TIM4, TIM_OC1);
166
167         timer_enable_counter(TIM4);
168 }
169
170 int main(void)
171 {
172         clock_setup();
173         gpio_setup();
174         tick_setup();
175         usart_setup();
176         pwm_init();
177
178         debug_puts("Hello, world!\n");
179
180         ds_init();
181         modbus_init();
182
183         byte cycles = 0;
184         for (;;) {
185                 debug_led_toggle();
186                 delay_ms(100);
187                 ds_step();
188                 modbus_loop();
189                 if (usart_get_flag(USART1, USART_SR_RXNE)) {
190                         uint ch = usart_recv(USART1);
191                         if (ch == 'B') {
192                                 bypass_active = 1;
193                                 gpio_set(GPIOC, GPIO15);        // opto-coupler
194                                 gpio_clear(GPIOB, GPIO0);       // LED
195                         } else if (ch == 'b') {
196                                 bypass_active = 0;
197                                 gpio_clear(GPIOC, GPIO15);      // opto-coupler
198                                 gpio_set(GPIOB, GPIO0);         // LED
199                         } else if (ch >= '0' && ch <= '9') {
200                                 fan_pwm = 3*(ch - '0') + 1;
201                                 /*
202                                  *      ch      pwm       %
203                                  *      0         1       0
204                                  *      1         4       0
205                                  *      2         7      18
206                                  *      3        10      31
207                                  *      4        13      44
208                                  *      5        16      57
209                                  *      6        19      71
210                                  *      7        22      84
211                                  *      8        25      97
212                                  *      9        28     100
213                                  *
214                                  *      % = pwm*4.389 - 12.723
215                                  */
216                                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
217                         }
218                 }
219                 if (cycles++ >= 50) {
220                         cycles = 0;
221                         show_temperature();
222                 }
223         }
224
225         return 0;
226 }
227
228 /*** Modbus callbacks ***/
229
230 enum aircon_coils {
231         AIRCON_COIL_EXCHANGER_BYPASS,
232         AIRCON_COIL_MAX,
233 };
234
235 enum aircon_input_registers {
236         AIRCON_IREG_TEMP_FROM_INSIDE,
237         AIRCON_IREG_TEMP_TO_INSIDE,
238         AIRCON_IREG_TEMP_FROM_OUTSIDE,
239         AIRCON_IREG_TEMP_TO_OUTSIDE,
240         AIRCON_IREG_TEMP_MIXED,
241         AIRCON_IREG_MAX,
242         AIRCON_IREG_DS_ID_BASE = 0x1000,
243         AIRCON_IREG_DS_ID_MAX = AIRCON_IREG_DS_ID_BASE + 4*DS_NUM_SENSORS,
244 };
245
246 enum aircon_holding_registers {
247         AIRCON_HREG_EXCHANGER_FAN,
248         AIRCON_HREG_REMOTE_CONTROL,
249         AIRCON_HREG_MAX,
250 };
251
252 bool modbus_check_discrete_input(u16 addr UNUSED)
253 {
254         return false;
255 }
256
257 bool modbus_get_discrete_input(u16 addr UNUSED)
258 {
259         return false;
260 }
261
262 bool modbus_check_coil(u16 addr)
263 {
264         return addr < AIRCON_COIL_MAX;
265 }
266
267 bool modbus_get_coil(u16 addr)
268 {
269         switch (addr) {
270         case AIRCON_COIL_EXCHANGER_BYPASS:
271                 return bypass_active;
272         default:
273                 return false;
274         }
275 }
276
277 void modbus_set_coil(u16 addr, bool value)
278 {
279         switch (addr) {
280         case AIRCON_COIL_EXCHANGER_BYPASS:
281                 bypass_active = value;
282                 if (bypass_active) {
283                         gpio_set(GPIOC, GPIO15);        // opto-coupler
284                         gpio_clear(GPIOB, GPIO0);       // LED
285                 } else {
286                         gpio_clear(GPIOC, GPIO15);      // opto-coupler
287                         gpio_set(GPIOB, GPIO0);         // LED
288                 }
289                 break;
290         default:
291                 ;
292         }
293 }
294
295 bool modbus_check_input_register(u16 addr)
296 {
297         return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_MAX && addr < AIRCON_IREG_DS_ID_MAX);
298 }
299
300 static const byte temp_sensor_addrs[][8] = {
301         { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
302         { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
303         { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
304         { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
305         { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
306 };
307
308 u16 modbus_get_input_register(u16 addr)
309 {
310         if (addr <= AIRCON_IREG_TEMP_MIXED) {
311                 byte i = 0;
312                 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address, temp_sensor_addrs[addr], 8))
313                         i++;
314                 if (i >= DS_NUM_SENSORS)
315                         return 0x8000;
316                 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
317                         return 0x8000;
318                 return ds_sensors[i].current_temp & 0xffff;
319         } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
320                 byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 4;
321                 byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 4;
322                 return get_u16_be(ds_sensors[i].address + 2*j);
323         } else {
324                 return 0;
325         }
326 }
327
328 bool modbus_check_holding_register(u16 addr)
329 {
330         return addr < AIRCON_HREG_MAX;
331 }
332
333 u16 modbus_get_holding_register(u16 addr)
334 {
335         switch (addr) {
336         case AIRCON_HREG_EXCHANGER_FAN:
337                 return fan_pwm;
338         case AIRCON_HREG_REMOTE_CONTROL:
339         default:
340                 return 0;
341         }
342 }
343
344 void modbus_set_holding_register(u16 addr, u16 value)
345 {
346         switch (addr) {
347         case AIRCON_HREG_EXCHANGER_FAN:
348                 fan_pwm = MIN(value, 255);
349                 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
350                 break;
351         case AIRCON_HREG_REMOTE_CONTROL:
352                 break;
353         default:
354                 ;
355         }
356 }
357
358 void modbus_ready_hook(void)
359 {
360         // Frame LED off
361         gpio_set(GPIOB, GPIO1);
362 }
363
364 void modbus_frame_start_hook(void)
365 {
366         // Frame LED on
367         gpio_clear(GPIOB, GPIO1);
368 }
369
370 const char * const modbus_id_strings[MODBUS_ID_MAX] = {
371         [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
372         [MODBUS_ID_PRODUCT_CODE] = "42",
373         [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
374         [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
375         [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
376         [MODBUS_ID_USER_APP_NAME] = NULL,
377 };