Un poco más sobre la multitarea en microcontroladores

En un artículo anterior , hablamos sobre cómo, según el autor, es posible programar las acciones habituales de un microcontrolador en tiempo real, dividiéndolas en varias tareas que son independientes (o casi independientes) entre sí.


Se eligió un microcontrolador, con un núcleo de una familia muy extendida de ARM Cortex M. De las opciones familiares para muchos, y no solo para el autor, con los números 0,3,4 y 7, se eligió M4, porque estaba a la mano.


Las dos consideraciones que nos llevaron a embarcarnos en el camino resbaladizo e inestable de "inventar la bicicleta", como algunos lectores comentaron ingeniosamente, en realidad eran simples. La primera fue que todavía tenemos que vivir y vivir con estas "cortezas". Y el segundo es intentar no hacer algo universal (ganar fama y fortuna), sino hacer algo más centrado, con la esperanza de lograr eficiencia y simplicidad. Aquellos que a veces hacen algo con sus manos recordarán fácilmente que, por regla general, un destornillador especialmente seleccionado es mejor que el que se toma de un juego universal brillante.


Se proporcionó un ejemplo de ensamblador para mostrar que no se gastan más de 80 ciclos de reloj en la conmutación. Y a una velocidad de reloj de 72 megahercios resulta un poco más de 1 microsegundo. Por lo tanto, una marca de 50 microsegundos de tamaño no será tan costosa. Solo el 2 por ciento de los gastos generales. Por lo tanto, como dijo uno de los personajes favoritos del autor, "es aconsejable sufrir".


Por lo tanto, tenemos N tareas, cada una de las cuales está garantizada para ejecutar una parte (marca T) de tiempo y está garantizada para repetir este segmento a más tardar después de (N-1) T marcas, más un retraso que no exceda D. Este retraso molesto, afortunadamente , está limitado por el tamaño máximo posible en el tiempo, que es igual a la suma de la duración de la operación de todas las interrupciones permitidas. En otras palabras, la tarea que tiene la mayor cantidad de interrupciones potenciales durante un período determinado antes del próximo tic es muy desafortunada. Durante más tiempo, la tarea no se puede retrasar. Inevitablemente recibirá su intervalo de tiempo a más tardar en (N-1) T + D microsegundos. En mi opinión, esto se llama tiempo real difícil.


Las tareas deben completar sus tareas e informar sobre la implementación. ¿A quién debo informar? Aparentemente, alguien está a cargo, y sucede que, por regla general, son mucho más pequeños que los subordinados (en verdad, el autor también encontró excepciones cuando había cuatro jefes para tres trabajadores con ambas manos izquierdas, de los cuales solo uno conocía y respetaba la palabra " adecuación ").


Y si "ustedes son muchos, pero yo soy uno", entonces esto significa una cola. Muchos comenzarán a empujar e intentarán deslizarse. Y alguien tendrá que esperar, y luego llegar tarde y explicar. A pesar de que todo esto parece terrible, se llama hermoso: la lucha por el recurso. Las colas son una solución bien conocida para todos. Conocí a muchos que no alimentan el pan; déjenme hacer cola.


¡Pero el nuestro no puede esperar! En el sentido, tareas. Son de tiempo real difícil. Suponga que dos tareas leen lecturas una vez cada segundo, y la tercera debe medir algo cada 10 milisegundos, ponerla en una pila e informar al principio. Y le dicen: "Dime, no hemos terminado con los chefs".


Aparentemente, tienes que girar, por decirlo suavemente, a un tiempo no del todo real (tiempo real suave).


Tengamos una tarea especial que sepa esperar y le encanta hacerlo. El recurso que servirá será el canal de comunicación. Como sabes, no meterás todo de una vez.
Pero, puede averiguar de inmediato qué velocidad debe ser el canal para que no se pierda nada. Para hacer esto, necesita conocer el rendimiento con el que trabajan todos nuestros grafomaníacos, pah, tareas. Obviamente, también debe calcular el tamaño del búfer o los búferes desde los que se enviarán todos los paquetes (o hacia la derecha).


Si el canal no es uno, entonces la esencia no cambia. Para cada canal, simplemente se agrega una tarea separada, que está diseñada para esperar (y, por supuesto, esperar y creer).


