En este tutorial vamos a aprender un truco de programación orientado a máquinas de eventos discretos en la que vamos a desarrollar una matriz de funciones para desarrollo en robótica.
Los eventos discretos son aquellos eventos que se caracterizan por estar determinados como uno dentro de un intervalo del conjunto continuo.
Para explicarlo mejor. Es como las agujas de un reloj. Avanza en intervalos definiendo la hora, otra para el minuto y otra para el segundo. Y cada una nos define el paso del tiempo en eventos discretos de 24 horas (12 horas repetidas 2 veces), 60 minutos y 60 segundos.
Cada movimiento de una aguja, es un evento discreto para movernos en el tiempo y por sí solas no nos dan una información completa, pero las 3 en su conjunto son suficientes para no llegar tarde a nuestra cita.
En el caso continuo, lo más cerca que disponemos para medir el tiempo con tanta exactitud sería un reloj atómico pero en fin, para más información aquí.
Hay algunos momentos en los que no sabemos como integrar distintas funcionalidades a desarrollos que no sabemos muy bien qué es lo que tienen que hacer en función de un evento.
Para ello, vamos a crear un ejemplo de programa que lo que hará es introducir una función como parámetro de entrada de otra función.
Puede sonar extraño, pero se puede hacer. Al igual que podemos introducir variables de entrada a una función; ¿por qué no funciones de entrada?
void F_input( void *f() ){ f(); } void functionHello(){ Serial.println("Hello"); } void functionWorld(){ Serial.println("World"); } void setup() { Serial.begin(9600); F_input( functionHello ); F_input( functionWorld ); }
Para realizar este concepto, lo que hay que entender es que no manejamos las funciones como parámetros de entrada, sino los punteros de memoria a donde accede la función ejecutora y desempeñarla.
Quizás esto pueda parecer al principio una tontería, pero muchas veces es aconsejable buscar alternativas de ejecución en función de nuestros objetivos.
Por ejemplo, si tengo un robot con distintos sensores; podemos definir una función concreta cuando ese sensor se active. Pero quizás otra persona quiere asociarle otra función distinta que aún está por definir…
Así que dejamos ese espacio de puntero con una función vacía para que esa persona ponga la referencia al puntero que ha de ejecutar en el momento de escoger un evento.
ARRAY DE FUNCIONES
Imaginemos ahora que disponemos de un sensor que mide seis estados distintos. Y cada estado realiza una función distinta y que no queremos especificar aún, pero alguién en un futuro lo hará.
Evidentemente nuestro objetivo no es rellenar un complejo conjunto de funciones, sino que las asociaremos a un vector que será capaz de determinar con un número definido por el sensor, cuál de nuestro conjunto deberá de ejecutar.
#define MAXFUNCS 6 #define sensorPIN 10 void f11(){ Serial.println("F11"); } void f12(){ Serial.println("F12"); } void f13(){ Serial.println("F13"); } void f21(){ Serial.println("F21"); } void f22(){ Serial.println("F22"); } void f23(){ Serial.println("F23"); } typedef void (*matrixFunction) (); matrixFunction mF[MAXFUNCS]={ f11, f12, f13, f21, f22, f23 }; void setup() { Serial.begin(9600); mF[0](); mF[1](); mF[2](); mF[3](); mF[4](); mF[5](); } void loop() { var n_func = analogRead(sensorPIN); mF[n_func](); }
Con este modelo, podemos crear más dinamismo a nuestros desarrollos, ya que podemos ordenar todas las funciones con un acceso numérico que nos lleve a esta referencia de la función.
MATRIZ DE FUNCIONES
Pero no acaba aquí la cosa. Si en algún momento se tuercen las cosas y queremos asociar una función con dos sensores, deberíamos pensar en el conjunto de alternativas que nos dan estos dos sensores.
Si por ejemplo un primer sensor nos da 6 estados discretos y un segundo sensor nos da 5 estados discretos … Entonces deberíamos crear 30 funciones distintas para cada una de las combinaciones que podamos establecer…
Esto puede ser un poco arduo y si además queremos que otra persona pueda redefinir estas funciones para personalizarlas…
typedef void (*matrixFunction) (); #define MAXFROWS 3 #define MAXFCOLS 3 #define sensorPIN1 A0 #define sensorPIN2 A1 void f11(){ Serial.println("F11"); } void f12(){ Serial.println("F12"); } void f13(){ Serial.println("F13"); } void f21(){ Serial.println("F21"); } void f22(){ Serial.println("F22"); } void f23(){ Serial.println("F23"); } void f31(){ Serial.println("F31"); } void f32(){ Serial.println("F32"); } void f33(){ Serial.println("F33"); } matrixFunction mF[MAXFROWS][MAXFCOLS]={ {f11,f12,f13}, {f21,f22,f23}, {f31,f32,f33} }; void setup() { Serial.begin(9600); mF[0][0](); mF[0][1](); mF[1][0](); mF[1][1](); mF[1][2](); mF[2][0](); mF[2][1](); mF[2][2](); } void loop() { int sensor1 = analogRead(sensorPIN1); int sensor2 = analogRead(sensorPIN2); mF[sensor1][sensor2](); }
Como solución y adelanto, este es un estudio que estoy aplicando sobre un robot con dos sensores de color.
Estos sensores de color asocian un número a una tabla de colores definida. Los colores son un buen ejemplo para hablar sobre eventos discretos, ya que el color para un robot en el espectro continuo es complicarse la vida.
El problema que se antojaba es el determinismo de ocurrencias de eventos si el robot se mueve para pasar de un color a otro.
No es lo mismo realizar una acción si pasa del rojo al azul que del azul al rojo y además en un momento dado quiero hacer que estas funciones sean personalizables en la medida de lo posible para crear un modelo de juego.
Así que esta es una buena manera de crear todo un conjunto de eventualidades aprovechando estos conceptos avanzados en programación.