• STM32-通用定时器


    目录

    通用定时器

    功能框图

    输入捕获

    测量频率        

    测量脉宽

    PWM输入模式

    输出比较

    PWM边沿对齐模式

    PWM中心对齐模式

    初始化结构体

    TIM_TimeBaseInitTypeDef:时基结构体

    TIM_OC_InitTypeDef:输出比较结构体

    TIM_IC_InitTypeDef:输入捕获结构体

    通用定时器实验1:输入捕获脉冲宽度

    常规配置

    TIM5配置

    测试环节

    实验现象

    通用定时器实验2:输出比较PWM呼吸灯

    TIM5配置

    测试环节

    实验现象

    通用定时器实验3:输入捕获电容按键

    常规配置

    TIM5配置

    测试环节

    实验现象


    通用定时器

    通用定时器由一个可编程预分频器驱动的16位自动重新加载计数器组成。应用测量输入的脉冲长度信号(输入捕获)产生输出波形(输出比较和PWM)

    脉冲长度和波形周期可以从几微秒调制到几毫秒,使用定时器预分频器和RCC时钟控制器预分频器。

    输入捕获应用场景:脉冲跳变沿时间测量PWM输入测量

    输出比较应用场景:PWM8种模式(常用PWM边沿对齐模式PWM中心对齐模式

    功能框图

    16位向上、向下、向上/向下自动重装载计数器。

    16位可编程预分频器,1~65536。

    多达4个独立通道,用于:

            输入捕获

            输出比较

            PWM产生(边沿对齐模式和中心对齐模式)

            单脉冲模式输出

    同步电路控制定时器和外部信号,并互连多个定时器。

    中断/DMA生成以下事件:

            更新:计数器溢出/下溢,计数器初始化(由软件或内部/外部触发)

            触发事件(计数器启动、停止、初始化或由内部/外部触发计数)

            输入捕获

            输出比较

    支持增量(正交)编码器和霍尔传感器电路的定位目的。

    触发输入外部时钟或逐周期电流管理。

    时基单元包括:

    计数器寄存器(TIMx_CNT)

    预分频寄存器(TIMx_PSC)

    自动重装载寄存器(TIMx_ARR)

    计数模式:

    向上计数

    向下计数

    中心对齐:计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件。然后继续从0开始重新计数,以此循环。

    TIx为输入通道,需要被测量的信号从定时器的外部引脚TIMx_CH1/2/3/4进入。

    当输入的信号存在高频干扰时,需要对输入信号进行滤波,根据采样定律(采样频率必须大于或等于两倍的输入信号),比如输入信号为1M,存在高频信号干扰时就要进行滤波,可以设置采样频率为2M,这样可以在保证采样到有效信号的基础上把高于2M的高频干扰信号过滤掉。

    输入滤波器的配置由TIMx_CR1:CKD[1:0]和TIMx_CCMR1/2:ICxF[3:0]控制。根据ICxF位的描述,采样频率Fsample可以由Fck_int(内部时钟)和Fdts(Fck_int经过分频后的频率,分频因子由CKD[1:0]决定,1/2/4分频)分频后的时钟提供。

    边沿检测器用来设置信号在捕获时什么边沿有效(上升沿、下降沿、双边沿),具体由TIMx_CCER:CCxP、CCxNP决定。

    捕获通道:IC1/2/3/4。每个捕获通道都有对应的捕获寄存器CCR1/2/3/4,当发生捕获时,计数器CNT的值就会被锁存到捕获寄存器中。

    输入通道TIx是用来输入信号的,捕获通道ICx是用来捕获输入信号的通道。一个输入通道的信号可以同时输入给两个捕获通道。比如TI1的信号经过滤波和边沿检测器后TI1FP1和TI1FP2可以进入到捕获通道IC1和IC2。输入通道和捕获通道的映射关系具体由TIMx_CCMR:CCxS[1:0]配置。

    ICx的输出信号会经过预分频器,用于决定发生多少个事件时进行一次捕获。具体由TIMx_CCMR:ICxPSC配置。如果希望捕获信号的每一个边沿,则不分频。

    经过预分频器的信号ICxPS是最终被捕获的信号,当发生捕获时(第一次),计数器CNT的值会被锁存到捕获寄存器TIMx_CCR中,还会产生CCxI中断,相应的中断位CCxIF(在SR寄存器中)会被置位,通过软件或读取CCR的值可以将CCxIF清0。如果发生第二次捕获(即重复捕获,CCR寄存器中已捕获到计数器值且CCxIF标志已置1),则捕获溢出标志位CCxOF(在SR寄存器中)会被置位,CCx_OF只能通过软件清零。

    输入捕获

    测量频率        

    当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。

    当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,并再次进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。利用value2-value1的差值就可以算出信号的周期(频率)。

    测量脉宽

    当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器的值读取到value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。

    当下降沿到来时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器TIM_CCRx中,而且还会进入捕获中断,在中断服务程序中记录一次捕获,并把捕获寄存器的值读取到value2中,清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。

    在测量脉宽过程中需要来回切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出时会产生更新中断,我们可以在中断里对溢出进行记录处理。

    PWM输入模式

    测量脉宽和频率还有一个更简便的方法:PWM输入模式,该模式是输入捕获的特例,只能使用通道1和2,通道3和4不能使用。测量脉宽和频率的方法只使用一个捕获寄存器,PWM输入模式需要占用两个捕获寄存器

    当使用PWM输入模式时,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入模式时最多只能使用两个输入通道(TIx)。

    以输入通道TI1工作在PWM输入模式为例:

    PWM信号由输入通道TI1进入,因为是PWM输入模式,所以信号被分为两路(TI1FP1、TI1FP2)。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入(作为触发输入的那一路信号对应的是周期,另一路信号对应的是占空比)。作为触发输入的那一路信号还需要设置极性(上升沿捕获还是下降沿捕获),一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。

    概况:选定输入通道,确定触发信号,然后设置触发性的极性(因为是PWM输入模式,所以另一路由硬件配置,无需软件配置)

    当使用PWM输入模式时必须将从模式控制器配置为复位模式(配置寄存器TIMx_SMCR:SMS[2:0]实现),即当我们启动触发信号开始进行捕获时,同时把计数器CNT复位清零。

    上图为例,PWM信号由输入通道TI1进入,配置TI1FP1为触发信号,上升沿捕获。

    当上升沿到来时IC1和IC2同时捕获,计数器CNT清零;

    到了下降沿时IC2捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR2中;

    到了下一个上升沿到来时IC1捕获,计数器CNT的值被锁存在捕获寄存器TIMx_CCR1中。

    其中TIMx_CCR2 + 1测量的是脉宽,TIMx_CCR1 + 1测量的是周期。(注意:TIMx_CCR1和TIMx_CCR2都必须加1,因为计数器是从0开始计数的)

    从软件上看,PWM输入模式测量脉宽和周期更容易,缺点是需要占用两个捕获寄存器。

    输出比较

    输出比较模式共8种,具体由TIMx_CCMR1/2:OCxM[2:0]配置。

    PWM输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装载寄存器ARR的值决定,占空比由比较寄存器CCR的值决定。

    PWM模式分两种:PWM1、PWM2。

    以PWM1模式为例。以计数器CNT计数方向不同还分为边沿对齐模式和中心对齐模式。PWM信号主要都是用来控制电机,一般电机控制用的都是边沿对齐模式,FOC电机一般用的是中心对齐模式。

    PWM边沿对齐模式

    在递增计数模式下,计数器从0计数到自动重装载值(TIMx_ARR寄存器的内容),然后重新从0开始计数并生成计数器上溢事件。

    在边沿对齐模式下,计数器CNT只工作在一种模式:递增或递减模式。

    以CNT工作在递增模式为例,ARR=8,CCR=4,CNT从0开始计数。

    当CNT < CCR的值时,OCXREF为有效的高电平,此时比较中断寄存器CCxIF置位。

    当CCR <= CNT <= ARR时,OCXREF为无效的低电平。

    然后CNT又从0开始计数并生成计数器上溢事件,以此循环往复。

    PWM中心对齐模式

    中心对齐模式下,计数器CNT是工作在递增/递减模式下。

    开始时,计数器CNT从0开始计数到自动重装载值减1(ARR-1),生成计数器上溢事件;

    然后从自动重装载值开始向下计数到1,生成计数器下溢事件。然后从0开始重新计数,以此往复。

    以PWM1模式的中心对齐模式为例,ARR=8,CCR=4。

    第一阶段计数器CNT工作在递增模式下从0开始计数。

            当CNT < CCR的值时,OCXREF为有效的高电平。

            当CCR <= CNT <= ARR时,OCXREF为无效的低电平。

    第二阶段计数器CNT工作在递减模式下从ARR的值开始计数。

            当CNT > CCR的值时,OCXREF为无效的低电平。

            当1 <= CNT <= CRR时,OCXREF为有效的高电平。

    在波形图中可以把波形分为两个阶段,

    第一个阶段是计数器CNT工作在递增模式的波形,这个阶段又分为1和2两个阶段。

    第一个阶段是计数器CNT工作在递减模式的波形,这个阶段又分为3和4两个阶段。

    中心对齐模式下的波形特征是1和3的时间相等,2和4的时间相等。

    中心对齐模式又分为中心对齐1/2/3三种,具体由寄存器TIMx_CR1:CMS[1:0]配置。具体的区别是比较中断标志位CCxIF在何时置1。

    中心模式1在CNT递减计数时置1,中心模式2在CNT递增计数时置1,中心模式3在CNT递增和递减计数时都置1

    初始化结构体

    TIM_TimeBaseInitTypeDef:时基结构体

    1. typedef struct
    2. {
    3.     uint16_t TIM_Prescaler;           // 预分频器,基本定时器配置需要
    4.     uint16_t TIM_CounterMode;         // 计数模式,通用定时器配置需要
    5.     uint16_t TIM_Period;              // 定时周期,基本定时器配置需要
    6.     uint16_t TIM_ClockDivision;     // 时钟分频,通用定时器配置需要
    7.     uint8_t TIM_RepetitionCounter;   // 重复计数器
    8. } TIM_TimeBaseInitTypeDef;   

    对于TIM_CounterMode,有。

    1. #define TIM_CounterMode_Up ((uint16_t)0x0000) //边沿对齐模式,向上计数
    2. #define TIM_CounterMode_Down ((uint16_t)0x0010) //边沿对齐模式,向下计数
    3. #define TIM_CounterMode_CenterAligned1 ((uint16_t)0x0020) //中央对齐模式1,计数器交替向上向下计数。
    4. //产生下溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向下计数时被设置。
    5. #define TIM_CounterMode_CenterAligned2 ((uint16_t)0x0040) //中央对齐模式2,计数器交替向上向下计数。
    6. //产生上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,只在计数器向上计数时被设置。
    7. #define TIM_CounterMode_CenterAligned3 ((uint16_t)0x0060) //中央对齐模式3,计数器交替向上向下计数。
    8. //产生下溢和上溢中断,配置为输出的通道(TIMx_CCMRx寄存器中CCxS=00)的输出比较中断标志位,在计数器向上和向下计数时均被设置。

    对于TIM_ClockDivision,为时钟分频,设置定时器时钟CK_INT频率与死区发生器以及数字滤波器采样时钟频率分频比,可选择1/2/4分频。

    TIM_OC_InitTypeDef:输出比较结构体

    与TIM_OCxInit函数配合使用完成指定定时器输出通道初始化配置。

    1. typedef struct {
    2. uint32_t OCMode; // 比较输出模式,8种,常用PWM1或PWM2
    3. uint32_t Pulse; // 比较输出脉冲宽度。0~65535
    4. uint32_t OCPolarity; // 比较输出极性。决定着定时器通道有效电平
    5. uint32_t OCNPolarity; // 互补输出极性
    6. uint32_t OCFastMode; // 比较输出模式快速使能
    7. uint32_t OCIdleState; // 空闲状态下比较输出状态
    8. uint32_t OCNIdleState; // 空闲状态下比较互补输出状态
    9. } TIM_OCInitTypeDef;

    TIM_IC_InitTypeDef:输入捕获结构体

    与HAL_TIM_IC_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。

    如果使用PWM输入模式需要与HAL_TIM_PWM_ConfigChannel函数配合使用完成指定定时器输入通道初始化配置。

    1. typedef struct {
    2. uint32_t ICPolarity; // 输入捕获通道的极性选择,上升沿/下降沿/边沿跳变触发捕获事件
    3. uint32_t ICSelection; // 输入捕获通道的输入源,捕获通道ICx的信号可来自三个输入通道,分别为TIM_ICSELECTION_DIRECTTI、TIM_ICSELECTION_INDIRECTTI或TIM_ICSELECTION_TRC
    4. uint32_t ICPrescaler; // 输入捕获预分频器,1/2/4/8,如果需要捕获输入信号的每个有效边沿,则设置1分频即可
    5. uint32_t ICFilter; // 输入捕获滤波器,0x0~0xf。一般不使用滤波器,即设置为0
    6. } TIM_IC_InitTypeDef;

    ICSelection为输入捕获通道的输入源。

            当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC1、IC2、IC3、IC4。

            当为TIM_ICSELECTION_DIRECTTI时,TI1、TI2、TI3、TI4对应IC2、IC1、IC4、IC3。

            当为TIM_ICSELECTION_TRC时,TI1、TI2、TI3、TI4对应TRC。

    通用定时器实验1:输入捕获脉冲宽度

    常规配置

    USART:115200-8-N-1,支持重定向输出printf函数,勾选使用C库。

    TIM5配置

    硬件原理图得知,PA0是KEY1的按键IO,也是TIM5_CH1的IO。

    1. TIM_HandleTypeDef htim5;
    2. void MX_TIM5_Init(void)
    3. {
    4. TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    5. TIM_MasterConfigTypeDef sMasterConfig = {0};
    6. TIM_IC_InitTypeDef sConfigIC = {0};
    7. htim5.Instance = TIM5;
    8. htim5.Init.Prescaler = 71;
    9. htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
    10. htim5.Init.Period = 65535;
    11. htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    12. htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    13. if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
    14. {
    15. Error_Handler();
    16. }
    17. sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    18. if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
    19. {
    20. Error_Handler();
    21. }
    22. if (HAL_TIM_IC_Init(&htim5) != HAL_OK)
    23. {
    24. Error_Handler();
    25. }
    26. sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    27. sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    28. if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
    29. {
    30. Error_Handler();
    31. }
    32. sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    33. sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    34. sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    35. sConfigIC.ICFilter = 0;
    36. if (HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
    37. {
    38. Error_Handler();
    39. }
    40. }
    41. void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
    42. {
    43. GPIO_InitTypeDef GPIO_InitStruct = {0};
    44. if (tim_baseHandle->Instance == TIM5)
    45. {
    46. __HAL_RCC_TIM5_CLK_ENABLE();
    47. __HAL_RCC_GPIOA_CLK_ENABLE();
    48. /**TIM5 GPIO Configuration
    49. PA0-WKUP ------> TIM5_CH1
    50. */
    51. GPIO_InitStruct.Pin = GPIO_PIN_0;
    52. GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    53. GPIO_InitStruct.Pull = GPIO_NOPULL;
    54. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    55. HAL_NVIC_SetPriority(TIM5_IRQn, 0, 0);
    56. HAL_NVIC_EnableIRQ(TIM5_IRQn);
    57. }
    58. }
    59. void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
    60. {
    61. if (tim_baseHandle->Instance == TIM5)
    62. {
    63. __HAL_RCC_TIM5_CLK_DISABLE();
    64. /**TIM5 GPIO Configuration
    65. PA0-WKUP ------> TIM5_CH1
    66. */
    67. HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);
    68. /* TIM5 interrupt Deinit */
    69. HAL_NVIC_DisableIRQ(TIM5_IRQn);
    70. }
    71. }

    测试环节

    1. typedef struct
    2. {
    3. uint8_t ucFinishFlag; // 捕获结束标志位
    4. uint8_t ucStartFlag; // 捕获开始标志位
    5. uint16_t usCtr; // 捕获寄存器的值
    6. uint16_t usPeriod; // 自动重装载寄存器更新标志
    7. }STRUCT_CAPTURE; // 定时器输入捕获用户自定义变量结构体声明
    8. STRUCT_CAPTURE TIM_ICUserValueStructure = {0, 0, 0, 0};
    9. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    10. {
    11. TIM_ICUserValueStructure.usPeriod++;
    12. }
    13. void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    14. {
    15. TIM_IC_InitTypeDef IC_Config;
    16. if (TIM_ICUserValueStructure.ucStartFlag == 0) // 上升沿捕获,开始捕获
    17. {
    18. __HAL_TIM_SET_COUNTER(htim, 0); // 清零定时器计数
    19. TIM_ICUserValueStructure.usPeriod = 0;
    20. TIM_ICUserValueStructure.usCtr = 0;
    21. // 配置输入捕获参数,主要是修改触发电平
    22. IC_Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
    23. IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
    24. IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
    25. IC_Config.ICFilter = 0;
    26. HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);
    27. // 清除TIM捕获/比较1中断标志位
    28. __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
    29. // 启动输入捕获并开启中断
    30. HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
    31. TIM_ICUserValueStructure.ucStartFlag = 1;
    32. }
    33. else // 下降沿捕获,结束捕获
    34. {
    35. // 获取定时器计数值
    36. TIM_ICUserValueStructure.usCtr = HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_1);
    37. // 配置输入捕获参数,主要是修改触发电平
    38. IC_Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    39. IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI;
    40. IC_Config.ICPrescaler = TIM_ICPSC_DIV1;
    41. IC_Config.ICFilter = 0;
    42. HAL_TIM_IC_ConfigChannel(&htim5, &IC_Config, TIM_CHANNEL_1);
    43. // 清除中断标志位
    44. __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
    45. // 启动输入捕获并开启中断
    46. HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
    47. TIM_ICUserValueStructure.ucStartFlag = 0;
    48. TIM_ICUserValueStructure.ucFinishFlag = 1;
    49. }
    50. }
    51. void test(void)
    52. {
    53. uint32_t time, TIM_PscCLK;
    54. 初始化
    55. // TIM 计数器的驱动时钟
    56. TIM_PscCLK = HAL_RCC_GetHCLKFreq() / 71;
    57. /* 启动定时器 */
    58. HAL_TIM_Base_Start_IT(&htim5);
    59. /* 启动定时器通道输入捕获并开启中断 */
    60. HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
    61. while (1)
    62. {
    63. /* 完成测量高电平脉宽 */
    64. if (TIM_ICUserValueStructure.ucFinishFlag == 1)
    65. {
    66. /* 计算高电平计数值 */
    67. time = TIM_ICUserValueStructure.usPeriod * 65535 + TIM_ICUserValueStructure.usCtr;
    68. /* 打印高电平脉宽时间 */
    69. printf("测得高电平脉宽时间:%d.%d s\n", time / TIM_PscCLK, time % TIM_PscCLK);
    70. TIM_ICUserValueStructure.ucFinishFlag = 0;
    71. }
    72. }
    73. }

    实验现象

    通用定时器实验2:输出比较PWM呼吸灯

    TIM5配置

    硬件原理图得知,PB0是LED_G的IO,也是TIM3_CH3的IO。

    测试环节

    1. /* LED亮度等级(PWM表,指数曲线),此表使用工程目录下的python脚本index_wave.py生成 */
    2. uint16_t indexWave[] =
    3. {
    4. 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 11, 13,
    5. 15, 17, 19, 22, 25, 28, 32, 36, 41, 47, 53, 61, 69, 79, 89, 102,
    6. 116, 131, 149, 170, 193, 219, 250, 284, 323, 367, 417, 474, 539,
    7. 613, 697, 792, 901, 1024, 1024, 901, 792, 697, 613, 539, 474, 417,
    8. 367, 323, 284, 250, 219, 193, 170, 149, 131, 116, 102, 89, 79, 69,
    9. 61, 53, 47, 41, 36, 32, 28, 25, 22, 19, 17, 15, 13, 11, 10, 9, 8,
    10. 7, 6, 5, 5, 4, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1
    11. };
    12. //计算PWM表有多少个元素
    13. uint16_t POINT_NUM = sizeof(indexWave) / sizeof(indexWave[0]);
    14. // 定时器捕获回调函数
    15. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    16. {
    17. static uint8_t pwm_index = 1; /* 用于PWM查表 */
    18. static uint8_t period_cnt = 0; /* 用于计算周期数 */
    19. period_cnt++;
    20. /* 若输出的周期数大于30,输出下一种脉冲宽的PWM波 */
    21. if (period_cnt >= 30)
    22. {
    23. /* 根据PWM表修改定时器的比较寄存器值 */
    24. __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, indexWave[pwm_index]);
    25. /* 标志PWM表的下一个元素 */
    26. pwm_index++;
    27. /* 若PWM脉冲表已经输出完成一遍,重置PWM查表标志 */
    28. if (pwm_index >= POINT_NUM)
    29. {
    30. pwm_index = 0;
    31. }
    32. /* 重置周期计数标志 */
    33. period_cnt = 0;
    34. }
    35. }
    36. void test(void)
    37. {
    38. 初始化
    39. /* 启动通道PWM输出 */
    40. HAL_TIM_Base_Start_IT(&htim3);
    41. HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
    42. while(1){}
    43. }

    实验现象

    绿色LED有规律的亮灭,经典呼吸灯。

    通用定时器实验3:输入捕获电容按键

    常规配置

    USART1:115200-8-N-1,支持重定向输出printf函数,勾选使用C库。

    蜂鸣器:PA8,高电平响。

    TIM5配置

    硬件原理图得知,PA1是电容按键IO,也是TIM5_CH2的IO。

    1. TIM_HandleTypeDef htim5;
    2. void MX_TIM5_Init(void)
    3. {
    4. TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    5. TIM_MasterConfigTypeDef sMasterConfig = {0};
    6. TIM_IC_InitTypeDef sConfigIC = {0};
    7. htim5.Instance = TIM5;
    8. htim5.Init.Prescaler = 47;
    9. htim5.Init.CounterMode = TIM_COUNTERMODE_UP;
    10. htim5.Init.Period = 0xffff;
    11. htim5.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    12. htim5.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    13. if (HAL_TIM_Base_Init(&htim5) != HAL_OK)
    14. {
    15. Error_Handler();
    16. }
    17. sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    18. if (HAL_TIM_ConfigClockSource(&htim5, &sClockSourceConfig) != HAL_OK)
    19. {
    20. Error_Handler();
    21. }
    22. if (HAL_TIM_IC_Init(&htim5) != HAL_OK)
    23. {
    24. Error_Handler();
    25. }
    26. sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    27. sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    28. if (HAL_TIMEx_MasterConfigSynchronization(&htim5, &sMasterConfig) != HAL_OK)
    29. {
    30. Error_Handler();
    31. }
    32. sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    33. sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    34. sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    35. sConfigIC.ICFilter = 3;
    36. if (HAL_TIM_IC_ConfigChannel(&htim5, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
    37. {
    38. Error_Handler();
    39. }
    40. }
    41. void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *tim_baseHandle)
    42. {
    43. GPIO_InitTypeDef GPIO_InitStruct = {0};
    44. if (tim_baseHandle->Instance == TIM5)
    45. {
    46. __HAL_RCC_TIM5_CLK_ENABLE();
    47. __HAL_RCC_GPIOA_CLK_ENABLE();
    48. /**TIM5 GPIO Configuration
    49. PA1 ------> TIM5_CH2
    50. */
    51. GPIO_InitStruct.Pin = GPIO_PIN_1;
    52. GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    53. GPIO_InitStruct.Pull = GPIO_NOPULL;
    54. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    55. }
    56. }
    57. void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *tim_baseHandle)
    58. {
    59. if (tim_baseHandle->Instance == TIM5)
    60. {
    61. __HAL_RCC_TIM5_CLK_DISABLE();
    62. /**TIM5 GPIO Configuration
    63. PA1 ------> TIM5_CH2
    64. */
    65. HAL_GPIO_DeInit(GPIOA, GPIO_PIN_1);
    66. }
    67. }

    测试环节

    1. //保存没按下时定时器计数值
    2. __IO uint16_t tpad_default_val = 0;
    3. /****************************************************
    4. * 得到定时器捕获值
    5. * 如果超时,则直接返回定时器的计数值.
    6. *****************************************************/
    7. static uint16_t TPAD_Get_Val(void)
    8. {
    9. GPIO_InitTypeDef GPIO_InitStruct;
    10. /* 设置引脚输出为低电平 */
    11. HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
    12. /* 设定电容按键对应引脚IO编号 */
    13. GPIO_InitStruct.Pin = GPIO_PIN_1;
    14. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    15. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    16. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    17. HAL_Delay(5);
    18. __HAL_TIM_SET_COUNTER(&htim5, 0); // 清零定时器计数
    19. __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE | TIM_FLAG_CC2); // 清除中断标志
    20. GPIO_InitStruct.Pin = GPIO_PIN_1;
    21. GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    22. GPIO_InitStruct.Pull = GPIO_NOPULL;
    23. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    24. HAL_TIM_IC_Start(&htim5, TIM_CHANNEL_2);
    25. while (__HAL_TIM_GET_FLAG(&htim5, TIM_FLAG_CC2) == RESET)
    26. {
    27. uint16_t count;
    28. count = __HAL_TIM_GET_COUNTER(&htim5);
    29. if (count > (0xFFFF - 500))
    30. {
    31. return count; //超时了,直接返回CNT的值
    32. }
    33. }
    34. return HAL_TIM_ReadCapturedValue(&htim5, TIM_CHANNEL_2);
    35. }
    36. /****************************************************
    37. *
    38. * 读取n次,取最大值
    39. * n:连续获取的次数
    40. * 返回值:n次读数里面读到的最大读数值
    41. *
    42. *****************************************************/
    43. static uint16_t TPAD_Get_MaxVal(uint8_t n)
    44. {
    45. uint16_t temp = 0;
    46. uint16_t res = 0;
    47. while (n--)
    48. {
    49. temp = TPAD_Get_Val(); //得到一次值
    50. if (temp > res)
    51. {
    52. res = temp;
    53. }
    54. }
    55. return res;
    56. }
    57. /********************************************************
    58. *
    59. * 初始化触摸按键
    60. * 获得空载的时候触摸按键的取值.
    61. * 返回值:0,初始化成功;1,初始化失败
    62. *
    63. *********************************************************/
    64. uint8_t TPAD_Init(void)
    65. {
    66. uint16_t buf[10];
    67. uint32_t temp = 0;
    68. uint8_t i, j;
    69. /* 连续读取10次 */
    70. for (i = 0; i < 10; i++)
    71. {
    72. buf[i] = TPAD_Get_Val();
    73. HAL_Delay(10);
    74. }
    75. /* 排序 */
    76. for (i = 0; i < 9; i++)
    77. {
    78. for (j = i + 1; j < 10; j++)
    79. {
    80. /* 升序排列 */
    81. if (buf[i] > buf[j])
    82. {
    83. temp = buf[i];
    84. buf[i] = buf[j];
    85. buf[j] = temp;
    86. }
    87. }
    88. }
    89. temp = 0;
    90. /* 取中间的6个数据进行平均 */
    91. for (i = 2; i < 8; i++)
    92. {
    93. temp += buf[i];
    94. }
    95. tpad_default_val = temp / 6;
    96. printf("tpad_default_val:%d\r\n", tpad_default_val);
    97. /* 初始化遇到超过0xffff/2的数值,不正常! */
    98. if (tpad_default_val > 0xffff / 2)
    99. {
    100. return 1;
    101. }
    102. return 0;
    103. }
    104. /*******************************************************************************
    105. *
    106. * 扫描触摸按键
    107. * mode:0,不支持连续触发(按下一次必须松开才能按下一次);1,支持连续触发(可以一直按下)
    108. * 返回值:0,没有按下;1,有按下;
    109. *
    110. *******************************************************************************/
    111. //阈值:捕获时间必须大于(tpad_default_val + TPAD_GATE_VAL),才认为是有效触摸.
    112. #define TPAD_GATE_VAL 100
    113. uint8_t TPAD_Scan(uint8_t mode)
    114. {
    115. //0,可以开始检测;>0,还不能开始检测
    116. static uint8_t keyen = 0;
    117. //扫描结果
    118. uint8_t res = 0;
    119. //默认采样次数为3次
    120. uint8_t sample = 3;
    121. //捕获值
    122. uint16_t rval;
    123. if (mode)
    124. {
    125. //支持连按的时候,设置采样次数为6次
    126. sample = 6;
    127. //支持连按
    128. keyen = 0;
    129. }
    130. /* 获取当前捕获值(返回 sample 次扫描的最大值) */
    131. rval = TPAD_Get_MaxVal(sample);
    132. /* printf打印函数调试使用,用来确定阈值TPAD_GATE_VAL,在应用工程中应注释掉 */
    133. printf("scan_rval=%d\r\n", rval);
    134. //大于tpad_default_val+TPAD_GATE_VAL,且小于10倍tpad_default_val,则有效
    135. if (rval > (tpad_default_val + TPAD_GATE_VAL) && rval < (10 * tpad_default_val))
    136. {
    137. //keyen==0,有效
    138. if (keyen == 0)
    139. {
    140. res = 1;
    141. }
    142. keyen = 3; //至少要再过3次之后才能按键有效
    143. }
    144. if (keyen)
    145. {
    146. keyen--;
    147. }
    148. return res;
    149. }
    150. void test(void)
    151. {
    152. 初始化
    153. TPAD_Init();
    154. while(1)
    155. {
    156. if (TPAD_Scan(1))
    157. {
    158. HAL_GPIO_WritePin(Beep_GPIO_Port, Beep_Pin, GPIO_PIN_SET);
    159. HAL_Delay(30);
    160. HAL_GPIO_WritePin(Beep_GPIO_Port, Beep_Pin, GPIO_PIN_RESET);
    161. }
    162. }
    163. }

    实验现象

    按下电容按键,蜂鸣器发出滴滴响声。

  • 相关阅读:
    Day 28:2748. 美丽下标对的数目
    快速入门python基础语法二(附加实现人生重开模拟器小案例)
    最全的推特群推营销秘籍
    Text to image论文精读PDF-GAN:文本生成图像新度量指标SSD Semantic Similarity Distance
    STM32标准库(固件库)分析
    flutter显示这样的错误如何解决
    电脑重装系统后鼠标动不了该怎么解决
    【JavaScript复习十】数组入门知识
    R语言生物群落(生态)数据统计分析与绘图教程
    vscode在docker镜像环境编程
  • 原文地址:https://blog.csdn.net/weixin_47077788/article/details/134038364