为了阐述清晰和 miniFOC 的开发进行的区分, 单独一章讲一讲这个上位机调试工具 Vofa+, 当时是看到 B 站 Up mandzy 的视频以及 Vofa+ 精致的界面感觉这个工具能够帮上不少忙。 目前 Vofa+ 支持三种传输协, FileWater, JustFloat, RawData。 对于电机控制而言, 传输波形的数据需要用到浮点, 第二个传输浮点数据的协议就比较合适了, 具体选用哪种协议示情况而定。
Vofa+ demo, HangX-Ma
0. 资料汇总
- Vofa plus
- Vofa plus - plugins
- STM32使用HAL库生成USB虚拟串口连接VOFA+输出波形 - mandzy Bilibili
- STM32F4使用DMA串口通信连接VOFA软件进行电机调试和波形显示 - mandzy Bilibili
- ST RM0008 Reference manual
1. STM32 Virtual Serial Port
本质上是用 USB 虚拟串口进行数据收发, 没想到 F103 系列也支持, 但是后来看了看 F103 虚拟串口没有 DMA 功能, 这个就比较致命了。 按照现有设计, 除了进行指令的交互, MCU 还需要通过串口向上位机传输数据, 如果直接交由 CPU 进行处理会极大浪费计算资源, 一般来说需要通过 DMA 规避 CPU 对这些数据的处理。 所以, 对于 F103 系列还是老老实实用 USARTx + DMA 的组合比较合适。
2. Vofa+: JustFloat Protocol
具体的协议内容可以看官网的 JustFloat 说明, 除了采样数据解析外, 该协议还能进行图像解析。 目前我只用到了数据采样解析部分, 搞懂这个也就一通百通了, 可以看到滚放定义的数据帧如下所示, 帧尾是一个 4 字节的特定数据 0x00, 0x00, 0x80, 0x7f
, 这是小端的顺序, 大端需要调换一下数据的存储顺序。 也就是说, 我们在发送完希望传输的数据后, 需要紧跟在这些数据后面发送一个尾帧, 才能让 Vofa+ 识别数据帧的内容并进行可视化以及文本打印。
#define CH_COUNT 通道数量
struct Frame {
float fdata[CH_COUNT];
unsigned char tail[4]{0x00, 0x00, 0x80, 0x7f};
};
一般来说, 串口是无法接收 float 类型的数据的, 但 float 类型在 STM32 中占据 4 个字节, 因而我们可以定义这样一个 union 联合体结构解决 float 数据传输的问题。 fdata
和 chars
共用一块内存区域并且都占据 4 个字节, 这样在使用串口发送数据的时候就能将 fdata
拆分成 4 个 chars
进行发送。
/**
* @brief Use union to decompose the float_data into 4 bytes char.
* Because the USART only transfer bytes.
*/
typedef union Format{
float fdata; // little-endian float
uint8_t chars[4];
} Format_Typedef;
Format_Typedef send_data[VOFA_SEND_CH_COUNT];
Format_Typedef recv_data[VOFA_RECV_CH_COUNT];
这样, 根据这个协议, 我们仅需要定义两个数据就能完成 USART 的收发, 将这两个数组的地址分别配置为发送部分 DMA 的内存地址, 以及接收部分的内存地址。 当然, 在设置 DMA 的传输数据总量的时候也需要考虑到末尾 4 个字节的特定数据帧。
具体的配置可以查看 vofa_usart.h 和 vofa_usart.c 这两个文件。
特别感谢 B 站 mandzy Up 提供的代码参考!
3. Vofa+ Components
Vofa+ 还提供了一系列控件方便我们进行对下位机的调试, 这里以这个 ExtraButtonToggle
控件为例。
将这个控件拖出到新的 Tab 中, 右击可以看到一个 事件与参数
的选项, 这是表示按钮在 激活 以及 关闭 的时候会发送的指令。 如果我们的下位机协议非常简单, 配置这个控件的这个选项的参数就能实现功能, 但我们经常会需要对指令包装一个帧头以区分不同功能的指令, 并在解析帧头后继续接收数据, Vofa+ 提供了一个 命令
区块方便我们发送 帧头+数据 这种组合。
Vofa+ components, HangX-Ma
以上图为例, 我定义了一段命令的内容为 AA FF 00 01
%% 00 00 00
, 前半部分是我定义的帧头数据, 只有识别到 AA
和 FF
我才会继续解析指令, 将末尾的 01
作为区分不同指令的具体标识。
而后半段则是我定义的数据帧, %%
标识占位符, 这样一组 %%
占位一个字节, 我在这个按钮控件的 事件与参数
部分设置了激活态发送 01
, 关闭态发送 02
, 这样在我按下按钮后, 根据当前按钮的状态, %%
会被替换为 01
或者 02
。 这样, 每次按下按钮后, Vofa+ 都会向 STM32 发送 AA FF 00 01
01 00 00 00
或者 AA FF 00 01
02 00 00 00
。
可以结合我在 USART 中 DMA 接收部分对数据的解析方式进行理解。
void USARTx_DMA_RX_IRQHandler(void) {
// If data receiving complete, clear the TC5 flag
if (LL_DMA_IsActiveFlag_TC5(DMA1) != RESET) {
LL_DMA_ClearFlag_TC5(DMA1);
// receive frame header: 0xAA 0xFF
Format_Typedef recv_header = recv_frame.data_group[0];
if ((recv_header.chars[0] == 0xAA) && (recv_header.chars[1] == 0xFF)) {
char cmd = recv_header.chars[3];
Format_Typedef recv_data = recv_frame.data_group[1];
switch (cmd) {
case 0x01: // control motor start/stop
if (recv_data.chars[0] == 1) { // start
LED_STATE_TOGGLE();
g_bldc.start_pwm();
} else if (recv_data.chars[0] == 2) { // stop
LED_STATE_TOGGLE();
g_bldc.stop_pwm();
}
break;
case 0x02: // set velocity
g_target_motor_vel = recv_data.fdata;
break;
default:
break;
}
}
// we need to disable dma rx channel and reset the transfer length
usart_rx_dma_reload();
}
}
这里为什么设置的发送帧为 8 个字节? 这是因为目前使用的是 JustFloat 协议, 协议发送和接收都是依据 float 的格式与大小进行的。 而我定义了接收帧为大小为 2 的
Format_Typedef
数组, 正好是 2 个 float, 8 个字节大小。 若是不理解可以看看 mandzy 的相关 视频。
4. 调试记录
一个简单的 USART + DMA 配置, 结果配完 USART1 后发现没有数据发送出来, 也没有数据接收到。
- 将 USART1 更换为 USART2 以及 USART3 都是一样的情况。
- 检查了 DMA 的配置, 发现
recv_frame
和send_frame
的地址忘记写目标地址上了, 但是还是不行。 - 单独测试收的过程, 在指令中加入一个翻转 LED 的命令, 无效, cortex-debug 也接收不到中断。
- 主程序可以跑通, 加入 LED 翻转指令有效, 通过 cortex-debug 插件也没有发现数据有问题。
- 用自己之前写的可以工作的 USART1 的程序进行回环测试, 无效, 基本确定 CH340 硬件坏了。
- 用逻辑分析仪测了一下输出脚 PB6(Tx) 有不同的输出波形, 说明 DMA 通信不成问题了, 协议部分还不清楚, 但 CH340 肯定坏了。
JustFloat
协议测试可用。