레이블이 Wemos D1 mini인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Wemos D1 mini인 게시물을 표시합니다. 모든 게시물 표시

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번을 겨거나 하면 된다. 두개를 동시에 켜도 되긴 하지만 굳이 전력을 낭비할 필요가 없으니 둘중 하나면 켜 놓으면 된다. 켜진 것은 전원 공급장치 전면부에 뚤어 놓은 구멍으로 확인 가능하다. 이제 부터 하나는 쉬면서 하나는 일 할 수 있게 되었다. 


2018년 11월 19일 월요일

MCP3202 - 12bit ADC

1. MCP3202란?

 Analog to Digital Converter (ADC) 칩의 하나로 12비트, SPI 통신지원 등이 특성이다. ESP8266이 analog핀이 하나인 관계로,  동시에 2개 채널을 읽기 위하여 사용하기 위하여 사용법을 연구하였지만, 사실 속도와 관계 없다면 ESP8266에서 몇개의 아날로그 센서를 붙이는 것은 편법이 존재한다. 아날로그 센서의 특징에 따라서 관계되지만, 3V 정도로 동작하면서 소비전력이 지극히 적은 센서들은 ESP8266의 디지털 핀들을 이용하여 여러개 제어 가능하다.  다만 센서가 소비 전력이 좀 되거나, 정상 동작 레벨까지 1분정도 걸리는 미세 먼지 센서등은 ESP8266에 여러개 붙이기 위하여 노력하기 보다는 그냥 ESP32를 사용하는 것을 추천한다.

2. MCP3202의 특성

 12비트로 결과값이 나온다는 것은 이미 알려  드렸고, 이하 제조사가 제공하는 데이터시트에서 발췌한 내용으로 설명한다.  (이 포스트에 사용된 모든 그림은 제조사가 제공하는 데이터 시트에서 발췌하였다.)


위의 그림에서 CS, CLK, Dout(MISO), Din(MOSI)는 SPI통신의 기본이니 다들 아실테고, Vref는 기준전압이 되겠다.  MCP3202가 동작하는 주 전원이며 이 입력에 따라서 샘플링 속도나 frequency가 정해진다. 당연하겠지만 최고 속도를 위해서는 5V 가 공급되어야 한다. Vss는 전체 공통 Ground이니, 시스템이나 측정 프로브와 연결해 주면 된다. CH0과 CH1은 측정 프로브의 +측에 연결하면 된다.
 스펙에 Vref에는 최대 7V라고 했지만, 다들 그리 사용하지 않으리라 생각한다. 이유는 간단하다. 5V에서 이미 최대 기능을 발휘하니, 일부러 높은 전압을 입력할 필요도 없으며, 7V라는 전압을 따로 구성해야 하는 불편함을 감수하지 않아도 되기 때문이다.


 MCP3202의 통신 Timing에 관련된 그림을 보면 MSBFIRST 먼저 오는 것을 알수 있고, 일반적인 SPI_MODE0 으로 동작함을 알 수 있다. (아.. 내 능력으로는 남들이 그렇다고 해서 그런가 보다 하는 정도이다.)


친절하게도 스펙에 Output Value가 어떤 숫자를 의미하는지 계산식을 보여주고 있엇다. 기준전압대비 측정전압의 비율에 4096을 곱하여 계산한다고 적혀있다. 즉 기준전압이 5V 일때 측정 전압이 2.5V 라 가정하면 MCP3202는 2048 이라는 값을 반환해 준다.



3. 소스코드

아래의 소스코드를 실행시키면 1초마다 측정하여 Serial 창에 표시한다.

// MCP3202는 12비트 Analog-to-Digital Converter이다.
// MCP3202는  테스트한 바로는 클럭을 14MHz로 동작 시키면
// 7마이크로초 정도에 1회 읽을수 있지만,
// 샘플링 속도의 한계를 고려 한다면 큰 의미가 있는 것은 아니다.
// 1.8MHz의 클럭이 스펙상 최대;;;
// 또한 스펙상 5V 상태에서 100Ksps 가 한계이다.. 즉... 1만 헤르츠 정도면.. 신뢰도 급감.
#include <SPI.h>
#define CS_3202  D1      // 15
//#define SPI_SETTING     SPISettings(1800000, MSBFIRST, SPI_MODE0)

