Forum: Mikrocontroller und Digitale Elektronik SPI.transfer übermittelt nur einmal Array Inhalt


von Epi K. (epi_k)


Lesenswert?

Hallo,

ich denke zu folgendem Problem fehlen mir gewisse Grundlagen:

Die fillDisplay Funktion unten überträgt nur beim ersten SPI.transfer 
den array Inhalt "0xff, 0x00, 0xff" (also bei i=0), danach wird bis i < 
102400 immer 3mal 0x00 übertragen.

Wieso das?
1
void fillDisplay(void){
2
    uint8_t array[3]={0xff,0x00,0xff};
3
4
    SPI1.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0));
5
    for(uint32_t i=0; i<102400;i++){
6
        SPI1.transfer(&array,3);
7
8
    }
9
    SPI1.endTransaction();
10
}

von Michael H. (mha1)


Lesenswert?

Leider verschweigst du die Definition von SPI1.transfer(). Damit können 
wir nur raten.

SPI1.transfer(&array,3) ist vermutlich falsch. Das überträgt einen 
Zeiger auf einen Zeiger auf ein Array.

Entweder SPI1.transfer(&array[0],3) (Zeiger auf das erste Element des 
Arrays) oder SPI1.transfer(array,3) (Zeiger auf das Array) wäre hier 
vermutlich korrekt.

Das es beim ersten Aufruf funktioniert ist vermutlich Zufall, da an der 
Speicherstelle (vermutlich Stack) zufällig noch der korrekte Wert aus 
der Initialisierung steht.

von Epi K. (epi_k)


Angehängte Dateien:

Lesenswert?

Michael H. schrieb:
> SPI1.transfer(array,3) (Zeiger auf das Array)

das gibt ein Fehler zurück, siehe Anhang.


unten die Klasse...
Verwendet wird, oder sollte wohl folgende Funktion:
1
virtual void transfer(void *buf, size_t count);
1
class MbedSPI : public SPIClass
2
{
3
public:
4
    MbedSPI(int miso, int mosi, int sck);
5
    MbedSPI(PinName miso, PinName mosi, PinName sck);
6
    virtual uint8_t transfer(uint8_t data);
7
    virtual uint16_t transfer16(uint16_t data);
8
    virtual void transfer(void *buf, size_t count);
9
10
    // Transaction Functions
11
    virtual void usingInterrupt(int interruptNumber);
12
    virtual void notUsingInterrupt(int interruptNumber);
13
    virtual void beginTransaction(SPISettings settings);
14
    virtual void endTransaction(void);
15
16
    // SPI Configuration methods
17
    virtual void attachInterrupt();
18
    virtual void detachInterrupt();
19
20
    virtual void begin();
21
    virtual void end();
22
23
private:
24
    SPISettings settings = SPISettings(0, MSBFIRST, SPI_MODE0);
25
    _mbed_spi* dev = NULL;
26
    PinName _miso;
27
    PinName _mosi;
28
    PinName _sck;
29
};

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> danach wird bis i <
> 102400 immer 3mal 0x00 übertragen.

Vermutlich weil in dem Array für die Sendedaten danach die Empfangsdaten 
stehen. Den SPI sendet und empfängt parallel. Dummerweise überschreibt 
diese Funktion deine Sendedaten. Das könnte man auch intelligenter 
lösen, hat man aber nicht.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Epi K. schrieb:
> überträgt nur beim ersten SPI.transfer den array Inhalt
Hast du das mit dem Oszi auf dem Bus gemessen?

> SPI.transfer
Du hast dich aber schon informiert, wie SPI prinzipiell funktioniert? 
Du weißt, dass bei SPI gleichzeitig mit jedem gesendeten Bit auch ein 
Bit empfangen wird? Was macht also diese Funktion? Gibt sie evtl. im 
Puffer die gleichzeitig empfangenen SPI-Daten zurück?

Epi K. schrieb:
> das gibt ein Fehler zurück, siehe Anhang.
Ist ja eigentlich klar: die Funktion möchte einen void Pointer und du 
übergibst einen char Pointer.

Probiers mal so:  SPI1.transfer((void*)array,3);

Und dann arbeite doch einfach mal irgendein Grundlagenbuch zum Thema C 
durch. Darin besonders den Abschnitt Pointer und Arrays.

: Bearbeitet durch Moderator
von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> Michael H. schrieb:
>> SPI1.transfer(array,3) (Zeiger auf das Array)
>
> das gibt ein Fehler zurück, siehe Anhang.

Ja logisch. array IST schon ein Zeiger auf das Array! &array ist Unsinn, 
das wäre ein Zeiger auf den Zeiger auf das Array. Bestenfalls &array[0] 
wäre OK, was ein Zeiger auf das erste Element ist, was gleichbedeutend 
mit array ist. Ja, die Zeiger in C sind manchmal etwas verwirrend.

von Wilhelm M. (wimalopaan)


Lesenswert?

Die Signatur der Funktion hat explizit einen Input/Output-Parameter. Das 
Array wird wohl mit der Antwort überschrieben.

von J. S. (jojos)


Lesenswert?

SPI1.transfer(array,3)
Ist richtig, die Fehlermeldung passt nur wenn der Typ von array auch 
geändert wurde. Der cast auf void* ist nicht nötig.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

1
void fillDisplay(void)
2
{
3
  const uint8_t array[3] = {0xff, 0x00, 0xff};
4
  SPI1.beginTransaction(SPISettings(30000000, MSBFIRST, SPI_MODE0));
5
  for(uint32_t i = 0; i < 102400; i++)
6
   for(const uint8_t zelle:array)
7
    SPI1.transfer(zelle);
8
 SPI1.endTransaction();
9
}

ohne Gewähr

von J. S. (jojos)


Lesenswert?

SPI.transfer() ist hier ungünstig und langsam, Grund wurde ja schon 
genannt.   Besser ist es eine ganze Zeile im RAM zu initialisieren und 
diese per SPI.write() zu senden, ohne das Rücklesen was die meisten 
Displays eh nicht unterstützen.

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> SPI1.transfer(array,3)

das geht nicht

Lothar M. schrieb:
> Du weißt, dass bei SPI gleichzeitig mit jedem gesendeten Bit auch ein
> Bit empfangen wird?
Lothar M. schrieb:
> Was macht also diese Funktion? Gibt sie evtl. im
> Puffer die gleichzeitig empfangenen SPI-Daten zurück?

Wilhelm M. schrieb:
> Die Signatur der Funktion hat explizit einen Input/Output-Parameter. Das
> Array wird wohl mit der Antwort überschrieben.
wird wohl das Hauptproblem sein. Ich denke man erkennt es daran?
1
void arduino::MbedSPI::transfer(void *buf, size_t count) {
2
    dev->obj->write((const char*)buf, count, (char*)buf, count);
3
}



so funktioniert es...
1
      for(uint32_t i=0; i<102400;i++){
2
        uint8_t array[3]={0xff,0x00,0xff};
3
        SPI1.transfer(&array,3);
4
      }

Falk B. schrieb:
> Ja logisch. array IST schon ein Zeiger auf das Array! &array ist Unsinn
scheint aber zu funktionieren...

vielen Dank schon mal...

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> SPI.transfer() ist hier ungünstig und langsam, Grund wurde ja schon
> genannt.   Besser ist es eine ganze Zeile im RAM zu initialisieren und
> diese per SPI.write() zu senden, ohne das Rücklesen was die meisten
> Displays eh nicht unterstützen.

hmm interessant, kann man mit write() auch Buffer / Arrays übertragen? 
Naja muss ich mich wohl mal schlau machen.

Arduino F. schrieb:
> for(const uint8_t zelle:array)
das habe ich noch nie gesehen, und diesen Code verstehe ich auch 
nicht...

von Falk B. (falk)


Lesenswert?

Wilhelm M. schrieb:
> Die Signatur der Funktion hat explizit einen Input/Output-Parameter.

Wo siehst du das?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Epi K. schrieb:
> das habe ich noch nie gesehen,

Nennt sich: "Range based for loop"

Sollte sich in jedem halbwegs guten/aktuellen C++ Grundlagenbuch finden 
lassen

Auch möglich: (erfordert libstdc++)
1
  for(uint32_t i = 0; i < 102400; i++)
2
   for(const uint8_t zelle:{0xff,0x00,0xff})
3
    SPI1.transfer(zelle);

: Bearbeitet durch User
von Epi K. (epi_k)


Angehängte Dateien:

Lesenswert?

J. S. schrieb:
> SPI.transfer() ist hier ungünstig und langsam, Grund wurde ja schon
> genannt.   Besser ist es eine ganze Zeile im RAM zu initialisieren und
> diese per SPI.write() zu senden, ohne das Rücklesen was die meisten
> Displays eh nicht unterstützen.

da würde mich schon interessieren wie das gehen soll und ob es schneller 
ist, mit SPI.write kann man ja nur 1 Byte übertragen, kein Buffer...?

Siehe Anhang Bild, ich verliere aktuell ca. 2 Clocks an Zeit zwischen 
jedem Byte...

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
>> diese per SPI.write() zu senden, ohne das Rücklesen was die meisten
>> Displays eh nicht unterstützen.
>
> da würde mich schon interessieren wie das gehen soll und ob es schneller
> ist, mit SPI.write kann man ja nur 1 Byte übertragen, kein Buffer...?

Doch, die Methode write überträgt auch Arrays. Ein "Experte" hat es 
geschafft, die überaus sinnvolle Methode zu verkrüppeln, indem die 
Empfangsdaten die Sendedaten überschreiben. Man hätte schlicht die 
Methode write DIREKT nutzen sollen! Es leben die 1001 
Abstraktionsschichten!
1
void arduino::MbedSPI::transfer(void *buf, size_t count) {
2
    dev->obj->write((const char*)buf, count, (char*)buf, count);
3
}

von Epi K. (epi_k)


Lesenswert?

nun ja, ich verstehe diese transfer Funktion noch nicht richtig (sie 
greift ja schlussendlich auch auf write zu)...

ich kann eine Variable wie folgt anlegen:

uint32_t a = 0xFF00FF;

und mit SPI.transfer(&a,3), wird mir 0xFF, 0x00, 0xFF ausgegeben mit ca. 
1-2 Clock Pause zwischen den Bytes.

Jetzt möchte ich mit SPI.transfer gleich ein Inhalt von 102400 * 3 Bytes 
aufeinmal übertragen, wie stelle ich das am besten an (also ohne 
mehrmals SPI.transfer aufrufen zu müssen)?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

DMA?

von Wilhelm M. (wimalopaan)


Lesenswert?

Falk B. schrieb:
> Wilhelm M. schrieb:
>> Die Signatur der Funktion hat explizit einen Input/Output-Parameter.
>
> Wo siehst du das?
1
void transfer(void* buf, size_t count);

im Gegensatz zu
1
void transfer(const void* buf, size_t count);

von Wilhelm M. (wimalopaan)


Lesenswert?

Da es sich um C++ handelt, ist es einfach unverständlich, wieso man so 
eine vermurkste Signatur schreibt.

Besser wäre

[c]
struct MbedSPI {
    template<auto N> void transfer(char (&a)[N]);

    template<auto N>
    void transfer(std::array<std::byte, N>& a);
};

von Wilhelm M. (wimalopaan)


Lesenswert?

Falk B. schrieb:
> Epi K. schrieb:
>> Michael H. schrieb:
>>> SPI1.transfer(array,3) (Zeiger auf das Array)
>>
>> das gibt ein Fehler zurück, siehe Anhang.
>
> Ja logisch. array IST schon ein Zeiger auf das Array! &array ist Unsinn,
> das wäre ein Zeiger auf den Zeiger auf das Array.

Nein, das ist falsch.

a ist ein Array-Objekt. In den meisten Kontexten zerfällt a zu einem 
Zeiger, was ein Zeiger auf das erste Element ist.

&a ist ein Zeigr auf das Array-Objekt, was auch ein Zeiger auf das erste 
Element ist vom Wert her.

Allerdings zerfällt a dann zu char* während &a den Type char(*)[3] hat.

Dies ist hier zwar egal wegen des void* (Polymorphie für Arme ala C), 
jedoch merkt man das, wenn man (&a + 1) schreibt. Das ist dann ein 
Zeiger (&a[0] + 3), da das Array-Objekt 3 Bytes groß ist.

> Ja, die Zeiger in C sind manchmal etwas verwirrend.

Ganz genau!

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Wilhelm M. schrieb:
> Da es sich um C++ handelt, ist es einfach unverständlich, wieso man so
> eine vermurkste Signatur schreibt.
>
> Besser wäre
>
> struct MbedSPI {
>     template<auto N> void transfer(char (&a)[N]);
>
>     template<auto N>
>     void transfer(std::array<std::byte, N>& a);
> };

(da fehlte das schließende Tag)
1
struct MbedSPI {
2
    template<auto N> void transfer(char (&a)[N]);
3
4
    template<auto N>
5
    void transfer(std::array<std::byte, N>& a);
6
};

von J. S. (jojos)


Lesenswert?

Wilhelm M. schrieb:
> a ist ein Array-Objekt. In den meisten Kontexten zerfällt a zu einem
> Zeiger, was ein Zeiger auf das erste Element ist.

deshalb verstehe ich nicht warum SPI.transfer(array, 3) einen Fehler 
werfen soll. Nur wenn es zu uint32_t array = 0xFF00FF; geändert wurde. 
Oder?

Falk B. schrieb:
> Doch, die Methode write überträgt auch Arrays. Ein "Experte" hat es
> geschafft, die überaus sinnvolle Methode zu verkrüppeln, indem die
> Empfangsdaten die Sendedaten überschreiben. Man hätte schlicht die
> Methode write DIREKT nutzen sollen! Es leben die 1001
> Abstraktionsschichten!
> void arduino::MbedSPI::transfer(void *buf, size_t count) {
>     dev->obj->write((const char*)buf, count, (char*)buf, count);
> }

ja, das wurde hier von Arduino 'vereinfacht'. Das Mbed write ist sehr 
komplex weil ja auch unterschiedliche Anzahlen für transmitt/receive 
count möglich sind.
Und das Arduino transfer ist das blockierende Mbed write, in Mbed gibt 
es noch eine asynchrone Methode wenn es das Target unterstützt, und die 
heißt da dann transfer. Es sollte aber möglich sein auf das komplette 
Mbed API zuzugreifen und kann dann für receive buffer nullptr übergeben, 
dann sollte die Targetabhängige Implementierung das Optimieren.

