Forum: Mikrocontroller und Digitale Elektronik AVR AtMega 16 schnellste SPI realisierung? Daisy Chain...


von Kaplan (Gast)


Lesenswert?

Hallo,

ich hab eine Shake Hands Implementierung realisiert. Dabei benutze ich 2 
ATMega 16, einen als Master, einen als Slave.

allerdings erreiche ich zwischen 2 Atmenga16 gerademal 90kbyte/sekunde.

ich hatte mir etwas mehr vorgestellt.

ISt eine Interrupt gesteuerte Methode schneller?

ich benutze den schnellsten SPI clock (fCLK/4).
Ausserdem moechte ich auf dauer mehr als einen Slave benutzen. Ich denke 
da an 5 oder mehr im Daisy Chain modus.

das problem ist jetzt aber erstmal die langsame uebertragung.

von Kornfisch (Gast)


Lesenswert?

Das Problem der Master Slave SPI zwischen controllern ist dass der Slave 
nicht gleich schnell wie der Master kann. Auch wenn er koennte. Es 
sollte ein FIFO mit sagen wir 64 byte fuer den Empfaenger da sein. Sind 
aber nicht. Es gibt beinm Slave einen Interrupt, und dann muss man das 
Byte holen. Sonst ist das naechste auch schon da. Meines Erachtens ist 
SPI mangels Buffern nicht geeignet fuer schnelle controller/controller 
kommunikation. Falls es doch schnell sein sollte, koennte man eine 
Schieberegister/Buffer-Architektur in einem CPLD bauen. Fuer erhoehte 
Anforderungen gibt's das schon : Serdes. 32bit parallel, der clock von 
25MHz vor hoch-ge-PLL't auf 860MHz oder so und dann hat man PCI auf 
LVDS. Auf der anderen Seite wieder runter. Was dazwischen gibt's leider 
nicht standardmaessig.

von Falk (Gast)


Lesenswert?

@  Kaplan

>ich hab eine Shake Hands Implementierung realisiert. Dabei benutze ich 2
>ATMega 16, einen als Master, einen als Slave.

>allerdings erreiche ich zwischen 2 Atmenga16 gerademal 90kbyte/sekunde.

>ISt eine Interrupt gesteuerte Methode schneller?

Ich denke eher langsamer. Denn wenn für jedes Byte der Interrupt 
aufgerufen werden muss, beschäftigt sich der AVR zu 90% mit Overhead.
Bei fCLK/4 SPI-takt dauert die Übertragung eines Bytes gerade mal 32 
Takte. In dieser Zeit kann der Sendende AVR schom mal das nächste Byte 
bereitstellen (lesen über Pointer ais dem SRAM) und noch bissel warten. 
Der Empfänger kann das zuletzt empfangene Byte speichern. Damit sollte 
man schon recht nah an die theoretisch mögliche Grenze von 500kB/s 
rankommen. Abr wie gesagt OHNE Interrupt.

>Ausserdem moechte ich auf dauer mehr als einen Slave benutzen. Ich denke
>da an 5 oder mehr im Daisy Chain modus.

Daisy Chain bei SPI?

@ Kornfisch

>Byte holen. Sonst ist das naechste auch schon da. Meines Erachtens ist
>SPI mangels Buffern nicht geeignet fuer schnelle controller/controller
>kommunikation. Falls es doch schnell sein sollte, koennte man eine

Naja mal langsam. Bei 16 MHz sind das immerhin 4 Mbit/s, schlappe 500 
kB/s. WENN man es richtig macht! Geht hier eben nicht wie beim UART, wo 
man jedes Byte per Interrupt abholen kann. Manachmal sind schnöde 
Polling-Schleifen halt doch besser als Interupts. ;-)

MfG
Falk

von Kaplan (Gast)


Lesenswert?

dann ist meine schallmauer wohl schon bei 100kbyte/s erreicht. denn ich 
mach ja nicht mehr ausser nur in das register zu schreiben und zu lesen.


