VGA terminal

Terminal VGA


Version Fr à traduire.

Présentation :

Ce montage permet le piloage d'un moniteur VGA en lui envoyant les trames nécessaires pour afficher du texte (45 colonnes, 40 lignes). On récupère les données provenant de l'UART du dspic et on les affiche sur l'écran..
Nous utilisons un microcontrôleur dsPIC 30F4012 au lieu du dsPIC 30F3010 de chez Microchip comme système numérique. En effet, le 4012 a le double de la mémoire du 3010 pour un brochage identique (28 pins). Il a aussi un bus CAN, mais ce n'est pas pour cette fois :-)
On a besoin de beaucoup mémoire données (data memory) pour stocker le buffer vidéo des 40x45 caractères. C'est-à-dire presque la totalité des 2048 mots de la mémoire données (92%utilisée)

Sources internet qui traitent du sujet :
http://www.circuitdb.com/circuits/id/69
http://yusoft.kulichki.com/english/pic/my_proj.htm
VGA timing information

mots clefs : microcontrôleur dspic 30F4012, Terminal VGA, UART, liaison série.



Affichage sur le moniteur (le temps de pose sans flash a rendu la photo floue)



Carte du montage


Cliquez ici pour un schéma électronique détaillée
Schéma électronique (version RX à 5V ou 3.3V)





Cliquez ici pour un schéma électronique détaillée
Schéma électronique (version RX en RS232 19200 BAUDS)



Configuration des entrées sorties logiques

Les définitions et l'assignation des E/S logiques :
   
  1. // VGA_G sortie directement sur la SPI MOSI
  2. #define VGA_G    LATFbits.LATF3  // Output
  3. #define VGA_debug  LATEbits.LATE0  // Output
  4. #define VGA_HSync  LATEbits.LATE1  // Output
  5. #define VGA_VSync  LATEbits.LATE2  // Output
  6. #define InfoLED   LATDbits.LATD0  // Output
  7.  
  8. //  SPI1CON = 0x013F;  // Master mode, SCK = Fcy/1/1 = 29.4912 MHz 1|1111), CKP=0 CKE=1
  9.                         // sampled on the rising edge of SCLK, Clk idle is low, 8 bits
  10. //  SPI1CON = 0x013B;  // Master mode, SCK = Fcy/2/1 = 14.7456 MHz 1|1011), CKP=0 CKE=1
  11.                         // sampled on the rising edge of SCLK, Clk idle is low, 8 bits
  12. #define SPI1CON_29_5MHz 0x013F
  13. #define SPI1CON_14_7MHz 0x013B
  14.  
  15. //Macro for video handle
  16. #define video_off  VGA_debug=0        //Video (Green Color)
  17. #define video_on    VGA_debug=1
  18. #define hsync_off  VGA_HSync=1    //HSYNC
  19. #define hsync_on    VGA_HSync=0
  20. #define vsync_off  VGA_VSync=1    //VSYNC
  21. #define vsync_on    VGA_VSync=0
  22.  
  23. //-----------------------------------------------------------------------------
  24. //  Setup ports
  25. //-----------------------------------------------------------------------------
  26. void setup_ports(void)
  27. {
  28.   ADPCFG = 0xFFFF;        // all PORTB = Digital(1) ie 1111 1111
  29.   // Clear All Ports Prior to defining I/O
  30.   PORTB=0//Initialize LED pin data to off state
  31.   PORTC=0;
  32.   PORTD=0;
  33.   PORTE=0
  34.   // Now set pin direction registers
  35.   TRISB = 0xFFFF;  // RB0-5 inputs : NC
  36.   TRISC = 0xDFFF;  // U1ATX/RC13 in , U1ATX/RC14 out , inutile pour le UART donc 0xFFFF is also ok
  37.   TRISD = 0xFFFE;  // InfoLED RD0 out, RD1 in NC
  38.   TRISE = 0x01F8;  // VGA_debug RE0 out, VGA_HSync RE1 out, VGA_VSync RE2 out, RE8 in NC : 1 1111 1000
  39.   TRISF = 0xFFF7;  // SDI1/RF2 in, SDO1/RF3 out : 0111 car je dois forcer le MOSI apres le SPI à 0 et non pas le laisser à la dernière valeur
  40.   // SPI pour le output shifté du G(Green component)
  41.   SPI1STAT = 0x8000;  // Enable SPI port
  42.   SPI1CON = SPI1CON_29_5MHz;    // vitesse max du SPI
  43. }
  44. //-----------------------------------------------------------------------------

