Изменение параметров скетча в полевых условиях

Нет ответов
step962
Offline
Зарегистрирован: 23.05.2011
Прошедшей зимой появилось немного свободного времени, часть которого я решил потратить на оптимизацию и стандартизацию рутинных операций, часто встречающихся в практике программирования микроконтроллеров - по крайней мере, в моей. Первым результатом в этом направлении стал редактор параметров.
Итак, имеем успешно работающий скетч, основные ошибки в котором уже выявлены и устранены на рабочем месте и пришло время тонкой настройки логики “в поле”. Эту задачу - при правильном проектировании скетча - можно решить путем изменения программных параметров. Вопрос, как изменять их с минимальными усилиями вдали от рабочего компьютера.
Вариант с прикручиванием к МК дисплея и набора кнопок лично меня не очень привлекает - слишком много приходится прилагать усилий для выполнения в общем-то плевой задачи. Работа через UART тоже не всегда удобна: МК может стоять в труднодоступном месте, да и устройство с последовательным портом не всегда оказывается под рукой. Поэтому для своих приложений я выбрал связку смартфон/планшет+Bluetooth-модуль.
Для такой комбинации, естественно, требуется установить на смартфон приложение, способное общаться с МК по Bluetooth-каналу. Оно есть у меня (приложение MoDyz, загружать тут). В конкретном проекте (как и в других планируемых) оно выступает “черным ящиком”, выполняющим возлагаемые на него функции без необходимости знать, каким образом они выполняются.
В описываемом проекте от MoDyz требуется очень немного - вывести на экран смартфона несколько полей ввода и поддерживать весь цикл редактирования параметров. Большая часть отправляемых из скетча в MoDyz инструкций занимается украшательством. Они выводят заголовки групп, рисуют рамочки, выводят пояснительные тексты. Настройка собственно поля ввода производится двумя инструкциями - первая определяет индекс поля и его координаты, а вторая задает исходное значение параметра.
Вот так выглядит простенький скетч, поддерживающий манипуляции с четырьмя программными параметрами:
#include <avr/pgmspace.h>;
// библиотека, автоматизирующая процесс создания управляющих строковых последовательностей
#include <MD_Scout.h>
#include <mdsender_bt.h>

#include <SoftwareSerial.h>
#include <EEPROM.h>

const PROGMEM char sTitle1[] = "========= MoDyz_BT =========";
const PROGMEM char sTitle2[] = "Software serial test started";

struct Params {
  byte flg;
  int Param1, Param2;
  float Param3, Param4;
} params;

SoftwareSerial swSer(7,6); // RX,TX

String sResponse;

String sCmd="";
long nextMillis;

MD_Sender_BT mdScout;

char sBuffer[60];

void getMEMStr(const char *MEMStr) {
  int len,k;
  len = strlen_P(MEMStr);
  for(k=0;k<len;k++) sBuffer[k] = pgm_read_byte_near(MEMStr+k);
  sBuffer[k]=0;
}

void getEEPROM() {
  EEPROM.get(0,params);  
}

void updateEEPROM() {
  EEPROM.put(0,params);  
}

// функция формирует из поданного на вход числа с плавающей точкой строку,
// содержащую до 4 цифр в дробной части (нули справа отбрасываются 
String float2Str(float v) {
  String s = String(v,4);
  while(s.length()>0 && s.charAt(s.length()-1)=='0') {
      s[s.length()-1] = ' ';
      s.trim();
  }
  return s;
}

void InitParams() {
  getEEPROM();
  if(params.flg!=62) {  // флаг не установлен - начальная инициализация параметров
    params.flg = 62;
    params.Param1 = 100;
    params.Param2 = 200;
    params.Param3 = 300.33;
    params.Param4 = 400.44;
    updateEEPROM();
  }  
}

// ==========================================================================================

void addCmdString(String sCmd) {
  sResponse += sCmd;
  if(sResponse.length()>100) mdScout.send(swSer,sResponse);
}

// ==========================================================================================
void testEdit() {
  int y;
  sResponse = "";
// Рамка и текст вокруг первой группы органов управления
  addCmdString(mdScout.setTextColors(C_YELLOW, C_BLACK));
  addCmdString(mdScout.setLineColors(C_RED,C_GREEN));
  addCmdString(mdScout.drawRectangle(8,58,448,252));
  addCmdString(mdScout.drawRectangle(12,62,444,248));
  addCmdString(mdScout.setTextSize(15));
  addCmdString(mdScout.outTextL(20,70,"Южная бочка"));

  addCmdString(mdScout.setTextColors(C_YELLOW, C_BLUE));
  addCmdString(mdScout.setTextSize(15));
  addCmdString(mdScout.outTextL(20,110,"Hmin (15px)"));
  addCmdString(mdScout.defineEditText(1,270,80,160,60));
  addCmdString(mdScout.setEditTextStr(1,String(params.Param1)));
  addCmdString(mdScout.setTextSize(20));
  addCmdString(mdScout.outTextL(20,200,"Hmax (20px)"));
  addCmdString(mdScout.defineEditText(2,270,170,160,60));
  addCmdString(mdScout.setEditTextStr(2,String(params.Param2)));

// Рамка и текст вокруг второй группы органов управления  

  y=300;
  addCmdString(mdScout.setTextColors(C_CYAN, C_DKGRAY));
  addCmdString(mdScout.setLineColors(C_YELLOW,C_GREEN));
  addCmdString(mdScout.drawRectangle(48,y-18,474,462));
  addCmdString(mdScout.drawRectangle(52,y-12,470,458));
  addCmdString(mdScout.setTextSize(20));
  addCmdString(mdScout.outTextL(60,y,"Северная бочка"));

  addCmdString(mdScout.setTextColors(C_GREEN, C_RED));
  addCmdString(mdScout.setTextSize(17));
  addCmdString(mdScout.outTextR(310,y+40,"Hmin (17px)"));
  addCmdString(mdScout.defineEditText(4,320,y+10,140,60));
  addCmdString(mdScout.setEditTextStr(4,float2Str(params.Param3)));
  addCmdString(mdScout.setTextSize(22));
  addCmdString(mdScout.outTextR(310,y+120,"Hmax (22px)"));  
  addCmdString(mdScout.defineEditText(5,320,y+90,140,60));
  addCmdString(mdScout.setEditTextStr(5,float2Str(params.Param4)));

  mdScout.send(swSer,sResponse);
  
  sResponse = "#XX;#XX;"; mdScout.send(swSer,sResponse);
} //of testEdit

void doTest101() {
  Serial.println("Just a test...");
}

void updateParams(String sCmd) {
  int p1=sCmd.indexOf(',');
  int p2=sCmd.indexOf(';');
  int controlID=0,newValInt;
  float newValFloat;
  String sVal;
  char sValArr[15];
  Serial.print("p1: "); Serial.print(p1); Serial.print(" / ");
  if(p1!=-1) {
    controlID = sCmd.substring(3,p1).toInt();
    Serial.print("<"+sCmd.substring(3,p1)+">");
    if(p2==-1) Serial.println(" semicolon not found!");
    else {
      Serial.print(" new Value: ");
      if(controlID==1 || controlID==2) { // целые числа
        newValInt=sCmd.substring(p1+1,p2).toInt();
        Serial.println(newValInt);
      }
      if(controlID==4 || controlID==5) {  // числа с плавающей точкой
        sVal = sCmd.substring(p1+1,p2);
        sVal.toCharArray(sValArr,sVal.length()+1);        
        newValFloat = atof(sValArr);
        Serial.print(" "+sVal+" -> ");
        if(sVal.indexOf('.')>=0)
        Serial.println(newValFloat,sVal.length()-sVal.indexOf('.')-1);
        else Serial.println(newValFloat,0);
      }
      
      switch (controlID) {
        case 1: params.Param1=newValInt; break;
        case 2: params.Param2=newValInt; break;
        case 4: params.Param3=newValFloat; break;
        case 5: params.Param4=newValFloat; break;
      }
      updateEEPROM();
    }
  }
  else Serial.println("comma not found!");
} //of updateParams

void interpreteCommand() {
  Serial.print("Interprete command: "); Serial.println(sCmd);
  if(sCmd.length()>0) {
    if(sCmd.equals("drw")) testEdit();
    else if(sCmd.equals("fun1")) testEdit();
    else if(sCmd.equals("101")) doTest101();
    else if(sCmd.indexOf("#FE")>=0) {updateParams(sCmd); sCmd = "";}
    sCmd = "";
  }
} //of interpreteCommand

// ==========================================================================================

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

  getMEMStr(sTitle1);
  Serial.println(sBuffer);
  getMEMStr(sTitle2);
  Serial.println(sBuffer);

  InitParams();

  Serial.print("Flag: ");   Serial.println(params.flg);
  Serial.print("Param1: "); Serial.println(params.Param1);
  Serial.print("Param2: "); Serial.println(params.Param2);
  Serial.print("Param3: "); Serial.println(float2Str(params.Param3));
  Serial.print("Param4: "); Serial.println(float2Str(params.Param4));
  
  nextMillis = millis();
  nextMillis = nextMillis + 100;
}

void loop() {
  char c;
  while (swSer.available() > 0) {
    c = swSer.read();
    if(c=='\r')       interpreteCommand();
    else if(c=='\n')  interpreteCommand();
    else              sCmd = sCmd + c;
    Serial.write(c);
  }
} //of loop

 

Строки 1...7 - подключение необходимых библиотек. Библиотеку MD_Scout можно скачать здесь.
Строки 9...27 - объявление используемых в скетче типов, констант и переменных.
В строках 9-10 объявлены размещаемые в программной памяти текстовые константы (экономим оперативную память; промежуточное считывание такой константы в буфер для дальнейшего вывода в окно терминала производится в функции getMEMStr), далее идет определение структуры, в которой хранятся параметры, редактированием которых и занимается описываемый скетч. Параметры сведены в структуру, чтобы не заморачиваться вычислением адреса того или иного параметра для его чтения/записи во флэш-памяти. Параметры считываются из флэш-памяти единым блоком (функция getEEPROM), также и сохраняются в памяти (функция updateEEPROM).
Строки 29...42 - уже упомянутые функции для работы с программной и флэш-памятью.
Строки 55...65 - функция, которая производит инициализацию параметров при запуске скетча. При “первом” запуске скетча функция сравнивает значение флага (первый байт структуры Params) с “магическим” числом и если они не совпадают, производит начальную настройку параметров и записывает их в память. В противном случае в качестве параметров будут использоваться сохраненные ранее (при одном из предыдущих прогонов скетча) значения. Если требуется заново выполнить начальную настройку параметров, измените “магическое” число на какое-либо другое - после компиляции эта ветка выполнится снова. Ровно один раз.
Строки 69...72 - сборка командной строки из отдельных команд и отправка по Bluetooth по мере роста длины строки.
Строки 75...123 - а вот это то место, где эти отдельные команды и формируются. 
Строки 125...163 - в этой функции разбираются поступившие от смартфона блоки символов, начинающиеся с последовательности “#FE”. Выделяется идентификатор поля ввода (первый параметр в методе defineEditText) и новое (в частном случае и старое) значение параметра. Это строки 133...151, затем производится обновление соответствующего параметра и запись во флэш.
Строки 165...174 - тут разбираются поступающие от смартфона данные и выполняются те или иные действия:
по “drw” или “fun1” вызывается функция testEdit, готовящая смартфон к редактированию параметров,
по “101” - вызов функции-заглушки doTest101, заготовка для дальнейших проверок. Не стоит обращать внимания,
по уже упомянутой “#FE” - обновление значения параметра.
 
Ну, а в оставшихся строках 178...208 размещены стандартные для Arduino функции setup и loop - инициализация и главный цикл.
 
Парочка картинок для иллюстрации написанного (кликабельно):
1. Вид экрана смартфона после инициализации:
2. Стандартная клавиатура выводится после клика на одно из полей ввода: