上一篇写了要关闭看门狗,后面需要用到的时候再开
下面的启动代码依然要用汇编写
为什么用汇编不用C语言
因为这时候不具备C语言的运行环境,所以需要创造一个环境给C语言。
为什么要设置栈
因为C语言的局部变量都是用栈来实现的。只有设置了合法的栈地址,局部变量才可以定义。
为什么平时的不用设置
单片机程序(譬如51单片机)或者编写应用程序时并没有去设置栈,但是C程序还是可以运行的。原因是:在单片机中由硬件初始化时提供了一个默认可用的栈,在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编实现的代码,这个代码中就帮我们的C程序设置了栈及其他的运行时需要。
那么栈设置在哪里?
这个要去看开发板的数据手册
现在用的这个开发板是
可以看到有个SVC Stack,这个就是栈了。
SVC:supervisor ,因为系统复位是默认进入SVC的(课程这样讲)
那到底是哪个地址,上面还是下面
在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈
结合iROM_application_note中的memory map,可知SVC栈应该设置为0xd0037D80
满增、满减、空增、空减栈
知道了地址之后怎么设置,放在哪个寄存器里面?
栈嘛,需要用到栈指针,stack pointer ,即上面讲到的sp,只需要把sp跟上述的数据手册的地址挂钩即可。
ldr sp = 0xd0037D80
然后执行完之后就可以用c语言了
设置完之后怎么用C语言?
使用bl命令
bl:branch with link 跳转并链接的意思
b :branch 跳转的意思
直接后面
bl clanguage
bl + 函数名 即可
最后Makefile会做好编译链接的。
最后记得不要漏死循环
b . //到这就一直循环,防止执行了后面未知位置的东西跑飞。
volatile
volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
一点C语言语法技巧
宏定义的位置,下面那段话是自己第一次学的时候的笔记。
unsigned int p= (unsigned int)0xE0200240
//定义一个类型为unsigned int的指针p,把0xE0200240这个值强制转换成unsigned int类型,保持与指针p的类型一致,然后把这个值赋值给指针p即p,可以理解为现在p指着的地址就是0xE0200240
unsigned int p1= (unsigned int)0xE0200244
//这句话跟上面一样理解
*p= 0x11111111 //因为经过上面第一句,*p已经指向那个地址了,现在是往那个地址里面赋值,初始化GPJOCON
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)
void delay(void);
// 该函数要实现led闪烁效果
void led_blink(void)
{
// led初始化,也就是把GPJ0CON中设置为输出模式
//volatile unsigned int *p = (unsigned int *)GPJ0CON;
//volatile unsigned int *p1 = (unsigned int *)GPJ0DAT;
rGPJ0CON = 0x11111111;
while (1)
{
// led亮
rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
// 延时
delay();
// led灭
rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
// 延时
delay();
}
}
void delay(void)
{
volatile unsigned int i = 900000; // volatile 让编译器不要优化,这样才能真正的减
while (i--); // 才能消耗时间,实现delay
}