2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hogyha sikeresen O/PLED illetve hagyományos alfanumerikus - esetleg tán grafikus kijelzővel gyűlik meg a baja valakinek:)
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Ez biztosan jól jön kezdőknek, meg lustáknak!

De ha kicsit átgondoljuk a működését Mega esetében!
1 LCD pont írása az 2 Byte, tegyük fel 100 pontott akarunk megváltoztatni, az 100*2 Byte írása.
A 3 felé osztott adatvonal miatt ebben a formában 200 * meghivni:

Kód: Egész kijelölése

  #define write8inline(d) {                                              \
    PORTH = (PORTH&B10000111)|(((d)&B11000000)>>3)|(((d)&B00000011)<<5); \
    PORTB = (PORTB&B01001111)|(((d)&B00101100)<<2);                      \
    PORTG = (PORTG&B11011111)|(((d)&B00010000)<<1);                      \
    WR_STROBE; }
Míg ha áthuzalozódd, mondjuk az PORTA-ra, csak így nézne ki.

Kód: Egész kijelölése

PORTA = d;
Szerintem megvadul a grafikus rajzolás tölle!
Avatar
Robert
Elektronbűvölő
Hozzászólások: 10191
Csatlakozott: 2005. december 9. péntek, 7:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Robert »

Az alapötlet az volt, hogy ke kelljen áthuzalozni a kijelzőt, hanem közvetlenül a Megára berakni elég legyen.
Valamit - valamiért... :)

Az UNO-ból van származtatva, ott meg egyben nincs is komplett portom (ott csak 2 db-ból áll össze legalább :) )
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Unora rakjál SPI buszos LCDt hogy maradjon még szabad portod!
Az már úgysem lesz elég gyors sosem!
Avatar
csabeszq
Bitfaragó
Hozzászólások: 678
Csatlakozott: 2012. szeptember 5. szerda, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: csabeszq »

Nekem simán megy Mega esetén, bár brutális megoldást választottam.

Előre legyártom a sorozatot memóriába:

Kód kb ilyen:

Kód: Egész kijelölése

  uint8_t ram_values[3*256];
  
  uint8_t * loc = ram_values + 3 * kikuldendo_bajt;
  PORTH = *loc++;
  PORTB = *loc++;
  PORTG = *loc++; 
Assembly valami ilyesmi:

Kód: Egész kijelölése

  ld r24, Z+
  sts PORTH, r24
  ld r24, Z+
  out PORTB, r24
  ld r24, Z+
  out PORTG, r24
A fenti assembly úgy megy, mint állat, ezzel sikerült MHz feletti átviteli értéket produkálni. Mindennek ára van, jelen esetben 768 byte RAM-mal fizetek a sebességért, meg 8 pin helyett 24-et foglalok le.
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Nem értelek!?

8 drótnak 2 vége van. Olyan nehéz azt felfórasztani?
Vagy nem lehet megjegyezni mit, hova kel kötni?

Különbe a megán pont a sok port pint szeretem.
Avatar
Robert
Elektronbűvölő
Hozzászólások: 10191
Csatlakozott: 2005. december 9. péntek, 7:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Robert »

Ha külön, oldaltkivezetéses a panel, akkor nem is gond a közvetlen portrakötés. Azonban ha UNO Shield kialakítású, ami közvetln rányomható a MEGA-ra, akkor nem kéne kábelezgetni :) . A feltevés a Mega esetén, hogy a TFT port 3 Arduino/Mega portból áll össze - innen származott....
imi535
Újonc
Újonc
Hozzászólások: 2
Csatlakozott: 2016. október 10. hétfő, 18:54

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: imi535 »

Sziasztok!Olyan kérdésem lenne,hogy egy melévö müködö programon szeretném a kijelzöt nagyobra cserélni!st7735-ös-1.8"ast ,ili 9341 2.4"TFT SPI 240x320 ra!Szerintetek ez megoldhato egyszerü felhasználonak?Vagy az egész programot újra kellene irn?Ebben kérnék segitséget!Bár programot irni nem tudok,csak hobbi felhasználo vagyok!De ha valaki tudná modositani a meglévö sketch et nem lennék hálátlan!Elöre is köszönöm a segitö hozzászolást!A hardver pro micro!

Kód: Egész kijelölése

// ----------------------------------------------------------------------------
// Reflow Oven Controller
// (c) 2014 Karl Pitrich <karl@pitrich.com>
// (c) 2012-2013 Ed Simmons
// ----------------------------------------------------------------------------

//#define FAKE_HW 1
//#define PIDTUNE 1 // autotune wouldn't fit in the 28k available on my arduino pro micro.

// run a calibration loop that measures how many timer ticks occur between 2 zero corssings
// FIXME: does not work reliably at the moment, so a oscilloscope-determined value is used.
//#define WITH_CALIBRATION 1 // loop timing calibration
#define DEFAULT_LOOP_DELAY 89 // should be about 16% less for 60Hz mains

#include <avr/eeprom.h>
#include <EEPROM.h>
#include <PID_v1.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Menu.h>
#include <TimerOne.h>
#include <ClickEncoder.h>
#include "temperature.h"
#include "helpers.h"
#ifdef FAKE_HW
#include <TimerThree.h>
#endif
#ifdef PIDTUNE
#include <PID_AutoTune_v0.h>
#endif
// ----------------------------------------------------------------------------

const char * ver = "3.1";

// ----------------------------------------------------------------------------

#define MS_PER_SINE 100       // for 50Hz mains; 100ms per sinusoid
//#define MS_PER_SINE  83.333 // for 60Hz Mains; 83,3333ms per sinusoid

// ----------------------------------------------------------------------------
// Hardware Configuration 

// 1.8" TFT via SPI -> breadboard
#ifdef FAKE_HW
#define LCD_CS  10
#define LCD_DC   7
#define LCD_RST  8
#else 
#define LCD_CS   10
#define LCD_DC   9
#define LCD_RST  8
#endif

// Thermocouple via SPI
#define THERMOCOUPLE1_CS  3

#define PIN_HEATER   0 // SSR for the heater
#define PIN_FAN      1 // SSR for the fan

#define PIN_ZX       2 // pin for zero crossing detector
#define INT_ZX       1 // interrupt for zero crossing detector
                       // Leonardo == Pro Micro:
                       //   Pin: 3 2 0 1 7
                       //   Int: 0 1 2 3 4 

// ----------------------------------------------------------------------------

#define WITH_SPLASH 1

// ----------------------------------------------------------------------------

volatile uint32_t timerTicks     = 0;
volatile uint32_t zeroCrossTicks = 0;
volatile uint8_t  phaseCounter   = 0;

char buf[20]; // generic char buffer

// ----------------------------------------------------------------------------

// data type for the values used in the reflow profile
typedef struct profileValues_s {
  int16_t soakTemp;
  int16_t soakDuration;
  int16_t peakTemp;
  int16_t peakDuration;
  double  rampUpRate;
  double  rampDownRate;
  uint8_t checksum;
} Profile_t;

Profile_t activeProfile; // the one and only instance
int activeProfileId = 0;

int idleTemp = 50; // the temperature at which to consider the oven safe to leave to cool naturally
int fanAssistSpeed = 33; // default fan speed

const uint8_t maxProfiles = 30;

// EEPROM offsets
const uint16_t offsetFanSpeed   = maxProfiles * sizeof(Profile_t) + 1; // one byte
const uint16_t offsetProfileNum = maxProfiles * sizeof(Profile_t) + 2; // one byte
const uint16_t offsetPidConfig  = maxProfiles * sizeof(Profile_t) + 3; // sizeof(PID_t)

Thermocouple A;

// ----------------------------------------------------------------------------

uint32_t startCycleZeroCrossTicks;
uint32_t lastUpdate = 0;
uint32_t lastDisplayUpdate = 0;

// ----------------------------------------------------------------------------
// UI

// NB: Adafruit GFX ASCII-Table is bogous: https://github.com/adafruit/Adafruit-GFX-Library/issues/22
//
Adafruit_ST7735 tft = Adafruit_ST7735(LCD_CS, LCD_DC, LCD_RST);

#ifdef FAKE_HW
ClickEncoder Encoder(A0, A1, A2, 2);
#else
ClickEncoder Encoder(A1, A0, A2, 2);
#endif

Menu::Engine Engine;

int16_t encMovement;
int16_t encAbsolute;
int16_t encLastAbsolute = -1;

const uint8_t menuItemsVisible = 5;
const uint8_t menuItemHeight = 12;
bool menuUpdateRequest = true;
bool initialProcessDisplay = false;

// ----------------------------------------------------------------------------
// state machine

typedef enum {
  None     = 0,
  Idle     = 1,
  Settings = 2,
  Edit     = 3,

  UIMenuEnd = 9,

  RampToSoak = 10,
  Soak,
  RampUp,
  Peak,
  RampDown,
  CoolDown,

  Complete = 20,

  Tune = 30
} State;

State currentState  = Idle;
State previousState = Idle;
bool stateChanged = false;
uint32_t stateChangedTicks = 0;

// ----------------------------------------------------------------------------

// track menu item state to improve render preformance
typedef struct {
  const Menu::Item_t *mi;
  uint8_t pos;
  bool current;
} LastItemState_t;

LastItemState_t currentlyRenderedItems[menuItemsVisible];

void clearLastMenuItemRenderState() {
  // memset(&currentlyRenderedItems, 0xff, sizeof(LastItemState_t) * menuItemsVisible);
  for (uint8_t i = 0; i < menuItemsVisible; i++) {
    currentlyRenderedItems[i].mi = NULL;
    currentlyRenderedItems[i].pos = 0xff;
    currentlyRenderedItems[i].current = false;
  }
}

// ----------------------------------------------------------------------------

extern const Menu::Item_t miRampUpRate, miRampDnRate, miSoakTime, 
                          miSoakTemp, miPeakTime, miPeakTemp,
                          miLoadProfile, miSaveProfile,
                          miPidSettingP, miPidSettingI, miPidSettingD,
                          miFanSettings;

// ----------------------------------------------------------------------------
// PID

uint8_t fanValue;
uint8_t heaterValue;

double Setpoint;
double Input;
double Output;

typedef struct {
  double Kp;
  double Ki;
  double Kd;
} PID_t;

PID_t heaterPID = { 4.00, 0.05,  2.00 };
PID_t fanPID    = { 1.00, 0.03, 10.00 };

PID PID(&Input, &Output, &Setpoint, heaterPID.Kp, heaterPID.Ki, heaterPID.Kd, DIRECT);

#ifdef PIDTUNE
PID_ATune PIDTune(&Input, &Output);

double aTuneStep       =  50,
       aTuneNoise      =   1,
       aTuneStartValue =  50; // is set to Output, i.e. 0-100% of Heater

unsigned int aTuneLookBack = 30;
#endif

// ----------------------------------------------------------------------------

bool menuExit(const Menu::Action_t a) {
  clearLastMenuItemRenderState();
  Engine.lastInvokedItem = &Menu::NullItem;
  menuUpdateRequest = false;
  return false;
}

// ----------------------------------------------------------------------------

bool menuDummy(const Menu::Action_t a) {
  return true;
}

// ----------------------------------------------------------------------------

void printDouble(double val, uint8_t precision = 1) {
  ftoa(buf, val, precision);
  tft.print(buf);
}

// ----------------------------------------------------------------------------

void getItemValuePointer(const Menu::Item_t *mi, double **d, int16_t **i) {
  if (mi == &miRampUpRate)  *d = &activeProfile.rampUpRate;
  if (mi == &miRampDnRate)  *d = &activeProfile.rampDownRate;
  if (mi == &miSoakTime)    *i = &activeProfile.soakDuration;
  if (mi == &miSoakTemp)    *i = &activeProfile.soakTemp;
  if (mi == &miPeakTime)    *i = &activeProfile.peakDuration;
  if (mi == &miPeakTemp)    *i = &activeProfile.peakTemp;
  if (mi == &miPidSettingP) *d = &heaterPID.Kp;
  if (mi == &miPidSettingI) *d = &heaterPID.Ki;
  if (mi == &miPidSettingD) *d = &heaterPID.Kd; 
  if (mi == &miFanSettings) *i = &fanAssistSpeed;
}

// ----------------------------------------------------------------------------

bool isPidSetting(const Menu::Item_t *mi) {
  return mi == &miPidSettingP || mi == &miPidSettingI || mi == &miPidSettingD;
}

bool isRampSetting(const Menu::Item_t *mi) {
  return mi == &miRampUpRate || mi == &miRampDnRate;
}

// ----------------------------------------------------------------------------

bool getItemValueLabel(const Menu::Item_t *mi, char *label) {
  int16_t *iValue = NULL;
  double  *dValue = NULL;
  char *p;
  
  getItemValuePointer(mi, &dValue, &iValue);

  if (isRampSetting(mi) || isPidSetting(mi)) {
    p = label;
    ftoa(p, *dValue, (isPidSetting(mi)) ? 2 : 1); // need greater precision with pid values
    p = label;
    
    if (isRampSetting(mi)) {
      while(*p != '\0') p++;
      *p++ = 0xf7; *p++ = 'C'; *p++ = '/'; *p++ = 's';
      *p = '\0';
    }
  }
  else {
    if (mi == &miPeakTemp || mi == &miSoakTemp) {
      itostr(label, *iValue, "\367C");
    }
    if (mi == &miPeakTime || mi == &miSoakTime) {
      itostr(label, *iValue, "s");
    }
    if (mi == &miFanSettings) {
      itostr(label, *iValue, "%");
    }
  }

  return dValue || iValue;
}

// ----------------------------------------------------------------------------

bool editNumericalValue(const Menu::Action_t action) { 
  if (action == Menu::actionDisplay) {
    bool initial = currentState != Edit;
    currentState = Edit;

    if (initial) {
      tft.setTextColor(ST7735_BLACK, ST7735_WHITE);
      tft.setCursor(10, 80);
      tft.print("Edit & click to save.");
      Encoder.setAccelerationEnabled(true);
    }

    for (uint8_t i = 0; i < menuItemsVisible; i++) {
      if (currentlyRenderedItems[i].mi == Engine.currentItem) {
        uint8_t y = currentlyRenderedItems[i].pos * menuItemHeight + 2;

        if (initial) {
          tft.fillRect(69, y - 1, 60, menuItemHeight - 2, ST7735_RED);
        }

        tft.setCursor(70, y);
        break;
      }
    }

    tft.setTextColor(ST7735_WHITE, ST7735_RED);

    int16_t *iValue = NULL;
    double  *dValue = NULL;
    getItemValuePointer(Engine.currentItem, &dValue, &iValue);

    if (isRampSetting(Engine.currentItem) || isPidSetting(Engine.currentItem)) {
      double tmp;
      double factor = (isPidSetting(Engine.currentItem)) ? 100 : 10;
      
      if (initial) {
        tmp = *dValue;
        tmp *= factor;
        encAbsolute = (int16_t)tmp;
      }
      else {
        tmp = encAbsolute;
        tmp /= factor;
        *dValue = tmp;
      }      
    }
    else {
      if (initial) encAbsolute = *iValue;
      else *iValue = encAbsolute;
    }

    getItemValueLabel(Engine.currentItem, buf);
    tft.print(buf);
    tft.setTextColor(ST7735_BLACK, ST7735_WHITE);
  }

  if (action == Menu::actionParent || action == Menu::actionTrigger) {
    clearLastMenuItemRenderState();
    menuUpdateRequest = true;
    Engine.lastInvokedItem = &Menu::NullItem;


    if (currentState == Edit) { // leave edit mode, return to menu
      if (isPidSetting(Engine.currentItem)) {
        savePID();
      }
      else if (Engine.currentItem == &miFanSettings) {
        saveFanSpeed();
      }
      // don't autosave profile, so that one can do "save as" without overwriting the current profile

      currentState = Settings;
      Encoder.setAccelerationEnabled(false);
      return false;
    }

    return true;
  }
}

// ----------------------------------------------------------------------------

bool factoryReset(const Menu::Action_t action) {
#ifndef PIDTUNE
  if (action == Menu::actionDisplay) {
    bool initial = currentState != Edit;
    currentState = Edit;

    if (initial) { // TODO: add eyecandy: colors or icons
      tft.setTextColor(ST7735_BLACK, ST7735_WHITE);
      tft.setCursor(10, 80);
      tft.print("Click to confirm");
      tft.setCursor(10, 90);
      tft.print("Doubleclick to exit");
    }
  }

  if (action == Menu::actionTrigger) { // do it
    factoryReset();
    tft.fillScreen(ST7735_WHITE);
    Engine.navigate(Engine.getParent());
    return false;
  }

  if (action == Menu::actionParent) {
    if (currentState == Edit) { // leave edit mode only, returning to menu
      currentState = Settings;
      clearLastMenuItemRenderState();
      return false;
    }
  }
#endif // PIDTUNE
}

// ----------------------------------------------------------------------------

void saveProfile(unsigned int targetProfile, bool quiet = false);

// ----------------------------------------------------------------------------

bool saveLoadProfile(const Menu::Action_t action) {
#ifndef PIDTUNE
  bool isLoad = Engine.currentItem == &miLoadProfile;

  if (action == Menu::actionDisplay) {
    bool initial = currentState != Edit;
    currentState = Edit;

    tft.setTextColor(ST7735_BLACK, ST7735_WHITE);

    if (initial) {
      encAbsolute = activeProfileId;      
      tft.setCursor(10, 90);
      tft.print("Doubleclick to exit");
    }

    if (encAbsolute > maxProfiles) encAbsolute = maxProfiles;
    if (encAbsolute <  0) encAbsolute =  0;

    tft.setCursor(10, 80);
    tft.print("Click to ");
    tft.print((isLoad) ? "load " : "save ");
    tft.setTextColor(ST7735_WHITE, ST7735_RED);
    tft.print(encAbsolute);
  }

  if (action == Menu::actionTrigger) {
    (isLoad) ? loadProfile(encAbsolute) : saveProfile(encAbsolute);
    tft.fillScreen(ST7735_WHITE);
    Engine.navigate(Engine.getParent());
    return false;
  }

  if (action == Menu::actionParent) {    
    if (currentState == Edit) { // leave edit mode only, returning to menu
      currentState = Settings;
      clearLastMenuItemRenderState();
      return false;
    }
  }
#endif // PIDTUNE
}

// ----------------------------------------------------------------------------

void toggleAutoTune();

bool cycleStart(const Menu::Action_t action) {
  if (action == Menu::actionDisplay) {
    startCycleZeroCrossTicks = zeroCrossTicks;
    menuExit(action);

#ifndef PIDTUNE    
    currentState = RampToSoak;
#else
    toggleAutoTune();
#endif
    initialProcessDisplay = false;
    menuUpdateRequest = false;
  }

  return true;
}

// ----------------------------------------------------------------------------

void renderMenuItem(const Menu::Item_t *mi, uint8_t pos) {
  //ScopedTimer tm("  render menuitem");
  bool isCurrent = Engine.currentItem == mi;
  uint8_t y = pos * menuItemHeight + 2;

  if (currentlyRenderedItems[pos].mi == mi 
      && currentlyRenderedItems[pos].pos == pos 
      && currentlyRenderedItems[pos].current == isCurrent) 
  {
    return; // don't render the same item in the same state twice
  }

  tft.setCursor(10, y);

  // menu cursor bar
  tft.fillRect(8, y - 2, tft.width() - 16, menuItemHeight, isCurrent ? ST7735_BLUE : ST7735_WHITE);
  if (isCurrent) tft.setTextColor(ST7735_WHITE, ST7735_BLUE);
  else tft.setTextColor(ST7735_BLACK, ST7735_WHITE);

  tft.print(Engine.getLabel(mi));

  // show values if in-place editable items
  if (getItemValueLabel(mi, buf)) {
    tft.print(' '); tft.print(buf); tft.print("   ");
  }

  // mark items that have children
  if (Engine.getChild(mi) != &Menu::NullItem) {
    tft.print(" \x10   "); // 0x10 -> filled right arrow
  }

  currentlyRenderedItems[pos].mi = mi;
  currentlyRenderedItems[pos].pos = pos;
  currentlyRenderedItems[pos].current = isCurrent;
}

// ----------------------------------------------------------------------------
// Name, Label, Next, Previous, Parent, Child, Callback

MenuItem(miExit, "", Menu::NullItem, Menu::NullItem, Menu::NullItem, miCycleStart, menuExit);

#ifndef PIDTUNE
MenuItem(miCycleStart,  "Start Cycle",  miEditProfile, Menu::NullItem, miExit, Menu::NullItem, cycleStart);
#else
MenuItem(miCycleStart,  "Start Autotune",  miEditProfile, Menu::NullItem, miExit, Menu::NullItem, cycleStart);
#endif
MenuItem(miEditProfile, "Edit Profile", miLoadProfile, miCycleStart,   miExit, miRampUpRate, menuDummy);
  MenuItem(miRampUpRate, "Ramp up  ",   miSoakTemp,      Menu::NullItem, miEditProfile, Menu::NullItem, editNumericalValue);
  MenuItem(miSoakTemp,   "Soak temp", miSoakTime,      miRampUpRate,   miEditProfile, Menu::NullItem, editNumericalValue);
  MenuItem(miSoakTime,   "Soak time", miPeakTemp,      miSoakTemp,     miEditProfile, Menu::NullItem, editNumericalValue);
  MenuItem(miPeakTemp,   "Peak temp", miPeakTime,      miSoakTime,     miEditProfile, Menu::NullItem, editNumericalValue);
  MenuItem(miPeakTime,   "Peak time", miRampDnRate,    miPeakTemp,     miEditProfile, Menu::NullItem, editNumericalValue);
  MenuItem(miRampDnRate, "Ramp down", Menu::NullItem,  miPeakTime,     miEditProfile, Menu::NullItem, editNumericalValue);
MenuItem(miLoadProfile,  "Load Profile",  miSaveProfile,  miEditProfile, miExit, Menu::NullItem, saveLoadProfile);
MenuItem(miSaveProfile,  "Save Profile",  miFanSettings,  miLoadProfile, miExit, Menu::NullItem, saveLoadProfile);
MenuItem(miFanSettings,  "Fan Speed",  miPidSettings,  miSaveProfile, miExit, Menu::NullItem, editNumericalValue);
MenuItem(miPidSettings,  "PID Settings",  miFactoryReset, miFanSettings, miExit, miPidSettingP,  menuDummy);
  MenuItem(miPidSettingP,  "Heater Kp",  miPidSettingI, Menu::NullItem, miPidSettings, Menu::NullItem, editNumericalValue);
  MenuItem(miPidSettingI,  "Heater Ki",  miPidSettingD, miPidSettingP,  miPidSettings, Menu::NullItem, editNumericalValue);
  MenuItem(miPidSettingD,  "Heater Kd",  Menu::NullItem, miPidSettingI, miPidSettings, Menu::NullItem, editNumericalValue);
MenuItem(miFactoryReset, "Factory Reset", Menu::NullItem, miPidSettings, miExit, Menu::NullItem, factoryReset);

// ----------------------------------------------------------------------------

#define NUMREADINGS 10

typedef struct {
  double temp;
  uint16_t ticks;
} Temp_t;

Temp_t airTemp[NUMREADINGS];

double readingsT1[NUMREADINGS]; // the readings used to make a stable temp rolling average
double runningTotalRampRate;
double rampRate = 0;
double rateOfRise = 0;          // the result that is displayed
double totalT1 = 0;             // the running total
double averageT1 = 0;           // the average
uint8_t index = 0;              // the index of the current reading

// ----------------------------------------------------------------------------
// Ensure that Solid State Relais are off when starting
//
void setupRelayPins(void) {
  DDRD  |= (1 << 2) | (1 << 3); // output
  //PORTD &= ~((1 << 2) | (1 << 3));
  PORTD |= (1 << 2) | (1 << 3); // off
}

void killRelayPins(void) {
  Timer1.stop();
  detachInterrupt(INT_ZX);
  PORTD |= (1 << 2) | (1 << 3);
}

// ----------------------------------------------------------------------------
// wave packet control: only turn the solid state relais on for a percentage 
// of complete sinusoids (i.e. 1x 360°)

#define CHANNELS       2
#define CHANNEL_HEATER 0
#define CHANNEL_FAN    1

typedef struct Channel_s {
  volatile uint8_t target; // percentage of on-time
  uint8_t state;           // current state counter
  int32_t next;            // when the next change in output shall occur  
  bool action;             // hi/lo active
  uint8_t pin;             // io pin of solid state relais
} Channel_t;

Channel_t Channels[CHANNELS] = {
  // heater
  { 0, 0, 0, false, 2 }, // PD2 == RX == Arduino Pin 0
  // fan
  { 0, 0, 0, false, 3 }  // PD3 == TX == Arduino Pin 1
};

// delay to align relay activation with the actual zero crossing
uint16_t zxLoopDelay = 0;

#ifdef WITH_CALIBRATION
// calibrate zero crossing: how many timerIsr happen within one zero crossing
#define zxCalibrationLoops 128
struct {
  volatile int8_t iterations;
  volatile uint8_t measure[zxCalibrationLoops];
} zxLoopCalibration = {
  0, {}
};
#endif

// ----------------------------------------------------------------------------
// Zero Crossing ISR; per ZX, process one channel per interrupt only
// NB: use native port IO instead of digitalWrite for better performance
void zeroCrossingIsr(void) {
  static uint8_t ch = 0;

  // reset phase control timer
  phaseCounter = 0;
  TCNT1 = 0;

  zeroCrossTicks++;

  // calculate wave packet parameters
  Channels[ch].state += Channels[ch].target;
  if (Channels[ch].state >= 100) {
    Channels[ch].state -= 100;
    Channels[ch].action = false;
  }
  else {
    Channels[ch].action = true;
  }
  Channels[ch].next = timerTicks + zxLoopDelay; // delay added to reach the next zx

  ch = ((ch + 1) % CHANNELS); // next channel

#ifdef WITH_CALIBRATION
  if (zxLoopCalibration.iterations < zxCalibrationLoops) {
    zxLoopCalibration.iterations++;
  }
#endif
}

// ----------------------------------------------------------------------------
// timer interrupt handling

void timerIsr(void) { // ticks with 100µS
  static uint32_t lastTicks = 0;

  // phase control for the fan 
  if (++phaseCounter > 90) {
    phaseCounter = 0;
  }

  if (phaseCounter > Channels[CHANNEL_FAN].target) {
    PORTD &= ~(1 << Channels[CHANNEL_FAN].pin);
  }
  else {
    PORTD |=  (1 << Channels[CHANNEL_FAN].pin);
  }

  // wave packet control for heater
  if (Channels[CHANNEL_HEATER].next > lastTicks // FIXME: this looses ticks when overflowing
      && timerTicks > Channels[CHANNEL_HEATER].next) 
  {
    if (Channels[CHANNEL_HEATER].action) PORTD |= (1 << Channels[CHANNEL_HEATER].pin);
    else PORTD &= ~(1 << Channels[CHANNEL_HEATER].pin);
    lastTicks = timerTicks;
  }

  // handle encoder + button
  if (!(timerTicks % 10)) {
    Encoder.service();
  }

  timerTicks++;

#ifdef WITH_CALIBRATION
  if (zxLoopCalibration.iterations < zxCalibrationLoops) {
    zxLoopCalibration.measure[zxLoopCalibration.iterations]++;
  }
#endif
}

// ----------------------------------------------------------------------------

void abortWithError(int error) {
  killRelayPins();

  tft.setTextColor(ST7735_WHITE, ST7735_RED);
  tft.fillScreen(ST7735_RED);

  tft.setCursor(10, 10);
  
  if (error < 9) {
    tft.println("Thermocouple Error");
    tft.setCursor(10, 30);
    switch (error) {
      case 0b001:
        tft.println("Open Circuit");
        break;
      case 0b010:
        tft.println("GND Short");
        break;
      case 0b100:
        tft.println("VCC Short");
        break;
    }
    tft.setCursor(10, 60);
    tft.println("Power off,");
    tft.setCursor(10, 75);
    tft.println("check connections");
  }
  else {
    tft.println("Temperature"); 
    tft.setCursor(10, 30);
    tft.println("following error");
    tft.setCursor(10, 45);
    tft.print("during ");
    tft.println((error == 10) ? "heating" : "cooling");
  }

  while (1) { //  stop
    ;
  }
}

// ----------------------------------------------------------------------------

void displayThermocoupleData(struct Thermocouple* input) {
  switch (input->stat) {
    case 0:
      printDouble(input->temperature);
      tft.print("\367C");
      break;
    case 1:
      tft.print("---");
      break;
  }
}

// ----------------------------------------------------------------------------

void alignRightPrefix(uint16_t v) {
  if (v < 1e2) tft.print(' '); 
  if (v < 1e1) tft.print(' ');
}

uint16_t pxPerS;
uint16_t pxPerC;
uint16_t xOffset; // used for wraparound on x axis

