STM32学习笔记(二):GPIO 、RCC、位带操作、SysTick滴答定时器。

STM32学习笔记(二): GPIO 、RCC、位带操作、SysTick滴答定时器。

STM32学习笔记(一):简介、软件安装及新建工程

一、GPIO

GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,STM32 芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。

最基本的输出功能是由 STM32 控制引脚输出高、低电平,实现开关控制;最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。

1. GPIO 框图剖析

该图从最右端看起,最右端就是代表 STM32 芯片引出的 GPIO 引脚,其余部件都位于芯片内部。GPIO 引脚线路经过两个保护二极管后,向上流向“输入模式”结构,向下流向“输出模式”结构。

1.1 保护二极管及上、下拉电阻

引脚的两个保护二级管可以防止引脚外部过高或过低的电压输入,当引脚电压高于VDD时,上方的二极管导通,当引脚电压低于 VSS时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。

上面的电阻闭合,上拉输入,高电平;下面的电阻闭合,下拉输入,低电平;都不闭合,浮空输入,该状态引脚电平易受外界干扰而改变。

1.2 P-MOS 管和 N-MOS 管
1.2.1 推挽输出模式

推挽等效电路:

在这里插入图片描述

在该结构中输入高电平时,经过反向后,上方的 P-MOS 导通,下方的 N-MOS 关闭,对外输出高电平;而在该结构中输入低电平时,经过反向后,N-MOS管导通,P-MOS关闭,对外输出低电平。使其负载能力和开关速度都比普通的方式有很大的提高。推挽输出的低电平为 0 伏,高电平为 3.3 伏。

1.2.2 开漏输出模式

开漏电路:

在这里插入图片描述

在开漏输出模式时,上方的 P-MOS 管完全不工作。如果我们控制输出为 0,低电平,则 P-MOS 管关闭,N-MOS 管导通,使输出接地,若控制输出为 1 (它无法直接输出高电平)时,则 P-MOS 管和 N-MOS 管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。为正常使用时必须外部接上拉电阻。

1.2.3 关闭

引脚配置为输入模式时关闭

1.2.4 推挽与开漏输出模式选择

推挽输出模式一般应用在输出电平为 0 和 3.3 伏而且需要高速切换开关状态的场合。在STM32 的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。开漏输出一般应用在 I2C等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出 5 伏的高电平,就可以在外部接一个上拉电阻,上 拉电源为 5伏,并且把 GPIO 设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出 5 伏的电平。

1.3 输出数据寄存器

前面提到的双MOS管结构电路的输入信号,是由GPIO“输出数据寄存器GPIOx_ODR”提供的,因此我们通过修改输出数据寄存器的值就可以修改 GPIO 引脚的输出电平。而“置位/复位寄存器 GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。

1.4 复用功能输出

“复用功能输出”中的“复用”是指 STM32 的其它片上外设对 GPIO 引脚进行控制,此时 GPIO 引脚用作该外设功能的一部分。从其它外设引出来的“复用功能输出信号”与 GPIO 本身的数据据寄存器都连接到双 MOS 管结构的输入中,通过图中的梯形结构作为开关切换选择。

1.5 输入数据寄存器

看 GPIO 结构框图的上半部分,GPIO 引脚经过内部的上、下拉电阻,可以配置成上/下拉输入,然后再连接到施密特触发器,信号经过触发器后,模拟信号转化为 0、1 的数字信号,然后存储在“输入数据寄存器 GPIOx_IDR”中,通过读取该寄存器就可以了解 GPIO引脚的电平状态。

1.6 复用功能输入

与“复用功能输出”模式类似,在“复用功能输入模式”时,GPIO 引脚的信号传输到STM32 其它片上外设,由该外设读取引脚状态。如我们使用 USART 串口通讯时,需要用到某个 GPIO 引脚作为通讯接收引脚,这个时候就可以把该 GPIO 引脚配置成 USART 串口复用功能,使 USART 可以通过该通讯引脚的接收远端数据。

1.7 模拟输入输出

当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施密特触发器的,因为经过施密特触发器后信号只有 0、1 两种状态,所以 ADC 外设要采集到原始的模拟信号,信号源输入必须在施密特触发器之前。类似地,当 GPIO 引脚用于 DAC 作为模拟电压输出通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双 MOS 管结构,模拟信号直接输出到引脚。

2.GPIO 工作模式

2.1 输入模式(模拟/浮空/上拉/下拉)

浮空输入的电平是不确定的,完全由外部的输入决定,一般接按键的时候用的是这个模式。模拟输入则用于 ADC 采集。上拉模式在没有信号的时候是高电平,有信号输入时是低电平;下拉模式相反。

在输入模式时输出被禁止

2.2 输出模式(推挽/开漏)

在STM32 的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。开漏输出一般应用在 I2C等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出 5 伏的高电平,就可以在外部接一个上拉电阻,上 拉电源为 5伏,并且把 GPIO 设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出 5 伏的电平。

在输出模式时允许输入

二、STM32时钟系统

1. RCC 框图剖析—时钟部分

时钟树:

在这里插入图片描述

1.1 系统时钟
1.1.1 HSE 高速外部时钟信号

HSE 是高速的外部时钟信号,可以由有源晶振或者无源晶振提供,频率从 4-16MHZ 不等。HSE 最常使用的就是 8M 的无源晶振。当确定 PLL 时钟来源的时候,HSE 可以不分频或者 2 分频,这个由时钟配置寄存器 CFGR 的位 17:PLLXTPRE 设置。我们设置为 HSE 不分频。

1.1.2 PLL 时钟源

PLL时钟来源可以有两个,一个来自HSE,另外一个是 HSI/2,具体用哪个由时钟配置寄存器 CFGR 的位 16:PLLSRC 设置。 HSI 是内部高速的时钟信号,频率为 8M,根据温度和环境的情况频率会有漂移,一般不作为 PLL的时钟来源。 这里我们选 HSE作为 PLL的时钟来源 。

1.1.3 PLL 时钟 PLLCLK

通过设置 PLL 的倍频因子,可以对 PLL 的时钟来源进行倍频,倍频因子可以是:[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],具体设置成多少,由时钟配置寄存器 CFGR 的位21-18:PLLMUL[3:0]设置。当这里设置为 9 倍频,设置 PLL 的时钟来源为 HSE=8M,经过 PLL 倍频之后的 PLL 时钟:PLLCLK = 8M *9 = 72M。72M 是 ST 官方推荐的稳定运行时钟,如果你想超频的话,增大倍频因子即可,最高为128M。我们这里

设置 PLL 时钟:PLLCLK = 8M *9 = 72M。

1.1.4 系统时钟 SYSCLK

系统时钟来源可以是:HSI、PLLCLK、HSE,具体的时钟配置寄存器 CFGR 的位 1-0:SW[1:0]设置。我们这里设置系统时钟:SYSCLK = PLLCLK = 72M。

1.1.5 AHB 总线时钟 HCLK

分频因子可以是:[1,2,4,8,16,64,128,256,512],具体的由时钟配置寄存器 CFGR的位 7-4 :HPRE[3:0]设置。片上大部分外设的时钟都是经过 HCLK 分频得到,至于 AHB总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB 的时钟即可。我们这里设置为 1 分频,即 HCLK=SYSCLK=72M。

1.1.6 APB1 总线时钟 PCLK1

APB1 总线时钟 PCLK1 由 HCLK 经过低速 APB 预分频器得到,分频因子可以是:[1,2,4, 8,16],具体的由时钟配置寄存器 CFGR 的位 10-8:PRRE1[2:0]决定。HCLK1 属于低速的总线时钟,最高为 36M,片上低速的外设就挂载到这条总线上,比如USART2/3/4/5、SPI2/3,I2C1/2等。至于 APB1总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB1 的时钟即可。我们这里设置为 2 分频,即 PCLK1 = HCLK/2 = 36M。

1.1.7 APB2 总线时钟 PCLK2

APB2总线时钟 PCLK2由 HCLK经过高速 APB2预分频器得到,分频因子可以是:[1,2,4, 8,16],具体由时钟配置寄存器 CFGR的位 13-11:PPRE2[2:0]决定。HCLK2属于高速的总线时钟,片上高速的外设就挂载到这条总线上,比如全部的 GPIO、USART1、SPI1 等。至于 APB2 总线上的外设的时钟设置为多少,得等到我们使用该外设的时候才设置,我们这里只需粗线条的设置好 APB2 的时钟即可。我们这里设置为 1 分频,即 PCLK2 = HCLK = 72M。

1.2 其他时钟
1.2.1 A、USB 时钟

USB 时钟是由 PLLCLK 经过 USB 预分频器得到,分频因子可以是:[1,1.5],具体的由时钟配置寄存器 CFGR 的位 22:USBPRE 配置。USB 的时钟最高是 48M,根据分频因子反推 过 来 算 , PLLCLK 只 能 是 48M 或者是 72M 。 一 般 我 们 设 置 PLLCLK=72M ,USBCLK=48M。USB 对时钟要求比较高,所以 PLLCLK 只能是由 HSE 倍频得到,不能使用 HSI 倍频。

1.2.2 B、Cortex 系统时钟

Cortex 系统时钟由 HCLK 8 分频得到,等于 9M,Cortex 系统时钟用来驱动内核的系统定时器 SysTick,SysTick 一般用于操作系统的时钟节拍,也可以用做普通的定时。

1.2.3 C、ADC 时钟

ADC时钟由 PCLK2经过 ADC预分频器得到,分频因子可以是[2,4,6,8],具体的由时钟配置寄存器 CFGR的位 15-14:ADCPRE[1:0]决定。很奇怪的是怎么没有 1分频。ADC时钟最高只能是 14M,如果采样周期设置成最短的 1.5 个周期的话,ADC 的转换时间可以达到最短的 1us。如果真要达到最短的转换时间 1us 的话,那 ADC 的时钟就得是 14M,反推PCLK2 的时钟只能是:28M、56M、84M、112M,鉴于 PCLK2 最高是 72M,所以只能取28M 和 56M。

1.2.4 D、RTC 时钟、独立看门狗时钟

RTC 时钟:实时时钟,是个独立的BCD定时器/计数器。RTC 提供一个日历时钟,两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。

RTC 时钟可由 HSE/128 分频得到,也可由低速外部时钟信号 LSE 提供,频率为32.768KHZ,也可由低速内部时钟信号 HSI 提供,具体选用哪个时钟由备份域控制寄存器BDCR的位9-8:RTCSEL[1:0]配置。

独立看门狗:用通俗一点的话来解释就是一个 12 位的递减计数器,当计数器的值从某个值一直减到 0 的时候,系统就会产生一个复位信号,即 IWDG_RESET。如果在计数没减到 0 之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作。

独立看门狗的时钟由LSI提供,且只能是由LSI提供,LSI 是低速的内部时钟信号,频率为 30~60KHZ 直接不等,一般取 40KHZ。

1.2.5 E、MCO 时钟输出

MCO 是 microcontroller clock output 的缩写,是微控制器时钟输出引脚,在 STM32 F1系列中 由 PA8 复用所得,主要作用是可以对外提供时钟,相当于一个有源晶振。MCO 的时钟来源可以是:PLLCLK/2、HSI、HSE、SYSCLK,具体选哪个由时钟配置寄存器CFGR 的位 26-24:MCO[2:0]决定。除了对外提供时钟这个作用之外,我们还可以通过示波器监控 MCO 引脚的时钟输出来验证我们的系统时钟配置是否正确。

2.时钟配置函数

2.1时钟初始化配置函数

STM32 系统复位后首先进入 SystemInit 函数进行时钟的设置,然后进入主函数 main。
SystemInit 函数执行完,时钟大小设置如下:
SYSCLK(系统时钟) =72MHz
AHB 总线时钟(HCLK=SYSCLK) =72MHz
APB1 总线时钟(PCLK1=SYSCLK/2) =36MHz
APB2 总线时钟(PCLK2=SYSCLK/1) =72MHz
PLL 主时钟 =72MHz

2.2时钟使能配置函数
2.2.1 时钟使能函数

外设时钟使能函数

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);

外设时钟使能函数有两个形参,第一个是你所使用的外设所挂接的时钟,第二个是选择你用的外设时钟使能还是失能。比如我们要使能端口 GPIOC,那么第一个传递的参数是:RCC_APB2Periph_GPIOC 宏,第二个传递的参数是ENABLE 使能。从第一个参数名来看也非常好理解,RCC 表示复位和时钟控制器,APB2 表示GPIOC 是挂接在 APB2 总线上,Periph 表示外设,后面的 GPIOC 表示我们使能的是 GPIOC 端口。第二个参数 ENABLE 表示使能。
时钟源使能函数

void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);

这些函数都是用来使能相应的时钟源,比如我们要使能 PLL 时钟,那么就调用 RCC_PLLCmd 函数,函数有一个形参,如果为 ENABLE 表示使能,DISABLE 表示失能。

2.2.2 时钟源和倍频因子配置函数

这类函数主要用来选择相应的时钟源和配置时钟倍频因子,比如系统时钟,它可以由HSE、HSI 或者 PLLCLK 作为它的时钟源,具体选择哪个,就是通过时钟源配置函数实现。比如我们设置 HSE 作为系统时钟源,那么调用的函数就是:

RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);//配置时钟源为 HSE

在前面也介绍了 APB1 的时钟频率是 HCLK 的 2 分频。那么可以调用下面这个函数来实现:

RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 APB1 时钟(PCLK1)

时钟倍频因子配置函数主要用来修改系统的时钟频率。

void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t RCC_PLLMul);

第一个参数是 PLL 时钟源选择,我们例程中采用的都是 HSE 作为 PLL 的时钟源,可以设置为 :RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2。
第二个参数就是倍频因子值:(RCC_PLLMul_2~RCC_PLLMul_16)。

2.2.3 外设复位函数
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);

在 STM32F10x 高容量的芯片中没有RCC_AHBPeriphResetCmd 函数。这类函数与前面讲解的外设时钟使能函数用法一样,只不过外设时钟使能函数是用于使能外设时钟,而这类函数是用于外设复位,从函数名也可以区分出来。

2.3 自定义系统时钟
/**************************************************************************
*****
* 函 数 名 : RCC_HSE_Config
* 函数功能 : 自定义系统时钟,可以通过修改 PLL 时钟源和倍频系数实现时
钟调整
* 输 入 : div:RCC_PLLSource_HSE_Div1/RCC_PLLSource_HSE_Div2
pllm:RCC_PLLMul_2-RCC_PLLMul_16
* 输 出 : 无
***************************************************************************
****/
void RCC_HSE_Config(u32 div,u32 pllm) //自定义系统时间(可以修改时钟)
{
	RCC_DeInit(); //将外设 RCC 寄存器重设为缺省值
	RCC_HSEConfig(RCC_HSE_ON);//设置外部高速晶振(HSE)
	if(RCC_WaitForHSEStartUp()==SUCCESS) //等待 HSE 起振
	{
		RCC_HCLKConfig(RCC_SYSCLK_Div1);//设置 AHB 时钟(HCLK)
		RCC_PCLK1Config(RCC_HCLK_Div2);//设置低速 AHB 时钟(PCLK1)
		RCC_PCLK2Config(RCC_HCLK_Div1);//设置高速 AHB 时钟(PCLK2)
		RCC_PLLConfig(div,pllm);//设置 PLL 时钟源及倍频系数
		RCC_PLLCmd(ENABLE); //使能或者失能 PLL
		while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET);//检查指定的 RCC标志位设置与否,PLL 就绪
		RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);// 设 置 系 统 时 钟(SYSCLK)
		while(RCC_GetSYSCLKSource()!=0x08);//返回用作系统时钟的时钟源,0x08:PLL 作为系统时钟
	}
}

在未修改系统时钟时,系统初始化后的时钟是 72M,对应着此函数参数设置如下:

RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);

如果现在我们想让系统时钟为 36M,只需要将参数值修改即可,如下:

RCC_HSE_Config(RCC_PLLSource_HSE_Div2,RCC_PLLMul_9);

此时修改的是 div 这个参数值,此参数用来对 HSE 时钟分频系数设置,从时钟树可知,HSE 可以直接流入到 PLLSRC,还可以经过 2 分频后给 PLLSRC。它的取值为 RCC_PLLSource_HSE_Div1 或 RCC_PLLSource_HSE_Div2。

三、STM32位带操作

STM32 位操作优点非常多,我们这里就列举几个突出的:
(1)对于控制 GPIO 的输入和输出非常简单
(2)操作串行接口芯片非常方便(DS1302、74HC595 等),如果采用库函数的话,那么这个时序编写就非常不方便。
(3)代码简洁,阅读方便

1.位带操作

将每个位膨胀成一个 32 位字,当访问这些字的时候就达到了访问比特的目的。比方说 BSRR 寄存器有 32 个位,那么可映射到 32 个地址上,当我们去访问这 32 个地址就达到访问 32 个比特的目的。
STM32F1 中有两个区域支持位带操作,一个是 SRAM 区的最低 1MB 范围,一个是片内外设区的最低 1MB 范围(APB1、APB2、AHB 外设)。
在这里插入图片描述
通常我们使用位带操作都是在外设区,在外设区中应用比较多的也就是GPIO 外设,SRAM 区内很少使用位操作。
SRAM 的最低 1MB 区域,地址范围是 0X2000 0000-0X200FFFFF。片内外设最低 1MB 区域,地址范围是 0X4000 0000-0X400F FFFF。
在 SRAM 区中还有 32MB 空间,其地址范围是 0X2200 0000-0X23FF FFFF,它是 SRAM 的 1MB 位带区膨胀后的位带别名区,片内外设区的 32MB 地址范围是0X4200 0000-0X43FF FFFF。

2.位带区与位带别名区地址转换

(1)外设位带别名区地址
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n,n值的范围是 0-7,则该比特在别名区的地址为:

AliasAddr=0x42000000+ (A-0x40000000)*8*4 +n*4

0x42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以*8,一个位膨胀后是 4 个字节,所以*4,n 表示在该字节的第几位,因为一个位经过膨胀后是4个字节,所以也*4。
(2)SRAM 位带别名区地址
类比于外设位带别名区地址:

AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4

为了操作方便,我们将这两个公式进行合并,通过一个宏来定义,并把位带地址和位序号作为这个宏定义的参数。公式如下:

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))

addr & 0xF0000000 是为了区分我们操作的是 SRAM 还是外设,实际上就是获取最高位的值是 4 还是 2。如果操作的是外设,那么 addr & 0xF0000000 结果就是 0x40000000,后面**+0x2000000**就等于 0X42000000,0X42000000 是外设别名区的起始地址。如果操作的是 SRAM,那么 addr & 0xF0000000 结果就是0x20000000,后面+0x2000000 就等于 0X22000000,0X22000000 是 SRAM 别名区的起始地址。
addr & 0x000FFFFF相当于位带地址减去 0X20000000 或
0X40000000。
<<5 相当于*8*4, <<2 相当于*4。
(例:0001(1)左移一位变0010(2)、左移二位变0100(4)、左移三位变1000(8)、左移四位变1 0000(16)、左移五位变10 0000(32))
最后就可以通过指针形式来操作这些位带别名区地址,实现位带区对应位的操作。代码如下:

//把 addr 地址强制转换为 unsigned long 类型的指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
//把位带别名区内地址转换为指针 ,获取地址内的数据
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))

volatile 关键字,volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有 volatile 关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

3.GPIO 位带操作

通过位带操作控制 STM32 引脚输入与输出,因此我们就以 GPIO 中 IDR 和 ODR 这两个寄存器的位操作进行讲解。

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)   

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)   
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)   

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)   
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)   

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  

根据《STM32F10x 中文参考手册》对应的 GPIO 寄存器章节(下图)
在这里插入图片描述
在这里插入图片描述
可以知道,IDR和 ODR 寄存器相对于 GPIO 基地址的偏移量是 8 和 12。
从上述代码中可以看到有 GPIOx_BASE,这个也是一个宏,里面封装的是相应 GPIO 端口的基地址,在库函数中有定义。
获取寄存器的地址以后,就可以采用位操作的方法来操作 GPIO 的输入和输出。
上述代码中我们已经将 STM32F1 芯片的所有端口都进行了位定义封装,假如要使用 PC0 管脚进行输出,那么就可以调用 PCout(n)宏,n 值即为 0。假如使用的是 PC0 管脚作为输入,那么就可以调用 PCin(n)宏,n 值即为 0。其他端口调用方法类似。

四、SysTick 系统定时器

SysTick 定时器也叫 SysTick 滴答定时器,是一个 24 位向下递减的定时器,每计数一次所需时间为 1/SYSTICK,SYSTICK 是系统定时器时钟,它可以直接取自系统时钟,还可以通 过系统时钟 8 分频后获取。若取自系统时钟,每计数一次所需时间为 1/72us,换句话说在 1us 的时间内会计数72 次。当定时器计数到 0 时,将从 LOAD 寄存器中自动重装定时器初值,重新向下递减计数,如此循环往复。如果 开启 SysTick 中断的话,当定时器计数到 0,将产生一个中断信号。因此只要知 道计数的次数就可以准确得到它的延时时间。
SysTick 定时器通常应用在操作系统中,为其提供时钟周期。

1.SysTick 定时器寄存器

我们要操作 SysTick 定时器就需要了解它的寄存器功能,分别是 CTRL、LOAD、VAL、CALIB。

1.1 CTRL 寄存器

CTRL 是 SysTick 定时器的控制及状态寄存器。其相应位功能如下:
在这里插入图片描述
CLKSOURCE 位是用于选择 SysTick 定时器时钟来源,如果该位为 1,表示其时钟是由系统时钟直接提供即 72M。如果为 0,表示其时钟是由系统时钟八分频后提供即 72/8=9M。

CTRL 寄存器的第 16 位是 SysTick 递减到 0 的标志位,如果递减到 0,此位置 1,通过读取该位来判断延时是否完成,从而退出 while 循环。

1.2 LOAD 寄存器

LOAD 是 SysTick 定时器的重装载数值寄存器。其相应位功能如下:
在这里插入图片描述
因为 STM32F1 的 SysTick 定时器是一个 24 位递减计数器,因此重装载寄存器中只使用到了低 24 位,即 bit0-bit23。当系统复位时,其值为 0。

1.3 VAL 寄存器

VAL 是 SysTick 定时器的当前数值寄存器。其相应位功能如下:
在这里插入图片描述
同样只有 bit0-bit23 有效,复位时值为 0。

1.4 CALIB 寄存器

CALIB 是 SysTick 定时器的校准数值寄存器。其相应位功能如下:
在这里插入图片描述

2.SysTick 定时器操作步骤

(1)设置定时器重装值

(2)清空当前计数值

(3)设置时钟源,启动定时器

(4)等待计数到0,CTRL寄存器的第16位是SysTick递减到0的标志位,如果为0,会置1

(5)关闭定时器

#include "stm32f10x.h"

/**
  * @brief  微秒级延时
  * @param  xus 延时时长,范围:0~233016(LOAD是24位寄存器,最大数是2^24,xus最大值为2^24 ÷ 72 = 233016)
  * @retval 无
  */
void Delay_us(uint32_t xus)
{
	SysTick->LOAD = 72 * xus;				//设置定时器重装值
	SysTick->VAL = 0x00;					//清空当前计数值
	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0,CTRL寄存器的第16位是SysTick递减到0的标志位,如果为0,会置1
	SysTick->CTRL = 0x00000004;				//关闭定时器
}

/**
  * @brief  毫秒级延时
  * @param  xms 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_ms(uint32_t xms)
{
	while(xms--)
	{
		Delay_us(1000);
	}
}
 
/**
  * @brief  秒级延时
  * @param  xs 延时时长,范围:0~4294967295
  * @retval 无
  */
void Delay_s(uint32_t xs)
{
	while(xs--)
	{
		Delay_ms(1000);
	}
} 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值