레이블이 ESP8266인 게시물을 표시합니다. 모든 게시물 표시
레이블이 ESP8266인 게시물을 표시합니다. 모든 게시물 표시

2025년 3월 20일 목요일

ESP8266 Digital Input Pin의 불편한 진실

1. 믿었던 놈인데...

ESP8266은 2017년도 부터 사용했기 때문에, 7년 정도 사용해 왔었다. 그동안 여러 종류의 내 프로젝트에 사용한 MCU이고, 이에 따라서 신뢰도가 상당히 높았다. 물론 순수한 ESP8266으로 사용하지는 않고 Wemos D1 mini 계열(정확하게는 clone)을 사용해왔다. 

[ Wemos D1 mini ]



2. 발명품 때문에...

아직 중학생인 아들의 발명품 경진대회 출품작 때문에 최근에 다시 D1 mini를 사용했다. 발명품은 작은 OLED와 푸시 버튼을 2개 가지는 구조이었다. 푸시 버튼 입력을 어떻게 구성할까 생각하다가 그냥 Pull-Down 구조로 구성하기로 하였다.

[ 2개 푸시 버튼과 Pull-down 회로 ]

3. 시작은 쉬웠으나...

OLED야 자주 사용하던 모듈이었기 때문에 별 어려움 없이 회로를 구성하고, 기존 개발해 놓았던 소스코드를 이용하여 나름 쉽게(?) 한글을 출력하였다. 


푸시 버튼은 2개의 Input Pin을 이용하여 데이터를 읽어서 HIGHT, LOW 상태를 체크 하였는데, 문제는 입력신호가 다음번 버튼을 누를때 올라오거나, 한번 눌렀는데 2회가 눌리는 경우, 버튼 근처에 손가락을 가져갔는데도 동작하는 등 여러가지 문제가 나왔다. 사실 Wemos D1 mini의 여러 버전의 Clone을 사용해 보았는데, 이런 경우는 사실 처음이었다.  

4. 의심은 또 다른 의심을...

손쉽게 개발이 끝날줄 알았는데, 버튼이 이상하게 동작해서 먼저 작성한 코드를 의심해 보았다. 이리저리 조건을 더 넣어 보기도 하고, 분기를 좀더 세분화 하기도 해보았지만 해결이 되지 않았다.  그래서 처음으로 푸시 버튼의 물리적 고장을 의심해 보았다.  푸시 버튼을 오래 사용하다 보면은 버튼이 눌려진 채로 안 올라오는 경우도 있고, 또는 눌렀지만 실제적으로는 전기적으로 연결이안되는 경우가 있어서 이다. 열심히 테스트를 가지고 버튼을 눌러 보면서 테스트 하였지만, 특이점을 발견하지 못하였다. 한 백번쯤 테스트 해보고 물리적 이상은 없다고 판단 하였다. 
 그 다음으로 Pull-down 회로를 의심하기 시작했다. 보통 Pull-down 회로를 추가하는 이유는 버튼의 상태가 floating 현상을 가지는 경우가 있어서 이를 안정화하기 위하여 사용한다.  사실 Pull-down 회로가 어려운 것도 아니고 간단하게 저항하나 걸치면 되는 것이라 문제될 부분이 없어 보였는데, 이것도 확실하게 하기위하여 테스터기를 또 열심히 찍어 보았다. 단락도 테스트하고, 저항도 테스트 하며 역시 문제가 없다는 것을 확인하였다. 15K 저항이 너무 낮은 값이어서 Pull-down이 동작을 잘 하지 않을 수도 있다는 글을 어디선가 보아서 100K 저항으로 변경하여 테스트 하였지만 역시 증상은 동일하였다.

 다시 원점으로 돌아와서 소스 코드를 확인하였다. 수십년의 개발 경험을 가지고 있지만, 노안과 집중력 감소로 아무래도 실수는 할 수 있다는 생각으로 열심히 살펴보았다. 

이미  독자분들은 결론을 알겠지만, 이러한 의심들은 전혀 소용이 없었다. 어떠한 노력을 해도 문제가 해결되지 않았다. 

5. 검색을...

결국 구글신의 도움을 받아 보기로 하였다. 구글링한지 30분도 안되어서 아주 아주 흥미로운 내용을 발견하였다. 


결론은 Wemos D1의 짝퉁 버전들은 Digital Input Pin을 이용하여 데이터를 읽을때 편차가 있다는 것이다. 이번 발명품은 2개의 Digital Input Pin을 사용하는데 하나는 큰 문제점이 없었는데, 다른 한개의 Pin이 문제를 일으키고 있었다. 

6. 고생의 끝은...