void setup()
{
  Serial.begin(115200);
  pinMode(A0, INPUT);
  pinMode(CS_3202, OUTPUT);
  digitalWrite(CS_3202, HIGH);

  SPI.begin();
  SPI.setFrequency(2000000);  // 2MHz로 동작. mcp3202 기준으로 12MHz 정도가 한계이다.
                              // 다만 10MHz 이상되면, 신뢰성은 이미 바닥..
}

void loop()
{
  int ch0, ch1;
  Read3002_EX(CS_3202, &ch0, &ch1);

  Serial.print("CH0 =");
  Serial.println(ch0);
  Serial.print("CH1 =");
  Serial.println(ch1);

  delay(1000);
}

// 2개 채널 모두 읽는 버전
void Read3002_EX(int CS, int *ch0, int* ch1) {
  int msb;
  int lsb;
  digitalWrite(CS, LOW);
  SPI.transfer (B00000001);// Start bit
  msb = SPI.transfer(B10100000) ;  // ch0
  msb = msb & B00001111;
  lsb = SPI.transfer(0x00);
  *ch0 = ((int) msb) << 8 | lsb;

  digitalWrite(CS, HIGH);
  digitalWrite(CS, LOW);

  SPI.transfer (B00000001);// Start bit
  msb = SPI.transfer(B11100000) ; // ch1
  msb = msb & B00001111;
  lsb = SPI.transfer(0x00);
  *ch1 = ((int) msb) << 8 | lsb;
  digitalWrite(CS, HIGH);
}

// 1개 채널만 읽는 버전
int Read3202(int CHANNEL, int CS) {
  int msb;
  int lsb;
  int commandBytes = B10100000 ;// channel 0
  if (CHANNEL == 1) commandBytes = B11100000 ; // channel 1
  digitalWrite(CS, LOW);
  SPI.transfer (B00000001);// Start bit
  msb = SPI.transfer(commandBytes) ;
  msb = msb & B00001111;
  lsb = SPI.transfer(0x00);
  digitalWrite(CS, HIGH);
  return ((int) msb) << 8 | lsb;
}






2018년 11월 18일 일요일

ESP로 오실로스코프를 만들자 - #2 (MCP3202편)

1. ESP8266의 한계

일전의 포스트를 통하여 ESP8266의 ADC 성능이 상당히 느리다는 것을 이야기 한적이 있다. 사실상 1KHz 정도가 측정 가능하며, 10KHz정도 되면 원하는 정보를 얻을 수 없는 정도이다. 물론 동시에 3개 채널을 읽어야 하는 상황이기에 ESP8266은 아무래도 모자란 상황이었다. 이런 이유로 이미 보유하고 있던 MCP3202 칩을 사용하기로 결정하였다.  MCP3202는 2개의 채널을 가지고 있기 때문에 EPS8266의 1개 채널을 이용하면, 총 3개의 채널로 구성이 가능하다.

2. 일단은 만들어 보았지만...

계획이 섰고, 이미 각 모듈에 대한 연구는 선행하였기 때문에 바로 작업에 들어 갔다.
아래의 사진과 같이 쓱쓱 배치를 하고...
기판을 보면 보통 컨셉의 반대로 납땜하였지만, 저 기판의 뒷면에는 TFT LCD가 붙어 있는 형태 이다. 즉 TFT LCD의 핀들이 위의 사진에서 맨 아래쪽 14개 핀인 것이다. 듀얼레이어를 구성하기 위하여 저리 납땜 하였다.


그리고 배선을 열심히 해서, 아래와 같이 만들었다. (뒷면에도 많은 점퍼케이블이 지나간다.ㅠㅠ)
위의 사진에서 오른쪽 맨 아래에 위치한 것이 MCP3202이다. 18650 배터리와 ESP8266을 각각의 소켓에 꼽으면 사용 가능한 형태이다.  전류 안정화를 위해서 배치한 콘덴서는 보유한 것 중에서 손에 잡히는 아무거나 였고, 후에 용량을 보정하였다. 뭐 사실 위에 사진을 보면 알겟지만 별거 없다. 분압을 위한 6개의 저항과 다이오드3개, 콘덴서 1개, 스위치 1개가 부가적으로 사용된 소재들이고, 18650 충방전 보호모듈, MCP3202, ESP8266, TFT LCD가 전부이다.

테스트 결과가 상당히 아쉬웠다. MCP3202 2채널을 사용하는 것은 문제가 없었지만, ESP8266의 1개 채널이 상대적으로 1회 측정 속도가 너무 느렸고, 전체 성능에 많은 영향을 미쳤다.  결국 다 만들었지만 버리게 되는 상황이 되어 버렸다. (특별히 회로도나 그외의 것들을 상세히 소개 안하는 이유가 버렸기 때문이다....)


3. ESP32로...

나는 Lora 연구용으로 ESP32계열의 개발 보드를 3개 가지고 있었는데, 이것들 중 하나로  ADC를 테스트 해 보았다. ESP8266의 ADC에 대해서는 그전 부터 알고 있었기에 ESP32도 비슷하겠지라는 마음과 함께, 큰 기대를 하지않고 테스트를 진행하였는데, 결과는 경이적이었다. 이유는 ESP32의 ADC 칩이 MCP3202라는 칩과 동일한 성능을 가지면서(아마도 그 이상) 사용할 수 있는 채널이 10개가 넘기 때문이었다. (ESP32에는 2개의 ADC 칩이 존재 하며 첫번째 칩이 두번째 칩보다 5% 정도 읽기 속도가 빠르다. 내부 회로와 관련된 부분이며 5%라는 수치는 실험 결과이다)  결국 오실로스코프제작에 있어서 ESP8266을 포기하게 되었다.  실제 제작은 Lolin32라는 개발용 보드를 가지고 진행하였다. 이하 내용은 다음 포스트로 정리하겠다. 



2018년 11월 13일 화요일

ESP로 오실로스코프를 만들자 - #1 (ESP8266편)

1. 오실로스코프는 왜?

 나는 오실로스코프를 실제 사용해본적이 없으며, 심지어 오실로스코프를 지인에게 만들어서 배달하면서 지인의 집에서 20년 정도나 되어 보이는 아날로그형 오실로스코프를 직접 본 것이 처음일 정도이다. (실제 그 전에 봤을지도 모르는 기계일지도 모르겠다. 다만 관심이 없었던 터라 인지하지 못하였을 수도 있다)  이러한 내가 지인의 관심 분야를 듣게 되면서 오실로스코프를 만들어 되었다.

2. 쓸만한가?

 내가 지인에게 배달한 오실로스코프는 총 3개의 버전이다. (나는 5개의 버전을 만들었으며,  배달하지 않은 2개의 버전은 집에서 쓰레기 아닌 쓰레기 취급을 받고 있다) 지인은 1차 버전부터 맘에 들어 하였지만, 지인과 지속적인 대화를 통하여 업그레이드가 된 형태이다.  몇개의 포스트를 통하여 수개월간 작업한 내용을 기록할 예정이다.

3.  ESP8266으로 만들기
 내가 가지고 있는 MCU중에서 가장 흔하게 사용 가능한 것으로 시작하였다. 언제나 대량생산이 아닌 관계로 Wemos D1 mini로 만들었었다. (요새는 Wemos라는 이름이 lolin으로 변경된듯 하다.)

이전에 이 블로그에서 소개한 한글 및 OLED 출력용 라이브러리를 이용하여 오실로스코프를 제작하였다. 제작 과정중에 몇개의 살짝 스샷을 찍어 보았다.


위의 첫번째 사진은 측정된 값을 점으로만 표현한 형태이며, 두번째 세번째 사진은 각 측정된 값을 직선으로 연결한 형태이다.


4. 회로도

 크게 복잡할 것 없는 회로이며, 손으로 쓱쓱 그린 버전을 아래에 넣었다.


0.98인치 OLED를 쓰는 것 이외에 Prove로 연결하는 회로가 있는데, 입력 전압을 30V정도까지 고려하였기 때문에 분압 하였다.  프로브 + 측 단자에서 10K 저항을 먼저 연결하고 중간부분을 ESP8266의 A0 핀에 연결, 1K 저항을 직렬연결후 Ground에 연결하면 된다.
 위의 그림에는 트래킹모드(=로깅모드) 시작 버튼에 대한 내용이 없는데, 실제 적용한 모델에서는 D0핀에 푸쉬버튼 하나를 달아서 사용하는 형태이다.

5. 소스코드

 소프트웨어적으로 간단하긴 하지만, 사실 트래킹 모드라는 컨셉을 생각하여 적용한 터라 아주 짧지 않은 코드이다. 트래킹모드(=로깅모드)라 하면 측정 시작 이후 위상 변화가 생길때까지 대기하다가, 위상 변화가 생기면 그때부터 설정된 시간만큼 기록하고 화면을 스크롤 하면서 보여주는 모드이다.

아래에 있는 코드가 이번 오실로스코프제작에 생성한 코드의 전체 이다.
(한글출력이나 OLED 제어용 코드들은 이전 포스트에 소개한바 있으므로 생략한다.)

Oscilloscope.ino
#include <Arduino.h>
#include "Display_SSD1306.h"
#include "OscilloscopeClass.h"

#define TOUCH_BUTTON    D0  // 트래킹 시작 버튼
Display_SSD1306 Oled(-1);

void setup() {
  Serial.begin(115200);
  pinMode(TOUCH_BUTTON, INPUT);

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

  // Callback 함수를 설정해 주면 필요시 호출 하여 사용함.
  Oscilloscope.begin(12,
      [](void) { Oled.clearDisplay(); },
      [](int16_t x, int16_t y, uint16_t color) { Oled.drawPixel(x, y, color);  },
      [](void) { Oled.display(); }
  );

  delay(100);
}

void loop() {
  if (Oscilloscope.feed() == 0) { // 트래킹 상태가 아니면
    if (digitalRead(TOUCH_BUTTON)) { // 버튼 상태가 변경되었으면..
      Oscilloscope.startLogging(2000);     // 0.20초 로깅
      Oscilloscope.showLogging(); // 측정 결과를 보여줌.
    }   
  }
}



OscilloscopeClass.h
/*------------------------------------------------------------------
   BSD License

   Copyright (c) 2018 terminal0070@gmail.com 
  ------------------------------------------------------------------*/

#ifndef OscilloscopeClass_h
#define OscilloscopeClass_h
#include <Arduino.h>
#include "HanDraw.h"

#define MAX_LOGGING_NUM           20000  //최대 2만개의 데이터를 읽는다.
#define LOGGING_START_WAIT_TIME   2000   //로깅 요청시 얼마나 기다릴것인다.. 밀리세컨드 단위 (500 단위로 설정바람)
#define READ_PIN                  A0    // Wemos d1 mini는 아날로그 핀이 하나다.
enum oscilloscope_status {
  WAITING = 0,
  LOGGING = 1,
  SHOWING_LOG = 2
};

