参考传送门:

IO 端口和IO 内存(原理篇)
https://zhuanlan.zhihu.com/p/579146985

uboot常用
https://blog.csdn.net/xiaoxiaozhu2010/article/details/20138993

Linux下嵌入式读写寄存器
https://blog.csdn.net/Frank_sample/article/details/118385535

一、前言

本文是前面GPIO的复用与使用的引申,总写stm32的开发人员大概会有种想要配置寄存器来实现GPIO功能的冲动。在嵌入式上也是能控制寄存器来实现GPIO的,只是由于需要查芯片数据手册,相对来说反而需要更高知识基础。

二、原理

IO内存:IO内存又称为Memory-Mapped I/O(MMIO),该IO空间处在CPU空间范围内,IO内存和普通的内存没什么区别,两者都是通过CPU的地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据。要想操纵该IO就得首先将该IO映射到CPU的地址中,然后就可以访问该IO,如同访问内存。大多数嵌入式设备都属于此。

有uboot调试经验的,使用过md、mw命令的大致直接就能理解。没有用过的可以先看案例

三、通过io指令理解寄存器控制

1.io命令的理解

GPIO 调试有一个很好用的工具,那就是 IO 指令,Firefly-RK3399系统默认已经内置了 IO 指令,使用 IO 指令可以实时读取或写入每个 IO 口的状态,这里简单介绍 IO 指令的使用。首先查看 IO 指令的帮助:

#io --help
Unknown option: ?
Raw memory i/o utility - $Revision: 1.5 $

io -v -1|2|4 -r|w [-l <len>] [-f <file>] <addr> [<value>]

   -v         Verbose, asks for confirmation
   -1|2|4     Sets memory access size in bytes (default byte)
   -l <len>   Length in bytes of area to access (defaults to
              one access, or whole file length)
   -r|w       Read from or Write to memory (default read)
   -f <file>  File to write on memory read, or
              to read on memory write
   <addr>     The memory address to access
   <val>      The value to write (implies -w)

Examples:
   io 0x1000                  Reads one byte from 0x1000
   io 0x1000 0x12             Writes 0x12 to location 0x1000
   io -2 -l 8 0x1000          Reads 8 words from 0x1000
   io -r -f dmp -l 100 200    Reads 100 bytes from addr 200 to file
   io -w -f img 0x10000       Writes the whole of file to memory

Note access size (-1|2|4) does not apply to file based accesses.
2.使用示例

本示例使用io命令写GPIO数据寄存器,实现点亮或者关掉灯

在这里插入图片描述

如图绿灯在DIY_LED是高电平时点亮,其中WORK_LED是gpio0_B5

控制gpio大致分为三步,设置gpio复用寄存器,设置gpio方向寄存器,设置gpio的数据寄存器

本文将gpio的复用在设备树里,设置gpio的方向在userspace,只用io命令控制数据寄存器

  • 从RK3399的datasheet查到GPIO0对应寄存器基地址为:0xff720000
    在这里插入图片描述

  • 从主控的datasheet查到gpio数据寄存器的偏移量为:0x0000
    在这里插入图片描述

  • GPIO0_B5的数据寄存器地址为:基址(Operational Base) + 偏移量(offset)=0xff720000+0x00000=0xff720000

  • 先设置gpio0_b5为输出

root@firefly:/sys/class/gpio# echo 13 > export
root@firefly:/sys/class/gpio# cd gpio13
root@firefly:/sys/class/gpio/gpio13# echo out > direction
  • io命令设置gpio0_b5的电平
#设置低,灯灭
# io -4 -w 0xff720000 0x00000614

#设置高,灯亮
# io -4 -w 0xff720000 0x00002614

0x00002614转换为二进制为0000 0000 0000 0000 0010 0110 0001 1000
0x00000614转换为二进制为0000 0000 0000 0000 0000 0110 0001 1000
可以看出变化的为第14位,从a0数起变化的为b5

四、结合uboot理解寄存器控制

1.对于命令的理解
md       显示内存值
格式:  md [.b, .w, .l] address [# of objects]

mw     用指定的数据填充内存
格式:  mw [.b, .w, .l] address value [count]
这里的参数全是十六进制,每个地址存放8bit,即两个十六进制数 
这里的value为0x11 即显示17个地址
超过16个,地址进位1
=> md.b 2300000 11
02300000: 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 04    ................
02300010: 00  

=> md.b 230000b 1
0230000b: 05   

md.l 、mw.l 即操作32位为通用的常规控制方法,但在调试寄存器时应注意cpu是大端序(big-endian),还是小端序(little-endian)

以LS1046为例

md.b和md.l其关系如下图:

总结起来为 寄存器头8位放在末8位显示,8-16位放到16-24位显示,末8位放到头8位显示

在这里插入图片描述

2.操作gpio的示例

硬件为GPIO1_31接发光二极管,高电平点亮

在这里插入图片描述

LS1046数据手册里寄存器的地址为
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

使用md.b、md.l的不同

显示2300000的原始值
=> md.b 2300000 20
02300000: 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 05    ................
02300010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

在这里插入图片描述

红框为GPDIR、蓝框为GPDAT

=> mw.b 2300003 01    //设置GPIO direction register (GPDIR)31位为1输出
=> md.b 2300003 1     //能看到设置的值
02300003: 01    

再次显示2300000的值
=> md.b 2300000 20
02300000: 00 00 00 01 00 00 00 00 00 00 00 04 00 00 00 05    ................
02300010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

在这里插入图片描述

使用md.l显示
=>md.l 2300000 8
02300000: 01000000 00000000 04000000 05000000    ................
02300010: 00000000 00000000 00000000 00000000    ................

在这里插入图片描述

使用mw.b、mw.l点灯

=> mw.b 230000b 01  //设置GPIO data register (GPDAT)31位输出高  灯亮
=> mw.b 230000b 00  //设置GPIO data register (GPDAT)31位输出低  灯灭

由于原本是28位已经置1了,这里保留
=> mw.l 2300008 05000000	//设置GPIO data register (GPDAT)31位输出高  灯亮
=> mw.l 2300008 04000000  //设置GPIO data register (GPDAT)31位输出低  灯灭
3.复用IIC为GPIO并输出高低电平

复用I2C3_SCL、I2C3_SDA为GPIO

在这里插入图片描述

复用寄存器说明如下
在这里插入图片描述

在这里插入图片描述

uboot里的命令为

这里设置157040c 的16-23位为00010001(0x11)
=> mw.l 157040c 00110000
=> md.l 157040c
0157040c: 00110000 00000000 00000000 9e000000    ................

查gpio4的寄存器初始值
=> md.l 2330000 4
02330000: 00000000 00000000 00003030 00003c30    ........00..0<..

设置10、11位为输出
=> mw.l 2330000 00003000
设置10、11位为低电平
=> mw.l 2330008 00000030

设置10、11位为高电平
=> mw.l 2330008 00003030

五、在usersapce调试寄存器

应用层主要是通过操作/dev/mem设备文件,以及mmap函数,将寄存器的地址映射到用户空间,直接在应用层对寄存器进行操作。

/dev/mem是物理内存的全映像,可以用来访问物理内存,用mmap来访问物理内存以及外设的IO资源,是实现用户空间驱动的一种方法。

//mmap函数说明
用法:#include <sys/mman.h>
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void *start, size_t length);
参数:
start:映射区的开始地址;length:映射区的长度;
prot:期望的内存保护标志,不能与文件的打开模式冲突,PROT_EXEC //页内容可以被执行,PROT_READ //页内容可以被读取,PROT_WRITE //页可以被写入,PROT_NONE //页不可访问;
flags:指定映射对象的类型,映射选项和映射页是否可以共享;
fd:有效的文件描述词;
offset:被映射对象内容的起点,内部寄存器物理地址。
函数释义:将物理地址映射到用户空间的虚拟地址上,在用户空间完成对设备寄存器的操作。

下面的程序实现的是:读取寄存器GPIO1_GPDIR的值,并置位设置GPIO1_31为输出,读取寄存器GPIO1_GPDAT的值,并置位设置GPIO1_31为高电平,就是前面的点灯的现象

本来想改进下传参的,没成功,唉!

# cat mmap_gpio.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define GPIO1_GPDIR   (0x2300000)
#define GPIO1_GPDAT   (0x2300008)
#define MAP_SIZE        0xFF



static int dev_fd;
int main(int argc, char **argv)
{

        dev_fd = open("/dev/mem", O_RDWR | O_NDELAY);
        if (dev_fd < 0)
        {
                printf("open(/dev/mem) failed.");
                return 0;
        }
		
        unsigned char *map_base1=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, GPIO1_GPDIR );
        printf("GPIO1_GPDIR is %x \n", *(volatile unsigned int *)(map_base1));
        *(volatile unsigned int *)(map_base1 ) |=  0x01000000;
        printf("GPIO1_GPDIR is %x \n", *(volatile unsigned int *)(map_base1));

        unsigned char *map_base2=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, GPIO1_GPDIR );
        printf("GPIO1_GPDAT is %x \n", *(volatile unsigned int *)(map_base2+0x08));
        *(volatile unsigned int *)(map_base2+0x08 ) =  0x04000000;
        printf("GPIO1_GPDAT is %x \n", *(volatile unsigned int *)(map_base2+0x08));

        if(dev_fd)
                close(dev_fd);

        munmap(map_base1,MAP_SIZE);
        munmap(map_base2,MAP_SIZE);

        return 0;
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