commit 20ba610b729a676318951727a3f80b8d02de0ddc Author: Tom Date: Sat Dec 10 12:11:19 2022 +0100 initial working build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bf6b3f4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/TFT_ILI9163C"] + path = lib/TFT_ILI9163C + url = https://github.com/sumotoy/TFT_ILI9163C.git diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/Arduino-PID-Library/PID_v1.cpp b/lib/Arduino-PID-Library/PID_v1.cpp new file mode 100644 index 0000000..cb6637c --- /dev/null +++ b/lib/Arduino-PID-Library/PID_v1.cpp @@ -0,0 +1,224 @@ +/********************************************************************************************** + * Arduino PID Library - Version 1.2.1 + * by Brett Beauregard brettbeauregard.com + * + * This Library is licensed under the MIT License + **********************************************************************************************/ + +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include + +/*Constructor (...)********************************************************* + * The parameters specified here are those for for which we can't set up + * reliable defaults, so we need to have the user set them. + ***************************************************************************/ +PID::PID(double* Input, double* Output, double* Setpoint, + double Kp, double Ki, double Kd, int POn, int ControllerDirection) +{ + myOutput = Output; + myInput = Input; + mySetpoint = Setpoint; + inAuto = false; + + PID::SetOutputLimits(0, 255); //default output limit corresponds to + //the arduino pwm limits + + SampleTime = 100; //default Controller Sample Time is 0.1 seconds + + PID::SetControllerDirection(ControllerDirection); + PID::SetTunings(Kp, Ki, Kd, POn); + + lastTime = millis()-SampleTime; +} + +/*Constructor (...)********************************************************* + * To allow backwards compatability for v1.1, or for people that just want + * to use Proportional on Error without explicitly saying so + ***************************************************************************/ + +PID::PID(double* Input, double* Output, double* Setpoint, + double Kp, double Ki, double Kd, int ControllerDirection) + :PID::PID(Input, Output, Setpoint, Kp, Ki, Kd, P_ON_E, ControllerDirection) +{ + +} + + +/* Compute() ********************************************************************** + * This, as they say, is where the magic happens. this function should be called + * every time "void loop()" executes. the function will decide for itself whether a new + * pid Output needs to be computed. returns true when the output is computed, + * false when nothing has been done. + **********************************************************************************/ +bool PID::Compute() +{ + if(!inAuto) return false; + unsigned long now = millis(); + unsigned long timeChange = (now - lastTime); + if(timeChange>=SampleTime) + { + /*Compute all the working error variables*/ + double input = *myInput; + double error = *mySetpoint - input; + double dInput = (input - lastInput); + outputSum+= (ki * error); + + /*Add Proportional on Measurement, if P_ON_M is specified*/ + if(!pOnE) outputSum-= kp * dInput; + + if(outputSum > outMax) outputSum= outMax; + else if(outputSum < outMin) outputSum= outMin; + + /*Add Proportional on Error, if P_ON_E is specified*/ + double output; + if(pOnE) output = kp * error; + else output = 0; + + /*Compute Rest of PID Output*/ + output += outputSum - kd * dInput; + + if(output > outMax) output = outMax; + else if(output < outMin) output = outMin; + *myOutput = output; + + /*Remember some variables for next time*/ + lastInput = input; + lastTime = now; + return true; + } + else return false; +} + +/* SetTunings(...)************************************************************* + * This function allows the controller's dynamic performance to be adjusted. + * it's called automatically from the constructor, but tunings can also + * be adjusted on the fly during normal operation + ******************************************************************************/ +void PID::SetTunings(double Kp, double Ki, double Kd, int POn) +{ + if (Kp<0 || Ki<0 || Kd<0) return; + + pOn = POn; + pOnE = POn == P_ON_E; + + dispKp = Kp; dispKi = Ki; dispKd = Kd; + + double SampleTimeInSec = ((double)SampleTime)/1000; + kp = Kp; + ki = Ki * SampleTimeInSec; + kd = Kd / SampleTimeInSec; + + if(controllerDirection ==REVERSE) + { + kp = (0 - kp); + ki = (0 - ki); + kd = (0 - kd); + } +} + +/* SetTunings(...)************************************************************* + * Set Tunings using the last-rembered POn setting + ******************************************************************************/ +void PID::SetTunings(double Kp, double Ki, double Kd){ + SetTunings(Kp, Ki, Kd, pOn); +} + +/* SetSampleTime(...) ********************************************************* + * sets the period, in Milliseconds, at which the calculation is performed + ******************************************************************************/ +void PID::SetSampleTime(int NewSampleTime) +{ + if (NewSampleTime > 0) + { + double ratio = (double)NewSampleTime + / (double)SampleTime; + ki *= ratio; + kd /= ratio; + SampleTime = (unsigned long)NewSampleTime; + } +} + +/* SetOutputLimits(...)**************************************************** + * This function will be used far more often than SetInputLimits. while + * the input to the controller will generally be in the 0-1023 range (which is + * the default already,) the output will be a little different. maybe they'll + * be doing a time window and will need 0-8000 or something. or maybe they'll + * want to clamp it from 0-125. who knows. at any rate, that can all be done + * here. + **************************************************************************/ +void PID::SetOutputLimits(double Min, double Max) +{ + if(Min >= Max) return; + outMin = Min; + outMax = Max; + + if(inAuto) + { + if(*myOutput > outMax) *myOutput = outMax; + else if(*myOutput < outMin) *myOutput = outMin; + + if(outputSum > outMax) outputSum= outMax; + else if(outputSum < outMin) outputSum= outMin; + } +} + +/* SetMode(...)**************************************************************** + * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) + * when the transition from manual to auto occurs, the controller is + * automatically initialized + ******************************************************************************/ +void PID::SetMode(int Mode) +{ + bool newAuto = (Mode == AUTOMATIC); + if(newAuto && !inAuto) + { /*we just went from manual to auto*/ + PID::Initialize(); + } + inAuto = newAuto; +} + +/* Initialize()**************************************************************** + * does all the things that need to happen to ensure a bumpless transfer + * from manual to automatic mode. + ******************************************************************************/ +void PID::Initialize() +{ + outputSum = *myOutput; + lastInput = *myInput; + if(outputSum > outMax) outputSum = outMax; + else if(outputSum < outMin) outputSum = outMin; +} + +/* SetControllerDirection(...)************************************************* + * The PID will either be connected to a DIRECT acting process (+Output leads + * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to + * know which one, because otherwise we may increase the output when we should + * be decreasing. This is called from the constructor. + ******************************************************************************/ +void PID::SetControllerDirection(int Direction) +{ + if(inAuto && Direction !=controllerDirection) + { + kp = (0 - kp); + ki = (0 - ki); + kd = (0 - kd); + } + controllerDirection = Direction; +} + +/* Status Funcions************************************************************* + * Just because you set the Kp=-1 doesn't mean it actually happened. these + * functions query the internal state of the PID. they're here for display + * purposes. this are the functions the PID Front-end uses for example + ******************************************************************************/ +double PID::GetKp(){ return dispKp; } +double PID::GetKi(){ return dispKi;} +double PID::GetKd(){ return dispKd;} +int PID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;} +int PID::GetDirection(){ return controllerDirection;} + diff --git a/lib/Arduino-PID-Library/PID_v1.h b/lib/Arduino-PID-Library/PID_v1.h new file mode 100644 index 0000000..9cba046 --- /dev/null +++ b/lib/Arduino-PID-Library/PID_v1.h @@ -0,0 +1,90 @@ +#ifndef PID_v1_h +#define PID_v1_h +#define LIBRARY_VERSION 1.2.1 + +class PID +{ + + + public: + + //Constants used in some of the functions below + #define AUTOMATIC 1 + #define MANUAL 0 + #define DIRECT 0 + #define REVERSE 1 + #define P_ON_M 0 + #define P_ON_E 1 + + //commonly used functions ************************************************************************** + PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and + double, double, double, int, int);// Setpoint. Initial tuning parameters are also set here. + // (overload for specifying proportional mode) + + PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and + double, double, double, int); // Setpoint. Initial tuning parameters are also set here + + void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0) + + bool Compute(); // * performs the PID calculation. it should be + // called every time loop() cycles. ON/OFF and + // calculation frequency can be set using SetMode + // SetSampleTime respectively + + void SetOutputLimits(double, double); // * clamps the output to a specific range. 0-255 by default, but + // it's likely the user will want to change this depending on + // the application + + + + //available but not commonly used functions ******************************************************** + void SetTunings(double, double, // * While most users will set the tunings once in the + double); // constructor, this function gives the user the option + // of changing tunings during runtime for Adaptive control + void SetTunings(double, double, // * overload for specifying proportional mode + double, int); + + void SetControllerDirection(int); // * Sets the Direction, or "Action" of the controller. DIRECT + // means the output will increase when error is positive. REVERSE + // means the opposite. it's very unlikely that this will be needed + // once it is set in the constructor. + void SetSampleTime(int); // * sets the frequency, in Milliseconds, with which + // the PID calculation is performed. default is 100 + + + + //Display functions **************************************************************** + double GetKp(); // These functions query the pid for interal values. + double GetKi(); // they were created mainly for the pid front-end, + double GetKd(); // where it's important to know what is actually + int GetMode(); // inside the PID. + int GetDirection(); // + + private: + void Initialize(); + + double dispKp; // * we'll hold on to the tuning parameters in user-entered + double dispKi; // format for display purposes + double dispKd; // + + double kp; // * (P)roportional Tuning Parameter + double ki; // * (I)ntegral Tuning Parameter + double kd; // * (D)erivative Tuning Parameter + + int controllerDirection; + int pOn; + + double *myInput; // * Pointers to the Input, Output, and Setpoint variables + double *myOutput; // This creates a hard link between the variables and the + double *mySetpoint; // PID, freeing the user from having to constantly tell us + // what these values are. with pointers we'll just know. + + unsigned long lastTime; + double outputSum, lastInput; + + unsigned long SampleTime; + double outMin, outMax; + bool inAuto, pOnE; +}; +#endif + diff --git a/lib/Arduino-PID-Library/README.txt b/lib/Arduino-PID-Library/README.txt new file mode 100644 index 0000000..3f2fb63 --- /dev/null +++ b/lib/Arduino-PID-Library/README.txt @@ -0,0 +1,11 @@ +*************************************************************** +* Arduino PID Library - Version 1.2.1 +* by Brett Beauregard brettbeauregard.com +* +* This Library is licensed under the MIT License +*************************************************************** + + - For an ultra-detailed explanation of why the code is the way it is, please visit: + http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/ + + - For function documentation see: http://playground.arduino.cc/Code/PIDLibrary diff --git a/lib/Arduino-PID-Library/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino b/lib/Arduino-PID-Library/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino new file mode 100644 index 0000000..94f3faf --- /dev/null +++ b/lib/Arduino-PID-Library/examples/PID_AdaptiveTunings/PID_AdaptiveTunings.ino @@ -0,0 +1,56 @@ +/******************************************************** + * PID Adaptive Tuning Example + * One of the benefits of the PID library is that you can + * change the tuning parameters at any time. this can be + * helpful if we want the controller to be agressive at some + * times, and conservative at others. in the example below + * we set the controller to use Conservative Tuning Parameters + * when we're near setpoint and more agressive Tuning + * Parameters when we're farther away. + ********************************************************/ + +#include + +#define PIN_INPUT 0 +#define PIN_OUTPUT 3 + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Define the aggressive and conservative Tuning Parameters +double aggKp=4, aggKi=0.2, aggKd=1; +double consKp=1, consKi=0.05, consKd=0.25; + +//Specify the links and initial tuning parameters +PID myPID(&Input, &Output, &Setpoint, consKp, consKi, consKd, DIRECT); + +void setup() +{ + //initialize the variables we're linked to + Input = analogRead(PIN_INPUT); + Setpoint = 100; + + //turn the PID on + myPID.SetMode(AUTOMATIC); +} + +void loop() +{ + Input = analogRead(PIN_INPUT); + + double gap = abs(Setpoint-Input); //distance away from setpoint + if (gap < 10) + { //we're close to setpoint, use conservative tuning parameters + myPID.SetTunings(consKp, consKi, consKd); + } + else + { + //we're far from setpoint, use aggressive tuning parameters + myPID.SetTunings(aggKp, aggKi, aggKd); + } + + myPID.Compute(); + analogWrite(PIN_OUTPUT, Output); +} + + diff --git a/lib/Arduino-PID-Library/examples/PID_Basic/PID_Basic.ino b/lib/Arduino-PID-Library/examples/PID_Basic/PID_Basic.ino new file mode 100644 index 0000000..8028b58 --- /dev/null +++ b/lib/Arduino-PID-Library/examples/PID_Basic/PID_Basic.ino @@ -0,0 +1,35 @@ +/******************************************************** + * PID Basic Example + * Reading analog input 0 to control analog PWM output 3 + ********************************************************/ + +#include + +#define PIN_INPUT 0 +#define PIN_OUTPUT 3 + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +double Kp=2, Ki=5, Kd=1; +PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); + +void setup() +{ + //initialize the variables we're linked to + Input = analogRead(PIN_INPUT); + Setpoint = 100; + + //turn the PID on + myPID.SetMode(AUTOMATIC); +} + +void loop() +{ + Input = analogRead(PIN_INPUT); + myPID.Compute(); + analogWrite(PIN_OUTPUT, Output); +} + + diff --git a/lib/Arduino-PID-Library/examples/PID_PonM/PID_PonM.ino b/lib/Arduino-PID-Library/examples/PID_PonM/PID_PonM.ino new file mode 100644 index 0000000..121c161 --- /dev/null +++ b/lib/Arduino-PID-Library/examples/PID_PonM/PID_PonM.ino @@ -0,0 +1,36 @@ +/******************************************************** + * PID Proportional on measurement Example + * Setting the PID to use Proportional on measurement will + * make the output move more smoothly when the setpoint + * is changed. In addition, it can eliminate overshoot + * in certain processes like sous-vides. + ********************************************************/ + +#include + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +PID myPID(&Input, &Output, &Setpoint,2,5,1,P_ON_M, DIRECT); //P_ON_M specifies that Proportional on Measurement be used + //P_ON_E (Proportional on Error) is the default behavior + +void setup() +{ + //initialize the variables we're linked to + Input = analogRead(0); + Setpoint = 100; + + //turn the PID on + myPID.SetMode(AUTOMATIC); +} + +void loop() +{ + Input = analogRead(0); + myPID.Compute(); + analogWrite(3,Output); +} + + + diff --git a/lib/Arduino-PID-Library/examples/PID_RelayOutput/PID_RelayOutput.ino b/lib/Arduino-PID-Library/examples/PID_RelayOutput/PID_RelayOutput.ino new file mode 100644 index 0000000..17fbe1a --- /dev/null +++ b/lib/Arduino-PID-Library/examples/PID_RelayOutput/PID_RelayOutput.ino @@ -0,0 +1,64 @@ +/******************************************************** + * PID RelayOutput Example + * Same as basic example, except that this time, the output + * is going to a digital pin which (we presume) is controlling + * a relay. the pid is designed to Output an analog value, + * but the relay can only be On/Off. + * + * to connect them together we use "time proportioning + * control" it's essentially a really slow version of PWM. + * first we decide on a window size (5000mS say.) we then + * set the pid to adjust its output between 0 and that window + * size. lastly, we add some logic that translates the PID + * output into "Relay On Time" with the remainder of the + * window being "Relay Off Time" + ********************************************************/ + +#include + +#define PIN_INPUT 0 +#define RELAY_PIN 6 + +//Define Variables we'll be connecting to +double Setpoint, Input, Output; + +//Specify the links and initial tuning parameters +double Kp=2, Ki=5, Kd=1; +PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT); + +int WindowSize = 5000; +unsigned long windowStartTime; + +void setup() +{ + windowStartTime = millis(); + + //initialize the variables we're linked to + Setpoint = 100; + + //tell the PID to range between 0 and the full window size + myPID.SetOutputLimits(0, WindowSize); + + //turn the PID on + myPID.SetMode(AUTOMATIC); +} + +void loop() +{ + Input = analogRead(PIN_INPUT); + myPID.Compute(); + + /************************************************ + * turn the output pin on/off based on pid output + ************************************************/ + if (millis() - windowStartTime > WindowSize) + { //time to shift the Relay Window + windowStartTime += WindowSize; + } + if (Output < millis() - windowStartTime) digitalWrite(RELAY_PIN, HIGH); + else digitalWrite(RELAY_PIN, LOW); + +} + + + diff --git a/lib/Arduino-PID-Library/keywords.txt b/lib/Arduino-PID-Library/keywords.txt new file mode 100644 index 0000000..330d7fc --- /dev/null +++ b/lib/Arduino-PID-Library/keywords.txt @@ -0,0 +1,36 @@ +####################################### +# Syntax Coloring Map For PID Library +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +PID KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +SetMode KEYWORD2 +Compute KEYWORD2 +SetOutputLimits KEYWORD2 +SetTunings KEYWORD2 +SetControllerDirection KEYWORD2 +SetSampleTime KEYWORD2 +GetKp KEYWORD2 +GetKi KEYWORD2 +GetKd KEYWORD2 +GetMode KEYWORD2 +GetDirection KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +AUTOMATIC LITERAL1 +MANUAL LITERAL1 +DIRECT LITERAL1 +REVERSE LITERAL1 +P_ON_E LITERAL1 +P_ON_M LITERAL1 diff --git a/lib/Arduino-PID-Library/library.json b/lib/Arduino-PID-Library/library.json new file mode 100644 index 0000000..cf2510c --- /dev/null +++ b/lib/Arduino-PID-Library/library.json @@ -0,0 +1,19 @@ +{ + "name": "PID", + "keywords": "PID, controller, signal", + "description": "A PID controller seeks to keep some input variable close to a desired setpoint by adjusting an output. The way in which it does this can be 'tuned' by adjusting three parameters (P,I,D).", + "url": "http://playground.arduino.cc/Code/PIDLibrary", + "include": "PID_v1", + "authors": + [ + { + "name": "Brett Beauregard" + } + ], + "repository": + { + "type": "git", + "url": "https://github.com/br3ttb/Arduino-PID-Library.git" + }, + "frameworks": "arduino" +} diff --git a/lib/Arduino-PID-Library/library.properties b/lib/Arduino-PID-Library/library.properties new file mode 100644 index 0000000..4b3fadf --- /dev/null +++ b/lib/Arduino-PID-Library/library.properties @@ -0,0 +1,9 @@ +name=PID +version=1.2.1 +author=Brett Beauregard +maintainer=Brett Beauregard +sentence=PID controller +paragraph=A PID controller seeks to keep some input variable close to a desired setpoint by adjusting an output. The way in which it does this can be 'tuned' by adjusting three parameters (P,I,D). +category=Signal Input/Output +url=http://playground.arduino.cc/Code/PIDLibrary +architectures=* diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/TFT_ILI9163C b/lib/TFT_ILI9163C new file mode 160000 index 0000000..9b2928a --- /dev/null +++ b/lib/TFT_ILI9163C @@ -0,0 +1 @@ +Subproject commit 9b2928a2dfd132d73f0edcd9dcb648f948b956fe diff --git a/lib/TimerOne/README.md b/lib/TimerOne/README.md new file mode 100644 index 0000000..cf945a0 --- /dev/null +++ b/lib/TimerOne/README.md @@ -0,0 +1,33 @@ +#TimerOne Library# + +Paul Stoffregen's modified TimerOne. This version provides 2 main benefits: + +1: Optimized inline functions - much faster for the most common usage +2: Support for more boards (including ATTiny85 except for the PWM functionality) + +http://www.pjrc.com/teensy/td_libs_TimerOne.html + +https://github.com/PaulStoffregen/TimerOne + +Original code + +http://playground.arduino.cc/Code/Timer1 + +Open Source License + +TimerOne is free software. You can redistribute it and/or modify it under +the terms of Creative Commons Attribution 3.0 United States License. +To view a copy of this license, visit + +http://creativecommons.org/licenses/by/3.0/us/ + +Paul Stoffregen forked this version from an early copy of TimerOne/TimerThree +which was licensed "Creative Commons Attribution 3.0" and has maintained +the original "CC BY 3.0 US" license terms. + +Other, separately developed updates to TimerOne have been released by other +authors under the GNU GPLv2 license. Multiple copies of this library, bearing +the same name but distributed under different license terms, is unfortunately +confusing. This copy, with nearly all the code redesigned as inline functions, +is provided under the "CC BY 3.0 US" license terms. + diff --git a/lib/TimerOne/TimerOne.cpp b/lib/TimerOne/TimerOne.cpp new file mode 100644 index 0000000..3c28390 --- /dev/null +++ b/lib/TimerOne/TimerOne.cpp @@ -0,0 +1,59 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328 + * Original code by Jesse Tane for http://labs.ideo.com August 2008 + * Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support + * Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop + * Modified Oct 2009 by Dan Clemens to work with timer1 of the ATMega1280 or Arduino Mega + * Modified April 2012 by Paul Stoffregen + * Modified again, June 2014 by Paul Stoffregen + * Modified July 2017 by Stoyko Dimitrov - added support for ATTiny85 except for the PWM functionality + * + * This is free software. You can redistribute it and/or modify it under + * the terms of Creative Commons Attribution 3.0 United States License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ + * or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. + * + */ + +#include "TimerOne.h" + +TimerOne Timer1; // preinstatiate + +unsigned short TimerOne::pwmPeriod = 0; +unsigned char TimerOne::clockSelectBits = 0; +void (*TimerOne::isrCallback)() = TimerOne::isrDefaultUnused; + +// interrupt service routine that wraps a user defined function supplied by attachInterrupt +#if defined (__AVR_ATtiny85__) +ISR(TIMER1_COMPA_vect) +{ + Timer1.isrCallback(); +} +#elif defined(__AVR__) +ISR(TIMER1_OVF_vect) +{ + Timer1.isrCallback(); +} +#elif defined(__arm__) && defined(TEENSYDUINO) && (defined(KINETISK) || defined(KINETISL)) +void ftm1_isr(void) +{ + uint32_t sc = FTM1_SC; + #ifdef KINETISL + if (sc & 0x80) FTM1_SC = sc; + #else + if (sc & 0x80) FTM1_SC = sc & 0x7F; + #endif + Timer1.isrCallback(); +} +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(__IMXRT1062__) +void TimerOne::isr(void) +{ + FLEXPWM1_SM3STS = FLEXPWM_SMSTS_RF; + Timer1.isrCallback(); +} + +#endif + +void TimerOne::isrDefaultUnused() +{ +} diff --git a/lib/TimerOne/TimerOne.h b/lib/TimerOne/TimerOne.h new file mode 100644 index 0000000..99e4502 --- /dev/null +++ b/lib/TimerOne/TimerOne.h @@ -0,0 +1,623 @@ +/* + * Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328 + * Original code by Jesse Tane for http://labs.ideo.com August 2008 + * Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support + * Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop + * Modified April 2012 by Paul Stoffregen - portable to other AVR chips, use inline functions + * Modified again, June 2014 by Paul Stoffregen - support Teensy 3.x & even more AVR chips + * Modified July 2017 by Stoyko Dimitrov - added support for ATTiny85 except for the PWM functionality + * + * + * This is free software. You can redistribute it and/or modify it under + * the terms of Creative Commons Attribution 3.0 United States License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ + * or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. + * + */ + +#ifndef TimerOne_h_ +#define TimerOne_h_ + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include "config/known_16bit_timers.h" +#if defined (__AVR_ATtiny85__) +#define TIMER1_RESOLUTION 256UL // Timer1 is 8 bit +#elif defined(__AVR__) +#define TIMER1_RESOLUTION 65536UL // Timer1 is 16 bit +#else +#define TIMER1_RESOLUTION 65536UL // assume 16 bits for non-AVR chips +#endif + +// Placing nearly all the code in this .h file allows the functions to be +// inlined by the compiler. In the very common case with constant values +// the compiler will perform all calculations and simply write constants +// to the hardware registers (for example, setPeriod). + + +class TimerOne +{ + +#if defined (__AVR_ATtiny85__) + public: + //**************************** + // Configuration + //**************************** + void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { + TCCR1 = _BV(CTC1); //clear timer1 when it matches the value in OCR1C + TIMSK |= _BV(OCIE1A); //enable interrupt when OCR1A matches the timer value + setPeriod(microseconds); + } + void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { + const unsigned long cycles = microseconds * ratio; + if (cycles < TIMER1_RESOLUTION) { + clockSelectBits = _BV(CS10); + pwmPeriod = cycles; + } else + if (cycles < TIMER1_RESOLUTION * 2UL) { + clockSelectBits = _BV(CS11); + pwmPeriod = cycles / 2; + } else + if (cycles < TIMER1_RESOLUTION * 4UL) { + clockSelectBits = _BV(CS11) | _BV(CS10); + pwmPeriod = cycles / 4; + } else + if (cycles < TIMER1_RESOLUTION * 8UL) { + clockSelectBits = _BV(CS12); + pwmPeriod = cycles / 8; + } else + if (cycles < TIMER1_RESOLUTION * 16UL) { + clockSelectBits = _BV(CS12) | _BV(CS10); + pwmPeriod = cycles / 16; + } else + if (cycles < TIMER1_RESOLUTION * 32UL) { + clockSelectBits = _BV(CS12) | _BV(CS11); + pwmPeriod = cycles / 32; + } else + if (cycles < TIMER1_RESOLUTION * 64UL) { + clockSelectBits = _BV(CS12) | _BV(CS11) | _BV(CS10); + pwmPeriod = cycles / 64UL; + } else + if (cycles < TIMER1_RESOLUTION * 128UL) { + clockSelectBits = _BV(CS13); + pwmPeriod = cycles / 128; + } else + if (cycles < TIMER1_RESOLUTION * 256UL) { + clockSelectBits = _BV(CS13) | _BV(CS10); + pwmPeriod = cycles / 256; + } else + if (cycles < TIMER1_RESOLUTION * 512UL) { + clockSelectBits = _BV(CS13) | _BV(CS11); + pwmPeriod = cycles / 512; + } else + if (cycles < TIMER1_RESOLUTION * 1024UL) { + clockSelectBits = _BV(CS13) | _BV(CS11) | _BV(CS10); + pwmPeriod = cycles / 1024; + } else + if (cycles < TIMER1_RESOLUTION * 2048UL) { + clockSelectBits = _BV(CS13) | _BV(CS12); + pwmPeriod = cycles / 2048; + } else + if (cycles < TIMER1_RESOLUTION * 4096UL) { + clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS10); + pwmPeriod = cycles / 4096; + } else + if (cycles < TIMER1_RESOLUTION * 8192UL) { + clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11); + pwmPeriod = cycles / 8192; + } else + if (cycles < TIMER1_RESOLUTION * 16384UL) { + clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11) | _BV(CS10); + pwmPeriod = cycles / 16384; + } else { + clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11) | _BV(CS10); + pwmPeriod = TIMER1_RESOLUTION - 1; + } + OCR1A = pwmPeriod; + OCR1C = pwmPeriod; + TCCR1 = _BV(CTC1) | clockSelectBits; + } + + //**************************** + // Run Control + //**************************** + void start() __attribute__((always_inline)) { + TCCR1 = 0; + TCNT1 = 0; + resume(); + } + void stop() __attribute__((always_inline)) { + TCCR1 = _BV(CTC1); + } + void restart() __attribute__((always_inline)) { + start(); + } + void resume() __attribute__((always_inline)) { + TCCR1 = _BV(CTC1) | clockSelectBits; + } + + //**************************** + // PWM outputs + //**************************** + //Not implemented yet for ATTiny85 + //TO DO + + //**************************** + // Interrupt Function + //**************************** + void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { + isrCallback = isr; + TIMSK |= _BV(OCIE1A); + } + void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { + if(microseconds > 0) setPeriod(microseconds); + attachInterrupt(isr); + } + void detachInterrupt() __attribute__((always_inline)) { + //TIMSK = 0; // Timer 0 and Timer 1 both use TIMSK register so setting it to 0 will override settings for Timer1 as well + TIMSK &= ~_BV(OCIE1A); + } + static void (*isrCallback)(); + static void isrDefaultUnused(); + + private: + static unsigned short pwmPeriod; + static unsigned char clockSelectBits; + static const byte ratio = (F_CPU)/ ( 1000000 ); + +#elif defined(__AVR__) + +#if defined (__AVR_ATmega8__) + //in some io definitions for older microcontrollers TIMSK is used instead of TIMSK1 + #define TIMSK1 TIMSK +#endif + + public: + //**************************** + // Configuration + //**************************** + void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { + TCCR1B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timer + TCCR1A = 0; // clear control register A + setPeriod(microseconds); + } + void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { + const unsigned long cycles = ((F_CPU/100000 * microseconds) / 20); + if (cycles < TIMER1_RESOLUTION) { + clockSelectBits = _BV(CS10); + pwmPeriod = cycles; + } else + if (cycles < TIMER1_RESOLUTION * 8) { + clockSelectBits = _BV(CS11); + pwmPeriod = cycles / 8; + } else + if (cycles < TIMER1_RESOLUTION * 64) { + clockSelectBits = _BV(CS11) | _BV(CS10); + pwmPeriod = cycles / 64; + } else + if (cycles < TIMER1_RESOLUTION * 256) { + clockSelectBits = _BV(CS12); + pwmPeriod = cycles / 256; + } else + if (cycles < TIMER1_RESOLUTION * 1024) { + clockSelectBits = _BV(CS12) | _BV(CS10); + pwmPeriod = cycles / 1024; + } else { + clockSelectBits = _BV(CS12) | _BV(CS10); + pwmPeriod = TIMER1_RESOLUTION - 1; + } + ICR1 = pwmPeriod; + TCCR1B = _BV(WGM13) | clockSelectBits; + } + + //**************************** + // Run Control + //**************************** + void start() __attribute__((always_inline)) { + TCCR1B = 0; + TCNT1 = 0; // TODO: does this cause an undesired interrupt? + resume(); + } + void stop() __attribute__((always_inline)) { + TCCR1B = _BV(WGM13); + } + void restart() __attribute__((always_inline)) { + start(); + } + void resume() __attribute__((always_inline)) { + TCCR1B = _BV(WGM13) | clockSelectBits; + } + + //**************************** + // PWM outputs + //**************************** + void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { + unsigned long dutyCycle = pwmPeriod; + dutyCycle *= duty; + dutyCycle >>= 10; + if (pin == TIMER1_A_PIN) OCR1A = dutyCycle; + #ifdef TIMER1_B_PIN + else if (pin == TIMER1_B_PIN) OCR1B = dutyCycle; + #endif + #ifdef TIMER1_C_PIN + else if (pin == TIMER1_C_PIN) OCR1C = dutyCycle; + #endif + } + void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { + if (pin == TIMER1_A_PIN) { pinMode(TIMER1_A_PIN, OUTPUT); TCCR1A |= _BV(COM1A1); } + #ifdef TIMER1_B_PIN + else if (pin == TIMER1_B_PIN) { pinMode(TIMER1_B_PIN, OUTPUT); TCCR1A |= _BV(COM1B1); } + #endif + #ifdef TIMER1_C_PIN + else if (pin == TIMER1_C_PIN) { pinMode(TIMER1_C_PIN, OUTPUT); TCCR1A |= _BV(COM1C1); } + #endif + setPwmDuty(pin, duty); + TCCR1B = _BV(WGM13) | clockSelectBits; + } + void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { + if (microseconds > 0) setPeriod(microseconds); + pwm(pin, duty); + } + void disablePwm(char pin) __attribute__((always_inline)) { + if (pin == TIMER1_A_PIN) TCCR1A &= ~_BV(COM1A1); + #ifdef TIMER1_B_PIN + else if (pin == TIMER1_B_PIN) TCCR1A &= ~_BV(COM1B1); + #endif + #ifdef TIMER1_C_PIN + else if (pin == TIMER1_C_PIN) TCCR1A &= ~_BV(COM1C1); + #endif + } + + //**************************** + // Interrupt Function + //**************************** + + void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { + isrCallback = isr; + TIMSK1 = _BV(TOIE1); + } + void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { + if(microseconds > 0) setPeriod(microseconds); + attachInterrupt(isr); + } + void detachInterrupt() __attribute__((always_inline)) { + TIMSK1 = 0; + } + static void (*isrCallback)(); + static void isrDefaultUnused(); + + private: + // properties + static unsigned short pwmPeriod; + static unsigned char clockSelectBits; + + + + + + +#elif defined(__arm__) && defined(TEENSYDUINO) && (defined(KINETISK) || defined(KINETISL)) + +#if defined(KINETISK) +#define F_TIMER F_BUS +#elif defined(KINETISL) +#define F_TIMER (F_PLL/2) +#endif + +// Use only 15 bit resolution. From K66 reference manual, 45.5.7 page 1200: +// The CPWM pulse width (duty cycle) is determined by 2 x (CnV - CNTIN) and the +// period is determined by 2 x (MOD - CNTIN). See the following figure. MOD must be +// kept in the range of 0x0001 to 0x7FFF because values outside this range can produce +// ambiguous results. +#undef TIMER1_RESOLUTION +#define TIMER1_RESOLUTION 32768 + + public: + //**************************** + // Configuration + //**************************** + void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { + setPeriod(microseconds); + } + void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { + const unsigned long cycles = (F_TIMER / 2000000) * microseconds; + // A much faster if-else + // This is like a binary serch tree and no more than 3 conditions are evaluated. + // I haven't checked if this becomes significantly longer ASM than the simple ladder. + // It looks very similar to the ladder tho: same # of if's and else's + + /* + // This code does not work properly in all cases :( + // https://github.com/PaulStoffregen/TimerOne/issues/17 + if (cycles < TIMER1_RESOLUTION * 16) { + if (cycles < TIMER1_RESOLUTION * 4) { + if (cycles < TIMER1_RESOLUTION) { + clockSelectBits = 0; + pwmPeriod = cycles; + }else{ + clockSelectBits = 1; + pwmPeriod = cycles >> 1; + } + }else{ + if (cycles < TIMER1_RESOLUTION * 8) { + clockSelectBits = 3; + pwmPeriod = cycles >> 3; + }else{ + clockSelectBits = 4; + pwmPeriod = cycles >> 4; + } + } + }else{ + if (cycles > TIMER1_RESOLUTION * 64) { + if (cycles > TIMER1_RESOLUTION * 128) { + clockSelectBits = 7; + pwmPeriod = TIMER1_RESOLUTION - 1; + }else{ + clockSelectBits = 7; + pwmPeriod = cycles >> 7; + } + } + else{ + if (cycles > TIMER1_RESOLUTION * 32) { + clockSelectBits = 6; + pwmPeriod = cycles >> 6; + }else{ + clockSelectBits = 5; + pwmPeriod = cycles >> 5; + } + } + } + */ + if (cycles < TIMER1_RESOLUTION) { + clockSelectBits = 0; + pwmPeriod = cycles; + } else + if (cycles < TIMER1_RESOLUTION * 2) { + clockSelectBits = 1; + pwmPeriod = cycles >> 1; + } else + if (cycles < TIMER1_RESOLUTION * 4) { + clockSelectBits = 2; + pwmPeriod = cycles >> 2; + } else + if (cycles < TIMER1_RESOLUTION * 8) { + clockSelectBits = 3; + pwmPeriod = cycles >> 3; + } else + if (cycles < TIMER1_RESOLUTION * 16) { + clockSelectBits = 4; + pwmPeriod = cycles >> 4; + } else + if (cycles < TIMER1_RESOLUTION * 32) { + clockSelectBits = 5; + pwmPeriod = cycles >> 5; + } else + if (cycles < TIMER1_RESOLUTION * 64) { + clockSelectBits = 6; + pwmPeriod = cycles >> 6; + } else + if (cycles < TIMER1_RESOLUTION * 128) { + clockSelectBits = 7; + pwmPeriod = cycles >> 7; + } else { + clockSelectBits = 7; + pwmPeriod = TIMER1_RESOLUTION - 1; + } + + uint32_t sc = FTM1_SC; + FTM1_SC = 0; + FTM1_MOD = pwmPeriod; + FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_CPWMS | clockSelectBits | (sc & FTM_SC_TOIE); + } + + //**************************** + // Run Control + //**************************** + void start() __attribute__((always_inline)) { + stop(); + FTM1_CNT = 0; + resume(); + } + void stop() __attribute__((always_inline)) { + FTM1_SC = FTM1_SC & (FTM_SC_TOIE | FTM_SC_CPWMS | FTM_SC_PS(7)); + } + void restart() __attribute__((always_inline)) { + start(); + } + void resume() __attribute__((always_inline)) { + FTM1_SC = (FTM1_SC & (FTM_SC_TOIE | FTM_SC_PS(7))) | FTM_SC_CPWMS | FTM_SC_CLKS(1); + } + + //**************************** + // PWM outputs + //**************************** + void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { + unsigned long dutyCycle = pwmPeriod; + dutyCycle *= duty; + dutyCycle >>= 10; + if (pin == TIMER1_A_PIN) { + FTM1_C0V = dutyCycle; + } else if (pin == TIMER1_B_PIN) { + FTM1_C1V = dutyCycle; + } + } + void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { + setPwmDuty(pin, duty); + if (pin == TIMER1_A_PIN) { + *portConfigRegister(TIMER1_A_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; + } else if (pin == TIMER1_B_PIN) { + *portConfigRegister(TIMER1_B_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; + } + } + void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { + if (microseconds > 0) setPeriod(microseconds); + pwm(pin, duty); + } + void disablePwm(char pin) __attribute__((always_inline)) { + if (pin == TIMER1_A_PIN) { + *portConfigRegister(TIMER1_A_PIN) = 0; + } else if (pin == TIMER1_B_PIN) { + *portConfigRegister(TIMER1_B_PIN) = 0; + } + } + + //**************************** + // Interrupt Function + //**************************** + void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { + isrCallback = isr; + FTM1_SC |= FTM_SC_TOIE; + NVIC_ENABLE_IRQ(IRQ_FTM1); + } + void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { + if(microseconds > 0) setPeriod(microseconds); + attachInterrupt(isr); + } + void detachInterrupt() __attribute__((always_inline)) { + FTM1_SC &= ~FTM_SC_TOIE; + NVIC_DISABLE_IRQ(IRQ_FTM1); + } + static void (*isrCallback)(); + static void isrDefaultUnused(); + + private: + // properties + static unsigned short pwmPeriod; + static unsigned char clockSelectBits; + +#undef F_TIMER + +#elif defined(__arm__) && defined(TEENSYDUINO) && defined(__IMXRT1062__) + + public: + //**************************** + // Configuration + //**************************** + void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { + setPeriod(microseconds); + } + void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { + uint32_t period = (float)F_BUS_ACTUAL * (float)microseconds * 0.0000005f; + uint32_t prescale = 0; + while (period > 32767) { + period = period >> 1; + if (++prescale > 7) { + prescale = 7; // when F_BUS is 150 MHz, longest + period = 32767; // period is 55922 us (~17.9 Hz) + break; + } + } + //Serial.printf("setPeriod, period=%u, prescale=%u\n", period, prescale); + FLEXPWM1_FCTRL0 |= FLEXPWM_FCTRL0_FLVL(8); // logic high = fault + FLEXPWM1_FSTS0 = 0x0008; // clear fault status + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_CLDOK(8); + FLEXPWM1_SM3CTRL2 = FLEXPWM_SMCTRL2_INDEP; + FLEXPWM1_SM3CTRL = FLEXPWM_SMCTRL_HALF | FLEXPWM_SMCTRL_PRSC(prescale); + FLEXPWM1_SM3INIT = -period; + FLEXPWM1_SM3VAL0 = 0; + FLEXPWM1_SM3VAL1 = period; + FLEXPWM1_SM3VAL2 = 0; + FLEXPWM1_SM3VAL3 = 0; + FLEXPWM1_SM3VAL4 = 0; + FLEXPWM1_SM3VAL5 = 0; + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_LDOK(8) | FLEXPWM_MCTRL_RUN(8); + pwmPeriod = period; + } + //**************************** + // Run Control + //**************************** + void start() __attribute__((always_inline)) { + stop(); + // TODO: how to force counter back to zero? + resume(); + } + void stop() __attribute__((always_inline)) { + FLEXPWM1_MCTRL &= ~FLEXPWM_MCTRL_RUN(8); + } + void restart() __attribute__((always_inline)) { + start(); + } + void resume() __attribute__((always_inline)) { + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_RUN(8); + } + + //**************************** + // PWM outputs + //**************************** + void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { + if (duty > 1023) duty = 1023; + int dutyCycle = (pwmPeriod * duty) >> 10; + //Serial.printf("setPwmDuty, period=%u\n", dutyCycle); + if (pin == TIMER1_A_PIN) { + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_CLDOK(8); + FLEXPWM1_SM3VAL5 = dutyCycle; + FLEXPWM1_SM3VAL4 = -dutyCycle; + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_LDOK(8); + } else if (pin == TIMER1_B_PIN) { + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_CLDOK(8); + FLEXPWM1_SM3VAL3 = dutyCycle; + FLEXPWM1_SM3VAL2 = -dutyCycle; + FLEXPWM1_MCTRL |= FLEXPWM_MCTRL_LDOK(8); + } + } + void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { + setPwmDuty(pin, duty); + if (pin == TIMER1_A_PIN) { + FLEXPWM1_OUTEN |= FLEXPWM_OUTEN_PWMB_EN(8); + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_01 = 6; // pin 7 FLEXPWM1_PWM3_B + } else if (pin == TIMER1_B_PIN) { + FLEXPWM1_OUTEN |= FLEXPWM_OUTEN_PWMA_EN(8); + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_00 = 6; // pin 8 FLEXPWM1_PWM3_A + } + } + void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { + if (microseconds > 0) setPeriod(microseconds); + pwm(pin, duty); + } + void disablePwm(char pin) __attribute__((always_inline)) { + if (pin == TIMER1_A_PIN) { + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_01 = 5; // pin 7 FLEXPWM1_PWM3_B + FLEXPWM1_OUTEN &= ~FLEXPWM_OUTEN_PWMB_EN(8); + } else if (pin == TIMER1_B_PIN) { + IOMUXC_SW_MUX_CTL_PAD_GPIO_B1_00 = 5; // pin 8 FLEXPWM1_PWM3_A + FLEXPWM1_OUTEN &= ~FLEXPWM_OUTEN_PWMA_EN(8); + } + } + //**************************** + // Interrupt Function + //**************************** + void attachInterrupt(void (*f)()) __attribute__((always_inline)) { + isrCallback = f; + attachInterruptVector(IRQ_FLEXPWM1_3, &isr); + FLEXPWM1_SM3STS = FLEXPWM_SMSTS_RF; + FLEXPWM1_SM3INTEN = FLEXPWM_SMINTEN_RIE; + NVIC_ENABLE_IRQ(IRQ_FLEXPWM1_3); + } + void attachInterrupt(void (*f)(), unsigned long microseconds) __attribute__((always_inline)) { + if(microseconds > 0) setPeriod(microseconds); + attachInterrupt(f); + } + void detachInterrupt() __attribute__((always_inline)) { + NVIC_DISABLE_IRQ(IRQ_FLEXPWM1_3); + FLEXPWM1_SM3INTEN = 0; + } + static void isr(void); + static void (*isrCallback)(); + static void isrDefaultUnused(); + + private: + // properties + static unsigned short pwmPeriod; + static unsigned char clockSelectBits; + +#endif +}; + +extern TimerOne Timer1; + +#endif + diff --git a/lib/TimerOne/config/known_16bit_timers.h b/lib/TimerOne/config/known_16bit_timers.h new file mode 100644 index 0000000..ec93a45 --- /dev/null +++ b/lib/TimerOne/config/known_16bit_timers.h @@ -0,0 +1,189 @@ +#ifndef known_16bit_timers_header_ +#define known_16bit_timers_header_ + +// Wiring-S +// +#if defined(__AVR_ATmega644P__) && defined(WIRING) + #define TIMER1_A_PIN 5 + #define TIMER1_B_PIN 4 + #define TIMER1_ICP_PIN 6 + +// Teensy 2.0 +// +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) + #define TIMER1_A_PIN 14 + #define TIMER1_B_PIN 15 + #define TIMER1_C_PIN 4 + #define TIMER1_ICP_PIN 22 + #define TIMER1_CLK_PIN 11 + #define TIMER3_A_PIN 9 + #define TIMER3_ICP_PIN 10 + +// Teensy++ 2.0 +#elif defined(__AVR_AT90USB1286__) && defined(CORE_TEENSY) + #define TIMER1_A_PIN 25 + #define TIMER1_B_PIN 26 + #define TIMER1_C_PIN 27 + #define TIMER1_ICP_PIN 4 + #define TIMER1_CLK_PIN 6 + #define TIMER3_A_PIN 16 + #define TIMER3_B_PIN 15 + #define TIMER3_C_PIN 14 + #define TIMER3_ICP_PIN 17 + #define TIMER3_CLK_PIN 13 + +// Teensy 3.0 +// +#elif defined(__MK20DX128__) + #define TIMER1_A_PIN 3 + #define TIMER1_B_PIN 4 + #define TIMER1_ICP_PIN 4 + +// Teensy 3.1 / Teensy 3.2 +// +#elif defined(__MK20DX256__) + #define TIMER1_A_PIN 3 + #define TIMER1_B_PIN 4 + #define TIMER1_ICP_PIN 4 + #define TIMER3_A_PIN 32 + #define TIMER3_B_PIN 25 + #define TIMER3_ICP_PIN 32 + +// Teensy 3.5 / Teensy 3.6 +// +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + #define TIMER1_A_PIN 3 + #define TIMER1_B_PIN 4 + #define TIMER1_ICP_PIN 4 + #define TIMER3_A_PIN 29 + #define TIMER3_B_PIN 30 + #define TIMER3_ICP_PIN 29 + +// Teensy-LC +// +#elif defined(__MKL26Z64__) + #define TIMER1_A_PIN 16 + #define TIMER1_B_PIN 17 + #define TIMER1_ICP_PIN 17 + #define TIMER3_A_PIN 3 + #define TIMER3_B_PIN 4 + #define TIMER3_ICP_PIN 4 + +// Teensy 4.0 +// +#elif defined(__IMXRT1062__) + #define TIMER1_A_PIN 7 + #define TIMER1_B_PIN 8 + #define TIMER3_A_PIN 9 + #define TIMER3_B_PIN 6 + +// Arduino Mega +// +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + #define TIMER1_A_PIN 11 + #define TIMER1_B_PIN 12 + #define TIMER3_A_PIN 5 + #define TIMER3_B_PIN 2 + #define TIMER3_C_PIN 3 + #define TIMER4_A_PIN 6 + #define TIMER4_B_PIN 7 + #define TIMER4_C_PIN 8 + #define TIMER4_ICP_PIN 49 + #define TIMER5_A_PIN 46 + #define TIMER5_B_PIN 45 + #define TIMER5_C_PIN 44 + #define TIMER3_ICP_PIN 48 + #define TIMER3_CLK_PIN 47 + +// Arduino Leonardo, Yun, etc +// +#elif defined(__AVR_ATmega32U4__) + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 + #define TIMER1_C_PIN 11 + #define TIMER1_ICP_PIN 4 + #define TIMER1_CLK_PIN 12 + #define TIMER3_A_PIN 5 + #define TIMER3_ICP_PIN 13 + +// Uno, Duemilanove, LilyPad, etc +// +#elif defined (__AVR_ATmega168__) || defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined (__AVR_ATmega8__) + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 + #define TIMER1_ICP_PIN 8 + #define TIMER1_CLK_PIN 5 + +// Minicore generic +// +#elif defined(__AVR_ATmega48PB__) || defined(__AVR_ATmega88PB__) || defined(__AVR_ATmega168PB__) + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 + #define TIMER1_ICP_PIN 8 + #define TIMER1_CLK_PIN 5 +#elif defined(__AVR_ATmega328PB__) + #define TIMER1_A_PIN 9 + #define TIMER1_B_PIN 10 + #define TIMER1_ICP_PIN 8 + #define TIMER1_CLK_PIN 5 + #define TIMER3_A_PIN 0 + #define TIMER3_B_PIN 2 + #define TIMER3_ICP_PIN 25 + #define TIMER3_CLK_PIN 26 + #define TIMER4_A_PIN 1 + #define TIMER4_B_PIN 2 + #define TIMER4_ICP_PIN 23 + #define TIMER4_CLK_PIN 24 + +// attiny167 +// +#elif defined (__AVR_ATtiny167__) + #define TIMER1_A_PIN 14 + #define TIMER1_B_PIN 11 + //#define TIMER1_ICP_PIN 8 + //#define TIMER1_CLK_PIN 5 + +// Sanguino +// +#elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) + #define TIMER1_A_PIN 13 + #define TIMER1_B_PIN 12 + #define TIMER1_ICP_PIN 14 + #define TIMER1_CLK_PIN 1 + +// Wildfire - Wicked Devices +// +#elif defined(__AVR_ATmega1284P__) && defined(WILDFIRE_VERSION) && WILDFIRE_VERSION >= 3 + #define TIMER1_A_PIN 5 // PD5 + #define TIMER1_B_PIN 8 // PD4 + #define TIMER1_ICP_PIN 6 // PD6 + #define TIMER1_CLK_PIN 23 // PB1 + #define TIMER3_A_PIN 12 // PB6 + #define TIMER3_B_PIN 13 // PB7 + #define TIMER3_ICP_PIN 9 // PB5 + #define TIMER3_CLK_PIN 0 // PD0 +#elif defined(__AVR_ATmega1284P__) && defined(WILDFIRE_VERSION) && WILDFIRE_VERSION < 3 + #define TIMER1_A_PIN 5 // PD5 + #define TIMER1_B_PIN 4 // PD4 + #define TIMER1_ICP_PIN 6 // PD6 + #define TIMER1_CLK_PIN 15 // PB1 + #define TIMER3_A_PIN 12 // PB6 + #define TIMER3_B_PIN 13 // PB7 + #define TIMER3_ICP_PIN 11 // PB5 + #define TIMER3_CLK_PIN 0 // PD0 + +// Mighty-1284 - Maniacbug +// +#elif defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + #define TIMER1_A_PIN 12 // PD5 + #define TIMER1_B_PIN 13 // PD4 + #define TIMER1_ICP_PIN 14 // PD6 + #define TIMER1_CLK_PIN 1 // PB1 + #define TIMER3_A_PIN 6 // PB6 + #define TIMER3_B_PIN 7 // PB7 + #define TIMER3_ICP_PIN 5 // PB5 + #define TIMER3_CLK_PIN 8 // PD0 + +#endif + +#endif diff --git a/lib/TimerOne/docs/issue_template.md b/lib/TimerOne/docs/issue_template.md new file mode 100644 index 0000000..0610992 --- /dev/null +++ b/lib/TimerOne/docs/issue_template.md @@ -0,0 +1,64 @@ +Please use this form only to report code defects or bugs. + +For any question, even questions directly pertaining to this code, post your question on the forums related to the board you are using. + +Arduino: forum.arduino.cc +Teensy: forum.pjrc.com +ESP8266: www.esp8266.com +ESP32: www.esp32.com +Adafruit Feather/Metro/Trinket: forums.adafruit.com +Particle Photon: community.particle.io + +If you are experiencing trouble but not certain of the cause, or need help using this code, ask on the appropriate forum. This is not the place to ask for support or help, even directly related to this code. Only use this form you are certain you have discovered a defect in this code! + +Please verify the problem occurs when using the very latest version, using the newest version of Arduino and any other related software. + + +----------------------------- Remove above ----------------------------- + + + +### Description + +Describe your problem. + + + +### Steps To Reproduce Problem + +Please give detailed instructions needed for anyone to attempt to reproduce the problem. + + + +### Hardware & Software + +Board +Shields / modules used +Arduino IDE version +Teensyduino version (if using Teensy) +Version info & package name (from Tools > Boards > Board Manager) +Operating system & version +Any other software or hardware? + + +### Arduino Sketch + +```cpp +// Change the code below by your sketch (please try to give the smallest code which demonstrates the problem) +#include + +// libraries: give links/details so anyone can compile your code for the same result + +void setup() { +} + +void loop() { +} +``` + + +### Errors or Incorrect Output + +If you see any errors or incorrect output, please show it here. Please use copy & paste to give an exact copy of the message. Details matter, so please show (not merely describe) the actual message or error exactly as it appears. + + diff --git a/lib/TimerOne/examples/ATTiny85/ATTiny85.ino b/lib/TimerOne/examples/ATTiny85/ATTiny85.ino new file mode 100644 index 0000000..76d4587 --- /dev/null +++ b/lib/TimerOne/examples/ATTiny85/ATTiny85.ino @@ -0,0 +1,19 @@ +#include +#define ledPin 4 +int ledState = LOW; + +void setup() { + pinMode(ledPin, OUTPUT); + Timer1.initialize(500000); //The led will blink in a half second time interval + Timer1.attachInterrupt(blinkLed); +} + +void loop() { +} + +void blinkLed(){ + ledState = !ledState; + digitalWrite(ledPin, ledState); +} + + diff --git a/lib/TimerOne/examples/FanSpeed/FanSpeed.ino b/lib/TimerOne/examples/FanSpeed/FanSpeed.ino new file mode 100644 index 0000000..c3f46ee --- /dev/null +++ b/lib/TimerOne/examples/FanSpeed/FanSpeed.ino @@ -0,0 +1,37 @@ +#include + +// This example creates a PWM signal with 25 kHz carrier. +// +// Arduino's analogWrite() gives you PWM output, but no control over the +// carrier frequency. The default frequency is low, typically 490 or +// 3920 Hz. Sometimes you may need a faster carrier frequency. +// +// The specification for 4-wire PWM fans recommends a 25 kHz frequency +// and allows 21 to 28 kHz. The default from analogWrite() might work +// with some fans, but to follow the specification we need 25 kHz. +// +// http://www.formfactors.org/developer/specs/REV1_2_Public.pdf +// +// Connect the PWM pin to the fan's control wire (usually blue). The +// board's ground must be connected to the fan's ground, and the fan +// needs +12 volt power from the computer or a separate power supply. + +const int fanPin = 4; + +void setup(void) +{ + Timer1.initialize(40); // 40 us = 25 kHz + Serial.begin(9600); +} + +void loop(void) +{ + // slowly increase the PWM fan speed + // + for (float dutyCycle = 30.0; dutyCycle < 100.0; dutyCycle++) { + Serial.print("PWM Fan, Duty Cycle = "); + Serial.println(dutyCycle); + Timer1.pwm(fanPin, (dutyCycle / 100) * 1023); + delay(500); + } +} diff --git a/lib/TimerOne/examples/Interrupt/Interrupt.ino b/lib/TimerOne/examples/Interrupt/Interrupt.ino new file mode 100644 index 0000000..7e0c959 --- /dev/null +++ b/lib/TimerOne/examples/Interrupt/Interrupt.ino @@ -0,0 +1,54 @@ +#include + +// This example uses the timer interrupt to blink an LED +// and also demonstrates how to share a variable between +// the interrupt and the main program. + + +const int led = LED_BUILTIN; // the pin with a LED + +void setup(void) +{ + pinMode(led, OUTPUT); + Timer1.initialize(150000); + Timer1.attachInterrupt(blinkLED); // blinkLED to run every 0.15 seconds + Serial.begin(9600); +} + + +// The interrupt will blink the LED, and keep +// track of how many times it has blinked. +int ledState = LOW; +volatile unsigned long blinkCount = 0; // use volatile for shared variables + +void blinkLED(void) +{ + if (ledState == LOW) { + ledState = HIGH; + blinkCount = blinkCount + 1; // increase when LED turns on + } else { + ledState = LOW; + } + digitalWrite(led, ledState); +} + + +// The main program will print the blink count +// to the Arduino Serial Monitor +void loop(void) +{ + unsigned long blinkCopy; // holds a copy of the blinkCount + + // to read a variable which the interrupt code writes, we + // must temporarily disable interrupts, to be sure it will + // not change while we are reading. To minimize the time + // with interrupts off, just quickly make a copy, and then + // use the copy while allowing the interrupt to keep working. + noInterrupts(); + blinkCopy = blinkCount; + interrupts(); + + Serial.print("blinkCount = "); + Serial.println(blinkCopy); + delay(100); +} diff --git a/lib/TimerOne/keywords.txt b/lib/TimerOne/keywords.txt new file mode 100644 index 0000000..22c7bb1 --- /dev/null +++ b/lib/TimerOne/keywords.txt @@ -0,0 +1,14 @@ +Timer1 KEYWORD2 +TimerOne KEYWORD1 +initialize KEYWORD2 +start KEYWORD2 +stop KEYWORD2 +restart KEYWORD2 +resume KEYWORD2 +pwm KEYWORD2 +disablePwm KEYWORD2 +attachInterrupt KEYWORD2 +detachInterrupt KEYWORD2 +setPeriod KEYWORD2 +setPwmDuty KEYWORD2 +isrCallback KEYWORD2 diff --git a/lib/TimerOne/library.json b/lib/TimerOne/library.json new file mode 100644 index 0000000..fac69fe --- /dev/null +++ b/lib/TimerOne/library.json @@ -0,0 +1,16 @@ +{ + "name": "TimerOne", + "keywords": "timer", + "description": "Allow to use the built-in Timer1", + "repository": + { + "type": "git", + "url": "https://github.com/PaulStoffregen/TimerOne.git" + }, + "frameworks": "arduino", + "platforms": + [ + "atmelavr", + "teensy" + ] +} diff --git a/lib/TimerOne/library.properties b/lib/TimerOne/library.properties new file mode 100644 index 0000000..2664671 --- /dev/null +++ b/lib/TimerOne/library.properties @@ -0,0 +1,10 @@ +name=TimerOne +version=1.1 +author=Stoyko Dimitrov, Jesse Tane, Jérôme Despatis, Michael Polli, Dan Clemens, Paul Stoffregen +maintainer=Paul Stoffregen +sentence=Use hardware Timer1 for finer PWM control and/or running an periodic interrupt function +paragraph= +category=Timing +url=http://playground.arduino.cc/Code/Timer1 +architectures=avr + diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..7906e50 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328] +platform = atmelavr +board = nanoatmega328 +framework = arduino +lib_deps = + adafruit/Adafruit BusIO@^1.14.1 + adafruit/Adafruit GFX Library@^1.11.3 + mbed-billycorgan123/TFT_ILI9163C@0.0.0+sha.f0799be044ff + Adafruit ST7735 and ST7789 Library + Wire +lib_ignore = + SD \ No newline at end of file diff --git a/src/Maiskolben_TFT.ino b/src/Maiskolben_TFT.ino new file mode 100644 index 0000000..cd9e783 --- /dev/null +++ b/src/Maiskolben_TFT.ino @@ -0,0 +1,1046 @@ +#include +#include +#include +#include +#include +#include "definitions.h" + +/* + If your display stays white, uncomment this. + Cut reset trace (on THT on upper layer/0R), connect STBY_NO (A1) with reset of TFT (at 4050). + See also readme in mechanical folder for reference. +*/ + #define USE_TFT_RESET // Benötigt + +/* + If red is blue and blue is red change this + If not sure, leave commented, you will be shown a setup screen +*/ +// #define HARDWARE_DEFINED_TFT 2 +/* + Based on your Hardware-Revision there may be modifications to the PCB. + In V3 and up is a second voltage measurement circuit. + HW REVS: + 1.5 - 2.8: + For THT this should be set to anything < 3 + Normally leave this commented as it is stored in EEPROM +*/ + +// V 1.5 - 2.11, Maiskolben THT2 +//#define HARDWARE_REVISION 2 +// V 3.0 and 3.1 +//#define HARDWARE_REVISION 3 + +/* + Only used for testing, do not use. +*/ +// #define INSTALL +// #define TEST_ADC + +volatile boolean off = true, stby = true, stby_layoff = false, sw_stby_old = false, sw_up_old = false, sw_down_old = false, clear_display = true, store_invalid = true, menu = false; +volatile uint8_t pwm, threshold_counter; +volatile int16_t cur_t, last_measured; +volatile error_type error = NO_ERROR; +error_type error_old; +int16_t stored[3] = {300, 350, 450}, set_t = TEMP_MIN, set_t_old, cur_t_old, target_t; +double pid_val, cur_td, set_td; +uint8_t store_to = 255; +p_source power_source, power_source_old = NO_INIT; +boolean blink; +uint16_t cnt_measure_voltage, cnt_compute, cnt_sw_poll, cnt_but_press, cnt_off_press, cnt_but_store; +float v_c1, v_c2, v_c3, v_in, v; +uint8_t array_index, array_count; +uint32_t sendNext; +uint32_t last_temperature_drop; +uint32_t last_on_state; +boolean wasOff = true, old_stby = false; +boolean autopower = true, bootheat = false, fahrenheit = false; +uint8_t revision = 1; +boolean menu_dismissed = false; +boolean autopower_repeat_under = false; +boolean force_redraw = false; +boolean power_down = false; +uint16_t charge = 0; +float adc_offset = ADC_TO_TEMP_OFFSET; +float adc_gain = ADC_TO_TEMP_GAIN; + +#define RGB_DISP 0x0 +#define BGR_DISP 0x2 + +#ifdef USE_TFT_RESET +TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC, STBY_NO); +#else +TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC); +#endif + +#define BLACK 0x0000 +#define RED 0x001F // Blue +#define BLUE 0xF800 // Red +#define GREEN 0x07E0 +#define YELLOW 0x07FF // Cyan +#define MAGENTA 0xF81F +#define CYAN 0xFFE0 // Yellow +#define WHITE 0xFFFF +#define GRAY 0x94B2 + +PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT); + +void setup(void) { + digitalWrite(HEATER_PWM, LOW); + pinMode(HEATER_PWM, OUTPUT); + pinMode(POWER, INPUT_PULLUP); + pinMode(HEAT_LED, OUTPUT); + digitalWrite(HEAT_LED, HIGH); + pinMode(TEMP_SENSE, INPUT); + pinMode(SW_T1, INPUT_PULLUP); + pinMode(SW_T2, INPUT_PULLUP); + pinMode(SW_T3, INPUT_PULLUP); + pinMode(SW_UP, INPUT_PULLUP); + pinMode(SW_DOWN, INPUT_PULLUP); + pinMode(STBY_NO, INPUT_PULLUP); + pinMode(SW_STBY, INPUT_PULLUP); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + Serial.begin(115200); + + boolean force_menu = false; + if (EEPROM.read(0) != EEPROM_CHECK) { + EEPROM.update(0, EEPROM_CHECK); + updateEEPROM(); + force_menu = true; + } + tft.begin(); +#ifdef HARDWARE_DEFINED_TFT +#if HARDWARE_DEFINED_TFT == 1 + EEPROM.update(EEPROM_DISPLAY, RGB_DISP); + setDisplayMode(0); +#else + EEPROM.update(EEPROM_DISPLAY, BGR_DISP); + setDisplayMode(1); +#endif +#else + if (force_menu || EEPROM.read(EEPROM_VERSION) < 23 || EEPROM.read(EEPROM_VERSION) == 255 || (EEPROM.read(EEPROM_DISPLAY) != BGR_DISP && EEPROM.read(EEPROM_DISPLAY) != RGB_DISP)) { + tft.fillScreen(BLACK); + setDisplayMode(1); + tft.setTextSize(2); + tft.setCursor(0, 0); + tft.setTextColor(WHITE); + tft.print(F("What color is displayed?")); + tft.setCursor(10, 112); + tft.setTextColor(RED); + tft.print("RED BLUE"); + while (true) { + if (!digitalRead(SW_T1)) { + EEPROM.update(EEPROM_DISPLAY, BGR_DISP); + setDisplayMode(1); + break; + } + if (!digitalRead(SW_T3)) { + EEPROM.update(EEPROM_DISPLAY, RGB_DISP); + setDisplayMode(0); + break; + } + } + tft.fillScreen(BLACK); + tft.setTextColor(YELLOW); + tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW); + tft.setCursor(20, 86); + tft.setTextColor(YELLOW); + tft.setTextSize(2); + tft.print("Maiskolben"); + tft.setCursor(35, 104); + tft.print("Welcome!"); + delay(4000); + while (!digitalRead(SW_T3) || !digitalRead(SW_T1)) delay(100); + } else { + setDisplayMode(EEPROM.read(EEPROM_DISPLAY) == BGR_DISP); + } +#endif +#ifdef INSTALL + if (EEPROM.read(EEPROM_INSTALL) != EEPROM_CHECK) { + tft.fillScreen(BLACK); + tft.setTextColor(RED, BLACK); + tft.setCursor(0, 0); + tft.setTextSize(2); + tft.println("Installation"); + for (int16_t i = -255; i < 256; i++) { + analogWrite(HEAT_LED, 255 - abs(i)); + delay(1); + } + uint16_t adc1 = 0, adc2 = 0; + while (digitalRead(SW_STBY)) { + int t = getTemperature(); + uint16_t adc = analogRead(TEMP_SENSE); + Serial.println(t); + digitalWrite(HEATER_PWM, !digitalRead(SW_T1) | !digitalRead(SW_T2) | !digitalRead(SW_T3)/* | !digitalRead(SW_UP) | !digitalRead(SW_DOWN)*/); + if (!digitalRead(SW_DOWN)) { + if (!adc) { + digitalWrite(HEATER_PWM, HIGH); + } else { + adc1 = adc; + } + } + if (!digitalRead(SW_UP)) { + if (!adc) { + digitalWrite(HEATER_PWM, HIGH); + } else { + adc2 = adc; + } + } + tft.setCursor(0, 18); + tft.print(t); + tft.println(" "); + tft.print(adc); + tft.println(" "); + tft.println(adc * adc_gain + adc_offset); + if (adc1 != 0 && adc2 != 0) { + adc_gain = DELTA_REF_T / (float)(adc2 - adc1); + adc_offset = REF_T1 - adc_gain * adc1; + tft.println(adc_gain); + tft.println(adc_offset); + } + delay(50); + } + EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower); + EEPROM.update(EEPROM_VERSION, EE_VERSION); + EEPROM.update(EEPROM_INSTALL, EEPROM_CHECK); + EEPROM.put(EEPROM_ADCTTG, adc_gain); + EEPROM.put(EEPROM_ADCOFF, adc_offset); + + tft.println("done."); + delay(1000); + asm volatile("jmp 0"); + } +#endif + if (EEPROM.read(EEPROM_VERSION) != EE_VERSION) { + force_menu = true; + } + tft.fillScreen(BLACK); + uint8_t options = EEPROM.read(EEPROM_OPTIONS); + autopower = options & 1; + bootheat = options & 2; + fahrenheit = options & 4; + if (force_menu) { + optionMenu(); + } else { + updateRevision(); + tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW); + tft.setCursor(20, 86); + tft.setTextColor(YELLOW); + tft.setTextSize(2); + tft.print("Maiskolben"); + tft.setCursor(50, 110); + tft.setTextSize(1); + tft.print("Version "); + tft.print(VERSION); + tft.setCursor(46, 120); + tft.print("HW Revision "); + tft.print(revision); + //Allow Options to be set at startup + delay(100); + attachInterrupt(digitalPinToInterrupt(SW_STBY), optionMenu, LOW); + for (int i = 0; i < 10 && !menu_dismissed; i++) { + digitalWrite(HEAT_LED, i % 2); + delay(250); + } + detachInterrupt(digitalPinToInterrupt(SW_STBY)); + } + /* + lower frequency = noisier tip + higher frequency = needs higher pwm + */ + //PWM Prescaler = 1024 31Hz + //TCCR2B = (TCCR2B & 0b11111000) | 7; + //PWM Prescaler = 256 122Hz + //TCCR2B = (TCCR2B & 0b11111000) | 6; + //PWM Prescaler = 128 245Hz + //TCCR2B = (TCCR2B & 0b11111000) | 5; // Orginal + //PWM Prescaler = 64 490Hz + //TCCR2B = (TCCR2B & 0b11111000) | 4; + //PWM Prescaler = 32 980Hz + //TCCR2B = (TCCR2B & 0b11111000) | 3; + //PWM Prescaler = 8 3.9kHz + //TCCR2B = (TCCR2B & 0b11111000) | 2 + //PWM Prescaler = 1 31kHz - no Noise + TCCR2B = (TCCR2B & 0b11111000) | 1; + stby = EEPROM.read(1); + for (uint8_t i = 0; i < 3; i++) { + stored[i] = EEPROM.read(2 + i * 2) << 8; + stored[i] |= EEPROM.read(3 + i * 2); + } + set_t = EEPROM.read(EEPROM_SET_T) << 8; + set_t |= EEPROM.read(EEPROM_SET_T + 1); + + for (uint8_t i = 0; i < 50; i++) + measureVoltage(); //measure average 50 times to get realistic results + + tft.fillScreen(BLACK); + for (uint8_t i = 0; i <= 160; i++) { // Fix for remaining Corn-Bitmap + tft.drawFastVLine(i,0,128,BLACK); + } + last_measured = getTemperature(); + Timer1.initialize(1000); + Timer1.attachInterrupt(timer_isr); + heaterPID.SetMode(AUTOMATIC); + sendNext = millis(); + if (bootheat) { + threshold_counter = TEMP_UNDER_THRESHOLD; + setOff(false); + } + if (EEPROM.read(EEPROM_ADCTTG) == 255) { //Override unset values from older versions + EEPROM.put(EEPROM_ADCTTG, adc_gain); + EEPROM.put(EEPROM_ADCOFF, adc_offset); + } + EEPROM.get(EEPROM_ADCTTG, adc_gain); + EEPROM.get(EEPROM_ADCOFF, adc_offset); +} + +void updateRevision(void) { +#if (HARDWARE_REVISION > 2) + EEPROM.update(EEPROM_REVISION, HARDWARE_REVISION); + revision = 3; +#else + if (EEPROM.read(EEPROM_VERSION) < 26 || EEPROM.read(EEPROM_REVISION) > 100) { + EEPROM.update(EEPROM_REVISION, 2); + revision = 2; + } else { + revision = EEPROM.read(EEPROM_REVISION); + } +#endif +} + +void setDisplayMode(boolean bgr) { + // tft.colorSpace(bgr); + tft.setRotation(1); // 3 +} + +void optionMenu(void) { + tft.fillScreen(BLACK); + digitalWrite(HEAT_LED, LOW); + tft.setTextSize(2); + tft.setCursor(0, 0); + tft.setTextColor(WHITE); + tft.println("Options\n"); + tft.setTextColor(WHITE); + tft.setCursor(10, 112); + tft.print("ON OFF EXIT"); + uint8_t options = 3; + uint8_t opt = 0; + boolean redraw = true; + while (true) { + if (redraw) { + tft.setCursor(0, 36); +#ifdef SHUTOFF_ACTIVE + tft.setTextColor(autopower ? GREEN : RED); +#else + tft.setTextColor(GRAY); +#endif + tft.println(" Autoshutdown"); +#ifdef BOOTHEAT_ACTIVE + tft.setTextColor(bootheat ? GREEN : RED); +#else + tft.setTextColor(GRAY); +#endif + tft.println(" Heat on boot"); + tft.setTextColor(fahrenheit ? GREEN : RED); + tft.println(" Fahrenheit"); + + tft.setCursor(0, (opt + 2) * 18); + tft.setTextColor(WHITE); + tft.print(">"); + redraw = false; + } + if (!digitalRead(SW_UP)) { + tft.setCursor(0, (opt + 2) * 18); + tft.setTextColor(BLACK); + tft.print(">"); + opt = (opt + options - 1) % options; + while (!digitalRead(SW_UP)) delay(100); + redraw = true; + } + if (!digitalRead(SW_DOWN)) { + tft.setCursor(0, (opt + 2) * 18); + tft.setTextColor(BLACK); + tft.print(">"); + opt = (opt + 1) % options; + while (!digitalRead(SW_DOWN)) delay(100); + redraw = true; + } + if (!digitalRead(SW_T1)) { + switch (opt) { + case 0: autopower = 1; break; + case 1: bootheat = 1; break; + case 2: fahrenheit = 1; break; + } + redraw = true; + } + if (!digitalRead(SW_T2)) { + switch (opt) { + case 0: autopower = 0; break; + case 1: bootheat = 0; break; + case 2: fahrenheit = 0; break; + } + redraw = true; + } + if (!digitalRead(SW_T3)) { + break; + } + } + EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower); + updateRevision(); + EEPROM.update(EEPROM_VERSION, EE_VERSION); + if (EEPROM.read(EEPROM_VERSION) < 30) { + EEPROM.put(EEPROM_ADCTTG, ADC_TO_TEMP_GAIN); + EEPROM.put(EEPROM_ADCOFF, ADC_TO_TEMP_OFFSET); + } + menu_dismissed = true; +} + +void updateEEPROM(void) { + EEPROM.update(1, stby); + for (uint8_t i = 0; i < 3; i++) { + EEPROM.update(2 + i * 2, stored[i] >> 8); + EEPROM.update(3 + i * 2, stored[i] & 0xFF); + } + EEPROM.update(8, set_t >> 8); + EEPROM.update(9, set_t & 0xFF); + EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower); +} + +void powerDown(void) { + if (power_source != POWER_LIPO) { + power_down = false; + return; + } + //Timer1.stop(); + setOff(true); + delay(10); + tft.fillScreen(BLACK); + tft.setTextSize(4); + tft.setTextColor(RED); + tft.setCursor(50, 40); + tft.print("OFF"); + delay(3000); + SPI.end(); + digitalWrite(POWER, LOW); + pinMode(POWER, OUTPUT); + delay(100); + force_redraw = true; + power_down = false; + Timer1.start(); //unsuccessful +} + +float toFahrenheit(float t) { + return t * 1.8 + 32; +} + +int getTemperature(void) { + analogRead(TEMP_SENSE);//Switch ADC MUX + uint16_t adc = median(TEMP_SENSE); +#ifdef TEST_ADC + Serial.println(adc); +#endif + if (adc >= 900) { //Illegal value, tip not plugged in - would be around 560deg + analogWrite(HEATER_PWM, 0); + if (!off) { + setError(NO_TIP); + return 999; + } + } else { + analogWrite(HEATER_PWM, pwm); //switch heater back to last value + } + //return round(adc < 210 ? (((float)adc) * 0.530805 + 38.9298) : (((float)adc) * 0.415375 + 64.6123)); //old conversion + return round(((float) adc) * adc_gain + adc_offset); +} + +void measureVoltage(void) { + analogRead(BAT_C1); //Switch analog MUX before measuring + v_c1 = v_c1 * .9 + (analogRead(BAT_C1) * 5 / 1024.0) * .1; //no divisor + analogRead(BAT_C2); + v_c2 = v_c2 * .9 + (analogRead(BAT_C2) * 5 / 512.0) * .1; //divisor 1:1 -> /2 + analogRead(BAT_C3); + v_c3 = v_c3 * .9 + (analogRead(BAT_C3) * (5.0 * 3.0) / 1024.0) * .1; //maximum measurable is ~15V + v = v_c3; + if (revision < 3) { + return; + } +#ifdef VIN + analogRead(VIN); + v_in = v_in * .9 + (analogRead(VIN) * 25 / 1024.0) * .1; //maximum measurable is ~24.5V + v = v_in; //backwards compatibility +#endif +} + +uint16_t median(uint8_t analogIn) { + uint16_t adcValue[3]; + for (uint8_t i = 0; i < 3; i++) { + adcValue[i] = analogRead(analogIn); // read the input 3 times + } + uint16_t tmp; + if (adcValue[0] > adcValue[1]) { + tmp = adcValue[0]; + adcValue[0] = adcValue[1]; + adcValue[1] = tmp; + } + if (adcValue[1] > adcValue[2]) { + tmp = adcValue[1]; + adcValue[1] = adcValue[2]; + adcValue[2] = tmp; + } + if (adcValue[0] > adcValue[1]) { + tmp = adcValue[0]; + adcValue[0] = adcValue[1]; + adcValue[1] = tmp; + } + return adcValue[1]; +} + +void timer_sw_poll(void) { + if (power_down) { + return; + } + if (!digitalRead(SW_STBY)) { + if (cnt_off_press == 100) { + setOff(!off); + } + if (cnt_off_press == 200 && power_source == POWER_LIPO) { + setOff(true); + power_down = true; + return; + } + cnt_off_press = min(201, cnt_off_press + 1); + } else { + if (cnt_off_press > 0 && cnt_off_press <= 100) { + setStandby(!stby); + } + cnt_off_press = 0; + } + boolean t1 = !digitalRead(SW_T1); + boolean t2 = !digitalRead(SW_T2); + boolean t3 = !digitalRead(SW_T3); + + //simultanious push of multiple buttons + if (t1 + t2 + t3 > 1) { + store_to = 255; + store_invalid = true; + } else if (error != NO_ERROR) { + if (!(t1 | t2 | t3)) { + store_invalid = false; + } else if (!store_invalid && t3) { + error = NO_ERROR; //dismiss + set_t_old = 0; //refresh set_t display + store_invalid = true; //wait for release + } + } else { + //all buttons released + if (!(t1 | t2 | t3)) { + if (store_to != 255) { + if (cnt_but_store <= 100) { + set_t = stored[store_to]; + setStandby(false); + updateEEPROM(); + } + } + store_to = 255; + store_invalid = false; + cnt_but_store = 0; + } else + //one button pressed + if (!store_invalid) { + store_to = t2 + 2 * t3; + if (cnt_but_store > 100) { + if (set_t != stored[store_to] && !stby) { + stored[store_to] = set_t; + cnt_but_store = 100; + updateEEPROM(); + } + } + cnt_but_store++; + } + } + boolean sw_up = !digitalRead(SW_UP); + boolean sw_down = !digitalRead(SW_DOWN); + boolean sw_changed = (sw_up != sw_up_old) || (sw_down != sw_down_old); + sw_up_old = sw_up; + sw_down_old = sw_down; + if ((sw_up && sw_down) || !(sw_up || sw_down)) { + cnt_but_press = 0; + return; + } + if (sw_up || sw_down) { + cnt_but_press++; + if ((cnt_but_press >= 100) || sw_changed) { + setStandby(false); + if (sw_up && set_t < TEMP_MAX) { + set_t++; + } + else if (sw_down && set_t > TEMP_MIN) { + set_t--; + } + if (!sw_changed) { + cnt_but_press = 97; + } + updateEEPROM(); + } + } +} + +void setStandby(boolean state) { + if (stby_layoff) { + return; + } + if (state == stby) { + return; + } + stby = state; + last_measured = cur_t; + last_temperature_drop = millis(); + last_on_state = millis() / 1000; + EEPROM.update(1, stby); +} + +void setStandbyLayoff(boolean state) { + if (state == stby_layoff) { + return; + } + stby_layoff = state; + stby = false; + last_measured = cur_t; + last_on_state = millis() / 1000; +} + +void setOff(boolean state) { + if (state == off) { + return; + } + if (!state) { + analogWrite(HEATER_PWM, 0); + } else { + setStandby(false); + } + if (power_source == POWER_USB && !state) { + state = true; //don't switch on, if powered via USB + setError(USB_ONLY); + } + last_on_state = millis() / 1000; + off = state; + wasOff = true; + last_measured = cur_t; +} + +void printTemp(float t) { + if (fahrenheit) { + t = toFahrenheit(t); + } + if (t < 100) { + tft.write(' '); + } + tft.print((int)t); +} + +void display(void) { + if (force_redraw) { + tft.fillScreen(BLACK); + } + int16_t temperature = cur_t; //buffer volatile value + boolean yell = stby || (stby_layoff && blink); + tft.drawCircle(20, 63, 8, off ? RED : yell ? YELLOW : GREEN); + tft.drawCircle(20, 63, 7, off ? RED : yell ? YELLOW : GREEN); + tft.fillRect(19, 55, 3, 3, BLACK); + tft.drawFastVLine(20, 53, 10, off ? RED : yell ? YELLOW : GREEN); + if (error != NO_ERROR) { + if (error != error_old || force_redraw) { + error_old = error; + tft.setTextSize(1); + tft.setTextColor(RED, BLACK); + tft.setCursor(0, 96); + switch (error) { + case EXCESSIVE_FALL: + tft.print(F("Error: Temperature dropped\nTip slipped out?")); + break; + case NOT_HEATING: + tft.print(F("Error: Not heating\nWeak power source or short")); + break; + case BATTERY_LOW: + tft.print(F("Error: Battery low\nReplace or charge")); + break; + case USB_ONLY: + tft.print(F("Error: Power too low\nConnect power >5V")); + break; + case NO_TIP: + tft.print(F("Error: No tip connected\nTip slipped out?")); + break; + } + tft.setTextSize(2); + tft.setTextColor(YELLOW, BLACK); + tft.setCursor(10, 112); + tft.print(F(" OK ")); + + tft.setTextColor(RED, BLACK); + tft.setCursor(36, 26); + tft.setTextSize(3); + tft.print(F(" ERR ")); + } + } else { + if (error != error_old || force_redraw) { + tft.fillRect(0, 96, 160, 16, BLACK); + error_old = NO_ERROR; + } + tft.setTextSize(2); + tft.setCursor(15, 112); + tft.setTextColor(WHITE, BLACK); + printTemp(stored[0]); + tft.write(' '); + printTemp(stored[1]); + tft.write(' '); + printTemp(stored[2]); + + if (set_t_old != set_t || old_stby != (stby || stby_layoff) || force_redraw) { + tft.setCursor(36, 26); + tft.setTextSize(3); + if (stby || stby_layoff) { + old_stby = true; + tft.setTextColor(YELLOW, BLACK); + tft.print(F("STBY ")); + } else { + old_stby = false; + set_t_old = set_t; + tft.setTextColor(WHITE, BLACK); + tft.write(' '); + printTemp(set_t); + tft.write(247); + tft.write(fahrenheit ? 'F' : 'C'); + tft.fillTriangle(149, 50, 159, 50, 154, 38, (set_t < TEMP_MAX) ? WHITE : GRAY); + tft.fillTriangle(149, 77, 159, 77, 154, 90, (set_t > TEMP_MIN) ? WHITE : GRAY); + } + } + if (!off) { +#ifdef SHUTOFF_ACTIVE + if (autopower) { + int16_t tout; + if (stby || stby_layoff) { + tout = min(max(0, (last_on_state + OFF_TIMEOUT - (millis()) / 1000)), OFF_TIMEOUT); + } else { + tout = min(max(0, (last_temperature_drop + STANDBY_TIMEOUT - (millis()) / 1000)), STANDBY_TIMEOUT); + } + tft.setTextColor(stby ? RED : YELLOW, BLACK); + tft.setTextSize(2); + tft.setCursor(46, 78); + if (tout < 600) { + tft.write('0'); + } + tft.print(tout / 60); + tft.write(':'); + if (tout % 60 < 10) { + tft.write('0'); + } + tft.print(tout % 60); + } +#endif + } else if (temperature != 999) { + tft.fillRect(46, 78, 60, 20, BLACK); + } + } + if (cur_t_old != temperature || force_redraw) { + tft.setCursor(36, 52); + tft.setTextSize(3); + if (temperature == 999) { + tft.setTextColor(RED, BLACK); + tft.print(F(" ERR ")); + tft.setCursor(44, 76); + tft.setTextSize(2); + tft.print(F("NO TIP")); + } else { + if (cur_t_old == 999) { + tft.fillRect(44, 76, 72, 16, BLACK); + } + tft.setTextColor(off ? temperature < TEMP_COLD ? CYAN : RED : tft.Color565(min(10, abs(temperature - target_t)) * 25, 250 - min(10, max(0, (abs(temperature - target_t) - 10))) * 25, 0), BLACK); + if (temperature < TEMP_COLD) { + tft.print(F("COLD ")); + } else { + tft.write(' '); + printTemp(temperature); + tft.write(247); + tft.write(fahrenheit ? 'F' : 'C'); + } + } + if (temperature < cur_t_old) { + tft.fillRect(max(0, (temperature - TEMP_COLD) / 2.4), 0, 160 - max(0, (temperature - TEMP_COLD) / 2.4), BAR_HEIGHT, BLACK); + } else if (cur_t != 999) { + for (int16_t i = max(0, (cur_t_old - TEMP_COLD) / 2.4); i < max(0, (temperature - TEMP_COLD) / 2.4); i++) { + tft.drawFastVLine(i, 0, BAR_HEIGHT, tft.Color565(min(255, max(0, i * 5)), min(255, max(0, 450 - i * 2.5)), 0)); + } + } + cur_t_old = temperature; + } + if (v_c3 > 1.0) { + tft.setTextColor(YELLOW, BLACK); + tft.setCursor(122, 5); + tft.setTextSize(2); + int power = min(15, v) * min(15, v) / 4.8 * pwm / 255; + if (power < 10) { + tft.write(' '); + } + tft.print(power); + tft.write('W'); + + if (v < 5.0) { + power_source = POWER_USB; + } else if (v_c2 < 1.0) { + power_source = POWER_CORD; + } else { + power_source = POWER_LIPO; //Set charging later to not redraw if charging mode toggles + } + if (power_source != power_source_old || force_redraw) { + tft.fillRect(0, 5, 128, 20, BLACK); + tft.fillRect(11, 25, 21, 20, BLACK); + switch (power_source) { + case POWER_CHARGING: + case POWER_LIPO: + for (uint8_t i = 0; i < 3; i++) { + tft.drawRect(11, 5 + i * 14, 20, 12, WHITE); + //tft.fillRect(12, 6+i*14, 18, 10, BLACK); + tft.drawFastVLine(31, 8 + i * 14, 6, WHITE); + } + break; + case POWER_USB: + tft.setTextSize(1); + tft.setTextColor(RED, BLACK); + tft.setCursor(0, 5); + tft.print("USB power only\nConnect power supply."); + if (!off) { + setError(USB_ONLY); + } + break; + } + power_source_old = power_source; + } + if (power_source == POWER_CORD) { + /*if (v > v_c3) { + tft.setTextSize(2); + tft.setTextColor(GREEN, BLACK); + tft.setCursor(0,5); + tft.print(v); + tft.print("V "); + } else {*/ + tft.drawBitmap(0, 5, power_cord, 24, 9, tft.Color565(max(0, min(255, (14.5 - v) * 112)), max(0, min(255, (v - 11) * 112)), 0)); + //} + } else if (power_source == POWER_LIPO || power_source == POWER_CHARGING) { + float volt[] = {v_c1, v_c2 - v_c1, v_c3 - v_c2}; + uint8_t volt_disp[] = {max(1, min(16, (volt[0] - 3.0) * 14.2)), max(1, min(16, (volt[1] - 3.0) * 14.2)), max(1, min(16, (volt[2] - 3.0) * 14.2))}; + if (power_source == POWER_CHARGING) { + uint8_t p = min(16, (millis() / 100) % 20); + for (uint8_t i = 0; i < 3; i++) { + volt_disp[i] = max(0, min(volt_disp[i], p)); + } + } + for (uint8_t i = 0; i < 3; i++) { + if (volt[i] < 3.20) { + setError(BATTERY_LOW); + tft.fillRect(13, 7 + 14 * i, volt_disp[i], 8, blink ? RED : BLACK); + } else { + tft.fillRect(13, 7 + 14 * i, volt_disp[i], 8, tft.Color565(250 - min(250, max(0, (volt[i] - 3.4) * 1000.0)), max(0, min(250, (volt[i] - 3.15) * 1000.0)), 0)); + } + tft.fillRect(13 + volt_disp[i], 7 + 14 * i, 17 - volt_disp[i], 8, BLACK); + } + } + } +#ifdef SHUTOFF_ACTIVE + if (autopower) { + if (!stby_layoff) { + if (pwm > max(20, (cur_t - 150) / 50 * round(25 - min(15, v))) + 5) { + //if (target_t-cur_t > 0.715*exp(0.0077*target_t)) { + //if (cur_t / (double)target_t < STANDBY_TEMPERATURE_DROP) { + if (autopower_repeat_under || stby) { + if (stby && !wasOff) { + setStandby(false); + } else { + last_temperature_drop = millis() / 1000; + } + } + autopower_repeat_under = true; + } else if (wasOff) { + wasOff = false; + } else { + autopower_repeat_under = false; //over the max pwm for at least two times + } + } + if (!off && !stby && millis() / 1000 > (last_temperature_drop + STANDBY_TIMEOUT)) { + setStandby(true); + } + if (!off && (stby || stby_layoff) && millis() / 1000 > (last_on_state + OFF_TIMEOUT)) { + setOff(true); + } + } +#endif + blink = !blink; + force_redraw = false; +} + +void compute(void) { +#ifndef USE_TFT_RESET + setStandbyLayoff(!digitalRead(STBY_NO)); //do not measure while heater is active, potential is not neccessary == GND +#endif + cur_t = getTemperature(); + if (off) { + target_t = 0; + if (cur_t < adc_offset + TEMP_RISE) { + threshold_counter = TEMP_UNDER_THRESHOLD; //reset counter + } + } else { + if (stby_layoff || stby) { + target_t = TEMP_STBY; + } else { + target_t = set_t; + } + if (cur_t - last_measured <= -30 && last_measured != 999) { + setError(EXCESSIVE_FALL); //decrease of more than 30 degree is uncommon, short of ring and gnd is possible. + } + if (cur_t < adc_offset + TEMP_RISE) { + if (threshold_counter == 0) { + setError(NOT_HEATING); //temperature is not reached in desired time, short of sensor and gnd too? + } else { + threshold_counter--; + } + } else { + threshold_counter = THRES_MAX_DECEED; //reset counter to a smaller value to allow small oscillation of temperature + } + } + + set_td = target_t; + cur_td = cur_t; + last_measured = cur_t; + + heaterPID.Compute(); + if (error != NO_ERROR || off) { + pwm = 0; + } else { + pwm = min(255, pid_val * 255); + } + analogWrite(HEATER_PWM, pwm); +} + +void timer_isr(void) { + if (cnt_compute >= TIME_COMPUTE_IN_MS) { + analogWrite(HEATER_PWM, 0); //switch off heater to let the low pass settle + + if (cnt_compute >= TIME_COMPUTE_IN_MS + DELAY_BEFORE_MEASURE) { + compute(); + cnt_compute = 0; + } + } + cnt_compute++; + + if (cnt_sw_poll >= TIME_SW_POLL_IN_MS) { + timer_sw_poll(); + cnt_sw_poll = 0; + } + cnt_sw_poll++; + + if (cnt_measure_voltage >= TIME_MEASURE_VOLTAGE_IN_MS) { + measureVoltage(); + cnt_measure_voltage = 0; + } + cnt_measure_voltage++; +} + +void setError(error_type e) { + error = e; + setOff(true); +} + +uint16_t serialReadTemp(void) { + uint16_t t; + uint8_t n; + n = Serial.read() - '0'; + t = min(9, max(0, n)) * 100; + n = Serial.read() - '0'; + t += min(9, max(0, n)) * 10; + n = Serial.read() - '0'; + t += min(9, max(0, n)) * 1; + return t; +} + +void loop(void) { + analogWrite(HEAT_LED, pwm); + //Switch to following if the oscillation of the led bothers you + //digitalWrite(HEAT_LED, cur_t+5 < target || (abs((int16_t)cur_t-(int16_t)target) <= 5 && (millis()/(stby?1000:500))%2)); + + if (sendNext <= millis()) { + sendNext += 100; +#ifndef TEST_ADC + Serial.print(stored[0]); + Serial.print(";"); + Serial.print(stored[1]); + Serial.print(";"); + Serial.print(stored[2]); + Serial.print(";"); + Serial.print(off ? 0 : 1); + Serial.print(";"); + Serial.print(error); + Serial.print(";"); + Serial.print(stby ? 1 : 0); + Serial.print(";"); + Serial.print(stby_layoff ? 1 : 0); + Serial.print(";"); + Serial.print(set_t); + Serial.print(";"); + Serial.print(cur_t); + Serial.print(";"); + Serial.print(pid_val); + Serial.print(";"); + Serial.print(v_c2 > 1.0 ? v_c1 : 0.0); + Serial.print(";"); + Serial.print(v_c2); + Serial.print(";"); + Serial.println(v); +#endif + Serial.flush(); + display(); + } + if (Serial.available()) { + uint16_t t = 0; + switch (Serial.read()) { + //Set new Temperature (eg. S350 to set to 350C) + case 'T': + if (Serial.available() >= 3) { + t = serialReadTemp(); + //Serial.println(t); + if (t <= TEMP_MAX && t >= TEMP_MIN) { + set_t = t; + updateEEPROM(); + } + } + break; + //Store new Preset (eg. P1200 to store 200C to Preset 1, NOT 0 indexed) + case 'P': + if (Serial.available() >= 4) { + uint8_t slot = Serial.read() - '1'; + if (slot < 3) { + t = serialReadTemp(); + if (t <= TEMP_MAX && t >= TEMP_MIN) { + stored[slot] = t; + updateEEPROM(); + } + } + } + break; + //Clear errors + case 'C': + error = NO_ERROR; + break; + //Set standby + case 'S': + setStandby(Serial.read() == '1'); + break; + //Set on/off + case 'O': + setOff(Serial.read() == '0'); + break; + } + } + delay(DELAY_MAIN_LOOP); + if (power_down) { + powerDown(); + } +} diff --git a/src/definitions.h b/src/definitions.h new file mode 100644 index 0000000..745e438 --- /dev/null +++ b/src/definitions.h @@ -0,0 +1,195 @@ +#define VERSION "3.1" +#define EE_VERSION 31 +#define EEPROM_CHECK 42 + +#define BAR_HEIGHT 4 //Should be no bigger than 5 + +/* + TIPS ARE SPECIFIED FOR 450 DEGREE MAX + If read 1023 on Analog in, the tip is turned off automatically +*/ +#define TEMP_MAX 450 +#define TEMP_MIN 200 +#define TEMP_STBY 150 +#define TEMP_COLD 50// (adc_offset + 15) + +#define SHUTOFF_ACTIVE +#define BOOTHEAT_ACTIVE + +#define STANDBY_TIMEOUT 240 // seconds without any significant temperature drop, if exceeded it will standby +#define OFF_TIMEOUT 900 // seconds in standby before turning off + +#define TEMP_RISE 0.1 //threshold temperature, that must be exceeded delta in given time: +#define TEMP_UNDER_THRESHOLD 30 // x (TIME_COMPUTE_IN_MS + DELAY_BEFORE_MEASURE) +#define THRES_MAX_DECEED 5 //max times the threshold temperature may be undercut by the current temperature + +//Temperature in degree to rise at least in given time +#define TEMP_MIN_RISE 1 +//Time in that the temperature must rise by the set temperature +#define TEMP_RISE_TIME 5000 // increase from 1000ms to 5000ms to enshure proper heating + +//#define OLD_PWM + +// RX 0 +// TX 1 +#define SW_STBY 2 +#define HEATER_PWM 3 +#define SW_DOWN 4 +#define HEAT_LED 5 +#define SW_UP 6 +#define SW_T3 7 +#define SW_T2 8 +#define SW_T1 9 +#define TFT_CS 10 // Display Pin: 7 CS +// MOSI 11 // Display Pin: 4 SDA +#define POWER 12 //use MISO PULLUP as switch +// SCK 13 // Display Pin: 3 SCK +#define TEMP_SENSE A0 +#define STBY_NO A1 // Display Pin: 5 RES +#define BAT_C3 A2 +#define BAT_C2 A3 +#define BAT_C1 A4 +#define TFT_DC A5 // Display Pin: 6 RS +#ifdef PIN_A7 +#define CHARGEDET A6 +#define VIN A7 +#endif + +#define kp 0.03 +#define ki 0.00001 +#define kd 0.0 + +#define TIME_COMPUTE_IN_MS 10 +#define TIME_MEASURE_VOLTAGE_IN_MS 200 +#define TIME_SW_POLL_IN_MS 10 +#define DELAY_BEFORE_MEASURE 10 +#define DELAY_MAIN_LOOP 10 +#define PID_SAMPLE_TIME 10 + +#define ADC_TO_TEMP_GAIN 0.54 //default value if no calibration is performed +#define ADC_TO_TEMP_OFFSET 42.8 //default value if no calibration is performed + +#define EEPROM_SET_T 8 +#define EEPROM_VERSION 10 +#define EEPROM_DISPLAY 11 +#define EEPROM_OPTIONS 12 +#define EEPROM_REVISION 13 +#define EEPROM_ADCTTG 14 +#define EEPROM_ADCOFF (EEPROM_ADCTTG + sizeof(float)) + +#define EEPROM_INSTALL 42 +#define REF_T1 275 +#define REF_T2 410 +#define DELTA_REF_T (REF_T2 - REF_T1) + + +typedef enum POWER_SOURCE { + NO_INIT, + POWER_USB, + POWER_CORD, + POWER_LIPO, + POWER_CHARGING +} p_source; +typedef enum ERROR_TYPE { + NO_ERROR, + EXCESSIVE_FALL, + NOT_HEATING, + NO_TIP, + BATTERY_LOW, + USB_ONLY +} error_type; + +const unsigned char power_cord [] PROGMEM = { + 0x00, 0x00, 0xC0, + 0x00, 0xFF, 0xC0, + 0x01, 0xFF, 0xDF, + 0x03, 0xFF, 0xDF, + 0xFB, 0xFF, 0xC0, + 0x03, 0xFF, 0xDF, + 0x01, 0xFF, 0xDF, + 0x00, 0xFF, 0xC0, + 0x00, 0x00, 0xC0 +}; + +const unsigned char maiskolben [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xe0, 0x60, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xcf, 0xff, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xdc, 0xf7, + 0x18, 0x47, 0x89, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, + 0x26, 0x78, 0xc6, 0x33, 0x9c, 0x7f, 0x0c, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x08, 0x23, 0x49, 0xe6, 0x1b, 0xff, 0xf8, 0x39, 0xd0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xc5, 0x31, 0xcf, 0xe7, 0x3f, 0xfe, 0x14, 0x21, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x47, 0xf9, 0xff, 0xff, 0xcc, + 0x40, 0x08, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x4e, + 0x0f, 0xc1, 0x08, 0x04, 0x63, 0x00, 0x21, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x47, 0xe0, 0x0c, 0x01, 0x08, 0x07, 0xaf, 0x80, 0x60, 0x84, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x02, 0x0c, 0x01, 0x8c, 0xc7, 0xa4, 0x80, 0xe0, 0xf4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0x0c, 0x60, 0x9f, 0xc7, + 0xf0, 0xf3, 0x33, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x83, + 0x0e, 0xe0, 0x77, 0x69, 0x9a, 0xf7, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0xfd, 0xb7, 0xb6, 0x67, 0x79, 0x9f, 0xfe, 0xee, 0x12, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0xfc, 0xf3, 0xbe, 0xff, 0xfb, 0xff, 0xf8, 0x46, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xcd, 0xcf, 0xbc, 0x63, 0x18, 0x6e, + 0x21, 0x80, 0x42, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8c, 0xfe, + 0x08, 0x42, 0x08, 0x44, 0x21, 0x48, 0x42, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x84, 0xe2, 0x1e, 0x4a, 0x18, 0x44, 0xe1, 0xc0, 0x63, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x63, 0x3c, 0x7b, 0x18, 0x6e, 0x60, 0x80, 0x31, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x65, 0xec, 0x71, 0x08, 0x22, + 0x10, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x39, + 0x84, 0x21, 0x8c, 0x33, 0x10, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x38, 0x84, 0x00, 0x08, 0x32, 0x10, 0x02, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x58, 0x10, 0x84, 0x00, 0x08, 0x20, 0x10, 0x03, 0xf1, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x70, 0x44, 0x60, 0x08, 0x23, + 0x30, 0xc4, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0x00, + 0xc7, 0x1f, 0xff, 0xff, 0x1f, 0x84, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0xc0, 0x7f, 0x82, 0x10, 0x8c, 0x03, 0x08, 0x84, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x78, 0x82, 0x10, 0x88, 0x01, 0x08, 0x84, 0x10, 0x87, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7c, 0x28, 0x82, 0x18, 0xc8, 0x21, + 0x08, 0x82, 0x10, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x28, + 0x42, 0x08, 0xc0, 0x21, 0x8c, 0x42, 0x10, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, + 0x00, 0x3f, 0xfc, 0x18, 0x42, 0x08, 0xc4, 0x11, 0x8c, 0x42, 0x10, 0x83, 0x80, 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xfe, 0x89, 0xfc, 0xff, 0x94, 0x0c, 0x42, 0x08, 0x44, 0x30, 0x88, 0x42, 0x10, 0x03, + 0x18, 0x00, 0x00, 0x00, 0x06, 0xff, 0xff, 0xff, 0xff, 0x00, 0x04, 0x0c, 0x62, 0x00, 0x04, 0x00, + 0x88, 0x42, 0x10, 0x03, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x04, 0x1c, + 0x20, 0x00, 0x88, 0x00, 0x88, 0x42, 0x10, 0xc7, 0x06, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x80, 0x1c, 0x23, 0x10, 0xc8, 0x00, 0x8c, 0x43, 0x30, 0xf9, 0x01, 0xf0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x37, 0x9d, 0xbc, 0x30, 0xfe, 0xe7, 0x6f, 0x11, + 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xce, 0x04, 0x4c, 0xe7, 0x9e, 0x2f, + 0x31, 0x8e, 0x64, 0x31, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xce, 0x23, + 0x8f, 0xe7, 0x1d, 0xc7, 0x31, 0x8c, 0x64, 0x11, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x46, 0x30, 0xcf, 0xf6, 0x1c, 0xe6, 0x39, 0xcc, 0x64, 0x11, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x44, 0x38, 0xcc, 0x76, 0x18, 0xc2, 0x3d, 0x2c, 0x6c, 0x13, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x38, 0xcc, 0x66, 0x10, 0x86, + 0x31, 0x08, 0x48, 0x13, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x44, 0x38, + 0xc0, 0x44, 0x10, 0x84, 0x21, 0x00, 0x48, 0x12, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x40, 0x30, 0x80, 0x44, 0x01, 0x04, 0x20, 0x00, 0x48, 0x1e, 0x00, 0x01, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x84, 0x44, 0x21, 0x04, 0x20, 0x0c, 0xfc, 0x26, + 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x86, 0xcc, 0x21, 0x04, + 0x23, 0x0f, 0x8e, 0xc2, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x00, + 0x8e, 0xff, 0xff, 0x0f, 0xfb, 0xf9, 0x84, 0x46, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x71, 0xf0, 0x6c, 0x31, 0xc4, 0x40, 0x19, 0x00, 0x46, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xb8, 0x30, 0x4c, 0x21, 0x04, 0x40, 0x11, 0x00, 0x46, + 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x20, 0x20, 0x48, 0x20, 0x00, + 0x02, 0x11, 0x08, 0x44, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x28, + 0x20, 0x08, 0x02, 0x00, 0x80, 0x11, 0x08, 0x4c, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x22, 0x18, 0x20, 0x00, 0x02, 0x00, 0x80, 0x13, 0x1c, 0xd8, 0x00, 0x00, 0x03, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x18, 0x20, 0x00, 0x42, 0x08, 0x84, 0x1d, 0xe6, 0x38, + 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x18, 0x21, 0x1c, 0x7e, 0x0e, + 0x7f, 0x90, 0x44, 0x38, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7c, + 0x7c, 0xcc, 0x31, 0x88, 0x42, 0x10, 0x44, 0x38, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1e, 0x42, 0x10, 0x08, 0x21, 0x08, 0x42, 0x10, 0x84, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x40, 0x00, 0x08, 0x20, 0x08, 0x62, 0x10, 0x8c, 0x70, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x08, 0x00, 0x08, + 0xc6, 0x21, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8, + 0x60, 0x08, 0x44, 0x3f, 0xff, 0xe7, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html