返回文章列表

SPI 客戶端與加速度計驅動程式開發

本文深入講解了在 Linux 系統下開發 SPI 客戶端驅動程式以及 ADXL345 加速度計驅動程式的步驟,包含 SPI 客戶端驅動程式的註冊、裝置樹的組態、驅動程式碼的撰寫以及中斷處理等關鍵環節。文章以 ADXL345 加速度計為例,詳細說明瞭如何設定 SPI 控制器、組態裝置樹節點、定義 SPI

嵌入式系統 驅動程式開發

SPI 驅動程式開發涉及與硬體和核心互動,需要仔細設定才能確保裝置正常運作。首先,驅動程式必須正確註冊到 SPI 子系統,並與裝置樹中的硬體描述匹配。透過相容性字串,核心可以找到正確的驅動程式來控制硬體。在裝置樹中,必須定義 SPI 控制器和連線的裝置,包含必要的引數,例如時脈速度、中斷設定和晶片選擇。驅動程式碼本身需要處理 SPI 交易、讀寫裝置暫存器,以及管理中斷。以 ADXL345 加速度計為例,驅動程式需要讀取加速度資料並將其轉換為輸入事件,以便應用程式使用。

SPI 客戶端驅動程式開發

在這一章中,我們將探討如何開發 SPI 客戶端驅動程式,以控制加速計和混合訊號 I/O 裝置。首先,我們將介紹設定 SPI 客戶端驅動程式的主要步驟。

SPI 客戶端驅動程式註冊

SPI 子系統定義了一個 spi_driver 結構,該結構繼承自 device_driver 結構。每個 SPI 客戶端驅動程式都必須例項化和註冊到 SPI 匯流排核心。通常,您將實作一個單一的驅動程式結構,並從中例項化所有客戶端。驅動程式結構包含一般的存取例程,應初始化為零,除了您提供的欄位。

範例:ADXL345 加速計裝置的 spi_driver 定義

static struct spi_driver adxl345_driver = {
    .driver = {
        .name = "adxl345",
        .owner = THIS_MODULE,
        .of_match_table = adxl345_dt_ids,
    },
    .probe = adxl345_spi_probe,
    .remove = adxl345_spi_remove,
    .id_table = adxl345_id,
};
module_spi_driver(adxl345_driver);

module_spi_driver() 巨集用於註冊/登出驅動程式。

相容性字串定義

在您的 Linux SPI 驅動程式中,您將建立一個 of_device_id 結構陣列,指定相容性字串,該字串與裝置樹(DT)裝置節點的 compatible 屬性值相同。

static const struct of_device_id adxl345_dt_ids[] = {
    { .compatible = "arrow,adxl345", },
    { }
};
MODULE_DEVICE_TABLE(of, adxl345_dt_ids);

驅動程式的 probe() 函式在 of_device_id 條目中的 compatible 欄位與 DT 裝置節點的 compatible 屬性匹配時被呼叫。probe() 函式負責使用從匹配的 DT 裝置節點取得的組態值初始化裝置,並將裝置註冊到適當的核心框架。

在裝置樹中宣告 SPI 裝置

在裝置樹中,SPI 控制器通常在描述處理器的 .dtsi 檔案中宣告。SPI 控制器的狀態通常設定為 "disabled"

BCM2837 SPI 控制器的宣告範例

spi: spi@7e204000 {
    compatible = "brcm,bcm2835-spi";
    reg = <0x7e204000 0x200>;
    interrupts = <2 22>;
    clocks = <&clocks BCM2835_CLOCK_VPU>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};

所需的屬性包括:

  • compatible:與 spi-bcm2835.c 驅動程式的相容值匹配。
  • reg:裝置的暫存器集偏移和長度。
  • interrupts:中斷訊號的列表。
  • clocks:輸入時鐘的列表。

SPI 裝置的宣告

SPI 裝置在板級/平台級別(arch/arm/boot/dts/bcm2710-rpi-3-b.dts)宣告為 SPI 主控制器的子節點。所需的和可選的屬性包括:

  • reg:裝置的晶片選擇位址。
  • compatible:SPI 裝置的名稱,應與驅動程式的 of_device_id 相容字串之一匹配。
  • spi-max-frequency:裝置的最大 SPI 時脈速度(Hz)。

範例:SPI 裝置宣告

&spi {
    cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
    status = "okay";
    
    adxl345@0 {
        compatible = "arrow,adxl345";
        reg = <0>;
        spi-max-frequency = <1000000>;
    };
};

程式碼解析

static const struct spi_device_id adxl345_id[] = {
    { .name = "adxl345", },
    { }
};
MODULE_DEVICE_TABLE(spi, adxl345_id);

此程式碼定義了一個名為 adxl345_id 的陣列,用於儲存 SPI 裝置的 ID。

內容解密:

此段程式碼的作用是定義 SPI 裝置的 ID,並將其註冊到核心中。透過定義相容性字串,驅動程式可以正確地匹配和初始化裝置。

SPI 加速度計輸入裝置驅動程式開發

在本次實驗中,我們將開發第一個SPI裝置的驅動程式,用於管理連線到SPI匯流排的加速度計裝置。我們將使用與前一個實驗相同的ADXL345 Accel click mikroBUS™配件板。

硬體連線描述

以下是連線描述:

  • 將Raspberry Pi GPIO8(CE0)連線到ADXL345 CS(CS)
  • 將Raspberry Pi SCLK連線到ADXL345 SCL(SCK)
  • 將Raspberry Pi MISO連線到ADXL345 SDO(MISO)
  • 將Raspberry Pi MOSI連線到ADXL345 SDI(MOSI)
  • 將Raspberry Pi GPIO23連線到ADXL345 INT1(INT)

裝置樹描述

我們需要在bcm2710-rpi-3-b.dts裝置樹檔案中新增adxl345@0子節點,在spi0控制器主節點下。pinctrl-0屬性指向accel_int_pin引腳組態節點,其中GPIO23被複用為GPIO訊號。int-gpios屬性使GPIO23可供驅動程式使用,以便設定引腳方向為輸入並取得與該引腳相關聯的Linux IRQ編號。reg屬性提供CS編號。

&spi0 {
    pinctrl-names = "default";
    pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
    cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
    Accel: ADXL345@0 {
        compatible = "arrow,adxl345";
        spi-max-frequency = <5000000>;
        spi-cpol;
        spi-cpha;
        reg = <0>;
        pinctrl-0 = <&accel_int_pin>;
        int-gpios = <&gpio 23 0>;
        interrupts = <23 1>;
        interrupt-parent = <&gpio>;
    };
};

accel_int_pin: accel_int_pin {
    brcm,pins = <23>;
    brcm,function = <0>; /* Input */
    brcm,pull = <0>; /* none */
};

內容解密:

此段裝置樹組態定義了SPI介面下的ADXL345加速度計裝置。其中:

  • compatible = "arrow,adxl345" 指定了裝置的相容性,使其與驅動程式匹配。
  • spi-max-frequency = <5000000> 設定SPI介面的最大頻率為5 MHz。
  • spi-cpolspi-cpha 設定SPI的時脈極性和相位。
  • reg = <0> 指定了裝置的CS編號為0。
  • int-gpios = <&gpio 23 0> 設定GPIO23為中斷輸入引腳。
  • interrupts = <23 1>interrupt-parent = <&gpio> 定義了中斷組態。

驅動程式碼描述

以下是驅動程式的主要程式碼段:

標頭檔引入

#include <linux/module.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>

SPI交易指令定義

#define ADXL345_CMD_MULTB (1 << 6)
#define ADXL345_CMD_READ (1 << 7)
#define ADXL345_WRITECMD(reg) (reg & 0x3F)
#define ADXL345_READCMD(reg) (ADXL345_CMD_READ | (reg & 0x3F))
#define ADXL345_READMB_CMD(reg) (ADXL345_CMD_READ | ADXL345_CMD_MULTB | (reg & 0x3F))

內容解密:

這些巨集定義了用於與ADXL345裝置進行SPI交易的命令位元組。其中:

  • ADXL345_CMD_MULTB 設定多位元組讀取模式。
  • ADXL345_CMD_READ 設定讀取模式。
  • ADXL345_WRITECMD(reg)ADXL345_READCMD(reg)ADXL345_READMB_CMD(reg) 分別用於生成寫入、讀取和多位元組讀取命令。

ADXL345暫存器定義

#define DEVID 0x00 /* R Device ID */
#define THRESH_TAP 0x1D /* R/W Tap threshold */
#define DUR 0x21 /* R/W Tap duration */
#define TAP_AXES 0x2A /* R/W Axis control for tap/double tap */
#define ACT_TAP_STATUS 0x2B /* R Source of tap/double tap */
#define BW_RATE 0x2C /* R/W Data rate and power mode control */
#define POWER_CTL 0x2D /* R/W Power saving features control */
#define INT_ENABLE 0x2E /* R/W Interrupt enable control */
#define INT_MAP 0x2F /* R/W Interrupt mapping control */
#define INT_SOURCE 0x30 /* R Source of interrupts */
#define DATA_FORMAT 0x31 /* R/W Data format control */
#define DATAX0 0x32 /* R X-Axis Data 0 */
#define DATAX1 0x33 /* R X-Axis Data 1 */
#define DATAY0 0x34 /* R Y-Axis Data 0 */
#define DATAY1 0x35 /* R Y-Axis Data 1 */
#define DATAZ0 0x36 /* R Z-Axis Data 0 */
#define DATAZ1 0x37 /* R Z-Axis Data 1 */
#define FIFO_CTL 0x38 /* R/W FIFO control */

內容解密:

這些巨集定義了ADXL345裝置的暫存器位址,用於讀取和寫入裝置的設定和資料。其中包括裝置ID、點選閾值、資料率控制、中斷控制等。

ADXL345 加速度計驅動程式開發

前言

本章節將探討如何開發 ADXL345 加速度計的 Linux 驅動程式。ADXL345 是一款高解析度的加速度計,廣泛應用於各種裝置中,如手機、遊戲控制器和工業裝置等。為了使該裝置正常運作,我們需要編寫一個 Linux 驅動程式來與其進行互動。

驅動程式結構設計

1. 定義必要的巨集和資料結構

首先,我們需要定義一些巨集和資料結構來描述 ADXL345 的暫存器和操作。

#define FULL_RES (1 << 3)
/* FIFO_CTL Bits */
#define FIFO_MODE(x) (((x) & 0x3) << 6)
#define FIFO_BYPASS 0
#define FIFO_FIFO 1
#define FIFO_STREAM 2
#define SAMPLES(x) ((x) & 0x1F)

/* 定義一個結構來儲存 SPI 匯流排操作 */
struct adxl345_bus_ops {
    u16 bustype;
    int (*read)(struct device *, unsigned char);
    int (*read_block)(struct device *, unsigned char, int, void *);
    int (*write)(struct device *, unsigned char, unsigned char);
};

struct axis_triple {
    int x;
    int y;
    int z;
};

/* 定義一個結構來儲存特定驅動程式的資訊 */
struct adxl345_platform_data {
    u8 low_power_mode;
    u8 tap_threshold;
    u8 tap_duration;
    u8 tap_axis_control;
    u8 data_rate;
    u8 data_range;
    u32 ev_code_tap[3];
    u8 fifo_mode;
    u8 watermark;
};

2. 初始化 adxl345_bus_ops 結構

接下來,我們需要初始化 adxl345_bus_ops 結構,並將其傳遞給 adxl345_probe() 函式作為引數。

static const struct adxl345_bus_ops adxl345_spi_bops = {
    .bustype = BUS_SPI,
    .write = adxl345_spi_write,
    .read = adxl345_spi_read,
    .read_block = adxl345_spi_read_block,
};

static int adxl345_spi_probe(struct spi_device *spi)
{
    /* 建立一個私有結構 */
    struct adxl345 *ac;
    
    /* 初始化驅動程式並傳回初始化的私有結構 */
    ac = adxl345_probe(&spi->dev, &adxl345_spi_bops);
    
    /* 將 SPI 裝置附加到私有結構 */
    spi_set_drvdata(spi, ac);
    return 0;
}

#### 內容解密:

此段程式碼主要完成了以下工作:

  1. 定義必要的巨集:定義了與 ADXL345 相關的巨集,例如 FULL_RESFIFO_MODE 等,用於控制暫存器。
  2. 定義資料結構:定義了 adxl345_bus_opsadxl345_platform_data 結構,分別用於描述 SPI 匯流排操作和特定驅動程式的資訊。
  3. 初始化 adxl345_bus_ops 結構:初始化了 adxl345_spi_bops 結構,並將其傳遞給 adxl345_probe() 函式。

驅動程式初始化流程

1. adxl345_probe() 函式

adxl345_probe() 函式是驅動程式初始化的核心部分,它負責建立輸入裝置、設定初始暫存器值和註冊中斷處理程式。

struct adxl345 *adxl345_probe(struct device *dev, const struct adxl345_bus_ops *bops)
{
    /* 宣告私有結構 */
    struct adxl345 *ac;
    
    /* 建立輸入裝置 */
    struct input_dev *input_dev;
    
    /* 組態輸入裝置 */
    input_dev->name = "ADXL345 accelerometer";
    input_dev->phys = ac->phys;
    input_dev->dev.parent = dev;
    
    /* 設定事件型別和事件程式碼 */
    __set_bit(EV_KEY, input_dev->evbit);
    __set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
    
    /* 請求 threaded 中斷 */
    devm_request_threaded_irq(input_dev->dev.parent, ac->irq, NULL, adxl345_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, dev_name(dev), ac);
    
    /* 初始化 ADXL345 暫存器 */
    AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold);
    AC_WRITE(ac, DUR, pdata->tap_duration);
    
    return ac;
}

#### 內容解密:

此段程式碼主要完成了以下工作:

  1. 建立輸入裝置:使用 devm_input_allocate_device() 建立了一個輸入裝置,並對其進行了組態。
  2. 設定事件型別和事件程式碼:設定了輸入裝置支援的事件型別和事件程式碼。
  3. 請求 threaded 中斷:使用 devm_request_threaded_irq() 請求了一個 threaded 中斷,用於處理單次點選中斷。
  4. 初始化 ADXL345 暫存器:使用 AC_WRITE() 對 ADXL345 的暫存器進行了初始化,設定了點選閾值和持續時間等引數。

中斷處理程式

1. 編寫 threaded 中斷處理程式

threaded 中斷處理程式用於處理單次點選中斷。

static irqreturn_t adxl345_irq(int irq, void *dev_id)
{
    /* 處理中斷 */
    return IRQ_HANDLED;
}

#### 內容解密:

此段程式碼主要完成了以下工作:

  1. 處理中斷:threaded 中斷處理程式用於處理單次點選中斷,並傳回 IRQ_HANDLED 表示中斷已被處理。