2 * Air Conditioning Controller
4 * (c) 2019 Martin Mareš <mj@ucw.cz>
10 #include "registers.h"
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>
21 static void rc_init(void);
22 static bool rc_send(char key);
24 static void clock_init(void)
26 rcc_clock_setup_in_hse_8mhz_out_72mhz();
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);
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);
50 static void gpio_init(void)
52 // Switch JTAG off to free up pins
53 gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
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);
59 // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
61 // PA9 = TXD1 for debugging console
62 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
64 // PA10 = RXD1 for debugging console
65 gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
68 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
69 gpio_set(GPIOB, GPIO0);
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);
75 // PB6 = TIM4_CH1 fan control opto-coupler
76 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
78 // PB7 = TIM4_CH2 IR LED
79 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
81 // PB10 = TXD3 for RS485
82 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
84 // PB11 = RXD3 for RS485
85 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
87 // PC13 = BluePill LED
88 gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
89 gpio_clear(GPIOC, GPIO13);
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);
96 static volatile u32 ms_ticks;
98 void sys_tick_handler(void)
103 static void tick_init(void)
105 systick_set_frequency(1000, 72000000);
106 systick_counter_enable();
107 systick_interrupt_enable();
111 static void delay_ms(uint ms)
113 u32 start_ticks = ms_ticks;
114 while (ms_ticks - start_ticks < ms)
119 static void usart_init(void)
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);
128 usart_enable(USART1);
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)
134 static byte bypass_active;
137 static void show_temperature(void)
140 for (uint i=0; ds_sensors[i].address[0]; i++) {
142 int t = ds_sensors[i].current_temp;
143 if (t == DS_TEMP_UNKNOWN)
144 debug_puts("---.---");
146 debug_printf("%3d.%03d", t / 1000, t % 1000);
148 debug_printf(" %d", bypass_active);
149 debug_printf(" %d", fan_pwm);
153 static void pwm_init(void)
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);
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);
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);
172 timer_enable_counter(TIM4);
184 debug_puts("Hello, world! This is Aircon Controller speaking.\n");
189 u32 last_show_temp = 0;
190 u32 last_ds_step = 0;
193 if (ms_ticks - last_ds_step >= 100) {
196 last_ds_step = ms_ticks;
202 if (usart_get_flag(USART1, USART_SR_RXNE)) {
203 uint ch = usart_recv(USART1);
206 gpio_set(GPIOC, GPIO15); // opto-coupler
207 gpio_clear(GPIOB, GPIO0); // LED
208 } else if (ch == 'b') {
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;
227 * % = pwm*4.389 - 12.723
229 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
236 if (ms_ticks - last_show_temp >= 5000) {
238 last_show_temp = ms_ticks;
241 wait_for_interrupt();
247 /*** Infra-red remote control transmitter ***/
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*$",
276 static const char rc_keys[] = "aufhtmdslp";
278 static void rc_init(void)
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);
290 static volatile const char *rc_pattern_pos;
291 static volatile char rc_pending;
293 void tim1_up_isr(void)
295 if (TIM_SR(TIM1) & TIM_SR_UIF) {
296 TIM_SR(TIM1) &= ~TIM_SR_UIF;
298 if (!rc_pattern_pos) // Just to be sure
301 bool val; // 1=pulse, 0=break
302 uint duration; // in μs
304 switch (*rc_pattern_pos++) {
330 // End of transmission
331 gpio_set(GPIOC, GPIO13);
332 rc_pattern_pos = NULL;
338 timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1);
340 timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);
342 timer_set_period(TIM1, duration - 1);
343 timer_generate_event(TIM1, TIM_EGR_UG);
344 timer_enable_counter(TIM1);
348 static bool rc_send(char key)
355 const char *s = strchr(rc_keys, key);
359 rc_pattern_pos = rc_patterns[s - rc_keys];
360 debug_printf("RC sending: %c\n", key);
362 gpio_clear(GPIOC, GPIO13);
364 timer_set_period(TIM1, 1);
365 timer_generate_event(TIM1, TIM_EGR_UG);
366 timer_enable_counter(TIM1);
370 /*** Modbus callbacks ***/
372 bool modbus_check_discrete_input(u16 addr UNUSED)
377 bool modbus_get_discrete_input(u16 addr UNUSED)
382 bool modbus_check_coil(u16 addr)
384 return addr < AIRCON_COIL_MAX;
387 bool modbus_get_coil(u16 addr)
390 case AIRCON_COIL_EXCHANGER_BYPASS:
391 return bypass_active;
397 void modbus_set_coil(u16 addr, bool value)
400 case AIRCON_COIL_EXCHANGER_BYPASS:
401 bypass_active = value;
403 gpio_set(GPIOC, GPIO15); // opto-coupler
404 gpio_clear(GPIOB, GPIO0); // LED
406 gpio_clear(GPIOC, GPIO15); // opto-coupler
407 gpio_set(GPIOB, GPIO0); // LED
415 #define AIRCON_IREG_DS_ID_MAX (AIRCON_IREG_DS_ID_BASE + 3*DS_NUM_SENSORS)
417 bool modbus_check_input_register(u16 addr)
419 return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_BASE);
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 },
430 u16 modbus_get_input_register(u16 addr)
432 if (addr <= AIRCON_IREG_TEMP_MIXED) {
434 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address + 1, temp_sensor_addrs[addr], 6))
436 if (i >= DS_NUM_SENSORS)
438 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
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);
450 bool modbus_check_holding_register(u16 addr)
452 return addr < AIRCON_HREG_MAX;
455 u16 modbus_get_holding_register(u16 addr)
458 case AIRCON_HREG_EXCHANGER_FAN:
460 case AIRCON_HREG_REMOTE_CONTROL:
467 void modbus_set_holding_register(u16 addr, u16 value)
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);
474 case AIRCON_HREG_REMOTE_CONTROL:
476 modbus_slave_error();
483 void modbus_ready_hook(void)
486 gpio_set(GPIOB, GPIO1);
489 void modbus_frame_start_hook(void)
492 gpio_clear(GPIOB, GPIO1);
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,