БЛОК УПРАВЛЕНИЯ ОБОРОТАМИ МЕДОГОНКИ + LCD Nokia 1202.

Iwan73
Offline
Зарегистрирован: 02.11.2013
БЛОК УПРАВЛЕНИЯ ОБОРОТАМИ МЕДОГОНКИ НА ARDUINO UNO.
  
 Предистория, зачем и почему мне это надо
___________________________________
   Киргизия,горы,с мая по сентябрь сезон пчеловодов.летом днем жара,ночью холода,красота,и лень матушка -      двигатель прогресса...
    Дед (мой тесть) 40 лет занимается пчелами,в ручную маслал медогонку,да и вообще он лучше сам приспособится к условиям чем будет что то менять. Область моих интересов лежала далеко от полосатых мух,да и дед не привлекал меня к мушинным делам на пасеке,только перевозка пчел в горы и назад  домой.
Но в этом году мне пришлось без теории муховодства придти сразу на практику,подменял деда после операции на пасеке практически весь сезон,так как вручную крутить медогонку надоело,на скорую руку сляпал электропривод с управлением на NE555(писк,необходимость постоянного нахождения рядом и прочие неприиятности) от аккамулятора авто. В новом сезоне собираюсь воткнуть солнечные батареи,щелочные аккамуляторы и
да здравствует цивилизация,электрификация воровства меда у пчел(электрические ножи,програмируемый 
регулятор оборотов медогонки),спутниковое ТВ,ноотбучина,интернет(сотовая связь в ущелье присуствует),авто-холодильник. Как в санатории,и НИКТО мозг не выносит пока домой не спустишься с гор. И ВРЕМЯ,которого так не хватает дома,для глубокого изучения языка програмирования и МК.
Можно конечно купить готовый привод с блоком управления,но жаба душит платить 10000 наших денюжков,при 
себестоимости устройства в 1,5 тыс и наличии рук не из жопы....
____________________________________
Принцип откачни меда с рамок :  Полностью залитая рамка с медом весит 4-5.5 кг.
В зависимости от конструкции медогонки бывают радиальные и хордовые,3-4 рамочные и прочие.
Карулель с рамками вращается с определенныи оборотами внутри бака и за счет центробежной силы капли меда вылетают на стенки бака по которым потом стекают на дно.
Обороты нужно ограничивать что бы не допустить разрушения рамки. Маленькие обороты - мед не вылетает из ячеек,большие обороты - грозит поломкой рамки.
____________________________________
Описание циклов откачки меда : цикл полной откачки  рамок состоит из 3 циклов вращения
  1 - предварительная откачка первой стороны секунд 30-90 на скорости 50-60 процентов от максимальной.
  вращение допустим влево 
  2 - откачка второй стороны начиная от малых оборотов и заканчивая максимальными 1,5-3 минуты.
  вращение вправо
  3 - полная откачка (докачка) первой стороны на полных оборотах 1,5 - 2 минуты.
  вращение влево
при желании направление меняется как хочешь сам
___________________________________
Задача: Регулирование и поддерживание количества оборотов на определенный период времени в 3 циклах работы
Подпункты задачи : 1- контроль тока потребления от аккамулятора
                                2- контроль напряжения аккамулятора
                                3- контроль количества оборотов
                                4- контроль времени вращения
                                5- контроль уровня вибрации
                                6- индикация направления вращения
                                7- подача звуковых сигналов
                                8- навиигация меню через энкодер с кнопкой
                                9- кнопки Пуск и Стоп,концевик на крышке 
                               10- сохранение и чтение настроек из еепром
                               11- меню на дисплее Nokia 1202
         Варианты решения задачи по железу и ATMega328:
  Микроконтроллер  ATMega328, в качестве базового берется ARDUINO UNO
                        Дисплей от Nokia 1202
       задача                                     железо                     модуль ARDUINO UNO 
 1 -контроль тока              - датчик тока ACS712 20А       - АЦП
 2 -контроль напряжения  - резисторный делитель         - АЦП
 3 -контроль оборотов       - датчик Холла                         - прерывания
                                         - ШИМ регулятор                     - PWM
 4 -контроль времени        -                                              - таймеры
 5 -контроль вибрации      - датчик вибрации                   - АЦП
 6 -инд.напр вращения     - светодиоды 2 шт.                  - цифровые выводы 
7 -звуковые сигналы         - буззер                                  - цифровой вывод
8 -навигация меню            - энкодер                               - цифровые выводы,прерывания
9 -кнопки                         - тактовые кнопки                   - цифровые выводы,прерывания
10- сохранение и чтение настроек                                   - еепром
11- меню                         - Дисплей от Nokia 1202          - цифровые выводы
 
Более детальное описание задач:
                                          общее
_______________________________________
   Во-первых пуск медогонки должен быть плавный (так как при пуске с помощью реле происходит "удар" 
тока по обмотке электродвигателя само собой дополнительная нагрузка на АКБ а если у тебя на пасеке 
к концу дня АКБ разряжен то бывают моменты отключения привода и как результат работы реле нагрев
 корпуса электродвигателя). 
 Во-вторых считаю оптимальный вариант работы медогонки это три цикла:
    первый - предварительная откачка 30-90 секунд на небольшой скорости, 
    второй - полная откачка второй стороны начиная с малых оборотов и заканчивая быстрыми оборотами
 (чтоб не поломать рамки) 1,5-2 минуты,
    третий - докачка первой стороны на полных оборотах 1,5-3 минуты.
В третьих неплохо чтоб привод мог работатькак на радиальных, хордиальных медогонках
Неплохо чтоб на экранчике отображалось
время оставшегося цикла работы,
температура,
скорость и т.д. 
Датчик вибрации например при превышении установленного уровня вибрации количество
 оборотов медогонки снижалось на 10 -15 процентов. 
Датчик тока при превышении установленной силы тока ускорение снижалось 
 (это необходимо если какие-то проблемы с подшипником, или высокий уровень меда не дает свободно
 вращаться валу тем самым будет снижена нагрузка на АКБ и электродвигатель.
 Неплохо было бы если бы можно было установить датчик температуры на мотор с включением
 принудительного охлаждения. 
 Все перечисленные требования должны регулироваться больше-меньше.
 
      Электропитание устройства и электропривод 
___________________________________________
Общее питание от автомобильногоо аккамулятора 12 вольт но можно и 24
привод - коллекторный двигатель постоянного тока с обмотками возбуждения.
у меня немеческий генератор на 24 вольта,120 ватт,обороты 1180(шильдик на нем информирует)
 куплен на базаре за 350 денюжков.
При подключении в режиме двигателя,на 12 вольт,ток хх был 2.7А,обороты не измерял,нечем было.
После утановки на медогонку,прямой привод на вал карусели,при полнозалитых рамках при подаче 12 вольт напрямую без регулятора потребление тока подскакивало в момент пуска до 20А. а после набора нужной скорости примерно 4А.
 
      контроль тока потребления
______________________________________________
датчик подключаемый и отключаемый в меню программно,задаем уровень потребления тока.
 если, например, медогонка полная, начинает цеплять крыльями,проблемы  механикой, то потребление тока значительно возрастает и система не даст сгореть двигателю - отключит его программно. Ну и зачем нам большие пусковые токи,будем регулировать ШИМ.
 
                                    контроль напряжения аккамулятора
_______________________________________________________
если напряжение слишком низкое - отключение. Надо подзарядить аккамулятор.
Просадка напряжения - решаема. Будем просто мерять в момент останова мотора и в следующий раз замерять после последующего запуска мотора.
 
      контроль оборотов
_______________________________________________________
Функция плавного пуска подключаемая и отключаемая в меню,в программе задать время на плавный пуск.
при пуске частота ШИМ ниже, а потом переключать на более высокую частоту.
Для коллекторного двигателя частоту желательно подбирать под каждый двигатель,обычно 2-3 кГц.
 Но как показывает практика, некоторые дв. туго запускаются с шим на высокой частоте,
 но за то хорошо работают при мощности более 50% (меньше гудят и тп).
В сам же момент запуск дв. я думаю - шим до 500 Гц 50%, потом поднимаю частоту и все гуд.
 
По плавному старту. Если в настройках будет выбран пункт меню "Плавный старт",
 то первоначальный разгон будет проходить при частоте ШИМ 61Гц - это позволит плавно разгонять
 на любых, даже очень старых и не очень качественных моторах. 
 После времени плавного разгона, система автоматически будет переключаться на 
 приблизительно 22КГц - чтобы никакого шума от ШИМа не было.
 
Функция торможения двигателем - подключаемая и отключаемая в меню. При торможении двигателем якорь закорачиваем на массу а на обмотку возбуждения подаем напряжение в противофазе от направления вращения.
Повышеный расход аккамулятора??? Сочинить бы рекурперацию...
 
  По датчику Холла
Униполярный цифровой датчик. ногу Output нужно подключить к D2 или к D3, т.к. они поддерживают внешние прерывания  (а оно у вас будет генерироваться когда магнит будет проходить под датчиком).
 
 есть два типа датчиков - биполярные и унополярные. 
  Для переключения датчика первого типа нужно как минимум два магнита - один на север, другой на юг.
  Для второго типа достаточно одного магнита.
 Теоретически чем больше магнитов - тем точнее можно регулировать обороты медогонки.
 
 переключение направления вращения через автомобильные реле 12 вольт 30 ампер,которые управляются через
ULN2008. смена полярности обмоток возбуждения и смена полярности якоря. 
 
  Датчик вибрации
______________________________________________________
датчик подключаемый и отключаемый в меню программно,задаем уровень вибрации. при откачке капли меда вылетев из ячеек бьются  о стенки бака,сначала крупные капли,потом меньше,шум как от дождика. нужна регулировка чуствительности,желательно программно. при превышении установленного уровня вибрации количество оборотов медогонки снижалось бы на 10 -15 процентов. 
либо после срабатывания датч. вибр. медогонка начинает крутить на минимальных оборотах постепенно их увеличивая до нормальных под контролем датчика вибрации. 
 Если же вибрация не исчезает через какое то время медогонка останавливается а на дисплее загорается соответствующая запись. Пчеловод может спокойно заниматься  другими делами при этом не боясь , что медогонка ускачет куда нибудь, далее он отключает датчик и уже под наблюдением сам добавляя обороты откачивает эти рамки,или же просто меняет или переставляет их местами и включает снова и т. д.
 
индикация направления вращения, режим СТОП, режим ПАУЗА,режим АВАРИЯ
_____________________________________________________________
При левом направлении вращении    - горит левый светодиод
При правом направлении вращения   - горит правый
Оба светодиода потушены- режим остановки - режим СТОП
Оба светодиода горят одновременно        - режим ПАУЗА
Оба светодиода моргают одновременно      - режим аварийной остановки
                      
 звуковые сигналы
___________________________________________________________
 Буззер подает сигналы при нажатии кнопок,при переключении энкодера,сохранении в еепром,при аварии.
 
кнопки, энкодер
___________________________________________________________
Кнопка ПУСК - запуск программы откачки
Кнопка СТОП - остановка вращения
Тактовые кнопки продублировать,сделать разьем для подключения кнопок большого размера,типа как на станках грибки.
Руки в меде и маленькие кнопки начнут со временем заликать в корпусе. Либо предусмотреть замену тактовых кнопок на использование сенсора от мобилы,нарисовал под ним надписи и попав в зону надписи отработка функций кнопок. И по моему не страшно что  заляпался сенсор,протер после работы и все????? 
 
Концевик на крышке - Геркон и магнит (не боится загрязнений)- Пауза в программе пока не закроем крышку. ( во время выполнения программы вдруг понадобилось заглянуть внутрь- рамку поправить,уровень меда посмотреть ну мало ли,и как защита от дурака не запустится вращение пока крышка открыта)
либо продублировать им кнопк пуска Загрузил рамки в медогонку опустил крышку цикл работы начался, 
 поднял крышку медогонка отключилась. и даже до пульта дотрагиваться не нужно т. к. руки в меде.
        Энкодер с кнопкой - навигация в меню,изменение настроек,сохранение настроек в еепром по длительному удержанию нажатой кнопки.
В процессе исполнения основной программы откачки предусмотреть оперативное изменение оборотов,времени,
 с запросом сохранения новой настройки в еепром
 
меню 
______________________________
1. (1_Экран)
1. ЗАПУСК ПРОГРАММЫ
2. НАСТРОЙКА
3. ИНФОРМАЦИЯ
4 НАЗАД
1.0 (ЗАПУСК ПРОГРАММЫ)
   1.1 цикл N
   1.1.0 направление вращения
   1.1.1 обороты
   1.1.2 оставшееся время вращения
   1.1.3 напряжение аккамулятора
   1.1.4 ток потребления
   1.1.5 температура ??? а надо ли ?
     2.1 (2_Зкран )
        2.0 НАСТРОЙКА
        2.1 ОБОРОТЫ
           2.1.1 цикл 1
           2.1.2 цикл 2
           2.1.3 цикл 3
        2.2 ВРЕМЯ ВРАЩЕНИЯ
           2.2.1 цикл 1
           2.2.2 цикл 2
           2.2.3 цикл 3
       2.3 ПЛАВНЫЙ ПУСК
          2.3.1 ВКЛ
          2.3.2 ВЫКЛ
       2.4 ТОРМОЖЕНИЕ ДВИГАТЕЛЕМ
          2.4.1 ВКЛ
          2.4.2 ВЫКЛ
       2.5 ДАТЧИК ТОКА
          2.5.1 ВЫКЛ 
          2.5.2 ВКЛ
          2.5.2.1 УСТАНОВКА ТОКА
       2.6 ДАТЧИК ВИБРАЦИИ
          2.6.1 ВЫКЛ 
          2.6.2 ВКЛ
          2.6.2.1 УСТАНОВКА УРОВНЯ
       2.7 ДАТЧИК ХОЛЛА
          2.7.1 УНИПОЛЯРНЫЙ 
          2.7.2 БИПОЛЯРНЫЙ
          2.7.3 УСТАНОВКА КОЛИЧЕСТВА МАГНИТОВ
       2.8 НАЗАД
 
     3.0 ИНФОРМАЦИЯ (3_Зкран )
      инфа по подключению датчиков какие есть
      времени вращения по циклам и общему
      обороты по циклам.
      версия прошивки.
 
PS: можно сделать автоматическую регулировку оборотов в зависимости от темперратуры воздуха.таблица зависимости текучести меда от температуры есть. но сомневаюсь в необходимости такой автоматизации,каждый пчеловод по своему откачивает и лучше один раз настроить программу со знанием  всех ньюансов своей медогонки и своих желаний и оперативно изменять настройки,чем довериться  автоматизации во всем. Бывают рамки с располдом и медом,так те вообще очень аккуратно надо крутить.
   Лично я против такого подхода,да и откачка у нас в регионе идет при температуре летом до 42-45 градусов.
Можно еще и на тензодатчики установить медогонку и подсчитывать вес откаченного меда но это уже излишества.
////////////////////////////////////////////
***************************
Программная реализация
______________________________
1 -контроль тока  - считываем данные с датчика в АЦП и преобразуем их в понятные нам Амперы для вывода на дисплей
2 -контроль напряжения -  через резисторный делитель из 12 вольт полуачем 5, считываем с аналогово вывода в АЦП  и сравнивая с максим. и минимальным напряжением на дисплей выводим изображ. батарейки с уровнем заполнения (как на мобиле)
3 -контроль вибрации - считываем с аналогово вывода в АЦП либо используем компоратор?????
4 -инд.напр вращения - цифровые вводы выводы
5 - звуковые сигналы - функция tone(), notone()
6 - кнопки - "титановый велосипед для кнопки"
7- энкодер - библиотека  с прерываниями
8- сохранение чтение- библиотека EEPROM
9 - контроль времени - milis(). Timer
10- контроль оборотов - счетчик на прерываниях
11- Шим - генерация шим на низкой частоте при плавном запуске. смена частоты шим по достижении определенных  оборотов или истечении времени разгона.
12- прерывания на все пины порта библиотека
 
менюшка уже есть но она пока на кнопочках,переделываю для энкодера.
 
Приглашаю желающих присоединиться к разработке подобного устройства,но есть одно условие,оставить проект открытым не превращая в коммерческое изделие.  Информацию по подобным устройствам искал в интернете,много взято оттуда,практически все проекты ближе к стадии завершения становились комерческими, что лично мне не нравится, как советов просить так все белые и пушистые,а потом ....
 
Продолжение следует...
Iwan73
Offline
Зарегистрирован: 02.11.2013

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

если нет опыта то стоит дисплей заменить на 1602 или 2004. так как много чего еще, хотя бы для начала стоило бы рассмотреть такие варианты

для начала стоит максимально упростить задачу, так как походу дела будет лучше видно что стоит еще сделать, а что неважно

да и каждую железку надо сначала отдельно протестировать, чтобы не искать ошибку в большом количестве кода

 

Iwan73
Offline
Зарегистрирован: 02.11.2013

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

из неопробованого- ШИМ на нестандартной частоте (отличной по умолчанию),работа с энкодером,датчик вибрации.

Iwan73
Offline
Зарегистрирован: 02.11.2013

В принципе само устройство с кнопочками для навигации в меню уже есть, вытравил платку,спаял,отлаживал меню. Но все же решил переделать под энкодер,удобнее в управлении будет,крутилка она и в африке интуитивно крутилка. Взять моего деда так он регулятор на NE555 враз освоил,покрутил резюк ,глянул на амперметре ток, на глазок обороты,нарисовал на корпусе  и на ручке резюка точки и все,писк только напрягал.

Схема в протеусе под кнопочное управление нарисована есть,даже симуляция работает,но в связи с поиском баланса между "умными и красивыми" сейчас переделываю . управление релюхами и бузером через ULN2008, через нее же шим- управляем полевичком IRF 3205 -  на якорь.

Iwan73
Offline
Зарегистрирован: 02.11.2013

Iwan73
Offline
Зарегистрирован: 02.11.2013

что то как то не так вставилась,да и фиг с ней,все равно изменения идут

Iwan73
Offline
Зарегистрирован: 02.11.2013

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

ну тогда другое дело. схема тяжеловато воспринимается

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

ULN2008 в качестве драйвера для полевика не лучший выбор

 

 

Iwan73
Offline
Зарегистрирован: 02.11.2013

да это какой то из промежуточных файлов на этом компе остался,походу переделывал все кнопки на один аналоговый вывод,честно уже и не помню,завтра переработаю схему в нормальный вариант. А еханый бабай,протеус 8 надо снести,назад к 7 откатиться.. В основном на "работе" все разработки веду ,в отсутствии работы занимаюсь своим хобби, дома сын компом рулит,куда все ныкает я без него и не найду пожалуй... Разделение труда...Принципиалка в голове,печатку под энкодер набросал но встрял с датчиком вибрации,куда разводить выводы,то ли на АЦП то ли на компоратор ? офигенная чуствительность на нем и не нужна,так как удары капель меда по стенкам бака надо изначально отсекать как уровень шумов.

 На счет ULN как драйвер полевика- есть вариант установить NE555 для этих целей,схемку включения где то на форум скидывал, нашел http://arduino.ru/forum/apparatnye-voprosy/upravlenie-ne555-optoparoi. Лепим из того что доступно и под рукой. Так то и Регулятор частоты вращения DC моторов 60А в продаже есть,но сцуко дорого, ну есть же варианты..

вот адрес самого близкого магазина (60 км) и его содержимое http://geek.kg/, потому и лепим из старого хлама накопленного годами

romanikq
Offline
Зарегистрирован: 17.12.2015

Iwan73   на сайте  http://www.chipmaker.ru/ проскакивала тема про управление оборотами двигателя  на UNO

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

Вы на самом деле смогли впихнуть такое емкое меню в Уно?

Iwan73
Offline
Зарегистрирован: 02.11.2013

romanikq спасибо ,посмотрю.

Gippopotam меню еще не полностью,нескольких пунктов не хватает но на данный момент скетч с меню и основным циклом откачки и еще кое какими функциями занимает почти 16к. Это так наброски ,без оптимизации и много чего по колхозному,я не профи,уровень чайник. за основу меню брал https://rln.nnov.ru/index.php?ind=reviews&op=entry_view&iden=211# и адаптировал как смог на дисплейчик 1202 используя бибу от Gres

Iwan73
Offline
Зарегистрирован: 02.11.2013

http://arduino.ru/forum/programmirovanie/biblioteka-dlya-lcd-nokia-1100?page=6  #304 есть наброски меню и еще кое чего.

а как видео работы в меню прикрепить сюда?

сейчас подумал,поскольку на энкодер хочу переделать,то графические функции в библиотеке дисплея мне  как бы и не нужны будут,в предыдущем варианте рисовалось изибражение панельки с кнопками,если только линии использовать для отделения заголовка меню от списка подпунктов, и использовать бибу от jeka_tm , в прошлом варианте как раз из-за медленной отрисовки изо брал от Gres что бы за раз выводить все на дисплей. В общем пробовать буду... 

romanikq
Offline
Зарегистрирован: 17.12.2015
bankir_1986
Offline
Зарегистрирован: 23.03.2015

Вопрос по железу (конкретно по медогонке) и циклам откачки:

Какая у Вас медогонка хордиальная или радиальная?

Если Хордиальная то6 Какие кассеты оборотные или нет? Или кассеты полностью автоматические?

хордиальная оборотные кассеты:

1 цикл предварительно откачал первую сторону, медогонка остановилась, подошел к ней, перевернул кассеты.

2 цикл по окончанию см 1 цикл.

Если кассеты не оборотные, то тоже самое но рамки вытаскиваем и переворачиваем (тот еще геморой)

Если радиальная, то крутить в разные стороны не имеет никакого смысла.

На мой взгляд (конкретно я так поступаю) нужна новая медогонка с автоматическими кассетами (4, 6 или 8 рамок). Посмотрите Медогонку Грановского, его принципы работы. У него и недорогие приводы имеются.

http://www.bee-prom.ru/

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

Iwan73
Offline
Зарегистрирован: 02.11.2013

медогонка 4 рамочная хордиальная с оборотными кассетами. Нам на 30 ульев дадановских вполне,вторая такая же у деда в затарке на чердаке вообще масловая. с автоматическими кассетами больше диаметром,не влезет вместо этой.

bankir_1986
Offline
Зарегистрирован: 23.03.2015

Я на заре своей пасечной деятельности (пасека расоложена в горах, тайге, нет сотовой связи, но есть много свежего воздуха и куча домов отдыха) много чего хотел автоматизировать (для того чтобы больше времени уходило именно на отдых, а не на работу), но многие самоделки тешили только моё эго, могу это сделать и всё. Твой проект тоже рассматривал, чего проще, нажал на кнопку и радуйся. Остановило одно (точнее одна - ЖЕНА), говорит - попробуй сначала по старинке сделать, а уж потом автоматику прилипи. На 3 3а 3 года занятия пчеловодством, вспомнил её слова. На 4-й стал задумываться. На пятый стал делать.

Так вот к чему все это. ТЗ должно выгледить следующим образом:

Подошел к медогонке, установил в кассеты рамки, регулятором установил небходимые обороты (с функцией корректировки оборотов) и нажал кнопку "лево" или "право". Кнопка "Стоп" осуществляет торможение двигателем. Если "стоп" не нажат через Х минут (если Х мало, тогда через Y минут торможение). Экран нужен на первых, тестовых, запусков. Потом обороты будешь ставить на глаз, в зависимости от темперетуры и влажности окружающей среды и влажности-вязкости меда (каждая закладка может вести себя по разному). В радиальной прощще.

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

ЗЫ: у меня 15 ульев.

Iwan73
Offline
Зарегистрирован: 02.11.2013

Угу, потом,попозже оно мне может быть и не нужно будет здесь, ребенку надо дать перспективу на будущую жизнь, а ее тут нет и не будет,сидим в Киргизии пока дед жив (ему 78),это наш якорь. Вот как отпустит якорь так и будем перебираться в Россию. А сколько ему осталось никто не знает,может год а может и более. Но это так отступление от темы.

 Весь смысл регулятора оборотов медогонки как раз в том и заключается,что сначала,при первом включении используя дисплей(большой и дорогой нафиг не нужен а за 60 денюжков вполне нормально в отличии от 1602 за 255) я введу базовые настройки по циклам,для каждого свои,1 цикл -время ххх обороты-ххх,2цикл -ххх,3-цикл-ххх, ввел параметр для датчиков, пнул кнопку пуск и занялся подготовкой следующей партии рамок. по необходимости тут же подкоректировал количество оборотов. Цикл закончился и она остановилась. и ждет пока не развернешь кассеты и опять не тыркнешь кнопку пуск,пошел второй цикл (вторая сторона рамок),и т.д.  ограничить время работы в каждом цикле нужно потому что , допустим пошел отлить,а придавило отложить личинку,и спокойно этим процессом занимаешься зная что медогонка остановится и будет ждать пока не придешь счастливы и довольный минут через 10-15 (вдруг клапан барахлит)- это так,утрировано-обобщенно. На следующий день опять включил,глянул в инфо сохраненные настройки,надо сразу поправил,нет так в процессе.

у нас влажность сильно не влияет,все лето жара от 30 до 45,дождь за счастье,радуемся если температура стоит 30-32. К середине июля уже выгорает все в предгорьях,только в ущельях сбор и идет на северных склонах . Все склоны обращенные на юг высыхают даже в ущельях. Этим летом вообще дождь был 4 раза с мая по конец августа,и то дождь так себе,пыль прибило только. За сезон качали три раза,температура в будке была 40-42,на улице и до 46 доходило

"В любом случае необходимо менять продолжительность и скорость откачки рамок (особенно для молодых рамок). На мой взгляд лучше крутить ручку переменного резистора чем кнопками выбирать нужное вращение."

Вот потому и нужна оперативная регулировка в процессе и от кнопок отказался в пользу энкодера.

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

рядом с нами 2 пасеки стоят,у мужиков вообще регулировка на таймере от стиральной машинки и переменном резисторе от подсветки щитка приборов жигулей,но там движки шикарные,с древних советских ЭВМ,на напряжение 120 вольт,магнитное возбуждение,питание на якорь,им 12 вольт хватало крутить с нужной скоростью,с полной нагрузкой ток потребления был 1,5-2 ампера. К сожалению таких уже тут не найти при всем желании,достали с РИВЦа и Радиозавода пока еще были такие конторы...

Вот набросок схемы , номиналы пока "от фонаря", геркон и датчик холла в протеусе не нашел и заменил изобр. кнопочками,так же и с энкодероми датчиком вибрации, интересует узел управления шим на полевике .

в последущем на плате готового устройства вместо УНО будет на панельке втавлен ATmega328,печатку под такой вариант и развожу. При необходимости выдернул,вставил в Уно,перепрошил новой версией и назад в плату. 

Iwan73
Offline
Зарегистрирован: 02.11.2013

Наконец-то добрался до самого интересного,силовая часть,датчик холла,датчик тока и датчик биений.

Имеем Arduino Uno, дисплей от нокиа 1202,2 кнопки пуск и стоп, энкодер с кнопкой(при одиночном нажатии вход в меню),датчик холла.

все пока еще на макетке,окончательный вариант печатки пока не делаю так как с силовой частью не разобрался  

Вот выкладываю наработку того что уже работает.

Main_Medogon.Ino

[code]


#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <Encoder.h>
#include <EEPROM.h>
//#include <TimerOne.h>
#include <MsTimer2.h>
#include <class_BUTTON.h>
#include <avr/io.h>
#include <stdio.h>
#include "Font.h"
#include "arrays.h"
#include "TimeHelpers.h"
#include <avr/pgmspace.h> //Храним статические массивы во флеш
#define pgm     pgm_read_byte

//============ Вывода Uno =====================================
#define D_HOLLA       2  //D2 - датчик Холла (тахометр)
//BUTTON KeyPusk     (5); // объявляем объект класса BUTTON кнопку с именем KeyPusk,
#define RELAY_YAKORJ  6  //А4 -  управление реле питания обмотки возбуждения(при переключении меняется полярность на обмотке) 
#define Led_Rotate    7   // - светодиод режима вращения в печатной плате будет 
#define RELAY_WOZBUD  8  //А3 -  управление реле питания обмотки возбуждения(при переключении меняется полярность на обмотке)
#define PWM_Pin       9  //D9 - ШИМ выход управление оптопарой (гальваническая развязка с полевиком) выход ШИМ регулятор поступает через резисторный делитель напряжения
//#define  CS        10
//#define  SDA       11 
//#define  CLK       13 
#define BUZZER       14  //A0 - бузер подает звуковые сигналы при нажатии кнопок в печатной плате будет 
//BUTTON KeyEncoder (15);
//Encoder myEnc     (17, 18);
//BUTTON KeyStop    (19); // объявляем объект класса BUTTON кнопку с именем KeySetup

//=== Подключение дисплея =================================
// CLK - 13 ,SDA - 11 , CS - 10
#define CMD   0
#define DATA  1
#define LCD_X        96
#define LCD_Y        68
#define swap(a, b) { int t = a; a = b; b = t; }
byte LCD_RAM[LCD_X * 9];   // видеобуфер
char Width = 1;            //<>0 - удвоенная ширина символов
boolean color = 1;
unsigned char lcd1100_X = 0, lcd1100_Y = 0;
//==== Подключение энкодера и кнопки ==============================
Encoder myEnc(17, 18);
BUTTON KeyStop(19); // объявляем объект класса BUTTON кнопку с именем KeySetup, подключенную к пину 
BUTTON KeyPusk(5); // объявляем объект класса BUTTON кнопку с именем KeyPusk, подключенную к пину 
BUTTON KeyEncoder(15);
byte butt = 0;                // переменная последней нажатой кнопки
byte lastposition;                 // переменная, хранящая значение энкодера.
byte Pause;
//======= адреса сохранения в ЕЕПРОМ ====================================
#define addr_Save_SensVibro    0
#define addr_Save_SensTok      2
#define addr_Save_RotateZikl_1 4
#define addr_Save_TimeZikl_1   6
#define addr_Save_RotateZikl_2 8
#define addr_Save_TimeZikl_2   10
#define addr_Save_RotateZikl_3 12
#define addr_Save_TimeZikl_3   14
#define addr_Save_ACCELERATED  16
#define addr_Save_BRAKE        18
//=== максималььные значения конфигов =================================================
// у меня устанавливает максимум для тока двигателя в амперах и для датчика вибрации в попугаях
#define MAX_CONF_Vibro 50  // максимальное значение при смене типа конфига вибрации из булева в лимитированный
#define CONF_Vibro     1   // значение при смене типа конфига вибрации из булева в лимитированный
#define MAX_CONF_Tok   30  // максимальное значение при смене типа конфига тока из булева в лимитированный
#define CONF_Tok       1  // значение при смене типа конфига тока из булева в лимитированный
#define MAX_Speed      200 // максимальная скорость медогонки   
#define MAX_POWER      255 // максимальная мощность, выдаваемая на мотор (зависит от разрядности ШИМ,по стандарту 8 разрядный)
#define CONF_LIM_MAX   255 // максимальное значение лимитированного конфига - 1 байт 
//==============================================================================================
#define fSetup            1             //признак нахождения в меню в пункте настроек
#define fSensVibro        2             //признак нахождения в меню в пункте конфига датчик биений
#define fSensTok          3             //признак нахождения в меню в пункте конфига датчик тока
#define fRotate           4             //признак нахождения в меню в пункте конфига обороты 
#define fTime             5             //признак нахождения в меню в пункте конфига время
#define fAccelerated      6             //признак нахождения в меню в пункте конфига плавный пуск  двигателя
#define fBrake            7             //признак нахождения в меню в пункте конфига торможение двигателем
// ******** Данные для применения тормоза ************************************
#define BRAKE_POWER 125      // Мощность, подаваемая на мотор в случае необходимости применения торможения
#define BRAKE_OFF_TURNOVER 10 // Обороты, при которых выключать торможение двигателя
// ******** Константы базовые ******************************************
#define STOP  0  // нет направление вращения
#define LEFT  2 // направление вращения ВЛЕВО
#define RIGHT 1  // направление вращения ВПРАВО
#define STEP_ROTATE  10 // приращение скорости при однократном нажатии на кнопку
// ******** Константы времени ******************************************
#define  BLINK_INTERVAL  _SEC_(1)  // интервал между включение/выключением светодиода (5 секунд)
#define  Speed_INTERVAL  _SEC_(1)  // интервал между подсчетом количества оборотов 
#define ACCEL_TIME          10000  // время плавного разгона двигателя в миллисекундах - 10 сек
#define Speed_CHANGE_DELAY  100    //время применения изменения скорости (для учета инерции)
#define STEP_TIME           1  // приращение времени при однократном нажатии на кнопку на 1 в минутах
#define MAX_TIME_CONF       10  // максимальное время конфига времени в минутах
#define MAX_ROTATE_TIME     600000 //мах. время вращения медогонки в цикле в миллисекундах 10 минут
//========== переменные для скорости вращения ================
byte configSpeed = 0;   // сюда считываем данные из еепрома
volatile byte Speed = 0;         // текущие обороты об/мин  снимаемые от датчика холла
volatile unsigned long impulsSpeed = 0; // переменная подсчета импульсов с датчика
volatile byte  f_isrSpeed =0;            // флаг что были в прерывании
byte ACCELERATED = 0;       // флаг вкючения плавного разгона двигателя если 0 то отключено,1- включено
byte workAccelerated = 0;   // флаг нахождения в цикле плавного разгон
byte BRAKE = 0;             // флаг торможения двигателем 0-отключено,1 включено
byte workBrake = 0;         // флаг нахждения в цикле торможения двигателем
byte rotate_direction = 0; // Текущее направление вращения мотора (изначально нет) - направление вращения, возможны значения LEFT или RIGHT или STOP
byte current_power = 0; // Текущая мощность вращения мотора -ШИМ от 0 до 255
//========== переменные для времени вращения ==============
volatile byte Sec = 60;
volatile byte minut = 0;         // минуты
volatile byte secund = 0;        // секунды
volatile int secundIsr = 0;       //счетчик секунд в прерывании
unsigned long RotateTime = 0;
volatile unsigned long microsold = 0; // время для определения скорости по прерываниям от датчика Хола
float Kp = 0.1;    // коэф. усиления (пропорциональный)
float Ki = 0.05;   // коэф. усиления (интегральный)
int Ti = 100;      // время интегрирования в милисекундах
long millis1 = 0;  // для вычисления интегральной составляющей
int P = 0;
int I = 0;
volatile unsigned long isrRotateTime = 0; ////время в секундах проработаное в цикле вращения
byte ReadConfigTime;//сюда считываем из  еепрома настройки времени
//================================================================
byte Flag = 0;         //нужен для вывода данных о цикле
byte EndZikl = 0;
byte conftype = 0;
byte zikl = 0; //флаг номера цикла
byte lastZikl = 0; //флаг номера цикла
byte VIBRO = 0;//  уровень вибрации
byte TOK = 0;
//======================================================================================
//=== Объявления для меню =======================================
#define T_FOLDER         0 //папка, содержит список файлов
#define T_APP            4 //Приложение
#define T_CONF           5 //Конфиг

#define FILENUMB 19             //общее количество файлов в массив
#define FILEREW  3              // количество байт на каждый файл
#define MAXDEPTH 4              // максимальная глубина внутренного раздела  #define MAXDEPTH 4 

#define DISPSTR 4               // количество строк, используемых для записи меню. // Дисплей может иметь несколько строк!
#define LCDCOL  16             //  Максимальное количество символов в строке
#define LCDBITS (DISPSTR-1)     //  маска курсора 
#define LCDINVBITS ~LCDBITS     //  маска раздела 
#define ROWSHIFT 2              // сдвиг позиции курсора на  2 знакоместо по y

byte level;                  // сохранить текущий уровень узла
volatile byte fileNumb = 0;  // запоминаем номер текущего файла
byte workType = 0;            //используется для блокировки в приложениях. Флаг активности того или иного модуля
volatile byte configLim[4];  //используемые фр строковых параметров и текущего значения файла конфигурации
byte brCrumbs[MAXDEPTH][3];  // массив хлебных крошек. Используется для навигации, особенно с динамическими и выбор файлов.
//двухмерный 1 байт - номер программы, второй - положение курсора
volatile byte snumb = 0;              //  номер текущей  строки
byte lastcurst=0;
byte fStop = 0;
//==== некоторые буферы =================================================
char buffer[LCDCOL - 1]; // объявляем массив размером ((LCDCOL=16 Максимальное количество символов в строке)-1)=15
char buff[LCDCOL - 3]; // объявляем массив размером ((LCDCOL=16 Максимальное количество символов в строке)-3)=13
byte fileData[3]; //используется для хранения информации о текущем файле
char Stringbuffer[LCDCOL - 1]; // объявляем массив размером ((LCDCOL=16 Максимальное количество символов в строке)-1)=15
char buffString[4];

extern const char fileStruct[FILENUMB * FILEREW];
extern const char* const StringNames[];
extern const char* const fileNames[]; 
extern const byte configsLimit[];

//=== обьявление функций ===================================
void dispHead(char head[LCDCOL]);
void fileGet(byte fnumb);//данные о файле. Входной параметр - номер файла СИСТЕМНЫЙ
void pageList();
void fileReturn();
void fileSelect();//вызывается в обработчике кнопок меню при нажатии
void ConfigDispSetup(byte conftype);
void Priwetstwie();
void ConfigReadLimits(byte cnumb);//данные о конфиге. Входной парамтер - номер конфига СИСТЕМНЫЙ
inline byte ConfigRead(unsigned int _param1);
void indikazia_Speed(byte Speed);
void indikacya_Time(byte secund, byte minut);
void timerIsr();
void Init_Lcd();
void configSelect(byte cnumb, byte conftype);
//void SpeedRotate ();
void ConfigDispStatic();
void isrSpeed();
void Accelerated();
void Brake();
void getSpeed();
//******************************************************************************************
//==== сама программа ======================================================================
//*****************************************************************************************
void setup()
{
  Serial.begin (9600);
  //===вывода=====
  pinMode(RELAY_YAKORJ, OUTPUT);    // настраиваем пин управления реле якоря на выход
  digitalWrite(RELAY_YAKORJ, LOW);  // переключаем реле возбуждения якоря
  pinMode(RELAY_WOZBUD, OUTPUT);    // настраиваем пин управления реле возбуждения на выход
  digitalWrite(RELAY_WOZBUD, LOW);  // переключаем реле возбуждения обмотки
  pinMode(PWM_Pin, OUTPUT);         // настраиваем пин ШИМа на выход
  pinMode(BUZZER, OUTPUT);          // настраиваем пин буззера на выход
  pinMode(Led_Rotate, OUTPUT);      // настраиваем пин светодиода вращения на выход
  digitalWrite(Led_Rotate, LOW );   // гасим светодиод вращения
  //устанавливаем значения по умолчанию для скорости и направления вращения
  current_power = 0;              // Текущая скорость вращения мотора - от 0 до 255
  rotate_direction = STOP;          // Текущее направление вращения мотора левое
  setdirspeed(rotate_direction);                 //применяем значения направления вращения и скорости
  //настраиваем энкодер
  lastposition = 0;            // начало отсчёта энкодера.
  myEnc.write(lastposition * 4); // записываем стартовое значение позиции энкодера
  Init_Lcd();       // инициализация дисплея
  Priwetstwie();    //выводим текст приветствия
  delay(3000);
  Clear_LCD();      // очищаем дисплей
  dispStaticDraw();     // Эта функция обрисовывает экран
  fileNumb = 0;
  fileGet(fileNumb); // Получение данных о файле
  pageList();
  Update();       //Обновление экрана, загрузка информации на дисплей
  //==========================
  //  EEPROM.write(addr_Save_SensVibro,0);//для проверки стираем еепром
  //   EEPROM.write(addr_Save_SensTok,0);
  //   EEPROM.write(addr_Save_RotateZikl_1,0);
  //   EEPROM.write(addr_Save_TimeZikl_1,0);
  //   EEPROM.write(addr_Save_RotateZikl_2,0);
  //   EEPROM.write(addr_Save_TimeZikl_2,0);
  //   EEPROM.write(addr_Save_RotateZikl_3,0);
  //   EEPROM.write(addr_Save_TimeZikl_3,0);
  //   EEPROM.write(addr_Save_ACCELERATED,0);
  //  EEPROM.write(addr_Save_BRAKE ,0);
}

void loop()
{
  butt = buttonsStat();
  if (butt)
  {
    Clear_LCD();      // очищаем дисплей
    dispStaticDraw();     // Эта функция обрисовывает экран
    dispHead(buffer);     // функция вывода сообщения в шапку дисплея
    menuButtons(butt);    //опрос кнопок
    Update();       //Обновление экрана, загрузка информации на дисплей
  }
}



[/code]

Buttons.ino

[code]
//===  Общий для всех типов файлов обработчик кнопок. Возвращает номер нажатой кнопки ===
byte buttonsStat()
{
  butt = 0;
  ReadEncoder();            // читаем энкодер
  KeyEncoder.read();        // обновляем состояние переменных кнопки энкодера
  KeyPusk.read();           // обновляем состояние переменных кнопки Пуск.
  KeyStop.read();           // обновляем состояние переменных кнопки Стоп
  if (KeyStop.click_down) { //если нажимали кнопку Стоп
    click_down_KeyStop();   // внутри функции что то делаем
  }
  if (KeyPusk.click_down) { //если нажимали кнопку Пуск
    click_down_KeyPusk();   // внутри функции что то делаем
  }
  if (KeyEncoder.click_down) { //если нажимали кнопку энкодера Setup
    click_down_KeyEncoder();   // внутри функции что то делаем
  }
  if (KeyEncoder.retention) { //если удерживали 2 секунды кнопку энкодера Setup
    retention_KeyEncoder();   // внутри функции что то делаем
  }
  // if (BUTTON_01.click_up) {что-то делаем при отпускании}
  if (butt != 0) {
    // tone (BUZZER , 3000, 50);                     // "Бип" при нажатии кнопки.
  }
  if (butt == 5) {
    // tone (BUZZER , 2500, 100);                     // "Бип" при нажатии кнопки.
  }
  return butt;
}
//==== функция чтения состояния энкодера ===================================================
void ReadEncoder()                // функция чтения состояния энкодера
{
  int newPos = myEnc.read() / 4;  // читаем энкодер
  if (newPos !=  lastposition) {  // если новая позиция не равна старой позиции
    // ----- KeyMinus -----------
    if (newPos < lastposition) {
      butt = 2;                   // если новая позиция меньше старой позиции считаем что нажали кнопку  Минус
    }
    // ----- KeyPlus -----------
    if (newPos > lastposition) {
      butt = 3;                   // если новая позиция больше старой позиции считаем что нажали кнопку  Плюс
    }
    lastposition = newPos;        // присвоим старой позиции значение новой позиции
  }
}
//==== функции присвоения значения кнопки от нажатия =================================
// ----- KeySetup -----------
void click_down_KeyEncoder() {
  butt = 4; //кнопка  Setup
}
void retention_KeyEncoder() {
  butt = 5; //удержание кнопки стоп в течении 1 сек
}
// ----- KeyStop -----------
void click_down_KeyStop() {
  butt = 1; //кнопка  Стоп
}
// ----- KeyPusk -----------
void click_down_KeyPusk() {
  butt = 6; //кнопка  Пуск
}
//=============================================================================
//----- обработчики кнопок меню -------------------------
void menuButtons(uint8_t but)//Обработчик кнопок в меню
{
  switch (but)
  {
    case 1:            //< на пункт назад - кнопка стоп
        fileReturn();//Serial.println("key Stop-fileReturn()");
      break;
    case 2://- и все что нам надо - изменить положение курсора :) - вращение энкодера
      if (brCrumbs[level][1] > 0) brCrumbs[level][1]--;//Serial.println("key Minus-pageList()");
      else brCrumbs[level][1] = fileData[2]; //дошли до начала, начали с конца.
      pageList();//обрисовываем экран только когда это требуется
      break;
    case 3://+ тут то же самое                                    + вращение энкодера
      if (brCrumbs[level][1] < fileData[2]) brCrumbs[level][1]++;// Serial.println("key Plus-pageList()");
      else brCrumbs[level][1] = 0; //не дошли еще до конца? тогда стремимся к нему. Дошли? класс, начинаем сначала
      pageList();
      break;
    case 4:            //> выбор пункта- кнопка энкодера
      fileSelect();      //Serial.println("key encoder-fileSelect()");
      break;
    case 6:          // нажали кнопку Пуск
    {
      if (Pause == 1) {                  // если кнопки нажимались в процессе исполнения цикла
      break;  
      }
      else {
      Programm1();   //запуск программы работы
      }
    }
      break;
    default:
      break;
  }
}
//****************************************************************************
//======================================================================
void ConfigUpButton()
{
  if (configLim[1] == 1)  { //значит что вырианты только да или нет
    configLim[3] = !configLim[3];
    //configLim[3] = 0;
  }
  else { //изменяющееся:
    if (configLim[3] < configLim[1]) {
      if (conftype == fRotate) configLim[3] = configLim[3] + STEP_ROTATE;    // если конфиг оборотов
      else  if  (conftype == fTime) configLim[3] = configLim[3] + STEP_TIME; // если конфиг времени
      else configLim[3]++;
    }
    else configLim[3] = configLim[0];
  }
}
//==============================================================================
void ConfigDownButton()
{
  if (configLim[1] == 0) { //значит что без ограничений
    if (configLim[3] > 0)    configLim[3]--;
    else configLim[3] = 0;
  }
  else if (configLim[1] == 1)  { //значит что вырианты только да или нет
    configLim[3] = !configLim[3];
    // configLim[3] = 0;
  }
  else { //изменяющееся:
    if (configLim[3] > configLim[0])   {
      if (conftype == fRotate) configLim[3] = configLim[3] - STEP_ROTATE;   // если конфиг оборотов
      else  if (conftype == fTime) configLim[3] = configLim[3] - STEP_TIME; // если конфиг времени
      else configLim[3]--;
    }
    else configLim[3] = configLim[1];
  }
}




[/code]

Change_Rotate.ino

//===== функция меняет направление вращения в зависимости от цикла ===========
//===== возвращает напрвление врщения rotate_direction
byte  Change_Rotate(byte zikl) { //функция меняет направление вращения в зависимости от цикла
  switch (zikl)
  {
    case (1): //в 1 цикле запуск двигателя влево (частичная откачка 1 стороны рамок)
      if (rotate_direction == STOP) rotate_direction = LEFT; //если превый раз в цикле то ставим левое вращение
      //current_power = ConfigRead(addr_Save_RotateZikl_1);// считываем сохраненные настройки RotateSpeed_1;
      break;
    case (2): // во 2 цикле запуск двигателя вправо(откачка второй стороны рамок)
      if (rotate_direction == LEFT) rotate_direction = RIGHT;// если направление вращения мотора противоположное, то сбрасываем скорость вначале до 0
      //current_power = ConfigRead(addr_Save_RotateZikl_2);// считываем сохраненные настройки RotateSpeed_2;
      break;
    case (3): // в 3 цикле запуск двигателя влево (докачка рамок 1 стороны)
      if (rotate_direction == RIGHT) rotate_direction = LEFT;// если направление вращения мотора противоположное, то сбрасываем скорость вначале до 0
     // current_power = ConfigRead(addr_Save_RotateZikl_3);// считываем сохраненные настройки RotateSpeed_3;
      break;
    default:
      rotate_direction = STOP;
      current_power = 0;
      break;
  }
  return rotate_direction;
}
//==== функция применяет скорость и направление =====================================
void setdirspeed(byte rotate_direction)// функция применяет скорость и направление
{
  if (rotate_direction == STOP)
  {
    current_power = STOP;                 //сбрасываем шим.
    digitalWrite(Led_Rotate, LOW );     // гасим светодиод Led_Rotate
    digitalWrite(RELAY_YAKORJ, LOW);   // переключаем реле возбуждения якоря
    digitalWrite(RELAY_WOZBUD, LOW);   // переключаем реле возбуждения обмотки
    analogWrite(PWM_Pin, current_power); //включаем ШИМ
  }
  if (rotate_direction == LEFT) {      //если мотор вращается влево, то
    digitalWrite(Led_Rotate, HIGH );   //зажигаем светодиод вращения
    digitalWrite(RELAY_YAKORJ, HIGH); // переключаем реле возбуждения якоря
    digitalWrite(RELAY_WOZBUD, LOW);  // переключаем реле возбуждения обмотки
    analogWrite(PWM_Pin, current_power); //включаем ШИМ
  }
  if (rotate_direction == RIGHT) {
    digitalWrite(Led_Rotate, HIGH );   //зажигаем светодиод вращения
    digitalWrite(RELAY_YAKORJ, HIGH); // переключаем реле возбуждения якоря
    digitalWrite(RELAY_WOZBUD, HIGH); // переключаем реле возбуждения обмотки
    analogWrite(PWM_Pin, current_power); //включаем ШИМ
  };
}
//=== функция выводит на экран направление и скорость =========
void updateWorkLcd(byte zikl, byte rotate_direction)// функция выводит на экран направление и скорость
{
  byte nextZikl = zikl + 1;
  dispStaticDraw();                              // выводим изображение кнопок
  NameStringGet(16);                             // считываем из памяти  строку "Цикл 1"
  drawString_1607(2, 0, !color, Stringbuffer);   //пишем  "цикл 1" в строку
  drawString_1607(7, 0, !color, utoa(zikl, buffString, 10)); //пишем номер какой цикл,затираеся "1" в строке "Цикл 1"
  if (EndZikl == 1) {                            //в паузе между циклами выводим на дисплей
    Clear_LCD();                                 // очищаем дисплей
    dispStaticDraw();                            // выводим изображение кнопок
    NameStringGet(16);                           // считываем из памяти  строку "Цикл"
    drawString_1607(2, 0, !color, Stringbuffer); //пишем  "цикл" в строку
    drawString_1607(7, 0, !color, utoa(zikl, buffString, 10)); //пишем какой цикл
    NameStringGet(17);                           // считываем из памяти  строку "завершен"
    drawString_1607(0, 1, color, Stringbuffer);  //пишем выделенное "завершен" в строку
    NameStringGet(18);                           // считываем из памяти  строку "следующий"
    drawString_1607(3, 2, color, Stringbuffer);  //пишем "следующий" в строку
    NameStringGet(16);                           // считываем из памяти  строку "цикл"
    drawString_1607(5, 3, color, Stringbuffer);  //пишем "цикл" в строку
    NameStringGet(4);                            // считываем из памяти  строку "стоп"
    drawString_1607(6, 4, color, Stringbuffer);  //пишем "стоп" в строку
    drawString_1607(1, 4, color, "<<<<");        //рисуем направление вращения
    drawString_1607(11, 4, color, ">>>>");       //рисуем направление вращения
    if (nextZikl == 4) nextZikl = 1;
    drawString_1607(10, 3, color, utoa(nextZikl, buffString, 10)); //пишем какой цикл
    if ((nextZikl == 1) || (nextZikl == 3)) {
      drawString_1607(0, 4, color, "     <<<<<<     "); //рисуем направление вращения
    }
    if (nextZikl == 2) {
      drawString_1607(0, 4, color, "     >>>>>>     "); //рисуем направление вращения //lcd.setCursor_drawString_1607(0,4,1,"     >>>>>>     "); //рисуем направление вращения
    }
  }
  else {// в работе рисуем от направления вращения и выводим данные
    if (rotate_direction == STOP) {
      drawString_1607(1, 4, color, "<<<<");             //рисуем направление вращения
      drawString_1607(11, 4, color, ">>>>");            //рисуем направление вращения
      NameStringGet(4);                                 // считываем из памяти  строку "стоп"
      drawString_1607(6, 4, color, Stringbuffer);       //пишем "стоп" в строку
    }
    if (rotate_direction == LEFT) {
      drawString_1607(1, 4, color, "                "); // затираем содержимое строки
      drawString_1607(1, 4, color, "    <<<<<<     ");  //рисуем направление вращения
    }
    if (rotate_direction == RIGHT) {
      drawString_1607(1, 4, color, "                "); // затираем содержимое строки
      drawString_1607(1, 4, color, "    >>>>>>     ");  //рисуем направление вращения
    }
    if ((workAccelerated == 1) || (workBrake == 1)) {     // если у нас начато п\п двигателя или торможение
      if (workAccelerated == 1) {                         //если у нас начат плавный пуск двигателя
      drawString_1607(1, 1, color, "                "); // затираем содержимое строки
      drawString_1607(5, 1, color,"\xA0\xB0\xB7\xB3\xBE\xBD"); //пишем  "Разгон"
      }
      if (workBrake == 1) {                               // //если у нас начато торможение
        drawString_1607(1, 1, color, "                "); // затираем содержимое строки
        drawString_1607(1, 1, color,"\xA2\xBE\x80\xBC\xBE\xB7" );//пишем "Тормоз"
      }
    }
    if ((workAccelerated == 0) && (workBrake == 0)) {   // если у нас закончено п\п двигателя или торможение
      drawString_1607(1, 1, color, "                "); // затираем содержимое строки
    }
    indikazia_Speed(Speed);
    indikacya_Time(secund, minut);
  }
  drawFastVLine(0, 0, 55, color);   //рисуем вертикальную линию периметра
  drawFastVLine(95, 0, 55, color);  //рисуем вертикальную линию периметра слева, а то строки затерли местами периметр
  Update();                         //Обновление экрана, загрузка информации на дисплей
  Speed=0;
}

//=== вывод скорости на дисплей ==============================================
void indikazia_Speed(byte Speed)
{
  drawString_1607(1, 2, color, "               "); //затираем область вывода
  drawString_1607(1, 2, color, "\x9E\xB1\xBE\x80\xBE\x82\x8B"); //пишем "ОБОРОТЫ"
  drawString_1607(13, 2, color, "\x2f\xBC"); //пишем "/м"
  if ( Flag == 0) { //нужен для вывода данных во время исполнения цикла
    drawString_1607(9, 2, color, utoa(Speed, buffString, 10)); //пишем значение оборотов получаемых из прерывания
  }
//  else if ( Flag == 1) { //нужен для вывода данных  между циклами
//   drawString_1607(9, 2,color,utoa( configSpeed, buffString, 10)); //пишем значение оборотов сохраненных веепром
//  } 
}
//==========================================================
void indikacya_Time(byte secund, byte minut) {
  //------ выводим время ----------------------------------
  drawString_1607(1, 3, color, "               "); //затираем область вывода
  drawString_1607(1, 3, color, "\x92\x80\xB5\xBC\x8F"); //пишем "Время"
  if ( Flag == 0) {  //нужен для вывода данных во время исполнения цикла
    if( workAccelerated == 1){
      drawString_1607(7,3,color,utoa(ReadConfigTime, buffString, 10)); //пишем значение минут
      drawString_1607(8,3,color,":00");
      drawString_1607(12, 3, color, "\xBC\xB8\xBD"); //пишем "мин"
    }
    else {
    if (minut < 10) { // если минуты меньше 10
      drawString_1607(7, 3, color, utoa(minut, buffString, 10)); //пишем целое значение минут
    }
    else if (minut >= 10) { // если минут 10 и больше
      drawString_1607(6, 3, color, utoa(minut, buffString, 10)); //пишем целое значение минут
    }
    drawChar(49, 32, color, ':');   // пишем разделитель между минутами и секундами
    if (secund < 10) {              // если секунды меньше 10
      drawChar(54, 32, color, '0'); // пишем первым 0
      drawString_1607(10, 3, color, utoa(secund, buffString, 10)); //пишем значение секунд
    }
    else { drawString_1607(9, 3, color, utoa(secund, buffString, 10)); //пишем полное значение секунд
    }
    }
  }
  else if ( Flag == 1) //нужен для вывода данных  между циклами
  {
      drawString_1607(7,3,color,utoa(ReadConfigTime, buffString, 10)); //пишем значение минут
      drawString_1607(8,3,color,":00");
  }
   drawString_1607(12, 3, color, "\xBC\xB8\xBD"); //пишем "мин"
}

Font.h

[code]
static const char font[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00 ,  // 0x20   space  32
  0x00, 0x60, 0x60, 0x00, 0x00 ,  // 0x2e   .      46
  0x20, 0x10, 0x08, 0x04, 0x02 ,  // 0x2f   /      47
  0x3e, 0x51, 0x49, 0x45, 0x3e ,  // 0x30   0      48
  0x00, 0x42, 0x7f, 0x40, 0x00 ,  // 0x31   1
  0x42, 0x61, 0x51, 0x49, 0x46 ,  // 0x32   2
  0x21, 0x41, 0x45, 0x4b, 0x31 ,  // 0x33   3
  0x18, 0x14, 0x12, 0x7f, 0x10 ,  // 0x34   4
  0x27, 0x45, 0x45, 0x45, 0x39 ,  // 0x35   5
  0x3c, 0x4a, 0x49, 0x49, 0x30 ,  // 0x36   6
  0x01, 0x71, 0x09, 0x05, 0x03 ,  // 0x37   7
  0x36, 0x49, 0x49, 0x49, 0x36 ,  // 0x38   8
  0x06, 0x49, 0x49, 0x29, 0x1e ,  // 0x39   9      57
  0x00, 0x36, 0x36, 0x00, 0x00 ,  // 0x3a   :      58
  0x08, 0x14, 0x22, 0x41, 0x00 ,  // 0x3c   <      60
  0x00, 0x41, 0x22, 0x14, 0x08 ,  // 0x3e   >
  0x7e, 0x11, 0x11, 0x11, 0x7e ,  // 0x80   A   //р    128
  0x7f, 0x49, 0x49, 0x49, 0x33 ,  // 0x81   Б   //с    129
  0x7f, 0x49, 0x49, 0x49, 0x36 ,  // 0x82   В   //т
  0x7f, 0x01, 0x01, 0x01, 0x03 ,  // 0x83   Г   //у
  0xe0, 0x51, 0x4f, 0x41, 0xff ,  // 0x84   Д  	//ф
  0x7f, 0x49, 0x49, 0x49, 0x41 ,  // 0x85   E  	//х
  0x77, 0x08, 0x7f, 0x08, 0x77 ,  // 0x86   Ж  	//ц
  0x41, 0x49, 0x49, 0x49, 0x36 ,  // 0x87   З  	//ч
  0x7f, 0x10, 0x08, 0x04, 0x7f ,  // 0x88   И   //ш
  0x7c, 0x21, 0x12, 0x09, 0x7c ,  // 0x89   Й  	//щ
  0x7f, 0x08, 0x14, 0x22, 0x41 ,  // 0x8A   K  	//ъ
  0x20, 0x41, 0x3f, 0x01, 0x7f ,  // 0x8B   Л  	//ы
  0x7f, 0x02, 0x0c, 0x02, 0x7f ,  // 0x8C   M  	//ь
  0x7f, 0x08, 0x08, 0x08, 0x7f ,  // 0x8D   H  	//э
  0x3e, 0x41, 0x41, 0x41, 0x3e ,  // 0x8E   O  	//ю        142
  0x7f, 0x01, 0x01, 0x01, 0x7f ,  // 0x8F   П  	//я        143
  0x7f, 0x09, 0x09, 0x09, 0x06 ,  // 0x90   P  	//А        144
  0x3e, 0x41, 0x41, 0x41, 0x22 ,  // 0x91   C  	//Б
  0x01, 0x01, 0x7f, 0x01, 0x01 ,  // 0x92   T  	//В
  0x47, 0x28, 0x10, 0x08, 0x07 ,  // 0x93   У  	//Г
  0x1c, 0x22, 0x7f, 0x22, 0x1c ,  // 0x94   Ф  	//Д
  0x63, 0x14, 0x08, 0x14, 0x63 ,  // 0x95   X  	//Е
  0x7f, 0x40, 0x40, 0x40, 0xff ,  // 0x96   Ц	  //Ж
  0x07, 0x08, 0x08, 0x08, 0x7f ,  // 0x97   Ч 	//З
  0x7f, 0x40, 0x7f, 0x40, 0x7f ,  // 0x98   Ш 	//И
  0x7f, 0x40, 0x7f, 0x40, 0xff ,  // 0x99   Щ   //й
  0x01, 0x7f, 0x48, 0x48, 0x30 ,  // 0x9A   Ъ   //К
  0x7f, 0x48, 0x30, 0x00, 0x7f ,  // 0x9B   Ы   //Л
  0x00, 0x7f, 0x48, 0x48, 0x30 ,  // 0x9C   Э   //М
  0x22, 0x41, 0x49, 0x49, 0x3e ,  // 0x9D   Ь   //Н
  0x7f, 0x08, 0x3e, 0x41, 0x3e ,  // 0x9E   Ю  	//О
  0x46, 0x29, 0x19, 0x09, 0x7f ,  // 0x9F   Я 	//П
  0x20, 0x54, 0x54, 0x54, 0x78 ,  // 0xA0   a 	//Р
  0x3c, 0x4a, 0x4a, 0x49, 0x31 ,  // 0xA1   б 	//С
  0x7c, 0x54, 0x54, 0x54, 0x28 ,  // 0xA2   в 	//Т
  0x7c, 0x04, 0x04, 0x04, 0x0c ,  // 0xA3   г 	//У
  0xe0, 0x54, 0x4c, 0x44, 0xfc ,  // 0xA4   д 	//Ф
  0x38, 0x54, 0x54, 0x54, 0x18 ,  // 0xA5   e 	//Х
  0x6c, 0x10, 0x7c, 0x10, 0x6c ,  // 0xA6   ж 	//Ц
  0x44, 0x44, 0x54, 0x54, 0x28 ,  // 0xA7   з 	//Ч
  0x7c, 0x20, 0x10, 0x08, 0x7c ,  // 0xA8   и	  //Ш
  0x7c, 0x41, 0x22, 0x11, 0x7c ,  // 0xA9   й 	//Щ
  0x7c, 0x10, 0x10, 0x28, 0x44 ,  // 0xAA   к 	//Ъ
  0x20, 0x44, 0x3c, 0x04, 0x7c ,  // 0xAB   л 	//Ы
  0x7c, 0x08, 0x10, 0x08, 0x7c ,  // 0xAC   м 	//Ь
  0x7c, 0x10, 0x10, 0x10, 0x7c ,  // 0xAD   н 	//Э
  0x38, 0x44, 0x44, 0x44, 0x38 ,  // 0xAE   o 	//Ю
  0x7c, 0x04, 0x04, 0x04, 0x7c ,  // 0xAF   п 	//Я   175
  0x7C, 0x14, 0x14, 0x14, 0x08 ,  // 0xB0   p 	//а   176
  0x38, 0x44, 0x44, 0x44, 0x20 ,  // 0xB1   c 	//б
  0x04, 0x04, 0x7c, 0x04, 0x04 ,  // 0xB2   т 	//в
  0x0C, 0x50, 0x50, 0x50, 0x3C ,  // 0xB3   у   //г
  0x30, 0x48, 0xfc, 0x48, 0x30 ,  // 0xB4   ф   //д
  0x44, 0x28, 0x10, 0x28, 0x44 ,  // 0xB5   x   //е
  0x7c, 0x40, 0x40, 0x40, 0xfc ,  // 0xB6   ц   //ж
  0x0c, 0x10, 0x10, 0x10, 0x7c ,  // 0xB7   ч   //з
  0x7c, 0x40, 0x7c, 0x40, 0x7c ,  // 0xB8   ш   //и
  0x7c, 0x40, 0x7c, 0x40, 0xfc ,  // 0xB9   щ   //й
  0x04, 0x7c, 0x50, 0x50, 0x20 ,  // 0xBA   ъ   //к
  0x7c, 0x50, 0x50, 0x20, 0x7c ,  // 0xBB   ы   //л
  0x7c, 0x50, 0x50, 0x20, 0x00 ,  // 0xBC   ь   //м
  0x28, 0x44, 0x54, 0x54, 0x38 ,  // 0xBD   э   //н
  0x7c, 0x10, 0x38, 0x44, 0x38 ,  // 0xBE   ю   //о
  0x08, 0x54, 0x34, 0x14, 0x7c ,  // 0xBF   я   //п   191
};

[/code]

Lcd_Func.ino

[code]

//=== Инициализация дисплея
void Init_Lcd() {
  DDRB = 0xff;        //data direction for signals LCD_CLC, LCD_SDA, LCD_CSX
  // Инициализация дисплея
  PORTB &= ~(1 << (PB5));          //сбросить в 0 линию CLK
  PORTB &= ~(1 << (PB3));     //иначе - выставить 0 в SDA
  PORTB &= ~(1 << (PB2));          //сбросить в 0 линию CS
  SendByte (CMD, 0xE2);    //SOFTWARE RESET
  delay(20);
  PORTB |= (1 << (PB3));
  SendByte (CMD, 0x3A);    //Use internal oscillator
  SendByte (CMD, 0xEF);    //FRAME FREQUENCY:
  SendByte (CMD, 0x04);    //80Hz
  SendByte (CMD, 0xD0);    //1:65 divider
  SendByte (CMD, 0x20);    //Запись в регистр Vop
  SendByte (CMD, 0x90);    //Contrast 0x90...0x9F
  SendByte (CMD, 0xA4);    //Clear screen
  SendByte (CMD, 0x2F);    //power control set(charge pump on/off)
  //SendByte (CMD, 0xC8);    //mirror Y axis (about X axis). Для 1110 удалить!!!
  // SendByte (CMD, 0xA1);    //Инвертировать экран по горизонтали
  SendByte (CMD, 0xAC);    //set initial row (R0) of the display
  SendByte (CMD, 0x07);    //all on/normal display
  SendByte (CMD, 0xAF);    //display ON/OFF
  SendByte(CMD, 0xA6);    //positive mode (A6, A7)
  Clear_LCD();
}


//======================================================Очистка дисплея
void Clear_LCD() {
  for (int index = 0; index < 864 ; index++) {
    LCD_RAM[index] = (0x00);
  }
}
//******************************************************************************
void SendByte(char cmd, unsigned char c) //
{ unsigned char i;            //cmd = 1  - передача данных
  PORTB &= ~(1 << (PB2));          //сбросить в 0 линию CS
  PORTB &= ~(1 << (PB5));          //сбросить в 0 линию CLK
  (cmd) ? (PORTB |= (1 << (PB3))) : (PORTB &= ~(1 << (PB3)));
  PORTB |= (1 << (PB5));           //установить в 1 линию CLK
  for (i = 0 ; i < 8 ; i++ ) {      //передача 8-бит:
    PORTB &= ~(1 << (PB5));        //сбросить в 0 линию CLK
    (c & 0x80) ? (PORTB |= (1 << (PB3))) : (PORTB &= ~(1 << (PB3))); //если передаваемый бит 1 -  выставить 1 в SDA иначе - выставить 0
    PORTB |= (1 << (PB5));        //установить в 1 линию CLK
    c <<= 1;                      //сдвиг передаваемого бита
  }
  PORTB |= (1 << (PB2));         //установить в 1 линию CS
  //PORTB &= ~(1 << (PB5));          //сбросить в 0 линию CLK
}
//=== =Обновить дисплей
void Update() {
  for (byte p = 0; p < 9; p++) {
    SendByte(CMD, 0xB0 | p);
    SendByte(CMD, 0x00);
    SendByte(CMD, 0x10);
    for (byte col = 0; col < LCD_X; col++) {
      SendByte(DATA, LCD_RAM[(LCD_X * p) + col]);
    }
  }
}
//======Нарисовать пиксель
void drawPixel (byte x, byte y, boolean color) {
  if ((x < 0) || (x >= LCD_X) || (y < 0) || (y >= LCD_Y)) return;

  if (color) {
    LCD_RAM[x + (y / 8)*LCD_X] |= _BV(y % 8);
  }
  else       {
    LCD_RAM[x + (y / 8)*LCD_X] &= ~_BV(y % 8);
  }
}
//====Нарисовать букву
void drawChar(byte x, byte y, boolean color, unsigned char c) {
  if ((x >= LCD_X) || (y >= LCD_Y) || ((x + 4) < 0) || ((y + 7) < 0))
    return;
//        Serial.println("c do");  
//      Serial.println(c,DEC);
    
   if (c >= 32 && c <= 44)  c = c - 32;  // для пробела
  if (c >= 45 && c <= 47)  c = c - 45;  // для . и /
  if (c >= 48 && c <= 58)  c = c + 211; // для цифр от 0 до 9
  if (c >= 59 && c <= 60)  c = c + 210; // <
  if (c >= 61 && c <= 127)  c = c + 209;// >
  if (c >= 128 && c <= 143)  c = c - 64; // от р
  if (c >= 144 && c <= 175)  c = c - 128; //от А
  if (c >= 176 && c <= 191) c = c + 128; // от а
  if (c > 191)  return;  
//            Serial.println("c posle");  
//      Serial.println(c,DEC);  
//  if (c >= 32 && c <= 44)  c = c - 32;
//  if (c >= 45 && c <= 46)  c = c - 45;
//  if (c >= 47 && c <= 58)  c = c + 210;
//  if (c >= 59 && c <= 60)  c = c + 209;
//  if (c >= 61 && c <= 127)  c = c + 208;
//  if (c >= 128 && c <= 143)  c = c - 65; // (c-128)+63;
//  if (c >= 144 && c <= 175)  c = c - 129; //-143;
//  if (c >= 176 && c <= 191) c = c + 127;
//  if (c > 191)  return;
  for (byte i = 0; i < 6; i++ ) {
    byte line;
    (i == 5) ? line = 0x0 : line = pgm(&(font[(c * 5) + i])); //pgm(font+(c*5)+i);
    for (byte j = 0; j < 8; j++) {
      (line & 0x1) ? drawPixel(x + i, y + j, color) : drawPixel(x + i, y + j, !color);
      line >>= 1;
    }
  }
}
//===Вывод строки
void drawString(byte x, byte y, boolean color, char *str) {
  //unsigned char type = *str;
  //if(type>=128) x=x-3;
  while (*str) {
    drawChar(x, y, color, *str++);
    // unsigned char type = *str;
    // (type>=128)? x=x+3 : x=x+6;
    x = x + 6;
  }
}
//====Вывод строки в заданное знакоместо и строку
void drawString_1607(byte x, byte y, boolean color, char *str)
{
  byte nPos[16] = {0, 6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90};
  byte nStr[7] = {2, 12, 22, 32, 42, 52, 62};
  drawString(nPos[x], nStr[y], color, str);
}

//==== графика ======================================
//====Рисование линии
void drawLine(byte x0, byte y0, byte x1, byte y1, boolean color) {
  int steep = abs(y1 - y0) > abs(x1 - x0);
  if (steep) {
    swap(x0, y0);
    swap(x1, y1);
  }
  if (x0 > x1) {
    swap(x0, x1);
    swap(y0, y1);
  }
  int dx, dy;
  dx = x1 - x0;
  dy = abs(y1 - y0);

  int err = dx / 2;
  int ystep;

  (y0 < y1) ?  ystep = 1 : ystep = -1;

  for (; x0 <= x1; x0++) {
    (steep) ? drawPixel(y0, x0, color) : drawPixel(x0, y0, color);
    err -= dy;
    if (err < 0) {
      y0 += ystep;
      err += dx;
    }
  }
}
//===Рисование вертикальной линии
void drawFastVLine(byte x, byte y, byte h, boolean color) {
  drawLine(x, y, x, y + h - 1, color);
}
//====Рисование горизонтальной линии
void drawFastHLine(byte x, byte y, byte w, boolean color) {
  drawLine(x, y, x + w - 1, y, color);
}
//=== =Рисование прямоугольника
void drawRect(byte x, byte y, byte w, byte h, boolean color) {
  drawFastHLine(x, y, w, color);
  drawFastHLine(x, y + h - 1, w, color);
  drawFastVLine(x, y, h, color);
  drawFastVLine(x + w - 1, y, h, color);
}
//=== Рисование залитый прямоугольник
void fillRect(byte x, byte y, byte w, byte h, boolean color) {
  for (int16_t i = x; i < x + w; i++) {
    drawFastVLine(i, y, h, color);
  }
}
//=== Рисование дуги
void drawCircleHelper(byte x0, byte y0, byte r, byte cornername, boolean color) {
  int f = 1 - r;
  int ddF_x = 1;
  int ddF_y = -2 * r;
  int x = 0;
  int y = r;

  while (x < y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x;
    if (cornername & 0x4) {
      drawPixel(x0 + x, y0 + y, color);
      drawPixel(x0 + y, y0 + x, color);
    }
    if (cornername & 0x2) {
      drawPixel(x0 + x, y0 - y, color);
      drawPixel(x0 + y, y0 - x, color);
    }
    if (cornername & 0x8) {
      drawPixel(x0 - y, y0 + x, color);
      drawPixel(x0 - x, y0 + y, color);
    }
    if (cornername & 0x1) {
      drawPixel(x0 - y, y0 - x, color);
      drawPixel(x0 - x, y0 - y, color);
    }
  }
}
//=======Рисование дуги
void fillCircleHelper(byte x0, byte y0, byte r, byte cornername, byte delta, boolean color) {

  int f = 1 - r;
  int ddF_x = 1;
  int ddF_y = -2 * r;
  int x = 0;
  int y = r;

  while (x < y) {
    if (f >= 0) {
      y--;
      ddF_y += 2;
      f += ddF_y;
    }
    x++;
    ddF_x += 2;
    f += ddF_x;

    if (cornername & 0x1) {
      drawFastHLine(x0 - x, y0 + y, 2 * x + 1 + delta, color);
      drawFastHLine(x0 - y, y0 + x, 2 * y + 1 + delta, color);
    }
    if (cornername & 0x2) {
      drawFastHLine(x0 - x, y0 - y, 2 * x + 1 + delta, color);
      drawFastHLine(x0 - y, y0 - x, 2 * y + 1 + delta, color);
    }
  }
}



[/code]

Meny.ino

[code]
//=========== ЭКРАН ПРИВЕТСТВИЯ =================================
void Priwetstwie()
{ //===  рисуем рамку по периметру ===
  drawRect(0,  0, LCD_X, LCD_Y, color);     // Рисуем прямоугольник по координатам x, y, высота, ширина, цвет
  //*****  выводим текст ****************************************
  NameStringGet(0);                         //Считываем название регулятор
  drawString(20, 2, color,  Stringbuffer);  // выводим надпись регулятор
  NameStringGet(1);                         //Считываем "оборотов"
  drawString(20, 12, color, Stringbuffer);  // выводим надпись "оборотов"
  NameStringGet(2);                         //Считываем название "медогонки"
  drawString(20, 22, color, Stringbuffer);  // выводим надпись "медогонки"
  NameStringGet(3);                         //Считываем название "ПЧЕЛКА"
  drawString(20, 32, color,  Stringbuffer); // выводим надпись "ПЧЕЛКА"
  drawString_1607(5, 5, color, "0.3"); // выводим надпись версии програмы
  Update();
}
//=== Эта функция обрисовывает экран ====
void dispStaticDraw() {
  fillRect(1, 0, 94, 11, color);            //рисуем залитый квадрат кнопка стоп
  //drawFastHLine(2, 8, 92, color);             //рисуем горизонтальную линию периметра
  drawFastVLine(0, 0, 55, color);             //рисуем вертикальную линию периметра
  drawFastVLine(95, 0, 55, color);            //рисуем вертикальную линию периметра
  fillRect(0, 56, 28, 12, color);             //рисуем залитый квадрат кнопка пуск
  fillCircleHelper(48, 72, 19, 2, 0, color);  //рисуем залитый полукруг кнопка меню энкодер
  fillRect(68, 56, 28, 12, color);            //рисуем залитый квадрат кнопка стоп
  NameStringGet(21);                          //Считываем название
  drawString(3, 58, !color, Stringbuffer);    // выводим надпись кнопки Пуск
  NameStringGet(20);                          //Считываем название
  drawString(37, 59, !color, Stringbuffer);   // выводим надпись кнопки Меню
  NameStringGet(4);                           //Считываем название
  drawString(71, 58, !color, Stringbuffer);   // выводим надпись кнопки Стоп
}

//===== считывание строк из progmem=================
void NameStringGet(byte snumb) { //данные о файле. Входной параметр - номер файла СИСТЕМНЫЙ
  strcpy_P(Stringbuffer, (char*)pgm_read_word(&(StringNames[snumb]))); //Считываем название
}

//=== функция вывода сообщения в шапку дисплея ====
void dispHead(char head[LCDCOL - 1])
{
  drawString_1607(0, 0, !color, "                "); // выводим сообщение
  drawString_1607(1, 0, !color, head); // выводим сообщение
}
//===================================================
/* Эта функция выводит строку в одну из 4 строк дисплея. */
/*byte str - номер стоки,  char textstr[16]- надпись  */
void dispString(byte str, char textstr[LCDCOL])
{
  if ((str >= 0) && (str < DISPSTR)) {                  // ограничим область работы
    drawString_1607(2, str + 1, color, textstr); // переходим в область вывода текста и пишем
  }
}

//=== функция подсвечивает строку курсором ========
void stringSelect()
{
  uint8_t curst = brCrumbs[level][1] & LCDBITS; // выкорчевываем лишнюю инфу. чем хреново? кол-во строк должно быть кратно 2 :)
  if(lastcurst != curst){
  fillRect(2, 14 + (10 * lastcurst), 5, 3, !color); // затираем прямоугольничек динамический курсор  
  }
  fillRect(2, 14 + (10 * curst), 5, 3, color); // рисуем прямоугольничек динамический курсор
  lastcurst=curst;
}

//========== Чтение из ЕЕПРОМ ====================================
inline byte ConfigRead(unsigned int _param1) {
  return EEPROM.read(_param1);
}
//========== Сохранение в ЕЕПРОМ =================================
inline void ConfigWrite(unsigned int _param1, byte _param2) {
  EEPROM.write (_param1, _param2);
}

//================================================================
/*brCrumbs[level][х] - массив хлебных крошек. двухмерный 1 байт "level" - номер программы,
  2 байт "х" - положение курсора   */
void pageList()
{
  dispStaticDraw();     //рисуем статику
  char buff[LCDCOL];  // объявляем массив размером ((LCDCOL=16 Максимальное количество символов в строке)-5)=11
  uint8_t currs;        // локальная переменная
  uint8_t fstart;       // локальная переменная
  if ((brCrumbs[level][1]&LCDINVBITS) == ((fileData[2])&LCDINVBITS)) currs = (fileData[2])&LCDBITS; //Это максимальное положение курсора на последней странице
  /*brCrumbs[level][х] - массив хлебных крошек. двухмерный 1 байт "level" - номер программы, 2 байт "х" - положение курсора   */
  else currs = LCDBITS; //currs=3;
  fstart = fileData[1] + (brCrumbs[level][1] & LCDINVBITS); //номер файла для старта вывода.
  for (uint8_t i = 0; i <= currs; i++) //пишем из массива для статических папок
  {
    strcpy_P(buff, (char*)pgm_read_word(&(fileNames[i + fstart]))); //прочитали имя
    dispString(i, buff); //пишем из массива
  }
  dispHead(buffer); //пишем заголовок
  stringSelect();  //подсвечиваем строку
}

//=========== Получение данных о файле ======================
void fileGet(byte fnumb)//данные о файле. Входной параметр - номер файла СИСТЕМНЫЙ
{
  for (uint8_t i = 0; i < FILEREW; i++)
  {
    fileData[i] = pgm_read_byte(&fileStruct[FILEREW * fnumb + i]); //Считываем числовые значения
    if ((FILEREW * fnumb) == 27)conftype = fSensVibro; // выбран пункт меню датчик биений, ставим флаг
    if ((FILEREW * fnumb) == 30)conftype = fSensTok; // выбран пункт меню датчик тока, ставим флаг
    if (((FILEREW * fnumb) == 33) || ((FILEREW * fnumb) == 39) || ((FILEREW * fnumb) == 45)) {
      conftype = fRotate; // выбран пункт меню обороты, ставим флаг
    }
    if (((FILEREW * fnumb) == 36) || ((FILEREW * fnumb) == 42) || ((FILEREW * fnumb) == 48)) {
      conftype =  fTime; // выбран пункт меню время, ставим флаг
    }
    if ((FILEREW * fnumb) == 51)conftype = fAccelerated; // выбран пункт меню плавный пуск, ставим флаг
    if ((FILEREW * fnumb) == 54)conftype = fBrake;     // выбран пункт меню торможение двигателем, ставим флаг
  }
  strcpy_P(buffer, (char*)pgm_read_word(&(fileNames[fnumb])));//Считываем название
}
//=================================================
void fileReturn() {
  //во первых, удалим текущую информацию о местоположении:
  brCrumbs[level][0] = 0;
  brCrumbs[level][1] = 0;
  brCrumbs[level][2] = 0;
  //вернемся на уровень назад, только если он еще больше нуля. А то уйдем еще в минуса.
  if (level > 0) level--;     //находясь на уровне выше, уже можно прочесть инфу о родителе:
  fileNumb = brCrumbs[level][0]; //при этом положение курсора не сбрасывается! мы вернемся туда, откуда пришли.
  if (Pause == 1){ // если мы в паузе во время исполнения циклов работы то должны придти в настройки цикла
  if(zikl==1){   // если  1 цикл
   fileNumb = 6 ;// номер файла (папка 1 цикла) 
  }
  if(zikl==2){  // если  2 цикл
   fileNumb = 7;// номер файла (папка 2 цикла)  
  }
  if(zikl==3){  // если  3 цикл
   fileNumb = 8;// номер файла (папка 3 цикла)  
  }  
}
  fileGet(fileNumb);          //Читаем инфу о файле.
  Clear_LCD();      // очищаем дисплей
  pageList();
}
//=======================================================
void fileSelect()//вызывается в обработчике кнопок меню при нажатии >
{ //вычисляем новый номер файла.
  fileNumb = fileData[1] + brCrumbs[level][1]; //берем номер стартового файла и прибавляем  текущую позицию списка файлов в папке (сколько мы вниз нащелкали.)
  level++;                                  //на уровень глубже
  brCrumbs[level][0] = fileNumb;            //вошли внутрь? супер, запишем инфу о нашем местоположении.номер последнего выбранного файла
  brCrumbs[level][1] = 0;                   //текущую позицию списка файлов в папке =0
  brCrumbs[level][2] = 0;                   //количества выбранных файлов
  fileGet(fileNumb);                        //считываем информацию
  byte fileType = fileData[0];              //Читаем 1 байт файла
  switch (fileType)                         //сюда добавляем в зависимости от типа файла.
  {
    case T_FOLDER:
      pageList();                          // обновление страницы меню
      break;
    case T_APP:
      programSelect();                     // выбор запускаемой программы, у меня вывод информации о сохраненых настройках
      break;
    case T_CONF:
      configSelect(fileData[2], conftype); // настройки конфигов
      break;   
  }
}
//обработчик типа файлов
void programSelect()//тип файла 0x01
{
  byte Sensor;
  char buffString[4];       // объявляем локальный массив
  Clear_LCD();              // очищаем дисплей
  dispStaticDraw();         // Обрисовываем экран
  strcpy_P(buffer, (char*)pgm_read_word(&(fileNames[2])));//Считываем название
  dispHead(buffer);  // пишем заголовок
  Sensor = ConfigRead(addr_Save_SensVibro); // считываем из памяти значение датчика вибрации
  NameStringGet(22);                        // считываем из памяти  строку "Дат.биений"
  drawString_1607(1, 1, color, Stringbuffer);       //пишем строку "Дат.биений"
  if (Sensor == 0) {        // если значение 0 то датчик отключен
    NameStringGet(14);               // считываем из Progmem строку "Выкл"
    drawString_1607(12, 1, color, Stringbuffer); //выводим "Выкл"
  }
  else {                    // если значение не 0 то датчик включен
    NameStringGet(15);               // считываем из Progmem строку "Вкл "
    drawString_1607(12, 1, color, Stringbuffer); //выводим "Вкл "
  }

  Sensor = ConfigRead(addr_Save_SensTok); // считываем из памяти значение датчика тока
  NameStringGet(23);                        // считываем из памяти  строку "Дат.тока"
  drawString_1607(1, 2, color, Stringbuffer);       //пишем строку "Дат.тока"
  if (Sensor == 0) {       // если значение 0 то датчик отключен
    NameStringGet(14);               // считываем из Progmem строку "Выкл"
    drawString_1607(12, 2, color, Stringbuffer); //выводим "Выкл"
  }
  else
  {
    NameStringGet(15);               // считываем из Progmem строку "Вкл "
    drawString_1607(12, 2, color, Stringbuffer); //выводим "Вкл "
  }

  Sensor = ConfigRead(addr_Save_ACCELERATED); // считываем из памяти значение "Плав.пуск"
  NameStringGet(24);                        // считываем из памяти  строку "Плав.пуск"
  drawString_1607(1, 3, color, Stringbuffer);       //пишем строку "Плав.пуск"
  if (Sensor == 0) {       // если значение 0 то датчик отключен
    NameStringGet(14);               // считываем из Progmem строку "Выкл"
    drawString_1607(12, 3, color, Stringbuffer); //выводим "Выкл"
  }
  else
  {
    NameStringGet(15);               // считываем из Progmem строку "Вкл "
    drawString_1607(12, 3, color, Stringbuffer); //выводим "Вкл "
  }

  Sensor = ConfigRead(addr_Save_BRAKE); // считываем из памяти значение Торможение двигателем
  NameStringGet(25);                        // считываем из памяти  строку "Тормоз"
  drawString_1607(1, 4, color, Stringbuffer);       //пишем строку "Тормоз"
  if (Sensor == 0) {       // если значение 0 то датчик отключен
    NameStringGet(14);               // считываем из Progmem строку "Выкл"
    drawString_1607(12, 4, color, Stringbuffer); //выводим "Выкл"
  }
  else
  {
    NameStringGet(15);               // считываем из Progmem строку "Вкл "
    drawString_1607(12, 4, color, Stringbuffer); //выводим "Вкл "
  }
  drawFastVLine(95, 0, 55, color);  //рисуем вертикальную линию периметра слева, а то строки затерли местами периметр
  Update();            //Обновление экрана, загрузка информации на дисплей
  delay(5000);
  fileReturn();
}

[/code]

Rabota.ino

[code]
void Programm1() {
  /*  //читаем сохраненые в еепроме настройки времени и оборотов
    VIBRO = ConfigRead(addr_Save_SensVibro);// считываем сохраненные настройки VIBRO = 0;//  уровень вибрации
    TOK = ConfigRead(addr_Save_SensTok);    // считываем сохраненные настройки byte TOK = 0;*/
  /* сначала покажем на дисплее номер цикла,сохраненые настройки цикла  и направление вращения в цикле
    до тех пор пока не нажмем кнопку пуск*/
  Clear_LCD();                           // очищаем дисплей
  // обнулим переменные вывода времени
  minut = 0;
  secund = 0;
  secundIsr = 0;
  isrRotateTime = 0;
  //=============================================================
  workType = 1;
  Flag = 1;                              // для вывода данных о цикле во время остановки между циклами
  zikl = 1;                              // переключимся на 1 цикл вращения
  Speed = 0;                             // стартовое значение скорости вращения
  rotate_direction = STOP;               // направление вращения нет
  setdirspeed(rotate_direction);         // функция применяет скорость и направление
  //ReadDataZikl(zikl);             //считываем данные времени и оборотов из еепрома согласно цикла
  updateWorkLcd(zikl, rotate_direction); // функция выводит на экран направление и скорость
  butt = 0;
  while (butt != 6) {                   // ждем нажатия кнопки пуск
    butt = buttonsStat();
  }
  //**** нажали пуск и начался 1 цикл *****************************************************
  digitalWrite(Led_Rotate, HIGH );     // зажигаем светодиод вращения
  Clear_LCD();      // очищаем дисплей
  while (workType == 1) { // блокируемся в программе
    butt = 0;
    EndZikl = 0;       // сбросим флаг конца цикла
    Flag = 0;          //сбросим флаг что бы отображались переменные оборотов и убывающего времени цикла
    while (EndZikl == 0) { //пока не нажали СТОП или не закончилось время работы 1 цикла
      butt = buttonsStat();
      rotate_direction = Change_Rotate(zikl);//в зависимости от цикла меняем направление вращения
      setdirspeed(rotate_direction);  // функция применяет скорость и направлени
     rotate(rotate_direction, ACCELERATED, BRAKE);
    }
    digitalWrite(Led_Rotate, LOW );     // гасим светодиод вращения
    if (workType == 0) { // проверяем не нажимали ли кнопку стоп во время цикла
      goto POINTEXIT;  //переход на точку выхода из функции
    }  
    //***** Пауза между 1 и 2 циклами для перестановки рамок **************
    //***** запуск цикла возможен при закрытой крышке(концевик) и нажатой кнопке пуск *****
    Clear_LCD();      // очищаем дисплей
    zikl = 1;                            //поставим 1 цикл вращения для индикации завершения цикла в паузе
    rotate_direction = Change_Rotate(zikl);//в зависимости от цикла меняем направление вращения
    updateWorkLcd(zikl, rotate_direction); // функция выводит на экран направление и скорость
    while (butt != 6)                    // ждем нажатия кнопки пуск
    {
      butt = buttonsStat();
    }
    //**** нажали пуск и начался 2 цикл *****************************************************
    digitalWrite(Led_Rotate, HIGH );     // зажигаем светодиод вращения
    Clear_LCD();      // очищаем дисплей
    minut = 0;
    secund = 0;
    secundIsr = 0;
    zikl = 2;          //переключимся на 2 цикл вращения
    butt = 0;          // обнулим кнопку
    Flag = 0;          //сбросим флаг что бы отображались переменные оборотов и убывающего времени цикла
    EndZikl = 0;
    while (EndZikl == 0) { //пока не нажали СТОП или не закончилось время работы 2 цикла 
      butt = buttonsStat();
      rotate_direction = Change_Rotate(zikl);//в зависимости от цикла меняем направление вращения
      setdirspeed(rotate_direction);  // функция применяет скорость и направлени
      rotate(rotate_direction,ACCELERATED, BRAKE);   
    }
    digitalWrite(Led_Rotate, LOW );     // гасим светодиод вращения
    if (workType == 0) { // проверяем не нажимали ли кнопку стоп во время цикла
      
      goto POINTEXIT;  //переход на точку выхода из функции
    }
    //***** Пауза между 2 и  3 циклами для перестановки рамок **************
    //***** запуск цикла возможен при закрытой крышке(концевик) и нажатой кнопке пуск *****
    Clear_LCD();      // очищаем дисплей
    zikl = 2;                            //поставим 1 цикл вращения для индикации завершения цикла в паузе
    rotate_direction = Change_Rotate(zikl);//в зависимости от цикла меняем направление вращения 
    updateWorkLcd(zikl, rotate_direction); // функция выводит на экран направление и скорость
    while (butt != 6)                    // ждем нажатия кнопки пуск
    {
      butt = buttonsStat();
    }
    //**** нажали пуск и начался 3 цикл *****************************************************
    digitalWrite(Led_Rotate, HIGH );     // зажигаем светодиод вращения
    Clear_LCD();      // очищаем дисплей
    minut = 0;
    secund = 0;
    secundIsr = 0;
    zikl = 3;          //переключимся на 3 цикл вращения
    butt = 0;          // обнулим кнопку
    Flag = 0;          //сбросим флаг что бы отображались переменные оборотов и убывающего времени цикла
    EndZikl = 0;
      while (EndZikl == 0) { //пока не нажали СТОП или не закончилось время работы 3 цикла
        butt = buttonsStat();
        rotate_direction = Change_Rotate(zikl);//в зависимости от цикла меняем направление вращения
        setdirspeed(rotate_direction);  // функция применяет скорость и направлени
        rotate(rotate_direction, ACCELERATED, BRAKE);
      } 
POINTEXIT:  //точка выхода из функции по переходу goto/ нажимали СТОП
;
    workType = 0;
    EndZikl = 0;                     // конец цикла
  }
  digitalWrite(Led_Rotate, LOW );     // гасим светодиод вращения
  fileReturn();
}

[/code]

ReadDataZikl.ino

[code]
void ReadDataZikl(byte zikl)
{
  BRAKE = ConfigRead(addr_Save_BRAKE);// считываем сохраненные настройки _BRAKE
  ACCELERATED = ConfigRead(addr_Save_ACCELERATED);// считываем сохраненные настройки ACCELERATED
  if (zikl == 1)
  {
    configSpeed = ConfigRead(addr_Save_RotateZikl_1);// считываем сохраненные настройки RotateSpeed_1
    if (configSpeed > MAX_Speed)//проверям не превышаем ли мы лимит скорости вращения
    {
      configSpeed = MAX_Speed; // ограничиваем максимальную скорость вращения
    }
    ReadConfigTime = ConfigRead(addr_Save_TimeZikl_1);// считываем сохраненные настройки
    RotateTime = ReadConfigTime * 60000; // переводим в милисекунды
    if (RotateTime > MAX_ROTATE_TIME)  RotateTime = MAX_ROTATE_TIME; //проверям не превышаем ли мы лимит времени вращения// ограничиваем максимальное время вращения
  }
  if (zikl == 2)
  {
    configSpeed =  ConfigRead(addr_Save_RotateZikl_2);// считываем сохраненные настройки RotateSpeed_2
    if (configSpeed > MAX_Speed)configSpeed = MAX_Speed; // ограничиваем максимальную скорость вращения
    ReadConfigTime =  ConfigRead(addr_Save_TimeZikl_2);// считываем сохраненные настройки
    RotateTime = ReadConfigTime * 60000; // переводим в милисекунды
    if (RotateTime > MAX_ROTATE_TIME) RotateTime = MAX_ROTATE_TIME; // ограничиваем максимальное время вращения
  }
  if (zikl == 3)
  {
    configSpeed =  ConfigRead(addr_Save_RotateZikl_3);// считываем сохраненные настройки RotateSpeed_3
    if (configSpeed > MAX_Speed) configSpeed = MAX_Speed; // ограничиваем максимальную скорость вращения
    ReadConfigTime =  ConfigRead(addr_Save_TimeZikl_3);// считываем сохраненные настройки
    RotateTime = ReadConfigTime * 60000; // переводим в милисекунды
    if (RotateTime > MAX_ROTATE_TIME) RotateTime = MAX_ROTATE_TIME; // ограничиваем максимальное время вращения
  }
  minut = ReadConfigTime - 1;
}

[/code]

Rotate.ino

[code]
//Функция rotate предназначена для работы двигателя заданное время в заданном направлении.
/////////////////////////ПАРАМЕТРЫ ФУНКЦИИ////////////////////////////////////////////
// rotate_direction   - направление вращения, возможны значения LEFT или RIGHT или STOP
// configSpeed    - количество оборотов в минуту, которое будет поддерживаться при работе двигателя. Указать 0, если нет ограничений.
// RotateTime        - время работы в заданном направлении в секундах
// ACCELERATED - плавный пуск, возможные значения 0 или 1. Время плавного пуска - ACCEL_TIME
// _BRAKE       - использовать торможение для остановки мотора,возможные значения TRUE или FALSE
//////////////////////////////////////////////////////////////////////////////////////
void rotate(byte rotate_direction, byte ACCELERATED, boolean BRAKE) {
  //*** готовим переиенные и прерывания *****
  f_isrSpeed = 0;
  Speed = 0;                            // обнулим значение  скорости
  isrRotateTime = 0;                    // обнулим время проработаное в цикле
  MsTimer2::set(1000,  timerIsr);       //инициализация таймера 1 секунда1
  digitalWrite(D_HOLLA, HIGH);          // настраиваем пин вывода датчика Холла
  attachInterrupt(0, isrSpeed, FALLING);// разрешаем прерывания по выводу от датчика Холла
  ReadDataZikl(zikl);                   //считываем данные времени и оборотов из еепрома согласно цикла

  //=======   ПЛАВНЫЙ РАЗГОН =====================================================================//
  if (ACCELERATED == 1) {             //если у нас разрешен плавный пуск двигателя
    Accelerated();                    // начинаем пуск двигателя
  }
  //============ ОСНОВНОЙ ЦИКЛ работы двигателя ======================//
  TCCR1B = TCCR1B & 0b11111000 | 0x01; // настраиваем таймер 1 на частоту   31.250 кГц
  unsigned long time_in = millis();    // время входа в функцию
POINT_new_Time:                        //точка выхода из функции по переходу goto, меняли настройки времени илли оборотов
  ;
  if (Pause == 1) {                   // если еще в паузе
    ReadDataZikl(zikl);               //считываем данные времени и оборотов из еепрома согласно цикла
    RotateTime = RotateTime - isrRotateTime; // от установленного времени отнимем уже отработаное время
    Pause = 0;                       // выход из паузы
    digitalWrite(Led_Rotate, HIGH ); // зажигаем светодиод вращения
  }
  MsTimer2::start();                     //разрешим прерывания по таймеру 1 с интервалом в 1 сек.
  while ((workType == 1) && (millis() - time_in) <= RotateTime) { //пока время нахождения в функции меньше или равно времени сохраненного для данного цикла
    butt = 0;                            // обнулим кнопки
    getSpeed();                          // регулируем скорость пид регулятором
    butt = buttonsStat();                // проверяем кнопки на нажатие
    //=== В ПАУЗЕ ИЛИ СТОП ===========================================
    if (butt != 0) {                                   // если кнопки нажимались
      if ((butt == 6) || (butt == 2) || (butt == 3)) { // если нажали кнопку пуск или кутили энкодер
        Pause = 0;                                     // никак не реагируем
      }
      else {
        //Timer1.detachInterrupt();            //останавливаем прерывания по таймеру
        MsTimer2::stop();
        switch (butt) {                      //
          case 1:                            // если нажали кнопку СТОП
            digitalWrite(RELAY_YAKORJ, LOW); // переключаем реле возбуждения якоря для остановки двигателя
            if (BRAKE == 1) {                // если включено торможение двигателем
              Brake();                       // то тормозим двигателем
            }
            EndZikl = 1;                     // конец цикла
            zikl = 0;                        // обнуляем цикл
            workType = 0;
            break;
          case 4:                           // нажималась кнопка энкодера - будем менять настройки в процессе работы
            Pause = 1;                      // ставим флаг что в паузе
            Clear_LCD();                    // очищаем дисплей
            fileReturn();                   // выходим в меню
            butt = 0;                       // сбросим значение нажатой кнопки
            do {
              DO_EVERY(BLINK_INTERVAL, digitalWrite(Led_Rotate, !digitalRead(Led_Rotate))); // мигаем диодом(переодически переключаем его состояние)
              menuButtons(butt);           // что то меняем в меню
              butt = buttonsStat();        // проверяем кнопки на нажатие
              Update();                    // Обновление экрана, загрузка информации на дисплей
            } while (butt != 6);           // пока не нажмем кнопку Пуск
            Clear_LCD();                   // очищаем дисплей
            break;
          default:
            break;
        }
        if (Pause == 1) {                 // если еще в паузе
          goto POINT_new_Time;            // переходим на точку входа в цикл для смены времени вращения
        }
      }
    }
  }
  //Timer1.detachInterrupt();                 //останавливаем прерывания по таймеру
  MsTimer2::stop();
  digitalWrite(RELAY_YAKORJ, LOW);          // переключаем реле возбуждения якоря для остановки двигателя
  //========== ТОРМОЖЕНИЕ ДВИГАТЕЛЯ ===================================//
  //Для торможения подаем BRAKE_POWER мощности в противофазе на мотор до снижения оборотов до значения BRAKE_OFF_TURNOVER
  if (BRAKE == 1) {                        // если установлено торможение двигателем
    Brake();                               // то тормозим
  }
  detachInterrupt(0);                     // запрещаем прерывания по выводу от датчика Холла
  EndZikl = 1;                            // конец цикла вращения
  Flag = 1;                               // для вывода данных во время исполнения цикла или между циклами
}
//*******************************************
//=== функция плавного пуска ===============
//*******************************************
void Accelerated() {
  workAccelerated = 1;                             // ставим флаг что вошли в функцию плавного разгона двигтеля
  TCCR1B = TCCR1B & 0b11111000 | 0x05;             // настраиваем таймер 1 на частоту  30.517578125 Гц
  unsigned long time_in = millis();                //время входа в функцию
  current_power = byte (MAX_POWER * (millis() - time_in) / ACCEL_TIME);// получаем значение мощности для PWM
  setdirspeed(rotate_direction);                   // функция применяет мощность и направление
  while (((millis() - time_in) < ACCEL_TIME) ) {   //пока время нахождения в функции меньше времени плавного пуска
    DO_EVERY(Speed_INTERVAL, {                     // каждую секунду проверяем скорость и увеличиваем PWM
      if (Speed < (configSpeed - 30)) {            // ограничиваем скорость разгона
        analogWrite(PWM_Pin, current_power++);     // увеличиваем PWM
      }
      updateWorkLcd(zikl, rotate_direction);       //раз в сек. выводит на экран направление и скорость
    });
  }
  workAccelerated = 0;                             // сбросим флаг - цикл в функции закончен
}

//=== функция торможения двигателем =========
void Brake() {
  workBrake = 1;                             // ставим флаг что вошли в функцию торможения
  while (Speed > BRAKE_OFF_TURNOVER) {       // пока скорость вращения больше чем минимальная скорость
    current_power = BRAKE_POWER;             //
    if (rotate_direction == LEFT) {          // если крутились влево
      digitalWrite(RELAY_WOZBUD, HIGH );     // переключаем реле возбуждения обмотки на противоположное
    }
    if (rotate_direction == RIGHT) {         // если крутились вправо
      digitalWrite (RELAY_WOZBUD, LOW);      // переключаем реле возбуждения обмотки на противоположное
      updateWorkLcd(zikl, rotate_direction); // выводим на экран направление и скорость
    }
  }
  workBrake = 0;                             // сбосим флаг, значит что цикл функции торможения закончен
}


//=== функция плавного пуска ===============
//void Accelerated() {
//  unsigned long time_in = millis();           //время входа в функцию
//  workAccelerated = 1;                   // ставим флаг что вошли в функцию плавного разгона двигтеля
//  //DO_EVERY(Speed_INTERVAL,SpeedRotate); // подсчет количества оборотов
//  while (((millis() - time_in) <= ACCEL_TIME) )   //пока время нахождения в функции меньше или равно времени плавного пуска
//  {
//    current_power = int (MAX_POWER * (millis() - time_in) / ACCEL_TIME);// получаем значение мощности для PWM
//        if(f_isrSpeed == 1) {
//      Speed = getSpeed(impulsSpeed);
//     }                             //получаем количество оборотов из прерываний
//    if ((Speed > 0) && (Speed > configSpeed)) {//если превышаем установленое ограничение на количество оборотов
//      current_power = current_power - 1;       //уменьшаем значение мощности для PWM на единицу
//      //delay(Speed_CHANGE_DELAY);               //делаем задержку 100 миллисекунд
//    }
//    setdirspeed(rotate_direction);             // функция применяет мощность и направление
//    updateWorkLcd(zikl, rotate_direction); // функция выводит на экран направление и скорость
//  }
//  //RotateTime = RotateTime - ACCEL_TIME;     //уменьшаем время вращения на время разгона
//  workAccelerated = 0;                        // сбросим флаг - цикл в функции закончен
//}

[/code]

Speed.ino

[code]
//--- пид регулирование скорости ----------------
void getSpeed() {
//  if(micros()- microsold > 41667){ // если обороты меньше 10 об/мин
//    Speed = 0;                     // значит обороты равны 0 об/мин
//  }
  P_reg();                        // вычисление пропорциональной составляющей 
  I_reg();                        // вычисление интегральной составляющей 
  int PI_reg = P + I;             // получаем значение для шим
  if(PI_reg < 0){ // ограничение по максимуму
    PI_reg = 0;
  }
  if(PI_reg > 255){ // ограничение по максимуму
    PI_reg = 255;
  }
  analogWrite(PWM_Pin, PI_reg);

//  Serial.print("RPM = ");
//  Serial.print(Speed, DEC); // об/мин
//  Serial.print("  PWM = ");
//  Serial.print(PI_reg, DEC);
//  Serial.print("  P = ");
//  Serial.print(P, DEC);
//  Serial.print("  I = ");
//  Serial.print(I, DEC);
//  Serial.print('\n');
}

//=== вычисление оборотов в прерываниях =============
void isrSpeed (){                                // вычисление оборотов
  Speed = (1000000.0/(micros() - microsold))*60; // получаем значение скорости вращения
  microsold = micros();                          // присваеваем значение
  f_isrSpeed = 1;                                // флаг сработки прерывания
}
//== вычисление пропорциональной составляющей ======
void P_reg(){ // пропорциональная составляющая
  P = Kp*(configSpeed - Speed) + current_power;
  if(P < 0){ // ограничение по минимуму
    P = 0;
  }
}
//== вычисление интегральной составляющей ======
void I_reg(){ // интегральная составляющая
  if(millis()-millis1 > Ti){
    if(configSpeed > Speed){
      I += Ki*(configSpeed - Speed);
    }
    if(configSpeed < Speed){
      I -= Ki*(Speed - configSpeed);
    }
    millis1 = millis();
  }
  if(I < -255){ // ограничение по минимуму
    I = -255;
  }
  if(I > 255){ // ограничение по максимуму
    I = 255;
  }
}




















//
//uint8_t getSpeed(volatile uint8_t impulsSpeed)
//{
//uint32_t freqLongInt = 0;
///*
//Частота = 1 / (текущее время - время предыдущего срабатывания)
//Приведение к оборотам в минуту:
//RPM = 60 х 1.000 х Частота (для униполярного датчика 1 магнит). Для биполярного - разделить на 2 (два магнита)
//*/
//if (microsold != 0) {//если датчик сработал хотя бы один раз, то время будет не нулевое. Если не сделать такую проверку, то первые 10 секунд после старта частота вращения будет 1 об/мин, а должно быть 0
//freqLongInt = (60 * (1000 / (millis() - microsold))) / 1; //this->sensorType;
//} 
//else {
//freqLongInt = 0;
//}
//if (impulsSpeed > 0) {
//microsold = millis();
//impulsSpeed = 0;
//}
//
//Speed = byte(freqLongInt);
//f_isrSpeed = 0;
//return  Speed;
//}


[/code]

 

Iwan73
Offline
Зарегистрирован: 02.11.2013

Time.ino

[code]
//=== прерывание каждую секунду =======================
void timerIsr() {
  secundIsr++;                               //счетчик секунд в прерывании
  isrRotateTime = isrRotateTime + secundIsr; //время в секундах проработаное в цикле вращения
  Time();                                    // вызываем обработчик времени
}
//==== обработчик времени ( обратный отсчет )===================
void Time() {
  secund = Sec - secundIsr;                           // подсчет секунд из минуты (60 - secundIsr )
  if ((isrRotateTime + (minut*60000)) < RotateTime) { //если время отработанное  цикле меньше чем запрограмированое 
    if (secund == 0) {                                
      Sec = 59;                            
      secundIsr = 0;
      minut--;
      secund = 59;
    }
  }
  if ((isrRotateTime +(minut*60000)) >= RotateTime) {//если время отработанное  цикле боьше или равно чем запрограмированое
    minut = 0;
    secund = 0;
    isrRotateTime = 0;
    EndZikl = 1;
  }
  updateWorkLcd(zikl, rotate_direction);             // все на дисплей
}


[/code]

arrays.h

[code]
#define FILENUMB 19             //общее количество файлов в массив
#define FILEREW  3              // количество байт на каждый файл
#define T_FOLDER         0 //папка, содержит список файлов
#define T_APP            4 //Приложение
#define T_CONF           5 //Конфиг
#define addr_Save_SensVibro    0
#define addr_Save_SensTok      2
#define addr_Save_RotateZikl_1 4
#define addr_Save_TimeZikl_1   6
#define addr_Save_RotateZikl_2 8
#define addr_Save_TimeZikl_2   10
#define addr_Save_RotateZikl_3 12
#define addr_Save_TimeZikl_3   14
#define addr_Save_ACCELERATED  16
#define addr_Save_BRAKE        18
#define CONF_LIM_MAX   255 // максимальное значение лимитированного конфига - 1 байт 
/*===============================================================
  Массив: fileStruct
  Содержит: Файлы различных типов
  Каждый файл описывается тремя элементами:
  <type>[parameter 1][parameter 2]
  type - тип файла.
  параметры файла зависят от его типа:
  ---------------------------------------------------------------------
  T_FOLDER - папка, содержит список файлов
  parameter 1 - Номер первого файла в массиве
  parameter 2 - Количество файлов в папке минус 1
  ---------------------------------------------------------------------
  T_APP - Приложение
  parameter 1 - Не используется
  parameter 2 -номер вызываемого приложения
  ---------------------------------------------------------------------
  T_CONF - Конфиг
  parameter 1 - Не используется
  parameter 2 -номер конфига

  =====================================================================*/
//массив fileStruct размером [FILENUMB*FILEREW] 20*3=60 байт
const char fileStruct[FILENUMB * FILEREW] PROGMEM =
{
  T_FOLDER, 1, 1,                   //0 "Меню" заголовок,стартовая папка. Содержит в себе файлы 1-2. Вложенные файлы должны идти подряд по структуре!
  T_FOLDER, 3, 2,                   //1 "Настройки" - Папка выбора папок конфигов. Содержит файлы с 3 по 5
  T_APP, 0, 2,                      //2 "Информация" - приложение. Выводит системную информацию
  T_FOLDER, 9, 1,                   //3 "Датчики" - Папка выбора конфигов для датчиков. Содержит в себе варианты с 9 по 10
  T_FOLDER, 17, 1,                  //4 "Двигатель" - Папка выбора управления двигателем. Содержит файлы с 17 по 18
  T_FOLDER, 6, 2,                   //5 "Режимы" - Папка выбора папок для циклов. Содержит файлы с 6 по 8
  T_FOLDER, 11, 1,                  //6 "Цикл 1" - Папка выбора конфигов для 1 цикла. Содержит файлы с 11 по 12
  T_FOLDER, 13, 1,                  //7 "Цикл 2" - Папка выбора конфигов для 2 цикла. Содержит файлы с 13 по 14
  T_FOLDER, 15, 1,                  //8 "Цикл 3" - Папка выбора конфигов для 3 цикла. Содержит файлы с 15 по 16
  T_CONF, addr_Save_SensVibro, 0,   //9 "Датчик биений" - булев конфиг T_CONF - Конфиг,parameter 1 - сдвиг стартовой ячейки EEPROM относительно номера родительской папки выбора.,parameter 2 -номер конфига=0
  T_CONF, addr_Save_SensTok, 0,     //10 "Датчик тока" - булев конфиг T_CONF - Конфиг,parameter 1 - сдвиг стартовой ячейки EEPROM относительно номера родительской папки выбора. 2 -номер конфига=0
  T_CONF, addr_Save_RotateZikl_1, 1,//11 "Обороты 1" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_TimeZikl_1, 1,  //12 "Время 1" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_RotateZikl_2, 1,//13 "Обороты 2" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_TimeZikl_2, 1,  //14 "Время 2" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_RotateZikl_3, 1,//15 "Обороты3" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_TimeZikl_3, 1,  //16 "Время 3" - лимитированный конфиг оборотов. Номер конфига - 1.
  T_CONF, addr_Save_ACCELERATED, 0, //17 "Плавный пуск" - булев конфиг
  T_CONF, addr_Save_BRAKE, 0        //18 "Торможение" - булев конфиг
};

/*===================================================================
  Массив: fileNames
  Содержит: имена файлов. Файлы следуют в том же порядке, что и в предыдущем массиве
  =====================================================================*/
static const char file_0[] PROGMEM =  "\x9C\xB5\xBD\x8E";                        //0 "Меню" заголовок,стартовая папка
static const char file_1[] PROGMEM =  "\x9D\xB0\x81\x82\x80\xBE\xB9\xBA\xB8";    //1 "Настройки"- папка конфигов.
static const char file_2[] PROGMEM =  "\x98\xBD\x84\xBE\x80\xBC\xB0\x86\xB8\x8F";//2 "Информация"- приложение. Выводит системную информацию
static const char file_3[] PROGMEM =  "\x94\xB0\x82\x87\xB8\xBA\xB8\x20\x20";            //3 "Датчики"- Папка выбора конфигов датчиков
static const char file_4[] PROGMEM =  "\x94\xB2\xB8\xB3\xB0\x82\xB5\xBB\x8C\x20\x20";    //4 "Двигатель" - Папка выбора управления двигателем
static const char file_5[] PROGMEM =  "\xA0\xB5\xB6\xB8\xBC\x8B";                //5 "Режимы" - Папка выбора папок циклов
static const char file_6[] PROGMEM =  "\xA6\xB8\xBA\xBB\x20\x31";                //6 "Цикл 1" - Папка выбора конфигов для 1 цикла.
static const char file_7[] PROGMEM =  "\xA6\xB8\xBA\xBB\x20\x32";                //7 "Цикл 2" - Папка выбора конфигов для 2 цикла.
static const char file_8[] PROGMEM =  "\xA6\xB8\xBA\xBB\x20\x33";                //8 "Цикл 3" - Папка выбора конфигов для 3 цикла.
static const char file_9[] PROGMEM =  "\x94\xB0\x82\x87\xB8\xBA\x20\xB1\xB8\xB5\xBD\xB8\xB9";//9 "Датчик биений" - булев конфиг. Номер конфига  - 0
static const char file_10[] PROGMEM = "\x94\xB0\x82\x87\xB8\xBA\x20\x82\xBE\xBA\xB0";//10 "Датчик тока" - булев конфиг. Номер конфига  - 1
static const char file_11[] PROGMEM = "\x9E\xB1\xBE\x80\xBE\x82\x8B";            //11 "Обороты" - лимитированный конфиг оборотов
static const char file_12[] PROGMEM = "\x92\x80\xB5\xBC\x8F";                    //12 "Время" - лимитированный конфиг оборото
static const char file_13[] PROGMEM = "\x9E\xB1\xBE\x80\xBE\x82\x8B";            //13 "Обороты" - лимитированный конфиг оборотов
static const char file_14[] PROGMEM = "\x92\x80\xB5\xBC\x8F";                    //14 "Время" - лимитированный конфиг оборотов
static const char file_15[] PROGMEM = "\x9E\xB1\xBE\x80\xBE\x82\x8B";            //15 "Обороты" - лимитированный конфиг оборотов
static const char file_16[] PROGMEM = "\x92\x80\xB5\xBC\x8F";                    //16 "Время" - лимитированный конфиг оборотов
static const char file_17[] PROGMEM = "\x9F\xBB\xB0\xB2\xBD\x8B\xB9 \xBF\x83\x81\xBA";//17 "Плавный пуск" - булев конфиг.
static const char file_18[] PROGMEM = "\xA2\xBE\x80\xBC\xBE\xB6\xB5\xBD\xB8\xB5";//18 "Торможение" - булев конфиг.

const char* const fileNames[]PROGMEM = {
  file_0,//указатель на "МЕНЮ" заголовок,стартовая папка
  file_1,//указатель на "Настройки" - папка конфигов.
  file_2,//указатель на "Информация" - приложение которое Выводит системную информацию
  file_3,//указатель на "Датчики" - папка конфигов.
  file_4,//указатель на "Двигатель" - Папка выбора управления двигателем
  file_5,//указатель на "Режимы" - Папка выбора папок циклов
  file_6,//указатель на "Цикл 1" - Папка выбора конфигов для 1 цикла.
  file_7,//указатель на "Цикл 2" - Папка выбора конфигов для 2 цикла.
  file_8,//указатель на "Цикл 3" - Папка выбора конфигов для 3 цикла.
  file_9,//указатель на "Датчик биений" - булев конфиг.
  file_10,//указатель на "Датчик тока" - булев конфиг.
  file_11,//указатель на "Обороты 1" -лимитированный конфиг оборотов
  file_12,//указатель на "Время 1" -лимитированный конфиг времени
  file_13,//указатель на "Обороты 2" -лимитированный конфиг оборотов
  file_14,//указатель на "Время 2" -лимитированный конфиг времени
  file_15,//указатель на "Обороты 3" -лимитированный конфиг оборотов
  file_16,//указатель на "Время 3" -лимитированный конфиг времени
  file_17,//указатель на "Плавный пуск" - булев конфиг.
  file_18,//указатель на "Торможение" - булев конфиг.
};
//=====================================================
static const char strRegul[] PROGMEM = "\x80\xB5\xB3\x83\xBB\x8F\x82\xBE\x80"; // 0 "регулятор";
static const char strRotate[]PROGMEM = "\xBE\xB1\xBE\x80\xBE\x82\xBE\xB2";   // 1 "оборотов" ;
static const char strMedogon[]PROGMEM = "\xBC\xB5\xB4\xBE\xB3\xBE\xBD\xBA\xB8"; // 2 "медогонки";
static const char strName[] PROGMEM  = "\x9F\xA7\x95\x9B\x9A\x90";           // 3 "ПЧЕЛКА";
static const char strStop[] PROGMEM  = "\xA1\xA2\x9E\x9F";                   // 4 "СТОП";
static const char strSave[] PROGMEM  = "\xA1\xBE\x85\x80\xB0\xBD\xB5\xBD\xBE"; // 5 "Сохранено";
static const char strCurrent[]PROGMEM = "\x97\xBD\xB0\x87\xB5\xBD\xB8\xB5\x20"; // 6 "Значение:";
static const char strMin[] PROGMEM   = "\x9C\xB8\xBD\xB8\xBC\x83\xBC\x20";   // 7 "Минимум:";
static const char strMax[] PROGMEM   = "\x9C\xB0\xBA\x81\xB8\xBC\x83\xBC\x20"; // 8 "Максимум:";
static const char strNo[]  PROGMEM   = "\x9D\xB5\x82";                       // 9 "Нет";
static const char strYes[] PROGMEM   = "\x94\xB0\x20";                       // 10 "Да ";
static const char strSensEnable[]PROGMEM = "\x94\xB0\x82\x87\xB8\xBA\x20\xB2\xBA\xBB\x20\x20"; // 11 "Датчик вкл";
static const char strEnable[] PROGMEM = "\x92\xBA\xBB\x8E\x87\xB8\x82\x8C\x20"; // 12 "Включить:";
static const char strSetting[]PROGMEM = "\x9D\xB0\x81\x82\x80\xBE\xB9\xBA\xB0"; // 13 "Настройка" ;
static const char strDisab[]PROGMEM  = "\x92\x8B\xBA\xBB";                   // 14 "Выкл";
static const char strEnab[] PROGMEM  = "\x92\xBA\xBB\x20";                   // 15 "Вкл ";
static const char strZikl[] PROGMEM  = "\xA6\xB8\xBA\xBB";                   // 16 "Цикл";
static const char strEnd[] PROGMEM   = "    \xB7\xB0\xB2\xB5\x80\x88\xB5\xBD    "; // 17 "завершен";
static const char strNext[] PROGMEM  = "\x81\xBB\xB5\xB4\x83\x8E\x89\xB8\xB9";   // 18 "следующий";
static const char strTemperatura[]PROGMEM = "\xA2\xB5\xBC\xBF\xB5\x80\xB0\x82\x83\x80\xB0"; // 19 "температура";
static const char strMeny[] PROGMEM  = "\x9C\x95\x9D\xAE";                   // 20 "МЕНЮ";
static const char strPusk[] PROGMEM  = "\x9F\xA3\xA1\x9A";                   // 21 "ПУСК";
static const char strDat_bien[] PROGMEM  = "\x94\xB0\x82\x2e\xB1\xB8\xB5\xBD\xB8\xB9"; // 22 "Дат.биений";
static const char strDat_Tok[] PROGMEM  = "\x94\xB0\x82\x2e\x82\xBE\xBA\xB0"; // 23 "Дат.тока";
static const char strPlawPusk[] PROGMEM = "\x9F\xBB\xB0\xB2\x2e\xBF\x83\x81\xBA";//24 "Плав.пуск"
static const char strTormoz[] PROGMEM = "\xA2\xBE\x80\xBC\xBE\xB7";//24 "Тормоз"

//============ массив указателей строквых массивов ========================================
const char* const StringNames[]PROGMEM = {
  strRegul,       // 0 указатель на "регулятор"
  strRotate,      // 1 указатель на  "оборотов"
  strMedogon,     // 2 указатель на  "медогонки"
  strName,        // 3 указатель на  "ПЧЕЛКА"
  strStop,        // 4 указатель на  "СТОП"
  strSave,        // 5 указатель на  "Сохранено";
  strCurrent,     // 6 указатель на  "Значение:";
  strMin,         // 7 указатель на  "Минимум:";
  strMax,         // 8 указатель на  "Максимум:";
  strNo,          // 9 указатель на  "Нет";//
  strYes,         // 10 указатель на "Да ";//
  strSensEnable,  // 11 указатель на "Датчик включен";
  strEnable,      // 12 указатель на "Включить:";
  strSetting,     // 13 указатель на "Настройка" ;
  strDisab,       // 14 указатель на "Выкл";
  strEnab,        // 15 указатель на "Вкл ";
  strZikl,        // 16 указатель на "Цикл";
  strEnd,         // 17 указатель на "завершен";
  strNext,        // 18 указатель на "следующий";
  strTemperatura, // 19 указатель на "температура";
  strMeny,        // 20 указатель на "МЕНЮ" заголовок кнопки
  strPusk,        // 21 указатель на "Пуск" заголовок кнопки
  strDat_bien,    // 22 указатель на "Дат.биений"
  strDat_Tok,     // 23 указатель на "Дат.тока"
  strPlawPusk,    // 24 указатель на "Плав.пуск"
  strTormoz,      // 25 указатель на "Тормоз"
};

/*===================================================================
  Массив: configsLimit
  Содержит:Параметры конфигов:
  булев, только  Да/Нет
  Числовой, С ограничением или бел.
  Каждый конфиг описывается тремя значениями:
  1. Минимальное значение
  2. Максимальное значение
  3. Номер ячейки EEPROM
  Если максимальное значение ==0 - Это безлимитный конфиг
  Если максимальное значение == 1 -Это булев конфиг
  Иначе это лимитированный конфиг, как сверху так и снизу.
  Учитывайте, что безлимитный конфиг занимает две ячейки EEPROM, а динамический конфиг занимает, для
  безлимитного конфига = 2 ячейки EEPROM на количество копий динамического конфига
  Для лимитированного конфига = 1 йчейка EEPROM на количество копий динамического конфига
  =====================================================================*/
const byte configsLimit[]PROGMEM = {
  0, 1, 0,  // 0 Булев конфиг. Да/Нет
  0, CONF_LIM_MAX, 0, // 1 Лимитированный конфиг.  Числовое значение может быть только между 10 и 255
  0, 0, 2  // 2 нелимитированный конфиг.
  // 0,0,20,    // 3 конфиг для динамической папки.
};
[/code]

Config.ino

[code]
//=========== чтение данных о конфиге
void ConfigReadLimits(byte cnumb) { //данные о конфиге. Входной парамтер - номер конфига СИСТЕМНЫЙ
  for (int i = 0; i < 3; i++)
    configLim[i] = pgm_read_byte(&configsLimit[cnumb * 3 + i]); //Считываем числовые значения
}
//=========== выбор конфига ============================
/*
  #define MAXDEPTH 4              // максимальная глубина внутренного раздела
  byte brCrumbs[MAXDEPTH][3];    //массив хлебных крошек. Используется для
  //навигации, особенно с динамическими и выбор файлов.
  //двухмерный 1 байт - номер программы, второй - положение курсора
  brCrumbs[level][0] - хранить номер последнего выбранного файла. обычно онемевшей из папки файл
  brCrumbs[level][1] - хранить текущую позицию списка файлов в папке
  brCrumbs[level][2] - Флаг. Используется для считывания количества выбранных файлов
*/
void configSelect(byte cnumb, byte conftype)//тип файла 0x02
{
  byte Conf_Shift = 0;
  workType = 2;             // Флаг активности модуля
  ConfigReadLimits(cnumb);  // читаем данные о конфиге. Входной парамтер - номер конфига
  configLim[3] = ConfigRead(fileData[1]);
  ConfigDispSetup(conftype);
  while (workType == 2) {  // запускаем программу редактирования конфигов
    ConfigDispStatic();    // обновили информацию
    butt = buttonsStat();  // читаем кнопки
    if (butt) {            // если нажали кнопки
      switch (butt)
      {
        case 3:              // кнопка  +
          ConfigUpButton();  // увеличиваем значение
          break;
        case 2:              // кнопка  -
          ConfigDownButton();// уменьшаем значение
          break;
        case 5:              // удержание кнопки стоп сохранение конфига в еепроме и  выход из настройки конфига
          {
            ConfigWrite(fileData[1], configLim[3]);            //записываем 1 байт
            // выводим надпись "сохранено" в строке заголовка
            drawString_1607(1, 0, !color, "                "); //затираем строку
            NameStringGet(5);                                  // считываем из памяти  строку "Сохранено"
            drawString_1607(2, 0, !color, Stringbuffer);       //пишем "Сохранено"
            drawFastVLine(95, 0, 55, color);  //рисуем вертикальную линию периметра слева, а то строки затерли местами периметр
            Update();            //Обновление экрана, загрузка информации на дисплей
            delay(1500);         // чтобы успеть прочитать
              }
           if (Pause == 1){ // если мы в паузе во время исполнения циклов работы  
            workType = 1;        //сбросим флаг зацикливания          
           }
           else {workType = 0; }       //сбросим флаг зацикливания    
          break;
      }
    }
    drawFastVLine(95, 0, 55, color);  //рисуем вертикальную линию периметра слева, а то строки затерли местами периметр
    Update();                         //Обновление экрана, загрузка информации на дисплей
  }
  fileReturn();
}
//==============================================================================================================
void ConfigDispStatic()
{
  char buffString[4];               //объявляем локальный массив который будет использоваться для преобразования значения конфига в строку для вывода
  //=== определяем по 2 байту какой конфиг - булев или с ограничениями ? =============================
  if (configLim[1] == 1) {          //значит что вырианты только да или нет
    NameStringGet(12);              // считываем из памяти  строку "Включить:"
    dispString(1,  Stringbuffer);   // пишем "Включить:" в строку
    if (configLim[3]) {
      /*возможность подключить или отключить датчик  определенный булевым конфигом
        если подключаем датчик то переводим конфиг из булева в лимитированный
        что бы конфиг стал опять булевым нужно просто значение установить в 0
        если датчик подключен сделать конфиг лимитированым */
      if ((conftype == fSensVibro) || (conftype == fSensTok)) {
        if (conftype == fSensVibro) {    // если мы в настройках датчика вибрации
          configLim[1] = MAX_CONF_Vibro; // делаем конфиг лимитным,записываем максимальное значение в configLim[1],что бы вводить зачение вибрации
        }
        if (conftype == fSensTok) {     // если мы в настройках датчика тока
          configLim[1] = MAX_CONF_Tok;  //делаем конфиг лимитным что бы ввести зачение тока двигателя
        }
        NameStringGet(10);              // считываем из Progmem строку "Да "
        drawString_1607(12, ROWSHIFT, color, Stringbuffer); //выводим "Да "
        NameStringGet(11);              // считываем из Progmem строку "Датчик включен"
        dispString(2, Stringbuffer);    // пишем "Датчик включен"
        Update();                       //Обновление экрана, загрузка информации на дисплей
        delay(1500);                    // чтобы успеть прочитать
      }
      if ((conftype == fAccelerated) || (conftype == fBrake)) {
        if (conftype == fAccelerated) {  // если мы в настройках плавного пуска
          ACCELERATED = 1;               // ставим флаг что разрешен плавный пуск
        }
        if (conftype == fBrake) {        // если мы в настройках торможения двигателем
          BRAKE = 1;                     // ставим флаг что разрешено торможения двигателем
        }
        NameStringGet(10);               // считываем из Progmem строку "Да "
        drawString_1607(12, ROWSHIFT , color, Stringbuffer); //выводим "Да "
      }
    }
    else {
      NameStringGet(9);
      drawString_1607(12, ROWSHIFT , color, Stringbuffer); // выводим "Нет"
      if (conftype == fAccelerated) {  // если мы в настройках плавного пуска
        ACCELERATED = 0;               // сбросим флаг,означает что плавный пуск не разрешен
      }
      if (conftype == fBrake) {       // если мы в настройках торможения двигателем
        BRAKE = 0;                    // ставим флаг что не разрешено торможения двигателем
      }
    }
  }
  //=== если лимитированный конфиг ===
  else //if (configLim[1] != 1) {         // если лимитированный конфиг
  {
    if (conftype == fTime) {
      configLim[1] = MAX_TIME_CONF;       // конфиг времени максимальное значение  в минутах
    }
    if (conftype == fRotate) {
      configLim[1] = MAX_Speed;          // конфиг оборотов максимальное значение
    }
    dispString(0, "                ");                                         // затираем всю строку
    dispString(1, "                ");                                         // затираем всю строку
    NameStringGet(6);                                                          // считываем из Progmem строку "Значение:"
    dispString(1, Stringbuffer);                                               // пишем в  строку "Значение:"
    drawString_1607(11, ROWSHIFT , color, itoa(configLim[3], buffString, 10)); //выводим значение конфига из configLim[3]
    dispString(0, "                ");                                         // затираем всю строку
    dispString(2, "                ");                                         // затираем всю строку
    NameStringGet(8);                                                          // считываем из Progmem строку "Максимум:"
    dispString(3, Stringbuffer);                                               // пишем "Максимум:" в  строку
    drawString_1607(11, ROWSHIFT + 2, color, utoa(configLim[1], buffString, 10)); //выводим максимальное значение конфига из configLim[1]
  }
}

//*********************************
void ConfigDispSetup(byte conftype) {
  dispStaticDraw();      // выводим изображение кнопок
  dispHead(buffer);      //пишем заголовок конфига
  ConfigDispStatic();
}



[/code]

скетч использует 14200 байт (44%) памяти устройства

глобальные переменные  1368 байт(66%) динамической памяти,оставляя 680 байт  для локальных переменных.

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

длинный код)) а видео?

Iwan73
Offline
Зарегистрирован: 02.11.2013

Для проверки  на столе сделал такую приблуду

Iwan73
Offline
Зарегистрирован: 02.11.2013

Iwan73
Offline
Зарегистрирован: 02.11.2013

а куда оно цепляется,видео?

 

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

только ссылку например на ютуб

Iwan73
Offline
Зарегистрирован: 02.11.2013

Пока выходные (грипп и нас нашел ) буду разбираться с силовой частью. 

#include <class_BUTTON.h> это титановый велосипед от Клапауций 999.
#include "TimeHelpers.h"       это leshakutils от leshak
 
 
Iwan73
Offline
Зарегистрирован: 02.11.2013

хорошо,попробую.

в файлике Rotate должно быть не

TCCR1B = TCCR1B & 0b11111000 | 0x01; // настраиваем таймер 1 на частоту   31.250 кГц

а 

TCCR1A = TCCR1A & 0b11111000 | 0x01; // настраиваем таймер 1 на частоту   31.250 кГц

и в функции Accelerated()

 TCCR1A = TCCR1A & 0b11111000 | 0x05;             // настраиваем таймер 1 на частоту  30.517578125 Гц

Хотя думаю может подключить библиотеку Arduino PWM Frequency Library v_05 , в ней можно выставить частоту 16-17 кГц. 

Iwan73
Offline
Зарегистрирован: 02.11.2013

видео менюшки   https://youtu.be/pMmMG4qdfsE

barbus
Offline
Зарегистрирован: 06.04.2013

Проект закончен или нет, можете выложить весь проект..

 

Roman0611
Offline
Зарегистрирован: 14.10.2017

Люди есть большой интерес на блок кто может такую вещь собрать 

за оплату

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

Напишите в раздел Ищу исполнителя

IgorOK
Offline
Зарегистрирован: 02.11.2017

Итоги есть? Из тех кто интересовался ранее, если нсть информация, кто-то может поделиться?

Govemail
Offline
Зарегистрирован: 29.10.2018

Можно как то заполучить прошивку вашу или изделие?

SmolkaVlad
Offline
Зарегистрирован: 17.04.2019

Iwan73 Приветствую собрал блок управления на arduino NANO 328P в FLProg. Проверил на стенде - работает.

Если интересует описание и проект могу скинуть

Схемотехника до безобразия простая получилась.

алгоритм АСУ

АСУ построена по функциональной схеме

Где: ЗИ – задатчик интенсивности разгона/торможения. Уставка времени разгона и торможения из основного меню. Выход ЗИ от 0 до 255. На вход подается задание с потенцеометра - в ручном режиме. В автоматическом – работает по предустановленому заданию с основного меню (имеет пять ступеней, с возможностью выбора скорости, направления, времени работы- для каждой ступени).

