2009-06-11 17 views
9

En muchas aplicaciones integradas existe una compensación entre hacer que el código sea muy eficiente o aislar el código de la configuración específica del sistema para que sea inmune a los requisitos cambiantes.¿Cómo hacer que su código C incrustado sea inmune a los cambios de requisitos sin agregar demasiados gastos generales y complejidad?

¿Qué tipos de construcciones C usualmente empleas para lograr lo mejor de ambos mundos (flexibilidad y reconfigurabilidad sin perder eficiencia)?

Si tiene tiempo, continúe leyendo para ver exactamente de lo que estoy hablando.

Cuando estaba desarrollando SW integrado para controladores de airbag, tuvimos el problema de tener que cambiar algunas partes del código cada vez que el cliente cambiaba de opinión con respecto a los requisitos específicos. Por ejemplo, la combinación de condiciones y eventos que desencadenaría el despliegue del airbag cambia cada dos semanas durante el desarrollo. Odiamos cambiar esa pieza de código con tanta frecuencia.

En ese momento, asistí a la Conferencia de sistemas integrados y escuché una brillante presentación de Stephen Mellor titulada "Hacer frente a los requisitos cambiantes". Puede leer el documento here (hacen que se registre, pero es gratis).

La idea principal de esto era implementar el comportamiento del núcleo en su código pero configurar los detalles específicos en forma de datos. Los datos son algo que puede cambiar fácilmente e incluso puede programarse en EEPROM o en una sección diferente de flash.

Esta idea suena genial para resolver nuestro problema. Compartí esto con mi colega e inmediatamente comenzamos a modificar algunos de los módulos SW.

Cuando intentamos utilizar esta idea en nuestra codificación, encontramos cierta dificultad en la implementación real. Nuestras construcciones de código se volvieron terriblemente pesadas y complejas para un sistema embebido limitado.

Para ilustrar esto, detallaré el ejemplo que mencioné anteriormente. En lugar de tener un montón de declaraciones if para decidir si la combinación de entradas estaba en un estado que requería una implementación de airbag, cambiamos a una gran tabla de tablas. Algunas de las condiciones no eran triviales, así que usamos muchos indicadores de función para poder invocar muchas pequeñas funciones auxiliares que de alguna manera resolvieron algunas de las condiciones. Tuvimos varios niveles de indirección y todo se volvió difícil de entender. Para resumir, terminamos usando mucha memoria, tiempo de ejecución y complejidad del código. La depuración de la cosa tampoco fue directa. El jefe nos hizo cambiar algunas cosas porque los módulos se estaban volviendo demasiado pesados ​​(¡y quizás tenía razón!).

PD: Hay una pregunta similar en SO, pero parece que el enfoque es diferente. Adapting to meet changing business requirements?

+1

su introducción es bastante largo y no especifica la pregunta muy bien ... pero aún así es una buena. – jpinto3912

+0

De acuerdo. Es probable que obtenga más (y mejores) respuestas si ajusta un poco la pregunta. Haz que sea fácil de leer. Haga la pregunta primero y luego nos puede dar los antecedentes relevantes y explicar todos los detalles. Nadie quiere leer una novela antes siquiera de averiguar cuál es la pregunta *; *) – jalf

+0

Creo que el título de la pregunta es un poco largo: P –

Respuesta

3

Como otro punto de vista sobre el cambio de requisitos ... los requisitos entran en construyendo el código. Por qué no tomar un enfoque meta a esto:

  1. separar las partes del programa que son propensos a cambiar
  2. Crear un script que va a pegar partes de la fuente junto

De esta manera usted son el mantenimiento de bloques de construcción de la lógica compatibles en C ... y luego pegar las piezas compatibles juntos al final:

/* {conditions_for_airbag_placeholder} */ 
if(require_deployment) 
    trigger_gas_release() 

luego mantener condiciones independientes:

/* VAG Condition */ 
if(poll_vag_collision_event()) 
    require_deployment=1 

y otro

/* Ford Conditions */ 
if(ford_interrupt(FRONT_NEARSIDE_COLLISION)) 
    require_deploymen=1 

Su escritura de la estructura podría ser:

BUILD airbag_deployment_logic.c WITH vag_events 
TEST airbag_deployment_blob WITH vag_event_emitter 

Pensando en voz alta realmente. De esta forma obtienes un blob binario apretado sin leer en config. Esto es algo así como usar superposicioneshttp://en.wikipedia.org/wiki/Overlay_(programming) pero hacerlo en tiempo de compilación.

+0

Gracias por la información. Ahora que lo menciona, este enfoque es el que usamos en las unidades de control automotriz para generar la pila CAN o configurar el sistema operativo OSEK.El proveedor del software CAN o OSEK le ofrece una herramienta elegante para generar código configurado. Podríamos haber hecho algo como esto en los módulos de airbag. La herramienta no necesitaba ser elegante. – guzelo

+0

Los conceptos básicos a veces funcionan mejor, tengo un script de Python que uso para todo tipo de cosas como esta. Desde plantillas de CMS hasta generación de código basado en emisores de código configurados. :) Ahorra un montón de tiempo cuando solo necesitas algo sucio –

2

Nuestro sistema se subdivide en muchos componentes, con configuración expuesta y puntos de prueba. Hay un archivo de configuración que se lee en la puesta en marcha que realmente nos ayuda a crear instancias de los componentes, a conectarlos entre sí y a configurar su comportamiento.

