Título : 4.1.- Recibiendo sobre un Buffer. 1ª ParteObjetivosLas más de las veces todo lo que hemos visto hasta ahora de
Comandos de Un Solo Carácter se nos queda corto, ya sea porque necesitamos comandos mas largos, usando palabras en lugar de solo una letra, ya sea porque necesitamos argumentos, o sea datos añadidos a nuestro comandos que deben ser procesados a la llegada de éstos. Todo esto no es posible montarlo procesando uno a uno los caracteres que le vamos enviando al PIC.
Hay que hacerlo recibiendo y acumulando en la memoria del PIC todo aquello que necesitamos y enviando como último carácter uno especial que dispare su procesado. Esto es lo que se conoce como "
recibir sobre un buffer" y es lo que vamos a estudiar aquí y ahora: Recibir caracteres sobre un buffer, uno a uno, y procesarlo cuando se reciba uno en concreto, yo siempre uso 0x0D [
Retorno de Carro] o sea al pulsar la telca [
Enter].
Implementación en CEl primer recurso que necesitamos es el
Buffer. En C nuestro buffer va a ser una matriz (un arreglo) de caracteres, también conocido como string, o sea n caracteres consecutivos en la memoria del PIC, de forma que el primer carácter que recibamos lo guardemos en la primera posición del buffer, la 0, el segundo en la siguiente, la 1, y así sucesivamente hasta que le enviemos el carácter especial de procesado o que se nos acabe el buffer porque hayamos alcanzado su final.
Muy importante es la
longitud máxima que vamos a darle a nuestro buffer, o sea el máximo número de caracteres que va a tener, tanto para reservar la memoria RAM correspondiente como para detectar si hemos alcanzado el final del mismo y no podemos seguir recibiendo sobre él so pena de sobrescribir otras zonas de la memoria del PIC. Así que vamos a definir una
constante con esta Longitud Máxima del buffer que utilizaremos después para estos dos cometidos: Declarar el buffer en la RAM y controlar la recepción máxima de caracteres sobre él.
Además necesitamos un recurso añadido que nos diga a qué posición debe guardarse el siguiente carácter que se reciba. Esto lo vamos a hacer declarando en RAM una variable de tipo entero que inicialmente pondremos a 0 de forma que al recibir el primer carácter lo guardemos en esa posición e incrementamos nuestro
índice, así al llegar el siguiente lo guardaremos en 1 y volveremos a incrementar el índice. ¿Hasta cuando? pues hasta recibir el carácter especial de final o llegar al final físico declarado con la constante anterior.
Esta declaración y definición de nuestro buffer en CCS C se hace de la siguiente forma:
///////////////////////////////////////
// Constantes
///////////////////////////////////////
int const lenbuff=32; // Longitud máxima del buffer
///////////////////////////////////////
// RAM
///////////////////////////////////////
int xbuff=0x00; // Índice: siguiente char en cbuff
char cbuff[lenbuff]; // Buffer de recepción
Con
lenbuff definimos la máxima longitud del buffer que lo usamos al declarar el propio buffer en
cbuff[lenbuff] y utilizaremos
xbuff como índice del mismo, poniéndolo a cero para comenzar e incrementando su valor cada vez que guardemos un carácter en el buffer.
Tenemos entonces ya lo fundamental, que es el "
receptáculo" donde guardar lo recibido y los recursos para ello. Tenemos ahora que recibirlo y guardarlo efectivamente.
Para ello vamos a utilizar la
Interrupción por Recepción Serie que vimos en
Comandos de un solo carácter pero que en aquel caso era un simple
command=getc() y en el que nos ocupa va a ser un mucho mas complejo
add_2_cbuff() que es una función donde vamos ha hacer todo lo necesario para recoger el carácter recibido y dejar el buffer preparado para el siguiente carácter por recibir.
La interrupción RDA y el añadir lo recibido al Buffer podría quedar de esta forma:
///////////////////////////////////////
// INTERRUPCIONES : RDA Recepción USART
///////////////////////////////////////
#int_rda
void serial_isr() { // Interrupción recepción serie USART
char rcvchar=0x00; // último caracter recibido
if(kbhit()){ // Si hay algo pendiente de recibir ...
rcvchar=getc(); // lo descargo y ...
add_2_cbuff(rcvchar); // lo añado al buffer
}
}
void add_2_cbuff(char c){
switch(c){
case 0x0D: // Enter -> Habilita Flag para procesar comando
flagcommand=1;
break;
default: // Añade caracter recibido al Buffer
cbuff[xbuff++]=c;
}
}
Como veis en el código anterior distinguimos en lo recibido si es cualquier cosa o es el carácter especial
0x0D en cuyo caso ponemos un flag en alto.
Pero le vamos a dar un vuelta de tuerca más.
Ya que estamos interactuando con nuestro PIC vamos a hacerlo de forma mas completa. Por un lado vamos a implementar alguna forma de
feed-back, algo que nos diga cómo va nuestro buffer llenándose, y algo mas de control sobre él. Por ejemplo una tecla para borrar el último carácter recibido y poder así corregir un error de digitación o incluso una tecla especial para borrar todo el buffer almacenado hasta el momento. Así convertiremos las teclas
Backspace y
Escape, códigos ASCII
0x08 y
0x1B resprectivamente, en teclas especiales también junto a nuestro anterior
0x0D.
Introducimos entonces una nueva función a la que vamos a llamar
echo_sel() por "
Eco selectivo" para monitorizar lo que estamos enviándole al PIC y también vamos a modificar nuestra
add_2_cbuff() anterior para contemplar las nuevas posibilidades de borrado del buffer.
La función
echo_sel() la vamos a llamar después de guardar lo recibido en el buffer y así nos irá informando de cómo está el buffer y de si hemos pulsado alguna de las teclas especiales, mostrándonos el resultado sobre el buffer.
El código queda entonces de la siguiente forma:
///////////////////////////////////////
// INTERRUPCIONES : RDA Recepción USART
///////////////////////////////////////
#int_rda
void serial_isr() { // Interrupción recepción serie USART
char rcvchar=0x00; // último caracter recibido
if(kbhit()){ // Si hay algo pendiente de recibir ...
rcvchar=getc(); // lo descargo y ...
add_2_cbuff(rcvchar); // lo añado al buffer y ...
echo_sel(rcvchar); // hago eco selectivo (si procede).
}
}
void add_2_cbuff(char c){
switch(c){
case 0x0D: // Enter -> Habilita Flag para procesar comando
flagcommand=1;
break;
case 0x08: // Del -> Borra último caracter del Buffer
if(xbuff>0) cbuff[--xbuff]=0x00;
break;
case 0x01B: // Esc -> Borra el Buffer completamente
init_cbuff();
break;
default: // Añade caracter recibido al Buffer
cbuff[xbuff++]=c;
}
}
void echo_sel(char c){
switch(c){
case 0x0D: // Si he pulsado la tecla [Intro]
printf("\r\n[Ent]\r\n");
break;
case 0x08: // Si he pulsado la tecla [Retroceso]
printf("\r\n[Del]\r\n>%s",cbuff);
break;
case 0x1B: // Si he pulsado la tecla [Escape]
printf("\r\n[Esc]\r\n>");
break;
default: // Echo de cualquier otro caracter
putc(c);
}
}
Fijaos que al pulsar la tecla
Backspace lo único que hacemos es decrementar la variable índice del buffer y ponemos esa posición a 0x00, mientras que por el contrario cuando pulsamos la tecla
Escape lo que hacemos es borrar todo el buffer y poner dicho índice a cero.
Esto último lo hacemos con la función
init_cbuff()void init_cbuff(void){
int i;
for(i=0;i<lenbuff;i++){// Bucle que pone a 0 todos los
cbuff[i]=0x00; // caracteres en el buffer
}
xbuff=0x00; // Inicializo el indice de siguiente caracter
}
Con lo que ya lo tenemos casi todo. Solo constatar que el
main() es hiper-simple ya que lo único que hacemos en él, aparte de habilitar la correspondiente interrupción es esperar a que el
flag para procesar comando se ponga en alto, cosa que solo haremos en
add_2_cbuff() cuando recibamos el
0x0D. En ese caso ejecutaremos la función
commad_process() donde haremos lo que nos de la gana con lo recibido (es ente ejemplo lo copiamos a otra variable y jugamos con los
printf para mostrarlo)
///////////////////////////////////////
// MAIN
///////////////////////////////////////
void main() {
init_cbuff(); // Borra buffer al inicio
enable_interrupts(int_rda); // Habilita Interrupción RDA
enable_interrupts(global); // Habilita interrupciones
do {
if(flagcommand) commad_process(); // Hay algo pendiente de procesar y lo procesa.
} while (TRUE);
}
El programa completo incluye una presentación previa donde se explica lo que se puede hacer con este programa y alguna cosa más irrelevante para el funcionamiento descrito aquí.
Programa Fuente CompletoRecuerda que la cabecera es la misma para todos los programas y la tienes descrita en el
primer post de esta serie.
///////////////////////////////////////
// Constantes
///////////////////////////////////////
int const lenbuff=32; // Longitud máxima del buffer
///////////////////////////////////////
// RAM
///////////////////////////////////////
int xbuff=0x00; // Índice: siguiente char en cbuff
char cbuff[lenbuff]; // Buffer de recepción
int1 flagcommand=0; // Flag para comando disponible
///////////////////////////////////////
// Funciones de Buffer
///////////////////////////////////////
// Echo selectivo ----------------------
void echo_sel(char c){
switch(c){
case 0x0D: // Si he pulsado la tecla [Intro]
printf("\r\n[Ent]\r\n");
break;
case 0x08: // Si he pulsado la tecla [Retroceso]
printf("\r\n[Del]\r\n>%s",cbuff);
break;
case 0x1B: // Si he pulsado la tecla [Escape]
printf("\r\n[Esc]\r\n>");
break;
default: // Echo de cualquier otro caracter
putc(c);
}
}
// Inicia a \0 cbuff -------------------
void init_cbuff(void){
int i;
for(i=0;i<lenbuff;i++){// Bucle que pone a 0 todos los
cbuff[i]=0x00; // caracteres en el buffer
}
xbuff=0x00; // Inicializo el indice de siguiente caracter
}
// Añade a cbuff -----------------------
void add_2_cbuff(char c){
switch(c){
case 0x0D: // Enter -> Habilita Flag para procesar comando
flagcommand=1;
break;
case 0x08: // Del -> Borra último caracter del Buffer
if(xbuff>0) cbuff[--xbuff]=0x00;
break;
case 0x01B: // Esc -> Borra el Buffer completamente
init_cbuff();
break;
default: // Añade caracter recibido al Buffer
cbuff[xbuff++]=c;
}
}
///////////////////////////////////////
// Procesador de Comandos
///////////////////////////////////////
void commad_menu(void){
printf("\r\nTyP_Serie_TTL\r\n");
printf("Método : Recibiendo sobre Buffer\r\n\n");
printf("[Enter] Procesa el buffer recibido.\r\n");
printf("[Escape] Borra todo el buffer.\r\n");
printf("[Delete] Borra último carácter del buffer.\r\n");
printf("\r\n\r\n>");
}
void commad_process(void){
int i;
char cmd[lenbuff]; // Comando
flagcommand=0; // Desactivo flag de comando pendiente.
printf("Procesando ...\r\n");
strcpy(cmd,cbuff); // Lo copio para procesarlo
printf("Rec. Buffer <%s>\r\n",cmd); // ... y lo muestro
init_cbuff(); // Borro buffer.
printf("Procesado.\r\n\r\n>"); // Monitorizo procesado.
}
///////////////////////////////////////
// INTERRUPCIONES : RDA Recepción USART
///////////////////////////////////////
#int_rda
void serial_isr() { // Interrupción recepción serie USART
char rcvchar=0x00; // último caracter recibido
if(kbhit()){ // Si hay algo pendiente de recibir ...
rcvchar=getc(); // lo descargo y ...
add_2_cbuff(rcvchar); // lo añado al buffer y ...
echo_sel(rcvchar); // hago eco selectivo (si procede).
}
}
///////////////////////////////////////
// MAIN
///////////////////////////////////////
void main() {
delay_ms(2000); // Espero a estabilizar antes de actuar
commad_menu(); // Nos presentamos
init_cbuff(); // Borra buffer al inicio
enable_interrupts(int_rda); // Habilita Interrupción RDA
enable_interrupts(global); // Habilita interrupciones
do {
if(flagcommand) commad_process(); // Hay algo pendiente de procesar y lo procesa.
} while (TRUE);
}
///////////////////////////////////////
// Fin de programa
///////////////////////////////////////
Aquí podéis ver un ejemplo de este programa funcionando. Un primera parte donde se presenta nuestro PIC y nos informa de sus posibilidades, es la parte de la función
commad_menu() y después un ejemplo de escribir una serie de caracteres pulsando a continuación [Escape] para borrar todo lo escrito, y otro con la eliminación del último caracter escrito y su posterior procesado.
Continuará.