STM32-GPIO和定时器使用

一、GPIO工作方式

GPLO是通用输入/输出(General Purpose l/O)的简称,主要用于工业现场需要用到数字量输入/输出的场合,例如:
输出功能:继电器、LED、蜂鸣器等的控制
输入功能:传感器状态、高低电平等信息的读取
复用功能:片内外设的对外接口
时序模拟:模拟SPI、I2C等常用接口的时序

1.GPIO结构:在这里插入图片描述
在这里插入图片描述
钳位电路:GPIO内部具有钳位保护二极管,其作用是防止从外部管脚Pin输入的电压过高或者过低。VDD正常供电是3.3V。如果从Pin输入的信号(假设任何输入信号都有一定的内阻)电压超过VDD加上二极管D1的导通压降(假定在0.7V左右),则二极管D1导通,会把多于的电流引到VDD,而真正输入到内部的信号电压不会超过4V。同理,如果从Pin输入的信最电压比GND还低,则由于二极管D2的作用,会把实际输入质部的信号电压钳制在-0.7V左右。

2.STM32F103有PA、PB、PC、PD和PE五个16位通用接口,每个GPIO端口有16个口线对应16个管脚,编号为0~15。每个 IO 口可以自由编程,但 IO口寄存器必须要按 32 位字被访问。
3.三种速度:

  • GPIO引脚速度:GPIO_Speed_2MHz (10MHz, 50MHz) ; ①2MHz②10MHz③50MHz
    又称GPIO输出驱动电路响应速度(芯片内部在I/O口的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要选择合适的驱动电路,通过选择速度来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。)
    可理解为输出驱动电路的带宽:即一个驱动电路可以不失真地通过信号的最大频率。
    如果一个信号的频率超过了驱动电路的响应速度,就有可能信号失真。
    GPIO的引脚速度跟应用相匹配,速度配置越高,噪声越大,功耗越大。
    带宽速度高的驱动器耗电大、噪声也大,带宽低的驱动器耗电小、噪声也小。使用合适的驱动器可以降低功耗和噪声。
    高频的驱动电路,噪声也高,当不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。当然如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。关键是GPIO的引脚速度跟应用匹配(推荐10倍以上)。
    ①USART串口,若最大波特率只需115.2k,那用2M的速度就够了,既省电也噪声小。
    ②I2C接口,若使用400k波特率,若想把余量留大些,可以选用10M的GPIO引脚速度。
    ③SPI接口,若使用18M或9M波特率,需要选用50M的GPIO的引脚速度。

  • 对翻转速度理解:
    输入/输出寄存器的0 ,1值反映到外部引脚(APB2上)高低电平的速度.手册上指出GPIO最大翻转速度可达18MHz。
    通过简单的程序测试,用示波器观察到的翻转时间: 是综合的时间,包括取指令的时间、指令执行的时间、指令执行后信号传递到寄存器的时间(这其中可能经过很多环节,比如AHB、APB、总线仲裁等),最后才是信号从寄存器传输到引脚所经历的时间。
    如:有上拉电阻,其阻值越大,RC延时越大,即逻辑电平转换的速度越慢,功耗越大。

  • GPIO 输出速度:与程序有关,(程序中写的多久输出一个信号)。
    ①GPIO口设为输入时,输出驱动电路与端口是断开,所以输出速度配置无意义。
    ②在复位期间和刚复位后,复用功能未开启,I/O端口被配置成浮空输入模式。
    ③所有端口都有外部中断能力。为了使用外部中断线,端口必须配置成输入模式。
    ④GPIO口的配置具有上锁功能,当配置好GPIO口后,可以通过程序锁住配置组合,直到下次芯片复位才能解锁。

