Подключение кнопок управления магнитолой по CAN



Не редкость, что на автомобилях выходят из строя штатные мультимедийные системы,
особенно если в них используются многодисковые CD-чейнджеры. Стоимость ремонта или замены такого неисправного устройства может составлять большую сумму.
Иногда целесообразнее приобрести переходную рамку и установить современную мультимедийную систему(далее МС), недорогую и гораздо более функциональную.

Останется только задействовать кнопки управления на руле для управления ею.
Как правило, многие МС (made in CHINA) могут легко адаптироваться к штатным резистивным кнопкам руля.
Но вот по штатной автомобильной системе CAN их мало.
Имеем автомобиль Volvo S60 2006 года (аналогично и у XC70, XC90).
Родная МС пережевала и накрошила CD — диски, при включении впадала в конвульсии,
и в конце концов была выкинута на мороз.
В данном автомобиле, блоки кнопок на руле (каждый на микроконтроллере),
передают информацию по LIN – шине о их состоянии в центральный электронный блок автомобиля — CEM.
А дальше по низко скоростной CAN шине, в МС и другие заинтересованные в звуке блоки.
В качестве новой МС владелец выбрал магнитолу Pioneer, имеющий специальный вход для
подключения дистанционного управления:



В интернете полно схем и решений использования данного входа, приведу для примера такой рисунок:



Подключение проводов питания, зажигания, громкоговорителей думаю читателей мало интересна,
все на уровне школьных знаний, есть даже переходные разъемы…



С помощью планшета, программы CANHACKER, своего переходника CAN-USB, подключенного к CAN шине на разъеме МС,
был определен пакет данных, содержащий информацию о состоянии кнопок управления МС.
Нужные провода CAN — белый и зеленый:



Ищем и записываем:



Я для своей работы разработал и использую универсальные платы-конструкторы, на базе STM32,
с помощью которых можно быстро реализовывать разнообразные устройства
(если CAN модуль Starline не подходит для этого :) ).
Вот схема одной из плат:



Теперь осталось только запрограммировать следующий алгоритм:
Настраиваем плату только слушать CAN шину по указанному адресу. Из принятых данных анализируем выбранный байт. И в зависимости от его содержимого, активируем определенные транзисторы для выдачи правильного сопротивления резисторной матрицы на плате в шину дистанционного управления МС Pioneer. Я знаю что есть электронные сопротивления и прочее прочее. Мне так было дешевле и удобней.
Весь проект выкладывать не буду, там много еще другого реализовано. А кусочки кода для Volvo приложу.
Настройка CAN фильтра:

//	смотрим данные volvo s60 2006 г. на шине can low speed в диагностическом разъеме
//	кнопки на руле по LIN передают информацию в ECM, а он по CAN LS передает в шину их состояние.
//	по адресу 		CAN_ID_EXT 	0x0131726C 
//	количество байт 	DLC 		0x08
//	Data0					0x00
//	Data1					0x0C
//	Data2					0x2A (меняется периодически на другие значения)
//	Data3					0x52
//	Data4					0xA0
//	Data5					0x00
//	Data6					0x00
//	Data7					0x3F (состояние кнопок на руле)
//	0b00111111	-	ничего не нажато (0x3F)
//	0b00111110	-	нажато [<]       (0x3E)
//	0b00111101	-	нажато [>]       (0x3D)
//	0b00111011	-	нажато [-]       (0x3B)
//	0b00110111	-	нажато [+]       (0x37)
//настройка CAN фильтра, пропускаем только рабочий указанный адрес в фильтре 
void	CAN_Filter_Init(uint32_t iaddr)
{	
  CAN_FilterConfTypeDef canFilterConfig;
	canFilterConfig.FilterNumber     = 0;                           // Номер фильтра, доступны с 0 по 13	
	canFilterConfig.FilterMode       = CAN_FILTERMODE_IDLIST; 	// Режим работы фильтра
	canFilterConfig.FilterScale 	 = CAN_FILTERSCALE_32BIT; 	// Разрядность (масштабирование)
	if((iaddr & 0xFFFF0000)==0xFFFF0000) 
	{
		//Стандартный размер кадра сообщения CAN (11 бит)
		canFilterConfig.FilterIdHigh 	= (uint16_t)(iaddr<<5);	// Старшая часть идентификатора №1
		canFilterConfig.FilterIdLow 	= 0x0000; 								// Младшая часть идентификатора №1
	}
	else
	{	
		//Расширенный размер кадра сообщения CAN (29 бит)	
		canFilterConfig.FilterIdHigh 	= (uint16_t)(iaddr>>13);													// Старшая часть идентификатора №1
		canFilterConfig.FilterIdLow 	= (uint16_t)((iaddr<<3) | CAN_IDE_32);						// Младшая часть идентификатора №1
	}
	canFilterConfig.FilterFIFOAssignment = CAN_FIFO0;   // Номер буфера FIFO (у нас их всего два)	
	canFilterConfig.FilterActivation = ENABLE; 	    // Активность фильтра
	canFilterConfig.BankNumber = 1;
	HAL_CAN_ConfigFilter(&hcan, &canFilterConfig);	
	HAL_CAN_Receive_IT(&hcan, CAN_FIFO0) ; 
}


Инициализация CAN:


//инициализация CAN модуля под требуемые параметры от программы CANHACKER
void MX_CAN_MySET(uint16_t ispd)
{
  hcan.Instance = CAN;
	if(interface_mode == 1)		//выберем режим
		{hcan.Init.Mode = CAN_MODE_NORMAL;}
			else
		{hcan.Init.Mode = CAN_MODE_SILENT;}
	switch(interface_speed)		//выберем скорость
	{
		case	500:
			hcan.Init.Prescaler = 8;
		break;
		
		case	250:
			hcan.Init.Prescaler = 16;
		break;

		case	125:
			hcan.Init.Prescaler = 32;
		break;
		
		default:
			hcan.Init.Prescaler = 5;
		break;	
	}
  hcan.Init.SJW = CAN_SJW_1TQ;
  hcan.Init.BS1 = CAN_BS1_6TQ;
  hcan.Init.BS2 = CAN_BS2_5TQ;
  hcan.Init.TTCM = DISABLE;   //Time triggered communication mode ВЫКЛ
  hcan.Init.ABOM = ENABLE;    //автоматический выход из bus-off
  hcan.Init.AWUM = ENABLE;    //Автоматический выход из спящего режима 
  hcan.Init.NART = ENABLE;    //DISABLE - зацикливаемся на передаче, если нет ответа от приемника
                              //ENABLE	-	передаем 1 раз
  hcan.Init.RFLM = DISABLE;   //Фифо не лочится, если оно заполнено и принято лишнее сообщение	
  hcan.Init.TXFP = ENABLE;    //Приоритет передачи сообщений определяется идентификаторами
  HAL_CAN_Init(&hcan);
}


Процедура, вызываемая при приеме пакета:


//процедура, вызывается при завершении приёма посылки от CAN
void HAL_CAN_RxCpltCallback(CAN_HandleTypeDef* CanHandle)
{
			if (hcan.pRxMsg->IDE == CAN_ID_STD)
			{
				can_addr=CanHandle->pRxMsg->StdId;
			}
			else
			{
				can_addr=CanHandle->pRxMsg->ExtId;
			}	
			can_dlc     = CanHandle->pRxMsg->DLC;
			can_data[0] = CanHandle->pRxMsg->Data[0];
			can_data[1] = CanHandle->pRxMsg->Data[1];
			can_data[2] = CanHandle->pRxMsg->Data[2];
			can_data[3] = CanHandle->pRxMsg->Data[3];
			can_data[4] = CanHandle->pRxMsg->Data[4];
			can_data[5] = CanHandle->pRxMsg->Data[5];
			can_data[6] = CanHandle->pRxMsg->Data[6];
			can_data[7] = CanHandle->pRxMsg->Data[7];
			HAL_CAN_Receive_IT(&hcan, CAN_FIFO0); //запускаем Rx прерывания, очистить буфер
			can_data_in++;	//если данные пришли, увеличим счетчик
}


И далее разбор принятого сообщения:


//-------------------------------------------------------------------------------------------------------
//периодически в цикле проверяем пришли ли данные по кан, выставляем соответствующие признаки,
//и если данные по кан перестали приходить, то через определенное время выставляем признак что никакие кнопки
//не нажаты
void	KeyCanDetect(void)
{
	extern	TIM_HandleTypeDef htim17;
	uint8_t	selbyte;		//какой байт проверяем
	uint8_t	cmpbyte;		//значение байта для проверки
	uint8_t	okbyte;			//признак для проверки
	okbyte=0;			//пока ничего не распознали
	if(can_data_in>0)		//обработаем что пришло
	{
	    LedG(1);
	    can_work=1;
	    __HAL_TIM_SET_COUNTER(&htim17, 0);		//сбросим таймер
	    //проверим нажата ли кнопка mode/src, сравним со значением соответствующих данных считанных во флеш
	    selbyte=(uint8_t)((can_cmp[0] >> 8) & 0x07);//какой байт в сообщение отвечает за выбранную кнопку 0..7
	    cmpbyte=(uint8_t)((can_cmp[0]       & 0xff));//какое значение байта соответствует ее нажатию
	    if(can_data[selbyte]==cmpbyte)
	    {
	        okbyte=1;		//есть такой байт
		LedMode=PRESS;		//мигаем часто
		if(IrWireMode==0)
		{
	            SetOutRM(1200);		//сопротивления = 1200
		}
		else
		{
	            SendMODE();
		}
//!тестовый вывод, убрать после отладки
//TestCanPrint(1);
	    }
	    //проверим нажата ли кнопка +
	    selbyte=(uint8_t)((can_cmp[1] >> 8) &    0x07);//какой байт в сообщение отвечает за выбранную кнопку 0..7
	    cmpbyte=(uint8_t)((can_cmp[1] 	&    0xff));//какое значение байта соответствует ее нажатию
	    if(can_data[selbyte]==cmpbyte)
	    {
		okbyte=1;	//есть такой байт
		LedMode=PRESS;	//мигаем часто
		if(IrWireMode==0)
		{
	            SetOutRM(16000);	//сопротивления = 16000
		}
		else
		{
		    SendVLPLUS();
		}
//!тестовый вывод, убрать после отладки
//TestCanPrint(2);
	    }
	    //проверим нажата ли кнопка -
	    selbyte=(uint8_t)((can_cmp[2] >> 8)	&    0x07);//какой байт в сообщение отвечает за выбранную кнопку 0..7
	    cmpbyte=(uint8_t)((can_cmp[2] 	&    0xff));//какое значение байта соответствует ее нажатию
	    if(can_data[selbyte]==cmpbyte)
	    {
		okbyte=1;		//есть такой байт
		LedMode=PRESS;		//мигаем часто
		if(IrWireMode==0)
		{
	            SetOutRM(24000);	//сопротивления = 24000
		}
		else
		{
		    SendVLMINUS();
		}
//!тестовый вывод, убрать после отладки
//TestCanPrint(3);
	    }
	//проверим нажата ли кнопка >
	selbyte=(uint8_t)((can_cmp[3] >> 8) &    0x07);//какой байт в сообщение отвечает за выбранную кнопку 0..7
	cmpbyte=(uint8_t)((can_cmp[3] 	    &    0xff));//какое значение байта соответствует ее нажатию
	if(can_data[selbyte]==cmpbyte)
	{
	    okbyte=1;		//есть такой байт
	    LedMode=PRESS;	//мигаем часто
	    if(IrWireMode==0)
	    {
		SetOutRM(8000);	//сопротивления = 8000
	    }
	    else
	    {
		SendCHUP();
	    }
//!тестовый вывод, убрать после отладки
//TestCanPrint(4);
	}
	//проверим нажата ли кнопка <
	selbyte=(uint8_t)((can_cmp[4] >> 8) &    0x07);//какой байт в сообщение отвечает за выбранную кнопку 0..7
	cmpbyte=(uint8_t)((can_cmp[4] 	    &    0xff));//какое значение байта соответствует ее нажатию
	if(can_data[selbyte]==cmpbyte)
	{
	    okbyte=1;		//есть такой байт
	    LedMode=PRESS;	//мигаем часто
	    if(IrWireMode==0)
	    {
		SetOutRM(11250);	//сопротивления = 11250
	    }
	    else
	    {
		SendCHDOWN();
	    }
//!тестовый вывод, убрать после отладки
//TestCanPrint(5);
	}
	//проверим , если не нашли нажатую кнопку
	if(	okbyte==0)			//не нашли такой байт
	{	
	    if(IrWireMode==0) {	LedMode=1;}	else {LedMode=2;}//мигаем 1 или 2 раз редко
	    SetOutRM(1);			//сопротивления отключены
	    Ring(0);			        //контакт ринг не активен
	}
	can_data_in=0;	//раз все разобрали, сбросим признак наличия данных в CAN
	ResOff=0;	//и если связь кан оборвется, послать 1 раз команду отключить резисторы
	LedG(0);	
        }
	else
	{
	    cantime=__HAL_TIM_GET_COUNTER(&htim17);			//прочитаем значения таймера
	    if(cantime>CANTIMEOUT)
	    {
	        __HAL_TIM_SET_COUNTER(&htim17, CANTIMEOUT);	//не даем таймеру переполниться
	        if(ResOff==0)			        //пошлем один раз команду отключить резисторы
	        {
	            if(IrWireMode==0) {	LedMode=1;}	else {LedMode=2;}
	            SetOutRM(1);	//сопротивления отключены
	            Ring(0);		//контакт ринг не активен
	            ResOff=1;
	            can_data_in=0;	//обнулить действия кнопок, очистить данные can
	        }
            }
        }
}


Некоторые данные для работы программы хранятся в оперативно изменяемой области флеш памяти, что бы к примеру подключив плату к компьютеру, оперативно что либо изменить, не внося изменений в код:


//загрузка значений CAN для работы из флеш
//  Хранить данные во флеш будем в таком формате по следующим адресам:
//  E0    ffff ffff ffff ffff ffff ffff ffff ffff
//  E8    ffff ffff ffff ffff ffff ffff ffff ffff
//  F0    ffff ffff ffff ffff ffff ffff ffff ffff
//  F8    ffff ffff ffff ffff ffff ffff ffff ffff
//
//  E0 -старшие 2 байта адреса/фильтра  EXT (если STD то FFFF)
//  E1 -младшие 2 байта адреса/фильтра STD
//  E2 -скорость шины CAN (125/250/500) биты 14..0
//			бит 15 - режим работы 0-только слушаем, 1- отвечаем и можем передавать
//  E3 - старший байт > какой байт по номеру в посылке рассматриваем для кнопки  SRC/MODE
//     - младший байт > значение, которое будет активировать команду SRC/MODE
//  E4 - старший байт > какой байт по номеру в посылке рассматриваем для кнопки  [<]
//     - младший байт > значение, которое будет активировать команду             [<]
//  E5 - старший байт > какой байт по номеру в посылке рассматриваем для кнопки  [>]
//     - младший байт > значение, которое будет активировать команду             [>]
//  E6 - старший байт > какой байт по номеру в посылке рассматриваем для кнопки  [-]
//     - младший байт > значение, которое будет активировать команду             [-]
//  E7 - старший байт > какой байт по номеру в посылке рассматриваем для кнопки  [+]
//     - младший байт > значение, которое будет активировать команду             [+]
//пример 0x131726C 125 :
// addr   addr   spd    src    +      -      >      <
// 0x0131 0x726C 0x007D 0x07xx 0x0737 0x073B 0x073D 0x073E

void	InitCanFromFlash(void)
{
	interface_addr	=(Read_MyFlash(FLASHBASECAN+0)<<16) + Read_MyFlash(FLASHBASECAN+1); //адрес
	interface_speed	=(Read_MyFlash(FLASHBASECAN+2) & 0x7FFF);		            //скорость
	interface_mode	=(uint8_t) (	Read_MyFlash(FLASHBASECAN+2) >> 15);
	can_cmp[0]=	Read_MyFlash(FLASHBASECAN+3);		//данные кнопки src
	can_cmp[1]=	Read_MyFlash(FLASHBASECAN+4);		//данные кнопки +
	can_cmp[2]=	Read_MyFlash(FLASHBASECAN+5);		//данные кнопки -
	can_cmp[3]=	Read_MyFlash(FLASHBASECAN+6);		//данные кнопки >
	can_cmp[4]=	Read_MyFlash(FLASHBASECAN+7);		//данные кнопки <
}



Тестирование:



Написав все это, думаю что чуть упростил жизнь другим интересующимся людям, хотя разбираться в чужом коде сомнительное удовольствие. Главное не боятся. И «лучше день потерять, затем за час долететь». В итоге все заработало как надо.
Если есть вопросы, пишите, буду исправлять и дополнять по возможности.
  • +11
  • 14 октября 2017, 20:48
  • astaninss
  • 1
Файлы в топике: shema.jpg

Комментарии (39)

RSS свернуть / развернуть
Довольно мощно. Еще бы орфографию подтянуть :)
0
На схеме ничего не видно, хотя идея ясна.
В принципе, если взять МК с 5В питанием либо с 9-10 5V-tolerant пинами — можно транзисторы выкинуть, коммутируя резисторы напрямую пинами МК. Диоды тоже можно выкинуть, дергая линию R отдельным пином (это как раз и позволяет обойтись 9 пинами на 10 кнопок).
0
  • avatar
  • Vga
  • 15 октября 2017, 09:05
Можно, но плата для меня универсальный конструктор, сделан чтобы для других изделий потом не городить ключи и прочее, к тому же она потом иногда попадает в руки установщиков, в шерстяной одежде и резиновых сланцах :).
0
Странновато на схеме ключи включены для «универсального конструктора». Ну и установщики в шерстяной одежде вполне успешно вышибут чип и сквозь транзисторные ключи.
0
Обычно не ставят столько резисторов с транзисторами, а пользуют PWM с фильтром. Опорное напряжение идет с МС, и его подтягивают PWM'ом и фильтром к земле. Итого один n-кальный транзистор и резистор с конденсатором в качестве фильтра.
Для Sony/Pioneer — два. Второй для подтяжки шифта.

Второе — схема кнопок эта от Sony, а не от Pioneer. На морозе или жаре работать не будет или будет глючить. Резисторы у Sony хоть и попадают в диапазон Pioneer, но находятся на их границах. Лучше найти схему от пионера.
0
Вот у меня все работает, именно потому что именно так а не иначе, намекал же что советов по схеме нет необходимости делать :)
Отдельная тема схемотехники у военных, казалось бы зачем так? а вот не все так просто!
0
Схему в более лучшем качестве можно?
Исходники то же не доступны?
0
  • avatar
  • jec
  • 16 октября 2017, 19:16
