14.2linux中platform无设备树情况下驱动LED灯(详细编写程序)_csdn
我尽量讲的更详细,为了关注我的粉丝!!!
因为这跟之前的不一样,提出来驱动的分离和分层。
提到驱动分离和分层,必然可以联系上一章咱们知道的驱动-总线-设备。
在无设备树的状态下,必然要写寄存器地址,编写设备信息,所以这里我们要创建设备和驱动两个文件。
本章实验我们需要编写一个驱动模块和一个设备模块,其中驱动模块是 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、设备的注册和注销

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

在无设备树的情况下需要:
使用 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:


释放platform设备模块的时候此函数会执行(也就是执行platform_device_unregister(&leddevice));
1.4、添加寄存器物理地址信息

这个是STM32MP157正点原子上操作LED0的物理地址信息。
这个在之前的文章里面讲到过具体查找手册,感兴趣的朋友可以翻翻前面的博客!
REGISTER_LENGTH 定义了寄存器的长度为 4 字节(32 位),这在许多嵌入式系统中是比较常见的寄存器长度。
1.5、向platform_device配置资源信息
我们已经准备好了寄存器的物理地址了,需要将它利用到:
.resource = led_resources里面

这里我们用到了上一章讲到的:
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 4static 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、驱动的注册和注销

Linux 内核中平台驱动的模块初始化和退出函数,其作用是在模块加载时注册平台驱动,在模块卸载时注销平台驱动。
platform_driver_register:这是一个内核函数,其功能是向内核注册一个平台驱动。&led_driver是指向struct platform_driver结构体的指针,这个结构体定义了驱动的具体操作函数,例如probe、remove等。platform_driver_unregister:这是一个内核函数,其作用是从内核中注销一个平台驱动。&led_driver同样是指向struct platform_driver结构体的指针。
2.3、编写platform驱动结构体

在 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:它的
probe和remove函数接收struct device *类型的参数。这个参数提供的是设备的一些通用信息。这就好比你只知道这个设备的基本身份信息,但对于设备的一些特殊资源(如特定的内存区域、中断号等),你可能需要通过其他复杂的转换或者查询才能获取。 - struct platform_driver:它的
probe和remove函数接收struct platform_device *类型的参数。这个参数就像是一个 “豪华套餐”,除了包含设备的通用信息外,还能让你直接获取平台设备特有的资源信息。例如,你可以很方便地通过这个参数获取设备的内存资源(使用platform_get_resource函数),这对于初始化设备和配置设备的寄存器等操作非常方便。
这里我们用struct platform_driver内的probe和remove函数接收特定的内存区域。
一旦匹配上了,就可以直接执行probe函数!
2.4、配置probe 函数和remove 函数

其中struct platform_device *dev就是这里面的。
int (*probe)(struct platform_device *);int (*remove)(struct platform_device *);
2.4.1、配置字符设备结构体
到这里开始跟以前一样了!

同时也配置设备名字和数量。

2.4.2、注册字符设备

同时注销字符设备驱动:

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


可以执行到/dev
2.4.3.1这里可以添加字符驱动操作集:

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

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

添加cdev:

同时注销字符设备对象:

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


可以在/dev下创建platled。即/dev/platled。
同时注销字符设备类和设备节点:

2.4.5、获取资源信息
在这个地方原本是要获取设备树下的信息或者寄存器物理地址的,但是在这个章节我们就要利用leddevice.c文件传过来的设备信息相匹配,里面的数组信息从而传到这里的驱动里面来。

这里我们要在前面定义好变量:

从设备文件传过来的也是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、地址映射和取消映射
这里我们就要利用上面存储的地址来映射虚拟内存地址(这里以前就讲过)

其中的->start也就是设备文件中的资源:

大小也就是ressize决定!
其中等式左边的虚拟地址指针由下面的决定:

同理,也要在释放驱动的时候取消映射:
我们在这里利用模块化取消映射!


2.4.7、使能PI时钟

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

2.4.9、设置PI0为推挽模式

2.4.10、设置PI0为高速

2.4.11、设置PI0为上拉

2.4.12、配置错误信息

2.5、配置操作集函数


其中模块化led灯的开灯关灯状态:

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 是实现驱动功能的模块。这两个模块之间存在一种相互依赖的关系,使得它们在加载顺序上有一定的灵活性。


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