4.功能特性

  • 具有外部中断/唤醒能力,端口配置成输入模式时,具有外部中断能力。每个GPIO都可以作为外部中断/唤醒线。
  • 具有复用功能,复用功能的端口兼有I/O功能等。
  • 复用功能的重映射:为了使不同封装的外设I/O功能的数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。这可以通过软件配置到相应的寄存器来完成。
  • GPlO口的配置具有锁定机制。当配置好GPIO口后,在端口位上执行了锁定(LOCK) ,可以通过程序锁住配置组合,在下一次复位之前,将不能再更改端口位的配置。

1-1 输入模式

在浮空/上拉/下拉模式时:

  • 输出驱动器关闭
  • 施密特触发器打开,可以获取引脚状态
  • 通过寄存器使能上/下拉电阻:浮空/上拉/下拉输入
  • 引脚电平状态将存入输入数据寄存器

在模拟输入时:

  • 输出驱动器关闭
  • 施密特触发器关闭、上/下拉电阻关闭
  • 读输入数据寄存器的值为0
  • 信号被送入AD模块

1-1-1 输入浮空模式

在这里插入图片描述
既不接高电平,也不接低电平

1-1-2 上拉输入

在这里插入图片描述

1-1-3 下拉输入

在这里插入图片描述

1-1-4 模拟输入

在这里插入图片描述

常见的ADC采集

1-2 输出模式

  • 高低电平从引脚输出。
  • 触发器为开启状态。
  • 读输入数据寄存器可以获得引脚电平状态。

1-2-1 开漏输出

在这里插入图片描述

  • 只可以输出强低电平,高电平得靠外部电阻拉高。
  • 输出端相当于三极管的集电极、要得到高电平状态需要外加上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)
  • P-mos管一直关闭
  • 开漏输出优点:
    1)具有逻辑“线与”功能,多个开漏输出信号允许直接并联;
    2)电平转换。
  • 开漏输出的应用场合:
    1)综合保护电路2)总线系统

1-2-2 复用开漏输出

在这里插入图片描述

1-2-3 推挽输出

在这里插入图片描述

  • 可以输出强高低电平,连接数字器件
  • P-mos管和N-mos管交替导通

1-2-4 复用推挽输出

在这里插入图片描述

二、 GPIO相关配置寄存器

每组GPIO端口的寄存器包括:
两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)
两个32位数据寄存器(GPIOx_IDR,GPIOx_ODR)
一个32位置位/复位寄存器(GPIOx_BSRR)
一个16位复位寄存器(GPIOx_BRR)
一个32位锁存寄存器(GPIOx_LCKR)
每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问)。

2-1 端口配置寄存器CRL,CRH

在这里插入图片描述

1.CRL/CRH每4位控制一个IO口,CRL控制标号0~7的口,CRH控制标号8~15的口
2.配置时,先配置MODE确定输入输出及端口速度,再配置CNF确定具体的输入/出模式,具体的上拉/下拉由PxODR寄存器决定。
在这里插入图片描述

2-2 端口输入数据寄存器IDR

1.IDR 是一个端口输入数据寄存器,只用了低 16 位,每个位控制该组IO的一个IO口,对应的是IO口的输入电平。该寄存器为只读寄存器,并且只能以16 位的形式读出。
在这里插入图片描述

2-3 端口输出数据寄存器ODR

在这里插入图片描述
1.ODR是一个端口输出数据寄存器,也只用了低16位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前IO口的输出状态。而向该寄存器写数据,则可以控制某个IO口的输出电平。
2.注:在输入模式下,通过该寄存器配置上拉还是下拉。

2-4 端口位设置/清零寄存器BSRR

在这里插入图片描述

2-5 端口位清除寄存器BRR

与BSRR寄存器的高16位几乎一样(时至20220928理解)

三、STM32引脚说明

1.端口复用

  • 所谓复用,就是些端口不仅仅可以做为通用IO口,还可以复用为些外设引脚,比如PA9、PA10可以复用为STM32的串口1引脚。
  • 作用:最大限度的利用端口资源

2.端口重映射功能

  • 把某些功能引脚映射到其他引脚
  • 比如串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7
  • 作用:方便布线

3.所有IO口都可以作为中断输入

四、GPIO库函数介绍

1.1个初始化函数:

void GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);
  • 作用:初始化一个或多个I/O口(同一组)的工作方式和速度。
  • 该函数主要是操作GPIO_CRL(CRH)寄存器,在上拉/下拉的时候有设置BSRR/BRR寄存器。
  • GPIOx:GPIOA~GPIOG
typedef struct
{
unit16_t GPIO_Pin;//指定要初始化的IO口
GPIOSpeed_TypeDef GPIO_Speed;//设置IO口输出速度
GPIOMode_Typedef GPIO_Mode;//设置工作模式:8种中的一个
}GPIO_InitTypeDef;
  • 注:外设(包括GPIO)在使用之前,几乎都要先使能对应的时钟。不同的IO组,调用的时钟使能函数不一样

初始化样例

GPIOInitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//IO口速度为50MHz
GPIO_Init(GPIOB,&GPIO_InitStructure);//根据设定参数初始化GPIOB.5
GPIO_Mode描述
GPIO_Mode_AIN设置管脚工作模式为模拟输入
GPIO_Mode_IN_FLOATING设置管脚工作模式为浮空输入
GPIO_Mode_IPD设置管脚工作模式为输入下拉
GPIO_Mode_IPU设置管脚工作模式为输入上拉
GPIO_Mode_OUT_OD设置管脚工作模式为开漏输出
GPIO_Mode_OUT_PP设置管脚工作模式为推挽输出
GPIO_Mode_AF_OD设置管脚工作模式为复用的开漏
GPIO_Mode_AF_PP设置管脚工作模式为复用的推挽

2.2个读取输入电平函数:

unit8_t GPIO_ReadInputDataBit(GPIO_Typedef*GPIOx,unit16_t GPIO_Pin);
//作用:读取某个GPIO的输入电平,实际操作的是GPIOx_IDR寄存器
GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5);//读取GPIOA组中所有IO口的电平 
unit16_t GPIO_ReadOutputData(GPIO_TypeDef*GPIOx);
//作用:读取某个GPIO的输入电平,实际操作的是GPIOx_IDR寄存器
GPIO_ReadOutputData(GPIOA);//读取GPIOA组中所有IO口的电平 

3.2个读取输出电平函数

unit8_t GPIO_ReadOutputDataBit(GPIO_TypeDef*GPIO,unit16_t GPIO_Pin);
unit16_t GPIO_ReadOutputData(GPIO_TypeDef*GPIO);

4.4个设置输出电平函数

void GPIO_SetBits(GPIO_TypeDef*GPIOx,unit16_t GPIO_Pin);
//作用:设置某个IO口输出为高电平,实际操作BSRR寄存器
void GPIO_ResetBits(GPIO_TypeDef*GPIOx,unit16_t GPIO_Pin);
//作用:设置某个IO口输出为低电平,实际操作BRR寄存器
void GPIO_WriteBit(GPIO_TypeDef*GPIOx,unit16_t GPIO_Pin,BitAction BitVal);
void GPIO_Write(GPIO_TypeDef*GPIOx,unit16_t PortVal);
//这两个函数不常用,也是用来设置IO口输出电平

5.头文件中,使用

#ifndef
#define
#endif

条件编译,避免头文件内容重复定义。

五、寄存器操作

5-1 外设时钟复位寄存器RCC_APB2RSTR

1.查看数据手册6.3.4
在这里插入图片描述

注:或运算: A ∣ = B    ⟹    A = A ∣ B A|=B\implies A=A|B A=BA=AB

5-2 端口配置寄存器

在这里插入图片描述

六、位操作

1.位操作基本原理:把每个比特膨胀为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR奇存器有32个位,那么可以映射到32个地址,我们去访问(读-改-写)这32个地址就达到访问32个比特的目的。
2.哪些区域支持位操作:
其中一个是 SRAM 区的最低1MB范围:0x20000000- 0x200FFFFF (SRAM 区中的最低1MB)
第二个则是片内外设区的最低1MB范围:0x40000000 - 0x400FFFFF(片上外设区中最低1MB)

IO口操作的总结:
①对该IO口时钟初始化
②初始化IO口的模式
③对IO口输入/出进行控制(库函数/寄存器/位操作)

七、蜂鸣器的使用(本质上还是对GPIO的操作)

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。

1.我的STM32开发板板载的蜂鸣器是电磁式的有源蜂鸣器
在这里插入图片描述
这里的有源不是指电源的“源”,而是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路,一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供2~5Khz左右的方波驱动,才能发声。
2.需要注意的是:IO口的电流驱动能力是很有限的,所有一般不能通过IO口直接驱动大功率器件。
在这里插入图片描述
如图所示,使用三极管S8050来驱动蜂鸣器(利用了三极管电流放大作用)。
R33作用:在STM32复位后,IO口引脚默认浮空,浮空状态下,IO口产生跳变的电压可能会使蜂鸣器“嘀~嘀~”响,通过R33下拉电阻使得小电流直接通过R33接地,只有电流达到一定程度才会通过三极管放大。

八、按键输入(GPIO口作为输入)

1.关于按键的扫描方式:
①支持连续按:按下不松开,一直会检测到有效信号
②不支持连续按:按下不松开,只能检测到一次有效信号
故而可以联想到C语言关键字static

  • Static申明的局部变量,存储在静态存储区。
  • 它在函数调用结束之后,不会被释放。它的值会一直保留下来。
  • 所以可以说static申明的局部变量,具有记忆功能。
int getValue(void)
{
	int flag=0;
	flag++;
	return flag;
}

返回的值每次都为1,因为每次执行这个函数,flag都会初始化为0,没有记忆功能

int getValue(void)
{
	static int flag=0;
	flag++;
	return flag;
}

返回的值每次都加1,static有记忆功能
2.按键扫描函数的理解:
在这里插入图片描述
key_up=1表示按键按下,key_up=0表示按键松开
如果支持连按则会一直检测按键按下的脉冲,反之则只会检测一次

九、C语言常用知识

9-1 位操作

运算符含义运算符含义
&按位与~取反
|按位或<<左移
^按位异或>>右移
GPIOA->CRL&=0xFFFFFF0F;//将4~7位清0
GPIOA->CRL|=0x00000040;//设置相应位的值
GPIOA->ODR|1<<5;	   //将ODR寄存器的第5位设置为1
TIMx->SR=(unit16_t)~TIM_FLAG;

9-2 define宏定义关键词

1.define是C语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。
常见格式:#define 标识符 字符串
“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串。
2.ifdef条件编译(时至20220930不理解)
单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

#ifdef 标识符
程序段1
#else
程序段2
#endif

9-3 extern变量申明

1.C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
2.注:对于extern申明变量可以多次,但定义只有一次。

9-4 typedef 类型别名

定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。

typedef unsigned int unit32_t;//将unsigned int替换为unit32_t
typedef unsigned char unit8_t;
typedef unsigned short int unit16_t;

9-5 结构体

Struct 结构体名{
成员变量1;
成员变量2...
}变量名列表

在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:Struct 结构体名字 结构体变量列表;
同一个类型可以用数组,不同类型可以用结构体组织。结构体可扩展性强。

十、MDK中寄存器地址名称映射分析

对MCU,一切底层配置,最终都是配置寄存器。
感觉迷迷瞪瞪似懂非懂的(时至20221006不理解)

十一、STM32时钟系统在这里插入图片描述

1.STM32有5个时钟源:HSI、HSE、LSI、LSE、PLL.

  • HSI是高速内部时钟, RC振荡器,频率为8MHz,精度不高。
  • HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MIH~16MHz
  • LSI是低速内部时钟,RC振荡器,频率为40kH吃,提供低功耗时钟。
  • LSE是低速外部时钟,接频率为32.768kHz的石英晶体。RTC
  • PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍但是其输出频率最大不得超过72MHz。"

2.系统时钟SYSCLK可来源于三个时钟源:

  • HSI振荡器时钟
  • HSE振荡器时钟
  • PLL时钟

3.STM32可以选择—个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、 HSI、HSE、或者系统时钟。
4.任何一个外设在使用之前,必须首先使能其相应的时钟。
5.RCC相关头文件和固件库源文件
头文件:stm32f10x_rcc.h
源文件:stm32f10x_rcc.c

  • 时钟使能配置
RCC_LSEConfig();
RCC_HSEConfig();
RCC_HSICmd();
RCC_LSICmd();
RCC_PLLCmd();
  • 时钟源相关配置
RCC_PLLConfig();
RCC_SYSCLKConfig();
RCC_RTCCLKConfig();
  • 分频系数选择配置
RCC_HCLKConfig();
RCC_PCLK1Config();
RCC_PCLK2Config();
  • 外设时钟使能
RCC_APB1PeriphClockCmd();//APB1线上外设时钟使能
RCC_APB1PeriphClockCmd();//APB2线上外设时钟使能
RCC_AHBPeriphClockCmd();//AHB线上外设时钟使能
  • 其他外设时钟配置
RCC_ADCCLKConfig();
  • 状态参数获取
RCC_GetClocksFreq();
RCC_GetSYSCLKSource();
RCC_GetFlagStatus();
  • RCC中断相关函数
RCC_ITConfig();
RCC_GetITStatus();
RCC_ClearITPendingBit();

6.RCC相关配置寄存器

typedef struct
{
  __IO uint32_t CR;//HSL,HSE,,CSS,PLL等的使能和就绪标志位
  __IO uint32_t CFGR;//PLL等的时钟源选择,分频系数设定
  __IO uint32_t CIR;//清除/使能 时钟就绪中断
  __IO uint32_t APB2RSTR;//APB2线上外设复位寄存器
  __IO uint32_t APB1RSTR;//APB1线上外设复位寄存器
  __IO uint32_t AHBENR;//DMA,SDIO等时钟使能
  __IO uint32_t APB2ENR;//APB2线上时钟使能
  __IO uint32_t APB1ENR;//APB1线上时钟使能
  __IO uint32_t BDCR;//备份域控制寄存器
  __IO uint32_t CSR;//控制状态寄存器
  }RCC_TypeDef

7.时钟系统初始化函数剖析(部分)

void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */

在这里插入图片描述

static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/    
  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* Wait till HSE is ready and if Time out is reached exit等待振荡器稳定 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)
  {
    HSEStatus = (uint32_t)0x01;//判断HSE时钟就绪
  }
  else
  {
    HSEStatus = (uint32_t)0x00;
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* Enable Prefetch Buffer */
    FLASH->ACR |= FLASH_ACR_PRFTBE;
	//这三行代码的原因是因为CPU的速度是比FLASH的速度快很多的
    /* Flash 2 wait state,通过查看手册对ACR寄存器介绍,如果系统时钟在48~72MHz范围内,需要两个等待状态 */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

 
    /* HCLK = SYSCLK AHB预分频器设置为1*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK APB2预分频器设置为1*/
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK/2 */
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

	//STM32F103ZET6为大容量芯片HD,所以这一段不用看,直接看else
    #ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 




#else    
    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }
    
    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

8.小问题:为什么在main函数中,不调用SystemInit();函数却可以使能时钟呢?

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP
                

通过上面这段代码(用汇编语言写的)可知,在系统复位之后,会先执行SystemInit();函数,再执行main();函数。

十二、Systick定时器

12-1 Systick定时器基础知识

