Manuel Jasch

Alles rund um Internet & Technik

Tea-Boy - James

Dez 202016

undefined

Für ein Weihnachtsgeschenk kam mir die spontane Idee eines Tea Boys. Dieser soll den Tee überwachen, nach abgelaufener Zeit Bescheid geben, ob der Tee fertig ist und den Teebeutel aus der Tasse ziehen.

Über einen Potentiometer soll dabei die Teezeit eingestellt werden und mit einem Taster der Tee-Beutel abgelassen werden. Nach abgelaufener Zeit soll der Beutel wieder aus der Tasse gezogen werden und mit einem Piezo darauf aufmerksam gemacht werden. Eine LED zeigt dabei die Status an.

Was ich für den Tea Boy benötige:

  • ATtiny
  • LED
  • Widerstände: 150 Ohm, 22 kOhm
  • 2 Micro Taster
  • Mini Servo
  • Potentiometer
  • Piezo

Zum Programmieren des ATtiny benötige ich:

  • Kondensator 10 uF, 16 V
  • Arduino Uno Board

Der ATtiny wird mithilfe eines in-system programmer (ISP) und eines Arduino Uno Board programmiert. Dafür wird der ATtiny, wie hier beschrieben, an das Arduino Uno Board angeschlossen. Der Kondensator muss dabei zwischen Masse und Reset gesteckt werden um zu verhindern, dass der Arduino reseted wird. Dann noch ein ISP Programm auf den Arduino installieren, das richtige Board auswählen und schon kann der ATtiny programmiert werden. Über die Entwicklungssoftware von Arduino kann noch die Frequenz des ATtinys ausgewählt werden. Ich habe bei diesem Projekt 8 Mhz gewählt. Nicht vergessen den Bootloader über die Arduino Software zu brennen. Dieser brennt nicht wirklich einen Bootloader sondern dieser setzt die Fuse-Bits, welche den internen Oszillator auf 8 Mhz wählt.

Der nächste Schritt ist die Programmierung. Dabei habe ich mich von dieser Seite inspirieren lassen, um einen Energiesparmodus zu ermöglichen. Der Autor verspricht dabei 0,12 µA im Standby, welcher aber nur durch einen Reset des Mikrocontrollers beendet werden kann. Nachgemessen wurde es nicht. Der Reset Pin wird dafür über einen 22 kOhm Widerstand auf die Versorgungsspannung und gleichzeitig über einen Taster auf Masse gelegt. Nun führt ein Tastendruck zum Reset des Mikrocontrollers.

Der zweite Taster, der den Tee-Vorgang starten soll, wird an A2 und an die Versorgungsspannung gelegt. Ein zusätzlicher Widerstand zieht den Pin auf Masse, falls der Taster nicht gedrückt ist. Der Tastendruck löst einen Interrupt aus, welcher den Potentiometer abfragt, den Servo-Arm herunter fährt und die angegebene Zeit verweilt.

Wenn der Tee fertig ist soll ein Piezo ertönen. Dafür habe ich mich für die hier beschriebene Methode entschieden. Eine andere Melodie ist dabei ohne Probleme möglich und kann in einem Array angegeben werden. Ich entschied mich für eine einfache Tonfolge.

Die Bauteile werden an die folgenden Pins des ATtiny angeschlossen:

undefined

LED D0 (0)
Servo D1 (1)
Reset Reset
Start A2 (4)
Poti A3 (3)
Piezo A1 (2)

undefined

Und das Programm natürlich:

#include <SoftwareServo.h>
#include <avr/sleep.h>           
#include <avr/interrupt.h>       

#define PIN_POTI  3 //A3
#define PIN_START 4 //A2
#define PIN_SERVO 1 //D1
#define PIN_LED   0 //D0
#define PIN_SPEAKER 2 //A1

#define SERVO_MIN 45
#define SERVO_MAX 135
#define SERVO_POSITIONING_TIME 1600  //in ms

#define MAX_TEA_TIME 780000 // 780 000 = 13 min ; 600 000 ms = 10 min
#define SLEEP_AFTER 20000   //go to sleep after this time (in ms 30 000 ms = 30 s)

#define WAKE_UP_BLINK 2 //wake up or reset
#define WAKE_UP_FREQUENCY 400 //in ms

#define TEA_START_BLINK 3
#define TEA_START_FREQUENCY 300

#define END_BREWING_SHAKING 3 //number of shaking the teabag
#define END_BREWING_SHAKE_DISTANCE 10 //in degree
#define END_BREWING_SHAKE_POSITIONING_TIME 300 //in ms

#define TEA_FINISHED_BLINK 7

#define BODS 7                   //BOD Sleep bit in MCUCR
#define BODSE 2                  //BOD Sleep enable bit in MCUCR

// DEFINITION OF TONES  ==========================================
//       note, period, &  frequency. 
#define  c     3830    // 261 Hz 
#define  d     3400    // 294 Hz 
#define  e     3038    // 329 Hz 
#define  f     2864    // 349 Hz 
#define  g     2550    // 392 Hz 
#define  a     2272    // 440 Hz 
#define  b     2028    // 493 Hz 
#define  C     1912    // 523 Hz 
#define  R     0       // to represent a rest
int melody[] = {  c, R, c, R};
int beats[]  = { 32, 32, 32, 128}; // 32 => 320ms

int MAX_COUNT = sizeof(melody) / 2; // Melody length, for looping. (2 byte)
long tempo = 10000; // Set overall tempo
int pause = 2000; // // Set length of pause between notes
// Loop variable to increase Rest length
int rest_count = 100; //<-BLETCHEROUS HACK; See NOTES
// Initialize core variables
int tone_ = 0;
int beat = 0;
long duration  = 0;

//GLOBAL VARIABLES ==============================================
float tea_time, elapsed_time;
bool start, tea_brewing;
uint32_t start_time; 
uint8_t mcucr1, mcucr2, led_brightness, i;
SoftwareServo servo;

// PLAY TONE  ===================================================
void playTone() {
  long elapsed_time = 0;
  if (tone_ > 0) { // if this isn't a Rest beat, while the tone has 
    //  played less long than 'duration', pulse speaker HIGH and LOW
    while (elapsed_time < duration) {
      digitalWrite(PIN_SPEAKER,HIGH);
      delayMicroseconds(tone_ / 2);
      digitalWrite(PIN_SPEAKER, LOW);
      delayMicroseconds(tone_ / 2);
      // Keep track of how long we pulsed
      elapsed_time += (tone_);
    } 
  }
  else { // Rest beat; loop times delay
    for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count
      delayMicroseconds(duration);  
    }                                
  }                                 
}

// PLAY SONG ===================================================
bool toggle = false;
void playSong() {
  for (int i=0; i<MAX_COUNT; i++) {
    tone_ = melody[i];
    beat = beats[i];
    duration = beat * tempo; // Set up timing
    // Blink LED
    digitalWrite(PIN_LED, toggle);
    toggle = !toggle;
    
    playTone(); 
    delayMicroseconds(pause);
  }
}

// GO TO DEEP SLEEP ==============================================
void goToSleep(void) {
    // GIMSK |= _BV(INT0);                    //enable INT0
    // MCUCR &= ~(_BV(ISC01) | _BV(ISC00));   //INT0 on low level
    ACSR |= _BV(ACD);                         //disable the analog comparator
    ADCSRA &= ~_BV(ADEN);                     //disable ADC
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    //turn off the brown-out detector.
    //must have an ATtiny45 or ATtiny85 rev C or later for software to be able to disable the BOD.
    //current while sleeping will be <0.5uA if BOD is disabled, <25uA if not.
    cli();
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    cli();                         //wake up here, disable interrupts
    // GIMSK = 0x00;               //disable INT0
    sleep_disable();               
    sei();                         //enable interrupts again (but INT0 is disabled from above)
}

// WRITE SERVO ANGLE  ============================================
int last_angle = 0;
void setServo(uint8_t degree, uint32_t duration) {
  int diff = degree - last_angle;
  diff = abs(diff);
  int w = (float)duration / (float)diff * 0.5 * 1000.0;
  if(last_angle < degree) {
    for (float k = last_angle; k < degree; k+=0.5) {
      servo.write(k); 
      delayMicroseconds(w);
      SoftwareServo::refresh(); 
    }
  } else if (last_angle > degree) {
    for (float k = last_angle; k > degree; k-=0.5) {
      servo.write(k);
      delayMicroseconds(w); 
      SoftwareServo::refresh(); 
    }
  }
  last_angle = degree;
}

// BLINK STATUS LED ==============================================
void blink_led(int num, uint16_t frequency) {
  for(i = 0; i<num; i++){
    digitalWrite(PIN_LED, HIGH);
    delay(frequency);
    digitalWrite(PIN_LED, LOW);
    delay(frequency);
  }
}

// SETUP =========================================================
void setup() {  
  tea_time = 0;
  start = false;
  tea_brewing = false;
  elapsed_time = 0;

  pinMode(PIN_POTI, INPUT);
  pinMode(PIN_START, INPUT);
  pinMode(PIN_SERVO, OUTPUT);
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_SPEAKER, OUTPUT);
  
  servo.attach(PIN_SERVO);
  servo.setMaximumPulse(2100);
  servo.setMinimumPulse(550);
  servo.write(SERVO_MAX);              
  for(int i=0; i<600/20; i++) { //stellzeit
    SoftwareServo::refresh();  
    delay(20); 
  }
  last_angle = SERVO_MAX;
  
  start_time = millis();
  blink_led(WAKE_UP_BLINK, WAKE_UP_FREQUENCY);
  digitalWrite(PIN_LED, HIGH);
  //cli(); 
  //General Interrupt Mask Register, External Interrupts: 0b01000000; Pin Change Interrupts: 0b00100000
  GIMSK = 0b00100000;    // turns on pin change interrupts
  //Pin Change Mask Register 
  PCMSK = 0b00010000;    // turn on interrupts on pin PB4
  sei();                 // enables interrupts (bzw. _SEI();)
}

void loop() {
  if (tea_brewing) {
    elapsed_time = millis() - start_time;
    analogWrite(PIN_LED, 60);
    // Check if tea is finished
    if (elapsed_time > tea_time) {
      endRoutine();    
      digitalWrite(PIN_LED, LOW);  
      goToSleep(); 
    }
  } else {
    if (millis() - start_time > SLEEP_AFTER) {
      digitalWrite(PIN_LED, LOW); // TODO auskommentieren? müsste auch ohne aus gehen?! geht nicht aus!
      goToSleep(); 
    }
  }

  if (start) {
    blink_led(TEA_START_BLINK, TEA_START_FREQUENCY);
    startRoutine();
    start = false;
  }
}

//Start the tea procedure
void startRoutine() {
  tea_brewing = true; //set tea making flag
  //get current time value time in ms
  tea_time = (float)MAX_TEA_TIME - (float)MAX_TEA_TIME * analogRead(PIN_POTI) / 1023.0;
  setServo(SERVO_MIN, SERVO_POSITIONING_TIME);
  start_time = millis();  //start time measure
}

//End tea procedure
void endRoutine() {
  tea_brewing = false;
  setServo(SERVO_MAX, SERVO_POSITIONING_TIME);
  //shake arm
  for (i = 0; i < END_BREWING_SHAKING; i++) {
    setServo(SERVO_MAX, END_BREWING_SHAKE_POSITIONING_TIME);
    setServo(SERVO_MAX - END_BREWING_SHAKE_DISTANCE, END_BREWING_SHAKE_POSITIONING_TIME);
  }
  setServo(SERVO_MAX, END_BREWING_SHAKE_POSITIONING_TIME);
  for(int l = 0; l<TEA_FINISHED_BLINK; l++) {
    playSong();
  }
}

//Interrupt Service Routine
ISR(PCINT0_vect)
{
  if (digitalRead(PIN_START)) {  // is start pressed?
    start = true;
  }
}

Das Programm erfüllt seinen Zweck und ist lange nicht optimiert aber es musste schnell gehen damit das Weihnachtsgeschenk rechtzeitig fertig wird :)

Und so sieht das ganze fertig aus.. mit dem Kopf lässt sich die Minutenzahl einstellen. Dabei habe ich darauf geachtet das bei sechs Minuten die Mittelstellung erreicht ist. Das Video wurde gekürzt um die Wartezeit zu verringern.

Quellen:

Energiesparmodus: http://www.arduino-hausautomation.de/2014/emils-ampel-attiny45-im-tiefschlaf/

ATtiny programmieren: https://www.frag-duino.de/index.php/maker-faq/37-atmel-attiny-85-mit-arduino-arduinoisp-flashen-und-programmieren

Farbcodierung bei Widerständen: http://www.calculino.com/de/elektronik/ohmscher-widerstand_rechner.html

Vorwiderstand LED: http://www.elektronik-kompendium.de/sites/bau/1109111.htm

Piezo Ansteuerung: https://www.arduino.cc/en/Tutorial/PlayMelody

  • Permalink
  • Kommentare

Einführung in die Programmierung mit C und Arduino

