Cleanup, reorganization

This commit is contained in:
2014-12-23 01:12:21 +01:00
parent e78d91a900
commit dbdcefea38
223 changed files with 1260 additions and 660 deletions
+69
View File
@@ -0,0 +1,69 @@
PRG = main
MCU_TARGET = attiny13
OPTIMIZE = 2
HZ = 9600000UL
LFUSE = 0x7A
HFUSE = 0xFF
DEFS =-std=gnu99 -funsigned-char -funsigned-bitfields -ffunction-sections -fpack-struct -fshort-enums -ffreestanding -fwhole-program -fno-inline-small-functions -fno-split-wide-types -fno-tree-scev-cprop -Wl,--relax,--gc-sections
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
OBJ = $(PRG).o
override CFLAGS = -g2 -Wall -O$(OPTIMIZE) -mmcu=$(MCU_TARGET) $(DEFS) -DF_CPU=$(HZ)
# program: $(PRG).elf lst hex
#
$(PRG).elf: $(OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
avr-size -C -d --mcu=$(MCU_TARGET) $(PRG).elf
lst: $(PRG).lst
%.lst: %.elf
$(OBJDUMP) -h -S $< > $@
hex: $(PRG).hex lst
build: hex ehex
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
wflash: hex
sudo avrdude -P usb -c dragon_isp -p $(MCU_TARGET) -U flash:w:$(PRG).hex
wfuses:
sudo avrdude -P usb -c dragon_isp -p $(MCU_TARGET) -U lfuse:w:$(LFUSE):m -U hfuse:w:$(HFUSE):m
weeprom: ehex
sudo avrdude -P usb -c dragon_isp -p $(MCU_TARGET) -U lfuse:w:$(LFUSE):m -U eeprom:w:$(PRG)_eeprom.hex
ee: ehex
eeprom: ehex
ehex: $(PRG)_eeprom.hex
%_eeprom.hex: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@
clean:
rm -f *.o $(PRG).elf *.hex *.lst *~
term:
sudo avrdude -P usb -c dragon_isp -p $(MCU_TARGET) -t
+139
View File
@@ -0,0 +1,139 @@
# Electronic Dice with AVR
The idea is pretty straightforward, hardware is simple, but it's still darn
cool!
The project is built around ATTiny13, but you can of course use any bigger chip
or even arduino, but it'll really be overkill. It's not making full use even of
the few peripherals the ATTiny13 provides!
## What it does?
Really simple, you have a couple LEDs that represent dots on a dice, and one
button.
When the button is down, the numbers quickly spin, and when you release
it, they slow down to a crawl, then the number blinks a few times and that's an
indication that your roll is complete.
When the dice is idle for 5 seconds, it goes to sleep (can be woken by pressing
the button). The delay can be customized in the source file.
## Hardware
The dice is not really challenging on the hardware side.
All you need is 7 LEDs with resistors, one button, and obviously the AVR.
You can also add a power switch, but it's not really needed, since the dice
is smart enough to sleep when it's not used.
It can be powered by any DC source, 3..5V. Three AA batteries in series can do
the trick, four AA's won't hurt anything as well - the chip is quite durable
if you don't overdo it.
Cunsumption when lights are on is (with my LEDs and 180R resistors) somewhat
around 50 mA, in sleep mode it's negligible (< 1 uA if my meter can be trusted).
### Pinout
```
+--u--+
RST --| |-- Vcc
SEG_DIAG2 : PB3 --| t13 |-- PB2 : SEG_DIAG1
SEG_HORIZ : PB4 --| |-- PB1 : SEG_DOT
GND --| |-- PB0 : BUTTON
+-----+
```
### Placement of the LED segments
One could think that using 7 pins for 7 leds is needed, but in fact, you can use
just four - which is great, since ATTiny13 has only 5 I/O pins (unless you use
the reset pin as I/O, but that's quite uncommon).
```
(DIAG1) (DIAG2)
(HORIZ) (DOT) (HORIZ)
(DIAG2) (DIAG1)
```
### Wiring of the segments
In the project, LEDs are connected by anodes to the pins. It's okay,
since the chip can handle it just fine, but you may want to connect them by
cathode instead (useful for greater loads).
To do so, adjust the `show()` routine accordingly (there's an explanatory
comment included). Basically, just negate the output states, and you're good.
```
DOT sengment:
PIN ---> LED -> RESISTOR ---> GND
Other segments:
PIN -+-> LED -> RESISTOR -+-> GND
| |
'-> LED -> RESISTOR -'
Button:
PIN --> BUTTON --> GND
```
## The program
Now that you have your dice hardware assembled, let's move on to the fun part -
the software.
You can of course just grab it from the repository, compile, flash and be done
with it, but I think it's useful to explain how it works.
Feel free to tinker with the source, add extra effects etc, it's fun!
### Generating random numbers
Firstly, we don't use any hardware entropy, or even a random number generator.
That may sound odd, but it's in fact not really needed.
The core idea is that the button is never pressed at the same time and for the
same duration. So, we can easily spin a counter when the button is down, and
when it's released, we pretty much already have our random number.
To add more randomness, there's also a second, "entropy" counter, that spins all
the time (unless, of course, the dice is sleeping), and when the button is
pushed, the value from this "entropy" counter is copied to the main counter.
The idea is that people can't cheat by somehow managing to press the button
for the exact same time.
Note, that those counters are just ints incrememnted and wrapped by the program
- we don't have to use of the hardware timers for this.
### Sleep mode
When the dice is inactive for five seconds (that is, no animation and
interaction), it automatically enters a POWER DOWN sleep mode.
Clearly, going to sleep without a way to wake up would be silly. Therefore, it
also enables a Pin Change Interrupt right before entering the sleep mode, so a
button press will wake it up. The cool thing is that it immediately starts
spinning the numbers and resumes normal operation.
Note, that the dice also starts into the sleep mode, so when you power it on,
it will sleep until you press the button. This is important, because I didn't
like the idea of showing the same number it starts - this way, you randomize it
right away by the first button press.
+281
View File
@@ -0,0 +1,281 @@
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
/**
* Fuses are defined in makefile.
*
* Led segments:
*
* (DIAG1) (DIAG2)
* (HORIZ) (DOT) (HORIZ)
* (DIAG2) (DIAG1)
*
*
* Wiring:
*
* +--u--+
* RST --| |-- Vcc
* SEG_DIAG2 : PB3 --| t13 |-- PB2 : SEG_DIAG1
* SEG_HORIZ : PB4 --| |-- PB1 : SEG_DOT
* GND --| |-- PB0 : BUTTON
* +-----+
*
* SEG: PIN -> LED -> RESISTOR -> GND
* BTN: PIN -> BUTTON -> GND
*
*/
// general macros
#define SECTION(pos) __attribute__((naked, used, section(pos)))
#define true 1
#define false 0
typedef uint8_t bool;
// individual segment pins
#define SEG_DOT _BV(PB1)
#define SEG_DIAG1 _BV(PB2)
#define SEG_DIAG2 _BV(PB3)
#define SEG_HORIZ _BV(PB4)
// button pin
#define PIN_BUTTON _BV(PINB0)
#define OUT_PINS (SEG_DOT | SEG_DIAG1 | SEG_DIAG2 | SEG_HORIZ)
#define IN_PINS (BUTTON)
// test if button is down
#define BUTTON_DOWN() ((PINB & PIN_BUTTON) == 0)
void init_io() SECTION(".init8");
/**
* Initialize IO ports.
*/
void init_io()
{
DDRB = OUT_PINS;
PORTB = PIN_BUTTON; // pullup
}
/**
* Consume interrupt used to wake from sleep
*/
ISR(PCINT0_vect)
{
// do nothing
}
/**
* Show dice segments.
*
* @param segments to show
*/
void show(uint8_t bits)
{
// PIN -> LED -> GND
PORTB = (PORTB & ~OUT_PINS) | (bits & OUT_PINS);
// VCC -> LED -> PIN
//PORTB = (PORTB & ~OUT_PINS) | ~(bits & OUT_PINS);
}
/**
* Show number using segments.
* 0=one, 5=six.
*
* @param number to show. MUST BE IN RANGE 0..5
*/
void show_number(const uint8_t number)
{
if(number > 5) return;
static const uint8_t num2seg[] = {
/*1*/ SEG_DOT,
/*2*/ SEG_DIAG1,
/*3*/ SEG_DIAG2 | SEG_DOT,
/*4*/ SEG_DIAG1 | SEG_DIAG2,
/*5*/ SEG_DIAG1 | SEG_DIAG2 | SEG_DOT,
/*6*/ SEG_DIAG1 | SEG_DIAG2 | SEG_HORIZ,
};
show( num2seg[number] );
}
/**
* Enter power down mode and wait for pin change to wake up.
*/
void go_sleep()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
GIMSK = _BV(PCIE);
PCMSK = _BV(PCINT0);
sleep_enable();
sei();
sleep_cpu();
cli();
sleep_disable();
GIMSK = 0;
PCMSK = 0;
sei();
}
// Delay between faces in slowing roll,
// after which the dice is stopped
#define DELAY_MS_FOR_STOP 600
// How long until dice enters sleep mode
#define IDLE_MS_FOR_SLEEP 5000
/**
* Main function
*/
void main()
{
// == VARS ==
// Used to randomize when button goes down
// Needed to prevent cheating and add more randomness
uint8_t entropy = 0;
// Number shown on dice (zero-based, 0 ... one dot)
uint8_t number = 0;
// Delay between face flip in roll
uint16_t delay_ms = DELAY_MS_FOR_STOP;
// Added to delay_ms each flip.
// Is increased to make slowing non-linear
uint16_t plus = 1;
// counts milliseconds when the dice was idle
// used to trigger sleep mode
uint16_t idle_counter = 0;
// flag that dice is either spinning or slowing down
bool rolling = false;
// == BRING IT ON! ==
// start by sleeping (avoid showing one default number)
go_sleep();
show_number(number);
while(1) {
// increment entropy counter
entropy++;
if(entropy > 5) entropy -= 6;
// grab button state
bool down = BUTTON_DOWN();
if(!down && !rolling) {
// dice idle
// increment idle counter
_delay_ms(5);
idle_counter += 5;
if(idle_counter > IDLE_MS_FOR_SLEEP) {
// go sleep
show(0);
go_sleep();
show_number(number);
idle_counter = 0;
}
continue;
} else {
// button down, or rolling
// reset idle counter
idle_counter = 0;
}
if(down) {
// button pressed
// start rolling
delay_ms = 1;
plus = 1;
if(!rolling) {
// was not rolling before
// apply entropy to number
number = entropy;
rolling = true;
}
_delay_ms(10); // delay to make iterating visible
} else {
// button not pressed
if(delay_ms >= DELAY_MS_FOR_STOP) {
// too slow flip, stop the rolling
rolling = false;
// blink
for(uint8_t i=0; i<4; i++) {
show(0);
_delay_ms(40);
show_number(number);
_delay_ms(40);
}
continue;
}
// slow down a bit
delay_ms += plus;
plus += 6;
}
// advance to next number
number++;
if(number > 5) number -= 6;
show_number(number);
// delay in rolling
for(uint16_t i=0; i < delay_ms; i++) {
_delay_ms(1);
// button pressed? Cancel delay.
if(BUTTON_DOWN())
break;
}
}
}
+153
View File
@@ -0,0 +1,153 @@
MCU = attiny13
F_CPU = 9600000
LFUSE = 0x72
HFUSE = 0xFF
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.) ##########
##########------------------------------------------------------##########
PROGRAMMER_TYPE = dragon_isp
PROGRAMMER_ARGS =
##########------------------------------------------------------##########
########## 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 -Os -I. -I$(EXTRA_SOURCE_DIR)
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
CFLAGS += -Wall -Wno-main
CFLAGS += -g2 -ggdb
CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--relax
CFLAGS += -std=gnu99
# 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)))
SRC = $(TARGET).c
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
%.hex: %.elf
$(OBJCOPY) -R .eeprom -O ihex $< $@
%.elf: $(SRC)
$(CC) $(CFLAGS) $(SRC) --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
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_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
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
set_default_fuses: fuses
+31
View File
@@ -0,0 +1,31 @@
Keyboard Lamp
=============
This is a keyboard lamp with adjustable brightness, that remembers last brightnbess level even when powered off.
I've made it as a replacement for my broken ThinkLight.
It uses ATtiny13, and the schematic is something like this:
Vcc
|
,---------------+
| |
| +----u----+ |
'--| |--'
| ATMEL | Buttons
,--[180R]-|<|---| |--BRT----o/ o---,
| LED2 | ATtiny | |
| -| 13 |--DIM----o/ o---+--- GND
GND | |
,--| |----,
| +---------+ | LED1
| | ,-|<|--[47R]-- Vcc
GND | |
'-[10k]-|< Transistor NPN
|
GND
LED1 is the bright white LED, LED2 is an indicator that the brightness has been saved. You can leave LED2 out if you want.
**Fuses** and other settings can be found in the Makefile.
+116
View File
@@ -0,0 +1,116 @@
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <stdint.h>
#include <stdbool.h>
#include "utils.h"
/**
* +--u--+
* RST --| |-- Vcc
* SAVE LED : PB3 --| t13 |-- PB2 : BTN: HIGH
* N.C. : PB4 --| |-- PB1 : BTN: LOW
* GND --| |-- PB0 : LED
* +-----+
*/
// pins config
#define PIN_LED PB0
#define PIN_LOW PB1
#define PIN_HIGH PB2
#define PIN_INDICATOR PB3
#define btn_low() (0 == get_bit(PINB, PIN_LOW))
#define btn_high() (0 == get_bit(PINB, PIN_HIGH))
/** Initialize IO ports. */
void SECTION(".init8") init_io(void)
{
// set pin directions
DDRB = _BV(PIN_LED) | _BV(PIN_INDICATOR);
// pullups
PORTB = _BV(PIN_LOW) | _BV(PIN_HIGH);
// initialize the timer
// Fast PWM mode, Output to OC0A
OCR0A = 32;
TCCR0A = _BV(WGM00) | _BV(WGM01) | _BV(COM0A1);
TCCR0B = _BV(CS00);
}
#define BRIGHTNESS_LEN 121
const uint8_t BRIGHTNESS[] PROGMEM = {
0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5,
6, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 13, 14, 14,
15, 16, 17, 18, 20, 21, 22, 24, 26, 27, 28, 30, 31, 32, 34,
35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 48, 49, 50, 52, 54,
56, 58, 59, 61, 63, 65, 67, 69, 71, 72, 74, 76, 78, 80, 82,
85, 88, 90, 92, 95, 98, 100, 103, 106, 109, 112, 116, 119,
122, 125, 129, 134, 138, 142, 147, 151, 156, 160, 165, 170,
175, 180, 185, 190, 195, 200, 207, 214, 221, 228, 234, 241,
248, 255
};
#define apply_brightness(level) OCR0A = pgm_read_byte( & BRIGHTNESS[level] )
/**
* Main function
*/
void main()
{
bool changed = 0;
bool changed_since_last_save = 0;
uint8_t savetimer = 0;
uint8_t level = eeprom_read_byte((uint8_t *) 0);
if (level >= BRIGHTNESS_LEN)
level = BRIGHTNESS_LEN - 1;
else if (level == 0)
level = BRIGHTNESS_LEN / 2;
apply_brightness(level);
while (1) {
if (btn_high() && level < BRIGHTNESS_LEN - 1) {
level++;
changed = 1;
}
if (btn_low() && level > 1) {
level--;
changed = 1;
}
if (changed) {
changed = 0;
savetimer = 0;
changed_since_last_save = 1;
apply_brightness(level);
}
_delay_ms(20);
savetimer++;
if (savetimer >= 50 && changed_since_last_save) {
changed_since_last_save = 0;
savetimer = 0;
sbi(PORTB, PIN_INDICATOR);
eeprom_update_byte((uint8_t*) 0, level);
_delay_ms(10);
cbi(PORTB, PIN_INDICATOR);
}
}
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
// general macros
#define SECTION(pos) __attribute__((naked, used, section(pos)))
// pin manipulation
#define sbi(port, bit) (port) |= _BV(bit)
#define cbi(port, bit) (port) &= ~ _BV(bit)
#define set_bit(port, bit, value) (port) = (((port) & ~_BV(bit)) | ((value) << (bit)))
#define get_bit(port, bit) (((port) >> (bit)) & 1)
#define bus_pulse(port, bit) { sbi(port, bit); cbi(port, bit); }