결국 캐패시터 하나로 해결되었다. 단순히 Digital Input Pin 과 GND 사이에 캐패시터를 연결하면 된다.  세라믹 캐패시터는 극성이 없기 때문에 아무 방향으로 연결해도 문제없다.


0.1uF 세라믹 캐패시터 ]

캐패시터를 추가하니 2개의 푸시 버튼 모두 내가 원하는 형태로 동작하였다.  뭐랄까.. 쾌적해진 느낌이 되었다. 


7. 왜 캐패시터를 사용하는가?

커패시터를 사용하는 이유는 대략 느낌상으로 알고 있었지만, 구글링을 통하여 찾은 내용을 한번더 정리해본다. (여기에 정리해 두면 나중에 찾기 쉬우니....)

- 디지털 신호 안정화(노이즈 필터링) : Arduino 계열의 Digital Input Pin은 전압의 빠른 변화를 감지할 수 있는데, 외부 환경이나 전자기적 간섭으로 인해 신호에 노이즈가 발생할 수 있다. 이에 따라서 의도 하지 않은 상황에서 Input Pin에 HIGH 또는 LOW가 걸릴수 있다.  캐패시터를 연결하면 빠른 전압 변화를 완화하고, 신호가 더 안정적으로 들어오게 된다.

- 디바운싱(Debouncing) : 버튼이나 스위치와 같은 입력 장치가 연결된 경우, 버튼을 눌렀을 때 물리적인 특성으로 인해 여러 번 신호가 변화하는 현상이 발생할 수 있다. 이 현상을 디바운싱이라고 하는데, 캐패시터를 추가하면 신호의 변화를 부드럽게 만들어 여러 번의 잘못된 입력을 방지할 수 있다. 

* 보통 스위치 신호를 안정화 하기 위해서는  0.1uF 정도의 캐패시터를 사용한다.  용량이 커지면 스위치의 반응이 느려지고, 용량이 커지면 스위치의 반응이 민감해지는데, 너무 작은 용량의 캐패시터를 사용하면 노이즈 필터링 효과가 줄어들기 때문에 추천하지 않는다.  


다른 사람들이 나처럼 고생하지 않기를 바라면서 글을 적었다. 
May the force be with you. 

2023년 1월 29일 일요일

18650 배터리 내부저항을 ESP8266으로 측정하자

 1. 인간은 끊임없이 편의성을 추구하는 동물

 18650배터리의 내부저항을 수동으로 측정을 하였지만, 한두번 정도는 할 수 있어도 수십번 하려니 고충이었다. 무엇인가 편리함을 추구하기 위하여 다시금 머리를 굴렸다. 18650 배터리의 내부저항을 측정하기 위해서는 전압 측정이 필수이다. 이전에 ESP8266으로 20V 전압을 측정하는 방법에 대하여 글을 적었지만 이번에는 4.2V를 위하여 다시금 정리한다.

[ ESP8266으로 20V 저항을 측정하는 방법 ]


2. ESP8266 의  ADC (Analog to Digital Converter) Pin

 ESP8266에는 A0라는 단 한개의 ADC Pin이 존재한다. 

(두개 이상의 ADC Pin이 필요하다면 ESP32를 사용하면 된다. 일전에 내 블로그에 소개한 3채널 오실로스코프가 ESP32를 사용하였다) 

ESP8266의 A0 Pin은 최대 1V를 입력받도록 설계되어 있다. 물론 나는 ESP8266 그대로 사용하는 것이 아니라 Wemos D1 mini 의 제품으로 사용한다.  제조사의 홈페이지에는 아래와 같이 핀 설명을 하고 있다.



A0 Pin에 대해서는 max 3.3V라고 표시하고 있다. 어찌된 일이냐 하면 아래의 회도로들 보면 알 수 있다. (붉은색 테두리)




A0 핀으로 입력된 내용은 220KΩ와 100KΩ 두개의 저항으로 분압되어 ADC에 연결되는 형태이다.  3.3V가 A0에 입력되었다고 할떄 분압공식에 의거하여

ADC에 걸리는 전압 = 3.3V X ( 100 / (220 + 100) )
                          = 1.03125V

A0 Pin의 정밀도는 10bits 이며 전압을 인가하고 값을 읽으면 0V 일때 0, 분압이 없는 상태에서  1.0V 일때 1024의 값이 나온다고 할 수 있다. 다만 위의 분압 공식으로 유추해 보면 대략 3.25V부터 1024값이 나온다. 실제로 측정해보니 0V일때 20, 3.06V 부터 1024의 값이 나왔다.  이는 사용된 220K와 100K의 저항값의 오차에서 발생할 수도 있고, 측정핀에 존재하는 저항이 있을 수도 있다. 다양한 원인에 근거하지만 핵심은 3.3V 이전에 1024값이 나온다는 것이다. 3.06V는 내가 사용한 모듈에서만 그런 것이며, 제품별로 결과가 다를 수 있다. 같은 제품이더라도 모듈에 따라서 다를 수 있다.  핵심은 Wemos D1 mini의 A0 Pin으로 3V 이상 측정하려고 한다면 분압을 해야 한다는 것이다.


