在前面的LED驅動控制中,都只使用了一個裝置節點(一個次裝置號)來進行操作,本例來討論一下如何把三個基色的LED分別當成三個次裝置,即產生出三個裝置節點檔案,但共用一個裝置驅動(同一個主裝置號),應用程式各自控制各自的LED 。
下面先給出完整的驅動程式程式碼,檔名仍為led.c。
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> //以下定義匯流排及暫存器的實體地址 #define AHB4_PERIPH_BASE (0x50000000) #define RCC_BASE (AHB4_PERIPH_BASE + 0x0000) #define RCC_MP_GPIOENA (RCC_BASE + 0xA28) #define GPIOA_BASE (AHB4_PERIPH_BASE + 0x2000) #define GPIOA_MODER (GPIOA_BASE + 0x0000) #define GPIOA_OTYPER (GPIOA_BASE + 0x0004) #define GPIOA_OSPEEDR (GPIOA_BASE + 0x0008) #define GPIOA_PUPDR (GPIOA_BASE + 0x000C) #define GPIOA_ODR (GPIOA_BASE + 0x0014) #define GPIOA_BSRR (GPIOA_BASE + 0x0018) #define GPIOG_BASE (AHB4_PERIPH_BASE + 0x8000) #define GPIOG_MODER (GPIOG_BASE + 0x0000) #define GPIOG_OTYPER (GPIOG_BASE + 0x0004) #define GPIOG_OSPEEDR (GPIOG_BASE + 0x0008) #define GPIOG_PUPDR (GPIOG_BASE + 0x000C) #define GPIOG_ODR (GPIOG_BASE + 0x0014) #define GPIOG_BSRR (GPIOG_BASE + 0x0018) #define GPIOB_BASE (AHB4_PERIPH_BASE + 0x3000) #define GPIOB_MODER (GPIOB_BASE + 0x0000) #define GPIOB_OTYPER (GPIOB_BASE + 0x0004) #define GPIOB_OSPEEDR (GPIOB_BASE + 0x0008) #define GPIOB_PUPDR (GPIOB_BASE + 0x000C) #define GPIOB_ODR (GPIOB_BASE + 0x0014) #define GPIOB_BSRR (GPIOB_BASE + 0x0018) //以下定義時鐘控制暫存器名稱 volatile void __iomem *RCC_MP_AHB4ENSETR; volatile void __iomem *GPIO_MODER_PA; volatile void __iomem *GPIO_OTYPER_PA; volatile void __iomem *GPIO_OSPEEDR_PA; volatile void __iomem *GPIO_PUPDR_PA; volatile void __iomem *GPIO_ODR_PA; volatile void __iomem *GPIO_BSRR_PA; volatile void __iomem *GPIO_MODER_PB; volatile void __iomem *GPIO_OTYPER_PB; volatile void __iomem *GPIO_OSPEEDR_PB; volatile void __iomem *GPIO_PUPDR_PB; volatile void __iomem *GPIO_ODR_PB; volatile void __iomem *GPIO_BSRR_PB; volatile void __iomem *GPIO_MODER_PG; volatile void __iomem *GPIO_OTYPER_PG; volatile void __iomem *GPIO_OSPEEDR_PG; volatile void __iomem *GPIO_PUPDR_PG; volatile void __iomem *GPIO_ODR_PG; volatile void __iomem *GPIO_BSRR_PG; dev_t devid; //裝置號 struct cdev chrdev[3]; //字元裝置結構體陣列 struct class *class; //類結構體 struct device *device; //裝置結構體 //實現open函式,為file_oprations結構體成員函式 static int led_open(struct inode *inode, struct file *filp) { unsigned int tmp; //以下使能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0); iowrite32(tmp, RCC_MP_AHB4ENSETR); return 0; } //實現write函式,為file_oprations結構體成員函式 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { unsigned char value; unsigned long n; unsigned int minor; minor = iminor(file_inode(filp)); //獲取次裝置號 n = copy_from_user(&value, buf, cnt); //從應用空間獲取值 switch(minor) { case 0: //次裝置號0表示紅色LED if(value) iowrite32(0x2000, GPIO_BSRR_PA);//熄滅紅色LED else iowrite32(0x20000000, GPIO_BSRR_PA);//點亮紅色LED break; case 1: //次裝置號1表示綠色LED if(value) iowrite32(0x04, GPIO_BSRR_PG);//熄滅綠色LED else iowrite32(0x40000, GPIO_BSRR_PG);//點亮綠色LED break; case 2: //次裝置號2表示藍色LED if(value) iowrite32(0x20, GPIO_BSRR_PB);//熄滅藍色LED else iowrite32(0x200000, GPIO_BSRR_PB);//點亮藍色LED default: break; } return 0; } //實現release函式,為file_oprations結構體函式 static int led_release(struct inode *inode, struct file *filp) { unsigned int tmp; //以下禁能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp &= ~0x43; iowrite32(tmp, RCC_MP_AHB4ENSETR); return 0; } //定義一個file_oprations型別的結構體,名為led_dev_fops,包含上述宣告的成員函式 static struct file_operations led_dev_fops = { .owner = THIS_MODULE, .open = led_open, //指定open函式成員 .write = led_write, //指定write函式成員 .release = led_release, //指定release函式成員 }; //初始化函式,此處為驅動模組的入口函式 static int __init led_init(void) { unsigned int tmp; dev_t cur_id; //以下實現各個暫存器的地址對映 RCC_MP_AHB4ENSETR = ioremap(RCC_MP_GPIOENA, 4); GPIO_MODER_PA = ioremap(GPIOA_MODER, 4); GPIO_OTYPER_PA = ioremap(GPIOA_OTYPER, 4); GPIO_OSPEEDR_PA = ioremap(GPIOA_OSPEEDR, 4); GPIO_PUPDR_PA = ioremap(GPIOA_PUPDR, 4); GPIO_ODR_PA = ioremap(GPIOA_ODR, 4); GPIO_BSRR_PA = ioremap(GPIOA_BSRR, 4); GPIO_MODER_PB = ioremap(GPIOB_MODER, 4); GPIO_OTYPER_PB = ioremap(GPIOB_OTYPER, 4); GPIO_OSPEEDR_PB = ioremap(GPIOB_OSPEEDR, 4); GPIO_PUPDR_PB = ioremap(GPIOB_PUPDR, 4); GPIO_ODR_PB = ioremap(GPIOB_ODR, 4); GPIO_BSRR_PB = ioremap(GPIOB_BSRR, 4); GPIO_MODER_PG = ioremap(GPIOG_MODER, 4); GPIO_OTYPER_PG = ioremap(GPIOG_OTYPER, 4); GPIO_OSPEEDR_PG = ioremap(GPIOG_OSPEEDR, 4); GPIO_PUPDR_PG = ioremap(GPIOG_PUPDR, 4); GPIO_ODR_PG = ioremap(GPIOG_ODR, 4); GPIO_BSRR_PG = ioremap(GPIOG_BSRR, 4); //以下使能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp |= (0x1 << 6) | (0x1 << 1) | (0x1 << 0); iowrite32(tmp, RCC_MP_AHB4ENSETR); //以下把GPIOA、GPIOB、GPIOG埠配置為輸出、上位模式 tmp = ioread32(GPIO_MODER_PA); tmp &= ~(0x3 << 26); tmp |= (0x1 << 26); iowrite32(tmp, GPIO_MODER_PA); tmp = ioread32(GPIO_MODER_PB); tmp &= ~(0x3 << 10); tmp |= (0x1 << 10); iowrite32(tmp, GPIO_MODER_PB); tmp = ioread32(GPIO_MODER_PG); tmp &= ~(0x3 << 4); tmp |= (0x1 << 4); iowrite32(tmp, GPIO_MODER_PG); tmp = ioread32(GPIO_PUPDR_PA); tmp &= ~(0x3 << 26); tmp |= (0x1 << 26); iowrite32(tmp, GPIO_PUPDR_PA); tmp = ioread32(GPIO_PUPDR_PB); tmp &= ~(0x3 << 10); tmp |= (0x1 << 10); iowrite32(tmp, GPIO_PUPDR_PB); tmp = ioread32(GPIO_PUPDR_PG); tmp &= ~(0x3 << 4); tmp |= (0x1 << 4); iowrite32(tmp, GPIO_PUPDR_PG); //以下設定GPIOA、GPIOB、GPIOG埠初始值 iowrite32(0x2000, GPIO_BSRR_PA); iowrite32(0x20, GPIO_BSRR_PB); iowrite32(0x04, GPIO_BSRR_PG); //以下禁能GPIOA、GPIOB、GPIOG埠時鐘 tmp = ioread32(RCC_MP_AHB4ENSETR); tmp &= ~0x43; iowrite32(tmp, RCC_MP_AHB4ENSETR); //申請主裝置號 if(alloc_chrdev_region(&devid, 0, 1, "led") < 0) { printk("Couldn't alloc_chrdev_region!\r\n"); return -EFAULT; } //建立類 class = class_create(THIS_MODULE, "led_dev"); //以下實現三個字元型裝置的申請和註冊及建立三個裝置節點 for (tmp=0; tmp < 3; tmp++) { chrdev[tmp].owner = THIS_MODULE; //繫結前面宣告的file_oprations型別的結構體到字元裝置 cdev_init(&chrdev[tmp], &led_dev_fops); cur_id = MKDEV(MAJOR(devid), MINOR(devid) + tmp); //填充上面申請到的主裝置號到字元裝置 if(cdev_add(&chrdev[tmp],cur_id, 1) < 0) { printk("Couldn't add chrdev!\r\n"); return -EFAULT; } //根據建立的類生成一個裝置節點 device = device_create(class, NULL, cur_id, NULL, "led%d", tmp); } return 0; } //退出函式,此處為驅動模組的出口函式 static void __exit led_exit(void) { unsigned int tmp; dev_t cur_id; //以下實現各個暫存器的解除對映 iounmap(RCC_MP_AHB4ENSETR); iounmap(GPIO_MODER_PA); iounmap(GPIO_OTYPER_PA); iounmap(GPIO_OSPEEDR_PA); iounmap(GPIO_PUPDR_PA); iounmap(GPIO_ODR_PA); iounmap(GPIO_BSRR_PA); iounmap(GPIO_MODER_PB); iounmap(GPIO_OTYPER_PB); iounmap(GPIO_OSPEEDR_PB); iounmap(GPIO_PUPDR_PB); iounmap(GPIO_ODR_PB); iounmap(GPIO_BSRR_PB); iounmap(GPIO_MODER_PG); iounmap(GPIO_OTYPER_PG); iounmap(GPIO_OSPEEDR_PG); iounmap(GPIO_PUPDR_PG); iounmap(GPIO_ODR_PG); iounmap(GPIO_BSRR_PG); //以下銷燬三個字元型裝置及三個裝置節點 for (tmp=0; tmp < 3; tmp++) { //刪除字元裝置 cdev_del(&chrdev[tmp]); cur_id = MKDEV(MAJOR(devid), MINOR(devid) + tmp); //銷燬裝置節點 device_destroy(class, cur_id); } //釋放主裝置號 unregister_chrdev_region(devid, 1); //銷燬類 class_destroy(class); } module_init(led_init); //模組入口宣告 module_exit(led_exit); //模組出口宣告 MODULE_LICENSE("GPL"); //GPL協議宣告
接下來是配套的Makefile檔案,內容如下。
KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := led.o all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
然後給出應用程式,內容如下,檔名為app.c。
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd; unsigned char val = 0; if( argc != 3 ) //命令引數不對時提示 { printf("Usage :\n"); printf("%s <all|red|green|blue> <on|off>\n", argv[0]); return 0; } if(strcmp(argv[1], "all") == 0) { if(strcmp(argv[2], "on") == 0) { val = 0; //值為0時點亮 fd = open("/dev/led0", O_RDWR);//開啟裝置節點0 write(fd, &val, 1); //把值寫入裝置節點0 close(fd); //關閉裝置節點0 fd = open("/dev/led1", O_RDWR);//開啟裝置節點1 write(fd, &val, 1); //把值寫入裝置節點1 close(fd); //關閉裝置節點1 fd = open("/dev/led2", O_RDWR);//開啟裝置節點2 write(fd, &val, 1); //把值寫入裝置節點2 close(fd); //關閉裝置節點2 } else { val = 1; //值為1時熄滅 fd = open("/dev/led0", O_RDWR);//開啟裝置節點0 write(fd, &val, 1); //把值寫入裝置節點0 close(fd); //關閉裝置節點0 fd = open("/dev/led1", O_RDWR);//開啟裝置節點1 write(fd, &val, 1); //把值寫入裝置節點1 close(fd); //關閉裝置節點1 fd = open("/dev/led2", O_RDWR);//開啟裝置節點2 write(fd, &val, 1); //把值寫入裝置節點2 close(fd); //關閉裝置節點2 } } else { if(strcmp(argv[1], "red") == 0) { fd = open("/dev/led0", O_RDWR); //開啟裝置節點0 if( fd < 0 ) printf("can`t open\n"); if(strcmp(argv[2], "on") == 0) val = 0; //值為0時紅色點亮 else val = 1; //值為1時紅色熄滅 } else if(strcmp(argv[1], "green") == 0) { fd = open("/dev/led1", O_RDWR); //開啟裝置節點1 if( fd < 0 ) printf("can`t open\n"); if(strcmp(argv[2], "on") == 0) val = 0; //值為0時綠色點亮 else val = 1; //值為1時綠色熄滅 } else if(strcmp(argv[1], "blue") == 0) { fd = open("/dev/led2", O_RDWR); //開啟裝置節點2 if( fd < 0 ) printf("can`t open\n"); if(strcmp(argv[2], "on") == 0) val = 0; //值為0時藍色點亮 else val = 1; //值為1時藍色熄滅 } write(fd, &val, 1); //把值寫入裝置節點 close(fd); //關閉裝置節點 } return 0; }
完成後,先執行make命令編譯驅動程式,生成名為led.ko的驅動模組檔案。然後對應用程式進行交叉編譯,執行“arm-linux-gnueabihf-gcc app.c -o app”即可。最後,把編譯生成的驅動模組檔案led.ko和應用程式檔案app一起複製到NFS共享目錄下 ,並在開發板上執行“insmod led.ko”,把模組插入到核心中(可執行lsmod命令檢視一下是否載入成功),之後可檢視一下/dev目錄,應該生成了三個裝置節點檔案led0、led1、led2,分別對應紅、綠、藍三個LED。此外,還可以執行“cat /proc/devices”檢視一下led裝置的主裝置號。
以上都正常後,就可以執行應用程式來進行測試了。以控制紅色為例,輸入“./app red on”並回車,就可看到紅色LED亮,輸入“./app red off”並回車,就可看到紅色LED滅,其他顏色的LED可如法進行測試。輸入“./app all on”回車可實現三個LED全部點亮,輸入“./app all off”回車實現三個LED全部熄滅。實驗結果與“嵌入式Linux中的LED驅動控制”一文中的完全一樣。
把上面的驅動程式碼和“嵌入式Linux中的LED驅動控制”一文中的程式碼進行比較可以看出,最大區別在於字元型cdev結構體一共註冊了3個,裝置節點也建立了3個。主裝置號使用動態方式申請了1個,次裝置號則指定了3個(0~2),即三個裝置共用了一個驅動。
上面的程式碼中,最關鍵的是如何獲取到開啟檔案的次裝置號。本例在write介面函式中使用了函式iminor(file_inode(filp))來實現,minor函式呼叫了file_inode函式,file_inode的引數為file結構體指標。關於這部分的詳細介紹可參考“嵌入式Linux中字元型驅動程式的基本框架”一文。其實,獲取開啟裝置節點檔案次裝置的方式還有很多,比如使用函式iminor(inode)方式,或使用宏MINOR(inode->i_rdev)方式等等,返回值就是獲取到的次裝置號。