树莓派高级开发——“IO口驱动代码的编写“包含总线地址、物理_虚拟地址、BCM2835芯片手册知识

嵌入式悦翔园 2022-12-08 11:40


针对初学者的基于Linux的物联网综合实战课程,详情请点击下面文章:

‍《一个适合初学者学习的基于linux的物联网综合项目》


微机总线地址

地址总线:

  • 百度百科解释: 地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。
  • 地址总线 = cpu能够访问内存的范围:用一个现象来解释地址总线:装了32位的win7系统,明明内存条8G,可是系统只识别了3.8G,装了64位,才能识别到8G。32位能表示/访问 4,294,967,296 bit
kbit——mbit——gbit差1024
bit4,294,967,296
kbit4,194,304 K
mbit4,096 M
gbit4 G
  • 地址总线 = CPU寻找外部的内存单元靠的是地址总线传输的数据。如果CPU有8根地址总线,每根线上传输0或1,那么传输的数据范围为00000000~ 11111111,每一个数值都对应内存中的一个内存单元,所以可以找到编号为00000000~ 11111111号的内存单元。如果传输的数据为00110011,那么就会找到00110011号内存单元,如果传输的数据为10110111,那么就会找到10110111号内存单元。编号不在[00000000,11111111]范围内的CPU就寻找不到,例如100000000号内存单元,CPU就寻找不到。寻址能力就是计算CPU能寻找多少个内存单元00000000~11111111号内存单元,一共有256个,一个内存单元的大小为1byte,这256个内存单元的大小为256byte
  • CPU通过地址总线来指定存储单元的。
  • **==地址总线决定了cpu所能访问的最大内存空间的大小==eg: 10根地址线访问的最大的内存为1024**(2的10次方)位二进制数据(1B)
  • 地址总线是地址线数量之和。若CPU的地址总线宽度是32位,那么CPU的寻址范围是4G(2的32次方位),所以最多支持4G内存.
  • 比如上面我们说的那个现象:装了32位的win7系统,明明内存条8G,可系统只是别了3.8G ,装了64位才能识别到8G。装了32位的操作系统CPU的访问范围是2^32bit,就是4194304kbit,就是4096Mbit,等于4G。树莓派也是32位 ,一个G的内存,但它只能访问949M剩下的挪作他用。

数据总线:

  • CPU通过地址总线寻址,然后通过数据总线与外部设备互换信息。
  • 是CPU与内存或其他器件之间的数据传送的通道。
  • 数据总线的宽度决定了CPU和外界的数据传送速度。
  • 每条传输线一次只能传输1位二进制数据。eg: 8根数据线一次可传送一个8位二进制数据(即一个字节)。
  • 数据总线是数据线数量之和,数据总线的位数决定CPU单次通信能交换的信息数量。

数据总线的宽度对CPU的性能的影响:

  • 首先,总线的速度(即:CPU的主频,CPU的性能指标之一)决定CPU和外设互换信息的速度。
  • 其次,数据总线的宽度也是表示CPU性能的参数之一(通常,我们说“64位的CPU”是指CPU的数据总线的宽度是64位)。如:64位数据总线的CPU一次就能取出64bit的数据,8位数据总线的CPU一次只能取出8bit的数据,在相同频率的情况下,8位数据总线的CPU就得连续取8次数据,数据量才能和64位数据总线一次取出的数据量相同,单就比较取数据的性能就相差8倍。况且,通常CPU中的寄存器的位数与数据总线的宽度一样,所以在数据处理方面,64位的CPU又比8位的CPU快很多。
  • CPU的地址总线位数和数据总线可以不同(典型代表就是51单片机),但是一般都相同。16位机有16根数据总线,20根地址总线,能访问1M(2的20次方),32位机有32根数据总线,32根地址总线,能访问4G(2的32次方),64位机确实有64根数据总线。

物理地址(PA)

  • 百度百科解释:网卡物理地址存储器中存储单元对应实际地址称物理地址,与逻辑地址相对应。网卡的物理地址通常是由网卡生产厂家写入网卡的EPROM(一种闪存芯片,通常可以通过程序擦写),它存储的是传输数据时真正赖以标识发出数据的电脑和接收数据的主机的地址

  • 这里说的 物理地址是内存中的内存单元实际地址,不是外部总线连接的其他电子元件的地址!**==物理地址属于比较好理解的,物理地址就是内存中每个内存单元的编号==**,这个编号是顺序排好的,物理地址的大小决定了内存中有多少个内存单元,物理地址的大小由地址总线的位宽决定!物理地址是硬件实际地址或绝对地址

虚拟地址(VA)

  • 虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的==逻辑地址(基于算法的地址[软件层面的地址:假地址])称为虚拟地址==,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。而linux没有各种保护模式,本来用的就是虚拟地址。
  • 虚拟地址是CPU保护模式下的一个概念,保护模式是80286系列和之后的x86兼容CPU操作模式,在CPU引导完操作系统内核后,操作系统内核会进入一种CPU保护模式,也叫虚拟内存管理,在这之后的程序在运行时都处于虚拟内存当中,虚拟内存里的所有地址都是不直接的,所以你有时候可以看到一个虚拟地址对应不同的物理地址,比如A进程里的call函数入口虚拟地址是0x001,而B也是,但是它俩对应的物理地址却是不同的,操作系统采用这种内存管理方法。
  • 是防止程序对物理地址写数据造成一些不可必要的问题,比如知道了A进程的物理地址,那么向这个地址写入数据就会造成A进程出现问题,在虚拟内存中运行程序永远不知道自己处于内存中那一段的物理地址上!现在操作系统运行在保护模式下即便知道其他进程的物理地址也不允许向其写入!但是可以通过操作系统留下的后门函数获取该进程上的虚拟地址空间所有控制权限并写入指定数据。
  • 虚拟内存管理采用一种拆东墙补西墙的形式,所以虚拟内存的内存会比物理内存要大许多。在进入虚拟模式之前CPU以及Bootloader(BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境),操作系统内核均运行在实模式下,直接对物理地址进行操作!虚拟内存中也有分页管理,这种管理方法是为了确保内存中不会出现内存碎片,当操作系统内核初始化完毕内存中的分页表后CPU的分页标志位会被设置,这个分页标志位是给MMU看的!
  • MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是==中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权,多用户多进程操作系统==。作用有两点,地址翻译和内存保护。==MMU将虚拟地址翻译为物理地址。==

有关各种地址介绍的博文:

物理地址、虚拟地址、总线地址物理地址和总线地址区别

页表(MMU的单元)

分页管理:

  • 内存分页其实就是我们所说的4G空间,内存的所有内存被操作系统内核以4G为每页划分开,当我们程序运行时会被加载到内存中的4G空间里,其实说是有4G其实并没有真正在的4G空间,4G空间中有一小部分被映射到了物理内存中,或者被映射到了硬盘的文件上(fopen),或者没有被映射,还有一部分在内存当中就会被划分栈,堆,其中有大片大片的内存是没有被映射的,同样物理内存也是被分页了用来与虚拟内存产生映射关系。将虚拟地址映射为物理地址有一个算法(页表)决定了将虚拟地址映射到物理地址的哪个位置,页表是通过MMU(分页内存管理单元)来管理的,就是设计完页表后通过MMU来执行将虚拟地址映射为物理地址。
  • 其实真正情况下只有3G用户空间,假如你的内存是4G的那么其中有1G是给操作系统内核使用的,所谓的4G空间只是操作系统基于虚拟内存这种拆东墙补西墙的形式给你一种感觉每个进程都有4G的可用空间一样!这里来说一下拆东墙补西墙,当我们程序被加载进4G空间时其实根本用不了所谓的4G空间,其中有大片内存被闲置,那么这个时候呢,其他程序被加载进来时发现内存不够了,就把其他程序里的4G空间里闲置部分拿出来给这个进程用,换之这个进程内存不够时就会把其他进程里闲置的空间拿过来给该进程使用。银行也是如此!
  • 当我们要对物理地址做操作时比如if语句要根据CPU的状态标志寄存器来做不同的跳转,那么这个时候就要对CPU额状态寄存器做操作了就必须知道它的物理地址,内存中有一个电子元件叫MMU负责从操作系统已经初始化好的内存映射表里查询与虚拟地址对应的物理地址并转换,比如mov 0x4h8这个是虚拟地址,当我们要对这个虚拟地址里写数据时那么MMU会先判断CPU的分页状态寄存器里的标志状态是否被设定,如果被设定那么MMU就会捕获这个虚拟地址物理并在操作系统内核初始化好的内存映射表里查询与之对应的物理地址,并将其转换成真正的实际物理地址,然后在对这个实际的物理地址给CPU,在由CPU去执行对应的命令,相反CPU往内存里读数据时比如A进程要读取内存中某个虚拟地址的数据,A进程里的指令给的是虚拟地址,MMU首先会检查CPU的分页状态寄存器标志位是否被设置,如果被设置MMU会捕获这个虚拟地址并将其转换成相应的物理地址然后提交给CPU,在由CPU到内存中去取数据!

更详细的地址问题看这里

BCM2835芯片手册

下面截取树莓派芯片手册的一张图:BCM2835是树莓派3B CPU的型号,是ARM-cotexA53架构cpu Bus是地址总线00000000~FFFFFFFFCPU寻址的范围(4G)DMA是高速拷贝单元,CPU可以发动DMA直接让DMA进行数据拷贝,直接内存访问单元。物理地址(PA)1G虚拟地址(VA)4G若程序大于物理地址1G,是不是就跑不了了,不是的,它有个MMU的单元,把物理地址映射成虚拟地址,我们操作的代码基本上都是在虚拟地址,它有一个映射页表(上面提及到过)

  • 通过芯片手册了解树莓派的GPIO:有54条通用I/O GPIO行,分为两行,备用功能通常是外围IO并且可以在每个银行中出现一个外围设备,以允许灵活地选择IO电压。

  • GPIO有41个寄存器,所有访问都是32位的。

  • Description是寄存器的功能描述。GPFSEL0(寄存器名)GPIO Function Select 0(功能选择:输入或输出);GPSET0 (寄存器名)GPIO Pin Output Set 0(将IO口置0);GPSET1(寄存器名)GPIO Pin Output Set 1(将IO口置1);GPCLR0(寄存器名)GPIO Pin Output Clear 0 (清0)下图的地址是:总线地址(并不是真正的物理地址)

  • FSELn表示GPIOn,下图给出第九个引脚的功能选择示例,对寄存器的29-27进行配置,进而设置相应的功能。根据图片下方的register 0表示0~9使用的是register 0这个寄存器。

  • 输出集寄存器用于设置GPIO管脚。SET{n}字段定义,分别对GPIO引脚进行设置,将“0”写入字段没有作用。如果GPIO管脚为在输入(默认情况下)中使用,那么SET{n}字段中的值将被忽略。然而,如果引脚随后被定义为输出,那么位将被设置根据上次的设置/清除操作。分离集和明确功能取消对读-修改-写操作的需要。GPSETn寄存器为了使IO口设置为1,set4位设置第四个引脚,也就是寄存器的第四位。

  • 输出清除寄存器用于清除GPIO管脚。CLR{n}字段定义要清除各自的GPIO引脚,向字段写入“0”没有作用。如果的在输入(默认),然后在CLR{n}字段的值是忽略了。然而,如果引脚随后被定义为输出,那么位将被定义为输出根据上次的设置/清除操作进行设置。分隔集与清函数消除了读-修改-写操作的需要。GPCLRn是清零功能寄存器。

配置树莓派的pin4引脚为输出引脚:

功能选择 输出/输入(GPIO Function Select Registers)32
14-12    001    =   GPIO Pin4 is an output

只需要将GPFSL0这个寄存器的14~12位设置为001就可以了。只需要将0x6(对应的2进制是110)左移12位·然后取反再与上GPFSL0就可以将13、14这两位配置为0,然后再将0x6(对应2进制110)左移12位,然后或上GPFSL0即可将12位置1。

  • 可使用copy_from_user()这个函数在驱动代码里面读取用户输入的指令,使用copy_to_user()这个函数让引脚反馈现在的状态,也就是让用户读取到。

若想找树莓派引脚点这里

树莓派IO操控驱动代码:

ioremap、iounmap:

一. 一般我们的外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器、数据寄存器三大类。外设的寄存器通常被连续编址,并且根据CPU的体系架构不同CPU对IO端口的编制方式有两种:

  • IO映射方式(IO-mapped):比较典型的有X86处理器为外设专门实现了一个单独的地址空间,称为“IO端口空间”或者“IO地址空间”,此时CPU可以通过专门的指令(比如X86的IN和OUT)来访问这个“IO端口空间”。
  • 内存映射方式(memory-mapped):RISC指令系统的CPU一般只实现一个物理地址空间,外设IO端口成为内存的一部分。此时CPU可以访问外设的IO端口,就像访问自己的内存一样方便,不必再设置专门的指令来访问。在驱动开发过程中一般使用内存映射方式。

二、 在驱动开发过程中,一般来说外设的IO内存资源的物理地址是已知的,由硬件的设计决定。但是CPU不会为这些已知的外设IO内存资源预先指定虚拟地址的值,所以驱动程序不可以直接就通过外设的物理地址访问到IO内存,而必须要将其映射到虚拟地址空间(通过页表),然后才能根据内核映射过后的虚拟地址来通过内存指令访问这些IO内存,并对其进行操作。

三、 在Linux内核的io.h头文件中声明了ioremap()函数,用来将IO内存资源映射到核心虚拟地址空间(3Gb~4GB)中,当然不用了可以将其取消映射iounmap()。这两个函数在mm/ioremap.c文件中:

开始映射:void* ioremap(unsigned long phys_addr , unsigned long size , unsigned long flags)
//用map映射一个设备意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或写入,实际上就是对设备的访问。
第一个参数是映射的起始地址
第二个参数是映射的长度
第二个参数怎么定啊?
====================
这个由你的硬件特性决定。
比如,你只是映射一个32位寄存器,那么长度为4就足够了。
(这里树莓派IO口功能设置寄存器、IO口设置寄存器都是32位寄存器,所以分配四个字节就够了)

比如:GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
   GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
      GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
这三行是设置寄存器的地址,volatile的作用是作为指令关键字
确保本条指令不会因编译器的优化而省略,且要求每次直接读值
ioremap函数将物理地址转换为虚拟地址,IO口寄存器映射成普通内存单元进行访问。
 
解除映射:void iounmap(void* addr)//取消ioremap所映射的IO地址
比如:
     iounmap(GPFSEL0);
        iounmap(GPSET0);
        iounmap(GPCLR0); //卸载驱动时释放地址映射

树莓派IO口四的驱动代码:

#include             //file_operations声明
#include     //module_init  module_exit声明
#include       //__init  __exit 宏定义声明
#include         //class  devise声明
#include    //copy_from_user 的头文件
#include      //设备号  dev_t 类型声明
#include           //ioremap iounmap的头文件


static struct class *pin4_class;
static struct device *pin4_class_dev;

static dev_t devno;                //设备号
static int major =231;             //主设备号
static int minor =0;               //次设备号
static char *module_name="pin4";   //模块名

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0   = NULL;
volatile unsigned int* GPCLR0   = NULL;
//这三行是设置寄存器的地址
//volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值

//led_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
        printk("pin4_open\n");  //内核的打印函数和printf类似
       
        //配置pin4引脚为输出引脚        
        *GPFSEL0 &=~(0x6 <<12); // 把bit13 、bit14置为0  
        //0x6是110  <<12左移12位 ~取反 &按位与
        *GPFSEL0 |=~(0x1 <<12); //把12置为1   |按位或
        
        return 0;

}
//read函数
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{
        printk("pin4_read\n");  //内核的打印函数和printf类似

        return 0;
}

//led_write函数
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
        int usercmd;
        printk("pin4_write\n");  //内核的打印函数和printf类似
        
        //获取上层write函数的值                
        copy_from_user(&usercmd,buf,count); //将应用层用户输入的指令读如usercmd里面
        //根据值来操作io口,高电平或者低电平
        if(usercmd == 1){
                printk("set 1\n");
                *GPSET0 |= 0x01 << 4;
        }
        else if(usercmd == 0){
                printk("set 0\n");
                *GPCLR0 |= 0x01 << 4;
        }
        else{
                printk("undo\n");
        }
        return 0;
}

static struct file_operations pin4_fops = {

        .owner = THIS_MODULE,
        .open  = pin4_open,
        .write = pin4_write,
        .read  = pin4_read,
};

//static限定这个结构体的作用,仅仅只在这个文件。
int __init pin4_drv_init(void)   //真实的驱动入口
{
        int ret;
        devno = MKDEV(major,minor);  //创建设备号
        ret   = register_chrdev(major, module_name,&pin4_fops);  //注册驱动  告诉内核,把这个驱动加入到内核驱动的链表中
        pin4_class=class_create(THIS_MODULE,"myfirstdemo");//让代码在dev下自动>生成设备
        pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件
        
        GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
        GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
        GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);

        printk("insmod driver pin4 success\n");
        return 0;
}

void __exit pin4_drv_exit(void)
{

        iounmap(GPFSEL0);
        iounmap(GPSET0);
        iounmap(GPCLR0); //卸载驱动时释放地址映射

        device_destroy(pin4_class,devno);
        class_destroy(pin4_class);
        unregister_chrdev(major, module_name);  //卸载驱动
}
module_init(pin4_drv_init);  //入口,内核加载驱动的时候,这个宏会被调用,去调用pin4_drv_init这个函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

1. 设置寄存器的地址

设置寄存器的地址,但是这样写是有问题的,我们上面讲到了在内核里代码和上层代码访问的是虚拟地址(VA),而现在设置的是物理地址,**==必须把物理地址转换成虚拟地址==**

//这三行是设置寄存器的地址
volatile unsigned int* GPFSEL0 = volatile (unsigned int *)0x3f200000;
volatile unsigned int* GPSET0  = volatile (unsigned int *)0x3f20001C;
volatile unsigned int* GPCLR0  = volatile (unsigned int *)0x3f200028;
//volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值

我们先把地址初始

volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0   = NULL;
volatile unsigned int* GPCLR0   = NULL;

在初始化int __init pin4_drv_init(void) //真实的驱动入口里赋值。

//整数11 //0xb 11 00010001 即便是16进制也是整数,左边是volatile unsigned int* GPFSEL0 右边也强制转换成(volatile unsigned int*)

volatile的作用是作为指令关键字,确保本条 ==指令不会因编译器的优化而省略==,==且要求每次直接读值==因为它是地址我希望它是无符号的unsigned

我们在编写驱动程序的时候,IO空间的起始地址是0x3f000000,加上GPIO的偏移量0x2000000,所以GPIO的物理地址应该是从0x3f200000开始的然后在这个基础上进行Linux系统的MMU内存虚拟化管理,映射到虚拟地址上。用到了一个函数ioremap

//物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
 GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
 GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
 GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);   //4是4个字节

2. 配置pin4引脚为输出引脚

配置pin4引脚为输出引脚      bit 12-14  配置成001

31 30 ······14 13 12 11 10 9 8 7 6 5 4 3 2 1 
0  0  ······0  0  1  0  0  0 0 0 0 0 0 0 0 0 
 //配置pin4引脚为输出引脚      bit 12-14  配置成001  
  *GPFSEL0 &=~(0x6 <<12); // 把bit13 、bit14置为0  
 //0x6是110  <<12左移12位 ~取反 &按位与
  *GPFSEL0 |=~(0x1 <<12); //把12置为1   |按位或

忘记按位与 按位或 点这里

3. 获取上层write函数的值,根据值来操作io口,高电平或者低电平

copy_form_user(char *buf , user_buf , count)获取上层write函数的值

int usercmd;
copy_from_user(&usercmd,buf,count); //将应用层用户输入的指令读如usercmd里面
       
 //根据值来操作io口,高电平或者低电平
 printk("get value\n");
        if(usercmd == 1){
                printk("set 1\n");        //置1
                *GPSET0 |= 0x01 << 4;      //用 | 或操作  目的是不影响其他位
                //写1 是让寄存器    开启置1  让bit4为高电平
        }
        else if(usercmd == 0){           
                printk("set 0\n");        //清0
                *GPCLR0 |= 0x01 << 4;      //用 | 或操作  目的是不影响其他位
                //写1 是让清0寄存器 开启置0 让bit4为低电平
        }
        else{
                printk("undo\n");  //提示不支持该指令
        }

4. 解除映射

解除映射:void iounmap(void* addr);//取消ioremap所映射的IO地址

void __exit pin4_drv_exit(void)
{
        iounmap(GPFSEL0);   //解除映射 GPFSEL0
        iounmap(GPSET0);    //解除映射 GPSET0
        iounmap(GPCLR0);   //解除映射 GPCLR0

        device_destroy(pin4_class,devno);//先销毁设备
        class_destroy(pin4_class);//再销毁类
        unregister_chrdev(major, module_name);  //卸载驱动

}

上层测试代码:

#include
#include
#include
#include 
#include
#include

int main()
{
   int fd;
   int cmd;
   int data;

   fd = open("/dev/pin4",O_RDWR);
   if(fd<0){
           printf("open failed\n");
   }else{
    printf("open success\n");
   }
   
   printf("input commnd:1/0 \n 1:set pin4 high \n 0 :set pin4 low\n");
   scanf("%d",&cmd);

   printf("cmd = %d\n",cmd);
   fd = write(fd, &cmd,4); //cmd类型是int  所以 写4
}

驱动卸载

在装完驱动后可以使用指令:sudo rmmod +驱动名(不需要写ko)将驱动卸载。

IO口驱动代码编译

  1. 首先在系统目录/SYSTEM/linux-rpi-4.14.y下使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules驱动模块进行编译生成.ko文件.
  2. 然后将编译后的驱动发送到树莓派:scp ./drivers/char/pin4driver.ko pi@192.168.0.104:/home/pi,然后再将上层代码进行编译arm-linux-gnueabihf-gcc pin4test.c -o realtest,然后再将测试代码传到树莓派:scp realtest pi@192.168.43.136:/home/pi/
  3. 然后在树莓派上面使用指令:insmod pin4drive.ko进行加载驱动(然后lsmod即可查看到该驱动),
  4. 然后使用指令:sudo chmod 666 /dev/pin4给予pin4这个设备可访问权限,还可以在虚拟机上面使用mk5sum查看驱动文件的值,并在树莓派上面使用该指令进行查看该驱动文件的值,看是否一致。
  5. dmesg查看内核打印的信息,如下图所示:
  6. 然后运行测试代码,在新建一个窗口,使用指令gpio readall可以看到BCM下面的4号引脚模式是输出模式,电平是低电平或高电平(根据输入的上层代码而定,输入0就是低电平,输入1就是高电平),这里我输入的是0,如下图所示:

