Sending numbers bigger than 255 via serial

Hello,

I found this code on this forum to send a distance measurement from a LIDAR attached to a ardunio mini to an ardunio mega via serial:

MINI SIDE

void loop() {
    measurement = calc_measurement();
}

void send_measurement() {
  Serial.write((byte)measurement / 256);
  Serial.write((byte)measurement % 256);
  Serial.flush();
}

MEGA SIDE

  if (Serial1.available() > 1) {
    int measure;
    byte b1  = Serial1.read();
    byte b2 =  Serial1.read();
    measure = ((int)b1) * 256 + b2;
  }

This is was working until a needed to send a number bigger than 255. After some research I realise that a byte can only represent numbers from 0-255.

I now need a solution to send numbers up to 600.

So I need to understand what is in the mini code.

Google tells me that there are 8 bits in a byte, so where does the number 256 come from, and how does it work in this case?

Thanks.

The easiest thing to do is to use Serial.print() rather than Serial.write()

Sending data in human-readable form makes debugging very much easier.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data. There is also a parse example to illustrate how to extract numbers from the received text.

...R

A byte of 8 binary bits can represent 28 = 256 numbers: 0 - 255.
The serial connection does indeed send data byte by byte, so if you want to send a number larger than 255, you’ll have to send multiple bytes.

For example, an int consists of 2 bytes on most Arduino AVR boards, so you’ll have to split it into two separate bytes. To get the lower byte, you just use a bitmask, by performing a binary AND operation. Let’s say that 0bhhhh hhhh llll llll is the binary representation of an int (where 0bhhhh hhhh is the high byte (MSB) and 0bllll llll the low byte (LSB), each ‘h’ and ‘l’ can either be a 1 or a 0). If you perform a bitwise AND with the number 0b1111 1111, you’re left with the lower 8 bits of the original int:

0bhhhh hhhh  llll llll
0b0000 0000  1111 1111
---------------------- &
0b0000 0000  llll llll

This particular bitwise AND operation is mathematically equivalent to “int modulo 28”.

0bhhhh hhhh  llll llll % 256 == 0b0000 0000  llll llll

Note that this operation doesn’t have to be explicit: if you just cast an int to an 8-bit type (uint8_t or byte, for example), you try to fit a 16-bit number into an 8-bit variable, so the 8 highest bits are thrown out. (Cfr. overflow.)

int x = 0b1001 1001  1110 1110;  // spaces are for readability only
uint8_t LSB = x;  // a uint8_t can only store 8 bits, so the MSB is just ignored (implicit type conversion)
Serial.print(x, BIN);  // prints "1001 1001  1110 1110"
Serial.print(x & 0b11111111, BIN);  // prints "1110 1110"
Serial.print(x % 256, BIN);  // prints "1110 1110"
Serial.print(LSB, BIN);  // prints "1110 1110"
Serial.print( (uint8_t) x, BIN);  // prints "1110 0110"  (explicit type-casting)

To get the most significant byte (the highest 8 bits), we can use a bit shift to the right:

0bhhhh hhhh  llll llll
---------------------- >> 8
0b0000 0000  hhhh hhhh

As you can see, everything is just shifted 8 bits to the right, so llll llll just falls off, and hhhh hhhh is now the LSB of the result.
We can now use the same trick as above to extract these 8 bits (hhhh hhhh).
This bitshift operator is mathematically equivalent to a floor division by 28.

0bhhhh hhhh  llll llll / 256 == 0b0000 0000  hhhh hhhh
int x = 0b1001 1001  1110 1110;  // spaces are for readability only
uint8_t MSB = x >> 8;
Serial.print(x, BIN);  // prints "1001 1001  1110 1110"
Serial.print(MSB, BIN);  // prints "1001 1001"
Serial.print( (uint8_t) x / 256, BIN);  // prints "1001 1001"

Now that you know how to extract single bytes from a multi-byte variable, you can send them over Serial:

