1. Objetivos
  2. Requerimientos
  3. Procedimiento
    1. Descripción de Hardware.
    2. Qsys.
    3. Nios II.
    4. UCOS II.
    5. Secuencia de Sprite.
    6. Sintesis de Audio.
  4. Descargas
  5. Glosario
  6. Otros Resultados.
  7. Ejemplo de Sprites.

 

 

Objetivos:

  • Diseñar una plantilla general para el diseño de Juegos Retro Usando Verilog HDL.
  • Aprender a Diseñar Escenarios para Juegos Retro.
  • Hacer animaciones usando Sprites sobre la pantalla VGA usando Verilog HDL.
  • Hacer sintesis de sonido para juegos retro usando Divisores de frecuencia Verilog HDL.
  • Implementar Nios II para facilitar la logica y programación de un Juego Retro.
  • Implementar Acceleradores Hardware.
  • Implementar un sistema operativo de Tiempo Real(UCOS-II).
  • Implementar Controles SNES.
  • Dar la base inicial para hacer un Juegro Retro Sobre la DE0-NANO u otra FPGA.
 

Requerimientos: 

 
  1. VGA Retro Game Introduction - DE0-NANO

  2. SNES Controller Module - DE0-NANO-SOC

 

Procedimiento Base:

 Este procedimiento tiene como principal objetivo dar una serie de pasos para crear una plantilla general con la cual se puedan diseñar juegos retro usando Verilog HDL, Nios II, y C/C++. Usted es responsable de analizar y estudiar serie de pasos dados en este manual, para de esta manera absorber por medio de la practica las bases necesarias para diseñar juegos retro utilizando esta metodologia.
 
Como resultado de esta practica usted obtendra lo que se ve en el siguiente video
 
Resultados esperados
Tambien es importante resaltar que en base a esta practica algunas personas han decidido hacer sus propias versiones de video juegos retro usando diferentes sprites, sonidos y animaciones, las cuales usted podra ver en  Otros Resultados.
 
Nota: Recuerde que antes de iniciar esta serie de pasos usted debio haber entendido a cabalidad el siguiente manual https://fpgalover.com/projects/simple-video-game, ya que sin esto le sera mas dificil de enteder lo que se explica en este paso a paso. En adición, en ese manual aprendera a genera los sprites necesarios para esta practica.

  • Descargue la Download this file (generado_ram_imag (1).m)plantilla base con la cual se trabajara este laboratorio/pfinal.
  • Guardela en un lugar cercano a la Ruta "C:\" sin espacios, de lo contrario tendra dolores de cabeza.
    • Recuerde manejar todo como administrador tambien.
  • Debe tener montado el circuito de VGA ya montado a la FPGA.
  • Abra la plantilla, en ella vera que estan instanciados los modulos del laboratorio 7 de sincronizacion de video.
  • Ahora descargue el siguiente Download this file (generado_ram_imag (1).m)modulo, el cual es el encargado de tomar los diferentes botones de los controles de SNES.
    Agregue el modulo en esta plantilla(copielo) e Instancie el modulo dos veces de la siguiente manera:

    En este caso los dos controles de SNES se conectaran al GPIO_B, a diferentes pines como usted aprecia en la descripcion de hardware.
  • Ahora sintetice co compile el diseño, si todo esta conectado correctamente, usted podra ver en los LED[7:0] los botones menos significaivos del controls de SNES1 y 2, si pulsa el KEY[0] vera el control 2, si lo deja sin pulsar vera los botones del control1.
    Nota: recuerde que el si mira debajo de la tarjeta el pin 1 es el que tiene la soldadura cuadrada, tenga cuidado con esto al hacer las conexiones.
  • Ahora descargue el script en Matlab "Download this file (generado_ram_imag (1).m)generado_ram_imag_colors" y abralo para que generemos el .v para las siguientes imagenes, utilice como transparent "12'h0f0" o verde puro
  • Una vez generado los cuatro archivos Verilog para cada render, agreguelos e instancielos de la siguiente manera en la plantilla principal.



  • Si lo sintetizo bien, deberia ver cuatro gokus diferentes en la pantalla
  • Ahora descargue el siguiente Download this file (generado_ram_imag (1).m)modulo(Divisor de frecuencia) e instancielo en la plantilla
     
     
  • Ahora vamos a modificar el codigo para agregar una descripcion del procesador Nios II, para esto usted va a descargar el siguiente Download this file (generado_ram_imag (1).m)archivo
  • Una vez descargado, debe copiarlo y argegarlo a la plantilla como cualquier otro modulo.
  • Ahora abra el Qsys y con este abra el archivo "mi_nios.qsys", una vez abierto vaya a la plantilla principal, si se da cuenta cada imagen agregada tiene un valor fijo para la escala, el on_off, la posicion en x y y, la gracia de agregar el nios es modificar estos valores desde software de tal manera que sea mas facil para usted hacer modificaciones sobre el juego.
  • Para esto vamos a la ventana del Qsys y agregue un PIO de 4 bits de salida que usted llamara scale1 y conectelo respectivamente como lo hice en el lab6, al clk, al reset, el data_master de la cpu y exporte los pines por medio de Conduit, como se puede apreciar en la siguiente imagen:



  • Haga esto mismo 3 veces mas, llame el puerto scale2, scale3, y scale4, luego oprima en  System-> Assign Base Addresses



  • Ahora agregue otros 4 puertos de salida 1 bit cada uno llamados on_off1, on_off2, on_off3, on_off4



  • Ahora agregue otros 4 puertos de salida de 11 bit cada uno llamados goku_x1, goku_x2, goku_x3, goku_x4



  • Ahora agregue otros 4 puertos de salida de 11 bit cada uno llamados goku_y1, goku_y2, goku_y3, goku_y4



  • Ahora agregue otros 2 puertos de entrada de 12 bit cada uno llamados SNES_control1, y SNES_control2.


  • Ahora agregue un puerto de salida de 32 bits que se llame Nota.


  • Ahora guarde el Qsys y Genere, una vez generado debe instanciar el procesador Nios de la siguiente manera en la plantilla principal
  • Una vez se tenga todo instanciado y unido correctamente, compile todo el hardware para generar el .sof.
    Si tiene problemas puede bajar la solucion hasta el momento: Solucion hasta el momento(recuerde guardarla en una ruta cercana a C:\ sin espacios)
    Nota: La solucion tiene dos canales de Audio, usted puede usar uno para efectos de sonido del juego y otro para el sonido de fondo, uno esta conectado a GPIO_B[33] y el otro a GPIO_B[32].


  • Ahora abra el Nios II Eclipse como administrador y cree un proyecto en base a la descripcion de hardware que acaba de generar y llame este proyecto "Ejemplito", creelo de la siguiente manera:



  • Es muy importante que seleccione "Hello MicroC/OS-II", vamos a trabajar con multitasking para hacer el juego y el MicroC/OS-II es un Sistema operativo que se pondra en el procesador de tiempo real para poder realizar multitasking.
  • Ahora programe el .sof y compile el sofwtare del procesador para probar que funciona de la siguiene manera:


  • Compilado correctamente el software debera aparecer un nuevo archivo en la carpeta del proyecto eclipse "ejemplito" llamado ejemplito.elf


  • ahora de click derecho sobre la carpeta y programe el procesador Nios II:



  • Una vez programado deberia aparecer en la consola lo siguiente 


  • Ahora procedamos analizar el codigo C que esta como base del proyecto:


  • Para crear multiasking usando el procesador Nios II, estamos usando el MicroC/OS-II, en el main siempre se deben crear las tareas de la forma como se muestra anteriormente y luego se ejecuta el sistema operativo.
  • Cada tarea debe tener prioridad y el OS le dara mas tiempo entre mayor prioridad tenga, en el caso de que las dos tengan igual numero de prioridad estas seran igual de importantes, la estructura de las tareas se explica acontinuacion:


  • Ahora modifiquemos el codigo para prender y apagar las imagenes que estan en la FPGA usando el procesador Nios II que esta enlazado por medio de puertos a cada una de
    estas.
    #include <stdio.h>
    #include "includes.h"
    
    #include <system.h>
    #include "io.h"
    
    /* Definition of Task Stacks */
    #define   TASK_STACKSIZE       2048
    OS_STK    task1_stk[TASK_STACKSIZE];
    OS_STK    task2_stk[TASK_STACKSIZE];
    
    /* Definition of Task Priorities */
    
    #define TASK1_PRIORITY      1
    #define TASK2_PRIORITY      2
    
    
    void set_pos_image(int basex, int basey,int x, int y){
     IOWR(basex,0,x);
     IOWR(basey,0,y);
    }
    
    void init_game(void){
     // INICIALIZO LA ESCALA DE CADA IMAGEN
     IOWR(SCALE1_BASE,0,0);
     IOWR(SCALE2_BASE,0,0);
     IOWR(SCALE3_BASE,0,0);
     IOWR(SCALE4_BASE,0,0);
     //APAGAO CADA IMAGEN AL INICIO DEL JUEGO A MENOS DE QUE QUIERA MOSTRARLAS
     IOWR(ON_OFF1_BASE,0,0);
     IOWR(ON_OFF2_BASE,0,0);
     IOWR(ON_OFF3_BASE,0,0);
     IOWR(ON_OFF4_BASE,0,0);
     //INICIALIZO LA POSICION DE LAS IMAGENES
     set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,0, 0);
     set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,30, 0);
     set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,60, 0);
     set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,90, 0);
    }
    
    /* Prints "Hello World" and sleeps for three seconds */
    void task1(void* pdata)
    {
      unsigned char toggle_image1=0;
      while (1)
      { 
       toggle_image1 =toggle_image1^ 1;
    
       IOWR(ON_OFF1_BASE,0,toggle_image1);
    
       //tiempo de retrazo
          OSTimeDlyHMSM(0, 0, 3, 0);
      }
    }
    /* Prints "Hello World" and sleeps for three seconds */
    void task2(void* pdata)
    {
      unsigned char toggle_image2=0;
      while (1)
      { 
       toggle_image2 =toggle_image2^ 1;
    
       IOWR(ON_OFF2_BASE,0,toggle_image2);
    
        OSTimeDlyHMSM(0, 0, 5, 0);
      }
    }
    
    
    /* The main function creates two task and starts multi-tasking */
    int main(void)
    {
      
     //INICIALIZO JUEGO ANTES DE LANZARLO
     init_game();
    
      OSTaskCreateExt(task1,
                      NULL,
                      (void *)&task1_stk[TASK_STACKSIZE-1],
                      TASK1_PRIORITY,
                      TASK1_PRIORITY,
                      task1_stk,
                      TASK_STACKSIZE,
                      NULL,
                      0);
                  
                   
      OSTaskCreateExt(task2,
                      NULL,
                      (void *)&task2_stk[TASK_STACKSIZE-1],
                      TASK2_PRIORITY,
                      TASK2_PRIORITY,
                      task2_stk,
                      TASK_STACKSIZE,
                      NULL,
                      0);
      OSStart();
      return 0;
    }
    

 

  • Programe y analice el anterior codigo, se dara cuenta que la tarea 1 apaga y prende cada 3 segundos la imagen 1 de goku, y la tarea 2 prende y apaga la imagen de goku cada 5 segundos.(Modifiquelo apra que sea cada 10 ms)
  • Ahora modifiquemos las tareas para que hagan lo siguiente
    void task1(void* pdata)
    {
      unsigned char animation=0;
      // inicializo todas las imagenes en el mismo punto
     set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,0, 0);
     set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,0, 0);
     set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,0, 0);
     set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,0, 0);
      while (1)
      { 
       animation++;
       if(animation>3){animation=0;}
       switch(animation){
       case 0:  IOWR(ON_OFF1_BASE,0,1);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case  1:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,1);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case 2:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,1);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case 3:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,1);
          break;
       }
    
    
    
       //tiempo de retrazo
          OSTimeDlyHMSM(0, 0, 0, 100);
      }
    }
    
    void task2(void* pdata)
    {
      unsigned int pos_x=0,pos_y=0;
      while (1)
      {
       pos_x++;
       pos_y++;
       set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
    
        OSTimeDlyHMSM(0, 0, 0, 70);
      }
    }
  • Programe y analice el codigo, si se da cuenta una tarea esta encargada de animar a Goku y la otra tarea esta encargada de mover al goku.
  • Ahora modifiquemos el prorgama para que goku se mueva con las direcciones del control 1 usando el task2
    void task2(void* pdata)
    {
      unsigned int pos_x=0,pos_y=0;
      unsigned short snes1_control=0;
      unsigned char buttons_control1[12];
      while (1)
      {
    
       snes1_control=IORD(SNES_CONTROL1_BASE,0);
       buttons_control1[0]=snes1_control & 1;// B
       buttons_control1[1]=(snes1_control & 2)>>1;// Y
       buttons_control1[2]=(snes1_control & 4)>>2;// Start
       buttons_control1[3]=(snes1_control & 8)>>3;// select
       buttons_control1[4]=(snes1_control & 16)>>4;//UP
       buttons_control1[5]=(snes1_control & 32)>>5;//down
       buttons_control1[6]=(snes1_control & 64)>>6;//left
       buttons_control1[7]=(snes1_control & 128)>>7;//right
       buttons_control1[8]=(snes1_control & 256)>>8;//A
       buttons_control1[9]=(snes1_control & 512)>>9;//X
       buttons_control1[10]=(snes1_control & 1024)>>10;//L
       buttons_control1[11]=(snes1_control & 2048)>>11;//R
    
       if(buttons_control1[4]==0){
       pos_y=pos_y-10;
       }
       else if(buttons_control1[5]==0){
       pos_y=pos_y+10;
       }
    
       if(buttons_control1[6]==0){
          pos_x=pos_x-10;
       }
       else if(buttons_control1[7]==0){
       pos_x=pos_x+10;
       }
    
       set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
    
        OSTimeDlyHMSM(0, 0, 0, 70);
      }
    }
  • Si modifica bien la tarea 2 vera a goku moverse usando las flechas del control 1.
  • Ahora modifique el codigo para lo siguiente:
    #include <stdio.h>
    #include "includes.h"
    
    #include <system.h>
    #include "io.h"
    
    /* Definition of Task Stacks */
    #define   TASK_STACKSIZE       2048
    OS_STK    task1_stk[TASK_STACKSIZE];
    OS_STK    task2_stk[TASK_STACKSIZE];
    OS_STK    task3_stk[TASK_STACKSIZE];
    
    /* Definition of Task Priorities */
    
    #define TASK1_PRIORITY      1
    #define TASK2_PRIORITY      2
    #define TASK3_PRIORITY      3
    
    
    void set_pos_image(int basex, int basey,int x, int y){// FUNCION PARA POSICIONAR IMAGENES
     IOWR(basex,0,x);
     IOWR(basey,0,y);
    }
    
    void init_game(void){// FUNCION PARA INICIALIZAR JUEGO
     // INICIALIZO LA ESCALA DE CADA IMAGEN
     IOWR(SCALE1_BASE,0,0);
     IOWR(SCALE2_BASE,0,0);
     IOWR(SCALE2_BASE,0,0);
     IOWR(SCALE2_BASE,0,0);
     //APAGAO CADA IMAGEN AL INICIO DEL JUEGO A MENOS DE QUE QUIERA MOSTRARLAS
     IOWR(ON_OFF1_BASE,0,0);
     IOWR(ON_OFF2_BASE,0,0);
     IOWR(ON_OFF3_BASE,0,0);
     IOWR(ON_OFF4_BASE,0,0);
     //INICIALIZO LA POSICION DE LAS IMAGENES
     set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,0, 0);
     set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,30, 0);
     set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,60, 0);
     set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,90, 0);
    }
    
    
    // VARIABLES GLOBALES DE BOTONES
    unsigned char buttons_control1[12];
    unsigned char buttons_control2[12];
    
    
    void task1(void* pdata)// TASK PARA ANIMAR A goku
    {
      unsigned char animation=0;
      // inicializo todas las imagenes en el mismo punto
     set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,0, 0);
     set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,0, 0);
     set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,0, 0);
     set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,0, 0);
      while (1)
      { 
       animation++;
       if(animation>3){animation=0;}
       switch(animation){
       case 0:  IOWR(ON_OFF1_BASE,0,1);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case  1:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,1);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case 2:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,1);
       IOWR(ON_OFF4_BASE,0,0);
          break;
       case 3:    IOWR(ON_OFF1_BASE,0,0);
       IOWR(ON_OFF2_BASE,0,0);
       IOWR(ON_OFF3_BASE,0,0);
       IOWR(ON_OFF4_BASE,0,1);
          break;
       }
    
    
    
       //tiempo de retrazo
          OSTimeDlyHMSM(0, 0, 0, 100);
      }
    }
    
    
    void task2(void* pdata)// TASK PARA ADQUIRIR BOTONES
    {
      unsigned short snes1_control=0;
    
      while (1)
      {
    
       snes1_control=IORD(SNES_CONTROL1_BASE,0);
       buttons_control1[0]=snes1_control & 1;// B
       buttons_control1[1]=(snes1_control & 2)>>1;// Y
       buttons_control1[2]=(snes1_control & 4)>>2;// Start
       buttons_control1[3]=(snes1_control & 8)>>3;// select
       buttons_control1[4]=(snes1_control & 16)>>4;//UP
       buttons_control1[5]=(snes1_control & 32)>>5;//down
       buttons_control1[6]=(snes1_control & 64)>>6;//left
       buttons_control1[7]=(snes1_control & 128)>>7;//right
       buttons_control1[8]=(snes1_control & 256)>>8;//A
       buttons_control1[9]=(snes1_control & 512)>>9;//X
       buttons_control1[10]=(snes1_control & 1024)>>10;//L
       buttons_control1[11]=(snes1_control & 2048)>>11;//R
       snes1_control=IORD(SNES_CONTROL2_BASE,0);
       buttons_control2[0]=snes1_control & 1;// B
       buttons_control2[1]=(snes1_control & 2)>>1;// Y
       buttons_control2[2]=(snes1_control & 4)>>2;// Start
       buttons_control2[3]=(snes1_control & 8)>>3;// select
       buttons_control2[4]=(snes1_control & 16)>>4;//UP
       buttons_control2[5]=(snes1_control & 32)>>5;//down
       buttons_control2[6]=(snes1_control & 64)>>6;//left
       buttons_control2[7]=(snes1_control & 128)>>7;//right
       buttons_control2[8]=(snes1_control & 256)>>8;//A
       buttons_control2[9]=(snes1_control & 512)>>9;//X
       buttons_control2[10]=(snes1_control & 1024)>>10;//L
       buttons_control2[11]=(snes1_control & 2048)>>11;//R
    
        OSTimeDlyHMSM(0, 0, 0, 70);
      }
    }
    
    
    void task3(void* pdata)// TASK PARA MOVER A GOKU
    {
      unsigned int pos_x=0,pos_y=0;
    
      while (1)
      {
       if(buttons_control1[4]==0){
          pos_y=pos_y-10;
       }
       else if(buttons_control1[5]==0){
       pos_y=pos_y+10;
       }
    
       if(buttons_control1[6]==0){
       pos_x=pos_x-10;
       }
       else if(buttons_control1[7]==0){
       pos_x=pos_x+10;
       }
    
       set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
    
    
    
        OSTimeDlyHMSM(0, 0, 0, 70);
     }
    }
    
    /* The main function creates two task and starts multi-tasking */
    int main(void)
    {
      
     //INICIALIZO JUEGO ANTES DE LANZARLO
     init_game();
    
      OSTaskCreateExt(task1,
                      NULL,
                      (void *)&task1_stk[TASK_STACKSIZE-1],
                      TASK1_PRIORITY,
                      TASK1_PRIORITY,
                      task1_stk,
                      TASK_STACKSIZE,
                      NULL,
                      0);
                  
                   
      OSTaskCreateExt(task2,
                      NULL,
                      (void *)&task2_stk[TASK_STACKSIZE-1],
                      TASK2_PRIORITY,
                      TASK2_PRIORITY,
                      task2_stk,
                      TASK_STACKSIZE,
                      NULL,
                      0);
    
      OSTaskCreateExt(task3,
                        NULL,
                        (void *)&task3_stk[TASK_STACKSIZE-1],
                        TASK3_PRIORITY,
                        TASK3_PRIORITY,
                        task3_stk,
                        TASK_STACKSIZE,
                        NULL,
                        0);
    
      OSStart();
      return 0;
    }
  • Si analiza y ejecuta el codigo anterior, se dara cuenta que existen ahora tres tasks, uno para animar a goku, el otro para adquirir los botones de los dos controles de SNES, y el otro para mover a goku de acuerdo al control 1 del snes.
    //Ahora agreguemos un task mas para el audio del video juego
    void task4(void* pdata)// TASK PARA GENERAR AUDIO
    {
    
     unsigned int nota_div_freq[32]={56817,56817,47777,56817,42565,56817,37920,42565,47777,47777,37920,47777,31887,47777,37920,47777,63775,63775,50619,63775,47777,63775,42565,47777,71585,71585,56817,71585,50619,71585,47777,50619};
     unsigned char count=0;
     //init div_freq
     IOWR(NOTA_BASE,0,0);
      while (1)
      {
       count++;
       if(count>31){
       count=0;
       }
        IOWR(NOTA_BASE,0,nota_div_freq[count]);
    
          
      if(buttons_control1[0]==0){// si se oprime B
    		   OSTimeDlyHMSM(0, 0, 0, 170/4);
      }
      else if(buttons_control1[1]==0){// si se oprime Y
    			   OSTimeDlyHMSM(0, 0, 0, 170/2);
       }
       else{// si no se oprime nada
    		   OSTimeDlyHMSM(0, 0, 0, 170);
       }
     }
    }
  • Si agrego bien el task4, debe ahora sonar en paralelo una pequeña cancion repetitivamente, recuerde que el puerto nota[31:0] estaconectado por meido del Nios al divisor de frecuencia, con el cual usted puede variar para genear diferentes frecuencias por lo cual puede generar canciones o tonos diferentes.
    Recuerde la formula del divisor de frecuencia IN= (50Mhz/(2*fout))-1
  • El codigo completo en C esta en el Download this file (generado_ram_imag (1).m)siguiente link.
  • Modifique el task3, para que se vea como el siguiente y analice lo que sucede.
    void task3(void* pdata)// TASK PARA MOVER A GOKU
    {
      unsigned int pos_x=0,pos_y=0;
    
      while (1)
      {
       if(buttons_control1[4]==0){
       pos_y=pos_y-10;
       }
       else if(buttons_control1[5]==0){
       pos_y=pos_y+10;
       }
    
       if(buttons_control1[6]==0){
       pos_x=pos_x-10;
       }
       else if(buttons_control1[7]==0){
       pos_x=pos_x+10;
       }
            if (buttons_control1[8]==0)
       {
       int i;
       for(i=0;i<4;i++){
     IOWR(SCALE1_BASE,0,i);
     IOWR(SCALE2_BASE,0,i);
     IOWR(SCALE3_BASE,0,i);
     IOWR(SCALE4_BASE,0,i);
     OSTimeDlyHMSM(0, 0, 0, 10);
       }
       IOWR(SCALE1_BASE,0,0);
       IOWR(SCALE2_BASE,0,0);
       IOWR(SCALE3_BASE,0,0);
       IOWR(SCALE4_BASE,0,0);
       }
                if(buttons_control1[0]==0){
       int i;
       for(i=0;i<10;i++){
          pos_x=pos_x+10;
         set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
       }
       }
       if(buttons_control1[1]==0){
       int i;
       for(i=0;i<10;i++){
       pos_x=pos_x-10;
       set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
    
       }
       }
    
    
    
       set_pos_image(GOKU_X1_BASE,GOKU_Y1_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X2_BASE,GOKU_Y2_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X3_BASE,GOKU_Y3_BASE,pos_x, pos_y);
        set_pos_image(GOKU_X4_BASE,GOKU_Y4_BASE,pos_x, pos_y);
    
    
        OSTimeDlyHMSM(0, 0, 0, 70);
     }
    }

 

Resultados:


 
Enhorabuena!! usted tiene la base para empezar ha diseñar su propio juego Retro!
 
Nota: cuando modifique el hardware para agregar las nuevas imagenes y los puertos nuevos al nios II debe generar de nuevo el hw, como hizo cambios en el hw cuando vaya de nuevo al eclipse, recuerde hacer "clean en las dos carpetas" y luego generar el BSP en la segunda carpeta, depues si construir el software, de lo contrario el proyecto Eclipse no tomara los cambios realizados en el hw y vera todo como error.
Nota: La solucion tiene dos canales de Audio, usted puede usar uno para efectos de sonido del juego y otro para el sonido de fondo, uno esta conectado a GPIO_B[33] y el otro a GPIO_B[32], el pin GPIO_B[33] usa los Beep, Beep2 y el GPIO_B[32] usa el Beep3 y Beep4.
Hay cuatro canales de audio en total enviados al canal estereo de salida de la FPGA.
Beep==> Canal 1 ==> L
Beep2==> Canal 2 ==>L
Beep3==> Canal 3  ==>R
Beep4==> Canal 4 ==>R
 
El siguiente Download this file (generado_ram_imag (1).m)codigo contiene la cancion de mario cuandop se oprime la tecla A y la de tetris cuando se oprime la tecla X, usando la funcion Beep() de microsoft, si usted busca Beep() songs C/C++ encontrara varias canciones.
 
 
 
Ref1: 
http://en.wikipedia.org/wiki/Parallax_scrolling
 
Ref2:
youtube.com/watch?v=ltuRuGM271Q
 
 

Descargas

 
Todos los archivos necesarios para este proyecto puede encontrarlos en:
 
 

 Glosario:

 

Aqui pueden encontrar muchos SPRITES : http://spritedatabase.net/ ó http://www.spriters-resource.com/snes/ miren este http://spritedatabase.net/file/8612
 
Editor de Sprites :http://www.xnview.com/en/xnview/#downloads
Download this file (generado_ram_imag (1).m)Manual pare redimensionar sprites correctamente.
 
Recomendaciones: Si el la imagen es grande, usen la version "Download this file (generado_ram_imag (1).m)generado_ram_imag.", si no es muy grande usen "Download this file (generado_ram_imag (1).m)generado_ram_imag_colors_v2", y si es grande "Download this file (generado_ram_imag (1).m)generado_ram_imag_colors"
Para animaciones usen "Download this file (generado_ram_imag (1).m)generado_ram_imag_colors" ó usen el visto en https://fpgalover.com/projects/simple-video-game.
 
Ejemplo de crear un arbol con fondos y texturas simples: 
 

Otros Resultados

 
 
 
 
 
 
 

Sprites Ejemplos (https://spritedatabase.net/)

 
 
 
 
 
 
 

 

 

 

 

 
 
Written by Holguer A. Becerra
 
 
 
 
 
Requerimientos:
  • DE0-NANO
  • USB-UART(solo para parte 3)
  • Python 2.7 ó superior.
 
Objetivos:
  • Dar una introducción a los conceptos de Multitasking, Scheduling y Context Switching.
  • Introducir al estudiante al mundo de los sistemas operativos en tiempo real.
  • Hacer descripción de hardware basica compatible con RTOS(Real-Time Operating Systems).
  • Correr un sistema operativo de tiempo real sobre el soft-core Nios II.
  • Transferir datos de acceleración por medio de un conversor UART-USB a el PC.
  • Usar Python para adquirir y graficar datos de acceleración.
 
  1. Introducción.
  2. Construir Hardware y Software para FreeRTOS.
  3. Accelerometro and UART en FREERTOS.
    1. VPython para adquirir graficar datos de accelerometro por UART.
 
 
Parte 1(Introducción a FREERTOS):
 
Hoy en día vivimos en un mundo rodeado de computadores que ayudan a tener una vida mucho mas sencilla, incluso gracias a un computador usted tiene hoy día acceso a esta información que años atrás no hubiera sido posible sin la ayuda de las ciencias de la computación. Hoy en día usted sabe que existen PC, laptops y smartphones y que estos sistemas se basan en procesadores de arquitecturas hardware CISC y ARM, pero también usted sabe que esos sistemas no podrían ser lo que son hoy en día sin un Sistema Operativo(OS) que distribuya de manera eficiente los tiempos que requiere cada aplicación para ejecutarse de manera correcta sobre la maquina en la que esta corriendo.
 
A lo largo del curso usted ha implementado el soft-core Nios II y usted se ha dado cuenta que al igual que cualquier microprocesador el ejecuta cada proceso o tarea de manera secuencial, es decir para poder pasar a otro proceso el antes debe haber terminado el anterior, esto no es solo es cuestión de este soft-core, todos los procesadores diseñados son maquinas de estados generalizadas que ejecutan código linea a linea y que antes de pasar de una linea a otra debe haber terminado la anterior, pero ahora la pregunta que usted probablemente debe hacerse es ¿Como es posible que un computador convencional ejecute varias tareas al mismo tiempo?, bueno muchos responderían que es gracias a la frecuencia del procesador... cosa que no es cierto del todo ya que usted se ha dado cuenta que el procesador por si solo no podría hacer todas las tareas que hace de manera totalmente secuencial y que hay algo mas que debe controlara los tiempos de ejecución de cada programa que esta corriendo en el computador para que todo se ejecute de manera adecuada y sin el mínimo retraso. Bueno pero ¿Qué es eso que controla esos tiempos de ejecución? la respuesta es el OS, gracias a este software las aplicaciones que corren sobre la maquina(HW) pueden aparentemente ejecutarse en "paralelo"(al mismo tiempo), pero recuerde que un procesador solo puede ejecutar una cosa a la vez... lo que sucede es que el sistema operativo se encarga de distribuir tiempos específicos para dar permiso de ejecución a la aplicación y por esto pareciera que estas tareas se ejecutaran de forma simultanea.
 
Usted conoce los sistemas operativos de propósitos general como Windows, Linux, iOS y Solaris, estos como su nombre lo dice, son "de propósito general" y son sistemas no deterministicos lo que quiere decir que no hay certeza un una formula que pueda decir con exactitud el tiempo de ejecución de cada una de las tareas, o que esta se ejecute realmente cada vez de manera estricta y en tiempos especificos, ademas que entre mas servicios se pidan al OS esto puede ocasionar retrasos en los tiempos de ejecución de las tareas que están corriendo sobre la maquina, y como su nombre lo dicen son OS de Proposito General y no para aplicarlos a solucionar problemas específicos.
 
Existen otros sistemas operativos que usted tal vez no conozca llamados sistemas operativos de tiempo real o RTOS, como FreeRTOS, uC/OS-II, uC/OS-III, RTLinux, QNX, eCOS entre otros... estos OS a diferencias del los de propósito general se aplican para cosas especificas y mantienen tiempos específicos para cada aplicación lo que asegura que no existan retrasos entre la ejecución de una tarea y otra tarea(algo muy útil para una aplicación que requiera de confiabilidad y que tenga una aplicación especifica en la industria).
 
Recordando la capa de abstracción recuerde que el OS aparte de distribuir los tiempos de ejecución de las aplicaciones también esta encargado de permitir el paso de información desde la aplicación hasta la capa HAL.
 
 
En esta practica vamos a implementar un RTOS(FreeRTOS) que se ejecutara sobre el soft-core Nios II, pero para esto usted deberá leer antes los siguientes conceptos:
Una vez leído deberá responder estas preguntas(Saldrán en el parcial):
  • ¿Qué es un kernel?.
  • ¿Qué es Multitasking?.
  • ¿Qué es single tasking?
  • Escriba dos diferencias entre Multitasking y Concurrency.
  • ¿Que es un scheduler?
  • ¿Para que se utiliza el Context Switching?
  • ¿Qué es tiempo real?
Una vez usted tenga claro los conceptos anteriores podrá seguir a la parte aplicada de esta practica.
 
 
Parte 2(Aplicación):
 
Materiales:
  1. DE0-NANO.
  2. USB-UART.
 
En esta parte implementaremos el sistema operativo de tiempo real FreeRTOS sobre el soft-core Nios II utilizando la DE0-NANO.
 
Como guía descargue la siguiente Download this file (doc.zip)documentación.
 
  1. Cree una plantilla con el System Builder de la DE0-NANO, como el que se muestra a continuación(recuerde guardarla en una ruta sin espacios y corta):
  2. Descargue el archivo IP y descomprima en la carpeta o plantilla manteniendo el nombre de la carpeta ip.
  3. Diseñe utilizando Qsys un sistema que funcione a 100MHz con las siguientes características(Puede guiarse de otro que ya haya descrito), pero asegúrese de que los nombres de los diferentes periféricos son los que se indican a continuación.
    1. Reset Vector conectado a la epcs.
    2. Exception vector conectado a la sdram.
    1. Nios II/e, llamado cpu.
    2. System ID Pheripheral, llamado sysid
    3. SDRAM Controller 32 MB, llamado sdram.
    4. JTAG UART conecte tambien al IRQ y de prioridad 5, llamado jtag_uart.
    5. Interval Timer a 1ms con interrupcion conectada al bus IRQ y de prioridad 0, llamado sys_clk.==> gracias a la interrupción con este  timer, el scheduler puede distribuir tiempos, hasta este punto usted ha creado un HW compatible con FreeRTOS, lo siguiente se agrega para hacer demostraciones usando el FreeRTOS.
      El sys_clk es el timer con el que funcionara el FreeRTOS, sin el no funcionaria el OS.
    6. UART(RS-232 serial port), llamado uart, con IRQ conectada y prioridad de 2
    7. PIO Parallel I/O de salida, llamado LED y de 8 bits.
    8. PIO Parallel I/O de entrada, llamado SW y de 4 bits.
    9. PIO Parallel I/O de entrada, llamado KEY y de 2 bits.
    10. EPCS, llamado epcs.
    11. TERASIC_SPI_3WIRE, llamado gsensor_spi. ==> vamos a utilizar el accelerometro en la parte 3 mas adelante en esta practica

      Para comprobar que su sistema esta bien diseñado en qsys descargue el archivo Download this file (doc.zip)system.qsys siguiente.


  4. Una vez tenga su sistema Qsys, agréguelo al quartus y instancielo de la siguiente manera(recuerde crear el pll_sys y recuerde el desfase de sdram_clk).
  5. Ahora genere el hardware con el Quartus.
  6. Abra Nios II Software Build Tools y cree una Nios II application and BSP  from template de tipo "Blank Project", seleccione el SOPC information y llame el proyecto como "FreeRTOS_test".
  7. Descargue el Download this file (doc.zip)archivo .zip  y copie los archivos que se encuentran en este zip, en la carpeta de aplicación del proyecto "FreeRTOS_test".
  8. Ahora haga click derecho sobre la carpeta FreeRTOS_test del proyecto y vaya a propierties y modifique el "Application includes directories" para que se vea como el siguiente(Debe ubicar cada carpeta donde esta ubicado su proyecto y cuando agregue cada directorio le saldrá un letrero que le preguntara si quiere convertir el path en un path relativo, usted debe decirle que si).
  9. Ahora cree la carpeta de aplicación un SourceFile llamado "main.c" y agregue en el encabezado del proyecto las siguientes librerías.
  10. Ahora escriba debajo de los includes, los siguientes macros que nos ayudaran a leer y escribir en los puertos LED, SW y KEY de nuestro sistema.


  11. Ahora escriba la función principal del sistema int main(), y escriba un programa que haga TOGGLE de LED 0 cada segundo si el puerto SW esta en 0, que haga toggle cada 0.5 segundos si el puerto SW esta en 1, de lo contrario que haga toggle cada 5 segundos.
  12. El anterior solo fue un ejemplo sin OS, antes de implementar el OS haremos unos ejercicios extras para luego apreciar al FreeRTOS. Ahora modifique el programa para que tambien haga TOGGLE del LED 1, de manera constante cada 200ms, y que el LED 0 haga toggle cada segundo.
  13. En el anterior programa usted se ha dado cuenta que para lograr que el LED1 y el LED0 se enciendan en diferentes tiempos usted debe distribuir los tiempos en que ellos hagan TOGGLE, usted por medio de código hizo lo que hace un scheduler, el código debió quedarle algo similar a esto.

    Si no logro hacer el punto 12, analice el código que aparece en la imagen y tendrá claro lo que es administrar tiempos.
  14. Ahora modifique el código para que haga lo siguiente:
    ->Haga Toggle del LED 0 cada 1Hz.
    ->Haga Toggle del LED 1 cada 5Hz.
    ->Haga que utilice los LED del 2 al 6 para hacer la siguiente secuencia con cambios de 10Hz entre cambio:
    100000
    010000
    001000
    000100
    000010
    000001

  15. Si hizo bien el código debió quedarle algo así:
  16. Ahora a este mismo código añada que cada 11Hz haga toggle de LED 7 y seguido de esto llame la función usleep(1000000) 
  17. ¿Que sucedió con las demás tareas?, ¿Estas siguen ejecutándose en los tiempos predispuestos anteriormente?, ¿Por qué ocurre este retraso en las otras tareas?, ¿Por qué el LED 7 hace TOGGLE cada 11 Hz? y ¿Como haría para que este proceso no afecte a las demás secuencias?.

    Como usted vio usleep(1000000) afecta la continuidad del encendió y apagado de los otros leds, el usleep() seria la representación de una función que requiera una carga computacional un poco mas alta que solo asignar valores a los LEDs o leer un puerto, si esto fuera un sistema que necesita muestrear datos a tiempos específicos, claramente al agregar una función que requiera de un costo computacional alto, esta afectaría el muestreo en tiempo real.
    Esto también demuestra que para hacer código que respete tiempos utilizando single tasking, requiere que el programador sea quien modifique los tiempos, y como se ve en el momento que se llame una función que requiera bastante tiempo se verán afectados los otros procesos.

    Para ve un ejemplo vamos a ver un vídeo que muestra como un sistemas puede ser afectado(este vídeo lo grabe hace dos años, así que el ingles no es muy bueno).


  18. Todos los ejercicios que se han hecho hasta ahora han sido sin OS, ahora veamos como seria usando el FreeRTOS, replicando el ejercicio anterior.
    Para esto usted ha debido leer el archivo "Sistemas Embebidos_RTOS.pdf" que esta dentro de la documentación de FreeRTOS.
  19. Como usted leyó en el archivo, específicamente en la pagina 12, se habla de que una tarea es un subproceso que debe gestionar el sistema operativo y que cada proceso es un pequeño main que tiene un bucle principal y ademas un delay que libera la CPU por un tiempo especifico, ademas que cada tarea tiene una prioridad para el OS.


    Ahora vamos ha replicar el experimento que se hizo sin OS, pero esta vez con FreeRTOS.

  20. Copie entre los includes y los macros de acceso a puertos lo siguiente, con el fin de definir las prioridades que va a tener cada tarea:
  21. Ahora modifique el código para que luzca como el siguiente:
  22. Compile y corra el programa sobre el Nios, ¿Que pasa con el LED0, cada cuanto hace toggle?.
  23. Ahora agregue otra tarea para que haga toggle el LED 1 cada 5Hz.
  24. ¿Cuantas tareas están siendo ejecutadas en el FreeRTOS?.
    Ahora agregue una tarea que tenga prioridad LED_2_6_TASK_PRIORITY
    Haga que utilice los LED del 2 al 6 para hacer la siguiente secuencia con cambios de 10Hz entre cambio:
    100000
    010000
    001000
    000100
    000010
    000001
  25. Ahora por ultimo agregue la tarea que causo problemas cuando no se tenia OS.
    La tarea que cada 11Hz haga toggle de LED 7 y seguido de esto llame la función vTaskDelay(1000 /portTICK_RATE_MS);
  26. Ahora responda: 
    1) ¿Cada cuanto se prende y apaga el LED 7?.
    2)¿Por qué ahora no se afectan las otras tareas?.
    3)¿Que ventajas tiene utilizar un RTOS con respecto al single tasking visto anteriormente?
    4) ¿Para que sirve el archivo FreeRTOSConfig.h? http://www.freertos.org/a00110.html
    5) ¿Cual es la tarea de mayor prioridad y cual la de menor?.
    6) ¿Qué pasaría si una tarea tuviera la misma prioridad que otra usando FreeRTOS?
    7)¿Qué ventajas tiene utilizar RTOS con Nios II y en una FPGA?, ¿Que pasaría si usted combinara los conceptos que ha aprendido a lo largo del curso HW y SW con el FreeRTOS?, ¿Qué podría hacer con un RTOS en un FPGA comparado con un procesador convencional, teniendo en cuenta custom hardware?.

  27. Con estos conocimiento ahora es su turno de investigar, las otras funcionalidades que ofrece un RTOS, en este caso FreeRTOS, como Mutex, Semaphores entre otras cosas que usted puede ver en la documentación de FreeRTOS.
 

 

