首页 > 系统相关 >嵌入式Linux中的LED驱动控制(使用多个次设备号)

嵌入式Linux中的LED驱动控制(使用多个次设备号)

时间:2024-06-16 21:32:31浏览次数:26  
标签:__ LED 嵌入式 BASE fd Linux 设备 节点 define

在前面的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)方式等等,返回值就是获取到的次设备号。

标签:__,LED,嵌入式,BASE,fd,Linux,设备,节点,define
From: https://www.cnblogs.com/fxzq/p/18239889

相关文章

  • 【Linux】线程(一)
    谈论之前需要先谈论一些线程的背景知识其中就有进程地址空间,又是这个让我们又爱又恨的东西。注意:全篇都是在32位的情况下进行的目录背景知识:地址空间:内存:页表:基于以上理解文件缓冲区与虚拟地址:文件缓冲区:虚拟地址:线程:linux下的线程:与进程的澄清:win下的进程:与linux......
  • Linux 系统下工作中常用的shell命令
    目录ls:列出目录内容cd:改变当前工作目录pwd:显示当前工作目录的路径cp:复制文件或目录mv:移动文件或目录rm:删除文件或目录mkdir:创建新目录touch:创建空文件cat:连接文件并打印到标准输出设备上grep:在文件中查找模式find:在文件系统中查找文件这些命令是非常常用的,每个命令......
  • Linux 文件的权限信息解读 chmod修改权限 数字序号表示权限
    ls-l#列出当前文件显示详细信息drwxr-xr-x.2dpctest6Jun1507:45test.txt共分为三部分drwxr-xr-x.:表示文件和文件夹的权限信息dpc:文件,文件夹所属的用户test:文件和文件夹所属的用户组drwxr-xr-x解读d表示为文件夹rwx表示dpc的权限r-x所属用户组......
  • Linux 内核定时器实验
    Linux内核定时器实验内核时间管理简介Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频......
  • Linux的Terminal调用不出来,一直转圈圈
    后来发现是环境变量的问题[oracle@ora19rac01~]$cat.bash_profile#.bash_profile#Getthealiasesandfunctionsif[-f~/.bashrc];then.~/.bashrcfi#Userspecificenvironmentandstartupprograms#aliassqlplus="rlwrapsqlplus"#aliasrman......
  • 云计算【第一阶段(14)】Linux的目录和结构
    一、Liunx目录结构1.1、linux目录结构linux目录结构是树形目录结构根目录(树根)所有分区,目录,文件等的位置起点整个树形目录结构中,使用独立的一个"/",表示1.2、常见的子目录必须知道目录路径目录作用/root系统管理员root的宿主目录/home普通用户的宿主目录/boot系统内核、......
  • linux的权限管理
    linux的权限管理1.权限介绍和示例root用户权限最高,所以一般对它不做什么权限设置。其他用户就要设定权限并且遵守权限了。文件权限:#文件属性[root@localhost~]#touch1.txt[root@localhost~]#ls-l总用量4-rw-r--r--.1rootroot06月1519:091.txt-r......
  • 【3】Linux常见命令
    常用的操作系统有哪些:【1】Windows操作系统:》不同的版本:WindowsXP,Windows7,Windows10【2】Linux操作系统:》不同的版本:centos6.5,redhat红帽,Ubuntu乌班图centos用的比较多,但版本比较老,服务器首选,内核比较稳定Ubuntu用的也比较多,版本比较新【3】Unix操作系统【4】Macos苹果......
  • Linux测试点对点连接速度工具
    iPerfiperf是一个网络性能测试工具,它可以测试TCP和UDP带宽质量,可以测量最大TCP带宽,具有多种参数和UDP特性,可以报告带宽,延迟抖动和数据包丢失。利用iperf这一特性,可以用来测试一些网络设备如路由器,防火墙,交换机等的性能。Debian系的发行版可以使用如下命令安装iPerf......
  • 在Linux中,如何修改IP地址、网关和主机名?
    在Linux中,修改IP地址、网关和主机名可以通过不同的方法实现,具体取决于你使用的是哪种网络管理工具和主机名管理方式。下面我将分别介绍静态配置和使用NetworkManager工具的两种情况。1.修改IP地址、网关(静态配置)修改IP地址和子网掩码:通常需要编辑网络接口的配置文件。对于基......