Cortex-M3学习调试之系统初始化

           在刚开始接触Cortex-M3的时候,对所有未知的东西都进行刨根问底的最后,从而达到从细节掌握知识要点。为了到到真正弄懂,所有决定从程序调试入手:


函数例程如下:

int main(void)
{
SystemInit();//系统初始化函数,由ST公司编写的库文件函数实现,所以我们进入该函数进行分析;
}

void SystemInit (void)
{
  RCC->CR |= (uint32_t)0x00000001;
}

  进入系统初始化函数后,我们看到的第一条语句,会觉得简单,然而具体实现了什么功能,不知道......所以继续跟踪如下....


#define RCC     ((RCC_TypeDef *) RCC_BASE)


对于以上一个宏定义:真正要明白该宏定义的含义,需要掌握以下三个方面的知识点:

预处理命令

    预处理的概念:在编译前的处理, 预处理命令以符号“#”开头。 C语言的预处理主要有三个方面的内容: 1.宏定义; 2.文件包含; 3.条件编译

宏定义

1.不带参数的宏定义:

如:#define 标识符 字符串;其中的标识符就是所谓的符号常量,也称为“宏名”。预处理(预编译)工作也叫做宏展开:将宏名替换为字符串。

说明:
(1)宏名一般用大写
(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如: 数组大小常用 宏定义
(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
(4) 宏定义末尾不加分号;
(5) 宏定义写在函数的花括号外边, 作用域为其后的程序,通常在文件的最开头。
(6)可以用#undef命令终止 宏定义作用域
(7) 宏定义可以嵌套
(8)字符串" "中永远不包含宏
(9) 宏定义不分配内存, 变量定义分配内存。

2、带参数的宏定义

#define 宏名(参数表) 字符串
例如: #define S(a,b) a*b
area=S(3,2);第一步被换为area=a*b; ,第二步被换为area=3*2;
类似于 函数调用,有一个哑实结合的过程:
(1) 实参如果是 表达式容易出问题
#define S(r) r*r
area=S(a+b);第一步换为area=r*r;,第二步被换为area=a+b*a+b;
正确的 宏定义#define S(r) (r)*(r)
(2)宏名和参数的括号间不能有空格
(3)宏替换只作替换,不做计算,不做 表达式求解
(4) 函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存
(5)宏的哑实结合不存在类型,也没有类型转换。
(6)函数只有一个返回值,利用宏则可以设法得到多个值
(7)宏展开使源程序变长, 函数调用不会
(8)宏展开不占运行时间,只占 编译时间函数调用占运行时间(分配内存、保留现场、值传递、返回值)
本文目前只涉及到了不带参数的宏定义,所以针对预处理部分就止于此吧,继续回到宏定义分析.....

类型转换

概念:在赋值和混合表达式中会出现隐式转化,除了隐式转换外,还有显示的转换成为类型转换。
其一般形式为: (类型说明符) ( 表达式 ) 其功能是把表达式的运算结果 强制转换 成类型说明符所表示的类型。
((RCC_TypeDef *) RCC_BASE)
在使用 强制转换时应注意以下问题:
1.类型说明符和 表达式都必须加括号(单个 变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。
2.无论是 强制转换或是自动转换,都只是为了本次运算的需要而对 变量的数据长度进行的临时性转换,而不改变数据说明时对该 变量定义的类型。
在该表达式中RCC_BASE指向了0x4002 1000RCC寄存器的基地址空间:如下图所示:


所以可以知道该表达式实现了把RCC寄存器的基地址转换为RCC_TyoeDef *指针类型的数据类型。


用户自定义数据类型typedef

用途一:

定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:

char* pa, pb; // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针,

// 和一个字符变量;

以下则可行:

typedef char* PCHAR;

PCHAR pa, pb;  

这种用法很有用,特别是char* pa, pb的定义,初学者往往认为是定义了两个字符型指针,其实不是,而用typedef char* PCHAR就不会出现这样的问题,减少了错误的发生。

用途二:
      用在旧的C代码中,帮助struct。以前的代码中,声明struct新对象时,必须要带上
struct,即形式为: struct 结构名对象名,如:

struct tagPOINT1

 {
      int x;

    int y; 
};

struct tagPOINT1 p1;

而在C++中,则可以直接写:结构名对象名,即:tagPOINT1 p1;

typedef struct tagPOINT
{
    int x;

     int y;
}POINT;

POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候,或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。

用途三:

用typedef来定义与平台无关的类型。

比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:

typedef long double REAL;

在不支持 long double 的平台二上,改为:

typedef double REAL;

在连 double 都不支持的平台三上,改为:

typedef float REAL;

也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。

标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健。
     这个优点在我们写代码的过程中可以减少不少代码量哦!

用途四:

为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部

分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化

版。举例: 

 原声明:void (*b[10]) (void (*)());

变量名为b,先替换右边部分括号里的,pFunParam为别名一:

typedef void (*pFunParam)();

再替换左边的变量b,pFunx为别名二:

typedef void (*pFunx)(pFunParam);

原声明的最简化版:

pFunx b[10];
 
      原声明:doube(*)() (*e)[9];

变量名为e,先替换左边部分,pFuny为别名一:

typedef double(*pFuny)();

再替换右边的变量e,pFunParamy为别名二

typedef pFuny (*pFunParamy)[9];

原声明的最简化版:

pFunParamy e;

理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号

就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直

到整个声明分析完。举例:

int (*func)(int *p);

首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针

;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以

func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值

类型是int。

int (*func[5])(int *);

func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明

func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符

优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数

组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。

这种用法是比较复杂的,出现的频率也不少,往往在看到这样的用法却不能理解,相信以上的解释能有所帮助。


在这里用到了用法二,源定义如下:

typedef struct
{
  __IO uint32_t CR; //_IO 是宏定义volatile,表示在程序运行中,该值是变化的。uint_32_t unsiged int型数据,即32位ARM 
  __IO uint32_t CFGR;
  __IO uint32_t CIR;
  __IO uint32_t APB2RSTR;
  __IO uint32_t APB1RSTR;
  __IO uint32_t AHBENR;
  __IO uint32_t APB2ENR;
  __IO uint32_t APB1ENR;
  __IO uint32_t BDCR;
  __IO uint32_t CSR;
#ifdef STM32F10X_CL  
  __IO uint32_t AHBRSTR;
  __IO uint32_t CFGR2;
#endif /* STM32F10X_CL */ 
} RCC_TypeDef;


到此 我们今晚要分析的第一个语句即将完成了,还差最后一步,通过查看STM32F10X的数据手册,如下:


可以看出实现RCC_RC寄存器的bit0位写1,即 使能内部高速时钟。Internal high-speed clock enable


至此,STM32F10X的System_init()函数就可以十分清楚的知道实现了什么功能了。











  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值