Forum: Mikrocontroller und Digitale Elektronik ATmega328/P ADXL345 i2c Sampling Rate SD-Karte


von Nils (Gast)


Lesenswert?

Liebe Freunde,

Ich habe einen Data-Logger mit ATmega328/P gebaut, welcher einen ADXL345 
über TWI/i2c ausliest und die Messwerte auf eine SD-Karte schreibt. Der 
ATmega läuft mit externen 8 Mhz Quarz ohne Prescaler.

Problem: der Data-Logger schaft gerade einmal ca. 6 Samples pro Sekunde. 
Ist das was man erwarten kann von diesem Setup, oder sollte deutlich 
mehr möglich sein?

Wäre mega froh um jeden Hinweis! Habe irgendwie die TWI/i2c Verbindung 
in Verdacht, aber weiss leider nicht wie ich weiter vorgehen kann um das 
Problem zu lösen.

Viele Grüsse

Niels

von Wolfgang (Gast)


Lesenswert?

Nils schrieb:
> Wäre mega froh um jeden Hinweis!

Gehe systematisch vor. Stelle fest, wo der Flaschenhals ist?

von Nebel Stocherer (Gast)


Lesenswert?

Nils schrieb:
> Ist das was man erwarten kann von diesem Setup

Von welchem Setup?

Du hast ja praktisch nichts gezeigt, wie sollen wir davon
eine Ahnung haben?

Nils schrieb:
> aber weiss leider nicht wie ich weiter vorgehen kann

Das Allermindeste wäre aufzuzeigen wie dein zeilicher
Ablauf aussieht, Programm-Sourcecode wäre besser.
Asl sicher sehe ich an dass man bei der SD-Karte viel
falsch machen kann dass es (nicht) schnell funktioniert.

Zweifel bestehen auch ob man überhaupt sampeln kann und
gleichzeitig auf die Karte schreiben ohne dass das System
ins Stolpern kommt. Kommt halt auf die Sample-Rate an.

von Ingo L. (corrtexx)


Lesenswert?

Nils schrieb:
> der Data-Logger schaft gerade einmal ca. 6 Samples pro Sekunde.
Laut Datenblatt kann er von 0,1-3,2kHz Daten ausspucken. Deine 6Hz sind 
vermutlich schlechter Software geschuldet

von Falk B. (falk)


Lesenswert?

@Nils (Gast)

>Problem: der Data-Logger schaft gerade einmal ca. 6 Samples pro Sekunde.
>Ist das was man erwarten kann von diesem Setup,

Nö.

> oder sollte deutlich mehr möglich sein?

Deutlich mehr. Ich hab mal einen DMX-Rekorder gebaut, der hat mit 22kB/s 
von/auf SD-Karte gelesen/geschrieben und nebenbei DMX512 mit voller 
Bandbreite abgearbeitet.

>Wäre mega froh um jeden Hinweis! Habe irgendwie die TWI/i2c Verbindung
>in Verdacht, aber weiss leider nicht wie ich weiter vorgehen kann um das
>Problem zu lösen.

Mit einer systematischen Fehlersuche. Man muss schrittweise das 
Programm vereinfachen und sehen, was dabei passiert. Nimm einfach mal 
die Sensorauswertung raus und schreibe konstante Testdaten auf deine 
SD-Karte.

Vermutlich hast du viel zuviele Warteschleifen etc. drin, die dir alles 
tierisch ausbremsen. Oder du öffnest und schließt deine Datei nach jedem 
Zugriff, was auch reichlich unsinnig ist.

von Falk B. (falk)


Lesenswert?

@Nebel Stocherer (Gast)

>Zweifel bestehen auch ob man überhaupt sampeln kann und
>gleichzeitig auf die Karte schreiben ohne dass das System
>ins Stolpern kommt. Kommt halt auf die Sample-Rate an.

Das kann man, wenn man weiß wie. Die zeitkritische Datenerfassung läuft 
per Interrupt, der Schreibvorgang auf SD-Karte als normale Funktion. 
Über einen FIFO werden die Daten gepuffert. Der SD-Zugriff per SPI 
kann beliebig unterbrochen werden, da kommt nix aus dem Takt.

von Niels (Gast)


Lesenswert?

Vielen Dank schon mal für die Antworten, mich hat erstmal interessiert 
ob es überhaupt möglich ist schneller zu sampeln. Ausserdem ist der Code 
nicht sehr schön geschrieben. Hier nun der Code mit welchem ich den 
ADXL345 auslese und die Messwerte auf die UART schreibe, ohne den 
SD-Card Teil. Damit erreiche ich ca. 16 Hz, also auch nicht sehr viel.

Mit einem Timer des ATmega wird alle 1/16 Sekunde ein Flag gesetzt. Ist 
das Flag nach der ADXL Messung nicht gesetzt, bedeutet dass dass das 
Sample zu langsam verarbeitet wurde und das Programm geht in einen 
Fehlerzustand.

Was wäre der nächste Schritt bei der Fehlersuche?
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <stdlib.h>
5
6
#include "uart.h"
7
#include "i2cmaster.h"
8
9
#ifndef F_CPU
10
#define F_CPU 8000000UL
11
#endif
12
13
#define BAUD 9600L
14
15
#define UBRR_VAL  ((F_CPU+BAUD*8)/(BAUD*16)-1)
16
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))
17
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD-1000)
18
19
#if ((BAUD_ERROR>10) || (BAUD_ERROR<-10))
20
  #error systematic Baud rate error >1%
21
#endif
22
23
//ADXL345 definitions
24
#define ADXL345_W  0x3A
25
#define ADXL345_R  0x3B
26
27
//programm state definitions
28
#define DO_MEASUREMENT 1
29
#define MEASUREMENT 2
30
#define MEASUREMENT_DONE 0
31
32
volatile uint8_t centi_seconds;
33
34
volatile uint8_t m_flag;                  // indicates whether a measurement should be done
35
36
ISR(TIMER1_COMPA_vect) {
37
38
  if(centi_seconds < 100){
39
    centi_seconds++;
40
  }
41
  else{
42
    centi_seconds = 0;
43
  }
44
45
  if(centi_seconds % 6 == 0){              // approx. 16.7 Hz
46
    m_flag = DO_MEASUREMENT;
47
  }
48
}
49
50
51
int main(void)
52
{
53
54
  // set prescaler to 1
55
  CLKPR = 0x80;
56
  CLKPR = 0x00;
57
58
  uart_init();
59
  uart_puts_p(PSTR("STARTING ADXL345 TEST\n"));
60
61
  // LED setup
62
  DDRB = (1<<PORTB1);                    // Port B1 as output
63
  PORTB = (0<<PORTB1);                  // LED off
64
65
  // configure timer1 (16bit) for generating 10ms clock signal
66
  OCR1A = (78-1);                      // 100.16 Hz
67
68
  TCCR1B = (1<<WGM12)|(1<<CS12)|(0<<CS11)|(1<<CS10);    // CTC mode and division by 1024
69
  TIMSK1 = 1<<OCIE1A;
70
71
  centi_seconds = 0;
72
  //seconds = 0;
73
74
  sei();
75
76
  char s[1];                        //buffer
77
78
  uart_puts_p(PSTR("INITIALIZING I2C LIBRARY\n"));
79
80
  i2c_init();                        // initialize I2C library
81
82
  uart_puts_p(PSTR("SETTING DEVICE ADDRESS AND WRITE MODE\n"));
83
  i2c_start_wait(ADXL345_W);                // set device address and write mode
84
  uart_puts_p(PSTR("WRITE ADDRESS\n"));
85
  i2c_write(0x2D);                    // write address = POWER_CTL of ADXL
86
  uart_puts_p(PSTR("SET MEASUREMENT BIT ON D3\n"));
87
  i2c_write((1<<3));                    // Set the measure bit on D3
88
  i2c_stop();                        // set stop conditon = release bus
89
90
91
  uart_puts_p(PSTR("ENTERING MEASUREMENT LOOP\n"));
92
93
  char buff[6];
94
  uint16_t x, y, z;
95
96
  uint8_t i;
97
98
  while(1){
99
100
    if(m_flag == DO_MEASUREMENT){
101
102
      m_flag = MEASUREMENT;
103
104
      i2c_start_wait(ADXL345_W);            // set device address and write mode
105
      i2c_write(0x32);                // write address = 32
106
      i2c_rep_start(ADXL345_R);            // set device address and read mode
107
108
      // multi byte read from sensor
109
      for(i=0; i< 5; i++){
110
        buff[i] = i2c_readAck();
111
      }
112
      buff[5] = i2c_readNak();
113
114
      i2c_stop();
115
      x = (((int)buff[1]) << 8) | buff[0];
116
      y = (((int)buff[3])<< 8) | buff[2];
117
      z = (((int)buff[5]) << 8) | buff[4];
118
119
      itoa(x, s, 10);
120
      uart_puts(s);
121
      uart_puts_p(PSTR(" \t"));
122
123
      itoa(y, s, 10);
124
      uart_puts(s);
125
      uart_puts_p(PSTR(" \t"));
126
127
      itoa(z, s, 10);
128
      uart_puts(s);
129
130
      uart_puts_p(PSTR("\n"));
131
132
       if (m_flag == DO_MEASUREMENT) {
133
        uart_puts_p(PSTR("\nFatal error -- unhandled tasks detected -- terminating.\n"));
134
        PORTB = (1<<PORTB1);
135
        while(1);
136
            }
137
      m_flag = MEASUREMENT_DONE;            // indicate that the measurement task has been completed
138
    }
139
140
    }
141
142
    return 0;                        // never reached
143
}

von Falk B. (falk)


Lesenswert?

@Niels (Gast)

>nicht sehr schön geschrieben. Hier nun der Code mit welchem ich den
>ADXL345 auslese und die Messwerte auf die UART schreibe, ohne den
>SD-Card Teil. Damit erreiche ich ca. 16 Hz, also auch nicht sehr viel.

Bei 9600 Baud? Schon mal über die Übertragungszeiten nachgedacht? Und 
nein, ich glaube dein UART läuft NICHT per FIFO und Interrupt, sondern 
einfachst per Polling und Warten. 1 Zeichen = 1ms

16 Hz == 62,5ms ~62 Zeichen.

>Was wäre der nächste Schritt bei der Fehlersuche?

Die UART-Ausgaben auf ein Minimum reduzieren, nämlich bestenfalls 1 
Zeichen ala '.', damit man was sieht, die restlichen uart_put_s 
auskommentieren.

Die itoa-Funktionen kann man erstmal drinlassen.

>if(centi_seconds % 6 == 0){

Sowas macht man nicht, denn das braucht eine echte Division, die eher 
aufwändig ist. Wenn du das haben willst, nimm einen einfachen Zähler von 
0-5. Das ist aber eher Nebensache und nicht der Grund für dein langsames 
Programm.

von Stefan F. (Gast)


Lesenswert?

Kleiner Tipp zur Optimierung, auch wenn das gerade nicht dein 
Hauptproblem ist:

> uart_puts_p(PSTR("\n"));

Das ist am Ende weniger effizient, als uart_put("\n") weil der lesende 
Zugriff auf den Flash Speicher wesentlich aufwändiger ist, als 
Lesezugriffe auf Strings im RAM. Ich benutze PSTR() daher nur für 
längere Strings.

Wenn du später wieder die Schreibzugriffe auf die SD Karte einbaust, 
dann bedenke, dass SD Karten Blockweise arbeiten. Einzelne Bytes zu 
schreiben wäre ganz schlecht. Du musst außerdem in den Pausen, wo die SD 
Karte beschäftigt ist, Daten in einem RAM Puffer sammeln. Da sind 100ms 
durchaus normal.

von Falk B. (falk)


Lesenswert?

@ Stefanus F. (stefanus)

>Das ist am Ende weniger effizient, als uart_put("\n") weil der lesende
>Zugriff auf den Flash Speicher wesentlich aufwändiger ist, als
>Lesezugriffe auf Strings im RAM.

Hör auf so einen Käse zu erzählen!!! Ein lds kostet 2 Takte, ein lpm 3. 
Das fällt beim Rest der Verarbeitung praktisch keine Mikrosekunde ins 
Gewicht!

Mann O Mann! Immer diese "Expertentips" 8-(

>Wenn du später wieder die Schreibzugriffe auf die SD Karte einbaust,
>dann bedenke, dass SD Karten Blockweise arbeiten. Einzelne Bytes zu
>schreiben wäre ganz schlecht.

Macht sowieso keiner, jede gescheite Lib hat mindesten einen Puffer für 
1 Sektor.

von Niels (Gast)


Lesenswert?

Merci für den Tipp mit der UART Falk! Habe den UART Code raus genommen, 
da lief es tatsächlich mit 100Hz. (Das weiß ich, weil über eine LED 
signalisiert wird, wenn die Sampling Rate nicht erreicht wird).

Für das Schreiben auf die SD-Karte verwende ich FatFS 
(http://elm-chan.org/fsw/ff/00index_e.html) zusätzlich etwa folgenden 
Code:
1
#include "ff.h"
1
f_mount(0, &Fatfs);
1
f_write(&File1, s, strlen(s), &bw);

Das ist aber offenbar wieder viel zu langsam. Wahrscheinlich wegen dem 
was Stefanus gesagt hat. Selbst wenn ich nur ein einzelnes Zeichen 
schreibe, werden keine 100Hz erreicht.

Wie weiter?

Ist FaFS die richtige library? Welche Library wäre ggfs. besser 
geeignet?

Angenommen ich würde die Daten nicht direkt auf die SD schreiben sondern 
zwischenspeichern bis etwas mehr Daten zusammengekommen sind, dann hätte 
ich ja immer noch das Problem, dass die Zeit zwischen zwei Samples nicht 
ausreicht um die Daten auf die SD-Karte zu schreiben. Wie löst man 
dieses Problem?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Niels schrieb:
> Angenommen ich würde die Daten nicht direkt auf die SD schreiben sondern
> zwischenspeichern bis etwas mehr Daten zusammengekommen sind, dann hätte
> ich ja immer noch das Problem, dass die Zeit zwischen zwei Samples nicht
> ausreicht um die Daten auf die SD-Karte zu schreiben. Wie löst man
> dieses Problem?

 Welches Problem ?

 Auch mit SPI und Mega328P schafft man zwischen 170KB und 220KB beim
 schreiben auf die SD-Karte.
 Selbst bei nur 170KB brauchst du weniger als 3ms pro Sektor (512Byt).

 Das reicht locker für 250 Samples pro sekunde und da bleibt sogar
 genügend Zeit für Ausgabe der Daten über USART übrig -
 (natürlich nicht mit 9600B, sondern mit 115KB).

 P.S.
 Es war mir sowieso niemals klar, warum Leute denken, daß es bei 9600B
 weniger Fehler gibt und alles zuverlässiger läuft als bei 115KB - ich
 spreche von USART.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Niels schrieb:
> Selbst wenn ich nur ein einzelnes Zeichen
> schreibe, werden keine 100Hz erreicht.

Das wäre auch der ungünstigste Fall...

Der beste Fall wäre das Schreiben von 512 Bytes am Stück, und der Offset 
im File muss auch durch 512 teilbar sein. Dann muss nämlich der 
Datensektor nicht von der Karte gelesen werden.

Aber eine SD Karte kann beim Schreiben auch schon mal für 300-500ms 
abtauchen. Daher müsste man den I²C Sensor im Interrupt auslesen und 
dort auch puffern.

von Jim M. (turboj)


Lesenswert?

Marc V. schrieb:
> Es war mir sowieso niemals klar, warum Leute denken, daß es bei 9600B
>  weniger Fehler gibt und alles zuverlässiger läuft als bei 115KB

Weil die Atmega nur 1MHz per default takten, erreicht man die 115200 
Baud einfach nicht.

Und 9600 ist die nächsthäufigste Baudrate, deren Teiler bei 1 MHz noch 
halbwegs aufgeht.

von Wolfgang (Gast)


Lesenswert?

Jim M. schrieb:
> Weil die Atmega nur 1MHz per default takten, erreicht man die 115200
> Baud einfach nicht.

Den Flash-Speicher vom ATmega lässt du doch auch nicht so, wie er vom 
Hersteller kommt. Was spricht also dagegen, beim Programmieren auch den 
Taktteiler abzuschalten?

von Falk B. (falk)


Lesenswert?

@ Niels (Gast)

>Merci für den Tipp mit der UART Falk! Habe den UART Code raus genommen,
>da lief es tatsächlich mit 100Hz. (Das weiß ich, weil über eine LED
>signalisiert wird, wenn die Sampling Rate nicht erreicht wird).

Hast du ein Oszi oder Logic Analyzer? Da kann man das genau messen. Am 
Anfang der Sequenz ein Pin auf HIGH setzen, am Ende auf LOW.

>f_mount(0, &Fatfs);

>f_write(&File1, s, strlen(s), &bw);

Klar.

>Das ist aber offenbar wieder viel zu langsam. Wahrscheinlich wegen dem
>was Stefanus gesagt hat.

NEIN!

> Selbst wenn ich nur ein einzelnes Zeichen
> schreibe, werden keine 100Hz erreicht.

Dann stimmt was nicht. Zeig uns deinen echten, vollständigen Code als 
Anhang.

>Ist FaFS die richtige library?

Ja, die ist sehr gut.

> Welche Library wäre ggfs. besser
>geeignet?

Brauchst du nicht.

>Angenommen ich würde die Daten nicht direkt auf die SD schreiben sondern
>zwischenspeichern bis etwas mehr Daten zusammengekommen sind,

Kann man machen, ist hier aber nicht das Thema.

> dann hätte
>ich ja immer noch das Problem, dass die Zeit zwischen zwei Samples nicht
>ausreicht um die Daten auf die SD-Karte zu schreiben. Wie löst man
>dieses Problem?

Mit einem FIFO, denn den braucht man beim Schreiben von 
zeitkritischen Echtzeitdaten IMMER, denn hier hat der Stafan 
ausnahmsweise mal recht. EineSD-Karte kann beim Schreiben ab und an 
Pausen einlegen, welche bei 100ms und mehr liegen.

Deine Daten mußt du in einer ISR lesen und in einen Software-FIFO 
schreiben. Im normalen Hauptprogramm mußt du den FIFO periodisch prüfen 
und wenn genügend Daten drinstehen diese auf SD-Karte schreiben.

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.