STM32汇编启动文件详解:C语言操作寄存器实现点灯功能

以往使用STM32一直是基于各种库函数进行开发,最近打算从手册出发,直接操作寄存器对其进行编程

启动流程

来看下人工智能的描述
stm32启动流程

中断向量表

根据该描述,芯片复位后代码会首先指向中断向量表,查阅Cortex M4内核编程手册关于该向量表的描述:

可以看到该向量表第一个元素为初始栈指针值,而第二个元素应该存放Reset中断,因此汇编启动文件首先要声明一个向量表:

			AREA RESET,DATA,READONLY  ;定义了一个用于存放中断向量表的只读数据区域
			EXPORT Vector_Table		  ;将中断向量表导出,供链接器调用
Vector_Table	DCD 0				  ;向量表第一个元素
				DCD Reset_Handler	  ;向量表第二个元素

由于向量表第一个元素暂时不需要,因此设置为0

堆栈指针及main函数跳转

关于堆栈的分配的大小可以从keil软件中知道:

STM32F4xx参考手册对于SRAM的描述如下:

本次将映射在地址0x2000 0000的128KB内存全部分配到栈空间
分配完栈空间后,即可进行main函数的跳转了,代码如下:

				AREA |.text|,CODE,READONLY   ;定义代码段
Reset_Handler	PROC
				IMPORT main				     ;将main函数导入进来,后续进行跳转				
				LDR	SP,=(0x20000000+0x20000) ;分配栈空间,其初始栈指针指向栈顶(最大地址处)				
				BL main						 ;跳转到C文件中的main函数			
				ENDP
					END

由于本次对时钟没有特别要求,因此未在Reset_Handler中配置时钟

C文件

使能时钟

要点亮LED灯,首先要使能对应管脚的时钟,本次使用的正点原子STM32F407ZGT6核心板,LED1挂载于PF10管脚,因此首先需要使能GPIOF的时钟,查阅STM32F4xx参考手册,关于寄存器边界地址的描述:


从这里知道RCC外设的起始地址为0x40023800,关于GPIO时钟的偏移地址描述如下:


可以看到控制GPIO时钟的寄存器(RCC_AHB1ENR)偏移地址为0x30,有0x40023800+0x30 = 0x40023830,在该寄存器的描述中有:

因此需要将该寄存器的第5位置1,代码如下:

unsigned int *pGpioRcc = (unsigned int *)0x40023830;//GPIO RCC address
*pGpioRcc |= (unsigned int)1<<5;					//enable GPIOF clock

配置管脚

首先翻阅手册,知道GPIOF的基地址为0x40021400:


1、将管脚配置成输出模式:

根据描述,要将PF10设置为输出模式,就需要将该寄存器中的MODER10写入值2’b01,其偏移地址为0x00,因为该寄存器的地址:0x40021400 + 0x00 = 0x40021400 ,代码:

unsigned int *pGpioF = (unsigned int*)0x40021400;	//GPIOF base address	
*pGpioF |= (unsigned int)1 << 20;		         	//GPIOF set output mode
*pGpioF &= ~((unsigned int)1 << 21);

2、设置输出类型
输出类型一般设置为推挽输出,手册中关于这部分的描述:


可以知道控制输出类型的寄存器的地址为:0x40021400 + 0x04 = 0x40021404,要设置为推挽输出,将OT10写入1’b1即可,代码:

pGpioF =(unsigned int*)(0x40021400 + 0x04) ;		//GPIO output type set
*pGpioF &= ~((unsigned int)1 << 10);				//set push-pull output

后续关于输出速度、输出数据的寄存器解读以及代码,与上方两个寄存器的步骤完全一致,不再赘述

代码

工程目录:

start.s:

			PRESERVE8				  ;设置内存区位8字节对其
			THUMB					  ;使用thumb指令集
			
			AREA RESET,DATA,READONLY  ;定义了一个用于存放中断向量表的只读数据区域
			EXPORT Vector_Table		  ;将终端向量表导出,供链接器调用
Vector_Table	DCD 0				  ;向量表第一个元素
				DCD Reset_Handler	  ;向量表第二个元素

				AREA |.text|,CODE,READONLY   ;定义代码段
Reset_Handler	PROC
				IMPORT main				     ;将main函数导入进来,后续进行跳转				
				LDR	SP,=(0x20000000+0x20000) ;分配栈空间,其初始栈指针指向栈顶(最大地址处)				
				BL main						 ;跳转到C文件中的main函数			
				ENDP
					END

led.c:

void delay(int time)
{
	while(time--);
}

int main(void)
{
	unsigned int *pGpioRcc = (unsigned int *)0x40023830;//GPIO RCC address
	*pGpioRcc |= (unsigned int)1<<5;					//enable GPIOF clock
	
	unsigned int *pGpioF = (unsigned int*)0x40021400;	//GPIOF base address	
	*pGpioF |= (unsigned int)1 << 20;		         	//GPIOF set output mode
	*pGpioF &= ~((unsigned int)1 << 21);		     

	pGpioF =(unsigned int*)(0x40021400 + 0x04) ;		//GPIO output type set
	*pGpioF &= ~((unsigned int)1 << 10);				//set push-pull output
	
	pGpioF =(unsigned int*)(0x40021400 + 0x08) ;		//GPIO output speed set
	*pGpioF |= (unsigned int)1 << 20;		    		//set middle speed
	*pGpioF &= ~((unsigned int)1 << 21);
	
	pGpioF =(unsigned int*)(0x40021400 + 0x14) ;		//GPIO output value set 
	while(1)
	{
		*pGpioF &= ~(1 << 10);//GPIO value set 0
		delay(1000000);
		*pGpioF |= 1 << 10;   //GPIO value set 1
		delay(1000000);
	}
	
	return 0;
}

结果

最终的结果是绿灯明暗闪烁

思考

1、为什么启动文件选用汇编语言,而不用C语言
2、指向偏移地址时,为什么不能直接用指针变量加上偏移地址,例如配置端口输出数据寄存器时:

pGpioF =(unsigned int*)0x40021400 ;//GPIOF base address
*(pGpioF + 0x14) |= 1 << 10;       //GPIO value set 1

这种写法为什么是错的

作者:匀升↑

物联沃分享整理
物联沃-IOTWORD物联网 » STM32汇编启动文件详解:C语言操作寄存器实现点灯功能

发表回复