Изменение параметров скетча в полевых условиях
- Войдите на сайт для отправки комментариев
Пт, 05/04/2019 - 18:15
Прошедшей зимой появилось немного свободного времени, часть которого я решил потратить на оптимизацию и стандартизацию рутинных операций, часто встречающихся в практике программирования микроконтроллеров - по крайней мере, в моей. Первым результатом в этом направлении стал редактор параметров.
Итак, имеем успешно работающий скетч, основные ошибки в котором уже выявлены и устранены на рабочем месте и пришло время тонкой настройки логики “в поле”. Эту задачу - при правильном проектировании скетча - можно решить путем изменения программных параметров. Вопрос, как изменять их с минимальными усилиями вдали от рабочего компьютера.
Вариант с прикручиванием к МК дисплея и набора кнопок лично меня не очень привлекает - слишком много приходится прилагать усилий для выполнения в общем-то плевой задачи. Работа через 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. Стандартная клавиатура выводится после клика на одно из полей ввода: