Skip to content

Instantly share code, notes, and snippets.

@stecman
Last active February 18, 2020 13:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stecman/1216fa8a89d0c21be895cdd22b13a4fe to your computer and use it in GitHub Desktop.
Save stecman/1216fa8a89d0c21be895cdd22b13a4fe to your computer and use it in GitHub Desktop.
ATTiny13A Button Pusher - Remember how many times a button was pushed and push it electronically on startup
/*
* Button pusher with memory
*
* This is a simple program to push a button automatically after power-on.
* The controller watches for button presses and remembers how many times
* the button should be pushed next time a reset/power-on occurs.
*/
#include <avr/io.h>
#include <util/delay.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
// How many presses before the button loops back to the first option
#define MAX_PRESSES 8
// How long to wait after power/reset to simulate button presses
#define POWER_UP_DELAY_MS 600
// How long to pause between simulated button presses
#define TIME_BETWEEN_PRESSES_MS 100
// How long to hold each simulated button press
#define PRESS_LENGTH_MS 50
// Software debounce timeout for button reads
#define DEBOUNCE_TIME_MS 10
#define PLISTEN PB1
#define POUTPUT PB3
/**
* Simulate a single button press
* This pulls the target's button input low for a short time
*/
void simulate_press()
{
PORTB &= ~(1<<POUTPUT);
_delay_ms(PRESS_LENGTH_MS);
PORTB |= (1<<POUTPUT);
}
void simulate_presses(uint8_t count)
{
// Press button n times from memory
for (uint8_t i = 0; i < count; i++) {
simulate_press();
_delay_ms(TIME_BETWEEN_PRESSES_MS);
}
}
uint8_t load_saved_press_count()
{
uint8_t data = eeprom_read_byte((uint8_t*) 0);
if (data > MAX_PRESSES) {
data = 0;
}
return data;
}
inline void setup()
{
// Disable power to all peripherals
PRR = 0xFF;
DDRB &= ~_BV(PLISTEN); // target button as input
PORTB |= _BV(PLISTEN); // pull line high to prevent floating
DDRB |= _BV(POUTPUT); // button as output
PORTB |= _BV(POUTPUT); // Initial high output as target is active low
// Enable INT0 interrupt for waking and processing button presses
// Triggers on a falling edge
GIMSK |= (1<<INT0); // Enable INT0
MCUCR |= (1<<ISC01);
}
/**
* Put the MCU in the "power down" sleep mode
* This stops almost everything until another external interrupt occurs
*/
inline void power_off()
{
// Prepare for sleep
cli();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
// Go to sleep
sei();
sleep_cpu();
// Clean up after sleep
sleep_disable();
}
// Count of external button pushes that haven't been stored
volatile uint8_t pendingPresses = 0;
ISR (INT0_vect) {
_delay_ms(DEBOUNCE_TIME_MS);
// Process if we're still low after the debounce period
if ((PINB & (1<<PLISTEN)) == 0) {
pendingPresses++;
}
// Ignore any pending interrupt that occurred during debounce
GIFR |= (1<<INTF0);
}
int main(void)
{
setup();
// Running memory of number of button presses to make
uint8_t presses = load_saved_press_count();
uint8_t lastWritten = presses;
// Simulate button presses on external device
_delay_ms(POWER_UP_DELAY_MS);
simulate_presses(presses);
// Start listening for button presses
// Pending interrupts from us changing the PLISTEN line are cleared
GIFR |= (1<<INTF0);
sei();
for (;;) {
if (pendingPresses != 0) {
pendingPresses--;
presses = (presses + 1) % MAX_PRESSES;
}
// Write any change in value to EEPROM
else if (presses != lastWritten) {
cli();
eeprom_write_byte((uint8_t*) 0, presses);
lastWritten = presses;
sei();
// Wait for more button presses
// We don't handle wake-up interrupts very reliably, so this is a hack to not miss presses
_delay_ms(2000);
// Go to a low power state if there areno pending increments
if (pendingPresses == 0) {
power_off();
}
}
}
return 0;
}
DEVICE = attiny13a
CLOCK = 1200000
PROGRAMMER = -c dragon_isp
SOURCES = $(shell find . -name '*.c' -or -name '*.cpp' -or -name '*.S')
OBJECTS = $(SOURCES:.c=.o)
AVRDUDE = avrdude $(PROGRAMMER) -p t13
COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)
COMPILE += -I -I. -I./lib/
COMPILE += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
COMPILE += -ffunction-sections -fdata-sections -Wl,--gc-sections
COMPILE += -Wl,--relax -mcall-prologues
COMPILE += -std=gnu99
# symbolic targets:
all: $(SOURCES) main.hex
.c.o:
$(COMPILE) -c $< -o $@
.S.o:
$(COMPILE) -x assembler-with-cpp -c $< -o $@
# "-x assembler-with-cpp" should not be necessary since this is the default
# file type for the .S (with capital S) extension. However, upper case
# characters are not always preserved on Windows. To ensure WinAVR
# compatibility define the file type manually.
.c.s:
$(COMPILE) -S $< -o $@
flash: all
$(AVRDUDE) -U flash:w:main.hex:i
fuse:
# 1.2MHz, preserve EEPROM, BOD enable
echo "Fuse with:"
echo " -U lfuse:w:0x2a:m -U hfuse:w:0xfb:m"
# Xcode uses the Makefile targets "", "clean" and "install"
install: flash fuse
# if you use a bootloader, change the command below appropriately:
load: all
bootloadHID main.hex
clean:
find . -name '*.d' -exec rm {} +
find . -name '*.o' -exec rm {} +
rm -f main.hex main.elf
# file targets:
main.elf: $(OBJECTS)
$(COMPILE) -o main.elf $(OBJECTS)
main.hex: main.elf
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size --format=avr --mcu=$(DEVICE) main.elf
# If you have an EEPROM section, you must also create a hex file for the
# EEPROM and add it to the "flash" target.
# Targets for code debugging and analysis:
disasm: main.elf
avr-objdump -d main.elf
cpp:
$(COMPILE) -E main.c
@stecman
Copy link
Author

stecman commented Dec 5, 2017

More info about this project and schematics can be found on the Hackaday.io project page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment