Linux第92步_如何编写“设备树”下的platform设备驱动
Linux字符设备驱动,新字符设备驱动和设备树下的GPIO驱动,都是配置IO引脚所使用的GPIO寄存器,驱动开发方式和裸机没啥区别。Limux内核提供了pinctrl和gpio子系统用于GPIO驱动,借助它可简化GPIO驱动开发。
对GPIO进行读写操作,使用pinctrl和gpio子系统来实现,但像I2C、SPI、LCD这些复杂外设的驱动就要使用“设备树下的platform驱动”来进行开发。学完设备树下的platform驱动,说明你已经掌握linux下最常见的驱动编写方式。
Linux驱动开发不需要严格按照框架开发,但遵循框架可以大大简化开发的过程,提高代码的可维护性。Linux驱动开发遵循一定的框架和规范,主要是为了确保驱动的稳定性和兼容性。Linux驱动框架为开发者提供了一套标准的接口和函数,使得驱动的开发更加规范化和模块化。例如,字符设备驱动、块设备驱动和网络设备驱动都有各自的规范和标准,开发者需要实现特定的函数,如:open、close、write和read等。
platform设备驱动,也叫平台设备驱动。
对于STM32MP1来说,若要编写“设备树”下的platform设备驱动,需要先修改“pinctrl-stm32.c”这个文件,否则在使用pinctrl的时候,会提示GPIO引脚无法申请到。
1、使用Vscode打开文件夹,见下图:
修改“pinctrl-stm32.c”文件
1)、打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,点击“转到”,点击“转到文件”。见下图:
2)、输入“pinctrl-stm32.c”,得到下图:
3)、点击文本框下面的“pinctrl-stm32.c”,就可以打开这个文件了。
4)、点击“编辑”,点击“查找”,在弹出的文本框中输入“pinmux_ops stm32_pmx_ops”,就会显示“pinmux_ops stm32_pmx_ops结构”,见下图:
5)、将“.strict= true”修改为“.strict= false”,见下图的红线框:
6)、点击“文件”,点击“保存”
7)、在VSCode终端,输入“make uImage dtbs LOADADDR=0XC2000040 -j8
回车”,执行编译“Image”和“dtbs”,并指定装载的起始地址为0XC2000040,j8表示指定采用8线程执行。“make dtbs”,用来指定编译设备树。
2、创建“引脚的pinctl节点”为“led_pins_a”
1)、点击“转到”,点击“转到文件”,在文本框中输入“stm32mp15-pinctrl.dtsi”,见下图:
2)、点击“文件结果”前面的“stm32mp15-pinctrl.dtsi”,就可以打开这个文件了。见下图:
3)、点击“编辑”,点击“查找”,再弹出的文本框中输入“&pinctrl”,然后按一下“回车”,就会跳至“pinctrl”节点。
见下图:
4)、输入内容如下:
led_pins_a: gpioled-0 {
pins {
pinmux = <STM32_PINMUX('I', 0, GPIO)>;
/*设置 PI0复用为GPIO功能*/
drive-push-pull;/*设置PI0为推挽输出*/
bias-pull-up; /*设置PI0为内部上拉*/
utput-high; /*设置PI0默认输出为高电平*/
slew-rate = <0>;/*设置PI0的速度为0档,也就是最慢*/
};
};
这是led的“pinctrl节点”,见下图:
pinmux = <STM32_PINMUX('I', 0, GPIO)>;表示将PI0复用GPIO
pinmux = <STM32_PINMUX('F', 14, ANALOG)>;表示将PF14复用为ADC2_in6
pinmux = <STM32_PINMUX('F', 13, AF6)>;
表示将PF13重新映射到DFSDM1_DATIN3引脚;
pinmux = <STM32_PINMUX('H', 2, AF14)>;表示将PH2重映射到LCD_R0引脚;
3、检查引脚是否被其他设备复用
STM32MP1的一个引脚可以复用为多种功能,由于“stm32mp15-pimctrl.dtsi”是ST公司根据自己的开发板编写的,因此PI0这个引脚就可能被ST公司用作其他功能。因此,我们需要修改这个PI0引脚。
1)、打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,点击“转到”,点击“转到文件”,在文本框中输入“stm32mp15-pinctrl.dtsi”。
2)、点击“文件结果”前面的“stm32mp15-pinctrl.dtsi”,就可以打开这个文件了。
3)、点击“编辑”,点击“查找”,再弹出的文本框中输入“STM32_PINMUX('I', 0,”,然后按一下“回车”,就会跳至“STM32_PINMUX('I', 0,”,见下图:
4)、将上图中,画红框中的语句屏蔽掉,确保所使用的设备树中,一个引脚只复用为一个功能!
4、检查 GPIO是否被其他设备占用
检查引脚是否被其他设备复用后,然后检查 GPIO是否被其他设备占用。因为我们是在ST官方提供的设备树上修改的,因此还要检査一下当PI0作为GPIO的时候,ST官方是否将这个GPIO分配给其他设备。其实对于PI0这个引脚来说不会的,因为ST官方将其复用为了LCD_G5,所以也就不存在说将其在作为GPIO分配给其他设备。但是我们在实际开发中要考虑到这一点,说不定其他的引脚就会被分配给某个设备做GPIO,而我们没有检查,导致两个设备共用一个GPIO,那么肯定有一个因为申请不到GPIO而导致驱动无法工作。所以当我们将一个引脚用作GPIO的时候,一定要检査一下“当前设备树”里面是否有其他设备也使用到了这个GPIO,保证设备树中只有一个设备树在使用这个GPIO。
5、在设备树文件中创建“设备节点”为“gpioled”
1)、打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,点击“确定”,点击“转到”,点击“转到文件”,在文本框中输入“stm32mp157d-atk.dts”。打开这个设备树文件,见下图:
2)、输入内容如下:
gpioled {
compatible = "zhang,led";
pinctrl-names = "default";
status = "okay";
pinctrl-0 = <&led_pins_a>;
/*表示gpioled节点的父节点为led_pins_a*/
led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>;
/*PI0默认输出低电平,这个0表示端口引脚的下标*/
};
注意:在编写platfonm驱动的时候of_match_table属性表中要有“zhang,led”。
见下图的红方框:
注意:在编写platfonm驱动的时候of_match_table属性表中要有“zhang,led”。
6、编译
1)、在VSCode终端,输入“cd linux-5.4.31/回车”
输入“make uImage dtbs LOADADDR=0XC2000040 -j8回车”,执行编译“Image”和“dtbs”,并指定装载的起始地址为0XC2000040,j8表示指定采用8线程执行。“make dtbs”,用来指定编译设备树。
2)、输入“ls arch/arm/boot/uImage -l”
查看是否生成了新的“uImage”文件
3)、输入“ls arch/arm/boot/dts/stm32mp157d-atk.dtb -l”
查看是否生成了新的“stm32mp157d-atk.dtb”文件
拷贝输出的文件:
4)、输入“cp arch/arm/boot/uImage /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC;
5)、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/atk-mp1/linux/bootfs/ -f回车”,执行文件拷贝,准备烧录到EMMC
6)、输入“cp arch/arm/boot/uImage /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
7)、输入“cp arch/arm/boot/dts/stm32mp157d-atk.dtb /home/zgq/linux/tftpboot/ -f回车”,执行文件拷贝,准备从tftp下载;
8)、输入“ls -l /home/zgq/linux/atk-mp1/linux/bootfs/回车”,查看“/home/zgq/linux/atk-mp1/linux/bootfs/”目录下的所有文件和文件夹
9)、输入“ls -l /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”目录下的所有文件和文件夹
输入“chmod 777 /home/zgq/linux/tftpboot/stm32mp157d-atk.dtb回车”
给“stm32mp157d-atk.dtb”文件赋予可执行权限
输入“chmod 777 /home/zgq/linux/tftpboot/uImage回车” ,给“uImage”文件赋予可执行权限
输入“ls /home/zgq/linux/tftpboot/回车”,查看“/home/zgq/linux/tftpboot/”
8、编写“platform驱动”
“引脚的pinctl节点led_pins_a” 和“设备节点gpioled”创建完成后,就表示“设备已经准备好了”,接下来就要编写“platform驱动”。
1)、创建Platform_GpioLED目录
输入“cd /home/zgq/linux/Linux_Drivers/回车”
切换到“/home/zgq/linux/Linux_Drivers/”
输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”
输入“mkdir Platform_GpioLED回车”,创建“Platform_GpioLED”目录
输入“ls回车”,查看“/home/zgq/linux/Linux_Drivers/”
2)、新建Platform_GpioLED_Driver.c
打开虚拟机上“VSCode”,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“Linux_Drivers”,点击“Platform_GpioLED”。
3)、点击“确定”。点击“文件”,点击“新建文件”,点击“文件”,点击“另存为”。然后在“名称”右边的文本框中输入“Platform_GpioLED_Driver.c”,点击“保存”
Platform_GpioLED_Driver.c文件内容如下:
#include <linux/types.h>
/* 数据类型重命名 使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t 使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t */
#include <linux/ide.h> //使能copy_from_user(),copy_to_user()
#include <linux/module.h> //使能LEDDriver_init(),LEDDriver_exit()
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include <linux/cdev.h> //使能cdev结构
#include <linux/device.h>//使能class结构和device结构
#include <linux/of_gpio.h>
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()
#include <linux/fs.h> //使能fasync_struct结构
#include <linux/platform_device.h> //使能platform_driver结构
#define LEDDEV_CNT 1 //设备数量
#define LEDDEV_NAME "platform_led" //定义设备的名字
#define LEDOFF 0
#define LEDON 1
/* leddev设备结构体 */
struct leddev_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
struct device_node *node;/*LED设备节点*/
int gpio_led; /*LED灯GPIO标号*/
};
struct leddev_dev leddev; /* led设备 */
//函数功能:sta=LEDON表示开灯, sta=LEDOFF表示关灯
void led_switch(u8 sta)
{
if (sta == LEDON ) gpio_set_value(leddev.gpio_led,0);
//设置输出低电平
else if (sta == LEDOFF) gpio_set_value(leddev.gpio_led,1);
//设置输出高电平
}
static int led_gpio_init(struct device_node *nd)
{
int ret;
/* 从设备树中获取GPIO */
leddev.gpio_led = of_get_named_gpio(nd, "led-gpio", 0);
//在gpio_led节点中,led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>
//nd是指定的“设备节点”
//propname="led-gpio",给定要读取的属性名字
//Index=0,给定的GPIO索引为0
//返回值:正值,获取到的GPIO编号;负值,失败。
if(!gpio_is_valid(leddev.gpio_led)) {
printk(KERN_ERR "leddev: Failed to get led-gpio\n");
return -EINVAL;
}
/* 申请使用GPIO */
ret = gpio_request(leddev.gpio_led, "LED0");
//gpio=leddev.gpio_led,指定要申请的“gpio编号”
//Label="LED0",给这个gpio引脚设置个名字为"LED0"
//返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;
if (ret)
{
printk(KERN_ERR "led: Failed to request led-gpio\n");
return ret;
}
/* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
gpio_direction_output(leddev.gpio_led,1);
//设置LED引脚输出高电平
return 0;
}
/* 打开设备 */
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);
//将buf[]中的前cnt个字节拷贝到databuf[]中
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0];
if (ledstat == LEDON) { led_switch(LEDON); }
else if (ledstat == LEDOFF) { led_switch(LEDOFF); }
return 0;
}
/* 设备操作函数 */
/*声明file_operations结构变量led_fops*/
/*它是指向设备操作函数的集合的变量*/
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
//函数功能:
//flatform驱动的probe函数,当驱动与设备匹配以后此函数就会被执行
//参数dev指向platform设备
//返回值为0,表示成功;其他负值,失败
static int led_probe(struct platform_device *pdev)
{
int ret;
printk("led driver and device was matched!\r\n");
/* 初始化 LED */
ret = led_gpio_init(pdev->dev.of_node);
if(ret < 0) return ret;
/* 1、设置设备号 */
ret = alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);
//注册字符设备驱动
//leddev.devid:保存申请到的设备号
//0:次设备号的起始地址
//LEDDEV_CNT:要申请的设备数量;
//LEDDEV_NAME:表示“设备名字”
if(ret < 0)
{
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", LEDDEV_NAME, ret);
goto free_gpio;
}
/* 2、初始化cdev */
leddev.cdev.owner = THIS_MODULE;
cdev_init(&leddev.cdev, &led_fops);
//初始化字符设备
//leddev.cdev是等待初始化的结构体变量
//led_fops就是字符设备文件操作函数集合
/* 3、添加一个cdev */
ret = cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
//添加字符设备
// &leddev.cdev表示指向要添加的字符设备,即字符设备结构testcdev变量
//leddev.devid表示设备号
//LEDDEV_CNT表示需要添加的设备数量
if(ret < 0) goto del_unregister;
/* 4、创建类*/
leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
//创建类 //使用THIS_MODULE将owner指针指向当前这个模块 //LEDDEV_NAME是类名字 //返回值是指向结构体class的指针,也就是创建的类
if (IS_ERR(leddev.class)) goto del_cdev;
/* 5、创建设备*/
leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
//创建设备
//设备要创建在leddev.class类下面
//NULL表示没有父设备
//leddev.devid是设备号;
//参数drvdata=NULL,设备没有使用数据
//LEDDEV_NAME是设备名字
//如果设置fmt=LEDDEV_NAME的话,就会生成/dev/LEDDEV_NAME设备文件。
//返回值就是创建好的设备。
if (IS_ERR(leddev.device)) goto destroy_class;
return 0;
destroy_class:
class_destroy(leddev.class);
//注销类,删除类
//leddev.class就是要删除的类
del_cdev:
cdev_del(&leddev.cdev);
//删除字符设备
//&leddev.cdev表示指向需要删除的字符设备,即字符设备结构leddev.cdev变量
del_unregister:
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
/* 释放设备号 */
//leddev.devid:需要释放的设备号
//LEDDEV_CNT:需要释放的次设备号数量;
free_gpio:
gpio_free(leddev.gpio_led);
/* 注销GPIO,leddev.gpio_led:要释放的“gpio编号”*/
return -EIO;
}
/*
//用来移除platform驱动
//dev指向platform设备
//返回值为0,成功;其他负值,失败
*/
static int led_remove(struct platform_device *dev)
{
gpio_set_value(leddev.gpio_led, 1);/*先关闭LED,再卸载驱动*/
gpio_free(leddev.gpio_led);
/* 注销GPIO,leddev.gpio_led:要释放的“gpio编号”*/
cdev_del(&leddev.cdev);
//删除字符设备
//&leddev.cdev表示指向需要删除的字符设备,即字符设备结构leddev.cdev变量
unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
/*注销设备号,释放设备号 */
//leddev.devid:需要释放的设备号
//LEDDEV_CNT:需要释放的次设备号数量;
device_destroy(leddev.class, leddev.devid);
/* 注销设备 */
//删除创建的设备
//参数leddev.class是设备所处的类,leddev.devid是设备号
class_destroy(leddev.class);
//注销类,删除类
//leddev.class就是要删除的类
return 0;
}
/* 匹配列表 */
//驱动中的compatible属性要和和设备树中的compatible属性相匹配。
static const struct of_device_id led_of_match[] = {
{ .compatible = "zhang,led" /*这是驱动中的compatible属性*/},
{/*这是一个空元素,在编写of_device_id时最后一个元素一定要为空*/ }
};
MODULE_DEVICE_TABLE(of, led_of_match);
//声明led_of_match为设备匹配表
/* platform驱动结构体 */
static struct platform_driver led_driver = {
.driver = {
.name = "stm32mp1-led",
/* 驱动名字stm32mp1-led.ko,用于和设备匹配 */
.of_match_table = led_of_match,
/*of_match_table匹配列表为led_of_match*/
},
.probe = led_probe, /*调用led_probe()函数*/
.remove = led_remove,/*调用led_remove()函数*/
};
/*驱动的入口函数*/
static int __init leddriver_init(void)
{
return platform_driver_register(&led_driver);
//向Linux内核注册一个platform驱动
//led_driver:要注册的 platform 驱动
//返回值:负数,失败;0,成功;
}
/*驱动的出口函数 */
static void __exit leddriver_exit(void)
{
platform_driver_unregister(&led_driver);
//卸载一个platform驱动
//led_driver:要卸载的platform驱动
}
module_init(leddriver_init);
//声明leddriver_init()为驱动入口函数,注意module_init为小写字母;
module_exit(leddriver_exit);
//声明leddriver_exit()为驱动出口函数,注意module_init为小写字母;
MODULE_LICENSE("GPL"); //LICENSE采用“GPL协议”
MODULE_AUTHOR("zgq"); //添加作者名字
MODULE_DESCRIPTION("This is Platform_Driver_Test_Module!");//模块介绍
MODULE_INFO(intree, "Y");
//去除显示“loading out-of-tree module taints kernel.”
9、新建PLATFORM_GPIOLED_APP.c
PLATFORM_GPIOLED_APP.c文件内容如下:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
//APP运行命令:./PLATFORM_GPIOLED_APP MyPLATFORM_GPIOLED 1表示打开LED
//APP运行命令:./PLATFORM_GPIOLED_APP MyPLATFORM_GPIOLED 0表示关闭LED
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/*
参数argc: argv[]数组元素个数
参数argv[]:是一个指针数组
返回值: 0 成功;其他 失败
*/
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;
}
//argv[]是指向输入参数“./PLATFORM_GPIOLED_App” “/dev/MyPLATFORM_GPIOLED” “1”
filename = argv[1];
//argv[1]指向字符串“/dev/MyPLATFORM_GPIOLED”
fd = open(filename, O_RDWR);
//如果打开“/dev/PLATFORM_GPIOLED”文件成功,则fd为“文件描述符”
//fd=0表示标准输入流; fd=1表示标准输出流;fd=2表示错误输出流;
if(fd < 0)
{
printf("Can't open file %s\r\n", filename);
return -1;
}
databuf[0]= atoi(argv[2]); /* 写入的数据,是数字的,表示打开或关闭 */
retvalue = write(fd, databuf, 1);
//将databuf[]中前1个字节发送给用户
//返回值大于0表示写入的字节数;
//返回值等于0表示没有写入任何数据;
//返回值小于0表示写入失败
if(retvalue < 0)
{
printf("write file %s failed!\r\n", filename);
close(fd);
//fd表示要关闭的“文件描述符”
//返回值等于0表示关闭成功
//返回值小于0表示关闭失败
return -1;
}
/* 关闭设备 */
retvalue = close(fd);
//fd表示要关闭的“文件描述符”
//返回值等于0表示关闭成功
//返回值小于0表示关闭失败
if(retvalue < 0)
{
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}
10、新建Makefile
Makefile文件如下:
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
MyAPP := PLATFORM_GPIOLED_APP
MyPLATFORM_GPIOLED-objs = Platform_GpioLED_Driver.o
obj-m := MyPLATFORM_GPIOLED.o
CC := arm-none-linux-gnueabihf-gcc
drv:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
app:
$(CC) $(MyAPP).c -o $(MyAPP)
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm $(MyAPP)
install:
sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -f
11、添加“c_cpp_properties.json”
按下“Ctrl+Shift+P”,打开VSCode控制台,然后输入“C/C++:Edit Configurations(JSON)”,打开以后会自动在“.vscode ”目录下生成一个名为“c_cpp_properties.json” 的文件。
修改c_cpp_properties.json内容如下所示:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31",
"/home/zgq/linux/Linux_Drivers",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include",
"/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "gnu11",
"cppStandard": "gnu++14",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}
12、编译
输入“make clean回车”
输入“make drv回车”
输入“make app回车”
输入“make install回车”
输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”查看是存在“PLATFORM_GPIOLED_APP和MySpinlockLED_Module.ko”
13)、拷贝驱动
输入“cd /home/zgq/linux/Linux_Drivers/Platform_GpioLED/”
输入“ls”
输入“sudo cp MyPLATFORM_GPIOLED.ko /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入密码“123456回车”
输入“sudo cp PLATFORM_GPIOLED_APP /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入“cd /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31”
输入“ls -l”
14)、测试
启动开发板,从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/”
在nfs挂载中,切换到“/lib/modules/5.4.31/”目录,
注意:“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中。
输入“ls”
输入“depmod”,驱动在第一次执行时,需要运行“depmod”
输入“lsmod”查看有哪些驱动在工作;
若MyPLATFORM_GPIOLED.ko在工作,则输入“rmmod MyPLATFORM_GPIOLED.ko回车”卸载“MyPLATFORM_GPIOLED.ko”
输入“modprobe MyPLATFORM_GPIOLED.ko”,加载“MyPLATFORM_GPIOLED.ko”模块
输入“cd /sys/bus/platform/drivers/”
切换到“/sys/bus/platform/drivers/”目录。
我们在Platform_GpioLED_Driver.c中设置led_driver 中的“.name”字段为“stm32mp1-led”,因此在“/sys/bus/platform/drivers/”目录下存在名为“stmm32mpl-led”这个文件。同理,“/sys/bus/platform/devices/”目录下也存在gpioled的设备文件,也就是设备树中gpioled这个节点。
输入“ls stm32mp1-led -l回车”
输入“cd /sys/bus/platform/devices/”
切换到“/sys/bus/platform/devices/”目录。
输入“ls gpioled -l回车”
输入“cd /lib/modules/5.4.31/”
输入“ls /dev/platform_led -l回车”,发现节点文件“/dev/platform_led”
输入“./PLATFORM_GPIOLED_APP /dev/platform_led 1回车”执行写1开灯
输入“./PLATFORM_GPIOLED_APP /dev/platform_led 0回车”执行写0关灯
操作完成,则执行卸载模块:
输入“rmmod MyPLATFORM_GPIOLED.ko”,卸载“MyPLATFORM_GPIOLED.ko”模块
注意:输入“rmmod platform_led”也可以卸载“MyPLATFORM_GPIOLED.ko”模块
输入“lsmod”查看有哪些驱动在工作。
输入“modprobe MyPLATFORM_GPIOLED.ko”,加载“MyPLATFORM_GPIOLED.ko”模块
输入“cat /proc/devices回车”查询设备号
platform_led的设备号为241
相关文章:

Linux第92步_如何编写“设备树”下的platform设备驱动
Linux字符设备驱动,新字符设备驱动和设备树下的GPIO驱动,都是配置IO引脚所使用的GPIO寄存器,驱动开发方式和裸机没啥区别。Limux内核提供了pinctrl和gpio子系统用于GPIO驱动,借助它可简化GPIO驱动开发。 对GPIO进行读写操作&#…...

从零开始学习 sg200x 多核开发之 eth0 MAC 地址修改
在 sophpi 中,默认网卡 eth0 的 MAC 地址未配置,是随机生成的。这样就会导致每次重启之后,MAC 地址会改变,从而导致通过 DHCP 获取 IP 地址每次也都在变化。 查看 MAC 地址 前文提到 eth0 自动使能并通过 DHCP 获取 IP 地址&…...

JMeter与大模型融合应用之JMeter日志分析服务化实战应用
JMeter与大模型融合应用之JMeter日志分析服务化 引言 在当今的互联网时代,网站和应用程序的性能直接影响到用户的体验和业务的成功。为了保证系统的稳定性和高效性,性能测试成为了软件开发过程中的一个重要环节。在这其中,Apache JMeter作为一款开源的性能测试工具,凭借其…...

AtCoder Beginner Contest 380(A-F)
比赛链接:AtCoder Beginner Contest 380(A-F) A - 123233 题意 给出一个数字 N N N,问这个数字中是否 1 1 1 恰好出现了 1 1 1 次, 2 2 2 恰好出现了 2 2 2 次, 3 3 3 恰好出现了 3 3 3 次。 数据范围 100000 ≤ N ≤ 99…...

