祝各位道友念头通达
GitHub Gitee 语雀 打赏

通过对/dev/mem操作寄存器点灯程序

点灯程序如下

soc: zynq7010
该方式通过linux下对 /dev/mem 的操作实现点等程序
有一点是自己之前一直理解错的,就是这些寄存器,是CPU架构内部有程序定义的,CPU的架构也是通过软件实现的,语言用的硬件逻辑语言, 而寄存器也是CPU内部定义的, 然后每个寄存器有对应的数据位宽, 每个位都代表一个功能,向指定位写入0或者1, 使能对应的功能。至于是啥功能,这个就得看寄存器手册了。
这次代码虽然简单,但是让我从CPU架构到FPGA,在到硬件,软件linux架构有了更深层次的认知。

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
// 查看寄存器手册 ug585-Zynq-7000-TRM.pdf
#define GPIO_ADDR_BASE  0xE000A000
#define GPIO_DATA1      0xE000A044 //第几个引脚
#define GPIO_DIRECTION  0xE000A204 //控制IO脚是输出还是输入, 0 input 1 output
#define GPIO_CTRLDIREC  0xE000A208 //当引脚用于输出时候, 该寄存器是否启用输出 0 disable,1 enabled
#define GPIO_INTERGPIO  0xE000A214 //用于取消或者屏蔽GPIO中断输入, 0 nochange 1 clear interrupt mask 
#define APER_CLK_CTRL   0xF8000000 //时钟控制的起始地址,如果确定地址去偏移不行, 只能偏移相对起始地址,需要看手册
#define APER_CLK_CTRL1  0xF800012c //控制GPIO时钟地址, 如果该时钟没有控制上,相关GPIO值只能读到0

void led_gpio() {
    int fd = open("/dev/mem", O_RDWR | O_NOCTTY);
    if(fd < 0) {
        printf("error open /dev/mem; fd: %d \n", fd);
        return;
    }
    int gpio = 7;
    unsigned int * addr_aper = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, APER_CLK_CTRL);
    unsigned int * addr_aper1 = addr_aper + 75;
    int aper1_val = addr_aper1[0];
    addr_aper1[0] = aper1_val | (0x1U << 22);  //控制GPIO时钟使能的,只有给上这个信号,才能读出默认数据,不然一直读到的是0
    aper1_val = addr_aper1[0];

    printf("aper1_val %p, addr_aper: %p \n", aper1_val, addr_aper);

    unsigned int * addr_base = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_ADDR_BASE);
	// 0x40 data0。 需要对长度 * 4,就是地址 0xE000A044, 以下同样, 
	// mmap是不能够对地址 0xE000A044 直接映射,只能对起始地址进行映射 0xE000A000
    unsigned int * addr_data = addr_base + 16; 
    unsigned int * addr_dire = addr_base + 129; //0x204
    unsigned int * addr_ctrl = addr_base + 130; //0x208
    unsigned int * addr_inte = addr_base + 133; //0x214

    printf("addr_base %p \n", addr_base);

    int val1 = addr_data[0];
    int val2 = addr_dire[0];
    int val3 = addr_ctrl[0];
    int val4 = addr_inte[0];
    
    // addr_data[0] = val1 & ~(0x1U << 7);
    addr_inte[0] = val4 | (0x1U << 7);  //清除GPIO中断功能
    addr_dire[0] = val2 | (0x1U << 7);  //设置IO输出
    addr_ctrl[0] = val3 | (0x1U << 7);  //启用输出
    addr_data[0] = val1 | (0x1U << 7);  //引脚高电平, 默认将灯点亮

    // 控制灯闪烁
    int times = 0;
    while(1) {
        if(times++%2 == 0) {
            addr_data[0] = val1 | (0x1U << 7);  //引脚高电平
        }else {
            addr_data[0] = val1 & ~(0x1U << 7);  //引脚高电平
        }
        printf("=> addr_data: %0x, times %d \n", addr_data[0], times);
        sleep(1);
    }

    // int cnt = 0;
    // for(cnt = 0; cnt < 150; cnt++) {
    //     if(cnt % 10 == 0) {
    //         printf("\n");
    //     }
    //     printf("%08x ", addr_base[cnt]);
    // }
    // printf("\n");
}

int main(int argc, char const *argv[])
{
    led_gpio();
    return 0;
}

总结

  1. 对/dev/mem 可以操作到所有存在的外设, 只要是通过寄存器能控制的都能通过该方式操作,包括串口,SPI等。和裸机开发一样操作寄存器即可
  2. 该方式不需要设备树的支持,设备树主要是为驱动提供的一种初始参数供设备树例化程序,操作寄存器
posted @ 2023-03-12 01:35  韩若明瞳  阅读(177)  评论(0编辑  收藏  举报