学习和理解STM32F103系列芯片的地址映射和寄存器映射原理;了解GPIO端口的初始化设置三步骤
一、STM32F103系列芯片的地址映射和寄存器映射原理
1.寄存器
寄存器是中央处理器内的组成部分。 寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
简单来说,寄存器就是存放东西的一个空间器物。寄存器可能存放的是指令、数据或地址。
按照功能的不同,可将寄存器分为基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据,也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。
存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像老张行李寄存处在101号店铺,老王行李寄存处在258号店铺。
指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。
地址映射和寄存器映射原理
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?
说到映射大家可能就会想到函数映射,脑海里会有一个画面:左边一个集合中的某个元素“射”出一条带箭头的直线指向右边的集合的某个元素。其实外围设备的内存映射原理是一样的,只不过左边的集体变成了CPU,右边的集合变成了外围设备,那条带箭头的线就是连接CP和外设地址引脚的地址总线。存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射。
地址映射:由百度词条可知为了保证CPU执行指令时可正确访问存储单元,需将用户程序中的逻辑地址转换为运行时由机器直接寻址的物理地址,这一过程称为地址映射。
寄存器映射:在存储器的区域单元中,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

3.GPIO简介
GPIO(英语:General-purpose input/output),通用型之输入输出的简称,功能类似8051的P0—P3,其接脚可以供使用者由程控自由使用,PIN脚依现实考量可作为通用输入(GPI)或通用输出(GPO)或通用输入与输出(GPIO),如当clk generator, chip select等。
既然一个引脚可以用于输入、输出或其他特殊功能,那么一定有寄存器用来选择这些功能。对于输入,一定可以通过读取某个寄存器来确定引脚电位的高低;对于输出,一定可以通过写入某个寄存器来让这个引脚输出高电位或者低电位;对于其他特殊功能,则有另外的寄存器来控制它们。

4.GPIO模式

GPIO的工作模式主要有八种:4种输入方式,4种输出方式。
分别为输入浮空,输入上拉,输入下拉,模拟输入;
输出方式为开漏输出,开漏复用输出,推挽输出,推挽复用输出。
1)GPIO_Mode_AIN 模拟输入 (应用ADC模拟输入,或者低功耗下省电)
(2)GPIO_Mode_IN_FLOATING 浮空输入 (浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
(3)GPIO_Mode_IPD 下拉输入 (IO内部下拉电阻输入)
(4)GPIO_Mode_IPU 上拉输入 (IO内部上拉电阻输入)
(5)GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
(6)GPIO_Mode_Out_PP 推挽输出 (推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
(7)GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
(8)GPIO_Mode_AF_PP 复用推挽输出 (片内外设功能TX1,MOSI,MISO.SCK.SS)
GPIO 8 种工作模式
typedef enum
{
GPIO_Mode_AIN = 0x0, // 模拟输入
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入
GPIO_Mode_IPD = 0x28, // 下拉输入
GPIO_Mode_IPU = 0x48, // 上拉输入
GPIO_Mode_Out_OD = 0x14, // 开漏输出
GPIO_Mode_Out_PP = 0x10, // 推挽输出
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出
} GPIOMode_TypeDef;
5.输入和输出
简单来说:输出是CPU计算后进行控制,输入是读取后给CPU进行计算。
输入 进行数据的采集,外部电路通过IO口输入模拟量,然后通过“TTL肖特基触发器”(肖特基触发器是将相对缓慢变化的模拟信号变成矩形信号,便于后面读取),进入输入数据寄存器,最后就能给CPU读取数据。
输出 GPIO的输出与51的 IO口是差不多的概念,都是输出高、低电平来控制外部电路:
处理过程:CPU下达输出高或低电平指令,指令配置“位设置/清除寄存器(GPIOx_BSRR)”(设置就是“1”高电平,清除就是“0”低电平),再由位寄存器配置输出数据寄存器(GPIOx_ODR),经过一个选择器(选择是一般输出还是复用功能输出),然后进行输出控制,控制是什么模式:推挽、开漏或者关闭,然后输出高或低电平到IO口
输入模式

输出模式
推挽输出模式:(最常用)
推挽电路是两个参数相同的三极管或 MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。
简单来说,当一个mos管电流增大时,另一个mos管的电流就减小,当P-MOS管饱和时,同时N-MOS截止,接通并输出Vdd及高电平“1”,反之,当N-MOS管饱和时,同时P-MOS截止,接通并输出Vss及低电平“0”。 就像是这两个mos管在相互“推、拉(挽)”,所以就叫推挽。
开漏输出模式:(不常用)
输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。
利用外部电路的驱动能力,减少 IC 内部的驱动。当 IC 内部 MOSFET 导通时,驱动电流是从外部的VCC 流经 R pull-up ,MOSFET到 GND。IC 内部仅需很少的栅极驱动电流。
推挽、开漏复用模式:
当GPIO口被用作第二功能时,需要配置,端口重映射AFIO。
GPIO 端口初始化
GPIO 端口初始化大致可以分为三个步骤:
1.时钟配置
2.输入输出模式设置
3.最大速率设置
时钟配置
对于 STM32 有 5 个时钟源,如下:
| 简称 | 时钟源名称 | 频率范围 |
|---|---|---|
| HSI | 高速内部时钟 | 8MHZ |
| HSE | 高速外部时钟 | 4MHZ~16MHZ |
| LSI | 低速内部时钟 | 40KHZ |
| LSE | 低速外部时钟 | 32.768KHZ |
| PLL | 锁相环倍频输出 | 输出频率最大不得超过 72MHz |
时钟树如下图:

程序刚启动的时候,stm32采用的为内部高速时钟。如果需要采用外部时钟,需要按照如下的方式配置:
-
时钟初始化,即将时钟的寄存器采用默认值。
-
开始外部时钟且外部时钟起震准备就绪。
-
设置 PLLXTPRE(只能在关闭PLL时才能写入此位),可选择分频不分频。
-
设置进入 PLL 的源时钟(只能在关闭 PLL 时才能写入此位)。因为采用外部时钟所以只有一种设置。
-
设置 PLL 倍频系数 PLLMUL(只有在 PLL 关闭的情况下才可被写入)。
-
开启 PLL ,且准备就绪。
-
设置 SW ,选择时钟源为系统时钟。
-
判断是否是预选的时钟为系统时钟。
输入输出模式设置
-
GPIO_Mode_AIN 模拟输入 (应用ADC模拟输入,或者低功耗下省电)
-
GPIO_Mode_IN_FLOATING 浮空输入 (浮空就是浮在半空,可以被其他物体拉上或者拉下,可以用于按键输入)
-
GPIO_Mode_IPD 下拉输入 (IO内部下拉电阻输入)
-
GPIO_Mode_IPU 上拉输入 (IO内部上拉电阻输入)
-
GPIO_Mode_Out_OD 开漏输出(开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行)
-
GPIO_Mode_Out_PP 推挽输出 (推挽就是有推有拉电平都是确定的,不需要上拉和下拉,IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的 )
-
GPIO_Mode_AF_OD 复用开漏输出(片内外设功能(I2C的SCL,SDA))
-
_Mode_AF_PP 复用推挽输出 (片内外设功能(TX1,MOSI,MISO.SCK.SS))
| 代码 | 模式 |
|---|---|
| GPIO_Mode_IPU | 上拉输入 |
| GPIO_Mode_AIN | 模拟输入 |
| GPIO_Mode_IN_FLOATING | 浮空输入 |
| GPIO_Mode_IPD | 下拉输入 |
| GPIO_Mode_Out_OD | 开漏输出 |
| GPIO_Mode_Out_PP | 推挽输出 |
| GPIO_Mode_AF_OD | 复用开漏输出 |
| GPIO_Mode_AF_PP | 复用推挽输出 |
| 输入模式名称 | 作用 | 原理 |
|---|---|---|
| 上拉输入模式 | 在默认状态下(GPIO引脚无输入),读取得的 GPIO 引脚数据为 1,高电平 | 与 VDD 相连的为上拉电阻。再连接到施密特触发器就把电压信号转化为1的数字信号存储在输入数据寄存器( IDR ) |
| 下拉输入模式 | 在默认状态下( GPIO 引脚无输入),读取得的 GPIO 引脚数据为 0,低电平 | 与 VSS 相连的为下拉电阻。再连接到施密特触发器就把电压信号转化为 0 的数字信号存储在输入数据寄存器( IDR ) |
| 浮空输入模式 | 配置成这个模式直接用电压表测量其引脚电压为1点几伏,这是个不确定值 | 没有接上拉,也没有接下拉电阻,经由触发器输入 |
| 模拟输入模式 | 把电压信号传送到片上外设模块,如传送至给 ADC 模块,由 ADC 采集电压信号 | 关闭了施密特触发器,不接上、下拉电阻 |
| 输出模式名称 | 作用 | 原理 |
|---|---|---|
| 普通推挽输出 | 推挽输出的供电平为 0 伏,高电平为 3.3 pp*伏 | 在输出高电平时,P-MOS 导通,低电平时,N-MOS 管导通。两个管子轮流导通,一个负责灌电流,一个负责拉电流,使其负载能力和开关速度都比普通的方式有很大的提高 |
| 普通开漏输出 | 控制输出为 0,低电平;若控制输出为 1,为高阻态 | 如果我们控制输出为 0,低电平,则使 N-MOS 管导通,使输出接地,若控制输出为1 (无法直接输出高电平),则既不输出高电平,也不输出低电平,为高阻态 |
| 复用推挽输出 | GPIO 的引脚用作串口的输出 | |
| 复用开漏输出 | 用在 IC、SMBUS 这些需要线与功能的复用场合 |
注意:在使用任何一种开漏模式,都需要接上拉电阻
最大速率设置
GPIO 的输出速率:GPIO 电平每秒切换的最大次数
这个输出速率主要体现 I/O 驱动电路的输出反应能力,通过选择不同的输出驱动速率,实现最佳的噪声与和功耗控制。不难理解,选择输出驱动速率越高,噪声也越大,相应的芯片功耗也会越大
当 STM32 的 GPIO 端口设置为输出模式时,有三种速度可以选择:2MHz、10MHz 和 50MHz,这个速度是指 I/O 口驱动电路的速度,是用来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。
对于 STM32 GPIO 输出速率的选择问题,我们在开发应用中应多加注意。如果因为这个输出速率选择导致麻烦,原因往往比较隐晦,很难直接从代码语句或程序逻辑上找到突破。
高频的驱动电路,噪声也高,当你不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。
当然如果你要输出较高频率的信号,但却选用了较低频率的驱动模块,你很可能会得到失真的输出信号。实际上芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要选择合适的驱动电路。在满足实际应用需求的前提下,速率就低不就高,这对降低功耗、减少噪声、改善 EMI 都有好处
gpio 不同速率设置对实际开发的影响:
1、LED 闪烁快慢不一致
2、Audio 噪声
其中有 I2S 的音频播放功能。在调试时用到 Printf 串口打印,发现使用 printf 输出时会出现噪音,如果关闭 printf 则正常。直到将 UART 的 TX 输出端口的管脚输出速率由 very high 改为 Low 后噪声消失。
3、SPI 通信异常
用到 SPI 通信。STM32 做主,其它外围器件做从,有时发现 SPI 读取数据总是出错。对于这里的通信出错,如果 SPI 通信端口脚的输出速率选择跟实际通信速率不合适的话也会出现。相比实际速率需求,过高或过低了都会导致通信出错。
注意:GPIO 的引脚速度是指 I/O 口驱动电路的响应速度而不是输出信号的速度,输出信号的速度与你的程序有关
代码示例
/* GPIO_InitTypeDef结构体 */
typedef enum
{
GPIO_Speed_10MHz = 1, //枚举常量,值为 1,代表输出速率最高为 10MHz
GPIO_Speed_2MHz, //对不赋值的枚举变量,自动加 1,此常量值为 2
GPIO_Speed_50MHz //常量值为 3
} GPIOSpeed_TypeDef;
typedef enum
{
GPIO_Mode_AIN = 0x0, //模拟输入模式
GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式
GPIO_Mode_IPD = 0x28, //下拉输入模式
GPIO_Mode_IPU = 0x48, //上拉输入模式
GPIO_Mode_Out_OD = 0x14, //开漏输出模式
GPIO_Mode_Out_PP = 0x10, //通用推挽输出模式
GPIO_Mode_AF_OD = 0x1C, //复用功能开漏输出
GPIO_Mode_AF_PP = 0x18 //复用功能推挽输出
} GPIOMode_TypeDef;
typedef struct
{
uint16_t GPIO_Pin; /* 指定要配置的引脚 */
GPIOSpeed_TypeDef GPIO_Speed; /* 指定GPIO引脚输出的最高频率 */
GPIOMode_TypeDef GPIO_Mode; /* 指定GPIO引脚工作状态 */
} GPIO_InitTypeDef;
/* 初始化GPIO -- GPIO_Init() */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* 断言,用于检查输入的参数是否正确 */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
/*---------------------------- GPIO 的模式配置 -----------------------*/
/*把输入参数 GPIO_Mode 的低四位暂存在 currentmode*/
currentmode = ((uint32_t)GPIO_InitStruct -
> GPIO_Mode) & ((uint32_t)0x0F);
/*判断是否为输出模式,输出模式,可输入参数中输出模式的 bit4 位都是 1*/
if ((((uint32_t)GPIO_InitStruct -
> GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* 检查输入参数 */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* 输出模式,所以要配置 GPIO 的速率:00(输入模式) 01(10MHz) 10(2MHz) 11 */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*----------------------------配置 GPIO 的 CRL 寄存器 -----------------------
-*/
/* 判断要配置的是否为 pin0 ~~ pin7 */
if (((uint32_t)GPIO_InitStruct -
> GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
/*备份原 CRL 寄存器的值*/
tmpreg = GPIOx->CRL;
/*循环,一个循环设置一个寄存器位*/
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
/*pos 的值为 1 左移 pinpos 位*/
pos = ((uint32_t)0x01) << pinpos;
/* 令 pos 与输入参数 GPIO_PIN 作位与运算,为下面的判断作准备 */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
/*判断,若 currentpin=pos,说明 GPIO_PIN 参数中含的第 pos 个引脚需要配置*/
if (currentpin == pos)
{
/*pos 的值左移两位(乘以 4),因为寄存器中 4 个寄存器位配置一个引脚*/
pos = pinpos << 2;
/*以下两个句子,把控制这个引脚的 4 个寄存器位清零,其它寄存器位不变*/
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* 向寄存器写入将要配置的引脚的模式 */
tmpreg |= (currentmode << pos);
/* 复位 GPIO 引脚的输入输出默认值*/
/*判断是否为下拉输入模式*/
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
/*下拉输入模式,引脚默认置 0,对 BRR 寄存器写 1 可对引脚置 0*/
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/*判断是否为上拉输入模式*/
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
/*上拉输入模式,引脚默认值为 1,对 BSRR 寄存器写 1 可对引脚置 1*/
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
/*把前面处理后的暂存值写入到 CRL 寄存器之中*/
GPIOx->CRL = tmpreg;
}
/*---------------------------- 以下部分是对 CRH 寄存器配置的 -----------------
--------当要配置的引脚为 pin8 ~~ pin15 的时候,配置 CRH 寄存器, -----
------------- -----这过程和配置 CRL 寄存器类似------------------------------
------
-------读者可自行分析,看看自己是否了解了上述过程--^_^-----------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
GPIOx->CRH = tmpreg;
}
}

浙公网安备 33010602011771号