Hoy quiero mostrar qué "Recursos de desempeño" se atascó en el ESP32. Es mucho menos conocido que nuestro microcontrolador ESP32 no es un solo procesador, sino un multiprocesador con 2 núcleos. En el ESP, 2 XTENSA 32-BIT LX6 CPUs, que comparte RAM y ROM. Esto se distingue de su predecesor, el ESP8266. Los dos núcleos tienen nombres diferentes. La CPU 0 también se denomina CPU de protocolo (PRO_CPU) y CPU 1 APLICACIÓN CPU (APP_CPU). La CPU 0 controla Wi-Fi, Bluetooth y otros periféricos internos, como SPI, I2C, ADC, etc., mientras que la CPU 1 está disponible para nuestro programa de usuario. Los bocetos que escribimos en el bucle principal y la carga al ESP se ejecutará sin excepción en la CPU 1, mientras que la APP_CPU (CPU 0) se omite de forma predeterminada para el código de la aplicación. El siguiente diagrama muestra la distribución predeterminada de las tareas en las CPU:
Se puede ver que 2 núcleos casi el doble del rendimiento de ESP32 no están directamente disponibles para uso gratuito.
Sin embargo, el Marco ESP también proporciona funciones con el IDE Arduino, que permite distribuir tareas individuales en las CPU ESP32 y, por lo tanto, a la CPU.
Taskhandle_t nedimestaskhadle; |
a disposición. Para crear una nueva tarea, usamos la función XTaskcReApinnedToCoreToCore usando las siguientes opciones:
Xtaskcreatepinnedtocore ( |
Nuestro objetivo es ejecutar el código personalizado como una tarea en la CPU1. Por lo tanto, nuestro código se ejecuta como una tarea en la CPU1 independientemente de la CPU0, como se muestra en la siguiente figura:
Ahora entramos en el siguiente código de muestra en nuestro IDE e invítelo a la ESP32:
TaskHandle_T Core0teekhnd ; TaskHandle_T Core1taskhnd ; vacío configurar() { De serie.Empezar(9600); Xtaskcreepinnedtocore(Coretask0,"CPU_0",1000,CERO,1,&Core0teekhnd,0); Xtaskcreepinnedtocore(Coretask1,"CPU_1",1000,CERO,1,&Core0teekhnd,1); } vacío círculo() { De serie.imprimir ("La CPU de la aplicación está en el núcleo:"); De serie.Imprimir (Xportgetcoreid()); demora (500); } vacío Coretask0( vacío * parámetro ) { por (;;) { De serie.imprimir("Coretask0 se ejecuta en el núcleo:"); De serie.Imprimir(Xportgetcoreid()); flexible(); demora (600); } } vacío Coretask1( vacío * parámetro ) { por (;;) { De serie.imprimir("Coretask1 corre en el núcleo:"); De serie.Imprimir(Xportgetcoreid()); demora (700); } }
.
Con la función interna de ESP xportgetcoreid () podemos gastar el número central en el que se está ejecutando nuestra sección de código. Este número de núcleo puede aceptar el valor 0 o 1. Utilizamos esta función para dar información serial sobre la cual el núcleo se ejecuta la tarea ahora mismo:
Ahora vemos las 3 tareas que se ejecutan en el tema. Una tarea llamada "coretask 0" en la CPU 0, una tarea llamada "coretask1" en la CPU 1, así como nuestra tarea principal (bucle) en el núcleo 1.
Hasta ahora, todo suena demasiado agradable para ser verdad. De hecho, con el uso de la CPU 0, tenemos un problema que debemos prestar atención: como se muestra en la imagen superior, la tarea del protocolo del kernel se ejecuta en la CPU 0. Esta tarea se encarga de la pila WiFi y TCP / IP, entre otras cosas. Si este tiempo más largo no se está ejecutando, porque, por ejemplo, nuestra tarea exige demasiado tiempo de CPU, el sistema puede volverse inestable como un completo y un choque. Por lo tanto, debemos asegurarnos de que nuestra propia tarea no reciba ninguna o solo las afirmaciones máximas de retardo a pequeña escala, de modo que la tarea del protocolo del kernel obtenga suficiente tiempo de computación.
Los lectores atentos de los EM notaron otro problema de código: el programa genera 3 tareas que se encuentran independientemente entre sí, por ejemplo, en diferentes CPU, pero aún comparten un recurso (el puerto COM de la ESP). En principio, las tareas no saben nada acerca de "saber" y, por lo tanto, cuando un recurso está ocupado o cambiado por otra tarea. Puede venir aquí a las colisiones. Estos causan un resultado no predecible, ya que no se puede determinar con precisión momento en que la tarea utiliza el recurso. Tales constelaciones pueden entonces en el mejor de los casos, ya sea en una programática. Condición de carrera o incluso en uno punto muerto terminar. ¿Qué es exactamente un punto muerto, explica que Problema del filósofo, donde 5 filósofos son una tabla de espaguetis, muy claros. Quiero evitar problemas mutuos con la exclusión mutua (MUTEX) y las colisiones al acceder a la búsqueda de recursos compartidos como variables o interfaces.
Aquí es donde estamos en medio del tema de la comunicación de interprocesos. Hemos aprendido mucho sobre tareas y multitarea.
Más información sobre la generación de tareas y el sistema de operación en tiempo real (RTOS) se puede encontrar en la segunda parte de esta serie o en:
https://exploreembedded.com/wiki/index.php?title=Hello%20World%20with%20ESP32%20Explained
Y ahora divertirse experimentando.
5 comentarios
PiffPoff
“Wir müssen also dafür Sorge trage, dass unser eigener Task keine oder nur maximal sehr klein bemessene delay-Anweisungen erhält, damit der Kernel Protokoll Task genügend Rechenzeit zugewiesen bekommt.”
Wenn der andere Task mehr Rechenzeit bekommen soll, dann ist es doch gut wenn der eigene task möglichst lange suspended ist.
Also ist es doch gut wenn der eigene task viele/lange delays hat, oder?
Siggi
Hallo und guten Tag,
herzlichen Dank für die Erklärung. Hat mir sehr dabei geholfen, ein flackerndes Display in den Griff zu bekommen. Unabhängig von der Berechnung wird jetzt die Anzeige über CPU0 ausgegeben.
Kleine Anmerkung zur Ressourcenaufteilung:
Wird eine Funktion aus dem Core Task aufgerufen, wird diese Funktion auch in der zugehörigen CPU ausgeführt.
Liebe Grüße Siggi
doob
{
Serial.begin(9600);
xTaskCreatePinnedToCore(CoreTask0,“CPU_0”,1000,NULL,1,&Core0TaskHnd,0);
xTaskCreatePinnedToCore(CoreTask1,“CPU_1”,1000,NULL,1,&Core0TaskHnd,1);
}
noch ein Tippfehler? sollte es beim zweiten pinning nicht Core1TaskHnd heißen?
Sven
CPU 1 ist für das Anwenderprogramm verantwortlich.
Der Tippfehler wird bestimmt zeitnah korrigiert.
veit
Diese Namentliche Unterscheidung wird getroffen, um zu verdeutlichen, dass die CPU 0 das WLAN, Bluetooth und andere interne Peripheriegeräte wie SPI, I2C, ADC usw. steuert, während die CPU 0 für unser Anwenderprogramm zur Verfügung steht.
bitte korrigieren …. irgendwas müsste von cpu 1 gemacht werden