Like-O-Meter, ofwel in goed Nederlands ‘Meningteller’

Stel je voor, je loopt de zaal uit waar je net een presentatie hebt gevolgd en ziet op een tafeltje twee apparaten staan in het rood en groen. Beide hebben een verlichte knop aan de bovenkant, die uitnodigend pulserend verlicht is. Op het display zie je wat cijfers staan. Een poster aan de muur bevat de tekst ‘Evaluatie – Uw mening wordt op prijs gesteld’ en de kreet ‘Gebruik de Like-O-Meters hieronder’. Met een druk op de knop van de groene kolom kun je aangeven dat je de presentatie leuk vond, met een druk op de knop van de rode kolom geef je juist aan dat je het maar niks vond.

Motiverende poster voor het gebruik van de Meningtellers

Dit is wat ik voor ogen had toen ik met project Meningteller begon. De zichtbare functionaliteit ervan is relatief simpel: een verlichte schakelaar aan de bovenkant, een display dat bij iedere druk op de knop één optelt en een stevige behuizing die ook bij intensief gebruik overeind blijft. Ik heb ze ‘Like-O-Meter’ gedoopt, een knipoog naar de ‘Like‘ knop die je van Facebook kent (‘Vind ik leuk’). Maar ‘Meningteller’ is eigenlijk leuker, want Nederlands.

Omdat het project gestalte moest krijgen tussen lunch en diner heb ik in in huis rondgezocht om te zien wat er aan materialen zouden kunnen passen. Ik had recentelijk wat PVC materialen ingekocht voor een uitbreiding aan de badkamer en daar nog wat van overgehouden. Daar zat net voldoende materiaal bij om twee gesloten ‘bussen’ te maken. En PCV laat zich gemakkelijk boren en zagen, dat hielp in de besluitvorming.

PVC passtukken, ruimte voor display en schakelaar bovenkant uitgezaagd en in kleur gespoten

Het handgebouwde prototype van de Like-O-Meter is opgebouwd uit enkele PVC-delen van Martens, een Serieel led display, een led verlichte drukschakelaar en een ATtiny85. Het geheel is voorzien van een paar laagjes spuitlak van Motip.

Werken aan het prototype: in eerste instantie met een ATmega328 development board, daarna de software overzetten op een veel kleinere en energiezuiniger ATtiny85

Aan de softwarekant moest ik wat improviseren om het geheel met een ATtiny85 aan de gang te krijgen. De ATtiny is een kleine 8-pins microcontroller, met 6 pennen vrij te gebruiken voor I/O toepassingen, zoals het inlezen van de stand van een schakelaar, het activeren van een led of het serieel aansturen van een led display. Het led display gebruikt hiervan twee (I2C clock en I2C data), de schakelaar gebruikt er één en de led in de schakelaar ook één. Er zijn nog twee pennen vrij, voor bijvoorbeeld een rgbled in de schakelaar.

Eenvoud troef aan de binnenzijde van het prototype. De 9 volt batterijclip is nog niet met een batterij verbonden

Voor de I2C communicatie heb ik gebruik gemaakt van de TinyWireM library van Bro Hogan, die met 320 bytes een veel kleinere footprint heeft dan de AdaFruit led Backpack library. De ATtiny85 heeft ruimte voor 8K gecompileerde programmatuur, hiervan gebruikt de Like-O-Meter iets meer dan 4K. De Arduino ontwikkelomgeving 1.01 heeft een fout die het gebruik van meer ruimte dan 4K voorkomt, dat was nog wel een uitzoekklusje. De broncode bevat aanwijzigingen voor het hercompileren en de benodigde codebibliotheken.

Het gedrag van de Like-O-Meter is als volgt. Zodra de 9 volt accu wordt ingeschakeld (de onderkant schroeft eenvoudig van de behuizing en de accu zit met een clipje vast) toont het display vier streepjes en wordt de led in de schakelaar een seconde ontstoken. Hierna wordt een ‘0’ op het display getoond en is de led in de schakelaar uit. Iedere keer als de schakelaar wordt ingedrukt brandt de led en toont het display de tekst ‘PLUS’. Als de schakelaar weer wordt losgelaten toont het display opnieuw het getal, dat nu met één is verhoogd.

