当前位置:   article > 正文

基于寄存器的开发、存储器映射以及其他相关,以STM32为例_stm32寄存器库

stm32寄存器库

前言

无论是对STM32还是ZYNQ的开发,基于库函数的开发都有着寄存器开发无法比拟的便捷易懂的优势,但是基于寄存器的开发是嵌入式开发最基础,最底层的技术,了解什么是寄存器、什么是地址映射以及如何基于寄存器进行开发,有着十分重要的意义。

本文讲一下个人的认识,从stm32的片上外设、地址映射、寄存器作用到库函数具体实现的操作,由下到上进行分析,供大家参考。

STM32架构与存储器映射 

我们都知道STM32是ST公司基于ARM内核设计的,整体结构如图1所示。其中Cortex-M3就是ARM核,由ARM公司设计,其他诸如Flash、SRAM、ADC、GPIO等等都是ST公司设计,称为片上外设。

下面是STM32F103与STM32F407的系统结构图,两者表现方法不一样,放一起进行对比可以增加理解,图1中的总线矩阵与图2中的总线矩阵相对应。其中

  • ICode(I总线)表示指令总线
  • DCode(D总线)表示数据总线
  • System(S总线)表示系统总线

图1.STM32F103结构 

 图2.STM32F407结构

我们知道芯片内部由许多存储器,通过修改读取存储器的数据即可实现对外设的控制以及数据的读取功能,但是存储器本身是没有地址信息的,这样我们就没办法准确在众多存储器中定位某一个存储器进行读写。因此,我们需要给每个存储器进行地址分配来实现对存储器的精确控制,这些地址是由芯片厂商或用户分配的,给存储器分配地址的过程就是存储器映射。如果给存储器再分配一个地址就叫存储器重映射。

Crotex-M3有32根地址线,所以它的寻址空间大小为2的32次方,即4GB。而这4GB地址如何分配,是ARM公司设计好的,共分了8块(block),每块512MB,每块具体功能如表1所示,详细地址分配如图3所示。其中,0x40000000到0x5FFFFFFF这512MB的地址分配给片上外设。

图3.存储器映射

序号用途地址范围
Block0Code
0x0000 0000 ~ 0x1FFF FFFF(512MB)
Block1SRAM
0x2000 0000 ~ 0x3FFF FFFF(512MB)
Block2片上外设
0x4000 0000 ~ 0x5FFF FFFF(512MB)
Block3FSMC的bank1、bank2
0x6000 0000 ~ 0x7FFF FFFF(512MB)
Block4FSMC的bank3、bank4
0x8000 0000 ~ 0x9FFF FFFF(512MB)
Block5FSMC寄存器
0xA000 0000 ~ 0xCFFF FFFF(512MB)
Block6保留
0xD000 0000 ~ 0xDFFF FFFF(512MB)
Block7Crotex-M3内部外设
0xE000 0000 ~ 0xFFFF FFFF(512MB)

表1.存储器分块功能

 寄存器

分配给片上外设的地址(0x40000000到0x5FFFFFFF)以4字节(32bit)为一个单位(这也与STM32是32位系统对应),每个单元对应不同的功能,通过控制这些单元内的数据即可实现对外设的操作。我们可以通过C语言指针操作的方式来访问这些单元,但是以地址进行操作不方便记忆,因此,根据每个单元的实现的功能对这些单元取一个别名,这个别名就是我们所说的寄存器。给已经分配好地址的具有特定功能的内存单元取别名的过程就是寄存器映射

根据外设不同,寄存器通常根据实现的功能进行分组,以GPIO为例,如图4所示,主要分了配置寄存器(分高低两组是因为一组无法实现对所有端口的配置)、数据寄存器、位控制寄存器以及锁定寄存器四类。

  • 配置寄存器:用于配置GPIO特定功能,如输入输出以及输入输出的模式。
  • 数据寄存器:保存GPIO输入的数据或将要输出的数据,可通过读取对应地址获取端口数据。
  • 位控制寄存器:设置某位以及为1或0,控制输出电平。
  • 锁定寄存器:用于锁定某引脚,锁定后不能修改其配置。

 图4.GPIO寄存器描述

我们也可以查阅对寄存器的具体描述来了解对寄存器每一位写入数据后可以实现什么样的功能。如图4,我们可以看到GPIO端口配置低寄存器每4位控制一个GPIO引脚(pin), 而4位中前两位控制输入输出模式,后两位控制输出速度并起到选择方向的作用。那么我们对这个寄存器写入0x00000004,即可将pin0设置为浮空输入模式。

