寄存器读写

    linux下嵌入式读写寄存器一般可以有以下几种方式实现:

  • Boot终端下使用mw\mb命令读写物理地址。
  • 在文件系统中使用sysfs接口进行操作。
  • 在应用层读写/dev/mem节点。
  • 在内核中使用ioremap映射物理地址为虚拟地址进行操作。
  • 使用内核提供的gpio接口进行读写操作。

    一般进行寄存器的读写操作需要先知道寄存器地址,在嵌入式中寄存器的查找方式如下:

以LED灯为例,先从硬件主板上找到对应的LED灯元器件,再查找在其原理图上对应的位号,依次寻找到其对应SOC的I/O口。
然后查找芯片的数据手册,找到对应I/O的寄存器地址。

BOOT下读写寄存器

    boot下对寄存器的操作主要依赖mw与mb命令,命令的详细解说。使用示例:

//mw 物理地址 配置值
	mw 0xe0200c00 0x1111:(对应控制寄存器设置输出)
	mw 0xe0200c04 0xf:(对应数据寄存器设为1,则LED点亮) 
sysfs文件系统读写寄存器

    找到gpio在文件系统下的属性节点,操作gpio的属性,进行配置。

// cd /sys/class/gpio
	找到gpio编号:cat gpiochip128/label
    创建gpio属性文件:echo 129 > export
    设备引脚方向:echo out > direction
    修改引脚值:echo 1 > value
    删除gpio属性文件:echo 129 > unexport
应用层读写寄存器

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

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <unistd.h> 
    #include <sys/mman.h>
    #define AUDIO_REG_BASE   (0xB800E000)
    #define MAP_SIZE        0xFF
     
    static int dev_fd;
    int main(int argc, char **argv)
    {
     	/* 打开linux系统dev目录下的/dev/mem文件 */
    	dev_fd = open("/dev/mem", O_RDWR | O_NDELAY);     
    	if (dev_fd < 0)  
    	{
    		printf("open(/dev/mem) failed.");    
    		return 0;
    	}  
     	
     	/* 将寄存器地址映射到用户空间 */
    	unsigned char *map_base=(unsigned char * )mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, AUDIO_REG_BASE );
    	
    	printf("%x \n", *(volatile unsigned int *)(map_base+0x38)); //打印该寄存器地址的value
    #if 1 // LINE IN
    	printf("%x \n", *(volatile unsigned int *)(map_base+0x30));
    	*(volatile unsigned int *)(map_base + 0x30) = 0x208121bc; //修改该寄存器地址的value
    	usleep(1000000);
    	*(volatile unsigned int *)(map_base + 0x30) &= ~(0x1<<16); //修改该寄存器地址的value
    	usleep(1000000);
    	printf("%x \n", *(volatile unsigned int *)(map_base+0x30));
    #endif
    	if(dev_fd)
    		close(dev_fd);
     
    	munmap(map_base,MAP_SIZE);//解除映射关系
     
    	return 0;
    }

    /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:被映射对象内容的起点,内部寄存器物理地址。
函数释义:将物理地址映射到用户空间的虚拟地址上,在用户空间完成对设备寄存器的操作。

内核读写寄存器

    内核空间中读写寄存器的方式有两种,一种是使用iroemap函数映射物理地址到内核虚拟空间中,再进行寄存器的操作;另一种是使用内核中提供的gpio的操作接口直接操作,这种方式需要在设备树已经配置好GPIO的硬件信息。

  • 内核中物理地址映射,内部寄存器的映射方法如下:(注意外部寄存器的操作需要先向内核申请并分配一段内存才能进行映射)
#include <asm/io.h>

#define SYS_WRITEL(Addr, Value) ((*(volatile unsigned int *)(Addr)) = (Value))
#define SYS_READ(Addr)          (*((volatile int *)(Addr)))

static void  *g_reg_iocfg_base = NULL;

static inline void reg_write32(unsigned long value, unsigned long mask, unsigned long addr)
{
    unsigned long t;

    t = SYS_READ((const volatile void *)(uintptr_t)addr);
    t &= ~mask;
    t |= value & mask;
    SYS_WRITEL((volatile void *)(uintptr_t)addr, t);
}

static void i2c0_pin_mux(void)
{
    /* SDA SCL */
    SYS_WRITEL(g_reg_iocfg_base + 0x0014, 0x1501);//操作寄存器
    SYS_WRITEL(g_reg_iocfg_base + 0x0018, 0x1501);
}

static int sysconfig_init(void)
{
    g_reg_iocfg_base = (void*)ioremap(0x170F0000, 0x10000);//将物理地址映射成虚拟地址
    if (g_reg_iocfg_base == NULL) {
        return 1;
    }

    return 0;
}

static void sysconfig_exit(void)
{
    if (g_reg_iocfg_base != NULL) {
        iounmap(g_reg_iocfg_base);
        g_reg_iocfg_base = NULL;
    }
}

用法:#include <io.h>
void *ioremap(unsigned long phys_addr, unsigned long size)
参数:phys_addr:是要映射的物理地址;size:是要映射的长度,单位是字节。
函数释义:将物理地址转换为内核虚拟地址,通常内核态设备驱动程序会使用这个虚拟地址访问寄存器。

  • linux驱动中gpio操作接口,包括基本的申请IO口,设置输入输出,设置高低电平,以及读取端口电压等操作函数,基本的使用示例如下:详细解析
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
...

struct gpio_drvdata {
    /* gpio号 */
    int gpio_num;
    ...
};

static int __init gpio_init(void)
{
   struct gpio_drvdata *ddata;
   int ret;
   
   ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
   if (!ddata)
       return -ENOMEM;
    ...
    
    /* gpio初始化 */
    if (gpio_is_valid(ddata->gpio_num)) {
        /* 申请gpio资源 */
        ret = gpio_request(ddata->gpio_num, "test-gpio");
        if (ret) {
            printk("failed to request gpio\n");
            return ret;
        }
        
        /* 设置gpio的方向(输出) */
        ret = gpio_direction_output(ddata->gpio_num, 0);
        if (ret) {
            printk("failed to set output direction\n");
            return ret;
        }
        
        /* 在sysfs中导出gpio(方向能改变) */
        ret = gpio_export(ddata->gpio_num, true);
        if (ret) {
            printk("failed to export gpio in sysfs\n");
            return ret;
        }
        
        /* 设置gpio电平值(高电平) */
        gpio_set_value(ddata->gpio_num, 1);
    }
    ...
    
    return 0;
}

static void __exit gpio_exit(void)
{
    ...
    /* 释放已经申请的gpio资源 */
    if (gpio_is_valid(ddata->gpio_num))
        gpio_free(ddata->gpio_num);
    ...
}

module_init(gpio_init);
module_exit(gpio_exit);
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