• Willkommen im Geoclub - dem größten deutschsprachigen Geocaching-Forum. Registriere dich kostenlos, um alle Inhalte zu sehen und neue Beiträge zu erstellen.

Erstes Projekt

hcy

Geoguru
Hallo zusammen,

ich wollte ja schon immer mal etwas mit µCs machen und hab mal angefangen eine "Dose mit Drähten, mit (tickenden) Uhr'n die abwärts zähl(t)en"* zu bauen. Inspiriert vom Reverse Geocache hab ich's der Einfachheit halber mit einem Arduino gebaut (für Newbies absolut zu empfehlen). Es gibt ein LCD-Display das eine Aufgabe ausgeben kann, 4 Potis an denen man einen Code einstellen muss damit die Kiste aufgeht und eine Uhr die rückwärts zählt, sodass die Aufgabe in einer bestimmten Zeit gelöst werden muss.
Zum Verriegeln bzw. Öffnen gibt es einen Servo.

Fall jemand sowas interessiert kann ich auch Bilder und Programmcode posten.

Ich glaub am Bahnhof sollte man das Teil nicht verstecken ... :lachtot:

*Dosenfischer: Die goldenen Jahre
 

Roter-Wolf

Geocacher
Na das ist doch mal eine tolle Idee. Über soetwas habe ich auch schon nachgedacht. Fotos fände ich mal sehr interessant.
 
OP
hcy

hcy

Geoguru
Ok, werd ich heute abend mal etwas knipsen. Ich muss eh noch weiter basteln ..
 
OP
hcy

hcy

Geoguru
So, hier erst mal Fotos. Das ganze ist nicht unbedingt so als Cache gedacht, also vor allem ist es nicht wetterfest verpackt, aber das ist "nur" Mechanik (nicht so mein Thema):
6120612.jpg

Die Kiste von außen. Kiste ist aus dem Baumarkt, es geht natürlich auch ein anderer Behälter.

6120613.jpg

Man sieht das Display (LCD 2x16) von hinten, 4 Potis, oben der Servo zum Verriegeln (sicher so keine optimale Lösung, aber wie gesagt, Mechanik ist nicht mein Thema) und unten das Arduino Protoshield.

6120614.jpg

Noch mal das Protoshield mit der (abenteuerlichen) Verdrahtung.

6120615.jpg

Arduino Duemilanove + Batterie, hier wird das Protoshield einfach aufgesteckt.

6120616.jpg

Code-Display und Uhr-Countdown

6120617.jpg

Geschafft, die Kiste ist auf.

Die Programmierung ist dank Arduino super einfach, da ist das AVR Basic hohe Kunst dagegen. Die Entwicklungsumgebung läuft praktisch auf jedem Betriebssystem (und ist natürlich kostenlos). Das einzige was etwas am Arduino-Code "vorbei" programmiert ist ist der Timer Interrupt.
Code:
#include <Servo.h> 
#include <LiquidCrystal.h>

unsigned int tcnt2;
int int_counter = 0;

int rot = 10; // Ausgabepin rote LED
int gruen = 11; // Ausgabepin gruene LED

int minute = 59;
int sekunde = 59; // Eine Stunde Zeit zum oeffnen

Servo myservo;
LiquidCrystal lcd(12, 6, 5, 4, 3, 2); // an diesen Pins hängt das LCD

int p1 = 0;
int p2 = 0;
int p3 = 0;
int p4 = 0;
boolean kiste = false;
 
void setup() 
{ 
  // Das Timer-Interrupt Zeugs findet man so im Netz
  /* First disable the timer overflow interrupt while we're configuring */  
  TIMSK2 &= ~(1<<TOIE2);  
   
  /* Configure timer2 in normal mode (pure counting, no PWM etc.) */  
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));  
  TCCR2B &= ~(1<<WGM22);  
   
  /* Select clock source: internal I/O clock */  
  ASSR &= ~(1<<AS2);  
   
  /* Disable Compare Match A interrupt enable (only want overflow) */  
  TIMSK2 &= ~(1<<OCIE2A);  
   
  /* Now configure the prescaler to CPU clock divided by 128 */  
  TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits  
  TCCR2B &= ~(1<<CS21);             // Clear bit  
   
  /* We need to calculate a proper value to load the timer counter. 
   * The following loads the value 131 into the Timer 2 counter register 
   * The math behind this is: 
   * (CPU frequency) / (prescaler value) = 125000 Hz = 8us. 
   * (desired period) / 8us = 125. 
   * MAX(uint8) + 1 - 125 = 131; 
   */  
  /* Save value globally for later reload in ISR */  
  tcnt2 = 131;     

  pinMode(rot, OUTPUT);
  pinMode(gruen, OUTPUT);
  myservo.attach(9);  // der Servo hängt an Pin 9 (PWM) 
  kisteZu();
  lcd.begin(16, 2);
  anzeige("Hallo!", 0);
  delay(2000);
  anzeige("Herzlichen", 1);
  delay(2000);
  anzeige("Glueckwunsch!", 1); 
  delay(2000);
  lcd.clear();
  anzeige("Findet den Code", 0);
  anzeige("und oeffnet die", 1);
  delay(3000);
  lcd.clear();
  anzeige("Kiste. Ihr habt", 0);
  anzeige("aber nur eine", 1);
  delay(3000);
  lcd.clear();
  anzeige("Stunde Zeit.", 0);
  anzeige("Also beeilt Euch", 1);
  delay(3000);
  lcd.clear();
  lcd.print("Kiste ist: zu ");
  delay(2000);
  lcd.clear();

  /* Finally load end enable the timer */  
  TCNT2 = tcnt2;  
  TIMSK2 |= (1<<TOIE2);  
} 

 
void loop() 
{ 
  if (!kiste) {
    lcd.setCursor(0,1);
    lcd.print("Zeit:");
    lcd.setCursor(11,1);
    if (minute < 10) {
      lcd.print(0);
    }
    lcd.print(minute);
    lcd.print(":");
    if (sekunde < 10) {
      lcd.print(0);
    }
    lcd.print(sekunde);
     
    p1 = lesePoti(0);
    p2 = lesePoti(1);
    p3 = lesePoti(2);
    p4 = lesePoti(3);

    lcd.setCursor(0,0);
    lcd.print("Code: ");
    lcd.print(p1, DEC);
    lcd.print(":");
    lcd.print(p2, DEC);
    lcd.print(":");
    lcd.print(p3, DEC);
    lcd.print(":");
    lcd.print(p4, DEC);
    if (p1 == 1 && p2 ==2 && p3 == 3 && p4 == 4) { // Beispiel-Code: 1234
      kisteAuf();
    }
    if (minute == 0 && sekunde == 0) {
        // disable timer
        TIMSK2 &= ~(1<<TOIE2);
        lcd.setCursor(0,0);
        lcd.print("Die Zeit ist um!");
        delay(2000);
        kisteAuf(); // Ok, das ist einfach, kann man natuerlich anders machen
    }
    delay(100);
  } else {
    anzeige("Geschafft!", 0);
    delay(10000);
  }
} 

void kisteAuf()
{
  // disable timer
  TIMSK2 &= ~(1<<TOIE2); 
  digitalWrite(rot, LOW);
  digitalWrite(gruen, HIGH);
  lcd.clear();  
  kiste = true;
  lcd.setCursor(0,1);
  lcd.print("Kiste ist: auf");
  myservo.write(0); // Servo geht in Auf-Stellung (0 Grad)
  delay(2000);
}

void kisteZu()
{
  digitalWrite(rot, HIGH);
  digitalWrite(gruen, LOW);
  lcd.setCursor(0,1);
  lcd.print("Kiste ist: zu ");
  myservo.write(45); // Servo geht in Zu-Stellung (45 Grad)
}

int lesePoti(int nr) 
{
  int val = analogRead(nr);
  // der Wert vom Analog-Eingang (0..1023) wird gemappt auf 0..9
  return map(val, 0, 1023, 0, 9); 
}

void anzeige(char* s, byte zeile) 
{
  lcd.setCursor(0,zeile);
  lcd.print(s);
}

/* Hier ist die ISR vom Timer-Interrupt, da wird nur die Zeit gezählt (jede ms) */
ISR(TIMER2_OVF_vect) {  
   /* Reload the timer */  
   TCNT2 = tcnt2;  
   int_counter += 1;
   if (int_counter == 1000) {
     int_counter = 0;
     sekunde--;
     if (sekunde == 0) {
       if (minute == 0) {
         // disable timer
         TIMSK2 &= ~(1<<TOIE2); 
       } else {
         minute--;
         sekunde = 59;
       }
     }
   }
}
 

englishfire

Geomaster
Wow..finde ich super interessant. Was kosten denn die ganzen Einzelteile?
So etwas würde ich gerne nachbauen !!

Wie stellst du dir den Cache denn vor?
Soll man den Counter aktivieren und die Box dann zu den einzelnen Stationen mitnehmen, an denen man die einzelnen Codezahlen erhält? Und was ist, wenn die Geocacher die Box erst aktivieren, wenn sie schon alle Zahlen zusammen haben? Dann bringt der Countdown nichts.
 
OP
hcy

hcy

Geoguru
Na ja, handwerklich ist das ganz sicher verbesserungswürdig. Es ist wie gesagt so als Cache nicht unbedingt gedacht (Ziel ist ein Spiel am Kindergeburtstag).
Zu den Preisen: dieses Arduino-Board kostet ca. 25 EUR, Prototype-Shield ca. 14, Display für ungeführ 8, diverse Kleinteile k.A...
Man kann das natürlich auch ohne ein vollständiges Arduino-Board nur mit einem ATmega328 für ca. 4 EUR aufbauen, dann kommt man deutlich günstiger bei rum. Für Einsteiger bietet das Arduino-Board halt den Vorteil dass vieles recht einfach gesteckt werden kann und man keinen ISP-Programmer braucht.
Mit der Arduino IDE kann man dann aber auch direkt den ATmega über ISP programmieren.
 

thomas_st

Geowizard
hcy schrieb:
So, hier erst mal Fotos.
Sehr schön :) Die Idee Potis für die Codeeingabe zu nutzen finde ich klasse ...

... und der Aufbau ist auch sehr gut gelungen.

Viele Grüße,
Thomas(_st) - die Lib zur LCD-Ausgabe gehört zum Arduino? Oder was ist das für eine?
 

thomas_st

Geowizard
hcy schrieb:
Die Programmierung ist dank Arduino super einfach, da ist das AVR Basic hohe Kunst dagegen.
Sieht WinAVR sehr ähnlich (mal von solchen Luxusfunktionen wie direkte LCD-Ansteuerung mal abgesehen). Ist vermutlich kein Zufall, da ich hinter Arduino den gleichen GCC vermute, der hinter WinAVR steht.

Zwei Punkte die mir aufgefallen sind (habe es aber erstmal nur überflogen):

hcy schrieb:
Code:
ISR(TIMER2_OVF_vect) {  
   /* Reload the timer */  
   TCNT2 = tcnt2;  
[...]
Den Sinn hinter dem Setzen des Counterregisters verstehe ich nicht ganz. Ist das so ein "per Hand" programmierter CTC-Mode? Bei letzterem läuft der Counter von 0 bis zum Wert der in OCR2A steht, löst einen Overflow IRQ aus (könnte auch ein Compare Match IRQ sein - bin mir hier nicht ganz sicher), wird automatisch wieder mit 0 initialisiert und läuft erneut los.

hcy schrieb:
Code:
[...]
void loop()
{
[...]
    if (minute == 0 && sekunde == 0) {
        // disable timer
        TIMSK2 &= ~(1<<TOIE2);

[...]

ISR(TIMER2_OVF_vect) { 
[...]
     if (sekunde == 0) {
       if (minute == 0) {
         // disable timer
         TIMSK2 &= ~(1<<TOIE2); 
[...]
Ist doppelt. Ich würde des in der loop-Funktion bevorzugen (so wenig wie möglich in die ISR).

Viele Grüße,
Thomas(_st)
 

Hippokrat

Geonewbie
Sehr schön gemacht und klasse Idee, mich würde noch der genaue Verschlussmechanismus interessieren, der mich bei meinen Projekten immer etwas ärgert. :hilfe:
siehe http://geocracher.blogspot.com/

Gruß
 
OP
hcy

hcy

Geoguru
thomas_st schrieb:
Sieht WinAVR sehr ähnlich
Ja, zum Glück (um genau zu sein sieht es C ähnlich, WinAVR ist ja nur eine Portierung des GCC für Windows). So ist man auch mit anderen AVR-C-Programmen kompatibel. Außerdem programmiere ich den ganzen Tag in Sprachen mit C-ähnlicher Syntax, da will ich mir nicht noch Basic antun.
thomas_st schrieb:
(mal von solchen Luxusfunktionen wie direkte LCD-Ansteuerung mal abgesehen).
Das ist auch ein Vorteil von Arduino, es gibt sehr viele fertige Libs für alles mögliche, LCD-Ansteuerung, Servo, Schrittmotor, diverse Sensoren usw.
Leider (offiziell noch) nicht für Timer-Interrupts und Watchdog.
thomas_st schrieb:
Ist vermutlich kein Zufall, da ich hinter Arduino den gleichen GCC vermute, der hinter WinAVR steht.
Genau, ist GCC was es u.a. auch so schön plattformunabhängig macht.

thomas_st schrieb:
hcy schrieb:
Code:
ISR(TIMER2_OVF_vect) {  
   /* Reload the timer */  
   TCNT2 = tcnt2;  
[...]
Den Sinn hinter dem Setzen des Counterregisters verstehe ich nicht ganz.
Keine Ahnung, das hab ich so gefunden und nicht weiter drüber nachgedacht. Das ist wie gesagt nicht-Arduino-Code.

Das mit der Dopplung stimmt, werd ich noch raus nehmen, danke für den Hinweis.
 
OP
hcy

hcy

Geoguru
Hippokrat schrieb:
Sehr schön gemacht und klasse Idee, mich würde noch der genaue Verschlussmechanismus interessieren, der mich bei meinen Projekten immer etwas ärgert. :hilfe:
siehe http://geocracher.blogspot.com/
Genau an sowas hab ich mich orientiert, ich hab's aber nicht so schön hinbekommen wie bei Dir. Bei mir hakt dieses Kreuz am Servo einfach hinter ein Platik-Nibbel in der Kiste innen wenn die Kiste zu ist. Ist mechanisch nicht optimal, roher Gewalt hält das nicht stand. Für das Thema der Verriegelung gibt's hier ja noch einen anderen Thread.
Das ganze war aber wie gesagt eher Proof-of-concept für mich und noch kein richtiger Cache.
 

thomas_st

Geowizard
hcy schrieb:
thomas_st schrieb:
Sieht WinAVR sehr ähnlich
Ja, zum Glück (um genau zu sein sieht es C ähnlich, WinAVR ist ja nur eine Portierung des GCC für Windows).
Na ja, erkannt habe ich es jetzt eher am dem, was nicht im C-Standard ist - nämlich der Deklaration der ISR :p

hcy schrieb:
Keine Ahnung, das hab ich so gefunden und nicht weiter drüber nachgedacht. Das ist wie gesagt nicht-Arduino-Code.
Das ist Code den ich ganz genau so auch in WinAVR geschrieben haben könnte.

Zur Erklärung meiner Vermutung: Nach der Initialisierung des Timers, wird der Counter mit 131 vorbelegt. Er wird jetzt bis 255 hochgezählt, wird per Hardware auf 0 gesetzt und löst den IRQ aus. In der zugehörigen ISR wird er aber gleich wieder mit 131 belegt. Damit läuft der Timer immer nur von 131 bis 255 - also 124 (oder 125) Takte.
Das gleiche könnte man mit den CTC (clear timer on compare match) Mode errreichen. Dabei wird der Wert, bis zu dem der Timer hochläuft, hardwaremäßig auf den Wert gesetzt, der im OCR2A Register steht. Der Timer läuft also nur noch von 0 bis OCR2A, wird per Hardware auf 0 gesetzt und löst einen IRQ aus. Wenn OCR2A = 125 (oder 124 - müsste man mal genau durchrechnen, incl etwaiger zusätzlicher Takte beim Überlauf) ist, erreichst Du damit genau das selbe, was Du momentan programmtechnisch realisiert hast - nur ohne den Timer in der ISR immer wieder neu setzen zu müssen. Einziger Unterschied - was wird nicht mehr der Overflow IRQ, sonder der Compare Match A IRQ ausgelöst.

Viele Grüße,
Thomas(_st)
 
OP
hcy

hcy

Geoguru
Ok, danke für den Hinweis, so tief hab ich mich mit dem Thema noch nicht beschäftigt. Hättest Du denn auch ein Code-Snippet wie das konkret aussehen muss
?
 

thomas_st

Geowizard
hcy schrieb:
Hättest Du denn auch ein Code-Snippet wie das konkret aussehen muss
?
Jep. Aber ungetestet ... (ich habe da auch noch einige Anweisungen im Setup zusammengefaßt)
Code:
void setup() 
{ 
  /* Configure timer2 in CTC mode */  
  /* and configure the prescaler to CPU clock divided by 128 */  
  TCCR2A = 2<<WGM20;  
  TCCR2B = 0<<WGM22 | 5<<CS20;  
   
  /* Select clock source: internal I/O clock */  
  ASSR &= ~(1<<AS2);  
   
  /* We need to calculate a proper value to load the timer counter. 
   * (CPU frequency) / (prescaler value) = 16MHz / 128 = 125000 Hz => T = 8us. 
   * (desired period) / T = 1ms / 8us = 125. 
   */  
  /* Set value to OCR2A */  
  OCR2A = 125;     

  /* Finally enable Compare Match A interrupt */  
  TIMSK2 |= (1<<OCIE2A);  

  [...]

   /* Hinweis: die Geschichte mit enable und disable
    * des IRQ ist vermutlich unnötig, da anduino
    * global noch ein cli (clear IRQ flag)
    * drumherum setzt - wenn nicht, dann am Anfang ein
    * cli(); und am Ende der Funktion ein sei();
    */
} 

[...]

/* Hier ist die ISR vom Timer-Interrupt, da wird nur die Zeit gezählt (jede ms) */
/* nun aber der Compare Match A - IRQ */

ISR(TIMER2_COMPA_vect) {  
   /* Reload the timer */  
   /* TCNT2 = tcnt2;   <--- alles bis auf diese Zeile */  
   [...]
}

HTH,
Thomas(_st)
 

englishfire

Geomaster
Ich bin als Anfänger gerade dabei das Ganze hier nachzubauen. Allerdings möchte ich anstatt der Potis entweder "push buttons" oder eine Folientastatur verwenden (siehe: http://www.komputer.de/zen/index.php?main_page=product_info&cPath=25&products_id=119). Kann mir jemand sagen, was ich an dem Programmcode verändern muss?
 
OP
hcy

hcy

Geoguru
Für meine nächste "Kiste" plane ich genau sowas, hab's aber auch noch nicht ausprobiert. Muss mir erst mal eine Tastatur besorgen.
Noch cooler wäre ein Nummernschalter (Wählscheibe) von einem alten Telefon, allerdings ist die Anbindung da ein wenig trickreicher.
 

englishfire

Geomaster
Super coole Idee.
Habe gerade im Internet das hier gefunden:
// Controlling a telefone dialer
//
// Peter Heß alias Elo Mai 2011
//
// Version 12
//
#include <mstimer2.h>
#include <eventfuse.h>
#include <bounce.h>

// Configuration /////////////////////////////////////////////////////////////////////////////

// Colors of the Line
// WHITE -> DIALERREADY
// YELLOW -> DIALPULSE
// BROWN -> GND

// set pin numbers:
const int PIN_DIALREADY = 12; // WHITE CABLE
const int PIN_DIAL = 11; // YELLOW

void setup() {

Serial.begin(9600); // used for debugging
Serial.println( "Start" ); // used for debugging

initDialer();
}

void loop() {

int result = dialerLastResult();
if ( result != -1) {
Serial.print("dialed:");
Serial.println(result);
}

delay(10);
}

// BEGIN DIALER /////////////////////////////////////////////////////////////////////////////////

/*
* A small libary to decode the impulses send from an "Wählschreibe" of an old german
* telefon.
* ____________________________________________
* DIALREADY: ___| __ __ __ __ |______
* DIALPULSE: _______________________| |__| |__| |__| |__________
* ^ ^ ^ ^
* Start dialing Release Disk last Dialing finished
* turn to number eg. number 3 Pulse
*
*/

// Instantiate a Bounce object with a 5 millisecond debounce time
Bounce bouncerDial = Bounce( PIN_DIAL,5 );
Bounce bouncerReady = Bounce( PIN_DIALREADY,5 );

int _lastResultCounter = -1;
int _dialerResultCounter=-1;
int _dialerLastPulses = -1;

/*
Init the Dialer. PIN_DIALREDAY and PIN_DIAL must be defined
*/
void initDialer() {

pinMode(PIN_DIALREADY, INPUT);
digitalWrite(PIN_DIALREADY, HIGH); // pullup widerstand aktivieren

pinMode(PIN_DIAL, INPUT);
digitalWrite(PIN_DIAL, HIGH); // pullup widerstand aktivieren

/*
* Downcounter wie oft bur aufgerufen werden muss, bis der Callback durchgeht
* bei jedem burn(n) werden fuses verbraucht und wenn der Wert auf o geht,
* brennt die Sicherung durch...
*/
int fuse = 10;
eventFuse.newFuse( fuse, -1, DialerCheckForActivities );
}

/*
* Get the dial result. Returns -1 if there ist no
* new value available.
*/
int dialerLastResult() {
eventFuse.burn(1);
if (_lastResultCounter == _dialerResultCounter) {
return -1; // no new result
}
_lastResultCounter = _dialerResultCounter;
return _dialerLastPulses;
}

const int DIALREADYRUHELAGE = HIGH;

/*
* Prüfe ob die Wählscheibe aus der Ruhelage gebracht wurde. Ab diesem Moment ist "Wählen" angesagt!
* Callbackroutine called from fuse
*/
void DialerCheckForActivities(FuseID, int){

if ( digitalRead(PIN_DIALREADY) != DIALREADYRUHELAGE) {
_dialerLastPulses = WaehlscheibeAuswerten();
_dialerResultCounter++; // new data available
}
}

/*
Es wird solange in dieser Routine geblieben, bis der Ruhekontakt
wieder frei gegeben wurde und ein Wahlergebnis vorliegt
*/
int WaehlscheibeAuswerten(){

// Wählscheibe wurde bewegt

// initialisiere den Impulszaehler
int impulszaehler=-1;

// aktualisiere den Zustand der "Ruhekontaktes"
bouncerReady.update();
while( bouncerReady.read() != DIALREADYRUHELAGE)
{
// Es darf gewählt werden!

bouncerReady.update ( );
bouncerDial.update ( );

// Wenn kein Impuls anliegt, dann tue nichts
while( bouncerReady.read() != DIALREADYRUHELAGE
&& bouncerDial.read() == LOW )
{
bouncerReady.update ( );
bouncerDial.update ( );
}
// Loope nun so lange bis der Kontakt wieder frei gegeben wird.

bouncerReady.update ( );
bouncerDial.update ( );
while( bouncerReady.read() != DIALREADYRUHELAGE
&& bouncerDial.read() == HIGH )
{
bouncerReady.update ( );
bouncerDial.update ( );
}
// Wahl um erhöhen
impulszaehler++;
}

return impulszaehler;
}

// END DIALER /////////////////////////////////////////////////////////////////////////////////

Vielleicht hilft es dir weiter.

Maik
 
OP
hcy

hcy

Geoguru
Das Problem der meisten Keypads ist dass man viele Pins am Arduino braucht, ich mag da ja immer I2C-Lösungen. Hab da aber noch nichts vernünftiges gefunden.
 
Oben