형광등 램프 대신 LED 램프로 변경하면 전력 소모도 줄어 들고 형광등 램프 수명 종료로 인한 교체 주기도 길어 지기에 LED 램프로 교체를 많이 하고 있습니다.


그런데 기존 형광등 기구가 멀쩡하거나 특정 크기의 매립형 이라면 교체하기 아깝거나 교체 하기 쉽지 않을 수 있습니다.

자기 집이 아닌 경우 더욱 곤란 하겠죠!


그래서~ 

이 글에 소개 드리는 내용은 쉽게 구매할 수 있는 램프 소켓소켓형 LED 램프만 활용하여 기존 형광등 기구를 그대로 재활용 하여 최소 노력 + 최소 비용으로 LED의 장점을 누릴 수 있는 DIY 입니다.

아래는 매립형 기구를 소개 드리고 있지만 공간만 충분히 나온다면 방등 역시 재활용 가능 합니다.


필요 부품 

전구용 소켓 - 검색어로 "방수소켓"으로 여러 온라인 몰에서 구매 가능 (E26, 26베이스)

LED 램프 - 표준 크기 이하로 구매 (E26, 26베이스)

추가 전선 - 소켓에 전선이 없거나 짧은 경우 연장하여 이어서 사용 합니다

케이블 타이 - 아래 사진 참고하셔서 필요한 크기와 수량을 선정

압축 튜브 혹은 전기 테잎 - 전선 연장 후 마무리 작업


필요 도구 

니퍼 - 전선 피복 까기, 케이블 타이 자르기

플라이어(벤치) - 전선 잡기 용도 등

십자 드라이버 (형광등 기구의 전선 고정용 볼트를 풀때 사용 합니다)

납땝 인두기

납땜용 땜납

전기작업용 장갑(고무 코팅 장갑)




필수 부품인 E26 규격의 소켓 램프를 끼울 수 있는 소켓 입니다.

일명 "방수소켓" 이라고 합니다 - 이와 유사품으로 구매 하시면 됩니다. 단! 크기가 작아야 기존 등기구에 잘 장착 하겠죠


구매한 소켓에는 전선이 이미 짧지만 부착이 되어 있습니다.

끝 부분을 니퍼로 자르고, 전선의 피복을 벗겨 냅니다. (7~8mm 정도)

제가 부착할 조명 기구에는 하나는 그대로 부착이 되고 하나는 전선 연장이 필요 했습니다.

아래 부분에 연장에 사용할 두가닥 전선 입니다. 적당히 굵은 전선을 이용 하세요.

램프는 미리 끼워 두셔도 OK! - 설치 때는 램프 결합 상태로 위치를 잡으셔야 두번 작업 하지 않습니다.


피복을 벗긴 끝 부분은 납땝기로 적절히 납을 코팅 해 줍니다.

인두기가 없다면 그냥 손으로 감아 주셔도 됩니다.


연장해야 한다면 납땜으로 연결 후, 아래처럼 압축 튜브 혹은 전기 테잎으로 잘 감아 줍니다.

저는 압축 튜브로 깔끔하게 했습니다만 - 상황에 맞추어 배선이 노출 되지 않게 처리 하시면 됩니다.


공략 대상 형광등 기구의 내부 입니다.

형광등 2개는 빼냅니다. 그리고 연결 단자에 기존 연결된 형광등 안정기 배선도 뽑아 냅니다.

뽑는 방법:

1. 최우선으로 전등 스위치를 내립니다. 당연히 전기 작업용 코팅 장갑을 손에 끼우시고~

2. 아래 보이는 연결 단자에 보이는 단자 고정 부분의 볼트를 적당히 돌려서(왼쪽으로) 전선을 물고 있던 부분을 풀어 줍니다. (아래 사진 확대 하시면 십자 볼트 3개가 잘 보일 것입니다)

