STM32F10X便利的定时器C++驱动设计(基于基础库)
2016-08-26 11:42
495 查看
目的
该驱动的目的是使用户远离繁琐的定时器配置,而更关心代码逻辑,加快项目开发。使用效果
1.用户只需要在主函数中定义一个定时器Timer tim(TIM2,1,2,3);//使用定时器2 计时 :1s 2ms 3us
2.开启定时器
tim.Start();
这时定时器已经按照你需要确定的时间开始运转,如果你需要编写中断函数,
在UserInterrupt.cpp文件中找到相应的函数即可,例如
void Timer2_IRQ() { static bool flag = true; if(flag) { cnt++; if(cnt>100) flag=false; } else { cnt--; if(cnt<1) flag=true; } }
每次到达你设计的时间便会进入一次这个函数。更多功能下面会详细讲出
文件设计
为了应对之后个其他片上资源的封装,这里将中断分离了开来。一共使用了7个文件。驱动封装Timer.Cpp/h
里面主要包含编写的驱动函数。也就是用户主要使用的东西了.用户通过他来对定时器做出相应操作,致力于几个简单的函数就能满足用户的要求。中断路标Interrupt.cpp/h
由于系统是固定了中断函数的名字的,当发送中断的时候,系统会根据中断的通道找到相应的中断函数,这文件就是用来汇聚中断,再派发给用户自己定义的中断函数。在定时器这里不好理解为什么要这样写,但到了使用捕获的时候就发现,自己的中断函数是写在类里面的,而系统又找不到他,于是中断路标的用处也就体现出来了。用户中断函数UserInterrupt.cpp/h
这里就是存放用户想自定义的中断类容了,就和系统给除的定义好的4000
函数一样,这里也是将其函数名写好,用户只需要填空即可。为何要这么写,而不用系统已经定义好的?就是不想打破Interrupt文件中断路标的身份,Interrupt是不允许用户修改的。于是对于需要用户自定义的中断函数就需要一个专门提供可编写的文件来完成,UserInterrupt就诞生了
资源使用配置Configuration.h
这里面主要是宏定义,开关用,对于无需使用的功能,将不进行编译,节约空间。文件讲解
STM32的时钟
想弄清定时器肯定要先了解一下STM32的时钟吧,首先我们先看一下我在网上扒的一张图可以看出系统时钟是可以来至外部晶振或者内部晶振的。在经过PLL倍频后成为系统时钟。系统默认PLL倍频是9,也就是外接8m晶振,系统时钟便是72M,接下来会经过三个分频器后提供给设备使用。系统库函数中将AHB和APB1/2都设置为了1。也就是我们定时器实际使用的时钟就是72M的。系统在执行主函数之前,会对这些先进行初始化。默认就行的话也无需在主函数中写啥了。
这里补充一些关于时钟的知识吧.下面是截取至别人的一片帖子
在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 其实是四个时钟源,如下图所示(灰蓝色),PLL是由锁相环电路倍频得到PLL时钟。
①、HSI是高速内部时钟,RC振荡器,频率为8MHz。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
在STM32中,连接在APB1(低速外设)上的设备有:电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3、SPI2、窗口看门狗、Timer2、Timer3、Timer4 。
连接在APB2(高速外设)上的设备有:GPIO_A-E、USART1、ADC1、ADC2、ADC3、TIM1、TIM8、SPI1、ALL。
Timer.Cpp/h
引用
extern "C"{ #include "stm32f10x.h" } #include "Interrupt.h"
为什么要用extern,原因在于C++编译的时候函数名和C编译不一样,例如int a();C编剧会在前面加上_ 变成 _a,而C++会在加上些其他的。具体没研究,就是如果用C++编译C的库容易发生找不到函数的问题,所以要加上extern。
Interrupt作为中断路标被引用
宏定义
#define INPUTMAX 59702385 //911*65535 最大计数 #define COEFFICIENT 911 //72M下不分频最大计时us
INPUTMAX表示最大输入时间,当分频为最大值且计数也为最大值时的时间。其实定时可以超出这个,具体在待完善部分会讲到
COEFFICIENT 表示72M下不分频最大计时。
类成员和成员函数
私有成员
u16 mArr;//计数器初值 u16 mPsc;//计数器预分频 TIM_TypeDef *mTempTimer;//时钟选择 u8 ErrorMassage;//错误信息标识 0标识没有出错 1:输出时间超出最大值 bool Conversion(u16 s,u16 ms,u16 us);//将时分秒转换为初值和预分频
有注释我就无需说啥,这里讲一下Conversion函数吧,这是一个我写的小算法,作用是根据输入时间计算出分频值和初值。
这里将函数实现贴出来吧
bool Timer::Conversion(u16 s,u16 ms,u16 us) //将时分秒转化为预分频和初值 { u32 time; u16 tempPsc;//用于暂存计算值 u32 tempArr=0xfffff; time=s*1000000+ms*1000+us; //计算总时间 单位us if(time >INPUTMAX) return false; //超出最大计数范围 if(time<COEFFICIENT) //如果一分频可以满足 mPsc=1; else mPsc=time/COEFFICIENT; //计算出最相近的预分频 tempPsc=mPsc; //保存初次计算结果 // 当计算出的ARR没有小数且ARR小于0xffff时退出循环,如果mps大于了65535 也退出 while( ((time*72)% mPsc!=0 || tempArr>0xffff ) && mPsc<=65535)//如果计算的初值是个整数,或者没有找到可以计算出整数的分频数 { mPsc++; tempArr=(time*72)/mPsc; //计算出初值 } if(mPsc>=65535) //如果找到能够整除的分频值,则选用精度最大的分频值 { mPsc=tempPsc; tempArr=(time*72)/mPsc; //计算出初值 } else mArr=tempArr; return true; }
几乎每一句都有注释,看懂应该不难,可能有人发现我定义都是无符号型却在用除法,这里可没有写错,第一个除法我就是要保留他的整数部分,如果整除了初值不久为0了嘛,后面的除法我是先进行了判断的。写得肯定不是无懈可击,有各种不健全的东西 - -|,利用率也没做优化。这些就是之后需要完善的东西了。
公共成员
构造函数//////////////////////////////////////// ///定时器初始化,默认定时器1,定时1ms ///@param timer 选择使用的定时器 ///@param Prioritygroup 中断分组 ///@param preemprionPriority 抢占优先级 ///@param subPriority 响应优先级 ///@param second 秒 ///@param millisecond 毫秒 ///@param microsecond 微秒 //////////////////////////////////////// Timer(TIM_TypeDef *timer=TIM1,u16 second=0, u16 millisecond=1,u16 microsecond=0, u8 Prioritygroup=2,u8 preemprionPriority=2,u8 subPriority=2);
定时器的相关配置都在这里面了,在定义的时候就完成配置。
接下来我就分段讲解一下这个的组成吧。
首先是局部变量的定义:
uint8_t timerIrqChannel;//用于保存中断通道 TIM_TimeBaseInitTypeDef TIM_BaseInitStructure;//定时器配置结构体 mTempTimer=timer;//保存使用的定时器 NVIC_InitTypeDef NVIC_InitStructure;//中断配置结构体
然后计算出初值和分频值:
if(!Conversion(second,millisecond,microsecond)) { ErrorMassage = 1;//输入超出最大值 return ; }
之后通过传入的定时器开启响应的时钟和确定中断通道,宏定义通过configuration开启,通过他确保不会传入错误的东西。。。
#ifndef USE_TIMER1 #ifndef USE_TIMER2 #ifndef USE_TIMER3 #ifndef USE_TIMER4 return ; #endif #endif #endif #endif if(timer==TIM1) { #ifdef USE_TIMER1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); timerIrqChannel=TIM1_UP_IRQn; #endif } else if(timer==TIM2) { #ifdef USE_TIMER2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); timerIrqChannel=TIM2_IRQn; #endif } else if(timer==TIM3) { #ifdef USE_TIMER3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); timerIrqChannel=TIM3_IRQn; #endif } else if(timer==TIM4) { #ifdef USE_TIMER4 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); timerIrqChannel=TIM4_IRQn; #endif } else { }
接下来就是定时器的相关配置,其实就是让结构体里面填充值,然后去初始化他
// TIM_DeInit(timer);//将寄存器重设为缺省值 TIM_BaseInitStructure.TIM_Period = mArr-1; //设置初值 TIM_BaseInitStructure.TIM_Prescaler =mPsc-1;//设置预分频 TIM_BaseInitStructure.TIM_ClockDivision = 0;//设置时钟分割 TIM_BaseInitStructure.TIM_RepetitionCounter=0;//重复溢出多少次产生一个中断 TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//设置计数方式 TIM_TimeBaseInit(timer,&TIM_BaseInitStructure); TIM_ClearFlag(timer, TIM_FLAG_Update);//清空中断标识 TIM_ITConfig(timer, TIM_IT_Update, ENABLE); //使能中断
配置完成后就是最后一步,中断配置了,根据上面传入的中断分组,强制优先级和响应优先级做相应配置就可以了,如果没传入就是默认值。(0 2 2)
switch(Prioritygroup)//中断分组 { case 0: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); break; case 1: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); break; case 2: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); break; default: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3); break; case 4: NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); break; } NVIC_InitStructure.NVIC_IRQChannel =timerIrqChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = preemprionPriority; //先占优先 d5b5 NVIC_InitStructure.NVIC_IRQChannelSubPriority = subPriority; //从优先 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器 //TIM_Cmd(mTempTimer, ENABLE);//开启定时器 /**/ ErrorMassage = 0;//没有出错
这里就完成了相应配置,但是还没有开启定时器。可以看到我在配置哪里屏蔽了使能,目的是让用户去开启他,而不是定义了他就自己开始跑,可控性高一些。
之后就是写功能函数
//////////////////// ///开启定时器 /////////////////// void Start(); /////////////////// ///关闭定时器 ////////////////// void Stop(); ///////////////////////////////////////// ///中断开关 ///@param bool true 开启中断 false 关闭中断 ////////////////////////////////////////// void OnOrOffIrq(bool Switch); /////////////////// ///清空计数器的值 ////////////////// void ClearCNT(void);
都是一两句的函数,就不列出具体实现了。
Interrupt.cpp/h
声明部分仍然是extern包裹,对四个系统函数定义void TIM1_UP_IRQHandler(void); void TIM2_IRQHandler(void) ; void TIM3_IRQHandler(void) ; void TIM4_IRQHandler(void) ;
发送中断了系统就会更加相应中断来找这几个函数,下面列出.CPP里面的内容
void TIM1_UP_IRQHandler(void) //----TIM1 Up-------// { #ifdef USE_TIMER1 TIM_ClearITPendingBit(TIM1, TIM_FLAG_Update); Timer1_IRQ(); #endif } void TIM2_IRQHandler(void) //----TIM4 IRQ------// { #ifdef USE_TIMER2 TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update); Timer2_IRQ(); #endif } void TIM3_IRQHandler(void) //----TIM4 IRQ------// { #ifdef USE_TIMER3 TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update); Timer3_IRQ(); #endif } void TIM4_IRQHandler(void) //----TIM4 IRQ------// { #ifdef USE_TIMER4 TIM_ClearITPendingBit(TIM4, TIM_FLAG_Update); Timer4_IRQ(); #endif } 这个文件和库一样是无需用户管理的。中断方面,用户需要编写的就是下面的文件。
UserInterrupt.cpp/h
这里保存了interrupt指向的各个中断编写函数,是留给用户自定义的#ifdef USE_TIMER1 void Timer1_IRQ(); #endif #ifdef USE_TIMER2 void Timer2_IRQ(); #endif #ifdef USE_TIMER3 void Timer3_IRQ(); #endif #ifdef USE_TIMER4 void Timer4_IRQ(); #endif
.cpp
#ifdef USE_TIMER1 void Timer1_IRQ() { } #endif #ifdef USE_TIMER2 void Timer2_IRQ() { } #endif #ifdef USE_TIMER3 void Timer3_IRQ() { } #endif #ifdef USE_TIMER4 void Timer4_IRQ() { } #endif
Configuration.h
当见到了上面茫茫多的宏定义的时候,那些定义在哪呢,就是这个文件了,这个文件同样需要用户修改。//~~~~~~~~~~~~~~~USART Congfigure~~~~~~~~~~~// #define USE_USART //---USART---// #define USE_USART1 //---USART1--// //#define USE_USART2 //---USART2--// //#define USE_USART3 //---USART3--// //~~~~~~~~~~~~~~~Timer Congfigure~~~~~~~~~~~// #define USE_TIMER //---Timer---// //#define USE_TIMER1 //---Timer1--// #define USE_TIMER2 //---Timer2--// //#define USE_TIMER3 //---Timer3--// //#define USE_TIMER4 //---Timer4--// #ifdef USE_TIMER #include "Timer.h" #endif #ifdef USE_USART #include "USART.h" #endif
待完善部分
1.在定时器配置处有个TIM_RepetitionCounter结构体成员,通过它可以打破最大定时的限制。理由是它的作用是多少次溢出后才产生中断,这样可以继续扩大计数范围2.改封装没有个计数模式留接口,默认是想上计数。
3.其他茫茫多,靠君来补了 - -|
附录
附上上了提到的各个文件,至于为啥不附上工程,你导入进去就明白我遇到的结构问题了,在编写自定义定时器中断函数的时候有点奇怪,有很多方法能解决,我想看看大家怎么想的~~啦。不要看到这儿就认为这个这是个渣渣驱动额。。使用是没有问题的,测试过了的,我真是觉得结构上有点别扭而已。代码下载
相关文章推荐
- 基于C/S架构的3D对战网络游戏C++框架_04客户端详细设计与OpenGL、Qt基础
- 《深入浅出Linux设备驱动》第二章 驱动设计的硬件基础(1)
- 《嵌入式设计及Linux驱动开发指南——基于ARM9处理器》读书笔记
- 基于IMD驱动ARP防火墙设计(windows平台)
- 程序基础设计模式的解析和实现(C++)之二十-Visitor模式
- 基于C++的数据库设计源代码
- linux驱动设计的硬件基础
- 基于领域驱动设计的本体论
- 基于视窗系统 CE的USB设备驱动程式设计
- 基于Verilog的VGA驱动设计(一)VGA时序分析
- AssionShop开源B2C系统:依然表驱动,基础部分的表结构设计(第一部分)
- 嵌入式Linux中基于 Qt/Embeded触摸屏驱动的设计
- 基于用例驱动和UML的电子商务系统模型设计
- C++临时变量的另类应用:基于iostream的类型安全的log接口设计
- 基于数据访问的集合类型-领域驱动设计的又一种特定对象
- C++临时变量的另类应用:基于iostream的类型安全的log接口设计
- “基于关键字匹配的文本过滤系统”配置文件的设计和实现(C/C++源码)
- 使用模型驱动开发和基于模式的工程来设计 SOA之第 4 部分
- 第2章 驱动设计的硬件基础
- Linux设备驱动开发详解--笔记2--驱动设计的硬件基础