STM32理论 —— 定时器、时钟

news2024/11/11 13:15:11

文章目录

  • 1. 定时器
    • 1.1 分类与简介
      • 1.1.1 分类与主要功能特点
      • 1.1.2 三种常用的定时器简介
      • 1.1.3 三种计数模式
      • 1.1.4 定时器计数原理
    • 1.2 时钟来源
    • 1.3 通用定时器简介
    • 1.4 计数溢出时间公式
    • 1.4 定时器中断的原理
    • 1.5 输入捕获
    • 1.6 核心代码
      • 1.6.1 通用定时器初始化
      • 1.6.2 高级定时器初始化
    • 1.7 高级定时器应用 - PWM信号
      • 1.7.1 定时器产生PWM信号的原理
      • 1.7.2 PWM信号频率计算公式
      • 1.7.3 PWM 初始化代码
        • 1.7.3.1 关于TIM_SetComparex函数
        • 1.7.3.2 关于TIM_OCMode参数
      • 1.7.4 PWM信号控制步进电机
        • 1.7.4.1 关于函数`TIM_SetCompare1`对电机运动的控制
      • 1.7.5 PWM信号控制led
      • 1.7.6 PWM信号控制LED闪烁
  • 2. 时钟
    • 2.1 STM32F1系列时钟系统框图
    • 2.2 时钟源
    • 2.3 系统时钟SYSCLK
    • 2.4 USB 时钟
    • 2.5 时钟输出
    • 2.6 系统时钟通过AHB分频器给外设提供时钟
      • 2.6.1 APB1和APB2对应的外设(F1系列)
    • 2.7 RCC相关寄存器(F1系列)
    • 2.8 RCC初始化

1. 定时器


以F103系列为例;

1.1 分类与简介

在这里插入图片描述

1.1.1 分类与主要功能特点

  • STM32F103系列单片机一共有11个定时器::
    2个高级定时器:TIM1、TIM8;挂载在APB2时钟总线上;
    4个通用定时器:TIM2~TIM5;挂载在APB1时钟总线上;
    2个基本定时器:TIM6、TIM7;
    2个看门狗定时器
    1个系统嘀嗒定时器

  • 主要功能特点

    • 位于低速的APB1时钟总线上;
    • 16位向上、向下、向上向下(中心对齐)计数模式,自动重装载寄存器(TIMx_ARR);
    • 16位可编程(可实时修改)预分频器寄存器(TIMx_PSC),计时器时钟频率的分频系数为0~65535之间的任意值;
    • 4个独立通道:输入捕获比较(测量输入信号的脉冲长度)、输出比较、PWM信号生成(边缘或中间对齐模式)、单脉冲模式输出;具体对应的IO查看手册中的“定时器复用功能重映射”;

其中预分频系数自动重载值就是我们要设置的参数,参数设置完后,打开定时器,定时器就开始计数工作;


1.1.2 三种常用的定时器简介

在这里插入图片描述

  • 三种定时器的主要功能:
    • 基本定时器:顾名思义,它只有最基本的定时功能,包含一个16位自动装载计数器,由可编程预分频器驱动,主要用驱动DAC,无对应的外部IO;
    • 通用定时器:在基本定时器基础上,还具有测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM),每个定时器对应有4个外部IO;
    • 高级定时器:具有基本,通用定时器的所有的功能外,还具有控制交直流电动机所有的功能,比如它可以输出6路互补带死区的信号、紧急刹车功能、PWM电机控制等等,每个定时器对应有8个外部IO;

1.1.3 三种计数模式

  • 向上计数模式:从0开始,向上计到TIMx_ARR预设值,产生溢出事件,并返回重新计时;
  • 向下计数模式:从TIMx_ARR预设值开始,向下计到0,产生溢出事件,并返回重新计时;
  • 向上/向下计数模式:从0开始向上计数,计到TIMx_ARR产生溢出事件,然后向下计数,计数到0以后,又产生溢出,然后再从0开始向上计数,以此循环(又叫中央对齐计数模式);
    在这里插入图片描述

其中TIMx_ARR为自动重装载值

1.1.4 定时器计数原理

单片机的定时器独立于CPU,目的是分担CPU的计时功能,释放CPU性能;当定时器被开启后,内部的计数器会以预先设定的计数器时钟频率开始计数;

如:设定计时器时钟频率为1MHz,那么定时器被开启后,内部的计数器就每隔1μs(1/1M)进行加1计数;

定时器的计数过程是离散的,为了简化,可把计数过程近似成下图线性直线;
在这里插入图片描述
(以向上计数模式为例)当定时器计数到了目标数值(溢出时间)后,会产生溢出事件,然后返回到0,重新进行计数,如此循环(就像时钟走到12会回到0,然后小时数会加1一样);当然这个“目标数值”不是无限大的(就像一天的时间最多是24小时一样),其大小取决于定时器的存储空间位数,如16位定时器(有16位的空间去存储该“目标数值”)每次能计数的最大值就是2^16 - 1=65536-1=65535
在这里插入图片描述

1.2 时钟来源

  1. 内部时钟(CK_INT):如下图,系统时钟SYSCLK通过预分频产生定时器时钟TIMXCLK,因为APB1产生的时钟最大为36MHz,而APB1一般不为1,所以TIMXCLK的频率一般为72MHz;
  2. 外部时钟模式1:外部输入脚(TIx);
  3. 外部时钟模式2:外部触发输入(ETR);
  4. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器;
    在这里插入图片描述

1.3 通用定时器简介

  • 通用定时器的工作过程:如下公式所示,内部时钟发生器产生的时钟信号,它又叫工作频率,再经过预分频器PSC分频后,得到计数器时钟频率(一般是72MHz),而计数器时钟频率根据自动重载值最终得到定时器的定时频率(即定时器计数的频率);
  • 如下图中的“实际单元”是通用定时器的主要部分,16位的计数器寄存器(TIMx_CNT)自动重装载寄存器(TIMx_ARR)预分频器寄存器(TIMx_PSC)
    在这里插入图片描述
    计数频率的计算公式

1.4 计数溢出时间公式

  • 定时器溢出时间就是定时频率(定时器计数频率)的倒数,它取决于以下两个参数:
    • 时钟频率(即以下公式中的(PSC+1)/Tclk,一般单片机的时钟频率Tclk为72MHz);
    • 自动装载值ARR

时钟频率,见3.2 时钟来源中的内部时钟(CK_INT)讲解;

