返回文章列表

樹莓派 RGB LED 平台驅動程式開發

本文講解如何在 Raspberry Pi 3 Model B 上開發 RGB LED 平台驅動程式,包含 Device Tree 修改、GPIO 控制、以及驅動程式碼解析。透過修改 bcm2710-rpi-3-b.dts 檔案,新增必要的 pin 組態和裝置節點,實作對 LED 的精確控制。文章詳細說明瞭 GPIO

嵌入式系統 驅動程式開發

在嵌入式系統開發中,控制外部裝置是常見的需求。本文將探討如何在 Raspberry Pi 3 Model B 平台上,開發一個控制 RGB LED 的平台驅動程式。此過程涉及到 Device Tree 的修改,以及 Linux 核心驅動程式的編寫,包含 GPIO 的初始化、控制以及與使用者空間的互動。透過理解這些核心概念,開發者可以更有效地控制硬體資源,並建立客製化的嵌入式系統應用。以下將逐步說明 Device Tree 的修改方式、驅動程式碼的架構及核心函式的實作細節,並提供必要的程式碼片段和解說,以幫助讀者理解整個開發流程。

平台驅動程式 Chapter 5

LAB 5.2 硬體描述

BCM2837 SoC 擁有 54 個通用輸入/輸出(GPIO)線,分為兩個銀行。所有 GPIO 鎖都至少有兩個替代功能在 SoC 內部。替代功能通常是周邊 IO,並且單個周邊裝置可能出現在每個銀行中,以允許 IO 電壓的靈活性。GPIO 有 41 個暫存器,所有存取都被假設為 32 位。

GPIO 暫存器功能

  • 功能選擇暫存器用於定義通用輸入/輸出引腳的操作。每個 GPIO 引腳至少有兩個替代功能。FSEL{n} 欄位決定了第 n 個 GPIO 引腳的功能。所有未使用的替代功能線都被拉到地,並且如果被選擇,將輸出 “0”。
  • 輸出設定暫存器用於設定 GPIO 引腳。SET{n} 欄位定義了要設定的相應 GPIO 引腳,向該欄位寫入 “0” 沒有任何效果。如果 GPIO 引腳被用作輸入(預設情況下),則 SET{n} 欄位中的值將被忽略。但是,如果引腳隨後被定義為輸出,則該位將根據最後的設定/清除操作被設定。分開設定和清除功能消除了讀取-修改-寫入操作的需要。
  • 輸出清除暫存器用於清除 GPIO 引腳。CLR{n} 欄位定義了要清除的相應 GPIO 引腳,向該欄位寫入 “0” 沒有任何效果。如果 GPIO 引腳被用作輸入(預設情況下),則 CLR{n} 欄位中的值將被忽略。但是,如果引腳隨後被定義為輸出,則該位將根據最後的設定/清除操作被設定。

LAB 5.2 裝置樹描述

正如您在第 5 章的 Pin Controller 一節中已經看到的那樣,引腳控制器允許處理器在幾個功能區塊之間分享一個 PAD。透過多路復用 PAD 輸入/輸出訊號來實作分享。由於不同的模組需要不同的 PAD 設定(例如,上拉,保持器),引腳控制器還控制 PAD 設定引數。受引腳組態影響的硬體模組被稱為「客戶端裝置」。每個客戶端裝置必須像其他硬體模組一樣,在裝置樹中表示為一個節點。要使客戶端裝置正常工作,某些引腳控制器必須設定特定的引腳組態。每個引腳控制器必須在裝置樹中表示為一個節點。

BCM2837 SoC 的引腳控制器

BCM2837 SoC 的引腳控制器在核心原始碼樹的 arch/arm/boot/dts/bcm283x.dtsi 中宣告,如下所示:

gpio: gpio@7e200000 {
    compatible = "brcm,bcm2835-gpio";
    reg = <0x7e200000 0xb4>;
    interrupts = <2 17>, <2 18>, <2 19>, <2 20>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;

    dpi_gpio0: dpi_gpio0 {
        brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27>;
        brcm,function = <BCM2835_FSEL_ALT2>;
    };

    emmc_gpio22: emmc_gpio22 {
        brcm,pins = <22 23 24 25 26 27>;
        brcm,function = <BCM2835_FSEL_ALT3>;
    };

    // ...
};

引腳控制器屬性

  • brcm,pins:一個單元格陣列,每個單元格包含一個引腳的 ID。有效的 ID 是整數 GPIO ID;0==GPIO0,1==GPIO1,…,53==GPIO53。
  • brcm,function:整數,包含要多路復用到引腳的功能:
    • 0:GPIO 輸入
    • 1:GPIO 輸出
    • 其他值對應不同的替代功能
  • brcm,pull:整數,表示要應用於引腳的上拉/下拉:
    • 0:無
    • 1:下拉
    • 2:上拉

