Archiv verlassen und diese Seite im Standarddesign anzeigen : [I²C] Beschleunigungssensor gibt kein ACK aus
crabtack
31.03.2012, 11:40
Hallo!
Also, ich habe da ein Problem.
Ich habe ein Beschlunigungssensor Modul von ELV mit einem BMA020 an einen atmega 32 angeschlossen.
Über den I²C bus.
Das Teil hat schon 10K pullup widerstände drauf.
Sonst habe ich keine pull ups.
Das Teil sendet aber kein Acknowledgment, also SDA wird am Ende nicht low.
Könnt ihr euch das mal ansehen?
Mein gut komemntierter code :)
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#define BAUD 1200UL // Baudrate
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // clever runden
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1))) // Reale Baudrate
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) // Fehler in Promille, 1000 = kein Fehler.
#if ((BAUD_ERROR<990) || (BAUD_ERROR>1010))
#error Systematischer Fehler der Baudrate grösser 1% und damit zu hoch!
#endif
#include <util/delay.h>
int zahl = 0;
void uart_init(void)
{
UBRRH = UBRR_VAL >> 8;
UBRRL = UBRR_VAL & 0xFF;
UCSRB |= (1<<TXEN) | (1<<RXEN); // UART TX und RX einschalten
UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // Asynchron 8N1
}
void uart_write (char eingabe[40]) //funktion um strings auszugeben
{
while (eingabe [zahl] != 0)
{
while (!(UCSRA & (1<<UDRE))) /* warten bis Senden moeglich */
{
}
UDR = eingabe[zahl];
zahl ++;
}
zahl = 0;
}
uint8_t uart_getc(void)
{
while (!(UCSRA & (1<<RXC))) // warten bis Zeichen verfuegbar
;
return UDR; // Zeichen aus UDR an Aufrufer zurueckgeben
}
int main (void)
{
uart_init ();
DDRC &= ~(1<<PC1);
if (PINC & (1<<PINC1))
{uart_write("High");}
DDRC = 0xFF;
PORTC &= ~(1 <<PC0) | (1<<PC1); //SCL und SDA auf LOW
DDRC &= ~(1<<PC0) | (1<<PC1); //SCL und SCA als Eingang also high
DDRC = (1 <<PC1); //SDA low (starbedingung2) [ecvnetuell pause davor]
//01110000
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl high 1.bit
DDRC &= ~(1 <<PC1); //SDA high
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl high 2.bit
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0);//SCl high 3.bit
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0);//SCl high 4.bit
DDRC =(1 <<PC1); //SDA low
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl high 5.bit
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl high 6.bit
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl hogh 7.bit
DDRC = (1 <<PC0); //SCL low
DDRC &= ~(1<<PC0); //SCl high 8.bit
DDRC &= ~(1<<PC1);
if (!(PINC & (1<<PINC1)))
{ uart_write ("low");}
else
{ uart_write ("high2");}
while(1)
{
}
}
Ich ahbe den gnazen Code gepostet, weil der fehler ja überalls ein könnte.
Aber der wichtige Teil sit unten.
Zuerst kommt die Startbedingung, dann wird die Adresse übertragen und dann wird das ACK geprüft.
Aber das ist bei mir immer High.
Wenn ich die pullups wegmache, dann ist es low, nur so als Test.
Für Antworten wäre ich sehr Dankbar.
Gruß
Olaf
Hallo,
am Code fällt mir auf, dass du zur Ausgabe auf SDA/SCL immer auf DDRC schreibst, damit schaltest du aber immer nur von Eingang auf Ausgang und umgekehrt. Müsste wohl PORTC heißen ;) Und wahrscheinlich läuft das auch viel zu schnell ab (1Mhz Takt und beim Setzen hast du keine Pausen o.ä., evtl kommt der Sensor nicht mit).
Ich würde dir empfehlen, unbedingt das integrierte TWI-Modul (I2C) im Atmega zu verwenden, das ist viel einfacher und komfortabler, als immer die Pins selber setzen zu müssen. Das TWI-Interface ist ja genau an den beiden Pins, die du verwendest (PC0,PC1). Außerdem lassen sich bei der Übertragung Fehlercodes auslesen (z.B. ob ACK fehlt) - noch ein Vorteil ;)
Ohne Pullup-Widerstände kann kein High-Pegel erreicht werden, da jeder Teilnehmer nur open-Collector/open-Drain-Ausgänge hat (d.h. jeder Busteilnehmer kann nur auf Low ziehen oder hochohmig sein, im hochohmigen Zustand ziehen die Pullups den Pegel auf High; schützt vor Kurzschlüssen)
Grüße, Bernhard
crabtack
31.03.2012, 12:06
Hallo Bernhard!
Danke für die schnelle Antwort.
Auch wenn ich mit Google so einiges zum TWI modul finde, so hilft es mir nicht wirklich weiter.
Da steht zwar, welche Register man setzen muss und so, aber nicht wie man das jetzt genau in C umsetzt.
Kennst du vielleicht eine gute Anleitung?
Gruß
Olaf
-schumi-
31.03.2012, 12:34
Du musst die Namen der Register einfach wie eine Variable benutzen :)
Da kannst du dann (bei den meisten) reinschreiben wie du willst, lesen und auch logisch miteinander verknuepfen usw. Also genauso benutzen, wie du z.B PORTA benutzt.
(PORTB, DDRC, PINA usw. sind auch alles nur Register)
crabtack
31.03.2012, 12:40
Danke,
jetzt weiß ich endlich mal, wie man Register richtig verwendet :)
Also um zum beispiel das Interrupt Flag zu setzen müsste ich:
TWCR =| (1<<TWINT);
schreiben?
Ich bin aber gerade auf noch ein Problem gestoßen.
Also.
Beim I²C soll man ja nicht den Pin vom atmega High machen sondern den einfach als Eingang setzen und den Pull up widerstand die Arbeit amchen lassen.
Das problem ist nun:
Wenn ich einfach mal
DDRC = (1 <<PC0); //SCL low
[12:34:49] crabtack: PORTC &= ~(1 <<PC0) | (1<<PC1);
mache, dann ist der Pin immernoch high.
Der ganze Port ist high.
Was amche ich falsch?
Gruß
Olaf
schorsch_76
31.03.2012, 12:53
Was mir noch auffällt, bzw als Verbesserungsvorschlag. Mach dir Makros wie
#define sda_to_high() (PXYZ |= (1<<PXY))
#define sda_to_low() (PXYZ &= ~(1<<PXY))
dann erscheint der code leserlicher und du bist sicher, dass du immer bei SDA/SCL whatever das gleiche register beschreibst :)
Gruß
Georg
crabtack
31.03.2012, 13:22
Danke,
Makros werden vor allem den Code meienr Binäruhr übersichtlicher machen.
Für das Momentane Problem bringen sie aber ncihts, da ich ejtzt das TWI Modul vom atmega nutzen möchte.
Was ich aber nicht verstehe ist, wie das mit dem ACK funktioniert.
Wann liest er das ACK aus und wo kann ich nachgucken, ob es ein ACK gab oder nicht?
Gruß
Olaf
oberallgeier
31.03.2012, 15:42
... TWI modul ... wie man das jetzt genau in C umsetzt ... eine gute Anleitung? ...Hi Olaf,
der Sternthaler hatte GENAU für den BMA020 hier (klick) Codeschnippsel in C (https://www.roboternetz.de/community/threads/47783-3-Achs-Beschleunigungssensor-sensationell-günstig-!?p=470340&viewfull=1#post470340) in seinem bewährten, sauberen Form zum Auslesen vorgelegt. Ich hatte das weitergeführt und im gleichen Thread ein paar Seiten weiter meine Subroutine (klick) (https://www.roboternetz.de/community/threads/47783-3-Achs-Beschleunigungssensor-sensationell-günstig-!?p=485247&viewfull=1#post485247) zum Auslesen des BMA020 gepostet. Meine I²C-Routine basiert auf der Bibliothek von P.Fleury. Die Sache läuft bei mir recht ordentlich, siehe klick. (https://www.roboternetz.de/community/threads/47783-3-Achs-Beschleunigungssensor-sensationell-günstig-!?p=485256&viewfull=1#post485256)
Vielleicht hilft Dir dies und die sonstigen Postings im zitierten Thread.
Viel Erfolg.
crabtack
31.03.2012, 16:35
Hallo Oberallgeier!
Danke für deine Antwort.
Allerdings würde ich am liebsten keine fertige LIB nutzen, das wäre dann die Notlösung.
Aber eigentlich muss nurnoch das ACK Problem gelöst werden :)
Gruß
Olaf
Hallo,
soweit ich weiß, gibt ein I2C-Slave ein ACK, wenn er seine Adresse erkannt hat bzw. noch weitere Daten entgegennehmen will/kann.
Irgendetwas stimmt noch nicht, sonst würde sich der Slave ja melden :D
Wenn du das integrierte TWI-Modul verwendest (was ich dir wirklich nocheinmal ans Herz legen möchte, ich meine es ja nur gut ;) ), kannst du im Register TWSR nachschauen, ob ein ACK angekommen ist (Datenblatt Seite 186, z.B. Code $38 oder $48 ).
Infos zum TWI/I2C im Datenblatt gibt es ab Seite 167; konkret wird es ab Seite 175.
Grüße,
Bernhard
crabtack
31.03.2012, 17:13
Hallo Bernhard,
ich möchte doch das I²C Modul verwenden, nur keine fertige Lib:)
Also, d akann ich eifnach gucken, bo ein ACK angekommen sit?
Und das amcht der ganz automtisch ohne, dass ich irgendein bit setzten muss?
Was hat es eigneltich mit der Startbedingung auf sich?
Muss ich da das Bit setzen und dann selbst wieder das Bit löschen?
Gruß
Olaf
oberallgeier
31.03.2012, 17:23
... am liebsten keine fertige LIB ... muss nur noch das ACK Problem gelöst werden ...Ne fertige Lib hat den Vorteil, dass sie (von fast allen Autoren) ziemlich sicher funktioniert. Unter fast allen Bedingungen. Und im Code bzw. in der onlinehilfe von Fleury kannst Du auch lesen, (http://homepage.hispeed.ch/peterfleury/group__pfleury__ic2master.html#ga6) wann und warum das ACK kommt.
Zum ACK: ich hatte mir vor ner Weile, bei meinem ersten, erfolgreichen I²C-Abenteuer (nach etlichen erfolglosen), einen I²C-Sniffer gebaut (klick für mehr) (https://www.roboternetz.de/community/threads/55744-I²C-Master-m328-kann-Slave-m328-nicht-lesen?p=533026&viewfull=1#post533026)und damit den Datenaustausch mitgeschnitten. Da siehst Du, wann - und ob - das ACK kommt. Schöner wär natürlich ein Logikanalysator mit I²C-Interpreter, da kann man sauber lesen, wann und ob das ACK kommt. Die teure Lösung . . .
Kennst Du dieses Tutorial? (http://www.rn-wissen.de/index.php/I2C#Best.C3.A4tigung_.28Acknowledgment.29)
Hallo,
bevor das TWI-Modul verwendest, musst du das Modul "einschalten" und die Geschwindigkeiten usw. einstellen (drei Register setzen, nämlich TWBR, TWSR und TWCR).
Für die Startbedingung musst du dann "nur" ein paar Bits in TWCR setzen und warten bis die Signale ausgegeben wurden:
void i2c_start()
{
TWCR=0b10100100; //TWEN, TWSTA und TWINT; unschön, aber geht ;)
while( !(TWCR & 128) ) ; //warten bis fertig (TWINT auf logisch 1)
}
Grüße,
Bernhard
crabtack
01.04.2012, 10:06
Danke!
Jo, das Tutorial kenne ich :)
Also, wie gesagt eine LIB wäre die notlösung.
Ich ahbe noch nie eine "nicht standart Lib" benutzt und das soll erstmal sob leiben :)
Also irgendwas macht er jedenfalls.
Das hier steht jetzt in meiner Main:
uart_init ();
TWSR |= (1 << TWPS1);
i2c_start ();
TWDR = 0b01110000; //sla +R
TWCR = 0b10000100;
if (TWINT &1)
{uart_write ("TWINT = 1");}
else
{uart_write ("TWINT = 0");}
wenn ich ihn nun starte bekomme ich TWINT = 1 ausgegeben.
Wenn ich ihn starte aber dem Beschleunigungssensor die VCC Leitung kappe, dann bekomem ich garnichts.
Das Problem ist nur, dass wenn ich dem Sensor Masse kappe, dann bekomme ich TWINT = 1
Also liegt es nur and en Pull up widerständen, er redet nicht wirklich mit mit.
Aber im Datenblatt ist eigneltich bei jedem Status Code TWINT = 1;
Und viele Unterscheiden sich überhaupt nicht.
Wie kann ich nun ein ACK erkennen?
Gruß
Olaf
Hallo,
das TWINT hat damit erst mal nichts zu tun. TWINT wird gesetzt wenn die aktuelle Übertragung abgeschlossen ist.
Außerdem wäre die Abfrage des TWINT-Bits so falsch. Richtig müsste es heißen if(TWCR & 128 ) oder if(TWCR & (1<<TWINT)).
TWINT ist nur ein Bit im Register TWCR und ist an siebter Stelle, d.h. TWINT ist immer 7.
7 dezimal ist 111 binär, und in der if-Abfrage steht also: 111 & 1 , das gibt immer 1, ist also kein Wunder warum er über uart dann "TWINT = 1" ausgibt ;)
Das steht also in keiner Verbindung dazu, was auf dem Bus passiert.
ACK auslesen:
if( TWSR ==0x18 || TWSR==0x28 ){/*Ack wurde erkannt*/} beim Senden
if( TWSR ==0x40 || TWSR==0x50 ){/*Ack wurde erkannt*/} beim Lesen
Die Zahlen kommen von Seite 183 und 186 im Datenblatt. $ bzw 0x bedeutet hexadezimal.
Grüße,
Bernhard
crabtack
01.04.2012, 23:55
Vielen Dank!
Das bringt mich schon einen ganzen Schritt weiter.
Er empfängt jetzt jedenfalls schonmal ACK´s
Aber irgendwie macht er nicht merh weiter nachdem ich stoppe und dann wieder starte.
uart_init ();
TWBR = 00010001;
i2c_start ();
while(!( TWSR == 0x08) )
{}
uart_write ("Start ");
TWDR = 0b01110000; //sla +R
TWCR = 0b10000100; // senden
while(!( TWSR ==0x18 || TWSR==0x28 ))
{}
uart_write ("ACK empfangen ");
TWDR = 0b00000010; //2. register
TWCR = 0b10000100; //senden
while(!( TWSR ==0x18 || TWSR==0x28 ))
{}
uart_write ("ACK2 empfangen ");
TWCR = 10010100; //Stop
while( !(TWCR & 128) )
{} //warten
uart_write ("Stop ");
i2c_start ();
while(!( TWSR == 0x08) )
{}
uart_write ("Start2 ");
TWDR = 0b01110001;
TWCR = 0b10000100; // senden
while(!( TWSR ==0x18 || TWSR==0x28 ))
{}
uart_write ("ACK3 empfangen ");
Das letzte, was ich empfange ist "STOP"
Aber eigneltich mache ich beim Starten ja genau das gleiche, wie oben auch.
Aber ich bekomme einfach kein START2.
Was mache ich falsch?
Gruß
Olaf
Hier fehlt noch ein 0b, momentan ist das noch dezimal:
TWCR =10010100; //Stop ;)
Grüße, Bernhard
crabtack
02.04.2012, 11:51
Oh, sowas dummes :)
Danke vielmals, es funktioniert jetzt.
Ich erhalte alle ACK´s.
Wenn ich dem Sensor seine Masse wegnehme, dann bekomme ich keine ACK´s.
Also er fühlt sich jedenfalls angesprochen und funktioniert.
Jetzt habe ich nur noch eine Frage.
Wann beginnt der Sensor zu senden?
Automatsich, anchdem das ACK angekommen ist oder muss man erst TWINT auf 1 setzen?
Sobald er gesendet hat, steht dann der wert sofort im TWDR register?
Gruß
Olaf
Hallo,
Wenn ich dem Sensor seine Masse wegnehme, dann bekomme ich keine ACK´s.
Also er fühlt sich jedenfalls angesprochen und funktioniert.
Wunderbar, dass das funktioniert. (Wenn du dem Sensor die Masse wegnimmst, kann er logischerweise nicht antworten, da er für's ACK ja auf Masse ziehen muss, die dann ja fehlt.)
Der Master kann steuern, ob er lesen oder schreiben will, das geschieht mit der Adresse, genauer: mit dem niedrigsten Bit.
Siehe: http://rn-wissen.de/index.php/I2C#Adressierung
Du brauchst ungefähr diesen Ablauf:
I2C-Start
Sende Slave-Adresse+Write
Sende Nummer des Registers, das ausgelesen werden soll
I2C-Stop oder Repeatet-Start
Sende Slave-Adresse+Read
Lese vom Bus (" TWCR=0b10000100; while(!(TWCR&128)){}; return TWDR; ")
I2C-Stop
Ich würde dir auch noch empfehlen, einzelne Codeabschnitte als Methoden/Funktionen zu programmieren, das erhöht die Lesbarkeit/Übersicht, lässt sich wiederverwenden und bringt meistens weniger Fehler mit sich ;)
Grüße,
Bernhard
crabtack
02.04.2012, 19:51
Hallo
Vieln Dank für deine Antwort.
Es ist mir gerade gelungen die Chip ID auszulesen: 0b010 :)
Wenn du dem Sensor die Masse wegnimmst, kann er logischerweise nicht antworten, da er für's ACK ja auf Masse ziehen muss, die dann ja fehlt
Das habe ich gemacht um zu sehen, dass acuh wirklich der Sensor mit mir redet und das ACK nicht sonstwoher kommt :)
Jetzt gehts daran schöne, nutzerfreundliche und schnelle Funktionen zu schreiben.
Aber ein Problem habe ich da noch.
Und zwar muss ich trotz der Überprüfung des Statusregisters nach dem Start und nach dem Senden warten, damit alles funktioniert (_delay_ms (1);)
Ich habe die Startfunktion ein wenig umgeschrieben, aber das bringt auch nichts:
void i2c_start()
{
TWCR=0b10100100; //TWEN, TWSTA und TWINT; unschön, aber geht ;)
while(!( TWSR ==0x08 || TWSR==0x10 )) ; //warten bis fertig start oder repeated start
_delay_us (10); //Ohne delay geht es momentan nicht
}
Also nach dem Senden muss ich erst auf TWINT warten und dann auf das ACK ?
Ich habe gerade gesehen, dass es 2 Register gratis dazu gibt :)
2 Ungenutzte Register, in die man schreiben kann, was man will, 2Byte Speicher umsosnt!
Gruß
Olaf
Gruß
Olaf
Und zwar muss ich trotz der Überprüfung des Statusregisters nach dem Start und nach dem Senden warten, damit alles funktioniert (_delay_ms (1):wink:
Ich habe die Startfunktion ein wenig umgeschrieben, aber das bringt auch nichts
Mit welcher Geschwindigkeit betreibst du denn nun den Bus? (SCL-Frequenz)?
Evtl musst du auch mal das Datenblatt von deinem Sensor durchschauen, ob in dieser Hinsicht etwas von Verzögerungen drinsteht. Auch die maximale Taktfrequenz vom Bus für den Sensor müsste da drinstehen.
Von I2C-EEPROMs weiß ich z.B., dass sie ungefähr 1ms zum Schreiben von einem Byte benötigen.
Also nach dem Senden muss ich erst auf TWINT warten und dann auf das ACK ?
In dem Moment, wo TWINT entsprechend gesetzt wurde, ist die komplette Datenübertragung abgeschlossen. Ob das ACK dabei empfangen wurde, kannst du dann ja auslesen. Auf das ACK musst du in diesem Sinne nicht "warten".
Ich würde den Controller auch nicht in eine Endlosschleife schicken zwecks ACK. Falls kein ACK kommt, würde ich die Übertragung abbrechen. Sonst wartet der Prozessor ewig wenn der Sensor mal nicht angeschlossen ist. Besser ist dann eine Art Fehlerbehandlung.
;)
Grüße, Bernhard
crabtack
04.04.2012, 12:12
Hallo, danke für deine Antwort!
Also, an der Frequenz wird es nciht lieen.
Ich Betreibe das Teil mit 20KHz.
Das Seltsame ist nur, dass ich vor dem repeatet Start 1ms warten muss.
Hier der Entsprechende Code Abschnitt:
TWDR = 0b00000010; //2. register
TWCR = 0b10000100; //senden
while(!( TWSR ==0x18 || TWSR==0x28 )) //ACK warten
{}
_delay_ms (1);
i2c_start (); //rep start
In TWDR kommt die Adresse des 2. Registers, dann wird gesendet, aufs ACK gewartet und dann muss die 1ms pause kommen, sonst klappt der repeatet Start nicht.
Dabei muss ja eigneltich anchdem das ACK empfangen waurde alles erledigt sein.
Um eine Fehlerbehandlung kümmere ich mich Später.
Jetzt möchte ich erstmal nur eine Messung hinbekommen :)
Gruß
Olaf
Silent01
06.08.2012, 11:26
Hi. Ich habe den gleichen Beschleunigungssensor und möchte den mit meinem myavr mk2 Board testen. Mit Bascom hab ich es hinbekommen. Würde aber lieber mein Projekt mit c machen. Hast du deinen funktionierenden Code noch da? Wäre echt super
Danke schonmal
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.