ARDUINO для детей: "как Лего"

Насколько полезна тема и стоит ли расписывать детально далее?

  • 2. Может кому-то и полезно, лично мне нет, я уже вырос, обучать? Есть кружки, школы..

    Голосов: 0 0.0%
  • 3. Может кому и полезно, но сам повторять не собираюсь, полно готовых изделий.

    Голосов: 0 0.0%
  • 4. Фигня и мазохизм. Проще купить готовое, тема бесполезна.

    Голосов: 0 0.0%
  • 5. Тема вредна, учит делать своими руками - кто покупать будет то, что я продаю?

    Голосов: 0 0.0%

  • Всего проголосовало
    4
  • Опрос закрыт .
09.06.2019
57
5
8
#1
Здравствуйте, всем!
Хочу перенести сюда (в т.ч. и с других ресурсов по Ардуино) свои наработки по обучению сына робототехнике и Ардуино в частности в варианте "как Лего".
Большая просьба: пока не комментировать тему, материала очень много, буду выкладывать постепенно и переносить с других ресурсов, по мере наличия времени.

Назначение проекта: для обучения детей от 8 до 12 лет: "Ардуино как Лего"
Основные особенности проекта:

а) отказ от самостоятельного (детьми) изготовления крепежных изделий и их замена на Лего-детали, в основном Лего - техник.
б) Применение визуального программирования (по типу лего-скретч) - Ardublock (github:: tawelli) в качестве расширения Arduino IDE.

.. позже проект "вырос" до изготовления собственных плат на базе Atmega128a, ATmega2560 с дополнением их платой расширения ОЗУ (прямой доступ к массивам до 32кбайт) на 512 килобайт сегментированной памяти и доработку avr-gcc линкера и ИДЕ (1.8.5) по аналогии с проектом Arduino MegaCore для полноценного использования всего объема расширенной памяти в программах (статическое выделение места под массивы до 32кб непрерывного адресного пространства).
Есть герберы и опыт изготовления плат с заказом в Китае.
Но, это "позже".
 
09.06.2019
57
5
8
#2
Как понятно из названия темы, ничего особо специального тут нет. Решение банальное - просто блоки Ардуино загоняем в "лего-детали" и получаем готовый, широко разрекламированный и достаточно удобный комплект для быстрой сборки изделий: "придумал - собери - проверь".
Первые "успешные" опыты выглядели так:
MegaCorps1-1top.JPG
(* не понял как можно уменьшить размер картинки, ну да ладно *)

Плата Ардуино Мега 2560 рев.3 аккуратно вставлена в "корпус из лего-деталек". Сверху смонтирован расширительный шилд для УНО, снизу добавлен такой же лего-блок для батареек.

Недостатки такого подхода:
1. Получается очень тяжелый монстр, хотя и всё остается полностью разбираемым и целым.
2. Катастрофический недостаток линий земли и питания для подключения периферии просто требует применения расширительного шилда.

Второй релиз платы Мега2560:
Last_Blocks-3.JPG
Тут лего детали уже склеены как между собой так и с платой, что позволило существенно снизить общий вес, и сделать конструкцию модульной.
На снимке: сама плата с виде "слоя", справа от неё "блок питания" из 8-и гнезд "АА", с отводами от 4, 5, 6 и 8 гнезда на разное питание модулей. Материал БП - картон для детского труда + снятые с детских игрушек кнотакты для батареек. Тут момент изготовления, окончательно к нему подклеен разьем и 2 кнопки с фиксацией для включения 4-х напряжений попарно.
Выполнено сыном примерно в его 9 лет.
И сверху - вариант компоновки драйверов моторов на базе двух шилдов L298N со снятыми радиаторами.

Ну и "применение" этого пирога:
IMG_0070.JPG
Тут иной вариант компоновки драйверов моторов + оформление дисплея LCD 1602 I2C.

Недостатки все теже: высокий вес Ардуино-кирпича и снова нехватка линий земли и питания для полноценной сборки.

Ещё пример построения робота, движущегося по линии с объездом препятствий сыном (около 9 лет)
 
Последнее редактирование:
09.06.2019
57
5
8
#3
Окончательно, Мега2560 была дополнена отсутствующими ножками ( +16пинов ):


И окончательно оформлена примерно так:

На боковых сторонах были доклеены угловые крепежные детали шириной 1/2 (из тонких пластинок, фото не нашел пока). Все лего-крепления тут условно соответствуют расположению креплений блока Lego EV3, но применить на практике такое совпадение нам так ни разу и не пригодилось. Оказалось что старый блок был значительно удобней. :) Существенного уменьшения габаритов и веса - также не получилось, что и послужило переходом на платы Ардуино НАНО.

Здесь только Ардуино Мега2560 с дополнением 16-и ножек и блок питания на 3-х банках Li-ion 18650 Samsung 2400mah. В среднем одной зарядки хватает примерно на неделю запусков роботов в процессе отладки и настройки по 1.5-2.5 часа в день. :)
 
09.06.2019
57
5
8
#4
Ардуино НАНО "как Лего".

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


На фото ниже также можно обнаружить самодельный драйвер моторов на базе АО4606 (до 30в, 5А на мотор), блок питания на базе 2х Li-ion 18560, сами моторы, а также датчики.
Обо всем этом - постепенно выложу тут, по мере наличия времени.





Применение НАНО как Лего для прошивки прототипа платы на ATmega128a (ещё без БП):
Прошивка_Нано-128А.JPG
Таких плат НАНО было изготовлено 5шт, с небольшими отличиями: изменения в расположении второго питающего контакта от аккумуляторного блока + добавление кнопки включения платы со сдвигом "взад" левого ряда контактов, если смотреть со стороны USB-разьема.

Прототип платы на ATmega2560-16au изготовленный вручную методом ЛУТ. Первый вариант со стабилизатором питания на базе AMS1117-5.0 из-за слабости стабилизатора был переработан и уже он отдавался китайцам на изготовление. Тут оказался "битым" сам микроконтроллер: память flash имела участок около 1кб плохо прошиваемый и почти в самом начале. Успешно заливать, кроме блинка удавалось очень мало чего.

Mega2560-blink.JPG
 
Последнее редактирование:
Симпатии: Понравилось EandV
09.06.2019
57
5
8
#6
Спасибо, интересно. Но у этого проекта уже давно есть название: "Ардуино как Лего".

Часть 2 "Моторы".

Как ни странно, но в нашем Лего дома увы .. не оказалось поначалу никаких моторов. То, что было в первом наборе "сервомотор SG90 + шаговик" в данную категорию никак не попадало, т.к. мы планировали делать разного рода машинки.

Поэтому были закуплены Downsol strong motor with encoder (12в, 8000rpm, 0.07a - 0.4a, 45g*cm) в количестве 10шт. Под спойлером их внешний вид, то что они представляют себя "изнутри", и то что получилось собрать из них (+разбор плохих детских моторчиков этого же класса: DC-130-motor)
и итоговый мотор-редуктор (примерно после года эксплуатации и нескольких соревнований, в т.ч. общероссийского уровня)


IMG_9102.JPG

IMG_9141.JPG

IMG_9107.JPG

IMG_2829.JPG

Что было переделано:
1. разбор якоря + наращивание его пластин (они по 0.5мм толщиной) с 12шт до 18шт на якорь. Дело в том что ширина статорных постоянных магнитов около 10мм, что позволяет иметь якорь до 18 пластин, дальше нет смысла только рост веса. Можно впихнуть и 20 (как на фото), но придется обойтись без передней и задней накладки и слегка укоротить коллектор, что оказалось не так уж и сложно на самом деле, чем казалось вначале. Часовая лупа в глаз, канцелярский нож и твердая рука - в помощь. :)
2. Снят оптический энкодер и его диск с якоря. Вал якоря укорочен до разумного размера. Ось просто взята от другого "игрушечного" моторчика.
3. Намотка якоря "синхронно вручную в три обмотки виток к витку". Опять же под той же часовой лупой.
Тут надо пояснить: дело в том, что все покупные (заводские) моторчики мотаются автоматически и последовательно - сначала первая обмотка, потом вторая и т.д. Это не позволяет достичь даже 60% заполнения каркаса - просто не влезет последняя обмотка - не останется места для пропихивания провода.
Поэтому, вручную мотали (2 моторчика перемотал и сын, примерно в теже 9 лет самостоятельно) сразу все обмотки "одновременно": сначала по 40-60 витков на каждую (сколько влезает не мешаясь), потом по 2 слоя (туда-обратно) виток к витку, по очереди все катушки. Каждая такая пара слоев проклеивалась цианакрилатом (клей "Секунда").
В итоге получается, хоть и не разборный, но очень центрированный якорь с почти 100% заполнением.

Параметры якоря в 18 пластин: провод ПЭВ-2 0.125 около 200витков на катушку (5м30см на обмотку? не помню, не нашел записи), сопротивление обмотки всего якоря (треугольник) - около 7 ом.
Итого параметры мотора в корпусе Downsol strong motor (очень сильная магнитная система) на 8.4в (2хLi-ion):
обороты холостого хода 11400 в минуту, ток ХХ - 74мА(!), останавливающий момент близок к 100гр*см при токе КЗ около 1.2А
Собственно этих данных достаточно, чтобы нарисовать его механическую характеристику и получить КПД за 60% :)

Они были установлены в этом "синем" мотор-редукторе с передаточным отношением 30:1 и показали себя с самой лучшей стороны, что видно по потертости корпуса на фото. До сих пор это лучшее что у нас есть. Механическая мощность незначительно превышает Lego EV3 моторы.
 
Последнее редактирование:
09.06.2019
57
5
8
#7
Прочие наши моторы и моторчики. Как легко применить Лего моторы в Ардуино.

IMG_2826.JPG
IMG_2824.JPG

LargeMotor.JPG

motorReductors1.jpg
Первые 2 фото - попытка изготовить мотор-редукторы со сменным редуктором и/или мотором. На нижнем фото - Downsol strong motor with encoder что называется "в чистом виде", то есть с энкодером. Пробная эксплуатация, хоть и показала что энкодер, имеющий по 32 полосы "черное/белое" и сидящий на валу мотора, хоть и генерирует большое количество прерываний, но вполне можно одновременно применять 2 мотора.
В конце-концов от сменных моторов и редукторов отказались и в эксплуатации остались только "красные" мотор-редукторы с передаточным числом 40:1 На фото выше они присутствуют.

На третьем фото можно увидеть простое решение как применить Лего Power Function Large motor в Ардуино. Разрезаем провод мотора и впаиваем разьем. :)

(* найти фото medium motor для Ардуино *)

На нижнем фото - наш самый мелкий мотор-редуктор с передаточным числом 64:1 :) Там же - разные попытки изготовления первых "сборных конструкций" мотор-редукторов (2015г). Голый якорь - тот самый что перематывал сын в свое время самостоятельно.

..продолжение следует .. (мотор-редукторы на базе N20, мотор-редуктор со сменными шестернями и встроенным энкодером а-ля EV3 средний мотор)
 
Последнее редактирование:
09.06.2019
57
5
8
#8
Самодельный драйвер 2-х DC-моторов 6-30в х 5(20)А. Схема электрическая, svg для самостоятельного изготовления методом ЛУТ платы на 2 или 4 мотора. Описание, особенности работы с драйвером.

DC-motor12v_1.png
Электрическая схема (кажется эта - уточню по разводке) драйвера на 1 DC-мотор. Основная идея - сократить количество управляющих выводов и тут их всего 2.
Dir - направление вращения "вперед/назад" и Pwm - скорость вращения (ШИМ сигнал).
Полумост АО4606 согласно даташиту держит до 30в напряжения и терпит до 5А непрерывного тока через себя. Импульсно способен терпеть до 20А. Его низкая стоимость (брал 50шт по 5.5руб) и определила выбор.
Описание работы:
Транзисторы VT1, VT2 - управление p-канальной частью полумостов. В их стоках стоят делители, ограничивающие напряжение на истоках до допустимых 20в, при питающем в 30в на моторах (AVCC). Это ограничивает нижний предел питающего моторы напряжения до 6в для уверенного открытия верхних плеч полумостов. (таки схема не совсем эта..)
Номиналы резисторов в делителях правильные 1.2кОм + 2.4кОм вместо указанных. Это для рассеиваемой на них мощности в 250мВт.
Транзистор Q1 - инвертор, разрешающий/запрещающий тот или иной полумост (ШИМ) подачи на нижние плечи. Резисторы R1.3, R1.4 - токоограничивающие на выходах схемы "2И" до допустимых 40мА.

Программирование:
Переключение направления вращения, для избежания сквозных токов через полумост, следует производить только при отключенном входе Pwm (=0) с задержками около 6 микросекунд "до" и "после" перевода Pwm в низкий уровень. Собственно время рассасывания зарядов на верхних плечах около 2мксек, но для надежности у нас делаются задержки в 6мксек.
Переключать направление вращения при включенном сигнале Pwm - гарантия сквозных токов, которые тут могут достигать до 36-40А с шансом вывести АО4606 из строя.

Недостаток:
Драйвер не имеет режима "короткозамкнутый ротор", что однако не мешает нам применять его и очень широко в своей практике.
Также нет "защиты по току", но для наших моторов с токами потребления до 3А оно оказалось и не нужным.

Разводка платы сделана для пары моторов для полного использования микросхемы 2И и выполнена на одном слое так, что допускается травление двухстороннего варианта, что в итоге дает драйвер на 4 мотора (есть и те и эти, дополню фотками)
Выложил svg для ЛУТ в виде архива внизу поста.

Выложил под спойлер последнюю версию драйвера на 1 мотор. Плата размером 32х16мм, назначение: размещение драйвера непосредственно на самом моторе DC-130-мотор или больших.
DC-v5 схема.png

DC-v5 pcb.png

Синим цветом обозначены перемычки проводом (5-11 нога + земли). Плата односторонняя, чип "2И" задействован полностью, есть все режимы работы мотора. Управляющих ножек тут уже 3шт. :(

Добавлен архив с svg-файлами для самостоятельного изготовления драйвера 5-й версии. Открываем, печатаем на лазерном принтере (можно накопировать в редакторе несколько изображений рядышком) переносим рисунок платы на фольгу утюгом, снимаем под теплой водой бумажку и травим как обычно. Расположение деталек есть на своем слое отдельно.

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

L293D - предзназначен исключительно для слаботочных моторов (N20, высокоомные варианты DC-130 и пр.), поскольку имеет ограничение на потребляемый ток мотором всего до 0.5А. По сути, для мамомальски рабочего применения этого категорически недостаточно и нами (с сыном) даже не приобретались.

L298N - приобреталось 4шт, делались попытки обрамить их в Лего самыми разными способами (есть примеры в постах), но оказались наверное худшим представителем драйверов в целом. Выходные транзисторы - это "схема Дарлингтона", на которой теряется напряжение "чем больше ток мотора - тем больше" и в критических случаях токов около 2А можно легко терять до 3вольт от поданного питания! Греется он в таких случаях тоже весьма весело. Это практически полностью исключает применение низковольтных моторов с небольшим питанием - потери на драйвере могут даже превышать полезную нагрузку мотора .. финиш.

Monster Shield - отличный вариант, видел в работе. Очень хороший шилд, но его цена .. в общем моя жаба взбунтовалась, и осознав что цена такого драйвера "3 копейки" решил получить приемлемый результат за меньшие деньги, что и удалось.

Другие варианты на мосфетах .. то что видел, тоже "слаботочно", в основном на 1.5 - 2А этого часто "маловато" или "на пределе". Ну и на тот момент (2015-2016гг) мне были или не известны или их ещё не было в продаже.

В любом случае, то что получилось пока для наших целей - "роботостроение детьми" является лучшим с точки зрения "грам на градус" - изготовление крайне простое, программирование требует аккуратности, но и только.

P.S. Добавил архив для изготовления 2 или 4-х моторного драйвера. Схема электрическая (вроде бы все верно), SVG файл для травления под ЛУТ и размещение деталек и перемычек на стороне платы.
Можно использовать 2-х сторонний текстолит, и травить симметрично на обоих сторонах. Окружности обозначают места соединения питания моторов для второй стороны. Контакты управления 3 и 4 мотором - "с обратной стороны" также.
Найду, дополню фотками готовых драйверов.
 

Вложения

Последнее редактирование:
09.06.2019
57
5
8
#9
Наши самодельные ИК-датчики, особенности, учет требований АЦП AVR микроконтроллеров.
IRsensorAnalog1.JPG
IRsensorAnalog2.JPG
ИК датчики из мышей.JPG



IRsensorsSchemas.jpg
ИК-Датчики:

Электрическая схема ИК-датчика на паре фот- свето- диода ИК области достаточно примитивна (схема #1):
а) Типовое подключение ИК-светодиода на ограничение заданного тока (как правило хватает 10-25мА) жестко токоограничивающим резистором или в паре с подстроечным для регулировки яркости освещения. Не думаю, что может вызывать какие-то проблемы. Мы использовали почти везде жесткое ограничение на 10-25мА (в разных по-разному).
б) подключение ИК-фотодиода практически тоже стандартно ("встречно") через "токосьемный" резистор. Для нормальной отдачи приходится ставить очень высокоомный резистор - 510..750кОм. Подбор резистора мы делали увеличением его сопротивления до появления шумового напряжения от темногого тока. после чего уменьшали его номинал на примерно 20%. Темновой ток - для нас это было то, что "видит" фотодиод с одетой трубкой и направленный "в пол" (ок 1м) при дневном освещении без подсветки.
Чем больше номинал резистора - тем выше "отдача", но и тем хуже отношение "сигнал/шум" - каждый выбирает нечто свое.. :)

Первые 2 фото - "сьемный резистор" - М75 (видно на фото). Таких датчиков сделано 2 шт. Оба полностью (в т.ч. и пайка, корпусирование) сделаны сыном в его 11лет. :)

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

Недостаток такого простого решения в том, что выходное сопротивление датчика - это фактически номинал этого токосьемного резистора и оно оказывается далеко выше, чем рекомендовано даташитом на микроконтроллеры Меги, НАНО и т.д. Согласно даташиту он не должно превышать .. 10кОм если мы "хотим что-то измерить". Проверка показала, что ошибка при последовательном измерении нескольких датчиков даже на типовой скорости составляет не менее 30%!

Соответственно (схема #2), была изготовлена серия (8шт) "правильных" датчиков - нижние фото под спойлером: выход токосьемного резистора подключен к ОУ LM358 в режиме "единичного усилителя" и уже его низкоомный выход подключается к микроконтроллеру. Сам ОУ закреплялся клеем поверх пары резисторов, что видны на фото, всё тем же клеем "секунда". :) Получилась очень компактная (не разборная) конструкция а-ля "микросборка", что видно на нижнем фото.

Особенности изготовления:
а) Надо помнить что значительное количество материалов (в т.ч. ПВХ!) практически прозрачны для ИК-излучения, соответственно требуется изоляция ИК-фотодиода от засветки своим же ИК-светодиодом.
Решается размещением изолирующей прокладки (нижнее фото), которая предварительно оклеена .. алюминиевым скотчем. Просто и надежно.
Аналогично, на фотодиод мы одеваем ПВХ трубку из термоусадки, обвернутую тем же алюминиевым скотчем.

Все получается "хорошо" если датчик имеет разницу показаний между "белое" и "черное" больше 10 крат (отношение сигнал/шум > 20Дб).

б) Можно задействовать вторую половину ОУ как усилитель сигнала (дополнение схемы #2 куском #3). Для этого достаточно добавить ему типовой подстроечный резистор для изменения коэффициента усиления. Такая серия (8шт) датчиков у нас тоже была сделана. В итоге оказалось что коэффициент усиления поднять больше 8-9 раз не представляется возможным, но и этого оказалось достаточно, чтобы при токе светодиода в 30мА (по даташиту до 50мА) датчик имел чувствительность около 5метров.

в) изготовление "линейки датчиков" для самобеглых тележек по линии. Собственно все то же самое, но для узкой линии удобно расставлять датчики на расстоянии 2/3 от заявленной толщины линии. В этом случае "поперечная ширина" срабатывания для одного датчика и для пары становятся одинаковыми и ПО несколько упрощается.

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

д) Отдельный вывод "земли" светодиода (если позволяет ток!) позволяет подключать его к управляющему выводу микроконтроллера и подсвечивать датчик только "когда надо" (в процессе замера), что дает возможность дополнительной экономии батарейки. Кроме этого, к нему можно довешивать доп. сопротивления для "снижения яркости" в том или ином применении.
(* найти фотки *)

е) ИК-светодиод вполне может быть применен вместо приемной части. У него несколько хуже шумовые параметры, но отдача явно не хуже, а порой бывает и даже лучше .. так что вполне можно обойтись закупкой только светодиодов. :) Экспериментируйте. По мне так - "все равно", ибо покупалось 2 пакета по 50руб таких пар "свето- фото- приборы на 940нм" .. стоимость по рублю прибор (и это даже ещё и дорого!) :)

P.S. Светодиоды и прочие наши датчики вынесу в отдельные посты ниже.. и так много.
 
Последнее редактирование:
09.06.2019
57
5
8
#10
Библиотека Arhat.h - "расширение Cyberlib"
для эффективного управления на AVR. Дополнение к ИДЕ. Работа с пинами, таймерами, прерываниями и PWM.

Архив распаковывается в папку libraries как обычная библиотека Ардуино. Далее можно смотреть примеры и пробовать.

В силу невозможности выложить код под спойлер, добавил в пост архив со всей библиотекой, в той части в которой она совместима с Ардуино ИДЕ. В связи с чем, возможны некоторые накладки с описаниями, ссылками на отсутствующие куски в коде.

Отсутствует: "свой" код таймера Т0 для функций времени millis(), micros() и пр. Обработчик этого прерывания сильно конфликтует со встроенным обработчиком в Wiring и компилировать скетчи получается только с большим бубном, особенно после выхода версии 1.6.7 кажется. Где в ИДЕ добавлен "builder" и насильно впихивается в скетчи инклуд Arduino.h первой строкой. До той версии, этот инклуд пихался последним, что позволяло отключать его использование. Увы, сие изменение возникло практически сразу после того как отправил разработчикам Wiring код этой библиотеки на посмотреть и улучшить, добавив возможность эффеткивной компиляции в скетчи.

2. Все имена функций тут - разведены (свои) от типовых из Wiring. Одна из первоначальных целей написания как раз и состояла в том, чтобы подключая эту библиотеку можно было существенно уменьшать размеры программ и улучшать их быстродействие от 10 до 100раз.

Библиотека имеет существенное ограничение в использовании - точно такое же как и исходная - Cyberlib, а именно: все пины предполагаются заданными константно или через #define или через префикс const! С номерами пинов в переменных она работать не будет точно также!

В библиотеке есть настроечные файлы для микроконтроллеров ATmega328 и всех его клонов и ATmega2560 и всех его клонов. Тут вторые ограничены пинами типовой платы Arduino Mega 2560 r3. Если у Вас MegaCore или плата на базе всех ножек микроконтроллера, то можно раскомментировать кусок описаний в конце arhat_pins2560.h

1. Работа с пинами:
Есть базовые макросы pin0 .. pin69, pinLed, которые расписаны в файлах настройки на микроконтроллер arhat_pinsXXX.h. Можно дополнить своим файлом по образцу и в arhat.h воткнуть #ifdef также по образцу, для выбора нового файла настройки пинов под Ваш микроконтроллер. Не проблема ни разу, просто муторно. :)
Ногодрыги обеспечиваются макросами: pinModeOut(p), pinModeIn(p), pinOutHigh(p), pinOutLow(p), pinOut(p,v), pinRead(p), pinModePullup(p) -- их смысл понятен из названий. Это в общем-то замена для pinMode(), digitalWrite() и digitalRead() функций Wiring (раньше они так и переименовывались, дабы не править скетчи совсем).

Работа с таймерами
базовая часть, практически не нужна:
pwmGetTimer(p) -- получить номер таймера по номеру его пина. Пин p не "какой попало" а только тот где есть таймер: [2..13,44,45,46, T1C]
pwmGetChannel(p) -- Получить букву(!) канала таймера по его номеру. Пин p - аналогично выше.
timerCount(t) -- Получить регистр текущего счета таймера. Тут уже принимается числовой номер таймера: 8/16 битовые.
timerControl(t,r) -- p:[0,2,[1,3,4,5]], r:[A,B[,C]] -- буква! из пред. макроса. Чтение/Установка регистра ШИМ таймера и канала соответсвенно.
timerCompare(t,r) -- Регистр сравнения таймера. Аналогично передается номер таймера (число) и буква(!) канала.
timerCapture(t) -- Регистр захвата для 16-битных таймеров. p:[1,3,4,5].
timerIMask(t,v,b) -- Регистр маски прерываний. t:[0,2[,1,3,4,5]] v:[OVF,COMPA,COMPB[,COMPC,CAPT]], b:[0,1] -- запретить/разрешить.
timerIFlag(t,v) -- Читать флаг состояния прерывания таймера t:[0,2[,1,3,4,5]] v:[OVF,COMPA,COMPB[,COMPC,CAPT]].
timerISR(t,v) -- make interrupt vector name t:[0,2[,1,3,4,5]] v:[OVF,COMPA,COMPB[,COMPC,CAPT]]. Это если забылось как оно пишется в interrupt.h
prescalerMode(pr) -- Перевод коэффициента делителя в код режима прескалера: 64 --> 3. Удобства для.

Это всё, если надо что-то делать с таймером сильно особенное. На практике не пригодилось практически ни разу. Поскольку это макрокоманды для препроцессора, которые на самом деле формируют текст требуемых вызовов, то тут надо указывать конкретную букву канала таймера, как будто это "имя переменной". На самом деле, она тупо "приклеивается к имени другого макроса, в результате чего получается имя макроса, который уже и компилируется компилятором. Своеобразная система "темплейтов" на Си. :)

Настройка 16-и битных таймеров для прямой работы с серводвигателями:

timerSetServo(t,pwmPinMask) Установка 16битного таймера и его каналов в типовой ШИМ для серво моторов. Ноги на вывод устанавливать отдельно!
pwmSetServo(p1) -- Установка одной ноги на ШИМ и её таймера в типовой режим сервоШИМа: 50Гц. Для последующего канала этого же таймера достаточно вызвать pwmSet(p).

Собственно работа с серводвигателями у нас и оказалась единственно полезным применением этих макросов. Детально читать описания к этим макроопределениям в arhat.h. Их задача - перевод таймера в режим ШИМ с типовой частотой для серв 50Гц и попутно настройка каналов для работы с серводвигателем функцией analogWrite() или макросами ниже напрямую.

Работа с ШИМ:

pwmPinMode(p,m) -- тут p:[2..13,44,45,46,T1C] - ШИМ нога, m[PWM_DISABLE,PWM_TOGGLE,PWM_NORMAL,PWM_INVERSE] - режим ШИМ.
pwmSet(p) -- установить типовой (500Гц) режим ШИМ для заданной ноги (см. выше) "как в Wiring"
pwmOff(p) -- Выключить ногу из ШИМ. Нога остается "на вывод". Весь таймер - не отключаем.
pwmWrite(p,v) -- местный analogWrite(): out PWM p:[2..13,44,45,46,T1C], v:[0..255] with 250kHz. Аналог этой функции 1-й командой.

В архиве есть примеры как это все использовать. Дополнительно там же есть папка doc, в которой лежат даташиты на 328 и 2560 серии микроконтроллеров, дабы не искать, если что-то забылось. Там же есть тест Drystone (оригинал, так и не адаптировал для AVR).
 

Вложения

Последнее редактирование:
09.06.2019
57
5
8
#11
Библиотека Arhat.h - работа с I2C, дисплеем LCD1602.

Типовая библиотека для LCD1602 с этим интерфейсом - LiquidCrystal_I2C к сожалению написана на С++, с виртуализацией работы через монстрообразный стек классов, начиная со Stream и опирается на удивительно плохую реализацию библиотеки Wire.h, несмотря на то, что в недрах самого Wiring есть неплохая реализация аппаратного драйвера этого интерфейса twi.h + twi.c. Все это относится к версии ИДЕ 1.6.4, с которой когда-то начинал, и по анализу стека вызываемых функций и пришел к такому выводу, что и побудило сделать собственную реализацию интерфейса и как пример работы с ним - драйвера дисплея.

Интерфейс I2C (TWI) arhat_twi.h

Ничего сверхесстественного из себя не представляет и является несколько улучшенной компиляцией типовых решений, как из базовых библиотек avr-gcc, так и Wiring. Собственно, там особо и негде что-то выдумывать, все определено даташитом.
Особенности реализации:
1. Реализация полного драйвера (мастер, слейв, многомастерная работа шины и т.д. - есть всё) в минимальном размере. На это делался специальный упор;
2. Реализация препроцессорного выбора какие части обработчика прерываний аппаратного I2C будут использованы в скетче. Дальнейшее уменьшение объема программ на базе I2C - к примеру, для работы только с дисплеем можно указать что предполагается только режим twi_master и размер драйвера будет "ужат" до каких-то 190 байт или около того;
3. Вынос в основной скетч объявлений буфера передачи и приема. Позволяет управлять размером отжираемой ОЗУ по требованиям скетча. Как пример, драйвер дисплея формирует себе буфер передачи всего 4-6 байт и ему этого хватает.
4. Начата (и брошена) реализация контроля ошибок приема/передачи с накоплением "статистики", типа "принято столько", "передано столько", "получено NACK столько" и т.д. для всех режимов работы. Тоже делалось в предположении условной компиляции через препроцессор: надо - включаем накопление статистики работы интерфейса и ошибок. Не сложно дополнить снова.
5. Возможность настройки и переустановки скорости работы интерфейса во всем диапазоне допустимых скоростей аппаратного драйвера от 490Гц до 880кГц включительно. Проверено, на 1Мгц аппаратный драйвер не работает ни на Мега2560 ни на 328 микроконтроллерах.
6. Добавлены примитивы для написания своих функций приема/передачи с произвольным протоколом, согласованным с I2C: прием после передачи без освобождения шины, передача после приема вызова "Всем" и т.д.;
7. Файл как и все остальные широко документирован.

Опция препроцессора (определять до @include "arhat_twi.h")
#define TWI_ON (TWI_MASTER_TX + TWI_MASTER_RX + TWI_IS_SLAVE_RX + TWI_IS_SLAVE_TX + TWI_LOG_ON)

Чего не требуется - можно не суммировать. Константы определены в arhat.h. Последняя - не реализована в этой версии и ни на что не влияет.
Если ничего не указано - будет скомпилирован минимальный драйвер "только мастер на передачу".

Функции библиотеки:
TWI_STATUS_MASK -- возвращает битовую маску для выделения статуса аппаратного I2C из регистра состояния
TWI_STATUS -- возвращает собственно статус аппаратного контроллера из регистра
twi_on() -- включить аппаратный I2C (по умолчанию в Wiring - включен)
twi_off() -- выключить, уменьшает энергопотребление микроконтроллера, если не нужен

Внутреннее применение (примитивы работы с аппаратной частью для обработчика прерываний в основном):
twiSetRate(_twbr, _twsr) -- установка скорости работы записью в регистры аппаратуры
twiStart(ack) -- начать передачу
twiReply(ack) -- продолжить передачу
twiReleaseBus(ack) -- освободить шину
twiSetAddress(address, isGcall) -- сохранить "мой" адрес (режимы мультимастер, слейв)
twiSetMaskAddress(mask) -- сохранить маску моего адреса
twiStop(ack) -- остановить передачу (завершить)

Пользовательские функции и данные (глобалы):
twiSpeed(uint32_t freq) -- настроить скорость интерфеса с автоподбором значений регистров.
twiSetup(uint32_t freq, uint8_t mode) -- в setup() включение интерфейса, настройка пинов (@see arhat_pinsXXX.h)
twiWrite(uint8_t address, const uint8_t * data, uint8_t length) -- передать на адрес с заданного места, заданное количество байт
twiRead(uint8_t address, uint8_t * data, uint8_t length) -- прочитать с адреса в это место заданное количество байт
Отправить на адрес команду (с заданного места, заданной длины) и затем прочитать с него же в заданное место сколько сказано.
БЕЗ(!) освобождения шины:
twiRAW(
uint8_t address // адрес устройства
, uint8_t* command, uint8_t clength // команда и её длина
, uint8_t* data, uint8_t dlength // буфер приема данных и его длина
)

Дополнительные примитивы для создания своих протоколов (применение смотреть в реализации предыдущих функций!)
_twiStartTo(uint8_t address) -- Запуск интерфейса (прием/передача)
_twiStartRe(uint8_t address) -- Передача с рестартом. Запись адреса собеседника и запуск автомата TWI
_twiWaitReady() -- ожидание завершения предыдущей передачи
_twiMT_Buffer(data, length) -- установить буфер передача (мастер)
_twiRX_Buffer(data, length) -- установить буфер приема (мастер, слейв)
_twiST_Buffer(data, length) -- установить буфер передачи в режиме слейв (допускается одновременная работа мастером и слейвом на передачу!)

Глобальные переменные, создаваемые библиотекой:
volatile uint8_t twiMode; // состояние приема/передачи. Можно смотреть из скетча.
volatile uint8_t twiState; // state TWI automat
volatile uint8_t twiSLARW; // "мой адрес" - address for send to (SLARW)

volatile uint8_t twiMT_Count; // остаток байт для передачи мастеров
volatile uint8_t * twiMT_Ptr; // указатель текущего байта внешнего буфера передачи мастером

volatile uint8_t twiRX_Count; // остаток байт для приема мастером/слейвом
volatile uint8_t * twiRX_Ptr; // указатель текущего байта внешнего буфера приема мастером/слейвом

volatile uint8_t twiST_Count; // остаток байт для передачи слейвом
volatile uint8_t * twiST_Ptr; // указатель текущего байта внешнего буфера передачи слейвом

void (* twiHookRestart)(void) = 0; // указатель на функцию перезапуска мастера без освобождения шины (TWI_SEND_STOP)
void (* twiMasterReader)(void) = 0; // указатель на функцию "Master принял данные, куда их?"
void (* twiSlaveReader)(void) = 0; // указатель на функцию "Slave принял данные, куда их?"
void (* twiSlaveWriter)(void) = 0; // указатель на функцию "Slave всё отправил, что дальше?"

Драйвер дисплея LCD1602 (lcd1602.h)

Начинался как глубокая переработка библиотеки LiquidCrystal_I2C, что нашло свое отражение в наборе констант и примитивов работы с дисплеем.
В общем, там тоже все документировано и крайне просто.
 
Последнее редактирование:
09.06.2019
57
5
8
#12
Библиотека Arhat.h - автоматное программирование.

Конечные автоматы тут представлены достаточно широко, начиная от давнего и хорошо известного макроса everyMillis() до автоматов Милли и Мура.

everyMillis( interval, block_code) -- формирует собственный блок исполнения, в котором определяется внутренняя, недоступная извне переменная для хранения начального времени, что позволяет использовать этот макрос многократно в программе без накладок - каждый вызов макроса будет работать совершенно независимо от всех остальных.
Одна из его удобных особенностей - возможность указать "онлайн" блок операторов, который надо исполнить по истечению интервала (см. пример в описании макроса). Например так:
C:
void loop(){
  everyMillis( 1000, blink(100) );                     // точка с запятой не требуется, т.к. есть внутри макроса.
  everyMillis( 500, {blink(100); anythingElse(); });   // блок действий в фигурных скобках, прямо в макросе
}
Мы им пользуемся не так часто из-за long переменных. На практике столь большие интервалы никому не нужны и часто можно обходится словом или даже и вовсе байтом. Проще дополнить макрос нужными приведениями типов самостоятельно.
!!! Внимательно: при использовании байтовых переменных вычитание надо производить через отдельный временный байтовый регистр с явным приведением к беззнаковому байту - в противном случае, работаем integer propagation стандарта языка и результат может (и будет) приведен компилятором к .. знаковому слову !!! Это замечание важно во всех случаях беззнаковой байтовой арифметики .. стандарт, он такой стандарт. В нем есть ещё несколько подобных мест, где байтовая арифметика будет вести себя несколько "неожиданным" образом. В частности - сдвиговые операции. Тут за деталями отсылаю к описанию стандарта языка. Кстати, в С++ - аналогичные моменты.

Автоматное программирование с #include "ka.h" (Конечные Автоматы)

Собственно в нем достаточно подробное комментирование всего, так что только отдельные моменты:
1. Переопределен макрос everyMillis() на макс. величину интервалов в размер unsigned int (2 байта) или 65.535 секунд. Этого вполне достаточно для большинства применений, а при необходимости всегда можно "нарастить" интервалы доп.состояниями автомата.
2. Таблицы состояний и переходов конечных автоматов можно хранить в программной памяти при необходимости. Для такой работы есть соответствующие варианты функций.