void updateProcessDisplay() {
  const uint8_t h =  86;
  const uint8_t w = 160;
  const uint8_t yOffset =  30; // space not available for graph  

  uint16_t dx, dy;
  uint8_t y = 2;
  double tmp;

  // header & initial view
  tft.setTextColor(ST7735_WHITE, ST7735_BLUE);

  if (!initialProcessDisplay) {
    initialProcessDisplay = true;

    tft.fillScreen(ST7735_WHITE);
    tft.fillRect(0, 0, tft.width(), menuItemHeight, ST7735_BLUE);
    tft.setCursor(2, y);
#ifndef PIDTUNE
    tft.print("Profile ");
    tft.print(activeProfileId);
#else
    tft.print("Tuning ");
#endif

    tmp = h / (activeProfile.peakTemp * 1.10) * 100.0;
    pxPerC = (uint16_t)tmp;
    
#if 0 // pxPerS should be calculated from the selected profile, wint fit in flash right now
    double estimatedTotalTime = 60 * 12;
    // estimate total run time for current profile
    estimatedTotalTime = activeProfile.soakDuration + activeProfile.peakDuration;
    estimatedTotalTime += (activeProfile.soakTemp - 20.0) / (activeProfile.rampUpRate / 10);
    estimatedTotalTime += (activeProfile.peakTemp - activeProfile.soakTemp) / (activeProfile.rampUpRate / 10);
    estimatedTotalTime += (activeProfile.peakTemp - 20.0) / (activeProfile.rampDownRate  / 10);
    //estimatedTotalTime *= 2; // add some spare
    Serial.print("total est. time: ");
    Serial.println((uint16_t)estimatedTotalTime);
#endif
    tmp = 60 * 8;
    tmp = w / tmp * 10.0; 
    pxPerS = (uint16_t)tmp;

    // 50°C grid
    int16_t t = (uint16_t)(activeProfile.peakTemp * 1.10);
    for (uint16_t tg = 0; tg < t; tg += 50) {
      uint16_t l = h - (tg * pxPerC / 100) + yOffset;
      tft.drawFastHLine(0, l, 160, tft.Color565(0xe0, 0xe0, 0xe0));
    }
#ifdef GRAPH_VERBOSE
    Serial.print("Calc pxPerC/S: ");
    Serial.print(pxPerC);
    Serial.print("/");
    Serial.println(pxPerS);
#endif
  }

  // elapsed time
  uint16_t elapsed = (zeroCrossTicks - startCycleZeroCrossTicks) / 100;
  tft.setCursor(125, y);
  alignRightPrefix(elapsed); 
  tft.print(elapsed);
  tft.print("s");

  y += menuItemHeight + 2;

  tft.setCursor(2, y);
  tft.setTextColor(ST7735_BLACK, ST7735_WHITE);

  // temperature
  tft.setTextSize(2);
  alignRightPrefix((int)A.temperature);
  displayThermocoupleData(&A);
  tft.setTextSize(1);

#ifndef PIDTUNE
  // current state
  y -= 2;
  tft.setCursor(95, y);
  tft.setTextColor(ST7735_BLACK, ST7735_GREEN);
  
  switch (currentState) {
    #define casePrintState(state) case state: tft.print(#state); break;
    casePrintState(RampToSoak);
    casePrintState(Soak);
    casePrintState(RampUp);
    casePrintState(Peak);
    casePrintState(RampDown);
    casePrintState(CoolDown);
    casePrintState(Complete);
    default: tft.print((uint8_t)currentState); break;
  }
  tft.print("        "); // lazy: fill up space

  tft.setTextColor(ST7735_BLACK, ST7735_WHITE);
#endif

  // set point
  y += 10;
  tft.setCursor(95, y);
  tft.print("Sp:"); 
  alignRightPrefix((int)Setpoint); 
  printDouble(Setpoint);
  tft.print("\367C  ");

  // draw temperature curves
  //

  if (xOffset >= elapsed) {
    xOffset = 0;
  }

  do { // x with wrap around
    dx = ((elapsed - xOffset) * pxPerS) / 10;
    if (dx > w) {
      xOffset = elapsed;
    }
  } while(dx > w);

  // temperature setpoint
  dy = h - ((uint16_t)Setpoint * pxPerC / 100) + yOffset;
  tft.drawPixel(dx, dy, ST7735_BLUE);

  // actual temperature
  dy = h - ((uint16_t)A.temperature * pxPerC / 100) + yOffset;
  tft.drawPixel(dx, dy, ST7735_RED);

  // bottom line
  y = 119;

  // set values
  tft.setCursor(2, y);
  tft.print("\xef");
  alignRightPrefix((int)heaterValue); 
  tft.print((int)heaterValue);
  tft.print('%');

  tft.print(" \x2a");
  alignRightPrefix((int)fanValue); 
  tft.print((int)fanValue);
  tft.print('%');

  tft.print(" \x12 "); // alternative: \x7f
  printDouble(rampRate);
  tft.print("\367C/s    ");
}

// ----------------------------------------------------------------------------

void setup() {
  setupRelayPins();

  tft.initR(INITR_BLACKTAB);
  tft.setTextWrap(false);
  tft.setTextSize(1);
  tft.setRotation(1);

  if (firstRun()) {
    factoryReset();
    loadParameters(0);
  } 
  else {
    loadLastUsedProfile();
  }

  tft.fillScreen(ST7735_WHITE);
  tft.setTextColor(ST7735_BLACK, ST7735_WHITE);

  Timer1.initialize(100);
  Timer1.attachInterrupt(timerIsr);

#ifndef FAKE_HW
  pinMode(PIN_ZX, INPUT_PULLUP);
  attachInterrupt(INT_ZX, zeroCrossingIsr, RISING);
  delay(100);
#else
  Timer3.initialize(1000); // x10 speed
  Timer3.attachInterrupt(zeroCrossingIsr);
#endif

#ifdef WITH_SPLASH
  // splash screen
  tft.setCursor(10, 30);
  tft.setTextSize(2);
  tft.print("Reflow");
  tft.setCursor(24, 48);
  tft.print("Controller");
  tft.setTextSize(1);
  tft.setCursor(52, 67);
  tft.print("v"); tft.print(ver);
  #ifdef FAKE_HW
  tft.print("-fake");
  #endif
  tft.setCursor(7, 119);
  tft.print("(c)2014 karl@pitrich.com");
  delay(1000);
#endif

  // setup /CS line for thermocouple and read initial temperature
  A.chipSelect = THERMOCOUPLE1_CS;
  pinMode(A.chipSelect, OUTPUT);
  digitalWrite(A.chipSelect, HIGH);
#ifndef FAKE_HW
  readThermocouple(&A);
#endif
  if (A.stat != 0) {
    abortWithError(A.stat);
  }

  // initialize moving average filter
  runningTotalRampRate = A.temperature * NUMREADINGS;
  for(int i = 0; i < NUMREADINGS; i++) {
    airTemp[i].temp = A.temperature;
  }

#ifdef WITH_CALIBRATION
  tft.setCursor(7, 99);  
  tft.print("Calibrating... ");
  delay(400);

  // FIXME: does not work reliably
  while (zxLoopDelay == 0) {
    if (zxLoopCalibration.iterations == zxCalibrationLoops) { // average tick measurements, dump 1st value
      for (int8_t l = 0; l < zxCalibrationLoops; l++) {
        zxLoopDelay += zxLoopCalibration.measure[l];
      }
      zxLoopDelay /= zxCalibrationLoops;
      zxLoopDelay -= 10; // compensating loop runtime
    }
  }
  tft.print(zxLoopDelay);
#else
  zxLoopDelay = DEFAULT_LOOP_DELAY;
#endif

  loadFanSpeed();
  loadPID();

  PID.SetOutputLimits(0, 100); // max output 100%
  PID.SetMode(AUTOMATIC);

  delay(1000);

  menuExit(Menu::actionDisplay); // reset to initial state
  Engine.navigate(&miCycleStart);
  currentState = Settings;
  menuUpdateRequest = true;
}

// ----------------------------------------------------------------------------
/* moving average
    int samples[8];

    total -= samples[i];
    samples[i] = A.temperature; // new value
    total += samples[i];

    i = (i + 1) % 8; // next position
    average = total >> 3; // == div by 8 */
// ----------------------------------------------------------------------------

uint32_t lastRampTicks;

void updateRampSetpoint(bool down = false) {
  if (zeroCrossTicks > lastRampTicks + MS_PER_SINE) {
    double rate = (down) ? activeProfile.rampDownRate : activeProfile.rampUpRate;
    Setpoint += (rate / MS_PER_SINE * (zeroCrossTicks - lastRampTicks)) * ((down) ? -1 : 1);
    lastRampTicks = zeroCrossTicks;
  }
}

// ----------------------------------------------------------------------------

#ifdef PIDTUNE
void toggleAutoTune() {
 if(currentState != Tune) { //Set the output to the desired starting frequency.
    currentState = Tune;

    Output = aTuneStartValue;
    PIDTune.SetNoiseBand(aTuneNoise);
    PIDTune.SetOutputStep(aTuneStep);
    PIDTune.SetLookbackSec((int)aTuneLookBack);
  }
  else { // cancel autotune
    PIDTune.Cancel();
    currentState = CoolDown;
  }
}
#endif // PIDTUNE

// ----------------------------------------------------------------------------

uint8_t thermocoupleErrorCount;
#define TC_ERROR_TOLERANCE 5 // allow for n consecutive errors due to noisy power supply before bailing out

// ----------------------------------------------------------------------------

void loop(void) 
{
  // --------------------------------------------------------------------------
  // handle encoder
  //
  encMovement = Encoder.getValue();
  if (encMovement) {
    encAbsolute += encMovement;
    if (currentState == Settings) {
      Engine.navigate((encMovement > 0) ? Engine.getNext() : Engine.getPrev());
      menuUpdateRequest = true;
    }
  }

  // --------------------------------------------------------------------------
  // handle button
  //
  switch (Encoder.getButton()) {
    case ClickEncoder::Clicked:
      if (currentState == Complete) { // at end of cycle; reset at click
        menuExit(Menu::actionDisplay); // reset to initial state
        Engine.navigate(&miCycleStart);
        currentState = Settings;
        menuUpdateRequest = true;
      }
      else if (currentState < UIMenuEnd) {
        menuUpdateRequest = true;
        Engine.invoke();
      }
      else if (currentState > UIMenuEnd) {
        currentState = CoolDown;
      }
      break;

    case ClickEncoder::DoubleClicked:
      if (currentState < UIMenuEnd) {
        if (Engine.getParent() != &miExit) {
          Engine.navigate(Engine.getParent());
          menuUpdateRequest = true;
        }
      }
      break;
  }

  // --------------------------------------------------------------------------
  // update current menu item while in edit mode
  //
  if (currentState == Edit) {
    if (Engine.currentItem != &Menu::NullItem) {
      Engine.executeCallbackAction(Menu::actionDisplay);      
    }
  }

  // --------------------------------------------------------------------------
  // handle menu update
  //
  if (menuUpdateRequest) {
    menuUpdateRequest = false;
    if (currentState < UIMenuEnd && !encMovement && currentState != Edit && previousState != Edit) { // clear menu on child/parent navigation
      tft.fillScreen(ST7735_WHITE);
    }  
    Engine.render(renderMenuItem, menuItemsVisible);
  }

  // --------------------------------------------------------------------------
  // track state changes
  //
  if (currentState != previousState) {
    stateChangedTicks = zeroCrossTicks;
    stateChanged = true;
    previousState = currentState;
  }

  // --------------------------------------------------------------------------

  if (zeroCrossTicks - lastUpdate >= 10) {
    uint32_t deltaT = zeroCrossTicks - lastUpdate;
    lastUpdate = zeroCrossTicks;

#ifndef FAKE_HW
    readThermocouple(&A); // should be sufficient to read it every 250ms or 500ms
#else
    A.temperature = encAbsolute;
#endif

    if (A.stat > 0) {
      thermocoupleErrorCount++;
    }
    else {
      thermocoupleErrorCount = 0;
    }

    if (thermocoupleErrorCount > TC_ERROR_TOLERANCE) {
      abortWithError(A.stat);
    }

#if 0 // verbose thermocouple error bits
    tft.setCursor(10, 40);
    for (uint8_t mask = B111; mask; mask >>= 1) {
      tft.print(mask & A.stat ? '1' : '0');
    }
#endif
      
    // rolling average of the temp T1 and T2
    totalT1 -= readingsT1[index];       // subtract the last reading
    readingsT1[index] = A.temperature;
    totalT1 += readingsT1[index];       // add the reading to the total
    index = (index + 1) % NUMREADINGS;  // next position
    averageT1 = totalT1 / NUMREADINGS;  // calculate the average temp

    // need to keep track of a few past readings in order to work out rate of rise
    for (int i = 1; i < NUMREADINGS; i++) { // iterate over all previous entries, moving them backwards one index
      airTemp[i - 1].temp = airTemp[i].temp;
      airTemp[i - 1].ticks = airTemp[i].ticks;     
    }

    airTemp[NUMREADINGS - 1].temp = averageT1; // update the last index with the newest average
    airTemp[NUMREADINGS - 1].ticks = deltaT;

    // calculate rate of temperature change
    uint32_t collectTicks;
    for (int i = 0; i < NUMREADINGS; i++) {
      collectTicks += airTemp[i].ticks;
    }
    rampRate = (airTemp[NUMREADINGS - 1].temp - airTemp[0].temp) / collectTicks * MS_PER_SINE;

    Input = airTemp[NUMREADINGS - 1].temp; // update the variable the PID reads

    // display update
    if (zeroCrossTicks - lastDisplayUpdate > 50) {
      lastDisplayUpdate = zeroCrossTicks;
      if (currentState > UIMenuEnd) {
        updateProcessDisplay();
      }
    }

    switch (currentState) {
#ifndef PIDTUNE
      case RampToSoak:
        if (stateChanged) {
          lastRampTicks = zeroCrossTicks;
          stateChanged = false;
          Output = 80;
          PID.SetMode(AUTOMATIC);
          PID.SetControllerDirection(DIRECT);
          PID.SetTunings(heaterPID.Kp, heaterPID.Ki, heaterPID.Kd);
          Setpoint = airTemp[NUMREADINGS - 1].temp;
        }

        updateRampSetpoint();

        if (Setpoint >= activeProfile.soakTemp - 1) {
          currentState = Soak;
        }
        break;

      case Soak:
        if (stateChanged) {
          stateChanged = false;
          Setpoint = activeProfile.soakTemp;
        }

        if (zeroCrossTicks - stateChangedTicks >= (uint32_t)activeProfile.soakDuration * MS_PER_SINE) {
          currentState = RampUp;
        }
        break;

      case RampUp:
        if (stateChanged) {
          stateChanged = false;
          lastRampTicks = zeroCrossTicks;
        }

        updateRampSetpoint();

        if (Setpoint >= activeProfile.peakTemp - 1) {
          Setpoint = activeProfile.peakTemp;
          currentState = Peak;
        }
        break;

      case Peak:
        if (stateChanged) {
          stateChanged = false;
          Setpoint = activeProfile.peakTemp;
        }

        if (zeroCrossTicks - stateChangedTicks >= (uint32_t)activeProfile.peakDuration * MS_PER_SINE) {
          currentState = RampDown;
        }
        break;

      case RampDown:
        if (stateChanged) {
          stateChanged = false;
          lastRampTicks = zeroCrossTicks;
          PID.SetControllerDirection(REVERSE);
          PID.SetTunings(fanPID.Kp, fanPID.Ki, fanPID.Kd);
          Setpoint = activeProfile.peakTemp - 15; // get it all going with a bit of a kick! v sluggish here otherwise, too hot too long
        }

        updateRampSetpoint(true);

        if (Setpoint <= idleTemp) {
          currentState = CoolDown;
        }
        break;
#endif
      case CoolDown:
        if (stateChanged) {
          stateChanged = false;
          PID.SetControllerDirection(REVERSE);
          PID.SetTunings(fanPID.Kp, fanPID.Ki, fanPID.Kd);
          Setpoint = idleTemp;
        }

        if (Input < (idleTemp + 5)) {
          currentState = Complete;
          PID.SetMode(MANUAL);
          Output = 0;
        }

#ifdef PIDTUNE
      case Tune:
        {
          Setpoint = 210.0;
          int8_t val = PIDTune.Runtime();
          PIDTune.setpoint = 210.0;

          if (val != 0) {
            currentState = CoolDown;
          }

          if (currentState != Tune) { // we're done, set the tuning parameters
            heaterPID.Kp = PIDTune.GetKp();
            heaterPID.Ki = PIDTune.GetKi();
            heaterPID.Kd = PIDTune.GetKd();
            
            savePID();

            tft.setCursor(40, 40);
            tft.print("Kp: "); tft.print((uint32_t)(heaterPID.Kp * 100));
            tft.setCursor(40, 52);
            tft.print("Ki: "); tft.print((uint32_t)(heaterPID.Ki * 100));
            tft.setCursor(40, 64);
            tft.print("Kd: "); tft.print((uint32_t)(heaterPID.Kd * 100));
          }
        }
        break;
#endif
    }
  }

  // safety check that we're not doing something stupid. 
  // if the thermocouple is wired backwards, temp goes DOWN when it increases
  // during cooling, the t962a lags a long way behind, hence the hugely lenient cooling allowance.
  // both of these errors are blocking and do not exit!
  //if (Setpoint > Input + 50) abortWithError(10); // if we're 50 degree cooler than setpoint, abort
  //if (Input > Setpoint + 50) abortWithError(20); // or 50 degrees hotter, also abort
  
#ifndef PIDTUNE
  PID.Compute();

  // decides which control signal is fed to the output for this cycle
  if (   currentState != RampDown
      && currentState != CoolDown
      && currentState != Settings
      && currentState != Complete
      && currentState != Idle
      && currentState != Settings
      && currentState != Edit)
  {
    heaterValue = Output;
    fanValue = fanAssistSpeed;
  } 
  else {
    heaterValue = 0;
    fanValue = Output;
  }
#else
  heaterValue = Output;
  fanValue = fanAssistSpeed;
#endif

  Channels[CHANNEL_HEATER].target = heaterValue;

  double fanTmp = 90.0 / 100.0 * fanValue; // 0-100% -> 0-90° phase control
  Channels[CHANNEL_FAN].target = 90 - (uint8_t)fanTmp;
}

void memoryFeedbackScreen(uint8_t profileId, bool loading) {
  tft.fillScreen(ST7735_GREEN);
  tft.setTextColor(ST7735_BLACK);
  tft.setCursor(10, 50);
  tft.print(loading ? "Loading" : "Saving");
  tft.print(" profile ");
  tft.print(profileId);  
}

void saveProfile(unsigned int targetProfile, bool quiet) {
#ifndef PIDTUNE
  activeProfileId = targetProfile;

  if (!quiet) {
    memoryFeedbackScreen(activeProfileId, false);
  }
  saveParameters(activeProfileId); // activeProfileId is modified by the menu code directly, this method is called by a menu action

  if (!quiet) delay(500);
#endif
}

void loadProfile(unsigned int targetProfile) {
  memoryFeedbackScreen(activeProfileId, true);
  bool ok = loadParameters(targetProfile);

#if 0
  if (!ok) {
    lcd.setCursor(0, 2);
    lcd.print("Checksum error!");
    lcd.setCursor(0, 3);
    lcd.print("Review profile.");
    delay(2500);
  }
#endif

  // save in any way, as we have no undo
  activeProfileId = targetProfile;
  saveLastUsedProfile();

  delay(500);
}

#define WITH_CHECKSUM 1

bool saveParameters(uint8_t profile) {
#ifndef PIDTUNE
  uint16_t offset = profile * sizeof(Profile_t);

#ifdef WITH_CHECKSUM
  activeProfile.checksum = crc8((uint8_t *)&activeProfile, sizeof(Profile_t) - sizeof(uint8_t));
#endif

  do {} while (!(eeprom_is_ready()));
  eeprom_write_block(&activeProfile, (void *)offset, sizeof(Profile_t));
#endif
  return true;
}

bool loadParameters(uint8_t profile) {
  uint16_t offset = profile * sizeof(Profile_t);

  do {} while (!(eeprom_is_ready()));
  eeprom_read_block(&activeProfile, (void *)offset, sizeof(Profile_t));

#ifdef WITH_CHECKSUM
  return activeProfile.checksum == crc8((uint8_t *)&activeProfile, sizeof(Profile_t) - sizeof(uint8_t));
#else
  return true;  
#endif
}

bool savePID() {
  do {} while (!(eeprom_is_ready()));
  eeprom_write_block(&heaterPID, (void *)offsetPidConfig, sizeof(PID_t));
  return true;
}

bool loadPID() {
  do {} while (!(eeprom_is_ready()));
  eeprom_read_block(&heaterPID, (void *)offsetPidConfig, sizeof(PID_t));
  return true;  
}

bool firstRun() { 
#ifndef PIDTUNE
  // if all bytes of a profile in the middle of the eeprom space are 255, we assume it's a first run
  unsigned int offset = 15 * sizeof(Profile_t);

  for (uint16_t i = offset; i < offset + sizeof(Profile_t); i++) {
    if (EEPROM.read(i) != 255) {
      return false;
    }
  }
#endif
  return true;
}

void makeDefaultProfile() {
  activeProfile.soakTemp     = 130;
  activeProfile.soakDuration =  80;
  activeProfile.peakTemp     = 220;
  activeProfile.peakDuration =  40;
  activeProfile.rampUpRate   =   0.80;
  activeProfile.rampDownRate =   2.0;
}

void factoryReset() {
#ifndef PIDTUNE
  makeDefaultProfile();

  tft.fillScreen(ST7735_RED);
  tft.setTextColor(ST7735_WHITE);
  tft.setCursor(10, 50);
  tft.print("Resetting...");

  // then save the same profile settings into all slots
  for (uint8_t i = 0; i < maxProfiles; i++) {
    saveParameters(i);
  }

  fanAssistSpeed = 33;
  saveFanSpeed();

  heaterPID.Kp =  0.60; 
  heaterPID.Ki =  0.01;
  heaterPID.Kd = 19.70;
  savePID();

  activeProfileId = 0;
  saveLastUsedProfile();

  delay(500);
#endif
}

void saveFanSpeed() {
  EEPROM.write(offsetFanSpeed, (uint8_t)fanAssistSpeed & 0xff);
  delay(250);
}

void loadFanSpeed() {
  fanAssistSpeed = EEPROM.read(offsetFanSpeed) & 0xff;
}

void saveLastUsedProfile() {
  EEPROM.write(offsetProfileNum, (uint8_t)activeProfileId & 0xff);
}

void loadLastUsedProfile() {
  activeProfileId = EEPROM.read(offsetProfileNum) & 0xff;
  loadParameters(activeProfileId);
}
Szerkesztve (admin): Program beszúráshoz használd a Code feliratú gombot és a megjelenő [Co_de] [/Co_de] közé tedd a programkódot.
Avatar
Robert
Elektronbűvölő
Hozzászólások: 10191
Csatlakozott: 2005. december 9. péntek, 7:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Robert »

Az a valaki aki módosítja Te tudsz lenni:

A kijelző az alábbi két sort használja:

Kód: Egész kijelölése

#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
Neked a másodikat kell a mostani új kijelződnek javítani. Az első az eljárások: szöveg, vonal, kör, pont kezelés. A második a kijelzőspecifikus meghajtó.
Ezt kell feltelepítened libraryként:
https://github.com/adafruit/Adafruit_ILI9341
És a program módosítása:

Kód: Egész kijelölése

#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
Ne felejtkezz el a

Kód: Egész kijelölése

Adafruit_ST7735 tft = Adafruit_ST7735(LCD_CS, LCD_DC, LCD_RST);
sorod módosításáról sem (ez a LCD bekötés ill szoftveres initje!)