class OscilloscopeClass  {
  protected:
    uint8_t m_aryLogginData[MAX_LOGGING_NUM + 128 * 4];
    uint16_t m_dLogginIndex; // 현재 몇번째 읽엇는가 또는 몇번째 부터 표시하고 있는가.
    uint16_t m_dLogginSize; // 전체적으로 몇개의 데이터를 읽을 것인가
    uint16_t m_dMaxValue;   // 측정한 값중에서 최대값
    uint16_t m_dTotalLoggingTime; // 실제 로깅 하는데 소요한 시간
    oscilloscope_status m_dStatus;    // 현재 진행중인 모드
    float m_fFrequency;   // 주파수
  private:
    unsigned long m_dLLastFeedingTIme;//로깅 데이터를 보여주기 시작한 시간
    unsigned long m_dLLastCheckingTIme;//로깅 데이터를 보여주기 시작한 시간
    void displayChart();
    int16_t findUpPosition(int16_t startPos);
    void findFrequence();
   
  public:
    OscilloscopeClass();
    virtual ~OscilloscopeClass();
    void begin(int8_t fontSize, CLEAR_FUNC clearFunction , DRAW_PIXEL_FUNC drawPixelFunction, DISPLAY_FUNC displayFunction );
    void end();
    void clear();
    void display();
    void drawPixel(int16_t x, int16_t y, uint16_t color);
    void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
    void drawString(int16_t x, int16_t y, char* message);
    void drawString(int16_t x, int16_t y, const char* message);
    void drawString(int16_t x, int16_t y, String message);
    float getVolt();
    void startLogging(int16_t loggingSize);
    void showLogging();
    void displayFreqAndVolt();
    void showCurrent();
    int feed();

};
extern OscilloscopeClass Oscilloscope;

#endif



OscilloscopeClass.cpp
/*
   BSD License

   Copyright (c) 2018 terminal0070@gmail.com
*/

#include "OscilloscopeClass.h"

#ifndef SWAP_INT16_T
#define SWAP_INT16_T(a, b) { int16_t t = a; a = b; b = t; }
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif

#ifndef BLACK
#define BLACK 0
#endif

#ifndef WHITE
#define WHITE 1
#endif

int loopX; // for loop
// 범용 사용.. 특히나..차트 그릴때 사용
uint8_t prev1, prev2, prev3, prev4, now1, now2, now3, now4;

OscilloscopeClass::OscilloscopeClass() {}
OscilloscopeClass::~OscilloscopeClass() {
  end();
}
void OscilloscopeClass::end() {}

// 시작시 몇 사이즈의 폰트 크기를 사용할 것인가를 정한다.
// 3개의 callback function을 이용하여 디스플레이를 제어한다.
void OscilloscopeClass::begin(int8_t fontSize,
CLEAR_FUNC clearFunction, DRAW_PIXEL_FUNC drawPixelFunction, DISPLAY_FUNC displayFunction) {
  HanDraw.begin(12, clearFunction, drawPixelFunction, displayFunction);
  m_dLogginIndex = 0;
  m_dLogginSize = MAX_LOGGING_NUM;
  m_dStatus = WAITING;
  m_fFrequency = 1.0;
  pinMode(READ_PIN, INPUT); // bolt및 frequency
}

// 화면을 지우는 것은 콜백 함수를 부른다.
void OscilloscopeClass::clear() {
  HanDraw.clear();
}

// 역시 화면에 표시하는 것은 콜백 함수를 부른다
void OscilloscopeClass::display() {
  HanDraw.display();
}

void OscilloscopeClass::drawPixel(int16_t x, int16_t y, uint16_t color) {
  HanDraw.drawPixel(x, y, color);
}

void OscilloscopeClass::drawString(int16_t x, int16_t y, char* message) {
  HanDraw.drawString(x, y, (const char*)message);
}
void OscilloscopeClass::drawString(int16_t x, int16_t y, String message) {
  HanDraw.drawString(x, y, message.c_str());
}

void OscilloscopeClass::drawString(int16_t x, int16_t y, const char* message) {
  HanDraw.drawString(x, y, message);
}

void OscilloscopeClass::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) {
  int16_t steep = abs(y1 - y0) > abs(x1 - x0);
  if (steep) {
    SWAP_INT16_T(x0, y0);
    SWAP_INT16_T(x1, y1);
  }

  if (x0 > x1) {
    SWAP_INT16_T(x0, x1);
    SWAP_INT16_T(y0, y1);
  }

  int16_t dx, dy;
  dx = x1 - x0;
  dy = abs(y1 - y0);

  int16_t err = dx / 2;
  int16_t ystep;

  if (y0 < y1) {
    ystep = 1;
  } else {
    ystep = -1;
  }

  for (; x0 <= x1; x0++) {
    if (steep) {
      HanDraw.drawPixel(y0, x0, color);
    } else {
      HanDraw.drawPixel(x0, y0, color);
    }
    err -= dy;
    if (err < 0) {
      y0 += ystep;
      err += dx;
    }
  }
}

// loggingSize가 10000인 경우 약 1초간 로깅한다.
void OscilloscopeClass::startLogging(int16_t loggingSize) {
  if (loggingSize > MAX_LOGGING_NUM) {
    loggingSize = MAX_LOGGING_NUM;
  } else  if (loggingSize < 500) {
    loggingSize = 500;
  }
  m_dLogginIndex = 0;
  m_dLogginSize = loggingSize;
  m_dStatus = LOGGING;
  int waitCnt = LOGGING_START_WAIT_TIME /  500;
  char buf[128];

  HanDraw.clear();
  HanDraw.drawString(22, 22, "시그널 대기중");
  HanDraw.display();
  m_dMaxValue = 1.0;
  uint16_t check = analogRead(READ_PIN);
  m_dMaxValue = MAX(m_dMaxValue, check);
  m_aryLogginData[m_dLogginIndex++] = (check / 10);

  if (check < 25) check = 0;
  uint16_t current = 0;
  while (true) {
    yield();
    current = analogRead(READ_PIN);
    if (check == 0) {
      if (current > 25) break;
    } else {
      if (current < 15) break;
    }
  }

  uint16_t startTime = millis();

  m_dMaxValue = MAX(m_dMaxValue, current);
  m_aryLogginData[m_dLogginIndex++] = (current / 10);

  for (loopX = 1; loopX < m_dLogginSize; loopX ++) {
    current = analogRead(READ_PIN); // ESP8266 기준으로 1회에 약 100마이크로초 정도 걸린다.
    m_dMaxValue = MAX(m_dMaxValue, current);
    // 굳이 10으로 나누어서 넣는 이유는 한 바이트 범위 넣기 위함이다.
    m_aryLogginData[m_dLogginIndex++] = (current / 10);
  }
  m_dTotalLoggingTime = millis() - startTime;
}
void OscilloscopeClass::showLogging() {
  if (0 == m_dLogginSize) {
    return; // 보여줄 것이 없음으로 포기~
  }
  m_dStatus = SHOWING_LOG;
  HanDraw.clear();
  HanDraw.drawString(31, 46, "표시 준비중");
  HanDraw.display();

  m_dLLastCheckingTIme = millis();
  m_dLogginIndex = 0;
  findFrequence();
  displayChart();
  delay(500);
}

void OscilloscopeClass::displayChart() {
  HanDraw.clear();
  float fDrawScale = 1024 / m_dMaxValue / 5.13;   // 최대측정값 대비 그리기 비율
  uint16_t hop = m_dLogginSize / 2;

  now1 = m_aryLogginData[hop * 0 + m_dLogginIndex] * fDrawScale;
  now2 = m_aryLogginData[hop * 1 + m_dLogginIndex] * fDrawScale;
  for (loopX = 1; loopX <= 127; loopX++) {
    prev1 = now1;
    prev2 = now2;
    now1 = m_aryLogginData[hop * 0 + m_dLogginIndex + loopX] * fDrawScale;
    now2 = m_aryLogginData[hop * 1 + m_dLogginIndex + loopX] * fDrawScale;
    drawLine(loopX , 35 - prev1, loopX , 35 - now1, WHITE);
    drawLine(loopX , 63 - prev2, loopX , 63 - now2, WHITE);
  }

  // 차트에서 표시 범위가 얼만큼인지.스크롤 바로 표시.
  int scrollPos = 39 * m_dLogginIndex / (hop - 128);
  drawLine(87 , 6, 127 , 6, WHITE);
  drawLine(86 + scrollPos , 4, 86 + scrollPos , 8, WHITE);
  drawLine(87 + scrollPos , 2, 87 + scrollPos , 10, WHITE);
  drawLine(88 + scrollPos , 4, 88 + scrollPos , 8, WHITE);

  displayFreqAndVolt();
  HanDraw.display();
}

