최초 수동기어 인디케이터 만들기 글을 올린지가 2009년이었네요.

지금이 2013년이니 4년이 다 되어 갑니다.


지금 올리는 글은 최초 버전에서 조금 더 개선 한 버전으로, 최초 버전 이후 곧 새로 만들기는 했으나 블로그에 포스팅은 하지 않은 회로와 code로 되어 있어 이제서야 글로 올립니다.


상세 내용은 이미 공유 드린 예전 자료를 한번 쭈욱 읽어 주시기 바랍니다.



최초 내용은 AVR을 처음 학습 하던 관계로 무려 MEGA128 모듈을 사용 했습니다만, 이번 내용은 AVR의 좀더 저렴하고 DIY 하기 쉬운 8535L 을 이용하는 내용입니다.

다만 8535L 은 현 시점에 구입이 어려운 관계로, 보드 구성은 MEGA128 모듈을 그대로 사용하시고 Port배치와 chip 간의 차이점을 확인 후 아래 공유 드리는 code를 아주 약간만 수정해서 재사용 가능 하리라 생각 합니다.

추가로 LED에 TR를 달아서 PWM으로 밝기 제어를 해서 최초의 code상 밝기 제어와 비교하여 우수한 표시 안정성을 제공 합니다. 또한 효과도 더욱 멋지게 바뀌었습니다.


회로 구성을 먼저 올리고, 이어서 code를 올려 드립니다.

8MHz 진동자를 사용하는 것을 조심 하셔야 겠습니다.

그래서 16MHz의 모듈을 사용 하신다면 code상에 timing 관련 값을 2배 정도 높여 사용 하셔야 합니다.


상세 내용은 회로도와 code를 보세요. code에 충분한 comment가 달려 있답니다.



[회로도] AVR 8535L 8MHz 진동자와 3.3V LM2576 을 모두 함게 구성 했습니다.

또한 LED 밝기 조절을 위해 A1015 TR하나를 이용한 PWM 제어를 하게 됩니다.




소스코드 파일 다운로드: 

iocompat.h


GearPositionIndicatorLib.h


GearPositionIndicatorLib.c


GearPositionIndicator8535L.c



소스코드 전체 내용

iocompat.h

  1. /*
  2.  * ----------------------------------------------------------------------------
  3.  * "THE BEER-WARE LICENSE" (Revision 42):
  4.  * <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
  5.  * can do whatever you want with this stuff. If we meet some day, and you think
  6.  * this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
  7.  * ----------------------------------------------------------------------------
  8.  *
  9.  * IO feature compatibility definitions for various AVRs.
  10.  *
  11.  * $Id: iocompat.h,v 1.6.2.1 2008/03/17 22:08:46 joerg_wunsch Exp $
  12.  */
  13.  
  14. #if !defined(IOCOMPAT_H)
  15. #define IOCOMPAT_H 1
  16.  
  17. /*
  18.  * Device-specific adjustments:
  19.  *
  20.  * Supply definitions for the location of the OCR1[A] port/pin, the
  21.  * name of the OCR register controlling the PWM, and adjust interrupt
  22.  * vector names that differ from the one used in demo.c
  23.  * [TIMER1_OVF_vect].
  24.  */
  25. #if defined(__AVR_AT90S2313__)
  26. #  define OC1 PB3
  27. #  define OCR OCR1
  28. #  define DDROC DDRB
  29. #  define TIMER1_OVF_vect TIMER1_OVF1_vect
  30. #elif defined(__AVR_AT90S2333__) || defined(__AVR_AT90S4433__)
  31. #  define OC1 PB1
  32. #  define DDROC DDRB
  33. #  define OCR OCR1
  34. #elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || \
  35.       defined(__AVR_AT90S4434__) || defined(__AVR_AT90S8535__) || \
  36.       defined(__AVR_ATmega163__) || defined(__AVR_ATmega8515__) || \
  37.       defined(__AVR_ATmega8535__) || \
  38.       defined(__AVR_ATmega164P__) || defined(__AVR_ATmega324P__) || \
  39.       defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || \
  40.       defined(__AVR_ATmega1284P__)
  41. #  define OC1 PD5
  42. #  define DDROC DDRD
  43. #  define OCR OCR1A
  44. #  if !defined(TIMSK)           /* new ATmegas */
  45. #    define TIMSK TIMSK1
  46. #  endif
  47. #elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega48__) || \
  48.       defined(__AVR_ATmega88__) || defined(__AVR_ATmega168__)
  49. #  define OC1 PB1
  50. #  define DDROC DDRB
  51. #  define OCR OCR1A
  52. #  if !defined(TIMSK)           /* ATmega48/88/168 */
  53. #    define TIMSK TIMSK1
  54. #  endif /* !defined(TIMSK) */
  55. #elif defined(__AVR_ATtiny2313__)
  56. #  define OC1 PB3
  57. #  define OCR OCR1A
  58. #  define DDROC DDRB
  59. #elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || \
  60.       defined(__AVR_ATtiny84__)
  61. #  define OC1 PA6
  62. #  define DDROC DDRA
  63. #  if !defined(OCR1A)
  64. #    /* work around misspelled name in avr-libc 1.4.[0..1] */
  65. #    define OCR OCRA1
  66. #  else
  67. #    define OCR OCR1A
  68. #  endif
  69. #  define TIMSK TIMSK1
  70. #  define TIMER1_OVF_vect TIM1_OVF_vect /* XML and datasheet mismatch */
  71. #elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || \
  72.       defined(__AVR_ATtiny85__)
  73. /* Timer 1 is only an 8-bit timer on these devices. */
  74. #  define OC1 PB1
  75. #  define DDROC DDRB
  76. #  define OCR OCR1A
  77. #  define TCCR1A TCCR1
  78. #  define TCCR1B TCCR1
  79. #  define TIMER1_OVF_vect TIM1_OVF_vect
  80. #  define TIMER1_TOP 255        /* only 8-bit PWM possible */
  81. #  define TIMER1_PWM_INIT _BV(PWM1A) | _BV(COM1A1)
  82. #  define TIMER1_CLOCKSOURCE _BV(CS12) /* use 1/8 prescaler */
  83. #elif defined(__AVR_ATtiny26__)
  84. /* Rather close to ATtinyX5 but different enough for its own section. */
  85. #  define OC1 PB1
  86. #  define DDROC DDRB
  87. #  define OCR OCR1A
  88. #  define TIMER1_OVF_vect TIMER1_OVF1_vect
  89. #  define TIMER1_TOP 255        /* only 8-bit PWM possible */
  90. #  define TIMER1_PWM_INIT _BV(PWM1A) | _BV(COM1A1)
  91. #  define TIMER1_CLOCKSOURCE _BV(CS12) /* use 1/8 prescaler */
  92. /*
  93.  * Without setting OCR1C to TOP, the ATtiny26 does not trigger an
  94.  * overflow interrupt in PWM mode.
  95.  */
  96. #  define TIMER1_SETUP_HOOK() OCR1C = 255
  97. #elif defined(__AVR_ATtiny261__) || defined(__AVR_ATtiny461__) || \
  98.       defined(__AVR_ATtiny861__)
  99. #  define OC1 PB1
  100. #  define DDROC DDRB
  101. #  define OCR OCR1A
  102. #  define TIMER1_PWM_INIT _BV(WGM10) | _BV(PWM1A) | _BV(COM1A1)
  103. /*
  104.  * While timer 1 could be operated in 10-bit mode on these devices,
  105.  * the handling of the 10-bit IO registers is more complicated than
  106.  * that of the 16-bit registers of other AVR devices (no combined
  107.  * 16-bit IO operations possible), so we restrict this demo to 8-bit
  108.  * mode which is pretty standard.
  109.  */
  110. #  define TIMER1_TOP 255
  111. #  define TIMER1_CLOCKSOURCE _BV(CS12) /* use 1/8 prescaler */
  112. #elif defined(__AVR_ATmega32__) || defined(__AVR_ATmega16__)
  113. #  define OC1 PD5
  114. #  define DDROC DDRD
  115. #  define OCR OCR1A
  116. #elif defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__) || \
  117.       defined(__AVR_ATmega165__) || defined(__AVR_ATmega169__) || \
  118.       defined(__AVR_ATmega325__) || defined(__AVR_ATmega3250__) || \
  119.       defined(__AVR_ATmega645__) || defined(__AVR_ATmega6450__) || \
  120.       defined(__AVR_ATmega329__) || defined(__AVR_ATmega3290__) || \
  121.       defined(__AVR_ATmega649__) || defined(__AVR_ATmega6490__) || \
  122.       defined(__AVR_ATmega640__) || \
  123.       defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \
  124.       defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__)
  125. #  define OC1 PB5
  126. #  define DDROC DDRB
  127. #  define OCR OCR1A
  128. #  if !defined(PB5)             /* work around missing bit definition */
  129. #    define PB5 5
  130. #  endif
  131. #  if !defined(TIMSK)           /* new ATmegas */
  132. #    define TIMSK TIMSK1
  133. #  endif
  134. #else
  135. #  error "Don't know what kind of MCU you are compiling for"
  136. #endif
  137.  
  138. /*
  139.  * Map register names for older AVRs here.
  140.  */
  141. #if !defined(COM1A1)
  142. #  define COM1A1 COM11
  143. #endif
  144.  
  145. #if !defined(WGM10)
  146. #  define WGM10 PWM10
  147. #  define WGM11 PWM11
  148. #endif
  149.  
  150. /*
  151.  * Provide defaults for device-specific macros unless overridden
  152.  * above.
  153.  */
  154. #if !defined(TIMER1_TOP)
  155. #  define TIMER1_TOP 1023       /* 10-bit PWM */
  156. #endif
  157.  
  158. #if !defined(TIMER1_PWM_INIT)
  159. #  define TIMER1_PWM_INIT _BV(WGM10) | _BV(WGM11) | _BV(COM1A1)
  160. #endif
  161.  
  162. #if !defined(TIMER1_CLOCKSOURCE)
  163. #  define TIMER1_CLOCKSOURCE _BV(CS10) /* full clock */
  164. #endif
  165.  



GearPositionIndicatorLib.h

  1. #ifndef __GETPOSITIONINDICATOR_H__
  2. #define __GETPOSITIONINDICATOR_H__
  3.  
  4.  
  5. #define PORT_SEG                        PORTA   // Segment 출력용 port
  6. #define SEG_COMMON_ANODE        1               // Anode 공통 == + 극 공통으로 출력이 0으로 나가야 동작 하는 경우
  7.  
  8. #ifdef  SEG_COMMON_ANODE
  9.         #define SEGOUT(value)   (~value)        // Coommon Anode이므로 bit 상태를 뒤집는다
  10. #else
  11.         #define SEGOUT(value)   (value)
  12. #endif
  13.  
  14.  
  15. unsigned char GetGearPosition( unsigned char state );
  16. char getSegmentData( char currentMode );
  17. void displaySegmentDataNoEffect( char segmentData );
  18.  
  19.  
  20. #endif  // of __GETPOSITIONINDICATOR_H__


GearPositionIndicatorLib.c

  1. /*******************************************************************************
  2.  (C) 2009 Seung-Won Lee   http://whoisit.tistory.com    SoftWareYi@gmail.com
  3.  
  4.  
  5.   수동기어 단수표시 장치
  6.  
  7.   @File  GearPositionIndicator.c
  8. *******************************************************************************/
  9.  
  10. #include <avr/io.h>
  11. #include "GearPositionIndicatorLib.h"
  12.  
  13.  
  14.  
  15. const unsigned char     SEGMENT_MINUS   = 0b10000000;   // -
  16. const unsigned char     SEGMENT_0               = 0b01110111;   // 0
  17. const unsigned char     SEGMENT_1               = 0b00010100;   // 1
  18. const unsigned char     SEGMENT_2               = 0b10110011;   // 2
  19. const unsigned char     SEGMENT_3               = 0b10110110;   // 3
  20. const unsigned char     SEGMENT_4               = 0b11010100;   // 4
  21. const unsigned char     SEGMENT_5               = 0b11100110;   // 5
  22. const unsigned char     SEGMENT_6               = 0b11100111;   // 6
  23. const unsigned char     SEGMENT_7               = 0b01110100;   // 7
  24. const unsigned char     SEGMENT_8               = 0b11110111;   // 8
  25. const unsigned char     SEGMENT_9               = 0b11110110;   // 9
  26. const unsigned char     SEGMENT_BACK    = 0b11111001;   // P. for Back
  27. const unsigned char     SEGMENT_ERROR   = 0b11101011;   // E. for Error
  28. const unsigned char     SEGMENT_H3              = 0b10100010;   // 한자 3
  29. const unsigned char     SEGMENT_ALL             = 0b11111111;   // All for test
  30.  
  31.  
  32.  
  33. //******************************************************************************
  34. // Port로 부터 입력된 기어 위치값으로 부터 현재 기어 위치를 char 문자로 돌려준다
  35. //      중립='N'
  36. //      각 단수='1'~'6'
  37. //      후진:'B'
  38. //      기어조작중:'-'
  39. //      에러상태:'E'
  40. //******************************************************************************
  41. typedef struct
  42. {
  43.         unsigned char   state;
  44.         unsigned char   position;
  45. } stateToPosition;
  46.  
  47. unsigned char GetGearPosition( unsigned char state )
  48. {
  49.         const stateToPosition   stateData[] = {
  50.                 { 0b00100010, 'N' },    // Natural
  51.                 { 0b00010100, '1' },    // 1
  52.                 { 0b01000100, '2' },    // 2
  53.                 { 0b00010010, '3' },    // 3
  54.                 { 0b01000010, '4' },    // 4
  55.                 { 0b00010001, '5' },    // 5
  56.                 { 0b01000001, '6' },    // 6
  57.                 { 0b00011000, 'B' },    // Back
  58.         };
  59.         char    result = '-';
  60.         char    sensingCount = 0;
  61.         unsigned char   i;
  62.  
  63.         state = state & 0b01111111;     // 상위 1bit 사용 안함 처리
  64.        
  65.         // CheckError - 최소한 하나의 bit는 1이어야 정상이다. (실은 2개)
  66.         if ( !(0b01111111 & state) )
  67.         {
  68.                 return 'E';     // 센서로부터 아무런 입력값이 없을 경우 Error처리
  69.         }
  70.  
  71.         // 변속 중인 상태 확인 -> 앞뒤
  72.         sensingCount += ( state & 0b01000000 ) ? 1 : 0;
  73.         sensingCount += ( state & 0b00100000 ) ? 1 : 0;
  74.         sensingCount += ( state & 0b00010000 ) ? 1 : 0;
  75.         if ( sensingCount > 1 ) // 두 위치값이 동시에 입력되면 변동 중인 경우
  76.                 return result;
  77.  
  78.         // 변속 중인 상태 확인 -> 좌우
  79.         sensingCount = 0;
  80.         sensingCount += ( state & 0b00001000 ) ? 1 : 0;
  81.         sensingCount += ( state & 0b00000100 ) ? 1 : 0;
  82.         sensingCount += ( state & 0b00000010 ) ? 1 : 0;
  83.         sensingCount += ( state & 0b00000001 ) ? 1 : 0;
  84.         if ( sensingCount > 1 ) // 두 위치값이 동시에 입력되면 변동 중인 경우
  85.                 return result;
  86.  
  87.         // 모두 통과 되었다면 단수 확인 과정, 확인 불가시 '-' 처리가 default
  88.         for ( i=0; i<sizeof(stateData)/sizeof(stateData[0]); i++ )
  89.         {
  90.                 if ( stateData[i].state == state )
  91.                 {
  92.                         result = stateData[i].position;
  93.                         break;
  94.                 }
  95.         }
  96.  
  97.         return result;
  98. }
  99.  
  100. //******************************************************************************
  101. // getTransmissionValue로 부터 받아진 결과값을 입력하면 포트출력 값을 돌려준다
  102. //******************************************************************************
  103. typedef struct
  104. {
  105.         char    mode;
  106.         char    segmentData;
  107. } modeToSegment;
  108.  
  109. char getSegmentData( char currentMode )
  110. {
  111.         const modeToSegment     modeToSegmentData[] = {
  112.                 { '-', SEGMENT_MINUS }, // -
  113.                 { 'N', SEGMENT_0 },     // 0
  114.                 { '1', SEGMENT_1 },     // 1
  115.                 { '2', SEGMENT_2 },     // 2
  116.                 { '3', SEGMENT_3 },     // 3
  117.                 { '4', SEGMENT_4 },     // 4
  118.                 { '5', SEGMENT_5 },     // 5
  119.                 { '6', SEGMENT_6 },     // 6
  120.                 { 'B', SEGMENT_BACK },  // R == P.
  121.                 { 'E', SEGMENT_ERROR }, // E.
  122.         };
  123.         unsigned char   i;
  124.         char    segmentData = SEGMENT_ERROR;    // Default to Error
  125.  
  126.  
  127.         // 입력값에 대응하는 Segment값을 가져온다
  128.         for ( i=0; i<sizeof(modeToSegmentData)/sizeof(modeToSegmentData[0]); i++ )
  129.         {
  130.                 if ( modeToSegmentData[i].mode == currentMode )
  131.                 {
  132.                         segmentData = modeToSegmentData[i].segmentData;
  133.                         break;
  134.                 }
  135.         }
  136.  
  137.         return segmentData;
  138. }
  139.  
  140.  
  141. void displaySegmentDataNoEffect( char segmentData )
  142. {
  143.         // 일반적인 변동 상황
  144.         PORT_SEG = SEGOUT(segmentData); // Invert for common Anode LED
  145.        
  146. }


