openharmony GPIO ドライバー開発

openharmony GPIO ドライバー開発


GPIOの基本

GPIO の基本 - 概念

GPIO: 入力または出力の高レベルと低レベル、高低レベルと波形の任意の組み合わせは、プロトコル要件なしで、LED、ボタン、およびその他の周辺機器を駆動できます。波形の数、組み合わせ、および波形の持続時間は、I2C、SPI、UART、PWM などの対応するプロトコルに従います。

  • PWM
    ここに画像の説明を挿入
  • 2c
    ここに画像の説明を挿入

GPIO の基礎 - IO 多重化

チップはできるだけ多くの機能と外部インターフェイスを提供する必要がありますが、チップのピン (Pin) の数は制限されており、多くの IO ピンを使用して複数の機能を持たせ、同じピンの時分割多重化を実現しています。ソフトウェア構成を通じて。HI3516DV300 を例にとると、合計で 92 個の GPIO があり、GPIO3_6 の多重化の関係は次のとおりです。

ここに画像の説明を挿入
ここに画像の説明を挿入

すべての IO ピンを GPIOI として使用できるわけではありません。一部のピンは外部メモリ チップなどの専用 IO としてのみ使用でき、一部のピンは GPIO としてのみ使用できます。

GPIO の基本 - GPIO のグループ化と番号付け

多数の GPIO はグループ化して管理されるため、各 GPIO にはグループ番号とグループ番号 (グループ オフセット、オフセット) があり、GPIO グループの数とグループ内の GPIO ピンの数の定義は、チップごとに異なります。

例: RK3399/RK3399Pro は合計 122 の 5 つの GPIO グループ (GPIO0~GPIO4) を提供し、すべての GPIO を割り込みとして使用でき、GPIO0/GPIO1 をシステム ウェイクアップ ピンとして使用でき、すべての GPIO をプルアップまたはプルアップとして構成できます。ソフトウェアによるプルダウン、すべての GPIO はデフォルトで入力され、GPIO の駆動能力はソフトウェアで設定できます。GPIO4c0など、回路図上のGPIOとdts内のGPIOの対応関係については、対応するdtsは「gpio4 16」となります。GPIO4A には 8 つのピンがあり、GPIO4B にも 8 つのピンがあるため、c0 ポートは 16、c1 ポートは 17 などです; GPIO の使用については、docs\Kernel\Pin- の「Rockchip Pin」を参照してください。 Ctrl\ディレクトリ -Ctrl 開発ガイド V1.0-20160725.pdf".

GPIO の基本 - ユーザー モード テスト

  • GPIO ピン番号とレベル ステータスを決定する
  • ピンを GPIO 機能として多重化 (デフォルトはリセット後の GPIO)
  • たとえば、GPIO3_6 ピンは GPIO30 として計算され、echo 30 > export を実行できます。
  • このとき、gpio の下に gpio30 ディレクトリが追加され、このディレクトリで、方向 (in、out)、レベル値 (1/0) などの GPIO30 ピンを制御する操作を実行できます。

HDF フレームワークの GPIO ドライバー

HDF フレームワーク下の GPIO ドライバー - ケースの説明 (例として HI3516DV300 プラットフォームを取り上げ、コードを提供)

  • GPIO0_6 外部 LED、出力ローレベルで LED を点灯、ハイレベルで LED をオフ
  • GPIO3_6 は外部 KEY に接続され、割り込みとして構成され、トリガー モードはダブルエッジ トリガーです。
  • ユーザーモードプログラムはドライバーに命令を送り、LED のオン/オフ動作を実現し、ドライバープログラムは LED の対応するピンのレベルステータスをユーザーモードに返します。正式なパラメータとイベントを使用してアプリケーション プログラムを作成します。
  • キーは外部割り込みをトリガーし、割り込みサービス ルーチンは LED をオンまたはオフにできます。

HDF フレームワークの下の GPIO ドライバー - アプリケーション バインディング サービス

  • Linux システム上のアプリケーション プログラムは、open システム コールによって /dev/ ディレクトリ内のデバイス ノードを開き、デバイス ファイル ハンドルを取得し、このファイル ハンドルを介して read/write/ioctl およびその他のシステム コール インターフェイスを呼び出すことで動作を実現します。デバイスの
  • HDF フレームワークの下で、ユーザー モード アプリケーションは、特定のインターフェイスを呼び出してドライバーが提供するサービスを取得し、アプリケーションとドライバーのバインディングを実現し、アプリケーションがサービスを取得した後、ドライバーとドライバーの操作を実現できます。サービスに基づくデバイス
//应用程序绑定服务
struct HdfIoService *serv = HdfIoServiceBind("GPIO_TEST_SERVICE");
if(serv == NULL){
    HDF_LOGE("fail to get service %s", "GPIO_TEST_SERVICE");
    return HDF_FAILURE;
}

gpio_drv_test_host::host{
    hostName = "gpio_drv_test";
    priority = 100;
    device_test_driver::device{
        device0::deviceNode{
            policy = 2;
            priority = 100;
            preload = 0;
            permission = 0664;
            moduleName = "GPIO_TEST_DRIVER";
            serviceName = "GPIO_TEST_SERVICE";
        }
    }
}

HDF フレームワーク下の GPIO ドライバー - ユーザー モード HdfSBuf

  • アプリケーションがドライバー サービスを取得すると、そのサービスを使用してドライバーと通信できます。The Carrier of Communication Data is HdfSBuf, and the application can call HdfSBufObtainDefaultSize to get a memory heap space with the default size of 256 bytes. HDF は、このメモリ空間を循環キューとして編成します。アプリケーションはこのキューにデータを書き込み、ドライバーはキューからデータを読み取ることができ、その逆も可能です。循環キューであるため、読み取りデータの順序、読み取りデータのタイプが書き込みデータと一致していることを確認し、先入れ先出しの原則に従う必要があります。
//用户态申请空间
struct HdfSBuf *data = HdfSBufObtainDefaultSize();
if(data == NULL){
    printf("fail to obtain sbuf data\n");
    ret = HDF_DEV_ERR_NO_MEMORY;
    goto out;
}

//用户态写数据
if(!HdfSbufWriteString(data, eventData)){
    printf("fail to write data\n");
    goto HDF_FAILURE;
}

//用户态读数据
char *replyData = HdfSbufReadString(data);
if(replyData == NULL){
    printf("fail to read data\n");
    goto HDF_FAILURE;
}

HDF フレームワーク下の GPIO ドライバー - アプリケーションとドライバーの通信 1

  • アプリケーションは、ドライバーとのデータ対話用に 2 つのバッファー (循環キュー) を適用します。
  • アプリケーションプログラムは文字列型のデータをデータバッファに書き込みます
  • アプリケーションは、サービスの Dispatch 関数を介してドライバーにデータを送信し、ドライバーの Dispatch 関数が実行されるようにします。
  • ユーザー プログラムは、ドライバーから返されたデータを読み取ります。最初に String 型を取得し、次に uint16 型を取得します。
struct HdfSBuf  *data = HdfSBufObtainDefaultSize();
struct HdfSBuf *reply = HdfSBufObtainDefaultSize();

if(id == LED_ON){
    SbufWriteString(data, eventData);
    ret = ser->dispatcher->Dispatch(&serv->object, LED_ON, data, reply);
    string = HdfSbufReadString(data)l;
    HdfSbufReadUint16(reply, &pin_val);
}

HDF ドライバー フレームワークでの GPIO ドライバー - アプリケーションとドライバーの通信 2

アプリケーションは、取得したサービスを通じてイベントリスナーを登録し、ドライバーがイベント送信関数 HdfDeviceSendEvent を呼び出すと、イベントリスナーのコールバック関数の実行をトリガーし、ドライバーが送信したデータをコールバック関数で受信します。

static struct HdfDevEventlistener listener = {
    .callBack = OnDevEventReceived,
    .priv = "Service0"
};

//应用程序注册服务
if(HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCESS){
    HDF_LOGE("fail to register event listener");
    return HDF_FAILURE;
}
  • ドライバーは、イベント送信インターフェイス HdfDeviceSendEvent を呼び出して、ユーザー モード プログラムにイベントを送信し、ユーザー モード イベント リスナーの実行をトリガーします。
  • アプリケーション プログラムは、ドライバーがバッファーに書き込む順序でデータを読み取ります: 最初に文字列型のデータを読み取り、次に uint16 型のデータを読み取ります。
static int32_t OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *reply)
{
    uint16_t pin_val = 0;
    
    const char *string  = HdfSbufReadString(reply);
    if(string == NULL){
        printf("fail to read string in event reply\n");
        return HDF_FAILURE;
    }

    if(!HdfSbufReadUint16(reply, &pin_val){
        printf("fail to read uint16 in event reply\n");
        return HDF_FAILURE;
    }

    printf("%s: event reply received : id %u, string %s, pin val %u\n", (char *)priv, id, string, pin_val);
    return HDF_SUCCESS;
}

HDF フレームワーク下の GPIO ドライバー - ドライバー エントリ

  • ドライバー エントリ g_GPIODriverEntry は、3 つの関数とドライバー モジュール名 moduleName を定義します。
  • 新しいノード gpio_drv_test_host が device_info.hcs に追加されます。このノードには、moduleName という名前の属性が含まれています。
  • 2 つの moduleNames の値が等しい場合は、hcs とドライバーが一致したことを意味し、ドライバー エントリの Bind 関数と Init 関数を呼び出します。

このプロセスは、dts および Linux ドライバーの互換フィールドに似ており、2 つが一致すると、ドライバーでプローブ関数を呼び出します。

struct HdfDriverEntry g_GPIODriverEntry = {
    .moduleVersion = 1,
    .moduleName = "GPIO_TEST_DRIVER",
    .Bind = HdfGPIODriverBind,
    .Init = HdfGPIODriverInit,
    .Release = HdfGPIODriverRelease,
}

HDF_INIT(g_GPIODriverEntry);

//device_info.hcs
gpio_drv_test_host :: host{
    hostName = "gpio_drv_test";
    priority = 100;
    device_test_driver :: device{
        device0 :: deviceNode{
            policy = 2;
            priority = 100;
            preload = 0;
            permission = 0664;
            moduleName = "GPIO_TEST_DRIVER";
            serviceName = "GPIO_TEST_SERVICE";
        }
    }
}
int32_t HdfGPIODriverBind(strutc HdfDeviceObject *deviceObject)
{
    if(deviceObject == NULL){
        return HDF_FAILURE;
    }
    static struct IDeviceIoService gpioTestService = {
        .Dispatch = HdfGPIODriverDispatch,
    };
    
    deviceObject->service = &gpioTestService;
    HDF_LOGE("GPIO driver bind success");
    return HDF_SUCCESS;
}
* Bind 函数中定义了一个结构体,它是一个名为 gpioTestService 的服务,需要实现 Dispatch 成员函数,该函数用来接收用户态程序发送到内核态的消息,实现用户态和内核态之间的通信。
* 驱动中定义了一个服务,即驱动可以对外提供服务,应用程序可以使用该服务。

HDF フレームワークの下の GPIO ドライバー - ドライバーはデータを送受信します

  • アプリケーション プログラムは、最初にサービスを取得し、サービスで定義された Dispatch インターフェイスを呼び出して関数の実行をトリガーし、パラメーター id によってユーザー プログラムからの命令を区別します。

  • ユーザーステートから送られてきた文字列型のデータを読み込み、LEDの点灯・消灯の操作を行う

  • 文字列と LED 対応ピンのレベル ステータスをユーザー プログラムに返します ユーザー モード プログラムは、FIFO の原則に従って、最初の文字列型データを最初に読み取り、次に uint16 型データを読み取る必要があります。

  • ドライバーがデータをユーザー プログラムに返す 2 つの方法に注意してください。

      * 返回值和发送事件,针对不同的方式
      * 用户态获取驱动数据的方式也不同
    
int32_tHdfGPIODriverDispatch(struct HdfDeviceIoClient *client, int32_t id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
    //驱动程序通过接口读取来自用户态的数据,并向用户态返回数据

    if(id == LED_ON){
        const char *readData = HdfSbufReadString(data);
        if(readData != NULL){
            HDF_LOGE("%s: read data is : %s", __func__, readData);
            led_on();
            GpioRead(LED_PIN, &led_pin_val);
        }
        HdfSbufWriteString(reply, "ledon");
        HdfSbufWriteUint16(reply, led_pin_val);
    }
    else if(id == LED_OFF){
        const char *readData = HdfSbufReadString(data);
        if(readData != NULL){
            HDF_LOGE("%s: read data is :%s", __func__, readData);
            led_off();
            GpioRead(LED_PIN, &led_pin_val);
        }
        HdfSbufWriteString(reply, "ledoff");
        HdfSbufWriteUint16(reply, led_pin_val);
        if(HdfDeviceSend(client->device, id, reply) != HDF_SUCCESS)
            return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

HDF フレームワーク下の GPIO ドライバー - GPIO 構成

機能分類 インターフェイス名 説明
GPIO 読み書き GpioRead 端子レベル値の読み取り
GpioWrite 書き込み端子レベル値
GPIO 構成 GpioSetDir ピンの向きを設定
GpioGetDir ピンの向きを取得
GPIO 割り込み設定 GpioSetIrq 端子に対応する割り込みサービス機能を設定
GpioUnSetIrq 端子に対応する割り込みサービス関数をキャンセル
GpioEnableIrq ピン割り込みを有効にする
GpioDisableIrq ピン割り込みを無効にする
#define LED_PIN    6// GPIO0_6,    0*8+6 = 6
#define IRQ_PIN    30//GPIO3_6    3*8+6 = 30

static int32_t GpioSetup()
{
    //驱动程序,配置 GPIO
    if(GpioSetDir(LED_PIN, GPIO_DIR_OUT) != HDF_SUCCESS){
        HDF_LOGE("GPIOsetDir: LED_PIN failed\n");
        return HDF_FAILURE;
    }

    GpioSetDir(IRQ_PIN, GPIO_DIR_IN);
    GpioDisableIrq(IRQ_PIN);
    GpioSetIrq(IRQ_PIN, OSAL_IRQF_IRIGGER_RISING | OSAL_IRQF_TRIGGER_FALLING, gpio_test_irq, NULL);
    GpioEnableIrq(IRQ_PIN);
}

GpioSetup、Init()、または Dispatch を呼び出す場所は?

HDF フレームワーク下の GPIO ドライバー - GPIO 構成 (割り込み)

  • 割り込みトリガ モード
パラメータ 割り込みトリガ モード
OSAL_IRQF_TRIGGER_RISING 立ち上がりエッジトリガー
OSAL_IRQF_TRIGGER_FALLING 立ち下がりトリガー
OSAL_IRQF_TRIGGER_HIGH ハイレベルトリガー
OSAL_IRQF_TRIGGER_LOW 低レベルトリガー

ここに画像の説明を挿入

int32_t gpio_testr_irq(uint16_t gpio, void *data)
{
    //驱动,中断服务程序
    if(GpioDisableIrq(gpio) != HDF_SUCCESS){
        HDF_LOGE("%s: disable irq failed", __func__);
        return HDF_FAILURE;
    }

    GpioRead(IRQ_PIN, &irq_pin_val);
    if(irq_pin_val == 0)
        led_off();
    else
        led_on();

    GpioEnableIrq(gpio);
}

HDF フレームワーク下の GPIO ドライバー - アンチシェイクおよびフローティング

割り込みジッターは、多くの場合、GPIO 割り込みトリガー ソースとしてボタンを使用することによって引き起こされます. ボタンの機械的性質により、ジッターを根本的に排除することは困難です. ジッターの影響をシールドする必要があります. この技術はアンチシェイクと呼ばれます:

  • ハードウェア: プラットフォームは、GPIO バリ取り、構成可能な割り込みトリガー レベル、およびその他のテクノロジをサポートします。
  • ソフトウェア: レベルが安定するまで、割り込みサービス ルーチンで割り込みピンのレベル値を複数回読み取る

外部から GPIO ピンがハイまたはローにプルされていない状態をフローティング状態と呼びます. フローティング状態の GPIO は不安定です. プログラムが GPIO の対応する値を読み取るときに、頻繁にハイジャンプとロージャンプが発生する場合があります. 浮動ピンが外部割り込みとして使用される場合、割り込みが頻繁にトリガーされます. この状況を回避するには:

  • GPIO 外部回路が GND または VCC に明確に接続されている
  • プルアップまたはプルダウン抵抗を使用する

ここに画像の説明を挿入

HDF フレームワーク下の GPIO ドライバー - LED 制御

#define LED_PIN    6// GPIO0_6,    0*8+6 = 6
#define IRQ_PIN    30//GPIO3_6    3*8+6 = 30

//高电平熄灭LED
static int32_t led_off(void)
{
    if(GpioWrite(LED_PIN, 1) != HDF_SUCCESS){
        HDF_LOGE("GpioWrite: LED_PIN failed\n");
        return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

//低电平点亮 LED
static int32_t led_on(void)
{
    if(GpioWrite(LED_PIN, 0) != HDF_SUCCESS){
        HDF_LOGE("GpioWrite: LED_PIN failed\n");
        return HDF_FAILURE;
    }

    return HDF_SUCCESS;
}

HDF フレームワーク下の GPIO ドライバー - ドライバーのディレクトリと構造

  • ドライバーのソース ディレクトリ:drivers/adapter/khdf/linux/gpio_test_drv/
  • コンパイル ターゲットを前のディレクトリの Makefile に追加します。drivers/adapter/khdf/linux/Makefile
obj-$(CONFIG_DRIVERS_HDF)    += gpio_test_drv/
  • デバイス ノード定義を hcs 構成ファイルに追加します。vendor/hisilicon/Hi3516DV300/hdf_config/khdf/device_info/device_info.hcs

HDF フレームワークの下の GPIO ドライバー - アプリケーション ディレクトリ構造

  • サブディレクトリ examples/gpio_test_app/ を openharmony ソース コードのルート ディレクトリの下に作成します。ここで、examples はサブシステムであり、gpio_test_app はサブシステムのコンポーネントです。
  • 上記のディレクトリにアプリケーションのソースとビルド ファイルを作成します。
  • サンプル サブシステムと gpio_test_app コンポーネントを製品定義ファイル productdefine/common/products/Hi3516DV300.json に追加して、コンパイル済みにします。
  • out ディレクトリをクリアし、すべてのコードをコンパイルし、ドライバーをカーネルにコンパイルして、bin ディレクトリでプログラム gpio_test_app をテストします。

要約する

  • GPIO: 汎用 IO と専用 IO の違い、さまざまなプラットフォームでの GPIO のグループ化と番号付け、および GPIO の一般的なデバッグ方法
  • HDF ドライバー: GPIO インターフェース構成、読み取りおよび書き込み操作、割り込み、アプリケーションとドライバー間の通信を実現する 2 つの方法、バッファーの基本操作、基本的にすべての GPIO インターフェースをカバー
  • ドライバーとアプリケーションの完全なセットを提供し、それらのディレクトリ構造を提供します

参照リンク

openharmony 公式ウェブサイト: https://www.openharmony.cn/mainPlay
openharmony 公式ビデオ リンク: https://www.bilibili.com/video/BV1z34y1t76h/

おすすめ

転載: blog.csdn.net/qq_37596943/article/details/128534964