嵌入式Linux中的LED驅動控制(基於misc)

fxzq發表於2024-06-14

在開發驅動程式的時候,有時希望快速地實現其業務功能,以把精力放在檔案介面功能的測試上來。這樣,對於常見的一些繁瑣而又不能省略的步驟(如裝置號的申請、字元裝置的註冊、裝置節點檔案的建立等),就希望能儘量地簡化。這時,就可以借用Linux核心提供的misc(雜項)來實現。

這裡仍然使用“嵌入式Linux中的LED驅動控制”一文中的例子,實現三個LED的驅動,但改成使用misc來實現,其驅動程式程式碼如下,檔名為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>
#include <linux/miscdevice.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_IDR (GPIOA_BASE + 0x0010)
#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)
//以下定義時鐘控制暫存器名稱
static void __iomem *RCC_MP_AHB4ENSETR;
static void __iomem *GPIO_MODER_PA;
static void __iomem *GPIO_OTYPER_PA;
static void __iomem *GPIO_OSPEEDR_PA;
static void __iomem *GPIO_PUPDR_PA;
static void __iomem *GPIO_ODR_PA;
static void __iomem *GPIO_BSRR_PA;
static void __iomem *GPIO_MODER_PB;
static void __iomem *GPIO_OTYPER_PB;
static void __iomem *GPIO_OSPEEDR_PB;
static void __iomem *GPIO_PUPDR_PB;
static void __iomem *GPIO_ODR_PB;
static void __iomem *GPIO_BSRR_PB;
static void __iomem *GPIO_MODER_PG;
static void __iomem *GPIO_OTYPER_PG;
static void __iomem *GPIO_OSPEEDR_PG;
static void __iomem *GPIO_PUPDR_PG;
static void __iomem *GPIO_ODR_PG;
static void __iomem *GPIO_BSRR_PG;
//實現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;
    n = copy_from_user(&value, buf, cnt);    //從應用空間獲取值
    switch(value)            //根據應用空間的值判斷具體操作
  {
        case 0:                        //全部點亮三個LED
            iowrite32(0x20000000, GPIO_BSRR_PA);
            iowrite32(0x200000, GPIO_BSRR_PB);
            iowrite32(0x40000, GPIO_BSRR_PG);
            break;
        case 1:                        //點亮紅色LED
            iowrite32(0x20000000, GPIO_BSRR_PA);
            break;
        case 2:                        //點亮綠色LED
            iowrite32(0x40000, GPIO_BSRR_PG);
            break;
        case 3:                        //點亮藍色LED
            iowrite32(0x200000, GPIO_BSRR_PB);
            break;
        case 4:                        //熄滅紅色LED
            iowrite32(0x2000, GPIO_BSRR_PA);
            break;
        case 5:                        //熄滅綠色LED
            iowrite32(0x04, GPIO_BSRR_PB);
            break;
        case 6:                        //熄滅藍色LED
            iowrite32(0x20, GPIO_BSRR_PG);
            break;
        case 7:                        //全部熄滅三個LED
            iowrite32(0x2000, GPIO_BSRR_PA);
            iowrite32(0x20, GPIO_BSRR_PB);
            iowrite32(0x04, GPIO_BSRR_PG);
            break;
        default:                        //全部熄滅
            iowrite32(0x2000, GPIO_BSRR_PA);
            iowrite32(0x20, GPIO_BSRR_PB);
            iowrite32(0x04, GPIO_BSRR_PG);
            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函式成員
};
//定義一個miscdevice型別的結構體,名為led
static struct miscdevice led = {
    .minor = 0,                 //定義次裝置號
    .name = "led",              //定義裝置節點名稱
    .fops = &led_dev_fops,      //指定file_oprations介面
};
//初始化函式,此處為驅動模組的入口函式
static int __init led_init(void)
{
    unsigned int tmp;
    //以下實現各個暫存器的地址對映
    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);
    //註冊misc裝置
    tmp = misc_register(&led);
    return tmp;
}
//退出函式,此處為驅動模組的出口函式
static void __exit led_exit(void)
{
    //以下實現各個暫存器的解除對映
    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);
    //銷燬misc裝置
    misc_deregister(&led);
}
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

Makefile檔案中的第一行指定了開發板的核心原始碼位置,若放到了其他路徑可自行修改。另外,該內容在複製貼上後,需要修改命令兩行(倒數第1、3行)前面的空餘部分,修改為Tab鍵開頭。

以下是測試用的應用程式程式碼,檔名為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;
   fd = open("/dev/led", O_RDWR);        //開啟裝置節點
   if( fd < 0 )
      printf("can`t open\n");
   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時全部點亮
      else
         val = 7;                        //值為7時全部熄滅
    }
   else if(strcmp(argv[1], "red") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 1;                        //值為1時紅色點亮
      else
        val = 4;                        //值為4時紅色熄滅
    }
   else if(strcmp(argv[1], "green") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 2;                        //值為2時綠色點亮
      else
        val = 6;                        //值為6時綠色熄滅
    }
   else if(strcmp(argv[1], "blue") == 0)
    {
      if(strcmp(argv[2], "on") == 0)
        val = 3;                        //值為3時藍色點亮
      else
        val = 5;                        //值為5時藍色熄滅
    }
   write(fd, &val, 1);            //把值寫入裝置節點
   close(fd);                     //關閉裝置節點
   return 0;
}

執行make命令編譯驅動程式,成功後會生成名為led.ko的驅動模組檔案。再對應用程式進行交叉編譯,執行“arm-linux-gnueabihf-gcc app.c -o app”即可。實驗的果與“嵌入式Linux中的LED驅動控制”一文中的完全一樣,這裡就不給出了。

把上面的驅動程式碼和“嵌入式Linux中的LED驅動控制”一文中的程式碼進行比較可以看出,本例的程式碼省略了動態申請裝置號、定義字元結構體cdev、建立類、建立裝置節點檔案等部分,只關注了具體的控制過程。為何這些內容可以被精簡?其時並沒有減,而是在Linux核心中,本來就有一個名為misc的裝置(雜項裝置),其主裝置號固定為10(可透過cat /proc/devices檢視)。本例程式碼其實就是“借用”了這個裝置名和主裝置號,所以就不用再申請和註冊了。misc裝置有一個自己的結構體名為struct miscdevice,在它內部可透過minor成員指定次裝置號(主裝置號已固定為10),透過name成員指定裝置節點檔案的名稱(裝置名稱已固定為misc),透過fops成員指定file_oprations結構體。一般只需要指定這三個成員變數就可以了,檔案操作的介面仍然透過file_oprations結構體來指定。在驅動模組的入口函式中,在進行暫存器對映和配置之後,呼叫了misc_register函式,對前面定義的miscdevice結構體(misc裝置)進行註冊。在出口函式中,在解除暫存器對映之後,呼叫了misc_deregister函式,對註冊的裝置進行銷燬。

本例程式碼中使用的應用程式和“嵌入式Linux中的LED驅動控制”一文中的完全一樣,因為驅動的裝置節點檔案沒變,對於驅動操作的介面(file_oprations)也沒變,所以可以延用該應用程式。

可見,透過misc雜項裝置的應用,可讓驅動開發者專注於具體功能的實現,而忽略一些形式化的東西。當然,在開發測試完成後,最好還是回到通用的方法上來,這樣比較規範和保險。

相關文章