Es muy similar a OO, en C, con el hack ocasional para implementar algo así como la herencia.

En el mundo de la defensa/aviónica las actualizaciones de software están muy estrictamente controladas, y no se puede simplemente actualizar SW para solucionar problemas ... sin embargo, por alguna extraña razón puede actualizar un archivo de configuración sin una pelea importante. Por lo tanto, nos ha resultado muy útil poder especificar gran parte de nuestra implementación en esos archivos de configuración.

No hay magia, solo una buena separación de preocupaciones cuando se diseña el sistema y un poco de previsión por parte de los desarrolladores.

+0

¿Significa esto que está utilizando la asignación de memoria dinámica? ¿Cómo instancias los componentes al inicio? – guzelo

+0

Sí, asignamos memoria dinámicamente, especialmente al inicio del sistema. Los archivos de configuración se leen, los componentes se asignan, las configuraciones se aplican y luego los procesos se generan para los componentes que los requieren. –

0

Enganchar en un lenguaje dinámico puede ser un salvavidas, si tiene la memoria y la potencia del procesador para ello.

Haga que el C hable con el hardware, y luego pase un conjunto conocido de eventos a un idioma como Lua. Haga que el script Lua analice el evento y devuelva la llamada a la (s) función (es) C correspondiente (s).

Una vez que haya ejecutado correctamente su código C, no tendrá que volver a tocarlo a menos que cambie el hardware. Toda la lógica comercial se convierte en parte del guión, que en mi opinión es mucho más fácil de crear, modificar y mantener.

+2

¡Siempre que PHP no esté cuidando mis sistemas de bolsas de aire! –

+2

Esto sería un problema ya que las cosas deben ir tan rápido como sea posible. Este es un problema en tiempo real. Si va un poco más lento, lo peor que puede pasar no es "un pequeño retraso", sino que es una muerte porque un airbag se despliega un milisegundo más tarde de lo que debería. C es el nivel más alto que puede alcanzar para este tipo de sistemas. – Earlz

+0

La pregunta no fue expresada como específica de los sistemas incorporados en tiempo real, aunque el ejemplo resultó ser uno. – patros

1

Supongo que lo que podría hacer es especificar varios comportamientos válidos basados ​​en un byte o palabra de datos que podría obtener de EEPROM o un puerto de E/S si es necesario y luego crear código genérico para manejar todos los posibles eventos descritos por esos bytes.

Por ejemplo, si usted tenía un byte que especifica los requisitos para la liberación de la bolsa de aire podría ser algo como:

Bit 0: colisión trasera

Bit 1: velocidad por encima de 55 mph (puntos de bonificación para generalizar el valor de la velocidad)

Bit 2: pasajero en el coche

...

Etc

Luego, ingresa otro byte que dice qué sucedió y compara los dos. Si son lo mismo, ejecuta tu comando, si no, no lo hagas.

+0

Si no recuerdo mal, esto es lo que terminamos haciendo. – guzelo

2

¿Qué intentas ahorrar exactamente? Esfuerzo de re-trabajo de código? ¿La cinta roja de un lanzamiento de versión de software?

Es posible que cambiar el código sea razonablemente sencillo y posiblemente más fácil que cambiar datos en tablas. Mover la lógica que cambia con frecuencia del código a los datos solo es útil si, por alguna razón, es menos esfuerzo modificar los datos en lugar del código. Eso podría ser cierto si los cambios se expresan mejor en una forma de datos (por ejemplo, parámetros numéricos almacenados en EEPROM). O podría ser cierto si las solicitudes del cliente hacen que sea necesario lanzar una nueva versión de software, y una nueva versión de software es un procedimiento costoso de construir (una gran cantidad de documentos, o tal vez chips OTP quemados por el fabricante del chip).

La modularidad es un principio muy bueno para este tipo de cosas. Parece que ya lo estás haciendo hasta cierto punto. Es bueno intentar aislar el código que cambia a menudo en un área lo más pequeña posible, y tratar de mantener el resto del código (funciones de "ayuda") por separado (modular) y lo más estable posible.

1

Para adaptarme a los requisitos cambiantes, me concentraría en hacer el código modular y fácil de cambiar, p. usando macros o funciones en línea para parámetros que probablemente cambien.

W.r.t. una configuración que se puede cambiar independientemente del código, espero que los parámetros que son reconfigurables también se especifiquen en los requisitos. Especialmente para cosas críticas de seguridad como los controladores de bolsas de aire.

2

No hago que el código sea inmune a los cambios de requisitos per se, pero siempre etiqueto una sección de código que implementa un requisito al poner una cadena única en un comentario. Con las etiquetas de requisitos en su lugar, puedo buscar fácilmente ese código cuando el requisito necesita un cambio. Esta práctica también satisface un proceso CMMI.

Por ejemplo, en el documento de requisitos:

La siguiente es una lista de requisitos relacionados con el RST:

  • [RST001] Juliet iniciará el RST con 5 minutos de retraso cuando el encendido está APAGADO.

Y en el código:

/* Delay for RST when ignition is turned off [RST001] */ 
#define IGN_OFF_RST_DELAY 5 

...snip... 

         /* Start RST with designated delay [RST001] */ 
         if (IS_ROMEO_ON()) 
         { 
          rst_set_timer(IGN_OFF_RST_DELAY); 
         } 
Cuestiones relacionadas