ghans
29.10.2017, 10:32
На VW Touareg NF 2010 - 2015 год отсутствуют кнопки включения обогрева руля и лобового стекла. Управление происходит через меню климата на голове. Иногда это не удобно, хотя бы потому, что голова грузится довольно долго и утром лень ждать. С 2015 года кнопки появились, но просто поставить и подключить их не получается, потому что изменился блок управления бортовой сети.
Для решения использовалась ардуино уно (https://duino.ru/arduino-uno-r3.html) и кан шилд (https://duino.ru/CAN-BUS-Shield.html) от SeedStudio.
Засыпание не реализовано. Ардуинка тупо висит на засыпающем плюсе.
Код сильно не вылизывал. Сейчас переделываю на Leonardo с засыпанием, новый код выложу позже.
#include <mcp_can.h>
#include <SPI.h>
#include <EEPROM.h>
/* Скетч управления подогревом руля и лобового для WV Touareg 2014-2014
* тестировано на Arduino UNO и SEEDStudio CAN-BUS Shield v 1.0
* для версии CAN-BUS Shield v 1.2 поменять порт MCP на 9
*/
const int MCP_PORT = 10; // Для версии 1.0, для версии 1.2 поменять на 9.
const int Port = 0x601; // Порт КАН шины для управления
const int Button_Glass_Pin = 4; // Пин кнопки стекла
const int Button_Steer_Pin = 6; // Пин кнопки руля
const int LED_Glass_Pin = 5; // Пин контрольного светодиода стекла
const int LED_Steer_Pin = 7; // Пин контрольного светодиода руля
const int EE_Steer_Address = 0; // Адрес постоянного хранилища состояния обогрева руля
const int EE_Glass_Address = 1; // Адрес постоянного хранилища состояния обогрева стекла
boolean EE_Steer = false; // Состояние руля true - вкл, false - выкл
boolean EE_Glass = false; // Состояния стекла true - вкл, false - выкл
MCP_CAN CAN(MCP_PORT); // Инициализация порта CAN шины
unsigned char SteerOff[5] = {0x20, 0x74, 0x00, 0x00, 0x00}; // КАН строка выключение руля
unsigned char SteerOn [5] = {0x20, 0x74, 0x00, 0x01, 0x00}; // КАН строка включение руля
unsigned char GlassOff[4] = {0x20, 0x6D, 0x00, 0x01}; // КАН строка выключение стекла
unsigned char GlassOn [4] = {0x20, 0x6D, 0x01, 0x01}; // КАН строка включение стекла
boolean flagRecv = 0; // Флаг того, что сработало прерывание - получен сигнал по шине.
unsigned char len = 0; // Длина полученной строки
unsigned char buf[8]; // Буфер КАН шины
struct BUTTON // структура кнопка
{
boolean on; // нажатие было
boolean longpress; // нажатие было длинным
};
void setup()
{
Serial.begin(115200); // Инициализация ком порта для контрольного вывода. В будущем, все строки Serial можно закомментировать
while (CAN_OK != CAN.begin(CAN_500KBPS)) // Инициализация КАН шины : baudrate = 500k
{
Serial.println("CAN BUS Shield init fail");
Serial.println(" Init CAN BUS Shield again");
delay(100);
}
Serial.println("CAN BUS Shield init ok!");
pinMode(Button_Glass_Pin,INPUT); // Инициализация кнопки стекла
pinMode(LED_Glass_Pin,OUTPUT); // Инициализация контрольного светодиода стекла
pinMode(Button_Steer_Pin,INPUT); // Инициализация кнопки руля
pinMode(LED_Steer_Pin,OUTPUT); // Инициализация контрольного светодиода руль
digitalWrite(Button_Glass_Pin, HIGH); // Включение внутреннего подтягивающего резистора стекло
digitalWrite(Button_Steer_Pin, HIGH); // Включение внутреннего подтягивающего резистора руль
EE_Steer = EEPROM.read(EE_Steer_Address); // Чтение из памяти состояние руля
EE_Glass = EEPROM.read(EE_Glass_Address); // Чтение из памяти состояние стекла
Steer (); // Инициализация обогревов из памяти
Glass ();
attachInterrupt(0, MCP2515_ISR, FALLING); // Инициализация прерывания по чтению из КАН шины
CAN.init_Mask(0, 0, 0x7FF) ; // there are 2 mask in mcp2515, you need to set both of them
CAN.init_Mask(1, 0, 0x7FF); // в оригинале стоит 0x3FF ?
CAN.init_Filt(0, 0, Port); // принимаем сигнал с единственного порта
}
void MCP2515_ISR() // Обработка прерывания. Не понимаю, как это работает, поэтому сделано строго, как в примере
{
flagRecv = true;
}
void Steer() // Подпрограмма управление рулем
{
if (EE_Steer) // Если флаг обогрева установлен
{
CAN.sendMsgBuf(Port, 0, 5, SteerOn); // Посылка команды включения обогрева руля
digitalWrite(LED_Steer_Pin,HIGH); // Включение светодиода руля
Serial.println(" руль включен");
}
else // Иначе - флаг обогрева сброшен
{
CAN.sendMsgBuf(Port, 0, 5, SteerOff); // Посылка команды вкылючения обогрева руля
digitalWrite(LED_Steer_Pin,LOW); // Выключение светодиода руля
Serial.println(" руль выключен");
}
delay(200);
}
void Glass() // Подпрограмма управление стеклом
{
if (EE_Glass) // Если флаг стекла установлен
{
CAN.sendMsgBuf(Port, 0, 4, GlassOn); // Посылка команды включение обогрева стекла
digitalWrite(LED_Glass_Pin,HIGH); // Включение светодиода стекла
Serial.println(" стекло включено");
}
else // Иначе - флаг обогрева сброшен
{
CAN.sendMsgBuf(Port, 0, 4, GlassOff); // Посылка команды выключение обогрева стекла
digitalWrite(LED_Glass_Pin,LOW); // Выключение светодиода стекла
Serial.println(" стекло выключено");
}
delay(200);
}
void Blink(int pin, int n, boolean led) // Подпрограмма "Моргание светодиодом в подтверждение". Номер пина, количество морганий, статус в котором надо оставить.
{
if (led) // если светодиод уже горит, то выключаем
{
digitalWrite(pin,LOW);
delay (500);
}
for (int i=0; i<n; i++)
{ // n раз моргнули в подтверждение
digitalWrite(pin,HIGH);
delay (500);
digitalWrite(pin,LOW);
delay (500);
}
if (led) digitalWrite(pin,HIGH); // Включили светодиод обратно, если надо.
}
BUTTON Pressing(int pin) // Подпрограмма обработка нажатия кнопки. Pin - номер порта
{
int n = 0; // Счетчик
BUTTON key; // Структура кнопки для возврата
while (digitalRead(pin)==LOW && n <=100) // Обрабатывет длинное нажатие, считает тайминги, выход, если превышен таймиг или кнопка отпущена
{
n++; // Увеличение счетчика тайминга
delay (20);
}
key.on = (n>0&& n<100); // Установлен флаг короткого нажатия
key.longpress = (n>=100); // Установлен флаг длинное нажатие
return key;
}
void KeyDetect() // Подпрограмма чтения кнопки
{
BUTTON glass = Pressing (Button_Glass_Pin); // Чтение нажатия кнопки стекла
BUTTON steer = Pressing (Button_Steer_Pin); // Чтение нажатия кнопки руля
if (steer.longpress) // ОБработка длинного нажатия кнопки руля
{
EEPROM.write (EE_Steer_Address,EE_Steer); // Запись текущего состояния в EEPROM
Serial.write("запись руль");
Blink (LED_Steer_Pin, 5, EE_Steer); // Моргнуть 5 раз и оставить в том же положении, что был
delay (500);
}
if (glass.longpress) // ОБработка длинного нажатия кнопки стекла
{
EEPROM.write (EE_Glass_Address,EE_Glass); // Запись текущего состояния в EEPROM
Serial.write("запись руль");
Blink (LED_Glass_Pin, 5, EE_Glass); // Моргнуть 5 раз и оставить в том же положении, что был
delay(500);
}
if (steer.on) // ОБработка простого нажатия кнопки руля
{
EE_Steer = !EE_Steer; // Меняем значение флага обогрева руля на противоположное
Steer (); // Отправляем на обработку с текушщим значением
}
if (glass.on) // ОБработка простого нажатия кнопки стекла
{
EE_Glass = !EE_Glass; // Обращаем значение обогрева стекла
Glass(); // Отправляем на обработку с текушщим значением
}
}
void External_Control() // ОБработка перехвата внешнего воздействия через КАН шину
{
flagRecv=0;
boolean steer_on = true, steer_off = true; // Инициализация ключей проверки строки
boolean glass_on = true, glass_off = true;
/* Взято из примера, проверка всего буфера
* iterate over all pending messages. If either the bus is saturated or the MCU is busy, both RX buffers
* may be in use and reading a single message does not clear the IRQ conditon.
*/
while (CAN_MSGAVAIL == CAN.checkReceive()) // Взято из примера, очищение буфера
{
CAN.readMsgBuf(&len, buf); // Чтение данных из буфера КАН. Данные - len: data length, buf: data buf
if (len==5) // Если длина строки == 5, то это руль
{
for(int i = 0; i<len; i++)
{
steer_on = steer_on && (buf[i]==SteerOn [i]); // Сравниваем побитово на совпадение со сторокй включения руля
steer_off = steer_off && (buf[i]==SteerOff[i]); // Сравниваем побитово на совпадение со строкой выключения руля
}
if (steer_on) // Если перехвачена команда включения руля
{
EE_Steer = true; // Установка флага -подогрев руля включен
digitalWrite(LED_Steer_Pin,HIGH); // Включение светодиода руля
Serial.println(" Обогрев руля включен внешним воздействием");
}
else if (steer_off) // Если перехвачена команды выключения руля
{
EE_Steer = false; // Установка флага - подогрев руля выключен
digitalWrite(LED_Steer_Pin,LOW); // Выключение светодиода руля
Serial.println(" Обогрев руля выключен внешним воздействием");
}
else
Serial.println(" неизвестная команда руль");
}
else if (len==4) // Если длина строки == 4, ьо это стекло
{
for(int i = 0; i<len; i++)
{
glass_on = glass_on && (buf[i]==GlassOn [i]); // Сравниваем побитово на совпадение со строкой включения стекла
glass_off = glass_off && (buf[i]==GlassOff[i]); // Сравниваем побитово на совпадение со строкой вылючения стекла
}
if (glass_on) // Если перехвачена команда включения стекла
{
EE_Glass = true; // Установка флага включения стекла
digitalWrite(LED_Glass_Pin,HIGH); // Включение светодиода стекла
Serial.println(" стекло включено внешним воздействием");
}
else if (glass_off) // Если перехвачена команда включения стекла
{
EE_Glass = false; // Установка флага выключения стекла
digitalWrite(LED_Glass_Pin,LOW); // Выключение светодиода стекла
Serial.println(" стекло выключено внешним воздействием");
}
else
Serial.println(" неизвестная команда стекло");
}
else
{
Serial.println(" неизвестная управляющая команда ");
}
} // Конец цикла чтения из буфера
}
void loop() // Основной цикл
{
if (flagRecv) External_Control(); // Обработка прерывания
KeyDetect(); // ОБработка нажатия клавиши
}
Для решения использовалась ардуино уно (https://duino.ru/arduino-uno-r3.html) и кан шилд (https://duino.ru/CAN-BUS-Shield.html) от SeedStudio.
Засыпание не реализовано. Ардуинка тупо висит на засыпающем плюсе.
Код сильно не вылизывал. Сейчас переделываю на Leonardo с засыпанием, новый код выложу позже.
#include <mcp_can.h>
#include <SPI.h>
#include <EEPROM.h>
/* Скетч управления подогревом руля и лобового для WV Touareg 2014-2014
* тестировано на Arduino UNO и SEEDStudio CAN-BUS Shield v 1.0
* для версии CAN-BUS Shield v 1.2 поменять порт MCP на 9
*/
const int MCP_PORT = 10; // Для версии 1.0, для версии 1.2 поменять на 9.
const int Port = 0x601; // Порт КАН шины для управления
const int Button_Glass_Pin = 4; // Пин кнопки стекла
const int Button_Steer_Pin = 6; // Пин кнопки руля
const int LED_Glass_Pin = 5; // Пин контрольного светодиода стекла
const int LED_Steer_Pin = 7; // Пин контрольного светодиода руля
const int EE_Steer_Address = 0; // Адрес постоянного хранилища состояния обогрева руля
const int EE_Glass_Address = 1; // Адрес постоянного хранилища состояния обогрева стекла
boolean EE_Steer = false; // Состояние руля true - вкл, false - выкл
boolean EE_Glass = false; // Состояния стекла true - вкл, false - выкл
MCP_CAN CAN(MCP_PORT); // Инициализация порта CAN шины
unsigned char SteerOff[5] = {0x20, 0x74, 0x00, 0x00, 0x00}; // КАН строка выключение руля
unsigned char SteerOn [5] = {0x20, 0x74, 0x00, 0x01, 0x00}; // КАН строка включение руля
unsigned char GlassOff[4] = {0x20, 0x6D, 0x00, 0x01}; // КАН строка выключение стекла
unsigned char GlassOn [4] = {0x20, 0x6D, 0x01, 0x01}; // КАН строка включение стекла
boolean flagRecv = 0; // Флаг того, что сработало прерывание - получен сигнал по шине.
unsigned char len = 0; // Длина полученной строки
unsigned char buf[8]; // Буфер КАН шины
struct BUTTON // структура кнопка
{
boolean on; // нажатие было
boolean longpress; // нажатие было длинным
};
void setup()
{
Serial.begin(115200); // Инициализация ком порта для контрольного вывода. В будущем, все строки Serial можно закомментировать
while (CAN_OK != CAN.begin(CAN_500KBPS)) // Инициализация КАН шины : baudrate = 500k
{
Serial.println("CAN BUS Shield init fail");
Serial.println(" Init CAN BUS Shield again");
delay(100);
}
Serial.println("CAN BUS Shield init ok!");
pinMode(Button_Glass_Pin,INPUT); // Инициализация кнопки стекла
pinMode(LED_Glass_Pin,OUTPUT); // Инициализация контрольного светодиода стекла
pinMode(Button_Steer_Pin,INPUT); // Инициализация кнопки руля
pinMode(LED_Steer_Pin,OUTPUT); // Инициализация контрольного светодиода руль
digitalWrite(Button_Glass_Pin, HIGH); // Включение внутреннего подтягивающего резистора стекло
digitalWrite(Button_Steer_Pin, HIGH); // Включение внутреннего подтягивающего резистора руль
EE_Steer = EEPROM.read(EE_Steer_Address); // Чтение из памяти состояние руля
EE_Glass = EEPROM.read(EE_Glass_Address); // Чтение из памяти состояние стекла
Steer (); // Инициализация обогревов из памяти
Glass ();
attachInterrupt(0, MCP2515_ISR, FALLING); // Инициализация прерывания по чтению из КАН шины
CAN.init_Mask(0, 0, 0x7FF) ; // there are 2 mask in mcp2515, you need to set both of them
CAN.init_Mask(1, 0, 0x7FF); // в оригинале стоит 0x3FF ?
CAN.init_Filt(0, 0, Port); // принимаем сигнал с единственного порта
}
void MCP2515_ISR() // Обработка прерывания. Не понимаю, как это работает, поэтому сделано строго, как в примере
{
flagRecv = true;
}
void Steer() // Подпрограмма управление рулем
{
if (EE_Steer) // Если флаг обогрева установлен
{
CAN.sendMsgBuf(Port, 0, 5, SteerOn); // Посылка команды включения обогрева руля
digitalWrite(LED_Steer_Pin,HIGH); // Включение светодиода руля
Serial.println(" руль включен");
}
else // Иначе - флаг обогрева сброшен
{
CAN.sendMsgBuf(Port, 0, 5, SteerOff); // Посылка команды вкылючения обогрева руля
digitalWrite(LED_Steer_Pin,LOW); // Выключение светодиода руля
Serial.println(" руль выключен");
}
delay(200);
}
void Glass() // Подпрограмма управление стеклом
{
if (EE_Glass) // Если флаг стекла установлен
{
CAN.sendMsgBuf(Port, 0, 4, GlassOn); // Посылка команды включение обогрева стекла
digitalWrite(LED_Glass_Pin,HIGH); // Включение светодиода стекла
Serial.println(" стекло включено");
}
else // Иначе - флаг обогрева сброшен
{
CAN.sendMsgBuf(Port, 0, 4, GlassOff); // Посылка команды выключение обогрева стекла
digitalWrite(LED_Glass_Pin,LOW); // Выключение светодиода стекла
Serial.println(" стекло выключено");
}
delay(200);
}
void Blink(int pin, int n, boolean led) // Подпрограмма "Моргание светодиодом в подтверждение". Номер пина, количество морганий, статус в котором надо оставить.
{
if (led) // если светодиод уже горит, то выключаем
{
digitalWrite(pin,LOW);
delay (500);
}
for (int i=0; i<n; i++)
{ // n раз моргнули в подтверждение
digitalWrite(pin,HIGH);
delay (500);
digitalWrite(pin,LOW);
delay (500);
}
if (led) digitalWrite(pin,HIGH); // Включили светодиод обратно, если надо.
}
BUTTON Pressing(int pin) // Подпрограмма обработка нажатия кнопки. Pin - номер порта
{
int n = 0; // Счетчик
BUTTON key; // Структура кнопки для возврата
while (digitalRead(pin)==LOW && n <=100) // Обрабатывет длинное нажатие, считает тайминги, выход, если превышен таймиг или кнопка отпущена
{
n++; // Увеличение счетчика тайминга
delay (20);
}
key.on = (n>0&& n<100); // Установлен флаг короткого нажатия
key.longpress = (n>=100); // Установлен флаг длинное нажатие
return key;
}
void KeyDetect() // Подпрограмма чтения кнопки
{
BUTTON glass = Pressing (Button_Glass_Pin); // Чтение нажатия кнопки стекла
BUTTON steer = Pressing (Button_Steer_Pin); // Чтение нажатия кнопки руля
if (steer.longpress) // ОБработка длинного нажатия кнопки руля
{
EEPROM.write (EE_Steer_Address,EE_Steer); // Запись текущего состояния в EEPROM
Serial.write("запись руль");
Blink (LED_Steer_Pin, 5, EE_Steer); // Моргнуть 5 раз и оставить в том же положении, что был
delay (500);
}
if (glass.longpress) // ОБработка длинного нажатия кнопки стекла
{
EEPROM.write (EE_Glass_Address,EE_Glass); // Запись текущего состояния в EEPROM
Serial.write("запись руль");
Blink (LED_Glass_Pin, 5, EE_Glass); // Моргнуть 5 раз и оставить в том же положении, что был
delay(500);
}
if (steer.on) // ОБработка простого нажатия кнопки руля
{
EE_Steer = !EE_Steer; // Меняем значение флага обогрева руля на противоположное
Steer (); // Отправляем на обработку с текушщим значением
}
if (glass.on) // ОБработка простого нажатия кнопки стекла
{
EE_Glass = !EE_Glass; // Обращаем значение обогрева стекла
Glass(); // Отправляем на обработку с текушщим значением
}
}
void External_Control() // ОБработка перехвата внешнего воздействия через КАН шину
{
flagRecv=0;
boolean steer_on = true, steer_off = true; // Инициализация ключей проверки строки
boolean glass_on = true, glass_off = true;
/* Взято из примера, проверка всего буфера
* iterate over all pending messages. If either the bus is saturated or the MCU is busy, both RX buffers
* may be in use and reading a single message does not clear the IRQ conditon.
*/
while (CAN_MSGAVAIL == CAN.checkReceive()) // Взято из примера, очищение буфера
{
CAN.readMsgBuf(&len, buf); // Чтение данных из буфера КАН. Данные - len: data length, buf: data buf
if (len==5) // Если длина строки == 5, то это руль
{
for(int i = 0; i<len; i++)
{
steer_on = steer_on && (buf[i]==SteerOn [i]); // Сравниваем побитово на совпадение со сторокй включения руля
steer_off = steer_off && (buf[i]==SteerOff[i]); // Сравниваем побитово на совпадение со строкой выключения руля
}
if (steer_on) // Если перехвачена команда включения руля
{
EE_Steer = true; // Установка флага -подогрев руля включен
digitalWrite(LED_Steer_Pin,HIGH); // Включение светодиода руля
Serial.println(" Обогрев руля включен внешним воздействием");
}
else if (steer_off) // Если перехвачена команды выключения руля
{
EE_Steer = false; // Установка флага - подогрев руля выключен
digitalWrite(LED_Steer_Pin,LOW); // Выключение светодиода руля
Serial.println(" Обогрев руля выключен внешним воздействием");
}
else
Serial.println(" неизвестная команда руль");
}
else if (len==4) // Если длина строки == 4, ьо это стекло
{
for(int i = 0; i<len; i++)
{
glass_on = glass_on && (buf[i]==GlassOn [i]); // Сравниваем побитово на совпадение со строкой включения стекла
glass_off = glass_off && (buf[i]==GlassOff[i]); // Сравниваем побитово на совпадение со строкой вылючения стекла
}
if (glass_on) // Если перехвачена команда включения стекла
{
EE_Glass = true; // Установка флага включения стекла
digitalWrite(LED_Glass_Pin,HIGH); // Включение светодиода стекла
Serial.println(" стекло включено внешним воздействием");
}
else if (glass_off) // Если перехвачена команда включения стекла
{
EE_Glass = false; // Установка флага выключения стекла
digitalWrite(LED_Glass_Pin,LOW); // Выключение светодиода стекла
Serial.println(" стекло выключено внешним воздействием");
}
else
Serial.println(" неизвестная команда стекло");
}
else
{
Serial.println(" неизвестная управляющая команда ");
}
} // Конец цикла чтения из буфера
}
void loop() // Основной цикл
{
if (flagRecv) External_Control(); // Обработка прерывания
KeyDetect(); // ОБработка нажатия клавиши
}