История создания еще одного робота. Часть вторая, «it's alive!»

Продолжаю серию публикаций о создании простого колесного робота на микроконтроллере ATmega16A.
Во второй части моей публикации я опишу процесс создания и сборки своего робота. Начнем с изготовления печатной платы и закончим видео первых шагов (правильней сказать — прокручивания колес) нашего устройства. Также уделю внимание первому опыту программирования под PC в Qt, а именно созданию программы управления и обмена данными с роботом по Bluetooth.
Если хотите, можете ознакомится с первой публикацией и узнать с чего все началось, ну а всех остальных прошу под кат.




Прошлая часть закончилась на том, что перед нами красовалась готовая разводка печатной платы, а значит, пора творить и переносить нашу задумку из электронной версии в реальный осязаемый мир, разве не это желание теплится в голове у каждого радиолюбителя, инженера, изобретателя?! У меня на руках был только 1мм текстолит, так что я недолго думая начал процесс переноса на плату рисунка дорожек стандартным ЛУТом.

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




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





80-90% дорожек на плате получились хорошо, но т.к. в основном дорожки были 0.2мм, попались протравы, которые я устранил с помощью припоя и тонких проводников, благодаря им же сделал переходные отверстия. Вообще весь процесс пайки занял 3 вечера.





Моторы пришлось перенести на нижнюю часть платы, так как понял, что мне нужен максимальный зазор между платой и поверхностью по которой нужно будет ездить, но и все равно робот получился с низкой посадкой.
И раз уж мы вспомнили про моторы, то вспомним и об идее с энкодером, так как магниты, которые я заказывал, были в диаметре буквально на пол миллиметра меньше пазов в колесе, то их нужно было как-то там зафиксировать, а как мы знаем все гениальное просто — я просто взял и загнал их туда, залив при этом из клеевого пистолета, не очень эстетично, зато быстро и практично. Главное не перепутать полярность, а то будут косяки с точностью, а она и так небольшая — 12 магнитов на колесо, т.е. 6 срабатываний за поворот, а с диаметром колес в 43мм получаем примерно 22мм пройденного колесом пути за одно срабатывание.



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



Для крепления аккумулятора, сервы и УЗ дальномера были распечатаны на 3D принтере простейшие детали.

Держатель аккумулятора.


Держатель сервы.


Держатель УЗ дальномера.


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





И да, это просто бусина, бусина на толстой проволоке, и это пока единственная нерешенная «проблема» конструкции. На самом деле не брался за решение вопроса о третьем колесе, ибо загорелся подрыгать колесами, повертеть сервой и т.п.
Из за использования 1мм текстолита робот кажется хлюпким, но так как масса у нас небольшая это никак не влияет на его прочность и маневренность.
Ну и первым делом захотелось прокатиться по линии, так сказать первый опыт с автоматизированным управлением.
Сразу скажу, что я не программист по профессии и это наверно моя самая слабая точка в хобби, так что прошу не сильно критиковать код, в любом случае я уверен, что нужно будет переписывать всю программу в будущем.

Была написана тестовая программа, которая реализовывала PD алгоритм. Она состояла из обработчика прерываний АЦП и собственно основного цикла работы с данными.

Обработчик прерывания
<code>#define FIRST_ADC_INPUT 2 #define LAST_ADC_INPUT 7  unsigned int real_adc[8]={0,0,0,0,0,0,0,0}; unsigned char sample_adc; volatile unsigned char adc_ready = 0; unsigned char leds[8]={0x21,0x41,0x61,0x63,0x23,0x43}; unsigned char adc_inputs[8]={0,1,2,4,6,7,3,5};  interrupt [ADC_INT] void adc_isr(void)                     //////////////ADC_INT { static unsigned char input_index=0;  if (adc_ready == 0){      if (sample_adc == 0) {         real_adc[input_index]=(signed int)(ADCW);         if (input_index < LAST_ADC_INPUT-FIRST_ADC_INPUT){             input_index++;         } else {             input_index = 0;             PORTB=leds[input_index];              sample_adc = 1;                         }         ADMUX=(ADC_VREF_TYPE & 0xff)+adc_inputs[(FIRST_ADC_INPUT+input_index)];         ADCSRA|=0x40;                       } else {         if (adc_ready==0) {             if (ADCW > real_adc[input_index]){                     real_adc[input_index]=(signed int)(ADCW);                  } else {                 real_adc[input_index]=(signed int)(real_adc[input_index]);             }                                                              if (input_index < (LAST_ADC_INPUT-FIRST_ADC_INPUT)){                input_index++;                PORTB=leds[input_index];               } else {                input_index = 0;                adc_ready = 1;                 PORTB&=~(1<<0);             }                                            ADMUX=(ADC_VREF_TYPE & 0xff)+adc_inputs[(FIRST_ADC_INPUT+input_index)];          }          if (adc_ready == 0){             ADCSRA|=0x40;         }     }   } } </code>

Реализация PID
<code>if (adc_ready == 1) {         adc_l = (real_adc[1]+real_adc[2]); 	adc_r = (real_adc[3]+real_adc[4]); 					 	error = (adc_l-adc_r); 	delta_error = error - old_error; 	//sum_error += error; 	PID = Kp*error + Kd*delta_error + Ki*sum_error; 	old_error = error; 					 	if (PID > 0) { 		pwr_l += (signed int)PID ; 		pwr_r -= (signed int)PID ; 	} else {  		pwr_l += (signed int)PID ; 		pwr_r -= (signed int)PID ; 	}   	for(i=0; i < (LAST_ADC_INPUT-FIRST_ADC_INPUT)+1; i++){                  real_adc[i]=0;          }                                     ADCSRA |= 0x40; adc_ready = 0; sample_adc = 0;     } </code>


Как же я был рад видеть, что робот выполняет возложенную на него функцию, не так конечно как на крутых видеороликах с абсолютно плавными и быстрыми linefolower'ами, но для меня даже такой результат был достижением.



После этого я немного успокоился с частью программирования самого микроконтроллера и понял, что в будущем нужно будет реализовать программу управления роботом с ПК и принялся за изучение Qt, а так как последнюю программу для ПК я писал только в университете (на паскале) и это была какая-то стандартная лабораторная по информатике, то мои знания были в районе нуля.

оффтоп, о моих грандиозных планах управления роботом с смартфона и о том как я сдался и забил на эту идеюВообще я изначально загорелся желанием написать программу для мобильного телефона на Android, но изучать Java не захотел, вернее не хотел уходить от Си, да и есть у меня друг, который шарил в Qt и у него можно было много чего спросить. Сначала я сам пытался постичь дзен работы с Qbluetooth и попытками состыковаться с HC-05 с помощью моего китайского Jiayu g3, но каждый раз натыкался на проблему, которая не позволяла HC-05 и Jiayu дружить. Сначала грешил на Qbluetooth и тем, что он не воспринимает HC-05, но в интернете нашел информацию о том, что люди запускают обмен данными через линейку блютуз модулей HC с помощью QBluetooth или пишут свои библиотеки, после жалоб другу о своей тяжелой жизни, друг за один день написал программу и все же смог обмениваться данными с роботом, но к сожалению через свой планшет. В итоге обосновав все тем что, мой китаец не поддерживает rfcomm, я сдался и решил писать все для ПК.


Программа должна была переключать режимы работы робота (следование по линии, управление с клавиатуры, автономный режим) и выдавать(задавать) данные со всех интересующих датчиков (сервы, датчики линии, УЗ дальномер, моторы, энкодеры).
После пары недель, была написана первая версия программы контрольной панели управления роботом. Очень понравилась работа с GUI в Qt — удобно.



Код программы лучше не буду вставлять, дабы меня не забанили за издевательство над Си. Вкратце скажу, что программа просто по сигналу наличия данных в QSerialPort берет их и рассовывает по лэйблам и виджетам, в ответ по таймеру, если нужно, отправляет задающие данные, такие как положение сервы и скорость моторов. Зеленое поле в программе это в мечтах — будущая карта локации перед роботом.

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

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

П.С. В будущем постараюсь написать еще одну часть публикации, так сказать по мере продвижений в программной части.

Источник: habrahabr.ru/post/256209/