Контроллер для кордовых электричек
процесс идет, только медленнее чем хотелось бы.
уже прикрутил разложение целочисленных значений на разряды и прикрутил изменение значащих цифр.
но что то с этим надо делать, целочисленные в тысячах, время - минуты секунды, надо что то придумать, как стандартизовать.
для обработки значащих значений, разные алгоритмы, так еще надо учитывать, какой параметр, каким алгоритмом менять (((
вот кусок кода для разложения тысяч на четыре числа
data[i][0] = Stack[i]/1000;
data[i][1] = (Stack[i]-data[i][0]*1000)/100;
data[i][2] = (Stack[i]-data[i][0]*1000-data[i][1]*100)/10;
data[i][3] = (Stack[i]-data[i][0]*1000-data[i][1]*100-data[i][2]*10);
а потом все опять в кучу при записи 😄
ладно, поплакался … полегчало … продолжу )
завтра на полетушки, к вечеру попробую “причесать” но без обмена,
пока не понимаю, как его реализовать, есть одна задумка, но … дъявол в деталях.
я не могу программатор сделать ведущим i2c, а будучи ведомым он не может давать команды таймеру,
думаю передавит таймеру байт команды, типа готов принимать/передавать,
таймер приняв байт - смотрит и либо отправляет пакет с параметрами либо принимает,
на макетке между двумя UNO вроде работает, а дальше… посмотрим
Поразрядную корректировку целочисленных значений до 9999 сделал.
И сделал изменение булевых параметров False/True.
Следующим аккордом будут временные параметры, минуты/десятки секунд,
Точность до секунд делать не вижу никакого смысла.
т.е. параметр в программаторе будет выглядеть, ММ:С0,
сделаю возможность установки от 00:00 до 30:00, думаю этого будет достаточно
Вроде допилил, только без обмена с таймером.
Если есть связка Uno+LCD+Key, можно потестить.
Programmer.zip
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define serialPort
#define BTN_UP 1
#define BTN_DOWN 2
#define BTN_LEFT 3
#define BTN_RIGHT 4
#define BTN_SELECT 5
#define BTN_NONE 10
// стоковые параметры для таймера
#define Motor_ESC_Max_THR 2200 // максимальные обороты двигателя
#define Motor_ESC_Normall_THR 2000 // номинальные (полетные) обороты
#define Motor_ESC_Landing_THR 1400 // посадочный режим
#define Motor_ESC_Min_THR 800 // двигатель стоп
#define Time_Of_Calibrate 2 // время между Макс газ и мин газ при необходимости калибровке регулятора
#define Takeoff 120 // время отведенное на взлет и уборку шасси, в течении этого времени процедура старта может быть отменена (т.е. время до уборки шасси) - 10 секунд
#define GearUp 40 // время время после старта, начало уборки шасси - 5 секунд
#define InFly 330 // время полета в секундах с момента завершения процедуры старта - сейчас стоит 60 секунд
#define Landing 40 // время отводимое на посадку, в течении этого времени будут выпущены шасси и снижены обороты мотора до посадочных - 60 секунд
#define delay_g 100 // коэффициент торможения ))
#define free1 0
#define free2 0
#define free3 0
#define free4 0
#define free5 0
#define free6 0
#define free7 0
#define free8 0
uint8_t stateButtonKey = 0;
int countMenu = 15;
boolean flagButtonState = true; // изменилось состояние кнопок
uint8_t lastStateButton = 10; // нет нажатия кнопок
uint64_t timeOfChange;
uint8_t position = 0;
int8_t data [14] [4]; // массив для отображения побайтно
char* menu [17] {
"THR Norm Level ", // нормальные обороты мотора
"THR Land Level ", // обороты мотора при посадке
"FREE-1 uint16_t ", // 1 резервный пункт меню числовой параметр
"FREE-2 uint16_t ", // 2 резервный пункт меню числовой параметр
"FREE-3 uint16_t ", // 3 резервный пункт меню числовой параметр
"Flight time ", // общее время полета с момента уборки шасси
"Time for start ", // время на “убежать к ручке”
"Time for GUp ", // момент уборки шасси с момента старта
“Time for landing”, // время на посадку.
"FREE-4 uint16_t ", // 4 резервный пункт меню временной параметр
"FREE-5 uint16_t ", // 5 резервный пункт меню временной параметр
"FREE-6 uint16_t ", // 6 резервный пункт меню временной параметр
"FREE-1 bollean ", // 1 резервный пункт меню логический параметр
"FREE-2 bollean ", // 2 резервный пункт меню логический параметр
"Write data ", // записать данные в таймер
"Read data " // прочитать данные с таймера
};
uint16_t Stack [] { // программирумые параметры
Motor_ESC_Normall_THR, // нормальные обороты мотора
Motor_ESC_Landing_THR, // обороты мотора при посадке
free1,
free2,
free3,
InFly, // общее время полета с момента уборки шасси
Takeoff, // время на “убежать к ручке”
GearUp, // момент уборки шасси с момента старта
Landing, // время на посадку.
free4,
free5,
free6,
free7,
free8
};
int readButton()
{
int keyAnalog = analogRead(A0);
if (keyAnalog < 50) return BTN_RIGHT;
else if (keyAnalog < 150) return BTN_UP;
else if (keyAnalog < 300) return BTN_DOWN;
else if (keyAnalog < 550) return BTN_LEFT;
else if (keyAnalog < 750) return BTN_SELECT;
else return BTN_NONE;
}
/*
void clearLine(int line)
{
lcd.setCursor(line, 1);
lcd.print(" ");
}
/
void printDisplay(uint8_t pos, uint8_t line, String message)
{
lcd.setCursor(pos, line);
lcd.print(message);
}
//******************************************************************************
void readData()
{
uint8_t pos = 0;
uint8_t i = 0;
printDisplay(6, 1, “Read OK!”);
Serial.println(“Read OK!”);
for (i = 0; i<13; i++)
{
if (i >= 0 && i <= 4) pos = 0;
else if (i >= 5 && i <= 11) pos = 1;
else pos = 2;
Serial.println(i);
Serial.println(Stack[i]);
switch (pos)
{
case 0:
data[i][0] = Stack[i]/1000;
data[i][1] = (Stack[i]-data[i][0]*1000)/100;
data[i][2] = (Stack[i]-data[i][0]*1000-data[i][1]*100)/10;
data[i][3] = (Stack[i]-data[i][0]*1000-data[i][1]*100-data[i][2]*10);
break;
case 1:
data[i][0] = Stack[i]/60; // минуты
data[i][3] = (Stack[i]-data[i][0]*60)/10; // десятки секунд
break;
}
}
}
void writeData()
{
printDisplay(0, 1, “write timer data”);
}
//*******************************************************************************
void setup()
{
#ifdef serialPort
Serial.begin(9600);
#endif
lcd.begin(16, 2);
lcd.print(“Programmer ready”);
lcd.cursor();
// lcd.blink();
#ifdef serialPort
Serial.println(“Programmer ready”);
#endif
printDisplay(0,0,menu[countMenu]);
}
void loop()
{
stateButtonKey = readButton();
if ((stateButtonKey != lastStateButton) && flagButtonState)
{
Serial.println(“Button”);
Serial.println(stateButtonKey);
flagButtonState = false; // изменилось состояние и флаг поднят
timeOfChange = millis();
}
if (millis() - timeOfChange > 120 && (stateButtonKey != lastStateButton))
{
Serial.println(“Wait”);
lastStateButton = stateButtonKey;
Serial.println(stateButtonKey);
flagButtonState = true;
switch (stateButtonKey)
{
//***********************************************************************
case BTN_UP:
if (countMenu >= 0 && countMenu <= 4)
{
data [countMenu][position]++;
if (data [countMenu][position] > 9) data [countMenu][position] = 0;
lcd.setCursor(position, 1);
lcd.print(data [countMenu][position]);
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
data [countMenu][position]++;
if (position == 0 && data [countMenu][position] > 30) data [countMenu][position] = 0;
if (position == 3 && data [countMenu][position] > 5) data [countMenu][position] = 0;
printDisplay(0,1," ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print("😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0) data [countMenu][0] = 1; else data [countMenu][0] = 0;
lcd.setCursor(0, 1);
if (data [countMenu][0] == 0) lcd.print("False "); else lcd.print("True “);
lcd.setCursor(2, 1);
}
break;
//*********************************************************************
case BTN_DOWN:
if (countMenu >= 0 && countMenu <= 4)
{
data [countMenu][position]–;
if (data [countMenu][position] < 0) data [countMenu][position] = 9;
lcd.setCursor(position, 1);
lcd.print(data [countMenu][position]);
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
data [countMenu][position]–;
if (position == 0 && data [countMenu][position] < 0) data [countMenu][position] = 30;
if (position == 3 && data [countMenu][position] < 0) data [countMenu][position] = 5;
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print("😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0) data [countMenu][0] = 1; else data [countMenu][0] = 0;
lcd.setCursor(0, 1);
if (data [countMenu][0] == 0) lcd.print("False "); else lcd.print("True “);
lcd.setCursor(2, 1);
}
break;
//*********************************************************************
case BTN_LEFT:
countMenu–;
if (countMenu < 0) countMenu = 15;
printDisplay(0,0,menu[countMenu]);
if (countMenu >= 0 && countMenu <= 4)
{
printDisplay(0,1,” “);
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.print(data[countMenu][1]);
lcd.print(data[countMenu][2]);
lcd.print(data[countMenu][3]);
lcd.setCursor(0, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print(“😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(2, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0)
{
printDisplay(0,1,” ");
printDisplay(0,1,"False “);
}
else
{
printDisplay(0,1,” ");
printDisplay(0,1,"True “);
}
}
if (countMenu >= 14 && countMenu <= 15)
{
printDisplay(0,1,” “);
}
position = 0;
lcd.setCursor(position, 1);
break;
//**************************************************************
case BTN_RIGHT:
countMenu++;
if (countMenu > 15) countMenu = 0;
printDisplay(0,0,menu[countMenu]);
if (countMenu >= 0 && countMenu <= 4)
{
printDisplay(0,1,” “);
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.print(data[countMenu][1]);
lcd.print(data[countMenu][2]);
lcd.print(data[countMenu][3]);
lcd.setCursor(0, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print(“😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(2, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0)
{
printDisplay(0,1,” ");
printDisplay(0,1,"False “);
}
else
{
printDisplay(0,1,” ");
printDisplay(0,1,"True “);
}
}
if (countMenu >= 14 && countMenu <= 15)
{
printDisplay(0,1,” ");
}
position = 0;
lcd.setCursor(position, 1);
break;
//****************************************************************************
case BTN_SELECT:
if (countMenu == 14) {writeData(); break;}
if (countMenu == 15) {readData(); break;}
if (countMenu >= 0 && countMenu <=4)
{
position++;
if (position > 3) position = 0;
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
if (position == 0) position = 3; else position = 0;
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
position = 0;
lcd.setCursor(position, 1);
}
break;
}
}
}
Массив Stack[] предназначен для обмена с таймером
массив data[][] нужен для отображения и изменения значений, что бы менять поразрядно )
Алгоритм обмена в принципе “нарисован”, осталось “причесать” и “прикрутить” в программатор и таймер.
Пришлось его делать через … в общем надо было придумать, как заставить ведущего i2c выполнять команды ведомого )
На нобелевку конечно не потянет, а так … самому понравилось.
Алгоритм выглядит примерно так
- при включении тайера он проверяет наличие на i2c устройства с адресом 0x0f, с этим адресом будет инициализироваться программатор.
- если устройство есть, таймер переходит в режим программирования.
- каждую секунду таймер запрашивает один байт информации от программатора.
- программатор может передать таймеру только три кода 0x00 - никаких действий, 0xCC - передать данные и 0xAA - принять данные,
- получив команду на передачу данных, таймер отправит программатору массив,
- получив от программатора команду на прием данных, таймер запросит передачу от программатора массива.
В будни буду дома, попробую
Приступил к встраиванию обмена между таймером (пока это Nano на “соплях”) и программатором,
как не странно к нему тоже “сопли” прикручены ), разъема i2c на платах нет (
А если через ISP попробовать? Или, как это в фирмовых) через порт к которому регулятор подключен.
ISP это интерфейс внутрисхемного программирования, под наши задачи не подходит,
разве что каждый раз прошивать прошивку в камне таймера ))
Регулятор подключается к обычному цифровому выходу,
его в принципе можно сделать двунаправленным, что бы и передавать и принимать через него,
но это реально лишняя головная боль, в плане написания протокола обмена.
Тогда уж проще использовать UART.
Может так и надо сделать, тем более, что UART на таймере всяко будет выведен,
надо же к чему то подключать программатор, если использовать не Arduino NANO или MICRO, а Arduino MINI.
Вот так может выглядеть
размер 70х25 мм, слева разъем для подключения USB-UART, к нему же будет подключен программатор,
справа разъем для кнопки (2-х контактный) и (3-х контактный) для регулятора, с регулятора будет питание.
Загрузил, картинка есть кнопочки клацают. Немного интуитивно не понятна навигация по меню.
ИМХО надо так:
перемещение по пунктам меню “up/down”,
вход в настройку параметра “select”
изменение параметра “up/down”, выбор позиции курсора “left/right”
выход из настройки “select”.
Ждём для теста схему и ПО для связки таймер - программатор. Использование для настройки RS232 имеет место быть, благо у “мини” он уже выведен на край платы.
Разъём для регулятора то же надо штырями выводить.
это понятно, с точки зрения EasyEDA и травления
на плате все равно три отверстия ) с шагом 1,25
ИМХО надо так:
пока реализовано одноуровневое меню, с многоуровневым свои трудности
Думаю к выходным закончу обмен данными между программатором и таймером,
на данный момент работает чтение данных из таймера в программатор по UART,
пришлось повозится с протоколом обмена, что бы не зависало при потере данных или сбое передачи/приема.
Таймаут вылечил )), если дошло дело до таймаута, процесс тупо начинается с начала.
как же все сложно у Мег при работе с железным UART, да и в реализации на Ардуино,
на STM32 настроил прерывание на прием байта и “кури бамбук”, пока оно не придет.
а здесь надо в цикле проверять “а есть ли данные в буфере”
и не дай бог попробуешь прочитать Serial.read () больше чем есть в буфере,
как можно прочитать то, чего нет, что за хрень такая, нет данных верните ошибку.
а самое паршивое, что всю эту хрень надо обязательно предусмотреть.
В общем, сегодня буду упрощать алгоритм приема/передачи,
передача запроса и тупое ожидание пакета от таймера в течении 0,2 секунды,
если пакет не пришел или пришел не весь, тогда все с начала,
не уверен, что стоит городить огород с контрольной суммой, возможно позже.
А пока ограничусь контролем четности по байтам.
Главное переменной “w0dka” не придавать значение больше 500 😃
не, “w0dka” она не просто константа, так еще и глобальная.
а по сути,
упростил чтение данных с таймера по самое не хочу, проверку точности чтения не делал, положился на контрольные суммы байтов,
да и не надо оно по сути своей на приеме, при листании и корректировке все автоматом вылезет.
опять же проверить точность приема пакета таймером можно прочитав повторно после отправки и сверить,
руками или автоматически это уже второй вопрос ))
во второй половине дня приеду с работы, “дорисую” обратный обмен, “рыба” уже есть и она вполне съедобная
и думаю к вечеру интегрирую обмен в программу таймера.
Пока на втором конце Ардуино Нано читает команды и отправляет данные по запросу.
команд осталось три,
- 0x55 установить соединение, ответ таймера 0x55, готов к обмену
- 0x5A запрос пакета с таймера, ждем пакет от таймера 0,2 секунды
- 0xA5 передача пакета таймеру, отправляем пакет и ждем ответ 0xA5.
5 и A потому как чередование 0101 1010, а это самое удобное для синхронизации ))
Работоспособность самого таймера не проверял,
пока, ВРОДЕ КАК, полностью реализован обмен.
Данные в таймере будут сохраняться в EEPROM, сейчас при старте в EEPROM будут загружены стоковые данные,
для обмена будут использоваться данные из EEPROM, но пока сохранение того что вернул программатор не допилил, там пару команд добавить, решил пока не ломать то, что работает
как оно должно работать, проверяйте.
- если при включении программатора таймер не включен, на экране сообщение - “not connection”
- после подключения таймера, программатор переходит в режим - “Read”
- кнопкой SELECT считываем данные с таймера, сообщение - “Complete”
- после корректировки переходим в меню - “Write”
- соответствие принятого отправленному проверяется в программаторе автоматически, повторным чтением.
- если запись прошла успешно, сообщение - “Coomplete”
- если при записи выявлена ошибка, сообщение - “ERROR”, можно еще раз попробовать записать )
Будет добавлено в программу, при успешной проверке отправленных данных, программатор передаст команду на запись в EEPROM,
этот кусок еще не реализован, пока надо все остальное проверить.
ближе к вечеру сооружу стендик и буду тестить.
В плане, добавить переменные
- GYRO on - если используется гироскоп, теоретически режим с гироскопом сейчас должен работать, если в этой версии я ничего не сломал.
- RPM sensor - если подключен датчик оборотов, на будущее
и под них будем делать софт )
Ещё бы схему электрическую подключений… к каким ногам какие проводки подключать.
GND-GND - на LCD это две гоги между 5V и VСС на MINI разъем UART (снизу) подписано GND
RX-TX и TX-RX, на мини подписаны, на LCD RX-правый крайний, TX-рядом с ним
Если вдруг случка не произойдет, можно RT/TX на одной из плат поменять местами
После этого Reset на таймере обязателен, Таймер будет ждать соединения не сильно долго, сейчас стоит 10 секунд, после подключения питания,
если за 10 секунд соединение не произошло, таймер выходит из режима программирования и отправляется в нормальный режим.
Собрал стенд, с записью какой то косяк, записывает через раз.
Будем искать ((