linux控制树莓派,树莓派3B-linux控制GPIO(不用树莓派的库)

这篇博客记录了我在用户程序中将物理地址映射到虚拟地址,然后使用虚拟地址控制树莓派3B的GPIO的过程。以下是整个过程的记录:

1、下载数据手册

和控制单片机IO口相似,如果用户想控制树莓派的GPIO,就得先知道GPIO相关寄存器的地址和设置的方法。树莓派的网站上提供了外设说明手册(Peripheral specification),这个手册对芯片上的外设怎么使用进行了描述。不过,Pi 3 的处理器是BCM2837,官网只提供了BCM2835(Pi 1 处理器)的外设说明手册。由于两个芯片外设上区别不大,我直接下载了BCM2835的手册来参考。下载手册的网址:https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/README.md

2、查阅GPIO相关寄存器地址和设置方法

翻到第5页,可以看到下图这个关于ARM地址映射的描述。

1536837415c7ad49b7aa47f35c862e1c.png

中间的部分为ARM的物理地址分配方式。IO外设(IO Peripherals)的物理地址分配在0x20000000(这是BCM2835的)。由于芯片不同,BCM2837的IO设备地址已经改为 0x3F000000,这一点,在官网提供的文档中也有说到。(https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md)

从第89页开始,描述的就是GPIO外设的地址和设置方法。

第90-91页的表格标明了和GPIO相关的寄存器的地址(下图是90页的部分信息)。

5c6b503aebfdddf598080f194b946f55.png

GPFESLn(选择引脚功能)、GPSETn(设置引脚输出高电平)和GPCLRn(设置引脚输出低电平)是控制引脚输出电平需要用到的寄存器。手册后面的几页内容将详细描述这些寄存器如何设置。例如GPFSEL1的描述为:

60359837c4d7609153960d6a44516abe.png

根据手册的描述,我们得到了GPIO相关寄存器的地址和设置方法,接下来将编写一个控制引脚输出电平的程序。

3、根据手册的描述编写程序

我在这里选择GPIO的Pin3作为实验对象,以下的程序是以Pin3为例。

#include #include //open函数的定义

#include //close函数的定义

#include #include //mmap函数的定义

#include //errno的定义

#include #include //uint8_t、uint32_t等类型的定义

#include //sleep函数的定义

//BCM2837外设的物理地址

#define PERIPHERALS_PHY_BASE 0x3F000000

//外设物理地址的数量

#define PERIPHERALS_ADDR_SIZE 0x01000000

//引脚高电平

#define HIGH 0x01

//引脚低电平

#define LOW 0x00

int memfd;

volatile uint32_t* bcm2837_peripherals_base;

volatile uint32_t* bcm2837_gpio_base;

//定义寄存器地址

volatile uint32_t* GPFSEL0;

volatile uint32_t* GPSET0;

volatile uint32_t* GPCLR0;

//将物理地址映射到用户进程的虚拟地址

int8_t paddr2vaddr();

//设置引脚3为输出功能

void pin3_select_output();

//控制引脚3的电平

void pin3_ctrl(uint8_t level);

//往地址addr写入值value

void write_addr(volatile uint32_t* addr, uint32_t value);

//读取地址addr的值

uint32_t read_addr(volatile uint32_t* addr);

int main()

{

//物理地址映射到虚拟地址

if(!paddr2vaddr())

{

return 0;

}

//pin3的功能选择为输出

pin3_select_output();

printf("Pin3 level:\n");

while(1)

{

//每两秒反转电平一次

printf("High\n");

pin3_ctrl(HIGH);

sleep(2);

printf("Low\n");

pin3_ctrl(LOW);

sleep(2);

}

}

int8_t paddr2vaddr()

{

if( (memfd = open("/dev/mem", O_RDWR | O_SYNC)) >= 0 )

{

//“/dev/mem”内是物理地址的映像

//通过mmap函数将物理地址映射为用户进程的虚拟地址

bcm2837_peripherals_base = mmap(NULL, PERIPHERALS_ADDR_SIZE, (PROT_READ | PROT_WRITE),

MAP_SHARED, memfd, (off_t)PERIPHERALS_PHY_BASE);

if(bcm2837_peripherals_base == MAP_FAILED)

{

fprintf(stderr, "[Error] mmap failed: %s\n", strerror(errno));

}

else

{

//计算控制pin3引脚的寄存器的地址

bcm2837_gpio_base = bcm2837_peripherals_base + 0x200000 / 4;

GPFSEL0 = bcm2837_gpio_base + 0x0000 / 4;

GPSET0 = bcm2837_gpio_base + 0x001C / 4;

GPCLR0 = bcm2837_gpio_base + 0x0028 / 4;

printf("Virtual address:\n");

printf("\tPERIPHERALS_BASE -> %X\n", (uint32_t)bcm2837_peripherals_base);

printf("\tGPIO_BASE -> %X\n", (uint32_t)bcm2837_gpio_base);

printf("\tGPFSEL0 -> %X\n", (uint32_t)GPFSEL0);

printf("\tGPSET0 -> %X\n", (uint32_t)GPSET0);

printf("\tGPCLR0 -> %X\n", (uint32_t)GPCLR0);

}

close(memfd);

}

else

{

fprintf(stderr, "[Error] open /dev/mem failed: %s\n", strerror(errno));

}

return bcm2837_peripherals_base != MAP_FAILED;

}

void pin3_select_output()

{

uint32_t value = read_addr(GPFSEL0);

//1111 1111 1111 1111 1111 0001 1111 1111 -> 0xFFFFF1FF

//0000 0000 0000 0000 0000 0010 0000 0000 -> 0x00000200

value = (value & 0xFFFFF1FF) | 0x00000200;

write_addr(GPFSEL0, value);

}

void pin3_ctrl(uint8_t level)

{

volatile uint32_t* reg;

uint32_t value;

if(level == HIGH)

{

reg = GPSET0;

}

else if(level == LOW)

{

reg = GPCLR0;

}

value = read_addr(reg);

//1111 1111 1111 1111 1111 1111 1111 1011 -> 0xFFFFFFFB

//0000 0000 0000 0000 0000 0010 0000 0100 -> 0x00000004

value = (value & 0xFFFFFFFB) | 0x00000004;

write_addr(reg, value);

}

void write_addr(volatile uint32_t* addr, uint32_t value)

{

__sync_synchronize();

*addr = value;

__sync_synchronize();

}

uint32_t read_addr(volatile uint32_t* addr)

{

uint32_t value;

__sync_synchronize();

value = *addr;

__sync_synchronize();

return value;

}

(代码参考了BCM2835驱动源码:http://www.airspayce.com/mikem/bcm2835/)

代码写完后,直接通过GCC编译即可,运行时要加上管理员权限,因为在普通用户的权限下,不能打开/dev/mem。

以上便是linux下控制树莓派3B的GPIO的整个过程的记录。如果大家发现问题,希望可以多多指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值