메이커 Maker

IC-PBL과 비전 설계: 미세먼지 측정기 메이커 활동

지구빵집 2021. 5. 9. 14:02
반응형

 

 

IC-PBL과 비전 설계: 미세먼지 측정기 메이커 활동 

 

IC-PBL과 비전 설계 교과목 수업에 어드벤처 디자인 교과목에 대한 소개와 IC-BPL 교과목으로 진행하는 과정을 알려주기 위해 4주 간 수업을 진행하기로 했다. 잘 이해가 되지 않을 때는 일단 하는 것이다. 수업을 맡아 4주간 이론과 실습으로 진행하기로 한다. 우선 잘 알지도 못하는 선생님을 위해 과목 소개를 간단히 알아보자. 교과목 매 주차 수업 예시와 활동 사항에 대한 내용은 추가하기로 한다.

 

IC-PBL과 비전 설계 교과목은 1학년 공통 기초 필수 교과목으로서 신입생이 대학 생활에의 적응과 전공에 대한 이해를 바탕으로 진로 비전을 설계할 수 있도록 하는 것을 목표로 한다. 신입생은 전공 교수, 학습자 소그룹(10명 내외) 등과의 긴밀한 소통을 통해 전공에 대한 이해를 높일 수 있고, 전공 분야의 현장에서 요구되는 역량을 갖추기 위한 학습-진로의 비전을 설계할 수 있다. 또한 대학 생활에의 적응과 전공의 이해를 위한 기초 강의 그리고 학습자 중심의 교수-학습 방법인 PBL (Problem Based Learning) 등을 바탕으로 협동 학습에 필요한 역량, 문제 해결 능력 등을 함양하고 사회적 스킬을 배워 나갈 수 있다.  

 

4주를 제외한 나머지 수업은 10여 명 정도 아이들을 맡은 교수가 수업을 진행한다. 수업 일정과 내용을 채워 넣기로 한다. 1학년과 2학년에 전공 교과목으로 들어 있는 어드벤처 디자인 과목에 대한 소개와 수업 내용을 설명하기로 하는 데 이전에 작성한 블로그 내용을 참고하기로 한다. COVID 19 상황이 엄중하여 전체를 동영상으로 할지, 실습만이라도 모여서 할 수 없는지 알아보고 있다. 복잡하거나 어려우면 하지 않아야 하는 게 맞다. 의미는 우리가 부여하는 것이라서 어떻게든 부여를 하면 되지만 실제 돌아가는 일은 의미만으로는 되지 않는다. 동영상을 찍어 해당 학생들이 속해있는 과정에 올리기로 한다. 

 

수업 내용과 학습 구성

 

  • 1주: 주제는 어드벤처 디자인 과목 소개, 오픈소스 플랫폼, 메이커에 대해 학습한다. 자료를 아래에 올리니 참고하시기 바란다. 보통 아이들 대상으로 하는 같은 주제에는 모두 포함되는 내용이지만 참고하시라고 올려둔다.
  • 2주: 오픈 소스 플랫폼과 Maker 활동(이론, 실습)-오픈 소스플랫폼 이해, 여러 플랫폼 활용법에 대한 이해, 실습환경 이해, 메이커 활동 예시, 다음 주 미세먼지 측정기 만들기 설명
  • 3주: 휴대용 미세먼지 공기질 측정기" 조립 구동(실습) - 조립과정으로 직접 제작후 동작 확인 미세먼지 센서 PM2008M, RGB LED, OLED, 아두이노 Nano Mini, 리튬폴리머 충전지(업체 선정 후 일괄 제작 구매하여 사용할 예정임), 미세먼지 측정기 조립하고 결과 확인, 결과 이미지 사진 올리기
  • 4주 : 메이커 활동 확장, 미세먼지 센서를 이용한 프로젝트 주제 토론, Adventure Design Expansion(토론 발표)-공기질 측정기에 더 해 볼 것들, 공기질 측정기를 활용한 확장 방안을 연구 토론(스마트 팜, 이동형 청정기, 미세먼지 해소 방안 등)

4주간 수업 내용 자료를 따로 만들어 공개하기로 한다. 뿐만 아니라 모든 내용을 길게 써서 누구라도 따라하는 데 지장이 없는 자료를 만든다. 다음주 강의자료를 만들고 동영상을 찍기로 하고 배포해야 하고 남은일이 있다. 

 

PCB와 샘플을 받아 각 구성품을 테스트하고 하나로 합쳐 펌웨어를 개발해야 한다. 일이 많기는 하지만 남자는 늘 미루고 하지 않는 벌을 받는 중이라 크게 개의치 않는다. 어떻게 하든 흘러가기는 마찬가지니까.

 

1. 전원 공급과 케이블 연결

 

프로그램을 업로딩하고 개발할 때는 USB B타입 커넥터를 연결하여 개발한다. 일단 9V 각배터리를 연결할 전원 커넥터를 만들어 전원을 테스트 한다. 외부 전원을 연결하고 잘 돌아가면 다행이다.

 

1-1. 버튼 인터럽트 테스트

 

버튼을 누를 때마다 디스플레이 화면을 변경하는 기능을 테스트 한다. 스위치 바운스 문제, 인터럽트 문제를 해결해야 하는 데 어려운 일이다. 아래 코드는 잘 동작한다. 문제는 Nano 보드를 제거하고 그 자리에 Nano 33 IoT 보드를 꼽아서 동작하면 인터럽트 처리가 제대로 동작하지 않는다. 아마도 속도가 빨라서 생기는 문제와 스위치 바운스 현상으로 이해한다. 제대로 해결하는 과정을 포스팅하기로 한다.

 

int swPin = 2;
int currentmode =1;
#define debounceTime 200 // <<- Set debounce Time (unit ms) 

void setup() {
   Serial.begin(9600);
   pinMode(swPin, INPUT_PULLUP);
   attachInterrupt(0, swInterrupt, FALLING);
}

void loop()
{
    //mode lection process
    if(currentmode == 1)
    {
        Serial.println("Mode 1");
    }
        
    if(currentmode == 2)
    {
        Serial.println("Mode 2");
    }
        
    if(currentmode == 3)
    {
        Serial.println("Mode 3");
    }

    Serial.println(currentmode);
    delay(100);
}

void swInterrupt() 
{
    static unsigned long lastTime = 0;
    unsigned long now = millis();
    if((now-lastTime) > debounceTime)
    {
        Serial.println("interrupt");
        currentmode++;
        if(currentmode == 4) currentmode = 1;
    }
    lastTime = now;
} 

 

2. 온습도 센서 DHT11 테스트는 아래 코드를 가지고 테스트 한다. 라이브러리 관리자에서 simpledht 라이브러리를 검색하여 설치하고 아래 코드를 테스트 한다. 여러가지 테스트와 방법들을 구현하고 싶다는 생각이 들어도 필요한 일이 아니면 가능한 늦춘다. 지금은 돌아가는 상태를 확인하는 일이 먼저니까.

 

#include <SimpleDHT.h>

// for DHT11, 
//      VCC: 5V or 3V
//      GND: GND
//      DATA: 2

int pinDHT11 = 9;
SimpleDHT11 dht11(pinDHT11);

void setup() {
  Serial.begin(9600);
}

void loop() {
  // start working...
  Serial.println("=================================");
  Serial.println("Sample DHT11...");
  
  // read without samples.
  byte temperature = 0;
  byte humidity = 0;
  int err = SimpleDHTErrSuccess;
  if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
    Serial.print("Read DHT11 failed, err="); Serial.print(SimpleDHTErrCode(err));
    Serial.print(","); Serial.println(SimpleDHTErrDuration(err)); delay(1000);
    return;
  }
  
  Serial.print("Sample OK: ");
  Serial.print((int)temperature); Serial.print(" *C, "); 
  Serial.print((int)humidity); Serial.println(" H");
  
  // DHT11 sampling rate is 1HZ.
  delay(1500);
}

 

3. RGB LED를 테스트 한다. 소스 코드는 아래 코드를 사용한다. 

 

int redPin = 5;
int greenPin = 4;
int bluePin = 3;
 
void setup()
{
    pinMode(redPin, OUTPUT);
    pinMode(greenPin, OUTPUT);
    pinMode(bluePin, OUTPUT); 
}
 
