創客

樹莓派 Raspberry Pi 控制 GPIO 連接的 LED 燈教學與範例

介紹如何在樹莓派上使用 sysfs、gpio 指令、C 語言或 Python 指令稿等方式,控制 GPIO 連接的 LED 燈。

樹莓派 GPIO 排針輸出腳位

若要查詢樹莓派 GPIO 排針輸出腳位,可以開啟終端機並執行 pinout 指令:

# 查詢樹莓派 GPIO 排針輸出腳位
pinout

執行此令之後,會自動偵測樹莓派的版本,並列出所有 GPIO 排針輸出腳位的配置圖。

樹莓派 Raspberry Pi 4B GPIO 排針輸出腳位

接線圖

將 LED 的正極連接到樹莓派的 GPIO4(Pin 7)腳位,中間接上一個限流電阻,LED 的負極則連接到樹莓派的 GND(Pin 6)腳位。限流電阻在選用時,要依照 LED 工作電壓計算適合的電阻值,當 GPIO 處於 high 的狀態時電壓是 3V3,不同顏色的 LED 會有不同的工作電壓,建議可以使用 LED 串聯電阻計算器來計算。

接線圖

LED 燈的正極與負極是依據針腳的長度來判斷的,比較長的那一支針腳是正極,而比較短的那一支針腳則是負極。

LED 正極與負極
連接樹莓派 GPIO 與 LED 燈
樹莓派 GPIO 針腳
LED 燈與限流電阻

GPIO Sysfs 介面

Linux 核心所提供的 GPIO sysfs 介面可以用來控制 GPIO,這是一種舊式的 GPIO 操作方式,在 Linux 4.7 版本以前都是採用這樣的方式,在 Linux 4.8 版本之後才又出現新的 character device 介面。

這我們希望透過 GPIO sysfs 介面來操控 GPIO4(Pin 7),以下是操作步驟。
Step 1
將要操控的 GPIO 號碼寫入 /sys/class/gpio/export,這個檔案是一個唯寫(只能寫入不能讀取)的檔案,透過這樣的方式可以從 Linux 核心取得指定 GPIO 的控制權:

# 取得 GPIO 4 的控制權
echo 4 > /sys/class/gpio/export

Step 2
/sys/class/gpio/gpio4/direction 檔案內容設定為 out,設定 GPIO 4 為輸出模式(如果要設定為輸入模式,則可將檔案內容設為 in):

# 設定 GPIO 4 為輸出模式(out)
echo out > /sys/class/gpio/gpio4/direction

Step 3
透過對應 GPIO 4 的 /sys/class/gpio/gpio4/value 檔案,設定輸出電位,若設定為 1 則代表高電位,若設定為 0 則代表低電位:

# 設定 GPIO 4 輸出值為高電位
echo 1 > /sys/class/gpio/gpio4/value

# 設定 GPIO 4 輸出值為低電位
echo 0 > /sys/class/gpio/gpio4/value

以這個例子來說,若將 GPIO 4 輸出值設為高電位,就可以點亮 LED 燈,而若將輸出值設為低電位,則 LED 燈就會熄滅。
Step 4
在使用完 GPIO 之後,將不用的 GPIO 號碼寫入 /sys/class/gpio/unexport,讓 Linux 核心收回 GPIO 4 的控制權:

# 撤回 GPIO 4 的控制權
echo 4 > /sys/class/gpio/unexport

gpio 指令工具

gpio 是一個用來存取與控制樹莓派 GPIO 的指令工具,可用於 GPIO 相關應用的開發與測試,或是直接用於 shell 指令稿中。

顯示所有 GPIO 狀態

若要顯示所有 GPIO 的狀態,可以執行:

# 顯示所有 GPIO 狀態
gpio readall
 +-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | IN   | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | IN   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 4B--+---+------+---------+-----+-----+

設定 GPIO 輸出

gpio 可以作為 GPIO sysfs 的另外一層使用介面,也就是說我們可以使用簡單的 gpio 指令來透過 GPIO sysfs 控制 GPIO,:

# 取得 GPIO 4 的控制權,並設定為輸出模式
gpio export 4 out

# 設定 GPIO 4 輸出值為高電位
gpio -g write 4 1

# 設定 GPIO 4 輸出值為低電位
gpio -g write 4 0

gpio 還提供簡單的 GPIO 切換與閃爍功能:

# 切換 GPIO 4 輸出值(0 與 1 互換)
gpio -g toggle 4

# 持續定時切換 GPIO 4 輸出值(閃爍)
gpio -g blink 4

使用完之後,撤回 GPIO 4 的控制權:

# 撤回 GPIO 4 的控制權
gpio unexport 4

常見問題

若在 Raspberry Pi 4B 上面執行 gpio 指令時,出現了以下的錯誤訊息:

Oops - unable to determine board type... model: 17

表示 Wiring Pi 版本過舊,可以參考 Wiring Pi 官方的建議升級 Wiring Pi:

# 下載最新的 Wiring Pi 套件
wget https://project-downloads.drogon.net/wiringpi-latest.deb

# 安裝最新的 Wiring Pi 套件
sudo dpkg -i wiringpi-latest.deb

DebugFS

debugfs 也可以用來查看目前的 GPIO 設定,在樹莓派中預設就已經有掛載 debugfs,若遇到沒有掛載 debugfs 的系統,可以使用以下指令掛載:

# 掛載 debugfs
sudo mount -t debugfs none /sys/kernel/debug

當有了 debugfs 之後,就可以查看 GPIO 的狀態:

# 查看 GPIO 狀態
sudo cat /sys/kernel/debug/gpio
gpiochip0: GPIOs 0-57, parent: platform/fe200000.gpio, pinctrl-bcm2711:
 gpio-0   (ID_SDA              )
 gpio-1   (ID_SCL              )
 gpio-2   (SDA1                )
 gpio-3   (SCL1                )
 gpio-4   (GPIO_GCLK           )
 gpio-5   (GPIO5               )
 gpio-6   (GPIO6               )
 gpio-7   (SPI_CE1_N           )
 gpio-8   (SPI_CE0_N           )
 gpio-9   (SPI_MISO            )
 gpio-10  (SPI_MOSI            )
 gpio-11  (SPI_SCLK            )
 gpio-12  (GPIO12              )
 gpio-13  (GPIO13              )
 gpio-14  (TXD1                )
 gpio-15  (RXD1                )
 gpio-16  (GPIO16              )
 gpio-17  (GPIO17              )
 gpio-18  (GPIO18              )
 gpio-19  (GPIO19              )
 gpio-20  (GPIO20              )
 gpio-21  (GPIO21              )
 gpio-22  (GPIO22              )
 gpio-23  (GPIO23              )
 gpio-24  (GPIO24              )
 gpio-25  (GPIO25              )
 gpio-26  (GPIO26              )
 gpio-27  (GPIO27              )
 gpio-28  (RGMII_MDIO          )
 gpio-29  (RGMIO_MDC           )
 gpio-30  (CTS0                )
 gpio-31  (RTS0                )
 gpio-32  (TXD0                )
 gpio-33  (RXD0                )
 gpio-34  (SD1_CLK             )
 gpio-35  (SD1_CMD             )
 gpio-36  (SD1_DATA0           )
 gpio-37  (SD1_DATA1           )
 gpio-38  (SD1_DATA2           )
 gpio-39  (SD1_DATA3           )
 gpio-40  (PWM0_MISO           )
 gpio-41  (PWM1_MOSI           )
 gpio-42  (STATUS_LED_G_CLK    |led0                ) out lo
 gpio-43  (SPIFLASH_CE_N       )
 gpio-44  (SDA0                )
 gpio-45  (SCL0                )
 gpio-46  (RGMII_RXCLK         )
 gpio-47  (RGMII_RXCTL         )
 gpio-48  (RGMII_RXD0          )
 gpio-49  (RGMII_RXD1          )
 gpio-50  (RGMII_RXD2          )
 gpio-51  (RGMII_RXD3          )
 gpio-52  (RGMII_TXCLK         )
 gpio-53  (RGMII_TXCTL         )
 gpio-54  (RGMII_TXD0          )
 gpio-55  (RGMII_TXD1          )
 gpio-56  (RGMII_TXD2          )
 gpio-57  (RGMII_TXD3          )

gpiochip1: GPIOs 504-511, parent: platform/soc:firmware:gpio, raspberrypi-exp-gpio, can sleep:
 gpio-504 (BT_ON               )
 gpio-505 (WL_ON               )
 gpio-506 (PWR_LED_OFF         |led1                ) out lo ACTIVE LOW
 gpio-507 (GLOBAL_RESET        )
 gpio-508 (VDD_SD_IO_SEL       |vdd-sd-io           ) out hi
 gpio-509 (CAM_GPIO            )
 gpio-510 (SD_PWR_ON           |sd_vcc_reg          ) out hi
 gpio-511 (SD_OC_N             )

raspi-gpio 指令工具

raspi-gpio 是另一個可以用來操控樹莓派 GPIO 的指令工具,以下指令可以查詢所有 GPIO 的狀態:

# 查詢所有 GPIO 狀態
raspi-gpio get
BANK0 (GPIO 0 to 27):
GPIO 0: level=1 fsel=0 func=INPUT pull=UP
GPIO 1: level=1 fsel=0 func=INPUT pull=UP
GPIO 2: level=1 fsel=4 alt=0 func=SDA1 pull=UP
GPIO 3: level=1 fsel=4 alt=0 func=SCL1 pull=UP
GPIO 4: level=0 fsel=1 func=OUTPUT pull=UP
GPIO 5: level=1 fsel=0 func=INPUT pull=UP
GPIO 6: level=1 fsel=0 func=INPUT pull=UP
GPIO 7: level=1 fsel=0 func=INPUT pull=UP
GPIO 8: level=1 fsel=0 func=INPUT pull=UP
GPIO 9: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 10: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 11: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 12: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 13: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 14: level=1 fsel=0 func=INPUT pull=NONE
GPIO 15: level=1 fsel=0 func=INPUT pull=UP
GPIO 16: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 17: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 18: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 19: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 20: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 21: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 22: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 23: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 24: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 25: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 26: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 27: level=0 fsel=0 func=INPUT pull=DOWN
BANK1 (GPIO 28 to 45):
GPIO 28: level=1 fsel=2 alt=5 func=RGMII_MDIO pull=UP
GPIO 29: level=0 fsel=2 alt=5 func=RGMII_MDC pull=DOWN
GPIO 30: level=0 fsel=7 alt=3 func=CTS0 pull=UP
GPIO 31: level=0 fsel=7 alt=3 func=RTS0 pull=NONE
GPIO 32: level=1 fsel=7 alt=3 func=TXD0 pull=NONE
GPIO 33: level=1 fsel=7 alt=3 func=RXD0 pull=UP
GPIO 34: level=1 fsel=7 alt=3 func=SD1_CLK pull=NONE
GPIO 35: level=1 fsel=7 alt=3 func=SD1_CMD pull=UP
GPIO 36: level=1 fsel=7 alt=3 func=SD1_DAT0 pull=UP
GPIO 37: level=1 fsel=7 alt=3 func=SD1_DAT1 pull=UP
GPIO 38: level=1 fsel=7 alt=3 func=SD1_DAT2 pull=UP
GPIO 39: level=1 fsel=7 alt=3 func=SD1_DAT3 pull=UP
GPIO 40: level=0 fsel=4 alt=0 func=PWM1_0 pull=NONE
GPIO 41: level=0 fsel=4 alt=0 func=PWM1_1 pull=NONE
GPIO 42: level=0 fsel=1 func=OUTPUT pull=UP
GPIO 43: level=1 fsel=0 func=INPUT pull=UP
GPIO 44: level=1 fsel=0 func=INPUT pull=UP
GPIO 45: level=1 fsel=0 func=INPUT pull=UP
BANK2 (GPIO 46 to 53):
GPIO 46: level=0 fsel=0 func=INPUT pull=UP
GPIO 47: level=0 fsel=0 func=INPUT pull=UP
GPIO 48: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 49: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 50: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 51: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 52: level=0 fsel=0 func=INPUT pull=DOWN
GPIO 53: level=0 fsel=0 func=INPUT pull=DOWN

若只要查詢 GPIO 4 的狀態,可以執行:

# 查詢 GPIO 4 的狀態
raspi-gpio get 4
GPIO 4: level=0 fsel=1 func=OUTPUT pull=UP

以下指令可以將 GPIO 4 設定為輸出模式,並控制輸出電位:

# 設定 GPIO 4 為輸出模式
raspi-gpio set 4 op

# 設定 GPIO 4 輸出值為高電位
raspi-gpio set 4 dh

# 設定 GPIO 4 輸出值為低電位
raspi-gpio set 4 dl

C 語言操控 GPIO

若要使用 C 語言操控樹莓派的 GPIO,可以參考 RPi GPIO Code Samples。以下是適用於 Raspberry Pi 4B 的範例:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

/**
 * Raspberry Pi 4B 的 peripheral base 是 0xFE000000
 * Raspberry Pi 23 peripheral base 是 0x3F000000
 * Raspberry Pi 的 peripheral base 是 0x20000000
 */
#define BCM2708_PERI_BASE 0xFE000000
#define GPIO_BASE         (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */

#define PAGE_SIZE  (4 * 1024)
#define BLOCK_SIZE (4 * 1024)

int  mem_fd;
void *gpio_map;

/* 存取 GPIO 用指標 */
volatile unsigned *gpio;

/* GPIO 設定巨集,在呼叫 OUT_GPIO(x) 或 SET_GPIO_ALT(x,y) 之前,要先呼叫 INP_GPIO(x) */
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))

#define GPIO_SET *(gpio+7)   /* 將 bits 為 1 的 GPIO 設為高電位,忽略 bits 為 0 的 GPIO */
#define GPIO_CLR *(gpio+10)  /* 將 bits 為 1 的 GPIO 設為低電位,忽略 bits 為 0 的 GPIO */

/* 將存取 GPIO 用的指標 gpio 映射到正確的記憶體位址 */
void setup_io() {
    /* 開啟 /dev/mem */
    if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
        printf("can't open /dev/mem \n");
        exit(-1);
    }

    /* 以 mmap 映射 GPIO 記憶體位址 */
    gpio_map = mmap(
        NULL,         /* Any adddress in our space will do */
        BLOCK_SIZE,   /* Map length */
        PROT_READ|PROT_WRITE, /* Enable reading & writting to mapped memory */
        MAP_SHARED,   /* Shared with other processes */
        mem_fd,       /* File to map */
        GPIO_BASE     /* Offset to GPIO peripheral */
        );
    /* 處理完記憶體映射後,即可關閉 mem_fd */
    close(mem_fd);

    if (gpio_map == MAP_FAILED) {
        printf("mmap error %d\n", (int)gpio_map); /* errno also set! */
        exit(-1);
    }

    /* 設定存取 GPIO 用的指標,必須使用 volatile 指標 */
    gpio = (volatile unsigned *)gpio_map;
}

int main(int argc, char *argv[]) {
    /* 要使用的 GPIO 號碼 */
    int g = 4;

    /* 設定存取 register 用的 gpio 指標 */
    setup_io();

    /* 先呼叫 INP_GPIO,再呼叫 OUT_GPIO  */
    INP_GPIO(g);
    OUT_GPIO(g);

    /* 將指定的 GPIO 設為高電位 */
    GPIO_SET = 1 << g;
    sleep(1);

    /* 將指定的 GPIO 設為低電位 */
    GPIO_CLR = 1 << 4;

    return 0;
}