3. 기존 전선(가운데 초록색 접지선을 뺀 백색, 흑색 2개)을 분리 후 단자가 닿지 않는 위치로 잘 분리 합니다.


앞서 준비한 램프 + 소켓을 합체(!) 한 생태에서 형광등이 빠진 자리에 적절히 위치 시킨 후, 전선을 앞서 안정기의 전원선이 연결되었던 위치로 두 선을 각각 연결 합니다.

가정용 전원은 교류(AC)전원이기에 극성이 없습니다, 합선이 되지 않게만 잘 연결 해 주시면 됩니다.

(아래 사진 클릭 하면 원본 크기로 더 자세히 볼 수 있습니다)

전선 연결과 함께 케이블 타이로 램프를 등기구에 잘 매달아 주시면 됩니다.

케이블 타이는 플라스틱 재질이라 전기적인 문제 없이 고정 가능 합니다. 이 때 DIY 능력을 발현 해 주셔야 겠습니다 "잘" 묶어야 하니 말이죠!!!

마무리는 앞서 풀어 두었던 전선 연결 단자를 드라이브로 적당한 힘으로 다시 감아(오른쪽) 주세요 전선이 빠지지 않도록!


전선 연결을 최종 완료 후 전원 스위치를 켭니다. 아래처럼 잘 점등 하면 OK


아래는 커버를 닫은 상태를 촬영했습니다.

사실 기존 형광등 보다는 약간 어둡긴 하지만 기존 36W x 2개 에서 9W x 2개로 전력 소모가 낮아지기도 하고 약간 spec. down이라 어두워 진 것은 당연 한 결과 입니다.

대신 LED는 켜자 마자 최대 광량을 보이고 형광등은 켜고서 약간 시간이 지나여 최대 광량을 보입니다.

밝기를 더 밝게 하고 싶으면 12W급 램프로 LED 램프를 변경 하거나 아예 램프를 3개 설치 할 수 있겠지만, 실 사용 결과 9W급 2개도 만족 합니다.

더욱이 36W x 2 (72W) 에서 9W x 2 (18W) 라 엄청난 전력 감소 효과를 볼 수 있습니다.

LED라서 감소 한 것도 있지만 기존 램프가 오버 스펙(Over spec.) 이라서...


아래는 다른 위치의 내장형 램프를 변경한 결과 입니다. 위치가 반대이긴 하나 DIY 법은 동일 합니다.


이상 기존 형광등 기구를 그대로 활용한 소켓형 LED 램프 변경 DIY를 소개 드렸습니다.


012345678


이사 온 집에 기본 사양으로 설치 되어 있는 삼성 빌트인 12인 식기 세척기를 사용 해 보았습니다.

기존 사용하던 식기 세척기가 삼성쉐프콜렉션이라 그런지(다운 그레이드) 확연한 사용성 저하가 느껴 집니다.

세척 성능 차이는 논하지 않습니다. 정확한 비교는 제가 하기에 노력이 과해서~


아래는 삼성쉐프콜렉션 사용 하다 빌트인 세척기로 바꾸고 난 후 느낀 차이점 입니다. 

1. 동작 소음이 확연히 크다(신경이 쓰일 만큼)

2. 식기가 더 적게 들어 간다 - 나중에 확인 하니 세로(깊이) 길이가 빌트인12인이 더 짧습니다.

3. 식기 배치가 맘에 들지 않는다 (트레이가 영 배치가 힘듬)

4. 한동안 방치한 상태 여파로 환기구 쪽에서 녹물이 배어 나옴 (이건 품질 문제로 보이네요)

쉐프콜렉션은 동작 완료후 문을 자동으로 살짝 열어 주고, 빌트인은 닫힌체로 내부 뜨거운 공기를 밖으로 강제 배출 방식 입니다


결론은 중고로 팔까 했던 삼성쉐프콜렉션 식기 세척기를 다시 사용 하기로 했습니다.

