UART Communication with ACK/NACK for each byte

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
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.
If there is any good source regarding error correction in UART (or alike) communication I would be more than happy to dive into it and understanding how real life practical system implementing UART communication work. However I haven't found much on this topic even though I really searched for it.

Currently, only error checking is on hardware level - that is framing and overrun. You suggest I should focus on software based error correction like checksum and CRC and ignore hardware based ones (detected by internal logic of microprocessor)?
 

Ya’akov

Joined Jan 27, 2019
9,173
If there is any good source regarding error correction in UART (or alike) communication I would be more than happy to dive into it and understanding how real life practical system implementing UART communication work. However I haven't found much on this topic even though I really searched for it.

Currently, only error checking is on hardware level - that is framing and overrun. You suggest I should focus on software based error correction like checksum and CRC and ignore hardware based ones (detected by internal logic of microprocessor)?
This is precisely what I would suggest. You can start here:

https://users.ece.cmu.edu/~koopman/des_s99/coding/

And if you need to dive into concepts presented, you wil know the keywords to find other sources. The fundamentals of error detection and correction do not change based on the physical layer involved. You will learn so much more if you try to get something like what is described in that document.

I hope it helps.
 

Papabravo

Joined Feb 24, 2006
21,228
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've had a career that spanned half a century. In all that time I can count the framing and overrun errors combined on the fingers of one hand. The only way practical way to create them is on purpose. You're wasting your time with this one.
 

Thread Starter

Lu Ka

Joined Jan 25, 2018
12
I've had a career that spanned half a century. In all that time I can count the framing and overrun errors combined on the fingers of one hand. The only way practical way to create them is on purpose. You're wasting your time with this one.
Oh, okay. It is difficult for someone like me with zero real world experiences to assume framing or overrun won't cause any problems.
So only way of error handling that actually matters is data integrity verification (using CRC, checksum, parity, etc.)?
 

MrChips

Joined Oct 2, 2009
30,824
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..
I don't know where you got that impression. UART serial communications is in use everyday in zillions of installations.

Learning how to communicate with UARTs is a good starting point in serial communications. You need to take one step back and three steps forwards in order to understand how to implement secure digital communications.

One step back
Learn how to send and receive one byte at a time.
Learn error detection and correction schemes on one byte. Learn about parity check, redundancy.
Learn error detection and correction schemes on n bytes. Learn about transverse and longitudinal parity, CRC, checksums and error correcting codes.

Three steps forwards
Nobody is going to implement ACK/NACK on single byte transmission because of the inefficiency as others have already pointed out. Data will be sent in packets. Study packet transmission. Imagine communications on the internet. How does the sender know that a packet was incorrectly/correctly received and must resend/continue with transmission?
 

MrChips

Joined Oct 2, 2009
30,824
Framing error, overrun error, parity error are hardware transmission issues that rarely happen.
When these happen your data is corrupted, period.

You need to take three steps forward and implement secure error detection and correction at the packet level.

As an example, when a packet is transmitted, it could be sent to 100 different devices. How does your device know that it is the intended recipient when the data packet has been corrupted?
 

djsfantasi

Joined Apr 11, 2010
9,163
Framing error, overrun error, parity error are hardware transmission issues that rarely happen.
When these happen your data is corrupted, period.

You need to take three steps forward and implement secure error detection and correction at the packet level.

As an example, when a packet is transmitted, it could be sent to 100 different devices. How does your device know that it is the intended recipient when the data packet has been corrupted?
I think because he is only working with two devices in a self-education project.
 

BobaMosfet

Joined Jul 1, 2009
2,113
@Lu Ka Nothing wrong with what you're wanting to do. You're trying to learn so you have many, many questions that you want answered, and need patience in response.

What you're talking about is called a 'handshake'. You have to determine the 'protocol' you want to use. This protocol should describe:

collision detection
error indication/correction
packet-size
timing method

You can either communicate synchronously (timing) or asynchronously (no required or shared timing model). What it sounds like you want is asych- in order to do that, you have to create logic that allows both sides to determine what is going on, and constraints that each side follows when there is a failure- such as times out after x seconds, and starts over.

Are you doing your framing/error detecting? If so, you have to design that. If you are letting the UART do it- depending on its capability- this may be transparent to you.

Communication isn't difficult, it's just pulses. But since you are working with _signals_ you need to understand that signals are voltage references, and current is a means to provide enough energy for the signal to travel the length of the given conductor. The less current you allow to flow, the weaker the signal is, more prone to errors it is, or not even making it all the way. Too much, and you can get a bounce- an impedance mismatch.

You need to read the datasheet, and understand it, for the MCU you are using and its UART so that you can satisfy the electrical requirements the designers require you to follow. Anything beyond that is code _logic_.
 

ApacheKid

Joined Jan 12, 2015
1,619
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).

View attachment 233461

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
There's a lot going on here and so there's lots of scope for failures and bugs, I think you are attempting too much, too many different things in the one project at the same time.

If you want to get some feel for stuff like ack/nack and perhaps flow control and so on, then try doing that away from these MCUs and C and so on, get some understanding without the hassle of unstable hardware or buggy low level UART code.

Are these two devices communicating with full or half duplex?
 

trebla

Joined Jun 29, 2019
549
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?
If you want send a byte fom array at each for() cycle you better calculate array size before for() loop and use then array size in for() loop condition checking. For example, you can calculate array size as follows:

C:
tx_size = *(&data_tran + 1) - data_tran;
If data transfer speed is extremely low and only one byte needs to be sent, then you can send same byte multiple times ( 3 for example) and read it back every time. If any error occurs on transmission then you see it by comparing sent and received bytes in master side or comparing all received bytes in receiver side. This is very simple protocol at start, of course it is far from ideal. You can add some special sequences for starting, stopping, retransmitting and for error condition indications if you want experiment with UART. There are some communication protocols for one-directional transmitting such LANC which uses multiple times transmitting.
 

MrChips

Joined Oct 2, 2009
30,824
TS is not intending to implement anything useful but just learning programming algorithms while using UART serial communications. Nobody does ACK/NACK on single bytes because it is inefficient. Instead, TS should send a packet that includes:

<START> <MESSAGE> <CHECKSUM> <END>

with parity included on each byte transmitted.

If the receiver receives a valid packet then receiver sends ACK, otherwise receiver does nothing.
Transmitter uses timeout as indicator that packet was not received.

The takeaway here is that in any application you have to sit down and consider all possible fault scenarios in order to design a working algorithm. In other words, ask yourself, What if?
 

BobaMosfet

Joined Jul 1, 2009
2,113
Title: Algorithms in C, 3rd Ed. [Parts 1-4, Fundamentals, Data Structures, Sorting, Searching]
Author: Robert Sedgewick
ISBN: 0-201-31452-5
 
Top