首页 > 系统相关 >嵌入式Linux中的LED驱动控制(基于misc)

嵌入式Linux中的LED驱动控制(基于misc)

时间:2024-06-14 23:43:12浏览次数:27  
标签:__ LED void misc BASE static Linux iomem define

在开发驱动程序的时候,有时希望快速地实现其业务功能,以把精力放在文件接口功能的测试上来。这样,对于常见的一些繁琐而又不能省略的步骤(如设备号的申请、字符设备的注册、设备节点文件的创建等),就希望能尽量地简化。这时,就可以使用Linux内核提供的misc(杂项)来实现。

这里仍然使用“嵌入式Linux中的LED驱动控制”一文中的例子,实现三个LED的驱动,但改成使用misc来实现,其驱动程序代码如下。

#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杂项设备的应用,可让驱动开发者专注于具体功能的实现,而忽略一些形式化的东西。当然,在开发测试完成后,最好还是回到通用的方法上来,这样比较规范和保险。

标签:__,LED,void,misc,BASE,static,Linux,iomem,define
From: https://www.cnblogs.com/fxzq/p/18239884

相关文章

  • 【安装笔记-20240613-Linux-在 OpenWrt 的 LuCI界面支持命令行调试】
    安装笔记-系列文章目录安装笔记-20240613-Linux-在OpenWrt的LuCI界面支持命令行调试文章目录安装笔记-系列文章目录安装笔记-20240613-Linux-在OpenWrt的LuCI界面支持命令行调试前言一、软件介绍名称:ttyd主页官方介绍特点二、安装步骤测试版本:openwrt-23.05.3......
  • Linux openSSH 只能够使用SFTP 不能使用ssh登陆
    实施目标:1. 用户只能通过sftp访问,不能登录SSH2.用户要被锁定在特定的目录下,没有读写其它目录的权限 下面开始:首先修改sshd的配置文件:#vim/etc/ssh/sshd_config将该文件的末尾修改如下:#Subsystem sftp /usr/lib/openssh/sftp-server#该行(上面这行)注释掉Subsystem sftp......
  • 使用shell脚本在Linux中管理Java应用程序
    目录前言一、目录结构二、脚本实现1.脚本内容2.使用说明2.1配置脚本2.2脚本部署2.3操作你的Java应用总结前言在日常开发和运维工作中,管理基于Java的应用程序是一项基础且频繁的任务。本文将通过一个示例脚本,展示如何利用Shell脚本简化这一流程,实现Java应用的一键式启动、......
  • fs.1.10 ON rockeylinux8 dockerfile模式
     概述freeswitch是一款简单好用的VOIP开源软交换平台。rockeylinux8docker上编译安装fs.1.10的流程记录,本文使用dockerfile模式。环境dockerengine:Version24.0.6rockylinuxdocker:8freeswitch:v1.10.7dockerfile创建空目录,创建dockerfile文件。github访问经常失败,先......
  • BUUCTF-Misc(141-150)
    [MRCTF2020]小O的考研复试flag=0foriinrange(19260817):flag=(flag*10+2)%1000000007print(flag)flag{577302567}真的很杂参考:BUUCTF真的很杂_buuctfclass.dex-CSDN博客BUUCTF-真的很杂-「配枪朱丽叶。」(hatenablog.com)没啥思路,binwalk直接......
  • Linux 并发与竞争
    Linux并发与竞争并发与竞争Linux系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话可能会导致系统崩溃。现在的Linux系统并发产生的原因很复杂,总结一下有下面几个主要......
  • Linux下的python的部署
    一、python的验证一般情况下liunx系统都会自带python输入python--version便可以查看当前安装的python版本二、Linux系统下安装anaconda步骤大纲:下载anaconda文件→更改文件的权限→开始安装→完成交互问题(更改安装位置)→配置环境变量→更改文件目录权限2.1、下载anacon......
  • 在Linux中,如何停止正在运行的进程?
    在Linux中,停止正在运行的进程可以采取多种方法,具体取决于你希望如何控制进程以及进程的当前状态。以下是一些常用的方法:1.使用kill命令kill命令是最常用的停止进程的方法。你需要知道进程的进程ID(PID)。发送SIGTERM信号(默认):killPID这会给进程发送一个终止信号(SIGTERM),......
  • 在Linux中,什么是initrd镜像?
    在Linux系统中,initrd(initialRAMdisk)镜像是一个非常重要的组件,它用于在启动过程中加载必要的内核模块和驱动,以便系统能够完全引导起来。以下是具体介绍:概念与作用:initrd是一个被压缩的磁盘映像,它在系统启动时被加载到内存中,并且暂时作为根文件系统使用。其主要目的是在系统启......
  • 在Linux中,如何检查某项服务是否在运行?
    在Linux系统中,检查某项服务是否正在运行可以通过多种方法实现。以下是一些常用的命令和步骤:使用systemctl命令:systemctlstatus服务名:此命令可以显示服务的状态,包括是否正在运行、启动日志等信息。例如,检查Apache服务是否运行,可以使用:systemctlstatusapache2.service。......