STM32,从字面上来理解,ST 是意法半导体,M 是 Microelectronics 的缩写,32 表示32 位,合起来理解,STM32 就是指 ST 公司开发的 32 位微控制器。在如今的 32 位控制器当中,STM32 可以说是最璀璨的新星。
STM32F103 采用的是 Cortex-M3 内核,内核即 CPU,由 ARM 公司设计。ARM 公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商(SOC)如 ST、TI、Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称为核外外设或片上外设。如 GPIO、USART(串口)、I2C、SPI 等都叫做片上外设。具体下图。
STM32系列基于专门要求高性能,低成本,低功耗的嵌入式应用专门设计的ARM Cortex-M0,Cortex-M1,Cortex-M3,Cortex-M4,Cortex-M7等。其中Cortex-M0主打的是低功耗和混合信号的处理,M3主要用来替代ARM7,重点侧重能耗和性能的均衡,而M7则重点放在高性能控制运算领域。
STM32 属于一个微控制器,自带了各种常用通信接口,比如 USART、I2C、SPI 等,可接非常多的传感器,可以控制很多的设备。现实生活中,我们接触到的很多电器产品都有 STM32 的身影,比如智能手环,微型四轴飞行器,平衡车、移动 POST 机,智能电饭锅,3D 打印机等等。
以STM32F103C8T6为例:
(1) STM32:表示32位MCU;
(2) F103:表示基础型;
(3) C:表示芯片上含48个引脚;
(4) 8:表示闪存容量为64K字节;
(5) T:表示QFP封装;
(6) 6:表示工作温度范围在-40°C到85°C;
STM32F103C8T6是一款基于ARM Cortex-M内核STM32系列的32位的微控制器,程序存储器容量是64KB,需要电压2V~3.6V,工作温度为-40°C ~ 85°C。
其中, Cortex-M3摒弃了冯· 诺依曼结构(普林斯顿结构),采用了将指令存储和数据存储分开的 的哈佛结构(Harvard Architecture ),这样一来Cortex-M3同时拥有了独立的32-bit指令总线和32-bit数据总线,数据访问将不再占用指令总线,同时读取指令和数据后提升了MCU运行速度。冯诺依曼和哈弗结构的宏观对比如下图所示:
参数名 | 参数详情 |
---|---|
类别 | 集成电路(IC) |
家族 | 嵌入式-微控制器 |
总线宽度 | 32-位 |
速度 | 72MHz |
外围设备 | DMA,电机控制PWM,PWM,温度传感器 |
输入/输出数 | 37 |
程序存储器容量 | 64KB (64K x 8) |
程序存储器类型 | FLASH |
RAM容量 | 20K x 8 |
电压-电源(Vcc/Vdd) | 2 V ~ 3.6 V |
模数转换器 | A/D 10x12b |
振荡器型 | 内部 |
工作温度 | -40°C ~ 85°C |
封装/外壳 | 48-LQFP |
【产品介绍】
该系统板是基于STM32F103C8T6为主芯片的ARM核心板,有如下特点:
1、板载了基于MCU的最基本电路,如晶振电路、USB电源管理电路和USB接口等。
2、核心板引出了所有的I/O口资源。
3、带有SWD仿真调试下载接口,该接口最少需要3根线就可以完成调试下载任务,相比传统的JTAG调试有不少的好处
4、外形尺寸只有传统的DIP40封装(例如AT89S52)的大小,目前还未在淘宝上发现比我们更小的同规格的核心板。
5、使用了目前智能手机所使用的Mirco USB接口,使用方便,可做USB通讯和供电。
6、针对STM32 RTC不起振的问题,我们采用了官方建议的低负载RTC晶振方案,并使用了爱普生品牌的晶振,而没有使用廉价的圆柱晶振。
7、配有优质的1*40/2.54mm间距的单排排针,确保导电接触优良,方便用户将核心板放置到标准的的万用板或者面包板上。排针默认不焊接,用户可以根据自己的需要选择焊接方向。
寄存器是中央处理器内的组成部分,是有限容量的高速存贮部件,可用来暂存指令、数据、地址等。
简单来说,寄存器用来存放东西的地方,类似于行李寄存,只不过,寄存器存放的不是行李,而是指令、数据、地址等。不同的·数据会存放在不同的寄存器中,通过地址区分不同的寄存器。
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,如下图所示。如果给存储器再分配一个地址就叫存储器重映射。
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。
我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1挂载低速外设,APB2 和 AHB 挂载高速外设。
相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1 总线的地址最低,片上外设从这里开始,也叫外设基地址。
(1)确定GPIOx总线基地址
总线名称 | 总线基地址 | 相对外设基地址的偏移 |
APB1 | 0x4000 0000 | 0x0 |
APB2 | 0x4001 0000 | 0x0001 0000 |
APB | 0x4001 8000 | 0x0001 8000 |
上述表格的“相对外设基地址偏移”即该总线地址与“片上外设”基地址 0x4000 0000的差值。
(2)找到GPIOx外设基地址
总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX 外设基地址”,也叫 XX 外设的边界地址。
在此,以 GPIO 外设来讲解外设的基地址,GPIO 属于高速的外设 ,挂载到APB2 总线上:
外设名称 | 外设基地址 | 相对APB2总线地址的偏移 |
GPIOA | 0x0000 0800 | 0x0 |
GPIOB | 0x4001 0C00 | 0x0000 C000 |
GPIOC | 0x4001 0000 | 0x0001 0000 |
GPIOD | 0x4001 1400 | 0x0001 4000 |
GPIOE | 0x4001 1800 | 0x0001 8000 |
GPIOF | 0x4001 C000 | 0x0001 C000 |
GPIOG | 0x4002 0000 | 0x0002 0000 |
位31:16 保留(写程序时不用管),始终读为0;
位15:0 IDRy[15:0]:端口输入数据(y =0…15),这些位为只读并只能以**字(16位)**的形式读出。读出的值为对应I/O口的状态。
-------------------------------------------------【总结】-------------------------------------------------
GPIOA下的某个寄存器,挂载在GPIOA下,地址为GPIOA基地址+偏移量
GPIOA挂载在APB2总线,地址为APB2总线基地址+GPIOA偏移量
ABP2挂载加外设基地址,地址为外设基地址+ABP2偏移量
//外部总线基地址
#define PERIPH_BASE ((uint32_t)0x40000000)
//APB2基地址=外部总线基地址+偏移量
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
//GPIOA基地址=APB2基地址+偏移量
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
//GPIOA将地址顺序分配给7个32位寄存器(结构体分配)
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
//将寄存器地址映射到7个32位寄存器,分别控制
typedef struct
{
__IO unit32_t CRL;
__IO unit32_t CRH;
__IO unit32_t ODR;
__IO unit32_t IDR;
__IO unit32_t BSRR;
__IO unit32_t BRR;
__IO unit32_t LCKR;
}GPIO_TypeDef;
比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C,ODR 寄存器是 32bit,低 16bit有效,对应着 16 个外部 IO,写 0/1 对应的 IO 输出低/高电平。现在我们通过 C 语言指针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平:
(1)通过绝对地址访问内存单元
1 // GPIOB 端口全部输出 高电平
2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
0x4001 0C0C在我们看来是 GPIOB端口 ODR的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4001 0C0C,然后再对这个指针进行 * 操作。
(2)通过寄存器别名方式访问内存单元
1 // GPIOB 端口全部输出 高电平
2 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
3 * GPIOB_ODR = 0xFF;
(3)通过寄存器别名访问内存单元
1 // GPIOB 端口全部输出 高电平
2 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
3 GPIOB_ODR = 0xFF;
第三章节的介绍只是GPIO端口配置的基础,因为后续的所有操作都基于寄存器操作,而对于寄存器的操作就是通过访问地址实现的,因此在正式进行GPIO的初始化之前需要掌握寄存器地址访问方法。
GPIO口的初始化大致分成三部分:时钟配置;输入输出模式配置;最大速率配置。
任何外设都需要时钟,51单片机,STM32等等,因为寄存器是由D触发器组成的,往触发器里面写东西,前提条件是有时钟输入。
51单片机不需要配置时钟,是因为一个时钟开了之后所有的功能都可以使用,而在51中这个时钟是默认开启的,但是也存在一个问题,耗能大。
STM32之所以是低功耗,因为它将所有的门都默认设置为disable,在你需要用哪个门的时候,开哪个门就可以,也就是说用到什么外设,只要打开对应外设的时钟就可以,其他的没用到的可以还是disable,这样耗能就会减少。
根据数据手册可知,抚慰和时钟控制RCC(时钟控制)地址从0x4002 1000开始,根据外设偏移地址为0x18,可以确定APB2的地址为0x4002 1018,根据下图可得GPIOB的时钟使能控制位为第3位,因此将位3赋值为1,即开启GPIOB的时钟
代码如下:
RCC_APB2ENR |= (1<<3);
IO:表示“input”、“output”,就可以当作输入也可以当作输出,因此需要通过配置来确定状态。
STM32的每个IO都需要4位进行配置,因此一个32位的寄存器最大只能配置8个IO(32位的单片机的寄存器就是32位的)。STM32中,用低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。
配置引脚PB0,使用的寄存器是GPIOB_CRLCRL 中包含 0-7 号引脚,每个引脚占用 4 个寄存器位。MODE位用来配置输出的速度,CNF 位用来配置各种输入输出模式。
在代码实现中,先将控制 PB0 的端口位清 0,然后再向它赋值“0001 b”,从而使
GPIOB0 引脚设置成输出模式,速度为 10M。
在对应保存目录下创建文件夹LED,之后在keil中创建工程。具体创建步骤,可以转到答主之前的博客查看。
(1)添加.h文件
首先在工程中添加头文件,在“Target1”-“Source group1”,右键-“Add New Item…”,在弹出的窗口中选中“.h”文件,填写文件名保存,到此完成.h文件的添加。
(2)编写.h文件
连接 LED 灯的 GPIO 引脚,是要通过读写寄存器来控制,寄存器就是给一个已经分配好地址的特殊
的内存空间取的一个别名,这个特殊的内存空间可以通过指针来操作。
在编程之前,首先实现寄存器映射,有关寄存器映射的代码都统一写在 Led.h 文件中,代码如下:
/*片上外设基地址 */
#define PERIPH_BASE ((unsigned int)0x40000000)
/*总线基地址,GPIO 都挂载到 APB2 上 */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
/*GPIOB 外设基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
/* GPIOB 寄存器地址,强制转换成指针 */
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_IDR *(unsigned int*)(GPIOB_BASE+0x08)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
#define GPIOB_BRR *(unsigned int*)(GPIOB_BASE+0x14)
#define GPIOB_LCKR *(unsigned int*)(GPIOB_BASE+0x18)
/*RCC 外设基地址*/
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
/*RCC 的 AHB1 时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
(1)在工程中添加main函数
与上述添加头文件的步骤一样:
(2)配置引脚为GPIO输出模式。
把连接到LED 灯的 GPIO 引脚PB0配置成输出模式,即配置 GPIO 的端口配置低寄存器 CRL。CRL 中包含 0-7 号引脚,每个引脚占用 4 个寄存器位。
其中,MODE位用来配置输出的速度,CNF 位用来配置各种输入输出模式。在这里将 PB0 配置为通用推挽输出,输出的速度为 10M,代码如下:
// 清空控制 PB0 的端口位
GPIOB_CRL&= ~( 0x0F<< (4*0));
// 配置 PB0 为通用推挽输出,速度为 10M
GPIOB_CRL |= (1<<4*0);
(3)控制引脚输出电平
在输出模式时,对端口位设置/清除寄存器 BSRR 寄存器、端口位清除寄存器 BRR 和ODR 寄存器写入参数即可控制引脚的电平状态,其中操作 BSRR 和 BRR 最终影响的都是ODR 寄存器,然后再通过 ODR 寄存器的输出来控制 GPIO。
为了操作的简便性,在这里直接操作 ODR 寄存器来控制 GPIO 的电平。代码如下
1 // PB0 输出低电平
2 GPIOB_ODR &= ~(1<<0);
(4)时钟配置
配置完完 GPIO 的引脚,控制电平之后,还需要将对应的时钟打开,外设才可以工作。
STM32 的所有外设的时钟由一个专门的外设来管理,叫 RCC(reset and clockcontrol)。所有的 GPIO 都挂载到 APB2 总线上,具体的时钟由 APB2 外设时钟使能寄存器(RCC_APB2ENR)来控制,代码如下:
1 // 开启 GPIOB 端口 时钟
2 RCC_APB2ENR |= (1<<3);
(5)编写main
函数
1 int main(void)
2 {
3 // 开启 GPIOB 端口时钟
4 RCC_APB2ENR |= (1<<3);
5
6 //清空控制 PB0 的端口位
7 GPIOB_CRL &= ~( 0x0F<< (4*0));
8 // 配置 PB0 为通用推挽输出,速度为 10M
9 GPIOB_CRL |= (1<<4*0);
10
11 // PB0 输出 低电平
12 GPIOB_ODR |= (0<<0);
13
14 while (1);
15
16 }
其中,在嵌入式编程中,程序需要处于一个无限循环中,及死循环。在嵌入式编程中,初始化完成后,单片机就在死循环内一遍又一遍的执行程序逻辑。复位后,就从头开始,初始化完成后,再次进入死循环。
(1)编译成功后,按下调试按钮“d”,进入调试;
(2)之后,按下菜单栏的系统分析窗口,选择逻辑分析仪;
(3)进入setup,设置观察的信号:
(3)点击运行按钮,观察波形:
根据波形,不难得出GPIOB_ODR输出一直为低电平,led灯常亮。
[1]https://blog.csdn.net/fantastic_sky/article/details/110229474
[2]http://t.csdn.cn/cLnYh
[3]https://blog.csdn.net/geek_monkey/article/details/86291377