Linux中编写GPIO驱动


前言

GPIO是嵌入式开发中常用的一个模块,Linux下的任何外设驱动,最终目的都是要配置相应的硬件寄存器。在Linux中,不管是内核空间代码,还是用户空间代码,访问的都是虚拟地址。
gpio子系统是Linux内核中用于管理GPIO资源的一套系统,提供了很多GPIO相关的API接口,虽然方便驱动开发者使用,但最终还是得操作寄存器;在使用此系统前,需要向内核注册这一套操作的方法。
pinctrl子系统通常用于管理SoC中各PIN脚的复用功能和电气特性。对于 ZYNQ MPSoC 来说,使用了 vivado 图形化完成了对 PIN 的配置并在 fsbl 阶段将
配置信息写入了硬件寄存器中,所以不需要在内核阶段进行配置。


一、MMU是什么?

开始前我们要了解什么是MMU。Linux中一个重要的部分就是MMU(内存管理单元),其作用是将虚拟空间映射到物理空间,让我们可以方便的操作各个硬件。像一般32位的系统,虚拟地址范围即为2^32=4GB。这样我们就可以通过程序中的虚拟地址对硬件进行操作。

二、操作GPIO

内核中操作GPIO有两种方法,一种是比较硬核的不使用linux为我们提供的标准GPIO接口,还有一种就是用标准接口,下面来说明这两种方式。

1.不使用GPIO标准接口

既然不用标准接口,我们就只能通过看数据手册,查找对应寄存器的地址和各bit对应功能,映射地址的方式操作对应的物理设备,主要用到的接口有:

将一个IO地址空间映射到内核的虚拟地址空间
#define ioremap(phys_addr,size) __arm_ioremap((phys_addr), (size), MT_DEVICE)
void __iomem *__ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
phys_addr:物理起始地址
size:映射内存空间大小
返回虚拟地址首地址

释放映射:
void iounmap (volatile void __iomem *addr);
addr:虚拟地址首地址

读操作:
u8 readb(const volatile void __iomem *addr);	8bit
u16 readw(const volatile void __iomem *addr);	16bit
u32 readl(const volatile void __iomem *addr);	32bit

写操作:
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);

例子:
假设有个GPIO口控制LED灯的开关,寄存器地址为0x3000001C,第12bit为0时gpio拉低,1时拉高。
#define GPIO_LED_REG ((unsigned int)0x3000001C)

unsigned int *addr = (unsigned int *)ioremap(GPIO_LED_REG,4);
int val = 0;
拉低:
val = readl(addr);
val &= ~(0x01U<<12);
writel(val,addr);

拉高:
val = readl(addr);
val |= (0x01U<<12);
writel(val,addr);

iounmap(GPIO_LED_REG);

2.使用GPIO标准接口

代码如下(示例):

1.gpio申请
int gpio_request(unsigned gpio, const char *label)
成功返回0,失败返回负数

释放gpio
void gpio_free(unsigned gpio)

2.GPIO的方向设置
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);

