0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何驅(qū)動(dòng)Linux開發(fā)板LED燈

CHANBAEK ? 來(lái)源:嵌入式攻城獅 ? 作者:安迪西 ? 2023-04-14 11:41 ? 次閱讀

Linux下的任何外設(shè)驅(qū)動(dòng),最終都是要配置相應(yīng)的硬件寄存器。 前面的文章中介紹了新舊字符設(shè)備的驅(qū)動(dòng)開發(fā)框架,也介紹了IMX6ULL處理器GPIO的工作原理及配置方法,本篇我們將實(shí)際操作一個(gè)GPIO,點(diǎn)亮Linux驅(qū)動(dòng)開發(fā)路上的第一個(gè)燈

1. 地址映射

1.1 MMU介紹

MMU (Memory Manage Unit),即內(nèi)存管理單元,它提供統(tǒng)一的內(nèi)存空間抽象,程序訪問虛擬內(nèi)存中的地址,MMU將虛擬地址翻譯成實(shí)際的物理地址,之后CPU即可操作實(shí)際的物理地址

MMU具有如下功能:

  • 保護(hù)內(nèi)存:MMU給一些指定的內(nèi)存塊設(shè)置了讀、寫以及可執(zhí)行的權(quán)限,這些權(quán)限存儲(chǔ)在頁(yè)表當(dāng)中,MMU會(huì)檢查CPU當(dāng)前所處的是特權(quán)模式還是用戶模式,只有權(quán)限匹配才可以訪問
  • 提供方便統(tǒng)一的內(nèi)存空間抽象,實(shí)現(xiàn)虛擬地址到物理地址的轉(zhuǎn)換:CPU可以運(yùn)行在虛擬的內(nèi)存當(dāng)中,虛擬內(nèi)存一般比物理內(nèi)存大很多,使得CPU可以運(yùn)行比較大的應(yīng)用程序

圖片

1.2 IO映射函數(shù)

Linux內(nèi)核啟動(dòng)時(shí)會(huì)初始化MMU,設(shè)置內(nèi)存映射,之后CPU訪問的都是虛擬地址。 在程序編寫時(shí),可使用下面兩個(gè)函數(shù)進(jìn)行物理內(nèi)存和虛擬內(nèi)存之間的轉(zhuǎn)換

ioremap():將物理地址映射為虛擬地址

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) 
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype){ 
    return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0)); 
} 
//phys_addr:被映射的IO起始地址(物理地址)
//size:需要映射的空間大小,以字節(jié)為單位
//mtype: ioremap的類型
//return: __iomem類型的指針,指向映射成功后返回的虛擬空間起始地址

ioremap():將物理地址映射為虛擬地址

void iounmap (volatile void __iomem *addr) 
//addr: 要取消映射的虛擬地址空間首地址
//return: void

1.3 IO內(nèi)存訪問函數(shù)

使用ioremap函數(shù)將寄存器的物理地址映射到虛擬地址以后,理論上就可以直接通過指針訪問這些地址了,但是為了符合驅(qū)動(dòng)的跨平臺(tái)以及可移植性,推薦使用一組操作函數(shù)來(lái)對(duì)映射后的內(nèi)存進(jìn)行讀寫操作

u8  readb(const volatile void __iomem *addr);    /*讀取一個(gè)字節(jié)*/
u16 readw(const volatile void __iomem *addr);    /*讀取一個(gè)字*/
u32 readl(const volatile void __iomem *addr);    /*讀取一個(gè)雙字*/
    
void writeb(u8 value,  volatile void __iomem *addr);  /*寫入一個(gè)字節(jié)*/
void writew(u16 value, volatile void __iomem *addr);  /*寫入一個(gè)字*/
void writel(u32 value, volatile void __iomem *addr);  /*寫入一個(gè)雙字*/

2. 程序編寫

本實(shí)驗(yàn)?zāi)康模壕帉慙inux下的LED燈驅(qū)動(dòng),通過應(yīng)用程序?qū).MX6U開發(fā)板上的LED燈(GPIO1_IO03)進(jìn)行開關(guān)操作

2.1 驅(qū)動(dòng)程序編寫

LED驅(qū)動(dòng)屬于字符設(shè)備驅(qū)動(dòng),之前介紹了新舊兩種字符驅(qū)動(dòng)的寫法,本篇中按照新字符設(shè)備驅(qū)動(dòng)的框架來(lái)編寫

圖片

接下來(lái)分步驟完善具體的驅(qū)動(dòng)代碼:

GPIO寄存器宏定義、設(shè)備結(jié)構(gòu)體定義

#define NEWCHRLED_CNT  1             //設(shè)備號(hào)個(gè)數(shù)
#define NEWCHRLED_NAME "newchrled"   //名字
#define LEDOFF    0                  //關(guān)燈
#define LEDON     1                  //開燈 
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE            (0X020C406C) 
#define SW_MUX_GPIO1_IO03_BASE    (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE    (0X020E02F4)
#define GPIO1_DR_BASE             (0X0209C000)
#define GPIO1_GDIR_BASE           (0X0209C004)
/* 映射后的寄存器虛擬地址指針 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* newchrled設(shè)備結(jié)構(gòu)體 */
struct newchrled_dev{
    dev_t devid;               //設(shè)備號(hào)
    struct cdev cdev;          //cdev
    struct class *class;       //類  
    struct device *device;     //設(shè)備
    int major;                 //主設(shè)備號(hào)
    int minor;                 //次設(shè)備號(hào)
};

struct newchrled_dev newchrled; //led設(shè)備

控制LED亮滅函數(shù)

void led_switch(u8 sta){
    u32 val = 0;
    if(sta == LEDON) {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3); 
        writel(val, GPIO1_DR);
    }else if(sta == LEDOFF) {
        val = readl(GPIO1_DR);
        val|= (1 << 3); 
        writel(val, GPIO1_DR);
    } 
}

設(shè)備操作函數(shù)集合

/* 打開設(shè)備 */
static int led_open(struct inode *inode, struct file *filp){
    filp->private_data = &newchrled;  //設(shè)置私有數(shù)據(jù)
    return 0;
}
/* 從設(shè)備讀取數(shù)據(jù) */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
    return 0;
}
/* 向設(shè)備寫數(shù)據(jù) */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\\r\\n");
        return -EFAULT;
    }

    ledstat = databuf[0];  //獲取狀態(tài)值

    if(ledstat == LEDON) { 
        led_switch(LEDON);  //打開LED燈
    } else if(ledstat == LEDOFF) {
        led_switch(LEDOFF);  //關(guān)閉LED燈
    }
    return 0;
}
/* 關(guān)閉設(shè)備 */
static int led_release(struct inode *inode, struct file *filp){
    return 0;
}
/* 設(shè)備操作函數(shù) */
static struct file_operations newchrled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release =  led_release,
};

在驅(qū)動(dòng)入口函數(shù)中:初始化GPIO外設(shè)

/* 驅(qū)動(dòng)入口函數(shù) */
static int __init led_init(void){
    u32 val = 0;
    /* 初始化LED */
    /* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
    /* 2、使能GPIO1時(shí)鐘 */
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);     //之前的設(shè)置 
    val |= (3 << 26);      //設(shè)置新值
    writel(val, IMX6U_CCM_CCGR1);
    /* 3、設(shè)置復(fù)用功能,并設(shè)置IO屬性 */
    writel(5, SW_MUX_GPIO1_IO03);
    writel(0x10B0, SW_PAD_GPIO1_IO03);
    /* 4、設(shè)置GPIO1_IO03為輸出功能 */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3);     //清除之前的設(shè)置
    val |= (1 << 3);      //設(shè)置為輸出
    writel(val, GPIO1_GDIR);
    /* 5、默認(rèn)關(guān)閉LED */
    val = readl(GPIO1_DR);
    val |= (1 << 3); 
    writel(val, GPIO1_DR);

在驅(qū)動(dòng)入口函數(shù)中:注冊(cè)字符設(shè)備

/* 注冊(cè)字符設(shè)備驅(qū)動(dòng) */
    /* 1、創(chuàng)建設(shè)備號(hào) */
    if (newchrled.major) {      //定義了設(shè)備號(hào)
        newchrled.devid = MKDEV(newchrled.major, 0);
        register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
    } else {                    //沒有定義設(shè)備號(hào)
        alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); 
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MINOR(newchrled.devid);
    }
    printk("newcheled major=%d,minor=%d\\r\\n",newchrled.major, newchrled.minor);  
    /* 2、初始化cdev */
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops); 
    /* 3、添加一個(gè)cdev */
    cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
    /* 4、創(chuàng)建類 */
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.class)) {
        return PTR_ERR(newchrled.class);
    }
    /* 5、創(chuàng)建設(shè)備 */
    newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.device)) {
        return PTR_ERR(newchrled.device);
    }
 
    return 0;
}

在驅(qū)動(dòng)出口函數(shù)中:取消地址映射,注銷字符設(shè)備驅(qū)動(dòng)

/* 驅(qū)動(dòng)出口函數(shù) */
static void __exit led_exit(void){
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);
    /* 注銷字符設(shè)備驅(qū)動(dòng) */
    cdev_del(&newchrled.cdev);
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

2.2 應(yīng)用程序編寫

LED驅(qū)動(dòng)加載成功以自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn),應(yīng)用程序通過向節(jié)點(diǎn)文件寫0或1,關(guān)閉/打開LED燈

#define LEDOFF  0
#define LEDON  1

int main(int argc, char *argv[]){
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];
 
    if(argc != 3){
        printf("Error Usage!\\r\\n");
        return -1;
    }

    filename = argv[1];

    /* 打開led驅(qū)動(dòng) */
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\\r\\n", argv[1]);
        return -1;
    }

    databuf[0] = atoi(argv[2]); //要執(zhí)行的操作:打開或關(guān)閉
    /* 向/dev/led文件寫入數(shù)據(jù) */
    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
        printf("LED Control Failed!\\r\\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd); 
    if(retvalue < 0){
        printf("file %s close failed!\\r\\n", argv[1]);
        return -1;
    }
    return 0;
}

3. 編譯測(cè)試

3.1 程序編譯

驅(qū)動(dòng)程序編譯:創(chuàng)建Makefile文件,使用make命令,編譯驅(qū)動(dòng)程序

KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := newchrled.o

build: kernel_modules

kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

應(yīng)用程序編譯:無(wú)需內(nèi)核參與,直接編譯即可

arm-linux-gnueabihf-gcc newchrledApp.c -o newchrledApp

3.2 運(yùn)行測(cè)試

為了方便,選擇通過TFTP從網(wǎng)絡(luò)啟動(dòng),并使用NFS掛載網(wǎng)絡(luò)根文件系統(tǒng)。 確保開發(fā)板能正常啟動(dòng),在Ubuntu中將驅(qū)動(dòng)和測(cè)試文件復(fù)制到modules/4.1.15目錄中

加載驅(qū)動(dòng)模塊

depmod                 #第一次加載驅(qū)動(dòng)的時(shí)候需要運(yùn)行此命令
modprobe newchrled.ko  #加載驅(qū)動(dòng)

驅(qū)動(dòng)加載成功后會(huì)自動(dòng)在/dev目錄下創(chuàng)建設(shè)備節(jié)點(diǎn)文件/dev/newchrdev,輸入如下命令查看

ls /dev/newchrled -l

之后就可使用應(yīng)用程序來(lái)測(cè)試驅(qū)動(dòng)是否能正常工作

./newchrledApp /dev/newchrled 1    #打開LED燈
./newchrledApp /dev/newchrled 0    #關(guān)閉LED燈

若要卸載驅(qū)動(dòng)輸入如下命令

rmmod newchrled.ko

至此,Linux 驅(qū)動(dòng)開發(fā)路上的第一個(gè)燈被成功點(diǎn)亮。 本文主要是通過操作寄存器來(lái)點(diǎn)亮開發(fā)板上的LED,通過編寫相應(yīng)的驅(qū)動(dòng)程序和應(yīng)用程序,實(shí)現(xiàn)程序設(shè)計(jì)的分層

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • led燈
    +關(guān)注

    關(guān)注

    22

    文章

    1592

    瀏覽量

    108007
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11304

    瀏覽量

    209521
  • 開發(fā)板
    +關(guān)注

    關(guān)注

    25

    文章

    5050

    瀏覽量

    97484
  • GPIO
    +關(guān)注

    關(guān)注

    16

    文章

    1204

    瀏覽量

    52104
  • MMU
    MMU
    +關(guān)注

    關(guān)注

    0

    文章

    91

    瀏覽量

    18291
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    IMX6ULL正點(diǎn)原子開發(fā)板LED驅(qū)動(dòng)

    用C語(yǔ)言裸機(jī)編程驅(qū)動(dòng)正點(diǎn)原子I.MAX6ULL開發(fā)板上的LED
    的頭像 發(fā)表于 05-02 15:22 ?25.9w次閱讀
    IMX6ULL正點(diǎn)原子<b class='flag-5'>開發(fā)板</b><b class='flag-5'>LED</b><b class='flag-5'>驅(qū)動(dòng)</b>

    基于STM32開發(fā)板LED

    硬件資源LEDSTM32開發(fā)板線纜LED驅(qū)動(dòng)電路LED
    發(fā)表于 08-11 09:11

    如何配置Linux開發(fā)板的GPIO

    (I.MX6ULL)環(huán)境:Ubuntu 20.04 (LTS) (內(nèi)核版本:Linux 5.4.0)交叉編譯器:arm-linux-gnueabihf 4.9.4一、目的編寫裸機(jī)代碼(匯編)點(diǎn)亮LED
    發(fā)表于 12-15 07:46

    基于Study210開發(fā)板點(diǎn)亮LED的相關(guān)資料推薦

    Linux 嵌入式系列】點(diǎn)亮LED實(shí)戰(zhàn)記錄--基于Study210開發(fā)板Linux 嵌入式系列】點(diǎn)亮
    發(fā)表于 12-16 07:36

    TQ2440開發(fā)板按鍵點(diǎn)亮LED驅(qū)動(dòng)開發(fā)詳解

    記錄了作者在TQ2440開發(fā)板上實(shí)現(xiàn)按鍵點(diǎn)亮LED驅(qū)動(dòng)開發(fā)的詳細(xì)過程,還記錄了一些容易出現(xiàn)的錯(cuò)誤,以及怎么解決這些錯(cuò)誤。 一、驅(qū)動(dòng)
    發(fā)表于 11-04 16:46 ?179次下載
    TQ2440<b class='flag-5'>開發(fā)板</b>按鍵點(diǎn)亮<b class='flag-5'>LED</b><b class='flag-5'>驅(qū)動(dòng)</b><b class='flag-5'>開發(fā)</b>詳解

    嵌入式開發(fā)板_iTOP-4412開發(fā)板linux系統(tǒng)存儲(chǔ)空間

    [入式開發(fā)板]4412開發(fā)板linux 系統(tǒng)存儲(chǔ)空間的修改
    發(fā)表于 02-29 16:58 ?13次下載

    開發(fā)板上的LED閃爍起來(lái)吧

    這節(jié)課給大家講一下如何讓開發(fā)板上網(wǎng)絡(luò)編號(hào)為 D4 LED 閃爍起來(lái),先看一下開發(fā)板上關(guān)于 LED 的原理圖
    發(fā)表于 11-11 17:17 ?13次下載

    如何配置和操作Linux驅(qū)動(dòng)程序開發(fā)板

    本文檔概述了利用Linux開發(fā)板Linux 內(nèi)核開發(fā)驅(qū)動(dòng)程序的基礎(chǔ)知識(shí),并簡(jiǎn)單介紹了如何配置和操作
    的頭像 發(fā)表于 02-15 13:36 ?3064次閱讀
    如何配置和操作<b class='flag-5'>Linux</b><b class='flag-5'>驅(qū)動(dòng)</b>程序<b class='flag-5'>開發(fā)板</b>

    利用Linux開發(fā)板為TLV320ADC5120開發(fā)Linux內(nèi)核驅(qū)動(dòng)的方法

    利用Linux開發(fā)板為TLV320ADC5120開發(fā)Linux內(nèi)核驅(qū)動(dòng)的方法
    發(fā)表于 10-28 11:59 ?0次下載
    利用<b class='flag-5'>Linux</b><b class='flag-5'>開發(fā)板</b>為TLV320ADC5120<b class='flag-5'>開發(fā)</b><b class='flag-5'>Linux</b>內(nèi)核<b class='flag-5'>驅(qū)動(dòng)</b>的方法

    通過Web網(wǎng)頁(yè)控制開發(fā)板LED

    接下來(lái)將介紹如何通過Web網(wǎng)頁(yè)來(lái)控制開發(fā)板上的LED,本文只是在網(wǎng)頁(yè)上實(shí)現(xiàn)功能,并無(wú)交互功能,與開發(fā)板的交互功能實(shí)現(xiàn)將在《Web網(wǎng)頁(yè)點(diǎn)燈二》中介紹
    的頭像 發(fā)表于 04-25 15:05 ?1570次閱讀
    通過Web網(wǎng)頁(yè)控制<b class='flag-5'>開發(fā)板</b><b class='flag-5'>LED</b><b class='flag-5'>燈</b>

    匯編驅(qū)動(dòng)IMX6ULL LED

    用匯編編寫正點(diǎn)原子Linux開發(fā)板Led驅(qū)動(dòng)
    的頭像 發(fā)表于 05-01 09:19 ?25.9w次閱讀
    匯編<b class='flag-5'>驅(qū)動(dòng)</b>IMX6ULL <b class='flag-5'>LED</b><b class='flag-5'>燈</b>

    迅為基于RK3568開發(fā)板的嵌入式學(xué)習(xí)之Linux驅(qū)動(dòng)視頻

    迅為基于RK3568開發(fā)板的嵌入式學(xué)習(xí)之Linux驅(qū)動(dòng)視頻
    的頭像 發(fā)表于 05-19 16:30 ?982次閱讀
    迅為基于RK3568<b class='flag-5'>開發(fā)板</b>的嵌入式學(xué)習(xí)之<b class='flag-5'>Linux</b><b class='flag-5'>驅(qū)動(dòng)</b>視頻

    STM32 Linux開發(fā)板推薦 ,入門進(jìn)階必備!

    推薦一款適合入門進(jìn)階學(xué)習(xí)的Linux開發(fā)板:華清遠(yuǎn)見FS-MP1A開發(fā)板(STM32MP157開發(fā)板開發(fā)板介紹 FS-MP1A
    發(fā)表于 10-22 09:22 ?1次下載

    fpga開發(fā)板linux開發(fā)板區(qū)別

    FPGA開發(fā)板Linux開發(fā)板是兩種不同的硬件開發(fā)平臺(tái),各自具有不同的特點(diǎn)和應(yīng)用場(chǎng)景。在以下的文章中,我將詳細(xì)介紹FPGA開發(fā)板
    的頭像 發(fā)表于 02-01 17:09 ?2265次閱讀

    linux開發(fā)板與樹莓派的區(qū)別

    定義和用途 Linux開發(fā)板Linux開發(fā)板是一種基于Linux操作系統(tǒng)的嵌入式開發(fā)板,通常用
    的頭像 發(fā)表于 08-30 15:34 ?969次閱讀