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);
23 static void clock_init(void)
25 rcc_clock_setup_in_hse_8mhz_out_72mhz();
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);
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);
49 static void gpio_init(void)
51 // Switch JTAG off to free up pins
52 gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
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);
58 // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
60 // PA9 = TXD1 for debugging console
61 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
63 // PA10 = RXD1 for debugging console
64 gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
67 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
68 gpio_set(GPIOB, GPIO0);
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);
74 // PB6 = TIM4_CH1 fan control opto-coupler
75 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
77 // PB7 = TIM4_CH2 IR LED
78 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
80 // PB10 = TXD3 for RS485
81 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
83 // PB11 = RXD3 for RS485
84 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
86 // PC13 = BluePill LED
87 gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
88 gpio_clear(GPIOC, GPIO13);
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);
95 static volatile u32 ms_ticks;
97 void sys_tick_handler(void)
102 static void tick_init(void)
104 systick_set_frequency(1000, 72000000);
105 systick_counter_enable();
106 systick_interrupt_enable();
109 static void delay_ms(uint ms)
111 u32 start_ticks = ms_ticks;
112 while (ms_ticks - start_ticks < ms)
116 static void usart_init(void)
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);
125 usart_enable(USART1);
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)
131 static byte bypass_active;
135 static void show_temperature(void)
138 for (uint i=0; ds_sensors[i].address[0]; i++) {
140 int t = ds_sensors[i].current_temp;
141 if (t == DS_TEMP_UNKNOWN)
142 debug_puts("---.---");
144 debug_printf("%3d.%03d", t / 1000, t % 1000);
146 debug_printf(" %d", bypass_active);
147 debug_printf(" %d", fan_pwm);
151 static void pwm_init(void)
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);
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);
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);
171 timer_enable_counter(TIM4);
183 debug_puts("Hello, world!\n");
194 if (usart_get_flag(USART1, USART_SR_RXNE)) {
195 uint ch = usart_recv(USART1);
198 gpio_set(GPIOC, GPIO15); // opto-coupler
199 gpio_clear(GPIOB, GPIO0); // LED
200 } else if (ch == 'b') {
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;
219 * % = pwm*4.389 - 12.723
221 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
224 if (cycles++ >= 50) {
233 /*** Infra-red remote control transmitter ***/
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*$",
262 static const char rc_keys[] = "aufhtmdslp";
264 static void rc_init(void)
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);
276 static volatile const char *rc_pattern_pos;
277 static volatile char rc_pending;
279 void tim1_up_isr(void)
281 if (TIM_SR(TIM1) & TIM_SR_UIF) {
282 TIM_SR(TIM1) &= ~TIM_SR_UIF;
284 if (!rc_pattern_pos) // Just to be sure
287 bool val; // 1=pulse, 0=break
288 uint duration; // in μs
290 switch (*rc_pattern_pos++) {
316 // End of transmission
317 gpio_set(GPIOC, GPIO13);
318 rc_pattern_pos = NULL;
324 timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1);
326 timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH);
328 timer_set_period(TIM1, duration - 1);
329 timer_generate_event(TIM1, TIM_EGR_UG);
330 timer_enable_counter(TIM1);
334 static bool rc_send(char key)
339 const char *s = strchr(rc_keys, key);
343 rc_pattern_pos = rc_patterns[s - rc_keys];
345 gpio_clear(GPIOC, GPIO13);
347 timer_set_period(TIM1, 1);
348 timer_generate_event(TIM1, TIM_EGR_UG);
349 timer_enable_counter(TIM1);
353 /*** Modbus callbacks ***/
355 bool modbus_check_discrete_input(u16 addr UNUSED)
360 bool modbus_get_discrete_input(u16 addr UNUSED)
365 bool modbus_check_coil(u16 addr)
367 return addr < AIRCON_COIL_MAX;
370 bool modbus_get_coil(u16 addr)
373 case AIRCON_COIL_EXCHANGER_BYPASS:
374 return bypass_active;
380 void modbus_set_coil(u16 addr, bool value)
383 case AIRCON_COIL_EXCHANGER_BYPASS:
384 bypass_active = value;
386 gpio_set(GPIOC, GPIO15); // opto-coupler
387 gpio_clear(GPIOB, GPIO0); // LED
389 gpio_clear(GPIOC, GPIO15); // opto-coupler
390 gpio_set(GPIOB, GPIO0); // LED
398 #define AIRCON_IREG_DS_ID_MAX (AIRCON_IREG_DS_ID_BASE + 3*DS_NUM_SENSORS)
400 bool modbus_check_input_register(u16 addr)
402 return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_BASE);
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 },
413 u16 modbus_get_input_register(u16 addr)
415 if (addr <= AIRCON_IREG_TEMP_MIXED) {
417 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address + 1, temp_sensor_addrs[addr], 6))
419 if (i >= DS_NUM_SENSORS)
421 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
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);
433 bool modbus_check_holding_register(u16 addr)
435 return addr < AIRCON_HREG_MAX;
438 u16 modbus_get_holding_register(u16 addr)
441 case AIRCON_HREG_EXCHANGER_FAN:
443 case AIRCON_HREG_REMOTE_CONTROL:
450 void modbus_set_holding_register(u16 addr, u16 value)
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);
457 case AIRCON_HREG_REMOTE_CONTROL:
459 modbus_slave_error();
466 void modbus_ready_hook(void)
469 gpio_set(GPIOB, GPIO1);
472 void modbus_frame_start_hook(void)
475 gpio_clear(GPIOB, GPIO1);
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,