目录
一、C语言工程简介
Makefile
#=============================================================================#
NAME = interface
CROSS_COMPILE = arm-none-linux-gnueabi-
#=============================================================================#
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJDUMP = $(CROSS_COMPILE)objdump
OBJCOPY = $(CROSS_COMPILE)objcopy
CFLAGS += -g -O0 -mabi=apcs-gnu -mfpu=neon -mfloat-abi=softfp -fno-builtin \
-nostdinc -I ./common/include
#============================================================================#
OBJSss := $(wildcard start/*.S) $(wildcard common/src/*.S) $(wildcard *.S) \
$(wildcard start/*.c) $(wildcard common/src/*.c) \
$(wildcard usr/*.c) $(wildcard *.c)
OBJSs := $(patsubst %.S,%.o,$(OBJSss))
OBJS := $(patsubst %.c,%.o,$(OBJSs))
#============================================================================#
%.o: %.S
$(CC) $(CFLAGS) -c -o $@ $<
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
all:clean $(OBJS)
$(LD) $(OBJS) -T map.lds -o $(NAME).elf
$(OBJCOPY) -O binary $(NAME).elf $(NAME).bin
$(OBJDUMP) -D $(NAME).elf > $(NAME).dis
#============================================================================#
clean:
rm -rf $(OBJS) *.elf *.bin *.dis *.o
#============================================================================#
map.lds 链接脚本文件:告诉编译器 链接的信息 在什么位置 map.lds中找
map.lds:代码、数据、未初始化的全局变量放在BSS段,地址和排版,都可以在map.lds 找到信息
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x40008000;
. = ALIGN(4);
.text :
{
start/start.o(.text)
*(.text)
}
. = ALIGN(4);
.rodata :
{ *(.rodata) }
. = ALIGN(4);
.data :
{ *(.data) }
. = ALIGN(4);
.bss :
{ *(.bss) }
}
二、启动代码分析
异常向量表32个字节,AR,碰到异常,先跳转到相应异常向量表的位置,异常向量表中写跳转指令,跳转至异常处理程序
b.就是跳转到自身相当于死循环
1 .text
2 .global _start
3 _start:
4 /*
5 * Vector table
6 */
7 b reset
8 b .
9 b .
10 b .
11 b .
12 b .
13 b .
14 b .
15
16 reset:
17 /*
18 * Set vector address in CP15 VBAR register
19 */
20 ldr r0, =_start //异常向量表地址给R0
21 mcr p15, 0, r0, c12, c0, 0 @Set VBAR,mcr将ARM寄存器中的值写入协处理器中的寄存器;此处是修改异常向量表位置,ARM默认0地址,从0地址先找这个表;将R0存入cp15的C12,总体作用就是转存异常向量表
22
23 /*
24 * Set the cpu to SVC32 mode, Disable FIQ/IRQ
25 */
26 mrs r0, cpsr
27 bic r0, r0, #0x1f
28 orr r0, r0, #0xd3
29 msr cpsr ,r0
30
31 /*
32 * Defines access permissions for each coprocessor
33 */
34 mov r0, #0xfffffff
35 mcr p15, 0, r0, c1, c0, 2
36
37 /*
38 * Invalidate L1 I/D
39 */
40 mov r0, #0 @Set up for MCR
41 mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
42 mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
43
44 /*
45 * Set the FPEXC EN bit to enable the FPU
46 */
47 mov r3, #0x40000000
48 fmxr FPEXC, r3
49
50 /*
51 * Disable MMU stuff and caches 物理地址与虚拟地址的转换MMu
52 */
53 mrc p15, 0, r0, c1, c0, 0
54 bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
55 bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
56 orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
57 orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
58 orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
59 mcr p15, 0, r0, c1, c0, 0
60
61 /*
62 * Initialize stacks 初始化栈指针sp,保证正确压栈和出栈
63 */
64 init_stack:
65 /*svc mode stack*/
66 msr cpsr, #0xd3
67 ldr sp, _stack_svc_end
68
69 /*undef mode stack*/
70 msr cpsr, #0xdb
71 ldr sp, _stack_und_end
72
73 /*abort mode stack*/
74 msr cpsr,#0xd7
75 ldr sp,_stack_abt_end
76
77 /*irq mode stack*/
78 msr cpsr,#0xd2
79 ldr sp, _stack_irq_end
80
81 /*fiq mode stack*/
82 msr cpsr,#0xd1
83 ldr sp, _stack_fiq_end
84
85 /*user mode stack, enable FIQ/IRQ*/
86 msr cpsr,#0x10
87 ldr sp, _stack_usr_end
88
89 /*Call main*/
90 b main
91
92 _stack_svc_end:
93 .word stack_svc + 512
94 _stack_und_end:
95 .word stack_und + 512
96 _stack_abt_end:
97 .word stack_abt + 512
98 _stack_irq_end:
99 .word stack_irq + 512
100 _stack_fiq_end:
101 .word stack_fiq + 512
102 _stack_usr_end:
103 .word stack_usr + 512
104
105 .data
106 stack_svc:
107 .space 512 //伪操作把512个字节占了,作为栈的位置。6个有栈指针的模式都有自己的栈空间。
108 stack_und:
109 .space 512
110 stack_abt:
111 .space 512
112 stack_irq:
113 .space 512
114 stack_fiq:
115 .space 512
116 stack_usr:
117 .space 512
92 _stack_svc_end:
93 .word stack_svc + 512
用来计算栈指针的起始位置,因为是满减栈,要从高往低压
总结
- 异常向量表初始化
- 把r0的值放到协处理器p15的c12
- 设置cpu为SVC模式,禁用FIQ/IRQ
- 定义协处理器访问权限
- TLBs 和icache关闭
- 使能运算浮点型数据的协处理器
- MMU负责物理内存和虚拟内存转换,关闭:现在还没涉及系统移植,暂时不使用
- 初始化栈
- 申请各模式栈地址
- 计算栈的起始位置
- 恢复为user模式,跳转到main
三、 c语言实现LED实验
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*通过设置GPX2CON寄存器来将GPX2_7引脚设置成输出功能*/
//直接写0x11000c40会报错,这是一个常量,加*会报错
//,编译器是不知道这个值是地址,这是我们查表得到的,需要强制类型转换成地址
//指针的数据类型不是自身的数据类型,而是指向数据的类型
//寄存器地址四个字节,所以转成int或unsigned int
*(unsigned int *)0x11000c40 = 0x10000000;
while(1)
{
//还没学到系统调用,我们无法使用其他封装好的函数如sleep()和头文件
//点亮
*(unsigned int *)0x11000c44 = 0x00000080;
Delay(1000000);
//熄灭
*(unsigned int *)0x11000c44 = 0x00000000;
Delay(1000000);
}
return 0;
}
四、寄存器的封装方式
在上述代码中发现,读写性不高,后期维护成本特别高,特别容易出错。
所以我们可以将寄存器进行封装
1.第一种封装方式:宏定义
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
第二种封装方式:
从芯片手册能看到,GPX2的控制寄存器地址是连续的
C语言中除了数组地址是连续的,结构体的成员地址也是连续的。所以我们可以使用结构体将GPX2的不同寄存器封装起来更加高效
typedef struct
{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40) //0x11000c40表示为结构体的起始地址
//指针的数据类型不是自身的数据类型,而是指向数据的类型
//这里是想告诉编译器这个地址是结构体指针,所以这里要转变
//成结构体的数据类型
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
第三种:头文件封装
当控制硬件很多的时候,我们可以使用头文件将所有寄存器的封装写好,不同项目的时候直接使用
(头文件已封装)
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
五、寄存器操作标准化
在上述代码中,操作寄存器的时候控制GPX2CON[7]单
位为输出 但将整个寄存器的其他引脚进行了改变,而GPX2CON[7]
只占用了寄存器的[31,28],所以需要对单个位进行控制操作
/*
* 1.unsigned int a; 将a的第3位置1,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****1***
* 00000000 00000000 00000000 00001000
*
* a = a | (1 << 3); //通过移位进行位置的选择,或运算对单独的一位进行操作
*
* 2.unsigned int a; 将a的第3位置0,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****0***
a = a &0xFFFFFFFF7 ;//效率低,易出错
* 11111111 11111111 11111111 11110111
*
* a = a & (~(1 << 3));
*
* 3.unsigned int a; 将a的第[7:4]位置为0101,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** 0101****
* (操作GPX2CON寄存器是要控制[31,28]位.单独与,或无法完成多位的变化)
* 1).四位清零
* 11111111 11111111 11111111 00001111
* 00000000 00000000 00000000 11110000
* 00000000 00000000 00000000 00001111
*
* a = a & (~(0xF << 4));
*
* 2).再置位
* 00000000 00000000 00000000 01010000
* 00000000 00000000 00000000 00000101
*
* a = a | (0x5 << 4);
*
* => a = a & (~(0xF << 4)) | (0x5 << 4);
*/
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}