PY32F003F18之通用定时器MspInit函数

2023-09-15 11:36:16

PY32F003F18高级定时器有TIM1,通用定时器有TIM3,TIM14,TIM16和TIM17。在初始化定时器前,要先写好MspInit函数,才可以调用与之对应的初始化函数。

1、TIM1更新事件的MspInit函数

//函数功能:在初始化定时器时,HAL库使用该函数
//使能TIMx时钟,设置中断优先级,使能TIMx中断

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM1)//初始化TIM1
    {
    __HAL_RCC_TIM1_CLK_ENABLE();                          //使能TIM1时钟
    HAL_NVIC_SetPriority(TIM1_BRK_UP_TRG_COM_IRQn, 0, 0); //设置中断优先级
    HAL_NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);         //使能TIM1中断
    }
    if(htim->Instance == TIM3)//初始化TIM3
    {
    __HAL_RCC_TIM3_CLK_ENABLE();           //使能TIM3时钟
    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); //设置中断优先级
    HAL_NVIC_EnableIRQ(TIM3_IRQn);         //使能TIM3中断
    }
}

TIM1更新事件,需要调用HAL_TIM_Base_Init ()进行定时器初始化。

2、TIM1采用OC1互补输出的MspInit函数

//函数功能:使能TIM1时钟,GPIOA时钟,将PA0复用为TIM1_CH1N,将PA3复用为TIM1_CH1
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
  GPIO_InitTypeDef   GPIO_InitStruct;

  if(htim->Instance == TIM1)//初始化TIM1
    {
    __HAL_RCC_TIM1_CLK_ENABLE();  //TIM1时钟使能
    __HAL_RCC_GPIOA_CLK_ENABLE(); //GPIOA时钟使能

    /*初始化PA0/PA7为TIM1_CH1N/TIM1_CH1*/
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;       //复用功能推挽模式
    GPIO_InitStruct.Pull = GPIO_PULLUP;           //引脚上拉被激活
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //引脚速度为高速
    GPIO_InitStruct.Alternate = GPIO_AF14_TIM1;   //选择AF14通道
    GPIO_InitStruct.Pin = GPIO_PIN_0;             //选择第0引脚
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);       //A0-TIM1_CH1N
      //根据GPIO_InitStruct结构参数初始化GPIOA的外设寄存器
      //将PA0复用为TIM1_CH1N

    GPIO_InitStruct.Alternate = GPIO_AF13_TIM1;//选择AF13通道
    GPIO_InitStruct.Pin = GPIO_PIN_3;          //选择第3引脚
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);         //A3-TIM1_CH1
      //根据GPIO_InitStruct结构参数初始化GPIOA的外设寄存器
      //将PA3复用为TIM1_CH1

    }
    if(htim->Instance == TIM3)//初始化TIM3
    {
    }
}

TIM1采用OC1互补输出则需要调用HAL_TIM_PWM_Init()。

3、TIM1采用IC1输入捕获的MspInit函数

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef   GPIO_InitStructure;

    if(htim->Instance == TIM1)//初始化TIM1
    {
    __HAL_RCC_TIM1_CLK_ENABLE();  //使能TIM1时钟
      __HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟

      GPIO_InitStructure.Pin = GPIO_PIN_3;       //选择第3脚,PA3是为TIM1_CH1
    GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; //复用功能推挽模式
    GPIO_InitStructure.Pull = GPIO_PULLUP;     //引脚上拉被激活
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚速度为最高速
    GPIO_InitStructure.Alternate = GPIO_AF13_TIM1;//选择AF13,将PA3引脚复用为TIM1_CH1
    HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
      //根据GPIO_InitStructureure结构变量指定的参数初始化GPIOA的外设寄存器
      //将PA3初始化为TIM1_CH1功能,用作IC11输入捕获引脚

    HAL_NVIC_SetPriority(TIM1_CC_IRQn,1, 0); //设置"捕获/比较"的中断优先级为1
    HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);        //开启"捕获/比较"总中断
    }
    if(htim->Instance == TIM3)//初始化TIM3
    {
    }
}

TIM1采用IC1捕获,则需要调用HAL_TIM_IC_Init()

通过上面的举例,我们发现MspInit()有很多分支,显然HAL库采用这种方式管理程序,降低了程序的可读性,且给移植也带来不便,因为移植时需要将不要的内容删除。同时,MspInit()很庞大。所以,我个人觉得不需要这么设计,也是可以实现的。举例如下:

//函数功能:TIM1中基本计数功能,并使能了更新中断,每次重装ARR值时会产生一次更新中断
//arr:自动重装值。
//psc:时钟预分频数
//TIM1_COUNTERMODE_UP_IC1_Init(20000,240);//若使用HSE,当arr=20000,psc=240时,则为200ms,误差为10us;
//TIM1_COUNTERMODE_UP_IC1_Init(20000,80);//若使用HSI,当arr=20000,psc=80时,则为200ms,误差为10us;
void TIM1_COUNTERMODE_UP_IC1_Init(uint16_t arr,uint16_t psc)
{
	GPIO_InitTypeDef   GPIO_InitStructure;
	TIM_HandleTypeDef  TIM1_HandleStructure;
	TIM_IC_InitTypeDef TIM1_IC_InitStructure;

//HAL_TIM_IC_MspInit开始/
  __HAL_RCC_TIM1_CLK_ENABLE();  //使能TIM1时钟
	__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟

	GPIO_InitStructure.Pin = GPIO_PIN_3;       //选择第3脚,PA3是为TIM1_CH1
  GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; //复用功能推挽模式
  GPIO_InitStructure.Pull = GPIO_PULLUP;     //引脚上拉被激活
  GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚速度为最高速
  GPIO_InitStructure.Alternate = GPIO_AF13_TIM1;//选择AF13,将PA3引脚复用为TIM1_CH1
  HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
	//根据GPIO_InitStructureure结构变量指定的参数初始化GPIOA的外设寄存器
	//将PA3初始化为TIM1_CH1功能,用作IC11输入捕获引脚

  HAL_NVIC_SetPriority(TIM1_CC_IRQn,1, 0); //设置"捕获/比较"的中断优先级为1
  HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);        //开启"捕获/比较"总中断
//HAL_TIM_IC_MspInit结束/

  TIM1_HandleStructure.Instance = TIM1;   //选择TIM1
  TIM1_HandleStructure.Init.Period            = arr-1;
	//设置在下一个更新事件产生时,装入"自动重载入寄存器TIMx_ARR"的值
	//将(arr-1)写入"自动重载入寄存器TIMx_ARR",设置自动重装载值
  TIM1_HandleStructure.Init.Prescaler         = psc-1;
	//设置用来作为TIMx时钟频率除数的预分频值
	//将(psc-1)写入"预装载寄存器TIMx_PSC",的PSC[15:0],设置预分频值
	//计数器的时钟频率CK_CNT=fCK_PSC/(PSC[15:0]+1)
  TIM1_HandleStructure.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;//时钟不分频,则tDTS=tCK_INT
	//若使用HSE,计算公式:arr*psc/24000000/1,当arr=20000,psc=240时,则为200ms,误差为10us;
	//若使用HSI,计算公式:arr*psc/8000000/1,当arr=20000,psc=80时,则为200ms,误差为100us;
  TIM1_HandleStructure.Init.CounterMode       = TIM_COUNTERMODE_UP;//向上计数
  TIM1_HandleStructure.Init.RepetitionCounter = 1 - 1;
	//不重复计数
	//将(1-1)写入"重复计数寄存器TIMx_RCR"中的REP[7:0],设置"重复计数器值"
  TIM1_HandleStructure.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
	//"自动重装载寄存器"没有缓冲
	//不允许将"TIMx自动重新加载寄存器TIMx_ARR"的值被装入缓冲区;
	HAL_TIM_Base_Init(&TIM1_HandleStructure);    //TIM1初始化
	//选择计数器模式:向上计数
	//设置时钟分频因子:时钟不分频,则tDTS=tCK_INT
	//设置自动重装载:"自动重装载寄存器"没有缓冲
	//设置自动重装载值:TIM_Base_InitStructure.Period
	//设置预分频值:TIM_Base_InitStructure.Prescaler
	//设置"重复计数器值":TIM_Base_InitStructure.RepetitionCounter
  //启动更新事件:将TIMx_EGR中的UG位置1,由软件产生更新事件

  TIM1_IC_InitStructure.ICPolarity  = TIM_ICPOLARITY_RISING;   //上升沿捕获
  TIM1_IC_InitStructure.ICSelection = TIM_ICSELECTION_DIRECTTI;//CC1通道配置为输入
  TIM1_IC_InitStructure.ICPrescaler = TIM_ICPSC_DIV1;          //输入不分频
  TIM1_IC_InitStructure.ICFilter    = 0;                       //输入无滤波
	HAL_TIM_IC_ConfigChannel(&TIM1_HandleStructure, &TIM1_IC_InitStructure, TIM_CHANNEL_1);
	//配置通道1输入捕获

	HAL_TIM_IC_Start_IT(&TIM1_HandleStructure, TIM_CHANNEL_1);
	//使能输入捕获1中断,使能输入捕获1通道
}

