matemat/display/firmware/main.c
2021-03-09 11:52:38 +01:00

232 lines
4.7 KiB
C

#include <inttypes.h>
#include <stdbool.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <can.h>
#include <messages.h>
static bool report_change = false;
static void init() {
/*
PB2 BUTTON0
PB3 BUTTON1
PB4 BUTTON2
PB5 BUTTON3
PB6 BUTTON4
PC0 BUTTON5
PC1 BUTTON6
PC2 CANTX
PC3 CANRX
PC4 BUTTON7
PC5 VFDRS
PC6 VFDRW
PC7 VFDE
PD0 VFDDB0
PD1 VFDDB1
PD2 VFDDB2
PD3 VFDDB3
PD4 VFDDB4
PD5 VFDDB5
PD6 VFDDB6
PD7 VFDDB7
*/
// PB2, PB3, PB4, PB5, PB6, PC0, PC1, PC4: input, no pullup (button0..7)
// PC5, PC6, PC7: output (VFD RS, RW, E), RS=0, RW=H, E=H
// PD: output (VFD DB) - can be switched to input w/ pullup
PORTB = 0x00;
DDRB = 0x00;
PORTC = _BV(PC6) | _BV(PC7);
DDRC = _BV(PC5) | _BV(PC6) | _BV(PC7);
PORTD = 0x00;
DDRD = 0xff;
// Initialize CAN interface
can_init(BITRATE_125_KBPS);
// Regular receive/transmit mode
can_set_mode(NORMAL_MODE);
// Filter for messages addressed to us (RTR on)
can_filter_t us = {
.id = CAN_HOSTID_DISPLAY,
.mask = CAN_HOSTID_MASK,
.flags = {
.rtr = 1,
.extended = 0,
},
};
can_set_filter(0, &us);
// Filter for public messages (RTR off)
can_filter_t pub = {
.id = CAN_HOSTID_BROADCAST,
.mask = CAN_HOSTID_MASK,
.flags = {
.rtr = 0,
.extended = 0,
},
};
can_set_filter(1, &pub);
// Enable interrupts to start reception
sei();
}
static uint8_t get_buttons() {
uint8_t pinb = PINB;
uint8_t pinc = PINC;
uint8_t buttons = 0;
if (pinb & _BV(PB2)) buttons |= _BV(0);
if (pinb & _BV(PB3)) buttons |= _BV(1);
if (pinb & _BV(PB4)) buttons |= _BV(2);
if (pinb & _BV(PB5)) buttons |= _BV(3);
if (pinb & _BV(PB6)) buttons |= _BV(4);
if (pinc & _BV(PC0)) buttons |= _BV(5);
if (pinc & _BV(PC1)) buttons |= _BV(6);
if (pinc & _BV(PC4)) buttons |= _BV(7);
return buttons;
}
static void send_status() {
uint8_t status = get_buttons();
if (can_check_free_buffer()) {
can_t msg = {
.id = CAN_MSG_DISPLAY_STATUS,
.flags = {
.rtr = 0,
},
.length = 1,
.data = { (uint8_t) status, },
};
can_send_message(&msg);
}
}
static uint8_t read_ir() {
// switch PD to input
DDRD = 0x00;
// E=0 (enable/clock) RS=0 (IR) RW=1 (read)
PORTC = (PORTC & ~(_BV(PC5) | _BV(PC7))) | _BV(PC6);
_delay_us(0.23);
// E=1 (enable/clock)
PORTC |= _BV(PC7);
_delay_us(0.16);
uint8_t data = PIND;
_delay_us(0.23 - 0.16);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle
PORTC = _BV(PC6) | _BV(PC7);
DDRD = 0xff;
return data;
}
static uint8_t read_dr() {
// switch PD to input
DDRD = 0x00;
// E=0 (enable/clock) RS=1 (IR) RW=1 (read)
PORTC = (PORTC & ~_BV(PC7)) | (_BV(PC5) | _BV(PC6));
_delay_us(0.23);
// E=1 (enable/clock)
PORTC |= _BV(PC7);
_delay_us(0.16);
uint8_t data = PIND;
_delay_us(0.23 - 0.16);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle
PORTC = _BV(PC6) | _BV(PC7);
DDRD = 0xff;
return data;
}
static void write_ir(uint8_t data) {
// E=0 (enable/clock) RS=0 (IR) RW=0 (write)
PORTC &= ~(_BV(PC5) | _BV(PC6) | _BV(PC7));
_delay_us(0.23);
// E=1 (enable/clock)
PORTC |= _BV(PC7);
// data
PORTD = data;
_delay_us(0.23);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle
PORTC = _BV(PC6) | _BV(PC7);
PORTD = 0x00;
}
static void write_dr(uint8_t data) {
// E=0 (enable/clock) RS=1 (IR) RW=0 (write)
PORTC = (PORTC & ~(_BV(PC6) | _BV(PC7))) | _BV(PC5);
_delay_us(0.23);
// E=1 (enable/clock)
PORTC |= _BV(PC7);
// data
PORTD = data;
_delay_us(0.23);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle
PORTC = _BV(PC6) | _BV(PC7);
PORTD = 0x00;
}
static bool wait_ready() {
// if the busy flag never goes high, we'll run into a deadlock here.
// let's put a deadline on the wait loop.
for (uint8_t i = 0; i < 100; i++) {
// read_ir() takes about 500ns
if (read_ir() & _BV(7)) return true;
// wait 10us until the next status read
_delay_us(10 - 0.5);
}
return false;
}
static void write_display(uint8_t command, const uint8_t *data, uint8_t length) {
if (wait_ready()) {
write_ir(command);
for (uint8_t i = 0; i < length; i++) {
write_dr(data[i]);
}
}
}
static void loop() {
// Check for new messages
if (can_check_message()) {
can_t msg = { 0 };
uint8_t status = can_get_message(&msg);
if (status != 0) {
switch (msg.id) {
case CAN_MSG_DISPLAY_STATUS:
send_status();
break;
case CAN_MSG_DISPLAY_CMD:
if (msg.length >= 1) {
write_display(msg.data[0], &msg.data[1], msg.length - 1);
}
break;
case CAN_MSG_AUTO_STATUS:
if (msg.length == 1) {
report_change = msg.data[0] ? true : false;
}
break;
}
}
}
}
int main() {
init();
while (1) {
loop();
}
}