//Тестирование джойстика KY-023 и сервопривода SG90 //Управление сервоприводом джойстиком и кнопками //задействован только один канал Х //Настройка параметров : //ограничение сектора, ограничение с краев сектора //поправка джойстика, направление движения серво //сохранение настроек в память контроллера //по кнопке на джойстике можно отключить и включить все ограничения //на LCD выводятся подсказки : //Sec - есть ограничение по сектору //Edg - есть ограничение справа или слева // ! - введена поправка джойстика //CW - серво по часовой при движении джойстика слева на право (синхронно) //CCW - серво против часовой при движении джойстика слева на право (асинхронно) #include <EEPROM.h> //библиотека работы с памятью #include <LiquidCrystal_I2C.h> //библиотеки работы с LCD через I2C #include <Wire.h> #include <Servo.h> //библиотека работы с сервоприводом //Не забудь шунтировать выход от кнопок резистором 10 КОм const byte BtModePin = 3 ; //пин кнопки режимов управления const byte BtExitPin = 4 ; //пин кнопки выход const byte BtDownPin = 5 ; //пин кнопки уменьшить const byte BtUpPin = 6 ; //пин кнопки увеличить const byte BtSettingPin = 7 ; //пин кнопки настроек const byte JoystickXPin = A1; //пин аналогового входа с джойстика const byte ServoPin = 8; //пин управления сервоприводом const byte BtJoystickPin = 9; //пин кнопки джойстика //при нажатии кнопки джойстика пин джойстика замыкается на землю, //поэтому этот пин нужно подтягивать к питанию (на всех цифровых пинах //Arduino UNO есть встроенные подтягивающие к питанию резисторы) //чтобы их задействовать пишем в секции setup : pinMode (BtJoystickPin, INPUT_PULLUP); //при этом кнопка будет работать в инвертируемом режиме : //при нажатии - сигнал LOW, при отпускании - HIGH //питание сервопривода только от шилда (1А) //пины модуля I2C LCD-дисплея : //SDA - A4 //SCL - A5 //питание от шилда int Init_Eeprom_Addr = 1000; //номер ячейки в памяти EEPROM (0-1023), куда мы запишем ключ о //самом первом запуске программы //это нужно, так как при первом запуске в памяти нет наших настроек //и нужно загрузить значения по умолчанию byte Init_Key = 250; //это ключ (0-255). //Если при пуске скетча данные из ячейки Init_Eeprom не совпадают //с Init_Key, вводим в настройки значения по умолчанию, //иначе берем настройки из памяти Servo myservo; //создаем свой объект серво boolean LastButtonSetting = LOW; //предыдущее состояние кнопки Настройки boolean CurrentButtonSetting = LOW; //текущее состояние кнопки Настройки boolean LastButtonDown = LOW; //--- Меньше boolean CurrentButtonDown = LOW; //--- Меньше boolean LastButtonUp = LOW; //--- Больше boolean CurrentButtonUp = LOW; //--- Больше boolean LastButtonMode = LOW; //--- Режим : джойстик - кнопки boolean CurrentButtonMode = LOW; //--- Режим : джойстик - кнопки boolean LastButtonExit = LOW; //--- Выход boolean CurrentButtonExit = LOW; //--- Выход boolean LastButtonJoystick = LOW; //--- кнопки на джойстике boolean CurrentButtonJoystick = LOW; //--- кнопки на джойстике boolean FlagSettingLoop = true; //были ли мы в цикле нажатия кнопки Настройки boolean FlagUpLoop = true; // ------- Больше boolean FlagDownLoop = true; // ------- Меньше boolean FlagModeLoop = true; // ------- Режим : джойстик - кнопки boolean FlagExitLoop = true; // ------- Выход boolean FlagJoystickLoop = true; //были ли мы в цикле нажатия кнопки на джойстике //нужна при нажатии и удерживании кнопки ВВЕРХ unsigned long ButtonHoldTimerUp = 0; //нужна при нажатии и удерживании кнопки ВНИЗ unsigned long ButtonHoldTimerDown = 0; //по прошествии этого времени кнопка ВВЕРХ или ВНИЗ //перейдет в режим удержания, мс const long ButtonHoldIncTime = 500; boolean FlagUpHold = true; //были ли мы в цикле удержания кнопки ВВЕРХ boolean FlagDownHold = true; //были ли мы в цикле удержания кнопки ВНИЗ boolean FlagSettingIn = false; //находимся ли в настройках int SettingNumber = 0; //пункт меню настройки int ServoPosition = 90; //положение сервопривода (0...180) boolean ControlMode = true; //режим управления серво : //true - джойстик, false - кнопки int SectorLimit = 180; //ограничение сектора разворота серво (макс 180, мин 10) //Если потенциометр обратной связи врет, и серво пытается уйти за 0 или за 180, //что может привести к поломке, можно ограничить движение слева и справа int EdgeCCW = 0; //ограничение слева. int EdgeCW = 0; //ограничение справа int JoystickCorrection = 0; //корректировка положения джойстика boolean Direction = true; //направление вращения : //true - по часовой, false - против //включить (true) или выключить ограничения по кнопке на джойстике boolean FlagJoystickButton = true; LiquidCrystal_I2C lcd(0x27,16,2); //Задаем адрес и размерность дисплея. byte degree[8] = { //рисуем пользовательский символ градуса B00110, B01001, B01001, B00110, B00000, B00000, B00000, B00000, }; void setup() //**************************************************** { pinMode (BtModePin, INPUT); //прописываем режим пинов pinMode (BtExitPin, INPUT); pinMode (BtDownPin, INPUT); pinMode (BtUpPin, INPUT); pinMode (BtSettingPin, INPUT); pinMode (JoystickXPin, INPUT); pinMode (BtJoystickPin, INPUT_PULLUP); //пин джойстика с подтяжкой к питанию myservo.attach(ServoPin); //прописываем пин для серво //если запись в настройки когда-то была, то читаем их if (EEPROM.read(Init_Eeprom_Addr) == Init_Key) { EEPROM.get(0,ServoPosition); //для типа int нужны 2 ячейки памяти EEPROM.get(2,ControlMode); EEPROM.get(3,SectorLimit); EEPROM.get(6,EdgeCCW); EEPROM.get(8,EdgeCW); EEPROM.get(10,Direction); EEPROM.get(12,JoystickCorrection); } else //если записи не было, записываем в память значения по умолчанию { EEPROM.put(0,ServoPosition); EEPROM.put(2,ControlMode); EEPROM.put(3,SectorLimit); EEPROM.put(6,EdgeCCW); EEPROM.put(8,EdgeCW); EEPROM.put(10,Direction); EEPROM.put(12,JoystickCorrection); //и ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); } lcd.init(); //Инициализация lcd lcd.backlight(); //Включаем подсветку lcd.setCursor(0,0); //рисуем первую строчку lcd.print(" SERVO-JOYSTICK "); lcd.setCursor(0,1); //рисуем вторую строчку lcd.print(" TEST "); delay(1500); //Пауза в мс. lcd.createChar(0, degree); //прописываем пользовательский символ градуса } void loop() //********************************************************* { //------------------ отображаем инфо на LCD ---------------------- if (FlagSettingIn == false) //если мы не в настройках { lcd.setCursor(0,0); //отображаем уставку в верхней строке lcd.print("Pos="); lcd.print(180-ServoPosition); lcd.write((byte)0); lcd.print(" "); //если ограничения не отключены кнопкой на джойстике if (FlagJoystickButton == true) { if (SectorLimit < 180) {lcd.print("Sec ");} //есть ограничение сектора //есть ограничение слева или справа if (EdgeCCW > 0 || EdgeCW > 0) {lcd.print("Edg");} } lcd.print(" "); lcd.setCursor(0,1); //отображаем режим управления в нижней строке строке if (ControlMode == true) //джойстик { lcd.print("Joystick"); if (Direction == true) { lcd.print(" CW"); } else { lcd.print(" CCW"); } //если есть корректировка положения джойстика if (FlagJoystickButton == true) { if (JoystickCorrection != 0) { lcd.print(" ! "); } else { lcd.print(" "); } } else { lcd.print(" OFF "); } } else //кнопки { if (FlagJoystickButton == true) { lcd.print("Buttons "); } else { lcd.print("Buttons OFF "); } } //---------------- задаем ограничения по сектору --------------- int i = 0; //левый край int j = 180; //правый край //если ограничения не отключены кнопкой на джойстике if (FlagJoystickButton == true) { i = int((180 - SectorLimit) / 2); j = 180 - int((180 - SectorLimit) / 2); } //--------------- определяем положение серво ------------------- if (ControlMode == true) //джойстик { if (Direction == true) //по часовой { ServoPosition = map(analogRead(JoystickXPin), 1023, 0, i, j); } else //против часовой { ServoPosition = map(analogRead(JoystickXPin), 0, 1023, i, j); } //если ограничения не отключены кнопкой на джойстике if (FlagJoystickButton == true) { ServoPosition = ServoPosition - JoystickCorrection; //добавляем корректировку } } else //кнопки { //ограничиваем сектор if (ServoPosition < i) {ServoPosition = i;} if (ServoPosition > j) {ServoPosition = j;} } //если ограничения не отключены кнопкой на джойстике if (FlagJoystickButton == true) { //ограничиваем движение серво справа и слева //так как в библиотеке Servo.h отсчет градусов у серво идет против часовой, //то лево - это CW, а право - CCW //вводим ограничение слева if (ServoPosition < EdgeCW) {ServoPosition = EdgeCW;} //вводим ограничение справа if (ServoPosition > 180 - EdgeCCW) {ServoPosition = 180 - EdgeCCW;} } //------------------------ движение серво -------------------------- myservo.write(ServoPosition); } //if (FlagSettingIn == false) //если мы не в настройках //--------------------- Нажата кнопка РЕЖИМ ------------------------ ModeBtFunction(); //--------------------- Нажата кнопка EXIT ------------------------- ExitBtFunction(); //------------------ Нажата кнопка на дойстике --------------------- JoystickBtFunction(); //-------------------- Нажата кнопка Настройки --------------------- //считываем состояние кнопки с устраненным дребезгом CurrentButtonSetting = debounce(LastButtonSetting, BtSettingPin); if(CurrentButtonSetting == HIGH) //кнопка нажата { if (FlagSettingLoop == true) //эта переменная нужна для однократного нажатия кнопки, //даже если ее удерживать { FlagSettingLoop = false; FlagSettingIn = true; SettingNumber = SettingNumber + 1; if (SettingNumber > 7) { SettingNumber = 1; } } } else //кнопка отжата { FlagSettingLoop = true; } //обновляем текущее значение кнопки LastButtonSetting = CurrentButtonSetting; //-------------------- Кнопки Увеличить и Уменьшить --------------------- if (FlagSettingIn == true) //если мы в настройках { //то кнопками Увеличить и Уменьшить меняем параметры ButtonPlus(); //функция реакции на нажатие кнопки Увеличить ButtonMinus(); //функция реакции на нажатие кнопки Уменьшить SettingsOnLCD(SettingNumber); //отображаем строку с настройками } else { if (ControlMode == false) //управление кнопками { //если мы не в настройках, то кнопками Увеличить и Уменьшить //задаем положение серво int sn = SettingNumber; //запоминаем последний пункт настроек SettingNumber = 15; ButtonPlus(); ButtonMinus(); SettingNumber = sn; //восстанавливаем пункт настроек } } } //void loop() //********************** ФУНКЦИИ ********************************* //*********** Функция отображения настроек на LCD ************* void SettingsOnLCD(int SettingItem) { lcd.setCursor(0,0); switch (SettingItem) { case 1: //ограничение сектора разворота серво (макс 180) lcd.print("Sector Limit : "); lcd.setCursor(0,1); lcd.print(SectorLimit); lcd.write((byte)0); lcd.print(" "); break; case 2: //ограничение слева lcd.print("Edge to CCW "); lcd.setCursor(0,1); lcd.print(EdgeCCW); lcd.write((byte)0); lcd.print(" "); break; case 3: //ограничение справа lcd.print("Edge to CW "); lcd.setCursor(0,1); lcd.print(EdgeCW); lcd.write((byte)0); lcd.print(" "); break; case 4: //корректировка положения джойстика lcd.print("Joystick Correc. "); lcd.setCursor(0,1); lcd.print(JoystickCorrection); lcd.write((byte)0); lcd.print(" "); break; case 5: //чтение настроек из EEPROM lcd.print("Read Settings "); lcd.setCursor(0,1); lcd.print("Press UP button "); break; case 6: //запись настроек в EEPROM lcd.print("Save Settings "); lcd.setCursor(0,1); lcd.print("Press UP button "); break; case 7: //загрузка значений по умолчанию lcd.print("Load Default "); lcd.setCursor(0,1); lcd.print("Press UP button "); break; } } //********* Функция кнопки Больше, Увеличить, Плюс и т.п. ************* void ButtonPlus() { CurrentButtonUp = debounce(LastButtonUp, BtUpPin); if(CurrentButtonUp == HIGH) //кнопка нажата { if (SettingNumber == 1 || SettingNumber == 2 || SettingNumber == 3 || SettingNumber == 4 || SettingNumber == 15) //пункты настроек, где нужен разгон параметра в режиме удержания { if(FlagUpHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerUp = millis(); FlagUpHold = false; } if(millis() - ButtonHoldTimerUp > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonPlusAction(SettingNumber, true); } if(FlagUpLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } else //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagUpLoop == true) { FlagUpLoop = false; ButtonPlusAction(SettingNumber, false); } } } else //кнопка отжата { FlagUpLoop = true; FlagUpHold = true; } LastButtonUp = CurrentButtonUp; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Больше ********** void ButtonPlusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //ограничение сектора разворота серво (макс 180) SectorLimit = SectorLimit + Inc_Val_Calc(IncTrigger); if(SectorLimit > 180) { SectorLimit = 180; } break; case 2: //ограничение слева EdgeCCW = EdgeCCW + 1; if(EdgeCCW > 80) {EdgeCCW = 80;} break; case 3: //ограничение справа EdgeCW = EdgeCW + 1; if(EdgeCW > 80) {EdgeCW = 80;} break; case 4: //корректировка положения джойстика JoystickCorrection = JoystickCorrection + 1; if(JoystickCorrection > 45) {JoystickCorrection = 45;} break; case 5: //чтение настроек из EEPROM EEPROM.get(0,ServoPosition); //для типа int нужны 2 ячейки памяти EEPROM.get(2,ControlMode); EEPROM.get(3,SectorLimit); EEPROM.get(6,EdgeCCW); EEPROM.get(8,EdgeCW); EEPROM.get(10,Direction); EEPROM.get(12,JoystickCorrection); lcd.setCursor(0,1); lcd.print("Reading... OK "); delay(1500); break; case 6: //запись настроек в EEPROM EEPROM.put(0,ServoPosition); EEPROM.put(2,ControlMode); EEPROM.put(3,SectorLimit); EEPROM.put(6,EdgeCCW); EEPROM.put(8,EdgeCW); EEPROM.put(10,Direction); EEPROM.put(12,JoystickCorrection); //ставим метку, что запись в настройки произведена EEPROM.put(Init_Eeprom_Addr,Init_Key); lcd.setCursor(0,1); lcd.print("Saving... OK "); delay(1500); break; case 7: //загрузка значений по умолчанию ServoPosition = 90; ControlMode = true; SectorLimit = 180; EdgeCCW = 0; EdgeCW = 0; Direction = true; JoystickCorrection = 0; lcd.setCursor(0,1); lcd.print("Loading... OK "); delay(1500); break; case 15: //положение серво в градусах ServoPosition = ServoPosition - Inc_Val_Calc(IncTrigger); if(ServoPosition < 0) { ServoPosition = 0; } break; } } //********* Функция кнопки Меньше, Уменьшить, Минус или т.п. ************* void ButtonMinus() { CurrentButtonDown = debounce(LastButtonDown, BtDownPin); if(CurrentButtonDown == HIGH) //кнопка нажата { if (SettingNumber == 1 || SettingNumber == 2 || SettingNumber == 3 || SettingNumber == 4 || SettingNumber == 15) //пункты настроек, где нужен разгон параметра в режиме удержания { if(FlagDownHold == true) //отсечка времени удержания кнопки { ButtonHoldTimerDown = millis(); FlagDownHold = false; } if(millis() - ButtonHoldTimerDown > ButtonHoldIncTime) //сначала однократное нажатие, но если держать кнопку более //ButtonHoldIncTime, она переходит в режим удержания { ButtonMinusAction(SettingNumber, true); } if(FlagDownLoop == true) //однократное нажатие, даже если удерживать кнопку, //пока не прошло ButtonHoldIncTime { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } else //пункты настроек, где нужно только однократное нажатие кнопки { if(FlagDownLoop == true) { FlagDownLoop = false; ButtonMinusAction(SettingNumber, false); } } } else //кнопка отжата { FlagDownLoop = true; FlagDownHold = true; } LastButtonDown = CurrentButtonDown; //обновляем текущее значение кнопки } //******** Функция изменения параметров при нажатии кнопки Меньше ********** void ButtonMinusAction(int SettingItem, boolean IncTrigger) { switch (SettingItem) { case 1: //ограничение сектора разворота серво (мин 10) SectorLimit = SectorLimit - Inc_Val_Calc(IncTrigger); if(SectorLimit < 10) { SectorLimit = 10; } break; case 2: //ограничение слева EdgeCCW = EdgeCCW - 1; if(EdgeCCW < 0) {EdgeCCW = 0;} break; case 3: //ограничение справа EdgeCW = EdgeCW - 1; if(EdgeCW < 0) {EdgeCW = 0;} break; case 4: //корректировка положения джойстика JoystickCorrection = JoystickCorrection - 1; if(JoystickCorrection < -45) {JoystickCorrection = -45;} break; case 15: //положение серво ServoPosition = ServoPosition + Inc_Val_Calc(IncTrigger); if(ServoPosition > 180) { ServoPosition = 180; } break; } } //*************** Функция устранения дребезга контактов *********** boolean debounce(boolean last, int ButtonPinDebounce) { boolean current = digitalRead(ButtonPinDebounce); //считываем текущее состояние кнопки if(last != current) //если оно иное, чем предыдущее... { delay(50); //ждем 50 мс current = digitalRead(ButtonPinDebounce); //считываем состояние снова } return current; //возвращаем текущее состояние кнопки } //*************** Функция разгона приращения параметра ************ float Inc_Val_Calc(boolean IncTrigger) { static int count_inc = 0; //счетчик итераций if (IncTrigger == true) //пришел сигнал на разгон { count_inc = count_inc + 1; //играя этими числами, вы можете подстраивать разгон параметра под себя if (count_inc > 0 && count_inc <= 30) { return 1; } if (count_inc > 30 && count_inc <= 60) { return 2; } if (count_inc > 60) { return 3; } } else //приращение по умолчанию { count_inc = 0; return 1; } } //*************** выход из настроек ********************** void SetingExit() { //это нужно, чтобы остаться на том же пункте меню настроек SettingNumber = SettingNumber - 1; if(SettingNumber < 0) { SettingNumber = 0; } FlagSettingIn = false; } //***************** функция кнопки РЕЖИМ ******************** void ModeBtFunction() { //считываем состояние кнопки с устраненным дребезгом CurrentButtonMode = debounce(LastButtonMode, BtModePin); if(CurrentButtonMode == HIGH) //кнопка нажата { if (FlagModeLoop == true) //эта переменная нужна для однократного нажатия кнопки, //даже если ее удерживать { FlagModeLoop = false; if (ControlMode == true) { ControlMode = false; } else { ControlMode = true; } } } else //кнопка отжата { FlagModeLoop = true; } //обновляем текущее значение кнопки LastButtonMode = CurrentButtonMode; } //***************** функция кнопки Exit ***************************** void ExitBtFunction() { //считываем состояние кнопки с устраненным дребезгом CurrentButtonExit = debounce(LastButtonExit, BtExitPin); if(CurrentButtonExit == HIGH && FlagExitLoop == true) //если кнопка нажата { if(FlagSettingIn == true) //если мы в настройках, то выходим из настроек { SetingExit(); } else //если не в настройках, то меняем направление вращения { if (Direction == true) { Direction = false; } else { Direction = true; } } FlagExitLoop = false; } if(CurrentButtonExit == LOW) { FlagExitLoop = true; } LastButtonExit = CurrentButtonExit; //обновляем текущее значение кнопки } //***************** функция кнопки на джойстике ***************************** void JoystickBtFunction() { //считываем состояние кнопки с устраненным дребезгом CurrentButtonJoystick = debounce(LastButtonJoystick, BtJoystickPin); //так как кнопка джойстика подтянута к питанию встроенным резистором, //то сотояние LOW - это нажатие кнопки if(CurrentButtonJoystick == LOW && FlagJoystickLoop == true) { if (FlagJoystickButton == true) { FlagJoystickButton = false; } else { FlagJoystickButton = true; } FlagJoystickLoop = false; } if(CurrentButtonJoystick == HIGH) { FlagJoystickLoop = true; } LastButtonJoystick = CurrentButtonJoystick; //обновляем текущее значение кнопки }