• STM32笔记—定时器


    目录

    一、TIM简介   

    二、基本定时器(TIM6和TIM7)

    1. TIM6和TIM7简介

    2. TIM6和TIM7的主要特性

    3. TIM6和TIM7的功能

    3.1 时基单元

    3.2 计数模式

    3.3 时钟源

    三、通用定时器

    1. TIMx(2、3、4、5)简介

    2. TIMx主要功能

    3. 时钟选择

    4. 影子寄存器

    5. 定时中断程序实现

    5.1 函数TIM_TimeBaseInit

    源码:

    5.2 函数NVIC_Init

    5.3 定时中断初始化

    5.4 TIM2中断函数

    6. TIMx输出比较功能

    6.1 输出比较模式的配置步骤

    6.2 PWM模式

    PWM基本结构:

    6.3 呼吸灯代码实现 

    初始化:

    STM32103C8T6的引脚定义图:

    pwm配置:

    main函数:

    7.  输入捕获

    7.1 频率测量方法

     7.2 输入捕获通道

            TIM_ICInit函数:(输入捕获初始化)

            STM32通用或高级定时器的从模式有如下几种:

     8. PWMI模式

     注意:

             PWMI模块初始化:

            TIM_PWMIConfig()函数:

    9. 编码器模式

    编码器接口测速

    四、高级定时器

     1. TIM1和TIM8简介

     2. 重复计数器 

     3. 互补输出和死区插入

    位操作

              左移与右移    


    一、TIM简介   

            定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断;

            16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时【1/(72MHZ/65536*65536)(预分频器和计数器都是16位的)

            根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型  

    类型

    编号

    总线

    功能

    高级定时器

    TIM1TIM8

    APB2

    拥有通用定时器全部功能,

    并额外具有重复计数器、死区生成、互补输出、刹车输入等功能

    通用定时器

    TIM2TIM3

    TIM4TIM5

    APB1

    拥有基本定时器全部功能,

    并额外具有内外时钟源选择、输入捕获、输出比较、

    编码器接口、主从触发模式等功能

    基本定时器

    TIM6TIM7

    APB1

    拥有定时中断、主模式触发DAC的功能

    二、基本定时器(TIM6和TIM7)

    1. TIM6和TIM7简介

             基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。 它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。 这2个定时器是互相独立的,不共享任何资源。

    2. TIM6和TIM7的主要特性

            TIM6和TIM7定时器的主要功能包括: 16位自动重装载累加计数器 ,16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频 ,触发DAC的同步电路 , 在更新事件(计数器溢出)时产生中断/DMA请求。

             上图可以简单的由下图理解:(基准时钟预分频,计数器累加到重装载值产生中断或者事件)

            

    3. TIM6和TIM7的功能

    3.1 时基单元

             时基单元包含: ● 计数器寄存器(TIMx_CNT) ● 预分频寄存器(TIMx_PSC) ● 自动重装载寄存器(TIMx_ARR) 

            预分频器:预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频。它是通过一个16位寄存器(TIMx_PSC)的计数实现分频。

            因为TIMx_PSC控制寄存器具有缓冲,可以在运行过程中改变它的数值,新的预分频数值将在下一个更新事件时起作用。

            CK_PSC预分频器的输入时钟、CNT_EN计数器使能,前半段,从计数器使能开始,定时器时钟等于计数器时钟,后半段,预分频系数从1变成2,定时器时钟变成预分频器输入时钟的一半。当计数周期没有结束时,改变预分频寄存器的值,不会立即生效,而是等到计数结束产生中断,才会传递改变的数值到缓冲寄存器里。

            计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

     3.2 计数模式

            计数器从0累加计数到自动重装载数值(TIMx_ARR寄存器),然后重新从0开始计数并产生一个计数器溢出事件。

            当发生一次更新事件时,所有寄存器会被更新并(根据URS位)设置更新标志(TIMx_SR寄存器的UIF位): ● 传送预装载值(TIMx_PSC寄存器的内容)至预分频器的缓冲区。 ● 自动重装载影子寄存器被更新为预装载值(TIMx_ARR)。

     

             当计数值到了0x36后,计数器溢出,触发中断,并更新中断标志位计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)

     3.3 时钟源

            计数器的时钟由内部时钟(CK_INT)提供。

    三、通用定时器

    1. TIMx(2、3、4、5)简介

            通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。 它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。 使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。

    2. TIMx主要功能

            通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:

            ● 16位向上、向下、向上/向下自动装载计数器 (与基本定时器的区别之一)● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值 ● 4个独立通道: ─ 输入捕获 ─ 输出比较 ─ PWM生成(边缘或中间对齐模式) ─ 单脉冲模式输出 ● 使用外部信号控制定时器和定时器互连的同步电路

            ● 如下事件发生时产生中断/DMA: ─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发) ─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) ─ 输入捕获 ─ 输出比较

            ● 支持针对定位的增量(正交)编码器和霍尔传感器电路 ● 触发输入作为外部时钟或者按周期的电流管理      

    3. 时钟选择

            计数器时钟可由下列时钟源提供:

            ● 内部时钟(CK_INT)

            ● 外部时钟模式1:外部输入脚(TIx)

            ● 外部时钟模式2:外部触发输入(ETR)

            ● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

            对于基本定时器,只能选择72MHz的内部时钟,而通用定时器可以选择如下时钟:(外部时钟+内部时钟)[ITR接别的定时器]
      

     4. 影子寄存器

            当发生一个更新事件时,所有的寄存器都被更新,硬件同时(依据URS位)设置更新标志位(TIMx_SR寄存器中的UIF位)。 ● 预分频器的缓冲区被置入预装载寄存器的值(TIMx_PSC寄存器的内容)。 ● 自动装载影子寄存器被重新置入预装载寄存器的值(TIMx_ARR)。

            下图给出一些例子,当TIMx_ARR=0x36时计数器在ARPE=0/1时的动作。

            当没有预装入时,写入新数值至TIMx_ARR寄存器,,计数器会直接开始自增到36,再发生溢出触发中断。

            而当ARPE=1,真正起作用的变成了自动加载影子寄存器,计数器会先计数到F5,等到计数周期结束才开始重新计数到36,如果没有影子寄存器,就会出现,计数器先从F1自增到0xFFFF,再从0计数到36的现象,计数时间增加。

     5. 定时中断程序实现

    1. static void Timer5_Config(uint16_t psc,uint16_t arr)
    2. {
    3. rcu_periph_clock_enable(RCU_TIMER5);
    4. timer_deinit(TIMER5);
    5. timer_parameter_struct timer_initparam;
    6. timer_struct_para_init(&timer_initparam);
    7. timer_initparam.alignedmode = TIMER_COUNTER_EDGE;
    8. timer_initparam.clockdivision = TIMER_CKDIV_DIV1;
    9. timer_initparam.counterdirection = TIMER_COUNTER_UP;
    10. timer_initparam.period = arr - 1;
    11. timer_initparam.prescaler = psc - 1;
    12. timer_initparam.repetitioncounter = 0U;
    13. timer_init(TIMER5,&timer_initparam);
    14. timer_interrupt_enable(TIMER5,TIMER_INT_UP);
    15. timer_enable(TIMER5);
    16. nvic_irq_enable(TIMER5_DAC_IRQn, 4, 0);
    17. }

    5.1 函数TIM_TimeBaseInit

    1. TIM_TimeBaseInitTypeDef structure
    2. TIM_TimeBaseInitTypeDef定义于文件“stm32f10x_tim.h”:
    3. typedef struct
    4. {
    5. u16 TIM_Period;
    6. u16 TIM_Prescaler;
    7. u8 TIM_ClockDivision;
    8. u16 TIM_CounterMode;
    9. } TIM_TimeBaseInitTypeDef;

    TIM_Period
            TIM_Period设置了在下一个更新事件装入活动的自动重装载寄存器周期的值。它的取值必须在0x0000和0xFFFF之间。
    TIM_Prescaler
            TIM_Prescaler设置了用来作为TIMx时钟频率除数的预分频值。它的取值必须在0x0000和0xFFFF之间。
    TIM_ClockDivision
            TIM_ClockDivision设置了时钟分割。该参数取值见下表。

    TIM_ClockDivision描述
    TIM_CKD_DIV1TDTS = Tck_tim
    TIM_CKD_DIV2TDTS = 2Tck_tim
    TIM_CKD_DIV4TDTS = 4Tck_tim

    TIM_CounterMode
            TIM_CounterMode选择了计数器模式。该参数取值见下表。

    TIM_CounterMode描述
    TIM_CounterMode_UpTIM向上计数模式
    TIM_CounterMode_DownTIM向下计数模式
    TIM_CounterMode_CenterAligned1TIM中央对齐模式1计数模式
    TIM_CounterMode_CenterAligned2TIM中央对齐模式2计数模式
    TIM_CounterMode_CenterAligned3TIM中央对齐模式3计数模式
    1. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    2. TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
    3. TIM_TimeBaseStructure.TIM_Prescaler = 0xF;
    4. TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
    5. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    6. TIM_TimeBaseInit(TIM2, & TIM_TimeBaseStructure);
     源码:

            注意:TIM_TimeBaseInit函数末尾,手动产生了更新事件,若不清除此标志位,则开启中断后,会立刻进入一次中断,如果不介意此问题,则不清除此标志位也可。

    1. void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
    2. {
    3. uint16_t tmpcr1 = 0;
    4. /* Check the parameters */
    5. assert_param(IS_TIM_ALL_PERIPH(TIMx));
    6. assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
    7. assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));
    8. tmpcr1 = TIMx->CR1;
    9. if((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM2) || (TIMx == TIM3)||
    10. (TIMx == TIM4) || (TIMx == TIM5))
    11. {
    12. /* Select the Counter Mode */
    13. tmpcr1 &= (uint16_t)(~((uint16_t)(TIM_CR1_DIR | TIM_CR1_CMS)));
    14. tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
    15. }
    16. if((TIMx != TIM6) && (TIMx != TIM7))
    17. {
    18. /* Set the clock division */
    19. tmpcr1 &= (uint16_t)(~((uint16_t)TIM_CR1_CKD));
    20. tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
    21. }
    22. TIMx->CR1 = tmpcr1;
    23. /* Set the Autoreload value */
    24. TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
    25. /* Set the Prescaler value */
    26. TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
    27. if ((TIMx == TIM1) || (TIMx == TIM8)|| (TIMx == TIM15)|| (TIMx == TIM16) || (TIMx == TIM17))
    28. {
    29. /* Set the Repetition Counter value */
    30. TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
    31. }
    32. /* Generate an update event to reload the Prescaler and the Repetition counter
    33. values immediately */
    34. TIMx->EGR = TIM_PSCReloadMode_Immediate;
    35. }

     5.2 函数NVIC_Init

     5.3 定时中断初始化

            1. 开启时钟 2. 配置时钟源 3. 配置时基单元初始化 4. 触发中断(开启中断、中断优先级、调用NVIC)

    1. /**
    2. * 函 数:定时中断初始化
    3. * 参 数:无
    4. * 返 回 值:无
    5. */
    6. void Timer_Init(void)
    7. {
    8. /*开启时钟*/
    9. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
    10. /*配置时钟源*/
    11. TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    12. /*时基单元初始化*/
    13. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
    14. TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    15. TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    16. TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
    17. TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
    18. TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
    19. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    20. /*中断输出配置*/
    21. TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
    22. //TIM_TimeBaseInit函数末尾,手动产生了更新事件
    23. //若不清除此标志位,则开启中断后,会立刻进入一次中断
    24. //如果不介意此问题,则不清除此标志位也可
    25. TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
    26. /*NVIC中断分组*/
    27. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
    28. //即抢占优先级范围:0~3,响应优先级范围:0~3
    29. //此分组配置在整个工程中仅需调用一次
    30. //若有多个中断,可以把此代码放在main函数内,while循环之前
    31. //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
    32. /*NVIC配置*/
    33. NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
    34. NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
    35. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
    36. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
    37. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
    38. NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
    39. /*TIM使能*/
    40. TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
    41. }
    5.4 TIM2中断函数
    1. * 函 数:TIM2中断函数
    2. * 参 数:无
    3. * 返 回 值:无
    4. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
    5. * 函数名为预留的指定名称,可以从启动文件复制
    6. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
    7. */
    8. void TIM2_IRQHandler(void)
    9. {
    10. if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
    11. {
    12. Num ++; //Num变量自增,用于测试定时中断
    13. TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
    14. //中断标志位必须清除
    15. //否则中断将连续不断地触发,导致主程序卡死
    16. }
    17. }

    6. TIMx输出比较功能

         输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形;每个高级定时器和通用定时器都拥有4个输出比较通道;高级定时器的前3个通道额外拥有死区生成和互补输出的功能。

            即通过比较CNT计数器和捕获/比较寄存器,输出电平。

            上图,CCIP极性选择,此外,输出模式控制器模式如下:

    模式

    描述

    冻结

    CNT=CCR时,REF保持为原状态

    匹配时置有效电平

    CNT=CCR时,REF置有效电平

    匹配时置无效电平

    CNT=CCR时,REF置无效电平

    匹配时电平翻转

    CNT=CCR时,REF电平翻转

    强制为无效电平

    CNTCCR无效,REF强制为无效电平

    强制为有效电平

    CNTCCR无效,REF强制为有效电平

    PWM模式1

    向上计数:CNT时,REF置有效电平,CNTCCR时,REF置无效电平

    向下计数:CNT>CCR时,REF置无效电平,CNTCCR时,REF置有效电平

    PWM模式2

    向上计数:CNT时,REF置无效电平,CNTCCR时,REF置有效电平

    向下计数:CNT>CCR时,REF置有效电平,CNTCCR时,REF置无效电平

     6.1 输出比较模式的配置步骤

            1. 选择计数器时钟(内部,外部,预分频器)

            2. 将相应的数据写入TIMx_ARR和TIMx_CCRx寄存器中

            3. 如果要产生一个中断请求和/或一个DMA请求,设置CCxIE位和/或CCxDE位。

            4. 选择输出模式(往往设置为PWM1模式)

            例如当计数器CNT与CCRx匹配时翻转OCx的输出引脚,CCRx预装载未用,开启OCx输出且高电平有效,则必须设置OCxM=’011’、OCxPE=’0’、CCxP=’0’和CCxE=’1’。(怎么理解?)

            CCxP = 0///CCxE = 1(捕获/比较使能寄存器(TIMx_CCER))开启OCx输出且高电平有效

             OCxM=’011’、OCxPE=’0翻转OCx的输出引脚,CCRx预装载未用

    捕获/比较模式寄存器1(TIMx_CCMR1):

          

           5. 设置TIMx_CR1寄存器的CEN位启动计数器

            TIMx_CCRx寄存器能够在任何时候通过软件进行更新以控制输出波形,条件是未使用预装载寄存器(OCxPE=’0’,否则TIMx_CCRx影子寄存器只能在发生下一次更新事件时被更新)。下图给出了一个例子。

            计数器的值和输出比较寄存器的值一直在比较,待,计数器的值达到CCR的003A,因为设置的模式时翻转电平,所以通过输出模式控制器后到达的OC1电平从低电平变成高电平,但是由于CNT的值还没有到重装载寄存器的值,计数器接着自增。而此时,在CC1R寄存器写入B201h,那么由于没有使用预装载,那么待到B201,再次马上翻转电平。 

    6.2 PWM模式

            脉冲宽度调制模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。

    PWM模式1

    向上计数:CNT时,REF置有效电平,CNTCCR时,REF置无效电平

    向下计数:CNT>CCR时,REF置无效电平,CNTCCR时,REF置有效电平

    PWM模式2

    向上计数:CNT时,REF置无效电平,CNTCCR时,REF置有效电平

    向下计数:CNT>CCR时,REF置有效电平,CNTCCR时,REF置无效电平

            仅当发生一个更新事件的时候,预装载寄存器才能被传送到影子寄存器,因此在计数器开始计数之前,必须通过设置TIMx_EGR寄存器中的UG位来初始化所有的寄存器。


              下面是一个PWM模式1的例子。当TIMx_CNT

                注意:CCxIF中断标志,当TIMx_CNT与TIMx_CCR1匹配时置1

        PWM基本结构:

       30相当于CCR的值(捕获比较寄存器),99相当于自动重装载值

            PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1)
            PWM占空比:  Duty = CCR / (ARR + 1)
            PWM分辨率:  Reso = 1 / (ARR + 1)
    6.3 呼吸灯代码实现 
    初始化:

            需要注意的是GPIO的配置,要配置成复用推挽输出。

            将PA0引脚初始化为复用推挽输出,受外设控制的引脚,均需要配置为复用模式 。   

    /***********************************************************************************************************/

    此外特别注意两个地方:

      TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;                    //计数周期,即ARR的值
      TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;                //预分频器,即PSC的值

    如上图,对于TIM2,因为上面到APB1外设的时钟最大36MHZ可以知道预分频系数是2,所以频率乘2,变成72mhz最大给定时器2,所以一般下面的与分频器psc设置为72,而上面的计数周期设置为100是方便设置占空比的。

    /***********************************************************************************************************/

    1. /*开启时钟*/
    2. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
    3. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
    4. /*GPIO初始化*/
    5. GPIO_InitTypeDef GPIO_InitStructure;
    6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    7. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
    8. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    9. GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
    10. //受外设控制的引脚,均需要配置为复用模式
    11. /*配置时钟源*/
    12. TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
    13. /*时基单元初始化*/
    14. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
    15. TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    16. TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    17. TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
    18. TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
    19. TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
    20. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
    21. /*输出比较初始化*/
    22. TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
    23. TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
    24. //则最好执行此函数,给结构体所有成员都赋一个默认值
    25. //避免结构体初值不确定的问题
    26. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
    27. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反
    28. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
    29. TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值
    30. TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1
    31. /*TIM使能*/
    32. TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
    33. }

     注意其中对于GPIO的初始化还可以使用I/O口重映射:

    1. /*GPIO初始化*/
    2. GPIO_InitTypeDef GPIO_InitStructure;
    3. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    4. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
    5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    6. GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
    7. //受外设控制的引脚,均需要配置为复用模式
     STM32103C8T6的引脚定义图:

             查手册可以知道复用的引脚为PA15,如下表:

             使用下图函数进行配置,改变指定引脚的映射。

     

             但是PA15正好是主功能是JTDI的特殊引脚,故在配置io口时需要注意让该io口变成普通的io再配置复用。

             具体配置代码如下:

    1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启AFIO时钟
    2. GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);
    3. GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);
    pwm配置:
    1. /**
    2. * 函 数:PWM设置CCR
    3. * 参 数:Compare 要写入的CCR的值,范围:0~100
    4. * 返 回 值:无
    5. * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
    6. * 占空比Duty = CCR / (ARR + 1)
    7. */
    8. void PWM_SetCompare1(uint16_t Compare)
    9. {
    10. TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
    11. }
    main函数:
    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "PWM.h"
    5. uint8_t i; //定义for循环的变量
    6. int main(void)
    7. {
    8. /*模块初始化*/
    9. OLED_Init(); //OLED初始化
    10. PWM_Init(); //PWM初始化
    11. while (1)
    12. {
    13. for (i = 0; i <= 100; i++)
    14. {
    15. PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
    16. Delay_ms(10); //延时10ms
    17. }
    18. for (i = 0; i <= 100; i++)
    19. {
    20. PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
    21. Delay_ms(10); //延时10ms
    22. }
    23. }
    24. }

    7.  输入捕获

            输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数;每个高级定时器和通用定时器都拥有4个输入捕获通道;可配置为PWMI模式,同时测量频率和占空比。

    7.1 频率测量方法

            测频法:在闸门时间T内,对上升沿计次,得到N,则频率f = N/T;

            测周法:两个上升沿内,以标准频率fc计次,得到N ,则频率f = fc/N。

            频率的定义是:1s内出现多少个重复的周期。那么测频法如果T=1s,那么在闸门时间内,计数次数刚好等于频率。但是如果待测信号的频率非常低,1s内出现的上升沿将非常少甚至没有,但是频率肯定不是0,故当计数次数少时,即上升沿出现次数少时,对于频率测量的误差会很大,故往往测频法适用于测量高频信号,有助于减少误差。(计数次数都希望越大越好,以减少误差,但是注意计数均存在±1的误差,因为当周期只出现了一半的时候,若闸门时间就到了,那么就会只取次数,出现误差)

            而测周法恰恰相反,对于低频信号而言,周期更长,计数次数会增加,误差减小。假如有一待测信号为500KHz,而fc = 1MHz,那么一个周期内仅仅能计数一两次,那么当待测信号频率更高时,计次次数将更小甚至为0,那么对于待测信号频率的判断误差会很大。

            那什么是高频信号什么是低频信号?中界频率:测频法与测周法误差相等的频率点。

     7.2 输入捕获通道
            TIM_ICInit函数:(输入捕获初始化)

            具体代码如下:

    1. /*输入捕获初始化*/
    2. TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
    3. TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
    4. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    5. TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获
    6. TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获
    7. TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉
    8. TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道

             需要注意下图部分,每次捕获后计数器清零。

             STM32通用或高级定时器的从模式有如下几种:

            1、复位模式 【Reset mode】

            2、触发模式 【Trigger mode】

            3、门控模式 【Gate mode】

            4、外部时钟模式1 【External clock mode 1】

            5、编码器模式 【encode mode】

            计数器开始依据内部时钟计数,然后正常运转直到TI1出现一个上升沿;此时,计数器被清零然后从0重新开始计数。同时,触发标志(TIMx_SR寄存器中的TIF位)被设置,根据TIMx_DIER寄存器中TIE(中断使能)位和TDE(DMA使能)位的设置,产生一个中断请求或一个DMA请求。下图显示当自动重装载寄存器TIMx_ARR=0x36时的动作。在TI1上升沿和计数器的实际复位之间的延时,取决于TI1输入端的重同步电路。

            当有效触发输入信号出现时,计数器将会被复位,同时还会产生更新事件和触发事件。如果计数器向上计数或中央对齐模式的话,复位后计数器从0开始计数,如果向下计数模式,复位后计数器从ARR值开始计数。不妨以计数器向上计数为例,将它配置在复位从模式。比方说当计数器计数到某个数据的时候,来了个触发信号,计数器不再继续往上计数,而是重新归0后开始计数。当然,计数器的实际复位操作与触发沿之间往往会有个小的延时,这是由于触发信号作为有效触发脉冲的话,还需要经过定时器内的同步电路确认。

            下面列出了相关寄存器:

             清零逻辑如下图,触发源选择器选择TI1FP1,TRGI触发从模式reset。(但是自动清除只能选择通道1和通道2)

            即当检测到上升沿后,该触发信号将CNT计数器的值转运到捕获寄存器中,即CCR1 = CNT,之后CNT置零,CNT计数继续++,只要没有达到自动重装载值,当边沿检测到下一次上升沿,再次发生转运,CCR1 = CNT,这样刚好得到了一个周期的计数值,故其实输入捕获使用的是测周法获取频率值。

     这里再简单介绍一下从模式控制寄存器(TIMx_SMCR)

            滤波的配置:

             从模式清零:

             TIM_SelectInputTrigger():

             即TI1产生上升沿时,会触发CNT归零。实现代码:

    1. /*选择触发源及从模式*/
    2. TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1
    3. TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位
    4. //即TI1产生上升沿时,会触发CNT归零

     8. PWMI模式

            两个通道,一个上升沿触发一个下降沿触发,当上升沿触发时,CCR1 = CNT,之后从模式控制寄存器使得CNT = 0,待下次下降沿到来,CCR2 = CNT(该计数值刚好一个高电平的计数值。),之后依次循环。

     注意:

            GPIO的配置,需要配置成浮空输入,我这里配置成了上拉输入:可以参考这篇文章:

            输入捕获时,GPIO引脚的输入方式如何设置?-CSDN博客

            引申:什么时候可以用浮空输入?
            当捕获方波信号的时候,应该用浮空输入,不能用下拉输入。因为方波信号本身既有高电平也有低电平,强制下拉为低电平会影响方波信号。捕获发生在上升沿或者下降沿,如果没有上升沿和下降沿,就无法发生捕获。被捕捉的信号本身没有上升沿或下降沿,要通过IO口输入设置造出上升沿或下降沿,被捕捉信号本身有上升沿或下降沿的,IO口设置为浮空输入即可。

    1. /*GPIO初始化*/
    2. GPIO_InitTypeDef GPIO_InitStructure;
    3. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    4. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    6. GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入

            为什么初始化PA6?

             PWMI模块初始化:
    1. /*PWMI模式初始化*/
    2. TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
    3. TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
    4. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    5. TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获
    6. TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获
    7. TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉
    8. TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道
    9. //此函数同时会把
            TIM_PWMIConfig()函数:
    1. /**
    2. * @brief Configures the TIM peripheral according to the specified
    3. * parameters in the TIM_ICInitStruct to measure an external PWM signal.
    4. * @param TIMx: where x can be 1, 2, 3, 4, 5, 8, 9, 12 or 15 to select the TIM peripheral.
    5. * @param TIM_ICInitStruct: pointer to a TIM_ICInitTypeDef structure
    6. * that contains the configuration information for the specified TIM peripheral.
    7. * @retval None
    8. */
    9. void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)
    10. {
    11. uint16_t icoppositepolarity = TIM_ICPolarity_Rising;
    12. uint16_t icoppositeselection = TIM_ICSelection_DirectTI;
    13. /* Check the parameters */
    14. assert_param(IS_TIM_LIST6_PERIPH(TIMx));
    15. /* Select the Opposite Input Polarity */
    16. if (TIM_ICInitStruct->TIM_ICPolarity == TIM_ICPolarity_Rising)
    17. {
    18. icoppositepolarity = TIM_ICPolarity_Falling;
    19. }
    20. else
    21. {
    22. icoppositepolarity = TIM_ICPolarity_Rising;
    23. }
    24. /* Select the Opposite Input */
    25. if (TIM_ICInitStruct->TIM_ICSelection == TIM_ICSelection_DirectTI)
    26. {
    27. icoppositeselection = TIM_ICSelection_IndirectTI;
    28. }
    29. else
    30. {
    31. icoppositeselection = TIM_ICSelection_DirectTI;
    32. }
    33. if (TIM_ICInitStruct->TIM_Channel == TIM_Channel_1)
    34. {
    35. /* TI1 Configuration */
    36. TI1_Config(TIMx, TIM_ICInitStruct->TIM_ICPolarity, TIM_ICInitStruct->TIM_ICSelection,
    37. TIM_ICInitStruct->TIM_ICFilter);
    38. /* Set the Input Capture Prescaler value */
    39. TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    40. /* TI2 Configuration */
    41. TI2_Config(TIMx, icoppositepolarity, icoppositeselection, TIM_ICInitStruct->TIM_ICFilter);
    42. /* Set the Input Capture Prescaler value */
    43. TIM_SetIC2Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    44. }
    45. else
    46. {
    47. /* TI2 Configuration */
    48. TI2_Config(TIMx, TIM_ICInitStruct->TIM_ICPolarity, TIM_ICInitStruct->TIM_ICSelection,
    49. TIM_ICInitStruct->TIM_ICFilter);
    50. /* Set the Input Capture Prescaler value */
    51. TIM_SetIC2Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    52. /* TI1 Configuration */
    53. TI1_Config(TIMx, icoppositepolarity, icoppositeselection, TIM_ICInitStruct->TIM_ICFilter);
    54. /* Set the Input Capture Prescaler value */
    55. TIM_SetIC1Prescaler(TIMx, TIM_ICInitStruct->TIM_ICPrescaler);
    56. }
    57. }

    9. 编码器模式

             编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度
            每个高级定时器和通用定时器都拥有1个编码器接口,C8T6拥有4个编码器接口
            两个输入引脚借用了输入捕获的通道1和通道2(CH1和CH2引脚)
            编码器接口可以看做是带有方向控制的外部时钟
            https://blog.csdn.net/NRWHF/article/details/128553847

            编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。

            所以在开始计数之前必须配置TIMx_ARR;同样,捕获器、比较器、预分频器、重复计数器、触发输出特性等仍工作如常。编码器模式和外部时钟模式2不兼容,因此不能同时操作。   

         计数器时钟可由下列时钟源提供: ● 内部时钟(CK_INT) ● 外部时钟模式1:外部输入引脚 ● 外部时钟模式2:外部触发输入ETR ● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器。如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

             在这个模式下,计数器依照增量编码器的速度和方向被自动的修改,因此计数器的内容始终指示着编码器的位置。计数方向与相连的传感器旋转的方向对应。下表列出了所有可能的组合,假设TI1和TI2不同时变换。

             (这个毛刺相当于滤波掉了,在TI1和TI2上计数,对照表73即可知)

     编码器接口配置:

            函数TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//配置编码器接口,第二个参数为编码器模式,参数三四为通道的极性
    边沿检测:上升沿有效表示的是高低电平不翻转,下降沿有效表示翻转高低电平

    1. /*输入捕获初始化*/
    2. TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
    3. TIM_ICStructInit(&TIM_ICInitStructure); //结构体初始化,若结构体没有完整赋值
    4. //则最好执行此函数,给结构体所有成员都赋一个默认值
    5. //避免结构体初值不确定的问题
    6. TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
    7. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    8. TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    9. TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2
    10. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    11. TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    12. /*编码器接口配置*/
    13. TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    14. //配置编码器模式以及两个输入通道是否反相
    15. //注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
    16. //此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
    编码器接口测速

            本质:编码器计次,计次可以使用外部中断方式,方波信号来自编码器,在中断函数里面手动计次,占用软件资源,CPU会频繁进入中断,可以采用硬件自动化计次,也就是定时器的编码器接口模式。使用场景:使用PWM驱动电机,再使用编码器(无接触式的霍尔传感器或光栅)测量电机速度,再用PID算法实现闭环控制https://blog.csdn.net/NRWHF/article/details/128553847

             TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; 

            //编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR;

             TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;              

             //预分频器,即PSC的值,//PSC,不分频,编码器时钟直接驱动计数器    

    •   编码器接口的两个输入引脚借用了输入捕获的通道的CH1和CH2引脚编码器接口是从模式控制器,通路连接到CNT计数器,72MHZ的内部时钟和时基单元初始化设置的计数方向并不会使用,而是受到编码器控制,

            TIM_ICStructInit(&TIM_ICInitStructure);                         

            //结构体初始化,若结构体没有完整赋值

    1. void Encoder_Init(void)
    2. {
    3. /*开启时钟*/
    4. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
    5. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
    6. /*GPIO初始化*/
    7. GPIO_InitTypeDef GPIO_InitStructure;
    8. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    10. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    11. GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6和PA7引脚初始化为上拉输入
    12. /*时基单元初始化*/
    13. //因为编码器接口是一个带有方向控制的外部时钟,所以不需要配置时钟
    14. TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
    15. TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    16. TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    17. TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值
    18. TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值,//PSC,不分频,编码器时钟直接驱动计数器
    19. TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
    20. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
    21. /*输入捕获初始化*/
    22. TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量
    23. TIM_ICStructInit(&TIM_ICInitStructure); //结构体初始化,若结构体没有完整赋值
    24. //则最好执行此函数,给结构体所有成员都赋一个默认值
    25. //避免结构体初值不确定的问题
    26. TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1
    27. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    28. TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    29. TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; //选择配置定时器通道2
    30. TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动
    31. TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
    32. /*编码器接口配置*/
    33. TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    34. //配置编码器模式以及两个输入通道是否反相
    35. //注意此时参数的Rising和Falling已经不代表上升沿和下降沿了,而是代表是否反相
    36. //此函数必须在输入捕获初始化之后进行,否则输入捕获的配置会覆盖此函数的部分配置
    37. /*TIM使能*/
    38. TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
    39. }
    40. /**
    41. * 函 数:获取编码器的增量值
    42. * 参 数:无
    43. * 返 回 值:自上此调用此函数后,编码器的增量值
    44. */
    45. int16_t Encoder_Get(void)
    46. {
    47. /*使用Temp变量作为中继,目的是返回CNT后将其清零*/
    48. int16_t Temp;
    49. Temp = TIM_GetCounter(TIM3);
    50. TIM_SetCounter(TIM3, 0);
    51. return Temp;
    52. }

    函    数:TIM2中断函数

    1. /**
    2. * 函 数:TIM2中断函数
    3. * 参 数:无
    4. * 返 回 值:无
    5. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
    6. * 函数名为预留的指定名称,可以从启动文件复制
    7. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
    8. */
    9. void TIM2_IRQHandler(void)
    10. {
    11. if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
    12. {
    13. Speed = Encoder_Get(); //每隔固定时间段读取一次编码器计数增量值,即为速度值
    14. TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
    15. //中断标志位必须清除
    16. //否则中断将连续不断地触发,导致主程序卡死
    17. }
    18. }

    四、高级定时器

    1. TIM1和TIM8简介

            高级控制定时器(TIM1和TIM8)由一个16位的自动装载计数器组成,它由一个可编程的预分频器驱动。 它支持针对定位的增量(正交)编码器和霍尔传感器电路,它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM(死区时间可编程的互补输出)等)。 使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。

            和高级定时器最大的两处区别:

    2. 重复计数器

             关于高级定时器 重复计数值寄存器的使用介绍 - ZaiLi - 博客园 (cnblogs.com)

     3. 互补输出和死区插入

              驱动电机的时候常用,防止mos管短暂的直接导通。

            高级控制定时器(TIM1和TIM8)能够输出两路互补信号,并且能够管理输出的瞬时关断和接通。 这段时间通常被称为死区,用户应该根据连接的输出器件和它们的特性(电平转换的延时、电源开关的延时等)来调整死区时间。

            如果OCx和OCxN为高有效:

            ● OCx输出信号与参考信号相同,只是它的上升沿相对于参考信号的上升沿有一个延迟。

            ● OCxN输出信号与参考信号相反,只是它的上升沿相对于参考信号的下降沿有一个延迟。         如果延迟大于当前有效的输出宽度(OCx或者OCxN),则不会产生相应的脉冲。 下列几张图显示了死区发生器的输出信号和当前参考信号OCxREF之间的关系。

               逻辑门图解—与门、或门、非门、与非门、或非门、异或门、同或门_小小本科生debug的博客-CSDN博客

             当写入0时,与门有0则0,全1为1

    对于GD32A503x

    1. gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
    2. gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    3. gpio_af_set(GPIOE, GPIO_AF_1, GPIO_PIN_8);

     在这段代码中,GPIO 配置的目的是将 TIM7 的 CH2 输出映射到 GPIOE 的第8引脚上。具体来说,为了配置 GPIO 作为 TIM7 的 CH2 的输出引脚,需要进行以下几个步骤:

    1. 将 GPIOE 的第8引脚的模式设置为复用模式,即 AF 模式。在这种模式下,GPIOE 第8引脚将具有两种功能:GPIO 功能和复用功能。

    2.配置 GPIOE 的第8引脚为推挽输出(PP 模式)和输出速度为 50MHz。推挽输出是指输出引脚既能拉高电平,也能拉低电平。

    3.设置 GPIOE 的第8引脚的复用功能为 AF1,即 TIM7_CH2 的功能。TIM7_CH2 的功能是通过复用功能选择的,而 AF1 对应 TIM7_CH2。

    1. timer_parameter_struct timer_initparam;
    2. timer_struct_para_init(&timer_initparam);
    3. timer_initparam.alignedmode = TIMER_COUNTER_EDGE;
    4. timer_initparam.clockdivision = TIMER_CKDIV_DIV1;
    5. timer_initparam.counterdirection = TIMER_COUNTER_UP;
    6. timer_initparam.period = arr - 1;
    7. timer_initparam.prescaler = psc - 1;
    8. timer_initparam.repetitioncounter = 0U;
    9. timer_init(TIMER7,&timer_initparam);

    timer_struct_para_init 是一个函数,用于初始化一个 timer_parameter_struct 结构体,将结构体的各个成员设置为默认值。这个函数通常用于确保在开始配置定时器参数之前,结构体的每个成员都处于已知的初始状态。

            这一行代码的作用是初始化 timer_initparam 结构体,将其所有成员设置为默认值。这样可以确保在之后对结构体的具体成员进行设置之前,所有成员都处于一种已知状态。

            这种初始化的方式通常有助于代码的可读性和可维护性,避免了在开始配置之前忘记设置某个成员的初值。

    1. 这段代码是对定时器 TIMER7 进行初始化的操作。下面是对每个参数的简要解释:
    2. alignedmode: 指定计数器的对齐模式。在这里设置为 TIMER_COUNTER_EDGE 表示边缘对齐模式。这意味着计数器从零开始,当计数器达到设定的周期值时,产生更新事件。
    3. clockdivision: 指定时钟分频因子。在这里设置为 TIMER_CKDIV_DIV1 表示不分频,即时钟频率不经过分频。
    4. counterdirection: 指定计数器的计数方向。在这里设置为 TIMER_COUNTER_UP 表示计数器递增。在计数器递增的情况下,当计数器达到设定的周期值时,产生更新事件。
    5. period: 指定自动重载寄存器的值,即周期的长度。这里设置为 arr - 1,以达到设置的 PWM 频率。
    6. prescaler: 指定预分频器的分频系数。这里设置为 psc - 1,以达到设置的 PWM 频率。
    7. repetitioncounter: 用于高级定时器,这里设置为 0U 表示不使用重复计数器。
    8. 最后,timer_init(TIMER7,&timer_initparam) 调用实际上对定时器进行了初始化,将上述参数应用到 TIMER7 上,以便正确配置定时器的工作模式。

    所以这样计算出的频率就是200hz

    配置输出极性:

    1. timer_oc_parameter_struct timer_ocintpara;
    2. timer_channel_output_struct_para_init(&timer_ocintpara);
    3. timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    4. timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
    5. timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
    6. timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    7. timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    8. timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    1. outputstate: 该成员配置通道的输出状态。在这里设置为 TIMER_CCX_ENABLE,表示使能通道的输出。
    2. outputnstate: 该成员配置通道的互补输出状态。在这里设置为 TIMER_CCXN_DISABLE,表示禁用互补输出。
    3. ocpolarity: 该成员配置通道的输出极性。在这里设置为 TIMER_OC_POLARITY_HIGH,表示通道输出的高电平。
    4. ocnpolarity: 该成员配置互补通道的输出极性。在这里设置为 TIMER_OCN_POLARITY_HIGH,表示互补通道输出的高电平。
    5. ocidlestate: 该成员配置通道在空闲状态下的输出电平。在这里设置为 TIMER_OC_IDLE_STATE_LOW,表示通道在空闲状态下输出低电平。
    6. ocnidlestate: 该成员配置互补通道在空闲状态下的输出电平。在这里设置为 TIMER_OCN_IDLE_STATE_LOW,表示互补通道在空闲状态下输出低电平。

     至于这一段代码:

    1. timer_channel_output_config(TIMER7, TIMER_CH_2, &timer_ocintpara);
    2. timer_channel_output_pulse_value_config(TIMER7, TIMER_CH_2, 0); //一直输出低电平
    3. timer_channel_output_mode_config(TIMER7, TIMER_CH_2, TIMER_OC_MODE_PWM0);
    4. timer_channel_output_shadow_config(TIMER7, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE);
    1. timer_channel_output_config: 该函数配置定时器通道输出的一般特性,例如输出使能、输出极性等。在这里,配置 TIMER7 的 TIMER_CH_2 通道,使用 timer_ocintpara 结构体中的参数进行配置。
    2. timer_channel_output_pulse_value_config: 该函数用于设置 PWM 的脉冲值。在这里,设置为 0,表示始终输出低电平。这意味着在 PWM 周期内的大部分时间内输出低电平,只在周期的某一部分输出高电平。
    3. timer_channel_output_mode_config: 该函数配置 PWM 输出的模式。在这里,设置为 TIMER_OC_MODE_PWM0,表示 PWM 模式。PWM 模式中,通道输出在计数器值小于脉冲值时为高电平,大于脉冲值时为低电平。
    4. timer_channel_output_shadow_config: 该函数配置是否启用比较寄存器的影子寄存器。在这里,设置为 TIMER_OC_SHADOW_DISABLE,表示不使用影子寄存器。
    5. 这些配置组合在一起,实现了将 TIMER7 的通道2配置为 PWM 输出,并且该 PWM 输出在周期内的大部分时间为低电平,只在一个小的时间段内为高电平,从而形成了 PWM 波形。
    1. /* TIMER7 primary output function enable 高级定时器(0和7 19)特有 所有的通道输出使能*/
    2. timer_primary_output_config(TIMER7,ENABLE);
    3. /* auto-reload preload enable */
    4. timer_auto_reload_shadow_enable(TIMER7);
    5. /* auto-reload preload enable */
    6. timer_enable(TIMER7);
    1. timer_primary_output_config: 该函数用于配置高级定时器(TIM0、TIM7和TIM19)的主输出功能。
    2. 在这里,通过 timer_primary_output_config(TIMER7, ENABLE) 启用了 TIMER7 的主输出功能。
    3. 对于高级定时器,主输出功能使能后,所有通道的输出都受到主输出控制。
    4. timer_auto_reload_shadow_enable: 该函数用于使能自动重载寄存器的影子传输功能。
    5. 在这里,通过 timer_auto_reload_shadow_enable(TIMER7) 启用了 TIMER7 的自动重载寄存器的影子传输功能。
    6. 这意味着在更新事件发生时,新的自动重载值会从影子寄存器传输到自动重载寄存器。
    7. timer_enable: 该函数用于使能定时器。
    8. 在这里,通过 timer_enable(TIMER7) 启用了 TIMER7。
    9. 启用定时器后,定时器开始工作,根据配置的参数生成相应的定时事件。
    10. 这些配置和使能操作,结合前面的 PWM 配置,最终将 TIMER7 配置为一个带有 PWM 输出的定时器。
    1. void Tim7_pwm_init(uint16_t psc, uint32_t arr)
    2. {
    3. /*configure PE8(TIMER7 CH2) as alternate function*/
    4. gpio_mode_set(GPIOE, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_8);
    5. gpio_output_options_set(GPIOE, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
    6. gpio_af_set(GPIOE, GPIO_AF_1, GPIO_PIN_8);
    7. /************TIMER base**************/
    8. Tim0_ARR = arr;
    9. rcu_periph_clock_enable(RCU_TIMER7);
    10. timer_deinit(TIMER7);
    11. timer_parameter_struct timer_initparam;
    12. timer_struct_para_init(&timer_initparam);
    13. timer_initparam.alignedmode = TIMER_COUNTER_EDGE;
    14. timer_initparam.clockdivision = TIMER_CKDIV_DIV1;
    15. timer_initparam.counterdirection = TIMER_COUNTER_UP;
    16. timer_initparam.period = arr - 1;
    17. timer_initparam.prescaler = psc - 1;
    18. timer_initparam.repetitioncounter = 0U;
    19. timer_init(TIMER7,&timer_initparam);
    20. /**************OC********************/
    21. timer_oc_parameter_struct timer_ocintpara;
    22. timer_channel_output_struct_para_init(&timer_ocintpara);
    23. timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
    24. timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
    25. timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
    26. timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
    27. timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
    28. timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    29. timer_channel_output_config(TIMER7, TIMER_CH_2, &timer_ocintpara);
    30. timer_channel_output_pulse_value_config(TIMER7, TIMER_CH_2, 0); //一直输出低电平
    31. timer_channel_output_mode_config(TIMER7, TIMER_CH_2, TIMER_OC_MODE_PWM0);
    32. timer_channel_output_shadow_config(TIMER7, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE);
    33. /* TIMER7 primary output function enable 高级定时器(0和7 19)特有 所有的通道输出使能*/
    34. timer_primary_output_config(TIMER7,ENABLE);
    35. /* auto-reload preload enable */
    36. timer_auto_reload_shadow_enable(TIMER7);
    37. /* auto-reload preload enable */
    38. timer_enable(TIMER7);
    39. }

    位操作

    【精选】【STM32】位操作、按位与、按位或、按位异或、取反、左移、右移等基础 C 语言知识补充_按位与操作是什么意思-CSDN博客

    运算符含义
    &按位与
    按位或
    ^按位异或
    ~取反
    <<左移
    >>右移

            总结:对于原二进制数来说,&0是屏蔽,&1是不变。0&x = 0,1&x = x;0与任何数等于0,1与任何数等于本身,不改变。进行清0操作

            举个例子:(usart.c里面的)

    1. void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
    2. {
    3. /* Check the parameters */
    4. assert_param(IS_USART_ALL_PERIPH(USARTx));
    5. assert_param(IS_USART_DATA(Data));
    6. /* Transmit Data */
    7. USARTx->DR = (Data & (uint16_t)0x01FF);
    8. }

            对于usart_DR寄存器,0-8位有效, USARTx->DR = (Data & (uint16_t)0x01FF);可以将无关的高位清0。剩下的都是1 1111 1111,与上任何数都等于任何数本身。

            总结:对于原二进制数来说,|0是不变,|1是置1。0|x = x,1|x = 1;0或任何数等于本身,1或任何数等于1。

    总结:对于原二进制数来说,^0是不变,^1是反转。即:0 ^ 0 = 0 ,0 ^ 1 = 1,1 ^ 0 = 1 ,1 ^ 1 = 0 ,
    按位异或的3个特点:1.) 0 ^ 0 = 0 , 0 ^ 1 = 1, 0异或任何数=任何数。2.)1 ^ 0 = 1 , 1 ^ 1 = 0 , 1异或任何数=任何数取反。

           而对于取反需要注意:对一个二进制数进行取反。1变0,0变1。

            唯一需要注意的一点是,~的优先级是逻辑运算符中最高的,必须优先计算。

            不改变其他位时,对某几个位设定值。比如要改变 GPIOA 的状态,可以先对寄存器的值进行 & 清零操作GPIOA -> CRL &= 0XFFFFFF0F;  将第 4-7 位清 0,然后再与需要设置的值进行 | 或运算GPIOA -> CRL |= 0X00000040;设置相应位的值,且不改变其他位的值。


            左移与右移    

            对于无符号数,左移时右侧补0(相当于逻辑移位)
            对于无符号数,右移时左侧补0(相当于逻辑移位)

            左移规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0。

            简单说就是:左边丢弃,右边补0

            https://blog.csdn.net/m0_64280701/article/details/123064976

            右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1 。简单说就是:(分为 2 种)1. 逻辑右移,左边用0填充,右边丢弃。2. 算术右移左边用原该值的符号位填充,右边丢弃。到底是逻辑右移还是算术右移取决于编译器,我当前使用的编译器,它采用的是算术右移。

       32单片机的位操作知识_单片机位操作-CSDN博客   

            位运算实战演练1
            回顾:要置1用|,用清零用&,要取反用^,~和<< >>用来构建特定二进制数。
            1.给定一个整型数a,设置a的bit3,保证其他位不变。
                    a = a | (1<<3)或者 a |= (1<<3)

            2.给定一个整形数a,设置a的bit3~bit7,保持其他位不变。
                    a = a | (0b11111<<3)或者 a |= (0x1f<<3);

            3.给定一个整型数a,清除a的bit15,保证其他位不变。
                    a = a & (~(1<<15));或者 a &= (~(1<<15));

            4.给定一个整形数a,清除a的bit15~bit23,保持其他位不变。
                    a = a & (~(0x1ff<<15));或者 a &= (~(0x1ff<<15));

            5.给定一个整形数a,取出a的bit3~bit8。
            思路:
                    第一步:先将这个数bit3~bit8不变,其余位全部清零。
                    第二步,再将其右移3位得到结果。
                    第三步,想明白了上面的2步算法,再将其转为C语言实现即可。
                    a &= (0x3f<<3);
                    a >>= 3;

            6.用C语言给一个寄存器的bit7~bit17赋值937(其余位不受影响)。
            关键点:第一,不能影响其他位;第二,你并不知道原来bit7~bit17中装的值。
    思路:

            第一步,先将bit7~bit17全部清零,当然不能影响其他位。

            第二步,再将937写入bit7~bit17即可,当然不能影响其他位。

            a &= ~(0x7ff<<7);

            a |= (937<<7);

  • 相关阅读:
    【MyBatis】MyBatis 理论 40 问(二)
    mysql两阶段提交
    ab4d:ViewerSvg - svg to xaml converter
    9.20华为机试-后端
    Spring中什么样的Bean存在线程安全问题-有状态bean
    java springboot儿童影楼管理系统
    详解AST抽象语法树
    盘点检索任务中的损失函数
    游戏设计与刚体力学
    华纳云:Ubuntu下开启php调试模式报错如何解决
  • 原文地址:https://blog.csdn.net/weixin_57604904/article/details/134258794