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