структуры и PROGMEM

borischi
Offline
Зарегистрирован: 16.03.2012

Здравствуйте.
Заранее извиняюсь за вопрос на который уже возможно были ответы.Но я ,к сожалению,не нашел.
Есть ли возможность записывать во флэш и работать со структурами в ардуино при помощи PROGMEM например?

maksim
Offline
Зарегистрирован: 12.02.2012
maksim
Offline
Зарегистрирован: 12.02.2012
borischi
Offline
Зарегистрирован: 16.03.2012

Я читал этот референс.Там сказано не очень понятно про пользовательцкие типы переменных.Можно ли их закатывать во флэш

leshak
Offline
Зарегистрирован: 29.09.2011

 Напрямую - нет. Нельзя даже обычные типы использовать. И там довольно четко написанно:

Цитата:

it is important to use the datatypes outlined in pgmspace.h. Some cryptic bugs are generated by using ordinary datatypes for program memory calls

Что в вольном переводе "важно использовать только типы, описанные в pgmspace.h. Некоторые критические ошибки происходят именно из-за использования обычных типов". И ниже даны типы которые "можно".

То есть "в итоге", думаю можно. Но нужно будет приводить типы, и писать-читать их самому побайтово. Используя типы из pgmspace.

Возможно поможет в этом чтение http://easyelectronics.ru/avr-uchebnyj-kurs-programmirovanie-na-si-rabota-s-pamyatyu-adresa-i-ukazateli.html

Там пол статьи объяснения "указателей", а потом и про PROGMEM есть рассказ и как с ним обращатся.

 

 

borischi
Offline
Зарегистрирован: 16.03.2012

 Спасибо.Прояснилось немного :)

Casper17
Offline
Зарегистрирован: 21.06.2013

Для практики хочу написать свое меню для LCD. И сталкнулся с небольшой непоняткой.

char buf[30] = {};

typedef const struct PROGMEM MENU1 {
  const struct MENU1 *Next; 
  const struct MENU1 *Previous;
  const struct MENU1 *Parent;
  const struct MENU1 *Child;
  void (*handler)(void);
  const char Name[21];
} MENU;

MENU *curr = NULL;

MENU Menu[]={

{&Menu[1],&Menu[2],&Menu[0],&Menu[3],0,"MANUAL"},  //
{&Menu[2],&Menu[0],&Menu[1],&Menu[1],0,"AUTOMATIC"},  //  ГЛАВНОЕ МЕНЮ
{&Menu[0],&Menu[1],&Menu[2],&Menu[2],0,"SETTINGS"},  //

{&Menu[4],&Menu[5],&Menu[0],&Menu[6],0,"SUB_MANUAL_ONE     "},  //
{&Menu[5],&Menu[3],&Menu[1],&Menu[4],0,"SUB_MANUAL_TWO     "},  //  СУБМЕНЮ MANUAL
{&Menu[3],&Menu[4],&Menu[2],&Menu[5],0,"SUB_MANUAL_THREE   "}
};

void setup() {
  Serial.begin(115200);


  curr = (MENU*)pgm_read_byte(&Menu[0]);
  sprintf_P(buf, PSTR(">%S"), (char*)curr->Name);
  Serial.println(buf);
  
  curr = (MENU*)pgm_read_byte(&curr->Next);    
  sprintf_P(buf, PSTR(">%S"), (char*)curr->Name);
  Serial.println(buf);

  curr = (MENU*)pgm_read_byte(&curr->Next);    
  sprintf_P(buf, PSTR(">%S"), (char*)curr->Name);
  Serial.println(buf);

  curr = (MENU*)pgm_read_byte(&curr->Next);    
  sprintf_P(buf, PSTR(">%S"), (char*)curr->Name);
  Serial.println(buf);

}

void loop() {
  // put your main code here, to run repeatedly:

}

По идее должен вывод начаться с MANUAL, но он выводит AUTOMATIC. Почему так происходит, я же указал адрес &Menu[0], а он начинает выводить с Menu[1], если после 29 строки указать curr-- то работает как надо! Почему так происходит?

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
0

Доброго времени суток. Имею скетч, пока занимает 774 (37%) оперативы и 16 794 байт (52%) флэш. Но скетч постоянно дополняется и улучшается и предстоит еще немало доработать.  Поэтому если не составит труда подскажите как элементы структуры ниже с помощью PROGMЕM перенести во флэш чтобы потом не возникало желания перейти на другую плату помощнее, uno вполне устраивает

//массив элементов меню
struct PODMENU_1{ //
char name1[6];
  double *k1;
};

struct PODMENU_2{ //
char name2[6];
  double *k2;
};

//инициализация меню
//строковое имя, адрес переменной которую надо менять
PODMENU_1 mMenu[CountMenu] = {
"KP1 =", &kp1,
"KI1 =", &ki1,
"KD1 =", &kd1,
"PW1 =", &pwr_BOTTOM
};

//инициализация меню
//строковое имя, адрес переменной которую надо менять
PODMENU_2 nMenu[CountMenu] = {
"KP2 =", &kp2,
"KI2 =", &ki2,
"KD2 =", &kd2,
"PW2 =", &pwr_TOP
};

void setup() {}
void loop() {}

//функция выполнения меню в loop
void selectParam(void){

  // добавить сохранение текущих параметров в eeprom  void SaveOption()
  
  //вывод использую буфер
  static char charbuf1[8];
  static char charbuf2[8]; 
  
  dtostrf(*mMenu[curMenu].k1, sizeof(charbuf1)-1, 2, charbuf1);
  dtostrf(*nMenu[curMenu].k2, sizeof(charbuf2)-1, 2, charbuf2);
  
  lcd.setCursor(5,0);  lcd.print(mMenu[curMenu].name1); lcd.print(charbuf1);    
  lcd.setCursor(5,2);  lcd.print(nMenu[curMenu].name2); lcd.print(charbuf2);
}

Полный листинг кода

#include <EEPROM.h> // подключаем библиотеку EEPROM
#include <Wire.h> // библиотека для управления устройствами по I2C 
#include <LiquidCrystal_I2C.h> // подключаем библиотеку для LCD 1602
#include "max6675.h"  // подключаем библиотеку для работы с чипом max6675 
#include <PID_v1.h> //библиотека ПИД-регулятора
#include <avr/io.h> // библиотека в которой находятся определения констант, имен регистров avr
#include <avr/pgmspace.h>

//#define serialenabled // раскомментировать для выдачи в порт отладочной инфы

//#define MAX_PROFILES 30

//#define OFFSET 10

//Назначаем пины кнопок управления
#define BTN_DOWN 13  // кнопка 
#define BTN_UP 12  // кнопка  
#define BTN_OK 11 // кнопка
#define BTN_PREV 10 // кнопка 
#define BTN_NEXT 9 // кнопка 
#define BTN_CANSEL 8 // кнопка  

#define RELAYPIN_H PC0  //назначаем пин "НИЖНЕГО" нагревателя
#define RELAYPIN_B PC1  //назначаем пин "ВЕРХНЕГО" нагревателя 

#define MIN_TEMP 100
#define MAX_TEMP 350		

#define buzzerPin 3 // назначаем пин для пьезодинамика

LiquidCrystal_I2C lcd(0x3f, 20, 4); // присваиваем имя lcd для дисплея 20х4

boolean updateScreen = true; //Update whole screen boolean

byte ButtonModecount = 0;        // счетчик нажатий кнопки
boolean MenuEnter = 0;           // текущий статус входа в меню
boolean modeOfOperation = true;  // профиль выдержан

boolean TopStart = 0;      // флаг установки работы "ВЕРХНЕГО" нагревателя
boolean stOKState = 0;      // флаг окончания текущей операции 
boolean exOKState = 0;      // флаг возврата в главное меню
boolean runProfile = 0;      // флаг начала пайки по профилю

byte btnupclick, btndnclick;
byte btn1, btn2;
const uint8_t mass_byte[4] = {100, 1, 10, 50}; // сохраняем несколько беззнаковых целых чисел

const char  str[] PROGMEM = "STOP";
const char str1[] PROGMEM = "PEAK";
const char str_[] PROGMEM = "    ";

uint8_t symbol[8]  = {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}; // полностью закрашенный символ
uint8_t forward_arrow[8]  = {0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x04}; // символ стрелка вверх
uint8_t backward_arrow[8]  = {0x04, 0x04, 0x04, 0x04, 0x15, 0x0e, 0x04}; // символ стрелка вниз
uint8_t main_arrow[8]  = {0x08, 0x0C, 0x0E, 0x0F, 0x0E, 0x0C, 0x08};
uint8_t clock[8] = {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0}; // символ часы

byte sek = 0; //значение секунд
byte minu = 0; //значение минут
boolean counter = false; // счетчик для полусекунд

#define PERIOD_CLOCK 500000UL
#define PERIOD_PWM 1000UL // периодичность вывода температуры (1 cекунда) > 750
#define PERIOD_DURATION 1000UL // периодичность 
#define PERIOD_BLINK 200UL // периодичность
#define TIME_ALARM 3000UL // время в режиме ТРЕВОГА
#define TIME_WAIT 1500UL // ожидание для подтверждения возврата в меню

double kp1, kd1, ki1;  
double kp2, kd2, ki2;  

boolean flag = 0;  //флаг для фиксации стартовой температуры

double startTemp; // стартовая температура // можно сделать локальной?
double setTemp1 = 0.0; // заданная температура "НИЖНЕГО" нагревателя
double setTemp2 = 0.0; // заданная температура "ВЕРХНЕГО" нагревателя
double *pSetTemp1, *pSetTemp2; // указатель на setTemp1, setTemp2
double temp1Status, temp2Status; double curTemp; boolean sel = 0;
double tc1 = 0.0; // текущая температура "НИЖНЕГО" нагревателя
double tc2 = 0.0; // текущая температура "ВЕРХНЕГО" нагревателя

// переменные для фильтра Калмана "НИЖНЕГО" нагревателя
//#define varTerm1 0.25  // среднее отклонение (ищем в excel)
//#define varProcess1 0.0125 // скорость реакции на изменение (подбирается вручную)

// переменные фильтра Калмана "ВЕРХНЕГО" нагревателя
//#define varTerm2 0.25  // среднее отклонение (ищем в excel)
//#define varProcess2 0.0125 // скорость реакции на изменение (подбирается вручную)

double outputPower1, outputPower2; // по умолчанию диапазон выходных значений для ШИМ устанавливается от 0 до 100

double pwr_BOTTOM = 100.0;  //максимальная мощность нижнего нагревателя
double pwr_TOP = 100.0;     //максимальная мощность верхнего нагревателя

boolean state_top = 0;
boolean state_bottom = 0;

PID myPID1(&tc1, &outputPower1, &setTemp1, kp1, ki1, kd1, DIRECT);
PID myPID2(&tc2, &outputPower2, &setTemp2, kp2, ki2, kd2, DIRECT);

#define thermoDO 7
#define thermoCLK 5
#define thermoCS_b 6
#define thermoCS_t 4
MAX6675 thermocouple_b(thermoCLK, thermoCS_b, thermoDO); //термопара "НИЖНЕГО" нагревателя
MAX6675 thermocouple_t(thermoCLK, thermoCS_t, thermoDO); //термопара "ВЕРХНЕГО" нагревателя

enum switchVariants : byte  {  // Определения для переключателя пунктов меню;
  MAIN_MENU, MENU_MANUAL, MENU_AUTO, MENU_SETUP, MENU_START, EXITSAVE
};
switchVariants switchPointer = MAIN_MENU; // С чего начнем цикл;

enum manuState : byte {  // Определения для переключателя ручного режима;
  EDIT_PARAM, HEAT_ZONE, PEAK_ZONE, COOL_ZONE, STOP_PROCESS, EXIT_PROCESS
};
manuState mode = EDIT_PARAM; // С чего начнем цикл;

typedef enum REFLOW_STATE : byte { // Определения для переключателя режимов пайки по профилю;
    REFLOW_STATE_IDLE,
    REFLOW_STATE_COMPLETE,
    REFLOW_STATE_ERROR
}
reflowState_t;
reflowState_t reflowState;

byte curMenu = 0; //текущий пункт меню,
bool b_ShowmMenu = 0; // флаг отображения меню
const byte CountMenu = 4; //количество пунктов меню

//массив элементов меню
struct PODMENU_1{ //
char name1[6];
  double *k1;
};

struct PODMENU_2{ //
char name2[6];
  double *k2;
};

//инициализация меню
//строковое имя, адрес переменной которую надо менять
PODMENU_1 mMenu[CountMenu] = {
"KP1 =", &kp1,
"KI1 =", &ki1,
"KD1 =", &kd1,
"PW1 =", &pwr_BOTTOM
};

//инициализация меню
//строковое имя, адрес переменной которую надо менять
PODMENU_2 nMenu[CountMenu] = {
"KP2 =", &kp2,
"KI2 =", &ki2,
"KD2 =", &kd2,
"PW2 =", &pwr_TOP
};

typedef void (*pDo)() ;// тип -функция обработчик
//------Cl_BtnLong----------------------//
class Cl_BtnLong { // класс кнопка
  protected:
    const byte pin; // номер ноги на кнопке
    pDo press, longPress; //обработчик короткий,длиный
    bool bounce = 0; // // антидребезговый флаг
    bool btn = 1, oldBtn;
    unsigned long time_Pressed; // Переменные для работы со временем;
    bool button_state;
    uint32_t last_state = 0;
  public:
    /*конструктор*/
    Cl_BtnLong(byte pin_, pDo press_, pDo longPress_)
      : pin(pin_), press(press_), longPress(longPress_)  {}
    /*инициализация-вставить в setup() void init() { } */

    /*работа-вставить в loop()*/
    void run() {
      if (!bounce && digitalRead(pin) != btn) { // если прошел фронт изменения на выводе
        bounce = 1;              // выставить флаг
        time_Pressed = millis();
      }
      if (bounce && millis() - time_Pressed >= 10) { // если прошло антидребезговое время
        bounce = 0; // то снять флаг
        oldBtn = btn;
        btn = digitalRead(pin);  // прочитать реальное значение на выводе
        if (!btn && oldBtn) { // если нажата кнопка
          button_state = 1;
          last_state = millis();
        }
        if (!oldBtn && btn && button_state && millis() - last_state < 500 ) {
          button_state = 0;
          press();// короткое нажатие
        }
      }
      if (button_state && millis() - last_state >= 500 ) {
        button_state = 0;
        longPress();//длиное нажатие
      }
    }
};

//-----Компоновка----------------------
// кнопка 1
void press_down() {
  updown(false);
}
void longPress_down() {
  long_updown(false);
}
Cl_BtnLong Btn_down(/*пин*/BTN_DOWN,/*обработчик короткого*/press_down,/*длинного*/longPress_down);
// кнопка 2
void press_up() {
  updown(true);
}
void longPress_up() {
  long_updown(true);
}
Cl_BtnLong Btn_up(/*пин*/BTN_UP,/*обработчик короткого*/press_up,/*длинного*/longPress_up);
// кнопка 3
void press_ok() {
  ok();
}
void longPress_ok() {
  mode = HEAT_ZONE;
}
Cl_BtnLong Btn_ok(/*пин*/BTN_OK,/*обработчик короткого*/press_ok,/*длинного*/longPress_ok);
// кнопка 4
void press_prev() {

}
void longPress_prev() {

}
Cl_BtnLong Btn_prev(/*пин*/BTN_PREV,/*обработчик короткого*/press_prev,/*длиного*/longPress_prev);
// кнопка 5
void press_next() {
  next();
}
void longPress_next() {
  
}
Cl_BtnLong Btn_next(/*пин*/BTN_NEXT,/*обработчик короткого*/press_next,/*длиного*/longPress_next);
// кнопка 6
void press_cansel() {
  back();
}  

void longPress_cansel() {
  stOKState = 1; // халтура
}
Cl_BtnLong Btn_cansel(/*пин*/BTN_CANSEL,/*обработчик короткого*/press_cansel,/*длиного*/longPress_cansel);

//-----main----------------------//
void setup() {  

  #ifdef serialenabled
    Serial.begin(9600);
  #endif
  
  lcd.begin();     // инициализация LCD дисплея
  lcd.backlight(); // включение подсветки дисплея

  lcd.setCursor(1, 1);
  lcd.print(F("ARDUINO REWORK v05"));
  
  lcd.createChar(1, symbol); // создает символ
  lcd.createChar(2, forward_arrow); // создает символ
  lcd.createChar(3, backward_arrow); // создает символ
  lcd.createChar(4, main_arrow); // создает символ
  lcd.createChar(5, clock); // создает символ
  
  DDRB = 0x00; // назначить пины кнопок на вход
  PORTB = 0b00111111; // подключить внутренние подтягивающие pull-up резисторы
  DDRC |= 1 << RELAYPIN_H;  // настраиваем 0-й пин порта C (реле) на вывод
  DDRC |= 1 << RELAYPIN_B;  // настраиваем 1-й пин порта C (реле) на вывод

  DDRD |= 1 << PD3;  // назначить пин для пьезодинамика на выход
  
  //Мелодия приветствия
  tone(buzzerPin, 523);
  delay(200);
  tone(buzzerPin, 659);
  delay(200);
  tone(buzzerPin, 784);
  delay(200);
  tone(buzzerPin, 1046);
  delay(200);
  noTone(buzzerPin);

  delay(1000); // wait for MAX chip to stabilize
  lcd.clear();
  
  //long magic;
  //EEPROM.get(MAGIC_ADDR, magic);
  //if(magic == MAGIC_VAL){
  //  EEPROM.get(SETPOINT_ADDR, setPoint);
  //  EEPROM.get(BACKLIGHT_ADDR, backlight);
  //}

  myPID1.SetSampleTime(1000); //  Время расчета выходного сигнала в милисекундах
  myPID1.SetOutputLimits(0, 100); // Устанавливает границы выходного сигнала
  myPID1.SetMode(AUTOMATIC);  //  ПИД-регулятор включен

  myPID2.SetSampleTime(1000); //  Время расчета выходного сигнала в милисекундах
  myPID2.SetOutputLimits(0, 100);  // Устанавливает границы выходного сигнала
  myPID2.SetMode(AUTOMATIC);  //  ПИД-регулятор включен
}

void loop() {
  
  Btn_down.run();
  Btn_up.run();
  Btn_ok.run();
  Btn_prev.run();
  Btn_next.run();
  Btn_cansel.run();
    
  static uint32_t previousMillis = 0; // время последнего изменения состояния
  
  stopProcess();
  exitMenu();
          
  // дебаг-инфо - в терминал
  #ifdef serialenabled
       
  #endif
  
  switch (switchPointer)  // Все делаем в одном операторе и одной функции;
  {
    case MAIN_MENU:  /***************** Главное меню ***************/
      
      setString(ButtonModecount); // включаем отображение стрелки выбора меню

      lcd.setCursor(1, 0); // 1 строка
      lcd.print(F("MANUAL")); // настройка
      lcd.setCursor(1, 1); // 2 строка
      lcd.print(F("AUTOMATIC")); // настройка
      lcd.setCursor(1, 2); // 3 строка
      lcd.print(F("OPTIONS")); // ручной режим
      lcd.setCursor(1, 3); // 4 строка
      lcd.print(F("START")); // автоматический режим

      break;

      case MENU_MANUAL: /***************** Ручной режим ***************/
      {
      SetTuning(); // настройки регулятора
      tempToPID(PERIOD_PWM);  // передача температуры для расчета ПИД регулятору
      
      uint8_t Out1 = OutPWR_TOP();
      uint8_t Out2 = OutPWR_BOTTOM();
      
      lcd.setCursor(1, 1);  // Печатаем на ЖК;
      lcd.print(F("TH:"));
      lcd.print(tc1, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(12, 1); // Печатаем на ЖК;
      lcd.print(F("TY:"));
      lcd.print(setTemp1, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(1, 3);  // Печатаем на ЖК;
      lcd.print(F("TB:"));
      lcd.print(tc2, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(12, 3); // Печатаем на ЖК;
      lcd.print(F("TY:"));
      lcd.print(setTemp2, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(14, 0); // Печатаем на ЖК;
      lcd.print(Out1, 0);
      lcd.print(F("\x99""%"));

      lcd.setCursor(14, 2); // Печатаем на ЖК;
      lcd.print(Out2, 0);
      lcd.print(F("\x99""%"));

      //фиксируем стартовую температуру
      if (flag == 0)
      {
        startTemp = tc1;
        flag = 1;
      }

      switch (mode) { // Выбор режима работы ручного режима
        case EDIT_PARAM: 
          TopStart = 0;
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("EDIT"));   // Режим редактирования температуры
        break;

        case HEAT_ZONE:
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("HEAT"));   // Режим редактирования температуры

          timing();
          //stopProcess();         
  
          mode_PWM1(Out1);
          mode_PWM2(Out2);

          // запуск работы "ВЕРХА" сразу после догрева "НИЗОМ"
          //верхний нагреватель включится еcли температура "низа" достигнет уснановленной
          if (tc1 >= setTemp1) {    
            

            //if (abs(setTemp1-tc1) <= 2.5) tone(buzzerPin, 1045, 50); //звуковой сигнал
            TopStart = 1; //   нижний нагреватель вышел на необходимую температуру
          }
          
          //
          if (tc2 >= setTemp2) { 
            previousMillis = millis();
            mode = PEAK_ZONE;
          }
        break;

        case PEAK_ZONE:
          
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          //blinking(PERIOD_BLINK, str[0]);
          blinking(PERIOD_BLINK,0);
          
          timing();
          
          mode_PWM1(Out1);
          mode_PWM2(Out2);

          peak_Temp(); 

          if (previousMillis) {
            tone(buzzerPin, 550); // play 550 Hz tone in background for 'onDuration'
          }

          if (previousMillis && (millis() - previousMillis >= 1000)) {
            previousMillis = 0;
            noTone(buzzerPin);
          }

          if (!modeOfOperation) {
            mode = COOL_ZONE;
            modeOfOperation = true; // Подъем флага - профиль выдержан!
          }
        break;

        case COOL_ZONE:
          timing();          
          turnoff(); // откл реле
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("COOL"));   // Режим редактирования температуры

        break;
          
        case STOP_PROCESS:
           sek = 0; minu = 0;
           turnoff();
           lcd.setCursor(8, 0); // Печатаем на ЖК;
           //blinking(PERIOD_BLINK, str[1]);
           blinking(PERIOD_BLINK,1);
           
        break;
      }
    }
    break;

    case MENU_AUTO: /***************** Автоматический режим ***************/
    //currentProfile = currentProfile + 1;
    //if (currentProfile >= 5)//if currentProfile = 4 and up is pressed go back to profile 1
    //    {
    //      currentProfile = 1;
    //    }
    //currentProfile = currentProfile - 1;
    //    if (currentProfile <= 0)
    //    {
    //      currentProfile = 4;
    
    //loadProfile();//вызываем функцию loadProfile для выбора профиля из eeprom
    // устанавливаем количество шагов профиля
    //profileSteps = profileSteps + 1;
    //    if (profileSteps >= 10) {
    //      profileSteps = 1;
    
    //profileSteps = profileSteps - 1;
    //    if (profileSteps <= 0) 
    //    {
    //      profileSteps = 9;
    //    }
    
    break;

    case MENU_SETUP: /***************** Настройки ***************/
    
       selectParam();
       // save eeprom
    break;

    case MENU_START: /*********** Пуск по заданному термопрофилю *********/         
         if(!runProfile) { prep_Profile(); }     
         else { if (updateScreen) {
              //Настройка экрана в режиме ожидания
              lcd.clear();
              updateScreen = 0; }          
         }
         /*
         static byte i = 0;
         if (reflowState == REFLOW_STATE_COMPLETE) {  //|| alarmOn
            if (i < 5) {
              //alarmOn = true;
              tone(buzzerPin, 1046);
              delay(100);
              noTone(buzzerPin);
              delay(100);
              i++;
            }
            else {
              //alarmOn = false;
            }
         }
         
         switch (reflowState)
         {
           case REFLOW_STATE_IDLE:
           
                if (updateScreen) {
                //Настройка экрана в режиме ожидания
                lcd.clear();
                
                }
           
           break;

           case REFLOW_STATE_MENU_RESET:
           
                lcd.clear();
                lcd.setCursor(0,0);
                lcd.print("EEPROM formatted");
                for (int i = 0; i < 512; i++){
                    EEPROM.write(i, 0);
                }           
                softReset();
           break;
           
         }*/
         
    break;
    
    case EXITSAVE: /*********** Полная очистка экрана *********/
       lcd.setCursor(3, 1);
       lcd.print(F("SAVE and EXIT")); // согласие на сохранение результата/изменений
    break;
  }
}

//
void tempToPID(unsigned long INTERVAL ) {
  static unsigned long prevTime = 0; // время когда последний раз переключали диод
  if (millis() - prevTime > INTERVAL) {
    prevTime = millis();  //
    //tc1 = thermocouple_b.readCelsius();
    //tc2 = thermocouple_t.readCelsius();
    tc1 = 30; tc2 = 30;
    //tc1 = kalmanFilter1 (thermocouple_b.readCelsius());
    //tc2 = kalmanFilter2 (thermocouple_t.readCelsius());
    //double gap1 = abs(setTemp1 - tc1);
    //double gap2 = abs(setTemp2 - tc2);
    
    myPID1.Compute(); // Расчет выходного сигнала
    myPID2.Compute(); // Расчет выходного сигнала
  }
}

/*double kalmanFilter1(double val_1) {  //функция фильтрации показений "Верхней" термопары
  
  double Pc1 = 0.0;
  double G1 = 0.0;
  double P1 = 1.0;
  double Xp1 = 0.0;
  double Zp1 = 0.0;
  double Xe1 = 0.0;
  
  Pc1 = P1 + varProcess1;
  G1 = Pc1/(Pc1 + varTerm1);
  P1 = (1-G1)*Pc1;
  Xp1 = Xe1;
  Zp1 = Xp1;
  Xe1 = G1*(val_1-Zp1)+Xp1; // "фильтрованное" значение
  return(Xe1);
}*/

/*double kalmanFilter2(double val_2) {  //функция фильтрации показений "Нижней" термопары

  double Pc2 = 0.0;
  double G2 = 0.0;
  double P2 = 1.0;
  double Xp2 = 0.0;
  double Zp2 = 0.0;
  double Xe2 = 0.0;
  
  Pc2 = P2 + varProcess2;
  G2 = Pc2/(Pc2 + varTerm2);
  P2 = (1-G2)*Pc2;
  Xp2 = Xe2;
  Zp2 = Xp2;
  Xe2 = G2*(val_2-Zp2)+Xp2; // "фильтрованное" значение
  return(Xe2);
}*/

uint8_t OutPWR_BOTTOM() {
  static uint8_t er1=0; //  ошибка округления    
  uint8_t reg1 = round(outputPower1*(pwr_BOTTOM*0.01)) + er1; //pwr- задание выходной мощности в %, er- ошибка округления 
  if (reg1 < 50) er1 = reg1; // reg- переменная для расчетов 
  else er1 = reg1 - 100;
  return reg1; 
}

uint8_t OutPWR_TOP()  {
  static uint8_t er2=0; //  ошибка округления   
  uint8_t reg2 = round(outputPower2*(pwr_TOP*0.01)) + er2; //pwr- задание выходной мощности в %, er- ошибка округления  
  if (reg2 < 50) er2 = reg2; // reg- переменная для расчетов 
  else er2=reg2-100;
  return reg2;
}

// алгоритм ШИМ с частотой 5 Гц
void mode_PWM1(uint8_t Output1) {
  static unsigned long prevTime = 0;
  if ((state_bottom == 0) && ((millis() - prevTime) >= 200 * (100 - Output1) / 100)) {
    state_bottom = 1;
    PORTC |= 1 << RELAYPIN_H;
    prevTime = millis();
  }
  if ((state_bottom == 1) && ((millis() - prevTime) >= 200 * Output1 / 100)) {
    state_bottom = 0;
    PORTC &= ~(1 << RELAYPIN_H);
    prevTime = millis();
  }
}

// алгоритм ШИМ с частотой 5 Гц
void mode_PWM2(uint8_t Output2) {
  static unsigned long prevTime = 0;
  if (TopStart && (state_top == 0) && ((millis() - prevTime) >= 200 * (100 - Output2) / 100)) {
    state_top = 1;
    PORTC |= 1 << RELAYPIN_B;
    prevTime = millis();
  }
  if (TopStart && (state_top == 1) && ((millis() - prevTime) >= 200 * Output2 / 100)) {
    state_top = 0;
    PORTC &= ~(1 << RELAYPIN_B);
    prevTime = millis();
  }
}

//
void blinking(unsigned long intervalTime, bool number) {
  static unsigned long prevTime = 0;
  static boolean i=0;
  if(millis() - prevTime >= intervalTime) {                 //Если счетчик превысил интервал, 
     i = !i; prevTime = millis(); }  //меняем значение переменной i, добавляем к переменной время интервала.
     if (number) lcd.print(i ? (const __FlashStringHelper*)str : (const __FlashStringHelper*)str_); 
     else lcd.print(i ? (const __FlashStringHelper*)str1 : (const __FlashStringHelper*)str_);  
}
        
void peak_Temp(void) {
  static unsigned long prevTime = 0;
  static byte count = 0;
  if (millis() - prevTime >= PERIOD_DURATION) {
    prevTime = millis();
    
    if (count >= 10) { count = 0; modeOfOperation = false; }
    else { count++; modeOfOperation = true; }
    
    lcd.setCursor(9, 2);  // Печатаем на ЖК;
    lcd.write(count > 0 ? 5 : 32);
    if (count) lcd.print(count); else lcd.print(F(" "));    
  }
}

// отключение реле
void turnoff(void) {
  PORTC &= ~(1 << RELAYPIN_H);
  PORTC &= ~(1 << RELAYPIN_B);
}

void setString(uint8_t bcount) {    
    lcd.setCursor(0, bcount);
    lcd.write(62);    
    if (bcount - 1 < 0) lcd.setCursor(0, bcount+3); 
    else lcd.setCursor(0, bcount-1); 
    lcd.write(32);
}

void ButtonClick(byte ButtonId) {

  if (MenuEnter) { // Если мы в меню
    if (ButtonId == 0)  switchPointer = MENU_MANUAL;   // Клик [Menu] Выход из меню
    else if (ButtonId == 1) switchPointer = MENU_AUTO;  //MenuCurent--;   // Клик [Prev] Позицию ниже
    else if (ButtonId == 2) switchPointer = MENU_SETUP; //MenuCurent++;   // Клик [Next] Позиция выше   
    else if (ButtonId == 3) switchPointer = MENU_START;//MenuItems[MenuCurent].on_click(1);  // Клик [+] Увеличиваем значение выбранного параметра
  } else {
    switchPointer = MAIN_MENU; // Возврат в главное меню
  }
}

void prep_Profile(void) {         
  static uint8_t j = 0;       
  if (j > 19) runProfile = 1;        
  else j++;      
  lcd.setCursor(0,1);
  for (uint8_t i = 0; i<20; i++) {
        lcd.write(j>i ? 1 : 32);
  }
  lcd.setCursor(5,2); lcd.print(F("Loading..."));
  delay(500);
}
         
// остановка профиля 
void stopProcess(void) {
  static uint32_t timer = 0;
  //static bool timerenabled = 0;
  if(stOKState) {  // нажали на кнопку CANSEL для остановки режима
     timer = millis();  //нулим таймер
     stOKState = 0;
     mode = STOP_PROCESS;
  }
  if (timer && millis() - timer > TIME_ALARM){
     timer = 0;
     lcd.clear();
     mode = EDIT_PARAM;     
  }
}

void exitMenu(void) {
  static uint32_t timer = 0;
  static uint8_t  accum = 0;
  if(exOKState) {  // нажали на кнопку ОК для сохранения и выхода
       if(!timer) {
           timer = millis();  //перезапускаем таймер
           accum = ButtonModecount; // хранится текущий пункт меню
           lcd.clear(); // очистим заранее экран
           switchPointer = EXITSAVE; // выход из подменю 
       }
  }       
  if (timer && millis() - timer > TIME_WAIT) {     
          timer = 0; // не забываем выключить таймер    
          if (exOKState) { MenuEnter = 0; exOKState = 0; }// переходим в главное меню
          else { MenuEnter = 1; } // возвращаемся обратно в топодменю откуда пришли       
          lcd.clear(); // очистим заранее экран
          ButtonClick(accum); // Вызывается функция обработки нажатия на кнопку
  }     
}
  
//функция выполнения меню
void selectParam(void){

  // BTN_CANSEL  выход из меню
  // выход из меню
  // переход к начальному меню

  // BTN_UP +1 значение
  //(*mMenu[curMenu].k1)++; или
  //(*nMenu[curMenu].k2)++;

  // BTN_DOWN -1 значение
  // (*mMenu[curMenu].k1)--; или
  // (*nMenu[curMenu].k2)--;

  //настройка "ПИД" нижнего нагревателя
  //настройка "ПИД" верхнего нагревателя
  //сохраняем текущие параметры в eeprom  void SaveOption()
  
  //вывод использую буфер
  static char charbuf1[8];
  static char charbuf2[8]; 
  
  dtostrf(*mMenu[curMenu].k1, sizeof(charbuf1)-1, 2, charbuf1);
  dtostrf(*nMenu[curMenu].k2, sizeof(charbuf2)-1, 2, charbuf2);
  
  lcd.setCursor(5,0);  lcd.print(mMenu[curMenu].name1); lcd.print(charbuf1);    
  lcd.setCursor(5,2);  lcd.print(nMenu[curMenu].name2); lcd.print(charbuf2);
}

//void saveState() {
//  EEPROM.put(MAGIC_ADDR, MAGIC_VAL);
//  EEPROM.put(SETPOINT_ADDR, setPoint);
//  EEPROM.put(BACKLIGHT_ADDR, backlight);
//}

//
void timing(void) {
  static unsigned long prevmicros = 0; // время когда последний раз делали
  if (micros() - prevmicros > PERIOD_CLOCK) {
      prevmicros = micros();  //принимает значение каждые полсекунды
      counter = !counter;
      if (counter == false) {
          sek++;
          lcd.setCursor(3, 0);
          lcd.print(F(":"));         //выводим символ ":"между  минутами и секундами
      }
      if (sek > 59) { //если переменная секунда больше 59 ...
          sek = 0; //сбрасываем ее на 0
          minu++;//пишем +1 в переменную минута
      }
      if (minu > 59) { //если переменная минута больше 59 ...  
          minu = 0; //сбрасываем ее на 0
      }
      
      lcd.setCursor(1, 0); //выводим значение минут
      if (minu >= 0 && minu < 10) {
          lcd.print(F("0"));
          lcd.print(minu);
      } else lcd.print(minu); //количество минут
  
      lcd.setCursor(4, 0); //выводим значение секунд
      if (sek >= 0 && sek < 10) {
          lcd.print(F("0"));
          lcd.print(sek);
      } else lcd.print(sek); //количество секунд
  }
}

void chsPos(void) { // функция отвечющая за выбор числа
    sel=!sel;
    temp1Status = *pSetTemp1;
    temp2Status = *pSetTemp2;
    curTemp = 0;   
}
  
void supCursor(void){
     if (sel) { *pSetTemp2 = curTemp + temp2Status; 
        lcd.setCursor(11, 1); lcd.write(32);
        lcd.setCursor(11, 3); lcd.write(4);  
     }
     else { *pSetTemp1 = curTemp + temp1Status; 
        lcd.setCursor(11, 1); lcd.write(4); 
        lcd.setCursor(11, 3); lcd.write(32); 
     }     
}

/*void popCursor() {
     if (sel) { *pSetTemp2 = curTemp + temp2Status; 
        lcd.setCursor(11, 1); lcd.write(32);
        lcd.setCursor(11, 3); lcd.write(4);  
     }
     else { *pSetTemp1 = curTemp + temp1Status; 
        lcd.setCursor(11, 1); lcd.write(4); 
        lcd.setCursor(11, 3); lcd.write(32); 
     }     
}*/

void SetTuning(void) {
  
  supCursor(); 
  
  pSetTemp1 = &setTemp1; 
  pSetTemp2 = &setTemp2;
  
  if (setTemp1 <= MIN_TEMP) {
      setTemp1 = MIN_TEMP;
  }
  
  if (setTemp2 <= MIN_TEMP) {
      setTemp2 = MIN_TEMP;
  }
  
  if (setTemp1 >= MAX_TEMP) {
      setTemp1 = MAX_TEMP;
  }
  
  if (setTemp2 >= MAX_TEMP) {
      setTemp2 = MAX_TEMP;
  }
  
  myPID1.SetTunings(kp1, ki1, kd1); // Настройка ПИД-регулятора во время работы
  myPID2.SetTunings(kp2, ki2, kd2); // Настройка ПИД-регулятора во время работы

  /* Тут настраиваются коэффициенты ПИД*/
  /*kp1 = 50;
  ki1 = 0;
  kd1 = 0;

  kp2 = 50;
  ki2 = 0;
  kd2 = 0;*/
}

// делать при длином нажатии
void DoLong(byte button){  
  lcd.print(button); 
  if (button < 10) lcd.print(F("  "));
  else if (button < 100) lcd.print(F(" "));
}



void loadProfile(uint8_t profile) {
  /*for (int i=0; i<10; i++){
            //byte readByte = EEPROM.read(i+OFFSET);
            readByte = EEPROM.read(i+OFFSET);
            Serial.println(readByte);
      }*/
}


void updown(bool up)
{
  if (MenuEnter) { // если находимся в подменю, то
      if (switchPointer == MENU_MANUAL) (up == 1 ? (curTemp += btn1) : (curTemp -= btn2));
      else if (switchPointer == MENU_SETUP) {
        if(up) {
          if ((*mMenu[curMenu].k1) >= 500)  
             (*mMenu[curMenu].k1) = 0; 
          else  (*mMenu[curMenu].k1)+=10; //0.05; 0.5 5 50 
        }  else {
          if ((*mMenu[curMenu].k1) <= 0)   
             (*mMenu[curMenu].k1) = 0; 
          else  (*mMenu[curMenu].k1)-=10;//--; //0.05; 0.5 5 50 
        }
      }  else if (switchPointer == MENU_AUTO) {
        
      }    
  }
}

void long_updown(bool lg) {
  if (MenuEnter) { // если находимся в подменю, то
      if (switchPointer == MENU_MANUAL) {
          if (lg) {         
             if(btnupclick<3) btnupclick++; //увеличивает счетчик кнопки на 1
             else btnupclick = 0;          //если счетчик достиг предела положений то его надо обнулить.
             lcd.setCursor(1, 2);  
             lcd.write(2);
             btn1 = mass_byte[btnupclick];
             DoLong(btn1);
        }   else {
             if(btndnclick<3) btndnclick++; //увеличивает счетчик кнопки на 1
             else btndnclick = 0;          //если счетчик достиг предела положений то его надо обнулить.
             lcd.setCursor(1, 2); 
             lcd.write(3);
             btn2 = mass_byte[btndnclick];
             DoLong(btn2);
        }
      }  else if (switchPointer == MENU_SETUP) {
       
      }
  }   
}

void ok()
{
  if (MenuEnter) { // если находимся в подменю, то
     exOKState = 1;
  } else { // если находимся в главном меню, то
     lcd.clear();
     MenuEnter = 1;
     ButtonClick(ButtonModecount); // Вызывается функция обработки нажатия на кнопку
  }
}

void next() {
  if (MenuEnter) { // если находимся в подменю, то
      if (switchPointer == MENU_MANUAL) {
        chsPos();
      } else if (switchPointer == MENU_AUTO) {
        
      } else if (switchPointer == MENU_SETUP) {
         if (curMenu == CountMenu - 1) curMenu = 0; //следующий пункт меню по кругу
         else  curMenu++; 
      }  
  } else { // если находимся в главном меню, то
    ButtonModecount++;  // с каждым нажатием ButtonModecount++ пока ButtonModecount<4
    if (ButtonModecount > 3) ButtonModecount = 0;
  }
}

void back()
{
  exOKState = 0; // если что то поменял -> не сохр
}

void softReset() {
  asm volatile ("jmp 0");
}

/*
static int profile1[]={
// профиль 1: шаги, температура низа, 9 скоростей роста температуры шага умноженое на 4
          4,150,4,2,4,4,4,4,4,4,4,
          // 9 продолжительностей шага
          120,60,9,15,1,1,1,1,1,
          // температуры верхнего нагревателя по 9 шагам
          150,180,189,190,205,210,215,220};
          for (int i = 0; i < 29; i++) EEPROM.write(i, profile1[i]); 
          
static int profile2[]={
// профиль 2: шаги, температура низа, 9 скоростей роста температуры шага умноженое на 4
          4,150,4,2,4,4,4,4,4,4,4,
          // 9 продолжительностей шага
          120,60,14,15,0,0,0,0,0,
          // температуры верхнего нагревателя по 9 шагам
          150,180,194,195,0,0,0,0};
          //for (int i = 29; i < 58; i++) EEPROM.write(i, profile2[i-29]); 
          for (int i = 0; i < 29; i++) EEPROM.write(i+OFFSET, profile2[i]); //OFFSET = 29
          
static int profile3[]={
// профиль 3: шаги, температура низа, 9 скоростей роста температуры шага умноженое на 4
          4,150,4,3,4,4,4,4,4,4,4,
          // 9 продолжительностей шага
          120,93,4,15,0,0,0,0,0,
          // температуры верхнего нагревателя по 9 шагам
          150,220,224,225,0,0,0,0,0};
          //for (int i = 58; i < 87; i++)EEPROM.write(i, profile3[i-58]);  
          for (int i = 0; i < 29; i++) EEPROM.write(i+OFFSET, profile2[i]); //OFFSET = 58
          
static int profile4[]={
// профиль 4: шаги, температура низа, 9 скоростей роста температуры шага умноженое на 4
          4,150,4,12,4,4,4,4,4,4,4,
          // 9 продолжительностей шага
          120,93,9,15,1,1,1,1,1,
          // температуры верхнего нагревателя по 9 шагам
          150,220,229,230,220,220,220,220,220};
          //for (int i = 87; i < 116; i++) EEPROM.write(i, profile4[i-87]);
          for (int i = 0; i < 29; i++) EEPROM.write(i+OFFSET, profile2[i]); //OFFSET = 87
 }*/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

А зачем вам рассказывать, если вы не слушаете.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

qwone пишет:

А зачем вам рассказывать, если вы не слушаете.

qwone в чем вы со мной не согласны. В том что хочу строки во флэш записать или вы о другом?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Casper17 пишет:
Почему так происходит, я же указал адрес &Menu[0], а он начинает выводить с Menu[1], если после 29 строки указать curr-- то работает как надо! Почему так происходит?

Где это Вы указали адрес &Menu[0]?

В 29-ой строке Вы прочитали то, что находится по адресу &Menu[0]. А там как раз находится адрес &Menu[1]. Вот он и попал в Ваш curr.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BuonanotteMasha, как Вы себе это видите? У Вас вторым элементом структуры идёт адрес некоего плавающего числа. Если Вы загоните адрес в progmem, то он будет константой - его нельзя будет менять в процессе работы. Это нормально? А на что он будет указывать? Где будет находиться само это число?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

BuonanotteMasha пишет:
qwone в чем вы со мной не согласны. В том что хочу строки во флэш записать или вы о другом?
Тем что здесь все расжевано и без меня. https://goo.gl/NxJGc8

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

ЕвгенийП, признал ошибку. Спасибо

Вас  и других не затруднит указать мне (знаю что не по теме, но не хочется сразу покидать эту ветку) как возможно сделать вместо многочисленных вызовов в ветках case функции установки курсора тольк один ее вызов. Под рукой нет платы, поэтому буду думать

void setup() {
  
}

void loop() {
    // объявлен оператор switch
    case  doA :
       lcd.setCursor(1, 1);
       lcd.print(F("A"));
    break;
    case   doB:
       lcd.setCursor(1, 1);
       lcd.print(F("B"));
    break;
    case  doC:
       lcd.setCursor(1, 1);
       lcd.print(F("B"));
    break;

}
BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Думаю либо завести отдельный буфер под строки, либо через указатели на эти строки

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А что такое doA? doB и doC? Есть ограничения на их значения или можно любые задать?

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

и ты это, инвариантные строки, типа  

       lcd.setCursor(1, 1);

за switch вынеси, не раздувай код. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DetSimen пишет:

и ты это, инвариантные строки, типа  

       lcd.setCursor(1, 1);

за switch вынеси, не раздувай код. 

Это не всегда возможно. Часто бывает, что эта строка нужна только в том случае, если хоть что-нибудь из свитча совпало, а если ничего не совпало, то она не нужна. Если же вынести, то она будет выполняться всегда.

Здесь, наверное, можно вообще убрать к чертям свитч, но для этого я жду ответа от ТС.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Полный листинг кода #7

Тут привел фрагмент для лучшего понимания сути моего вопроса. 

enum switchVariants : byte  {  // Определения для переключателя пунктов меню;
  MAIN_MENU, MENU_MANUAL, MENU_AUTO, MENU_SETUP, MENU_START, EXITSAVE
};
switchVariants switchPointer = MAIN_MENU; // С чего начнем цикл;

enum manuState : byte {  // Определения для переключателя ручного режима;
  EDIT_PARAM, HEAT_ZONE, PEAK_ZONE, COOL_ZONE, STOP_PROCESS, EXIT_PROCESS
};
manuState mode = EDIT_PARAM; // С чего начнем цикл;

typedef enum REFLOW_STATE : byte { // Определения для переключателя режимов пайки по профилю;
    REFLOW_STATE_IDLE,
    REFLOW_STATE_COMPLETE,
    REFLOW_STATE_ERROR
}
reflowState_t;
reflowState_t reflowState;

//-----main----------------------//
void setup() {  

}

void loop() {
  
  
  switch (switchPointer)  // Все делаем в одном операторе и одной функции;
  {
    case MAIN_MENU:  /***************** Главное меню ***************/
      
      setString(ButtonModecount); // включаем отображение стрелки выбора меню

      lcd.setCursor(1, 0); // 1 строка
      lcd.print(F("MANUAL")); // настройка
      lcd.setCursor(1, 1); // 2 строка
      lcd.print(F("AUTOMATIC")); // настройка
      lcd.setCursor(1, 2); // 3 строка
      lcd.print(F("OPTIONS")); // ручной режим
      lcd.setCursor(1, 3); // 4 строка
      lcd.print(F("START")); // автоматический режим

      break;

      case MENU_MANUAL: /***************** Ручной режим ***************/
      
      SetTuning(); // настройки регулятора
      tempToPID(PERIOD_PWM);  // передача температуры для расчета ПИД регулятору
      
      //uint8_t Out1 = OutPWR_TOP();
      //uint8_t Out2 = OutPWR_BOTTOM();
      
      lcd.setCursor(1, 1);  // Печатаем на ЖК;
      lcd.print(F("TH:"));
      lcd.print(tc1, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(12, 1); // Печатаем на ЖК;
      lcd.print(F("TY:"));
      lcd.print(setTemp1, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(1, 3);  // Печатаем на ЖК;
      lcd.print(F("TB:"));
      lcd.print(tc2, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(12, 3); // Печатаем на ЖК;
      lcd.print(F("TY:"));
      lcd.print(setTemp2, 0);
      lcd.print(F("\x99""C"));

      lcd.setCursor(14, 0); // Печатаем на ЖК;
      lcd.print(outputPower1, 0);
      lcd.print(F("\x99""%"));

      lcd.setCursor(14, 2); // Печатаем на ЖК;
      lcd.print(outputPower2, 0);
      lcd.print(F("\x99""%"));

      //фиксируем стартовую температуру
      if (flag == 0)
      {
        startTemp = tc1;
        flag = 1;
      }

      switch (mode) { // Выбор режима работы ручного режима
        case EDIT_PARAM: 
          TopStart = 0;
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("EDIT"));   // Режим редактирования температуры
        break;

        case HEAT_ZONE:
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("HEAT"));   // Режим редактирования температуры

          timing();
          //stopProcess();         
  
          mode_PWM1(outputPower1);
          mode_PWM2(outputPower2);

          // запуск работы "ВЕРХА" сразу после догрева "НИЗОМ"
          //верхний нагреватель включится еcли температура "низа" достигнет уснановленной
          if (tc1 >= setTemp1) {    
            

            //if (abs(setTemp1-tc1) <= 2.5) tone(buzzerPin, 1045, 50); //звуковой сигнал
            TopStart = 1; //   нижний нагреватель вышел на необходимую температуру
          }
          
          //
          if (tc2 >= setTemp2) { 
            previousMillis = millis();
            mode = PEAK_ZONE;
          }
        break;

        case PEAK_ZONE:
          
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          //blinking(PERIOD_BLINK, str[0]);
          blinking(PERIOD_BLINK,0);
          
          timing();
          
          mode_PWM1(outputPower1);
          mode_PWM2(outputPower2);

          peak_Temp(); 

          if (previousMillis) {
            tone(buzzerPin, 550); // play 550 Hz tone in background for 'onDuration'
          }

          if (previousMillis && (millis() - previousMillis >= 1000)) {
            previousMillis = 0;
            noTone(buzzerPin);
          }

          if (!modeOfOperation) {
            mode = COOL_ZONE;
            modeOfOperation = true; // Подъем флага - профиль выдержан!
          }
        break;

        case COOL_ZONE:
          timing();          
          turnoff(); // откл реле
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("COOL"));   // Режим редактирования температуры

        break;
          
        case STOP_PROCESS:
           sek = 0; minu = 0;
           turnoff();
           lcd.setCursor(8, 0); // Печатаем на ЖК;
           //blinking(PERIOD_BLINK, str[1]);
           blinking(PERIOD_BLINK,1);
           
        break;
      
    }
    break;

    case MENU_AUTO: /***************** Автоматический режим ***************/
    
    break;

    case MENU_SETUP: /***************** Настройки ***************/
    
    break;

    case MENU_START: /*********** Пуск по заданному термопрофилю *********/         
         
    break;
    
    case EXITSAVE: /*********** Полная очистка экрана *********/
       lcd.setCursor(3, 1);
       lcd.print(F("SAVE and EXIT")); // согласие на сохранение результата/изменений
    break;
  }
}

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

ЕвгенийП пишет:

А что такое doA? doB и doC? Есть ограничения на их значения или можно любые задать?

Про это забудьте, это я как мог хотел упростить ТЗ. Видимо не донес никакой информации

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018
 Хотелось бы избавиться от однотипных вызовов  lcd.setCursor(8, 0); Вот суть
 
        case EDIT_PARAM: 
             TopStart = 0;
             lcd.setCursor(8, 0);  // Печатаем на ЖК;
             lcd.print(F("EDIT"));   // Режим редактирования температуры
        break;
 
        case HEAT_ZONE:
            lcd.setCursor(8, 0);  // Печатаем на ЖК;
            lcd.print(F("HEAT"));   // Режим редактирования температуры
 
            timing();
            //stopProcess();         
  
            mode_PWM1(outputPower1);
            mode_PWM2(outputPower2);
 
            // запуск работы ВЕРХА сразу после догрева НИЗОМ
            //верхний нагреватель включится еcли температура низа достигнет уснановленной
            if (tc1 >= setTemp1) {         
                TopStart = 1; //   нижний нагреватель вышел на необходимую температуру
            }
 
            if (tc2 >= setTemp2) { 
               previousMillis = millis();
               mode = PEAK_ZONE;
            }        
        break;
 
        case PEAK_ZONE:
          
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          //blinking(PERIOD_BLINK, str[0]);
          blinking(PERIOD_BLINK,0);
          
          timing();
          
          mode_PWM1(outputPower1);
          mode_PWM2(outputPower2);
 
          peak_Temp(); 
 
          if (previousMillis) {
            tone(buzzerPin, 550); 
          }
 
          if (previousMillis && (millis() - previousMillis >= 1000)) {
            previousMillis = 0;
            noTone(buzzerPin);
          }
 
          if (!modeOfOperation) {
            mode = COOL_ZONE;
            modeOfOperation = true; // Подъем флага - профиль выдержан!
          }
        break;
 
        case COOL_ZONE:
          timing();          
          turnoff(); // откл реле
          lcd.setCursor(8, 0);  // Печатаем на ЖК;
          lcd.print(F("COOL"));   // Режим редактирования температуры
 
        break;
          
        case STOP_PROCESS:
           sek = 0; minu = 0;
           turnoff();
           lcd.setCursor(8, 0); // Печатаем на ЖК;
           //blinking(PERIOD_BLINK, str[1]);
           blinking(PERIOD_BLINK,1);
           
        break;
      
    }
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BuonanotteMasha пишет:

Про это забудьте, это я как мог хотел упростить ТЗ. Видимо не донес никакой информации

Блин, ну тогда, что такое EDIT_PARAM, HEAT_ZONE и т.п.?

Вопрос-то простой. Я могу все эти выборы свести к 0, 1, 2, и т.п.?

Если могу, то задача элементарно решается.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Замените на здоровье, тип byte. Насчет элементарности до меня не дошло решение

enum manuState : byte {  // Определения для переключателя ручного режима;
  EDIT_PARAM, HEAT_ZONE, PEAK_ZONE, COOL_ZONE, STOP_PROCESS, EXIT_PROCESS
};
manuState mode = EDIT_PARAM; // С чего начнем цикл;

 

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Да и функция blinking. Используется для вывода мигающего текста

const char  str[] PROGMEM = "STOP";
const char str1[] PROGMEM = "PEAK";
const char str_[] PROGMEM = "    ";

//
//

void blinking(unsigned long intervalTime, bool number) {
  static unsigned long prevTime = 0;
  static boolean i=0;
  if(millis() - prevTime >= intervalTime) {                 //Если счетчик превысил интервал, 
     i = !i; prevTime = millis(); }  //меняем значение переменной i, добавляем к переменной время интервала.
     if (number) lcd.print(i ? (const __FlashStringHelper*)str : (const __FlashStringHelper*)str_); 
     else lcd.print(i ? (const __FlashStringHelper*)str1 : (const __FlashStringHelper*)str_);  
}

