当前位置:网站首页 > Go语言开发 > 正文

linux内核驱动开发(linux内核驱动开发常用知识)



        Linux系统源码提供了操作系统的核心功能,如进程管理内存管理文件系统等。

        BusyBox这类的文件系统构建工具,则提供了在这些核心功能之上运行的一系列实用工具和命令,使得用户能够执行常见的文件操作、文本处理、网络配置等任务。文件系统构建工具的运行,依赖于内核源码

        Linux内核源码是开源的,其官方网站为https://www.kernel.org/,在该网站上可以下载到最新的Linux内核源码。进入该网站后,你会看到多个版本的内核分支,主要包括:

        主线版本(Mainline):这是由Linux之父Linus Torvalds领导的开发团队维护的最新、最前沿的内核版本。它包含了最新的功能和改进,但也可能包含未完全稳定的代码。

        稳定版本(Stable):这些版本在主线版本的基础上进行了进一步的测试和修复,以确保稳定性和可靠性。它们通常用于生产环境。

        长期支持版本(Longterm):这些版本在发布后会得到较长时间的支持和维护,通常用于需要长期稳定性和兼容性的场景。

嵌入式开发板与Linux内核源码

        在嵌入式系统开发中,开发板如迅为开发板,通常使用特定版本的Linux内核源码,这些源码可能是基于官方源码进行定制和优化的。半导体厂商或开发板制造商会提供经过适配和优化的内核源码包,以便在特定的硬件平台上运行。

迅为开发板与Linux内核

        底板+核心板设计:迅为开发板采用这种设计,方便用户进行扩展和定制。用户可以根据需要选择不同的核心板,以适应不同的应用场景。

        定制内核源码:迅为提供基于Linux官方源码定制的内核源码包,这些源码包已经过优化,以适应迅为开发板的硬件特性。

        开发便捷性:迅为开发板提供丰富的接口和文档,帮助用户快速上手并减少开发阶段的工作量。用户可以使用这些资料来编译和调试Linux内核,以满足自己的开发需求。

        内核源码解压后,得到很多文件夹,这些文件夹各司其职。

arch:跨平台架构代码,处理各CPU架构特定功能。

block:块设备驱动与管理,如硬盘读写。

crypto:加密、压缩及校验算法,保障数据安全。

Documentation:内核设计与开发指南相关文档。

drivers:设备驱动大全,按类别组织。

firmware:特殊固件,用于硬件初始化或功能实现。

fs:文件系统代码,支持多文件系统。

include:内核编程基础头文件,定义宏、类型及函数原型。

init:系统启动初始化代码,准备运行环境。

ipc:进程间通信代码,如管道、消息队列。

kernel:内核核心,含进程调度等关键子系统

lib:内核通用库函数,如字符串处理、数学运算。

  1. 准备编译环境
    • 确保您已经安装了迅为电子有限公司提供的编译环境,该环境已经过测试,可以直接用于编译内核源码。
    • 如果您使用的是虚拟机Ubuntu,请确保虚拟机配置满足编译要求,包括足够的内存和存储空间。
  2. 内核源码获取
    • 内核源码存放在指定路径:“iTOP-RK3568 开发板【底板 V1.7 版本】03_【iTOP-RK3568 开发板】指南教程02_Linux 驱动配套资料02_Linux_SDK 源码”。
    • 将此目录下的内核源码(通常是压缩包,如)拷贝到虚拟机Ubuntu的某个目录下,例如。
  3. 解压内核源码
    • 使用终端(Terminal)进入存放内核源码压缩包的目录
    • 执行解压命令:。这将解压出目录,其中包含内核源码和其他相关文件。
  4. 进入内核源码目录
    • 使用命令进入解压后的内核源码目录:。
  5. 编译内核源码
    • 在内核源码目录下,通常会有一个编译脚本,如。对于迅为iTOP-RK3568开发板,您可以使用命令来编译内核源码。
    • 编译过程可能需要一些时间,具体取决于您的计算机性能。编译过程中,脚本会自动处理依赖关系、配置选项等,并生成编译后的内核镜像文件。
  6. 检查编译结果
    • 编译完成后,您可以在指定的输出目录中找到编译后的内核镜像文件(如或等),以及可能的模块文件(文件)。
    • 确保没有编译错误,并检查编译日志以获取任何警告或提示信息。
  7. 后续步骤
    • 一旦内核源码编译成功,您可以将编译后的内核镜像烧录到开发板中,以进行进一步的测试和开发。
    • 如果需要,您还可以根据项目的具体需求,修改内核配置选项,并重新编译内核。

        讯为提供的 linux内核源码包解压后,包含有 kerner源码和 buildroot、debian、tocto等构建工具,build.sh编译脚本,prebuilds交叉编译工具链,u-boot源码等。

        运行 build.sh编译脚本后出现图形化操作界面。

        按照官方文档给的顺序执行编译操作。

        完了生成 uboot.img到 uboot目录,内核生成 boot.img到 kernel目录。

        值得注意的是第四步,在Build.sh界面选择rootfs,会进入文件系统镜像选择界面。

 

        发现这里有 5种镜像供我们选择,没有 BusyBox,查阅官方资料后发现 BusyBox要单独解压BusyBox工具进行编译。 

        这里选择 buildroot镜像,编译完成后

         打包 firmware下的固件包,选择 Build.sh界面的firmware选项,脚本会自动运行将所有固件移动到 rockdev目录下。

        打包成 update.img所需要的各种功能镜像就都拷贝到了 rockdev目录下,

         最后,还是在 Build.sh界面,选择 updateing,整个 rockdev目录下的全部固件,都会被打包成一个整体的 update.img镜像。最后用 官方提供的程序烧录工具,将镜像烧录到开发板。简单的文件管理系统,其实就是 linux操作系统,的移植,就做好了。

