目录
1.时钟
什么是时钟?
一个周期性的信号
1 ---- ---- ----
0 ———— ———— ————
所以时钟信号就是周期性变化的信号
关于时钟有两个比较重要的概念需要讲解:
T:时钟周期,最小重复的信号单元的时间长度,基本单元s
F: 时钟频率,1s内有多少重复的信号单元(1s振动多少次),单位为Hz
--->T=1/F
例子:
F=200hz 每次振动的时间是5ms
2.为什么需要时钟
时钟最主要的作用就是用来同步信号用的
什么是同步?
在通信内,比如A和B发送数据的时候,A发完数据之后应该要等待B去接收,等B接收完成之后A再继续发,这个就是
同步通信
我们的M4中大部分的时序逻辑电路都需要同步,那么设备之间怎么实现同步?
这个就需要用到时钟信号。
对于图示中的时序图可以得知,D此时输出的就是一条干净的,没有毛刺的信号,想处理掉毛刺的要求:
1.输入信号C只能再clock下降沿的时候改变,在clock高跳变的时候保持不变了
2.T > 2*de_t (周期必须大于2倍时延)
3.时钟信号是怎么产生的?
在自然中有一些物体天生就会产生振动 ---> 石英晶体
如果想利用石英晶体产生规则的、周期性信号,需要一些电路才能保证:
晶振电路:频率比较小,比如:8M、12M......
但是石英晶体难以满足现代计算机的高频需要
那么我们就有"分频/倍频"电路:
分频:把输入频率变小
倍频:把输入频率变大
在M4中有一个非常经典电路:PLL锁相环电路
4.STM32F4xx时钟树
在《STM32F4xx中文参考手册》第六章
在这个时钟树中有几个关键名词:
LSI: low Speed Internal 内部低速时钟(32khz RC振荡器)
LSE: low Speed External 外部低速时钟(32.768khz 晶振)
HSI: High Speed Internal 内部高速时钟(16Mhz RC振荡器)
HSE:High Speed External 外部高速时钟(8Mhz 晶振)
通过GEC-M4原理图得知:
HSE_VALUE=8M(外接晶振8M)
最重要就是SYSCLK(最高达到168Mhz),它的输入有三个,分别为HSE/HSI/PLLCLK,选择这三者之一作为系统时钟
的输入来源,很明显HSE和HSI达不到高频的要求,只能从PLLCLK提供时钟。而PLLCLK时钟的来源是HSE经过M分频
之后,到N倍频之后,P分频得到的。
即SYSCLK == PLLCLK ==168M
=(HSE/M)*N/P
所以PLLCLK是由M和N以及P决定的,实际上这三者的值都可以在代码中指定:
M --->在代码中用PLL_M表示
N --->在代码中用PLL_N表示
P --->在代码中用PLL_P表示
得到一个达到要求的SYSCLK之后,接着可以来到AHB总线,接着可以分成高速APB(APB2)和低速APB(APB1)
AHBCLK == SYSCLK == 168Mhz
APBxCLK == AHBCLK/(APBx Prescaler)
低速APB(APB1)最大值是42Mhz,高速APB(APB2)最大值84Mhz
来到APBx总线上后,所有的定时器都是挂载在APBx总线上,那么我们的定时器的时钟频率是多少?
通过时钟树的分析得到,定时器的输入频率分成两种:
a.如果APBx的presc == 1的话,那么定时器的输入频率等于APBx的频率
b.如果APBx的presc != 1的话,那么定时器的输入频率等于APBx的频率*2
因为APB1的presc == 4,所以位于APB1上的定时器的输入频率== 42M*2 == 84M
同理APB2的presc == 2,所以位于APB2上的定时器的输入频率== 84M*2 == 168M
总结:
PLLCLK == SYSCLK == AHBCLK == 168Mhz
APB1CLK == 42M APB2CLK == 84M 它们的定时器的输入频率==它们的频率*2
5.修改固件库时钟相关代码
因为ST公司提供固件库的时候,不知道其它公司设计的板子采用多少频率的HSE晶振,因此设定了一个可修改的值
我们就必须根据自己的硬件对代码进行修改。
5.1 修改HSE_VALUE为8M
stm32f4xx.h -- 144行
5.2 修改与PLL相关的
system_stm32f4xx.c
PLL_M 8 (371行)
PLL_N 336(不需要改)
PLL_P 2(不需要改)
上述修改根据硬件电路的设计而来的!!!
6.定时器
timer:定时的器件
在STM32上,一般来说,定时器由三部分组成:
时基单元、输入捕获单元、输出比较单元
6.1 时基单元: Time Base Unit
定时器的基本单元,所有的定时器都必须具备的单元
时基单元 = 计数器 + 重载计数值寄存器 + 定时器预分频器 组成
时基单元的工作原理:
将计数器设置为重载值按照一定的频率递减到0,或者按照一定的频率从0递增到重载值,当计数器溢出之后
可以产生一个溢出中断以此来达到定时的功能。
组成时基单元的三个器件的作用:
a.定时器预分频器TIMx_PSC
用来将定时器的总线时钟进行分频,提供一个合适的频率,给计数器去计数。分频系数的范围为1~65536
是一个16位的寄存器
b.重载计数值寄存器TIMx_ARR
用来设定最大值,设置为N值。如果重载计数值寄存器填0,则计数器停止
c. 计数器TIMx_CNT
按照预分频器得到的频率,从0递增到N,或者从N递减到0,并且可以在溢出之后,产生定时器中断事件
溢出的含义:
如果为递增模式,当计数值达到N之后还需要加1才会溢出
如果为递减模式, 当计数值达到0之后还需要减1才会溢出
那么我们怎么知道计数器溢出产生中断,到底花了多少时间?
我们知道计数器每一次运算(+1)是需要花费时间,那么我们只需要把每次+1的时间求出来就是定时器定时时间
假设每次+1的时间为t ,产生中断的时间为
t*(N+1)
举个例子:
假设TIMx是位于APB1总线上,那么Fin = 84Mhz
则计数器计数的频率 Fcnt = Fin/(TIMx_PSC+1)
为了方便计算,把TIMx_PSC设置为83
则Fcnt = Fin/(TIMx_PSC+1) = 84M/84 = 1M
此时计数器的计数周期Tcnt = 1/Fcnt = 1/1M = 1us
意味着计数器每过1us加1
所以定时器定时时间为t=(N+1)*Tcnt
假设N为999 则 t=(N+1)*Tcnt = 1000 * 1us = 1ms
6.2 输入捕获单元
可以对一个或多个输入信号进行处理
有些定时器不具备输入捕获单元。
具体可以捕捉多少个输入信号需要看你定时器有几个通道channel
有什么用?
比如:可以计算输入信号的频率
输入信号经过"输入捕获阶段"(数字滤波、多路复用、预分频、去噪.....),到信号检测,当检测到需要的信号
状态(上升沿变化/下降沿变化)变化的时候,计数器开始工作,当你第二次检测到相同的边沿,就会把计数器的值
锁定在输入捕获寄存器中
这样就可以根据预先设定的定时器参数,就可以计算出这个信号的周期
6.3 输出比较单元
可以输出一个或者多个信号
有些定时器没有输出比较单元
具体可以输出多少个信号,看定时器有几个通道
输出比较:
定时器可以向对应的GPIO引脚(复用)输出一个电平状态
并且可以根据"输出比较寄存器"的值,来翻转输出的电平状态。
比如:
TIMx_CCR >TIMx_CNT 向GPIO引脚输出一个低电平
TIMx_CCR <= TIMx_CNT 向GPIO引脚输出一个高电平
注意:
一条线路上的输入捕获和输出比较共用一个寄存器
因此一个定时器的输入捕获和输出比较功能不能同时使用
7.STM32F4xx 定时器概述
7.1 SysTick
时钟嘀嗒定时器 --> 系统定时器
在所有的M3/M4中都会内置于NVIC中一个SysTick定时器
这个定时器被称之为系统定时器,只有时基单元。
并且在溢出的时候,会产生SysTick中断,执行中断处理函数SysTick_Handler
为什么需要这样一个定时器?
因为大部分操作系统,都需要一个硬件定时器来产生操作系统需要的时钟嘀嗒中断,主要应用于操作系统计时
比如:时间片的产生
Systick定时器将作为整个系统的基本的时间单元。
不跑操作系统,这个定时器有用?
有,延时函数就可以用SysTick来实现。
SysTick被集成在NVIC中,也可以产生SysTick中断,其实就是一个简单的24bits递减计数器,它可以运行在
时钟主频上(168M),也可以运行在AHB的8分频上(21M)
SysTick定时器的描述在《Cortex m3与m4 权威指南.pdf》
寄存器配置步骤:
a.disable SysTick(禁用)
将控制和状态寄存器中bit0置为0
b.选择时钟源
将控制和状态寄存器中bit2:
1:168M
0:21M
c.使能中断
将控制和状态寄存器中bit1:
1 使能中断
0 禁止中断
d.设定重载计数值N和当前计数器的值
e.enable SysTick
完成配置后,每过一段时间就会产生一个SysTick中断
练习1:利用SysTick实现ms级别的延时
需要将SysTick设置为1ms产生一个中断
寄存器版本
delay.c
#include "delay.h"
#include "stm32f4xx.h"
int times = 0;
void mydelay_ms(int ms)
{
times = ms;
SysTick_CTRL &= ~(1<<0);
SysTick_CTRL |= (1<<2);
SysTick_CTRL |= (1<<1);
SysTick_LOAD = 167999;
SysTick_VAL = 0;
SysTick_CTRL |= (1<<0);
while(times);
SysTick_CTRL &= ~(1<<0);
}
void mydelay_us(int us)
{
times = us;
SysTick_CTRL &= ~(1<<0);
SysTick_CTRL |= (1<<2);
SysTick_CTRL |= (1<<1);
SysTick_LOAD = 167;
SysTick_VAL = 0;
SysTick_CTRL |= (1<<0);
while(times);
SysTick_CTRL &= ~(1<<0);
}
delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
#include "stm32f4xx.h"
/*SysTick相关的寄存器*/
#define SysTick_CTRL *((volatile unsigned long *)(0xE000E010))
#define SysTick_LOAD *((volatile unsigned long *)(0xE000E014))
#define SysTick_VAL *((volatile unsigned long *)(0xE000E018))
#define SysTick_CALIB *((volatile unsigned long *)(0xE000E01C))
void mydelay_ms(int ms);
void mydelay_us(int us);
#endif
在stm32f4xx_it.c中开头用关键字extern外部声明times
再在系统时钟函数里添加一行代码就可以使用了
思路:
选择SysTick时钟源为168Mhz
计数值每次减一的时间为 (1/168M)s
让SysTick产生中断的时间为1ms
(N+1)*(1/168M) = 1/1000s
N==167999
在TM32固件库提供了一个配置SysTick的函数
uint32_t SysTick_Config(uint32_t ticks)
此函数默认采用内核时钟168M bit2-->1
使能中断 bit1-->1
参数ticks就是要计数的次数 = 赋值给自动重载值 + 1
也就是说:
SysTick_Config(168000); 1ms产生一个中断
SysTick_Config(168); 1us产生一个中断
在固件库中有一个全局变量SystemCoreClock表示系统内核时钟(168M)
以上的代码可以变成:
SysTick_Config(SystemCoreClock/1000); 1ms产生一个中断
SysTick_Config(SystemCoreClock/1000000); 1us产生一个中断
调用库函数
delay.c
#include "delay.h"
#include "stm32f4xx.h"
int times = 0;
//void mydelay_ms(int ms)
//{
// times = ms;
// SysTick_CTRL &= ~(1<<0);
// SysTick_CTRL |= (1<<2);
// SysTick_CTRL |= (1<<1);
// SysTick_LOAD = 167999;
// SysTick_VAL = 0;
//
// SysTick_CTRL |= (1<<0);
// while(times);
// SysTick_CTRL &= ~(1<<0);
//}
//void mydelay_us(int us)
//{
// times = us;
// SysTick_CTRL &= ~(1<<0);
// SysTick_CTRL |= (1<<2);
// SysTick_CTRL |= (1<<1);
// SysTick_LOAD = 167;
// SysTick_VAL = 0;
//
// SysTick_CTRL |= (1<<0);
// while(times);
// SysTick_CTRL &= ~(1<<0);
//}
void mydelay_ms(int ms)
{
times = ms;
SysTick_Config(SystemCoreClock/1000);
NVIC_SetPriority(SysTick_IRQn, 0);
while(times);
}
void mydelay_us(int us)
{
times = us;
SysTick_Config(SystemCoreClock/1000000);
NVIC_SetPriority(SysTick_IRQn, 0);
while(times);
}
需要注意的是:
我们使用中断,必须执行SysTick_Handler函数,这个函数已经定义在stm32f4xx_it.c中
如果大家想设计延时函数,必须修改内容
方法一:
找到定义在stm32f4xx_it.c中的中断函数,把里面的内容替换成自己的内容
方法二:
删掉定义在stm32f4xx_it.c中的中断函数,自行编写一个中断函数
最好是把中断SysTick_Handler的优先级配置为最高-->0
主要是为了在其它中断中使用我们的延时函数,需要把SysTick中断的优先级配置一个高的
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
比如:
NVIC_SetPriority(SysTick_IRQn,0);
对之前寄存器版本的代码进行修改:
void mydelay_ms(int ms)
{
times = ms;
SysTick_Config(SystemCoreClock/1000);// 1ms产生一个中断
NVIC_SetPriority(SysTick_IRQn,0);//配置优先级为0
while(times);
}
7.2 基本定时器(TIM6、TIM7)
只有时基单元,没有输入捕获或者输出比较单元
16位自动重载计数值 -->计数值上限65535 只能递增计数
用途:用作定时器中断
7.3 通用定时器
a. TIM2~TIM5
TIM3/TIM4(16bits) TIM2和TIM5(32bits)计数器
计数模式由代码配置成:
递增、递减、先递增后递减
多达4个独立的通道(可以有4个GPIO引脚复用)
可以由软件配置为:输入捕获、输出比较
b.TIM9~TIM14
16bits计数器,只能递增计数
2个独立的通道
可以由软件配置为:输入捕获、输出比较
用途:
1.作为基本定时器使用:时基单元
2.捕获输入信号:时基单元+输入捕获单元
3.输出PWM方波:时基单元+输出比较单元
7.4 高级定时器(TIM1和TIM8)
16bits计数器、计数模式递增、递减、先递增后递减
4个独立的通道
高级的地方:
重复计数器(TIMx_RCR)
如果使用了重复计数器,则当计数器重复次数达到了设定的计数器的值+1后,才会产生中断
如果不用重复计数器,高级定时器跟通用定时器没区别
7.5 看门狗 watch dog
a.为什么要看门狗?
系统/程序可能会存在一些致命问题,会导致系统"卡死"
看门狗的作用就是当系统卡死之后,产生一个复位中断(Reset_Handler)
跟你按板子上的复位键效果一样。
但是看门狗只不过是忽略了问题,不能帮你解决问题
b.看门狗是怎么实现的 ?
看门狗原理其实也是定时器,当定时器溢出之后,产生中断,去执行Reset进行复位
c.看门狗的实现
1.初始化配置看门狗
比如设置初始值为N
当你设置好N之后,从N开始减,当减到0的时候,就会CPU复位。所以在正常情况下, 不能让看门狗减到0,隔一段时间
进行"喂狗"。
2.周期性的"喂狗"(重置计数值)
比如:
假如看门狗是50ms产生一次中断
你就必须每隔<50ms去"喂狗"一次
####
思考:
现在我设置了一个看门狗,定时为50ms,又定义了一个定时器,定时为30ms,这个定时器产生中断之后就会去"喂狗"
行不行?
不行,因为定时器中断无论CPU是否跑飞,都会在溢出后产生中断,用定时器去喂狗,会导致看门狗失去效果
"喂狗"的正确操作:每隔一段时间,写一段代码去喂狗
8. STM32F4xx固件库中定时器相关函数
1. 定时器时基单元配置
a.使能时钟
查看定时器是位于APB1还是APB2
RCC_APB1 or RCC_APB2
b.初始化时基单元
tim.h
b.1 定义一个结构体变量
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
b.2 结构体成员赋值
typedef struct
{
uint16_t TIM_Prescaler;//指定定时器的预分频值 TIMx_PSC 是一个16位的整数
//定时器的计数频率: Fcnt = Fin/(TIMx_PSC+1)
uint16_t TIM_CounterMode;
TIM_CounterMode_Up 递增模式
TIM_CounterMode_Down 递减模式
//中心对齐模式:计数器先递增再递减
TIM_CounterMode_CenterAligned1 中心对齐1 只有在向下计数到1时才会产生中断
TIM_CounterMode_CenterAligned2 中心对齐2 只有在向上计数到N-1时才会产生中断
TIM_CounterMode_CenterAligned3 中心对齐3 向上计数和向下计数都会产生中断
uint32_t TIM_Period;//自动重载值 TIMx_ARR N
uint16_t TIM_ClockDivision;//用于输入捕获功能
//用于指定"输入捕获阶段"中数字滤波的采集频率的分频值 不用的时候也需要赋值
TIM_CKD_DIV1 数字滤波采集频率 = 计数器频率 * 1
TIM_CKD_DIV2 数字滤波采集频率 = 计数器频率 * 2
TIM_CKD_DIV4 数字滤波采集频率 = 计数器频率 * 4
uint8_t TIM_RepetitionCounter;//重复计数器的值
//计数器溢出产生中断的计数的次数 = 重复计数器的值+1
} TIM_TimeBaseInitTypeDef;
b.3 写入寄存器
TIM_TimeBaseInit(TIMx,&TIM_TimeBaseInitStruct);
2. 定时器中断配置(同时还需要配置NVIC)
定时器中断配置函数:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
@TIMx:定时器的编号,一共有14个定时器
@TIM_IT:指定定时器中断方式
TIM_IT_Update 定时器更新中断(定时器溢出的时候产生的中断)
.....
@NewState:使能还是禁止
ENABLE
DISABLE
所以定时器中断实现的步骤:
a.配置时基单元
b.定时器中断使能
c.NVIC配置
以定时器13为例
TIM8_UP_TIM13_IRQn
d.开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
@TIMx:定时器的编号
@NewState:使能还是禁止
ENABLE
DISABLE
中断函数的编写:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
先去启动文件查找中断函数名
void TIM8_UP_TIM13_IRQHandler(void)
{
if(TIM_GetITStatus(TIM13,TIM_IT_Update) == SET)
{
......
}
TIM_ClearITPendingBit(TIM13,TIM_IT_Update);
}
练习2
tim.c tim.h
用定时器13实现D1,D2,D3,D4四个灯的1s后同时进行翻转
首先查看数据手册确定定时器13的时钟总线位于哪一条之上-------->APB1总线
之后计算定时1s需要的数据
定时器的预分频值TIMx_PSC = 8399
自动重载值 TIMx_ARR = 9999
通过本章第4点时钟树的知识可知APB1总线的定时器的输入频率为84MHz
Fcnt = Fin/(TIMx_PSC+1) = 84M/(8399+1) = 10000
N = TIMx_ARR
t=(N+1)*Tcnt = (9999+1)/10000 = 1s
timer.c
#include "stm32f4xx.h"
#include "timer.h"
#include "key_lib.h"
#include "led_lib.h"
#include "delay.h"
void TIM13_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8399;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 9999;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM13,&TIM_TimeBaseInitStruct);
TIM_ITConfig(TIM13, TIM_IT_Update, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM13, ENABLE);
}
void TIM8_UP_TIM13_IRQHandler(void)
{
if(TIM_GetITStatus(TIM13,TIM_IT_Update) == SET)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);//翻转D1灯的状态
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);//翻转D2灯的状态
GPIO_ToggleBits(GPIOE,GPIO_Pin_13);//翻转D3灯的状态
GPIO_ToggleBits(GPIOE,GPIO_Pin_14);//翻转D4灯的状态
}
TIM_ClearITPendingBit(TIM13,TIM_IT_Update);
}
timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
#include "stm32f4xx.h"
void TIM13_Init(void);
void TIM13_PWM_Init(u32 Period,u16 Prescaler);
#endif
main.c
#include "main.h"
#include "stm32f4xx.h"
#include "led_lib.h"
#include "beep_lib.h"
#include "key_lib.h"
#include "exti.h"
#include "delay.h"
#include "timer.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
led_lib_init();
beep_lib_init();
EXTIX_Init();
TIM13_Init();
while(1);
}
----------------------------------------------------------------------------------------------
配置输出比较
不同的定时器有2~4个独立的通道,每个通道都是GPIO口复用而来。
如果要使用输出比较,事先需要复用IO
输出比较的实现步骤(输出PWM方波):
以蜂鸣器为例: PF8 -- TIM13_CH1
1.GPIO配置
a.使能时钟
b.GPIO配置
复用推挽输出 --AF
c.配置引脚为具体的复用功能
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
@GPIOx:指定的GPIO分组
GPIOA
GPIOB
.....
@GPIO_PinSource:指定的GPIO引脚(不可以位或)
GPIO_PinSource0
GPIO_PinSource1
.....
@GPIO_AF:指定复用成什么功能
GPIO_AF_TIM13
2.配置时基单元
a.使能定时器的时钟
b.配置时基结构体
(只是输出方波可以不开中断)
3.配置输出比较单元(各个通道都是独立)
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
由于我们选择的是TIM13_CH1 --> OC1
a.定义结构体变量
TIM_OCInitTypeDef TIM_OCInitStruct;
b.结构体成员变量赋值
typedef struct
{
uint16_t TIM_OCMode;//指定输出通道的输出模式
TIM_OCMode_Timing 输出比较寄存器CCR 与 计数器CNT 的比较不会影响输出
TIM_OCMode_Active 当 CCR == CNT 输出高电平
TIM_OCMode_Inactive 当 CCR == CNT 输出低电平
TIM_OCMode_Toggle 当 CCR == CNT 输出极性翻转
TIM_OCMode_PWM1
CNT < CCR 输出有效电平 反之输出无效电平
TIM_OCMode_PWM2
CNT < CCR 输出无效电平 反之输出有效电平
uint16_t TIM_OutputState;//指定输出状态
TIM_OutputState_Disable 不输出信号
TIM_OutputState_Enable 输出信号
uint16_t TIM_OutputNState;//指定互补引脚输出状态(针对于高级定时器)
TIM_OutputNState_Disable 不输出信号
TIM_OutputNState_Enable 输出信号
uint32_t TIM_Pulse;//比较值 TIMx_CCR
uint16_t TIM_OCPolarity;//指定输出极性
TIM_OCPolarity_High 高电平为有效电平
TIM_OCPolarity_Low 低电平为有效电平
uint16_t TIM_OCNPolarity;//指定互补引脚的输出极性(只有高级定时器)
TIM_OCNPolarity_High 高电平为有效电平
TIM_OCNPolarity_Low 低电平为有效电平
uint16_t TIM_OCIdleState; //输出引脚空闲状态为什么电平(只有高级定时器才有)
TIM_OCIdleState_Set 高电平
TIM_OCIdleState_Reset 低电平
uint16_t TIM_OCNIdleState;//互补输出引脚空闲为什么电平(只有高级定时器才有)
TIM_OCNIdleState_Set 高电平
TIM_OCNIdleState_Reset 低电平
} TIM_OCInitTypeDef;
c.写进寄存器
TIM_OC1Init(TIM13,&TIM_OCInitStruct);
4.开启定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
@TIMx:定时器的编号
@NewState:使能还是禁止
ENABLE
DISABLE
练习3
模拟警车声
提供一个函数:可以随时把新的比较值写进寄存器
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);
@TIMx:定时器的编号
@Compare1:新写入的比较值
框架:
int main()
{
//初始化
u8 mode = 1 ;//0表示处于下降模式 1表示处于上升模式
u32 new_compare = 200;
while(1)
{
delay_ms(1);//定时器定时的时间小于改变比较值的时间间隔
if(mode == 1)
{
new_compare++;
if(new_compare == 你认定的最大值)//800
{
mode = 0;
}
}
else if(mode == 0)
{
new_compare--;
if(new_compare == 你认定的最小值)//200
{
mode = 1;
}
}
TIM_SetCompare1(TIM13,new_compare);
}
}
警车声的实现和呼吸灯相似,我在下面把呼吸灯的实现代码列出来了·,只要把定时器14改为定时器13以及复用的引脚改为PF8蜂鸣器就是模拟警车声的代码
timer.c
#include "stm32f4xx.h"
#include "timer.h"
#include "key_lib.h"
#include "led_lib.h"
#include "delay.h"
void TIM13_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8399;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 9999;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM13,&TIM_TimeBaseInitStruct);
TIM_ITConfig(TIM13, TIM_IT_Update, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = TIM8_UP_TIM13_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM13, ENABLE);
}
void TIM14_PWM_Init(u32 Period,u16 Prescaler)
{
uint32_t Compare1_num;
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOF,&GPIO_InitStruct);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_Prescaler = Prescaler;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = Period;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM14,&TIM_TimeBaseInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; //TIM输出比较模式
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
// TIM_OCInitStruct.TIM_Pulse = 10;
TIM_OC1Init(TIM14,&TIM_OCInitStruct);
TIM_Cmd(TIM14, ENABLE);
while(1)
{
for(Compare1_num = 1; Compare1_num <= 200 ; Compare1_num+=10)
{
TIM_SetCompare1(TIM14,Compare1_num);
mydelay_ms(20);
}
for(Compare1_num = 200; Compare1_num >= 1 ; Compare1_num-=10)
{
TIM_SetCompare1(TIM14,Compare1_num);
mydelay_ms(20);
}
}
}
void TIM8_UP_TIM13_IRQHandler(void)
{
if(TIM_GetITStatus(TIM13,TIM_IT_Update) == SET)
{
GPIO_ToggleBits(GPIOF,GPIO_Pin_9);//翻转D1灯的状态
GPIO_ToggleBits(GPIOF,GPIO_Pin_10);//翻转D2灯的状态
GPIO_ToggleBits(GPIOE,GPIO_Pin_13);//翻转D3灯的状态
GPIO_ToggleBits(GPIOE,GPIO_Pin_14);//翻转D4灯的状态
}
TIM_ClearITPendingBit(TIM13,TIM_IT_Update);
}
timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
#include "stm32f4xx.h"
void TIM13_Init(void);
void TIM14_PWM_Init(u32 Period,u16 Prescaler);
#endif
main.c
#include "main.h"
#include "stm32f4xx.h"
#include "led_lib.h"
#include "beep_lib.h"
#include "key_lib.h"
#include "exti.h"
#include "delay.h"
#include "timer.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
led_lib_init();
//beep_lib_init();
EXTIX_Init();
TIM14_PWM_Init(100-1,8400-1);
while(1);
}