多线程-阻塞队列
目录 阻塞队列 消息队列 阻塞队列用于生产者消费者模型 概念 实现原理 生产者消费者主要优势 缺陷 阻塞队列的实现 1.写一个普通队列 2.加上线程安全和阻塞等待 3.解决代码中的问题 阻塞队列 阻塞队列,是带有线程安全功能的队列,拥有队列先进…...

el-table合并单元格之后,再进行隔行换色的且覆盖表格行鼠标移入的背景色的实现
el-table 中有现成的隔行换色功能,只要增加 stripe 属性即可。但是如果有单元格合并的话,这个属性就不可用了。这时候我们就需要动点小心思了。 基于相同字段进行合并 单元格合并:基于表头中的某一列,具有相同值的个数相加进行合…...

java模拟键盘实现selenium上下左右键 table中的左右滚动条实现滚动
在这篇文章中,我们将学习如何使用Java编程语言模拟键盘输入,特别是模拟上下左右方向键的操作。这是一个很有趣的项目,尤其适合刚入行的开发者。我们将分步进行,接下来,我们会通过表格展示整个实现过程,然后…...

SDF,一个从1978年运行至今的公共Unix Shell
关于SDF 最近发现了一个很古老的公共Unix Shell服务器,这个项目从1978年运行至今,如果对操作系统,对Unix感兴趣,可以进去玩一玩体验一下 SDF Public Access UNIX System - Free Shell Account and Shell Access 注册方式 我一…...

前馈神经网络 (Feedforward Neural Network, FNN)
代码功能 网络定义: 使用 torch.nn 构建了一个简单的前馈神经网络。 隐藏层使用 ReLU 激活函数,输出层使用 Sigmoid 函数(适用于二分类问题)。 数据生成: 使用经典的 XOR 问题作为数据集。 数据点为二维输入ÿ…...

【Python进阶】Python中的数据库交互:使用SQLite进行本地数据存储
1、数据持久化与访问效率 数据持久化是指程序运行过程中产生的数据能够长期保存,即使程序关闭或系统重启后仍可读取和修改。通过数据库,我们可以确保数据持久化的同时,实现数据的快速访问。例如,银行系统需要实时更新账户余额&am…...

ZooKeeper单机、集群模式搭建教程
单点配置 ZooKeeper在启动的时候,默认会读取/conf/zoo.cfg配置文件,该文件缺失会报错。因此,我们需要在将容器/conf/挂载出来,在制定的目录下,添加zoo.cfg文件。 zoo.cfg logback.xml 配置文件的信息可以从二进制包…...

函数指针示例
目录: 代码: main.c #include <stdio.h> #include <stdlib.h>int Max(int x, int y); int Min(int x, int y);int main(int argc, char**argv) {int x,y;scanf("%d",&x);scanf("%d",&y);int select;printf(&q…...

vue如何实现组件切换
一、使用条件渲染 (v-if) <template><div><button click"currentView ComponentA">Show Component A</button><button click"currentView ComponentB">Show Component B</button><component-a v-if"curren…...

计算机视觉 1-8章 (硕士)
文章目录 零、前言1.先行课程:python、深度学习、数字图像处理2.查文献3.环境安装 第一章:概论1.计算机视觉的概念2.机器学习 第二章:图像处理相关基础1.图像的概念2.图像处理3.滤波器4.卷积神经网络CNN5.图像的多层表示:图像金字…...

整数唯一分解定理
整数唯一分解定理,也称为算术基本定理,是由德国数学家高斯在其著作《算术研究》中首次提出的。本文回顾整数唯一分解定理以及对应的几个重要结论。 一、整数唯一分解定理 整数唯一分解定理,也称为算术基本定理,是数论中的一个重…...

Grass脚本2倍速多账号
前言,小编也是第一次撸空投,我是抱着试一试的态度,梦想总是要有的万一白嫖了呢 Grass 是什么? Grass 扩展程序是一款创新的工具,它可以帮助您释放未使用的网络资源的力量。 通过分享您的剩余带宽,您可以赚…...

15分钟学 Go 第 56 天:架构设计基本原则
第56天:架构设计基本原则 学习目标 理解和掌握基本的架构设计原则,以提升软件系统的可维护性、可扩展性和可重用性。 内容提纲 架构设计原则概述常见架构设计原则 单一职责原则 (SRP)开放/封闭原则 (OCP)里氏替换原则 (LSP)接口分离原则 (ISP)依赖反…...

