STM32(二):STM32工作原理

0、参考

1、寄存器和存储器基本概念

在计算机系统中,寄存器和存储器是两个重要的概念,它们在数据存储和处理过程中扮演着不同但互补的角色。本文将详细讨论寄存器和存储器的区别与联系。

(1)基本概念

寄存器(Register)是一种速度极快的小容量存储单元,通常集成在CPU内部,用于暂存数据和指令。寄存器可以在一个CPU时钟周期内被读取或写入。

存储器(Memory)通常指随机存取存储器(RAM),是一种速度较慢但容量较大的存储设备,用于存储正在执行的程序和数据。存储器分为主存储器(如DRAM)和辅助存储器(如硬盘、SSD)。

(2)主要区别

速度

复制代码
寄存器:速度最快,通常是计算机中最快的存储设备,因为它们直接集成在CPU内部,能够在一个时钟周期内进行读写操作。
存储器:速度相对较慢,即使是快速的DRAM,其访问速度也比寄存器慢一个数量级以上。

容量

复制代码
寄存器:容量非常小,通常在几个字节到几百字节之间。这是因为寄存器的制造成本高,且CPU内部空间有限。
存储器:容量较大,现代计算机的RAM通常在几GB到几十GB之间,而辅助存储器则可达到TB级别。

位置和功能

复制代码
寄存器:位于CPU内部,主要用于临时存储指令和数据,参与运算操作。例如,累加器(Accumulator)、程序计数器(Program Counter)和状态寄存器(Status Register)。
存储器:位于CPU外部,通过系统总线与CPU连接。用于存储当前运行的程序和数据,是操作系统和应用程序的运行空间。

功能与用途

复制代码
寄存器:用于快速存取需要立即处理的数据,是CPU执行指令的关键组成部分。例如,寄存器用于存储操作数、结果、地址和控制信息。
存储器:用于存储大量数据和程序,是计算机系统的主要数据存储设备。例如,操作系统、应用程序以及用户数据都存储在存储器中。
(3)联系

虽然寄存器和存储器在性能和用途上有显著区别,但它们之间存在密切的联系:

数据交换

复制代码
寄存器和存储器之间需要频繁的数据交换。CPU从存储器中读取指令和数据到寄存器,进行处理后,再将结果存回存储器。这种数据交换是通过系统总线和高速缓存(Cache)实现的。

层次结构

复制代码
寄存器、缓存、主存储器(RAM)和辅助存储器(如硬盘、SSD)构成了计算机的存储层次结构。寄存器在最上层,速度最快但容量最小;辅助存储器在最底层,速度最慢但容量最大。这种层次结构保证了在成本和性能之间的平衡。

性能优化

复制代码
现代计算机系统通过多级缓存(L1、L2、L3)来优化寄存器和存储器之间的数据交换,从而提升整体性能。缓存存储器介于寄存器和主存储器之间,存储经常使用的数据和指令,减少对较慢存储器的访问次数。
(4)实际应用中的案例

CPU指令执行过程

复制代码
在执行一条指令时,CPU首先从存储器中取出指令(Fetch),将其存入指令寄存器(Instruction Register)。然后,指令译码器(Decoder)将指令译码,并根据指令类型从存储器或其他寄存器中获取操作数。接下来,执行单元(如算术逻辑单元ALU)对操作数进行运算,并将结果存入目标寄存器或存储器中。

高性能计算

复制代码
在高性能计算(HPC)中,寄存器和存储器的有效利用对于提高计算效率至关重要。通过优化寄存器分配和存储器访问模式,可以显著提升计算速度。例如,使用寄存器重命名技术可以避免寄存器资源冲突,从而提高指令级并行性。
(5)总结

寄存器和存储器在计算机系统中扮演着不同但互补的角色。寄存器负责快速数据存取和处理,而存储器则提供大量数据和程序的存储空间。通过高效的数据交换和存储层次结构,计算机系统在性能和成本之间找到了平衡。这种分工和协作是现代计算机系统高效运行的基础。

(6)一些名词解释

存储器:

  • FLASH:闪存存储器,我们编写好的程序就放在这个地方;
  • SRAM:即我们通常说的RAM,程序的变量,堆栈等的开销都是基于内部的SRAM;
  • ROM:只读存储器;

2、STM32指南者板子-存储器区域

总划分
Block0
Block1
Block2

3、STM32指南者板子-寄存器区域(片子上是存储器的BLOCK2这块区域)

在存储器Block2这块区域,设计的是片子的外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能, 当我们控制这些单元时就可以驱动外设工作。(使用板子的时候就是用这一块来控制外设)

我们可以找到每个单元的起始地址,然后通过C语言指针的操作方式来访问这些单元, 如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名, 这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

(1)片上外设基地址

即存储器Block2的首地址

bash 复制代码
0x40000000
(2)总线基地址

片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1挂载低速外设,APB2和AHB挂载高速外设。 相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中APB1总线的地址最低,片上外设从这里开始,也叫外设基地址。

(3)GPIO外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为"XX外设基地址",也叫XX外设的边界地址。

以GPIO这个外设来讲解外设的基地址,GPIO属于高速的外设 ,挂载到APB2总线上

(4)GPIO外设寄存器地址

GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占四个字节,在该外设的基地址上按照顺序排列, 寄存器的位置都以相对该外设基地址的偏移地址来描述。

以GPIOB端口为例,说明GPIO都有哪些寄存器
有关外设的寄存器说明可参考《STM32F10xx参考手册》中具体章节的寄存器描述部分,在编程的时候我们需要反复的查阅外设的寄存器说明。

总结
c 复制代码
/* 外设基地址 */
#define PERIPH_BASE           ((unsigned int)0x40000000)

/* 总线基地址 */
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x00020000)


