sábado, 27 de junio de 2020

Consola serial en PIC en XC16

Chan ya es famoso por ser el autor de la librería para manejo de archivos FAT32 en memorias SD pero tiene muchas otras creaciones geniales. Aquí describiré un ejemplo para usar la librería xprint que permite dotar de forma sencilla a casi cualquier microcontrolador de una pequeña consola de operación o depuración. Usaré un PIC24FJ32GA002 pero entendiendo la idea general se puede portar facilmente a cualquier otro dispositivo.  Primero, deben descargar los archivos xprinf.h y xprintf.c. Esta librería está escrita en ANSI C por lo que es independiente de la plataforma y el compilador. Las tres funciones más importantes que contiene son:
  • void xputs (const char* str)          : análoga a la función puts()
  • void xprintf (const char* fmt, ...) : análoga a la función printf()
  • int xgets (char* buff, int len)        : similar a la función gets()

 xputs() permite escribir una cadena simple. como un "Bienvenidos al programa". xprintf() nos permite dar formato a la cadena, como ("Temperatura %d",temp), como lo haríamos en un C en una PC. xgets() es muy útil y la que la dará mucha presentación a un programa para microcontrolador. Es en esencia muy distinta a su contraparte gets(). Lo que hace xgets es ir capturando en tiempo real lo que se vaya recibiendo el microcontrolador del puerto serial hasta que se presiona ENTER. La misma función va enviado simultáneamente cada carácter a Tx como espejo (aunque esta opción puede desactivarse en los macros del header). ¡Permite incluso borrar caracteres!    

La librería no puede funcionar por si sola. Es necesario asociarla a funciones de escritura y lectura bajo nivel. Lo que sigue ahora es crear un programa que contenga las rutinas para enviar y recibir un byte por puerto serial con la siguiente forma:

//-- Poner byte en el registro Rx
unsigned char uart_getc(void);

//-- Poner byte en el registro Tx

void uart_putc(unsigned char d);


Esta parte del programa siempre será dependiente del dispositivo y compilador. Estas dos rutinas pueden tener cualquier nombre siempre y cuando se asocien en el código principal de la siguiente manera:

//-- Unir UART con funciones de consola
xdev_in(uart_getc);        
xdev_out(uart_putc);

La implementación de las funciones de comunicación serial también están basadas en la implementación de Chan pero las he reescrito de una forma más clara y comentadas lo mejor que pude. Esta implementación utiliza dos colas (registros FIFO), una de envío y otra de recepción. Esto es muy útil porque permite aprovechar las interrupciones de la UART para no perder bytes. Las referencias a páginas son para la data del GB004 pero es la misma información.   

Aislamiento

Si están utilizando baterías para alimentar al PIC o van a estar realizando depuración es una buena práctica utilizar opto-acopladores. Esto también permite utilizar módulos de conversión USB-Serial que no tengan salida de 3.3v:


 

Programa completo:

/*
* File: GA_UART_main.c
* Author: ChaN & Rodolfo Escobar
*
* Created on 27 de junio de 2020, 05:54 PM
*/
//--- Bits de configuración ---
#pragma config POSCMOD = XT // Primary Oscillator Select (XT Oscillator mode selected)
#pragma config FNOSC = PRIPLL // Oscillator Select (Primary Oscillator with PLL module (HSPLL, ECPLL))
#pragma config FWDTEN = OFF // Watchdog Timer Enable (Watchdog Timer is disabled)
#pragma config JTAGEN = OFF // JTAG Port Enable (JTAG port is disabled)
//-----------------------------
#include "xc.h"
#include "xprintf.h"
#define FCY 16000000UL
#include "libpic30.h"
#define BUFFER_SIZE 128 // Buffer UART
#define _DI() __asm__ volatile("disi #0x3FFF") //Deshabilitar todas las interrupciones
#define _EI() __asm__ volatile("disi #0") //Habilitar todas interupciones
//-- Estructuras de datos para UART --
static volatile int TxRun; // Bandera de recepción activa
static volatile struct {
int ri, wi, ct; // Read index, Write index, Data counter
unsigned char buff[BUFFER_SIZE]; // FIFO buffer (Cola)
} TxFIFO, RxFIFO;
//------------------------------------
//-- Interrupciones UART --
// Rutina de interrupción Rx
void __attribute__((interrupt, auto_psv)) _U1RXInterrupt(void){
unsigned char d;
int i;
d = (unsigned char)U1RXREG; // Tomar dato del registro Rx
IFS0bits.U1RXIF = 0; // Bajar bandera de interrupción
i = RxFIFO.ct; // Número de bytes en el RxFIFO
if(i < BUFFER_SIZE){ // No hacer nada si esta lleno
RxFIFO.ct = ++i;
i = RxFIFO.wi;
RxFIFO.buff[i++] = d; // Encolar dato
RxFIFO.wi = i % BUFFER_SIZE; //Actualizar indice
}
}
// Rutina de interrupción Tx
void __attribute__((interrupt, auto_psv)) _U1TXInterrupt(void){
int i;
IFS0bits.U1TXIF = 0;
i = TxFIFO.ct; //Indice del dato en la cola
if(i){ // Hace envio si es diferente de cero
TxFIFO.ct = --i;
i = TxFIFO.ri;
U1TXREG = TxFIFO.buff[i++]; // Envia byte
TxFIFO.ri = i % BUFFER_SIZE;// Actualiza indice
}
else{ // Cola vacía
TxRun = 0; // Terminar transmisión
}
}
//---------------------------
//-- Inicializar modulo UART
void uart_init(unsigned long BaudRate);
//-- Poner byte en el registro Rx
unsigned char uart_getc(void);
//-- Poner byte en el registro Tx
void uart_putc(unsigned char d);
//-- Programa principal --
char Line[256]; // Buffer de entrada de consola
int main(void){
//-- Setup --
TRISBbits.TRISB4 = 0; // Tx
TRISBbits.TRISB5 = 1; // Rx
// Mapeo de pines para UART (ver Sec. 10.4, pag. 129)
RPINR18bits.U1RXR = 5; // Rx -> RP5/RB5 (Tab. 10-2,pag. 130)
RPOR2bits.RP4R = 3; // Tx -> RP4/RB4 (Tab. 10-3, pag. 131)
// Inicializar módulo UART
uart_init(9600);
__delay_ms(100);
// Unir UART con funciones de consola
xdev_in(uart_getc);
xdev_out(uart_putc);
//-----------
//-- Prueba de envío de caracter
uart_putc('H');
uart_putc('o');
uart_putc('l');
uart_putc('a');
uart_putc('\r');
//-- Prueba de biblioteca de consola
int dato = 342;
xputs("-- Consola en PIC24 --\n");
xprintf("El dato en binario es: %016b\n",dato);
xputs("$> "); // prompt
xgets(Line, sizeof Line); // Captura todo antes del enter (permite borrar)
xputs(Line);
xputs("\nFin del programa.\n");
while(1);
return 0;
}
//-- Initializar modulo UART
void uart_init (unsigned long BaudRate){
//-- Desactivar interrupcines Rx/Tx
IEC0bits.U1RXIE = 0; // Pag. 82
IEC0bits.U1TXIE = 0; // Pag 82
//-- Inicializar UART1 (baja velocidad))
U1MODEbits.BRGH = 0; // Modo de baja velocidad
U1BRG = FCY / 16 / BaudRate - 1; // EQ. 17-1 (pag. 190)
U1MODEbits.UARTEN =1;
U1STAbits.UTXEN = 1;
// Limpiar FIFOs Rx/Tx
TxFIFO.ri = 0; TxFIFO.wi = 0; TxFIFO.ct = 0;
RxFIFO.ri = 0; RxFIFO.wi = 0; RxFIFO.ct = 0;
TxRun = 0;
//-- Habilitar interrupciones Rx/Tx
IEC0bits.U1RXIE = 1; // Pag. 82
IEC0bits.U1TXIE = 1; // Pag 82
}
//-- Poner byte en el registro Rx
unsigned char uart_getc(void){
unsigned char d;
int i;
while(!RxFIFO.ct); //Esperar a que haya algo en el FIFO
//-- Desencolar byte --
i = RxFIFO.ri;
d = RxFIFO.buff[i++];
//---------------------
RxFIFO.ri = i % BUFFER_SIZE;
_DI();
RxFIFO.ct--;
_EI();
return d;
}
//-- Poner byte en el registro Tx
void uart_putc(unsigned char d){
int i;
while (TxFIFO.ct >= BUFFER_SIZE) ;// Esperar hasta que TxFIFO tenga espacio
//-- Poner dato en la cola de escritura --
i = TxFIFO.wi;
TxFIFO.buff[i++] = d;
TxFIFO.wi = i % BUFFER_SIZE; // actualizar indice de escritura
//----------------------------------------
_DI();
TxFIFO.ct++;
if (!TxRun) {// Si no se está tramitiendo...
TxRun = 1;
IFS0bits.U1TXIF = 1; //Forzar interrupción
}
_EI();
}
view raw GA_UART_main.c hosted with ❤ by GitHub

No hay comentarios: