Autor Tema: Emulador de un Casio F91W basado en FreeRTOS  (Leído 28551 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado Nocturno

  • Administrador
  • DsPIC33
  • *******
  • Mensajes: 18286
    • MicroPIC
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #15 en: 28 de Marzo de 2009, 14:47:24 »
¡Magnífico!, con la de veces que uso arrays de componentes y me tengo que comer el coco en establecer el mejor orden posible. Incluso para poner un conector de expansión podrá venir bien esta función.

No paro de aprender con este hilo, muchas gracias jgpeiro.

Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #16 en: 29 de Marzo de 2009, 17:02:45 »
Citar
Incluso para poner un conector de expansión podrá venir bien esta función.
Si, si, yo lo he comentado para los LEDs, pero es cierto que es más útil para conectores.


La tarea TaskGraphics



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:
   Esta 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

Código: C
  1. #ifndef TASKGRAPHICS_C
  2. #define TASKGRAPHICS_C
  3.  
  4. #include ".\TaskGraphics.h"
  5.  
  6. unsigned long mensajeBuffer[2][128];
  7.  
  8. int startTaskGraphics( xTaskGraphicsResources *pxResources ){
  9.                        
  10.         if( pxResources->xQueuePrimitive == NULL ){
  11.                 if( NULL == ( pxResources->xQueuePrimitive = xQueueCreate( 10, sizeof( xMessageGraphicsPrimitive ) ) ) )
  12.                         return -1;
  13.                 vQueueAddToRegistry( pxResources->xQueuePrimitive, ( signed portCHAR * )"xQueueGraphicsPrimitive" );
  14.         }
  15. //      if( pxResources->xQueueDisp == NULL ){
  16.                 if( NULL == ( pxResources->xQueueDisp = xQueueCreate( 1, sizeof( xMessageGraphicsDisp ) ) ) )
  17.                         return -1;
  18.                 vQueueAddToRegistry( pxResources->xQueueDisp, ( signed portCHAR * )"xQueueGraphicsDisp" );
  19. //      }
  20.        
  21.         if( pdPASS != xTaskCreate( vTaskGraphics, ( signed portCHAR * ) "Graphics", configMINIMAL_STACK_SIZE, pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
  22.                 return -1;
  23.                
  24.         return 0;
  25. }
  26.  
  27.  
  28. void vTaskGraphics( void *pxParameters ){
  29.        
  30.         unsigned long *pmensaje;
  31.         xTaskGraphicsResources *pxResources;            // Task parameteres handler.
  32.         xMessageGraphicsPrimitive xMessagePrimitive;
  33.         xMessageGraphicsDisp xMessageDisp;
  34.        
  35.         pxResources = (xTaskGraphicsResources*)pxParameters;   
  36.  
  37.         vTaskSuspend( NULL );
  38.  
  39.         if( pmensaje == &mensajeBuffer[0][0] )  pmensaje = &mensajeBuffer[1][0];
  40.         else                                                                    pmensaje = &mensajeBuffer[0][0];
  41.         glcd_init( 0, pmensaje );
  42.        
  43.         while( 1 ){
  44.                
  45.                 vTaskDelay( 10 );
  46.                
  47.                 xQueueReceive( pxResources->xQueuePrimitive, &xMessagePrimitive, portMAX_DELAY );
  48.        
  49.                 switch( xMessagePrimitive.function ){
  50.                         case __glcd_init : glcd_init( xMessagePrimitive.intA, xMessagePrimitive.pulongA );                                                              break;
  51.                         case __glcd_pixel : glcd_pixel( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.intC);                                                  break;
  52.                         case __glcd_line : glcd_line( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.intC,xMessagePrimitive.intD,xMessagePrimitive.intE);              break;
  53.                         case __glcd_rect : glcd_rect( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.intC,xMessagePrimitive.intD,xMessagePrimitive.intE,xMessagePrimitive.intF);break;
  54.                         case __glcd_bar : glcd_bar( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.intC,xMessagePrimitive.intD,xMessagePrimitive.intE,xMessagePrimitive.intF);break;
  55.                         case __glcd_circle : glcd_circle( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.intC,xMessagePrimitive.intD,xMessagePrimitive.intE);          break;
  56.                         case __glcd_text57 : glcd_text57( xMessagePrimitive.intA, xMessagePrimitive.intB, xMessagePrimitive.acharA/*xMessagePrimitive.pcharA*/, xMessagePrimitive.intC, xMessagePrimitive.intD);break;
  57.                         case __glcd_textArial : glcd_textArial( xMessagePrimitive.intA,xMessagePrimitive.intB,xMessagePrimitive.acharA/*xMessagePrimitive.pcharA*/,xMessagePrimitive.intC);                     break;
  58.                         case __glcd_fillScreen : glcd_fillScreen( xMessagePrimitive.intA );                                                                             break;
  59.                         case __glcd_update :
  60.                                 glcd_rect(0,0,127,23,0,1);
  61.                                 xMessageDisp.time = xTaskGetTickCount();
  62.                                 xMessageDisp.buffer = pmensaje;
  63.                                 if( pdTRUE == xQueueSend( pxResources->xQueueDisp, ( void * )&xMessageDisp, portMAX_DELAY ) ){
  64.                                         //while( uxQueueMessagesWaiting( pxResources->xQueueDisp ) )
  65.                                         //      vTaskDelay(100);
  66.                                        
  67.                                         if( pmensaje == &mensajeBuffer[0][0] )  pmensaje = &mensajeBuffer[1][0];
  68.                                         else                                                                    pmensaje = &mensajeBuffer[0][0];
  69.  
  70.                                         glcd_init( 0, pmensaje );
  71.                                 }
  72.                                
  73.                                
  74.                         default:        break;
  75.                 }
  76.         }                      
  77. }
  78.                
  79.  
  80. #endif  //#ifndef TASKACCE_C

La librería Graphics.c
Es la librería que proporciona el compilador CCS para dibujar primitivas 2D básicas como líneas, pixeles, 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 soluciono copiando toda la cadena (hasta 5 caracteres mas el carácter nulo) en el mensaje enviado.


Algunos links:
   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


Desconectado J1M

  • Moderadores
  • PIC24H
  • *****
  • Mensajes: 1960
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #17 en: 30 de Marzo de 2009, 03:55:30 »
Me quito el sombrero José Antonio! Gracias por iluminarnos con tus proyectos. Enhorabuena por la magistral forma en que dedicas tu tiempo libre! :P

Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #18 en: 30 de Marzo de 2009, 17:42:25 »
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:
   Esta dividida en dos funciones, vStartTaskSpeaker y vTaskSpeaker. La primera crea los recursos y la segunda recibe el mensaje y generara 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.

Código: C
  1. #ifndef TASKSPEAKER_C
  2. #define TASKSPEAKER_C
  3.  
  4. #include ".\TaskSpeaker.h"
  5.  
  6. int startTaskSpeaker( xTaskSpeakerResources *pxResources ){
  7.         if( pxResources->xMutexIO == NULL ){
  8.                 if( NULL == ( pxResources->xMutexIO = xSemaphoreCreateMutex() ) )
  9.                         return -1;
  10.                 vQueueAddToRegistry( pxResources->xMutexIO, ( signed portCHAR * )"xMutexIO" );
  11.         }              
  12.        
  13.         if( pxResources->xQueueSound == NULL ){
  14.                 if( NULL == ( pxResources->xQueueSound = xQueueCreate( 5, sizeof( xMessageSpeakerSound ) ) ) )
  15.                         return -1;
  16.                 vQueueAddToRegistry( pxResources->xQueueSound, ( signed portCHAR * )"xQueueSpeakerSound" );
  17.         }
  18.        
  19.         if( pdPASS != xTaskCreate( vTaskSpeaker, ( signed portCHAR * ) "Speaker", configMINIMAL_STACK_SIZE, pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
  20.                 return -1;
  21.                
  22.         return 0;
  23. }
  24.  
  25. void vTaskSpeaker( void *pvParameters ){
  26.  
  27.         int i, j, k;
  28.         xTaskSpeakerResources *pxResources;
  29.         pxResources = (xTaskSpeakerResources*)pvParameters;    
  30.         xMessageSpeakerSound xMessage;
  31.         portTickType xLastWakeTime;
  32.        
  33.         vTaskSuspend( NULL );
  34.        
  35.         while(1){
  36.                 vTaskDelay( 10 );
  37.                 xQueueReceive( pxResources->xQueueSound, &xMessage,  portMAX_DELAY );
  38.                
  39.                 if( pdTRUE == xSemaphoreTake( pxResources->xMutexIO, TASK_SPEAKER_MUTEX_BLOCKTIME ) ){
  40.                         TRISAbits.TRISA1 = 0;
  41.                        
  42.                         xLastWakeTime = xTaskGetTickCount();
  43.                         for( ; xMessage.reps ; xMessage.reps >>= 1 ){
  44.                                 vTaskDelayUntil( &xLastWakeTime, 500 );
  45. //                              taskENTER_CRITICAL();
  46.                                 vTaskPrioritySet( NULL, pxResources->uxPriorityHigh ); //putleds < newprio < wakeup );
  47.                                 if( xMessage.reps&0x1 ) {
  48.                                         for( j = 0 ; j < 100 ; j++ ){
  49.                                                 for( k = 0 ; k < 0x300 ; k++ );
  50.                                                 PORTAbits.RA1 = !PORTAbits.RA1;
  51.                                         }
  52.                                 }
  53. //                              taskEXIT_CRITICAL();
  54.                                 vTaskPrioritySet( NULL, pxResources->uxPriority );
  55.                         }
  56.                                        
  57.                         TRISAbits.TRISA1 = 1;
  58.                         xSemaphoreGive( pxResources->xMutexIO );
  59.                 }      
  60.         }
  61. }
  62.  
  63.  
  64. #endif  // #ifndef TASKSPEAKER_C

Generar el tono
El mensaje recibido posee 3 datos:
Aunque la función que se muestra arriba funciona, aun 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.

Algunos links:
   Preemption:
      http://en.wikipedia.org/wiki/Preemptive_multitasking


Desconectado todopic

  • Administrador
  • DsPIC30
  • *******
  • Mensajes: 3495
    • http://www.todopicelectronica.com.ar
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #19 en: 30 de Marzo de 2009, 21:41:11 »
Realmete IM PRE SIO NAN TE!!!!  :-/

aún me estoy levantando los dientes je je je!!! muy buen proyecto, bien detallado

Gracias por compartirlo!!!!

Norberto
Firmat - Santa Fe - Argentina

www.TodoPic.net

Solo se tiran piedras, al arbol que tiene frutos...

Desconectado barral

  • PIC10
  • *
  • Mensajes: 37
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #20 en: 31 de Marzo de 2009, 03:32:23 »
Simplemente IMPRESIONANTE. Es de admirar tu trabajo, y mucho más que lo compartas y lo expliques tan bien. A estudiar RTOS, la verdad es que yo hace bastante tiempo que quería echarle mano a alguno y siempre falta tiempo... ahora es el momento.

Un saludo y mis más sinceras felicitaciones.

Desconectado RICHI777

  • Colaborador
  • PIC24H
  • *****
  • Mensajes: 1498
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #21 en: 31 de Marzo de 2009, 11:17:01 »
Hola jgpeiro, realmente excelente el proyecto, hermano me hiciste googlear bastante ( y eso es bueno ) de como se hacia para leer estados con un LED en inversa, espectacular !!!, excelente tambien lo del acelerometro. Si en este foro se premiara al mejor proyecto, no dudes que para mi lo ganas de una, sencillo, innovador, excelentemente diseñado, escrito y explicado.

Saludos cordiales !

Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #22 en: 31 de Marzo de 2009, 12:48:03 »
La tarea TaskPutLeds


Responsabilidades:
   Debe imprimir una imagen de 128x24 sincronizada con el movimiento de la mano.
Tipo:
   Es una tarea consumidora.
Estructura de la tarea:
   Esta 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.

Código: C
  1. #ifndef TASKPUTLEDS_C
  2. #define     TASKPUTLEDS_C    
  3.  
  4. #include     ".\TaskPutLeds.h"
  5.  
  6. int startPutLedsTask( xTaskPutLedsResources *pxResources )
  7. {
  8.     if( pxResources->xMutexLeds == NULL )
  9.     {
  10.         if( NULL == ( pxResources->xMutexLeds = xSemaphoreCreateMutex( ) ) )
  11.             return -1;
  12.         vQueueAddToRegistry( pxResources->xMutexLeds, ( signed portCHAR * )"xMutexLeds" );
  13.     }
  14.     if( pxResources->xQueueMove == NULL )
  15.     {
  16.         if( NULL
  17.              == ( pxResources->xQueueMove
  18.                    = xQueueCreate( 5, sizeof( xMessagePutLedsMove ) ) ) )
  19.             return -1;
  20.         vQueueAddToRegistry( pxResources->xQueueMove, ( signed portCHAR * )"xQueuePutLedsMove" );
  21.     }
  22.     if( pxResources->xQueueDisp == NULL )
  23.     {
  24.         if( NULL
  25.              == ( pxResources->xQueueDisp
  26.                    = xQueueCreate( 1, sizeof( xMessagePutLedsDisp ) ) ) )
  27.             return -1;
  28.         vQueueAddToRegistry( pxResources->xQueueDisp, ( signed portCHAR * )"xQueuePutLedsDisp" );
  29.     }
  30.  
  31.     if( pdPASS != xTaskCreate( vPutLedsTask, ( signed portCHAR * ) "PutLEDs", configMINIMAL_STACK_SIZE, pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
  32.     return -1;
  33.  
  34.     return 0;
  35. }
  36.  
  37.  
  38.  
  39. void vPutLedsTask( void *pxParameters )
  40. {
  41.  
  42.     xTaskPutLedsResources * pxResources;                         /*  < Task parameteres handler. >                 */
  43. //  portTickType xLastWakeTime;             // Task waketime container.
  44.     xMessagePutLedsMove    xMessageMove;
  45.     xMessagePutLedsDisp    xMessageDisp;
  46.  
  47.                     int    i;
  48.                     int    whilecnt;
  49.     unsigned  long         printtime;
  50.                     char   failAcnt = 0,
  51.                            failBcnt = 0,
  52.                            failCcnt = 0;
  53.     Differentiator         dffrnttr1;
  54.  
  55.     DifferentiatorConstruct( &dffrnttr1 );
  56.  
  57.     pxResources = ( xTaskPutLedsResources * )pxParameters;
  58.  
  59.     vTaskSuspend( NULL );
  60.  
  61.     // Wait the first display data.
  62.     while( !uxQueueMessagesWaiting( pxResources->xQueueDisp ) )
  63.         vTaskDelay( 100 );
  64.  
  65.     while( 1 )
  66.     {
  67.  
  68.         vTaskDelay( 1000 );                                     /*    If your task take a mutex, be sure that     */
  69.                                                                 /*        release for a few ticks                 */
  70.  
  71.         xQueueReceive( pxResources->xQueueDisp, &xMessageDisp, 0 );
  72.                                                                 /*    Receive new display data for print. If      */
  73.                                                                 /*        nonew display data in 10 ticks, the     */
  74.                                                                 /*        task print last received data.          */
  75.  
  76.         if( pdTRUE == xSemaphoreTake( pxResources->xMutexLeds, portMAX_DELAY ) )
  77.         {                                                       /*    Take the mutex for operate with LEDs.       */
  78.  
  79.             put_LEDS( 0x0UL );                                  /*      Config LEDs hardware for operation        */
  80.             set_LEDS_DIR( 0x0UL );
  81.             set_COLS_DIR( 3 );
  82.  
  83.             while( pdTRUE == xQueueReceive( pxResources->xQueueMove, &xMessageMove, 256 ) )
  84.                                                                 /*      Wait for a movement in next 256 ticks.    */
  85.             {                                                  
  86.  
  87.                 xQueueReceive( pxResources->xQueueDisp, &xMessageDisp, 0 );
  88.  
  89.                 DifferentiatorProcessV( &dffrnttr1, xMessageMove.time );
  90.                 printtime = xMessageMove.time - 64 + dffrnttr1.out / 2;
  91.                                                                 /*        Check if the printtime is in suposed    */
  92.                                                                 /*            valid range.                        */
  93.                 if( !( xTaskGetTickCount( ) <= printtime
  94.                         && printtime < xTaskGetTickCount( ) + 128 ) )
  95.                 {
  96.                     failAcnt++;                                 /*          Chech a fails occurs, only for        */
  97.                                                                 /*              application debug.                */
  98.                     DifferentiatorPreset( &dffrnttr1, xTaskGetTickCount( ) - 64 );
  99.                                                                 /*          Set approx. valid previous movement   */
  100.                                                                 /*              time.                             */
  101.  
  102.                 }
  103.                 else
  104.                 {
  105.                     vTaskDelay( printtime - xTaskGetTickCount( ) );
  106.                                                                 /*          Wait to printtime. Print 128 cols in  */
  107.                                                                 /*              any direction.                    */
  108.                     for( i = ( xMessageMove.direction == 1 )
  109.                                ? 0:127; 0 <= i && i < 128;
  110.                          i += xMessageMove.direction )
  111.                     {
  112.                         put_LEDS( xMessageDisp.buffer[ i ] );
  113.                         vTaskDelay( 1 );
  114.                     }
  115.                     put_LEDS( 0x0UL );                          /*          Make sure all LEDs are off. Wait next */
  116.                                                                 /*              move with LEDs off.               */
  117.                 }
  118.             }
  119.  
  120.             set_COLS_DIR( 0 );                                  /*      Shutdown LEDs.                            */
  121.             set_LEDS_DIR( ~0x0UL );
  122.             put_LEDS( 0x0UL );
  123.  
  124.             xSemaphoreGive( pxResources->xMutexLeds );          /*      Release LEDs mutex.                       */
  125.         }
  126.     }
  127. }
  128.  
  129.  
  130. #endif                                                          /*#ifndef TASKACCE_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.


Desconectado SavageChicken

  • Colaborador
  • PIC24F
  • *****
  • Mensajes: 931
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #23 en: 31 de Marzo de 2009, 14:35:44 »
Increiblemente bueno, además de toda la documentación anexa y la explicación tan clara. !!!

Me sorprendió mucho el uso del led en inversa como sensor, es un uso que desconocía de este elemento que tan amenudo usamos.

Muchas gracias y salud  8)
No hay preguntas tontas...
Solo hay tontos que no preguntan.

Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #24 en: 31 de Marzo de 2009, 20:44:06 »
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:
Código: C
  1. #ifndef TASKWAKEUP_C
  2. #define TASKWAKEUP_C
  3.  
  4. #include "Hardware.h"   // incluir en taskwakeup.h
  5. #include "LEDs.h"               // incluir en taskwakeup.h
  6. #include ".\TaskWakeUp.h"
  7.  
  8. int startTaskWakeUp( xTaskWakeUpResources *pxResources ){
  9.        
  10.         if( pxResources->xQueueButtonKey == NULL ){
  11.                 if( NULL == ( pxResources->xQueueButtonKey = xQueueCreate( 5, sizeof( xMessageWakeUpKey ) ) ) )
  12.                         return -1;
  13.                 vQueueAddToRegistry( pxResources->xQueueButtonKey, ( signed portCHAR * )"xQueueWakeUpButtonKey" );
  14.         }
  15.  
  16.         if( pxResources->xQueueGetLedsKey == NULL ){
  17.                 if( NULL == ( pxResources->xQueueGetLedsKey = xQueueCreate( 5, sizeof( xMessageWakeUpKey ) ) ) )
  18.                         return -1;
  19.                 vQueueAddToRegistry( pxResources->xQueueGetLedsKey, ( signed portCHAR * )"xQueueWakeUpGetLedsKey" );
  20.         }
  21.  
  22.         if( pxResources->xQueueAlarm == NULL ){
  23.                 if( NULL == ( pxResources->xQueueAlarm = xQueueCreate( 5, sizeof( xMessageWakeUpAlarm ) ) ) )
  24.                         return -1;
  25.                 vQueueAddToRegistry( pxResources->xQueueAlarm, ( signed portCHAR * )"xQueueWakeUpAlarm" );
  26.         }
  27.  
  28.        
  29.         if( pdPASS != xTaskCreate( vTaskWakeUp, ( signed portCHAR * ) "WakeUp", configMINIMAL_STACK_SIZE, pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
  30.                 return -1;
  31.        
  32.         return 0;              
  33. }
  34.  
  35. void vTaskWakeUp( void *pvParameters ){
  36.  
  37.         int i;
  38.         portTickType xLastWakeTime;
  39.         xTaskWakeUpResources *pxResources;
  40.         pxResources = (xTaskWakeUpResources*)pvParameters;     
  41.         xMessageWakeUpKey xMessageKey1, xMessageKey2;
  42. //      xMessageWakeUpAlarm xMessageAlarm;
  43.         IFS3bits.RTCIF = 0;
  44.         IEC3bits.RTCIE = 0;                     // Disable RTCC interrupt
  45.        
  46.         while(1){
  47.                 vTaskDelay( 100 );
  48.                 if( pdTRUE == xQueueReceive( pxResources->xQueueButtonKey, &xMessageKey1, 0 )
  49.                         || IFS3bits.RTCIF == 1 ){       // Poll for interrup flag.
  50.                         // clear irq flag in taskwatch after sendSound Queue
  51.                         //IFS3bits.RTCIF = 0;                   // Clear interrup flag.
  52.                 //      clock = 8MIPS;                                                                                          // Fast operation.
  53.                        
  54.                         ACCS = 1;                                                                                                       // Power-up external hardware
  55.                        
  56.                         for( i = 0 ; i < 8 ; i++ ){                                                                     // Resume a list of task.
  57.                                 if( pxResources->xHandleTasks[i] != NULL )
  58.                                         vTaskResume( pxResources->xHandleTasks[i] );
  59.                         }
  60.                        
  61.                         xLastWakeTime = xTaskGetTickCount();
  62.                         for( i = 0 ; i < 60 ; i++ ){                                                            // Task operation for a 30 seconds.
  63.                                 if( xQueueReceive( pxResources->xQueueButtonKey, &xMessageKey1, 0 ) ) i = 60;
  64.                                 if( xQueuePeek( pxResources->xQueueGetLedsKey, &xMessageKey2, 1000 ) )   i = 0;
  65.                                 vTaskDelayUntil( &xLastWakeTime, 1000 );
  66.                         }
  67.                        
  68.                         for( i = 7 ; i >=0 ; i-- ){                                                                     // Suspend a list of task waiting mutexes in REVERSE ORDER(respect vTaskResume()).
  69.                                 if( pxResources->xHandleTasks[i] != NULL ){
  70.                                         if( pxResources->xMutexes[i] != NULL ){
  71.                                                 while( 1 != uxQueueMessagesWaiting(pxResources->xMutexes[i]) )
  72.                                                         vTaskDelay(1);
  73.                                         }
  74.                                         vTaskSuspend( pxResources->xHandleTasks[i] );
  75.                                 }
  76.                         }
  77.                        
  78.                         set_COLS_DIR( 0 );                                                                                      // Shut-down external hardware.
  79.                         set_LEDS_DIR( ~0x0UL );
  80.                         put_LEDS( 0x0UL );     
  81.                        
  82.                         ACCS = 0;
  83.                        
  84.                 //      clock = 32Khz;                                                                                          // Slow operation, low power.
  85.                 }
  86.         }      
  87. }
  88.  
  89. #endif  // #ifndef 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.


Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #25 en: 31 de Marzo de 2009, 20:49:46 »
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:

Código: C
  1. /* Standard includes. */
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <limits.h>
  5. #include <math.h>
  6. #include <string.h>
  7.  
  8. /* Device description includes */
  9. #include "P24FJ64GA004.h"
  10.  
  11. /* Scheduler includes. */
  12. #include "FreeRTOS.h"
  13. #include "task.h"
  14. #include "queue.h"
  15. #include "semphr.h"
  16.  
  17. /* Application includes*/
  18. #include ".\utils\Hardware.h"
  19. #include ".\utils\LEDs.h"
  20. #include ".\utils\GLCD.h"
  21. //#include "Filters.h"
  22. //#include "CORDIC.h"
  23.  
  24. #include ".\Tasks\TaskAcce.h"
  25. #include ".\Tasks\TaskPutLeds.h"
  26. #include ".\Tasks\TaskGetLeds.h"
  27. #include ".\Tasks\TaskButton.h"
  28. #include ".\Tasks\TaskSpeaker.h"
  29. #include ".\Tasks\TaskWakeUp.h"
  30. #include ".\Tasks\TaskWatch.h"
  31. #include ".\Tasks\TaskGraphics.h"
  32.  
  33. #include ".\Generated Files\VDIInit\init_PIC24FJ64GA004.sinit_vi.h"
  34.  
  35.  
  36. /*-----------------------------------------------------------*/
  37. // Create Tasks handlers, these must be placed as a GLOBAL varibles.
  38. xTaskAcceResources      xTaskAcce1;
  39. xTaskPutLedsResources xTaskPutLeds1;
  40. xTaskGetLedsResources xTaskGetLeds1;
  41. xTaskButtonResources xTaskButton1;
  42. xTaskSpeakerResources xTaskSpeaker1;
  43. xTaskWakeUpResources xTaskWakeUp1;
  44. xTaskGraphicsResources xTaskGraphics1;
  45. xTaskWatchResources xTaskWatch1;
  46.  
  47. /*      Create the demo tasks then start the scheduler.  */
  48. int main( void ){      
  49.        
  50.         /* Configure any hardware required for this demo. */
  51.         VisualInitialization();
  52.  
  53.  
  54.  
  55.         /* Create the test tasks defined within this file. */
  56.        
  57.         // Productor role tasks
  58.         xTaskAcce1.uxPriority = 5;
  59.         if( startAcceTask(&xTaskAcce1) )        return -1;
  60.  
  61.         xTaskGetLeds1.uxPriority = 1;
  62.         if( startGetLedsTask(&xTaskGetLeds1) )  return -1;
  63.  
  64.         xTaskButton1.uxPriority = 1;
  65.         if( startTaskButton( &xTaskButton1 ) )  return -1;
  66.        
  67.         // Processor role tasks
  68.         xTaskWatch1.xQueueSound = xTaskGetLeds1.xQueueSound;
  69.         xTaskWatch1.xQueueKey = xTaskGetLeds1.xQueueKey;
  70.         xTaskWatch1.uxPriority = 2;
  71.         if( startTaskWatch( &xTaskWatch1 ) )    return -1;
  72.  
  73.         xTaskGraphics1.xQueuePrimitive = xTaskWatch1.xQueuePrimitive;
  74.         xTaskGraphics1.uxPriority = 3;
  75.         if( startTaskGraphics( &xTaskGraphics1 ) )      return -1;
  76.        
  77.         // Consumer role tasks 
  78.         xTaskSpeaker1.xMutexIO = xTaskButton1.xMutexIO;
  79.         xTaskSpeaker1.xQueueSound = xTaskWatch1.xQueueSound;
  80.         xTaskSpeaker1.uxPriority = 4;
  81.         xTaskSpeaker1.uxPriorityHigh = 7;
  82.         if( startTaskSpeaker( &xTaskSpeaker1 ) )        return -1;
  83.        
  84.         xTaskPutLeds1.xMutexLeds = xTaskGetLeds1.xMutexLeds;
  85.         xTaskPutLeds1.xQueueMove = xTaskAcce1.xQueue;
  86.         xTaskPutLeds1.xQueueDisp = xTaskGraphics1.xQueueDisp;
  87.         xTaskPutLeds1.uxPriority = 6;
  88.         if( startPutLedsTask(&xTaskPutLeds1) )  return -1;
  89.        
  90.         // Control tasks
  91.         xTaskWakeUp1.xHandleTasks[7] = xTaskGetLeds1.xHandle;   xTaskWakeUp1.xMutexes[7] = xTaskPutLeds1.xMutexLeds;            // Productor Task
  92.         xTaskWakeUp1.xHandleTasks[6] = xTaskAcce1.xHandle;              xTaskWakeUp1.xMutexes[6] = NULL;                                                        // Productor Task
  93.         xTaskWakeUp1.xHandleTasks[5] = NULL;                                    xTaskWakeUp1.xMutexes[5] = NULL;                                                       
  94.         xTaskWakeUp1.xHandleTasks[4] = xTaskWatch1.xHandle;             xTaskWakeUp1.xMutexes[4] = NULL;                                                        // Processor Task
  95.         xTaskWakeUp1.xHandleTasks[3] = xTaskGraphics1.xHandle;  xTaskWakeUp1.xMutexes[3] = NULL;                                                        // Processor Task
  96.         xTaskWakeUp1.xHandleTasks[2] = NULL;                                    xTaskWakeUp1.xMutexes[2] = NULL;
  97.         xTaskWakeUp1.xHandleTasks[1] = xTaskSpeaker1.xHandle;   xTaskWakeUp1.xMutexes[1] = xTaskSpeaker1.xMutexIO;                      // Consumer Task
  98.         xTaskWakeUp1.xHandleTasks[0] = xTaskPutLeds1.xHandle;   xTaskWakeUp1.xMutexes[0] = xTaskPutLeds1.xMutexLeds;            // Consumer Task
  99.        
  100.         xTaskWakeUp1.xQueueButtonKey = xTaskButton1.xQueueKey;
  101.         xTaskWakeUp1.xQueueGetLedsKey = xTaskGetLeds1.xQueueKey;
  102.         xTaskWakeUp1.uxPriority = 8;
  103.         if( startTaskWakeUp( &xTaskWakeUp1 ) )  return -1;     
  104.        
  105.  
  106.  
  107.                
  108.         /* Finally start the scheduler. */
  109.         vTaskStartScheduler();
  110.  
  111.         return 0;
  112. }

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:

Código: C
  1. typedef struct xTaskXXResources{       
  2. xTaskHandle xHandle;                            // Task handler.
  3. unsigned portBASE_TYPE uxPriority;              // Task priority.
  4. xSemaphoreHandle xMutexXX;                      // Mutex handler.
  5. xQueueHandle xQueueXX;                          // Queue handler.
  6. }xTaskXXResources;

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

Código: C
  1. int vStartTaskXX( xTaskXXResources *pxResources ){
  2. if( pxResources->xMutexXX == NULL ){
  3.         if( NULL == ( pxResources->xMutexXX = xSemaphoreCreateMutex() ) )
  4.                 return -1;
  5.         vQueueAddToRegistry( pxResources->xMutexXX, ( signed portCHAR * )"xMutexXXName" );
  6. }              
  7.        
  8. if( pxResources->xQueueXX == NULL ){
  9.         if( NULL == ( pxResources->xQueueXX = xQueueCreate( 5, sizeof( xMessageXX ) ) ) )
  10.                 return -1;
  11.         vQueueAddToRegistry( pxResources->xQueueXX, ( signed portCHAR * )"xQueueXXName" );
  12. }
  13.        
  14. if( pdPASS != xTaskCreate( vTaskXX, ( signed portCHAR * ) "TaskName", configMINIMAL_STACK_SIZE, pxResources, pxResources->uxPriority, &pxResources->xHandle ) )
  15.         return -1;
  16.                
  17. return 0;
  18. }


Desconectado jgpeiro06

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 276
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #26 en: 31 de Marzo de 2009, 21:06:59 »
y FIN

Conclusiones
Este gadjet 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 gatjets en el futuro.


Algunos Links
Proyecto: ManualLedScanner, versión 2.45. José Antonio García Peiró. 2009.
   http://rapidshare.com/files/215956617/ManualLedScanner_2v46.rar

   

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

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

Me despido hasta el próximo proyecto.


Desconectado kain589

  • Colaborador
  • PIC18
  • *****
  • Mensajes: 324
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #27 en: 31 de Marzo de 2009, 21:16:47 »
Muy buen proyecto, creo que me quedo corto, un "proyectazo" y muy bien documentado, se ve espectacular y util para programas complejos. Espero con el tiempo, despues de todo lo que aún me queda por hacer, poder hacer algo en Rtos y seguro que este post servira de ayuda. Muchas gracias y suerte con los proximos proyectos
Saludos desde Córdoba, españa

Desconectado MLO__

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 4581
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #28 en: 31 de Marzo de 2009, 21:55:07 »
clap clap clap clap clap clap clap clap

Lo digo nuevamente: EXCELENTE TRABAJO

Recibe mis humildes agradecimientos por el hilo. Ya estoy en la tarea de comprenderlo -no he usado nunca el RTOS, pero si que me he motivado a hacerlo-.
El papel lo aguanta todo

Desconectado MGLSOFT

  • Moderador Local
  • DsPIC33
  • *****
  • Mensajes: 7912
Re: Emulador de un Casio F91W basado en FreeRTOS
« Respuesta #29 en: 31 de Marzo de 2009, 23:29:02 »
FA BU LO SO
No quise molestarte antes con comentarios para no desarmarte el hilo...
Decididamente esperare tu proximo proyecto...
Este ya tiene su chincheta... :mrgreen:
Todos los dias aprendo algo nuevo, el ultimo día de mi vida aprenderé a morir....
Mi Abuelo.


 

anything