|
| 1 | +--- |
| 2 | +title: 硬件定时器 |
| 3 | +icon: fa-solid fa-clock |
| 4 | +--- |
| 5 | + |
| 6 | +`HardwareTimer`库旨在提供对部分AirMCU硬件定时器功能的访问(如果需要其他功能,可以通过HAL/LL访问它们)。 |
| 7 | + |
| 8 | +使用该库假设您对 AirMCU 硬件定时器架构有一些基本了解。首先提醒一下,所有定时器并不等同,也不支持相同的功能。请参阅您的 MCU 的参考手册。 |
| 9 | + |
| 10 | +一些例子: |
| 11 | +1. `TIM6` 和 `TIM7` 没有输出的引脚,这就是为什么在可用时,它们被用于实现 `Tone` 和 `Servo`。 |
| 12 | +2. 有些定时器有多达 4 个输出通道,其中有 4 个互补通道,而其他定时器则没有互补通道,或者只有 1 或 2 个通道... |
| 13 | + |
| 14 | +每个定时器可以提供多个通道,但是重要的是要理解同一定时器的所有通道共享相同的计数器,因此具有相同的周期/频率。 |
| 15 | + |
| 16 | +::: warning |
| 17 | +出于通用性目的,`HardwareTimer` 库使用所有定时器,如 16 位定时器(即使有些定时器的位数更多)。 |
| 18 | +::: |
| 19 | + |
| 20 | +## API接口 |
| 21 | + |
| 22 | +```cpp |
| 23 | + void pause(void); // Pause counter and all output channels |
| 24 | + void pauseChannel(uint32_t channel); // Timer is still running but channel (output and interrupt) is disabled |
| 25 | + void resume(void); // Resume counter and all output channels |
| 26 | + void resumeChannel(uint32_t channel); // Resume only one channel |
| 27 | + |
| 28 | + void setPrescaleFactor(uint32_t prescaler); // set prescaler register (which is factor value - 1) |
| 29 | + uint32_t getPrescaleFactor(); |
| 30 | + |
| 31 | + void setOverflow(uint32_t val, TimerFormat_t format = TICK_FORMAT); // set AutoReload register depending on format provided |
| 32 | + uint32_t getOverflow(TimerFormat_t format = TICK_FORMAT); // return overflow depending on format provided |
| 33 | + |
| 34 | + void setPWM(uint32_t channel, PinName pin, uint32_t frequency, uint32_t dutycycle, callback_function_t PeriodCallback = nullptr, callback_function_t CompareCallback = nullptr); // Set all in one command freq in HZ, Duty in percentage. Including both interrup. |
| 35 | + void setPWM(uint32_t channel, uint32_t pin, uint32_t frequency, uint32_t dutycycle, callback_function_t PeriodCallback = nullptr, callback_function_t CompareCallback = nullptr); |
| 36 | + |
| 37 | + void setCount(uint32_t val, TimerFormat_t format = TICK_FORMAT); // set timer counter to value 'val' depending on format provided |
| 38 | + uint32_t getCount(TimerFormat_t format = TICK_FORMAT); // return current counter value of timer depending on format provided |
| 39 | + |
| 40 | + void setMode(uint32_t channel, TimerModes_t mode, PinName pin = NC); // Configure timer channel with specified mode on specified pin if available |
| 41 | + void setMode(uint32_t channel, TimerModes_t mode, uint32_t pin); |
| 42 | + |
| 43 | + TimerModes_t getMode(uint32_t channel); // Retrieve configured mode |
| 44 | + |
| 45 | + void setPreloadEnable(bool value); // Configure overflow preload enable setting |
| 46 | + |
| 47 | + uint32_t getCaptureCompare(uint32_t channel, TimerCompareFormat_t format = TICK_COMPARE_FORMAT); // return Capture/Compare register value of specified channel depending on format provided |
| 48 | + void setCaptureCompare(uint32_t channel, uint32_t compare, TimerCompareFormat_t format = TICK_COMPARE_FORMAT); // set Compare register value of specified channel depending on format provided |
| 49 | + |
| 50 | + void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority); // set interrupt priority |
| 51 | + |
| 52 | + //Add interrupt to period update |
| 53 | + void attachInterrupt(callback_function_t callback); // Attach interrupt callback which will be called upon update event (timer rollover) |
| 54 | + void detachInterrupt(); // remove interrupt callback which was attached to update event |
| 55 | + bool hasInterrupt(); //returns true if a timer rollover interrupt has already been set |
| 56 | + //Add interrupt to capture/compare channel |
| 57 | + void attachInterrupt(uint32_t channel, callback_function_t callback); // Attach interrupt callback which will be called upon compare match event of specified channel |
| 58 | + void detachInterrupt(uint32_t channel); // remove interrupt callback which was attached to compare match event of specified channel |
| 59 | + bool hasInterrupt(uint32_t channel); //returns true if an interrupt has already been set on the channel compare match |
| 60 | + void timerHandleDeinit(); // Timer deinitialization |
| 61 | + |
| 62 | + // Refresh() is usefull while timer is running after some registers update |
| 63 | + void refresh(void); // Generate update event to force all registers (Autoreload, prescaler, compare) to be taken into account |
| 64 | + |
| 65 | + uint32_t getTimerClkFreq(); // return timer clock frequency in Hz. |
| 66 | + |
| 67 | + static void captureCompareCallback(TIM_HandleTypeDef *htim); // Generic Caputre and Compare callback which will call user callback |
| 68 | + static void updateCallback(TIM_HandleTypeDef *htim); // Generic Update (rollover) callback which will call user callback |
| 69 | + |
| 70 | + // The following function(s) are available for more advanced timer options |
| 71 | + TIM_HandleTypeDef *getHandle(); // return the handle address for HAL related configuration |
| 72 | + int getChannel(uint32_t channel); |
| 73 | + int getLLChannel(uint32_t channel); |
| 74 | + int getIT(uint32_t channel); |
| 75 | + int getAssociatedChannel(uint32_t channel); |
| 76 | +#if defined(TIM_CCER_CC1NE) |
| 77 | + bool isComplementaryChannel[TIMER_CHANNELS]; |
| 78 | +#endif |
| 79 | +``` |
| 80 | +
|
| 81 | +## 使用方式 |
| 82 | +
|
| 83 | +1. `HardwareTimer` 是一个 C++ 类,要做的第一件事是以 `TIM` 实例作为参数实例化一个对象。 |
| 84 | +
|
| 85 | +::: note |
| 86 | +有些实例由 Servo、Tone 和 SoftSerial 使用(请参阅 TIMER_SERVO、TIMER_TONE 和 TIMER_SERIAL),但仅在使用时使用。只要确保与您自己的使用没有冲突即可。 |
| 87 | +::: |
| 88 | +
|
| 89 | +```cpp |
| 90 | +HardwareTimer *MyTim = new HardwareTimer(TIM3); // TIM3 is MCU hardware peripheral instance, its definition is provided in CMSIS |
| 91 | +``` |
| 92 | + |
| 93 | +2. 然后就可以配置通道的模式。 |
| 94 | + |
| 95 | +::: note |
| 96 | +无需配置引脚模式(输出/输入/AlternateFunction),它将由 HardwareTimer 库自动完成。 |
| 97 | +::: |
| 98 | + |
| 99 | +::: note |
| 100 | +通道范围[1..4],但并非所有定时器都支持4个通道。 |
| 101 | +::: |
| 102 | + |
| 103 | +```cpp |
| 104 | +MyTim->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin); |
| 105 | +``` |
| 106 | +
|
| 107 | +支持模式有: |
| 108 | +
|
| 109 | +```cpp |
| 110 | +typedef enum { |
| 111 | + TIMER_DISABLED, // == TIM_OCMODE_TIMING no output, useful for only-interrupt |
| 112 | + // Output Compare |
| 113 | + TIMER_OUTPUT_COMPARE, // == Obsolete, use TIMER_DISABLED instead. Kept for compatibility reason |
| 114 | + TIMER_OUTPUT_COMPARE_ACTIVE, // == TIM_OCMODE_ACTIVE pin is set high when counter == channel compare |
| 115 | + TIMER_OUTPUT_COMPARE_INACTIVE, // == TIM_OCMODE_INACTIVE pin is set low when counter == channel compare |
| 116 | + TIMER_OUTPUT_COMPARE_TOGGLE, // == TIM_OCMODE_TOGGLE pin toggles when counter == channel compare |
| 117 | + TIMER_OUTPUT_COMPARE_PWM1, // == TIM_OCMODE_PWM1 pin high when counter < channel compare, low otherwise |
| 118 | + TIMER_OUTPUT_COMPARE_PWM2, // == TIM_OCMODE_PWM2 pin low when counter < channel compare, high otherwise |
| 119 | + TIMER_OUTPUT_COMPARE_FORCED_ACTIVE, // == TIM_OCMODE_FORCED_ACTIVE pin always high |
| 120 | + TIMER_OUTPUT_COMPARE_FORCED_INACTIVE, // == TIM_OCMODE_FORCED_INACTIVE pin always low |
| 121 | +
|
| 122 | + //Input capture |
| 123 | + TIMER_INPUT_CAPTURE_RISING, // == TIM_INPUTCHANNELPOLARITY_RISING |
| 124 | + TIMER_INPUT_CAPTURE_FALLING, // == TIM_INPUTCHANNELPOLARITY_FALLING |
| 125 | + TIMER_INPUT_CAPTURE_BOTHEDGE, // == TIM_INPUTCHANNELPOLARITY_BOTHEDGE |
| 126 | +
|
| 127 | + // Used 2 channels for a single pin. One channel in TIM_INPUTCHANNELPOLARITY_RISING another channel in TIM_INPUTCHANNELPOLARITY_FALLING. |
| 128 | + // Channels must be used by pair: CH1 with CH2, or CH3 with CH4 |
| 129 | + // This mode is very useful for Frequency and Dutycycle measurement |
| 130 | + TIMER_INPUT_FREQ_DUTY_MEASUREMENT, |
| 131 | +
|
| 132 | + TIMER_NOT_USED = 0xFFFF // This must be the last item of this enum |
| 133 | +} TimerModes_t; |
| 134 | +``` |
| 135 | + |
| 136 | +3. 然后就可以配置`PrescalerFactor`。定时器时钟将除以该因子(如果定时器时钟为 10Khz,预分频器因子为 2,则定时器将以 5kHz 计数)。 |
| 137 | + |
| 138 | +:::note |
| 139 | +将方法 `setOverflow` 与 `format == MICROSEC_FORMAT` 或 `format == HERTZ_FORMAT` 一起使用时,预分频器的配置是自动的。 |
| 140 | +::: |
| 141 | + |
| 142 | +::: note |
| 143 | +预分频器用于定时器计数器,因此对所有通道都是通用的。 |
| 144 | +::: |
| 145 | + |
| 146 | +::: note |
| 147 | +预分频器因子范围:[1..0x10000](硬件寄存器范围为[0..0xFFFF])。 |
| 148 | +::: |
| 149 | + |
| 150 | +```cpp |
| 151 | +MyTim->setPrescaleFactor(8); |
| 152 | +``` |
| 153 | +
|
| 154 | +4. 然后就可以配置溢出(也称为翻转或更新)。 |
| 155 | +
|
| 156 | +对于输出,它对应于周期或频率。 |
| 157 | +
|
| 158 | +对于输入捕获,建议使用最大值:0x10000,以避免在捕获发生之前发生翻转。 |
| 159 | +
|
| 160 | +::: note |
| 161 | +
|
| 162 | +将方法 setOverflow 与 format == MICROSEC_FORMAT 或 format == HERTZ_FORMAT 一起使用时,预分频器的配置是自动的。 |
| 163 | +
|
| 164 | +溢出是所有通道共有的。 |
| 165 | +
|
| 166 | +溢出范围:[1..0x10000](硬件寄存器的范围为[0..0xFFFF])。 |
| 167 | +
|
| 168 | +::: |
| 169 | +
|
| 170 | +```cpp |
| 171 | +MyTim->setOverflow(10000); // Default format is TICK_FORMAT. Rollover will occurs when timer counter counts 10000 ticks (it reach it count from 0 to 9999) |
| 172 | +MyTim->setOverflow(10000, TICK_FORMAT); |
| 173 | +MyTim->setOverflow(10000, MICROSEC_FORMAT); // 10000 microseconds |
| 174 | +MyTim->setOverflow(10000, HERTZ_FORMAT); // 10 kHz |
| 175 | +``` |
| 176 | + |
| 177 | +5. 然后可以配置 CaptureCompare(通道特定的 CaptureCompare 寄存器)。 |
| 178 | + |
| 179 | +::: note |
| 180 | +CaptureCompare 仅适用于一个通道。 |
| 181 | + |
| 182 | +CaptureCompare 范围:[0.. 0xFFFF] |
| 183 | +::: |
| 184 | + |
| 185 | +```cpp |
| 186 | +MyTim->setCaptureCompare(channel, 50); // Default format is TICK_FORMAT. 50 ticks |
| 187 | +MyTim->setCaptureCompare(channel, 50, TICK_FORMAT) |
| 188 | +MyTim->setCaptureCompare(channel, 50, MICROSEC_COMPARE_FORMAT); // 50 microseconds between counter resetand compare |
| 189 | +MyTim->setCaptureCompare(channel, 50, HERTZ_COMPARE_FORMAT); // 50 Hertz -> 1/50 seconds between counterreset and compare |
| 190 | +MyTim->setCaptureCompare(channel, 50, RESOLUTION_8B_COMPARE_FORMAT); // used for Dutycycle: [0.. 255] |
| 191 | +MyTim->setCaptureCompare(channel, 50, RESOLUTION_12B_COMPARE_FORMAT); // used for Dutycycle: [0.. 4095] |
| 192 | +``` |
| 193 | +
|
| 194 | +可以在更新中断(翻转)和/或捕获/比较中断上附加用户回调。如果未指定通道,则用户回调将附加到更新事件。请注意,更新中断标志 (UIF) 在更新事件发生并生成中断时设置,并在执行用户回调之前由 HAL 驱动程序自动清除。用户回调无需显式清除 UIF。 |
| 195 | +
|
| 196 | +```cpp |
| 197 | +MyTim->attachInterrupt(Update_IT_callback); // Userdefined call back. See 'Examples' chapter to see how to use callback with or without parameter |
| 198 | +MyTim->attachInterrupt(channel, Compare_IT_callback); // Userdefined call back. See 'Examples' chapter to see how to use callback with or without parameter |
| 199 | +``` |
| 200 | + |
| 201 | +6. 现在可以启动定时器了 |
| 202 | + |
| 203 | +::: note |
| 204 | +同一定时器的所有通道同时启动(因为每个定时器只有 1 个计数器)。 |
| 205 | +::: |
| 206 | + |
| 207 | +```cpp |
| 208 | +MyTim->resume(); |
| 209 | +``` |
| 210 | + |
| 211 | +计时器可以暂停然后恢复 |
| 212 | + |
| 213 | +```cpp |
| 214 | +MyTim->pause(); |
| 215 | +// ... |
| 216 | +MyTim->resume(); |
| 217 | +``` |
| 218 | + |
| 219 | +以下是完整 PWM 配置的示例: |
| 220 | + |
| 221 | +```cpp |
| 222 | +MyTim->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin); |
| 223 | +// MyTim->setPrescaleFactor(8); // Due to setOverflow with MICROSEC_FORMAT, prescaler will be computedautomatically based on timer input clock |
| 224 | +MyTim->setOverflow(100000, MICROSEC_FORMAT); // 10000 microseconds = 10 milliseconds |
| 225 | +MyTim->setCaptureCompare(channel, 50, PERCENT_COMPARE_FORMAT); // 50% |
| 226 | +MyTim->attachInterrupt(Update_IT_callback); |
| 227 | +MyTim->attachInterrupt(channel, Compare_IT_callback); |
| 228 | +MyTim->resume(); |
| 229 | +``` |
| 230 | +
|
| 231 | +为了简化基本 PWM 配置,提供了专用的一体化 API。溢出/频率以赫兹为单位,占空比以百分比为单位。 |
| 232 | +
|
| 233 | +```cpp |
| 234 | +MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call |
| 235 | +MyTim->setPWM(channel, pin, 5, 10); // 5 Hertz, 10% dutycycle |
| 236 | +``` |
| 237 | + |
| 238 | +一些额外的 API 允许检索配置: |
| 239 | + |
| 240 | +```cpp |
| 241 | +getPrescaleFactor(); |
| 242 | +getOverflow(); |
| 243 | +getCaptureCompare(); // In InputCapture mode, this method doesn't retrieve configuration but retrieve thecaptured counter value |
| 244 | +getCount(); |
| 245 | +``` |
| 246 | + |
| 247 | +另外,要使用中断回调: |
| 248 | + |
| 249 | +```cpp |
| 250 | +detachInterrupt() |
| 251 | +``` |
| 252 | + |
| 253 | +::: note |
| 254 | +一旦计时器启动并启用回调,您可以通过 `detachInterrupt` 和 `attachInterrupt` 自由禁用和启用回调,次数不限。但是,如果第一个 resume (= 计时器启动)在调用 `attachInterrupt` 之前完成,则 `HardwareTimer` 将无法稍后附加中断(出于性能原因,计时器将启动禁用中断) |
| 255 | +::: |
| 256 | + |
| 257 | +如果在定时器运行时分离和附加中断,您还可以通过该方法知道是否已经附加了回调(无需在外部跟踪它) |
| 258 | + |
| 259 | +```cpp |
| 260 | +hasInterrupt() |
| 261 | +``` |
0 commit comments