14.2linux中platform无设备树情况下驱动LED灯(详细编写程序)_csdn

news2025/4/6 11:30:48

我尽量讲的更详细,为了关注我的粉丝!!!

因为这跟之前的不一样,提出来驱动的分离和分层。
提到驱动分离和分层,必然可以联系上一章咱们知道的驱动-总线-设备。
无设备树的状态下,必然要写寄存器地址,编写设备信息,所以这里我们要创建设备和驱动两个文件。
本章实验我们需要编写一个驱动模块和一个设备模块,其中驱动模块是 platform 驱动程序,设备模块是 platform 的设备信息。当这两个模块都加载成功以后就会匹配成功,然后 platform驱动模块中的 probe 函数就会执行, probe 函数中就是传统的字符设备驱动那一套。
这里就没有设备节点信息这一说了!
驱动分离是指将设备的硬件描述(如设备的寄存器地址、中断号等)与驱动的功能实现分离开来。传统的驱动开发方式中,硬件信息通常硬编码在驱动代码里,这使得驱动与特定的硬件紧密绑定,难以复用。而驱动分离的思想是把硬件信息提取出来,采用一种通用的方式描述,驱动程序只需要依据这些描述来操作设备,从而实现驱动与硬件的解耦。
驱动分层是将驱动程序按照功能划分为不同的层次,每个层次专注于特定的功能,层与层之间通过接口进行交互。通常可以分为设备无关层和设备相关层。设备无关层提供通用的接口给上层应用程序使用,而设备相关层则负责具体的硬件操作。

1、编写设备信息

首先编写leddevice.c这个LED灯的 platform 设备文件。就是把硬件的设备信息写到一个文件中。后面就是把驱动写到一个文件中。

1.1、头文件

比之前添加了这个头文件 <linux/platform_device.h>。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

在 Linux 内核里,平台设备(platform device)是一种抽象的设备模型,它和具体的总线无关。该模型主要用来处理那些没有特定总线的设备,像系统中的一些内部设备等。<linux/platform_device.h>头文件提供了与平台设备相关的结构体、函数以及宏定义,从而让开发者能够方便地进行平台设备驱动的开发。

1.2、设备的注册和注销

Pasted image 20250403151752.png
在无设备树的情况下需要:
用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform设备。

1.3、编写platform设备结构体

Pasted image 20250403152747.png
在无设备树的情况下需要:
使用 platform_device 来描述设备。

  • name:设备名称,用于和驱动匹配,此设备名是 "stm32mp1-led"
  • .id:设备 ID,设为 -1 表示只有一个该类型设备。
  • .dev:包含通用设备属性与操作,.release 指向资源释放函数 led_release
  • .num_resources:设备资源数量,由 ARRAY_SIZE(led_resources) 计算得出。
  • ARRAY_SIZE 是一个宏,它在 Linux 内核代码里较为常用,其用途是计算数组元素的数量。
  • .resource:指向资源数组 led_resources,定义了设备所需资源。
  • platform 设备结构体变量 leddevice,这里要注意 name 字段为“stm32mp1-led”,所以稍后编写platform 驱动中的 name 字段也要为“stm32mp1-led”,否则设备和驱动匹配失败。
    添加led_release:
    Pasted image 20250403154331.png
    Pasted image 20250403210427.png
    释放platform设备模块的时候此函数会执行(也就是执行platform_device_unregister(&leddevice));

1.4、添加寄存器物理地址信息

Pasted image 20250403154541.png
这个是STM32MP157正点原子上操作LED0的物理地址信息。
这个在之前的文章里面讲到过具体查找手册,感兴趣的朋友可以翻翻前面的博客!

REGISTER_LENGTH 定义了寄存器的长度为 4 字节(32 位),这在许多嵌入式系统中是比较常见的寄存器长度。

1.5、向platform_device配置资源信息

我们已经准备好了寄存器的物理地址了,需要将它利用到:

.resource = led_resources里面

