Search code examples
cserial-portmicrocontrolleratmega

How can I read correctly integer coming in serial port?


I have this code in client (simplified from bigger blocks of code)

  int key=0;
  while (1) {
    int n_read=read(fd, &key, 4);
    printf("%d\n",key);
    fflush(stdout);
    if(key==1){
        printf("in loop\n");
        }   
  }

and this code in server on ATmega2560 microcontroller

while(1){
  int key=1;
  printf("%d", key);
}

the printf in server uses this code

void usart_putchar(char data) {
    // Wait for empty transmit buffer
    while ( !(UCSR0A & (_BV(UDRE0))) );
    // Start transmission
    UDR0 = data; 
}

// this function is called by printf as a stream handler
int usart_putchar_printf(char var, FILE *stream) {
    // translate \n to \r for br@y++ terminal
    if (var == '\n') usart_putchar('\r');
    usart_putchar(var);
    return 0;
}

I want it to print "in loop" but this never occurs, when I print in loop the key in the client it has value 825307441 and never the value 1.

I think it may be because of how i put the bytes in the integer variable one after the other after reading it from the serial, or maybe I am using the read function in an incorrect way, but I really don't know

EDIT:

After reading the answers I changed the server like this:

for(int j=0; j<4;j++){
        while ( !(UCSR0A & (_BV(UDRE0))) );
        UDR0=(key >> (8*j)) & 0xff;;
        }

It gives me some troubles, I have now these kind of results:

1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
16777216
16777216
16777216
16777216
16777216
65536
65536
65536
65536
65536
256
256
256
1
in loop
1
in loop
1
in loop
1
in loop
1
in loop
1

is it the endianness problem? is there a way to solve it?

If I cannot resolve this I will use @the busybee 's suggestion


Solution

  • You are mixing textual and binary representation of your value.

    The server sends the key represented as characters, because you use printf(). Actually, since the value is 1, only one character is sent: '1'.

    The client expects instead 4 bytes of binary representation of the key, because you use read().

    Let's see what you receive. The 4 byte value read from the serial port is decimal 825307441. Quickly thrown in a desktop calculator and converted to hex reveals that it is 0x31313131.

    These 4 bytes are 4 ASCII characters of '1'. If you can count the number of transmissions, you will see that the server needs to run 4 times through its loop for the client to receive 1 key.


    How to resolve?

    You have two options, use only binary representation or use only textual representation.

    If you want to avoid endianness issues, choose the textual representation. For this, change the client code into:

      /* fd needs to be transformed into a FILE* f. */
      int key = 0;
      while (1) {
        int n_converted = fscanf(f, "%d", &key);
        if (n_converted == 1) {
          printf("%d\n", key);
          /* not necessary: fflush(stdout); */
          if (key == 1) {
            printf("in loop\n");
          }
        } else {
          /* error handling */
        }
      }
    

    And you need to separate the numbers by whitespace, that are at least one of for example blank, tab, or new-line. Extend the server code:

      while (1) {
        int key = 1;
        printf("%d\n", key);
      }
    

    To use binary representation, you need to define a protocol that specifies the endianness of the transmission, the number of bytes per value, and how you separate values. This is not simple and commonly too broad to show here in depth and depends on your (unknown to us) requirements.

    If you choose to transmit only one byte, which gives you a value range of 0 to 255 or -128 to +127, you can avoid the two other decisions. With a single byte per value you have no endianness (on byte level), and since each byte is a complete value, no extra measure is necessary to separate values.

    These are example implementations:

      /* client */
      char key = 0;
      while (1) {
        int n_read=read(fd, &key, 1);
        if (n_read == 1) {
          printf("%d\n", (int)key); /* or printf("%hhd\n", key); */
          if (key == 1) {
            printf("in loop\n");
          }
        } else {
          /* error handling */
        }
      }
    
      /* server */
      while (1) {
        char key = 1;
        usart_putchar(key);
      }
    

    The output you see in your client comes from read()'s timeout behavior. A serial transmission needs time and depends mostly on the baud rate. Apparently read() returns faster than a single byte needs for transmission.

    Since you do not check the return value of read(), you continue with whatever is in key, even if no byte was received. And this gives the multiple outputs you see.