江科大TIM定时器hal库实现
定时器相关hal库函数
hal库的定时器函数相比于标准库,多了很多的中断回调函数,同时对于定时器的初始化也改成使用句柄一次性顺带连带DMA等功能一起初始化了
typedef struct
{uint32_t Prescaler; /*定时器的预分频值*/uint32_t CounterMode; /*计数方向,向上计数,向下计数或双边计数*/uint32_t Period; /*自动重装载值*/uint32_t ClockDivision; /*定时器时钟分频*/uint32_t RepetitionCounter; /*高级定时器特有的重装载次数*/uint32_t AutoReloadPreload; /*自动重装值的更新方式,使能的话,就会使用影子寄存器来等待这次的定时器周期过了才会更新自动重装值到ARR寄存器,失能的话就是直接更新到ARR*/
} TIM_Base_InitTypeDef;
时基单元的初始化和标准库的也是几乎一样的,多了一个自动重装值的更新方式。
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
typedef struct __TIM_HandleTypeDef
#else
typedef struct
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
{TIM_TypeDef *Instance; /*TIMX的地址,选择我们要的定时器*/TIM_Base_InitTypeDef Init; /*定时器初始化结构体*/HAL_TIM_ActiveChannel Channel; /*定时器通道使能*/DMA_HandleTypeDef *hdma[7]; /*DMA结构体初始化*/HAL_LockTypeDef Lock; /*定时器配置锁定*/__IO HAL_TIM_StateTypeDef State; /*定时器状态标志*/__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; /*定时器四个通道状态*/__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4]; /*高级定时器的四个互补通道状态*/__IO HAL_TIM_DMABurstStateTypeDef DMABurstState; /*定时器DMA突发传输状态*/
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)void (* Base_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Base Msp Init Callback */void (* Base_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Base Msp DeInit Callback */void (* IC_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM IC Msp Init Callback */void (* IC_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM IC Msp DeInit Callback */void (* OC_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM OC Msp Init Callback */void (* OC_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM OC Msp DeInit Callback */void (* PWM_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Msp Init Callback */void (* PWM_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Msp DeInit Callback */void (* OnePulse_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM One Pulse Msp Init Callback */void (* OnePulse_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM One Pulse Msp DeInit Callback */void (* Encoder_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Encoder Msp Init Callback */void (* Encoder_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Encoder Msp DeInit Callback */void (* HallSensor_MspInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Hall Sensor Msp Init Callback */void (* HallSensor_MspDeInitCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Hall Sensor Msp DeInit Callback */void (* PeriodElapsedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Period Elapsed Callback */void (* PeriodElapsedHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Period Elapsed half complete Callback */void (* TriggerCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Trigger Callback */void (* TriggerHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Trigger half complete Callback */void (* IC_CaptureCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Input Capture Callback */void (* IC_CaptureHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Input Capture half complete Callback */void (* OC_DelayElapsedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Output Compare Delay Elapsed Callback */void (* PWM_PulseFinishedCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Pulse Finished Callback */void (* PWM_PulseFinishedHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM PWM Pulse Finished half complete Callback */void (* ErrorCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Error Callback */void (* CommutationCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Commutation Callback */void (* CommutationHalfCpltCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Commutation half complete Callback */void (* BreakCallback)(struct __TIM_HandleTypeDef *htim); /*!< TIM Break Callback */
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
} TIM_HandleTypeDef;
上面是关于定时器的相关配置和能与定时器联系起来的DMA的相关配置以及所有的中断回调函数的注册。
我们使用CubeMX初始化就行了,在Cubemx生成的代码里面,我们还能看见定时器时钟选择,主从模式的失能等操作。
所有的句柄成员都会在对应的.c文件下有着对应的定义,还有Is_xxx这类型的宏定义,来辅助系统对外设初始化的检测,所有的句柄初始化成功,就返回HAL_OK,错误就返回HAL_ERROR并提供给我们一个错误回调函数,我们就能在这里面知道对应的外设初始化失败,需要我们去调试程序。
定时器的中断使能和中断优先级设置cubemx都放在了mspInit初始化回调函数里面,但是我们还需要在主函数前面开启对应的TIMX中断,才能正常使用。
定时器中断
设置定时时间,也就是计数值,然后计数值CNT到达ARR后,清空CNT并且触发一次定时器溢出中断,通过设置的预分频值和自动重装值,控制我们需要的时间。
中断服务函数都在it.c文件里面找,void TIM2_UP_IRQHandler(void);TIM2更新中断触发的时候,就会进入这个中断服务函数,在里面调用总的定时器中断服务函数HAL_TIM_IRQHandler。在里面先对定时器TIMX进行判别,再清除对应的中断请求位,然后进入对应的中断回调函数。
在对中断进行编写的时候,容易遇到芯片的复位键无法使用了,这个时候基本就是遇到
中断处理异常:中断服务函数编写错误,如未正确清除中断标志位,导致中断一直触发,干扰正常复位流程。这个问题了,我们需要对我们的程序进行检查和修改。
还有对于我们的用户自定义函数也得进行检查,比如说我们移植的OLED函数,就算我们没有使用OELD_Init,但是我们依旧可以使用OLED里面的显示函数,还不会报错,因为没有初始化,导致OLED的I2C协议没有正常运行,使得程序卡死在了我们OLED显示函数那边,导致程序运行有误。
我们使用CubeMx来初始化定时器TIM2,ClockSource选择 Internal Clock
设置预分频值为7200-1和自动重装值10000-1,这样我们得到的计数时间就是
72000000/7200/10000 = 1s;
一秒产生溢出溢出更新中断。
在NVIC界面勾选TIM2的中断允许
其他的配置和前面的一样就行了。
记得开启中断
这里我们得注意了,定时器的中断,需要我们在主函数前面通过
HAL_TIM_Base_Start_IT(&htim2);
来开启对应的TIMX中断,没开启时中断是没响应的。
我们用到的是定时器TIM2的溢出中断,在HAL_TIM_IRQHandler里面找
里面有各种中断的标志位判别,以及他们的中断回调函数。
在里面我们找到TIM_FALG_UPDATE对应的中断回调函数,就是HAL_TIM_PeriodElapsedCallback()
中断回调函数都是weak修饰的,弱定义函数,我们直接对其进行内容修改即可。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim == &htim2){Num++; // 每秒递增1}
}
我们是TIM2的更新中断,所以在这个函数里面我们得对定时器先进行判别,再对数据进行操作
这样就能成功实现定时器的溢出中断计时功能了,OLEDInit别忘记了,也会导致芯片的下载出错的,但不会锁死芯片。
定时器输出比较
TIM的OC功能,我们常见的就是PWM的输出,
通过计数值CNT和CCR的比较,控制GPIO口的电平在一定时间内高电平的占比,这就是PWM输出的思路,这里实现PWM的呼吸灯功能
使用Cubemx来初始化TIM2作为PWM的输出定时器,使用通道1的PWM模式(pwm模式就是oc的一种功能,只不过运用的比较广泛,单独作为一个功能拎出来了),预分频值和自动重装载值设置为719和99,自动重装载值就是一个PWM的周期。
下面的Pulse就是我们要控制的CCR了,控制CCR就能控制PWM的占空比,如果我们一直循环控制CCR从0~100,再从100~0,就能实现我们的呼吸灯功能了。
别忘记 HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_1);//开启定时器TIM2的通道1中断
不开启的话,是使用不了的,这个函数的功能就是启动定时器的 PWM 输出并启用中断。
也可以使用 HAL_TIM_PWM_Start,不开启中断,开启PWM输出,这两个的区别就是是否启用中断,但必须有一个使用,来开启PWM输出。
HAL_TIM_PWM_Start_IT(&htim2,TIM_CHANNEL_1);//开启定时器TIM2的通道1中断while (1){for(i=0;i<100;i++){__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,i);//设置CCR值HAL_Delay(10);}for(i=100;i>0;i--){__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_1,i);//设置CCR值HAL_Delay(10);}}
和江科大一样的芯片连线就行,使用的是TIM2的通道1,按照硬件电路上看,就是PA0作为PWM的输出端口。
PWM驱动舵机和直流电机就按照上面的代码照壶画瓢就行了。
定时器输入捕获
这里需要我们上面的PWM生成的代码,IC输入捕获来测量PWM的频率,
IC输入捕获通过捕获输入TIM通道的信号上升沿下降沿,双边沿,来触发中断,在中断里面把CNT的值获取得到对应的时间,这个时间就是我们输入信号的周期,频率就是周期的倒数,得到频率。
CNT是按照时基单元的设置一直自增的。
按照江科大的设置来,定时器2通道1作为PWM的输出PA0
定时器3通道1 PA6作为输入引脚。
预分频值和自动重装值是决定频率的值,我们会在主函数里面进行修改,来控制频率可调,这边给一个72,得到的频率就是100000Hz的频率,自动重装载值我们给最大值,这样我们能测量的时间就更大,如果CNT溢出了还没有接收到信号,我们就定义一个Count值在溢出中断的时候自增,得到更大的计数值。
捕获极性这些就用默认设置的即可。
最后设置TIM3的从触发模式,TI1FP1:表示将 TI1 引脚的信号连接到通道 1的输入捕获模块,作为捕获源,在这里就是把TIM2的通道1的引脚信号作为TIM3的通道1的输入捕获。
TIm2通道1信号到达上升沿后,TIM3收到信号,会自动存储CNT的值到CCR里面,并重置CNT计数值,我们读取CCR保存的部分即可。
这边调用上面的PWM部分的时候不要弄错预分频值和自动重装值,PWM的为719和99
控制为1000HZ的频率,我们可以在主函数前面通过hal库的宏定义来修改这两个参数来获得不同的PWM频率,在TIM3处IC捕获,然后显示到我们的OLED上。
但是注意,在设置自动重装值值的时候,自动重装值不能小于我们的占空比CCR的值,或者说我们修改自动重装值ARR的时候,顺带修改CCR的值,不然会造成错误。
/* USER CODE BEGIN 2 */HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);//开启TIM2的通道1PWM输出功能HAL_TIM_IC_Start(&htim3,TIM_CHANNEL_1);//开启TIM3的通道1IC输入功能/* USER CODE END 2 */OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000Hz/* Infinite loop */__HAL_TIM_SET_PRESCALER(&htim2,359);//修改TIM2频率__HAL_TIM_SET_AUTORELOAD(&htim2,99);//设置自动重装值__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,50);//修改TIM2通道1的PWM占空比,TIM2的CCR值/* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE */
// OLED_ShowString(2,1,"ADDR");
// OLED_ShowString(3,1,"CCR");
// OLED_ShowNum(2,6,__HAL_TIM_GET_AUTORELOAD(&htim2),4);
// OLED_ShowNum(3,6,__HAL_TIM_GetCompare(&htim2,TIM_CHANNEL_1),4);OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不断刷新显示输入捕获测得的频率/* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}uint32_t IC_GetFreq(void){return 1000000/(__HAL_TIM_GET_COMPARE(&htim3,TIM_CHANNEL_1)+1);
}
TIM注意事项
TIM的注意点还是挺多的,首先就是使用CubeMx初始化完成TIm后,我们要使用他的相关功能,就得在主函数前面进行对应的定时器使能,使用普通的计数计时功能,就只需要时基使能,需要中断功能的加入,就得中断使能,运用输出功能就得OC使能,或者对应的功能使能如PWM使能,IC输入就得IC使能,普通的Start和startIT的区别就是普通的STart只开启功能,而startIT不仅会开启功能,还会开启对应的相关中断。