Parte 3(Accelerometro, plus de la practica):

En esta sección vamos agregar(Conservando las de la parte 2) crear una tarea en el FreeRTOS donde se lea del accelerometro el Pitch, Roll and Yaw,  y enviarlas por medio de UART a un script de python, donde se visualizara el movimiento de manera tridimensional.
  1. Ya en el laboratorio 3 usted vio como usar con python el puerto serial para enviar y recibir datos, ahora implementara un script que no solo recibe datos si no que gráfica de forma tridimensional la posición de un bloque en la pantalla del computador. Para esto usted debe tener instalado.
    1. Python 2.7.
    2. Pyserial 2.7.
    3. Vpython 2.7.
  2. Descargue el Download this file (doc.zip)archivo .zip que contiene los drivers para usar el accelerometro y copie la carpeta Drivers en la carpeta del proyecto "FreeRTOS_test".
  3. Ahora en el encabezado del main.c agregue la librería para usar el driver.
  4. Modifique el procedimiento SetupHardware para que se vea como se muestra a continuación:
  5. Ahora defina la prioridad de la tarea que se va a crear:
  6. Agregue la tarea y ejecútela al iniciar el sistema.
  7. Compile, conecte al USB-Serial a la DE0-NANO y al PC, descargue el script de python, cambien el puerto por el cual desea adquirir la información en el script y ejecútelo., y tendrá que obtener como resultado lo siguiente:
 
Codigo PythonDownload this file (Box_3d.py)Box_3d.py
 
import serial, math,time
from visual import *

ser = serial.Serial('COM5', 115200)

data1=0;
data2=0;
data3=0;
data4=0;
data5=0;
data6=0;
data7=0;

X=0;
Y=0;
Z=0;
signo=0;

scene.title='ECE 31289 UPB'

mybox = box(pos=(50,10,57/2), length=100, height=20, width=57)
pointer = arrow(pos=(0,0,0),axis=(0,100,0),shaftwidth=1)
pointer1 = arrow(pos=(0,0,0),axis=(0,0,100),shaftwidth=1)
pointer2 = arrow(pos=(0,0,0),axis=(100,0,0),shaftwidth=1)

mybox.opacity=0.5
mybox.color= color.blue
pointer.color= color.cyan
pointer1.color= color.red
pointer2.color= color.yellow
picth=0
roll=0
yaw=0

scene.autoscale =0

theta = 0 # initial polar angle of shaft (from vertical)
thetadot = 0 # initial rate of change of polar angle
alpha = 0 # initial spin angle
alphadot = 0.0 # initial rate of change of spin angle (spin ang. velocity)
phi = -pi/2. # initial azimuthal angle
phidot = 0 # initial rate of change of azimuthal angle
dt = 0.0001

while 1:
    
    keys1=ord(ser.read(1))
    
    if(keys1==7):
        data1=ord(ser.read(1))
        data2=ord(ser.read(1))
        data3=ord(ser.read(1))
        data4=ord(ser.read(1))
        data5=ord(ser.read(1))
        data6=ord(ser.read(1))
        data7=ord(ser.read(1))
        X=(data1 & 1)*128 | (((data1>>1)&1)<<16) | (data2 & 127)| ((data3 & 127)<<8)
        if(X & 4096):# paso a signed short 16 bits
            
            X=-0x1000 + (X & 4095)
        
            
        
        Y=(data1>>2 & 1)*128 | (((data1>>3)&1)<<16) |(data4 & 127) | ((data5 & 127)<<8)
        if(Y & 4096):# paso a signed short 16 bits
               
            Y=-0x1000 + (Y & 4095)
        
        
        Z=(data1>>4 & 1)*128 | (((data1>>5)&1)<<16) |(data6 & 127) | ((data7 & 127)<<8)
        if(Z & 4096):# paso a signed short 16 bits
               
            Z=-0x1000 + (Z & 4095)
            
        print 'X='+ str(X) + ' Y=' + str(Y) + ' Z=' + str(Z)
        #pitch=math.atan((X/(math.sqrt(Y**2 +Z**2)+0.01)))
        #roll=math.atan((Y/(math.sqrt(X**2 +Z**2)+0.01)))
        #yaw=math.atan((math.sqrt(X**2 +Y**2)/(Z+0.01)))

        mybox.axis= X,Y,Z
        #mybox.axis= pitch,roll,yaw
        mybox.length=30 
        mybox.width=100
        mybox.height=57

        
        
        
        
        

        
        
        
        
    
    


 
 
Ahora puede usar FreeRTOS para los proyectos que desee usando Nios II en la DE0-NANO.
 
Tambien puede usar otros sistemas operativos de tiempo real usando Nios II u otros procesadores sobre la DE0-NANO, trate de implementar uC/OS-IIeCOSRTLinuxuClinux, entre otros...
 
 
 
 
Written by: Holguer A Becerra
 
 
 
 

En esta practica vamos a construir nuestro primer juego retro  usando un sincronizador de Video VGA (Video Graphics Array), para esto debemos entender los conceptos basicos de sincronizacion VGA usando verilog, de tal manera que podamos visualizar el simple video juego que queremos construir en esta practica, y asi se adquieran los conceptos necesarios base para soltar tu imaginacion.

 

 
Siga las instrucciones paso a paso:
 
  • Cree una plantilla con el DE0_NANO_Control_Panel.exe de la DE0-NANO, como el que se muestra a continuación(recuerde guardarla en una ruta sin espacios y corta):
  • Primero se debe identificar, para esto vamos a ver la siguiente figura en donde usted entenderá como se conecta una FPGA a un conector VGA usando solo resistencias.
En la Fig 1, tenemos el conector VGA “HD-DB15” con una serie de conexiones que van directamente hacia la una FPGA por medio de resistencias de precisión de diferentes calibres.
El pin 1, 2, 3 corresponden al conjunto de colores que podemos enviar a la pantalla, en este caso RGB, por cada componente tenemos un 4 bits de resolución, lo que nos da 16 colores diferentes por color, dándonos una diversa serie de combinación de colores si juntamos las 3 componentes R,G y B.
En la conexión, la conexión de colores no son lo mas importante que debemos ver, hay dos pines llamados VGA_VSYNC y VGA_ HSYNC, estos dos se encargan de sincronizar cada pixel de imagen que mandemos hacia la pantalla, estos son los encargados de decirle a la pantalla en que posición de esta vamos a dibujar, con estos 2 pines nos dispondremos ha entender el modulo VGA que implementaremos en este manual.
 
  • Ahora como se entiende la conexión al puerto VGA se debe pasar a entender como actúan las señales de VGA_VSYNC y VGA_H_SYNC para lograr sincronizar la pantalla VGA.
la entrada al puerto VGA tiene una sincronización Horizontal(H_sync) se encarga de indicarle a la pantalla que se ha terminado de enviar una linea horizontal completa a la pantalla y una sincronización Veritical indicarle a la pantalla que ha terminado de enviar a la pantalla un frame de video de un alto determinado(V_sync), en las figuras siguientes se puede ver en detalle el comportamiento de estas señales (Gráficas del libro de PONG CHU cap 13).

  • La pregunta ahora es como calcular el reloj que controle el modulo de sincronización VGA, en el caso de las figuras anteriores se tiene la descripción de las señales de un modulo de 680x480 pixeles a una tasa de 60Hz, esto quiere decir que se esta sincronizando la pantalla VGA para enviar una imagen de tamaño 640x480 cada 1/60 segundos(Vea que el H_SYNC se mantiene en alto por un tiempo 640 flancos de reloj despues de un left border y antes de un retrace).
    • Observe que es el Retrace, Right Porch, Top Border... etc...
  • Los cálculos para saber cual es el reloj que debemos utilizar para crear este tipo de sincronización teniendo en cuenta que lo único que se tendrán en el diseño serán simples contadores y comparadores que generan H_SYNC y V_SYNC de acuerdo a las zonas que se muestran en la figura 13.4 y 13.5 del libro de PONG CHU.
 
  • Los cálculos que se hicieron en con la formula anterior dicen que para diseñar un sicronizador VGA basado en contadores se debe usar un reloj de 25MHz.
  • Haga los cálculos para las siguientes resoluciones para ver si Pixel Clock coincide con los calculos.
Format Pixel Clock
(MHz)
Horizontal (in Pixels) Vertical (in Lines)
Active
Video
Front
Porch
Sync
Pulse
Back
Porch
Active
Video
Front
Porch
Sync
Pulse
Back
Porch
640x480, 60Hz 25.175 640 16 96 48 480 11 2 31
640x480, 72Hz 31.500 640 24 40 128 480 9 3 28
640x480, 75Hz 31.500 640 16 96 48 480 11 2 32
640x480, 85Hz 36.000 640 32 48 112 480 1 3 25
800x600, 56Hz 38.100 800 32 128 128 600 1 4 14
800x600, 60Hz 40.000 800 40 128 88 600 1 4 23
800x600, 72Hz 50.000 800 56 120 64 600 37 6 23
800x600, 75Hz 49.500 800 16 80 160 600 1 2 21
800x600, 85Hz 56.250 800 32 64 152 600 1 3 27
1024x768, 60Hz 65.000 1024 24 136 160 768 3 6 29
1024x768, 70Hz 75.000 1024 24 136 144 768 3 6 29
1024x768, 75Hz 78.750 1024 16 96 176 768 1 3 28
1024x768, 85Hz 94.500 1024 48 96 208 768 1 3 36
Source: Rick Ballantyne, Xilinx Inc.
 
  • Ahora empecemos con el diseño del modulo de sincronización VGA, cree en Verilog un modulo llamado "mi_vga" con las siguientes entradas y salidas.
    • CLK==> 1 bit entrada.
    • h_sync==> 1 bit salida.
    • v_sync==> 1 bit salida.
    • video_on ==> 1 bit salida
    • pixel_x ==> configurable a N bits de salida.
    • pixel_y ==> configurable a N bits de salida.
  • Dentro del modulo declare un contador de N bits llamado "counter_x" y otro llamado "counter_y".
 
  • ahora declare los los parámetros locales siguientes en el modulo
  • ahora añada a la descripción un logica secuencial que modifique el "counter_x" y "counter_y", para que cada vez que counter_x llegue a un valor máximo de (HD+HF+HB+HR-1) haga que "counter_y" incremente 1 y "counter_x" se reinicie en 0, y que si en el caso de "counter_y" llegue al máximo (VD+VF+VB+VR-1) se reinicie en 0.
  • Ahora añada un registro que se llame h_sync_reg y otro que se llame v_sync_reg y cree una lógica como la siguiente. 
  • Ahora instancia el chip en la plantilla principal para probarlo de la siguiente manera, agregue al diseño un PLL que en base del CLOCK_50 genere una frecuencia de salida con respecto a la formula de sincronización VGA que dice PONG CHU, es mejor usar un PLL en vez de dividir el reloj con un divisor de frecuencia para que de esta forma se mantenga la FASE del sincronizador VGA, lo cual es algo muy importante.
 
  • Haga un circuito que aparece en la Fig 1 al inicio de esta practica y conéctelo de acuerdo a la descripción realizada en el Top Module, luego genere el archivo .SOF y programe la FPGA, si todo va bien la pantalla deberá iluminar toda en ROJO.
  • Ahora agregue/modifique la siguiente descripción al circuito principal y responda:
    • ¿Que aparece en la pantalla?
  • Ahora agregue/modifique la siguiente descripción al circuito principal y responda:
    • ¿Que aparece en la pantalla y por qué?
  • Ahora vamos agregar una imagen al diseño, para esto debemos descargar el siguiente script(Download this file (generado_ram_imag (1).m)generado_ram_imag.m) en matlab que convertirá una imagen de formato BMP-24 a formato MIF(Memory Initialization File).
  • Descargue la siguiente imagen(Download this file (ejemplo.bmp)ejemplo.bmp) que esta en formato BMP-24 y contiene la imagen de una figura de acción, y utilice el script en maltab para convertirla a formato "image.mif".
  • Abra el archivo .mif y verifique que este de la siguiente forma
  • Copie el Archivo "image.mif" a la carpeta de la plantilla.
  • En el proyecto Quartus vaya al megawizard y cree una memoria ROM  de 12 bits "WIDTH" y de tamaño "8200" words "DETPH" e tome a "imagen.mif" como el archivo de inicialización de la memoria ROM y por ultimo genere la memoria ROM.
  • Ahora instancie la ROM en el top de la plantilla principal de la siguiente manera, sin modificar lo que ya hizo anteriormente, si lo hace bien deberá aparecer la imagen que se convirtió con el script en matlab en la pantalla (Solución con imagen Download this file (solucion_con_imagen.sof)solucion_con_imagen.sof).
    • Analice el script en matlab y el código verilog.
    • llegue a sus conclusiones.
  • Ahora convierta la siguiente imagen "Download this file (render.bmp)render.bmp a formato .mif y modifique la memoria ROM de acuerdo al archivo MIF generado, y modifique la descripción de hardware para que se vea como lo siguiente(Solución .sof).
  • Ahora modifique la descripción de hardware para crear una pequeña animación con la imagen "render" que tenemos guardada en memoria.
