Go Down

Topic: [Bericht] RISC-V Inbetriebnahme (Read 1 time) previous topic - next topic

combie

Dec 14, 2019, 01:38 pm Last Edit: Apr 14, 2020, 12:04 pm by combie
Hi!

Am gestrigen Tag ist mein kleines Weihnachtsgeschenk eingetroffen.

Ein Sipeed M1w dock Board

Langzeitplan:
Habe ein paar Raspberries im Feld, mit angedockten Arduinos als Porterweiterungen.
Diese haben sich als recht anfällig erwiesen. (SD-Karte, Stromausfall usw.)
Also für den unbeaufsichtigten Betrieb, nur bedingt geeignet. (UPS nachrüsten)
Auch benötige ich nicht wirklich Linux, o.ä. darauf.

Bin schön länger auf der Suche nach einem leistungsfähigem Ersatz.
Als Alternative stand der Teensy4.0 zur Wahl.
Pinmangel und Preis haben mich (bisher) ein wenig abgeschreckt.

Dann ist mir der K210 unter die Augen gekommen.
Und nach einigem Vergleichen den Sipeed M1w dock geordert.
(da werden alle Pins herausgeführt)

Beeindruckende Daten: (nur ein Auszug)
2 RISC-V 64Bit Cores 
mit wahlweise mit 400MHz, 500MHz oder 600MHz angetrieben
Neurales Netzwerk und FFT in Hardware
16Mbyte Flash (8MB für Programme)
6MByte RAM (müssen sich CPU, neurales Netzwerk und FFT teilen)
IO Spannung 3,3V und 1,8V Bankweise einstellbar
IO Matrix, jedes Signal(z.B. SPI,I2C,UART,PWM) kann auf jeden Pin gemappt werden.
usw. usw. usw.
Für genauere Infos dem obigen Produktlink folgen. Dort finden sich einige PDF Dateien und Tools

Ein schneller Test, mit dem Modul im Auslieferungszustand, also mit MicroPython, zeigte, dass LCD, Kamera, Gesichtserkennung funktionieren.

MicroPython ist nicht so mein Ding, will C++.

Also Arduino IDE fix um die Board Definitionen bereichert.
Tage vorher schon die Seite auf Github untersucht.

Um es vorweg zu nehmen:
Vieles funktioniert.
Vieles aber auch noch nicht.

Kamera bisher noch nicht
Display ja
Wlan Client, ja
Wlan Server, noch nicht
Taktumschaltung, noch nicht
usw.


Zum Start habe ich mir mal einen kleinen Benchmark gebastelt:
Code: [Select]
#include <Streaming.h>

// UNO 288988 at 16MHz (gcc 9.2)
// Maix 336253 at 400MHz 500MHz 600MHz (gcc 8.2)
// ESP8266 86762 at 80MHz
// ESP8266 165909 at 160MHz


using MillisType  = decltype(millis());
MillisType merker = 0;
MillisType loops  = 0;

void setup()
{
  Serial.begin(9600);
  Serial << "Start: "<< __FILE__ << endl;
  Serial << "MillisType: "<< sizeof(MillisType) << endl;
}



void loop()
{
  loops++;
  if(millis() - merker > 1000)
  {
    merker += 1000;
    Serial << loops << endl;
    loops = 0;
  }
}

Die Ausgaben finden sich oben im Code.
Warum der UNO so überraschend schnell erscheint, weiß ich noch nicht.

Als interessante Hürde hat sich millis() erwiesen, es liefert uint64_t Werte.


Irgendwas um 340 Tausend Iterationen pro Sekunde erschien mir doch arg schlapp.
Da muss noch mehr gehen!

Im Lieferumfang befindet sich FreeRTOS.
Also fix damit einen Test gemacht.....

Code: [Select]
#include <Streaming.h>

#include <FreeRTOS.h>
#include <task.h>

using MillisType           = decltype(millis());
MillisType timestamp       = 0;
volatile MillisType loops  = 0;


void iteratorTask( void * parameter )
{
  while(1)
  {
     taskENTER_CRITICAL();
     loops++;
     taskEXIT_CRITICAL();
  }
}

