当前位置: 首页 > news >正文

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

在前面的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 CCM_BASE_ADDR         (0x20C4000)
#define CCM_CCGR1_ADDR        (CCM_BASE_ADDR + 0x6C)
#define CCM_CCGR2_ADDR        (CCM_BASE_ADDR + 0x70)
#define CCM_CCGR3_ADDR        (CCM_BASE_ADDR + 0x74)
#define GPIO1_BASE_ADDR       (0x209C000)
#define GPIO1_DR_ADDR         (GPIO1_BASE_ADDR + 0x0000)
#define GPIO1_GDIR_ADDR       (GPIO1_BASE_ADDR + 0x0004)
#define GPIO1_PSR_ADDR        (GPIOB_BASE_ADDR + 0x0008)
#define GPIO4_BASE_ADDR       (0x20A8000)
#define GPIO4_DR_ADDR         (GPIO4_BASE_ADDR + 0x0000)
#define GPIO4_GDIR_ADDR       (GPIO4_BASE_ADDR + 0x0004)
#define GPIO4_PSR_ADDR        (GPIOC_BASE_ADDR + 0x0008)
#define SW_MUX_CTL_ADDR       (0x20E0000)
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR   (SW_MUX_CTL_ADDR + 0x6C)
#define IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x1E0)
#define IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x1DC)
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR   (SW_MUX_CTL_ADDR + 0x2F8)
#define IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x46C)
#define IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR    (SW_MUX_CTL_ADDR + 0x468)
//以下定义时钟控制寄存器名称
static void __iomem *CCM_CCGR1;
static void __iomem *CCM_CCGR3;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC;
static void __iomem *IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC;
static void __iomem *IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC;
static void __iomem *GPIO1_GDIR;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO4_GDIR;
static void __iomem *GPIO4_DR;
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;//以下使能GPIO1、GPIO4的端口时钟tmp = ioread32(CCM_CCGR1);tmp |= 0xC000000;iowrite32(tmp, CCM_CCGR1);tmp = ioread32(CCM_CCGR3);tmp |= 0x3000;iowrite32(tmp, CCM_CCGR3);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 int tmp;unsigned long n;unsigned int minor;minor = iminor(file_inode(filp));        //获取次设备号n = copy_from_user(&value, buf, cnt);    //从应用空间获取值    switch(minor){case  0:                            //次设备号0表示红色LEDif(value){                //熄灭红色LEDtmp = ioread32(GPIO1_DR);tmp |= (1<<4);iowrite32(tmp, GPIO1_DR);}else{                //点亮红色LEDtmp = ioread32(GPIO1_DR);tmp &= ~(1<<4);iowrite32(tmp, GPIO1_DR);}break;case 1:                             //次设备号1表示绿色LEDif(value){                //熄灭绿色LEDtmp = ioread32(GPIO4_DR);tmp |= (1<<20);iowrite32(tmp, GPIO4_DR);}else{                //点亮绿色LEDtmp = ioread32(GPIO4_DR);tmp &= ~(1<<20);iowrite32(tmp, GPIO4_DR);}break;case 2:                             //次设备号2表示蓝色LEDif(value){                //熄灭蓝色LEDtmp = ioread32(GPIO4_DR);tmp |= (1<<19);iowrite32(tmp, GPIO4_DR);}else{                //点亮蓝色LEDtmp = ioread32(GPIO4_DR);tmp &= ~(1<<19);iowrite32(tmp, GPIO4_DR);}default:break;}return cnt;
}
//实现release函数,为file_oprations结构体函数
static int led_release(struct inode *inode, struct file *filp)
{unsigned int tmp;//以下禁止GPIO1、GPIO4的端口时钟tmp = ioread32(CCM_CCGR1);tmp &= ~0xC000000;iowrite32(tmp, CCM_CCGR1);tmp = ioread32(CCM_CCGR3);tmp &= ~0x3000;iowrite32(tmp, CCM_CCGR3);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;//以下实现各个寄存器的地址映射CCM_CCGR1 = ioremap(CCM_CCGR1_ADDR, 4);CCM_CCGR3 = ioremap(CCM_CCGR3_ADDR, 4);IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04_ADDR, 4);IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC_ADDR, 4);IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC_ADDR, 4);IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 = ioremap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04_ADDR, 4);IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC_ADDR, 4);IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC = ioremap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC_ADDR, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_ADDR, 4);GPIO4_GDIR = ioremap(GPIO4_GDIR_ADDR, 4);GPIO1_DR = ioremap(GPIO1_DR_ADDR, 4);GPIO4_DR = ioremap(GPIO4_DR_ADDR, 4);//以下使能GPIO1、GPIO4的端口时钟tmp = ioread32(CCM_CCGR1);tmp |= 0xC000000;iowrite32(tmp, CCM_CCGR1);tmp = ioread32(CCM_CCGR3);tmp |= 0x3000;iowrite32(tmp, CCM_CCGR3);//以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为GPIO模式tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);tmp = 0x05;iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);tmp = 0x05;iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);tmp = ioread32(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);tmp = 0x05;iowrite32(tmp, IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);//以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为上拉、低速、等模式tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);tmp = 0xF838;iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);tmp = 0xF838;iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);tmp = ioread32(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);tmp = 0xF838;iowrite32(tmp, IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);//以下把GPIO1_4、GPIO4_19、GPIO4_20端口配置为输出tmp = ioread32(GPIO1_GDIR);tmp |= 0x10;iowrite32(tmp, GPIO1_GDIR);tmp = ioread32(GPIO4_GDIR);tmp |= 0x180000;iowrite32(tmp, GPIO4_GDIR);//以下设定GPIO1_4、GPIO4_19、GPIO4_20端口的初始值tmp = ioread32(GPIO1_DR);tmp |= (1<<4);iowrite32(tmp, GPIO1_DR);tmp = ioread32(GPIO4_DR);tmp |= (1<<20 | 1<<19);iowrite32(tmp, GPIO4_DR);//以下禁止GPIO1、GPIO4的端口时钟tmp = ioread32(CCM_CCGR1);tmp &= ~0xC000000;iowrite32(tmp, CCM_CCGR1);tmp = ioread32(CCM_CCGR3);tmp &= ~0x3000;iowrite32(tmp, CCM_CCGR3);//申请主设备号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(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04);iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04);iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC);iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC);iounmap(IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC);iounmap(IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC);iounmap(GPIO1_GDIR);iounmap(GPIO1_DR);iounmap(GPIO4_GDIR);iounmap(GPIO4_DR);iounmap(CCM_CCGR1);iounmap(CCM_CCGR3);//以下销毁三个字符型设备及三个设备节点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协议声明
MODULE_INFO(intree,"Y");

接下来是配套的Makefile文件,内容如下。

KERNEL_DIR=/opt/ebf_linux_kernel/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
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);//打开设备节点0write(fd, &val, 1);            //把值写入设备节点0close(fd);                     //关闭设备节点0fd = open("/dev/led1", O_RDWR);//打开设备节点1write(fd, &val, 1);            //把值写入设备节点1close(fd);                     //关闭设备节点1fd = open("/dev/led2", O_RDWR);//打开设备节点2write(fd, &val, 1);            //把值写入设备节点2close(fd);                     //关闭设备节点2
      }else{val = 1;                       //值为1时熄灭fd = open("/dev/led0", O_RDWR);//打开设备节点0write(fd, &val, 1);            //把值写入设备节点0close(fd);                     //关闭设备节点0fd = open("/dev/led1", O_RDWR);//打开设备节点1write(fd, &val, 1);            //把值写入设备节点1close(fd);                     //关闭设备节点1fd = open("/dev/led2", O_RDWR);//打开设备节点2write(fd, &val, 1);            //把值写入设备节点2close(fd);                     //关闭设备节点2
      }}else{if(strcmp(argv[1], "red") == 0){fd = open("/dev/led0", O_RDWR);   //打开设备节点0if( fd < 0 )printf("can`t open\n");if(strcmp(argv[2], "on") == 0)val = 0;                        //值为0时红色点亮elseval = 1;                        //值为1时红色熄灭
      }else if(strcmp(argv[1], "green") == 0){fd = open("/dev/led1", O_RDWR);   //打开设备节点1if( fd < 0 )printf("can`t open\n");if(strcmp(argv[2], "on") == 0)val = 0;                        //值为0时绿色点亮elseval = 1;                        //值为1时绿色熄灭
      }else if(strcmp(argv[1], "blue") == 0){fd = open("/dev/led2", O_RDWR);   //打开设备节点2if( fd < 0 )printf("can`t open\n");if(strcmp(argv[2], "on") == 0)val = 0;                        //值为0时蓝色点亮elseval = 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)方式等等,返回值就是获取到的次设备号。

http://www.jsqmd.com/news/1012681/

相关文章:

  • 避坑指南:GEE计算城市热岛效应时,MODIS和Landsat数据该怎么选?看完这篇不再纠结
  • 三步快速上手的暗黑破坏神2存档修改器终极指南
  • 终极指南:Maid - 免费开源的移动AI助手,让AI模型在手机上触手可及
  • Win10BloatRemover:Windows 10系统清理与优化的终极指南
  • AI语音技术落地实践:从TTS模型训练到企业级语音Agent架构
  • Koikatu HF Patch:终极增强补丁,一键解锁完整游戏体验
  • MPC8309 eLBC控制器GPCM/FCM模式配置与NAND Flash接口实战
  • AI社交中的死亡与税务状态感知机制设计
  • 2026年深圳众智商学院CPPM采购成本控制课程咨询怎么确认?报名资料和8800元费用核对方式 - 众智商学院职业教育
  • AI 算法题分类与标签体系:从题目特征到知识点的自动映射
  • 2026淮南本地考生看过来!家门口的公办复读班,一年逆袭全日制大专(官方最新发布) - cc江江
  • 洛雪音乐音源配置完全指南:从零开始打造你的专属无损音乐库
  • 哔咔漫画下载器:打造高效离线漫画图书馆的终极解决方案
  • 5步解锁完整功能:如何突破Cursor使用限制
  • MPC8309 eLBC寄存器配置实战:从基址到时序的嵌入式内存控制器详解
  • Mi-Create技术架构深度解析:小米穿戴设备表盘开发的全栈解决方案
  • 如何快速掌握Onekey:面向初学者的Steam游戏清单自动化下载器完整指南
  • 终极Windows文件资源管理器标签管理指南:Explorer Tab Utility完整教程
  • 用CSDN_AI数字营销做AI辅助内容分发_我试了一周
  • 2026中小学阅读指导师证书报考全流程_报名条件_学习方式_证书含金量_就业前景 - 教育推荐官【官方】
  • 告别内存焦虑:用三星CMM-H TM给服务器“加内存”的保姆级方案(附成本分析)
  • LSPatch技术深度解析:免Root框架的架构设计与实践指南
  • MPC8260 SCC透明模式:从硬件配置到同步与CRC的工程实践
  • 如何快速安装Realtek RTL8125 2.5GbE网卡驱动:面向Linux新手的完整指南
  • 2026惠州黄金回收靠谱门店TOP5:惠奢汇(惠城旗舰店)中检认证+全城上门 - 生活测评小能手
  • BilibiliDown:终极B站视频下载器,5分钟掌握高效离线观看技巧
  • 如何快速掌握fSpy:静态图像相机匹配的终极指南
  • 2026国学与现代教育教师证书值得考吗?报考条件_学习方式_就业方向_含金量分析 - 教育推荐官【官方】
  • LiteDB.Studio终极指南:轻松管理嵌入式文档数据库的免费可视化工具
  • 2026年安徽初三初三中考考不上高中怎么办?上什么学校好?最新发布 - 我叫小周