3. 18650 배터리 전압

 일전에 소개한 글에서 배터리의 내부저항을 측정하기 위해서는 전압 측정이 필요하다고 했다. 18650 배터리의 만충전압은 4.2V가 대부분 이지만 4.35V도 있다. 이에 따라 4.35V 입력시 A0 Pin에 들어가는 전압이 최대 3.25V가 되도록 분압해야 한다.

위와 같이 전압을 배분하면 배터리의 전압이 4.35V 일때 

4.35V X 220 / (100 + 220)  = 2.990V 의 전압이 A0에 입력된다.  사실 오른쪽 저항값이 전체 저항값의 약 75%만 차지하게 설계하면 문제없다. 보유한 저항이 있다는 가정하에 25K + 75K로 구성하면 된다는 이야기 이다. (물론 50K + 150K도 마찬가지 이다.) 사실 저항을 병렬, 직렬 해서 구성하면 저항값을 못 맞출 것도 없기는 하다. 보유한 저항에 맞춰서 구성하자.

일단 100K, 220K로 구성하는 경우

V = (A0에서 읽은 값) X  (100KΩ + 220KΩ) / 220KΩ   * 3.3V / 1024 
V = (A0에서 읽은 값) X  0.004692082111

만약 A0에서 읽은 값이 512 라고 하면 2.4024V라는 값이 수학적으로 계산이된다. 다만 저항의 정확도 및 배선 사이에서 생기는 저항을 고려한다면 한번쯤은 실측해서 보정을 해주는 것이 좋다. 

다만 아무리 계산을 열심히 해 놓아도 현실적으로 보유한 저항을 적당히 쓰기 위해서는 결국 100K 저항 3개, 43K 저항 1개를 사용하여 100K + 243K의 형태로 구성하였다.

[ 요렇게 준비했다 ]


위에서 적은 공식을 다시 적으면 

V = (A0에서 읽은 값) X  (100KΩ + 243KΩ) / 243KΩ   * 3.3V / 1024 
V = (A0에서 읽은 값) X  0.0045532988185318

납땜해서 실측한 값과 계산된 값을 비교해 보니 아래와 같았다. 

-

전압이 올라갈수록 실제 값과 차이가 커지는 것으로 보아 위의 공식에서 계산한 상수가 문제이다. 

엑셀의 힘을 이용하여 새로운 계산식을 도출했다.  추세선이라는 기능을 이용하면 편리하다. 아래의 그림에서 파란색 선은 실제 측정한값, 검정색은 추세선인데 거의 동일하게 위치해서 두개를 분리하여 보기란 힘들다. (즉 추세선이 잘 만들어 졌다.) 일반적으로 ADC 값은 전압에 따라서 선형 그래프를 보인다. 


이제 세로운 수식이 만들어 졌다.
V =  (A0에서 읽은 값) X  0.0052 - 0.0832
손으로 약간의 보정을 해서 아래의 수식으로 마무리했다.
V =  (A0에서 읽은 값) X  0.00522 - 0.0771
저항값으로 계산한 수식과 엑셀 추세선의 차이는 기울기가 좀 더 커졌고, Y 절편이 생겼다. 


4. 회로도 및 구현 내용

 무척 간단해서 약 20포인트 납땜이 필요한 회로이다. 




회로도는 이미 납땜한 후에 그렸다. 워낙 간단해서 회로도 그리기 전에 작업을 하였고 블로그에 기록하여 위하여 회로도를 그렸다. 

[ 나름 산뜻 전면부 ]

[ 얼기 설기 후면부 ]

[ ESP8266 아래에 저항 3개가 숨어 있다 ]


