ARM裸机学习笔记(二)看门狗简介and汇编代码实现栈和调用C语言

(一)看门狗

1.什么是看门狗:

  • 看门狗(watch dog timer 看门狗定时器)。大家想象这样一个场景:家门口有一只狗,这个狗定时会饿(譬如说2小时一饿),够饿了会胡乱咬死人。人进进出出要想保证安全必须提前喂狗(必须在上次喂过后的2小时内喂狗才行)。如果超时没喂狗就会被咬死,如果提前喂狗没关系,但是本次喂狗时间就会从这里开始计算。现实中因为一些外部因素,电子设备经常会跑飞或者死机(譬如极端炎热、极端寒冷、工业复杂场合)。在这种情况下我们希望设备自动复位而不需要人工干预(无人值守)。看门狗用来完成这个工作。看门狗其实是我们SoC内部的一个定时器(类似于闹钟,类似于门口的狗),定好时间之后看门狗定时器会去计时,时间到之前(狗饿了之前)必须去重新置位看门狗定时器(喂狗),如果没有喂狗则系统会被强制复位。

  • 系统在正常工作时,系统软件会自己去喂狗,所以看门狗定时器不会复位。但是系统一旦故障跑飞啥的,看门狗就没人喂了,然后下一个周期就会自动复位,达到我们期望的效果。

2.关键性操作SFR(特殊功能寄存器)

  • WTCON(0xE2700000),其中bit5(Watchdog timer)是看门狗的开关:0代表关,1代表开

3.代码

#define WTCON   0xE2700000

.global _start            @把_start的链接属性改为外部,让其他的程序也能够看到
_start:                   @_start是一个函数的起始地址,也是编译、链接后程序的起始地址。
                          @由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的
	# 关闭看门狗
	ldr r0, =0x0   
	ldr r1, =WTCON
	str r0, [r1]          @将0写入WTCON中
	...

4.总结210中看门狗特性(iROM中已经关看门狗)

  • 为什么要关看门狗?
    + 一般CPU设计,在CPU启动后看门狗默认是工作的(为什么默认不关闭而要工作?我猜测是因为怕你的程序在启动代码前端就死机了或者跑飞了没人管),好处就是没有空当和漏洞,坏处就是在启动代码段我们不方便去喂狗(或者说懒得去喂狗)时看门狗会复位,所以为了偷懒我们就在启动代码前端先去关闭看门狗,然后在后面系统启动起来之后再根据需要决定是否要打开看门狗(一旦打开就必须同时提供喂狗)。
    + 在S5PV210内部的iROM代码(BL0)中,其实已经关过看门狗了。所以我们的启动代码实际上是不用去关也没事的,也就是说今天写的关闭看门狗的代码运行后没有任何现象(没有现象就是正常现象).
    + 很多CPU内部是没有BL0的,因此也没人给你关看门狗,都要在启动代码前段自己写代码关看门狗,所以今天学习的内容也是有价值的。

(二)汇编写启动代码之设置栈和调用C语言

1.C语言运行时需要和栈的意义

  • **C语言运行时(runtime)**需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈
  • C语言与栈的关系:C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个程序就死掉了。
  • 我们平时在编写单片机程序(譬如51单片机)或者编写应用程序时并没有去设置栈,但是C程序还是可以运行的。原因是:在单片机中由硬件初始化时提供了一个默认可用的栈,在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编实现的代码,这个代码中就帮我们的C程序设置了栈及其他的运行时需要。

2.CPU模式和各种模式下的栈

  • 在ARM中37个寄存器中,每种模式下都有自己的独立的SP寄存器(r13),为什么这么设计?
    如果各种模式都使用同一个SP,那么就意味着整个程序(操作系统内核程序、用户自己编写的应用程序)都是用一个栈的。你的应用程序如果一旦出错(譬如栈溢出),就会连累操作系统的栈也损坏,整个操作系统的程序就会崩溃。这样的操作系统设计是非常脆弱的,不合理的。
    解决方案就是各种模式下用不同的栈。我的操作系统内核使用自己的栈,每个应用程序也使用自己独立的栈,这样各是各的,一个损坏不会连累其他人。
    我们现在要设置栈,不可能也懒的而且也没有必要去设置所有的栈,我们先要找到自己的模式,然后设置自己的模式下的栈到合理合法的位置,即可。
    注意:系统在复位后默认是进入SVC模式的
    我们如何访问SVC模式下的SP呢?很简单,先把模式设置为SVC,再直接操作SP。但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。

3.查阅文档并设置栈指针至合法位置

  • 栈必须是当前一段可用的内存(可用的意思是这个地方必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用)
    当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。因此我们只能在SRAM中找一段内存来作为SVC的栈
  • 栈有四种:满减栈 满增栈 空减栈 空增栈
满增栈
满减栈
空增栈
空减栈
  • 在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈
  • 结合iROM_application_note中的memory map,可知SVC栈应该设置为0xd0037D80
#define SVC_Stack   0xD0037780 
	# 设置SVC栈	
	ldr sp, =SVC_Stack

4.汇编程序和C程序互相调用

  • 写好C语言代码后注意在Makeflie中添加新的依赖
/*
 *文件名:led.c
 *描述:流水灯
 *GPIOJ0_3、4、5
 *需要配置寄存器:GPJ0CON(0xE0200240)、GPJ0DAT(0xE0200244)
 */
#include <stdio.h>

#define rGPJ0CON     *((volatile unsigned int*)0xE0200240)
#define rGPJ0DAT     *((volatile unsigned int*)0xE0200244)  

void delay (void);

void led_blink (void)
{
	rGPJ0CON = 0x11111111;
	while(1)
	{
		rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); //全亮
		delay();
		rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); //全灭
		delay();
	}
}

void delay (void)
{
	volatile int i = 90000;
	while(i--);
}
/*
 *文件名:start.s
 *描述:流水灯
 *GPIOJ0_3、4、5
 *需要配置寄存器:GPJ0CON(0xE0200240)、GPJ0DAT(0xE0200244)
 */
#define GPJ0CON     0xE0200240
#define GPJ0DAT     0xE0200244
#define WTCON       0xE2700000
#define SVC_Stack   0xD0037780     

.global _start            @把_start的链接属性改为外部,让其他的程序也能够看到
_start:                   @_start是一个函数的起始地址,也是编译、链接后程序的起始地址。
                          @由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的
# 关闭看门狗	
	ldr r0, =0x0
	ldr r1, =WTCON
	str r0, [r1]
# 设置SVC栈	
	ldr sp, =SVC_Stack
# 调用C语言函数
	bl led_blink
	
	b .
  • 编译报错

    解决:在编译时添加-nostdlib这个编译选项即可解决nostdlib就是不使用标准函数库。标准函数库就是编译器中自带的函数库,用-nostdlib可以让编译器链接器优先选择我程序内自己写的函数库。
  • 神奇的volatile
    volatile的作用是让程序在编译时,编译器不对程序做优化。优化有时候是ok的,但是有时候是自作聪明会造成程序不对。如果你的一个变量是易变的,不希望编译器帮我们做优化,就在这个变量定义时加volatile。==加不加有没有差别,取决于编译器。==如果编译器做了优化则有差异;如果编译器本身没做优化,那就没有差别。在我们这里(编译器是arm-2009q3),实际测试加不加效果是一样的。
  • 使用C语言来访问寄存器的语法
    寄存器的地址类似于内存地址(IO与内存统一编址的),所以这里的问题是用C语言读写寄存器,就是用C语言来读写内存地址。用C语言来访问内存,就要用到指针
    unsigned int *p = (unsigned int *)0x0xE0200240;
    p = 0x11111111;
    上面这两句其实可以简化为1句:
    ((unsigned int *)0x0xE0200240) = 0x11111111;

总结:

  • C和汇编函数的互相调用(函数名和汇编标号的真实意义):函数名和汇编标号都是地址
  • C语法对内存访问的封装方式(使用指针来访问内存的技巧)
  • 汇编的意义(起始代码&效率关键部位)
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值