有关驱动代码里面GPIO口地址的问题:

有关驱动代码里面GPIO口地址的问题:

7Ennnnn意思是7E00000到7EFFFFFF,F2000000是3F000000映射的虚拟地址,然后7E00000和F200000对应,芯片手册里面使用的是和虚拟地址F200000有着对应关系的地址——7E00000,芯片手册上面地址偏移多少物理地址就偏移多少
根据上方图片描述,外设的物理地址范围是m 0x3F000000 to 0x3FFFFFFF,所以你看到的7E200000对应的实际物理地址应该是0x3F000000 + (7E200000-7E000000)
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); GPSET0   =(volatile unsigned int *)ioremap(0x3f20001C,4); GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
0x3f200000,0x3f20001C,0x3f200028是物理地址,树莓派的外设空间的起始地址是0x3f000000,根据芯片手册可知对应寄存器的偏移量为,比如GPFSEL0寄存器的实际地址是0x3f200000=0x3F000000  + (7E200000-7E000000)
然后通过数据手册可以看到,树莓派相关寄存器的总线地址(和映射的虚拟地址有某种对应关系的地址)进而可得知偏移量,如下图所示:

推荐阅读



01

加入嵌入式交流群


02

嵌入式资源获取


03

STM32中断优先级详解


04

STM32下载程序新思路--使用串口下载程序


嵌入式悦翔园 专注于嵌入式技术,包括但不限于STM32、Arduino、51单片机、物联网、Linux等编程学习笔记,同时包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论
  • 职场是人生的重要战场,既是谋生之地,也是实现个人价值的平台。然而,有些思维方式却会悄无声息地拖住你的后腿,让你原地踏步甚至退步。今天,我们就来聊聊职场中最忌讳的五种思维方式,看看自己有没有中招。1. 固步自封的思维在职场中,最可怕的事情莫过于自满于现状,拒绝学习和改变。世界在不断变化,行业的趋势、技术的革新都在要求我们与时俱进。如果你总觉得自己的方法最优,或者害怕尝试新事物,那就很容易被淘汰。与其等待机会找上门,不如主动出击,保持学习和探索的心态。加入优思学院,可以帮助你快速提升自己,与行业前沿
    优思学院 2025-01-09 15:48 98浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 96浏览
  • 在当前人工智能(AI)与物联网(IoT)的快速发展趋势下,各行各业的数字转型与自动化进程正以惊人的速度持续进行。如今企业在设计与营运技术系统时所面临的挑战不仅是技术本身,更包含硬件设施、第三方软件及配件等复杂的外部因素。然而这些系统往往讲究更精密的设计与高稳定性,哪怕是任何一个小小的问题,都可能对整体业务运作造成严重影响。 POS应用环境与客户需求以本次分享的客户个案为例,该客户是一家全球领先的信息技术服务与数字解决方案提供商,遭遇到一个由他们所开发的POS机(Point of Sal
    百佳泰测试实验室 2025-01-09 17:35 101浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 118浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 113浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 84浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 114浏览
  • HDMI 2.2 规格将至,开启视听新境界2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新HDMI规范为规模庞大的 HDMI 生态系统带来更多选择,为创建、分发和体验理想的终端用户效果提供更先进的解决方案。新技术为电视、电影和游戏工作室等内容制作商在当前和未来提供更高质量的选择,同时实现多种分发平台。96Gbps的更高带宽和新一代 HDMI 固定比率速率传输(Fixed Rate Link)技术为各种设备应用提供更优质的音频和视频。终端用户显示器能以最
    百佳泰测试实验室 2025-01-09 17:33 107浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 79浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 97浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 88浏览
  • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
    知白 2025-01-07 15:02 169浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 93浏览
  • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
    hai.qin_651820742 2025-01-07 14:52 131浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 95浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