Au début je voulais utiliser un port d'E/S pour sortir le signal de balayage de la trame vidéo mais c'est trop lent alors j'ai utilisé la sortie SPI MOSI (comme indiqué sur la référence bibliographique citée).
Dans le cas de notre dspic, il faut remettre à 0 cette sortie, sinon elle reste au dernier état après que les 8 bits de la sortie SPI se soient succédés. (encore une fois, la pin E/S RF3 est trop lente (l'exécution de l'instruction VGA_G=0; ) et on utilise une deuxième écriture : SPI1BUF = 0;).



Configuration de l'oscillateur

On choisit la fréquence maximale que permet d'avoir la version -30I du dspic. Avec un quartz de 7.3728 MHz, on obtient ainsi 30 MIPS.
On a besoin de cette puissance de traitement pour pouvoir afficher autant de colonnes !
   
  1. // Q=7.3728 MHz
  2. _FOSC(CSW_FSCM_OFF & XT_PLL16)// 7.3728MHz *16 = 117.9648 MHz /4 = 29.4912 MIPS maxi pour ce pic
  3. _FWDT(WDT_OFF);
  4. _FBORPOR(PBOR_OFF & BORV_27 & PWRT_16 & MCLR_EN);
  5. _FGS(CODE_PROT_OFF);
  6. //-----------------------------------------------------------------------------
  7. //Program Specific Constants
  8. #define FCY 29491200          //Instruction cycle rate (Osc x PLL / 4)
  9. #define T1Period 938          // pour 31.77 us (31.469 kHz de balayage Vertical) à 29.4912 MHz, T1Period = 937=FCY/31469 Hz
  10. #define tcntPRD 31469     // combien de fois pour ariver en 1s avec des pas de 31.77 us : 31469
  11. #define MILLISEC FCY/29491      // 1 mSec delay constant



Trame vidéo

Tout se passe dans la routine d'interruption du Timer 1.
On choisit une période de T1 de 31.77 us (31.469 kHz de balayage). C'est-à-dire que chaque ligne arrive toutes les 31.77 us.
Au début de chacune, notre routine est appelée :
   
  1. //------------------------------------------------------------------------
  2. void __attribute__((interrupt, auto_psv)) _T1Interrupt( void )
  3. {
  4. unsigned char j;
  5. char * _ptr;
  6. const char * _ptr1;
  7. //  InfoLED=1; 
  8.   IFS0bits.T1IF = 0
  9.   hsync_on;  // début de la raster
  10.   //Count number of lines
  11.   if (++linecount == vga_field_line_count)
  12.       {
  13.       linecount = 0;
  14.       //clear pointers for render display buffer
  15.       raw_render = 0;
  16.       y_line_render = 0;
  17.       }
  18.     else
  19.       {
  20.       __builtin_nop();
  21.       __builtin_nop();
  22.       __builtin_nop();
  23.       }
  24.   //Make Vsync length 2 VGA lines
  25.   if ((linecount == 10 )||(linecount == 11 ))
  26.               vsync_on;   //Make here vertical syncronization & HSYNC syncro level on
  27.       else    vsync_off;    //.. & HSYNC syncro level on
  28.  
  29.   video_enable_flg = 1;
  30.  
  31.   if (linecount < 45)
  32.       {
  33.       video_enable_flg = 0;
  34.       //Add to avoid flickering at top display
  35.       __builtin_nop();         
  36.       __builtin_nop();         
  37.       __builtin_nop();         
  38.       __builtin_nop();         
  39.       __builtin_nop();         
  40.       __builtin_nop();         
  41.       }
  42.     else
  43.       {
  44.        //Forming current string for rendering
  45.        if (++y_line_render == vga_symbol_height)
  46.           {
  47.           raw_render++;
  48.           y_line_render = 0;
  49.           }
  50.         else
  51.           {
  52.           __builtin_nop();
  53.           }
  54.       }
  55.   hsync_off; //HSYNC syncro level off
  56.   for (j=0; j<28; j++)      __builtin_nop();
  57.  
  58. // render ici pour être sûr d être synchrone
  59.   if(video_enable_flg)
  60.       {
  61.       if (raw_render > vga_row_countm1)  raw_render=vga_row_countm1;
  62.       //on est ds la zone visible, alors : render
  63.       _ptr = &str_array[raw_render * vga_symbols_per_row];    //Set pointer for render line (display bufffer)
  64.       _ptr1 = &symbol[0][y_line_render];        //Set pointer for render line (character generator)
  65.       //Cycle for render line
  66.       j = vga_symbols_per_row;
  67.       video_on; // pour débug
  68.       while(j--)
  69.         {
  70.         SPI1BUF = ~(*(_ptr1 + (* _ptr++)*vga_symbol_height));
  71.         SPI1BUF = 0// pour effacer le bit restant, mais il faut aller vite
  72.         }
  73.       video_enable_flg=0;
  74.       video_off;  // pour débug
  75.       }
  76. //  InfoLED=0;
  77. }
  78. //---------------------------------------------------------------------