Dez 152014
  • 1 Vorwort
  • 2 Einführung in C
  •   2.1 Datentypen
  •     2.1.1 Arrays
  •     2.1.2 Strings
  •     2.1.3 Strukturen
  •     2.1.4 Aufzählungstyp
  •     2.1.5 Konstanten
  •   2.2 Operatoren
  •     2.2.1 Arithemtische Operatoren
  •     2.2.2 Vergleichsoperatoren
  •     2.2.3 Logische Operatoren
  •     2.2.4 Bitorientierte Operatoren
  •   2.3 Kontrollstrukturen
  •     2.3.1 Verzweigung
  •     2.3.2 for-Schleife
  •     2.3.3 while-Schleife
  •     2.3.4 do-while-Schleife
  •     2.3.5 break and continue
  •     2.3.6 switch-Anweisung
  •   2.4 Zeiger (Pointer)
  •   2.5 Funktionen
  •     2.5.1 call-by-value
  •     2.5.2 call-by-pointer
  •     2.5.3 call-by-reference
  •     2.5.4 Default-Parameter
  •     2.5.5 Funktionen überladen
  •   2.6 Formatierung
  • 3 Einführung in Arduino
  •   3.1 Software und Installation
  •   3.2 Programmieren
  •   3.2.1 Sketch
  •    3.2.2 Digital I/O
  •    3.2.3 Analog I/O
  •    3.2.4 Zeit
  •    3.2.5 Kommunikation
  •    3.2.6 Mathe
  •    3.2.7 Externe Interrupts
  •   3.3 Boards
  •    3.3.1 Arduino Uno
  •    3.3.2 Arduino Mega
  •   3.4 Beispiele
  • 1 Vorwort

    Diese Einführung entstand im Rahmen eines Semester Praktikums in China. Bei einem Projekt das über die Programmierung und den Aufbau eines Quadrocopters ging wurde mit einem Arduino gearbeitet. Um alle Projektmitglieder auf einen gemeinsamen Wissensstand zu bringen brauchte ich ein speziell für Arduino und C ausgelegte Einführung. Alle die ich im Internet dazu gefunden habe waren nicht vollständig und deckten nur kleine Teilgebiete ab. Gleichzeitig war das schreiben dieser Einführung eine gute Wiederholung für mich. Es soll für all-diejenigen sein, die etwas eingerostet sind in der Sprache C oder den Umgang mit einem Arduino lernen möchten. Wobei der Arduino schon sehr benutzerfreundlich ist und für Einsteiger im Mikrocontroller Bereich gilt, finden dennoch viele die ersten Schritte schwierig. Weil es sehr knapp gehalten wurde kann es auch als Nachschlagewerk für die Syntax genutzt werden.

    2 Einführung in C

    Es gibt immer mehr als eine Möglichkeit, eine Aufgabe in einem Computerprogramm abzubilden. Finde deinen eigenen Programmierstil und verbessere ihn. Ablauf eines Programms:

    1. Präprozessing: Headerfiles(*.h) werden zum Quellfile hinzugefügt
    2. übersetzen in Assemblercode: Hier wird ein Quelltextfile in der prozessorspezifischen Programmiersprache Assembler erzeugt.
    3. Objektcode erzeugen: Hier wird aus dem Assemblerfile ein Hex-File generiert, welches die direkten Steuerbefehle für den Prozessor beinhaltet.
    4. Linken: Alle Objektfiles und notwendigen Bibliotheken werden miteinander zum ausführbaren Programm verbunden.

    2.1 Datentypen

    Die Größe der Datentypen ist System abhängig und besonders bei Mikrocontroller sehr wichtig, da der Speicher begrenzt ist. Ein Bool ist eigentlich nur ein Bit groß dennoch wird dafür ein ganzes Byte reserviert, weil kleinere Datentypen nicht verarbeitet werden können.

    Type Vorzeichen Anzahl Bit Wertebereich
    char mit 8 Character-Zeichen 'A'
    bool ohne 8 false, true
    byte ohne 8 0-255
    short mit 16 -32 768 bis 32 767
    word ohne 16 0 bis 65 535
    int mit 16 -32 768 bis 32 767
    unsigned int ohne 16 0 bis 65 535
    long mit 32 -2 147 483 648 bis 2 147 483 647
    unsigned long ohne 32 0 bis 4 294 967 295
    float mit 32 6-7 Stellen insgesamt
    double mit 32 wie float

    2.1.1 Arrays

    In einem Array werden Daten gleichen Datentyps zusammengefasst. 

    int x[3]; // Deklaration
    x[0] = 1; // 1. Element
    x[1] = 2; // 2. Element
    x[2] = 3; // 3. Element
    
    int y[3] = {1, 2, 3}; // Kann auch bei der Deklaration festgelegt werden
    for(int i = 0; i < 3; i++) // array durchlaufen
      x[i] = y[i]; // vorsicht: Element muss existieren!

    2.1.2 Strings

    Ein String ist nichts anderes als ein char-Array.

    char str1[5] = {’T’, ’e’, ’s’, ’t’, ’\0’}; // \0 beendet den String
    char str2[] = "Test"; // nach der Deklaration ist das Array 5 lang
    char str3[5] = "Test";
    char str4[15] = "Test"; // platz halten für einen größeren String

    2.1.3 Strukturen

    Die Struktur definiert einen neuen Datentyp, welcher Komponenten unterschiedlicher Datentypen beinhaltet

    struct Auto // neue Struktur
    {
      int gewicht;
      int maxGeschwindigkeit;
    };
    
    Auto bmw;
    bmw.gewicht = 1600;
    bmw.maxGeschwindigkeit = 290;

    2.1.4 Aufzählungstyp

    Der Aufzählungstyp wird verwendet, wenn man Werte gruppieren möchte, aber ein Array zu unübersichtlich ist.

    enum Tag
    {
      montag, dienstag, mittwoch, donnerstag, freitag
    };
    
    Tag arbeitstag; // neue Variable Deklarieren
    arbeitstag = montag; // Variable zuweisen
    if (arbeitstag == montag)
    {
      // schlechte Laune
    }

    2.1.5 Konstanten

    Es gibt Konstanten mit Variablennamen. Dabei wird eine Variable zusätzlich mit dem Schlüsselwort const gekennzeichnet, so kann sie später im Programm nicht mehr verändert werden. Es gibt auch symbolische Konstanten. Hier wird zu Beginn ein Name festgelegt, der später beim Kompilieren durch den dazugehörigen Wert ersetzt wird.

    // Symbolische Konstanten
    #define NL ’\n’
    #define Number 5
    #define Hello "Hello World\n"
    
    //Konstanten mir Variablennamen
    const int N = 5;

    2.2 Operatoren

    2.2.1 Arithemtische Operatoren

    Zu jeder arithmetischen Operation gibt es auch eine Kurzschreibweise, die aber mit bedacht verwendet werden sollte (führt aber zu schlecht lesbarem Code).

    a = a + b; // Addition
    a += b;
    a = a - b; // Subtraktion
    a -=b;
    a = a * b; // Multiplikation
    a *=b;
    a = a / b; // Division (Vorsicht bei Integer)
    a /=b;
    a = a % b; // Modulo (Rest bei Division)
    a %= b;
    a = b++; // Post Inkrement
    a = ++b // Pre Inkrement
    a = b--; // Post Dekrement
    a = --b; // Pre Dekrement

    2.2.2 Vergleichsoperatoren

    Alle Operationen geben true oder false zurück.

    int a = 3, b = 6;
    a < b; // a kleiner b
    a <= b; // a kleiner gleich b
    a > b; // a größer b
    a >= b; // a größer gleich b
    a == b; // a gleich b (Vorsicht bei Gleitkommazahlen)
    a != b; // a ungleich b (Vorsicht bei Gleitkommazahlen)
    
    if(a == b) // wenn erfüllt
    {
      //..dann tue das
    }

    2.2.3 Logische Operatoren

    Alle Operationen geben true oder false zurück.

    bool a = true;
    a = !a; // jetzt hat a den wert false
    a = a && true // logisches UND
    a = a || true // logisches ODER

    2.2.4 Bitorientierte Operatoren

    Alle Operatoren werden bitweise angewandt.

    short int n = 6 // 0..000110 entspricht 6
    n = ~n; // Complement, d.h. jede 0 wird eine 1 und jede 1 eine 0
    n = n & 1 // bit-AND
    n = n | 1 // bit-OR
    n = n ^ 1 // bit-XOR
    n = n << 2 // shift left by 2
    n = n >> 2 // shift right by 2
    
    if ((n | 1) == n) // 10101 OR 00001 = 10101
    {
      // zahl n ist ungerade
    }

    2.3 Kontrollstrukturen

    2.3.1 Verzweigung

    Es wird überprüft, ob eine Bedingung erfüllt ist. Je nach dem wird ein anderer Code ausgeführt.

    bool a = true;
    if(a == true)
    {
      // wenn a == true
    }
    else
    {
      // wenn a == false
    }
    
    int c = (a > b) ? a : b // Kurzschreibweise ist aber offiziell ein Ternärer Operator

    2.3.2 for-Schleife

    Der Code im Block wird eine bestimmte Anzahl durchlaufen. Auf die Zählvariable kann innerhalb des Blocks zugegriffen werden.

    for(int i=0; i<10; i++)
    {
      // dieser Code wird 10 mal ausgeführt
    }

    2.3.3 while-Schleife

    Der Code innerhalb des Blocks wird solange ausgeführt, wie die Bedingung im Kopf erfüllt ist.

    while(a == true)
    {
      // dieser Code wird solange ausgeführt wie a gleich true ist
    }

    2.3.4 do-while-Schleife

    Dies ist die fußgesteuerte while-Schleife. Sie funktioniert wie die normale while-Schleife nur, dass die Bedingung am Schluss kommt. Der Vorteil ist, dass der Code unabhängig von der Bedingung mindestens einmal ausgeführt wird.

    do {
      // dieser Code wird mindestens einmal ausgeführt
    } while(a == true); // nicht das Semikolon vergessen!

    2.3.5 break and continue

    Diese beiden Anweisungen werden im Zusammenhang mit Schleifen verwendet. Vorsicht bei der Anwendung. break: der Aufruf dieser Anweisung beendet die gesamte Schleife. continue: Der Aufruf beendet nur den aktuellen Durchlauf an dieser Stelle und setzt die Codeausführung an dem Schleifenkopf fort.

    int i = 0;
    for(;;)
    {
      i++; // i um 1 hochzählen
      if(i % 2 == 0) // alle geraden Zahlen überspringen
        continue; // den Rest vom Programmteil überspringen und beim Schleifen Kopf starten
    
      if(i>1000) // nach 1000 durchläufen Schleife beenden
        break;
    }

    2.3.6 switch-Anweisung

    Falls man einen Wert einer Variablen auf verschiedene Werte überprüfen möchte, ist die switch-Anweisung die beste Wahl.

    int n = 7;
    switch(n)
    {
      case 3:
        // falls n gleich 3 ist
      break; // die Anweisung break beendet die switch-Anweisung
      case 7:
        // falls n gleich 7 ist
      break;
      default:
        // Falls es auf kein Fall zu trifft
      break;
    }

    2.4 Zeiger (Pointer)

    Zeiger zeigen auf ein Objekt eines jeden beliebigen Datentyps. Mit einem Sternchen vor dem Namen wird ein Zeiger beim Deklarieren gekennzeichnet. Er muss vom gleichen Datentyp sein, wie der, auf den er späte zeigen soll.

    int n = 5 // Deklaration einer normalen Variablen
    int *ip; // Deklaration eines Zeigers der auf Integer zeigen kann
    ip = &n; // Pointer wird initialisiert mit der Adresse der Variablen
    *ip = *ip + 2; // n wird mithilfe des Zeigers um 2 erhöht
    
    struct Auto // Struktur erstellen
    {
      int maxGeschwindigkeit;
    };
    
    Auto bmw, *pBmw; // Anlegen einer Variablen und einem Pointer
    pBmw = &bmw; // Pointer bekommt die Adresse von bmw
    (*pBmw).maxGeschwindigkeit = 290; // zugriff auf die Variable
    pBmw->maxGeschwindigkeit = 290; // bessere alternative

    2.5 Funktionen

    Wenn Programmabschnitte meist öfter als einmal benötigt werden, oder einfach nur der übersichtlichkeit halber, bietet sich eine Funktion an. Sie besteht aus dem Datentyp, welchen sie zurückgibt, dem Namen und der übergabeparameter. Doch zuerst muss man sie, wie auch bei den Variablen, bekannt geben.

    int add(int a, int b); // Deklaration der Funktion meist außerhalb in einem Header File
    int add(int a, int b) // Definition der Funktion
    {
      return (a + b);
    }
    int c = add(1, 2); // Aufruf der Funktion

    2.5.1 call-by-value

    void calcY(int y) // Definition der Funktion. Void bedeutet das diese Funktion nichts zurückgibt
    {
      y = y + 2;
    }
    
    int y1 = 2;
    calcY(y1); // nach dem Aufruf hat die lokale Variable y1 immer noch den Wert 2

    2.5.2 call-by-pointer

    Falls die Übergabeparameter durch den Aufruf der Funktion verändert werden sollen, gibt es die Möglichkeit sie per Zeiger zu übergeben.

    void swap (int *a, int *b)
    {
      int *tmp = a; // Variable wird temporär zwischen gespeichert
      a = b;
      b = tmp;
      *tmp = (*a) + (*b); // Falls man auf die Werte zugreifen will
    }
    
    int x = 1, y = 2;
    swap(&x, &y); // nachdem Aufruf besitzt x den Wert 2, y den Wert 1

    2.5.3 call-by-reference

    ähnlich wie call-by-pointer, funktioniert call-by-reference. Man gibt nur die Speicheradresse der Variablen an die Funktion weiter.

    void swap (int &a, int &b)
    {
      int tmp = a; // Variable wird temporär zwischen gespeichert
      a = b;
      b = tmp;
    }
    
    int x = 1, y = 2;
    swap(x, y); // nachdem Aufruf hat x den Wert 2 und y den Wert 1

    2.5.4 Default-Parameter

    Um bei einem Funktionsaufruf nicht alle Parameter angeben zu müssen, kann man die Variablen vor belegen.

    int add(int a, int b, int c=0, int d=0); // muss bei der Deklaration geschehen
    
    add(1, 2, 3, 4); // man muss nicht alle Parameter angeben, für die fehlenden wird der Defaultwert 0 übergeben
    add(1, 2, 3);
    add(1, 2);

    2.5.5 Funktionen überladen

    Man kann den gleichen Namen einer Funktion verwenden, solange sie sich in der Parameterliste unterscheiden. Der Compiler entscheidet dann welche Funktion er verwenden muss.

    int add(int a, int b); // Deklaration der add Funktion mit zwei Integer Parameter
    int add(int a, int b, int c); // Deklaration mit drei Parameter

    2.6 Formatierung

    Ein wichtiges Thema wird meist vergessen, die Formatierung.

    • Bei der Benennung der Variablen werden wir dem CamelCase Style folgen. D.h. der Anfangsbuchstabe ist jeweils klein und jedes weitere Wort wird groß geschrieben.
    • Wichtig ist es auch sein Code zu kommentieren. Zu Beginn eine kurze Beschreibung, welche Funktion diese Datei hat, den Autor und das Datum, sowie Kommentare im Code. Ein gut kommentierter Code ist zu 50% Grün.
    • Außerdem ist es wichtig den Code übersichtlich zu gestalten und bei zu langen Ausdrücken in einer Zeile in mehrere Zeilen aufzuteilen.
    • Bitte auch darauf achten, dass der Code einer Funktion keinen größeren Umfang als eine Seite hat.
    • Jede neue Klasse sollte in einer neuen Datei geschrieben werden.
    int meineZahl = 5;
    Auto einObjectDerKlasseAuto;
    
    x = 1/2(b^2 * sin(angle/PI)) // dies ist ein Kommentar
    /* ein Kommentar..
    .. über mehrere Zeilen */
    
    if(a == true){b*=(a-b)%2} // kein Spaghetticode erzeugen

    3 Einführung in Arduino

    Arduino ist eine Open-Source-Plattform, basierend auf einem Mikrocontroller-Board und einer Entwicklungsumgebung. Warum Arduino? Arduino ist viel günstiger als so manches Entwickler Board. Sie sind leichter zu Programmieren als zum Beispiel die Atmel AVR Controller und es gibt eine große aktive Community.

    3.1 Software und Installation

    Arduino liefert eine eigene kleine Software die Ihr hier herunterladen könnt. Es existieren auch Plugins für Microsoft Visual Studio und Eclipse. Nach der Installation könt ihr auch schon gleich loslegen. Geht auf neuen Sketch erstellen (Programme in Arduino heißen Sketch). Dann wählt ihr noch den richtigen USB Port aus, unter Tools -> Serial Port. Nun muss noch das richtige Arduino Modell ausgewählt werden, unter Tools -> Board. Zum Schluss könnt ihr den Sketch hochladen.

    // Beispiel Sketch - Blinkende LED
    void setup() 
    {               
      pinMode(13, OUTPUT);     
    }
    
    void loop() 
    {
      digitalWrite(13, HIGH);
      delay(1000);            
      digitalWrite(13, LOW); 
      delay(1000);  
    }
    

    3.2 Programmieren

    Man kann den Arduino in C und teilweise in C++ programmieren, wobei man bei C++ auf den begrenzten Speicher acht geben muss. Zusätzlich gibt es noch spezielle mikrocontroller-typische Befehle, wie zum Beispiel das Einlesen und Ausgeben eines Pins. Für eine detaillierte Erklärung bitte hier nachlesen.

    3.2.1 Sketch

    Als Sketch wird ein Programm bezeichnet. Es beinhaltet immer zwei Funktionen, nämlich setup und loop. Setup wird genau einmal ausgeführt und dient zur Deklarationen von Variablen. Loop ist wie eine endlos-Schleife. Sie wird nach jedem Durchlauf wieder von neuem aufgerufen.

    void setup() 
    {
       // wird einmalig ausgeführt
    }
    void loop()  
    {
      // Endlos-Schleife
    }
    

    3.2.2 Digital I/O

    Folgendes dient zur Manipulierung der Digitalen Pins. Zuerst muss man im setup bekanntgeben, ob der Pin als Eingang oder als Ausgang fungiert.

    int val = 0;
    void setup()
    {
      pinMode(13, OUTPUT);  // setzt den Pin 13 als Ausgang
      pinMode(12, INPUT);  // setzt den Pin 12 als Eingang 
    }
    
    void loop()
    {
      val = digitalRead(12);  // einlesen des Digitalen Wert
      digitalWrite(13, val);  // macht den Ausgang HIGH  
      delay(1000);  // 1s warten
      digitalWrite(13, LOW);  // schaltet den Ausgang wieder auf LOW       
    }
    

    3.2.3 Analog I/O

    Für die Analogen Pins gilt fast das gleiche, wie bei den Digitalen Pins. Bei analogWrite wird der Wert in ein Rechtecksignal (PWM ca. 490Hz) umgewandelt, dass im Mittelwert dem gewünschten Analogen Wert entspricht.

    void setup()
    {
      pinMode(9, OUTPUT);   // Pin 9 unterstützt analogWrite()
    }
    
    void loop()
    {
      int val = analogRead(3);   // Potentiometer einlesen
      analogWrite(9, val >> 2);  // val muss von 0-1023 auf 0-255 angepasst werden
    }
    

    3.2.4 Zeit

    Für die Zeitgebung und die Verzögerung gibt es einige fertige Funktionen.

    // 1 s = 1 000 ms = 1 000 000 us
    t = millis();  // gibt die Millisekunden zurück seit der Mikrocontroller läuft. (Datentyp läuft in 50 Tagen über)
    t = micros();  // gibt die zeit in Mikrosekunden zurück. Die Auflösung beträgt 4 us bei 16MHz (Datentyp läuft in 70 Minuten über)
    delay(t);  // verzögert das Programm um ms
    delayMicroseconds(t);  // verzögert um us
    

    3.2.5 Kommunikation

    Die Kommunikation mit dem Arduino erfolgt meist über einen Seriellen Port. Zum einen wird der Arduino darüber programmiert, zum anderen kann man auch mit ihm kommunizieren. Zum Beispiel Sensorwerte auslesen und direkt anzeigen. Um dies zu machen muss man zuerst den Serial Monitor aktivieren und die richtige Baudrate auswählen. Für weitere Funktionen bitte hier entnehmen.

    void setup() 
    {
      Serial.begin(9600);  // Neue Kommunikation einrichten mit einer Baudrate von 9600
    }
    
    void loop() 
    {
      Serial.println(analogRead(0));  // den Analogen Pin0 einlesen und auf dem Serial Monitor ausgeben
      delay(100);  // verzögerung von 100 ms
    }   
    

    3.2.6 Mathe

    In der Standard Bibliothek gibt es schon vorhandene Funktionen für die Berechnung. Bei der Verwendung immer darauf achten, dass der Mikrocontroller nicht viel Leistung hat.

    minX = min(x1, x2);  // bestimmt die kleiner Zahl
    maxX = max(x1, x2);  // bestimmt die größere zahl
    x = abs(a);  // berechnet den Absolutwert
    x = constrain(x, a, b);  // wird überprüft ob x innerhalb des Wertebereich liegt. Wenn x kleiner als a ist wird a zurückgegeben und wenn x größer als b ist wird b zurückgegeben.
    value= map(value, fromLow, fromHigh, toLow, toHigh);  // Wertebereich wird angepasst
    a = pow(base, exponent);  // berechnet die Potenz einer Zahl
    a = sqrt(x);  // berechnet die Wurzel
    a = sin(a);  // berechnet den Sinus (Radiant)
    a = cos(a);  // der Rückgabewert ist auch Radiant
    a = tan(a);
    randomSeed(analogRead(0));  // Den Zufallsgenerator abhängig von einem Eingang machen. Der Funktionsaufruf muss im setup erfolgen
    x = random(300);  // eine Zufallszahl zwischen 0 und 299 generieren
    x = random(10, 100);  // Zufallszahl liegt zwischen 10 und 99);
    

    3.2.7 Externe Interrupts

    Ein Interrupt ist wenn ein wichtiges Ereignis eintritt und das Hauptprogramm unterbrochen wird, um das Ereignis zu verarbeiten. Bei dem Arduino Uno gibt es zwei Externe Interrupts an Pin 2 und Pin 3. Man kann den Interrupt auslösen, wenn der Eingang auf LOW ist, sich ändert ( CHANGE ), wechselt auf high ( RISING ) oder auf low wechselt ( FALLING ). Die Interrupt Service Routine ist die Funktion, die den Interrupt verarbeitet. Sie sollte so knapp wie möglich geschrieben werden. Manche Funktionen werden nicht mehr weiterverarbeitet, wenn ein Interrupt aufgerufen wird (serielle Kommunikation). Falls Interrupts bei kritischen Sachen nicht erwünscht sind, kann man sie mit noInterrupts() deaktivieren und mit interrupts() wieder aktivieren.

    volatile int state = LOW;  // falls die Funktion durch ein Interrupt verändert werden soll muss volatile angegeben werden
    void setup()
    {
      pinMode(13, OUTPUT);  // Pins 13 ist ein Ausgang
      attachInterrupt(0, blink, CHANGE);  // Interrupt 0, Interrupt Service Routine, soll auf ein Wechsel reagieren
      delay(60000);  // 60 s warten
      detachInterrupt(0);
    }
    
    void loop()
    {
      digitalWrite(13, state);
    }
    
    void blink()  // Interrupt Service Routine
    {
      state = !state;
    }
    

    3.3 Boards

    3.3.1 Arduino Uno

    Das meist verwendete Board heißt Arduino Uno. Aktuell in der dritten Revision ist es für fast jedes Projekt denkbar. Im Internet kursieren etliche Projekte und Anleitungen im Bezug auf den Arduino Uno. Es gibt zusätzlich einige Shields für dieses Board um die Funktionalität zu erweitern.

    Spezifikation Arduino Uno R3

    Microcontroller ATmega328
    Operating Voltage 5V
    Digital I/O Pins 14 (6 davon haben PWM)
    Analog Input Pins 6
    Strom maximal I/O Pin 40 mA
    Flash Memory 32 KB (0.5 KB Bootloader)
    SRAM 2 KB
    EEPROM 1 KB
    Clock Speed 16 MHz

    3.3.2 Arduino Mega

    Wenn einem die Anschlüsse doch mal nicht reichen sollten dann ist die bessere Wahl der Arduino Mega. Mit zusätzlichem Speicher ist er gut für die großen Projekte ausgerüstet.

    Spezifikation Arduino Mega R3

    Microcontroller ATmega1280
    Operating Voltage 5V
    Digital I/O Pins 54 (15 davon haben PWM)
    Analog Input Pins 16
    Strom maximal I/O Pin 40 mA
    Flash Memory 128 KB (4 KB Bootloader)
    SRAM 8 KB
    EEPROM 4 KB
    Clock Speed 16 MHz

    3.4 Beispiele

    Nun macht es hier wenig Sinn die gleichen Beispiele die es schon genügend im Internet gibt hier noch einmal auszutreten. Unter folgendem Link findet Ihr etliche gut beschriebene Tutorials die sich meist auf den Arduino Uno beziehen aber ohne Probleme auch mit dem Mega realisierbar sind.

    • Permalink
    • Kommentare
    k