ka.h (п.2) Конечные автоматы, управляемые по времени. Тут "доведены до абсурда", достаточно создать таблицу поведения и на этом программирование можно считать законченным. Смотреть пример KaTimers ("Умный светофор"), и в нем указан подход (пронумерован порядок) создания таких скетчей "с нуля", прямо по ТЗ. Есть пример совмещения как автомат одного типа может управлять другим автоматом, просто меняя его состояние или заменяя действия и пр. запчасти в таблице управления, если она лежит в ОЗУ, а не программном флеше (вот, не всегда надо прятать во flash!). В нашей практике - наиболее часто применяемое явление: это практически разные "моргалки", "стиралки" и прочие применения, где есть программа управления по времени: сделай то, потом это .. в т.ч. и с учетом внешних событий с таймаутами.

ka.h (п.3) Обобщенный конечный автомат со "слушателем входного потока" и единым "исполнителем" состояний. В общем-то предоставляет единую функцию реакции на изменение состояния слушателем и вызывает исполнителя, который сам решает что ему делать теперь и куда изменить состояние далее. Классика конечных автоматов, ничего более.

ka.h (п.4) Конечный автомат Милли со встроенной таблицей состояний. Тоже классика в своем роде: имеет таблицу состояний, каждое из которых может иметь несколько входных событий и своего "исполнителя" состояния. Фактически тут, также как и конечные автоматы по времени программируется описанием таблицы состояний с комплектом примитивных исполнителей. Это вид автоматов часто пишут в виде п.2. прописывая все состояния в блоке switch(). Вот .. для упрощения. Также имеет возможность размещения таблицы управления во flash.

ka.h (п.5) Конечный автомат Мура также со встроенной таблицей управления. Собственно в комментариях все есть. Дополнительно имеет "внутренний" конечный автомат, позволяющий ограничивать количество повторов "действия" при неизменном состоянии.
 
Последнее редактирование:
09.06.2019
57
5
8
#13
Датчик цвета TCS3200.
Особенности подключения, драйвер с пересчетом освещенности в люксы, восстановлением баланса белого.

Вот так у нас оформлен датчик цвета TCS3200. Плата слегка обточена по краям, и упрятана в "коробочку" размером 24х24х16мм. (3х3х2 лего размера) Сам датчик изолирован трубкой от светодиодов платы. Светодиоды слегка подогнуты, с расчетом максимального освещения пятна на рассотянии примерно 2.5-3см от датчика.
Это значительно улучшило отношение сигнал/шум и позволило работать датчику устойчивей.

C:
/**
* Обработка данных с датчиков цвета TCS3200(TCS3210) в стиле конечных автоматов (без потерь времени на ожидания).
*
* Предположено:
* 0. контроллер - Arduino Mega 2560
* 1. Замер данных методом подсчета периодов частоты внешним входом счетчика Т5 Atmega2560
* 2. Максимально - 2 датчика, различаемых сигналом !OE (проверять что на плате датчика он не закорочен на землю принудительно!)
* 3. Сигналы датчиков S0, S2 -- подключены к +5в (лог.1) и задают максимальную скорость счета и предел частоты <=600кгц.
* 4. Сигналы Out,S2,S3 -- общие от обоих датчиков. Активность датчиков устанавливается сигналом !OE
*
* Для подключения большего числа датчиков к счетчику надо (в этом файле!):
* а) задать доп. номера пинов для сигналов OE доп. датчиков. Каждый такой сигнал должен приходить на свой пин Ардуины;
* б) увеличить константу количества датчиков TCS_MAX_SENSORS до требуемого количества;
*
* Для использования другого 16-и разрядного таймера (1,3 или 4), если у вас есть его "счетный вход" надо переназначить
* все ноги на новый таймер и указать его номер .. см. первый блок #define
*
* Подключение файла:
* 1. Убедитесь, что ваш датчик(и) соединены с Мегой как тут предположено. Предварительно проверьте, что датчик(и)
*    управляются сигналом ОЕ .. а то мои оказались закорочены - чуть не спалил;
*
* 2. void setup(){
*      // ... настройки, те что надо вашей программе ..
*      tcsSetup(); // настраиваем ноги датчика(ов), таймер и пр. Можно поместить и в начало ..
*    }
*
* 3. void loop()
*    {
*      tcsPrepare({0|1|num}); -- вызов настройки заданного датчика на замеры (номер: число, переменная - все равно)
*
*      // ... ваш код основной программы ...
*      // ... в нем можно пользоваться данными из глобального массива tcsColors[num][color]
*      // ... поскольку в нем лежат только реальные данные.
*      // ... узнать что данные обновлены можно, например посмотрев счетчик замеров tcsCount (а надо ли?!?)
*      // ... режимы замеров можно изменять на лету в tcsModes
*      // ... повторный запуск того же самого датчика можно делать макросом tcsRestart() или просто присваиванием (как в нем)
*    }
*
* !!! Не желательно ускорять замеры шустрее 4мсек, если режим замера предполагает автоподстройку скорости самим датчиком.
*
* Остальное - читайте этот файл, пользуйтесь.
*
* @author Arhat109, e-mail: arhat109@mail.ru
*
* Лицензия: полностью бесплатное ПО, в том числе и от любых претензий. Вопросы и пожелания - принимаются на почту.
* Вы можете его использовать на свой страх и риск любым известным вам способом.
*
*/
#ifndef _TCS3200_H_
#define _TCS3200_H_

#include "arhat.h"

