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/.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/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..f22c917 --- /dev/null +++ b/src/Maiskolben_TFT.ino @@ -0,0 +1,1005 @@ +#include +#include +#include +#include +#include "TimerOne.h" +#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 + +/* + * 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 THT +//#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 BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define CYAN 0x07FF +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#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; + //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); + 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(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); +} + +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(); + } +} + +uint16_t serialReadTemp() { + 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; +} diff --git a/src/definitions.h b/src/definitions.h new file mode 100644 index 0000000..58c2385 --- /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 (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 30 //threshold temperature, that must be exceeded delta in given time: +#define TEMP_UNDER_THRESHOLD 80 // x (TIME_COMPUTE_IN_MS + DELAY_BEFORE_MEASURE) +#define THRES_MAX_DECEED 2 //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 10 +//Time in that the temperature must rise by the set temperature +#define TEMP_RISE_TIME 1000 + +//#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 +// MOSI 11 +#define POWER 12 //use MISO PULLUP as switch +// SCK 13 +#define TEMP_SENSE A0 +#define STBY_NO A1 +#define BAT_C3 A2 +#define BAT_C2 A3 +#define BAT_C1 A4 +#define TFT_DC A5 +#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