15、GPIO与外设控制:Jetson GPIO库安装、PWM输出配置、I2C/SPI总线调试、传感器数据读取
各位同学,欢迎来到第15讲。今天我们要聊的,是嵌入式开发中最有“手感”的部分——让Jetson跟外面的世界打交道。说白了,就是控制GPIO引脚、输出PWM波、用I2C和SPI总线去读传感器数据。
我刚开始接触Jetson的时候,觉得这东西不就是个带GPU的Linux板子嘛。但真到接外设的时候,才发现坑不少。比如GPIO的权限问题、PWM的时钟配置、I2C设备地址冲突……嗯,今天咱们一个一个捋清楚。
本章核心目标:在Jetson平台上,从零搭建GPIO控制环境,实现PWM输出,掌握I2C/SPI总线调试方法,最终成功读取一个真实传感器的数据。
15.1 Jetson GPIO 库安装
在Jetson上控制GPIO,最常用的库是 Jetson.GPIO。它跟树莓派的 RPi.GPIO 接口很像,但底层实现完全不同。我个人习惯用Python来操作,调试起来快。
安装其实很简单,但有个坑——必须用sudo。我曾经在普通用户下装了半天,结果import时报权限错误,折腾了半小时才发现是用户组没加对。
# 安装 Jetson.GPIO
sudo pip3 install Jetson.GPIO
# 添加用户到 gpio 组(重要!)
sudo usermod -aG gpio $USER
# 重启或重新登录使组生效
sudo reboot
注意:安装完成后,一定要把当前用户加到 gpio 组。否则运行脚本时会报 Permission denied。我刚开始就吃过这个亏,还以为库没装好。
验证安装是否成功,可以跑一个简单的LED闪烁程序:
import Jetson.GPIO as GPIO
import time
# 设置引脚编号模式
GPIO.setmode(GPIO.BOARD)
# 定义引脚(以物理引脚7为例)
led_pin = 7
GPIO.setup(led_pin, GPIO.OUT, initial=GPIO.LOW)
try:
while True:
GPIO.output(led_pin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(led_pin, GPIO.LOW)
time.sleep(0.5)
except KeyboardInterrupt:
GPIO.cleanup()
嗯,这里要注意:GPIO.setmode(GPIO.BOARD) 用的是物理引脚编号,而 GPIO.setmode(GPIO.TEGRA_SOC) 用的是芯片内部命名。我建议新手先用BOARD模式,不容易搞混。
15.2 PWM 输出配置
PWM(脉冲宽度调制)在电机调速、LED调光、舵机控制中非常常见。Jetson的PWM跟树莓派不太一样——它用的是硬件PWM,精度更高,但引脚是固定的。
以Jetson Nano为例,硬件PWM引脚是物理引脚32和33。我做过一个项目,用PWM控制一个舵机做云台,效果还不错。
import Jetson.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)
# 设置PWM引脚(物理引脚32)
pwm_pin = 32
GPIO.setup(pwm_pin, GPIO.OUT)
# 创建PWM对象,频率50Hz(适合舵机)
pwm = GPIO.PWM(pwm_pin, 50)
pwm.start(0) # 初始占空比0%
try:
while True:
# 从0%到100%渐变
for duty in range(0, 101, 5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
for duty in range(100, -1, -5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
except KeyboardInterrupt:
pwm.stop()
GPIO.cleanup()
小技巧:如果你发现PWM输出不稳定,可以检查一下Jetson的电源模式。在最大性能模式下,PWM时钟会更准确。运行 sudo nvpmodel -m 0 切换到最高性能模式。
为什么会这样?因为Jetson的PWM时钟源跟CPU频率有关。CPU降频时,PWM频率也会漂移。我遇到过舵机抖动的情况,最后发现是电源模式没设对。
15.3 I2C 总线调试
I2C是嵌入式系统中最常用的总线之一。Jetson默认有两条I2C总线,通常I2C-1接摄像头,I2C-0接扩展引脚。
调试I2C的第一步,是扫描总线上的设备。用 i2cdetect 工具:
# 安装 i2c-tools
sudo apt-get install i2c-tools
# 扫描 I2C-1 总线(通常是物理引脚3和5)
sudo i2cdetect -y -r 1
输出会显示一个地址表,有设备的地方会显示地址号。比如常见的温度传感器LM75地址是0x48,加速度计MPU6050地址是0x68。
我曾经调试一个气压传感器,怎么都读不到数据。用i2cdetect一查,发现设备地址是0x76,而我代码里写的是0x77。嗯,这种低级错误,谁还没犯过呢?
读取I2C传感器数据的Python示例:
import smbus
import time
# 创建I2C总线对象(总线1)
bus = smbus.SMBus(1)
# 假设传感器地址为0x48,寄存器0x00存温度值
address = 0x48
reg_temp = 0x00
try:
while True:
# 读取2字节数据
data = bus.read_i2c_block_data(address, reg_temp, 2)
# 转换为温度(具体转换公式看传感器手册)
temp = (data[0] << 8 | data[1]) >> 4
temp = temp * 0.0625
print(f"温度: {temp:.2f} °C")
time.sleep(1)
except KeyboardInterrupt:
bus.close()
注意:I2C设备地址通常是7位,但有些库要求左移一位后的8位地址。如果读不到数据,试试把地址左移一位,比如0x48变成0x90。我在这上面浪费过一下午。
15.4 SPI 总线调试
SPI比I2C快,适合高速数据传输,比如显示屏、ADC、SD卡等。Jetson的SPI引脚是物理引脚19、21、23、24。
启用SPI需要先配置设备树。在Jetson上,可以通过 /boot/extlinux/extlinux.conf 或者 config.txt 来启用。不过更简单的方法是用 jetson-io.py 工具:
# 运行 Jetson-IO 配置工具
sudo /opt/nvidia/jetson-io/jetson-io.py
在图形界面里选择“Configure Jetson 40pin Header”,然后启用SPI。重启后,就能在 /dev 下看到 spidev0.0 或 spidev0.1 了。
测试SPI通信,可以用 spi-test 工具,或者直接写Python代码:
import spidev
import time
# 创建SPI对象
spi = spidev.SpiDev()
spi.open(0, 0) # 总线0,片选0
# 设置SPI参数
spi.max_speed_hz = 1000000 # 1MHz
spi.mode = 0b00 # 模式0
# 发送数据并接收
try:
# 发送0xAA,读取返回数据
resp = spi.xfer2([0xAA])
print(f"SPI响应: {hex(resp[0])}")
except Exception as e:
print(f"SPI通信失败: {e}")
finally:
spi.close()
调试建议:如果SPI通信不稳定,先用示波器看时钟线和数据线的波形。没有示波器的话,可以降低SPI频率试试。我遇到过因为线太长导致信号反射的问题,降到500kHz就正常了。
15.5 传感器数据读取实战
理论讲完了,咱们来个实战。我选一个最常见的传感器——DHT22温湿度传感器。它虽然用单总线协议,但我们可以用GPIO模拟时序来读取。
DHT22的时序要求比较严格,Jetson的GPIO速度足够,但要注意实时性。我建议用 time.perf_counter() 来计时,精度更高。
import Jetson.GPIO as GPIO
import time
# DHT22 数据引脚(物理引脚7)
DHT_PIN = 7
GPIO.setmode(GPIO.BOARD)
def read_dht22():
# 发送起始信号
GPIO.setup(DHT_PIN, GPIO.OUT)
GPIO.output(DHT_PIN, GPIO.LOW)
time.sleep(0.02) # 至少18ms
GPIO.output(DHT_PIN, GPIO.HIGH)
time.sleep(0.00004) # 20-40us
# 切换为输入,等待响应
GPIO.setup(DHT_PIN, GPIO.IN)
# 等待DHT22拉低(响应信号)
while GPIO.input(DHT_PIN) == GPIO.HIGH:
pass
# 读取40位数据
data = []
for i in range(40):
while GPIO.input(DHT_PIN) == GPIO.LOW:
pass
start = time.perf_counter()
while GPIO.input(DHT_PIN) == GPIO.HIGH:
pass
end = time.perf_counter()
# 高电平持续时间决定0或1
if (end - start) > 0.00007:
data.append(1)
else:
data.append(0)
# 解析数据
humidity = (data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]) / 10.0
temperature = (data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]) / 10.0
checksum = data[8] << 4 | data[9]
# 校验(简单校验)
if (humidity + temperature) % 256 == checksum:
return temperature, humidity
else:
return None, None
try:
temp, hum = read_dht22()
if temp is not None:
print(f"温度: {temp:.1f}°C, 湿度: {hum:.1f}%")
else:
print("数据校验失败,请重试")
except Exception as e:
print(f"读取错误: {e}")
finally:
GPIO.cleanup()
关键点总结:
- GPIO库安装后必须加用户组,否则权限不足
- PWM输出注意电源模式,高性能模式下更稳定
- I2C调试先用i2cdetect扫描地址,确认设备在线
- SPI通信不稳定时,降低频率或检查接线
- 传感器读取注意时序,用高精度计时函数
好了,这一讲的内容就到这。GPIO控制是嵌入式开发的基本功,多练几次就熟了。下次遇到外设不工作,先检查接线,再查权限,最后看时序——八成能解决问题。
公众号:蓝海资料掘金营,微信deep3321