Aktualizacje:
[2018-04-27] - Drobne poprawki
Jakiś czas temu postanowiłem wymienić standardowe radio Ford 6000CD na radio 1dinowe Kenwood. Zrobiłem tak, przez słabą jakoś dźwięku radia oryginalnego i brak portu USB (nie jest zbyt wygodne szukanie i wymienianie płyt CD podczas jazdy).
Po tym zabiegu po jakimś czasie zaczęło mi brakować obsługi radia poprzez sterowanie przy kierownicy (nie jest ono kompatybilne z radiami Kenwood).
Zdecydowałem się zrobić adapter, który byłby podłączony pomiędzy sterowaniem z kierownicy a moim radiem. Można powiedzieć, nie ma problemu, wystarczy kupić taki adapter w necie. Tylko jak się okazuje, takie adaptery kosztują trochę (ok 100-150zł) a jak zobaczycie, można zrobić go niższym kosztem (ok 15zł).
Ten poradnik odnosi się do sterowania Ford'owskiego i radia Kenwood (moje radio to model KMM-122Y z 2017 roku) jak na zdjęciach poniżej. Może być przydatny dla innego sterowania na rezystorach ale wtedy należy zmienić część odpowiedzialną za odczytywanie napięcia (ADC).
![]() |
![]() |
| Rysunek 1. Sterowanie z kierownicy (Ford) | Rysunek 2. Przewód zdalnego sterowania radia Kenwood |
Co będzie potrzebne do zbudowania adaptera:
Sterowanie w kierownicy bazuje na przełączanych rezystancjach (przyciski odłączają różne rezystancje) jak widać na Rysunku 3.
![]() |
| Rysunek 3. Schemat sterowania w kierownicy |
Należy zrobić dzielnik napięcia ze sterowaniem z kierownicy wpiętym jako jeden z rezystorów, podłączyć wykonany dzielnik do źródła napięcia i odczytać wartość na przetworniku AC mikrokontrolera.
![]() |
| Rysunek 4. Schemat dzielnika napięcia |
W dzielniku napięcia przedstawionym na Rysunku 3 użyłem rezystora o wartości 1.47 kΩ ponieważ dawał niezły zakres mierzonego napięcia przy napięciu wejściowym 5V.
W miejsce rezystora R5 należy wpiąć sterowanie z kierownicy.
W Tabeli 1 umieściłem zmierzone rezystancje sterowania z kierownicy i odpowiadające im wartości napięcia na dzielniku napięcia.
| Funkcja | Rezystancja [Ω] | Napięcie na dzielniku [V] |
|---|---|---|
| NC | 5040 | 3.87 |
| SEL | 1050 | 2.08 |
| SEEK- | 562 | 1.38 |
| SEEK+ | 301 | 0.85 |
| VOL+ | 147.8 | 0.46 |
| VOL- | 54.4 | 0.18 |
Schemat całego układu przedstawiony jest na Rysunku 5. Jak widać jest to dosyć prosty układ.
![]() |
| Rysunek 5. Schemat adaptera |
W miejscu złącza J3 można dać przycisk reset (w tym układzie nie jest to za bardzo potrzebne).
Piny sterowania w kierownicy na złączu FAKRA są widoczne na Rysunku 6 (piny 6 i 8 w części B).
![]() |
| Rysunek 6. Złącze Ford FAKRA |
W gnieździe (czarna kostka w dole z prawej strony) należy wpiąć w miejsca jak na Rysunku 7. To połączenie było zrobione tylko do testów, w końcowym urządzeniu należy to zrobić bardziej solidnie, żeby nie wypadło ze złączki podczas jazdy.
![]() |
| Rysunek 7. Złącze sterowania z kierownicy |
Okazuje się, że sygnał do sterowania radiem Kenwood to protokół NEC, taki sam jak w pilotach zdalnego sterowania tegoż producenta, tylko odwrócony i bez nośnej. Dzięki temu, że nie jest to sygnał na bazie rezystancji, nie trzeba używać na przykład cyfrowego potencjometru.
Informacje o protokóle NEC:
Jak działa transmisja sygnału NEC:
![]() |
| Rysunek 8. Protokół ramki wiadomości NEC |
Należy wygenerować w mikrokontrolerze cyfrowy sygnał w formacie NEC, odpowiadający przyciśniętemu przyciskowi. Sygnał ten, wysłany przez cyfrowe wyjście mikrokontrolera (PB1) będzie włączał i wyłączał tranzystor Q1. Do kolektora tranzystora podłączony jest przewód remote radia.
Kod adresu radia Kenwood: 0xb9
Kody poleceń:
| Remote | Polecenie | Remote | Polecenie | Remote | Polecenie |
|---|---|---|---|---|---|
| 0 | 0x00 | 8 | 0x08 | volume + | 0x14 |
| 1 | 0x01 | 9 | 0x09 | volume - | 0x15 |
| 2 | 0x02 | track - | 0x0a | mute | 0x16 |
| 3 | 0x03 | track + | 0x0b | tuner | 0x1c |
| 4 | 0x04 | rev | 0x0c | tape | 0x1d |
| 5 | 0x05 | ff | 0x0d | cd | 0x1e |
| 6 | 0x06 | play/pause | 0x0e | cd-md-ch | 0x1f |
| 7 | 0x07 | source | 0x13 | dnpp | 0x5e |
Przykładowy format danych dla sygnału volume+ :
| Dane do przesłania | |||
|---|---|---|---|
| Addr | ~Addr | Comm | ~Comm |
| 0xB9 | 0x46 | 0x14 | 0xEB |
| 10111001 | 01000110 | 00010100 | 11101011 |
| LSB | |||
| 0x9D | 0x62 | 0x28 | 0xD7 |
| 10011101 | 01100010 | 00101000 | 11010111 |
Ostateczny wygląd polecenia:
Ten kod należy wysłać (używając protokołu NEC z sygnałami początki i końca) na tranzystor Q1.
I to by było na tyle. Jak widać, można zrobić taki adapter ok 10 razy mniejszym kosztem niż kupno takiego w sklepie (bez ceny gniazd, kabli i obudowy).
Możliwe, że jeszcze uaktualnię ten poradnik, w programie mikrokontrolera można dodać jeszcze obsługę powtórzeń przycisków.
Poniżej jest program w C do mikrokontrolera (Atmega8).
pilot1.c
/* pilot1.c * Ford SWC to Kenwood radio (NEC protocol) adapter * Author: Michal Babik <michalb1981@o2.pl> */ // Steering wheel remote resistances with a half values to use // in adc as a compare values // // REMOTE KEY RESISTANCE ADC VAL KEY VAL // NC 5040.0 198 0 // 152 // SEL 1050.0 106 1 // 88 // SEEK - 562.0 70 2 // 56.5 // SEEK + 301.0 43 3 // 33 // VOL + 147.8 23 4 // 16 // VOL - 54.4 8 5 // 4 #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> #include <inttypes.h> #define sbi(x,y) x |= _BV(y) //set bit - using bitwise OR operator #define cbi(x,y) x &= ~(_BV(y)) //clear bit - using bitwise AND operator #define tbi(x,y) x ^= _BV(y) //toggle bit - using bitwise XOR operator #define is_high(x,y) (x & _BV(y) == _BV(y)) //check if the y'th bit of //register 'x' is high ... test if its AND with 1 is 1 /* _BV(a) is a macro which returns the value corresponding to 2 to the * power 'a'. Thus _BV(PX3) would be 0x08 or 0b00001000 */ #define NEC_TIME 562.5f // base time in us #define NEC_LINE_HI 1 // pin value to set line high #define NEC_LINE_LO 0 // pin value to set line low #define NEC_PORT PORTB // kenwood radio remote pin port #define NEC_PIN PB1 // kenwood radio remote pin #define NEC_DDR DDRB // kenwood radio remote direction #define ADC_SAMPLES 50 // number of samples to read pressed key // ---------------------------------------------------------------------------- uint8_t adc_read (void); uint8_t check_pressed_adc_value (uint8_t adc_v); void couple_more_samples (uint8_t *k_pressed); void nec_set_pin (uint8_t hilo); void nec_base (uint8_t hm, uint8_t lm); void nec_start (void); void nec_finish (void); void nec_one (void); void nec_zero (void); void nec_8bit (uint8_t *bits); void nec_data (uint8_t addr, uint8_t dta); // ---------------------------------------------------------------------------- int main (void) { /* uint8_t k_codes[] = {0x00, // 0 0x01, // 1 0x02, // 2 0x03, // 3 0x04, // 4 0x05, // 5 0x06, // 6 0x07, // 7 0x08, // 8 0x09, // 9 0x0a, // track - 0x0b, // track + 0x0c, // rev 0x0d, // ff 0x0e, // play / pause 0x13, // source 0x14, // volume + 0x15, // volume - 0x16, // mute 0x1c, // tuner 0x1d, // tape 0x1e, // cd 0x1f, // cd-md-ch 0x5e // dnpp }; */ uint8_t ken_code = 0xb9; uint8_t key_codes[] = {0x00, 0x13, 0x0a, 0x0b, 0x14, 0x15}; uint8_t key_pressed = 0; uint8_t prev_pressed = 0; NEC_DDR = _BV (NEC_PIN); // Communication pin as output cbi (NEC_PORT, NEC_PIN); // set nec pin low ADMUX= (1 << REFS0) | (1 << ADLAR); //AVCC, left align // only 8 highest adc bits will be used, there is no need for a 10 bit precision ADCSRA= (1 << ADEN) | (1 << ADPS2); //ad enable, prescaler 16 (62.5kHz) while (1) { key_pressed = check_pressed_adc_value (adc_read ()); if (key_pressed != prev_pressed) { if ((key_pressed > 0) && (prev_pressed == 0)) { couple_more_samples (&key_pressed); nec_start (); nec_data (ken_code, key_codes[key_pressed]); nec_finish (); } prev_pressed = key_pressed; } } return 0; } // ---------------------------------------------------------------------------- uint8_t adc_read (void) { //Start Single conversion ADCSRA |= (1 << ADSC); //Wait for conversion to complete while (!(ADCSRA & (1 << ADIF))); //Clear ADIF by writing one to it ADCSRA |= (1 << ADIF); return ADCH; // only 8 highest bits } // ---------------------------------------------------------------------------- uint8_t check_pressed_adc_value (uint8_t adc_v) { uint8_t key_val = 0; // nothing (0) if (adc_v < 152) { key_val++; // SEL (1) if (adc_v < 88) { key_val++; // SEEK - (2) if (adc_v < 57) { key_val++; // SEEK + (3) if (adc_v < 33) { key_val++; // VOL + (4) if (adc_v < 16 && adc_v > 4) { key_val++; // VOL - (5) } } } } } return key_val; } // ---------------------------------------------------------------------------- void couple_more_samples (uint8_t *k_pressed) { // take couple more samples and create a small histogram of // of values, find which key has most counts uint8_t key_rep[] = {0, 0, 0, 0, 0, 0}; key_rep[*k_pressed]++; for (uint8_t i=0; i<ADC_SAMPLES; ++i) { key_rep[check_pressed_adc_value (adc_read ())]++; } uint8_t max_v = 0; for (uint8_t j=1; j<6; ++j) { if (key_rep[j] > max_v) { max_v = key_rep[j]; *k_pressed = j; } } } // ---------------------------------------------------------------------------- void nec_set_pin (uint8_t hilo) { if (hilo) sbi (NEC_PORT, NEC_PIN); // set high else cbi (NEC_PORT, NEC_PIN); // set low } // ---------------------------------------------------------------------------- void nec_base (uint8_t hm, uint8_t lm) { nec_set_pin (NEC_LINE_HI); for (uint8_t i=0; i<hm; ++i) _delay_us (NEC_TIME); nec_set_pin (NEC_LINE_LO); for (uint8_t i=0; i<lm; ++i) _delay_us (NEC_TIME); } // ---------------------------------------------------------------------------- void nec_start (void) { nec_base (16, 8); // 9ms - 1, 4.5ms - 0 } // ---------------------------------------------------------------------------- void nec_finish (void) { nec_base (1, 0); // 562.5us - 1, 0 - 0 } // ---------------------------------------------------------------------------- void nec_one (void) { nec_base (1, 3); // 562.5us - 1, 1.6875us - 0 } // ---------------------------------------------------------------------------- void nec_zero (void) { nec_base (1, 1); // 562.5us - 1, 562.5us - 0 } // ---------------------------------------------------------------------------- void nec_8bit (uint8_t *bits) { for (uint8_t i=0; i<8; ++i) { if (*bits & 0x01) nec_one (); else nec_zero (); *bits = *bits >> 1; } } // ---------------------------------------------------------------------------- void nec_data (uint8_t addr, uint8_t dta) { uint8_t naddr = ~addr; uint8_t ndta = ~dta; nec_8bit (&addr); nec_8bit (&naddr); nec_8bit (&dta); nec_8bit (&ndta); } // ----------------------------------------------------------------------------
Referencje: