(水)树莓派上 GPIO 简单控制测试

此处使用 Raspberry Pi 400 作测试,使用官方 Raspberry Pi OS演示,基本同于 Debian 系其它 Linux。

Linux 上的 GPIO 设备

通过在 Linux 的 /dev 目录查看本机外设,可以看到以下内容。

ls -l /dev/gpiochip*
crw-rw----+ 1 root gpio 254, 0  8月 12 14:15 /dev/gpiochip0
crw-rw----+ 1 root gpio 254, 1  8月 12 14:15 /dev/gpiochip1
lrwxrwxrwx  1 root root      9  8月 12 14:15 /dev/gpiochip4 -> gpiochip0

也可使用 gpiodetect 命令查看 GPIO 设备。

gpiochip0 [pinctrl-bcm2711] (58 lines)
gpiochip1 [raspberrypi-exp-gpio] (8 lines)

此命令显示了当前系统中所有的 GPIO 控制器及其对应的 GPIO 线数。

通过 gpioinfo 命令可以查看 GPIO 控制器的详细信息。

gpiochip0 - 58 lines:
        line   0:       "ID_SDA"                input
        line   1:       "ID_SCL"                input
        line   2:       "GPIO2"                 input
        line   3:       "GPIO3"                 input
        line   4:       "GPIO4"                 output drive=open-drain consumer="onewire@0"
        line   5:       "GPIO5"                 input
        line   6:       "GPIO6"                 input
        line   7:       "GPIO7"                 output active-low consumer="spi0 CS1"
        line   8:       "GPIO8"                 output active-low consumer="spi0 CS0"
        line   9:       "GPIO9"                 input
        line  10:       "GPIO10"                input
        line  11:       "GPIO11"                input
        line  12:       "GPIO12"                input
        line  13:       "GPIO13"                input
        line  14:       "GPIO14"                input
        line  15:       "GPIO15"                input
        line  16:       "GPIO16"                input
        line  17:       "GPIO17"                input
        line  18:       "GPIO18"                input
        line  19:       "GPIO19"                input
        line  20:       "GPIO20"                input
        line  21:       "GPIO21"                input
        line  22:       "GPIO22"                input
        line  23:       "GPIO23"                input
        line  24:       "GPIO24"                input
        line  25:       "GPIO25"                input
        line  26:       "GPIO26"                input
        line  27:       "GPIO27"                input
        line  28:       "RGMII_MDIO"            input
        line  29:       "RGMIO_MDC"             input
        line  30:       "CTS0"                  input
        line  31:       "RTS0"                  input
        line  32:       "TXD0"                  input
        line  33:       "RXD0"                  input
        line  34:       "SD1_CLK"               input
        line  35:       "SD1_CMD"               input
        line  36:       "SD1_DATA0"             input
        line  37:       "SD1_DATA1"             input
        line  38:       "SD1_DATA2"             input
        line  39:       "SD1_DATA3"             input
        line  40:       "PWM0_MISO"             input
        line  41:       "PWM1_MOSI"             input
        line  42:       "STATUS_LED_G_CLK"      output consumer="PWR"
        line  43:       "SPIFLASH_CE_N"         input
        line  44:       "SDA0"                  input
        line  45:       "SCL0"                  input
        line  46:       "RGMII_RXCLK"           input
        line  47:       "RGMII_RXCTL"           input
        line  48:       "RGMII_RXD0"            input
        line  49:       "RGMII_RXD1"            input
        line  50:       "RGMII_RXD2"            input
        line  51:       "RGMII_RXD3"            input
        line  52:       "RGMII_TXCLK"           input
        line  53:       "RGMII_TXCTL"           input
        line  54:       "RGMII_TXD0"            input
        line  55:       "RGMII_TXD1"            input
        line  56:       "RGMII_TXD2"            input
        line  57:       "RGMII_TXD3"            input
gpiochip1 - 8 lines:
        line   0:       "BT_ON"                 output consumer="shutdown"
        line   1:       "WL_ON"                 output
        line   2:       "PWR_LED_OFF"           output
        line   3:       "GLOBAL_RESET"          output
        line   4:       "VDD_SD_IO_SEL"         output consumer="vdd-sd-io"
        line   5:       "GLOBAL_SHUTDOWN"       output consumer="gpio-poweroff"
        line   6:       "SD_PWR_ON"             output consumer="regulator-sd-vcc"
        line   7:       "SHUTDOWN_REQUEST"      input

可见外设控制器处于 gpiochip0gpiochip1 为树莓派主板上控制器。

Shell 控制 GPIO 输出

此处连接一个 LED 到 GPIO 24,通过 shell 控制其亮灭。

gpioset -c gpiochip0 24=1

此命令使线序 24 输出高电平,LED 亮。

gpioset -c gpiochip0 24=0

此命令使线序 24 输出低电平,LED 灭。

Rust 控制 GPIO 输出

引入库 gpio-cdev

cargo add gpio-cdev

现在可以编写测试来控制 GPIO 输出。

简单写一个初始化函数,用来获取外设并把 GPIO 24 设置为输出。

fn led_init(gpio: u32) -> Result<LineHandle, Error> {
    let mut chip = Chip::new(path: "/dev/gpiochip0")?;

    let handle = chip
        .get_line(offset: gpio)?
        .request(LineRequestFlags::OUTPUT, 0, "led");

    handle
}

然后分别为 LED 亮和灭写两个测试函数。

#[test]
fn turn_on_led() -> Result<(), Error> {
    let handle = led_init(gpio: 24)?;
    handle.set_value(1)?;

    Ok(())
}

#[test]
fn turn_off_led() -> Result<(), Error> {
    let handle = led_init(gpio: 24)?;
    handle.set_value(0)?;

    Ok(())
}

运行测试:

cargo test turn_on_led

此时能够看到 LED 亮起。

cargo test turn_off_led

此时能够看到 LED 熄灭。

完整代码如下:

use gpio_cdev::{Chip, Error, LineHandle, LineRequestFlags};

fn main() {
    let _ = led_init(gpio: 24);
    println!("Hello, world!");
}

fn led_init(gpio: u32) -> Result<LineHandle, Error> {
    let mut chip = Chip::new(path: "/dev/gpiochip0")?;

    let handle: Result<LineHandle, Error> = chip
        .get_line(offset: gpio)?
        .request(LineRequestFlags::OUTPUT, 0, "led");

    handle
}

#[test]
fn turn_on_led() -> Result<(), Error> {
    let handle = led_init(gpio: 24)?;
    handle.set_value(1)?;

    Ok(())
}

#[test]
fn turn_off_led() -> Result<(), Error> {有多少个文件被修改了?
    let handle = led_init(gpio: 24)?;
    handle.set_value(0)?;

    Ok(())
}

读取 DHT11 温湿度传感器的 GPIO 控制方法

DHT11 温湿度传感器使用手册中有对裸传感器接线方式的阐述,此处使用模块。

由于 DHT11 传感器的通信协议比较特殊,直接使用 GPIO 读取数据需要严格按照时序进行操作,因此在 Linux 上直接使用 GPIO 控制 DHT11 传感器不现实。
可以使用内核驱动进行读取,此处不考虑这种方法。

尝试使用 Rust 进行读取

使用 GPIO25 作为数据线,使用 gpio-cdev 进行读取。

同样的,为这个 GPIO 写一个初始化函数。

fn dht11_init(gpio: u32) -> Result<LineHandle, Error> {
    let mut chip = Chip::new(path: "/dev/gpiochip0")?;

    {
        let handle = chip
            .get_line(offset: gpio)?
            .request(LineRequestFlags::OUTPUT, 0, "dht11")?;

        handle.set_value(0)?;
        sleep(Duration::from_millis(18));
        handle.set_value(1)?;
        sleep(Duration::from_micros(40));
    }

    let handle: Result<LineHandle, Error> = chip
        .get_line(offset: gpio)?
        .request(LineRequestFlags::INPUT, 0, "dht11");

    handle
}

此处初始化需要给 DHT11 发送一个低电平信号,持续 18ms,然后拉高 40us,之后再将 GPIO 设置为输入模式,等待 DHT11 的响应。

注意:此处 handle 必须手动加入作用域,否则会因为未释放而导致 GPIO 重用产生 EBUSY 错误。

Error: Error { kind: Ioctl { kind: LineHandle, cause: EBUSY } }

现在写读取数据的函数。

#[test]
fn read_dht11_data() -> Result<(), Error> {
    let handle = dht11_init(gpio: 25)?;

    let mut data: Vec<u8> = vec![];
    let mut timings: Vec<u64> = vec![];

    let start = Instant::now();
    while start.elapsed() < Duration::from_millis(10) {
        let val = handle.get_value()?;
        timings.push(start.elapsed().as_micros() as u64);
        data.push(val as u8);
    }

    println!("Sample len: {}", data.len());
    println!("Top 100 samples: {:?}", &data[..100.min(data.len())]);

    Ok(())
}

查看输出:

Sample len: 9296
Top 100 samples: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
test read_dht11_data ... ok

由于读取时需要确认高低电平的持续时间,我们需要一个 wait_while() 函数来等待高低电平的变化。

fn wait_while(
    handle: &LineHandle,
    value: u8,
    timeout: Duration,
) -> Result<(), Box<dyn std::error::Error>> {
    let start = Instant::now();
    while handle.get_value()? == value {
        if start.elapsed() > timeout {
            return Err("dht11: timeout waiting for line".into());
        }
    }
    Ok(())
}

read_dht11() 函数,把初始化去掉,直接写成。

fn read_dht11(gpio: u32) -> Result<(f32, f32), Box<dyn std::error::Error>> {
    let mut chip = Chip::new(path: "/dev/gpiochip0")?;

    // 初始化 DHT11,给定长度的低电平信号和高电平信号激活
    {
        let out = chip
            .get_line(gpio)?
            .request(LineRequestFlags::OUTPUT, 0, "dht11")?;
        out.set_value(0)?;
        sleep(Duration::from_millis(20));
        out.set_value(1)?;
        sleep(Duration::from_micros(30));
    }

    // 请求 GPIO 为输入模式,等待 DHT11 响应
    let input: LineHandle = chip
        .get_line(gpio)?
        .request(LineRequestFlags::INPUT, 0, "dht11")?;

    // 起始信号,DHT11 响应
    wait_while(&input, 1, Duration::from_millis(100))?;
    wait_while(&input, 0, Duration::from_millis(100))?;
    wait_while(&input, 1, Duration::from_millis(100))?;

    // 读取 40 位数据
    let mut bits = [0u8; 40];
    for bit in bits.iter_mut() {
        wait_while(&input, 0, Duration::from_millis(100))?;
        let start = Instant::now();
        wait_while(&input, 1, Duration::from_millis(100))?;
        *bit = if start.elapsed().as_micros() > 40 { 1 } else { 0 };
    }

    // 读取到的数据为 5 个字节,前 4 个字节为数据,第 5 个字节为校验和
    let mut bytes = [0u8; 5];
    for (i, &b) in bits.iter().enumerate() {
        // 解析方法:`i / 8 << 1 | b`
        bytes[i / 8] = (bytes[i / 8] << 1) | b;
    }

    // 计算校验和
    let sum = bytes[..4].iter().fold(init: 0u8, f: |acc, &b| acc.wrapping_add(b));
    // 校验和不匹配则返回错误
    if sum != bytes[4] {
        return Err(format!(
            "dht11: checksum mismatch (calc {sum:#04x}, got {:#04x})",
            bytes[4]
        ) String
        .into());
    }

    let humidity = bytes[0] as f32 + bytes[1] as f32 / 10.0;
    let temperature = bytes[2] as f32 + bytes[3] as f32 / 10.0;
    Ok((humidity, temperature))
}

这个函数接收一个 LineHandle,一个值 value,以及一个超时时间 timeout。它会持续检查 GPIO 的值是否等于 value,如果超过 timeout 时间仍然没有变化,则返回一个错误。

其它外设的控制方法

gpio-cdev 只提供基础 GPIO 外设的控制,想要解析 DHT11 传感器的协议,需要自己实现时序控制和数据解析。对于其它外设,如 I2C、SPI 等,建议使用专门的库来进行控制。

具体参考crates.io 上的 gpio-cdev 文档

由于本文没有什么含金量,也几乎没有实际使用价值,后面考虑写一篇关于 rpi 上的 HAL 库博客。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