void printTask( void * parameter )
{
  while(1)
  {
    taskENTER_CRITICAL();
    Serial << loops << endl;
    loops = 0;
    taskEXIT_CRITICAL();
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

TaskHandle_t iteratorHandle  = NULL;
TaskHandle_t printHandle     = NULL;

void setup()
{
  Serial.begin(9600);
  Serial << "Start: "<< __FILE__ << endl;
  Serial << "MillisType: " << sizeof(MillisType) << "Byte breit" << endl;
   
  // xTaskCreate(function,name,stackframe,parameter,priority,handle);
  xTaskCreate(iteratorTask,"iteratorTask",1024, NULL,4,&iteratorHandle);
  xTaskCreate(printTask,"printTask",1024,NULL,4,&printHandle);

  vTaskStartScheduler();
}



void loop()
{
  Serial << " Diese Ausgabe wird nie zu sehen sein " << endl;
}

Und "Klack" kommt es auf fast 2 Millionen Iterationen pro Sekunde
Auf dem UNO, mit Arduino_FreeRTOS.h, und reduziertem Stacksize für die Tasks, kommt es auf  ca. 564 Tausend Iterationen


Ja, ich weiß, alles noch nicht sonderlich aussagekräftig.
Mir scheint, das nutzt bisher nur 1 der 2 Cores.



Schlussendlich(vorläufig):
Die Arduino Integration ist noch eine Baustelle.
Die Dokumentation ist so lala, ein guter Zeitpunkt um mein Chinesisch zu verbessern.
Das Board bietet mehr als genug Potential um über die Feiertage zu kommen.
Für den Einsatz im Feld ist es noch zu früh (für mich)

Säge kein Sägemehl.

ArduFE

Danke für die Infos.

RISC-V scheint starkt im Kommen zu sein. Microchip hat ja auch neuerdings welche im Angebot. Könnte also langfristig sein, dass auch mal originale Arduinos damit auftauchen.

Da Du den Teensy 4.0 erwähnt hast: Eine Version mit mehr Pins, ähnlich dem 3.6, kommt wohl im nächsten Jahr. Danach etwas schnelleres (iMX RT1070).

combie

#2
Dec 14, 2019, 06:00 pm Last Edit: Dec 14, 2019, 09:34 pm by combie
Quote
RISC-V scheint starkt im Kommen zu sein.
Ja, an vielen Ecken und Enden.

Chinesische Firmen geben da offensichtlich gerade Vollgas.
Schätze mal, die wollen sich der amerikanischen/trumpschen Willkür entziehen.
Einen Ersatz für Lizenz gebundenen ARM Geräte

Wenn ich politischer wäre, oder mehr Schach spielen würde, könnte ich dem Trump evtl. einen Tipp geben:
> Zwinge niemals den Gegner zu einem guten Zug.

Auch deutsche Firmen/Unis sind am werkeln...
z.B. für Raumfahrt und Medizin.
Säge kein Sägemehl.

combie

#3
Dec 15, 2019, 12:09 am Last Edit: Dec 15, 2019, 09:59 am by combie
So. ein Stückchen weiter....

Die Inbetriebnahme des 2ten Core, war einfacher als gedacht.
Zu den Problemen, gleich mehr, erstmal die Vorstellung:

Hauptdatei:
Code: [Select]
#include <Streaming.h>
#include <entry.h>

const uint8_t led = LED_RED;

void setup()
{
  Serial.begin(9600);
  Serial << "Start Core: "<< current_coreid() << endl;
  pinMode(led,OUTPUT);
  register_core1(main1 , NULL ); // << -- hier die Magie
}

void loop()
{
  const unsigned long interval = 333;
  static uint64_t merker = 0;
  static bool ledStatus = false;
  if(millis() - merker > interval)
  {
    merker+=interval;
    ledStatus = !ledStatus;
    digitalWrite(led,ledStatus);
  }
}


------------------

Zweites Tab:
Code: [Select]

const uint8_t led1 = LED_BLUE;

void setup1()
{
  Serial << "Start Core: "<< current_coreid() << endl;
  pinMode(led1,OUTPUT);
}


void loop1()
{
  const unsigned long interval = 1000;
  static uint64_t merker = 0;

  static bool ledStatus = false;
  if(millis() - merker > interval)
  {
    merker+=interval;
    ledStatus = !ledStatus;
    digitalWrite(led1,ledStatus);
  }
}

int main1(void *parameter)
{
  setup1();
  while(1)
  {
    loop1();
  }
}




Ok, das Programm tut nicht viel, es blinkt aber mit beiden Cores fein vor sich hin.
Die Ausgabe:
Code: [Select]
Start Core: 0
Start Core: 1


Jetzt zu den Problemen:

Z.B. die INTERVAL() Makros tun es nicht in dieser Umgebung, da sie bei beiden Cores auf die gleiche statische Variable zu greifen. Das kommt sich ins Gehege.

Serielle Ausgaben in beiden Cores getätigt, vermischen sich ganz gerne. Denn bisher verhindert nichts und niemand, dass Serial.print(), und seine Brüder, GLEICHZEITIG von beiden Cores aufgerufen werden.
Da müssen Verriegelungen/Semaphore/Mutex geschaffen werden.

So weit ich das absehen kann, betrifft das alle Ressourcen, welche sich die beiden Cores teilen müssen/sollen.


-----

Für die Taktfrequenzmschaltung im Boardsmenue, gibt es auch eine "Lösung".
Ob und welche Nebenwirkungen diese hat, kann ich noch nicht sagen.

Code: [Select]
void setup()
{
  sysctl_cpu_set_freq(F_CPU);
}

void loop()
{

}




Säge kein Sägemehl.

Katsumi_S

#4
Dec 15, 2019, 12:14 pm Last Edit: Dec 15, 2019, 12:21 pm by Katsumi_S
Wie sieht es denn bei dem Ding mit der Stromaufnahme aus? Ich wollte mir die Datenblätter unter Deinem Link anschauen, aber da kommt immer bloß "Not found".
Am gestrigen Tag ist mein kleines Weihnachtsgeschenk eingetroffen.

Ein Sipeed M1w dock Board
Update: Habe es jetzt direkt bei sispeed versucht. Da scheint was kaputt zu sein. http://dl.sipeed.com/MAIX/HDK/M1&M1W/Specifications liefert "Internal Server Error     The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application."

Muss ich wohl in den nächsten Tagen nochmal versuchen.

Gruß, Jürgen

combie

#5
Dec 15, 2019, 12:34 pm Last Edit: Dec 15, 2019, 06:40 pm by combie
Hier mal ein Link zu einem Datenblatt, vielleicht reicht es für einen ersten Überblick.
Laut dortigen Infos benötigt der K210 min 30mA. Plus bis zu 49 mA pro Pin(?).

Eine Strommessung, habe ich noch nicht gemacht.
Das Blechgehäuse wird im Betrieb leicht warm.
Geschätzte 10° wärmer als Umgebung.
Vergleichbar mit einem ESP32 mit Blech Gehäuse

Es werkelt im Maix M1w ja auch noch ein ESP8285 unter dem Blech. So ganz niedrig wird der Stromverbrauch also nicht sein.
Säge kein Sägemehl.

combie

#6
Dec 15, 2019, 07:08 pm Last Edit: Dec 18, 2019, 09:26 pm by combie
Quote
Da müssen Verriegelungen/Semaphore/Mutex geschaffen werden.

So weit ich das absehen kann, betrifft das alle Ressourcen, welche sich die beiden Cores teilen müssen/sollen
Auch hier ein Schritt in die Richtung.
Noch mit der Holzhammer Methode, aber ein Ansatz.

Code: [Select]
#pragma once
//#include <Arduino.h>

// Frei nach Peterson
// https://de.wikipedia.org/wiki/Algorithmus_von_Peterson

class MutualBlock
{
  private:
  volatile int q[2] = {0, 0};
  volatile int turn = 0;

  public:
  void begin()
  {
    int id = current_coreid();
    q[id] = 1;
    turn = !id;
    while(q[!id] == 1 && turn == !id)
    {
      // hier waehre evtl. eine gute Gelegenheit fuer einen TaskSwitch
    }
  }

  void end()
  {
     q[current_coreid()] = 0;
  }
};


begin() belegt die Ressource und wartet bis sie vom anderen Core freigegeben wurde.
end() gibt sie frei.


An geeigneter Stelle, für beide Cores sichtbar, legt man eine Instanz für die zu schützende "Sache" an.

z.B. um die Serielle zu schützen:
Code: [Select]
MutualBlock serialBlock;


Als nächsten Schritt, muss man den gefährdeten Bereich umrahmen mit den zwei Methodenaufrufen begin() und end().

Hier auch ein Beispiel:

Code: [Select]

  {
    serialBlock.begin();
    Serial.print("Core: "); Serial.println(current_coreid());
    Serial.print("text text "); Serial.println("usw text text");
    Serial.print("Core: "); Serial.println(current_coreid());
    Serial.println("------- ");
    serialBlock.end();
  }


Das gewährleistet dann, dass sich beide Cores, bei der seriellen Ausgabe, nicht ins Gehege pfuschen.
Übertragbar auf SPI, I2C usw...

In Sachen Multitasking bringt z.B. FreeRTOS eigene Verriegelungs- Verfahren mit. Es nutzt beide Cores.




Säge kein Sägemehl.

postmaster-ino

Hi

Bedeutet Das dann nicht auch, daß während der Benutzung der 2.te Core blockiert, bis die Ressource wieder frei gegeben wurde?

mfG
Dein Problem, Dein Sketch, Deine Bilder.
Ob ich ohne Diese an Deinem Problem arbeiten will, entscheide aber immer noch ich.
Große Buchstaben? Immer wieder, neben Punkt und Komma, gerne gesehen.

combie

Quote
Bedeutet Das dann nicht auch, daß während der Benutzung der 2.te Core blockiert, bis die Ressource wieder frei gegeben wurde?
Ja! (richtig erkannt)
Das ist der Holzhammer.

Wäre schön, wenn dann nur die eine Task pausiert, und nicht der jeweilige Core blockiert ist.
Aber so weit bin ich noch nicht.

 :o bin für Ideen offen  :o

Säge kein Sägemehl.

postmaster-ino

Hi

Blöde Ideen könnte ich schon zusammen bringen - kaputt reden kann ich auch ganz gut ;) - aber ich befürchte, Da habe ich den Arsch Ecken tiefer, als Du!

Man muß sich ja 'merken', daß Da noch Was zu machen ist - wenn man eben z.B. das Serial.print überspringt, weil der andere Core damit noch beschäftigt ist.
Nur: Wie kommst Du wieder (automatisch) hier hin, daß die jetzt übersprungene Aufgabe ausgeführt wird, wenn die Ressource wieder frei ist?
Das zieht einen Rattenschwanz an Eventualitäten nach sich.
Bei Serial könnte vll. eine Art Ringspeicher helfen, den Core schnell wieder in Gang zu bringen - hier dürfte die Ausführung gefühlte Ewigkeiten benötigen.
Bei schnelleren Vorgängen sind's vll. nur ein paar Takte, Die man untätig in der Ecke stehend wartet - Das dürftest Du aber ebenfalls besser durchschauen, als mir Das bisher (und wohl auch zukünftig) möglich ist.

MfG
Dein Problem, Dein Sketch, Deine Bilder.
Ob ich ohne Diese an Deinem Problem arbeiten will, entscheide aber immer noch ich.
Große Buchstaben? Immer wieder, neben Punkt und Komma, gerne gesehen.

combie

Ach, zu tief solltest du nicht stapeln.. sonst glaubst du da wirklich irgendwann nochmal dran.
Denn immerhin hast du die Sorgen schon ganz richtig erkannt.
Säge kein Sägemehl.

michael_x

@combie:
Ich verstehe dich so, dass du (noch) nicht vorhast, ein Betriebssystem in der Arduino IDE zu schreiben, das selbständig n Tasks auf m Cores laufen lässt sondern dass dies auf Anwender-Ebene ( loop1() ) ablaufen soll.
 
Durch die Gegebenheiten (mehr RAM, 2 Cores) würde sich anbieten (wenn einen wirklich stört, dass 2 Tasks ihre Ausgaben im Serial-Stream mischen) zwei (und größere) Puffer zu verwenden. Der Haupt-Thread könnte sich dann um das synchronisierte Auslesen beider Puffer und die Kommunikation mit dem UART kümmern.

Arduinomäßig könnte das so aussehen, dass es z.B. die Objekte Serial und SerialX gibt, die mit dem gleichen UART verknüpft sind. Und die in ihren Puffern auch <EndOfMessageBlock> Zeichen enthalten.

Falls du wirklich nach Ideen suchst. :)


Aber, wenn du wirklich Wert darauf legst, dass ein zweiter Thread ungestört und ungebremst
vor sich hin schafft, sollte der eigentlich nicht durch zu synchronisierende Serial-Ausgaben behindert werden?

Wenn das Konzept eines einzigen Threads mit nur max. einer ISR im Hintergrund nicht mehr gilt, muss viel Overhead mitgedacht und dazu erfunden werden, fürchte ich.

Quote
Warum der UNO so überraschend schnell erscheint, weiß ich noch nicht.
Oder auch, womit die ESPs die Zeit vertrödeln, wäre unabhängig von dieser Diskussion eine Untersuchung wert.



combie

#12
Dec 16, 2019, 02:00 pm Last Edit: Dec 18, 2019, 09:30 pm by combie
Quote
Ich verstehe dich so, dass du (noch) nicht vorhast, ein Betriebssystem in der Arduino IDE zu schreiben, das selbständig n Tasks auf m Cores laufen lässt sondern dass dies auf Anwender-Ebene ( loop1() ) ablaufen soll.
Im Moment verfolge ich 2 Linien!

----

Einmal den "Arduino Stil", mit je einer loop() und setup() für jeden Core.
Denn mit loop() und setup()  habe ich schon etwas Erfahrung.


Leider bietet Arduino von Hause aus keinerlei Mittel, Tasks oder gar Cores zu synchronisieren.
Das bringt mich dazu, in alle Fallen zu stolpern, welche sich da anbieten.
Und das sind reichlich!
Das kendryte-sdk hat einige Werkzeuge im Bauch. Die muss ich erstmal alle kennen lernen.

