/** 
 *  TaskScheduler测试程序 - 使用任务ID和看门狗定时器来识别挂起的任务
 *  此程序仅在AVR板上运行
 *  测试用例：
 *    看门狗定时器设置为2秒（中断+复位）
 *    心跳任务（重置看门狗定时器）以500毫秒间隔调度
 *    多个任务每秒运行一次并"掷骰子"0..19。如果为5，任务将进入无限循环
 *    当任务进入无限循环后，设备应在2秒内复位
 *    在设备复位前，任务ID和控制点号被保存到EEPROM中，重启后显示
 *    在实际应用中，设备可能会选择不激活之前失败的某些任务（例如失败的传感器）
 */

#define _TASK_SLEEP_ON_IDLE_RUN  // 启用空闲时睡眠功能
#define _TASK_WDT_IDS  // 启用看门狗定时器ID功能
#include <TaskScheduler.h>  // 包含任务调度器库

#include <EEPROM.h>  // 包含EEPROM库
#include <avr/wdt.h>  // 包含AVR看门狗定时器库

Scheduler ts;  // 创建调度器对象

// 回调方法原型
void TaskCB(); 
void HB(); bool HBOn(); void HBOff();

// 三个模拟意外无限循环的任务
Task tTask1(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);  // 任务1：每秒运行一次，无限期运行
Task tTask2(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);  // 任务2：每秒运行一次，无限期运行
Task tTask3(TASK_SECOND, TASK_FOREVER, &TaskCB, &ts, true);  // 任务3：每秒运行一次，无限期运行

// 心跳任务 - 定期重置看门狗定时器
// 在使能时启动WDT，在禁用时停用WDT
Task tHB(500, TASK_FOREVER, &HB, &ts, false, &HBOn, &HBOff);  // 心跳任务：每500毫秒运行一次，无限期运行，初始禁用，有使能和禁用回调

/**
 * 模拟任务回调函数
 *   打印任务ID并随机在两个地方"挂起"。
 *   在可能挂起的部分之前，控制点被存储在任务上，
 *   使此信息对WDT中断处理程序可用
 */
void TaskCB() {
  Task& T = ts.currentTask();  // 获取当前任务
  
  Serial.print("Task #:");  // 打印任务编号
  Serial.print(T.getId());  // 打印任务ID
  Serial.print(" current iteration = ");  // 打印当前迭代
  Serial.println(T.getRunCounter());  // 打印运行计数器

// 如果0到19之间的随机数为5（5%概率），则挂起
  T.setControlPoint(10);  // 设置控制点10
  if (random(20) == 5) for(;;);  // 进入无限循环
  
// 如果0到99之间的随机数大于95（5%概率），则挂起
  T.setControlPoint(95);  // 设置控制点95
  if (random(100) > 94) for(;;);  // 进入无限循环
}

/**
 * 此使能方法设置WDT
 * 用于2秒后的中断和复位
 */
bool HBOn() {
  
  //禁用中断
  cli();
  //重置看门狗
  wdt_reset();
  //设置WDT中断
  WDTCSR = (1<<WDCE)|(1<<WDE);
  //启动看门狗定时器，带延迟预分频器
  WDTCSR = (1<<WDIE)|(1<<WDE)|(WDTO_2S & 0x2F);
//  WDTCSR = (1<<WDIE)|(WDTO_2S & 0x2F);  // 仅中断，不复位
  //启用全局中断
  sei();
}

/**
 * 此禁用方法禁用WDT
 */
void HBOff() {
  wdt_disable();  // 禁用看门狗定时器
}

/**
 * 这是WDT的定期重置
 */
void HB() {
  wdt_reset();  // 重置看门狗定时器

}

/**
 * 看门狗超时中断服务程序
 * 
 */
ISR(WDT_vect)
{
  Task& T = ts.currentTask();  // 获取当前任务

  digitalWrite(13, HIGH);  // 点亮LED
  EEPROM.write(0, (byte)T.getId());  // 将任务ID写入EEPROM地址0
  EEPROM.write(1, (byte)T.getControlPoint());  // 将控制点写入EEPROM地址1
  digitalWrite(13, LOW);  // 熄灭LED
}

/**
 * 标准Arduino设置程序
 */
void setup() {
  
  Serial.begin(115200);  // 初始化串口通信，波特率115200

  randomSeed(analogRead(0)+analogRead(5));  // 使用模拟引脚0和5的值初始化随机数种子
  
  pinMode(13, OUTPUT);  // 设置引脚13为输出模式
  digitalWrite(13, LOW);  // 设置引脚13为低电平
  
 
  Serial.println("WDT heartbeat test");  // 打印WDT心跳测试信息
  Serial.print("Last task before reset="); Serial.println(EEPROM.read(0));  // 打印复位前的最后一个任务
  Serial.print("Last control point before reset="); Serial.println(EEPROM.read(1));  // 打印复位前的最后一个控制点
  
  delay(2000);  // 延时2秒

  tHB.enableDelayed();  // 延迟启用心跳任务
}

/**
 * loop()函数中没有太多内容
 */
void loop() {
  ts.execute();  // 执行调度器，运行所有已安排的任务
}