My project needs to extract sensor data from STM32, now my problem is this CDC_TRANSMIT_FS function can only send uint8_t data, I want to at least send uint16_t data or 32-bit float, how can I achieve this? Thanks
2 Answers 2
The function is defined as:
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
So you can send anything you want in binary form:
int16_t data16;
float dataF;
CDC_Transmit_FS((uint8_t *)&data16, sizeof(data16));
CDC_Transmit_FS((uint8_t *)&dataF, sizeof(dataF));
Or in textual:
char buff[127];
snprintf(buff, 127, "Your float var = %f\r\n", dataF);
CDC_Transmit_FS((uint8_t *)buff, strlen(buff));
It's a basic C programming question, I recommend reading a C programming book as soon as possible, a basic read should help you to solve similar problems in the future. Preferably, find a modern one, perhaps with an emphasis on embedded systems (I think many "classic" C books, including K&R, were written for system programmers, not embedded programmers. The use of archaic short
, int
, long
instead of stdint.h
, or the lack of emphasis on bitwise operation, are all confusing for newcomers in embedded electronics, K&R only has two small sections on bit-wise operations for example).
Sending uint16_t
To send an uint16_t
integer using uint8_t
words, one can send its higher 8-bit part and the lower 8-bit part separately, using bit masks and shifts.
uint16_t x = 0x55AA;
/*
* Use a bitwise-AND operation to clear its higher 8 bits.
*
* For example: 0b0101010110101010
* AND 0b0000000011111111
* = 0b0000000010101010
*/
uint8_t x_low = x & 0x00FF;
/*
* Shift 8 bits to the right.
*
* Now, the lower 8 bits is overwritten by the higher 8 bits,
* and the higher 8 bits become zero. The final "& 0x00FF"
* is arguably superfluous, but I think it's still a good idea
* to show the truncation to 8-bit explicitly.
*
* For example: 0b0101010110101010 >> 8
* = 0b0000000001010101
*/
uint8_t x_high = (x >> 8) & 0x00FF;
Now, x_low
and x_high
can be transmitted as two 8-bit words separately. At the reception side, they're recombined into a 16-bit integer again using shifts and add.
uint16_t x = (x_high << 8) | x_low;
// or uint16_t x = (x_high << 8) + x_low;
Sending an uint32_t
integer using uint8_t
words requires four steps, and it's left as an exercise for the reader. See this post for answer.
Sending float
or double
Sending a single or double-precision floating-point number is a bit more tricky in C. It requires reinterpreting the underlying binary representation of a floating-point number as an integer. This is known as type punning in C, and its behavior is not well-defined by the C standard.
But roughly there are three ways to do this, with varying degrees of correctness.
Create an integer pointer, and point it to a floating-point number, and dereference the pointer as an integer, and then use it as if it's an integer.
float x_float = 3.14; uint32_t x_32 = *(uint32_t *) &x_float;
Now
x_32
can be transmitted as if it's a 32-bit integer. This invokes an undefined behavior in the C standard, most compilers will generate a warning about this operation. Nevertheless, this kind of code is not uncommon in low-level programs. But do keep its undefined nature in mind.Create an
union
type that contains both an floating-point number and an integer. First, store the floating-point number into the union, then take the integer out.union { uint32_t x_32; float x_float; } x_punning = { .x_float = 3.14 }; uint32_t x_32 = x_punning.x_32;
Most compilers do not generate a warning if this method is used. Nevertheless, there still has been a controversy on what the C standard says about it. I'm not a C standard expert and I stall remain silence, but my impression is that it's not really well-defined. Nevertheless, this kind of code is also not uncommon in low-level programs. But do keep its undefined nature in mind.
Use a
memcpy()
operation to copy the memory content of the floating-point number to the integer. This, as far as I know, is the most standard-compliant way to do this operation (but some may say it's superfluous and overkill). And when using a modern compiler like GCC or Clang, the performance overhead is not too high, as they can optimize the copy as an inline operation instead of doing an actual function call.float x_float = 3.14; uint32_t x_32; memcpy(&x_32, &x_float, sizeof(uint32_t));
For double
, use uint64_t
.
Incorrect Assumption in the Question
The OP said,
CDC_TRANSMIT_FS function can only send uint8_t data
So both brhans and I assumed it's a "breaking a word into multiple bytes via mask and shift" question. But it has been pointed out by out0f0rder, the actual function definition is:
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
In other words, the function can send any number of bytes given its initial address, the pointer *Buf
. Thus, none of these type punning I've just mentioned is necessary, one can simply take the address of the uint16_t
or uint32_t
and use the correct Len
, such as:
uint16_t x_16;
CDC_Transmit_FS((uint8_t *) &x_16, sizeof(uint16_t));
// sizeof(uint16_t) == 2
uint32_t x_32;
CDC_Transmit_FS((uint8_t *) &x_32, sizeof(uint32_t));
// sizeof(uint32_t) == 4
float x_float;
CDC_Transmit_FS((uint8_t *) &x_float, sizeof(float));
// sizeof(float) == 4
However, it should be mentioned that sending uint16_t
or uint32_t
as an array of uint8_t
is not well-defined and not portable, as the underlying machine may use Big Endian or Little Endian (the bit mask and shift method is well-defined). If the transmitter and receiver are running on machines with different endianness, incorrect data will be received and it can cause portability issues. Though Big Endian systems are exceedingly rare these days, do keep this issue in mind.
uint8_t
to make up a singleuint16_t
, or 4 xuint8_t
to make up afloat
? \$\endgroup\$