一、LED引脚说明
寄存器地址地图:
原理图:
关于MOS管的说明:
总结:当GPIO0_B5这个引脚输出高电平的时候,对应的N-MOS管导通—LED点亮
当GPIO0_B5这个引脚输出低电平的时候,对应的N-MOS管截止---LED熄灭
二、GPIO的时钟控制寄存器
分布在2组寄存器中
1、PMUCRU_CLKGATE_CON1
PMUCRU_CLKGATE_CON1寄存器通常用于控制特定时钟的使能状态。通过设置或清除该寄存器中的特定位,可以启用或禁用与这些位相关联的时钟(位0可能表示时钟0的使能状态,位1表示时钟1的使能状态)。
操作示例:
假设要启用与PMUCRU_CLKGATE_CON1寄存器中第3位相关联的时钟,可能需要执行以下操作:
读取PMUCRU_CLKGATE_CON1寄存器的当前值。readl()
将第3位设置为1(启用时钟)。
将修改后的值写回PMUCRU_CLKGATE_CON1寄存器。writel()
补充:
readl
原型:
static inline u32 readl(const volatile void __iomem *addr)
功能:
用作读取某个地址处的数据,通常用于从内存映射的I/O地址(如硬件寄存器)中读取一个值。
参数:
const volatile void __iomem *addr:这是函数的参数,一个指向常量、易失性、内存映射I/O地址的指针。
内核源码基本形式:
static inline u32 readl(const volatile void __iomem *addr)
{
return *(const volatile u32 *) addr;
}
write
原型:
void __writel(u32 val, volatile void __iomem *addr)
功能:
用于向内存映射的I/O(MMIO)地址写入一个32位无符号整数
参数:
u32 val:一个32位无符号整数,表示要写入的值。
volatile void __iomem *addr:一个指向内存映射I/O地址的易失性指针。这个指针指向的地址是要写入val值的地方。
内核源码的基本形式:
void __writel(u32 val, volatile void __iomem *addr)
{
void __iomem *a = __isamem_convert_addr(addr);
if ((unsigned long)addr & 3)
BUG();
__raw_writew(val, a);
__raw_writew(val >> 16, a + 4);
}
封装启动时钟函数:
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*1-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*0-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
2、CRU_CLKGATE_CON31
注意:这些时钟寄存器配置的时候,当将对应的位置1,关闭时钟;置0,开启时钟
三、GPIO输出数据寄存器
GPIO_SWPORTA_DR
作用:输出—控制对应GPIO引脚的电平状态
封装函数:
控制对应的GPIO0_B5输出高电平—对应的LED灯点亮或低电平LED灯熄灭--位13
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1 << 13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
四、GPIO的数据方向寄存器
GPIO_SWPORTA_DDR
五、GPIO的输入数据寄存器
GPIO_EXT_PORTA
作用:获取对应引脚的电平状态
六、相关外设的基地址
PMUCRU外设的基地址 0xFF75 0000 占用空间是64KB--65536
CRU外设的基地址 0xFF76 0000 占用的空间64KB
GPIO0的外设基地址 0xFF72 0000 占用的空间64KB
GPIO1的外设基地址 0xFF73 0000 占用的空间64KB
GPIO2的外设基地址 0xFF78 0000 占用的空间32KB
GPIO3的外设基地址 0xFF78 8000 占用的空间32KB
GPIO4的外设基地址 0xFF79 0000 占用的空间32KB
将获取到的基地址进行宏定义 :
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
注意:地址偏移都是在获取到对应的虚拟地址后的操作
偏移地址怎么找?
先找到对应的组或类->base_addr起始基地址,再找到需要偏移器件的偏移量->offset
base_addr+offset
七、相关函数
在Linux系统中CPU不能直接访问寄存器物理地址,而是通过内存管理单元MMU将物理地址空间映射为对应的虚拟地址空间,CPU通过虚拟地址来访问相关的数据。
ioremap
函数功能:
将指定物理内存空间映射为内核虚拟内存空间
头文件:
#include <linux/io.h>
函数原型:
void __iomem *ioremap(phys_addr_t offset, size_t size);
函数参数:
phys_addr_t offset:物理空间的起始地址—物理基地址
size_t size:指定空间的字节数
函数返回值:
成功 返回映射成功的虚拟内存空间的起始地址
失败 NULL
内核源码的基本形式:
void __iomem *ioremap_nocache(unsigned long phys_addr, unsigned long size)
{
unsigned long last_addr, addr;
unsigned long offset = phys_addr & ~PAGE_MASK;
struct vm_struct *area;
}
ioremap_nocache
函数功能:
将物理地址空间映射到内核虚拟地址空间
头文件:
#include <linux/io.h>
函数原型:
void __iomem *ioremap_nocache(unsigned long phys_addr, unsigned long size);
函数参数:
unsigned long phys_addr:要映射的物理地址的起始位置。
unsigned long size:要映射的内存区域的大小。
函数返回值:
成功 返回一个指向映射区域的虚拟地址的指针。
失败 返回 NULL。
内核源码的基本形式:
void __iomem *ioremap_nocache(unsigned long phys_addr, unsigned long size)
{
unsigned long last_addr, addr;
unsigned long offset = phys_addr & ~PAGE_MASK;
struct vm_struct *area;
pgprot_t prot = __pgprot(_PAGE_PRESENT|_PAGE_READ|_PAGE_WRITE
|(__HEXAGON_C_DEV << 6));
last_addr = phys_addr + size - 1;
/* Wrapping not allowed */
if (!size || (last_addr < phys_addr))
return NULL;
/* Rounds up to next page size, including whole-page offset */
size = PAGE_ALIGN(offset + size);
area = get_vm_area(size, VM_IOREMAP);
addr = (unsigned long)area->addr;
if (ioremap_page_range(addr, addr+size, phys_addr, prot)) {
vunmap((void *)addr);
return NULL;
}
return (void __iomem *) (offset + addr);
}
iounmap
函数功能:
取消映射,一般情况下放到出口函数里面
头文件:
#include <linux/io.h>
函数原型:
iounmap(void __iomem *addr)
函数参数:
void __iomem *addr:待解除映射的虚拟空间的起始地址
函数返回值:无
注意:当虚拟内存空间无用时,需要释放资源,否则会造成虚拟内存空间的紧张。
说明:
__iomem通常用于修饰指向 I/O 空间的指针。例如,在使用 ioremap 或 ioremap_nocache 函数时,返回的指针通常会被声明为 void __iomem * 类型。这是因为这些函数用于将物理 I/O 地址映射到内核虚拟地址空间,而映射后的地址需要被明确标记为 I/O 空间地址
出于安全性考虑,用户空间和内核空间不能直接进行数据交换。因此:Linux驱动程序不能与用户程序直接进行数据交换。需要通过专门的函数来完成数据交换-->copy_from_user 或copy_to_user,这些函数会对数据缓冲区进行安全检查,避免非法访问。
copy_from_user
函数功能:
从用户空间将数据复制到内核空间
头文件:
#include <linux/uaccess.h>
函数原型:
unsinged long __must_check copy_from_user(void *to,const void __user *from,unsigned long n)
函数参数:
void *to:内核数据缓冲区的起始地址
const void __user *from:用户空间数据缓存区起始地址
unsigned long n:待复制数据的字节数
函数返回值:
成功 0
失败 >0 表示没有赋值成功的字节数
内核源码的基本形式:
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
{
if (likely(access_ok(VERIFY_READ, from, n)))
n = __copy_from_user(to, from, n);
else
memset(to, 0, n);
return n;
}
copy_to_user
函数功能:
从内核空间将数据复制到用户空间
头文件:
#include <linux/uaccess.h>
函数原型:
unsinged long __must_check copy_to_user(void *to,const void __user *from,unsigned long n)
函数参数:
void *to:用户数据缓冲区的起始地址
const void __user *from:内核空间数据缓存区起始地址
unsigned long n:待复制数据的字节数
函数返回值:
成功 0
失败 >0 表示没有赋值成功的字节数
内核源码的基本形式:
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
{
if (likely(access_ok(VERIFY_WRITE, to, n)))
n = __copy_to_user(to, from, n);
return n;
}
说明:
①__must_check
功能:
__must_check 是 Linux 内核编程中的一个宏定义,它用于修饰函数原型,以指示调用者必须检查该函数的返回值
实现方式:
__must_check 通常是通过 GCC 的 __attribute__((warn_unused_result)) 属性来实现的。这个属性告诉编译器,如果一个函数的返回值没有被使用,就应该发出警告。在 Linux 内核中,__must_check 宏定义通常被展开为 __attribute__((warn_unused_result))。
②__user
功能:
__user 是 Linux 内核编程中的一个宏定义,它主要用于标记指向用户空间的指针
使用情景:
__user 通常用于内核模块或驱动程序中,当内核需要与用户空间进行通信时。例如,在读取或写入设备时,驱动程序可能需要从用户空间获取数据或将数据写回用户空间。在这种情况下,驱动程序会使用 __user 标记的指针来指向用户空间的数据缓冲区
实现方式:
__user通常是通过 GCC 的 __attribute__((noderef, address_space(1))) 属性来实现的
这里,noderef 表示这个指针不应该被解除引用(即不应该直接访问指针所指向的内存),而 address_space(1) 表示这个指针指向的是用户空间(地址空间1)。Linux内核将内存空间分为几个部分,其中0表示普通空间(即内核空间),1表示用户空间,2可能表示设备地址映射空间
八、驱动代码的实现
1、驱动的实现方式1->write接口
示例:利用虚拟地址控制对应的寄存器,从而操作硬件LED—闪烁亮灭
思路1
代码编写思路:
1:应用程序app.c --open write—控制引脚的电平 read---获取对应引脚的电平
2: 驱动程序:杂项设备驱动模型+硬件操作(开启时钟 配置输入输出 获取引脚电平)
//应用层编程步骤
led灯状态每隔一秒来回进行切换
//驱动程序编程思路
第一步:入口函数的实现
- 获取对应外设的虚拟地址
- 打开时钟
- 设置led引脚的输出方向1
- 设置led输出状态为熄灭0
- 注册杂项设备驱动模型
第二步:包括的头文件和变量相关的定义
①开启和关闭相应的GPIO时钟具体实现
1-读取对应寄存器中的值
2-判断当前的时钟状态
3-打开时钟
4-关闭时钟
②控制对应的DR寄存器实现灯的亮灭
1-读取对应DR寄存器中的值
2-判断是否有效
3-有效灯亮,无效灯灭
第三步:写入对应文件中的内容
应用层写函数,对应驱动中的写函数
1-从用户层获取对应的消息到k_buf中
2-判断获取的值 决定亮还是灭—自己封装的函数
第四步:读取对应文件中的内容
应用层的读函数,对应驱动中的读函数
1-在驱动层获取对应引脚状态
2-将对应的引脚状态给用户层
第五步:出口函数的具体实现
1-关灯
2-关时钟
3-注销杂项设备
4-收回空间取消映射
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd,i;
char t_buf[3]={'1','0',0};
char r_buf[3]={0};
//调用一下open函数
fd=open("/dev/led_misc",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
//控制对应引脚的电平状态
for(i=0;;i++)
{
write(fd,&t_buf[i%2],1); //将对应的字符写到驱动端---驱动收到字符之后可以改变对应引脚电平
read(fd,&r_buf[i%2],1); //读取fd中的内容放大r_buf中
//判断r_buf中的内容是否符合要求
if(r_buf[i%2] == '1')
{
printf("led_on \r\n");
}else if(r_buf[i%2] == '0')
{
printf("led_off \r\n");
}
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_write---write-写操作实现
//应用层写函数,对应驱动中的写函数
//1-从用户层获取对应的消息到k_buf中
//2-判断获取的值 决定亮还是灭—自己封装的函数
ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t size, loff_t *ploff)
{
char k_buf[3];
int res1;
/*硬件操作*/
//从用户层获取对应的消息到k_buf中
res1 = copy_from_user(k_buf,buf,1);
if(res1 == 0)
{
printk("copy_from_user success\r\n");
printk("k_buf=%s\r\n",k_buf);
}
//判断获取的值 决定亮还是灭
if(k_buf[0] == '1')//-点亮
{
rk3399_LED_on_off(1);
}
else//-熄灭
{
rk3399_LED_on_off(0);
}
return 0;
}
//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数
//1-在驱动层获取对应引脚状态
//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{
char r_buf[3]={0};
int res2;
/*硬件操作*/
//在驱动层获取对应引脚状态
if(readl(GPIO_EXT_PORTA) & 1 << 13)
{
r_buf[0]='1';
}
else
{
r_buf[0]='0';
}
//将对应的引脚状态给用户层
res2 = copy_to_user(buf,r_buf,1);
if(res2 == 0)
{
printk("copy_to_user success\r\n");
}
return 0;
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.read=xxx_read,
.release=xxx_release,
.write=xxx_write,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
int ret;
u32 val;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册杂项设备驱动模型
ret=misc_register(&pdevc);
if(ret == 0)
{
printk("misc_register success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销杂项设备
misc_deregister(&pdevc);
printk("misc_deregister success\r\n");
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
思路2
代码编写思路:
1:应用程序app.c --open write—控制引脚的电平 read---获取对应引脚的电平
2: 驱动程序:早期设备驱动模型+硬件操作(开启时钟 配置输入输出 获取引脚电平)
//应用层编程步骤
led灯状态每隔一秒来回进行切换
//驱动程序编程步骤
第一步:入口函数的实现
- 获取对应外设的虚拟地址
- 打开时钟
- 设置led引脚的输出方向1
- 设置led输出状态为熄灭0
- 注册早期设备驱动模型
第二步:包括的头文件和变量相关的定义
①开启和关闭相应的GPIO时钟具体实现
1-读取对应寄存器中的值
2-判断当前的时钟状态
3-打开时钟
4-关闭时钟
②控制对应的DR寄存器实现灯的亮灭
1-读取对应DR寄存器中的值
2-判断是否有效
3-有效灯亮,无效灯灭
第三步:写入对应文件中的内容
应用层写函数,对应驱动中的写函数
1-从用户层获取对应的消息到k_buf中
2-判断获取的值 决定亮还是灭—自己封装的函数
第四步:读取对应文件中的内容
应用层的读函数,对应驱动中的读函数
1-在驱动层获取对应引脚状态
2-将对应的引脚状态给用户层
第五步:出口函数的具体实现
1-关灯
2-关时钟
3-注销早期设备
4-收回空间取消映射
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd,i;
char t_buf[3]={'1','0',0};
char r_buf[3]={0};
//调用一下open函数
fd=open("/dev/led_early",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
//控制对应引脚的电平状态
for(i=0;;i++)
{
write(fd,&t_buf[i%2],1); //将对应的字符写到驱动端---驱动收到字符之后可以改变对应引脚电平
read(fd,&r_buf[i%2],1); //读取fd中的内容放大r_buf中
//判断r_buf中的内容是否符合要求
if(r_buf[i%2] == '1')
{
printf("led_on \r\n");
}else if(r_buf[i%2] == '0')
{
printf("led_off \r\n");
}
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include<linux/device.h>
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//主设备号
static int major;
//类设备
static struct class *pcls;
//设备节点
static struct device *pdev;
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_write---write-写操作实现
//应用层写函数,对应驱动中的写函数
//1-从用户层获取对应的消息到k_buf中
//2-判断获取的值 决定亮还是灭—自己封装的函数
ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t size, loff_t *ploff)
{
char k_buf[3];
int res1;
/*硬件操作*/
//从用户层获取对应的消息到k_buf中
res1 = copy_from_user(k_buf,buf,1);
if(res1 == 0)
{
printk("copy_from_user success\r\n");
printk("k_buf=%s\r\n",k_buf);
}
//判断获取的值 决定亮还是灭
if(k_buf[0] == '1')//-点亮
{
rk3399_LED_on_off(1);
}
else//-熄灭
{
rk3399_LED_on_off(0);
}
return 0;
}
//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数
//1-在驱动层获取对应引脚状态
//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{
char r_buf[3]={0};
int res2;
/*硬件操作*/
//在驱动层获取对应引脚状态
if(readl(GPIO_EXT_PORTA) & 1 << 13)
{
r_buf[0]='1';
}
else
{
r_buf[0]='0';
}
//将对应的引脚状态给用户层
res2 = copy_to_user(buf,r_buf,1);
if(res2 == 0)
{
printk("copy_to_user success\r\n");
}
return 0;
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.read=xxx_read,
.release=xxx_release,
.write=xxx_write,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
u32 val;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册早期设备驱动模型
//注册函数-字符设备的内部注册,并且不会直接对应于/sys/class下的目录名
major = register_chrdev(0,"whl_early",&pfop);
if(major>0)
{
printk("major=%d\r\n",major);
printk("register success\r\n");
}
//创建设备类-/sys/class
pcls = class_create(THIS_MODULE, "whl_class");
if(pcls != NULL)
{
printk("class_create success\r\n");
}
//创建设备节点-/dev
pdev = device_create(pcls, NULL,MKDEV(major,0),NULL,"led_early");
if(pdev != NULL)
{
printk("device_create success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销杂项设备
unregister_chrdev(major,"whl_early");
printk("unregister success\r\n");
//删除设备节点
device_destroy(pcls,MKDEV(major,0));
//删除设备类
class_destroy(pcls);
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
思路3
代码编写思路:
1:应用程序app.c --open write—控制引脚的电平 read---获取对应引脚的电平
2: 驱动程序:标准设备驱动模型+硬件操作(开启时钟 配置输入输出 获取引脚电平)
//应用层编程步骤
led灯状态每隔一秒来回进行切换
//驱动程序编程步骤
第一步:入口函数的实现
- 获取对应外设的虚拟地址
- 打开时钟
- 设置led引脚的输出方向1
- 设置led输出状态为熄灭0
- 注册标准设备驱动模型
第二步:包括的头文件和变量相关的定义
①开启和关闭相应的GPIO时钟具体实现
1-读取对应寄存器中的值
2-判断当前的时钟状态
3-打开时钟
4-关闭时钟
②控制对应的DR寄存器实现灯的亮灭
1-读取对应DR寄存器中的值
2-判断是否有效
3-有效灯亮,无效灯灭
第三步:写入对应文件中的内容
应用层写函数,对应驱动中的写函数
1-从用户层获取对应的消息到k_buf中
2-判断获取的值 决定亮还是灭—自己封装的函数
第四步:读取对应文件中的内容
应用层的读函数,对应驱动中的读函数
1-在驱动层获取对应引脚状态
2-将对应的引脚状态给用户层
第五步:出口函数的具体实现
1-关灯
2-关时钟
3-注销标准设备
4-收回空间取消映射
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd,i;
char t_buf[3]={'1','0',0};
char r_buf[3]={0};
//调用一下open函数
fd=open("/dev/led_std",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
//控制对应引脚的电平状态
for(i=0;;i++)
{
write(fd,&t_buf[i%2],1); //将对应的字符写到驱动端---驱动收到字符之后可以改变对应引脚电平
read(fd,&r_buf[i%2],1); //读取fd中的内容放大r_buf中
//判断r_buf中的内容是否符合要求
if(r_buf[i%2] == '1')
{
printf("led_on \r\n");
}else if(r_buf[i%2] == '0')
{
printf("led_off \r\n");
}
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//主设备号
int major;
//存放分配的起始设备号
static dev_t dev_no;
//内存块的指针
struct cdev *p;
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_write---write-写操作实现
//应用层写函数,对应驱动中的写函数
//1-从用户层获取对应的消息到k_buf中
//2-判断获取的值 决定亮还是灭—自己封装的函数
ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t size, loff_t *ploff)
{
char k_buf[3];
int res1;
/*硬件操作*/
//从用户层获取对应的消息到k_buf中
res1 = copy_from_user(k_buf,buf,1);
if(res1 == 0)
{
printk("copy_from_user success\r\n");
printk("k_buf=%s\r\n",k_buf);
}
//判断获取的值 决定亮还是灭
if(k_buf[0] == '1')//-点亮
{
rk3399_LED_on_off(1);
}
else//-熄灭
{
rk3399_LED_on_off(0);
}
return 0;
}
//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数
//1-在驱动层获取对应引脚状态
//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{
char r_buf[3]={0};
int res2;
/*硬件操作*/
//在驱动层获取对应引脚状态
if(readl(GPIO_EXT_PORTA) & 1 << 13)
{
r_buf[0]='1';
}
else
{
r_buf[0]='0';
}
//将对应的引脚状态给用户层
res2 = copy_to_user(buf,r_buf,1);
if(res2 == 0)
{
printk("copy_to_user success\r\n");
}
return 0;
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.read=xxx_read,
.release=xxx_release,
.write=xxx_write,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
u32 val;
int ret;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册标准设备驱动模型
//申请空间
p = cdev_alloc();
if(p != NULL)
{
printk("cdev_alloc success\r\n");
}
//申请设备号 动态:静态 ---把对应申请的号
ret = alloc_chrdev_region(&dev_no,0,10,"whl_std");
if(ret == 0)
{
printk("alloc_chrdev_region success\r\n");
}
//初始化cdev结构
cdev_init(p,&pfop);
//注册已经初始化的cdev结构
ret = cdev_add(p,dev_no,10);
if(ret == 0)
{
printk("cdev_add success\r\n");
}
//主设备号
major = MAJOR(dev_no);
printk("major:%d\r\n",major);
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销标准设备
//注销cdev结构
cdev_del(p);
//释放设备号
unregister_chrdev_region(dev_no,10);
//释放空间
kfree(p);
printk("free success\r\n");
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
手动添加文件设备:mknod /dev/led_std c 240 0
2、驱动的实现方式2->ioctl接口
当用户空间程序调用 ioctl 函数时,系统调用会进入内核空间,具体调度的过程如下:
ioctl对应的驱动函数形式
文件集合:struct file_operations
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long args);
参数:
struct file *file:
这是一个指向file结构体的指针,该结构体代表了被打开的文件。在字符设备驱动程序中,这个指针通常用于访问与设备相关的私有数据。
unsigned int cmd:
这是一个32位的无符号整数,用作命令码。命令码用于指定ioctl调用要执行的具体操作。驱动程序根据命令码的值来区分不同的操作。命令码通常通过位操作合成,包括设备类型(魔数)、命令编号以及数据方向(读/写)和大小等信息。
unsigned long args:
这是一个整型数值,用作数据传递的指针。尽管它的类型是unsigned long,但在用户空间和内核空间之间传递数据时,它通常被用作一个指向用户空间数据的指针。
驱动程序需要小心地处理这个参数,因为直接访问用户空间数据可能会导致安全问题。通常,驱动程序会使用诸如copy_from_user()和copy_to_user()之类的函数来安全地复制数据。
应用程序app.c的控制形式:
ioctl系统调用接口函数
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
while(1)
{
ioctl(fd, IOC_CMD_ON_X, &nr); //亮
sleep(1);
ioctl(fd, IOC_CMD_OFF_X, &nr); //灭
sleep(1);
}
命令码
命令码的组成有32个位,类似:寄存器
bits 含义
31~30
00---表示用户程序和驱动程序之间没有数据传递 宏定义 _IOC_NONE
01---表示用户程序向驱动程序通过参数args写入数据 宏定义 _IOC_WRITE
10---表示用户程序从驱动程序读取参数args 宏定义 _IOC_READ
11--表示用户程序向驱动程序写入数据,然后再从驱动程序里面读取数据
宏定义 _IOC_WRITE|_IOC_READ
29~16 表示传递数据的大小---字节数 否则为0 ---size
15~8 魔数\幻数---代表的是驱动程序,给每一个驱动程序分配一个唯一的ASCII值(字符),用于标识驱动 ---type
0~7 同一个驱动程序中所有的命令码的编号(每种功能(256种)对应一个命令码),范围0~255通常按顺序的连续编号,用于标识命令码 ---nr
合成宏
命令码的合成总共有两种形式:1:手动合成 2:系统自动合成(方便)
手动合成:
例子:
//xxx_unlocked_ioctl()调用时不用给定参数args
#define IOC_CMD_LED_ON_ALL 0<<30|0<<16|’L’<<8|0 //灯亮
#define IOC_CMD_LED_OFF_ALL 0<<30|0<<16|’L’<<8|1 //灯灭
//xxx_unlocked_ioctl()调用时给定参数args
#define IOC_CMD_LED_ON_X 1<<30|4<<16|’L’<<8|2 //指定灯亮
#define IOC_CMD_LED_OFF_X 1<<30|4<<16|‘L‘<<8|3 //指定灯灭
系统自动合成:linux系统里面有专门合成命令码的宏。
①_IO(type,nr)
作用:表示合成“没有数据参数”传递的命令码
参数: type:表示命令码的魔数,ASCII字符,也就是8~15位
nr :表示命令码的编号,也就是0~7位
②_IOR(type,nr,size) 作用:用于合成应用程序需要从驱动程序中读取数据的命令码。
③_IOW(type,nr,size) 作用:用于合成应用程序需要从驱动程序中写入数据的命令码。
④_IOWR(type,nr,size) 作用:用于合成应用程序需要从驱动程序中写入数据然后再从驱动读取数据的命令码。
参数:
size :表示命令码需要传递数据的大小---注意不是具体的字节数而是数据类型(如int)。
例子:
#define IOC_CMD_LED_MAGIC 'L' // 定义命令码的魔数
//没有数据传递的命令
#define IOC_CMD_LED_ON_ALL _IO(IOC_CMD_LED_MAGIC, 0) // 开全部灯
#define IOC_CMD_LED_OFF_ALL _IO(IOC_CMD_LED_MAGIC, 1) // 灭全部灯
//有数据传递的命令(即使我们不从驱动读取数据,也使用_IOW来传递参数)
#define IOC_CMD_LED_ON_X _IOW(IOC_CMD_LED_MAGIC, 2, int)//指定灯开(传递LED编号)
#define IOC_CMD_LED_OFF_X _IOW(IOC_CMD_LED_MAGIC, 3, int)//指定灯灭(传递LED编号)
说明:
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 13 /* Actually 14, see below. */
#define _IOC_DIRBITS 3
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_XSIZEMASK ((1 << (_IOC_SIZEBITS+1))-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT + _IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT + _IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT + _IOC_SIZEBITS)
#define _IOC_NONE 1U
#define _IOC_READ 2U
#define _IOC_WRITE 4U
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
_IOC 宏用于生成一个ioctl命令编号。这个编号通过组合方向(dir)、类型(type)、编号(nr)和数据大小(size)来生成。这些组成部分通过左移操作被放置在命令编号的不同位段上。
示例
利用系统自动合成宏的方法,判断这个宏,从而控制对应的硬件。
代码编写思路1:
1:应用程序app.c --open ioctl—控制引脚的电平
2: 驱动程序:杂项设备驱动模型+硬件操作(开启时钟 配置输入输出 获取引脚电平)
//应用层编程步骤
led灯状态每隔一秒来回进行切换
//驱动程序编程步骤
定义头文件command..h
第一步:入口函数的实现
- 获取对应外设的虚拟地址
- 打开时钟
- 设置led引脚的输出方向1
- 设置led输出状态为熄灭0
- 注册杂项设备驱动模型
第二步:包括的头文件和变量相关的定义
①开启和关闭相应的GPIO时钟具体实现
1-读取对应寄存器中的值
2-判断当前的时钟状态
3-打开时钟
4-关闭时钟
②控制对应的DR寄存器实现灯的亮灭
1-读取对应DR寄存器中的值
2-判断是否有效
3-有效灯亮,无效灯灭
第三步:ioctl函数的具体实现
根据应用层传过来的cmd来做决定
IOC_CMD_LED_ON_ALL--->rk3399_LED_on_off(1)开灯
IOC_CMD_LED_OFF_ALL--->rk3399_LED_on_off(0)关灯
第四步:出口函数的具体实现
1-关灯
2-关时钟
3-注销杂项设备
4-收回空间取消映射
头文件:
Command.h
#ifndef _COMMAND_H
#define _COMMAND_H
#define IOC_CMD_LED_MAGIC 'L' // 定义命令码的魔数
//没有数据传递的命令
#define IOC_CMD_LED_ON_ALL _IO(IOC_CMD_LED_MAGIC, 0) // 开全部灯
#define IOC_CMD_LED_OFF_ALL _IO(IOC_CMD_LED_MAGIC, 1) // 灭全部灯
#endif
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "command.h"
int main(int argc,char *argv[])
{
int fd,nr;
//调用一下open函数
fd=open("/dev/led_misc",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
while(1)
{
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_ON_ALL,&nr);
printf("LED_ON\r\n");
//等待一秒钟
sleep(1);
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_OFF_ALL,&nr);
printf("LED_OFF\r\n");
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "command.h"
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long args)
{
switch(cmd)
{
case IOC_CMD_LED_ON_ALL:rk3399_LED_on_off(1);break;
case IOC_CMD_LED_OFF_ALL:rk3399_LED_on_off(0);break;
}
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.release=xxx_release,
.unlocked_ioctl=xxx_ioctl,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
int ret;
u32 val;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册杂项设备驱动模型
ret=misc_register(&pdevc);
if(ret == 0)
{
printk("misc_register success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销杂项设备
misc_deregister(&pdevc);
printk("misc_deregister success\r\n");
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
分解宏
Linux系统里面有专门的分解宏:作用就是为了专门将命令码里面的内容给分解出来。
内核源码的基本信息获取到的:
/* used to create numbers */
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
_IOC_DIR(cmd) :分解命令码的数据传输方向,即命令码的31~30位的值
_IOC_TYPE(cmd) :分解命令码的魔数,即命令码的15~8位的值
_IOC_NR(cmd): 分解出命令的编号,即命令码的7~0位的值。
_IOC_SIZE(cmd) :分解命令码的类型的大小。
示例
1-利用一下对应的分解宏,将命令码里面的信息分解出来,观察一下。
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "command.h"
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long args)
{
char ch;
int num;
//分解
ch = _IOC_TYPE(cmd);//魔数
printk("ch:%c\r\n",ch);
//控制对应的硬件
num=_IOC_NR(cmd);
printk("num:%d\r\n",num);
if(num == 1)
{
printk("LED ON\r\n");
}
switch(cmd)
{
case IOC_CMD_LED_ON_ALL:rk3399_LED_on_off(1);break;
case IOC_CMD_LED_OFF_ALL:rk3399_LED_on_off(0);break;
}
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.release=xxx_release,
.unlocked_ioctl=xxx_ioctl,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
int ret;
u32 val;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册杂项设备驱动模型
ret=misc_register(&pdevc);
if(ret == 0)
{
printk("misc_register success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销杂项设备
misc_deregister(&pdevc);
printk("misc_deregister success\r\n");
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
2-利用应用程序ioctl函数的第三个参数,将它传递到驱动端,观察它的数值。
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "command.h"
int main(int argc,char *argv[])
{
int fd;
long nr1 = 666,nr2 =777;
//调用一下open函数
fd=open("/dev/led_misc",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
while(1)
{
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_ON_ALL,&nr1);
printf("LED_ON\r\n");
//等待一秒钟
sleep(1);
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_OFF_ALL,&nr2);
printf("LED_OFF\r\n");
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "command.h"
#define DEV_NAME "whl-leds" //定义led 设备名
#define LED_NUM (1) //定义led 灯数量
#define PMUCRU_BASE_ADDR (0xFF750000) //GPIO0时钟开启寄存器
#define PMUCRU_CLKGATE_CON1 (cru_base_addr+0x0104) //PMUCRU_CLKGATE_CON1 register
//按照数据手册定义寄存器指针,开发板上 LED_GREEN 指示灯在 GPIO0_B5
#define SIZE_64K 65536
#define GPIO0_BASE_ADDR (0xFF720000) //GPIO0的基地址-这组的起始地址
#define GPIO_SWPORTA_DR (base_addr+0x00) //Port A 数据寄存器
#define GPIO_SWPORTA_DDR (base_addr+0x04) //Port A 方向寄存器
#define GPIO_EXT_PORTA (base_addr+0x0050) //Port A 引脚状态
void __iomem *base_addr= NULL; //存放GPIO0寄存器 对应的虚拟地址
void __iomem *cru_base_addr = NULL; //存放PMUCRU虚拟地址,用来开启GPIO时钟
//相应的GPIO时钟具体实现
static void rk3399_pwrclk_enable(int en)
{
u32 regv;
//读取PMUCRU_CLKGATE_CON1寄存器的当前值
regv=readl(PMUCRU_CLKGATE_CON1);
if(en)//en非0的就使能--/*0-需要开启时钟*/
{
if(regv & (1<<3))
{
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为0--启用时钟
regv &= ~(1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv,PMUCRU_CLKGATE_CON1);
}
return ;
}
/*1-不需要开启时钟*/
//先将对应的掩码位打开--解锁或允许修改
regv |= (1 << (16+3));
//将第3位设置为1--关闭时钟
regv |= (1 << 3);
//将修改后的值写回PMUCRU_CLKGATE_CON1寄存器
writel(regv ,PMUCRU_CLKGATE_CON1);
}
//控制对应的DR寄存器实现灯的亮灭
static void rk3399_LED_on_off(int en)
{
u32 regv;
//读取GPIO_SWPORTA_DR寄存器的当前值
regv=readl(GPIO_SWPORTA_DR);
//判断是否需要开灯 1-需要点灯 0-不需要点灯
if(en)//en非0即真->/* 1-需要点灯*/
{
//判断灯的状态
if(regv & (1<<13))//-点亮
{
;
}
else//-熄灭
{
//将第13位设置为1,将其点亮
regv |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv,GPIO_SWPORTA_DR);
}
return ;
}
/* 0-不需要点灯*/
//将第13位设置为0,将其熄灭
regv &= ~(1 << 13);
//将修改后的值写回GPIO_SWPORTA_DR寄存器
writel(regv ,GPIO_SWPORTA_DR);
}
//定义一个xxx_write---write-写操作实现
//应用层写函数,对应驱动中的写函数
//1-从用户层获取对应的消息到k_buf中
//2-判断获取的值 决定亮还是灭—自己封装的函数
ssize_t xxx_write(struct file *pfile, const char __user *buf, size_t size, loff_t *ploff)
{
char k_buf[3];
int res1;
/*硬件操作*/
//从用户层获取对应的消息到k_buf中
res1 = copy_from_user(k_buf,buf,1);
if(res1 == 0)
{
printk("copy_from_user success\r\n");
printk("k_buf=%s\r\n",k_buf);
}
//判断获取的值 决定亮还是灭
if(k_buf[0] == '1')//-点亮
{
rk3399_LED_on_off(1);
}
else//-熄灭
{
rk3399_LED_on_off(0);
}
return 0;
}
//定义一个xxx_read---read-读操作实现
//应用层的读函数,对应驱动中的读函数
//1-在驱动层获取对应引脚状态
//2-将对应的引脚状态给用户层
ssize_t xxx_read(struct file *pfile, char __user *buf, size_t size, loff_t *ploff)
{
char r_buf[3]={0};
int res2;
/*硬件操作*/
//在驱动层获取对应引脚状态
if(readl(GPIO_EXT_PORTA) & 1 << 13)
{
r_buf[0]='1';
}
else
{
r_buf[0]='0';
}
//将对应的引脚状态给用户层
res2 = copy_to_user(buf,r_buf,1);
if(res2 == 0)
{
printk("copy_to_user success\r\n");
}
return 0;
}
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long args)
{
char ch;
int num,ret;
int arr[2];
//分解
ch = _IOC_TYPE(cmd);//魔数
printk("ch:%c\r\n",ch);
//控制对应的硬件
num=_IOC_NR(cmd);
printk("num:%d\r\n",num);
if(num == 1)
{
printk("LED ON\r\n");
}
ret = copy_from_user((void*)&arr[0],(void*)args,sizeof(arr[0]));
if(ret == 0)
{
printk("copy_from_user success\r\n");
printk("arr[0]:%d\r\n",arr[0]);
}
switch(cmd)
{
case IOC_CMD_LED_ON_ALL:rk3399_LED_on_off(1);break;
case IOC_CMD_LED_OFF_ALL:rk3399_LED_on_off(0);break;
}
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.read=xxx_read,
.release=xxx_release,
.write=xxx_write,
.unlocked_ioctl=xxx_ioctl,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
int ret;
u32 val;
//1-获取对应外设的虚拟地址
base_addr = ioremap(GPIO0_BASE_ADDR,SIZE_64K); //GPIO0的虚拟地址
cru_base_addr = ioremap(PMUCRU_BASE_ADDR,SIZE_64K); //GPIO0时钟寄存器的虚拟地址
//2-打开时钟
rk3399_pwrclk_enable(1);
//3-设置led引脚的输出方向1
//读取GPIO_SWPORTA_DDR寄存器的当前值
val=readl(GPIO_SWPORTA_DDR);
//将第13位设置为1--输出模式
val |= (1 << 13);
//将修改后的值写回GPIO_SWPORTA_DDR寄存器
writel(val,GPIO_SWPORTA_DDR);
//4-设置led输出状态为熄灭0
rk3399_LED_on_off(0);
//5-注册杂项设备驱动模型
ret=misc_register(&pdevc);
if(ret == 0)
{
printk("misc_register success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-关灯
rk3399_LED_on_off(0);
//2-关时钟
rk3399_pwrclk_enable(0);
//3-注销杂项设备
misc_deregister(&pdevc);
printk("misc_deregister success\r\n");
//4-收回空间取消映射
iounmap(base_addr);
iounmap(cru_base_addr);
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象:
问题:
为什么用long类型定义nr?
linux系统下,地址是以long类型(8字节)的形式来存储的
3、驱动的实现方式3->gpio函数接口
GPIO函数接口是linux内核源码里面自带的库。优点:利用Linux提供的GPIO的API函数,避免寄存器和物理地址到虚拟地址映射的操作等。
Linux内核中GPIO引脚的常用操作,提供了一套便捷的API(类似:M4的标准库—gpio_init()),开发者不需要映射或配置对应的一个寄存器。开发者需要完成的工作就是调用这些标准的API函数,去配置对应的GPIO口,引脚的方向,电平的状态,状态的读写,转换为外部的中断编号等。不同的芯片,GPIO的API接口是一样的,这些API通过GPIO引脚编号识别操作GPIO口引脚对应的寄存器。这里GPIO的引脚编号是芯片厂家定义的逻辑意义的编号。
注意:不同的芯片厂商,定义芯片编号的规则是不同的。对RK3399来说,GPIO0_B5引脚计算方法为:32*0+8*1+5=13
相关函数
gpio_is_valid
函数功能:
检查给定的 GPIO(通用输入输出)编号是否有效,每个GPIO都有一个唯一的编号来标识
头文件
#include <linux/gpio.h>
函数原型
int gpio_is_valid(int gpio)
函数参数:
int gpio:GPIO引脚编号
函数返回值
合法:1 非法:0
注意:在进行GPIO操作之前,如读取、写入或配置GPIO之前,使用gpio_is_valid函数可以确保操作的GPIO是有效的,从而避免因为无效的GPIO编号而导致的错误或系统崩溃
gpio_request
函数功能:
注册指定的GPIO引脚资源
头文件
#include <linux/gpio.h>
函数原型
int gpio_request(unsigned gpio,const char *label)
函数参数:
unsigned gpio:GPIO引脚编号->将这组时钟打开
const char *label:自定义的GPIO引脚的名称
函数返回值
成功 0 失败 <0
注意:在使用GPIO引脚必须通过该函数注册,否则会可能引起资源冲突
gpio_direction_output
函数功能:
将指定的GPIO引脚设置为输出模式,并同时设置该引脚的输出值
头文件
#include <linux/gpio.h>
函数原型
int gpio_direction_output(unsigned gpio,int value)
函数参数:
unsigned gpio:GPIO引脚编号
int value:0表示低电平 1表示高电平
函数返回值
成功 0 失败 <0
gpio_direction_input
函数功能:
将指定的GPIO引脚设置为输入模式
头文件
#include <linux/gpio.h>
函数原型
int gpio_direction_input(unsigned gpio)
函数参数:
unsigned gpio:GPIO引脚编号
函数返回值
成功 0 失败 <0
gpio_set_value
函数功能:
设置指定的GPIO引脚输出的高低电平
头文件
#include <linux/gpio.h>
函数原型
void gpio_set_value(unsigned gpio,int value)
函数参数:
unsigned gpio:GPIO口引脚编号
int value:0表示低调平 1表示高电平
函数返回值
成功 0 失败 <0
gpio_get_value
函数功能:
读取指定的GPIO引脚的电平状态
头文件
#include <linux/gpio.h>
函数原型
int gpio_get_value(unsigned gpio)
函数参数:
unsigned gpio:GPIO口引脚编号
函数返回值
成功 返回 GPIO 引脚的当前电平状态
失败 <0
gpio_free
函数功能:
释放或复位指定的GPIO
头文件
#include <linux/gpio.h>
函数原型:
void gpio_free(unsigned gpio)
函数参数:
unsigned gpio:代表要释放的GPIO端口的编号
函数返回值:无
注意:在某些情况下,GPIO端口的配置可能会占用系统资源(如内存或特定的硬件寄存器)。gpio_free()函数会确保这些资源被释放,以便它们可以被其他任务或进程使用
示例
利用gpio函数接口,控制对应的硬件LED
代码编写思路:
1:应用程序app.c --open ioctl—控制引脚的电平
2: 驱动程序:杂项设备驱动模型+硬件操作(开启时钟 配置输入输出 获取引脚电平)
//应用层编程步骤
led灯状态每隔一秒来回进行切换
//驱动程序编程步骤
第一步:入口函数的实现
- 检查给定的 GPIO编号是否有效
- 注册指定的GPIO引脚资源
- 将指定的GPIO引脚设置为输出模式
- 注册杂项设备驱动模型
第二步:ioctl函数的具体实现
根据应用层传过来的cmd来做决定
IOC_CMD_LED_ON_ALL--->gpio_set_value(gpio,1)开灯
IOC_CMD_LED_OFF_ALL--->gpio_set_value(gpio,0)关灯
第三步:出口函数的实现
- 释放指定的GPIO
- 注销杂项设备驱动模型
应用层代码:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "command.h"
int main(int argc,char *argv[])
{
int fd;
long nr1 = 666,nr2 =777;
//调用一下open函数
fd=open("/dev/led_misc",O_RDWR);
if(fd > 0)
{
printf("open success fd=%d\r\n",fd);
}
while(1)
{
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_ON_ALL,&nr1);
printf("LED_ON\r\n");
//等待一秒钟
sleep(1);
//控制对应引脚的电平状态
ioctl(fd,IOC_CMD_LED_OFF_ALL,&nr2);
printf("LED_OFF\r\n");
//等待一秒钟
sleep(1);
}
//关闭对应的文件描述符
close(fd);
}
驱动层代码:
//驱动包含的头文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include "command.h"
//定义一个led引脚相关的结构体
struct led_init{
int num; //序号
int gpio; //引脚编号
char name[10]; //引脚名称
};
//定义变量
struct led_init leds[]={
{0,13,"led0"},
{},
};
//定义一个xxx_release---close释放操作实现
static int xxx_release(struct inode *pnode, struct file *pfile)
{
printk("Goodbye\r\n");
printk("name:%s,line:%d\r\n",__FUNCTION__,__LINE__);
return 0;
}
//定义一个xxx_ioctl---ioctl对io口操作实现
static long xxx_ioctl(struct file *file,unsigned int cmd,unsigned long args)
{
char ch;
int num,ret;
int arr[2];
//分解
ch = _IOC_TYPE(cmd);//魔数
printk("ch:%c\r\n",ch);
//控制对应的硬件
num=_IOC_NR(cmd);
printk("num:%d\r\n",num);
if(num == 1)
{
printk("LED ON\r\n");
}
ret = copy_from_user((void*)&arr[0],(void*)args,sizeof(arr[0]));
if(ret == 0)
{
printk("copy_from_user success\r\n");
printk("arr[0]:%d\r\n",arr[0]);
}
switch(cmd)
{
case IOC_CMD_LED_ON_ALL: gpio_set_value(leds[0].gpio,1);break;
case IOC_CMD_LED_OFF_ALL:gpio_set_value(leds[0].gpio,0);break;
}
return 0;
}
// 定义file_operations结构体-核心数据结构-文件操作集合
struct file_operations pfop=
{
.release=xxx_release,
.unlocked_ioctl=xxx_ioctl,
};
//定义miscdevice结构体-核心数据结构
struct miscdevice pdevc=
{
.minor=255, // 次设备号
.name="led_misc",// 设备名称
.fops=&pfop,//指向文件操作方法集合的指针
};
//入口函数
static int __init hello_init(void)
{
int ret;
//1-检查给定的 GPIO编号是否有效
ret = gpio_is_valid(leds[0].gpio);
if(ret == 1)
{
printk("gpio valid\r\n");
}
//2-注册指定的GPIO引脚资源
ret = gpio_request(leds[0].gpio,leds[0].name);
if(ret == 0)
{
printk("gpio_request success\r\n");
}
//3-将指定的GPIO引脚设置为输出模式
ret = gpio_direction_output(leds[0].gpio,0);
if(ret == 0)
{
printk("gpio_output set success and led-off\r\n");
}
//4-注册杂项设备驱动模型
ret=misc_register(&pdevc);
if(ret == 0)
{
printk("misc_register success\r\n");
}
return 0;
}
//出口函数
static void __exit hello_exit(void)
{
//1-释放指定的GPIO
gpio_free(leds[0].gpio);
printk("gpio_free success\r\n");
//2-注销杂项设备
misc_deregister(&pdevc);
printk("misc_deregister success\r\n");
}
//标记函数
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
现象: