본문 바로가기
아두이노

차량용 대시보드 업그래이드 (3.5인치 TFT모니터 + 아두이노 UNO)

by 구루가 되고픈 2022. 6. 6.

그간 여러버전의 차량용 대시보드를 제작하였고, 최근에 3.5인치 액정 3개를 활용한 대시보드까지 소개해 드렸습니다.

 

최종적으로 GPS를 더해 총 4개의 디스플레이로 구성된 대시보드를 완성하였습니다.

 

각 화면별로 표시하는 정보는 아래와 같습니다.

 

첫번째: 연료량(LPG, 가솔린), 총주행거리, 속도차트(10킬로 단위)

두번째: 주행시간, 정지시간, rpm, 냉각수온도, 엔진부하량

세번째: 속도

네번째: 실내온도, 고도, 진행방향, 진행방향 차트

 

대시보드 주행모습
대시보드 주행모습

 

대시보드 제작에 사용된 부품들은 아래와 같습니다.

 

- 아두이노 UNO 4개

- 3.5인치 TFT LCD 쉴드 4개

- 아두이노 micro pro 1개

- MCP2515 can-bus 드라이버 모듈

- GPS수신기 모듈 1개

- 광센서 모듈 1개

- 접촉식 온도센서 모듈 1개

- LM2596 DC레귤레이터 1개

 

micro pro보드는 mcp2515로부터 차량의 can-bus신호를 분석해서 속도, rpm등의 차량정보를 가져오고,  시리얼통신을 통해 3개 uno보드에 전달합니다.

uno에서는 전달받은 차량정보를 받아서 각각 정보를 뿌려주는 역할입니다.

마지막 4번째 uno보드는 gps를 연결하여 단독으로 gps신호를 처리하여 표시해 줍니다.

 

제작에 사용된 아두이노 우노, 3.5인치 TFT LCD

 

대시보드의 전체적인 회로구성

 

전체적인 회로는 아두이노 뒷면에 부품들을 일렬로 부착하여 점퍼선으로 회로를 구성하였습니다.

 

전압을 공급하기 위한 LM2596 DC레귤레이터를 장착해서 차량의 12~14V 사이 전압을 약 7V정도로 낮춰 UNO, micro pro전체 아두이노 보드에 공급합니다.

 

아두이노 micro pro보도의 serial핀을 3개 uno보드 serial핀에 연결하여 차량정보를 동시에 uno보드에 전달할 수 있도록 하였고, 광센서 모듈도 uno보드 4개에 센서값이 들어가도록 점퍼선으로 일괄 연결해 주었습니다.

 

나머지 MCP2515, GPS수신기 등도 각 보드에 연결해 주었습니다.

 

이중 차량 ODB2 커넥터에서 차량 정보를 가져오는것이 난이도가 있는데, 이전 글에서 소개하였으니 참고하세요.

 

https://clemencyking.tistory.com/26

 

아두이노로 obd2 차량정보 읽어 오기

차량은 상당히 복잡한 기계장치로 작동에 필요한 각종 정보를 ECU에서 처리합니다. 차량의 상태를 파악하기 위해 상당히 많은 센서들이 실시간으로 차량의 상태를 모니터하고 있기도 합니다. 차

clemencyking.tistory.com

 

차량정보는 컬러 TFT LCD를 사용하는 만큼 adafruit GFX 라이브러리로 그래픽 처리하여 최대한 깔끔하고 예쁘게 디자인 하였습니다.

이부분이 가장 시간이 오래 걸린것 같습니다.

 

만족할때까지 수정-컴파일을 수도없이 반복하면 고쳐나갔습니다.

 

다만 이것을 디자인하고 처리하면서 3가지 문제가 생겼습니다.

 

첫번째가 고질적인 메모리 부족문제였습니다.

그래픽 라이브러리가 워낙 메모리를 많이 차지하기도 하고 다른 라이브러리까지 추가하는 경우  조금만 코딩수를 늘려도 프로그램 메모리가 초과해서 업로드가 안되는 문제가 생겨서 최대한 최적화와 메모리 사이즈 줄이기는데 상당한 시간을 투자한것 같습니다.

디자인도 좀 양보하고 기술적인 부분도 양보해 가면서 대부분 98~99%정도의 메모리 사이즈에 맞게 코딩을 했습니다.

 

메모리 부족문제는 정말 코딩자체를 어렵게 합니다

 

또 하나는  느린 화면 디스플레이 속도로 인해 심하게 깜빡거림이 발생하는 것과 야간 운전시 화면이 너무 밝아서 눈이 부시는 문제가 생겼습니다.

 

제가 사용한 LCD는 불과 7~8천원정도하는 아주 저렴하기도 하고 우노 보드가 데이터를 고속으로 처리할 수 도 없기 때문에 처리속도 문제는 어찌보면 당연하다고 볼수 있겠습니다.

 

특히 차량 속도를 전체 화면에 크게 표시하는 경우 화면전체를 검정색으로 채우고, 숫자를 표시하는 방식으로는 너무 심하게 깜밖거리면서 상당히 불편하였고, 그나마 작은 숫자들은 크게 거슬릴 정도는 아니었습니다.

 

제가 이 질문을 작년 12월에 올렸었는데, 질문내용의 영상을 보면 아주 심하게 깜빡거림을 알수 있습니다.

 

https://cafe.naver.com/arduinostory/142440

 

TFT LCD 전체화면 지울때 깜빡임 처리

안녕하세요. TFT액정에 숫자를 표시하는 간단한 작업을 하고 있습니다. 숫자는 1초에도 2~3번씩 빠르게 변하는 숫자여서 실시간으로 표시할 예정인데요. 일단 랜덤으로 뿌려보...

cafe.naver.com

 

이 문제를 해결하는게 1차 문제였습니다.

원하는 것을 다 구현해도 디스플레이가 이상태만 완성도가 떨여지기 때문에 많이 고민하였습니다.

 

결과적으로 해결은 숫자를 일일이 박스로 그래서 해결하는 방법을 선택하였습니다.

 

7세그먼트처럼 박스를 그래서 숫자를 표시

 

위의 그림처럼 박스를 7개를 그리면 7세그먼트와 같은 효과를 내면서 모든 숫자를 표시할 수 있게 됩니다.

 

저는 이렇게 정확히 좌표를 맞춰서 숫자를 대응해서 그리는 방식으로 처리하였는데, 무식한 방식으로 처리했으나 결과는 깜빡임이 전혀 없이 구현이 되었습니다.

 

예를 들어 숫자 4를 표시한다면 아래처럼 숫자가 표시되는 부분은 노랑색 박스를 그리고 나머지 부분은 검정색 박스를 그리는 방식입니다.

 

박스로 그려서 7세그먼트를 구현

박스가 그려지는 위치는 복잡한 좌표값으로 계산해서 잡아야 하기 때문에 코드는 분석이 불가능할 정도가 되버렸습니다.

 

그래도 필요하신 분들은 참고하세요.

// TFT두번째 속도

#include <Adafruit_GFX.h>    // Core graphics library
#include <MCUFRIEND_kbv.h>   // Hardware-specific library
MCUFRIEND_kbv tft;

#define BLACK   0x0000

// 데이터 처리 변수
int coolant, runtime, spd, rpm, load, fuel, fueltype, light, daynight=1, bar=1, pre_daynight=1;
float spd_avg, pre_avg, volt;
String scoolant, sruntime, sspd, srpm, sload, svolt, sfuel, sfueltype, sr;
int s1, s2, s3, s4, s5, s6, s7, srlength;
int sn1, sn2, sn3;

//추가 데이터 처리변수
unsigned long pre_time1, pre_time2, pre_time3;
unsigned long cur_time = 0;
int i, j, bpx, g_step, datastat = 1;

//글자크기, 폭 등을 결정하는 변수
int wid = 120;  // 숫자 가로폭
int hei = 220;  // 숫자 높이
int thi = 26;   // 숫자 두께
int bpy = 50;   // y축 높이

uint16_t color; // 컬러값

void setup(void)
{
  Serial.begin(9600);
    
  uint16_t ID = tft.readID();
  if (ID == 0xD3) ID = 0x9481;
  tft.begin(ID);
  tft.setRotation(1);
  tft.fillScreen(BLACK);

  pinMode(A5, INPUT);
}

void serial_receive()
{
  light = analogRead(A5);

  if (light <= 800)
    daynight = 1; // 낮
  else
    daynight = 2; // 밤
    
  if(Serial.available())
  {
    sr = Serial.readStringUntil('\n');

    s1 = sr.indexOf("/");         // 첫번째 슬래시 위치
    s2 = sr.indexOf("/", s1+1);   // 두번째 슬래시
    s3 = sr.indexOf("/", s2+1);
    s4 = sr.indexOf("/", s3+1);
    s5 = sr.indexOf("/", s4+1);
    s6 = sr.indexOf("/", s5+1);
    s7 = sr.indexOf("/", s6+1);
    
    srlength = sr.length();       // 전체 문자열 길이
    
    scoolant = sr.substring(0, s1);
    sruntime = sr.substring(s1+1, s2);
    sspd = sr.substring(s2+1, s3);
    srpm = sr.substring(s3+1, s4);
    sload = sr.substring(s4+1, s5);
    svolt = sr.substring(s5+1, s6);
    sfuel = sr.substring(s6+1, s7);
    sfueltype = sr.substring(s7+1, srlength);

    coolant = scoolant.toInt();
    runtime = sruntime.toInt();
    spd = sspd.toInt();
    rpm = srpm.toInt();
    load = sload.toInt();
    volt = svolt.toFloat();
    fuel = sfuel.toInt();
    fueltype = sfueltype.toInt();
  }
}

void loop(void)
{
  serial_receive();

  if (daynight == 1)
    color = 0xFFE0;
  else
    color = 0x8427;
    
  if (pre_daynight != daynight)
  {
    tft.fillScreen(BLACK);
    pre_daynight = daynight;
  }
   
  sn1 = spd/100;
  sn2 = (spd-sn1*100)/10;
  sn3 = spd-sn1*100-sn2*10;

  bpx = -40;
  if (spd >= 100)
  {
    if (sn1==0)
      n0();
    else if (sn1==1)
      n1();
    else if (sn1==2)
      n2();
    else if (sn1==3)
      n3();
    else if (sn1==4)
      n4();
    else if (sn1==5)
      n5();
    else if (sn1==6)
      n6();
    else if (sn1==7)
      n7();
    else if (sn1==8)
      n8();
    else if (sn1==9)
      n9();
    else {}
  }
  else
    n_null();
    
  bpx = 140;
  if (spd >= 10)
  {
    if (sn2==0)
      n0();
    else if (sn2==1)
      n1();
    else if (sn2==2)
      n2();
    else if (sn2==3)
      n3();
    else if (sn2==4)
      n4();
    else if (sn2==5)
      n5();
    else if (sn2==6)
      n6();
    else if (sn2==7)
      n7();
    else if (sn2==8)
      n8();
    else if (sn2==9)
      n9();
    else {}
  }
  else
    n_null();
    
  bpx = 310;
  if (sn3==0)
    n0();
  else if (sn3==1)
    n1();
  else if (sn3==2)
    n2();
  else if (sn3==3)
    n3();
  else if (sn3==4)
    n4();
  else if (sn3==5)
    n5();
  else if (sn3==6)
    n6();
  else if (sn3==7)
    n7();
  else if (sn3==8)
    n8();
  else if (sn3==9)
    n9();
  else {}
}

void n0()
{
//4
  for (i=0; i<wid-(thi*2); i++)
    tft.drawLine(bpx+thi+i, bpy+(hei/2)-(thi/2), bpx+thi+i, bpy+(hei/2)+(thi/2), BLACK);  
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//5
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n1()
{
//1
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, BLACK);
//2
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, BLACK);
//4
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), BLACK);  
//5
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, BLACK);   
//7
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, BLACK);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color); 
}

