- PVSM.RU - https://www.pvsm.ru -
Продолжаю публикацию статей из серии "ардуино головного мозга". Петя — это очень дешёвый (примерно десять баксов) гексапод. Он может быть прекрасным проектом на один ненастный выходной, который развлечёт как и взрослых, так и детей. Раз уж мы про развлечения, вот вам видеоролик с Петей, танцующим под фанк-музыку:
Разумеется, никакого анализа звука я не делал, просто запрограммировал Петю на танец в определённом ритме. Вот ещё один ролик, в котором Петя выказывает своё презрение к мячикам для жонглирования:
В его текущем виде Петя умеет только ходить, но при этом он может видеть (измерять расстояние до) близлежащие препятствия. Его
Если у вас есть доступ к 3д принтеру, то распечатать непосредственно тело/ноги робота не будет стоить практически ничего. Вот список основных деталей, необходимых для клонирования Пети:
Покупать конденсаторы штучно, конечно, никто не будет. Лично я конкретно под этот проект заказывал платы (бесплатно, поскольку в нагрузку к другому проекту), три сервопривода и держалку для батареек. Вся остальная мелочёвка у меня лежит в куче всякого другого хлама. Итого десять баксов — это ещё с запасом. Кстати, самой дорогой вещью в этом роботе могут оказаться батарейки ;)
NB: При заказе 9g сервоприводов не забудьте, что они бывают разных размеров. Петя предполагает использвание низкопрофильных серв с пластиковыми редукторами. Конечно, он будет работать и с другими сервами, но в этом случае не исключено, что придётся внести косметические изменения в SketchUp файлы. Ну и вообще металлический редуктор в данном случае не только оверкилл, но и зло в виде излишнего жужжания, трения, энергопотребления и выламывания деталей в случае форсмажора.
Тут сюрпризов нет, если у вас есть доступ к 3д принтеру, просто напечатайте содержимое каталога hardware/body/ [9]. Распечатанные детали выглядят как-то так::
У меня на принтере стоит сопло диаметром 1мм, так что все детали распечатались за пару часов в сумме. После сборки оно должно выглядеть как-то так:
М3 нейлоновые винты идеально подходят для сборки. Используйте нейлоновые шайбочки между движущимися частями, и затормозите гайку на резьбе любым удобным вам способом. Лично я просто нежно ткнул паяльником:
Сама по себе материнка крайне примитивная. Исходники и гербер лежат в папке hardware/motherboard/ [10]. Вот рендер гербера:
Материнка несёт на себе микроконтроллер ATMega8 и схему датчика препятствий, и ничего кроме этого. Вот так выглядит схема подключения
Я рекомендую сначала запаять строгий минимум, необходимый для запуска процессора, чисто чтобы убедиться, что деликатная пайка в полном порядке. На этом этапе материнка выглядит вот так:
N.B. Обратите внимние, что даташит ATMega8A предписывает рабочее напряжение в диапазоне 2.7-5.5В, и абсолютный максимум напряжения 6В. Безопасный вариант питания Пети — это четыре NiMH 1.2V аккумулятора. Чисто из духа противоречния я перекрестился и засунул в батарейный отсек четыре стандартные щелочные батарейки (6.4В в сумме), и это не сожгло Пете
Вот фотография полностью распаянной материнской платы (за исключением ИК светодиодов и фототранзисторов):
У Пети два глаза, каждый из них состоит из инфракрасного светодиода и соответствующего фототранзистора. Светодиод излучает инфракрасный свет; этот свет распространяется через воздух и отражается от препятствий назад к фотоприёмнику. Если препятствие близко, отражённый свет будет сильнее, нежели если пепрятствие находится далеко. Обратите внимание, что хоть инфракрасный свет и не виден невооружённым глазом, некоторые камеры его регистрируют и могут показать на записи, что весьма удобно для отладки:
Схема датчика препятствий крайне примитивна:
Мы запитываем два светодиода; когда фототранзисторы не освещены, коллекторы Q3 и Q4 "привязаны" к Vcc, а когда фототранзисторы улавливают достаточное количество ИК излучения, напряжение на коллекторах падает. На следующем примере я тестирую эту схему перед установкой светодиодов и фототранзисторов в глазницы Пети:
Обратите внимание, что в зависимости от того, какие у вас светодиоды, вам может быть нужно подобрать значение резистора R6. 47 Ом дают 55мА через светодиоды, но некоторые светодиоды могут хотеть больше (или меньше). Например, я выпаял безызвестные ИК светодиоды из поломанной детской игрушки, и они прекрасно работают на трёх миллиамперах (910 Ом)!
Я рекомендую сначала собрать схему приёмника на макетке (без светодиодов). Затем возьмите ИК светодиод, запитайте его напрямую от таблетки типа CR2032, и направьте в фототранзистор. (Я ничего не знаю про внутреннее сопротивление таблеток, но в моём [небогатом] опыте я никогда не видел изжаренных светодиодов от подобных манипуляций, поправьте меня, если я ошибаюсь)
Осветив ИК излучением фототранзисторы, убедитесь, что напряжение на коллекторах Q3 и Q4 падает согласно ожиданиям. Убедившись, что фотоприёмник работает корректно, подберите необходимый резистор для пары светодиодв, для того, чтобы получить поведение похожее на то, что я привёл в моём видео.
Обратите внимание, что лучше надеть на светодиоды и на фотоприёмники термоусадку для отсекания паразитных засветок. Ну и кроме того, с термоусадкой они прекрасно садятся в глазницы. При запаивании 2n3904, я бы посоветовал сначала запаивать центральный вывод, и только потом боковые, а то очень легко посадить трудноубираемую соплю припоя между ногами. Лично я нахожу пайку этих транзисторов более трудной, нежели пайку самого микропроцессора, но я плохой монтажник.
Да, если у вас нет осциллографа, это не беда, вполне можно обойтись парой светодиодов, обратите внимание на синие светодиоды на этом видео:
Для более далёких препятствий светодиоды будут тусклее.
Если у вас не получается заставить работать датчик препятствий, ну или если вам просто не понравилась предложенная схема, то существует множество других вариантов:
В принципе, Петю можно программировать через среду ардуино, но я её нахожу слишком громоздкой и непонятной для такого простого микроконтроллера, как ATMega8. Давайте разобьём объяснение работы прошивки на четыре части:
Хоббийные сервоприводы принимают на вход 50 Гц ШИМ сигнал; 1 мс минимальная длительность импульса (0 градусов положения вала), 2 мс максимальная длительность импульса (90 градусов положения вала). У Пети на борту три сервопривода, два из них заведены на 16 битный таймер (timer1), а третий на восьмибитный таймер (timer2). Если я не ошибаюсь, ардуиновская библиотека Servo.h управляет сервами в режиме софтверного ШИМ, что на мой взгляд расточительно, так что у меня оба таймера тикают в режиме fast PWM.
Сам микроконтроллер тикает на 8 МГц, при этом timer1 работает на частоте 1 МГц (делитель 8).
Регистр ICR1 задаёт значение TOP (20000), таким образом, таймер перезапускается каждые 20 мс, выдавая корректный 50 Гц сигнал. Регистры OCR1A и OCR1B контролируют длительность (в микросекундах) импульсов для левой и правой серв.
А вот с центральной сервой проблема. Она заведена на восьмибитный таймер timer2, а он, к сожалению, не имеет аналога столь удобного ICR1, то есть, частота переполнения счётчика контролируется только через делитель. В таблице делителей нет такого, который позволил бы приблизить 50 Гц с приемлемой точностью, так что вот дикая идея, которая лежит где-то посередине между софтверным и хардверным ШИМ генераторами:
4 мс это больше 2 мс максимальной длительности импульса, которая нам нужна, и сильно меньше перезаряжающего ритма в 20 мс. Подводя итог, если мы хотим поставить все три сервы в среднее положение (1.5 мс длительность импульса), то мы должны сделать следующее:
OCR1A = 1500; // left servo
OCR1B = 1500; // right servo
OCR2 = 1500/16; // center servo
Перво-наперво, в коде есть шесть важных констант:
const uint8_t zero[3] = {45, 50, 40}; // zero position of the servo (degrees)
const uint8_t range[3] = {25, 25, 20}; // the servos are allowed to move in the zero[i] +- range[i] interval
Массив zero[3]
хранит значения углов для всех трёх сервоприводов, соответствующих нейтральной позиции (см. левую фотографию чуть ниже). В идеале, эти углы должны были бы быть 45° (середина диапазона серв), но на практике дискретность установки ног на зубчатые валы требует отклонения от идеала 45° для того, чтобы добиться симметричности нейтральной позиции. Затем, range[3]
предписывает максимальный разрешённый диапазон движения сервоприводов. Это означает, что сервоприводу с индексом i
разрешается двигаться только в диапазоне от zero[i]-range[i]
до zero[i]+range[i]
.
Текущее задание положения сервоприводов (в градусах, 0°-90°) хранится в массиве uint8_t pos[3]
. Вызов функции update_servo_timers()
обновляет значения регистров ШИМ-генератора согласно заданию. Правая фотография из картинки выше соответствует заданию pos[i]=zero[i]+range[i]
для всех трёх индексов i=0,1,2
.
В текущей реализации все движения планируются как движения с постоянной скоростью. Для этого у меня заведено четыре вспомогательных массива pos_beg[3], pos_end[3], time_start[3]
и duration[3]
. Давайте предположим, что я хочу двинуть только левой сервой. Для этого нужно выполнить следующие операции:
pos[0]
в pos_beg[0]
, это положение, соответствующее началу движения;pos_end[0]
в желаемое положение (по-прежнему в градусах);time_start[0]
текущую метку времени (миллисекунды, прошедшие с момента загрузки);duration[0]
(в секундах). Таким образом, скорость будет (pos_end[0]-pos_beg[0])/duration[0]
градусов/сек.Затем в бесконечном цикле я вызываю функцию movement_planner()
, которая обновляет массив текущего задания положения сервоприводов pos[]
согласно плану движения, а затем функцию update_servo_timers()
, которая обновляет регистры ШИМ-генератора согласно заданию положения pos[]
.
Обратите внимание, что планировщик движений хранит свои значения в трёхэлементных массивах, таким образом, все движения (включая скорости) могут быть независимы друг от друга. Несмотря на это, в моей текущей реализации все походки используют синхроннные движения всех трёх сервоприводов. Давайте посмотрим, как Петя идёт вперёд. Для этого он циклически повторяет следующие четыре шага, вот их целевые позиции:
{zero[0]-range[0], zero[1]-range[1], zero[2]+range[2]}
{zero[0]-range[0], zero[1]-range[1], zero[2]-range[2]}
{zero[0]+range[0], zero[1]+range[1], zero[2]-range[2]}
{zero[0]+range[0], zero[1]+range[1], zero[2]+range[2]}
Мы можем записать эту последовательность как 2д массив (четыре тройки целевых позиций):
const int8_t advance_sequence[4][3] = {{-1, -1, 1}, {-1, -1, -1}, { 1, 1, -1}, { 1, 1, 1}};
Этот массив говорит нам, что конечное положение сервопривода i
на шаге step
равно zero[i] + range[i]*advance_sequence[step][i]
.
Ну а следующий код позволяет Пете идти вперёд неопределённое время:
uint8_t step = steps_per_sequence-1; // at the initialization stage the (previous) movement is considered to be complete, thus the next movement will be planned starting from the step 0
while (1) {
if (is_movement_finished()) {
step = (step + 1) % 4; // if previous movement is complete, then perform the next step; this variable loops as 0,1,2,3.
plan_next_movement(step, advance_sequence); // execute next movement
}
movement_planner(); // update the servos position according to the planning
_delay_ms(1);
}
Давайте вспомним, что наш датчик препятствий выдаёт напряжения, заведённые на каналы 4 и 5 АЦП микропроцессора. Для того, чтобы отсечь высокочастотный шум в измерениях (особенно учитывая количество шума, создаваемого сервами), на каждой итерации главного цикла я обновляю переменные adc_left_eye
и adc_right_eye
по следующему закону, что даёт мне фильтр низких частот:
adc_left_eye = adc_left_eye *.99 + adc_read(5)*.01; // low-pass filter on the ADC readings
adc_right_eye = adc_right_eye*.99 + adc_read(4)*.01;
Частота отсечки может настраивается или через задержку _delay_ms()
внутри главного цикла, или же через коэффициент взвешенной суммы .99
и 1-.99
в вышеприведённом коде.
Наличие препятствия определяется как простое пороговое сравнение:
uint8_t lobst = adc_left_eye < distance_threshold; // obstacle on the left?
uint8_t robst = adc_right_eye < distance_threshold; // obstacle on the right?
Затем в конце каждого шага (напоминаю, четыре шага на каждую последовательность) я проверяю наличие препятствий слева и справа и соответственно меняю последовательности:
if (is_movement_finished()) {
if (!lobst && !robst) {
sequence = advance_sequence; // no obstacles => go forward
} else if (lobst && robst) {
sequence = retreat_sequence; // obstacles left and right => go backwards
} else if (lobst && !robst) {
sequence = turn_right_sequence; // obstacle on the left => turn right
} else if (!lobst && robst) {
sequence = turn_left_sequence; // obstacle on the right => turn left
}
step = (step + 1) % steps_per_sequence; // if previous movement is complete, then perform the next step
plan_next_movement(step, sequence); // execute next movement
}
Это просто, но работает!
Любой вклад приветствуется! Присылайте ваши идеи, а пока я приведу спиок вещей, которые мне хотелось бы увидеть улучшенными:
Если вы добрая душа, желающая помочь с созданием версии V2 материнки, то не стесняйтесь это сделать! Вот список вещей, которые я хотел бы добавить/изменить/поправить в текущей материнке:
Петя — это страшное веселье!
Автор: haqreu
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/347498
Ссылки в тексте:
[1] мозги: http://www.braintools.ru
[2] SG90 9G сервопривды, 3 * 1.47€ / шт: https://www.aliexpress.com/item/4000595327297.html
[3] 4x AAA держалка для батареек, 1.34€ / шт: https://www.aliexpress.com/item/33049875634.html
[4] ATMega8A-AU (QFP-32), 1€ / шт: https://www.aliexpress.com/item/32557093316.html
[5] IR LED + IR фототранзистор, 0.20€ / шт: https://www.aliexpress.com/item/32849824664.html
[6] Электролитический конденсатор 1000uF 16V, 0.17€ / шт: https://www.aliexpress.com/item/32954075821.html
[7] 2n3904 транзисторы, 3 * 0.01€ / шт: https://www.aliexpress.com/item/32494899564.html
[8] hardware/motherboard/BOM.html: https://github.com/ssloy/penny/blob/master/hardware/motherboard/BOM.html
[9] hardware/body/: https://github.com/ssloy/penny/tree/master/hardware/body
[10] hardware/motherboard/: https://github.com/ssloy/penny/tree/master/hardware/motherboard/
[11] Источник: https://habr.com/ru/post/489144/?utm_source=habrahabr&utm_medium=rss&utm_campaign=489144
Нажмите здесь для печати.