本文是在学习 麦子学院 的嵌入式课程之后整理的一篇关于GPIO的笔记。内容比较基础。不过用老师的话来讲,思路很重要。学习应该是要触类旁通、举一反三的。
课程中老师使用的是 友善之臂的 s5pv210 开发板。而我手头是一块 s3c2440 开发板。本文的程序都是基于 s3c2440 编写。交叉工具链使用的是我自己制作的ARM-Linux-gcc工具链。可以参考这里. 另外烧录工具使用的是友善之臂提供的 minitools。
本人的学习经历算是比较奇葩。学习过数字电路基础,但并未真正玩过一些单片机或者ARM开发板。后来又直接跳到了应用层面:网络编程,网络协议这些。所以对底层总是保持一颗好奇的心。想搞清楚在ARM开发板的层面上是如何点亮或者关闭一个LED灯。又或者如何操作网卡来接收发送数据。当然还有更底层的东西,比如信号处理这些,可是好奇心并不是很大。对我目前的学习而言,就是想把 硬件接口开发——Linux内核驱动开发—— Linux系统移植以及集成——上层应用开发 这一整套知识尽可能的掌握。好了,闲话不多说了,开始正题。
GPIO就是通用输入输出的意思。是计算机上最简单也是最常用的一种接口资源。在没有做这个实验之前,也能做猜想到电路LED灯也就是将板子上的某个引脚设置为地或者高电平就行了。下面是具体的过程。
1. 在 mini2440 开发板原理图,也就是电路图上找到 LED
这里以 led1 led2 为例,它们是接在 GPB5 GPB6 这两个IO口上。
2. 在 s3c2440 芯片手册中查找相应的GPIO口
按照芯片手册的说明,GPB5 位于 GPBCON 控制寄存器的 [11,10]位,而GPB6位于[13,12]位。它们的功能描述也很清楚,01表述输出功能。再来看GPBDAT数据接口。GPB[10:0]一共11位,GPBDat[5]就代表GPB5的输出,GPBDat[6]就代表GPB6的输出。按照后面的描述,当GPIO口设置为 output 功能,pin state 跟相应的 bit 位的值是一致的。在写代码实现的过程中,要注意两个寄存器的地址GPBCON——0x56000010 GPBDAT——0x56000014.
参考上面的思路: 找到相应的寄存器地址——设置寄存器为输出功能——将IO口设置为低电平,点亮LED灯 。下面给出部分代码,完整的代码可以参考 麦子学院 老师的视频。手把手教你写代码,不怕你不会。真心很赞。
#ifndef __ASM_ARCH_GPIO_H #define __ASM_ARCH_GPIO_H struct s3c2440_gpio_bank { unsigned int con; unsigned int dat; unsigned int up; unsigned char reserved[4]; }; struct s3c2440_gpio { struct s3c2440_gpio_bank gpio_a; struct s3c2440_gpio_bank gpio_b; struct s3c2440_gpio_bank gpio_c; struct s3c2440_gpio_bank gpio_d; struct s3c2440_gpio_bank gpio_e; struct s3c2440_gpio_bank gpio_f; struct s3c2440_gpio_bank gpio_g; struct s3c2440_gpio_bank gpio_h; }; #endif
#ifndef _S3C2440_CPU_H #define _S3C2440_CPU_H #define s3c2440_GPIO_BASE 0x56000000 #define __REG(x) (*(volatile unsigned int *)(x)) #define readb(a) (*(volatile unsigned char *)(a)) #define readw(a) (*(volatile unsigned short *)(a)) #define readl(a) (*(volatile unsigned int *)(a)) #define writeb(v, a) (*(volatile unsigned char *)(a) = v) #define writew(v, a) (*(volatile unsigned short *)(a) = v) #define writel(v, a) (*(volatile unsigned int *)(a) = v) #endif
#include "gpio.h" #include "led.h" #include "cpu_io.h" void led_init() { struct s3c2440_gpio *base = (struct s3c2440_gpio *)s3c2440_GPIO_BASE; unsigned int var; var = __REG(&base->gpio_b.con); var &= ~(0xf << 10); var |= (0x5 << 10); writel(var, &base->gpio_b.con); } void led_blink(LEDStatus s) { struct s3c2440_gpio *base = (struct s3c2440_gpio *)s3c2440_GPIO_BASE; unsigned int var; if (s == ON) { var = __REG(&base->gpio_b.dat); var &= ~(0x3 << 5); writel(var, &base->gpio_b.dat); } else { var = __REG(&base->gpio_b.dat); var |= (0x3 << 5); writel(var, &base->gpio_b.dat); } }
下面是main函数调用相应的接口:
#include "gpio.h" #include "led.h" #include "cpu_io.h" void delay(); void led_test(); int main() { led_test(); return 0; } void delay() { volatile unsigned int i = 1000000; while (i--); } void led_test() { led_init(); LEDStatus s; while (1) { s = ON; led_blink(s); delay(); s = OFF; led_blink(s); delay(); } }
最值得注意的是,在底层操作中每个 bit 都有重要的含义。所以在操作寄存器的时候不能一股脑往里面写一个值。通常的做法是 读取——操作——回写 的机制。其中操作的过程中使用 &= |= 只改写相应的位。不影响寄存器原来的值。
这一阶段的学习集中在ARM底层的一些接口开发。为将来更好的学习Linux内核驱动打基础。所以大都是C语言编写的裸板程序,并且这些程序组织关系非常相似。都是先通过 函数,然后在 跳到 main
函数,然后在 函数中调用相关的接口模块。所以可以使用统一的 makefile 来管理。下面是一个我在课程老师编写的基础上做了一些修改的 makefile 模板。 函数中调用相关的接口模块。所以可以使用统一的 makefile 来管理。下面是一个我在课程老师编写的基础上做了一些修改的 makefile 模板。
# this makefile is the temlate of # all ARM-Linux bare machine develop # # Gru__ at 20160108 # the makefile var CURDIR := $(shell basename `pwd`) BUILDDIR ?= ./build/ TARGET := $(CURDIR).bin BUILD := $(TARGET).elf SRCFILE := $(shell ls | grep '\.'c$) COBJS += start.o COBJS += $(patsubst %.c, %.o, $(SRCFILE)) PATH += :/home/gru/cross/embedded-toolchains/tool-chain/bin CROSS_COMPILE := arm-linux- CC := $(CROSS_COMPILE)gcc LD := $(CROSS_COMPILE)ld OBJCOPY := $(CROSS_COMPILE)objcopy BOOTWAY ?= RAM CFLAGS += -Wall CFLAGS += -I./inc LDFLAGS += -Tmap.lds ifeq ($(BOOTWAY), RAM) LDFLAGS += -Ttext=0x30000000 else LDFLAGS += -Ttext=0x0 endif # the common make way all : $(TARGET) ifeq ($(BOOTWAY), RAM) $(TARGET) : $(BUILD) $(OBJCOPY) -O binary $^ $@ else endif $(BUILD) : $(COBJS) $(LD) $(LDFLAGS) -o $@ $^ %.o : %.c $(CC) $(CFLAGS) -c $^ -o $@ %.o : %.S $(CC) $(CFLAGS) -c $^ -o $@ clean: rm -rf *.bin *.elf *.o
每个小实验的目录组织关系如下: