news 2026/10/26 1:09:52

飞控从STM32迁移到HPM6750:非对称双核架构实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
飞控从STM32迁移到HPM6750:非对称双核架构实战

1. 为什么我要把飞控从STM32换到HPM6750

第一次拿到HPM6750这颗片子的时候,我其实没抱太大期望。国产MCU我用过不少,大多数在飞控这种对实时性和浮点性能要求极高的场景里,跑起来总差点意思。但HPM6750的参数确实让我多看了两眼:双核RISC-V架构,一个800MHz的高性能核加一个600MHz的实时核,内置双精度浮点单元和DSP扩展,还有2MB的片上SRAM。这个配置放在飞控领域,说实话是有点超规格的。

我之前的飞控平台是STM32H743,480MHz的Cortex-M7,单核跑PX4的NuttX系统。这个方案很成熟,社区支持也好,但问题在于当你需要同时跑姿态控制、导航滤波、日志记录和MAVLink通信的时候,单核的算力天花板很快就摸到了。尤其是EKF2滤波器和控制循环抢CPU的时候,偶尔会出现控制周期抖动,虽然不至于炸机,但总让人心里不踏实。

HPM6750的非对称双核架构正好能解决这个痛点。所谓非对称,就是两个核的定位完全不同:CPU0是高性能核,主频800MHz,带双精度FPU,适合跑导航滤波、路径规划这类计算密集的任务;CPU1是实时核,600MHz,中断延迟极低,专门负责姿态控制、PWM输出这类对时序敏感的工作。两个核通过共享内存和消息队列通信,各干各的活,互不干扰。

这个方案的核心价值在于:它让飞控系统的任务分配变得非常清晰。你不需要再纠结哪个任务该优先跑,因为硬件本身就帮你做了隔离。姿态控制跑在CPU1上,就算CPU0那边在跑复杂的视觉SLAM算法,控制循环的周期抖动也能控制在微秒级别。这对于做室内导航、视觉避障、集群编队的场景来说,意义非常大。

这篇文章适合谁看?如果你正在做飞控开发,不管是PX4还是ArduPilot,或者你在评估国产MCU能不能扛起飞控这个活,那这篇内容应该能给你一些参考。我会从架构设计、任务分配、通信机制、实操配置这几个角度,把HPM6750跑飞控的完整思路拆开讲。不是那种泛泛而谈的介绍,而是我实际踩过坑之后总结出来的东西。

2. 非对称双核架构到底怎么理解

2.1 两个核的分工逻辑

先把这个“非对称”讲清楚。对称双核,比如两个Cortex-A53,每个核能力一样,操作系统负责调度,任务随便扔给哪个核都行。非对称双核不一样,两个核的指令集可能相同,但微架构、主频、外设访问权限、甚至内存映射都不一样。HPM6750就是典型的非对称设计。

CPU0我管它叫“计算核”。800MHz主频,带双精度浮点,L1缓存也更大。它的强项是吞吐量,适合跑那些一次要算很多数据、但对单次延迟不那么敏感的任务。比如EKF2的状态估计,一次要处理几十个传感器的数据,做矩阵运算,这种活扔给CPU0最合适。还有路径规划、任务调度、日志存储这些,都可以放在CPU0上。

CPU1我管它叫“控制核”。600MHz,虽然主频低一点,但它的中断响应是硬件级别的,延迟可以做到几十纳秒。而且它的定时器资源更丰富,PWM输出可以直接挂在这边。姿态控制循环要求的是确定性,每个周期必须在固定时间点开始、固定时间点结束,抖动越小越好。CPU1就是干这个的。

两个核之间怎么通信?HPM6750提供了几种机制:共享内存、消息队列、以及核间中断。我的方案是用共享内存做数据交换,用核间中断做同步信号。具体来说,CPU0算完姿态估计之后,把结果写到共享内存的固定地址,然后触发一个核间中断通知CPU1去取。CPU1取到数据后执行控制律,算出PWM占空比,直接写寄存器输出。整个过程不需要操作系统参与,纯硬件同步,延迟非常低。

2.2 为什么不用单核超频或者对称双核

有人可能会问:为什么不直接买个更高频的单核?或者用对称双核,让操作系统自己调度?

单核的问题在于,不管你超到多高,任务之间的干扰是避免不了的。PX4的NuttX虽然是实时操作系统,但当一个高优先级任务在跑的时候,低优先级任务只能等着。如果你把姿态控制设成最高优先级,那日志记录、MAVLink通信这些就会被频繁打断,反过来又影响控制循环的稳定性。这是个零和博弈,单核架构下无解。

对称双核的问题在于,操作系统调度器本身有开销。任务在两个核之间迁移的时候,缓存会失效,上下文切换也要时间。对于飞控这种要求确定性的场景,这种不确定性是致命的。你没法保证姿态控制任务每次都在同一个核上跑,也就没法保证每次的执行时间一致。

非对称双核的好处就是确定性。任务和核是绑定的,姿态控制永远在CPU1上跑,导航滤波永远在CPU0上跑。没有迁移,没有调度抖动,每个任务的执行时间都是可预测的。这对于做PID调参和系统辨识来说,简直是福音。你调出来的参数,换到另一块板子上,只要硬件一样,表现就是一样的。

2.3 内存和外设的分配策略

HPM6750有2MB的片上SRAM,这个容量在MCU里算很大的了。我的分配方案是这样的:CPU1独占64KB,专门放姿态控制相关的代码和数据,确保它的访问延迟最低;CPU0用512KB跑NuttX系统和导航任务;剩下的内存做共享缓冲区和DMA描述符。

外设方面,PWM输出、IMU的SPI接口、遥控输入捕获这些挂在CPU1上,保证控制回路的实时性。SD卡、以太网、USB这些挂在CPU0上,因为它们对实时性要求不高,但数据量大。这样分配的好处是,CPU1的外设中断不会因为CPU0那边的大数据量传输而被阻塞。

注意:HPM6750的外设挂载不是随便分的,有些外设只能被特定核访问。在规划阶段一定要先查清楚数据手册里的外设亲和性表,不然代码写完了发现访问不了,返工成本很高。

3. 飞控任务在双核上的具体分配

3.1 CPU1上的实时任务清单

CPU1上我跑了四个任务,全部是硬实时或者接近硬实时的:

第一个是姿态控制循环,跑在1kHz。这个任务从IMU读取原始数据,做温度补偿和零偏校正,然后跑PID或者LQR控制律,最后输出PWM。整个流程必须在1毫秒内完成,抖动不能超过50微秒。这个任务优先级最高,中断触发,不经过操作系统调度。

第二个是传感器数据采集,包括IMU、气压计、磁力计。IMU跑在8kHz,用SPI DMA传输,每次采完一批数据触发一次中断,把数据推到环形缓冲区。气压计和磁力计跑在100Hz左右,用I2C轮询。这些数据采集任务虽然频率不同,但都挂在CPU1上,因为它们的数据最终要喂给姿态控制。

第三个是遥控信号解码,包括SBUS、CRSF这些协议。遥控信号的更新率一般是50Hz到150Hz,延迟要求很高。如果遥控信号丢了,飞控要立刻进入失控保护。这个任务放在CPU1上,用串口中断加DMA接收,解码后直接更新控制目标值。

第四个是电机输出安全监控,这个任务跑在500Hz。它检查PWM输出的占空比是否在合理范围内,如果发现异常(比如某个电机输出饱和了),立刻触发保护逻辑。这个任务不参与控制,但它是最后一道防线。

3.2 CPU0上的计算任务清单

CPU0上跑的是NuttX系统,任务多一些,但实时性要求相对宽松:

EKF2状态估计是CPU0上最重的任务,跑在200Hz。它融合IMU、气压计、磁力计、GPS的数据,输出位置、速度、姿态的估计值。这个任务的计算量很大,尤其是当GPS信号不好的时候,EKF2要做更多的迭代。放在CPU0上,就算偶尔超时也不会直接影响控制回路。

导航和路径规划跑在50Hz。这个任务根据当前估计的位置和目标航点,计算期望的速度和加速度指令。如果是自动模式,还要跑L1制导律或者更复杂的轨迹跟踪算法。这个任务的输出通过共享内存传给CPU1,作为姿态控制的目标值。

MAVLink通信跑在10Hz到50Hz之间。它负责和地面站通信,发送遥测数据,接收指令。这个任务的实时性要求不高,但数据量不小。放在CPU0上,用DMA串口或者以太网传输,不会占用CPU1的资源。

日志记录跑在后台,优先级最低。它把飞行数据写到SD卡或者Flash里,用于事后分析。这个任务最容易被抢占,但没关系,丢几帧日志不影响飞行安全。

3.3 核间通信的数据结构设计

两个核之间的数据交换,我用的是共享内存加环形缓冲区的方式。具体来说,在共享内存区域定义了两个结构体:一个是从CPU0到CPU1的“控制指令”,包含期望姿态、期望角速率、油门值、飞行模式标志;另一个是从CPU1到CPU0的“状态反馈”,包含当前姿态、角速率、电机输出、传感器健康状态。

每个结构体都配了一个序列号和一个校验字段。CPU0写完数据后,把序列号加一,然后触发核间中断。CPU1在中断里读取数据,检查序列号是否连续,校验是否通过。如果发现数据损坏或者序列号跳变,就丢弃这一帧,继续用上一帧的数据。这个机制保证了数据的一致性,不会出现读到一半被改写的情况。

实操心得:共享内存的地址对齐非常重要。HPM6750的核间通信对地址对齐有要求,如果结构体没有按缓存行对齐,可能会出现伪共享问题,导致性能急剧下降。我的做法是把每个结构体都按64字节对齐,并且在结构体内部用填充字段保证关键变量不共享缓存行。

4. 从零搭建HPM6750飞控开发环境

4.1 工具链的选择和安装

HPM6750是RISC-V架构,所以工具链要用RISC-V的GCC。官方推荐的是SiFive的Freedom Studio,但我个人更喜欢用命令行工具链,因为更灵活,也更容易集成到CI里。

我用的工具链是riscv64-unknown-elf-gcc,版本是10.2.0。安装方式很简单,在Ubuntu下直接下载预编译包,解压后把bin目录加到PATH里就行。注意要选unknown-elf而不是unknown-linux-gnu,因为飞控是裸机或者RTOS环境,不需要Linux的系统调用。

wget https://example.com/riscv-toolchain-10.2.0.tar.gz tar -xzf riscv-toolchain-10.2.0.tar.gz -C /opt/ export PATH=/opt/riscv/bin:$PATH riscv64-unknown-elf-gcc --version

调试器我用的是OpenOCD加FT2232调试器。HPM6750支持JTAG调试,OpenOCD的配置文件在官方SDK里有现成的,直接拿来用就行。调试的时候可以同时连两个核,分别下断点,这对于调试核间通信问题非常有用。

4.2 SDK的获取和工程结构

HPM6750的SDK叫HPM_SDK,在GitHub上可以找到。SDK里包含了外设驱动、RTOS适配层、以及一些示例工程。我的做法是先把SDK克隆下来,然后基于hello_world示例创建一个新的工程目录。

工程结构我建议这样组织:

hpm6750_fmu/ ├── apps/ │ ├── cpu0/ │ │ ├── main.c │ │ ├── navigation/ │ │ └── comm/ │ └── cpu1/ │ ├── main.c │ ├── control/ │ └── sensors/ ├── boards/ │ └── hpm6750_fmu/ │ ├── pinmux.c │ └── board.c ├── drivers/ │ ├── imu/ │ ├── baro/ │ └── pwm/ ├── middleware/ │ ├── shared_mem/ │ └── ipc/ └── CMakeLists.txt

两个核的代码分开放在apps/cpu0和apps/cpu1里,共享的驱动和中间件放在drivers和middleware里。编译的时候用两个独立的CMake目标,分别生成两个核的elf文件,最后用工具合并成一个镜像烧录。

4.3 双核启动流程的配置