#ifdef __cplusplus
extern "C" {
#endif
// ====================== Настройка и чтение состояния датчиков ЦВЕТА TCS3200 ========================= //

#if defined(_ARHAT_PINS2560_)
    #define tcsOut    47                    // T5in   => выход обоих датчиков out
    #define tcsICP    48                    // T5icp на будущее: подсчет периода приходящей частоты
    #define tcsS2     44                    // T5outC => управление цветом обоих датчиков
    #define tcsS3     46                    // T5outA => управление цветом обоих датчиков
    #define tcsOE1    49                    // T4ICP  => !OE for TCS3200 1-й датчик
    #define tcsOE2    45                    // T5outB => !OE for TCS3200 2-й датчик
    #define tcsTimer   5
#elif defined(_ARHAT_PINS328P_)
    #define tcsOut     5                    // T1in   => выход обоих датчиков out
    #define tcsICP     8                    // T1icp на будущее: подсчет периода приходящей частоты
    #define tcsS2      3                    //
    #define tcsS3      4                    //
    #define tcsOE1     6                    //
    #define tcsOE2     7                    //
    #define tcsTimer   1
#else
    #error *** ERROR! Not MEGA2560 and NOT UNO or such .. new board? ***
#endif

#define TCS_MAX_SENSORS    2            // Всего датчиков на этом счетчике
#define TCS_MAX_MEASURES   2            // [1,2,4,8,..] для автозамены деления сдвигами
#define TCS_WAIT           2            // мсек на разовый подсчет частоты. Итого fmin=250hz (4лк), замер 4 цветов = 62.5Гц
//#define TCS_WAIT         8              // мсек на разовый подсчет частоты. Итого fmin=125hz (1.5лк), замер 4 цветов = 31.25Гц
//#define TCS_WAIT        16              // мсек на разовый подсчет частоты. Итого fmin=62.5hz (0.9лк), замер 4 цветов = 15.6Гц
//#define TCS_WAIT        32              // мсек на разовый подсчет частоты. Итого fmin=31.25hz (0.4лк), замер 4 цветов = 7.8Гц

#define TCS_MAX_WAIT    4*TCS_WAIT      // время, к которому приводим подсчитанные величины для повышения качества в темноте
#define TCS_CONTRAST     8              // делитель для повышения контраста цвета (1/2,1/4,1/8)

#define TCS_WHITE    0                  // порядок цветов в рез. массиве (значения для tcsColor)
#define TCS_GREEN    1
#define TCS_RED      2
#define TCS_BLUE     3
#define TCS_NOCOLOR  4                  // продолжение: состояние КА "замер завершен"
#define TCS_START    5                  // .. команда конечному автомату "начать замер"

/**
* Макрос повторного запуска того же датчика:
*/
#define tcsRestart() {tcsColor = TCS_START;}

/**
* Макрос переноса из временных значений в окончательные без обработки
* @global tcsTemp[],tcsColors[]
*/
#define tcsRaw()                         \
{                                        \
  tcsColors[tcsCurrent][0] = tcsTemp[0]; \
  tcsColors[tcsCurrent][1] = tcsTemp[1]; \
  tcsColors[tcsCurrent][2] = tcsTemp[2]; \
  tcsColors[tcsCurrent][3] = tcsTemp[3]; \
}

/**
* Макрос простого усреднения накопленных данных
* @global tcsTemp[]
*/
#define tcsAvg()                      \
{                                     \
  uint8_t i=4;                        \
  do{                                 \
    tcsTemp[--i] /= TCS_MAX_MEASURES; \
  }while( i>0 );                      \
}

/**
* Макрос перевода яркостного канала из частоты в люксы
* @global tcsTemp[0]
*/
#define tcsLukses()         \
{                           \
  int tg = tcsTemp[0];      \
  tcsTemp[0] = tg + (tg/4); \
}

enum TCS_Modes {
      TCS_AUTO         =  0 // авторежим: коррекция + усреднение + люксометр + вн.баланс + цв. контраст + автоподстройка скорости
    , TCS_NOT_CORRECT  =  1 // режим без коррекции данных
    , TCS_NOT_AVERAGE  =  2 // режим без усреднения результатов
    , TCS_NOT_LUKSES   =  4 // режим без перевода яркости в люксы
    , TCS_NOT_WHITE    =  8 // режим без коррекции баланса от вн. светодиодов
    , TCS_NOT_CONTRAST = 16 // режим без повышения цветового контраста
    , TCS_SPEED_CONST  = 32 // режим без адаптации скорости работы (всегда фикс.)
    , TCS_WB_ONLY      = 64 // режим замера только яркостного канала! -коррекция -усреднение -люксометр -вн.баланс -цв. контраст
};

extern volatile int      tcsColors[TCS_MAX_SENSORS][4]; // итоговые и усредненные данные замера
extern volatile int      tcsTemp[4];                    // внутреннее хранение данных при усреднениях и замерах
extern volatile int      tcsMinVal,tcsMaxVal;                 // мин и макс. уровни каналов цвета
extern volatile uint16_t tcsCount;                      // номер завершенного замера
extern volatile uint8_t  tcsColor;                      // текущий измеряемый цвет в попытке или состояние КА
extern volatile uint8_t  tcsMeasure;                    // номер текущей усредняемой попытки замера
extern volatile uint8_t  tcsWait;                       // текущая длительность одного замера частоты от датчика
extern volatile uint8_t  tcsIsBright;                   // Пересвет? (и прочие возвращаемые статусы на будущее)
extern volatile uint8_t  tcsModes;                      // Биты определяют режимы работы драйвера
extern volatile uint8_t  tcsCurrent;                    // Текущий датчик в режиме 2-х и более датчиков
extern volatile uint8_t  tcsCurWait;                    // пропуск заданного числа прерываний таймера @see tcsRun()

void tcsSetup();               // Настройка таймера и выводов на работу с датчиком @global tcsWait
void tcsPrepare(uint8_t num);  // настройка заданного датчика на чтение данных о цвете @global tcsColor,tcsCurrent
void tcsNextColor();           // переключение датчика на текущий цвет и сброс таймера для нового замера @global tcsColor
void tcsCorrect();             // Коррекция показаний по усредненной обратной матрице "среднее" из даташит: @global tcsTemp[]
void tcsMinMax();              // Подсчет мин/мах уровней каналов цвета @global int minVal,maxVal,tcsTemp[]
void tcsWhiteLocal();          // коррекция баланса встроенных светодиодов @global tcsTemp[]
void tcsContrast();            // повышение контраста @global minVal,maxVal,tcsTemp[]
void tcsRun();                 // КА датчика: замеры цвета, пересчеты, коррекции и сохранение результата

#ifdef __cplusplus
  } // extern "C"
#endif

#endif // _TCS3200_H_

Ну вот .. как-то так. На практике оказалось, что датчик при закрытых собственных светодиодах достаточно надежно работает на освещенностях примерно от 7 люкс с определением цветов. На освещенностях около 40лк уже имеет точность в пределах 1.5%.
 
Последнее редактирование:
09.06.2019
57
5
8
#14
Работа с энкодерами и ультразвуковым датчиком на прерываниях. Точность измерений HCSR-04.
 
09.06.2019
57
5
8
#15
Работа с клавиатурной матрицей 4х4 и кнопками. Наши датчики касания.

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

C:
//
// Обработчик клавиатуры 4х4 кнопки
//
// Неблокирующее чтение матрицы клавиатуры, обработка дребезга контактов и
// возможность отслеживания изменения состояния любого количества кнопок
//
// kbdSetup() -- настройка на начало работы.
// kbdScan()  -- опрос состояния клавиатуры.
//
// kbdRows[], kbdCols[] -- номера пинов куда подключены строки и столбцы клавиатуры
//
// После вызова сканера можно смотреть количество и списки клавиш (из kbdChars[] !) в
// kbdCountPressed, kbdJustPressed[] -- кол-во и список нажатых клавиш (прошли дребезг).
// kbdCountLonged,  kbdJustLonged[]  -- кол-во и список удерживаемых клавиш.
// kbdCountUpped,   kbdJustUpped[]   -- кол-во и список отпущенных клавиш.
//
// Требует 5*16+2*8+3 = 99 байт ОЗУ.
//

#define KBD_MAX_ROWS 4
#define KBD_MAX_COLS 4
#define KBD_MAX_KEYS (KBD_MAX_ROWS * KBD_MAX_COLS)

// состояния кнопок клавиатуры: отпущена, нажата и т.д.
#define KBD_KEY_FREE   0
#define KBD_KEY_PRESS  0x80
#define KBD_KEY_BOUNCE 0x40
#define KBD_KEY_LONG   0x20

// маска битов времени кнопки
#define KBD_MASK_TIMER 0x1F

// сдвиг времени в битах вправо - как грубо оцениваем дребезг и удержание (6 - по 64мсек)
#define KBD_TIME_TICK  6

// время дребезга в местных единицах (3*64 = 192мсек):
#define KBD_TIME_BOUNCE 3

// время удержания в них же (максимально 31*64=1984мсек):
#define KBD_TIME_LONG  31

// местная функция времени
#define kbdMillis() (KBD_MASK_TIMER & ( (uint8_t)(millis()>>KBD_TIME_TICK) ))

// получить время из кнопки
#define kbdTime(p) (KBD_MASK_TIMER & (p))

uint8_t kbdRows[] = {2,3,4,5}; // пины "строки" матрицы клавиатуры (выходы) изменять тут!
uint8_t kbdCols[] = {6,7,8,9}; // пины "колонки" клавитурной матрицы (входы) изменять тут!

// массив текущего состояния кнопок клавиатуры:
uint8_t kbdKeys[KBD_MAX_KEYS];
// массив символов, назначенных каждой кнопке:
uint8_t kbdChars[KBD_MAX_KEYS] = {
  '1', '2', '3', 'A',
  '4', '5', '6', 'B',
  '7', '8', '9', 'C',
  '*', '0', '#', 'D'
};

// списки клавиш в нужном состоянии:
uint8_t kbdJustPressed[KBD_MAX_KEYS];
uint8_t kbdJustUpped[KBD_MAX_KEYS];
uint8_t kbdJustLonged[KBD_MAX_KEYS];

uint8_t kbdCountPressed = 0;
uint8_t kbdCountLonged = 0;
uint8_t kbdCountUpped = 0;

// setup(): настройка входов/выходов и начального состояния
void kbdSetup()
{
  for(int8_t pin=0; pin<KBD_MAX_ROWS; pin++){ // строки на выход и в "1"
    pinMode( kbdRows[pin], OUTPUT);
    digitalWrite(kbdRows[pin], HIGH);
  }
  for(int8_t pin=0; pin<KBD_MAX_COLS; pin++){ // столбцы - входы
    pinMode( kbdCols[pin], INPUT_PULLUP);
  }

  kbdCountPressed = kbdCountLonged = kbdCountUpped = 0;

  for(uint8_t num=0; num<KBD_MAX_KEYS; num++ )
  {
    kbdKeys[num]=0;
  }
}

// сканер - проверяльщик текущего состояния кнопок на момент вызова:
void kbdScan()
{
  uint8_t _time = kbdMillis();

  kbdCountPressed = kbdCountLonged = kbdCountUpped = 0;

  for( int8_t row=0; row<KBD_MAX_ROWS; row++)  // сканируем строку
  {
    digitalWrite(kbdRows[row], LOW);       // прижимаем строку в "0"
    for(int8_t col=0; col<KBD_MAX_COLS; col++) // ищем нажатую кнопку в строке
    {
      uint8_t keyNum = row*4+col;
      uint8_t keyVal = kbdKeys[keyNum];

      if(
          (keyVal & KBD_KEY_PRESS) &&                 // кнопка уже нажата и
          !(keyVal & KBD_KEY_BOUNCE) &&               // не прошла дребезг и
          (_time - kbdTime(keyVal) < KBD_TIME_BOUNCE) // не вышло время дребезга
      ){
        continue;                                     // пропуск опроса - антидребезг!
      }
      // далее только для не нажатых кнопок или у которых прошла проверка дребезга

      uint8_t keyState = digitalRead(kbdCols[col]);

      if(keyState == LOW){                                                  // кнопка нажата:
        if( !(keyVal & KBD_KEY_PRESS) ){                                    // ..только что нажата?
          keyVal = KBD_KEY_PRESS | _time;                                   // ..и время для дребезга
        }else
          if( !(keyVal & KBD_KEY_BOUNCE) ){                                 // ..нажата, но проверяется дребезг?
            keyVal = KBD_KEY_PRESS | KBD_KEY_BOUNCE | _time;                // ..новое время для удержания
            kbdJustPressed[kbdCountPressed++] = kbdChars[keyNum];
          }else
            if( !(keyVal & KBD_KEY_LONG) ){                                 // .. проверка удержания?
              if( _time - kbdTime(keyVal) > KBD_TIME_LONG ){
                keyVal = KBD_KEY_PRESS | KBD_KEY_BOUNCE | KBD_KEY_LONG;     // .. теперь "удерживается"
                kbdJustLonged[kbdCountLonged++] = kbdChars[keyNum];
              }
            }else{                                                          // .. уже был LONG
                kbdJustLonged[kbdCountLonged++] = kbdChars[keyNum];         // .. всё ещё удерживается
            }
      }else{                                                                // кнопка отпущена:
        if( keyVal == KBD_KEY_FREE ){ continue; }                           // .. и не была нажата. Пропуск.
        if( (keyVal & (KBD_KEY_BOUNCE | KBD_KEY_LONG)) != 0 ){              // .. отпуск нажатой или удерживаемой
          kbdJustUpped[kbdCountUpped++] = kbdChars[keyNum];
        }
        keyVal = KBD_KEY_FREE;
      }
      kbdKeys[keyNum] = keyVal;
    }
    digitalWrite(kbdRows[row], HIGH); // отжимаем строку обратно
  }
}

// ============== собственно скетч: ===================

void setup() {
  kbdSetup();
}

void loop() {
  kbdScan();
  // далее можно смотреть счетчики нажатых, удерживаемых и отпущенных клавиш и их списки
}

Нашел фото нашего датчика касания на базе "мышиной кнопки":
Touch-1.JPG
 
Последнее редактирование:
09.06.2019
57
5
8
#16
О, забыл! Наши серводвигатели.. :)
Servo90-1.JPG

SG-90 вид1.JPG

DSCN0141.JPG

Первая и вторая попытка "прикрутить" серводвигатели SG90 в наш проект. На втором варианте обрамления серв, был собран вот такой робот "совенок" и запрограммирован в Ардублок. Для ускорения передвижения, "шаг" ноги был разбит на несколько этапов и пока опорная диагональ перемещала робота вперед, вторая диагональ перемещалась одновременно обратно. Была попытка научить его бегать "прыжками", но мощностей SG90 оказалось катастрофически мало для его веса (475гр).
 
Последнее редактирование:
10.06.2019
19
0
1
#18
Зачем же так над ардуинкой издеваться? На фото видно что проц плавится начал. На хрен ещё 16 пинов? Там и так пинов много.
Окончательно, Мега2560 была дополнена отсутствующими ножками ( +16пинов ):
 
09.06.2019
57
5
8
#19
@Veselchak, Это не плавление корпуса, это просто неотмытая канифоль ещё. :)
16 пинов, "утерянных" у платы Ардуино Мега 2560 - это пины счетных входов и захвата таймеров, пины прерываний PCINT (и только!) и третий UART.
Если UART3 - в общем-то не нужен, то пины чистого PCINT не смешанного ни с чем (5шт) оказались очень полезны в работе с моторными энкодерами, о чем я постараюсь рассказать в соответствующем посту.
А ножки "захват таймера" (ICP) крайне удобны для получения точной длительности импульса без избыточной загрузки процессора силами pulseIn() и у нас активно использовались для датчика цвета TCS3200. Штатные 2 ноги не позволяют обслуживать больше 2-х датчиков.

Ну и конечно же, это был "эксперимент" можно ли подпаяться к такому мелкому шагу ножек у микросхемы. Проблематично для меня, но оказалось вполне можно. Позже этот опыт пригодился для монтажа этих корпусов на платы собственной разводки, в т.ч. и под комбинацию "Мега + 512килобайт оперативы".. :)
butterbrod-2.JPG

Mega-2560last-2.JPG

Но об этом развитии проекта, несколько позже. Это "вторая иетрация" разводки плат. Уже есть и третья.
Тут выведены все 86 ножек, кроме ног разьема с платой памяти все они имеют свою землю и питание. БП на плате держит 5в х 5А.
Габаритный размер плат считается по "лего-отверстиям". :)
 
Последнее редактирование:

Wan-Derer

Модератор
Команда форума
31.07.2018
917
131
43
Москва
wan-derer.ru
#20
@Arhat109, про память интересно. Я знаю что к многоногим Мега можно подключать внешнюю память и включать её в адресное пространство ядра. Но оно (пространство) всего 64к. Как организовывали доп память? Страничками?
Ну и конечно интересно зачем столько памяти :)
Программирование всего это счастья уже не в Arduino IDE?
В общем, ждём статейку на тему большой платы.
Я тоже развёл плату на 2560, но пока не довёл до практического результата: https://wan-derer.ru/blog/diptrace-первые-впечатления-библиотека-гост/