Wenn es um den RP2040 geht, dann ist in dem Fall der core von 
EarlePhilPower besser. Und man kann die Bodmer/eTFT_SPI Lib nehmen, die 
nutzt da SPI mit PIO und DMA. Diese Lib unterstützt den offiziellen core 
leider nicht, die Krux mit den Arduino cores... Arduino hat hier für den 
RP2040 aber in zwei Jahren nicht viel gemacht und der andere core ist 
hier beliebter. Hat halt nur nicht die angenehmen Dinge von Mbed.
Wenn das Target ein H7 ist, dann geht das SPI noch durch die STM HAL, da 
ist die asynchrone (Mbed)Transfer Funktion deutlich schneller.
Arduino33BLE habe ich nicht benutzt, mit Nordic MCU, k.A. wie es da 
Implementiert ist.

Die Anzahl 102400 ist auch krumm, ist das wirklich ein 320*320 Display 
oder welche Größe?

: Bearbeitet durch User
von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> Wenn es um den RP2040 geht, dann ist in dem Fall der core von
> EarlePhilPower besser. Und man kann die Bodmer/eTFT_SPI Lib nehmen, die
> nutzt da SPI mit PIO und DMA. Diese Lib unterstützt den offiziellen core
> leider nicht, die Krux mit den Arduino cores... Arduino hat hier für den
> RP2040 aber in zwei Jahren nicht viel gemacht und der andere core ist
> hier beliebter. Hat halt nur nicht die angenehmen Dinge von Mbed.
> Wenn das Target ein H7 ist, dann geht das SPI noch durch die STM HAL, da
> ist die asynchrone (Mbed)Transfer Funktion deutlich schneller.
> Arduino33BLE habe ich nicht benutzt, mit Nordic MCU, k.A. wie es da
> Implementiert ist.
>
> Die Anzahl 102400 ist auch krumm, ist das wirklich ein 320*320 Display
> oder welche Größe?

RP2040, und 320*320, jap.
Mit EarlePhilPower Core (und platformio / VS) kriege ich leider ein 
Fehler.

Ich versuche gerade folgendes, leider ohne Erfolg. Beim ausgeklammerten 
funktioniert es, aber beim Zweiten nicht, wieso??
Ziel ist nur alles mit einem SPI.transfer übertragen zu können.
1
void fillDisplay(uint32_t a){
2
/*
3
      for(uint32_t i=0; i<102400;i++){
4
        uint8_t array[3]={a>>16,a>>8,a};
5
        //uint32_t array = a;
6
        SPI1.transfer(array,3);
7
      }
8
*/
9
    uint8_t array[102400*3];
10
    for (uint32_t i = 0; i < (102400*3); i=(i+1)*3) {
11
        array[i] = a>>16;
12
        array[i+1] = a>>8;
13
        array[i+2] = a;
14
    }
15
    SPI1.transfer(array,102400*3);
16
}

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

J. S. schrieb:
>> Abstraktionsschichten!
>> void arduino::MbedSPI::transfer(void *buf, size_t count) {
>>     dev->obj->write((const char*)buf, count, (char*)buf, count);
>> }
>
> ja, das wurde hier von Arduino 'vereinfacht'.

Verschlimmbessert!

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> Ich versuche gerade folgendes, leider ohne Erfolg. Beim ausgeklammerten
> funktioniert es, aber beim Zweiten nicht, wieso??
> Ziel ist nur alles mit einem SPI.transfer übertragen zu können.

Was soll denn der Unsinn? Ich mein dein Ziel ist zwar richtig, der Weg 
aber aus Holz!

Deine Funktion benötig ein 300kB lokales Array! Das hat dein RSP2040 
nicht! Und für ein konstantes Füllmuster ist das Unfug. Bestenfalls für 
eine Zeile!

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

Epi K. schrieb:
> uint8_t array[102400*3];

zuviel für den Stack. So einen Buffer muss man global oder auf dem Heap 
anlegen.

von Epi K. (epi_k)


Lesenswert?

Falk B. schrieb:
> Deine Funktion benötig ein 300kB lokales Array! Das hat dein RSP2040
> nicht! Und für ein konstantes Füllmuster ist das Unfug. Bestenfalls für
> eine Zeile!

nur zum Testen, stimmt, dass mit den 300kB ist gerade zuviel.
Aber leider geht es auch nicht mit 51200*3 Bytes..., und dass der RAM 
voll wäre meldet mir der Compiler nicht.

von J. S. (jojos)


Lesenswert?

Der Stack im main ist 4 kB wenn das im Arduino core nicht geändert ist.

von Falk B. (falk)


Lesenswert?

1
void fillDisplay(uint8_t a){
2
    uint32_t line[240];   // 320*3=480*2=240*4
3
    uint32_t pattern;
4
5
    pattern = a<<24 | a<<16 | a<<8 | 8;
6
    for (uint32_t i = 0; i < 320; i++) {
7
        for (int j=0; j<240; j++) line[j]=pattern;
8
        SPI1.transfer(line, 320*3);
9
    }
10
}

Und ein Array mit den Namen array ist doof.

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> nur zum Testen, stimmt, dass mit den 300kB ist gerade zuviel.
> Aber leider geht es auch nicht mit 51200*3 Bytes..., und dass der RAM
> voll wäre meldet mir der Compiler nicht.

Weil so ein Compiler nicht hellsehen kann und Programmieren nicht 
idiotensicher ist. Stackverbrauch kann der Compiler im Normalfall nicht 
anzeigen. Es gibt einige Ausnahmen, die das können.

von Falk B. (falk)


Lesenswert?

Naja, eigentlich will man ja beim Füllen eine konstante Farbe haben und 
nicht für RGB immer den gleichen Wert, was ja nur Grauwerte ergibt. Also 
eher so.
1
void fillDisplay(uint32_t color) {  // RGB
2
    uint32_t line[240];   // 320*3=240*4
3
    uint32_t p1, p2, p3;
4
5
    p1 = (color <<  8) | (color >> 16);
6
    p2 = (color << 16) | (color >>  8);
7
    p3 = (color << 24) |  color;
8
    
9
    for (uint32_t i = 0; i < 320; i++) {
10
        for (int j=0; j < 240; j+= 3) {
11
            line[j]=p1
12
            line[j+1]=p2;
13
            line[j+2]=p3;
14
        }
15
        SPI1.transfer(line, 320*3);
16
    }
17
}

von Epi K. (epi_k)


Lesenswert?

Falk B. schrieb:
> Naja, eigentlich will man ja beim Füllen eine konstante Farbe haben und
> nicht für RGB immer den gleichen Wert, was ja nur Grauwerte ergibt. Also
> eher so.

ich habe es jetzt so gelöst (array global):
1
  for(uint8_t i = 0; i < 2; i++){
2
    for (uint32_t i = 0; i < (51200); i++) {
3
        array[i*3] = a>>16;
4
        array[i*3+1] = a>>8;
5
        array[i*3+2] = a;
6
    }
7
    SPI1.transfer(array,3*51200);
8
  }

Falk B. schrieb:
> Was soll denn der Unsinn? Ich mein dein Ziel ist zwar richtig, der Weg
> aber aus Holz!

Aber ja, zwischen jedem Byte verliert die SPI1.transfer Funktion noch 
1-2 Clock an Zeit... deshalb ist es wohl wirklich Unsinn :-( ...

von J. S. (jojos)


Lesenswert?

Das sieht auch nach 20 MHz SPI clock aus, mit der genannten Lib kommt 
man auf 62,5 MHz und DMA. Das macht sich beim Bildaufbau schon 
bemerkbar.
Allerdings wird da auf das Ende des DMA gewartet, auch da gibt es noch 
Optimierungsmöglichkeiten.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Falk B. schrieb:
> Naja, eigentlich will man ja beim Füllen eine konstante Farbe haben und
> nicht für RGB immer den gleichen Wert, was ja nur Grauwerte ergibt. Also
> eher so.
>
>
1
> void fillDisplay(uint32_t color) {  // RGB
2
>     uint32_t line[240];   // 320*3=240*4
3
>     uint32_t p1, p2, p3;
4
> 
5
>     p1 = (color <<  8) | (color >> 16);
6
>     p2 = (color << 16) | (color >>  8);
7
>     p3 = (color << 24) |  color;
8
> 
9
>     for (uint32_t i = 0; i < 320; i++) {
10
>         for (int j=0; j < 240; j+= 3) {
11
>             line[j]=p1
12
>             line[j+1]=p2;
13
>             line[j+2]=p3;
14
>         }
15
>         SPI1.transfer(line, 320*3);
16
>     }
17
> }
18
>

Das Array line mit einem repetitiven Pattern vollzuschreiben, macht 
eigentlich auch keinen Sinn.
Da das ganze in C++ realisiert ist, könnte man bis zur Treiber-Ebene mit 
Containern arbeiten. Für diesen speziellen Fall würde man einen 
"Folding-Container" benutzen, der ein Wertemuster wiederholt an 
bestimmten Indexpositionen sichtbar macht. Dann bräuchte man bei 
intelligenter Auslegung nur 3 Byte RAM.
Da das ganze Interface aber eigentlich ein C-Interface ist (s.a. 
Anmerkungen oben), geht das natürlich nicht so einfach. Zudem ist das 
RAM ja da, und ungenutzter Speicher bringt kein Geld zurück ;-)

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> Das sieht auch nach 20 MHz SPI clock aus, mit der genannten Lib kommt
> man auf 62,5 MHz und DMA. Das macht sich beim Bildaufbau schon
> bemerkbar.
> Allerdings wird da auf das Ende des DMA gewartet, auch da gibt es noch
> Optimierungsmöglichkeiten.

welche Lib meinst du jetzt? TFT_eSPI ?

Wie kriege ich ohne GUI Library eine SPI.transfer Funktion hin, die kein 
1-2 Clock zwischen jedem Byte verliert?

