STM32寄存器编码最小MVP

STM32寄存器编程指南

STM32编程通常有两种方法:一种是寄存器编程,另一种是固件库编程。寄存器编程是基础,固件库编程是在寄存器编程的基础上升级而来,提供了一种易于学习和开发的编程方法。尽管固件库编程对于项目开发更加简单和快速,但从学习的角度出发,掌握寄存器编程也是非常必要的。

在学习寄存器编程时,我们会从STM32芯片的外观开始,逐步深入到寄存器的操作。最终,通过学习,您应该能够用一句话给寄存器下一个定义。

5.1 STM32芯片外观

我们开发板中使用的芯片是100引脚的STM32F103VET6,如图5-1所示。STM32芯片将带领我们进入嵌入式开发的领域。

在这里插入图片描述

芯片四周是引脚,左下角的小圆点表示引脚1,然后从引脚1起按照逆时针的顺序排列。开发板中把芯片的引脚引出,连接到各种传感器上,通过编程控制这些引脚输出高电平或者低电平,从而控制各种传感器工作。开发板是一种评估板,板载资源丰富,引脚复用较多,力求在一个板子上验证芯片的全部功能。

5.2 芯片里面有什么

STM32芯片是一个封装好的成品,主要由内核(CPU)和片上外设组成。若与电脑类比,内核与外设的关系类似于电脑上的CPU与主板、内存、显卡、硬盘的关系。

内核与片上外设

STM32F103采用的是Cortex-M3内核,内核即CPU,由ARM公司设计。ARM公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商(SOC)如ST、TI、Freescale负责在内核之外设计部件并生产整个芯片。这些内核之外的部件被称为核外外设或片上外设,如GPIO、USART(串口)、I2C、SPI等。具体见下图。

在这里插入图片描述

驱动单元和被动单元

芯片(即内核或CPU)和外设之间通过各种总线连接。驱动单元有4个,被动单元也有4个。为了方便理解,可以将驱动单元理解为CPU部分,而将被动单元理解为外设。

驱动单元
  1. ICode总线:用于读取指令。编写好的程序编译后变成一条条指令,存放在Flash中,内核通过ICode总线读取这些指令来执行程序。ICode总线几乎每时每刻都在使用,是专门用来取指的。

  2. DCode总线:用于读取数据。程序中的数据有常量和变量两种,常量用const关键字修饰,放在内部的Flash中;变量放在内部的SRAM中。为了避免数据访问冲突,DCode总线和DMA总线通过总线矩阵来仲裁决定哪个总线取数。

  3. System总线:主要用于访问外设的寄存器。我们通常所说的寄存器编程,即读写寄存器,都是通过这根系统总线来完成的。

  4. DMA总线:主要用于数据传输。数据可以在某个外设的数据寄存器中,也可以在SRAM中或内部的Flash中。为了避免数据访问冲突,DCode总线和DMA总线通过总线矩阵来仲裁决定哪个总线取数。

被动单元
  1. 内部闪存存储器:即Flash,存放编写好的程序,内核通过ICode总线来取指令。

  2. 内部SRAM:即RAM,存放程序的变量和堆栈,内核通过DCode总线来访问。

  3. FSMC:即灵活的静态存储器控制器(Flexible Static Memory Controller),是STM32F10xx中一个特色外设。通过FSMC,可以扩展外部的SRAM、NAND Flash和NOR Flash,但不能扩展动态内存如SDRAM。

  4. AHB到APB的桥:AHB总线作为一个中央通道连接多个关键组件,从AHB总线延伸出的两条APB2和APB1总线挂载了STM32的各种特色外设,如GPIO、USART、I2C、SPI等。学习STM32的重点是编程这些外设去驱动外部设备。

在这里插入图片描述

5.3 存储器映射

在图5-4中,STM32芯片的被动单元(Flash、RAM、FSMC和AHB到APB的桥)这些功能部件共同排列在一个4GB的地址空间内。我们在编程时,可以通过它们的地址找到它们,并操作它们(通过C语言对它们进行数据的读和写)。

存储器本身不具有地址信息,其地址由芯片厂商或用户分配,给存储器分配地址的过程称为存储器映射。如果给存储器再分配一个地址,就叫存储器重映射。

地址空间划分

在这4GB的地址空间中,ARM已经粗略地将其平均分成了8个块,每块512MB,每个块也都规定了用途,具体分类见下表:

Block起始地址终止地址用途
00x000000000x1FFFFFFF内部Flash
10x200000000x3FFFFFFF内部RAM
20x400000000x5FFFFFFF片上外设
30x600000000x7FFFFFFF外部RAM
40x800000000x9FFFFFFF外部设备
50xA00000000xBFFFFFFF不使用
60xC00000000xDFFFFFFF不使用
70xE00000000xFFFFFFFF系统控制空间

在这8个块中,有3个块非常重要,也是我们最关心的3个块:Block0被设计成内部Flash,Block1被设计成内部RAM,Block2被设计成片上的外设。下面我们介绍这3个Block内的具体区域功能划分。

1. 存储器Block0内部区域功能划分

Block0主要用于片内的Flash存储器。我们使用的STM32F103ZET6(霸道)和STM32F103VET6(指南者)的Flash容量都是512KB,属于大容量。内部集成更大的Flash或SRAM意味着芯片成本增加,ST能在追求性价比的同时做到512KB,实属不易。Block0内部区域的功能划分如下表所示:

区域起始地址终止地址大小用途
主存储区0x080000000x0807FFFF512KB用户程序存储
系统存储区0x1FFF00000x1FFF77FF30KB系统引导加载程序
2. 存储器Block1内部区域功能划分

Block1用于片内的SRAM。我们使用的STM32F103ZET6和STM32F103VET6的SRAM容量都是64KB。Block1内部区域的功能划分如下表所示:

区域起始地址终止地址大小用途
SRAM0x200000000x2000FFFF64KB数据存储
3. 存储器Block2内部区域功能划分

Block2用于片内的外设。根据外设的总线速度不同,Block2被分成了APB和AHB两部分,其中APB又分为APB1和APB2。具体划分如下表所示:

区域起始地址终止地址用途
APB10x400000000x40007FFF低速外设,如定时器、UART
APB20x400100000x40017FFF高速外设,如GPIO、SPI
AHB0x400200000x40023FFF高速外设,如DMA控制器、SDIO

5.4 寄存器映射

存储器映射是给存储器分配地址的过程,那么寄存器映射是给有特定功能的内存单元取别名的过程。寄存器是我们通过编程控制外设的重要接口。

STM32的外设地址映射

片上外设区分为3条总线:APB1、APB2和AHB。APB1挂载低速外设,APB2和AHB挂载高速外设。每条总线的基地址和各外设的基地址如下表所示:

总线基地址说明
APB10x40000000低速外设
APB20x40010000高速外设
AHB0x40020000高速外设
GPIO外设寄存器

以GPIO外设为例,GPIO是通用输入输出端口,基本功能是控制引脚输出高电平或低电平。以下是GPIOB端口的寄存器列表:

寄存器名地址偏移功能说明
GPIOB_CRL0x00配置低8位引脚功能
GPIOB_CRH0x04配置高8位引脚功能
GPIOB_IDR0x08输入数据寄存器
GPIOB_ODR0x0C输出数据寄存器
GPIOB_BSRR0x10位设定/复位寄存器
GPIOB_BRR0x14位复位寄存器
GPIOB_LCKR0x18引脚锁定寄存器

通过这些寄存器,我们可以控制GPIO引脚的状态,实现对外部设备的控制。了解和熟悉这些寄存器的操作,是进行STM32编程的重要基础。

5.5 C语言对寄存器的封装

为了方便寄存器的操作,我们使用C语言的结构体对寄存器进行封装。

封装总线和外设基地址
#define PERIPH_BASE         ((unsigned int)0x40000000)
#define APB2PERIPH_BASE     (PERIPH_BASE + 0x00010000)
#define GPIOB_BASE          (APB2PERIPH_BASE + 0x0C00)
#define GPIOB_ODR           (*(volatile unsigned int *)(GPIOB_BASE + 0x0C))
封装寄存器列表

使用结构体封装GPIO寄存器:

typedef struct {
    volatile unsigned int CRL;
    volatile unsigned int CRH;
    volatile unsigned int IDR;
    volatile unsigned int ODR;
    volatile unsigned int BSRR;
    volatile unsigned int BRR;
    volatile unsigned int LCKR;
} GPIO_TypeDef;

#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

void set_gpio_high(void) {
    GPIOB->ODR = 0xFFFF;  // 设置GPIOB所有引脚为高电平
}

5.6 修改寄存器位操作方法

  1. 清零某一位
a &= ~(1 << n);
  1. 清零某几个连续位
a &= ~(0xF << n);
  1. 对某几位进行赋值
a |= (value << n);
  1. 对某一位取反
a ^= (1 << n);
  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值