Вот в этом фрагменте допустим

          
        case STOP_PROCESS:
           sek = 0; minu = 0;
           turnoff();
           lcd.setCursor(8, 0); // Печатаем на ЖК;
           //blinking(PERIOD_BLINK, str[1]);
           blinking(PERIOD_BLINK,1);
           
        break;
      
    }
    break;

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Тогда делаете так. Заводите функции для каждой ветви. Скорее всего столько функций, сколько ветвей, Вам не понадобится, т.к. часть действий одинаковая с точностью до параметров.

Все функции должны иметь одинаковый прототип (т.е. возвращаемое значение и параметры). Даже, если какой-то функции параметры не нужны, они должны быть, что у всех функций было одинаково.

Сейчас я сделаю пример, только Вы тут уже столько разных кодов наплодили, что давайте я сделаю абстрактный пример, чтобы Вы идею поняли а Вы уж для своего кода по образу и подобию там сделаете что надо.

Вот, смотрите, допустим, есть у Вас вот такой switch

switch(kaka) {
	case 0:
		Serial.println("0 detected");
		break;
	case 1:
		Serial.println("1 detected");
		break;
	case 2:
		Serial.println("2 detected");
		break;
	case 3:
		Serial.println("3 detected");
		break;
	case 4:
		digitalWrite(LED_BUILTIN, LOW);
		break;
	case 5:
		digitalWrite(LED_BUILTIN, HIGH);		
		break;
}

Что мы здесь видим? Ветви 0-3 - одинаковые с точностью до параметра - строки. Но реально достаточно передавать целое число. А ветки 4-5 одинаковые с точностью до параметра - логического выражение (0 или не 0).

Значит, чтобы заменить всё, нам достаточно двух функций. У обеих параметр будет int. Для веток 0-3 - пердавать будем собственно индекс. А для веток 4-5 - тоже индекс, но внутри вычтем из него 4, чтобы получить 0 или 1.

Пишем эти две функции.

void f_0_3 (const int n) {
	Serial.print(n);
	Serial.println(" detected");
}

void f_4_5 (const int n) {
	digitalWrite(LED_BUILTIN, n - 4);
}

Теперь запишем массив функций, которые надо вызывать при том или ином значении kaka. очевидно, что при 0-3 надо вызывать f_0_3, а при 4-5 надо вызывать f_4_5. Так и напишем.

typedef void (* TFunc) (const int);
static const TFunc functions[] = { f_0_3, f_0_3, f_0_3, f_0_3, f_4_5, f_4_5 };

Ну, собственно, теперь вместо того длинного switch можно использовать вот такую строку

functions[kaka] (kaka);

Соберём всё вместе.

//
//	Где-то выше определяем две функции и массив
//
void f_0_3 (const int n) {
	Serial.print(n);
	Serial.println(" detected");
}
void f_4_5 (const int n) {
	digitalWrite(LED_BUILTIN, n - 4);
}
typedef void (* TFunc) (const int);
static const TFunc functions[] = { f_0_3, f_0_3, f_0_3, f_0_3, f_4_5, f_4_5 };