Explications :
On commence à gauche de la zone d'affichage en donnant le signal de synchronisation horizontal par le biais de :
   
  hsync_off; //HSYNC syncro level off
et
   
  hsync_off; //HSYNC syncro level off

Il faut que le timing entre les deux corresponde à 3.77 us (VGA timing information)
On profite du temps pour incrémenter le numéro de la ligne et on vérifie si on est en ligne 10 pour donner le signal de synchronisation vertical :
   
  //Make Vsync length 2 VGA lines
  if ((linecount == 10 )||(linecount == 11 ))
              vsync_on;   //Make here vertical syncronization & HSYNC syncro level on
      else    vsync_off;    //.. & HSYNC syncro level on

On vérifier si on est dans le bord supérieur de l'écran ou dans une zone d'affichage, auquel cas, on récupère la ligne du motif de caractère 12x8 sur laquelle la trame se trouve :
   
       if (++y_line_render == vga_symbol_height)
          {
          raw_render++;
          y_line_render = 0;
          }

Après, on doit être à 3.77 us, c'est à dire le moment de donner la fin du pulse de synchronisation horizontale. On attend alors un petit peu (28 cycles d'horloge) pour arriver à la marge gauche visible de l'écran :
   
  hsync_off; //HSYNC syncro level off
  for (j=0; j<28; j++)      __builtin_nop();

On remarquera que la synchronisation est hyper importante dans la partie là. C'est pour cela que pour chaque if, le else doit comportant autant de nop pour avoir un temps d'excution équivalent dans tous les cas de figure.

On sort alors la ligne (trame) des 40 colonnes de texte (vga_symbols_per_row). C'est un seule ligne donc 1 pixel de hauteur mais sur 40 colonnes x (8 pixels du motif du caractère + espace), l'espacement entre les caractères se fait juste par SPI1BUF = 0; et la boucle while (j--) :
   
// render ici pour être sûr d être synchrone
  if(video_enable_flg)
      {
      if (raw_render > vga_row_countm1)  raw_render=vga_row_countm1;
      //on est ds la zone visible, alors : render
      _ptr = &str_array[raw_render * vga_symbols_per_row];    //Set pointer for render line (display bufffer)
      _ptr1 = &symbol[0][y_line_render];        //Set pointer for render line (character generator)
      //Cycle for render line
      j = vga_symbols_per_row;
      video_on; // pour débug
      while(j--)
        {
        SPI1BUF = ~(*(_ptr1 + (* _ptr++)*vga_symbol_height));
        SPI1BUF = 0// pour effacer le bit restant, mais il faut aller vite
        }
      video_enable_flg=0;
      video_off;  // pour débug
      }

On voit alors toute la puissance d'utilisation du port SPI pour sortir à la suite les 8 bits d'une ligne (trame) du caractère.
Sur d'autres micro-contrôleurs, au mieux, on aurait pu avoir qu'une douzaine de colonnes de texte sans utiliser cette technique !



Récupération des données de l'UART

On peut soit le faire via une routine qui s'exécute hors synchronisation (hors ISR du Timer1). Elle serait appelée par la boucle while (1) du programme principal dès qu'un caractère est reçu et prêt à être lu sur le buffer de reception (U1RXREG) de l'UART.

On peut cependant utiliser l'interruption de l'UART pour prendre les caractères dès qu'il arrive, on notera que si l'on veut effacer tout l'ecran d'un seul coup, in risque de faire planter le dspic car la routine qui efface le buffer prend trop de temps cpu.
On utilisera alors une technique qui conciste à effacer juste la line d'après. Cette manière permet de garder une trace plus longue notamment pour les évènements reçu en fin de page...

   
  1. //---------------------------------------------------------------------
  2. void __attribute__((interrupt, auto_psv)) _U1RXInterrupt(void)
  3. {
  4. // Parser received symbols from UART
  5. unsigned int i;
  6. char * ptr;
  7. unsigned char current_line, received_symbol;
  8.   IFS0bits.U1RXIF = 0// clear interrupt flag
  9.   received_symbol = U1RXREG;
  10.   //Check for overflow display buffer
  11.   if(current_symbol == (vga_row_count*vga_symbols_per_row))
  12.         {
  13. //        ClearDisplayBuffer(); il n y a plus d efface en entier mais ligne par ligne
  14.         current_symbol = 0x0;  // remet au début du tableau
  15. //        // vide le buffer car on a pris bcp de tps à effacer...
  16. //        U1STAbits.OERR=0; // y a surement eu trop de donner alors clear overflow to continue receiving
  17. //        return;
  18.         }
  19.  
  20.   switch ( received_symbol )
  21.     {
  22.     //BackSpace
  23.     case  0x08:
  24.       if(current_symbol)
  25.         {
  26.         str_array[current_symbol] = 0x0;
  27.         str_array[--current_symbol] = 0x0;
  28.       }
  29.       break;
  30.     //TAB
  31.     case  0x09:
  32.       if((current_symbol + 5) < (vga_row_count*vga_symbols_per_row))
  33.         {
  34.         //Add 5 Space
  35.         str_array[current_symbol] = 0x0;
  36.         current_symbol += 5;
  37.       }
  38.       break;
  39.     //RETURN
  40.     case  0x0D:
  41.       current_line = current_symbol / vga_symbols_per_row;
  42.       if(current_line < vga_row_countm1)
  43.         {
  44.         str_array[current_symbol] = 0x0;
  45.         current_symbol = (current_line+1)*vga_symbols_per_row;
  46.         // efface ligne suivante
  47.         i = vga_symbols_per_row;
  48.         //Set pointers for clear string array
  49.         ptr = &str_array[(current_line+1)*vga_symbols_per_row];
  50.         while(i--)  *ptr++ = 0x0;
  51.         }
  52.         else  {
  53.               str_array[current_symbol] = 0x0;
  54.               current_symbol = 0x0;  // remet au début du tableau
  55.               // efface la première ligne
  56.               i = vga_symbols_per_row;
  57.               //Set pointers for clear string array
  58.               ptr = &str_array[0];
  59.               while(i--)  *ptr++ = 0x0;
  60.               }
  61.       break;
  62.     default: str_array[current_symbol++] = received_symbol;
  63.     }
  64. }
  65. //---------------------------------------------------------------------


Download executable and sources.
Download :
vga_dspic_orcad.pdf   Schéma du montage (version RX à 5V ou 3.3V).
vga_dspic_orcad_rs.pdf   Schéma du montage (version RX en RS232).
vga_dspic_orcad.zip   Schématics et pcb (version RX à 5V ou 3.3V).
vga_dspic_orcad_rs232.zip   Schématics et pcb (version RX en RS232).
vga_dspic.zip   Programme source complet (nécessite MPLAB et le compilateur C30).


Back to homepage

Last update : 15/02/2007