이미 설치 공간 확보, 이사 전 분해, 복구 까지 경험이 쌓여서 맘 먹고 바로 실행 했습니다.


사진으로는 남기지 않았지만 교체의 경우는 기존 연결 부분이 모두 온전하기에 첫 설치 보다는 쉽다고 할 수 있습니다.

다만 실전에는 변수가 존재 합니다. 이번에도 교체 후 싱크대 안쪽으로 밀어 넣었는데, 몇 센티미터가 마저 들어 가지 않는 것이었습니다.

확인 결과 기존 싱크대가 짧았고, 어떤 이유인지 뒤쪽에 스토퍼(Stopper)형태로 나무 부품을 고정해서 밀려 들어가지 않게 해 둔 부분이 있었습니다.

간단히 제거 후 최종 설치는 완료 했습니다.


주요 사진 몇 장 공유 드립니다.


기존 식기 세척기 분해를 위해 좌/우에 고정된 볼트를 제거 합니다.


빌트인이라(입주전 사전 설치) 바닥의 높이가 주방 바닥과 동일하여 별다른 조치 없이 바로 뽑아 낼 수 있습니다.


살살 앞으로 당겨 냅니다.

당연히 미리 급수/배수구 연결 부위는 미리 분리 해 놓아야 합니다.

분해 시에 호스에 물이 남아 있으므로 적절한 도구(물을 담을 수 있는)를 사용하여 미리 배출을 해 놓아야 합니다.


뽑아낸 빌트인 12인 식기 세척기와 쉐프콜렉션 식기 세척기 형태 비교 입니다.

눈에 보이지 않는 부위지만, 철판 재질의 차이가 확연 합니다. 빌트인은 조금만 힘 주면 휘어 버리네요.

그리고 상단만 보더라도 방수 처리만 되었는 것과 흡음 패드로 소음 처리 된 것 차이가 눈으로 확인 됩니다.


뒤쪽 부분 길이 비교 입니다. 실제 사용 때에도 세로 길이가 많이 짧다고 생각 했는데, 정말 외형 길이 차이가 있었습니다.


깊이 길이 재 확인


교체하여 넣기 전 모습 입니다. 옆판도 고급 스럽네요 ^^

밀어 넣기 전에 급수/배수 호스 연결을 완료 하시고, 넣으면서 꼬이지 않게 정리(당기기) 하면서 싱크대 안쪽으로 옮기셔야 합니다.


비교되는 빌트인의 옆판


최종 교체 완료!

역시 동작 소리가 조용합니다. 다만 배수 모터 소리가 옥의 티 정도^^


이상 별 차이 없어 보이는 빌트인 식기 세척기와 쉐프콜렉션 식기 세척기의 간단 비교와 교체 작업 내용 이었습니다.


012345678


IKEA - BILLY / 이케아 빌리 책장 간단히 소개 드립니다.


아마도?! IKEA에서 많이 팔리는 물건 중 하나가 아닐까 생각 되는 물건으로, 보통의 책장이며 칸의 높이 조절을 할 수 있습니다. 최고 가로 80cm 까지 나오기에 공간에 맞추어 조합이 가능 합니다. (40, 60cm 선택 가능)


다만 가로 80cm 모델의 경우 패키지 무게가 무려 37kg ! 입니다. 길이(206cm)도 꾀 길고 - 직접 구매 하신다면 옮기는 방법과 충분한 길이의 적재 공간이 있는 차량(SUV 혹은 앞좌석을 눞이고 터렁크 공간이 열리는)을 확보 해야 합니다.


아래 사진 물건은 2016년 6월 구매 물품이었고, 이후 구매한 2017년 물품의 경우 몇 가지 업그레이드가 되어 있었습니다.


업그레이 내용은 2가지로 

1. 발 받침이 추가 됨 (위치 옮길때 좋네요)

2. 뒷판 접힘 부분 고정 테잎이 종이 테잎에서 비닐 테잎으로 개선 (적어도 찟어 먹는 경우는 방지)



2016년 판으로 사진 몇 장 공유 드립니다.


무겁고 긴 육중한 패키지! - 옮기실 때에 직접 들지 마시고 한쪽은 바닥에 붙이고 이동 하세요!


옆판 크기로만 패키징 되어 있습니다 - 어떻게 가능 한지는 나중에 뒷판 조립 때 밝혀집니다


고정 가로판 3개 장착! - 혹시나 옆에서 치면 부러지니 조심!


뒷판 빼고 조립 완료!


뒷판은 1/3 크기로 즉 접혀 있더라는! - 세상에나 이런 패키징, 조립 방법을 생각 하시다니!


근데 2016년 판에는 이게 종이 테잎 입니다. 살짝 힘을 주었더니 연결 종이 테잎이 찟어지더라는!
* 2017년 업그레이드판에는 종이 테잎 대신 셀로판 테잎이라 쉽게 찟어 지지 않습니다.


그리고 못을 박아서 최종 고정 합니다. 나사 아닙니다! 못!




중앙 고정은 자와 연필을 동원 하여 정확한 위치에 줄을 긋고 적절한(!) 간격으로 못을 박습니다.


최종 완성!

1/3 크기로 있던 뒷판이 - 접혀 있었다 펴진 - 변신 완료 상태임을 알 수 없이 깜쪽같이 통 뒷판으로 보입니다.


이상 IKEA BILLY 책장 조립과 완성품을 간단히 살펴 보았습니다.


0123456789


IKEA 노트북 스탠드 VITTSJÖ 조립 사진 공유 드립니다. (2016-01-02)

개인적으로는 노트북 대신에 짜투리 공간을 활용한 작업 스탠드로 사용 했습니다.

저렴한 가격에 투명 유리판을 이용한 작업대로 적절한 물품으로 보입니다.



박스 내용물과 포장 상태


높이 조절 가능한 발 받침


부착된 라벨


볼트 만으로 프레임 고정






유리와 상판 안착 부분은 접착식 투명하고 말랑한 보호 스트커를 사용 합니다.


최종 완성된 모습


몰아서 보기~

0123456789


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

MultiSegmentDisplay.h

#ifndef MULTI_SEGMENT_DISPLAY_H_
#define MULTI_SEGMENT_DISPLAY_H_

#include <stdint.h>

#define SEGMENT_OUT			PORTF
#define DIGIT_0_TO_7		PORTA
#define DIGIT_8_TO_15		PORTC

#define BAR_SEGMENT_OUT		PORTB
#define BAR_GRAPH_0_TO_7	PORTE


class MultiSegmentDisplay {
public:
	MultiSegmentDisplay();
	static void clearAll();
	static void setSegmentData(uint8_t bufferIndex, uint8_t rawValue);
	static uint8_t getRawValueFromOneDigitValue(uint8_t oneDigitValue);
	static uint8_t getRawValueFromOneChar(char aChar);
	static void set3DigitValueWithDotPos(uint8_t bufferIndex, uint16_t digit3Value, uint8_t dotPosition);
	static void setString(uint8_t bufferIndex, const char* string);
	static void setAttribute(uint8_t bufferIndex, uint8_t attribute);
	static void setAttribute(uint8_t bufferIndexStart, uint8_t bufferIndexEnd, uint8_t attribute);

	const static uint8_t MAX_SEGMENT_COUNT = 15;
								//   .gfedcba
	const static uint8_t RAW_0 = ~(0b00111111);
	const static uint8_t RAW_1 = ~(0b00000110);
	const static uint8_t RAW_2 = ~(0b01011011);
	const static uint8_t RAW_3 = ~(0b01001111);
	const static uint8_t RAW_4 = ~(0b01100110);
	const static uint8_t RAW_5 = ~(0b01101101);
	const static uint8_t RAW_6 = ~(0b01111101);
	const static uint8_t RAW_7 = ~(0b00000111);
	const static uint8_t RAW_8 = ~(0b01111111);
	const static uint8_t RAW_9 = ~(0b01101111);
	const static uint8_t RAW_DOT = (uint8_t)~(0b10000000);
	
	const static uint8_t RAW_A = ~(0b01110111);
	const static uint8_t RAW_B = ~(0b01111100);
	const static uint8_t RAW_C = ~(0b00111001);
	const static uint8_t RAW_D = ~(0b01011110);
	const static uint8_t RAW_E = ~(0b01111001);
	const static uint8_t RAW_F = ~(0b01110001);
	const static uint8_t RAW_G = ~(0b00111101);
	const static uint8_t RAW_H = ~(0b01110110);
	const static uint8_t RAW_I = ~(0b00110001);
	const static uint8_t RAW_J = ~(0b00001110);
	const static uint8_t RAW_K = ~(0b01110000);
	const static uint8_t RAW_L = ~(0b00111000);
	const static uint8_t RAW_M = ~(0b01111001);
	const static uint8_t RAW_N = ~(0b01010100);
	const static uint8_t RAW_O = RAW_0;
	const static uint8_t RAW_P = ~(0b01110011);
	const static uint8_t RAW_Q = ~(0b01100111);
	const static uint8_t RAW_R = ~(0b01010000);
	const static uint8_t RAW_S = ~(0b01101101);
	const static uint8_t RAW_T = ~(0b00000111);
	const static uint8_t RAW_U = ~(0b00111110);
	const static uint8_t RAW_V = ~(0b00111100);
	const static uint8_t RAW_W = ~(0b01001111);
	const static uint8_t RAW_X = ~(0b00110110);
	const static uint8_t RAW_Y = ~(0b01110010);
	const static uint8_t RAW_Z = ~(0b01011011);
	const static uint8_t RAW_BLANK = ~(0b00000000);
	
	//: ; < = > ? @						   .gfedcba
	const static uint8_t RAW_COLON =	~0b01000001;
	const static uint8_t RAW_SEMICOLON =~0b01000101;
	const static uint8_t RAW_SMALL =	~0b01011010;
	const static uint8_t RAW_EQUAL =	~0b00001001;
	const static uint8_t RAW_GREAT =	~0b01101100;
	const static uint8_t RAW_QUESTION =	~0b01010011;
	const static uint8_t RAW_AT =		~0b01011111;

	//[ \ ] ^ _ '
	const static uint8_t RAW_SQUARE_BRACKET_LEFT = ~0b00111001;
	const static uint8_t RAW_BACK_SLASH = ~0b01100100;
	const static uint8_t RAW_SQUARE_BRACKET_RIGHT = ~0b00001111;
	const static uint8_t RAW_HAT = ~0b00000001;
	const static uint8_t RAW_UNDER_LINE = ~0b00001000;
	const static uint8_t RAW_SINGLE_QUOTATION = ~0b00100000;
	
	// '|'
	const static uint8_t RAW_BAR = ~0b00110000;
	// + , -
	const static uint8_t RAW_PLUS = ~0b01000110;
	const static uint8_t RAW_MINUS = ~0b01000000;
	
	const static uint8_t ATTR_BLINK = 0b10000000;			// Blink
	const static uint8_t ATTR_BLINK_FAST = 0b11000000;		// Fast blink
	const static uint8_t ATTR_BLINK_FOREVER = 0b00111111;	// Not 63 secs but forever blink
	
	
	//----- Bar graph
	const static uint8_t MAX_BAR_SEGMENT_COUNT = 5;			// 8x5 = 40 LED
	static void setAccelerator(uint16_t accelerator);				// max 10bit = 1024 (Not linear)
	static void setRpmData(uint16_t down, uint16_t current, uint16_t up);	// Set rpm values
	
public:
	static uint16_t tick_timer;
	static uint8_t segmentOutBuffer[MAX_SEGMENT_COUNT];			// For fast operation it declared as public.
	static uint8_t segmentAttributeBuffer[MAX_SEGMENT_COUNT];	// Attribute for display
	static uint8_t blinkDelayCount;								// Internal blinking timing count
	
	static uint8_t barGraphSegmentOutBuffer[MAX_BAR_SEGMENT_COUNT];
	static uint8_t barGraphSegmentAttributeBuffer[MAX_BAR_SEGMENT_COUNT];
	
	static uint8_t maxAcceleratorIndex;
	static uint16_t maxAcceleratorIndexUpdateTickTime;
	static bool startMoveDown;
	
	static uint8_t downShiftPointSegmentIndex;
	static uint8_t downShiftPointOffsetIndex;
	static bool	 downShiftPointDimming;

private:
	static uint8_t charMappingRawData['}' - '0'+ 1];
	static void initTimer0ForSegmentRefresh();
	static void initTimer2ForAttributeUpdate();
	static void initPort();
};

#endif /* MULTI_SEGMENT_DISPLAY_H_ */

상단에 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 개념을 몰라 이렇게 둔 것은 아니니 감안 해서 봐 주세요)

 

이후 private method로 내부에서만 불리는 함수를 두었습니다.

 

이제 MultiSegmentDisplay.cpp 파일을 보겠습니다.

Constructor 

MultiSegmentDisplay::MultiSegmentDisplay()
{
	clearAll();
	initTimer0ForSegmentRefresh();
	initTimer2ForAttributeUpdate();
	initPort();
}

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 변환 루틴

/*******************************************************************************
 '0' ~ '9'		: 48 ~ 57
 : ; < = > ? @	: 58 ~ 64
 'A' ~ 'Z'		: 65 ~ 90
 [ \ ] ^ _ '	: 91 ~ 96
 'a' ~ 'z'		: 97 ~ 122
 { | }			: 123 ~ 125
*******************************************************************************/
uint8_t MultiSegmentDisplay::getRawValueFromOneChar(char aChar)
{
	uint8_t result = RAW_BLANK;
	if (aChar >= '0' && aChar <= '}') {
		result = charMappingRawData[aChar - '0'];
	} else if (aChar == '+') {
		result = RAW_PLUS;
	} else if (aChar == '-') {
		result = RAW_MINUS;
	}
	return result;
}

aChart는 ASCII code 한문자로 받아 7 segment common output bit값을 돌려 받습니다. code에서 보는 것 처럼 지정한 범위 내에서 준비한 bit 값으로 변환하는 것이 모두 입니다.

모르는 문자라면 그냥 빈 문자(아무 표시 없음)를 보여 주게 됩니다.

나머지 함수들은 크게 어려운 내용이 없어 추가 설명은 생략 하겠습니다.

 

 

15자리 7 segment + Bar graph 출력을 위한 인터럽터 핸들러 

