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.
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.
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.
ü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?
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.
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.
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.
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...
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!
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)?
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.
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?
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.
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!
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.
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.
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.
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.
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.
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
>voidfillDisplay(uint32_tcolor){// RGB
2
>uint32_tline[240];// 320*3=240*4
3
>uint32_tp1,p2,p3;
4
>
5
>p1=(color<<8)|(color>>16);
6
>p2=(color<<16)|(color>>8);
7
>p3=(color<<24)|color;
8
>
9
>for(uint32_ti=0;i<320;i++){
10
>for(intj=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 ;-)
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)
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.
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
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.
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...
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'.
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);
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.
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.
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.
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.
Ü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?
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