Схему приложил, больше исходников не будет, они уже не к этой теме :)
0
это был намёк на то, что значение байта для кнопки SRC/MODE можно не ждать от вас?
0
Конечно, ведь её на руле нету… :)
0
ЦАП можно было собрать по более простой и верной схеме, как у камрада здесь Это и дешевле и точнее, без транзисторов. можно вообще на резистивных сборках, так как номинал в принципе может быть один.
0
В схеме нет ЦАП, и даже не планировалось.
0
OUTRM тогда что?
0
всего лишь набор резисторов, никакое напряжение не формируем на выход в данной схеме.
0
Формируем. Опорное напряжение (3/3.3/5V) с магнитолы приходит. Его подтягиваем к земле резистором, и получается на входе магнитолы определенное резистором напряжение. ADC магнитолы это видит и определяет какая кнопка нажата. Так что этот набор резисторов и есть DAC.
0
Хорошо, пусть и переменный резистор тогда тоже будет DAC :)
0
Ну так потому он потенциометром и назван.
0
А это, в принципе, идея. R2R лесенкой бит на 8 наверняка можно выставить сопротивление, которое попадет в диапазон, принимаемый магнитолой, и не придется искать резисторы на 11.25к.
0
P.S. Ну и заодно под магнитолу с другими резисторами достаточно будет изменить константы в коде, а не перепаивать резюки.
0
Нарисуйте пожалуйста, как лесенкой с одинаковыми сопротивлениями, на 8 разрядов, задать именносопротивление в диапазоне от 0 до 64кОм, с шагом 500 Ом. Для меня это будет познавательно :)
0
Ровно так же, как по ссылке, только выходы МК использовать в режиме «открытый сток». Там, правда, есть определенные проблемы с равномерностью шага, и с соотношением R-3R оно получается чуть ровнее (по крайней мере для трехбитового варианта, считать вариант с большей разрядностью мне лень). Возможно, при каком-то соотношении резисторов косяки вообще пропадут — но тут уже надо моделировать и подбирать либо анализировать математически.
0
Что то у меня Фантазии не хватает это реализовать лесенкой с одинаковыми сопротивлениями по R-2R, думаю R-3R не поможет :)
0
Это проблемы твоей фантазии. Ну или ты хочешь фиксированый шаг, а он в такой схеме получается (примерно) логарифмическим.
0
Если не трудно, можно ссылку или схемку :)?
0
0
эх… Мы что то не понимаем друг друга, либо невнимательно читаем ..,
давайте про другое тогда :)
0
А что там неясно, собственно? Схема там приведена. Если управлять выходами в режиме OD, а не PP — это будет регулируемый резистор и характеристикой, похожей на обратную логарифмическую. Только, по видимому, для минимизации отклонений от этой функции нужно иное соотношение резисторов, нежели 2:1. С 4:1 явно лучше получается.
0
Хорошо что пришли к общей мысли — что сопротивление придется разные ставить, а не одинаковое, идея r2r с одинаковыми сопротивлениями никак не подходит. Для защиты выходов микроконтроллера от шаловливых рук придется ставить стабилитроны или супрессоры, вариант с транзисторами более более стабилен и дешевле, и позволяет получить больший ряд доступных сопротивлений. Или использовать уже стоящие ключи для других целей.
0
В R-2R они тоже не одинаковые, потому он и называется R-2R. Но 2 номинала из ряда Е24 (а то и Е12) и 8 номиналов из Е192 — это все же разные вещи.
Супрессоры так и так надо ставить, через транзисторы разряд статики прилетает не хуже.
вариант с транзисторами более более стабилен и дешевле, и позволяет получить больший ряд доступных сопротивлений
Напротив, дороже, и больший рад доступных сопротивлений — ценой того, что их надо подбирать готовые (хотя с 8 битами сетка, возможно, даже плотнее будет, чем Е192). С резистивной лесенкой достаточно изменить прошивку.
0
Чем плох ряд, который стоит в схеме? почему 9 резисторов дороже, чем если их по схеме в r2r в два раза больше (примерно) :)?
(сорри, но какой — то знатный срач получается, давайте более серьезные темы обсуждать)
0
Под дороже я, естественно, имею в виду схему с транзисторами — там и резисторов столько же или больше (базовые и подтягивающие), и транзисторов еще по количеству резисторов.
Это во-первых. А во-вторых — два десятка идентичных резисторов запросто обходятся дешевле, чем десяток, но все разные и из редкого ряда. К тому же, идентичные с подходящим соотношением сопротивлений, вероятно, уже лежат в ящике, купленные пакетом на сотню штук. А 11.25? Ну не знаю, если ты работаешь в ЧиДе, то может у тебя они и лежат в ящике. На работе.
0
Я понимаю что я выложил схему в плохом качестве, потом приложил в более лучшем качестве, так вот, на схеме, если посмотреть, почти все требуемые сопротивления формируются их комбинациями, из ряда 2^n. что тоже имеет право на жизнь :)
0
На ней тоже номиналы не видно. А у ряда 2^n именно тот недостаток, из-за которого вообще придумали R-2R — их сложно набрать из стандартных рядов и все разные.
Кстати, у биполярных транзисторов достаточно большое напряжение насыщения. А вот порт МК при таких токах имеет практически нулевое падение.
0
Вот так я делаю
Минимум деталей, под любые кнопки.
0
Хорошо, будем ждать когда это заработает у кого нибудь в реальности :)
надеюсь с учетом всего написанного другие люди сделаю правильные свои выводы :)
Других вопросов нет открытых :)?
0
А чего ждать то? Года четыре уже работает :-)
0
И да, таких устройств ездит в разных машинах больше 1000. :-)
0
а Я то кучу подобных устройств не разобрал и не знал что внутри во всех подробностях:)
0
я бы добавил «резистор, притянутый к земле»
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.