使用 NFS,将 讯为板子文件系统上的文件夹挂载到 ubuntu上,

 

        判断客户端和主机能不能Ping通,要用 ip&子网掩码,看是否相等,相等才是处于同一网段,才能Ping通。 不同网段需要配置指定网关才能实现互通。

        记得检查板子和虚拟机是不是都联网了。没联网检查虚拟机模式设置(桥接、NAT、仅主机)。

        1、可使用 dhclient指令设置网卡自动获取 ip地址。

        2、或者直接修改虚拟机网络配置。

NFS将讯为板子挂载到虚拟机上成功

        在本小节中,我们将编写一个简单的Linux内核驱动——helloworld驱动。

        这个驱动主要用于演示Linux内核模块的基本结构和注册/注销流程。以下是对提供的helloworld.c代码的解释和必要的背景知识。

模块初始化函数     static int __init helloworld_init(void)

模块清理函数        static void __exit helloworld_exit(void)

模块注册和注销宏         module_init()         module_exit()

模块许可证                 MODULE_LICENSE()

头文件包含

 

模块初始化函数

 

模块清理函数

 

模块注册和注销宏

 

模块许可证

 
 

        下载交叉编译器并解压到/usr/local目录。

解压命令:

 

修改环境变量,在/etc/profile文件末尾添加

 

        首先,确保 Makefile和 helloworld.o位于同一级目录下,并参照以下模板编写Makefile:

 

        makefile文件和要编译成模块的源码文件要在一个目录下,在该目录下执行make命令,得到helloworld.ko 模块文件。

        要运行模块有两种方式,一种是将模块编译进内核内核运行模块自动运行;另一种是手动加载和卸载模块,这样模块加载时会调用模块入口函数,模块卸载时会调用模块出口函数

        insmod命令用于加载模块,若 A.ko依赖B.ko,则须先加载B.ko。

        rmmod命令用于卸载模块。

        modprobe命令用于加载模块,自动加载依赖模块。

        modprobe -r 命令用于卸载模块,自动卸载依赖模块。

        modprobe要求驱动文件位于/lib/modules/generic目录下

 
 

驱动模块传参在嵌入式系统开发中的重要性

        在嵌入式系统开发中,驱动模块传参是一个关键的技术点,它允许在运行时动态地调整或配置硬件设备的参数,而无需重新编译整个内核或模块

Linux内核提供了几个宏来支持不同类型的参数传递:

1. 基本类型参数:使用module_param(name, type, perm)宏。

        name:模块参数的名称。

        type:参数的数据类型(如int, bool, charp等)。

        perm:参数在sysfs中的访问权限(如S_IRUGO表示可读)。

 

2. 数组类型参数:使用module_param_array(name, type, nump, perm)宏。

        name:模块参数的名称。

        type:数组元素的数据类型

        nump:指向存储数组元素个数的变量的指针

        perm:访问权限

 

3. 字符串类型参数:使用module_param_string(name, string, len, perm)宏。

        name:外部传入的参数名。

        string:程序内定义的字符串变量名。

        len:字符串buffer的大小

        perm:访问权限

 

权限宏定义

        权限宏定义在 include/linux/stat.h中,如:

 

         模块参数的访问权限,代表了参数在 sysfs 文件系统中所对应文件节点的属性。

        sysfs是一个虚拟文件系统,用于导出内核对象的信息给用户空间内核模块可以利用sysfs来暴露其配置参数,使得这些参数可以在运行时被用户空间程序读取和修改

 

        parameter.c文件编写完以后,执行 makefile进行编译,得到 .ko驱动模块文件。

        使用 insmod xxx.ko或者 probemode xxx.ko 装载驱动时,由于通过 module_param 定义了参数宏来接收传递的参数,因此 装载驱动时可以传递参数。

        符号导出:允许一个模块中的函数或变量对其他模块可见

EXPORT_SYMBOL():导出参数指定的符号,函数或变量,使其对所有模块可见。

EXPORT_SYMBOL_GPL():导出符号,但仅对符合GPL许可的模块可见。

extern 关键字引用外部变量

(GNU General Public License,GPL,GNU通用公共许可) 

 

        mathmodule.c 文件定义了一个整数 num 和一个加法函数 add(),并将其导出以供其他模块使用。

 

        hello.c 文件引用 mathmodule.c 中的 num 和 add() 函数,并将结果打印到串口终端。

 

        linux内核可以输入make menuconfig来使用 mconf程序打开图形化配置界面。

        内核的图形化配置界面需要 ncurses库支持。

        图形化配置界面主要有以下四种,在这四种方式中,最推荐的是 make menuconfig,它不依赖于 QT 或 GTK+,且非常直观。

make config (基于文本的最为传统的配置界面,不推荐使用)

make menuconfig (基于文本菜单的配置界面)

make xconfig (要求 QT 被安装)

make gconfig (要求 GTK+ 被安装)

        图形化配置界面中的每一个界面都会对应一个 Kconfig 文件。所以图形化配置界面的每一级菜单是由 Kconfig 文件来决定的。

        图形化配置界面有很多菜单。所以就会有很多 Kconfig 文件,因此内核源码的每个子目录下都存在Kconfig文件。

        我们在图形化配置界面配置好了以后,使用 make config以后,

        会通过 scripts/kconfig 目录下的 mconf程序去解析 Kconfig文件,在编译内核的时候会根据这个.config 文件来编译内核。     

        .config 会通过 syncconfig 目标将.config 作为输入然后输出编译需要的文件,重点关注 auto.confautoconf.h

         auto.conf 中存放的是配置信息。在内核源码的顶层 Makefile中会包含 auto.conf文件,并引用其中的变量来控制编译哪些驱动

        defconfig 文件Linux 系统默认的用来生成.config文件的配置文件

        defconfig文件位于 arch/$(ARCH)/configs目录下。 

        .config文件位于内核源码顶层目录,用于存储内核编译时的配置选项。编译内核时,make命令会根据.config文件中的配置来生成内核镜像。

        如果.config文件已存在,make menuconfig会加载.config中的配置作为默认配置,允许用户通过图形界面进行修改。修改并保存make menuconfig中的配置后,.config文件会被更新以反映新的配置。

        如果.config文件不存在,可以使用make XXX_defconfig命令来生成它。

        make XXX_defconfig命令会根据 XXX_defconfig文件中的配置自动生成 .config文件。

        图形化配置界面的每个页面都是由一个Kconfig文件来生成的。

        Kernel下的Kconfig文件,是主页面,其他KConfig是子菜单。

        修改主页的Kconfig,就能实现自定义菜单。

        我们自己写了 helloworld驱动,想把自己的驱动编译进内核,每次内核运行驱动自动运行。

        内核的 drivers/char文件夹存放字符设备驱动

        创建一个 hello 文件夹,将,将 hello.c拷贝进去。

        在 hello文件夹下,创建 Kconfig文件

 

        然后在hello文件夹下创建 Makefile文件

 

        接着修改上一级目录,也就是 driver/char 目录,的 Kconfig文件和 Makefile文件,添加如下内容。

 
 

        最后打开 menuconfig图形化配置工具,

        在配置界面选择 helloworld驱动,选择 *,代表设置成Y。

         然后选择 save,将配置保存到 .config文件,然后退出配置界面,

        然后输入以下命令,

 

        编译成功后,在 drivers/char/hello目录下生成对应的 .o文件。说明已经将驱动编译进内核。

        将编译好的内核镜像烧写到开发板上后,开发板系统启动就可看到 helloworld驱动成功加载,初始化函数__initxxx执行。

        字符设备是Linux中以字符为单位进行I/O传输的设备,支持标准的文件操作(如打开、关闭、读写等)。

        LED、按键、IIC、SPI、LCD等常见硬件在Linux中通常被实现为字符设备。通过设备号可以定位并操作这些设备。