GearPositionIndicator8535L.c

  1. /*
  2.   ATMega 8535L 을 이용한 수동기어 인디케이터
  3.  
  4.   기존 ATMEGA128에서 구현한 기능 + PWM 출력을 조절하여 표시 변동시에 부드러운 밝기 변동 처리가 목적
  5.  
  6.   Port A: 7 Segment 출력
  7.   Port C: 포지션 센서 입력
  8.   Port D: PWM 출력을 이용하여 TR을 거쳐서 7 Segment 밝기 조절 처리용으로 사용
  9.  
  10.  
  11.   이전과 같이 Loop의 단순 timing으로 효과 극대화가 곤란
  12.   새로운 구현에서는 밝기 조절은 Timer1을 이용하여 diming 처리
  13.   이어 포지션 변경으 아래와 같은 시나리오롤 처리하자.
  14.  
  15.  
  16.  
  17. */
  18.  
  19.  
  20. #include <inttypes.h>
  21. #include <avr/io.h>
  22. #include <avr/interrupt.h>
  23. #include <limits.h>
  24. #include "iocompat.h"
  25. #include "GearPositionIndicatorLib.h"
  26.  
  27. #define SIZEOF(x) (sizeof(x)/sizeof(x[0]))
  28. #define TIMER0_PWM_RATIO_MAX    4096
  29.  
  30. typedef enum
  31. {
  32.         DIMMING_OFF,
  33.         DIMMING_UP,
  34.         DIMMING_DOWN,
  35.         DIMMING_FLASH,
  36.         DIMMING_CHANGE_ALARM,
  37.         DIMMING_DOWN_FAST,
  38.         _DIMMING_FINISH,
  39.  
  40. } LedDimmingMode;
  41.  
  42.  
  43. static unsigned char LastPinInput = 0xff;               // 이전 입력값
  44. // LED brightness control variables
  45. static char currentDimmingMode = DIMMING_UP;
  46. unsigned char dimmingIndex = 0;
  47. short int brightIndex = 0;              // [0..1] 0 for full power brightness / 1 for >> 1 == /2
  48.  
  49.  
  50. /*
  51.   TCNT1 == Timer/Counter Register (현재 count 값 저장)
  52.                 0되었을 경우 BOTTOM 신호 발생
  53.                 0xFFFF 되었거나 OCR1A/B, ICR1 에 설정된 값에 도달하면 TOP 신호 발생
  54.                   --> BOTTOM / TOP 신호에 의해 카운트를 업/다운 혹은 리셋으로 처리
  55.  
  56.  
  57.   TCCR1AB == Timer/Counter Control Register
  58.  
  59.  
  60.   ICR1 == Input Capture Register
  61.                 입력캡쳐핀 ICx(타이머1의 경우에는 아날로그 비교기의 출력신호도 가능)
  62.                 으로 들어오는 신호변화를 검출하여 일어나는 입력캡쳐시 TCNT1의 카운터 값을 저장하는 16비트 레지스터로
  63.                 이때 ICFx 플랙이 세트되고 입력캡쳐인터럽트가 요청된다. 어떤 신호의 주기 측정에 응용될 수 있다.
  64.  
  65.  
  66.   OCR1AB == Output Compare Register
  67.                 TCNT1 값과 출력 비교되기 위한 16bit data값 저장 / TCNT와 같을경우 OCF 플랙설정, 출력비교 인터럽터 요청,
  68.                 출력핀 OC1A/B 단자로 데이타 출력
  69.  
  70.  
  71.   ICP1 == Input Capture Pin 1: PD6 pin for Timer/Counter1
  72.  
  73.                
  74.   OC1A == 출력핀
  75.   OC1B == 출력핀
  76.  
  77. */
  78.  
  79.  
  80. /*******************************************************************************
  81.  Timer 1 Duty cycle 을 조절하여 LED 발기값을 조절 한다
  82.  [0..256] 받아서, 추가 곱하기 scaling 하여 처리하자
  83. *******************************************************************************/
  84. void SetLedBrightness( int brightness )
  85. {
  86.         brightness = 0xFF - ((brightness & 0xFF)>> brightIndex);
  87.         OCR1A = brightness << 4 | 0x0f;
  88. }
  89.  
  90.  
  91. /*******************************************************************************
  92.  INT0 (PORT D0) - 스위치 입력 처리
  93. *******************************************************************************/
  94. ISR( INT0_vect , ISR_BLOCK )
  95. {
  96. /*      if ( 0 == brightIndex )
  97.                 brightIndex = 1;
  98.         else
  99.                 brightIndex = 0;
  100. */
  101.         if ( ++brightIndex > 2 )
  102.                 brightIndex = 0;
  103.  
  104.         LastPinInput = 0xff;    // 강제로 이전 pin값을 무효화 하여 기어 위치 재출력 로직타게 한다.
  105. }
  106.  
  107.  
  108. /*******************************************************************************
  109.  Timer 0 - 기어 포지션 센서 값 확인 핸들러 주기 설정
  110.  
  111. *******************************************************************************/
  112. void Timer0_Init(void)
  113. {
  114.         // 아래 두 문장은 직접 관련 없는 사항
  115.         DDRB = 0x08;    // Enable output for OC0(PB3)  
  116.         PORTD = 0x00;   // Clear
  117.  
  118.         // Configure Timer/Counter Control Register for 0
  119.         // Fast PWM mode with Clear OC0 on Compare Match, set OC0 at TOP / Clock to No prescaling -> /64 divide
  120.         //TCCR0 = (1<<WGM01) | ( 1<<WGM00) | ( 1<<COM01) | (1<<CS01) | (1<<CS00);
  121.  
  122.         // Normal mode for event looping logic
  123.         // 8MHz 0.125us == 0.000125 ms
  124.         // to /256 then 256*256(count) =
  125.         // to /1024 then 1024*256(count) = 0.000125 * 1024*256 = 32.768 ms = [ 0.032 sec ] * 31 = 0.992 sec ! (31 회 핸들링 시 마다 1초에 가까움)
  126.         //
  127.         // TCCR0 = 0x04;        /256 divide
  128.         //       = 0x05;        /1024 divide
  129.  
  130.         TCCR0 = 0x05;   // Use /1024 divide
  131.         TIMSK = TIMSK | (1<<OCIE0);     // OCR0 match 시에 match intrrupt 발생!
  132.  
  133.         // Output Compare Register - OCR0
  134.         OCR0 = 255;     //126;  //255;  // It's 8 bit!(Max 255)
  135. }
  136.  
  137.  
  138. /*******************************************************************************
  139.  
  140.  확실한 dimming 처리를 위해서는 Timer 1의 16bit 해상도를 사용해야 완전 어두움을 구현 가능!
  141.  밝기 조절은 Timer1 사용
  142. *******************************************************************************/
  143. void Timer1_PWM_Init(void)
  144. {
  145.         //--------------------------------------------------------------------------
  146.         // PortD Pin 4 == OC1B / Pin 5 == OC1A  ==> 00110000 == 0x30
  147.         DDRD = 0x30;    // Enable output for OC1A|OC1B         
  148.         PORTD = 0x00;   // Clear
  149.        
  150.         //--------------------------------------------------------------------------
  151.         // Set TCCR1A/B == Timer/Counter Control Register
  152.         // Fast PWM Mode --> Need WGM13, WGM12, WGM11
  153.         // No prescaling --> CS10
  154.         // /8 prescaling --> CS11
  155.         // Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at BOTTOM
  156.         // Non-Inverting Mode
  157.         TCCR1A = (1<<COM1A1)|(1<<COM1B1)|(1<<WGM11);
  158.         //TCCR1B = (1<<WGM13)|(1<<WGM12)|(1<<CS10);
  159.         TCCR1B = (1<<WGM13)|(1<<WGM12)|(1<<CS11);
  160.        
  161.         // ICR1, TOP Value!
  162.         // Crystal = 4MHz, 0.25us
  163.         // //0.25us * 40000 = 10msec, Frequency = 100Hz
  164.         // ICR1 = 39999;        //40000 - 1
  165.         // //Duty Cycle = 50%, 40000/2
  166.         // OCR1A = 19999;       //20000 - 1
  167.         // //Duty Cycle = 25%, 40000/4
  168.         // OCR1B = 9999;        //10000 - 1
  169.  
  170.  
  171.         //--------------------------------------------------------------------------
  172.         // Crystal = 8MHz -> 0.125us (0.000000125 sec / 0.000125 ms)
  173.         // For 240Hz need 0.0042 ms  / 80000 for 100Hz, 20000 for 400Hz
  174.         //ICR1 = 16384-1;       // 0x4000 2^15  -->> 이 값을 높여 버리면! - 전체적인 밝기 조절이 가능!!!!!!!!!!!!!!!!!!!
  175.         ICR1 = TIMER0_PWM_RATIO_MAX - 1;        // TIMER0_PWM_RATIO_MAX = 4096
  176.         OCR1A = 0;      //TIMER0_PWM_RATIO_MAX - 1;     // OCR1A PWM MAX for dimming, 0 for Max Brightness
  177.         OCR1B = TIMER0_PWM_RATIO_MAX - 1;
  178. }
  179.  
  180.  
  181. /*******************************************************************************
  182. *******************************************************************************/
  183. void Timer1_PWM_Init2(void)
  184. {
  185. /*
  186.         DDRB = 0xff;
  187.         PORTB = ~0x08;
  188. //      PORTB = 0x00;
  189.  
  190.         TCCR1A = TIMER1_PWM_INIT;
  191.         TCCR1B = TIMER1_CLOCKSOURCE;
  192.  
  193.         OCR0 = 0;
  194. */
  195. }
  196.  
  197.  
  198. /*******************************************************************************
  199.  
  200. *******************************************************************************/
  201. void Timer2_Init(void)
  202. {
  203.         // Normal mode for event looping logic
  204.         // 8MHz 0.125us to /1024 then 1024*88(count) = 11Hz
  205.         TCCR2 = 0x07;   // /1024  [CS2,CS1,CS0] = 111
  206.         TIMSK = TIMSK | (1<<OCIE2);     // OCR2 match 시에 match intrrupt 발생!
  207.  
  208.         // Output Compare Register - OCR0
  209.         OCR2 = 255;     // COMP 핸들러에서 TCNT2를 0 으로 초기화하여 fine tunning 값으로 사용
  210. }
  211.  
  212.  
  213. /*******************************************************************************
  214.  Interrupt vector for Timer 0 overflow signal
  215.  
  216. *******************************************************************************/
  217. /*ISR( TIMER0_OVF_vect, ISR_BLOCK )
  218. {
  219.         // 아직 활성화 하지 않음.
  220. }*/
  221.  
  222.  
  223. //******************************************************************************
  224. // getTransmissionValue로 부터 받아진 결과값을 입력하면 포트출력 값을 돌려준다
  225. //******************************************************************************
  226. void SetLedDimming( LedDimmingMode mode )
  227. {
  228.         currentDimmingMode = mode;
  229.         dimmingIndex = 0;
  230.         // 위 설정값은 TIMER2_COMP_vect 에서 처리함.
  231. }
  232.  
  233.  
  234. //******************************************************************************
  235. // Gear위치를 LED로 출력
  236. //******************************************************************************
  237. void SetLedDisplayChar( char gearPosition )
  238. {
  239.         displaySegmentDataNoEffect( getSegmentData( gearPosition ) );
  240. }
  241.  
  242.  
  243. /*******************************************************************************
  244.  Control LED dimming effects
  245.  LED 밝기값을 시나리오별로 조절하는 역할을 한다.
  246.  
  247.  현재 대충~ 30 회당 1초로 확인 됨. // 100회당 3.3 초
  248.  --> 10회당 1초로 할까?  --> 이게 만만해 보임 좀 버번 거리게 보일 수도...
  249.  --> 100회당 1초로 할까? --> 100Hz > 좀 부하가 있을 느낌
  250. *******************************************************************************/
  251. //typedef struct
  252. //{
  253. //      unsigned char   index;                  // 0 then stop the effect progressing
  254. //      unsigned char   brightness;             // -1 then restart the effect - it reset dimmingIndex
  255. //} indexToBrightness;
  256.  
  257. ISR( TIMER2_COMP_vect, ISR_BLOCK )
  258. {
  259.         const int                       // 1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17   18   19   20
  260.                 dimmingUpTbl[] =        { 20,  40,  60,  80, 100, 150, 200, 255, 255, 255,  -1,  -1 };
  261.         const int
  262.                 dimmingDownTbl[] =      {200, 200, 190, 180, 170, 160, 150, 140, 130, 120, 110, 100,  90,  80, 75,  70,  65,  60,  55,  50,
  263.                                                           45,  40,  35,  30,  27,  24,  21,  19,  18,  17,  16, -1,  -1 };
  264.         const int
  265.                 dimmingFlashbl[] =      {255, 255, 200, 160, 100,  30,   0,   0, 120, 250,  -2 , -2 };
  266.         const int
  267.                 dimmingChangeAlarmTbl[] =
  268.                                                         {255,   0, 255,   0, 255, 200, 150, 100,  50, 100, 150,200, 250, 200, 170, 200, 255,  -1,  -1 };
  269.         const int
  270.                 dimmingDownFastTbl[] =
  271.                                                         {100, 100, 180, 255, 200, 150, 100,  70,  50,  30,  20, 10,  0,   -1,  -1 };
  272. //      static unsigned char  flash = 0;                // For debugging
  273.         static unsigned char  ISRdelay = 0;
  274.         int     brightness;
  275.  
  276.         ++ISRdelay;
  277.         if ( ISRdelay < 3 )
  278.                 return;
  279.         else
  280.                 ISRdelay = 0;
  281.  
  282.  
  283.         TCNT2 = 0;      // Timer reset for resolution tunning
  284.  
  285.         //__________ 디버깅용 LED 깜빡임
  286. //      ++flash;
  287. //      if ( 5 == flash )
  288. //              PORTB = PORTB | 0x01;
  289. //      else if ( 10 == flash )
  290. //      {
  291. //              PORTB = PORTB & ~0x01;
  292. //              flash = 0;
  293. //      }
  294.         //^^^^^^^^^^
  295.  
  296.         brightness = 0;
  297.         switch ( currentDimmingMode )
  298.         {
  299.         case DIMMING_OFF:               // Just turn off the LED
  300.                 SetLedBrightness( 0 );
  301.                 currentDimmingMode = _DIMMING_FINISH;
  302.                 dimmingIndex = 0;
  303.                 return;
  304.                 break;
  305.  
  306.         case DIMMING_UP:
  307.                 brightness = dimmingUpTbl[dimmingIndex];
  308.                 if ( -1 == brightness )                                         // -1 to finish effect scenario
  309.                 {
  310.                         currentDimmingMode = _DIMMING_FINISH;
  311.                         dimmingIndex = 0;
  312.                         return;
  313.                 }
  314.                 break;
  315.  
  316.         case DIMMING_DOWN:
  317.                 brightness = dimmingDownTbl[dimmingIndex];
  318.                 if ( -1 == brightness )                                         // -1 to finish effect scenario
  319.                 {
  320.                         currentDimmingMode = _DIMMING_FINISH;
  321.                         dimmingIndex = 0;
  322.                         return;
  323.                 }
  324.                 break;
  325.  
  326.         case DIMMING_FLASH:
  327.                 brightness = dimmingFlashbl[dimmingIndex];
  328.                 if ( -2 == brightness )                                         // -2 to repeat the effect
  329.                 {
  330.                         dimmingIndex = 0;
  331.                         return;
  332.                 }
  333.                 break;
  334.  
  335.         case DIMMING_CHANGE_ALARM:
  336.                 brightness = dimmingChangeAlarmTbl[dimmingIndex];
  337.                 if ( -1 == brightness )                                         // -1 to finish effect scenario
  338.                 {
  339.                         currentDimmingMode = _DIMMING_FINISH;
  340.                         dimmingIndex = 0;
  341.                         return;
  342.                 }
  343.                 break;
  344.  
  345.         case DIMMING_DOWN_FAST:
  346.                 brightness = dimmingDownFastTbl[dimmingIndex];
  347.                 if ( -1 == brightness )                                         // -1 to finish effect scenario
  348.                 {
  349.                         currentDimmingMode = _DIMMING_FINISH;
  350.                         dimmingIndex = 0;
  351.                         return;
  352.                 }
  353.                 break;
  354.  
  355.         case _DIMMING_FINISH:
  356.                 currentDimmingMode = _DIMMING_FINISH;
  357.                 return; // 더 이상 counting 하지도, 동작 수정 안함.
  358.                 break;
  359.         }
  360.  
  361.         SetLedBrightness( brightness );
  362.         ++dimmingIndex;
  363. }
  364.  
  365.  
  366. /*******************************************************************************
  367.  
  368. *******************************************************************************/
  369.  
  370.  
  371. /*******************************************************************************
  372.  Interrupt vector for Timer 0 compare matching
  373.  기어 센서 상태값을 확인하고, 전환 시나리오별로 처리 한다.
  374. *******************************************************************************/
  375. #define TIMEOUT_TO_1SEC (31)            // 30은 1분 이하로 오차 발생, 31이 좀더 근접하나 상세 TUNING을       tcnt0 를 통할 필요 있음
  376.  
  377. ISR( TIMER0_COMP_vect, ISR_BLOCK )
  378. {
  379.         //__________ 디버깅용 LED 깜빡임
  380.         {
  381.                 static char  flash = 0;
  382.                 if ( ++flash > TIMEOUT_TO_1SEC )
  383.                 {
  384.                         flash = 0;
  385.                         PORTB ^= 0x01;
  386.                 }
  387.         }
  388.         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  389.  
  390.  
  391.  
  392.         // Timer 해상도를 좁히기 위해 OCR0 값을 설정하고, 이 code를 활성화 하고 값을 높이면 timer 분주를 빨리함 (시작 카운트 값 설정)
  393.         TCNT0 = 3;
  394.         {
  395.                 static char LastGearPosition = '-';                                     // 이전 기어 상태 (LastPinInput으로 부터 변환된 값)
  396.                 static unsigned long int InputChangeTimeCount = 0;      // 기어 변속 이후 경과시간 - 0: Timer 중지, 1~ Counting 계속
  397.                 static unsigned long int IdleTimeCount = 0;                     // Idle time 경과시간 - 0: Timer 중지, 1~ Counting 계속
  398.  
  399.  
  400.                 unsigned char NewPinInput = ~PINC & 0x7f;                       // 현재 Pin 값 받기
  401.                 unsigned char NewGearPosition = GetGearPosition( NewPinInput ); // Gear 위치값으로 환산
  402.  
  403.                 if ( LastPinInput != NewPinInput )              // 센서 입력 변동?
  404.                 {//----- 센서 변동 -----------------------------------------------------
  405.                         LastPinInput = NewPinInput;                     // 이전값 갱신
  406.                         InputChangeTimeCount = 1;                       // 변동 시간 count 시작
  407.                         IdleTimeCount = 0;                                      // Idle time count 중지
  408.  
  409.                         switch ( NewGearPosition )
  410.                         {
  411.                                 case '-' :      // 기어 전환중, 중립상태 시나리오
  412.                                 case 'N' :      // 다른 기어로 전환이 곧 될 수 있기에 Dimming처리고 어둡게 함
  413.                                         // 0~6 에서
  414.                                         if ( '-' != LastGearPosition )                  // 이전엔 기어위치였으나, 전환 하려는 경우임
  415.                                         {
  416.                                                 SetLedDimming( DIMMING_DOWN );          // 천천히 어두워 지게 함
  417.                                                 LastGearPosition = NewGearPosition;     // 기어값만 갱신
  418.                                                                                                                         // 변동 시간 count 계속 동작하게 둠. 0 상태에서 에니메이션 시나리오 가능케 함
  419.                                                                                                                         // LED 표시는 수정하지 않음
  420.                                         }
  421.                                         break;
  422.  
  423.                                 case 'E' :      // 입력 에러E / 후진(B)시는 즉각적으로, 또한 깜빡임 처리
  424.                                 case 'B' :
  425.                                         LastGearPosition = NewGearPosition;             // 마지막 기어 위치값 갱신
  426.                                         SetLedDisplayChar( NewGearPosition );   // LED 표시 갱신
  427.                                         InputChangeTimeCount = 0;                               // 변동 시간 count 중지 -> 무변동 상테에서 시나리오 멈춤
  428.                                         SetLedDimming( DIMMING_FLASH );                 // 반복적인 깜빡임
  429.                                         break;
  430.  
  431.                                 default:        // 나머지 기어값인 1 ~ 6 의 경우 기어 위치 확정 표시
  432.                                         LastGearPosition = NewGearPosition;             // 마지막 기어 위치값 갱신
  433.                                         SetLedDisplayChar( NewGearPosition );   // LED 표시 갱신
  434.                                         InputChangeTimeCount = 0;                               // 변동 시간 count 중지 -> 무변동 상테에서 시나리오 멈춤
  435.                                         SetLedDimming( DIMMING_CHANGE_ALARM );  // 깜빡이면서 밝게 표시
  436.                                         break;
  437.                         }
  438.                 }
  439.                 else
  440.                 {//----- 변화가 없는 경우 ----------------------------------------------
  441.                         // '-' 혹은 '0' 가 계속 유지 되고 있다면 특정 시간 이후에 '-' / '0'을 표시한다
  442.  
  443.                         //----- 기어 변경 후 일정 시간 지남 확인 카우트 동작 진행
  444.                         if ( InputChangeTimeCount )
  445.                         {
  446.                                 // 0 표시는 즉각적으로 갱신하지 않고, 1초간 대기 후 갱신한다.
  447.                                 // 왜냐면 기어 변속중에 항상 0 위치를 거치게 되므로 잦은 갱신을 하지 않아 자연스럽게 다음 기어를 표시하게 된다.
  448.  
  449.                                 //----- N(중립) 에서 1초 초과시에 진짜 N(0)으로 표시 갱신
  450.                                 if ( ('N' == NewGearPosition) && (InputChangeTimeCount > 1*TIMEOUT_TO_1SEC) )
  451.                                 {
  452.                                         InputChangeTimeCount = 0;                               // 이후 Timer 작동 중지
  453.                                         IdleTimeCount = 1;                                              // Idle time count 시작 -> Idle animation 처리 용
  454.                                         LastGearPosition = NewGearPosition;             // 기어 포지션 갱신
  455.                                         SetLedDisplayChar( NewGearPosition );   // LED 표시 갱신
  456.                                         SetLedDimming( DIMMING_CHANGE_ALARM );  // 깜빡이면서 밝게 표시
  457.                                 }
  458.                                 //----- 기어 이동중(기어 물림 위치가 아닌경우)이라면 3초간 기존 상태 유지(위에서 dimming down) 후, 새값으로 갱신
  459.                                 else if ( ('-' == NewGearPosition) && ( InputChangeTimeCount > 3*TIMEOUT_TO_1SEC ))
  460.                                 {
  461.                                         InputChangeTimeCount = 0;                               // 이후 Timer 작동 중지
  462.                                         //IdleTimeCount = 1;                                    // Idle time count 시작 X
  463.                                         LastGearPosition = NewGearPosition;             // 기어 포지션 갱신
  464.                                         SetLedDisplayChar( NewGearPosition );
  465.                                         SetLedDimming( DIMMING_UP );                    // 천천히 밝게 표시
  466.                                 }
  467.                                 else
  468.                                 {
  469.                                         if ( ++InputChangeTimeCount >= ULONG_MAX )      // Counting
  470.                                                 InputChangeTimeCount = 0;                       // Overflow 시에 더이상 이어변동 후 타이머 동작은 작동 안함.
  471.                                 }
  472.                         }
  473.                         //----- 이어 변경 직후 타이밍 동작 완료 후, Idling 상태에서 0 위치에서 animation 표시 등 부가 동작 수행
  474.                         else
  475.                         if ( IdleTimeCount && 'N' == NewGearPosition )
  476.                         {       // 0 상태에서 장시간 대기중일 경우 Rotation animation 처리하기~
  477.                                 const char segmentAnimationData[] = {           // 오른쪽으로 회전하는 에니메이션에 해당한 segment LED 출력 bit
  478.                                         0b01000000,     // 0b01000100,
  479.                                         0b00100000,     // 0b00100010,
  480.                                         0b00010000,     // 0b00010001,
  481.                                         0b00000100,     // 0b01000100,
  482.                                         0b00000010,     // 0b00100010,
  483.                                         0b00000001,     // 0b00010001,
  484.                                 };
  485.  
  486.  
  487.                                 //----- 30초 초과시 부터 idle animation시작
  488.                                 if ( IdleTimeCount >= 30*TIMEOUT_TO_1SEC )
  489.                                 {
  490.                                         static unsigned char prevAniIndex = 6;
  491.                                         char bDisplayIdleMinTime = 0;
  492.                                         unsigned char aniIndex = ( (IdleTimeCount - 1 ) / TIMEOUT_TO_1SEC ) % 6;
  493.                                         unsigned char i;
  494.  
  495.  
  496.                                         //----- {{110723 lsw2000}} 1분, 2분, 3분, 최고 6분 까지에 대해 Idle 경과 시간 표시! / 웜업, 후열용 참고 시간 표시
  497.                                         if ( IdleTimeCount < (6*60 +10) *TIMEOUT_TO_1SEC )
  498.                                         {
  499.                                                 for ( i=1; i<=6; i++ )
  500.                                                         if ( i*60*TIMEOUT_TO_1SEC <= IdleTimeCount &&IdleTimeCount < (i*60 +(4+i)) *TIMEOUT_TO_1SEC )          // 최초 4초부터 1초식 증가하여 표시
  501.                                                         {
  502.                                                                 if ( i*60*TIMEOUT_TO_1SEC == IdleTimeCount )    // 숫자 전화, 효과 설정은 단 1회만, 이후 자동
  503.                                                                 {
  504.                                                                         SetLedDisplayChar( '0' + i );                           //
  505.                                                                         SetLedDimming( DIMMING_FLASH );                         // 반복적인 깜빡임
  506.                                                                 }
  507.                                                                 bDisplayIdleMinTime = 1;
  508.                                                         }
  509.                                         }
  510.                                         else
  511.                                         //----- 7분 경과부터 매 1분 마다 0 표기 깜빡이기
  512.                                         if ( (7*60) *TIMEOUT_TO_1SEC <= IdleTimeCount )
  513.                                         {
  514.                                                 if ( IdleTimeCount % (60*TIMEOUT_TO_1SEC) < (5*TIMEOUT_TO_1SEC) )      // 매붖 최초 5초 동안
  515.                                                 {
  516.                                                         if ( 0 == IdleTimeCount % (60*TIMEOUT_TO_1SEC) )
  517.                                                         {
  518.                                                                 SetLedDisplayChar( 'N' );
  519.                                                                 SetLedDimming( DIMMING_FLASH );                         // 반복적인 깜빡임
  520.                                                         }
  521.                                                         bDisplayIdleMinTime = 1;
  522.                                                 }
  523.                                         }
  524.                                        
  525.                                         //----- 경과 분수 표시 타이밍이 아닌 경우 animation 표시
  526.                                         if ( !bDisplayIdleMinTime && ( prevAniIndex != aniIndex ) )
  527.                                         {
  528.                                                 prevAniIndex = aniIndex;
  529.                                                 PORT_SEG = ~segmentAnimationData[ aniIndex ];   // ~for Negative output
  530.                                                 SetLedDimming( DIMMING_DOWN_FAST );                             // 새 위치 표시 후 어두워지게 효과 처리
  531.                                         }
  532.                                 }
  533.  
  534.                                 if ( ++IdleTimeCount >= ULONG_MAX )                             // Overflow 대비, animation 계속하게 함.
  535.                                         IdleTimeCount = 30*TIMEOUT_TO_1SEC;
  536.                         }
  537.                         // TODO: 기어 상태에서 장시간 있을 경우 살짝 깜빡임성 표시로 환기 시키기
  538.                 }
  539.  
  540.         }
  541.  
  542.  
  543. }
  544.  
  545. /*******************************************************************************
  546.  Main enterance
  547.  
  548. *******************************************************************************/
  549. int main( void )
  550. {
  551.         cli();
  552.  
  553.         Timer0_Init();          // 노브 조작 감지(센서 감지 루프)
  554.         Timer1_PWM_Init();      // PWM ouput 조작
  555.         Timer2_Init();          // 깜빡임, 밝기 시나리오 흐름
  556.  
  557.         //----- INT0 ----------
  558.         GICR |= 1<<INT0;        // INT0 enable
  559.         MCUCR = 2;                      // 하강엣지에 인트럽트 발생
  560.  
  561.         DDRA = 0xFF;            // PortA: 출력 / 7 Segment
  562.         DDRB = 0xFF;            // PortB: Timer and Debug signal
  563.         DDRC = 0x00;            // PortC: 입력 / 기어 포지션 센서
  564.         //DDRD = ~0x04;         // PortD2(INT0) 입력 활성화
  565.  
  566.         PORT_SEG = 0xFF;        // Common anode 이므로 1상태가 off 가 된다
  567.         PORTB = 0xFF;           // + 인가
  568.         PORTC = 0xFF;           // Input port에 1을 setting하여 Internal pull-up 저항을 활성화
  569.         PORTD = 0x04;           // PD2(INT0) Pull-up
  570.  
  571.  
  572.         sei();                          // 주 동작은 Timer handler에서 수행 됨.
  573.         // Timer0: Main loop
  574.         // Timer1: PWM power 조절
  575.         // Timer2: LED 깜빡인 control
  576.  
  577.         SetLedBrightness(10);   // First Full brightness.
  578.  
  579.         while ( 1 )
  580.         {
  581.  
  582.         }
  583.  
  584.         return 0;
  585. }



