Hallo zusammen, ich bin auf der Suche nach einen funktionierenden Beispielcode in C, um mit einem Atmega168 einen Temperatursensor (MCP9804) per Software-I2C auszulesen. Per Hardware-TWI funktioniert es schon, nur brauche ich den HW-TWI des Atmega für die Verbindung zu einem anderen Master-uC. In der Codequelle (Beitrag "I2C-Master [ohne TWI (Softwarelösung) für die ATMEGAs") habe ich einen Code gefunden, leider funktioniert er bei mir nicht. Laut diesem Thread (Beitrag "LM75 mit Software TWI auslesen (Atmega 16, C)") ist der Code anscheindend auch nicht ganz fehlerfrei. Ich habe leider kein Oszi zur Hand, nur einen AVR-Dragon und eine UART-Verbindung zum debuggen. Für Assembler gibt es ja so einige Beispiel (Peter Fleury) nur leider finde ich keine Code für AVR-Controller. Ich verwende den GCC-Compiler und das AVR-Studio als Entwicklungsumgebung. Danke
Kann ich die Peter Fleury Bibliothek (http://homepage.hispeed.ch/peterfleury/group__pfleury__ic2master.html) insofern nutzen, dass die main-Funktion in C-Code die Funktionen, die in Assembler-Sprache geschrieben sind, aufruft ? Im Anhang hab ich das Zip-File nochmal hinzugefügt.
Ich habe jetzt das Projekt von Peter Fleury übernommen und nun habe nun folgendes Problem: ... i2c_start_wait(L_Device_ADDRESS+I2C_WRITE); // set device address and write mode i2c_write(L_Sensor_ADDRESS_MSB_BYTE); // write address = 5 i2c_stop(); i2c_start_wait(L_Device_ADDRESS+I2C_READ); // set device address and read mode ret = i2c_readNak(); USART_Transmit(???); i2c_stop(); ... Ich will das gelesene letzte Byte aus einen Sensor über die UART-Schnittstelle auf einen Terminalprogramm ausgeben, und nun weiß ich nicht, was ich anstatt der drei Fragezeichen als Variable einfügen soll. Gebe ich ret als Variable ein, so bleibt der Wert konstant auf Null. i2c_readNak hat ja als Ausgabe entweder 0 oder 1, oder ? Was bedeutdet r24 ? Ist das eine Adresse auf den Wert ? Beim Hardware TWI gebe ich einfach TWDR als Variable ein, und es wird der Sensorwert übergeben. Unten steht nochmal der Kommentar zur i2c_readNak -Funktion. Kann mir da bitte jemand weiterhelfen ? ;*********************************************************************** ** ; read one byte from the I2C device, send ack or nak to device ; (ack=1, send ack, request more data from device ; ack=0, send nak, read is followed by a stop condition) ; ; extern unsigned char i2c_read(unsigned char ack); ; ack = r24, return = r25(=0):r24 ; extern unsigned char i2c_readAck(void); ; extern unsigned char i2c_readNak(void); ; return = r25(=0):r24 ;*********************************************************************** **
http://www.mikrocontroller.net/articles/FAQ#Wie_kann_ich_Zahlen_auf_LCD.2FUART_ausgeben.3F Das müsste dir weiterhelfen.
Danke für die schnelle Antwort. Mein Terminalprogramm kann aber doch alle Formate lesen, warum muss ich das dann noch wandeln ? In der "ret"-Variable steht also sicher der ausgelesene Wert ? Ich habe leider gleich noch eine Frage: Unter i2cmaster.S steht folgendes: ;-- map the IO register back into the IO address space #define SDA_DDR (_SFR_IO_ADDR(SDA_PORT) - 1) #define SCL_DDR (_SFR_IO_ADDR(SCL_PORT) - 1) #define SDA_OUT _SFR_IO_ADDR(SDA_PORT) #define SCL_OUT _SFR_IO_ADDR(SCL_PORT) #define SDA_IN (_SFR_IO_ADDR(SDA_PORT) +1) #define SCL_IN (_SFR_IO_ADDR(SCL_PORT) +1) Was bedeudet das ? Unter iom8x.h steht als als Registeradresse des PORTD (Pin 7 soll SDA und Pin6 soll SCL sein) der Wert _SFR_I08 (0x0B). Hat das damit zu tun ? Ist das im Datenblatt (ich verwende den Atmega168) beschrieben ? Unter welchen Stichwort suche ich da am Besten ?
Michael schrieb: > Ist das im Datenblatt (ich verwende den Atmega168) > beschrieben ? Unter welchen Stichwort suche ich da am Besten ? Kapitel "Register summary" Die IO-Register des AVR liegen auch alle im Adressspace. Die Makros berechnen die Adressen für alle benötigten Register. An den Zeilen brauchst du aber nichts zu ändern, nur an denen davor. Oliver
Ich hab den Wert in den beiden unteren Zeilen schon jeweils von -2 auf +1 abgeändert. Ich habe das aus diesem Thread entnommen: (http://8515.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=100373) Noch eine Sache verwundert mich ein wenig. In der main-Funktion habe ich den Ausdruck if(!(i2c_start(L_Device_ADDRESS +I2C_WRITE)))//Slave bereit zumschreiben? {... } else { i2c_stop(); } hinzugefügt, um festzustellen, ob die Adresse des devices richtig übertragen und mit einem ACK bestätigt wurde. Nun ist es leider so, dass das Programm, selbst wenn ich eine falsche device_Adresse eingebe, dennoch nicht in den error Zusatnd wechselt. Selbst wenn ich die den SDA-Leitung vom Controller trenne, geht er nicht in den Fehlermodus. Woran kann das liegen ?
Michael schrieb: > Ich hab den Wert in den beiden unteren Zeilen schon jeweils von -2 auf > +1 abgeändert. Ich habe das aus diesem Thread entnommen: > (http://8515.avrfreaks.net/index.php?name=PNphpBB2&...) Na ja, in dem verlinkten Thread geht es um einen ATXmega128A1, du hast eine Mega168. Vielleicht schaust du tatsächlich mal ins Datenblatt. Denn ansonsten ist deine Aktion ziemlich sinnfrei. Oliver
Ich habe die Werte aus den genannten Thread genommen, weil nur mit dieser "Zahlenkombination" auf meinem UART was ankommt. Leider das falsche, weil ja wie oben erwähnt diese Abfrageschlaufe ständig als O.K. abgehadnelt wird, obwohl die device-Adresse falsch ist, oder überhaupt nix an SDA angelötet ist. Anderseits funktioniert auch bei richtiger Adressierung des devices bei Verwendung der +2 im Makro der Code nicht, d.h. das Programm spingt nicht in die Schleife, obwohl er es eigentlich sollte. Per Hardware-TWI funktioniert alles, am Sensor kann es also auch nicht liegen. Im Anahng habe ich mal mein Projekt angefügt.
Nimm den Originalcode. Der funktioniert mit ATMegas, nur für ATXmegas müssen die Adressenoffsets geändert werden. Warum auch immer dein Code nicht funktioniert, an den Offsets liegt es nicht. Oliver
In Ordnung, dann kann ich schon mal das als Fehlerquelle ausschließen. Im Moment stellt es sich so dar, dass die start condition vom Slave-Sensor nicht beantwortet wird. Ich habe jetzt schon den zweiten Sensor am Controller (der mit Hardware-TWI auch funktioniert hat). Ich weiß nicht mehr, woran es noch liegen kann. Ich kann schon irgendeinen unbesetzten PORT-Pin für die Kommunikation nutzen, oder ? Prinzipiell funktioniert das Programm schon, oder ? Hat das von euch schon mal jemand ausprobiert ?
Schreib dir doch selbst eine Routine?! Ich habe mir damals für nen LM75 mit I2C folgendes geschrieben....:
1 | #define DS75_SDA PORTB.B5
|
2 | #define DS75_PinDATA PINB.F5
|
3 | #define DS75_SCL PORTB.B4
|
4 | |
5 | |
6 | void DS75_setWRITE(void){ |
7 | DDRB += 0x20; |
8 | }
|
9 | |
10 | void DS75_setREAD(void){ |
11 | DDRB -= 0x20; |
12 | }
|
13 | |
14 | void DS75_Start(){ |
15 | DS75_setWRITE(); |
16 | DS75_SDA = 1; |
17 | DS75_SCL = 1; |
18 | asm nop; |
19 | DS75_SCL = 1; |
20 | DS75_SDA = 0; |
21 | asm nop; |
22 | DS75_SCL = 0; |
23 | DS75_SDA = 0; |
24 | asm nop; |
25 | DS75_setREAD(); |
26 | }
|
27 | |
28 | void DS75_Stop(){ |
29 | DS75_setWRITE(); |
30 | DS75_SCL = 0; asm nop; |
31 | DS75_SDA = 0; asm nop; |
32 | DS75_SCL = 1; asm nop; |
33 | DS75_SDA = 1; asm nop; |
34 | DS75_setREAD(); |
35 | }
|
36 | |
37 | unsigned short DS75_Write(unsigned short WTR) { |
38 | short i, error = 0; |
39 | |
40 | DS75_setWRITE(); |
41 | for(i=8;i>0;i--) |
42 | {
|
43 | DS75_SCL = 0; mdelay; |
44 | if((WTR >> (i-1)) & 1) DS75_SDA = 1; else DS75_SDA = 0; mdelay; |
45 | DS75_SCL = 1; mdelay; |
46 | }
|
47 | |
48 | DS75_setREAD(); |
49 | |
50 | DS75_SCL = 0; mdelay; |
51 | DS75_SCL = 1; mdelay; |
52 | error = DS75_PinDATA; mdelay; |
53 | DS75_SCL = 0; mdelay; |
54 | |
55 | return error; |
56 | }
|
57 | |
58 | int DS75_Read_Hex(unsigned short ack) { |
59 | unsigned short i; |
60 | unsigned short test = 0; |
61 | DS75_SCL = 0; |
62 | |
63 | for(i=8;i>0;i--) |
64 | {
|
65 | DS75_SCL = 1; asm nop; |
66 | if(DS75_PinDATA == 1) test |= ((unsigned long)1 << (i-1)); |
67 | DS75_SCL = 0; asm nop; |
68 | }
|
69 | |
70 | if(ack == 1) |
71 | {
|
72 | DS75_setWRITE(); |
73 | DS75_SDA = 0; asm nop; |
74 | DS75_SCL = 1; asm nop; |
75 | DS75_SCL = 0; asm nop; |
76 | } else { |
77 | DS75_setWRITE(); |
78 | DS75_SDA = 1; asm nop; |
79 | DS75_SCL = 1; asm nop; |
80 | DS75_SCL = 0; asm nop; |
81 | }
|
82 | |
83 | DS75_setREAD(); |
84 | return test; |
85 | }
|
Ich kann leider kein Assembler, weswegen ich gehofft habe, eine fertige Routine zu übernehmen. Die Bibliothek aus diesem link hier (Beitrag "I2C-Master [ohne TWI (Softwarelösung) für die ATMEGAs") hat bei mir leider auch nicht funktioniert. Ich bin auch allgemmein nicht so fit in der C-Programmierung, sodass ist hier mehr oder weniger auf fertige Funktionen angewisen bin. Es kann eigentlich nur eine Kleinigkeit sein, aber komm ich nicht drauf.
Michael schrieb: > Ich kann leider kein Assembler, weswegen ich gehofft habe, eine fertige > Routine zu übernehmen. Das obige ist auch kein Assembler. Ist C - kein feines C, geb ich ja zu - aber es funktioniert. im Grunde wird die funktion aus dem Hauptprogramm folgendermaßen aufgerufen:
1 | unsigned int ret; |
2 | |
3 | DS75_Start(); |
4 | DS75_Write(0x9f); // Die Slaveadresse |
5 | ret = DS75_Read(0); // auslesen 1 für ACK und 0 für NoACK vom Master |
6 | DS75_Stop(); |
Klar die Namen der Routinen und die Portzuweisungen müssen / können natürlich angepasst werden.
Danke für den Beispielcode in C, Rene, aber ich denke ich versuchs doch noch erstmal mit der Lib vom Peter Fleury. Ich habe jetzt die compiler optimziation auf 0s (so wie's auch im makefile angegeben ist) verändert, und noch zwei Pullup-Widerstände mit 4,7k eingelötet, obwohl seitens am Sensor schon Pullup-Widerstände eingebaut sind. Im Beispielcode habe ich noch eine if-Abfrage eingebaut, um festzustellen, ob die Start Anweisung am Sensor angekommen ist. Laut Programm kommt die Start-Anweisung vom Sensor mit einem Ack bestätigt an. Das Programm hüpft nämlich in die if-Schleife. Das Problem ist nur, dass es das auch tut, wenn ich eine falsche Adresse für den Sensor-device eingebe. Als Sensorwert (hier das LSB-Byte von einem Lichtsensor) wird immer die Zahl 52 (Dez.) übertragen, der sich leider nicht mit der Lichteinstrahlung auf den Sensor ändert. Bei einem Temperatursensor und gleichen (an Sensor angepassten Programm) wird auch diese Zahl 52 übertragen. Hat hier vielleicht jemand eine Erklärung dafür ?
Auch wenn du schon fast fertig bist; dieser schlanke Code hat sich bei mir schon oft bewährt: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=77455 Gruß Skriptkiddy
Danke für den Code, ich hab ihn gleich mal ausprobiert (an einem Temperatursensor MCP9804) und leider erscheint als Wert nur ständig 0xFF. Ich habe einen 4Mhz Quartz am Mastercontroller in Verwendung. Die internen Delay-Zeiten ändern sich ja dadurch. Kann es daran liegen, warum es nicht funktioniert ? Am HW-TWI funktioniert es mit dem gleichen Ablauf der Befehle. Eine I2C_init Routine gibt's im Code auch nicht. Braucht mann das gar nicht unbedingt ? Pin PB0 soll mein SDA, PB0 der SCL Anschluss sein. Im Anhang habe ich mein Projekt mal angehängt. Egal welche Register ich auch auslesen will, es wird immer nur die 0XFF übertragen.
Die Routinen von Peter Fleury funktionieren bei mir einwandfrei. Hardwareprobleme kannst du ausschließen? Oliver
Ich kann sie insofern aussschließen, dass die Kommunikation über den Hardware TWI einwandfrei funktioniert. Auch mit verschiedenen Sensoren.
An den selben Pins? Hardware-TWI geht, Software nicht?
Da steht folgender Satz in der Doku:
>Adapt the SCL and SDA port and pin definitions and eventually the delay >routine
in the module i2cmaster.S to your target when using the software >I2C
implementation !
Die delay-Routine in i2cmaster.S ist für 4MHz ausgelegt. Wenn dein
Prozessor auch mit 4MHz läuft, sollte es ja passen. Trotzdem kannst du
ja mal ausprobieren, was passiert, wenn du vor dem ret noch ein paar nop
einfügst.
Oliver
Ich habe die beiden "Software" Anschlüsse SDA und SCL auf verschiedene Portpins gelegt. (PC0/PC1, PD0/PD1) und entsprechend den Assembler-Code angepasst. Leider mit den gleichen enttäuschenden Ergebnis. Ich hatte auch erst einen 4Mhz Quarz am Controller und dann einen 8Mhz Quarz mit entsprechender Codeanpassung laut diesen Thread (Beitrag "I2C, es tut sich nichts an den Leitungen", ganz unten) ausgetestet. Was meinst du, Oliver, mit den "nops" vor den "ret" ? An welcher Stelle des Programmcodes soll ich da noch etwas einfügen ?
Michael schrieb: > Was meinst du, Oliver, mit den "nops" vor den "ret" ? An welcher Stelle > des Programmcodes soll ich da noch etwas einfügen ? Mit den nops meinte ich das gleiche, was auch in dem von dir verlinkten Thread steht. Michael schrieb: > Ich habe die beiden "Software" Anschlüsse SDA und SCL auf verschiedene > Portpins gelegt. (PC0/PC1, PD0/PD1) Warum 4 Pins? Was passiert, wenn du Software-TWI auf den Hardware-TWI-Pins laufen lässt? Pull-Up-Widerstände (4,7k) hast du selbstverständlich überall dran, oder? Wie steht die CKDIV8-Fuse? Oliver
Ich habe entweder den Slave an PC0/PC1 oder PD0/PD1 angeschlossen. Ich hab's nur an verschiedenen Portpins ausprobiert. Die Software TWI habe ich an den Hardware Pins angeschlossen (das Assembler File entsprechend abgeändert) und leider habe ich das selbe Ergebnis, der Slave gibt keie Antwort. Die beiden Sensordatenleitungen SDA7SCL sind schon mit einen 4,7k Widerstand versehen. Beim Controller noch extra zwei Pullups einzubauen macht ja dann keinen Sinn mehr, oder ? Wobei ich es auch ausgetestet habe, und am Ergebnis hat sich auch nichts geändert. Beim CKDIV8-Fuse ist im AVR-Studio kein Häkchen. Ich hab nun testweise ein Häkchen dorthin gesetzt und im Terminalprogramm kommt anstatt ein "A" für Fehler ein ganz anderes Zeichen raus. Es ist aber immernoch der Fehlerfall der durchlaufen wird.
Tja, dann musst du dich da weiter durchwühlen. Wenn du kein Speicheroszi hast, auch hier mit nicht weiterkommst: Beitrag "I2C (TWI) Sniffer mit AVR" und auch keinen PC mit Soundkarte hast, den du als "Soundkartenoszi" nutzen könntest, dann häng doch zumindest mal eine LED an SCL, um zu schauen, ob sich da überhaupt was tut. Nutz den Dragon zum debuggen, und versuch halt rauszufinden, was da schief geht. Oliver
Ich habe nun mal LED's mit an die Portpins angelötet, um zu sehen, ob sich überhaupt was tut. Ich debugge über SPI mit Hilfe des AVR-Dragons. Leider ziehen die LEDs ein Signal, dass an Port anliegt (PINxx) sofort nach unten. Die LEDs ziehen aber max. nur 25mA pro Portpin. Es leuchtet als leider gar nichts. Sobald ich die LEDs wieder weglöte, erkennt man beim Debuggen, dass das Programm die Portpins schon entsprechend nach oben oder unten zieht. Wie kann es denn sein, dass die LEDs den PORT in die Knie zwingen ? Außerdem ist mir noch aufgefallen, dass das Programm eigentlich schon am Anfang des Programms bei i2c_init hängen bleibt und nicht mehr die Funktion verlässt. In Wirklichkeit läuft das Programm ja aber weiter, weil ansonst ja auch keine Fehlermeldung erscheinen würde.
TWI ist low-active, der Pegel wird nur über die Pull-Ups nach VCC gezogen, und die haben 4,7k. Da fliesst nicht viel. Also müssen die LED's vom Pin nach VCC, mit passendem Vorwiderstand. Die Leuchten dann, wenn der Pin aktiv = low ist. 25mA für eine LED sind aber selbst für normale Anwendungen schon sehr viel. Ich würde da nicht mehr als ein paar mA fliessen lassen, keine Ahnung, was so ein LM75 an maximalem Strom verträgt. Es reicht ja, wenn die LED so gerade erkennbar glimmt. Michael schrieb: > Außerdem ist mir noch aufgefallen, dass das Programm eigentlich schon am > Anfang des Programms bei i2c_init hängen bleibt und nicht mehr die > Funktion verlässt. In Wirklichkeit läuft das Programm ja aber weiter, > weil ansonst ja auch keine Fehlermeldung erscheinen würde. ??? Was meinst du mit "in Wirklichkeit?" i2c_init bleibt hängen, wenn der Slave nicht reagiert (oder gar keiner da ist). Da aber im lm75 vermutlich time-outs vorhanden sind, dürfte das im Einzelschrittmodus nicht funktionieren. Setz mal einen Breakpoint, und lass das Programm von Anfgang an bis dahin durchlaufen. Wenn es klappt, setze den breakpoint weiter nach hinten, und starte erneut. Oliver
O.K., dass debuggen mit den breakpoints probiere ich gleich mal aus. Ich muss noch sagen, ich will nicht den LM75 ansteuern, sondern einen Temperatursensor MCP9804 von Microchip bzw. einen Lichtsensor von Intersil namens ISL29010. Das ich den Sensor mit 3V und den Mikrocontroller mit 5V betreibe stellt aber kein Problem dar, oder ?
Michael schrieb: > Das ich den Sensor mit 3V und den Mikrocontroller mit 5V betreibe stellt > aber kein Problem dar, oder ? Laut Mega-Datenblatt muß der high-Pegel mindestens 0.6*VCC betragen, um sicher erkannt zu werden, das sind 3V. Wenn du die Pull-ups gegen 3V schaltest, sollte der Mega das also als high erkennen, was mit Hardware-TWI ja anscheinend auch funktioniert. Das ist aber einfach nachzuprüfen. Schaltest du die pull ups gegen 5V, steht im Datenblatt der Sensoren, ob die das vertragen. Ich schätze mal, die tun das nicht. Oliver
Es funktioniert jetzt. Der Grund war die falsche Adressierung des Sensors. Die Adresse musste nur um ein Bit linksgeshifted werden. In diesem thread ist es kurz beschrieben: (http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&p=625681). Danke für eure Hilfe !
Michael schrieb: > Ich kann sie insofern aussschließen, dass die Kommunikation über den > Hardware TWI einwandfrei funktioniert. > Auch mit verschiedenen Sensoren. Michael schrieb: > Es funktioniert jetzt. > > Der Grund war die falsche Adressierung des Sensors. Die Adresse musste > nur um ein Bit linksgeshifted werden. Ja nee, is klar. Und wieder mal sinnlos Zeit verplempert. Der Problem mit der Positionierung der 7-bit-Adresse im Byte ist nicht neu, sogar naheliegend, aber in Peter Fleury's lib in der Hardware- und in der Softwareversion gleich. Wenn es also mit der Harwareversion läuft, kann es das nicht gewesen sein. Was genau verstehst du also unter "laufen"? Leicht angepisst... Oliver
Ich habe für den Hardware-TWI die Funktionsbibliothek von Manfred Langemann verwendet, die jedoch auf der Bib von Peter Fleury beruht. Hier ist bei der Adressierung kein Linksshift notwendig. Entschuldige Oliver, dass hätte ich als Fehlerquelle mit etwas nachdenken und nachschauen schon ausschließen können.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.