ausserdem ist mir nur die datenuebertragung von mehreren SLAVES zum 
MASTER wichtig. gibts da alternativen.

Meine SLAVES sammeln analogdaten und filtern diese. dann werden nur die 
fuer mich sinnvollen an den MASTER uebertragen. und das moeglichst 
schnell!!!

von holger (Gast)


Lesenswert?

>ich benutze den schnellsten SPI clock (fCLK/4).

Ist er das wirklich ?

// SPI2X=1; //Double speed for SPI = 8MHz @16MHz Clock
 SPSR=0x01;

von holger (Gast)


Lesenswert?

War natürlich Quatsch :(
Der Slave kann nur fclk/4.
Bitte löschen bevor falsches verbreitet wird.

von Falk (Gast)


Lesenswert?

@ Kaplan

>dann ist meine schallmauer wohl schon bei 100kbyte/s erreicht. denn ich
>mach ja nicht mehr ausser nur in das register zu schreiben und zu lesen.

Zeig mal deinen Quelltext.

>ausserdem ist mir nur die datenuebertragung von mehreren SLAVES zum
>MASTER wichtig. gibts da alternativen.

SPI ist schon so ziemlich das schnellste, was man sich denken kann. Es 
sei denn, du kanns/willst viele IOs "opfern" und die Daten parallel 
übertragen. Da könnte man noch etwas Zeit rausholen.

MfG
Falk

von Kaplan (Gast)


Lesenswert?

Hallo Falk,
im Anhang noch die Osziaufnahme dazu. 1 byte alle ca 7 us ergeben 139 
kbyte pro sekunde. Mit Shakehands sind nur noch 97.66kbyte/s. allerdings 
kann ich dort von fehlerfreier uebertragung vom Slave zum Master 
ausgehen.
 das ziel ist von mehreren Slaves in kurzer zeit die daten zu einem 
Master zu bringen.


[c]
#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>

#include <avr/interrupt.h>
#include <avr/delay.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include <string.h>

#include "uart.h" // communication with serial interface


#define UART_BAUD_RATE 56000

#define MASTER 1

//overwrite PortB with SPI
#define DD_SS    PB4
#define DD_MOSI    PB5
#define DD_MISO    PB6
#define DD_SCK    PB7

#define DDR_SPI    DDRB

char receivebyte=0x00;
char receivebyte2=0x00;

void SPI_MasterInit(void) {
  // Set MOSI SCKK and SS as output, all others input
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);

  //Set SS as output (SS= PB4)
  //DDRB|=(1<<PB4);

  //Enable SPI, Master, set clock rated fclk/4
  SPCR = (1<<SPE) | (1<<MSTR) ;
}

void SPI_SlaveInit(void)
{
  //Set MISO output, all others input
  DDR_SPI = (1<<DD_MISO);
  //Enable SPI
  SPCR = (1<<SPE);
}

char SPI_SlaveReceiveTransmit(char cData){
  SPDR=cData;
  //wait for reception complete
  while (!(SPSR & (1<<SPIF)));

  //return data register
  return SPDR;
}

char SPI_MasterTransmitReceive (char cData){
  //Start Transmission
  SPDR = cData;

  //wait for transmission complete
  while (!(SPSR & (1<<SPIF)));

  return SPDR;
}

int main(){
  if (MASTER==1) {
    sei();
                  //initialize UART and set baudrate
    uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );     char 
countvar=0;
    SPI_MasterInit();
    while (1) {
      PORTB &= ~(1<<DD_SS);

      //receivebyte=SPI_MasterTransmitReceive(0xF1);
      receivebyte=SPI_MasterTransmitReceive(countvar);
      countvar+=1;

      PORTB |= (1<<DD_SS);

      if (receivebyte != receivebyte2) {
        uart_putc(receivebyte);
        uart_putc(receivebyte2);
        uart_putc(receivebyte-receivebyte2);
        uart_putc(0xAA);
      }
      receivebyte2=receivebyte+1;

    }
  }
//SLAVE
  else {
    sei();
                  //initialize UART and set baudrate
    uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) ); 
char countvar=0;
    SPI_SlaveInit();

    while (1){
      while (!(PINB & (1<<DD_SS)));
      receivebyte=SPI_SlaveReceiveTransmit(countvar);
      countvar+=1;
    }
  }

}
[\c]

von Magnus Müller (Gast)


Lesenswert?

Kaplan wrote:
> Hallo Falk,
> im Anhang noch die Osziaufnahme dazu.

Anhang fehlt.

von Kaplan (Gast)


Angehängte Dateien:

Lesenswert?

hier noch die shakehands loesung plus bild im anhang. SlaveBusy ist die 
ShakeHands leitung :)
wer das verwenden will sollte gleichen ground bei beiden 
Mikrocontrollern verwenden...


[c]
#define SLAVE_BUSY   PINB3
#define DD_SLAVE_BUSY  PB3

// Input influences SPI CLK: 0 = fCLK/4,  1 = fCLK/16, 2=fCLK/64, 
2=fCLK/128
void SPI_MasterInit(char clockselect) {
  // Set MOSI SCKK and SS as output, all others input
  DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)|(1<<DD_SS);

  //Enable SPI, Master, set clock rated fclk/16
  SPCR = (1<<SPE) | (1<<MSTR) | (clockselect);

  // ~SS ist high, somit disable
  PORTB |= (1<<SLAVESELECT); // set SS high

  while (PINB & (1<<SLAVE_BUSY));//warte auf busy low

  while (!(PINB & (1<<SLAVE_BUSY)));//warte auf busy high

}


// cData is a byte to send via SPI,returns a byte that has been received 
synchronously
char SPI_MasterTR(char cData){

  PORTB &= ~(1<<SLAVESELECT); // set SS low


  while (PINB & (1<<SLAVE_BUSY));//warte auf busy low

  SPDR = cData;

  //wait for transmission complete. this is not necessary but clears 
SPIF bit.
  while (!(SPSR & (1<<SPIF)));

  //wait for slave, till transmission is finished
  while (!(PINB & (1<<SLAVE_BUSY)));//warte auf busy high

  //Pulling high slave select
  PORTB |= (1<<SLAVESELECT);

  return SPDR;
}


void SPI_SlaveInit(void)
{
  //Set MISO and SLAVE_BUSY as output, all others input
  //DD_SLAVE_BUSY = PB3 to show status to Master
  DDR_SPI = (1<<DD_MISO) | (1<<DD_SLAVE_BUSY);
  //Enable SPI
  SPCR = (1<<SPE);

  //SET Slave to busy
  //PORTB|=(1<<SLAVE_BUSY);
}


//cData is a byte send to Master
char SPI_SlaveTR(char cData){
  while (PINB & (1<<SLAVESELECT)); //warte auf SS low
  SPDR = cData;
  PORTB &= ~(1<<SLAVE_BUSY);  //setze SLAVE BUSY low

  while (!(SPSR & (1<<SPIF)));  //warte auf SPIF

  PORTB |= (1<<SLAVE_BUSY);    //setze SLAVE BUSY high

  return SPDR;
}

int main(){
  sei();

  if (MASTER==1) {
    uart_puts("MASTER|");
    SPI_MasterInit(0x00);

    while (1) {

      receivebyte = SPI_MasterTR(0x12);

      }
  }
  else {
    uart_puts("SLAVE|");
    while (!(PINB & (1<<SLAVESELECT)));//warte auf SS high

    //SET SLAVE BUSY as output
    DDRB |=  (1<<SLAVE_BUSY);


    PORTB &= ~(1<<SLAVE_BUSY); //set busy low
    _delay_ms(1);

    PORTB |= (1<<SLAVE_BUSY); // set BUSY high

    while (1) {
      SPI_SlaveInit();
      receivebyte = SPI_SlaveTR(0x34);
    }


  }
}

von Kaplan (Gast)


Angehängte Dateien:

Lesenswert?

hier noch der anhang der gefehlt hat...

von Patrick K. (kaplan)


Lesenswert?

dennoch bin ich am ueberlegen ob ein Interrupt gesteuerte SPI 
kommunikation nicht schneller wäre... denn die while schleifen sind doch 
irgendwie lahm. dass ich während einer  while schleife nichts machen 
kann, stört mich nicht.
aber das der slave so zeitkritisch arbeiten muss ist schon hart. mit 
interrupts kenn ich mich nicht so aus aber wenn ich die leitungen SS und 
SLAVE_Busy als externe interrupts verwende, könnte ich doch unter 
umständen einsparen oder?

falls der code oben sich beschleunigen lässt bin ich auch dankbar.

von Andreas W. (Gast)


Lesenswert?

mit SPI habe ich noch nichts gemacht.

ein interrupt braucht ein paar takte um überhaupt zum code zu kommen. es 
sind  ja ein unterroutinen-aufruf und ein sprung in den code. beides 
braucht zeit.

von Carsten P. (papa_of_t)


Lesenswert?

Dein C-Compiler wird auch einen gewissen (wenn auch kleinen) Overhead 
erzeugen, z.B. wenn Du Funktionen aufrufst, Werte zurückgibst, zuweist 
usw.

Du kannst ja mal versuchen, z.B. den Inhalt von 
"SPI_SlaveReceiveTransmit" direkt in die aufrufende Programmstruktur 
'reinzuschreiben. Das sollte schneller gehen als ein Funktionsaufruf, 
schon weil Du die Zeit einsparen solltest, cData in SPDR zu speichern 
und beim Rücksprung das Funktionsergebnis zu lesen. (ok, es kommt auch 
nicht unwesentlich auf den Compiler an). Das sieht dann aus wie "mit 
Absicht schlechterer Programmierstil, dafür schnellerer". Oder wenn es 
schnellstmöglich sein soll, in Assembler:

loop1:
sbis spsr,spif
rjmp loop1

von Falk (Gast)


Lesenswert?

@ Kaplan

>dennoch bin ich am ueberlegen ob ein Interrupt gesteuerte SPI
>kommunikation nicht schneller wäre... denn die while schleifen sind doch

Nein, wäre sie nicht.

>irgendwie lahm. dass ich während einer  while schleife nichts machen

Wieso lahm? Was soll der Interrupt da schneller machen?
Und wieso machst du vor jeder Überttragung eines Bytes einen 
SPI_SlaveInit()? Das ist Unsinn und kostet haufenweise Zeit.

>kann, stört mich nicht.
>aber das der slave so zeitkritisch arbeiten muss ist schon hart. mit

Wieso? Den AVR interessiert das nicht. Ober er nun NOPs ausführt oder 
sinnvolle Operationen ist ihm egal.

>falls der code oben sich beschleunigen lässt bin ich auch dankbar.

Lässt er.

@ Carsten Pietsch

>Dein C-Compiler wird auch einen gewissen (wenn auch kleinen) Overhead
>erzeugen, z.B. wenn Du Funktionen aufrufst, Werte zurückgibst, zuweist
>usw.

>Du kannst ja mal versuchen, z.B. den Inhalt von
>"SPI_SlaveReceiveTransmit" direkt in die aufrufende Programmstruktur
>'reinzuschreiben. Das sollte schneller gehen als ein Funktionsaufruf,

Kann amn einfacher und sauberer, indem man die Funktionen als inline 
definiert. Das spart das Übergeben der Parameter udn den Aufruf per 
call.

>Absicht schlechterer Programmierstil, dafür schnellerer". Oder wenn es
>schnellstmöglich sein soll, in Assembler:

