+++ /dev/null
-ROOT=..
-BINARY=main
-OBJS=main.o
-LIB_OBJS=util-debug.o ds18b20.o modbus.o
-
-include $(ROOT)/mk/bluepill.mk
+++ /dev/null
-/*
- * Air Conditioning Controller -- Configuration
- *
- * (c) 2019 Martin Mareš <mj@ucw.cz>
- */
-
-// Processor clock
-
-#define CPU_CLOCK_MHZ 72
-
-// Debugging port
-
-#define DEBUG_USART USART1
-#define DEBUG_LED_BLUEPILL
-
-// MODBUS library parameters
-
-#define MODBUS_USART USART3
-#define MODBUS_NVIC_USART_IRQ NVIC_USART3_IRQ
-#define MODBUS_USART_ISR usart3_isr
-
-#define MODBUS_TXEN_GPIO_PORT GPIOA
-#define MODBUS_TXEN_GPIO_PIN GPIO6
-
-#define MODBUS_TIMER TIM2
-#define MODBUS_NVIC_TIMER_IRQ NVIC_TIM2_IRQ
-#define MODBUS_TIMER_ISR tim2_isr
-
-#define MODBUS_OUR_ADDRESS 42
-
-#define MODBUS_BAUD_RATE 19200
-
-#define MODBUS_DEBUG
-
-// DS18B20 library parameters
-
-#define DS_TIMER TIM3
-#define DS_GPIO GPIOA
-#define DS_PIN GPIO7
-#define DS_DMA DMA1
-#define DS_DMA_CH 6
-#define DS_NUM_SENSORS 8
-
-#undef DS_DEBUG
-#undef DS_DEBUG2
--- /dev/null
+ROOT=..
+BINARY=main
+OBJS=main.o
+LIB_OBJS=util-debug.o ds18b20.o modbus.o
+
+include $(ROOT)/mk/bluepill.mk
--- /dev/null
+/*
+ * Air Conditioning Controller -- Configuration
+ *
+ * (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
+// Processor clock
+
+#define CPU_CLOCK_MHZ 72
+
+// Debugging port
+
+#define DEBUG_USART USART1
+#define DEBUG_LED_BLUEPILL
+
+// MODBUS library parameters
+
+#define MODBUS_USART USART3
+#define MODBUS_NVIC_USART_IRQ NVIC_USART3_IRQ
+#define MODBUS_USART_ISR usart3_isr
+
+#define MODBUS_TXEN_GPIO_PORT GPIOA
+#define MODBUS_TXEN_GPIO_PIN GPIO6
+
+#define MODBUS_TIMER TIM2
+#define MODBUS_NVIC_TIMER_IRQ NVIC_TIM2_IRQ
+#define MODBUS_TIMER_ISR tim2_isr
+
+#define MODBUS_OUR_ADDRESS 42
+
+#define MODBUS_BAUD_RATE 19200
+
+#define MODBUS_DEBUG
+
+// DS18B20 library parameters
+
+#define DS_TIMER TIM3
+#define DS_GPIO GPIOA
+#define DS_PIN GPIO7
+#define DS_DMA DMA1
+#define DS_DMA_CH 6
+#define DS_NUM_SENSORS 8
+
+#undef DS_DEBUG
+#undef DS_DEBUG2
--- /dev/null
+set port /dev/ttyUSB0
+set speed 115200
+set flow-control none
+set carrier-watch off
+connect
--- /dev/null
+/*
+ * Air Conditioning Controller
+ *
+ * (c) 2019 Martin Mareš <mj@ucw.cz>
+ */
+
+#include "util.h"
+#include "ds18b20.h"
+#include "modbus.h"
+
+#include <libopencm3/cm3/nvic.h>
+#include <libopencm3/cm3/systick.h>
+#include <libopencm3/stm32/rcc.h>
+#include <libopencm3/stm32/gpio.h>
+#include <libopencm3/stm32/timer.h>
+#include <libopencm3/stm32/usart.h>
+
+#include <string.h>
+
+static void clock_setup(void)
+{
+ rcc_clock_setup_in_hse_8mhz_out_72mhz();
+
+ rcc_periph_clock_enable(RCC_GPIOA);
+ rcc_periph_clock_enable(RCC_GPIOB);
+ rcc_periph_clock_enable(RCC_GPIOC);
+ rcc_periph_clock_enable(RCC_USART1);
+ rcc_periph_clock_enable(RCC_USART3);
+ rcc_periph_clock_enable(RCC_TIM1);
+ rcc_periph_clock_enable(RCC_TIM2);
+ rcc_periph_clock_enable(RCC_TIM3);
+ rcc_periph_clock_enable(RCC_TIM4);
+ rcc_periph_clock_enable(RCC_DMA1);
+
+ rcc_periph_reset_pulse(RST_GPIOA);
+ rcc_periph_reset_pulse(RST_GPIOB);
+ rcc_periph_reset_pulse(RST_GPIOC);
+ rcc_periph_reset_pulse(RST_USART1);
+ rcc_periph_reset_pulse(RST_USART3);
+ rcc_periph_reset_pulse(RST_TIM1);
+ rcc_periph_reset_pulse(RST_TIM2);
+ rcc_periph_reset_pulse(RST_TIM3);
+ rcc_periph_reset_pulse(RST_TIM4);
+}
+
+static void gpio_setup(void)
+{
+ // Switch JTAG off to free up pins
+ gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
+
+ // PA6 = RS485 TX enable
+ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6);
+ gpio_clear(GPIOA, GPIO6);
+
+ // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
+
+ // PA9 = TXD1 for debugging console
+ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
+
+ // PA10 = RXD1 for debugging console
+ gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
+
+ // PB0 = bypass LED*
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
+ gpio_set(GPIOB, GPIO0);
+
+ // PB1 = MODBUS frame LED*
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO1);
+ gpio_set(GPIOB, GPIO1);
+
+ // PB6 = TIM4_CH1 fan control opto-coupler
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
+
+ // PB7 = TIM4_CH2 IR LED
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
+
+ // PB10 = TXD3 for RS485
+ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
+
+ // PB11 = RXD3 for RS485
+ gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
+
+ // PC13 = BluePill LED
+ gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
+ gpio_clear(GPIOC, GPIO13);
+
+ // PC15 = bypass opto-coupler
+ gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
+ gpio_clear(GPIOC, GPIO15);
+}
+
+static volatile u32 ms_ticks;
+
+void sys_tick_handler(void)
+{
+ ms_ticks++;
+}
+
+static void tick_setup(void)
+{
+ systick_set_frequency(1000, 72000000);
+ systick_counter_enable();
+ systick_interrupt_enable();
+}
+
+static void delay_ms(uint ms)
+{
+ u32 start_ticks = ms_ticks;
+ while (ms_ticks - start_ticks < ms)
+ ;
+}
+
+static void usart_setup(void)
+{
+ usart_set_baudrate(USART1, 115200);
+ usart_set_databits(USART1, 8);
+ usart_set_stopbits(USART1, USART_STOPBITS_1);
+ usart_set_mode(USART1, USART_MODE_TX_RX);
+ usart_set_parity(USART1, USART_PARITY_NONE);
+ usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
+
+ usart_enable(USART1);
+}
+
+// TIM4 will run on CPU clock, it will overflow with frequency 38 kHz (IR modulation frequency)
+#define T4_CYCLE ((CPU_CLOCK_MHZ * 1000000 + 37999) / 38000)
+
+static byte bypass_active;
+static byte fan_pwm;
+
+static void show_temperature(void)
+{
+ debug_putc('#');
+ for (uint i=0; ds_sensors[i].address[0]; i++) {
+ debug_putc(' ');
+ int t = ds_sensors[i].current_temp;
+ if (t == DS_TEMP_UNKNOWN)
+ debug_puts("---.---");
+ else
+ debug_printf("%3d.%03d", t / 1000, t % 1000);
+ }
+ debug_printf(" %d", bypass_active);
+ debug_printf(" %d", fan_pwm);
+ debug_puts("\r\n");
+}
+
+static void pwm_init(void)
+{
+ timer_set_prescaler(TIM4, 0);
+ timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
+ timer_disable_preload(TIM4);
+ timer_set_period(TIM4, T4_CYCLE - 1);
+
+ // 50% PWM for the IR LED
+ timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH); // will be TIM_OCM_PWM1 when transmitting
+ // timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1); // FIXME
+ timer_set_oc_value(TIM4, TIM_OC2, T4_CYCLE / 2);
+ timer_set_oc_polarity_high(TIM4, TIM_OC2);
+ timer_enable_oc_output(TIM4, TIM_OC2);
+
+ // PWM for controlling fan
+ timer_set_oc_mode(TIM4, TIM_OC1, TIM_OCM_PWM1);
+ timer_set_oc_value(TIM4, TIM_OC1, 0);
+ timer_set_oc_polarity_high(TIM4, TIM_OC1);
+ timer_enable_oc_output(TIM4, TIM_OC1);
+
+ timer_enable_counter(TIM4);
+}
+
+int main(void)
+{
+ clock_setup();
+ gpio_setup();
+ tick_setup();
+ usart_setup();
+ pwm_init();
+
+ debug_puts("Hello, world!\n");
+
+ ds_init();
+ modbus_init();
+
+ byte cycles = 0;
+ for (;;) {
+ debug_led_toggle();
+ delay_ms(100);
+ ds_step();
+ modbus_loop();
+ if (usart_get_flag(USART1, USART_SR_RXNE)) {
+ uint ch = usart_recv(USART1);
+ if (ch == 'B') {
+ bypass_active = 1;
+ gpio_set(GPIOC, GPIO15); // opto-coupler
+ gpio_clear(GPIOB, GPIO0); // LED
+ } else if (ch == 'b') {
+ bypass_active = 0;
+ gpio_clear(GPIOC, GPIO15); // opto-coupler
+ gpio_set(GPIOB, GPIO0); // LED
+ } else if (ch >= '0' && ch <= '9') {
+ fan_pwm = 3*(ch - '0') + 1;
+ /*
+ * ch pwm %
+ * 0 1 0
+ * 1 4 0
+ * 2 7 18
+ * 3 10 31
+ * 4 13 44
+ * 5 16 57
+ * 6 19 71
+ * 7 22 84
+ * 8 25 97
+ * 9 28 100
+ *
+ * % = pwm*4.389 - 12.723
+ */
+ timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
+ }
+ }
+ if (cycles++ >= 50) {
+ cycles = 0;
+ show_temperature();
+ }
+ }
+
+ return 0;
+}
+
+/*** Modbus callbacks ***/
+
+enum aircon_coils {
+ AIRCON_COIL_EXCHANGER_BYPASS,
+ AIRCON_COIL_MAX,
+};
+
+enum aircon_input_registers {
+ AIRCON_IREG_TEMP_FROM_INSIDE,
+ AIRCON_IREG_TEMP_TO_INSIDE,
+ AIRCON_IREG_TEMP_FROM_OUTSIDE,
+ AIRCON_IREG_TEMP_TO_OUTSIDE,
+ AIRCON_IREG_TEMP_MIXED,
+ AIRCON_IREG_MAX,
+ AIRCON_IREG_DS_ID_BASE = 0x1000,
+ AIRCON_IREG_DS_ID_MAX = AIRCON_IREG_DS_ID_BASE + 4*DS_NUM_SENSORS,
+};
+
+enum aircon_holding_registers {
+ AIRCON_HREG_EXCHANGER_FAN,
+ AIRCON_HREG_REMOTE_CONTROL,
+ AIRCON_HREG_MAX,
+};
+
+bool modbus_check_discrete_input(u16 addr UNUSED)
+{
+ return false;
+}
+
+bool modbus_get_discrete_input(u16 addr UNUSED)
+{
+ return false;
+}
+
+bool modbus_check_coil(u16 addr)
+{
+ return addr < AIRCON_COIL_MAX;
+}
+
+bool modbus_get_coil(u16 addr)
+{
+ switch (addr) {
+ case AIRCON_COIL_EXCHANGER_BYPASS:
+ return bypass_active;
+ default:
+ return false;
+ }
+}
+
+void modbus_set_coil(u16 addr, bool value)
+{
+ switch (addr) {
+ case AIRCON_COIL_EXCHANGER_BYPASS:
+ bypass_active = value;
+ if (bypass_active) {
+ gpio_set(GPIOC, GPIO15); // opto-coupler
+ gpio_clear(GPIOB, GPIO0); // LED
+ } else {
+ gpio_clear(GPIOC, GPIO15); // opto-coupler
+ gpio_set(GPIOB, GPIO0); // LED
+ }
+ break;
+ default:
+ ;
+ }
+}
+
+bool modbus_check_input_register(u16 addr)
+{
+ return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_MAX && addr < AIRCON_IREG_DS_ID_MAX);
+}
+
+static const byte temp_sensor_addrs[][8] = {
+ { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
+ { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
+ { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
+ { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
+ { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
+};
+
+u16 modbus_get_input_register(u16 addr)
+{
+ if (addr <= AIRCON_IREG_TEMP_MIXED) {
+ byte i = 0;
+ while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address, temp_sensor_addrs[addr], 8))
+ i++;
+ if (i >= DS_NUM_SENSORS)
+ return 0x8000;
+ if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
+ return 0x8000;
+ return ds_sensors[i].current_temp & 0xffff;
+ } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
+ byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 4;
+ byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 4;
+ return get_u16_be(ds_sensors[i].address + 2*j);
+ } else {
+ return 0;
+ }
+}
+
+bool modbus_check_holding_register(u16 addr)
+{
+ return addr < AIRCON_HREG_MAX;
+}
+
+u16 modbus_get_holding_register(u16 addr)
+{
+ switch (addr) {
+ case AIRCON_HREG_EXCHANGER_FAN:
+ return fan_pwm;
+ case AIRCON_HREG_REMOTE_CONTROL:
+ default:
+ return 0;
+ }
+}
+
+void modbus_set_holding_register(u16 addr, u16 value)
+{
+ switch (addr) {
+ case AIRCON_HREG_EXCHANGER_FAN:
+ fan_pwm = MIN(value, 255);
+ timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
+ break;
+ case AIRCON_HREG_REMOTE_CONTROL:
+ break;
+ default:
+ ;
+ }
+}
+
+void modbus_ready_hook(void)
+{
+ // Frame LED off
+ gpio_set(GPIOB, GPIO1);
+}
+
+void modbus_frame_start_hook(void)
+{
+ // Frame LED on
+ gpio_clear(GPIOB, GPIO1);
+}
+
+const char * const modbus_id_strings[MODBUS_ID_MAX] = {
+ [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
+ [MODBUS_ID_PRODUCT_CODE] = "42",
+ [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
+ [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
+ [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
+ [MODBUS_ID_USER_APP_NAME] = NULL,
+};
+++ /dev/null
-set port /dev/ttyUSB0
-set speed 115200
-set flow-control none
-set carrier-watch off
-connect
+++ /dev/null
-/*
- * Air Conditioning Controller
- *
- * (c) 2019 Martin Mareš <mj@ucw.cz>
- */
-
-#include "util.h"
-#include "ds18b20.h"
-#include "modbus.h"
-
-#include <libopencm3/cm3/nvic.h>
-#include <libopencm3/cm3/systick.h>
-#include <libopencm3/stm32/rcc.h>
-#include <libopencm3/stm32/gpio.h>
-#include <libopencm3/stm32/timer.h>
-#include <libopencm3/stm32/usart.h>
-
-#include <string.h>
-
-static void clock_setup(void)
-{
- rcc_clock_setup_in_hse_8mhz_out_72mhz();
-
- rcc_periph_clock_enable(RCC_GPIOA);
- rcc_periph_clock_enable(RCC_GPIOB);
- rcc_periph_clock_enable(RCC_GPIOC);
- rcc_periph_clock_enable(RCC_USART1);
- rcc_periph_clock_enable(RCC_USART3);
- rcc_periph_clock_enable(RCC_TIM1);
- rcc_periph_clock_enable(RCC_TIM2);
- rcc_periph_clock_enable(RCC_TIM3);
- rcc_periph_clock_enable(RCC_TIM4);
- rcc_periph_clock_enable(RCC_DMA1);
-
- rcc_periph_reset_pulse(RST_GPIOA);
- rcc_periph_reset_pulse(RST_GPIOB);
- rcc_periph_reset_pulse(RST_GPIOC);
- rcc_periph_reset_pulse(RST_USART1);
- rcc_periph_reset_pulse(RST_USART3);
- rcc_periph_reset_pulse(RST_TIM1);
- rcc_periph_reset_pulse(RST_TIM2);
- rcc_periph_reset_pulse(RST_TIM3);
- rcc_periph_reset_pulse(RST_TIM4);
-}
-
-static void gpio_setup(void)
-{
- // Switch JTAG off to free up pins
- gpio_primary_remap(AFIO_MAPR_SWJ_CFG_JTAG_OFF_SW_ON, 0);
-
- // PA6 = RS485 TX enable
- gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6);
- gpio_clear(GPIOA, GPIO6);
-
- // PA7 = TIM3_CH2 for DS18B20 bus -- configured by ds18b20 library
-
- // PA9 = TXD1 for debugging console
- gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9);
-
- // PA10 = RXD1 for debugging console
- gpio_set_mode(GPIOA, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10);
-
- // PB0 = bypass LED*
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO0);
- gpio_set(GPIOB, GPIO0);
-
- // PB1 = MODBUS frame LED*
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO1);
- gpio_set(GPIOB, GPIO1);
-
- // PB6 = TIM4_CH1 fan control opto-coupler
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO6);
-
- // PB7 = TIM4_CH2 IR LED
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO7);
-
- // PB10 = TXD3 for RS485
- gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10);
-
- // PB11 = RXD3 for RS485
- gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11);
-
- // PC13 = BluePill LED
- gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
- gpio_clear(GPIOC, GPIO13);
-
- // PC15 = bypass opto-coupler
- gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO15);
- gpio_clear(GPIOC, GPIO15);
-}
-
-static volatile u32 ms_ticks;
-
-void sys_tick_handler(void)
-{
- ms_ticks++;
-}
-
-static void tick_setup(void)
-{
- systick_set_frequency(1000, 72000000);
- systick_counter_enable();
- systick_interrupt_enable();
-}
-
-static void delay_ms(uint ms)
-{
- u32 start_ticks = ms_ticks;
- while (ms_ticks - start_ticks < ms)
- ;
-}
-
-static void usart_setup(void)
-{
- usart_set_baudrate(USART1, 115200);
- usart_set_databits(USART1, 8);
- usart_set_stopbits(USART1, USART_STOPBITS_1);
- usart_set_mode(USART1, USART_MODE_TX_RX);
- usart_set_parity(USART1, USART_PARITY_NONE);
- usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
-
- usart_enable(USART1);
-}
-
-// TIM4 will run on CPU clock, it will overflow with frequency 38 kHz (IR modulation frequency)
-#define T4_CYCLE ((CPU_CLOCK_MHZ * 1000000 + 37999) / 38000)
-
-static byte bypass_active;
-static byte fan_pwm;
-
-static void show_temperature(void)
-{
- debug_putc('#');
- for (uint i=0; ds_sensors[i].address[0]; i++) {
- debug_putc(' ');
- int t = ds_sensors[i].current_temp;
- if (t == DS_TEMP_UNKNOWN)
- debug_puts("---.---");
- else
- debug_printf("%3d.%03d", t / 1000, t % 1000);
- }
- debug_printf(" %d", bypass_active);
- debug_printf(" %d", fan_pwm);
- debug_puts("\r\n");
-}
-
-static void pwm_init(void)
-{
- timer_set_prescaler(TIM4, 0);
- timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
- timer_disable_preload(TIM4);
- timer_set_period(TIM4, T4_CYCLE - 1);
-
- // 50% PWM for the IR LED
- timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_FORCE_HIGH); // will be TIM_OCM_PWM1 when transmitting
- // timer_set_oc_mode(TIM4, TIM_OC2, TIM_OCM_PWM1); // FIXME
- timer_set_oc_value(TIM4, TIM_OC2, T4_CYCLE / 2);
- timer_set_oc_polarity_high(TIM4, TIM_OC2);
- timer_enable_oc_output(TIM4, TIM_OC2);
-
- // PWM for controlling fan
- timer_set_oc_mode(TIM4, TIM_OC1, TIM_OCM_PWM1);
- timer_set_oc_value(TIM4, TIM_OC1, 0);
- timer_set_oc_polarity_high(TIM4, TIM_OC1);
- timer_enable_oc_output(TIM4, TIM_OC1);
-
- timer_enable_counter(TIM4);
-}
-
-int main(void)
-{
- clock_setup();
- gpio_setup();
- tick_setup();
- usart_setup();
- pwm_init();
-
- debug_puts("Hello, world!\n");
-
- ds_init();
- modbus_init();
-
- byte cycles = 0;
- for (;;) {
- debug_led_toggle();
- delay_ms(100);
- ds_step();
- modbus_loop();
- if (usart_get_flag(USART1, USART_SR_RXNE)) {
- uint ch = usart_recv(USART1);
- if (ch == 'B') {
- bypass_active = 1;
- gpio_set(GPIOC, GPIO15); // opto-coupler
- gpio_clear(GPIOB, GPIO0); // LED
- } else if (ch == 'b') {
- bypass_active = 0;
- gpio_clear(GPIOC, GPIO15); // opto-coupler
- gpio_set(GPIOB, GPIO0); // LED
- } else if (ch >= '0' && ch <= '9') {
- fan_pwm = 3*(ch - '0') + 1;
- /*
- * ch pwm %
- * 0 1 0
- * 1 4 0
- * 2 7 18
- * 3 10 31
- * 4 13 44
- * 5 16 57
- * 6 19 71
- * 7 22 84
- * 8 25 97
- * 9 28 100
- *
- * % = pwm*4.389 - 12.723
- */
- timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
- }
- }
- if (cycles++ >= 50) {
- cycles = 0;
- show_temperature();
- }
- }
-
- return 0;
-}
-
-/*** Modbus callbacks ***/
-
-enum aircon_coils {
- AIRCON_COIL_EXCHANGER_BYPASS,
- AIRCON_COIL_MAX,
-};
-
-enum aircon_input_registers {
- AIRCON_IREG_TEMP_FROM_INSIDE,
- AIRCON_IREG_TEMP_TO_INSIDE,
- AIRCON_IREG_TEMP_FROM_OUTSIDE,
- AIRCON_IREG_TEMP_TO_OUTSIDE,
- AIRCON_IREG_TEMP_MIXED,
- AIRCON_IREG_MAX,
- AIRCON_IREG_DS_ID_BASE = 0x1000,
- AIRCON_IREG_DS_ID_MAX = AIRCON_IREG_DS_ID_BASE + 4*DS_NUM_SENSORS,
-};
-
-enum aircon_holding_registers {
- AIRCON_HREG_EXCHANGER_FAN,
- AIRCON_HREG_REMOTE_CONTROL,
- AIRCON_HREG_MAX,
-};
-
-bool modbus_check_discrete_input(u16 addr UNUSED)
-{
- return false;
-}
-
-bool modbus_get_discrete_input(u16 addr UNUSED)
-{
- return false;
-}
-
-bool modbus_check_coil(u16 addr)
-{
- return addr < AIRCON_COIL_MAX;
-}
-
-bool modbus_get_coil(u16 addr)
-{
- switch (addr) {
- case AIRCON_COIL_EXCHANGER_BYPASS:
- return bypass_active;
- default:
- return false;
- }
-}
-
-void modbus_set_coil(u16 addr, bool value)
-{
- switch (addr) {
- case AIRCON_COIL_EXCHANGER_BYPASS:
- bypass_active = value;
- if (bypass_active) {
- gpio_set(GPIOC, GPIO15); // opto-coupler
- gpio_clear(GPIOB, GPIO0); // LED
- } else {
- gpio_clear(GPIOC, GPIO15); // opto-coupler
- gpio_set(GPIOB, GPIO0); // LED
- }
- break;
- default:
- ;
- }
-}
-
-bool modbus_check_input_register(u16 addr)
-{
- return (addr < AIRCON_IREG_MAX || addr >= AIRCON_IREG_DS_ID_MAX && addr < AIRCON_IREG_DS_ID_MAX);
-}
-
-static const byte temp_sensor_addrs[][8] = {
- { 0xAA, 0x36, 0x69, 0x19, 0x13, 0x02 },
- { 0x4A, 0x1B, 0x79, 0x97, 0x01, 0x03 },
- { 0xAA, 0xAC, 0xD9, 0x18, 0x13, 0x02 },
- { 0x30, 0x66, 0x79, 0x97, 0x08, 0x03 },
- { 0xAA, 0x02, 0x64, 0x19, 0x13, 0x02 },
-};
-
-u16 modbus_get_input_register(u16 addr)
-{
- if (addr <= AIRCON_IREG_TEMP_MIXED) {
- byte i = 0;
- while (i < DS_NUM_SENSORS && memcmp(ds_sensors[i].address, temp_sensor_addrs[addr], 8))
- i++;
- if (i >= DS_NUM_SENSORS)
- return 0x8000;
- if (ds_sensors[i].current_temp == DS_TEMP_UNKNOWN)
- return 0x8000;
- return ds_sensors[i].current_temp & 0xffff;
- } else if (addr >= AIRCON_IREG_DS_ID_BASE && addr < AIRCON_IREG_DS_ID_MAX) {
- byte i = (addr - AIRCON_IREG_DS_ID_BASE) / 4;
- byte j = (addr - AIRCON_IREG_DS_ID_BASE) % 4;
- return get_u16_be(ds_sensors[i].address + 2*j);
- } else {
- return 0;
- }
-}
-
-bool modbus_check_holding_register(u16 addr)
-{
- return addr < AIRCON_HREG_MAX;
-}
-
-u16 modbus_get_holding_register(u16 addr)
-{
- switch (addr) {
- case AIRCON_HREG_EXCHANGER_FAN:
- return fan_pwm;
- case AIRCON_HREG_REMOTE_CONTROL:
- default:
- return 0;
- }
-}
-
-void modbus_set_holding_register(u16 addr, u16 value)
-{
- switch (addr) {
- case AIRCON_HREG_EXCHANGER_FAN:
- fan_pwm = MIN(value, 255);
- timer_set_oc_value(TIM4, TIM_OC1, T4_CYCLE * fan_pwm / 256);
- break;
- case AIRCON_HREG_REMOTE_CONTROL:
- break;
- default:
- ;
- }
-}
-
-void modbus_ready_hook(void)
-{
- // Frame LED off
- gpio_set(GPIOB, GPIO1);
-}
-
-void modbus_frame_start_hook(void)
-{
- // Frame LED on
- gpio_clear(GPIOB, GPIO1);
-}
-
-const char * const modbus_id_strings[MODBUS_ID_MAX] = {
- [MODBUS_ID_VENDOR_NAME] = "United Computer Wizards",
- [MODBUS_ID_PRODUCT_CODE] = "42",
- [MODBUS_ID_MAJOR_MINOR_REVISION] = "1.0",
- [MODBUS_ID_VENDOR_URL] = "http://www.ucw.cz/",
- [MODBUS_ID_PRODUCT_NAME] = "Magic Gadget",
- [MODBUS_ID_USER_APP_NAME] = NULL,
-};