3

I have some C code running on an embedded processor, which outputs integers (16 bits) over a serial port (stdout) using printf("%d", my_int);. I have some python code running on a PC that reads the serial stream using pyserial and writes it to a file, which I can just achieve using ser.readline(). This works quite happily, although it isn't very efficient.

As the embedded C is sending quite a lot of numbers and I want to cut down on the transfer time, I would like to send the data formatted as binary rather than ascii (i.e. two bytes/chars rather than several bytes/chars.) I tried to do this with:

char msbs = my_int>>8;
char lsbs = my_int;
printf("%c%c\n", msbs, lsbs)

And then in my python code:

chars = ser.readline()
value = ord(chars[0])<<8 + ord(chars[1])

But value seems to be gibberish. Could anyone point out the thing(s) I'm doing wrong?

0

3 Answers 3

4

Try with this:

import struct
chars = ser.readline()[:-1]
value = struct.unpack('>h', chars)

I've assumed that your integer is signed short and please note the presence of '>' for endianness (bytes order).

However the error with you code was because of operator precedence: value = (ord(chars[0])<<8) + ord(chars[1]) this should work. But it is better to use struct anyway.

Sign up to request clarification or add additional context in comments.

Comments

0

The struct idea is great, but your protocol is faulty.

If you write binary data, it is not useful to have line endings in-between: one of the "value bytes" could have that value - 10 - as well.

So better refrain from using these line ends and readline().

But there could arise the problem that you could get out of sync. So you either should define a kind of package boundaries, or you could encode your data in more than 2 bytes.

Example: Each 16bit value is encoded in 3 bytes, in the following way:

AADDDDDD BBBDDDDD CCCDDDDD

AA is 10, BBB is 110, CCC is 111.

The value 57723 - 0xE17B, 0b1110000101111011, is encoded as

10111000 11001011 11111011

or

B8 CB FB

by means of

byte1 = 0x80 + ((value >> 10) & 0x3F) # bits 10..15
byte2 = 0xC0 + ((value >> 5)  & 0x1F) # bits 5..9
byte3 = 0xE0 + ((value)       & 0x1F) # bits 0..4

Upon reception of these bytes, you immediately can detect

a) which byte it is (the 1st, the 2nd or the 3rd) and b) its contents.

So even if one byte gets missing, you can detect that and can resume reception of the stream immediately.

Plus, this implementation is endianness-agnostic - it will work on every machine regardless of its endianness, a way every protocol should be designed.

How to implement that I'll leave to you.

Comments

0

As the embedded C is sending quite a lot of numbers and I want to cut down on the transfer time,

To improve time performance you could send and/or receive more than one number at a time. In my tests, array.fromfile() is 10 - 100 times faster than struct.unpack(). It comes at the cost of calling array.byteswap() sometimes to take into account endianness explicitly.


If the C program and Python script were running on the same machine (same size, same endianess); then you could use fwrite to write short ints as platform values on C side and array.fromfile() on Python side to read them back in a native format.

For example, print short ints as binary:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  short a[] = {31415, 9265, 3589, 793};
  size_t n = sizeof a / sizeof *a;
  if (fwrite(&n, sizeof n, 1, stdout) != 1) exit(EXIT_FAILURE); /* send size */
  return (fwrite(a, sizeof *a, n, stdout) < n) ? EXIT_FAILURE : EXIT_SUCCESS;
}

Read it in Python:

#!/usr/bin/env python3
import sys
import array
import struct

# make stdin binary
file = sys.stdin.detach()

# read size
size_format = 'N' # size_t
n, = struct.unpack(size_format, file.read(struct.calcsize(size_format)))
print(n)

a = array.array('h') # native short int
a.fromfile(file, n)
print(a.tolist()) # -> [31415, 9265, 3589, 793]

array.fromfile should be efficient both time and memory-wise. If you don't know the size then call a.fromfile until EOFError is raised.


If C program and Python script are on different machines then you could send integers in the network byte order:

#include <stdio.h>
#include <stdlib.h>

#include <netinet/in.h> /* htons() */

int main(void) {
  short a[] = {31415, 9265, 3589, 793};
  /* print one integer at a time */
  short *p = a, *end = &a[sizeof a / sizeof *a];
  for ( ; p != end; ++p) {
    uint16_t s = htons(*p); /* convert from native to network byte order */
    if (fwrite(&s, sizeof s, 1, stdout) != 1)  exit(EXIT_FAILURE);
  }
  return 0;
}

And swap byte order if necessary on the Python side:

#!/usr/bin/env python
import array
import sys

a = array.array('h') # short int in native byte order, byte swap might be needed
for i in range(15, 128): 
    try: # double size to avoid O(n**2) behaviour
        a.fromfile(sys.stdin, 2 << i)
    except EOFError:
        break
if sys.byteorder != 'big': # if not network order
    a.byteswap()  # swap byte order
print(a.tolist()) # -> [31415, 9265, 3589, 793]

To avoid converting to network order, you could send a magic number instead. It allows to send numbers in a native byte order on C side (as in the 1st code example) and check it in Python to swap bytes if necessary:

MAGIC = 1
if a[0] != MAGIC:
   a.byteswap()
   if a[0] != MAGIC:
      raise ValueError("Unexpected %d" % a[0])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.