.



수동기어 인디케이터의 완성

앞서 브레드보드로 만든 회로를 최종적으로 만능기판을 이용하여 구성 했습니다.
향후 확장을 위해 충분한 크기의 기판을 사용 했습니다. 겨우 안쪽에 넣을 크기가 되었더군요 - 좀더 작게 만들 필요가 있어 보입니다.


글은 모두 4개로 구분하여 올렸습니다.




앞쪽 모습입니다. 주문 실수로 저항을 1/2W 용량을 사버렸네요. 굴찍한게 좀 없어 보입니다 ㅎㅎ
중앙 부분에 위치한 풀업 저항 역시 1/2W 용량인데 크기는 1/4W 급과 동일 합니다.




장착 사진을 추가로 소개 드리면 아래와 같습니다.

*실제 최종 완성본에는 뒷쪽에 필터기능용 부품 2개(고전압 필터, 적은 용량 컨덴서 한개)를 추가 했습니다.
오동작 때문에 넣었는데, 필요 없겠더군요. 오동작 원인은 마이컴을 차량 배선에 가까이 붙이지 않으면 됩니다.


세워 둔 곳이 리어덕터 생략된 위치 입니다. 수동모델은 리어덕터가 없기에 공간 확보가 되었네요 @.@


7 Segment 단자를 비교적 길~게 해서 중앙 콘솔까지 연결합니다.


글로브박스를 분리해서 연결하면 매우 쉽습니다. 절대 그냥 하려 하지 마세요 - 분리 할 건 분리 하셔야!




최종 동작 결과 소개를 드립니다.
개별 기어에 맞추어 7-Segment를 통해 현재 기어가 표시가 됨을 확인 할 수 있습니다.
개별 표시는 그냥 바뀌는 것이 아니라 아래의 규칙대로 효과를 보여 줍니다.

기어 변경
해당 기어 숫자가 빠르게 깜빡이면서 눈에 띄도록 표시가 됩니다. 깜빡임에는 몇종류의 형태로 변화 하면서 변화를 보이다가 고정이 됩니다.

기어 변경 조작 중인 경우
기어가 변경중인 경우이고 다음 기어 위치로 고정 되지 않은 상태에서는 점점 희미하게 이전 기어 표시가 어두어 지다가 '-' 표기로 바뀌게 됩니다.

기어 위치 센서에 아무런 값이 입력 되지 않는 경우
이 경우 에러 상태를 알리는 'E'표기가 됩니다.
입력 상태를 확인 할 필요가 있습니다. 특히 센서 동작을 확인 해야 합니다.

후진은 R 표시가 곤란하므로 P. 형태로 표시합니다.
또한 후진 상태에서 일정 시간이 지나면 빠르게 깜빡여서 후진임을 알려줍니다.

중립 상태에서 장시간 대기시에
0으로 표시되던것이 하나의 LED만 점등하면서 회전하는 애니메이션을 보여 줍니다.

다시금 동작 동영상을 소개 드립니다.
시내주행이라 억지로 6단 까지 넣어 봤습니다 ^.^


개별 기어 동작별 표시 변경 내용만 보면 아래와 같습니다.
정차 상태에서 운전석에서 제가 촬영하고, 친구가 조수석에서 조작해서 좀 부드럽지 못합니다.
전체적인 동작을 확인 하는 영상으로 이해 하세요.

*개별 기어 상태에서 변속 동작이 완료 되지 않은 상태로 빠지면 점점 어두워지게 했습니다.
 PWM 동작은 아니고 루프에서 카운트 값을 적당히 조절 했답니다(소스 참조)



개별 기어수에 대한 사진 입니다.














요거이 후진입니다. R 이라 하고 싶었지만 구할 수 있는 표시기가 순수 7 Segment라 P. 으로 표시하고 일정 시간 직후 "깜빡이"게 했습니다.
제가 R에서 실수 해서 한이 맺혀서 말이죠 ㅋㅋㅋ (빵판에서는 부저도 달았었습니다. - 빽빽 거립니다 ㅎㅎ)


이상입니다.
혹시나 수동기어 인디케이터가 필요하신 분께 참고 자료가 되었으면 합니다.



이번 글에는 메인 모듈 회로 구성 회로와 마이컴 모듈인 ATMega128용 소스를 소개 드립니다.

제가 한방에 납땜하고, 설계하고, 코딩하지는 않았습니다.
나름 여러 재료와 함께 브레드보드(일명 빵판)도 생에 처음으로 구입하여 미리 구성하고 확인하여 회로를 완성 했습니다.
전원 연결, 포트 3개를 이용하여 하나는 기어센서로 부터 입력 7개, 디버깅용 - 즉 센서 감지용 LED 7개(실은 미리 8개 연결), 그리고 최종 기어단수 표시용 7-segment용 출력 8단자 입니다.

[수정: 2010-1-8 ]
풀업 저항 10k옴* 8개는 삭제 합니다. 소스코드도 수정 되었습니다.
제가 내부 풀업 셋팅을 몰라서 사용 했는데, 코드 한줄 추가로 설정 했습니다(초자라 이해를...)
파일도 수정된 내용으로 바꾸었습니다.
아래 회로 그림에서 10k부분은 완전히 제거 하시면 됩니다(더 간단하죠)


글은 모두 4개로 구분하여 올렸습니다.
1. 소개글, 개별 단수 인식 방법과 필요한 부품
2. 센서 모듈 만들기와 장착
3. 회로 구성을 위한 회로도와 마이컴 소스코드 [지금 보고있는 글입니다]
4. 완성품 설치와 동작 모습 소개


