지난글 까지 내용으로 H/W 적인 구성은 완료 되었다고 볼 수 있습니다. 물론 완전 상세한 구성법을 올려드리지 못해 이전에 이런 내용을 경험 해 보지 않으셨다면 쉽지 않을 것 같습니다.
어찌되었건 ATMega8 Kit(시중에 판매 중)를 이용할 수도 있기에 컨트롤러 구성은 그렇게 어려운 일만은 아닐 것입니다.

H/W에 영혼을 불어 넣는 S/W 만들기 작업이 남아 있습니다.
여기에서는 S/W 구성에 필요한 여러 요소와 함께 실제 Code부분을 제시해 드리고자 합니다.
실제 ATMega8 보드로의 ISP 연결, ISP프로그래머 구비 등등은 설명 드리지 않으므로, 관련 지식이 없으시면 AVR 개발 방법은 별도로 학습 하셔야 합니다.

[사진: 제작한 코너링램프 콘트롤러를 이용한 실제 동작 모습]
차례대로, 위회전, 미동작, 좌회전용 코너링 램프가 켜진 모습입니다 (LED라 푸른 빛을 보입니다


이 내용은 총 3개 글로 나뉘어 져 있습니다
  1. 제작에 필요한 내용 확인 
  2. 컨트롤러와 센서모듈 제작
  3. 컨트롤러 프로그래밍과 설치 [지금 보고 계십니다]  


본 내용에 대해서 상업적 이용을 제외한 개인적인 사용에는 아무런 제약이 없이 자유롭게 이용 가능 합니다. 가능하면 링크를 추천드리며, 복사하실경우 출처를 표기 해 주시기 바랍니다. 감사합니다.







1. 컨트롤러 프로그래밍 
1.1. 램프 On/Off 조건에 대한 고려 

사실 1편- "제작에 필요한 내용 확인"에서 언급한 내용이 H/W 구성 뿐만 아니라 결국 S/W 개발에 필요한 사항을 정리 한 것이라 할 수 있습니다.
즉 크게는 휠각도, 차량속도, 후진상태 이 3가지 변수를 조합 해서 좌/우의 램프를 켜는 것이 아래에 언급한 S/W 가 해야 할 일입니다.
그러나 앞서 언급 드렸지만 휠 각도는 절대값을 읽을 수 없는 문제를 해결 해야 하며(스티어링휠은 좌,우로 1바퀴 반씩 총 3바퀴 회전 할 수 있습니다), 차량 속도는 한 시점에서 On/Off 가 결정되는 것이 아니라 올라 갈 경우와 내려 갈 경우의 경계값이 다릅니다. 그리고 후진상태는 좌/우 램프 방향을 반대로 해 주어야 합니다. 


1.2. 스티어링 휠 센서를 통한, 회전 감지 알고리즘 개발 

불행이도 리니어(Linear)형태의 절대값으로 회전각을 알수 없습니다! 또한 최초 0위치 여부도 가정 할 수 없습니다(0도에는 센서가 없으며, 시동 걸기전 휠 회전상태에 따라 오류 발생).
왜!냐면 6개의 Hall effect sensor를 두어서 회전 방향과 회전 각을 감지하는 방식이기에 Automata 방식 State transition 알고리즘을 만들어야 합니다.
상태값 전이(Finite State Machine) 기법을 채용하는 것이죠.

홀센서 배치위치에 따른   -80 , -38 , -26 , +26 , +38 , +80 각으로 부터
구체적으로 우리가 알아야 할 각도값은 아래와 같습니다.
-440, -398, -386, -334, -322, -280, -80, -38, -26, 0 , 26, 38, 80, 280, 322, 334, 386, 398, 440

좀더 시각화 하면 아래와 같습니다. (아래는 개별 State 입니다)

<<<   <<       <        >      >>     >>>   <<< <<    <     |     >    >>   >>>   <<<     <<        <        >        >>     >>>
L440,L398,L386,L334,L322,L280,L80,L38,L26,0,R26,R38,R80,R280,R322,R334,R386,R398,R440
색상의 의미는 센서 위치에 따라 구분을 했습니다. 

YELLOW : LEFT80

ORANGE : LEFT38

RED         : LEFT26

GREEN   : RIGHT26

CYAN       : RIGHT38

MAGENTA : RIGHT80


원론적으로 개별 state transition은 에러 보정 처리 이외는 인접한 state로만 이동이 가능!
양쪽 한계를 넘어가는 현상이 일어나면, 최초 계산 시점에 360도 돌려 져 있는 상황이므로, 해당 시점에 보정 수행을 하면 됩니다  (차량 전원이 켜지기 전에 휠이 0 위치가 아니면 발생 합니다)

> 시동을 켜기전에 휠이 좌측으로 꺽어져 있었다면 최초 정방향 전진을 위해 휠을 0도로 위치 시킴에도 불구하고 코너링 콘트롤러는 우회전으로 인식하여 오른쪽 램프를 켜게 됩니다. 이때 언젠가는 한번 우회전을 한번 하게 되고, 이때에 잘못된 계산을 바로 잡게 됩니다.
제가 몇개월 이상 운행 해 보았는데 이러한 보정법으로도 실제 사용시에는 아무런 불편함이 없었습니다. 좌회전만 하는 혹은 우회전만 하는 운전을 한다면 모르겠지만, 대부분 시동 직후 좌/우 회전은 수행 하게 되므로 이때 잘못된 0점 조절을 수행하게 됩니다.

*EPROM에 마지막 회전각을 기억 하는 방식도 가능하나 휠의 움직임에 의해 시동 전후의 미세한 변동이 있을 수 있기에 EPROM 방식 보다는 에러 보정 방식을 채용 했습니다. 

다시 한번 정리 하자면 6개의 센서는 스티어링 회전에 연동하므로, 센서로 들어오는 신호의 전후 신호만으로 전체 회전값을 에러없이 계산이 가능 하다는 이야기 입니다.
에러가 발생하는 조건은 state가 L440, R440 의 좌/우 한계값을 넘어 서는 경우이고 이때는 보정처리 한번으로 이후에 정확성을 보장 할 수 있습니다.
더 상세한 것은 소스코드(함수: 
RotateStateTransition)를 참고 하세요!  


1.3. 속도에 따른 코너링 Lamp On/Off 판단 로직 개발
 

앞서 정리를 다시한번 언급 하면
[0..20) km 까지 조향각 80도 부터 ON  20km 는 약 - 7 pulse/sec 이고,
20 km 초과 부터 28도 부터 ON
95 km(33 pulse):  / 100km(35 pulse)             14:40 = x:95 | 14:40 = x:100 

속도 값은 Pulse 갯수를 세어서 환산이 가능하기에
10km 당 3.5 펄스
여기서 사용할 20km 는 7 펄스/ 95 km 는 33펄스, 100km 35 펄스로 환산 가능
A 0 km : 0 pulse
B 20 km : 7  
C 22.8 km : 8 
D 95 km : 33 
E 100 km : 35 

아래와 같이 펄스(속도)에 따른 State 상태는 아래와 같이 표현이 가능합니다.
여기서도 State machine 으로 처리를 하여 실전에 응용을 하게 됩니다. 
A  →   B  →(High)  C  →   D  →(Off)   E 
A  ←   B   (Low)←  C  ←   D  (On)←  E  

속도와 휠각도값에 의한 램프 켜짐 조건은 아래와 같이 최종 정리가 됩니다.
저속에서는 80도 까지 꺽여야 켜지고, 중속에서는 38도만 꺽여도 켜지게 됩니다.
 
                       L80, L38, L26, R26, R38, R80
SPEED_LOW L . . . . R
SPEED_HIGH L L . . R R
SPPED_OVER . . . . . .

상세한 것은 소스코드(함수: 
SpeedStateTransition)를 참고 하세요. 


앞서 내용은 따분할 수 있는데, 결국 Code
는 알고리즘의 표현이고 우리의 목적을 달성하려면 막코딩으로 불가능 하여 좀 길게 주절 주절 정리를 했습니다.
실제 저도 쉽게 생각 했다가 결국 state machine을 사용 하게 되었네요. 복잡하다 생각이 될 수도 있지만 최종 목적을 더 쉽게 하는 방법을 채용 한 것이랍니다.
[땜방은 땜방일뿐 제대로된 알고리점 적용은 필수!]


1.4. 소스코드 
아래부터 소스 코드 입니다. 앞서 설명한 부분이 실제 code화 되었습니다. comment도 달려 있으니 앞서 설명한 부분과 연계하면 어렵지 않을 것(?) 같습니다.

/*******************************************************************************
 (C) 2011 Seung-Won Lee   http://whoisit.tistory.com SoftWareYi@gmail.com


  코너링 램프 컨트롤러

  @File  CorneringLampControl.c

  휠각도 센서를 직접적으로 사용 할 수 없으므로, Hall 센서를 이용하여
  스티어링 휠 회전 각도를 측정하여 코너링 램프를 조절 하게 된다.
  스티어링 휠은 좌우로 1회전 반을 하기 때문에 센서로 절대값을 측정 할 수 없다
  그러므로 회전 판단과 에러 보정을 위해 Automata 형식의 state machine 을 채택

  20km (7 pulse/sec) 까지는 80 도에서 On 
  20km 초과 부터 38 도에서 On
  100km (35 pulse) 초과 부터 전체 Off
  100km 초과 Off에서 95km (33 pulse) 진입시에 다시 On

  Turn on 상태에서 일정시간 초과시에는 auto off 하여 error 상황 등에서 지나치게
  오래 점등 되지 않도록 한다.

*******************************************************************************/
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay_basic.h>
#include <limits.h>


//******************************************************************************
// Configurations
//******************************************************************************
#define USE_POLLING_SENSOR_CHECKING 1 // Interrupt가 아닌 폴링 방식으로 센서값 읽기 루틴 사용
#define SENSOR_PORT PINC // ATMega8 PortC[0..5] 를 센처 입력 처리.


//******************************************************************************
// Enumerations
//******************************************************************************
typedef enum { FALSE, TRUE } BOOL;

// 센서 구별값 정의
enum { VOID, SENSOR_1, SENSOR_2, SENSOR_3, SENSOR_4, SENSOR_5, SENSOR_6 };
#define LEFT80 SENSOR_1
#define LEFT38 SENSOR_2
#define LEFT26 SENSOR_3
#define RIGHT26 SENSOR_4
#define RIGHT38 SENSOR_5
#define RIGHT80 SENSOR_6

// State 정의
enum {  L440, L398, L386, L334, L322, L280, L80, L38, L26, 
ZERO, 
R26, R38, R80, R280, R322, R334, R386, R398, R440 };

// Cornering lamp 조절
enum { OFF, ON_LEFT, ON_RIGHT, ON_LEFT_RIGHT };

// 현재 속도 상태
enum { SPEED_LOW, SPEED_HIGH, SPEED_OVER }; // Cornering lamp On 조건 (LOW: 80 도 On / HIGH: 28 도 On / OVER: 전체 Off)
enum { SPPED_0_20km, SPPED_20_23km, SPPED_23_95km, SPPED_95_100km, SPPED_100_OVERkm };


//******************************************************************************
// Global variables
//******************************************************************************
char RotateState = ZERO;
char LastRotateState = ZERO;

unsigned char speedPulseCount = 0;
unsigned char lastSpeedPulseCount = 0;

char SpeedState = SPPED_0_20km;
char LampControlSpeed = SPEED_LOW;

unsigned long int eleapsedTimeSet = 0; // 최초 동작이후 경과 시간(초) - 범위 초과시 round off됨.


/*******************************************************************************
 현재 time count 돌려주기

*******************************************************************************/
unsigned long int GetCurrentTime()
{
return eleapsedTimeSet;
}


/*******************************************************************************
 저장한 time 기준으로 몇초 경과 여부 확인

*******************************************************************************/
int IsTimeOver( unsigned long int baseTime, unsigned int elapseTime )
{
if ( eleapsedTimeSet > baseTime )
return ( (eleapsedTimeSet - baseTime) > elapseTime );
else 
return ( (baseTime - baseTime) > elapseTime );
}


/*******************************************************************************
 Pulse count 에 따라 개별 속도 지점 상태를 갱신 한다

*******************************************************************************/
void SpeedStateTransition( unsigned char speedPulse )
{ //    L     LH     H      HO      O
int i; //  0-20  20-23  23-95  95-100  100-300
unsigned char pulseForSpeedIndex[] = { 0,    7,    8,     33,     35,    255 };
unsigned char currentSpped = SPPED_0_20km;

for ( i = 0; i<sizeof(pulseForSpeedIndex)/sizeof(pulseForSpeedIndex[0])-1; i++ )
if (( pulseForSpeedIndex[i] <= speedPulse ) && ( speedPulse < pulseForSpeedIndex[i+1] ))
{
currentSpped = i;
break;
}
switch (currentSpped)
{
case SPPED_0_20km:
switch (SpeedState)
{
//case SPPED_0_20km: // Nothing todo
case SPPED_20_23km:
case SPPED_23_95km:
case SPPED_95_100km: // 1초 만에 100km->23km 는 불가능 하겠지만... 안전 차원 (이하 동일)
case SPPED_100_OVERkm:
LampControlSpeed = SPEED_LOW;
break;
}
SpeedState = SPPED_0_20km;
break;

case SPPED_20_23km:
switch (SpeedState)
{
case SPPED_0_20km: // 인접 상태에서 전이 된 경우 이전 state 유지
case SPPED_20_23km:
case SPPED_23_95km:
// Do noting
break;
case SPPED_95_100km:
case SPPED_100_OVERkm:
LampControlSpeed = SPEED_HIGH;
break;
}
SpeedState = SPPED_20_23km;
break;

case SPPED_23_95km:
switch (SpeedState)
{
case SPPED_23_95km:
break;
case SPPED_0_20km:
case SPPED_20_23km:
case SPPED_95_100km:
case SPPED_100_OVERkm:
LampControlSpeed = SPEED_HIGH;
break;
}
SpeedState = SPPED_23_95km;
break;

case SPPED_95_100km:
switch (SpeedState)
{
case SPPED_23_95km:
case SPPED_95_100km:
case SPPED_100_OVERkm:
break;
case SPPED_0_20km:
case SPPED_20_23km:
LampControlSpeed = SPEED_HIGH;
break;
}
SpeedState = SPPED_95_100km;
break;

case SPPED_100_OVERkm:
switch (SpeedState)
{
case SPPED_100_OVERkm:
break;
case SPPED_0_20km:
case SPPED_20_23km:
case SPPED_23_95km:
case SPPED_95_100km:
LampControlSpeed = SPEED_OVER;
break;
}
SpeedState = SPPED_100_OVERkm;
break;
}
}


/*******************************************************************************
 Timer interrupt - 비교 대상과 일치하였을때 인터럽트 
 1024 분주 카운팅에 125회째 인터럽트가 발생 16,000,000 / 1024 / 125 = 125 Hz
*******************************************************************************/
SIGNAL( SIG_OUTPUT_COMPARE2 )
{
volatile static unsigned char timer_count = 0;

//if ( 1 == timer_count || 65 == timer_count )
// PORTD ^= 0x40; // Toggle on PD5
if ( 65 <= timer_count )
PORTD |= 0x40; // Toggle on PD5
else
if ( 10 >= timer_count )
PORTD &= ~0x40; // Toggle on PD5

    if(timer_count >= 125){ // 125번 인터럽트가 발생하면(1초마다)
SpeedStateTransition( speedPulseCount );
timer_count=0;
lastSpeedPulseCount = speedPulseCount;
speedPulseCount = 0;
++eleapsedTimeSet; // Global elapsed time
    }

    ++timer_count; //인터럽트 발생 횟수 기록
}


/*******************************************************************************
 센서값을 읽어 Active 센서를 리턴

 6개 중 한개만 Active bit (1) 고 나머지는 bit(0)을 유지 함. -> 어느 센서인지 리턴
 5V TTL -> 12V relay 제어가 필요 하므로 NPN TR을 이용한다. 즉 5V 출력으로 active
*******************************************************************************/
void SetCorneringLamp( char state )
{
switch ( state )
{
case OFF:
PORTD &= 0xFC; // ******00 == off
break;

case ON_LEFT:
PORTD = (PORTD & 0xFC) | 0x01; // 0 위치 bit을 On
break;

case ON_RIGHT:
PORTD = (PORTD & 0xFC) | 0x02; // 1 위치 bit을 On
break;

case ON_LEFT_RIGHT:
PORTD = (PORTD & 0xFC) |  0x03; // 하위 두 bit On
break;
}
}


/*******************************************************************************
 센서값을 읽어 Active 센서를 리턴

 6개 중 한개만 Active bit (0) 고 나머지는 bit(1)을 유지 함. -> 어느 센서인지 리턴
*******************************************************************************/
char GetActiveSensor()
{
char result = VOID;
char activeSensorBit = (~SENSOR_PORT) & 0x3F; // Pull up이므로 기본 1 / 선택 센서만 0, 반전 후 하위 6bit 만

switch (activeSensorBit)
{
case 0x01 :
result = LEFT80; 
break;
case 0x02 :
result = LEFT38; 
break;
case 0x04 :
result = LEFT26; 
break;
case 0x08 :
result = RIGHT26; 
break;
case 0x10 :
result = RIGHT38; 
break;
case 0x20 :
result = RIGHT80; 
break;
}
return result;
}


/*******************************************************************************
 새로 입력 받은 SENSOR 위치를 확인 해서 STATE를 변동 처리

 6개 센서 입력에 대해 이전 state를 확인하여 회전상태를 갱신 하게 한다.
 또한 이전의 잘못된 판단을 확인하여 state를 보정하는 역할도 하게 된다.

 case 우측 부분에 Jumping 부분은 바로 이전 state가 check 되지 못한 error상황에서
 에러를 복구하여 state전환을 허용 하기 위한 case 임.

*******************************************************************************/
void RotateStateTransition( char NewSensor )
{
LastRotateState = RotateState;

switch ( NewSensor )
{
case LEFT80 :
switch ( RotateState )
{
case L398: case L386://Jumping
RotateState = L440;
break;
case L280: case L322://Jumping
case ZERO:
case L38: case L26://Jumping
RotateState = L80;
break;
case R80: case R38://Jumping
case R322: case R334://Jumping
RotateState = R280;
break;
// Miss zero base case - 왼쪽 한바퀴 돌린 상황에서 시동걸었을 경우
// 이후 우회전하여 0로 맞추었으나, 1.5 회전 이상 돌아가는 있을 수 없는 경우 보정 처리 함.
case R440: case R398://Jumping
RotateState = R280;
break;
}
break;

case LEFT38 :
switch ( RotateState )
{
case L440:
case L386: case L334://Jumping
RotateState = L398;
break;
case L80: case L280://Jumping correction
case L26: case R26://Jumping correction
case ZERO:
RotateState = L38;
break;
case R280: case R80://Jumping
case R334: case R386://Jumping
RotateState = R322;
break;
}
break;

case LEFT26 :
switch ( RotateState )
{
case L398: case L440://Jumping
case L334: case L322://Jumping
RotateState = L386;
break;
case L38: case L80://Jumping
case ZERO:
case R26: case R38://Jumping
RotateState = L26;
break;
case R322: case R280://Jumping
case R386: case R398://Jumping
RotateState = R334;
break;
}
break;

case RIGHT26 :
switch ( RotateState )
{
case L386: case L398://Jumping
case L322: case L280://Jumping
RotateState = L334;
break;
case L26: case L38://Jumping
case ZERO:
case R38: case R80://Jumping
RotateState = R26;
break;
case R334: case R322://Jumping
case R398: case R440://Jumping
RotateState = R386;
break;
}
break;

case RIGHT38 :
switch ( RotateState )
{
case L334: case L386://Jumping
case L280: case L80://Jumping
RotateState = L322;
break;
case R26: case L26://Jumping
case R80: case R280://Jumping
case ZERO:
RotateState = R38;
break;
case R386: case R334://Jumping
case R440:
RotateState = R398;
break;
}
break;

case RIGHT80 :
switch ( RotateState )
{
case L322: case L334://Jumping
case L80: case L38://Jumping
RotateState = L280;
break;
case R38: case R26://Jumping
case R280: case R322://Jumping
case ZERO:
RotateState = R80;
break;
case R398: case R386://Jumping
RotateState = R440;
break;
// Miss zero base case - 오르쪽 한바퀴 돌린 상황에서 시동걸었을 경우
// 이후 좌회전하여 0로 맞추었으나, 1.5 회전 이상 돌아가는 있을 수 없는 경우 보정 처리 함.
case L440: case L398://Jumping
RotateState = L280;
break;
}
break;
}
}


/*******************************************************************************
 Sensor polling checking routine
*******************************************************************************/
#ifdef USE_POLLING_SENSOR_CHECKING // Enable only for non-interrupt schematic
void PollingCheckAndSetRotateState()
{
static char OldActiveSensor = 0; // For sensor value chaning
char  ActiveSensor = GetActiveSensor();

if ( OldActiveSensor != ActiveSensor )
{
PORTD ^= 0x20; // Toggle on PD6
OldActiveSensor = ActiveSensor;
RotateStateTransition( ActiveSensor );
}
}
#endif



/*******************************************************************************
 Reverse gear position check
 return TRUE for Reverse state.
*******************************************************************************/
BOOL IsReversGear()
{
return ( 0 == (PIND & 0x10) );
}


/*******************************************************************************
 Set Lamp state for Reverse gear state. (Left to right Right to Left)
*******************************************************************************/
void SetCorneringLampForReverseGear( char LampState )
{
switch ( LampState )
{
case ON_LEFT:
SetCorneringLamp( ON_RIGHT ); // 임시로 LAMP 방향을 바꿈
break;
case ON_RIGHT:
SetCorneringLamp( ON_LEFT ); // 임시로 LAMP 방향을 바꿈
break;
}
}


/*******************************************************************************
 INT0 (PORT D2) - 스티어링휠 회전 감지 스위치 변동 핸들러
*******************************************************************************/
ISR( INT0_vect , ISR_BLOCK )
{
#ifndef USE_POLLING_SENSOR_CHECKING // Enable code for interrupt enabled schematic

PORTD ^= 0x20; // Toggle on PD6

char  ActiveSensor = GetActiveSensor();
RotateStateTransition( ActiveSensor );

#endif
}


/*******************************************************************************
 INT1 (PORT D3) - 차량 속도 센서 핸들러
 초당 14 pulse == 40 km  35 pulse = 100km
*******************************************************************************/
ISR( INT1_vect , ISR_BLOCK )
{
PORTD ^= 0x80; // Toggle on PD7
++speedPulseCount; // Increase speed pulse count
}


/*******************************************************************************
 인터럽 관련 초기화
 INT0 : 하강 edge에만
 INT1 : 하강 edge에만
*******************************************************************************/
void InitInterrupt()
{
cli();
//----- INT0 ----------
GICR |= 1<<INT0 | 1<<INT1; // INT0, INT1 enable
MCUCR = 1<<ISC11 | // INT1 하강엣지에 인트럽트 발생
1<<ISC01; // INT0 하강엣지에 인트럽트 발생
sei();
}


/*******************************************************************************
 시간 측정용 Timer 초기화
 Atmaega8 0번 Timer는 비교기 사용이 불가 -> 2번(8bit) 사용으로 변경
*******************************************************************************/
void InitTimer(void)
{
  cli();
    TCCR2 = 0x0F; // ----1111 CTC모드(TOP==OCR2)로 조정하고 클럭을 1024분주하기
// → 소스클럭을 15625hz로 만들기 ( 16,000,000 hz/1024 = 15,625 )
// 15625hz/x=125hz,   x=125, 0~124번 카운트 필요
                   
    OCR2 =124; // 카운트할 숫자 설정(125hz를 만들기 위해)
    TCNT2 = 0x00; // 카운터를 초기화 한다음
    TIMSK = 0x80; // CTC모드로 카운터인터럽트 허용
    sei();
}


/*******************************************************************************
 인터럽 관련 초기화
 INT0 : 하강 edge에만
 INT1 : 하강 edge에만
*******************************************************************************/
void InitPort()
{
DDRC = 0x00; // PORT C는 입력 (PC0~PC5 까지 6개)
PORTC = 0xFF; // Pull up

DDRD = 0xE3; // PORT D: 11100011 , INT0, INT1, PD4 만 입력으로 두고, 나머지는 출력 설정
PORTD = 0x10; // Turn off all out, Internal pullup for D4 - Reverse gear input (0 for R)
}


/*******************************************************************************
 Main
 RotateState는 Interrut 방식으로 갱신 될 수 있기에 RotateState를 global 변수로 갱신
*******************************************************************************/
int main( void )
{
unsigned long int TimeCount = 0;
unsigned long int BaseTimeOfTurnOn = GetCurrentTime();
char LampState = OFF;
char LastLampState = OFF;
BOOL bForceTurnOffed = FALSE;
BOOL OnReverseGear;
BOOL LastOnReverseGear = FALSE;


InitPort();
InitInterrupt();
InitTimer();

SetCorneringLamp( OFF ); // 최초 꺼진 상태


while(1)
{
  #ifdef USE_POLLING_SENSOR_CHECKING // Polling logic for non-interrupt
PollingCheckAndSetRotateState();
  #endif

//========== 차량 속도를 확인 하여 좌/우 회전 각도에 따라 램프 좌우 조작.===================
switch (LampControlSpeed)
{
case SPEED_LOW: // 저속에서는 좌/우 80도 회전 부터 turn on
if ( RotateState <= L80 )
LampState = ON_LEFT;
else
if ( R80 <= RotateState)
LampState = ON_RIGHT;
break;
case SPEED_HIGH: // 고속에서는 좌/우 38도 회전 부터 turn on
if ( RotateState <= L38 )
LampState = ON_LEFT;
else
if ( R38 <= RotateState)
LampState = ON_RIGHT;
break;
case SPEED_OVER: // 100km 이상 부터는 Off
LampState = OFF;
break;
}
if ( L26 <= RotateState && RotateState <= R26 ) // 좌/우 26도로 되돌아 오면 turn off
LampState = OFF;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


//********** 후진기어인 경우 Left/Right 바꾼다 - 단 LampState 변수는 유지하고 출력만 임시 설정
// 후진 기어 변동시에만 수행되며 좌우와 달리 현재 state를 유지하고 임시로 출력만 변경
OnReverseGear = IsReversGear();
if ( LastOnReverseGear != OnReverseGear )
{
LastOnReverseGear = OnReverseGear;
if ( OnReverseGear )
SetCorneringLampForReverseGear( LampState );// R 상태에서 Lamp 출력
else
SetCorneringLamp( LampState ); // R 기어가 아니면 원상태로 원복
}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


//********** Lamp state 변동시에만 램프 점등도 변경 처리 ***********************************
if ( LastLampState != LampState ) // Lamp 점등 여부 변경시에
{
if ( OFF != LampState ) // OFF -> ON 으로 변동시에
{
BaseTimeOfTurnOn = GetCurrentTime(); // ON 시점 시간값을 저장
bForceTurnOffed = FALSE; // 강제 Off 상태가 아님 설정(Off되기 위한 초기화)
}

if ( OnReverseGear && (OFF != LampState) ) // Gear Reverse state then exchange left/right set.
SetCorneringLampForReverseGear( LampState );// R 상태에서 Lamp 출력
else
SetCorneringLamp( LampState ); // Normal case
LastLampState = LampState;
}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


//********** 장시간 Lamp 켜짐을 방지 처리
if ( (!bForceTurnOffed) && (LastLampState != OFF) )
if ( IsTimeOver( BaseTimeOfTurnOn, 60*5 ) ) // 지정 시간(초)가 지나도록 ON 이라면 강제 off한다.
{
LastLampState = LampState = OFF;
SetCorneringLamp(OFF); // 강제 꺼짐, 이후 각도/속도 변동에 의한 꺼짐-켜짐 동작이 이루어 져야 다시 켜질 수 있음
bForceTurnOffed = TRUE; // 강제 꺼짐 상태로 표식, 이후 진입 방지.
}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


if ( ++TimeCount >= ULONG_MAX ) // Counting
TimeCount = 0; // Reset
}

return 0;
}




2. 설치 
2.1. 컨트롤러 설치 

찍어논 사진이 없어서... 우선 말로만 살짝 언급 드리고, 향후 사진 확보하여 추가 하겠습니다.
대신 이전에 DIY한 내용과 동일한 위치에 설치 해 두었네요.
차량 전원을 공급 받아야 하며, 앞쪽 램프까지 전선을 연결하기 쉬운~ 그러한 위치를 잡아야 합니다. 저의 경우는 센터페시아 하단 부분에 위치 합니다. 공간도 있고, 전원 공급도 쉽고, 분리 합체도 쉬운 위치랍니다(QM5의 경우)

참고: 
[DIY] 수동기어 인디케이터 만들기 - 2케이블로 동작하는 QM5용 [4/4]  
*위 DIY 내용도 업그레이드 하여 좀더 작아지고 싼 버전의 ATMega8535로 구성 했답니다.



2.2. 센서모듈 설치 

센서 모듈 설치가 가장 까다로운 내용입니다. 
앞서  만들어 둔 센서는 아래 사진을 참고하여 설치 합니다. 
QM5 오너시라면 금방 이해가 가실 듯 합니다.
타 차량이라면 해당 차량의 설치 여부를 잘 확인 하여 진행 해야 할 것입니다.
*사진도 모바일폰으로 찍은 사진만이 있어 향후(언제?)에 DSLR 사진으로 갱신 예정 입니다.
  (촬영 기기는 갤럭시S2 인데, 작은 사진으로는 쓸만 하군요)

센서 모듈 뒷면에 3M 양면테잎을 이용하여 고정 합니다. 회전부위와 마칠이 없도로 설치 하셔야 합니다.
*아래와 같이 위치를 잡는데도 수차례 시도해서 저렇게 설치 하는 것으로 결론 내렸답니다.

[스티어링휠 좌측: 80도 담당 홀센서 / 전선들이 여기서 빠져 나가게 됩니다. 마무리 잘 하셔야 겠죠]



[스티어링휠 아래쪽: 26도, 38도를 담당하는 홀센서들이 보입니다.]
 홀센서 위로 자석이 지나가야 합니다.


[스티어링휠 오른쪽: 80도 담당 홀센서 위치]


[스티어링휠 180도 상태: 작은 네오디움 자석을 흰색 테잎으로 임시 고정한 모습]
앞서 고정한 센서들은 자석이 있어야 동작을 합니다!
자석 고정은 해당 부위를 인두 등으로 홈을 만들어서 자석을 끼우고, 작은 테잎으로 마무리 하시는 것이 가장 안정적이고 동작을 잘합니다.
N/S 극 상관 없이 측정하는 Hall 센서가 아닌 경우 방향을 확인후 최종 고정 하셔야 합니다.

 


2.3. 컨트롤러와 주변 장치 연결 

컨트롤러와 연결해야 하는 케이블은 아래와 같습니다.
1. 전원 
+12/GND와 연결해야 하는데 시거짹 배선 혹은 동격인 ACC 전원과 연결 합니다.

2. 속도입력 
센터페시아 하단에 위치한 갈색 커넥터를 찾아다가 2번 핀과 연결 합니다
[아래 사진을 보시면 쉽게 찾을 수 있을 것입니다]




3. 후진센서
저의 경우 수동기어 위치 표시 DIY때 해당 신호를 이미 받고 있어, 비교적 쉽게 해결했습니다. 하지만 자동 변속기 차량의 경우는 리드스위치 혹은 홀센서를 추가 장착해서 연결 해야 합니다.
센서 장착 부위의 핵심 포인트는 - 변속기 커브를 완전 분리 하시고, 변속기 앞뒤로 조작시에 함께 움직이는 보호커버에 자석을 달고, 해당 자석과 연동하도록 센서를 부착 하면 됩니다 (사진이 없네요... 알아서들 잘~).

4. 코너링센서 
앞서 장착한 코너링센서와 10pin 커넥터와 연결 합니다.
저의 경우 중고로 10pin 커넥터를 구입해서 비교적 쉽게 연결 했습니다. 이전 사진 참조 하세요.

5. 코너링램프동작용 릴레이 
램프쪽에 코너링 램프의 부하를 감당할 수 있는 릴레이를 장착 하고, 이 릴레이를 제어하기 위한 -(GND) 신호만 컨트롤러와 연결하게 됩니다. 아래 램프 배선 부분을 참조하시기 바랍니다.



2.4. 램프배선 

램프배선도 사진으로 쫘악 올려 드리면 좋겠지만, 고난의 연속이라... 사진은 없고
배선도만 별도로 정리하여 올려 드립니다.
QM5 라면 본넷으로 선을 통과 시켜야 하는데, 후방 워셔액관이 통과하는 고무 부품에 구멍을 내어 사용 하는 것이 가장 현실적이고 쉽습니다.

아래처럼 연결하시면 되겠습니다.
저의 경우 배터리와 결선을 피하고자 LED로 만들어진 H7 규격 램프를 기존 코너링 램프 대신 사용하였습니다. (밝기는 조금 아쉽지만, 코너링시에 도움이 됩니다)
당연하지만 램프는 직접 On/Off를 할 수 없으므로 반드시 추가의 Relay를 거쳐서 동작 시키게 됩니다. 아래 회로로 모든 것을 표현 했으나 참고 하세요. [클릭하면 2배 크기 확대]


 

3. 최종 동작 확인 
완성, 장착 후 최종 동작 영상 입니다.




이상 코너링 램프 컨트롤러 DIY 제작 내용을 완료 했습니다.

제가 직장인이다 보니, 램프 구입, DIY구상, 가끔가다 있는 주말에 조금씩 하다보니 여름에 시작해서 가을이 다 되어 끝냈습니다. 개인적으로 약속한 추가 QM5 한대 장착은 겨울이 되어서야 완료를 했구요.
의도하지 않게 꾀나 긴 프로젝트가 되었습니다.

직업이 H/W와는 전혀 관계가 없어 대량 생산(?)은 불가능 합니다.
실제 작업의 어려운 부분은 장착 부분이라 도움을 드리기는 더욱 힘들기도 합니다.
공개된 자료를 잘 활용하셔서 관심있으신 분들에게 도움이 되었으면 합니다.
상업적인 의도가 아니라면 컨트롤러 기판 정도를 공동제작 하여 실비로 판매하시면 좋을 것 같습니다.



*참고 자료
-1차 버전에 사용한 중간단자와 연결모습 참고 사진을 올려 드립니다.


-QM5의 경우 필요한 배선 길이 계산


긴 기간동안 포기하지 않고 저를 열심히 Push(?) 해 주신 전상준 님께 감사 드립니다^^
이번 DIY 설치시에 환상의 DIY 콤비가 되었답니다 ㅋ~

.

+ Recent posts