Linux ドライバ (6.2) の紹介 ボタン ドライバと LED ドライバ --- 論理レベルを物理レベルから分離する

序文

(1) Linux ドライバーの導入を学習した後(6) LED ドライバー - デバイス ツリー で問題が見つかりました。デバイス ツリーの gpios 情報には、明らかに 3 つの要素があります。 gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; &gpio5 3 は、どの要素を使用するかを決定するために使用されます。足を制御するためのブート、そして GPIO_ACTIVE_LOW の用途は何ですか?
(2) 前回の実験により、GPIO_ACTIVE_LOW は使用されていないようであることがわかりました。それで、これを書いて何の役に立つのですか?
(3) この要素は Linux デバイスツリーに設定されるため、意味のある要素である必要があります。次に、Linux における論理レベルと物理レベルの関係について説明します。

ロジックレベルの意味

なぜロジックレベルが必要なのか

(1) 前のコードでは、GND と VCC の位置を調整するなど、LED ドライバーのハードウェアが変更されると、コードを大幅に変更する必要があることがわかりました。Linux にはコードがたくさんあるため、特定の箇所が見落とされやすく、ハードウェアに小さな変更が生じる可能性があり、当然ながら、作成されたソフトウェアはデバッグやチェックなど多くの作業を行う必要があります。
(2) Linux はハードウェアから強力に分離する必要があるため、ロジック レベルの概念が提案されています。

ここに画像の説明を挿入

論理レベルと物理レベルの関係

(1)物理レベルは実際の電圧値であり、物理レベル 1 は実際の高レベルを指します。たとえば、TTL 規格では、ピンが 3.3V を受信した場合、それは 1 になります。ピンが 0V を受信すると、それは 0 になります。
(2)論理レベルは抽象化された論理状態であり、論理レベル 1 は有効レベルを指します。たとえば、上の左側の図では、ボタンが押されてピンがローレベルになっているため、ローレベルがアクティブレベルとなり、このボタンの論理レベルは 1 がローレベルになります。右の写真はその逆です。

ロジックレベルを導入する利点

(1) ロジックレベルの導入後、ハードウェアの実効レベルのみが変更された場合は、ドライバを変更する必要はありません。デバイス ツリーの gpios = <&gpio5 3 GPIO_ACTIVE_LOW>; を gpios = <&gpio5 3 GPIO_ACTIVE_HIGH>; に変更するだけです。
(2) このようにして、ソフトウェアとハ​​ードウェア間の強力な分離が達成されるため、同じドライバがさまざまな類似のハードウェアに対してより高い適応性を持ちます。

プログラミングと上記の違い

(1) Linux ドライバー (4) LED ドライバーの導入を説明する際に、説明のためにいくつかの GPIO サブシステム機能について言及しました。今回説明する関数は基本的にこれらの関数を微調整しただけであり、最下層で呼び出される関数自体は悪くないので、興味のある方はご自身でソースコードを読んでみてください。微調整するだけなので古いバージョンは削除してみてはいかがでしょうか?もちろん、これは古いバージョンのコードとの互換性のためなので、削除されません。

of_get_gpio_flags() GPIO 情報を取得する

機能紹介

(1) 上記では of_get_gpio() 関数を呼び出して gpio ピン番号を取得していますが、この記事では of_get_gpio_flags() 関数を使用します。
(2) ソースコードを読むと、of_get_gpio() は of_get_gpio_flags() 関数を呼び出していることがわかりますが、第 3 引数に null ポインタが渡されており、有効なレベル情報が取得されていません。

ここに画像の説明を挿入

/* 作用 : 从设备树中获取GPIO引脚的标志信息的函数
 * 传入参数 :
     * np :设备节点
     * index : 节点中的索引
     * flags : 存储有效电平的信息
 * 返回值 : GPIO的引脚号
*/
int of_get_gpio_flags(struct device_node *np, int index, enum of_gpio_flags *flags)

GPIO情報を取得できることが判明

gpios[i].gpio = of_get_gpio(np, i);   //获得gpio信息

GPIO 情報を取得します

gpios[i].gpio = of_get_gpio_flags(np, i, &flag);

devm_gpio_request_one() はピンの初期化を設定します

機能紹介

