GPIO-LED驅動

一、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燈狀態每隔一秒來回進行切換

//驅動程序編程思路

第一步:入口函數的實現

  1. 獲取對應外設的虛擬地址
  2. 打開時鐘
  3. 設置led引腳的輸出方向1
  4. 設置led輸出狀態為熄滅0
  5. 注冊雜項設備驅動模型

第二步:包括的頭文件和變量相關的定義

????????①開啟和關閉相應的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燈狀態每隔一秒來回進行切換

//驅動程序編程步驟

第一步:入口函數的實現

  1. 獲取對應外設的虛擬地址
  2. 打開時鐘
  3. 設置led引腳的輸出方向1
  4. 設置led輸出狀態為熄滅0
  5. 注冊早期設備驅動模型

第二步:包括的頭文件和變量相關的定義

開啟和關閉相應的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燈狀態每隔一秒來回進行切換

//驅動程序編程步驟

第一步:入口函數的實現

  1. 獲取對應外設的虛擬地址
  2. 打開時鐘
  3. 設置led引腳的輸出方向1
  4. 設置led輸出狀態為熄滅0
  5. 注冊標準設備驅動模型

第二步:包括的頭文件和變量相關的定義

開啟和關閉相應的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

第一步:入口函數的實現

  1. 獲取對應外設的虛擬地址
  2. 打開時鐘
  3. 設置led引腳的輸出方向1
  4. 設置led輸出狀態為熄滅0
  5. 注冊雜項設備驅動模型

第二步:包括的頭文件和變量相關的定義

開啟和關閉相應的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燈狀態每隔一秒來回進行切換

//驅動程序編程步驟

第一步:入口函數的實現

  1. 檢查給定的 GPIO編號是否有效
  2. 注冊指定的GPIO引腳資源
  3. 將指定的GPIO引腳設置為輸出模式
  4. 注冊雜項設備驅動模型

第二步:ioctl函數的具體實現

????????根據應用層傳過來的cmd來做決定

IOC_CMD_LED_ON_ALL--->gpio_set_value(gpio,1)開燈

IOC_CMD_LED_OFF_ALL--->gpio_set_value(gpio,0)關燈

第三步:出口函數的實現

  1. 釋放指定的GPIO
  2. 注銷雜項設備驅動模型

應用層代碼:

#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");

現象:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/84910.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/84910.shtml
英文地址,請注明出處:http://en.pswp.cn/web/84910.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Gartner《Generative AI Use - Case Comparison for Legal Departments》

概述 這篇文章由 Gartner, Inc. 出品,聚焦于生成式人工智能(GenAI)在法律部門中的應用情況,通過對 16 個較為突出的 GenAI 法律技術應用場景進行分析,從商業價值和可行性兩個維度進行評估,旨在為法律總顧問等提供戰略對話依據,以便更好地做出技術投資決策,推動法律部門…

Vue 中 filter 過濾的語法詳解與注意事項

Vue 中 filter 過濾的語法詳解與注意事項 在 Vue.js 中,"過濾"通常指兩種不同概念:模板過濾器(Vue 2 特性)和數組過濾(數據過濾)。由于 Vue 3 已移除模板過濾器,我將重點介紹更實用且通用的數組過濾語法和注意事項。 一、數組過濾核心語法(推薦方式) 1. …

webpack+vite前端構建工具 -6從loader本質看各種語言處理 7webpack處理html

6 從loader本質看各種語言處理 語法糖&#xff1f; 6.1 loader的本質 loader本質是一個方法&#xff0c;接收要處理的資源的內容&#xff0c;處理完畢后給出內容&#xff0c;作為打包結果。 所有的loader&#xff08;例如babel-loader, url-loader等&#xff09;export出一個方…

算法第41天|188.買賣股票的最佳時機IV、309.最佳買賣股票時機含冷凍期、714.買賣股票的最佳時機含手續費

188.買賣股票的最佳時機IV 題目 思路與解法 基于 買賣股票的最佳時機iii&#xff0c;得出的解法。關鍵在于&#xff0c;每一天的賣或者買都由前一天推導而來。 class Solution { public:int maxProfit(int k, vector<int>& prices) {if(prices.size() 0) return …

【AI News | 20250623】每日AI進展

AI Repos 1、tools Strands Agents Tools提供了一個強大的模型驅動方法&#xff0c;通過少量代碼即可構建AI Agent。它提供了一系列即用型工具&#xff0c;彌合了大型語言模型與實際應用之間的鴻溝&#xff0c;涵蓋文件操作、Shell集成、內存管理&#xff08;支持Mem0和Amazon…

Python裝飾器decorators和pytest夾具fixture詳解和使用

此前一直認為fixture就叫python中的裝飾器&#xff0c;學習后才發現decorators才是裝飾器&#xff0c;fixture是pytest框架的夾具&#xff0c;只是通過裝飾器去定義和使用。所以要了解fixture就得先了解python裝飾器。 一、裝飾器(decorators) 1.定義 裝飾器&#xff08;dec…

目標檢測之YOLOv5到YOLOv11——從架構設計和損失函數的變化分析

YOLO&#xff08;You Only Look Once&#xff09;系列作為實時目標檢測領域的標桿性框架&#xff0c;自2016年YOLOv1問世以來&#xff0c;已歷經十余年迭代。本文將聚焦YOLOv5&#xff08;2020年發布&#xff09;到YOLOv11&#xff08;2024年前后&#xff09;的核心技術演進&am…

