2 * Air Conditioning Controller
4 * (c) 2019 Martin Mareš <mj@ucw.cz>
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>
20 static void clock_setup(void)
22 rcc_clock_setup_in_hse_8mhz_out_72mhz();
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);
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);
46 static void gpio_setup(void)
48 // Switch JTAG off to free up pins
49 gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
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);
55 // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
57 // PA9 = TXD1 for debugging console
58 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
60 // PA10 = RXD1 for debugging console
61 gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
64 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
65 gpio_set(GPIOB, GPIO0);
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);
71 // PB6 = TIM4_CH1 fan control opto-coupler
72 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
74 // PB7 = TIM4_CH2 IR LED
75 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
77 // PB10 = TXD3 for RS485
78 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
80 // PB11 = RXD3 for RS485
81 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
83 // PC13 = BluePill LED
84 gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
85 gpio_clear(GPIOC, GPIO13);
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);
92 static volatile u32 ms_ticks;
94 void sys_tick_handler(void)
99 static void tick_setup(void)
101 systick_set_frequency(1000, 72000000);
102 systick_counter_enable();
103 systick_interrupt_enable();
106 static void delay_ms(uint ms)
108 u32 start_ticks = ms_ticks;
109 while (ms_ticks - start_ticks < ms)
113 static void usart_setup(void)
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);
122 usart_enable(USART1);
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)
128 static byte bypass_active;
131 static void show_temperature(void)
134 for (uint i=0; ds_sensors[i].address[0]; i++) {
136 int t = ds_sensors[i].current_temp;
137 if (t == DS_TEMP_UNKNOWN)
138 debug_puts("---.---");
140 debug_printf("%3d.%03d", t / 1000, t % 1000);
142 debug_printf(" %d", bypass_active);
143 debug_printf(" %d", fan_pwm);
147 static void pwm_init(void)
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);
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);
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);
167 timer_enable_counter(TIM4);
178 debug_puts("Hello, world!\n");
189 if (usart_get_flag(USART1, USART_SR_RXNE)) {
190 uint ch = usart_recv(USART1);
193 gpio_set(GPIOC, GPIO15); // opto-coupler
194 gpio_clear(GPIOB, GPIO0); // LED
195 } else if (ch == 'b') {
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;
214 * % = pwm*4.389 - 12.723
216 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
219 if (cycles++ >= 50) {
228 /*** Modbus callbacks ***/
231 AIRCON_COIL_EXCHANGER_BYPASS,
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,
242 AIRCON_IREG_DS_ID_BASE = 0x1000,
243 AIRCON_IREG_DS_ID_MAX = AIRCON_IREG_DS_ID_BASE + 4*DS_NUM_SENSORS,
246 enum aircon_holding_registers {
247 AIRCON_HREG_EXCHANGER_FAN,
248 AIRCON_HREG_REMOTE_CONTROL,
252 bool modbus_check_discrete_input(u16 addr UNUSED)
257 bool modbus_get_discrete_input(u16 addr UNUSED)
262 bool modbus_check_coil(u16 addr)
264 return addr < AIRCON_COIL_MAX;
267 bool modbus_get_coil(u16 addr)
270 case AIRCON_COIL_EXCHANGER_BYPASS:
271 return bypass_active;
277 void modbus_set_coil(u16 addr, bool value)
280 case AIRCON_COIL_EXCHANGER_BYPASS:
281 bypass_active = value;
283 gpio_set(GPIOC, GPIO15); // opto-coupler
284 gpio_clear(GPIOB, GPIO0); // LED
286 gpio_clear(GPIOC, GPIO15); // opto-coupler
287 gpio_set(GPIOB, GPIO0); // LED
295 bool modbus_check_input_register(u16 addr)
297 return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_MAX && addr < AIRCON_IREG_DS_ID_MAX);
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 },
308 u16 modbus_get_input_register(u16 addr)
310 if (addr <= AIRCON_IREG_TEMP_MIXED) {
312 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address, temp_sensor_addrs[addr], 8))
314 if (i >= DS_NUM_SENSORS)
316 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
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);
328 bool modbus_check_holding_register(u16 addr)
330 return addr < AIRCON_HREG_MAX;
333 u16 modbus_get_holding_register(u16 addr)
336 case AIRCON_HREG_EXCHANGER_FAN:
338 case AIRCON_HREG_REMOTE_CONTROL:
344 void modbus_set_holding_register(u16 addr, u16 value)
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);
351 case AIRCON_HREG_REMOTE_CONTROL:
358 void modbus_ready_hook(void)
361 gpio_set(GPIOB, GPIO1);
364 void modbus_frame_start_hook(void)
367 gpio_clear(GPIOB, GPIO1);
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,