Proefgebruik van het prototype van de Like-o-Meter

Na ongeveer 15 minuten inactiviteit wordt het display wat gedimd en pulseert de led om de naderende slaapstand aan te kondigen. Een druk op de schakelaar levert het normale tel-er-een-bij-op gedrag en de slaapstand wordt onderbroken. Als er echter niet op de schakelaar wordt gedrukt dan gaat het display na een minuut uit. Het stroomverbruik is nu minimaal (ca. 7mA), maar de telstand wordt wel behouden. Als de schakelaar wordt ingedrukt dan wordt de tekst ‘AAN’ op het display getoond. Zodra de schakelaar nu wordt losgelaten dan toont het display de oude telstand en is de Like-O-Meter weer klaar voor gebruik. Om de telstand te wissen moet de accu (9 volt blokbatterij) worden losgekoppeld. Het stroomverbruik is sterk afhankelijk van het gebruik: met ‘8888’ op het display is de opgenomen stroom ongeveer 80mA. Bij regulier gebruik gaan de accu’s ongeveer 7 dagen mee.

Afgeronde prototypes in actie

De software is Public Domain, doe ermee wat je goed dunkt 🙂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
 * Like-O-Meter firmware 1.00
 * Copyright (c) 2013 Rudi Niemeijer
 * Placed in the Public Domain
 *
 * This is the firmware for the Like-O-Meter, a device that
 * acts as a counter with some light effects.
 *
 * A Like-O-Meter contains an Adafruit 4-digit 7-segment
 * LED backpack, button with built-in LED and an ATtiny85.
 *
 * In order to compile, this firmware requires the TinyWireM
 * library for I2C communication and the Tiny_LEDBackpack
 * library for the various LED display functions.
 *
 * TinyWireM library obtained from http://www.scenelight.nl/?wpfb_dl=22
 * Tiny_LEDBackpack obtained from https://github.com/millerlp/Tiny_LEDBackpack
 *
 * ATtiny pin configuration
 * Pin 1 - Not connected 
 * Pin 2 - Not connected (ATtiny PIN 3)
 * Pin 3 - Button (ATtiny PIN 4)
 * Pin 4 - GND
 * Pin 5 - SDA to LED backpack (grey)
 * Pin 6 - LED in button (ATtiny PIN 1 w. PWM)
 * Pin 7 - SCL to LED backpack (white)
 * Pin 8 - VCC/+5V
 *
 */
 
// These two libraries are essential
#include <TinyWireM.h>
#include <Tiny_LEDBackpack.h>
 
Tiny_7segment likeOMeterDisplay = Tiny_7segment();
int buttonPin = 4; // button that pulls the pin UP
int ledPin = 1; // indicator LED inside the button, pin 1 supports PWM
int buttonState; // current state of the button
int counterValue = 0; // current value for the counter
 
#define maxLEDBrightness 150 // maximum LED brightness 0..255
#define maxLEDBrightnessFade 75  // maximum LED brightness in fade mode
#define fadeSpeed 10 // increase LED brightness every fadeSpeed millis
#define napTimeOut 900000 // millis before sleep, 15 minutes
#define sleepTimeOut 960000 // millis before hybernation, 15 minutes + 60 seconds 
 
int currentLEDBrightness = 0;
int brighter = true;
unsigned long timeStampButtonPressed;
unsigned long timeStamp = 0;
 
#define i2c_addr 0x70 // stock address for Adafruit 7-segment LED backpack
 