void sendIntSerial(int x) {
  uint8_t LSB = x;
  uint8_t MSB = x >> 8;
  Serial.write(MSB);
  Serial.write(LSB);
}

On the receiver side, just do the inverse operation: shift the MSB 8 bits to the right, and combine it with the LSB, using a bitwise OR:

if(Serial.available() > 1) { // if two bytes have arrived
    uint8_t MSB = Serial.read();
    uint8_t LSB = Serial.read();
    int x = (MSB << 8) | LSB;
    // do something with x ...
  }

Note that this is not a good approach, because it is prone to framing errors.
If the sender and the receiver get out of sync (because of a missed byte, for example), the receiver will interpret the LSB as a MSB and vice versa.

To get around this, you could send only 7 data bits per byte, and use the 8th bit to indicate whether it’s an MSB or an LSB:

void send14bitSerial(int x) {
  uint8_t low = x & 0b01111111; // seven least significant bits (bits 0-6)
  uint8_t high = (x >> 7) & 0b01111111; // bits 7-13
  Serial.write(0b10000000 | high); // set bit 7 to 1, to indicate that this is these are the seven high bits.
  Serial.write(low);
}

On the receiver side:

void loop() {
  if (Serial.available() > 1) { // if two bytes have arrived
    uint8_t first = Serial.read();
    if (!(first & 0b10000000)) // if the first byte is not an MSB
      return; // out of sync, throw byte away and restart loop
    uint8_t second = Serial.read(); // bits 0-6
    int x = ((first & 0b1111111) << 7) | second;
    // do something with x ...
  }
}

Note that you lose two bits of accuracy by doing this (7*2 = 14 instead of 16 bits)
This is not a problem if all numbers you want to send are smaller than 16384.

Alternatively, you could use Serial.print, like Robin2 suggested, the link he posted contains everything you need to know.

Pieter

1 Like

Thanks Pieter, I was aware of Robin's post - your answer was what I was looking for. Cheers.

benjaminhoey:
Thanks Pieter, I was aware of Robin's post - your answer was what I was looking for. Cheers.

If you really do need to send data as a series of bytes you should learn about UNIONs. They allow you to refer to the same memory space in different ways - for example as an INT and as an array of 2 bytes. That would allow you to send (or receive) the 2 bytes without any need to convert anything.

...R

Robin2:
If you really do need to send data as a series of bytes you should learn about UNIONs. They allow you to refer to the same memory space in different ways - for example as an INT and as an array of 2 bytes. That would allow you to send (or receive) the 2 bytes without any need to convert anything.

Most of the time, a simple type cast will do the trick, but that's a matter of preference, I guess.

uint16_t x = 0xABCD;
Serial.write((uint8_t*) &x, sizeof(x));

Just keep in mind that Arduino is Little Endian, so it will write out 0xCD first, followed by 0xAB. Also note that the size of an 'int' is not the same on 8-bit AVRs (2 bytes) and 32-bit ARM microcontrollers (4 bytes), so it's a good idea to use a fixed number of bits (uint16_t instead of int, for example).
This doesn't solve framing issues though, once sender and receiver get out of sync, all data you receive is garbage, and you have no way of knowing that. That might not be a problem for a non-critical hobby project with short wires etc., but it's good to know.

Pieter

benjaminhoey:

void send_measurement() {

Serial.write((byte)measurement / 256);
 Serial.write((byte)measurement % 256);
 Serial.flush();
}

This code would work if the cast were done in the correct place. As it stands, it casts the measurement to a byte before the division and modulus operations, so it loses the most-significant bytes. Wrap "measurement / 256" and "measurement % 256" in parentheses so that the whole expressions get cast to a byte:

void send_measurement() {
  Serial.write((byte)(measurement / 256));
  Serial.write((byte)(measurement % 256));
  Serial.flush();
}

The division and modulus operators are very slow.

...R

The compiler will convert the division and modulus to fast shift and mask operations (though that might be true only if the measurement variable is unsigned).