si lo hizo de manera correcta deberá aparecer una animación(Download this file (solucion_con_render.sof)solucion_con_render.sof) en la pantalla en base a la imagen "render" que esta guardada en la memoria ROM, ahora entienda la lógica del circuito y modifique para que el CHAN(Muñeco) se mueva en la pantalla como lo hace la caja azul(blue box).
Modificando la descripción de la siguiente forma obtendra el movimiento y agregara un efecto de alpha_blending a la imagen variando los SW[3:0] (Solución con movimiento Download this file (solucion_con_animacion_base.sof)solucion_con_animacion_base.sof).
 
La transparencia se logra gracias a la siguiente formula de alpha blending, donde LayerA es la capa superior que se quiere mezclar con LayerB que seria la capa inferior y done LayerAlpha seria el nivel de transparencia de la capa A sobre la capa B.
 
Plantilla de Solución(Download this file (practica_vga.qar)practica_vga.qar) Final(Solo para guiarse) a esto deberá llegar el final de esta practica.
 
 

Ahora:

General timing

Screen refresh rate 60 Hz
Vertical refresh 49.678571428571 kHz
Pixel freq. 83.46 MHz
 
 

Horizontal timing (line)

Polarity of horizontal sync pulse is negative.
Scanline part Pixels Time [µs]
Visible area 1280 15.336688233884
Front porch 64 0.76683441169422
Sync pulse 136 1.6295231248502
Back porch 200 2.3963575365445
Whole line 1680 20.129403306973
 

Vertical timing (frame)

Polarity of vertical sync pulse is positive.
Frame part Lines Time [ms]
Visible area 800 16.103522645579
Front porch 1 0.020129403306973
Sync pulse 3 0.06038820992092
Back porch 24 0.48310567936736
Whole frame 828 16.667145938174
  • En base al archivo "Download this file (mi_vga (1).v)mi_vga.v" modifiquelo para crear un sincronizador VGA(SXGA) de 1280x1024 a 60Hz teniendo en cuenta la siguiente información

General timing

Screen refresh rate 60 Hz
Vertical refresh 63.981042654028 kHz
Pixel freq. 108.0 MHz

Horizontal timing (line)

Polarity of horizontal sync pulse is negative.
Scanline part Pixels Time [µs]
Visible area 1280 11.851851851852
Front porch 48 0.44444444444444
Sync pulse 112 1.037037037037
Back porch 248 2.2962962962963
Whole line 1688 15.62962962963

Vertical timing (frame)

Polarity of vertical sync pulse is positive.
Frame part Lines Time [ms]
Visible area 1024 16.004740740741
Front porch 1 0.01562962962963
Sync pulse 3 0.046888888888889
Back porch 38 0.59392592592593
Whole frame 1066 16.661185185185
 
 
  • En base al archivo "Download this file (mi_vga (1).v)mi_vga.v" modifiquelo para crear un sincronizador VGA(WUXGA) de 1920x1200 a 60Hz teniendo en cuenta la siguiente información

General timing

Screen refresh rate 60 Hz
Vertical refresh 74.521604938272 kHz
Pixel freq. 193.16 MHz

Horizontal timing (line)

Polarity of horizontal sync pulse is negative.
Scanline part Pixels Time [µs]
Visible area 1920 9.939946158625
Front porch 128 0.66266307724166
Sync pulse 208 1.0768275005177
Back porch 336 1.7394905777594
Whole line 2592 13.418927314144

Vertical timing (frame)

Polarity of vertical sync pulse is positive.
Frame part Lines Time [ms]
Visible area 1200 16.102712776972
Front porch 1 0.013418927314144
Sync pulse 3 0.040256781942431
Back porch 38 0.50991923793746
Whole frame 1242 16.666307724166
  1. Ahora cree un modulo que se pueda configurar en tiempo real mientras esta sobre la DE0-NANO que le brinde al usuario todas las resoluciones posibles disponibles utilizando el mismo modulo de sincronización.
 
 
 
EXTRA BONUS:
  • Descargue lo siguiente es el juego BreakOut(Download this file (BreakOut_Game_DE0_NANO.qar)BreakOut_Game_DE0_NANO.qar) basado en el juego de PONG CHU funcionando en la DE0-NANO, y pruebe jugando con el KEY[1:0].
 
 
Written by Holguer A. Becerra
 
 
 
 
 
 
Based on Gregory Estrade's Work.
 
I have ported the PC Engine System on the DE0-NANO back in 2014, why don't you have a look?, Maybe port it on another FPGA?
 
Here you go the DE0-NANO (Download:Sources) Ready to be Synthesized. 
 
Open the Project, Analyze the project, make your changes, Compile and then program the .sof.
 
I made some changes on the original project so that you know, which are:
  • Booting the PCengine from EPCS memory instead of using the flash memory of DE1 or DE2-115.
    //FLASH
    wire [23:0]FL_ADDR;
    wire [7:0]FL_DQ;
    wire data_rdy;
    wire clk_epcs;
    wire boot_oe_n;
    
    //EPCS reader
    epcs_reader flash
    (
    	.clk(clk_epcs) ,	// input  clk_sig
    	.data_out_valid(data_rdy),
    	.start(boot_oe_n) ,	// input  read_clk_sig
    	.Serial(EPCS_ASDO) ,	// output  Serial_sig
    	.address({2'b00,FL_ADDR[21:0]}) ,	// input [23:0] address_sig
    	.serial_byte(EPCS_DATA0) ,	// input  serial_byte_sig
    	.data_out(FL_DQ) ,	// output [7:0] flash_byte_sig
    	.SPI_clock(EPCS_DCLK) ,	// output  SPI_clock_sig
    	.FSM_ENABLE(1'b1) ,	// input  FSM_ENABLE_sig
    	.CS(EPCS_NCSO) 	// output  CS_sig
    );
    ​
  • Audio works with Delta Sigma instead of WM8731 codec, ideal for the DE0-NANO.
    //AUDIO PINS
    assign GPIO[27]=AUDIO_L;
    assign GPIO[29]=AUDIO_R;

  • The VGA Connection is based on the following Schematic
    // VGA Pins
    assign GPIO[0]=vga_R[3];
    assign GPIO[1]=vga_R[2];
    assign GPIO[3]=vga_R[1];
    assign GPIO[5]=vga_R[0];
    assign GPIO[7]=vga_G[3];
    assign GPIO[9]=vga_G[2];
    assign GPIO[11]=vga_G[1];
    assign GPIO[13]=vga_G[0];
    assign GPIO[15]=vga_B[3];
    assign GPIO[17]=vga_B[2];
    assign GPIO[19]=vga_B[1];
    assign GPIO[21]=vga_B[0];
    assign GPIO[23]=V_SYNC;
    assign GPIO[25]=H_SYNC;​
 
  • In addition, the GPIO of the DE0-NANO
  • For now the system has no controller added, but you could simply add one by following any of the given manuals on:
     
     
    For that you should modify the following lines by adding any of the controllers and connecting them to the GPIOs.
    // CONTROLLER
    wire UP_CONTROLLER=1'b0;
    wire DOWN_CONTROLLER=1'b0;
    wire LEFT_CONTROLLER=1'b0;
    wire RIGHT_CONTROLLER=1'b0;
    wire SELECT_CONTROLLER=1'b1;
    wire RUN_CONTROLLER=1'b1;
    wire BUTTON1_CONTROLLER=1'b1;
    wire BUTTON2_CONTROLLER=1'b1;
    
     
     

In order to Load the PC Engine ROMS on the EPCS you should follow the following steps:

  1. Connect the DE0-NANO using your USB to power it on.
  2. Run the "DE0_NANO_Control_Panel.exe" program.
  3. Click on "Open", select "Open USB Port 0".
  4. Select the "EPCS" tab, and click on "Chip Erase".
  5. Check "File Length", then click on "Write a File to EPCS".
  6. Choose a PC-Engine ROM(JackieChan.pce). You can fin other ROMS here https://romhustler.org/rom/tg16/ 
  7. Once the write operation is done, close the "DE0-NANO Control Panel".
  • Open Control panel, once opened, click on Memory, then on Memory type list select "EPCS", select the check "File Length", on the address of section Sequential Write  type "07d000" and then click on the button write file to memory.
  • Select the file  JackieChan.pce, and OK.
  • Then wait until the EPCS is written.


    Note: if it does not let you write, first click on Erase "Chip Erase (70 sec)"
 
The switches(SW[3:1]) depend on the type of pceeng image.
- SW1 : header present or not (usually, ROMs do have this header, so set it to "1").
- SW2 : set it to "1" if your ROM is 768 Kb.
- SW3 : set it to "1" if you're dealing with a TGFX ROM instead of a PCE ROM.
  NEC created a very basic region-lockout by swapping data bits on the cartridge
  and cartridge connector on US (TGFX-16) consoles.
  So, for the (very few) ROMs that have been dumped off a US cartridge, set it to "1".  
  If unsure, set it to "0".
 
//CONTROL PCEngine Signals
wire RESET = ~KEY[0];
wire ROM_HEADER =SW[1];
wire ROM_SPLIT =SW[2];
wire BITFLIP =SW[3];
 
  

Demostration videos:

 
 
 
 

  Aditionally you can see the PC engine running on a PSP Screen.

 
 

 

 
For more info you can go to:LCD DRIVER(PSP SCREEN) with and without Nios II

 

Challenges to be done, that you could do on your own:

  1. Why don't you try to port it on the DE0-NANO-SOC, and use the ARM9 to reprogram the logic of the FPGA, and to load the ROMS?
  2. Do the same using Nios II, or RISC-V.
  3. Port it on the Sipeed TANG PriMER FPGA Dev. Board

 

 

Written by: Andrea Paola Pabón Ortega & Daniel Enrique Mejia Rueda

Revision by: Ing Holguer A. Becerra

 

DESCRIPCIÓN DEL PROYECTO:

El  RTAWD DE0NANO es un reproductor de audio que permite al usuario escuchar música, observar  la onda de audio en tiempo real a través de la pantalla touch 4.3inch-480x272-Touch-LCD. Para esto, por medio del NIOSII se leerán los  archivos  .WAV contenidos en una memoria SD, que se seleccionarán  a través de una lista diseñada en el mismo, es decir en la pantalla se podrá observar una lista de canciones diseñadas previamente en el NIOSII, permitiendo al usuario seleccionar la canción que  desea reproducir por medio de los botones de navegación contenidos en la pantalla, además la pantalla contara con algunos botones que facilitan la reproducción de canciones, como son play, stop, pause, los botones de navegación previamente mencionados para la lista entre otros. Una vez seleccionado el archivo a reproducir, se tratará la señal con el fin de calcular el histograma y así poder obtener una visualización estilo FFT (esto permitiera observar la señal de audio en estilo de barras),  ya que el reproductor será estéreo la señal de audio será enviada a dos delta sigma (cada uno de 16 bits) encargados cada uno de una canal ya sea  R o L. A continuación se presentará un diagrama de bloque con el Diseño:


Este proyecto consiste en el diseño de un reproductor de audio que permitirá al usuario además de escuchar música, observar la onda de audio en tiempo real a través de la pantalla touch  4.3 inch del proyecto
Ya que nuestro proyecto consiste en la reproducción de archivos de audio .wav, se ve la necesidad de estudiar este tipo de formato, así que a continuación se presentara su concepto y el código en maltab para abrir un archivo de este tipo. 


Waveform Audio Format [.wav o wave]

" WAV (o WAVE), es un formato de audio digital normalmente sin compresión de datos desarrollado y propiedad de Microsoft y de IBM , Es un formato muy extendido entre los usuarios de PC, ya que funciona en cualquier aplicación de Windows. Se desaconseja su uso para internet por el gran tamaño de los archivos en este formato,  admite archivos mono y estéreo a diversas resoluciones y velocidades de muestreo, su extensión es .wav."

 

* Abrir un archio .wav en MATLAB

Para la reproducción de un archivo de audio en Matlab, se emplea el comando "wavread", el cual es el encargado de leer el archivo de sonido, y para la reproducción del mismo se hace uso del comando "sound" el cual es el encargado de enviar el sonido a los parlantes del PC.

 

Código en Matlab para la reproducción de un archivo .wav

close all
clear all
hfile = 'HearYouMe.wav';
[y,Fs] = wavread(hfile);
sound(y,Fs);

* Donde para : >> [y, Fs] = wavread(filename) 

  • "Y corresponde a un vector cuyos elementos son los datos tomados en la grabación (muestreo)."
  • "Fs es la tasa de muestreo en Hz utilizada para la grabación."
  • "Filename corresponde al  nombre del archivo de la grabación."

Conclusiones:

- los archivos de formato .wav son sonidos de buena calidad debido a que no presentan compresión de datos, y ademas presentan una frecuencia de grabación de 44100 Hz. 

- Se debe tener en cuenta que para la reproducción en MATLAB de este tipo de archivos de sonido, estos deben estar ubicados en  la misma carpeta  donde se encuentra el  programa diseñado en matlab, de lo contrario se debe escribir la dirección completa (ruta) donde se encuentra el archivo que se  desea reproducir.

(*Por Andrea Pabón*)

 


 

Cómo graficar un archivo .wav

 

Se selecciono la canción Hear You Me de Jimmy Eat para ser graficada empleando Matlab, pero debido al tamaño tan grande del archivo (47.9MB con una duración de 4:40 minutos)  se presentaron dificultades a la hora de ejecutar el programa.

 

Presentándose un erro "Out of memory", este error se debe a la insuficiencia de memoria del computador.
Para ver la capacidad de memoria (memoria disponible) se empleo el comando memory, como se ve a continuación:

 


Como solución se decidió recortar la canción (Audacity) , cuyo tiempo de reproducción era de 4:40 minutos, quedando de un tamaño de 16MB, con un tiempo de 1:35 minutos. 
Una vez recortada se procedió a ejecutar el programa, obteniéndose la imagen que se ve a continuación.

 

Código para Graficar un archivo .WAV

clc
close all
clear all

cancion='jimmy.wav'; %% Señal de Audio
[only,fs,nbits]=wavread(cancion); %% Lee la señal de audio
Ts=1/fs; % Periodo de muestreo
L=length(only); %% Número de muestras
t=0:Ts:((L-1)*Ts);
figure
plot(t,only),title('Grafica de una señal .wav'),grid on, zoom on
xlabel('t')
ylabel('Señal de audio')
- Código Para Graficar un archivo .WAV 
 
 

 



(*Por Andrea Pabón*)


 

  • SOLUCIÓN AL PROBLEMA DE MEMORIA.

 

El día anterior surgió el inconveniente de poder graficar la señal de una canción completa por causa de la memoria del computador en la cual se estaba ejecutando el programa y se planteó la solución de recortar la señal utilizando por medio de un programa diferente a matlab (audiocity).

Este inconveniente se pudo solucionar diezmando las señales de cada uno de los canales utilizando matlab evitando que recurrir a recortar la señal,  a continuación se muestran los resultados obtenidos

 

Código en Matlab

%lectura señal de audio
[s FS]=wavread('paramore.wav');

%separar canales r y l
canal_r=s(:,1);
canal_l=s(:,2);

%tiempo de la señal
tiempo=size(s,1)/FS;

%recorte señal
%s_r=canal_l(1:350000);
%wavplay(s_r,FS)

%invercion señal
s_inv=canal_r(end:-1:1);
%wavplay(s_inv,FS)

%diezmacion
s_di=canal_r(1:3:end);
wavplay(s_di,FS/3 )% Es necesario dividir la frecuencia de muestreo
                            % en el número de pasos que va a hacer el comando
                            % s_di para que la señal valla a la misma velocidad
                            % de la original en este ejemplo es 3.

%plot
subplot(2,1,1);plot(canal_r)
title('canal r')
subplot(2,1,2);plot(s_di)
title('canal r diezmado')

 

mediante la funcion whos de matlab podemos comprobar que la señal tiene menos bytes 

(*Por Daniel Rueda*)


 

                              

Graficar un archivo .wav Empleando FFT

 

Para el análisis en frecuencia de la señal de audio se emplea la transformada Rápida de Fourier, a continuación se presentara el código empleado para realizar la gráfica de la señal. 

código para Gráficar un archivo .wav empleando FFT

clc
clear all
close all
 
%lectura señal de audio
[s FS]=wavread('paramore.wav');
 
%separar canales r y l
canal_r=s(:,1);
canal_l=s(:,2);
 
%tiempo de la señal
tiempo=size(s,1)/FS;
 
%inversion señal
s_inv=canal_r(end:-1:1);
%wavplay(s_inv,FS)
 
%diezmacion
s_dir=canal_r(1:3:end);
%wavplay(s_di,FS/3)% Es necesario dividir la frecuencia de muestreo
                  % en el número de pasos que va a hacer el comando
                  % s_di para que la señal valla a la misma velocidad
                  % de la original.
 
                 
s_dil=canal_l(1:4:end);
 
%
s_di=[s_dir s_dil];
wavplay(s_di,FS/3)
 
%recorte señal
%s_r=s_di(1:50000);
 
%wavplay(s_r,FS)
 
%FFT
 
s_di_fft=abs(fft(s_di)); % abs se refiere al valor absoluto
                         % fft(x):es la transformada discreta de Fourier del vector s_di(archivo .wav)                       
 
%plot
subplot(3,1,1);plot(s), grid on
title('cancion normal')
subplot(3,1,2);plot(s_di), grid on
title('cancion diezmada')
subplot(3,1,3);plot(s_di_fft, 'r'), grid on
title('FFT de la cancion diezmada en frecuencia')

 

(*Por Andrea Pabón*)


 

Diseño Caja para el RTAW

Debido a que nuestro proyecto consiste en realizar un reproductor de música y está conformado por una fpga, una pantalla táctil y un plug es necesario crear un diseño del prototipo el cual contendrá todos componentes antes mencionados de forma que nuestro reproductor se físicamente llamativo.

 

Para la realización de este diseño se contó con el programa solidworks el cual nos permite crear un diseño en 3d de los componentes físicos que conforman nuestro reproductor y poder tener una idea más real de cómo quedaría el reproductor en la vida real.

 

A continuación se muestran las imágenes de los diseños que se hicieron

 
pantalla tactil

carcasa

ensamblaje final

Los diseños están sujetos a cambios a medida que el proyecto valla avanzando.

 

(*Por Daniel Mejia*)


 

 Diseño del módulo de Sincronización de Vídeo:

Una vez entendido el formato .wav se procede a diseñar la interfaz gráfica del Reproductor,  para esto se diseña el sincronizador de Vídeo como se explica a continuación.
Para el diseño del módulo de sincronización de vídeo de la pantalla touch de 4.3 inch, primero se procedió a calcular el reloj que controla el modulo, para esto nos basamos en los datos de los parámetros ubicados en la pagina 63 del  datasheet.  para una resolución 480x272 60Hz y se obtuvo un reloj de 9MHz como se ve en la siguiente figura.



Una vez obtenido el valor del reloj se diseña el Pll que genera el reloj de 9MHz  empleando  la herramienta Tools/MegaWizard.

 
 
Una vez diseñado el PLL y el modulo de sincronización VGA que se presenta a continuación  (mi_vga)el cual fue diseñado con anterioridad al realizar la practica 4 del laboratorio, se realiza la instanciación de estos.
 
module mi_vga(clk, h_sync, v_sync, video_on, pixel_x, pixel_y);
parameter N=11;
input clk;
output h_sync, v_sync, video_on;
output [N-1:0] pixel_x;
output [N-1:0] pixel_y;

reg [N-1:0] counter_x;
reg [N-1:0] counter_y;

reg h_sync_reg;
reg v_sync_reg;

//parametros locales

localparam HD = 480; // horizontal display area
localparam HF = 2 ; // h. front (left) border
localparam HB = 2 ; // h. back (right) border
localparam HR = 41 ; // h. retrace
localparam VD = 272; // vertical display area
localparam VF = 2 ; // v. front (top) border
localparam VB = 2 ; // v. back (bottom) border
localparam VR = 10  ; // v. retrace


//contadores x & y

always @(posedge clk)
begin
     counter_y <= counter_y;
   
   if (counter_x >= (HD+HF+HB+HR-1))
   begin
        counter_x<=0;
  if (counter_y >= (VD+VF+VB+VR-1))
  begin
  counter_y <= 0;
  end
  else
  begin
  counter_y <= counter_y + 1'b1;
  end
   end
   else
   begin
   counter_x <= counter_x + 1'b1;
   end

end


always @(posedge clk)
begin
     h_sync_reg <= (counter_x >= (HD+HB)) && (counter_x <= (HD+HB+HR-1));
   v_sync_reg <= (counter_y >= (VD+VB)) && (counter_y <= (VD+VB+VR-1));
end

assign h_sync = h_sync_reg;
assign v_sync = v_sync_reg;
assign video_on = (counter_x < (HD)) && (counter_y < (VD));

assign pixel_x = counter_x;
assign pixel_y = counter_y;


endmodule

 

 

 

 
Para verificar la resolución de la pantalla (480x272), se ubicaron en diferentes puntos de la pantalla 3 cuadriláteros, como se ve en la siguiente figura


La siguiente prueba consiste en simular los cuadros diseñados con anterioridad como botones, así al oprimir dentro del área de cada cuadrado este debe cambiar de color.
Para esto se instanció el módulo para el touch de la pantalla.


Este permite usar el touch sin necesidad del NIOS II . Además fue necesario reubicar los cuadrados de tal manera que queden alejados entre si, con el fin de evitar 
interferencia al tocar cada cuadrado, a continuación se puede observar la ubicación de los "botones", y  el color que de estos al estar y no estar presionados.


 PEN_DOWN sera 1'b0, al estar tocando la pantalla de lo contrario será 1'b1,  para cambiar el color del "botón"  por ejemplo el Azul [24'b0000ff] el PEN_DOWN sera 0, y si se encuentra dentro de los limites del  cuadrado este cambiara a color verde [24'b00ff00] de lo contrario permanecerá azul.
 
 
 
Al oprimir un botón seguido de otro se detectaba un rebote en el cuadrado anterior,  para evitar esto se  empleo el Generate (ver siguiente figura) el cual consiste en un número de registros en cascada  que generan un retraso en la señal, dicho retraso para este laboratorio sera de 0,1s, (Delay=N/Fs siendo N el número de registros) .
La señal a retrasar es TFT_ADC_PENIRQ_N la cual indica cuando es tocada la pantalla, (1'b0 --> oprime la pantalla)  ahora la señal a utilizar sera PEN_DOWN la cual esta retrasada 0,1 segundos a TFT_ADC_PENIRQ_N.
 
 

A continuación puedes observar como cambian de color cada cuadrado al ser tocados. 

Vídeo de YouTube


ahora se insertara la siguiente imagen con el fin de que esta sea los botones de nuestro Reproductor, ya que  tenia un formato .bmp se cambiara a formato .mif  (Memory Initialization File), por medio del siguienre script (Download this file (generado_ram_imag.m)generado_ram_imag.m) en matlab proporcionado por el docente (Holguer A. Becerra) 
 
Una vez hecha la conversión se abrió el archivo .mif 
 
 
 
Ya que la imagen tiene un tamaño de 253x37  calculando el número de pixeles serian  9361,  con esto se determina que deben haber 9361 datos de 12bits  (ya que son 12bits por color)
con esto datos se crea la memoria ROM por medio de la herramienta MegaWizard con 12 bits y tamaño de 9361 words.
 
 
 
una vez creada la Rom, se instancia el modulo,  y se ubica en la pantalla, ademas se adiciona un cambio de color al pulsar el botón play. (ver siguiente figura).
 
 
se adiciona el siguiente código en el mux de video
 
En el siguiente vídeo se puede observar esta primera prueba.

Vídeo de YouTube

 
 
Ya que se requiere un botón para cada una de las funciones del reproductor como son play, stop, pause, adelantar y atrasar una canción, se procede a insertar  cada uno de estos por separado, empleando una memoria para cada uno, a continuación se presentara el diseño para le botón play. 
Primero se inserta la figura empleando la memoria ROM, como se realizo en la prueba anterior, seguido a esto se ubica el botón en la pantalla, ademas el botón debe cambiar de color al ser oprimido, para esto se crea la siguiente lógica PEN_DOWN se encuentra en estado alto y solo modifica su estado al tocar la pantalla (PEN_DOWN == 0), si tocamos la pantalla y se encuentra sobre el área del botón,  play_oprimido sera igual a 1'b1 de lo contrario sera 1'b0. así que para el color de play, si  play_oprimido=1'b1 sera el valor negado del color original (blanco -->negro, negro-->blanco) de lo contrario el botón tendrá el color original.
 
 
 
 
Para insertar cada botón se realiza en mismo procedimiento.
 
 

Vídeo de YouTube

 
 
 
 
Una vez insertado los botones se procede a trabajar  en el qsys y en el software de lectura del archivo .wav según CHU.

 

PRUEBA SD-CARDDE0-NANO.



Para leer archivos de la SD, primero se debe probar que  se este cargando correctamente, para esto se descarga la siguiente  Descargar Plantilla(Download this file (prueba_sdhc_code.zip)prueba_sdhc_code.zip), se crea un nuevo proyecto en Eclipse (llamelo read_sd) y se copia la carpeta sd_card y el main.c que están contenidas en el archivo.zip descargado recientemente.
se conecta la SD como se ve en la siguiente figura.


y se diseña el Qsys:

Al terminar no olvidad guardar cambios y assign Base Addresses.

ahora se copia la instanciación generada desde el Qsys.


Una vez realizado la instanciación y conectado todo como se muestra en el siguiente código, se programa la DE0-NANO y  el software, y se observara en el terminal del Nios que la Memoria SD se cargo con exito.
system u0 (
        .clk_clk              (sys_clk),              //           clk.clk
        .reset_reset_n        (reset_n),        //         reset.reset_n
 

 //EPCS 
        .epcs_sdo             (EPCS_ASDO),             //          epcs.sdo
        .epcs_data0           (EPCS_DATA0),           //              .data0
        .epcs_dclk            (EPCS_DCLK),            //              .dclk
        .epcs_sce             (EPCS_NCSO),             //              .sce
 
 //SDRAM
        .sdram_dqm            (DRAM_DQM),            //         sdram.dqm
        .sdram_ras_n          (DRAM_RAS_N),          //              .ras_n
        .sdram_cs_n           (DRAM_CS_N),           //              .cs_n
        .sdram_we_n           (DRAM_WE_N),           //              .we_n
        .sdram_dq             (DRAM_DQ),             //              .dq
        .sdram_cke            (DRAM_CKE),            //              .cke
        .sdram_addr           (DRAM_ADDR),           //              .addr
        .sdram_ba             (DRAM_BA),             //              .ba
        .sdram_cas_n          (DRAM_CAS_N),          //              .cas_n
   
      
   //LED & SW
        .pio_led_green_export (), // pio_led_green.export LED[7:1]
        .pio_sw_export        (SW[3:0]),        //        pio_sw.export
   
   //SD-CARD
        .spi_master_0_sclk    (GPIO2[3]),    //  spi_master_0.sclk  6 GPIO2[28]
        .spi_master_0_mosi    (GPIO2[1]),    //              .mosi  3 GPIO2[26]
        .spi_master_0_miso    (GPIO2[5]),    //              .miso   8 GPIO2[30]
        .spi_master_0_cs      (GPIO2[0])      //              .cs  2 GPIO2[24]

 );

 

LEER UN ARCHIVO .WAV  USANDO NIOS II

 

Para la lectura de archivos .wav se tomo como referencia el capitulo 18.7 AUDIO FILE PROCESSING del libro Embedded SoPC Design with Nios II Processor and VHDL Examples de Pong P. Chu, en el cual se encuentra toda la información requerida para el procesamiento de  archivos de audio del formato .wav

 

"Formato de archivo WAV (o WAVE) es un subconjunto de RIFF (formato de archivo de intercambio de recursos) especificación para el almacenamiento de datos multimedia. Un archivo sencillo WAV frecuentemente contiene una sola  porción WAVE. Una porción consiste en un sub-segmento fmt, que proporciona información sobre el formato de audio, y un sub-segmento de datos, que contiene los datos de audio reales. Por simplicidad, suponemos que el archivo WAV ha sido pre-procesado para conformar las especificaciones de controlador  de audio": 

* Formato de audio de datos: no comprimido, que se conoce como formato PCM (modulación por impulsos codificados)

* Número de canales: 2

* Frecuencia de muestreo: 48.000 muestras por segundo

* Resolución: 16 bits por muestra de datos.

 

 Programa de conversión del formato de audio

Dado que el  código de demostración sólo acepta un formato específico, el archivo de audio debe ser pre-procesado de antemano.

 Una utilidad gratuita, es  SoX ("Sound Exchange"), el cual es un procesador de audio de línea de comandos  que puede convertir archivos de audio de un formato a otro formato, La siguiente declaración  convierte un archivo WAV (input.wav) con parámetros arbitrarios a un archivo (output.wav) con el formato deseado:

sox input.wav -r 48000 -b 16 -c 2 -e signed output.wav

Las opciones -r,-b,-c y-e representan la frecuencia de muestreo, bits por muestra (resolución), número de canales, y el esquema de codificación, respectivamente. La opción signed significa que el tipo de codificación es PCM y los datos se almacenan en signed (complemento a dos). El archivo ejecutable y la documentación detallada de SoX se pueden encontrar en http://sox.sourceforge.net/. (tomado del libro Chu)

Una vez descargado el ejecutable, se convierte la canción Let Her Go de formato MP3  a WAV por medio del programa Free Audio Converter.

Ahora modificamos el archivo .wav con el fin de obtener el formato deseado empleando el SoX descargado con anterioridad.

 

A continuación se presenta el código que proporciona Chu para la lectura de archivos .wav

 

int read_wav_file ( char *file_name , alt_u32 *buf)
{
  /* note that fget32 read in little endian, but id in big endian */
  const alt_u32 RIFF_ID = 0x46464952;  // ascii for FFIR
  const alt_u32 WAVE_ID = 0x45564157;  // ascii for EVAW
  const alt_u32 FMT_ID  = 0X20746d66;  // ascii FOR \btmf
  const alt_u32 DATA_ID = 0X61746164Ç; // ascii for ATAD
  
  /*
  Compruebe que el archivo se ajusta a nuestro formato específico. 
  Utilizamos cuatro constantes, 
  RIFF_ID, WAVE-ID, FMT_ID y DATA_ID, para la cadena de Identificación designado. 
  */
  
  FILE *fp;
  alt_u32 r_id, w_id, f_id, d_id, srate, data_size;
  alt_u16 compression, channel, res;
  int i, s_size;
  
  /* open the file*/
  fp = fopen(file_name , "rb");
  if(fp==NULL) {
  printf("Error: no se puede abrir el archivo %s.\n" , file_name);
  return(-1);
  }
  /*extract relevant chunk / subchunk info */
  r_id = fget32(fp);         // offset  0: "RIFF" chunk id
  fskip(fp,4);               // offset  4: chunk size
  w_id = fget32(fp);         // offset  8: "WAVE" chunk id
  f_id = fget32(fp);         // offset 12: "fmt" subchunk id
  fskip(fp,4);               // offset 16: subchunk size
  compression = fget16(fp);  // offset 20: 1 for PCM
  channel = fget16(fp);      // offset 22: 2 for stereo
  strate = fget32(fp);       // offset 24: 48k sampling rate
  fskip(fp,4);               // offset 28: byte rate
  fskip(fp,2);               // offset 32: block size
  res = fget16(fp);          // offset 34: 16 bits resolution
  d_id= fget32(fp);          // offset 36: "data" subchunk id
  data_size = fget32(fp);    // offset 40: # byte of data subchunk
  
  /* check chunck / subchunk ids and parameters */
  if ((r_id != RIFF_ID) || (w_id! = WAVE_ID)  || (f_id!=FMT_ID) ||
      (d_id != DATA_ID) || (compression != 1) || (channel != 2) ||
   (strate != 48000) || (res != 16)){
   printf ("Error: Formato de archivo de sonido incorrectos \n");
   printf ("Debe ser PCM, 2 Canales, 48k velocidad, 16 bits resolución.\n");
   printf ("RIFF/WAVE/fmt/data ids: %08x/ %08x/ %08x/ %08x\n",
            r_id, w_id, f_id, d_id);
   printf ("compression/channel/rate/res/data_size: %d/%d/%d/%d/%d\n",
            compression, channel, srate, res, data_size);
   fclose(fp);
   return(-1);
  }
  
  s_size = data_size/4;
  printf ("Archivo abierto.\n # muestras de datos de audio: %d\n", s_size);
  for (i=0; i<s_size; i++){
  // get 32-bit data
  buf[i] = fget32(fp);
  if (i%1000 == 0 )
      printf (".");
  }
  fclose(fp);
  printf("\nFile loaded.\n");
  return(0);
  }

 

Como primera prueba se transcribió el código anterior basándonos en las funciones de la siguiente página   (http://elmchan.org/fsw/ff/00index_e.html) y se incluyeron las respectivas librerías

 
obteniendo como resultado:
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <system.h>
#include <io.h>
#include "./sd_card/fatfs.h"
#include "./sd_card/diskio.h"

#include "./sd_card/ff.h"
#include "./sd_card/monitor.h"
#include <sys/alt_alarm.h>


/*=========================================================================*/
/*  DEFINE: Definition of all local Data                                   */
/*=========================================================================*/
static alt_alarm alarm2;
static unsigned long Systick = 0;
static volatile unsigned short Timer;   /* 1000Hz increment timer */
FATFS Fatfs[_VOLUMES];          /* File system object for each logical drive */
/*=========================================================================*/
static void IoInit(void);
static alt_u32 TimerFunction (void *context);
void init_hw();


#define SIZE_OF_SONG 1024000
alt_u32 muestras_stereo[SIZE_OF_SONG];
FIL cancion;
FRESULT fr;

int read_wav_file ( char *file_name , alt_u32 *buf)
{
  /* note that fget32 read in little endian, but id in big endian */
  const alt_u32 RIFF_ID = 0x46464952;  // ascii for FFIR
  const alt_u32 WAVE_ID = 0x45564157;  // ascii for EVAW
  const alt_u32 FMT_ID  = 0X20746d66;  // ascii FOR \btmf
  const alt_u32 DATA_ID = 0X61746164; // ascii for ATAD

  /*
  Compruebe que el archivo se ajusta a nuestro formato específico.
  Utilizamos cuatro constantes,
  RIFF_ID, WAVE-ID, FMT_ID y DATA_ID, para la cadena de Identificación designado.
  */


  alt_u32 r_id, w_id, f_id, d_id, srate, data_size;
  alt_u16 compression, channel, res;
  int i, s_size;
  int Byte_read_rid;
  int Byte_read_wid;
  int Byte_read_fid;
  int Byte_read_compression;
  int Byte_read_channel;
  int Byte_read_srate;
  int Byte_read_res;
  int Byte_read_did;
  int Byte_read_data_size;
  int Byte_read_buf;


  /* open the file*/
  fr=f_open(&cancion, file_name, FA_READ);
  if(fr) {
  printf("Error: no se puede abrir el archivo %s.\n" , file_name);
  return(-1);
  }
  /*extract relevant chunk / subchunk info */
  fr = f_read (&cancion, &r_id,4, &Byte_read_rid);               //fget32(fp);                // offset  0: "RIFF" chunk id
  fr = f_lseek(&cancion, 8);                                    //fskip(fp,4);               // offset  4: chunk size
  fr = f_read (&cancion, &w_id, 4, &Byte_read_wid);              //w_id = fget32(fp);         // offset  8: "WAVE" chunk id
  fr = f_read (&cancion, &f_id, 4, &Byte_read_fid);              //f_id = fget32(fp);         // offset 12: "fmt" subchunk id
  fr = f_lseek(&cancion, 20);                                   //fskip(fp,4);               // offset 16: subchunk size
  fr = f_read (&cancion, &compression, 2, &Byte_read_compression); //compression = fget16(fp);  // offset 20: 1 for PCM
  fr = f_read (&cancion, &channel, 2, &Byte_read_channel);         //channel = fget16(fp);      // offset 22: 2 for stereo
  fr = f_read (&cancion, &srate,4, &Byte_read_srate);          //strate = fget32(fp);       // offset 24: 48k sampling rate
  fr = f_lseek(&cancion, 32); //fskip(fp,4);                    // offset 28: byte rate
  fr = f_lseek(&cancion, 34);  //fskip(fp,2);                   // offset 32: block size
  fr = f_read (&cancion, &res, 2, &Byte_read_res);               //res = fget16(fp);          // offset 34: 16 bits resolution
  fr = f_read (&cancion, &d_id, 4, &Byte_read_did);              //d_id= fget32(fp);          // offset 36: "data" subchunk id
  fr = f_read (&cancion, &data_size, 4, &Byte_read_data_size);   //data_size = fget32(fp);    // offset 40: # byte of data subchunk

  printf ("rid=%x, w_id=%x, f_id=%x, compression=%x, channel=%x, srate=%d, res=%d,d_id=%x, data_size=%d\n",r_id,
   w_id,f_id,compression,channel,srate,res,d_id,data_size);

  /* check chunck / subchunk ids and parameters */
  if ((r_id != RIFF_ID) || (w_id!= WAVE_ID)  || (f_id!=FMT_ID) || (d_id != DATA_ID) || (compression != 1) || (channel != 2) || (srate != 48000) || (res != 16))
  {
   printf ("Error: Formato de archivo de sonido incorrectos \n");
   printf ("Debe ser PCM, 2 Canales, 48k velocidad, 16 bits resolución.\n");
   printf ("RIFF/WAVE/fmt/data ids: %08x/ %08x/ %08x/ %08x\n",r_id, w_id, f_id, d_id);
   printf ("compression/channel/rate/res/data_size: %d/%d/%d/%d/%d\n",compression, channel, srate, res, data_size);
   f_close(&cancion);
   return(-1);
  }

  s_size = data_size/4; //da el tamaño de un archivo en word
  printf ("Archivo abierto.\n # muestras de datos de audio: %d\n", s_size);
  for (i=0; i<s_size; i++){
   if(i>=SIZE_OF_SONG)
   {
   break;
   }
  // get 32-bit data
   fr = f_read (&cancion, &buf[i], 4, &Byte_read_buf); //buf[i] = fget32(fp);

  if (i%1000 == 0 || i==0)
      printf ("%x. ",buf[i]);
  }
  f_close(&cancion);
  printf("\nFile loaded.\n");
  return(0);
}





int main(){
 init_hw();  //inicialice  hardware
 printf("hola prueba\n");
 read_wav_file("OUTPUT.WAV",&muestras_stereo[0]);
 while(1){

 }
}
 
 
 
 
Seguido a esto se procede a programar la NANO, y el  software, con el fin de verificar si se abrió correctamente el archivo.
 
 
para verificar si es correcto se puede abrir el archivo en Matlab
 
Ya que la consola imprime cada 1000 muestras 4 bytes, se verifica en matlab los datos cada 4000, se puede observar que el archivo es abierto correctamente, ahora el siguiente paso es reproducir dicho archivo.
Para esto se realizo la siguiente lógica.
 
 
 
Donde un buffer FIFO (primero en entrar, primero en salir) es una instalación de almacenamiento “elástica” entre dos subsistemas, tiene dos señales de control wr y rd, para las operaciones de escritura y lectura. Cuando wr=1’b1, el dato de entrada es escrito en el buffer, La operación de lectura es algo engañoso. Ya que se puede leer en cualquier momento, la señal rd actúa como una señal de "eliminar". Cuando esta en alto, el primer elemento del búfer FIFO se elimina y el próximo elemento se encuentre disponible.
 
 
 
sclr permite eliminar cada elemento del buffer, y así al oprimir el botón de stop detener por completo  la reproducción del archivo.
 
DATA_STEREO contiene el bus de datos de audio del canal derecho e izquierdo.
 
FULL_FIFO indica que el FIFO está lleno es decir no se puede escribir
 
EMPTY indica que el FIFO está vacío por lo tanto no se puede leer. (tomado de libro Chu)
 

 

Para el diseño del FIFO, se implemento la herramienta MegaWizard.
 
 
Ya que los datos se almacenan en signed (complemento a dos), se debe asegurar que no existan valores negativos, para esto se suma 16'd 32727 [((2^16)/2) -1= 32767]
y se emplea un monodac para cada canal (derecho e izquierdo)
 
 
 
////////////// MONO DAC DERECHA //////////////
dac_nano dac_nano_derecha
(
 .DACout(R) , // output  DACout_sig
 .DACin(Data_stereo[15:0]+16'd32767) , // input [15:0] DACin_sig {~Data_mono[15],Data_mono[14:0]}
 .Clk(clk_50) , // input  Clk_sig
 .Reset(1'b0)  // input  Reset_sig
);

////////////// MONO DAC IZQUIERDA //////////////
dac_nano dac_nano_izquierda
(
 .DACout(L) , // output  DACout_sig
 .DACin(Data_stereo[31:16]+16'd32767) , // input [15:0] DACin_sig {~Data_mono[31],Data_mono[30:16]}
 .Clk(clk_50) , // input  Clk_sig
 .Reset(1'b0)  // input  Reset_sig
);
 
Ya que el reproductor posee botones de play, pause, stop, next, y back se diseña la siguiente máquina de estados.
 
 
Para el diseño del software se emplea el programa Eclipse (lenguaje c).
 
Para el diseño del reproductor se realizan varias pruebas, la primera de ellas es la reproducción del archivo al oprimir el botón play. Seguida a esta se prueba el botón pause
 
El divisor de frecuencia permite reproducir todo tipo de archivo que cumpla con las características de formato, permitiendo que  varié la frecuencia de muestreo.
 
Reproducción de canción de 8KHz

Vídeo de YouTube

 
Reproducción de canción 11KHz
 

Vídeo de YouTube

 
Reproducción de canción 16KHz

Vídeo de YouTube

 
Reproducción de canción 22KHz
 

Vídeo de YouTube

 
 
Reproducción de canción 44KHz
 

Vídeo de YouTube

Reproducción de canción 48KHz
 

Vídeo de YouTube

 
Reproducción de canción 96KHz
 

Vídeo de YouTube

 
Beyonce Single Dadies
 

Vídeo de YouTube

 

 
 
  • 27 de mayo del 2014.

DISEÑO DEL HISTOGRAMA SE UNA SEÑAL.

 

 

Para nuestro proyecto se desea mostrar el histograma de la canción que está sonando en ese momento en la pantalla tactil por tal motivo se realizaron pruebas en matlab usando una señal sinoidal, a continuación se muestra el código.

 

clc

close all

FS=48000;

xx=0:30*pi/(1452*100):30*pi;

canal_r=sin(2*pi*1000*xx);

d=1;

tam=size(canal_r);

tiempo=33e-3; % 33ms

numeromuestras=round(FS*tiempo);

tam2=tam(2)/numeromuestras;

 figure;title('Distribución Weibull'); xlabel('Observación'); ylabel('Frecuencia');

 while(d<(tam2-1))

    nueva=round(canal_r(1452*d:1452*(d+1))*(2^8/2)+(2^8/2));

    x=1:1:255;

    hist(nueva,x)

    pause(tiempo)

    d=d+1;

end

 

a continuación se muestra un video con la simulación obtenida en matlab:

Vídeo de YouTube

 

Después de haber  comprobado que se puede realizar un histograma de una señal realizando matlab procesemos a realizar el código verilog necesario para poder generar un histograma lo primero que se hiso fue crear un sub-modulo "histograma_audio" encargado de hacer el histograma que tiene 256 puntos(numero de registros) ya que se toma una señal de entrada de 8 bits, tomando una ventana de tiempo(33ms) para calcular el numero de veces que se repite una amplitud guardandola así en 256 registros de 8 bits cada ventana de tiempo.


A continuación se muestra el código empleado:





el siguiente paso es hacer el frecGen(Download this file (frecGen.v)frecGen.v) el cual fue dado por el profesor en practicas pasadas

lo siguiente que se tiene que hacer es crear un NCO que se encarga de enviar la funcion sinoidal al "histograma_audio" esto se realiza con la herramienta Mega Wizard que posee Quartus.(Solo para probar el histograma en la pantalla y ver como se se veía según la pruebas de matlab) 

Después de haber creado los 3 sub-módulos procedemos a instanciarlos en el módulo principal, así:

 
wire [31:0]signal;
wire [2047:0]histoGrama;
wire [9:0] fsin_o;
wire[8:0] div_freg;
wire OUT_FREG;


//=======================================================
//  Structural coding
//=======================================================

assign signal[31:0]=  (SW[2:0]==3'b000)? 32'd4492 :
                           (SW[2:0]==3'b001)? 32'd5042 :
                           (SW[2:0]==3'b010)? 32'd5660 :
                           (SW[2:0]==3'b011)? 32'd5995 :
                           (SW[2:0]==3'b100)? 32'd6725 :
                           (SW[2:0]==3'b101)? 32'd7559 :
                           (SW[2:0]==3'b110)? 32'd8478 :
                            32'd8985;

nco nco_inst
(
    .phi_inc_i(signal[31:0]) ,    // input [31:0] phi_inc_i_sig
    .clk(CLOCK_50) ,    // input  clk_sig
    .reset_n(1'b1) ,    // input  reset_n_sig
    .clken(1'b1) ,    // input  clken_sig
    .fsin_o(fsin_o[9:0])     // output [9:0] fsin_o_sig
   
);

frecGen frecGen_inst
(
    .clock(CLOCK_50) ,    // input  clock_sig
    .IN(32'd520) ,    // input [31:0] IN_sig
    .OUT(OUT_FREG)     // output  OUT_sig
);


histograma_audio histograma_audio_inst
(
    .data_x({~fsin_o[9],fsin_o[8:2]}) ,    // input [7:0] data_x_sig
    .clk_sampler(OUT_FREG) ,    // input  clk_sampler_sig
    .histograma(histoGrama[2047:0]) ,    // output [2047:0] histograma_sig
    .clock_50(CLOCK_50) ,    // input  clock_50_sig
    .finish()     // output  finish_sig
);

endmodule

 

El siguiente paso fue ubicar los botones de play, pause, stop, adelantar y atrasar, en la pantalla táctil de tal forma que no tapara el histograma de la canción.

A continuación se muestra el código de los botones:

 

/// Ubicacion boton play /// 70x70
wire play_on= (CounterX > 11'd10) && (CounterX < 11'd80) &&(CounterY > 11'd55) && (CounterY < 11'd125);

wire [13:0]address_play=(CounterY-11'd55)*70+CounterX - 11'd10;
wire [11:0]rgb_play;

wire play_oprimido= ((PEN_DOWN==1'b0) & (real_x[31:0]>11'd10) & (real_x[31:0]<11'd80) & (real_y[31:0]>11'd55) & (real_y[31:0]<11'd125));
wire[23:0]color_boton_play={rgb_play[11:8],rgb_play[11:8],rgb_play[7:4],rgb_play[7:4],rgb_play[3:0],rgb_play[3:0]};
wire[23:0]color_play=play_oprimido?~color_boton_play[23:0]:color_boton_play[23:0];

////////////////////////////////// Ubicacion Boton pause /// 70x70 ///////////////////////////////////////////////////////////////////////////

wire pause_on= (CounterX > 11'd10) && (CounterX < 11'd80) &&(CounterY > 11'd147) && (CounterY < 11'd217);
wire [11:0]rgb_pause;
wire pause_oprimido= ((PEN_DOWN==1'b0) & (real_x[31:0]>11'd10) & (real_x[31:0]<11'd80) & (real_y[31:0]>11'd147) & (real_y[31:0]<11'd217));
wire[23:0]color_boton_pause={rgb_pause[11:8],rgb_pause[11:8],rgb_pause[7:4],rgb_pause[7:4],rgb_pause[3:0],rgb_pause[3:0]};
wire[23:0]color_pause=pause_oprimido?~color_boton_pause[23:0]:color_boton_pause[23:0];
//
wire [13:0]address_pause=(CounterY-11'd147)*70+CounterX-11'd10;

//////////////////////////////////// Ubicacion boton stop /// 70x70 /////////////////////////////////////////////////////////////////////////
wire stop_on= (CounterX > 11'd400) && (CounterX < 11'd470) &&(CounterY > 11'd101) && (CounterY < 11'd171);
wire [11:0]rgb_stop;
wire stop_oprimido= ((PEN_DOWN==1'b0) & (real_x[31:0]>11'd400) & (real_x[31:0]<11'd470) & (real_y[31:0]>11'd101) & (real_y[31:0]<11'd171));
wire[23:0]color_boton_stop={rgb_stop[11:8],rgb_stop[11:8],rgb_stop[7:4],rgb_stop[7:4],rgb_stop[3:0],rgb_stop[3:0]};
wire[23:0]color_stop=stop_oprimido?~color_boton_stop[23:0]:color_boton_stop[23:0];
//
wire [13:0]address_stop=(CounterY-11'd101)*70+CounterX-11'd400;

/// Ubicacion boton adelantar /// 70x70
wire adelantar_on= (CounterX > 11'd400) && (CounterX < 11'd470) &&(CounterY > 11'd191) && (CounterY < 11'd261);
wire [11:0]rgb_adelantar;
wire adelantar_oprimido= ((PEN_DOWN==1'b0) & (real_x[31:0]>11'd400) & (real_x[31:0]<11'd470) & (real_y[31:0]>11'd191) & (real_y[31:0]<11'd261));
wire[23:0]color_boton_adelantar={rgb_adelantar[11:8],rgb_adelantar[11:8],rgb_adelantar[7:4],rgb_adelantar[7:4],rgb_adelantar[3:0],rgb_adelantar[3:0]};
wire[23:0]color_adelantar=adelantar_oprimido?~color_boton_adelantar[23:0]:color_boton_adelantar[23:0];
//
wire [13:0]address_adelantar=(CounterY-11'd191)*70+CounterX-11'd400;

/// Ubicacion boton atrasar /// 70x70
wire atrasar_on= (CounterX > 11'd400) && (CounterX < 11'd470) &&(CounterY > 11'd12) && (CounterY < 11'd81);
wire [11:0]rgb_atrasar;
wire atrasar_oprimido= ((PEN_DOWN==1'b0) & (real_x[31:0]>11'd400) & (real_x[31:0]<11'd470) & (real_y[31:0]>11'd12) & (real_y[31:0]<11'd81));
wire[23:0]color_boton_atrasar={rgb_atrasar[11:8],rgb_atrasar[11:8],rgb_atrasar[7:4],rgb_atrasar[7:4],rgb_atrasar[3:0],rgb_atrasar[3:0]};
wire[23:0]color_atrasar=atrasar_oprimido?~color_boton_atrasar[23:0]:color_boton_atrasar[23:0];
//
wire [13:0]address_atrasar=(CounterY-11'd12)*70+CounterX-11'd400;
/////MUX DE VIDEO ////

always@(negedge TFT_DCLK)
begin
   if(!TFT_DE)
    begin
         TFT_RED<=8'h00;
          TFT_GREEN <=8'd0;
          TFT_BLUE <=8'd0;
    end
    else
        begin
         TFT_RED<=8'h00;
          TFT_GREEN<=8'h00;
          TFT_BLUE <=8'h00;
         if(bar_on)
         begin
            TFT_RED<=Data_histo[15:8];
           TFT_GREEN<=Data_histo2[7:0]+Data_histo2[15:8];
           TFT_BLUE <=Data_histo[7:0];
         end
         else if(play_on)
          begin
            TFT_RED  <=color_play[23:16];
          TFT_GREEN<=color_play[15:8];
          TFT_BLUE <=color_play[7:0];
          end
          else if(pause_on)
          begin
            TFT_RED  <=color_pause[23:16];
          TFT_GREEN<=color_pause[15:8];
          TFT_BLUE <=color_pause[7:0];
          end
          else if(atrasar_on)
          begin
            TFT_RED  <=color_atrasar[23:16];
          TFT_GREEN<=color_atrasar[15:8];
          TFT_BLUE <=color_atrasar[7:0];
          end
          else if(stop_on)
          begin
            TFT_RED  <=color_stop[23:16];
          TFT_GREEN<=color_stop[15:8];
          TFT_BLUE <=color_stop[7:0];
          end
          else if(adelantar_on)
          begin
            TFT_RED  <=color_adelantar[23:16];
          TFT_GREEN<=color_adelantar[15:8];
          TFT_BLUE <=color_adelantar[7:0];
          end
   end
end
a continuación se muestra del histograma de la señal sinoidal en la pantalla tactil y los botones de reproducción.




Después de haber comprobado que se puede graficar en la pantalla táctil un histograma de una señal por medio de Quartus se procede a reemplazar el módulo NCO que se encargaba de enviar la señal sinodal por la señal de la canción.




a continuación se observan los videos del proyecto funcionando, en la parte inferior de la pagina se podra encontrar el archivo .zip(Download this file (wav_player.zip)wav_player.zip) que contiene el codigo completo de la descripción de hardware y el software nios II.


 

 

 

 Enjoy!