1.对于CM3、CM4 内核的处理器,内部都包含了一个 SysTick 定时器,SysTick 是一个 24 位的倒计数定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。利用 STM32 的内部 SysTick 来实现延时的,这样既不占用中断,也不占用系统定时器。一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。
2.SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常。(时至20221006不理解)

12-2 Systick定时器相关寄存器库函数

1.Systick控制和状态寄存器

位段名称类型复位值描述
16COUNTFLAGR0如果在上次读取本寄存器后,Systick已经数到了0,则该位为1,如果读取该位,该位自动清零
2CLKSOURCER/W00=外部时钟源(STCLK)、1=内核时钟(FCLK)
1TICKINTR/W01=Systick倒数到0时产生Systick异常请求、0=数到0时无动作
0ENABLER/W0Systick定时器的使能位

2.对于STM32,外部时钟源是HCLK(AHB总线时钟的1/8,内核时钟是HCLK时钟)
配置函数:Systick_CLKSourceConfig();
3.SysTick重装载数值寄存器-LOAD

位段名称类型复位值描述
23:0RELOADR /W0当倒数至零时,将被重装载的值

4.SysTick当前值寄存器-VAL

位段名称类型复位值描述
23:0CURRENTR/Wc0读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick控制及状态寄存器中的COUNTFLAG标志

5.固件库中的Systick相关函数:

SysTick_CLKSourceConfig();//Systick时钟源选择(misc.c文件中)

SysTick_Config(unit32_t ticks);//初始化systick,时钟为HCLK,并开启中断(core_cm3.h/core_cm4.h文件中)

6.Systick中断服务函数

void SysTick_Handler(void);

12-3 delay延时函数

void delay_init()
{
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	u32 reload;
#endif
	SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);	//选择外部时钟  HCLK/8
	fac_us=SystemCoreClock/8000000;				//为系统时钟的1/8  
#if SYSTEM_SUPPORT_OS  							//如果需要支持OS.
	reload=SystemCoreClock/8000000;				//每秒钟的计数次数 单位为K	   
	reload*=1000000/delay_ostickspersec;		//根据delay_ostickspersec设定溢出时间
												//reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右	
	fac_ms=1000/delay_ostickspersec;			//代表OS可以延时的最少单位	   

	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;   	//开启SYSTICK中断
	SysTick->LOAD=reload; 						//每1/delay_ostickspersec秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;   	//开启SYSTICK    

#else
	fac_ms=(u16)fac_us*1000;					//非OS下,代表每个ms需要的systick时钟数   
#endif
}		

12-4 SysTick应用

1.在操作系统中充当系统“心跳”

void InitSysTick()
{
	if(SysTick_Config(SystemCoreClock/1000))//1ms
	{
		while(1);
	}
}
//在system_stm32f10x.c文件中:
#elif define SYSCLK_FREQ_72MHz 
unit32_t SystemCoreClock=SYSCLK_FREQ_72MHz;
void SysTick_Handler(void)
{
	OSIntEnter();//进入中断
	OSTimeTick();//调用ucos的时钟服务程序
	OSIntExit();//触发任务切换软中断
}

十三、通用定时器基本原理

13-1 三种定时器区别

1.三种STM32定时器区别

定时器种类位数计数器模式产生DMA请求捕获/比较通道互补输出特殊应用场景
高级定时器(TIM1,TIM8)16向上,向下,向上/下可以4带死区控制盒紧急刹车,可应用于PWM电机控制
通用定时器(TIM2~TIM5)16向上,向下,向上/下可以4通用。定时计数,PWM输出,输入捕获,输出比较
基本定时器(TIM6,TIM7)16向上,向下,向上/下可以0主要应用于驱动DAC

