/********************************************************
   20W加热块PID 模拟
   读取模拟输入口A0控制PWM输出口P3
 ********************************************************/
//  模拟PID 驱动的 20W 加热器块。
//  电位计改变设定点,加热器将随设定温度变换
//  Based on
//  Wokwi https://wokwi.com/projects/357374218559137793
//  Wokwi https://wokwi.com/projects/356437164264235009

#include "PID_v1.h" // https://github.com/br3ttb/Arduino-PID-Library

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
double Kp = 17, Ki = 0.3, Kd = 2; // works reasonably with sim heater block 
//double Kp = 255, Ki = .0, Kd = 0; // works reasonably with sim heater block 
//double Kp = 2, Ki = 5, Kd = 1; // commonly used defaults
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, P_ON_E, DIRECT);//创建一个名为 myPID 的PID控制器对象

//const int PWM_PIN = 3;  // UNO PWM pin
const int INPUT_PIN = -1; // 输入模拟引脚(模拟时设置为 <0)
const int SETPOINT_PIN = A1;   // 温度设定点电位计的模拟引脚
//const int SETPOINT_INDICATOR = 6; // 温度设定点的 PWM 引脚
//const int INPUT_INDICATOR = 5; //传感器温度输入

void setup()
{
  Serial.begin(115200);
  Serial.println(__FILE__);
  Setup_heating();
  //myPID.SetOutputLimits(-4, 255);
  //if (SETPOINT_INDICATOR >= 0) pinMode(SETPOINT_INDICATOR, OUTPUT);
  //if (INPUT_INDICATOR >= 0) pinMode(INPUT_INDICATOR, OUTPUT);
  //Setpoint = 0;
  //开启PID模式
  //myPID.SetMode(AUTOMATIC);
  //Input = simPlant(0.0,1.0); // 采用模拟加热
  /*
  if(INPUT_PIN>0){
    Input = analogRead(INPUT_PIN);//不触发
  }else{
    Input = simPlant(0.0,1.0); // 采用模拟加热
  }
  */
  Serial.println("Setpoint Input Output Watts");
}

void loop()
{
  PIDexecute();
  /*
  // 从 INPUT_PIN 或模拟加热块收集输入信息
  float heaterWatts = (int)Output * 20.0 / 255; // 20W加热块,按比例输出0~20W的功率
  float blockTemp = simPlant(heaterWatts,Output>0?1.0:1-Output); // 模拟加热
  Input = blockTemp;   // 读取来自模拟加热块的温度
  
  if (INPUT_PIN > 0 ) {// INPUT_PIN = -1不触发第一个条件
    Input = analogRead(INPUT_PIN);
  } else {
    float blockTemp = simPlant(heaterWatts,Output>0?1.0:1-Output); // 模拟加热
    Input = blockTemp;   // 读取来自模拟加热块的温度
  }
  
  if (myPID.Compute())//判断是否需要进行PID计算,返回bool值;ture则执行if内语句
  {
    //analogWrite(PWM_PIN, (int)Output);//PWM输出引脚输出功率(0-255范围)

    Setpoint = analogRead(SETPOINT_PIN) / 4; // 从电位计读取设定点
    //if (INPUT_INDICATOR >= 0) analogWrite(INPUT_INDICATOR, Input);
    //if (SETPOINT_INDICATOR >= 0) analogWrite(SETPOINT_INDICATOR, Setpoint);
  }
  pidReport();
  */
}

void pidReport(void)
{
  static uint32_t last = 0;
  const int interval = 1000;
  if (millis() - last > interval) {
    last += interval;
    //    Serial.print(millis()/1000.0);
    Serial.print("SP:");//目标温度设置点 /////////绿线 目标温度
    Serial.print(Setpoint);
    Serial.print(" PV:");//温度输入大小///////// 橙线 传感器实测温度
    Serial.print(Input);
    //Serial.print(" CV:");//PID输出大小 桃红线
    //Serial.print(Output);
    //Serial.print(" Int:");//积分结果 天蓝线
    //Serial.print(myPID.GetIntegral());
    Serial.print(' ');
    Serial.println();
  }
}

float simPlant(float Q,float hfactor) { // 以W (or J/s)的热量输入
  // 模拟一个1x1x2cm采用被动散热内含加热头的铝块
  // float C = 237; // W/mK 铝的热导系数(默认)
  float h = 5 *hfactor ; // W/m2K 铝块的被动热对流系数(默认)
  float Cps = 0.89; // J/g°C
  float area = 1e-4; // m2 对流面积
  float mass = 10 ; // g 铝块质量
  float Tamb = 25; // °C 环境温度
  static float T = Tamb; // °C
  static uint32_t last = 0;
  uint32_t interval = 100; // ms

  if (millis() - last >= interval) {
    last += interval;
    // 0 维传热
    T = T + Q * interval / 1000 / mass / Cps - (T - Tamb) * area * h;
  }
  return T;
}
void Setup_heating(){
  Setpoint = 0;
  myPID.SetMode(AUTOMATIC);
  Input = simPlant(0.0,1.0); // 采用模拟加热
}

void PIDexecute(){
   // 从 INPUT_PIN 或模拟加热块收集输入信息
  float heaterWatts = (int)Output * 20.0 / 255; // 20W加热块,按比例输出0~20W的功率
  float blockTemp = simPlant(heaterWatts,Output>0?1.0:1-Output); // 模拟加热
  Input = blockTemp;   // 读取来自模拟加热块的温度
  if (myPID.Compute())//判断是否需要进行PID计算,返回bool值;ture则执行if内语句
  {
    Setpoint = analogRead(SETPOINT_PIN) / 4; // 从电位计读取设定点
  }
  pidReport();
}
255° Setpoint 0°