| PicManía by RedRaven |
Búsqueda personalizada
|
TEORÍA Y
PRAXIS
DE |
|
Hablando con el PIC hasta
por los codos usando su USART. |
|
|
A modo de Índice: |
|
|
|
|
|
|
|
|
|
|
|
|
|
Una y otra
vez vemos consultas, consultas y consultas sobre este tema ... |
|
Exclusiones |
|
Lo que no voy a tratar son los hardware's necesarios para comunicar distintos dispositivos según los distintos protocolos y/o niveles. Desde el punto de vista de un PIC es totalmente indiferente si se comunica con un PC o con otro PIC y es absolutamente igual si lo hace en RS232 con el uno que en TTL con el otro. |
|
Propósito |
|
Escribo este
artículo en un intento por clarificar conceptos, organizarlos y mostrarlos
de la forma mas clara y sencilla posible, unificarlos y crear una
jerarquía de menos a más según sea lo que necesitemos creciendo en
complejidad. |
|
Metodología |
|
Voy a ir escribiendo
sub-artículos
cerrados y completos. Cada uno con sus respectivos Objetivos,
Implementación en C y su Programa Fuente
completo funcionando. |
|
Condiciones del Test |
|
Todos los programas los voy a escribir en CCS C, versión 3.242, y van a ser probados sobre un PIC 18F4550 con un cristal de 20 Mhz con el PLL activado para generar un ciclo de reloj de 48 Mhz, conectado a un PC mediante una interface hardware RS232 construida alrededor de un MAX232, transmitiendo y recibiendo a 115200 baudios, 8 bits, 1 bit de Stop y sin Paridad (none) . Por ello los fuses que aparecerán en los ejemplos y la definición CCS del canal Serie va a ser: |
|
///////////////////////////////////////////// // Definiciones de configuración ///////////////////////////////////////////// #include <18f4550.h> ///////////////////////////////////////////// // Fuses y ajuste de Clock ///////////////////////////////////////////// #fuses HSPLL, NOMCLR, PUT, BROWNOUT, BORV43, NOWDT, NOPROTECT, NOLVP #fuses NODEBUG, USBDIV, PLL5, CPUDIV1, VREGEN, CCP2B3 #use delay(clock=48000000) ///////////////////////////////////////////// // Canal de Comunicación : usart ///////////////////////////////////////////// #define TTL_TX PIN_C6 #define TTL_RX PIN_C7 #use rs232(baud=115200, xmit=TTL_TX, rcv=TTL_RX) |
||
|
Esta parte va a ser común a todos los
ejemplos y es la única, salvo que expresamente se diga lo contrario, que
es necesario adaptar a cada uno de los distintos modelos y condiciones de
vuestro PIC en particular. Si esta cabecera corresponde con vuestro PIC
todos los ejemplos que tratemos deben funcionar correctamente desde el
principio. |
|
//////////////////////////////////////////////// // Definiciones de configuración //////////////////////////////////////////////// #include <16f876a.h> //////////////////////////////////////////////// // Fuses y ajuste de Clock //////////////////////////////////////////////// #fuses XT,NOWDT,NOPROTECT,NOPUT,NOBROWNOUT #use delay(clock=4000000) //////////////////////////////////////////////// // Canal de Comunicación : usart //////////////////////////////////////////////// #define TTL_TX PIN_C6 #define TTL_RX PIN_C7 #use rs232(baud=9600, xmit=TTL_TX, rcv=TTL_RX) |
||
|
Como veis las modificaciones son mínimas. El resto lo dejamos para cada uno de los artículos que tratemos. Espero que os guste y os sirva. |
|
|
| Objetivos |
|
Lo mas simple que se puede hacer con un PIC en esto de las comunicaciones es recibir un carácter y devolverlo a modo de eco. Sin manos, sin pies y casi sin bicicleta, esperamos a recibir algo e inmediatamente lo reenviamos a su destino. |
| Implementación en C |
|
Para
implementarlo en C lo que vamos a hacer es montar nuestro sempiterno bucle
infinito del While(true) y dentro de él vamos a utilizar las
funciones built-in de C llamadas kbhit() que devuelve true cuando
hay algo pendiente de recibir (comprueba si está en alto el bit 5
RCIF del registro PIR1), getc() que obtiene,
"lee", el byte recibido (descarga RCREG) y putc() que
emite un byte por la USART (escribe TXREG). |
| Programa Fuente |
|
Recuerda que la cabecera es la misma para todos los programas y la tienes descrita en nuestra Introducción de esta serie. |
| void main() { char rec; printf("TyP_Serie_TTL\r\n"); // Al inicio escribe para ver que emite correctamente do{ // Bucle ... if(kbhit()){ // Si hay algo pendiente de recibir .... rec=getc(); // recibe el caracter ... putc(rec); // ... y lo escribes ... } // ... después continúa ... }while(TRUE); // ... hasta el infinito. } |
||
|
Descargar typ_serie_ttl_01_eco_main.c |
| Al inicio he incluido una línea de la forma printf("TyP_Serie_TTL\r\n"); para ver que emite correctamente nada mas darle alimentación al PIC. |
![]() |
|
Realmente la variable rec de
tipo char que hemos escrito no es necesaria ya que las
líneas : |
|
rec=getc();
// recibe el caracter ... putc(rec); // ... y lo escribes ... |
| las podemos escribir en una sola de la forma: |
|
getc(putc(rec)); // escribe lo que recibas ... |
|
|
| Objetivos |
|
No tan simple como nuestro anterior ejemplo pero casi. Esta vez vamos a hacer que el eco se produzca de igual forma que anteriormente pero haciendo uso de la interrupción por recepción serie, INT_RDA, que se nos va a disparar cuando nuestro PIC reciba un carácter por dicho canal. |
| Implementación en C |
|
Para
implementarlo en C lo que vamos a hacer es simplemente mover la recepción
y reenvío del carácter desde el bucle infinito en el main(), que es
donde lo teníamos antes, a una rutina especial que solo se ejecutará cada
vez que se reciba un carácter por la USART. Recordad que la función
escrita a continuación de la directiva #int_rda será la que se
ejecute cuando se dispare dicha interrupción. De ahí que le haya llamado
rda_handler o manejador de la interrupción RDA. |
| Programa Fuente |
|
Recuerda que la cabecera es la misma para todos los programas y la tienes descrita en nuestra Introducción de esta serie. |
|
//////////////////////////////////////////////// // INTERRUPCIONES : RDA Recepción USART /////////////////////////////////////////////// #int_rda void rda_handler(void){ putc(getc()); // recibe el caracter y lo reenvía... } /////////////////////////////////////////////// // MAIN /////////////////////////////////////////////// void main() { printf("TyP_Serie_TTL\r\n"); // Al inicio escribe para ver que emite correctamente printf("Method : Interrupt\r\n"); enable_interrupts(int_rda); // Habilitamos la interrupción por recepción serie enable_interrupts(global); // Habilitamos las interrupciones do{ // Bucle ... // Aqui no tenemos nada que hacer ... // pero podemos hacer lo que queramos. }while(TRUE); // ... hasta el infinito. } |
||
|
Descargar typ_serie_ttl_02_eco_interrupcion.c |
| Como podéis ver los resultados son idénticos al ejemplo anterior, pero con la inmensa diferencia de que en el bucle infinito del main() podemos escribir cualquier otro código que haga cualquier otra cosa con la seguridad de que al recibir un carácter lo tendremos disponible.. |
![]() |
|
|
| Objetivos |
|
Entramos ya en terrenos con una utilidad directa. En este tercer ejemplo tenemos la intención de recibir comandos desde fuera del PIC y ejecutar distintas funciones según sea lo recibido. Pero además vamos a tener un pequeño "feed-back" para poder saber qué es lo que está recibiendo el PIC, de forma que el PIC nos devuelva el comando recibido y los resultados de la función que se ejecuta, o algo que nos indique que el comando es erróneo. |
| Implementación en C |
|
Lo que vamos
a hacer es declarar una variable de tipo char en la RAM a la
que llamaremos Command, que inicializaremos a cero 0x00 en el
main() y sobre la que recogeremos lo que recibamos por el canal serie
con la interrupción #INT_RDA, tal como vimos en el ejemplo
anterior. |
|
En el main(),
dentro del bucle infinito While(true), comprobaremos en cada vuelta
si el contenido de Command es distinto de de
0x00 (recordad que lo inicializamos con este valor) ya que
entonces significaría que si hemos recibido algo. |
|
Entonces lo
primero que haremos será monitorizar esta recepción mediante un printf
y después usando un switch ejecutaremos la función seleccionada. En
caso de no ser uno de los que hemos contemplado en el switch devolveremos
una señal en tal sentido. |
|
En cualquier caso volveremos a inicializar a 0x00 la variable Command para no volver a ejecutar la misma función hasta no recibir de nuevo el comando correspondiente. |
| Programa Fuente |
|
Recuerda que la cabecera es la misma para todos los programas y la tienes descrita en nuestra Introducción de esta serie. |
|
//////////////////////////////////////////// // RAM //////////////////////////////////////////// char Command; //////////////////////////////////////////// // INTERRUPCIONES : RDA Recepción USART //////////////////////////////////////////// #int_rda void rda_handler(void){ Command=getc(); // recibe el comando ... } // Ejecutar según comando recibido ///////// void first_function(void){ printf(" > Exec first_function()\r\n"); } void second_function(void){ printf(" > Exec second_function()\r\n"); } //////////////////////////////////////////// void main() { printf("TyP_Serie_TTL\r\n"); // Al inicio escribe para ver que emite correctamente printf("Method : Interrupt\r\n"); printf("Commands mono-character\r\n"); enable_interrupts(int_rda); // Habilitamos la interrupción por recepción serie enable_interrupts(global); // Habilitamos las interrupciones Command=0x00; // Inicializamos Command para asegurar que vale 0x00 do{ // inicio del Bucle infinito if(Command!=0x00){ // Si hemos recibido un comando ... printf(" < %c\r\n",Command); // Lo mostramos para saber que ha recibido ... switch(Command){ case '1': // y si éste es el caracter ASCII '1' entonces ... first_function(); // llamamos a la primera funcion. break; case '2': // pero si éste es el caracter ASCII '2' entonces ... second_function(); // llamamos a la segunda funcion. break; default: printf(" < ?\r\n");// si no es ninguno de los anteriores protestamos ... break; } Command=0x00; // y volvemos a inicializar Command para que no // se vuelva a ejecutar hasta volver a recibirlo. } }while(TRUE); // final del Bucle infinito } // Fin del programa //////////////////////////////////////////// |
||
|
Un detalle importante a tener en cuenta con este modo de tratar los comandos recibidos: Aunque la USART no pierda ningún carácter que pueda enviársele solo se ejecutarán aquellos que sean recibidos fuera de la ejecución de una de las funciones asociadas a ellos, y esto es debido a que tras la ejecución de cualquiera de ellas ponemos a 0x00 la variable Command, por eso cualquier cosa recibida durante la ejecución no se tendrá en cuenta. |
![]() |
|
|
|
Objetivos |
|
Las 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 tecla [Enter]. |
| Implementación en C |
|
El 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 carácter 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 carácter 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 respectivamente, 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
(en este 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 |
|
Recuerda que la cabecera es la misma para todos los programas y la tienes descrita en nuestra Introducción 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 /////////////////////////////////////// |
||
|
Descargar typ_serie_ttl_04_comando_buffer_1.c |
|
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. |
![]() |
Esta página se modificó el 27/12/2008
|