Собственно данный проект развитие старой темы с танком. Возникла идея уменьшить размеры пульта, задействовав Digispark. А кроме того, сделать нормальное управление танком, так как в случае вертолета поворот осуществляется за счёт небольшого относительного замедления\ускорения винтов. В случае же танка для эффективного разворота одну из гусениц нужно стопорить полностью.
Исследования я начал с определения несущей частоты, она составила 39.4 кГц, период примерно 25.4 мкс.
А вот с кодированием возникли сложности. Сам сигнал выглядит следующим образом: Что удалось установить. Всего он содержит 24 импульса, причем информацию несет длительность как импульса, так и паузы. Итого получаем длину пакета 47 бит. Первые два бита фиксированной длины - импульс 69 периодов и пауза 15 периодов. Остальные биты это паузы и импульсы длиной 15 или 35 периодов. Алгоритм подсчета контрольной суммы я так и не смог разгадать, корректные команды я подобрал перебором. Вроде как первые шест бит это один мотор, вторые 6 бит второй мотор, но полной уверенности у меня нет. Описания подобного кодирования в интернете я не нашёл. В общем и целом мне кодирование понравилось, так как постоянное чередование длины сигнала уменьшает создаваемые помехи и увеличивает стойкость от помех.
Для управления танком я планировал использовать 4 кнопки, пин5 отпадает, так как при его замыкании на землю чип перезагружается. Таким образом пин 0 у меня отошёл светодиоду, а пины 1,2,3,4 я использовал для кнопок. И тут вылезла проблема пина 3. Чтобы понять причину, взглянем на схему Digispark:
Видно, что пин 3 через резистор подтянут к +5 Вольт, но напряжение ограничено стабилитроном. Это сделано, чтобы компьютер мог распознать Digispark как USB устройство. Но при подключении кнопки и замыкании на землю, напряжение не снизится до достаточного уровня. Из оригинальных решений мне встречалось перевод пина в аналоговый режим и замер уровня. Но это жесть. Правильное решение, отладить скетч без кнопки на третьем пине, а после достижения требуемого результата выпаять резистор, он в обведённых с маркировкой 102.Но и это ещё не всё. Свой пульт я решил запитать от павер банка. А как оказалось, павер банк держит на выводах данных постоянное напряжение, это пины 3 и 4 Digispark. И кнопки от этого не работали как надо. В итоге пришлось выпаять и резисторы на 68 Ом, маркировка 680. После этого все кнопки заработали идеально. Конечно, для перепрошивки придётся резисторы впаять на место.
Алгоритм борьбы с дребезгом контактов был описан в статье про PWM регулятор. Вкратце - постоянно считывается состояние кнопок и раз в 25 мс (переменная press_voting) подводятся итоги, если больше половины времени кнопка была нажата, значит она реально нажата.
Сперва стояла задача воспроизвести несущую частоту. Здесь без осциллографа не обойтись, нужно смотреть длительность импульса в оригинальном пульте и добиться такой же от Digispark. Программно можно устанавливать паузу только до микросекунды, а требуется установка периода с точностью до одной десятой. Эта проблема решена переменной popravka - каждый импульс она увеличивается на подобранный коэффициент, и при превышении порога длительность импульса увеличивается на микросекунду. Таким образом удалось добиться полного соответствия частоты несущей.
Следующая проблема, которую пришлось решить - сканирование кнопок. Оно занимает немалое количество процессорного времени. С одной стороны, и с другой кнопки надо опрашивать как можно чаще для отзывчивости системы управления. Как показали замеры осциллографом, сканирование состояния 4х кнопок как раз занимает длительность одного периода несущей - примерно 25.4 мкс. В итоге сканирование кнопок делается в период паузы между импульсами, когда не нужно излучать сигнал. То есть паузы 1 (35 импульсов) и 0 (15 импульсов) по факту после сканирования кнопок делаются 34 и 14 импульсов.
Также пришлось подобрать минимальную паузу между командами. Если слать команды без паузы, приёмник начинает их игнорировать. Скетч приведен ниже:
int popravka = 0; const int naklad = 5; const int flash_len = (15 - naklad); const int after_len = (10 - naklad); const byte cmd_max_stop[6] = { 0, 0, 104, 231, 14, 24}; //0000 0 const byte cmd_min_rigt[6] = { 0, 32, 186, 3, 0, 0}; //0001 1 const byte cmd_max_rigt[6] = { 0, 252, 3, 0, 0, 0}; //0010 2 const byte cmd_min_left[6] = { 16, 3, 182, 3, 0, 0}; //0100 4 const byte cmd_max_left[6] = { 254, 1, 0, 0, 0, 0}; //1000 8 const byte cmd_max_frwd[6] = { 255, 254, 1, 0, 0, 0}; //1010 10 const byte cmd_min_frwd[6] = { 16, 35, 116, 3, 0, 0}; //0101 5 const byte cmd_some0[6] = { 251, 7, 0, 0, 0, 0}; const byte cmd_some2[6] = { 245, 192, 59, 47, 5, 9}; const byte cmd_some3[6] = { 95, 88, 2, 110, 104, 10}; byte mode; unsigned long votes1=0, votes2=0, votes3=0, votes4=0; const unsigned long press_voting = 25000; unsigned long loop_time = 0; unsigned long last_loop_time = 0, last_election = 0; void setup() { pinMode(0, OUTPUT); pinMode(1, INPUT); pinMode(2, INPUT); pinMode(3, INPUT); pinMode(4, INPUT); digitalWrite(0, LOW); mode = 0; sendit(cmd_max_frwd); sendit(cmd_max_stop); delay(100); sendit(cmd_max_frwd); sendit(cmd_max_stop); delay(100); sendit(cmd_max_frwd); sendit(cmd_max_stop); delay(100); sendit(cmd_max_left); sendit(cmd_max_stop); delay(100); sendit(cmd_max_rigt); sendit(cmd_max_stop); delay(100); } void scan_button(){ loop_time = micros(); unsigned long cycle_length = (loop_time - last_loop_time); if( digitalRead(1) == HIGH ) votes1 += cycle_length; if( digitalRead(2) == HIGH ) votes2 += cycle_length; if( digitalRead(3) == HIGH ) votes3 += cycle_length; if( digitalRead(4) == HIGH ) votes4 += cycle_length; if( (loop_time - last_election) >= press_voting ){ mode = 0; if( votes1 >= (press_voting >> 1) ) mode += 1; if( votes2 >= (press_voting >> 1) ) mode += 2; if( votes3 >= (press_voting >> 1) ) mode += 4; if( votes4 >= (press_voting >> 1) ) mode += 8; votes1 = 0; votes2 = 0; votes3 = 0; votes4 = 0; last_election = loop_time; } last_loop_time = loop_time; } void seria(int how_much, int state){ if( state == LOW ){ scan_button(); how_much -= 1; } while(how_much > 0){ digitalWrite(0, state); delayMicroseconds(flash_len); digitalWrite(0, LOW); popravka += 30; if( popravka >= 100 ){ popravka -= 100; delayMicroseconds(after_len + 1); }else{ delayMicroseconds(after_len ); } how_much--; } } void sendit(const byte bytes[]){ byte c; seria(69, HIGH);//const seria(15, LOW);//const for(c = 0; c < 6; c++){ if( (bytes[c] & B00000001) > 0 ) seria(35, HIGH); else seria(15, HIGH); if( (bytes[c] & B00000010) > 0 ) seria(35, LOW); else seria(15, LOW); if( (bytes[c] & B00000100) > 0 ) seria(35, HIGH); else seria(15, HIGH); if( (bytes[c] & B00001000) > 0 ) seria(35, LOW); else seria(15, LOW); if( (bytes[c] & B00010000) > 0 ) seria(35, HIGH); else seria(15, HIGH); if( (bytes[c] & B00100000) > 0 ) seria(35, LOW); else seria(15, LOW); if( (bytes[c] & B01000000) > 0 ) seria(35, HIGH); else seria(15, HIGH); if( (bytes[c] & B10000000) > 0 ) seria(35, LOW); else seria(15, LOW); } } void loop() { switch(mode){ case 1: sendit(cmd_min_rigt); break; case 2: sendit(cmd_max_rigt); break; case 4: sendit(cmd_min_left); break; case 8: sendit(cmd_max_left); break; case 5: sendit(cmd_min_frwd); break; case 10: sendit(cmd_max_frwd); break; default: sendit(cmd_max_stop); } for(byte c = 0; c < 32; c++){ seria(35, LOW); } } //const long freq = 39400; 25.4 micros //35 = 885 mcs; 15+15 = 745..760 mcs
Более интересным мне видится создание приёмника на Digispark. И создать пару приёмник\пульт. Это вполне реально, когда-нибудь наверно это реализую.