8.1.1 设备号申请

        在Linux系统中,每个设备都有唯一的设备号,分为主设备号(用于标识驱动)和次设备号(用于区分同一驱动下的不同设备)。

        设备号的申请可通过静态或动态两种方式:

register_chrdev_region() 函数静态申请设备号

alloc_chrdev_region() 函数动态申请设备号

unregister_chrdev_region() 函数注销设备号

        字符设备号其实是字符设备区域号

静态申请

        使用 register_chrdev_region()函数静态申请设备号,需指定起始设备号申请的设备数量

 

动态申请

        使用 alloc_chrdev_region()函数,内核会自动分配一个未使用的设备号

 

注销设备号

 

8.1.2 设备号类型

        Linux内核中,设备号(dev_t)是一个32位无符号整数,用于唯一标识系统中的设备。

        这个类型在内核源码中定义,其中高12位代表主设备号低 20位代表次设备号

        dev_t 类型通过 u32定义,位于不同的头文件中。设备号相关的宏定义在 linux/kdev_t.h 中,这些宏包括:

 

        如果驱动模块加载时,传入了设备号,则通过静态方式申请设备号

        如果驱动模块加载时,没有传设备号,则通过动态方式分配设备号

 

注册字符设备可以分为两个步骤:

        1.字符设备初始化

        2.字符设备的添加

9.1.1 字符设备初始化

        在 Linux内核中,struct cdev结构体表示字符设备

        cdev_init() 初始化字符设备结构体cdev将设备结构体与 file_operations结构体关联。 

        该结构体包含了字符设备的核心信息,如内核对象所属的内核模块设备操作函数集设备号、全局字符设备列表的连接等。

 
 

9.1.2 字符设备的注册

        cdev_add()函数用来注册字符设备。为设备号赋值,将内核结构体与设备号关联。

        cdev_del() 函数用来注销字符设备。将设备号归零,从内核结构体删除设备号。

字符设备的注册

        cdev_add() 函数用于为字符设备struct cdev结构体的设备号成员赋值,也就是将内核结构体与设备号关联起来

 

字符设备的注销

        cdev_del()函数用于从内核中删除一个之前已经注册的 struct cdev结构体

 

        在驱动程序卸载时,应调用 cdev_del()来注销设备,以确保系统资源的正确释放。

 

        编译出.ko文件,装载模块后,可以在 /proc/devices看到注册的字符设备,包括设备号和设备名。

        Linux 操作系统一切皆文件,设备访问也是通过文件的方式来进行的,用来进行设备访问的文件称之为设备节点设备节点被创建在/dev 目录

        设备节点的创建过程,是先创建设备类结构体,然后向设备类结构体对象添加设备号

10.1.1 手动创建设备节点

        mknod命令用于创建设备节点

        设备类型可以是 b(块设备)c(字符设备)p(管道)

        需要指定设备的主设备号和从设备号。

 

10.1.2 自动创建设备节点

        在Linux内核中,设备文件的自动创建通常通过udev(嵌入式系统中mdev作为简化版)机制实现。这一机制能够根据系统中硬件设备的状态自动创建或删除设备文件

        设备类(device class)。这个“class”是结构体名,含义是逻辑上的分组,用于将具有相似功能的设备组织在一起。

        class_create()           //动态创建设备节点,并将其添加到Linux内核系统中。

        class_destroy()         //从Linux内核系统中删除指定的设备节点

        device_create()        //在指定设备节点中创建一个设备

        device_destroy()      //删除指定设备节点中的设备

 
 

        udev用户空间的守护进程,它通过netlink机制监听内核空间的设备事件动态管理设备节点

        当内核检测到设备变化时,会发送 uevent事件到用户空间,udevd守护进程接收到事件后执行相应的操作。

 

        在Linux内核中,字符设备通过申请设备号注册字符设备(使用struct cdev结构体)并与设备号关联来注册。为了使用户空间访问这些设备,需要创建设备节点(通常位于/dev目录下)。

        在Linux字符设备驱动开发中,struct file_operations 结构体用来操作设备节点

        定义了一系列操作函数的指针,每个操作函数指针都被设置为指向相应的处理函数。

        这些函数指针,如 read、write、open、release 和 unlocked_ioctl 等,对应着用户空间对设备的读写、打开、关闭和控制操作。 通常定义 file_operations 结构体后,会让 open 指向 chrdev_open,read 指向 chrdev_read 等。

        当 close文件时,会调用 release函数执行清理工作,释放模块资源、减少模块使用计数等

        unlocked_ioctlioctl的变体,在调用时不用再上大内核锁(BKL),也就是调用之前不再先调用 lock_kernel()然后再 unlock_kernel()

        struct cdev 结构体则表示一个字符设备,通过 cdev_init() 初始化与 file_operations结构体的关联,然后通过 cdev_add() 将该字符设备注册到内核中。

 
 

        在Linux内核中,杂项设备(misc devices)提供了一种简化字符设备注册的方式,特别适用于那些不需要复杂设备号管理且操作相对简单的设备。使用杂项设备可以节省主设备号,并简化了设备驱动的编写过程。

定义一个杂项设备主要涉及填充 miscdevice结构体,该结构体通常包含以下关键成员:

        minor:次设备号,可以设置为MISC_DYNAMIC_MINOR来让系统自动分配。

        name:设备名称,注册成功后,在/dev目录下会生成以该名称命名的设备节点。

        fops:指向 file_operations结构体的指针,定义了设备的操作函数集。

        list:链表节点。

 

int misc_register();         //杂项设备注册。将 miscdevice结构体挂载到 misc_list链表。

int misc_deregister();     //杂项设备卸载。将 miscdevice结构体从 misc_list链表移除。

 

        杂项设备驱动通常使用 file_operations结构体来定义设备操作(如读、写等),

        并使用 miscdevice结构体来注册设备。

 

       后来到/sys/class/misc 目录下,

        可以看到名为“xxx”的文件夹(miscdevice结构体定义的杂项设备名字)已经被创建了,

        在/sys/class/misc 目录下有 misc 类的所有设备,每个注册的杂项设备对应一个文件夹目录

        内核空间直接管理硬件资源

        用户空间的应用程序不能直接访问硬件,必须通过内核提供的系统调用来间接访问。

系统调用:应用程序主动请求内核服务,通过系统调用接口实现,如read()、write()等。

软中断:由当前正在执行的指令产生,如定时器到期、程序异常等,触发内核处理。

硬中断:由外部设备产生,通知CPU处理事件,CPU响应后执行相应的中断处理程序。

        为了安全地在用户空间和内核空间传输数据,

        Linux内核提供了 copy_to_user()copy_from_user()这两个函数。

 
 

数据写入

        当用户空间通过写操作(如write系统调用)向设备文件写入数据时,cdev_test_write函数会被调用。该函数使用 copy_from_user()函数将用户空间的数据安全复制到内核空间的缓冲区中。

 

数据读取

        当用户空间通过读操作(如read系统调用)从设备文件读取数据时,cdev_test_read函数会被调用。将内核空间的一个字符串通过 copy_to_user()函数安全地复制到用户空间提供的缓冲区中。

 

        struct file表示一个打开的文件。

        每当用户在用户空间通过系统调用(如 open)打开一个文件或设备时,内核都会创建一个 struct file 的实例来跟踪这个打开的文件或设备的相关信息。 

 

file_operations绑定

 

测试APP

 

        从这里也能看出,每个设备驱动的设备号都跟 file_oprations结构体绑定,那些常见的系统调用如read、write、release等方法其实底层调用的是 file_operations结构体的成员指向的方法。

        像 read、write这种系统调用更底层 是 copy_to_user()copy_from_user()

        在Linux设备驱动开发中,使用 struct file结构体private_data字段来存储设备相关的私有数据是一种非常常见的做法。这种机制允许驱动程序在文件操作的不同阶段(如打开、读取、写入、关闭等)访问设备特定的信息。

open 函数中的设置

 
 
 

        Linux设备驱动通过主设备号来标识驱动类型,通过次设备号来区分同一类型下的不同设备。当多个设备共享同一驱动但次设备号不同时,驱动内部通过为每个设备分配并维护一个独立的私有数据结构(private_data)来区分和管理这些设备。

        container_of 宏通过结构体成员的指针结构体的类型以及该成员在结构体中的名称,计算出并返回包含该成员的结构体指针

 

定义用来管理设备信息的结构体,包含struct cdev成员

 

file_operations定义与初始化

 

驱动入口函数

        一个驱动兼容不同的设备,申请设备号时指明子设备的范围多个设备的设备号注册到同一个设备节点下

 

打开设备

        想要一个驱动兼容多个不同的设备,该驱动装载时,要在入口函数注册不同的子设备,因此/dev下会有不同设备名的设备文件

        通过open+文件名的方式打开不同子设备文件时,进入这些子设备绑定的同一个 file_operations结构体获取操作方法

        因此可以通过 struct file* file->privatedata,在打开文件时,获取设备文件的用来管理 cdev结构体的结构体,从而判断打开的是哪个设备,在写入时执行不同的操作。实现一个驱动兼容不同设备。(其实完全可以通过 inode直接获取cdev然后获取设备号来区分设备,这里主要是讲container_of和 inode的用法)

        在 open文件时,使用 container_of宏传入 inode结构体的 i_cdev指针,得到指针指向的包含 cdev指针成员的结构体,并传入文件私有数据 file->privatedata

        当你看到

 
 

        它是在说:“我有一个指向 cdev 结构体的指针(即 inode->i_cdev),

        我知道这个 cdev 结构体实际上是 struct device_test 结构体中的一个成员(即 cdev_test)。

        因此,我可以通过这个 cdev 指针和 cdev_test 成员在 struct device_test 中的偏移量,反向计算出整个 struct device_test 结构体的地址。”

        可以这么理解,这个指针是inode的成员,而指针指向的cdev不是inode的成员,而是cdevrest的成员,而通过 container_of得到的是 cdev成员的所属结构体。

        注意,这里 container_of从 inode的 cdev指针成员,得到的不是 inode的首地址,而是 cdev指针成员指向的 cdev实例的所属结构体的首地址。

        所注册的 cdev实例对于整个设备文件应该是唯一的。

 

        inode 结构体包含了文件的元数据(如权限、所有者、代表的字符设备cdev等),而 file 结构体则包含了打开文件的状态信息(如当前的读写位置、打开的文件模式等)。

         在 Linux 内核的字符设备驱动中,inode 结构体代表了文件系统中的一个节点,而 inode->i_cdev 指向该驱动文件通过 cdev_add()注册进内核的 cdev结构体

 

写入设备

 
 
 

        在 Linux 内核中,对于错误处理有一个独特且高效的机制,它利用了内核地址空间的一部分来存储错误信息,而不是直接返回错误码

        这种机制通过 ERR_PTRIS_ERRPTR_ERR 等宏来实现,使得错误处理和返回值检查变得更加直接和统一。

1. 错误指针的概念

        错误指针(也称为无效指针)指向内核地址空间特定区域(通常是内核地址空间的高端部分)的指针。这些区域并不实际映射到任何物理内存或设备,而是被保留用作错误码的表示。对于 64 位系统,通常 0xfffffffffffff000 到 0xffffffffffffffff 这段地址就是这样一个区域。

2. 相关宏的解释

ERR_PTR(error)

        接受一个错误码返回错误指针来报告错误

IS_ERR(ptr)

        这个宏检查给定的指针是否是一个错误指针。就是看地址是否在特定区域。

PTR_ERR(ptr)

        用于从错误指针中提取原始的错误码

        它通过一些位操作(如掩码和位移)来实现,因为错误指针实际上是通过将错误码转换为特定范围内的地址来创建的。

 

        RK3568一共有 5组GPIO,也就是GPIO0~4。

        每组GPIO为一个Bank,每个Bank有 32引脚,包括 Group (GPIOA(0~7) ~ D( 0~7)) 。

        也就是一共 5*4*8=160个引脚。

LED原理图例子

        由上图可以看出,LED 灯是由 GPIO0_B7 控制的。

        当 GPIO0_B7 为高电平时,三极管 Q16导通,LED9 点亮。当 GPIO0_B7 为低电平时,三极管 Q16 截止,LED9 不亮。

        一般情况下需要对 GPIO 的复用寄存器方向寄存器数据寄存器进行配置。

        有五个GPIO(GPIO0在PD_PMU中,GPIO1/GPIO2/GPIO3/GPIO4在PD_BUS中),并且它们每个都拥有相同的寄存器组。因此,这五个GPIO的寄存器组拥有五个不同的基地址

        本章以 GPIO0举例。

17.2.1 查询复用寄存器 

        查询 复用寄存器的基地址

        查询 GPIOB的复用寄存器的偏移地址

查询复用寄存器的偏移地址

复用寄存器功能设置位域

查看复用寄存器功能设置的位域

         我们要控制 led灯,因此要将引脚复用为 GPIO功能。

        复用寄存器的位域功能,高16位为低16位的写使能,低16位的[14:12]为 GPIO0B7的复用功能设置。

        复用寄存器地址 = 基地址+偏移地址=0xFDC2000C

         使用 io 命令查看此寄存器的地址:

 

        可知寄存器值为 00000001,[14:12]位为 000,如下图(图 18-6)所示,

        所以默认设置的为 gpio 功能。

17.2.2 查询方向寄存器

        方向寄存器DDR有32位,分了高 16位和 低 16位。高16位使能低16位。

查找GPIO0的基地址

查看address  mapping

        GPIO0 的基地址为 0xFDD60000。

        方向寄存器的地址 = GPIOx基地址+方向寄存器偏移地址

        =0xFDD60000+0x0008=0xFDD60008

使用IO命令查看方向寄存器地址的值:

        转换成二进制,发现DDR_L第15位默认为1,也就是默认GPIO方向为输出。

查找方向寄存器的偏移地址

        方向寄存器的偏移地址为 0x0008。

        接着查看 GPIO_SWPORT_DDR_L 寄存器的具体描述,

        [31:16]位属性是 WO,也就是只可写入。

        [31:16]位是写标志位,是低 16 位的写使能。如果低 16 位中某一位要设置输入输入输出,则对应高位写标志也应该设置为 1。

         [15:0] 是数据方向控制寄存器低位,如果要设置某个 GPIO 为输出,则对应位置 1,如果要设置某个 GPIO 为输入,则对应位置 0。

        对于GPIO0 B7 ,就是 DDR寄存器的第16位控制。

17.2.3 查询数据寄存器

        查询 GPIO0的基地址。

         查询数据寄存器偏移地址。

         所以数据寄存器的地址为GPIO0基地址+DR偏移地址=0xFDD60000。

         数据寄存器也是 高16位控制低16位的写使能,低16位对应 ABCD的分组引脚。

         IO指令查看数据寄存器的原值。发现为 0x0000c040。因此要写入数据寄存器对应引脚位给1来点亮灯,则需要写入 0x。

 

        通过在应用层传入 0/1 数据到内核,如果传入数据是 1,则设置 GPIO 的数据寄存器值为 0x8000c040,如果应用层传入 0,则设置 GPIO的数据寄存器值为 0x,这样就可以达到控制 led 的效果。

 

到此这篇linux内核驱动开发(linux内核驱动开发常用知识)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • goa电路(goa电路原理与维修)2025-03-31 20:18:05
  • linux内核驱动开发 vscode(linux内核驱动开发书籍)2025-03-31 20:18:05
  • argparse什么意思(argos是什么意思)2025-03-31 20:18:05
  • 时钟c语言程序设计(c语言数字时钟程序设计)2025-03-31 20:18:05
  • 嵌入式驱动开发需要学什么(嵌入式驱动程序开发)2025-03-31 20:18:05
  • 苹果开发者账号出售(苹果开发者账号卖给别人的风险)2025-03-31 20:18:05
  • 嵌入式驱动开发流程(简述嵌入式设备驱动的开发流程)2025-03-31 20:18:05
  • git用法(c语言中isdigit用法)2025-03-31 20:18:05
  • goa电路维修视频(电工电器维修视频)2025-03-31 20:18:05
  • 程序员入门要学什么软件(程序员入门学什么语言)2025-03-31 20:18:05
  • 全屏图片