HAL库这种做法确实有点坑,若你不了解程序的管理方法,就不知道MspInit()是什么东西。我就是采用自己的方式来实现,程序的可读性更强。其次Callback()也是一样,把初学者带入坑中。我不用Callback函数,直接在中断中调用自己中断处理程序。还是距离说明,免得不放心。

//函数功能:TIM1输入捕获中断服务程序
//IC1为上升沿捕获,所以捕获周期是TIM3周期的2倍
void TIM1_CC_IRQHandler(void)
{
HAL_TIM_PeriodElapsedCallback开始/
  if (_HAL_TIM_GET_FLAG(TIM1,TIM_FLAG_CC1) != RESET)
  {//根据TIM_FLAG_CC1,读"TIMx状态寄存器(TIMx_SR)"中输入捕获中断标志位
    _HAL_TIM_CLEAR_IT(TIM1, TIM_IT_CC1);
		//TIM_IT_CC1,令CC1IF=0,清除输入捕获中断标志位
    TIM1_LED_Toggle();
  }
HAL_TIM_PeriodElapsedCallback结束/
}

为了能说明问题,我把其它程序贴上来。
 

//函数功能:TIM3中基本计数功能,并使能了更新中断,每次重装ARR值时会产生一次更新中断
//arr:自动重装值。
//psc:时钟预分频数
//TIM3_COUNTERMODE_UP_Init(20000,240);//若使用HSE,当arr=20000,psc=240时,则为200ms,误差为10us;
//TIM3_COUNTERMODE_UP_Init(20000,80);//若使用HSI,当arr=20000,psc=80时,则为200ms,误差为10us;
void TIM3_COUNTERMODE_UP_Init(uint16_t arr,uint16_t psc)
{
	TIM_HandleTypeDef  TIM3_HandleStructure;

//HAL_TIM_Base_MspInit开始/
  __HAL_RCC_TIM3_CLK_ENABLE();           //使能TIM3时钟
  HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); //设置中断优先级
  HAL_NVIC_EnableIRQ(TIM3_IRQn);         //使能TIM3中断
//HAL_TIM_Base_MspInit开始/

  TIM3_HandleStructure.Instance = TIM3;   //选择TIM3
  TIM3_HandleStructure.Init.Period            = arr-1;
	//设置在下一个更新事件产生时,装入"自动重载入寄存器TIMx_ARR"的值
	//将(arr-1)写入"自动重载入寄存器TIMx_ARR",设置自动重装载值
  TIM3_HandleStructure.Init.Prescaler         = psc-1;
	//设置用来作为TIMx时钟频率除数的预分频值
	//将(psc-1)写入"预装载寄存器TIMx_PSC",的PSC[15:0],设置预分频值
	//计数器的时钟频率CK_CNT=fCK_PSC/(PSC[15:0]+1)
  TIM3_HandleStructure.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;//时钟不分频,则tDTS=tCK_INT
	//若使用HSE,计算公式:arr*psc/24000000/1,当arr=20000,psc=240时,则为200ms,误差为10us;
	//若使用HSI,计算公式:arr*psc/8000000/1,当arr=20000,psc=80时,则为200ms,误差为100us;
  TIM3_HandleStructure.Init.CounterMode       = TIM_COUNTERMODE_UP;//向上计数
  TIM3_HandleStructure.Init.RepetitionCounter = 1 - 1;
	//不重复计数
	//将(1-1)写入"重复计数寄存器TIMx_RCR"中的REP[7:0],设置"重复计数器值"
  TIM3_HandleStructure.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
	//"自动重装载寄存器"没有缓冲
	//不允许将"TIMx自动重新加载寄存器TIMx_ARR"的值被装入缓冲区;
	HAL_TIM_Base_Init(&TIM3_HandleStructure);    //TIM3初始化
	//选择计数器模式:向上计数
	//设置时钟分频因子:时钟不分频,则tDTS=tCK_INT
	//设置自动重装载:"自动重装载寄存器"没有缓冲
	//设置自动重装载值:TIM_Base_InitStructure.Period
	//设置预分频值:TIM_Base_InitStructure.Prescaler
	//设置"重复计数器值":TIM_Base_InitStructure.RepetitionCounter
  //启动更新事件:将TIMx_EGR中的UG位置1,由软件产生更新事件

	HAL_TIM_Base_Start_IT(&TIM3_HandleStructure);
  //允许计数器产生"更新中断";
  //如果计数器不是工作在触发模式中,则开始计数
}

//函数功能:TIM3中断服务程序
void TIM3_IRQHandler(void)
{
HAL_TIM_PeriodElapsedCallback开始/
  if (_HAL_TIM_GET_FLAG(TIM3,TIM_FLAG_UPDATE) != RESET)
  {//根据TIM_FLAG_UPDATE,读"TIMx状态寄存器(TIMx_SR)"中更新中断标志位
    _HAL_TIM_CLEAR_IT(TIM3, TIM_IT_UPDATE);
		//TIM_IT_UPDATE,令UIF=0,清除定时器更新中断标志位
    TIM3_LED_Toggle();
  }
HAL_TIM_PeriodElapsedCallback结束/
}

由于没有将结构定义为全局变量,有些宏定义,需要自己重写或重构。这个可以去HAL库里找,修改也很容易。见下面的代码,可以仿照对比一下就知道怎么改了,很容易做。

#ifndef __TIM1_EdgeAligned_InputCapture_H
#define __TIM1_EdgeAligned_InputCapture_H

#include "py32f0xx_hal.h"

#define _HAL_TIM_GET_FLAG(__INSTANCE__, __FLAG__)          (((__INSTANCE__)->SR &(__FLAG__)) == (__FLAG__))
//根据__FLAG__,读"TIMx状态寄存器(TIMx_SR)"中相应的中断标志位
//TIM_FLAG_UPDATE,若UIF=1,建立"更新事件"
//TIM_FLAG_CC1,若CC1IF=1,如果通道CC1配置为输出模式,则建立"CC1输出事件";
//TIM_FLAG_CC1,若CC1IF=1,如果通道CC1配置为输入模式,则建立"CC1捕获事件"
//TIM_FLAG_CC2,若CC2IF=1,如果通道CC2配置为输出模式,则建立"CC2输出事件";
//TIM_FLAG_CC2,若CC2IF=1,如果通道CC2配置为输入模式,则建立"CC2捕获事件"
//TIM_FLAG_CC3,若CC3IF=1,如果通道CC3配置为输出模式,则建立"CC3输出事件";
//TIM_FLAG_CC3,若CC3IF=1,如果通道CC3配置为输入模式,则建立"CC3捕获事件"
//TIM_FLAG_CC4,若CC4IF=1,如果通道CC4配置为输出模式,则建立"CC4输出事件";
//TIM_FLAG_CC4,若CC4IF=1,如果通道CC4配置为输入模式,则建立"CC4捕获事件";
//TIM_FLAG_COM,若COMIF=1,则建立"COM事件"
//TIM_FLAG_TRIGGER,若TIF=1,则建立"触发事件"
//TIM_FLAG_BREAK,若BIF=1,则建立"刹车事件"
//TIM_FLAG_CC1OF,若CC1OF=1,则表示"计数器x的值被捕获到TIMx_CCR1寄存器"
//TIM_FLAG_CC2OF,若CC2OF=1,则表示"计数器x的值被捕获到TIMx_CCR2寄存器"
//TIM_FLAG_CC3OF,若CC3OF=1,则表示"计数器x的值被捕获到TIMx_CCR3寄存器"
//TIM_FLAG_CC4OF,若CC4OF=1,则表示"计数器x的值被捕获到TIMx_CCR4寄存器"

#define _HAL_TIM_CLEAR_IT(__INSTANCE__, __INTERRUPT__)      ((__INSTANCE__)->SR = ~(__INTERRUPT__))
//根据__INTERRUPT__,将"TIMx状态寄存器(TIMx_SR)"中相应的中断标志位置0
//TIM_IT_UPDATE,令UIF=0,清除定时器更新中断标志位
//TIM_IT_CC1,令CC1IF=0,清除捕获/比较1中断标志位
//TIM_IT_CC2,令CC2IF=0,清除捕获/比较2中断标志位
//TIM_IT_CC3,令CC3IF=0,清除捕获/比较3中断标志位
//TIM_IT_CC4,令CC4IF=0,清除捕获/比较4中断标志位
//TIM_IT_COM,令COMIF=0,清除COM事件中断标志位
//TIM_IT_TRIGGER,令TIF=0,清除触发中断标志位
//TIM_IT_BREAK,令BIF=0,清除刹车中断标志位

extern void TIM1_COUNTERMODE_UP_IC1_Init(uint16_t arr,uint16_t psc);
extern void TIM3_COUNTERMODE_UP_Init(uint16_t arr,uint16_t psc);
#endif /* __TIM1_EdgeAligned_InputCapture_H */
#include "py32f0xx_hal.h"
#include "SystemClock.h"
#include "delay.h"
#include "LED.h"
#include "TIM1_EdgeAligned_InputCapture.h"

int main(void)
{
	HSE_Config();
//	HAL_Init();//systick初始化
  delay_init();
	HAL_Delay(1000);

	TIM1_LED_Init();
	TIM3_LED_Init();
	TIM1_COUNTERMODE_UP_IC1_Init(20000,240);//若使用HSE,当arr=20000,psc=240时,则为200ms,误差为10us;
	TIM3_COUNTERMODE_UP_Init(20000,240);//若使用HSE,当arr=20000,psc=240时,则为200ms,误差为10us;

  while (1)
  {
		delay_ms(100);
//		TIM1_LED_Toggle();
//		TIM3_LED_Toggle();
  }
}

在程序设计时,别人好的的地方,我们要学习,要借鉴。不清楚HAL库为什么会这么干,也许他们认为这样做很好。经过实践证明的,肯定是好的。

更多推荐

多语言多平台给线程增加命名

在许多编程语言和多线程库中,可以为线程分配一个名称以便更好地识别和调试线程。以下是在一些常见编程语言中如何为线程增加命名的示例:1.Python(使用threading模块):在Python中,你可以使用threading模块来创建和管理线程。要为线程增加名称,可以在创建线程对象时指定name参数:importthre

[C++随笔录] vector使用

vector使用初始化排序算法reverse和resize通过上一篇文章string的模拟实现,其实我们就已经踏入了STL的门槛.STL容器的大致用法是差不多的⇒那我们这篇博客就讲一点跟string类不一样的新颖的东西初始化跟string一样,vector可以采用下面的形式初始化//默认空间,默认初始化vector<i

瑞芯微:基于RK3568的ocr识别

光学字符识别(OpticalCharacterRecognition,OCR)是指对文本资料的图像文件进行分析识别处理,获取文字及版面信息的过程。亦即将图像中的文字进行识别,并以文本的形式返回。OCR的应用场景卡片证件识别类:大陆、港澳台身份证、通行证、护照识别,卡类识别,车辆类驾驶证识别、行驶证识别,执照识类识别,企

浅谈GPGPU任务调度-1

文章目录前言1.Stream的概念1.文献1的工作2.Hyper-Q的改进前言转自GPUandComputing公众号在先前的文章中《近距离看GPU计算(2)》我们介绍了GPUSM单元以ThreadBlock为单位的调度方法,这些的Block属于同一个kernel任务,当然处于相同的进程上下文(CUDAContext)

什么是BDD测试(行为驱动开发测试)?

1、什么是BDD测试?BDD(BehaviorDrivenDevelopment)测试,即行为驱动开发测试,是一种基于用户行为和需求的软件测试方法。通过将测试用例编写为自然语言脚本,BDD测试可以促进业务需求、开发和测试团队之间的沟通和协作,从而提高代码的可读性、可维护性和可重复性。BDD测试的优点在于,它能够将开发、

微信小程序与idea后端如何进行数据交互

交互使用的其实就是调用的req.get('url')方法进行路径访问,你要先保证自己的springboot项目已经成功运行了:如下:如何交互的?微信小程序:如下为index.js页面在onLoad()事件中调用方法Project.findAllCities()要在当前js页面中先引入project.js别名Projec

Java——》线程的打断(停止正在运行的线程)

推荐链接:总结——》【Java】总结——》【Mysql】总结——》【Redis】总结——》【Kafka】总结——》【Spring】总结——》【SpringBoot】总结——》【MyBatis、MyBatis-Plus】总结——》【Linux】总结——》【MongoDB】总结——》【Elasticsearch】Java—

Linux下git安装及使用

Linux下Git使用1.git的安装sudoaptinstallgit安装完,使用git--version查看git版本2.配置gitgitconfig--globaluser.name"YourName“##配置用户gitconfig--globaluser.emailemail@example.com##配置邮箱

vuex实现简易购物车加购效果

目录一、加购效果动图二、前提条件三、开始操作四、解决vuex刷新数据丢失问题五、最终效果一、加购效果动图二、前提条件创建了vue项目,安装了vuex三、开始操作目录结构如下:main.js文件中引入store:importVuefrom'vue'importAppfrom'./App.vue'importstorefr

服务器的维护是如何操作

服务器的维护是如何操作服务器可以说是不可或缺的资源,因为现在网络技术发达,我们的生活也都离不开网络的存在,我们想要获取的业务、资料等大多是通过网络进行,所以想要顺应潮流并获得发展,肯定需要服务器来将企业的相关信息与产品等发布到网络中,供客户选择。那应该如何维护好服务器呢?硬件维护1、增加内存和硬盘容量的工作。增加内存是

linux 杂乱汇总

SO_LINGER作用设置函数close()关闭TCP连接时的行为。缺省close()的行为是,如果有数据残留在socket发送缓冲区中则系统将继续发送这些数据给对方,等待被确认,然后返回。利用此选项,可以将此缺省行为设置为以下两种a.立即关闭该连接,通过发送RST分组(而不是用正常的FIN|ACK|FIN|ACK四个

热文推荐