一、STM32F103系列芯片
1. 简介
STM32F系列属于中低端的32位ARM微控制器,该系列芯片是意法半导体(ST)公司出品,其内核是Cortex-M3[^1]。 该系列芯片按片内Flash的大小可分为三大类:小容量(16K和32K)、中容量(64K和128K)、大容量(256K、384K和512K)。 芯片集成定时器Timer,CAN,ADC,SPI,I2C,USB,UART等多种外设功能。
详解STM32T103C8T6: STM32代表STM32家族,32位MCU; F代表产品类型为基础型; 103代表特定功能为STM32基础型; C代表引脚数为48&49引脚; 8代表内存容量为64KB; T代表封装为QFP; 6代表温度范围为-40到+85℃。
2. 存储器映射
-
存储器空间 Cortex‐M3 支持4GB 存储空间。整块4G存储器开始地址标为0x0000_0000,结束地址为0xFFFF_FFFF,地址的位数是32位,那么2^32=4,294,967,296。 由于一个基本的存储单元是8bits即1Byte(每个地址对应一个存储单元,这样如果只是访问某一bit就要使用位操作,或者使用位带操作),因此4,294,967,296/1024=4,194,304KB,4,194,304/1024=4096MB,4094/1024=4GB。 -
存储器映射
这4GB的存储空间被划分成8个块,每一块用来与特定功能完成映射。映射关系如图所示。
(某博主的个人理解:这4GB的空间指的是地址空间,每个地址对应一个具体的设备。CPU并不知道每个设备是什么,它所关心的只有地址,获取相应的地址,然后找到地址对应的存储单元或者寄存器,进行读取或者写入数据即可。4GB是它最大支持的地址数目,但是实际可能没有使用那么多。)
序号 | 用途 | 地址范围 |
---|
Block0 | Code | 0x0000 0000 ~ 0x1FFF FFFF(512MB) | Block1 | SRAM | 0x2000 0000 ~ 0x3FFF FFFF(512MB) | Block2 | 片上外设 | 0x4000 0000 ~ 0x5FFF FFFF(512MB) | Block3 | FSMC的bank1~bank2 | 0x6000 0000 ~ 0x7FFF FFFF(512MB) | Block4 | FSMC的bank3~bank4 | 0x8000 0000 ~ 0x9FFF FFFF(512MB) | Block5 | FSMC寄存器 | 0xA000 0000 ~ 0xCFFF FFFF(512MB) | Block6 | 没有使用 | 0xD000 0000 ~ 0xDFFF FFFF(512MB) | Block7 | Cortex-M3内部外设 | 0xE000 0000 ~ 0xFFFF FFFF(512MB) |
在这 8个 Block里面,有 3个块非常重要,也是我们最关心的三个块。Block0用来设计 成内部 FLASH,Block1 用来设计成内部 RAM,Block2 用来设计成片上的外设,下面我们 简单的介绍下这三个 Block 里面的具体区域的功能划分。
Block0 主要用于设计片内的 FLASH,我们使用的 STM32F103ZET6(霸道)和 STM32F103VET6(指南者)的 FLASH 都是 512KB,属于大容量。要在芯片内部集成更大 的 FLASH 或者 SRAM 都意味着芯片成本的增加,往往片内集成的 FLASH 都不会太大,ST 能在追求性价比的同时做到512KB,实乃良心之举。Block内部区域的功能划分具体见下表。
块 | 用途说明 | 地址范围 |
---|
Block0 | 预留 | 0x1FFE C008 ~ 0x1FFF FFFF | Block0 | 选项字节:用于配置读写保护、 BOR 级别、软件/硬件看门狗以及器 件处于待机或停止模式下的复位。当 芯片不小心被锁住之后,我们可以从 RAM 里面启动来修改这部分相应的 寄存器位。 | | Block0 | 系统存储器:里面存的是ST出厂时 烧 写 好 的 isp 自 举 程 序 ( 即 Bootloader),用户无法改动。串口 下载的时候需要用到这部分程序。 | 0x1FFF F000- 0x1FFF F7FF | Block0 | 预留 | 0x0808 0000 ~ 0x1FFF EFFF | Block0 | FLASH:我们的程序就放在这里。 | 0x0800 0000 ~ 0x0807 FFFF (512KB) | Block0 | 预留 | 0x0008 0000 ~ 0x07FF FFFF | Block0 | 取决于 BOOT引脚,为 FLASH、系 统存储器、SRAM 的别名。 | 0x0000 0000 ~ 0x0007 FFFF |
存储器Block1内部区域功能划分
Block1 用 于 设 计 片 内 的 SRAM 。 我 们 使 用 的 STM32F103ZET6 ( 霸 道 ) 和 STM32F103VET6(指南者)的 SRAM 都是 64KB,Block 内部区域的功能划分具体下表。
块 | 用途说明 | 地址范围 |
---|
Block1 | 预留 | 0x2001 0000 ~ 0x3FFF FFFF | Block1 | SRAM 64KB | 0x2000 0000 ~0x2000 FFFF |
Block2 用于设计片内的外设,根据外设的总线速度不同,Block 被分成了 APB 和 AHB 两部分,其中 APB 又被分为 APB1 和 APB2,具体见下表。
块 | 用途说明 | 地址范围 |
---|
Block2 | APB1总线外设 | 0x4000 0000 ~ 0x4000 77FF | Block2 | APB2总线外设 | 0x4001 0000 ~ 0x4001 3FFF | Block2 | AHB总线外设 | 0x4001 8000 ~ 0x5003 FFFF |
3. 寄存器映射
每个寄存器都是32bit,占用4个Byte即4个存储单元。可以把寄存器看作一个特殊的单元,一个这样的单元占32bit,只要找到这个单元的起始地址就可以对其进行操作。
其映射地址 = 外设总基地址(块基地址)+ 总线相对于外设总基地址的偏移 + 具体外设基地址相对于总线基地址的偏移 + 寄存器相对于具体外设基地址的偏移。
直接地址操作访问 以GPIOB_ODR寄存器为例:
我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C(至于这个地址如何找到可以参考–怎么找到某个寄存器的地址?查看数据手册),ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指 针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平。
通过绝对地址访问内存单元
*(unsigned int*)(0x4001 0C0C) = 0xFFFF;
0x4001 0C0C在我们看来是 GPIOB端口 ODR的地址,但是在编译器看来,这只是一个 普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把 它转换成指针,即(unsigned int *)0x4001 0C0C,然后再对这个指针进行 * 操作。 刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存 器的方式来操作。
通过寄存器别名方式访问内存单元
#define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
* GPIOB_ODR = 0xFF;
为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面.
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;
其实以上所讲并不是很清晰明了,笔主在这里推荐一个写得很好的博客 STM32寄存器的简介、地址查找,与直接操作寄存器。
二、流水灯
1. 流水灯实验详悉
板子供电有两种方式: 通过U3 USB-micro接口提供5V供电,然后经过板载的LDO芯片转为VCC3V3;通过P2 接口,即SWD下载接口中的VCC3V3给核心板供电。 核心板上有两个LED,其中一个为电源指示灯PWR,另外一个LED与PC13引脚相连,当PC13置高时,LED灭;当PC13置低时,LED亮; 核心板上的跳线是为了选择启动模式使用。我们为了让程序以主闪存存储器作为启动区域,需要将BOOT0置低,BOOT1随意,此种启动模式是最常用的用户FLASH启动,为默认启动模式; 核心板上的按键为RESET复位按键; P2接口为SWD下载模式对应的引脚接口。
2. Keil新建项目
3. 用C语言和汇编语言分别做流水灯
3.1 C语言
- 新建项目。
- 写入
.c 文件
注:使用的引脚是PA7,PB9,PC15。如果是不一样的请改为自己相应的引脚。
#define RCC_AP2ENR *((unsigned volatile int*)0x40021018)
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ORD *((unsigned volatile int*)0x4001080C)
#define GPIOB_CRH *((unsigned volatile int*)0x40010C04)
#define GPIOB_ORD *((unsigned volatile int*)0x40010C0C)
#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++);
}
int main()
{
int j=100;
RCC_AP2ENR|=1<<2;
RCC_AP2ENR|=1<<3;
RCC_AP2ENR|=1<<4;
GPIOA_CRL&=0x0FFFFFFF;
GPIOA_CRL|=0x20000000;
GPIOA_ORD|=1<<7;
GPIOB_CRH&=0xFFFFFF0F;
GPIOB_CRH|=0x00000020;
GPIOB_ORD|=1<<9;
GPIOC_CRH&=0x0FFFFFFF;
GPIOC_CRH|=0x30000000;
GPIOC_ORD|=0x1<<15;
while(j)
{
GPIOA_ORD=0x0<<7;
Delay_ms(1000000);
GPIOA_ORD=0x1<<7;
Delay_ms(1000000);
GPIOB_ORD=0x0<<9;
Delay_ms(1000000);
GPIOB_ORD=0x1<<9;
Delay_ms(1000000);
GPIOC_ORD=0x0<<15;
Delay_ms(1000000);
GPIOC_ORD=0x1<<15;
Delay_ms(1000000);
}
}
- 配置环境
点击魔法棒:
Output中:
Dialog DLL:DARMSTM.DLL Parameter:-pSTM32F103C8
- 连上串口之后要把板子上的boot0置1,boot1置0,并且要在烧录之前按下reset键
- FlyMcu串口调试
将板子及线路连接好,网上很多教程,建议多看看,但不要看太杂。
看图!
- 实验结果
3.2 汇编语言
- 新建项目,与前面一致 ,稍有不同在于不选择Starup,不然会烧录不成功。
- 写入
.c 文件
RCC_APB2ENR EQU 0x40021018;配置RCC寄存器,时钟,0x40021018为时钟地址
GPIOC_CRH EQU 0x40011004;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOC_ORD EQU 0x4001100c;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOA_CRL EQU 0x40010800;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOA_ORD EQU 0x4001080C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
GPIOB_CRH EQU 0x40010C04;配置GPIOC_CRH寄存器,是端口配置高寄存器,高位的偏移地址为0x04
GPIOB_ORD EQU 0x40010C0C;配置GPIOC_ORD寄存器,是端口输出寄存器,输出由这里控制
Stack_Size EQU 0x00000400;栈的大小
;分配一个stack段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;NOINIT: = NO Init,不初始化。READWRITE : 可读,可写。ALIGN =3 : 2^3 对齐,即8字节对齐。
Stack_Mem SPACE Stack_Size
__initial_sp
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
Reset_Handler
bl LED_Init;bl:带链接的跳转指令。当使用该指令跳转时,当前地址(PC)会自动送入LR寄存器
MainLoop BL LED_ON_C
BL Delay
BL LED_OFF_C
BL Delay
BL LED_ON_A
BL Delay
BL LED_OFF_A
BL Delay
BL LED_ON_B
BL Delay
BL LED_OFF_B
BL Delay
B MainLoop;B:无条件跳转。
LED_Init;LED初始化
PUSH {R0,R1, LR};R0,R1,LR中的值放入堆栈
LDR R0,=RCC_APB2ENR;LDR是把地址装载到寄存器中(比如R0)。
ORR R0,R0,#0x1c;ORR 按位或操作,11100将R0的第二位置1,其他位不变
LDR R1,=RCC_APB2ENR
STR R0,[R1];STR是把值存储到寄存器所指的地址中,将r0里存储的值给rcc寄存器
;上面一部分汇编代码是控制时钟的
;初始化GPIOA部分
LDR R0,=GPIOA_CRL
BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与
LDR R1,=GPIOA_CRL
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOA_CRL
ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOA_CRL
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x80; 二进制为0b1000 0000 ,第7位就是a7引脚的输出电压
LDR R1,=GPIOA_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
;初始化GPIOB部分
LDR R0,=GPIOB_CRH
BIC R0,R0,#0xffffff0f;BIC 先把立即数取反,再按位与,用的是b9,所以把第二位置零
LDR R1,=GPIOB_CRH
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOB_CRH
ORR R0,#0x00000020;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOB_CRH
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x200; 二进制为0b10 0000 0000,第16位就是b9引脚的输出电压
LDR R1,=GPIOB_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
;初始化GPIOC部分
LDR R0,=GPIOC_CRH
BIC R0,R0,#0x0fffffff;BIC 先把立即数取反,再按位与,就是将三十二位全部置零
LDR R1,=GPIOC_CRH
STR R0,[R1]
;上面的代码是初始化CPIOC_CRH
LDR R0,=GPIOC_CRH
ORR R0,#0x20000000;开启的是pc15,所以是2,为0100,是推挽输出模式,最大速度为2mhz
LDR R1,=GPIOC_CRH
STR R0,[R1]
;GPIOC的端口配置高寄存器配置完毕,也就是CPIOA_CRH配置完成,端口的输出模式确定,不使用的都设为复位后的状态,为0100,所以上面处理输出为都是4
;将PC15置1
MOV R0,#0x8000; 二进制为0b1000 0000 0000 0000,第16位就是c15引脚的输出电压
LDR R1,=GPIOC_ORD ;由r1控制ord寄存器
STR R0,[R1] ;将ord寄存器的值变为r0的值
POP {R0,R1,PC};将栈中之前存的R0,R1,LR的值返还给R0,R1,PC
LED_ON_A;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x00 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_A;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x80 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOA_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
POP {R0,R1,PC}
LED_ON_B;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_B;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x200 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOB_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
POP {R0,R1,PC}
LED_ON_C;亮灯
PUSH {R0,R1, LR}
MOV R0,#0x0000 ;二进制为0b0000 0000 0000 0000,第16位为0,后面将作为pc15的输出电压
LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
STR R0,[R1];将r0的值赋予在GPIOC_ORD中
POP {R0,R1,PC}
LED_OFF_C;熄灯
PUSH {R0,R1, LR}
MOV R0,#0x8000 ;二进制为0b 1000 0000 0000 0000,第16位为1,后面将作为pc15的输出电压
LDR R1,=GPIOC_ORD ;将GPIOC的地址赋予r1
STR R0,[R1] ;[]是指对里面的地址操作,所以是将r0的值赋予GPIOC_ORD
POP {R0,R1,PC}
Delay
PUSH {R0,R1, LR}
MOVS R0,#0
MOVS R1,#0
MOVS R2,#0
DelayLoop0
ADDS R0,R0,#1
CMP R0,#330
BCC DelayLoop0
MOVS R0,#0
ADDS R1,R1,#1
CMP R1,#330
BCC DelayLoop0 ;无进位
MOVS R0,#0
MOVS R1,#0
ADDS R2,R2,#1
CMP R2,#15
BCC DelayLoop0
POP {R0,R1,PC}
NOP
END
- 烧录步骤与前面一致。
- 实验结果:
小小的总结
其实实验并不是很复杂,但是在连接实验线路(最小核心板及串口之间)连接时会出现很多小问题。首先可能是器件(面包板、STM32最小核心板、串口等)自身的问题,再者线路连接的稳定问题,在实验过程中需要极大限度的耐心。好好做,加油呀!
参考文献
- STM32F103的存储器映射&寄存器映射
- 百度百科——STM32F103
- STM32F103寄存器方式点亮LED流水灯
- STM32寄存器的简介、地址查找,与直接操作寄存器
- STM32F103C8T6实现流水灯(c语言和汇编两个版本)
|