您的位置:首页 > 其它

树莓派2 gpio led blink实验

2015-11-27 22:44 295 查看
树莓派2使用的是bcm2836,树莓派1使用的是bcm2835,据说bcm2836除了CPU使用的是cortex-a7,外设与bcm2835基本相同,因为官方没有提供bcm2836的外设参考手册,所以只能使用bcm2835的外设参考手册,BCM2835-ARM-Peripherals.pdf

手册中第5页的地址映射图如下:



左侧的是bcm2835中的GPU的虚拟地址空间,中间的是在没有启用mmu时ARM的物理地址空间,右侧是的ARM开启mmu时的虚拟地址空间映射。

对于我们的裸机led实验,我们只关心中间的物理地址空间。所以bcm2835的外设被映射到0x20000000开始的物理地址。但是在树莓派2中这个外设起始地址变为0x3f000000。

有人在uboot的补丁中发现了这个地址。继续查看手册的第90页,可以得知gpio寄存器被映射到0x7e200000,这个GPU的虚拟地址,根据上图可以换算为0x20200000,也就是相对外设偏移0x200000,由此推测出在树莓派中gpio的起始地址为0x3f200000。

啰嗦了这么多,我们终于可以写下第一条语句了

#define GPIO_BASE    0x3f200000u


手册的89页说bcm2835中共有54个pin,90页的表6.1说明了gpio的41个寄存器的细节,包括被映射到的地址,以及功能等。根据此表我们可以写代码如下:

/* gpio register index */
enum gpio_reg_index {
GPIO_GPFSEL0   = 0,
GPIO_GPFSEL1   = 1,
GPIO_GPFSEL2   = 2,
GPIO_GPFSEL3   = 3,
GPIO_GPFSEL4   = 4,
GPIO_GPFSEL5   = 5,
GPIO_GPSET0    = 7,
GPIO_GPSET1    = 8,
GPIO_GPCLR0    = 10,
GPIO_GPCLR1    = 11,
GPIO_GPLEV0    = 13,
GPIO_GPLEV1    = 14,
GPIO_GPEDS0    = 16,
GPIO_GPEDS1    = 17,
GPIO_GPREN0    = 19,
GPIO_GPREN1    = 20,
GPIO_GPFEN0    = 22,
GPIO_GPFEN1    = 23,
GPIO_GPHEN0    = 25,
GPIO_GPHEN1    = 26,
GPIO_GPLEN0    = 28,
GPIO_GPLEN1    = 29,
GPIO_GPAREN0   = 31,
GPIO_GPAREN1   = 32,
GPIO_GPAFEN0   = 34,
GPIO_GPAFEN1   = 35,
GPIO_GPPUD     = 37,
GPIO_GPPUDCLK0 = 38,
GPIO_GPPUDCLK1 = 39,
};

enum gpio_fsel {
GPIO_FSEL_INPUT  = 0x0,
GPIO_FSEL_OUTPUT = 0x1,
GPIO_FSEL_ALT0   = 0x4,
GPIO_FSEL_ALT1   = 0x5,
GPIO_FSEL_ALT2   = 0x6,
GPIO_FSEL_ALT3   = 0x7,
GPIO_FSEL_ALT4   = 0x3,
GPIO_FSEL_ALT5   = 0x2,
};

extern void gpio_pin_fsel(u32 pin, enum gpio_fsel value);

extern void gpio_pin_high(u32 pin);
extern void gpio_pin_low(u32 pin);


我们把gpio的寄存器映射到的这块内存当成一个unsigned int类型的数组,GPIO_BASE是数组的起始地址,因为每个寄存器都是32位的,即4个字节,所以我们可以通过寄存器的索引来访问寄存器。

前6个寄存器是功能选择寄存器,每个pin可以配置成2^3==8种不同的功能,如0b000表示输入,0b001表示输出,因为每个pin都占用3位,所以一个32位的寄存器可以容纳10个pin的配置信息,因为有54个pin,所有需要6个功能选择寄存器。

我们通过函数gpio_pin_fsel来配置相应pin的功能

第7第8个寄存器加起来一共64位,每一位对应一个pin,对应位为1,表示相应pin输出高电平

我们通过函数gpio_pin_high来使相应pin输出高电平,对于led就是点亮

第10第11个寄存器加起来一共64位,每一位对应一个pin,对应位为1,表示相应pin输出低电平。

我们通过函数gpio_pin_low来使相应pin输出低电平,对于led就是熄灭

仔细查看表6.1的寄存器功能,可以写出如下的实现代码

void gpio_pin_fsel(u32 pin, enum gpio_fsel value)
{
volatile u32 *base = (u32 *)GPIO_BASE;
base[GPIO_GPFSEL0+pin/10] |= (value << pin%10*3);
}

void gpio_pin_high(u32 pin)
{
volatile u32 *base = (u32 *)GPIO_BASE;
base[GPIO_GPSET0+pin/32] |= (1 << pin%32);
}

void gpio_pin_low(u32 pin)
{
volatile u32 *base = (u32 *)GPIO_BASE;
base[GPIO_GPCLR0+pin/32] |= (1 << pin%32);
}


主程序代码如下:

#include <gpio.h>

void delay();

int main(int argc, char *argv[])
{
gpio_pin_fsel(47, GPIO_FSEL_OUTPUT);

while (1) {
delay();
gpio_pin_high(47);
delay();
gpio_pin_low(47);
}

return 0;
}

void delay()
{
for (int i = 0; i < 500000; ++i)
;
}


pin 47是树莓派2上那个绿色的led

还有一个细节是要让c语言在裸机上运行,我们必须要设置stack pointer。

通过一个简短的汇编代码 start.S来完成:

.text
.global _start
_start:
mov sp, #0x8000
bl main


通过命令

rm-none-eabi-gcc -std=gnu99 -I. -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -mtune=cortex-a7 -nostartfiles -g start.S main.c gpio.c -o kernel.elf
arm-none-eabi-objcopy kernel.elf -O binary kernel.img


-g是防止编译器将我们的delay函数优化掉

将 bootcode.bin start.elf kernel.img 拷贝到sd卡中插入树莓派2,通电后,可以看到绿色led不停的闪烁了

note:

本文主要参考Brian的文章,非常感谢Brian

gnu cross toolchain: https://launchpad.net/gcc-arm-embedded/+download
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: