软件:MDK-KEIL 5.0 、mcusip V0.993
芯片:STM32F103C8T6
在上图 中,被控单元的FLASH,RAM,FSMC 和AHB 到APB 的桥(即片上外设),这些功能部件共同排列在一个 4GB 的地址空间内。我们在编程的时候,可以通过他们的地址找到他们,然后来操作他们(通过 C 语言对它们进行数据的读和写)。
在这4G的地址空间中,ARM已经品均分成了8 个块,如以下图中,每块 512MB,每个块也都规定了用途。每个块的大小都有 512MB,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而已。
存储器功能分类:
序号 | 用途 | 地址范围 |
---|---|---|
Block 0 | Code | 0x0000 0000 ~ 0x1FFF FFFF(512MB) |
Block 1 | SRAM | 0x2000 0000 ~ 0x3FFF FFFF(512MB) |
Block 2 | 片上外设 | 0x4000 0000 ~ 0x5FFF FFFF(512MB) |
Block 3 | FSMC 的 bank1 ~ bank2 | 0x6000 0000 ~ 0x7FFF FFFF(512MB) |
Block 4 | FSMC 的 bank3 ~ bank4 | 0x8000 0000 ~ 0x9FFF FFFF(512MB) |
Block 5 | FSMC 寄存器 | 0xA000 0000 ~ 0xCFFF FFFF(512MB) |
Block 6 | 没有使用 | 0xD000 0000 ~ 0xDFFF FFFF(512MB) |
Block 7 | Cortex-M3 内部外设 | 0xE000 0000 ~ 0xFFFFFFFF(512MB) |
在这 8 个 Block 里面,有 3 个块非常重要,也是我们最关心的三个块。
Block0 用来设计成内部 FLASH
Block0 主要用于设计片内的 FLASH ,我们使用的STM32F103ZET6 (霸道)和STM32F103VET6(指南者)的 FLASH 都是 512KB,属于大容量。要在芯片内部集成更大的 FLASH 或者 SRAM 都意味着芯片成本的增加,往往片内集成的 FLASH 都不会太大,ST能在追求性价比的同时做到512KB,实乃良心之举。
块 | 用途 | 地址范围 |
block0 | ||
预留 | 0x1FFE C008 ~ 0x1FFF FFFF | |
选项字节: 用 于 配 置 读写 保 护 、 BOR 级别、软件/硬件看门狗以及器 件处于待机或停止模式下的复位。当 芯片不小心被锁住之后,我们可以从 RAM 里面启动来修改这部分相应的 寄存器位。 | 0x1FFF F800 - 0x1FFF F80F | |
系统存储器:里面存的是ST 出厂时 烧 写 好 的 isp 自 举 程 序 ( 即 Bootloader),用户无法改动。串口 下载的时候需要用到这部分程序。 | 0x1FFF F000- 0x1FFF F7F | |
预留 | 0x0808 0000 ~ 0x1FFF EFFF | |
FLASH:我们的程序就放在这里。 | 0x0800 0000 ~ 0x0807 FFFF (512KB) | |
预留 | 0x0008 0000 ~ 0x07FF FFFF | |
取决于 BOOT 引脚,为 FLASH、系 统存储器、SRAM 的别名。 | 0x0000 0000 ~ 0x0007 FFF |
储存器 Block1 内部区域功能划分
Block1 用 于 设 计 片 内 的 SRAM.我 们 使 用 的 STM32F103ZET6( 霸 道 )和STM32F103VET6(指南者)的 SRAM 都是 64KB。
块 | 用途 | 地址范围 |
block1 | ||
预留 | 0x2001 0000 ~ 0x3FFF FFFF | |
SRAM 64KB | 0x2000 0000 ~0x2000 FFFF | |
储存器 Block2 内部区域功能划分
Block2 用于设计片内的外设,根据外设的总线速度不同,Block 被分成了 APB 和 AHB两部分,其中 APB 又被分为 APB1 和 APB2。
块 | 用途 | 地址范围 |
block2 | ||
APB1 总线外设 | 0x4000 0000 ~ 0x4000 77FF | |
APB2 总线外设 | 0x4001 0000 ~ 0x4001 3FFF | |
AHB 总线外设 | 0x4001 8000 ~ 0x5003 FFFF |
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么寄存器映射?寄存器到底是什么?
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解),ODR 寄存器是 32bit,低 16bit有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指
针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平。
例:通过绝对地址访问内存单元
1 // GPIOB 端口全部输出 高电平
2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
0x4001 0C0C 在我们看来是 GPIOB 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4001 0C0C,然后再对这个指针进行 * 操作。
刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作。
例:通过寄存器别名方式访问内存单元
1 // GPIOB 端口全部输出 高电平
2 #define GPIOB_OD (unsigned int*) (GPIOB_BASE+0x0C)
3 * GPIOB_ODR = 0xFF;
为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面。
例:通过寄存器别名访问内存单元
1 // GPIOB 端口全部输出 高电平
2 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
3 GPIOB_ODR = 0xFF;
GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚,如型号为 STM32F103VET6 型号的芯片有 GPIOA、GPIOB、GPIOC 至 GPIOE 共 5 组 GPIO,芯片一共 100 个引脚,其中 GPIO就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。
通过 GPIO 硬件结构框图,就可以从整体上深入了解 GPIO 外设及它的各种应用模式。该图从最右端看起,最右端就是代表 STM32 芯片引出的 GPIO 引脚,其余部件都位于芯片内部。
图中编号1-7分别为保护二极管及上下拉电阻、P-MOS管和N-MOS管、输出数据寄存器、复用功能输出、输入数据寄存器、复用功能输入、模拟输入输出。其中每个编号对应的部分功能及介绍参考STM32F103使用指南。
GPIO的结构决定了GPIO有以下的几种模式,这几种模式也相应地体现在固件库的GPIO当中
1 typedef enum 2 {
3 GPIO_Mode_AIN = 0x0, // 模拟输入
4 GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入
5 GPIO_Mode_IPD = 0x28, // 下拉输入
6 GPIO_Mode_IPU = 0x48, // 上拉输入
7 GPIO_Mode_Out_OD = 0x14, // 开漏输出
8 GPIO_Mode_Out_PP = 0x10, // 推挽输出
9 GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出
10 GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 11
} GPIOMode_TypeDef
但总体来说GPIO的八种工作模式大致可以分为以下三类:
(1)输入模式(模拟/浮空/上拉/下拉)
在输入模式时,施密特触发器打开,输出被禁止,可通过输入数据寄存器 GPIOx_IDR读取 I/O 状态。其中输入模式,可设置为上拉、下拉、浮空和模拟输入四种。上拉和下拉输入很好理解,默认的电平由上拉或者下拉决定。浮空输入的电平是不确定的,完全由外部的输入决定,一般接按键的时候用的是这个模式。
施密特触发器的作用是用于在现实中信号杂乱无章的抖动中判断高低电频
(2)输出模式(推挽/开漏)
在输出模式中,推挽模式时双 MOS 管以轮流方式工作,输出数据寄存器 GPIOx_ODR可控制 I/O 输出高低电平。开漏模式时,只有 N-MOS 管工作,输出数据寄存器可控制 I/O输出高阻态或低电平。输出速度可配置,有 2MHz\10MHz\50MHz 的选项。此处的输出速度即 I/O 支持的高低电平状态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设置成最大即可。
在输出模式时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR可读取 I/O 的实际状态。
(3)复用功能(推挽/开漏)
复用功能模式中,输出使能,输出速度可配置,可工作在开漏及推挽模式,但是输出信号源于其它外设,输出数据寄存器 GPIOx_ODR 无效;输入可用,通过输入数据寄存器可获取 I/O 实际状态,但一般直接用外设的寄存器来获取该数据信号。
通过对 GPIO 寄存器写入不同的参数,就可以改变 GPIO 的工作模式,再强调一下,要了解具体寄存器时一定要查阅《STM32F10X-中文参考手册》中对应外设的寄存器说明。在 GPIO 外设中,控制端口高低控制寄存器 CRH 和 CRL 可以配置每个 GPIO 的工作模式和工作的速度,每 4 个位控制一个 IO,CRH 控制端口的高八位,CRL 控制端口的低 8 位,具体的看 CRH 和 CRL 的寄存器描述。
而后新建一个LED2工程文件夹,进入LED2文件夹后输入工程名称LED2,再点击保存
而后选择对应的芯片并确认
再勾选CORE
点击左上角魔术棒图标,选择output之后勾选Create HEx File
source group 1里创建main.c,并写入代码,注意项目结构,使用的引脚是PA7,PB9,PC15。
#include "stm32f10x.h"
//----------------APB2使能时钟寄存器 ---------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL *((unsigned volatile int*)0x40010800)
#define GPIOA_ODR *((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned volatile int*)0x40011004)
#define GPIOC_ODR *((unsigned volatile int*)0x4001100C)
//延时函数
void Delay()
{
u32 i=0;
for(;i<5000000;i++);
}
int main(void)
{
RCC_APB2ENR|=1<<2|1<<3|1<<4; //APB2-GPIOA、GPIOB、GPIOC外设时钟使能
GPIOA_CRL&=0xFFF0FFFF; //设置位 清零
GPIOA_CRL|=0x00020000; //PB5推挽输出
GPIOA_ODR&=~(1<<4); //设置初始灯为灭
GPIOB_CRL&=0xFF0FFFFF; //设置位 清零
GPIOB_CRL|=0x00200000; //PB5推挽输出
GPIOB_ODR&=~(1<<5); //设置初始灯为灭
GPIOC_CRH&=0xF0FFFFFF; //设置位 清零
GPIOC_CRH|=0x02000000; //PB5推挽输出
GPIOC_ODR&=~(1<<14); //设置初始灯为灭
while(1){
//A灯
GPIOA_ODR|=1<<4; //PB5高电平
Delay();
GPIOA_ODR&=~(1<<4); //PB5低电平,因为是置0,所以用按位与
//B灯
GPIOB_ODR|=1<<5; //PB5高电平
Delay();
GPIOB_ODR&=~(1<<5); //PB5低电平,因为是置0,所以用按位与
//C灯
GPIOC_ODR|=1<<14; //PB5高电平
Delay();
GPIOC_ODR&=~(1<<14); //PB5低电平,因为是置0,所以用按位与
}
}
编译后用mcusip打开生成的HEx文件,并注意mcusip中的一些配置
断电后先将芯片上的boot0通过线帽置于1
再将连接好的电路通过USB to TT 连接上电脑之后才能有Port,再点击上图中的读取器件信息->开始编程
读取期间信息显示:
开始编程正确显示(有时需要再按一次)
有以上结果说明程序烧录成功,这事需要断掉电源再将BooT1再置为0。
最后再接上电源,观察结果:
https://blog.csdn.net/weixin_47554309/article/details/120810913
https://blog.csdn.net/qq_47281915/article/details/120812867