ATtiny84 drop-in vervanger voor Parallax BASIC Stamp 1 voor gebruik in de Lego BASIC-Buggy uit 1999

De BASIC Stamp 1 van Parallax is een kleine single board computer met een PIC16C56A microcontroller die bij de introductie in 1992 vooral werd gebruikt om in studenten en hobbyisten bekend te maken met de mogelijkheden en de werking van microcontrollers. De BASIC Stamp 1 werd uitgebracht met een uitgebreide experimenteerhandleiding met veel toepassingsvoorbeelden.

De Parallax BASIC Stamp 1 (afbeelding Parallax)

Werken met de BASIC Stamp 1

De microcontroller was door Parallax voorzien van PBASIC 2.5, een BASIC dialect met taaluitbreidingen voor het gebruik van de hardwarevoorzieningen van de Stamp. Deze vroeger populaire maar nu wat in vergetelijkheid geraakte programmeertaal werd door Parallax goed ‘verkocht’, met omschrijvingen als “program execution is a single process from top to bottom, making it easy to follow and understand” en “the syntax is very clean and simple — no curly braces or semicolons required“, voorbijgaand aan in PBASIC missende concepten als functies en namespaces. Maar bonuspunten voor de poging deze retro programmeertaal weer wat op te vijzelen.

Schematische weergave van de Parallax BASIC Stamp 1 (illustratie Parallax)

De BASIC Stamp 1 bestaat uit een kleine printplaat van 35,8 x 10,2 mm, met hierop de volgende onderdelen:

  • PIC16C56A microcontroller in 20 pins SSOP behuizing
  • 93LC56 2K seriële EEPROM in 8 pins SOIC behuizing
  • TC54VN43 spanningsdetector in 3 pins SOT-23A behuizing
  • LM2936M-5 spanningsregulator in 8 pins SOIC behuizing
  • 4 MHz kristal
  • 4-voudige weerstandsarray
  • 15µF/10V elektrolytische condensator
  • 14 printpennen

Er zijn 8 I/O pinnen die voor diverse doeleinden kunnen worden gebruikt. De I/O pinnen zijn genummerd P0..P7 en worden ook zo in PBASIC geadresserd. Hiernaast zijn er twee aansluitingen voor de voedingsspanning: Vin voor spanningen tussen 5,5 V en 15 V en Vdd voor een vaste voedingsspanning van 5 V. Seriële communicatie vindt plaats via PCO en PCI. De stroomopname is 1 mA bij normaal gebruik en 25 µA in slaapstand.

Om de bruikbaarheid van PBASIC in te schatten een voorbeeldprogramma: met een op P7 aangesloten led kan deze met het volgende programma met tussenpauzen van 500 ms worden aan- en uitgeschakeld:

DO
  HIGH 7
  PAUSE 500
  LOW 7
  PAUSE 500
LOOP

Er is in de BASIC Stamp 1 ruimte voor in totaal 80 instructies, die in de 2K EEPROM worden opgeslagen. De verwerkingssnelheid is ongeveer 2000 instructies per seconde.

Lego BASIC-Buggy met BASIC Stamp 1

Ik heb een Lego BASIC-Buggy gemaakt, in 1999 door Elektuur gepubliceerd in haar aprilnummer. Het is het soort robotplatform dat ik in 1994 zelf had willen ontwerpen, als ik toen de beschikking had gehad over de Lego micromotoren en een BASIC Stamp 1. Als platform is het een uiterst minimalistisch geheel, met voldoende sensoren en actuatoren om het geheel ook als autonome robot een kans van slagen te geven. 80 instructies, de maximum capaciteit van de BASIC Stamp 1, is echter niet veel om een programma met een hoog ambitieniveau in te schrijven.

De Elektuur BASIC-Buggy met de BASIC Stamp 1