우선 브레드 보드로 마이컴 모듈을 장착하고, 기어 상태 입력을 위한 부분, 현재 입력 상태 확인용 LED 부분, 최종 기어 단수 표시를 위한 7-Segment 출력 부분으로 구성 했습니다.

입력은 Port A 단자와 연결
입력값 확인용 출력단은 Port C 단자와 연결
7-Segment 출력은 Port  F 단자와 연결 했습니다.

포트 선택의 기준은 한쪽에 몰려 있어서 그냥 사용 했습니다.
AVR은 처음 만져 보는 것이라, 인터럽터, 타이머쪽 고려는 전혀 되지 않았습니다.
- 회로, 코드 보시고 고수분들의 한수 지도 의견 환영 합니다.

상세 연결은 브레드 보드와 연결된 실제 회로 구성 사진으로 대신 하고자 합니다.

위 사진 보고는 느낌밖에 오지 않습니다 ^.^ 밑에 회로도를 보시기 바랍니다.

중요한 핀 번호와 개별 연결 단자와의 구성은 아래와 같이 정리 할 수 있습니다.


네... 회로도가 잘 되었는지 모르겠네요 ^.^


AVR 동작을 위한 code는 아래와 같습니다. 실제 사용하는 code입니다.
제가 AVR 프로그래밍은 처음이라 인터럽트, 타이머는 사용하지 않는 단순한 폴링 방식의 구현임을 감안 하셨으면 합니다. 고수분들의 한수 지도 환영 합니다.
코드에 코멘트를 보시면 이해에 도움이 될 듯 합니다.

다운로드:

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


  수동기어 단수표시 장치

  @File  GearPositionIndicator.c
*******************************************************************************/


#include <avr/io.h>

#define    PORT_SEG            PORTF    // Segment 출력용 port
#define    SEG_COMMON_ANODE    1        // Anode 공통 == + 극 공통으로 출력이 0으로 나가야 동작 하는 경우

#ifdef    SEG_COMMON_ANODE
    #define    SEGOUT(value)    (~value)    // Coommon Anode이므로 bit 상태를 뒤집는다
#else
    #define    SEGOUT(value)    (value)
#endif


const unsigned char    SEGMENT_MINUS    = 0b10000000;    // -
const unsigned char    SEGMENT_0        = 0b01110111;    // 0
const unsigned char    SEGMENT_1        = 0b00010100;    // 1
const unsigned char    SEGMENT_2        = 0b10110011;    // 2
const unsigned char    SEGMENT_3        = 0b10110110;    // 3
const unsigned char    SEGMENT_4        = 0b11010100;    // 4
const unsigned char    SEGMENT_5        = 0b11100110;    // 5
const unsigned char    SEGMENT_6        = 0b11100111;    // 6
const unsigned char    SEGMENT_7        = 0b01110100;    // 7
const unsigned char    SEGMENT_8        = 0b11110111;    // 8
const unsigned char    SEGMENT_9        = 0b11110110;    // 9
const unsigned char    SEGMENT_BACK    = 0b11111001;    // P. for Back
const unsigned char    SEGMENT_ERROR    = 0b11101011;    // E. for Error
const unsigned char    SEGMENT_H3        = 0b10100010;    // 한자 3
const unsigned char    SEGMENT_ALL        = 0b11111111;    // All for test


//******************************************************************************
//******************************************************************************
void init( void )
{
    // Enable pull up
    int    SpecialFunctionIO = SFIOR;
    SpecialFunctionIO = ~4 & SpecialFunctionIO;
    SFIOR = SpecialFunctionIO;
}



//******************************************************************************
// 7Segement test 출력
//******************************************************************************
void Display7SegmentOnPortF( void )
{
    const unsigned char    segmentData[] = {
                SEGMENT_0,        // 0
                SEGMENT_1,        // 1
                SEGMENT_2,        // 2
                SEGMENT_3,        // 3
                SEGMENT_4,        // 4
                SEGMENT_5,        // 5
                SEGMENT_6,        // 6
                SEGMENT_7,        // 7
                SEGMENT_8,        // 8
                SEGMENT_9,        // 9
                SEGMENT_H3,        // =
                SEGMENT_BACK,    // R
                SEGMENT_ALL,    // All for test
    };
    static int    index = 0;
    static long int delay = 0;

    if ( ++delay > 10000 )
    {
        delay = 0;
        PORT_SEG = SEGOUT(segmentData[ index++ ]);    // Invert for common Anode LED
        if ( index >= sizeof(segmentData)/sizeof(segmentData[0]) )
            index = 0;
    }
    else
    {
        //PORT_SEG = SEGOUT(0);    // Turn off
    }
}

//******************************************************************************
// Port로 부터 입력된 기어 위치값으로 부터 현재 기어 위치를 char 문자로 돌려준다
//    중립='N'
//    각 단수='1'~'6'
//    후진:'B'
//    기어조작중:'-'
//    에러상태:'E'
//******************************************************************************
typedef struct
{
    char    state;
    char    position;
} stateToPosition;

char getTransmissionValue( char state )
{
    const stateToPosition    stateData[] = {
        { 0b00100010, 'N' },    // Natural
        { 0b00010100, '1' },    // 1
        { 0b01000100, '2' },    // 2
        { 0b00010010, '3' },    // 3
        { 0b01000010, '4' },    // 4
        { 0b00010001, '5' },    // 5
        { 0b01000001, '6' },    // 6
        { 0b00011000, 'B' },    // Back
    };
    char    result = '-';
    int        sensingCount = 0;
    int        i;

    state = state & 0b01111111;    // 상위 1bit 사용 안함 처리
   
    // CheckError - 최소한 하나의 bit는 1이어야 정상이다. (실은 2개)
    if ( !(0b01111111 & state) )
    {
        return 'E';    // 센서로부터 아무런 입력값이 없을 경우 Error처리
    }

    // 변속 중인 상태 확인 -> 앞뒤
    sensingCount += ( state & 0b01000000 ) ? 1 : 0;
    sensingCount += ( state & 0b00100000 ) ? 1 : 0;
    sensingCount += ( state & 0b00010000 ) ? 1 : 0;
    if ( sensingCount > 1 )    // 두 위치값이 동시에 입력되면 변동 중인 경우
        return result;

    // 변속 중인 상태 확인 -> 좌우
    sensingCount = 0;
    sensingCount += ( state & 0b00001000 ) ? 1 : 0;
    sensingCount += ( state & 0b00000100 ) ? 1 : 0;
    sensingCount += ( state & 0b00000010 ) ? 1 : 0;
    sensingCount += ( state & 0b00000001 ) ? 1 : 0;
    if ( sensingCount > 1 )    // 두 위치값이 동시에 입력되면 변동 중인 경우
        return result;

    // 모두 통과 되었다면 단수 확인 과정, 확인 불가시 '-' 처리가 default
    for ( i=0; i<sizeof(stateData)/sizeof(stateData[0]); i++ )
    {
        if ( stateData[i].state == state )
        {
            result = stateData[i].position;
            break;
        }
    }

    return result;
}

//******************************************************************************
// getTransmissionValue로 부터 받아진 결과값을 입력하면 포트출력 값을 돌려준다
//******************************************************************************
typedef struct
{
    char    mode;
    char    segmentData;
} modeToSegment;

char getSegmentData( char currentMode )
{
    const modeToSegment    modeToSegmentData[] = {
        { '-', SEGMENT_MINUS },    // -
        { 'N', SEGMENT_0 },    // 0
        { '1', SEGMENT_1 },    // 1
        { '2', SEGMENT_2 },    // 2
        { '3', SEGMENT_3 },    // 3
        { '4', SEGMENT_4 },    // 4
        { '5', SEGMENT_5 },    // 5
        { '6', SEGMENT_6 },    // 6
        { 'B', SEGMENT_BACK },    // R == P.
        { 'E', SEGMENT_ERROR },    // E.
    };
    int        i;
    char    segmentData = SEGMENT_ERROR;    // Default to Error


    // 입력값에 대응하는 Segment값을 가져온다
    for ( i=0; i<sizeof(modeToSegmentData)/sizeof(modeToSegmentData[0]); i++ )
    {
        if ( modeToSegmentData[i].mode == currentMode )
        {
            segmentData = modeToSegmentData[i].segmentData;
            break;
        }
    }

    return segmentData;
}

/*******************************************************************************
 segmentData를 포트를 이용하여 출력한다
 이어 위치가 변경될 경우 깜빡이는 효과를 시차를 이용하여 보여준다
 중립(0)상태가 일정시간 지속될 경우 animation 효과(뱅글뱅글)를 보여준다

 변속 조작 진행 상태('-')의 경우 이전 단수가 서서이 사라지는 형태로 처리한다.
*******************************************************************************/
void displaySegmentData( char segmentData )
{
    const char segmentAnimationData[] = {        // 왼쪽으로 회전하는 에니메이션
        0b00000001,
        0b00000010,
        0b00000100,
        0b00010000,
        0b00100000,
        0b01000000,
    };
    static char prevData = 0;
    static unsigned long int    timingCount = 0;
    static unsigned int            rotateTimingCount = 0;
    static char dimmingMode = 0;


    // 특정 단수 --> '-' 즉 기어 조작 진행 중인 경우
    // '-' 표시 전에 이전 단수를 희미하게 사라지게 한다
    if ( ( prevData != segmentData ) && ( SEGMENT_MINUS == segmentData ) )
    {
        if ( !dimmingMode )
        {
            dimmingMode = 1;    // Dimming mode전환
            timingCount = 0;
            PORTB = 0;            // Clear
        }

        if ( timingCount < 1000 )        // 조작 직후 이전 기어 위치 유지
            PORT_SEG = SEGOUT( prevData );
        else
        if ( timingCount < 3000 )
            PORT_SEG = ( 90 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        if ( timingCount < 5000 )
            PORT_SEG = ( 70 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        if ( timingCount < 7000 )
            PORT_SEG = ( 50 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        if ( timingCount < 9000 )
            PORT_SEG = ( 30 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        if ( timingCount < 11000 )
            PORT_SEG = ( 10 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        if ( timingCount < 13000 )
            PORT_SEG = ( 5 < timingCount % 100 ) ? SEGOUT(0) : SEGOUT(prevData);
        else
        {
            PORT_SEG = (char)SEGOUT(SEGMENT_MINUS);    // '-' 최종 반영
            prevData = segmentData;    // '-'로 이전값 설정
            dimmingMode = 0;        // timingCount reset용
        }
       
        // 효과 과정 까지 timingCount 증가
        ++timingCount;
        return;
    }
    else
    {    // Dimming 도중에 다른 기어로 진입시에 reset 처리하여 차후 기어 조작 진행 상태 반영
        if ( dimmingMode )
        {
            dimmingMode = 0;
            timingCount = 0;
        }
    }


    // 일반적인 변동 상황
    if ( prevData != segmentData )
    {
        PORT_SEG = SEGOUT(segmentData);    // Invert for common Anode LED
        prevData = segmentData;
        timingCount = 0;        // For effect
        rotateTimingCount = 0;
    }
    else
    {
        if ( timingCount < 1000 )
            PORT_SEG = ( 100 < timingCount % 200 ) ? SEGOUT(prevData) : SEGOUT(0);
        else if ( timingCount < 3000 )
            PORT_SEG = ( 200 < timingCount % 500 ) ? SEGOUT(0) : SEGOUT(prevData);
        else if ( timingCount < 6000 )
            PORT_SEG = ( 300 < timingCount % 1000 ) ? SEGOUT(0) : SEGOUT(prevData);
        else if ( timingCount < 10000 )
            PORT_SEG = SEGOUT(prevData);
       
        // 효과 과정 까지 timingCount 증가
        if ( timingCount < 600000 )
        {
            ++timingCount;
            if ( (timingCount > 50000) && (SEGMENT_BACK == prevData) )
            {
                //PORT_SEG = ( 4000 < timingCount % 6000 ) ?  ~0 : ~prevData;
                if ( 4000 < timingCount % 6000 )
                {
                    PORT_SEG = SEGOUT(0);
                    PORTB = 0xFF;
                }
                else
                {
                    PORT_SEG = SEGOUT(prevData);
                    PORTB = 0;               
                }
            }
        }
        // 효과 완료 시점
        else
        {
            if ( SEGMENT_0 == prevData )
            {
                static int segmentIndex = 0;

                // 0 인 경우 뱅글뱅글 돌아가는 animation 출력 시작
                if ( ++rotateTimingCount > 20000 )
                {
                    PORT_SEG = SEGOUT(segmentAnimationData[segmentIndex]);
               
                    if ( ++segmentIndex >= sizeof(segmentAnimationData)/sizeof(segmentAnimationData[0]) )
                        segmentIndex = 0;
                    rotateTimingCount = 0;
                }
            }

        }

    }
   
}

//******************************************************************************
//******************************************************************************
int    main( void )
{
    const unsigned int    inOutTime = 1000;
    unsigned long int    timingCount = 0;
    char    currentMode = 'N';
    char     segmentData = 0;

    init();

    DDRA = 0x00;
    DDRC = 0xFF;
    DDRF = 0xFF;
    DDRB = 0xFF;

    PORTA = 0xFF;        // Input port에 1을 setting하여 Internal pull-up 저항을 활성화
    PORTC = 0x00;

    PORT_SEG = 0xFF;        // Common anode 이므로 1상태가 off 가 된다
    PORTB = 0x00;        // 버즈 출력 시도

    while ( 1 )
    {
        char input = 0;

        if ( 0 == timingCount % inOutTime*10 )
        {
            input = ~PINA;    // N==1 접속==0
            PORTC = input;

            // 상태값으로 부터 기어 단수 위치를 판단한다.
            currentMode = getTransmissionValue( input );
            segmentData = getSegmentData( currentMode );
        }

        displaySegmentData( segmentData );
       




        if ( ++timingCount >= 60000 )
            timingCount = 0;
    }


}

다음글은 최종 완성품 사진과 실제 동작사진, 나머지 동영상 모음을 올려 드리도록 하겠습니다.

앞글에 이어서 기어케이블의 위치 값을 읽기 위한 센서 모듈을 만들어 보겠습니다.
센서 모듈이라 해서 거창하지 않습니다.
자석이 가까이 오면 접전이 붙어서 ON 상태로 변하는 리드 스위치를 4개 혹은 3개를 기어 케이블 동작 유격에 맞추어 배치하여 외부로 커넥터를 만들어 주는 작업 입니다.


글은 모두 4개로 구분하여 올렸습니다.


케이블 조작에 따른 위치 변화거리는 ?
기어 조작에 따른 케이블 변위를 측정 해 보았습니다.
둘다 공통적으로 전체 유격은 36mm 입니다.
시프트는 3위치로 해서 나누기 3으로 센서 배치, 셀렉터는 나누기 4로 센서 배치가 필요 합니다.

시프트 케이블
(앞뒤 조작용)
    3가지 위치값을 가지며 개별 위치는 18mm 간격,
    즉 [앞, N, 뒤쪽] 위치 판별   ** 앞은 엔진쪽 위치입니다.
      =   0, 18, 36 위치  [18mm 간격]

셀렉터 케이블 (좌우 조작용)
    4가지 위치값을 가지며 개별 위치는 12mm 간격,
    즉 [우, N, 좌, 후진 ] 위치는 아래와 같음
       =   0, 12, 24, 36 위치  [12mm 간격]  ** 0위치는 엔진쪽 위치 입니다.


[사진: 최초 만든 리드 스위치로 구성한 위치 센서 모듈]
커넥터 부분을 엔진쪽으로 장착 합니다. 그래서 자연스레 위쪽 모듈은 차례로 [우, N, 좌, 후진]을 담당 합니다.
 - 수정 후 빨간색으로 수축튜브 색상을 바꾸었습니다.
아래쪽 모듈은은 [ 앞, N, 뒤쪽] 위치 판별을 담당 합니다.

최종 모듈은 위 모듈에 자석이 부착된 케이블 타이를 고정 해 주는 부분을 추가 했습니다.
볼록 볼록 한 부분에 리드 스위치가 고정 되어 있습니다. 그리고 압축튜브로 감싼게 위의 결과 입니다.
각각 5pin 케이블로 쉽게 연결 분리 하도록 만들었습니다. 직접 단자를 붙이고 하니 시간 꾀나 걸리더군요 ^.^
모듈 뒷쪽에는 양면 테잎을 부착 했습니다. 실제 장착시에 미끄럼 방지와 충격완화 등의 효과를 기대 할 수 있습니다.


조작 단위로 리드 스위치를 배치하여 센서 모듈 설계
시프트 케이블 위치 확인용 모듈과 셀렉터 케이블 위치 확인용 모듈을 각각 아래 설계대로 만듭니다.

셀렉터 케이블용은 처음 만들었다가 실 동작에서 실패해서 새롭게 설계 했습니다.
측정 값대로 하면 12mm 간격으로 총 36mm 를 전체 유격으로 해야 했어나, 영구 자석을 매달아 두는 부품을 기어 조작부분의 회전 동작 외각에 고정하는 관계로 측정한 케이블 위치 보다 바깥위치에 고정이 되어 실제 유격은 13.333mm 간격으로 총 40mm 유격을 이루게 되었습니다.

수정 전:

만일 자석을 고정한 케이블 타이를 시프트 케이블 처럼 고정 위치를 바꾼다면 이 수치대로 만들어야 합니다.

수정 후:

셀렉터 케이블용 자석을 고정한 타이를 기어 록 외각에 위치해서 외각 차이 만큼의 길이 증가로 인해 이 간격을 사용 했습니다.


시프트 케이블용 모듈은 영구 자석 고정 부품을 실제 케이블 고정 부위와 동일 위치로 할 수 있어서 측정 수치 그대로 리드 스위치를 배열 하여 만들었습니다.


'시안'색상으로 된 부분은 "케이블 타이"로 만든 영구 자석 고정 부위를 스위치 모듈과 밀착 되도록 하는 가이드 부품 입니다. 실제 좀더 큰 "케이블 타이"의 고정 걸쇠 부분을 접착제로 붙여서 만들 었습니다.
아래 사진을 보면 이해가 쉬울 것 같습니다. - 케이블 타이를 이용해서 핵심 부품을 만들었네요 ^.^


[사진: 실제 장작된 시프트 케이블용 센서모듈과 영구자석 부착된 케이블타이 조합]
사진 하단 부위에 시프트 케이블 접속 부위가 위치 합니다.
흰색 수축 튜브로 감싸진 둥근 형태 부분에 영구자석이 고정 되어 있습니다.(NS극을 수평으로 위치해야 합니다)
현재 사진은 중립에 해당 하는 위치이며, 윗쪽으로는 후/1/3/5단 위치이며 아래로는 2/4/6단 위치 입니다.
초록색 수축 튜브아래에 볼록하게 보이는 부분이 리드 수위치 부분 입니다.
상단 끝에는 영구자석이 부착된 케이블을 잡아 주기 위한 대형 케이블 타이의 걸쇠 부분으로 만든 고리 입니다.


센서 모듈은 Ground 공통으로 5pin 커넥터로 연결

위 설계대로 센서를 납땜하고, 커넥터는 ground를 공통으로 연결하고 개별 4pin / 3pin으로 스위치와 연결 합니다.
상세 연결은 별도로 올려질 회로도 구성을 참고 바랍니다.

[사진: 고정된 센서 모듈 2개]
왼쪽 빨간색 모듈이 셀렉터 케이블 센서 모듈입니다.(4가지 위치)
오른쪽 시프트 모듈은 현재 후/1/3/5 단 위치 입니다
. (전방으로 기어 위치 조작 상태)



실제 위와 같이 고정하는 작업은 간단 하지 않습니다.
공간도 좁고, 손을 넣을 만한 여유가 없으며, 작은 케이블 타이 만으로 케이블 고정 부위로 부착 해야 하기 때문입니다.
부착 후에도 센서 입력이 제대로 되도록 하기 위해서 튜브로 고정되어 있는 영구 자석의 위치를 움직여 줄 필요가 있습니다.


센서 부위 고정방법을 정리 하면 아래 그림과 같습니다.
개별 모듈은 조작 케이블 고정 부위에 있는 보호부분에 케이블 타이로 고정합니다(케이블 타이의 활약!)
자석도 케이블 타이를 적당히 이어 붙여서 실제 조가 후 개별 조작 위치에 맞도록 자석을 고정 합니다.
-> 자석 고정은 수축 튜브로 고정하여 미세한 위치를 손으로 바꿀 수 있게 합니다.



아래는 장착 참고 사진 입니다.
[기어모듈 윗쪽에서 촬영]


[셀렉터 감지를 위한 모듈을 - 조수석 측면에서 촬영 / 잘 보이지 않는답니다...]
빨간색 모듈 위에, 노락색 압축튜브로 고정된 자석 부분을 확인 할 수 있습니다.



센서 모듈과 실제 장착 방법을 설명 드렸습니다.
실제 부착 부위 확인과 부착, 센서 동작 확인에 꾀나 많은 시간을 소모 하느라 제대로 된 사진이 부족 합니다. 더욱이 촬영 각이 나오지도 않아서... 이번 내용은 좀 난해한 면이 있습니다. 양해 바라며....

다음글에는 회로도와 ATMega128 프로그램 코드를 소개 드리겠습니다.
회로도는 그려야 해서... 시간이 좀 걸리 듯 하네요 ^.^
안녕하세요!
수동기어 인디케이터를 이번에 만들었습니다.

무엇하는 물건이냐면 - 현재 기어 위치를 숫자로 표시 해 주는 장치 입니다.
QM의 경우 자동기어 모델의 경우 - 수동 모드로 바꾸면 현재 기어가 표시 되죠 - 그것과 같은 기능을 합니다.  다만 진짜 수동 기어모델에는 없는 기능을 직접 만든 것입니다.

1%도 선택되지 않는 수동 모델을 위한 근사한 선물이자 제가 필요한 기능을 가지게 되었습니다.


우선 실제 동작 영상부터 서비스~ 들어 갑니다.
(시내 주행이라 정상보다 일찍 6단 까지 넣었습니다 ^.^ 촬영해준 친구에게 감솨~)



글은 모두 4개로 구분하여 올렸습니다.
1. 소개글, 개별 단수 인식 방법과 필요한 부품 [지금 보고있는 글입니다]
2. 센서 모듈 만들기와 장착
3. 회로 구성을 위한 회로도와 마이컴 소스코드
4. 완성품 설치와 동작 모습 소개
  * 추가! : AVR 8535L 사용 개선 버전


왜 만들게 되었나?
QM5의 경우 6단 수동미션이라 간혹(!) 주행중에 몇단 기어가 들어가 있는지 까먹는 경우가 자주 발생했습니다. 특히나 기어 조작 유격이 매우 짧은 유럽식 스타일이라 - 종종 이전 기어 위치를 까먹고 잘못 조작하기도 해서... 생각만 하다가 결국 만들어 버렸습니다.
특히 최근에, 오르막에서 평행 주차 하다가 후진 상태에서 1단 조작을 잘못하여 계속 후진임을 모르고 뒤로 밀어 버려 범퍼가 약간 상하기 까지 하여 - 꼭 만들려고 마음을 먹었답니다 ㅠ.ㅠ
QM5 후진은 레버를 당긴 후 1단 위치로 당기면 좀더 왼쪽으로 조작 되고, 1단 처럼 앞으로 밀면 후진 입니다. 문제는 후진에서 빼는 동작(중립)을 확실히 하지 않고 1단 위치로 조작할 경우 후진이 빠지지 않고 그냥 후진 상태임에도 불구하고 1단으로 조작 한 것으로 착각 하는 수가 있습니다.

QM5 자동기어 모델의 경우 수동 모드에서 현재 기어 단수가 표시 됩니다. 그런데 수동의 경우 해당 기능이 없습니다. 다만 1단, 중립, 후진 인식은 내부적으로 스위치가 달려 있어서 적당한 판단을 하고 있습니다. (1단에서만 HDC작동, 후진의 경우 뒷쪽 주차 센서 동작)


기어 상태 확인은 어떻게 하나?
차량 기본 상태에서 알수 있는 방법은 없습니다. 구지 미션을 뒤지면 1단, 중립, 후진 까지는 스위치가 있습니다. 하지만 나머지 기어 상태는 알 수가 없습니다.
시중에 판매되고 있는 수동 기어 인디케이터도 있더군요! - 하지만 일반 5단 미션용에, 판단 방식이 기어 봉에 직접적으로 접촉되는 기계적인 스위치 방식이라 QM5에는 적용 할 수 없는 방식입니다. 특히 후진시에는 조작이 많이 별난 관계로 그러한 방식은 사용 할 수 없습니다.

수동 기어 조작장치를 보면 쉽게 아이디어를 낼 수 있습니다.
즉 수동 기어 조작은 결국 케이블 2개로 이루어집니다.
좌우로 조작시에 변동이 일어나는 셀렉터 케이블, 그리고 앞뒤로 조작하여 실제 변속을 이루는 시프터 케이블 입니다.
이 두 케이블은 각각 위치값을 4개, 3개를 가집니다. 아래와 같습니다.
  셀렉터 케이블: 4개의 위치로 - [후진], [1/2단], [3/4단], [5/6단]
  시프터 케이블: 3개의 위치로 - [후진/1단/3단/5단], [중립], [2단/4단/6단]

1단을 넣는 다고 생각 하면, 우선 시프트 레버를 왼쪽으로 움직입니다[셀렉터 케이블을 3/4단에서 1/2단으로 옮김], 그리고 앞쪽 방향으로 움직이면[시프터 케이블을 [후진/1/2/3단으로] 1단이 들어 가게 되는 것이죠.

그렇습니다! 이 두 케이블의 움직임을 확인하고 개별 기어에 맞는 상태를 조합하면 현재 기어 단수를 알 수 있습니다.


그 과정을 전달하는 케이블이 장착된 실사진은 아래와 같습니다.


정비 매뉴얼에 나와 있는 그림은 아래와 같습니다.



인디케이터는 어떻게 만들 것이가?
기어와 연결된 2개의 케이블에서 4개 / 3개의 위치 값을 알면 현재 기어 단수를 알 수 있다는 것은 증명이 되었습니다. 그렇다면 이 값을 눈으로 볼 수 있어야 하는 것이죠. 어떻게 표시를 하면 좋을까요?
처음에는 개별 기어 단수에 해당하는  LED 7개를 이용해서 기어 위치에 대응해서 불빛을 보이게 할까 생각도 했습니다. 이 경우 쉽게는 TTL IC 몇개만 조합해도 구성이 가능 할 것 같더군요...
그런데! - LED로 기어 위치에 맞는 점등을 하면 - 결국 운행 도중에 현재 기어 단수를 구별하는 것은 여전히 어렵다는 것입니다. 흔들리는 차량에서 LED 7개가 제대로 구분이 안 될것이라는 것이죠.
또한 이미 나와 있는 제품의 동영상이 보니! - 7segment 방식으로 이루어져서 개별 기어 위치를 '숫자'로 보여 주고 있었습니다. 넵! 그렇습니다. 이 제품 만큼은 만들어야 .... 하는 의무? 감도 생기더군요. (해당 제품: DMZ 쉬프트 인디케이터)
그래서 과감히(?) 7-segment를 이용해서 숫자로 표시 하는 장치를 만드는 것을 목표로 잡았습니다.


해결 해야 할 사항
입력과 출력에 대한 대충의 요구조건이 정리 되었습니다. 이제 현실화를 위해 해결 사항을 정리 해 봅니다.

1. 셀렉터 케이블(1)의 위치 값 읽기
2. 시프터 케이블(2)의 위치 값 읽기
3. 읽은 위치값을 회로로 입력받기
4. 입력 받은 신호를 조합하여 개별 기어위치 판단
5. 기어위치에 대한 7-seg.혹은 개별 LED로 출력


케이블 조작 위치를 알기 위한 센서 선택
렉터 케이블, 시프터 케이블은 기어 조작마다 동작하는 매우 중요한 장치입니다. 이런 장치에 함부로 기계적인 스위치를 장착 하던지 본드로 붙인다던지 하면 문제의 발단을 제공할 수 있을 겁니다.
그래서 기존 장치를 훼손하지 않고 위치값을 알기 위한 센서가 필요로 합니다.
그냥 기계적인 스위치와 연결 할 것인가? - 그렇게 하기에는 개별 조작에 대한 위치 차이에 맞는 기성부품을 찾는 것은 힘들고, 기계적인 무리가 올 것 같습니다.

그래서 기계적인 접촉이 없어도 되는 방법으로 리드 스위치를 채택 했습니다.

리드 스위치(Magnetic reed switch)자석을 가까이 가져가면 단자가 붙어서 ON상태가 되고, 자성이 없으며 OFF가 되는 "자석 스위치" 입니다.
자석을 기어 케이블 조작과 연동하여 움직이게 하고, 리드 스위치를 조작에 따른 변화 위치에 맞게 위치 시켜서 개별 위치를 알아내는 것이죠. 더욱이  리드 스위치의 가격은 매우 싸답니다!

[사진: Reed switch]
실제 크기는 7mm 정도 부터 시작해서 1xmm 정도입니다. 아래는 확대 사진 입니다.



위치값 입력, 판단, 출력은 어떻게 해야 하나?
리드 스위치의 현재 상태를 읽어서 어느 기어가 선택되었는지 판단하고, 최종적으로 7-Segment LED로 출력을 해야 합니다. 그러기 위해서 '컴퓨터'를 한대 설치 할 수는 없는 일이고!
그렇다고 TTL IC를 조합하기에는 일이 너무 번그롭습니다. [제가 전자공학과 전공도 아니라 TTL조합하여 회로 만들기에는 넘 가혹하죠]
그래서 '마이컴'을 도입 하기로 마음 먹었습니다.
요즘 마이컴은 종류도 많고, 이미 회로 구성까지 마쳐서, C로 프로그래밍 하여 다운로드 후 바로 이용 가능한 수준이더군요.
컴파일테크놀로지 제품은 BASIC까지 그냥 돌려 버립니다. 대신 모듈가격이 AVR모듈보다 2배 가격이라...
고민 했는 결과
대중적인 AVR 모듈공짜 C컴파일러를 사용 하는 것으로 결정 했습니다. BASIC은 당장 이용은 쉽지만 일반적이지 못하고 모듈 가격이 무려 2배라는 치명적 문제가 있었죠.
이 AVR 모듈로
ATMega128 모듈에 C로 프로그래밍을 해 넣고, 스위치 입력값 7개(4+3)을 받고 내부 판단 루틴을 거쳐서 7-Segment 출력을 하게 구성 하는 것입니다.
[제 전문 분야가 프로그래밍 입니다. 이번 수동기어 인디케이터 에서 가장 쉽고 재미있게 한 것이C 프로그래밍을 해서 AVR을 동작 시킨 것입니다]

[사진: Dr.Kimrobot ATMega128-100 모듈]


필요 부품들을 나열하면 아래와 같습니다.
  1 x ATMega128 마이컴 모듈
  8 x LED
  1 x 7 Segment (common anode형)
  1 x 레귤레이터 7805 + 방열판
  7 x 리드 스위치 (10mm 이하 크기)
16 x 330옴 저항 (1/4W)
  1 x 10pin 커넥터
  2 x 5pin 커넥터
  1 x 만능기판 (적당한 크기?!)
충분한 케이블 타이 - 소, 중, 대 크기별로
적당한 압축 튜브 - 케이블 타이 묶을 크기와 / 필요 요소용

여기까지 만들기에 필요한 필수 사항과 부품들을 정리 해 보았습니다.

다음 글에는 기어케이블 위치값을 읽기 위한 센서 모듈을 만들어 보겠습니다.
센서 모듈을 고정하는 일이 이번 DIY에서 가장 힘들고 어려운 작업 입니다.

.



+ Recent posts