De a képernyőelrendezést át kell szerkesztened. Így most a 120*160-as eredeti helyen jelennek meg a szövegek/grafikák...


U.i.: megírni senki nem fogja _helyetted_ mert Te tudod igazán mit szeretnél benne...
U.i.2.: Javasolt ingyenes képzés: http://www.tavir.hu/60nap :)
http://www.tavir.hu - a gazda :)
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Ehhez még tegyük hozzá, Hogy a HardWer is a te asztalodon van.

És a menüt át méretezni anélkül, hogy látnád? Nem megoldható!
Tehát a fejlesztőnek kellene 1 példány a konkrét HW-böl!
imi535
Újonc
Újonc
Hozzászólások: 2
Csatlakozott: 2016. október 10. hétfő, 18:54

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: imi535 »

Köszönöm a segitö hozzászolást!Ugylátom nem lesz egyszerü megoldás számomra!Robert amit ajánlott átirtam,lett egy csomo hibaüzenet az elsö ami szembe jött(tft.drawFastHLine(0, l, 160, tft.Color565(0xe0, 0xe0, 0xe0));)igy nyilván le sem forditja,gondolom most kellene átszerkeszteni!
Az arduino tanfolyamot most töltögetem le,28. ik lecke úgyhogy nagyon kezdö vagyok!Az eredeti program megjelenik a kijelzön modositás nélkül is,igaz a kijelzö egy harmadát tölti ki! Csatolom a hiba üzenetet!

Kód: Egész kijelölése

 tft.drawFastHLine(0, l, 160, tft.Color565(0xe0, 0xe0, 0xe0));
Arduino: 1.6.9 (Windows 7), Alaplap:"Arduino/Genuino Micro"

WARNING: Spurious .github folder in 'Adafruit ILI9341' library
In file included from C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino:24:0:

sketch\temperature.h:50:1: warning: 'typedef' was ignored in this declaration [enabled by default]

 };

 ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino: In function 'bool getItemValueLabel(const Item_t*, char*)':

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino:293:37: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]

       itostr(label, *iValue, "\367C");

                                     ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino:296:33: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]

       itostr(label, *iValue, "s");

                                 ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino:299:33: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]

       itostr(label, *iValue, "%");

                                 ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino: In function 'void updateProcessDisplay()':

reflow_2.4_lcd.ino:815: error: 'class Adafruit_ILI9341' has no member named 'Color565'

       tft.drawFastHLine(0, l, 160, tft.Color565(0xe0, 0xe0, 0xe0));

                                        ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino: In function 'void setup()':

reflow_2.4_lcd.ino:920: error: 'class Adafruit_ILI9341' has no member named 'initR'

   tft.initR(INITR_BLACKTAB);

       ^

reflow_2.4_lcd.ino:920: error: 'INITR_BLACKTAB' was not declared in this scope

   tft.initR(INITR_BLACKTAB);

             ^

C:\Users\apa\Desktop\reflow 2.4 lcd pro mini\reflow_2.4_lcd.ino\reflow_2.4_lcd.ino.ino: At global scope:

reflow_2.4_lcd.ino:1510: error: expected declaration before '}' token

 }

 ^

Multiple libraries were found for "PID_v1.h"
Használt: C:\Users\apa\Documents\Arduino\libraries\PID_v1
Nem használt: C:\Users\apa\Documents\Arduino\libraries\Arduino-PID-Library-master
exit status 1
'class Adafruit_ILI9341' has no member named 'Color565'

Invalid library found in C:\Users\apa\Documents\Arduino\libraries\old: C:\Users\apa\Documents\Arduino\libraries\old

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Nem elég a programot módosítani!

Be kel rakni ez: https://github.com/adafruit/Adafruit_ILI9341 lib-et
Az: user\arduino\librari\ könyvtárba, hogy meg is találja!

Ezt is előbb megkellene tanulnod!

Ez alapok tudása nélkül ez nem fog menni neked!
Szóval szépen sorban haladj a tanulással. Az ABC. után jöhet az 1*1!
Bearnie74
DrótVégénSzéndarab
Hozzászólások: 34
Csatlakozott: 2016. november 25. péntek, 7:10

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Bearnie74 »

Nem találtam jobbat, igy itt kérdezném meg ha lehet.

Van egy touch panelem csak touch panel a kijelző külön van alatta külön tokozással. Ketté lehet őket venni.
A touchnak 4 kivezetése van. A kijelző most legyen felejtős egyenlőre.
Magát a tochpadot a 4 kivezetéssel hogyan lehet életre kelteni arduinoval?

Gondolom a 4 kivezetésen valamilyen jeleket kűld, amit értelmezni kellene valahogy. Megérzésem szerint rezistiv tapi a kincsem.

Ha megérdemel egy külön szálat, kérem az adminokat, hogy tegyék új témába.

A válaszokat előre is köszönöm.
A microkontroler sok mindenre alkalmas. a Pc bármire amire meg tudod tanítani.
Avatar
kapu48
Elektronbűvölő
Hozzászólások: 3375
Csatlakozott: 2008. augusztus 29. péntek, 6:00

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: kapu48 »

Bearnie74
DrótVégénSzéndarab
Hozzászólások: 34
Csatlakozott: 2016. november 25. péntek, 7:10

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Bearnie74 »

Óhhhh!
Nagyon szépen köszönöm!

Ez tökéletes tutorial volt.
A microkontroler sok mindenre alkalmas. a Pc bármire amire meg tudod tanítani.
Bearnie74
DrótVégénSzéndarab
Hozzászólások: 34
Csatlakozott: 2016. november 25. péntek, 7:10

Re: 2.4" TFT LCD (320*240)/SD - Arduino shield: HELP!

Hozzászólás Szerző: Bearnie74 »

A tutorial alapján megcsinálatam. De valami nem oké.

A program ez:
/*=================================
This code demostrates 4-Wire Touch screen
interfacing with Arduino

blog.circuits4you.com
www.circuits4you.com

4- Wire Touchscreen Connections
A0=====X+
A1=====X-
A2=====Y+
A3=====Y-
=================================*/
//Define your Touch screen connections
//A0 X1
//A1 X2
//A2 Y1
//A3 Y2
//Define your screen resolution as per your Touch screen (Max: 1024)
int Xresolution = 128
int Yresolution = 64

void setup()
{
Serial.begin(9600);
}

void loop()
{
int X,Y; //Touch Coordinates are stored in X,Y variable
pinMode(A2,INPUT);
pinMode(A3,INPUT);
digitalWrite(A3,LOW);
pinMode(A0,OUTPUT);
digitalWrite(A0,HIGH);
pinMode(A1,OUTPUT);
digitalWrite(A1,LOW);
X = (analogRead(A2))/(1024/Xresolution); //Reads X axis touch position

pinMode(A0,INPUT);
pinMode(A1,INPUT);
digitalWrite(A1,LOW);
pinMode(A2,OUTPUT);
digitalWrite(A2,HIGH);
pinMode(A3,OUTPUT);
digitalWrite(A3,LOW);
Y = (analogRead(A0))/(1024/Yresolution); //Reads Y axis touch position

//Display X and Y on Serial Monitor
Serial.print("X = ");
Serial.print(X);
Serial.print(" Y = ");
Serial.println(Y);
delay(300);
}

De nekem akármekkora felbontást állítok, csak a max értékeket küldözgeti feszt.

640x420 nál 620-623 ig küld, és 400 - 420 ig.

Ha bárhol megnyomom a dolog akkor sem változik.

Please Help!!

Mi lehet a gond.
A microkontroler sok mindenre alkalmas. a Pc bármire amire meg tudod tanítani.
Válasz küldése