前面文章介绍了存储器映射、寄存器和寄存器映射,这些都是为了介绍使用 C语言封装寄存器做铺垫。这里我们通过一个实例来对 C 语言封装寄存器进行介绍。

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

总线和外设基地址封装

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

 1    //定义外设基地址
2
3    #define PERIPH_BASE ((unsigned int)0x40000000) 1)
4
5 //定义 APB2 总线基地址
6
7 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 2)
8
9 //定义 GPIOC 外设基地址
10
11 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 3)
12
13 //定义寄存器基地址 这里以 GPIOC 为例
14
15 #define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00) 4)
16
17 #define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
18
19 #define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
20
21 #define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
22
23 #define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
24
25 #define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
26
27 #define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)

上述代码中我们在后面备注了数字,下面对其进行简单介绍下其功能:

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

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

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

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

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

1    //控制 GPIOC 第 0 管脚输出一个低电平
2
3 GPIOC_BSRR = (0x01<<(16+0));
4
5 //控制 GPIOC 第 0 管脚输出一个高电平
6
7 GPIOC_BSRR = (0x01<<0);

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

1 unsigned int temp;
2 temp =GPIOC_IDR;

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

寄存器封装

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

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

 1    typedef unsigned int uint32_t; /*无符号 32 位变量*/
2
3 typedef unsigned short int uint16_t; /*无符号 16 位变量*/
4
5 /* GPIO 寄存器列表 */
6
7 typedef struct
8
9 {
10
11 uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
12
13 uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
14
15 uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
16
17 uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
18
19 uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
20
21 uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
22
23 uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
24
25 }GPIO_TypeDef;

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

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

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

1 GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef型结构体指针GPIOx
2
3 GPIOx = GPIOC_BASE; //把指针地址设置为宏 GPIOC_BASE 地址
4
5 GPIOx->BSRR =(1<<(16+0)); //通过指针访问并修改 GPIOC_BSRR 寄存器

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

 1    #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
2
3 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
4
5 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
6
7 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
8
9 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
10
11 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
12
13 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
14
15 GPIOC->BSRR = (1<<(16+0));

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

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

  1. STM32入门系列-存储器与寄存器介绍

    介绍两部分内容: 什么是存储器映射 什么是寄存器及寄存器映射 为了让大家对存储器与寄存器有一个更清楚的认识,并且为之后使用 C 语言来访问 STM32 寄存器内容打下基础.等明白了如何使用 C 语言封 ...

  2. 【Go语言入门系列】Go语言工作目录介绍及命令工具的使用

    [Go语言入门系列]前面的文章: [保姆级教程]手把手教你进行Go语言环境安装及相关VSCode配置 [Go语言入门系列](八)Go语言是不是面向对象语言? [Go语言入门系列](九)写这些就是为了搞 ...

  3. STM32入门系列-库目录及文件介绍

    已经介绍了过了CMSIS标准,ST公司按照这个标准设计了一套基于STM32F10x的固件库,我们可以直接在ST公司的官网进行下载,现在给大家STM32最新固件库v3.5,在网盘上给大家提供了下载包,链 ...

  4. STM32入门系列-创建寄存器模板

    介绍如何使用 KEIL5 软件创建寄存器模板, 方便之后使用寄存器方式来操作STM32开发板上的LED,让大家创建属于自己的寄存器工程模板. 获取工程模板的基础文件 首先我们在电脑任意位置创建一个文件 ...

  5. STM32入门系列-学习STM32要掌握的内容

    STM32芯片架构 STM32F103系列芯片的系统架构如下: STM32芯片基于ARM公司的Cortex-M3内核,由ST公司设计生产,内核与总线矩阵之间有I(指令).S(系统).D(数据)三条信号 ...

  6. STM32入门系列-启动文件介绍

    在启动文件内部使用的都是汇编语言,这个文件的作用是负责执行微控制器从"复位"到"开始执行 main 函数"中间这段启动时间所必须进行的工作.它完成的具体工作有: ...

  7. STM32入门系列-STM32时钟系统,时钟使能配置函数

    之前的推文中说到,当使用一个外设时,必须先使能它的时钟.怎么通过库函数使能时钟呢?如需了解寄存器配置时钟,可以参考<STM32F10x中文参考手册>"复位和时钟控制(RCC)&q ...

  8. STM32入门系列-使用库函数点亮LED,LED初始化函数

    要点亮LED,需要完成LED的驱动, 在工程模板上新建一个led.c和led.h文件,将其存放在led文件夹内.这两个文件需要我们自己编写. 通常xxx.c文件用于存放编写的驱动程序,xxx.h文件用 ...

  9. STM32入门系列-CMSIS标准

    使用寄存器点亮开发板上LED,这种开发方式显然是不适合大众,对于STM32这样庞大的芯片,内部寄存器实在太多,如果操作的外设比较多,那么就需要花很多时间查询底层寄存器内容,而且即使程序写好,如果要换其 ...

随机推荐

  1. nginx+tomcat集群方法

    下载地址:wget http://nginx.org/download/nginx-1.16.1.tar.gz 解压:tar -zxvf 预编译 nginx+tomcat集群方法: 进入nginx配置 ...

  2. 转载:tensorflow slim模块用法

    https://www.cnblogs.com/hellcat/p/8058092.html

  3. 记录jmeter使用beanshell断言获取复杂的json字符串参数值

    实战示例 测试场景 电商系统经常会涉及到商品的库存数量的压测,在用户下单前需要先做库存余量的判断,当余量不足时用户无法下单,保证商品的有效售卖 库存余量查询响应结果 响应结果一般是json字符串的形式 ...

  4. 主键生成器效率提升方案|基于雪花算法和Redis控制进程隔离

    背景 主键生成效率用数据库自增效率也是比较高的,为什么要用主键生成器呢?是因为需要insert主表和明细表时,明细表有个字段是主表的主键作为关联.所以就需要先生成主键填好主表明细表的信息后再一次过在一 ...

  5. Django-发送注册、忘记密码邮件验证-send_mail

    用户邮箱注册.发送验证码流程图 那,如何解决? 1.  setting配置邮箱参数 # 邮箱设置,需要在邮箱中开启smtp服务 # 提供服务的主机域名 EMAIL_HOST = 'smtp.163.c ...

  6. Linux系统编程—信号集操作函数

    先来回顾一下未决信号集是怎么回事. 信号从产生到抵达目的地,叫作信号递达.而信号从产生到递达的中间状态,叫作信号的未决状态.产生未决状态的原因有可能是信号受到阻塞了,也就是信号屏蔽字(或称阻塞信号集, ...

  7. 温故知新——C++--封装

      参考: 1.https://blog.csdn.net/cherrydreamsover/article/details/81942293 2.https://www.cnblogs.com/ji ...

  8. 联赛模拟测试12 C. sum 莫队+组合数

    题目描述 分析 \(80\) 分的暴力都打出来了还是没有想到莫队 首先对于 \(s[n][m]\) 我们可以很快地由它推到 \(s[n][m+1]\) 和 \(s[n][m-1]\) 即 \(s[n] ...

  9. 多测师讲解 _教师(必备)_高级讲师肖sir

    教学心得1.备课要充分,防止第二天上课会出现一些突发情况2.上课要有自己的思路,不一定要按照课件上的讲3.上课气氛比较沉闷的时候,可以适当的开下玩笑,缓解大家的学习氛围4.讲课的时候提醒学员不要做笔记 ...

  10. linq 整理(前序)

    前言 对linq进行整理,分为前序.中序和后序. 前序就是一些简单的概念和模拟. 中序的话就是深挖一些思想. 后序对其进行解刨. 正文 语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# ...