2.通用定时器功能特点描述

  • 位于低速的APB1总线上
  • 16位向上、向下、向下/上(中心对齐)计数模式,自动重装载计数器(TIMx_CNT)
  • 16位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535之间的任意数值。
  • 4个独立通道(TIMx_CH1~4),这些通道可以用来作为
    ①输入捕获
    ②输出比较
    ③PWM生成(边缘或中间对齐模式)
    ④单脉冲模式输出
  • 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用一个定时器控制另外一个定时器)的同步电路
  • 如下事件发生时可产生中断/DMA(6个独立的IRQ/DMA请求生成器)
    ①更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
    ②触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    ③输入捕获
    ④输出比较
    ⑤支持针对定位的增量(正交)编码器和霍尔传感器电路触发输入作为外部时钟或者按周期的电流管理
  • STM32的通用定时器可以被用于:测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
  • 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32的每个通用定时器都是完全独立的,没有互相共享的任何资源。

3.计数器模式

  • 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
  • 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
  • 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
    在这里插入图片描述

4.通用定时器工作过程
在这里插入图片描述
计数器时钟可以由下列时钟源提供:
①内部时钟(CK_INT)
在这里插入图片描述
默认调用Systemlnit函数情况下:
SYSCLK=72M
AHB时钟=72M
APB1时钟=36M
所以APB1的分频系数=AHB/APB1时钟=2。所以,通用定时器时钟CK_INT=2*36M=72M
②外部时钟模式1:外部输入脚(TIx)
③外部时钟模式2:外部触发输入(ETR)
④内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
5.定时器中断实验相关寄存器

  • 计数器当前值计数器TIMx_CNT
    在这里插入图片描述

  • 预分频寄存器TIMx_PSC
    在这里插入图片描述

  • 自动重装载寄存器TIMx_ARR
    在这里插入图片描述

  • 控制寄存器TIMx_CR1
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • DMA中断使能寄存器
    在这里插入图片描述
    在这里插入图片描述

6.常用库函数

  • 定时器参数初始化
void TIMx_TimeBaseInit(TIM_TypeDef*TIMx,TIMx_TimeBaseInitTypeDef*TIMx_TimeBaseInitStruct);
typedef struct
{
//预分频系数
  uint16_t TIM_Prescaler;         /*!< Specifies the prescaler value used to divide the TIM clock.
                                       This parameter can be a number between 0x0000 and 0xFFFF */
                                       
//计数模式
  uint16_t TIM_CounterMode;       /*!< Specifies the counter mode.
                                       This parameter can be a value of @ref TIM_Counter_Mode */

//自动装载值
  uint16_t TIM_Period;            /*!< Specifies the period value to be loaded into the active
                                       Auto-Reload Register at the next update event.
                                       This parameter must be a number between 0x0000 and 0xFFFF.  */ 

  uint16_t TIM_ClockDivision;     /*!< Specifies the clock division.
                                      This parameter can be a value of @ref TIM_Clock_Division_CKD */

  uint8_t TIM_RepetitionCounter;  /*!< Specifies the repetition counter value. Each time the RCR downcounter
                                       reaches zero, an update event is generated and counting restarts
                                       from the RCR value (N).
                                       This means in PWM mode that (N+1) corresponds to:
                                          - the number of PWM periods in edge-aligned mode
                                          - the number of half PWM period in center-aligned mode
                                       This parameter must be a number between 0x00 and 0xFF. 
                                       @note This parameter is valid only for TIM1 and TIM8. */
} TIM_TimeBaseInitTypeDef;       
  • 定时器使能函数
void TIM_Cmd(TIM_TypeDef*TIMx,FunctionalState NewState);
  • 定时器中断使能函数
void TIM_ITConfig(TIM_TypeDef*TIMx,unit_16 TIM_IT,FunctionalState NewState);
  • 状态标志位获取或清除
s TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

7.定时器中断实现步骤

  • 使能定时器时钟
RCC_APB1PeiphClockCmd();
  • 初始化定时器,配置ARR,PSC
TIM_TimeBaseInit();
  • 开启定时器中断
void TIM_ITConfig();
NVIC_Init();
  • 使能定时器
TIM_Cmd();
  • 编写中断服务函数
TIMx_IRQHandler();
  • 9
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值