De ontwerper van de robot, Gerhard Nöcker, krijgt van mij de prijs voor het implementeren van precies de belangrijkste sensoren in een robot. Hoewel er met een beetje inspanning nog wel ruimte voor verbetering was geweest is er met het huidige hardwareplatform voldoende te beleven: tastsensoren in de vorm van twee microschakelaars, twee infraroodsensoren voor het meten van afstanden, twee lichtsensoren voor het vinden van lichtbronnen, een kleine pieper en alles gevoed met een 9 volt batterij, voldoende voor een paar uur actieradius. Het schema laat zien dat werkelijk alle I/O aansluitingen van de BASIC Stamp 1 worden benut:

Schema Elektuur BASIC-Buggy (schema Elektuur/Gerhard Nöcker)

Naast de BASIC Stamp 1 vinden we de volgende onderdelen:

  • Sharp IS471F OPIC Light Detector with Built-in Signal Processing Circuit for Light Modulation System
  • 78L05 spanningsregulator
  • L293D motor driver
  • 74HC04 6-voudige NOT-poort
  • 2 x lichtgevoelige weerstand
  • Buzzer

In het schema wordt de voedingsspanning aan Vin toegevoerd en wordt Vdd niet gebruikt. PCO en PCI worden naar buiten gevoerd middels een programmeerconnector. De RESET aansluiting wordt niet gebruikt. De I/O pinnen zijn als volgt toegepast:

  • P0, P1 en P2 zijn verbonden met de motorbesturing. P0 activeert hierbij beide motoren en P1 en P2 bepalen de richting ervan. Hoewel andere manieren mogelijk zijn, heeft de ontwerper bedacht dat P0 de snelheid van beide motoren regelt
  • P2 bestuurt de buzzer, met de in PBASIC beschikbare SOUND instructie
  • P3 en P4 zijn verbonden met de afstandsensoren, waarbij de sensoren worden ingeregeld om op ca. 20 centimeter afstand de pinnen logisch 1 te maken; de waarde hiervan wordt ingelezen met de in PBASIC beschikbare POT instructie
  • P5 en P6 zijn verbonden met de LDR’s en de waarde hiervan wordt ingelezen met de in PBASIC beschikbare POT instructie
  • P7 is verbonden met een weerstandsnetwerkje waarmee de stand van de microschakelaars wordt bepaald, eveneens met de POT instructie

De PIC16C56A microcontroller die op de BASIC Stamp 1 wordt gebruikt heeft geen ADC omzetters aan boord en gebruikt een andere manier om analoge signalen te bemonsteren. De PBASIC POT pin, schaal, variabele instructie geeft een variabele (0 – 255) terug die de hoeveelheid tijd weergeeft die nodig was om een condensator via een weerstand te ontladen. De pin moet worden aangesloten op één kant van de variabele weerstand, waarvan de andere kant via een condensator met aarde is verbonden. POT realiseert dit door de pin eerst voor 10 ms logisch hoog te maken zodat de condensator is geladen. De pin wordt dan als input ingesteld en POT start een timer. Als de RC-spanning beneden 1.4 V valt ziet de PIC16C56A dit als logisch laag en de timer wordt gestopt. De tijd wordt vervolgens vermenigvuldigd met schaal / 256 en de resulterende waarde wordt aan de variabele toegekend.

In het schema worden P3, P4, P5, P6 en P7 op deze manier toegepast. P0, P1 en P2 worden als digitale pinnen met PWM gebruikt.

Van BASIC Stamp 1 naar ATtiny84

De BASIC Stamp 1 is anno vandaag de dag niet meer heel praktisch, met een moeizame programmeerinterface en weinig programmageheugen. Ik voel veel meer voor een drop-in replacement met een ATtiny84 bijvoorbeeld, een 14-pins microcontroller met 12 I/O pennen en 8 kbyte aan programmageheugen.

Omdat de elektronische schakelingen die zijn verbonden met P3, P4, P5, P6 en P7 een afwijkend gebruik kennen, moet daar eerst een voorziening voor komen wil er sprake kunnen zijn van een drop-in replacement.

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();
}