12.嵌入式Linux读写寄存器原理与实现方式
参考传送门:
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;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)