2018년 11월 29일 목요일

ESP로 오실로스코프를 만들자 - #4 (최소 크기 및 비용편) - 소스코드 #1

이 소스코드는 [ 최소 비용의 오실로스코프 만들기 ] 에 포함된 소스코드이다. 상세 내용은 링크를 참조하자.

1.  ./Web_Oscilloscope_32.ino
/* ------------------------------------------------------
  Tiny + Cheap Oscilloscope
  [[ AP MODE  Info]]  SSID : OSCIL_V06,  PWD  : 12345678

  +-------- GPL License --------------------------------+
  do not remove contact & url
  contact :  terminal0070@gmail.com
  blog :  https://andy-power.blogspot.com/
-------------------------------------------------------- */
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <SPIFFS.h>
#include "OscilloscopeClass.h"

#define LED_PIN       2               // ESP32 mini
#define SSID      "your router SSID"  // AP 모드인 경우 사용하지 않음
#define PWD       "your router PWD"   // AP 모드인 경우 사용하지 않음
#define AP_MODE   true                // AP 모드인지 아닌지..
                                      // Station 모드인 경우 false 하면 된다.
//--------------------------------------------------------------
// Global variables
//--------------------------------------------------------------                        
uint32_t g_dPrevMainInfoTime = 0// 화면 정보를 마지막으로 전달한 시각
uint32_t g_dPrevSubInfoTime = 0;  // 부가 정보를 마지막으로 전달한 시각
uint8_t  g_dClientCnt = 0;        // 연결된 클라이언트 
WebServer g_WebServer(80);         // WebServer
WebSocketsServer g_WebSocket(8080); // Websocket 서버
OscilloscopeClass oscilloscope;     // 오실로스코프

//--------------------------------------------------------------
// Setup function
// start wifi + webserver + websocket server + oscilloscope
//--------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  if (!SPIFFS.begin()) {
    Serial.println("SPIFFS Mount Failed");
    return;
  }
  pinMode(LED_PIN, OUTPUT);
  wifiProcess();      // start wifi
  g_WebSocket.begin();
  g_WebSocket.onEvent(webSocketEvent);
  g_WebServer.begin();
  g_WebServer.on("/", handleWebRoot);
  oscilloscope.begin();
}

//--------------------------------------------------------------
// Setup function
// start wifi + webserver + websocket server + oscilloscope
//--------------------------------------------------------------
void loop() {
  if (AP_MODE == false) {
    wifiProcess();
  }
  g_WebSocket.loop();
  g_WebServer.handleClient();
  if (oscilloscope.loop()) {
    return;
  }
  uint32_t now = millis();
  if (now - g_dPrevMainInfoTime < 100) {
    if (now - g_dPrevSubInfoTime > 520) {
      // send sub data ( 2 frames per 1s )
      g_WebSocket.broadcastBIN(oscilloscope.getSubInfo(), oscilloscope.getSubInfoSize());
      g_dPrevSubInfoTime = now;
    } else {
      delay(2);
    }
    return;
  }
  // send main data  ( 10 frames per 1s)
  g_WebSocket.broadcastBIN(oscilloscope.getScreenData(), oscilloscope.getScreenDataSize());

  g_dPrevMainInfoTime = now;
  return;
}

//--------------------------------------------------------------
// build wifi connection
//--------------------------------------------------------------
void wifiProcess() {
  if (AP_MODE) {
    // start soft ap
    WiFi.mode(WIFI_AP_STA);
    WiFi.softAP("OSCIL_V06""12345678");
  } else {
    // 무선 공유기에 붙여서 쓰는 경우
    if (WiFi.status() != WL_CONNECTED) {
      bool ledOn = false;
      WiFi.begin(SSID, PWD);
      Serial.println("Connecting to WiFi..");
      while (WiFi.status() != WL_CONNECTED) {
        digitalWrite(LED_PIN, ledOn);
        ledOn = !ledOn;
        delay(200);
      }
      Serial.println(WiFi.localIP());
    }
  }
}

//--------------------------------------------------------------
// send html
//--------------------------------------------------------------
void handleWebRoot() {
  File file = SPIFFS.open("/web_interface.html""r");
  String contents = file.readStringUntil(NULL);
  if (!AP_MODE) {
    contents.replace("192.168.4.1", WiFi.localIP().toString());
  }
  g_WebServer.send200"text/html", contents );
}

//--------------------------------------------------------------
// process websocket event
//--------------------------------------------------------------
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
  switch (type) {
    case WStype_CONNECTED:
      g_dClientCnt++;
      if (g_dClientCnt > 0) { // 연결된 클라이언트가 있으면 LED 켜기
        digitalWrite(LED_PIN, HIGH);
      }
      break;
    case WStype_DISCONNECTED:
      g_dClientCnt--;
      if (g_dClientCnt <= 0) { // 연결된 클라이언트가 없으면 LED 끄기
        digitalWrite(LED_PIN, LOW);
      }
      break;
    case WStype_BIN: // 모든 데이터는 바이너리로 온다.
      if (length >= 2) {
        oscilloscope.processUI(payload);
        g_dPrevSubInfoTime = 0;
      }
      break;
    case WStype_TEXT: break;
    case WStype_ERROR:
    case WStype_FRAGMENT_TEXT_START:
    case WStype_FRAGMENT_BIN_START:
    case WStype_FRAGMENT:
    case WStype_FRAGMENT_FIN:
      break;
  }
}


2. ./Oscilloscope.h
/* ------------------------------------------------------
  Tiny + Cheap Oscilloscope
  +-------- GPL License --------------------------------+
  do not remove contact & url
  contact :  terminal0070@gmail.com
  blog :  https://andy-power.blogspot.com/
  -------------------------------------------------------- */
#ifndef _OSCILLOSCOPE_CLASS_H_
#define _OSCILLOSCOPE_CLASS_H_

#include <Arduino.h>
#define ANALOG_PIN0   34  // 아날로그 입력 첫번째 .
#define ANALOG_PIN1   33
#define ANALOG_PIN2   35

#define CHART_WIDTH       300     // 차트 부분만 해당하는 가로 크기
                                  //  숫자는 화면에 한번에 표시하는 너비이며,
                                  // 웹방식에서는 통신을 고려해야 하기 때문에  숫자는 그닥이다.
#define CHART_HEIGHT      190     // 차트 부분만 해당하는 세로 크기
#define MAX_READ_NUM      (CHART_WIDTH + 80)  // 최대 데이터 읽기..화면에 표시하는 범위보다 살짝 넓게..
#define SECTOR_WIDTH      (MAX_READ_NUM * 8)  // maximum of each channel
#define CHANNEL_NUM       3      // 현재는 3 채널만 읽는다..(4개가 필요할까?)

class OscilloscopeClass {
  public:
    OscilloscopeClass();
    void begin(void);
    uint16_t getScreenDataSize(void);
    uint8_t *getScreenData(void);
    uint16_t getSubInfoSize(void);
    uint8_t *getSubInfo(void);
    void processUI(uint8_t* payload);
    float getVolt(uint16_t analogPinValue);
    bool loop(void);
  private:
     // 현재 상황에서 ADC  통하여 읽어야 하는 데이터 개수..
     // Tracking mode 경우 평소의 3배까지 읽는다.
    uint32_t m_dNowReadNum;      

