El desarrollador de software Michael van Lammeren informa sobre algunas técnicas innovadoras que su equipo de ingeniería usó en un proyecto reciente. El enfoque de la empresa para desarrollar un código simple y robusto les permitió cumplir con los exigentes requisitos de diseño y evitar costosos retrasos en la programación.
El desarrollador de software Michael van Lammeren informa sobre algunas técnicas innovadoras que su equipo de ingeniería usó en un proyecto reciente. El enfoque de la empresa para desarrollar un código simple y robusto les permitió cumplir con los exigentes requisitos de diseño y evitar costosos retrasos en la programación.
Los desarrolladores de software de Nuvation no tienen idea de los desafíos que planteará su próximo proyecto de servicios de diseño electrónico. Los osciloscopios y los kits de desarrollo son herramientas estándar de la industria, pero en un proyecto reciente descubrimos algunas herramientas antiguas y creamos algunas nuevas e interesantes.
Al comienzo del proyecto, a nuestro equipo se le presentaron los siguientes requisitos para los nuevos dispositivos integrados.
• Medir, calcular y almacenar datos de aproximadamente 250 campos
• Responder consultas de red: Web, Telnet, SNMP, Modbus
• Uso de Freescale K60 de 150 MHz con 512 MB de Flash y 128 KB de RAM
Somos muy conscientes de la necesidad de actualizar continuamente nuestra lista de 250 artículos. Sabíamos que, durante el desarrollo del software, casi con certeza necesitaríamos agregar, editar o eliminar campos. Necesitábamos un enfoque que pudiera responder con flexibilidad a los requisitos cambiantes. Tuve que empezar a hacer herramientas.
Comencé traduciendo el requisito de 250 campos del archivo de Excel a la estructura de datos del script. Verifiqué ese script en el control de versiones (SVN en este caso) para poder administrar todos los cambios. A continuación, agregué código al script que usa la estructura de datos para generar código C. Esto incluye definiciones de estructura, funciones de acceso y archivos de encabezado. Con una secuencia de comandos que traduce sus requisitos en código, es muy fácil cambiar un campo de entero a flotante, por ejemplo. Simplemente cambie la estructura de datos en un lugar, vuelva a ejecutar el script y verifique los muchos archivos modificados. ¡Gracias computadora!
Entonces decidí generar la documentación. Agregamos la generación de comentarios al estilo de Doxygen a nuestros scripts y usamos Doxygen para generar una mejor documentación para nuestro equipo de desarrollo. Nuestro entorno de desarrollo recogió esos comentarios y ayudó con información sobre herramientas y finalización de código. También creamos entregas de documentación hacia el final del proyecto. Hay un archivo MIB de SNMP que describe esa interfaz de red y un mapa de registro de Modbus conceptualmente similar. Cada vez que volví a ejecutar el script se generó todo.
Entonces realmente empezamos a volvernos locos. Se agregaron más y más campos a las estructuras de datos del script, incluidas las devoluciones de llamadas a funciones de validación para campos que contienen direcciones IP. Se agregó una devolución de llamada a la función de configuración que se activa cada vez que cambia un campo específico. Un enfoque similar manejó la seguridad para campos y usuarios específicos. Algunos campos se almacenan en la RAM, algunos se almacenan en la memoria no volátil y algunos no se almacenan en absoluto y en realidad se calculan en tiempo de ejecución. Todo estaba cubierto de guión.
Al final del proyecto, el script de generación de código tenía 2000 líneas y generaba directamente 20 000 líneas de código en 23 archivos. Indirectamente, el código también alimenta herramientas llamadas Doxygen y Gperf (más sobre esto más adelante) para generar aún más código. Lo que solía ser una pesadilla de mantenimiento ahora es más flexible y potente de lo que se imaginó originalmente. El equipo era libre de abordar problemas difíciles y podía darse el lujo de decir ‘no hay problema’. a las solicitudes de cambio de los clientes. Finalmente, poder ver una representación de alto nivel del código fuente me ayudó a encontrar errores lógicos al principio del desarrollo de software.
Después de resolver el problema de ahorro de datos, pasé al siguiente desafío. Esos datos debían recuperarse a través de múltiples interfaces de red, como Web, Telnet, SNMP y Modbus.
Los datos residían en una gran estructura C con 250 miembros. Cada miembro de la estructura contenía datos para un campo. Por conveniencia de programación, había funciones de acceso para leer y escribir datos. Cada función de acceso (generada automáticamente) hacía referencia a un miembro de la estructura, como en el siguiente ejemplo.
Esto ha funcionado bien para aplicaciones web que construyen páginas con referencias codificadas a funciones de acceso. Sin embargo, también necesitábamos admitir Telnet, SNMP y Modbus. Las consultas contra estas interfaces se originaron en los usuarios de la red y podían leer o escribir cualquier combinación de campos de datos.
Para complicar aún más las cosas, estas tres interfaces de red tienen claves muy diferentes para identificar campos de datos. La interfaz Telnet utiliza claves de texto, SNMP utiliza identificadores de objetos (OID) y Modbus divide el campo de dirección IP de 4 bytes en dos palabras de 2 bytes identificadas por números enteros. La siguiente tabla muestra las distintas teclas.
Las claves Modbus llamadas “registros” son números enteros secuenciales, por lo que todo lo que tiene que hacer es crear una matriz para asignar los registros a las funciones. También utilicé funciones auxiliares para convertir varios tipos de datos en las palabras de 2 bytes requeridas por Modbus. El primer parámetro de la función auxiliar es la función accesoria y el segundo parámetro es el desplazamiento. Esta matriz asigna el registro 88 a get_modbus_register(get_WIFI_IP_ADDRESS(), 0) y el registro 89 a get_modbus_register(get_WIFI_IP_ADDRESS(), 1). Por supuesto, esa matriz era otra tarea en el script de generación de código.
El resto son Telnet y SNMP. Obviamente, se necesitaban dos índices para devolver los datos para estas consultas de red. Los índices suelen estar integrados en la RAM, pero los dispositivos integrados tienen solo 128 kilobytes de RAM. Los índices también requieren ciclos de CPU para encontrar los campos de datos solicitados. Desafortunadamente, nuestro procesador de 150 MHz no tenía muchos ciclos de sobra. La implementación típica no funcionó para esta aplicación. Quería una solución que usara poca o nada de RAM y usara la menor cantidad de ciclos de CPU posible.
Una característica atractiva del Freescale K60, el MCU de este proyecto, es que puede ejecutarse desde una memoria flash. Pensé que si podía mover la generación del índice del tiempo de ejecución al tiempo de compilación, podría almacenar el índice en flash y ahorrar RAM para otras tareas. Esto fue factible porque todos los campos de datos se conocían en el momento de la compilación. También quería minimizar el uso de la CPU moviendo las búsquedas de índice para compilar el tiempo también. ¿cómo hiciste eso? Encontramos una herramienta llamada Gperf que genera una función hash perfecta mínima y la hicimos funcionar.
Una función hash toma una entrada (generalmente una cadena) y calcula un valor (generalmente un número) basado en esa entrada. Aquí hay un ejemplo que ilustra el concepto de funciones hash. Imagine una función que reemplaza las letras del alfabeto con números del 1 al 26, suma los números y devuelve el último dígito de la suma. Cada palabra que se pasa a una función de este tipo devuelve un número entre 0 y 9. Por el contrario, usemos la función de ejemplo para comparar manzanas y naranjas.
¿Cómo implementaría una búsqueda de índice utilizando la función hash en su ejemplo? Puede asignar claves. Tienes que hacer algo de trabajo en tiempo de ejecución para encontrar lo que estás buscando. Pero como sabemos qué contenedores buscar, solo tenemos que hacer el 10% del trabajo. Esto es definitivamente una mejora, pero hay maneras de hacerlo aún mejor.
Una función hash perfecta le dará un número de contenedor único para cada palabra. Aún mejor es la función hash perfecta mínima “más pequeña”, lo que significa que el número de contenedor más grande está cerca del tamaño del índice. Puede usar una función como esta para encontrar los datos de “manzana” en el contenedor 1, los datos de “naranja” en el contenedor 2, etc. sin necesidad de un millón de contenedores.
Esto es lo que Gperf hizo por nosotros. Escribí un código para asignar la “DIRECCIÓN_IP_WIFI” del servidor telnet a get_DIRECCIÓN_IP_WIFI() dado un conjunto de datos que asocia claves con punteros de función. La segunda llamada a Gperf crea un código que asigna “1.3.6.1.4.1.123.2.1.2.1” a la misma función de acceso en el servidor SNMP.
Gperf crea una función hash perfecta mínima iterando sobre dos números y generando un hash de todas las claves dadas. Realice un seguimiento de todos los pares ajustados que den como resultado valores hash perfectos y, a partir de ese subconjunto, elija el par que minimice el tamaño de la tabla hash. Este tipo de enfoque de fuerza bruta requiere bastante tiempo, pero solo debe realizarse una vez y nunca en dispositivos integrados.
Como beneficio adicional para este proyecto, el hecho de que Gperf implemente tablas hash en código significa que en K60 estas tablas viven en flash y no requieren RAM. Esto le dio al dispositivo el máximo de ciclos de RAM y CPU para mediciones y cálculos. Esto es importante. Porque necesitaba alrededor del 75% de la CPU para mantenerlo en tiempo real, y todas esas interfaces de red necesitaban una buena cantidad de RAM.
Cuando algo funciona tanto teórica como prácticamente, siempre es satisfactorio. Las herramientas de generación de código que desarrollamos y usamos en este proyecto nos salvaron de los retrasos en la resolución de problemas asociados con el mantenimiento de una gran base de código. ¿Qué clientes podrían objetarlo?
Mike van Lammeren es desarrollador de software sénior en Nuvation, una empresa de servicios de diseño electrónico que se especializa en el desarrollo de nuevos productos. www.nuvation.com ■