十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

APM32F407基本定时器TIM6/TIM7配置详解与Tick实现

APM32F407基本定时器TIM6/TIM7配置详解与Tick实现 简介面向APM32F407及APM32F4系列单片机开发者该资源提供基本定时器TIM1/TIM2等驱动从时钟源选择、预分频、自动重载到捕获比较与中断处理的完整实现可用于周期中断、PWM输出、输入捕获及实时控制等典型嵌入式场景适合具备一定C语言基础并希望掌握标准外设库驱动写法的学习者参考。压缩包共86个文件包含41个头文件与40个C源文件分别覆盖CMSIS器件层、标准外设库、BSP板级支持与用户应用另有2个汇编启动文件、MDK-ARM的uvprojx/uvoptx工程文件以及可直接烧录的hex固件导入Keil后即可编译调试。整个压缩包仅308KB目录按Drivers、CMSIS、Projects等模块组织便于针对具体外设移植到APM32F4其他型号同时遵循标准外设库的命名与封装习惯。目前已有61人学习属于入门定时器外设驱动开发的高性价比参考包尤其适合高校嵌入式课程设计与毕业设计作为底层驱动参考。资源不仅给出初始化、启停、捕获比较寄存器读写及中断服务等代码还结合官方库框架展示模块化组织方式对照main.c与system文件可快速理清时钟配置和外设使能链路并可参考中断服务程序中的高效处理写法最终形成可复用的定时器驱动模板并落地到实际项目中代码注释清晰便于二次修改与功能扩展。1. 为什么 APM32F407 的基本定时器是 APM32F4 系列单片机最好先跑通的外设看到 APM32F407 资源表大多数人会把定时器分成通用和高级基本定时器被划到“以后再说”。真做协议栈超时、系统 tick、周期采样时反而应该先看 TIM6/TIM7。它们没有输入捕获、没有 PWM 输出、没有外部引脚连线少到只需要一条 APB1 总线。如果你只想要固定频率的更新中断TIM6/TIM7 比通用定时器更不容易被通道配置干扰也不会被 GPIO 复用表绑架。下面这套配置按 APM32F407 展开同样能搬到 APM32F4 系列其他使用同一套定时器外设的型号上换 MCU 时只需要核对 TIM6/7 是否存在、APB1 分频值以及中断向量名。2. APM32F407 基本定时器原理TIM6/7 的时钟线、寄存器位和 APM32F4 系列共性2.1 基本定时器少了什么又因为什么好使基本定时器在 APM32F407 上就是 TIM6 和 TIM7它们的硬件结构比通用定时器简单得多一个 16 位向上计数器、一个预分频器、一个自动重载寄存器溢出后产生更新事件并可以触发中断或 DMA 请求。没有预分频分频器之外的通道也没有输出比较和输入捕获逻辑。因此基本定时器不能输出 PWM不能测量外部脉冲宽度也没有编码器接口。如果是从“高级定时器减配”这个角度去理解会很容易觉得它没用。但在时基类任务里这些缺少的功能反而都是干扰项不用管通道极性不用管互补输出死区不用在引脚占用表里找它因为 TIM6/TIM7 根本不连外部引脚。APM32F4 系列单片机里这套定时器外设的寄存器偏移和中断逻辑在系列内高度一致。写 APM32F407 的初始化代码时只要没有用到某个型号独有的外设裁剪通常可以原样移植到同一系列其他型号上。唯一的差异集中在 RCC 总线使能位和启动文件里的 IRQ 名称这点在后面的章节会专门说明。2.2 TIM6/7 的时钟为什么是 APB1 时钟的两倍APM32F407 的基本定时器挂在 APB1 总线上但它们的计数时钟并不直接等于 PCLK1。APM32F407 的系统时钟最高可以配置到 168MHzAHB1 也按 168MHz 运行APB1 的最大限制是 42MHz所以 APB1 预分频器通常设置为 /4。此时 PCLK1 输出 42MHz但定时器时钟会由 APB1 预分频器内部自动变成 84MHz。系统时钟AHB1APB1 分频PCLK1TIM6/TIM7 计数时钟168MHz168MHz/442MHz84MHz144MHz144MHz/436MHz72MHz120MHz120MHz/430MHz60MHz很多刚接触 APM32F407 的人直接把定时器时钟按 PCLK1 算结果中断频率比预期快一倍或者反过来对不上。正确做法是先在系统时钟初始化完成后确认RCC-CFGR的 PPRE1 分频位再决定公式里的f_TMR。使能基本定时器的总线时钟也很直接RCC-APB1ENR | (1U 4); // TIM6 的 APB1 时钟使能TIM7 对应 bit5这段代码只负责打开 TIM6 的接口时钟和 84MHz 这个计数时钟没有直接关系。定时器内部是否分频、是否倍频由 RCC 的分频配置决定不用软件再补一步乘法。2.3 基本定时器用到哪几个寄存器基本定时器的寄存器很少初始化时只需要关注下面这几个。下面这张表按偏移地址整理无论你用官方固件库还是直接操作寄存器最后都要落到这几项上。偏移寄存器关键位作用0x00CR1/CTRL1CEN、UDIS、URS、ARPE定时器开关、更新事件行为0x0CDIER/INTENUIE更新中断使能0x10SR/STAUIF更新中断标志0x14EGR/ECTRLUG软件触发更新事件0x24CNT/COUNT无当前计数值0x28PSC/PSC无预分频值实际分频为 PSC10x2CARR/RELOAD无自动重载值溢出条件为 CNT ARRARPE位决定 ARR 有没有影子寄存器。置 1 时写入的自动重载值不会立刻生效而是等下一次更新事件才装载这样在定时器运行中改周期不会产生毛刺。做固定周期中断时建议把ARPE置 1。URS位决定哪些更新源可以触发中断置 1 后只有计数器真正溢出才触发软件写UG不会触发中断可以用来安心装载初值。3. 在 APM32F407 上初始化基本定时器寄存器配置与最小中断代码3.1 直接用寄存器把 TIM6 转起来官方固件库在不同版本里命名有出入有的用TMR6有的用TIM6函数名也出现过TMR_TimeBaseInit和APM32_TMR_TimeBaseInit两种。为了不被版本差异带偏我先用寄存器地址宏固定一份最小配置后面再给固件库写法。#include apm32f4xx.h #define TMR6_BASE 0x40001000UL #define TMR7_BASE 0x40001400UL #define TMR6_CTRL1 (*(volatile uint32_t *)(TMR6_BASE 0x00)) #define TMR6_INTEN (*(volatile uint32_t *)(TMR6_BASE 0x0C)) #define TMR6_STA (*(volatile uint32_t *)(TMR6_BASE 0x10)) #define TMR6_ECTRL (*(volatile uint32_t *)(TMR6_BASE 0x14)) #define TMR6_CNT (*(volatile uint32_t *)(TMR6_BASE 0x24)) #define TMR6_PSC (*(volatile uint32_t *)(TMR6_BASE 0x28)) #define TMR6_RELOAD (*(volatile uint32_t *)(TMR6_BASE 0x2C)) #define TMR_CR1_CEN (1U 0) #define TMR_CR1_UDIS (1U 1) #define TMR_CR1_URS (1U 2) #define TMR_CR1_ARPE (1U 7) #define TMR_INTEN_UIE (1U 0) #define TMR_STA_UIF (1U 0) #define TMR_ECTRL_UG (1U 0) void apm32f407_tim6_init(uint16_t psc, uint16_t arr) { RCC-APB1ENR | (1U 4); // 使能 TIM6 总线时钟 TMR6_CTRL1 0; // CEN0先让定时器停下来 TMR6_CNT 0; // 计数器清零 TMR6_PSC psc; // 预分频值实际分频为 psc1 TMR6_RELOAD arr; // 自动重载值溢出条件为 CNT arr TMR6_CTRL1 | (TMR_CR1_ARPE | TMR_CR1_URS); // ARR 影子装载软件 UG 不进中断 TMR6_ECTRL | TMR_ECTRL_UG; // 触发一次更新装载 PSC/ARR 影子寄存器 TMR6_INTEN | TMR_INTEN_UIE; // 打开更新中断 NVIC_EnableIRQ(TIM6_DAC_IRQn); // 向量名以启动文件为准新工程可能是 TMR6_UP_IRQn TMR6_CTRL1 | TMR_CR1_CEN; // CEN1定时器开始向上计数 }这段代码的TMR6_RELOAD对应的就是自动重载寄存器 ARR只是按 APM32F4 的命名习惯用了 RELOAD。TMR6_ECTRL对应 EGR写UG位的作用是强迫计数器执行一次更新事件这样 PSC 和 ARR 不会在启动的边界处产生一次错误计数。TMR_CR1_URS置 1 后软件写UG不会制造更新中断所以把UG放在UIE使能之前也不会产生多余中断。先开中断再启动定时器能避免计数溢出发生在中断使能之前而丢掉第一次 tick。3.2 中断服务函数的写法TIM6 在 ST 兼容命名里的中断向量是TIM6_DAC_IRQHandler在极海新固件库里可能叫TMR6_UP_IRQHandler。无论叫什么函数体里的状态判断和清标志逻辑一样volatile uint32_t g_tick 0; void TIM6_DAC_IRQHandler(void) { if ((TMR6_STA TMR_STA_UIF) ! 0U) { TMR6_STA ~TMR_STA_UIF; // 写 0 清 UIF g_tick; } }这里先读UIF再清标志是为了不让同一向量里的其他事件错误触发回调。TIM6 的向量在兼容命名里同时挂 DAC 事件如果工程里还使能了 DAC建议在同一个 Handler 内分开判断各自的标志位而不是只清一个定时器中断位。清标志用 ~TMR_STA_UIF是对寄存器做读改写只会把UIF位写 0其他状态位不受影响。不要直接对整个寄存器赋值 0否则未来扩展时序状态位时会埋雷。3.3 固件库写法和寄存器写法的对应如果用官方固件库常见的一段初始化长这样TMR_TimeBaseConfig_T tmrConfig {0}; RCC_EnableAPB1Periph(RCC_APB1_PERIPH_TMR6, ENABLE); tmrConfig.clockDivision TMR_CLOCK_DIV1; tmrConfig.countMode TMR_COUNT_MODE_UP; tmrConfig.prescaler 839; tmrConfig.period 9999; TMR_TimeBaseInit(TMR6, tmrConfig); TMR_EnableInterrupt(TMR6, TMR_INT_UPDATE); NVIC_EnableIRQ(TIM6_DAC_IRQn); TMR_Enable(TMR6);clockDivision在基本定时器上通常只用TMR_CLOCK_DIV1因为它没有外部输入滤波时钟这个字段实际上只对通用定时器有意义。prescaler对应 PSCperiod对应 ARR两个都建议直接使用查表得到的值而不在初始化函数里写一堆运行时除法。不同 SDK 可能把TMR_TimeBaseInit改成APM32_TMR_TimeBaseInit把TMR_EnableInterrupt改成APM32_TMR_EnableInterrupt。命名不同参数顺序和寄存器含义是同一套。4. 基本定时器参数计算APM32F407 的 PSC 与 ARR 别靠猜4.1 一个公式加一条边界基本定时器溢出频率的完整公式是f_update f_TMR / ((PSC 1) * (ARR 1))反推时间间隔就是T (PSC 1) * (ARR 1) / f_TMRAPM32F407 的 TIM6/TIM7 是 16 位定时器PSC 和 ARR 的上限都是 65535。在 TIM 计数时钟为 84MHz 时最大单次溢出时间约等于65536 * 65536 / 84000000 51.1 秒所以若目标时基超过 51 秒不能靠加大 ARR 硬撑要么改用 TIM2/TIM5 这类 32 位通用定时器要么在中断里用一个软件变量累加计数。4.2 84MHz 下可直接抄的参数表下面这张表按 TIM6/TIM7 计数时钟 84MHz 计算。PSC 越小计数时钟分辨率越高但 ARR 也会更快逼近 65535需要按目标时间折中。目标时基PSCARR计数时钟实际频率1ms839991MHz1kHz10ms8399991MHz100Hz100ms8399999100kHz10Hz1s8399999910kHz1Hz51.1s 上限6553565535约1.281kHz约0.0195Hz以 1ms 这行为例PSC83 表示预分频器输出为 84MHz/(831)1MHz也就是计数器每 1us 加一ARR999 表示计满 1000 个脉冲后溢出时间正好 1ms。4.3 自己选参数时先定分辨率再定 ARR我一般会先选 PSC让计数分辨率满足时间精度要求再反推 ARR。比如需要 1us 分辨率计数频率就是 1MHz在 84MHz 下PSC1 84所以 PSC83然后目标 100ms 就是 100000 个计数但 ARR 最大 65535不够装于是必须把计数频率降到 100kHz也就是 PSC839ARR9999。这里最常见的错误是把PSC直接当作分母写 84 而不是 83。预分频值要减一ARR 也要减一。代码里看到prescaler 839时实际分频是 840不是 839。调试时如果所有中断周期都比理论值多一个计数几乎都是这个原因。5. APM32F407 基本定时器排错不进中断、不复位和时间偏快怎么办5.1 定时器完全不动先看 RCC 和 CEN先确认RCC-APB1ENR对应位已经被打开。TIM6 是 bit4TIM7 是 bit5。很多从 STM32F407 平移过来的工程把外设时钟使能放在一个独立Clock_Config函数里但那个函数可能没被调用或者被后来的代码关掉了。然后在调试器里看TMR6_CTRL1 0x01CEN 必须为 1。CEN 为 0 时计数器不会跑。比较隐蔽的是 APM32F4 系列某些型号的定时器有分支复位如果APB1RSTR里对应位一直为 1外设也会停在复位状态。一般初始化代码不会碰APB1RSTR但做低功耗唤醒恢复时容易漏掉。5.2 能跑但进不了中断优先查向量名基本定时器初始化完成、计数也在走但回调函数永远不执行通常是 IRQ handler 名字和启动文件对不上。APM32F407 的启动文件如果采用 ST 兼容命名向量入口是TIM6_DAC_IRQHandler极海新版启动文件可能叫TMR6_UP_IRQHandler。函数名不一致时编译器不会报错因为 startup 文件里通常有一个默认的弱实现你的函数哪怕没被链接进去程序也只是跑进了默认死循环。打开工程的 map 文件搜索TIM6或TMR6确认你的中断函数地址出现在向量表对应位置。另一个常见问题是 NVIC 优先级分组没配置导致中断被默认优先级挡在处理中这种情况在SystemInit里一般会配好但如果自己重写了时钟配置就要重新检查NVIC_SetPriorityGrouping。5.3 时间偏快一倍先怀疑 APB1 倍频如果中断频率正好是计算值的两倍多半是把 TIM6/TIM7 的输入时钟按 42MHz 算而实际是 84MHz。反过来如果频率只有计算值的一半则实际系统时钟低于 168MHzPLL 配置没有生效。用示波器或逻辑分析仪测一个 GPIO 翻转是最直接的验证方法void TIM6_DAC_IRQHandler(void) { static volatile uint32_t cnt 0; TMR6_STA ~TMR_STA_UIF; cnt; if (cnt 100) { GPIO_Toggle(GPIOA, GPIO_PIN_0); cnt 0; } }配置成 10ms 中断一次时PA0 翻转周期应该是 2 秒。如果测到 1 秒计数时钟就是 168MHz 而不是 84MHz说明 APB1 分频或 PLL 参数不是预期值。这个办法比反复读寄存器更直接因为寄存器里的分频位只是配置实际生效还受硬件预分频器是否同步的影响。注意TIM6_DAC 向量在 ST 兼容工程里同时挂 DAC 中断如果 DAC 开启Handler 里的清标志逻辑需要额外判断 DAC 事件标志避免把定时器中断和 DAC 中断混在一起。6. 用 APM32F407 基本定时器做毫秒时基一个可直接复用的 tick 模块最后给一个我常用的移植手法把 TIM7 固化成毫秒 tickTIM6 留给业务定时。这样不会和 SysTick 抢资源也为以后跑 RTOS 留出 SysTick 给内核调度。static volatile uint32_t s_tick 0; void TIM7_IRQHandler(void) { if ((TMR7_STA TMR_STA_UIF) ! 0U) { TMR7_STA ~TMR_STA_UIF; s_tick; } } uint32_t apm32_get_tick(void) { return s_tick; } void apm32_delay_ms(uint32_t ms) { uint32_t start s_tick; while ((uint32_t)(s_tick - start) ms) { /* 非阻塞式等待依赖 TIM7 溢出计数 */ } }这段代码里的s_tick必须是volatile uint32_t否则编译器可能把while条件里的s_tick优化成只读一次。比较的写法用了两次uint32_t差值ms较小时可以直接判断(s_tick - start) ms这样即使s_tick发生了 49 天后的回绕也不会造成死循环。要让 TIM7 跑出 1ms tick沿用第 4 章参数表psc83、arr999系统时钟 168MHz 且 APB1 定时器时钟为 84MHz。初始化 TIM7 时把RCC-APB1ENR | (1U 5);打开总线时钟中断向量用TIM7_IRQn其余代码和 TIM6 完全一致。如果项目里已经用了 RTOS不建议再单独开 TIM7 做这种软延时。直接用内核提供的延时接口把睡眠期间的定时器功耗问题交给 RTOS tickless 处理。没有 RTOS 时APM32F407 基本定时器是最稳定的时基来源因为它不依赖 GPIO、不依赖 SysTick也不会因为关闭中断而完全停摆。编写中断服务函数时只做s_tick和清标志两步其他任务放到主循环里查询 tick 差值就能避免在中断里做耗时操作导致 tick 抖动。本文还有配套的精品资源点击获取
返回列表