    // ADC 부터 데이터를 읽어서 저장하는 부분
    // 일반적으로는 MAX_READ_NUM * 2 크기만큼 읽지만, Tracking모드인 경우 네배까지 읽음.
    uint8_t* m_pOriginDatas;    // 측정한 값들을 저장해 두는 
    uint8_t* m_pScreenDatas;    // 화면출력용으로 정제된 데이터를 저장하는 
    uint8_t  m_arySubInfos[40];           // 클라이언트에게 부가정보 보내기용 버퍼
    uint16_t m_aryMaxValues[CHANNEL_NUM]; //  채널별 측정된 최고값
    uint8_t  m_dChNum;                    // 현재 몇개의 채널이 켜져 있는가?
    bool     m_bCollapseChannel;          // 모아 보기 인지나누어 보기 인지 ..
   
    uint8_t  m_dTracking;       // Tracking 모드인가 아닌가..
    bool     m_bWaitHigh;       // Tracking 모드에서 기다리는 신호가 무엇인지(0에서 신호가 올라가는 것을 대기?)
    uint16_t m_dScrollStart;    // 측정된 데이터중에서 화면에 표시하는 시작 위치 (스크롤 위치)

    // 정밀도.. 1 가장 좋은 것이고.. 커질수록 나빠진다...
    //  m_dScale  3 경우 ADC 부터 세번 읽어서 두개는 버리고 하나의 값만 쓴다.
    uint8_t  m_dScale;   
    // 이건 한번 측정한 값을 얼마나.. 넓게 표시하냐의 단위이다
    //  값이 3 경우, 1 측정한 값을 화면에 3개의 픽셀로 표시한다.
    uint8_t  m_dSubScale;
   
    uint16_t m_dFrequency;        // 채널1 주파수..

    void readADC();               // 아날로그 핀들을 이용하여 데이터를 읽는다.
    void makeDisplayingtDatas();  // 화면 표시에 필요한 정보를 만든다.
    void enterTrackingMode();     // Tracking 모드로 진입한다.
    void trackingModeProcess();   // Tracking 모드에서 위상 변화를 대기중일때 처리함수
    void leaveTrackingMode();     // Tracking 모드를 종료한다.
};

#endif //_OSCILLOSCOPE_CLASS_H_


3. ./Oscilloscope.cpp
/* ------------------------------------------------------
  Tiny + Cheap Oscilloscope
  +-------- GPL License --------------------------------+
  do not remove contact & url
  contact :  terminal0070@gmail.com
  blog :  https://andy-power.blogspot.com/
  -------------------------------------------------------- */
#include "OscilloscopeClass.h"

#define DEFAULT_MAX  7
#define min(a,b)  (a > b ? b : a)
#define max(a,b)  (a > b ? a : b)

OscilloscopeClass::OscilloscopeClass() {
  m_dChNum = 3;
  m_bCollapseChannel = false;
  m_dTracking = 0;    //  값은 3종류 이며 0이면 노말모드, 1이면 트래킹 대기 모드, 2이면 트래킹정보 표시 모드
  m_dScale = 1;       // 화면 축소비율
  m_dSubScale = 1;    // 화면 확대 비율
  m_dScrollStart = 0;
  m_dFrequency = 200// 패널 1 기준 주파수..
  m_dNowReadNum = MAX_READ_NUM * 2;
}

// 반드시 초기에 한번 호출되어야 한다.
void OscilloscopeClass::begin() {
  m_pOriginDatas = (uint8_t*)malloc(SECTOR_WIDTH * CHANNEL_NUM);  // almost 10K
  m_pScreenDatas  = (uint8_t*)malloc(CHART_WIDTH * CHANNEL_NUM + 2);

  // 측정용  정의
  pinMode(ANALOG_PIN0, INPUT);
  pinMode(ANALOG_PIN1, INPUT);
  pinMode(ANALOG_PIN2, INPUT);

  // ESP32 측정 데시벨 정의
  analogSetAttenuation(ADC_6db); // 2.2v 이상이면 최대값  // 11db  하면 노이즈가 생긴다.
  // 10자리 수로 읽는다. (16비트까지 가능할것 같은데.. 의미가 있는지 모르겟슴)
  analogReadResolution(10);      // 최대 값이 1024 설정  비트수가 너무 높으면노이즈가 생긴다.
  delay(10);
}

// 최대 3개의 ADC 핀으로 부터 정보를 읽는다.
// 읽은 것은 m_pOriginDatas 저장한다.
void OscilloscopeClass::readADC() {
  int i, j;
  if (m_dTracking == 0) {   // Tracking  모드가 아닌경우
    int check = 0;
    uint32_t tstart;
    // 파형의 첫번째 올라 가는 부분 찾기...
    while (check++ < 2000) {
      if (analogRead(ANALOG_PIN0) < 3 ) {
        check = 0;
        while (check++ < 2000) {
          if (analogRead(ANALOG_PIN0) > 6) {
            tstart = micros();
            break;
          }
        }
        break;
      }
    }

    // 파형의 두번째 올라 가는 부분 찾기...
    check = 0;
    while (check++ < 2000) {
      if (analogRead(ANALOG_PIN0) < 3 ) {
        check = 0;
        while (check++ < 2000) {
          if (analogRead(ANALOG_PIN0) > 6) {
            break;
          }
        }
        break;
      }
    }

    // find frequency
    uint32_t tend = micros();
    double pulseTime = (double)(tend - tstart);
    pulseTime = max(pulseTime, 1);
    m_dFrequency = 1001000 / pulseTime; //약간의 보정을 위해서 100.01% 값으로 계산
    m_dFrequency = max(m_dFrequency, 10);
  }

  // 정해진 숫자만큼 데이터를 읽는다.
  for (i = 0; i < m_dNowReadNum; i ++) {
    for (j = 0; j < m_dScale; j++) { // 이건 스킵의 개념이다.
      m_pOriginDatas[SECTOR_WIDTH * 0 + i] = (uint8_t)(analogRead(ANALOG_PIN0) >> 2);
      if (m_dChNum >= 2) {
        m_pOriginDatas[SECTOR_WIDTH * 1 + i] = (uint8_t)(analogRead(ANALOG_PIN1) >> 2);
      }
      if (m_dChNum >= 3) {
        m_pOriginDatas[SECTOR_WIDTH * 2 + i] = (uint8_t)(analogRead(ANALOG_PIN2) >> 2);
      }
    }
  }

  //  채널별로 최대값을 찾는다.
  m_aryMaxValues[0] = DEFAULT_MAX; // 기본 최대값을 설정.
  m_aryMaxValues[1] = DEFAULT_MAX; // 어느 정도 레벨 이하는 바닥에 깔리게 만들기 위함임
  m_aryMaxValues[2] = DEFAULT_MAX; // 측정하는 것을 고려하여 변경해야 할수도 있음..
  for (uint16_t i = 0; i < m_dNowReadNum; i ++) {
    m_aryMaxValues[0] = max(m_aryMaxValues[0], m_pOriginDatas[i + SECTOR_WIDTH * 0]);
    m_aryMaxValues[1] = max(m_aryMaxValues[1], m_pOriginDatas[i + SECTOR_WIDTH * 1]);
    m_aryMaxValues[2] = max(m_aryMaxValues[2], m_pOriginDatas[i + SECTOR_WIDTH * 2]);
  }
}

// 이미 읽어 놓은 ADC 값들을 화면 표시용 버퍼에 계산해서 넣는다.
// 클라이언트에 전달할때는 최대값을 256으로 맞추어 전달한다.
void OscilloscopeClass::makeDisplayingtDatas() {
  uint16_t i, j, k;
  float aryRatio[3];

  if (m_bCollapseChannel) {
    int maxpos = max(m_aryMaxValues[0], m_aryMaxValues[1]);
    maxpos = max(maxpos, m_aryMaxValues[2]);
    aryRatio[0] = 250 / (float)maxpos;
    aryRatio[1] = 250 / (float)maxpos;
    aryRatio[2] = 250 / (float)maxpos;
  } else {
    aryRatio[0] = 250 / (float)m_aryMaxValues[0];
    aryRatio[1] = 250 / (float)m_aryMaxValues[1];
    aryRatio[2] = 250 / (float)m_aryMaxValues[2];
  }

  // 측정값들이 미세한 경우 상하폭을 확대하지 않음.
  for (i = 0; i < 3; i ++) {
    if (m_aryMaxValues[i] < DEFAULT_MAX) aryRatio[i] = 1.0;
  }

  m_pScreenDatas[0] = 0;         // main data flag
  m_pScreenDatas[1] = m_dChNum;  // 2nd byte is #channel
  uint8_t * tmpPointer = m_pScreenDatas + 2;
  int dataIndex = m_dScrollStart;
  for (i = 0; i < CHART_WIDTH;) {
    for (j = 0; j < m_dSubScale; j++) {
      tmpPointer[i + CHART_WIDTH * 0] = (uint8_t)(aryRatio[0] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 0]);
      tmpPointer[i + CHART_WIDTH * 1] = (uint8_t)(aryRatio[1] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 1]);
      tmpPointer[i + CHART_WIDTH * 2] = (uint8_t)(aryRatio[2] * m_pOriginDatas[dataIndex + SECTOR_WIDTH * 2]);
      i++;
      if (i >= CHART_WIDTH) break;
    }
    dataIndex++;
  }
}

//----------------------------------------------------------------------
// 전압 계산
//  330R 저항 하나, 100R 저항을 연결하여 분압한 경우이다.
//----------------------------------------------------------------------
//   signal ----- 330R ---+---- 100R ----------  GND
//                        |
//                       ADC
//----------------------------------------------------------------------
// 여러 차수 방정식을 고려해 보았는데 1 방정식이 가장 무난했다.
float OscilloscopeClass::getVolt(uint16_t analogPinValue) {
  if (DEFAULT_MAX >= analogPinValue) { // 너무 적은 값이 측정되면 전압을 0v 표시한다.
    return 0.000001;
  } else {
    return (0.0289 * analogPinValue + 0.592);
  }
}

//----------------------------------------------------------------------
// 중요 모드인 트래킹 모드 진입함수 이다.
// 트래킹 모드는 대기 모드후에 위상변화가 발생하면
// 그때부터 MAX_READ_NUM * 4 만큼 측정하여 화면에 표시한다.
// 트래킹 모드를 종료하기 전까지는 이미 측정된 내용만을 사용한다.
//----------------------------------------------------------------------
void OscilloscopeClass::enterTrackingMode() {
  m_dTracking = 1;
  m_dScrollStart = 0;
  m_dSubScale = 1;
  m_dScale = 1;
  m_dNowReadNum = MAX_READ_NUM * 4;

  // 일단 현재의 채널 값으로 차트를 평행선만 들어가게 만들고.
  uint8_t ch0 =  analogRead(ANALOG_PIN0) >> 2;
  uint8_t ch1 =  analogRead(ANALOG_PIN1) >> 2;
  uint8_t ch2 =  analogRead(ANALOG_PIN2) >> 2;
  for (int i = 0; i < m_dNowReadNum; i ++) {
    m_pOriginDatas[i + SECTOR_WIDTH * 0] = ch0;
    m_pOriginDatas[i + SECTOR_WIDTH * 1] = ch1;
    m_pOriginDatas[i + SECTOR_WIDTH * 2] = ch2;
  }

  // 위상 추적을 위에서 아래로 내려올때 할것인가,
  // 아래에서 위로 올라갈때 할것인가를 정하는 부분
  uint32_t loopCnt = 0;
  uint16_t chk =  analogRead(ANALOG_PIN0);
  for (int i = 0; i < 100; i ++) {
    chk = max(chk, analogRead(ANALOG_PIN0));
  }
  m_bWaitHigh = true;
  //  100 측정한 최대값이 일정값 이상이면,
  // 지금부터 내려가는 시점을 찾게 된다.
  if (chk > DEFAULT_MAX) {
    m_bWaitHigh = false;
  }
}

// 일단 트래킹 모드가 시작되면 채널기준 위상 변화가 생기지 않으면
// 그대로 멈춰 있는다위상 변화가 감지되면 그때부터 MAX_READ_NUM * 4 회를 측정하고
// 화면에 표시한다.
void OscilloscopeClass::trackingModeProcess() {
  uint16_t loopCnt = 0;
  uint16_t chk;

  // 위상이 변할때 까지 기다린다.
  // 다만 어느정도 기다리면 그외에 ESP 해야  일이 있을지도
  // 모르기 때문에 잠시 다른 작업을 하기 위하여
  // 함수를 빠져 나간다.
  while (loopCnt < 10000) {
    chk =  analogRead(ANALOG_PIN0);
    if (m_bWaitHigh && chk > DEFAULT_MAX) {
      break;
    } else if (!m_bWaitHigh && chk < 2) {
      break;
    }
    loopCnt++;
  }

  if (loopCnt >= 10000) {
    return;
  }

  // 위상 변화가 감지 되었으면 그때부터 MAX_READ_NUM * 4 회를 읽고 화면에 표시한다.
  readADC();
  makeDisplayingtDatas();
  m_dTracking = 2;  //  값은 3종류 이며 0이면 노말모드, 1이면 트래킹 대기 모드, 2이면 트래킹정보 표시 모드
}

// 트래킹된 정보를 표시하다가
// 사용자가 버튼을 눌러서 노말 모드로 돌아가는 경우
void OscilloscopeClass::leaveTrackingMode() {
  m_dTracking = 0;
  m_dNowReadNum = MAX_READ_NUM * 2;
  m_dScrollStart = 0;
  m_dSubScale = 1;
  m_dScale = 1;
}

// 화면 표시 데이터 크기
uint16_t OscilloscopeClass::getScreenDataSize(void) {
  return 2 + CHART_WIDTH * m_dChNum;
}

// 화면 표시용 데이터를 반환한다.
uint8_t *OscilloscopeClass::getScreenData(void) {
  if (m_dTracking == 0) {
    readADC();
    makeDisplayingtDatas();
  } else if (m_dTracking == 2) {
    makeDisplayingtDatas();
  }
  return m_pScreenDatas;
}

// 메인데이터 이외에 부가 데이터의 크기
uint16_t OscilloscopeClass::getSubInfoSize(void) {
  return 1 + 16;  // heaer + data
}

//----------------------------------------------------------
// 부가 데이터 순서 ( sub info bytes order)
//   0    1               2           3        4           5
// code, collapsed flag, sub scale, scale,  freq / 256, freq % 256,
//      6, 8, 10                       7,9, 11                  
//    volt (Integer part),  volt (Fraction part * 10) 
//    12                         13           
//  #total_data /  256,      #total_data % 256,
//    14                               15
//  m_dScrollStart / 256,    m_dScrollStart % 256,
//    16
//  m_dTracking
//----------------------------------------------------------
uint8_t *OscilloscopeClass::getSubInfo(void) {
  m_arySubInfos[0] = 1// main data flag
  m_arySubInfos[1] = (m_bCollapseChannel ? 1 : 0);
  m_arySubInfos[2] = m_dSubScale;
  m_arySubInfos[3] = m_dScale;
  m_arySubInfos[4] = (uint8_t)(m_dFrequency >> 8);
  m_arySubInfos[5] = (uint8_t)(m_dFrequency & 0xff);

  float volt = getVolt(m_aryMaxValues[0]);
  m_arySubInfos[6] = (uint8_t)volt;
  m_arySubInfos[7] = (uint8_t)((volt - (int)volt) * 10);
  volt = getVolt(m_aryMaxValues[1]);
  m_arySubInfos[8] = (uint8_t)volt;
  m_arySubInfos[9] = (uint8_t)((volt - (int)volt) * 10);
  volt = getVolt(m_aryMaxValues[2]);
  m_arySubInfos[10] = (uint8_t)volt;
  m_arySubInfos[11] = (uint8_t)((volt - (int)volt) * 10);
  m_arySubInfos[12] = (uint8_t)(m_dNowReadNum >> 8);
  m_arySubInfos[13] = (uint8_t)(m_dNowReadNum & 0xff);
  m_arySubInfos[14] = (uint8_t)(m_dScrollStart >> 8);
  m_arySubInfos[15] = (uint8_t)(m_dScrollStart & 0xff);
  m_arySubInfos[16] = (uint8_t)(m_dTracking);
  return m_arySubInfos;
}

//---------------------------------------------------
// 사용자 입력 처리
void OscilloscopeClass::processUI(uint8_t*  payload) {
  if (m_dTracking == 1) { // 위상 변화 대기중인 경우 무엇을 누르던 간에.. 그걸 우선 멈춘다...
    readADC();
    makeDisplayingtDatas();
    m_dTracking = 2;
    return;
  } else if (payload[0] == 1) { // button ui processing
    switch (payload[1]) {
      case 0:
        switch (m_dTracking) {
          case 0:
            m_dTracking = 1;
            enterTrackingMode();
            break;
          case 1//  함수의 첫부분에서 처리했슴
            // canceled by user
            break;
          case 2:
            leaveTrackingMode();
            break;
        }
        break;
      case 1// zoom in
        if (m_dScale > 1) {
          m_dScale--;
        } else {
          m_dSubScale ++;
          m_dSubScale = min(8, m_dSubScale);
        }
        break;
      case 2// zoom out
        if (m_dSubScale > 1) {
          m_dSubScale--;
        } else {
          m_dScale++;
          m_dScale = min(8, m_dScale);
          if (m_dTracking != 0) {
            m_dScale = min(1, m_dScale);
          }
        }
        break;
      case 3//  <  scroll left
        if (m_dTracking == 0) {
          m_dScrollStart = max(m_dScrollStart - 60);
        } else {
          m_dScrollStart = max(m_dScrollStart - 180);
        }
        break;
      case 4//  > scroll right
        if (m_dTracking == 0) {
          m_dScrollStart = min(m_dScrollStart + 6, m_dNowReadNum - CHART_WIDTH - 1);
        } else {
          m_dScrollStart = min(m_dScrollStart + 18, m_dNowReadNum - CHART_WIDTH - 1);
        }
        break;
      case 5//  collapse
        m_bCollapseChannel = !m_bCollapseChannel;
        break;
      case 6// change  number of channel
        m_dChNum++;
        if (m_dChNum >= 4) m_dChNum = 1;
        // Serial.println(m_dChNum);
        break;
      case 10// scroll position
        m_dScrollStart = payload[2] * 256 + payload[3];
        m_dScrollStart = max(m_dScrollStart, 0);
        m_dScrollStart = min(m_dScrollStart, m_dNowReadNum - CHART_WIDTH - 1);
    }
  }
}

// 정보를 클라이언트에게 보내야 하는 경우에는 false 반환
bool OscilloscopeClass::loop(void) {
  if (m_dTracking == 1) {
    trackingModeProcess();
    return true;
  } else {
    return false;
  }
}



댓글 없음:

댓글 쓰기

활용도가 높은 파워뱅크를 만들어 보자 - 제1편 (설계 및 재료 조달편)

미안함 때문에...  파워뱅크 1차 버전을 만들어서 선물하였다 (총 2개 제작) . 파워뱅크를 먼저 선물받은 사람은 캠핑을 좋아하는 사람이기에 엄청 좋아했다. 컨셉 자체가 전등을 켤 수 있게 만들었고, 필요시 핸드폰등을 충전할 수 있으며, 쌀쌀한 날씨...