/*这是一个笔记文本,用来以分块的形式记录STM32 各个模块的启动原理及步骤*/
#include "stm32f10x.h"
//这是必要的头文件,用来引用stm32f1系列的官方库
/*接下来是GPIO的初始化*/
GPIO_InitTypeDef xxx; //(其中xxx可以自行命名)
/*好,接下来会用到结构体的知识。因为STM32中的寄存器每个操作位所对应的 绝对地址有一定规律可寻(每个递增4字节,刚好与某些数据类型的大小所对应), 而结构体中的申明变量也是一个连续的空间,所以用结构体来编程比较方便*/
//来我们看一下库文件里面的GPIO_InitTypeDef结构体的内容:
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_MODE;
}GPIO_InitTypeDef;
/*我们可以看到,除了第一个元素之外,GPIO的初始化的结构体对速度和模式的选择 进行了套娃,所以我们继续深入,看看GPIOSpeed_TypeDef结构体里面到底有什么*/
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
/*这是一个枚举类型,已经属于C语言的范畴了,如果对此生疏的话,我们来看看它的语法: 格式:enum 枚举名 {枚举元素1,枚举元素2,…}; 花括号最后面的分号不要忘记了!
好的,开始正题。我们来举一个例子: 一星期有7天,如果不用枚举,我们需要使用#define来为每一个整数来定义一个别名
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
这个代码量看起来就比较多,这时我们就可以使用枚举来改进:
enum DAY
{
MON = 1,TUE,WED,THU,FRI,SAT,SUN
}
有趣的是,第一个枚举成员的默认值为整型的0,后续枚举成员的值再前一个成员上加1。 我们在这个地方把第一个枚举成员的值定义为1,则第二个就是2,以此类推。
更进一步,我们可以在定义枚举类型时改变枚举元素的值:
enum season
{spring,summer = 3,autumn,winter}
此时spring为0,autumn和winter分别为4和5
最后,说一下STM32里面经常看到的枚举变量的定义和一个使用示例: 1.先定义枚举类型,再定义枚举变量。
enum DAY
{
MON = 1,TUE,WED,THU,DRI,SAT,SUN
};
enum DAY day
2.定义枚举类型的同时定义枚举变量
enum DAY
{
MON = 1,TUE,WED,THU,FRI,SAT,SUN
}day;
3.省略枚举名称,直接定义枚举变量
enum
{
MON = 1,TUE,WED,THU,FRI,SAT,SUN
}day;
示例:
enum DAY
{
MON = 1,TUE,WED,THU,FRI,SAT,SUN
};
void UseIt()
{
enum DAY day; //声明一个枚举变量
day = WED; //给枚举变量赋值
printf("%d",day);
return 0;
}
最终会得到一个为3的结果
枚举就说到这里,关于更多的知识(如遍历问题、switch中的使用和整数转换请 参考菜鸟手册:https://www.runoob.com/cprogramming/c-enum.html) */
/*说完了枚举类型的基本语法,我们回头看一下速度设置的枚举类型。 我们可以看到库文件中的枚举类型又使用了typedef用法,这个用法在 百度上的解释是:将一个自己命名的类型用已经有的类型来代替,同时 typedef有很多用法,其中最多的用法就是为数据类型,同时也方便程序员写代码。
例如typedef int ababa;
那么,ababa就相当于int,
可以这样使用:
ababa a = 1;
好,回到代码来。 我们可以现在就可以知道,GPIOSpeed_TypeDef就相当于
enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
};
这样,我们就能顺理成章地理解它的用法: GPIOSpeed_TypeDef GPIO_Speed; 即创建了一个名字叫GPIO_Speed的枚举类型,里面 包含有那三个变量。可以看出,当不打出GPIOSpeed_TypeDef xxx这句话时,就不会创建 那个枚举类型,也不会把它写进内存。
然后,我们再次回到包含着枚举类型的结构体里面(GPIO_InitTypeDef) 不要绕晕了哦233
可以看到,stm32f10x_gpio.h在结构体里面对GPIO_Speed这个枚举类型进行了创建的操作 此时,GPIO_Speed还没有具体的赋值。
那么,这个枚举类型该怎么赋值呢?好,我们回到调用GPIO_InitTypedef这个结构体的地方。
*/
GPIO_InitTypeDef xxx;
//到此为止,我们创建了名字为xxx的包含有一个uint16类型(已经在某处被define过了,请在MDK中自行按F12寻找,不用MDK的当我没说),名字为GPIO_Pin的变量、一个GPIO_SPeed的枚举类型和一个GPIO_Mode的枚举类型;一箭多雕的感觉是不是很爽?在如此套娃之下你永远不知道你的一行代码会创建多少变量、占用多少内存。
xxx.GPIO_Pin = GPIO_Pin_5;
xxx.GPIO_Mode = GPIO_Mode_AF_PP;
xxx.GPIO_Speed = GPIO_Speed_50MHz;
/*那么这些语句的右值最终是怎么来的呢?当然是通过#define 来定义的啦。那为什么又找不到这些右值所对应的数值呢? 众所周知,所谓的函数库就是用来辅助操作寄存器的一些代码,而寄存器的绝对地址这么多,肯定不可能一一列举完,所以 就使用了基地址相加偏移量等各种神奇的数据结构一类的魔法来缩减代码量,写好计算公式让单片机自己来计算。于是,我们就 可以紧接在初始化GPIO的时候看到如下代码:*/
GPIO_Init(GPIOB, &xxx);
//我也不知道这个函数是怎样实现的,但是知道这个函数是给初始化结构体取地址值用的
/*我们可以直接利用F12快捷键对GPIOB进行查看,可以看到它来自于这行代码:*/
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
//所以GPIO_Mode和GPIO_Speed的枚举值(GPIO_Mode_AF_PP、GPIO_Speed_50MHz等)的偏移量是来源于GPIOB的基地址。 //又通过相同的方法,我们可以看到GPIO_Pin5的定义代码:
#define GPIO_Pin_5 ((uint16_t)0x0020
/*每个GPIO口(GPIOA-E)都有许多的Pin,所以我们推理得到这个0x0020只是为了给某个基地址相加用的,即每个相同的 GPIO_Pin都对应于相同的GPIO(A-E)基地址偏移量(这句话听不懂的话建议去翻数据手册康康)
综上,需要初始化GPIO的某一个引脚只需要在引用官方库齐全的情况下输入如下代码:
*/
**GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //选择Pin5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化引脚 GPIO_InitTypeDef GPIO_InitStructure;//定义结构体**
分析了这么多,最后做一个总结吧。在我们初学STM32的时候特别容易陷入一个钻牛角尖的误区, 就比如去钻研GPIO_Init()这个函数是怎么实现的。而等我们搜集了很多资料之后终于弄明白了, 却发现别人已经比自己的进度快了一大截,这就得不偿失了。根据二狗学长所说的「黑盒原理」, 处于初学者的我们只需要弄懂输入什么,输出什么就可以了。就像这个函数,我们只需要知道它需 要被指定一个GPIOx和一个已经初始化好的结构体的指针作为形参就可以起到初始化引脚的作用了。 仅此而已,点到为止。 当我们已经处于大师的段位的时候,再回头看这些函数的实现,是不是会比作为初学者的我们来研究 更节省时间呢?或许这就是牙学长口中的“上下上原则”吧。
希望我们学习32的思路更加清晰明了。
文本格式的草稿下载: