UART Communication with ACK/NACK for each byte

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
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.jpg

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):

C:
#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


// INTIALIZE INTERNAL OSCILLATOR REGISTERS
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
}

// INITIALIZE UART REGISTERS
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
}

// UART TRANSMISSION FUNCTION
void uart_transmit(uint8_t *tran){
    do{
        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
}


// UART RECEPTION FUNCTION
void uart_receive(uint8_t *rec){
   
    // MAIN ERROR - overrun
    if(RCSTA2bits.OERR){
        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
    }
    // NORMAL OPERATION
    else{
        *rec = RCREG2;     // store received data to "rec"
    }
}

// ISR WHEN REQUEST FOR RECEPTION HAPPENS
void __interrupt() high_isr(void){
    INTCONbits.GIEH = 0;
    if(PIR3bits.RC2IF){
        uart_receive(&data_rec);
        isr_flag = 1;
    }
    INTCONbits.GIEH = 1;
}

void main(void) {
   
    TRISC = 0;
    TRISD = 0;
    LATC = 0;
    LATD = 0;
   
    osc_initialize();
    uart_initialize();
   
    uint8_t data_tran[] = {"ABCDE"};     //character set to be transmitted
   
    while(1){
       
        for(int i = 0; data_tran; i++){
            
            // GENERATE DELAY WHILE CONTINUOUSLY CALL TRANSMISSION
            for(float j = 0; j < 5; j += 0.1){
                uart_transmit(&data_tran);
            }
        }
    }
    
    return;
}
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.
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.

EDIT: I realized that pull-up resistors are needed for UART to operate properly (RX not picking up noise), but after re-wiring, now the program won't even start executing; that is, data won't start transmitting and also I get no indication of any error.
Another fact is that when it works (if no resistor is used), data should be transmitted from transmitter also in case if any data line is disconnected; that is, if any error triggers. But it isn't transmitted - as soon as any error shows up, TX line (of transmitter) goes high.

Moderators note : used code tags for code
 

Attachments

Last edited by a moderator:

trebla

Joined Jun 29, 2019
428
I think you must rewrite your code in more structured manner. Firstly, your flowchart and your code don't match well. In TX code is for() loop (in main() function) defined with array data_tran[] instead of single integer value. In interrupt routine you call receive function which increases interrupt latency. Make good plan (flowchart) and follow this.
 

Papabravo

Joined Feb 24, 2006
16,775
This is wildly inefficient from a bandwidth utilization stand point. Aside from the fact that in asynchronous communications there are 2 or 3 overhead bits for each 8-bit data payload your bandwidth utilization is below 50%. To get reliable communication on a noisy channel you want to implement a scheme where the probability of error detection is high and the cost of a retransmission is low given an expected bit error rate. You do not seem to have done your homework in this regard. Good error detection can be done with a CRC check-word. Also you want to be Acking or Nacking whole packets, not individual characters.
 

Yaakov

Joined Jan 27, 2019
3,482
This is wildly inefficient from a bandwidth utilization stand point. Aside from the fact that in asynchronous communications there are 2 or 3 overhead bits for each 8-bit data payload your bandwidth utilization is below 50%. To get reliable communication on a noisy channel you want to implement a scheme where the probability of error detection is high and the cost of a retransmission is low given an expected bit error rate. You do not seem to have done your homework in this regard. Good error detection can be done with a CRC check-word. Also you want to be Acking or Nacking whole packets, not individual characters.
I was going to get to that but first I wanted to understand how the receiver knows to ACK or NAK.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
I am a bit confused. How does the receiver know to ACK or NAK?
If framing (FERR) or overrun (OERR) error occurs at reception of data, receiver transmits NACK, otherwise ACK.

If there is anything else to clarify, I would gladly explain it. ;)
 

Yaakov

Joined Jan 27, 2019
3,482
If framing (FERR) or overrun (OERR) error occurs at reception of data, receiver transmits NACK, otherwise ACK.

If there is anything else to clarify, I would gladly explain it. ;)
I guess I am having difficulty in offering help because I can't understand your goal. Is this a practical project? Is it to learn something? If so, what are you trying to learn?

I ask because I can't see any practical application for this and I am not sure why you would want to use something completely impractical as a learning tool.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
If framing (FERR) or overrun (OERR) error occurs at reception of data, receiver transmits NACK, otherwise ACK.

If there is anything else to clarify, I would gladly explain it. :D
I think you must rewrite your code in more structured manner. Firstly, your flowchart and your code don't match well. In TX code is for() loop (in main() function) defined with array data_tran[] instead of single integer value. In interrupt routine you call receive function which increases interrupt latency. Make good plan (flowchart) and follow this.
Regarding flowchart and code - in flowchart I tried to consider only main aspects of code (to reduce complexity, improve readability).
Array is defined for data_tran[] because I want to send an array of characters (one-by-one of course). Is there something wrong with that?
Regarding ISR, I agree calling a function from it adds much complexity to whole program and also performance, as ISR should be terminated as soon as possible. I will try to solve that also.
 

djsfantasi

Joined Apr 11, 2010
7,826
This is wildly inefficient from a bandwidth utilization stand point. Aside from the fact that in asynchronous communications there are 2 or 3 overhead bits for each 8-bit data payload your bandwidth utilization is below 50%. To get reliable communication on a noisy channel you want to implement a scheme where the probability of error detection is high and the cost of a retransmission is low given an expected bit error rate. You do not seem to have done your homework in this regard. Good error detection can be done with a CRC check-word. Also you want to be Acking or Nacking whole packets, not individual characters.
Totally agree 100%. This is an impractical, and dare I say ridiculous design. I can’t think of any communications application that works with a packet size of 1. There is no need for one. The overhead is as inefficient as can be. And you’re cutting bandwidth by 60%. Depending on the source of the bytes to be transmitted, you could lose data while processing your ACK/NAK protocol.

Since you have an array of char, why not calculate a CRC value and send the entire array in one transmission. By recalculating the CRC on the receiving end and comparing it to the CRC code received, THEN you can ACK/NAK the entire array/transmission.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
I guess I am having difficulty in offering help because I can't understand your goal. Is this a practical project? Is it to learn something? If so, what are you trying to learn?

I ask because I can't see any practical application for this and I am not sure why you would want to use something completely impractical as a learning tool.
Why do you think is learning on how UART works and error handling to ensure "safe" and "stable" communications is impractical?
Actually, that is my first (small) project on communication between two digital components (microprocessors) - I chose it because UART should be relatively easy to understand and realize.

I want to understand how to implement at least some sort of digital communication in embedded system - because it is fundamental to any project where interfacing between two or more digital components is required (e.g. interfacing between microprocessor and TFT (or LCD) display).

UART might not be in use much these days, but in my opinion is good enough to start with. I'm not saying I'm going to control Space Shuttle with it.. :D But is good enough for learning, before I proceed to some other, more practical form of digital communication.

Also I'm just a beginner to embedded systems and C programming so I think a project to establish good UART communication between two devices is good enough project for start.
 

Yaakov

Joined Jan 27, 2019
3,482
If you want to learn how to create a reliable channel between devices I would expect you would start with a simple practical example which this is not. Why not use something that represents what it really means to make a reliable channel?

Just because you are trying to learn doesn't mean you have to throw out everything practical. Doing practical things is the way to learn.

In any case, it is, of course, entirely up to you how you choose to learn but you are asking people to help you debug something that is partly buggy because you aren't using practical code.

Thanks for answering, and good luck in your learning, data communications can be a lot of fun.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
This is wildly inefficient from a bandwidth utilization stand point. Aside from the fact that in asynchronous communications there are 2 or 3 overhead bits for each 8-bit data payload your bandwidth utilization is below 50%. To get reliable communication on a noisy channel you want to implement a scheme where the probability of error detection is high and the cost of a retransmission is low given an expected bit error rate. You do not seem to have done your homework in this regard. Good error detection can be done with a CRC check-word. Also you want to be Acking or Nacking whole packets, not individual characters.
I agree that this is pretty unefficient but still, I didn't want to dive into efficiency right at the beginning of understanding how relatively simple UART communication should operate. I didn't just want to clear errors and proceed like nothing ever happened - that might cause problems, depending of how critical is each byte of data to the receiver.
How is "packet of data" defined? Should I check for error every 10 bytes or every 1000 bytes? Probably depends on possibility of error occurrence (due to outside noise, power grid spikes, nearby RF transmissions, etc.).
Also, for now, I'm just considering framing or overrun error; I'm not dealing with data integrity (corruption of single or multiple bits) - that comes later, after I establish some stable form of communication between two microprocessors.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
If you want to learn how to create a reliable channel between devices I would expect you would start with a simple practical example which this is not. Why not use something that represents what it really means to make a reliable channel?

Just because you are trying to learn doesn't mean you have to throw out everything practical. Doing practical things is the way to learn.

In any case, it is, of course, entirely up to you how you choose to learn but you are asking people to help you debug something that is partly buggy because you aren't using practical code.

Thanks for answering, and good luck in your learning, data communications can be a lot of fun.
I agree to some point that jumping straight to most practical data communication might be a better start. But still, UART is (probably) somehow of a fundamental to other serial communications. Understanding it (mostly basics) through a simple project won't hurt, I guess.
Also, the two PICs I'm currently using both have "integrated" hardware EUSART blocks (which is optimized for such communication). I think its only natural to try it out, understand it (if possible), realize a simple project implementing it, and move on to the next topic.

Thank you, for considering understanding explained topic!
 

Yaakov

Joined Jan 27, 2019
3,482
I agree to some point that jumping straight to most practical data communication might be a better start. But still, UART is (probably) somehow of a fundamental to other serial communications. Understanding it (mostly basics) through a simple project won't hurt, I guess.
Also, the two PICs I'm currently using both have "integrated" hardware EUSART blocks (which is optimized for such communication). I think its only natural to try it out, understand it (if possible), realize a simple project implementing it, and move on to the next topic.

Thank you, for considering understanding explained topic!
Just to be clear, using the UART isn't the problem. The physical layer could be whatever, it's the error correction scheme that is not very useful. A little research into practical error correction would suggest several simple schemes not much harder to code (if at all) that would be a much better starting point.

If you just want to learn to make the UART work, drop the error stuff and communicate between the two. Then add the correction but, if I was doing it, I'd use something more in line with what you'd have to do to actually make a reliable channel.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
Just to be clear, using the UART isn't the problem. The physical layer could be whatever, it's the error correction scheme that is not very useful. A little research into practical error correction would suggest several simple schemes not much harder to code (if at all) that would be a much better starting point.

If you just want to learn to make the UART work, drop the error stuff and communicate between the two. Then add the correction but, if I was doing it, I'd use something more in line with what you'd have to do to actually make a reliable channel.
I forgot to mention - I already set up working UART com before I started with error correction (before that I just cleared the error and move on). That thing worked as intended. But the code was much simpler than now too.

Can you elaborate what you meant with the following?
I'd use something more in line with what you'd have to do to actually make a reliable channel.
Thanks!
 

djsfantasi

Joined Apr 11, 2010
7,826
If I may add, it’s kinda like learning to drive. One could learn on a go-cart, but practically that won’t help you to get your license.

Error detection and correction of a single byte is your go-cart. But it won’t help you drive on the highway.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
Totally agree 100%. This is an impractical, and dare I say ridiculous design. I can’t think of any communications application that works with a packet size of 1. There is no need for one. The overhead is as inefficient as can be. And you’re cutting bandwidth by 60%. Depending on the source of the bytes to be transmitted, you could lose data while processing your ACK/NAK protocol.

Since you have an array of char, why not calculate a CRC value and send the entire array in one transmission. By recalculating the CRC on the receiving end and comparing it to the CRC code received, THEN you can ACK/NAK the entire array/transmission.
It wouldn't work with packet size of 1 because of too slow transmission regarding given baud rate? Or is there any other reason?

As mentioned, I'm not concerned (at lest for now) with speed and inefficiency. That is to come AFTERWARDS I manage to realize operating transmission without such unpredicted activity as mentioned above (referring to "real life implementation" section).
 

Yaakov

Joined Jan 27, 2019
3,482
I forgot to mention - I already set up working UART com before I started with error correction (before that I just cleared the error and move on). That thing worked as intended. But the code was much simpler than now too.

Can you elaborate what you meant with the following?


Thanks!
What I meant is that your scheme can’t be used in practice to make the channel more reliable because it is utterly naive and inefficient. Bit error rates on serial connections are well known and documented. Schemes to deal with them, which look nothing like yours are also documented everywhere. Even a naive scheme that at least used blocks of data would make more sense. The errors you are “correcting” are redundant if you check the actual data for errors. A simple checksum, or a CRC on packetized data would get you started in reliable data transmission.

The packet size can be calculated by the expected BER (Bit Error Rate) of the channel. I really think you’d find more to learn making a simple but practical error correction scheme than this.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
If I may add, it’s kinda like learning to drive. One could learn on a go-cart, but practically that won’t help you to get your license.

Error detection and correction of a single byte is your go-cart. But it won’t help you drive on the highway.
I mean, whether the program checks for validity of data every one byte or every 1000 bytes shouldn't really be difficult task to implement. However, I haven't tried any of data verification algorithms like CRC, checksum, parity, etc.
 
Top