DIY - TPMS(Tire Pressure Monitoring System) value display for RENAULT and NISSAN via CAN(Controller Area Network)
세번째 글 입니다.
첫번째 글에서 전체 설명과 모듈 구성의 실제 사진을 주로 보았고, 두번째 글에서는 실제 결선도와 주요 기능에 해당하는 code를 함수 단위로 살펴 보았습니다.
이번 글에서는 타이어 압력 수치 값을 표현하는 7 segment display 를 출력하는 루틴과 악셀과 엔진 RPM을 표현하는 bar-graph display 루틴을 살펴 봅니다.
앞서 언급 드렸지만, LED 7 segment 를 사용한 출력을 선택한 이유는 낮/밤 시인성이 좋아서 입니다. Graphic LCD를 사용하면 좀 나을 지 모르지만 Back light 를 통해 보이는 STN LCD 특유의 희미함은 어떻게 할 수 없는 아쉬움을 남게 하더군요.
3자리 7 segment 5개를 사용했기에 최종적으로 총 15자리를 디스플레이 해야 합니다.
7 segment 당 8개의 출력이 있어야 하기에 단순하게 모두 직결 한다면 8*15 = 120개의 신호선이 필요 합니다만, 이렇게 하지는 않습니다.
대신 8개의 출력은 15자리 모두에 공통으로 연결 됩니다(숫자표시+점). 다만 15개의 세그먼트 중 한번에 하나의 세그먼트만 선택이 됩니다.
즉 모두 15개 중 순간적으로 멈추어 보면 한개만 표시 하는 것이죠.
이렇게 하면 총 필요 신호선은 8 + 15 = 23 선만 연결하면 됩니다.
다만 충분히 빠른 속도로 15자리를 순차적으로 보여 주어야 합니다. 그래야 사람의 눈으로 보았을 때 전체 정보가 동시에 보이게 됩니다. 올려둔 위의 동영상/사진을 보면 동시에 15자리 세그먼트가 잘 보이고 있는 것을 확인 할 수 있습니다 - SLR 카메라 등의 셔터 속도로 충분히 짧게 (1/200 이하) 하여 촬영하면 1개만 출력 되는 것을 확인 할 수 있습니다.
이 부분에 대한 code를 살짝 살펴 보겠습니다.
Multiplexing segment refresh 루틴은 저도 처음 짜 보는 code 라 아마도 개선할 부분이 있을 듯 합니다! (제보 환영 합니다^^)
아두이노 처럼 C++ class로 묶어 보았습니다. 8bit AVR MICOM에서 C++ 사용 할 수 있게 된 것은 정말 큰 축복! 입니다. 다만 memory(AT90CAN도 겨우 8KB)가 워낙 작아 그냥 모듈에 대한 class화 정도 만으로 만족을...
Display routine header file - C++ class static method sets
상단에 LED 제어용에 할당한 Atmega 주요 port를 선언 하고, .cpp에서 해당 이름으로 사용 하게 됩니다.
이 class는 실제 singleton 으로 처리해서 사용 해야 하겠지만, MICOM에서 getInstance 함수 만들고 중복 생성 막고 하는 code를 넣는 것은 너무 과한 것이라 한번만 만들고 static method 직접 사용 하는 것을 기본으로 했습니다. (PC가 아닙니다!)
상단 부분에 7 segment 15자리 출력 제어용 함수들이 모여 있고 (clearAll() ~ setAttribute()) 그 다음에 7 segment 출력 선에 맞는 문자 data를 선언 했습니다. 알파벳 문자도 7 segment 한계에 맞추어 출력 하게 됩니다.
그리고 Bar graph 용 함수가 위치 합니다(setAccelerator(), setRpmData)
아래 부분에 public 으로 공개된 static member variable이 위치 합니다. 여기서도 당연히 일반 C++라면 member variable은 private 영역이어야 하겠지만 MICOM + 1ms 도 아까운 multiplexing display routine이라 그냥 public 처리하고 각종 interrupt 함수를 비롯한 주요 함수에서 바로 접근 하도록 설정 한 것입니다. (public/private 개념을 몰라 이렇게 둔 것은 아니니 감안 해서 봐 주세요)
clearAll() 에서는 segment 출력 buffer 를 표시가 되지 않게 기본 값으로 둡니다.
initTimer0ForSegmentRefresh(), initTimer2ForAttributeUpdate() 는 주기적인 갱신을 위한 timer를 초기화 하게 됩니다.
마지막 initPort() 는 LED 출력에 할당된 port를 초기화 - 첫 출력값을 설정 합니다.
타이머 설정 함수 두개만 먼저 보면,
/*******************************************************************************
7 Segment update timer initialization
*******************************************************************************/
void MultiSegmentDisplay::initTimer0ForSegmentRefresh()
{
cli();
TCCR0A = _BV(WGM01) | _BV(CS01) | _BV(CS00); // 0b00001011WGM01 for CTC and Clock bit 011 (/64)
OCR0A = 64; //125; // 16,000,000 / 64 / x = 2,000 | x = 125
TIMSK0 = _BV(OCIE0A); // 0b10 – OCIE0A: Timer/Counter0 Output Compare Match A Interrupt Enable
sei();
}
/*******************************************************************************
Setup the timer 2 for update the attribute with timeout
*******************************************************************************/
void MultiSegmentDisplay::initTimer2ForAttributeUpdate()
{
// Long term timer for attribute time out looping logic
// 16MHz 0.0625us to /1024 then --> 15,625 Hz
// / 255(OCR2A) then 61.03 Hz
// / 156(OCR2A) then 100.16 Hz
cli();
TCCR2A =0b00000111; // /1024 [CS22,CS21,CS20] = 111
OCR2A = 155; // /156 then 100.16Hz --> + 10 count then 10Hz
TIMSK2 = TIMSK2 | (1<< OCIE2A); // Invoke interrupt on OCR2A matched
sei();
}
initTimer0ForSegmentRefresh() 함수 - 15자리 7 segment 한개 문자 refresh를 위한 interrupt 설정
TCCR0A = _BV(WGM01) | _BV(CS01) | _BV(CS00);
Atmega 의 8-bit Timer/Counter Register 를 설정 하는 부분입니다.
TCCR0A (Timer/Counter0 Control Register A) 값을 설정하여 순수 timer interrupt 를 발생 시키게끔 설정 하는게 목적 입니다.
Timer mode 2 (Clear Timer on Compare match (CTC) mode) 선택을 위해 WGM01 = 1 / WGM00 = 0 으로 설정 합니다. [!]이 두 bit는 연속 하지 않습니다
그리고 timer 증가 clock source 설정 하기 위해 나머지 하위 bit 3개인 CS02 / CS01 / CS00 를 설정합니다. code 상에서는 011 을 설정하여 현재 clock 기준으로(System Clock Prescaler - CLKPR 참조) 으로 /64 분기를 사용 합니다.
16MHz 를 사용 중이므로 16,000,000 / 64 = 250,000 Hz 값이 나오고 counter 는 250KHz 주기로 증가(+1) 하게 됩니다.
앞서 Timer mode 2 는 counter(TCNT0) 가 기본으로 매 주기(앞서 250KHz로 설정)마다 1 값이 증가 합니다. 이 증가 값이 OCR0A (Output Compare Register A)에 지정한 값이 되면 interrupt가 발생하게 사용할 수 있습니다.
** _BV() 는 해당 bit index 에 해당 하는 bit 값을 만들어 주게 됩니다. (AVR compiler only)
OCR0A = 64;
다시 말에 앞서 250KHz 주기당 1씩 증가하는 값에 대해서 OCR0A 를 적절히 설정하면 250KHz / OCR0A 값의 주기 마다 interrupt가 발생하도록 세밀한 조절을 할 수 있게 됩니다.
code에서는 64 값을 사용 했으므로 250,000 / 64 = 3,906.25 Hz 가 나오게 됩니다.
나중에 보겠지만 총 15자리 수에 대해 이 주기 값을 사용 하게 되므로 15자리 모두 디스플레이(한 장면 모두 갱신)에는 3,906.25 / 15 = 260 Hz 라는 수치가 나오게 됩니다. 즉 15자리 display 모두 출력하는데 약 1/260 sec 가 걸리게 된다는 의미 입니다. 형광등의 1/60 보다 4배 정도의 속도 이므로 고정된 시점에서는 깜빡임을 느끼지 않을 만한 속도가 되겠습니다.
(디스플레이를 막 흔들어 보거나 눈동자를 주우로 움직여보면 약간의 떨림을 느낄 수는 있답니다)
TIMSK0 = _BV(OCIE0A);
우리는 OCR0A 의 설정한 값이 match 값과 동일하면 interrupt를 발생 시키는 것을 원하기에
OCIE0A : Timer/Counter0 Output Compare Match A Interrupt Enable 를 설정 합니다.
initTimer2ForAttributeUpdate() 함수 - 7 segment 의 깜빡이 효과를 제어하기 위한 timer를 설정
특정 값이 바뀌었음을 알기 위한 가장 효과 적인 방법은 해당 부분을 강조 하면 되는데 깜빡이거나 색상을 달리 하면 효과 적입니다. 우리는 단색 7 segment 를 사용 중이므로 깜빡임 처리 만큼 효과 적인 선택은 없을 듯 합니다.
이 깜빡임을 위한 효과를 위하여 별도의 timer를 설정 하는 것으로 앞서 Timer0 설정 한 것과 매우 유사하게 설정 합니다.
초당 10번 까지의 깜빡임 혹은 타이밍 계산을 위해 최종적을 10Hz 호출 주기의 interrupt 소스를 만들게 됩니다.
상세 내용은 앞서 timer 0 와 유사하기에 추가 설명은 생략 합니다.
(음... 이 글 쓰면서 code를 확인하니 mode를 2 로 설정하지 않고 있네요 ^^ / 향후 다시(!) 만들 일 있으면 해당 부분 재 확인 하겠습니다)
타이머 인터럽터 핸들러를 보기 전에 15문자를 buffer에 기록 하는 부분을 먼저 보겠습니다.
최종 출력을 위해서 static으로 잡혀 있는 buffer에 문자를 기록 하게 되고, 인터럽트 루틴에서 buffer에 있는 raw한 value를 port에 적절히 출력 지정 하게 됩니다.
static uint8_t getRawValueFromOneChar(char aChar) : 문자 -> Port 출력 8bit 변환 루틴
-> 좀더 길게 시간을 띄워서 PWM제어보다는 빠른 깜빡임으로 50% 비율로 보이게 합니다.
나머지 code부분은 Accelerator 에서 발을 떼었을 경우 직전 최고 위치에서 아래 단계로 LED 한개를 스스륵 아래로 이동 시키는 모습을 보여 주는 부분입니다. Audio graphics equalizer 처럼 최대 높이에 대해서 아래로 밀려 오는 그 모습을 code로서 만든 것입니다.
총 10bit를 차례로 움직이게 하는 노력이 포함 되어 있습니다. 말로 설명하기에 code보다 길어 질 듯 하여 이 부분은 직접 보시고 이해 해 주셨으면 합니다. 아마도 추가 optimize가 필요 할 수 있을 듯 합니다.
이상 주요 code 부분을 확인 했습니다. bar graph 부분 등은 이미 공유 드린(두번째 글에서 다운로드 가능 합니다) 소스 code에서 직접 확인 하시기 바랍니다.
더 많은 것을 설명 드렸으면 하지만 이것으로 마무리 하고자 합니다. 향후 변동 사항 혹은 개선 내용이 있다면 update 하도록 하겠습니다.
2년 전 시작한 DIY project를 결국 마무리를 했습니다. 시원섭섭? - 좀 더 작게 만들고 싶은 욕심은 있지만 당분간은 제 차에서 최초의 목적을 잘 수행 하리라 생각 됩니다.
덕분에 타이어 공기압을 조절 해야만 한다는 압박을 받고 있습니다. 날이 조금 시원해 지니 절로 타이어 압력이 10% 가량 줄어 들었음을 바로 바로 확인 할 수 있는 덕이죠.