// 
//	Вместо всего switch просто пишем одну строку
//
functions[kaka] (kaka);

Ну, вот, как-то так, если нигде не ляпнул чего-нибудь.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Спасибо, освобожусь, обязательно попробую вашу идею

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Пока сделал так, еще доработаю 

#include <avr/pgmspace.h>

const char string_0[] PROGMEM = "EDIT";
const char string_1[] PROGMEM = "HEAT";
const char string_2[] PROGMEM = "COOL";

char buf[6];  
        
// создаем таблицу с отсылками к этим строкам:
const char *const string_table[] PROGMEM  = { string_0, string_1, string_2 };

void f1 (const uint8_t n) {  
        strcpy_P(buf, (char*)pgm_read_word(&(string_table[n]))); 
        Serial.println(buf);
	
}
void f2 (const uint8_t n) {
	//digitalWrite(LED_BUILTIN, n - 4);
}
typedef void (* TFunc) (const uint8_t);
static const TFunc functions[] = { f1, f2 };

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

void loop() {
  functions[0] (1);
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Строки 13 и 14 ... зачем? print и сам умеет из прогмема брать.

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Всмысле Serial.println(string_table[n]) верно?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, вроде работало так. Работает?

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Нет  Serial.println(string_table[n]); не работает

Евгений спасибо вам за активную помощь, но возможно ли вызывать функцию blinking #23 из f1. Или лучше сделать ее отдельный вызов. Все строки string_0 ... string_5 у меня наэкране занимают одинаковые знакоместа. С первыми тремя я разобрался, но две другие понадобилось чтобы мигали  как бы сигнализировали, поэтому и спрашиваю

// строка мигает в течение intervalTime
void blinking(unsigned long intervalTime, bool number) {
  static unsigned long prevTime = 0;
  static boolean i=0;
  if(millis() - prevTime >= intervalTime) {                 //Если счетчик превысил интервал, 
     i = !i; prevTime = millis(); }  //меняем значение переменной i, добавляем к переменной время интервала.
    // if (number) Serial.println(i ? (const __FlashStringHelper*)string_3 : (const __FlashStringHelper*)string_5); 
    // else Serial.println(i ? (const __FlashStringHelper*)string_4 : (const __FlashStringHelper*)string_5);  
    // тут хотелось бы выводить нужную строку в зависимости от состояния i
}
#include <avr/pgmspace.h>

const char string_0[] PROGMEM = "EDIT";
const char string_1[] PROGMEM = "HEAT";
const char string_2[] PROGMEM = "COOL";
const char string_3[] PROGMEM = "STOP";
const char string_4[] PROGMEM = "PEAK";
const char string_5[] PROGMEM = "    ";

char buf[6];  
        
// создаем таблицу с отсылками к этим строкам:
const char *const string_table[] PROGMEM  = { string_0, string_1, string_2,string_3, string_4, string_5 };

void f1 (const uint8_t n) {  
        strcpy_P(buf, (char*)pgm_read_word(&(string_table[n]))); 
        Serial.println(buf);
	
}
void f2 (const uint8_t n) {
	//digitalWrite(LED_BUILTIN, n - 4);
}
typedef void (* TFunc) (const uint8_t);
static const TFunc functions[] = { f1, f2 };

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

void loop() {
  functions[0] (1);
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А почему нельзя-то? Неужели Он уж и это запретил? :)

BuonanotteMasha
BuonanotteMasha аватар
Offline
Зарегистрирован: 02.01.2018

Тогда буду пробывать, если разрешено :)