历史上的今天
返回首页

历史上的今天

今天是:2025年08月01日(星期五)

正在发生

2019年08月01日 | STM32实战四 定时器和按键

2019-08-01 来源:eefocus

这一章编写定时器,包括定时器基类 Timer 和派生的通用定时器 GeneralTimer。基类对定时器参数进行封装,通用定时器封装一些定时应用,对应PLC的一些功能,包括:


1ms定时中断

100个32位数字时间继电器,最小1ms,最大0xffffffff,大约50天。

一个高精度回调函数,微秒级误差,最小定时间隔1ms。

按键抖动和干扰过滤,并产生按键上升沿和下降沿。

代码中有详细的说明,这里只解释几个知识点,其它文档介绍按键防抖和延时的时候一般都是死循环,官方文档也是这么用,如果有很多按键和延时就会一个一个等,效率很低。我这里用了另外一种高效的方法,就是模仿时间继电器,100个计数器同时工作,直到计数为0时执行对应操作,这样主循环没有等待,循环周期只有几十微秒,能进行高精度实时控制,具体方法下一章中介绍,这里先做好基础。


按键滤波后动作会有一定的延时,大约4ms加主循环周期,屏蔽了高频信号,对高速信号不适用。


GeneralTimer中的100个定时器u32 m_t[100]占用很多栈空间,导至程序不运行,要增加栈空间,方法是增大 startup_stm32f10x_hd.s 文件中的 Stack_Size 项,我这里加到了 0x0800,也就是2k字节,原来是0x0200,512字节。其它不多说了,看代码:


 Timer.h


#ifndef __TIMER__

#define __TIMER__

 

extern "C" { // 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段

#pragma diag_remark 368 //消除 warning:  #368-D: class "" defines no constructor to initialize the following:

 

#include "stm32f10x.h"

#include "stm32f10x_tim.h"

 

#pragma diag_default 368 // 恢复368号警告

}

 

// TIMx 1ms定时

class Timer

{

// Construction

public:

Timer(TIM_TypeDef * TIMx); // tim:TIM1-8

 

// Properties

public:

TIM_TypeDef * m_pTIMx; // 定时器指针

  TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

 

private:

 

// Methods

public:

inline void timInit(void); // 初始化定时

inline void nvicInit(void); // 初始化优先级

void enableInterrupt(void); // 使能中断

 

virtual void onTimer(void); // 中断,返回1溢出,0不溢出

// Overwrite

public:

};

 

// 中断处理

extern "C" { // 兼容C

void TIM2_IRQHandler(void);

void TIM3_IRQHandler(void);

void TIM4_IRQHandler(void);

void TIM5_IRQHandler(void);

void TIM6_IRQHandler(void);

void TIM7_IRQHandler(void);

void TIM8_UP_IRQHandler(void);

void TIM1_UP_IRQHandler(void);

}

 

#endif

 Timer.cpp


/**

  ******************************************************************************

  * @file  Timer.cpp

  * @author  Mr. Hu

  * @version  V1.0.0 STM32F103VET6

  * @date  05/19/2019

  * @brief 定时器基类

*

  ******************************************************************************

  * @remarks

  * 定时器基类,默认不开启中断,可用类方法enableInterrupt使能中断

* 默认定时周期1ms

*

* 参考资料:

* STM32 Keil C++编写单片机程序

* https://www.cnblogs.com/yeshuimaowei/p/6949642.html

* 利用基本定时器进行精确延时

* https://blog.csdn.net/sahpah/article/details/38545637

* TIM1和8是高级定时器,配置方法不同

* https://www.cnblogs.com/pertor/p/9488813.html

  */ 

 

/* Includes ------------------------------------------------------------------*/

extern "C" { // 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段

#pragma diag_remark 368 //消除 warning:  #368-D: class "" defines no constructor to initialize the following:

 

#include "stm32f10x_tim.h"

#include "misc.h"

 

#pragma diag_default 368 // 恢复368号警告

}

 

#include "Timer.h"

 

// 全局指针,每个定时器对应一个指针,不能合并

Timer * pTim2 = 0;

Timer * pTim3 = 0;

Timer * pTim4 = 0;

Timer * pTim5 = 0;

Timer * pTim6 = 0;

Timer * pTim7 = 0;

Timer * pTim8 = 0;

Timer * pTim1 = 0;

 

/**

  * @date 05/19/2019

  * @brief  初始化定时器基类.

  * @param m_pTIMx:TIM1-8

  * @retval None

  */

Timer::Timer(TIM_TypeDef * pTIMx)

{

assert_param(IS_TIM_ALL_PERIPH(TIMx));

// 保存变量

m_pTIMx = pTIMx;

if(pTIMx == TIM2) pTim2 = this;

else if(pTIMx == TIM3) pTim3 = this;

else if(pTIMx == TIM4) pTim4 = this;

else if(pTIMx == TIM5) pTim5 = this;

else if(pTIMx == TIM6) pTim6 = this;

else if(pTIMx == TIM7) pTim7 = this;

else if(pTIMx == TIM8) pTim8 = this;

else if(pTIMx == TIM1) pTim1 = this;

//else // ?? 异常

nvicInit();

timInit();

}

 

/**

  * @date 05/19/2019

  * @brief  初始化定时器,周期1ms.

  * @param None

  * @retval None

  */

void Timer::timInit(void)

{

if(m_pTIMx == TIM2) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2-7 时钟使能

else if(m_pTIMx == TIM3) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

else if(m_pTIMx == TIM4) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

else if(m_pTIMx == TIM5) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);

else if(m_pTIMx == TIM6) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);

else if(m_pTIMx == TIM7) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);

else if(m_pTIMx == TIM8) RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE); // TIM1和8 时钟使能

else if(m_pTIMx == TIM1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); 

 

  TIM_DeInit(m_pTIMx); // 使用缺省值初始化TIM外设寄存器

//TIM_TimeBaseStructInit( &TIM_TimeBaseStructure ); // 5项都独立初始化了,不用这个

  TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 自动重装载寄存器值,软件仿真用1000-1,软件仿真很慢,可用100-1

  TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 时钟预分频数为72-1,定时周期计算方法 72M/72/1000 = 1ms

  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //采样分频倍数1,未明该语句作用。

  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //上升模式

TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //高级定时器1和8是用定时器功能配置这个才可以是正常的计数频率一开始的72Mhz 值得注意的地方

  TIM_TimeBaseInit(m_pTIMx, &TIM_TimeBaseStructure);

  TIM_ClearFlag(m_pTIMx, TIM_FLAG_Update); //清除更新标志位

  //TIM_ITConfig(m_pTIMx, TIM_IT_Update, ENABLE); //使能中断, //在这里使能中断不好,会产生很多没用的中断,所有初始化完成后

  TIM_Cmd(m_pTIMx, ENABLE); //使能TIM定时器

}

 

/**

  * @date 05/19/2019

  * @brief  初始化优先级 // ?? 需要完善

  * @param None

  * @retval None

  */

void Timer::nvicInit(void)

{

NVIC_SetPriorityGrouping(NVIC_PriorityGroup_0);

NVIC_InitTypeDef NVIC_InitStructure;

NVIC_InitStructure.NVIC_IRQChannel = //TIMx 中断 // ?? 越界

m_pTIMx != TIM2 ? m_pTIMx != TIM3 ? m_pTIMx != TIM4 ? m_pTIMx != TIM5 ? m_pTIMx != TIM6 ? m_pTIMx != TIM7 ? m_pTIMx != TIM8 ? m_pTIMx != TIM1

? 0 : TIM1_UP_IRQn : TIM8_UP_IRQn : TIM7_IRQn : TIM6_IRQn : TIM5_IRQn : TIM4_IRQn : TIM3_IRQn : TIM2_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级 1 级

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级 3 级

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能

NVIC_Init(&NVIC_InitStructure); //初始化 NVIC 寄存器

}

 

/**

  * @date 05/19/2019

  * @brief  中断

  * @param None

  * @retval None

  */

void Timer::onTimer(void)

{

// ?? 下面的判断在大部分示例中都有,实测无用,先注释

//if(TIM_GetITStatus(m_tim,TIM_IT_Update) == RESET) //溢出中断

// return;

  TIM_ClearITPendingBit(m_pTIMx, TIM_IT_Update);//清除溢出标志

 

}

 

/**

  * @date 05/19/2019

  * @brief  使能中断,放到初始化和配制以后,否则会产生好多没用的中断

  * @param None

  * @retval None

  */

void Timer::enableInterrupt(void)

{

  TIM_ClearITPendingBit(m_pTIMx, TIM_IT_Update);//清除溢出标志,否则会产生一个中断

TIM_ITConfig(m_pTIMx, TIM_IT_Update, ENABLE);//使能中断, 不能先使能中断,会产生很多没用的中断

}

 

// 各定时器中断入口,调用各自的onTimer();

void TIM2_IRQHandler(void)

{

pTim2->onTimer();

}

 

void TIM3_IRQHandler(void)

{

pTim3->onTimer();

}

 

void TIM4_IRQHandler(void)

{

pTim4->onTimer();

}

 

void TIM5_IRQHandler(void)

{

pTim5->onTimer();

}

 

void TIM6_IRQHandler(void)

{

pTim6->onTimer();

}

 

void TIM7_IRQHandler(void)

{

pTim7->onTimer();

}

 

// TIM1和8是高级定时器

void TIM8_UP_IRQHandler(void)

{

pTim8->onTimer();

}

 

void TIM1_UP_IRQHandler(void)

{

pTim1->onTimer();

}

 

GeneralTimer.h


#ifndef __GENERALTIMER__

#define __GENERALTIMER__

 

#pragma anon_unions // 允许使用匿名结构

 

#include "Timer.h"

 

union INx // 记录输入口状态,按位分割

{

u16 state; // 状态字

struct

{

u8 count : 8; // 转换计数,低8位

u8 enable : 1; // 前次电平,第8位,从0开始

u8 level : 1; // 当前电平,第9位

u8 up : 1; // 上升沿,第10位

u8 down : 1; // 下降沿,第11位

u8 reserve: 4; // 预留

};

};

 

// 通用定时器

class GeneralTimer : public Timer

{

// Construction

public:

GeneralTimer(TIM_TypeDef * m_pTIMx);

 

// Properties

public:

u32 m_t[100]; // 100个定时器,要修改栈空间,startup_stm32f10x_hd.s Stack_Size      EQU     0x400 ;//0x00000200 // 栈大小http://m.elecfans.com/article/588038.html

INx ina[16], inb[16], inc[16], ind[16], ine[16];

 

private:

u16 m_nFT1;

u16 m_nFT2;

void (*m_pBack)(void);

 

// Methods

public:

virtual void onTimer(void); // 中断,返回1溢出,0不溢出

void setCallback(void (*pBack)(void), u16 nCount);

void loop(void); // 主循环中调用,必需在循环的最开始

inline void addCount( GPIO_TypeDef* GPIOx, u16 nPin, INx & inx );

inline void upDown( INx & inx );

 

// Overwrite

public:

};

 

#endif

 GeneralTimer.cpp


/**

  ******************************************************************************

  * @file GeneralTimer.cpp

  * @author  Mr. Hu

  * @version  V1.0.0 STM32F103VET6

  * @date  06/62/2019

  * @brief  通用定时器

*

  ******************************************************************************

  * @remarks

  * 通用定时器,启用中断,包括3大功能中断方式

* 1. 100个时间继电器,单位ms,最大0xffffffff,大约50天。

* 2. 输入信号数字滤波,方法是,连续4ms(四次采样),信号反向不变,输入信号改变,并产

*    生一个主循环(main中的循环)周期的上升或下降沿信号。作用是防止抖动和干扰,高

*    速信号不能用这个滤波。

* 3. 高精度回调函数,回调周期最小1ms,最大65535ms,精度us

*

* 参考资料:

* STM32 Keil C++编写单片机程序

* https://www.cnblogs.com/yeshuimaowei/p/6949642.html

* 利用基本定时器进行精确延时

* https://blog.csdn.net/sahpah/article/details/38545637

* TIM1和8是高级定时器,配置方法不同

* https://www.cnblogs.com/pertor/p/9488813.html

  */ 

 

/* Includes ------------------------------------------------------------------*/

extern "C" { // 兼容C,按C语言编译,Keil5中的包含文件已经加入了C++兼容,不用再加这一段

#pragma diag_remark 368 //消除 warning:  #368-D: class "" defines no constructor to initialize the following:

 

#include "stm32f10x_tim.h"

 

#pragma diag_default 368 // 恢复368号警告

}

 

#include "GeneralTimer.h"

 

#define COUNT 0x4 // 二进制1位

 

/**

  * @date 06/02/2019

  * @brief  通用定时器,从Timer继承

  * @param m_pTIMx:TIM1-8

  * @retval None

  */

GeneralTimer::GeneralTimer(TIM_TypeDef * m_pTIMx)

: Timer(m_pTIMx)

, m_nFT1(0)

, m_nFT2(0)

, m_pBack(0)

{

// 初始化100个时间继电器

for(int i = 0; i < sizeof(m_t)/sizeof(m_t[0]); i++)

m_t[i] = 0;

// 按键滤波,防止抖动和干扰

for( u8 i = 0; i < 16; i++ )

{

ina[i].state = 0x0200; // 0x0200表示ina[i].level = 1,默认上拉

inb[i].state = 0x0200;

inc[i].state = 0x0200;

ind[i].state = 0x0200;

ine[i].state = 0x0200;

}

enableInterrupt(); // 允许中断

}

 

/**

  * @date 06/02/2019

推荐阅读

史海拾趣

Electronicon Kondensatoren GmbH公司的发展小趣事

1976年,ELECTRONICON推出了采用金属化聚丙烯薄膜的电容器系列,这一创新技术为电容器行业带来了显著的空间和成本优势,进一步巩固了其在市场上的领先地位。随着技术的不断进步,ELECTRONICON的产品逐渐拓展至照明、电机和电力电子等多个领域。

Davies Molding公司的发展小趣事

Davies Molding公司深知,人才是企业发展的核心动力。因此,公司高度重视团队建设与人才培养。通过建立完善的培训体系,Davies Molding公司不断提升员工的技能和素质,打造了一支高素质、专业化的团队。这支团队不仅为公司的发展提供了有力保障,还为行业的进步做出了积极贡献。

American Power Management Inc公司的发展小趣事

在追求经济效益的同时,APMI始终关注可持续发展和社会责任。公司注重环保和节能技术的研发与应用,推出了一系列绿色电源管理产品。此外,APMI还积极参与社会公益活动,支持教育事业和环保事业。通过这些举措,APMI不仅为社会的可持续发展做出了贡献,还树立了良好的企业形象。

请注意,以上故事均为虚构内容,仅用于展示电子行业发展过程中可能遇到的情境和策略,并不代表任何真实公司的实际经历。

EnerSys公司的发展小趣事

EnerSys一直致力于技术创新和研发投入。公司拥有一支专业的研发团队,不断推出具有竞争力的新产品和解决方案。例如,EnerSys在锂电池领域取得了重要突破,成功开发出高能量密度、长寿命的锂电池产品。这些创新产品不仅满足了客户的多样化需求,还推动了公司在电子行业中的持续发展。

德欣(COV)公司的发展小趣事

德欣(COV)公司自创立之初,就致力于压敏电阻器、SPD防雷芯片、智能过压保护器等核心电子元件的研发与生产。公司创始人凭借对电子行业发展趋势的精准判断,以及对技术创新的执着追求,成功研发出了一系列具有竞争力的产品。这些产品不仅满足了当时市场的需求,也为德欣公司在电子行业打下了坚实的基础。

Abracon公司的发展小趣事

随着技术的不断成熟和产品的不断优化,德欣公司开始积极拓展市场。公司通过参加各类行业展会、举办技术交流会等方式,与国内外客户建立了广泛的联系。同时,德欣公司还注重品牌建设,通过优质的产品和服务赢得了客户的信任和认可。逐渐地,德欣品牌在电子行业中崭露头角,成为了一家备受瞩目的企业。

问答坊 | AI 解惑

《社区大讲堂》DO-254中的高设计可靠性的逻辑综合(七)--支持逻辑等效型检查

设计可靠性在DO-254的A级和B级设计中尤其重要。. 附录B中对有这样的描述:“当设计可靠性级别增加后, 设计方法需要能够验证被测设计满足安全性要求, 这些要求有可能有重合的部分,需要有层次化的设计可靠性设计方法。 在任何设计过程中,如 ...…

查看全部问答>

关于WINCE5.0 驱动开发的问题

我是按照这个网页一步一步作的:http://www.pcwiki.net/ht/view/cps-4/id-20978 前面一切都调试成功,作到上面页面的Target | Attach步后,,出现“download Runtime Image to CE Device \"  然后进度条就一直没有变,请问这个下载很 ...…

查看全部问答>

switching module processor

谁能详细的介绍一下SMP which is in SM.体系结构,功能等等。谢谢 。…

查看全部问答>

RegReplaceKey的疑惑?

这是WINCE帮助文档上的介绍: LONG RegReplaceKey(   HKEY hKey,   LPCTSTR lpSubKey,   LPCTSTR lpNewFile,   LPCTSTR lpOldFile ); hKey [in] The hKey parameter must be HKEY_LOCAL_MACHINE ...…

查看全部问答>

想学RAM,给点意见.

本人是计算机专业毕业,对java,php,delphi都有相当的工作经验,学过一定时间的伟福,但它的接口没有RAM丰富,现在想学一下RAM,请问一下RAM是在windows还是linux下运行,还有就是调度软件用什么,要不要仿真器.…

查看全部问答>

M0 新唐CM0+MDK-ARM入门开发过程简介之平台的建立

一  平台资源下载   1.Mdk4.12的下载因为是最新发布的芯片所以现在只有mdk4.11和4.12支持本系列芯片。   下载地址 在http://www.mcu123.com/news/Soft/ShowSoftDown.asp?UrlID=3&SoftID=529上右击“下载地址:” ...…

查看全部问答>

tftp下载的内核死掉

死掉情况1(这种情况可以解释成TFTP传输过程误码):Bytes transferred = 2641544 (284e88 hex)                       &nb ...…

查看全部问答>

5502-300MHZ

5502最大可接收多高频率的信号?  MCBSP/GPIO。。。最高的频率是多少?   108MHZ的信号能直接接么,不考虑CPU有没有时间工作…

查看全部问答>

3G模块已获得私有IP,如何进行UDP通信

3G模块:EM770w 直接用AT命令控制,已实现PPP连接(因为获得网关分配的私有IP,我认为,不知道对否)。在这种情况下如何进行UDP通信。因为打算把EM770W放在一块开发板上,所以希望有高手能提供一些关于AT命令或者更底层的意见。 这个EM770W完全不 ...…

查看全部问答>