РС – регулятор скорости. Есть выбор из трех вариантов: 1-прямое управление, без обратной связи по скорости (отключен энкодер).

2. – с использованием PID регулятора. Коэффициенты изменяются с основного меню.

3 – с использованием пропорционального регулятора.

РТ- регулятор тока. Построен на датчике холла  ACS712-30А с выходным сигналом от 0,7В до 4,3В (0А=100ед, 30А=810ед).

В алгоритме предусмотрена аварийная остановка: 1- по превышению тока более 40% от номинального; 2- при отсутствии вращения ротора более 3сек, после подачи задания (стоянка под током).

Имеется защита от противовключения. Т.е., если во время работы в одном направлении, поменять на задание в противоположное, то привод остановится по задатчику интенсивности до 0, а потом разгонится до установленной скорости выбранного направления.

При нажатии стоповой кнопки во время работы – снимается все задания на привод и двигатель останавливается выбегом.

Работа двигателя не возможна когда экран включен на основное меню.

Управлять приводом в ручном режиме можно как, сначала задать нужную скорость, а потом нажать кнопку пуск (привод разгонится по ЗИ) или при 0 задании нажать «ПУСК» и управлять потенцеометром.

В автоматическом режиме после перевода на «авт» и нажатия кнопки «ПУСК» -работа будет по заданной программе, если перевести переключатель на ручной режим, то отчет выполнения программы встанет на паузу, если нажать «СТОП», то программа остановится и обнулится до начала цикла.

Govemail
Offline
Зарегистрирован: 29.10.2018

хочу переделать свою медогонку на электро, продайте мне прошивку SmolkaVlad

SmolkaVlad
Offline
Зарегистрирован: 17.04.2019

Govemail.   Схема работает с двигателем постоянного тока. У меня стоит 12В, 90Вт. Драйвер на 43А до 27В BTS7960B, но нагружать более 100Вт без принудительного охлажтения не советую. Если все устраивает дай координаты куда выслать, так отдам. Если получится усовершенствовать жду от тебя предложения.

Govemail
Offline
Зарегистрирован: 29.10.2018

спасибо! почта govemailkn@gmail.com

Pavel57
Offline
Зарегистрирован: 19.04.2019

SmolkaVlad  день добрый. Можно и мне глянуть на ваш труд. Есть ардуино уно, шилд LCD - 16х2, драйвер двигателя BTS7960-43A-H, двигатель 12В - 90Вт и 110Вт, энкодер. 

 

SmolkaVlad
Offline
Зарегистрирован: 17.04.2019

Pavel57 давайте адрес, драйвер должен тянуть 110Вт, если что поставите дополнительно охлождение или другой радиатор, у меня использован датчик тока, так что можно и ограничить мощьность.

SmolkaVlad
Offline
Зарегистрирован: 17.04.2019

https://cloud.mail.ru/public/2Cfg/2mnNvXnbk

Сылка в облаке на файлы

Pavel57
Offline
Зарегистрирован: 19.04.2019

Благодарю. Смотрю, изучаю.

Pavel57
Offline
Зарегистрирован: 19.04.2019

 SmolkaVlad напишите на почту ser-dyuk@list.ru.