Fotos der DIY Smartwatch

Wiedergeburt meiner DIY Smartwatch

Seit meinem Artikel zur DIY Smartwatch ist nun eine Zeit vergangen. Meine letzten Basteleien an der Firmware für den Trinket Pro führten zwar zu einer Akkulaufzeit (170mA) von 30 Stunden, aber die Version war nicht ganz fehlertolerant. Daher lief ich in den letzten Monaten mit einer übermalten Spongebob Uhr herum, die ich glaub ich 2006 mal aus den USA mitgebracht habe.

Nach ein paar Messungen fiel mir auf, dass das Display primär für den hohen Stromverbrauch verantwortlich ist, egal wie viele Pixel an sind. Der ATmega und das Bluetooth Low Energy 4.0 Modul brauchen nur sehr wenig. Der letzte Code und die letzte Logik der App hatte außerdem zur Folge, dass ich immer etwas auf die (durch die App einstellbar) letzten 150 Zeichen der letzten Nachrichten warten musste. Will ich die Uhrzeit und muss dann 5s darauf warten... das ist doof! Außerdem kam es durch die teilweise unzuverlässige Bluetooth Übertragung gerne mal zu einem Deadlock Aufgrund der sehr aggressiven Low Energy Funktionen des ATmega328 im Trinket Pro. Kurz: Es war einfach zu viel abgestellt um nach einer fehlerhaften Übertragung wieder auf zu wachen.

Die aktuelle Version der DIY Smartwatch Firmware ist wieder deutlich knapper im Code, weniger fehleranfällig und mit inzwischen 18 Stunden Akkulaufzeit (vielleicht noch länger?) sparsam genug. Als besonderen Bonbon zählt der ATmega nun die Sekunden selber, so dass man sofort eine Uhrzeit nach Knopfdruck hat. Drückt man etwas länger, wird die App am Handy aktiv und gleicht die Zeit ab. Bei dem Abgleich werden auch die letzten Nachrichten geholt und scrollen durchs Display der Smartwatch. Durch den "internen Chronometer" kann ich die Zeit auch von der Uhr bekommen, sollte die Verbindung zum Handy unterbrochen sein. Eine Messung ergab, dass nach 6 Stunden die Uhr um etwas weniger als 1 Minute vorgeht. Man kommt so nicht zu spät und wie gesagt: ein langer Tastendruck und man bekommt die aktuelle Zeit vom Handy wieder auf die Uhr kopiert. In dem Zustand ist die Firmware wieder brauchbar und kein Rückschritt. Die Akku-Anzeige und die Analog-Darstellung ist auch noch weiterhin als Feature vorhanden.

Das Design der Uhr erinnert etwas an eine Designstudie eines Schulpraktikanten Mitte der 80er Jahre, der bei Apple oder Commodore arbeitet. Die bunten Streifen, die früher auf den C64 Homecomputer waren und auch im alten Apple Symbol zeigten damals aus meiner Sicht die Parallelen: Es waren in beiden MOS 6502 Chips verbaut. Egal, ich finde die Streifen ganz nett, und da der ATmega328 auch ein 8-Bit Rechner mit 32kB (statt der C64 mit 64kB) Speicher ist, finde ich die Affinität gegeben.

Aktuell macht mir meine Android App, die die Nachrichten des Smartphones aufsammelt und per UART über Bluetooth an die Uhr sendet, etwas sorgen. Die ist nicht so ganz stabil beim ersten Starten und bekommt auch nicht immer neue Nachrichten mit.

Knackpunkte

Nach Spielereien mit Blinken und Vibrationsalarm verzichte ich darauf. Das kann das Handy gefälligst machen! Aber fühlt euch frei, das selber ein zu bauen! Jemand meinte, dass wäre doof mit dem Knopf. Er habe keine 2. Hand frei, wolle er die Uhrzeit ablesen. Tja: bei mir ist das anders! Ihr könnt ja mal nach bezahlbaren eInk Displays in der Baugröße suchen - ich finde das leuchtende Display bei Tastendruck einfach schöner.

Code

Und so schaut der Code aus: häßlich zusammengeschustert!

#include <SPI.h>
#include <SFE_MicroOLED.h>
#include <avr/power.h>
#include <avr/sleep.h>

#define PIN_RESET  9
#define PIN_DC     8
#define PIN_CS    10
#define DC_JUMPER  0
#define BUTTON     3
#define MESSAGEPOS 30

// display 64x48
MicroOLED oled(PIN_RESET, PIN_DC, PIN_CS);

String memoStr = "";
int vccVal     = 0;
int page       = 0;

int hours = 2;
int minutes = 43;
int seconds = 33;
int tick = 0;

// save power, because sin/cos is to "expensive"
const int xHour[] = {29,34,38,39,38,34,29,24,20,19,20,24,29};
const int yHour[] = {21,22,26,31,36,40,41,40,36,31,26,22,21};
const int xMin[]  = {29,30,32,33,34,35,37,38,39,40,40,41,41,42,
    42,42,42,42,41, 41,40,40,39,38,37,35,34,33,32,30,29,28,26,25,
    24,22, 21,20,19,18,18,17,17,16,16,16,16,16,17,17,18,18,19,20,
    21,23,24,25,26,28};
const int yMin[]  = {18,18,18,19,19,20,20,21,22,23,24,26,27,28,30,31,32,
    34,35,36,38,39,40,41,42,42,43,43,44,44,44,44,44,43,43,42,42,41,
    40,39,37,36,35,34,32,31,30,28,27,26,24,23,22,21,20,20,19,19,18,18};

int readVcc() {
  int result;
  power_adc_enable();
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  
  delay(10); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  result = ADCL; 
  result |= ADCH<<8; 
  result = 1126400L / result;
  power_adc_disable();
  return (result-2700)/26; // scale: 3310 -> 24, 2710 -> 0
}

void wakeUpNow() {}

void setup() {
  pinMode(BUTTON, INPUT);
  digitalWrite(BUTTON, HIGH);
  Serial.begin(9600);
  
  power_timer1_disable();
  power_timer2_disable();
  power_adc_disable();
  power_twi_disable();
  vccVal = readVcc();
  
  oled.begin();
  oled.clear(ALL); // Clear the display's internal memory logo
  oled.display();
  delay(1000);
  /*
  Serial.println("+++"); delay(250);
  Serial.println("ATE=0"); delay(250);
  Serial.println("AT+HWMODELED=BLEUART"); delay(250);
  Serial.println("AT+GAPDEVNAME=UART Notify Watch");
  delay(250); Serial.println("AT+BLEPOWERLEVEL=4");
  delay(250); Serial.println("ATZ");
  */
}

char umlReplace(char inChar) {
  if (inChar == -97) {
    inChar = 224; // ß
  } else if (inChar == -92) {
    inChar = 132; // ä
  } else if (inChar == -74) {
    inChar = 148; // ö
  } else if (inChar == -68) {
    inChar = 129; // ü
  } else if (inChar == -124) {
    inChar = 142; // Ä
  } else if (inChar == -106) {
    inChar = 153; // Ö
  } else if (inChar == -100) {
    inChar = 154; // Ü
  }
  return inChar;  
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();    
    if (inChar == -61) continue; // symbol before utf-8
    if (inChar == '\n') {
      page = 0;
      // besser lesbar MESSAGEPOS 30
      memoStr = "                              " + memoStr;
      continue;
    }
    memoStr += umlReplace(inChar);
  }
}

void printClock(int dx=0, int dy=0) {
    oled.circle(29+dx, 31+dy, 13);
    int hour = hours;
    if (hour>12) hour-=12;
    oled.line(29+dx, 31+dy, xMin[minutes]+dx, yMin[minutes]+dy);
    oled.line(29+dx, 31+dy, xHour[hour]+dx, yHour[hour]+dy);  
}

void ticktack() {
  tick++;
  if (tick > 9) {
    seconds += tick/10;
    tick = tick % 10;
    if (seconds > 59) {
      minutes += seconds / 60;
      seconds  = seconds % 60;
    }
    if (minutes > 59) {
      hours  += minutes / 60;
      minutes = minutes % 60;
    }
    if (hours > 23) {
      hours = hours % 24;
    }
  }
}

void loop() {
  delay(93);

  if (digitalRead(BUTTON) == LOW) {
    
    delay(500);
    tick += 5;
    
    if (digitalRead(BUTTON) == LOW) {
      oled.command(DISPLAYON);

      oled.clear(PAGE);
      oled.setCursor(8, 5);
      if (hours<10) oled.print("0");
      oled.print(hours);
      oled.print(":");
      if (minutes<10) oled.print("0");
      oled.print(minutes);
      oled.print(":");
      if (seconds<10) oled.print("0");
      oled.print(seconds);

      printClock();
      
      // Battery
      vccVal = readVcc();
      oled.pixel(61, 23);
      oled.pixel(62, 23);
      oled.rect(60, 24, 4, 24);
      oled.rectFill(60, 48-vccVal, 4, vccVal);

      oled.display();
            
      delay(1000);
      seconds++;
      
      if (digitalRead(BUTTON) == LOW) {
        memoStr = "";
        Serial.println( F("~") );
      } else {
        oled.command(DISPLAYOFF);
      }
    }
  }

  if (memoStr.length() > 0 && page <= memoStr.length()) {
    if (memoStr[MESSAGEPOS] == '#') {
      memoStr[MESSAGEPOS] = ' ';
      hours = memoStr.substring(MESSAGEPOS+1,MESSAGEPOS+3).toInt();
      minutes = memoStr.substring(MESSAGEPOS+4,MESSAGEPOS+6).toInt();
      seconds = memoStr.substring(MESSAGEPOS+7,MESSAGEPOS+9).toInt();
    }
    oled.clear(PAGE);
    // Battery
    oled.pixel(61, 23);
    oled.pixel(62, 23);
    oled.rect(60, 24, 4, 24);
    oled.rectFill(60, 48-vccVal, 4, vccVal);
    // message
    oled.setCursor(0, 0);
    oled.print(memoStr.substring(page));
    oled.display();
  }
  
  if (page == memoStr.length()) {
    oled.command(DISPLAYOFF);
    page++;
  } else {
    page++;
  }
  ticktack();
}

Update (15.9.2016)

Ich bin kurz davor, das Ganze in f-Droid zu stellen. Code, Schaltplan, Firmware und App sind daher nun etwas "professioneller" geworden.

Schaltplan
Schaltplan

2 Gedanken zu “Wiedergeburt meiner DIY Smartwatch

  1. Update: Ich dachte immer, dass die Probleme bei mehr als ca 120 Zeichen an der Bluetooth Übertragung lang. Dem ist nicht so! In der Version 3.1 konnte ich nun deutlich mehr Zeichen an die Smartwatch senden. Das Problem lang in der Implementierung des AVR Strings. String muss zur Laufzeit Platz schaffen für weitere Zeichen. Das scheint zusammen mit dem Seriellen-Empfang an seine Grenzen zu kommen. Nun hab ich alles mit einem Char-Array gemacht und ES TUT 😀

Schreibe einen Kommentar