Emulador de un Casio F91W basado en FreeRTOS (2 de 2)

mayo 15, 2009 en 3:46 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

Aunque el gadget pretende ser un emulador fiel al reloj Casio, este se encuentra en modo “desarrollo” y se ha añadido una pequeña funcionalidad de ayuda a la depuración.
Como el Casio original, este gadget tiene 6 teclas distintas A,B,C cortos y A,B,C largos. Si estando en un menú, introducimos una tecla que no hace nada, se muestra una pantalla de información con todas las funciones asociadas a cada una de las teclas.

Las funciones de hora, alarma y cronómetro basan su precisión en el periférico Real Time Clock Calendar (RTCC) que incorpora el microcontrolador. El cronómetro tiene opciones de Start, Stop y Split, pero estas solo tienen precisión de 1 segundo(el Casio tiene una precisión de 0.01 segundos).

Los métodos mostrados a continuación permiten al usuario realizar las operaciones de cronómetro definidas en documento USER’S GUIDE 2428 de Casio:


void Inicio( Watch *this );
void Parada( Watch *this );
void Fraccion( Watch *this );
void Libera( Watch *this );
void Borrado( Watch *this );
void ClearSplit( Watch *this );
void StartStop( Watch *this );
/* 
Función Medición de tiempo transcurrido.
Tecla C C C C A
Acción Inicio Parada Reinicio Parada Borrado
Función  Medición de tiempo fraccionado.
Tecla C A A C A
Acción Inicio Fracción Libera Parada Borrado
Función Tiempos fraccionados y/o tiempos del primero y del segundo en llegar.
Tecla C A C A A
Acción Inicio Fracción Parada Libera Borrado
*/

La tarea TaskGetLeds.

Responsabilidades:
Debe informar al sistema cunado el usuario pulse una tecla, indicando la tecla pulsada.
Estructura de la tarea:
Está dividida en dos funciones, vStartTaskGetLeds y vTaskLeds. La primera crea los recursos usados. La segunda comprueba continuamente las teclas e informa y envía un mensaje.
Funcionamiento:
Lo primero que hace esta tarea es intentar obtener un mutex. Esta tarea comparte los 24 LEDs con TaskPutLeds y por eso ambas deben estar sincronizadas. Cuando obtiene el mutex llama a la función get_leds y ésta devuelve un valor con el siguiente significado:
-1: No hay ninguna tecla pulsada.
0: La tecla A está pulsada.
1: La tecla B está pulsada.
2: La tecla C está pulsada.
Si no hay ninguna tecla pulsada se libera el mutex y vuelve al principio.
Si hay una tecla pulsada se espera hasta que se suelte y se envía en un mensaje uno de estos códigos:
0: La tecla A esta pulsada menos de 1.5 segundos.
1: La tecla B esta pulsada menos de 1.5 segundos.
2: La tecla C esta pulsada menos de 1.5 segundos.
3: La tecla A esta pulsada más de 1.5 segundos.
4: La tecla B esta pulsada más de 1.5 segundos.
5: La tecla C esta pulsada más de 1.5 segundos.

La librería LEDs.c
Incorpora funciones para el control de los LEDs. Permite controlar individualmente el estado de cada LED y su polaridad.

unsigned long long order_LEDS( unsigned long value );
void put_ordered_LEDS( unsigned long long out );
void put_LEDS( unsigned long value );
void set_COLS_DIR( unsigned char COLS );
void set_LEDS_DIR( unsigned long value );
unsigned long get_LEDS( void );

La función get_LEDS necesita que los LEDS puedan ser polarizados en inversa, para esto el PCB incorpora dos mosfet que pueden dar una salida de GND o VCC.
Los LEDS están divididos en 2 grupos, pares e impares. Mientras unos se polarizan como receptores los otros pueden estar como emisores y medir asi la luz reflejada.

PIN SWAP para un ruteado más sencillo en Altium Designer.
Esta es una de las opciones que me han permitido reducir notablemente el tiempo de ruteado y el número de vias de la placa. Se trata de indicar al programa que pines tienen funciones intercambiables y él mismo los intercambia para que el número de cruces sea mínimo.

Aquí se puede ver el antes y el después:

Reordenar antes de mostrar.
Puesto que no hay ningún orden entre los LEDs y los pins que ocupan, es necesario reordenar todos los datos que quieran ser mostrados. La función ordenar debe ser optima ya que es llamada muchas veces durante el funcionamiento del programa. Una primera implementación en C es suficientemente rápida y portable, pero decidí implementar una segunda versión en ensamblador para mejorar la eficiencia.

Solución en C


La tarea TaskGraphics


Generar el tono

Responsabilidades:
Debe dibujar en un buffer todas las primitivas. Cuando recibe el mensaje Qglcd_update envía el buffer a la tarea TaskPulLeds.
Recursos:
Dispone de un queue de entrada llamado xQueuePrimitive para recibir las primitivas que debe dibujar y otro queue llamado xQueueDisp para enviar el buffer con toda la imagen.
Estructura de la tarea:
Está dividida en dos funciones, vStartTaskGraphics y vTaskGraphics. La primera crea los recursos y la segunda dibuja y envía el buffer.
Funcionamiento:
No tiene ningún misterio, cada vez que recibe un mensaje con una primitiva llama a la función correspondiente de la librería Graphics.c

code

La librería Graphics.c
Es la librería que proporciona el compilador CCS para dibujar primitivas 2D básicas como líneas, píxeles, círculos…
Tiene algunas modificaciones:
La función glcd_init acepta un puntero a un array tipo unsigned long, todas las primitivas serán dibujadas en este array.
La función glcd_pixel utiliza una tabla (rotate table) para pintar los píxeles en el buffer.
La función glcd_textArial pinta texto en formato Arial. Las fuentes de este texto han sido generadas con el programa BitFontCreator Pro.

Doble buffer compartido con la tarea TaskPutLeds
La tarea TaskGraphics declara dos buffers del mismo tamaño que la pantalla, para poder representar la información antigua mientras se genera la nueva.
Cuando se recibe el mensaje Qglcd_update, se envía a la tarea TaskPutLeds el buffer pintado y se empieza a dibujar sobre el otro buffer.
Puesto que los buffers son grandes, no se hace copia en el mensaje, solo se envía un puntero. Esto es algo con lo que hay que tener cuidado, y en este caso el tamaño del queue (solo admite 1 mensaje) impide que se produzcan errores.

Paso de punteros en los mensajes
Cuando se pasa un puntero en un mensaje se debe estar seguro que el dato al que apunta “exista” cuando el mensaje es recibido.
En la tarea TaskWatch las funciones Qglcd_text5x7 y Qglcd_Arial pasaban un puntero a la cadena que debía ser dibujada y de vez en cuando se dibujaban caracteres sin sentido… Cuando se trabaja con concurrencia hay que tener en cuenta que todas las tareas se ejecutan en “paralelo” y que mientras una esta leyendo un dato la otra lo puede borrar. Esto se solucionó copiando toda la cadena (hasta 5 caracteres mas el carácter nulo) en el mensaje enviado.

