hallo, ich bin relativer mikrocontrolleranfänger, habe aber die ersten hürden schon hinter mir. nun versuche ich mich daran daten per spi zwischen 2 atmega8 auszutauschen, was aber nicht klappt. der master sollte mir was (z.b.15) an den slave schicken und der sollte sie ausgeben. aber leider schreibt schon der master in das SPDR 255 und nicht die gewünschte 15. vielleicht kann ja jemand helfen. danke! dieter
Woher weist du eigentlich dass da 255 reingeschrieben wird und nicht 15? Ansonsten: Wenn du auf Byte-Ebene arbeitest, wie in diesem Fall, dann solltest du als Datanetyp 'unsigned char' benutzen und nicht 'int'. Ob das bei deinem konkretem Problem was hilft kann ich nicht sagen, da ich den Compiler so nicht kenne (ist wohl kein gcc, so wie die Initialisierungs- Sequenz aussieht wurde die von einem IDE-Wizard generiert, dessen Namen mir im Moment nicht einfällt.)
du hast recht, das weiß ich nicht, da ich mir ja nur den inhalt des SPDR ausgeben lasse und nicht was er reinschreibt. habe es auch mit 'unsigned char' ausprobiert, der slave gibt mir aber dennoch nicht, wie von mir beabsichtigt die 15 aus. ja, ist mit wizard von codevision erstellt.
Bevor du ins SPDR schreibst, frage mal ab, ob die SPI überhaupt frei ist (irgend so ein Bit im SPSR, glaube ich), MW
Ich wuerde mal als erstes aus den while-Schleifen die Aufrufe von Master_init und Slave_init rauswerfen. Vor allem beim Slave bin ich mir fast sicher, dass das keine gute Idee ist, den SPI ständig neu zu initialisieren. Vor allem dann nicht wenn gerade eine Übertragung läuft :-) Zumal ja der Slave auch gar nicht darauf wartet, dass ihm die SPI die Ankunft eines Zeichens signalisiert. Initialisiert wird nur einmal. Der Codevision Wizard hat dir die Initialisierungen bereits eingebaut. Kein Grund das nochmal selbst zu machen, vor allem nicht in einer Schleife.
klingt sehr einleuchtend und habe ich auch gemacht, aber leider gibt mir der slave immer noch nicht das aus was ich will.
war ein verzwickter hardwarefehler. klappt jetzt! trotzdem danke an alle! dieter
nun habe ich schon wieder eine frage: wie kann ich denn größere daten als 8 bit übertragen?
Indem du die gesammte Übertragung auf mehrere 8 Bit Teile aufdröselst. Das ist allerdings naheliegend, daher jetzt meine Frage: Was ist dein konkretes Problem?
mein problem ist, dass ich leider nicht weiß, wie ich das machen kann. ich habe ein 20 bit meßwert, den ich übergeben muß.
20 bit 3 * 8 = 24 Also die 20 Bit in 3 Bytes aufteilen und nacheinander senden. In welchem Datentyp hast du den die 20 Bit? (geraten: long. da long aus 4 Bytes besteht, würde ich der Einfachheit halber alle 4 Bytes übertragen. Dann braucht man sich keinen Kopf darüber zerbrechen, wo jetzt das Highbyte liegt). Kannst du zb so machen: void SendLong( long DerWert ) { unsigned char* pData = (unsigned char*)&DerWert; SendByte( *pData++ ); SendByte( *pData++ ); SendByte( *pData++ ); SendByte( *pData++ ); } und auf der Gegenstelle setzt du alles wieder zusammen long ReceiveLong() { long EmpfangenerWert; unsigned char* pTmp = (unsigned char*)&EmpfangenerWert; ReceiveByte( pTmp++ ); ReceiveByte( pTmp++ ); ReceiveByte( pTmp++ ); ReceiveByte( pTmp++ ); } EmpfangeByte muss natuerlich warten, bis auch tatsaechlich ein Byte daherkommt. Dazwischen würde ich noch ein Byte mit bekanntem Inhalt setzen, damit sich die Übertragung auch synchronisieren kann. Ein einfaches Protokoll eben. void SendMesswert( long Wert ) { SendByte( 'M' ); SendLong( Wert ); } long ReceiveMesswert() { unsigned char Tmp; // // Warte bis das Synchronisationsbyte daherkommt // do { ReceiveByte( &Tmp ); while( Tmp != 'M' ); // Synchronisation ist da. Jetzt die eigentlichen // Daten return ReceiveLong(); } In der Praxis wird man das wahrschinlich nicht mit Pollen machen, sondern über Interrupt Funktionen. Aber ich will dir ja nur die Idee eines Protokolls nahebringen. Und ausserdem soll für dich ja auch noch was zu tun übrig bleiben :-)
> long ReceiveLong() > { > long EmpfangenerWert; > unsigned char* pTmp = (unsigned char*)&EmpfangenerWert; > > ReceiveByte( pTmp++ ); > ReceiveByte( pTmp++ ); > ReceiveByte( pTmp++ ); > ReceiveByte( pTmp++ ); return EmpfangenerWert; > }
Guten Tag, hoffentlich kann mir jemand auch bei der Hitze helfen. Ich habe leider auch ein Problem mit dem SPI. Ich möchte 2 Bytes nacheinander vom Master an den Slave schicken und der slave gibt mir immer nur das zweite Byte aus. Master: char Master_write1(unsigned char data) { char received=0; SPDR = data; while(!(SPSR&0x80)); return received; } char Master_write2(unsigned char data2) { char received2=0; SPDR = data2; while(!(SPSR&0x80)); return received2; } Slave: unsigned char Slave_Read1(void) { SPDR=0x00; while(!(SPSR&0x80)); data=SPDR; return data; } unsigned char Slave_Read2() { SPDR=0x00; while(!(SPSR&0x80)); data2=SPDR; return data2; } Was mache ich falsch? Gruß Jürgen
> unsigned char Slave_Read1(void) > { > SPDR=0x00; > while(!(SPSR&0x80)); > data=SPDR; > return data; > } Ist data (bzw. data2) global? Wozu dann ein return? Ansonsten mal ne ganz blöde Frage: Warum zwei Funktionen, die sich (bis auf einen Index) überhaupt nicht unterscheiden? Da tuts auch eine Funktion...
Ach ja, mehr kann ich übrigens nur sagen, wenn ich den Rest vom Code kenne. (Ich weiß jetzt nicht, ob wir den Witz mit der Kristallkugel heute schon hatten, deshalb schenke ich mir den jetzt...)
Ja die sind beide global. Die beide funktionen sind gleich, aber ich will damit auch nur testen ob das übertragen zweier Funktionen hintereinander funktioniert. Das tut es ja leider auch nicht.
Fällt mir doch noch was auf: Der Slave wartet in einer Funktion darauf, dass vom Master was kommt. Woher weiß der Slave, dass er diese Funktion aufrufen muss? Er kann ja nicht wissen, wann der Master mit ihm reden will. Angenommenes Szenario: Der Slave macht grad irgendwas (Hauptprogramm...) und kümmert sich einen Dreck um alles andere. Inzwischen hat der Master Lust bekommen, dem Slave was zu schicken, warum auch immer. Er schickt sein erstes Byte los. Der Slave ist aber immer noch dabei, irgendwas anderes zu machen. Da SPI nicht Interrupt-gesteuert abgefragt wird, bekommt er gar nicht mit, dass der Master ihm gerade was mitteilen will. In der Zwischenzeit schickt der Master sein zweites Byte. Da der Slave noch nix gelesen hat, wird das erste Byte überschrieben. Irgendwann später ruft das Hauptprogramm im Slave die Read-Funktionen auf. Da das erste Byte überschrieben wurde, wird natürlich nur das zweite gelesen...
...Und das compiliert fehlerfrei? Ich sehe im Slave-Programm nirgends eine Deklaration von data und data2... Abgesehen davon ist es unsinnig, mit globalen Variablen in den Funktionen rumzuspielen. 'return SPDR;' tut das selbe und Du schreibst nicht die selbe Variable zwei mal hintereinander (und Du brauchst nur eine Funktion). Aber das ist nur unsauberes Programmieren. Funktionieren dürfte es aus o.g. Gründen auch mit den Änderungen nicht!
Danke! Habe es jetzt mit einem Interrupt gemacht und es funktioniert einwandfrei. Danke! Jürgen
Leider zu früh gefreut. Zwei Byte ausgeben funktioniert, bei drei hingegen nicht mehr. Es ist zum Verzweifeln.... Das erste und das dritte werden ausgegeben, das in der Mitte nicht.
Klar, wenn Du in Deiner Interrupt-Routine den Riesenbrocken printf aufrufst, dann ist das System erst mal für ne Weile beschäftigt. Ist nachvollziehbar, dass da was verloren geht! Immer dran denken: Interrupt Handler so kurz wie möglich, keine Funktionsaufrufe (v.a. keine riesenhaften Bibliotheksfunktionen wie printf oder irgendwelche Fließkommaarithmetik...)! In der ISR tust Du nichts anderes als nur das SPDR einzulesen und in einer Puffer-Variable (Array) zu speichern (dran denken: Puffer global und volatile deklarieren!). Den ganzen Rest (eben z.B. die Ausgabe) im Hauptprogramm!
Vielleicht noch mal kurz zur Erklärung (Du scheinst noch nicht sonderlich viel Erfahrung in Sachen µC zu haben): Tritt ein Interrupt auf, dann löscht die Hardware das I-Bit im Statusregister, wodurch die Bearbeitung neuer Interrupts während der Ausführung der Interrupt-Routine verhindert wird. Wenn jetzt in der ISR ein programmtechnischer Brocken wie printf, der eine Schnittstellenausgabe macht (was für µC-Verhältnisse ne halbe Ewigkeit dauert), aufgerufen wird, kann der µC keine weiteren über SPI reinkommenden Daten verarbeiten. In Deinem Fall bedeutet das: Es kommt ein Byte rein, die ISR wird aufgerufen. Während das empfangene Byte ausgegeben wird (was eine Weile dauert), kommen schon die beiden nächsten Bytes an. Dann passiert wieder genau das, was am Anfang schon das Problem war: Das zuletzt empfangene Byte (also das dritte) überschreibt das zweite, was dadurch verloren geht. Generell gilt bei der Datenkommunikation, dass man ankommende Daten in der entsprechenden ISR in einem Puffer speichert. Dieser Puffer (i.a. ein Array von ausreichender Größe) kann nun mit (in kurzen Abständen eintreffenden) Daten gefüllt werden. In einer Sendepause hat man dann genug Zeit, die Daten weiterzuverarbeiten. In Deinem Fall müsste eine Variable 'volatile unsigned char buffer[3]' her, in dem Du die drei Werte zwischenspeichern kannst. Wenn der Master dann seine drei Bytes gesendet hat, kannst Du im Hauptprogramm in aller Ruhe mit printf den ganzen Mist ausgeben. Dabei geht nix verloren, weil ein printf im Hauptprogramm durch einen Interrupt unterbrochen werden kann, was in der ISR nicht geht.
Danke für die Erklärungen, die nimmt ein Anfänger wie ich gerne auf. Ich habe es auch genauso gemacht, jetzt gibt er aber für die ersten beiden Werte 0 aus und nur der dritte stimmt. volatile unsigned char dataarray[3]; // SPI interrupt service routine interrupt [SPI_STC] void spi_isr(void) { unsigned char data; int i; data=SPDR; // Place your code here for (i=0; i<3; i++) dataarray[i]=data; } Ausgabe: ... int j; for (j = 0; j<3; j++) { printf("%d",dataarray[j]); }
Da ist ein logischer Fehler drin: Was soll die for-Schleife in der ISR? Du schreibst Dein dataarray mit einem einzigen Wert voll. Das kann so nicht klappen. Im Prinzip müsstest Du Dir den Index, bei dem Du das letzte Element gespeichert hast, global merken und in der ISR jeweils den nächsten Wert schreiben. Der jeweils nächste Wert kann ja erst gespeichert werden, wenn er auch eingetroffen ist. Also, Reihenfolge (Beispiel): 1. Wert trifft ein 2. ISR wird aufgerufen 3. In der ISR wird der eingetroffene Wert gesichert (Index 0) 4. Die ISR wird verlassen. 5. Der nächste Wert trifft ein 6. ISR wird erneut aufgerufen 7. zweiter Wert wird gespeichert (Index 1) 8. Raus aus der ISR. 9.-12. Das ganze noch mal 13. Die drei Werte können ausgegeben werden Wenn Du das mit dem Speichermanagement hinbekommst, kannst Du selbstverständlich auch schon mit der Ausgabe beginnen, sobald der erste Wert vorliegt. Da die Ausgabe im Hauptprogramm stattfindet, kann sie ja durch einen Interrupt unterbrochen werden, so dass keine Daten verloren gehen. Aber für den Anfang solltest Du es vielleicht mit der Variante von oben probieren, also erst ausgeben, wenn alle drei Werte da sind.
Danke! Ich habe die For Schleife rausgeworfen und eine globale Variable hochzählen lassen. volatile unsigned char dataarray[3]; volatile int i=0; // SPI interrupt service routine interrupt [SPI_STC] void spi_isr(void) { unsigned char data; data=SPDR; // Place your code here dataarray[i]=data; i++; } Um die richtige Ausgabe zu erhalten, habe ich noch ein delay eingebaut, da mir der erste Wert sonst nicht angezeigt wird. So funktioniert es jetzt endlich, aber kann ich das auch noch eleganter als mit einem delay lösen? delay_ms(100); for (j = 0; j<3; j++) { printf("%d",dataarray[j]); }
Ein delay ist eigentlich immer die schlechteste Lösung. Du willst ja, wenn drei Werte da sind, ausgeben. Du hast eine globale Indexvariable, die Du abfragen kannst. Mach z.B. ne while-Schleife, in der Du wartest, bis i den Wert 2 erreicht hat, was ja bedeutet, dass alle drei Werte drin sind. Der Umweg über dei lokale Variable data in der ISR ist übrigens überflüssig. Du kannst auch direkt 'dataarray[i] = SPDR;' schreiben...
Ach ja, Du musst natürlich abfragen, ob i drei ist und nicht zwei. Wird ja bei Dir nach jedem empfangenen Zeichen inkrementiert...
Leider gibt er mir bei der while Schleife gar keine Werte mehr aus. Die Zählvariable i zählt anscheinend nicht bis 3. while (i==3) { for (j = 0; j<3; j++) { printf("%d",dataarray[j]); } };
while(i == 3) macht keinen Sinn! Eine Warteschleife sieht so aus: while(i < 3); Das ist eine leere Schleife, die so lange ausgeführt wird, wie i kleiner als drei ist. Danach erst soll ausgegeben werden. Anschließend sollte i wieder '0' gesetzt werden, damit die nächsten drei Zeichen empfangen werden können. Also z.B.: void main(void) { unsigned char i, j; //...irgendwelcher Code... while(1) { while(i < 3); for(j = 0; j < 3; j++) printf("%d",dataarray[j]); i = 0; } }
Du sollst ja auch warten, bis i den Wert 3 erreicht hat! while( i != 3 ) ; // mach nichts bzw. normalerweise bindet man das ganze in die grosse Hauptschleife ein: int main() { ... i = 0; interruptes einschalten while( 1 ) { if( i == 3 ) { // 3 Werte sind angekommen for (j = 0; j<3; j++) { printf("%d",dataarray[j]); } i = 0; // bereit zur Aufnahme der nächsten 3 Werte } .. } }
Oh je, das war ja wirklich ein C Anfängerfehler. Ich werde zukünftig aufmerksamer programmieren, um dann keine peinliche Fragen mehr zu posten. Es funktioniert jetzt. Danke!
Nein, er muss vom Master dazu aufgefordert werden. Außerdem muss i.a. gleichzeitig der Master Daten der entsprechenden Länge an den Slave schicken, da der Master Clock nur beim Ausgeben von Daten läuft. D.h. der Slave kann nur dann z.B. 4 Bytes Daten an den Master schicken, wenn er gleichzeitig 4 Bytes vom Master empfängt. Der Master kann natürlich einen Dummy schicken, aber es ist eigentlich immer eine bidirektionale Kommunikation. Der Master hat immer die volle Kontrolle über das System.
Wieso 'schade'. Ist alles nur eine Frage des Protokolls. Der Master kann zb reihum alle Slaves befragen (indem er ein bestimmtes Byte schickt) ob und wieviele Daten ein Slave zum Master schicken will. Danach fordert der Master einen sendewilligen Slave auf seine Daten zu senden. Der Master weiss ja jetzt wieviele Bytes der Slave schicken will und kann daher entsprechend viele Dummy-Bytes zum Slave schicken. Wenn das System nicht so wäre, dann würde man sich eine Menge Probleme mit Bus-Arbritierung einhandeln: Was ist wenn 2 Slaves gleichzeitig Daten schicken wollen? Dein Slave müsste ständig den Bus überwachen um nicht eine Sendung anzufangen während ein anderer Slave gerade Daten sendet. Usw. Dadurch dass die Initiative immer vom Master ausgeht wird vieles einfacher.
Genau. Wenn es jedem Busteilnehmer ermöglicht werden soll, jederzeit Daten zu senden, dann muss man den Buszugriff über wesentlich kompliziertere Protokolle regulieren. Das gibt dann viel Overhead und führt zu Arbitrierungsverfahren wie CSMA-CD/CR, die in Feldbussystemen Verwendung finden. Kannst ja mal nach CSMA o.ä. oder Profibus und Artverwandtem googeln und Dir mal anschauen, wie das da gelöst wird. Das ist schon ein bisschen komplizierter als die Protokolle, das man für SPI verwenden kann.
Danke für den Code zum Senden von 4 Bytes(an Karl Heinz Buchegger). Verwende ihn mit einem Interrupt. Klappt gut. Wenn der Slave nun auch 4 Bytes Versenden soll klappt das leider noch nicht.
> unsigned char Master_Read(unsigned char swdata) > { > swdata=SPDR; > return swdata; > } Du hast da eine Inkonsistenz. Wie gibt Master_Read das gelesene Zeichen an den Aufrufer der Funktion? Entweder per Return Wert oder aber in dem der Aufrufer eine Variable angibt in die Master_Read das Zeichen hineinschreibt. Nun: Im obigen klappt die Methode mit dem Return Wert, die andere, Übergabe einer Variablen klappt nicht, da ist ein Programm-Fehler. Also: Return Wert würde klappen, blöderweise > Master_write( *pData++ ); > Master_Read(swdata1); benutzt du das nicht. Du willst also die Übergabe per Commandline machen. (Auch hier wieder: Das sind eigentlich C-Grundlagen, daher muss ich wieder mal den Hinweis auf Literatur anbringen: Kauf dir Bücher!) Nachdem der Aufgerufene beim Aufrufer eine Variable ändern können soll, musst du einen Pointer auf die Variable übergeben (Warum? Steht in jedem schlechterem C-Buch) Also: void Master_Read( unsigned char* swdata ) { *swdata = SPDR; } und der Aufruf würde so aussehen Master_write( *pData++ ); Master_Read( &swdata1 ); oder aber Variante 2: Rückgabe als Return Wert unsigned char Master_Read() { return SPDR; } und der Aufruf würde so aussehen Master_write( *pData++ ); swdata1 = Master_Read(); Das Allerschlechteste ist allerdings ein Mischmasch aus dem Einen und dem Anderen. Wenn dann noch (wie hier) ein Programmier- fehler dazukommt, dann kompiliert das Ganze wunderbar. Nur funktioniert es halt nicht.
> Du willst also die Übergabe per Commandline machen.
Sorry: Tippfehler:
***********
Argumentübergabe
Meine C-Grundlagen sind leiderschon etwas länger her und selbst beigebracht und deshalb leider lückenhaft. Ich habe es geändert: void SendLong( long DerWert ) { unsigned char *pData; pData=&DerWert; Master_write( *pData++ ); swdata1 = Master_Read(); printf("%d",swdata1); Master_write( *pData++ ); swdata2 = Master_Read(); printf("%d",swdata2); Master_write( *pData++ ); swdata3 = Master_Read(); printf("%d",swdata3); Master_write( *pData++ ); swdata4 = Master_Read(); printf("%d",swdata4); } unsigned char Master_Read() { return SPDR; } Ich bekomme nun Werte ausgegeben, aber leider nicht in der vom Slave vorgegebenen Reihenfolge, sondern manche gar nicht und manche doppelt.
Ich habe die Beiträge hier im Forum über das SPI mit Interesse verfolgt. Auch ich habe ein Problem. Mein System aus Bewegungssensoren kommuniziert über SPI, was auch einwandfrei funktioniert. Jeder Slave hat einen eigenen Mikrocontroller und eine externes Quarz. Wie kann ich alle Quarze synchronisieren? Da ich die Slaves nur nacheinander abfragen kann, alle Daten aber zum exakt gleichen Zeitpunkt aufgenommen werden sollen, benötige ich eine Synchronisation der Quarze. Der Vorschlag nur ein Quarz zu nehmen geht nicht, da ich die Hardware leider schon aufgebaut habe und die Synchronisation gerne per Software hinbekommen würde. Als Busleitungen sind Clock, MOSI, MISO, SS und sonst noch Versorgungsspannung, GND und RESET vorhanden.
Hat denn niemand einen Lösungsvorschlag oder eine Idee? Muß ich tatsächlich das Layout nochmal verändern?
Hi, Hannes, kein Lösungsvorschlag wegen Rätselei: Denn der SPI-Master steuert mit SCK ja diejenigen Slaves, mit denen er Daten austauschen will. Ich hoffe, das, was Du gerade "Clock" nennst, ist dieser SCK nach Datenblatt der Atmegas, andernfalls kämst Du um eine asynchrone Kommunikation nach Art der UART und RS-232 nicht herum. Ciao Wolfgang Horn
Ja, mit Clock meinte ich SCK. Aber ich kann ja über den SPI die Slaves nur einzeln ansprechen und somit keine gemeinsame, zeitgleiche Synchronisation der einzelnen Slaves erreichen. Um zu verhindern, dass die Quarze der einzelnen Slave zu weit "auseinanderlaufen", würde ich sie von Zeit zu Zeit synchronisieren.
Du kannst ja die Slaves auf Software-SPI umschalten (z. B. mit einem vorgelagerten Kommando), damit sie für den nächsten SPI- Transfer ihren MISO-Treiber außer Betrieb nehmen. Über diese Software-SPI überträgst du dann (mit so weit reduzierter Datenrate, dass die Slaves folgen können) eine Art "Broadcast-SPI-Kommando" zum Synchronisieren der Zeit. Danach reaktivieren die Slaves ihr Hardware-SPI, damit sie nach der Messung vom Master schnell der Reihe nach gepollt werden können.
Das klingt ja sehr einfach, aber leider weiß ich nicht wie man auf Software SPI umschaltet und mit einem vorgelagerten Kommando den MISO-Treiber außer Betrieb nehmen kann. Ich habe leider auch nichts dazu gefunden.
da ich jetzt anfange mich mit SPI zu beschäftigen ist der beitrag gerade richtig. @Hannes Oder ziehe die Quartze auf eine gleiche Frequenz mit hilfe Trimmerkondensator.
@masterof: Ich möchte die Synchronisation per Software lösen. @Jörg Wunsch: Wie kann ich auf Software SPI umschalten und den MISO Treiber außer Betrieb nehmen?
Kann mir denn keiner helfen, dabei wie man auf Software SPI umschalten und den MISO Treiber außer Betrieb nehmen kann?
@Hannes: Ich versteh nicht was du willst. Ein Software SPI ist eine selbstprogrammierte Schnittstelle. Hast du schon eine selbst programmiert? Was für einen "Miso Treiber" meinst du? Der Pin hat seine ursprüngliche Funktion, indem man das Hardware SPI deaktiviert.
Ja und genau hier ist leider mein Problem. Ich habe keine Ahnung, wie ich diese Schnittstelle selber programmieren kann. Das Hardware SPI kann ich deaktivieren indem ich das SPI Control Register null setze.
>Ich habe keine Ahnung, wie ich diese Schnittstelle selber programmieren >kann. Das steht im Datenblatt
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.