matemat/display/firmware/display.c
2021-04-22 00:42:38 +02:00

212 lines
5.5 KiB
C

#include <avr/io.h>
#include <util/delay.h>
#include "display.h"
// maximum wait time until busy should be deasserted
// bail out if it takes longer than that
// unit: 10us (roughly)
// range: 0..255
#define DISPLAY_WAIT_MAX_10US 100
void display_init() {
// PC5, PC6, PC7: output (VFD RS, RW, E), RS=0, RW=1, E=1
// PB0, PB1, PB7, PD0, PD1, PD5, PD6, PD7: output (VFD DB) - can be switched to input (pullup not needed)
PORTB &= ~(_BV(PB0) | _BV(PB1) | _BV(PB7));
DDRB |= _BV(PB0) | _BV(PB1) | _BV(PB7);
PORTC |= _BV(PC6) | _BV(PC7);
DDRC |= _BV(PC5) | _BV(PC6) | _BV(PC7);
PORTD &= ~(_BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7));
DDRD |= _BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7);
display_wait_ready();
_delay_ms(100);
// initialize display: 8 bit, 2 lines, 50% brightness
display_write_ir(0x3c);
display_wait_ready();
}
static void display_set_rs(bool on) {
if (on) {
PORTC |= _BV(PC5);
} else {
PORTC &= ~_BV(PC5);
}
}
static void display_set_rw(bool on) {
if (on) {
PORTC |= _BV(PC6);
} else {
PORTC &= ~_BV(PC6);
}
}
static void display_set_e(bool on) {
if (on) {
PORTC |= _BV(PC7);
} else {
PORTC &= ~_BV(PC7);
}
}
static void display_set_data(uint8_t data) {
uint8_t datab = PORTB & ~(_BV(PB0) | _BV(PB1) | _BV(PB7));
uint8_t datad = PORTD & ~(_BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7));
datab |= (data & _BV(2)) ? _BV(PB0) : 0;
datab |= (data & _BV(3)) ? _BV(PB1) : 0;
datab |= (data & _BV(4)) ? _BV(PB7) : 0;
datad |= (data & _BV(0)) ? _BV(PD0) : 0;
datad |= (data & _BV(1)) ? _BV(PD1) : 0;
datad |= (data & _BV(5)) ? _BV(PD5) : 0;
datad |= (data & _BV(6)) ? _BV(PD6) : 0;
datad |= (data & _BV(7)) ? _BV(PD7) : 0;
PORTB = datab;
PORTD = datad;
}
static uint8_t display_get_data() {
uint8_t datab = PINB;
uint8_t datad = PIND;
uint8_t data = 0;
data |= (datad & _BV(PD0)) ? _BV(0) : 0;
data |= (datad & _BV(PD1)) ? _BV(1) : 0;
data |= (datad & _BV(PD5)) ? _BV(5) : 0;
data |= (datad & _BV(PD6)) ? _BV(6) : 0;
data |= (datad & _BV(PD7)) ? _BV(7) : 0;
data |= (datab & _BV(PB0)) ? _BV(2) : 0;
data |= (datab & _BV(PB1)) ? _BV(3) : 0;
data |= (datab & _BV(PB7)) ? _BV(4) : 0;
return data;
}
uint8_t display_read_ir() {
// switch PD to input
DDRB &= ~(_BV(PB0) | _BV(PB1) | _BV(PB7));
DDRD &= ~(_BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7));
// 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);
// read inputs
uint8_t datab = PINB;
uint8_t datad = PIND;
_delay_us(0.23 - 0.16);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle / output
PORTC = _BV(PC6) | _BV(PC7);
DDRB |= _BV(PB0) | _BV(PB1) | _BV(PB7);
DDRD |= _BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7);
// reassemble data
uint8_t data = 0;
data |= (datad & _BV(PD0)) ? _BV(0) : 0;
data |= (datad & _BV(PD1)) ? _BV(1) : 0;
data |= (datad & _BV(PD5)) ? _BV(5) : 0;
data |= (datad & _BV(PD6)) ? _BV(6) : 0;
data |= (datad & _BV(PD7)) ? _BV(7) : 0;
data |= (datab & _BV(PB0)) ? _BV(2) : 0;
data |= (datab & _BV(PB1)) ? _BV(3) : 0;
data |= (datab & _BV(PB7)) ? _BV(4) : 0;
return data;
}
uint8_t display_read_dr() {
// switch PD to input
DDRB &= ~(_BV(PB0) | _BV(PB1) | _BV(PB7));
DDRD &= ~(_BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7));
// 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);
// read inputs
uint8_t datab = PINB;
uint8_t datad = PIND;
_delay_us(0.23 - 0.16);
// E=0 (enable/clock)
PORTC &= ~_BV(PC7);
_delay_us(0.01);
// reset to idle / output
PORTC = _BV(PC6) | _BV(PC7);
DDRB |= _BV(PB0) | _BV(PB1) | _BV(PB7);
DDRD |= _BV(PD0) | _BV(PD1) | _BV(PD5) | _BV(PD6) | _BV(PD7);
// reassemble data
uint8_t data = 0;
data |= (datad & _BV(PD0)) ? _BV(0) : 0;
data |= (datad & _BV(PD1)) ? _BV(1) : 0;
data |= (datad & _BV(PD5)) ? _BV(5) : 0;
data |= (datad & _BV(PD6)) ? _BV(6) : 0;
data |= (datad & _BV(PD7)) ? _BV(7) : 0;
data |= (datab & _BV(PB0)) ? _BV(2) : 0;
data |= (datab & _BV(PB1)) ? _BV(3) : 0;
data |= (datab & _BV(PB7)) ? _BV(4) : 0;
return data;
}
void display_write_ir(uint8_t data) {
// RS=0 (IR) RW=0 (write)
display_set_rs(false);
display_set_rw(false);
_delay_us(0.23);
// E=1 (enable/clock)
display_set_e(true);
// send data
display_set_data(data);
_delay_us(0.23);
// E=0 (enable/clock)
display_set_e(false);
_delay_us(0.01);
// reset to idle
display_set_rs(true);
display_set_rw(true);
display_set_data(0);
}
void display_write_dr(uint8_t data) {
// RS=1 (DR) RW=0 (write)
display_set_rs(true);
display_set_rw(false);
_delay_us(0.23);
// E=1 (enable/clock)
display_set_e(true);
// send data
display_set_data(data);
_delay_us(0.23);
// E=0 (enable/clock)
display_set_e(false);
_delay_us(0.01);
// reset to idle
display_set_rw(true);
display_set_data(0);
}
bool display_wait_ready() {
// FIXME hack to avoid read mode
_delay_ms(1);
return true;
// FIXME
// 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 < DISPLAY_WAIT_MAX_10US; i++) {
// read_ir() takes about 500ns
if (display_read_ir() & _BV(7)) return true;
// wait 10us until the next status read
_delay_us(10 - 0.5);
}
return false;
}
void display_write(uint8_t command, const uint8_t *data, uint8_t length) {
if (display_wait_ready()) {
display_write_ir(command);
for (uint8_t i = 0; i < length; i++) {
display_write_dr(data[i]);
}
}
}