HTML5 Video(视频)
HTML5 Video(视频) HTML5视频是现代网页设计中不可或缺的一部分,它允许开发者在网页中嵌入视频内容,为用户提供丰富多样的媒体体验。本文将深入探讨HTML5视频的各个方面,包括其基本用法、支持的格式、自定义播放器、浏览器兼容性以及最佳实践。 一、HTML5视频的基本用法 …...

开源模型应用落地-qwen模型小试-Qwen2.5-7B-Instruct-tool usage入门-串行调用多个tools(三)
一、前言 Qwen-Agent 是一个利用开源语言模型Qwen的工具使用、规划和记忆功能的框架。其模块化设计允许开发人员创建具有特定功能的定制代理,为各种应用程序提供了坚实的基础。同时,开发者可以利用 Qwen-Agent 的原子组件构建智能代理,以理解和响应用户查询。 本篇将介绍如何…...

MySQL:表设计
表的设计 从需求中获得类,类对应到数据库中的实体,实体在数据库中表现为一张一张的表,类中的属性就对应着表中的字段(也就是表中的列) 表设计的三大范式: 在数据库设计中,三大范式࿰…...

173. 二叉搜索树迭代器【 力扣(LeetCode) 】
文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 173. 二叉搜索树迭代器 一、题目描述 实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器: BSTIterato…...

大三学生实习面试经历(1)
最近听了一位学长的建议,不能等一切都准备好再去开始,于是就开始了简历投递,恰好简历过了某小厂的初筛,开启了线上面试,记录了一些问题: (通过面试也确实了解到了自己在某些方面确实做的还不够…...

【论文复现】STM32设计的物联网智能鱼缸
📝个人主页🌹:Eternity._ 🌹🌹期待您的关注 🌹🌹 ❀STM32设计的物联网智能鱼缸 【1】项目功能介绍【2】设计需求总结【3】项目硬件模块组成 1.2 设计思路【1】整体设计思路【2】ESP8266工作模式…...

常见长选项和短选项对应表
长选项和短选项的等效形式 在命令行工具中,这种长选项(如--delete)和短选项(如-d)等效的情况很常见。例如--verbose和-v(用于输出详细信息),--quiet和-q(用于安静模式&a…...

Ubuntu24 上安装搜狗输入法
link 首先在终端中依次输入以下代码 sudo apt update sudo apt install fcitx 找到语言支持 在终端中依次输入 sudo cp /usr/share/applications/fcitx.desktop /etc/xdg/autostart/ sudo apt purge ibus 进入网页 搜狗输入法linux-首页 shurufa.sogou.com/linux 找到刚才下…...

【AI图像生成网站Golang】JWT认证与令牌桶算法
AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与调试(等待更新) 三、JWT认证与令牌桶算法 在现代后端开发中,用户认证和接口限流是确保系统安全性和性能的两大关键要素…...
关于强化学习的一份介绍
在这篇文章中,我将介绍与强化学习有关的一些东西,具体包括相关概念、k-摇臂机、强化学习的种类等。 一、基本概念 所谓强化学习就是去学习:做什么才能使得数值化的收益信号最大化。学习者不会被告知应该采取什么动作,而是必须通…...

Python3.11.9+selenium,获取图片验证码以及输入验证码数字
Python3.11.9+selenium,获取图片验证码以及输入验证码数字 1、遇到问题:登录或修改密码需要验证码 2、解决办法: 2.1、安装ddddocr pip install ddddocr 2.2、解析验证码函数 import ddddocr def get_capcha_text():#获取验证码图片ele_pic = driver.find_element(By.XPAT…...

Flutter:事件队列,异步操作,链式调用。
Flutter分2种队列 1、事件队列:异步的处理,按顺序执行 import package:flutter/material.dart; main(){testFuture1();testFuture2(); }// 按顺序执行处理A->B->C testFuture1() async {Future((){return 任务A;}).then((value){print(按顺序执行&…...

从零开始学习 sg200x 多核开发之 eth0 自动使能并配置静态IP
前文提到 sophpi 默认没有使能有线网络,需要手工配置: [rootsg200x]~# ifconfig eth0 up [rootsg200x]~# udhcpc -i eth0 [rootsg200x]~# ifconfig eth0 Link encap:Ethernet HWaddr EA:BD:18:08:1E:87 inet addr:192.168.188.142 Bcast:192.1…...