第一章:PX4与SPI基础
各位同学好,我是你们的飞控开发工程师。今天咱们来聊聊PX4驱动SPI设备这件事。说实话,我刚开始接触PX4的时候,也被它的架构绕得晕头转向。但别担心,我会用最接地气的方式,带你把这块硬骨头啃下来。
1.1 PX4架构概览
先说说PX4的整体架构。说白了,它就是一个模块化的实时操作系统。我习惯把它想象成一个三层蛋糕:最底层是硬件抽象层,中间是核心调度层,最上面是应用层。
为什么会这样设计?你想想看,如果所有代码都揉在一起,改一个传感器驱动就得重新编译整个固件,那得多痛苦。PX4把驱动、控制算法、通信协议都拆成了独立的模块,每个模块各司其职。
核心模块关系:
- uORB(微对象请求代理):模块间的消息总线,所有数据都通过它传递
- Driver Framework:驱动框架,负责管理硬件外设
- Navigator/Commander:导航与任务管理
- Estimator:状态估计,比如EKF2
我记得第一次看PX4源码时,最让我头疼的就是uORB。但后来发现,它其实就是个发布-订阅模型。传感器驱动发布数据,控制算法订阅数据,就这么简单。
1.2 SPI协议原理
SPI,全称串行外设接口。嗯,名字挺唬人,但说白了就是四根线的事:
| 信号线 | 名称 | 作用 |
|---|---|---|
| SCLK | 时钟线 | 由主机产生,决定通信速率 |
| MOSI | 主机输出/从机输入 | 主机发送数据给从机 |
| MISO | 主机输入/从机输出 | 从机发送数据给主机 |
| CS | 片选线 | 低电平有效,选中对应从机 |
SPI有四种模式,由时钟极性(CPOL)和时钟相位(CPHA)决定。我刚开始做项目时,经常因为模式不匹配导致通信失败。后来学乖了,每次拿到新传感器,第一件事就是看数据手册里的时序图。
我的小技巧:调试SPI通信时,先用逻辑分析仪抓波形。看看时钟是不是对的,数据是不是在正确的边沿采样。这比盯着代码看半天管用多了。
1.3 飞控中的SPI总线拓扑
在PX4飞控上,SPI总线是怎么连的?我以Pixhawk为例,给你画个图:
看到没?SPI1总线上挂的都是高速传感器,IMU、磁力计这些。SPI2总线留给外部设备。为什么要分开?因为IMU数据需要高频率读取,如果和低速设备混在一起,会严重影响实时性。
注意:SPI总线上所有设备共享SCLK、MOSI、MISO三根线。每个设备有独立的CS片选线。我曾经遇到过一个问题:两个设备的CS引脚在PCB上不小心短接了,结果一读A设备,B设备也跟着响应,数据全乱了。
1.4 PX4中的SPI驱动框架
在PX4里写SPI驱动,其实有固定的套路。我总结一下核心步骤:
- 继承基类:从
SPI类派生你的设备驱动 - 实现probe():检测设备是否存在,读取ID寄存器
- 实现init():初始化设备,配置寄存器
- 实现ioctl():处理控制命令
- 注册到uORB:发布传感器数据
给你看个最简单的SPI读操作示例:
// 读取SPI设备寄存器
uint8_t spi_read_reg(uint8_t reg) {
uint8_t tx_buf[2] = {reg | 0x80, 0x00}; // 最高位置1表示读
uint8_t rx_buf[2] = {0};
struct spi_transfer_t transfer = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 2
};
spi_transfer(SPI_DEV, &transfer, 1);
return rx_buf[1]; // 第二个字节是读到的数据
}
这里有个细节:SPI读操作时,第一个字节发的是寄存器地址(最高位置1),同时主机要发一个空字节来产生时钟,从机才会把数据送出来。这个坑我踩过,第一次写驱动时忘了发空字节,读回来的全是0xFF。
关键点总结:
- SPI是全双工通信,收发同时进行
- CS片选必须由软件精确控制,时序要求严格
- PX4中SPI驱动通过
SPIDev类管理总线 - 每个SPI设备在启动时注册,通过uORB发布数据
好了,这一章的基础知识就讲到这里。记住,理解PX4的架构和SPI协议是后面写驱动的基础。下一章我们会动手写一个真实的传感器驱动,到时候这些概念都会用上。
课后建议:找一块Pixhawk飞控板,用万用表量一下SPI总线的引脚,看看CS片选是怎么分配的。动手操作比看书印象深刻多了。