void n2()
{
//2
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+thi+i, bpx+thi, bpy+thi+i, BLACK);
//6
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)+(thi/2)+i, bpx+wid, bpy+(hei/2)+(thi/2)+i, BLACK);
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//5
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, color);    
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n3()
{
//2
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+thi+i, bpx+thi, bpy+thi+i, BLACK);
//5
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)+(thi/2)+i, bpx+thi, bpy+(hei/2)+(thi/2)+i, BLACK);  
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n4()
{
//1
  for (i=0; i<wid-(thi*2); i++)
    tft.drawLine(bpx+thi+i, bpy, bpx+thi+i, bpy+thi, BLACK);
//5
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)+(thi/2)+i, bpx+thi, bpy+(hei/2)+(thi/2)+i, BLACK);
//7 - 짧은거
//  for (i=0; i<wid-(thi*2); i+=bar)
//    tft.drawLine(bpx+thi+i, bpy+hei-thi, bpx+thi+i, bpy+hei, BLACK);
//7 - 긴거
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, BLACK);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
}

void n5()
{
//3
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx+wid-thi, bpy+thi+i, bpx+wid, bpy+thi+i, BLACK);
//5
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)+(thi/2)+i, bpx+thi, bpy+(hei/2)+(thi/2)+i, BLACK);
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n6()
{
//3
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx+wid-thi, bpy+thi+i, bpx+wid, bpy+thi+i, BLACK);
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//5
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n7()
{
//4
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), BLACK);
//5
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)+(thi/2)+i, bpx+thi, bpy+(hei/2)+(thi/2)+i, BLACK);  
//7
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, BLACK);
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2-thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);
}

void n8()
{
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//5
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);  
//7
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, color);
}

void n9()
{
//5
  for (i=0; i<(hei-thi*3)/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)+(thi/2)+i, bpx+thi, bpy+(hei/2)+(thi/2)+i, BLACK);  
//7
  for (i=0; i<wid-thi; i++)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, BLACK);
//1
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, color);
//2
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, color);
//3
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, color);  
//4
  for (i=0; i<wid; i+=bar)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), color);  
//6
  for (i=0; i<hei/2+thi/2; i+=bar)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, color);
}

void n_null()
{
//1
  for (i=0; i<wid; i++)
    tft.drawLine(bpx+i, bpy, bpx+i, bpy+thi, BLACK);
//2
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx, bpy+i, bpx+thi, bpy+i, BLACK);
//3
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx+wid-thi, bpy+i, bpx+wid, bpy+i, BLACK);  
//4
  for (i=0; i<wid; i++)
    tft.drawLine(bpx+i, bpy+(hei/2)-(thi/2), bpx+i, bpy+(hei/2)+(thi/2), BLACK);  
//5
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx, bpy+(hei/2)-(thi/2)+i, bpx+thi, bpy+(hei/2)-(thi/2)+i, BLACK);  
//6
  for (i=0; i<hei/2+thi/2; i++)
    tft.drawLine(bpx+wid-thi, bpy+(hei/2)-(thi/2)+i, bpx+wid, bpy+(hei/2)-(thi/2)+i, BLACK);  
//7
  for (i=0; i<wid; i++)
    tft.drawLine(bpx+i, bpy+hei-thi, bpx+i, bpy+hei, BLACK);
}

/*
//    1
// 2     3
//    4
// 5     6
//    7 
*/

 

단순히 세자리 숫자표시하는데 460줄 정도의 코딩이 되었습니다.

 

이방식으로 숫자를 표시하니 일체의 깜빡임없이 숫자가 표시됩니다.

(맨위쪽의 주행영상을 참고하세요)

 

 

두번째 문제는 야간 운전시 LCD가 밝아서 눈부심과 유리창에 비춰 운전에 매우 크게 방해가 되었습니다.

 

이문제는 LCD밝기를 줄이면 간단히 해결될 문제이긴 하나 아두이노용 LCD는 7~8천원 정도의 저렴한 부품이라 그런 기능자체가 아예 없다는게 문제입니다.

 

제가 생각한 방법은 2가지 인데, 하나는 자동차의 미등을 켰을때 이를 인식해서 처리하는 방법과 또 하나는 광센서를 달아서 주변 밝기에 따라 자동으로 어둡게 하는 방식이었는데, 미등을 인식하는 것은 차에서 선을 따와야해서 더 번거로울수 있어서 광센서로 처리하는 방식을 택하였습니다.

 

광센서 모듈의 아날로그 출력값을 아두이노에 동일하게 연결

 

제가 사용한 광센서 모듈은 디지털 출력과 아날로그 출력이 되는 모듈이었는데, 아날로그 출력을 아두이노 4대에 동일하게 아날로그 핀에 다 연결해 주었습니다.

(참고로 센서 하나를 여러개의 아두이노 보드에 연결하면 모든 아두이노 보드가 다 신호값을 받을수 있습니다)

 

이렇게 해서 광센서의 밝기값을 받아서 주간, 야간을 구분할수는 있긴한데, 이후 눈부심을 어떻게 처리하느냐가 문제인데, 저는 글자 색깔을 바꾸는 방법으로 처리하였습니다.

 

즉 야간일때는 채도를 엄청 낮춰서 어두운 색깔로 표시하는 방식입니다.

 

효과는 상당히 괜찮았습니다.

 

야간 주행시 크게 눈부심이나 불편함이 없었고, 이러한 처리방식은 필요에 따라 채도를 더 낮추면서 조절할 수 있는 장점이 되기도 했습니다.

 

앞서 올린 코드와는 조금 차이가 있으나 모든 코드에 동일하게 광센서 측정값을 처리해서 색상을 바꾸는 코드부가 들어가 있습니다.

 

 

 

광센서값을 아날로그 값으로 읽어서 낮밤에 따라 색상을 바꾸는 코드부입니다.

 

이 코드에 따라서 색상이 낮밤에 따라 아래처럼 변화합니다. (손으로 가려가며 테스트해봄)

 

 

광센서에 따라 색상을 바꿔가며 표시

(수정사항)

광센서를 통한 낮밤 구분은 나중에 자동차 미등 전원을 디지털 신호로 입력받아서 미등 켜고 끄는 거에 따라 변하도록 변경하였습니다.

퓨즈박스에서 미등전원을 따왔는데, 미등을 켤때 14V정도의 전원이 나오기 때문에 작은 DC레귤레이터를 하나 추가하여 5V이하로 다운하여 아두이노의 디지털입력으로 넣어주었습니다.

 

 

 

 

아두이노로 기술적인 부분들은 모두 해결하였고 3D프린터로 케이스를 출력하기 위해 먼저 모델링 작업을 하였습니다.

 

여러번 출력해 가면서 치수를 보완하여 딱 들어맞게 모델링하였습니다.

 

FUSION 360으로 케이스 모델링 (중간 2,3번 부분)
FUSION 360으로 케이스 모델링 (1, 4번 부분)

 

전체 가로폭이 40센치정도로 한 몸체로 출력할수가 없어 3파트로 짤라서 모델링하였고, 출력후엔 본드로 붙여서 완성하였습니다.

 

출력은 대략 10시간 정도가 걸리네요.

 

ABS 필라멘트로 출력

 

처음에는 PLA로 출력했는데, 사진처럼 뜨거운 대시보드 열을 못견디고 흘러내려서 ABS로 필라멘트를 바꿔서 재출력 하였습니다.

 

대시보드 열기로 인해 흘러내리면서 변형이 온 PLA 재질의 케이스

 

재출력을 하면서 좌우측 모니터는 살짝 안쪽으로 각도를 휘게하여 시야각이 거의 정면을 향하도록 하였습니다.

 

3D 프린터로 출력하여 최종 조립한 케이스

 

뒤쪽도 커버를 출력해서 씌워주었습니다.

 

뒷커버 없이 노출시켰더니 직사광선으로 고온으로 상승하여 정상작동 하지 않는 현상이 생겨 직사광선을 막아주기 위해 뒷커버를 씌웠습니다.

 

 

대시보드를 상부에 시야각이 정면을 향하도록  장착하였습니다.

 

대시보드 주행모습

 

OBD2 신호선과 전원선은 운전석 도어쪽으로 해서 하단으로 내렸고, 전원선은 퓨즈박스에 연결하여 키 ON시 자동으로 켜지도록 하였습니다.

 

여기까지의 과정이 포스트 1개의 글로 표현하였으나 장장 6개월정도의 기간이 걸린 프로젝트였습니다.

 

문제해결부터 코딩, 3D프린팅 등 재밌고도 험난한 길이었습니다.