지난글 까지 내용으로 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 콤비가 되었답니다 ㅋ~

.

지난 글에서 코너링램프 컨트롤러를 만들기 위한 기본적인 필요사항을 주욱~ 나열 하고, 실제 적용 방법을 알아 보았습니다.
이번 글은 앞서 아이디어와 주어진 조건을 실체화 할 수 있는 '센서모듈', '컨트롤러'를 직접 만드는 방법을 설명 하고자 합니다.

대부분의 내용이 도면, 사진 등으로 많은 것을 전달 가능 할 것으로 생각 되므로 아주 소소한 부분까지는 상세하게 설명 드리지는 못할 것 같습니다.
다만 QM5의 경우 아래 내용으로 쉽게 접근이 가능 할 것으로 생각 되며, 타 차량도 기본 개념은 동일 하므로 실전 DIY에서 제가 했던 여러 고민을 줄여 나갈 수 있을 듯 합니다.





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


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






1. 컨트롤러 제작
1.1. AVR - ATMega8 마이컴 사용

값싸고 주변 부품을 최소화 하고, 프로그래밍이 쉬운 마이컴으로서 ATMega8 을 사용 했습니다.
핀수가 상위 버전에 비해서 적어서 포트 구성이 조금 아쉬운데... 구하기 쉽고 싸기 때문에 컨트롤러 핵심 부품으로 선정 했습니다.


1.2. ATMega8 기본 Pin-out 과 입력/출력 구상

A.
출력
- 필수 2개 / 옵션 3개
PD0: Left cornering lamp ON
PD1: Right cornering lamp ON
아래는 optional (Debugging 용)
PD5: 동작 확인 LED
PD6: 회전각 센서 감지 LED
PD7: 속도 센서 감지 LED

B. 입력 - 필수 8개 / 옵션 1개
PC0~PC5: 회전각 6개
(-80,-38,-26,26,38,80)
PD3(INT1): 스피드 1개
PD4: 후진 1개

PD2(INT0): 옵션(최초 회전각 변동 인터럽트로 설계했으나 아래 설명에서는 사용 안합니다.)
옵션기능(폴링방식으로도 실제 사용성에 문제 없음)
위 입력 모두를 TTL IC 7408(4XAND gate) AND 연결해서 - INT 0 연결
[변동 감지용 - 높은 등급 AVR과 달리 Port change detect기능이 없음]


1.3. 회로 구성

만능기판을 기준으로 직접 배치하는 방식으로 그림으로 대신 설명을 드리겠습니다.
이 분야 전문가분들 께서는 CAD를 이용하여 회로도를 그려 올리시겠지만.... 저는 H/W 전공도 아니고, 최종 만능기판 기준으로 회로를 그렸습니다.

[아래 그림은 클릭하면 2배 크기로 확인 가능 합니다.]



회로 설명을 하면 아래와 같습니다.
ATMega8을 기준으로,
우측에 휠각도 센서 연결용 8pin 단자(저는 있던 10pin 단자 사용), 후진상태 연결용 3pin 단자가 위치 하게 됩니다.
오른쪽 바로 옆10pin header가 연결되어 AVR ISP 프로그래머와 연결 하도록 했습니다.
좌측에 속도센서와 연결을 위한 포터 커플러 PC817과 주변 부품(저항: 입력 10k, 풀업 4.7k) 위치
좌측 하단16MHz 오실레이터(XTAL)와 안정화용 세라믹 20pF 컨덴서, 동작 확인용 LED3개
-사용 LED종류에 맞추어 저항은 별도 사용 하세요.
- R100은 3.3V LED(화이트,블루), R330은 일반 LED(2.1V) 용으로 사용 했습니다.
상단에 트랜지스터 C1815 가 2개 위치하여 좌/우 코너링 램프 릴레이 연결용으로 사용 되었고 릴레이 역전류 방어용으로 1N4004 두개도 연결 되어 있습니다. 또한 출력 확인용 LED가 위치 합니다.
TR2개와 1N4004의 조합은 실물 사진에서와 같이 모터구동용 TR모듈을 사용가능 합니다.
우측 상단RJ45 터미널 단자는 일반 단자로 대치 가능합니다.
[저는 편의상 LAN 케이블을 이용하여 차량 전면부 까지 쉽게 연결을 하기 위해 사용 해서 RJ 45를 사용 했습니다.]
좌측 상단의 'Head Light level controller'는 이전에 설명한 수동 조절식 레벨러를 연결하기 위한 단자로, 코너링과는 직접 상관 없는 부분으로 제외 가능 합니다.


1.4. 실제 회로 구성

실물 사진을 다시금 보면 아래와 같습니다.
차량 12V -> 5V 용 레귤레이터 모듈이 좌측에 위치 했고, C1815대신 모터구동 TR모듈을 사용 했습니다. 이때 저항은 4.7k를 사용 해야 합니다. (10k 사용시 다알링턴 구조의 TR모듈 구동에 전류가 부족 하게 되기 때문)
RJ45 대신 4개 짜리 기판 부착형 터미널 모듈을 사용 했습니다.
5.5파이 DC입력째(12V 차량전원) 옆에 핀헤드로 속도 센서를 연결 합니다.
나머지는 앞서 회로 설명과 동일 합니다.



참고로, 기판 뒤 배선상태 참고 사진도 올려 드립니다. (위와 비교해서 좌/우 대칭)
[프린트 기판을 쉽게 만들 수 있는 환경이 없서 이렇게 어지럽게 배선이 날아 다닙니다^^]




2. 센서모듈 제작
2.1. 스티어링센서 배치 도면(각도별 홀센서 고정용)

아래 도면처럼 개별 차량에 잘 숨겨지면서도 고정이 용이하게 센서 배치를 계산하여 개별 각도에 센서를 고정하도록 해야 합니다.


QM5 이외의 차종이라면 해당 차량에 맞도록 확인 후 작업 하셔야 겠습니다.


2.2 도면에 맞추어 플락스틱판 준비

센서를 스티어링 부위에 직접 배치하는 것은 현실적이지 못하므로, 센서 6개를 한번에 모듈로 만들 필요가 있습니다. 이때 가장 좋은것이 플렉시블 기판(flexible printed circuits)이겠지만 헝그리한 일반인들은 접근불가의 영역이고...
얅은 플라스틱판에, 위 도면을 출력후 붙여서 잘라 냅니다. - 주변에서 쉽게 구할 수 있는 파일의 겉표지를 이용 했습니다.

아래 사진은 최초 시도한 QM5용 14cm 크기입니다만. 13.5cm가 더 적합하니 오해 마세요~
(작업 사진 남아 있는게 처음 시도한 14cm 라서...)
홀센서를 6개 각도에 딱딱 맞추어 임시 고정합니다.



1차 제작은 아래와 같이 했으나, 너무 두꺼워서 휠회전 부위와 간섭이 생겼습니다.
보드도 1차 완성분이라 앞서 설명한 보드와는 조금 달리 생겼습니다.(이건 제가 지금도 사용 중인 버전입니다. 추가로 붙어 있는 TTL IC 2개는 최초 디자인과 달리 사용 안하고 있답니다_._)



2.3. 센서모듈 완성

2차 제작은 아래와 같이 했습니다. 참고하시고 실전 제작은 알아서들.... 잘~
홀센서가 워낙 귀하신 몸이라(개당 700원에 육박) 버리지는 못하고 살려 사용 하다보니 조금 아쉽네요.


케이블 고정과 모양잡기는 투명본드(순간접착이 아닌 점성이 강한 E6000 사용)로 고정 했습니다.
실제 장착은 위아래 180도 회전해서 스티어링 부위에 부착 합니다.
*케이블 색상 빨강이 기판 핀 1번 핀(Left 80)과 연결 됩니다. 전원은 모든 홀센서와 연결합니다.

2.4. Hall Effect Sensor 선택 주의점!

제가 주로 구입하는 상점에서는 특정 극에만 반응하는 센서만 판매해서, 실제 장착시에 자석 극성을 확인 한다고 약간의 노력이 필요 했습니다.
이 글 보시는 분들께서는 Hall 센서를 꼭 N/S 극 모두에 반응하는 스위치 방식을 사용하시기 바랍니다.
추천 드리는 부품은 WSH131-XPAN3 (제조:WINSON) [2.4V~26V, NS polarity ]입니다.
자력 유무를 판단하는 버전이며, 자력 세기 측정 버전을 사용 하시면 안됩니다.
참고로 제가 사용한 부품은 A1104EU-T (제조:ALLEGRO) [3.8V~24V S polarity] 입니다.




여기까지가 컨트롤러, 센서 모듈 만들기 작업입니다.
.

코너링 램프 컨트롤러를 직접 만들어 보고자 합니다.

QM5를 비롯한 여러 차종들이 스티어링휠(핸들)의 방향을 판단 후, 전방에 위치한 보조 램프를 켜는 기능이 있습니다. 밤길- 특히 어두운 시골길이라면 의외로 좌우 방향 회전시에 시야 확보에 큰 도움을 주는 기능이죠.
이러한 기능이 순정으로 장착 되지 않은 차량에서 코너링 램프를 비교적 쉽게 자동 조작하게 하는 컨트롤러를 제작 하는 내용을 설명 드립니다.
코너링 램프 기능이 있는 램프 어셈블리를 교환 하셨거나, 죽어 있던 기능을 살리시거나, 집접 코너링 램프를 추가 장착 하신다면 - 수동이 아닌 자동 동작하는 컨트롤러가 있다면 도움이 되는 것이죠. 

어떤 DIY 내용을 보면 수동 스위치를 장착 하거나, 방향지시 동작과 연동하여 동작시키기도 하나 - 거추장 스럽고, 의도와 달리 동작하는 아쉬움이 있는 것은 분명 합니다. 


여기서 제작하는 방식은 전방램프를 리니어(Linear)하게 동작하는 어댑티브 방식이 아닌, 정 회전각에 반응하여 보조 램프를 추가로 On/Off 하는 방식을 제작 하는 것입니다.


[아래는 완성한 코너링 램프 컨트롤러 사진 입니다]


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



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






1. 코너링 램프의 동작 

QM5 기준으로 코너링 램프 동작은 아래와 같은 조건이 있습니다.
코너링 램프가 켜지고 끄지는 조건 
   이그니션 ON(시동 상태)
   헤드램프 스위치 ON(전방 전조등 켜진 상태)
   기어 레버 P이외의 위치
   차량 에러 복구 모드가 아니어야 함
   조향각과 차량 속도는 아래의 조건을 충족 
     [0..20) km: 조향각 80도 부터 ON 
     [20..40) km: 조향각 [80..38]도에 대응하여 ON (20km:80도~30km:59도~40km:38도)
     [40..95] km: 조향각 38ON 

   조향각이 26도 이하로 떨어지면 OFF 
   100km 이상 부터 OFF 

정리를 먼저 하면 아래 두 조건은 꼭 알아야 합니다. (나머지는 가정 혹은 다르게 알아낼 수 있죠)
  A. 스티어링 휠 회전 각도 
  B. 차량 속도 



2. 동작 조건의 단순화

앞서 동작은 스티어링휠(핸들)에 부착되어 있는 휠 각도 센서에 의해서 1도(혹은 더 정밀하게) 단위의 연속적인 방향각을 정확하게 읽은 처리하는 것을 가정 합니다.
그러나 이러한 센서를 설치 하는 것은 비용 상승의 문제가 있고(이런 부품도 보쉬가 갑이더군요@@), 설사 이미 설치된 차량(VDC/ESP장착 차량들)의 경우도 해당 센서값을 얻기위해서는 CAN 통식을 해야하는 부담(제작비 상승과 함께)이 있기에 회전값 측정을 단순화 할 필요가 있습니다.

즉 별도의 스티어링휠 움직임 각도는 아래의 값 만을 측정 하는 것으로 단순화 합니다.
좌측(-)으로 / 우측(+)으로 휠을 돌렸을 때 아래의 6개의 값을 받도록 합니다.
   -80 , -38 , -26 , +26 , +38 , +80  


눈치 채셨나요?
QM5의 코너링 램프 동작 조건 중에 차량 속도별 ON 동작에 필요한 휠각도가 최대 80도(20km/h 이하일때), 최소 38도(40km/h) 이상일 때, 그리고 OFF 조건인 26도를 알아 내기 위한 최소 구별입니다.

(앞서 언급한 ON/OFF 동작에 가장 필수 적인 각도값으로 부터 추출 했습니다)
 


 
3. 휠 회전 각도는 어떻게 판단하나? 

횔 회전각을 알아야 하는데, 모든차량에 휠각도 센서가 기본 장착된 것도 아니고, 있다해서 땡겨 쓰기도 쉽지 않고, 그렇다고 진짜 센서를 붙이기도 곤란하고... 
만들어 버리는 것이죠!
예전 제가 진행한 리드 스위치와 같이 자석을 이용하면 딱~ 좋을 것 같습니다.
하지만 리드센서는 덩치가 작지 않고, 유리로 된 부품이라 사용성에 문제가 많습니다.
결국 자석을 이용하는 소형 센서인 "홀센서"(Hall Effect Sensor)를 사용합니다. 
이번 DIY에 사용한 Hall Sensor는 자성 방향이 있는 부품을 구입해 버렸는데(부품 수급시점에 구한게 이것이라서...) 이글 보시는 분들은 자성 방향(N/S극)에 무관하게 동작하는 부품을 사용하세요!

이 Hall Sensor를 스티어링휠 회전 부위에 -80/-30/-26/26/38/80 각도에 '잘' 배치하고, 회전하는 휠에는 소형 자석을 붙여서 무접촉으로 휠 회전각을 알아 내도록 하는 것입니다.
실제 제가 직접 만든 센서 부위 사진을 소개 드립니다.

[홀센서로 구성된 휠 회전각 센서 모듈]
실제 설치는 위아래를 뒤집어서 스티어링휠 횐전 부위에 부착 합니다.
트랜지스터 처럼 생긴 부품 위로 '자석'이 지나 가도록 휠부위에 '자석'을 잘 부착 해야 합니다.



위의 센서로 휠이 회전에 맞추어 6개의 위치 변동값을 알수 있게 됩니다.
그러나 불행이도 위 입력값으로 절대적인 회전각을 알 수가 없습니다. 일반적인 자동차의 스티어링휠은 좌우로 각각 1바퀴 반을 회전 합니다. 다시말해 끝에서 끝까지 감을 경우 3회전을 하게 된다는 것이죠.
결과적으로 센서 하나당 3가지 각도값이 존재 할 수 있습니다. @@
절대값을 돌려주는 휠각센서가 아니라서, 이러한 모호한 상태를 극복하기 위해 위 센서값은 프로그램 적으로 상태값 전이(Finite State Machine)를 이용하며 상태값에 모순이 발생하는 순간 가능한 바른 상태로 보정하는 루틴 등이 작성되게 됩니다. 이와 관련하여서는 [3. 컨트롤러 프로그래밍과 설치]에서 자세히 다루겠습니다.

 

4. 차량 진행 속도는 어떻게 판단하나? 

회전각과 달리 차량 속도는 대부분의 차량에서 CAN통신 없이도 사용가능한 쉬운 방법을 제공 하고 있습니다. 현대기아차의 경우 OBD 소켓으로도 펄스 형식의 신호가 예전부터 출력 되고 있고, 제가 목표로 하는 QM5에서도 쉽지는 않지만 내부에 2가지 경로로 속도 신호가 펄스 형식의 신호가 출력되고 있습니다.
타 차량은 인터넷에서 쉽게 공개된 자료를 찾아 볼 수 있으니 더 자세히 설명은 하지 않고, 당장 제 차량인 QM5의 경우에 대해서 차량 속도 신호 출력 부분에 대한 설명을 드리겠습니다.

공식적으로 QM5 속도 신호에 대해서는 공개된 내용이 없습니다.
결국 회로도를 뒤져 볼 수 밖에 없는데... 아래 2가지 배선에서 펄스형 속도신호가 출력 되고 있습니다.

Line 1: 47F = VEHICLE SPEED SIGNAL (차속 신호)
  실제 연결 시작: 콤비네이션 미터[247]:Pin 5 - 47F - RD(빨강) or PU(보라) 
  연결된 장치로는: 네비게이션 컨트롤 유닛[662], 리어차고센서[1372] 입니다.
  그런데 위 두 장치는 위치가 좀 어정쩡 합니다. 그중 만만한 것은 네비게이션 컨트롤 유닛인데...
  글로브박스를 분리 후에 소켓에 연결해야하는 부담이 있습니다.

Line 2: H47 = VEHICLE SPEED - SECONDARY SPEED SIGNAL (이차 차속 신호)
  실제 연결 시작: 콤비네이션 미터[247]:Pin 6 - H47 - RD(빨강)
  연결된 장치로는: 썬루프 모터[157]:Pin 8,  서비스 소켓(Socket (After-sales))[1776]:Pin 2 
  만만한 곳은 바로! 이 서비스 소켓 입니다. 센터페시아 하단 스위치쪽에 묶여 있다는 것이죠.

아래 사진(크루즈 DIY 때 촬영한 사진입니다)을 보시면 위치를 알 수 있을 것으로 생각 됩니다.
실제 연결시에는 센터페시아 분리 하지 말고, 우측 마감재를 분리해서 연결 하면 됩니다.



참고로 H47와 연결된 썬루프 모터는 차량 진행 중, 썬루프를 닫을 경우 바람에 의한 저항을 무시할 수 있도록, 속도값을 참고하고 있답니다. 정지 상태에서는 핀치(끼임) 상태를 확인하고 썬루프가 닫히지 않는게 기본이나, 속도값이 일정 이상 되면 그냥 닫이게 하는 거죠. (손을 끼우면 엄청난 고통을....)

중요 사항으로 이 라인에서 출력 되는 신호는 펄스 입니다. PWM 신호 정도로 이해하면 될것 같습니다.
그러나 정밀하게 Pulse 폭의 시간값을 측정하면 좋겠지만, 엄청나게 code량이 늘어나기도 하고 정확히 순간순간의 속도값이 필요한게 아니므로, 1초당 이 신호의 개수를 세어서 속도값을 정하고자 합니다.
실측 결과는 아래와 같습니다 (속도계를 만들경우  실제 Pulse width 간격을 정밀 측정 필요!)
  10km/h 당 = 약 3.5 Pulse/Sec
  20km/h = 약 7 Pulse/Sec
  100km/h = 약 35 Pulse/Sec

1초 마다 타이머 루틴에서 차속센서 인터럽트 개수를 확인하여
아래 속도에 맞추어 속도 state를 갱신하게 됩니다.
속도별 구간: [0, 20, 95, 100, 100이상]
안정적인 확인을 위해 구간별 Finite State Machine으로 처리 합니다. 



5. 램프 On/Off 동작 제어 

컨트롤러의 최종 동작은 앞서 설명한 휠 회전각과 차량 속도를 입력받아 좌/우에 있는 코너링 램프를 제어 하는 것입니다.
즉 왼쪽/오른쪽으로 차량을 조절 할 때 마다 알아서 판단해서 좌우 램프를 켜고 끄도록 해야 하는 것이죠.
동작은 궁극적으로 앞에 모두 설명을 했고, 현실적인 문제만 본다면 차량용 램프는 12V에 적어도 5~6A 를 감당해야 합니다.
그래서 직접적으로 제어를 할 수는 없고, 컨트롤러에서는 릴레이를 동작 시킬만한 출력을 제어 하는 것만으로도 OK 입니다. 나머지는 차량 배선을 릴레이의 접전과 연결하여 실제 램프를 구동 하는 것이죠.
하지만 릴레이라는 물건이 여전히 보통 12V 차량 전원에 연동되어야 하고 또한 적어도 100mA 를 흘려 주어야 합니다.
결과적으로 마이컴(여기서는 AVR 사용)에서 출력하는 신호를 직접 연결은 못하고 추가적인 트랜지스터(TR)를 사용해서 외부에 위치할 릴레이를 가동 해야 합니다.

저와 같은 실수가 반복되지 않도록 집고 넘어가야 할 점이라면,  5V TTL 신호로 12V 를 제어 해야 하므로 NPN 계열의 TR을 사용 해야 합니다. 즉 +5V를 NPN TR로 흘려주고, TR에서는 GND와 연결된 E를 C와 연결하도록 회로를 구성 해야 합니다. (저는 최초에 PNP을 사용해서 +12를 연결 하려 했으나 잘못된 구성입니다 - 전압 Level 이 다르므로 TR구동의 용이성을 위해 NPN을 사용)
더 복잡한 이야기는 TR회로를 공부 하시면 자연스럽게 아는 내용이라, 여기서는 언급 하지 않겠습니다.(저도 전자공학 전공이 아니라 한참을 공부하고 원리를 이해 했네요)



6. 후진에서는 반대방향 동작 

전진에서는 당연히 왼쪽 반향으로 스티어링휠을 돌리면 왼쪽 램프, 오른쪽으로 돌리면 오른쪽 램프를 켜야 하는 것은 당연한데!, 후진시에는 이게 반대로 되어야 합니다.
즉 후진하면서 우측으로 붙이기 위해서는 스티어링휠은 왼쪽으로 돌리게 되고, 확인이 필요한 방향은 우측 입니다. 즉 반대 동작이죠.
이것을 만족하기 위해서 후진기어 위치를 확인하기 위한 센서 입력을 하나 더 받고, 홀센서 혹은 리드스위치로 입력을 받으면 쉽게 해결 되겠습니다.
더 상세한 내용은 컨트롤러 제작 부분을 참고 하세요.



7. 컨트롤러 제작 

컨트롤러는 1 Chip MICOM인 AVR시리즈의 ATMega8을 사용 합니다. 만능기판을 이용할 것이므로 대중적인 DIP형을 사용하여 기본 구성을 합니다.
주변 부품으로는 전압 레귤레이팅을 위해 1,000 원 짜리 DC-DC 컨버터, 속도입력 신호 입력을 위해 포토커플러 정도를 사용합니다.
또한 릴레이 제어용으로 소출력 TR과 정류 다이오드 혹은 모터 제어용 중출력 TR모듈 IC를 사용 할 수도 있겠습니다 (1차 제작은 C1815이용, 2차 제작은 저렴하게 구한 모터제어용 TR모듈 사용) - 어차피 회로는 같습니다.
또한 동작 확인용 LED와 주변 부품이 추가 됩니다.
더 상세한 것은 그림, 도면, 사진과 함께 다음글에 이어 집니다.


.

안녕하세요~
벌써 몇주 전에 있었던 일입니다.
이런 저런 사정으로 일요일 밤 늦게 경부 고속도로를 타고서 집으로 오고 있었습니다.

대충의 위치라면~ 청원 IC~ 천안 휴게소 사이의 나름의 급 코너링이 많은 구간이었습니다.
청원-상주간 고속도로에 비하면, 이곳은 최소 3차로~4차로 여유가 있는 구간입니다.
그런데, 1차로에 떡하나 엉덩이 눌러 앉아서 1차로로 유유히 갈길을 가시는 운전자 분들이 꼭 계시죠! 아! 물론 2차로 비었습니다. 3차로 까지 빈 경우도 있죠.

그런데, 역시나 이번에도 NF한대가 떡하니 1차로에서 유유히 - 사실 X20km 정도이니 그리 느린 속도는 아닙니다.
저도 귀찮고 해서, 1차로에서 걍 2차로로 깜빡이 넣고 2차로로 추월하려고 속도를 내려는데! [원칙을 지키는 독일에서는 이렇게 추월하면 사고 납니다, 벌금도 있구요]
아 글쎄... 왜 이제서야 속도를 내고(가속) 있냐는 거죠....
뒤 따라 오던 차가 보이면, 미리 2차로로 가던지, 아니며 걍 가던 속도 유지 하면, 알아서 피해 갈 것이데.
심뽀가 어떻게 붙었길래, 같이 속도를 내고 있네요.

그래서... 아까운 기름 쏟아 부으며, 속도를 높이기도 그렇고, 그렇다고 걍 두자니.... 미워 보이고...
해서, 골려먹을 아이디어가 번쩍! 들더군요.

