mount -t nfs -o nolock,vers=3 192.168.1.117:/home/book/nfs_rootfs /mnt

cat /proc/sys/kernel/printk

echo 8 > /proc/sys/kernel/printk

cp led.ko  ~/nfs_rootfs/

arm-buildroot-linux-gnueabihf-gcc app.c -o app -static

一、C语言LED驱动实验

1.设置处理器模式

  设置6ULL处于SVC模式下。设置CPSR寄存器的bit4-0,也就是M[4:0]=0x13。读写状态要用到MRS指令和MSR指令。MRS指令将CPSR寄存器数据读出到通用寄存器里面,MSR指令将通用寄存器的值写入到CPSR里面去。

2.设置SP指针

  处理器栈增长方式,对于A7而言是向下增长的。设置SP指向 0x200000+0x80000000=0x80200000。

3.跳转到C语言

  使用b指令,跳转到C语言函数,比如main函数

1.怎么进入到make menuconfig图形化界面?

首先进入到内核源码的路径下,然后输入make menuconfig即可打开这个界面。

2.make menuconfig图形化界面的操作

(1)搜索功能

  输入 / ,即可弹出搜索界面,然后输入我们想要搜索的内容即可。

(2)配置驱动的状态。

  a.把驱动编译成模块

  b.把驱动编译到内核里面,用*

  c.不编译

3.和make menuconfig有关的文件

Makefile  里面是编译规则,告诉我们在make 的时候要怎么编译,相当于菜的做饭。

Kconfig   内核配置的选项,相当于吃饭的菜单。

.config     配置完内核以后生成的配置选项,相当于我们点完的菜

4.make menuconfig 会读取哪个目录下的Kconfig文件。

arch/ $ARCH /目录下的Kconfig。

/arch/arm/configs#下面有好多的配置文件。相当于饭店的特色菜。

5.为什么要复制成.config而不复制成其他文件呢?

因为内核会默认读取Linux内核根目录下的.config作为默认的配置选项,所以不能改名字。

6.复制的这个默认的配置选项不符合要求咋办?

7.怎么和Makefile文件建立的关系呢?

内核中编译驱动

Kconfig的一个例子

source “drivers/redled/Kconfig”
config LED__4412
tristate “Led Support for GPIO Led”
depends on LEDS_CLASS
help

1.source “drivers/redled/Kconfig”, 他会包含 drivers/redled/这个路径下的驱动文件,方便我们对菜单进行管理
2.config LED__4412 配置选项的名称,CONFIG_LED_4412
3.tristate 表示的驱动的状态, 三种状态是把驱动编译成模块, 把驱动编译到内核, 不编译。 与之对应的还
有 bool 分别是编译到内核, 不编译
4 “Led Support for GPIO Led”make menuconfig 显示的名字
5 A depends on B 表示只有在选择 B 的时候才可以选择 A
比如我想直接去掉 LED 相关的驱动, 我们直接改.config 文件可以吗? 可以, 但是不推荐。 如果有依赖的话,
直接修改.config 是不成功的。
6.select 反向依赖, 该选项被选中时, 后面的定义也会被选中。
7.help

我们输入“ vim Kconfig” 命令编辑 Kconfig, Kconfig 写入以下内容:

config HELLO
  tristate "helloworld"
  help
  hello hello

 config HELLO 里就是Makefile里的名字。

Linux三大设备驱动

字符设备:IO的传输过程是以字符位单位的,没有缓冲的。比如 I2C,SPI  都是字符设备

块设备:IO的传输过程是以块为单位的。跟存储相关的,都属于块设备,比如 tf 卡

网络设备:与前两个不一样,是以socket套接字来访问的。

1.杂项设备是字符设备的一种,可以自动生成设备节点。

我们的系统里面有很多杂项设备。我们可以输入 cat /proc/misc 命令来查看。

2.杂项设备除了比字符设备简单,杂项设备的主设备号是相同的,均为10次,次设备号是不同的。主设备号相同就可以节省内核的资源。

3.主设备号和次设备号的概念

设备号包含主设备号和次设备号, 设备号是计算机识别设备的一种方式, 主设备号相同的就被视为同
一类设备, 主设备号在 Linux 系统里面是唯一的, 次设备号不一定唯一。

主设备可以比作电话号码的区号。比如北京区号010。次设备号相当于电话号码。

主设备号可以通过以下命令来查看, 前面的数字就是主设备号, 如下图所示:cat /proc/devices

4.杂项设备的描述

misc 设备用 miscdevice 结构体表示, miscdevice 结构体的定义在内核源码具体定义在include/linux/miscdevice.h 中, 内容如下:

struct miscdevice  {
int minor; //次设备号
const char *name;//设备节点的名字
const struct file_operations *fops;//文件操作集
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};

file_operations 文件操作集在定义在 include/linux/fs.h 下面

里面的一个结构体成员都对应一个调用。

extern int misc_register(struct miscdevice *misc);注册杂项设备
extern void misc_deregister(struct miscdevice *misc);注销杂项设备

5 注册杂项设备的流程。

(1)填充 miscdevice 这个结构体

(2)填充 file_operations 这个结构体

(3)注册杂项设备并生生成设备节点

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific devices */ struct file_operations misc_fops = { //文件操作集
.owner = THIS_MODULE }; struct miscdevice misc_dev = { //杂项设备结构体
.minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号
.name = "hello_misc",     //杂项设备名字是hello_misc
.fops = &misc_fops,      //文件操作集 }; static int misc_init(void) //注册杂项设备
{
int ret;
ret = misc_register(&misc_dev);
if (ret < 0)
{
printk("misc registe is error\n");
return -1;
} printk("misc registe is succed\n");
return 0;
} static void misc_exit(void)
{
misc_deregister(&misc_dev);
printk("misc bye bye\n");
} module_init(misc_init);
module_exit(misc_exit); MODULE_LICENSE("GPL");

应用层和内核层数据传输

Linux一切接文件!

文件对应的操作有打开,关闭,读写。

设备节点对应的操作有打开,关闭,读写

1.如果我们在应用层使用系统IO对设备节点进行打开,关闭,读写等操作会发生啥???

struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数。
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    //当我们在应用层 poll/select设备节点的时候,就会触发我们驱动里面poll这个函数
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    //当我们在应用层ioctl设备节点的时候,就会触发发我们驱动里面ioctl这个函数
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 
    //当我们在应用层open设备节点的时候,就会触发我们驱动里面open这个函数
    int (*open) (struct inode *, struct file *);
    //当我们在应用层close设备节点的时候,就会触发我们驱动里面close这个函数
    int (*release) (struct inode *, struct file *);

通过框图我们可以知道:

上层应用        设备节点        底层驱动

设备节点就是连接上层应用和底层驱动的桥梁

2.假如我们的file_operations里面没有read,我们在应用层read设备

3.我们的应用层和内核层是不能直接进行数据传输的。

copy to

open read write close

应用层从内核读数据

从设备中读取数据, 当用户层调用函数 read 时, 对应的, 内核驱动就会调用这个函数。

应用层从内核写数据

Linux物理地址到虚拟地址映射

1.操作一个寄存器, 可以定义一个指针来操作寄存器

  unsighted int *p = 0x12345678;
  *p=0x87654321;

但是在linux上不行,在Linux上,如果想要操作硬件,需要先把物理地址转换成虚拟地址。

因为Linux使能了MMU,所以我们在Linux上不能直接操作物理地址。

2.使能了MMU的好处???

(1)让虚拟地址成了可能

(2)可以让系统更加安全,因为有了MMU,我们上层应用看到的内存都是虚拟内存,我们的应用就不能直接访问硬件,所以这样就保证了系统安全。

3.MMU非常复杂,如何完成物理地址到虚拟地址的转换呢??

内核提供了函数

ioremap: 把物理地址转换成虚拟地址
iounmap: 释放掉 ioremap 映射的地址

static inline void __iomem *ioremap(phys_addr_t offset, size_t size)

phys_addr_t offset:映射物理地址的起始地址。

size_t size:要映射多大的内存空间

返回值:成功返回虚拟地址的首地址,失败返回NULL

static inline void iounmap(void __iomem *addr)

*addr:要取消映射的虚拟地址的首地址。

注意: 物理地址只能被映射一次, 多次映射会失败。

4.如何查看哪些物理地址被映射过了呢??

可以用  cat /proc/iomem

驱动模块传参

1.什么是驱动传参

驱动传参就是传递参数给我们的驱动。

ex:

insmod beep.ko  a=1

2.驱动传参的作用??

(1)设置驱动的相关参数,比如设置缓冲区的大小

(2)设置安全校验,防止我们写的驱动被人盗用

3.怎么给驱动传参数?

(1)传递普通的参数,比如char ,int 类型的

  函数:

  module_param(name, type, perm);

  参数:

    name  要传递进去的参数的名称

    type    类型

    perm   参数读写的权限

(2)传递数组

module_param_array(name, type, nump, perm);

参数:

    name  要传递进去的参数的名称

    type    类型

    nump   实际传入进去的参数的个数

    perm   参数读写的权限

#include <linux/init.h>
#include <linux/module.h> static int a;
static int b[5];
static int count;
//传递普通的参数 名字 类型 参数读写的权限
module_param(a, int, S_IRUSR);
//传递数组 名字 类型 传入进去参数的个数 参数读写的权限
module_param_array(b, int, &count, S_IRUSR);
static int hello_init(void)
{
int i;
for(i=0; i<count; i++)
{
printk("b[%d] = %d\n", i , b[i]);
}
printk("count = %d \n", count);
printk("a = %d \n", a);
return 0;
}
static void hello_exit(void)
{
// printk("a=%d \n", a);
// printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

(3)如果多传递进去参数,会发生什么?

    会报错!!!

   

1.字符设备和杂项设备的区别

1.杂项设备的主设备号是固定的,固定为10, 那么我们要学习的字符类设备号就需要自己或者系统来分配了。

杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点。

2.注册字符类设备号的两个方法。

第一种:静态分配一个设备号,使用的是:

register_chrdev_region(dev_t,  unsigned, const char *);

需要明确知道我们系统里面哪些设备号没有用。

参数:

dev_t:设备号的起始值。类型是 dev_t 类型

unsigned:次设备号的个数。

const char *:设备的名称

返回值:成功返回0,失败返回非0

dev_t 类型:

dev_t是用来保存设备号的,是一个32位数。

高12位是用来保存设备号,低12位用来保存次设备的号

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

Linux提供了几个宏定义来操作设备号

#define MINORBITS    20
次设备号的位数,一共是20位
#define MINORMASK ((1U << MINORBITS) - 1)
次设备号的掩码

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
在dev_t 里面获取我们的主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

在dev_t 里面获取我们的次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
将我们主设备号和次设备号组成一个dev_t类型。第一个参数是主设备号,第二个参数是次设备号。

第二种方法:动态分配

alloc_chrdev_region(dev_t * , unsigned, unsigned,  const char *);

参数:

dev_t *:保存生成的设备号

unsigned:我们请求的第一个次设备号,通常是0

unsigned:连续申请的设备号的个数。

const char *:设备名称

返回值:成功返回0,失败返回负数

使用动态分配会优先使用255到234

3.注销设备号

unregister_chrdev_region(dev_t, unsigned)

dev_t  分配设备号的起始地址

unsigned  申请的连续设备号的个数

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
static int major_num, minor_num; //定义主设备号和次设备号 //传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if(major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); //MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); }
return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");
  • 使用静态分配
  • 使用动态分配,不传递参数

建议使用动态申请。如果驱动很多人用,动态不会重复!!!

注册杂项设备:

misc_register(&misc_dev);

注销杂项设备:

misc_deregister(&misc_dev);

cdev结构体:描述字符设备的一个结构体

struct cdev{

  struct kobject kobj;

  struct module *owner;

  const struct file_operation *ops;

  struct list_head list;

  dev_t dev;

  unsigned int count;

};

步骤一:定义一个cdev结构体

步骤二:使用cdev_init函数初始化cdev结构体成员变量

void cdev_init(struct cdev *, struct file_operations *);

参数:

struct cdev *  要初始化的cdev

file_operations *  文件操作集

cdev->ops=fops;  //实际就是把文件操作集写个ops

步骤三:cdev_add函数注册到内核

cdev_add(struct cdev *, dev_t, unsigned);

第一个参数:cdev 的结构体指针

第二个:设备号

第三个:次设备号的数量

注销字符设备:

void cdev_del(struct cdev *);

字符设备注册完以后自动生成设备节点。

我们需要使用 mknod 命令创建一个设备节点

格式:

mknod 名称 类型 主设备号 次设备号

eg:

  mknod /dev/test  c  245  0

32

高12 主   低20 次设备号

手动创建

  可以通过命令mknod创建设备节点。

  mknod命令格式:

  mknod  设备节点名称   设备类型(字符设备用c,块设备用b)   主设备号   次设备号

举例: mknod /dev/test c236 0

2.在注册设备的时候自动创建。

可以通过mdev机制实现设备节点的自动创建与删除。

自动创建设备节点

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
static int major_num, minor_num; //定义主设备号和次设备号 struct cdev cdev;
struct class *class;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
} struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open}; static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if (major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num);
} cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);

return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
class_destroy(class);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");


#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> #define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //定义静态注册设备的名称
#define DEVICE_ANAME "achrdev" //定义动态注册设备的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class" //宏定义类名
#define DEVICE_NODE_NAME "chrdev_test" //宏定义设备节点的名字 static int major_num, minor_num; //定义主设备号和次设备号
dev_t dev_num; //设备号 struct cdev cdev;
struct class *class;
struct device *device;
//传递普通的参数 名字 类型 参数读写的权限
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR); int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open\n");
return 0;
} struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open}; static int hello_init(void)
{
dev_t dev_num;
int ret;
/* 如果传进去了主设备号可以用静态方法,如果没有传进去用动态方法*/
if (major_num)
{
/** 静态注册设备号 */
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num); dev_num = MKDEV(major_num, minor_num); // MKDEV 将主设备号和次设备号合并为一个设备号
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if (ret < 0)
{
printk("register_chrdev_region error\n");
}
printk("register_chrdev_region OK\n");
}
else
{ //保存申请的设备号,次设备号起始地址,设备号数量,设备名字
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if (ret < 0)
{
printk("alloc_chrdev_region error\n");
}
printk("alloc_chrdev_region OK\n"); major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d \n", major_num);
printk("minor_num = %d \n", minor_num);
} cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
//在class类下创建设备 设备号 设备节点的名字
device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME); return 0;
} static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
device_destroy(class, dev_num);
class_destroy(class);
printk("bye bye! \n");
} module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

平台总线模型介绍

1.什么是平台总线模型?

  平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线,不是真实的导线。

  平台总线模型就是把原来的驱动C文件分成了两个C文件,一个是

2.为什么会有平台总线模型?

(1)可以提高代码的重用性

(2)减少重复性代码。

设备            总线            驱动

device.c                        driver.c

3.平台总线的优点

4.怎么编写以平台总线模型设计的驱动?

一个是device.c,一个是driver.c,分别注册device.c和driver.c。

平台总线是以名字来匹配的,实际上就是字符串比较。

注册Platform设备

1.平台总线注册一个device

device.c里面写的是硬件资源,这里的硬件资源是指寄存器的地址,中断号,时钟等硬件资源。

 在Linux内核里面,我们是用一个结构体来描述硬件资源的。

struct platform_device
{
const char *name; //平台总线进行匹配的时候用到的name, /sys/bus......
int id; //设备id,一般写-1
bool id_auto;
struct device dev;//内嵌的device结构体
u32 num_resources; //资源的个数
struct resource *resource;//device里面的硬件资源
};

struct resource {
    resource_size_t start; //资源的起始
    resource_size_t end;   //资源的结束
    const char *name;      //资源的名字
    unsigned long flags;   //资源的类型
    struct resource *parent, *sibling, *child;
};
 

#define IORESOURCE_IO         IO 的内存
#define IORESOURCE_MEM    表述一段物理内存
#define IORESOURCE_IRQ      表示中断

注册platform驱动

编写driver.c思路

  首先定义一个platform_driver结构体变量,然后去实现结构体中的各个成员变量,那么当我们的driver和device匹配成功的时候,就会执行probe函数,所以匹配成功以后的重点就在于probe函数的编写。

struct platform_driver {
  /*当 driver 和 device 匹配成功的时候,就会执行 probe 函数*/
  int (*probe)(struct platform_device *);
  /*当 driver 和 device 任意一个 remove 的时候,就会执行这个函数*/
  int (*remove)(struct platform_device *);
  /*当设备收到 shutdown 命令的时候,就会执行这个函数*/
  void (*shutdown)(struct platform_device *);
  /*当设备收到 suspend 命令的时候,就会执行这个函数*/
  int (*suspend)(struct platform_device *, pm_message_t state);
  /*当设备收到 resume 命令的时候,就会执行这个函数*/
  int (*resume)(struct platform_device *);
  //内置的 device_driver 结构体
  struct device_driver driver;
//该设备驱动支持的设备的列表, 他是通过这个指针去指向 platform_device_id 类型的数组
  const struct platform_device_id *id_table;
};
struct device_driver{
const char *name; //这个是我们匹配时用到的名字
struct bus_type *bus;
struct module *owner;
}

改改

id_table的优先级比driver的.name这个高。

driver.c里的.name的名字要和device.c里面的名字一样

1.driver.c和device.c里面都要定义一个platform_driver和platform_device结构体变量。

2.匹配成功,执行probe函数。。id_table的优先级比driver.name的优先级要高。

平台总线probe函数的编写

编写probe函数的思路:

(1)从 device.c 里面获得硬件资源,因为我们的平台总线将驱动拆成了俩部分,第一部分是 device.c,另一部分是 driver.c。那么匹配成功了之后,driver.c 要从 device.c 中获得硬件资源,那么 driver.c 就是在 probe 函数中获得的。

方法一: 直接获取,不推荐

int beep_probe(struct platform_device *pdev){
printk("beep_probe\n");
return 0;
}

方法二:只用函数获得

extern struct resource * platform_get_resource(struct platform_device *, unsigned int ,unsigned int)

(2)获得硬件资源之后,就可以在 probe 函数中注册杂项/字符设备,完善 file_operation 结构体,并生成设备节点。

注册之前要先登记:

  request()

ls  /sys/bus/platform/devices/

设备树中添加自定义节点

make dtbs  

cp arch/arm/boot/dts/100ask_imx6ul l_mini.dtb ~/nfs_rootfs/decp

sudo cp arch/arm/boot/dts/100ask_i mx6ull_mini.dtb   ~/nfs_rootfs/demo_hcl/

获得设备树文件节点里面资源的步骤:

步骤一:查找我们要找的节点。
步骤二:获取我们需要的属性值。

 与查找节点有关的 OF 函数有 3 个

  • inline struct device_node *of_find_node_by_path(const char *path)
  • struct device_node *of_get_parent(const struct device_node *node)
  • struct device_node *of_get_next_child(const struct device_node *node struct device_node *prev)

获取属性值的 of 函数

  • of_find_property 函数     property *of_find_property(const struct device_node *np,const char *name,int *lenp)

设备树下的platform总线

优先级顺序:

Pinctrl 子系统和 GPIO 子系统

匹配成功后,进到了probe函数。查找我们要查找的节点

注册杂项设备和字符设备,GPIO用杂项设备完成

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h> #include <linux/miscdevice.h>
#include <linux/fs.h> /* for kernel specific devices */
#include <linux/uaccess.h>
#include <linux/io.h> #include <linux/gpio.h>
#include <linux/of_gpio.h> int size;
u32 out_values[2];
struct device_node *test_device_node;
struct property *test_node_property;
unsigned int *virt_gpio_dr;
int led_gpio = 0; int misc_open(struct inode *inode, struct file *file)
{
printk("hello misc_open\n");
return 0;
} int misc_release(struct inode *inode, struct file *file)
{
printk("hello misc_release bye bye\n");
return 0;
} ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = "hahaha";
if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0) //成功了是0
{
printk("copy_to_user error\n");
return -1;
}
return 0;
} ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if (copy_from_user(kbuf, ubuf, size) != 0) //成功 了是0
{
printk("copy_from_user error\n");
return -1;
}
printk("kbuf is %s\n", kbuf); if (kbuf[0] == 1)
gpio_set_value(led_gpio, 1);
else if (kbuf[0] == 0)
gpio_set_value(led_gpio, 0); return 0;
} struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write }; struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc", //
.fops = &misc_fops}; int led_probe(struct platform_device *pdev)
{
int ret = 0;
printk("led_probe\n"); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} led_gpio = of_get_named_gpio(test_device_node, "led-gpio", 0);
if(led_gpio <0){
printk("of_get_named_gpio is error~ \n");
return -1;
}
printk("led_gpio is %d \n", led_gpio); ret = gpio_request(led_gpio, "led");
if(ret <0){
printk("gpio_request is error~ \n");
return -1;
} gpio_direction_output(led_gpio, 1); //杂项设备
ret = misc_register(&misc_dev);
if (ret < 0)
{
printk("misc registe is error\n");
return -1;
} return 0;
} int led_remove(struct platform_device *pdev)
{
misc_deregister(&misc_dev);
gpio_free(led_gpio);
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "test1234"},
{}
};
struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

IOCTL接口

1.什么是unlocked_ioctl接口?

 unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。

2.unlocked_ioctl 和 read/write 函数有什么异同呢?

相同点:都可以往内核写数据。

不同点:read 函数只能完成读的功能,write 只能完成写的功能。读取大数据的时候效率高。ioctl 既可以读也可以写,读取大数据的时候效率不高。

3.unlocked_ioctl 接口命令规则

第一个分区:0-7,  命令的编号,范围是 0-255.
第二个分区:8-15,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。
第三个分区:16-29  表示传递的数据大小。

第四个分区:30-31 代表读写的方向。
00:表示用户程序和驱动程序没有数据传递
10:表示用户程序从驱动里面读数据
01:表示用户程序向驱动里面写数据
11:先写数据到驱动里面然后在从驱动里面把数据读出来。

中断基础概念

在设备树里面配置中断的时候只需要两个步骤即可:

1.把管脚设置为gpio功能。

2.使用 interrupt-parent 和 interrupts 属性来描述中断。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h> struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu;//GPIO编号
int irq; //中断号 irqreturn_t test_key(int irq, void *args)
{
printk("test_key hhh\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
// if (test_device_node == NULL)
// {
// printk("of_find_node_by_path is error~ \n");
// return -1;
// }
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} gpio_direction_input(gpio_nu);
irq = gpio_to_irq(gpio_nu); ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} printk("irq is %d \n", irq); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table }; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

按键中断

中断下文之tasklet

步骤一:定义一个 tasklet 结构体
步骤二:动态初始化 tasklet
步骤三:编写 tasklet 绑定的函数
步骤四:在中断上文调用 tasklet
步骤五:卸载模块的时候删除 tasklet

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h> struct device_node *test_device_node;
struct property *test_node_property;
struct tasklet_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号 void test(unsigned long data)
{
int i = data;
printk("i is %d\n", i);
while(i--)
printk("test_key is %d\n"
, i);
}
irqreturn_t test_key(int irq, void *args)
{
printk("start~\n");
tasklet_schedule(&key_test);
printk("end~\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
irq = gpio_to_irq(gpio_nu);
//irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} tasklet_init(&key_test, test, 100); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
tasklet_kill(&key_test);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

等待队列

阻塞:  操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。

非阻塞:  操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

irqreturn_t test_key(int irq, void *args)
{
value = !value;
wq_flags = 1;
wake_up(&key_wq);
return IRQ_RETVAL(IRQ_HANDLED);
}

唤醒的时候不会立马解除阻塞,先去判断wq_flags是否达成,如果还是0,不会解除,如果标志位为1 解除。

工作队列

工作队列(workqueue)是实现中断下文的机制之一,是一种将工作推后执行的形式。那工作队列和我们之前学的 tasklet 机制有什么不同呢?tasklet 也是实现中断下文的机制。他们俩个最主要的区别是 tasklet不能休眠,而工作队列是可以休眠的。所以,tasklet 可以用来处理比较耗时间的事情,而工作队列可以处理非常复杂并且更耗时间的事情。

Linux 系统在启动期间会创建内核线程,该线程创建以后就处于 sleep 状态,然后这个线程会一直去队列里面读,看看有没有任务,如果有就执行,如果没有就休眠。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/workqueue.h> struct device_node *test_device_node;
struct property *test_node_property;
//struct tasklet_struct key_test;
struct work_struct key_test;
int gpio_nu;//GPIO编号
int irq; //中断号 void test(struct work_struct *data)
{
int i = 100;
printk("i is %d\n", i);
while(i--)
printk("test_key is %d\n"
, i);
}
irqreturn_t test_key(int irq, void *args)
{
printk("start~\n");
//tasklet_schedule(&key_test);
//调度
schedule_work(&key_test);
printk("end~\n");
return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); //printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if(gpio_nu < 0){
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
//irq = gpio_to_irq(gpio_nu);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if(ret < 0){
printk("request_irq is error~ \n");
return -1;
} //tasklet_init(&key_test, test, 100);
INIT_WORK(&key_test, test); return 0;
} int led_remove(struct platform_device *pdev)
{
printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[]={
{.compatible = "keys"},
{}
}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table
},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table
}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
//tasklet_kill(&key_test);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

内核定时器

expires=jiffies+ 1*HZ

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h> static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0
);

/*现在是10:00,定时了一分钟,到了10:01进入了
超时处理函数,超时处理函数里面又使用了mod_timer()
把它设置了10:02,10:02进来设置成10:03...*/

static void timer_function(unsigned long data)
{
printk("This is time_function!\n");
mod_timer(&test_timer, jiffies+ 3*HZ);
}
static int hello_init(void)
{
printk("hello world!\n");
test_timer.expires=jiffies+ 3*HZ;
add_timer(&test_timer);//启动定时器
return 0;
} static void hello_exit(void)
{
printk("bye bye\n");
del_timer(&test_timer);
}
module_init(hello_init);
module_exit(hello_exit); MODULE_LICENSE("GPL");

按键消抖实验

按键按下,第一次进入中断,会定时20ms,20ms超时后,就会在超时处理函数判断当前电容是否低电平,如果是低电平,就执行想要执行的

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h> static void timer_function(unsigned long data);
DEFINE_TIMER(test_timer, timer_function, 0, 0); struct device_node *test_device_node;
struct property *test_node_property;
int gpio_nu; // GPIO编号
int irq; //中断号 static void timer_function(unsigned long data)
{
printk("This is tim e_function!\n");
//mod_timer(&test_timer, jiffies + 1 * HZ); //周期性定时 } irqreturn_t test_key(int irq, void *args)
{
printk("test_key hhh\n");
//20ms
test_timer.expires = jiffies + msecs_to_jiffies(20); //定义时间点
add_timer(&test_timer);//添加到内核里面

return IRQ_HANDLED;
} int led_probe(struct platform_device *pdev)
{
int ret = 0; printk("led_probe\n"); // printk("node name is %s\n", pdev->dev.of_node->name); /*查找我们要的节点*/
test_device_node = of_find_node_by_path("/test_key");
if (test_device_node == NULL)
{
printk("of_find_node_by_path is error~ \n");
return -1;
} /** 获得GPIO的编号*/
gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);
if (gpio_nu < 0)
{
printk("of_get_named_gpio is error~ \n");
return -1;
} /*设置GPIO的方向*/
gpio_direction_input(gpio_nu); /*获得中断号*/
// irq = gpio_to_irq(gpio_nu);
irq = irq_of_parse_and_map(test_device_node, 0);
printk("irq is %d \n", irq); /*申请中断*/
ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);
if (ret < 0)
{
printk("request_irq is error~ \n");
return -1;
} // printk("irq is %d \n", irq); return 0;
} int led_remove(struct platform_device *pdev)
{ printk("led_remove \n");
return 0;
} const struct platform_device_id led_id_table = {
.name = "led_test"}; const struct of_device_id of_match_table[] = {
{.compatible = "keys"},
{}}; struct platform_driver led_device = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.owner = THIS_MODULE,
.name = "hhh",
.of_match_table = of_match_table},
// const struct platform_device_id *id_table;
//如果id_table的优先级比driver的.name这个高,就不能匹配成功
.id_table = &led_id_table}; static int led_driver_init(void)
{
int ret = 0;
ret = platform_driver_register(&led_device);
if (ret < 0)
{
printk("platform_driver_register error \n");
return ret;
}
printk("platform_driver_register OK~ \n");
return 0;
} static void led_driver_exit(void)
{
printk("bye bye\n");
free_irq(irq, NULL);
platform_driver_unregister(&led_device);
}
module_init(led_driver_init);
module_exit(led_driver_exit); MODULE_LICENSE("GPL");

输入子系统

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd;
struct input_event test_event; fd = open("/dev/input/event1", O_RDWR); if (fd < 0)
{
perror("open error");
return fd;
} while(1)
{
read(fd, &test_event, sizeof(test_event));
if(test_event.type == EV_KEY){
printf("type is %#x\n", test_event.type);
printf("value is %#x\n", test_event.value);
}
}
return 0;
}

(二)

应用层实现I2C通信

IO 模型

A钓鱼不干其他的事情,等着

B钓鱼看书,画画...

C拿了几个鱼竿钓鱼

D在鱼竿上系了一个铃铛

A请助手B钓鱼

IMX6Ull驱动的更多相关文章

  1. IMX6ULL开发板Linux_WIFI驱动实验

    1.在迅为i.MX6ULL开发板上使用的是 usb 接口的 RTL8723 wifi 模块,原理图如下所示:可以看到 RTL8723 模块的接口非常简单,只有 DP1 和 DM1 连接到 usb HU ...

  2. 【NXP开发板应用—智能插排】4. PWM驱动

    [前言] 首先感谢深圳市米尔科技有限公司举办的这次活动并予以本人参加这次活动的机会,以往接触过嵌入式,但那都是皮毛,最多刷个系统之类的,可以说对于嵌入式系统开发这件事情是相当非常陌生的,这次活动为我提 ...

  3. 第二次写linux驱动总结

    第一次写驱动是在去年,2019年十月份左右.当时是看着韦老师的视频一步步完成的.其中经历了很多error.搭建环境花费了很多精力.时间来到了2020年2月19日星期三,韦老师新视频出来了,我跟着再来了 ...

  4. 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发

    在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...

  5. 【linux】驱动-8-一文解决设备树

    目录 前言 8. Linux设备树 8.1 设备树简介 8.2 设备树框架 8.2.1 设备树格式 8.2.1.1 DTS 文件布局 8.2.1.2 node 格式 8.2.1.3 propertie ...

  6. 【linux】驱动-11-gpio子系统

    目录 前言 11. gpio子系统 11.1 操作步骤 11.1.1 新版 API 操作流程 11.1.2 旧版 API 操作流程 11.2 设备树中使用gpio子系统 11.3 GPIO 子系统 A ...

  7. rtl8188eu 驱动移植

    测试平台 宿主机平台:Ubuntu 16.04.6 目标机:iMX6ULL 目标机内核:Linux 4.1.15 rtl8188eu 驱动移植 在网上下载Linux版的驱动源码: wifi驱动的实现有 ...

  8. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  9. 浅谈我对DDD领域驱动设计的理解

    从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...

  10. “四核”驱动的“三维”导航 -- 淘宝新UI(需求分析篇)

    前言 孔子说:"软件是对客观世界的抽象". 首先声明,这里的"三维导航"和地图没一毛钱关系,"四核驱动"和硬件也没关系,而是为了复杂的应用而 ...

随机推荐

  1. Docker安装部署Mysql8(以作数据持久化)

    1.创建容器并进行持久化处理 #拉取镜像 docker pull mysql:8.0.20 #启动镜像,用于拷贝配置文件到宿主机 docker run -p 3306:3306 --name mysq ...

  2. css background背景透明

    background: transparent; background: rgba(0, 0, 0, 0.8);

  3. ArcGIS for Android 地图图文查询

    ArcGIS for Android 地图图文查询 1.前期项目准备 1.1. 创建新工程 新建一个空活动项目 选择语言.平台,修改命名等 1.2. 添加ArcGIS SDK build.gradle ...

  4. 流浪地球2:AI人工智能+数字生命+元宇宙

    推荐:将 NSDT场景编辑器 加入你的3D开发工具链 剧情介绍 太阳危机   太阳即将老化膨胀,吞没太阳系,地球上的人类构思了各种生存计划:其一是"数字生命计划",该计划制造强大的 ...

  5. 批量下载Landsat遥感影像的方法

      本文介绍在USGS网站批量下载Landsat系列遥感影像的方法.   首先,打开EarthExplorer的官网,首先完成注册与登录.   接下来,点击左侧"Search Criteri ...

  6. 自己动手从零写桌面操作系统GrapeOS系列教程——8.x86介绍

    由于GrapeOS目前只支持x86,所以本讲我们简单的介绍一下x86. 百度百科上是这样介绍的:x86泛指一系列基于Intel 8086且向后兼容的中央处理器指令集架构. 中央处理器就是我们平时说的C ...

  7. kubebuilder简明教程

    一.operator概述 Operator 是 Kubernetes 的扩展软件,它利用 定制资源 管理应用及其组件. Operator 遵循 Kubernetes 的理念,特别是在控制器 方面[1] ...

  8. uniapp 返回上一页事件监听

    需求 点击订单页---订单详情页----点击修改地址---来到地址列表,修改地址--- 成功以后返回订单详情页,并且更新界面数据 首先在订单详情页   设置事件  监听另一界面触发事件后  就会执行并 ...

  9. vue实现瀑布流

    <template> <div id="app"> <ul> <li ref='waterfallItem' v-for="(i ...

  10. IDEA激活 重置试用30天

    转载于 https://www.cnblogs.com/renlywen/p/14216325.html jetbrains-agent已经停止,现在又出现了新的激活方式,重置试用时间,以下为操作步骤 ...