Code-Prüfung und Tipps?

Hallo,

nachfolgend mein erster "großer" Sketch, der auch tatsächlich irgendwo einen Sinn erfüllt...
Es handelt sich um einen ATTiny84, an den ein DS18S20 zur Temperaturmessung und ein einfacher 433 Mhz-Sender zur Übertragung angeschlossen ist. Alle 5 Minuten überträgt er das Temperatur-Signal und die Spannung der Stromversorgung per 433 Mhz.

Ich habe viel probiert und versucht, den Code zu vereinfachen und zu optimieren - auch habe ich mich ein wenig anderer Scripts (z.B. denen vom TinyTX-Projekt) bedient, und sie entsprechend angepasst eingefügt (nach Möglichkeit mit Quellenverweis).

Nun bin ich an einem Punkt, an dem ich den Sketch aufspielen und den Chip auf eine kleine Platine löten möchte.
Bevor ich das aber tue, wüsste ich gerne, ob man an meinem Code noch etwas verbessern oder vereinfachen kann - oder ob ich mir irgendwo grobe Schnitzer geleistet habe.
Habe irgendwie das gefühl: Wenn ich den Chip einmal irgendwo eingelötet habe, kriege ich den nie wieder "im Ganzen" raus :slight_smile:

Vielen Dank schonmal für eure Tipps!

#include <VirtualWire.h>
#include <OneWire.h>
#include <JeeLib.h>

ISR(WDT_vect) { 
  Sleepy::watchdogEvent(); 
}

// Die ID des Sensor-Nodes. Dienst zur Identifizierung der Daten.
int NODEID = 1;

// PIN, an den das DATA-Beinchen des Temperatur-Sensor angeschlossen ist
int DS18S20_DATA_Pin = 2;

// PIN, an den das VCC-Beinchen des Temperatur-Sensors angeschlossen ist
int DS18S20_VCC_Pin = 9;

// PIN, an den der 433 Mhz Sender angeschlossen ist
int TX_Pin = 3;

// Temperatur-Variable deklarieren
float temp1;

// Interval x 60 Sekunden = Schlafzeit zwischen Sensordaten-Übermittlung
int interval = 5;
int durchlauf = interval; // Zähler für Schleife; ist gleich 5, damit bei Start des ATTiny direkt ein Temperaturwert übermittelt wird

OneWire ds(DS18S20_DATA_Pin);

void setup()
{
  // VirtualWire und 433 Mhz-Sender initialisieren
  vw_setup(2000);                 // Bits per sec
  vw_set_tx_pin(TX_Pin);          // PIN, an dem der 433-Transmitter angeschlossen ist

  // Set the aref to the internal 1.1V reference
  analogReference(INTERNAL);  

  // VCC-Pin des DS18S20 als Ausgang setzen
  pinMode(DS18S20_VCC_Pin, OUTPUT);

  // Stromsparen: ADC deaktivieren
  ADCSRA &= ~ bit(ADEN); 
  bitSet(PRR, PRADC);
}

void loop()
{
  while(durchlauf < interval) {
    durchlauf = durchlauf +1;
    Sleepy::loseSomeTime(60000);
  }
  
  // DS18S20-Sensor einschalten
  digitalWrite(DS18S20_VCC_Pin, HIGH);
  // Kurze Delay zur Initialisierung
  delay(50);

  // Fix: Nach dem der DS18S20 mit Strom versorgt wurde, ist die erste Temperatur-Ausgabe 85.00
  // Die Temperatur wird so oft abgefragt, bis getTemp() nicht mehr 85.00 zurückliefert
  while(getTemp() == 85.00) {
    getTemp(); 
  }

  // Temperatur Temp1 auslesen und Vor/Nachkomma in zwei Variablen schreiben (int)
  // Float-Vars lassen sich nicht mit printf() auf Arduino ausgeben! Das ist ein Workaround!
  // http://forum.arduino.cc/index.php?topic=197900.msg1461205#msg1461205
  temp1=getTemp()*100;                  // z.B. 22,59 * 100

  // DS18S20 ausschalten
  digitalWrite(DS18S20_VCC_Pin, LOW);
  
  int temp1a =(int)temp1;                  // z.B. 2259
  int temp1VOR = temp1a/100;               // 22
  int temp1NACH = temp1a%100;              // 59
  
  // Temperatur-String zum Versand initialisieren
  char msg[20];
  
  // msg-Variable mit Daten zum Versand füllen, die später an das WebScript übergeben werden
  snprintf(msg, 20, "n=%d&t1=%d.%d&v=%d", NODEID,temp1VOR,temp1NACH,readVcc()) ;
  
  // Daten per VirtualWire-Lib versenden
  vw_send((uint8_t *)msg, strlen(msg));

  // Auf kompletten Versandt warten
  vw_wait_tx();

  // Den Durchlauf-Zähler auf 0 setzen, damit das Interval von vorne beginnt
  durchlauf = 0;

  // ATTiny schlafen legen
  // Sleepy::loseSomeTime(60000);
}

// getTemp() - Liest den Temperaturwert EINES angeschlossenen DS18S20-Sensor in Celsius aus
float getTemp(){
  byte data[12];
  byte addr[8];

  if ( !ds.search(addr)) {
    //no more sensors on chain, reset search
    ds.reset_search();
    return -1000;
  }

  ds.reset();
  ds.select(addr);
  ds.write(0x44,1); // start conversion, with parasite power on at the end

  byte present = ds.reset();
  ds.select(addr);
  ds.write(0xBE); // Read Scratchpad

  for (int i = 0; i < 9; i++) { // we need 9 bytes
    data[i] = ds.read();
  }

  ds.reset_search();

  byte MSB = data[1];
  byte LSB = data[0];

  float tempRead = ((MSB << 8) | LSB); // using two's compliment
  float TemperatureSum = tempRead / 16;

  return TemperatureSum;
}

// readVcc()-Lib von Nathan Chantrell
// Gibt die Spannung der Stromversorgung in mV aus
// https://github.com/nathanchantrell/TinyTX/blob/master/TinyTX_DS18B20/TinyTX_DS18B20.ino
long readVcc() {
  bitClear(PRR, PRADC); 
  ADCSRA |= bit(ADEN); // Enable the ADC
  long result;
  // Read 1.1V reference against Vcc
#if defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0); // For ATtiny84
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // For ATmega328
#endif
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;

  // Kalibrierung der VCC-Anzeige
  // http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
  // Abschnitt: Improving Accuracy
  // scale_constant = internal1.1Ref * 1023 * 1000
  // internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)
  // Default: 1125300L
  // Meine Konstante: 1070860L, errechnet mit 3x1,5V Batterien als VCC
  result = 1070860L / result; // Back-calculate Vcc in mV; Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  ADCSRA &= ~ bit(ADEN); 
  bitSet(PRR, PRADC); // Disable the ADC to save power
  return result;
}

Dafür gibt es IC-Sockel:
http://www.ebay.de/itm/10x-DIP-Sockel-IC-Fassung-8-polig-DIP8-/161022005942?pt=Bauteile&hash=item257da8d2b6

Sieht soweit ok aus :slight_smile:

Die Pin Definitionen kannst du als "const" definieren.

Um die Bits in den internen Registern zu setzen, machst du einerseits bitSet()/bitClear() und bei anderen direkt boolsche Logik. Da sollte man sich vielleicht eine Variante aussuchen und dabei bleiben. Ist aber nur eine Frage der Konsistenz und Leserlichkeit. Wenn es dich nicht stört kannst du es auch lassen :slight_smile:

Hi,
danke für die Antwort.
Habe vorhin nach diesen Sockeln auf REichelt gesucht; konnte sie aber nicht finden - wusste nämlich nicht mehr genau, wie sie heißen.
Hab dann gleich mal bei eBay 5 Stück mit 14 Pins für 2,20 € bestellt :slight_smile:

Macht es den einen Unterschied, ob ich die Pins per Const definiere? Also, wird dann z.B. weniger RAM o.ä. belegt?

Rein theoretisch landen const Variablen oft im Flash und nicht im RAM. Der Compiler kann sich aber auch darüber hinwegsetzten. Das ist mehr eine Empfehlung für die Code-Optimierungs Phase (und hängt damit auch davon ab mit welchem Level das compiliert wird). Hier hat das mal jemand auf dem Arduino getestet:
http://www.reddit.com/r/arduino/comments/1gkq8b/

Ansonsten ist es einfach ein Hinweis an den Programmierer, dass sich die Variable nicht ändert. Der Compiler hindert dich auch explizit daran wenn es mal aus Versehen geschieht.

Die Teile nennen sich IC Sockel, oder sind hier die teuren Teile gemeint, wo man mit dem kleinen Hebel arbeitet?

hi,

naja, teuer? ich hab' mir letztens 5 stück um 5€ aus chinaland geholt (40pin, 5€ für 5 stück, nicht pro stück...). sind schon recht praktisch.

gruß stefan

Das mit dem Hebel nennt sich ziff-sockel und ist erheblich teurer. Wird (oder sollte) allerdings nur für testaufbauten benutzt werden, da es weder spannungsfest noch vibrationssicher ist.