相关文章:
14.2linux中platform无设备树情况下驱动LED灯(详细编写程序)_csdn
我尽量讲的更详细,为了关注我的粉丝!!! 因为这跟之前的不一样,提出来驱动的分离和分层。 提到驱动分离和分层,必然可以联系上一章咱们知道的驱动-总线-设备。 在无设备树的状态下,必然要写寄存…...
K8s的BackUP备份
文章目录 1、kubeadm 安装的单 master 节点数据备份和恢复方式2、Velero 工具3、Velero 服务部署4、备份还原数据 ETCD备份/还原有多种类型,取决于你 k8s 集群的搭建方式 1、kubeadm 安装的单 master 节点数据备份和恢复方式 拷贝 etcdctl 至 master 节点…...
Ruoyi-vue plus 5.2.2 flowble设计流程点击开始流程图错误
网关设置条件或者是事件删除后出现,点击网关节点无法找到下面的事件节点。 配置页面事件错误,点背景配置进去了事件,发现再次加载,或者删除的时候VUE页面无法加载。 解决方式:查看XML文件,这个节点是否存在…...
如何快速入门物联网单片机开发?
背景 物联网单片机硬件开发涉及多个阶段,元器件是否“自己设计”取决于具体需求。以下是详细解答和学习方案: 一、元器件是否自己设计? 通用元器件: 大多数情况下,开发者直接使用现成的标准化元器件(如电阻…...
在 .NET 8 中使用自定义令牌身份验证掌握 SignalR Hub 安全性
最近在练习做一个 Web 开发项目,需要使用 WebSockets 传输数据,实现实时通信。这是一个 React.js 项目,后端是 .NET。 虽然 MSDN 提供了出色的顶级文档,但它通常缺少高级用例所需的低级细节。 一种这样的场景是使用自定义令牌对…...
《SQL赋能人工智能:解锁特征工程的隐秘力量》
在当今的科技发展进程中,人工智能(AI)已经成为推动各领域变革的核心驱动力。而在人工智能的庞大体系里,特征工程占据着举足轻重的地位,它是将原始数据转化为能够让模型有效学习的特征的关键环节。鲜有人深入探讨的是&a…...
基于springboot+vue的二手车交易系统
开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…...
React安装使用教程
ReactAnt Designrouteraxios安装完整教程 官网:React Native 中文网 使用React来编写原生应用的框架 一,安装 npx create-react-app my-app npm start npm eject 暴露项目优先提交代码 git add . git commit -m “搭建项目“ 4.yarn add node-sass …...
Day20 -自动化信息收集工具--ARL灯塔的部署
准备: 纯净的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…...
高级:分布式系统面试题精讲
一、引言 分布式系统在现代软件开发中占据重要地位,其设计和实现需要考虑多个关键因素。面试官通过相关问题,考察候选人对分布式系统核心概念的理解、实际应用能力以及在复杂场景下的问题解决能力。本文将深入分析分布式系统的CAP定理、一致性协议、分布…...
Java 实现冒泡排序:[通俗易懂的排序算法系列之二]
引言 大家好!欢迎来到我的排序算法系列第二篇。今天,我们将学习另一种非常基础且广为人知的排序算法——冒泡排序 (Bubble Sort)。 冒泡排序的名字非常形象,它模拟了水中气泡上升的过程:较小(或较大)的元素会像气泡一样,通过不断交换,逐渐“浮”到数组的一端。 什么是…...
精品可编辑PPT | “新基建”在数字化智慧高速公路中的支撑应用方案智慧建筑智慧交通解决方案施工行业解决方案
本文详细阐述了“新基建”在数字化智慧高速公路中的支撑应用方案,从政策背景出发,指出国家在交通领域的一系列发展规划和指导意见,强调了智慧交通建设的重要性。分析了当前高速公路存在的问题,如基础感知设施不足、协同水平低、服…...
【瑞萨 RA-Eco-RA2E1-48PIN-V1.0 开发板测评】PWM
【瑞萨 RA-Eco-RA2E1-48PIN-V1.0 开发板测评】PWM 本文介绍了瑞萨 RA2E1 开发板使用内置时钟和定时器实现 PWM 输出以及呼吸灯的项目设计。 项目介绍 介绍了 PWM 和 RA2E1 的 PWM 资源。 PWM 脉冲宽度调制(Pulse Width Modulation, PWM)是一种对模拟…...
高级:微服务架构面试题全攻略
一、引言 在现代软件开发中,微服务架构被广泛应用于构建复杂、可扩展的应用程序。面试官通过相关问题,考察候选人对微服务架构的理解、拆分原则的掌握、服务治理的能力以及API网关的运用等。本文将深入剖析微服务架构相关的面试题,结合实际开…...
数据流和重定向
1、数据流 不管正确或错误的数据都是默认输出到屏幕上,所以屏幕是混乱的。所以就需要用数据流重定向将这两 条数据分开。数据流重定向可以将标准输出和标准错误输出分别传送到其他的文件或设备去 标准输入(standard input,简称stdinÿ…...
Excel时间类型函数(包括today、date、eomonth、year、month、day、weekday、weeknum、datedif)
目录 1. TODAY()2. DATE()3. EOMONTH()4. YEAR()5. MONTH()6. DAY()7. WEEKDAY()8. WEEKNUM()9. DATEDIF()10.📌 函数扩展与应用11. 📚 时间函数基础概念与分类 Excel 提供了许多 日期与时间类型的函数,用于操作与处理日期或时间数据。这些函…...
【GPT入门】第33 课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南
[TOC](【GPT入门】第33课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南) 1. fallback概述 模型回退,可以设置在llm上,也可以设置在chain上,都带有with_fallbacks([])函数 2. llm的回退 2.1 代码 核心代码&…...
高级语言程序设计
第八章 结构体类型和自定义类型-CSDN博客 第九章 预编译处理-CSDN博客 第十章 文件-CSDN博客...
【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…...
叁仟数智指路机器人的智能导航精度如何?
哇塞!各位朋友们,来了解一下超厉害的叁仟数智指路机器人的智能导航精度吧!它的精度可是因为采用了不同的定位技术而展现出独特魅力哦! 先看蓝牙定位,这可是超实用的!一般精度能保持在 3 - 5 米左右呢&…...
华为存储考试内容HCIP-Storage
华为认证存储高级工程师 | Huawei Certified ICT Professional-Storage 是培训与认证具备对存储系统进行规划设计、部署实施、性能优化、管理运维和故障处理能力的存储高级工程师 通过该认证证明:工程师能理解闪存及分布式存储产品的相关功能及使用场景࿰…...
A*算法详解(新手入门)——图文并茂,学习笔记分享
前言 本文是博主在学习A*算法时做的一个小案例,有不懂的地方可以私信博主一起讨论学习,由于博主水平有限,可能存在部分知识点遗漏或书写不够严谨,欢迎各位志同道合的朋友批评指教,博主定当虚心学习,感谢各…...
初学STM32系统时钟设置
资料来自正点原子 在学习江科大教程示例的时候默认系统时钟是72MHZ,但是这个系统时钟是怎么过来的呢,通过时钟树以及相关的资料的学习可知,系统时钟它可以是内部RC时钟HSI 8MHZ通过锁相环倍频而来,也可以是外部晶振4-16MHZ通过锁相…...
如何在 Windows 10 上安装 PyGame
PyGame 是 Python 编程语言中的一组跨平台模块,这意味着您可以在任何操作系统上安装它,这篇文章告诉您如何在 Windows 10 上安装 PyGame。 如何在 Windows 10 上安装 PyGame? PyGame 依赖于 Python,这意味着您必须在安装 PyGame …...
STM32 × CLion 新建项目
STM32 CLion 新建项目 新建和配置一个 STM32 项目 1 创建项目 假如是 ST 官方开发板,比如 NUCLEO 板,选择从 ST 板创建 假如是单芯片或淘宝买的那种 F103 开发板,选择从 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 连接和用户注…...
详解七大排序
目录 一.直接插入排序 (1)基本思想 (2)算法步骤 (3)代码实现 (4)算法特性 (5)算法优化 (6)示例演示 二.希尔排序 (…...
python爬虫:小程序逆向实战教程
根据我之前发表的文章,我们进行延伸实战https://blog.csdn.net/weixin_64809364/article/details/146981598?spm1001.2014.3001.5501 1. 想要爬取什么小程序,我们进行搜索 2. 找到我们vx小程序的文件地址,我们就可以进行破解 破解步骤强看…...
day 8 TIM定时器
一、STM32 定时器概述 1. 定时器的概述定时器的基本功能,但是 STM32 的定时器除了具有定时功能之外,也具有定时器中断功能,还具有输入捕获(检测外部信号)以及输出比较功能(输出不同的脉冲)&…...
全星 研发项目管理APQP 软件:驱动汽车及制造业研发升级的数字化引擎
全星 APQP 软件:驱动汽车及制造业研发升级的数字化引擎 在汽车及制造业竞争白热化的当下,如何高效推进研发项目,同时确保严格合规,成为企业亟待解决的难题。 全星研发项目管理 APQP 软件系统,凭借卓越的功能与显著优势…...
