Jetson GPIO 控制与传感器数据采集实战

Jetson GPIO 控制与传感器数据采集实战

1. Jetson GPIO 接口概览

Jetson Orin NX 提供丰富的 GPIO 接口,可直接连接传感器和执行器:

复制代码
Orin NX GPIO 资源:
├── GPIO 引脚:40-pin 扩展头
├── I2C 总线:7 路(I2C0-I2C6)
├── SPI 总线:4 路(SPI0-SPI3)
├── UART:3 路(UART0-UART2)
├── PWM:8 路
└── CAN:2 路

40-pin 引脚定义:

复制代码
引脚编号(物理):
     3.3V [1]  [2] 5V
    GPIO02 [3]  [4] 5V
    GPIO03 [5]  [6] GND
    GPIO04 [7]  [8] GPIO14
       GND [9] [10] GPIO15
   GPIO17 [11] [12] GPIO18
   GPIO27 [13] [14] GND
   GPIO22 [15] [16] GPIO23
     3.3V [17] [18] GPIO24
    GPIO10 [19] [20] GND
    GPIO09 [21] [22] GPIO25
    GPIO11 [23] [24] GPIO08
       GND [25] [26] GPIO07
    GPIO00 [27] [28] GPIO01
    GPIO05 [29] [30] GND
    GPIO06 [31] [32] GPIO12
    GPIO13 [33] [34] GND
    GPIO19 [35] [36] GPIO16
    GPIO26 [37] [38] GPIO20
       GND [39] [40] GPIO21

2. GPIO 控制(Python)

2.1 安装 Jetson.GPIO

bash 复制代码
# Jetson.GPIO 已预装在 JetPack 中
# 如需更新
sudo pip3 install Jetson.GPIO

# 设置用户组权限
sudo groupadd -f -r gpio
sudo usermod -aG gpio $USER
sudo cp /opt/nvidia/jetson-gpio/etc/99-gpio.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

# 需要重新登录才能生效

2.2 基础 GPIO 输出

python 复制代码
#!/usr/bin/env python3
"""gpio_output.py - GPIO 输出控制 LED"""
import Jetson.GPIO as GPIO
import time

# 设置模式(BCM 编号)
GPIO.setmode(GPIO.BOARD)  # 或 GPIO.BCM

# 设置引脚(BOARD 编号 = 物理引脚号)
LED_PIN = 7  # 物理引脚 7 = GPIO04

GPIO.setup(LED_PIN, GPIO.OUT)

try:
    while True:
        GPIO.output(LED_PIN, GPIO.HIGH)  # LED 亮
        print("LED ON")
        time.sleep(1)
        
        GPIO.output(LED_PIN, GPIO.LOW)   # LED 灭
        print("LED OFF")
        time.sleep(1)

except KeyboardInterrupt:
    pass

finally:
    GPIO.cleanup()  # 释放所有引脚

2.3 GPIO 输入(按钮检测)

python 复制代码
#!/usr/bin/env python3
"""gpio_input.py - 按钮输入检测"""
import Jetson.GPIO as GPIO
import time

BUTTON_PIN = 11  # 物理引脚 11 = GPIO17
LED_PIN = 7      # 物理引脚 7 = GPIO04

GPIO.setmode(GPIO.BOARD)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # 上拉
GPIO.setup(LED_PIN, GPIO.OUT)

def button_callback(channel):
    """按钮按下回调"""
    print("按钮按下!")
    GPIO.output(LED_PIN, not GPIO.input(LED_PIN))

# 注册中断回调(边沿检测)
GPIO.add_event_detect(
    BUTTON_PIN,
    GPIO.FALLING,
    callback=button_callback,
    bouncetime=300  # 消抖 300ms
)

try:
    print("等待按钮按下...")
    while True:
        time.sleep(0.1)

except KeyboardInterrupt:
    pass

finally:
    GPIO.cleanup()

2.4 PWM 控制(舵机/电机)

python 复制代码
#!/usr/bin/env python3
"""gpio_pwm.py - PWM 控制舵机"""
import Jetson.GPIO as GPIO
import time

SERVO_PIN = 32  # 物理引脚 32 = GPIO12

GPIO.setmode(GPIO.BOARD)
GPIO.setup(SERVO_PIN, GPIO.OUT)

# 创建 PWM 对象(50Hz 舵机标准频率)
pwm = GPIO.PWM(SERVO_PIN, 50)
pwm.start(0)  # 初始占空比 0

def set_angle(angle):
    """设置舵机角度(0-180度)"""
    # 角度 → 占空比映射
    # 0° → 2.5%, 90° → 7.5%, 180° → 12.5%
    duty = 2.5 + (angle / 180.0) * 10.0
    pwm.ChangeDutyCycle(duty)
    time.sleep(0.3)  # 等待舵机到位

try:
    print("舵机控制:输入角度 (0-180),输入 'q' 退出")
    while True:
        user_input = input("角度: ")
        if user_input.lower() == 'q':
            break
        angle = int(user_input)
        if 0 <= angle <= 180:
            set_angle(angle)
            print(f"舵机转到 {angle}°")
        else:
            print("角度范围:0-180")

except KeyboardInterrupt:
    pass

finally:
    pwm.stop()
    GPIO.cleanup()

2.5 GPIO 事件检测(超声波测距)

python 复制代码
#!/usr/bin/env python3
"""ultrasonic.py - HC-SR04 超声波测距"""
import Jetson.GPIO as GPIO
import time

TRIG_PIN = 16  # 物理引脚 16 = GPIO23
ECHO_PIN = 18  # 物理引脚 18 = GPIO24

GPIO.setmode(GPIO.BOARD)
GPIO.setup(TRIG_PIN, GPIO.OUT)
GPIO.setup(ECHO_PIN, GPIO.IN)

def measure_distance():
    """测量距离(cm)"""
    # 发送 10us 触发脉冲
    GPIO.output(TRIG_PIN, GPIO.HIGH)
    time.sleep(0.00001)
    GPIO.output(TRIG_PIN, GPIO.LOW)
    
    # 等待回波信号
    timeout = time.time() + 0.04  # 40ms 超时(约 6m)
    pulse_start = time.time()
    pulse_end = time.time()
    
    while GPIO.input(ECHO_PIN) == 0:
        pulse_start = time.time()
        if pulse_start > timeout:
            return -1
    
    while GPIO.input(ECHO_PIN) == 1:
        pulse_end = time.time()
        if pulse_end > timeout:
            return -1
    
    # 计算距离
    duration = pulse_end - pulse_start
    distance = duration * 34300 / 2  # 声速 343m/s
    
    return round(distance, 2)

try:
    print("超声波测距(HC-SR04)")
    print("按 Ctrl+C 退出\n")
    
    while True:
        dist = measure_distance()
        if dist > 0:
            print(f"距离: {dist} cm")
        else:
            print("测量超时")
        time.sleep(0.5)

except KeyboardInterrupt:
    pass

finally:
    GPIO.cleanup()

3. I2C 传感器

3.1 I2C 总线扫描

bash 复制代码
# 安装 I2C 工具
sudo apt install -y i2c-tools

# 扫描 I2C 总线
i2cdetect -y -r 0  # 总线 0
i2cdetect -y -r 1  # 总线 1
i2cdetect -y -r 8  # 总线 8

# 输出示例:
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- -- 
# 70: -- -- -- -- -- -- -- --                         
# 0x40 = 温湿度传感器, 0x68 = MPU6050 陀螺仪

3.2 温湿度传感器(AHT20)

python 复制代码
#!/usr/bin/env python3
"""aht20.py - AHT20 温湿度传感器"""
import smbus2
import time

class AHT20:
    """AHT20 I2C 温湿度传感器"""
    
    def __init__(self, bus_number=1, address=0x38):
        self.bus = smbus2.SMBus(bus_number)
        self.address = address
        self._initialize()
    
    def _initialize(self):
        """初始化传感器"""
        time.sleep(0.04)  # 上电等待
        # 检查是否需要初始化
        self.bus.write_i2c_block_data(self.address, 0xBE, [0x08, 0x00])
        time.sleep(0.01)
    
    def read(self):
        """读取温湿度"""
        # 发送测量命令
        self.bus.write_i2c_block_data(self.address, 0xAC, [0x33, 0x00])
        time.sleep(0.08)  # 等待测量完成
        
        # 读取 6 字节数据
        data = self.bus.read_i2c_block_data(self.address, 0x00, 6)
        
        # 解析湿度
        humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4
        humidity = humidity * 100.0 / 1048576.0
        
        # 解析温度
        temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
        temperature = temperature * 200.0 / 1048576.0 - 50.0
        
        return {
            "temperature": round(temperature, 2),
            "humidity": round(humidity, 2)
        }

if __name__ == "__main__":
    sensor = AHT20(bus_number=1)
    
    print("AHT20 温湿度传感器")
    print("按 Ctrl+C 退出\n")
    
    while True:
        data = sensor.read()
        print(f"温度: {data['temperature']}°C | 湿度: {data['humidity']}%")
        time.sleep(1)

3.3 MPU6050 六轴陀螺仪

python 复制代码
#!/usr/bin/env python3
"""mpu6050.py - MPU6050 六轴传感器"""
import smbus2
import time
import math

class MPU6050:
    """MPU6050 六轴 IMU"""
    
    # 寄存器地址
    PWR_MGMT_1 = 0x6B
    SMPLRT_DIV = 0x19
    CONFIG = 0x1A
    GYRO_CONFIG = 0x1B
    ACCEL_CONFIG = 0x1C
    ACCEL_XOUT_H = 0x3B
    TEMP_OUT_H = 0x41
    GYRO_XOUT_H = 0x43
    
    def __init__(self, bus_number=1, address=0x68):
        self.bus = smbus2.SMBus(bus_number)
        self.address = address
        self._initialize()
    
    def _initialize(self):
        """初始化"""
        # 唤醒
        self.bus.write_byte_data(self.address, self.PWR_MGMT_1, 0x00)
        time.sleep(0.1)
        
        # 设置采样率 1kHz
        self.bus.write_byte_data(self.address, self.SMPLRT_DIV, 0x07)
        
        # 设置低通滤波 5Hz
        self.bus.write_byte_data(self.address, self.CONFIG, 0x06)
        
        # 设置陀螺仪量程 ±500°/s
        self.bus.write_byte_data(self.address, self.GYRO_CONFIG, 0x08)
        
        # 设置加速度计量程 ±4g
        self.bus.write_byte_data(self.address, self.ACCEL_CONFIG, 0x08)
    
    def _read_raw(self, register):
        """读取 16 位原始数据"""
        high = self.bus.read_byte_data(self.address, register)
        low = self.bus.read_byte_data(self.address, register + 1)
        value = (high << 8) | low
        if value > 32767:
            value -= 65536
        return value
    
    def read_accel(self):
        """读取加速度(g)"""
        x = self._read_raw(self.ACCEL_XOUT_H) / 8192.0  # ±4g
        y = self._read_raw(self.ACCEL_XOUT_H + 2) / 8192.0
        z = self._read_raw(self.ACCEL_XOUT_H + 4) / 8192.0
        return {"x": round(x, 4), "y": round(y, 4), "z": round(z, 4)}
    
    def read_gyro(self):
        """读取角速度(°/s)"""
        x = self._read_raw(self.GYRO_XOUT_H) / 65.5  # ±500°/s
        y = self._read_raw(self.GYRO_XOUT_H + 2) / 65.5
        z = self._read_raw(self.GYRO_XOUT_H + 4) / 65.5
        return {"x": round(x, 4), "y": round(y, 4), "z": round(z, 4)}
    
    def read_temp(self):
        """读取温度(°C)"""
        raw = self._read_raw(self.TEMP_OUT_H)
        temp = raw / 340.0 + 36.53
        return round(temp, 2)
    
    def get_angles(self):
        """计算俯仰角和横滚角"""
        accel = self.read_accel()
        pitch = math.atan2(accel["y"], math.sqrt(accel["x"]**2 + accel["z"]**2)) * 180 / math.pi
        roll = math.atan2(-accel["x"], accel["z"]) * 180 / math.pi
        return {"pitch": round(pitch, 2), "roll": round(roll, 2)}

if __name__ == "__main__":
    imu = MPU6050(bus_number=1)
    
    print("MPU6050 六轴传感器")
    print("按 Ctrl+C 退出\n")
    
    while True:
        accel = imu.read_accel()
        gyro = imu.read_gyro()
        temp = imu.read_temp()
        angles = imu.get_angles()
        
        print(f"加速度(g): X={accel['x']}, Y={accel['y']}, Z={accel['z']}")
        print(f"角速度(°/s): X={gyro['x']}, Y={gyro['y']}, Z={gyro['z']}")
        print(f"角度: 俯仰={angles['pitch']}°, 横滚={angles['roll']}°")
        print(f"温度: {temp}°C")
        print("-" * 50)
        time.sleep(0.1)

3.4 OLED 显示屏(SSD1306)

python 复制代码
#!/usr/bin/env python3
"""oled_display.py - SSD1306 OLED 显示"""
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from luma.core.render import canvas
from PIL import ImageFont, ImageDraw
import time
import psutil
import subprocess

class OLEDDisplay:
    """SSD1306 OLED 显示驱动"""
    
    def __init__(self, bus_number=1, address=0x3C):
        serial = i2c(port=bus_number, address=address)
        self.device = ssd1306(serial, width=128, height=64)
        self.font = ImageFont.load_default()
    
    def show_text(self, lines):
        """显示多行文本"""
        with canvas(self.device) as draw:
            for i, line in enumerate(lines):
                draw.text((0, i * 12), line, font=self.font, fill="white")
    
    def show_system_info(self):
        """显示系统信息"""
        cpu = psutil.cpu_percent(interval=0.1)
        mem = psutil.virtual_memory()
        
        # 获取温度
        try:
            with open("/sys/devices/virtual/thermal/thermal_zone0/temp") as f:
                temp = float(f.read().strip()) / 1000
        except:
            temp = 0
        
        # 获取 IP
        try:
            ip = subprocess.check_output(["hostname", "-I"]).decode().strip().split()[0]
        except:
            ip = "N/A"
        
        self.show_text([
            f"CPU: {cpu}%",
            f"MEM: {mem.percent}%",
            f"TEMP: {temp}°C",
            f"IP: {ip}"
        ])

if __name__ == "__main__":
    display = OLEDDisplay(bus_number=1)
    
    print("OLED 系统监控显示")
    while True:
        display.show_system_info()
        time.sleep(1)

4. SPI 传感器

4.1 SPI 通信基础

python 复制代码
#!/usr/bin/env python3
"""spi_basic.py - SPI 通信示例"""
import spidev
import time

class SPIController:
    """SPI 控制器"""
    
    def __init__(self, bus=0, device=0, speed=1000000):
        self.spi = spidev.SpiDev()
        self.spi.open(bus, device)
        self.spi.max_speed_hz = speed
        self.spi.mode = 0b00  # SPI Mode 0
    
    def transfer(self, data):
        """SPI 数据传输"""
        return self.spi.xfer2(data)
    
    def read_adc(self, channel=0):
        """读取 MCP3008 ADC(8 通道 10 位)"""
        # MCP3008 协议
        cmd = [0x01, (0x08 | channel) << 4, 0x00]
        result = self.transfer(cmd)
        value = ((result[1] & 0x03) << 8) | result[2]
        return value
    
    def close(self):
        self.spi.close()

if __name__ == "__main__":
    spi = SPIController(bus=0, device=0)
    
    print("MCP3008 ADC 读取")
    while True:
        for ch in range(8):
            value = spi.read_adc(ch)
            voltage = value * 3.3 / 1024
            print(f"CH{ch}: {value} ({voltage:.2f}V)  ", end="")
        print()
        time.sleep(0.5)
    
    spi.close()

5. UART 串口通信

python 复制代码
#!/usr/bin/env python3
"""uart_serial.py - UART 串口通信"""
import serial
import time

class UARTController:
    """UART 串口控制器"""
    
    def __init__(self, port="/dev/ttyTHS1", baudrate=115200):
        self.ser = serial.Serial(
            port=port,
            baudrate=baudrate,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=1
        )
    
    def send(self, data):
        """发送数据"""
        if isinstance(data, str):
            data = data.encode()
        self.ser.write(data)
    
    def receive(self, size=1024):
        """接收数据"""
        return self.ser.read(size)
    
    def readline(self):
        """读取一行"""
        return self.ser.readline().decode().strip()
    
    def close(self):
        self.ser.close()

# GPS 模块示例(NMEA 解析)
class GPSReader:
    """GPS NMEA 解析"""
    
    def __init__(self, port="/dev/ttyTHS1", baudrate=9600):
        self.uart = UARTController(port, baudrate)
    
    def parse_gpgga(self, sentence):
        """解析 GPGGA 语句"""
        parts = sentence.split(',')
        if len(parts) < 15:
            return None
        
        return {
            "time": parts[1],
            "latitude": self._to_decimal(parts[2], parts[3]),
            "longitude": self._to_decimal(parts[4], parts[5]),
            "fix": int(parts[6]),
            "satellites": int(parts[7]),
            "altitude": float(parts[9]) if parts[9] else 0
        }
    
    def _to_decimal(self, value, direction):
        """NMEA 坐标转十进制"""
        if not value:
            return 0.0
        degrees = int(value[:2]) if len(value) > 7 else int(value[:3])
        minutes = float(value[2:]) if len(value) > 7 else float(value[3:])
        decimal = degrees + minutes / 60
        if direction in ('S', 'W'):
            decimal = -decimal
        return round(decimal, 6)
    
    def read(self):
        """读取 GPS 数据"""
        while True:
            line = self.uart.readline()
            if line.startswith('$GPGGA') or line.startswith('$GNGGA'):
                return self.parse_gpgga(line)

