0
\$\begingroup\$

So, I'm prototyping a project for my school, and I have to read serial data from a GPS module (uBlox NEO-6M) and then do some calculations about velocity and distance and turn on some leds. For the moment I'm displaying the recived data onto a 16x2 LCD. The point is that I cannot read serial data succesfully. I know that serial data is present because I can read it on my computer with an FTDI chip. And I know that the LCD library works fine since its the one my school has been giving to students for years (it's a port from the arduino lib).

For now, I've traced where the problem is. The thing is that I don't know how to fix it, since it's a read-only register. I'm using this UART library. I've modified the required parameters to work with my code/setup (such as _XTAL_FREQ and RX/TX pins/ports).

The thing is that when first powering up the uC, I usually see something on the LCD (sometimes actual data, and sometimes just garbage, or partial receptions). But then, that data won't update, even though it should be (while(1){}).

The problem is on the UARTReadChar() function. I figured this out because I used the debug mode on MPLAB X and the PC was stuck on the while loop while (!UARTDataReady()).

Now, I think this could be caused by the 0.16 error factor that the EUSART module has in my current configuration (9600bps, 4MHz FOSC, HI-SPEED mode, Asynchronous), but I dont really know how to fix it.

Here is my code and libraries:

config.h

/* Microchip Technology Inc. and its subsidiaries.  You may use this software 
 * and any derivatives exclusively with Microchip products. 
 * 
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS".  NO WARRANTIES, WHETHER 
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED 
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A 
 * PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION 
 * WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION. 
 *
 * IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, 
 * INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND 
 * WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS 
 * BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE 
 * FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS 
 * IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF 
 * ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE 
 * TERMS. 
 */

/* 
 * File:   
 * Author: 
 * Comments:
 * Revision history: 
 */

// This is a guard condition so that contents of this file are not included
// more than once.  
#ifndef XC_HEADER_TEMPLATE_H
#define XC_HEADER_TEMPLATE_H

// PIC16F1519 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = ON       // MCLR Pin Function Select (MCLR/VPP pin function is MCLR)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = ON        // Internal/External Switchover (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config VCAPEN = OFF     // Voltage Regulator Capacitor Enable bit (VCAP pin function disabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LPBOR = OFF      // Low-Power Brown Out Reset (Low-Power BOR is disabled)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

#endif  /* XC_HEADER_TEMPLATE_H */

drv_lcd.h

#ifndef drv_lcd
#define drv_lcd

#include <xc.h>
#include <stdio.h>

/*
 * Libreria para manejo de LCD con el controlador HD44780
 * Basada en la libreria original de arduino LiquidCrystal
 * Por Enrique Walter Philippeaux, el día 5 de Mayo de 2018
 */

/*
 *  Libreria para el manejo de un display LCD
 *  Utiliza las rutinas de delay.c y funciona con XC8
 *  Este codigo conecta al PIC con el controlador estandar 
 *  HD44780 de Hitachi. Usa el modo de 4 bits, con el hardware 
 *  conectado segun como defina las salidas LCD_X.
 *  
 *  Para utilizar la libreria, realize la configuracion correspondiente en el archivo
 *  "drv_lcd.h" ( al final de este ) y llame luego de todas las rutinas de configuración (TRISA, TRISB, ...)
 *  a la funcion lcd_init();
 */

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

//Variables para uso interno de la librería
char _displayfunction = 0;
char _displaycontrol = 0;
char _displaymode = 0;

void LCD_init(); //Llamar en el main
void LCD_clear(); //Limpia el LCD
void LCD_home(); //Reinicia el cursor y lo lleva a la posicion 0
void LCD_setCursor(char columna, char fila); //Se usa para mover el cursor
char LCD_print(const char * s); //Para escribir cadenas de texto
void LCD_noDisplay(); //Para dejar de mostrar los caracteres
void LCD_display(); //Para volver a mostrar los caracteres
void LCD_noCursor(); //Para apagar el cursor (se parece al guion bajo)
void LCD_cursor(); //Para encender el cursor (se parece al guion bajo)
void LCD_noBlink(); //Para que el cursor deje de titilar
void LCD_blink(); //Para que el cursor titile
void LCD_scrollDisplayLeft(); //Para mover todo el texto del lcd 1 caracter a la izquierda
void LCD_scrollDisplayRight(); //Para mover todo el texto del lcd 1 caracter a la derecha
void LCD_leftToRight(); //Para introducir el texto de izquierda a derecha
void LCD_rightToLeft(); //Para introducir el texto de derecha a izquierda
void LCD_autoscroll(); //Justifica el texto a la derecha desde el cursor
void LCD_noAutoscroll(); //Justifica el texto a la izquierda desde el cursor
void LCD_createChar(char location, char charmap[]); //Permite crear un caracter según lo siguiente:
//char carac1[] = {
//    0B01110,
//    0B10001,
//    0B10001,
//    0B01111,
//    0B01111,
//    0B10001,
//    0B10001,
//    0B01110
//};

//LCD_createChar(0, carac1); Para cargar el caracter en la RAM del LCD, posiciones 0-7 (max 8 car. personalizados)
//LCD_write(0); Para escribir el caracter personalizado en la posicion 0 de la ram en la pantalla

void LCD_command(char value); //Comando de bajo nivel para enviar un comando al lcd

void LCD_write(char value); //Funcion para escribir un char en el LCD

void LCD_send(char value, char mode); //Comando de bajo nivel para enviar los bits al lcd
void LCD_RS_SET(char value); //Comando de bajo nivel para setear el pin RS
void LCD_EN_SET(char value); //Comando de bajo nivel para setear el pin EN
void LCD_pulseEnable(); //Comando de bajo nivel para hacer un pulso en el pin EN
void LCD_write4bits(char a); //Comando de bajo nivel para escribir los pines D4-D7


//Definir el conexionado del lcd!!!

#define LCD_RS      (PORTEbits.RE0)
#define LCD_RS_TRIS (TRISEbits.TRISE0)
#define LCD_EN      (PORTEbits.RE1)
#define LCD_EN_TRIS (TRISEbits.TRISE1)
#define LCD_D4      (PORTDbits.RD4)
#define LCD_D4_TRIS (TRISDbits.TRISD4)
#define LCD_D5      (PORTDbits.RD5)
#define LCD_D5_TRIS (TRISDbits.TRISD5)
#define LCD_D6      (PORTDbits.RD6)
#define LCD_D6_TRIS (TRISDbits.TRISD6)
#define LCD_D7      (PORTDbits.RD7)
#define LCD_D7_TRIS (TRISDbits.TRISD7)

//#define TRISONINIT

#endif

uart.h

/* 
 * File:   UART.h
 * Author: Alex Saucet
 *
 * Created on August 6, 2017, 10:33 AM
 * 
 * Library for UART communication with a 8-bit PIC microcontroller. 
 * Change the values below for the clock frequency and the UART pins used.
 * 
 */

#ifndef UART_H
#define UART_H

#include <xc.h>
#include <stdint.h>

/****** CHANGE VALUES BELOW ******/
// System clock frequency
#define _XTAL_FREQ  4000000

// Define the pins used
#define UART_TRIS_RX     TRISCbits.TRISC7
#define UART_TRIS_TX     TRISCbits.TRISC6

/**
 * Initialize the UART module with given baud rate and speed mode
 * @param baud_rate
 * @param BRGH speed mode (high speed or low speed)
 */
void UARTInit(const uint32_t baud_rate, const uint8_t BRGH);

/**
 * Send a character
 * @param c character to send
 */
void UARTSendChar(const char c);

/**
 * Send a string, until '\0' is read, up to max_length characters
 * @param str
 * @param max_length
 */
void UARTSendString(const char* str, const uint8_t max_length);

/**
 * Determines whether a new data is available to read
 * @return 
 */
uint8_t UARTDataReady();

/**
 * Read one character
 * @return  byte read
 */
char UARTReadChar();

/**
 * Read a string, until we read '\0', up to max_length characters.
 * @param buf   pre-allocated buffer
 * @param max_length
 * @return number of bytes read
 */
uint8_t UARTReadString(char *buf, uint8_t max_length);

#endif  /* UART_H */

drv_lcd.c

#include <xc.h>
#include <stdio.h>
#include "drv_lcd.h"


void LCD_init()
{
    _displayfunction = (LCD_4BITMODE | LCD_2LINE);

#ifdef TRISONINIT
    LCD_RS_TRIS = 0;
    LCD_EN_TRIS = 0;
    LCD_D4_TRIS = 0;
    LCD_D5_TRIS = 0;
    LCD_D6_TRIS = 0;
    LCD_D7_TRIS = 0;
#endif
    
    // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
    // according to datasheet, we need at least 40ms after power rises above 2.7V
    // before sending commands. Arduino can turn on way before 4.5V so we'll wait 50
    __delay_ms(50);
    
    // Now we pull both RS and R/W low to begin commands
    
    LCD_RS_SET(0);
    LCD_EN_SET(0);
    LCD_D4 = 0;
    LCD_D5 = 0;
    LCD_D6 = 0;
    LCD_D7 = 0;

    //put the LCD into 4 bit mode
    // this is according to the hitachi HD44780 datasheet
    // figure 24, pg 46

    // we start in 8bit mode, try to set 4 bit mode
    LCD_write4bits(0x03);
    __delay_us(4500); // wait min 4.1ms

    // second try
    LCD_write4bits(0x03);
    __delay_us(4500); // wait min 4.1ms

    // third go!
    LCD_write4bits(0x03);
    __delay_us(150);

    // finally, set to 4-bit interface
    LCD_write4bits(0x02);

    // finally, set # lines, font size, etc.
    LCD_command(LCD_FUNCTIONSET | _displayfunction);

    // turn the display on with no cursor or blinking default
    _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
    LCD_display();

    // clear it off
    LCD_clear();

    // Initialize to default text direction (for romance languages)
    _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
    // set the entry mode
    LCD_command(LCD_ENTRYMODESET | _displaymode);

}

/********** high level commands, for the user! */
void LCD_clear()
{
    LCD_command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
    __delay_ms(2); // this command takes a long time!
}

void LCD_home()
{
    LCD_command(LCD_RETURNHOME); // set cursor position to zero
    __delay_ms(2); // this command takes a long time!
}

void LCD_setCursor(char columna, char fila)
{
    switch (fila)
    {
        case 0:
            LCD_command(0x80 + columna);
            break;
        case 1:
            LCD_command(0xC0 + columna);
            break;
        case 2:
            LCD_command(0x94 + columna);
            break;
        case 3:
            LCD_command(0xD4 + columna);
            break;
    }
}

char LCD_print(const char * s)
{
    char cs = 0;
    while (*s)
    {
        LCD_write(*s++);
        cs++;
    }
    return cs; //Devuelve la longitud de la cadena
}

// Turn the display on/off (quickly)

void LCD_noDisplay()
{
    _displaycontrol &= ~LCD_DISPLAYON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

void LCD_display()
{
    _displaycontrol |= LCD_DISPLAYON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turns the underline cursor on/off

void LCD_noCursor()
{
    _displaycontrol &= ~LCD_CURSORON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

void LCD_cursor()
{
    _displaycontrol |= LCD_CURSORON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turn on and off the blinking cursor

void LCD_noBlink()
{
    _displaycontrol &= ~LCD_BLINKON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

void LCD_blink()
{
    _displaycontrol |= LCD_BLINKON;
    LCD_command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM

void LCD_scrollDisplayLeft()
{
    LCD_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}

void LCD_scrollDisplayRight()
{
    LCD_command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right

void LCD_leftToRight()
{
    _displaymode |= LCD_ENTRYLEFT;
    LCD_command(LCD_ENTRYMODESET | _displaymode);
}

// This is for text that flows Right to Left

void LCD_rightToLeft()
{
    _displaymode &= ~LCD_ENTRYLEFT;
    LCD_command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'right justify' text from the cursor

void LCD_autoscroll()
{
    _displaymode |= LCD_ENTRYSHIFTINCREMENT;
    LCD_command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'left justify' text from the cursor

void LCD_noAutoscroll()
{
    _displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
    LCD_command(LCD_ENTRYMODESET | _displaymode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters

void LCD_createChar(char location, char charmap[])
{
    location &= 0x7; // we only have 8 locations 0-7
    LCD_command(LCD_SETCGRAMADDR | (location << 3));
    for (int i = 0; i < 8; i++)
    {
        LCD_write(charmap[i]);
    }
}

/*********** mid level commands, for sending data/cmds */

void LCD_command(char value)
{
    LCD_send(value, 0);
}

void LCD_write(char value)
{
    LCD_send(value, 1);
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection



void LCD_send(char value, char mode)
{
    LCD_RS_SET(mode);
    LCD_write4bits(value >> 4);
    LCD_write4bits(value);
}

void LCD_RS_SET(char value)
{
    LCD_RS = value;
}

void LCD_EN_SET(char value)
{
    LCD_EN = value;
}

void LCD_pulseEnable()
{
    LCD_EN_SET(0);
    __delay_us(1);
    LCD_EN_SET(1);
    __delay_us(1); // enable pulse must be >450ns
    LCD_EN_SET(0);
    __delay_us(100); // commands need > 37us to settle
}

void LCD_write4bits(char a)
{
    if (a & 1)
    {
        LCD_D4 = 1;
    } else
    {
        LCD_D4 = 0;
    }

    if (a & 2)
    {
        LCD_D5 = 1;
    } else
    {
        LCD_D5 = 0;
    }

    if (a & 4)
    {
        LCD_D6 = 1;
    } else
    {
        LCD_D6 = 0;
    }

    if (a & 8)
    {
        LCD_D7 = 1;
    } else
    {
        LCD_D7 = 0;
    }

    LCD_pulseEnable();
}

main.c

/*
 * File:   main.c
 * Author: Administrador
 *
 * Created on 29 de julio de 2022, 08:14
 */


#include <xc.h>
#include <stdbool.h>
#include <string.h>
#include "config.h"
#include "drv_lcd.h"
#include "uart.h"

char *strtok_single(char *, char const *);

unsigned int us_tte[8] = {0};

#define trigger PORTAbits.RA0

void main(void) {
    OSCCON = 0x6A;
    TRISA = 0x00;
    TRISB = 0xFF;
    TRISC = 0x00;
    TRISD = 0x00;
    TRISE = 0x00;
    ANSELA = 0x00;
    ANSELB = 0x00;
    ANSELC = 0x00;
    ANSELD = 0x00;
    ANSELE = 0x00;
    PORTA = 0x00;
    PORTB = 0x00;
    PORTC = 0x00;
    PORTD = 0x00;
    PORTE = 0x00;

    INTCON = 0xE8; //Activa las interrupciones por cambio de estado, timer0 y perifericos
    IOCBN = 0x3F; //Activa las interrupciones por cambio de estado(flanco negativo) en todos los pines del puerto B

    OPTION_REG = 0x46; //Activa el timer0 con preescaler de 1:256
    T1CON = 0x01; //Activa el timer1 con preescaler de 1:1
    T1GCON = 0xE1; //Activa el Gate Control del timer1 y usa como gate el TMR0IF (modo toggle)

    LCD_init();
    __delay_ms(10);
    LCD_clear();
    UARTInit(9600, 1);
    while (1) {
        LCD_setCursor(0, 0);
        char buf[32] = {0};
        //        char lcd[16] = {0};
        //        sprintf(tmp, "%03u %03u %03u", (us_tte[0]/58), (us_tte[1]/58), (us_tte[2]/58));
        //        LCD_print(tmp);
        //        LCD_setCursor(0,1);
        //        sprintf(tmp, "%03u %03u %03u", (us_tte[3]/58), (us_tte[4]/58), (us_tte[5]/58));
        //        LCD_print(tmp);
        UARTReadString(buf, sizeof buf);
        //        char out[10][7];
        //        char *pch;
        //        pch = strtok_single(buf, ",");
        //        int i = 0;
        //        while (pch != NULL) {
        //            strcpy(out[i], pch);
        //            printf("%s\n", out[i]);
        //            i++;
        //            pch = strtok_single(NULL, ",");
        //        }
        //        sprintf(lcd, "%s", out[7]);
        LCD_print(buf);
        PORTAbits.RA1 = !PORTAbits.RA1;
    }

}

void __interrupt() isr() {
    if (INTCONbits.TMR0IF) {
        static bool meas = true;
        TMR0 = TMR0 + 139;
        TMR1H = 0x00;
        TMR1L = 0x00;
        if (meas) {
            trigger = 1;
            __delay_us(10);
            trigger = 0;

        }
        PORTAbits.RA2 = !PORTAbits.RA2;
        meas = !meas;
        INTCONbits.TMR0IF = 0;
    }
    if (INTCONbits.IOCIF) {
        for (int i = 0; i < 6; i++) {
            us_tte[i] = (((IOCBF >> i) & 0b1) == 1 ? (((TMR1H << 8) | TMR1L) - 600) : us_tte[i]);
            IOCBF = (IOCBF & (~(1 << i))) | (0 << i);
        }
        INTCONbits.IOCIF = 0;
    }

}

char *strtok_single(char *str, char const *delims) {
    static char *src = NULL;
    char *p, *ret = 0;

    if (str != NULL)
        src = str;

    if (src == NULL)
        return NULL;

    if ((p = strpbrk(src, delims)) != NULL) {
        *p = 0;
        ret = src;
        src = ++p;
    } else if (*src) {
        ret = src;
        src = NULL;
    }

    return ret;
}

uart.c

#include "UART.h"

/**
 * Initialize the UART module with given baud rate and speed mode
 * @param baud_rate
 * @param BRGH speed mode (high speed or low speed)
 */
void UARTInit(const uint32_t baud_rate, const uint8_t BRGH) {
    // Calculate BRG
    if (BRGH == 0) {
        SPBRG = _XTAL_FREQ/(64*baud_rate) - 1;
        TXSTAbits.BRGH = 0;
    } else {
        SPBRG = _XTAL_FREQ/(16*baud_rate) - 1;
        TXSTAbits.BRGH = 1;
    }
   
    // TXSTA register
    TXSTAbits.TX9 = 0;      // 8-bit transmission
    TXSTAbits.TXEN = 1;     // Enable transmission
    TXSTAbits.SYNC = 0;     // Asynchronous mode
    
    // RCSTA register
    RCSTAbits.SPEN = 1;     // Enable serial port
    RCSTAbits.RX9 = 0;      // 8-bit reception
    RCSTAbits.CREN = 1;     // Enable continuous reception
    RCSTAbits.FERR = 0;     // Disable framing error
    RCSTAbits.OERR = 0;     // Disable overrun error
    
    // Set up direction of RX/TX pins
    UART_TRIS_RX = 1;
    UART_TRIS_TX = 0;
}

/**
 * Send a character
 * @param c character to send
 */
void UARTSendChar(const char c) {
    while (TXSTAbits.TRMT == 0);    // Wait for buffer to be empty
    TXREG = c;
}

/**
 * Send a string, until '\0' is read, up to max_length characters
 * @param str
 * @param max_length
 */
void UARTSendString(const char* str, const uint8_t max_length) {
    int i = 0;
    for (i=0 ; i<max_length && str[i]!='\0' ; i++) {
        UARTSendChar(str[i]);
    }
}

/**
 * Determines whether a new data is available to read
 * @return 
 */
uint8_t UARTDataReady() {
    return PIR1bits.RCIF;
}

/**
 * Read one character
 * @return  byte read
 */
char UARTReadChar() {
    while (!UARTDataReady());   // Wait for data to be available
    return RCREG;
}

/**
 * Read a string, until we read '\0', up to max_length characters. 
 * Null termination will use 1 byte, max_length must be set accordingly.
 * @param buf   pre-allocated buffer
 * @param max_length
 * @return number of bytes read
 */
uint8_t UARTReadString(char *buf, uint8_t max_length) {
    uint8_t i = 0;
    char tmp = 1;
    for (i=0 ; i<max_length-1 ; i++) {
        tmp = UARTReadChar();
        // Stop reading if end of string is read
        if (tmp == '\0' || tmp == '\n' || tmp == '\r') {
            break;
        }
        buf[i] = tmp;
    }
    
    buf[i+1] = '\0';
    
    return i;
}

EDIT: I've continued testing, and I'm very sure that the RCIF flag gets stuck in 0 because of some kind of overflow. As the uC gets stuck on the while loop while (!UARTDataReady()), code from main() stops being executed, but timer0 and interrupt on change still work. Maybe i can implement some way of a watchdog timer, and in case it gets stuck, i can flip it. In theory, the uC should never stop reciving serial data, so, no compromises there.

EDIT2: After more testing, I'm getting more confused. I dont know if this is still related to the 0.16 error rate, or what. I made a simple test code on an arduino nano. Configured the baud rate at 9600, and wrote a code that prints a number to the serial, adds one, and waits x ms to send the next message. I've tested from 0 to 15ms delay, and i can say that with a greater the delay, all problems start to go away. The thing is that i cant manipulate message rates of the gps module, so i will have to continue investigating. I tested using the internal oscilator at 16MHz and things got better, but only with my arduino test. As soon as i tried to plug the gps module back, the uC freezes. Soon i will try to buy a 18.432MHz crystal (or somethign close) and test if that fixes it, though i doubt it, since i think it has to do more with how fast can you poll data from the registers than to get the error rate down to 0

EDIT3: Thanks to @PStechPaul, @Bruce Abbott and @user85471 I was able to make the uC not get frozen when reciving data too fast. I achieved this by modifying the UARTDataReady() function like this:

uint8_t UARTDataReady() {
    if (PIR1bits.RCIF && RCSTAbits.OERR){
        char tmp = RCREG;
        RCSTAbits.CREN = 0;
        RCSTAbits.CREN = 1;
    }
    return PIR1bits.RCIF;
}

This way, when getting overrun errors (wich whas what i was facing before), they can get taken care of, but at the cost of loosing data. For now, i always recive serial data, but the majority of it is garbage (only when sending too many characters and really fast, which is basically the "normal" condition, so, no solution yet). I know i'm getting closer, but i think that reducing the 0.16 error factor is now my only solution, since i dont think i can set up the GPS module to send ASCCI "U" on startup to use the auto-baud detect (i will try to find if its really not possible)

\$\endgroup\$
9
  • \$\begingroup\$ 0.16 accounts to 16% error in baudrate. I would say anything over 4-5%, is enough to corrupt UART reception. \$\endgroup\$
    – Mitu Raj
    Jul 30 at 22:11
  • \$\begingroup\$ It sure is, but it makes no sense in why would microchip add that baud rate as a supported one in my current configuration \$\endgroup\$
    – fpp
    Jul 30 at 23:05
  • \$\begingroup\$ @fpp I don't have an answer for you. But some advice. If you are using someone else's code and you find yourself in trouble using it on your system, then you have two choices: debug their code as it applies to your system (which I believe will require you to study the datasheet to do it properly) or write your own code (which will require you to study the datasheet to do it properly.) I think you get where I'm going. It seems as though you are asking us to read the datasheet and debug someone else's code as it applies to your system. Am I wrong? \$\endgroup\$
    – jonk
    Jul 31 at 0:18
  • \$\begingroup\$ Baud rate error is not 16%, it is 0.16%, provided the 4 MHz Fosc is accurate. The value in SPBRG should be 25 (decimal). \$\endgroup\$
    – glen_geek
    Jul 31 at 0:45
  • \$\begingroup\$ @jonk Well, at least the problematic lib is easy to understand that i could re-write it and see what happens, but i see no point in doing it, since the problem is not logic, but something wrong on the registers (thats my guess after reading the datasheet). Also, i've already tested multiple uart libraries, from very bare-bones ones with a more manual approach to setting them up, to more sophisticated ones that do setup in a more easier way \$\endgroup\$
    – fpp
    Jul 31 at 19:32

3 Answers 3

1
\$\begingroup\$

From my experience with similar (PIC16F145x) parts.

According to the datasheet, the FERR and OERR bits of the RCSTA register are read-only and thus cannot be cleared as you do in uart.c.

The datasheet notes the way these should be cleared; FERR by reading RCREG and OERR by clearing CREN.

I would add a RCSTAbits.CREN = 0; to the start of the UART receive section and follow it with a RCREG read (I think just a RCREG; statement is sufficient.)

It sounds like you have a debugger, so you can check the status of these bits to confirm if they are the problem.

\$\endgroup\$
1
  • \$\begingroup\$ After also reading the datasheet and doing some more investigation on those registers, I finally undestood what you tell me to do. I added a if (PIR1bits.RCIF && RCSTAbits.OERR), and when true, it reads RCREG to shift to the next char, and I also set RCSTAbits.CREN to false an then true, so RCSTAbits.OERR also gets cleared. I included that if on the UARTDataReady() function, so every serial read operation checks for overrun errors and "un-freezes" the uC. Now, at faster rates, i recive information, though some of it is garbage (only happens with the NEMA sentences) \$\endgroup\$
    – fpp
    Aug 1 at 23:42
0
\$\begingroup\$

One starting point would be checking to see what value is actually stored in _XTAL_FREQ. I THINK an int in the 8-bit compiler is 16 bits.

\$\endgroup\$
5
  • \$\begingroup\$ it shouldn't matter, since its only used for the SPBRG register calculation. That register is used as a reference to set up the baud rate. The more important value is the OSCCON register, which defines the acual HFINTOSC freq. \$\endgroup\$
    – fpp
    Jul 30 at 23:02
  • \$\begingroup\$ @fpp It matters and please check it. If it is not 25, then baud rate is incorrect. \$\endgroup\$
    – Justme
    Jul 31 at 0:51
  • \$\begingroup\$ @Justme the value is 25, i checked it with the variable watch tool on debug mode \$\endgroup\$
    – fpp
    Jul 31 at 19:33
  • \$\begingroup\$ A scope is really helpful when debugging serial comms. Send a stream of 0x55 or 0xaa which will have 8 bits of alternating polarity and easy to measure. \$\endgroup\$
    – PStechPaul
    Aug 1 at 5:09
  • \$\begingroup\$ @PStechPaul it would be useful if i didnt know if data was being recived, or transmitted properly, but the problem is on how fast can the uC read the serial bits and how fast i can read the recived data register \$\endgroup\$
    – fpp
    Aug 1 at 23:58
0
\$\begingroup\$

Solved it by implementing Rx interruptions onto the library I was using:

if (PIR1bits.TXIF) {
  endline = UARTReadString(buf, sizeof buf);
}

endline is used to evaluate when a full line was sent, to then, on main, do some tokenization of the recieved data:

if (endline) {
  if (!strncmp(buf, "$GPVTG", 5)) {
    char *pch;
    pch = strtok_single(buf, ",");
    int i = 0;
      while (pch != NULL) {
        strcpy(out[i], pch);
        //printf("%s\n", out[i]);
        i++;
        pch = strtok_single(NULL, ",");
      }
    }
  }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.