第二讲:硬件接口基础——GPIO、I2C、SPI、UART在PX4中的抽象层与硬件探测
各位同学,欢迎来到第二讲。
上一讲我们聊了PX4的整体架构,今天咱们把镜头拉近,看看那些最底层的硬件接口——GPIO、I2C、SPI、UART。说白了,这些就是飞控和外部传感器、执行器对话的“嘴巴”和“耳朵”。
我个人习惯把PX4的硬件抽象层比作一个“翻译官”。它把芯片厂商那些千奇百怪的寄存器操作,翻译成统一的、好用的API。你想想看,没有这层抽象,换个MCU就得重写所有驱动,那得多崩溃?
2.1 PX4的硬件抽象层:从寄存器到API
在PX4里,硬件抽象层(HAL)的核心思想就是“隔离变化”。
它把底层的硬件细节封装起来,向上层驱动提供一套标准接口。比如你要读一个GPIO引脚的电平,不管底层是STM32还是NuttX,你调用的函数都是一样的。
核心抽象层组件:
- board_gpio.h / board_i2c.h / board_spi.h / board_serial.h:这些头文件定义了板级硬件接口的宏和函数原型。
- px4_arch.h:架构相关的抽象,比如中断管理、时钟配置。
- px4_work_queue.h:工作队列,用于处理中断下半部和延迟任务。
我记得刚开始接触PX4时,看到一堆board_开头的函数,心里直犯嘀咕:“这跟直接操作寄存器有啥区别?”后来踩了坑才明白,这些函数背后做了很多脏活累活——比如时钟使能、引脚复用配置、中断优先级设置。你只管调用,剩下的交给抽象层。
咱们来看一个GPIO操作的例子:
// 在PX4中操作一个GPIO输出引脚
#include <board_config.h>
// 初始化引脚为推挽输出
px4_arch_configgpio(GPIO_LED_RED);
// 设置高电平
px4_arch_gpiowrite(GPIO_LED_RED, true);
// 读取电平
bool level = px4_arch_gpioread(GPIO_LED_RED);
你看,代码里没有出现任何GPIOx->BSRR或者GPIOx->ODR。这就是抽象层的好处——换芯片时,只需要改board_config.h里的引脚定义,驱动代码一行都不用动。
2.2 四大接口的抽象实现
接下来咱们逐个拆解。我会结合我在实际项目中遇到的坑,给你讲讲每个接口的要点。
2.2.1 GPIO:最基础的“开关”
GPIO在PX4里主要用来控制LED、蜂鸣器、检测按键、或者做简单的片选信号。它的抽象层在platforms/common/include/px4_platform_common/board_gpio.h里。
常用的API有:
px4_arch_configgpio(pin, mode):配置引脚模式(输入、输出、复用等)。px4_arch_gpiowrite(pin, value):写电平。px4_arch_gpioread(pin):读电平。px4_arch_gpiosettech(pin, value):设置引脚的上拉/下拉。
避坑指南:
我曾经在调试一个外部传感器时,发现它的片选引脚怎么都拉不高。查了半天,原来是GPIO的复用功能没配置对——那个引脚默认被用作I2C的SCL了。所以,用GPIO前一定要确认引脚没有被其他外设占用。
2.2.2 I2C:双线制“慢速”总线
I2C在PX4里常用于连接IMU、气压计、磁力计等传感器。它的抽象层在board_i2c.h里,核心结构体是i2c_master_s。
I2C的通信流程一般是:
- 获取I2C总线实例:
px4_i2c_bus_get_device(bus_number, address, speed) - 发送数据:
i2c_master_send(device, msg, len) - 接收数据:
i2c_master_receive(device, msg, len) - 释放总线:
px4_i2c_bus_put_device(device)
这里有个细节:PX4的I2C驱动默认使用中断方式,而不是轮询。这样做的好处是CPU不用傻等,但坏处是——如果中断响应不及时,可能会丢数据。我在做高速IMU采集时就遇到过这个问题,后来把I2C时钟频率从400kHz降到100kHz才稳定下来。
注意:I2C总线上挂多个设备时,地址不能冲突。PX4启动时会扫描总线上的设备,如果发现两个设备地址相同,会报错并跳过其中一个。
2.2.3 SPI:四线制“高速”总线
SPI是PX4里用得最多的接口,没有之一。几乎所有高速传感器(比如BMI088、ICM-20602)都走SPI。它的抽象层在board_spi.h里。
SPI的API比I2C更简洁:
px4_spi_bus_initialize(bus_number, config):初始化SPI总线。px4_spi_transfer(device, tx_buffer, rx_buffer, len):全双工传输。px4_spi_select(device, select):手动控制片选。
我个人习惯在调试SPI设备时,先用逻辑分析仪抓波形。你想想看,SPI的时序问题太多了——时钟极性(CPOL)、时钟相位(CPHA)、数据采样边沿,任何一个配错,数据都是乱的。
我曾经帮一个团队调试飞控,他们的磁力计读数总是跳变。抓了波形才发现,SPI的时钟频率设得太高(20MHz),而磁力计的数据手册上写的是最大10MHz。降频后问题立刻解决。
2.2.4 UART:最通用的“串口”
UART在PX4里主要用于GPS、数传、遥控器接收机等设备。它的抽象层在board_serial.h里,核心是文件操作——因为PX4把UART设备映射成了字符设备文件。
常用的操作:
open("/dev/ttyS1", O_RDWR):打开串口设备。ioctl(fd, TIOCSETA, &termios):配置波特率、数据位、停止位等。read(fd, buf, len)/write(fd, buf, len):读写数据。close(fd):关闭串口。
关键点:UART的波特率必须与对端设备匹配。PX4默认的GPS波特率是38400,但很多GPS模块出厂设置是9600。如果你发现GPS没数据,先检查波特率对不对。
2.3 通过Shell命令进行硬件探测
好了,理论讲完了。咱们来点实操——怎么用PX4的Shell命令来探测硬件?
PX4的nsh(NuttShell)提供了几个非常有用的命令。我每次拿到一块新板子,第一件事就是跑一遍这些命令,确认硬件没问题。
2.3.1 查看系统设备列表
nsh> ls /dev
这个命令会列出所有注册的设备节点。你会看到类似这样的输出:
/dev:
console
ttyS0
ttyS1
ttyS2
spi0
spi1
i2c0
i2c1
gpio0
gpio1
...
如果某个设备没出现,说明驱动没加载或者硬件没初始化成功。
2.3.2 探测I2C总线上的设备
nsh> i2c detect -b 0
这个命令会扫描I2C总线0上的所有地址,并打印出有响应的设备地址。输出示例:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- 28 -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- 76 -- -- -- -- -- -- --
这里0x28和0x76就是检测到的设备地址。你可以对照原理图,看看是不是你期望的传感器。
小技巧:如果I2C设备没被检测到,别急着怀疑硬件坏了。先检查上拉电阻有没有焊,地址线有没有接对。我遇到过好几次,都是因为地址引脚悬空导致地址偏移了一位。
2.3.3 测试SPI设备
SPI没有像I2C那样的“扫描”命令,但你可以用spi_test工具来测试:
nsh> spi_test -b 0 -d 1 -r 0x00 -l 4
参数说明:
-b 0:SPI总线编号-d 1:设备片选编号-r 0x00:读取的寄存器地址-l 4:读取的字节数
如果返回的数据全是0xFF或者0x00,大概率是硬件连接有问题——要么片选没拉对,要么时钟极性配反了。
2.3.4 检查GPIO状态
nsh> gpio info
这个命令会列出所有GPIO引脚的当前配置和电平状态。输出示例:
GPIO_PIN DIR VALUE CONFIG
PA0 IN 0 FLOAT
PA1 OUT 1 PUSHPULL
PA2 IN 1 PULLUP
...
你可以用这个命令快速确认某个引脚的电平对不对。比如,你想检查LED引脚是不是被拉高了,直接看对应引脚的VALUE列就行。
2.3.5 测试UART回环
nsh> echo "hello" > /dev/ttyS1
nsh> cat /dev/ttyS1
如果你把UART的TX和RX短接,执行这两条命令后,应该能看到“hello”被打印出来。如果没反应,检查波特率、接线、还有驱动有没有加载。
2.4 知识体系总览
为了让你更直观地理解这一讲的内容,我画了一张图:
这张图把整个知识体系串起来了。从上到下依次是:驱动层调用HAL接口,HAL封装四大硬件接口,然后通过Shell命令可以反向探测硬件状态。你调试时,可以从下往上排查——先用Shell确认硬件通不通,再检查驱动有没有正确调用HAL。
2.5 总结与个人心得
这一讲的内容其实就两件事:
- 理解抽象层——知道PX4怎么把复杂的硬件操作封装成简单API。
- 学会硬件探测——用Shell命令快速定位硬件问题。
我个人觉得,硬件接口调试最忌讳的就是“猜”。别上来就改代码,先跑一遍Shell命令,看看硬件到底在不在、通不通。我曾经花了一整天调试一个I2C设备,最后发现是总线上的上拉电阻虚焊了。要是早点跑i2c detect,十分钟就能定位问题。
嗯,这一讲就到这里。记住,硬件接口是飞控的“地基”,地基不稳,上层代码写得再漂亮也没用。
公众号:蓝海资料掘金营,微信deep3321