La tarea TaskSpeaker

Responsabilidades:
Debe generar un sonido utilizando el piezo-altavoz cada vez que reciba un mensaje.
Tipo:
Tarea consumidora.
Estructura de la tarea:
Está dividida en dos funciones, vStartTaskSpeaker y vTaskSpeaker. La primera crea los recursos y la segunda recibe el mensaje y generará el sonido.
Funcionamiento:
Esta tarea esta inactiva mientras no se reciba ningún mensaje. Una vez recibido un mensaje lo primero que hace esta tarea es intentar obtener un mutex. Esta tarea comparte un pinIO con la tarea TaskButton y ambas deben estar sincronizadas.
Cuando se obtiene el mutex se configura el pinIO como salida y se genera el sonido indicado en el mensaje.

TASKSPEAKER_C

El mensaje recibido posee 3 datos:
Aunque la función que se muestra arriba funciona, aún esta en desarrollo. Aquí voy a describir su funcionamiento final:
long pattern: Es la secuencia de sonidos que se va a generar.
Por ejemplo SOS podría ser algo como 0b00000010101001110111011100101010.
int pitch: Es el tiempo que dura cada “bit” de la secuencia.
int reps: Es el número de veces que se repite la secuencia.

Tomar el control del sistema
Como el piezo-altavoz fue un periférico añadido al sistema mucho tiempo después del diseño del PCB, este no cuenta con el pin mas adecuado para generar audio ( como PWM). El tono es generado con muchas instrucciones PORTAbits.x = ! PORTAbits.x y eso requiere que el procesador este en la tarea TaskSpeaker durante un tiempo relativamente largo sin poder atender otras tareas.
Como el RTOS esta configurado en modo preemtitive se deben evitar los cambios de contexto al menos durante un “bit” de la secuencia. Esto se consigue dando a la tarea TaskSpeaker la prioridad más alta.
Una opción anterior fue utilizar las funciones taskENTER_CRITICAL y taskEXIT_CRITICAL pero no resultaron muy adecuadas puesto que modificaban indirectamente el comportamiento de la función vTaskDelayUntil.

La tarea TaskPutLeds

Responsabilidades:
Debe imprimir una imagen de 128×24 sincronizada con el movimiento de la mano.
Tipo:
Es una tarea consumidora.
Estructura de la tarea:
Está dividida en dos funciones, vStartTaskSpeaker y vTaskSpeaker. La primera crea los recursos y la segunda recibe la imagen en un mensaje y la imprime cuando se lo indica la tarea TaskAcce.

Funcionamiento:
Puesto que los LEDs son un recurso compartido controlado por mutex, lo primero que hace antes de mostrar nada es intentar obtener el mutex.
Una vez tiene el mutex configura los LEDs y espera, durante 256 ticks, un mensaje de la tarea TaskAcce para mostrar la imagen. Después de mostrar la imagen volverá a esperar el mensaje de la tarea TaskAcc. Cuando pasen mas de 256 ticks y no se reciba un mensaje liberara el mutex.

TASKPUTLEDS_C

Sincronización con el movimiento de la mano
La tarea TaskPutLeds recibe mensajes de la tarea TaskAcce indicando que la mano de el usuario esta a la izquierda o a la derecha del todo, pero esto no es suficiente para mantener estable la imagen en el aire.
Se calcula el instante en que se debe mostrar la imagen ( O[n] ) en base a los dos últimos instantes en que la mano se encontraba a la izq./drcha. del todo (I[n] e I[n-1] ). Conociendo estos dos datos y el “tamaño” de la imagen 128 se puede calcular O[n] de la siguiente manera:
O[n] = I[n] + ( I[n] – I[n-1] )/2 -128/2.
Donde I[n] – I[n-1] puede ser sido sustituido por DIFF(I[n])
Si por alguna razón el instante O[n] pertenece al pasado o a un futuro lejano se asigna un nuevo instante O[n] aproximado.

En esta imagen se muestra el movimiento y la imagen mostrada.

Uso de la CPU
Puesto que esta tarea es larga, y además tiene una prioridad alta, se ha diseñado para liberar a la CPU el máximo tiempo posible, permitiendo así a otras tareas tomar la CPU para realizar sus trabajos. El tiempo de CPU consumido por esta tarea es inferior al 5%.

En esta imagen se muestran los estados de la tarea TaskPutLeds

Impedir que el sistema se bloquee debido a queues llenos.
Un criterio de diseño escogido para la mayoría de las tareas productoras/procesadoras ha sido que si el queue de salida de la tarea esta lleno esta se bloquee.
Si la tarea PutLeds no consume mensajes durante mucho tiempo bloqueara indirectamente a la tarea anterior, y esta a la anterior… Para evitar esto periódicamente consume mensajes aunque no se utilicen.

La tarea WakeUp

Responsabilidades:
Debe despertar a todas las tareas cuando el usuario pulse el botón y volverlas a dormir si en 20 Segundos no se pulsa nada.
Tipo:
Es una tarea de control.
Estructura de la tarea:
Esta dividida en dos funciones, vStartTaskWakeUp y vTaskWakeUp. La primera crea los recursos y la segunda despierta o duerme al resto de tareas.
Funcionamiento:
Esta tarea espera continuamente un mensaje de la tarea TaskButton. Cuando lo recibe despierta a todas las tareas del sistema. Ahora comprueba la tarea TaskGetLeds para resetear un contador y la tarea TaskButton para borrarlo (lo pone a su valor máximo). Cuando el contador llega a su valor máximo suspende todas las tareas del sistema, excepto la tarea TaskButton.
Además, aunque en principio no es necesario, la tarea TaskWakeUp también apaga los LEDs, el mosfet y el acelerómetro.

Codigo de la tarea:

TASKWAKEUP_C

El orden para suspender y resumir tareas
Si se suspende una tarea consumidora antes que una productora se llenaran los queues de mensajes. Estos mensajes serán leídos cuando se vuelva a resumir la tarea consumidora y probablemente ya no tengan utilidad.
El orden que se ha escogido para resumir tareas es:
Consumidoras.
Procesadoras.
Productoras.
El orden para suspenderlas es el contrario. Esto evita que se llenen los queues mientras el sistema entero se suspende.
La tarea TaskWakeUp posee la prioridad más alta del sistema, y eso le permite tomar la CPU siempre que la necesite. Por este motivo todas las tareas serán resumidas al mismo tiempo, pero este orden puede ser útil a la hora de suspenderlas.

Suspender una tarea que posee un mutex
Si suspendemos una tarea que tiene un mutex, nunca lo liberará, y estaremos bloqueando indirectamente a otra tarea que se encuentre esperando ese mutex.
También es importante asegurar que un periférico no esta en uso cuando se bloquea a la tarea que lo controla.
Para evitar todo esto la tarea TaskWakeUp “conoce” los mutex que puede poseer cada tarea. Si una tarea posee un mutex, se espera a que lo suelte antes de suspenderla.

Modo bajo consumo
Cuando todas las tareas están suspendidas la tarea TaskWakeUp cambia el reloj del sistema a 32kHz, esto permite seguir funcionando al RTOS y a la tarea TaskButton pero con un consumo aproximado de 0.1 mA y da un tiempo de funcionamiento teórico de 1 año.
Cuando el usuario pulsa el botón, la tarea TaskWakeUp vuelve a cambiar el reloj del sistema a 8MIPs y resume todas las tareas.

“Interrupción” por RTCC
La tarea TaskWakeUp comprueba (pooling) continuamente el flag de interrupción por RTCC. Cuando la alarma esta activada es capaz de despertar al sistema y generar el sonido de alarma. Esta opción no esta terminada ya que aun no he podido programar la alarma del RTCC, pero de momento no tiene importancia.

La función Main

Responsabilidades:
Debe inicializar todos los periféricos de la CPU, configurar la aplicación y arrancar el RTOS
Variables
xTaskAcceResources xTaskAcce1;
xTaskPutLedsResources xTaskPutLeds1;
xTaskGetLedsResources xTaskGetLeds1;
xTaskButtonResources xTaskButton1;
xTaskSpeakerResources xTaskSpeaker1;
xTaskWakeUpResources xTaskWakeUp1;
xTaskGraphicsResources xTaskGraphics1;
xTaskWatchResources xTaskWatch1;

Funcionamiento:
Primero llama a la función VisualInitialization(). Esta función ha sigo generada totalmente con la herramienta VisualDeviceInitializar que incorpora el MPLAB.
Después llama a todas las funciones del tipo vStartTaskX y va copiando los recursos de unas

tareas a otras.
Finalmente arranca el RTOS llamando a la función vTaskStartScheduler.

Aplicación creada por la tarea main:

/* Standard includes. */
#include
#include
#include
#include
#include
/* Device description includes */
#include "P24FJ64GA004.h"
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* Application includes*/
#include ".\utils\Hardware.h"
#include ".\utils\LEDs.h"
#include ".\utils\GLCD.h"
//#include "Filters.h"
//#include "CORDIC.h"
#include ".\Tasks\TaskAcce.h"
#include ".\Tasks\TaskPutLeds.h"
#include ".\Tasks\TaskGetLeds.h"
#include ".\Tasks\TaskButton.h"
#include ".\Tasks\TaskSpeaker.h"
#include ".\Tasks\TaskWakeUp.h"
#include ".\Tasks\TaskWatch.h"
#include ".\Tasks\TaskGraphics.h"
#include ".\Generated Files\VDIInit\init_PIC24FJ64GA004.sinit_vi.h" 

/*-----------------------------------------------------------*/
// Create Tasks handlers, these must be placed as a GLOBAL varibles.
xTaskAcceResources xTaskAcce1;
xTaskPutLedsResources xTaskPutLeds1;
xTaskGetLedsResources xTaskGetLeds1;
xTaskButtonResources xTaskButton1;
xTaskSpeakerResources xTaskSpeaker1;
xTaskWakeUpResources xTaskWakeUp1;
xTaskGraphicsResources xTaskGraphics1;
xTaskWatchResources xTaskWatch1;
/* Create the demo tasks then start the scheduler.  */
int main( void ){

/* Configure any hardware required for this demo. */
VisualInitialization();
 /* Create the test tasks defined within this file. */

// Productor role tasks
xTaskAcce1.uxPriority = 5;
if( startAcceTask(&xTaskAcce1) ) return -1;
 xTaskGetLeds1.uxPriority = 1;
if( startGetLedsTask(&xTaskGetLeds1) ) return -1;
 xTaskButton1.uxPriority = 1;
if( startTaskButton( &xTaskButton1 ) ) return -1;

// Processor role tasks
xTaskWatch1.xQueueSound = xTaskGetLeds1.xQueueSound;
xTaskWatch1.xQueueKey = xTaskGetLeds1.xQueueKey;
xTaskWatch1.uxPriority = 2;
if( startTaskWatch( &xTaskWatch1 ) ) return -1;
 xTaskGraphics1.xQueuePrimitive = xTaskWatch1.xQueuePrimitive;
xTaskGraphics1.uxPriority = 3;
if( startTaskGraphics( &xTaskGraphics1 ) ) return -1;

// Consumer role tasks
xTaskSpeaker1.xMutexIO = xTaskButton1.xMutexIO;
xTaskSpeaker1.xQueueSound = xTaskWatch1.xQueueSound;
xTaskSpeaker1.uxPriority = 4;
xTaskSpeaker1.uxPriorityHigh = 7;
if( startTaskSpeaker( &xTaskSpeaker1 ) ) return -1;

xTaskPutLeds1.xMutexLeds = xTaskGetLeds1.xMutexLeds;
xTaskPutLeds1.xQueueMove = xTaskAcce1.xQueue;
xTaskPutLeds1.xQueueDisp = xTaskGraphics1.xQueueDisp;
xTaskPutLeds1.uxPriority = 6;
if( startPutLedsTask(&xTaskPutLeds1) ) return -1;

// Control tasks
xTaskWakeUp1.xHandleTasks[7] = xTaskGetLeds1.xHandle;
xTaskWakeUp1.xMutexes[7] = xTaskPutLeds1.xMutexLeds; // Productor Task
// Productor Task
xTaskWakeUp1.xHandleTasks[6] = xTaskAcce1.xHandle;
xTaskWakeUp1.xMutexes[6] = NULL;
xTaskWakeUp1.xHandleTasks[5] = NULL;
xTaskWakeUp1.xMutexes[5] = NULL;
// Processor Task
xTaskWakeUp1.xHandleTasks[4] = xTaskWatch1.xHandle;
xTaskWakeUp1.xMutexes[4] = NULL;
xTaskWakeUp1.xHandleTasks[3] = xTaskGraphics1.xHandle;
xTaskWakeUp1.xMutexes[3] = NULL;
xTaskWakeUp1.xHandleTasks[2] = NULL;
xTaskWakeUp1.xMutexes[2] = NULL;
xTaskWakeUp1.xHandleTasks[1] = xTaskSpeaker1.xHandle;
// Consumer Task
xTaskWakeUp1.xMutexes[1] = xTaskSpeaker1.xMutexIO;
xTaskWakeUp1.xHandleTasks[0] = xTaskPutLeds1.xHandle;
xTaskWakeUp1.xMutexes[0] = xTaskPutLeds1.xMutexLeds;

xTaskWakeUp1.xQueueButtonKey = xTaskButton1.xQueueKey;
xTaskWakeUp1.xQueueGetLedsKey = xTaskGetLeds1.xQueueKey;
xTaskWakeUp1.uxPriority = 8;
if( startTaskWakeUp( &xTaskWakeUp1 ) ) return -1;


/* Finally start the scheduler. */
vTaskStartScheduler();
 return 0;
}
/*
void _ISRauto _RTCCInterrupt(void)
{}
if( pdTRUE == xTaskResumeFromISR( xTaskWakeUp1.xHandle ) )
{
// We should switch context so the ISR returns to a different task.
// NOTE: How this is done depends on the port you are using. Check
// the documentation and examples for your port.
portYIELD_FROM_ISR();
}
}
*/

Crear, configurar e interconectar tareas
La estructura xTaskXXResources engloba los manejadores (punteros) de los recursos utilizados por la tarea TaskXX. Es inicializada al llamar a la función vStartTaskXX( & xTaskXXn ). Esta devuelve un 0 todo ha ido bien y un -1 si ha habido algún error.
Se puede observar como coincide el diagrama de la aplicación con las asignaciones que se van haciendo entre la inicialización de cada tarea.

La estructura xTaskXXResources esta definida en el archivo TaskXX.h de la siguiente manera:

typedef struct xTaskXXResources{
xTaskHandle xHandle; // Task handler.
unsigned portBASE_TYPE uxPriority; // Task priority.
xSemaphoreHandle xMutexXX; // Mutex handler.
xQueueHandle xQueueXX; // Queue handler.
}xTaskXXResources;

La tarea vStartTaskXX tiene la siguiente forma básica, definida en el archivo TaskXX.c:

int vStartTaskXX( xTaskXXResources *pxResources ){
if( pxResources->xMutexXX == NULL ){
if( NULL == ( pxResources->xMutexXX = xSemaphoreCreateMutex() ) )
return -1;
vQueueAddToRegistry( pxResources->xMutexXX, ( signed portCHAR * )"xMutexXXName" );
}

if( pxResources->xQueueXX == NULL ){
if( NULL == ( pxResources->xQueueXX = xQueueCreate( 5, sizeof( xMessageXX ) ) ) )
return -1;
vQueueAddToRegistry( pxResources->xQueueXX, ( signed portCHAR * )"xQueueXXName" );
}

if( pdPASS != xTaskCreate(vTaskXX,(signed portCHAR *)"TaskName",
configMINIMAL_STACK_SIZE,
pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
return -1;

return 0;
}

Conclusiones
Este gadGet ha sido construido con herramientas caseras. El acabado final no es como el de un producto comercial, pero creo que el diseño, tanto software como hardware, si lo son.

El objetivo de este proyecto ha sido iniciarse en los RTOS y lo ha cumplido a la perfección. También se ha desarrollado una técnica de programación orientada a objetos útil para todos los que programamos en C y he improvisado un método práctico para la divulgación y especificación del funcionamiento global y detallado del sistema.

Según lo que he visto, un RTOS no proporciona una ventaja tan grande como lo es el paso de ASM a C, pero creo que se vuelve imprescindible a la hora de crear sistemas ROBUSTOS con más de 2500 líneas de código.
Para este proyecto se han escrito casi 50 archivos, y creo que no hay ninguno con más de 500 líneas. Esto refleja un buen enfoque en la división de los algoritmos según el principio “divide y vencerás”.También se ha seguido otro principio que ha permitido, de manera invisible, una mayor “módularidad” del código. Este ha sido el principio de “la regularidad favorece la simplicidad”.

La herramienta RTOSViewer del MPLAB ha sido de gran ayuda en la depuración del sistema. Ha permitido conocer en cada momento el estado del RTOS y sus recursos. Es casi imprescindible, sobre cuando uno esta empezando con los RTOS…

Una de las ideas que más me han gustado ha sido la de utilizar un conector ICSP compatible con el conector macho del tipo miniUSB.
Este conector es sencillo, muy asequible, ocupa poco espacio en placa (6mm x 6mm) y es atractivo para el usuario final.

Mejoras
Algoritmo de detección y sincronización: Aunque el algoritmo actual es sencillo y funciona bastante bien, es la mejora más útil que se le puede hacer a este proyecto. Creo que también podría añadirse algún tipo de corrección sobre el desplazamiento vertical.
RTOS: La aplicación ha sido implementada con recursos del FreeRTOS. Quizás se pueda simplificar o añadir nuevas funcionalidades con un RTOS mas completo.
DEPURACION DEL RTOS: El RTOS posee una serie de utilidades para seguir desde el exterior su funcionamiento. En proyectos posteriores intentaré sacarles partido.
LEDs: Por supuesto cuantos mas leds, mejor resolución vertical.
LEDs RGB: Seria una opción interesante añadir mas colores a la aplicación. La harían mucho más vistosa, aunque el PIC24 no tiene más pines para controlarlos.
PCB: El PCB esta en general bien, aunque, junto con las pilas es algo grande para utilizarlo de llavero.
PESO: La verdad, mover rápido dos pilas AAA durante un rato se hace incomodo. Todo el peso que se pueda eliminar al gadjet se ganara en comodidad de uso. Creo que se podría utilizar una sola pila y un elevador de voltaje.

Bugs
SCH y PCB
Los componentes MOSFET y ACELEROMETRO no tienen correctamente asignados los pines en el encapsulado. Esto se ha solucionado mediante apaños en la placa ya soldada.
SOFTWARE:
Si que existirán, pero aun no he encontrado ninguno que se merezca aparecer aquí.

Agradecimientos
Agradezco a toda la comunidad de todopic, y especialmente a Nocturno, septiembre_negro, aitopes, MLO__, vtasco, J1M, todopic, barral, RICHI777 y SavageChicken los buenos comentarios sobre el proyecto que sin duda me han motivado mucho para seguir creando gadgets en el futuro.

DESCARGA
Proyecto: ManualLedScanner, versión 2.45. José Antonio García Peiró. 2009.

Descarga del proyecto completo

Algunos documentos de consulta
Sistemas embebidos
Libro: Embedded Systems Firmware
RTOS
Libro: Real Time Concepts for Embedded Systems
Programación orientada a objetos en ANSI C
PDF: Implementing object-oriented designs in ANSI-standard C
PDF: Object-oriented programming with ANSI C

Links interesantes

Enlace a la guía de usuario del reloj de Casio. Aunque no es el F91W, es el modelo mas parecido del que conseguí la guía ya que no en contre justo la del F91W.
http://ftp.casio.co.jp/pub/world_manual/wat/en/qw2428.pdf
Enlace a una pagina que explica como usar un LED como sensor de Luz.
http://en.wikipedia.org/wiki/LED_as_light_sensor
Enlace a la página del RTOS utilizado
http://www.freertos.org/
Maquina de estados finitos:
http://en.wikipedia.org/wiki/Finite_state_machine#Software_applications
Led como sensor:
http://en.wikipedia.org/wiki/LEDs_as_Photodiode_Light_Sensors
BitFontCreator Pro:
http://www.iseasoft.com/bfc.htm
Librerías graficas, incluidas en el compilador CCS:
http://www.ccsinfo.com/
Computación concurrente:
http://es.wikipedia.org/wiki/Concurrente
Preemption:
http://en.wikipedia.org/wiki/Preemptive_multitasking

Software utilizado
MPLAB 8.30
http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1406&dDocName=en019469∂=SW007002
MPLAB PIC24 Compiler 3.12
http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1406&dDocName=en535364
Altium Winter 09
http://www.altium.com/products/altium-designer/en/altium-designer_home.cfm
Proteus 7
http://www.labcenter.co.uk/index.cfm

Anuncios

Emulador de un Casio F91W basado en FreeRTOS (1 de 2)

mayo 15, 2009 en 3:27 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

Emulador de un reloj Casio F91W basado en un PIC24 de Microchip, por <!– var prefix = 'ma' + 'il' + 'to'; var path = 'hr' + 'ef' + '='; var addy10475 = 'jgpeiro' + '@'; addy10475 = addy10475 + 'gmail' + '.' + 'com'; var addy_text10475 = 'José Antonio García Peiró'; document.write( '‘ ); document.write( addy_text10475 ); document.write( ” ); //–>\n José Antonio García Casio F91WPeiró <!– document.write( '‘ ); //–> Esta dirección de correo electrónico está protegida contra los robots de spam, necesita tener Javascript activado para poder verla <!– document.write( '’ ); //–> .
El sistema pretende emular al modelo F91W, imitando las especificaciones y el funcionamiento del reloj de Casio.
El emulador dispone de 3 botones, un display y un altavoz que permiten al usuario interactuar de la misma manera que con el F91W, aunque con una interfaz totalmente distinta.Este gadget no tiene ni pantalla ni botones, para visualizar la hora se utiliza un mecanismo similar al de los relojes “propeller clock” o “giroplay” y para detectar las teclas pulsadas se utilizan LEDs como sensores.

El altavoz es piezo-eléctrico, igual que en el Casio.

El proyecto tiene unos 5 meses y la mayor parte del tiempo la he dedicado a escribir el software, con el objetivo de iniciarme con los RTOS. En proyectos anteriores me encontré con la dificultad de mantener y ampliar el código cuando el proyecto iba creciendo. Por ejemplo: Si queremos añadir una nueva funcionalidad al sistema será fácil si nuestro sistema aun es pequeño, pero será cada vez mas y mas difícil a medida que el sistema vaya creciendo. Esto no ocurre si diseñamos el sistema basado en un RTOS. En este caso, el sistema se divide en subsistemas y al RTOS se le indican las interconexiones entre ellos. Si queremos añadir una nueva funcionalidad será tan fácil como escribirla e indicar al RTOS su relación con las demás.

Algunas fotos

Circuito

Este es el aspecto del circuito montado. En la parte inferior se acopla un portapilas con dos pilas tipo AAA. Tambien se puede ver como el conector ICSP tiene un formato miniUSB.

Pulsando teclas

Esta imagen muestra como se usan los LEDs cuando se detecta una pulsación.

Funcionamiento

El sistema esta dividido en 8 tareas concurrentes comunicadas entre sí con colas “queues”. Cada tarea incluye objetos y librerías para efectuar su función. Las tareas que acceden a un mismo recurso poseen mecanismos de sincronización como mutex. Las tareas se pueden agrupar en cuatro grupos diferentes según su rol en el sistema: productoras, procesadoras, consumidoras y control. Cada grupo de taras posee prioridades distintas, teniendo la menor las productoras … y la mayor la de control.

Para utilizar el gadget el usuario debe agitar continuamente la mano para leer la información. El aparato tiene una columna de 24 LEDs y genera en el aire una imagen estable de 128*24 pixels. Incorpora un acelerómetro para sincronizar el movimiento de la mano con el barrido de la imagen y así mantenerla lo más estable posible en el aire.
Los mismos 24 LEDs se pueden poner en modo inverso y así ser usados como sensores. De este modo se emulan los 3 botones (A B y C) del Casio.

El sistema también posee un botón, un botón físico real, que no existe en el reloj Casio. Este botón permite “despertar” al reloj durante un tiempo para que el usuario interactúe con él. Además el botón comparte pin IO con el altavoz y, aunque sus funciones son totalmente incompatibles, el sistema sincroniza perfectamente el pin IO de tal manera que las dos funciones se ejecuten en “paralelo” y NUNCA se interfieran entre sí.

Después de un reset, la función main configura todas las 8 tareas y arranca el RTOS. En este punto solo hay dos tareas en funcionamiento: TaskButton y TaskWakeUp.
Cuando el usuario pulsa el botón, la tarea TaskButton envía un mensaje a la tarea TaskWakeUp y esta ultima despierta todas las demás tareas. Pasados 20 segundos, las vuelve a suspender.

Mientras todas las tareas están despiertas, el usuario puede interactuar con el reloj visualizando en pantalla la misma información que en el reloj original y navegando por los mismos menús.
Cuando pulsa una tecla la tarea GetLeds envía un mensaje a la tarea TaskWatch con la tecla pulsada.


La tarea TaskWatch le pasará a la función WatchProcces la tecla pulsada para actualizar el estado del reloj y después enviará a la tarea TaskGraphics todas las primitivas que componen la pantalla del reloj en el modo actual ( texto, líneas, símbolos…).

La tarea TaskGraphics pintará todas las primitivas en una matriz de 128*24 y cuando termine le pasará esta matriz a la tarea TaskPutLeds.

La tarea TaskPutLeds recibirá el mensaje y esperará a que la tarea TaskAcce le indique el momento exacto para mostrar el mensaje en el aire.

La tarea TaskAcce mide constantemente la aceleración y envía un mensaje a la tarea TaskPutLeds cuando la aceleración alcanza un máximo o un mínimo.

Las tareas TasButton y TaskSpeaker comparten un recurso (un pin IO del PIC), y para evitar que ambas accedan al recurso al mismo tiempo están sincronizadas mediante un mutex. Las tareas GetLeds y PutLeds también comparten recursos (los 24 LEDs y el Mosfet) y también están sincronizadas con un mutex.

Diagrama de la aplicación

Diagrama

Este grafico muestra un diagrama de bloques de la tarea TaskAcce y su equivalente en C. Los bloques Avg, Diff y AND, RSFF están modelados como objetos.

TaskAcce

El código

El código esta dividido en 3 partes principales:
El main. Este se encarga de definir la aplicación en si (tareas y su interconexión) y de arrancar el RTOS llamando a la función vTaskStartScheduler.
Las tareas. Hay 4 tipos de tareas según su rol:
-Productoras: Cogen datos del exterior del sistema como un pinIO o el ADC y envían un mensaje con el dato leído.
-Procesadoras: Reciben un mensaje, procesan la información y reenvían los resultados en otro mensaje.
-Consumidoras: Reciben un mensaje y lo mandan al exterior del sistema como a un LED o a algún periférico externo.
-Control: Supervisan el funcionamiento del sistema “escuchando” la información que se pasan las tareas.
Las utilidades. Aquí se agrupan todas las funciones de utilidad general necesarias para las tareas, como por ejemplo la clase Watch.c o las funciones graficas GLCD.c.

Voy a empezar describiendo la tarea TaskButton. Es una tarea de tipo productor, y realiza una función muy sencilla en el sistema: informar a quien lo necesite de que el usuario ha pulsado el botón.

Tiene 2 funciones vStartTaskButton() y vTaskButton(). La primera es llamada por el main y se encarga de indicar al RTOS los recursos que tiene esta tarea (un mutex llamado xMutexIO y un queue llamado xQueueKey). La segunda función es la que ejecutara el RTOS cuando este arranque.

TaskButton http://www.megaupload.com/?d=2LGV3LWR

Una vez arrancado el RTOS el PC empezara a correr por la función vTaskButton y esta realiza lo siguiente en el bucle infinito while(1):
Primero comprueba si el recurso compartido esta disponible para su uso, de lo contrario esperará un tiempo y vuelve a comprobar. Hay que recordar que el botón y el altavoz están conectados al mismo pin y que hay que asegurar que NUNCA se lea del botón mientras se escribe en el altavoz. Para esto las dos tareas implicadas, vTaskButton y vTaskSpeaker comparten un mutex y se mantienen sincronizadas.
Cuando la tarea vTaskButton “se adueña” del mutex sabe que la tarea vTaskSpeaker no podrá tocar el pin IO hasta que vuelva a “liberar” el mutex.
Ahora, en posesión del mutex, sí podemos configurar el pin como entrada, leer su valor desconfigurarlo y liberar el mutex para que otra tarea pueda usarlo.
Si el valor leído es de “botón pulsado” se comunica a otras tareas enviando un mensaje.

¿Sencillo?, yo creo que si. ¿Eficiente?, no mucho…¿Robusto? 100%.
Las funciones xSemaphoreTake, vTaskDelay, xSemaphoreGive… son las herramientas que proporciona el RTOS y se encuentran perfectamente documentadas en su página web (ver links recomendados al final del artículo).

Tarea TaskAcce.

Esta tarea tiene como objetivo sincronizar el mensaje de la tarea TaskPutLeds con el movimiento de la mano y ofrecer así una imagen lo mas estable posible.
Debe leer continuamente el acelerómetro (a través del ADC) e indicar al sistema dos eventos clave del movimiento de la mano: cuando la mano esta a la izq. del todo y cuando esta a la drcha. del todo.
Con esta información se puede calcular el momento en el que se debe refrescar la imagen para que se superponga con la anterior.

Lo primero de todo es definir la relación que existe entre el movimiento en zig-zag de la mano y la aceleración medida.

Movimiento zig-zag

En esta figura se muestran los eventos que queremos detectar. En t = 1.54 la mano está a la izquierda del todo, su velocidad es 0 y su aceleración es mínima. En t = 4.68 la mano está a la derecha del todo, su velocidad es 0 y su aceleración es máxima.


Puesto que solo se puede leer la aceleración, se debe calcular cuando está en su punto máximo o mínimo, y esto se cumple cuando su derivada (la velocidad) es 0.

Diagrama de bloques del algoritmo.

Diagrama de bloques del algoritmo

En el diagrama de bloques esto esta implementado con el Diff y el Avg, aunque este último hace un promedio con un buffer de 8 muestras.

A partir de aquí los bloques son similares tanto para detectar el máximo como para el mínimo, así que solo describiré el máximo.

Lo primero que hay que hacer es definir un margen ( TRIGER_LEVEL ) por debajo del cual consideraremos que la señal es 0, y otro margen (ARM_LEVEL) que nos dirá si la aceleración es máxima o mínima.
También hay que evitar que se envie más de un mensaje por evento, y para eso se ha introducido un biestable RS. Cuando se envía un mensaje (simbolizado con una banderita levantada) se resetea el biestable, y este no vuelve a ponerse a uno hasta que la velocidad es inferior a -ARM_LEVEL.

En esta imagen se muestra el uso de los márgenes y el biestable en la detección.
Margenes y biestable

Aquí se muestra el código de la aplicación. Tiene, como la tarea anterior, 2 funciones vStartTaskAcce y vTaskAcce.
La primera función es llamada por el main durante la configuración de aplicación (antes de arrancar el RTOS). Recibe como parámetro una estructura que contiene manejadores (handler) de todos los recursos de la tarea, devuelve un -1 si ha habido error al inicializar crear algún recurso o 0 si todo ha sido correcto. Es responsabilidad del main leer el valor devuelto y actuar en consecuencia (en este caso el main aborta el programa).

TaskAcce http://www.megaupload.com/?d=ULIEUWCO

La segunda es la que se encarga de detectar el máximo o mínimo con el algoritmo descrito antes y enviar un mensaje.

El código se podría haber implementado de muchas maneras, pero se ha intentado acercar a lo que seria la programación orientada a objetos(POO).

En lugar de utilizar una función que se llame diferencia, se han creado dos archivos: differenciator.c y differenciator.h
Differenciator.h- Contiene los atributos ( una estructura ) y la declaración de los métodos del objeto( declaraciones de las funciones).
Differenciator.c- Contiene las definiciones de los métodos del objeto.

Para utilizar un objeto tipo diferentiator podríamos utilizar un programa de este tipo:

 include 
#include "differentiator.h"
Main(){
Differentiator diff1;
DifferentiatorConstructor( &diff1 );
while(i++){
DifferentiatorProcces( &diff1, i );
printf(“%d”, diff1.out );
}
}

La tarea TaskWatch
Ésta es la tarea central del emulador. Se encarga de mantener el reloj y sus menús actualizados en pantalla.
Recibe la tecla pulsada de la tarea TaskGetLeds, actualiza el estado del reloj y envía una serie de primitivas que representan la pantalla a la tarea TaskGraphics.
Está dividida, como las dos anteriores, en dos funciones vStartTaskWatch y vTaskWatch.
El código de la tarea en si es sencillo y toda la implementación del reloj se encuentra en una clase llamada Watch. La clase Watch se apoya sobre otra clase llamada Menu.

TASKWATCH_C  http://www.megaupload.com/?d=VNHYX50C

sta tarea es mas larga, pero su código es más sencillo de entender,
ya que la mayor parte del código solo trata de enviar las primitivas a la
tarea TaskWatch.

Todas las funciones Qglcd_… están definidas en el archivo TaskWatch.h más o menos como:

#define Qglcd_pixel( x, y, c )      \
xMessagePrimitive.function = __glcd_pixel;
xMessagePrimitive.intA = x;
xMessagePrimitive.intB = y;
xMessagePrimitive.intC = c;
xMessagePrimitive.time = xTaskGetTickCount
();
xQueueSend
( pxResources->xQueuePrimitive, ( void * )&xMessagePrimitive, portMAX_DELAY );

La clase Menu

La case Menu esta compuesta de dos archivos:
Menu.h que declara los métodos y atributos del objeto.
Menu.c que define los métodos del objeto.
Un objeto del tipo menú implementa una maquina de estados finitos(FSM) de Mealy. Está declarado de la siguiente manera:

typedef struct connection{
int newnode;
char functname
[5];
void
(*pfunction)(void*);
}connection;
//enum KEY_VALUE{NONE, APRESS,BPRESS,CPRESS,AHOLD,BHOLD,CHOLD}; 
typedef struct{
char name
[5];
// int number;
connection connections
[6];
}node;
typedef struct{
node *tree;
int state;
int size;

}Menu;

Y tiene los siguientes métodos:

Menu *MenuConstruct( Menu *this, node *tree, int size );
void MenuDestruct
( Menu *this );
void MenuProcessV
( Menu *this, int input, void * object );
char *MenuGetNodeName
( Menu *this );
char *MenuGetNodeChildName
( Menu *this, int input );
char *MenuGetNodeFunctionName
( Menu *this, int input );

En la función MenuConstructor se asigna al puntero tree la dirección de una estructura que contendrá la organización del menú. Esta es la usada para emular los menús del F91W:

Menú

Como el reloj tiene 6 posibles teclas (A,B,C cortos y A,B,C largos) cada estado de la maquina tiene 6 posibles acciones. Cada acción puede llamar a una cambiar de estado y/o llamar a una función apuntada por el parámetro pfunction.

Después de “construir” un objeto menú (MenuConstructor) solo debemos llamar a la función MenuProcessV. Ésta acepta 3 parámetros:
Menu *this: El menú que se pretende procesar.
int input: La tecla pulsada.
void * object: El parámetro(normalmente un objeto) que se le pasará a la función apuntada por pfunction.

Un objeto menú tiene en cada uno de sus nodos un nombre. Para obtener información sobre los nombres la clase tiene 3 métodos:
MenuGetNodeName: Devuelve el nombre del nodo actual.
MenuGetNodeChildName: Devuelve el nombre de un nodo inferior.
MenuGetNodeFunctionName, Devuelve el nombre de la función a la que apunta pfunction.

La tarea TaskWatch llama a la función MenuGetNodeName cada vez que el reloj cambia de menú y su texto es representado en formato Arial(24 pixels de alto).

La clase Watch

typedef struct Wacth{
Menu menu1;
rtccTimeDate timedate;
rtccTime alarm;
rtccTime stopwatch;
rtccTime splittime;
long start, stop, split, out;
int ChangeAlarmCounter, ChangeTimeCounter;
int PM24;
int ALM;
int SIG;
int RUN;
int SPL;
}Watch;

La clase Watch encapsula todo el funcionamiento interno del reloj. Almacena el valor de la hora, la alarma y el cronometro, así como variables de estado. Si por ejemplo ponemos el reloj en modo 24H se pone a uno el campo de Watch.PM24 = 1 y esto será usado en la representación del reloj.ub-3142430588617024″; /* Artículo reloj casio */ google_ad_slot = “5444473188”; google_ad_width = 336; google_ad_height = 280; //–> window.google_render_ad()

Decodificador 4 bits a hexadecimal display 7 segmentos

mayo 14, 2009 en 10:15 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;Programa que muestra un numero de 4 bits en
;un display en hexadecimal

.include “m16def.inc”

.def rat = r16

.org 0×00

;Definimos entradas y Salidas

ldi rat,0×00
out ddra,rat
ldi rat,0×7f
out ddrb,rat

;Inicio

main:

ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
in rat,pina
andi rat,0×0f
add zl,rat
clr rat
adc zh,rat
lpm rat,z
out portb,rat
rjmp main

tabla:; display anodo común
.dw 0×7940;1,0
.dw 0×3024;3,2
.dw 0×1219;5,4
.dw 0×7802;7,6
.dw 0×1000;9,8
.dw 0×0308;b,a
.dw 0×2146;d,c
.dw 0×0e06;f,e

Suma o Resta de 2 numeros de 8 bits el resultado se muestra en 2 displays en hexadecimal

mayo 14, 2009 en 10:15 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;Programa Suma o Resta 2 números de bits
; y el resultado sera mostrado en hexadecimal en 2 displays

.include “m16def.inc”

.def aux1 = r16
.def aux2 = r17
.def rat = r18

.org 0×00

;Definimos entradas y Salidas

ldi aux1,0×00
out ddra,aux1
out ddrb,aux1
ldi aux1,0×7f
out ddrd,aux1
ldi aux1,0xff
out ddrc,aux1

;Inicio

main:

in aux1,pina
in aux2,pinb
sbic pind,7 ;Suma 1,Resta 0
rjmp suma

resta:

sub aux1,aux2
brbs 2,comp
ldi rat,0×00
rjmp mostrar

comp:
neg aux1
ldi rat,0×80
rjmp mostrar

suma:

add aux1,aux2

mostrar: ;Para mostrar en Hexadecimal en 2 Displays

mov aux2,aux1
cbr aux1,0xf0
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,aux1
clr aux1
adc zh,aux1
lpm aux1,z
sbrc rat,7
ori aux1,0×80
out portc,aux1
swap aux2
cbr aux2,0xf0
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,aux2
clr aux2
adc zh,aux2
lpm aux2,z
out portd,aux2
rjmp main;

tabla:; display anodo común
.dw 0×7940;1,0
.dw 0×3024;3,2
.dw 0×1219;5,4
.dw 0×7802;7,6
.dw 0×1000;9,8
.dw 0×0308;b,a
.dw 0×2146;d,c
.dw 0×0e06;f,e

Contador modulo 256 con retardo

mayo 14, 2009 en 10:14 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

.include “m16def.inc”

.def aux1= r17
.def aux = r16
.equ cont1=0×60
.equ cont2=0×62
.equ cont3=0×63

.org 0×00
ldi aux,low(ramend)
out spl,aux
ldi aux,high(ramend)
out sph,aux
ldi aux1,0xff
out ddrb,aux1
;config pueto b

clr aux1
main:

out portb,aux1
inc aux1
call retardo
cpi aux1,255
brlo main
; iniciando el stack pointer
retardo:
ldi aux,25
sts cont1,aux
carga_cont2:
ldi aux,47
sts cont2,aux

lds aux,cont1
dec aux
sts cont1,aux
brne paso1
rjmp final

paso1:
ldi aux,255
sts cont3,aux

lds aux,cont2
dec aux
sts cont2,aux
brne paso2
rjmp carga_cont2

paso2:
lds aux,cont3
dec aux
sts cont3,aux
brne paso2
rjmp paso1

final:

ret

Divisor de frecuencia a 50Hz

mayo 14, 2009 en 10:14 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;El ck interno esta trabajando a 4Mhz

.include “m16def.inc”

.def aux1= r17
.def aux = r16
.equ cont1=0×60
.equ cont2=0×62
.equ cont3=0×63

.org 0×00
ldi aux,low(ramend)
out spl,aux
ldi aux,high(ramend)
out sph,aux
ldi aux1,0xff
out ddrb,aux1
;config pueto b

clr aux1
main:

out portb,aux1
inc aux1
call retardo
out portb,aux1
dec aux1
call retardo
rjmp main
; iniciando el stack pointer
retardo:
ldi aux,10
sts cont1,aux
carga_cont2:
ldi aux,24
sts cont2,aux

lds aux,cont1
dec aux
sts cont1,aux
brne paso1
rjmp final

paso1:
ldi aux,26
sts cont3,aux

lds aux,cont2
dec aux
sts cont2,aux
brne paso2
rjmp carga_cont2

paso2:
lds aux,cont3
dec aux
sts cont3,aux
brne paso2
rjmp paso1

final:

ret

Division de 2 números binarios

mayo 14, 2009 en 10:13 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;En el display se muestra segun la entrada de 2 dip switch el cuociente o el residuo y con el otro las (decenas|unidades) o (cero|centenas)

.include “m16def.inc”

.def rsd =r16 ;residuo
.def dvdnd =r17 ;dividend
.def dvsr =r18 ;divisor
.def cont =r19 ;contador
.def aux =r20
.def bcd1 =r21 ;auxiliar

.org 0×00
ldi dvdnd,0×00
out ddra,dvdnd
out ddrc,dvdnd
ldi dvdnd,0×7f
out ddrb,dvdnd;decenas,centenas
out ddrd,dvdnd;cuociente,residuo
inicio:
in dvdnd,pina
in dvsr,pinc
sub rsd,rsd
ldi cont,9
lazo1:
rol dvdnd
dec cont
brne lazo2
rjmp resultado
lazo2:
rol rsd
sub rsd,dvsr
brcc lazo3
add rsd,dvsr
clc
rjmp lazo1
lazo3:
sec
rjmp lazo1

resultado:

sbic pinb,7
rjmp resultado_residuo
sub bcd1,bcd1
ldi cont,8
clr dvsr
clr bcd1

inicio1:

clc
rol dvdnd
rol dvsr
rol bcd1
dec cont
breq mostrar_decena
mov aux,dvsr
andi aux,0×0f
cpi aux,5
brsh sumar
dos:
mov aux,dvsr
swap aux
andi aux,0×0f
cpi aux,5
brsh sumar2
rjmp inicio1

sumar:
subi dvsr,-0×03
rjmp dos
sumar2:
subi dvsr,-0×30
rjmp inicio1

resultado_residuo:
sub bcd1,bcd1
ldi cont,8
clr dvsr
clr bcd1

inicio2:

clc
rol rsd
rol dvsr
rol bcd1
dec cont
breq mostrar_decena
mov aux,dvsr
andi aux,0×0f
cpi aux,5
brsh sumarr
dosr:
mov aux,dvsr
swap aux
andi aux,0×0f
cpi aux,5
brsh sumar2r
rjmp inicio2

sumarr:
subi dvsr,-0×03
rjmp dosr
sumar2r:
subi dvsr,-0×30
rjmp inicio2

mostrar_decena: ;Para mostrar en decimal en 2 Displays

sbic pind,7
rjmp mostrar_centena
mov aux,dvsr
cbr dvsr,0xf0
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,dvsr
clr dvsr
adc zh,dvsr
lpm dvsr,z
out portb,dvsr
swap aux
cbr aux,0xf0
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,aux
clr aux
adc zh,aux
lpm aux,z
out portd,aux
rjmp inicio
mostrar_centena:
clr aux
cbr bcd1,0xf0
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,bcd1
clr bcd1
adc zh,bcd1
lpm dvsr,z
out portb,dvsr
ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,aux
adc zh,aux
lpm aux,z
out portd,aux
rjmp inicio
tabla:; display anodo común
.dw 0×7940;1,0
.dw 0×3024;3,2
.dw 0×1219;5,4
.dw 0×7802;7,6
.dw 0×1000;9,8

Contador modulo 10 muestra su resultado en un display 7 segmentos

mayo 14, 2009 en 10:12 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;Ademas tiene un retardo de 500ms entre cada número

.include “m16def.inc”

.def aux1= r17
.def aux = r16
.equ cont1=0×60
.equ cont2=0×62
.equ cont3=0×63

.org 0×00
ldi aux,low(ramend)
out spl,aux
ldi aux,high(ramend)
out sph,aux
ldi aux1,0xff
out ddrb,aux1
ldi aux1,0×01
out ddrd,aux1
;config pueto b
inicio:

clr aux1
clr aux

main:

ldi zl,low(tabla<<1)
ldi zh,high(tabla<<1)
add zl,aux1
clr aux
adc zh,aux
lpm aux,z
out portb,aux
out portd,aux1
inc aux1
call retardo
cpi aux1,10
brlo main
rjmp inicio
; iniciando el stack pointer
retardo:
ldi aux,25
sts cont1,aux
carga_cont2:
ldi aux,47
sts cont2,aux

lds aux,cont1
dec aux
sts cont1,aux
brne paso1
rjmp final

paso1:
ldi aux,255
sts cont3,aux

lds aux,cont2
dec aux
sts cont2,aux
brne paso2
rjmp carga_cont2

paso2:
lds aux,cont3
dec aux
sts cont3,aux
brne paso2
rjmp paso1

final:

ret

tabla:; display anodo común
.dw 0×7940;1,0
.dw 0×3024;3,2
.dw 0×1219;5,4
.dw 0×7802;7,6
.dw 0×1000;9,8

Encuentra un valor ingresado por el puerto en una tabla de la Flash

mayo 14, 2009 en 10:11 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

;Sale en un display de 7 segmentos una S si el valor existe en al tabla

;y una n si este no existe

.include “m16def.inc”
.def tempo=r16
.def cont=r17
.def valor=r18
.org 0×0

ldi tempo,0×00
out ddra,tempo
out ddrc,tempo
out ddrd,tempo
ldi tempo,0xff
out porta,tempo;Para poder utilizar simplemente conectando a tierra un cero logico
out portc,tempo
out portb,tempo
in tempo,sfior
andi tempo,0xfb
out sfior,tempo
ldi tempo,0×7f
out ddrb,tempo
sbi portb,7
otro:
ldi zl,low(tabla*2)
ldi zh,high(tabla*2)
ldi cont,12
in valor,pina
lazo:
lpm tempo,z+
cp tempo,valor
breq igual
noyet:
dec cont
brne lazo
notfound:
ldi tempo,0×54
out portb,tempo
rjmp otro
igual:
ldi tempo,0×6d
out portb,tempo
rjmp otro
tabla:
.db 2,4,6,8,10,12
.db 13,15,18,19,0,10

Pasar tabla de la flash a la RAM

mayo 14, 2009 en 10:10 pm | Publicado en ATMega y AVR, Electrónica, Microcontroladores | Deja un comentario

.include “m16def.inc”
.def cont=r16
.def tempo=r17
.def maximo=r18
.def aux=r19
.equ destino=0×60
.org 0×0
ldi tempo,0xff
out ddrb,tempo
out porta,tempo
in tempo,sfior
andi tempo,0xfb
out sfior,tempo
ldi zh,high(tabla*2)
ldi zl,low(tabla*2)
ldi xl,low(destino)
ldi xh,high(destino)
main:
lpm cont,z+;El primer valor de la tabla contiene el número de elmentos
st x+,cont
mov aux,cont
lazo:
lpm tempo,z+
st x+,tempo
dec cont
brne lazo
mov cont,aux
mov maximo,tempo
movw yl,xl
lazo1:
ld tempo,-y
cp maximo,tempo
brsh nada
mov maximo,tempo
dec cont
brne lazo1
nada:
dec cont
brne lazo1
st x,maximo
final:
rjmp final
tabla:
.db 5,15,25,36,54,9


Entries y comentarios feeds.