void OscilloscopeClass::displayFreqAndVolt() {
  if (m_fFrequency > 0) {
    HanDraw.drawString(0, 0, String((int)m_fFrequency) + "Hz " + String(getVolt()) + "V");
  }  else {
    HanDraw.drawString(0, 0, String("--Hz ") + String(getVolt()) + "V");
  }
}

// 파형의 아래 부분에서 올라가기 시작 하는 부분을 찾는다.
int16_t OscilloscopeClass::findUpPosition(int16_t startPos) {
  uint16_t i;
  if (startPos < 0)
    return -1;
  for (i = startPos; i > 0; i--) {
    if (m_aryLogginData[i] < 2) {
      while (--i > 0) {
        if (m_aryLogginData[i] > 2) {
          return i;
        }
      }
      break;
    }
  }
  return -1;
}

// 주파수를 찾으면서 최대 값도 찾는다.
void OscilloscopeClass::findFrequence() {
  double stime, ttime, ttime2;
  int16_t j;
  int16_t etime = m_dLogginSize - 1;
  //float frequencyArray[10]; // frequency는 10개의 측정 값을 저장하고 이중 제일 큰것을 화면에표시한다.
  //int   frequencyIndex = 0;        // 배열의 어느 부분에 저장할 것인가를 표시
  double factor = m_dTotalLoggingTime * 996.15 / m_dLogginSize; // 원래는 1000을 곱함. 약간의 보정으로 996정도;;;
  ttime = 1;
  for (j = 0; j < 24; j ++) {
    stime = findUpPosition(etime);
    etime = findUpPosition(stime);
    if (etime <= 0 | stime <= 0)
      ttime2 = -1000000;
    else
      ttime2 = (stime - etime) * factor; // 90.91;
    //    Serial.println(ttime2);
    ttime = MAX(ttime, ttime2);
  }
  if (1 == ttime)
    m_fFrequency = 0.0;
  else
    m_fFrequency = (float)1000000 / (float)ttime;
}

/*
  // 이건 저항 연결 상태에 따라서 다르다.
  // 3.3v 전압이 직접  A0에 연결되는 경우
  float OscilloscopeClass::getVolt(int16_t val) {
  return  val * 3.3 / 1024.0;
  }

  // 100k 저항 하나, 21.5k 저항 하나를 연결하고 중간에서 체크할경우
  // 약간의 보정을 통해서... 3.3v ~ 6v 정도 구간이 테스터기와
  // 비슷한 값이 나오도록 했다.
  float OscilloscopeClass::getVolt(int16_t val) {
  float volt = val / 1024.0;
  volt += (1024 - volt) * 0.000019;
  volt *= 13.815;
  volt -= 0.02;
  if (volt < 0.25)
    volt = 0.0;
  return volt;
  }
*/

// 100k 저항 하나, 43k 저항 3개를 병렬 연결하고 중간에서 체크할경우
// 약간의 보정을 통해서... 3.3v ~ 6v 정도 구간이 테스터기와
// 비슷한 값이 나오도록 했다.
float OscilloscopeClass::getVolt() {
  float volt = m_dMaxValue / 1024.0;
  volt += (1024 - volt) * 0.0000042;
  //  volt *= 28.035;
  volt *= 28.135;
  volt -= 0.02;
  if (volt < 0.25)
    volt = 0.0;
  return volt;
}

// 현재 측정하는 값을 바로 바로 보여주는 경우
void OscilloscopeClass::showCurrent() {
  unsigned long etime, stime, ttime, xtime, scnt, ecnt, pos;
  scnt = 0;
  ecnt = 0;
  etime = 0;
  stime = 0;
  // 파형이 처음 올라가는 부분을 찾는다.
  while (scnt++ < 400) {
    pos = analogRead(READ_PIN);
    if (pos < 2) {
      while (ecnt++ < 400) {
        pos = analogRead(READ_PIN);
        if (pos > 2) {
          stime = micros();
          break;
        }
      }
      break;
    }
  }
  xtime = micros() - stime;
  scnt = 0;
  ecnt = 0;
  // 파형의 두번째 올라가는 부분을 찾는다.
  while (scnt++ < 400) {
    pos = analogRead(READ_PIN);
    if (pos < 2) {
      while (ecnt++ < 400) {
        pos = analogRead(READ_PIN);
        if (pos > 2) {
          etime = micros();
          break;
        }
      }
      break;
    }
  }

  if (etime == 0 | stime == 0) {
    ttime = -1000000;
  }
  else {
    ttime = etime - stime - xtime;
  }
  m_fFrequency = (float)1000000 / (float)ttime;
  m_dLogginIndex = 0;
  m_dLogginSize = 128;
  uint16_t current = 0;
  m_dMaxValue = 1.0;
  uint16_t startTime = millis();
  for (loopX = 0; loopX < m_dLogginSize; loopX ++) {
    current = analogRead(READ_PIN);
    m_dMaxValue = MAX(m_dMaxValue, current);
    m_aryLogginData[m_dLogginIndex++] = (current / 10);
  }
  m_dTotalLoggingTime = millis() - startTime;
  m_dLogginIndex = 0;
  HanDraw.clear();
  float fDrawScale = 1024 / m_dMaxValue / 2.2;   // 최대측정값 대비 그리기 비율
  now1 = m_aryLogginData[0] * fDrawScale;
  for (loopX = 1; loopX <= 127; loopX++) {
    prev1 = now1;
    now1 = m_aryLogginData[loopX] * fDrawScale;
    drawLine(loopX , 63 - prev1, loopX , 63 - now1, WHITE);
  }
  findFrequence() ;
  displayFreqAndVolt();
  HanDraw.display();
}

// 오실로스코프 기능이 동작하도록 주기적으로 시간을 배분한다.
int OscilloscopeClass::feed() {
  m_dLLastFeedingTIme = millis();
  // 1초에 최대 20번 진행 한다.
  if (m_dStatus == SHOWING_LOG) {
    if ((m_dLLastFeedingTIme - m_dLLastCheckingTIme) < 30) {
      return 2;
    }
    m_dLLastCheckingTIme = m_dLLastFeedingTIme;
    m_dLogginIndex += 2;
    displayChart();
    if (m_dLogginIndex  >= (m_dLogginSize / 2 - 128)) {
      m_dStatus = WAITING;
      delay(2000);
      HanDraw.clear();
      HanDraw.display();
    }
    return 1;
  } else if (m_dStatus == WAITING) {
    showCurrent();
  }
  return 0;
}

OscilloscopeClass Oscilloscope;




6. 실제 납땜하고 적용한 모습.


[ 그림 추가 예정]


7. 끝으로

  이 글을 적는 시점 기준으로 수개월 전에 Tiny Oscilloscope라는 이름을 붙여서 배달했다. 지인은 상당히 만족해 했으며, 나에게 소스코드를 받아가서 아래의 그림과 같이 3개를 연결하여 쓰리채널 오실로스코프로서 사용하였다. (숨은그림 찾이인가... 아래 그림을 잘 찾아 보면 0.98인치 OLED가 3개 있는 곳을 찾을 수 있다.)

지인이 실제 사용하는 모습
1 채널짜리 간이형 오실로스코프를 만들어서 배달 했더니, 바로 3 채널 구성하여 사용하시는 지인의 모습을 보면서 2차 버전의 오실로스코프를 제작하게 되는 계기가 되었다.





ESP8266 Digital Input Pin의 불편한 진실

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