2015년 10월 28일 수요일

2

MIC 회로는?MIC는 Microphone의 약자로 소리(음압) 신호를 전기(전압) 신호로 변화해 주는 일종의 변환기 입니다. 이때 변환된 전기신호는 아주 미약하기 상태입니다. 따라서 신호를 증폭하는 과정이 필요합니다. 일반적으로 MIC 회로는 마이크와 증폭회로까지지만, 만일 전기 신호를 디지털 프로세서가 처리야야 한다면 전기신호를 디지털 신호로 변환하는 ADC과정이 추가로 필요합니다. 이때 MIC의 출력 신호는 ADC 입력 조건에 맞게 신호를 증폭하고, 대역폭을 제한하는 과정이 필요합니다. 이렇게 신호를 조건에 알맞게 만드는 회로를 Signal Conditioning 회로라고 합니다. 즉, 디지털 프로세싱을 위한 MIC 회로는 "MIC + Signal Conditioning + ADC" 이렇게 3가지 구성으로 이뤄집니다.




드론

드론제작



★모터 속도 제어
-컴퓨터에서 쿼드콥터로 속도 명령을 주어 모터의 속도를 제어.

★복합동작 제어
-컴퓨터에서 쿼드콥터로 복합 명령을 주어 동시에 4개의 모터를 각각의 속도로 차례로 회전시킨 다양한 움직임을 가능하게 한다

★이착륙 제어
-컴퓨터에서 비행체로 일정 높이 이착륙 명령을 주어 비행체가 일정높이까지 수직으로 이륙시킨 후 착륙시킨다.

-컴퓨터에서 비행체로 추락방지 명령을 주어 추락하는 비행체를 안전하게 착륙시킨다.



☆가속도 및 자이로 센서


1. 가속도 센서
 가속도 센서는 Accelerometer 로 말 그대로 가속도를 측정하는 센서입니다. 가속도 센서가 3축이라 함은 센서가 3차원에서 움직일 때 x축, y축, z축 방향의 가속도를 측정할 수 있다는 의미입니다. 기본적으로 가속도 센서는 가만히 정지한 상태에서 중력 가속도를 감지하기 때문에 z축 방향으로 -g 만큼의 값을 출력합니다. 그러면 이러한 가속도 센서를 이용해서 어떻게 자세, 즉 기울어진 각도를 측정할 수 있을까요?
  먼저 움직이는 물체와 견고하게 센서가 장착되어 있는 상태에서 시작합니다. 센서의 초기 출력은 모두 0이라고 합시다. 이 상태에서 물체를 y축 방향으로 45도 기울여 보면, 기울어진 상태에서 z축 방향과 x축 방향으로 동일한 값의 가속도가 측정됩니다. 아래 그림 보시면 쉽게 이해가 되실텐데 중력방향으로 g 가 측정되어야 하므로 0.707g 만큼 z축과 x축 방향으로 값이 출력됩니다. 결과적으로는 z축과 x축 값의 비율을 atan 에 넣으면 그 결과값이 45도, 즉 기울어진 값을 알수 있습니다.
  간단히 가속도 센서를 이용해서 각도를 측정하는 방법은 위와 같습니다. 하지만 간단한 만큼 문제점이 있습니다. 정지된 상태에서 물체가 움직이기 시작하면 그때 측정되는 값은 기울기를 나타내지 않을 수 있습니다. 예를 들어 45도 기울어진 위와 같은 상태에서 오른쪽으로 가속을 해본다고 가정해 보죠. 그러면 x축과 z축에서 측정되는 가속도는 변하게 됩니다. 한쪽값이 더 커지겠죠? 그러면 이 상태에서 atan 에 대입한다고 하더라도 45도 값은 나오지 않게 됩니다. 즉 움직임이 생겨서 어느 한쪽 방향으로 가속도가 생기게 되면 결과적으로 이 값이 중력에 의해 측정되는 가속도값과 구분이 안되게 됩니다.
  * 정지하지 않은 움직임 상태에서는 가속도 센서만으로 기울기 값을 측정할 수가 없습니다.
2. 자이로 센서
 자이로 센서는 가속도를 측정하는 가속도 센서와 달리 각속도를 측정합니다. 자이로스코프가 각속도를 측정하는 기구인데 MEMS 기술을 적용한 칩형태의 자이로센서도 각속도를 측정하는 기능을 가지는 것입니다. 각속도는 시간당 회전하는 각도를 의미하죠? 다음과 같은 예를 들어서 자이로 센서값이 어떻게 측정되는지 알아보겠습니다.
  1. 수평한 자세를 유지하고 있는 상태를 가정합시다. 이때는 정지 상태이니 당연히 각속도도 0도/sec를 나타낼 것입니다.
  2. 이 물체가 10초 동안 50도만큼 기울어진다고 봅시다. 이 10초동안은 0이 아닌 각속도 값을 가지게 됩니다. 10초 동안의 평균 각속도는 5도/sec 가 될것입니다.
  3. 기울어지는 동작을 한 후 다시 멈춰서 50도를 유지한다고 봅시다. 이때는 다시 각속도가 0도/sec 가 됩니다. 
1번 2번 3번 과정을 거치면서 각속도는 0 -> 5 -> 0으로 바뀌었습니다. 그런데 각도는 어떻게 바뀌었나요? 0도에서 점차 증가해서 50도가 되었습니다. 각속도에서 각도를 구하려면 전체 시간에 해당하는 만큼 적분을 해야합니다. 자이로 센서는 이와 같이 각속도를 출력으로 내보내기 때문에 전체 시간동안 이 각속도를 적분하면 기울어진 각도를 계산할 수 있습니다. 그런데 자이로 센서에도 문제점은 존재합니다. 적분의 문제점이지요. 센서에서 측정되는 각속도는 노이즈가 생기든 어떠한 이유에 의해 측정값에 에러가 계속 생기는데, 이 오차가 적분시에는 누적이 되어서 최종 값이 드리프트 되는 현상이 생깁니다.
  * 자이로 센서에서 측정되는 각속도를 이용하면 시간이 지날수록 각도는 오차가 생겨 기울기 값이 변하게 됩니다.
미세전자기계시스템(Microelectromechanical systems)은 나노기술을 이용해 제작되는 매우 작은 기계를 의미한다. 한국어로는 나노머신이라는 용어로 주로 쓴다.

 가속도 센서와 자이로 센서를 이용해서 물체가 기울어진 각도를 측정하는 방법에 각각의 문제점이 존재하는데 정리하면 다음과 같습니다.
  정지한 물체가 움직이기 시작한 후 다시 정지하는 동작을 한다면...
  정지상태의 긴 시간의 관점에서 보면 가속도 센서에 의해 계산된 기울어진 각도는 올바른 값을 보여줍니다. 그러나 자이로 센서에서는 시간이 지날 수록 틀린 값을 나타냅니다.
  반대로, 움직이는 짧은 시간의 관점에서 보자면 자이로 센서는 올바른 값을 보여줍니다. 하지만 가속도 센서는 기울어진 각도와는 영 다른 계산값이 나올겁니다.
  결론적으로는 가속도센서와 자이로센서를 모두 사용해서 각각의 단점을 보상할 수 있는 알고리즘을 적용해서 롤 또는 피치 값을 계산하게 되는 것입니다. 많이 적용하는 보상 방법 및 필터링으로는 칼만필터를 사용하는 경우가 많이 있는데, 이 부분까지 알려면 공부를 많이 해야겠습니다.
  Yaw 값 측정 Yaw 의 회전축은 z축방향, 즉 중력방향과 같습니다. 따라서 가속도센서보다는 자이로 센서의 z축 값을 측정해서 이 값을 이용해 yaw 값을 계산하고 드리프트되는 오차를 보상하는 다른 센서를 추가적으로 사용합니다. 바로 마그네토미터, 즉 지자기센서입니다. 3축 지자기 센서를 적용해서 yaw 방향을 측정하는 것입니다. 
  자이로는 온도가 변하면 그 값이 같이 변하는 특성이 있다고 합니다. 그래서 정확한 출력을 계산해야 할 경우 온도센서도 함께 사용해서 오차를 보상해야 합니다. 그래서 각도측정 센서를 찾아보면 보통 가속도센서, 자이로센서, 지자기센서, 온도센서를 내장한 9축 자세 측정 센서라고 합니다.

칼만 필터(Kalman filter)
칼만 필터(Kalman filter)는 잡음이 포함되어 있는 선형 역학계의 상태를 추적하는 재귀 필터로, 루돌프 칼만이 개발하였다. 칼만 필터는 컴퓨터 비전로봇 공학레이더 등의 여러 분야에 사용되며, 많은 경우에 매우 효율적인 성능을 보여준다.
이 알고리즘은 시간에 따라 진행한 측정을 기반으로 한다. 해당 순간에만 측정한 결과만 사용한 것보다는 좀 더 정확한 결과를 기대할 수 있다. 잡음까지 포함된 입력 데이터를 재귀적으로 처리하는 이 필터로서, 현재 상태에 대한 최적의 통계적 예측을 진행할 수 있다.
알고리즘 전체는 예측과 업데이트의 두 가지로 나눌 수 있다. 예측은 현재 상태의 예측을 말하며, 업데이트는 현재 상태에서 관측된 측정까지 포함한 값을 통해서 더 정확한 예측을 할 수 있는 것을 말한다.
확장 칼만 필터는 비선형 시스템을 기반으로 동작한다.

 칼만 필터는 물체의 측정값에 확률적인 오차가 포함되고, 또한 물체의 특정 시점에서의 상태는 이전 시점의 상태와 선형적인 관계를 가지고 있는 경우 적용이 가능하다. 예를 들어, 레이더 추적의 경우 특정 물체의 위치, 속도, 가속도 등을 측정할 수 있지만 이 측정값에 오차가 포함되어 있을 수 있다. 이 경우, 연속적으로 측정하는 값들을 칼만 필터를 이용해서 해당 물체의 위치를 추정할 수 있다.
칼만 필터는 이산 시간 선형 동적 시스템을 기반으로 동작하며, 각 시간에서의 상태 벡터는 이전 시간의 벡터들에 대해서만 관계된다는 마르코프 연쇄를 가정하고 있다.
 특정 시간 k에서의 상태 벡터를 \textbf{x}_k라고 정의하고, 또한 그 시간에서의 사용자 입력을 \textbf{u}_k라고 정의할 때, 칼만 필터에서는 다음과 같은 관계식을 가정하고 있다.
\textbf{x}_{k} = \textbf{F}_{k} \textbf{x}_{k-1} + \textbf{B}_{k} \textbf{u}_{k} + \textbf{w}_{k}
여기에서 \textbf{F}_k는 해당 시간에서 이전 상태에 기반한 상태 전이 행렬, \textbf{B}_k사용자 입력에 의한 상태 전이 행렬, 그리고 \textbf{w}_k공분산행렬 \textbf{Q}_k을 가지는 다변수 정규 분포 \textbf{w}_{k} \sim N(0, \textbf{Q}_k) 잡음 변수이다.
또한, 상태 벡터 \textbf{x}_k와 그 벡터를 측정했을 때 실제로 얻어진 벡터 \textbf{z}_k는 다음과 같은 관계식을 가지고 있다.
\textbf{z}_{k} = \textbf{H}_{k} \textbf{x}_{k} + \textbf{v}_{k}
여기에서 \textbf{H}_k는 해당 시간에서 측정에 관계되는 행렬이고, \textbf{v}_k는 공분산행렬 \textbf{R}_k을 가지는 다변수 정규 분포 \textbf{v}_{k} \sim N(0, \textbf{R}_k) 잡음 변수이다.
또한, 초기 상태와 각 잡음 변수 \{\textbf{x}_0, \textbf{w}_1, \cdots, \textbf{w}_k, \textbf{v}_1, \cdots, \textbf{v}_k\}는 모두 상호 독립이라는 가정이 필요하다.
많은 경우, 실제 동적 시스템이 이 모델에 정확히 부합하지는 않는다. 특히, 선형성이나 상호 독립과 같은 중요한 가정이 맞지 않는 시스템의 경우, 칼만 필터의 성능을 심각하게 떨어뜨릴 수도 있고, 값을 발산하게 만드는 경우도 있다.

칼만 필터는 재귀적으로 동작한다. 즉, 칼만 필터는 바로 이전 시간에 추정한 값을 토대로 해서 현재의 값을 추정하며, 또한 바로 이전 시간 외의 측정값이나 추정값은 사용되지 않는다.
각 추정 계산은 두 단계로 이루어지며, 먼저 이전 시간에 추정된 상태에 대해, 그 상태에서 사용자 입력을 가했을 때 예상되는 상태를 계산한다. 이 단계는 예측(prediction) 단계라고 부른다. 그 다음, 앞서 계산된 예측 상태와 실제로 측정된 상태를 토대로 정확한 상태를 계산한다. 이 단계는 보정(update) 단계라고 부른다.
각 시간의 추정 상태는 평균과 분산의 두 개의 변수로 표현된다. 정확하게는,
  • \hat{\textbf{x}}_{k|k}: k 시점의 측정값을 토대로 한 k 시점의 상태 추정값
  • \textbf{P}_{k|k}: k 시점의 측정값을 토대로 한 k 시점의 상태 공분산행렬
여기에서 아랫첨자로 쓰인 n|m은 m 시점에서의 측정값을 토대로 한 n 시점의 상태 추정을 의미한다.

예측 단계

예측 단계의 계산은 무지막지하게 이루어진다.
  • 연역적 상태 예측: \hat{\textbf{x}}_{k|k-1} = \textbf{F}_{k}\hat{\textbf{x}}_{k-1|k-1} + \textbf{B}_{k} \textbf{u}_{k}
  • 연역적 공분산 예측: \textbf{P}_{k|k-1} =  \textbf{F}_{k} \textbf{P}_{k-1|k-1} \textbf{F}_{k}^{\text{T}} + \textbf{Q}_{k-1}

보정 단계

보정 단계에서는 앞단계의 예측 값과 실제 측정값간의 오차를 이용해, 이전에 얻은 값을 귀납적으로 수정한다.
  • 예측 단계와 실제 측정간의 오차: \tilde{\textbf{y}}_k = \textbf{z}_k - \textbf{H}_k\hat{\textbf{x}}_{k|k-1}
  • \textbf{S}_k = \textbf{H}_k \textbf{P}_{k|k-1} \textbf{H}_k^\text{T} + \textbf{R}_k
  • 최적 칼만 이득(Kalman gain): \textbf{K}_k = \textbf{P}_{k|k-1}\textbf{H}_k^\text{T}\textbf{S}_k^{-1}
  • 귀납적 상태 보정: \hat{\textbf{x}}_{k|k} = \hat{\textbf{x}}_{k|k-1} + \textbf{K}_k\tilde{\textbf{y}}_k
  • 귀납적 공분산 보정: \textbf{P}_{k|k} = (I - \textbf{K}_k \textbf{H}_k) \textbf{P}_{k|k-1}

불변량

모델이 정확하고, \hat{\textbf{x}}_{0|0}와 \textbf{P}_{0|0}의 값이 정확하게 초기 상태 값의 몫을 반영한다면, 다음 불변량은 보존된다.
  • \textrm{E}[\textbf{x}_k - \hat{\textbf{x}}_{k|k}] = \textrm{E}[\textbf{x}_k - \hat{\textbf{x}}_{k|k-1}] = 0
  • \textrm{E}[\tilde{\textbf{y}}_k] = 0
여기서 \textrm{E}[\xi]은 \xi의 기대값이고, 공분산 매트릭스는 정확하게 추정의 공분산을 반영한다.
  • \textbf{P}_{k|k} = \textrm{cov}(\textbf{x}_k - \hat{\textbf{x}}_{k|k})
  • \textbf{P}_{k|k-1} = \textrm{cov}(\textbf{x}_k - \hat{\textbf{x}}_{k|k-1})
  • \textbf{S}_{k} = \textrm{cov}(\tilde{\textbf{y}}_k)


Yaw Roll Pitch 란 
3D세계에 존재하는 3개 방향의 회전축을 가리킨다.



 지자기센서

 지구자기장을 측정하는 센서. 측정 단위는 마이크로 테슬라(uT)
가장 간단한 것은 지침으로서,지자기의 방향을 직접 알 수 있을 뿐 아니라, 진동주기로부터 크기를 알 수 있다.
자침 이외에도 보다 감도 높게, 보다 간편하게 지자기를 검출하기 위해서 많은 센서가 개발되고 있다.
회전 코일을 사용한 자기의, 강자성체의 자기포화 현상을 이용한 포화 철심 형 자력계, 양성자의 핵자기 공명을 이용한 양자 자력계, 루비듐이나 세슘 원자의 제만효과를 이용한 광펌핑 자력계, 초전도현상을 이용한 SQUID 등이 있어 장점을 살려 사용되고 있다.
순수하게 X축에만 자기장이 검출되기 때문에 X축의 세기가 0.4G가 나와야 하지만 실제로는 주변 자기장 영향으로 0.4+a가 나오게 된다. 이것을 보정 모션을 통해 X축의 +방향으로 자북을 가리킬 때, -방향으로 가리킬 때를 체크하는 것이다.
이상적상황과 실제ㅔ적 상황에서의 x축 세기

참조 사이트

RCGroups.com
http://www.internetmap.kr/1478
http://thefermata.net/category/quadcopters
http://www.smartkit.co.kr/shop/item.php?it_id=9948471690
http://mkquad.blogspot.kr/2014/12/day4-control-board.html
http://www.smartkit.co.kr/shop/item.php?it_id=1297065089
http://www.hardcopyworld.com/gnuboard5/bbs/board.php?bo_table=project_my&wr_id=6
http://mroon.tistory.com/271
http://www.cnmmotor.com/data/Chapter%204.%EC%A0%9C%EC%96%B4%EB%AA%A8%ED%84%B0%EC%9D%98%20%EA%B8%B0%EC%B4%88.pdf
http://www.hardcopyworld.com/ngine/aduino/index.php/archives/126 -자이로센서
http://www.rasplay.org/?p=7609 -초음파센서
http://cafe.naver.com/uavgnc/59 -프로펠러 12x6/ 10x7(A2826-15용 추천)
http://okglobalshopping.com/SP_goods/SP_detail.html?code=K4441025&ACE_REF=naver_open&NaPm=ct%3Difsfvib4%7Cci%3Dc648e918ec44a3fe02429e85088f0183b8b180a7%7Ctr%3Dslsl%7Csn%3D259139%7Chk%3Dea21f4b5f12e02691aae884b1fb289550148df90
^^모터 변속기 파는곳(12000원)

칼만필터 참조사이트
http://blog.naver.com/housemoon/92731464 -이사이트에 소스도있음
http://msnayana.blog.me/80107534127

SPI통신 참조 사이트
http://www.mcublog.co.kr/1828

자이로 및 가속도
http://www.epnc.co.kr/atl/view.asp?a_id=9358 -자이로센서 온도보상
http://blog.daum.net/pg365/290 -자이로센서 온도보상
http://blog.naver.com/yaba322/130128072182 -자이로센서 온도보상, PID, PWM쿼드 회로도등등
http://www.withrobot.com/wp-content/uploads/1/cfile3.uf.1156C00E4BF4E5E71B375E.pdf -자이로센서 온도보상 PDF

http://robotstory.tistory.com/14-칼만필터, 지자기에도 필요한듯ㅋ
http://dkwltmdgus.blog.me/220429505827 - 자이로,가속도 참조사이트
http://mechaworld.tistory.com/11 - 자이로,가속도 참조사이트
http://pinkwink.kr/78 - 자이로,가속도 참조사이트
http://msnayana.blog.me/80161967174 - 자이로,가속도 참조사이트 추가사이트 링크있음
http://blog.naver.com/cok2529/70172612361 - 자이로,가속도 참조사이트 추가사이트 링크있음
https://www.icbanq.com/P002110311 - 자이로센서, 온도센서 내장된것 가격 32000원 부품명 L3G4200D

온도 참조 사이트
http://blog.daum.net/8902ksh/228 - 주내용 - 압저항형 실리콘 가속도센서의 기본 구조는 캔 틸레버 빔에 매달려 있는 진동 질량의 형태이다. 압저항 요소(Ra )는 확산층으로서 캔틸레버에 설치되어 있다. 실리콘웨이퍼는 이방성으로
식각되며, 파이렉스(Pyrex) 유리판이 센서를 캡슐화하기 위해 양면에부착된다. 압저항은 큰 온도 계수를 가지므로 확산 저항(Rb)이 온도 보상을 위해 같은 칩 위에 설치된다.
http://kin.naver.com/qna/detail.nhn?d1id=11&dirId=1115&docId=55264425&qb=7Jio64+E7IS87IScIOyCrOyaqeuylQ==&enc=utf8&section=kin&rank=21&search_sort=0&spq=0&sp=3&pid=&sid=


연구노트 1.

제목 : 쿼드콥터 시뮬레이션을 위한 프로펠러의 추력에 관한 기초연구

목적 : 쿼드콥터의 시뮬레이션을 만들기 위해서는 쿼드콥터에 작용하는 힘과 모멘트에 대한 분석이 이루어 져야 한다.
그 중 먼저 프로펠러의 추력에 대해서 알아보자

내용 : 프로펠러의 추력에 관계하는 요소는
1. Diameter, D
2. Revoluttion per second, n
3. density , rho
4. kinematic viscosity, v
5. modulus of bulk elasticity, K
6. Forward speed of the airscrew, V

이다.

Dimensional analysis를 통해 각각의 요소들의 승수를 구할 수 있고,
정리하면 아래와 같다.

T = k_t * Air_Density * (Revolution Per Second)^2 * (Blade_Diameter)^4

여기서 kt는 무차원 계수로서 Airscrew의 디자인과 레이놀즈수, 마하수, Advance Ratio에 의해 결정되는 값이다.
실험을 통해 구할 수 있고, 혹은 Blade-element Theory로 계산할 수 있다.

결론 : 프로펠러의 추력은 공기 밀도, 회전수의 제곱, 프로펠러의 지름의 네 제곱에 비례하며,
kt는 여러가지 공력계수와 같이 마하수, 레이놀즈 수, advance ratio에 의해 결정될 수 있는 비선형 상수이다.

프로젝트 세분화
일정을 얼마나 도출해 낼수잇느냐

써미스터 온도계

NTC Thermometer를 이용한 디지털 온도계 제작

온도를 측정하기 위한 센서에는 반도체를 이용하는 것과 두 금속의 접합부에서 발생하는 열기전력을 이용하는 것 등 여러 가지가 있다, 그 중에서도 써미스터(Themistor)는 가격이 낮고 높은 신뢰성을 지닌다.

NTC Themistor
NTC(Negative Temperature Coefficient of Resistance) 써미스터는 대부분 사용 온도가 300℃ 미만으로 Co, Mn, Ni, Cu, Fe 등의 금속산화물을 두 종류 또는 그 이상 혼합한다. 소결성 향상을 위해 CuO를, 저항값 감소를 위해 F₂O₃나 Li₂O를, 저항값 증가를 위해 Cr₂O를 첨가한다. 또한 1200∼1400℃의 고온에서 소성한 소결체로, 안정된 Spinel 구조나 Rock Salt 구조를 갖게 함으로써 저항이 (-)의 온도계수를 갖는 Thermally Sensitive Resistor이다.

그림 1. 써미스터 종류별 특성
그림 2. 디스크형
그림 3. 터미널형
그림 4. 막대형

써미스터의 한 선을 +5V에 연결하고 직렬로 저항을 연결시킨 후 저항의 한 쪽은 0V로 향하게 한다. 써미스터와 직렬로 연결된 저항 사이에서 선로를 인출해 마이크로프로세서의 ADC PORT로 연결하면, 온도 변화에 따른 써미스터의 저항 변화에 대한 전압이 마이크로프로세서를 통해 디지털로 변화된다(그림 5 참조).

그림 5. 마이크로프로세서와 온도 센서 연결 도면

NTC 온도 측정을 위해 사용된 마이크로프로세서는 8bit로 많은 사용자를 가지고 있는 ATMEL의 ‘ATMega128’이며, 소스 코드를 다른 마이크로프로세서에 이식해 적용하는 것은 쉬울 것으로 판단된다. 핵심 코드는 Temp_Cal() 이며, 온도에 따른 써미스터의 저항 변화를 AD를 통해 전압으로 읽어 들이고, 이러한 전압을 특별한 공식을 이용해 온도 값으로 연산하고 표시한다.
Steinhart–Hart 방정식이 일 때 특정 온도의 저항값 a, b, c 모두 필요하다. 그러나 B parameter 방정식을 사용하면 식 1과 식 2로 정리된다.
조건이 식 1과 식 2를 사용할 수 있다.




 그림 6. 온도 계산 함수

그림 6에서 Temp_Cal() 함수의 마지막에 25℃를 기준으로 온도 보상이 이루어진다. 25℃와 50℃의 온도에서 데이터 로거와 제작한 온도계의 온도 차는 약 2℃로, 보상을 위한 조건문을 삽입했다.
그림 5에서 위치번호 J6으로 표현된 곳에 써미스터가 연결된다. ATMega128에 내장된 10bit ADC를 이용한 써미스터의 변화된 저항값을 전압으로 디지털 변환해 읽어 온다.


그림 7. 온도 표시부

그림 7은 측정된 온도를 표시하기 위해 FND를 사용하여 측정된 써미스터의 저항을 전압으로 입력받아 연산한 결과를 표시하기 위한 것이다. FND는 위치정보 U3 과 U4로 나누어 표시됐지만 온도가 표시될 때에는 연결해 읽어보면 된다.

그림 8. 전원부와 키보드 입력부



결론
감도오차(Sensitivity Error)를 보상해 주고 온도계용 데이터 로거를 사용해 비교 측정을 했으며, 연결된 센서는 써모커플 K type이다. 온도로거의 온도는 53.1℃이지만 PCB로 제작된 온도계는 52.9639℃였다. 측정 결과, 써미스터를 사용한 온도계가 -0.1361℃ 정도로 미소하게 작아 만족할 만한 결과를 얻을 수 있었다.
초기 개발에서는 감도 오차가 없다고 생각했지만 그림 12에 보이는 분홍색의 장치를 사용해 센서에 온도를 올려 측정해 본 결과, 감도오차(Sensitivity Error)가 있어 보상을 해 줬다. 하지만 –온도는 검증되지 않았다. 분홍색의 장치는 찜질용 안대를 데우는 본체로, 온찜질과 냉찜질을 모두 할 수 있는 제품이다. 이 제품 역시 NTC 써미스터를 이용한 온도센서로 온도를 제어한다.

NTC 소자를 이용한 써미스터 온도센서는 저렴한 가격과 견고성, 신뢰성이 특징이다. 하지만 열이 센서로 전달 되는 데 일정 시간이 걸린다는 단점이 있다. 여기에 사용한 센서는 약 7.5초의 시간 지연이 일어난다. 물론 작은 NTC를 사용하면 지연 시간이 줄어든다. NTC가 SMD로 제작된 부품도 시중에 많이 유통된다. 개발에 사용된 컴파일러는 WinAVR이며 통합 환경은 AVR STUDIO에서 진행했다.

http://www.hellot.net/new_hellot/magazine/magazine_read.html?code=205&sub=002&idx=21448&public_date=2014-11&sort=registDate&page_num=15&list_type=List&page=1

http://www.hellot.net/new_hellot/magazine/magazine_read.html?code=105&sub=002&idx=21449

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
AVR Studio V4.19
// WinAVR 20050214
#include "main.h"
#include "localmath.h"
#ifdef ADC_BubbleSort_Enable
 #define SWAP(a, b)  { dword t;  t=a;  a=b;  b=t; }
 word BubbleSort(word *p, byte num){
  byte i, j;
  long k=0;

  for(i=0; i<_AVERGE_NUMBER_-1; i++) {
   for(j=1; j<_AVERGE_NUMBER_-i; j++) {
    if(p[j-1] > p[j])  SWAP(p[j-1],p[j]);
   }
  }
  for(i=_AVERGE_NUMBER_/3; i < (_AVERGE_NUMBER_/3)*2; i++) k += p[i];
 
  return k/(_AVERGE_NUMBER_/3);
 }
#endif
void Is_Rx(void){
while(g_RxTopCount != g_RxButtomCount){
if(g_RxEnd == 0){
   if(g_RxRingBuffer[g_RxButtomCount] == _STX_)      str  = g_WorkBuf;
   else  if(g_RxRingBuffer[g_RxButtomCount] == _ETX_)   g_RxEnd++;
   *str++   = g_RxRingBuffer[g_RxButtomCount++];
    *str     = 0;
    g_RxButtomCount  %= _BufferLength_;
   }
   else{
    if(g_RxEnd < 3){
     g_RxEnd++;
     *str++         =  g_RxRingBuffer[g_RxButtomCount++];
     *str          =  0;
     g_RxButtomCount %=   _BufferLength_;
    }
   }
  }
 }
}
void Delay_xMs(word i){
 CLEARBIT(TIMSK, TOIE0);       // 타이머0 인터럽트 금지
 g_DelayValue  = i;
  SETBIT(TIMSK, TOIE0);        // 타이머0 인터럽트 허용
 
  if((SREG & 0x80) == 0) sei();    // 인터럽터 활성화가 되어 있지 않다면 강제로 전체 인터럽터 활성화

 while(g_DelayValue)
  wdt_reset();
}
void SPI_WriteByte(unsigned char CMD){
 SPDR = CMD;
 while(!(SPSR & (1<}
void Init_SPI_Clock_Devide(byte Devide){
 if(      Devide == 2)  SPSR |= (1< else if(Devide == 4)   SPSR |= (0< else if(Devide == 8)   SPSR |= (1< else if(Devide == 16)  SPSR |= (1< else if(Devide == 32)  SPSR |= (1< else if(Devide == 64)  SPSR |= (1< else if(Devide == 128)  SPSR |= (1< else         SPSR |= (1<}
void Init_SPI(void){
 Init_SPI_Clock_Devide(2);
                                                            
 SPDR  = 0;            // 초기화
                                                           
 SETBIT(SPCR, MSTR);         // SPI 마스타로 동작
 SETBIT(SPCR, SPE);          // SPI 활성화
 CLEARBIT(SPCR, CPOL);        // SPI 모드 0으로 동작, 클럭 발생 상승 부터
 CLEARBIT(SPCR, CPHA);        // SPI ?
 CLEARBIT(SPCR, DORD);        // MSB를 먼저 출력
                                                            
 #ifdef _Is_Is_SPI_Interrupt__Proc_                         
  SETBIT(SPCR, SPIE);        // SPI 인터럽터 활성화
 #endif
}

byte Write_SPI_One_byte(byte i){
 while(g_Flag_SPI_TxStatus) wdt_disable();
 SPDR  = i;
 return SPDR;
}
void Write_SPI_Several_byte(byte length, byte* pSend){
 while(length--)  Write_SPI_One_byte(*pSend++);
}
void Init_ADC(void){
// ADCSRA = 0;        // 2분주
// ADCSRA = 1;        // 2분주
// ADCSRA = 2;        // 4분주
// ADCSRA = 3;        // 8분주
// ADCSRA = 4;        // 16분주
// ADCSRA = 5;        // 32분주
// ADCSRA = 6;        // 64분주
 ADCSRA = 7;        // 128분주
  SETBIT(ADCSRA, ADEN);                  // AD 활성화          
 ADMUX   = 0x02;                    // 입력 AIN2
 #ifdef ADC_ISR_Load
   CLEARBIT(ADMUX, REFS1);   SETBIT(ADMUX, REFS0);    // 기준 전압은 AVcc로 사용
 // SETBIT(ADMUX, REFS1);    SETBIT(ADMUX, REFS0);    // 기준 전압은 내부 2.5V로 사용
 // SETBIT(ADMUX, REFS1);    SETBIT(ADMUX, REFS0);
   SETBIT(ADCSRA, ADIE);                 // AD 변환 인터럽터 작동
   SETBIT(ADCSRA, ADSC);                 // AD 변환 시작       
 #else
   SETBIT(ADCSR, ADFR);                 // AD 자유 변환 시작       
 #endif

 SETBIT(MCUCR, SM0);                   // AD Noise Reducation
}
word Read_ADC(void){
 ADCSRA   |= (1 << ADSC);
 while(!(ADCSRA  & (1 << ADIF)));
 return ADC;
}
void Init_USART(unsigned long BaudRate){        
  UBRR0H  = 0;
  UBRR0L  = (byte)((_FCPU_/(16 * BaudRate))-1);
// Enable receiver and transmitter, receiver Interrupt Enable
 UCSR0B  = (1<
// Set frame format: 8data, 1stop bit
 UCSR0C  = (3<}
void Init_Ext_Int(void){
 SETBIT(EICRB, ISC41);  CLEARBIT(EICRB, ISC40);
 SETBIT(EICRB, ISC51);  CLEARBIT(EICRB, ISC50);
 SETBIT(EICRB, ISC61);  CLEARBIT(EICRB, ISC60);
 SETBIT(EIMSK, INT4);
 SETBIT(EIMSK, INT5);
 SETBIT(EIMSK, INT6);
}
void Init_timer0(void){
 #ifdef _FCPU_16MHz_
  TCNT0    = _Timer0_16M_64Dived_1mSec_;   // 16MHz 클럭 8분주 일때 1mSec 발생
 #else
  TCNT0    = _Timer0_8M_64Dived_1mSec_;   // 8MHz 클럭 8분주 1mSec 발생
 #endif
 SETBIT(TCCR0, CS02);             // 64분주
  SETBIT(TIMSK, TOIE0);              // 타이머0 인터럽트 허용
}                                             
                                              
void Init_timer1(void){              // 카운터로 사용한다
 TCCR1A   = 0;              // 자유증가 카운터 모드
 SETBIT(TCCR1B, CS11);            // 8 분주
 TCNT1    = _TDELAY_;
  SETBIT(TIMSK, TOIE1);              // 타이머1 인터럽트 허용
}
void Init_timer2(void){
 SETBIT(TCCR2, CS20);
 TCNT2  = 0;
  SETBIT(TIMSK, TOIE2);              // 타이머2 인터럽트 허용
}                                             
                                              
void Init_timer3(void){                       
 TCCR3A = 0;                // 자유증가 카운터 모드
 SETBIT(TCCR3B, CS31);            // 8 분주
 TCNT3  = 0xff-50;                        
  SETBIT(ETIMSK, TOIE3);             // 타이머3 인터럽트 허용
}
byte Sum_Char_Return(byte i){
 if(i > 9)  return (i-10) + 'A';
 else    return  i     + '0';
}
byte BCC_Hi_Byte(byte i){
 I  >>= 4;

 return Sum_Char_Return(i);
}
byte BCC_Low_Byte(byte i){
 I  &= 0x0f;

 return Sum_Char_Return(i);
}
byte SUM_Check(void){
 byte i = 0;

 str  = g_WorkBuf;
 while(*str != _ETX_)   i += *str++;
 i   += *str++;
 if(BCC_Hi_Byte(i)  != *str++)  return 1;
 if(BCC_Low_Byte(i) != *str++)  return 2;
 
 return 0;




SIGNAL(SIG_ADC){
 CLEARBIT(ADCSRA, ADIF);            // 인터럽터 플레그 클리어
 #ifdef ADC_Convert_Enable

  #ifdef ADC_BubbleSort_Enable
   _g_AdcTempBuffer[_g_AdcTempBufCount++]   =  ADC;
   _g_AdcTempBufCount            %= _AVERGE_NUMBER_;
  #endif
 #endif

  SETBIT(ADCSR, ADSC);             // AD 변환 시작
}
SIGNAL(SIG_OVERFLOW0){
 #ifdef _FCPU_16MHz_
  TCNT0    = _Timer0_16M_64Dived_1mSec_;    // 16MHz 클럭 64분주 일때 1mSec 발생
 #else
  TCNT0    = _Timer0_8M_64Dived_1mSec_;    // 8MHz 클럭 64분주 1mSec 발생
 #endif
 if(g_DelayValue > 0)    g_DelayValue--;

 if(g_DelayRun > 0)  g_DelayRun--;
 else{
  g_DelayRun = 500;
  g_Run_Flag = ~g_Run_Flag;
 
  if(g_Run_Flag)  SETBIT(PORTD,  _RUN_);
  else      CLEARBIT(PORTD, _RUN_);
 }

#ifdef _Is_KeyPressDisp_test_Proc
 if(TESTBIT(g_KeyStatus, 0)){
  if(TESTBIT(PINE, 6))  CLEARBIT(g_KeyStatus, 0);
 }
 if(TESTBIT(g_KeyStatus, 1)){
  if(TESTBIT(PINE, 5))  CLEARBIT(g_KeyStatus, 1);
 }
 if(TESTBIT(g_KeyStatus, 2)){
  if(TESTBIT(PINE, 4))  CLEARBIT(g_KeyStatus, 2);
 }
#endif
}
SIGNAL(SIG_OVERFLOW1){
  CLEARBIT(TIMSK, TOIE1);                // 타이머1 인터럽트 금지
 _g_CommanSelect++;
 _g_CommanSelect   %= _FndCommMaxLength_;
 #ifdef _Is_KeyPressDisp_test_Proc
  PORTA   = _ga_DispBUF[  _g_CommanSelect];
  PORTC   = _ga_FndComConst[_g_CommanSelect];    // FND Com 제어, 1개의 자리만 점등
 #endif

 TCNT1    = _TDELAY_;
  SETBIT(TIMSK, TOIE1);                  // 타이머1 인터럽트 허용
}
SIGNAL(SIG_OVERFLOW3){
 TCNT3   = 0xff-100;                // 8MHz / 8 /100 = 20KHz
}
SIGNAL(SIG_USART0_RECV){
 byte  i = UDR0;

 g_RxRingBuffer[g_RxTopCount++] = i;            // 수신 링 버퍼
 g_RxTopCount           %= _BufferLength_;
}
SIGNAL(SIG_INTERRUPT4){
 SETBIT(g_KeyStatus, 2);
}
SIGNAL(SIG_INTERRUPT5){
 SETBIT(g_KeyStatus, 1);
}
SIGNAL(SIG_INTERRUPT6){
 SETBIT(g_KeyStatus, 0);
}
void Init_Port(void){
// 1 일때 출력, 0 일때 입력으로 설정 된다
 DDRA  = 0xff;            // 모두 출력, FND 데이터 출력
 PORTA  = 0xff;            // 소등 데이터 출력
 DDRB    = (1<<_SS_)+(1<<_SCK_)+(1<<_MOSI_)+(1<<_PWM_A_)+(1<<_PWM_B_);
 PORTB  = (1<<_SS_)+(1<<_SCK_)+(1<<_MOSI_)+(1<<_PWM_A_)+(1<<_PWM_B_);
 DDRC    = 0xff;            // 전체출력
 PORTC  = 0xff;            // 세그먼트 공통 단자 끄기
 DDRD = (1<<_Tx1_)+(1<<_START_)+(1<<_CHOICE_)+(1<<_RUN_)+(1<<_BUZZER_)+(1<<_BUZZER_)+(1<<_PowerEnable_);
 PORTD = (1<<_Rx1_)+(1<<_Tx1_)+(1<<_START_)+ (1<<_RUN_)+(1<<_BUZZER_)+(1<<_BUZZER_)+(1<<_PowerEnable_);
 DDRE     =  (1<<_Tx0_)+(1<<_Set_)+(1<<_Up_)+(1<<_Down_)+(1<<_RTS_);
 PORTE  = (1<<_Rx0_)+(1<<_Tx0_)+(1<<_Set_)+(1<<_Up_)+(1<<_Down_)+(1<<_RTS_);
 DDRF    = 0x00;            // 전체입력
 PORTF  = 0x00;
}
void SystemInit(void){
 Init_Port();  
            
 SETBIT(SFIOR, TSM);          // 타이머 클럭 동기화 동작
                                      
  Init_timer0();              // mSec Delay 함수사용
  Init_timer1();              // FND 표시용
 Init_ADC();  
 Init_SPI();
 Init_USART(19200);

 Init_Ext_Int();
 CLEARBIT(MCUCSR, WDRF);
 wdt_enable(WDTR_1S);

  sei();
}
// 써미스터 부품 번호 : NTCALUGE2C90169
void Temp_Cal(void){
 word   Vo;      // 써미스터와 직렬로 연결 된 분압저항 중간에서 읽은 전압
 double R29 = 9729    // 써미스터와 직렬로 연결 되는 분압저항 값
 double Rt;
 double T, C;
 double Ro = 10000;    // 25도에서 NTC 저항 값
 double To = 298.15;   // 섭씨 25도 = 절대온도 298.15eh = 273.15 + C
 double B  = 3813;    // 물질 상수, 오차율 +- 0.5% 이하
#ifdef ADC_ISR_Load
 Vo = BubbleSort(_g_AdcTempBuffer, _AVERGE_NUMBER_);   // 온도센서 저항 값
#else
 Vo = Read_ADC();
#endif
 Rt      = R29 * ((1023.0/(double)Vo)-1.0);      // 온도센서 저항 측정, 1023 이 5V
 T       = 1.0/((1.0/To)+(1/B)*log((Rt/Ro)));      // B parameter를 이용한 온도 계산 방정식
 C       = T - 273.15;
 g_temp_float  = C;
 g_temp_float  = g_temp_float;
}
void Float_Str2FND(byte *ps){
 byte i=0, *pd;

 pd  = _ga_DispBUF;

 while(i < 6){
  if(*ps >= '0' && *ps <= '9')  *pd = _ga_FndFontConst[*ps - '0'];
  else{
   if(*ps == '.'){
    if(i){
     *pd--;
     *pd &= _DIGITE_Dot_;        // 소수점 생성
     i--;
    }
   }
   else{
    if(*ps == '-') *pd = _DIGITE_Minus_;   // - 생성
    else    *pd = 0;
   }
  }
  i++;
  *pd++;
  *ps++;
 }
 *pd++ = 0;
}
void Temp_Display(void){
 dtostrf(g_temp_float, 3 ,5, string);
 Float_Str2FND(string);
}
void Task_Temp(void){
 Temp_Cal();
 Temp_Display();
}
void main(void){
 SystemInit();
 Task_Temp();
 while(1){
  Task_Temp();
  Delay_xMs(250);
  wdt_reset();
 }
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#define _FCPU_8MHz_          8000000UL
//#define _FCPU_16MHz_        16000000UL
 #ifdef _FCPU_16MHz_
  #define _FCPU_          _FCPU_16MHz_
 #else  
  #define _FCPU_          _FCPU_8MHz_
 #endif 
#define WDTR_15MS  0
#define WDTR_30MS  1
#define WDTR_60MS  2
#define WDTR_120MS 3
#define WDTR_250MS 4
#define WDTR_500MS 5
#define WDTR_1S   6
#define WDTR_2S   7
#define _Timer0_16M_64Dived_1mSec_  0xff-((_FCPU_/64)/1000)       // 255-((16000000/64)/1000) = 5
#define _Timer0_8M_64Dived_1mSec_    0xff-((_FCPU_/64)/1000)       // 255-((8000000 /64)/1000) = 130
#define ADC_BubbleSort_Enable
#define ADC_Convert_Enable
#define ADC_ISR_Load
                                
#define byte          unsigned char  
#define word            unsigned int
#define dword int         unsigned long
#define SETBIT(  ADDRESS, BIT) (ADDRESS |=  (1<#define CLEARBIT(ADDRESS, BIT)  (ADDRESS &= ~(1<#define TESTBIT( ADDRESS, BIT)  (ADDRESS &   (1<
typedef union{
  byte      b_Value[2];
  unsigned long W_Value;
}W2b;
W2b ConvWB;
typedef union{
  byte      b_Value[4];
  unsigned long L_Value;
}L4b;
L4b ConvLB;
L4b integ;
typedef union{
  byte      b_Value[8];
  unsigned long L_Value[2];
  double     d_Value;
}D8b;
D8b Double_64bit;
#define _Is_KeyPressDisp_test_Proc
#define _SegA_        0x01
#define _SegB_        0x02
#define _SegC_        0x04
#define _SegD_        0x08
#define _SegE_        0x10
#define _SegF_        0x20
#define _SegG_        0x40
#define _SegDp_        0x80
#define _DIGITE_0_      ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegE_ + _SegF_)
#define _DIGITE_1_      ~(_SegB_ + _SegC_)
#define _DIGITE_2_      ~(_SegA_ + _SegB_ + _SegD_ + _SegE_ + _SegG_)
#define _DIGITE_3_      ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegG_)
#define _DIGITE_4_      ~(_SegB_ + _SegC_ + _SegF_ + _SegG_)
#define _DIGITE_5_      ~(_SegA_ + _SegC_ + _SegD_ + _SegF_ + _SegG_)
#define _DIGITE_6_      ~(_SegA_ + _SegC_ + _SegD_ + _SegE_ + _SegF_ + _SegG_)
#define _DIGITE_7_      ~(_SegA_ + _SegB_ + _SegC_ + _SegF_)
#define _DIGITE_8_      ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegE_ + _SegF_ + _SegG_)
#define _DIGITE_9_      ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegF_ + _SegG_)
#define _SPACE_        ~0
#define _b_          ~(_SegC_ + _SegD_ + _SegE_ + _SegF_ + _SegG_)
#define _a_          ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegE_ + _SegG_)
#define _A_          ~(_SegA_ + _SegB_ + _SegC_ + _SegE_ + _SegF_ + _SegG_)
#define _t_          ~(_SegD_ + _SegE_ + _SegF_ + _SegG_)
#define _E_          ~(_SegA_ + _SegD_ + _SegE_ + _SegF_ + _SegG_)
#define _R_          ~(_SegA_ + _SegB_ + _SegC_ + _SegE_ + _SegF_ + _SegG_)
#define _r_          ~(_SegE_ + _SegG_)
#define _S_          ~(_SegA_ + _SegC_ + _SegD_ + _SegF_ + _SegG_)
#define _y_          ~(_SegB_ + _SegC_ + _SegF_ + _SegG_)
#define _C_          ~(_SegA_ + _SegD_ + _SegE_ + _SegF_)
#define _h_          ~(_SegC_ + _SegE_ + _SegF_ + _SegG_)
#define _g_          ~(_SegA_ + _SegB_ + _SegC_ + _SegD_ + _SegF_ + _SegG_)
#define _DIGITE_Dot_     ~_SegDp_
#define _DIGITE_Minus_    ~_SegG_
#define _TDELAY_        65535-4000
#define _FndCommMaxLength_   6
static  byte _g_CommanSelect   = 0;
   byte _ga_DispBUF[_FndCommMaxLength_ + 1]= {0, };
const  byte _ga_FndFontConst[] = {_DIGITE_0_, _DIGITE_1_, _DIGITE_2_, _DIGITE_3_, _DIGITE_4_,
                                    _DIGITE_5_, _DIGITE_6_, _DIGITE_7_, _DIGITE_8_, _DIGITE_9_, 0 };
const  byte _ga_FndComConst[]  = {~0x08, ~0x10, ~0x20, ~0x01, ~0x02, ~0x04, 0xff, 0};
// PORTA
 #define _Sag_A_       0
 #define _Sag_B_       1
 #define _Sag_C_       2
 #define _Sag_D_       3
 #define _Sag_E_       4
 #define _Sag_F_       5
 #define _Sag_G_       6
 #define _Sag_P_       7

// PORTB
 #define _SS_         0
 #define _SCK_         1
 #define _MOSI_       2
 #define _MISO_       3
 #define _PWM_A_       4
 #define _PWM_B_       5
 #define _DSR_         6
 #define _DCD_         7

// PORTC
 #define _COM0_       0
 #define _COM1_       1
 #define _COM2_       2
 #define _COM3_       3
 #define _COM4_       4
 #define _COM5_       5
 #define _COM6_       6
 #define _COM7_       7

// PORTD
 #define _START_         0
 #define _CHOICE_        1
 #define _Rx1_          2
 #define _Tx1_          3
 #define _RUN_          4
 #define _BUZZER_        5
 #define _PowerEnable_         6
 #define _CTS_          7

// PORTE
 #define _Rx0_         0
 #define _Tx0_         1
 #define _Ain0_         2
 #define _Ain1_         3
 #define _Set_         4
 #define _Up_         5
 #define _Down_       6
 #define _RTS_         7

// PORTF
 #define _Adc0_       0
 #define _Adc1_       1
 #define _Adc2_       2
 #define _Adc3_       3
 #define _TCK_         4
 #define _TMS_         5
 #define _TDO_        6
 #define _TDI_         7
#define _AVERGE_NUMBER_   6
#ifdef ADC_BubbleSort_Enable
 word  _g_AdcTempBuffer[_AVERGE_NUMBER_+1]   = {0, };
 byte _g_AdcTempBufCount = 0;
#endif
byte g_Run_Flag     = 0;
word g_DelayRun     = 0;
word g_DelayValue        = 0;
word gw_Delay_10uSec     = 0;
                                
//=========== 232 통신 관련 ===========================================================
#define _NUL_             0x00
#define _BufferLength_   100
#define _STX_              0x02
#define _ETX_      0x03
#define _ACK_      0x06
#define _NAK_      0x15
//=========== SPI 통신 관련 ===========================================================
byte g_Flag_SPI_TxStatus = 0;
byte g_RxRingBuffer[_BufferLength_+1] = {0, };
byte g_WorkBuf[20]   = {0, };
byte g_TxBuf[20]    = {0, };
byte g_RxEnd     = 0;
byte g_RxTopCount   = 0;
byte g_RxButtomCount  = 0;
//=========== 공용  ===========================================================
#define _Err_OpenTempSenser_    1
byte *str;
byte g_KeyStatus    = 0;
byte string[20]    = {0, };
double g_temp_float  = 0;