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;
  }
}



댓글 없음:

댓글 쓰기

3단 6핀 스위치로 DC 모터의 회전 방향을 바꾸어 보자

1. 필요는 연구의 어머니 항상 느끼는 부분이다. 필요하지 않으면 연구하지 않으며, 필요하면 연구한다. DC 모터를 조건에 따라서 정방향 또는 역방향으로 회전시켜야 하는 필요가 생겼다. 처음에는 MCU 및 Relay Switch를 이용하는 방법을 생각...