Хочу сразу оговориться:
1. Статься из разряда «how to…». Пишу только потому что просили и чтоб не выглядеть голословным.
2. Мне столь простое устройство не нужно. Поэтому корпусом и подбором компонентов не заморачиваюсь. Делаем в виде прототипа.
3. Платформа Arduino. Ее уровень абстракции является наиболее подходящим для изучения школьниками (класса с 6-го). Собственно из моего учебного курса и взята большая часть описываемых примеров.
4. Статья скорее как затравка к изучению этой темы. Я не буду сильно углубляться и рассказывать все с азов. Это будет слишком объемно и не влезет в рамки статьи. В конце дам список ресурсов для самостоятельного изучения.
Постановка задачи
Хотим получить электронную начинку 4-х канального пульта пропорционального радиоуправления для… каждый сам решает для чего.
Формализуем наше «хочу»:
Вход – два двухосевых джойстика.
Выход – PPM сигнал для подключения к вч-модулю (или к компьютеру для симулятора).
Введение
Сердцем нашей конструкции будет микроконтроллер семейства mega фирмы Atmel, помещенный на плату с минимальной обвязкой и с зашитым в него специальным загрузчиком для программирования по последовательному порту. Собственно это и есть Arduino. Подробно можно почитать на официальном сайте проекта Arduino.cc или на его русскоязычном клоне Arduino.ru. Там желающие найдут всю необходимую информацию о том, какие бывают разновидности Arduino, как заливать прошивку, познакомятся с основами языка, смогут свободно скачать среду программирования… Там все по-русски и без затей. Никаких заумных инсталляторов, никаких сложных сред. Небольшое меню и две-три кнопки на тулбаре – все что нужно для того чтобы начать.
Джойстик. В его конструкции за прошедшие ндцать лет кардинально ничего не изменилось. Два потенциометра с перпендикулярными осями – вот и весь джойстик. Перемещение ручки по некоторой оси приводит к перемещению скользящего контакта соответствующего переменного резистора. Подключим их по следующей схеме(левый рисунок). Получится ни что иное как обыкновенный делитель напряжения(схема справа). Так для каждого положения ручки управления R1 и R2 будут свои. При этом R1 + R2 = const (номинал резистора). Т.е. для каждого положения ручки на оси на входе микроконтроллера будет свое напряжение в интервале от 0 до VCC(напряжения питания).
В джойстиках используются обычно резисторы номиналом в 5Ком. Но точность номинала при таком подключении не особо важна. Это может быть и 4, и 6 и 10Ком.
Где взять джойстики? Ну как-то так сложилось, что все самое интересное и нужное можно найти в пультах Nintendo Wii. Нет, можно конечно сделать самому... Но на сегодня, как мне кажется, самый простой и дешевый вариант – купить джойстики управления для Turnigy 9XR прямо тут на Паркфлаере. Лично я закупил их с десяток для различных поделок и экспериментов.
Левый стик управления в сборе - для передатчика Turnigy 9XR, (Mode 2)
Правый стик управления в сборе - для передатчика Turnigy 9XR, (Mode 2)
Где взять Arduino и какую выбрать для данной задачи? Поскольку задача крайне проста, плату можно брать любую. Самая маленькая и дешевая – Arduino Mini с atmega168 на борту. Правда к ней надо еще взять USB-to-TTL для программирования или Kingduino USB to serial UART interface Я взял Arduino Nano c atmega328 (потому что под руку подвернулась именно она) и шилд типа такого для удобства подключения периферии.
Получение положения джойстиков
Подключаем каждый из четырех резисторов джойстиков по выше указанной схеме. При этом у Arduino задействуем контакты A0-A3. Эти контакты подключены к АЦП микроконтроллера и могут быть запрограммированы как аналоговые входы. Более менее подробно о программировании аналоговых входов я писал тут. Повторяться не буду.
Пишем прошивку. Я предумышленно разбил код на три файла:
TX.ino – основной файл. Он содержит главные точки входа – функции setup() и loop().
Config.h – в этом файле в дефайнсах определяется подключение периферии
Analog.ino - собственно код работы с АЦП.
Принцип простой. Настраиваем регистры микроконтроллера так, чтобы АЦП непрерывно выполнял преобразования. По завершении каждого преобразования срабатывает прерывание ISR(ADC_vect) и выполняется соответствующий код. Нам остается только переключать входы к АЦП и запоминать результат в массиве. Время на одно преобразование порядка 26мкс. Если Вы внимательно посмотрите на мой код, то заметите, что я беру результат каждого второго преобразования. Все дело в том, что АЦП работает независимо от центрального ядра. Пока выполняется код в прерывании и переключаются входы АЦП уже делает следующее преобразование. Как следствие – недостоверный результат. Поэтому я и беру каждое второе… В итоге получим массив из четырех элементов, значения которых должны изменяться пропорционально движению джойстиков в интервале от 0 до 1023. Вот архив (TX1.rar) с кодом примера.
Формируем выходной сигнал
Теперь нам надо сформировать массив канальных импульсов. Сделаем для этого такую функцию:
void UpdateChannels()
{
for (uint8_t i = 0; i<CHANNELS_COUNT; i++)
{
Channels[i] = map(AnalogValues[i], 0, 1023, 1000, 2000);
}
}
Здесь мы заполняем массив значений каналов попутно приводя полученные с АЦП значения к интервалу 1000-2000мкс. Это будут наши канальные импульсы. Осталось по этому массиву сформировать выходной сигнал. Для этого воспользуемся таймером.
У 168-ой /328-ой меги есть три таймера/счетчика, работающих от основного задающего генератора. Два из них имеют разрядность 16 и один – 8. Будем использовать один из 16-ти разрядных таймеров.
Сначала его надо настроить. Во-первых при частоте 16МГц установим делитель на 8, чтоб таймер отсчитывал половинки микросекунд:
TCCR1B |= (1<<CS11);
Каждый из таймеров имеет два компаратора и может генерить прерывания по совпадению значений. Разрешим эти прерывания для нашего таймера:
TIMSK1 = (1<<OCIE1A) | (1<<OCIE1B);
Компаратор А настроим на длину фрейма – 20мс
OCR1A = 100000;
Компаратор В будем использовать для формирования канальных импульсов.
Выводить импульсы будем на 13-й вывод Arduino.
В прерывании компаратора А напишем код инициализации нового канального фрейма:
ISR (TIMER1_COMPA_vect)
{
ppm_cur = 0;
OCR1B = PPM_PAUSE;
TCNT1 = 0;
PPM_on;
}
Фрейм начинается с паузы. Ее длину мы и запишем в соответствующий регистр компаратора В.
В прерывании компаратора В будет следующий код:
ISR (TIMER1_COMPB_vect)
{
if (PPM_0){
PPM_off;
OCR1B = TCNT1 + PPM_PAUSE;
ppm_cur++;
}
else {
PPM_on;
if(ppm_cur <= CHANNELS_COUNT)
OCR1B = TCNT1 + (Channels[ppm_cur-1]<<1);
else {
OCR1B = 0;
}
}
Если на выходе был сигнал, то дальше должна быть пауза. Устанавливаем соответствующее значение на выходе и в регистре таймера. Попутно увеличим на 1 счетчик каналов.
Если на выходе была пауза, то установим на выходе сигнал. А в регистр таймера запишем его длину. Если это синхропауза, то длина будет до конца фрейма и в регистр запишем 0.
Вроде бы как все. Загружаем в контроллер, подключаем к вч-модулю, на выход приемника вешаем серы… Работает!
Вот только есть одно НО. Сервы работают не в полном диапазоне. В чем проблема?
На самом деле все элементарно. Точность резисторов посредственная, механическая часть тоже ширпотреб. В итоге наша изначальная схема делителя преобразуется к такой:
И сопротивления R1 и R4 как раз и дают эту погрешность. Ну да не проблема. Введем калибровку. Для этого подключим кнопку. Алгоритм будет примитивный:
Первый джойстик влево вниз, держать, нажать кнопку.
Первый джойстик вправо вверх, держать, нажать кнопку.
Второй джойстик влево вниз, держать, нажать кнопку.
Второй джойстик вправо вверх, держать, нажать кнопку.
Так мы заполним два массива минимальных и максимальных значений для каждого аналогового входа. Вот только каждый раз калибровать – как-то неудобно. Ок. У нашего контроллера есть энергонезависимая память – запишем туда.
Теперь все работает как надо. Код по ссылке (TX2.rar) .
Так же не сложно увидеть, что число каналов легко увеличить до восьми, добавив еще органы управления – «крутилки» (резисторы) и тумблеры. Далее можно усложнить функцию UpdateChannels() - добавить туда микшеры. Можно подключить дисплей и написать меню сервисных функций… Итог – пульт а-ля Turnigy 9XR своими руками. Если не будет хватать выводов или памяти контроллера – возьмите по-мощнее. Atmega 2560 сможет реализовать практически все Ваши идеи и фантазии.
В заключении обещанные ссылки:
1. Arduino.cc – официальный сайт платформы
2. Arduino.ru – русскоязычный сайт платформы
3. http://rc-master.ucoz.ru/publ/20-1-0-85 - достаточно неплохой справочник по микроконтроллерам Atmel
4. Улли Соммер - Программирование микроконтроллерных плат ArduinoFreeduino – 2012
5. Arduino. Блокнот программиста. Brian W. Evans – справочник языка. Есть русский перевод.
6. Гололобов В. - С чего начинаются роботы. О проекте Arduino для школьников (и не только) – 2011
Будут вопросы – задавайте. Постараюсь ответить.
Делать фотографии и снимать видео ради всего этого не вижу смысла - времени нет, да и лениво.
Оборудование, на котором тестировал(за исключением компьютера):
Все таки в МК
под "компаратор" подразумевают устройство для сравнения напряжения на двух входах(а не количество импульсов посчитанных таймерсчетчиком), поэтому , что бы не забивать голову школьникам и иже с ними , в данном случае лучше использовать "регистр сравнения".
размерность PPM_PAUSE; в регистре сравнения в итоге должна обеспечить прерывание раз в 20мс, а где она определена?
if (PPM_0) что это за условие, PPM_0 где нибудь определено?
>> усатые дяди, лет 25 минимум, занимаются....
Я лично знаком с некоторыми из них. В большинстве своем это старшеклассники и студенты. А "усатые дяди" пишут код на ассемблере и оптимизируют его до битов. Именно поэтому на ардуину они даже не смотрят.
С переменным успехом оцифровываю программу обучения школьников программированию ардуин. Выкладываю у себя на сайте. Но процесс идет крайне медленно, т.к. времени мало, а работа по преобразованию интерактивных методик обучения в методики самостоятельного изучения очень не простая...
Заказывали - исполнено :)
http://www.parkflyer.ru/57287/blogs/view_entry/9756/
Что за переменные, особенно не понятно, для чего PPM_on;
PPM_off; нужны. Ну и остальные вроде можно догадаться, но если разжуете....
ppm_cur = 0;
PPM_PAUSE;
ppm_cur - указатель на текущий канал в пачке.
ISR (TIMER1_COMPA_vect){ppm_cur = 0;OCR1B = PPM_PAUSE;TCNT1 = 0;PPM_on;}
Так вроде понятно, что в регистр сравнения помещаем длительность паузы, таймер счетчик обнуляем, все это каким то образом связанно с номером канала, плюс во всем этом безобразии участвует модуль захвата ISR.
Но вот какую роль играет PPM_on; совершенно не понятно (пусть она где то и определена). Ну или где то обьявлена ISR (TIMER1_COMPA_vect)? Или назначение этой функции изменение значений регистров?
ISR (TIMER1_COMPA_vect) - объявление обработчика этого прерывания
#define PPM_off PORTB |= (1<<5) //D13
#define PPM_on PORTB &= 0xDF //D13
PPM_off аналог digitalWrite(13, HIGH);
PPM_on аналог digitalWrite(13, LOW);
Прерывание по совпадению А отслеживает длину фрейма. По окончанию одного фрейма происходит инициализация переменных на начальные значения для следующего фрейма.
Прерывание по совпадению В - это канальные импульсы и паузы между ними. какой именно канальный импульс передается определяет переменная ppm_cur.
Проще купить ?
Так берите и покупайте кто вам мешает ?
Что вы лезете в темы тех, для кого самостоятельное изготовления чего либо тоже своего рода хобби ?
Как вы не поймете, что самоделки за частую дороже готового и делаются они потому что нравиться этим заниматься, а не способ с экономить.
Такой вопрос имеем три скетча для формирования массива данных TX чтоб конструкция заработала туда над добавить (в виде закладки) скетч по формированияю выходного сигнала или еще что то надо?
Регистр - 1 я чейка оперативной памяти. Очень быстрой памяти. МК использует регистры в различных целях. Как и какой - читаем даташит. Помнить какой как и что делает - не обязательно. Надо иметь общее представление и уметь читать.
Давайте так. На моем сайте есть форма обратной связи. Напишите там. Конкретизируйте задачу. Спишемся по почте, посмотрим на исходники - помогу, чем смогу.
#define TCCR1B _SFR_MEM8(0x81)
#define CS10 0 А в каких файлах данные установки хранятся, не в присоединяемом файле с типом МК?
В ППМ пачке паузы между импульсами есть const или или чем длинне импульс тем короче пауза?
Пауза в пачке PPM всегда одной длинны. Длина пачки приводится к константе длины фрейма за счет синхроимпульса.
Тут декодер PPM. Обратите внимание на строку
if (d_time>3500)
Суть: если длина импульса > максимальной длины для канального импульса, то это синхроимпульс. Заметьте, его длина точно не указывается именно потому, что она варьируется в зависимости от суммарной длины канальных импульсов.
PPM_offOCR1B = TCNT1 + PPM_PAUSE;ppm_cur++; это и есть содержание макроопределения- PPM_off
;если << - это бинарный сдвиг, то что означает все выражение 1 <<CS11 или CS и указывает на какое количество регистров происходит сдвиг?
uint8_t i - а почему 8 это не количество каналов?
Ну и в алгоритм работы вьхать не могу:
Каждый из таймеров имеет два компаратора и может генерить прерывания по совпадению значений
совпадающие значения откуда должны на компаратор поступит?
Компаратор В будем использовать для формирования канальных импульсов.
че то мозг отказывает компаратор же вроде сравнивает, ну ипри совпадении видимо выдает прерывание.....
Если можно распишите алгоритм, как считается длина импульсов, откуда она берется, как определяется длина фрейма ( длиной прерывания ?) , как второой компаратор формирует канальные импульсы ( 4 раза за фрейм делает пррывания, а в это время подпрограмма захватывает значения длительностей имульса?)
Из недр Arduino IDE выудил следующее:
#define TCCR1B _SFR_MEM8(0x81)
#define CS10 0
#define CS11 1
#define CS12 2
#define WGM12 3
#define WGM13 4
#define ICES1 6
#define ICNC1 7
Т.е. коплитися не 1 <<CS11, а 1<<1. CS11 указывает число бинарных разрядов, на которое надо сдвинуть.
А в программе по факту прерывания основной цикл программы останавливается и выполняется код, определенный для обработчика этого прерывания. ISR (TIMER1_COMPA_vect), например.
Компаратор А выдает прерывания каждые 20 мкс. Факт прерывания считаем началом фрейма и в прерывании инициализируем все переменные. Пакет начинается с паузы, поэтому устанавливаем второй компаратор на время паузы, а выход выключаем.
Когда срабатывает прерывание от компаратора В, смотрим на состояние выхода:
if (PPM_0){
Если на пине низкий уровень, значит была пауза, иначе был сигнал канала.
Если была пауза, то устанавливаем высокий уровень, а компаратор В настраиваем на длительность следующего канала.
Если был сигнал канала, устанавливаем низкий уровень и пишем в компаратор длину паузы.
- "<< "-что обозначает и какой это язык, я все опреаторы С++ перерыл (в мурзилке про Arduino) не нашел
- CS11- это название регистра ?
3<<2 = B00001100
CS11 - это дефайнс, определенный где-то в недрах базовых библиотек Arduino. Я так же определил PPM_on. Суть в том, что это как бы макроподстановка. Компилятор вместо условных обозначений подставит определенный текст.
Да, это хобби требует не малых специальных знаний, усидчивости и т.д., поэтому этим занимаются самые стойкие и глубоко увлеченные люди. Когда промышленный девайс ограничивает возможность творчески расширять его функциональность, вызывает у таких людей негодование и желание снять эти ограничения. Ну сделал, ну полетал вокруг себя, ну насладился FPV ..., а дальше?! А дальше сообщество разделяется на обсолютное большинство конструкторов-фанатов моделей и очень маленький коллетивчик не менее увлеченных электронщиков. Я еще не упамянул летчиков-фанатов - это отдельный разговор. Так что Саша вперед!!! И помни, что мало кто видит границу между здоровым самолюбием и хваставством.
Ценю ваше мнение, но хотелось бы добавить один акцент.
Когда звучит слово ЭЛЕКТРОНЩИК многие пологают, что это уже не МОДЕЛИСТ.
Смею заверить ВСЕХ в таком заблуждении, так как по себе знаю Авиамоделист-Электронщик это АДСКАЯ смесь, и может сделать то чего просто моделист не сделает никогда даже в мыслях. Например - Борт-инженер может легко стать пилотом, а пилот борт-инженером врядли, замучается учиться, ну если только раньше электроникой не занимался.
Ну а сейчас моделистом может стать каждый, купил готовую модель и знать ничего не надо, похоже на ОБЕЗЬЯНу с бананом, а главное всё просто и можно дальше отрафироваться посмеявшись над теми кто это сделал.