void loop()
{
    setColor(255, 0, 0); // red
    delay(1000);
    setColor(0, 255, 0); // green
    delay(1000);
    setColor(0, 0, 255); // blue
    delay(1000);
    setColor(255, 255, 0); // yellow
    delay(1000); 
    setColor(80, 0, 80); // purple
    delay(1000);
    setColor(0, 255, 255); // aqua
    delay(1000);
}
 
void setColor(int red, int green, int blue)
{
  analogWrite(redPin, red);
  analogWrite(greenPin, green);
  analogWrite(bluePin, blue); 
}

 

4. PM2008M 미세먼지 센서는 라이브러리 관리자에서 pm2008로 검색하여 라이브러리를 설치한 후 예제에서 불러와 테스트 한다. 씨리얼 모니터로 보면 아주 많은 값들이 나오는데 다 무시하고 pm1.0m, pm2.5, pm10 값만 사용하기로 한다. 그것도 필요할 때만 말이다. 아래는 예제 프로그램이다. 

 

#include <pm2008_i2c.h>

PM2008_I2C pm2008_i2c;

void setup() {
  pm2008_i2c.begin();
  Serial.begin(9600);
  pm2008_i2c.command();
  delay(1000);
  Serial.print("Here");
}

void loop() {
  uint8_t ret = pm2008_i2c.read();
  if (ret == 0) {
    Serial.print("PM 1.0 (GRIMM) : ");
    Serial.println(pm2008_i2c.pm1p0_grimm);
    Serial.print("PM 2.5 (GRIMM) : : ");
    Serial.println(pm2008_i2c.pm2p5_grimm);
    Serial.print("PM 10 (GRIMM) : : ");
    Serial.println(pm2008_i2c.pm10_grimm);
    Serial.print("PM 1.0 (TSI) : ");
    Serial.println(pm2008_i2c.pm1p0_tsi);
    Serial.print("PM 2.5 (TSI) : : ");
    Serial.println(pm2008_i2c.pm2p5_tsi);
    Serial.print("PM 10 (TSI) : : ");
    Serial.println(pm2008_i2c.pm10_tsi);
    Serial.print("Number of 0.3 um : ");
    Serial.println(pm2008_i2c.number_of_0p3_um);
    Serial.print("Number of 0.5 um : ");
    Serial.println(pm2008_i2c.number_of_0p5_um);
    Serial.print("Number of 1 um : ");
    Serial.println(pm2008_i2c.number_of_1_um);
    Serial.print("Number of 2.5 um : ");
    Serial.println(pm2008_i2c.number_of_2p5_um);
    Serial.print("Number of 5 um : ");
    Serial.println(pm2008_i2c.number_of_5_um);
    Serial.print("Number of 10 um : ");
    Serial.println(pm2008_i2c.number_of_10_um);
  }
  delay(1000);
}

 

5.  SSD1306 OLED 테스트도 라이브러리 관리자에서 adafruit ssd 1306 라이브러리를 설치하고 예제 파일을 불러온다. i2c 방식이므로 예제에서 불러올 파일은 ssd1306_128*64_i2c.ino 파일을 불러온다. 주의할 점은 반드시 정확히 59라인의 주소를 3D -> 3C 로 수정하여 테스트 한다.

 

/**************************************************************************
 This is an example for our Monochrome OLEDs based on SSD1306 drivers

 Pick one up today in the adafruit shop!
 ------> http://www.adafruit.com/category/63_98

 This example is for a 128x64 pixel display using I2C to communicate
 3 pins are required to interface (two I2C and one reset).

 Adafruit invests time and resources providing this open
 source code, please support Adafruit and open-source
 hardware by purchasing products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries,
 with contributions from the open source community.
 BSD license, check license.txt for more information
 All text above, and the splash screen below must be
 included in any redistribution.
 **************************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

void setup() {
  Serial.begin(9600);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  // 바로 여기다 3C 로 수정한다. 
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);
  // display.display() is NOT necessary after every single drawing command,
  // unless that's what you want...rather, you can batch up a bunch of
  // drawing operations and then update the screen all at once by calling
  // display.display(). These examples demonstrate both approaches...

  testdrawline();      // Draw many lines

  testdrawrect();      // Draw rectangles (outlines)

  testfillrect();      // Draw rectangles (filled)

  testdrawcircle();    // Draw circles (outlines)

  testfillcircle();    // Draw circles (filled)

  testdrawroundrect(); // Draw rounded rectangles (outlines)

  testfillroundrect(); // Draw rounded rectangles (filled)

  testdrawtriangle();  // Draw triangles (outlines)

  testfilltriangle();  // Draw triangles (filled)

  testdrawchar();      // Draw characters of the default font

  testdrawstyles();    // Draw 'stylized' characters

  testscrolltext();    // Draw scrolling text

  testdrawbitmap();    // Draw a small bitmap image

  // Invert and restore display, pausing in-between
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);

  testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}

void loop() {
}

void testdrawline() {
  int16_t i;

  display.clearDisplay(); // Clear display buffer

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn line
    delay(1);
  }
  for(i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();

  for(i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }
  for(i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000); // Pause for 2 seconds
}

void testdrawrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testfillrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2; i+=3) {
    // The INVERSE color is used so rectangles alternate white/black
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn rectangle
    delay(1);
  }

  delay(2000);
}

void testdrawcircle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillcircle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=3) {
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }

  delay(2000);
}

void testdrawroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfillroundrect(void) {
  display.clearDisplay();

  for(int16_t i=0; i<display.height()/2-2; i+=2) {
    // The INVERSE color is used so round-rects alternate white/black
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i,
      display.height()/4, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawtriangle(void) {
  display.clearDisplay();

  for(int16_t i=0; i<max(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_WHITE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testfilltriangle(void) {
  display.clearDisplay();

  for(int16_t i=max(display.width(),display.height())/2; i>0; i-=5) {
    // The INVERSE color is used so triangles alternate white/black
    display.fillTriangle(
      display.width()/2  , display.height()/2-i,
      display.width()/2-i, display.height()/2+i,
      display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
    display.display();
    delay(1);
  }

  delay(2000);
}

void testdrawchar(void) {
  display.clearDisplay();

  display.setTextSize(1);      // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.setCursor(0, 0);     // Start at top-left corner
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  // Not all the characters will fit on the display. This is normal.
  // Library will draw what it can and the rest will be clipped.
  for(int16_t i=0; i<256; i++) {
    if(i == '\n') display.write(' ');
    else          display.write(i);
  }

  display.display();
  delay(2000);
}

void testdrawstyles(void) {
  display.clearDisplay();

  display.setTextSize(1);             // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);        // Draw white text
  display.setCursor(0,0);             // Start at top-left corner
  display.println(F("Hello, world!"));

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
  display.println(3.141592);

  display.setTextSize(2);             // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print(F("0x")); display.println(0xDEADBEEF, HEX);

  display.display();
  delay(2000);
}

void testscrolltext(void) {
  display.clearDisplay();

  display.setTextSize(2); // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 0);
  display.println(F("scroll"));
  display.display();      // Show initial text
  delay(100);

  // Scroll in various directions, pausing in-between:
  display.startscrollright(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrollleft(0x00, 0x0F);
  delay(2000);
  display.stopscroll();
  delay(1000);
  display.startscrolldiagright(0x00, 0x07);
  delay(2000);
  display.startscrolldiagleft(0x00, 0x07);
  delay(2000);
  display.stopscroll();
  delay(1000);
}

void testdrawbitmap(void) {
  display.clearDisplay();

  display.drawBitmap(
    (display.width()  - LOGO_WIDTH ) / 2,
    (display.height() - LOGO_HEIGHT) / 2,
    logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
  display.display();
  delay(1000);
}

#define XPOS   0 // Indexes into the 'icons' array in function below
#define YPOS   1
#define DELTAY 2

void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  int8_t f, icons[NUMFLAKES][3];

  // Initialize 'snowflake' positions
  for(f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
    icons[f][YPOS]   = -LOGO_HEIGHT;
    icons[f][DELTAY] = random(1, 6);
    Serial.print(F("x: "));
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(F(" y: "));
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(F(" dy: "));
    Serial.println(icons[f][DELTAY], DEC);
  }

  for(;;) { // Loop forever...
    display.clearDisplay(); // Clear the display buffer

    // Draw each snowflake:
    for(f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
    }

    display.display(); // Show the display buffer on the screen
    delay(200);        // Pause for 1/10 second

    // Then update coordinates of each flake...
    for(f=0; f< NUMFLAKES; f++) {
      icons[f][YPOS] += icons[f][DELTAY];
      // If snowflake is off the bottom of the screen...
      if (icons[f][YPOS] >= display.height()) {
        // Reinitialize to a random position, just off the top
        icons[f][XPOS]   = random(1 - LOGO_WIDTH, display.width());
        icons[f][YPOS]   = -LOGO_HEIGHT;
        icons[f][DELTAY] = random(1, 6);
      }
    }
  }
}

 

6. 가장 중요한 버튼 입력을 받는 테스트 코드다. 인터럽트를 사용한다. 아래는 인터럽트 방식으로 버튼이 눌려질 떄마다 모드를 변경하는 코드다. 아주 잘 동작한다. 

 

int swPin = 2;
int currentmode =1;
#define debounceTime 200 // <<- Set debounce Time (unit ms) 

void setup() {
   Serial.begin(9600);
   pinMode(swPin, INPUT_PULLUP);
   attachInterrupt(0, swInterrupt, FALLING);
}

void loop()
{
    //mode lection process
    if(currentmode == 1)
    {
        Serial.println("Mode 1");
    }
        
    if(currentmode == 2)
    {
        Serial.println("Mode 2");
    }
        
    if(currentmode == 3)
    {
        Serial.println("Mode 3");
    }

    Serial.println(currentmode);
    delay(100);
}

void swInterrupt() 
{
    static unsigned long lastTime = 0;
    unsigned long now = millis();
    if((now-lastTime) > debounceTime)
    {
        Serial.println("interrupt");
        currentmode++;
        if(currentmode == 4) currentmode = 1;
    }
    lastTime = now;
} 

 

여기까지 잘 했으면 다음은 전체를 합쳐서 펌웨어를 개발하는 일이 남았는데, 여기까지만 하기로 하고 완성한 프로그램은 따로 올리기로 한다. 자료를 만들어야 하고, 조립 시험하는 방법을 동영상으로 설명해야 한다. 일을 안 해야 안 생기는데 계속하니까 계속 생긴다. 바로 이 지점이 무엇인가 필요한 지점이다. 현재 하고 있는 행동이나 일을 하는 방식을 바꿀 지점 말이다. 

 

아래는 2021년 4월 30일자 정확히 잘 돌아가는 배포 코드다. 당연히 보완할 코드가 있지만 여기까지만 하기로 한다. 그 많은 기능을 넣었으면서도 아주 간결하고 강력하고 정확한 코드다. 

 

보완할 점

1. 리얼타임 클럭 빼버림

2. 미세먼지 양에 따라 색 변하는 코드 없음

3. Serial 통신 테스트 못함

4. Nano 33 IoT 보드를 같은 위치에 호환성 확인 못함

 


#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <SimpleDHT.h>
#include <pm2008_i2c.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
//#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define NUMFLAKES     10 // Number of snowflakes in the animation example

#define LOGO_HEIGHT   16
#define LOGO_WIDTH    16

/*
static const unsigned char PROGMEM logo_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };
*/

// for DHT11, 
//      VCC: 5V or 3V
//      GND: GND
//      DATA: 9

int pinDHT11 = 9;
SimpleDHT11 dht11(pinDHT11);
PM2008_I2C pm2008_i2c;

int flag = 0; //for interrupt
#define debounceTime 200 // <<- Set debounce Time (unit ms) 

int redPin = 5;
int greenPin = 4;
int bluePin = 3;
int buttoninput = 2;

void setup()
{
    Serial.begin(9600);
    
    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    // 바로 여기다 3C 로 수정한다. 
    if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) 
    { // Address 0x3D for 128x64
        Serial.println(F("SSD1306 allocation failed"));
        for(;;); // Don't proceed, loop forever
    }

    pinMode(redPin, OUTPUT);
    pinMode(greenPin, OUTPUT);
    pinMode(bluePin, OUTPUT);
    pinMode(buttoninput, INPUT);
    attachInterrupt(0, ButtonInterrupt, FALLING);

    digitalWrite(redPin, LOW);
    digitalWrite(greenPin, LOW);
    digitalWrite(bluePin, LOW);


    pm2008_i2c.begin();
    pm2008_i2c.command();

    initial_oled();
    
    //delay(1000);

    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    //display.display();
    //delay(2000); // Pause for 2 seconds

    // Clear the buffer
    //display.clearDisplay();

    // Draw a single pixel in white
    //display.drawPixel(10, 10, SSD1306_WHITE);

    // Show the display buffer on the screen. You MUST call display() after
    // drawing commands to make them visible on screen!
    //display.display();
    //delay(2000);
    // display.display() is NOT necessary after every single drawing command,
    // unless that's what you want...rather, you can batch up a bunch of
    // drawing operations and then update the screen all at once by calling
    // display.display(). These examples demonstrate both approaches...
}

int currentmode = 1;

void loop()
{
    //mode lection process
    if(currentmode == 1)
    {
        display.clearDisplay();
        offlight();
        showfinedust();        
    }
        
    if(currentmode == 2)
    {
        display.clearDisplay();
        offlight();
        showtemphumi();
    }
        
    if(currentmode == 3)
    {
        showlight();        
    }
    //if(currentmode == 4)
    //{
   //     offlight();        
   // }
    
    Serial.println(currentmode);
    delay(500);

    //below Test Code 
    //rgbtest();
    //testdht11();
    //testpm2008m();
    //testbutton();
    
}

void ButtonInterrupt() 
{
    static unsigned long lastTime = 0;
    unsigned long now = millis();
    if((now-lastTime) > debounceTime)
    {
        Serial.println("interrupt");
        currentmode++;
        if(currentmode == 4) currentmode = 1;
    }
    lastTime = now;
} 

char _buffer[8];

void showfinedust()
{
    uint8_t ret = pm2008_i2c.read();
    //Serial.print(ret);
    if (ret == 0) {
        Serial.print("PM 1.0 (TSI) : ");
        Serial.println(pm2008_i2c.pm1p0_tsi);
        Serial.print("PM 2.5 (TSI) : : ");
        Serial.println(pm2008_i2c.pm2p5_tsi);
        Serial.print("PM 10 (TSI) : : ");
        Serial.println(pm2008_i2c.pm10_tsi);
    }
    delay(1000);

    display.setCursor(0, 10); 
    sprintf(_buffer, "PM 1.0 %d", pm2008_i2c.pm1p0_tsi);
    display.println(_buffer);
    display.setCursor(0, 30); 
    sprintf(_buffer, "PM 2.5 %d", pm2008_i2c.pm2p5_tsi);
    display.println(_buffer);
    display.setCursor(0, 50); 
    sprintf(_buffer, "PM 10 %d", pm2008_i2c.pm10_tsi);
    display.println(_buffer);
    
    // update the display 
    display.display();    
}

void showlight()
{
    digitalWrite(redPin, HIGH);
    digitalWrite(greenPin, HIGH);
    digitalWrite(bluePin, HIGH);
}

void offlight()
{
    digitalWrite(redPin, LOW);
    digitalWrite(greenPin, LOW);
    digitalWrite(bluePin, LOW);
}



void showtemphumi()
{
    byte temperature = 0;
    byte humidity = 0;
  
    if (dht11.read(pinDHT11, &temperature, &humidity, NULL)) {
        Serial.print("Read DHT11 failed.");
        return;
    }

    display.setCursor(23, 10); 
    sprintf(_buffer, "%d ", (int)temperature);
    display.print(_buffer);
    display.print((char)248); // degree symbol 
    display.println("C");
  
    display.setCursor(23, 30); 
    sprintf(_buffer, "%d ", (int)humidity);
    display.print(_buffer);
    display.println("%");
  
    //display.setCursor(23, 50);
    //printf(_buffer, "On/Off");
    //display.print("On/Off");
  
    //display.drawCircle(88, 12, 2, WHITE); 
  
    // update the display 
    display.display();
}

void initial_oled()
{
    //initialize the SSD1306 OLED display with I2C address = 0x3D
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  // clear the display buffer.
  display.clearDisplay();
 
  //display.setTextSize(1);   // text size = 1
  display.setTextColor(WHITE, BLACK);  // set text color to white and black background
  //display.setCursor(15, 0);            // move cursor to position (15, 0) pixel
  //display.print("Today Weather");
  //display.display();        // update the display
  display.setTextSize(2);   // text size = 2
}


void testbutton()
{
    if(flag == 1)
    {
        digitalWrite(bluePin, HIGH);
    }
    else 
    {
        digitalWrite(bluePin, LOW);
    }

    Serial.println(flag);
    delay(100);
}


void testpm2008m()
{
    uint8_t ret = pm2008_i2c.read();
    //Serial.print(ret);
  if (ret == 0) {
    //Serial.print("PM 1.0 (GRIMM) : ");
    //Serial.println(pm2008_i2c.pm1p0_grimm);
    //Serial.print("PM 2.5 (GRIMM) : : ");
    //Serial.println(pm2008_i2c.pm2p5_grimm);
    //Serial.print("PM 10 (GRIMM) : : ");
    //Serial.println(pm2008_i2c.pm10_grimm);
    Serial.print("PM 1.0 (TSI) : ");
    Serial.println(pm2008_i2c.pm1p0_tsi);
    Serial.print("PM 2.5 (TSI) : : ");
    Serial.println(pm2008_i2c.pm2p5_tsi);
    Serial.print("PM 10 (TSI) : : ");
    Serial.println(pm2008_i2c.pm10_tsi);
    //Serial.print("Number of 0.3 um : ");
    //Serial.println(pm2008_i2c.number_of_0p3_um);
    //Serial.print("Number of 0.5 um : ");
    //Serial.println(pm2008_i2c.number_of_0p5_um);
    //Serial.print("1 um : ");
    //Serial.println(pm2008_i2c.number_of_1_um);
    //Serial.print("2.5 um : ");
    //Serial.println(pm2008_i2c.number_of_2p5_um);
    //Serial.print("Number of 5 um : ");
    //Serial.println(pm2008_i2c.number_of_5_um);
    //Serial.print("10 um : ");
    //Serial.println(pm2008_i2c.number_of_10_um);
  }
  delay(1000);
}

void testdht11() {
  // start working...
  Serial.println("=================================");
  Serial.println("Sample DHT11...");
  
  // read without samples.
  byte temperature = 0;
  byte humidity = 0;
  int err = SimpleDHTErrSuccess;
  if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
    Serial.print("Read DHT11 failed, err="); Serial.print(SimpleDHTErrCode(err));
    Serial.print(","); Serial.println(SimpleDHTErrDuration(err)); delay(1000);
    return;
  }
  
  Serial.print("Sample OK: ");
  Serial.print((int)temperature); Serial.print(" *C, "); 
  Serial.print((int)humidity); Serial.println(" H");
  
  // DHT11 sampling rate is 1HZ.
  delay(1500);
}

    
void rgbtest()
{
    setColor(255, 0, 0); // red
    delay(1000);
    /*setColor(0, 255, 0); // green
    delay(1000);
    setColor(0, 0, 255); // blue
    delay(1000);
    setColor(255, 255, 0); // yellow
    delay(1000); 
    setColor(80, 0, 80); // purple
    delay(1000);
    setColor(0, 255, 255); // aqua
    delay(1000);*/
}

void setColor(int red, int green, int blue)
{
  analogWrite(redPin, red);
  analogWrite(greenPin, green);
  analogWrite(bluePin, blue); 
}

void swInterrupt() 
{
    //flag = !flag;   
    
    static unsigned long lastTime = 0;
    unsigned long now = millis();
    if((now-lastTime) > debounceTime)
    {
        Serial.println("int0");
        flag=!flag;
    }
    lastTime = now;
} 

  

 

 

IC-PBL과 비전 설계: 미세먼지 측정기 메이커 활동

 

 

 

반응형