STM32F1xx 系统时钟

来源:STM32F1中文参考手册 6.2时钟
时钟的作用
决定了程序执行的速度,给芯片提供一个稳定的执行频率
- STM32F103R8 最高速率是多少??
72 MHz maximum frequency
如果采用最高频率:执行一条指令 1/72M s ==> 1/72us
精简指令集:几乎所有的指令都是消耗一个时钟节拍(1/72 us)执行
R8的时钟来源
-
高速外部时钟信号(HSE) 4 – 16M 给系统时钟提供时钟信号
-
高速内部时钟信号(HSI) 内部RC振荡器
-
低速外部时钟信号(LSE) 32.768 给RTC实时时钟提供时钟信号
-
低速内部时钟信号(LSI)
系统时钟来源
-
HSI振荡器时钟
-
HSE振荡器时钟
-
PLL时钟
R8的时钟树和外设分布

来自:STM32F103R8数据手册 2.1 Device overview
STM32时钟的配置
- PLL的倍频因子:HSE * PLLMUL = 72M Hz
- AHB的频率:72M Hz
- APB2的频率:72M Hz
- APB1的频率:36M Hz
修改HSE_VALUE
设置函数static void SetSysClockTo72(void)
中的值

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口功能
推挽:既能输出低电平,又能输出高电平
开漏:只能输出低电平。 -- 用于总线通信,外部具有上拉功能。
通用推挽输出、通用开漏输出、复用推挽输出、复用开漏输出。

寄存器配置GPIO的模式
寄存器概念和作用
“寄存器” 是 “地址” 的别名。
操作寄存器就是操作地址。
在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 分配,
//通过操作 结构体的成员 达到操作对应的地址的内容。
---------------------------------------------------------

上述的结构体,可以通过操作 “结构体中的成员” ,从而达到对 “地址内容” 的更改,也就是完成了对 “寄存器内容” 的修改。
配置GPIO输入 / 输出模式
配置 7 :0 位(低8位)

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

配置 15 :8(高8位)

位 清零 与 置一
不影响其他位的值
位 清零: &= ~( );
位 置一: |= ( );
将想要 清零 或者 置一 的位 的值 对应为 1 ,带入上面的方法中就行了
将GPIOB5配置为 “通用推挽输出” 对应的4个位为 0011
先清零: GPIOB->CRL &= ~( 0xF << 20 ); //将4个位都清零 4*5=20。
后置位: GPIOB->CRL |= ( 0x3 << 20 ); //将对应的位,设置为所需的功能。
读取输入数据
出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
端口输入数据寄存器(GPIOx_IDR) (x=A..E)

如何判断固定的位的值??
& ( );
判断 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。

想要控制某个位输出 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);
....
}