diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..492a690 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: ["-I/opt/avr8-gnu-toolchain-darwin_x86_64/avr/include", "-D__AVR_ATtiny84A__"] diff --git a/.gitignore b/.gitignore index 314b196..a2d6a49 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ a.* +build/ diff --git a/Makefile b/Makefile index 1626965..9f4c49e 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ -all: a.bin +all: a.out -a.out: main.c - avr-gcc -g -Os -mmcu=attiny84a $< -o $@ +a.out: src/*.c src/*.h + avr-gcc -Wall -g -Os -mmcu=attiny84a src/*.c -o $@ a.bin: a.out avr-objcopy -O binary $< $@ -deploy: a.bin - avrdude -p t84a -c stk500v1 -P/dev/tty.usbmodemflip_Rab3gao3 -U flash:w:$<:r +deploy: a.out + avrdude -p t84a -c stk500v1 -P/dev/tty.usbmodemflip_Rab3gao3 -U flash:w:$<:e diff --git a/config.h b/config.h deleted file mode 100644 index 5bd08a1..0000000 --- a/config.h +++ /dev/null @@ -1,6 +0,0 @@ -#include - -#define OSCILLATOR_CALIBRATION 0x76 -// #define STARTING_TIME_MS (uint32_t)6*60*1000 // 6 min -#define STARTING_TIME_MS (uint32_t)5*1000 // 5 sec - diff --git a/main.c b/main.c deleted file mode 100644 index 5d8ff32..0000000 --- a/main.c +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include - -#include "config.h" - -#define PLAYER_A_BUTTON 0 -#define PLAYER_B_BUTTON 1 - -uint32_t ms_since_boot = 0; - -typedef enum { - STOPPED, - PLAYER_A, - PLAYER_B -} state_type; - -state_type state = STOPPED; - -uint32_t player_a_timer = STARTING_TIME_MS; -uint32_t player_b_timer = STARTING_TIME_MS; - -ISR(TIM1_COMPA_vect) -{ - ms_since_boot++; - - if(state == PLAYER_A) { - player_a_timer--; - if(player_a_timer == 0) { state = STOPPED; } - } else if(state == PLAYER_B) { - player_b_timer--; - if(player_b_timer == 0) { state = STOPPED; } - } -} - -void setupio(void) { - // Set port A to inputs - DDRA = 0x00; - PORTA |= _BV(PORTA0) | _BV(PORTA1); // pull-up - - // Set port B to output - DDRB = 0xFF; -} - -void setup_clock(void) { - OSCCAL = OSCILLATOR_CALIBRATION; - - TCCR1B = 0x09; - OCR1A = 1000; - TIMSK1 = 0x02; -} - -bool button_pressed(uint8_t pin) { - return (PINA & (1 << pin)) == 0; -} - -int main() { - setupio(); - setup_clock(); - sei(); - - while(true) { - cli(); - if(button_pressed(PLAYER_A_BUTTON) && player_b_timer != 0) { - state = PLAYER_B; - } - - if(button_pressed(PLAYER_B_BUTTON) && player_a_timer != 0) { - state = PLAYER_A; - } - - PORTB = 1 << state; - sei(); - } -} - diff --git a/src/as1115.c b/src/as1115.c new file mode 100644 index 0000000..d751735 --- /dev/null +++ b/src/as1115.c @@ -0,0 +1,7 @@ +#include "as1115.h" +#include "i2c.h" + +int as1115_send_command(uint8_t cmd, uint8_t data) { + uint8_t packet[] = {cmd, data}; + return i2c_write(0x00, packet, sizeof packet); +} diff --git a/src/as1115.h b/src/as1115.h new file mode 100644 index 0000000..77f97a3 --- /dev/null +++ b/src/as1115.h @@ -0,0 +1,23 @@ +#include + +#define DIGIT_0_REG 0x01 +#define DIGIT_1_REG 0x02 +#define DIGIT_2_REG 0x03 +#define DIGIT_3_REG 0x04 +#define DIGIT_4_REG 0x05 +#define DIGIT_5_REG 0x06 +#define DIGIT_6_REG 0x07 +#define DIGIT_7_REG 0x08 +#define DECODE_MODE_REG 0x09 +#define GLOBAL_INTENSITY_REG 0x0A +#define SCAN_LIMIT_REG 0x0B +#define SHUTDOWN_REG 0x0C +#define SELF_ADDRESSING_REG 0x0D +#define FEATURE_REG 0x0E +#define DISPLAY_TEST_MODE_REG 0x0F +#define DIG_0_1_INTENSITY_REG 0x10 +#define DIG_2_3_INTENSITY_REG 0x11 +#define DIG_4_5_INTENSITY_REG 0x12 +#define DIG_6_7_INTENSITY_REG 0x13 + +int as1115_send_command(uint8_t cmd, uint8_t data); diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..43defbb --- /dev/null +++ b/src/config.h @@ -0,0 +1,5 @@ +#include + +#define OSCILLATOR_CALIBRATION 0x76 +#define STARTING_TIME_MS (uint32_t)10*60*1000 // 10 min +// #define STARTING_TIME_MS (uint32_t)5*1000 // 5 sec diff --git a/src/i2c.c b/src/i2c.c new file mode 100644 index 0000000..9ce18ad --- /dev/null +++ b/src/i2c.c @@ -0,0 +1,78 @@ +#include +#include + +#include +#include + +#include "i2c.h" + +void i2c_init() { + DDRA |= _BV(SDA_BIT) | _BV(SCL_BIT); // Set SDA and SCL to output + PORTA |= _BV(SDA_BIT) | _BV(SCL_BIT); // Write HIGH to SDA and SCL +} + +#define HIGH 1 +#define LOW 0 + +void port_a_put(uint8_t pin, bool value) { + if(value) { + PORTA |= _BV(pin); + DDRA &= ~_BV(pin); + } else { + PORTA &= ~_BV(pin); + DDRA |= _BV(pin); + } +} + +int i2c_write_byte(uint8_t byte) { + for (int i = 7; i >= 0; i--) { + port_a_put(SDA_BIT, (byte & 0x80) != 0); + byte <<= 1; + + // pulse clock + port_a_put(SCL_BIT, HIGH); + port_a_put(SCL_BIT, LOW); + } + + port_a_put(SDA_BIT, HIGH); + + // check ACK + + port_a_put(SCL_BIT, HIGH); + + int ack = PINA & _BV(SDA_BIT); + + port_a_put(SCL_BIT, LOW); + + return ack; +} + +int i2c_write(uint8_t addr, const uint8_t* data, uint8_t len) { + // START condition + port_a_put(SDA_BIT, LOW); + + port_a_put(SCL_BIT, LOW); + + // write address + int ack = i2c_write_byte(addr); + if (ack) { + port_a_put(SDA_BIT, HIGH); + port_a_put(SCL_BIT, HIGH); + return 1; + } + + for (int i = 0; i < len; i++) { + int ack = i2c_write_byte(data[i]); + if (ack) { + port_a_put(SDA_BIT, HIGH); + port_a_put(SCL_BIT, HIGH); + return 1; + } + } + + // STOP condition + port_a_put(SCL_BIT, HIGH); + port_a_put(SDA_BIT, HIGH); + + return 0; +} diff --git a/src/i2c.h b/src/i2c.h new file mode 100644 index 0000000..517d16f --- /dev/null +++ b/src/i2c.h @@ -0,0 +1,4 @@ +#include + +void i2c_init(); +int i2c_write(uint8_t addr, const uint8_t* data, uint8_t len); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..9d0cd5b --- /dev/null +++ b/src/main.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +#include "config.h" +#include "as1115.h" +#include "i2c.h" + +#define PLAYER_A_BUTTON 0 +#define PLAYER_B_BUTTON 1 + +volatile uint32_t ms_since_boot = 0; + +typedef enum { + STOPPED, + PLAYER_A, + PLAYER_B +} state_type; + +volatile state_type state = STOPPED; + +volatile uint32_t player_a_timer = STARTING_TIME_MS; +volatile uint32_t player_b_timer = STARTING_TIME_MS; + +ISR(TIM1_COMPA_vect) +{ + ms_since_boot++; + + if(state == PLAYER_A) { + player_a_timer--; + if(player_a_timer == 0) { state = STOPPED; } + } else if(state == PLAYER_B) { + player_b_timer--; + if(player_b_timer == 0) { state = STOPPED; } + } +} + +void setupio(void) { + // Set port A to inputs + DDRA = 0x00; + PORTA |= _BV(PORTA0) | _BV(PORTA1); // pull-up + + // Set port B to output + DDRB = 0xFF; +} + +void setup_clock(void) { + OSCCAL = OSCILLATOR_CALIBRATION; + + TCCR1B = 0x09; + OCR1A = 1000; + TIMSK1 = 0x02; +} + +bool button_pressed(uint8_t pin) { + return (PINA & (1 << pin)) == 0; +} + +#define DISPLAY_LEFT 0 +#define DISPLAY_RIGHT 1 + +void render_timer(unsigned long ms, uint8_t display) { + uint8_t digits[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + + if (ms == 0) { + memset(&digits, 0x0A, 4); // show all dashes + goto render; + } + + uint8_t sec = (ms + (1000 - 1)) / 1000 % 60; + uint8_t min = (ms + (1000 - 1)) / 1000 / 60; + + digits[3] = sec % 10; + digits[2] = sec / 10; + + if (min != 0) { + digits[1] = min % 10; + if (min >= 10) { + digits[0] = min / 10; + } + } + +render: + switch(display) { + case DISPLAY_LEFT: + as1115_send_command(0x01, digits[0]); + as1115_send_command(0x02, digits[1]); + as1115_send_command(0x03, digits[2]); + as1115_send_command(0x04, digits[3]); + break; + case DISPLAY_RIGHT: + as1115_send_command(0x05, digits[0]); + as1115_send_command(0x06, digits[1]); + as1115_send_command(0x07, digits[2]); + as1115_send_command(0x08, digits[3]); + break; + } +} + +int main() { + cli(); + setupio(); + i2c_init(); + setup_clock(); + sei(); + + render_timer(STARTING_TIME_MS, DISPLAY_LEFT); + render_timer(STARTING_TIME_MS, DISPLAY_RIGHT); + + as1115_send_command(SHUTDOWN_REG, 0x01); // turn on + as1115_send_command(DECODE_MODE_REG, 0xFF); // decode + as1115_send_command(SCAN_LIMIT_REG, 0x07); // enable all digits + + while(true) { + render_timer(player_a_timer, DISPLAY_LEFT); + render_timer(player_b_timer, DISPLAY_RIGHT); + + cli(); + if(button_pressed(PLAYER_A_BUTTON) && player_b_timer != 0 && player_a_timer != 0) { + state = PLAYER_B; + } + + if(button_pressed(PLAYER_B_BUTTON) && player_b_timer != 0 && player_a_timer != 0) { + state = PLAYER_A; + } + sei(); + } +}