void setup()
{
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  analogWrite(ledPin, maxLEDBrightness);
  likeOMeterDisplay.begin(i2c_addr); // initialize HT16K33 controller
  likeOMeterDisplay.clear(); // clear all digits on display
  likeOMeterDisplay.setBrightness(15); // set maximum display brightness
  likeOMeterDisplay.writeDigitRaw(0, 64);  // dash on position 0
  likeOMeterDisplay.writeDigitRaw(1, 64);  // dash on position 1
  likeOMeterDisplay.writeDigitRaw(3, 64);  // dash on position 3
  likeOMeterDisplay.writeDigitRaw(4, 64);  // dash on position 4
  likeOMeterDisplay.writeDisplay(); // push data to display
  delay(1000); // wait a second
  likeOMeterDisplay.clear(); // clear all digits on display
  displayInteger(counterValue);
  analogWrite(ledPin, 0); // disable the LED for visual verification
  timeStampButtonPressed = millis(); // simulate button pressed to prevent nap and sleep right from the beginning
}
 
void loop()
{
  buttonState = digitalRead(buttonPin);
 
  // NAP MODE TO ATTRACT ATTENTION
  if ((millis() > timeStampButtonPressed + napTimeOut) && (millis() > timeStamp + fadeSpeed))
  {
    timeStamp = millis();
    if (currentLEDBrightness == 0)
    {
      likeOMeterDisplay.setBrightness(0);
      likeOMeterDisplay.writeDisplay();
    }
    analogWrite(ledPin, currentLEDBrightness);
    if (brighter)
    {
      currentLEDBrightness++;
      if (currentLEDBrightness > maxLEDBrightnessFade)
      {
        currentLEDBrightness = maxLEDBrightnessFade;
        brighter = false;
      }
    } 
    else
    {
      currentLEDBrightness--;
      if (currentLEDBrightness < 0)
      {
        currentLEDBrightness = 0;
        brighter = true;
      }      
    }
  }
 
  // SLEEP MODE TO PREVENT BATTERY DEPLETION
  if ((currentLEDBrightness == 0) && (millis() > timeStampButtonPressed + sleepTimeOut)) // button not pressed for a very long time
  {
    // Sleep
    likeOMeterDisplay.clear(); // replace this with actual battery preserving code, like switching off
    likeOMeterDisplay.writeDisplay();
    digitalWrite(ledPin, 0);    
    while (!digitalRead(buttonPin)) // wait here until button is pressed
    {
    }
    // Wakeup
    likeOMeterDisplay.setBrightness(15);
    likeOMeterDisplay.writeDigitRaw(0, 119); // A on position 0
    likeOMeterDisplay.writeDigitRaw(1, 119); // A on position 1
    likeOMeterDisplay.writeDigitRaw(3, 55);  // N on position 3
    likeOMeterDisplay.writeDigitRaw(4, 0);  // blank on position 4
    likeOMeterDisplay.writeDisplay(); // push data to display
    delay(1000); // wait a second
    likeOMeterDisplay.clear(); // clear all digits on display
    displayInteger(counterValue);
    while (digitalRead(buttonPin)) // wait here until button is released
    {
    }
    timeStampButtonPressed = millis();
  }
 
  if (buttonState) // button is pressed
  {
    analogWrite(ledPin, maxLEDBrightness);
    likeOMeterDisplay.setBrightness(15);
    likeOMeterDisplay.writeDisplay();
    counterValue += 1;
    likeOMeterDisplay.clear();
    likeOMeterDisplay.writeDisplay(); 
    likeOMeterDisplay.writeDigitRaw(0,115); // P on position 0
    likeOMeterDisplay.writeDigitRaw(1,56);  // L on position 1
    likeOMeterDisplay.writeDigitRaw(3,62);  // U on position 3
    likeOMeterDisplay.writeDigitRaw(4,109); // S on position 4
    likeOMeterDisplay.writeDisplay();
    delay(250); // debounce time
    while (digitalRead(buttonPin)) // wait here until button is released
    {
    }
    delay(250); // debounce time
    displayInteger(counterValue);
    analogWrite(ledPin, 0);
    currentLEDBrightness = 0;
    brighter = true; // nap light is increasing first
    timeStampButtonPressed = millis(); // administer last time button pressed
  }
}
 
void displayInteger(int number)
{
  if (number == 0)
  {
      likeOMeterDisplay.writeDigitRaw(4, 63); // put a zero on the display
  } else
  {
    likeOMeterDisplay.print(number); // put the number on the display
  }
  likeOMeterDisplay.writeDisplay();
}