걍 뒷 꽁무니 끝까지 쫓아 가보자!
- 다만 똥침이 아니라, 충분한 거리를 두고, 하지만 심뽀 고약한 앞차에 저의 존재는 각인 시킬 위치에서 쭈욱 따라가기로 했습니다.
당연히 저는 이 구간이 좌로~, 또 우로~ 확 확 쏠리는 구간임을 잘 알고 있습니다.
솔직히 적당히 속도를 내야 안전한 것이라는 것을 저도 잘 알고 있음에도 불구하고... 앞차의 똥심뽀에 약간의 처방을 하고자 크크...

역시나, 그 똥심뽀는 제가 붙어서 가니 계속 속도를 높입니다.
x50, x60 나름 급 코너에서도 속도를 줄이지 않네요 ㅎㅎ - 딱 걸렸습니다 ㅎ!
그리고 x70, x80 까지 속도가 붙는 상황이고 이번에 좌로 화~악! 꺽기는 코스입니다.

아시겠지만 경부고속도로는 태생이 x00 km/h 입니다. 그래서 한계에 가까운 코너인 경우, x80 km/h 는 꾀나 부담이 가는 속도이지요. 그것도 물커덩 NF 서스펜션으로는... 그 한계가 명확 합니다.
 - 아아... 저의 QM도 태생이 SUV라 전혀 부담이 없는 것은 아닙니다. 다만 순정임에도 불구하고 탱탱한 서스펜션을 좀더 믿을 수는 있겠죠.

자... x80 km/h 넘어 가고 왼쪽으로 확~ 꺽기는 상황에서 2차로에는 저속 차량이 지나가고, 이 NF... 브레이킹을 한번 합니다. 그렇죠... 이 속도로 걍 꺽기에는 심리적 + 물리적(휘어청 느낌이 불안함으로!)으로 참을래야 참기 힘든 상황이 닥쳤습니다.
그리고, 2차로에 차량이 비고서, 곧 브레이킹 한번 더 하더니..

친절하게도, 바라지도 않은 우측 깜빡이 신공과, 지나친 액션 - 즉 무려 한번에 두 차로를 오른쪽으로 옮기시고 꽁무니를 빼어 가 버렸습니다 - ㅎㅎ ㅎ [ 바이~ 잘가....... 미안해....... ㅎㅎ]

냅! 저의 의도에 100% 맞추어서 '깨갱' 하셨네요...

왜 1차로를 유유히 가다가 같이 속도를 내는지... 쩝, 다음번에는 안 그러시길 바라면서...
저는 가야 할 길을 계속 갔습니다. 아! 물론 속도는 좀더 안전한 X20~30 정도로 줄였답니다.


아! 그런데 또!!!!
또 하나의 소나타 이번엔 EF 께서.. 1차로를 열심히 달리고 계시는 군요 ㅠㅠ
네 또다시 같은 패턴입니다. 제가 1차로 가까이 붙으니, 다시금 열심히 달리기 시작 하시더라는....

과정과 결과는 앞서 NF와 90% 같게 진행 되었습니다. 우측 깜빡이 넣고 2차로 연속 옮기면서 포기 하시는 것은 100% 동일 하더라는 ㅠㅠ 하루에 그것도 10분도 안되는 시간에 두대가... ㅎㅎ


뭐... 저도 1차로 달리다가 저보다 더 빠른 차량을 만나기는 합니다.
다만. 미리 알고 있다면, 2차로로 비켜 주고 있습니다.  간혹 엄청난 속도로 오시는 분들 계시죠...
그럼 저도 판단을 유보합니다. 이 경우 잘못 비켜 주다가 큰일 나기에 - 알아서 2차로로 잘 가시더군요 부앙~ 하면서....
하지만 제발... 뒷 차가 더 빨리 붙는다고 같이 가속을 하지는 마시길... 미리 가속 하던지, 아니면 비켜 주시던지. 아니면 걍 원래 속도로 가셔서 뒷차가 알아서 판단하게 해야 겠습니다!
그렇다고 옆차량과 나란히 같은 속도로 친구 하지는 마시길 바랍니다!!!

원래 추월은 좌측 차로 입니다. 더 빠른 차량이 나타나면 우측 차선으로 비켜 주어야 안전한 주행이 됩니다.
이 원칙이 지켜지는 나라가 되길 바라면서 글을 끝낼까 합니다.


참고: 위 속도는 GPS 기준 입니다. X == 1 로 해석 가능 합니다

+ Recent posts