cleaning, moved memgame to projects

This commit is contained in:
2014-12-24 13:05:37 +01:00
parent c96cdbe66d
commit f2cdba327c
19 changed files with 1163 additions and 984 deletions
+166
View File
@@ -0,0 +1,166 @@
MCU = atmega328p
F_CPU = 16000000
LFUSE = 0xFF
HFUSE = 0xDE
EFUSE = 0x05
MAIN = main.c
## If you've split your program into multiple files,
## include the additional .c source (in same directory) here
## (and include the .h files in your foo.c)
LOCAL_SOURCE =
## Here you can link to one more directory (and multiple .c files)
# EXTRA_SOURCE_DIR = ../AVR-Programming-Library/
EXTRA_SOURCE_DIR =
EXTRA_SOURCE_FILES =
##########------------------------------------------------------##########
########## Programmer Defaults ##########
########## Set up once, then forget about it ##########
########## (Can override. See bottom of file.) ##########
##########------------------------------------------------------##########
#19200
PROGRAMMER_TYPE = arduino
PROGRAMMER_ARGS = -b 57600 -P /dev/ttyUSB0
##########------------------------------------------------------##########
########## Makefile Magic! ##########
########## Summary: ##########
########## We want a .hex file ##########
########## Compile source files into .elf ##########
########## Convert .elf file into .hex ##########
########## You shouldn't need to edit below. ##########
##########------------------------------------------------------##########
## Defined programs / locations
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AVRSIZE = avr-size
AVRDUDE = sudo avrdude
## Compilation options, type man avr-gcc if you're curious.
CFLAGS = -std=gnu99 -mmcu=$(MCU) -DF_CPU=$(F_CPU)UL -I. -I$(EXTRA_SOURCE_DIR)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall -Wno-main -Wno-strict-prototypes -Wno-comment
CFLAGS += -g2 -Wextra -pedantic -Wfatal-errors
CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--relax
CFLAGS_BUILD = $(CFLAGS) -Os
# CFLAGS += -lm
## CFLAGS += -Wl,-u,vfprintf -lprintf_flt -lm ## for floating-point printf
## CFLAGS += -Wl,-u,vfprintf -lprintf_min ## for smaller printf
## Lump target and extra source files together
TARGET = $(strip $(basename $(MAIN)))
SRC1 = $(TARGET).c
SRC = $(SRC1)
EXTRA_SOURCE = $(addprefix $(EXTRA_SOURCE_DIR), $(EXTRA_SOURCE_FILES))
SRC += $(EXTRA_SOURCE)
SRC += $(LOCAL_SOURCE)
## List of all header files
HEADERS = $(SRC:.c=.h)
## For every .c file, compile an .o object file
OBJ = $(SRC:.c=.o)
## Generic Makefile targets. (Only .hex file is necessary)
all: $(TARGET).hex size
pre: $(TARGET).pre
%.hex: %.elf
$(OBJCOPY) -R .eeprom -O ihex $< $@
%.elf: $(SRC)
$(CC) $(CFLAGS_BUILD) $(SRC) --output $@
%.pre: $(SRC1)
$(CC) $(CFLAGS) -E $(SRC1) --output $@
%.eeprom: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@
debug:
@echo
@echo "Source files:" $(SRC)
@echo "MCU, F_CPU, BAUD:" $(MCU), $(F_CPU), $(BAUD)
@echo
# Optionally create listing file from .elf
# This creates approximate assembly-language equivalent of your code.
# Useful for debugging time-sensitive bits,
# or making sure the compiler does what you want.
disassemble: $(TARGET).lst
dis: disassemble
lst: disassemble
eeprom: $(TARGET).eeprom
%.lst: %.elf
$(OBJDUMP) -S $< > $@
# Optionally show how big the resulting program is
size: $(TARGET).elf
$(AVRSIZE) -C --mcu=$(MCU) $(TARGET).elf
clean:
rm -f $(TARGET).elf $(TARGET).hex $(TARGET).obj \
$(TARGET).o $(TARGET).d $(TARGET).eep $(TARGET).lst \
$(TARGET).lss $(TARGET).sym $(TARGET).map $(TARGET)~ \
$(TARGET).eeprom
squeaky_clean:
rm -f *.elf *.hex *.obj *.o *.d *.eep *.lst *.lss *.sym *.map *~
##########------------------------------------------------------##########
########## Programmer-specific details ##########
########## Flashing code to AVR using avrdude ##########
##########------------------------------------------------------##########
flash: $(TARGET).hex
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U flash:w:$<
flash_eeprom: $(TARGET).eeprom
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U eeprom:w:$<
terminal:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -nt
flash_arduino: PROGRAMMER_TYPE = arduino
flash_arduino: PROGRAMMER_ARGS =
flash_arduino: flash
flash_dragon_isp: PROGRAMMER_TYPE = dragon_isp
flash_dragon_isp: PROGRAMMER_ARGS =
flash_dragon_isp: flash
##########------------------------------------------------------##########
########## Fuse settings and suitable defaults ##########
##########------------------------------------------------------##########
## Generic
FUSE_STRING = -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m -U efuse:w:$(EFUSE):m
fuses:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) \
$(PROGRAMMER_ARGS) $(FUSE_STRING)
show_fuses:
$(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -nv
## Called with no extra definitions, sets to defaults
set_default_fuses: FUSE_STRING = -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m -U efuse:w:$(EFUSE):m
set_default_fuses: fuses
+31
View File
@@ -0,0 +1,31 @@
Color Memory Game
=================
This is a memory game (with pairs of cards), played on a WS2812B RGB LED strip.
The code is designed for *Arduino Pro Mini* (16 MHz), and can be flashed with avrdude (use the Makefile) using a USB-to-serial adapter.
Peripherals
-----------
- WS2812B RGB LED strip (or chained pixels) for the game board
- 4 buttons for navigation (arrows)
- 2 buttons for RESTART and SELECT
The LED strip should have a large capacitor (~ 1000uF) in parallel to the power supply, to avoid flickering.
It's connected over a resistor (470R will do fine) to the Arduino.
All pins can be adjusted in the `main.c` near the top, also the board size can be changed (for more than 15 card pairs, you'll have to add more colors).
Board
-----
The LEDs are numbered in this manner:
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
Set the board width and height in `main.c` to match your setup.
+6
View File
@@ -0,0 +1,6 @@
MightyPorsk's AVR utils library
===============================
This is my ever-evolving library. When I'm done with a project, I copy the current library to the project, so it doesn't break when I do further improvements.
Each library file contains a large comment block explaining it's function.
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <avr/io.h>
#include <stdbool.h>
#include "calc.h"
/** Initialize the ADC */
void adc_init()
{
ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0); // 128 prescaler -> 125 kHz
ADMUX |= _BV(REFS0); // Voltage reference
sbi(ADCSRA, ADEN); // Enable ADC
}
/** Sample analog pin with 8-bit precision */
uint8_t adc_read_byte(uint8_t channel)
{
write_low_nibble(ADMUX, channel); // Select channel to sample
sbi(ADMUX, ADLAR); // Align result to left
sbi(ADCSRA, ADSC); // Start conversion
while(bit_is_high(ADCSRA, ADSC)); // Wait for it...
return ADCH; // The upper 8 bits of ADC result
}
/** Sample analog pin with 10-bit precision */
uint16_t adc_read_word(uint8_t channel)
{
write_low_nibble(ADMUX, channel); // Select channel to sample
cbi(ADMUX, ADLAR); // Align result to right
sbi(ADCSRA, ADSC); // Start conversion
while(get_bit(ADCSRA, ADSC)); // Wait for it...
return ADCW; // The whole ADC word (10 bits)
}
@@ -0,0 +1,42 @@
#pragma once
/**
Pin definitions for Arduino (Pro Mini with ATmega328P)
*/
#include "pins.h"
#define D0 D,0
#define D1 D,1
#define D2 D,2
#define D3 D,3
#define D4 D,4
#define D5 D,5
#define D6 D,6
#define D7 D,7
#define D8 B,0
#define D9 B,1
#define D10 B,2
// MOSI MISO SCK - not good for input
#define D11 B,3
#define D12 B,4
#define D13 B,5
#define D14 C,0
#define D15 C,1
#define D16 C,2
#define D17 C,3
#define D18 C,4
#define D19 C,5
#define D20 C,6
#define D21 C,7
#define A0 C,0
#define A1 C,1
#define A2 C,2
#define A3 C,3
#define A4 C,4
#define A5 C,5
#define A6 C,6
#define A7 C,7
+46
View File
@@ -0,0 +1,46 @@
#pragma once
/**
General purpose calculation and bit manipulation utilities.
*/
// if max, go to zero. Else increment.
#define inc_wrap(var, min, max) do { if ((var) >= (max)) { (var)=min; } else { (var)++; } } while(0)
// If zero, go to max. Else decrement,
#define dec_wrap(var, min, max) do { if ((var) > min) { (var)--; } else { (var)=(max); } } while(0)
// === general bit manipulation with register ===
#define sbi(reg, bit) do { (reg) |= (1 << (uint8_t)(bit)); } while(0)
#define cbi(reg, bit) do { (reg) &= ~(1 << (uint8_t)(bit)); } while(0)
#define read_bit(reg, bit) ((((uint8_t)(reg)) >> (uint8_t)(bit)) & 0x1)
#define get_bit(reg, bit) read_bit(reg, bit)
#define bit_is_high(reg, bit) read_bit(reg, bit)
#define bit_is_low(reg, bit) !read_bit(reg, bit)
// Can't use bit_is_set, as it's redefined in sfr_def.h
#define write_bit(reg, bit, value) do { (reg) = ((reg) & ~(1 << (uint8_t)(bit))) | (((uint8_t)(value) & 0x1) << (uint8_t)(bit)); } while(0)
#define set_bit(reg, bit, value) write_bit(reg, bit, value)
#define toggle_bit(reg, bit) do { (reg) ^= (1 << (uint8_t)(bit)); } while(0)
// general pin manipulation - with pointer to register
#define sbi_p(reg_p, bit) do { (*(reg_p)) |= (1 << (uint8_t)(bit)); } while(0)
#define cbi_p(reg_p, bit) do { (*(reg_p)) &= ~(1 << (uint8_t)(bit)); } while(0)
#define read_bit_p(reg_p, bit) ((*(reg_p) >> (uint8_t)(bit)) & 0x1)
#define get_bit_p(reg_p, bit) read_bit_p(reg_p, bit)
#define write_bit_p(reg_p, bit, value) do { *(reg_p) = (*(reg_p) & ~(1 << ((uint8_t)(bit) & 0x1))) | (((uint8_t)(value) & 0x1) << (uint8_t)(bit)); } while(0)
#define set_bit_p(reg, bit, value) write_bit_p(reg_p, bit, value)
#define toggle_bit_p(reg_p, bit) do { *(reg_p) ^= (1 << (uint8_t)(bit)); } while(0)
#define write_low_nibble(reg, value) do { (reg) = ((reg) & 0xF0) | ((uint8_t)(value) & 0xF); } while(0)
#define write_high_nibble(reg, value) do { (reg) = ((reg) & 0x0F) | (((uint8_t)(value) & 0xF) << 4); } while(0)
// Check if value is in range A..B or B..A
#define in_range(x, low, high) (((low) < (high)) && ((x) > (low) && (x) < (high))) || (((low) > (high)) && ((x) < (low) || (x) > (high)))
// Check if value is in range A..B. If B < A, matches all outside B..A
#define in_range_wrap(x, low, high) (((low) < (high)) && ((x) > (low) && (x) < (high))) || (((low) > (high)) && ((x) > (low) || (x) < (high)))
+83
View File
@@ -0,0 +1,83 @@
#pragma once
/*
Some useful utilities for RGB color manipulation
The XXXc macros don't use cast, so they can be used in array initializers.
xrgb ... 3-byte true-color RGB (8 bits per component)
rgbXX ... XX-bit color value, with equal nr of bits per component
XX_r (_g, _b) ... extract component from the color, and convert it to 0..255
*/
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} xrgb_t;
typedef uint32_t rgb24_t;
typedef uint16_t rgb16_t;
typedef uint16_t rgb12_t;
typedef uint8_t rgb6_t;
#define xrgb(rr, gg, bb) ((xrgb_t)xrgbc(rr, gg, bb))
// xrgb for constant array declarations
#define xrgbc(rr, gg, bb) { .r = ((uint8_t)(rr)), .g = ((uint8_t)(gg)), .b = ((uint8_t)(bb)) }
#define xrgb_r(c) ((uint8_t)(c.r))
#define xrgb_g(c) ((uint8_t)(c.g))
#define xrgb_b(c) ((uint8_t)(c.b))
#define xrgb_rgb24(c) ((((rgb24_t)c.r) << 16) | (((rgb24_t)c.g) << 8) | (((rgb24_t)c.b)))
#define xrgb_rgb15(c) (((((rgb15_t)c.r) & 0xF8) << 7) | ((((rgb15_t)c.g) & 0xF8) << 2) | ((((rgb15_t)c.b) & 0xF8) >> 3))
#define xrgb_rgb12(c) (((((rgb12_t)c.r) & 0xF0) << 4) | ((((rgb12_t)c.g) & 0xF0)) | ((((rgb12_t)c.b) & 0xF0) >> 4))
#define xrgb_rgb6(c) (((((rgb6_t)c.r) & 0xC0) >> 2) | ((((rgb6_t)c.g) & 0xC0) >> 4) | ((((rgb6_t)c.b) & 0xC0) >> 6))
#define rgb24c(r,g,b) (((((rgb24_t)r) & 0xFF) << 16) | ((((rgb24_t)g) & 0xFF) << 8) | (((rgb24_t)b) & 0xFF))
#define rgb24(r,g,b) ((rgb24_t) rgb24(r,g,b))
#define rgb24_r(c) ((((rgb24_t) (c)) >> 16) & 0xFF)
#define rgb24_g(c) ((((rgb24_t) (c)) >> 8) & 0xFF)
#define rgb24_b(c) ((((rgb24_t) (c)) >> 0) & 0xFF)
#define rgb24_xrgb(c) xrgb(rgb24_r(c), rgb24_g(c), rgb24_b(c))
#define rgb24_xrgbc(c) xrgbc(rgb24_r(c), rgb24_g(c), rgb24_b(c))
#define rgb15(r,g,b) ((rgb16_t) rgb15c(r,g,b))
#define rgb15c(r,g,b) (((r & 0x1F) << 10) | ((g & 0x1F) << 5) | (b & 0x1F))
#define rgb15_r(c) ((((rgb15_t) (c)) & 0x7C00) >> 7)
#define rgb15_g(c) ((((rgb15_t) (c)) & 0x3E0) >> 2)
#define rgb15_b(c) ((((rgb15_t) (c)) & 0x1F) << 3)
#define rgb15_xrgb(c) xrgb(rgb15_r(c), rgb15_g(c), rgb15_b(c))
#define rgb15_rgb24(c) rgb24(rgb15_r(c), rgb15_g(c), rgb15_b(c))
#define rgb15_rgb24c(c) rgb24c(rgb15_r(c), rgb15_g(c), rgb15_b(c))
#define rgb12(r,g,b) ((rgb12_t) rgb12c(r,g,b))
#define rgb12c(r,g,b) (((r & 0xF) << 8) | ((g & 0xF) << 4) | (b & 0xF))
#define rgb12_r(c) ((((rgb12_t) (c)) & 0xF00) >> 4)
#define rgb12_g(c) (((rgb12_t) (c)) & 0xF0)
#define rgb12_b(c) (((r(rgb12_t) (c)gb) & 0x0F) << 4)
#define rgb12_xrgb(c) xrgb(rgb12_r(c), rgb12_g(c), rgb12_b(c))
#define rgb12_xrgbc(c) xrgbc(rgb12_r(c), rgb12_g(c), rgb12_b(c))
#define rgb12_rgb24(c) rgb24(rgb12_r(c), rgb12_g(c), rgb12_b(c))
#define rgb12_rgb24c(c) rgb24c(rgb12_r(c), rgb12_g(c), rgb12_b(c))
#define rgb6(r,g,b) ((rgb6_t) rgb6c(r,g,b))
#define rgb6c(r,g,b) (((r & 3) << 4) | ((g & 3) << 2) | (b & 3))
#define rgb6_r(c) ((((rgb6_t) (c)) & 0x30) << 2)
#define rgb6_g(c) ((((rgb6_t) (c)) & 0xC) << 4)
#define rgb6_b(c) ((((rgb6_t) (c)) & 0x3) << 6)
#define rgb6_xrgb(c) xrgb(rgb6_r(c), rgb6_g(c), rgb6_b(c))
#define rgb6_xrgbc(c) xrgbc(rgb6_r(c), rgb6_g(c), rgb6_b(c))
#define rgb6_rgb24(c) rgb24(rgb6_r(c), rgb6_g(c), rgb6_b(c))
#define rgb6_rgb24c(c) rgb24c(rgb6_r(c), rgb6_g(c), rgb6_b(c))
#define add_xrgb(x, y) ((xrgb_t) { (((y).r > (255 - (x).r)) ? 255 : ((x).r + (y).r)), (((y).g > (255 - (x).g)) ? 255 : ((x).g + (y).g)), (((y).b > 255 - (x).b) ? 255 : ((x).b + (y).b)) })
+104
View File
@@ -0,0 +1,104 @@
#pragma once
/**
An implementation of button debouncer.
First, the system must be initialized - even before including:
#define DEBO_CHANNELS 2
#define DEBO_TICKS 5
#inclue "lib/debounce.h"
A pin is registered like this:
#define BTN1 B,0
#define BTN2 B,1
debo_add(BTN0); // The function returns number assigned to the pin (0, 1, ...)
debo_add_rev(BTN1); // active low
debo_register(&PINB, PB2, 0); // direct access - register, pin & invert
Then periodically call the tick function (perhaps in a timer interrupt):
debo_tick();
To check if input is active, use
debo_get_pin(0); // state of input registered as #0
debo_get_pin(1); // state of input registered as #1
*/
#include <avr/io.h>
#include <stdbool.h>
#include "calc.h"
#include "pins.h"
// Number of pins to debounce
#ifndef DEBO_CHANNELS
# error "DEBO_CHANNELS not defined!"
#endif
#ifndef DEBO_TICKS
# warning "DEBO_TICKS not defined, defaulting to 5!"
# define DEBO_TICKS 5
#endif
/* Internal deboucer entry */
typedef struct {
PORT_P reg;
uint8_t bit;
uint8_t count;
} debo_slot_t;
/** Debounce data array */
debo_slot_t debo_slots[DEBO_CHANNELS];
uint8_t debo_next_slot = 0;
/** Define a debounced pin (must be IO!) */
#define debo_add_rev(io) debo_register(&io2pin(io_pack(io)), io2n(io_pack(io)), 1)
#define debo_add(io) debo_register(&io2pin(io_pack(io)), io2n(io_pack(io)), 0)
uint8_t debo_register(PORT_P reg, uint8_t bit, bool invert)
{
debo_slots[debo_next_slot] = (debo_slot_t){
.reg = reg,
.bit = bit | ((invert & 1) << 7) | (get_bit_p(reg, bit) << 6), // bit 7 = invert, bit 6 = state
.count = 0,
};
return debo_next_slot++;
}
/** Check debounced pins, should be called periodically. */
void debo_tick()
{
for (uint8_t i = 0; i < debo_next_slot; i++) {
// current pin value (right 3 bits, xored with inverse bit)
bool value = get_bit_p(debo_slots[i].reg, debo_slots[i].bit & 0x7);
if (value != get_bit(debo_slots[i].bit, 6)) {
// different pin state than last recorded state
if (debo_slots[i].count < DEBO_TICKS) {
debo_slots[i].count++;
} else {
// overflown -> latch value
set_bit(debo_slots[i].bit, 6, value); // set state bit
debo_slots[i].count = 0;
}
} else {
debo_slots[i].count = 0; // reset the counter
}
}
}
/** Get a value of debounced pin */
#define debo_get_pin(i) (get_bit(debo_slots[i].bit, 6) ^ get_bit(debo_slots[i].bit, 7))
//(get_bit(debo_slots[i].bit, 6) ^ get_bit(debo_slots[i].bit, 7))
+6
View File
@@ -0,0 +1,6 @@
#pragma once
/** Weird constructs for the compiler */
// general macros
#define SECTION(pos) __attribute__((naked, used, section(pos)))
+18
View File
@@ -0,0 +1,18 @@
#pragma once
/**
Functions for precise delays (nanoseconds / cycles)
*/
#include <avr/io.h>
#include <util/delay_basic.h>
#include <stdint.h>
/* Convert nanoseconds to cycle count */
#define ns2cycles(ns) ( (ns) / (1000000000L / (signed long) F_CPU) )
/** Wait c cycles */
#define delay_c(c) (((c) > 0) ? __builtin_avr_delay_cycles(c) : __builtin_avr_delay_cycles(0))
/** Wait n nanoseconds, plus c cycles */
#define delay_ns_c(ns, c) delay_c(ns2cycles(ns) + (c))
+107
View File
@@ -0,0 +1,107 @@
#pragma once
/**
This file provides macros for pin manipulation.
You can define your application pins like so:
// Led at PORTB, pin 1
#define LED B,1
// Switch at PORTD, pin 7
#define SW1 D,7
Now you can use macros from this file to wirh with the pins, eg:
as_output(LED);
as_input(SW1);
pullup_on(SW1);
toggle_pin(LED);
while (pin_is_low(SW1));
- The macros io2XXX() can be used to get literal name of register associated with the pin.
- io2n() provides pin number.
- The XXX_aux() macros are internal and should not be used elsewhere.
- The io_pack() macro is used to pass pin (io) to other macro without expanding it.
*/
#include <avr/io.h>
#include "calc.h"
// Get particular register associated with the name X (eg. D -> PORTD)
#define reg_ddr(X) DDR ## X
#define reg_port(X) PORT ## X
#define reg_pin(X) PIN ## X
#define io2ddr_aux(reg, bit) reg_ddr(reg)
#define io2ddr(io) io2ddr_aux(io)
#define io2port_aux(reg, bit) reg_port(reg)
#define io2port(io) io2port_aux(io)
#define io2pin_aux(reg, bit) reg_pin(reg)
#define io2pin(io) io2pin_aux(io)
#define io2n_aux(reg, bit) bit
#define io2n(io) io2n_aux(io)
#define io_pack(port, bit) port, bit
// pointer to port
typedef volatile uint8_t* PORT_P;
// number of bit in port
typedef uint8_t BIT_N;
// === pin manipulation ===
#define set_pin_aux(port, bit) sbi(reg_port(port), (bit))
#define clear_pin_aux(port, bit) cbi(reg_port(port), (bit))
#define read_pin_aux(port, bit) get_bit(reg_pin(port), (bit))
#define write_pin_aux(port, bit, value) set_bit(reg_port(port), (bit), (value))
#define toggle_pin_aux(port, bit) sbi(reg_pin(port), (bit))
#define pin_up(io) set_pin_aux(io)
#define pin_high(io) set_pin_aux(io)
#define pin_down(io) clear_pin_aux(io)
#define pin_low(io) clear_pin_aux(io)
#define get_pin(io) read_pin_aux(io)
#define read_pin(io) read_pin_aux(io)
#define pin_is_low(io) !read_pin_aux(io)
#define pin_is_high(io) read_pin_aux(io)
#define set_pin(io, value) write_pin_aux(io, (value))
#define write_pin(io, value) write_pin_aux(io, (value))
#define toggle_pin(io) toggle_pin_aux(io)
// setting pin direction
#define as_input_aux(port, bit) cbi(reg_ddr(port), (bit))
#define as_output_aux(port, bit) sbi(reg_ddr(port), (bit))
#define set_dir_aux(port, bit, dir) write_bit(reg_ddr(port), (bit), (dir))
#define as_input(io) as_input_aux(io)
#define as_input_pu(io) do { as_input_aux(io); pullup_enable_aux(io); } while(0)
#define as_output(io) as_output_aux(io)
#define set_dir(io, dir) set_dir_aux(io, (dir))
// setting pullup
#define pullup_enable_aux(port, bit) sbi(reg_port(port), (bit))
#define pullup_disable_aux(port, bit) cbi(reg_port(port), (bit))
#define set_pullup_aux(port, bit, on) write_bit(reg_port(port), (bit), (on))
#define pullup_enable(io) pullup_enable_aux(io)
#define pullup_on(io) pullup_enable_aux(io)
#define pullup_disable(io) pullup_disable_aux(io)
#define pullup_off(io) pullup_disable_aux(io)
#define set_pullup(io, on) set_pullup_aux(io, on)
+98
View File
@@ -0,0 +1,98 @@
#pragma once
/**
Utils for driving a WS2812 (WS2812B) RGB LED strips.
It's implemented as macros to avoid overhead when passing values, and to
enable driving multiple strips at once.
To avoid bloating your code, try to reduce the number of invocations -
compute color and then send it.
[IMPORTANT]
Some seemingly random influences can ruin the communication.
If you have enough memory, consider preparing the colors in array,
and sending this array using one of the "ws_send_XXX_array" macros.
*/
#include <avr/io.h>
#include "pins.h"
#include "nsdelay.h"
#include "colors.h"
/* Driver code for WS2812B */
// --- timing constraints (NS) ---
#ifndef WS_T_1H
# define WS_T_1H 700
#endif
#ifndef WS_T_1L
# define WS_T_1L 150
#endif
#ifndef WS_T_0H
# define WS_T_0H 150
#endif
#ifndef WS_T_0L
# define WS_T_0L 700
#endif
#ifndef WS_T_LATCH
# define WS_T_LATCH 7000
#endif
/** Wait long enough for the colors to show */
#define ws_show() do {delay_ns_c(WS_T_LATCH, 0); } while(0)
/** Send one byte to the RGB strip */
#define ws_send_byte(io, bb) do { \
for (volatile int8_t __ws_tmp = 7; __ws_tmp >= 0; --__ws_tmp) { \
if ((bb) & (1 << __ws_tmp)) { \
pin_high(io_pack(io)); delay_ns_c(WS_T_1H, -2); \
pin_low(io_pack(io)); delay_ns_c(WS_T_1L, -10); \
} else { \
pin_high(io_pack(io)); delay_ns_c(WS_T_0H, -2); \
pin_low(io_pack(io)); delay_ns_c(WS_T_0L, -10); \
} \
} \
} while(0)
/** Send R,G,B color to the strip */
#define ws_send_rgb(io, r, g, b) do { \
ws_send_byte(io_pack(io), g); \
ws_send_byte(io_pack(io), r); \
ws_send_byte(io_pack(io), b); \
} while(0)
/** Send a RGB struct */
#define ws_send_xrgb(io, xrgb) ws_send_rgb(io_pack(io), (xrgb).r, (xrgb).g, (xrgb).b)
/** Send color hex */
#define ws_send_rgb24(io, rgb) ws_send_rgb(io_pack(io), rgb24_r(rgb), rgb24_g(rgb), rgb24_b(rgb))
#define ws_send_rgb15(io, rgb) ws_send_rgb(io_pack(io), rgb15_r(rgb), rgb15_g(rgb), rgb15_b(rgb))
#define ws_send_rgb12(io, rgb) ws_send_rgb(io_pack(io), rgb12_r(rgb), rgb12_g(rgb), rgb12_b(rgb))
#define ws_send_rgb6(io, rgb) ws_send_rgb(io_pack(io), rgb6_r(rgb), rgb6_g(rgb), rgb6_b(rgb))
/** Send array of colors */
#define ws_send_xrgb_array(io, rgbs, length) __ws_send_array_proto(io_pack(io), (rgbs), (length), xrgb)
#define ws_send_rgb24_array(io, rgbs, length) __ws_send_array_proto(io_pack(io), (rgbs), (length), rgb24)
#define ws_send_rgb15_array(io, rgbs, length) __ws_send_array_proto(io_pack(io), (rgbs), (length), rgb15)
#define ws_send_rgb12_array(io, rgbs, length) __ws_send_array_proto(io_pack(io), (rgbs), (length), rgb12)
#define ws_send_rgb6_array(io, rgbs, length) __ws_send_array_proto(io_pack(io), (rgbs), (length), rgb6)
// prototype for sending array. it's ugly, sorry.
#define __ws_send_array_proto(io, rgbs, length, style) do { \
for (uint8_t __ws_tmp_sap_i = 0; __ws_tmp_sap_i < length; __ws_tmp_sap_i++) { \
style ## _t __ws_tmp_sap2 = (rgbs)[__ws_tmp_sap_i]; \
ws_send_ ## style(io_pack(io), __ws_tmp_sap2); \
} \
} while(0)
+403
View File
@@ -0,0 +1,403 @@
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// #include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "lib/meta.h"
#include "lib/arduino_pins.h"
#include "lib/calc.h"
#include "lib/colors.h"
#include "lib/ws2812.h"
#include "lib/adc.h"
#define DEBO_CHANNELS 6
#define DEBO_TICKS 1 // in 0.01s
#include "lib/debounce.h"
// #define BOARD_WIDTH 6
// #define BOARD_HEIGHT 5
#define BOARD_WIDTH 4
#define BOARD_HEIGHT 4
// number of cards
#define CARD_COUNT (BOARD_WIDTH * BOARD_HEIGHT)
// number of pairs
#define PAIR_COUNT (CARD_COUNT / 2)
// color palette
const xrgb_t COLORS[] = {
rgb24_xrgbc(0x00FF99), // emerald
rgb24_xrgbc(0x0000CC), // full blue
rgb24_xrgbc(0xFF00FF), // magenta
rgb24_xrgbc(0xFF0000), // red
rgb24_xrgbc(0xFF2B00), // orange
rgb24_xrgbc(0xFFFF00), // yellow
rgb24_xrgbc(0x0BEE00), // green
rgb24_xrgbc(0xFF6D00), // tangerine yellow/orange
rgb24_xrgbc(0x00CCCC), // cyan
rgb24_xrgbc(0x4400FF), // blue-purple
rgb24_xrgbc(0x5FBA00), // yellow-green
rgb24_xrgbc(0xD70053), // wine
rgb24_xrgbc(0xCD2B64), // brick
rgb24_xrgbc(0xED1B24), // firetruck red
rgb24_xrgbc(0xFF6D55), // salmon?
};
// assert valid board size
#if CARD_COUNT % 2 == 1
# error "Board size is not even!"
#endif
// Pin assignments (see pins.h)
// RGB LED strip data line
#define WS1 D10
// Buttons (to ground)
#define BTN_LEFT D2
#define BTN_RIGHT D3
#define BTN_UP D4
#define BTN_DOWN D5
#define BTN_SELECT D6
#define BTN_RESTART D7
// Debouncer channels for buttons
// (Must be added in this order to debouncer)
#define D_LEFT 0
#define D_RIGHT 1
#define D_UP 2
#define D_DOWN 3
#define D_SELECT 4
#define D_RESTART 5
// [ IMPORTANT ]
// Pin A0 must not be connected, it is used to get
// entropy for the random number generator
// Prototypes
void render();
void update();
void deal_cards();
/** Program initialization */
void SECTION(".init8") init()
{
// Randomize RNG
adc_init();
srand(adc_read_word(0));
// led strip data
as_output(WS1);
// gamepad buttons
as_input_pu(BTN_LEFT);
as_input_pu(BTN_RIGHT);
as_input_pu(BTN_UP);
as_input_pu(BTN_DOWN);
as_input_pu(BTN_SELECT);
as_input_pu(BTN_RESTART);
// add buttons to debouncer
debo_add_rev(BTN_LEFT);
debo_add_rev(BTN_RIGHT);
debo_add_rev(BTN_UP);
debo_add_rev(BTN_DOWN);
debo_add_rev(BTN_SELECT);
debo_add_rev(BTN_RESTART);
// setup timer
TCCR0A = _BV(WGM01); // CTC
TCCR0B = _BV(CS02) | _BV(CS00); // prescaler 1024
OCR0A = 156; // interrupt every 10 ms
sbi(TIMSK0, OCIE0A);
// prepare game board
deal_cards();
// enable timer interrupts (update & render)
sei();
}
/** Tile state enum */
typedef enum {
SECRET,
REVEALED,
GONE
} tilestate_t;
/** Tile struct */
typedef struct {
uint8_t color; // color index from COLORS[]
tilestate_t state; // state of the tile (used for render)
} tile_t;
// board tiles
tile_t board[CARD_COUNT];
/** Randomly place pairs of cards on the board */
void deal_cards()
{
// clear the board
for (uint8_t i = 0; i < CARD_COUNT; ++i) {
board[i] = (tile_t) { .color = 0, .state = GONE };
}
// for all pair_COUNT
for (uint8_t i = 0; i < PAIR_COUNT; ++i) {
// for both cards in pair
for (uint8_t j = 0; j < 2; j++) {
// loop until empty slot is found
while(1) {
uint8_t pos = rand() % CARD_COUNT;
if (board[pos].state == GONE) {
board[pos] = (tile_t) { .color = i, .state = SECRET };
break;
}
}
}
}
}
/** timer 0 interrupt vector */
ISR(TIMER0_COMPA_vect)
{
debo_tick(); // poll debouncer
update(); // update game state
render();
}
// player cursor position
uint8_t cursor = 0;
uint8_t animframe = 0;
bool hide_timeout_match;
uint8_t hide_timeout = 0;
// Game state
uint8_t tiles_revealed = 0;
uint8_t tile1;
uint8_t tile2;
// length of pulse animation (in 10ms)
#define F_ANIM_LEN 20
#define HIDE_TIME 100
// length of button holding before it's repeated (in 10ms)
#define BTNHOLD_REPEAT 20
uint8_t btn_hold_cnt[DEBO_CHANNELS];
/** Handle a button press event */
void button_click(uint8_t n)
{
switch (n) {
case D_UP:
if (cursor < BOARD_WIDTH) // first row
cursor += (CARD_COUNT - BOARD_WIDTH);
else
cursor -= BOARD_WIDTH;
break;
case D_DOWN:
if (cursor >= (CARD_COUNT - BOARD_WIDTH)) // last row
cursor -= (CARD_COUNT - BOARD_WIDTH);
else
cursor += BOARD_WIDTH;
break;
case D_LEFT:
if (cursor > 0) // last row
cursor--;
else
cursor = (CARD_COUNT - 1);
break;
case D_RIGHT:
if (cursor < (CARD_COUNT - 1)) // last row
cursor++;
else
cursor = 0;
break;
case D_SELECT:
if (tiles_revealed == 2) break; // two already shown
if (board[cursor].state != SECRET) break; // selected tile not secret
// reveal a tile
if (tiles_revealed < 2) {
board[cursor].state = REVEALED;
tiles_revealed++;
if(tiles_revealed == 1) {
tile1 = cursor;
} else {
tile2 = cursor;
}
}
// Check equality if it's the second
if (tiles_revealed == 2) {
hide_timeout_match = (board[tile1].color == board[tile2].color);
hide_timeout = HIDE_TIME;
}
break;
case D_RESTART:
deal_cards();
break;
}
}
/** Press arrow key, skip empty tiles */
void safe_press_arrow_key(uint8_t n)
{
// attempt to arrive at some secret tile
for (uint8_t j = 0; j < BOARD_HEIGHT; j++) {
for (uint8_t k = 0; k < BOARD_WIDTH; k++) {
button_click(n);
if (board[cursor].state != GONE) break;
}
if (board[cursor].state != GONE) break;
// traverse right since current column is empty
//
button_click(D_RIGHT);
}
}
#define is_arrow_key(id) ((id) == D_LEFT || (id) == D_RIGHT || (id) == D_UP || (id) == D_DOWN)
/** Update game (every 10 ms) */
void update()
{
// handle buttons (with repeating when held down)
for (uint8_t i = 0; i < DEBO_CHANNELS; i++) {
if (debo_get_pin(i)) {
if (btn_hold_cnt[i] == 0) {
if (is_arrow_key(i)) {
safe_press_arrow_key(i);
} else {
button_click(i);
}
}
// non-arrows wrap to 1 -> do not generate repeated clicks
inc_wrap(btn_hold_cnt[i], is_arrow_key(i) ? 1 : 0, BTNHOLD_REPEAT);
} else {
btn_hold_cnt[i] = 0;
}
}
// game logic - hide or remove cards when time is up
if (hide_timeout > 0) {
if (--hide_timeout == 0) {
if (hide_timeout_match) {
// Tiles removed from board
board[tile1].state = GONE;
board[tile2].state = GONE;
if (board[cursor].state == GONE) {
// move to some other tile
// try not to change row if possible
if ((cursor % BOARD_WIDTH) == (BOARD_WIDTH-1))
safe_press_arrow_key(D_LEFT);
else
safe_press_arrow_key(D_RIGHT);
}
} else {
// Tiles made secret again
board[tile1].state = SECRET;
board[tile2].state = SECRET;
}
tiles_revealed = 0; // no revealed
}
}
// Animation for pulsing the active color
inc_wrap(animframe, 0, F_ANIM_LEN * 2);
}
// LED off
#define BLACK rgb24_xrgb(0x000000)
// LED on - secret tile
#define WHITE rgb24_xrgb(0x555555)
// colors to be displayed
xrgb_t screen[CARD_COUNT];
/** Update screen[] and send to display */
void render()
{
// Prepare screen (compute colors)
for (uint8_t i = 0; i < CARD_COUNT; i++) {
// get tile color to show
switch (board[i].state) {
case SECRET:
screen[i] = WHITE;
break;
case REVEALED:
screen[i] = COLORS[board[i].color];
break;
default:
case GONE:
screen[i] = BLACK;
break;
}
// pulse active tile
if (i == cursor) {
uint16_t mult;
if (animframe < F_ANIM_LEN) {
mult = animframe;
} else {
mult = (F_ANIM_LEN * 2) - animframe;
}
screen[i] = (xrgb_t) {
.r = (uint8_t) ((((uint16_t) screen[i].r) * mult) / F_ANIM_LEN),
.g = (uint8_t) ((((uint16_t) screen[i].g) * mult) / F_ANIM_LEN),
.b = (uint8_t) ((((uint16_t) screen[i].b) * mult) / F_ANIM_LEN),
};
}
}
// Send to LEDs
ws_send_xrgb_array(WS1, screen, CARD_COUNT);
ws_show();
}
void main()
{
while(1); // Timer does everything
}