將這段程式碼儲存為 led.c,然後以 gcc 編譯:

# 編譯
gcc -o led led.c

編譯完成後,會產生一個 led 執行檔,執行後會讓 LED 點亮一秒,然後熄滅:

# 執行
sudo ./led

C 語言 BCM2835 函式庫操控 GPIO

BCM2835 是一個適用於樹莓派的 C 語言函式庫,提供了 Broadcom BCM 2835 晶片上的 GPIO 與其他 IO 用的功能函數,使用 BCM2835 可以讓開發者比較方便操作 GPIO 或 I2C 等介面的裝置。

使用前先下載 BCM2835 的原始碼,自行編譯與安裝:

# 下載 BCM2835 原始碼
wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.71.tar.gz

# 解壓縮
tar zxvf bcm2835-1.71.tar.gz
cd bcm2835-1.71

# 編譯並測試
./configure
make
sudo make check

# 安裝
sudo make install

安裝好 BCM2835 函式庫之後,即可在 C 語言程式中使用 BCM2835 函式庫控制 GPIO,以下是一個簡單的範例:

#include <bcm2835.h>

// 設定要進行操作的 GPIO(RPI_GPIO_P1_07 等同於 BCM 編號 4)
#define PIN RPI_GPIO_P1_07

int main(int argc, char **argv) {
    // 除錯模式(不會實際操作 GPIO)
    //bcm2835_set_debug(1);

    // 初始化
    if (!bcm2835_init()) return 1;

    // 設定為輸入模式
    bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);

    // 設定 GPIO 輸出值為高電位
    bcm2835_gpio_write(PIN, HIGH);

    // 等待一秒
    bcm2835_delay(1000);

    // 設定 GPIO 輸出值為低電位
    bcm2835_gpio_write(PIN, LOW);

    // 關閉 BCM2835 函式庫(收回配置的資源)
    bcm2835_close();

    return 0;
}

將這段程式碼儲存為 bcm_led.c,使用以下指令編譯:

# 編譯
gcc bcm_led.c -o bcm_led -l bcm2835

使用 root 權限執行,執行後會點亮 LED 一秒鐘,然後熄滅:

# 執行
sudo ./bcm_led

Python 操控 GPIO

在 Raspbian 發行版中已經預設納入 Python 的 RPi.GPIO 模組,因此我們可以直接在 Python 指令稿中使用此模組操控樹莓派的 GPIO:

import RPi.GPIO as GPIO

# 使用 BCM 編號
GPIO.setmode(GPIO.BCM)

# 操作 GPIO 4(Pin 7)
pin = 4

# 設定為 GPIO 為輸入模式
GPIO.setup(pin, GPIO.OUT)

# 設定 GPIO 輸出值為高電位
GPIO.output(pin, GPIO.HIGH)

# 等待一秒鐘
time.sleep(1)

# 設定 GPIO 輸出值為低電位
GPIO.output(pin, GPIO.LOW)

執行這段程式碼之後,會讓 GPIO 4(Pin 7)上面的 LED 點亮一秒鐘,然後熄滅。

參考資料

Share
Published by
Office Guide

Recent Posts

Python 使用 PyAutoGUI 自動操作滑鼠與鍵盤

本篇介紹如何在 Python ...

1 年 ago

Ubuntu Linux 以 WireGuard 架設 VPN 伺服器教學與範例

本篇介紹如何在 Ubuntu ...

1 年 ago

Linux 網路設定 ip 指令用法教學與範例

本篇介紹如何在 Linux 系...

1 年 ago

Linux 以 Cryptsetup、LUKS 加密 USB 隨身碟教學與範例

介紹如何在 Linux 系統中...

1 年 ago