在这里插入图片描述

  • 当定时器计数溢出时,产生一次更新事件,如果使能相应的定时器中断,就相当于在每次定时器溢出时,进入一次定时器中断服务函数;

1.4 定时器中断的原理

以定时器的向上计数模式为例说明定时器中断的原理,如下图:

  1. APB1产生的时钟:CK_INT;
  2. 定时器使能:CNT_EN,使定时器开始计数;
  3. 定时器时钟:CK_CNT,因为规定时钟分频系数为1,则CK_CNT=CK_INT,否则CK_CNT=CK_INT*2;
  4. 计数到装载值(图中是 36),计数器溢出,更新溢出事件;
  5. 如果已使能了中断,则在产生溢出事件时执行中断服务函数,相应的中断标志位更新;
  6. 中断服务函数执行完毕,开始新的一轮计数;
    在这里插入图片描述

1.5 输入捕获

见通用定时器的工作过程原理图的输入捕获部分,下以输入捕获通道 1为例:

  1. 检测TIMx_CHx 上的边沿信号;
  2. 边沿信号发生跳变(如设置了捕获一次上升沿跳变),将当前定时器的值TIMx_CNT 存放到对应捕获/比较寄存器TIMx_CCRx中,完成一次捕获;
  3. 同理进行下一次输入捕获,通过计算两次定时器值的差值,得到目标结果;
    在这里插入图片描述

1.6 核心代码

1.6.1 通用定时器初始化

/**********************************************************************************************************
*	函 数 名: Timer3_Init
*	功能说明: 定时器3初始化,定时器3是个通用定时器
*	形    参:
						arr:自动重装载值
						psc:预分配系数
*	返 回 值: 无
**********************************************************************************************************/
void Timer3_Init(u16 arr,u16 psc)
{
	//定义结构体
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化定时器的时间基数单位
 	//假设预分配系数为7199、计时周期为999,那么定时器的计数周期为:((1+TIM_Prescaler )/72M)*(1+TIM_Period )=((1+7199)/72M)*(1+999)=1ms,即每1ms进入一次定时器中断
	
	//定时器中断配置
	TIM_ITConfig
	(  //使能定时器3的Update中断、Trigger中断
		TIM3, 
		TIM_IT_Update  |  //TIM 中断源
		TIM_IT_Trigger,   //TIM 触发中断源 
		ENABLE  //使能
	);
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化外设NVIC寄存器

	TIM_Cmd(TIM3, ENABLE);  //使能定时器,定时器开始工作,假如arr为7199,psc为999,则每1ms进入一次中断		
	TIM_Cmd(TIM3, DISABLE); // 失能定时器				 
}
/**********************************************************************************************************
*	函 数 名: TIM3_IRQHandler
*	功能说明: 定时器3中断服务函数,定时器每次计数溢出都会进入该函数
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void TIM3_IRQHandler(void)   
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查定时器是否发生中断
		{	
			CPU_LED = CPU_LED^1;//LED指示灯亮/灭一次
			if(status_flag == 1) //相应标志位清零
			{
				status_flag = 0;
				rec3_id = 0;
			}
			countTime++;  // 时间计时,比如设置了每1ms进入一次中断,那么就是每1ms,该变量加1,实现精确计时
			TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除定时器的中断待处理位
		}
}

1.6.2 高级定时器初始化

/**********************************************************************************************************
*	函 数 名: Timer1_Init
*	功能说明: 定时器1初始化,定时器1为高级定时器
*	形    参:
						arr:自动重装载值
						psc:预分配系数
*	返 回 值: 无
* 修改日期:2020-9-9
**********************************************************************************************************/
void Timer1_Init(u16 arr,u16 psc)
{
	//结构体定义
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割,TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //初始化定时器的时间基数单位
 	//假设预分配系数为7199、计时周期为9999,那么定时器的计数周期为:((1+TIM_Prescaler )/72M)*(1+TIM_Period )=((1+7199)/72M)*(1+9999)=1秒
	
	TIM_ClearFlag(TIM1, TIM_FLAG_Update);//清除更新标记
	TIM_ITConfig
	(  //使能定时器1的Update中断、Trigger中断
		TIM1, 
		TIM_IT_Update  |  //TIM 中断源
		TIM_IT_Trigger,   //TIM 触发中断源 
		DISABLE  //使能
	);
	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //TIM1中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

	TIM_Cmd(TIM1, ENABLE);  //使能定时器
	TIM_ClearITPendingBit(TIM1, TIM_IT_Update);      //清除TIMx的中断待处理位:TIM 中断源
	//TIM_ITConfig(TIM1,TIM_IT_Update,DISABLE);        //关闭 定时器中断		 
}
/**********************************************************************************************************
*	函 数 名: TIM1_UP_IRQHandler
*	功能说明: 定时器1中断服务函数,在定时器1发生更新事件时(计数发生上溢出或下溢出时),运行该函数
*	形    参:无
*	返 回 值: 无
**********************************************************************************************************/
void TIM1_UP_IRQHandler(void)   //TIM1中断
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{
		Pulse_Count++; //电机脉冲计数加1
//		Uart1_Printf("%d\r\n*_*",Pulse_Count);
		if(Pulse_Count == motor_step)//脉冲计数达到目标值
		{
			TIM_SetCompare1(TIM1,0); //设置PWM信号占空比为0,即PWM信号输出恒定的低电平,让电机停下
			TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);  //关闭定时器1中断,即定时器仍然计数,但不产生中断
			//TIM_Cmd(TIM1, DISABLE);  //关闭定时器1,即完全关闭定时器计数功能
		}
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源
	}
}

1.7 高级定时器应用 - PWM信号

  • PWM(Pulse Width Modulation):脉冲宽度调制,即占空比可调的脉冲波形;

占空比:即高电平在一个完整周期中占据的比例,如下图中PWM信号的占空比为T1/T

在这里插入图片描述

1.7.1 定时器产生PWM信号的原理

(以向上计数模式为例)如图由定时器的计数原理知:

  1. 计数器计数到自动重装载值 (TIMx_ARR) 时会返回到0并产生溢出事件,那么自动重装载值就决定了PWM信号的完整周期,自动重装载值越大,PWM信号的周期越长;
  2. 捕获/比较寄存器(CCRx),当计数值等于CCRx时,输出的IO电平反转,难么CRRx就决定了高电平所占据的时间周期,也即决定了PWM信号的占空比;显然,CRRx的值不能超过TIMx_ARR;
  3. IO逻辑就是TIMx_ARR和CCRx共同输出的PWM信号;
  4. 预分频系数PSC:如下图,预分频寄存器PSC两端有输入时钟信号CK_PSC和输出时钟信号CK_CNT,前者是时钟源的输出,后者用于驱动计数器CNT计数,那么通过设置预分频系数PSC,就可以得到不同的CK_CNT,实现0~65535的分频(16位预分频器寄存器(TIMx_PSC)),同时它也决定了PWM信号的频率上限;
    在这里插入图片描述

在这里插入图片描述

1.7.2 PWM信号频率计算公式

其实就是定时器计数频率的公式,即计数溢出时间的倒数,这里使用的STM32 芯片主频为72MHz,则计算公式如下:
在这里插入图片描述

其中72000000= 72M为APB1时钟的频率,在APB1预分频系数不为1的情况下,APB1时钟的频率为72MHz;

如:需求一个频率为10KHz,占空比为80%的PWM信号,可把参数设置为:

  • PSC=0
  • ARR=(72M/10K)-1=7200-1=7199
  • CRRx=(ARR+1)*0.8=5760

1.7.3 PWM 初始化代码

下面配置GPIO 口PA8 输出PWM信号,所用的定时器为高级定时器1(TIM1)的PWM 通道1

  • 初始化高级定时器1
/***************************************************************************
** 函数名称   :   TIM1_PWM_Init
** 功能描述   :  PWM参数初始化
** 输入变量   :   
				1. arr :重装载值
				2. psc :预分频系数
** 返 回 值   :   无
** 最24后修改人 :   xxx
** 最后更新日期:  20220524
** 说    明   :		无
***************************************************************************/
void TIM1_PWM_Init(u16 arr,u16 psc) // 使用TIM1 高级定时器输出pwm 波
{
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// 使能TIM1时钟
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIO外设时钟使能
	
	TIM_DeInit(TIM1); // 复位TIM1
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM1,ENABLE);
	
	/
	TIM_TimeBaseStructure.TIM_Period = arr; 				//设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 用于设置最后输出频率
																									//输出频率=分频频率/(arr+1)
	TIM_TimeBaseStructure.TIM_Prescaler = psc; 			//设置用来作为TIMx时钟频率除数的预分频值  不分频时TIMx时钟为72MHz
																									//如果分频,分频频率=72M/(psc+1)
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 		//设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	TIM_ITConfig
	(  //使能或者失能指定的TIM中断
		TIM1, //TIM1
		TIM_IT_Update,  //TIM 中断源 - 更新中断
		DISABLE  //不使能
	);
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;  //TIM1中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	TIM_ClearFlag(TIM1, TIM_FLAG_Update);
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
	
	// 定时器输出比较(output compare)配置
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;     //互补输出关闭
	TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性低,注意该参数配置成高,TIM_SetComparex() 函数输出的pwm 占空比会反过来,如设置占空比10,则输出90
	TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Set;
	TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx的CH1
  TIM_CtrlPWMOutputs(TIM1,ENABLE);	//使能TIM1的PWM输出,只有高级定时器TIM1 和TIM8 有该功能
	
	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
	
	TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
	
	TIM_Cmd(TIM1, ENABLE);  //使能TIM1
	
	TIM_ClearITPendingBit(TIM1, TIM_IT_Update);      //清除TIMx的中断待处理位:TIM 中断源
	
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);        //使能定时中断
	
//	TIM_InternalClockConfig(TIM1);     //配置内部时钟
	
//	TIM_UpdateDisableConfig(TIM1, DISABLE);        //不使能更新事件
}
//定时器4中断服务程序
void TIM1_UP_IRQHandler(void)   
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
	}
}
  • 中断服务函数
void TIM1_UP_IRQHandler(void)   
{
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
	{
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update  );  //清除TIMx的中断待处理位:TIM 中断源 
	}
}
  • main.c 函数:
//由公式计算得PWM 输出频率为.422kHz,即每秒翻转约1422次,即每秒输出PWM 波约1422个,周期约为703ns,即输出一个PWM 波耗费703ns
TIM1_PWM_Init(100,500);		
TIM_SetCompare1(TIM1,50);	// 配置高级定时器1 的通道1,设置占空比为50%=arr/2,这里arr 为100

在这里插入图片描述

1.7.3.1 关于TIM_SetComparex函数

从芯片资料可以找到如下图表33-1 一样的定时器通道引脚配置图表,以PA0为例,将PA0设为PWM 信号输出端口,见图表33-1知,PA0位于定时器通道CH1上,那么设置它的占空比的函数就是TIM_SetCompare1(). 以此类推,位于定时器通道CH2上的端口,设置它的占空比的函数就是TIM_SetCompare2().

  • 占空比计算公式:由1.7.2 节中知,【CRRx=(ARR+1)*占空比】,而这里的compare 值就是公式中的CRRx,故占空比公式就是 Duty_Cycle= compare/(arr+1)

比如在PWM初始化时,自动重装载值arr设为99,则设定50%的占空比就是TIM_SetCompare1(TIMx,50);

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; ,中参数若配置成 TIM_OCPolarity_High ,则输出的占空比取反,如配置输出占空比为10%,配置成 TIM_OCPolarity_High 则输出占空比为90%

  • 函数原型
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1) 
//其中:
//TIMx:定时器
//Compare1:与TIMx比较的数,即TIMx的一个完整周期的时间减去这个Compare1,使得TIMx的周期从这个Compare1后的时间内输出值取反;

在这里插入图片描述

在这里插入图片描述

1.7.3.2 关于TIM_OCMode参数

该参数用于选择脉冲宽度调制的模式,假设高电平为有效电平:

  1. TIM_OCMode_PWM1:在向上计数时,当TIMx_CNT<TIMx_CCR1时通道输出高电平,否则为低电平;在向下计数时,当TIMx_CNT>TIMx_CCR1时,通道为低电平,否则为高电平。
  2. TIM_OCMode_PWM2:在向上计数时,当TIMx_CNT<TIMx_CCR1时通道输出低电平,否则为高电平;在向下计数时,当TIMx_CNT>TIMx_CCR1时,通道为高电平,否则为低电平。

1.7.4 PWM信号控制步进电机

该部分定时器中断服务函数见3.6.2 高级定时器初始化中的中断服务函数;

函数索引:
void PSC_Updata(u16 MotorSpeed);
void motorSpeedControl(u16 motorSpeed,u16 moveStep,u8 motorDir);
u8 motorDirSet(int moveStep);
void motorGo(u16 speed);
u8 GetPluseValue(char *string);
void motorMovePluse(u8 motorDir,int motorPluse);
u8 motorGoHome(void);
/**********************************************************************************************************
*	函 数 名: PSC_Updata
*	功能说明: 以电机速度更新对应TIM1的PSC值(定时器预分频系数),等效于设置电机速度,MotorSpeed越大,PSC越小,定时器计数频率越大
*	形    参:MotorSpeed:电机速度,单位:r/min
*	返 回 值: 无
**********************************************************************************************************/
void PSC_Updata(u16 MotorSpeed)
{
	u16 psc = 0,freq = 0;
	freq = (MotorSpeed*16000) /60;//电机运动频率
	psc = (720000/freq) - 1;
	TIM_PrescalerConfig(TIM1,psc,TIM_PSCReloadMode_Update);//更新定时器1预分频系数PSC值
	return;
}
/**********************************************************************************************************
*	函 数 名: motorSpeedControl函数
*	功能说明: 电机输出控制函数,控制电机加减速
*	形    参:
					spped:电机速度,r/min
					moveStep:电机运动步数
					Direction:电机运动方向
*	返 回 值: 无
**********************************************************************************************************/

void motorSpeedControl(u16 spped,u16 moveStep,u8 Direction)
{
	u16 speed = 0;//实时速度
	u16 maxSpeed = spped;//设定最大速度
	u8 i=0;	
	u8 RunStep = 0;//梯形运动过程记录
	gMotor_add_step = 0;	//电机加速脉冲计数
	gMotor_sub_step = 0;	//电机减速脉冲计数

	motorBrakeRelease();	//松开刹车
	pulseCount = 0;
	PSC_Updata(5);   //更新定时器1预分频PSC值,电机加速阶段
	TIM_SetCompare1(TIM1,50);
	TIM_ITConfig(TIM1,TIM_IT_Update|TIM_IT_Trigger,ENABLE);//使能定时器1中断

	while(pulseCount < moveStep)
	{
	//超出物理限制位置
	if((motorPosSensor == 0)&&(Direction == 1))
	{
		Uart1_Printf("Motor triager POS Limit.\r\n*_*");	
		TIM_SetCompare1(TIM1,0);
		TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);
		motorBrake;	
		return;
	}
	if((motorNegSensor == 0)&&(Direction == 0))
	{
		Uart1_Printf("Motor triager NEG Limit.\r\n*_*");	
		TIM_SetCompare1(TIM1,0);
		TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);
		motorBrake;	
		return;
	}

	/* 开始加、匀、减速度运动 */
		if(speed < maxSpeed - 5)             	 //加速阶段
		{
			i+=2;
			delay_ms(400/(maxSpeed-5));
			if(speed >= maxSpeed) speed = maxSpeed;
			else speed += i;
			PSC_Updata(speed+2);//定时器
		}
		if((speed >= maxSpeed-5)&&(RunStep == 0)	)//匀速阶段
		{
			RunStep = 1;			
			speed = maxSpeed;
			gMotor_sub_step = gMotor_add_step = pulseCount;  
			i = 0;
		}
		if(pulseCount >= moveStep - gMotor_sub_step) //减速阶段
		{
			if(speed > 5)
			{
				i+=2;
				delay_ms(400/(maxSpeed-5));//在要求时间内跑到最小速度
				if(speed <= 5)speed=5;
				else	speed -= i;
				PSC_Updata(speed);
			}
		}
	}
	TIM_SetCompare1(TIM1,0);
	TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);
	motorBrake;
}
/**********************************************************************************************************
*	函 数 名: motorDirSet
*	功能说明: 控制电机运动方向
*	形    参:moveStep:电机运动步数
*	返 回 值: 0-电机正向运动,1-电机反向运动
**********************************************************************************************************/
u8 motorDirSet(int moveStep)
{
	u8 dirFlag = 0;//电机运动方向标志位
	//设置电机运动方向
	if(moveStep > 0){SetMotorDirPos;}
	else
	{
		SetMotorDirNeg;
		moveStep = -moveStep;
		dirFlag = 1; //更新标志位
	}
	motorStep = moveStep;
	//motorSpeedControl(motorSpeed,moveStep);
	//更新电机运动脉冲
	if(dirFlag == 0)
		{motorNewStep = motorNewStep + pulseCount;}
	else	
		{motorNewStep = motorNewStep - pulseCount;}
		
	if(pulseCount >= moveStep)
		return 0;
	else
		return 1;
}
/**********************************************************************************************************
*	函 数 名: motorGo
*	功能说明: 电机以目标速度启动
*	形    参:speed:电机速度值
*	返 回 值: 无
**********************************************************************************************************/
void motorGo(u16 speed)
{
		PSC_Updata(speed);//更新定时器1预分频PSC值,电机加速阶段
		TIM_SetCompare1(TIM1,50);//设置PWM占空比
		TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, ENABLE);    //使能定时器1中断
		motorBrakeRelease;//松开电机刹车
}
/**********************************************************************************************************
*	函 数 名: GetPluseValue函数
*	功能说明: 从串口获取电机运动脉冲数
*	形    参:string:串口输入的字符串
*	返 回 值:0-电机反向运动,1-电机正向运动
**********************************************************************************************************/
u8 GetPluseValue(char *string)
{
	u8 motorDir;
	char *ptr = &string[16];
	g_MovePluse = atoi(ptr);
	motorNewStep += g_MovePluse;
	if(*ptr == '-')
	{
		g_MovePluse = -g_MovePluse;//把数值转换为正数
		motorDir = 0;
	}
	else
	{
		motorDir = 1;	
	}
	return motorDir;
}
/**********************************************************************************************************
*	函 数 名: motorMovePluse
*	功能说明: 电机走指定脉冲
*	形    参:
					motorDir:1为正向,0为反向 
					MotorPluse:电机脉冲
*	返 回 值: 无
**********************************************************************************************************/
void motorMovePluse(u8 motorDir,int motorPluse)
{
	// motorDir只能是0或者1
	if(motorDir > 1) return;

//	motorSpeedControl(30,motorPluse);
//	if(motorPluse > 200)
//	motorGo(30);
//	else 
//	motorGo(10);	
	if(motorDir == 1) //电机正向运动
	{
		SetMotorDirPos; //设置电机正向运动
		pulseCount = 0; //脉冲计数清零
		motorSpeedControl(30,motorPluse,motorDir);//电机运动
//		while((pulseCount <= motorPluse)&&(motorPosSensor == 1));
//		TIM_SetCompare1(TIM1,0);
//		TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);    //使能定时器1中断
//		motorBrake;
//		Uart1_Printf("Motor MovePluse: +%5d\r\n*_*",motorPluse);
//		Uart1_Printf("Motor now TotalPluse: %5d\r\n*_*",motorNewStep);	
		return;
	}
	else
	{ //电机反向运动
		SetMotorDirNeg;
		pulseCount = 0;
		motorSpeedControl(30,motorPluse,motorDir);
//		while((pulseCount <= motorPluse)&&(motorNegSensor == 1));
//		TIM_SetCompare1(TIM1,0);
//		TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);    //使能定时器1中断
//		motorBrake;	
//		Uart1_Printf("Motor MovePluse: -%5d\r\n*_*",motorPluse);		
//		Uart1_Printf("Motor now TotalPluse: %5d\r\n*_*",motorNewStep);			
		return;
	}
}

/**********************************************************************************************************
*	函 数 名: motorGoHome
*	功能说明: 电机回原点
*	形    参:无
*	返 回 值:0-回原点成功
**********************************************************************************************************/

u8 motorGoHome(void)
{
		u8 motorPotionFlag = 0; //电机位置标志位,当motorPotionFlag为2时表示电机处于原点位置
	/* 电机不在正限位位置,先向上找到正限位 */
		if(motorPosSensor != 0)
		{
//			Uart1_Printf("Run step1.d\r\n*_*");
			motorBrakeRelease; //松开电机刹车
			SetMotorDirPos; //设置电机运动方向为正向
			pulseCount = 0; //脉冲计数清零
			motorStep = 50000;//预设一个较大的电机运动步数,电机会在该步数内走到传感器,否则就是传感器出问题
			motorGo(30);//电机以速度30开始运动
			//先找到上极限
			while((pulseCount <= motorStep)&&(motorPosSensor == 1));//等待电机走到正限位
			
			//电机向下找原点
			SetMotorDirNeg;
			motorGo(20);
			pulseCount = 0;	
			motorStep = 20000;
			while((pulseCount <= motorStep)&&(motorOriSensor == 1));	//等待电机走到原点位
//			Uart1_Printf("Step 1 get Motor Ori Sta = %d\r\n*_*",motorOriSensor);
			motorPotionFlag = 2;
		}
		/* 电机已在正限位,电机向下找原点 */
		else  
		{
//			Uart1_Printf("Run step2.d\r\n*_*");
			SetMotorDirNeg;
			motorGo(20);
			pulseCount = 0;
			motorStep = 20000;
			while((pulseCount <= motorStep)&&(motorOriSensor == 1));
//			{Uart1_Printf("Step 2 get Motor Ori Sta = %d\r\n*_*",motorOriSensor);}				
			motorPotionFlag = 2;
		}
			TIM_SetCompare1(TIM1,0);//设置电机占空比为0,电机停止运动
			TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);    //失能定时器1中断
			motorBrake;	//电机刹车	
		/* 电机到原点后再慢速往下离开原点,再慢速返回 */
		if(motorPotionFlag == 2)
		{
//			Uart1_Printf("Run step3.d\r\n*_*");
			SetMotorDirNeg;
			motorGo(5);
			pulseCount = 0;
			motorStep = 2000;
			while(pulseCount <= motorStep&&(motorOriSensor == 0));	//等待电机离开原点位
//			Uart1_Printf("Finish step3.d\r\n*_*");	
			//电机慢速返回原点
			SetMotorDirPos;
			motorGo(5);
			pulseCount = 0;
			motorStep = 2000;
			while(pulseCount <= motorStep&&(motorOriSensor == 1));	//等待电机离开原点位
			//让电机停止运动
			TIM_SetCompare1(TIM1,0);
			TIM_ITConfig(TIM1,TIM_IT_Update | TIM_IT_Trigger, DISABLE);    ///失能定时器1中断
			motorBrake;
			motorPotionFlag=0;//电机回原点成功
		}
		gMotorPosSta = 0;
		return 0;
}

1.7.4.1 关于函数TIM_SetCompare1对电机运动的控制

交流步进电机内部有两组线圈(A+/A-,B+/B-),电机的运动需要轮番对两组线圈通电,每次通电,电机就运动一定的角度,这种交流电信号必须是50%占空比的方波信号,不然电机运动会发生紊乱,所以需要电机正常运动,就需要把PWM信号的占空比设为50%,TIM_SetCompare1(TIM1,50);,反之要电机停止运动,就把PWM信号的占空比设为0;而电机的运动速度则通过PWM信号的频率决定,频率越高,电机转速越快;

  • 函数原型:
/**
  * @brief  Sets the TIMx Capture Compare1 Register value,设置TIMx Capture Compare1寄存器值
  * @param  TIMx: where x can be 1 to 17 except 6 and 7 to select the TIM peripheral.TIMx: x可以是1到17,除了6和7,以选择TIM外围设备。
  * @param  Compare1: specifies the Capture Compare1 register new value.Compare1:指定捕获的Compare1寄存器新值。
  * @retval None
  */
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST8_PERIPH(TIMx));
  /* Set the Capture Compare1 Register value */
  TIMx->CCR1 = Compare1;
}

1.7.5 PWM信号控制led

函数索引:
void ledUpdataFrequence(u16 frequence);
void ledPwmControl(u8 ledNubmer,u8 status,u8 showOption);
/**********************************************************************************************************
*	函 数 名: ledUpdataFrequence
*	功能说明: 更新led PWM信号 PSC预分频系数
*	形    参:frequence:PWM信号频率,frequence越大,PSC越小,定时器计数频率越大
*	返 回 值: 无
* 修改日期:2020-5-7
**********************************************************************************************************/
void ledUpdataFrequence(u16 frequence)
{
	u16 psc = 0;       
	psc = (720000/frequence) - 1;
	TIM_PrescalerConfig(TIM1,psc,TIM_PSCReloadMode_Update);//更新定时器1预分频系数PSC值
	return;
}
/**********************************************************************************************************
*	函 数 名: ledPwmControl
*	功能说明: 以PWM信号控制LED灯
*	形    参:
					ledNubmer:led编号,0: ALS1, 1: ALS2, 2: ALS3  
					status:led状态
          showOption:是否回显结果
*	返 回 值: 无
* 修改日期:2020-5-7
**********************************************************************************************************/
void ledPwmControl(u8 ledNubmer,u8 status,u8 showOption)
{
	ledUpdataFrequence(ALS_freq);//更新led PWM信号频率
	if(ledNubmer ==1)
	{
		if(status == ON)
		{
			TIM_SetCompare2(TIM1,100-ALS1_duty);//设置定时器2 PWM信号占空比
		}
		else
		{
			TIM_SetCompare2(TIM1,100);
		}
	}
	else if(ledNubmer == 2)
	{
		if(status == 1)
		{
			TIM_SetCompare4(TIM1,100-ALS2_duty);
		}
		else
		{
			TIM_SetCompare4(TIM1,100);
		}
	}
	if(showOption == 1)
	{
		Uart1_Printf("Led set pass\r\n*_*");
	}
}

