欢迎来到军工软件开发人才培养基地——学到牛牛

总结STM32开发中常用的C语言知识|技术文章

时间:2024-05-06 07:01:10 来源:学到牛牛

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位取反