D13. C工程与寄存器封装-ARM体系结构与接口技术-嵌入式学习LV9

DAY13. C工程与寄存器封装


如果出现图片无法查看可能是网络问题,我用的GitHub+图床保存的图片,可以参考我另外一篇文章GitHub的使用方法含网络问题解决
GitHub使用教程含网络问题_github加速器_肉丸子QAQ的博客-CSDN博客


相关作业和资料已上传,请在主页自行查看

1. C工程简介

  • 资料里面提供了工程模板,新项目就不用再重新创建,将压缩包放入虚拟机,使用tar xvf interface.tar命令解压

image-20230804100939609

  1. interface.c

编写C语言的文件

image-20230804101225735

  1. common文件夹

image-20230804101550774

  • include文件夹:一些头文件,后面再讲

image-20230804101305451

  • src文件夹:一些源文件,源代码

image-20230804101723143

  1. start文件夹:

cpu初始化

image-20230804101816415

.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

  1. 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
#============================================================================#

image-20230804102933990

  1. 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等异常的时候再填写

image-20230804104038129

  • r0的值放到协处理器p15c12

image-20230804104316517

  • 设置cpu为SVC模式,禁用FIQ/IRQ

image-20230804104429312

  • 定义协处理器访问权限

image-20230804104515025

  • TLBs 和icache关闭

image-20230804104635977

  • 使能运算浮点型数据的协处理器

image-20230804104712838

  • MMU负责物理内存和虚拟内存转换,关闭:现在还没涉及系统移植,暂时不使用

image-20230804104725706

  • 初始化栈

image-20230804104917372

将起始位置给sp,最后跳转到C语言编程main

  • 申请各模式栈地址

image-20230804105047777

为什么要开辟这么多个栈?

在前面内容可以知道不同模式下都有自己各自的栈R13.所以都需要单独开辟

image-20230804105249467

  • 计算栈的起始位置

image-20230804105434104

根据协议ARM使用满减栈,所以是从高位进行压栈,这段代码就是为了计算栈的起始位置

image-20230804105728309

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. 寄存器封装方式

在上述代码中我们可以发现,后期维护成本特别高,也特别容易出错。

所以我们可以将寄存器进行封装

  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)  //这里转换为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;
}

第三种:头文件封装

当控制硬件很多的时候,自己一遍一遍写就很麻烦,所以我们可以使用头文件将所有寄存器的封装写好,不同项目的时候也能直接使用

image-20230805093120466

相关库文件在工程模板中给出

#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语言工程模板中的启动代码主要做了哪些事

  1. 异常向量表初始化
  2. r0的值放到协处理器p15c12
  3. 设置cpu为SVC模式,禁用FIQ/IRQ
  4. 定义协处理器访问权限
  5. TLBs 和icache关闭
  6. 使能运算浮点型数据的协处理器
  7. MMU负责物理内存和虚拟内存转换,关闭:现在还没涉及系统移植,暂时不使用
  8. 初始化栈
  9. 申请各模式栈地址
  10. 计算栈的起始位置

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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肉丸子QAQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值