diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 37 | ||||
-rw-r--r-- | Charliplexing.c | 503 | ||||
-rw-r--r-- | Charliplexing.h | 33 | ||||
-rw-r--r-- | kbd.c | 167 | ||||
-rw-r--r-- | kbd.h | 25 | ||||
-rw-r--r-- | snake.c | 199 |
7 files changed, 966 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dee53c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d31ff5e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 2.8.3) + + +SET(BOARD "atmega328p" CACHE STRING "AVR CPU to build for") +SET(CLOCK "16000000" CACHE STRING "CPU clock") + +SET(FLASH_FLAGS "-patmega328p" "-carduino" "-P/dev/ttyUSB0" "-b57600" CACHE STRING "avrdude flags") + + +find_program(AVR_GCC avr-gcc) +find_program(AVRDUDE avrdude) + +SET(CMAKE_SYSTEM_NAME Generic) + + +SET(CMAKE_C_COMPILER ${AVR_GCC}) + +project(ARDKBD C) + +set(CMAKE_MODULE_PATH ${ARDKDB_SOURCE_DIR}) + + +add_executable(snake.elf + Charliplexing.c + kbd.c + snake.c +) +set_target_properties(snake.elf PROPERTIES + COMPILE_FLAGS "-Wall -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Os -mmcu=${BOARD}" + LINK_FLAGS "-Wall -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Os -mmcu=${BOARD}" + COMPILE_DEFINITIONS "F_CPU=${CLOCK}" +) + +add_custom_command(OUTPUT snake.hex COMMAND ${CMAKE_OBJCOPY} -O ihex -R .eeprom snake.elf snake.hex DEPENDS snake.elf) +add_custom_target(snake ALL DEPENDS snake.hex) + +add_custom_target(flash COMMAND ${AVRDUDE} ${FLASH_FLAGS} -D -Uflash:w:snake.hex:i DEPENDS snake) diff --git a/Charliplexing.c b/Charliplexing.c new file mode 100644 index 0000000..13cb248 --- /dev/null +++ b/Charliplexing.c @@ -0,0 +1,503 @@ +/* + Charliplexing.cpp - Using timer2 with 1ms resolution + + Alex Wenger <a.wenger@gmx.de> http://arduinobuch.wordpress.com/ + Matt Mets <mahto@cibomahto.com> http://cibomahto.com/ + + Timer init code from MsTimer2 - Javier Valencia <javiervalencia80@gmail.com> + Misc functions from Benjamin Sonnatg <benjamin@sonntag.fr> + + History: + 2009-12-30 - V0.0 wrote the first version at 26C3/Berlin + 2010-01-01 - V0.1 adding misc utility functions + (Clear, Vertical, Horizontal) comment are Doxygen complaints now + 2010-05-27 - V0.2 add double-buffer mode + 2010-08-18 - V0.9 Merge brightness and grayscale + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include <math.h> +#include <avr/interrupt.h> +#include <util/delay.h> +#include "Charliplexing.h" + +volatile unsigned int LedSign_tcnt2; + + +typedef struct _videoPage { + uint8_t pixels[SHADES][48]; // TODO: is 48 right? +} videoPage; + +/* ----------------------------------------------------------------- */ +/** Table for the LED multiplexing cycles + * Each frame is made of 24 bytes (for the 24 display cycles) + * There are SHADES frames per buffer in grayscale mode (one for each brigtness) + * and twice that many to support double-buffered grayscale. + */ +videoPage leds[2]; + +/// Determines whether the display is in single or double buffer mode +uint8_t displayMode = SINGLE_BUFFER; + +/// Flag indicating that the display page should be flipped as soon as the +/// current frame is displayed +volatile bool videoFlipPage = false; + +/// Pointer to the buffer that is currently being displayed +videoPage* displayBuffer; + +/// Pointer to the buffer that should currently be drawn to +videoPage* workBuffer; + +/// Flag indicating that the timer buffer should be flipped as soon as the +/// current frame is displayed +volatile bool videoFlipTimer = false; + + +// Timer counts to display each page for, plus off time +typedef struct timerInfo { + uint8_t counts[SHADES]; + uint8_t prescaler[SHADES]; +} timerInfo; + +// Double buffer the timing information, of course. +timerInfo* frontTimer; +timerInfo* backTimer; + +timerInfo* tempTimer; + +timerInfo timer[2]; + +// Record a slow and fast prescaler for later use +typedef struct prescalerInfo { + uint8_t relativeSpeed; + uint8_t TCCR2; +} prescalerInfo; + +// TODO: Generate these based on processor type and clock speed +prescalerInfo slowPrescaler = {1, 0x03}; +//prescalerInfo fastPrescaler = {32, 0x01}; +prescalerInfo fastPrescaler = {4, 0x02}; + +static bool initialized = false; + +/// Uncomment to set analog pin 5 high during interrupts, so that an +/// oscilloscope can be used to measure the processor time taken by it +//#define MEASURE_ISR_TIME +//#ifdef MEASURE_ISR_TIME +//uint8_t statusPIN = 19; +//#endif + +typedef struct LEDPosition { + uint8_t high; + uint8_t low; +} LEDPosition; + + +/* ----------------------------------------------------------------- */ +/** Table for LED Position in leds[] ram table + */ + +const LEDPosition ledMap[126] = { + {13, 5}, {13, 6}, {13, 7}, {13, 8}, {13, 9}, {13,10}, {13,11}, {13,12}, + {13, 4}, { 4,13}, {13, 3}, { 3,13}, {13, 2}, { 2,13}, + {12, 5}, {12, 6}, {12, 7}, {12, 8}, {12, 9}, {12,10}, {12,11}, {12,13}, + {12, 4}, { 4,12}, {12, 3}, { 3,12}, {12, 2}, { 2,12}, + {11, 5}, {11, 6}, {11, 7}, {11, 8}, {11, 9}, {11,10}, {11,12}, {11,13}, + {11, 4}, { 4,11}, {11, 3}, { 3,11}, {11, 2}, { 2,11}, + {10, 5}, {10, 6}, {10, 7}, {10, 8}, {10, 9}, {10,11}, {10,12}, {10,13}, + {10, 4}, { 4,10}, {10, 3}, { 3,10}, {10, 2}, { 2,10}, + { 9, 5}, { 9, 6}, { 9, 7}, { 9, 8}, { 9,10}, { 9,11}, { 9,12}, { 9,13}, + { 9, 4}, { 4, 9}, { 9, 3}, { 3, 9}, { 9, 2}, { 2, 9}, + { 8, 5}, { 8, 6}, { 8, 7}, { 8, 9}, { 8,10}, { 8,11}, { 8,12}, { 8,13}, + { 8, 4}, { 4, 8}, { 8, 3}, { 3, 8}, { 8, 2}, { 2, 8}, + { 7, 5}, { 7, 6}, { 7, 8}, { 7, 9}, { 7,10}, { 7,11}, { 7,12}, { 7,13}, + { 7, 4}, { 4, 7}, { 7, 3}, { 3, 7}, { 7, 2}, { 2, 7}, + { 6, 5}, { 6, 7}, { 6, 8}, { 6, 9}, { 6,10}, { 6,11}, { 6,12}, { 6,13}, + { 6, 4}, { 4, 6}, { 6, 3}, { 3, 6}, { 6, 2}, { 2, 6}, + { 5, 6}, { 5, 7}, { 5, 8}, { 5, 9}, { 5,10}, { 5,11}, { 5,12}, { 5,13}, + { 5, 4}, { 4, 5}, { 5, 3}, { 3, 5}, { 5, 2}, { 2, 5}, +}; + + +/* ----------------------------------------------------------------- */ +/** Constructor : Initialize the interrupt code. + * should be called in setup(); + */ +void LedSignInit(uint8_t mode) +{ +//#ifdef MEASURE_ISR_TIME +// pinMode(statusPIN, OUTPUT); +// digitalWrite(statusPIN, LOW); +//#endif + + float prescaler = 0.0; + +#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__) + TIMSK2 &= ~(1<<TOIE2); + TCCR2A &= ~((1<<WGM21) | (1<<WGM20)); + TCCR2B &= ~(1<<WGM22); + ASSR &= ~(1<<AS2); + TIMSK2 &= ~(1<<OCIE2A); + + if ((F_CPU >= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 + TCCR2B |= ((1<<CS21) | (1<<CS20)); + TCCR2B &= ~(1<<CS22); + prescaler = 32.0; + } else if (F_CPU < 1000000UL) { // prescaler set to 8 + TCCR2B |= (1<<CS21); + TCCR2B &= ~((1<<CS22) | (1<<CS20)); + prescaler = 8.0; + } else { // F_CPU > 16Mhz, prescaler set to 128 + TCCR2B |= (1<<CS22); + TCCR2B &= ~((1<<CS21) | (1<<CS20)); + prescaler = 64.0; + } +#elif defined (__AVR_ATmega8__) + TIMSK &= ~(1<<TOIE2); + TCCR2 &= ~((1<<WGM21) | (1<<WGM20)); + TIMSK &= ~(1<<OCIE2); + ASSR &= ~(1<<AS2); + + if ((F_CPU >= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 + TCCR2 |= (1<<CS22); + TCCR2 &= ~((1<<CS21) | (1<<CS20)); + prescaler = 64.0; + } else if (F_CPU < 1000000UL) { // prescaler set to 8 + TCCR2 |= (1<<CS21); + TCCR2 &= ~((1<<CS22) | (1<<CS20)); + prescaler = 8.0; + } else { // F_CPU > 16Mhz, prescaler set to 128 + TCCR2 |= ((1<<CS22) && (1<<CS20)); + TCCR2 &= ~(1<<CS21); + prescaler = 128.0; + } +#elif defined (__AVR_ATmega128__) + TIMSK &= ~(1<<TOIE2); + TCCR2 &= ~((1<<WGM21) | (1<<WGM20)); + TIMSK &= ~(1<<OCIE2); + + if ((F_CPU >= 1000000UL) && (F_CPU <= 16000000UL)) { // prescaler set to 64 + TCCR2 |= ((1<<CS21) | (1<<CS20)); + TCCR2 &= ~(1<<CS22); + prescaler = 64.0; + } else if (F_CPU < 1000000UL) { // prescaler set to 8 + TCCR2 |= (1<<CS21); + TCCR2 &= ~((1<<CS22) | (1<<CS20)); + prescaler = 8.0; + } else { // F_CPU > 16Mhz, prescaler set to 256 + TCCR2 |= (1<<CS22); + TCCR2 &= ~((1<<CS21) | (1<<CS20)); + prescaler = 256.0; + } +#endif + + LedSign_tcnt2 = 256 - (int)((float)F_CPU * 0.0005 / prescaler); + + // Record whether we are in single or double buffer mode + displayMode = mode; + videoFlipPage = false; + + // Point the display buffer to the first physical buffer + displayBuffer = &leds[0]; + + // If we are in single buffered mode, point the work buffer + // at the same physical buffer as the display buffer. Otherwise, + // point it at the second physical buffer. + if( displayMode & DOUBLE_BUFFER ) { + workBuffer = &leds[1]; + } + else { + workBuffer = displayBuffer; + } + + // Set up the timer buffering + frontTimer = &timer[0]; + backTimer = &timer[1]; + + videoFlipTimer = false; + LedSignSetBrightness(127); + + // Clear the buffer and display it + LedSignClear(0); + LedSignFlip(false); + + // Then start the display + TCNT2 = LedSign_tcnt2; +#if defined (__AVR_ATmega168__) || defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) || defined (__AVR_ATmega328P__) || (__AVR_ATmega1280__) + TIMSK2 |= (1<<TOIE2); +#elif defined (__AVR_ATmega128__) || defined (__AVR_ATmega8__) + TIMSK |= (1<<TOIE2); +#endif + + // If we are in double-buffer mode, wait until the display flips before we + // return + if (displayMode & DOUBLE_BUFFER) + { + while (videoFlipPage) { + _delay_ms(1); + } + } + + initialized = true; +} + + +/* ----------------------------------------------------------------- */ +/** Signal that the front and back buffers should be flipped + * @param blocking if true : wait for flip before returning, if false : + * return immediately. + */ +void LedSignFlip(bool blocking) +{ + if (displayMode & DOUBLE_BUFFER) + { + // Just set the flip flag, the buffer will flip between redraws + videoFlipPage = true; + + // If we are blocking, sit here until the page flips. + while (blocking && videoFlipPage) { + _delay_ms(1); + } + } +} + + +/* ----------------------------------------------------------------- */ +/** Clear the screen completely + * @param set if 1 : make all led ON, if not set or 0 : make all led OFF + */ +void LedSignClear(int set) { + int x, y; + for(x=0;x<14;x++) + for(y=0;y<9;y++) + LedSignSet(x,y,set); +} + + +/* ----------------------------------------------------------------- */ +/** Clear an horizontal line completely + * @param y is the y coordinate of the line to clear/light [0-8] + * @param set if 1 : make all led ON, if not set or 0 : make all led OFF + */ +void LedSignHorizontal(int y, int set) { + int x; + for(x=0;x<14;x++) + LedSignSet(x,y,set); +} + + +/* ----------------------------------------------------------------- */ +/** Clear a vertical line completely + * @param x is the x coordinate of the line to clear/light [0-13] + * @param set if 1 : make all led ON, if not set or 0 : make all led OFF + */ +void LedSignVertical(int x, int set) { + int y; + for(y=0;y<9;y++) + LedSignSet(x,y,set); +} + + +/* ----------------------------------------------------------------- */ +/** Set : switch on and off the leds. All the position #for char in frameString: + * calculations are done here, so we don't need to do in the + * interrupt code + */ +void LedSignSet(uint8_t x, uint8_t y, uint8_t c) +{ + uint8_t pin_high = ledMap[x+y*14].high; + uint8_t pin_low = ledMap[x+y*14].low; + // pin_low is directly the address in the led array (minus 2 because the + // first two bytes are used for RS232 communication), but + // as it is a two byte array we need to check pin_high also. + // If pin_high is bigger than 8 address has to be increased by one + + uint8_t bufferNum = (pin_low-2)*2 + (pin_high / 8) + ((pin_high > 7)?24:0); + uint8_t work = _BV(pin_high & 0x07); + + // If we aren't in grayscale mode, just map any pin brightness to max + if (c > 0 && !(displayMode & GRAYSCALE)) { + c = SHADES-1; + } + + int i; + for (i = 0; i < SHADES-1; i++) { + if( c > i ) { + workBuffer->pixels[i][bufferNum] |= work; // ON + } + else { + workBuffer->pixels[i][bufferNum] &= ~work; // OFF + } + } +} + + +/* Set the overall brightness of the screen + * @param brightness LED brightness, from 0 (off) to 127 (full on) + */ + +void LedSignSetBrightness(uint8_t brightness) +{ + // An exponential fit seems to approximate a (perceived) linear scale + float brightnessPercent = ((float)brightness / 127)*((float)brightness / 127); + uint8_t difference = 0; + + /* ---- This needs review! Please review. -- thilo */ + // set up page counts + // TODO: make SHADES a function parameter. This would require some refactoring. + int start = 15; + int max = 255; + float scale = 1.5; + float delta = pow( max - start , 1.0 / scale) / (SHADES - 1); + uint8_t pageCounts[SHADES]; + + pageCounts[0] = max - start; + uint8_t i; + for (i=1; i<SHADES; i++) { + pageCounts[i] = max - ( pow( i * delta, scale ) + start ); + } + //Serial.end(); + + if (! initialized) { + // set front timer defaults + int i; + for (i = 0; i < SHADES; i++) { + frontTimer->counts[i] = pageCounts[i]; + // TODO: Generate this dynamically + frontTimer->prescaler[i] = slowPrescaler.TCCR2; + } + } + + // Wait until the previous brightness request goes through + while( videoFlipTimer ) { + _delay_ms(1); + } + + // Compute on time for each of the pages + // Use the fast timer; slow timer is only useful for < 3 shades. + for (i = 0; i < SHADES - 1; i++) { + uint8_t interval = 255 - pageCounts[i]; + + backTimer->counts[i] = 255 - brightnessPercent + * interval + * fastPrescaler.relativeSpeed; + backTimer->prescaler[i] = fastPrescaler.TCCR2; + difference += backTimer->counts[i] - pageCounts[i]; + } + + // Compute off time + backTimer->counts[SHADES - 1] = 255 - difference; + backTimer->prescaler[SHADES - 1] = slowPrescaler.TCCR2; + + /* ---- End of "This needs review! Please review." -- thilo */ + + // Have the ISR update the timer registers next run + videoFlipTimer = true; +} + + +/* ----------------------------------------------------------------- */ +/** The Interrupt code goes here ! + */ +ISR(TIMER2_OVF_vect) { + DDRD = 0x0; + DDRB = 0x0; + //#ifdef MEASURE_ISR_TIME + // digitalWrite(statusPIN, HIGH); + //#endif + + // For each cycle, we have potential SHADES pages to display. + // Once every page has been displayed, then we move on to the next + // cycle. + + // 24 Cycles of Matrix + static uint8_t cycle = 0; + + // SHADES pages to display + static uint8_t page = 0; + + TCCR2B = frontTimer->prescaler[page]; + TCNT2 = frontTimer->counts[page]; + + if ( page < SHADES - 1) { + + if (cycle < 6) { + DDRD = _BV(cycle+2) | displayBuffer->pixels[page][cycle*2]; + PORTD = displayBuffer->pixels[page][cycle*2]; + + DDRB = displayBuffer->pixels[page][cycle*2+1]; + PORTB = displayBuffer->pixels[page][cycle*2+1]; + } else if (cycle < 12) { + DDRD = displayBuffer->pixels[page][cycle*2]; + PORTD = displayBuffer->pixels[page][cycle*2]; + + DDRB = _BV(cycle-6) | displayBuffer->pixels[page][cycle*2+1]; + PORTB = displayBuffer->pixels[page][cycle*2+1]; + } else if (cycle < 18) { + DDRD = _BV(cycle+2-12) | displayBuffer->pixels[page][cycle*2]; + PORTD = displayBuffer->pixels[page][cycle*2]; + + DDRB = displayBuffer->pixels[page][cycle*2+1]; + PORTB = displayBuffer->pixels[page][cycle*2+1]; + } else { + DDRD = displayBuffer->pixels[page][cycle*2]; + PORTD = displayBuffer->pixels[page][cycle*2]; + + DDRB = _BV(cycle-6-12) | displayBuffer->pixels[page][cycle*2+1]; + PORTB = displayBuffer->pixels[page][cycle*2+1]; + } + } + else { + // Turn everything off + DDRD = 0x0; + DDRB = 0x0; + } + + page++; + + if (page >= SHADES) { + page = 0; + cycle++; + } + + if (cycle > 24) { + cycle = 0; + + // If the page should be flipped, do it here. + if (videoFlipPage && (displayMode & DOUBLE_BUFFER)) + { + // TODO: is this an atomic operation? + videoFlipPage = false; + + videoPage* temp = displayBuffer; + displayBuffer = workBuffer; + workBuffer = temp; + } + + if (videoFlipTimer) { + videoFlipTimer = false; + + tempTimer = frontTimer; + frontTimer = backTimer; + backTimer = tempTimer; + } + } + + //#ifdef MEASURE_ISR_TIME + // digitalWrite(statusPIN, LOW); + //#endif +} diff --git a/Charliplexing.h b/Charliplexing.h new file mode 100644 index 0000000..3a912d6 --- /dev/null +++ b/Charliplexing.h @@ -0,0 +1,33 @@ +/* + Charliplexing.h - Library for controlling the charliplexed led board + from JimmiePRodgers.com + Created by Alex Wenger, December 30, 2009. + Modified by Matt Mets, May 28, 2010. + Released into the public domain. +*/ + +#ifndef Charliplexing_h +#define Charliplexing_h + +#include <stdint.h> +#include <stdbool.h> + +#define SINGLE_BUFFER 0 +#define DOUBLE_BUFFER 1 +#define GRAYSCALE 2 + +#define DISPLAY_COLS 14 // Number of columns in the display +#define DISPLAY_ROWS 9 // Number of rows in the display +#define SHADES 8 // Number of distinct shades to display, including black, i.e. OFF + +extern volatile unsigned int LedSign_tcnt2; + +void LedSignInit(uint8_t mode); +void LedSignSet(uint8_t x, uint8_t y, uint8_t c); +void LedSignSetBrightness(uint8_t brightness); +void LedSignFlip(bool blocking); +void LedSignClear(int set); +void LedSignHorizontal(int y, int set); +void LedSignVertical(int x, int set); + +#endif @@ -0,0 +1,167 @@ +#include "kbd.h" + +#include <avr/io.h> +#include <avr/interrupt.h> +#include <util/delay.h> + +#include <stdbool.h> +#include <stdint.h> + + +static volatile int8_t kbd_state = 0; + +static volatile uint8_t kbd_input = 0; +static volatile uint8_t kbd_output = 0; +static volatile uint8_t kbd_flags = 0; + + +static volatile uint8_t ts = 10; + + +static inline bool kbd_data() { + return (PINC & (1 << 0)); +} + +static inline bool kbd_clock() { + return (PINC & (1 << 1)); +} + + +static inline void kbd_clock_down() { + DDRC |= 0x02; + PORTC &= ~0x02; +} + +static inline void kbd_clock_in() { + DDRC &= ~0x02; + PORTC |= 0x02; +} + +static inline void kbd_data_set(bool state) { + if (state) + PORTC |= 0x01; + else + PORTC &= ~0x01; +} + +static inline void kbd_data_out() { + DDRC |= 0x01; +} + +static inline void kbd_data_in() { + DDRC &= ~0x01; + PORTC |= 0x01; +} + + +static inline void kbd_wait() { + while (kbd_state) {} +} + + +ISR(PCINT1_vect) { + if (kbd_clock()) + return; + + if (kbd_state < 0) { + if (kbd_state >= -8) { + kbd_data_set(kbd_output & _BV(-1-kbd_state)); + } + else if (kbd_state == -9) { + kbd_data_set(!(__builtin_popcount(kbd_output) & 1)); + } + else if (kbd_state == -10) { + kbd_data_in(); + } + else { + kbd_state = 0; + return; + } + + kbd_state--; + return; + } + + bool data = kbd_data(); + + if (kbd_state == 0) { + if (!data) { /* start bit */ + kbd_input = 0; + kbd_state++; + } + + return; + } + + if (kbd_state <= 8) { + if (data) + kbd_input |= _BV(kbd_state-1); + kbd_state++; + return; + } + + if (kbd_state == 9) { + if ((__builtin_popcount(kbd_input) & 1) == data) + kbd_flags |= KBD_FLAG_ERROR; + + kbd_state++; + return; + } + + kbd_state = 0; + + if (kbd_flags & KBD_FLAG_ERROR) { + /* Retry */ + return; + } + + if (kbd_input == 0xe0) { + kbd_flags |= KBD_FLAG_EXT; + return; + } + + if (kbd_input == 0xf0) { + kbd_flags |= KBD_FLAG_BREAK; + return; + } + + uint16_t code = kbd_input; + if (kbd_flags & KBD_FLAG_EXT) + code |= 0xe000; + + kbd_handle(code, !(kbd_flags & KBD_FLAG_BREAK)); + + kbd_flags = 0; +} + + +void kbd_send(uint8_t command) { + kbd_wait(); + + kbd_clock_down(); + _delay_us(100); + + kbd_data_out(); + kbd_data_set(false); + _delay_us(10); + + kbd_clock_in(); + + kbd_output = command; + kbd_state = -1; + + kbd_wait(); + + /* wait for ack */ + while (!kbd_state) {} + + kbd_wait(); +} + +void kbd_init(void) { + DDRC &= ~0x03; + PORTC |= 0x03; + + PCMSK1 = (1 << PCINT9); + PCICR = (1 << PCIE1); +} @@ -0,0 +1,25 @@ +#ifndef _AVR_KBD_H_ +#define _AVR_KBD_H_ + +#include <avr/io.h> +#include <stdint.h> +#include <stdbool.h> + +#define KBD_FLAG_ERROR (_BV(0)) +#define KBD_FLAG_BREAK (_BV(1)) +#define KBD_FLAG_EXT (_BV(2)) + +#define KBD_CODE_UP 0xe075 +#define KBD_CODE_LEFT 0xe06b +#define KBD_CODE_DOWN 0xe072 +#define KBD_CODE_RIGHT 0xe074 + +#define KBD_CMD_RESET 0xff + + +void kbd_handle(uint16_t code, bool make); + +void kbd_send(uint8_t command); +void kbd_init(void); + +#endif /* _AVR_KBD_H_ */ @@ -0,0 +1,199 @@ +#include "Charliplexing.h" +#include "kbd.h" + +#include <avr/io.h> +#include <avr/interrupt.h> +#include <util/delay.h> + + +typedef struct _point_t { + uint8_t x, y; +} point_t; + + +typedef enum _dir_t { + NORTH = 0, + WEST, + SOUTH, + EAST +} dir_t; + + +static volatile uint8_t dir; + +static point_t tail = {7, 4}; + +#define HISTORY_MAX 128 + +static uint8_t history[HISTORY_MAX >> 2]; +static uint8_t history_len = 0; +static uint8_t history_pos = 0; + +static point_t q; + +static uint16_t rand(void) { + static uint16_t lfsr = 0xace1; + unsigned bit; + + /* taps: 16 14 13 11; feedback polynomial: x^16 + x^14 + x^13 + x^11 + 1 */ + bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5)) & 1; + lfsr = (lfsr >> 1) | (bit << 15); + + return lfsr; +} + +static void rand_q(void) { + q.x = rand() % 14; + q.y = rand() % 9; +} + +static inline uint8_t rev(uint8_t d) { + return d ^ 2; +} + +static inline bool points_equal(const point_t *p1, const point_t *p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +static void move(uint8_t d, bool grow) { + uint8_t p = (history_pos+history_len) % HISTORY_MAX; + + uint8_t n = p >> 2, shift = (p & 3) << 1; + + history[n] &= ~(3 << shift); + history[n] |= d << shift; + + if (grow) + history_len++; + else + history_pos = (history_pos+1) % HISTORY_MAX; +} + +static uint8_t get_dir(uint8_t i) { + uint8_t p = (history_pos+i) % HISTORY_MAX; + + uint8_t n = p >> 2, shift = (p & 3) << 1; + + return (history[n] >> shift) & 3; +} + +static void reset(void) { + rand_q(); + + dir = EAST; + tail = (point_t){7, 4}; + history_len = 0; + history_pos = 0; + + unsigned i; + for (i = 0; i < 2; i++) + move(EAST, true); +} + +void kbd_handle(uint16_t code, bool make) { + if (!make) + return; + + switch(code) { + case KBD_CODE_UP: + dir = NORTH; + break; + + case KBD_CODE_LEFT: + dir = WEST; + break; + + case KBD_CODE_DOWN: + dir = SOUTH; + break; + + case KBD_CODE_RIGHT: + dir = EAST; + } +} + +static void go(point_t *point, uint8_t d) { + switch (d) { + case NORTH: + point->y = (point->y+8)%9; + break; + case WEST: + point->x = (point->x+13)%14; + break; + case SOUTH: + point->y = (point->y+1)%9; + break; + case EAST: + point->x = (point->x+1)%14; + } +} + +static bool point_used(const point_t *p, bool head) { + point_t pt = tail; + + unsigned i; + for (i = 0; i < history_len - (head ? 0 : 1); i++) { + go(&pt, get_dir(i)); + + if (points_equal(&pt, p)) + return true; + } + + return false; +} + +static void step(void) { + uint8_t d = dir; + + LedSignSet(tail.x, tail.y, 0); + + int i; + point_t pt = tail; + + LedSignSet(pt.x, pt.y, 0); + + for (i = 0; i < history_len; i++) { + go(&pt, get_dir(i)); + LedSignSet(pt.x, pt.y, 2); + } + + go(&pt, d); + LedSignSet(pt.x, pt.y, 3); + + if (point_used(&pt, true)) { + while(true) {} + } + + if (points_equal(&pt, &q)) { + rand_q(); + move(d, true); + } + else { + go(&tail, get_dir(0)); + move(d, false); + } + + if (point_used(&pt, false)) { + while(true) {} + } + + LedSignSet(q.x, q.y, 7); +} + +int main() { + LedSignInit(GRAYSCALE); + kbd_init(); + + reset(); + + sei(); + + kbd_send(KBD_CMD_RESET); + + while(true) { + step(); + _delay_ms(100); + } + + return 0; +} |