UART communication with ACK/NACK error handling

UART communication with ACK/NACK error handling

Postby Keno » Tue Mar 23, 2021 1:20 pm

IDEA: I'm trying to establish firm communication between a transmitter and receiver; firm in a sense of some basic error handling. I'm trying to implement ACK(acknowledgement)/NACK(negative-acknowledgement) style of error handling. I'm currently only transmitting data from one device (PIC18F46K80) and receiving it with other one (PIC18F452), where transmitter (T) sends "useful" data and receiver (R) accepts that data and send back to transmitter ACK or NACK for every byte of data send.

SETUP: Since there is a bit of complexity to the program, I made flowchart for each transmitter and receiver (only main aspects of code considered):

Transmitter&Receiver Flowchart-min-min (3).jpg
Transmitter&Receiver Flowchart-min-min (3).jpg (240.73 KiB) Viewed 2199 times

Here is code for transmitter (PIC18F46K80). Code for receiver is not same, but is similar to this one. Also, I use LEDs to indicate current position of program execution (e.g. when program is looping through "error" path or when is looping through "normal operation" path) and received data interpretation (LED bar graph connected in parallel to PORTD) but this is not included here (to improve code readability):

Code: Select all
#include <xc.h>
#include <stdint.h>
#include <stdbool.h>
#include "config_bits.h"

void osc_initialize(void);
void uart_initialize(void);
void uart_receive(uint8_t *c);
void uart_transmit(uint8_t *c);
void __interrupt() high_isr(void);

uint8_t data_rec;                // received data storage variable
uint8_t isr_flag = 0;             // used to indicate completion of ISR
uint8_t ack = 0x06;              // acknowledgement code
uint8_t nack = 0x15;            // negative acknowledgement code

void osc_initialize(void){
    OSCCONbits.SCS = 0b10;                  // set internal oscillator block
    OSCCONbits.IRCF = 0b101;               // set clock frequency to 4MHz
    while(OSCCONbits.HFIOFS == 0);      // wait for oscillator frequency to stabilize

void uart_initialize(void){
    // RX and TX inputs port initialization
    TRISDbits.TRISD6 = 1;
    TRISDbits.TRISD7 = 1;
    SPBRG2 = 25;                  // SPBRG = ((F_osc/BaudRate)/64)-1 => at F_osc = 4MHz, BaudRate = 2400, low speed
    TXSTA2bits.BRGH = 0;     // 8-bit data mode setting for transmitter/receiver
    TXSTA2bits.SYNC = 0;     // asynchronous mode
    RCSTA2bits.SPEN = 1;     // RX and TX set as serial port pins
    TXSTA2bits.TXEN = 1;     // transmitter enable
    RCSTA2bits.CREN = 1;     // receiver enable
    INTCONbits.GIEH = 1;    // must be set for HP interrupt to be generated
    INTCONbits.GIEL = 1;    // must be set for LP interrupt to be generated
    PIE3bits.RC2IE = 1;       // enable USART receive interrupt

void uart_transmit(uint8_t *tran){
        TXREG2 = *tran;                        // load the value of "tran" into TXREG (data stored into TXREG, then send into TSR)
        while(TXSTAbits.TRMT == 0);    // wait for data to be loaded into TSR and send
        while(isr_flag == 0);                // loop is terminated after ISR
        isr_flag = 0;                            // reset "isr_flag"
    } while(data_rec != ack);            // got NACK (or anything else) -> re-transmit current data

void uart_receive(uint8_t *rec){
    // MAIN ERROR - overrun
        RCSTA2bits.CREN = 0;      // to clear error, reception module must be reset
        RCSTA2bits.CREN = 1;
        RCREG2;                        // dummy read - part of module reset
        RCREG2;                        // dummy read
        *rec = nack;                  // current setting: if(OERR); discard data and re-transimission
    // MINOR ERROR - framing
    else if(RCSTA2bits.FERR){
        RCREG2;            // dummy read - part of module reset
        *rec = nack;      // current setting: if(FERR); discard data and re-transmission
        *rec = RCREG2; // store received data to "rec"

void __interrupt() high_isr(void){
    INTCONbits.GIEH = 0;
        isr_flag = 1;
    INTCONbits.GIEH = 1;

void main(void) {
    TRISC = 0;
    TRISD = 0;
    LATC = 0;
    LATD = 0;
    uint8_t data_tran[] = {"ABCDE"};        //character set to be transmitted
        for(int i = 0; data_tran[i]; i++){
            for(float j = 0; j < 5; j += 0.1){

CODE EXPLANATION: From main(), I'm calling for uart_transmit() function, which transmits one character at the time (and generating sufficient delay, so I can observe transmitted data on LED bar graph (serially received data is outputted in parallel to PORTD)). After character is send, transmitter simply waits for transmission of ACK or NACK from receiver - if it gets ACK, transmit next character; if it gets NACK, re-transmit current character until it gets ACK.
As for receiver, it waits in main() (in loop) for ISR request. After data is loaded into RSR register, uart_read() function is performed. If data is received without any error, data is outputted on PORTD, ACK is sent to transmitter and program returns to main(). If there is an error regarding reception of character, data on PORTD remains unchanged (bad data is discarded), NACK is sent to transmitter and program returns to main(), where it waits for re-transmission of character.

REAL LIFE IMPLEMENTATION: First of all, program won't even start correctly, if both TX and RX lines (on both sides of devices) aren't slightly loaded (added 1M Ohm resistor to GND at both pins at both sides). Otherwise, program works as intended, if there is no special event, like disconnecting either of data lines (and triggering either of FERR for transmitter or receiver - to simulate error handling). If that happens, LED indicates that program is looping through "error" path but as I connect back unconnected line, path returns to "normal operation" only from time to time (which is the strangest fact). Also, resetting both devices simultaneously also works from time to time.

CONCLUSION (to the problem): I suspect there is some issue regarding timing, when error is (or should be) reset and program is (or should be) returning to "normal operation" path. However, I cannot grasp, what timing error am I missing here. Should I implement additional delay at some point(s) to ensure reception/transmission timing "compatibility"?
After character is transmitted, F452 waits until TSR is empty (even if there is no need for that here, since it waits for ACK/NACK). After character is received by F452, uart_receive() is called only after RSR is full and when ACK/NACK is transmitted, uart_transmit() is ended immediately - not waiting for TMRT to be set - because program need to be back to uart_receive() then back to ISR then back to main() BEFORE new interrupt happens due to re-transmission from 46K80.

Considering all that timing facts, I really cannot solve this issue further on by myself.
On_board circuit-min (2).jpg
On_board circuit-min (2).jpg (248.36 KiB) Viewed 2199 times
(3.96 KiB) Downloaded 167 times
(4.12 KiB) Downloaded 149 times
Posts: 11
Joined: Sat Nov 07, 2020 10:15 am
PIC experience: EE Student

Re: UART communication with ACK/NACK error handling

Postby Keno » Tue Apr 06, 2021 9:41 am

For those interested in any discussion, here is the link to (currently) active discussion:
Posts: 11
Joined: Sat Nov 07, 2020 10:15 am
PIC experience: EE Student


Who is online

Users browsing this forum: No registered users and 1 guest