楼主在网上找了很多相关LINUX驱动开发的相关例程。发现基本的驱动开发都是有框架或者多种开发手段的,我们可以使用不同的开发方式来降低开发难度。有文件系统的开发和LINUX系统自带的内核函数来开发。这两者有什么区别呢?就像是单片机的库函数开发和寄存器开发一样。
先让我们来看一下GPIO子系统在linux内核中的结构吧。该文件目录在/sys/class/gpio/下。
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip, unsigned offset);
int (*direction_input)(struct gpio_chip *chip, unsigned offset);
int (*direction_output)(struct gpio_chip *chip, unsigned offset,
int value);
int (*get)(struct gpio_chip *chip,unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask,
unsigned long *bits);
int (*set_debounce)(struct gpio_chip *chip, unsigned offset,
unsigned debounce);
int (*to_irq)(struct gpio_chip *chip, unsigned offset);
int base;
u16 ngpio;
const char *const *names;
bool can_sleep;
bool irq_not_threaded;
bool exported;
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip
* inside the gpiolib to handle IRQs for most practical cases.
*/
struct irq_chip *irqchip;
struct irq_domain *irqdomain;
unsigned int irq_base;
irq_flow_handler_t irq_handler;
unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
struct device_node *of_node;
int of_gpio_n_cells;
int (*of_xlate)(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags);
};
以下几个是主要的注意事项:
request 是特定芯片激活的可选回调函数。如果提供了,在调用gpio_request()或gpiod_get()时,它会在分配GPIO之前执行。
free 是一个可选的回调函数,用于特定芯片的释放。如果提供了,那么在调用gpiod_put()或gpio_free()时,它会在GPIO被释放之前执行。
get_direction 在您需要知道方向的时候执行GPIO偏移量。返回值应为0表示out, 1表示in(与GPIOF_DIR_XXX相同),或负错误。
direction_input 将信号偏移量offset配置为输入,否则返回错误。
get 返回GPIO offset 的值;对于输出信号,这将返回实际感知到的值或0。
set 指定一个输出值给GPIO offset。
有些的芯片运行的Linux系统里面自带了引脚的编号,如果要获取编号可能要在该芯片文档中用公式计算。以Orange-pi为例它的计算公式如图所示:
利用SYSFS设置GPIO
当然,我们也能查看已经被占用的GPIO端口。
cat /sys/kernel/debug/gpio
例如第一行的意思是编号为8的GPIO的名字是goodix-int,现在是输入模式并且是高电平。已经被占用的GPIO无法使用,除非修改其寄存器。
让我们来尝试的用命令行初始化一个GPIO吧。
echo 10 > export #添加一个GPIO并且设置编号
然后我们进入GPIO10的direction 查看其电平状态。
echo out > direction #改变为输出模式
同理可用次办法尝试设置LED的参数,现在如下图我的电位是低电平所以我的LED灯已经亮起。
现在再次查看设备,GPIO10已被添加并且被设置。
当然我们也能用代码来实现上面的操作,这里举个例子,在我更后面的博客里面我会详细说明。Linux一切皆文件!! 这使得一切都变得非常好理解。
int gpio_set_dir(unsigned int gpio, unsigned int out_flag)
{
int fd, len;
char buf[MAX_BUF];
len = snprintf(buf, sizeof(buf), SYSFS_GPIO_DIR "/gpio%d/direction", gpio);
fd = open(buf, O_WRONLY);
if (fd < 0) {
perror("gpio/direction");
return fd;
}
if (out_flag)
write(fd, "out", 4);
else
write(fd, "in", 3);
close(fd);
return 0;
}
例如上面就是设置一个IO输入输出的函数。也是打开文件系统然后往里面写参数就可以了。相信大家学过linux文件系统的都会很简单的理解。
方法2:用linux内核函数来初始化GPIO
话不多说,上框架。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
static int led_init(void)//驱动函数入口
{
}
static void led_exit(void)//驱动函数出口
{
}
module_init(led_init);//注册入口
module_exit(led_exit);//注册出口
MODULE_LICENSE("GPL");//模块的许可证声明这个一般都要有这句话,具体可以去百度
然后丰富函数内容。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h> //PAD_GPIO_C
static int led_init(void)
{
gpio_request(10,"myled");
gpio_direction_output(10, 0);
printk("led init...\n");
return 0;
}
static void led_exit(void)
{
gpio_set_value(10, 1);
gpio_free(10);
printk("led exit...\n");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
这样,一个GPIO的LED驱动就完成啦。下面我将再次说明以下与GPIO相关的简单驱动内核函数。
fint gpio_request(unsigned gpio, const char *label)
函数功能:CPU的任何一个GPIO引脚硬件资源对于Linux内核来说都是一种宝贵的资源,如果某个内核程序要想访问这个GPIO引脚资源,首先必须想Linux内核申请资源(类似malloc)
void gpio_free(unsigned gpio)
函数功能:内核程序如果不再使用访问GPIO硬件资源记得要将硬件资源归还给linux内核,类似free
int gpio_direction_output(unsigned gpio, int value)
函数功能:配置GPIO引脚为输出功能,并且输出一个value值(1高电平/0低电平)
int gpio_direction_input(unsigned gpio)
函数功能:配置GPIO为输入功能
int gpio_set_value(unsigned gpio, int value)
函数功能:设置GPIO引脚的输出值为value(1:高/0:低),前提是必须首先将GPIO配置为输出功能
int gpio_get_value(unsigned gpio)
函数功能:获取GPIO引脚的电平状态,返回值就是引脚的电平状态(返回1:高电平;返回0:低电平),此引脚到底是输入还是输出没关系!
设备树
Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现以前,所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树之后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。设备树源文件扩展名为.dts(device tree source),一般放置/arch/arm/boot/dts/目录内。设备树信息在根文件系统中/proc/device-tree目录下,包括根节点’/’的所有属性和子节点。
设备树的格式如下(例子):
gpios {
compatible = "gpio-user";
status = "okay";
/*input*/
gpio0 {
label = "in0";
gpios = <&gpio1 1 0>;
default-direction = "in";
};
... ...
/*output*/
gpio17 {
label = "out1";
gpios = <&gpio3 3 0>;
default-direction = "out";
};
};
为啥有时候要修改设备树呢?因为有时候你不想重新改动驱动代码,或者其他不需要的设备占用了你的IO,你想把它ban掉。有时候开发会涉及到设备树的修改,这里我引用一个大佬写的比较详细的博客:设备树的使用和说明 - 知乎 (zhihu.com)。(如有侵权亲联系我我会立刻删掉)文章来源:https://www.toymoban.com/news/detail-453970.html
我们在编译这些文件的时候一般的会用makefile编译。不会写makefile的小伙伴可以去学CMake,足以应付一些不太复杂的文件编译。在这里我也推荐一篇大佬写的不错的博客:https://blog.csdn.net/weixin_44498318/article/details/106219135。(如有侵权亲联系我我会立刻删掉)文章来源地址https://www.toymoban.com/news/detail-453970.html
到了这里,关于LINUX驱动之——GPIO的基本驱动的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!