#define _dbg_dump_ret() #define dbg_dump_ret() do { \ union { uint16_t u; void *p; } ret = { .p = __builtin_return_address(0) }; \ _uart_putch('#'); uart_puthex16((ret.u & 0xfff) << 1); _uart_putch('\n'); } while (0) #define array_size(a) (sizeof(a) / sizeof(a[0])) #define F_CPU 8000000 #include #include #include #include #include #include #include #include const uint8_t __signature[3] __attribute__((section (".signature"), used)) = { SIGNATURE_2, SIGNATURE_1, SIGNATURE_0 }; #define B_SCK 5 #define B_MISO 4 #define B_MOSI 3 #define B_SS 2 #define D_TXD 1 #define D_DALII 3 #define D_DALIO 4 #define D_LED1 5 #define D_LED2 6 #define D_TAST 7 enum { G_BOOTING = 0, G_RUNNING, } global_state = G_BOOTING; #define SYSTICK_USER_100HZ static void systick_user_100hz(void); #define SYSTICK_USER_1HZ static void systick_user_1hz(void); #define CAN_USER_IRQH static void can_user_irqh(void); #define DALI_USER_IDLE static uint16_t dali_user_idle(void); #define DALI_NUMDEV 64 #define T_EVG_COOLDOWN 5 /* seconds */ /* cooldown: * set(0) => set_needed=1 * `-> handler performs DALI "OFF", * set_needed = 0, cooldown = T_EVG_COOLDOWN * | no other action while cooldown <> 0 * `-> systick_user_1hz cooldown-- * `-> cooldown == 0 => resume normal operations * * ack_changes: * set() => set_needed =1 * `-> handler performs DALI SET * ack_changes = 3 * `-> */ struct dali_dev { uint8_t set, actual; __extension__ uint8_t present : 1; __extension__ uint8_t _unused1 : 1; __extension__ uint8_t need_set : 2; __extension__ uint8_t need_poweron : 2; __extension__ uint8_t need_poweroff : 2; __extension__ uint8_t tx_read : 1; __extension__ uint8_t tx_set : 3; __extension__ uint8_t cooldown : 4; } __attribute__((packed)); #define NEED_SET 2 #define NEED_POWERON 2 #define NEED_POWEROFF 2 static struct dali_dev devices[DALI_NUMDEV]; static void target_set(uint8_t dst, uint8_t val); #define target_get(idx) devices[idx].set #define target_present(addr) devices[addr].present #define target_set_present(addr) devices[addr].present = 1 #define REFRESH_TIMER 30 /* seconds */ #include "uart.c" #include "tick.c" #include "dali2.c" #include "dali_ctl.c" #include "dim.c" #include "can.c" #include "wdt.c" #include "helpers.c" #define CANA_DALI_BASE 0x440 /* irq mixed */ static void target_set(uint8_t dst, uint8_t val) { uart_puts(" ["); uart_puthex(dst); uart_puts("="); uart_puthex(val); uart_puts("]"); devices[dst].need_set = NEED_SET; if (!devices[dst].set && val) devices[dst].need_poweron = NEED_POWERON; else if (!val) devices[dst].need_poweroff = NEED_POWEROFF; devices[dst].set = val; } static uint8_t refresh_timer = 0; /* irqonly */ static void systick_user_1hz(void) { if (global_state == G_BOOTING) return; for (uint8_t i = 0; i < DALI_NUMDEV; i++) { if (!devices[i].cooldown) continue; devices[i].cooldown--; if (!devices[i].cooldown && devices[i].set) { devices[i].need_set = NEED_SET; devices[i].need_poweron = NEED_POWERON; } } if (refresh_timer) refresh_timer--; else { uart_puts("periodic DALI refresh triggered\n"); for (uint8_t i = 0; i < DALI_NUMDEV; i++) { if (!target_present(i)) continue; if (devices[i].set) devices[i].need_set |= 1; else devices[i].need_poweroff |= 1; } refresh_timer = REFRESH_TIMER; } } /* irqonly */ static void systick_user_100hz(void) { if (global_state == G_BOOTING) return; for (uint8_t i = 0; i < DALI_NUMDEV; i++) if (devices[i].tx_set > 2) devices[i].tx_set--; do_tick(); } /* irqonly */ static void can_handle_light(uint16_t sublab_addr) { /* - 7 allows overlapping writes to a nonaligned address. * "base" below will start out at 0xf9~0xff in that case */ if (sublab_addr < CANA_DALI_BASE - 8) return; uint8_t base = sublab_addr - CANA_DALI_BASE, len = can_rx_len(), pos; for (pos = 0; pos < len; pos++) { uint8_t dst = base + pos, val; if (dst == 0xff) { /* bus element 43f: hw switch disable */ sw.enabled = can_rx_data[pos] != 0; continue; } if (dst >= 0x40) continue; val = can_rx_data[pos]; target_set(dst, val); } } /* irqonly */ static uint16_t dali_user_idle(void) { static uint8_t dali_exec_pos = 0; uint8_t stop_at = dali_exec_pos; uint16_t ret = DALI_INVALID; if (global_state == G_BOOTING) return ret; do { dali_exec_pos = (dali_exec_pos + 1) & (DALI_NUMDEV - 1); if (devices[dali_exec_pos].need_poweroff) { ret = (dali_exec_pos << 9) | 0x100; devices[dali_exec_pos].need_poweroff--; devices[dali_exec_pos].cooldown = T_EVG_COOLDOWN; } else if (devices[dali_exec_pos].need_poweron) { ret = (dali_exec_pos << 9) | 0x108; devices[dali_exec_pos].need_poweron--; } else if (devices[dali_exec_pos].need_set) { ret = (dali_exec_pos << 9) | (devices[dali_exec_pos].set == 0xff ? 0xfe : devices[dali_exec_pos].set); devices[dali_exec_pos].need_set--; } } while (dali_exec_pos != stop_at && ret == DALI_INVALID); if (ret == DALI_INVALID) return ret; uart_puttick(); uart_puts("dali async "); uart_puthex16(ret); uart_puts("\n"); if (!devices[dali_exec_pos].need_set && !devices[dali_exec_pos].need_poweron && !devices[dali_exec_pos].need_poweroff) devices[dali_exec_pos].tx_set = 7; return ret; } static struct can can_queue[4]; static uint8_t can_queue_ptr = 0; /* set commands are directly applied from CAN IRQ */ /* irqonly */ static void can_user_irqh(void) { uint16_t sublab_addr, sublab_proto; if (!can_rx_isext() || global_state == G_BOOTING) { can.rx_addr.u = 0; return; } sublab_addr = can_rx_sublab_addr(); if (sublab_addr >= CANA_DALI_BASE + DALI_NUMDEV || sublab_addr < CANA_DALI_BASE - 7) { can.rx_addr.u = 0; return; } sublab_proto = can_rx_sublab_proto(); uart_puttick(); uart_puts("CAN "); uart_puthex16(sublab_proto); uart_puts(" "); uart_puthex16(sublab_addr); if (sublab_proto == 0xcc08) can_handle_light(sublab_addr); else { can_queue[can_queue_ptr] = can; can_queue_ptr = (can_queue_ptr + 1) & (array_size(can_queue) - 1); } can.rx_addr.u = 0; uart_puts(" EOI\n"); } static struct can perform; const uint8_t dalidisc_page0[6] PROGMEM = {0x01, 0x01, 0x00, 0x01, 0x00, 0x00}; const uint8_t dalidisc_page8[4] PROGMEM = {'D', 'A', 'L', 'I'}; static uint8_t dali_grab_value(uint8_t busaddr, uint8_t cmd) { dali_send(0x100 | (busaddr << 9) | cmd); if (!dali_rx_avail) { _delay_ms(2); dali_send(0x100 | (busaddr << 9) | cmd); } if (!dali_rx_avail) { _delay_ms(5); dali_send(0x100 | (busaddr << 9) | cmd); } return dali_rx_avail ? dali_rx : 0xa5; } static void can_handle_disco(uint16_t sublab_addr) { wdt_reset(); if (sublab_addr < CANA_DALI_BASE) return; uint8_t addr = sublab_addr - CANA_DALI_BASE; uint8_t page = can_rx_sublab_disco_page2(perform); uint8_t buf[8]; if (!can_rx_ext_rr2(perform)) { uart_puts("nRR\n"); switch (page) { case 5: if (can_rx_len2(perform) != 2) return; dali_send((perform.rx_data[0] << 8) | perform.rx_data[1]); can_send(CANA_DISCOVERY_F(page, sublab_addr), !!dali_rx_avail, (uint8_t *)&dali_rx); return; case 6: if (can_rx_len2(perform) == 4 && perform.rx_data[0] == 's' && perform.rx_data[1] == 'c' && perform.rx_data[2] == 'a' && perform.rx_data[3] == 'n') { global_state = G_BOOTING; for (uint8_t c = 0; c < DALI_NUMDEV; c++) devices[c].present = 0; dali_search(); global_state = G_RUNNING; } else if (can_rx_len2(perform) == 3 && perform.rx_data[0] == 'q' && perform.rx_data[1] == 'b') { uint8_t addr = perform.rx_data[2] & 0x3f; uint8_t msg[3]; dali_send(DALI_C_QBALLAST | (addr << 9)); if (!dali_rx_avail) dali_send(DALI_C_QBALLAST | (addr << 9)); devices[addr].present = dali_rx_avail; msg[0] = 'q'; msg[1] = dali_rx_avail ? '+' : '-'; msg[2] = addr; can_send(CANA_DISCOVERY_F(page, sublab_addr), sizeof(msg), msg); } return; } return; } #define loadpgm(what) for (uint8_t c = 0; c < sizeof(what); c++) buf[c] = pgm_read_byte(what + c); switch (page) { case 0: loadpgm(dalidisc_page0); can_send(CANA_DISCOVERY_F(page, sublab_addr), sizeof(dalidisc_page0), buf); return; case 6: /* page 6: * VERSION NUMBER * DEVICE TYPE * PHYSICAL MIN LEVEL * RANDOM ADDRESS H * RANDOM ADDRESS M * RANDOM ADDRESS L */ buf[0] = dali_grab_value(addr, 0x97); buf[1] = dali_grab_value(addr, 0x99); buf[2] = dali_grab_value(addr, 0x9a); buf[3] = dali_grab_value(addr, 0xc2); buf[4] = dali_grab_value(addr, 0xc3); buf[5] = dali_grab_value(addr, 0xc4); can_send(CANA_DISCOVERY_F(page, sublab_addr), 6, buf); return; case 7: /* page 7: * ACTUAL DIM LEVEL * MAX LEVEL * MIN LEVEL * POWER ON LEVEL * SYSTEM FAILURE LEVEL * FADE RATE (low nibble), FADE TIME (high nibble) * STATUS * SHORT ADDRESS (or 0xff if no device) */ for (uint8_t offs = 0; offs < 6; offs++) buf[offs] = dali_grab_value(addr, 0xa0 + offs); buf[6] = dali_grab_value(addr, 0x90); dali_send(0x191 | (addr << 9)); buf[7] = dali_rx_avail ? addr : 0xff; can_send(CANA_DISCOVERY_F(page, sublab_addr), 8, buf); return; case 8: loadpgm(dalidisc_page8); can_send(CANA_DISCOVERY_F(page, sublab_addr), sizeof(dalidisc_page8), buf); return; default: can_send(CANA_DISCOVERY_F(page, sublab_addr), 0, NULL); } } static void can_delayed_exec_do(void) { uint16_t sublab_addr, sublab_proto; sublab_addr = can_rx_sublab_addr2(perform); sublab_proto = can_rx_sublab_proto2(perform); switch (sublab_proto) { case 0x4c08: can_handle_disco(sublab_addr); return; } } static void can_delayed_exec(void) { if (can_tx_busy()) return; for (uint8_t i = 0; i < array_size(can_queue); i++) { uint8_t idx = (can_queue_ptr + i) & (array_size(can_queue) - 1); cli(); if (can_queue[idx].rx_addr.u) { perform = can_queue[idx]; can_queue[idx].rx_addr.u = 0; sei(); can_delayed_exec_do(); } else sei(); } } static void can_send_acks(void) { static uint8_t tx_ack_pos = 0; uint8_t pkt_start_at = 0xff; uint8_t data[8], len = 0; if (can_tx_busy()) return; do { if (devices[tx_ack_pos].tx_set == 1 || devices[tx_ack_pos].tx_set == 2) { len = 1; break; } tx_ack_pos++; } while (tx_ack_pos < DALI_NUMDEV && !len); if (!len) { tx_ack_pos = 0; return; } pkt_start_at = tx_ack_pos; data[0] = devices[tx_ack_pos].set; devices[tx_ack_pos].tx_set--; tx_ack_pos++; while (tx_ack_pos < DALI_NUMDEV && len < 8 && devices[tx_ack_pos].tx_set) { data[tx_ack_pos - pkt_start_at] = devices[tx_ack_pos].set; if (devices[tx_ack_pos].tx_set > 2) devices[tx_ack_pos].tx_set = 2; devices[tx_ack_pos].tx_set--; len++; tx_ack_pos++; } can_send(CANA_LIGHT_F(0, CANA_DALI_BASE + pkt_start_at), len, data); if (tx_ack_pos == DALI_NUMDEV) tx_ack_pos = 0; } static void can_send_read(void) { uint8_t pkt_start_at = 0, i; uint8_t data[8], len = 0; if (can_tx_busy()) return; for (i = 0; i < DALI_NUMDEV; i++) if (devices[i].tx_read) break; if (i == DALI_NUMDEV) return; pkt_start_at = i; while (len < 8 && i < DALI_NUMDEV && devices[i].tx_read) { devices[i].tx_read = 0; dali_send(0x1a0 | (i << 9)); if (!dali_rx_avail) dali_send(0x1a0 | (i << 9)); if (!dali_rx_avail) { uart_puts("!RE "); uart_puthex(i); uart_puts("\n"); break; } data[i - pkt_start_at] = dali_rx; len++, i++; } if (!len) return; can_send(CANA_SENSOR_F(CANA_DALI_BASE + pkt_start_at), len, data); } static void can_sched(void) { static uint32_t last_dump_tick = 0; if (systick.u32 - last_dump_tick > 15) { last_dump_tick = systick.u32; for (uint8_t i = 0; i < DALI_NUMDEV; i++) if (!devices[i].tx_set) { devices[i].tx_set = devices[i].present; devices[i].tx_read = devices[i].present; } } } int main(void) { wdt_init(); DDRD |= (1 << D_LED1) | (1 << D_LED2 ) | (1 << D_TXD) | (1 << D_DALIO); PORTD |= (1 << D_LED1) | (1 << D_TAST); PORTD &= ~(1 << D_LED1); refresh_timer = REFRESH_TIMER; uart_init(); tick_init(); can_preinit(); dali_init(); dim_init(); postinit_slowboot(); dali_buscheck(); can_init(); can_CANSTAT(); wdt_reset(); dali_search(); uart_puts("\ninit done\n"); global_state = G_RUNNING; while (1) { wdt_reset(); can_delayed_exec(); can_sched(); can_send_acks(); can_send_read(); } }