LAB 5.2 操作與實作

要控制 LED,需要使用 BCM2837 SoC 的三個引腳,並在裝置樹中將它們多路復用為 GPIO。首先,需要在裝置樹中建立一個節點來描述這些引腳的組態。然後,在驅動程式中使用 copy_to_user()copy_from_user() 等函式來交換內核和使用者空間之間的字元陣列,從而實作對 LED 的控制。

連線硬體

  • 將 Raspberry Pi 的 GPIO27 引腳連線到 Color click™ 的 RD 引腳。
  • 將 GPIO22 引腳連線到 GR 引腳。
  • 將 GPIO26 引腳連線到 BL 引腳。
  • 在兩個板子之間連線 5V 電源和 GND。

程式碼範例

// 定義 GPIO 引腳
#define GPIO27 27
#define GPIO22 22
#define GPIO26 26

// 初始化 GPIO 引腳
void init_gpio(void) {
    // 將 GPIO27、GPIO22 和 GPIO26 設定為輸出模式
    gpio_set_function(GPIO27, BCM2835_FSEL_OUTP);
    gpio_set_function(GPIO22, BCM2835_FSEL_OUTP);
    gpio_set_function(GPIO26, BCM2835_FSEL_OUTP);
}

// 控制 LED 的函式
void control_led(int led, int state) {
    switch (led) {
        case 0: // 紅色 LED
            if (state) {
                gpio_write(GPIO27, HIGH);
            } else {
                gpio_write(GPIO27, LOW);
            }
            break;
        case 1: // 綠色 LED
            if (state) {
                gpio_write(GPIO22, HIGH);
            } else {
                gpio_write(GPIO22, LOW);
            }
            break;
        case 2: // 藍色 LED
            if (state) {
                gpio_write(GPIO26, HIGH);
            } else {
                gpio_write(GPIO26, LOW);
            }
            break;
        default:
            break;
    }
}

#### 程式碼解說:
1. 首先定義了要用於控制 LED  GPIO 引腳編號,分別是 GPIO27GPIO22  GPIO26
2. `init_gpio` 函式用於初始化這些 GPIO 引腳,將它們設定為輸出模式,以便控制 LED
3. `control_led` 函式根據傳入的引數控制對應的 LED,可以開啟或關閉指定的 LED

#### Plantuml 圖表說明
```plantuml
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title Plantuml 圖表說明

rectangle "GPIO27" as node1
rectangle "GPIO22" as node2
rectangle "GPIO26" as node3
rectangle "5V" as node4
rectangle "GND" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

此圖示展示了 Raspberry Pi 與 Color click™ 板之間的連線方式,包括 GPIO 引腳和電源連線。

平台驅動程式章節 5:RGB LED 平台裝置驅動程式開發

在 Raspberry Pi 3 Model B 上開發 RGB LED 平台驅動程式,需要對 Device Tree 和 GPIO 控制有深入的瞭解。首先,修改 bcm2710-rpi-3-b.dts 檔案以新增必要的 pin 組態節點和裝置節點。

Device Tree 修改

gpio 節點下新增 led_pins pin 組態節點,並在 soc 節點下新增 ledredledgreenledblue 裝置節點。

&gpio {
    [...]
    led_pins: led_pins {
        brcm,pins = <27 22 26>;
        brcm,function = <1>; /* 輸出 */
        brcm,pull = <1 1 1>; /* 下拉 */
    };
};

&soc {
    [...]
    ledred {
        compatible = "arrow,RGBleds";
        label = "ledred";
        pinctrl-0 = <&led_pins>;
    };
    ledgreen {
        compatible = "arrow,RGBleds";
        label = "ledgreen";
    };
    ledblue {
        compatible = "arrow,RGBleds";
        label = "ledblue";
    };
    [...]
};

RGB LED 平台裝置驅動程式碼解析

1. 引入必要的標頭檔案

#include <linux/module.h>
#include <linux/fs.h> /* struct file_operations */
#include <linux/platform_device.h> /* platform_driver_register(), platform_set_drvdata() */
#include <linux/io.h> /* devm_ioremap(), iowrite32() */
#include <linux/of.h> /* of_property_read_string() */
#include <linux/uaccess.h> /* copy_from_user(), copy_to_user() */
#include <linux/miscdevice.h> /* misc_register() */

2. 定義 GPIO 掩碼

定義用於組態 GPIO 暫存器的掩碼。

#define GPIO_27 27
#define GPIO_22 22
#define GPIO_26 26

#define GPIO_27_INDEX (1 << (GPIO_27 % 32))
#define GPIO_22_INDEX (1 << (GPIO_22 % 32))
#define GPIO_26_INDEX (1 << (GPIO_26 % 32))

#define GPIO_27_FUNC (1 << ((GPIO_27 % 10) * 3))
#define GPIO_22_FUNC (1 << ((GPIO_22 % 10) * 3))
#define GPIO_26_FUNC (1 << ((GPIO_26 % 10) * 3))

#define FSEL_27_MASK (0b111 << ((GPIO_27 % 10) * 3))
#define FSEL_22_MASK (0b111 << ((GPIO_22 % 10) * 3))
#define FSEL_26_MASK (0b111 << ((GPIO_26 % 10) * 3))

#define GPIO_SET_FUNCTION_LEDS (GPIO_27_FUNC | GPIO_22_FUNC | GPIO_26_FUNC)
#define GPIO_MASK_ALL_LEDS (FSEL_27_MASK | FSEL_22_MASK | FSEL_26_MASK)
#define GPIO_SET_ALL_LEDS (GPIO_27_INDEX | GPIO_22_INDEX | GPIO_26_INDEX)

#### 內容解密:

  • GPIO 引腳定義:定義了控制 RGB LED 的 GPIO 引腳編號(27、22、26)。
  • 掩碼定義:定義了用於設定和清除每個 LED 的掩碼,以及選擇輸出功能的掩碼。
  • 功能選擇掩碼:定義了用於選擇 GPIO 引腳功能的掩碼,以控制 RGB LED。

3. 宣告物理 I/O 暫存器地址

#define BCM2710_PERI_BASE 0x3F000000
#define GPIO_BASE (BCM2710_PERI_BASE + 0x200000)
#define GPFSEL2 (GPIO_BASE + 0x08)
#define GPSET0 (GPIO_BASE + 0x1C)
#define GPCLR0 (GPIO_BASE + 0x28)

#### 內容解密:

  • 基地址定義:定義了 BCM2710 的外設基地址和 GPIO 基地址。
  • 暫存器地址:定義了用於控制 GPIO 的暫存器地址,包括功能選擇暫存器(GPFSEL2)、設定暫存器(GPSET0)和清除暫存器(GPCLR0)。

4. 宣告虛擬地址指標

static void __iomem *GPFSEL2_V;
static void __iomem *GPSET0_V;
static void __iomem *GPCLR0_V;

#### 內容解密:

  • 虛擬地址指標:聲明瞭用於存放暫存器虛擬地址的指標,透過 ioremap() 函式對映物理地址到虛擬地址空間。

5. 定義私有資料結構

struct led_dev {
    struct miscdevice led_misc_device; /* 為每個 LED 分配裝置 */
    u32 led_mask; /* 不同 LED 的掩碼(紅、綠、藍) */
    const char *led_name; /* 儲存 "label" 字串 */
    char led_value[8];
};

#### 內容解密:

  • led_dev 結構:定義了一個私有資料結構,用於存放每個 LED 裝置的相關資訊,包括雜項裝置結構、LED 掩碼、名稱和值。

6. probe() 函式中的私有結構例項化

struct led_dev *led_device;
led_device = devm_kzalloc(&pdev->dev, sizeof(struct led_dev), GFP_KERNEL);

#### 內容解密:

  • 記憶體分配:在 probe() 函式中為每個新的探測到的裝置分配記憶體,用於存放私有資料結構。

7. 初始化虛擬地址

led_init() 例程中,使用 ioremap() 取得暫存器地址的虛擬地址。

GPFSEL2_V = ioremap(GPFSEL2, sizeof(u32));
GPSET0_V = ioremap(GPSET0, sizeof(u32));
GPCLR0_V = ioremap(GPCLR0, sizeof(u32));

#### 內容解密:

  • 虛擬地址對映:透過 ioremap() 將物理暫存器地址對映到虛擬地址空間,以便內核可以存取這些暫存器。

8. 初始化雜項裝置結構

probe() 例程中初始化每個雜項裝置結構。

of_property_read_string(pdev->dev.of_node, "label", &led_device->led_name);
led_device->led_misc_device.minor = MISC_DYNAMIC_MINOR;
led_device->led_misc_device.name = led_device->led_name;
led_device->led_misc_device.fops = &led_fops;

#### 內容解密:

  • 讀取標籤屬性:使用 of_property_read_string() 從裝置樹節點中讀取 “label” 屬性。
  • 初始化雜項裝置:設定雜項裝置的次編號、名稱和檔案操作結構。

9. 定義檔案操作結構

static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .read = led_read,
    .write = led_write,
};

#### 內容解密:

  • 檔案操作結構:定義了一個檔案操作結構,用於指定當使用者開啟、關閉、讀取和寫入字元檔案時要呼叫的驅動程式函式。