STM32入门系列-使用C语言封装寄存器

STM32入门系列-使用C语言封装寄存器

具体实例:控制 GPIOC 端口的第 0 管脚输出一个低电平。首先我们需要知道GPIOC 端口外设是挂接在哪个总线上的,然后根据总线基地址和本身的偏移地址得到 GPIOC 外设基地址,最后通过这个外设基地址得到里面各种寄存器基地址。

总线和外设基地址封装

根据寄存器的概念,我们可以使用 C 语言中的宏定义对寄存器进行定义。具体代码如下:

//定义外设基地址

#define PERIPH_BASE ((unsigned int)0x40000000)

定义外设的基地址,这个地址也是 Block2 的基地址。

//定义 APB2 总线基地址

#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)

定义 APB2 总线基地址,因为 Block2 的第一个总线是 APB1,而 APB2 总线地址只需要加上对应的地址偏移量即可。

//定义 GPIOC 外设基地址

#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)

定义 GPIO 外设基地址,因为 GPIOC 是挂接在 APB2 总线上的,所以找到对应的端口地址偏移量即可知道 GPIOC 端口基地址。

//定义寄存器基地址 这里以 GPIOC 为例

#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)

#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)

#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)

#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)

#define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)

#define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)

#define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)

定义 GPIO 外设寄存器基地址,这里以 GPIOC 端口为例,因GPIOC_CRL是 GPIOC 外设的第一个寄存器,所以基地址就是 GPIOC 地址,其他寄存器地址只需要在 GPIOC 基地址上加上相应的偏移量即可。

下面对其进行简单介绍下其功能:

我们得到了寄存器具体的地址,那么就可以使用 C 语言指针来操作读写。例如我们需要 GPIOC0 输出一个低电平或者高电平,可以使用下面语句来操作。

//控制 GPIOC 第 0 管脚输出一个低电平

GPIOC_BSRR = (0x01<<(16+0));

//控制 GPIOC 第 0 管脚输出一个高电平

GPIOC_BSRR = (0x01<<0);

我们知道 GPIOC_BSRR 的值是这个寄存器的地址,但是编译器不知道它是地址,而是把它当做立即数,所以我们必须要强制转换为(unsigned int *)指针类型才可以对其操作,这一点特别要注意。然后再在前面加上一个“*”作取指针操作,表示对该地址内内容进行写,读操作也同样使用“*”取指针操作。如下:

unsigned int temp;

temp =GPIOC_IDR;

将寄存器内的数据保存在变量 temp 中,使用到变量时一定要进行定义。

寄存器封装

通过前面讲解,我们已经可以对寄存器进行操作,但是还稍有不足,因为STM32的GPIO比较多, 我们不可能每使用一个GPIO都做前面一样的一大堆定义。根据GPIO寄存器的特点,我们知道不论GPIOA还是GPIOB等都拥有一组功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等,它们只是地址不一样。

 为了更方便地访问寄存器,我们引入C语言中的结构体对寄存器进行封装,具体代码如下:

typedef unsigned int uint32_t; /*无符号 32 位变量*/

typedef unsigned short int uint16_t; /*无符号 16 位变量*/

/* GPIO 寄存器列表 */

typedef struct

{

uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */

uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */

uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */

uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */

uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */

uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */

uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */

}GPIO_TypeDef;

这段代码用 typedef 关键字声明了名为GPIO_TypeDef的结构体类型,结构体内有7 个成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中32位的变量占用4个字节,16 位的变量占用2个字节

于是,我们定义的GPIO_TypeDef,假如这个结构体的首地址为0x4001 1000(这也是第一个成员变量 CRL的地址),那么结构体中第二个成员变量CRH的地址即为0x4001 1000 +0x04,加上的这个0x04,正是代表CRL所占用的4个字节地址的偏移量,(因为CRL占4个字节所以CRH地址偏移了4个单位即0x04)其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出。

这样的地址偏移与STM32 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,比如我们还是将GPIOC0输出低电平,具体代码如下:

GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef型结构体指针GPIOx

GPIOx = GPIOC_BASE; //把指针地址设置为宏 GPIOC_BASE 地址

GPIOx->BSRR =(1<<(16+0)); //通过指针访问并修改 GPIOC_BSRR 寄存器

这段代码先用GPIO_TypeDef类型定义一个结构体指针GPIOx,并让指针指向GPIOC基地址GPIOC_BASE,地址确定下来,然后根据C语言访问结构体的内容,用GPIOx->BSRR写寄存器。为了操作更简便灵活,我们直接使用宏定义好GPIO_TypeDef类型的指针,而且指针指向各个GPIO端口的首地址,使用时我们直接用该宏访问寄存器即可。具体代码如下:

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)

#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)

#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)

#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)

#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)

#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)

GPIOC->BSRR = (1<<(16+0));

我们这里仅仅以GPIO这个外设为例,给大家讲解了如何使用C语言对寄存器封装,对于其他的外设也是使用同样方法。其实到了后面的实验程序的编写,我们都是使用ST公司提供的固件库,他们把STM32所有外设都已经封装好了,我们只需要调用即可。 我们这里分析这个封装过程只是想让大家更加清楚理解如何使用C来封装寄存器的。

转载dilireba

原文地址:https://www.cnblogs.com/zhj868/p/12610503.html

时间: 2024-11-04 19:15:01

STM32入门系列-使用C语言封装寄存器的相关文章

C语言快速入门系列(六)

C语言快速入门系列(五) C语言指针加强 本节引言: 上一节我们对C语言中的指针进行了初步的了解,学习了指针的定义,与普通变量 一维数组,二维数组,字符串之间的使用!在本节中我们将会学习一些新的知识点, 函数,结构体与共用体,枚举类型以及类型定义符typedef! 本节学习路线图: 本节正文 函数 ps:空函数就是什么都不做的函数,开发过程中不需要马上实现的,先写空函数!简单的空函数:void abc(){   } 结构体 共用体: 代码示例2: 建立一个共用体类型,当输入的时学生类型时,要求输

C语言快速入门系列(五)

C语言快速入门系列(五) C语言指针初涉                                           ------转载请注明出处:coder-pig 本节引言: 上一节我们对C语言复合数据类型中的数组进行了解析,在本节中,我们会对C语言复合数据类型中的 重点,C语言的灵魂-----指针进行学习!使用指针的好处:利用指针可以表示与使用复杂的数据结构; 更加方便地使用我们的数组与字符串;可以像汇编语言一样直接处理内存单元地址;可以动态地进行内存空间 分配,C语言指针是重点,同

C语言快速入门系列(七)

C语言快速入门系列(七) C语言指针进阶 本章引言: 在前面第5节中我们对C语言的指针进行了初步的学习理解;作为C语言的灵魂, C指针肯定没那么简单,在这一节中,我们将会对指针进行进一步的学习,比如二级指针, 指针数组,内存分配和const修饰指针常量等!下面就请大家跟随笔者的脚步,对 C指针神秘的一面进行进一步的解析吧! 本节学习路线图: 函数与指针: ①指针作为函数的形参: ②指向函数的指针: ③指针函数: ④带参数的主函数 ps:该代码的运行:先要编译生成exe文件后,来到exe所在文件目

C语言高速入门系列(四)

C语言高速入门系列(四) C语言数组 ---------转载请注明出处:coder-pig 贴心小提示:假设图看不清晰可右键另存为,应该就非常清晰了; 注意上面的代码都要自己过一遍哦! 本节引言: 经过我们前面三个系列的学习,我们对C语言有了一定的了解; 如今要你写这样一个代码应该不难吧: 输入五个学生的成绩,然后求出总和与平均值,打印出结果! 相信大家都会先定义五个变量,用来存储五个学生的成绩,然后再进行计算吧! 可是,假如要求的学生不是5个而是20个,50个或者很多其它,难道你又定义一堆变量

C语言快速入门系列(四)

C语言快速入门系列(四) C语言数组 ---------转载请注明出处:coder-pig 贴心小提示:如果图看不清晰可右键另存为,应该就很清晰了; 注意上面的代码都要自己过一遍哦! 本节引言: 经过我们前面三个系列的学习,我们对C语言有了一定的了解; 现在要你写这样一个代码应该不难吧: 输入五个学生的成绩,然后求出总和与平均值,打印出结果! 相信大家都会先定义五个变量,用来存储五个学生的成绩,然后再进行计算吧! 但是,假如要求的学生不是5个而是20个,50个或者更多,难道你又定义一堆变量么?

C语言快速入门系列(八)

C语言快速入门系列(八) C语言位运算与文件 本章引言: 在不知不觉中我们的C快速入门系列已经慢慢地接近尾声了,而在这一节中,我们会对 C语言中的位运算和文件进行解析,相信这两章对于一些人来说是陌生的,因为很多 老师都会跳过这两个大知识点,其实这两个也是灰常重要的!比如一个问题,叫你算 变量a乘以2,怎么写效率高?直接a *2,很多人都这样写,但是如果你会位运算的话,你会a<<1; 位运算的效率可是比a*2高的哦!另一个问题,不用变量左中间值,直接交换两个变量的值? 你怎么做?也是用到位运算!

C语言高速入门系列(五)

C语言高速入门系列(五) C语言指针初涉                                           ------转载请注明出处:coder-pig 本节引言: 上一节我们对C语言复合数据类型中的数组进行了解析,在本节中,我们会对C语言复合数据类型中的 重点,C语言的灵魂-----指针进行学习!使用指针的优点:利用指针能够表示与使用复杂的数据结构; 更加方便地使用我们的数组与字符串;能够像汇编语言一样直接处理内存单元地址;能够动态地进行内存空间 分配,C语言指针是重点,同

C语言快速入门系列(九)

C语言快速入门系列(九)                                               ---转载请注明出处:coder-pig C语言知识点拾遗 本节引言: C语言系列已经接近尾声了,在前面八节的学习中,我们学会了C的基本语法,基本数据类型, 三种程序结构(顺序,判断,循环),数组,函数,指针,结构体,共用体,位运算,文件等内容, 本节将对前面没有讲的C的遗漏知识点进行补充,当然发现有那些的遗漏的知识点也会进行更新! 谢谢大家一直以来的支持,说了这么多的理论,缺的

C语言快速入门系列(一)

C语言快速入门系列(一)  本系列引言: 本教程的宗旨是将C语言入门的内容进行关键知识点的提纯,将一些笼统的废话去除; 再进行压缩,然后将本章的关键知识点做成路线图的,可以更加方便地掌握学习的方向; 最后提供相关的代码示例以及详细注释,可以帮助学者更快地上手C语言! 如果对本教程有什么建议和缺点纰漏的,欢迎指出,不胜感激! 本节学习路线图: 正文: 1.计算机与程序设计语言的关系: 答:计算机是由硬件与软件系统组成,硬件==>物质基础;软件==>灵魂; 如果脱离了软件,计算机就只是一台什么都做