下面这期博客我们来讲一下如何使用寄存器来配置STM32的端口模式
我们想要STM32的PA15引脚配置为输出推挽模式,输出速率为50MHz

首先需要找到控制PA15的位,分别是CNF15[1:0]和MODE15[1:0]
CNF控制端口是输入模式还是输出模式,MODE控制输出模式的速率
我想想要配置的是:PA15 输出推挽模式 速率为50MHz
所以CNF我们要选00,MODE我们要选11
放到寄存器的位上面就是:00 11
使用寄存器的方式来编写,就是:
GPIOA->CRH &= (0X0FFFFFFF);
GPIOA->CRH | = (0X30000000);
下面我们来具体解释一下上面的代码:
GPIOA->CRH,CRH是一个寄存器,这句代码的意思就是:
取出GPIOA中的CRH寄存器
GPIOA->CRH &= (0X0FFFFFFF);
这句代码的作用是对高四位(总共是32位,每个字母代表四位)进行清零操作
GPIOA-CRH |= (0X30000000);
对高四位进行赋值操作,让CNF15[1:0] = 0,MODE15[1:0] = 3;
这样的话,我们就完成了寄存器的配置,
成功将PA15配置成为了输出推挽模式了
上面的这种方式呢,我们是使用位操作的方式去配置STM32的端口
我们不难发现,这种方式有点麻烦,要通过一个一个位的来数
那有没有一种比较简单的方式来操作寄存器呢?有的,那就是我们的位域
在下面,我们将使用位域来操作寄存器,让PA15的端口模式为:输出推挽,输出速率50MHz

看上面的代码,我们定义了名称为CRH_Bits的位域,
这些位域的成员(例如:MODE8 CNF8)和寄存器的位一一对应
MODE8 CNF8这些都是每个域的名字,
后面的"2"表示的是所占位的大小
所有的位加起来,是一个uint32_t 的长度
我们定义了这样的一个位域之后,就可以直接使用位域来操作我们的寄存器了
看下面的代码:
((CRH_Bits*)(&GPIOA->CRH))->CNF15 = 0;
((CRH_Bits*)(&GPIOA->CRH))->MODE15 = 3;
通过上面的代码,我们成功实现了PA15的端口模式为:输出推挽,输出速率50MHz
这种方式是使用位域来操作寄存器的方式来实现的
下面我们具体来看一下解析:
首先是取出CRH寄存器的地址 (&GPIOA->CRH)
将CRH寄存器的地址转换成CRH_Bits这种位域指针类型的
最后使用这种类型的位域指针向位域中的一个成员CNF15赋值为0
同样的方法对MODE15赋值为3
这就是位域,它有点结构化数据解析的意思
原来很多无差别的位,通过位域的定义,就具有了一种结构化信息。
它还和结构体不一样,它是一直能够在"位"这样一个力度级别上的结构化
寄存器和位域具有符合性。寄存器基本上都是按位来进行功能划分的,
而C语言中的位域正好可以用来定义这种结构。
所以我们说,位域是单片机和嵌入式底层驱动的灵魂
那么它是一开始就是用于嵌入式和单片机的底层驱动的吗?
实际上并不是,C语言中的位域,一开始设计出来是为了节省内存的
我们都知道,C语言中是没有位类型的,比如布尔类型这种典型的位类型
布尔类型也就是只有0和1两种取值的类型,用来表示真和假
在C语言中,我们通常使用typedef uin8_t bool,把一个uin8_t类型当做布尔类型来使用
其实这是很浪费内存的(一个字节,有8个位,但是我们只使用到了其中的一个位)

这个时候,我们可以使用g_flags.flag来集中管理程序中的逻辑变量
它相当于是把一个uin8_t的类型切成8份来使用
每当我们有一个位的需求时,就可以只使用一个位而没有必要去使用一个字节
这样就大大节省了我们的内存消耗
这也是位域的一个好处