ATmega328/P ADXL345 i2c Sampling Rate SD-Karte

Gast #5488884
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
Gast #5489270
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.
#5489429
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.
#5489432
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.
Gast #5490100
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
}
#5490130
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.
Gast #5490155
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.
#5490179
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.
Gast #5490276
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?
(Firma: Vescomp) #5490304
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.
#5490313
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.
Gast #5490336
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?
#5490365
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren