Lo cierto es que el toolkit que se instala con Arduino viene ya con una completa toolchain GNU de C y C++: El propio lenguaje Processing es una especie de "subconjunto" de C y lo que hace el IDE del Arduino no es otra cosa que traducir a C los sketchs que hacemos en lenguaje Processing.
Para hacer nuestro primer programita en C++ para el Arduino vamos a probar hacer una versión OOP del famoso parpadeo (
blink.cc
):#include <avr/io.h> #include <util/delay.h> using namespace std; class Led { public: Led(); void on(); void off(); }; Led::Led() { // bit 7 el puerto C en modo salida DDRC |= 0x80; } void Led::on() { // bit 7 del puerto C a 1 PORTC |= 0x80; } void Led::off() { // bit 7 del puerto C a 0 PORTC &= 0x7F; } int main() { Led led; while (1) { led.on(); _delay_ms(250); led.off(); _delay_ms(250); } }
Hacer parpadear un led en C++ con OOP no deja de ser como matar moscas a cañonazos pero bueno, se trata de una prueba de concepto :-)
Para compilar el programita utilizamos la toolchain de GNU que ya viene con el software del Arduino:
PREFIJO_RUTA_BIN/avr-g++ -DF_CPU=16000000UL -mmcu=atmega32u4 -Os -c -o blink.o blink.cc PREFIJO_RUTA_BIN/avr-g++ -DF_CPU=16000000UL -mmcu=atmega32u4 -Os -o blink.elf blink.o PREFIJO_RUTA_BIN/avr-objcopy -O ihex blink.elf blink.hex
Utilizo la opción
-mmcu=atmega32u4
porque en mi caso concreto estoy utilizando un Arduino Leonardo, que posee un procesador ATmega32u4.Ahora sólo falta subir el fichero
hex
al Arduino mediante la utilidad avrdude
incluida en la toolchain de GNU. El Arduino cuando arranca permanece unos pocos segundos en modo "boot" a la espera de ver si desde un ordenador conectado por USB se le envía algún fichero hex para instalar y ejecutar. En caso de que no se le envíe nada en este modo, el procesador del Arduino pasa a ejecutar el código que contenga ahora mismo la flash interna. Si estando en modo "boot" le llega algún nuevo programa, lo escribe en la flash interna y a continuación lo ejecuta.Para poder cargar un fichero hex en el Arduino bastará con lanzar el siguiente comando inmediatamente después de hacer un reset:
PREFIJO_RUTA_BIN/avrdude -patmega32u4 -cavr109 -P/dev/ttyUSBXXX -b57600 -D -Uflash:w:blink.hex:i -C PREFIJO_RUTA_ETC/avrdude.conf
Algunos de los parámetros que le paso al
avrdude
son específicos para Arduino Leonardo, en caso de usar otro modelo de Arduino hay que mirar qué parámetros son los adecuados. Nótese que el ejecutable avrdude
no se encuentra en la misma carpeta que el fichero avrdude.conf
. Hay que asegurarse de que se usan las rutas correctas y acordes a las carpetas de instalación del software del Arduino.Si en el Arduino se encuentra previamente cargado un sketch realizado con lenguaje Processing es posible cargar un nuevo fichero hex sin necesidad de reiniciar el Arduino: Basta con abrir el puerto serie USB y configurarlo a 1200 bps (sin necesidad de enviar ni recibir ningún byte) para que el Arduino pase a modo "boot", como si lo hubiésemos reiniciado (gracias a Nicholas Kell por esta info).
He aquí un programa de ejemplo, compilable en cualquier *nix, que realiza este "reseteo soft":
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <termios.h> #include <string.h> int main(int argc, char *argv[]) { struct termios tio; int fd; if (argc < 2) return 1; fd = open(argv[1], O_RDONLY | O_NONBLOCK); tcgetattr(fd, &tio); tio.c_cflag = CS8; cfsetspeed(&tio, B1200); tcsetattr(fd, TCSANOW, &tio); close(fd); return 0; }
De esta manera y, de forma genérica, el script
upload.sh
podríamos dejarlo como sigue:#!/bin/sh ./reset $2 sleep 2 PREFIJO_RUTA_BIN/avrdude -patmega32u4 -cavr109 -P$2 -b57600 -D -Uflash:w:$1:i -C PREFIJO_RUTA_ETC/avrdude.conf
Invocándolo de la siguiente manera:
./upload.sh blink.hex /dev/ttyUSBXXX
Y voilà, ya tenemos nuestro primer led parpadeante hecho en C++ :-)
[ añadir comentario ] ( 2534 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1870 )
En 1961 el Lee C.Y. de los Bell Telephone Labs desarrolló un algoritmo muy sencillo y eficaz para el trazado de rutas sobre mallas de puntos. Este algoritmo es muy usado en la actualidad para el enrutado automático de las pistas de cobre en las placas de circuitos impresos.
El algoritmo, que parte de dos puntos que se desean conectar, se divide en dos pasos:
Paso 1. Se genera un frente de onda partiendo del punto origen de la ruta hasta tocar al punto destino. Este frente de onda se va extendiendo por la malla mediante un recorrido en amplitud marcando los puntos por los que va pasando y asignándoles valores cada vez mayores a medida que el frente de onda se aleja del punto origen.
A continuación puede verse cómo se propagaría el frente de onda desde el punto $o=(5, 5)$ y llega hasta el punto $d=(9, 9)$ sobre una malla de 11x11.
0 10 9 8 7 6 7 8 9 10 0
10 9 8 7 6 5 6 7 8 9 10
9 8 7 6 5 4 5 6 7 8 9
8 7 6 5 4 3 4 5 6 7 8
7 6 5 4 3 2 3 4 5 6 7
6 5 4 3 2 o1 2 3 4 5 6
7 6 5 4 3 2 3 4 5 6 7
8 7 6 5 4 3 4 5 6 7 8
9 8 7 6 5 4 5 6 7 8 9
0 9 8 7 6 5 6 7 8 d9 10
0 0 9 8 7 6 7 8 9 10 0
Paso 2. Se genera la ruta partiendo del punto destino y volviendo hacia atrás sobre las marcas generadas por el frente de onda de tal forma que cada punto sólo se conecta con otro que tenga una marca con un valor menor. Así hasta llegar al punto origen.
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 1 1 0 0 0
0 0 0 0 0 0 0 1 1 0 0
0 0 0 0 0 0 0 0 1 1 0
0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0
En pseudocódigo sería algo así:
Entrada:
M : Malla de puntos en la que cada punto tiene una marca (número) y un trazo
origen : Punto origen de la malla
destino: Punto destino de la malla
Salida:
La misma malla M con los puntos trazados uniendo origen con destino
Algoritmo:
cola := {}
meter(cola, origen)
poner todas las marcas de la malla a 0
poner_marca(M, origen, 1)
hacer
p := sacar(cola)
para cada punto q de M vecino de p, no marcado, ni trazado hacer
meter(cola, q)
poner_marca(M, q, obtener_marca(M, p) + 1)
fin para
fin hacer mientras ((cola no vacía) y (p != destino))
si (p == destino)
p := destino
poner_trazo(M, p)
mientras (p != origen) hacer
aux := punto de la vecindad de p con marca inferior a p y más cercano a origen
poner_trazo(M, aux)
p := aux
fin mientras
en otro caso
error: no hay ruta desde origen a destino
fin si
En la sección soft de la web puede descargarse una implementación en C++ que he hecho de este algoritmo. Como se puede apreciar la mayor carga de complejidad computacional recae en la primera parte, encargada de la propagación del frente de onda ya que se trata de un recorrido en amplitud.
El código de ejemplo en C++ genera una malla de 11 por 11 puntos e intenta trazar sobre ella 6 rutas. Las 5 primeras rutas se trazan mientras que para el último par de puntos el algoritmo no encuentra una ruta válida.
tracing from (5, 5) to (9, 9)... ok
tracing from (5, 10) to (9, 3)... ok
tracing from (0, 10) to (10, 6)... ok
tracing from (1, 1) to (1, 1)... ok
tracing from (0, 1) to (2, 1)... ok
tracing from (6, 8) to (3, 2)... no route found
output grid:
5 5 5 0 0 0 0 0 0 0 0
5 4 5 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 3 3 3
0 0 0 0 0 0 0 3 3 2 3
0 0 0 0 3 3 3 3 0 2 3
0 0 0 0 3 1 1 0 0 2 3
0 0 0 0 3 0 1 1 0 2 3
0 0 0 3 3 0 0 1 1 2 2
0 0 3 3 0 0 0 0 1 1 2
0 3 3 0 0 0 0 0 0 1 2
3 3 0 0 0 2 2 2 2 2 2
Como se puede ver es un algoritmo muy útil para el trazado automático de circuitos impresos.
[ 2 comentarios ] ( 1786 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1902 )
El modelado de masas interconectadas con muelles es muy utilizado para la simulación de sistemas físicos y de otras estructuras dinámicas. Plantearemos las ecuaciones diferenciales básicas y a continuación realizaremos una implementación numérica mediante el método de Euler.
La teoría.
La fuerza que actúa sobre un punto $\vec{p}_i$ se puede definir, según la primera ley de Newton, como:
$$\vec{F}_i = m_i \vec{a}_i$$
Si despejamos la aceleración nos queda:
$$\vec{a}_i = {1 \over m_i} \vec{F}_i$$
Como la aceleración es la derivada de la velocidad con respecto al tiempo:
$${d\vec{v}_i \over dt} = {1 \over m_i} \vec{F}_i$$
Teniendo en cuenta que la fuerza total aplicada sobre el punto $\vec{p}_i$ es la suma de cada una de las fuerzas aplicadas en ese punto:
$${d\vec{v}_i \over dt} = {1 \over m_i} \sum\limits_j \vec{F}_{ij}$$
Siendo $\vec{F}_{ij}$ la fuerza aplicada en el punto $\vec{p}_i$ por parte del muelle que conecta dicho punto al punto $\vec{p}_j$. Desarrollando la ecuación anterior utilizando la ley de Hooke para el cálculo de $\vec{F}_{ij}$, obtenemos el siguiente sistema de ecuaciones diferenciales:
$${d\vec{v}_i \over dt} = {1 \over m_i} \sum\limits_j (k_{mij} \vec{e}_{ij} - k_{aij}(\vec{v}_i - \vec{v}_j))$$
$${d\vec{p}_i \over dt} = \vec{v}_i$$
Siendo:
$\vec{p}_i$ = El vector posición del punto $i$.
$\vec{v}_i$ = El vector velocidad del punto $i$ (derivada de $\vec{p}_i$ con respecto al tiempo).
$\vec{v}_j$ = El vector velocidad del punto $j$ (derivada de $\vec{p}_j$ con respecto al tiempo).
$\vec{e}_{ij}$ = El vector elongación del punto $i$ con respecto al punto $j$.
$m_i$ = La masa del punto $i$.
$k_{mij}$ = La constante del muelle que une los puntos $i$ y $j$.
$k_{aij}$ = La constante del amortiguador que une los puntos $i$ y $j$.
En esta ecuación, $k_{mij} \vec{e}_{ij}$ es la fuerza del muelle puro y $k_{aij}(\vec{v}_i - \vec{v}_j)$ es la fuerza del amortiguador.
El vector de elongación $\vec{e}_{ij}$ se calcula de la siguiente manera:
Siendo $l_{rij}$ la longitud, en reposo, del muelle que une los puntos $\vec{p}_i$ y $\vec{p}_j$.
Si $Distancia(\vec{p}_i, \vec{p}_j) < l_{rij}$, entonces $\vec{e}_{ij}$ es el vector unitario que va de $\vec{p}_j$ a $\vec{p}_i$.
Si $Distancia(\vec{p}_i, \vec{p}_j) > l_{rij}$, entonces $\vec{e}_{ij}$ es el vector unitario que va de $\vec{p}_i$ a $\vec{p}_j$.
Si $Distancia(\vec{p}_i, \vec{p}_j) = l_{rij}$, entonces $\vec{e}_{ij} = (0, 0)$.
El sumatorio debe recorrer todos los $j$ que representen puntos unidos al punto $i$ mediante un muelle.
Para implementar las ecuaciones diferenciales se puede aplicar el método de Euler (tiene un buen comportamiento para intervalos de $t$ pequeños y constantes y es múy fácil de programar aunque el error es un poco alto). Lo que se suele recomendar en foros de programación es el método de Runge-Kutta de cuarto orden: no es complicado de implementar y tiene un error razonablemente bajo.
Implementación mediante el método de Euler.
Para una ecuación diferencial de la siguiente forma:
$${dy \over dt} = f(t, y)$$
Podemos aproximar de forma numérica la integral resultante:
$$y=\int f(t, y)dt$$
mediante la siguiente ecuación de recurrencia:
$$y_{n+1} = y_n + hf(t_n, y_n)$$
Siendo $h$ el ancho del intervalo de integración en el tiempo (cuando más chico mejor). Si integramos el sistema de ecuaciones diferenciales del sistema de puntos unidos por muelles:
$$\vec{v}_i = \int {1 \over m_i} \sum\limits_j (k_{mij} \vec{e}_{ij} - k_{aij}(\vec{v}_i - \vec{v}_j))dt$$
$$\vec{p}_i = \int \vec{v}_i dt$$
podemos aplicar el método de Euler de forma directa:
$$\vec{v}_i[n+1] = \vec{v}_i[n] + h{1 \over m_{i}} \sum\limits_j (k_{mij}\vec{e}_{ij}[n] - k_{aij}(\vec{v}_i[n] - \vec{v}_j[n]))$$
$$\vec{p}_i[n+1] = \vec{p}_i[n] + h \vec{v}_i[n]$$
$h$ es el ancho en segundos de cada intervalo de simulación. Esta $h$ se puede calcular de la siguiente manera:
$$h = {1 \over fps}$$
Siendo $fps$ la tasa de refresco en frames por segundo.
En esta web he implementado en javascript y HTML5 un pequeño sistema de cuatro masas unidas por muelles. Haciendo click con el ratón se pueden mover las masas e interactuar con el sistema.
[ añadir comentario ] ( 1567 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1850 )
La empresa japonesa de decoración Gurgle Co., Ltd. ha usado un tema mío como música de fondo en un vídeo promocional :-).
[ añadir comentario ] ( 1313 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1918 )
El uso de la memoria compartida en sistemas operativos compatibles SystemV y BSD (como Linux, FreeBSD, OSX, etc.) siempre ha estado tradicionalmente asociado al uso del lenguaje C. Sin embargo, si estamos en C++, podemos utilizar la memoria compartida sin renunciar al paradigma de la orientación a objetos: Creando objetos en dicha memoria compartida. Estos “objetos compartidos” serán accesibles, al estar alojados en memoria compartida, desde todos los procesos que tengan acceso a ella.
El truco consiste simplemente en redefinir el operador “new” para la clase de los objetos que queremos compartir:
extern "C++" { using namespace std; namespace avelino { class TShared { private: int a; public: TShared(); void setA(int v); int getA(); static void *memArea; void *operator new (unsigned int size); }; } } ... void *TShared::operator new (unsigned int size) { return TShared::memArea; }
Como se puede observar, hacemos que
new TShared()
devuelva un puntero a un área de memoria controlada por nosotros (no estoy implementando el delete
ni estoy realizando una gestión de memoria como tal: obsérvese que dos new TShared()
seguidos devolverían la misma posición de memoria, estoy haciéndolo así para facilitar la comprensión).Bueno, ya tenemos una clase que, al hacerle
new
nos va a devolver un puntero a una dirección de memoria controlada por nosotros. Ahora sólo nos queda inicializar dicho puntero adecuadamente.Debe haber un proceso que cree el objeto en memoria compartida:
int shmid = shmget(SHM_KEY, sizeof(TShared), IPC_CREAT | IPC_EXCL | 0700); TShared::memArea = shmat(shmid, NULL, 0); TShared *p = new TShared(); p->setA(12345);
Como se puede ver, antes de hacer el
new TShared()
inicializo TShared::memArea
con la zona de memoria compartida que acabo de crear (he omitido, por claridad, los if
que controlan los posibles errores que puedan devolver la funciones shmget
, shmat
, etc.). Ahora nuestro objeto de tipo TShared
está en la memoria compartida identificada por SHM_KEY
(una constante cualquiera definida por nosotros y mayor que cero).Si otro proceso quiere acceder a dicho “objeto compartido” sólo tendrá que acceder a dicha memoria compartida y hacer el cast correspondiente:
int shmid = shmget(SHM_KEY, sizeof(TShared), 0700); TShared::memArea = shmat(shmid, NULL, 0); TShared *p = (TShared *) TShared::memArea; cout << "a = " << p->getA() << endl;
El código fuente completo lo he puesto en la sección soft de la web.
[ 2 comentarios ] ( 7473 visualizaciones ) | [ 0 trackbacks ] | enlace permanente | ( 3 / 1955 )