为了进一步提高编程效率,我们还可以使用结构体对地址与操作位进行包装,使得程序更加方便编写。
一般的地址宏定义都放在头文件内,而不是main.c文件内,因此我们需要将这些#define地址移到一个自建的头文件中。首先在User文件夹中新建一个头文件,将其命名为STM32F407ZGT6.h,建立步骤与新建*.c文件类似,单击“File”→“New”→“Save As”菜单命令,将之另存为STM32F407ZGT6.h,将main.c里面的#define宏定义全部剪切到头文件中,如图2-18所示。
图2-18 新建STM32F407ZGT6.h头文件
此时main.c中的地址宏定义已被删除。为了使用这些宏定义,我们在main.c的开头要增加一行#include″STM32F407ZGT6.h″,如图2-19所示,这与C语言完全一致。
图2-19 修改后的main.c程序
修改完毕后,该程序实际上与原来的程序并没有任何不同的地方,只是程序更规范了。读者可以编译下载看看其是否与之前程序的运行效果一样。
按原来的宏定义只能使用GPIOF,这显然是不现实的。为了更方便地使用其他GPIO,这里建立宏定义结构体,修改STM32F407ZGT6.h,将原来的程序删除,输入如下程序:
这里首先说明volatile。在C语言里,volatile影响编译器编译的结果。volatile指出变量是随时可能发生变化的。与volatile变量有关的运算不要进行编译优化,以免出错。由之前的#define宏定义,__IO uint32_t MODER相当于volatile unsigned int MODER,意思是定义一个32位的变量MODER,而volatile告诉编译器MODER是随时可能发生变化的。每次使用MODER的时候必须从MODER的地址中读取,而不要去优化,因为若优化了,有可能MODER的地址会被优化,这样会造成地址变动。
另一个关键知识点是结构体。上述代码将GPIO的几个寄存器“打包”成了结构体。所谓结构体,就是将一些已知的数据类型放在一起来定义的一种数据类型。但结构体并没有创造出新的数据类型,它只是将一些相同或者不同数据类型的对象组织起来变成一个整体而已。
比如建立一个学生档案,它有姓名、性别、年龄三个表格,档案袋则可以认为是一个结构体,姓名、性别、出生年月这三项是其成员。这三个成员的数据类型不同,而且毫无关系,但是它们被组织在了档案袋这个结构体下,例如:
与使用变量一样,如果要装入张三档案,就需要先定义张三的档案袋(相当于做一个档案袋且里面放了三张表格)。其定义方法与定义变量类似。例如,“档案张三”。
有了档案袋,我们还要填写里面的三张表格,定义方法为“结构体名.成员”。例如:
张三.姓名=张三;张三.性别=男;张三.年龄=20;
例如:
这个过程定义好了一个结构体,叫作FILE。结构体成员有数组、字符型变量和整型变量。建立一个结构体本身并不占用内存,只有具体定义了对象之后才开辟内存。
现在要使用Tom和Ben两个同学的结构体,必须先定义:(www.xing528.com)
FILE Tom,Ben;
这样相当于建立两个结构体,一个结构体是Tom,另一个结构体是Ben,它们都有三个成员,并且为这两个结构体创建了内存空间,而且结构与大小一样。
要对Tom同学输入名字信息:Tom.name=Tommy;。
要对Tom同学的性别输入信息:Tom.sex=F;。
要对Tom同学的年龄输入信息:Tom.age=20;。
这样处理数据非常有条理,相当方便,程序可读性很高,而且可以将不同类型的数据组织在一起,而数组则做不到这一点。
如果使用结构体的指针,则语法变成结构体指针变量→成员名即ps→name(ps代表指针)。
使用时可以使用结构体的名称,也可以使用地址指针。
回到程序中,对结构体GPIO_TypeDef进行解读。这段代码相当于建立了一个名为GPIO_TypeDef的结构体。可以看到,结构体内有32位与16位长度的寄存器名称,如果定义了一个这样的结构体。会在内存开辟相应的内存空间,这些内存地址是连续的,读者可以观察注释部分的偏移量,它与首地址的偏移量正好是这些变量的长度。比如MODER的类型是unsigned int,长度是4个字节,它的下一个变量OTYPER与首地址之间的偏移量刚好是0x04。再比如变量BSRRH,它前面有6个4字节的unsigned int变量,有1个双字节的unsigned short变量,一共是26个字节,偏移量0x1A对应的十进制数正好是26。
经过对结构体的处理,只要把GPIOx的首地址指向结构体的首地址,各个GPIO的控制寄存器就自动对齐了,而无须一个一个去手动映射寄存器地址。例如:
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
这段代码的含义是定义一个结构体GPIOA,而且将GPIOA_BASE这个首地址指向结构体GPIOA的首地址。这里的GPIOA不仅仅是结构体的名字,也是结构体的指针,因为之前有#define GPIOA(GPIO_TypeDef*)GPIOA_BASE,已经将GPIOA定义成指针型变量了。
这时main.c可以写成下面的形式:
编译成功后下载观察,该程序的运行效果与之前的程序运行效果完全一致,将LED点亮了。
若要实现闪烁效果,可以加入延时函数,修改程序如下:
此时的红色LED已经可以闪烁了,如果想控制PF7引脚上面的绿色LED灯,只需要把Pin_6改成Pin_7即可。
进一步地,如果想控制另一个GPIO,那么也非常简单,比如使用PA3这个引脚输出闪烁效果,将程序修改如下:
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。