Linux內核驅動學習(八)GPIO驅動模擬輸出PWM


前言

上一篇的學習中介紹了如何在用戶空間直接操作GPIO,並寫了一個腳本可以產生PWM。本篇的學習會將寫一個驅動操作GPIO,同樣的也可以發生PWM,因此這里還需要部分的硬件配合,需要一塊開發板,當然可能還需要一台示波器。

原理圖

和上一篇相同,引腳依然是GPIO3_D0,具體硬件肯定會不同,注意參考socdatasheet和硬件原理圖,先定位正確需要操作的GPIO
Datasheet

IO模擬輸出PWM

這里驅動實現的方式是先創建一個內核線程,如何創建內核線程可以參考Linux內核驅動學習(五)KThread學習總結,然后在線程函數一直循環反轉IO口的輸出。這里的目的單純是為了學習操作GPIO,不建議項目中通過這種IO口模擬的方式去實現PWM的輸出,而應該直接使用自帶PWM功能的引腳。

設備樹

	gpio-demo {
		compatible = "gpio-demo";
		gpios = <&gpio3 0 GPIO_ACTIVE_LOW>;
	};

驅動源碼中通過of_get_gpio接口去解析gpio

驅動端

驅動源碼中of_device_id結構體變量中的成員.compatible的值必須和設備樹的設備節點兼容屬性compatible的值相同;

static struct of_device_id gpio_demo_of_match[] = {
	{ .compatible = "gpio-demo"},
	{},
}

MODULE_DEVICE_TABLE(of,gpio_demo_of_match);

static struct platform_driver gpio_demo_driver = {
	.probe = gpio_demo_probe,
	.driver = {
		.name = "gpio-demo-device",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(gpio_demo_of_match),
	}
};

probe函數實現對設備樹節點的解析,of_get_gpio對應gpio-demo節點下的gpios屬性;
然后ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name)語句初始化GPIO為輸出引腳;

static int gpio_demo_probe(struct platform_device *pdev){

	int ret,i;
	struct device *dev = &pdev->dev;
	struct device_node *node = dev->of_node;

	if (!node)
		return -EINVAL;
	ret = of_gpio_count(node);
	if (ret == 0){
		return -EINVAL;
	}
	priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(int) * ret, GFP_KERNEL);
	if (!priv){
		return -ENOMEM;
	}
	priv->count = ret;
	mutex_init(&priv->mtx);
	for (i = 0; i < priv->count; i++) {
		unsigned int gpio;
		gpio = of_get_gpio(node, i);
		if (gpio < 0) {
			dev_warn(dev, "Unable to get gpio #%d\n", i);
			continue;		
		}
		ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
		priv->gpio[i] = gpio;
		if (ret < 0) {

			dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
                      gpio, ret);
			continue;
		}
		printk(KERN_INFO "success request gpio %d\n",gpio);
		
		gpio_direction_output(gpio, 1); //設置輸出的電平
		
	}
	return 0;
}

線程執行函數中通過gpio_set_value設置GPIO的輸出值,然后休眠50毫秒,最終PWM的周期應該是100毫秒左右。

static int thread_func(void *data) {	

	int i, count;
	while (1){
		count++;
		mutex_lock(&priv->mtx);
		for ( i = 0; i < priv->count; i++){
			gpio_set_value(priv->gpio[i], count%2);
		}
		mutex_unlock(&priv->mtx);
		msleep(50);
		printk(KERN_INFO "thread count %d\n", count);
	}
	return 0;
}

gpio_set_valuegpio_direction_output的區別
如果使用該GPIO時,不會動態地切換輸入輸出,建議在開始時就設置好GPIO 輸出方向,后面拉高拉低時使用gpio_set_value()接口,而不建議使用gpio_direction_output(), 因為gpio_direction_output接口里面有mutex鎖,對中斷上下文調用會有錯誤異常,且相比 gpio_set_valuegpio_direction_output 所做事情更多,浪費。

調試信息

先通過debugfs查看相應的GPIO已經成功加載到內核了;但是我們目前沒有留用戶層調用的接口,這個有悖於我們的初衷,但是目前為止已經實現了自己想要的效果。
在這里插入圖片描述

實驗結果

在這里插入圖片描述

附錄

#include <linux/module.h> 
#include <linux/init.h>

#include <linux/platform_device.h>
//API for libgpio
#include <linux/gpio.h>
//API for malloc
#include <linux/slab.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread 
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/mutex.h>

static struct task_struct *thread_body;
struct gpio_demo_priv{
	int count;
	int gpio[0]; 
	struct mutex mtx;
	int mode;
};

struct gpio_demo_priv *priv;

static int thread_func(void *data) {	

	int i, count;
	while (1){
		count++;
		mutex_lock(&priv->mtx);
		for ( i = 0; i < priv->count; i++){
			gpio_set_value(priv->gpio[i], count%2);
		}
		mutex_unlock(&priv->mtx);
		msleep(50);
		printk(KERN_INFO "thread count %d\n", count);
	}
	return 0;
}

static int gpio_demo_probe(struct platform_device *pdev){

	int ret,i;
	struct device *dev = &pdev->dev;
	struct device_node *node = dev->of_node;

	if (!node)
		return -EINVAL;
		
	ret = of_gpio_count(node);
	if (ret == 0){
		return -EINVAL;
	}
	
	priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(int) * ret, GFP_KERNEL);
	
	if (!priv){
		return -ENOMEM;
	}
	
	priv->count = ret;
	mutex_init(&priv->mtx);
	for (i = 0; i < priv->count; i++) {
		unsigned int gpio;
		gpio = of_get_gpio(node, i);
		if (gpio < 0) {
			dev_warn(dev, "Unable to get gpio #%d\n", i);
			continue;		
		}
		ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
		priv->gpio[i] = gpio;
		if (ret < 0) {

			dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
                      gpio, ret);
			continue;
		}
		printk(KERN_INFO "success request gpio %d\n",gpio);
		
		gpio_direction_output(gpio, 1); //設置輸出的電平
		
	}
	platform_set_drvdata(pdev,priv);

	thread_body = kthread_create(thread_func, NULL, "thread_pwm");
    if((thread_body))
    {
        wake_up_process(thread_body);
    }

	return 0;
}

static struct of_device_id gpio_demo_of_match[] = {
	{ .compatible = "gpio-demo"},
	{},
}

MODULE_DEVICE_TABLE(of,gpio_demo_of_match);

static struct platform_driver gpio_demo_driver = {
	.probe = gpio_demo_probe,
	.driver = {
		.name = "gpio-demo-device",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(gpio_demo_of_match),
	}
};

static int __init gpio_demo_init(void){
	return  platform_driver_register(&gpio_demo_driver);
}

static void __exit gpio_demo_exit(void){
	platform_driver_unregister(&gpio_demo_driver);
}

late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gpio demo Driver");
MODULE_ALIAS("platform:gpio-demo");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM