Forum: Mikrocontroller und Digitale Elektronik ATmega 328 zu langsam für 40kHz TLC549?


von detsbet (Gast)


Lesenswert?

Hallo Leute,

ich habe gestern einen gebrauchen Arduino Duemilanove bekommen, und 
direkt ein wenig darum herumgespielt. Der AVR ist ein ATmega 328 mit den 
normalen 16Mhz Taktrate. Jetzt habe ich, weil er hier herumflog mal 
einen TLC 549 drangesteckt und versuche den auszulesen.
1
#include <TimerOne.h>
2
int CS_pin = 4;   // Pin für das CS Signal
3
int IO_clock = 2; //Pin für das I/O Clock Signal
4
int DATA_pin = 3; //Der Pin für die ausgelesenen Daten
5
int val = 0;     //Ausgelesener Wert
6
int rounds = 0;
7
8
void setup()
9
{
10
  pinMode(CS_pin, OUTPUT);   //CS ist Wandlungsanzeige
11
  pinMode(IO_clock, OUTPUT); //IO ist Taktsignal
12
  pinMode(DATA_pin, INPUT);  //Hier fallen dann später die konvertierten Werte heraus
13
  Serial.begin(9600); //Serielle Ausgabe anmachen
14
  Timer1.initialize(150);
15
  Timer1.attachInterrupt(getAnalogValues);
16
}
17
18
void getAnalogValues()
19
{
20
  digitalWrite(CS_pin, HIGH); //Wandlung auf aus
21
  digitalWrite(CS_pin, LOW); //Wandlung auf an
22
  
23
  digitalWrite(IO_clock, LOW); //Taktsignal auf LOW
24
  
25
  val = digitalRead(DATA_pin)*128; //Höchstwertigstes Bit lesen
26
27
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
28
  digitalWrite(IO_clock, LOW);
29
  val = val + digitalRead(DATA_pin)*64; //Bit 6 lesen und addieren
30
  
31
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
32
  digitalWrite(IO_clock, LOW);  
33
  val = val + digitalRead(DATA_pin)*32; //Bit 5 lesen und addieren
34
35
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
36
  digitalWrite(IO_clock, LOW);  
37
  val = val + digitalRead(DATA_pin)*16; //Bit 4 lesen und addieren
38
  
39
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
40
  digitalWrite(IO_clock, LOW);  
41
  val = val + digitalRead(DATA_pin)*8; //Bit 3 lesen und addieren
42
43
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
44
  digitalWrite(IO_clock, LOW);  
45
  val = val + digitalRead(DATA_pin)*4; //Bit 2 lesen und addieren
46
  
47
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
48
  digitalWrite(IO_clock, LOW);
49
  val = val + digitalRead(DATA_pin)*2; //Bit 1 lesen und addieren
50
  
51
  digitalWrite(IO_clock, HIGH); //Einen Taktzyklus
52
  digitalWrite(IO_clock, LOW);
53
  val = val + digitalRead(DATA_pin); //Bit 0 lesen und addieren
54
  
55
  rounds = rounds + 1;
56
}
57
58
void loop(){ //Das wird ausgeführt, wenn noch Zeit ist
59
    if (rounds > 1000){
60
      Serial.print(rounds); //Wert an den Rechner senden
61
      Serial.print("\n");
62
      rounds = 0;
63
      }
64
}

Das funktioniert auch ganz gut. Die loop führt er natürlich nur aus, 
wenn getAnalogValues() nicht länger als 150ms braucht. Das ist auch mein 
Problem. Wenn ich den Timer herunterdrehe, so das er alle 140ms oder 
weniger den Interrupt auslöst, ist Stille auf der seriellen Leitung. Er 
findet also keine Zeit mehr die Bedingung unten zu durchlaufen.

Jetzt stellt sich mit die Frage: Wie macht man sowas "richtig"? 
Irgendwie ist das ja alles ziemlich eklig, aus dem IO_clock fällt z.B. 
irgendein verkrüppeltes Rechteck statt eines sauberen Taktes mit immer 
gleicher Frequenz raus. Erzeugt man, wenn man schneller sein will eine 
PWM auf dem IO_clock und wie syncronisiert man das dann mit dem 
Auslesen? Und wie speichert man die Werte richtig heraus, das 
multiplizieren mit den Vorfaktoren dürfte ja auch ziemlich langsam sein.

Ich hoffe, jemand kann mit da etwas weiterhelfen,
detsbet

von spess53 (Gast)


Lesenswert?

Hi

>Jetzt stellt sich mit die Frage: Wie macht man sowas "richtig"?

Man benutzt SPI.

MfG Spess

von H.Joachim S. (crazyhorse)


Lesenswert?

Ohne jetzt verstanden zu haben, wo dein eigentliches Problem liegt:
deine getAnalogValues() solltest du noch mal überarbeiten.
1. Schleife benutzen
2. es ist unnötig, erst zu multiplizieren und dann zu addieren, schieben 
und einlesen reicht :-)

for (loop=0, val=0;loop<8;loop++)
   {clock=1;
    clock=0;
    val=val<<1;
    if (Data_Pin) val++;;
    }

so in etwa, es könnten Fehler enthalten sein :-)

von Peter D. (peda)


Lesenswert?

digitalWrite() ist ein schnarchlangsamer Funktionsaufruf.
Man kann die Pins auch direkt setzen, dürfte mindestens Faktor 20 
schneller sein.


Peter

von Karl H. (kbuchegg)


Lesenswert?

Ich schätze mal, dass die Funktionen digitalWrite und digitalRead ob 
ihrer Allgemeinheit jede Menge Taktzyklen verbrutzeln werden. Da greift 
man direkt auf die Portpins zu und ermöglicht so dem Compiler, die 
schnellen Bit Instuktionen für Ports zu benutzen.

Allerdings sind 150ms eine Menge Holz. Das sind über den Daumen 
120-TAUSEND Prozessor Instruktionen bei 1Mhz. So umständlich kann 
digitalWrite bzw digitalRead gar nicht implementiert sein, dass du da in 
Zeitnot kommst.

Bist du sicher, dass das hier

Timer1.initialize(150);

tatsächlich Millisekunden von einem Interrupt zum nächsten angibt?

von ... (Gast)


Lesenswert?

nicht zu vergessen die CLKDIV8 Fuse zu controllieren, sonst werden aus 
den 16MHz mal schnell 2MHz.

von Tom (Gast)


Lesenswert?

detsbet schrieb:
> as ist auch mein
> Problem. Wenn ich den Timer herunterdrehe, so das er alle 140ms oder
> weniger den Interrupt auslöst, ist Stille auf der seriellen Leitung. Er
> findet also keine Zeit mehr die Bedingung unten zu durchlaufen.

RTFM:

initialize(period)

You must call this method first to use any of the other methods. You can 
optionally specify the timer's period here (in microseconds), by 
default it is set at 1 second. Note that this breaks analogWrite() for 
digital pins 9 and 10 on Arduino.

von digitaler fritz (Gast)


Lesenswert?

wer Assembler benuetzen kann ist hier klar im Vorteil !

von detsbet (Gast)


Lesenswert?

Erst einmal danke für die Anregungen.
Habe da wohl "ms" mit Mikrosekunden verdreht, ich meinte mit "ms" 
eingentlich 10^-6 Sekunden. Was ist denn die konforme Abkürzung für 
diese Zeiteinheit?
1
#include <TimerOne.h>
2
3
/*
4
CS_pin = 4    Pin für das CS Signal
5
IO_clock = 2  Pin für das I/O Clock Signal
6
DATA_pin = 3  Pin für die ausgelesenen Daten
7
*/
8
int val = 0;     //Ausgelesener Wert
9
int rounds = 0;
10
11
void setup()
12
{
13
  pinMode(4, OUTPUT);   //CS ist Wandlungsanzeige
14
  pinMode(2, OUTPUT); //IO ist Taktsignal
15
  pinMode(3, INPUT);  //Hier fallen dann später die konvertierten Werte heraus
16
  
17
  Serial.begin(9600); //Serielle Ausgabe anmachen
18
  Timer1.initialize(30);
19
  Timer1.attachInterrupt(getAnalogValues);
20
}
21
22
void getAnalogValues()
23
{
24
  val = 0;
25
  digitalWrite(4, HIGH); //Wandlung auf aus
26
  digitalWrite(4, LOW); //Wandlung auf an
27
   
28
  if (PIND & (1<<PD3)) val++; //MSB lesen, also addieren wenn auf PD3 eine 1 anliegt
29
    
30
  for (int i=0; i<7; i++){
31
    //Ein Taktzyklus für den IO_clock
32
    PORTD = PORTD | B00000100; // Setzt nur den Pin 2 auf High, der Rest bleibt erhalten (OR)
33
    PORTD = PORTD ^ B00000100; // Setzt nur den Pin 2 auf Low, der Rest bleibt erhalten (XOR)
34
35
    val <<= 1; //Ein Bit nach links schieben
36
    if (PIND & (1<<PD3)) val++; //restliche Bits lesen
37
  }
38
    
39
  rounds = rounds + 1;
40
}
41
42
void loop(){ //Das wird ausgeführt, wenn noch Zeit ist
43
    if (rounds > 1000){
44
      Serial.print(val); //Wert an den Rechner senden
45
      Serial.print("\n");
46
      rounds = 0;
47
      }
48
}
Jetzt kann man den Timer problemlos auf 30 Mikrosekunden stellen, und es 
kommen immer noch die richtigen Werte aus dem ADC gefallen. Das obere 
digitalWrite habe ich gelassen, wenn ich das ersetzt habe gabs Probleme.

Danke auf jeden Fall, so ist die Lösung auch deutlich eleganter.

von ... (Gast)


Lesenswert?

detsbet schrieb:
> 10^-6 Sekunden. Was ist denn die konforme Abkürzung für
>
> diese Zeiteinheit?

µs

von Karl H. (kbuchegg)


Lesenswert?

Tu dir selbst einen Gefallen
1
#include <TimerOne.h>
2
3
#define CS_PIN    4    // Pin für das CS Signal
4
#define IO_CLOCK  2    // Pin für das I/O Clock Signal
5
#define DATA_PIN  3    // Pin für die ausgelesenen Daten
6
7
...
8
9
10
void setup()
11
{
12
  pinMode(CS_PIN, OUTPUT);   //CS ist Wandlungsanzeige
13
  pinMode(IO_CLOCK, OUTPUT); //IO ist Taktsignal
14
  pinMode(DATA_PIN, INPUT);  //Hier fallen dann später die konvertierten Werte heraus
15
16
....
17
18
19
void getAnalogValues()
20
{
21
  val = 0;
22
  digitalWrite(CS_PIN, HIGH); //Wandlung auf aus
23
  digitalWrite(CS_PIN, LOW); //Wandlung auf an
24
   
25
  if (PIND & (1<<DATA_PIN))
26
    val++; //MSB lesen, also addieren wenn auf PD3 eine 1 anliegt
27
    
28
  for (int i=0; i<7; i++){
29
    //Ein Taktzyklus für den IO_clock
30
    PORTD |= (1<<CS_PIN);
31
    PORTD &= ~(1<<CS_PIN);
32
33
    val <<= 1;               //Ein Bit nach links schieben
34
    if (PIND & (1<<DATA_PIN))
35
      val++;                 //restliche Bits lesen
36
  }
37
    
38
  rounds = rounds + 1;
39
}

liest sich doch gleich viel besser und wenn du mal andere Pins benutzen 
willst/musst brauchst du nur an 1 Stelle entsprechend ändern.

Und das XOR zum löschen war zwar hier nicht so ganz falsch, ist aber vom 
vorhergehenden Statement abhängig, was keine so gute Idee ist. Wenn du 1 
Bit auf 0 setzen willst, dann schreib das auch so: Bit auf 0 setzen. 
Alles andere kann in der Zukunft nämlich auch schon mal ins Auge gehen, 
wenn man die genauen Zusammenhänge nicht mehr im Kopf hat.

von detsbet (Gast)


Lesenswert?

Danke Karl Heinz, das ist natürlich übersichtlicher. Deinen (wohl 
gewollten) Fehler, das du unten den CS_pin und den IO_clock verdreht 
hast habe ich aber erst nach 5 Minuten Suche gefunden. Ist so auch 
wirklich sauberer zu Lesen und ggf. einfacher zu Ändern.

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.