Implemented display turning off when battery is low

Implemented button handling
This commit is contained in:
Mark van Renswoude 2017-08-02 16:17:23 +02:00
parent 8fad31a338
commit b54bb32e81
6 changed files with 413 additions and 28 deletions

View File

@ -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
View 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
View 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

View File

@ -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;
}

View File

@ -1,4 +1,5 @@
#include "shared.h"
PowerState powerState = BatteryLow;
uint16_t vcc = 0;
uint16_t counter = 0;

View File

@ -3,6 +3,10 @@
#include <stdint.h>
typedef enum { On, ManualOff, BatteryLow } PowerState;
extern PowerState powerState;
extern uint16_t vcc;
extern uint16_t counter;