1

I'm using a capacitive touch sensor that as 12 touch points and stores it's state data as a binary number.

getting the touch state

I want to take that state, add a bit of data onto the front, and then send it up to a server via websockets. I'm using the Arduino Websockets library for my websocket client.

The library has a sendBinary method that allows you to provide a character pointer for the data.

sendBinary declaration

So, I wrote a function that makes a new 32 bit unsigned integer, adds my metadata, then the touch state, and then attempts to take the address of the payload integer and cast it to a character pointer:

void sendTouchStateToServer()
{
    Serial.println("sending state to server");

    uint32_t payload = touchControllerMessageType << 12;
    Serial.println(payload);
    payload |= currentTouchState;

    Serial.print("Payload contents: ");
    Serial.println(payload);
    Serial.print("Payload contents cast as character pointer: ");
    Serial.println((char *)&payload);
    Serial.print("Payload size: ");
    Serial.println(sizeof(payload));

    client.sendBinary((char *)&payload, sizeof(payload));
}

The issue is that while the integer version of the payload looks fine, when I try to cast it into a character pointer the contents is wrong.

garbled output

I've also tried creating a payloadPointer and directly set the address of the integer as the pointer address, but this also fails.

direct pointer addressing (I think)

(I should take this moment to say I'm still pretty green to arduino cpp concepts like this).

What's the right way of doing this? I'm reading up on pointers and trying to understand how to properly do it, but I can't quite figure it out. Could someone explain the correct way of doing it and why? I want this to work, but more than that I want to understand the "why and how" of the proper way.

Thanks! :bows:

2
  • Are you trying to send the {metadata, payload} as a string of characters? Converting the payload by casting it doesn't do what you want it to. If so, you need build the string in a buffer (char[]), convert or copy the metadata into that buffer, convert (not cast) the data from integer to its string representation, append that string to the buffer, and finally, pass the address of the buffer (that's the (char *) you need) to the function expecting chars. Casting an int to a (char *) doesn't change it; it tells the compiler to consider this to be a pointer to characters - which it isn't!
    – JRobert
    Nov 20 '20 at 17:14
  • When sending binary data, use Serial.write(), not Serial.print(). The latter is only for ASCII encoded data
    – chrisl
    Nov 20 '20 at 18:29
3

Your code with sendBinary is probably fine, as long as on the other side you also use a function that expects to receive exactly 32 bits of binary data (in little-endian format).

Trying to print (char*)&some_32bit_int on the other hand will not do anything useful.

The short version is this:

  • if you want to send (or receive) binary data, use functions made for binary data - they will usually take a pointer to char (or uint8_t or something like that) and a length.
  • if you want to send (or receive) strings, use functions meant for strings. They'll sometimes take String, or std::string or, unfortunately perhaps, char * for C strings.

Never mix both.

Functions that expect a (C) string expect it to actually be a C string, i.e. a sequence of chars ending with a null byte (0x00). The chars are expected to be mostly ASCII printable characters. If you give them just plain raw data like the memory address of an int, you won't get what you want.

Trying to print raw data using a function that expects a C string will result in garbage (or nothing at all, or a lot more garbage than you expected!). (Details in the second part.)

If you want to format your data into a string for sending (using a string/text protocol), then use functions like sprintf to do the conversion. (Or use a library that does JSON if your receiver is a web thing - that's pretty handy.)

e.g.

char buffer[32];
sprintf(buffer, "{data:%d}", payload);

Then send buffer via a function that expects a C string.


Consider this:

uint32_t payload = 0x00323130;

The first line initializes a 32 bit int to a specific value, 0x00323130 in hex. Now let's assume that payload was stored in memory at address 0x0100. The memory after that assignment would look like this:

Addr.  Val
0x0100 0x30  // our same number 0x00323130 stored in little-endian format
0x0101 0x31
0x0102 0x32
0x0103 0x00

When you do:

client.sendBinary(&payload, 4);

Those four bytes get send over the wire (or the air) exactly as they are. Nothing more, nothing less. If that's what the receiver expects you're golden.

Now if you do:

Serial.println((char *) &payload);

Serial.println is an overloaded function. When you give it a char *, it expects a C-string, which is a series of characters terminated by a "null byte", i.e. a byte value of zero. Serial.println will then look at the first byte pointed to by the argument. If it's zero, it stops. Otherwise it outputs that char to the serial line, and moves on to the next character. Repeat until a zero byte is found.

In the specially crafted case here with that specific value of payload, Serial.println would receive address 0x0100 and:

  • Look at the value at 0x0100, get 0x30, check that it's not zero, and pass it on to the serial line. Serial monitor would receive 0x30 and display that. By lucky coincidence this is the ASCII character code for the digit "0".
  • Look at the value at 0x0101, get 0x31, check that it's not zero, and pass it on to the serial line. Serial monitor would receive 0x31 and display that. By lucky coincidence this is the ASCII character code for the digit "1".
  • Look at the value at 0x0102, get 0x32, ... serial monitor displays "2".
  • Loot at the value at 0x0103, get 0x00. That is a null byte, so it stops there, and sends a newline sequence to the serial.

So in this fabricated scenario the output on the serial monitor would be "123".

Try it with payload = 0x00616263; - serial monitor will display "cba".

In short, Serial.println will output exactly the bytes it finds in memory to the serial, until it encounters a zero byte. The serial monitor will try to display those bytes. But, unless you've crafted those values very carefully, all you'll get out of it is garbage - relatively few 8bit values map to printable characters, and even when they do they won't "look" anything like the raw data you had.

If your number happens to start with a zero byte in its binary little-endian representation, Serial.println won't print anything - like if you had given it an empty string.

If your number doesn't contain a zero byte, it will keep on reading memory past the storage allocated for payload until it finds one - possibly outputing much more "junk" than a 32bit variable could ever contain.

3
  • 1
    Excellent, thorough answer. (Voted)
    – Duncan C
    Nov 20 '20 at 21:24
  • 1
    Good answer. I also voted.
    – Erdem
    Nov 21 '20 at 10:29
  • Wow, this answer was fantastic! Thanks for taking the time to write this out. This makes a lot of sense. I'm going to do some c++ exploration outside of my arduino so i can do some memory inspection (I don't have the tools to do JTAG debugging). Thanks again for the answer! Nov 21 '20 at 18:09
2

The sending with client.sendBinary((char *)&payload, sizeof(payload)); is OK.

Your attempts to print binary data are wrong.

A print() of the 4 bytes of uint32_t as char array will print 4 characters which ASCII codes are in those 4 bytes and then continue print characters from memory after the variable, until a byte with 0 is read. Some of the characters can be a not printable terminal control characters.

A Serial.write((char *)&payload, sizeof(payload)); will print 4 characters which ASCII codes are in those 4 bytes.

The simplest way to visualize your binary payload is Serial.println(payload, BIN);. This will print the uint32_t value in binary.

1
  • Awesome. Thanks for the answer. It's good to know about the binary formatting :clap: Nov 21 '20 at 18:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.