Naja, man kann auch in C noch EINIGES an Geschwindigkeit rausholen.
Zunächst mal nicht dauern einen SPI_SlaveInit() beim Senden machen. 
Einfach nur das, was wirklich notwendig ist. Nämlich das RX bzw. TX 
complete Flag pollen und Daten rauschreiben. Und nicht bei jedem Byte 
ein Handshake mit dem Master machen. Das braucht mehr Zeit als der 
eigentliche Transfer! Den Handshake braucht man nur um die Übertragung 
einzuleiten. Dann wird eine feste Anzahl Bytes übertragen, mit maximaler 
Geschwindigkeit. Und das wars dann auch. Und wieso gibt die SENDEroutine 
die gesendeten Daten wieder zurück? Das ist sinnlos und 
Zeitverschwendung!

Ich würde es ungefähr so machen. Vom Master geht jeweils eine Leitung zu 
den jeweiligen Slaves (Slave Select).
Alle Slaves werdem über eine gemeinsame Leitung (nenn ich mal ACK) in 
Richtung Master verbunden (Die Ausgänge müssen dann als Open Drain 
betrieben werden, sonst gibts Kurzschlüsse).
Der jeweils selektierte Slave erkennt erstmal, dass er angesprochen 
wird, schreibt das erste Byte in sein SPI Datenregister und zieht ACK 
auf LOW als Zeichen dafür, dass es nun losgehen kann. Und das sollte 
auch der Master erkennt ACK=LOW und generiert nun N Datentransfers (N = 
Anzahl der Bytes). Und das so schnell wie möglich. Der Trick dabei ist, 
dass eben NICHT für jedes Byte ein Handshakezyklus gemacht wird wie er 
im Datenblatt beschrieben ist, das ist auch irgendwie Nonsense. Der 
Master muss nach Erkennen von SPIF ein minimale Zeit warten, die auch 
der Slave braucht, um SPIF zu erkennen und neue Daten ins Register zu 
schreiben. Das kann man eigentlich ausrechnen und per konstantem Delay 
realisieren. Das sollte um einiges schneller sein als die bisherige 
Lösung.

MfG
Falk

P.S. Atmel hätte dem SPI mal lieber noch einen Puffer für die 
Senderichtung spendiert, die paar Quadratmicrometer hätten den Preis 
nicht nennenswert beeinflusst, die Funktionalität jedoch WESENTLICH 
verbessert.

P.P.S. Das Problem ist hier ein exakt definiertes Timing, welches von 
Compiler zu Compiler und vor allem in Abhängigkeit der Optimierungsstufe 
schwanken kann. Assembler ist hier besser.


von Patrick K. (kaplan)


Lesenswert?

@FALK: vielen dank fuer deine rege Anteilmahme

Falk wrote:

> Und wieso machst du vor jeder Überttragung eines Bytes einen
> SPI_SlaveInit()? Das ist Unsinn und kostet haufenweise Zeit.

nun ich hab da wohl was falsch in der SPI doku verstanden:
"NOTE: IN SLAVE Mode, the SOI logic will be reset once the SS pin is 
brought high...."
da hab ich gedacht ich muss neu initialisieren. aber man lern ja nie aus 
:)


> Naja, man kann auch in C noch EINIGES an Geschwindigkeit rausholen.
> Zunächst mal nicht dauern einen SPI_SlaveInit() beim Senden machen.
> Einfach nur das, was wirklich notwendig ist. Nämlich das RX bzw. TX
> complete Flag pollen und Daten rauschreiben. Und nicht bei jedem Byte
> ein Handshake mit dem Master machen. Das braucht mehr Zeit als der
> eigentliche Transfer! Den Handshake braucht man nur um die Übertragung
> einzuleiten. Dann wird eine feste Anzahl Bytes übertragen, mit maximaler
> Geschwindigkeit. Und das wars dann auch. Und wieso gibt die SENDEroutine
> die gesendeten Daten wieder zurück? Das ist sinnlos und
> Zeitverschwendung!

also nur handshake bei der einleitung. der slave sagt dann wieviel er zu 
versenden hat und der Master stellt entsprechende anzahl an dummybytes 
zur verfuegung. soweit so gut.
Die senderoutine gibt die daten zurueck, da es sich um die EMPFANGENEN 
DATEN handelt. sowohl master als auch slave senden und empfangen. 
zugegeben: ich brauch nur die richtung vom slave zum master. in diesem 
fall ist es wirklich zeitverschwendung.


