Автомобльная магнитолла JVC KD-X362BT, Kia Rio 3 2011 и кнопки на руль

Увидел на Ali кнопки на руль для своего авто, и руки зачесались. Кнопки приехали, магнитолу выдернул, теорию изучил. Пора приступать к практике.
Кнопки на руль у Kia Rio — резистивные. А управление магнитолой по зелено-голубому проводу — цифровое. Это я понял из многочисленных статей в рунете. Те кто в итоге подружил JVC и резистивные кнопки на руле использовали покупные адаптеры-преобразователи. Но мы его сами соберем на Arduino, надо только узнать какой протокол управления используется. Кое как нашел заметку на буржуйском Reddit, а в ней нашлось самое главное.

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

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

 

/*
Цифровое дистанционное управление для магнитоллы JVC

Пульт дистанционного управления на рулевом колесе на магнитоле (синежелтый провод) подключен 
к подтягивающему резистору в схеме радиоприемника. Данные отправляются в виде модуляции 
с интервалом импульсов, то есть интервал после импульса определяет, отправляем ли мы 0 или 1.
Импульсы отправляются путем заземления входа радио.
Я управляю оптопарой, чтобы заземлить вход радиомодуля, поэтому ВЫСОКИЙ выход Arduino соответствует НИЗКОМ радиовходу (= импульс).
Когда я говорю о ВЫСОКОМ или НИЗКОМ уровне, я говорю о выходе Arduino.
Спецификации протокола:
Ширина импульса 527,5 мкс
Импульсный интервал для отправки 0 1055.0 мкс (ВЫСОКИЙ для ширины 1 импульса, НИЗКИЙ для ширины 1 импульса)
Импульсный интервал для отправки 1 2110.0 мкс (ВЫСОКИЙ для ширины 1 импульса, НИЗКИЙ для 3 ширины импульса)
Примечание: поскольку функция delayMicroseconds () принимает только целые числа без знака, мы используем длительность импульса 527 мкс
Пакеты данных строятся следующим образом:
HEADER всегда одинаковый: НИЗКИЙ (1 импульс), ВЫСОКИЙ (16 импульсов), НИЗКИЙ (8импульсов)
START BIT всегда 1
ADDRESS 7-битный (от 0x00 до 0x7F), сначала отправляем LSB, всегда 0x47 для JVC KD-R531, вероятно, то же самое для всех автомобильных радиоприемников JVC
COMMAND 7-битная (от 0x00 до 0x7F), сначала отправляем LSB, см. Следующий раздел для списка известных команд для JVC KD-R531
STOP BIT всегда 1, 1
ADDRESS, COMMAND и STOP BIT повторяются 3 раза, чтобы убедиться, что магнитолла правильно их принимает. Убрал, и так нормально работает.
Известные команды для JVC KD-R531:
HEX DEC BIN (7b) Название
0x04 4 0000100 Громкость +
0x05 5 0000101 Громкость -
0x08 8 0001000 Выбор источника (циклично)
0x0D 13 0001101 Выбор предустановленой настройки эквалайзера (циклично)
0x0E 14 0001110 Выключатель звука / воспроизведение/пауза
0x12 18 0010010 Автопоиск тюнера вперед / Трек вперед - короткое нажатие, и ручная настройка тюнеравперед / Перемотка вперед - с нажатием и удержанием
0x13 19 0010011 Автопоиск тюнера назад / Трек назад - короткое нажатие, и ручная настройка тюнера вперед / Быстрая перемотка назад - с нажатием и удержанием
0x14 20 0010100 Предустановка тюнера + / Папка USB +
0x15 21 0010101 Предустановка тюнера - / Папка USB -
0x1A 26         Голосовое управление
0x37 55 0110111 НЕИЗВЕСТНО, похоже, это своего рода сброс, а также проверка дисплея
0x58 88 1011000 НЕИЗВЕСТНО, отображает «SN WRITING», где WRITING мигает

*/

// Определяем команды
#define VOLUP 0x04
#define VOLDOWN 0x05
#define SOURCE 0x08
#define EQUALIZER 0x0D
#define MUTE 0x0E
#define TRACKFORW 0x12
#define TRACKBACK 0x13
#define FOLDERFORW 0x14
#define FOLDERBACK 0x15
#define VOICE 0x1A
#define UNKNOWN1 0x37       //типа теста дисплея чтоли...
#define UNKNOWN2 0x58       //SN WRITING / SN NG

// Подключаем провод с рулевого колеса к аналоговому пину
#define WHELLPIN A5

// Подключаем вход оптопары или транзистор, или вообще провод магнитоллы через резистор 1 кОм к этому выводу
#define OUTPUTPIN 3 // D8

// Встроенный светодиод, полезный для отладки
#define LEDPIN 13 // D13

// Ширина импульса в мкс
#define PULSEWIDTH 527

// Адрес, на который отвечает радио
#define ADDRESS 0x47


void setup() {
//  Serial.begin(9600);
  pinMode(OUTPUTPIN, OUTPUT);     // Переключаем выходной пин в режим выхода
  digitalWrite(OUTPUTPIN, LOW);   // Записываем низкий уровень
  pinMode(WHELLPIN, INPUT);       // Настраиваем пин к которому подключены кнопки руля как вход
  digitalWrite(WHELLPIN, HIGH);   // Подтягиваем его к питанию (тоесть подключаем встроенный в МК резистор на 20кОм к шине питания)
  pinMode(LEDPIN, OUTPUT);        // Настраиваем пин светодиода как выход...
  digitalWrite(LEDPIN, LOW);      // ...и выключаем светодиод
//  for (int i = 0; i <= 7; i++) {  // Помигаем светодиодом в знак готовности
//   delay(100);
//    digitalWrite(LEDPIN, !digitalRead(LEDPIN));
//  }
//  delay(100);
//  digitalWrite(LEDPIN, LOW);      // Убедимся что светодиод выключен
}

/*  
Пульт дистанционного управления на рулевом колесе имеет 8 кнопок и 2 провода.
Один провод подключен к GND, второй приходит на разъем магнитоллы. 
При нажатии на кнопку на руле, подключается резистор соответствующего номинала.
Вот измереные мной номиналы кнопок руля для Kia Rio 3 2013 купленые на AliExpress и 
результаты измерения микроконтроллера при нажатой кнопке:

Кнопка        Сопротивление   Значение считаное МК
UP            0,434 kOhm      26-29
DOWN          1,115 kOhm      43-46
TEL_UP        40,9 kOhm       534-538
TEL_DOWN      18,85 kOhm      351-355
VOLUP         4,58 kOhm       125-128
VOLDOWN       6,77 kOhm       170-173
MODE          2,09 kOhm       68-71
VOICE         10,67 kOhm      239-242

Значения считаны на тестовом стенде, при установке кнопок в автомобиль значения могут немного поменяться из-за того, что
проводка автомобиля от кнопок руля до магнитоллы тоже имеет свое сопротивление.

Функция GetInput() будет считывать аналоговое значение с пина к которому подключены кнопки
и в зависимости от значения будет выдавать соответствующее название кнопки
*/

unsigned char GetInput(void) {
// В этой функции читаем значение на пине к которому подключен провод с руля и сравниваем его с расчетными значениями
// В соответствии со сравнением возвращаем название нажатой кнопки
// Значения проверяем от самого мальнького до самого большого, поэтому названия кнопок не попорядку

  int analog_key = 1023;
  analog_key = analogRead(WHELLPIN);
  if (analog_key < 600) {
    delay(50);
    analog_key = analogRead(WHELLPIN);
  }

  if (analog_key < 35) {                //Если нажата кнопка UP
    return TRACKFORW;                      //Возвращаем TRACKFORW
  }

  if (analog_key < 50) {                //DOWN
    return TRACKBACK;
  }

  if (analog_key < 75) {                //MODE
    delay(300);                            //делаем задержку, и 
    analog_key = analogRead(WHELLPIN);     //замеряем еще раз
    if (analog_key < 75) {
      if (analog_key > 60) {
        return EQUALIZER;                  //и если зажата та же кнопка, то возврящаем EQUALIZER
      }
    }
    return SOURCE;                         //иначе нажатие было короткое, возвращаем SOURCE
  }

  if (analog_key < 130) {               //VOLUP
    return VOLUP;
  }

  if (analog_key < 175) {               //VOLDOWN
    return VOLDOWN;
  }

  if (analog_key < 245) {               //VOICE
    delay(300);                            //делаем задержку, и 
    analog_key = analogRead(WHELLPIN);     //замеряем еще раз
    if (analog_key < 245) {
      if (analog_key > 225) {
        return VOICE;                      //и если зажата та же кнопка, то возврящаем VOICE
      }
    }
    return MUTE;
  }

  if (analog_key < 360) {               //TEL_DOWN
/*    delay(300);                          //делаем задержку, и 
    analog_key = analogRead(WHELLPIN);     //замеряем еще раз
    if (analog_key < 360) {
      if (analog_key > 345) {
        return UNKNOWN2;                   //и если зажата та же кнопка, то возврящаем UNKNOWN2
      }
    }
*/
    return FOLDERFORW;
  }

  if (analog_key < 540) {                //TEL_UP
/*    delay(300);                           //делаем задержку, и 
    analog_key = analogRead(WHELLPIN);      //замеряем еще раз
    if (analog_key < 540) {
      if (analog_key > 530) {
        return UNKNOWN1;                    //и если зажата та же кнопка, то возврящаем UNKNOWN1
      }
      
    }
*/
    return FOLDERBACK;
  }
    
  return 0;
}

void loop() {
  unsigned char Key = GetInput();  // If any buttons are being pressed the GetInput() function will return the appropriate command code

  if (Key) {  // If no buttons are being pressed the function will have returned 0 and no command will be sent
//    Serial.println(Key);
    SendCommand(Key);
  }
}

// Send a value (7 bits, LSB is sent first, value can be an address or command)
void SendValue(unsigned char value) {
  int i, tmp = 1;
  for (i = 0; i < sizeof(value) * 8 - 1; i++) {
    if (value & tmp)  // Do a bitwise AND on the value and tmp
      SendOne();
    else
      SendZero();
    tmp = tmp << 1; // Bitshift left by 1
  }
}

// Send a command to the radio, including the header, start bit, address and stop bits
void SendCommand(unsigned char value) {
//  int i;
  Preamble();                         // Send signals to precede a command to the radio
//  for (i = 0; i < 3; i++) {           // Repeat address, command and stop bits three times so radio will pick them up properly
    SendValue(ADDRESS);               // Send the address
    SendValue((unsigned char)value);  // Send the command
    Postamble();                      // Send signals to follow a command to the radio
//  }
}

// Signals to transmit a '0' bit
void SendZero() {
  digitalWrite(OUTPUTPIN, HIGH);      // Output HIGH for 1 pulse width
  digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  delayMicroseconds(PULSEWIDTH);
  digitalWrite(OUTPUTPIN, LOW);       // Output LOW for 1 pulse width
  digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  delayMicroseconds(PULSEWIDTH);
}

// Signals to transmit a '1' bit
void SendOne() {
  digitalWrite(OUTPUTPIN, HIGH);      // Output HIGH for 1 pulse width
  digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  delayMicroseconds(PULSEWIDTH);
  digitalWrite(OUTPUTPIN, LOW);       // Output LOW for 3 pulse widths
  digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  delayMicroseconds(PULSEWIDTH * 3);
}

// Signals to precede a command to the radio
void Preamble() {
  // HEADER: always LOW (1 pulse width), HIGH (16 pulse widths), LOW (8 pulse widths)
  digitalWrite(OUTPUTPIN, LOW);       // Make sure output is LOW for 1 pulse width, so the header starts with a rising edge
  digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  delayMicroseconds(PULSEWIDTH * 1);
  digitalWrite(OUTPUTPIN, HIGH);      // Start of header, output HIGH for 16 pulse widths
  digitalWrite(LEDPIN, HIGH);         // Turn on on-board LED
  delayMicroseconds(PULSEWIDTH * 16);
  digitalWrite(OUTPUTPIN, LOW);       // Second part of header, output LOW 8 pulse widths
  digitalWrite(LEDPIN, LOW);          // Turn off on-board LED
  delayMicroseconds(PULSEWIDTH * 8);  // START BIT: always 1
  SendOne();
}

// Signals to follow a command to the radio
void Postamble() {  // STOP BITS: always 1
  SendOne();
  SendOne();
}

 

Опубликовано
В рубрике Arduino Отмечено