Hallo,
mein Ziel ist es, den ADS7825 (16bit, 25µS conversion time, 4 Kanäle)
von TI mit einem mega168 bei 18.432Mhz auszulesen.
Auf Seite 11 im Datenblatt ist dazu ein Diagramm, wie das mit den
Timings aussieht.
Man muss sowohl CS low als auch R/C auf low setzen, dann beginnt ein
Wandlungsprozess. R/C muss nach mind. 40ns und maximal 12µS wieder auf
high zurückgesetzt werden.
Nach einer Zeit, die für mich nicht klar ersichtlich ist, sendet der ADC
ein sync-Signal, kurz darauf (laut Datenblatt etwa 40ns) beginnt er, die
Daten seriell zu versenden, MSB first.
CS habe ich fest mit GND verbunden.
Nun hatte ich bisher folgende Auslese-Funktion:
Leider hat sich die als ziemlich instabil erwiesen. Jetzt hat mir hier
in einem anderen Thread jemand gesagt, die Verbindung sei über SPI Mode1
zu realisieren. Also hab ich das mal probiert:
> //DDRSS |= (1 << SEG_SS);
Auskommentieren ist IMHO falsch.
AVR151: Setup And Use of The SPI
http://www.atmel.com/Images/doc2585.pdf
"2.9.1 Example 1 - SPI communication controlled by polling:
2.9.1.1 Master Side:
If no interrupts are used there is just the SPI module and its pins to
configure. Important in this example is the setting of the SS pin as
output pin. This has to be done before the SPI is enabled in master
mode. Enabling the SPI while the SS pin is still configured as an input
pin would cause the SPI to switch to slave mode immediately if a low
level is applied to this pin. This pin is always configured as an input
pin in slave mode"
> STARTSIG_PORT |= (1 << STARTSIG_PIN); // Setzen des Start-Conversion-Bits> while(!(SPSR&(1<<SPIF))) {} // Warten auf Ende der Uebertragung
IMHO fehlt dazwischen etwas: Die Erzeugung der Clock durch den AVR
(Master)
"The master however generates the clock signal only while sending data.
That means that the master has to send data to the slave to read data
from the slave."
Deswegen sollte da ein Dummysenden vor das while()
1
SPDR=0xAA;
Der ADS7825 sendet 16-Bits während deine ADCread(void) nur einmal das
8-Bit Register SPDR ausliest. Die SPI Sequenz würde ich daher zweimal
nacheinander ausführen
1
uint16_tadcdata;
2
SPDR=0xAA;
3
while(!(SPSR&(1<<SPIF))){}// Warten auf Ende der Uebertragung
4
adcdata=SPDR<<8;// ADC7825 sendet MSB zuerst
5
SPDR=0xAA;
6
while(!(SPSR&(1<<SPIF))){}// Warten auf Ende der Uebertragung
Nein, hab ich nicht ...
Wenn alle Stricke reißen muss ich wohl bei meiner eigenen Kommunikation
bleiben und damit leben, dass sie manchmal versagt.
EDIT: habe deine Korrekturen berücksichtigt. Leider hängt er noch immer
in der Schleife.
Grüße
Nikolas
Ohne LA oder Oszi ist das schwer zu debuggen.
Der SPI Modus bei externer Clock sieht mir in FIGURE 5 aus dem DB nach
CPOL 1 und CPHA 1 aus.
Externe Clock ist hervorgehoben, weil ich mangels Schaltplan und
Codebeschreibung nicht weiss, ob du mit interner oder externer Clock
arbeitest.
Der Vorschlag oben bezog sich vorschnell auf die Annahme, dass du mit
externer SPI Clock (Master liefert Clock) arbeitest und da muss das SPDR
= ... her.
Es wäre IMHO hilfreich, wenn du mit einem Schaltplan an gibst, welche
der essentiellen Leitungen nCS und R/C des ADC du mit welcher Leitung
des AVR bedienst.
Naja nen Logicanalyzer kann ich mir nicht leisten, ein Oszi hab ich
zwar, aber ohne single shot wird da wohl wenig zu machen sein.
Da mein Schaltplan mal wieder furchtbar unlesbar geworden ist, hier die
relevante Beschaltung:
Par/Ser , CS, CONTC und PWRD sind auf GND.
A0 und A1 sind an PC2 und PC3.
D1 (DATA) ist an PB4(MISO).
D2 (CLK) ist an PB5(CLK).
R/C ist an PD5.
D0(TAG) ist auf GND.
D4 (EXTCLK) ist auf HIGH.
Momentan sieht mein Code so aus:
// SPIE 0 = Interrupt für fertig gesendete Daten deaktiviert
13
// SPE 1 = SPI aktiviert
14
// DORD 0 = Most significant bit (MSB) zuerst übermittelt
15
// MSTR 1 = Der ATmega ist Master der SPI-Übertragung
16
// CPOL 0 = Im Grundzustand ist das Signal auf GND
17
// CPHA 1 = Bei der steigenden Flanke wird geschrieben, bei der fallenden gelesen
18
// SPR1 0 & SPR0 0 = Übertragungstakt ist 1/4 des Prozessortaktes
19
// SPI2X 1 = Doppelte Geschwindigkeit
20
STARTSIG_PORT|=(1<<STARTSIG_PIN);
21
DDRMISO|=(1<<SEG_MISO);
22
DDRSCK|=(1<<SEG_SCK);
23
}
24
25
uint16_tADCread(void)//Empfaengt Daten ueber SPI
26
{
27
uint16_tdaten=0;
28
STARTSIG_PORT&=~(1<<STARTSIG_PIN);// Aufheben des Start-Conversion-Bits
29
_delay_us(1);
30
STARTSIG_PORT|=(1<<STARTSIG_PIN);// Setzen des Start-Conversion-Bits
31
SPDR=0xAA;
32
while(!(SPSR&(1<<SPIF))){}// Warten auf Ende der Uebertragung
33
daten=SPDR<<8;
34
SPDR=0xAA;
35
while(!(SPSR&(1<<SPIF))){}// Warten auf Ende der Uebertragung
36
daten|=SPDR;
37
returndaten;
38
}
Allerdings habe ich schon CPOL 1 getestet, es macht keinen Unterschied.
An meiner Übertragung zum PC (mittels UART) liegts nicht, die läuft mit
384000 fehlerfrei, z.B. wenn ich konstante Werte sende.
Auch an der grundsätzlichen Beschaltung kanns eigentlich nicht liegen,
da ja der zuvor gepostete Code funktionierte. Allerdings verschob sich
manchmal die Stelle, an der gelesen wird, sprich ein Datenpaket beginnt
mitten im Byte und endet dann zur Hälfte im nächsten Datenpunkt. Da ich
keine Lösung gefunden habe, das zu beheben, wollte ich mich dann doch
mal um SPI kümmern.
Danke auf jeden Fall soweit!
Grüße
Nikolas
-
Hallo.
Spontan sehe ich, dass die Delay vor eine Conversion 1,4 us beträgt.
Aber grundsätzlich würde ich mal nicht den SPI benutzen, sondern die
Bits separat ansteuern (Software SPI), dann kannst Du das Timing
Diagramm genau nach programmieren.
Gruß
Marvol
Hallo.
Ich sehe gerade, dass Dein Programmer natürlich auch auf SPI liegt,
damit die ADC Kommunikation sauber läuft, musst Du Programmer abziehen.
Gruß
Marvol
Der Programmer ändert garnix, ob er nun dran ist oder nicht, gerade
getestet.
SoftwareSPI habe ich doch implementiert, siehe meinen ersten Post, nur
richtig funktionieren tuts nicht.
Grüße
Nikolas
Sie haben natürlich recht. Zufällig ist DATASIG_PIN tatsächlich 0 und es
funktioniert.
Ich habe ihren Code eingebaut und er löst auch ein weiteres Problem. Ich
bin mir nicht sicher, warum er das tut, aber er tuts ;)
Grundsätzlich verstehe ich die Idee hinter dem Code, durch eine Maske
eine 1 durchzuschieben und bei Bedarf mit dem Ergebnis zu "odern".
Lediglich den code (mask>>=1) verstehe ich nicht, er schiebt wohl die 1
durch, bis es nicht mehr geht, aber so richtig klar ist mir nicht, was
der Operator >>= macht.
Danke auf jeden Fall! Hardware-SPI kann mir gestohlen bleiben!
Freut mich, dass es bei Dir klappt.
while((mask>>=1));
ist eine Kurzschreibweise für
while((mask=mask>>1)!=0);
was weiter ausgeschrieben
mask=mask>>1; while(mask!=0);
bedeutet.
Der Clou ist, dass die Variable zwei Aufgaben erfüllt: Maske und
Bitzähler.
Die doppelten Klammern setze ich, weil bei manchen Compilern dann die
Warnung "Assignment in Condition" nicht kommt.
Wie äußerte sich das erste Problem?
Welches war das zweite Problem?
Die Codegröße? ;-) Laufzeit?
((DATASIG_PORT&(1<<DATASIG_PIN))<<i); erzeugt aufwendigen Code, da die
Anzahl der Schiebungen von i abhängig sein muss (außer die CPU hätte
einen Barrel-Shifter).
Ok, faszinierende Abkürzung! Wieder was gelernt :)
Das erste Problem, dass er nur auf einem Pin mit Nummer 0 funktioniert,
war ja im Prinzip nicht relevant.
Das zweite Problem war etwas kurioser.
Ich habe eine UART-Verbindung zum PC hergestellt und in main hatte ich
das hier:
while(1)
{
send_UART(ADCread());
}
(denke das Prinzip ist klar).
Jedenfalls ging das mit meinem Code prinzipiell, aber nur wenn ich nach
jedem senden ein delay von 17µS eingefügt habe in die Schleife. Sonst
wurde das Paket in sich verschoben, aus:
11111111 00000000
11111111 00000000
wurde:
00001111 11110000
00001111 11110000
wobei die Verschiebung nicht fest war und auch mal 3 oder nur 2 Bits
betrug.
Das ist gleich doppelt problematisch gewesen:
1) drückt es meine maximale Frequenz von 19kHz auf 13kHz.
2) waren die 17µS ein instabiler Wert; um mit anderen PCs (!) als meinem
üblichen zu kommunizieren waren andere delays notwendig.
Als ich mich dann wiederfand, eine PC-seitige Routine zu schreiben, die
automatisch das minimale Delay bestimmt und die auch nicht so wirklich
funktionieren wollte, dachte ich mir: es heisst zwar never touch a
running system, aber das ist mir doch zu sehr ein Krampf, das muss
besser gehen :)
Danke nochnmal!
Nikolas
Hallo,
nachdem ich in der Entwicklung des Projektes, das den ADS7825 verwendet,
weiter gekommen bin, haben sich neue Probleme aufgeworfen, die leider
wohl am bisherigen Code liegen.
Kurzer Überblick, was im Thread bisher zustande kam:
- Hardware-SPI zum Auslesen des ADS7825 ist, trotz Beachtung diverser
Hürden, daran gescheitert, dass der Übertragungsvorgang nie als
abgeschlossen bestätigt wird
- als Stream funktioniert folgender Code:
1
uint16_tADC_read(void)
2
{
3
DCLK_PORT&=~(1<<DCLK_PIN);
4
uint16_tdata=0;
5
uint16_tmask=0x8000;
6
RC_PORT|=(1<<RC_PIN);
7
DCLK_PORT|=(1<<DCLK_PIN);
8
DCLK_PORT&=~(1<<DCLK_PIN);
9
DCLK_PORT|=(1<<DCLK_PIN);
10
do{
11
DCLK_PORT&=~(1<<DCLK_PIN);
12
if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
13
DCLK_PORT|=(1<<DCLK_PIN);
14
}while((mask>>=1));
15
RC_PORT&=~(1<<RC_PIN);
16
returndata;
17
}
Dabei dauert eine Wandlung + Übertragung mit 384kbaud an den PC 52µs.
Allerdings habe ich nun festgestellt, dass eine Änderung des
ADC-Channels erst 4 Wandlungen später Effekt zeigt, eine Änderung der zu
messenden Spannung 3 Wandlungen später. Das ist leider in meiner
zeitkritischen Anwendung ungeeignet.
Als Ursache sehe ich klar den überhaupt nicht Datenblatt-gemäßen
Auslesevorgang. Wie man sieht wird R/C am Ende des Auslesens LOW gesetzt
(und damit eine Wandlung gestartet), und erst wieder HIGH genommen, wenn
ein neuer Auslesevorgang begonnen wird. Die Zeit, die R/C LOW ist hängt
also massiv vom Rest des µC-Codes ab. Das Datenblatt sagt dazu:
"CS and/or R/C must go HIGH before BUSY goes HIGH or
a new conversion will be initiated without sufficient time to
acquire a new signal."
Ich habe also den Code nochmal genau an Seite 11 des Datenblattes
angepasst, und folgendes erzeugt:
1
uint16_tADC_read(void)
2
{
3
uint16_tdata=0;
4
uint16_tmask=0x8000;
5
RC_PORT&=~(1<<RC_PIN);
6
CS_PORT&=~(1<<CS_PIN);
7
_delay_us(1);
8
RC_PORT|=(1<<RC_PIN);
9
DCLK_PORT|=(1<<DCLK_PIN);
10
while(!(SYNC_PORT&(1<<SYNC_PIN)));
11
do{
12
DCLK_PORT&=~(1<<DCLK_PIN);
13
if(DATASIG_PORT&(1<<DATASIG_PIN))data|=mask;
14
DCLK_PORT|=(1<<DCLK_PIN);
15
}while((mask>>=1));
16
CS_PORT&=~(1<<CS_PIN);
17
}
Ergebnis ist, dass die erste Wandlung 0 ergibt, die zweite Wandlung
1000, die dritte ebenfalls und die vierte nicht fertig wird, da er in
der Warteschleife für SYNC hängenbleibt.
Leider besitze ich (wie zuvor geschrieben) keinen Logicanalyzer. Für das
Setup und die Rahmenbedingungen, siehe meine vorherigen Postings,
unterschied ist jetzt nur, dass ich CS nicht immer auf GND habe, sondern
angeschlossen, genauso wie SYNC.
Jemand eine Idee, warum das Teil nicht so läuft, wie es soll?
Vielen Dank schonmal,
Nikolas