在上一章的笔记中我们已经学到了如何使用纯汇编语言来进行最简单的LED灯点亮,即通过查阅数据手册进行寄存器配置。
GPIO最简单配置方案
- 首先设置CCGR0~6以此使能GPIO时钟;
- 然后设置GPIO的复用(SW_MUX_CTL);
- 再之后设置GPIO的电气属性(SW_PAD_CTL);
- 设置GPIO输入输出/中断等属性(DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR、ISR)
点灯方式之纯汇编版本
.global _start /* 全局标号 */
/*
* 描述: _start函数,程序从此函数开始执行此函数完成时钟使能
* GPIO初始化、最终控制GPIO输出低电平来点亮LED灯。
*/
_start:
/* 例程代码 */
/* 1、使能所有时钟 */
ldr r0, =0X020C4068 /* CCGR0 */
ldr r1, =0XFFFFFFFF
str r1, [r0]
ldr r0, =0X020C406C /* CCGR1 */
str r1, [r0]
ldr r0, =0X020C4070 /* CCGR2 */
str r1, [r0]
ldr r0, =0X020C4074 /* CCGR3 */
str r1, [r0]
ldr r0, =0X020C4078 /* CCGR4 */
str r1, [r0]
ldr r0, =0X020C407C /* CCGR5 */
str r1, [r0]
ldr r0, =0X020C4080 /* CCGR6 */
str r1, [r0]
/* 2、设置GPIO1_IO03复用为GPIO1_IO03 */
ldr r0, =0X020E0068 /* 将寄存器SW_MUX_GPIO1_IO03_BASE加载到r0中 */
ldr r1, =0X5 /* 设置寄存器SW_MUX_GPIO1_IO03_BASE的MUX_MODE为5 */
str r1,[r0]
/* 3、配置GPIO1_IO03的IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
ldr r0, =0X020E02F4 /*寄存器SW_PAD_GPIO1_IO03_BASE */
ldr r1, =0X10B0
str r1,[r0]
/* 4、设置GPIO1_IO03为输出 */
ldr r0, =0X0209C004 /*寄存器GPIO1_GDIR */
ldr r1, =0X0000008
str r1,[r0]
/* 5、打开LED0
* 设置GPIO1_IO03输出低电平
*/
ldr r0, =0X0209C000 /*寄存器GPIO1_DR */
ldr r1, =0
str r1,[r0]
/* 描述: loop死循环 */
loop:
b loop
点灯方式之C语言版本
本章中,将会把这些汇编语言封装成C语言的各类函数并进行调用。首先,根据上一章所查询手册得到的地址,将以下地址定义到.h文件中
点灯方式之C语言版本-main.h
/*定义配置时钟的寄存器地址*/
#define CCM_CCGR0 *((volatile unsigned long*)0X020C4068)
#define CCM_CCGR1 *((volatile unsigned long*)0X020C406C)
#define CCM_CCGR2 *((volatile unsigned long*)0X020C4070)
#define CCM_CCGR3 *((volatile unsigned long*)0X020C4074)
#define CCM_CCGR4 *((volatile unsigned long*)0X020C4078)
#define CCM_CCGR5 *((volatile unsigned long*)0X020C407C)
#define CCM_CCGR6 *((volatile unsigned long*)0X020C4080)
/*IOMUX寄存器地址 (配置IO复用/功能的东西)*/
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02f4)
/*GPIO1寄存器配置*/
#define GPIO1_DR *((volatile unsigned int *)0X0209C000)
#define GPIO1_GDIR *((volatile unsigned int *)0X0209C004)
#define GPIO1_PSR *((volatile unsigned int *)0X0209C008)
#define GPIO1_ICR1 *((volatile unsigned int *)0X0209C00C)
#define GPIO1_ICR2 *((volatile unsigned int *)0X0209C010)
#define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)
在定义寄存器地址中常用到volatile unsigned int,其中volatile关键字有以下用途:
(1)用来同步,因为同一个东西可能在不同的存储介质中有多个副本,有些情况下会使得这些副本中的值不同,这是不允许的。因此采用volatile,让它只有一个,没有其他的副本,这样就不会发生不同步的问题。
(2)防止编译器优化去掉某些语句,如果没有volatile说明,编译器就很有可能会去掉看似无用但实际上有用的语句。
(3)当地址是io端口的时候,读写这个地址是不能对它进行缓存的,这是相对于某些嵌入式中有cache才有这个。
比如写这个io端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候再写到io端口,这样就不能使数据及时的写到io端口。
有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到io端口,从而避免了读写io端口的延时。
点灯方式之C语言版本-main.c
1. 首先设置CCGR0~6以此使能GPIO时钟;
/*使能外部时钟*/
void clk_enable(void)
{
CCM_CCGR1 = 0xFFFFFFFF;
CCM_CCGR2 = 0xFFFFFFFF;
CCM_CCGR3 = 0xFFFFFFFF;
CCM_CCGR4 = 0xFFFFFFFF;
CCM_CCGR5 = 0xFFFFFFFF;
CCM_CCGR6 = 0xFFFFFFFF;
}
2. 然后设置GPIO的复用(SW_MUX_CTL)、电气属性(SW_PAD_CTL)、设置GPIO输入输出/中断等属性(DR、GDIR)
/*GPIO初始化:一个void函数便将四个寄存器均写好值*/
void led_init(void)
{
SW_MUX_GPIO1_IO03 =0x5; //GPIO复用为GPIO1_IO03
SW_PAD_GPIO1_IO03 =0x10B0; //配置GPIO1_IO03的电气属性
/*GPIO初始化*/
GPIO1_GDIR = 0x8; //设置为输出模式
GPIO1_DR = 0x0; //默认打开LED灯
}
3. 定义部分功能函数
void delay_short(volatile unsigned int n)
{
while(n--){}
}
/*主频396MHZ,delay(n)延时大概1ms*/
void delay(volatile unsigned int n)
{
while(n--){
delay_short(0x7ff);
}
}
/*打开LED灯*/
void led_on(void)
{
/*
将1左移3位,为1 0 0 0 ,取反后即0 1 1 1 ;
这时候再与运算,则此时bit3清零了(bit3 bit2 bit1 bit0)
,而其他保持位不变
*/
GPIO1_DR &= ~(1<<3);
}
/*关闭LED灯*/
void led_off(void)
{
/*
将1左移3位,为1 0 0 0 ;
这时候再或运算,则此时bit3置1了(bit3 bit2 bit1 bit0)
,而其他保持位不变
*/
GPIO1_DR |= (1<<3);/*bit3置1*/
}
4.main函数
int main(void)
{
clk_enable();
led_init();
while(1){
led_on();
delay(500);
led_off();
delay(500);
}
return 0;
}
5.Makefile
由上一章的Makefile文件作引,进行相关说明:
led.bin:led.s
/*使用 arm-linux-gnueabihf-gcc,将.c/.s文件变为.o文件。*/
arm-linux-gnueabihf-gcc -g -c led.s -o led.o
/*将所有的.o文件连接为elf格式的可执行文件*/
arm-linux-gnueabihf-ld -Ttext 0X87800000 led.o -o led.elf
/*将elf文件转为bin文件。.*/
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
/*将elf文件转为汇编,反汇编*/
arm-linux-gnueabihf-objdump -D led.elf > led.dis
/*清除makefile生成的所有.o文件,以及.bin .elf .dis文件*/
clean:
rm -rf *.o led.bin led.elf led.dis
即总体流程为:
.c/.s → .o → .elf → .bin
另外一个分支是.elf到.dis进行反汇编,查看编写的代码是否有问题:
.elf → .dis
或者可以升级为新的写法,采用Makefile的快速变量:
Makefile有三个非常有用的变量。分别是 $@,$^,$<
代表的意义分别是:
$@:目标文件,$^:所有的依赖文件,$<:第一个依赖文件。
objects = start.o main.o
ledc.bin: $(objects)
arm-linux-gnueabihf-ld -Ttext 0X87800000 start.o main.o -o ledc.elf
arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
/*以下两条规则则是把所有的.c/.s文件编译为.o文件*/
/*这个规则表示所有的.o文件都是依赖与相应的.c文件*/
%.o : %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
/*这个规则表示所有的.o文件都是依赖与相应的.s文件*/
%.o : %.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o ledc.bin ledc.elf ledc.dis;