if __name__ == "__main__":
    gps = GPSReader(port="/dev/ttyTHS1", baudrate=9600)
    
    print("GPS 定位")
    while True:
        data = gps.read()
        if data:
            print(f"纬度: {data['latitude']}, 经度: {data['longitude']}")
            print(f"卫星: {data['satellites']}, 海拔: {data['altitude']}m")
        time.sleep(1)

6. 多传感器融合系统

python 复制代码
#!/usr/bin/env python3
"""sensor_fusion.py - 多传感器数据采集与融合"""
import threading
import time
import json
from datetime import datetime
from collections import deque

class SensorManager:
    """传感器管理器"""
    
    def __init__(self):
        self.sensors = {}
        self.data = {}
        self.locks = {}
    
    def add_sensor(self, name, sensor_class, **kwargs):
        """添加传感器"""
        try:
            sensor = sensor_class(**kwargs)
            self.sensors[name] = sensor
            self.data[name] = deque(maxlen=100)
            self.locks[name] = threading.Lock()
            print(f"✅ 传感器 '{name}' 已添加")
        except Exception as e:
            print(f"❌ 传感器 '{name}' 初始化失败: {e}")
    
    def _read_loop(self, name, interval=1.0):
        """持续读取循环"""
        while True:
            try:
                value = self.sensors[name].read()
                with self.locks[name]:
                    self.data[name].append({
                        "timestamp": datetime.now().isoformat(),
                        "value": value
                    })
            except Exception as e:
                print(f"传感器 '{name}' 读取错误: {e}")
            time.sleep(interval)
    
    def start(self):
        """启动所有传感器采集"""
        self.threads = []
        for name in self.sensors:
            t = threading.Thread(
                target=self._read_loop,
                args=(name,),
                daemon=True
            )
            t.start()
            self.threads.append(t)
            print(f"🚀 传感器 '{name}' 采集已启动")
    
    def get_latest(self, name):
        """获取最新数据"""
        with self.locks[name]:
            if self.data[name]:
                return self.data[name][-1]
        return None
    
    def get_all_latest(self):
        """获取所有传感器最新数据"""
        result = {}
        for name in self.sensors:
            latest = self.get_latest(name)
            if latest:
                result[name] = latest
        return result
    
    def export_json(self, filepath):
        """导出数据为 JSON"""
        export = {}
        for name in self.sensors:
            with self.locks[name]:
                export[name] = list(self.data[name])
        
        with open(filepath, 'w') as f:
            json.dump(export, f, indent=2)
        print(f"数据已导出: {filepath}")

# 使用示例
if __name__ == "__main__":
    manager = SensorManager()
    
    # 添加各种传感器
    manager.add_sensor("temp_humidity", AHT20, bus_number=1, address=0x38)
    manager.add_sensor("imu", MPU6050, bus_number=1, address=0x68)
    manager.add_sensor("distance", UltrasonicSensor, trig_pin=16, echo_pin=18)
    
    manager.start()
    
    try:
        while True:
            all_data = manager.get_all_latest()
            for name, data in all_data.items():
                print(f"{name}: {data['value']}")
            print("-" * 50)
            time.sleep(2)
    except KeyboardInterrupt:
        manager.export_json("sensor_data.json")

总结

接口 典型传感器 Python 库 通信速率
GPIO LED、按钮、超声波 Jetson.GPIO -
I2C AHT20、MPU6050、OLED smbus2 100-400kHz
SPI MCP3008、MAX6675 spidev 1-10MHz
UART GPS、蓝牙、STM32 pyserial 9600-115200
PWM 舵机、电机、蜂鸣器 Jetson.GPIO 50-1000Hz

核心要点:

  1. GPIO 权限 :加入 gpio 用户组,避免 sudo
  2. I2C 扫描 :先用 i2cdetect 确认设备地址
  3. 多传感器并行:每个传感器独立采集线程
  4. 数据持久化:JSON 导出 + SQLite 存储