STM32时钟和GPIO配置


STM32F1xx 系统时钟

image-20201111160906639

来源:STM32F1中文参考手册 6.2时钟

时钟的作用

决定了程序执行的速度,给芯片提供一个稳定的执行频率

  1. STM32F103R8 最高速率是多少??

​ 72 MHz maximum frequency

  1. 如果采用最高频率:执行一条指令 1/72M s ==> 1/72us

  2. 精简指令集:几乎所有的指令都是消耗一个时钟节拍(1/72 us)执行

R8的时钟来源

  1. 高速外部时钟信号(HSE) 4 – 16M 给系统时钟提供时钟信号

  2. 高速内部时钟信号(HSI) 内部RC振荡器

  3. 低速外部时钟信号(LSE) 32.768 给RTC实时时钟提供时钟信号

  4. 低速内部时钟信号(LSI)

系统时钟来源

  1. HSI振荡器时钟

  2. HSE振荡器时钟

  3. PLL时钟

R8的时钟树和外设分布

image-20201111161926185

来自:STM32F103R8数据手册 2.1 Device overview

STM32时钟的配置

  1. PLL的倍频因子:HSE * PLLMUL = 72M Hz
  2. AHB的频率:72M Hz
  3. APB2的频率:72M Hz
  4. APB1的频率:36M Hz

修改HSE_VALUE

image-20201111185616475

image-20201111185701307

设置函数static void SetSysClockTo72(void) 中的值

image-20201111190315406

image-20201111163440099

GPIO的配置

GPIO的作用

GPIO通用的输入输出外设

数字接口:0/1

0 -- TTL电平:0v~1.5v

1 -- TTL电平:2.5v~5v

STM32F103C8 : 0 => 0v ± 0.1 1 => 3.3v ± 0.3v

STM32 中GPIO口如何表示(理解)

PA0 PA1 PA2..... PA15 ; PB0 .....

P port 端口

A B C.... 端口号

0 1 ..... 15 端口位

每个端口最多有16个端口位

PA1 端口A 的第1位

PA0 端口A 的第0位

GPIO的相关模式

  • 输入:4种

模拟输入:输入的模拟量,用于ADC转化。

浮空输入:输入的数字量,

上拉输入:输入的数字量,向上驱动能力以及稳定信号。

下拉输入:输入的数字量,向下驱动能力以及稳定信号。

配置成那种模式要结合外部电路,不能导致IO口的状态不确定。

比如说:外部是上拉,如果IO口配置成 “下拉模式”,就会导致当外部为高的时候IO口的电平不确定是高还是低,主要原因在于电阻的分压大小。

  • 输出:4种

    通用:GPIO口功能

    复用:除了GPIO口功能

    推挽:既能输出低电平,又能输出高电平

    开漏:只能输出低电平。 -- 用于总线通信,外部具有上拉功能。

    通用推挽输出、通用开漏输出、复用推挽输出、复用开漏输出。

image-20201111195007047

寄存器配置GPIO的模式

image-20201111194501464

寄存器概念和作用

“寄存器” 是 “地址” 的别名。

操作寄存器就是操作地址。

stm32f10x.h(宏定义)文件中,就定义了很多的地址

#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
//三条总线 AHB APB2 APB1
---------------------------------------------------------
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE            (APB2PERIPH_BASE + 0x1000)
//APB2PERIPH_BASE:说明GPIO是在APB2总线上的外设。
----------------------------------------------------------
typedef struct
{
  __IO uint32_t CRL;	//32 bit
  __IO uint32_t CRH;	//32 bit
  __IO uint32_t IDR;	//32 bit
  __IO uint32_t ODR;	//32 bit
  __IO uint32_t BSRR;	//32 bit
  __IO uint32_t BRR;	//32 bit
  __IO uint32_t LCKR;	//32 bit
} GPIO_TypeDef; 
----------------------------------------------------------
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
//将一个地址,强制转换为 GPIO_TypeDef 类型,
//就是将这一片空间按照 GPIO_TypeDef 分配,
//通过操作 结构体的成员 达到操作对应的地址的内容。
---------------------------------------------------------
image-20201111200546464

上述的结构体,可以通过操作 “结构体中的成员” ,从而达到对 “地址内容” 的更改,也就是完成了对 “寄存器内容” 的修改。

配置GPIO输入 / 输出模式

配置 7 :0 位(低8位)

image-20201111201004786

4bite 控制一个 “GPIO端口位 ” , 相关位,对应的功能如下图所示。

image-20201111201316084

配置 15 :8(高8位)

image-20201111201406980

位 清零 与 置一

不影响其他位的值

位 清零: &= ~( );

位 置一: |= ( );

将想要 清零 或者 置一 的位 的值 对应为 1 ,带入上面的方法中就行了

将GPIOB5配置为 “通用推挽输出” 对应的4个位为 0011

先清零: GPIOB->CRL &= ~( 0xF << 20 ); //将4个位都清零 4*5=20。

后置位: GPIOB->CRL |= ( 0x3 << 20 ); //将对应的位,设置为所需的功能。

读取输入数据

出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器

端口输入数据寄存器(GPIOx_IDR) (x=A..E)

image-20201111230550101

如何判断固定的位的值??

& ( );

判断 GOIOA5 的输入数据是1/0

( GPIOA->IDR & (0x1<<5) ) == 0 就说明外部输入的是 0

( GPIOA->IDR & (0x1<<5) ) != 0 就说明外部输入的是 1

也就是说,&1 对应的值不改变,否则就会对应位被置零;

所以说,当外部输入0,整体的就是0;外部输入1,整体不为0;

也可以判断多个位,将想要判断的位 写成1,

然后 (对象) & (对应位),判断结果,就可以知道多个位的状态。

输出数据

开漏模式:输出寄存器上的 ’0’ 激活N-MOS,而输出寄存器上的 ’1’ 将端口置于高阻状态(PMOS从不被激活)。
推挽模式:输出寄存器上的 ’0’ 激活N-MOS,而输出寄存器上的 ’1’ 将激活P-MOS。

image-20201111232114036

想要控制某个位输出 0/1 就在 寄存器对应的位写 0/1。

输出要求:不改变其他位的前提下,改变寄存器的值。

清零:&= ~( );

置一:|= ( );

函数代码编写

参考别人写的代码,一步步让自己的代码优秀起来。

//延时1us, 频率:1/72us ; => 72个_NOP(); 就是1us
#define Delay_1us(){\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
__NOP();__NOP();\
}//没有使用函数来定义,用宏定义,就是一个替代效果;尽可能让1us延时更精准,后续的调用误差会更小。

//延时n us , 函数调用,肯定有误差,尽可能的减少
void Delay_us(uint32_t time)
{
	while(time--)	
		Delay_1us();
}
//延时 n ms, 没有调用 Delay_us(); 函数,就是为了减少函数调用和返回时消耗的时间。
void Delay_ms(uint32_t time)
{
	uint64_t ms = time*1000;
	while(ms--)
		Delay_1us();
}
//Led_On:首字母大写,其余小写;1、能知道是宏定义; 2、用起来像是函数
#define Led_On(port, pin)	(port->ODR &= ~(0x1<<pin))
#define Led_Off(port, pin)	(port->ODR |=  (0x1<<pin))
//LED0_PORT 全部大写,就是这个量就只是一个宏定义的常量。
#define LED0_PORT GPIOB
#define LED0_PIN	5
#define LED1_PORT GPIOEc
#define LED1_PIN	5
......
int main(void)
{
    ....
    Led_On(LED0_PORT, LED0_PIN);	//一眼就能看出来是,打开LED0
    Delay_ms(200);
    Led_Off(LED0_PORT, LED0_PIN);	//关闭LED0
    Delay_ms(200);
    ....
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM