Увидел на 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();
}