5 . 如何用c语言封装寄存器

2023-05-16

前面我们介绍了存储器映射、寄存器和寄存器映射,这些都是为使用 C语言封装寄存器做铺垫。我们通过一个实例来对 C 语言封装寄存器进行介绍。
**实例 **:控制 GPIOC 端口的第 0 管脚输出一个低电平。首先我们需要知道GPIOC 端口外设是挂接在哪个总线上的,然后根据总线基地址和本身的偏移地址得到 GPIOC 外设基地址,最后通过这个外设基地址得到里面各种寄存器基地址。

(1)总线和外设基地址封装
根据寄存器的概念,我们可以使用 C 语言中的宏定义对寄存器进行定义。具体代码如下:

**//定义外设基地址**
#define PERIPH_BASE ((unsigned int)0x40000000)    1)
**//定义 APB2 总线基地址**
#defineAPB2PERIPH_BASE (PERIPH_BASE + 0x00010000)    2)
**//定义 GPIOC 外设基地址**
#define GPIOC_BASE (AHB2PERIPH_BASE + 0x1000)     3)
**//定义寄存器基地址 这里以 GPIOC 为例**
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)     4)
#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)

上述代码中我们在后面备注了数字,下面对其进行简单介绍下其功能:
1)定义外设的基地址,这个地址也是 Block2 的基地址。
2)定义 APB2 总线基地址,因为 Block2 的第一个总线是 APB1,而 APB2 总线地址只需要加上对应的地址偏移量即可。
3)定义 GPIO 外设基地址,因为 GPIOC 是挂接在 APB2 总线上的,所以找到对应的端口地址偏移量即可知道 GPIOC 端口基地址。
4)定义 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 中,使用到变量时一定要进行定义。

(2)寄存器封装
通过前面讲解,我们已经可以对寄存器进行操作,但是还稍有不足,因为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 ,正是代表 CRH所占用的 4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出。**这样的地址偏移与 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 来封装寄存器的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

5 . 如何用c语言封装寄存器 的相关文章

  • Latex数学字体

    默认 mathsf mathtt mathit 花体 mathcal 空心体 mathbb
  • darknet训练自己的数据集

    参考博客https blog csdn net lilai619 article details 79695109 系统环境 Ubuntu 16 04 xff0d xff11 xff0e 在制作数据时最好先将所有图片重新命名 xff0c 这
  • 配置自己的ubuntu深度学习环境(ubuntu16.04)

    安装NVIDIA驱动 https blog csdn net xunan003 article details 81665835 安装CUDA xff08 最好是9 0 xff09 43 cudnn https blog csdn net
  • 用vscode配置C++编译环境(非常简单四步搭建)

    为什么会选择vscode 其实一直想用一下强大的vscode xff0c 最近正好想用C 43 43 刷题 xff0c 于是想在vscode上配置一下C 43 43 的编译环境 xff0c 用舒服的编辑器刷题应该会效率max也会坚持吧 但是
  • openmv底层算法剖析---梦飞openmv前传

    前言 接梦飞openmv博客 xff0c 本篇重点剖析openmv的算法和功能实现 openmv是国外开源团队依托mirco python架构开发的一套基于stm32内核优化算法的图像识别模组 xff0c 其目的是让图像视觉算法应用开发更加
  • notify和notifyall的区别

    文章目录 场景分析例子经典java线程状态流转图 场景 调用wait的线程的唤醒 xff0c 一般通过notify和notifyAll 但是两者之间有什么区别呢 xff1f 分析 线程调用synchronized方法或者synchroniz
  • c++头文件重复引用问题

    https www cnblogs com Dyleaf p 7898167 html
  • 串口通信数据格式

    串行接口的定义 xff1a 串行接口简称串口 xff0c 也称串行通信接口或串行通讯接口 xff08 通常指COM接口 xff09 是指数据一位一位地顺序传送特点是通信线路简单 xff0c 只要一对传输线就可以实现双向通信 xff1b 成本
  • 浅谈 Nyquist–Shannon(奈奎斯特-香农)采样定理

    Nyquist Shannon sampling theorem 总结自 采样定理 Nyquist Shannon 奈奎斯特 香农 采样定理是数字信号处理领域中的一个定理 xff0c 它是连接连续时间信号和离散时间信号的基本桥梁 定理内容
  • 一次内核网络listendrops分析记录

    背景如下 xff1a 手上有一个机顶盒开发板 xff0c 于是想通过adb连接进去进行各种操作 1 机顶盒开机 xff0c 设置静态ip 192 168 10 99 pc的ip 192 168 10 88 eth0 Link encap E
  • [相机标定] 用Kalibr标定diy的双目相机

    由于diy的双目相机没有做时间上的硬件同步 xff0c 想着看看标定一下内外参能不能凑合用 首先试了一下采用ros自带的camera calibration xff0c 感觉还行 xff0c 在 approxiamte sync 61 0
  • 什么是内存对齐?为什么要内存对齐?

    要了解为什么要内存对齐 xff0c 首先我们要了解什么是内存对齐 什么是内存对齐 关于什么是内存对齐 xff0c 我们先来看几个例子 span class hljs keyword typedef span span class hljs
  • linux模拟POST请求

    curl X POST http 172 21 128 170 9000 v2 diskdomain domain construct d 39 34 name 34 34 test1 34 34 desc 34 34 test1 34 3
  • JVM(四) —— 运行时数据区之虚拟机栈的详细介绍

    运行时数据区之虚拟机栈的详细介绍 概述栈运行原理栈中的异常栈空间大小设置栈的内部结构栈中存储的是什么栈帧的内部结构局部变量表slot的理解补充说明 操作数栈操作数栈字节码指令分析 栈顶缓存技术动态链接方法的调用虚方法和非虚方法 invoke
  • JVM —— 运行时数据区之堆的详细介绍(汇总篇)

    JVM 运行时数据区之虚拟机栈的详细介绍 核心概述堆空间代码演示堆空间划分堆空间大小设置OOM介绍和举例年轻代和老年代堆空间大小分配 对象的分配过程对象分配流程图代码演示垃圾回收Minor GC Major GC Full GC年轻代GC
  • 信号量和互斥锁的区别

    背景 多个租户 一个租户一个数据库 执行相同的业务操作 xff0c 该操作涉及到读库和写库 首先拿到了4000个租户 xff0c 为了执行提高执行效率 xff0c 使用线程池 实际生产中每个租户有一堆任务 xff0c 比如 xff1a 每个
  • JVM--栈的运行原理与栈中存储的是什么

    栈中存储什么 xff1f 每个线程都有自己的栈 xff0c 栈中的数据都是以栈帧的格式存在 在这个线程上正在执行的每一个方法都各自对应一个栈帧栈帧是一个内存区块 xff0c 是一个数据集维系着方法执行过程中的各种数据信息 在一条活动线程中
  • C语言 数据结构 之 链式栈

    栈的链式存储结构简称为 链式栈 链式栈是通过单链表来实现的 每次入栈一个元素 xff0c 向链表中添加一个节点 相当于头插法 xff0c 出栈一个元素 xff0c 释放一个节点 链式栈是通过单链表来实现的 每次入栈一个元素 xff0c 向链
  • ROS CMakeLists.txt中的 target_link_libraries

    span class token function target link libraries span span class token punctuation span span class token punctuation span
  • 如何令ros melodic能够兼容ros indigo的message

    问题描述 xff1a 从ros indigo记录下来的rosbag遇到ros melodic就会报以下错误 xff1a Client wants topic xxxxx to have datatype md5sum xxxxx but o

随机推荐