rcc_periph_clock_enable(RCC_USART1);
rcc_periph_clock_enable(RCC_USB);
rcc_periph_clock_enable(RCC_TIM3);
+ rcc_periph_clock_enable(RCC_TIM4);
rcc_periph_reset_pulse(RST_GPIOA);
rcc_periph_reset_pulse(RST_GPIOB);
rcc_periph_reset_pulse(RST_USART1);
rcc_periph_reset_pulse(RST_USB);
rcc_periph_reset_pulse(RST_TIM3);
+ rcc_periph_reset_pulse(RST_TIM4);
}
static void gpio_init(void)
gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
gpio_clear(GPIOC, GPIO13);
- // PB12 = SS2 (but used as GP input)
- // PB13 = SCK2
- // PB15 = MOSI2
- gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO12);
- gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO13);
- gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO15);
+ // PB13 = SCK2 (pulled up)
+ // PB15 = MOSI2 (pulled up)
+ gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO13);
+ gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO15);
+ gpio_set(GPIOB, GPIO13 | GPIO15);
+
+ // PA8 = IR remote control
+ gpio_clear(GPIOA, GPIO8);
+ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8);
}
static void usart_init(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);
+ usart_set_mode(USART1, USART_MODE_TX_RX);
usart_set_parity(USART1, USART_PARITY_NONE);
usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);
nvic_enable_irq(NVIC_TIM3_IRQ);
}
+/*
+ * Data memory of TM1618:
+ *
+ * [0] . . . - - - - -
+ * [1] . . - - HEAT . . .
+ * [2] . . . DRY - SLP MED LOW
+ * [3] . . HIGH AUTO COOL . . .
+ * [4] . . . B2 - G2 D2 C2
+ * [5] . . E2 F2 A2 . . .
+ * [6] . . . B1 - G1 D1 C1
+ * [7] . . E1 F1 A1 . . .
+ *
+ * "." is an always-zero bit not defined by TM1618, "-" is defined, but not used by AC.
+ */
static volatile byte tm_data[8];
-static volatile byte tm_overrun;
+static volatile uint tm_overruns;
static volatile byte tm_buffer[256];
static volatile uint tm_len;
+/*
+ *
+ * Display segments:
+ *
+ * +--A--+
+ * | |
+ * F B
+ * | |
+ * +--G--+
+ * | |
+ * E C
+ * | |
+ * +--D--+
+ */
+
+enum tm_seg {
+ SEGa = 0x0800,
+ SEGb = 0x0010,
+ SEGc = 0x0001,
+ SEGd = 0x0002,
+ SEGe = 0x2000,
+ SEGf = 0x1000,
+ SEGg = 0x0004,
+};
+
+static const u16 tm_digits[10] = {
+ [0] = SEGa | SEGb | SEGc | SEGd | SEGe | SEGf,
+ [1] = SEGb | SEGc,
+ [2] = SEGa | SEGb | SEGd | SEGe | SEGg,
+ [3] = SEGa | SEGb | SEGc | SEGd | SEGg,
+ [4] = SEGb | SEGc | SEGf | SEGg,
+ [5] = SEGa | SEGc | SEGd | SEGf | SEGg,
+ [6] = SEGa | SEGc | SEGd | SEGe | SEGf | SEGg,
+ [7] = SEGa | SEGb | SEGc,
+ [8] = SEGa | SEGb | SEGc | SEGd | SEGe | SEGf | SEGg,
+ [9] = SEGa | SEGb | SEGc | SEGd | SEGf | SEGg,
+};
+
static volatile uint tm_timeouts;
void spi2_isr(void)
{
if (SPI_SR(SPI2) & SPI_SR_OVR)
- tm_overrun = 1;
+ tm_overruns++;
if (SPI_SR(SPI2) & SPI_SR_RXNE) {
byte x = SPI_DR(SPI2) ^ 0xff;
#if 0
debug_printf("TM:");
for (uint i=0; i<8; i++)
debug_printf(" %02x", tm_data[i]);
- debug_printf(" o=%d t=%d", tm_overrun, tm_timeouts);
- tm_overrun = 0;
+ debug_printf(" o=%d t=%d", tm_overruns, tm_timeouts);
+
+ debug_printf(" =>");
+ if (tm_data[1] & 0x08)
+ debug_printf(" HEAT");
+ if (tm_data[2] & 0x10)
+ debug_printf(" DRY");
+ if (tm_data[2] & 0x04)
+ debug_printf(" SLEEP");
+ if (tm_data[2] & 0x02)
+ debug_printf(" MED");
+ if (tm_data[2] & 0x01)
+ debug_printf(" LOW");
+ if (tm_data[3] & 0x20)
+ debug_printf(" HIGH");
+ if (tm_data[3] & 0x10)
+ debug_printf(" AUTO");
+ if (tm_data[3] & 0x08)
+ debug_printf(" COOL");
+
+ debug_putc(' ');
+ for (int i=0; i<2; i++) {
+ uint x = (tm_data[7-2*i] << 8) | tm_data[6-2*i];
+ uint j = 0;
+ while (j < 10 && tm_digits[j] != x)
+ j++;
+ if (j == 10)
+ debug_putc('?');
+ else
+ debug_putc('0' + j);
+ }
+
debug_putc('\n');
#if 0
#endif
}
+/*** Infra-red remote control simulator ***/
+
+/*
+ * The AC unit expects demodulated IR signal. The RC sends 52-bit messages
+ * (plus leader and trailer). The last 4 bits are a complement of checksum
+ * of 4-bit nibbles.
+ *
+ * We represent the messages as two 32-bit words, the upper word containing
+ */
+
+#define RC_POWER_OFF_HI 0b00000000000000000000
+#define RC_POWER_OFF_LO 0b00000000000000010000000010100100
+
+#define RC_DEFAULT_HI 0b00000011000000000000
+
+// Cooling with different fan settings. Combines with a temperature setting (17-30).
+#define RC_COOL_AUTO 0b00000000000000010000000000000000
+#define RC_COOL_HIGH 0b00000000000000010000100000000000
+#define RC_COOL_MED 0b00000000000000010001000100000000
+#define RC_COOL_LOW 0b00000000000000010010001000000000
+
+static const u32 rc_cool_fan[4] = {
+ RC_COOL_AUTO,
+ RC_COOL_LOW,
+ RC_COOL_MED,
+ RC_COOL_HIGH,
+};
+
+// Heating with fixed fan setting. Combines with a temperature setting (15-25).
+#define RC_WARM 0b00000000000000010000001100000000
+
+// Dehumidifying with fixed fan setting. This is always sent with temperature=17.
+#define RC_DEHUMIDIFY 0b00000000000000010010010000000000
+
+// This can be added to any command to enable sleep mode, but we do not issue it yet.
+#define RC_SLEEP 0b00000000000010000000000000000000
+
+enum rc_mode {
+ MODE_OFF,
+ MODE_COOL,
+ MODE_WARM,
+ MODE_DEHUMIDIFY,
+};
+
+static byte rc_mode = MODE_COOL; // MODE_xxx
+static byte rc_fan; // 0-3
+static byte rc_temp = 17; // 15-30
+
+static void rc_init(void)
+{
+ // TIM4 runs at 1 MHz and it is used for timing of RC pulses
+ timer_set_prescaler(TIM4, CPU_CLOCK_MHZ - 1);
+ timer_set_mode(TIM4, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
+ timer_update_on_overflow(TIM4);
+ timer_disable_preload(TIM4);
+ timer_one_shot_mode(TIM4);
+ timer_enable_irq(TIM4, TIM_DIER_UIE);
+ nvic_enable_irq(NVIC_TIM4_IRQ);
+}
+
+static u32 rc_pattern[2];
+static uint rc_tick;
+
+static void rc_encode(void)
+{
+ if (rc_mode == MODE_OFF) {
+ rc_pattern[0] = RC_POWER_OFF_HI;
+ rc_pattern[1] = RC_POWER_OFF_LO;
+ return;
+ }
+
+ rc_pattern[0] = RC_DEFAULT_HI;
+ uint t = rc_temp;
+
+ if (rc_mode == MODE_COOL) {
+ rc_pattern[1] = rc_cool_fan[rc_fan];
+ if (t < 17)
+ t = 17;
+ if (t > 30)
+ t = 30;
+ } else if (rc_mode == MODE_WARM) {
+ rc_pattern[1] = RC_WARM;
+ if (t < 15)
+ t = 15;
+ if (t > 25)
+ t = 25;
+ } else {
+ rc_pattern[1] = RC_DEHUMIDIFY;
+ t = 17;
+ }
+
+ // Encode temperature
+ rc_pattern[1] |= (t - 15) << 4;
+
+ // Compute checksum
+ uint sum = 0;
+ for (uint i=0; i<2; i++)
+ for (uint j=0; j<32; j+=4)
+ sum += (rc_pattern[i] >> j) & 0x0f;
+ rc_pattern[1] |= (sum & 0x0f) ^ 0x0f;
+}
+
+void tim4_isr(void)
+{
+ if (TIM_SR(TIM4) & TIM_SR_UIF) {
+ TIM_SR(TIM4) &= ~TIM_SR_UIF;
+
+ bool val; // 1=pulse, 0=break
+ uint duration; // in μs
+
+ switch (rc_tick) {
+ case 0:
+ // Better be safe
+ return;
+ case 2:
+ case 108:
+ // Initial / final marker
+ val = 0;
+ duration = 3600;
+ // debug_putc('#');
+ break;
+ case 110:
+ // Inter-packet gap
+ val = 0;
+ duration = 10000;
+ // debug_putc('$');
+ break;
+ case 111:
+ // End of message
+ rc_tick = 0;
+ return;
+ default:
+ if (rc_tick % 2) {
+ val = 1;
+ duration = 565;
+ // debug_putc('*');
+ } else {
+ // Even ticks 4 to 106 transmit 52 bits of data
+ uint i = 12 + (rc_tick - 4) / 2;
+ val = 0;
+ if (rc_pattern[i>>5] & (0x80000000 >> (i & 31))) {
+ duration = 1471;
+ // debug_putc('B');
+ } else {
+ duration = 480;
+ // debug_putc('A');
+ }
+ }
+ }
+
+ rc_tick++;
+
+ if (val)
+ gpio_set(GPIOA, GPIO8);
+ else
+ gpio_clear(GPIOA, GPIO8);
+
+ timer_set_period(TIM4, duration - 1);
+ timer_generate_event(TIM4, TIM_EGR_UG);
+ timer_enable_counter(TIM4);
+ }
+}
+
+static void rc_send(void)
+{
+ if (rc_tick)
+ return;
+
+ rc_encode();
+ debug_printf("RC sending: %05x %08x (mode=%d, fan=%d, temp=%d)\n",
+ (uint) rc_pattern[0], (uint) rc_pattern[1],
+ rc_mode, rc_fan, rc_temp);
+ rc_tick = 1;
+
+ timer_set_period(TIM4, 1);
+ timer_generate_event(TIM4, TIM_EGR_UG);
+ timer_enable_counter(TIM4);
+}
+
+static bool rc_key(char key)
+{
+ if (key == 'o') {
+ rc_mode = MODE_OFF;
+ rc_send();
+ return true;
+ } else if (key == 'c') {
+ rc_mode = MODE_COOL;
+ rc_send();
+ return true;
+ } else if (key == 'w') {
+ rc_mode = MODE_WARM;
+ rc_send();
+ return true;
+ } else if (key == 'd') {
+ rc_mode = MODE_DEHUMIDIFY;
+ rc_send();
+ return true;
+ } else if (key == 'a') {
+ rc_fan = 0;
+ rc_send();
+ return true;
+ } else if (key == 'l') {
+ rc_fan = 1;
+ rc_send();
+ return true;
+ } else if (key == 'm') {
+ rc_fan = 2;
+ rc_send();
+ return true;
+ } else if (key == 'h') {
+ rc_fan = 3;
+ rc_send();
+ return true;
+ } else if (key >= '7' && key <= '9') {
+ rc_temp = key - '0' + 10;
+ rc_send();
+ return true;
+ } else if (key >= '0' && key <= '6') {
+ rc_temp = key - '0' + 20;
+ rc_send();
+ return true;
+ } else if (key == '&') {
+ rc_temp = 27;
+ rc_send();
+ return true;
+ } else if (key == '*') {
+ rc_temp = 28;
+ rc_send();
+ return true;
+ } else if (key == '(') {
+ rc_temp = 29;
+ rc_send();
+ return true;
+ } else if (key == ')') {
+ rc_temp = 30;
+ rc_send();
+ return true;
+ }
+ return false;
+}
+
/*** USB ***/
static usbd_device *usbd_dev;
debug_printf("Hello, world!\n");
tm_init();
+ rc_init();
usb_init();
u32 last_blink = 0;
tm_show();
}
+ if (usart_get_flag(USART1, USART_SR_RXNE)) {
+ uint ch = usart_recv(USART1);
+#if 0
+ if (ch == '1')
+ gpio_set(GPIOA, GPIO8);
+ else if (ch == '0')
+ gpio_clear(GPIOA, GPIO8);
+#else
+ if (rc_key(ch))
+ ;
+#endif
+ else
+ debug_putc(ch);
+ }
+
if (usb_event_pending) {
usbd_poll(usbd_dev);
usb_event_pending = 0;