目录
1 STM32F103系列芯片的地址映射和寄存器映射原理
1.1 什么是寄存器
1.1.1 基本含义
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。
1.1.2 基本概念
寄存器最起码具备以下4种功能。
①清除数码:将寄存器里的原有数码清除。
②接收数码:在接收脉冲作用下,将外输入数码存入寄存器中。
③存储数码:在没有新的写入脉冲来之前,寄存器能保存原有数码不变。
④输出数码:在输出脉冲作用下,才通过电路输出数码。
仅具有以上功能的寄存器称为数码寄存器;有的寄存器还具有移位功能,称为移位寄存器。
1.2 地址映射和寄存器映射原理
以STM32为例,操作硬件本质上就是操作寄存器。在存储器片上外设区域,四字节为一个单元,每个单元对应不同的功能。当我们控制这些单元时就可以驱动外设工作,我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元。但若每次都是通过这种方式访问地址,不好记忆且易出错。这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名实质上就是寄存器名字。给已分配好地址(通过存储器映射实现)的有特定功能的内存单元取别名的过程就叫寄存器映射。
1.3 STM32系统架构
STM32 芯片是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内核与外设就如同电脑上的 CPU 与主板、内存、显卡、硬盘的关系
-
四个驱动单元(CUP)
Cortex™-M3内核DCode总线
Cortex™-M3内核系统总线System
通用DMA1
通用DMA2 -
四个被动单元(外设)
内部SRAM
内部闪存存储器FLASH
FSMC
AHB到APB的桥,它连接所有的APB外设 -
驱动单元
-
ICode 总线
ICode 中的 I 表示 Instruction,即指令。内核通过ICode 总线读取内部FLASH代码指令来执行程序.。 -
DCode 总线
DCode 中的 D 表示 Data,即数据,那说明这条总线是用来取数的。因为数据可以被 Dcode 总线和 DMA 总线访问(向flash,SRAM,或外设数据寄存器里面取数据),所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数,取到的数据可以暂存在Cortex™-M3内核里面的寄存器在进行处理。 -
系统总线System
系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的。 -
DMA 总线
DMA 总线与DCode总线一样主要是用来传输数据,但Dcode总线传输数据要占用内核(cpu)的资源,而DMA总线相当于独立于内核cpu但帮助内核cpu传输数据而不用占用内核(cpu)的资源,就是在DMA传输数据的同时内核cpu可以干别的事情比如点亮一个LED灯 -
总线矩阵
总线矩阵协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法。因为数据可以被 Dcode 总线和 DMA 总线访问,数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的 FLASH。所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数
-
-
被动单元
-
内部FLASH
简单介绍在flash存储内容:我们写好的程序编译之后都是一条条指令(二进制代码),存放在 FLASH 中,我们常量或常变量C 语言中的 const 关键字修饰也存放在FLASH -
内部SRAM
就是我们常说的电脑内存条,程序函数内部的局部变量和全局变量,堆(malloc分配)栈(局部变量)等的开销都是基于内部的SRAM。内核通过 DCode 总线来访问它 -
FSMC
FSMC 的英文全称是 Flexible static memory controller,叫灵活的静的存储器控制器,是 STM32F10xx 中一个很有特色的外设通过FSMC我们可以扩展内存,如外部的SRAM,NANDFLASH 和 NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的 S:static,不能是动态的内存,比如 SDRAM 就不能扩展。 -
AHB 到 APB 的桥
两个AHB/APB桥在AHB和2个APB总线间提供同步连接。APB1操作速度限于36MHz,APB2操作于全速(最高72MHz),上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备。
-
1.4 STM32的存储空间
stm32芯片内部的总线为32根,内存被划分为一个个的内存单元,每个内存单元的大小是一个字节,为了能有效的访问到内存的每个单元就给内存单元进行编号,编号就被称为该内存单元的地址
2 点亮小灯
2.1 keil建立工程
具体keil软件使用过程见以前文章:
https://blog.csdn.net/apple_52030329/article/details/127146518
- 选择stm32f103c8
- 添加启动文件
为source group 1
添加stm32f103c8t6
添加启动文件
先介绍一下启动代码:启动代码是一段和硬件相关的汇编代码。
主要作用如下:
(1) 堆栈(sp)的初始化
(2) 初始化程序计数器(pc)
(3) 设置向量表异常事件的入口地址
(4) 调用main函数
ST公司提供了许多个个启动文件给我们,分别用于不同容量的STM32芯片
其中,ld.s 适用于小容量 产品;md.s 适用于中等容量产品;hd 适用于大容量产品;
这里的容量是指 FLASH 的大小.判断方法如下
小容量:FLASH≤32K
中容量:64K≤FLASH≤128K
大容量:256K≤FLASH
我们查看一下C8T6的手册
其Flash容量为128k
,属于中容量,因此我们这里采用startup_stm32f10x_md.s
作为启动文件。
将启动文件拷贝到工程文件夹下
找到 Target1
→Source Group1
→双击→设置打开文件类型为 Asm Source file→选择 startup_stm32f10x_hd.s→点击 Add,如下图所示
这里看到的 2 个文件夹:Listings 和 Objects,是 KEIL 自行创建的,用于保存编译过程中生成的一些文件。
添加完之后,得到如下界面
2.2 流水灯的C程序
2.2.1 GPIO端口初始化
-
时钟配置
本次实验使用的引脚是PA5,PB9,PC14
按照stm32手册,找到时钟使能寄存器映射基地址
-
使能对应端口时钟
//----------------APB2使能时钟寄存器 ---------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
RCC_APB2ENR|=1<<2|1<<3|1<<4; //APB2-GPIOA、GPIOB、GPIOC外设时钟使能
2.2.2 输入输出模式
本次实验用的到A5、B9、C14三个引脚。其中A5、B9属于端口配置低寄存器偏移地址为0x00,C14属于端口配置高寄存器偏移地址为0x04。
查表获得CPIO端口基地址
根据此配置对应引脚寄存器
基地址+偏移量
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C04)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
2.2.3 代码实现
C语言实现代码
//--------------APB2使能时钟寄存器------------------------
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ORD *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ORD *((unsigned volatile int*)0x4001100C)
//-------------------简单的延时函数-----------------------
void Delay_ms( volatile unsigned int t)
{
unsigned int i;
while(t--)
for (i=0;i<800;i++);
}
void A_LED_LIGHT(){
GPIOA_ORD=0x0<<5; //PA5低电平
GPIOB_ORD=0x1<<9; //PB9高电平
GPIOC_ORD=0x1<<14; //PC14高电平
}
void B_LED_LIGHT(){
GPIOA_ORD=0x1<<5; //PA5高电平
GPIOB_ORD=0x0<<9; //PB9低电平
GPIOC_ORD=0x1<<14; //PC14高电平
}
void C_LED_LIGHT(){
GPIOA_ORD=0x1<<5; //PA5高电平
GPIOB_ORD=0x1<<9; //PB9高电平
GPIOC_ORD=0x0<<14; //PC14低电平
}
//------------------------主函数--------------------------
int main()
{
int j=100;
RCC_AP2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_AP2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_AP2ENR|=1<<4; //APB2-GPIOC外设时钟使能
//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<4;
GPIOA_CRL&=0xFF0FFFFF; //设置位 清零
GPIOA_CRL|=0x00200000; //PA5推挽输出
GPIOA_ORD|=1<<5; //设置PA5初始灯为灭
GPIOB_CRH&=0xFFFFFF0F; //设置位 清零
GPIOB_CRH|=0x00000020; //PB9推挽输出
GPIOB_ORD|=1<<9; //设置初始灯为灭
GPIOC_CRH&=0xF0FFFFFF; //设置位 清零
GPIOC_CRH|=0x02000000; //PC14推挽输出
GPIOC_ORD|=0x1<<14; //设置初始灯为灭
while(j)
{
A_LED_LIGHT();
Delay_ms(10000000);
B_LED_LIGHT();
Delay_ms(10000000);
C_LED_LIGHT();
Delay_ms(10000000);
}
}
本次实验采用三个灯实现,亮灯状态用1表示,灭灯状态用0表示。
初始状态为0 0 0,
状态一为1 0 0
状态二为0 1 0
状态三为0 0 1
状态三结束后继续进入状态一,一直循环达到流水灯效果。
2.3 流水灯的硬件配置
- 烧录程序
这里我们需要USB转接口来帮助我们完成,接法如上图
同时,需要下载烧录软件,我使用的是mcuisp
其中,烧录时要注意,boot0置1,boot1随意
- 面包板组装电路
如下图所示
注意,在运行程序时,烧录后需要断电,将boot0和boot1都置0
运行效果如下:
总结
经过本次实验熟悉了一遍寄存器方式编程的基本操作,完成了流水灯实验,在学习和实验期间也遇到过许多困难,但经过查阅资料询问老师同学也解决了问题,受益匪浅。
参考
https://blog.csdn.net/weixin_47554309/article/details/120810913
https://blog.csdn.net/qq_53112972/article/details/127153401?spm=1001.2014.3001.5502