——深入剖析FireWater与JustFloat协议,提供STM32实战代码

        你是否也曾遇到过这样的困扰:在STM32上跑算法,用VOFA+上位机看波形,却发现波形刷新一顿一顿,甚至串口通信占用了大量CPU时间?

        问题往往不出在算法本身,而在于你如何给VOFA+“送数据”

        今天,我们就来彻底解决这个问题。只需两个函数,你就能在“方便调试”和“极致性能”之间自由切换。

一、 从我们最熟悉的方式说起:Printf的“表亲”——FireWater

最开始用VOFA+时,我们很自然地会想到最类似printf的方法,因为它和我们在串口助手调试时一模一样。

就像这样:

float f1 = 0.5, f2 = 114.514;
printf("Data: %f, %f\n", f1, f2); // 串口助手调试

对应到VOFA+,我们使用前文支持的FireWater协议。

// 按printf格式写,最后必须加\r\n
void Vofa_FireWater(const char *format, ...)
{
    uint8_t txBuffer[100];
    uint32_t n;
    va_list args;
    va_start(args, format);
    n = vsnprintf((char *)txBuffer, 100, format, args);

    //....在此替换你的串口发送函数...........
    HAL_UART_Transmit_DMA(&huart1, (uint8_t *)txBuffer, n);
    //......................................

    va_end(args);
}

使用方法:

Vofa_FireWater("%f,%f\r\n", f1, f2); // 注意结尾必须是\r\n

工作原理浅析:

  1. vsnprintf函数将变量f1f2按照格式%f,%f转换成一个字符串,比如"0.5,114.514\r\n"

  2. 将这个字符串的每一个字符的ASCII码通过串口发送出去。

  3. VOFA+收到后,识别到换行符\r\n,就知道一帧数据结束了,然后将中间的字符串解析回数字并显示。

优点:

  • 极其友好:和printf用法完全一样,上手零门槛。

  • 人类可读:你用串口助手直接连接,能看到明文数据,非常利于单独调试。

缺点:

  • 效率低下:一个float类型数据本来只占4字节,转换成字符串后可能变成7、8个甚至更多字节,数据量暴增。

  • CPU负担重:浮点数转字符串是一个比较复杂的计算过程,会消耗宝贵的MCU资源。

        当你的数据量不大、刷新率不高时,用FireWater协议非常方便。但当你要同时传输十几个通道的数据、还要达到100Hz以上的刷新率时,它的缺点就会被无限放大,导致波形卡顿。

二、 让性能飞起来的“终极武器”:JustFloat协议

        那么,有没有一种方法,既能传输数据,又不用进行复杂的转换呢?

        有!答案就是:不要转换!直接发送数据的“原始字节”

这就像寄送一件易碎品:

  • FireWater:把它拆开,拍好照片,写成详细的说明书(生成字符串),寄过去让对方照着说明书组装(解析字符串)。

  • JustFloat:直接原包装打包,贴上一个“易碎品”的标签(加上帧尾),整个寄过去。对方一看标签就知道是什么。

仓库提供的第二个函数Vofa_JustFloat就是干这个的:

// 输入个数和数组地址
void Vofa_JustFloat(float *_data, uint8_t _num)
{
    uint8_t tempData[100];
    // JustFloat协议规定的帧尾:0x00, 0x00, 0x80, 0x7F
    uint8_t temp_end[4] = {0, 0, 0x80, 0x7F};
    float temp_copy[_num];

    // 将原始数据拷贝到一个临时数组(可选,出于安全考虑)
    memcpy(&temp_copy, _data, sizeof(float) * _num);

    // 核心操作:将float数组的“内存原始字节”拷贝到发送缓冲区
    memcpy(tempData, (uint8_t *)&temp_copy, sizeof(temp_copy));
    // 在数据末尾加上协议帧尾
    memcpy(&tempData[_num * 4], &temp_end[0], 4);

    //....在此替换你的串口发送函数...........
    HAL_UART_Transmit_DMA(&huart1, tempData, (_num + 1) * 4);
    //......................................
}

使用方法:

float my_data[3] = {88.77, 0.66, 55.44};
Vofa_JustFloat(my_data, 3); // 发送3个float

工作原理深究:

  1. 内存视角看Float
    一个float变量在STM32内存中占4个字节。例如1.0f这个数,它的二进制表示(IEEE 754标准)的16进制是0x3F800000。由于STM32是小端模式,它在内存中的字节序列是:
    地址0: 0x00 -> 地址1: 0x00 -> 地址2: 0x80 -> 地址3: 0x3F

  2. 核心魔法 (uint8_t *)&temp_copy

    • &temp_copy 获取的是这个float数组的首地址

    • (uint8_t *) 是一个强制类型转换。它告诉编译器:“别把这个地址当成float数组看了,请把它当成一个uint8_t(字节)数组来看待”。

    • memcpy的作用,就是把这片内存里的每一个字节,原封不动地、一个不落地复制到发送缓冲区tempData里。

  3. 神秘的帧尾 0x00, 0x00, 0x80, 0x7F
    这4个字节组合在一起,在IEEE 754标准中代表一个非数字(NaN)。VOFA+在解析数据流时,一旦发现这个特殊的字节序列,就确信之前的数据是一个完整的帧,可以开始解析显示了。因为它几乎不可能在正常数据中出现,所以作为帧尾非常可靠。

优点:

  • 极致高效:一个float就是精准的4个字节,带宽利用率达到100%。

  • 速度极快:没有浮点数转字符串的复杂计算,只有高效的内存拷贝,CPU占用率极低。

  • 无损传输:发送的就是内存bit位,不存在转换过程中的精度损失。

缺点:

  • 人类不可读:如果你用串口助手直接看,会看到一堆乱码,无法直接调试。

三、 总结与如何选择

特性 FireWater协议 JustFloat协议
原理 转换数据为字符串 发送原始内存字节
可读性  (串口助手可直接看)  (显示为乱码)
数据量 极小 (4字节/float)
CPU开销 极低
适用场景 低速调试,验证逻辑 高速实时波形显示,多通道数据采集

给你的建议:

  • 前期调试,数据量小,只是偶尔看一下数值:用 Vofa_FireWater,简单粗暴。

  • 后期优化,需要实时观察波形、图像,频道多,刷新率高:一定要用 Vofa_JustFloat,让你的波形如德芙般丝滑。

        希望这两个小巧而强大的函数,能帮助你更好地利用VOFA+这个强大的工具,让开发调试过程更加得心应手!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