arduino byte转string_Arduino 如何通过串口发送浮点数,比如带小数点的温度?

95b4d607268984b227e87a9240fa3f9b.png
逃避责任区:文中结论属于自己学习心得,不保证学术正确性和严谨性。如有问题,欢迎评论区讨论、批评、指正。部分图片、结论、源码可能来自其它文章,由于自己记学习笔记的时候并没有处处都标明出处,如有造成侵权问题,实属抱歉,请第一时间联系我删除。

问题的引入

很多时候我们需要把传感器测得的数据,通过串口或者 Wi-Fi 发送到上位机,而传感器的数据,比如温度、湿度等,往往并不是整型,比如 -12.34 °C,56.78% 等。对此有很多处理方法。比如把数字当成字符串处理,直接使用 Serial.print() 发送,或者通过乘法转换成整型以后再发送,又或者将浮点数拆分成整数和小数两部分发送 ... 下面介绍一种方法,通过先将浮点数转换成字节数组(Byte Array),然后再按字节发送,实现浮点数据的传输。好处就是:精度得到保留,数据长度短且统一。

实现方法

基本思路

用一个单精度浮点数定义一个假想的温度值:

float hypoTemp = -12.34  // 假想从传感器获得的温度值

一个单精度浮点数(float)在 Arduino 中长度为 32 位 ,也就是 4 Byte。 比如上面的 -12.34 把它写成二进制和十六进制就是:

BIN: 1100 0001 0100 0101 0111 0000 1010 0100
HEX: 0xC14570A4

至于这是怎么换算的,请参看 IEEE 754 浮点数换算标准,网上教程大把。实际应用中,你可以通过在线工具,直接换算。

d0d5b029a27161fe273fe5ae18eb9b21.png
IEEE-754 Floating Point Converter​www.h-schmidt.net

既然是 4 Byte,那一个非常朴实的想法就是,用一个数组来收纳这 4 Byte,然后再通过 Serial.write(byteArr, 4) 写入到串口。

代码原型

void send_sensor_data()
{
    float hypoTemp = -12.34;    // 假想的从传感器获得温度值 0xC14570A4
    uint8_t charArr[4];         // 用来存储 4 字节的字节数组

    uint8_t *p;
    p = (uint8_t*) &hypoTemp;   // 让指针指向浮点数所在的内存

    for(int i=0; i<4; i++)
    {
      charArr[i] = *p++;        // 读取内存,把表示浮点数的字节放到数组中
    }

    Serial.write(charArr, 4);   // 按字节,写入串口,串口得到的是逆序的 OxA4 70 45 C1
}

这个代码的想法可以说非常淳朴了,就是用一个指针,让它指向表示浮点数的字节所在的内存,然后取出来放到数组,构成字节数组。

有几个注意点:

  • uint8_t 也可以用 Arduino 的 byte 来代替,都是表示 8 bit 长度。uint8_t 实际定义为 unsigned char, 但是 uint8_t 具有更好的可移植性。因为它只要能用就一定能保证是 8 bit。 而 unsigned char 能保证一定能用,但不保证一定是 8 bit (但是在定义了 typedef unsigned char uint8_t 的系统上 char 一定是 8 bit ,这有点绕... ),所以从「想定一个 8 bit 的变量并且有可移植性」角度来说,uint8_t 是最优选择。
unsigned charis guaranteed to exist, but is only guaranteed to be 8 bits when CHAR_BIT == 8. uint8_tisn't guaranteed to exist, but is guaranteed to be 8 bits when it does
  • Serial.write() 可以把一个数组的字节全部打印出来,但是需要指明长度。当然, 这里的 4 可以用 sizeof() 来求得。 但是由于这里是固定的 4 Byte,所以还是让单片机少点工作吧。
Syntax Serial.write(val) Serial.write(str) Serial.write(buf, len)
Parameters Serial: serial port object. See the list of available serial ports for each board on the Serial main page. val: a value to send as a single byte. str: a string to send as a series of bytes. buf: an array to send as a series of bytes. len: the number of bytes to be sent from the array.
  • 由于内存中字节存放的顺序,或者说大端小端问题,实际运行上面代码后,你在串口工具中看到的字节是逆序的,也就是 0xA4 0x70 0x45 0xC1 。所以在上位机对收到数据进行解析的时候需要格外注意(当然你也可以在单片机上把数据逆过来)。

f2ad876211e4efc3af0806671a05bfbd.png

优化代码

上面的代码,只是展示下思路,其实完全可以用一行代码来代替, 但是核心逻辑是一样的。

Serial.write((uint8_t*) &hypoTemp, sizeof(hypoTemp));

从代码的可复用性角度来说,建议把这个浮点数转字节数组封装成一个函数

void convFloatToByteArr(float val, uint8_t byteArr[4])
{
    memcpy(byteArr, (uint8_t*) &val, 4);
}

void send_float_data()
{
    float hypoTemp = -12.34;    // 假想的从传感器获得温度值 0xC14570A4
    uint8_t byteArrTemp[4];     // 声明一个用来存储 4 字节的字节数组

    convFloatToByteArr(hypoTemp, byteArrTemp);  // 调用

    Serial.write(byteArrTemp, 4);  // 写入串口
}

使用联合体 Union

但是后来我又发现,还可以使用联合体 Union (也叫共用体) 继续优化。

// 定义联合体
typedef union {
    float floatTemp;
    byte  byteArrTemp[4];
} uTemp;

void setup() {
    uTemp hypoTemp;
    hypoTemp.floatTemp = -12.34;
    Serial.begin(9600);
    Serial.write(hypoTemp.byteArrTemp, 4);
}

使用 Union 的意义在于,Union 中的成员是共享一段内存的,所以里面的字节数组和浮点数是「捆绑在一起的」,无论通过点操作符修改哪一个,另一个都会跟着改变。

现在就可以再次把这个浮点数转字节数封装成一个函数:

void convFloatToByteArr(float val, uint8_t* byteArr)
{
    // 定义 Union
    union{
        float floatVal;
        uint8_t byteArr[4];
    }uFloatByteArr;

    // 函数参数写入 Union
    uFloatByteArr.floatVal = val;

    // 把 Union 的字节数组拷贝给参数传递进来的数组
    memcpy(byteArr, uFloatByteArr.byteArr, 4);

}


void send_float_data()
{
    float hypoTemp = -12.34;    // 假想的从传感器获得温度值 0xC14570A4
    uint8_t byteArrTemp[4];     // 声明一个用来存储 4 字节的字节数组

    convFloatToByteArr(hypoTemp, &byteArrTemp[0]);  // 调用

    Serial.write(byteArrTemp, 4);  // 写入串口
}

补充

有时候还会看到结构体(Struct)和联合体 (Union) 一起使用的情况,从代码上看,结构体代替的是上面的字节数组。

//定义结构和联合  
typedef union   
{  
    struct   
    {  
        unsigned char low_byte;  
        unsigned char mlow_byte;  
        unsigned char mhigh_byte;  
        unsigned char high_byte;  
     }float_byte;  
            
     float  value;  
}FLAOT_UNION;  

经过大佬点拨,使用结构体代替数组的好处就是,结构体相比数组的数字索引,访问过程更加清晰,可读性更好。比如上面的结构体中明确声明了,哪些成员是表示高位字节,哪些成员是低位字节。

关联阅读

Arduino Form: Serial.write a float value​forum.arduino.cc 关于 Arduino float 类型的2个问题和实验 - WWW.LAB-Z.COM​www.lab-z.com
8bd8c14154e60fe4f07f750b02a1e02d.png

完!感谢阅读,欢迎评论区讨论指正。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值