위의 사진에서 보이는 배터리 홀더는 지난번 블로그에 소개한 그것을 사용하였다.
또한 시멘트 저항이 4개 포함되어 있는 것을 알 수 있다. 이 회로는 향후 18650 배터리 방전 회로로 병행하여 이용할 계획이어서, 저항의 경우 8Ω 짜리 시멘트 저항을 4개 병렬 연결하였다.  4V의 전압을 인가하는 경우 시멘트 저항 1개당 0.5A가 걸리는 구조이다. 즉 최대 2A의 방전 능력을 가진다.  굳이 시멘트 저항을 여러개 병렬 연결하는 이유는, 단일 저항으로 2A의 전류를 흘려 보내면 생각보다 많이 뜨겁다. 4개로 분산해서 발열을 시키는 형태이다. 이정도로 연결해도 2A 전류가 흐를때 생각보다 뜨겁다. 

 회로에서 사용한 MOSFET은 55NF06인데, Data sheet 상에는 연속으로 50A가 가능하다고 기록되어 있지만 내가 알리에서 구매한 것들은 실제 그렇지는 않다. 방열판 없이는 개당 0.5A 정도가 적당하며 방열판이 있어야 개당 2A 정도가 가능하다. 그 이상의 전류를 흘려 보내면 손으로 잡을 수 없을 정도로 뜨거워진다.  작은 방열판이라도 각각 붙여줘야 하겠지만  MOSFET 가격이 방열판보다 싼 형태라 그냥 4개의 MOSFET을 병렬로 연결하였다. 
 회로도 및 구현한 사진에는 1개의 MOSFET만 보이지만 후에 3개를 추가하여 4개 병렬 연결하였다. 하나씩 추가 하면서 발열 상태를 체크하였는데 총 3개인 경우에도 발열이 부담되었고, 4개를 연결하니 안정적인 온도가 되었다. 

[ 4개 병렬 MOSFET ]

MOSFET을 똑바로 세워서 붙여 놓으니 살짝 열이 발생해서 위의 사진처럼 구부려서 부채 모양으로 전개했더니 열 발생이 거의 없었다. (처음부터 간격을 벌려서 납땜할껄 그랬다.)

"이렇게 계산한 내부 저항값은 참조용이며, 정확한 계산이라고 할 수 없다. "

5. 소스코드

 일단 시리얼 포트로 정보를 출력하였다. OLED를 붙여도 되겠지만(심지어 기판에는 OLED 올려 놓을 자리도 비워 놓았다. ) 일단은 시리얼 포트를 이용하기로 하였다.

소스코드 자체에 크게 어려운 부분이 없기 때문에 일부만 발췌하여 표기한다. (18650 배터리 방전 및 용량 체크 기능을 만들 예정이어서 그때 완성된 코드를 공개할 예정이다.)


void setup() {
  Serial.begin(115200);           // 시리얼 통신 속도 설정
  pinMode(MOSFET_PIN, OUTPUT);    // Mosfet control pin
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);    // Turn Off
  checkBattery();
}

float getVolt()
{
  float tmpValue = 0.0;

  // 측정 오차를 최대한 줄이기 위하여 CHK_CNT 만큼 측정해서 값을 나누어 사용한다.
  for (int i = 0; i < CHK_CNT; i++) {
    tmpValue += analogRead(A0);
    delay(5);
  }

  tmpValue /= CHK_CNT;
  g_fSensorValue = tmpValue;
  g_fVolt = tmpValue * 0.00522 - 0.0791;

  sprintf(g_strPrintBuf, "Volt = %.4f", g_fVolt);
  Serial.println(g_strPrintBuf);
  return g_fVolt;
}

void checkBattery()
{
  float onValue  = 0.0;   // 저항이 연결했을때 전압
  float offValue = 0.0;   // 저항이 연결되지 않았을때 전압
  float resistValue = 0.0;// 배터리 내부 저항
  float current = 0.0;    // 전류값
  digitalWrite(MOSFET_PIN, HIGH); // Turn On
  delay(500);
  onValue = getVolt();
  digitalWrite(MOSFET_PIN, LOW); // Turn off
  delay(1500);
  offValue = getVolt();
  current = offValue / 2.05;  // 전류값 측정
  // 1000은 밀리옴으로 변환하기 위한 값
  resistValue = (offValue - onValue) * 1000 / current;
  sprintf(g_strPrintBuf, "내부저항 = %.4f", resistValue);
  Serial.println(g_strPrintBuf);
}


소스코드의 핵심을 소개하자면 MOSFET을 켜고 전기를 흘려보낸 다음에 전압을 측정하고, 다시 MOSFET을 끄고 전압을 측정한다. 그뒤에 전압 차이를 전류로 나누면 내부 저항값이다. 
계산 과정이 궁금하다면 아래의 링크를 확인하자.


- 2023-02-06
소스코드를 일부 수정하였다. 
전류측정값을 고정으로 사용하였는데, 현재 전압에 의거하여 사용하도록 변경하였다.




2021년 1월 5일 화요일

원격 모니터링 : 12V 듀얼 전원 공급장치 만들기 - ver 1.0

1. 모니터링의 시작

시골에 노모께서 홀로 계신다. 덥지는 않으신지, 춥지는 않으신지 늘 걱정이다. 그래서 어머니께서 주거하시는 안방의 온습도 정보를 모으기 시작했다.  대단한 것은 아니였고 ESP8266 계열의 D1 mini 와 AM2302 온도 센서를 달아서 만들었다. 

가장 초창기 모델

 위의 사진은 내가 ESP8266을 공부하면서 가장 처음으로 만든 모델이다. 지금 보면 구성 자체가 어설프고, 납땜도 어설프고, 심지어 전선의 배색도 어설프다. VCC와 GND 색상이 동일하다니.. 대체 무슨 생각으로 전선 색상을 고른것인지.... 그나마 요즘은 전선 색상은 구별해서 사용하려고 노력한다. 가능한 GND는 검정 (또는 파랑) 을 사용하려 하고, 3.3V는 주황, 5V는 빨강, 12V는 노랑 색상의 전선을 사용하려고 노력중이다.  위에 사진에 표시된 1차 버전은 얼마나 어설펐는지 ESP8266의 DeepSleep을 위해서 필요한 전선 연결이 나중에 떨어졌다. 설치해서 가만히 두는 것이었는데 ㅠㅠ
 초기 버전을 만들고 나서 2차 보수까지 수행했지만, 역시나 문제가 생겨서 2차 버전을 다시 만들었다.  초기 버전과 2차 버전 사이의 기간이 수개월 있었고, 그 사이 내 실력도 많이 늘었다. 초기 모델은 퇴역 하였고, 2차 버전은 현장에 설치되어 있는데 아무리 사진을 찾아도 안보인다.   이를 이용하여 정보를 수집하기 시작하였다. 



 수집된 정보는 서버에 업로드하여 30일까지의 이력을 보게 만들었는데, 서버 부분은 내가 특별히 만든 것은 아니고 Adafruit를 활용하였다. (여러가지 이유가 있지만 일단은 돈이 안들어 가고 이미지 전송이 가능해서 사용하게 되었다.)  아래의 링크를 보면 활용하는 방법이 기재되어 있다. 예전에 내 블로그에서 다루었던 내용이다.

[Adafruit 활용기]

 단순히 온습도를 로깅하고 있지만, 어머니의 행동 패턴과 연계하여 고려하면 어머니의 취침시간과 기상 시간을 알게 되었다. 이제 어머니가 일어 나셨구나... 라는 것을 알게 되었다. 

2. 모니터링의 확장

 온습도를 기반으로 하여 어느 정도의 내용을 짐작하게 되었지만, 이것은 정말 정확한 정보가 아니였다.  카메라 장치가 더더욱 필요하게 되었다. 내 의견은 어머니가 주거 하시는 방에 카메라 장치를 설치하자는 것이었지만, 큰 형님의 "어머니 사생활을 존중하자" 라는 의견에 감명을 받아 외부에 설치를 고려하게 되었다. 



 태양광 패널을 이용하여 스스로 충전하며, 주기적으로 사진을 촬영하여 서버에 전송하는 형태의 작품이다.  위의 사진은 역시 초기 버전이며, 수차례 수정 보완하였다.  가장 큰 문제점은 태양광 패널이었다. 태양에 노출되면 생각보다 쉽게 못쓰게 되었다. (패널 가격이 싸서 그런가..ㅠㅠ) 태풍이 심하게 불면 태양광 패널이 돛이 되어 카메라가 하늘을 0.5초간 날게 되는 진풍경을 연출하기도 했다. 전체적으로 축전(蓄電) 관련된 설계가 나빴거나, 무인 운영 이라는 것 자체의 오류였던가 생각된다.  사용했던 반찬통도 태양광에 노출된지 2년 반만에 부서지기 시작했다. 이 모델은 2년 반만에 퇴역하게 되었다. 사실 퇴역하게 된 이유는 운용의 난해함이 아니라 정보 취득의 한계성 때문이었다. 2분 마다 1장씩 서버로 사진을 올리는 시스템이어서 실시간 반영이 안된다는 것이 문제점 이었다.  이정도의 간격으로 사진을 올린다면 비가 오는 것인지, 눈이 왔는지 정도를 구별하는 것 밖에 안된다.  원래 연구의 시작은 원거리 건물이나 비닐하우스 등에서 문이 열려 있는지, 닫혀있는지를 확인하기 위함이었다. 
 그리하여 IP Camera를 설치하게 되었다.


Wi-Fi로 연결이 가능하며, 적외선 LED가 있어서 야간에도 모니터링이 가능한 카메라 이다. 가격도 $30 정도 수준으로 SD Memory Card를 이용하면 자체 녹화도 가능한 형태이다.  이동식을 포기하고 외부에서 전원을 끌어서 설치하여 앞마당 및 어머니가 주거 하시는 안방의 외부까지 볼 수 있도록 만들었다.

