对于MCU,一切底层配置,最终都是在配置寄存器

51单片机访问地址

51单片机经常会引用一个reg51.h的头文件。下面看看它是怎么把名字和寄存器联系在一起的:

1 sfr p0=0x80;
2 p0=0x00;

sfr是一种扩充数据类型,使用一个内存单位,值域为0-255.利用它可以访问51单片机内部所有的特殊功能寄存器。前一句“sfr p0=0x80”就是将P0映射到地址0x80。以后对P0的读写操作即使对0x80地址对应的单元进行读写操作;注意:单独的地址是不能进行操作的,必须对地址所对应的变量操作

后一句“p0=0x00”就是往p0地址(0x80)代表的寄存器写值。

STM32访问地址
寄存器地址名称映射
STM32肯定也是可以这样来设置寄存器的。但是由于STM32的寄存器数目太多了,如果以这样的方式列出来,需要很大的篇幅,而且也不方便开发。所以,MDK采用的方式是通过结构体来将寄存器组织在一起。

下面就介绍MDK如何把结构体和地址对应起来的,为什么修改结构体成员变量的值就可以达到操作寄存器的值?这些事情都是在stm32f10x.h文件中完成的。

注:stm32f10x.h文件在STM32固件库下的目录是:

STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x文件夹下。

GPIOA的寄存器地址名称映射

我们通过GPIOA的寄存器为例来进行介绍。

1 GPIOA->ODR=0x00000000;

首先,我们需要看一下GPIOA是个什么东西?通过宏定义我们可以看到:

1 #define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
2 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
3 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
4 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
5 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
6 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
7 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)

GPIOA是一个将GPIOA_BASE强制转换成GPIO_TypeDef的指针。这句话的意思就是,GPIOA指向地址GPIOA_BASE,而GPIOA_BASE存放的数据类型是GPIO_TypeDef。

再看一下结构体GPIO_TypeDef的定义:

 1 typedef struct
2 {
3 __IO uint32_t CRL;
4 __IO uint32_t CRH;
5 __IO uint32_t IDR;
6 __IO uint32_t ODR;
7 __IO uint32_t BSRR;
8 __IO uint32_t BRR;
9 __IO uint32_t LCKR;
10 } GPIO_TypeDef;

结构体里面声明了7个变量,这个时候就明白了“GPIOA->ODR”就是指:GPIOA结构体下的ODR变量。

其实结构体的7个变量就是GPIOA的7个寄存器。我们需要知道GPIOA下的ODR寄存器的地址,首先需要知道的是GPIOA的基地址是怎么计算的呢?

1 #define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
2 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
3 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
4 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
5 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
6 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
7 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)

因为GPIO都是挂载在APB2总线之上的,所以它的基地址是由APB2总线的基地址+GPIO在APB2总线上的偏移地址决定的。

那么APB2总线的基地址是怎么计算的呢?

1 #define PERIPH_BASE       ((uint32_t)0x40000000)
2 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)

原理都是一样的,APB2总线的基地址也是从其他地址进行地址偏移得到的。

所以到这个时候,就可以算出GPIOA的基地址位了:

GPIOA_BASE=0x40000000+0x10000+0x0800=0x40010800

这上面就已经知道了GPIOA的基地址,那么那些GPIOA的7个寄存器的地址又是怎么计算出来的呢?

GPIOA的寄存器的地址=GPIOA基地址+寄存器相对GPIOA基地址的偏移值

寄存器相对于GPIOA基地址的偏移值可以在上面的寄存器地址映射表中查到。稍微解释一下:GPIO的每个寄存器都是32位的,所以每个寄存器是占用4个地址,也就是说一共占用28个地址。地址偏移范围为(000h-01Bh)。这个地址偏移是相对于GPIOA的基地址而言的。

那么你可能又有一个疑问:结构体里面的寄存器又是怎么与地址一一对应的呢?这就涉及到结构体的一个特征,那就是结构体存储的成员的地址是连续的。上面讲到GPIOA是指向GPIO_TypeDef类型的指针,又由于GPIO_TypeDef是结构体,所以自然而然我们就可以算出GPIOA指向的结构体成员变量对应地址了。

总结与分析
对于STM32而言,使用“GPIOA->ODR=0x00000000;”来对寄存器赋值的原理,也就是将GPIO下的所有寄存器放在一个结构体内,通过基地址和在基地址上的偏移地址不断转化,最终找到准确的寄存器实际地址来进行赋值。也就是说,和51单片机最大的不同就是:由于STM32的寄存器数目太多,就将其中控制同一外设的寄存器设置成一个结构体(如GPIO、DMA等),通过对结构体的地址和寄存器相对于结构体的偏移地址,来确定某个特定的寄存器。

原文链接:https://blog.csdn.net/qq_38410730/article/details/79816270

  可以结合博客中《stm32 中库函数、结构体、地址的强制类型转换、相应特殊功能寄存器之间的关系》这篇文章看更能体会到地址映射、为什么使用结构体、库函数中各种赋值是怎么一会事,就像这篇文章开头所说一切的配置就是对于相应的寄存器配置,应为不同的外设中有各种各样的寄存器,我们想要使用这个外设的相应功能,就需要对于这个外设中相应的寄存器进行配置,才能使这个外设进行正常的运行。

  举一个不是很恰当的例子:就像电脑一样你要使用电脑中的word这个软件,对于电脑这个外设就需要配置操作系统,以及显示器、键盘、鼠标这些驱动、安装word后才能正常使用word;而操作系统和安装驱动就是对于能够正常使用word所做的配置;就像对于stm32中外设寄存器配置一样。

【STM32】MDK中寄存器地址名称映射分析的更多相关文章

  1. 关于STM32 MDK中USE_STDPERIPH_DRIVER问题的解释

    初学STM32,在RealView MDK 环境中使用STM32固件库建立工程时,初学者可能会遇到编译不通过的问题.出现如下警告或错误提示: warning: #223-D: function &qu ...

  2. vue 中根据地址名称获取实际经纬度方法

    <div id="container" class="map" style="margin-top:30px; width: 1200px;he ...

  3. Linux0.11 中对地址的管理

    个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节).Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index). Linux中逻 ...

  4. 关于STM32 (Cortex-M3) 中NVIC的分析

    一.STM32 (Cortex-M3) 中的优先级概念 STM32(Cortex-M3)中有两个优先级的概念:抢占式优先级和响应优先级,也把响应优先级称作"亚优先级"或" ...

  5. resultMap自定义映射---8.3.1. 解决列名(表中的字段名称)和实体类中的属性名不一致

    1.1.1.1.      步骤一:将驼峰匹配注释掉 --------------测试完成后仍然 回来开启  其他地方可能用到 一旦注释掉驼峰匹配,那么再通过queryUserById查询的结果中,用 ...

  6. arduino 中通过寄存器地址访问寄存器内容

    void call_func( void (*func)(void)){ (*func)(); } void setup() { // put your setup code here, to run ...

  7. STM32和STR71X移植uCos-II操作系统比较分析

    STM32和STR71X移植uCos-II操作系统比较分析 ——ARM7 TDMI和ARMv7-M Cortex-M3 的异同 STM32F103ZE,大容量,ARMv7-M,Cortex-M3系列, ...

  8. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  9. keil mdk中如何确保某一段程序不被优化掉(转)

    源:keil mdk中如何确保某一段程序不被优化掉 使用mdk编程,假如有一个有用的函数你定义了但是没有显式的调用,mdk在默认方式下,将会把这个函数从整个程序总删除掉,以节省ROM. 比如,你在RO ...

随机推荐

  1. linux中()、[]、{}、(())、[[]]等各种括号的使用

    转至:https://www.jianshu.com/p/b88c7e07aaa9 linux中().[].{}.(()).[[]]等各种括号的使用 1.小括号.圆括号() 1.1 单小括号() 命令 ...

  2. 60天shell脚本计划-3/12-渐入佳境

    --作者:飞翔的小胖猪 --创建时间:2021年2月6日 --修改时间:2021年2月10日 说明 每日上传更新一个shell脚本,周期为60天.如有需求的读者可根据自己实际情况选用合适的脚本,也可在 ...

  3. linux多进/线程编程(4)——进程间通信之pipe和fifo

    前言: Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块 ...

  4. selenium+python操作浏览器

    前面已经把环境搭建好了,下面我们就正式学习selenium的webdriver框架.本篇主要讲如何用Python调用webdriver框架的API,对浏览器做一些基本的操作,如打开.前进.后退.刷新. ...

  5. Zookeeper(1)-安装与基础使用

    Zookeeper 服务端 工作机制 Zookeeper从设计模式角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发 ...

  6. Spring系列16:ApplicationContext扩展国际化

    本文内容 BeanFactory对比ApplicationContext ApplicationContext的扩展能力 国际化 BeanFactory对比ApplicationContext 简单点 ...

  7. Kotlin笔记小结(For Java Developer)

    这篇文章为kotlin学习记录,主要针对的是自己的知识盲区,不适用于新手. 文中所有demo均来自于kotlin官网 类型 整形 Type Size (bits) Min value Max valu ...

  8. Java学习笔记:04面向对象-内部类_访问修饰符_final

    04面向对象-内部类/访问修饰符/final 1.static的介绍 static:关键字,静态的 static的作用是用来修饰类中的成员 2.访问一个类中的某一个成员变量 方法一: _1.创建对象 ...

  9. DirectX11 With Windows SDK--37 延迟渲染:光源剔除

    前言 在上一章,我们主要介绍了如何使用延迟渲染,以及如何对G-Buffer进行一系列优化.而在这一章里,我们将从光源入手,讨论如何对大量的动态光源进行剔除,从而获得显著的性能提升. 在此之前假定读者已 ...

  10. 盘点十大GIS相关算法

    1.道格拉斯-普克算法(Douglas–Peucker) 道格拉斯-普克算法(Douglas–Peucker algorithm,亦称为拉默-道格拉斯-普克算法.迭代适应点算法.分裂与合并算法)是将曲 ...