Говорящее устройство рассчитано на тех, кто не замечает лампочки на панели приборов, и периодически ловит себя на том, что едет с ручником или дальним светом. Или что стрелка спидометра незаметно уползла сильно вперёд… Ага, для себя делал.

Проверялось на (заточено на) Daewoo Nexia N150, но должно работать на любой машине с электронным спидометром (датчик скорости — 6 импульсов на метр, вроде как стандарт). Фотографии немного мутные (любительские):

image

image

Функционал:
1. Говорит голосом (5 уровней громкости) о различных событиях (события описаны далее)

а) мужским
б) женским

Голоса синтезированы на компьютере, записаны в виде набора mp3-файлов на микроСД карту. Это значит, что вы можете их поменять или записать.

2. Настройки выполняются с ПДУ от телевизора (или любого другого с достаточным количеством кнопок, настоятельно рекомендую ПДУ с кнопками 0-9). Настройки запоминаются в момент изменения.

3. В случае отказа/потери ПДУ, извещатель может быть оперативно переключен на режимы «нет звуков» или «нет звуков, кроме ручника». Для этого используется тройное включение габаритов (идея использовать мигание габаритами в качестве управляющего сигнала для настройки спёрта с какого-то устройства регулировки яркости ДХО).

Озвучиваемые события (отключаемые)
— Ручник (если горит лампочка ручника во время движения)
— Включен дальний свет (мигание дальним игнорируется, предупреждает один раз после включения, далее не напоминает, пока дальний не переключат)
— Включите ходовые огни (если начато движение без включенных габаритов, у меня ДХО и ближний не работают, если не включены габариты).
— Напряжение бортовой сети (точность до десятой доли вольта)
— Превышение порога скорости 40, 50,… 120, 130 км/ч.
— Понижение порога скорости 40, 50,… 120, 130 км/ч.
— Изменение громкости, переключение голоса, отключение/включение каждого из озвучиваемых событий (включение вольтметра вызывает одновременно озвучивание напряжения).

Видео с демонтрацией:



Подключение к авто
1. К спидометру — сразу три провода: земля, +12 (отсюда берём питание на устройство и данные для вольтметра), сигнал от датчика скорости.

2. К лампочкам — ручника, дальнего света, подсветки приборов (габариты).

Согласование уровней сделано довольно просто — выводы ардуины подтянуты внутренним резистором в «1», а сигналы 12В уровня из приборной панели идут через диоды. Когда с панели идет +12, диод закрыт — на ардуине «лог.1». Когда сигнал с панели идет землёй — диод открыт, внутренний подтягивающий резистор игнорируется, на выводе ардуино «лог.0».

Вольтметр — просто резистивный делитель на аналоговый вход. Отношение примерно 1:13, из того расчета, что при максимальном напряжении бортовой сети 16В (аварийный режим) АЦП достигнет своего предела при опорном сигнале 1,2В.

Состав устройства
1. Arduino nano v3

Ссылка на Али

2. Модуль mp3 — DFplayer mini

Ссылка на Али

Данный модуль в интернет-схемах подключается по tx/rx сигналам, я добавил анализ сигнала busy, для поимки момента окончания воспроизведения звука, чтоб новое оповещение не перебивало текущее, а вставало в очередь.

3. Усилитель PAM8403 (необязательный элемент)

Ссылка на Али

4. Макетная плата, 5 резисторов (2 на АЦП вольтметра, и 3 на выводы DFplayer), 4 диода (согласование с +12В), а также пара конденсаторов и микросхема стабилизатора питания +5В типа LM7805 (у меня стоит DC/DC преобразователь AMSR 7805, потому что был в запасе и не греется), пъезозуммер (пищит при переполнении очереди мр3-файлов, больше для поиска ошибок при отладке), разъемы для подключения ИК-датчика (джек 2.5 в моем случае, но это непринципиально) и динамика (я взял RCA), ну и разъем для панели приборов (я взял IDC10, с защелками на папе — достаточно легкий и многоконтактный)

Прошивка:
Из текста прошивки можно легко увидеть, куда заведены все анализируемые сигналы, куда прописать коды вашего ИК-пульта (взять их можно с помощью демо-скетча IRRecvDump). Также константами явно прописаны моменты срабатывания озвучки скоростей (обратите внимание на то, что скорости озвучиваются несколько раньше, чем будут реально достигнуты — чтоб успеть сбавить, не превысив).

#include <Wire.h> 
//#include <LiquidCrystal_I2C.h>
#include <IRremote.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
int RECV_PIN = 6; //пин подключения IR приёмника
IRrecv irrecv(RECV_PIN);
decode_results results;
byte cfg[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
byte mp3stek[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte mp3stekpos = 0;
volatile byte busy = 1; //mp3 busy  0=play
volatile byte softbusy = 0; 
volatile byte nomer_mp3 = 1; 

volatile unsigned long micros_sp = 0;
volatile byte sz = 0; //счетчик обнуления
volatile unsigned int sp = 0; //скорость
volatile unsigned int sp20 = 0; //скорость*10
volatile boolean st = false; //триггер
volatile byte ruka = 1; //ручник
volatile byte dsvet = 0; //д.свет
volatile byte gaba = 0; //габариты
volatile byte lastspeed = 0; //последняя скорость
volatile byte lastdsvet = 0; //переменная для отсчета д.света
volatile byte lastgaba = 0; //переменная для отсчета DHO
volatile byte lastgabaonoff = 0; //переменная для отсчета щелчкоф габаритов
volatile word gabaonofftimer = 0; //переменная для отсчета времени щелчкоф габаритов
volatile byte gabaonoffcounter = 0; //переменная для отсчета времени щелчкоф габаритов
volatile byte speed40 = 38; // 40
volatile byte speed60 = 58; // 60
volatile byte speed80 = 78; // 80
volatile byte speed110 = 108; // 110
volatile byte unspeed40 = 36; // no 40
volatile byte unspeed60 = 56; // no 60
volatile byte unspeed80 = 76; // no 80
volatile byte unspeed110 = 106; // no 110
volatile byte speed50 = 48; // 50
volatile byte unspeed50 = 46; // no 50
volatile byte speed70 = 68; // 70
volatile byte unspeed70 = 66; // no 70
volatile byte speed90 = 88; // 90
volatile byte unspeed90 = 86; // no 90
volatile byte speed100 = 98; // 100
volatile byte unspeed100 = 96; // no 100
volatile byte speed120 = 118; // 120
volatile byte unspeed120 = 116; // no 120
volatile byte speed130 = 128; // 130
volatile byte unspeed130 = 126; // no 130
volatile byte s40 = 0;
volatile byte s50 = 0;
volatile byte s60 = 0;
volatile byte s70 = 0;
volatile byte s80 = 0;
volatile byte s90 = 0;
volatile byte s100 = 0;
volatile byte s110 = 0;
volatile byte s120 = 0;
volatile byte s130 = 0;


//LiquidCrystal_I2C lcd(0x3F, 16, 2);

float sensorValue = 0; //V
float outputValue = 0;  //V

void setup()
{
       Serial.begin(9600);
       Serial.println("start programm NEXIA 1.0");
       //start IR reciver 
        irrecv.enableIRIn(); // Start the receiver
	// initialize the LCD
	//lcd.begin();
	// Turn on the blacklight and print a message.
	//lcd.backlight();
//	lcd.print("Hello, world!");
        //tone (17,3000); //тестовый сигнал на выводе D3
        pinMode(2, INPUT);	//  пин будет работать на вход speed
        digitalWrite(2, HIGH);//подключаем подтягивающий резистор
        pinMode(3, INPUT);	//  пин будет работать на вход
        digitalWrite(3, HIGH);//подключаем подтягивающий резистор
        pinMode(4, INPUT);	//  пин будет работать на вход
        digitalWrite(4, HIGH);//подключаем подтягивающий резистор
        pinMode(5, INPUT);	//  пин будет работать на вход
        digitalWrite(5, HIGH);//подключаем подтягивающий резистор
//      pinMode(13, OUTPUT);	// 
        lastgabaonoff = digitalRead(5); //gaba
        attachInterrupt(0,speedometr, RISING);
        cfg[0]=EEPROM.read(5); //0=all sound  1=no sound(ruka) 2=no sound all
        cfg[1]=EEPROM.read(6); //volume mp3 0..30
        cfg[2]=EEPROM.read(7); //0 = dsvet sound? 1 = no dsvet sound
        cfg[3]=EEPROM.read(8); //0 = 40sound
        cfg[4]=EEPROM.read(9); //0 = 60sound
        cfg[5]=EEPROM.read(10);//0 = 80sound
        cfg[6]=EEPROM.read(11);//0 = 110 sound
        cfg[7]=EEPROM.read(12);//0 = Voltmetr sound 
        cfg[8]=EEPROM.read(13);//0 = DHO sound
        cfg[9]=EEPROM.read(14);//0 = 50sound
        cfg[10]=EEPROM.read(15);//0 = 70sound
        cfg[11]=EEPROM.read(16);//0 = 90sound
        cfg[12]=EEPROM.read(17);//0 = 100sound
        cfg[13]=EEPROM.read(18);//0 = 120sound
        cfg[14]=EEPROM.read(19);//0 = 130sound
        cfg[15]=EEPROM.read(20);//0 = голос0, 1=голос1
        //управление мр3
        pinMode(7, INPUT);  //busy
        mp3_set_serial (Serial);    
//      mp3_set_volume (25);
        mp3setvol();
        delay (100); 
        add_mp3(32); //включен голосовой режим
       // Вольтметр
       analogReference (INTERNAL);
    //    voltmeter();
        if (cfg[7]==0){voltmetertest();} // вольтметр
}

void loop()
{
  //индикация режимов датчиков
    dsvet = digitalRead(3); // Получить состояние пина : 
//  if (dsvet==1) {  lcd.setCursor(3, 0);  lcd.print("DD ");}
//  else{lcd.setCursor(3, 0); lcd.print("_s ");};
  
      ruka = digitalRead(4); // Получить состояние пина : 
//  if (ruka==1) {  lcd.setCursor(0, 0);  lcd.print("_r ");}
//  else{lcd.setCursor(0, 0); lcd.print("RR ");};
  
      gaba = digitalRead(5); // Получить состояние пина : 
//  if (gaba==1) {  lcd.setCursor(6, 0);  lcd.print("GG");}
//  else{lcd.setCursor(6, 0); lcd.print("_g");};

//voltmetertest(); //тест вольтметра

//    digitalWrite(13, !digitalRead(2));
  
  //измерение скорости
//  lcd.setCursor(10, 0);
//  lcd.print("     ");
//  lcd.setCursor(10, 0);
//  lcd.print(sp);
//  lcd.setCursor(0, 1);
//  lcd.print("     ");
//  lcd.setCursor(0, 1);
//  lcd.print(sp20);
  if (sz != 0){sz--;}else{sp20=0;sp=0;};
  delay(50);
  //  sdisplay();
//действия
gabamute(); //управление звуком по тройному щелчку габаритами
if ((sp20>0)&(ruka==0)) {rukabeep();}//пищит ручник
else
{ //обрабатываем д.свет и скорость только если не пищит ручник
if (dsvet==1) { if ( lastdsvet<200){lastdsvet=lastdsvet+1;};}
if (dsvet==1) { if ((lastdsvet>35)&(lastdsvet<38)){svetbeep();}; }
if (dsvet==0) {lastdsvet=0;};
//конец обработки д. света

//предупреждение о дхо
if ((gaba==0)&&(sp20>0)) { if ( lastgaba<150){lastgaba=lastgaba+1;};}
if ((gaba==0)&&(sp20>0)) { if ((lastgaba>20)&(lastgaba<23)){DHO_beep();}; }
if (gaba==1) {lastgaba=0;};
if (sp20==0) {lastgaba=0;};

//обрабатываем контроль превышения скорости 40, 60, 80, 110
if ((lastspeed<speed40)&(sp>=speed40)) {
 if (s40<1) { svet1beep(); s40=1; }};
if ((lastspeed>=unspeed40)&(sp<unspeed40)) {
 if (s40>0) { nsvet1beep(); s40=0; }};
if ((lastspeed<speed50)&(sp>=speed50)) {
 if (s50<1) { svet50beep(); s50=1; }};
if ((lastspeed>=unspeed50)&(sp<unspeed50)) {
 if (s50>0) { nsvet50beep(); s50=0; }};
if ((lastspeed<speed60)&(sp>=speed60)) {
 if (s60<1) { svet2beep(); s60=1; }};
if ((lastspeed>=unspeed60)&(sp<unspeed60)) {
 if (s60>0) { nsvet2beep(); s60=0; }};
if ((lastspeed<speed70)&(sp>=speed70)) {
 if (s70<1) { svet70beep(); s70=1; }}; 
if ((lastspeed>=unspeed70)&(sp<unspeed70)) {
 if (s70>0) { nsvet70beep(); s70=0; }}; //костыль. иначе на 66км/ч без конца твердит "нет 70"почему - непонятно. поставил костыли на все скорости.
if ((lastspeed<speed80)&(sp>=speed80)) {
 if (s80<1) { svet3beep(); s80=1; }};
if ((lastspeed>=unspeed80)&(sp<unspeed80)) {
 if (s80>0) { nsvet3beep(); s80=0; }};
if ((lastspeed<speed90)&(sp>=speed90)) {
 if (s90<1) { svet90beep(); s90=1; }};
if ((lastspeed>=unspeed90)&(sp<unspeed90)) {
 if (s90>0) { nsvet90beep(); s90=0; }};
if ((lastspeed<speed100)&(sp>=speed100)) {
 if (s100<1) { svet100beep(); s100=1; }};
if ((lastspeed>=unspeed100)&(sp<unspeed100)) {
 if (s100>0) { nsvet100beep(); s100=0; }};
if ((lastspeed<speed110)&(sp>=speed110)) {
 if (s110<1) { svet4beep(); s110=1; }};
if ((lastspeed>=unspeed110)&(sp<unspeed110)) {
 if (s110>0) { nsvet4beep(); s110=0; }};
if ((lastspeed<speed120)&(sp>=speed120)) {
 if (s120<1) { svet120beep(); s120=1; }};
if ((lastspeed>=unspeed120)&(sp<unspeed120)) {
 if (s120>0) { nsvet120beep(); s120=0; }};
if ((lastspeed<speed130)&(sp>=speed130)) {
 if (s130<1) { svet130beep(); s130=1; }};
if ((lastspeed>=unspeed130)&(sp<unspeed130)) {
 if (s130>0) { nsvet130beep(); s130=0; }};
lastspeed=sp;
delay(50);
//конец обработки превышения скорости
}//конец обработки при молчащем рчнике
// voltmeter();
  
//обработка очереди мр3
if (mp3stekpos != 0) 
{ busy = digitalRead(7); // Получить состояние пина 7: 
  if (softbusy > 0) softbusy = softbusy-1;//предварительная задержка, т.к. busy возникает не сразу
  if ((busy>0) && (softbusy ==0)) //если не занят мр3
  {mp3stekpos = mp3stekpos-1;
   nomer_mp3 = mp3stek[1];
   if (cfg[15]==0) {mp3_play (mp3stek[1]);} else {mp3_play (mp3stek[1]+100);} //выбор голоса
   softbusy = 25;   delay(50);
   for (int i=1; i < 9; i++){mp3stek[i]=mp3stek[i+1]; }
   mp3stek[9]= 0;  };}

//обработка ПДУ сигналов
/**/ if (irrecv.decode(&results)) 
  {
    Serial.println(results.value);
//    lcd.setCursor(6, 1); 
//    lcd.print(results.value);
    
    //проверяем кнопки
    //кнопка повер или муте  общий выкл звука 0-2
    if ((results.value==2255209021)|| (results.value==2255218711))
    {  cfg[0]=cfg[0]+1;
       if (cfg[0]>2) { cfg[0]=0; };    EEPROM.write(5,cfg[0]);
       if (cfg[0]==0) {add_mp3(5);}; //sound on
       if (cfg[0]==1) {add_mp3(3);}; //sound off krome ruka
       if (cfg[0]==2) {add_mp3(4);}; }//sound off all

    //кнопка V+ - прибавить громкость мр3
    if ((results.value==2255188111)||(results.value==2155866645))
    {   cfg[1]=cfg[1]+1;
    if (cfg[1]>4) {cfg[1]=4;add_mp3(41);}
    if (cfg[1]==1) {add_mp3(38);}
    if (cfg[1]==2) {add_mp3(39);}
    if (cfg[1]==3) {add_mp3(40);}
    if (cfg[1]==4) {add_mp3(41);}
    mp3setvol();   EEPROM.write(6,cfg[1]);  }
    
    //кнопка V- - убавить громкость мр3
    if ((results.value==2255210551)||(results.value==2155809015))  
    { if (cfg[1]<1)  {add_mp3(37); }  else  {cfg[1]=cfg[1]-1;}
      if (cfg[1]==1) {add_mp3(38);}
      if (cfg[1]==2) {add_mp3(39);}
      if (cfg[1]==3) {add_mp3(40);}
      mp3setvol();     EEPROM.write(6,cfg[1]);   }

    if (results.value==2255192191)  //кнопка sourse - переключить голос на +100
    { if (cfg[15]==0){  cfg[15]=1;}  
      else  {           cfg[15]=0;} add_mp3(42);    
      EEPROM.write(20,cfg[15]);    }

    if (results.value==2255222791)    //кнопка синяя - отключение д.свет
    { if (cfg[2]==0) {  cfg[2]=1;   add_mp3(18);} //no dsvet
      else {            cfg[2]=0;   add_mp3(19); }//dsvet on
      EEPROM.write(7,cfg[2]);    }

    if (results.value==2255220751)    //кнопка 4 - отключение скорости 40
    { if (cfg[3]==0) {  cfg[3]=1;   add_mp3(36); }//40 off
      else  {           cfg[3]=0;   add_mp3(35); }//40 on
      EEPROM.write(8,cfg[3]);   }
    
    if (results.value==2255200351)    //кнопка 5 - отключение скорости 50
    {  if (cfg[9]==0) { cfg[9]=1;   add_mp3(65); } //50 off
       else  {          cfg[9]=0;   add_mp3(66); }//50 on
       EEPROM.write(14,cfg[9]);    }
    
    if (results.value==2255184031)    //кнопка 6 - отключение скорости 60
    {  if (cfg[4]==0) { cfg[4]=1;   add_mp3(12); }//60 off
       else   {         cfg[4]=0;   add_mp3(13); }//60 on
       EEPROM.write(9,cfg[4]);   }
    
    if (results.value==2255171791)    //кнопка 7 - отключение скорости 70
    {  if (cfg[10]==0) {cfg[10]=1;  add_mp3(45);} //70 off
       else   {         cfg[10]=0;  add_mp3(46); }//70 on
       EEPROM.write(15,cfg[10]); }
    
    if (results.value==2255204431)   //кнопка 8 - отключение скорости 80
    { if (cfg[5]==0) {  cfg[5]=1;   add_mp3(14); }//80 off
      else  {           cfg[5]=0;   add_mp3(15); }//80 on
      EEPROM.write(10,cfg[5]);   }

    if (results.value==2255179951)    //кнопка 9 - отключение скорости 90
    { if (cfg[11]==0) {cfg[11]=1;  add_mp3(49);} //90 off
      else {           cfg[11]=0;  add_mp3(50);} //90 on
      EEPROM.write(16,cfg[11]);  }

    if (results.value==2255194231)    //кнопка 0 - отключение скорости 100
    { if (cfg[12]==0) {cfg[12]=1;  add_mp3(53);} //100 off
      else  {          cfg[12]=0;  add_mp3(54);} //100 on
      EEPROM.write(17,cfg[12]);  }

    if (results.value==2255167711)    //кнопка 1 - отключение скорости 110
    { if (cfg[6]==0) { cfg[6]=1;   add_mp3(16);} //110 off
      else   {         cfg[6]=0;   add_mp3(17);} //110 on
      EEPROM.write(11,cfg[6]);   }

    if (results.value==2255163631)    //кнопка 2 - отключение скорости 120
    { if (cfg[13]==0) { cfg[13]=1;  add_mp3(57);} //120 off
      else {            cfg[13]=0;  add_mp3(58);} //120 on
      EEPROM.write(18,cfg[13]);   }

    if (results.value==2255175871)    //кнопка 3 - отключение скорости 130
    { if (cfg[14]==0) { cfg[14]=1;  add_mp3(61);} //130 off
      else   {          cfg[14]=0;  add_mp3(62);} //130 on
      EEPROM.write(19,cfg[14]);   }

    if ((results.value==2255206471)||(results.value==2155829925)) //кнопка зеленая? - отключение вольтметра
    { if (cfg[7]==0)  { cfg[7]=1;   add_mp3(27);} //no Вольтметр
      else    {         cfg[7]=0;   add_mp3(28);  voltmetertest(); }//Вольтметр on
      EEPROM.write(12,cfg[7]);   }
    
    if (results.value==2255190151)   //кнопка желтая - отключение DHO
    { if (cfg[8]==0) {  cfg[8]=1;   add_mp3(31);} //no DHO
      else    {         cfg[8]=0;   add_mp3(30);} //DHO on
      EEPROM.write(13,cfg[8]);    }
     
    irrecv.resume(); // Receive the next value
  }
}

//вывод в монитор порта текущих параметров
void sdisplay(){
 if (ruka==1) {  Serial.print("ruka=off ");}  else{ Serial.print("RUKA=ON  ");}; 
 if (dsvet==1) {  Serial.print("dsvet=off ");}  else{ Serial.print("DSVET=ON  ");};  
 if (gaba==1) {  Serial.print("gaba=off ");}  else{ Serial.print("GABA=ON  ");};  
 Serial.println(" ");
 Serial.print(" speed=");
 Serial.println(sp);
 Serial.println("++++++++++++++++++ END ++++++");
}

void speedometr(){ if (!st){micros_sp = micros();} else {sp20 = (12000000/(micros() - micros_sp));}  sp=sp20/20;  st = !st;  sz=10;}

void rukabeep()    { if  (cfg[2]!=2) {mp3_play (1);} delay(1000);}
void svetbeep()    { if ((cfg[2]==0)&&(cfg[0]==0))   {add_mp3(2);}}
void DHO_beep()    { if ((cfg[8]==0)&&(cfg[0]==0))   {add_mp3(29);}}
void svet1beep()   { if ((cfg[3]==0)&&(cfg[0]==0))   {add_mp3(33);}} //40
void nsvet1beep()  { if ((cfg[3]==0)&&(cfg[0]==0))   {add_mp3(34);}} //n40
void svet50beep()  { if ((cfg[9]==0)&&(cfg[0]==0))   {add_mp3(63);}} //50
void nsvet50beep() { if ((cfg[9]==0)&&(cfg[0]==0))   {add_mp3(64);}} //n50
void svet2beep()   { if ((cfg[4]==0)&&(cfg[0]==0))   {add_mp3(6);}}  //60
void nsvet2beep()  { if ((cfg[4]==0)&&(cfg[0]==0))   {add_mp3(7);}}  //n60
void svet70beep()  { if ((cfg[10]==0)&&(cfg[0]==0))  {add_mp3(43);}} //70
void nsvet70beep() { if ((cfg[10]==0)&&(cfg[0]==0))  {add_mp3(44);}} //n70
void svet3beep()   { if ((cfg[5]==0)&&(cfg[0]==0))   {add_mp3(8);}}  //80
void nsvet3beep()  { if ((cfg[5]==0)&&(cfg[0]==0))   {add_mp3(9);}}  //n80
void svet90beep()  { if ((cfg[11]==0)&&(cfg[0]==0))  {add_mp3(47);}} //90
void nsvet90beep() { if ((cfg[11]==0)&&(cfg[0]==0))  {add_mp3(48);}} //n90
void svet100beep() { if ((cfg[12]==0)&&(cfg[0]==0))  {add_mp3(51);}} //100
void nsvet100beep(){ if ((cfg[12]==0)&&(cfg[0]==0))  {add_mp3(52);}} //n100
void svet4beep()   { if ((cfg[6]==0)&&(cfg[0]==0))   {add_mp3(10);}} //110
void nsvet4beep()  { if ((cfg[6]==0)&&(cfg[0]==0))   {add_mp3(11);}} //n110
void svet120beep() { if ((cfg[13]==0)&&(cfg[0]==0))  {add_mp3(55);}} //120
void nsvet120beep(){ if ((cfg[13]==0)&&(cfg[0]==0))  {add_mp3(56);}} //n120
void svet130beep() { if ((cfg[14]==0)&&(cfg[0]==0))  {add_mp3(59);}} //130
void nsvet130beep(){ if ((cfg[14]==0)&&(cfg[0]==0))  {add_mp3(60);}} //n130
//void svet1unbeep(){  tone (17,1000);  delay(200);  noTone(17);}


void add_mp3(byte nomermp3)
{  if ((mp3stekpos > 8)&&(cfg[0]<2))//если переполнение очереди то пищалка
  {  if (cfg[0]==0)  { tone (17,1000);  delay(200);  tone (17,1400);  delay(200);  noTone(17);  }}//пищалку не включать если все звуки отключены
     else   {  mp3stekpos = mp3stekpos+1;  mp3stek[mp3stekpos] = nomermp3;   }} //собственно постановка в очередь

void mp3setvol() //перекодировка уровней громкости из 0-4 в 0-30
{if(cfg[1]==0){mp3_set_volume(10);}if(cfg[1]==1){mp3_set_volume(15);}if(cfg[1]==2){mp3_set_volume(20);}if(cfg[1]==3){mp3_set_volume(25);}if(cfg[1]>3){mp3_set_volume(30);}}

void voltmetertest()
{ outputValue = 0; 
  int u_x10 =0 ; 
  int u_x1 =0 ; 
  int u_x0 =0 ;
  for (int iii=0; iii <10 ; iii++) {  outputValue =outputValue + float(analogRead(A0));  delay(100); }//усреднение нескольких измерений
  outputValue =  outputValue/620;//коэффициент прямого пересчета в вольты
  u_x10=round(outputValue*10); //напряжение х10 
  u_x1=floor(u_x10/10); //целая часть вольтов
  add_mp3(70+u_x1);
  u_x0=u_x10-(10*u_x1);   //десятые = 10U-10Uокруг
  add_mp3(90+u_x0);
}

void gabamute()
{  if (lastgabaonoff==gaba)
{//габариты не трогали
gabaonofftimer=gabaonofftimer+1;
if (gabaonofftimer>250 ) 
 {//давно не трогали габариты - обнуляем ожидание
 gabaonofftimer=250;
 gabaonoffcounter=0;
 }
}
else
{//габариты щелкнули
lastgabaonoff=gaba;
if (gabaonoffcounter==0) {gabaonofftimer=0;} //первый щелчок
// Serial.println(gabaonofftimer);
if (gabaonofftimer<25)
 {//щелчок недавно был
 gabaonoffcounter=gabaonoffcounter+1;
 gabaonofftimer=0;
 if (gabaonoffcounter>5) 
   {//блок отключения-включения звука по циклу
    gabaonoffcounter=0;
    cfg[0]=cfg[0]+1;
    if (cfg[0]>2) { cfg[0]=0; };
    EEPROM.write(5,cfg[0]);
    if (cfg[0]==0) {add_mp3(5);}; //sound on
    if (cfg[0]==1) {add_mp3(3);}; //sound off krome ruka
    if (cfg[0]==2) {add_mp3(4);}; //sound off all
   } }
 else  {   gabaonoffcounter=0;   gabaonofftimer=0; }
}}


Прошивка вспомогательной платы
Для отладки, доводки и демонстрации использовалась другая ардуина — arduino uno и модулем LCD-дисплея и кнопками.

Она умеет выдавать сигналы — габариты, дальний свет, ручник, и менять скорость с 0 до 150 примерно. Из недостатков — нет подавления дребезга контактов, но мне не особо мешает этот момент.

#include <Wire.h> // добавляем необходимые библиотеки
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7 );

const int rukaPin =  2;      // пин = ручник
const int dsvetPin =  1;      // пинт = д.свет
const int speedPin =  0;      // пин = датчик скорости
const int gabaPin =  11;      // пин6 = габариты
int rukaState = 0;  // Переменная значения кнопки
int dsvetState = 0;  // Переменная значения кнопки
int speedState = 0;  // Переменная значения кнопки
int gabaState = 0;  // Переменная значения кнопки
int speedkm = 0;  // км/ч
int lowdelay = 250;  // задержка генератора молой частоты


// Нажатые кнопки
int button;
const int BUTTON_NONE   = 0;
const int BUTTON_RIGHT  = 1;
const int BUTTON_UP     = 2;
const int BUTTON_DOWN   = 3;
const int BUTTON_LEFT   = 4;
const int BUTTON_SELECT = 5;


int getPressedButton()
{
  int buttonValue = analogRead(0); // считываем значения с аналогового входа(A0) 
  if (buttonValue < 60) {
    return BUTTON_RIGHT;  
  }
  else if (buttonValue < 200) {
    return BUTTON_UP;
  }
  else if (buttonValue < 400){
    return BUTTON_DOWN;
  }
  else if (buttonValue < 600){
    return BUTTON_LEFT;
  }
  else if (buttonValue < 800){
    return BUTTON_SELECT;
  }
  return BUTTON_NONE;
}

void setup()
{
  pinMode(rukaPin, OUTPUT); // Настраиваем  порт на выход
  pinMode(dsvetPin, OUTPUT); // Настраиваем  порт на выход
  pinMode(speedPin, OUTPUT); // Настраиваем  порт на выход
  pinMode(gabaPin, OUTPUT); // Настраиваем  порт на выход
  lcd.begin(16, 2);             
  lcd.print("starting...");     
  delay (500);
     lcd.clear();
     lcd.setCursor(12, 0);
     if(gabaState == 1){lcd.print("G_ON");}else{lcd.print("GOFF");}
     digitalWrite(gabaPin,gabaState);
     lcd.setCursor(6, 0);     
     if(dsvetState == 1){lcd.print("D__ON");}else{lcd.print("D_OFF");}   
     digitalWrite(dsvetPin,dsvetState);
     lcd.setCursor(0, 0);     
     if(rukaState == 0){lcd.print("R__ON");}else{lcd.print("R_OFF");}   
     digitalWrite(rukaPin,rukaState);
 }

void loop()
{
  delay(50);
  if ((speedState<=36)&(speedState>0))
  {
  lowdelay=500/speedState;
  digitalWrite(speedPin,1);
  delay(lowdelay);
  digitalWrite(speedPin,0);
  delay(lowdelay);
  lcd.setCursor(12, 1);
  lcd.print(lowdelay);
  lcd.print(" ");

  }
   
  button = getPressedButton();
  switch (button)
  {
  case BUTTON_RIGHT: // при нажатии кнопки выводим следующий текст
    lcd.setCursor(12, 0);
    if (gabaState == 0) 
    { 
      gabaState = 1;
      lcd.print("G_ON ");
    }
    else
    {
      gabaState = 0;
      lcd.print("GOFF ");
    }
      digitalWrite(gabaPin,gabaState);
    break;
  case BUTTON_LEFT:
    lcd.setCursor(6, 0);
    if (dsvetState == 0) 
    { 
      dsvetState = 1;
      lcd.print("D__ON ");
    }
    else
    {
      dsvetState = 0;
      lcd.print("D_OFF ");
    }
    digitalWrite(dsvetPin,dsvetState);
    break;
  case BUTTON_UP:
    if (speedState<5){speedState=speedState+1;} else {if (speedState<250){speedState=speedState+5;};}
    lcd.setCursor(0, 1);
    lcd.print(speedState);
    speedkm=speedState*0.6;
    lcd.print("  ");
    lcd.print(speedkm);
    lcd.print("km/h ");
    if (speedState>35){ tone(speedPin,speedState);}
      else
      {
      noTone(speedPin);
     
      };
    break;
  case BUTTON_DOWN:
    if ((speedState<10)&(speedState>0)){speedState=speedState-1;} 
  else 
     {
   if ((speedState<255)&(speedState>0)){speedState=speedState-5;}
    }  ;
    lcd.setCursor(0, 1);
    lcd.print(speedState);
    speedkm=speedState*0.6;
    lcd.print("  ");
    lcd.print(speedkm);
    lcd.print("km/h ");
//    if (speedState==0){noTone(speedPin);} else {tone(speedPin,speedState);};
 if (speedState>35){ tone(speedPin,speedState);}
      else
      {
      noTone(speedPin);
           };
    break;
  case BUTTON_SELECT:
//    lcd.setCursor(0, 0); 
//    lcd.print("                 "); 
    lcd.setCursor(0, 0);
    if (rukaState == 0) 
    { 
      rukaState = 1;
      lcd.print("R_OFF ");
    }
    else
    {
      rukaState = 0;
      lcd.print("R__ON ");
    }
     digitalWrite(rukaPin,rukaState);
    break;
  }
}

Поделиться с друзьями
-->

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


  1. serafims
    20.02.2017 12:17

    А сигнал о скорости откуда брался? с OBD2 разъема?


    1. Urvin
      20.02.2017 12:31
      +1

      1. К спидометру — сразу три провода: земля, +12 (отсюда берём питание на устройство и данные для вольтметра), сигнал от датчика скорости.


      1. mefiato
        24.02.2017 20:29

        К основному аккумулятору подключать или нужен съемный типа такой батарейки?


        1. AlexKooper
          24.02.2017 20:30

          В машине использует бортовую сеть, при отладке на столе питается от usb, при отладке вольтметра питался от внешнего блока питания с регулируемым выходом. Я ответил на Ваш вопрос?


          1. mefiato
            02.03.2017 12:03

            Спасибо, очень интересно)


  1. shtirlitsus
    20.02.2017 14:16

    Посоветовал бы Вам озвучку взять с https://www.ivona.com
    Повменяемей, на мой слух.


  1. artskep
    20.02.2017 18:52
    +1

    Это, конечно, круто, но зачем голосом? Ну, кроме очевидной причины «потому что могу»?
    По большому счету — весь функционал — предупреждение об опасной или потенциально опасной ситуации. В таких случаях подключать голову, чтобы слушать женский или мужской голос — не самая хорошая идея.
    Не лучше ли запилить разной степени «пикалки» в зависимости от проблемы.
    К примеру, на движение с ручником, без включенных огней — резкую пищалку (ибо надо).
    На дальний свет — че-то помягче (ибо, далеко не факт, что он просто так горит).
    На напряжение бортовой сети… даже не знаю.

    А для повышения/понижения порога скорости, я бы предложил посмотреть, как это сделано у вариометра у парапланеристов — не совсем хорошая аналогия, но все-таки, тон пикания при повышении, понижении скорости даст больше информации (особенно когда привыкнешь)


  1. AlexKooper
    20.02.2017 22:34

    Забыл добавить ссылку на голосовые и текстовые файлы. https://yadi.sk/d/Mk8eiMyP3Dwfv4
    Насчет «пикалки» вместо звука — первые варианты были именно такие. От них и зуммер остался в плате. Но когда событий стало за десяток — сложно на слух воспринимать отличия между писками разной тональности и длительности. Заодно проговаривается изменение настроек — ПДУ может не всегда сработать.

    Кстати, звуки воспроизводятся с ощутимой задержкой, поэтому немного нелитературно сделана озвучка вольтметра в плане десятых долей (Напряжение двенадцать вольт… И девять десятых.). Заказал на али голосовой модуль, который должен быть свободен от таких задержек, WT588D-U 32 М называется. Поэкспериментирую.

    Ещё одна из проблем — неправильно измеряется скорость на скоростях выше 60 км/ч. В принципе, пороги забиты константами, и подобрать можно экспериментально, но я хочу разобраться, почему не подходят теоретические расчеты.

    Да, панель приборов у меня блеклая, лампу дальнего света днем не видно, а лампа ручника ещё и закрыта рулем. Поэтому езда «на слух».

    Несложно также вывести со свободной ноги сигнал включения ДХО (простая проверка скорости s>0), возможно комбинировать с проверкой включения габаритов.


  1. bigbrotherwatchingyou
    20.02.2017 22:35

    Эммм… Это для тех, кто управляет автомобилем, закрыв глаза?


    1. artskep
      21.02.2017 16:41

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


  1. AlexKooper
    20.02.2017 23:44

    Ссылка на голосовые файлы битая, вот правильная https://yadi.sk/d/3xnPrad-3EKGjy


  1. AlexKooper
    21.02.2017 20:37

    Ещё у меня изменена библиотека ИК-датчика, с целью использовать другой таймер, вот только не помню, для этого проекта или другого менял. В общем, если будет на таймеры ругаться — надо в файле IRremoteInt.h найти и поменять:
    было:
    // Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, Nano, etc
    #else
    //#define IR_USE_TIMER1 // tx = pin 9
    #define IR_USE_TIMER2 // tx = pin 3
    стало:
    // Arduino Duemilanove, Diecimila, LilyPad, Mini, Fio, Nano, etc
    #else
    #define IR_USE_TIMER1 // tx = pin 9
    //#define IR_USE_TIMER2 // tx = pin 3


  1. heibert
    21.02.2017 23:06

    А в нексии точно нет CAN / LIN цифровых шин? Или она «со стажем»?


  1. ClearAirTurbulence
    22.02.2017 00:45

    Предельный угол атаки… Предельная перегрузка…


  1. AlexKooper
    22.02.2017 19:10

    В нексии есть ODBII разъем, там у меня иногда живет elm327 адаптер. Других шин нет.