3.GPIO输出值设置与输入值获取
/* GPIO INPUT:  return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);

例子:
假设该GPIO对应Soc上的引脚为66,则可以有:
int led_gpio = 66;
gpio_request(led_gpio,"LED");
gpio_direction_output(led_gpio, 0);

拉高:
gpio_set_value(led_gpio,1);
拉低:
gpio_set_value(led_gpio,0);

gpio_free(led_gpio);

三、与gpio相关的OF函数

1、 of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意
的是空的 GPIO 信息也会被统计到,比如:
gpios = <0
		&gpio1 1 2
		0
		&gpio2 3 4>;
上述代码的“ gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。
通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np:设备节点。
propname:要统计的 GPIO 属性。
返回值: 正值,统计到的 GPIO 数量;负值,失败。

2、 of_gpio_count 函数
和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“ gpios”这个
属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型
如下所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 正值,统计到的 GPIO 数量;负值,失败。

3、 of_get_named_gpio 函数
此函数获取 GPIO 编号, gpio 子系统为了方便管理系统中的 GPIO 资源,每一个 GPIO 管
脚都有一个对应的编号, Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会
将设备树中类似<&gpio0 38 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函
数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个
GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值: 正值,获取到的 GPIO 编号;负值,失败。

四、GPIO驱动

通常GPIO在设备树中的节点如下:

gpio: gpio@ff0a0000 {
	compatible = "xlnx,zynqmp-gpio-1.0";	//对应驱动程序
	status = "disabled";
	#gpio-cells = <0x2>;	//类似#address-cells,使用gpio时,要传递两个参数过去<&gpio 38 HIGH>
	interrupt-parent = <&gic>;
	interrupts = <0 16 4>;
	interrupt-controller;	
	#interrupt-cells = <2>;
	reg = <0x0 0xff0a0000 0x0 0x1000>;	//基地址为0xFF0A0000(查手册可知)
	gpio-controller;					//表明是个GPIO控制器,对应驱动是GPIO驱动
	power-domains = <&zynqmp_firmware 46>;
};

通常驱动程序都在内核目录的driver/gpio/下,gpiolib-开始的是就是gpio驱动的核心文件了。

五、设备树与驱动的使用

首先是在设备树中的常见节点案例:

	gpio-led {
		compatible = "test_led";
		status = "okay";
		default-status = "off";
		led-gpio = <&gpio 38 GPIO_ACTIVE_HIGH>;
	};

在驱动中,我们可以这样使用内核提供的API写来控制引脚的方法:

struct led_dev {
	struct device_node * nd; 
	int gpio_led;
};
struct led_dev led = {0};

int __init led_init(void)
{	
	led.nd = of_find_node_by_name(NULL,"gpio-led");
	if(led.nd == NULL)
	{
		printk("led gpio of_find_node_by_name = NULL !\n");
		return -1;
	}
	
	ret = of_property_read_string(led.nd,"status",&str);
	if(ret < 0)
		printk("led gpioread status failed !\n");
	else
		printk("led gpio read status success , str = %s\n",str);
	
	if(!strcmp(str,"okay"))
		printk("led gpio status on !\n");
	else
	{
		printk("led gpio status off !\n");
		return -1;
	}
	
	led.gpio_led = of_get_named_gpio(led.nd,"led-gpio",0);
	if(!gpio_is_valid(led.gpio_led))
	{
		printk("led gpio of_get_named_gpio failed !\n");
		return -1;
	}
	
	printk("led gpio = %d\n",led.gpio_led);
	
	ret = gpio_request(led.gpio_led,"LED-GPIO");
	if(ret)
	{
		printk("led gpio gpio_request failed !\n");
		return -1;
	}
	
	gpio_direction_output(led.gpio_led,0);
	
	ret = of_property_read_string(led.nd,"default-status",&str);
	if(ret < 0)
	{
		printk("led read default-status failed !\n");
		gpio_set_value(led.gpio_led,0);
	}
	else
	{
		printk("led read default-status success , str = %s\n",str);
		if(!strcmp(str,"on"))
			gpio_set_value(led.gpio_led,1);
		else
			gpio_set_value(led.gpio_led,0);
	}
	printk("led init success ~~~\n");
	
	return 0;
}
void __exit led_exit(void)
{
	gpio_free(led.gpio_led);
	return;
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

则加载驱动有如下打印:
在这里插入图片描述


总结

以上就是今天要讲的内容,本文简单介绍了内核中对GPIO的使用,制作不易,多多包涵。

  • 22
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于在Linux上使用Qt编写GPIO驱动,您可以按照以下步骤进行操作: 1. 首先,确保您的系统已经配置了GPIO驱动。如果没有,请参考相关文档来完成配置。 2. 在Qt项目,使用QProcess类或者编写一个简单的shell脚本来调用GPIO命令行工具,如gpiogpiod等。这些工具可以用来读取和设置GPIO引脚的状态。 3. 在Qt代码,使用QIODevice类来读取和写入GPIO设备文件。这些设备文件通常位于/sys/class/gpio目录下,每个GPIO引脚都有一个对应的文件。 例如,要设置GPIO引脚为输出模式并设置高电平,可以使用以下代码片段: ```cpp QFile gpioExportFile("/sys/class/gpio/export"); if (gpioExportFile.open(QIODevice::WriteOnly)) { gpioExportFile.write("gpio123"); // 替换为目标GPIO引脚号 gpioExportFile.close(); } QFile gpioDirectionFile("/sys/class/gpio/gpio123/direction"); if (gpioDirectionFile.open(QIODevice::WriteOnly)) { gpioDirectionFile.write("out"); gpioDirectionFile.close(); } QFile gpioValueFile("/sys/class/gpio/gpio123/value"); if (gpioValueFile.open(QIODevice::WriteOnly)) { gpioValueFile.write("1"); gpioValueFile.close(); } ``` 4. 根据需要,您可以使用信号与槽机制来实现GPIO状态的监测和更新。 请注意,GPIO驱动的具体实现可能因您使用的硬件平台和Linux发行版而有所不同。因此,您需要根据您的具体情况进行相应的调整和修改。此外,确保以root权限运行您的应用程序,以便具有对GPIO设备文件的访问权限。 希望这些信息能对您有所帮助!如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值