一、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);elsememset(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輸出狀態為熄滅0rk3399_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輸出狀態為熄滅0rk3399_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/classpcls = class_create(THIS_MODULE, "whl_class");if(pcls != NULL){printk("class_create success\r\n");}//創建設備節點-/devpdev = 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輸出狀態為熄滅0rk3399_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輸出狀態為熄滅0rk3399_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輸出狀態為熄滅0rk3399_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輸出狀態為熄滅0rk3399_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-釋放指定的GPIOgpio_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");
現象: