- 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(); | |
} |