leetcode:面試題 08.06. 漢諾塔問題

題目鏈接 面試題 08.06. 漢諾塔問題 題目描述 題目解析 當只有一個盤子時&#xff1a;直接從A柱放到C柱即可。當有兩個盤子時&#xff1a;將A柱第一個盤子先放到B柱&#xff0c;再將A柱第二個盤子放到C柱&#xff0c;最后將B柱上的盤子放到C柱子。當有3個盤子時&#xff1a;先…

mybatis-plus一對多關聯查詢

MyBatis-Plus 本身主要關注單表操作&#xff0c;但可以通過幾種方式實現一對多關聯查詢&#xff1a; 1. 使用 XML 映射文件實現 這是最傳統的方式&#xff0c;通過編寫 SQL 和 ResultMap 實現&#xff1a; <!-- UserMapper.xml --> <resultMap id"userWithOrd…

一些想法。。。

1.for里面的局部變量這種還是在for里面定義比較好 比如 for(int i 0;i<n;i){ int num; cin>>num; } 實不相瞞&#xff0c;有一次直接cin了i怎么都沒看出來哪里錯了。。。 2.關于long long 如果發現中間結果大約是10^9&#xff0c;就要考慮int 溢出 即用 long …

遷移科技拆垛工業相機:驅動智能拆碼垛革命,賦能工業自動化新紀元

——將復雜技術轉化為可感知價值&#xff0c;引領行業標桿級解決方案 作為工業自動化領域的品牌策略專家&#xff0c;我深知企業面臨的痛點&#xff1a;拆垛環節效率低下、人工成本高、安全隱患頻發。遷移科技憑借其領先的3D視覺技術&#xff0c;通過拆垛工業相機將抽象參數轉…

Linux筆記---線程控制

1. 線程創建&#xff1a;pthread_create() pthread_create() 是 POSIX 線程庫&#xff08;pthread&#xff09;中用于創建新線程的函數。調用該函數后系統就會啟動一個與主線程并發的線程&#xff0c;并使其跳轉到入口函數處執行。 #include <pthread.h>int pthread_cr…

Ragflow 源碼:ragflow_server.py

目錄 介紹1. 初始化和配置2. 數據庫管理3. 核心功能4. HTTP 服務5. 信號處理6. 調試支持 流程圖系統架構 代碼解釋1. **初始化系統**2. **運行時控制**3. **核心服務** 介紹 ragflow_server.py 是 RAGFlow 項目的主服務器程序&#xff0c;負責啟動和管理 RAGFlow 的核心服務。…

springboot企業級項目開發之項目測試——單元測試!

項目測試 項目測試是對項目的需求和功能進行測試&#xff0c;由測試人員寫出完整的測試用例&#xff0c;再按照測試用例執行測試。項目測試是項目質量的保證&#xff0c;項目測試質量直接決定了當前項目的交付質量。 測試人員在開展測試之前&#xff0c;首先需要進行測試的需…

Linux kdump遠程轉存儲配置手冊教程

一、前言 kdump是一個Linux內核崩潰轉儲機制,當系統崩潰時,它可以捕獲內核的內存轉儲信息,幫助分析崩潰原因。將轉儲文件存儲到遠程位置,便于集中管理和分析。本教程將詳細介紹如何配置kdump將轉儲文件遠程轉存儲。 二、安裝kdump 在大多數Linux發行版中,kdump相關的工…

c++bind和forward完美轉化

前言 1. std::bind概述 std::bind是C11引入的功能模板&#xff0c;位于<functional>頭文件中&#xff0c;用于將函數、成員函數或函數對象與特定參數綁定&#xff0c;生成一個新的可調用對象。 1.1 基本用法 #include <iostream> #include <functional>v…

【Dify精講】第14章:部署架構與DevOps實踐【知識卡片】

第14章&#xff1a;部署架構與DevOps實踐http://www.airinto.com/share/49997bb7 一、Docker 容器化方案&#xff1a;從開發到生產的統一 二、Kubernetes 部署&#xff1a;走向云原生 三、CI/CD 流程設計&#xff1a;自動化的藝術 四、高可用架構&#xff1a;讓 AI 服務永不停歇…

el-cascader 設置可以手動輸入也可以下拉選擇

el-cascader 設置可以手動輸入也可以下拉選擇 稍微修改一下就可食用 <template slot"stationId" slot-scope""><div style"position: relative;"><!-- 可輸入也可顯示選項 --><el-input:value"stationNameInput"…

Unity Shader開發-著色器變體(1)-著色器變體概述

有時我們希望一份 Shader 源代碼可能滿足多種功能&#xff08;如處理法線貼圖、自發光、不同光照模式、陰影&#xff0c;支持GPUInstacing等多種功能&#xff09;。所以我們需要能夠實現Shader分支的方法。 一.Shader分支實現 主要有三種手段實現Shader分支&#xff1a; 1.靜…

ECK 簡化:在 GCP GKE Autopilot 上部署 Elasticsearch

作者&#xff1a;來自 Elastic Eduard Martin 學習如何使用 GKE Autopilot 和 ECK 在 GCP 上部署 Elasticsearch 集群。 想要獲得 Elastic 認證&#xff1f;了解下一次 Elasticsearch Engineer 培訓的時間&#xff01; Elasticsearch 擁有豐富的新功能&#xff0c;可以幫助你為…