1. 指针和内存
假如我们知道一个寄存器GPIOH_ODR的地址是 0x4002 1C14, 该寄存器是32bit,低16bit有效,对应着 16 个外部 IO,对应位写0/1代表输出低/高电平。我们可以通过指针去访问该地址,从而控制IO口输出。例如:
unsigned int *p = ( unsigned int * )( 0x4002 1C14 );
*p = 0xFFFF;
亦可简写为:
*(volatile unsigned int *)( 0x4002 1C14) = 0xFFFF;
我们还可以通过宏定义的方式来操作:
#define GPIOH_ODR (*(volatile unsigned int *)(0x4002 1C14))
GPIOH_ODR=0xFFFF;
2. #if
#if和#endif是一组同时使用的,叫做条件编译指令。#if与#define、#include等指令一样是由预处理器这个强大的工具处理的,预处理器可以在编译前处理c程序。在STM32中,相近平台的编程处理相近,一个库可能支持多个平台,但是平台间也有差异,通过条件编译指令进行编译选择。
3. #define
#define是 C语言 和 C++ 中的一个预处理指令,其中的“#”表示这是一条预处理命令·。凡是以“#”开头的均为预处理命令,“define”为宏定义命令,“标识符”为所定义的宏名。#define的部分运行效果类似于word中的ctrl+F替换,与常量const相比有着无法替代的优势。
4. 结构体内存对齐
一组寄存器(同一外设下的不同功能寄存器)具有相同的基地址,不同的偏移地址。比如:每个通用 I/O 端口包括 4 个 32 位配置寄存器( GPIOx_MODER、 GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)、 2 个 32 位数据寄存器(GPIOx_IDR 和GPIOx_ODR)、 1 个 32 位置位/复位寄存器 (GPIOx_BSRR)、 1 个 32 位锁定寄存器(GPIOx_LCKR) 和 2 个 32 位复用功能选择寄存器( GPIOx_AFRH 和 GPIOx_AFRL)。
同组GPIO的基地址相同,但是偏移地址不同。如下图:
常规映射可通过:
#define GPIOH_MODER (*(volatile unsigned int *)(0x4002 1C14 + 0x00 ))
#define GPIOH_OTYPER (*(volatile unsigned int *)(0x4002 1C14 + 0x04 ))
#define GPIOH_OSPEEDR (*(volatile unsigned int *)(0x4002 1C14 + 0x08)
#define GPIOH_PUPDR (*(volatile unsigned int *)(0x4002 1C14 + 0x0C ))
……
此方法可以解决映射,但是操作繁琐不便。
可通过寄存器分组及内存对齐,以组为一个对象抽象结构体,通过内存对齐,确定排序方式及单属性字节长度。通过结构体指针直接访问基地址,即解决映射问题,后期访问也方便。如下图:
5. enum
enum是计算机编程语言中的一种数据类型。枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内。取值范围闭环,并结合其他方式判断取值是否合法。
6. typedef
C语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。起别名的目的不是为了提高程序运行效率,而是为了编码方便,或者简化复杂类型的理解。例如有一个结构体的名字是 student,要想定义一个结构体变量就得这样写:
struct student STU;
或者:
7. 结构体传参
在函数参数传递过程中,如果函数有多个参数需要传递,并且参数之间存在某种关联时,一般定义一个传参结构体进行参数传递。下面我们以GPIO为例进行说明。
每个通用 I/O 端口包括 4 个 32 位配置寄存器( GPIOx_MODER、 GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR),分别控制工作方式、开漏推挽、上拉下拉、工作速度等。
如果需要进行IO口初始化,那么函数最基本需要四个输入参数。比如:
void GPIO_Init(GPIO_TypeDef* GPIOx, int moder, int otyper, int ospeedr, int pupdr )
更好的方式采用:
// 传参结构体
// 函数定义
8. 位运算
6.1 位清零操作(某些位清零,其他位不变)
GPIOH_ODR &= ~(1<<10); // 第10位清零
GPIOH_ODR &= ~(3<<10); // 第11、12位清零
GPIOH_ODR &= ~((1<<10)|(3<<15)); //第10、15、16位清零
6.2 位置一操作(某些位置一,其他位不变)
GPIOH_ODR |= (1<<10); // 第10位置一
GPIOH_ODR |= (3<<10); // 第11、12位置一
GPIOH_ODR |= ((1<<10)|(3<<15)); //第10、15、16位置一
6.3 寄存器赋值操作
寄存器位经过上面的清零操作后,接下来就可以方便地对某几位写入所需要的数值了,且其它位不变。
GPIOH_ODR |= (2<<10); // 第11、12位赋值为2(先清零)
6.4位取反操作
某些情况下,我们需要对寄存器的某个位进行取反操作,即 1 变 0 , 0 变 1,这可以直接用如下操作,其它位不变。
GPIOH_ODR ^= (1<<10); // 第10位取反