基于STM32F407的定时器以及定时器中断的实现

目录

1.时钟 

2.为什么需要时钟 

3.时钟信号是怎么产生的?

4.STM32F4xx时钟树 

5.修改固件库时钟相关代码

6.定时器  

    6.1 时基单元: Time Base Unit 

            a.定时器预分频器TIMx_PSC 

            b.重载计数值寄存器TIMx_ARR 

            c. 计数器TIMx_CNT 

    6.2 输入捕获单元 

    6.3 输出比较单元 

7.STM32F4xx 定时器概述  

    7.1 SysTick  

        寄存器配置步骤:

            a.disable SysTick(禁用)

            b.选择时钟源 

            c.使能中断 

            d.设定重载计数值N和当前计数器的值 

            e.enable SysTick 

            练习1:利用SysTick实现ms级别的延时

    7.2 基本定时器(TIM6、TIM7)

    7.3 通用定时器

        a. TIM2~TIM5 

        b.TIM9~TIM14 

    7.4 高级定时器(TIM1和TIM8)

    7.5 看门狗 watch dog 

        a.为什么要看门狗?

        b.看门狗是怎么实现的 ?

        c.看门狗的实现 

            1.初始化配置看门狗

            2.周期性的"喂狗"(重置计数值)                

 8. STM32F4xx固件库中定时器相关函数 

 1. 定时器时基单元配置 

        a.使能时钟

        b.初始化时基单元    

            b.1 定义一个结构体变量

            b.2 结构体成员赋值 

            b.3  写入寄存器

2. 定时器中断配置(同时还需要配置NVIC)

        a.配置时基单元

        b.定时器中断使能 

        c.NVIC配置  

        d.开启定时器 

 练习2 

 配置输出比较

        1.GPIO配置 

            a.使能时钟 

            b.GPIO配置 

            c.配置引脚为具体的复用功能 

        2.配置时基单元 

        3.配置输出比较单元(各个通道都是独立)

        4.开启定时器 

练习3


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);
}


        
    
        
        
        
        

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值