> Ich würde es ungefähr so machen. Vom Master geht jeweils eine Leitung zu
> den jeweiligen Slaves (Slave Select).
> Alle Slaves werdem über eine gemeinsame Leitung (nenn ich mal ACK) in
> Richtung Master verbunden (Die Ausgänge müssen dann als Open Drain
> betrieben werden, sonst gibts Kurzschlüsse).
> Der jeweils selektierte Slave erkennt erstmal, dass er angesprochen
> wird, schreibt das erste Byte in sein SPI Datenregister und zieht ACK
> auf LOW als Zeichen dafür, dass es nun losgehen kann. Und das sollte
> auch der Master erkennt ACK=LOW und generiert nun N Datentransfers (N =
> Anzahl der Bytes). Und das so schnell wie möglich. Der Trick dabei ist,
> dass eben NICHT für jedes Byte ein Handshakezyklus gemacht wird wie er
> im Datenblatt beschrieben ist, das ist auch irgendwie Nonsense. Der
> Master muss nach Erkennen von SPIF ein minimale Zeit warten, die auch
> der Slave braucht, um SPIF zu erkennen und neue Daten ins Register zu
> schreiben. Das kann man eigentlich ausrechnen und per konstantem Delay
> realisieren. Das sollte um einiges schneller sein als die bisherige
> Lösung.

Das werd ich heute mal versuchen. vom prinzip her klar. wo ich das 
problem sehe ist diese "minimale zeit" die abgewartet werden muss. denn 
der slave muss ja eben rechtzeitig die daten ins register packen, sonst 
kommt mist dabei raus


> P.P.S. Das Problem ist hier ein exakt definiertes Timing, welches von
> Compiler zu Compiler und vor allem in Abhängigkeit der Optimierungsstufe
> schwanken kann. Assembler ist hier besser.

Am liebsten wuerd ich das natuerlich machen. aber ich hab nicht so viel 
ahnung von assembler... aber das kann ja noch kommen.

Vielen dank auf jeden fall!!!

von Mario. N. (mario731)


Lesenswert?

hallo leute ich bin Anfänger von atmega16,ich brauche  dringend hilfe 
und zwar:
 Es handelet sich um ein steurbares Pendel.Die Aufhängung des Pendel 
wird durch einen Winkelgeber gebildet.der eine analoge spannung ausgibt 
Am andere aluminiunstange befindet sich ein Elektromotor.:
in abhängigkeit von Drehzahl nimmt das pendel ein gleichgewischtslage 
an.

wie stellt ihr das vor? das ding habe ich noch nicht gebaut.brauch noch 
ideen(Literatur (link))?

von Peter D. (peda)


Lesenswert?

@Mario. Nasser

findest Du es gut, einen völlig themenfremden Thread zu hijacken ?

Mach selber einen auf !


Abgesehen davon habe ich ein Pendel noch nie drehen sehen, es pendelt 
(schwingt) halt. Eine Drehzahlmessung dürfte daher ziemlich sinnfrei 
sein.


Peter

von Patrick K. (kaplan)


Lesenswert?

So da bin ich nochmal.
Bezueglich SPI Kommunikation hab ich nochmal etwas herum gespielt. 
Vorallem die "OPTIMIZATION" des compilers umzustellen hat einiges an 
geschwindigkeit gebracht. So konnte ich von 195 kbyte/s auf 260 kbyte/s 
das ganze beschleunigen. Gemessen bei 9 uebertragenene Byte, wobei das 
erste byte nur zu Shakehands Zweck genutzt wird. Dort sagt der Slave dem 
master wieviele byte er uebertragen moechte. im gemessenen fall 8 byte. 
so ergeben sich 9 datenbyte mit 8 nutzdatenbyte in ca 30 microsekunden.

mfg
Patrick Kaplan

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
Noch kein Account? Hier anmelden.