(1) 上記では、gpio_request() 関数を呼び出して GPIO に適用し、次に gpio_direction_output() 関数を使用してピンを出力として設定します。
(2) これで、devm_gpio_request_one() 関数を呼び出すだけで済みます。
(3) ここで呼び出される関数は少なくなりますが、より多くの操作を実行する必要があります。

/* 作用 : 从设备树中获取GPIO引脚的标志信息的函数
 * 传入参数 :
     * dev :要申请GPIO的设备
     * gpio : 引脚号
     * flags : 有效电平,引脚的输入输出方向,默认输出电平(物理电平)信息
     * label : 注册GPIO时候的名字
 * 返回值 : 如果返回值小于0,表示申请失败
*/
int devm_gpio_request_one(struct device *dev, unsigned gpio,
			  unsigned long flags, const char *label)

元々設定されていたGPIO

/*------------GPIO设置成默认低电平输出------------*/
//申请指定GPIO引脚,申请的时候需要用到名字
err = gpio_request(gpios[i].gpio, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}
//如果GPIO申请成功,设置输出低电平
gpio_direction_output(gpios[i].gpio, 0);
/*------------GPIO设置成默认高电平输出------------*/
//申请指定GPIO引脚,申请的时候需要用到名字
err = gpio_request(gpios[i].gpio, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}
//如果GPIO申请成功,设置输出高电平
gpio_direction_output(gpios[i].gpio, 1);
/*------------GPIO设置成输入------------*/
//申请指定GPIO引脚,申请的时候需要用到名字
err = gpio_request(gpios[i].gpio, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}
//如果GPIO申请成功,设置为输入引脚
gpio_direction_input(gpios[i].gpio);

次にGPIOを設定します

/*------------GPIO设置成默认低电平输出(注意,这里是物理电平)------------*/
gpios[i].flag = GPIOF_OUT_INIT_LOW;  //将GPIO设置成默认低电平输出(注意,这里是物理电平)
if (flag & OF_GPIO_ACTIVE_LOW) //判断有效电平是否为低电平
{
    
    
	gpios[i].flag |= GPIOF_ACTIVE_LOW;
}
printk("gpios[%d].flag is %d \r\n",i,gpios[i].flag); 
err = devm_gpio_request_one(&pdev->dev, gpios[i].gpio, gpios[i].flag, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}
/*------------GPIO设置成默认高电平输出(注意,这里是物理电平)------------*/
gpios[i].flag = GPIOF_OUT_INIT_HIGH;  //将GPIO设置成默认高电平输出(注意,这里是物理电平)
if (flag & OF_GPIO_ACTIVE_LOW) //判断有效电平是否为低电平
{
    
    
	gpios[i].flag |= GPIOF_ACTIVE_LOW;
}
printk("gpios[%d].flag is %d \r\n",i,gpios[i].flag); 
err = devm_gpio_request_one(&pdev->dev, gpios[i].gpio, gpios[i].flag, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}
/*------------GPIO设置成输入------------*/
gpios[i].flag = GPIOF_IN;  //将引脚设置成输入方向
if (flag & OF_GPIO_ACTIVE_LOW)  //判断有效电平是否为低电平
{
    
    
	gpios[i].flag |= GPIOF_ACTIVE_LOW;
}
printk("gpios[%d].flag is %d \r\n",i,gpios[i].flag); 
err = devm_gpio_request_one(&pdev->dev, gpios[i].gpio, gpios[i].flag, gpios[i].name);
//如果返回值小于0,表示申请失败
if (err < 0) 
{
    
    
	//如果GPIO申请失败,打印出是哪个LED申请出现问题
	printk("can not request gpio %s \n", gpios[i].name);
	return -ENODEV;
}

gpiod_set_value() はピンの出力レベル (ロジック レベル) を設定します。

機能紹介

(1) 上記では gpio_set_value() 関数を呼び出して物理レベルを設定しましたが、この記事では gpiod_set_value() 関数を使用して論理レベルを設定します。
(2) 興味のある方は、gpio_set_value() 関数と gpiod_set_value() 関数の基礎となる実装を見てみると、両方とも gpiod_set_raw_value() 関数を呼び出していることがわかります。gpio_set_value() が独自のパラメータを直接渡し、gpiod_set_value() 関数が有効レベル情報を判断し、有効レベル情報に従って Value 値を反転するだけです。
(3) ここで注意すべき点の 1 つは、gpiod_set_value() 関数の最初のパラメータは gpio_desc 構造体タイプ ポインタで渡されるのに対し、gpio_set_value() はピン番号で渡されることです。gpio_to_desc() 関数を呼び出して、ピン番号を使用して gpio_desc 構造体タイプ ポインターを取得できます。

ここに画像の説明を挿入

当初設定されていたGPIO出力レベル(物理レベル)

/* gpios[(int)tmp_buf[0]].gpio是引脚号
 * tmp_buf[1]是要设置的物理电平信息
*/
gpio_set_value(gpios[(int)tmp_buf[0]].gpio, tmp_buf[1]);

次に、GPIO 出力レベル (ロジック レベル) を設定します。

(1) gpiod_set_value() 関数の最初のパラメータは gpio_desc 構造体で渡す必要があるため。そこで、プローブ関数でそれを取得し、gpios 構造体に保存します。
(2) ここで注意が必要なのは、現在ロジックレベルを設定していることです。LED を低レベルで点灯する必要がある場合は、デバイス ツリーでアクティブ レベルを低レベルに設定しています。したがって、gpiod_set_value() 関数によって渡される 2 番目の値が 1 の場合、出力は実際には低レベルになります。

/*---- 在probe函数中我们使用了gpio_to_desc函数获得gpio_desc结构体 */
gpios[i].gpiod = gpio_to_desc(gpios[i].gpio);
/*---- 下面这段是在驱动程序中write函数中修改 */
/* gpios[(int)tmp_buf[0]].gpio是引脚号
 * tmp_buf[1]是要设置的物理电平信息
*/
gpiod_set_value(gpios[(int)tmp_buf[0]].gpiod, tmp_buf[1]);

gpio_desc 構造体にはアクセスできないことに注意してください。

(1) ここで注意点があり、gpio_desc 構造体は Linux カーネル構造体です。彼は、私たちが作成した C ファイルのコンパイル環境から隔離されています。したがって、gpio_desc 構造体にアクセスする権限がありません。この問題は私にとって長い間引っかかっていましたが、ご理解いただけると幸いです。
(2) 頭の固い兄弟が「おい、俺はすごいから行ってみたい」と言ったら、どうすればいいでしょうか?はい、トップアイアンである兄弟にもアイデアを提供しています。

// 在对于的内核文件中写入下面这个宏,然后重新编译Linux内核。
EXPORT_SYMBOL(gpio_to_desc);

gpiod_get_value() はピン レベル (ロジック レベル) を取得します。

機能紹介

(1) 同じ理由で、gpiod_get_value() 関数と gpio_get_value() 関数のソース コードを読むと、実際の物理レベルを取得するために gpiod_get_raw_value() 関数を呼び出していることがわかります。
(2) ただし、gpiod_get_value() は、デバイスツリーに設定された有効レベルに応じて、gpiod_get_raw_value() 関数の戻り値を反転します。gpio_get_value() 関数は、gpiod_get_raw_value() 関数の戻り値を直接出力します。
(3)したがって、 gpiod_get_value() は論理レベルを返し、 gpio_get_value() は物理レベルを返します。

ここに画像の説明を挿入

GPIOレベル(物理レベル)を取得できることが判明

tmp_buf[1] = gpio_get_value(gpios[(int)tmp_buf[0]].gpio);

GPIO レベル (ロジック レベル) を取得します。

(1) 出力レベルと同様に、デバイスツリーで LED ドライバによって設定される実効レベルがローレベルの場合。次に、gpiod_get_value() 関数を呼び出して LED の物理レベルが高いことが判明すると、0 が返されます。

tmp_buf[1] = gpiod_get_value(gpios[(int)tmp_buf[0]].gpiod);

要約する

(1) 論理レベルと物理レベルが分離されると、ドライバー ファイルを変更する必要がなくなります。ハードウェアを交換した場合は、デバイス ツリーのみを変更する必要があります。
(2) アプリケーションプログラムを作成するプログラマにとっては、LED がハイレベルで点灯しているかローレベルで点灯しているかを気にする必要がなく、1 を入力するとオン、0 を入力するとオフと認識されます。

おすすめ

転載: blog.csdn.net/qq_63922192/article/details/132333419