stm32库函数各类宏定义总结
在各类芯片官方的库函数中,都会遇到大量的宏定义,无论是结构体,还是各类数据量、函数,都用宏来定义,封装了一层实际意义,不用对着datasheet计算各种寄存器的初始值,极大地简化了开发时间。今天就来总结一下在stm32官方库函数中宏定义的作用。
一、宏的表现形式
宏定义的写法:
#define A B
可以理解为,在c程序预编译过程中,程序中所有出现A的地方,都会用B来代替,然后程序再送到编译器进行编译。
二、宏定义的种类
1.普通定义
#define SYSCLK_FREQ_72MHz 72000000
该语句的作用是将数字72000000定义为SYSCLK_FREQ_72MHz,该标识符能直观表示系统时钟为72MHZ。
2.宏函数
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
这看起来复杂,其实就是定义了一个函数BITBAND(addr, bitnum),右边可以理解为关于这两个参数的运算。
宏函数还可以嵌套,比如
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n)
3.条件宏
条件宏的运用也很多,一般在头文件中开头都会出现如下所示语句:
#ifndef __SYS_H
#define __SYS_H
...
#endif
这套语句的主要作用是防止头文件被重复引用,比如,再A.h中#include “B.h”,然后C.c中#include “A.h”,#include “B.h”,如果B.h中没有这套语句,那么B.h就会在C.c中被重复引用,本来一遍就完事了的,硬生生多搞了一遍,导致编译效率低下。如果头文件中还有变量的定义,那么就会导致变量重复定义。
条件宏还有以下表现形式:
#if __sizeof_ptr == 8
#define SIZE_MAX UINT64_MAX
#else
#define SIZE_MAX UINT32_MAX
#endif
理解起来就和普通的if语句一样
#if !defined(__cplusplus)
...
#endif
#ifndef __cplusplus
...
#endif
上面两种表达方式是一样的,如果在c文件当中,它接下来的语句就会被考虑编译。如果在c++文件中,接下来的语句就会被忽略。
三、常见的宏表达
上面介绍了宏在库函数中的集中表现形式,几乎涵盖了大部分定义,但是实际上情况层出不穷,下面从stm32的库函数中摘取各种表现形式进行具体分析。
1.B为表达式的
#define GPIOA_ODR_Addr (GPIOA_BASE+12)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
这个还是非常有意思的,我们知道,在单片机里面,任何的配置都是对寄存器的配置。在stm32里面,寄存器都为32位的。表达一个寄存器的地址也是32位。例如:
*((unsigned long *)(addr)) = 0x01;
就表示将地址addr转化为指针,而最前面的*表示取值。这样,往它赋值0x01,就表示在地址为addr的寄存器中写0x01啦。
2.宏函数的嵌套
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n)
从最后一行往上看,不断替换,其实上面一大段表示:
*((volatile unsigned long *)((((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(n<<2)))))
把后面那个啰嗦的表达式简化为A,为:
*((volatile unsigned long *) A
这下就一目了然了。所以最终的PAout(n)就表示GPIOA的第n个引脚,赋值0或1就表示输出低或高电平。
那么问题又来了,地址A不是表示一个指向32位的地址么,为什么能单独改变一个口呢?其实这是stm32的一个特性,可以理解为将ODR(输出数据寄存器)的每一位都映射成一个寄存器,往这个寄存器写值,相当于改变相应位的值。
3.地址转化
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
这是每个GPIO的标志位。比如BSRR为GPIO的设置清除寄存器。每个GPIO为16个口,那么寄存器中的0~15位就表示这16个口,哪个位为1,相应的口输出就为1.
4.为宏函数
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) ||\
((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))
这个表达式在stm32的官方库中也十分常见,用于判断你的输入是否在规定的值当中。上面表示的就是判断PERIPH是否在GPIOA~GPIOG之间。