ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器

时间:2021-07-15 19:54:25
GPIO 的寄存器通过 ioremap函数转换之后,可以通过直接控制虚拟地址来控制物理地址(寄存器的实际地址),这样就实现 GPIO的读和写以及其它任意功能。  详情了解:http://topeetboard.com  更多了解:https://arm-board.taobao.com 需要的基础知识 虚拟地址和物理地址 内存管理单元概念 linux 驱动模块的加载   主要内容 GPIO 的寄存器文档详细介绍和说明 函数 ioremap的用法 使用 ioremap实现对 GPIO的控制   硬件 以 LED2(靠近蜂鸣器的 LED)为例,介绍原理图以及Datasheet 中对应的寄存器。 原理图 打开底板原理图 pdf文档,如下图所示,选取 LED2,网络标号是 KP_COL0     <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器 通过查找网络标号 KP_COL0,发现 KP_COL0接到连接器上,如下图所示。 <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器 打开核心板 pdf文档,查找网络标号 KP_COL0,找到exnoys4412 芯片对应的GPIO 如下图所示。   <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   如上图所示,这个管脚对应的 GPIO是 GPL2_0 打开 4412 的 Datasheet,找到 GPIO对应的章节,在第六章。   <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器 寄存器 在第六章中有对应的小节“6.2 Register Description ”,在“6.2.1 Registers Summary  中可以找到 GPL2对应的寄存器,如下图所示。 <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   如上图所示,通过阅读寄存器的描述文字,可以清楚需要控制的寄存器为 GPL2CON(配 置寄存器),GPL2DAT(数据寄存器,对应输出模式,寄存器写输出低电平,写 1输出高 电平),GPL2PUD(上拉和下拉配置,输出需要上拉),其它几个寄存器不需要使用,默认 即可。 寄存器地址一般由基地址和偏移地址组成,通过上图中的“Offset”可以发现三个寄存器 GPL2CONGPL2DATGPL2PUD的偏移地址分别是 0x01000x01040x0108 exnoys4412 GPIO 寄存器最大是32 位,同时也可以是16 位或者是 位,这一点在 对寄存器操作的时候需要用到。通过上图中的“Reset Value”,可以发现三个寄存器 GPL2CONGPL2DATGPL2PUD的复位之后的值是 0x0000_00000x000x5555,那 么这几个寄存器的值分别是 32 位,位,16 位。   寄存器GPL2CON 在 Datasheet 中搜关键字“GPL2CON”,如下图所示,可以发现这个寄存器的基地址是 0x1100_0000 。那么GPL2CON 寄存器的物理地址 phy = 0x11000000 + 0x0100   <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   LED2 对应的 GPIO是 GPL2中的 Bit0,那么找到 GPL2CON[0](就在上图的表格中,下 翻即可找到),如下图所示。 <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   如上图所示,GPL2CON 寄存器的[3:0]这低四位写 0x0 则是输入模式,0x1 则是输出模 式。LED2 显然需要配置为输出模式,那么 GPL2CON 寄存器的bit[3:0]需要写 0x1   寄存器GPL2DAT 在 Datasheet 中搜关键字“GPL2DAT”,如下图所示,可以发现这个寄存器的基地址是 0x1100_0000 。那么GPL2DAT 寄存器的物理地址 phy = 0x11000000 + 0x0104    <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   通过上图中 Description 部分,可以看到这段英文“When configuring as output port, the pin state should be same as the corresponding bit.”翻译成中文就是“IO 被配置为 输出模式,寄存器中写 0则输出低电平,写 则输出高电平”。那么GPL2DAT 寄存器的 bit 位写灯则灭,写 则亮。   寄存器GPL2PUD 在 Datasheet 中搜关键字“GPL2PUD”,如下图所示,可以发现这个寄存器的基地址是 0x1100_0000 。那么GPL2PUD寄存器的物理地址 phy = 0x11000000 + 0x0108   <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   如上图所示,先介绍“Bit”部分,“[2n + 1:2n] n = 0 to7”表示 可以取 0-7,分别 对应 8个 pin 脚,n=0,2x0:2x0+1,就是bit[0n=1,2x1:2x1+1,就是 bit[2依次类 推。GPL2[0]n=0,需要对 bit[0,1]进行操作。 这里有一点需要说明,如下图所示,红色框中“Disables”应该是Enables,这是三星 Datasheet 中的一个小错误。   <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器 综上所述,需要对 GPL2PUD寄存器的bit[0 1]写 0x3   代码中所需资源小结 代码中需要寄存器的物理地址、寄存器长度、对应位的值,如下表所示。 <ignore_js_op>ARM开发板|嵌入式开发板丨迅为4412开发板丨ioremap控制GPIO寄存器   软件部分 本节主要内容为软件知识,在介绍代码之前,会先简单介绍一下 ioremap函数,语言 中的 volatile 关键字。 ioremap函数 物理地址和虚拟地址之间需要使用 ioremap函数进行映射,将物理地址映射成虚拟地址 之后对虚拟地址操作就相当于对物理地址进行操作,也就是直接对寄存器进行操作。 映射函数 void *ioremap(unsigned long phys_addr, unsigned long size) phys_addr:要映射的起始的IO 地址 size:要映射的空间的大小。 取消映射函数 void iounmap(void * addr) addr:映射后得到的虚拟地址。   编译的小知识-volatile关键字 在 C语言中,编译器在进行编译优化时,如果发现多次获取同一变量的代码之间没有对 这个变量进行操作,例如: int j = 10 int call_first = j ……(大段代码,但是没有对 进行操作) int call_sec = j   编译器会自动将 call_first 的值给 call_sec,而不会再回头去取 j的值。如果涉及寄存器, 那就很容易出错。 涉及到寄存器的地址,语言中提供了volatile 关键字。它能影响编译器编译的结果,它会 通知编译器,volatile 定义的变量是随时可能发生变化的,与 volatile 变量有关的运算,不要 进行编译优化,以免出错。如果上面的变量使用 volatile 定义,那么每次使用它的时候必须从 的地址中读取,因而编译器生成的可执行码会重新从的地址读取数据放在变量 call_sec 中。 代码 本节的代码需要有文档前面的知识,以及模块注册和卸载的知识。 驱动源码 代码文件 dri_ioremap.c。基础知识前面已经介绍了很多,代码的注释也很多,就不额外 的代码分析了。 //sch:KP_COL0GPL2_0 /*GPL2 datasheet:GPL2CON,GPL2DAT,GPL2PUD Add = Base Address+Offset GPL2CON = 0x1100_0000 + 0x0100 = 0x11000000 + 0x0100   s3c_gpio_cfgpin GPL2DAT = 0x1100_0000 + 0x0104 = 0x11000000 + 0x0104 gpio_set_value GPL2PUD = 0x1100_0000 + 0x0108 = 0x11000000 + 0x0108 */ /*GPL2_0 datasheet:GPL2CON[0] GPL2DAT[0] GPL2PUD[0] GPL2CON[0] [3:0] output 0x0 GPL2DAT 0 or 1 GPL2PUD[0] 0x3 */ /* 寄存器不一定都是 32 位的,也有 16 和 位的 写寄存器的时候需要注意 */ #include <linux/module.h> #include <linux/init.h> #include <asm/io.h> volatile unsigned long virt_addr, phys_addr;//用于存放虚拟地址和物理地址 volatile unsigned long *GPL2CON, *GPL2DAT, *GPL2PUD;//用于存放三个寄存器的地址 void gpl2_device_init(void) { //0x11000100 + 0x10 包括全所有的 IO 引脚寄存器地址 phys_addr = 0x11000100; //0x11000100=GPL2CON //在虚拟地址空间中申请一块长度为 0x10 的连续空间 //这样,物理地址 phys_addr 到 phys_addr+0x10 对应虚拟地址 virt_addr 到 virt_addr+0x10 virt_addr =(unsigned long)ioremap(phys_addr, 0x10); //指定需要操作的寄存器的地址 GPL2CON = (unsigned long *)(virt_addr + 0x00); GPL2DAT = (unsigned long *)(virt_addr + 0x04); GPL2PUD = (unsigned long *)(virt_addr + 0x08); } //led 配置函数,配置开发板的 GPIO 的寄存器 void gpl2_configure(void)   { //GPL2CON 寄存器,bit0 设置为 1,输出模式 *GPL2CON |= 0x00000001; //GPL2PUD 寄存器,bit[01] 设置为 0x3,上拉模式 *GPL2PUD |= 0x0003; } void gpl2_on(void) //点亮 led { *GPL2DAT |= 0x01; } void gpl2_off(void) //灭掉 led { *GPL2DAT &= 0xfe; } static int __init led_gpl2_init(void) //模块初始化函数 { printk("init \n"); gpl2_device_init(); //实现 IO 内存的映射 gpl2_configure(); //配置 GPL2 为输出模式 gpl2_on(); printk("led gpl2 open\n"); return 0; } static void __exit led_gpl2_exit(void) //模块卸载函数 { printk("exit \n"); gpl2_off(); iounmap((void *)virt_addr); //撤销映射关系 printk("led gpl2 close\n"); } module_init(led_gpl2_init); module_exit(led_gpl2_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("dz20160510"); MODULE_VERSION("2016-05-10");   编译脚本 编译脚本 Makefile 代码如下。 #!/bin/bash #通知编译器我们要编译模块的哪些源码 #这里是编译 read_gpio.c 这个文件编译成中间文件 read_gpio.o obj-m += dri_ioremap.o #源码目录变量,这里用户需要根据实际情况选择路径 #作者是将 Linux 的源码拷贝到目录/home/topeet/android4.0 下并解压的 KDIR := /home/topeet/android4.0/iTop4412_Kernel_3.0 #当前目录变量 PWD ?= $(shell pwd) #make 命名默认寻找第一个目标 #make -C 就是指调用执行的路径 #$(KDIR)Linux 源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0 #$(PWD)当前目录变量 #modules 要执行的操作 all: make -C $(KDIR) M=$(PWD) modules #make clean 执行的操作是删除后缀为 的文件 clean: rm -rf *.o   编译运行测试 编译之后,运行 iTOP-4412 开发板最小系统,加载驱动模块,靠近 led 的灯会亮,卸载 模块则会灭。