본문 바로가기
아두이노

아두이노로 자동차 대시보드를 완성하다 - 3편

by 구루가 되고픈 2019. 2. 9.

아두이노로 자동차 대시보드를 완성하다 - 3편 (코드편)

 

이번 연재에서는 4개 아두이노에 올려진 프로그램 소스를 소개하도록 하겠습니다.

 

먼저 1번 아두이노는 데이터를 추출하고 속도를 액정에 표시하고 나머지 데이터 값들을 2, 3, 4번 아두이노로 넘기는 역할입니다.

 

// 아두이노 1번 - ODB데이터 처리 + 속도표시

// 아두이노 2번 - RPM표시

// 아두이노 3번 - 냉각수, 볼트, 주행거리, 가동시간 표시

// 아두이노 4번 - 온도, 습도 표시

//

// softwareserial 핀번호

// 1-2 8:9

// 1-3 14:15

// 1-4 10:16

 

#include <U8glib.h>

#include <OBD2UART.h>

#include <SoftwareSerial.h>

 

SoftwareSerial rpmSerial(8,9);

SoftwareSerial cvriSerial(14,15);             // 소프트웨어시리얼 선언

SoftwareSerial tempSerial(10,16);

COBD obd;

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST);

 

int coolant = 0;                              // 데이터 변수들 선언

int runtime = 0;

int intake = 0;

int spd = 0;

int rpm = 0;

float volt = 0;

int cnt = 0;

 

// ODB 데이터를 구하는 함수

void readODB_DATA()

{

  obd.readPID(PID_COOLANT_TEMP, coolant);

  obd.readPID(PID_RPM, rpm);

  obd.readPID(PID_SPEED, spd);

  obd.readPID(PID_RUNTIME, runtime);

  obd.readPID(PID_INTAKE_TEMP, intake);

  volt = obd.getVoltage();

}

 

void disp()

{

  u8g.firstPage();

  do {

    u8g.setFont(u8g_font_fub49n);

  

    if (spd<10)

      u8g.setPrintPos(82, 58);

    else if (spd>=10 && spd<100)

      u8g.setPrintPos(43, 58);

    else

      u8g.setPrintPos(4, 58);   

    

    u8g.print(spd);

  } while(u8g.nextPage());

}

 

void setup()

{

  cvriSerial.begin(9600);

  rpmSerial.begin(9600);

  tempSerial.begin(9600);

  Serial.begin(9600);

  obd.begin();

  obd.init();

}

 

void loop()

{

  readODB_DATA();

  

  disp();

 

  rpmSerial.println(rpm);                 // rpm으로 매번 보내고, cvri는 rpm5번갈때 1번만 보냄

 

  if (cnt == 0)

  {

    String scoolant = String(coolant);

    String sruntime = String(runtime);

    String sintake = String(intake);

    String svolt = String(volt);

    String sspd = String(spd);

    String cvri = "";                       // 냉각수, 볼트, 흡기온도, 가동시간 값들을 하나의 스트링으로 저장하기 위한변수

    String th = "";                         // 온도, 습도는 값을 넘기지는 않지만 나중에 필요한 값을 넘기기 위해 미리 선언

  

    cvri.concat(scoolant);                  // 냉각수, 볼트, 흡기온도, 가동시간을 /구분자를 넣어 하나의 문자열로 합치기

    cvri.concat("/");

    cvri.concat(sruntime);

    cvri.concat("/");

    cvri.concat(sintake);

    cvri.concat("/");

    cvri.concat(svolt);  

    cvriSerial.println(cvri);

    Serial.println(cvri);

  }

 

  tempSerial.println(spd);                 //  거리계산을 위해 속도정보 보내기 2번에 한번

  

  Serial.println(rpm); 

  Serial.println(spd); 

 

  if (cnt>5)

    cnt = 0;

  else

    cnt++;

}

 

OBD2 아답터에 연결하고 데이터를 가져오는 부분은 라이브러리에 따른거니까 딱히 설명드릴건 없을것 같고, 아두이노로 연결하기 위해 softwareserial을 4개 선언하였습니다.

 

속도 표시부분도 u8glib를 사용해서 뿌리는거기 때문에 딱히 특별한건 없고요.

 

3번 아두이노로 넘기는 데이터가 4가지여서 이것만 슬래시(/)를 구분자로 넣어서 하나의 문자열로 만든 다음에 문자열 전체를 넘기고 있습니다.

 

그리고 cnt변수로 속도와 RPM은 실시간으로 바로바로 데이터를 넘기고 3번으로 아두이노로 가는 정보는 굳이 빠른 갱신이 필요없어서 1/5속도로 넘기는 게 이 코드의 핵심이 되겠습니다.

 

다음은 2번 아두이노 코드입니다.

 

// 소프트시리얼 테스트

// 수신측 

#include <U8glib.h>

#include <SoftwareSerial.h>

 

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST);

SoftwareSerial rpmSerial(8,9);

 

int rpm=0;

String data;

 

// 게이지를 그리기 위해 필요한 변수들

int xmax=128;                                   // max length x-axis

int ymax=62;                                    // max length y-axis

int xcenter=xmax/2;                             // center of x-axis

int ycenter=63;    

int arc=57;   // 게이지 원 크기                         

int angle=0;

int p, w, m;

u8g_uint_t xx=0;

 

void setup()

{

  rpmSerial.begin(9600);

  Serial.begin(9600);

}

 

void serial_receive()

{

  if(rpmSerial.available())

  {

    data = rpmSerial.readStringUntil('\n');

    rpm = data.toInt();

    Serial.println(rpm);

  }

}

 

void disp(uint8_t angle)

{

  u8g.firstPage(); 

  do {             

    // draw border of the gauge

    u8g.drawCircle(xcenter,ycenter,arc+6, U8G_DRAW_UPPER_RIGHT);

    u8g.drawCircle(xcenter,ycenter,arc+4, U8G_DRAW_UPPER_RIGHT);

    u8g.drawCircle(xcenter,ycenter,arc+6, U8G_DRAW_UPPER_LEFT);

    u8g.drawCircle(xcenter,ycenter,arc+4, U8G_DRAW_UPPER_LEFT);

  

    // draw the needle

    float x1=sin(2*angle*2*3.14/360);              // needle position

    float y1=cos(2*angle*2*3.14/360);

    u8g.drawLine(xcenter, ycenter, xcenter+arc*x1, ycenter-arc*y1);

    u8g.drawDisc(xcenter, ycenter, 5, U8G_DRAW_UPPER_LEFT);

    u8g.drawDisc(xcenter, ycenter, 5, U8G_DRAW_UPPER_RIGHT);

  

    // show scale labels

    u8g.setFont(u8g_font_profont10r);

    u8g.drawStr(6, 64, "0");                  

    u8g.drawStr(36, 18, "1");

    u8g.drawStr(88, 18, "2");

    u8g.drawStr(117, 64, "3");

  

    // show gauge label

    u8g.setPrintPos(52, 23);            

    u8g.print("x1000");

  

    // show digital value and align its position

    u8g.setFont(u8g_font_profont29r);            

    if (rpm<1000)

    {                                    // leading 0 when value less than 10

      u8g.setPrintPos(34,52);  // 00을 찍고 숫자위치를 설정

      u8g.print("0");

      u8g.setPrintPos(50,52);

    }

    else

    {

      u8g.setPrintPos(34,52);

    }

    u8g.print(rpm);

  }

  while( u8g.nextPage() );

}

 

void loop()

{

  serial_receive();

 

  p = map(rpm,0,3000,0,1023);                 // 센서값을 0~1023에 대응해서 매핑

  w = map(p,0,1023,0,100);                      // map it between 0 and 100

  m = map(p,0,1023,0,90);                       // map needle movement

  

  // show needle and dial

  xx = m;                                      // 135 = zero position, 180 = just before middle, 0 = middle, 45 = max

  

  if (xx<45)                                   // positie correctie

    xx=xx+135;

  else

    xx=xx-45;

  

  disp(xx);                                  // 게이지 그리기

}

 