3. 다 좋은데 뭐가 문제?

 24시간 IP Camera를 통하여 동영상으로 모니터링이 가능하도록 되었는데, 문제는 외부에서 촬영하다 보니 주연이신 어머니께서는 24시간 중 30분 정도만 출연하셨다.  하루 종일 빈 앞마당을 보게 된다는 것이었다. 그래서 스트리밍 정보를 취득하여 움직임을 감지하고, 감지시 이미지를 다시 서버에 저장하는 기능들 만들었다. 사실상 요즘 IP Camera는 대부분 움직임 감시 기능을 제공한다.  내가 글을 쓰는 시점에 위에 연출된 카메라는 이미 퇴역하였다. IP Camera를 24시간 풀로 켜놓으면 대략 3년 정도 이내에 먼가가 고장나기 시작한다.  마지막으로 문제가 된 것이 12V 어댑터 이다. 한번 상태가 이상하여 전원을 뺏다가 다시 꼽아서 동작되게 만든 것이 5개월 정도 전인데, 역시나 5개월 후에 작동을 멈추었다. 모든 기계는 쉬게 해 줘야 하나 보다.

4. 그래서 듀얼

 처음에는 그동안 PC 버리는 것들에서 모아 둔 전원 공급 장치를 사용하려고 하였다. 전력량도 충분하고 안정적이라 생각해서이다. 그래서 필요없는 선들을 자르고 준비하다가 너무 과한 스펙이라고 생각이 들었다. 내가 필요한 것은 최대 2A 이내의 전력인데 20A 짜리를 하다니....  그래서 12V 의 적당한 어댑터를 구매하려고 알리를 살펴보다가 아래의 제품을 발견하게 되었다. 
 
오호 이걸로 만들면 되겟구나라는 생각과 함께, 이걸 듀얼로 구성해야 겠다는 생각이 들어서 2개 구매하였다. (개당 $2)


 사진으로 보면 상당히 거창해 보이지만, 별거없다. 그냥 구매한 제품 2개를 병렬로 잘 구성해서 만든 포맥스로 만든 껍데기에 안착 시켰을 뿐이다. 내부에 ESP8266이 하나 있는 이유는 온도 센서와 결합해서 전원 장치의 내부 온도를 원격에서 확인하기 위함이다. 12V가 나오는 장치에서 ESP8266 동작을 위하여 5V가 필요해서 아주 아주 작은 다운트랜스를 하나 붙였다. (최대 1A 가능)

 또 내부 온도가 걱정되어서 공기 순환용 구멍을 뚫었다. 8mm 드릴로 측면들에 구멍을 내었는데, 좌우 부분은 위쪽으로 , 아래쪽 부분은 전체적으로, 위쪽으로는 오른쪽 윗부분에 구멍을 내었다.  이 물건이 외부에 설치되는 터라 벌레들을 막기 위해서 구멍을 뚫은 부분은 방충망 쪼가리를 구해서 붙였다. 




전면부 구멍은 통풍용이 아니라 내부 LED등의 불빛이 보이도록 뚫었고, 안쪽으로 투명 아크릴 조각을 붙여서 LED가 보이도록 만들엇다.  이부분에 방충망을 안쓰고 아크릴을 쓴 이유는 혹시나 비가 들이치지 않을까 걱정을 해서이다.

5. 결과 및 설치

 다 만든 결과에 대한 사진을 찍어두지 않았던 터라,  설치후 촬영한 사진을 올린다. 실제 설치된 모습니다. 외부 벾에 아래의 사진과 같이 설치되어 있다. 


위의 사진에 포함된 것들은 동파 방지 장치 및 IP Camera관련된 것들이다.  제작한 12V 전원 장치는 스위치를 2개 가지고 있으며, 안타깝게도 전환은 자동이 아니라 수동이다. 1번을 켜거나 2번을 겨거나 하면 된다. 두개를 동시에 켜도 되긴 하지만 굳이 전력을 낭비할 필요가 없으니 둘중 하나면 켜 놓으면 된다. 켜진 것은 전원 공급장치 전면부에 뚤어 놓은 구멍으로 확인 가능하다. 이제 부터 하나는 쉬면서 하나는 일 할 수 있게 되었다. 


2019년 3월 21일 목요일

NFC를 이용한 선불 교통 카드 잔액 조회기

1. 딸래미(王)의 귀환

 아직 초등학생인 딸래미가 있다. 집에서 먼(대중교통으로 40분) 학원을 다니게 되었는데, 셔틀도 없다. 학원에 갈때는 와이프가 데려다 주지만, 올 때는 내가 퇴근하면서 학원으로 가서 딸래미를 데리고 집으로 온다. 하지만 나도 직딩이라 항상 갈 수는 없는 상황이었다.  딸래미의 귀환이 걱정되기도 하고, 만일의 사태를 위하여 딸래미에게 선불 교통 카드를 사 주었다.

[ 하트빵빵 귀여운 토끼 버전의 교통카드 ]
한두번 사용해보게 했지만, 잔액을 기억 못하는 상황이라, 집에서도 손쉽게 잔액이 확인 가능하도록 리더를 만들어야 했다.

2. 모듈의 귀환

 이번에 제작을 위하여 이전에 사용한 모듈들을 귀환시켰다.  예전의 포스트에 사용한 PN532 모듈을 제일 먼저 귀환 시키고,

[ PN532를 소개한 포스트 바로 가기 ]

[ PN532 ] 

그 다음 OLED 및 ESP8266을 귀환 시켯다.
[ OLED + ESP8266을 이용한 오실로스코프 바로가기 ]


[ OLED ]


3. 제작 결과물 소개

 일단 결과물을 소개하면 아래의 이미지와 같다.
[ 나름 투명 아크릴 상판 ]

[ 측면에서 찰칵. 이번엔 배터리 없이 설계 ]

[ 전원을 연결하면 카드를 태깅하라고 궁시렁 ]

[ 좀더 신선하게 고속버스 안에서 촬영 ]

[ 카드를 가져다 대면 잔액이 표시됨 ]


수준이 좀 낮은 상태로 촬영 되었지만, 동작은 아래의 모습으로 진행된다.




4. 제작 방법 소개

납땜할 포인트가 별로 없다. 기껏해야 20-30 포인트 정도된다.  회로도 까지라고 말할 필요도 없고 아래와 같이 연결하면 된다.
[ 심플한 연결도~ ]

나는 모듈들은 모두 소켓 방식을 사용하기 때문에, ESP8266은 기판의 뒷면에 꼽을수 있는 형태로 제작되었다. NFC 나 OLED 모듈은 기판의 앞면에서 꼽는다.
   
[ 급제작으로 인한 케이블 배선의 엉성함은 참아 주시라~ ]

소스코드는 전체 공개이지만, 라이브러리는 이 포스트에 기록하지 않는다.
먼저 필요한 라이브러리는 2개이다.

PN532관련된 라이브러리는 아래의 링크에서 다운로드 가능하다.
https://github.com/adafruit/Adafruit-PN532


OLED에 한글을 출력하는 과정은 나름 껄끄롭지만, 그 전체 과정 및 라이브러리는 아래의 링크에서 다운로드 가능하다.
http://andy-power.blogspot.com/2018/08/oled.html

대략 이번 선불 교통카드 잔액 조회 기능을 만들기 위해서는 100행 정도를 코딩했으며, 코딩된 전체 내용은 아래와 같다.
/* ------------------------------------------------------
  T-Money Checker
  +-------- GPL License --------------------------------+
  do not remove contact & url
  contact :  terminal0070@gmail.com
  blog :  https://andy-power.blogspot.com/
  -------------------------------------------------------- */
#include <Arduino.h>
#include "Adafruit_PN532.h"
#include "Display_SSD1306.h"
#include "HanDraw.h"
#define OLED_RESET -1 //  S/W 리셋..사용하지 않음

// PN532 V3 SPI모드로 사용하기 위해서는 모듈의 스위치를 변경하고 VCC,GND 포함하여
// 6개의 연결이 필요하다.
// ESP8266 기준으로 정의
#define PN532_SCK  (D5) //
#define PN532_MOSI (D7) //
#define PN532_SS   (D3) //
#define PN532_MISO (D6) //

// I2C 모드를 이용하기 위해서는 아래 2 연결이 필요하다.
#define PN532_IRQ   (2// Not Use
#define PN532_RESET (0// Not connected by default on the NFC Shield

//--------------------------------------------------------------
// Global variables
//--------------------------------------------------------------
Display_SSD1306 Oled(OLED_RESET); // 화면 표시용 객체
Adafruit_PN532 nfc(PN532_SS);     // NFC 객체

//--------------------------------------------------------------
// Setup function
//--------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(50);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH); // 내장 LED 끄고
  Serial.println("Start NFC");

  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // PN532 못찾은 경우에는 다시 부팅해 버린다.!! (찾을때 까지 리부팅)
  }
  // 제대로 연결되었으면 정보를 출력한다.
  Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC);

  // Oled I2C 방식으로 연결하고, 주소는 0x3c
  Oled.begin(SSD1306_SWITCHCAPVCC, 0x3C, false);

  // Callback 함수를 설정해 주면 필요시 호출 하여 사용함.
  HanDraw.begin(12,  
    [](void) { Oled.clearDisplay(); },// 화면 지우는 콜백함수
    [](int16_t x, int16_t y, uint16_t color) {// 1 픽셀을 그리는 콜백 함수
      Oled.drawPixel(x, y, color);   },
    [](void) { Oled.display(); } //  메모리에서 Display 표시 버퍼까지 보내는 함수
   );

  // configure board to read RFID tags
  nfc.SAMConfig();
  Serial.println("Waiting for an ISO14443A Card ...");
  HanDraw.display(); // 사실상 Oled.display() 동일....
  delay(2000);

  HanDraw.clear();
  HanDraw.setFontSize(16);
  HanDraw.drawString(1, 13, "선불 교통 카드를");
  HanDraw.drawString(1, 33, "태그해 주세요");
  HanDraw.display(); // 사실상 Oled.display() 동일...
}

//--------------------------------------------------------------
// loop function
//--------------------------------------------------------------
void loop() {
  uint8_t success;
  uint8_t responseLength = 64;
  success = nfc.inListPassiveTarget();
  if (success) {
    Serial.println("Found something!");
    // T-money 카드번호, 날짜등을 읽기 위한것
    // OLED에는 표시하지 않음
    uint8_t cardInfo[responseLength];
    uint8_t cardNumSize = 0;
    uint8_t selectApdu[] = { 0x00, 0xA4, 0x00, 0x00, 0x02, 0x42, 0x00 };
    success = nfc.inDataExchange(selectApdu, sizeof(selectApdu), cardInfo, &responseLength);
    if (success) {
      Serial.print("responseLength: "); Serial.println(responseLength);
      nfc.PrintHexChar(cardInfo, responseLength);
      if (responseLength >= 24) {
        Serial.print("card number : "); nfc.PrintHexChar(cardInfo + 8, 8);
        Serial.print("card date : ");   nfc.PrintHexChar(cardInfo + 21, 4);
      }
    }

    // 잔고 조회용
    // 조회가 완료되면 OLED 표시
    uint8_t balance[responseLength];
    uint8_t balanceApdu[] = { 0x90, 0x4C, 0x00, 0x00, 0x04 } ;
    success = nfc.inDataExchange(balanceApdu, sizeof(balanceApdu), balance, &responseLength);
    if (success) {
      Serial.print("responseLength: "); Serial.println(responseLength);
      nfc.PrintHexChar(balance, responseLength);
      if (responseLength >= 4) {
        char fpsbuf[32] = ""; // 숫자를 문자열로 바꾸어 화면에 출력할때 사용됨
        uint32_t money = balance[0] * 256 * 256 * 256 +  balance[1] * 256 * 256 +  balance[2] * 256 + balance[3];
        HanDraw.clear();
        HanDraw.drawString(2, 7, " 교통 카드 잔액 ");
        HanDraw.drawString(2, 25, "------------------");
        dtostrf((float)money, 10, 0,   fpsbuf);
        HanDraw.drawString(2, 43, fpsbuf);
        HanDraw.display();
        delay(2000);
        Serial.print("money : "); Serial.println(money);
      }
    }
  }
  else {
    HanDraw.clear();
    HanDraw.drawString(1, 13, "선불 교통 카드를");
    HanDraw.drawString(1, 33, "태그해 주세요");
    HanDraw.display(); // 사실상 Oled.display() 동일...
  }
  delay(100);
}


-2024-03-11 추가
위의 코드는 일반적인 선불 교통카드에 대한 조회 기능이다. 휴대폰의 NFC 교통카드 번호 확인은 위의 코드로 불가능하다.  nsmaster 님이 알려주신 아래의 APDU로 휴대폰의 교통카드 번호가 확인하다고 댓글을 달아 주셨다. (감사합니다.)
{0x00, 0xA4, 0x04, 0x00, 0x07, 0xD4, 0x10, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00} 
집안에 안드로이드폰이 없어서 아직 확인은 못했다. 

5. 끝으로

 사실 일전에 다 연구한 모듈과 내용을 다시금 정리하고 디스플레이를 추가한 결과물이다. 조회기만을 만드는 것은 당일 저녁 딸래미를 학원에서 집까지 데려오고 나서 시작해서 그날 잠자기 전에 다 만들었다. (수 시간 정도 걸림)  예전에 NFC 모듈 연구 당시 선불교통카드에 APDU 방식으로 커멘드를 보내고 받아야 하는데, 보내야하는 커멘드를 어느 자료에서도 찾을 수가 없어서 정말 고생했다.  이 포스트에서는 관련 커멘드는 1행의 소스코드로 공개되었지만, 이 1행의 코드는 수일간 고생해서 알아낸 코드이다.  연구는 끝이 없고 시간은 적으며, 흰머리는 너무나도 빠르게 늘어간다. ㅠㅠ


ESP8266 Digital Input Pin의 불편한 진실

1. 믿었던 놈인데... ESP8266은 2017년도 부터 사용했기 때문에, 7년 정도 사용해 왔었다. 그동안 여러 종류의 내 프로젝트에 사용한 MCU이고, 이에 따라서 신뢰도가 상당히 높았다. 물론 순수한 ESP8266으로 사용하지는 않고 Wemos...