/* GPIO外设基地址 */
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE            (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE            (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE            (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE            (APB2PERIPH_BASE + 0x2000)


/* 寄存器基地址,以GPIOB为例 */
#define GPIOB_CRL             (GPIOB_BASE+0x00)
#define GPIOB_CRH             (GPIOB_BASE+0x04)
#define GPIOB_IDR             (GPIOB_BASE+0x08)
#define GPIOB_ODR             (GPIOB_BASE+0x0C)
#define GPIOB_BSRR            (GPIOB_BASE+0x10)
#define GPIOB_BRR             (GPIOB_BASE+0x14)
#define GPIOB_LCKR            (GPIOB_BASE+0x18)

4、C语言使用封装寄存器

因为GPIOA ~ GPIOG每一个都有GPIOXXX_CRL ~ GPIOXXX_LCKR,因此在C语言使用定义的时候可以用结构体封装好:

(1)定义结构体GPIO_TypeDef
c 复制代码
//寄存器的值常常是芯片外设自动更改的,即使CPU没有执行程序,也有可能发生变化
//编译器有可能会对没有执行程序的变量进行优化

//volatile表示易变的变量,防止编译器优化,
#define     __IO    volatile
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

// GPIO 寄存器结构体定义
typedef struct
{
    __IO uint32_t CRL;       // 端口配置低寄存器,     地址偏移0X00
    __IO uint32_t CRH;       // 端口配置高寄存器,     地址偏移0X04
    __IO uint32_t IDR;       // 端口数据输入寄存器,   地址偏移0X08
    __IO uint32_t ODR;       // 端口数据输出寄存器,   地址偏移0X0C
    __IO uint32_t BSRR;      // 端口位设置/清除寄存器,地址偏移0X10
    __IO uint32_t BRR;       // 端口位清除寄存器,     地址偏移0X14
    __IO uint32_t LCKR;      // 端口配置锁定寄存器,   地址偏移0X18
} GPIO_TypeDef;;
(2)定义外设基地址
c 复制代码
/* 外设基地址 */
#define PERIPH_BASE           ((unsigned int)0x40000000)

/* 总线基地址 */
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x00020000)


/* GPIO外设基地址 */
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE            (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE            (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE            (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE            (APB2PERIPH_BASE + 0x2000)


/*RCC外设基地址:时钟用*/
#define RCC_BASE      (AHBPERIPH_BASE + 0x1000)
(3)使用GPIO_TypeDef把地址强制转换成指针
c 复制代码
#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)
#define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)

// RCC 外设声明
#define RCC                 ((RCC_TypeDef *) RCC_BASE)

/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR      *(unsigned int*)(RCC_BASE+0x18)
(4)则访问任意端口的某个寄存器
c 复制代码
/*使用定义好的宏直接访问*/
/*访问GPIOB端口的寄存器*/
GPIOB->BSRR = 0xFFFF;       //通过指针访问并修改GPIOB_BSRR寄存器
GPIOB->CRL = 0xFFFF;        //修改GPIOB_CRL寄存器
GPIOB->ODR =0xFFFF;         //修改GPIOB_ODR寄存器

uint32_t temp;
temp = GPIOB->IDR;          //读取GPIOB_IDR寄存器的值到变量temp中

/*访问GPIOA端口的寄存器*/
GPIOA->BSRR = 0xFFFF;
GPIOA->CRL = 0xFFFF;
GPIOA->ODR =0xFFFF;

uint32_t temp;
temp = GPIOA->IDR;          //读取GPIOA_IDR寄存器的值到变量temp中

5、C语言常用位操作

对某一位清零a &= ~(1<<2)

以变量a代表寄存器,并假设寄存器中本来已有数值,此时我们需要把变量a的某一位清零,且其它位不变

c 复制代码
//定义一个变量a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;

//对bit2 清零

a &= ~(1<<2);

//括号中的1左移两位,(1<<2)得二进制数:0000 0100 b
//按位取反,~(1<<2)得1111 1011 b
//假如a中原来的值为二进制数: a = 1001 1111 b
//所得的数与a作"位与&"运算,a = (1001 1111 b)&(1111 1011 b),
//经过运算后,a的值 a=1001 1011 b
// a的bit2 位被清零,而其它位不变。
对某一位取反a ^=(1<<6)

对寄存器的某个位进行取反操作,即 1变0 ,0变1,这可以直接用如下操作,其它位不变

c 复制代码
//a = 1001 0011 b
//把bit6取反,其它位不变

a ^=(1<<6);
//a = 1101 0011 b
对某几位清零a &= ~(3<<2*1)
c 复制代码
//定义一个变量a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;
//1、分组
//若把a中的二进制位分成2个一组
//即bit0、bit1为第0组,bit2、bit3为第1组,
//  bit4、bit5为第2组,bit6、bit7为第3组
//  同理,第4~15的高位由于a=0x009f,所以都为0,也是按此方法排列
//2、对第1组的bit2、bit3清零
a &= ~(3<<2*1);//!!!2:以2个为一组,1:第1组,3:清零工具11(二进制)!!!
//括号中的3左移两位,(3<<2*1)得二进制数:0000 1100 b
//按位取反,~(3<<2*1)得1111 0011 b
//假如a中原来的值为二进制数: a = 1001 1111 b
//所得的数与a作"位与&"运算,a = (1001 1111 b)&(1111 0011 b),
//经过运算后,a的值 a=1001 0011 b
// a的第1组的bit2、bit3被清零,而其它位不变。

//上述(~(3<<2*1))中的(1)即为组编号;如清零第3组bit6、bit7此处应为3
//括号中的(2)为每组的位数,每组有2个二进制位;若分成4个一组,此处即为4
//括号中的(3)是组内所有位都为1时的值;若分成4个一组,此处即为二进制数"1111 b"

//例如对第2组bit4、bit5清零
a &= ~(3<<2*2);
对某几位进行赋值a |= (1<<2*2)

寄存器位先经过上面的清零操作后,再对某几位写入所需要的数值,且其它位不变

c 复制代码
//先清零
//a = 1000 0011 b
//此时对清零后的第2组bit4、bit5设置成二进制数"01 b "

a |= (1<<2*2);//1:赋值工具"01 b ",2:以2个为一组,1:第1组
//a = 1001 0011 b,成功设置了第2组的值,其它组不变

6、GPIO-寄存器版

(1)GPIO七个寄存器
bash 复制代码
    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 */
(2)GPIO八个工作模式
bash 复制代码
    GPIO_Mode_AIN = 0x0,           // 模拟输入
    GPIO_Mode_IN_FLOATING = 0x04,  // 浮空输入
    GPIO_Mode_IPD = 0x28,          // 下拉输入
    GPIO_Mode_IPU = 0x48,          // 上拉输入
    GPIO_Mode_Out_OD = 0x14,       // 开漏输出
    GPIO_Mode_Out_PP = 0x10,       // 推挽输出
    GPIO_Mode_AF_OD = 0x1C,        // 复用开漏输出
    GPIO_Mode_AF_PP = 0x18         // 复用推挽输出
(3)如何查找GPIO对应的功能
  • F:\BaiduNetdiskDownload\A盘(资料盘)野火STM32F103指南者_开发板规格书.pdf
    如PB0和PB1分别接着LED绿灯和蓝灯:
(4)如何查找GPIO对应的工作模式
  • F:\BaiduNetdiskDownload\A盘(资料盘)\3-STM32官方资料\STM32F1官方手册资料\1-STM32F10x-中文参考手册.pdf
    若想让PB0对应的绿灯亮起,则应设置PB0的低电平有效,设置GPIOB_CRL为输出推挽模式,在代码中,我们先把控制PB0的端口位清0,然后再向它赋值"0001 b",从而使GPIOB0引脚设置成输出模式,速度为10M。
c 复制代码
// 清空控制PB0的端口位
GPIOB_CRL &= ~( 0x0F<< (4*0));//1111,4组,第0组;注意:低4位哦,所以只清空低4位,别的不要动!!!
// 配置PB0为通用推挽输出,速度为10M
GPIOB_CRL |= (1<<4*0);//0001,4组,第0组
// PB0输出低电平
GPIOB_ODR &= ~(1<<0);//0001,4组,第0组;管脚对于位写1 gpio 管脚为高电平,写 0 为低电平,1生效,0无效

注:以 STM32F103ZET6 芯片为例子,该芯片共有 144 脚芯片,包括7个通用目的的输入/输出口(GPIO)组,分别为GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF、GPIOG,同时每组 GPIO 口组有 16 个 GPIO 口。通常简略称为PAx、PBx、PCx、PDx、PEx、PFx、PGx,其中 x 为0-15,也就是端口的0-15所有引脚,PB0中的0代表CNF0+MODE0要设置,同理还有ODR0。

另外要记得开启时钟:

c 复制代码
// 开启 GPIOB 端口 时钟
RCC_APB2ENR |= (1<<3);
相关推荐
HQB20264 小时前
EG1198|屹晶微电子 120V 内置 MOS 异步降压 DC‑DC(高性价比)
嵌入式硬件·硬件工程
嵌入式软硬件攻城狮4 小时前
9.读写DDR测试
fpga开发
果壳science5 小时前
张祥前实验如何获取正电荷
笔记·科技·嵌入式硬件·硬件工程·制造
沉淀的.晴天6 小时前
can总线
stm32
桀人6 小时前
认识AT24C02
stm32·单片机·嵌入式硬件
国科安芯7 小时前
星间激光通信终端CANFD控制网络的时延确定性与辐射可靠性
网络·嵌入式硬件·信息与通信·canfd·低轨卫星·商业航天·抗辐射
一直C7 小时前
嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动
linux·驱动开发·嵌入式硬件·学习·imx6ull
传感器与混合集成电路8 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
comyccccc8 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
幻智星科技8 小时前
多通道数据采集DMA设计详解
fpga开发·udp·硬件架构