2번은 softwareserial로 값을 받아서 이를 게이지로 oled에 표시하는 코드입니다.

 

이 코드에서는 u8glib로 아날로그 게이지를 그리는게 좀 복잡한 코드로 되어 있고 핵심 코드는 매우 심플합니다.

 

다음은 3번 아두이노 코드입니다.

 

// 소프트시리얼 테스트

// 수신측 

#include <U8glib.h>

#include <SoftwareSerial.h>

 

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST);

SoftwareSerial cvriSerial(14,15);

 

String scoolant;

String sruntime;

String sintake;

String svolt;

String data;

String timeStr;

int runtime;

float volt;

 

void setup()

{

  cvriSerial.begin(9600);

//  Serial.begin(9600);

}

 

void serial_receive()

{

  if(cvriSerial.available())

  {

    data = cvriSerial.readStringUntil('\n');

 

    int one = data.indexOf("/"); // 첫번째 콤마위치

    int two = data.indexOf("/", one+1); // 두번째 콤마 위치

    int three = data.indexOf("/", two+1); // 세번째 콤마 위치

    int strlength = data.length(); // 문자열 길이

    scoolant = data.substring(0, one); // 첫번째 토큰

    sruntime = data.substring(one+1, two); // 두번째 토큰

    sintake = data.substring(two+1, three); // 두번째 토큰

    svolt = data.substring(three+1,strlength); // 세번째 토큰

 

    volt = svolt.toFloat();

    volt = volt * 10;

    volt = round(volt);

    volt = volt/10;

    svolt = String(volt);

    svolt = svolt.substring(0, 4);

    

    runtime = sruntime.toInt();

    int h = runtime/3600;

    int hmod = runtime%3600;

    int m = hmod/60;

    int s = hmod%60;

    String hr = String(h);

    String mt = String(m);

    String sc = String(s);

    timeStr = "";

    timeStr.concat(hr);

    timeStr.concat(":");

    if (m<10)

      timeStr.concat("0");

    timeStr.concat(mt);

    timeStr.concat(":");

    if (s<10)

      timeStr.concat("0");

    timeStr.concat(sc);

 

    Serial.println(scoolant);

    Serial.println(sruntime);

    Serial.println(sintake);

    Serial.println(svolt);

    Serial.println(timeStr);*/

  }

}

 

void disp()

{

  u8g.firstPage();

  do {

    u8g.drawFrame(0,0, 128,64);

    u8g.drawLine(0,32, 128,32);

    u8g.drawLine(64,0, 64,64);

  

    u8g.setFont(u8g_font_chikita);

    u8g.drawStr(3, 9, "COOLANT");

    u8g.drawStr(67, 9, "VOLTAGE");

    u8g.drawStr(3, 41, "RUNTIME");

    u8g.drawStr(67, 41, "INTAKE AIR");

    

    u8g.drawStr(54, 30, "'C");

    u8g.drawStr(120, 30, "V");

    u8g.drawStr(117, 60, "'C");

  

    u8g.setFont(u8g_font_helvR14);            

    u8g.setPrintPos(20,28); // 수온

    u8g.print(scoolant);

    u8g.setPrintPos(76,28); // 볼트

    u8g.print(svolt);

    u8g.setPrintPos(3,59); // 런타임

    u8g.print(timeStr);

    u8g.setPrintPos(83,59); // 흡기온도

    u8g.print(sintake);

  } while(u8g.nextPage());

}

 

void loop()

{

  serial_receive();

  disp();

}

 

3번은 역시나 softwareserial로 데이터를 4개 데이터값을 받아서 이것을 분리해 내고 oled화면을 4분할해서 뿌리는 역할입니다.

 

문자열을 쪼개서 값을 추출하는게 좀 복잡해 보입니다.

 

그리고 시간값이 초(seconde)로만 넘어오기 때문에 이를 시:분:초 형태로 만들어 주는 코드가 액정에 뿌리기 전에 있습니다.

 

다음은 4번 아두이노 코드입니다.

 

#include "U8glib.h"

#include "DHT.h"

#include "SoftwareSerial.h"

 

#define DHTPIN 5     // Digital pin connected to the DHT sensor

#define DHTTYPE DHT11   // DHT 11

 

DHT dht(DHTPIN, DHTTYPE);

 

U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_FAST);

SoftwareSerial tempSerial(10,16);

 

int spd = 0;

float temp = 0;

float humi = 0;

float tour = 0;         // 총 이동거리

float distance = 0;     // 속도에 대비해서 0.5동안 이동

unsigned long pre_time1 = 0;

unsigned long cur_time1 = 0;

unsigned long pre_time2 = 0;

unsigned long cur_time2 = 0;

String data;

 

void setup(void) {

  Serial.begin(9600);

  tempSerial.begin(9600);

  dht.begin();

  pre_time1 = millis();

  pre_time2 = pre_time1;

}

 

void serial_receive()

{

  if(tempSerial.available())

  {

    data = tempSerial.readStringUntil('\n');

    spd = data.toInt();

  }

}

 

void readTHD()

{

  humi = dht.readHumidity();

  temp = dht.readTemperature();

  

  temp = temp * 10;

  temp = round(temp);

  temp = temp/10;

}

 

void Disp()

{

u8g.firstPage();

do {

    u8g.drawFrame(0,0, 128,64);

    u8g.drawLine(0,32, 128,32);

    u8g.drawLine(64,0, 64,32);

  

    u8g.setFont(u8g_font_chikita);

    u8g.drawStr(3, 9, "TEMPERA");

    u8g.drawStr(67, 9, "HUMIDITY");

 

    u8g.drawStr(54, 30, "'C");

    u8g.drawStr(119, 30, "%");

    u8g.drawStr(114, 60, "KM");

 

    u8g.setFont(u8g_font_helvR14);            

    u8g.setPrintPos(14,28); // 온도

    u8g.print(temp, 1);

    u8g.setPrintPos(86,28); // 습도

    u8g.print(humi, 0);

 

    u8g.setFont(u8g_font_profont29r);

 

    if (tour >= 100)

      u8g.setPrintPos(10,59);

    else if (tour>=10 && tour<100)

      u8g.setPrintPos(25,59);

    else

      u8g.setPrintPos(40,59);

      

    u8g.print(tour);

  } while(u8g.nextPage());

 

  Serial.println(temp);

  Serial.println(humi);

  Serial.println(spd);

  Serial.println(distance);

  Serial.println(tour);

  Serial.println(""); 

}

 

void loop(void)

{

  serial_receive();

  cur_time1 = millis();

  cur_time2 = cur_time1;

  

  if (cur_time1 - pre_time1 >= 500)

  {

    if (spd > 0)

    {

      distance = (float)spd * 1000 / 7200;

      tour = tour + (distance / 1000);

    }

    Disp();

    pre_time1 = cur_time1;

  }

  

  if (cur_time2 - pre_time2 >= 5000)

  {

    readTHD();

    pre_time2 = cur_time2;

  }

}

 

4번 아두이노는 1번으로부터 속도 정보를 받고 있습니다.

 

이는 속도로 주행거리를 계산하기 위한것으로 0.5초 단위로 속도로 이동거리를 계산하고 있습니다.

 

그리고 온습도는  DTH11센서를 통해 받는데, 온습도는 5초 간격으로 갱신하고 있습니다.

 

이 코드의 특징은 주행거리 계산은 0.5초, 온습도 갱신은 5초 간격이다 보니 각각의 시차에 따라 해당 코드부가 실행되어야 하기 때문에 mills()함수를 통해 멀티태스킹 비슷하게 구현한게 특징입니다.

 

delay()만으로는 이렇게 동작시키는 코드를 구현할 수가 없기 때문에 멀티태스킹을 코드로 구현했다고 보시면 될것 같습니다.

 

 

이상으로 코드에 대한 소개를 마치겠습니다.