1.7.6 PWM信号控制LED闪烁

// 设置PWM的频率于占空比
int CalculateFrequencyPWM(uint32_t freq,uint32_t duty)
{															  
	uint8_t  i;
//	uint16_t ck;
	double   dt=0.0,dd=0.0,ck;
	//PortInfom  *pb=&InOutMessage;
	
	dd=72000000;	
	if(state.hawking_f==1)
	{dt=((double)freq);}//1mm=360脉冲 (1mm=360Hz)	
	else
	{dt=(double)freq;}
	i=0;				  //步进电机一圈为 10mm(3600脉冲 Hz)
	ck=0.0;
	do 
	{
		i++;		//取出分频系数
		ck=dd/dt;
		ck=ck/(double)i;	  
	}while(ck>60000.0);
	if(i>=1)
	{i=i-1;}
	dt=ck*((double)duty)/100;

	TIM4InitilToMotorPWM(i,ck,dt);

	return 0;		
}

void TIM4InitilToMotorPWM(unsigned char divfreq, unsigned int CouVal, unsigned int ButyCycle)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef        TIM_OCInitStructure;
	GPIO_InitTypeDef         GPIO_InitStructure;	  
	NVIC_InitTypeDef         NVIC_InitStructure;	//定义数据结构体	 
	  	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);   	  //使能TIM4的时钟	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

	TIM_DeInit(TIM4);	                      //复位时钟TIM2,恢复到初始状态
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 ;				  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;			  //配置为复用
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
//	GPIO_PinRemapConfig(GPIO_Remap_TIM4 , DISABLE);			  //禁止映射

	 
	/*-------------------------------------------------------------------
	TIM3CLK=72MHz  预分频系数Prescaler=2 经过分频 定时器时钟为24MHz
	根据公式 通道输出占空比=TIM4_CCR2/(TIM_Period+1),可以得到TIM_Pulse的计数值	 
	捕获/比较寄存器2 TIM4_CCR2= CCR1_Val 	 
	 T= fCK/TIM_Prescaler-1/TIM_Period   
	-------------------------------------------------------------------*/
	TIM_TimeBaseStructure.TIM_Prescaler = divfreq;		       //预分频器TIM4_PSC=3 计数器的时钟频率CK_CNT等于fCK_PSC/(PSC[15:0]+1)。
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;	  //计数器向上计数模式 TIM4_CR1[4]=0
	TIM_TimeBaseStructure.TIM_Period = CouVal;			//自动重装载寄存器TIM4_APR  确定频率为1KHz 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;				  //时钟分频因子 TIM4_CR1[9:8]=00
	TIM_TimeBaseStructure.TIM_RepetitionCounter = 0x0;
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseStructure);				  //写TIM4各寄存器参数
	TIM_ClearFlag(TIM4,TIM_IT_Update);			   //清标示位
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);        //更新中断		
	
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; 			 //PWM模式2 TIM4_CCMR1[14:12]=111 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平																 //
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 	//输入/捕获2输出允许  OC2信号输出到对应的输出引脚PD13
	TIM_OCInitStructure.TIM_Pulse = ButyCycle; 					//确定占空比,这个值决定了有效电平的时间。
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 	    //输出极性   TIM4_CCER[5]=1;
//	TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
		
	TIM_OC3Init(TIM4,&TIM_OCInitStructure); 
	TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);	

	
   // NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //将中断矢量放到Flash的0地址
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置优先级配置的模式,    
    NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; //使能中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	 //从优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);	   //将结构体丢到配置函数,即写入到对应寄存器中
	
	/* TIM4禁止使能 */
	TIM_Cmd(TIM4,ENABLE);
	TIM_CtrlPWMOutputs(TIM4, ENABLE); 

 
}

2. 时钟


时钟系统是CPU的脉搏,决定cpu速率,是CPU正常工作的时间基准。STM32使用任何外设都需要先启动时钟才能使用,但并不是所有的外设都需要用到系统时钟那么高的频率,如果都用高速时钟,必造成能耗浪费,且同一个电路,时钟越快不但功耗越大,同时抗电磁干扰能力也越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。对不同模块的时钟增加开启和关闭功能,可以降低单片机的功耗。

默认状态下,单片机把所有的外设时钟配置为失能(disable),用到哪个外设,就打开对应外设的时钟即可。

2.1 STM32F1系列时钟系统框图

在这里插入图片描述
系统时钟SYSCLK的左边 是设置系统时钟选择哪个作为时钟源。系统时钟SYSCLK 的右边,是系统时钟通过AHB预分频器,给对应的外设设置对应的时钟频率 。

从左到右可以简单理解为 :各个时钟源—>系统时钟来源的设置—>各个外设时钟的设置。

2.2 时钟源

即图中系统时钟SYSCLK的左边 部分,STM32 有4个独立时钟源:HSI、HSE、LSI、LSE:

  1. HSI高速内部时钟:RC振荡器,频率为8MHz,精度不高。
  2. HSE高速外部时钟:可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
  3. LSI低速内部时钟:RC振荡器,频率为40kHz,提供低功耗时钟。一般作为IWDGCLK(独立看门狗)时钟源和RTC时钟源而独立使用。
  4. LSE低速外部时钟:接频率为32.768kHz的石英晶体。

除LSI外,其他三个时钟源都可以过分频者倍频作为系统时钟来使用。

PLL锁相环倍频输出:其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。Keil编写程序时默认的时钟为72Mhz,是因为外部晶振(HSE)提供的8MHz(与电路板上的晶振的相关)通过PLLXTPRE分频器后,进入PLLSRC选择开关,进而通过PLLMUL锁相环进行倍频(x9)后,为系统提供72MHz的系统时钟(SYSCLK)。然后AHB预分频器对时钟信号进行分频,为低速外设提供时钟。但也可以是内部RC振荡器(HSI) 为8MHz/2 =4MHz进入PLLSRC选择开关*,通过PLLMUL锁相环进行倍频(x18)后为72MHz。

2.3 系统时钟SYSCLK

从4.2节知,系统时钟SYSCLK来源于三个时钟源:
①、HSI振荡器时钟
②、HSE振荡器时钟
③、LSE振荡器时钟

系统时钟SYSCLK最大为72MHz.

在这里插入图片描述

2.4 USB 时钟

STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取。其分频系数可选择为1.5分频或者1分频,即当需要使用USB模块时,PLL必须使能,且时钟频率只能配置为48MHz或72MHz。
在这里插入图片描述

2.5 时钟输出

STM32可选择一个时钟信号输出到MCO引脚(GPIO)上供外部使用。
在这里插入图片描述

2.6 系统时钟通过AHB分频器给外设提供时钟

从4.1节的时钟系统框图中右边知,外设时钟从左到右依次为:系统时钟—>AHB分频器—>各个外设分频倍频器 —> 外设时钟的设置。AHB分频器可选择1、2、4、8、16、64、128、256、512分频。其中AHB分频器输出的时钟送给5大模块使用:

  1. 内核总线:送给AHB总线、内核、内存和DMA使用的HCLK时钟
  2. Tick定时器:通过8分频后送给Cortex的系统定时器时钟。
  3. I2S总线:直接送给Cortex的空闲运行时钟FCLK
  4. APB1外设:送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给通用定时器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2-7使用。
  5. APB2外设:送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给高级定时器。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。

2.6.1 APB1和APB2对应的外设(F1系列)

  • APB1上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、USART2、USART3、UART4、UART5、SPI2、SP3等;
  • APB2上面连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、ADC3、所有的普通I/O口(PA-PE)、第二功能I/O(AFIO)口等。

在这里插入图片描述

2.7 RCC相关寄存器(F1系列)

//RCC 寄存器结构,RCC_TypeDeff,在文件“stm32f10x.h”中定义如下:
//1059行->1081行:  
typedef struct  
{  
vu32 CR;                  //HSI,HSE,CSS,PLL等的使能  
vu32 CFGR;              //PLL等的时钟源选择以及分频系数设定 
vu32 CIR;                // 清除/使能 时钟就绪中断 
vu32 APB2RSTR;      //APB2线上外设复位寄存器 
vu32 APB1RSTR;      //APB1线上外设复位寄存器 
vu32 AHBENR;         //DMA,SDIO等时钟使能 
vu32 APB2ENR;       //APB2线上外设时钟使能 
vu32 APB1ENR;      //APB1线上外设时钟使能 
vu32 BDCR;           //备份域控制寄存器 
vu32 CSR;             
} RCC_TypeDef; 

2.8 RCC初始化

以HSE(高速外部时钟)为例(实际应用中使用HSE最多),使用HSE时钟,程序设置时钟参数流程:

  1. 将RCC寄存器重新设置为默认值 RCC_DeInit;
  2. 打开外部高速时钟晶振HSE RCC_HSEConfig(RCC_HSE_ON);
  3. 等待外部高速时钟晶振工作 HSEStartUpStatus = RCC_WaitForHSEStartUp();
  4. 设置AHB时钟 RCC_HCLKConfig;
  5. 设置高速AHB时钟 RCC_PCLK2Config;
  6. 设置低速速AHB时钟 RCC_PCLK1Config;
  7. 设置PLL RCC_PLLConfig;
  8. 打开PLL RCC_PLLCmd(ENABLE);
  9. 等待PLL工作 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
  10. 设置系统时钟 RCC_SYSCLKConfig;
  11. 判断是否PLL是系统时钟 while(RCC_GetSYSCLKSource() != 0x08)
  12. 打开要使用的外设时钟 RCC_APB2PeriphClockCmd()/RCC_APB1PeriphClockCmd()
//使用外部8MHz晶振
//系统时钟72MHz,APH 72MHz,APB2 72MHz,APB1 32MHz,USB 48MHz TIMCLK=72M
void RCC_Configuration(void)
{
	//----------使用外部RC晶振-----------
	RCC_DeInit();			//初始化为缺省值
	RCC_HSEConfig(RCC_HSE_ON);	//使能外部的高速时钟 
	while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);	//等待外部高速时钟使能就绪
	
	FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);	//Enable Prefetch Buffer
	FLASH_SetLatency(FLASH_Latency_2);		//Flash 2 wait state
	
	RCC_HCLKConfig(RCC_SYSCLK_Div1);		//HCLK = SYSCLK
	RCC_PCLK2Config(RCC_HCLK_Div1);			//PCLK2 =  HCLK
	RCC_PCLK1Config(RCC_HCLK_Div2);			//PCLK1 = HCLK/2
	RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);	//PLLCLK = 8MHZ * 9 =72MHZ
	RCC_PLLCmd(ENABLE);			//Enable PLLCLK
 
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);	//Wait till PLLCLK is ready
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);	//Select PLL as system clock
	while(RCC_GetSYSCLKSource()!=0x08);		//Wait till PLL is used as system clock source
	
	//---------打开相应外设时钟--------------------
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//使能APB2外设的GPIOA的时钟		 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431798.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python_Scrapy学习笔记(十三)】基于Scrapy框架的图片管道实现图片抓取

基于Scrapy框架的图片管道实现图片抓取 前言 本文中介绍 如何基于 Scrapy 框架的图片管道实现图片抓取&#xff0c;并以抓取 360 图片为例进行展示。 正文 1、Scrapy框架抓取图片原理 利用 Scrapy 框架提供的图片管道类 ImagesPipeline 抓取页面图片&#xff0c;在使用时需…

领域驱动设计理论实践

战略设计 战略设计是将“混沌”解构成“清晰”的过程&#xff0c;在该过程从开始到结束的历程之中&#xff0c;我们会划分出领域、界定通用语言范围、确定出系统限界上下文以及上下文之间的映射方式。 领域划分 战略设计在领域驱动设计中起着关键作用&#xff0c;因为其决定了…

使用Bazel构建前端Sass

注&#xff1a;本文假设对Bazel有一定的了解。本文基于Bazel 4.2.2 版本 在web前端领域&#xff0c;前端样式&#xff0c;web浏览器只认CSS样式语言。而CSS样式语言又过于低级。于是有人发明了更高级的语言&#xff1a;Sass[1]&#xff0c;用于生成CSS代码。 这样的方案&#x…

【C++】队列模拟问题

文章目录队列模拟问题12.7.1 ATM问题12.7.2 队列类12.7.3 Queue类的接口12.7.4 **Queue类的实现**12.7.5 是否需要其他函数&#xff1f;12.7.6 Customer类queue.hqueue.cpp12.7.7 ATM模拟main.cpp队列模拟问题 12.7.1 ATM问题 Heather银行打算在Food Heap超市开设一个自动柜员…

【C++STL精讲】vector的基本使用与常用接口

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;vector是什么&#xff1f;&#x1f337;vector的基本使用&#x1f337;vector常用函数接口&#x1f490;专栏导读 &#x1f338;作者简介&#xff1a;花想云&#xff0c;在读本科生一枚&#xff0c;致力于 C/C…

HAL库版FreeRTOS(上)

目录 FreeRTOS 简介初识FreeRTOS什么是FreeRTOS?为什么选择FreeRTOS&#xff1f;FreeRTOS 的特点商业许可 磨刀不误砍柴工查找资料FreeRTOS 官方文档Cortex-M 架构资料 FreeRTOS 源码初探FreeRTOS 源码下载FreeRTOS 文件预览 FreeRTOS 移植FreeRTOS 移植移植前准备添加FreeRTO…

浏览器断点调试说明

断点调试 断点调试面板 功能按钮介绍 描述&#xff1a;继续执行脚本 或者叫&#xff08;逐过程执行&#xff09; 快捷键 &#xff08;F8&#xff09;或者是&#xff08;Ctrl\&#xff09; 作用&#xff1a;打断点了的地方&#xff08;比如有是三个断点地方&#xff09;就会 第一…

大数据能力提升项目|学生成果展系列之四

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

13.vue-cli

单页面应用程序&#xff1a;所有的功能只在index.html中完成 vue-cli是vue版的webpack 目录 1 安装vue-cli 2 创建项目 3 使用预设 4 删除预设 5 开启项目 6 项目文件内容 6.1 node_moduls 中是项目依赖的库 6.2 public 6.2.1 favicon.ico 是浏览器页签内部…

尚融宝——整合OpenFeign与Sentinel实现兜底方法——验证手机号码是否注册功能

一、整合过程 在项目添加依赖&#xff1a;添加位置 <!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> 在需要的服务中添加启动注…

spring中常见的注解

DI(依赖注入中常见的注解) Autowired&#xff1a;按类型自动装配Resource&#xff1a;按名称或类型自动装配&#xff0c;Qualifier&#xff1a;按名称自动装配&#xff0c;Value &#xff1a;注入int、float、String等基本数据类型&#xff0c;只能标注在成员变量、setter方法上…

【Gradle-1】入门Gradle,前置必读

1、为什么要学习Gradle Gradle作为Android开发默认的构建工具&#xff0c;你的每一次编译都会用到它。招聘要求从以前的熟悉加分&#xff0c;到现在的必备技能&#xff0c;可见Gradle的重要性。 做开发这么久了&#xff0c;你是否对Gradle又爱又恨&#xff1f;是否对Gradle的…

第三章(1):自然语言处理概述:应用、历史和未来

第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来 目录第三章&#xff08;1&#xff09;&#xff1a;自然语言处理概述&#xff1a;应用、历史和未来1. 自然语言处理概述&#xff1a;应用、历史和未来1.1 主要应用1.2 历史1.3 NLP的新…

【科普】PCB为什么常用50Ω阻抗?6大原因

在PCB设计中&#xff0c;阻抗通常是指传输线的特性阻抗&#xff0c;这是电磁波在导线中传输时的特性阻抗&#xff0c;与导线的几何形状、介质材料和导线周围环境等因素有关。 对于一般的高速数字信号传输和RF电路&#xff0c;50Ω是一个常用的阻抗值。 为什么是50Ω&#xff1f…

《程序员面试金典(第6版)》面试题 10.09. 排序矩阵查找(观察法,二分法,分治算法入门题目,C++)

题目描述 给定MN矩阵&#xff0c;每一行、每一列都按升序排列&#xff0c;请编写代码找出某元素。 示例: 现有矩阵 matrix 如下&#xff1a;[[1, 4, 7, 11, 15],[2, 5, 8, 12, 19],[3, 6, 9, 16, 22],[10, 13, 14, 17, 24],[18, 21, 23, 26, 30] ]给定 target 5&…

wma格式怎么转换mp3,4种方法超快学

其实我们在任何电子设备上所获取的音频文件都具有自己的格式&#xff0c;每种格式又对应着自己的属性特点。比如wma就是一种音质优于MP3的音频格式&#xff0c;虽然很多小伙伴比较青睐于wma所具有的音质效果&#xff0c;但也不得不去考虑因wma自身兼容性而引起很多播放器不能支…

【高危】Apache Solr 代码执行漏洞(MPS-wic0-9hjb)

漏洞描述 Apache Solr 是一款开源的搜索引擎。 在Apache Solr 受影响版本中&#xff0c;由于Solr默认配置下存在服务端请求伪造漏洞&#xff0c;且SolrResourceLoader中实现了java SPI机制。当Solr以SolrCloud模式启动时&#xff0c;攻击者可以通过构造恶意的solrconfig.xml文…

几个最基本软件的环境变量配置

在Windows中配置环境变量位置&#xff1a; 控制面板->系统和安全->系统。可以点击&#xff1a;“此电脑”->“属性”直接进入。 点击“高级系统设置”->【环境变量】。在这里可以看见用户变量和系统变量&#xff0c;如果你这台机器不是你一个人使用设置为用户变量…

接口文档设计避坑指南

我们做后端开发的,经常需要定义接口文档。 最近在做接口文档评审的时候&#xff0c;发现一个小伙伴定义的出参是个枚举值&#xff0c;但是接口文档没有给出对应具体的枚举值。其实&#xff0c;如何写好接口文档&#xff0c;真的很重要。今天田螺哥&#xff0c;给你带来接口文档…

Vue学习笔记(4. 生命周期)

1. 生命周期写法&#xff08;vue2与vue3比对&#xff09; 创建前&#xff1a;vue3 setup, vue2 beforeCreate //组件创建前执行的函数 创建后&#xff1a;vue3 setup, vue2 created //组件创建后执行的函数 挂载前&#xff1a;vue3 onBeforeMount, vue2 beforeMount //挂…