本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
.c文件与.h文件
.C:写具体函数(函数体)
.H:是.C的头文件,里面包含函数的定义,可以让其他.C函数调用。(函数声明,结构体,头文件等)
一般都在头文件中进行函数,变量声明,宏声明,结构体声明,而在C文件中去进行变量定义,函数实现。
所以,.h文件可以被多个.c文件引用(头文件),可以一改全改。
include的过程完全可以"看成"是一个文件拼接的过程。
- 在h文件中可以写函数体,只要在任何一个C文件包含此.h文件就可以将这个函数编译成目标文件的一部分(但编译是以C文件为单位的,如果不在任何C文件中包含此.h文件的话,这段代码就形同虚设)
- 一般都在h文件中进行函数,变量声明,宏声明,结构体声明,在C文件中去进行变量定义,函数实现
- 声明告诉编译器这些符号的含义
- 声明在回答who的问题,定义回答how的问题
- “定义”兼具“声明”的功能,任何变量、函数必须先声明后使用(否则编译器找不到)
- 编译是以c文件为单位的,编译器是不会编译h文件的
- #include本身只是一个简单的文件包含预处理命令,即用include的文件的内容代替该行语句
ifndef,define,endif
作用:防止重复定义产生错误
#ifndef _feed_dog_h //如果到目前为止还没有定义过“_feed_dog_h”这个宏
#define _feed_dog_h //则定义“_feed_dog_h”这个宏
extern void feed_dog(void); //声明一个外部函数
#endif //“#ifndef”到此结束
.h文件编译
在一个项目中,会有.h文件被多次引用,这样.h文件就会被放到多个.c文件中被多次编译,我们要尽量避免这样的多次声明.从而提高效率。
上面的D.h文件中就会重复出现两个int a();的声明,这样就有点重复了,这时条件编译宏就派上了用场:
这样就不会重复定义了。
部分名词解释
总线:信息的传递通道,连接各种外设和芯片,详细的介绍可以看stm32的总线AMBA、AHB、APB。
一般说来,有系统总线AHB和外设总线APB。
变量声明和变量定义
变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
变量声明:用于向程序表明变量的类型和名字(这句话可以理解成,变量声明告诉编译器某个符号的类型)。
定义也是声明:当定义变量时我们声明了它的类型和名字。
extern是声明不是定义:通过使用extern关键字声明变量名而不定义它,表示extern变量的定义不在这个文件中,请到其它.c文件中寻找。
变量在使用前必须被被定义或者声明。
在一个程序中,变量只能定义一次,却可以声明多次。
定义分配存储空间,而声明不会。
函数的声明与定义
函数定义的时候自带声明的性质
函数的声明告诉编译器,函数有哪些传入参数,分别是什么类型,函数有返回值吗,是什么类型。函数的定义告诉编译器函数如何实现
两者区别起来比较简单,带有{ }的就是定义,否则就是声明。
结构体
声明结构体类型格式
格式:
struct 结构体名
{
成员列表;
}变量名列表;
实例:
struct _GPIO
{
int TMODER;
int Tser;
}
注:变量名列表结构体声明的结构体变量,可不写,以后再定义
例如:struct _GPIO age,number;//就定义了两个结构体变量age和number
定义结构体变量后,结构体成员变量的引用方法是
例如我们上面定义了的age结构体变量,我要输出他的成员变量Tser,那么应该写为:
printf("%d",age.Tser);//也就是 结构体变量名.成员变量名,没什么特殊的
定义结构体指针变量,依然基于上面的_GPIO结构体 定义结构体指针变量:
struct _GPIO *age;
是的,只是结构体变量名前面加了*,但是要访问结构体变量成员时应该这样写:
printf("%d",age->Tser);
初始化一个外设
这里 PPP 代表任意外设。
- 在主应用文件中,声明一个结构 PPP_InitTypeDef,例如:
PPP_InitTypeDef PPP_InitStructure;
这里 PPP_InitStructure 是一个位于内存中的工作变量,用来初始化一个或者多个外设 PPP。
- 为变量 PPP_InitStructure 的各个结构成员填入允许的值。可以采用以下 2 种方式:
a)按照如下程序设置整个结构体
PPP_InitStructure.member1 = val1;
PPP_InitStructure.member2 = val2;
PPP_InitStructure.memberN = valN;
/* where N is the number of the structure members */
以上步骤可以合并在同一行里,用以优化代码大小:PPP_InitTypeDef PPP_InitStructure = { val1, val2,.., valN}
b)仅设置结构体中的部分成员:这种情况下,用户应当首先调用函数 PPP_SturcInit(..)来初始化变量PPP_InitStructure,然后再修改其中需要修改的成员。这样可以保证其他成员的值(多为缺省值)被正确填入。
PPP_StructInit(&PPP_InitStructure);
PP_InitStructure.memberX = valX;
PPP_InitStructure.memberY = valY;
/*where X and Y are the members the user wants to configure*/
-
调用函数 PPP_Init(..)来初始化外设 PPP。
-
在这一步,外设 PPP 已被初始化。可以调用函数 PPP_Cmd(..)来使能之。
PPP_Cmd(PPP, ENABLE);
可以通过调用一系列函数来使用外设。每个外设都拥有各自的功能函数。更多细节参阅 Section3 外设固件概述。
注:
- 在设置一个外设前,必须调用以下一个函数来使能它的时钟:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_PPPx, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_PPPx, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PPPx, ENABLE);
- 可以调用函数 PPP_Deinit(..)来把外设 PPP 的所有寄存器复位为缺省值: PPP_DeInit(PPP)
- 在外设设置完成以后,继续修改它的一些参数,可以参照如下步骤:
PPP_InitStucture.memberX = valX;
PPP_InitStructure.memberY = valY; /* where X and Y are the only
members that user wants to modify*/
PPP_Init(PPP, &PPP_InitStructure);
工程模板
1、core_cm4.h //内核功能的定义,比如NVIC相关寄存器的结构体和Systick配置;
2、core_cm4_simd.h //包含与编译器相关的处理
3、core_cmFunc.h //内核核心功能接口头文件
4、core_cmInstr.h //包含一些内核核心专用指令
5、startup_stm32f40_41xxx.s //devices vector table for MDK-ARM toolchain
6、stm32f4xx.h //contains all the peripheral register's definitions, bits definitions
//and memory mapping for STM32F4xx devices
7、system_stm32f4xx.c //contains the system clock configuration for STM32F4xx devices,
//关键函数SystemInit(),系统启动文件startup_stm32f40_41xxx.s里面调用
8、stm32f4xx_conf.h //里面包含了我们需要使用的标准库,对于需要使用的库需要在其中加以说明include,
//stm32f4xx.h里面可以看到对其include
9、stm32f4xx_it.c //provides template for all exceptions handler and
// peripherals interrupt service routine.
10、main.c //主程序
11、led.c //对于关键使用硬件的驱动配置文件