初学者的学习笔记,有问题的地方请多指教,会持续修改更新,不断学习进步。
4.26日更新
目录
1、对GPIO的简单刨析
2、直接操作绝对的内存地址点灯(一)
3、操作寄存器映射点灯(二)
GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚,STM32芯片的GPIO引脚与外部设备连接起来。
1、保护二极管及上、下拉电阻
目的:防止引脚外部过高或过低的电压输入引入芯片导致芯片毁损。
当引脚电压高于VDD时,上方的二极管导通,当引脚电压低于VSS时,下方的二极管导通。如果io口连接电机,电机会产生反电动势,在极短时间内产生高压,二极管反应不过来,就有可能直接导致芯片烧坏。
2、P-MOS管和N_MOS管(推挽输出和开漏输出)
输出数据寄存器(ODR)写0或1时,通过输出控制,控制两个MOS管工作,通过外设的GPIO输出3.3V或0V
推挽输出
在ODR对该结构输入高电平时, 经过反向后,上方满足Ug
在该结构输入低电平时,经过反向后,下方满足Ug>Us导通,NMOS导通,上方的PMOS关闭,OUT被拉入地,对外输出0
当引脚高低电平切换时,两个管子轮流导通,P管负责灌电流,N管负责拉电流,使其负载能力和开关速度都比普通的方式有很大的提高。
推挽输出的低电平为0V,高电平为3.3V
开漏输出
特性:只输出低电平,无法直接输出高电平。
若控制输出为1时,PMOS管和NMOS管都关闭,引脚既不输出高电平,也不输出低电平,为高阻态,为正常使用,必须接上拉电阻。
总结:推挽输出模式一般应用在输出电平为0和3.3V而且需要高速切换开关状态的场合。在STM32的应用中,除了必须用开漏模式的场合,都习惯用推挽输出模式
开漏输出具有“线与”功能,一个为低,全部为低,多用于I2C和SMBUS总线,除此之外,还有电平不匹配的场合,如需要输出5V的高电平,就可以在外部接一个上拉电阻,上拉电源为5V,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5V的电平。
3、输出数据寄存器(ODR)
低16位有效,每一位对应一个IO,写1对应高电平,写0对应低电平。
前面提到的双MOS管结构电路输入信号,是由 GPIO“输出数据寄存器 GPIOx_ODR”提供的,因此我们通过修改输出数据寄存器的值就可以修改 GPIO 引脚的输 出电平。而“置位/复位寄存器GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。
4、复用功能输出(暂时还没学到)
5、输入数据寄存器
如果是0,表示外部引脚输入的是0,如果是1,表示外部的引脚输入的是1。
另外:当配置成输出的时候,往ODR里写1时,可以从IDR里读取到。
看 GPIO 结构框图的上半部分,它是 GPIO 引脚经过上、下拉电阻后引入的,它连接到 施密特触发器,信号经过触发器后,模拟信号转化为 0、1 的数字信号,然后存储在“输入数据寄存器 GPIOx_IDR”中,通过读取该寄存器就可以了解 GPIO 引脚的电平状态。
从引脚进来之后,在输入的时候还可以配置成上拉输入或下拉输入
到底是上拉还是下拉呢?
当配置上拉输入,就往BSRR位设置位里写1,如果配置成下拉,就往BSRR清除位写1,上拉和下拉都需要通过BSRR软件来配置的。
再回到原先所说,引脚起来后,施密特触发器起到一个门禁作用,高于2V写1,低于1.2V写0.
6、复用功能输入
与“复用功能输出”模式类似,在“复用功能输出模式”时,GPIO 引脚的信号传输到STM32 其它片上外设,由该外设读取引脚状态。同样,如我们使用 USART 串口通讯时,需要用到某个 GPIO 引脚作为通讯接收引脚,这个时候就可以把该 GPIO 引脚配置成 USART 串口复用功能,使 USART 可以通过该通讯引脚的接收远端数据
7. 模拟输入输出
当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施密特触发器的,因为经过施密特触发器后信号只有 0、1 两种状态,所以 ADC 外设要采集到原始的模拟信号,信号源输入必须在施密特触发器之前。类似地,当 GPIO 引脚用于 DAC 作为模拟电压输出通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双 MOS 管结构了,在 GPIO 结构框图的右下角处,模拟信号直接输出到引脚。同时,当 GPIO 用于模拟功能时(包括输入输出),引脚的上、下拉电阻是不起作用的,这个时候即使在寄存器配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。
GPIO的 输出初始化顺序
1、选定具体的GPIO
2、配置GPIO工作模式(CPL和CRH寄存器)
3、控制GPIO输出高低电平(ODR、BRR和BSRR)
第一步:打开GPIOB端口的时钟
可以看到, GPIO的时钟是被关闭的,时钟是有RCC控制的,RCC挂载在AHB系统总线,如果需要打开GPIOB的时钟,就需要打开APB2的外设时钟。
我们需要把位3打开,开启时钟。
再找到RCC的基地址,算出RCC_APB2ENR寄存器地址为:0x40021000+0x80=0x40021018程序代码为:
*(ussigend int *)0x40021018|=(1<<3);
第二步:配置GPIO为输出模式
四位控制一个IO口,控制PBO只需要控制最后四位就可以了。
*(unsigned int *)0x40010c00|=(1<<0);
3.配置ODR寄存器当前状态:
要想PB0亮,就需要给端口输出数据寄存器最后一位写0
程序代码为:
*(unsigned int*)0x40010C0C &=~(1<<0);
#include "stm32f10x.h"
int main(void)
{
//打开GPIOB端口的时钟
*(unsigned int*)0x40021018|=(1<<3);
//配置IO口为输出
*(unsigned int*)0x40010C00|=(1<<(4*0));
//控制ODR寄存器
*(unsigned int*)0x40010C0C &=~(1<<0);
}
void SystemInit(void)
{
//函数体为空,目的是为了骗过编译器不报错
}
//置为| 清0 &=~
方法一直接操作内存地址可读性很差,把寄存器地址再取一个名字。
首先定义三条总线的基地址。
这是所有外设的起始地址:0x4000 0000,也是APB1的基地址
AHB以DMA1为总线的基地址:外设基地址+0x20000
RCC地址:AHB基地址+0x1000
APB2以AF1O为起始地址:外设基地址+0x10000
GPIO端口B:APB2基地址+0x0C00
外设的基地址都定义好了
/*片上外设基地址 */
#define PERIPH_BASE ((unsigned int)0x40000000)
/*APB1 总线基地址 */
#define APB1PERIPH_BASE PERIPH_BASE
/*APB2 总线基地址 */
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
/* AHB总线基地址 */
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
/*RCC外设基地址*/
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
/*GPIOB外设基地址*/
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
可知我们还需要RCC的AHB时钟使能寄存器地址,CRL和ODR的地址
RCC_APB2ENR的地址是在RCC的基地址上开始偏移0x18:RCC_BASE+0x18
CRL的地址就是GPIOB的地址:GPIOB_BASE+0x00
ODR的地址就是GPIOB的地址+0Ch:GPIOB_BASE+0x0C
/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
/* GPIOB寄存器地址,强制转换成指针 */
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
要注意的是需要强制类型转化成指针,取地址的操作也定义到寄存器这里。
现在就可以把绝对地址换成相应的寄存器了
对照一下
// 开启GPIOB 端口时钟
RCC_APB2ENR |= (1<<3);
//清空控制PB0的端口位
GPIOB_CRL &= ~( 0x0F<< (4*0));
// 配置PB0为通用推挽输出,速度为10M
GPIOB_CRL |= (1<<4*0);
// PB0 输出 低电平
GPIOB_ODR &= ~(1<<0);
注意:有可能CRL的低四位并不是0,所以严谨的方式就是要先清空端口位