图4.GPIO端口配置低寄存器 

通过指针进行的寄存器操作

我们知道了寄存器可以实现的功能,那么我们只需要找到寄存器对应的地址,对相应的地址写入相应的数据,即可使外设实现相应的功能。对地址的查找,通常都是根据基地址与偏移量来确定的。如我们知道片上外设的基地址是0x40000000,GPIO是连接在APB2上的外设,那么我们先查找APB2的地址偏移量,APB2相对于片上外设的偏移量是0x10000,那么APB2的基地址就是0x40010000

而GPIOA的相对于APB2的偏移量是0x0800,那么GPIOA的基地址就是0x40010800,通过图4,我们可以看到,端口配置低寄存器的偏移量是0x00,所以GPIOA的端口配置低寄存器地址就是0x40010800。

查到GPIOA的端口配置低寄存器地址是0x40010800,那么我们只需要对这个地址写入0x00000004,即可将GPIOA0配置为浮空输入模式。代码如下

 *(unsigned int*)(0x4001 0800) = 0x00000004;

这句代码先通过(unsigned int*)将0x40010800进行强制类型转换成地址类型,再通过“ * ”进行解地址来对地址内容进行修改。

我们用到基于库函数的开发就是开发商将地址帮我们封装好了,我们可以通过比较直观的命令进行对寄存器的修改,省去了我们查找寄存器功能和地址的时间。

修改寄存器的位操作

上面我们的操作是直接对整个32位的寄存器进行了赋值,但在实际操作的过程中我们往往只对几位进行操作,因此需要用到对位的清零、赋值与取反操作

对某位清零

我们还以GPIOA的寄存器为例,假设GPIOA_CRL寄存器的初始值为0000 1110(二进制),即

GPIOA_CRL = 0x0e(十六进制),那么当我们需要对第二位清零时,即可通过

GPIOA_CRL &= ~(1<<2);

命令来实现,其中(1<<2)表示将1左移两位,得 0000 0100(二进制),通过 ~ 取反得1111 1011,然后与GPIOA_CRL进行与操作,即可将GPIO_CRL的第二位置零同时保持其他位不变。

对某位赋值

通过移位并进行与操作即可按位进行赋值,例如

GPIOA_CRL |= (1<<4);

即可将GPIO_CRL第四位进行置1操作同时保持其他位不变

对某位取反

通过移位并进行异或操作即可按位进行取反,例如

GPIOA_CRL ^= (1<<4);

 即可将GPIO_CRL第四位进行取反操作同时保持其他位不变

对连续几位进行操作

有时我们会看到如下的指令

GPIOA_CRL &= ~(3<<2*1);

相对于我们前面介绍的多了 “*1” 的操作,这是将GPIOA_CRL中二进制每2个分为了1组,即bit0、bit1 为第 0 组,bit2、bit3 为第 1 组,如此类推,那么将3左移2位得到0000 1100,即可对第一组进行清零操作,因此后面乘1即是对第一组进行操作,乘2就是对第二组进行操作。

基于库函数的开发

前面我们了解了如何基于寄存器对外设进行设置,那么基于库函数的基本原理也就是这样,通过对寄存器的地址写入数据,实现对外设的配置。我们同样来看将GPIOA的pin0设置为浮空输入的过程。对STM32的开发具体流程这里就不细讲了,如先打开外设时钟,创建结构体对外设进行初始化配置这些。首先我们看将GPIO模式设置为浮空输入,具体指令如下

我们右键点击GPIO_Mode_IN_FLOATING,跳转到其定义

这里我们可以看到,浮空输入的对应值是0x04,和我们之前写入的值0x00000004是相同的。简单理解,这个结构体其实就是根据我们选择的模式生成一个对应的数值,如果我们不配置其他,只是将pin0设置成浮空输入,那么生成的值就是0x00000004,而GPIO_Init函数就是将这个值写入我们选好的GPIO对应的地址。现在我们一步步跳转,查看GPIOA的定义

我们可以清晰的看到GPIOA的地址也是根据基地址和偏置,一步步定义得到的。 

结语

以上,就是我对基于寄存器的开发与基于库函数的开发的一些理解,希望能够对大家的嵌入式学习有所启发。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/565900
推荐阅读
相关标签
  

闽ICP备14008679号