Multithreading en AVR

6

Si tengo un microcontrolador AVR y aparece un ISR cada 100 microsegundos aproximadamente, ¿puedo cambiar el puntero de pila en el ISR y luego falsificar múltiples subprocesos?

Código Psuedo:

uint8_t currentThread = 0;
void* process0StackPointer = 0;
uint8_t process0SREG = 0;
void* process1StackPointer = 0;
uint8_t process1SREG = 0;
ISR(TIMER0_OCA_vect)
{
  currentThread = !currentThread;
  if (currentThread == 0)
  {
    process0StackPointer = StackPointer;  //Back up the stack pointer of the
    //"0" thread and the status register
    process0SREG = SREG;
    StackPointer = process1StackPointer 
  }
  else
  {
    process1StackPointer = StackPointer;  //Back up the stack pointer of the
    //"1" thread and the status register
    process1SREG = SREG;
    StackPointer = process0StackPointer 
  }

}

¿Es esto algo plausible, o hay algo que no estoy viendo aquí?

    
pregunta DarthRubik

4 respuestas

8

Para crear un sistema de subprocesos múltiples adecuado, necesita hacer un 'cambio de contexto' ver Wikipedia 'cambio de contexto' para una explicación.

El código debe hacer que el 'cambio de contexto' sea 'invisible' para cada hilo. De lo contrario, no se podrá reiniciar un subproceso de forma confiable, destruyendo el valor de hacerlo.

Para hacer un cambio de contexto invisible a cualquier subproceso en ejecución todo de su estado se debe guardar. No es suficiente simplemente guardar el SREG (registro de estado) y voltear a otra pila. Debe guardar todos los registros que puedan estar en uso. Claramente, el conjunto obvio es el archivo de registro AVR completo, 32 registros. Deberían guardarse, y el archivo de registro para el hilo que se va a reanudar se cargará de tal manera que todo se restaurará al mismo estado en el punto en que el hilo se interrumpió previamente.

Resumen: sí, un sistema de subprocesos múltiples AVR podría construirse usando un temporizador, sin embargo, su código necesita guardar y restaurar mucho más estado.

Como dije en mi comentario, un AVR que se ejecuta a su velocidad máxima de 20MHz, necesita uno o más ciclos para completar una instrucción. Se necesitarían aproximadamente 32 instrucciones y, por lo tanto, 64 ciclos para almacenar todos los 32 registros en la memoria, y aproximadamente 64 ciclos más para cargar los registros de un hilo diferente, más un poco más para la entrada y salida del IRS.

Por lo tanto, debe estimar unos 140 ciclos para crear el cambio de contexto de subprocesos múltiples más básico. Eso es 7µs a 20MHz de reloj. Le sugiero que mantenga el cambio de contexto por debajo del 10% de los ciclos de CPU disponibles para que pueda realizar algún trabajo útil. Así que la interrupción es menos frecuente que 70µs.

Un lugar posible para ayudarlo a comprender el alcance de los problemas es mirar un sistema operativo simple existente. Entonces entenderás todas las otras piezas que probablemente se necesitarán más rápidamente. Por ejemplo, algo como FreeRTOS .

Esta página 'Arduino OS' puede ayudar también.

Gran parte del código de cambio de contexto tendrá que estar en el ensamblador porque no se puede obtener de manera confiable en los registros de C. Sin embargo, debido a que el conjunto de instrucciones del AVR es relativamente simple, no debería ser demasiado difícil.

Cada hilo suele estar representado por varios fragmentos de memoria (RAM) diferentes: 1. Los valores de registro. 2. La pila

El AVR de Atmel no tiene mucha RAM, por lo que una parte incómoda es asignar suficiente pila para el subproceso, sin usar tanta memoria que no haya espacio para suficientes subprocesos.

La última pequeña arruga que recuerdo es que una salida de ISR (RETI) no es exactamente lo mismo que un retorno de la subrutina (RET), por lo que también tendrá que pensar en eso. .

    
respondido por el gbulmer
4

Sí, con calificaciones.

Simplemente cambiar el puntero de la pila no será suficiente para cambiar los hilos; también deberá almacenar los valores de los 32 registros en el momento en que se llama a su ISR desde un subproceso dado y restaurar los valores originales cuando vuelva a ese subproceso. Entre esto y la necesidad de almacenar y modificar el puntero de la pila, esto significará que el ISR debe escribirse en conjunto.

También necesitarás asignar una pila separada para cada hilo. La pila predeterminada de AVR comienza en la parte superior de la memoria y se extiende hacia abajo desde allí; deberá asignar una pila separada en algún otro lugar de la memoria para cualquier subproceso secundario.

Entre todo esto, encontrarás que escribir (y, peor aún, ¡depurar!) esta funcionalidad será bastante difícil. Le recomiendo encarecidamente que busque otra forma de estructurar su programa si es posible.

    
respondido por el duskwuff
3

Cuando la pregunta se publicó originalmente, la tasa de interrupción se especificó como 100nsec. Así que respondí:

A una velocidad de interrupción de 100 nseg que corresponde a una frecuencia de 10 MHz. Si tuviera un AVR con una frecuencia de reloj de 100 MHz (y eso es mucho más alto que la mayoría de las partes en la familia AVR de Atmel), resultaría en que solo haya 10 relojes disponibles para responder y manejar cada interrupción. Teniendo en cuenta que la mayoría de los AVR son más lentos que 100 MHz, hay menos relojes disponibles para procesar cada interrupción.

Puedo sugerir que cinco o diez ciclos de reloj nunca serán suficientes para procesar cualquier tipo de rutina de servicio de interrupción de tipo real. Para este tipo de MCU, le sugiero encarecidamente que haga retroceder el período de su entrada de interrupción periódica a algo más en el orden de 1 a 10 milisegundos.

Ahora que el OP ha cambiado a 100usec rate, aquí está mi respuesta:

A una tasa de interrupción de 100 usec que corresponde a una frecuencia de 10 KHz. Si su AVR es uno con una frecuencia de operación típica de 20 MHz, esto corresponderá a:

20 MHz / 10 KHz = 2000 ciclos de reloj por interrupción. A este ritmo, es posible admitir rutinas de servicio de interrupción que tienen una cantidad de código cuidadosamente administrada dentro del contexto de interrupción.

A la pregunta de si es plausible crear un RTOS con cambio de contexto completo usando una tasa de interrupción como esta es algo que tendrá que calcular. Por ejemplo, si escribe código para hacer el cambio de contexto en cada tiempo de interrupción y encuentra que el código tarda 200 ciclos de reloj en completarse, es fácil ver que el 10% del ancho de banda de cómputo de la MCU se consume solo con el trabajo de sobrecarga antes de un determinado la tarea puede ejecutarse.

Personalmente he codificado docenas de aplicaciones MCU integradas que nunca se han acercado a necesitar / requerir un RTOS. Algunas de estas aplicaciones integradas eran proyectos que tenían el aspecto de 30, 40 o incluso 50 todo sucede al mismo tiempo. Estas son algunas de las razones por las que me alejo de las aplicaciones RTOS:

  1. La sobrecarga de cambio de contexto es costosa en términos de sobrecarga general si intenta codificar para una latencia de tarea inferior a milisegundos.
  2. En las MCU de recursos limitados como los AVR, solo hay tanta RAM disponible. Usar una buena parte de él para almacenar contextos de tareas y bloques de control de tareas puede ser una penalización demasiado alta para muchas aplicaciones.
  3. El entorno de tareas RTOS también requiere que haya una administración de agrupación de memoria entre tareas. Si se administra como un montón con asignación dinámica, hay una sobrecarga de procesamiento adicional para realizar la administración del montón. Por otro lado, si selecciona el enfoque más simple y decide una asignación fija de memoria por tarea, entonces coloca límites arbitrarios en la memoria máxima disponible para una tarea determinada y algunas tareas con poca huella de memoria estarán en la memoria no utilizada.
  4. El uso de RTOS casi siempre aumenta una aplicación dada al menos una o dos muescas de capacidades y recursos de la familia de procesadores, lo que aumenta el costo de la lista de materiales.
  5. Para el uso de RTOS "rodar su propio", siempre habrá una carga adicional en su proyecto para mantener el código, corregir errores y afinar los problemas de rendimiento que restan valor a la creación del producto real que debería recibir el 100% de atención.
  6. Si usa un RTOS de terceros, se agrega trabajo adicional al desarrollo del producto para aprender a usarlo, puede haber costos adicionales si se trata de un producto comercial y es posible que aún se encuentre con errores y problemas de rendimiento.

A menudo pienso que las únicas personas que realmente ganan dinero o obtienen beneficios tangibles de un RTOS son aquellas que hacen los productos RTOS y convencen a otros ingenieros de que son necesarios para que la MCU funcione en tiempo real.

    
respondido por el Michael Karas
1

Hay otros enfoques para manejar la concurrencia y los requisitos difíciles en tiempo real, ver, por ejemplo, Atom . Experimenté con este paquete dirigido a un ATMega328p y lo hice funcionar. No está tan bien documentado, pero las ideas son interesantes.

    
respondido por el marangisto

Lea otras preguntas en las etiquetas