HPM6750的双核启动流程是这样的:上电后CPU0先启动,执行BootROM里的代码,然后跳转到用户Flash里的启动代码。CPU0在初始化完基本时钟和内存之后,需要把CPU1的代码从Flash拷贝到它的运行地址,然后释放CPU1的复位,让CPU1开始执行。

这个流程在SDK里有现成的API,叫hpm_core1_start()。你只需要把CPU1的入口地址传进去就行。但有几个坑要注意:

第一,CPU1的代码必须放在它能访问的内存区域。HPM6750的CPU1只能访问特定的SRAM块,不能访问CPU0的私有内存。所以在链接脚本里要把CPU1的代码段和数据段分配到正确的地址。

第二,CPU1启动之前,CPU0必须把共享内存区域初始化好,不然CPU1起来之后读到的是随机数据。我的做法是在CPU0的启动代码里先把共享内存清零,然后再启动CPU1。

第三,两个核的时钟配置要协调好。CPU0和CPU1可以跑不同的频率,但它们的总线时钟必须是同源的,不然核间通信会出问题。SDK里的时钟配置工具可以帮你算分频系数,但最好还是手动检查一遍。

// CPU0启动CPU1的代码示例 void start_cpu1(void) { // 初始化共享内存 memset((void*)SHARED_MEM_BASE, 0, SHARED_MEM_SIZE); // 设置CPU1的入口地址 HPM_CORE1->ENTRY = (uint32_t)&cpu1_entry; // 释放CPU1复位 HPM_CORE1->RESET = 0; // 等待CPU1就绪 while (HPM_CORE1->STATUS != CORE1_READY) { // 超时处理 } }

注意:CPU1启动后不要立刻开始跑控制循环,要先等CPU0把传感器校准数据传过来。我的做法是CPU1启动后先进入一个等待循环,直到共享内存里的“校准完成”标志被置位,才开始执行控制任务。

5. 姿态控制回路在CPU1上的实现细节

5.1 传感器数据采集的时序设计

姿态控制的基础是传感器数据。IMU我用的是ICM-42688-P,SPI接口,最高支持8kHz输出。为了和1kHz的控制循环配合,我把IMU配置成8kHz采样,每8个样本触发一次DMA传输完成中断。在中断里,我把这8个样本做平均,得到一个1kHz的等效采样值,然后推入环形缓冲区。

这样做的好处是降低了噪声,同时保证了控制循环每次拿到的都是最新的数据。如果你直接用1kHz采样,IMU的内部滤波器可能会有相位延迟,影响控制效果。用8kHz过采样再平均,相位延迟可以控制在半个控制周期以内。

气压计和磁力计的更新率低,我用的是100Hz的定时器触发读取。读取的时候要注意I2C总线的仲裁问题,因为CPU0那边可能也在用I2C。我的做法是把气压计和磁力计挂在CPU1专用的I2C总线上,物理隔离,避免总线冲突。

5.2 控制律的计算和PWM输出

控制律我用的是串级PID:外环是角度环,内环是角速率环。角度环跑在500Hz,角速率环跑在1kHz。角度环的输出作为角速率环的期望值。这个结构在PX4里也是类似的,但PX4是跑在单核上的,两个环共享CPU时间。在HPM6750上,我把角度环放在CPU0上跑,角速率环放在CPU1上跑,两个环通过共享内存交换数据。

角速率环的计算很简单:读取角速率,和期望值做差,乘以P增益,加上积分项和微分项,输出就是电机混控的输入。整个过程在CPU1上执行,从读取传感器到写出PWM,我实测下来是12微秒左右。1kHz的周期是1000微秒,所以CPU1的负载率只有1.2%,余量非常大。

PWM输出我用的是HPM6750的PWM模块,配置成4路互补输出,带死区插入。死区时间设的是200纳秒,这个值要根据你用的电调来调。四合一电调一般死区要求是100到500纳秒,太小了会直通,太大了会损失效率。我建议先用示波器看一下电调的输入波形,确认死区合适再飞。

// 角速率环的简化实现 void rate_control_loop(void) { // 读取角速率 float gyro_x = imu_get_gyro_x(); float gyro_y = imu_get_gyro_y(); float gyro_z = imu_get_gyro_z(); // 计算误差 float err_x = target_rate_x - gyro_x; float err_y = target_rate_y - gyro_y; float err_z = target_rate_z - gyro_z; // PID计算 float out_x = pid_update(&pid_x, err_x, DT); float out_y = pid_update(&pid_y, err_y, DT); float out_z = pid_update(&pid_z, err_z, DT); // 电机混控 motor_mix(out_x, out_y, out_z, throttle); // 输出PWM pwm_update(); }

5.3 失控保护和故障切换逻辑

飞控最怕的就是失控。我在CPU1上实现了一套独立的失控保护逻辑,不依赖CPU0。具体来说,CPU1会监控几个关键信号:遥控输入是否有效、IMU数据是否更新、CPU0的心跳是否正常。如果任何一个信号丢失超过预设时间,CPU1会立刻切换到保护模式。

保护模式分三级:第一级是悬停,如果遥控信号丢了但IMU正常,就保持当前姿态悬停;第二级是自动降落,如果CPU0心跳丢了但CPU1还能控制,就执行缓慢降落;第三级是紧急停桨,如果IMU数据也丢了,说明姿态已经不可信,直接停桨。

这套逻辑全部跑在CPU1上,代码量不大,但非常关键。我测试的时候故意拔掉遥控接收机,飞机在1秒内进入悬停模式,然后自动降落,整个过程没有失控。

实操心得:失控保护的阈值不要设得太激进。我一开始把遥控信号丢失的判定时间设成100毫秒,结果遥控器偶尔丢一帧就触发保护,飞行体验很差。后来改成500毫秒,既保证了安全,又不会误触发。

6. 核间通信的坑和优化技巧

6.1 共享内存的缓存一致性问题

HPM6750的两个核各有自己的L1缓存,共享内存区域默认是不走缓存的。但如果你不小心把共享内存的地址映射到了缓存区域,就会出现一个核写了数据,另一个核读到的还是旧值的情况。这个问题我调试了整整两天才找到。

解决办法有两个:一是把共享内存区域配置成非缓存(uncached),这样每次读写都直接访问物理内存,速度慢一点但绝对安全;二是用缓存维护指令,写完数据后执行flush,读数据前执行invalidate。我选的是第一种方案,因为核间通信的数据量不大,非缓存访问的开销可以接受。

在链接脚本里,共享内存段要单独定义,并且加上NOLOAD属性,避免启动时被初始化。

MEMORY { SHARED_MEM (rwx) : ORIGIN = 0x40000000, LENGTH = 64K } SECTIONS { .shared_mem (NOLOAD) : { *(.shared_mem) } > SHARED_MEM }

6.2 核间中断的延迟测量

核间中断的延迟直接影响到控制回路的响应速度。我用GPIO翻转加示波器的方法测了一下:CPU0触发中断到CPU1开始执行中断服务程序,延迟大概是1.2微秒。这个延迟主要来自中断控制器的同步和流水线的刷新。

如果你觉得这个延迟还是太大,可以试试用忙等待代替中断。具体来说,CPU1在一个循环里不断检查共享内存的标志位,一旦发现标志位置位就立刻处理。这样延迟可以降到几百纳秒,但代价是CPU1会一直满负荷运行,功耗和发热都会增加。对于飞控来说,1.2微秒的延迟已经足够了,没必要用忙等待。

6.3 数据吞吐量的瓶颈分析

核间通信的吞吐量取决于共享内存的带宽和中断频率。我实测下来,用中断方式,每秒可以传输大约50万次32位数据。这个吞吐量对于飞控来说绰绰有余,因为控制指令的更新率只有1kHz,状态反馈也只有200Hz。

但如果你要在两个核之间传图像或者点云数据,那就要考虑用DMA了。HPM6750支持核间DMA,可以把数据从一个核的内存直接搬到另一个核的内存,不需要CPU参与。不过飞控场景下一般用不到这么高的带宽,所以我的方案里没有用DMA。

7. 常见问题排查和调试技巧

7.1 双核启动失败的排查步骤

双核启动失败是最常见的问题,表现是CPU1完全不运行,或者跑飞了。排查步骤我总结了一个清单:

现象可能原因排查方法
CPU1完全不运行复位没释放检查CORE1->RESET寄存器
CPU1跑飞入口地址错误检查链接脚本和ENTRY寄存器
CPU1运行但无输出时钟没配置检查CPU1的时钟分频寄存器
核间通信失败共享内存地址错误用调试器查看共享内存内容
数据不一致缓存问题检查MPU配置,确认共享内存非缓存

我遇到最多的问题是入口地址错误。HPM6750的CPU1入口地址必须是4字节对齐的,而且不能是Thumb地址(最低位不能是1)。如果你用函数指针直接赋值,编译器可能会生成Thumb地址,导致CPU1跑飞。解决办法是用(uint32_t)&cpu1_entry & ~1来清除最低位。

7.2 控制循环抖动的测量和优化

控制循环的抖动是飞控性能的关键指标。我用的是GPIO翻转法:在控制循环的开始和结束各翻转一次GPIO,用示波器看脉冲宽度的变化。理想情况下,每次循环的脉冲宽度应该完全一样,但实际上会有几十纳秒的抖动。

抖动的来源主要有三个:中断嵌套、缓存未命中、以及总线仲裁。优化方法对应也有三个:把控制循环的中断优先级设成最高,禁止其他中断打断;把控制循环的代码和数据放在紧耦合内存(TCM)里,避免缓存未命中;把CPU1的外设访问安排在控制循环之外,避免总线冲突。

我优化之后,控制循环的抖动从原来的200纳秒降到了30纳秒以内。这个水平对于大多数飞行场景已经足够了。

7.3 PX4生态的兼容性处理

如果你想让HPM6750跑PX4的代码,那需要做一些移植工作。PX4的NuttX系统本身支持多核,但默认配置是单核。你需要修改nuttx-config里的CONFIG_SMP选项,把CPU1加进去。然后修改board_config.h里的内存和外设定义,把CPU1的资源加进去。

MAVLink通信这块,PX4用的是mavlink模块,跑在CPU0上。你需要确保MAVLink的发送和接收不会阻塞CPU1的控制循环。我的做法是把MAVLink的串口DMA缓冲区设大一点,比如4KB,这样即使CPU0在忙,数据也不会丢。

注意:PX4的EKF2默认是单线程的,移植到双核上之后,你需要把EKF2的任务绑定到CPU0上,避免它在两个核之间迁移。绑定任务用pthread_setaffinity_np()就行,NuttX支持这个接口。

8. 实际飞行测试的数据和体会

8.1 悬停测试的振动分析

第一次悬停测试的时候,飞机有明显的低频振动,频率大概在5Hz左右。我一开始以为是PID参数的问题,调了半天没效果。后来用日志分析发现,振动频率和CPU0上EKF2的更新率一致,是200Hz的分频。问题出在共享内存的更新上:CPU0每5毫秒更新一次姿态估计,CPU1每1毫秒读一次,读到的数据有时候是新的,有时候是旧的,导致控制指令有阶梯状的跳变。

解决办法是在CPU1上加一个一阶低通滤波器,把共享内存读到的姿态估计做平滑。滤波器的截止频率设成50Hz,既能滤掉阶梯跳变,又不会引入太大的相位延迟。改完之后振动就消失了。

8.2 高速前飞时的控制延迟评估

高速前飞的时候,控制延迟的影响会被放大。我实测下来,从遥控杆量变化到飞机姿态响应,总延迟大概是15毫秒。这个延迟包括遥控接收延迟(5毫秒)、CPU0导航计算延迟(3毫秒)、核间通信延迟(1.2微秒)、CPU1控制计算延迟(12微秒)、以及电机响应延迟(5毫秒)。

这个延迟水平对于手动飞行来说是可以接受的,但对于竞速或者特技飞行来说还是有点大。如果你要追求极致的响应速度,可以考虑把导航计算也放到CPU1上,但这样CPU1的负载会增加,需要重新评估时序。

8.3 双核方案的功耗和发热表现

HPM6750的双核全开,功耗大概是1.2瓦左右。这个功耗在MCU里算高的,但考虑到它的性能,还是可以接受的。发热方面,不加散热片的情况下,芯片表面温度大概是60度。夏天户外飞行的时候,建议加一个小散热片,不然可能会触发温度保护。

如果你对功耗敏感,可以把CPU0降频到400MHz,CPU1保持600MHz。这样功耗可以降到0.8瓦左右,性能损失不大,因为导航计算本身不需要那么高的主频。

9. 这套方案还能怎么扩展

9.1 加入视觉处理的可能性

HPM6750的CPU0有800MHz主频和双精度FPU,跑一些轻量级的视觉算法是可行的。比如光流计算、AprilTag识别、或者简单的目标跟踪。你可以把摄像头挂在CPU0上,用DMA把图像数据搬到内存,然后在CPU0上跑视觉算法。视觉的输出通过共享内存传给CPU1,作为位置控制的输入。

但要注意,视觉算法的计算量波动很大,有时候一帧要算几十毫秒。如果CPU0被视觉任务占满了,EKF2的更新就会延迟,影响导航精度。我的建议是把视觉任务设成低优先级,并且限制它的最大执行时间,超时就丢弃这一帧。

9.2 集群编队的通信架构

如果你要做集群编队,HPM6750的双核架构也有优势。CPU0可以跑编队控制算法,计算每架飞机的期望位置;CPU1继续跑姿态控制。编队之间的通信可以用CPU0上的以太网或者无线模块,不会干扰CPU1的控制循环。

编队控制的关键是时间同步。我的做法是用GPS的PPS信号做硬件同步,两个核都维护一个本地时间戳,通过共享内存交换。这样编队内的飞机可以做到微秒级的时间对齐,对于密集编队来说非常重要。

9.3 从PX4迁移到自研框架的考量

PX4的代码量很大,移植到HPM6750上需要不少工作。如果你只是想快速验证硬件,可以先跑PX4。但如果你要做产品化,我建议还是自研一个轻量级的框架。自研框架的好处是你可以完全控制任务分配和通信机制,不用受PX4架构的限制。

自研框架的核心模块其实不多:传感器驱动、状态估计、控制律、混控输出、通信。每个模块都可以独立开发和测试。我的做法是先在一个核上跑通所有模块,然后再把实时性要求高的模块拆到CPU1上。这样逐步迁移,风险可控。

实操心得:自研框架的初期不要追求功能大而全,先把姿态控制和手动飞行做稳定。自动模式、返航、指点飞行这些功能可以后面再加。飞控的第一要务是不炸机,其他都是锦上添花。

10. 一些零散但重要的经验

调试双核系统的时候,我强烈建议你买一个支持双通道的调试器。单通道调试器只能同时看一个核的状态,调试核间通信问题的时候非常不方便。双通道调试器可以同时暂停两个核,查看共享内存的一致性,效率高很多。

还有,HPM6750的SDK更新比较频繁,建议你固定用一个版本,不要频繁升级。我有一次升级SDK之后,核间中断的API变了,代码编译不过,折腾了半天才找到原因。后来我就把SDK的版本号写进项目的README里,换电脑的时候直接拉对应版本。

最后说一个关于PID调参的小技巧。在双核架构下,因为控制循环的抖动很小,你可以用更高的微分增益。单核架构下,抖动会放大微分项的噪声,所以微分增益不敢给太大。HPM6750上我试过把微分增益提高30%,控制效果明显更跟手,而且没有出现振荡。这个优势在竞速飞行和特技飞行中非常明显。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 1:09:16

高可靠嵌入式软件设计:军工、汽车、医疗的共同准则

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/26 1:07:04

PLC工程师12年经验汇总:90条自动化入行避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/26 1:06:49

C#实现SPC在线质量监控系统:从休哈特控制图到实战部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/26 1:06:33

24bit音频ADC与STM32 I2S/DMA采集实战:差分输入与高保真录音

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华