Forum: Mikrocontroller und Digitale Elektronik Binärzähler in C - STK - 500


von Samson (Gast)


Lesenswert?

Hi Leute,
hab mal in C eine Binärzähler programmiert, der einfach hochzählen soll
1
#include <avr/io.h>
2
3
volatile uint8_t zaehler ;  
4
volatile unsigned int i;
5
6
void delay(){
7
8
  for(i=0;i<65535;i++)
9
  {
10
     
11
  }
12
}
13
14
15
int main (void)
16
{
17
  DDRB = 0xff;    
18
  DDRD = 0x00;    
19
  PORTB = 0xff;  
20
  PORTD = 0xff;  
21
  zaehler = 0;     // Anfangswert für Zählvariable ist Null
22
23
while (1)
24
{
25
  count++;      // counter hochzählen
26
  delay();      // Funktion delay aufrufen
27
  PORTB = ~count;    //invertierte Ausgabe, da 1 (high) die LEDs ausschaltet
28
            
29
}
30
return 0;
31
}


Leider habe ich die for Schleife eher zufällig bzw. auf gut Glück so 
gestaltet. Daher meine Frage, wie das da genau fuktioniert. Man könnte 
theoretisch auch zwei ineinander verschachtelte machen.


ich hatte vor, sowas wie eine Warteschlange zu machen mit der delay 
funktion, sodass im genauen Sekundentakt bei 8 Mhz Prozessor die Bits 
gesetzt werden.

von Karl H. (kbuchegg)


Lesenswert?

Samson schrieb:

> Leider habe ich die for Schleife eher zufällig bzw. auf gut Glück so
> gestaltet.

Und sobald du dem Compiler erlaubst zu optimieren, ist es vorbei mit der 
Funktionalität :-) Ein guter Compiler, und dazu muss er noch nicht 
einmal besonders gut drauf sein, schmeisst das einfach raus, weil es 
augenscheinlich keine Funktion erfüllt ausser Rechenzeit verbraten. Und 
genau das ist die Domäne des Optimizers: du schreibst das Programm und 
der Compiler kümmert sich darum, dass es schnell wird.

> Daher meine Frage, wie das da genau fuktioniert.

Wie was genau funktioniert.

> ich hatte vor, sowas wie eine Warteschlange zu machen mit der delay
> funktion, sodass im genauen Sekundentakt bei 8 Mhz Prozessor die Bits
> gesetzt werden.

Warum nimmst du nicht einfach die Funktion _delay_ms()
Die ist so geschrieben, dass die angegebenen Millisekunden so 
einigermassen genau eingehalten werden. Längere Timings erledigt man 
dann sowieso nicht mittels _delay, sondern setzt einen Timer dafür ein. 
Dann klappts nämlich auch wieder damit, dass der µC scheinbar mehrere 
Dinge gleichzeitig macht.

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Warteschleifen_.28delay.h.29

von g457 (Gast)


Lesenswert?

busy-waiting: _delay_ms(), _delay_us(), beider in der delay.h (Achtung, 
Doku lesen!). Die leere for-schleife wird dir ein anständiger Compiler 
nämlich wegoptimieren.

> [..] sodass im genauen Sekundentakt [..]

Nimm nen Timer, das ist einfacher(tm) und einfacher(tm) genauer(tm).

von Samson (Gast)


Lesenswert?

nunja wollte wie gesagt im Sekundentakt das nächstfolgende Bitmuster 
ausgeben. und wollte mit einer geeignet langen Warteschleife das Ganze 
selber realisieren. UNd die länge der Warteschleife wollte ich dann 
selber runtertunen bis etwa 1 Sekunde.

von g457 (Gast)


Lesenswert?

<quickndirty>_delay_ms(1000);</dasradnichtneuerfind>

von Samson (Gast)


Lesenswert?

Nun ,aj, hätte mich trotzdem interessiert, wie ich das anstelle und mein 
gewählter Weg war ja nicht ganz so falsch oder?

von Marcus B. (raketenfred)


Lesenswert?

für den anfang ok

- aber für profis eig nogo^^

aber du sollst ja lernen, da ist kreativität auch gut

Wenn du eine genaue Sekunde mit Timer haben willst guck mal hier im 
wiki, da ist ein Artikel drin, das sollte genau genug sein (1,5s/d wenn 
ichs richtig im kopf habe)

von Dennis U. (atmegadennis)


Lesenswert?

Hallo Samson,

du könntest in Deine Schleife ein einfaches nop einfügen und schon wirds 
besser.

Zur Funktion, du hast deinen Takt und mit jedem Takt wird ein Befehl 
ausgeführt, da du in C Proggrammierst, müsstet du Dir mal den 
Compilierten Code anschauen und gucken wieviele asm- Befehle der 
Compiler aus deiner Schleife macht. Danach einfach die Anzahl Deiner 
befehle durch die Frequenz und dann weisst du wie lange du für ein i 
brauchst. Danach sollte es auch kein Prob sein das ganze auf eine 
Sekunde hinzubekommen. Nur wie schon öfters erwähnt ist der weg mit dem 
delay_ms schöner und schneller, da du dir keine Gedanken  machen musst.

Gruß

Dennis

von M. M. (miszou)


Lesenswert?

Hi,

@quickndirty ist ja manchmal ganz gut, sollte dann aber funktionieren.
In der delay.h stehen maximale die Werte mit denen die delays aufgerufen 
werden können und auch noch ungefährt den Wartezeien entsprechen die man 
sich erhofft.

@Samson  Schau die den disassambler an zähl die benötigte Taktzahl für 
die Abarbeitung der Befehle zusammen und du weißt wie lange deine 
Schleife braucht.


Gruß MISZOU

von Dennis U. (atmegadennis)


Lesenswert?

habe soeben noch einen Fehler entdeckt.

  count++;      // counter hochzählen

ich denke sollte

  zaehler++;      // counter hochzählen


heissen.

von samson (Gast)


Lesenswert?

danke vorab. Werde mir das Ganze genauer anschauen. Ist trotzdem 
interessant, sowas mal selber zu programmieren.

von Peter D. (peda)


Lesenswert?

Samson schrieb:
> ich hatte vor, sowas wie eine Warteschlange zu machen mit der delay
> funktion, sodass im genauen Sekundentakt bei 8 Mhz Prozessor die Bits
> gesetzt werden.

Das kannste voll vergessen, das wird nie was.
Ein Delay berücksichtigt nicht die Programmlaufzeit und Interrupts.

Genaue Zeiten gehen nur mit einem Timer. Deshalb hat auch jeder MC 
mindestens einen Timer.


Peter

von Peter D. (peda)


Lesenswert?

samson schrieb:
> Ist trotzdem
> interessant, sowas mal selber zu programmieren.

Dann aber auch nur zum Lernen, daß das der falsche Ansatz ist.


Peter

von Karl H. (kbuchegg)


Lesenswert?

samson schrieb:
> danke vorab. Werde mir das Ganze genauer anschauen. Ist trotzdem
> interessant, sowas mal selber zu programmieren.

Wenn du etwas dabei lernen willst, dann sieh dir den Code der _delay_xx 
Routinen an. Du hast den Source Code auf deiner Festplatte. Header Files 
sind auch nur Text-Dateien, die man im Editor öffnen und anschauen kann.

Allerdings: Zu durchschauen, warum da einige Einzelheiten genau so sind, 
wie sie sind, ist nicht mehr so einfach. Aber probiers einfach.

Abgesehen davon lernst du bei deiner
1
void delay(){
2
3
  for(i=0;i<65535;i++)
4
  {
5
     
6
  }
7
}

nicht viel, ausser dass die Laufzeit eines Programmes von vielen 
Faktoren, nicht zuletzt Optimizer, verwendete Datentypen und verwendete 
Taktfrequenz abhängt. Denkt man einmal genauer darüber nach, dann sollte 
das allerdings nicht sonderlich verwunderlich sein.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>>>> runtertunen
Das ist jetzt mal ein Wort wie kühlkochen oder dunkelleuchten oder 
geradeauskurven  ;-)

von Peter H. (the_ride)


Lesenswert?

Hi!

Ich habe mir mal eine Binärled anzeige gemacht und einfach
hoch bzw runter zählen lassen
1
/*
2
 * main.c
3
 *
4
 *  Created on: 03.09.2009
5
 *      Author: The_ride
6
 */
7
8
#include <avr/io.h>
9
#include <stdint.h>
10
#include <util/delay.h>
11
12
int a =2;
13
int x = 0;
14
int  i = 100;
15
int main(void)
16
{
17
18
19
  DDRD = 0xff;
20
  DDRC = 0xff;
21
  DDRB = 0xff;
22
23
24
  //main loop
25
    while(1)
26
  {
27
       x = x - 1;
28
      PORTD=x;
29
30
31
     {
32
          _delay_ms(i);
33
       }
34
35
36
      /*
37
     PORTD = 0b10110011;
38
     {
39
      _delay_ms(i);
40
     }
41
*/
42
43
44
45
  }
46
47
}

