Bueno, voy a tratar de documentar el avance que he conseguido en este arduo mes.
Lo último que habia puesto es que tenía algunos problemas al sacar informacion por el SPI usnado poolling, el código que estaba usando era algo así:
if ((line >= lTop) && (line <= lTop + lWid -1)){ //Cada caracter tiene 7 pixeles de alto
ltemp = (line - lTop) * 27 - 64;
LPC_SSP0->DR = ~ltrs[head[0] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[1] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[2] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[3] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[4] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[5] + ltemp]; Wait();
LPC_SSP0->DR = ~ltrs[head[6] + ltemp]; Wait();
}
line es la línea actual en la que el haz de electrones esta dibujando. head[] es el array con el texto a enviar. Este código estaba en la interrupcion de Match 0 del Timer 2. Entonces a los 8useg de haver llegado el pulso de sincronismo horizontal se dispara este evento y si estoy en la linea correcta de la pantalla saco el dato por el SPI. La funcion Wait lo que hacía era verificar el FIFO del SPI. Si estaba lleno esperaba, sino volvia. Si bien eso andaba bastante bien, imprimir un texto era muy dificil y habia que escribir mucho código dificil de mantener, como el que puse arriba.
Por otra parte estube estudiando el tema del DMA. Como los datos estan en memoria y el SPI acepta DMA pensé que era buena opcion enviar todos los datos por DMA. Cuando empecé a esudiar esa posibilidad me di cuenta de algo interesantísmo. Es más facil scara con DMA una línea horizontal COMPLETA que sacar solo las zonas donde hay texto. Entonces si saco una línea completa y configuro el DMA en cada línea, puedo sacar la pantalla completa. Esto abría las puertas a algo que pensé iba a ser muy dificl, el método GRAFICO. Si es más fácil sacar todos los pixeles, entonces simplemente tengo que crear un buffer de video de 2 dimensiones, con la cantidad de filas como pixeles verticales (lineas visibles en pantalla) y la cantidad de Half Word (trabajo el SPI en modo 16 bits) que entran en la pantalla. Luego, el DMA se encargaría de sacar por sí solo cada línea de datos.
Costó bastante la configuracion y debug del DMA pero finalmente esta funcionando.
Veamos un poco el código:
Primero el extint.c
/*****************************************************************************
** Function name: EINT1_Handler
**
** Descriptions: external INT handler
**
** Interrupcion en el Pulso de Sincronismo Horizontal
**
*****************************************************************************/
void EINT1_IRQHandler (void) {
LPC_SC->EXTINT |= 2; // clear interrupt
line++; // Add 1 to current Line on screen
//First FIRST_LINE li
if (line>=FIRST_LINE && line<=(PIX_Y+FIRST_LINE)){ // If I'm on visible Line on screen
LPC_TIM2->TCR = 3; // Reset counter
LPC_TIM2->TCR = 1; // Enable counter
}else{
LPC_TIM2->TCR = 2; // Reset and disable counter
}
}
Cuando llega un pulso de sincronismo horizontal, veridico que esté dentro de los pulsos "visibles" en pantalla, ya que hay varios pulsos despues del pulso vertical que no son lineas visibles. si estamos en linea visible reseteo el TC del timer2 y lo habilito.
El timer 2 esta configurado para que interrumpa a los 8useg (desde el flanco de bajada del pulso de sinc hori hasta el comienzo de la info de video hay ese tiempo).
Veamos entonces que hace la interrupcion del Timer2:
/****************************************************************************/
/* Interrupt Functions */
/****************************************************************************/
void TIMER2_IRQHandler(void) {
if(LPC_TIM2->IR & 1){ // Verificamos que la Int es por Match Register 0
LPC_GPDMACH0->DMACCSrcAddr = (uint32_t) &membuff1[line-FIRST_LINE]; // membuff1[] is the array where I have stored
LPC_GPDMACH0->DMACCControl = PIX_X // Transferencia de PIX_X half word
| (1 << 12) // 0 burst
| (1 << 15) // 0 burst
| (1 << 18) // Half word width
| (1 << 21) // Half word width
| (1 << 26 ); // Source increment.
LPC_GPDMA->DMACIntErrClr |= 0xff; // Clear all DMA interrupts
LPC_GPDMA->DMACIntTCClear |= 0xff;
LPC_GPDMACH0->DMACCConfig |= 1; // enable ch0
LPC_TIM2->TCR = 2; // Reset and disable counter
}
LPC_TIM2->IR |= 0xFF; // Clear all timer interrupts if there are any
}
Aqui simplemente configuramos el canal 0 del DMA, como dato de origen le paso la direccion de la linea actual del buffer de video. Aqui ni bien se habilita el canal, el DMA se encarga de sacar los datos. Lo bueno de esto es que se hace de forma autmática y de fondo, la CPU queda libre!!!!! de otro modo, si quería pintar la pantalla de blanco, por ejemplo, la CPU necesitaba 52uS por línea trabajando x 260 lineas 13.mseg de los 20mseg que tarda el refresco de la imagen... ahora solo le toma un par de instrucciones por línea para configurar el DMA... eso es terrible!!!!. Ni hablar de la simplicidad del código. Ya que al contar con una rutina que sola e encarga de sacar por pantalla el contenido del buffer de video, es como tener un GLCD, solo hay que dedicarse a escribir funciones para llenar el buffer de video!!!
Ese es todo el código necesario para sacar la info y mostrarla en pantalla.
Veamos ahora las funciones para poner texto y gráficos en el buffer de video.
La fuente que estoy usando tiene este formato:
//
// Font data for Berlin Sans FB Demi 12pt
//
// Character bitmaps for Berlin Sans FB Demi 12pt
const short fnt3[] =
{
// @0 ' ' (14 pixels wide)
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
// @0 '!' (14 pixels wide)
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000000000000, //
0b0000000110000000, // ##
0b0000000110000000, // ##
0b0000000000000000, //
0b0000000000000000, //
0b0000000000000000, //
Entonces el código para colocar un caracter en una posicion del buffer de video es:
void OSD_put_char(short X, short Y, char Car){
short i;
for(i=Y; i<(Y+FNT3_H); i++)
membuff1[i][X] = fnt3[(Car - 32) * FNT3_H + (i-Y)];
}
Simplemente se va recorriendo cada fila del caracter y copiando en el buffer, en la poscion deseada. Aquí la coordenada X esta dada en Caracteres, no en pixeles. El 32 que se le resta a Car es debido a que la fuente arranca en el Chr 32 (espacio en blanco).
Para imprimir un texto tengo esta funcion:
// ***********************
// Funcion para enviar una cadena de caracteres
void OSD_put_string(short X, short Y, char *pcString){
int i = 0;
while (pcString[i] != 0) { // loop through until reach string's zero terminator
OSD_put_char(X, Y, pcString[i]); // print each character
X++;
i++;
}
}
Luego empiezan las funciones gráficas. Las funciones las tome de un post de Ariel del foro ucontrol:
http://www.ucontrol.com.ar/forosmf/tutoriales-guias-y-cursos-en-ucontrol/ccs-libreria-de-graficos-para-glcd-k0108/msg24846/#msg24846Para dibujar un punto tengo que hacer un par de pasos totalmente distintos a los del GLCD:
//-----------------------------------------------------------------------
// Dibuja un pixel
//-----------------------------------------------------------------------
void OSD_punto(unsigned short x, unsigned short y, char color){
short CarX;
char PosCar;
CarX = x/16; //Calculo en que Short cae el pixel
PosCar = 15-( 16 * ((float)x/16 - CarX)); //Calculo la posicion dentro del Short del pixel
x = (unsigned short)(1<<(PosCar)); //Armo el Short con ese pixel encendido
membuff1[y][CarX] |= x; //Pongo el Short completo en el buffer
}
Debido a que mi buffer es del tipo short y tengo todo armado para transferir de a 16 bits, es que tengo que convertir de pixel a posicion dentro de los 32 shorts que entran en el ancho de la pantalla.
Luefo para imprimir una línea uso exactemente la misma funcion de Ariel, creo que no toque nada, solo la definicion de variables... de hecho aún no estudie como funciona
:
//-----------------------------------------------------------------------
// Dibuja una linea desde (x1,y1) a (x2,y2) de color (0 o 1)
//-----------------------------------------------------------------------
void OSD_linea(unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, char color)
{
//Declaro variables-------------------
signed short x, y, incremento_x, incremento_y, distancia_x, distancia_y;
signed short P;
short i;
//Calculo las diferencias entre las coordenadas de origen y destino
distancia_x = fabs((signed short)(x2 - x1));
distancia_y = fabs((signed short)(y2 - y1));
//Inicializo x e y con las coordenadas de origen
x = x1;
y = y1;
//Calculo el sentido de los incrementos (positivos o negativos)
//en funcion de la posicion del origen y el destino
if(x1 > x2) incremento_x = -1; else incremento_x = 1;
if(y1 > y2) incremento_y = -1; else incremento_y = 1;
//Si la distancia horizontal es mayor a la vertical...
if(distancia_x >= distancia_y)
{ P = 2 * distancia_y - distancia_x;
for(i=0; i<=distancia_x; ++i)
{
OSD_punto(x, y, color);
if(P < 0)
{ P += 2 * distancia_y;
x += incremento_x; }
else
{ P += 2*distancia_y - 2*distancia_x;
x += incremento_x;
y += incremento_y;}
}
}
//Si la distancia vertical es mayor a la horizontal...
else
{ P = 2 * distancia_x - distancia_y;
for(i=0; i<=distancia_y; ++i)
{ OSD_punto(x, y, color);
if(P < 0)
{ P += 2 * distancia_x;
y += incremento_y; }
else
{ P += 2 * distancia_x - 2 * distancia_y;
x += incremento_x;
y += incremento_y; }
}
}
}
//-----------------------------------------------------------------------
Bueno, para usar esas funciones hago lo siguiente en el main:
for(i=0;i<PIX_Y;i++)
for(j=0;j<PIX_X;j++)
membuff1[i][j] = 0;
OSD_put_string(11, 110, "elgarbe");
OSD_linea(0, 0, 467, 239, 1);
OSD_linea(467, 0, 0, 239, 1);
SSP0Init(); // Inicializo el bus SPI
DMAInit();
tim2_init(0); // Inicializamos el Tmr2 y le establecemos prioridad 0. La mas alta
EINTInit(); // Inicializo las interrupciones externas - LM1881
while ( 1 ){
}
}
El resultado?
Este:
Bueno, ahora sí estoy en condiciones de pasar a las cosas interesantes del proyecto... el horizonte artificial y los datos de los sensores...
Saludos!