Implemented display turning off when battery is low
Implemented button handling
This commit is contained in:
parent
8fad31a338
commit
b54bb32e81
@ -1,3 +1,5 @@
|
||||
Schematics and PCB layout made using KiCad (http://kicad-pcb.org/).
|
||||
|
||||
ATTiny85's firmware is built using PlatformIO (http://platformio.org).
|
||||
ATTiny85's firmware is built using PlatformIO (http://platformio.org).
|
||||
|
||||
Before uploading the firmware I used the Arduino IDE and an "Arduino as ISP" to burn the 8 Mhz bootloader, you may get incorrect timing otherwise. I have not yet bothered to figure out how to do this with just PlatformIO.
|
197
Source/src/buttons.c
Normal file
197
Source/src/buttons.c
Normal file
@ -0,0 +1,197 @@
|
||||
#include "buttons.h"
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
|
||||
ButtonState buttonUp;
|
||||
ButtonState buttonDown;
|
||||
ButtonState buttonOption;
|
||||
|
||||
|
||||
void button_init(ButtonState* state)
|
||||
{
|
||||
(*state).flags = BSFLastReported;
|
||||
(*state).debounceTicks = 0;
|
||||
(*state).repeatTicks = 0;
|
||||
}
|
||||
|
||||
|
||||
void buttons_init()
|
||||
{
|
||||
button_init(&buttonUp);
|
||||
button_init(&buttonDown);
|
||||
button_init(&buttonOption);
|
||||
|
||||
// Input with internal pull-up resistor
|
||||
DDRB &= ~BUTTON_ALL;
|
||||
PORTB |= BUTTON_ALL;
|
||||
|
||||
// Mode 2: CTC
|
||||
TCCR0A = _BV(WGM01);
|
||||
|
||||
// Set prescaler to divide by 1024
|
||||
TCCR0B = _BV(CS02) | _BV(CS00);
|
||||
|
||||
// 10ms
|
||||
OCR0A = XTAL / 1024.0 * 10e-3 - 1;
|
||||
|
||||
// Enable timer interrupt
|
||||
TIMSK = _BV(OCIE0A);
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Since it's a pull-up, the value is low when the button is pressed.
|
||||
// This macro just helps make the code more readable.
|
||||
#define isPressed(state) (!state)
|
||||
|
||||
#define flagSet(flags, value) ((flags & value) != 0)
|
||||
#define setFlag(flags, value) flags |= (value)
|
||||
#define clearFlag(flags, value) flags &= ~(value)
|
||||
|
||||
|
||||
|
||||
void button_check(ButtonState* state, uint8_t readState)
|
||||
{
|
||||
#define stateRef (*state)
|
||||
|
||||
uint8_t lastState = flagSet(stateRef.flags, BSFLastReported);
|
||||
uint8_t newState = lastState;
|
||||
|
||||
// Debounce
|
||||
if (readState != lastState)
|
||||
{
|
||||
stateRef.debounceTicks++;
|
||||
if (stateRef.debounceTicks >= DEBOUNCE_TIME)
|
||||
{
|
||||
// Pressed / released
|
||||
newState = readState;
|
||||
|
||||
if (newState)
|
||||
setFlag(stateRef.flags, BSFLastReported);
|
||||
else
|
||||
clearFlag(stateRef.flags, BSFLastReported);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// State changed, reset debounce timer
|
||||
stateRef.debounceTicks = 0;
|
||||
}
|
||||
|
||||
|
||||
// Check new state
|
||||
if (isPressed(newState))
|
||||
{
|
||||
if (!isPressed(lastState))
|
||||
{
|
||||
// Pressed
|
||||
clearFlag(stateRef.flags, BSFRepeated | BSFReleased);
|
||||
setFlag(stateRef.flags, BSFPressed | BSFRepeatEnabled);
|
||||
stateRef.repeatTicks = REPEAT_START;
|
||||
}
|
||||
else if (flagSet(stateRef.flags, BSFRepeatEnabled))
|
||||
{
|
||||
// Held pressed
|
||||
stateRef.repeatTicks--;
|
||||
|
||||
if (stateRef.repeatTicks == 0)
|
||||
{
|
||||
stateRef.repeatTicks = REPEAT_NEXT;
|
||||
setFlag(stateRef.flags, BSFRepeated);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isPressed(lastState))
|
||||
{
|
||||
// Released
|
||||
setFlag(stateRef.flags, BSFReleased);
|
||||
}
|
||||
|
||||
#undef stateRef
|
||||
}
|
||||
|
||||
|
||||
|
||||
// every 10ms
|
||||
ISR(TIMER0_COMPA_vect)
|
||||
{
|
||||
uint8_t pinState = PINB;
|
||||
|
||||
button_check(&buttonUp, flagSet(pinState, _BV(BUTTON_UP)));
|
||||
button_check(&buttonDown, flagSet(pinState, _BV(BUTTON_DOWN)));
|
||||
button_check(&buttonOption, flagSet(pinState, _BV(BUTTON_OPTION)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline uint8_t readAndClearFlag(ButtonState* state, uint8_t flag)
|
||||
{
|
||||
uint8_t result;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_FORCEON)
|
||||
{
|
||||
result = flagSet((*state).flags, flag);
|
||||
(*state).flags &= ~(flag);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
uint8_t button_is_pressed(ButtonState* state)
|
||||
{
|
||||
return readAndClearFlag(state, BSFPressed);
|
||||
}
|
||||
|
||||
uint8_t button_is_released(ButtonState* state)
|
||||
{
|
||||
return readAndClearFlag(state, BSFReleased);
|
||||
}
|
||||
|
||||
uint8_t button_is_repeated(ButtonState* state)
|
||||
{
|
||||
return readAndClearFlag(state, BSFRepeated);
|
||||
}
|
||||
|
||||
|
||||
uint8_t button_is_pressed_or_repeated(ButtonState* state)
|
||||
{
|
||||
return readAndClearFlag(state, BSFPressed | BSFRepeated);
|
||||
}
|
||||
|
||||
|
||||
uint8_t button_is_released_short(ButtonState* state)
|
||||
{
|
||||
uint8_t result;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_FORCEON)
|
||||
{
|
||||
result = flagSet((*state).flags, BSFReleased) &&
|
||||
(!flagSet((*state).flags, BSFRepeated));
|
||||
|
||||
if (result)
|
||||
(*state).flags &= ~BSFReleased;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t button_is_pressed_long(ButtonState* state)
|
||||
{
|
||||
uint8_t result;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_FORCEON)
|
||||
{
|
||||
result = flagSet((*state).flags, BSFPressed) &&
|
||||
flagSet((*state).flags, BSFRepeated);
|
||||
|
||||
// Remove RepeatEnabled as well to prevent further repeat events
|
||||
if (result)
|
||||
(*state).flags &= ~(BSFPressed | BSFRepeated | BSFRepeatEnabled);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
90
Source/src/buttons.h
Normal file
90
Source/src/buttons.h
Normal file
@ -0,0 +1,90 @@
|
||||
#ifndef __Buttons
|
||||
#define __Buttons
|
||||
|
||||
#include <stdint.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
|
||||
// Some code borrowed from Peter Dannegger:
|
||||
// http://www.avrfreaks.net/forum/advanced-button-scan-routine
|
||||
//
|
||||
// While his version is optimized well, I found it hard to follow the logic
|
||||
// enough to customize it for my needs, so I went with a less optimized version.
|
||||
// It's good enough for this purpose.
|
||||
|
||||
// Last reported state
|
||||
#define BSFLastReported _BV(1)
|
||||
|
||||
// Event flags, cleared when polled
|
||||
#define BSFPressed _BV(2)
|
||||
#define BSFRepeated _BV(3)
|
||||
#define BSFReleased _BV(4)
|
||||
|
||||
// If not set repeats are disabled while the button is pressed
|
||||
#define BSFRepeatEnabled _BV(5)
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// Bitmask according to BSF defines above
|
||||
uint8_t flags;
|
||||
|
||||
// How many ticks the last state has been stable for
|
||||
uint8_t debounceTicks;
|
||||
|
||||
// How many ticks remaining for the next repeat event
|
||||
uint8_t repeatTicks;
|
||||
} ButtonState;
|
||||
|
||||
|
||||
extern ButtonState buttonUp;
|
||||
extern ButtonState buttonDown;
|
||||
extern ButtonState buttonOption;
|
||||
|
||||
|
||||
|
||||
// 8MHz
|
||||
#define XTAL 8e6
|
||||
|
||||
#ifndef BUTTON_UP
|
||||
#define BUTTON_UP PB1
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_DOWN
|
||||
#define BUTTON_DOWN PB3
|
||||
#endif
|
||||
|
||||
#ifndef BUTTON_OPTION
|
||||
#define BUTTON_OPTION PB4
|
||||
#endif
|
||||
|
||||
#define BUTTON_ALL (_BV(BUTTON_UP) | _BV(BUTTON_DOWN) | _BV(BUTTON_OPTION))
|
||||
|
||||
|
||||
// after 20ms (if the timer runs every 10ms, 20ms is the minimum)
|
||||
#define DEBOUNCE_TIME 2
|
||||
|
||||
// after 500ms
|
||||
#define REPEAT_START 50
|
||||
|
||||
// every 200ms
|
||||
#define REPEAT_NEXT 20
|
||||
|
||||
|
||||
extern void buttons_init();
|
||||
|
||||
|
||||
// Note: these functions act on events, not on the current state.
|
||||
// Calling them will reset the corresponding event and further calls
|
||||
// will return false until the event triggers again.
|
||||
|
||||
extern uint8_t button_is_pressed(ButtonState* state);
|
||||
extern uint8_t button_is_released(ButtonState* state);
|
||||
extern uint8_t button_is_repeated(ButtonState* state);
|
||||
|
||||
extern uint8_t button_is_pressed_or_repeated(ButtonState* state);
|
||||
|
||||
extern uint8_t button_is_released_short(ButtonState* state);
|
||||
extern uint8_t button_is_pressed_long(ButtonState* state);
|
||||
|
||||
#endif
|
@ -5,14 +5,17 @@
|
||||
#include <ssd1306xled.h>
|
||||
|
||||
#include "shared.h"
|
||||
#include "buttons.h"
|
||||
#include "screen/counter.h"
|
||||
|
||||
#define VccTreshold 3000
|
||||
|
||||
#define VccOffTreshold 3000
|
||||
#define VccOnTreshold 3100
|
||||
|
||||
|
||||
|
||||
// Forward declarations
|
||||
uint8_t hasPower();
|
||||
void checkPower();
|
||||
void waitForInput();
|
||||
|
||||
void handleCurrentScreen();
|
||||
@ -23,9 +26,12 @@ uint16_t readVCC();
|
||||
|
||||
int main()
|
||||
{
|
||||
buttons_init();
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (hasPower())
|
||||
checkPower();
|
||||
if (powerState == On)
|
||||
handleCurrentScreen();
|
||||
}
|
||||
|
||||
@ -40,43 +46,128 @@ void waitForInput()
|
||||
}
|
||||
|
||||
|
||||
uint8_t lastButton = 0;
|
||||
|
||||
void handleCurrentScreen()
|
||||
{
|
||||
handleCounterScreen();
|
||||
ssd1306_setpos(0, 0);
|
||||
|
||||
if (bit_is_set(PINB, BUTTON_DOWN))
|
||||
{
|
||||
ssd1306_string("released");
|
||||
}
|
||||
else
|
||||
{
|
||||
ssd1306_string("pressed ");
|
||||
}
|
||||
|
||||
char value[9];
|
||||
value[0] = (buttonDown.flags & BSFLastReported) ? '1' : '0';
|
||||
value[1] = (buttonDown.flags & BSFPressed) ? '1' : '0';
|
||||
value[2] = (buttonDown.flags & BSFRepeated) ? '1' : '0';
|
||||
value[3] = (buttonDown.flags & BSFReleased) ? '1' : '0';
|
||||
value[4] = (buttonDown.flags & BSFRepeatEnabled) ? '1' : '0';
|
||||
value[5] = '\0';
|
||||
|
||||
ssd1306_setpos(0, 1);
|
||||
ssd1306_string(&value[0]);
|
||||
|
||||
itoa(buttonDown.debounceTicks, value, 10);
|
||||
ssd1306_setpos(0, 2);
|
||||
ssd1306_string(&value[0]);
|
||||
|
||||
itoa(buttonDown.repeatTicks, value, 10);
|
||||
ssd1306_setpos(0, 3);
|
||||
ssd1306_string(&value[0]);
|
||||
|
||||
if (button_is_pressed(&buttonUp))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string("+");
|
||||
}
|
||||
else if (button_is_released(&buttonUp))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string(" ");
|
||||
}
|
||||
|
||||
if (button_is_pressed(&buttonDown))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string("-");
|
||||
}
|
||||
else if (button_is_released(&buttonDown))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string(" ");
|
||||
}
|
||||
|
||||
if (button_is_pressed(&buttonOption))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string("O");
|
||||
}
|
||||
else if (button_is_released(&buttonOption))
|
||||
{
|
||||
ssd1306_setpos(0, 5);
|
||||
ssd1306_string(" ");
|
||||
}
|
||||
_delay_ms(10);
|
||||
|
||||
//handleCounterScreen();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
uint8_t isOn = 0;
|
||||
uint8_t hasPower()
|
||||
void checkPower()
|
||||
{
|
||||
vcc = readVCC();
|
||||
|
||||
// Turn display off below 3v. It holds up surprisingly well, but at around
|
||||
// 1.5v it does corrupt the screen and requires reinitialization when the
|
||||
// voltage is turned back up.
|
||||
//
|
||||
// ...although by then the battery would be damaged, but still, turning off at
|
||||
// 3v means we're close enough to the recommended minimum of 2.7v for LiPo
|
||||
// batteries to be safe.
|
||||
//
|
||||
// TODO go into a sleep cycle until the battery is recharged
|
||||
if ((vcc > VccTreshold) != (isOn != 0))
|
||||
switch (powerState)
|
||||
{
|
||||
isOn = !isOn;
|
||||
if (isOn)
|
||||
{
|
||||
// Delay is required on power-on for the SSD1306 to initialize,
|
||||
// to be sure we're simply delaying every time it's reinitialized
|
||||
_delay_ms(40);
|
||||
ssd1306_init();
|
||||
}
|
||||
case On:
|
||||
// Turn display off below 3v. It holds up surprisingly well, but at around
|
||||
// 1.5v it does corrupt the screen and requires reinitialization when the
|
||||
// voltage is turned back up.
|
||||
//
|
||||
// ...although by then the battery would be damaged, but still, turning off at
|
||||
// 3v means we're still in the safe range when we go into battery saving mode
|
||||
// and the reinitialization afterwards prevents any issues.
|
||||
if (vcc < VccOffTreshold)
|
||||
{
|
||||
ssd1306_clear();
|
||||
powerState = BatteryLow;
|
||||
|
||||
ssd1306_clear();
|
||||
// TODO go into a sleep cycle until the battery is recharged
|
||||
_delay_ms(100);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BatteryLow:
|
||||
if (vcc > VccOnTreshold)
|
||||
{
|
||||
// Delay is required on power-on for the SSD1306 to initialize,
|
||||
// to be sure we're simply delaying every time it's reinitialized
|
||||
_delay_ms(40);
|
||||
ssd1306_init();
|
||||
ssd1306_clear();
|
||||
powerState = On;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO continue sleep cycle
|
||||
_delay_ms(100);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ManualOff:
|
||||
// TODO go into sleep mode
|
||||
_delay_ms(100);
|
||||
break;
|
||||
}
|
||||
|
||||
return isOn;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "shared.h"
|
||||
|
||||
PowerState powerState = BatteryLow;
|
||||
uint16_t vcc = 0;
|
||||
uint16_t counter = 0;
|
@ -3,6 +3,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum { On, ManualOff, BatteryLow } PowerState;
|
||||
|
||||
|
||||
extern PowerState powerState;
|
||||
extern uint16_t vcc;
|
||||
extern uint16_t counter;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user