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 clock_setup(void)
23 rcc_clock_setup_in_hse_8mhz_out_72mhz();
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);
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);
47 static void gpio_setup(void)
49 // Switch JTAG off to free up pins
50 gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
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);
56 // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
58 // PA9 = TXD1 for debugging console
59 gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
61 // PA10 = RXD1 for debugging console
62 gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
65 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
66 gpio_set(GPIOB, GPIO0);
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);
72 // PB6 = TIM4_CH1 fan control opto-coupler
73 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
75 // PB7 = TIM4_CH2 IR LED
76 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
78 // PB10 = TXD3 for RS485
79 gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
81 // PB11 = RXD3 for RS485
82 gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
84 // PC13 = BluePill LED
85 gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
86 gpio_clear(GPIOC, GPIO13);
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);
93 static volatile u32 ms_ticks;
95 void sys_tick_handler(void)
100 static void tick_setup(void)
102 systick_set_frequency(1000, 72000000);
103 systick_counter_enable();
104 systick_interrupt_enable();
107 static void delay_ms(uint ms)
109 u32 start_ticks = ms_ticks;
110 while (ms_ticks - start_ticks < ms)
114 static void usart_setup(void)
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);
123 usart_enable(USART1);
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)
129 static byte bypass_active;
132 static void show_temperature(void)
135 for (uint i=0; ds_sensors[i].address[0]; i++) {
137 int t = ds_sensors[i].current_temp;
138 if (t == DS_TEMP_UNKNOWN)
139 debug_puts("---.---");
141 debug_printf("%3d.%03d", t / 1000, t % 1000);
143 debug_printf(" %d", bypass_active);
144 debug_printf(" %d", fan_pwm);
148 static void pwm_init(void)
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);
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);
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);
168 timer_enable_counter(TIM4);
179 debug_puts("Hello, world!\n");
190 if (usart_get_flag(USART1, USART_SR_RXNE)) {
191 uint ch = usart_recv(USART1);
194 gpio_set(GPIOC, GPIO15); // opto-coupler
195 gpio_clear(GPIOB, GPIO0); // LED
196 } else if (ch == 'b') {
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;
215 * % = pwm*4.389 - 12.723
217 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
220 if (cycles++ >= 50) {
229 /*** Modbus callbacks ***/
231 bool modbus_check_discrete_input(u16 addr UNUSED)
236 bool modbus_get_discrete_input(u16 addr UNUSED)
241 bool modbus_check_coil(u16 addr)
243 return addr < AIRCON_COIL_MAX;
246 bool modbus_get_coil(u16 addr)
249 case AIRCON_COIL_EXCHANGER_BYPASS:
250 return bypass_active;
256 void modbus_set_coil(u16 addr, bool value)
259 case AIRCON_COIL_EXCHANGER_BYPASS:
260 bypass_active = value;
262 gpio_set(GPIOC, GPIO15); // opto-coupler
263 gpio_clear(GPIOB, GPIO0); // LED
265 gpio_clear(GPIOC, GPIO15); // opto-coupler
266 gpio_set(GPIOB, GPIO0); // LED
274 #define AIRCON_IREG_DS_ID_MAX (AIRCON_IREG_DS_ID_BASE + 3*DS_NUM_SENSORS)
276 bool modbus_check_input_register(u16 addr)
278 return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_BASE);
281 static const byte temp_sensor_addrs[][8] = {
282 { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
283 { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
284 { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
285 { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
286 { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
289 u16 modbus_get_input_register(u16 addr)
291 if (addr <= AIRCON_IREG_TEMP_MIXED) {
293 while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address + 1, temp_sensor_addrs[addr], 6))
295 if (i >= DS_NUM_SENSORS)
297 if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
299 return ((ds_sensors[i].current_temp + 5) / 10) & 0xffff;
300 } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
301 byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 3;
302 byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 3;
303 return get_u16_be(ds_sensors[i].address + 2*j + 1);
309 bool modbus_check_holding_register(u16 addr)
311 return addr < AIRCON_HREG_MAX;
314 u16 modbus_get_holding_register(u16 addr)
317 case AIRCON_HREG_EXCHANGER_FAN:
319 case AIRCON_HREG_REMOTE_CONTROL:
325 void modbus_set_holding_register(u16 addr, u16 value)
328 case AIRCON_HREG_EXCHANGER_FAN:
329 fan_pwm = MIN(value, 255);
330 timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
332 case AIRCON_HREG_REMOTE_CONTROL:
339 void modbus_ready_hook(void)
342 gpio_set(GPIOB, GPIO1);
345 void modbus_frame_start_hook(void)
348 gpio_clear(GPIOB, GPIO1);
351 const char * const modbus_id_strings[MODBUS_ID_MAX] = {
352 [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
353 [MODBUS_ID_PRODUCT_CODE] = "42",
354 [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
355 [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
356 [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
357 [MODBUS_ID_USER_APP_NAME] = NULL,