Пульт инфракрасного дистанционного управления на Digispark

 
    03.04.2021

Дешифровка кода управления

    Собственно данный проект развитие старой темы с танком. Возникла идея уменьшить размеры пульта, задействовав Digispark. А кроме того, сделать нормальное управление танком, так как в случае вертолета поворот осуществляется за счёт небольшого относительного замедления\ускорения винтов. В случае же танка для эффективного разворота одну из гусениц нужно стопорить полностью.

    Исследования я начал с определения несущей частоты, она составила 39.4 кГц, период примерно 25.4 мкс.

А вот с кодированием возникли сложности. Сам сигнал выглядит следующим образом:
Что удалось установить. Всего он содержит 24 импульса, причем информацию несет длительность как импульса, так и паузы. Итого получаем длину пакета 47 бит. Первые два бита фиксированной длины - импульс 69 периодов и пауза 15 периодов. Остальные биты это паузы и импульсы длиной 15 или 35 периодов. Алгоритм подсчета контрольной суммы я так и не смог разгадать, корректные команды я подобрал перебором. Вроде как первые шест бит это один мотор, вторые 6 бит второй мотор, но полной уверенности у меня нет. Описания подобного кодирования в интернете я не нашёл. В общем и целом мне кодирование понравилось, так как постоянное чередование длины сигнала уменьшает создаваемые помехи и увеличивает стойкость от помех.

   

Проблема использования пина 3

    Для управления танком я планировал использовать 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, а именно 50 мА. Кнопки притягиваются к земле любыми резисторами 1-2 кОм. Как описал выше, резисторы USB интерфейса пришлось выпаять. Пример работы пульта на видео. В конце я походу новый вид противотанковых заграждений придумал:
Как-то облагораживать пульт не планирую. Какое дальнейшее развитие темы возможно. Приложение на смартфон и ИК светодиоды вместо наушников. В теории можно, но вероятно оно того не стоит.

    Более интересным мне видится создание приёмника на Digispark. И создать пару приёмник\пульт. Это вполне реально, когда-нибудь наверно это реализую.

   

Фотогалерея