cya The_ride

von Karl H. (kbuchegg)


Lesenswert?

Peter Hartmann schrieb:
> Hi!
>
> Ich habe mir mal eine Binärled anzeige gemacht und einfach
> hoch bzw runter zählen lassen

Du solltest unbedingt den weiter oben angegebenen Link bzw. die Doku der 
Funktion im Header File lesen.

>       _delay_ms(i);

Keine Aufrufe der Funktion _delay_ms mit Variablen!

_delay_ms ist darauf angewiesen, dass der Optimizer einige Ausdrücke, 
die zur Berechnung der internen Schleifenwiederholungen benötigt werden, 
wegoptimieren kann. Das geht aber nur, wenn er die Zahlenwerte kennt!

Um _delay_ms mit einigermassen korrekten Zeiten benutzen zu können sind 
2 Dinge erforderlich:
* keine Variablen als Argumente
* Optimizer muss eingeschaltet sein

Variable delay Zeiten macht man so
1
void myDelay( unsigned int time )
2
{
3
  unsigned int i;
4
5
  for( i = 0; i < time; ++i )
6
    _delay_ms( 1 );
7
}

Durch die for-Schleife erhält man noch einen kleinen Fehler rein. Da 
aber _delay_ms swieso nicht das genaueste ist, spielt das im Regelfall 
keine grosse Rolle.

von Samson (Gast)


Lesenswert?

Ehrlich gesagt hat mir das Tutorial auch nix gebracht was die Durchläufe 
angeht. Wie genau kann ich das analysieren, wie ich bei einem 8Mhz 
Prozessor jeweils 1 Sekunde pro Schalten benötige, verstehe das nicht so 
genau ...

von avrn00b (Gast)


Lesenswert?

Wenns um Sekundengenauigkeit geht, würde ich eher einen geeigneten Quarz 
+ internen Prescaler verwenden und dann mit Interrupts arbeiten.
Hab ich bei meiner Binäruhr so erledigt.

von Karl H. (kbuchegg)


Lesenswert?

Man schreibt zunächst mal provisorischen Code. Dieser Code wird in 
seinem Kern aus einer Schleife bestehen.

Den Code jagt man durch den Compiler und sucht sich im Assemblerlisting 
besagte Schleife. Man muss allerdings sicher gehen, dass der Compiler 
immer denselben Code produzieren wird. Daher ist hier die Einstellung 
des Optimizers wichtig und auch die Details wie exakt der Schleifencode 
aussieht.

Mit dem Datenblatt des Prozessors geht man dann die Schleife durch und 
addiert für jeden Befehl in der Schleife die Anzahl der dafür benötigten 
Taktzyklen für jeden Assemblerbefehl.
Kennt man dann die Summe der Taktzyklen für einen Schleifendurchlauf, 
dann ist der Rest einfache Dreisatzrechnerei:

Wenn die Taktfrequenz y Megaherz beträgt, hat der µC genau y Takte in 1 
Sekunde zur Verfügung. Da 1 Durchlauf durch die Schleife x Takte 
benötigt, benötigtt man daher wieviele Durchläufe um möglichst genau z 
Takte zu verbraten, wenn z sich aus der in 1 Sekunde zur Verfügung 
stehenden Anzahl an Takten und der gewünschten Wartezeit ergibt?

Im Grunde ist das einfach nur eine Abart von:
Ein Arbeiter kann mit einer Schaufelladung 456 Gramm Sand bewegen und 
benötigt dazu 15 Sekunden.
Wieviel Sand muss man dem Arbeiter vorsetzen, damit er möglichst genau 
28 Minuten und 12 Sekunden beschäftigt ist?

von Peter D. (peda)


Lesenswert?

avrn00b schrieb:
> Wenns um Sekundengenauigkeit geht, würde ich eher einen geeigneten Quarz
> + internen Prescaler verwenden und dann mit Interrupts arbeiten.

Was wäre denn ein nicht geeigneter Quarz???

Wenn man Angst vor Interrupts hat, kann man die Timerflags auch im Main 
pollen und dann auf 1 setzen, um sie wieder zu löschen.
Man muß es nur vor dem nächsten Überlauf/Compare gemacht haben.


