Skip to content

Commit

Permalink
全面支持运行时设置时长
Browse files Browse the repository at this point in the history
  • Loading branch information
Silver-Fang committed Jul 5, 2021
1 parent c4ada2d commit f31708f
Show file tree
Hide file tree
Showing 12 changed files with 372 additions and 134 deletions.
58 changes: 44 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

充分利用开发板上所有的计时器。

音响、方波、延迟任务、定时重复,这些任务都需要应用开发板上的计时器才能完成。有时你甚至需要多个计时器同步运行,实现多线程任务。但是,当前Arduino社区并没有提供比较完善的计时器运行库。它们能够执行的任务模式非常有限,而且用户无法指定具体要使用哪个计时器。其结果就是,经常有一些使用计时器的库发生冲突,或者和用户自己的应用发生冲突。本项目旨在将计时器可能需要使用的所有功能在所有计时器上实现,最关键的是允许用户手动指定要是用的硬件计时器,避免冲突。

由于作者精力有限,目前本项目所有函数都还是模板,因此所有的参数都必须为编译期常量,暂不支持运行时动态设置。欢迎上GitHub主页贡献Pull request。

**由于作者精力有限,目前本项目所有函数,仅时长参数可以在运行时动态设置,其它参数必须为编译期常量,作为模板参数。后续有空会实现更多可以动态设置参数的API。当然更欢迎上GitHub主页贡献Pull request。**
# 硬件计时器
本库所有公开API的第一个模板参数,都是计时器的编号。不同计时器之间完全独立运行,互不干扰。用户必须在代码中手动指定要使用哪个硬件计时器来执行功能。不同CPU中的计时器数目和属性各不相同,此处以开发板上常用的ATMega2560系列CPU为例
本库所有公开API的第一个模板参数,都是计时器的编号。不同计时器之间完全独立运行,互不干扰。用户必须在代码中手动指定要使用哪个硬件计时器来执行功能。不同CPU中的计时器数目和属性各不相同,此处以Arduino Mega开发板上常用的ATMega2560系列CPU为例
## 计时器0
该计时器有8位,即2^8=256个计时状态,支持COMPA和COMPB中断,但不支持OVF中断,因为该中断被内置函数`millis()`占用了。本库考虑到这个情况,用COMPA和COMPB中断同样能实现所有的计时功能,因此该计时器仍然可用,但会付出一些微妙的性能代价。因此,您仍应避免使用该计时器,除非其它计时器都处于繁忙状态。
该计时器有8位,即2^8=256个计时状态,支持COMPA和COMPB中断,但不支持OVF中断,因为该中断被内置函数`millis();delay();micros();`占用了。本库考虑到这个情况,用COMPA和COMPB中断同样能实现所有的计时功能,因此该计时器仍然可用,但会付出一些微妙的性能代价。因此,您仍应避免使用该计时器,除非其它计时器都处于繁忙状态。
## 计时器1、3、4、5
这些计时器都具有16位,即65536个计时状态,因此比8位计时器更精确。COMPA、COMPB和OVF三个中断都为可用。绝大多数情况下,这些计时器是您的首选。
## 计时器2
Expand All @@ -18,15 +18,17 @@

再次重申,上述硬件说明仅适用于ATMega2560系列CPU,本库对该系列CPU也具有最好的支持。请确认您的开发板搭载的CPU型号及计时器硬件属性问题。此外,内置函数`analogWrite()``tone()`会在运行时动态地征用某些计时器,并且不会检测该计时器是否正在被本库使用;本库也无法检测用户指定的计时器是否正在被这些内置函数使用。因此如果同时使用这些内置函数,您将可能需要考虑潜在的冲突问题。

如果你使用的开发板并非ATMega2560系列的CPU,也可能可以使用本库。具体需要参考你的CPU数据表,看是否具有兼容的硬件计时器。

Make full use of all your hardware timers on your Arduino board.

The only library you can choose any hardware timer you like to use in your timing function. Tones, square waves, delayed tasks, timed repetitive tasks are provided as building blocks for your own sophisticated multi-timer tasks. My library hides hardware register details for you.

Currently all APIs are function templates, which means all arguments must be known as constants at compile-time. Non-template versions will be gradually added if I have more free time. However, you're welcomed to modify my code - not so difficult to understand if you are familiar with ATMega registers.
**Currently, for all APIs, only time length arguments can be specified at runtime. Other arguments are all template arguments, i.e., they must be known as constexprs at compiling time. I'll implement more runtime arguments if I have more free time. Pull requests are fully welcomed on my GitHub site.**
# Timers
All public APIs are under namespace TimersOneForAll, and require TimerCode as the first template argument. TimerCode indicates which hardware timer you want to use. Hardware timers vary by CPUs. For ATMega2560, there're 6 timers:
All public APIs are under namespace TimersOneForAll, and require TimerCode as the first template argument. TimerCode indicates which hardware timer you want to use. Hardware timers vary by CPUs. For ATMega2560 (Arduino Mega), there're 6 timers:
## Timer 0
This timer is 8-bit, which means it has 2^8=256 states. It can generate COMPA and COMPB interrupts, but not OVF, because it's occupied by Arduino builtin function `millis();delay();micros();`. This means this timer is the last one you want to use among all the timers. Only use it if you really have strong reasons.
This timer is 8-bit, which means it has 2^8=256 states. It can generate COMPA and COMPB interrupts, but not OVF, because it's occupied by Arduino builtin function `millis();delay();micros();`. This means that, though functionally usable (I specially workarounded this issue in my code), this timer is the last one you want to use among all the timers. Only use it if other timers are really busy.
## Timer 1, 3, 4, 5
These timers are all 16-bit, with 65536 states, which means that they're more accurate than 8-bit timers. COMPA, COMPB and OVF interrupts are all available. You may want to use these timers for most scenarios.
## Timer 2
Expand All @@ -36,38 +38,56 @@ This timer is also 8-bit, but different from timer 0 at 3 aspects:
- It's used by a famous timer library MsTimer2. You can't use this timer if you have MsTimer2 included.

Moreover, builtin `analogWrite()` and `tone()` may dynamically occupy any of the timers listed above. You'll have to handle potential conflicts if you want to use these builtins.

If you aren't using ATMega2560 CPU series, you may or may not be able to use this library. Refer to your datasheet to see if it has compatible hardware timer configurations.
# API参考 API Reference
```C++
//在指定的毫秒数后触发一个计时器中断,调用你的函数。
//Call your function with a timer interrupt after given milliseconds
template <uint8_t TimerCode,
uint16_t AfterMilliseconds,//If set to 0, DoTask will be called directly in this function call. Hardware timer won't be disturbed.
void (*DoTask)()>
void DoAfter()
void DoAfter();
//允许运行时动态设置毫秒数
//Specify milliseconds at runtime
template <uint8_t TimerCode, void (*DoTask)()>
void DoAfter(uint16_t AfterMilliseconds);

//每隔指定的毫秒数,无限重复调用你的函数。第一次调用也将在那个毫秒数之后发生。
//Repetitively and infinitely call your function with a timer interrupt for each IntervalMilliseconds. The first interrupt happens after IntervalMilliseconds, too.
template <uint8_t TimerCode, uint16_t IntervalMilliseconds, void (*DoTask)()>
void RepeatAfter()
void RepeatAfter();
//仅重复有限次数
//Repeat for only RepeatTimes
template <uint8_t TimerCode, uint16_t IntervalMilliseconds, void (*DoTask)(), uint32_t RepeatTimes>
void RepeatAfter()
template <uint8_t TimerCode, uint16_t IntervalMilliseconds, void (*DoTask)(), int32_t RepeatTimes>
void RepeatAfter();
//允许运行时动态设置毫秒数。重复次数不指定的话则为无限重复。
//Specify milliseconds at runtime
template <uint8_t TimerCode, void (*DoTask)(), int32_t RepeatTimes>
void RepeatAfter(uint16_t IntervalMilliseconds);

//将当前时刻设为0,计量经过的毫秒数。读取MillisecondsElapsed变量来获得经过的毫秒数。
//Set the time now as 0 and start to record time elapsed. Read MillisecondsElapsed variable to get the time elapsed.
template <uint8_t TimerCode>
void StartTiming()
void StartTiming();
//获取自上次调用StartTiming以来所经过的毫秒数。
//Get MillisecondsElapsed after the last call of StartTiming.
template <uint8_t TimerCode>
static volatile uint16_t MillisecondsElapsed;

//在指定引脚上无限播放指定频率的音调
//Play a tone of FrequencyHz on PinCode endlessly.
template <uint8_t TimerCode, uint8_t PinCode, uint16_t FrequencyHz>
void PlayTone()
void PlayTone();
//只播放限定的毫秒数
//Play for only given Milliseconds
template <uint8_t TimerCode, uint8_t PinCode, uint16_t FrequencyHz, uint16_t Milliseconds>
void PlayTone()
void PlayTone();
//允许运行时动态设置毫秒数。
//Specify milliseconds at runtime
template <uint8_t TimerCode, uint8_t PinCode, uint16_t FrequencyHz>
void PlayTone(uint16_t Milliseconds);

//在引脚上生成无限循环的方波。不同于音调,这里可以指定高电平和低电平的不同时长
//Generate an infinite sequence of square wave. The high level and low level can have different time length.
template <uint8_t TimerCode, uint8_t PinCode, uint16_t HighMilliseconds, uint16_t LowMilliseconds>
Expand All @@ -76,10 +96,20 @@ void SquareWave()
//Generate the square wave for only RepeatTimes cycles.
template <uint8_t TimerCode, uint8_t PinCode, uint16_t HighMilliseconds, uint16_t LowMilliseconds, int16_t RepeatTimes>
void SquareWave()
//允许运行时动态设置毫秒数。重复次数不指定的话则为无限重复。
//Specify milliseconds at runtime
template <uint8_t TimerCode, uint8_t PinCode, int16_t RepeatTimes>
void SquareWave(uint16_t HighMilliseconds, uint16_t LowMilliseconds)

//阻塞当前代码执行指定毫秒数
//Block current code from running for DelayMilliseconds
template <uint8_t TimerCode, uint16_t DelayMilliseconds>
void Delay()
//允许运行时动态设置毫秒数
//Specify milliseconds at runtime
template <uint8_t TimerCode>
void Delay(uint16_t DelayMilliseconds)

//取消指定给特定计时器的所有任务。其它计时器不受影响。
//Abort all tasks assigned to TimerCode. Other timers won't be affected.
template <uint8_t TimerCode>
Expand Down
5 changes: 5 additions & 0 deletions examples/LedAndBuzzerShow/LedAndBuzzerShow.ino
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ void setup()
{
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
Serial.begin(9600);
digitalWrite(LED, HIGH);
//设置计时器3在5秒后熄灭LED灯,但不阻断程序
DoAfter<3, 5000, LightDown>();

//设置4号计时器,每隔2秒,就用5号计时器生成2000㎐脉冲1秒,重复3次
RepeatAfter<4, 2000, PlayTone<5, Buzzer, 2000, 1000>, 3>();
//设置计时器1,将程序阻断7秒
Expand All @@ -19,6 +21,9 @@ void setup()
//设置计时器3在8秒后停止计时器1
DoAfter<3, 8000, ShutDown<1>>();
//观察到,LED灯明暗循环两次后,最终停在了亮状态,因为1号计时器尚未触发暗事件就被停止了
//亮1秒暗2秒的方波,重复5个循环
Delay<4, 8000>();
SquareWave<1, LED, 1000, 2000, 5>();
}
void LightDown()
{
Expand Down
20 changes: 20 additions & 0 deletions examples/RandomShining/RandomShining.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <TimersOneForAll.h>
//上传前在7号和8号引脚连接LED灯
constexpr uint8_t LED1 = 7;
constexpr uint8_t LED2 = 8;
void setup()
{
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
digitalWrite(LED1, HIGH);
//在8号引脚的LED灯上生成方波,高低电平时长动态随机生成
randomSeed(analogRead(6));
TimersOneForAll::SquareWave<3, LED2>(random(5000), random(5000));
}
void loop()
{
static bool CurrentState = HIGH;
//在7号引脚的LED灯上生成即时随机的闪烁,每次都重新随机
TimersOneForAll::Delay<1>(random(10000));
digitalWrite(LED1, CurrentState = CurrentState ? LOW : HIGH);
}
6 changes: 3 additions & 3 deletions library.properties
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name=TimersOneForAll
version=1.0.0
version=1.1.1
author=EbolaChan <Leenrung@outlook.com>
maintainer=EbolaChan <Leenrung@outlook.com>
sentence=充分利用所有的硬件计时器。Make full use of all your hardware timers on your Arduino board.
paragraph=唯一可以自由选择硬件计时器的完全库。The only library you can choose any hardware timer you like to use in your timing function. Tones, square waves, delayed tasks, timed repetitive tasks are provided as building blocks for your own sophisticated multi-timer tasks. My library hides hardware register details for you.
sentence=Make full use of all your hardware timers on your Arduino board. 充分利用你开发板上所有的硬件计时器
paragraph=Delayed task, repetitive delayed task, tones, square waves, timing by milliseconds. Hardware interrupt that can break into any running code accurately at the time you set. Specify which hardware timer to use, all with 1/16 microsecond accuracy. Allocate them to different tasks so that they run simultaneously without conflicts. Currently only support timing for less than 1 minute. 延迟任务,重复任务,音调,方波,毫秒秒表,可以自由指定互不冲突的独立硬件计时器,硬件中断,事件驱动。
category=Timing
url=https://github.com/Silver-Fang/TimersOneForAll
architectures=avr
Expand Down
13 changes: 13 additions & 0 deletions src/Internal/Debugger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <Arduino.h>
void SerialWrite(uint8_t Value)
{
Serial.write(Value);
}
void SerialWrite(uint16_t Value)
{
Serial.write((uint8_t *)&Value, 2);
}
void SerialWrite(uint32_t Value)
{
Serial.write((uint8_t *)&Value, 4);
}
11 changes: 10 additions & 1 deletion src/Internal/Delay.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ namespace TimersOneForAll
template <uint8_t TimerCode, uint16_t DelayMilliseconds>
void Delay()
{
Internal::Delaying<TimerCode> = true;
DoAfter<TimerCode, DelayMilliseconds, Internal::Undelay<TimerCode>>();
Internal::Delaying<TimerCode> = true;
while (Internal::Delaying<TimerCode>)
;
}
//阻塞当前代码执行指定毫秒数
template <uint8_t TimerCode>
void Delay(uint16_t DelayMilliseconds)
{
DoAfter<TimerCode, Internal::Undelay<TimerCode>>(DelayMilliseconds);
Internal::Delaying<TimerCode> = true;
while (Internal::Delaying<TimerCode>)
;
}
Expand Down
9 changes: 8 additions & 1 deletion src/Internal/DoAfter.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#pragma once
#include "Kernel.h"
namespace TimersOneForAll
{
{
//在指定毫秒数后执行任务
template <uint8_t TimerCode, uint16_t AfterMilliseconds, void (*DoTask)()>
void DoAfter()
{
constexpr Internal::TimerSetting TS = Internal::GetTimerSetting(TimerCode, AfterMilliseconds);
Internal::SLRepeaterSet<TimerCode, TS.TCNT, TS.PrescalerBits, DoTask, 1>();
}
//在指定毫秒数后执行任务
template <uint8_t TimerCode, void (*DoTask)()>
void DoAfter(uint16_t AfterMilliseconds)
{
Internal::TimerSetting TS = Internal::GetTimerSetting<TimerCode>(AfterMilliseconds);
Internal::SLRepeaterSet<TimerCode, DoTask, 1>(TS.TCNT, TS.PrescalerBits);
}
}
Loading

0 comments on commit f31708f

Please sign in to comment.