/*******************************************************************************
 Update 1 digit 7 segment display.
 It need 15 times for update all digit.
*******************************************************************************/
SIGNAL( SIG_OUTPUT_COMPARE0 )
{
	//static uint16_t tick_timer = 0;

	//---------------------------------------- 7 segment section
	static uint8_t digitIndex = 0;
	uint16_t activeSegment = 1 << digitIndex;
	SEGMENT_OUT = 0xFF;	// Turn off all LED before change digit scanning (prevent crosstalk)
	DIGIT_0_TO_7 = activeSegment & 0x00FF;
	DIGIT_8_TO_15 = (activeSegment & 0xFF00) >> 8;

	uint8_t& attr = MultiSegmentDisplay::segmentAttributeBuffer[digitIndex];
	bool showOrNot = true;
	if (attr & MultiSegmentDisplay::ATTR_BLINK) {
		if ((attr & MultiSegmentDisplay::ATTR_BLINK_FAST) == MultiSegmentDisplay::ATTR_BLINK_FAST) {
			showOrNot = (MultiSegmentDisplay::tick_timer % 300 > 150);
		} else {
			showOrNot = (MultiSegmentDisplay::tick_timer % 2000 > 1000);
		}
	}
	if (showOrNot) {
		SEGMENT_OUT = MultiSegmentDisplay::segmentOutBuffer[digitIndex];
	}

	// Shift to next LED segment	
	if (++digitIndex >= MultiSegmentDisplay::MAX_SEGMENT_COUNT) {
		digitIndex = 0;
	}
  

앞서 타이머는 3,906.25 Hz 주기로 호출 되게 됩니다. 약 4KHz로 호출이 됩니다만 이 빈도로는 1개의 7 segment 문자를 표시 하는 시간입니다. 15자리를 모두 출력해야 하기에 앞서 설명처럼 3,906.25 / 15 = 260Hz 가 되게 됩니다.

 

static uint8_t digitIndex = 0;

최초 0번 위치부터 출력을 시작 하고 14까지 증가 하다가 다시 0으로 돌아 가서 15자리 문자를 계속 적으로 돌아가면서 표시 하는 index값으로 사용 합니다.

 

uint16_t activeSegment = 1 << digitIndex;

15자리 중 하나만을 active하게 선택 되어야 하기에 16bit 범위의 port출력용 binary 값을 설정 합니다.

이 값은 8bit port 두개로 나누어 출력 하게 됩니다. 아래 두 code.

DIGIT_0_TO_7 = activeSegment & 0x00FF;

 

DIGIT_8_TO_15 = (activeSegment & 0xFF00) >> 8;

 

if (attr & MultiSegmentDisplay::ATTR_BLINK) {

Attribute에 깜빡임 속성을 가지고 있다면 깜빡임 처리를 하는 부분으로서 timer 에 의해서 증가하는 tick counter 를 이용하여 주기별로 깜빡임 처리를 하게 됩니다.

 

위의 code에 연속하여 아래 code가 Bar graph 용으로 위치 합니다.

	//---------------------------------------- Bar-graph section
	static uint8_t barIndex = 0;
	uint8_t activeBartSegment = 1 << barIndex;
	BAR_SEGMENT_OUT = 0xFF;	// Turn off before shift the segment
	BAR_GRAPH_0_TO_7 = activeBartSegment;
	
	BAR_SEGMENT_OUT = MultiSegmentDisplay::barGraphSegmentOutBuffer[barIndex];
	// Additional effect for specific bit
	if (MultiSegmentDisplay::downShiftPointSegmentIndex) {
		if (barIndex == MultiSegmentDisplay::downShiftPointSegmentIndex) {
			bool bitShowOrNot = MultiSegmentDisplay::downShiftPointDimming ?
							(MultiSegmentDisplay::tick_timer % 50 > 40) : (MultiSegmentDisplay::tick_timer % 500 > 250);
			if (!bitShowOrNot) {
				BAR_SEGMENT_OUT |= (uint8_t)1 << MultiSegmentDisplay::downShiftPointOffsetIndex;
			}
		}
	}
	
	
	if ((!MultiSegmentDisplay::startMoveDown) &&
		(MultiSegmentDisplay::tick_timer == MultiSegmentDisplay::maxAcceleratorIndexUpdateTickTime)) {
		MultiSegmentDisplay::startMoveDown = true;	
	}
	if (MultiSegmentDisplay::startMoveDown) {
		static bool tickTock = false;
		if (tickTock != (MultiSegmentDisplay::tick_timer % 1000 > 500)) {
			tickTock = !tickTock;
			if (MultiSegmentDisplay::maxAcceleratorIndex > 0) {
				--MultiSegmentDisplay::maxAcceleratorIndex;
			}
		}
	}
	
	if (MultiSegmentDisplay::maxAcceleratorIndex) {
		static const int16_t aceelToBarLevel[11] = {	// LSB bit is left most position in real LED bar.
			~0b0000000001,	// Full accelerator
			~0b0000000010,
			~0b0000000100,
			~0b0000001000,
			~0b0000010000,
			~0b0000100000,
			~0b0001000000,
			~0b0010000000,
			~0b0100000000,
			~0b1000000000,
			~0b0000000000,	// No accelerator
		};
		
		if (barIndex == 0) {
			BAR_SEGMENT_OUT &= aceelToBarLevel[10-MultiSegmentDisplay::maxAcceleratorIndex] & 0xff;
		} else if (barIndex == 1) {
			BAR_SEGMENT_OUT &= (uint8_t)(aceelToBarLevel[10-MultiSegmentDisplay::maxAcceleratorIndex] >> 8);
		}
	}
	
	if (++barIndex >= MultiSegmentDisplay::MAX_BAR_SEGMENT_COUNT) {
		barIndex = 0;
	}
	
	//---------------------------------------- Common section
	if (++MultiSegmentDisplay::tick_timer >= 60000) {
		MultiSegmentDisplay::tick_timer = 0;
	}
}

기본적인 동작은 7 segment 15 자리 출력과 같습니다 - 8bit을 공용으로 5번을 나누어 총 40개의 LED를 제어 하게 하는 것이죠.

다만 추가적인 effect 처리를 위한 기술이 약간 가미가 됩니다.

 

RPM 표시에서 down shift RPM 부분을 현재 RPM 에서 떨어진 위치에 어둡거나 깜빡이게 표시하기 위해 아래 code가 사용 됩니다. (실 동작은 동영상 잘 보아 주세요^^)

어둡게(클러치가 놓여 진 경우): (MultiSegmentDisplay::tick_timer % 50 > 40 

  -> PWM제어 처럼 약 20% duty cycle을 사용하여 이론상 1/5 가도로 LED 밝기가 조절 됩니다.

깜빡이게(클러치를 밟은 경우): (MultiSegmentDisplay::tick_timer % 500 > 250) 

  -> 좀더 길게 시간을 띄워서 PWM제어보다는 빠른 깜빡임으로 50% 비율로 보이게 합니다.

 

나머지 code부분은 Accelerator 에서 발을 떼었을 경우 직전 최고 위치에서 아래 단계로 LED 한개를 스스륵 아래로 이동 시키는 모습을 보여 주는 부분입니다. Audio graphics equalizer 처럼 최대 높이에 대해서 아래로 밀려 오는 그 모습을 code로서 만든 것입니다.

총 10bit를 차례로 움직이게 하는 노력이 포함 되어 있습니다. 말로 설명하기에 code보다 길어 질 듯 하여 이 부분은 직접 보시고 이해 해 주셨으면 합니다. 아마도 추가 optimize가 필요 할 수 있을 듯 합니다.

 

 

 

이상 주요 code 부분을 확인 했습니다. bar graph 부분 등은 이미 공유 드린(두번째 글에서 다운로드 가능 합니다) 소스 code에서 직접 확인 하시기 바랍니다.

더 많은 것을 설명 드렸으면 하지만 이것으로 마무리 하고자 합니다. 향후 변동 사항 혹은 개선 내용이 있다면 update 하도록 하겠습니다.

 

2년 전 시작한 DIY project를 결국 마무리를 했습니다. 시원섭섭? - 좀 더 작게 만들고 싶은 욕심은 있지만 당분간은 제 차에서 최초의 목적을 잘 수행 하리라 생각 됩니다.

덕분에 타이어 공기압을 조절 해야만 한다는 압박을 받고 있습니다. 날이 조금 시원해 지니 절로 타이어 압력이 10% 가량 줄어 들었음을 바로 바로 확인 할 수 있는 덕이죠.

 

감사합니다.

 

+ Recent posts