Pasted image 20250403155208.png
这里我们用到了上一章讲到的:

  • start:资源的起始地址。
  • end:资源的结束地址。
  • flags:资源的类型标志,像 IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源等。
    这样我们就完成了一个设备的信息配置,作用就是类似之前写代码中的设备树的功能!在这里,无设备树的情况,实现开发板内部有些设备无法通过总线(如I2C,SPI等总线)进行驱动和设备的分离。
    接下来进行编写驱动文件leddriver.c!

1.6、设备信息总代码

leddevice.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/*寄存器物理地址*/
#define PERIPH_BASE (0x40000000)
#define MPU_AHB4_PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define RCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000)
#define RCC_MP_AHB4ENSETR (RCC_BASE + 0XA28)
#define GPIOI_BASE (MPU_AHB4_PERIPH_BASE + 0xA000)
#define GPIOI_MODER (GPIOI_BASE + 0x0000)
#define GPIOI_OTYPER (GPIOI_BASE + 0x0004)
#define GPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#define GPIOI_PUPDR (GPIOI_BASE + 0x000C)
#define GPIOI_BSRR (GPIOI_BASE + 0x0018)
#define REGISTER_LENGTH 4

static void led_release(struct device *dev)
{
      printk("led device released!\r\n");
}

/*设备资源信息,也就是LED0所使用的所有寄存器*/
static struct resource led_resources[] = {
    [0] = {
    .start = RCC_MP_AHB4ENSETR,
    .end = (RCC_MP_AHB4ENSETR + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
    [1] = {
    .start = GPIOI_MODER,
    .end = (GPIOI_MODER + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
    [2] = {
    .start = GPIOI_OTYPER,
    .end = (GPIOI_OTYPER + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
    [3] = {
    .start = GPIOI_OSPEEDR,
    .end = (GPIOI_OSPEEDR + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
    [4] = {
    .start = GPIOI_PUPDR,
    .end = (GPIOI_PUPDR + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
    [5] = {
    .start = GPIOI_BSRR,
    .end = (GPIOI_BSRR + REGISTER_LENGTH - 1),
    .flags = IORESOURCE_MEM,
    },
};

/*platform设备结构体*/
static struct platform_device leddevice = {
        .name = "stm32mp1-led",
        .id = -1,
        .dev = {
        .release = &led_release,
        },
        .num_resources = ARRAY_SIZE(led_resources),
        .resource = led_resources,
};

/*设备模块加载*/
static int __init leddevice_init(void)
{
    return platform_device_register(&leddevice);
}
/*设备模块注销*/
static void __exit leddevice_exit(void)
{
    platform_device_unregister(&leddevice);
}

module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree, "Y");

2、编写驱动文件

也就是编写leddriver.c文件。

2.1、头文件

比之前添加了这个头文件 <linux/platform_device.h>。
因为platform_device.h同样也包含驱动模块。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

2.2、驱动的注册和注销

Pasted image 20250403164818.png
Linux 内核中平台驱动的模块初始化和退出函数,其作用是在模块加载时注册平台驱动,在模块卸载时注销平台驱动。

  • platform_driver_register:这是一个内核函数,其功能是向内核注册一个平台驱动。&led_driver 是指向 struct platform_driver 结构体的指针,这个结构体定义了驱动的具体操作函数,例如 proberemove 等。
  • platform_driver_unregister:这是一个内核函数,其作用是从内核中注销一个平台驱动。&led_driver 同样是指向 struct platform_driver 结构体的指针。

2.3、编写platform驱动结构体

Pasted image 20250403165228.png
在 Linux 内核的平台驱动模型中,platform_driver 结构体用于描述一个平台驱动,它涵盖了驱动的名称、探测函数、移除函数等关键信息。内核借助这些信息来管理驱动和设备之间的匹配与交互。

  • driver 子结构体
  • .name:这是驱动的名称,它是一个字符串。内核在进行驱动和设备的匹配时,会将驱动的名称和设备的名称进行比较,若两者一致,就会调用驱动的 probe 函数。在这个例子中,驱动的名称是 "stm32mp1-led",意味着它会尝试匹配名称同样为 "stm32mp1-led" 的设备。
  • led_probe:这是一个函数指针,指向驱动的探测函数。当驱动和设备匹配成功后,内核会调用这个函数。在 led_probe 函数里,通常会进行设备资源的获取、寄存器的映射、设备的初始化等操作。
  • led_remove:这是一个函数指针,指向驱动的移除函数。当设备被移除或者驱动被卸载时,内核会调用这个函数。在 led_remove 函数中,通常会进行资源的释放、寄存器映射的取消等清理操作。
    在这里我们看前面一章可以看出,struct platform_driver和struct device_driver同样都有probe和remove,但是前者的probe与remove定义是struct platform_device,后者的probe与remove定义是struct device *dev。
    struct device_driver 是 Linux 内核中表示设备驱动的通用结构体,它是所有设备驱动的基础抽象,适用于各种类型的设备驱动,涵盖字符设备驱动、块设备驱动、网络设备驱动等
    probe 和 remove 参数**:这两个函数的参数类型是 struct device *struct device 是内核中表示设备的通用结构体,它封装了设备的通用属性和操作,例如设备的名称、父设备、设备的状态等。使用 struct device * 作为参数,能够让 device_driver 适用于各种类型的设备,具备通用性 struct platform_driver 是专门为平台设备设计的驱动结构体,平台设备一般指那些不依赖于特定总线的设备,例如 CPU 内部的外设等。 **probe 和 remove 参数**:这两个函数的参数类型是 struct platform_device *struct platform_device 是 struct device 的子类,它继承了 struct device 的所有属性和操作,并且在此基础上添加了一些与平台设备相关的属性,例如设备的资源信息(内存资源、中断资源等)。使用 struct platform_device *` 作为参数,能够让平台驱动方便地访问平台设备的特定信息,简化驱动开发。
  • struct device_driver:它的proberemove函数接收struct device *类型的参数。这个参数提供的是设备的一些通用信息。这就好比你只知道这个设备的基本身份信息,但对于设备的一些特殊资源(如特定的内存区域、中断号等),你可能需要通过其他复杂的转换或者查询才能获取。
  • struct platform_driver:它的proberemove函数接收struct platform_device *类型的参数。这个参数就像是一个 “豪华套餐”,除了包含设备的通用信息外,还能让你直接获取平台设备特有的资源信息。例如,你可以很方便地通过这个参数获取设备的内存资源(使用platform_get_resource函数),这对于初始化设备和配置设备的寄存器等操作非常方便。
    这里我们用struct platform_driver内的proberemove函数接收特定的内存区域。
    一旦匹配上了,就可以直接执行probe函数!

2.4、配置probe 函数和remove 函数

Pasted image 20250403181206.png
其中struct platform_device *dev就是这里面的。

 int (*probe)(struct platform_device *);
 int (*remove)(struct platform_device *);

2.4.1、配置字符设备结构体

到这里开始跟以前一样了!
Pasted image 20250403182430.png
同时也配置设备名字和数量。
Pasted image 20250403183723.png

2.4.2、注册字符设备

Pasted image 20250403185542.png
同时注销字符设备驱动:
Pasted image 20250403190018.png

2.4.3、配置cdev结构体、初始化和添加cdev

Pasted image 20250403190640.png
Pasted image 20250403190820.png
可以执行到/dev

2.4.3.1这里可以添加字符驱动操作集:

Pasted image 20250403202523.png
这里发现正点原子提供的代码没有写:
.release = led_release,文件操作集。
Pasted image 20250403202727.png

  • platform_device中struct device 中的 release 函数用于释放设备占用的全局资源,确保设备资源被正确回收。
  • struct file_operations 中的 release 函数用于释放与用户空间文件操作相关的局部资源,保证文件操作的正常结束。
  • 综上所述,虽然都叫 release,但它们处于不同的结构体中,调用时机和作用也不一样。在设备驱动开发时,需要根据具体需求正确实现这两个函数。
    我们添加后:
    Pasted image 20250403204506.png
    添加cdev:
    Pasted image 20250403191111.png
    同时注销字符设备对象:
    Pasted image 20250403191135.png

2.4.4、配置和设备类和设备节点结构体、创建设备类和设备节点

Pasted image 20250403191432.png
Pasted image 20250403191817.png
可以在/dev下创建platled。即/dev/platled。
同时注销字符设备类和设备节点:
Pasted image 20250403191828.png

2.4.5、获取资源信息

在这个地方原本是要获取设备树下的信息或者寄存器物理地址的,但是在这个章节我们就要利用leddevice.c文件传过来的设备信息相匹配,里面的数组信息从而传到这里的驱动里面来。
Pasted image 20250403192743.png
这里我们要在前面定义好变量:
Pasted image 20250403192817.png
从设备文件传过来的也是struct resource变量,所以这里也要用struct resource变量。

  • platform_get_resource 函数:这是一个内核函数,其作用是从指定的平台设备 dev 中获取特定类型的资源。
  • ledsource[i]:这是一个 struct resource * 类型的数组,用于存储获取到的资源指针。
  • dev_err 函数:这是一个内核函数,用于输出错误信息。它会把错误信息输出到内核日志中。
  • -ENXIO:这是一个错误码,表示设备不存在或者无法打开。当获取资源失败时,函数会返回这个错误码。
  • resource_size 函数:这是一个内核函数,其功能是获取指定资源的大小。地址映射需要大小这个参数,
  • ledsource[i] 的本质ledsource[i] 是一个指向 struct resource 结构体的指针,该结构体定义了设备资源的基本信息,包含资源的起始地址(start)、结束地址(end)和资源类型(flags)等。然而,它本身并没有直接提供一个明确表示资源大小的字段。
  • resource_size 函数的作用resource_size 函数的作用就是根据 struct resource 结构体中的 start 和 end 字段来计算资源的大小。其实现原理通常是 end - start + 1。通过调用这个函数,你可以方便地获取资源的字节数,这在后续对资源进行操作时非常重要。例如,当你需要对资源进行内存映射(ioremap)时,就需要知道映射的长度,也就是资源的大小。
    在前面的设备文件中提到过.num_resources = ARRAY_SIZE(led_resources)
  • num_resources 和 ARRAY_SIZE 关注的是设备资源数组的元素数量,用于让内核知道设备有多少个资源项。
  • resource_size 关注的是单个资源的实际大小,在对具体资源进行操作时,如内存映射数据传输等,需要使用该函数获取资源大小,以确保操作的正确性。

2.4.6、地址映射和取消映射

这里我们就要利用上面存储的地址来映射虚拟内存地址(这里以前就讲过)
Pasted image 20250403195244.png
其中的->start也就是设备文件中的资源:
Pasted image 20250403195424.png
大小也就是ressize决定!
其中等式左边的虚拟地址指针由下面的决定:
Pasted image 20250403195540.png
同理,也要在释放驱动的时候取消映射:
我们在这里利用模块化取消映射!
Pasted image 20250403200607.png
Pasted image 20250403200713.png

2.4.7、使能PI时钟

Pasted image 20250403200931.png

2.4.8、设置PI0通用的输出模式

Pasted image 20250403201058.png

2.4.9、设置PI0为推挽模式

Pasted image 20250403201235.png

2.4.10、设置PI0为高速

Pasted image 20250403201419.png

2.4.11、设置PI0为上拉

Pasted image 20250403201751.png

2.4.12、配置错误信息

Pasted image 20250403202143.png

2.5、配置操作集函数

Pasted image 20250403204731.png
Pasted image 20250403204943.png
其中模块化led灯的开灯关灯状态:
Pasted image 20250403205112.png

2.6、驱动总代码

leddriver.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_CNT 1              /* 设备号长度 */
#define LED_NAME   "platled"   /* 设备名字 */
#define LEDOFF 0
#define LEDON  1

/*映射后的寄存器虚拟地址指针*/
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;

/*led设备结构体*/
struct led_dev{
    dev_t devid;//设备号
    int major;//主设备号
    int minor;//次设备号
    struct cdev cdev;//关联字符设备对象
    struct class *class; /*设备类*/
    struct device *device; /*设备节点*/
};
struct led_dev led;//设备

/*led灯模块化开灯或关灯*/
void led_switch(u8 sta)
{
    u32 val = 0;
    if(sta == LEDON) {
        val = readl(GPIOI_BSRR_PI);
        val |= (1 << 16);
        writel(val, GPIOI_BSRR_PI);
    }else if(sta == LEDOFF) {
        val = readl(GPIOI_BSRR_PI);
        val|= (1 << 0);
        writel(val, GPIOI_BSRR_PI);
    }
}

/*取消映射*/
void led_unmap(void)
{
    iounmap(MPU_AHB4_PERIPH_RCC_PI);
    iounmap(GPIOI_MODER_PI);
    iounmap(GPIOI_OTYPER_PI);
    iounmap(GPIOI_OSPEEDR_PI);
    iounmap(GPIOI_PUPDR_PI);
    iounmap(GPIOI_BSRR_PI);
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
    size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
    printk("kernel write failed!\r\n");
    return -EFAULT;
    }
    ledstat = databuf[0]; /* 获取状态值 */
    if(ledstat == LEDON) {
    led_switch(LEDON); /* 打开 LED 灯 */
    }else if(ledstat == LEDOFF) {
    led_switch(LEDOFF); /* 关闭 LED 灯 */
    }
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
   return 0;
}

/*设备操作函数*/
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .write = led_write,
    .release = led_release,
};

/*platform驱动的probe函数*/
static int led_probe(struct platform_device *dev)
{
    int i=0,ret;
    int ressize[6];
    u32 val = 0;
    struct resource *ledsource[6];
    /*1.注册字符设备驱动*/
    led.major=0;
    if(led.major){//若给定主设备号
          led.devid=MKDEV(led.major,0);
          ret=register_chrdev_region(led.devid,LED_CNT,LED_NAME);
    }else{//若未给定主设备号
          ret=alloc_chrdev_region(&led.devid,0,LED_CNT,LED_NAME);
          led.major=MAJOR(led.devid);
          led.minor=MINOR(led.devid);
    }
    if(ret<0){
        goto fail_devid;
    }
    printk("major=%d,minor=%d,NUm=%d,NAME=%s\r\n",led.major,led.minor,LED_CNT,LED_NAME);
    /*2.初始化cdev*/
    led.cdev.owner=THIS_MODULE;
    cdev_init(&led.cdev,&led_fops);
    /*3.添加cdev*/
    ret=cdev_add(&led.cdev,led.devid,LED_CNT);
    if(ret<0){
        goto fail_cdev;
    }
    /*4.创建设备类*/
    led.class=class_create(THIS_MODULE,LED_NAME);
    if(IS_ERR(led.class)){
        ret = PTR_ERR(led.class);
        goto fail_class;
    }
    /*5.创建设备节点*/
    led.device=device_create(led.class,NULL,led.devid,
            NULL,LED_NAME);
    if(IS_ERR(led.device)){
        ret = PTR_ERR(led.device);
        goto fail_device;
    }
    /*6.从设备信息中获取资源*/
    for(i=0;i<6;i++) {
        ledsource[i]=platform_get_resource(dev,IORESOURCE_MEM,i);
    if(!ledsource[i]) {
        dev_err(&dev->dev,"No MEM resource for always on\n");
        return -ENXIO;
    }
    ressize[i] = resource_size(ledsource[i]);
    }
    /*7.寄存器地址映射*/
    MPU_AHB4_PERIPH_RCC_PI = ioremap(ledsource[0]->start,
    ressize[0]);
    GPIOI_MODER_PI = ioremap(ledsource[1]->start, ressize[1]);
    GPIOI_OTYPER_PI = ioremap(ledsource[2]->start, ressize[2]);
    GPIOI_OSPEEDR_PI = ioremap(ledsource[3]->start, ressize[3]);
    GPIOI_PUPDR_PI = ioremap(ledsource[4]->start, ressize[4]);
    GPIOI_BSRR_PI = ioremap(ledsource[5]->start, ressize[5]);
    /*8.使能PI时钟*/
    val = readl(MPU_AHB4_PERIPH_RCC_PI);
    val &= ~(0X1 << 8); /* 清除以前的设置 */
    val |= (0X1 << 8); /* 设置新值 */
    writel(val, MPU_AHB4_PERIPH_RCC_PI);
    /*9.设置PI0通用的输出模式*/
    val = readl(GPIOI_MODER_PI);
    val &= ~(0X3 << 0); /* bit0:1 清零 */
    val |= (0X1 << 0); /* bit0:1 设置 01 */
    writel(val, GPIOI_MODER_PI);
    /*10.设置PI0为推挽模式*/
    val = readl(GPIOI_OTYPER_PI);
    val &= ~(0X1 << 0); /* bit0 清零,设置为上拉*/
    writel(val, GPIOI_OTYPER_PI);
    return 0;
    /*11.设置PI0为高速*/
    val = readl(GPIOI_OSPEEDR_PI);
    val &= ~(0X3 << 0); /* bit0:1 清零 */
    val |= (0x2 << 0); /* bit0:1 设置为 10 */
    writel(val, GPIOI_OSPEEDR_PI);
    /*12.设置PI0为上拉*/
    val = readl(GPIOI_PUPDR_PI);
    val &= ~(0X3 << 0); /* bit0:1 清零 */
    val |= (0x1 << 0); /*bit0:1 设置为 01 */
    writel(val,GPIOI_PUPDR_PI);
    /*13.默认关闭LED*/
    val = readl(GPIOI_BSRR_PI);
    val |= (0x1 << 0);
    writel(val, GPIOI_BSRR_PI);
    return 0;
fail_device:
    class_destroy(led.class);
fail_class:
    cdev_del(&led.cdev);
fail_cdev:
    unregister_chrdev_region(led.devid,LED_CNT); 
fail_devid:
    return ret;
}
/*platform驱动的remove函数*/
static int led_remove(struct platform_device *dev)
{
    /*取消映射*/
    led_unmap();
    /*注销设备节点*/
    device_destroy(led.class,led.devid);
    /*注销设备类*/
    class_destroy(led.class);
    /*注销字符设备对象*/
    cdev_del(&led.cdev);
    /*注销字符设备驱动*/
    unregister_chrdev_region(led.devid,LED_CNT); 
    return 0;
}

/*platform驱动结构体*/
static struct platform_driver led_driver = {
    .driver = {
    .name = "stm32mp1-led", /* 驱动名字,用于和设备匹配 */
    },
    .probe = led_probe,
    .remove = led_remove,
};

/*驱动模块加载*/
static int __init leddriver_init(void)
{
 return platform_driver_register(&led_driver);
}
/*驱动模块注销*/
static void __exit leddriver_exit(void)
{
 platform_driver_unregister(&led_driver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree,"Y");

3、LEDapp函数

这个之前就写过了,就不解释了!
ledApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3){
    printf("Error Usage!\r\n");
    return -1;
 }
    filename = argv[1];
    /* 打开 led 驱动 */
    fd = open(filename, O_RDWR);
    if(fd < 0){
    printf("file %s open failed!\r\n", argv[1]);
    return -1;
 }
    databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
    printf("LED Control Failed!\r\n");
    close(fd);
    return -1;
 }
    retvalue = close(fd); /* 关闭文件 */
    if(retvalue < 0){
    printf("file %s close failed!\r\n", argv[1]);
    return -1;
    }
    return 0;
 }

4、编写Makefile文件

makefile:

KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd) 
obj-m := leddevice.o
obj-m += leddriver.o
build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

其中跟以前不一样的是多了obj-m +:
obj-m := leddevice.o
obj-m += leddriver.o
该写法会生成两个独立模块:leddevice.ko 和 leddriver.ko

5、测试效果

我发现这两个命令:

modprobe leddevice.ko
modprobe leddriver.ko

发现 modprobe leddevice.ko 和 modprobe leddriver.ko 可以不分先后,这是因为 modprobe 能够处理模块之间的依赖关系。
leddevice.ko 可能是定义设备相关资源的模块,leddriver.ko 是实现驱动功能的模块。这两个模块之间存在一种相互依赖的关系,使得它们在加载顺序上有一定的灵活性。
Pasted image 20250403211109.png
Pasted image 20250403211147.png

./ledApp /dev/platled 1 //打开 LED 灯
./ledApp /dev/platled 0 //关闭 LED 灯

Pasted image 20250403211328.png
1106170780fa5cd43c34984bb650f4b.png
Pasted image 20250403211500.png
Pasted image 20250403211541.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s的BackUP备份

文章目录 1、kubeadm 安装的单 master 节点数据备份和恢复方式2、Velero 工具3、Velero 服务部署4、备份还原数据 ETCD备份/还原有多种类型&#xff0c;取决于你 k8s 集群的搭建方式 1、kubeadm 安装的单 master 节点数据备份和恢复方式 拷贝 etcdctl 至 master 节点&#xf…

Ruoyi-vue plus 5.2.2 flowble设计流程点击开始流程图错误

网关设置条件或者是事件删除后出现&#xff0c;点击网关节点无法找到下面的事件节点。 配置页面事件错误&#xff0c;点背景配置进去了事件&#xff0c;发现再次加载&#xff0c;或者删除的时候VUE页面无法加载。 解决方式&#xff1a;查看XML文件&#xff0c;这个节点是否存在…

如何快速入门物联网单片机开发?

背景 物联网单片机硬件开发涉及多个阶段&#xff0c;元器件是否“自己设计”取决于具体需求。以下是详细解答和学习方案&#xff1a; 一、元器件是否自己设计&#xff1f; 通用元器件&#xff1a; 大多数情况下&#xff0c;开发者直接使用现成的标准化元器件&#xff08;如电阻…

在 .NET 8 中使用自定义令牌身份验证掌握 SignalR Hub 安全性

最近在练习做一个 Web 开发项目&#xff0c;需要使用 WebSockets 传输数据&#xff0c;实现实时通信。这是一个 React.js 项目&#xff0c;后端是 .NET。 虽然 MSDN 提供了出色的顶级文档&#xff0c;但它通常缺少高级用例所需的低级细节。 一种这样的场景是使用自定义令牌对…

基于springboot+vue的二手车交易系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

React安装使用教程

ReactAnt Designrouteraxios安装完整教程 官网&#xff1a;React Native 中文网 使用React来编写原生应用的框架 一&#xff0c;安装 npx create-react-app my-app npm start npm eject 暴露项目优先提交代码 git add . git commit -m “搭建项目“ 4.yarn add node-sass …

Day20 -自动化信息收集工具--ARL灯塔的部署

准备&#xff1a; 纯净的Docker环境 ARL的包 一、Docker的部署 00x1 更新系统包 sudo apt update 00x2 安装必要的依赖包 sudo apt install -y apt-transport-https ca-certificates curl software-properties-common 00x3 下载docker和docker-compose apt-get install do…

精品可编辑PPT | “新基建”在数字化智慧高速公路中的支撑应用方案智慧建筑智慧交通解决方案施工行业解决方案

本文详细阐述了“新基建”在数字化智慧高速公路中的支撑应用方案&#xff0c;从政策背景出发&#xff0c;指出国家在交通领域的一系列发展规划和指导意见&#xff0c;强调了智慧交通建设的重要性。分析了当前高速公路存在的问题&#xff0c;如基础感知设施不足、协同水平低、服…

【瑞萨 RA-Eco-RA2E1-48PIN-V1.0 开发板测评】PWM

【瑞萨 RA-Eco-RA2E1-48PIN-V1.0 开发板测评】PWM 本文介绍了瑞萨 RA2E1 开发板使用内置时钟和定时器实现 PWM 输出以及呼吸灯的项目设计。 项目介绍 介绍了 PWM 和 RA2E1 的 PWM 资源。 PWM 脉冲宽度调制&#xff08;Pulse Width Modulation, PWM&#xff09;是一种对模拟…

数据流和重定向

1、数据流 不管正确或错误的数据都是默认输出到屏幕上&#xff0c;所以屏幕是混乱的。所以就需要用数据流重定向将这两 条数据分开。数据流重定向可以将标准输出和标准错误输出分别传送到其他的文件或设备去 标准输入&#xff08;standard input&#xff0c;简称stdin&#xff…

【GPT入门】第33 课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南

[TOC](【GPT入门】第33课 一文吃透 LangChain&#xff1a;chain 结合 with_fallbacks ([]) 的实战指南) 1. fallback概述 模型回退&#xff0c;可以设置在llm上&#xff0c;也可以设置在chain上&#xff0c;都带有with_fallbacks([])函数 2. llm的回退 2.1 代码 核心代码&…

【51单片机】2-7【I/O口】点亮数码管

1.硬件 51最小系统数码管模块 2.软件 静态数码管 #include "reg52.h" //头文件 typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8;sbit LSAP2^2;//位选 sbit LSBP2^3; sbit LSCP2^4;u8 code smgduan[17]{0x3f,0x06,0x5b,0x4f,0…

叁仟数智指路机器人的智能导航精度如何?

哇塞&#xff01;各位朋友们&#xff0c;来了解一下超厉害的叁仟数智指路机器人的智能导航精度吧&#xff01;它的精度可是因为采用了不同的定位技术而展现出独特魅力哦&#xff01; 先看蓝牙定位&#xff0c;这可是超实用的&#xff01;一般精度能保持在 3 - 5 米左右呢&…

华为存储考试内容HCIP-Storage

华为认证存储高级工程师 | Huawei Certified ICT Professional-Storage 是培训与认证具备对存储系统进行规划设计、部署实施、性能优化、管理运维和故障处理能力的存储高级工程师 通过该认证证明&#xff1a;工程师能理解闪存及分布式存储产品的相关功能及使用场景&#xff0…

A*算法详解(新手入门)——图文并茂,学习笔记分享

前言 本文是博主在学习A*算法时做的一个小案例&#xff0c;有不懂的地方可以私信博主一起讨论学习&#xff0c;由于博主水平有限&#xff0c;可能存在部分知识点遗漏或书写不够严谨&#xff0c;欢迎各位志同道合的朋友批评指教&#xff0c;博主定当虚心学习&#xff0c;感谢各…

初学STM32系统时钟设置

资料来自正点原子 在学习江科大教程示例的时候默认系统时钟是72MHZ&#xff0c;但是这个系统时钟是怎么过来的呢&#xff0c;通过时钟树以及相关的资料的学习可知&#xff0c;系统时钟它可以是内部RC时钟HSI 8MHZ通过锁相环倍频而来&#xff0c;也可以是外部晶振4-16MHZ通过锁相…

如何在 Windows 10 上安装 PyGame

PyGame 是 Python 编程语言中的一组跨平台模块&#xff0c;这意味着您可以在任何操作系统上安装它&#xff0c;这篇文章告诉您如何在 Windows 10 上安装 PyGame。 如何在 Windows 10 上安装 PyGame&#xff1f; PyGame 依赖于 Python&#xff0c;这意味着您必须在安装 PyGame …

STM32 × CLion 新建项目

STM32 CLion 新建项目 新建和配置一个 STM32 项目 1 创建项目 假如是 ST 官方开发板&#xff0c;比如 NUCLEO 板&#xff0c;选择从 ST 板创建 假如是单芯片或淘宝买的那种 F103 开发板&#xff0c;选择从 MCU 创建 2 STM CubeMX 配置 2.1 Pinout & Configuration 外…

WebSocket 详解:构建一个复杂的实时聊天应用

文章目录 一、前言二、WebSocket 基础2.1 WebSocket 与 HTTP 的区别2.2 WebSocket 的优点 三、搭建 WebSocket 服务端3.1 安装 ws 和 redis 库3.2 创建 WebSocket 服务端3.3 创建用户身份验证 四、前端实现 WebSocket 客户端4.1 创建 Vue 3 项目4.2 实现 WebSocket 连接和用户注…

python爬虫:小程序逆向实战教程

根据我之前发表的文章&#xff0c;我们进行延伸实战https://blog.csdn.net/weixin_64809364/article/details/146981598?spm1001.2014.3001.5501 1. 想要爬取什么小程序&#xff0c;我们进行搜索 2. 找到我们vx小程序的文件地址&#xff0c;我们就可以进行破解 破解步骤强看…