-----

Die andere Linie stützt sich auf FreeRTOS (welches dummer Weise nur einen Core bedient).
Damit kenne ich mich noch so gut wie gar nicht aus. Aber das wird noch ....
Die fertigen Programme sehen dann auch gar nicht mehr wie Arduino Programme aus.
Man könnte die Arduino Libs weiter nutzen. Aber in denen fehlte wiederum an den Synchronisierungswerkzeugen.


----

Wenn einzelne Tasks sich gegenseitig ausschließen, dann tut mir das nicht sonderlich weh, wird sich auch nicht vermeiden lassen. Wenn Cores blockiert werden, dann schon eher.
Im Moment überlege ich gerade, wie ich meine TaskMakros, INTERVAL, und auch die TaskObjekte in diese DualCore-DualLoop Umgebung pressen kann.
* TaskMakro und INTERVAL sind portiert*

Quote
(wenn einen wirklich stört, dass 2 Tasks ihre Ausgaben im Serial-Stream mischen)
Ja, das stört.
Denn es wird unlesbar.


Quote
Der Haupt-Thread könnte sich dann um das synchronisierte Auslesen beider Puffer und die Kommunikation mit dem UART kümmern.
Ja, auf sowas wird es dann wohl hinauslaufen.
Entweder Core blockieren, Task anhalten, oder eben Message Queuing
Säge kein Sägemehl.

michael_x

Quote
Ja, das stört.
Denn es wird unlesbar.
Klar.
Ich sehe es aber eher so, dass eventuell der zweite Thread eher als "etwas besonderes" gesehen wird und erstmal selbst gar keine Serial - Ausgaben macht. Sondern den Vorteil hat, dass er ungestört vor sich hin werkeln kann und vom ersten core nicht blockiert wird.

Quote
Das bringt mich dazu, in alle Fallen zu stolpern, welche sich da anbieten. Und das sind reichlich!
Dass der Arduino core und alle Libraries davon ausgehen, dass es gar keine Multithreading - Probleme geben kann, sehe ich auch als kritisch.

postmaster-ino

Hi

Aber auch, wenn Du 'core1' ungestört machen lässt - irgendwie will/soll Der ja auch mit der Außenwelt kommunizieren - wenn dann core0 den Buttler für core1 spielen muß, damit sich nicht Alles in die Haare kriegt, sehe ich keinen größeren Sinn im weiteren core.
Vll. wenn man Den die WS2812B ansteuern lässt ... könnten wir im Nachbar-Thread gebrauchen, wo die 7x42 LEDs als Matrix werkeln sollen ;)
... ist aber etwas wie: Mit dem Ferrari durch die Spielstraße zum Brötchen holen.

MfG
Dein Problem, Dein Sketch, Deine Bilder.
Ob ich ohne Diese an Deinem Problem arbeiten will, entscheide aber immer noch ich.
Große Buchstaben? Immer wieder, neben Punkt und Komma, gerne gesehen.

Go Up