hi,

Das mit dem Hebel nennt sich ziff-sockel und ist erheblich teurer.

nö, ZIF-sockel kriegst Du 40-polig aus chinaland um 1€ versandkostenfrei. die qualität ist gut, das ding fühlt sich wertig an, mechanisch kein wackeln oder so.

gruß stefan

Ich benutze einen IC-Testclip, mit dem kannst du den aufgelöteten ATtiny dann auch noch umprogrammieren :slight_smile:

Wenn man für die Pin Definition anstatt const #define verwendet, wird doch noch weniger Speicher verbraucht, oder nicht?
Also z.B.: #define NODEID 1

IC-Test-Clip.jpg

hi,

was macht man mit so einem testclip? link?

Wenn man für die Pin Definition anstatt const #define verwendet, wird doch noch weniger Speicher verbraucht, oder nicht?

naja, Du mußt bedenken, daß der compiler optimiert.
wenn Du im programm schreibst:
c = 3 * 5;
wird im maschinencode gleich 15 drinstehen.
bei
const ABCDE = 5;
c = 3 * ABCDE;
wird im maschinencode auch gleich 15 drinstehen, weil der compiler weiß, daß ABCDE konstant ist.
und bei
#define ABCDE 5
wird vor dem kompilieren jedes ABCDE im programm durch 5 ersetzt (rein textmäßig), damit hast Du beim kompilieren wieder
c = 3 * 5;
drinstehen, damit gleicher effekt.

gruß stefan

Spexx:
Wenn man für die Pin Definition anstatt const #define verwendet, wird doch noch weniger Speicher verbraucht, oder nicht?
Also z.B.: #define NODEID 1

Defines sollte man vermeiden, es sei den man macht damit komplexere Funktionen. Und auch dafür gibt es inline Funktionen als bessere Alternative.

Das Problem damit ist, dass es eine einfache Text-Substitution auf Token-Basis ist. Der Compiler ersetzt vor dem Compilieren (ist schließlich eine Präprozessor Anweisung) einfach alle NODEID Tokens durch 1. Dadurch findet keinerlei Typ-Überprüfung statt und er ersetzt auch alle anderen Tokens damit, z.B. in String-Literalen. Das ist auch der Grund weshalb Konstanten per Konvention groß geschrieben werden werden.

Define ist ein Relikt aus der Anfangszeit von C. Const mit seiner jetzigen Bedeutung gibt es erst seit C89/ANSI-C und wurde von C++ übernommen.

Bzgl. dem Testclip, den kann man direkt an einen aufgelöteten µC klemmen (ähnlich wie eine Wäscheklammer :D) und somit die Kontakte abgreifen. Am anderen Ende kann man dann z.B. einen ISP-Programmer anschliessen und direkt auf der Platine flashen. So kann man sich einen Sockel sparen, wobei die ja auch nicht viel kosten. Testclips findet man z.B. bei eBay...

Das mit dem #define habe ich noch nicht ganz verstanden. Wenn ich folgendes schreibe:

#define LED 9
pinMode(LED, OUTPUT);

Dann wandelt der Compiler das um in pinMode(9, OUTPUT); Somit wird keine zusätzliche Variable benötigt (?).

Wenn ich nun das #define durch ein const ersetzte, wird dafür doch Speicher belegt? Oder macht das für den Compiler keinen Unterschied?

Es sei denn du weist die Adresse einer const Variablen einem Pointer zu, merkt der Compiler i.d.R. dass die Variable weg optimiert werden kann und ersetzt sie genauso wie bei #define durch einen festen Ausdruck ("immediate" Variable in Assembler). Jedenfalls bei globalen Variablen. Bei lokalen Variable ist es weniger sicher, allerdings beachten #defines überhaupt keine Scope Regeln.

Noch eine Option für typsichere Konstanten sind enums (jedenfalls typsicherer als #define. Von typsicher kann bei C enums nicht wirklich sprechen). Die landen auf jeden Fall im ROM.

Hier ist eine Erklärung dazu:

hi,

Bzgl. dem Testclip, den kann man direkt an einen aufgelöteten µC klemmen (ähnlich wie eine Wäscheklammer ) und somit die Kontakte abgreifen. Am anderen Ende kann man dann z.B. einen ISP-Programmer anschliessen und direkt auf der Platine flashen. So kann man sich einen Sockel sparen, wobei die ja auch nicht viel kosten. Testclips findet man z.B. bei eBay...

aahhh, raffiniertes kleines ding...

gruß stefan