Ich suche quasi nur ein Code (Bare-Metal?) für die SPI denn ich in 
meinem "Arduino-Code" integrieren könnte... (RP2040)

Zahle gerne was dafür...

von Falk B. (falk)


Lesenswert?

Wilhelm M. schrieb:
> Das Array line mit einem repetitiven Pattern vollzuschreiben, macht
> eigentlich auch keinen Sinn.

Es ist ein Workaraund. Man kann ja auch die dämliche transfer-Methode 
aufräumen.

> Da das ganze in C++ realisiert ist, könnte man bis zur Treiber-Ebene mit
> Containern arbeiten. Für diesen speziellen Fall würde man einen
> "Folding-Container" benutzen, der ein Wertemuster wiederholt an
> bestimmten Indexpositionen sichtbar macht. Dann bräuchte man bei
> intelligenter Auslegung nur 3 Byte RAM.

Und der ist schneller als die Variante oben? Da habe ich meine Zweifel.

> Da das ganze Interface aber eigentlich ein C-Interface ist (s.a.
> Anmerkungen oben), geht das natürlich nicht so einfach. Zudem ist das
> RAM ja da, und ungenutzter Speicher bringt kein Geld zurück ;-)

Die 960 BYTE kann ein RP2040 wohl verschmerzen, zumal lokal auf dem 
Stack.

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> ich habe es jetzt so gelöst (array global):

Wo denn? Wenn du das global definierst, hast du zwar kein Problem mit 
dem Stack, dafür aber 51kB toten Speicher, der nur für den Unsinn hier 
gebraucht wird. Das ist Bloatware vom Feinsten! Würg

https://de.wikipedia.org/wiki/Bloatware
https://de.wikipedia.org/wiki/Wirthsches_Gesetz

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> Wie kriege ich ohne GUI Library eine SPI.transfer Funktion hin, die kein
> 1-2 Clock zwischen jedem Byte verliert?

Die normale SPI-Lib kann das.

https://www.arduino.cc/reference/en/language/functions/communication/spi/transfer/

Ohhh, dort steck der gleiche Mist dahinter! Wer produziert so einen 
Käse?

Nimm die Variante mit der einen Zeile von mir, das ist ein sehr guter 
Kompromiss aus Geschwindigkeit und RAM-Bedarf.

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

So einen Buffer (oder zwei) kann man sehr gut auch für die 
Zeichenfunktionen gebrauchen, siehe lvgl. Das RAM kann die CPU in 
nullkommanix füllen, jeden SPI Transfer einzeln starten und abwarten 
dauert ewig. Und wenn man dann noch DMA hat und den nächsten Buffer 
vorbereiten kann, dann macht es richtig Sinn.
Das SPI aus der eTFT Lib ist mit den EarlePhilPower Core verwurschtelt, 
ich habe lvgl auf dem Pico daher auch erstmal mit diesem Core 
angefangen.

oder probieren das Mbed SPI direkt zu benutzen:
1
mbed::SPI spi(SPI_MOSI, SPI_MISO, SPI_SCK);
2
3
...
4
   spi.frequency(30'000'000);
5
   spi.write((const char*) array, n, nullptr, 0);

kann es jetzt nur nicht testen.

: Bearbeitet durch User
von Epi K. (epi_k)


Angehängte Dateien:

Lesenswert?

Falk B. schrieb:
> Nimm die Variante mit der einen Zeile von mir, das ist ein sehr guter
> Kompromiss aus Geschwindigkeit und RAM-Bedarf.

nee, ich weiss nicht ob du mich verstehst,

das Problem ist siehe Anhang, diese Pause (roter Pfeil, 96ns, bzw. 1-2 
Clocks), die muss ich weghaben, dann wäre die SPI.transfer perfekt...

Ich weiss jetzt wie ich mit einem einzigen SPI.transfer Aufruf fast den 
ganzen Displayinhalt beschreiben kann, jedoch ist das ganze eben noch zu 
langsam, wegen dieser verflixten Pause im SPI.transfer...

Wie löse ich dies - ohne verwurschtelte Bibliotheken wo ich nicht weiss 
wo jetzt was ist...

von J. S. (jojos)


Lesenswert?

ist denn 24 Bit RGB nötig? RGB565 spart ein Drittel ohne viel Verlust an 
Farben.

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> spi.frequency(30'000'000);
>    spi.write((const char*) array, n, nullptr, 0);

mm sehr interessant, und wo, wie setze ich MOSI, SCK Pins?

"n" wird wohl die Anzahl bytes sein, und was ist nullptr?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Epi K. schrieb:
> nullptr
google: "c++ nullptr"

Habe ich nicht eben schon auf die C++ Referenz hingewiesen?
https://en.cppreference.com/w/cpp/language/nullptr

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

Epi K. schrieb:
> mm sehr interessant, und wo, wie setze ich MOSI, SCK Pins?

die sind als Konstanten so vorhanden, 19,16,18.
Normalerweise darf man da gültige andere Pins wählen, aber das ist in 
der kompilierten Mbed Lib nicht so umgesetzt. Andere Pins führen zum 
'Blink of Death'.

von Epi K. (epi_k)


Angehängte Dateien:

Lesenswert?

J. S. schrieb:
> oder probieren das Mbed SPI direkt zu benutzen:mbed::SPI spi(SPI_MOSI,
> SPI_MISO, SPI_SCK);
> ...
>    spi.frequency(30'000'000);
>    spi.write((const char*) array, n, nullptr, 0);
>
> kann es jetzt nur nicht testen.

ich erhalte folgenden Fehler, siehe Anhang...

von Harald K. (kirnbichler)


Lesenswert?

> class MbedSPI : public SPIClass

Was sagt uns das?

von Wilhelm M. (wimalopaan)


Lesenswert?

Harald K. schrieb:
>> class MbedSPI : public SPIClass
>
> Was sagt uns das?

Öffentliche Vererbung.

von Falk B. (falk)


Lesenswert?

Epi K. schrieb:
> Falk B. schrieb:
>> Nimm die Variante mit der einen Zeile von mir, das ist ein sehr guter
>> Kompromiss aus Geschwindigkeit und RAM-Bedarf.
>
> nee, ich weiss nicht ob du mich verstehst,
>
> das Problem ist siehe Anhang, diese Pause (roter Pfeil, 96ns, bzw. 1-2
> Clocks), die muss ich weghaben, dann wäre die SPI.transfer perfekt...

Ja und? Gewinnst du dann einen Preis? Ist dann dein Leben lebenswert?
Vermutlich entsteht die Pause zwischen den einzelnen Bytes durch das 
nicht sonderlich gute Nachladen des nächsten Werts. Das wird vermutlich 
per CPU und einer Warteschleife gemacht, die halt erstmal erkennen muss, 
daß das aktuelle Byte komplett übertragen wurde und dann erst das 
nächste in das SPI-Register schreiben kann. Und da das SPI vermutlich 
keine Doppelpufferung hat, entsteht halt die Lücke. Mein Gott, dadurch 
läuft dein SPI anstatt mit vollen 20 MHz effektiv nur mit ca. 16MHz. 
D.h. das Schreiben des LCDs mit 307200 Bytes dauert statt 123ms eben 
147ms. Das macht das Kraut nicht fett.

> Ich weiss jetzt wie ich mit einem einzigen SPI.transfer Aufruf fast den
> ganzen Displayinhalt beschreiben kann, jedoch ist das ganze eben noch zu
> langsam, wegen dieser verflixten Pause im SPI.transfer...

Was ist denn zu langsam? Wieviele Jahre Pause liegen denn dazwischen? 
Kann es sein, daß du die Verhältnisse LEICHT verkennst?

> Wie löse ich dies - ohne verwurschtelte Bibliotheken wo ich nicht weiss
> wo jetzt was ist...

Das schrieb ich bereits, du verstehst es leider nicht.

von J. S. (jojos)


Lesenswert?

1
#include <mbed.h>
2
3
using namespace mbed;

Braucht es noch.

: Bearbeitet durch User
von Epi K. (epi_k)


Lesenswert?

Falk B. schrieb:
> Was ist denn zu langsam? Wieviele Jahre Pause liegen denn dazwischen?
> Kann es sein, daß du die Verhältnisse LEICHT verkennst?

stimmt, das habe ich vor lauter Bäume übersehen... ist nicht viel.

Danke an alle.

von Harald K. (kirnbichler)


Lesenswert?

Wilhelm M. schrieb:
> Öffentliche Vererbung.

Ja. Und? Taucht da irgendwo eine Klasse namens "SPI" auf? Ich sehe da 
andere Klassennamen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Harald K. schrieb:
> Wilhelm M. schrieb:
>> Öffentliche Vererbung.
>
> Ja. Und? Taucht da irgendwo eine Klasse namens "SPI" auf? Ich sehe da
> andere Klassennamen.

Die heisst auch SPIClass ;-)

Harald K. schrieb:
>> class MbedSPI : public SPIClass

und wird sich in einer der nicht gezeigten Header-Dateien befinden.

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?


von Wilhelm M. (wimalopaan)


Lesenswert?

Harald K. schrieb:
> Genau. Und jetzt mal das hier ansehen:
>
> Beitrag "Re: SPI.transfer übermittelt nur einmal Array Inhalt"

Das ist ne gaaaanz andere Baustelle ;-)

von J. S. (jojos)


Lesenswert?

Das ist ja der ArduinoMbed core, da sind beide APIs drin. SPI ist die 
Klasse aus Mbed, die Arduino Funktionen greifen darauf zu, Mbed hat viel 
mehr Funktionalität als das Arduino API.
Das Arduino API ist einem eigenen Repo wo nur das API drin ist, da ist 
die SPIClass per define gleich HardwareSPI gesetzt.

https://github.com/arduino/ArduinoCore-API/blob/master/api/HardwareSPI.h

: Bearbeitet durch User
von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
>
1
> #include <mbed.h>
2
> 
3
> using namespace mbed;
4
>
>
> Braucht es noch.

spi.write(0xff0f) gibt nur 0x0f raus, und bei spi.write mit array geht 
gar nichts (kein clock).
1
    for (uint32_t i = 0; i < (51200); i++) {
2
        array[i*3] = a>>16;
3
        array[i*3+1] = a>>8;
4
        array[i*3+2] = a;
5
    }
6
    //SPI1.transfer(array,3*51200);
7
    //spi.unlock();
8
    spi.frequency(30'000'000);
9
    //spi.write((const char*)array,sizeof(array), nullptr, 0);
10
    spi.write(0xff0f);

von J. S. (jojos)


Lesenswert?

Das scheint an der halbherzigen Umsetzung des Mbed API zu liegen. 
Transfer benutzt spi_write_read_blocking vom Pico SDK, und das erwartet 
einen gültigen Pointer für den receive Buffer. Transfer müsste das 
prüfen und entsprechend read oder write only aufrufen, das gibt es im 
SDK.
Also Core patchen und PR einreichen, aber Arduino sieht da träge aus.
Oder gucken wie man an das spi Handle kommt und eine Subclass vom SPI 
bauen.
Ich gucke nachher mal, wollte auch mal meine picoProbe ausprobieren.

Und dann wäre da noch die Frage ob man den 2. core nutzen kann.

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> ist denn 24 Bit RGB nötig? RGB565 spart ein Drittel ohne viel Verlust an
> Farben.

der ILI9488 kann leider nur 18bit oder 3bit ....

von J. S. (jojos)


Lesenswert?

ja, der ILI9488 ist nicht gut für SPI. Über 8 Bit parallel kann der 
RGB565, das wäre noch schneller wenn es so angeklemmt werden kann.

von Falk B. (falk)


Lesenswert?

J. S. schrieb:
> ja, der ILI9488 ist nicht gut für SPI. Über 8 Bit parallel kann der
> RGB565,

Warum kann der das per SPI nicht? Waren die Entwickler zu faul oder 
doof?

>das wäre noch schneller wenn es so angeklemmt werden kann.

Sicher, aber SPI ist halt ein Kompromiss aus Geschwindigkeit und 
Pinbedarf.

von J. S. (jojos)


Lesenswert?

Falk B. schrieb:
> Waren die Entwickler zu faul oder
> doof?

Das würde ich denen nicht unterstellen. Ilitek hat viele Controller, 
dieser hat SPI, aber eben nur 1 oder 6 Bit pro Farbkanal. Dafür kann er 
noch MIPI, 8/9/16/18/24 Bit parallel, das ist doch schon was. Wenn man 
kann, dann sucht man sich einen Controller mit passendem Interface aus. 
Es gibt aber nun mal günstige fertige Kombis von Displays und 
Controller, da muss man mit den Schnittstellen leben die man angeboten 
bekommt.

von Epi K. (epi_k)


Lesenswert?

J. S. schrieb:
> Über 8 Bit parallel kann der
> RGB565, das wäre noch schneller wenn es so angeklemmt werden kann.

jäjo, aber mit Arduino-Code, da müsste ich irgendwie direkt die Port / 
GPIO -Register des RP2040 ansprechen können (und viel im Datenblatt 
herumstudieren), und ob das so einfach geht wie für ein 8bit MCU a la 
Attiny oder so (DDRB = 255; PORTB &= ~(1<<PB0);) - wage ich zu 
bezweifeln.
Und die TFT_eSPI Library ist mir zu unübersichtlich um es nur hierfür zu 
verwenden.

Tja und cool wäre wenn man auch SPI DMA mit paar Register setzen 
aktivieren könnte :D ..., ich denke da gibt es noch kein Beispiel-Code?

also zahlen würde ich gerne für solchen Code..

von J. S. (jojos)



Lesenswert?

habe das auch mal ausprobiert:

Das spi.write((const char*)array,sizeof(array), nullptr, 0); läuft in 
assert rx_len != tx_len und stoppt dann mit hardfault.
Um das zu sauber zu umgehen muss man die libFrameworkArduino.a neu 
bauen, aufwändig...

Ein 10 kB Buffer kann mit der vorhandenen transfer Funktion gesendet und 
gelesen werden (überschreibt den Buffer dann) und das dauert dann 3,705 
ms @ 62,5 MHz.

Quick and dirty kann man aber das write aus dem SDK aufrufen mit
1
uint8_t test_buffer[10*1024];
2
3
mbed::SPI spi(SPI_MOSI, SPI_MISO, SPI_SCK);
4
5
spi.frequency(62'500'000);
6
spi_write_blocking(spi0, (const uint8_t*) test_buffer, sizeof(test_buffer)); // Pico SDK

damit werden die 10 kB in 1,562 ms rausgehauen, also mehr also doppelt 
so schnell, das lohnt sich dann also schon.

: Bearbeitet durch User
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.