Peter

von Samson (Gast)


Lesenswert?

Ok, vielen Dank, das bringt mich etwas weiter, nur das ganze in meinem C 
Code zu optimieren wird dann der schwierigste Teil :-(

von Peter D. (peda)


Lesenswert?

Samson schrieb:
> nur das ganze in meinem C
> Code zu optimieren wird dann der schwierigste Teil :-(

Bravo.
Nun weißt Du also, warum man es nicht so macht.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Samson schrieb:
> Ok, vielen Dank, das bringt mich etwas weiter, nur das ganze in meinem C
> Code zu optimieren wird dann der schwierigste Teil :-(

Das sollst du auch gar nicht.
Diese Arbeit hat man dir bereits abgenommen.
Es reicht völlig wenn du im Prinzip weißt, wie _delay_ms 'mit der heißen 
Nadel' zusammengestrickt wurde. Ab hier übernimmst du und verwendest 
_delay_ms ganz einfach. Du lernst nichts dabei, wenn du dir selbst ein 
_delay_ms strickst. Arbeite lieber an den Dingen die dich wirklich 
weiterbringen. Zb. Wie man mit einem Timer arbeitet. Das bringt dich 
weiter! Nicht wie man Schleifen so hintrickst, dass sie möglichst genau 
20 Millisekunden verbraten und der Prozessor in dieser Zeit nichts 
anderes machen kann. Das ist eine Sackgasse. Ausser das man dort am Ende 
an die Wand fährt, lernt man nichts dabei, wenn man da voller 
Enthusiasmus mit Vollgas reindüst.

PS: Wie gut sind zb deine Kenntnisse was Stringverarbeitung in C angeht? 
Das wäre zb ein Feld, in dem du deine Zeit sehr viel besser investieren 
kannst als mit ödem schleifenzählen.
Was weißt du über Datentypen?
Wie stehts mit Bitmanipulationen?
Stellt dich ein Lauflicht vor große Probleme? Rechts rum / Links rum. 
Was, wenn nicht ein einzelnes Licht laufen soll, sondern ein Muster?

Das sind Problemkreise mit denen du dich beschäftigen kannst.

von Samson (Gast)


Lesenswert?

da hast du auch wieder recht, wollte eben von C dann das ganze auch in 
Assembler. Aber ich verstehe nicht mal diese GEschichte mit der 
BErechnung also in Zusammenhang mit 8MHz , Overlofws etc. ... vielleicht 
bin ich einfach zu blöd dafür. Versuche seit 2 Tagen das zu verstehen, 
klappt einfach nicht. ...

von Karl H. (kbuchegg)


Lesenswert?

Samson schrieb:
> da hast du auch wieder recht, wollte eben von C dann das ganze auch in
> Assembler. Aber ich verstehe nicht mal diese GEschichte mit der
> BErechnung also in Zusammenhang mit 8MHz , Overlofws etc. ... vielleicht
> bin ich einfach zu blöd dafür. Versuche seit 2 Tagen das zu verstehen,
> klappt einfach nicht. ...

:-)
Ein Kind benötigt zum Essen 1 Apfels 2 Minuten.
Wieviele Äpfel musst du dem Kind geben, um es 2 Stunden zu beschäftigen.

Grundschulmathematik

von magnetus (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Ein Kind benötigt zum Essen 1 Apfels 2 Minuten.
> Wieviele Äpfel musst du dem Kind geben, um es 2 Stunden zu beschäftigen.
>
> Grundschulmathematik

Theorie, Theorie, Theorie...

Das Apfelthema ist wohl etwas komplexer. Ich möchte mal sehen, wie ein 
Kind in 2 Stunden 60 Äpfel vertilgt. Da dürfte dann sogar ein 
Strafrechtlicher Aspekt zu berücksigtigen sein (vorsätzliche 
Körperverletzung) ;o)

* duckundweg *

von avrn00b (Gast)


Lesenswert?

Naja, ein nicht geeigneter Quarz ist, wenn ich hinterher noch wie dumm 
rumrechnen muss, um aus der Zahl der Overflows eine Sekunde 
rauszubekommen. Ich will ja, dass mein Register einmal in der Sekunde 
überläuft, denn dann ist die Schleife denkbar einfach:

<pseudocode>
wenn interrupt signal overflow
dann zeit.sekunde = zeit.sekunde + 1;
</pesudocode>

ansonsten muss man sich rumschlagen mit

<pseudocode>
wenn interrupt signal overflow
wenn bescheuerte_variable == bescheuerte_zahl
dann zeit.sekunde = zeit.sekunde + 1;
bescheuerte_variable = 0;
sonst
bescheuerte_variable = bescheuerte_variable + 1;
</pseudocode>

Ich hab keine Lust, mich damit rumzuschlagen, am Ende 
verrechnet/vertippt man sich, und dann ist alles wieder fürn Arsch.
Ist einfach "sauberer", der Code, meinem Empfinden nach.

von Peter D. (peda)


Lesenswert?

avrn00b schrieb:
> Ich hab keine Lust, mich damit rumzuschlagen, am Ende
> verrechnet/vertippt man sich, und dann ist alles wieder fürn Arsch.

Naja, man kann sich höchstens beim Definieren der Quarzfrequenz 
vertippen.
Das Rechnen lasse ich immer den Compiler machen, also verrechnen geht 
schonmal nicht.

Ich nehme meistens Baudratenquarze die teilen sich zwar glatt, aber als 
Uhr ist ihre Toleranz etwas zu groß.
D.h. da ist ein Abgleich nötig. Dazu lasse ich die Uhr einige Wochen 
laufen und ermittele die Abweichung in Sekunden. Dann errechne ich 
daraus die wirkliche Frequenz, trage sie ein und compiliere neu. Danach 
stimmt die Uhr.
Man könnte sie auch mit nem Trimmer hinziehen, aber dazu braucht man nen 
sehr genauen Frequenzmesser oder sehr viel Geduld.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Peter Dannegger schrieb:

> D.h. da ist ein Abgleich nötig. Dazu lasse ich die Uhr einige Wochen
> laufen und ermittele die Abweichung in Sekunden. Dann errechne ich
> daraus die wirkliche Frequenz, trage sie ein und compiliere neu. Danach
> stimmt die Uhr.
> Man könnte sie auch mit nem Trimmer hinziehen, aber dazu braucht man nen
> sehr genauen Frequenzmesser oder sehr viel Geduld.

Ich hab um Weihnachten rum etwas ausprobiert:
Ich hab mich an die 50Hz Netzfrequenz gehängt und eine Uhr seit 
Weihnachten damit betrieben. Funktioniert ausgesprochen gut. Bis jetzt 
hab ich nur bei der Sommerzeitumstellung eingegriffen und der Vergleich 
mit der danebenstehenden Funkuhr lässt das Teil gut aussehen. 
Hardwaremässig war es kein grosses Problem, die Netzfrequenz hinter dem 
Trafo abzugreifen.

von Dominik Barth (Gast)


Lesenswert?

//----------------------------------------------------------------------
//=== Programm-Kopf ===
// Titel: Sekundenzahl Ausgabe Binär
// Funktion:
//    Gibt die Sekundezahl aus.
//    ...
// Beschreibung:
//    Wartet 1s schreibe dann auf PB.0-3 und PC.0-3
//    256er Vorteiler / 3036 Startwert
//    ...
// Prozessor: ATmega328P
// Takt   16 MHz
// Sprache: C
// Datum: 05.03.16
// Autor: Dominik Barth;
//----------------------------------------------------------------------

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdint.h>

uint8_t Sekundenaktuell = 0; //8 bit int
uint8_t Minutenaktuell = 0; //8 bit int

void setup(void) {
    DDRB = 15; //PORT B
    DDRC = 15; // PORT C

    TCCR1A = 0; // muss 0 sein wegen TIMER1_OVF
    TCCR1B = 4; // Vorteiler 256

    TIMSK1 = 1; // mit 1 maskieren
    TCNT1 = 3036; // Zählerwert 3036

    sei(); // global interrupt an
}

void anzeigeBinaer() {
    PORTB = Sekundenaktuell & 15;
    PORTC = Sekundenaktuell >> 4;
    PORTC |= (Minutenaktuell << 2);
}

int main(void){

  setup();

    while(1){

    }
}

 ISR(TIMER1_OVF_vect) {

    TCNT1 = 3036; // zählerwert 3036

    anzeigeBinaer();
    Sekundenaktuell++;
    if (Sekundenaktuell > 59)
    {
      Sekundenaktuell = 0;
      Minutenaktuell++;
    }

    if (Minutenaktuell > 3)
    {
      Minutenaktuell = 0;

    }

}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.