DAY13. C工程与寄存器封装
如果出现图片无法查看可能是网络问题,我用的GitHub+图床保存的图片,可以参考我另外一篇文章GitHub的使用方法含网络问题解决
GitHub使用教程含网络问题_github加速器_肉丸子QAQ的博客-CSDN博客
相关作业和资料已上传,请在主页自行查看
1. C工程简介
- 资料里面提供了工程模板,新项目就不用再重新创建,将压缩包放入虚拟机,使用
tar xvf interface.tar
命令解压
- interface.c
编写C语言的文件
- common文件夹
- include文件夹:一些头文件,后面再讲
- src文件夹:一些源文件,源代码
- start文件夹:
cpu初始化
.text .global _start _start: /* * Vector table */ b reset b . b . b . b . b . b . b . reset: /* * Set vector address in CP15 VBAR register */ ldr r0, =_start mcr p15, 0, r0, c12, c0, 0 @Set VBAR /* * Set the cpu to SVC32 mode, Disable FIQ/IRQ */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr ,r0 /* * Defines access permissions for each coprocessor */ mov r0, #0xfffffff mcr p15, 0, r0, c1, c0, 2 /* * Invalidate L1 I/D */ mov r0, #0 @Set up for MCR mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs mcr p15, 0, r0, c7, c5, 0 @Invalidate icache /* * Set the FPEXC EN bit to enable the FPU */ mov r3, #0x40000000 fmxr FPEXC, r3 /* * Disable MMU stuff and caches */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @Clear bits 13 (--V-) bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM) orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB mcr p15, 0, r0, c1, c0, 0 /* * Initialize stacks */ init_stack: /*svc mode stack*/ msr cpsr, #0xd3 ldr sp, _stack_svc_end /*undef mode stack*/ msr cpsr, #0xdb ldr sp, _stack_und_end /*abort mode stack*/ msr cpsr,#0xd7 ldr sp,_stack_abt_end /*irq mode stack*/ msr cpsr,#0xd2 ldr sp, _stack_irq_end /*fiq mode stack*/ msr cpsr,#0xd1 ldr sp, _stack_fiq_end /*user mode stack, enable FIQ/IRQ*/ msr cpsr,#0x10 ldr sp, _stack_usr_end /*Call main*/ b main _stack_svc_end: .word stack_svc + 512 _stack_und_end: .word stack_und + 512 _stack_abt_end: .word stack_abt + 512 _stack_irq_end: .word stack_irq + 512 _stack_fiq_end: .word stack_fiq + 512 _stack_usr_end: .word stack_usr + 512 .data stack_svc: .space 512 stack_und: .space 512 stack_abt: .space 512 stack_irq: .space 512 stack_fiq: .space 512 stack_usr: .space 512
- 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
告诉编译器要链接的信息在什么位置
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) } }
有了工程模板,编写代码只需要编写interface.c
文件
2. 启动代码分析
打开start.s
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
b .
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
- 异常向量表,后续学习SWI等异常的时候再填写
- 把
r0
的值放到协处理器p15
的c12
- 设置cpu为
SVC
模式,禁用FIQ/IRQ
- 定义协处理器访问权限
- TLBs 和icache关闭
- 使能运算浮点型数据的协处理器
- MMU负责物理内存和虚拟内存转换,关闭:现在还没涉及系统移植,暂时不使用
- 初始化栈
将起始位置给
sp
,最后跳转到C语言编程main
- 申请各模式栈地址
为什么要开辟这么多个栈?
在前面内容可以知道不同模式下都有自己各自的栈R13.所以都需要单独开辟
- 计算栈的起始位置
根据协议ARM使用满减栈,所以是从高位进行压栈,这段代码就是为了计算栈的起始位置
3. 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;
}
4. 寄存器封装方式
在上述代码中我们可以发现,后期维护成本特别高,也特别容易出错。
所以我们可以将寄存器进行封装
- 第一种封装方式:宏定义
#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) //这里转换为gpx2型的数据是因为在第一种方式中0x11000c40就是GPX2CON的地址
//而将他们封装成结构体后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;
}
5. 寄存器操作标准化
在上述代码操作中,可以发现一个很严重的问题,就是当操作寄存器的时候控制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***
* 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);
*/
根据上面的规律我们可以实现对[31,28]位进行控制
#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;
}
6. 作业
1.简述课程中所使用的C语言工程模板中的启动代码主要做了哪些事
- 异常向量表初始化
- 把
r0
的值放到协处理器p15
的c12
- 设置cpu为
SVC
模式,禁用FIQ/IRQ
- 定义协处理器访问权限
- TLBs 和icache关闭
- 使能运算浮点型数据的协处理器
- MMU负责物理内存和虚拟内存转换,关闭:现在还没涉及系统移植,暂时不使用
- 初始化栈
- 申请各模式栈地址
- 计算栈的起始位置
2.使用C语言实现流水灯(LED2、LED3、LED4、LED5依次闪烁
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);//7[31:28]
GPX1.CON = GPX1.CON & (~(0xF)) | (0x1);//1[3:0]
GPF3.CON = GPF3.CON & (~(0xFF << 16)) | (0x11 << 16);//4[19:16]5[23:20]
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*点亮LED3*/
GPX1.DAT = GPX1.DAT | (1);
/*延时*/
Delay(1000000);
/*熄灭LED3*/
GPX1.DAT = GPX1.DAT & (~(1));
/*点亮LED4*/
GPF3.DAT = GPF3.DAT | (1 << 4);
/*延时*/
Delay(1000000);
/*熄灭LED4*/
GPF3.DAT = GPF3.DAT & (~(1 << 4));
/*点亮LED5*/
GPF3.DAT = GPF3.DAT | (1 << 5);
/*延时*/
Delay(1000000);
/*熄灭LED5*/
GPF3.DAT = GPF3.DAT & (~(1 << 5));
}
return 0;
}