本文说明如何把本目录下的 VOFA 串口组件移植到其他 STM32 HAL 工程中。
当前组件提供:
- 通过 UART DMA 环形缓冲发送数据
- 通过 UART ReceiveToIdle DMA 接收数据
- 按
\r或\n组装一帧下行命令 - 支持 VOFA+
JustFloat格式发送 6 个float - 支持简单的
key=value命令解析,例如 VOFA 发送控件格式kp=%.3f,帧尾选择\n
需要移植的文件:
vofa/
├── vofa.c
└── vofa.h
把 vofa.c 加入工程编译,把 vofa.h 所在目录加入头文件搜索路径。
仓库根目录提供了两个 VOFA+ 配置文件:
vofa+.tabviews.json
vofa+.cmds.json
使用 VOFA+ 上位机时,建议先导入这两个配置文件:
- 导入
vofa+.tabviews.json,用于恢复波形/标签页视图配置。 - 导入
vofa+.cmds.json,用于恢复下发命令配置。
导入后可以直接使用预设的窗口波形显示和 kp=%.3f 等命令发送格式。
以 STM32 HAL 工程为例,需要先在 CubeMX 中配置好 UART 和 DMA:
- 开启一个 UART,例如 USART1。
- 配置 UART 波特率,例如
115200或更高。 - 开启 UART TX DMA。
- 开启 UART RX DMA。
- 开启 UART 全局中断。
- 生成代码后确认工程中已初始化
MX_DMA_Init()和MX_USARTx_UART_Init()。
初始化顺序建议如下:
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
Vofa_Init(&huart1);如果使用其他串口,例如 huart2,只需要改成:
Vofa_Init(&huart2);组件内部的 HAL 回调会通过 Vofa_Init() 传入的 UART 句柄判断目标串口,不再依赖固定的 huart1。
平台相关内容集中在 vofa.h 顶部:
#include "stm32f4xx_hal.h"
#define VOFA_ENTER_CRITICAL() __disable_irq()
#define VOFA_EXIT_CRITICAL() __enable_irq()
#define VOFA_UART_DMA_SEND(huart, buf, len) HAL_UART_Transmit_DMA((UART_HandleTypeDef *)(huart), (uint8_t *)(buf), (len))移植到其他 STM32 系列时,通常只需要把 HAL 头文件改成对应芯片系列,例如:
#include "stm32h7xx_hal.h"如果不是 STM32 HAL 平台,需要适配这 3 个宏:
VOFA_ENTER_CRITICAL():进入临界区,防止环形缓冲状态被中断打断VOFA_EXIT_CRITICAL():退出临界区VOFA_UART_DMA_SEND(huart, buf, len):启动一次 UART DMA 发送
默认缓冲区在 vofa.c 中定义:
uint8_t tx_ring[512];
uint8_t rx_frame[128];
uint8_t dma_rx[128];含义如下:
tx_ring:发送环形缓冲区,发送数据量大时可以适当增大rx_frame:接收命令帧缓冲区,必须能容纳一整条命令dma_rx:UART RX DMA 临时缓冲区
如果 VOFA 发送频率高、波特率低,建议增大 tx_ring,避免 Vofa_Write() 因缓冲区满而丢弃部分数据。
在 main.c 中包含头文件:
#include "vofa.h"初始化外设后调用:
Vofa_Init(&huart1);定义好你要接收的变量
static void VofaCmdHandler(const char *str)
{
float value;
if (Vofa_GetKeyFloat(str, "kp=", &value))
{
// 使用 value
}
}在主循环中轮询接收命令并发送数据:
while (1)
{
Vofa_ReceivePoll(VofaCmdHandler);
Vofa_SendFloat(v1, v2, v3, v4, v5, v6);
HAL_Delay(10);
}Vofa_SendFloat() 固定发送 6 个 float,格式为 VOFA+ JustFloat:
Vofa_SendFloat(ch1, ch2, ch3, ch4, ch5, ch6);VOFA+ 上位机中选择 JustFloat 协议,即可查看波形。
如果需要发送自定义协议,可以直接使用:
const uint8_t data[] = {0x01, 0x02, 0x03};
Vofa_Write(data, sizeof(data));Vofa_Write() 会把数据写入发送环形缓冲区,并自动启动 DMA 发送。
组件按 \r 或 \n 判断一帧结束。VOFA+ 下发命令时可以使用发送控件,数据格式填写:
kp=%.3f
帧尾填写:
\n
发送时 VOFA 会把输入数值格式化成类似 kp=1.200\n 的文本帧,单片机会解析 kp= 后面的浮点数。
其他参数可以按同样方式添加发送控件,例如:
ki=%.3f
kd=%.3f
target=%.3f
主循环中调用:
Vofa_ReceivePoll(VofaCmdHandler);命令解析辅助函数:
float f;
int i;
if (Vofa_GetKeyFloat(str, "kp=", &f))
{
// 解析到浮点值
}
if (Vofa_GetKeyInt(str, "mode=", &i))
{
// 解析到整数值
}vofa.c 已实现两个 HAL 回调:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);如果你的工程中其他文件已经实现了同名回调,不能重复定义。需要把 VOFA 相关逻辑合并到已有回调中:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
// 保留已有逻辑
if (huart == (UART_HandleTypeDef *)g_vofa.huart)
{
VOFA_ENTER_CRITICAL();
uint16_t sent = g_vofa.tx_dma_len;
g_vofa.tx_tail = (g_vofa.tx_tail + sent) % g_vofa.tx_ring_size;
g_vofa.tx_dma_busy = false;
VOFA_EXIT_CRITICAL();
Vofa_TxKick();
}
}void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
// 保留已有逻辑
if (huart == (UART_HandleTypeDef *)g_vofa.huart)
{
for (uint16_t i = 0; i < Size; i++)
{
VofaSerial_PushRxByte(&g_vofa, dma_rx[i]);
}
HAL_UARTEx_ReceiveToIdle_DMA(huart, dma_rx, sizeof(dma_rx));
}
}说明目标工程不是 STM32F4,或 HAL 头文件路径不同。修改 vofa.h 顶部的 HAL 头文件,例如:
#include "stm32g4xx_hal.h"说明工程中已经有 HAL_UART_TxCpltCallback() 或 HAL_UARTEx_RxEventCallback()。删除其中一份定义,或把 VOFA 逻辑合并到现有回调中。
检查:
- 是否调用了
Vofa_Init(&huartx) - 是否开启 UART RX DMA
- 是否开启 UART 全局中断
- 是否在回调中重新调用了
HAL_UARTEx_ReceiveToIdle_DMA() - 上位机串口号、波特率、数据位、停止位是否一致
检查:
- 波特率是否太低
Vofa_SendFloat()调用频率是否太高tx_ring是否需要增大Vofa_Write()返回值是否小于待发送长度
- 复制
vofa.c和vofa.h。 - 把
vofa.c加入编译。 - 把
vofa.h所在目录加入 include 路径。 - 配置 UART TX DMA、RX DMA 和 UART 中断。
- 根据芯片系列修改
vofa.h中的 HAL 头文件。 - 在外设初始化后调用
Vofa_Init(&huartx)。 - 在主循环中调用
Vofa_ReceivePoll()和需要的发送函数。 - 如已有 HAL UART 回调,合并 VOFA 回调逻辑。
- 在 VOFA+ 上位机中导入根目录的
vofa+.tabviews.json和vofa+.cmds.json。
如果移植过程中遇到问题,或发现文档/代码有需要改进的地方,欢迎提交 Issue 交流。