Algunas palabras sobre el canal de comunicación con el operador, o más simplemente, con la persona. Aquí el canal es bidireccional, pero la dirección hacia afuera es más interesante. Inmediatamente haga una reserva, hay una circunstancia que, incluso con un fuerte deseo, es imposible de excluir. Esta es la sobrecarga del canal. Durante las pruebas, debemos alcanzarlo y tener un mecanismo para ver su aparición. No discuto, no es bueno engañar, pero se puede omitir un poco. Vaughn, Gerasim, lo abusó por completo.


Por lo tanto, asumimos de inmediato que el mensaje al operador de la tarea puede perderse. Y para que una persona sepa sobre esto, los numeraremos. Esto determinará dónde y cuántas veces nuestro operador se quedó sin nada. En última instancia, siempre puede hacer algo en el código, agregar los cálculos o incluso adjuntarlo al circuito eléctrico para corregir la situación. Parece que, por el momento, será más fácil. Pero, por supuesto, esto no es necesario para aplicaciones militares. Para ser sincero, perder un mensaje no parece una desgracia solo cuando se depura.


Por ejemplo, tengamos una interfaz serie dúplex sin reconocimiento a 115200 baudios. Por ejemplo, RS422 en la configuración "economía" de dos cables - allí, dos - atrás. Su capacidad es de aproximadamente 10,000 bytes por segundo. Tomemos el tamaño promedio de mensaje para una persona igual a 50 bytes. Recibimos 200 mensajes por segundo o un mensaje en 5 milisegundos. Si tenemos tres tareas que quieren comunicar algo, entonces permítales hacerlo cada 15 milisegundos cada una. En promedio, por supuesto. Y si no es en promedio, entonces se requerirán cálculos estadísticos serios o un experimento a gran escala. Elige el último. Después de todo, hemos aprendido a detectar mensajes faltantes y veremos todo en la pantalla del emulador de terminal.
Entonces, deje que las tres tareas creen mensajes individuales. Permita que los mensajes difieran en importancia o urgencia del contenido y nuestras tareas los coloquen en el búfer apropiado. Asigne estos tres amortiguadores de anillo para tres niveles de urgencia como se muestra en la Figura 1.



La cuarta tarea selecciona de estos buffers un mensaje de acuerdo con nuestro plan aprobado e intenta entregarlo. Si el envío aún no es posible, la cuarta tarea estima cuánto puede dormir y lo hace. Después de dormir, ella ya tiene el espacio necesario en el búfer de anillo para enviar.


Los buffers de diversa urgencia, por supuesto, no almacenan los mensajes en sí, sino sus direcciones (enlaces). Al mismo tiempo, las tareas en sí mismas no necesitan esperar en absoluto. Ok? No, en realidad no. Eso no funciona, y he aquí por qué. Cada uno de estos tres buffers de anillo es un recurso compartido. Imagina que la tarea 1 estaba a punto de poner una dirección en un buffer intermedio. Ella lee la palabra, comprueba que el lugar está vacío, es decir, el valor es cero y (en este momento es reemplazada por otra tarea 2, que quiere hacer exactamente lo mismo y tiene éxito), la primera tarea, regresar, pone la palabra allí, sobrescribiendo todo lo que tuvo éxito en segundo lugar. Aquí hay un colega que pide palabras. Parece que sé lo que dirá.
-Sí, todo es muy simple, puedes prohibir las interrupciones durante la prueba y no pasará nada malo, no es por mucho tiempo.
- Cierto, no por mucho tiempo, pero ¿cuántas veces? ¿Cuánto tiempo le quitaremos a la tarea? ¿Y cuál de ellos? Olvidé advertir, nunca prohibimos las interrupciones, nuestra dura secta del tiempo real nos prohíbe hacer esto.
-Y si no prohíbe las interrupciones, puede solicitar a nuestro conmutador de tareas que coloque la dirección del mensaje allí. Él puede hacerlo atómicamente.
- Sí, tal vez, pero luego quiero preguntarle algo más, luego otra. ¿Y por qué entonces logramos 72 grados y luego diluimos todo con agua? Lo siento, quise decir 72 ciclos para cambiar de contexto.
Intentemos hacerlo más fácil, como en la Figura 2.



En este caso, cada tarea tiene su propio búfer o su propio conjunto de búferes, si desea una urgencia, pompa e importancia diferentes. Personalmente, yo, como operador simple, sigo teniendo la misma importancia para todos.


Tal esquema no te hace luchar por el recurso. Ahora tenemos una opción muy funcional. Simplemente no me gusta. Pero, ¿qué pasa si las tareas a la izquierda en la imagen no tienen nada que enviar? Entonces sería más prudente pedir que se despertara la tarea a la derecha cuando aparezca la razón, y no despertarse solo para configurar la alarma nuevamente. Las tareas a la izquierda son más fáciles de hacer. Además, una función que ayuda a despertar a un amigo se mencionó en una publicación anterior.


Preveo una propuesta de racionalización: "Deje que la interrupción del puerto serie (UART) se involucre en lo que la tarea 4 está haciendo ahora, habrá ahorros". Puedes hacer esto, pero no creo que sea bueno. Trataré de aclararlo. Las tareas a la izquierda, de hecho, pueden activar el procedimiento de interrupción UART, y comenzará a funcionar y no se detendrá hasta que haga todo. El procedimiento de interrupción ahora debería hacer todo lo que solía hacer la tarea 4. El tiempo necesario para procesar la interrupción aumentará, ni una sola tarea podrá encenderse hasta que termine el próximo "spool". ¿Y qué les decimos a nuestros camaradas del círculo persistente en tiempo real? Pero nos dijeron que la respuesta a cualquier interrupción externa debería ser lo más breve posible. Este es solo un buen tono. O, en otras palabras: es necesario hacer el bien, el mal y sin ti funcionará.


La Figura 3 explica qué es el proceso y qué desafíos se encuentran.



Ahora pasamos a la situación, se podría decir, un espejo. Esto es cuando la información proviene del exterior. Que sea un canal SPI con varios gondoleros con góndolas y una pequeña orquesta de cuerdas amateur. No, es muy temprano para pensar en descansar. Deje solo la interfaz SPI y algunos chips. Por ejemplo, sensor de presión atmosférica, acelerómetro y memoria almacenada.


Debo decir de inmediato: un ejemplo estúpido. No por el gondolero con su eterno "debo agregar, caballero". No, es estúpido, de hecho, mezclar en una interfaz datos de entrada de diferente importancia. De hecho, si necesita conocer la aceleración, entonces, con seguridad, para determinar rápidamente cuándo quitar el pedal del acelerador, o girar las aletas, o cerrar los ojos, finalmente. Esta información es a menudo necesaria. Pero la presión, cambia lentamente y tendrá que volar unos tres metros hacia abajo, por lo que en los rangos inferiores la vida se volverá más cálida.


En cuanto a la memoria almacenada, ¿y quién generalmente la incluye en este SPI? ¿Hay un segundo SPI? ¿Y no se espera? No hay a dónde ir, hay que hacer algo. Redireccione las flechas en la dirección opuesta en la Figura 2 y comience a pensar.


La tarea 4 ahora sirve al SPI y se despierta solo por sus señales. Su conexión con la tarea 1, que quiere poner algo en la memoria almacenada, se dirige hacia afuera y se lleva a cabo a través de la cola. También es necesario proporcionar un mecanismo para monitorear el desbordamiento del búfer en anillo. La producción de valores de aceleración y presión de la tarea 4 debe proporcionarse sin la participación de dos tareas consumidoras. Solo necesitas girar y seguir el ritmo. Ahora podemos esbozar una imagen explicativa y escribir una nota explicativa. En la Figura 4, estos
Las acciones se muestran esquemáticamente (o diagrama de bloques).



Comprobación de flujo insuficiente: estas acciones lo ayudan a descubrir si el valor de aceleración tiene tiempo de cambiar antes de que la tarea de consumo lo lea nuevamente. Esta verificación se muestra mediante una acción separada en la Figura 4 solo para llamar la atención. De hecho, este paso ocurre junto con la lectura del valor del acelerómetro de acuerdo con el esquema, como se muestra en la Figura 5.



Cabe señalar que existe un recurso compartido, ya que el lugar de almacenamiento del resultado también es un indicador de acciones (semáforo). Las carreras son posibles aquí, hablando el lenguaje de los circuitos, pero para nosotros esto no es una omisión. Después de todo, deslizarse por la puerta de cierre de un vehículo solo en la vida puede considerarse una fortuna. Aquí lo consideraremos con confianza un retraso.


El acceso a la memoria ocurre en porciones para limitar el tiempo de cada paso. Por lo tanto, aseguraremos una lectura uniforme de los valores de aceleración que cambian rápidamente, y en el medio estaremos a tiempo ocupándonos del resto.


Bueno, ahora queda por encontrar algo adecuado de hierro y experimentar como debería. Esta, creo, será la próxima historia.

Source: https://habr.com/ru/post/456242/


All Articles