Python控制树莓派硬件

Python控制树莓派硬件的方式比较多,也比较杂乱,这里会混合使用其中一部分。

平台: raspbian-bookworm-arm64。

1. 安装工具

$ sudo apt install python3-rpi.gpio python3-gpiozero python3-smbus python3-spidev python3-serial python3-picamera2 python3-libgpiod

使用之前,先打开相应外设,如i2c:

$ sudo raspi-config

打开i2c:

3 Interface Options
    --> I5 I2C
        --> Yes

打开即可,启用有些接口无需重启(如i2c,spi),有些需要重启(1-wire),如果需要重启一般也会有提示,如果没有提示,需注意自行甄别。

2. Blink

接线: LED --> GPIO 3.

控制GPIO 3闪烁LED:

from time import sleep
from gpiozero import LED

# GPIO3 引脚.
led = LED(3)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

3. Button

接线: Button --> GPIO 17, LED --> GPIO 18.

按键控制LED:

from gpiozero import LED, Button
from signal import pause

def toggle_led():
    led.value = not led.value
    print('button pressed.')

led = LED(18)                       # GPIO18 控制 LED.
button = Button(17, pull_up=True)   # GPIO17 接按钮.

button.when_pressed = toggle_led

# 保持程序运行.
pause()

4. Interrupt

接线: Button --> GPIO 17.

按键触发中断,打印字符串:

from gpiozero import Button
from signal import pause


def rising_edge():
    print('Detected rising.')

button = Button(17)
button.when_activated = rising_edge

pause()

5. PWM

接线: LED --> GPIO 12.

呼吸灯:

from time import sleep
from gpiozero import PWMLED

# 使用 GPIO12 引脚.
led = PWMLED(12)

# 渐亮渐灭效果.
while True:
    for i in range(0, 101):
        led.value = i / 100     # 设置亮度(0.0 到 1.0).
        sleep(0.01)
    for i in range(100, -1, -1):
        led.value = i / 100
        sleep(0.01)

接线: PWM --> GPIO 12.

控制风扇转速:

from time import sleep
from gpiozero import PWMOutputDevice

# GPIO12 支持硬件PWM.
fan = PWMOutputDevice(12)

# 模拟风扇转速从低到高.
for speed in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]:
    fan.value = speed
    print(f'Fan speed: {speed * 100:.0f}%')
    sleep(2)

6. I2C

接线: SDA --> GPIO 2, SCL --> GPIO 3.

需在'raspi-config'中启用i2c。

读写AT24C02:

import time
from smbus2 import SMBus, i2c_msg

class AT24C02:
    def __init__(self, address=0x50, bus=1):
        self.bus = SMBus(bus)
        self.address = address

    def write_byte(self, mem_addr, data):
        self.bus.write_byte_data(self.address, mem_addr, data)
        time.sleep(0.01)

    def read_byte(self, mem_addr):
        return self.bus.read_byte_data(self.address, mem_addr)

eeprom = AT24C02()
eeprom.write_byte(0x00, 0xAB)
print(f'Address 0x00 value: {hex(eeprom.read_byte(0x00))}')

7. SPI

接线: MOSI --> GPIO 10, MISO --> GPIO 9, CLK --> GPIO11, CS --> GPIO 8. 

需在'raspi-config'中启用spi。

读写W25Q64 Flash(8 MiB):

import spidev
import time

class W25Q64:
    def __init__(self, spi_bus=0, spi_device=0, max_speed_hz=1000000):
        self.spi = spidev.SpiDev()
        self.spi.open(spi_bus, spi_device)
        self.spi.max_speed_hz = max_speed_hz
        self.spi.mode = 0b00  # SPI mode 0.

    def read_id(self):
        """ 读取设备ID """
        cmd = [0x9F]  # JEDEC ID命令
        id_data = self.spi.xfer2(cmd + [0x00, 0x00, 0x00])
        return (id_data[1] << 16) | (id_data[2] << 8) | id_data[3]

    def write_enable(self):
        """ 写使能 """
        cmd = [0x06]  # WREN
        self.spi.xfer2(cmd)

    def wait_busy(self):
        """ 等待设备就绪 """
        while True:
            cmd = [0x05, 0x00]  # 读状态寄存器1.
            status = self.spi.xfer2(cmd)[1]
            if not (status & 0x01):  # 检查BUSY位.
                break
            time.sleep(0.01)

    def sector_erase(self, addr):
        """ 擦除4KB扇区 """
        self.write_enable()
        cmd = [0x20, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]
        self.spi.xfer2(cmd)
        self.wait_busy()

    def page_program(self, addr, data):
        """ 写入一页数据 (最多256字节) """
        self.write_enable()
        cmd = [0x02, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]
        self.spi.xfer2(cmd + data)
        self.wait_busy()

    def read_data(self, addr, length):
        """ 读取数据 """
        cmd = [0x03, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]
        return self.spi.xfer2(cmd + [0x00]*length)[4:]

    def close(self):
        """ 关闭SPI连接 """
        self.spi.close()

# 使用示例.
if __name__ == "__main__":
    flash = W25Q64()

    try:
        # 验证设备ID (W25Q64的JEDEC ID应为0xEF4017).
        jedec_id = flash.read_id()
        print(f"JEDEC ID: {hex(jedec_id)}")

        # 测试参数设置.
        test_addr = 0x000000  # 测试地址 (确保该地址已擦除).
        test_data = list(b'hello world!')  # 测试数据 (256字节).

        # 擦除扇区 (4KB对齐).
        print("擦除扇区...")
        flash.sector_erase(test_addr)

        # 写入数据.
        print("写入数据...")
        flash.page_program(test_addr, test_data)

        # 读取数据.
        print("读取数据...")
        read_back = flash.read_data(test_addr, len(test_data))
        print(bytes(read_back).decode())

        # 验证数据.
        if test_data == read_back:
            print("数据验证成功!")
        else:
            print("数据验证失败!")

    finally:
        flash.close()

8. UART

接线: TXD --> GPIO 15, RXD --> GPIO 14 (TX接RX, RX接TX).

需在'raspi-config'中启用uart。

然后关闭BT:

$ sudo systemctl disable hciuart
$ sudo vim /boot/firmware/config.txt

在'config.txt'中添加如下内容(如果不存在的话):

enable_uart=1
dtoverlay=disable-bt

再然后,关闭串口控制台:

$ sudo vim /boot/firmware/cmdline.txt

修改如下:

--- console=serial0,115200 console=tty1 root=...
+++ # console=serial0,115200
+++ console=tty1 root=...

重启:

$ sudo reboot

收发测试代码(使用usb转串口连接电脑,或者短接树莓派的tx和rx):

import serial
from time import sleep


uart = serial.Serial(
    port     = '/dev/ttyAMA0',
    baudrate = 115200,
    parity   = serial.PARITY_NONE,
    stopbits = serial.STOPBITS_ONE,
    bytesize = serial.EIGHTBITS,
    timeout  = 1
)

try:
    while True:
        uart.write(b'hello world.\n')
        print('Send data.')

        data = uart.readline()
        if data:
            print('Receive data: ', data.decode('utf-8').strip())

        sleep(2)

except KeyboardInterrupt:
    uart.close()
    print('Uart closed.')

9. 1-Wire

接线: DS18B20 --> GPIO 4.

需在'raspi-config'中启用1-wire。

读取ds18b20的温度:

import os
import glob
import time

class DS18B20:
    def __init__(self):
        self.device_folder = '/sys/bus/w1/devices/'
        self.device_prefix = '28-'
        self._init_devices()

    def _init_devices(self):
        # 加载内核模块 (备用方案).
        if not glob.glob(self.device_folder + self.device_prefix + '*'):
            os.system('sudo modprobe w1-gpio')
            os.system('sudo modprobe w1-therm')

    def find_devices(self):
        devices = glob.glob(self.device_folder + self.device_prefix + '*')
        return [device.split('/')[-1] for device in devices]

    def read_temp(self, device_id=None):
        if not device_id:
            devices = self.find_devices()
            if not devices:
                raise Exception("未检测到DS18B20设备")
            device_id = devices[0]

        device_file = os.path.join(self.device_folder, device_id, 'w1_slave')
        
        with open(device_file, 'r') as f:
            lines = f.readlines()
            if lines[0].strip()[-3:] != 'YES':
                time.sleep(0.2)
                return self.read_temp(device_id)  # 重试读取.
            temp_pos = lines[1].find('t=')
            if temp_pos == -1:
                raise ValueError("温度数据格式错误")
            temp_raw = lines[1][temp_pos+2:]
            temp_c = float(temp_raw) / 1000.0
            return temp_c

if __name__ == "__main__":
    sensor = DS18B20()
    print(f"检测到设备:{sensor.find_devices()}")
    
    try:
        while True:
            temperature = sensor.read_temp()
            print(f"当前温度:{temperature:.2f}℃  ({time.strftime('%H:%M:%S')})")
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n监测已终止")

10. Camera

接线: OV5647 --> CSI 0.

拍摄一张照片'test.jpg',并保存到当前位置:

from picamera2 import Picamera2

camera = Picamera2()
config = camera.create_still_configuration()
camera.configure(config)

camera.start()
camera.capture_file('test.jpg')
camera.stop()

11. Thread

接线: LED --> GPIO 17.

多线程示例,主线程打印字符串,子线程闪烁LED:

import RPi.GPIO as GPIO
import time
import threading
import signal
import sys

# 配置参数.
LED_PIN = 17  # BCM编号.
BLINK_INTERVAL = 0.5  # 默认闪烁间隔(秒).
STOP_EVENT = threading.Event()

class LedController(threading.Thread):
    def __init__(self, pin, interval):
        super().__init__()
        self.pin = pin
        self.interval = interval
        self.daemon = True  # 设为守护线程,主程序退出自动终止.

    def run(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.pin, GPIO.OUT)
        try:
            while not STOP_EVENT.is_set():
                GPIO.output(self.pin, GPIO.HIGH)
                time.sleep(self.interval)
                GPIO.output(self.pin, GPIO.LOW)
                time.sleep(self.interval)
        finally:
            GPIO.cleanup(self.pin)

def signal_handler(sig, frame):
    print("\n终止程序...")
    STOP_EVENT.set()
    sys.exit(0)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal_handler)
    
    # 初始化线程.
    led_thread = LedController(LED_PIN, BLINK_INTERVAL)
    
    # 启动线程.
    led_thread.start()
    print("LED闪烁已启动,按Ctrl+C停止")

    # 主线程可执行其他任务.
    try:
        while True:
            # 示例:主线程每秒输出状态.
            print("主线程运行中...")
            time.sleep(1)
    except KeyboardInterrupt:
        signal_handler(None, None)

树莓派没有内建的ADC,所以上述不包含ADC示例,但可以通过外置ADC转换器使用ADC功能。

以上示例都是混合使用的,生态太混乱了,比较底层的就是RPI.GPIO,且RPI.GPIO还从2023年开始停止更新了,上层的gpiozero就是基于这个库做的,然后C版的WiringPi也停止更新了。

另外,还可以去看看adafruit-circuitpython,也可以控制树莓派3/4/5等硬件。

posted @ 2025-05-13 21:14  this毛豆  阅读(100)  评论(0)    收藏  举报