STM32汇编启动文件详解:C语言操作寄存器实现点灯功能
以往使用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
这种写法为什么是错的
作者:匀升↑