기말 정리

🔍 I2C 통신📌 I2C 데이터 전송 프로토콜📌 관련 함수⚙️ begin()⚙️ requestFrom()⚙️ beginTransmission(), endTransmission()⚙️ write()⚙️ available()⚙️ read()⚙️ SetClock()⚙️ onReceive()⚙️ onRequest()📌 I2C로 LCD 제어1️⃣ I2C LCD 디스플레이의 주요 구성 요소2️⃣ LCD 데이터 핀 연결3️⃣ PCF8574 I/O 익스팬더 핀 연결4️⃣ I2C 주소 결정🔍 SPI 통신1️⃣ 주요 구성 요소2️⃣ 데이터 전송 과정⚙️ Multi-drop Connection📌 SPI 설정📌 관련 함수⚙️ SPISettings⚙️ begin()⚙️ end()⚙️ beginTransaction()⚙️ endTransaction()⚙️ transfer()⚙️ usingInterrupt()⚙️ SPI Settings⚙️ 예제 코드📌 SPCR의 비트 구성 및 기능📌 SPI 모드🔍 직렬 통신📌 데이터 전송 타이밍⚙️ Data Frame의 구성 및 설정📌 데이터 프레임 비교표⚙️ UBRRn 레지스터 (UART)⚙️ I2C⚙️ SPI📌 아두이노 직렬 통신 핀⚙️ Atemega328⚙️ Atmega328 UART 프레임 설정UCSRC (제어, 상태 레지스터, 모드 설정)UCSRB (제어, 상태 레지스터, 송신 수신 등)UCSRA (데이터 수신, 전송 완료 등)UDRn📌 EEPROMEECR 레지스터 구조 예시EEPROM 쓰기EEPROM 읽기SELFPRGEN (Self Programming Enable)SREG (Status Register)시험 공부를 위한 요약 정리1. MQTT 개요2. MQTT 작동 원리3. MQTT의 구성 요소4. MQTT 브로커 종류5. MQTT 서버 구축 및 사용6. Node.js와 MQTT 연동 예제웹 응용 프로그램 작성 정리Part 1: 웹 응용 프로그램웹 프로그래밍웹 서비스를 만드는 언어 및 프레임워크Part 2: 웹 응용 프로그램 실습Node.jsExpressNode.js 모듈파일 시스템이벤트 모듈Socket.ioArduino & Node.js 통신ArduinoJson회로 구성 및 코드

🔍 I2C 통신

💡
필립스 사에서 제안한 2선 통신 방식
  • 하나의 보드에 장착된 부품들 간에 두 개의 선으로 직렬 통신
  • Master , Slave 로 나누어져 있음
  • 클럭 선(SCL)데이터 선(SDA)를 사용
    • SCL: 동기용 클록, Master가 생성
    • SDA: 데이터, 주소, 시작&정지로 사용, Slave가 사용
  • 7-bit 주소 사용: 최대 128개의 slave 연결 가능
  • 전송 속도: 100, 200, 400kbps
  • 디바이스 중 하나가 Master, 나머지는 모두 Slave
  • 일반적으로 4.8KR 저항으로 풀업 저항
  • SCL가 High 상태일 때 SDA가 Low가 되면 시작, SDA를 High로 만들어서 종료

📌 I2C 데이터 전송 프로토콜

  • Master → Slave: Start (1bit)
  • Master → Slave: Address (7bit)
  • Master → Slave: R/W (1bit)
    • Read일 때 1(High), Write일 때 0(Low)
  • Master ← Slave: ACK (1bit), low 신호

  • 데이터 패킷: N바이트, 주소는 7bit고 데이터는 8bit
  • 설정 항목
    • 데이터 전송 속도
    • 마스터/슬레이브
    • 인터럽트 인에이블
    • I2C 동작 인에이블
  • 아두이노의 ADC 4 : SDA, ADC 5 : SCL
 

📌 관련 함수

⚙️ begin()

💡
Wire.begin()
  • 주소를 지정하지 않으면, 마스터
 

⚙️ requestFrom()

💡
마스터가 슬레이브에게 데이터 전송 요청
  • 마스터는 available()과 read() 함수로 데이터 수신
  • Wire.requestFrom(address, quantity)
    • address: 데이터를 요청할 Slave 7-bit
    • quantity: 요청할 데이터 바이트 수
    • stop (optional): boolean.
      • TRUE: 데이터를 수신 후 전송 종료 (default). 연결 끊기.
      • FALSE: 데이터를 수신 후 다시 데이터 전송 요청. 연결 유지.
 

⚙️ beginTransmission(), endTransmission()

💡
슬레이브로 주소 전송 시작, 처음과 끝을 알려줌
  • 슬레이브로 주소 전송 시작
  • 이 함수에 이어서 write() 함수로 데이터를 큐에 저장하고, endTransmssion() 함수 호출해 전송
 

⚙️ write()

💡
1. 마스터의 요청에 따라 슬레이브에서 데이터 기록 2. 슬레이브 디바이스로 전송할 데이터를 전송 큐에 저장
 

⚙️ available()

💡
read() 함수로 수신 버퍼에서 가져올 수 있는 데이터의 바이트 수 리턴
  • 마스터 디바이스는 requestFrom() 함수 호출 후 이 함수 호출
  • 슬레이브 디바이스는 onReceive() 함수 호출 후 이 함수 호출
 

⚙️ read()

💡
데이터 읽기
  • 마스터는 슬레이브에게 requestFrom()을 호출한 이후 슬레이브가 전송한 한 바이트를 읽음
  • 슬레이브는 마스터가 슬레이브에게 보낸 한 바이트를 읽음
 

⚙️ SetClock()

💡
I2C 통신의 클록 주파수 변경 (디폴트는 100KHz, 고속은 400KHz)
 

⚙️ onReceive()

💡
슬레이브 디바이스가 마스터에서 전송한 데이터를 수신할 때 처리할 함수
#include <Wire.h> void setup() { Wire.begin(2); // 슬레이브 주소 2로 설정 Wire.onReceive(receiveEvent); // 데이터 수신 시 호출되는 함수 등록 Serial.begin(9600); } void loop() { delay(100); } void receiveEvent(int howMany) { Serial.print("Received: "); while (Wire.available()) { char c = Wire.read(); // 데이터 읽기 Serial.print(c); // 시리얼 모니터에 출력 } Serial.println(); }
#include <Wire.h> void setup() { Wire.begin(); // 마스터 장치로 설정 Serial.begin(9600); // 시리얼 통신 시작 } void loop() { Wire.beginTransmission(2); // 슬레이브 주소 2로 전송 시작 (7비트 주소) Wire.write("Hello"); // 데이터를 버퍼에 저장 Wire.endTransmission(); // STOP 신호 전송 (버퍼의 데이터를 슬레이브로 전송하고 통신 종료) delay(1000); // 1초 대기 }
 

⚙️ onRequest()

💡
마스터가 슬레이브 디바이스에게 데이터 요청할 때 처리할 함수
#include <Wire.h> void setup() { Wire.begin(2); // 슬레이브 주소 2로 설정 Wire.onRequest(requestEvent); // 마스터의 요청 시 호출되는 함수 등록 Serial.begin(9600); // 시리얼 통신 시작 } void loop() { delay(100); // 슬레이브의 다른 작업을 위한 루프 (필요에 따라 수정 가능) } void requestEvent() { Wire.write("Hello!"); // 마스터의 요청에 응답하여 데이터를 전송 }
#include <Wire.h> void setup() { Wire.begin(); // 마스터 장치로 설정 Serial.begin(9600); // 시리얼 통신 시작 } void loop() { Wire.requestFrom(2, 6); // 슬레이브 주소 2로부터 6바이트 데이터 요청 while (Wire.available()) { // 요청된 데이터가 있을 때까지 대기 char c = Wire.read(); // 데이터를 읽음 Serial.print(c); // 읽은 데이터를 시리얼 모니터에 출력 } Serial.println(); // 줄 바꿈 delay(500); // 0.5초 대기 }
 

📌 I2C로 LCD 제어

1️⃣ I2C LCD 디스플레이의 주요 구성 요소

  • PCF8574 I/O 익스팬더:
    • 두 개의 핀 (SDA와 SCL)만으로 최대 8개의 핀 (P0에서 P7까지) 연결 가능.

2️⃣ LCD 데이터 핀 연결

  • LCD 데이터 핀: RS, RW, E, D4, D5, D6, D7
  • PCF8574 핀: P0, P1, P2, P3, P4, P5, P6, P7에 각각 연결

3️⃣ PCF8574 I/O 익스팬더 핀 연결

  • SDA 핀: 아두이노 핀 A4에 연결
  • SCL 핀: 아두이노 핀 A5에 연결

4️⃣ I2C 주소 결정

  • PCF8574 I/O 익스팬더 주소 핀: A0, A1, A2 핀
  • 주소 형성: 0x20 | A2 A1 A0 (OR 연산)
    • 예: A2 = 1, A1 = 0, A0 = 1인 경우 주소는 0x27 (0x20 | 0x07)
  • 우리 회로에서의 설정: A2, A1 및 A0 핀이 5V에 연결되어 있어, 주소는 0x27.

  • I/O 확장 모듈에 I2C 버스 내부 풀업이 있어 풀업 저항이 필요 없음
notion image
 
#include <Wire.h> #include <string.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,16,2); String str; void setup() { Wire.begin(4); // I2C 버스 주소 #4로 시작 Wire.onReceive(receiveEvent); // 이벤트 등록 Serial.begin(9600); lcd.init(); lcd.clear(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Hello, World!"); } void loop() { lcd.setCursor(0, 1); lcd.print(str); str = ""; delay(100); } void receiveEvent(int howMany) { while(1 < Wire.available()) {// loop through all but the last { char c = Wire.read(); // receive byte as a character Serial.print(c); str += c; } int x = Wire.read(); str += " "; str += x; Serial.println(x); }
#include <Wire.h> void setup() { Wire.begin(); // 마스터로 I2C 버스를 시작합니다. Serial.begin(9600); // 시리얼 통신을 시작합니다. } void loop() { Wire.requestFrom(4, 16); // 슬레이브 주소 4로부터 16바이트의 데이터를 요청합니다. String receivedData = ""; while (Wire.available()) { // 슬레이브가 보낸 데이터가 있는지 확인합니다. char c = Wire.read(); // 데이터를 읽습니다. receivedData += c; // 문자열에 추가합니다. } if (receivedData.length() > 0) { Serial.println("Received from Slave: " + receivedData); // 시리얼 모니터에 출력합니다. } delay(1000); // 1초 대기합니다. }
notion image

🔍 SPI 통신

  • 모토롤라가 제안한 통신 방식
  • 짧은 거리에 있는 MCU 간에 데이터 전송 시 사용
    • 많은 데이터를 짧은 시간에 전송 가능
  • Full-duplex, 3-wire 동기 데이터 전송
  • Master 또는 Slave 모드에서 동작 가능
  • 통신 속도: 시스템 클럭 주파수 / (2 ~ 64)
    • 수 MHz 통신 가능 (굉장히 빠름)
특징
UART
SPI
I2C
통신 방식
비동기식
동기식
동기식
데이터 라인
TX, RX
SCK, MOSI, MISO, SS
SDA, SCL
속도
보드레이트에 따라 다름 (낮음)
시스템 클럭에 따라 다름 (높음)
최대 400 kHz (표준 모드), 최대 3.4 MHz (고속 모드)
거리
긴 거리에서 안정적
짧은 거리에서 고속 전송
중간 거리에서 안정적
다중 장치
여러 장치 연결 시 추가 회로 필요
여러 슬레이브 장치를 쉽게 연결
여러 마스터와 슬레이브 장치를 쉽게 연결 (주소 체계 사용)
사용 용도
시리얼 모니터, GPS 모듈, 블루투스 모듈 등
디스플레이, 메모리 칩, 고속 센서 데이터 전송 등
센서, EEPROM, RTC 모듈 등

1️⃣ 주요 구성 요소

  1. SPI Master: 데이터를 전송하고 클럭 신호를 생성하는 장치.
  1. SPI Slave: 데이터를 수신하고 마스터의 클럭 신호에 맞춰 데이터를 전송하는 장치.

2️⃣ 데이터 전송 과정

  • SCK (Serial Clock): SPI 마스터가 생성하는 클럭 신호. 마스터와 슬레이브 간의 데이터 동기화를 위해 사용됩니다.
  • MOSI (Master Out Slave In): 마스터에서 슬레이브로 데이터가 전송되는 라인.
  • MISO (Master In Slave Out): 슬레이브에서 마스터로 데이터가 전송되는 라인.
  • 보통 MSB를 전송하고 LSB에서 수신
notion image

⚙️ Multi-drop Connection

  • 하나의 마스터와 여러 개의 슬레이브 연결 가능:
    • MISO, MOSI, SCK: 이 세 신호는 모든 장치에서 공유됩니다.
    • 마스터는 슬레이브마다 하나의 /SS(슬레이브 선택) 선을 제공합니다:
      • 마스터가 여러 슬레이브를 제어할 때 각 슬레이브를 선택하기 위해 /SS 선을 사용합니다.
      • /SS 신호가 너무 가까이 배치되지 않도록 주의해야 합니다.
    • /SS 선은 GPIO 출력선으로 사용 가능:
      • 많은 슬레이브 장치를 연결할 수 있습니다.

  1. 마스터가 /SS 신호를 LOW로 설정하여 특정 슬레이브를 선택합니다.
  1. 동시에 슬레이브는 데이터 전송을 시작합니다.
  1. 마스터는 데이터를 전송하고 슬레이브로부터 데이터를 수신합니다.
  1. 마스터가 /SS 신호를 HIGH로 설정하여 통신을 종료합니다.
 

📌 SPI 설정

  • SPI Enable:
    • SPI 모듈을 활성화합니다.
  • Interrupt:
    • 인터럽트를 사용하여 SPI 통신을 처리할 수 있습니다.
    • 루프에서 폴링하는 대신 인터럽트를 사용하면 효율적으로 통신을 관리할 수 있습니다. 이를 통해 핸들링 함수가 호출됩니다.
  • Data Order:
    • MSB first 또는 LSB first: 데이터 전송 순서를 결정합니다.
      • MSB first: 최상위 비트부터 전송합니다.
      • LSB first: 최하위 비트부터 전송합니다.
    • 선택한 순서에 따라 데이터의 방향이 달라집니다.
  • Master/Slave select:
    • SPI 장치를 마스터 또는 슬레이브로 설정합니다.
    • 마스터: 클럭 신호를 생성하고 통신을 주도합니다.
    • 슬레이브: 클럭 신호를 받아 통신합니다.
  • Clock Polarity (CPOL) and Clock Phase (CPHA):
    • 클럭의 극성과 위상을 설정합니다.
    • CPOL: 클럭의 기본 상태를 결정합니다. (언제 데이터를 인식?)
      • CPOL = 0: 클럭의 기본 상태가 LOW.
      • CPOL = 1: 클럭의 기본 상태가 HIGH.
    • CPHA: 클럭 신호의 어느 지점에서 데이터를 샘플링하고 전송할지를 결정합니다.
      • CPHA = 0: 첫 번째 클럭 에지에서 데이터를 샘플링합니다.
      • CPHA = 1: 두 번째 클럭 에지에서 데이터를 샘플링합니다.
    • CPOL과 CPHA의 조합으로 4가지 동작 모드를 설정할 수 있습니다.
  • Clock rate:
    • 데이터 전송 속도를 설정합니다.
    • 클럭 속도의 분주기로 설정됩니다 (예: 클럭속도/2 ~ 클럭속도/128).
    • 주파수는 16MHz로 정해져 있습니다.

  • 핀 13 ~ 10까지 SCK, MISO, MOSI, SS
notion image

📌 관련 함수

⚙️ SPISettings

  • 설명: SPI 장치의 설정을 정의하는 객체입니다. 전송 속도, 데이터 전송 순서, 동작 모드의 세 가지 파라미터를 하나의 객체로 통합합니다.
  • 구문:
    • SPISettings mySettings(14000000, MSBFIRST, SPI_MODE0);
  • 파라미터:
    • speedMaximum: 최대 전송 속도 (최대는 20MHZ)
    • dataOrder: MSBFIRST 또는 LSBFIRST.
    • dataMode: SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3.
  • 반환 값: 없음.
 

⚙️ begin()

  • 설명: SPI 버스를 초기화하고, SCK, MOSI, SS 핀을 출력으로 설정하며 초기화합니다.
  • 구문:
    • SPI.begin();
  • 파라미터: 없음.
  • 반환 값: 없음.
 

⚙️ end()

  • 설명: SPI 버스를 비활성화합니다. (핀 모드는 변하지 않습니다.)
  • 구문:
    • SPI.end();
  • 파라미터: 없음.
  • 반환 값: 없음.
 

⚙️ beginTransaction()

  • 설명: SPISettings 정의를 사용하여 SPI 버스를 초기화합니다.
  • 구문:
    • SPI.beginTransaction(mySettings);
  • 파라미터: mySettings - SPISettings 객체.
  • 반환 값: 없음.
 

⚙️ endTransaction()

  • 설명: SPI 버스 사용을 중단합니다.
  • 구문:
    • SPI.endTransaction();
  • 파라미터: 없음.
  • 반환 값: 없음.
 

⚙️ transfer()

  • 설명: 데이터를 전송하면서 수신 데이터를 동시에 받아옵니다.
  • 구문:
    • byte receivedVal = SPI.transfer(val); uint16_t receivedVal16 = SPI.transfer(val16); SPI.transfer(buffer, size);
  • 파라미터:
    • val: 버스로 전송할 바이트 데이터.
    • val16: 버스로 전송할 두 바이트 데이터.
    • buffer: 전송할 데이터가 저장되어 있는 버퍼.
  • 반환 값: 수신된 데이터.
 

⚙️ usingInterrupt()

  • 설명: 프로그램에서 인터럽트를 사용하려면 이 함수로 인터럽트 번호를 등록합니다. SPI.beginTransaction()에서 사용하는 ISR과 중첩되지 않습니다.
  • 구문:
    • SPI.usingInterrupt(interruptNumber);
  • 파라미터: interruptNumber - 인터럽트 번호.
  • 반환 값: 없음.
 

⚙️ SPI Settings

  • Data Order
    • MSBFIRST: 최상위 비트(MSB)부터 전송합니다.
    • LSBFIRST: 최하위 비트(LSB)부터 전송합니다.
  • Clock Polarity (CPOL) and Clock Phase (CPHA)
    • CPOL: 클럭의 기본 상태를 결정합니다.
      • CPOL = 0: 클럭의 기본 상태가 LOW.
      • CPOL = 1: 클럭의 기본 상태가 HIGH.
    • CPHA: 클럭 신호의 어느 지점에서 데이터를 샘플링하고 전송할지를 결정합니다.
      • CPHA = 0: 첫 번째 클럭 에지에서 데이터를 샘플링.
      • CPHA = 1: 두 번째 클럭 에지에서 데이터를 샘플링.
 

⚙️ 예제 코드

아래는 SPI 마스터가 데이터를 전송하고 수신하는 예제 코드입니다:
#include <SPI.h> void setup() { // SPI 설정 SPI.begin(); SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0)); } void loop() { digitalWrite(SS, LOW); // 슬레이브 선택 byte receivedVal = SPI.transfer(0xB3); // 데이터 전송 및 수신 digitalWrite(SS, HIGH); // 슬레이브 선택 해제 delay(1000); // 1초 대기 }
 

📌 SPCR의 비트 구성 및 기능

💡
SPI 통신에 사용되는 레지스터
비트 위치
이름
설명
설정 값
7
SPIE
SPI 인터럽트 사용 시 1로 설정
0: 비활성화, 1: 활성화
6
SPE
SPI 사용 시 1로 설정
0: 비활성화, 1: 활성화
5
DORD
수신하는 데이터 순서 설정 (MSB/LSB)
0: MSB, 1: LSB
4
MSTR
마스터/슬레이브 선택
0: 슬레이브, 1: 마스터
3
CPOL
클럭의 기본 상태 설정
0: LOW, 1: HIGH
2
CPHA
클럭 신호의 어느 지점에서 데이터 샘플링
0: 첫 번째 에지, 1: 두 번째 에지
1
SPR1
주파수 설정
분주비 설정
0
SPR0
주파수 설정
분주비 설정
  • SPIE (SPI Interrupt Enable): SPI 인터럽트를 활성화하려면 1로 설정합니다.
  • SPE (SPI Enable): SPI 기능을 활성화하려면 1로 설정합니다.
  • DORD (Data Order): 데이터 전송 순서를 설정합니다 (MSB 또는 LSB).
  • MSTR (Master/Slave Select): 마스터 또는 슬레이브 모드를 선택합니다.
  • CPOL (Clock Polarity): 클럭의 기본 상태를 설정합니다.
  • CPHA (Clock Phase): 클럭 에지에서 데이터를 샘플링하는 시점을 설정합니다.
  • SPR1, SPR0 (SPI Clock Rate Select): 분주비 설정을 통해 SPI 클럭 속도를 조절합니다.
 

📌 SPI 모드

notion image
모드
CPOL
CPHA
클럭 기본 상태
데이터 샘플링 시점
설명
0
0
0
LOW
클럭의 첫 번째 상승 에지
클럭 신호가 LOW에서 시작하며, 첫 번째 상승 에지에서 데이터 샘플링
1
0
1
LOW
클럭의 첫 번째 하강 에지
클럭 신호가 LOW에서 시작하며, 첫 번째 하강 에지에서 데이터 샘플링
2
1
0
HIGH
클럭의 첫 번째 하강 에지
클럭 신호가 HIGH에서 시작하며, 첫 번째 하강 에지에서 데이터 샘플링
3
1
1
HIGH
클럭의 첫 번째 상승 에지
클럭 신호가 HIGH에서 시작하며, 첫 번째 상승 에지에서 데이터 샘플링
#include <SPI.h> byte queue[100]; byte head, tail; byte rxdata; byte rxcount = 0; char buf[80]; void setup() { pinMode(SCK, INPUT); // set SPI slave manually pinMode(MOSI, INPUT); pinMode(MISO, OUTPUT); pinMode(SS, INPUT); SPI.setClockDivider(SPI_CLOCK_DIV16); SPCR &= ~(1 << MSTR); // Slave SPCR |= (1 << SPIE); SPCR |= (1 << SPE); Serial.begin(9600); head = 0; tail = 0; } ISR(SPI_STC_vect) { // SPI Interrupt rxdata = SPDR; // rx data SPDR = rxdata; // echo back queue[head] = rxdata; head = (head + 1) % 100; rxcount += 1; } void loop() { int rxdata; if (head != tail) { rxdata = queue[tail]; tail = (tail + 1) % 100; sprintf(buf, "SPI RX count = %d, data = %d", rxcount, rxdata); Serial.println(buf); } }
#include <SPI.h> void setup() { SPI.begin(); // SCK, MOSI, SS into output mode SPI.setClockDivider(SPI_CLOCK_DIV16); Serial.begin(9600); } bytex = 0; byte rx1, rx2; char buf[80]; void loop() { digitalWrite(SS, LOW); rx1 = SPI.transfer(x); x++; rx2 = SPI.transfer(x); x++; digitalWrite(SS, HIGH); // select sprintf(buf, "Master x=%d, rx1=%d, rx2=%d", x, rx1, rx2); Serial.println(buf); delay(1000); }

#include <SPI.h> const int SWITCH = 2; const int LED = 7; void setup() { pinMode(LED, OUTPUT); pinMode(SWITCH, INPUT); SPI.begin(); // SPI 통신 시작 SPI.setClockDivider(SPI_CLOCK_DIV16); // 클록 분배 설정 Serial.begin(9600); } void loop() { byte localSwitchState = digitalRead(SWITCH) == LOW; // 스위치 읽기, 눌리지 않았을 때 꺼지도록 digitalWrite(SS, LOW); // 슬레이브 선택 byte slaveSwitchState = SPI.transfer(localSwitchState); // 스위치 상태 전송 및 slave 상태 수신 digitalWrite(SS, HIGH); // 슬레이브 해제 digitalWrite(LED, slaveSwitchState); // 수신된 슬레이브 스위치 상태에 따라 LED 제어 delay(100); // 딜레이 }
#include <SPI.h> const int SWITCH = 2; const int LED = 7; volatile byte receivedData = 0; // 마스터로부터 받은 데이터 void setup() { pinMode(LED, OUTPUT); // LED 출력 모드 설정 pinMode(SWITCH, INPUT); pinMode(SCK, INPUT); // set SPI slave manually pinMode(MOSI, INPUT); pinMode(MISO, OUTPUT); pinMode(SS, INPUT); SPI.setClockDivider(SPI_CLOCK_DIV16); SPCR &= ~(1 << MSTR); // Slave SPCR |= (1 << SPIE); SPCR |= (1 << SPE); Serial.begin(9600); } ISR(SPI_STC_vect) { // SPI 인터럽트 서비스 루틴 receivedData = SPDR; SPDR = digitalRead(SWITCH) == LOW; // 슬레이브 스위치 상태를 다시 마스터에게 보냄 } void loop() { digitalWrite(LED, receivedData); // 마스터로부터 받은 데이터에 따라 LED 제어 }
  1. SPI.setClockDivider:
      • 이 함수는 SPI 클럭 속도를 설정하는 데 사용됩니다.
      • SPI 통신에서 마스터 장치는 클럭 신호를 생성하며, 이 클럭 신호는 슬레이브 장치와 동기화됩니다.
  1. SPI_CLOCK_DIV16:
      • SPI_CLOCK_DIV16는 SPI 클럭 디바이더의 한 값입니다.
      • 시스템 클럭 주파수를 16으로 나눈 값을 SPI 클럭 속도로 설정합니다.
      • 예를 들어, 시스템 클럭이 16 MHz라면, SPI_CLOCK_DIV16를 설정하면 SPI 클럭 속도는 16 MHz / 16 = 1 MHz가 됩니다.
 

🔍 직렬 통신

  1. 병렬 통신
      • 한 번에 여러 비트(한 바이트, 8-bit)를 한 번에 전송
      • 송수신 장치 간에 전송 비트만큼 선을 연결해야 함
  1. 직렬 통신
      • 시프트 레지스터를 이용하여 한 번에 한 비트씩 전송
      • 데이터를 전송하기 위하여 최소 한 개의 선 연결 필요
  • 직렬 통신 방법
      1. 동기 직렬 통신 (Synchronous Serial Communication)
          • 기준 클록을 공유하여 데이터를 동기화
          • 예: SPI, I2C
      1. 비동기 직렬 통신 (Asynchronous Serial Communication)
          • 기준 클록을 공유하지 않고, 미리 양측에 통신 속도를 설정하여 통신
          • 예: UART
  • Simplex: 한 방향으로만 데이터 전송 (예: 키보드, 마우스)
  • Half-duplex: 양방향 통신이 가능하지만 동시에 이루어지지 않음 (예: 무전기)
  • Full-duplex: 양방향 통신이 동시에 이루어짐 (예: 전화)

  • MCU는 통신 제어기를 포함하여 직렬 통신을 처리합니다.
  • 송신 과정: 프로세서가 통신 제어기에 데이터를 기록하고, 통신 제어기가 이를 채널로 전송합니다.
  • 수신 과정: 통신 제어기가 채널에서 데이터를 수신하고, 프로세서가 이를 읽어 처리합니다.
 

📌 데이터 전송 타이밍

notion image
  • 프로세서와 통신제어기, 통신제어기와 채널간 데이터 전송 속도가 다르기 때문에 버퍼를 이용
  • 시프트 레지스터를 이용한 직렬/병렬 변환:
    • 데이터 전송 시 시프트 레지스터를 사용하여 직렬 데이터를 병렬로 변환하거나, 병렬 데이터를 직렬로 변환합니다.
  • 입출력 큐를 이용한 데이터 버퍼링:
    • 데이터 전송과 수신을 위한 버퍼를 사용하여 데이터 흐름을 관리합니다.
    • 버퍼링을 통해 데이터 전송 속도와 프로세서의 처리 속도 차이를 조정할 수 있습니다.
notion image
 
  • 직렬 통신 시, 데이터를 직렬로 보내고 병렬로 읽어들임.

notion image
  1. 송신 과정:
      • 프로세서:
        • 프로세서는 데이터를 병렬 방식으로 통신 제어기에 보냅니다.
        • 이 데이터는 주로 8비트 단위(한 바이트)로 전송됩니다.
      • 통신 제어기 (Tx Register, PISO Shift Register):
        • 통신 제어기는 프로세서로부터 받은 병렬 데이터를 Tx 레지스터에 저장합니다.
        • Tx 레지스터의 병렬 데이터는 PISO(Parallel-In Serial-Out) 시프트 레지스터로 전달됩니다.
        • PISO 시프트 레지스터는 병렬 데이터를 직렬 데이터로 변환합니다.
        • 변환된 직렬 데이터는 통신 채널을 통해 전송됩니다.
  1. 수신 과정:
      • 통신 제어기 (SIPO Shift Register, Rx Register):
        • 통신 채널을 통해 수신된 직렬 데이터는 SIPO(Serial-In Parallel-Out) 시프트 레지스터에 입력됩니다.
        • SIPO 시프트 레지스터는 직렬 데이터를 병렬 데이터로 변환합니다.
        • 변환된 병렬 데이터는 Rx 레지스터에 저장됩니다.
      • 프로세서:
        • 프로세서는 Rx 레지스터에서 병렬 데이터를 읽어와 처리합니다.
  • 단점
    • 송신:
      • 데이터를 완전히 전송하기 전에 다음 데이터가 도착하면 오류가 발생합니다.
    • 수신:
      • 수신된 데이터를 읽어 가기 전에 다음 데이터가 도착하면 overrun 오류가 발생합니다.

  • 직렬통신 제어기: 프로세서로부터 병렬 데이터(8bit)를 받아 직렬 신호로 변환하는 장치
    • 동기, 비동기 통신 설정 가능하지만 보통 비동기(UART)로 사용
    • Full duplex: 송신선과 수신선 각각 연결해 동시에 송수신 가능
  • RS232C:
    • 비동기 직렬 통신 표준
    • 중간에 모뎀 추가해서 장거리 통신 가능
    • 직렬 통신 케이블로 연결
    • 모뎀 사용하지 않을 때, 통신 거리는 약 15m
notion image

⚙️ Data Frame의 구성 및 설정

  • Start Bit: 1-bit 고정 (설정하지 않음)
  • Data Bits: 5, 6, 7, 8, 또는 9 비트
  • Parity Bit: 없음, 짝수(even), 홀수(odd)
  • Stop Bits: 1 또는 2 비트
  • 통신속도:
    • Baud rate, bit rate, bit/sec, bps
    • 예: 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200
    • 가장 많이 사용되는 조합: 9600 bps, no parity, 8 data bits, 1 stop bit
  • Overrun Error:
    • 데이터 수신 중에 발생.
    • 수신된 데이터를 처리하기 전에 다음 데이터가 도착했을 때 발생.
  • Framing Error:
    • 데이터 프레임을 형성하지 못한 경우 발생.
    • Start bit 후에 데이터 비트를 수신했으나 stop bit가 도착하지 않았을 때 발생.
  • Parity Error:
    • 데이터 프레임의 패리티 비트에서 발생.
    • 데이터 전송 중에 패리티 비트가 맞지 않을 때 발생.

📌 데이터 프레임 비교표

특징
직렬 통신 (UART)
I2C
SPI
시작 신호
Start bit (1비트)
Start condition (SDA가 LOW로 변할 때 SCL이 HIGH일 때)
SS (Slave Select 핀을 LOW로 설정)
데이터 비트
5, 6, 7, 8, 또는 9비트
8비트 데이터 프레임
MOSI (마스터 -> 슬레이브), MISO (슬레이브 -> 마스터)
패리티 비트
없음, 짝수, 홀수
N/A
N/A
종료 신호
Stop bit (1 또는 2비트)
Stop condition (SDA가 HIGH로 변할 때 SCL이 HIGH일 때)
SS 핀을 HIGH로 설정
인증 신호
패리티 비트
ACK/NACK 비트
N/A

⚙️ UBRRn 레지스터 (UART)

  • UBRRn (USART Baud Rate Register):
    • UART 통신의 보드레이트를 설정하는 16비트 레지스터.
    • UBRRn 값에 따라 UART 통신의 속도가 결정됩니다.
    • UBRRHUBRRL로 나뉘어 상위 8비트와 하위 8비트를 설정합니다.

⚙️ I2C

I2C 통신은 동기식 프로토콜로, SDA (데이터 라인)와 SCL (클럭 라인) 두 개의 라인을 사용합니다. I2C는 보드레이트 설정을 위한 별도의 레지스터를 사용하며, UART의 UBRRn과는 다른 설정을 필요로 합니다.
  • TWBR (Two-wire Serial Bit Rate Register):
    • I2C 통신의 클럭 속도를 설정하는 레지스터.
    • 클럭 속도는 SCL의 주파수로, TWBR과 프리스케일러 설정을 통해 결정됩니다.

⚙️ SPI

SPI 통신은 마스터-슬레이브 구조의 동기식 프로토콜로, SCK (클럭), MOSI (마스터 아웃 슬레이브 인), MISO (마스터 인 슬레이브 아웃), SS (슬레이브 셀렉트) 라인을 사용합니다. SPI는 클럭 속도를 설정하기 위해 자체적인 설정을 필요로 하며, UART의 UBRRn과는 다른 설정을 사용합니다.
  • SPCR (SPI Control Register):
    • SPI 통신의 주요 설정을 관리하는 레지스터.
    • 클럭 속도 분배기, 데이터 전송 순서, 마스터/슬레이브 모드 등을 설정합니다.
  • SPSR (SPI Status Register):
    • SPI 통신의 상태를 확인하는 레지스터.
    • 클럭 속도 이중화 설정 (SPI2X)을 포함하여 더 높은 속도를 설정할 수 있습니다.

📌 아두이노 직렬 통신 핀

  • 핀 RX/TX는 보드의 전원을 사용 (5V, 3V)
  • RS232 Serial Cable에 직접 연결하지 말 것
  • 일반적으로 시리얼 모니터 연결, 프로그램 업로드용으로 사용
  • 핀 RX/TX는 디지털 포트 D0/D1을 공유하므로 직렬 통신을 사용하면 두 핀을 디지털로 사용 불가

⚙️ Atemega328

  • USRAT 통신 지원
  • 비동기 모드로 별도의 클럭 사용 X
  • 시작, 정지 비트로 데이터 동기화
  • 일반적으로 정확한 타이밍과 전송속도를 얻기 위해 2배속 모드 사용
  • Baud Rate 설정
    • 2400 ~ 230.4kbps
    • UBRRn 레지스터에 필요한 통신 속도 설정
      • 보드율, 에러율, 주파수 등을 데이터시트에서 참조
      • 16MHz의 osc 주파수 사용 시
notion image

⚙️ Atmega328 UART 프레임 설정

notion image
  • Start Bit (ST): 1 비트 고정
    • 통신의 시작을 알리기 위해 항상 LOW 상태입니다.
  • Data Bits (0-8): 5, 6, 7, 8 또는 9 비트 사용 가능
    • 실제 데이터가 포함되는 비트입니다.
    • 5 비트에서 9 비트까지 설정할 수 있습니다.
  • Parity Bit (P): 패리티 비트
    • None: 패리티 비트를 사용하지 않음.
    • Even (짝수): 데이터 비트의 '1'의 개수가 짝수가 되도록 패리티 비트를 설정.
    • Odd (홀수): 데이터 비트의 '1'의 개수가 홀수가 되도록 패리티 비트를 설정.
  • Stop Bits (Sp): 1 또는 2 비트
    • 데이터 프레임의 끝을 알리는 비트로, 항상 HIGH 상태입니다.
  • Idle (IDLE): 통신이 이루어지지 않는 상태
    • IDLE 라인은 반드시 HIGH 상태여야 합니다.

UCSRC (제어, 상태 레지스터, 모드 설정)

비트
이름
설명
설정 값
비트 7:6
UMSELn1, UMSELn0
USART 모드를 선택합니다.
00: Asynchronous USART 01: Synchronous USART 10: Reserved 11: Master SPI (MSPIM) 모드
비트 5:4
UPMn1, UPMn0
패리티 모드를 설정합니다.
00: 패리티 비트 사용 안 함 01: Reserved 10: 짝수 패리티 (Even Parity) 11: 홀수 패리티 (Odd Parity)
비트 3
USBSn
Stop 비트 수를 설정합니다.
0: 1개의 Stop 비트 1: 2개의 Stop 비트
비트 2:1
UCSZn1, UCSZn0
데이터 비트의 크기를 설정합니다.
00: 5비트 01: 6비트 10: 7비트 11: 8비트
비트 0
UCPOLn
동기식 모드에서 클럭 극성을 설정합니다.
0: 전송 데이터 변경이 발생하는 상승 에지에서 클럭 1: 전송 데이터 변경이 발생하는 하강 에지에서 클럭

UCSRB (제어, 상태 레지스터, 송신 수신 등)

비트
이름
설명
초기 값
읽기/쓰기
비트 7
RXCIEn
RX 완료 인터럽트 활성화. RXCn 플래그가 설정되면 인터럽트 발생.
0
R/W
비트 6
TXCIEn
TX 완료 인터럽트 활성화. TXCn 플래그가 설정되면 인터럽트 발생.
0
R/W
비트 5
UDRIEn
데이터 레지스터 비어 있음 인터럽트 활성화. UDRn 플래그가 설정되면 인터럽트 발생.
0
R/W
비트 4
RXENn
수신기 활성화. RXDn 핀의 정상 포트 동작을 오버라이드하여 수신기 활성화.
0
R/W
비트 3
TXENn
송신기 활성화. TXDn 핀의 정상 포트 동작을 오버라이드하여 송신기 활성화.
0
R/W
비트 2
UCSZn2
데이터 비트 크기 설정을 위한 UCSZn2 비트.
0
R/W
비트 1
RXB8n
수신 데이터 비트 8.
0
R
비트 0
TXB8n
전송 데이터 비트 8.
0
R/W

UCSRA (데이터 수신, 전송 완료 등)

비트 번호
비트 이름
읽기/쓰기
초기값
설명
7
RXC0
읽기 전용
0
데이터 수신 완료 플래그
6
TXC0
읽기/쓰기
0
데이터 전송 완료 플래그
5
UDRE0
읽기 전용
0
송신 버퍼가 비었음을 나타내는 플래그
4
FE0
읽기 전용
0
프레이밍 오류 플래그
3
DOR0
읽기 전용
0
데이터 오버런 오류 플래그
2
UPE0
읽기 전용
0
패리티 오류 플래그
1
U2X0
읽기/쓰기
0
2배속 모드 선택 비트
0
MPCM0
읽기/쓰기
0
다중 프로세서 통신 모드 선택 비트

UDRn

비트 번호
비트 이름
읽기/쓰기
초기값
설명
7-0
RXB[7:0]
R/W
0
수신 데이터 버퍼 (읽기 시)
7-0
TXB[7:0]
R/W
0
송신 데이터 버퍼 (쓰기 시)
#define F_CPU 16000000UL #define _BV(bit) (1 << (bit)) #include <avr/io.h> #include <stdio.h> #include <util/delay.h> void UART_0_init(void); void UART1_transmit(char data); unsigned char UART1_receive(void); void UART1_send_String(char *data); void UART_0_init(void) { UBRR0H = 0x00; // 9,600 보율로 설정 UBRR0L = 207; UCSR0A |= (1 << U2X0); // 2배속 모드 UCSR0C |= 0x06; // 8N1 설정 UCSR0B |= _BV(RXEN0); // 수신 가능 UCSR0B |= _BV(TXEN0); // 송신 가능 } void UART1_transmit(char data) { // 송신 가능 대기 while (!(UCSR0A & (1 << UDRE0))) ; // UCSR0A는 USART Control and Status Register A로, UART의 상태를 나타냅니다. // UDRE0 비트는 UART Data Register Empty를 의미하며, 송신 데이터 레지스터가 비어 있어 새 데이터를 쓸 준비가 되었음을 나타냅니다. UDR0 = data; // 데이터 전송 } unsigned char UART1_receive(void) { // 수신 가능 대기 while (!(UCSR0A & (1 << RXC0))) ; // RXC0 비트는 USART Receive Complete를 의미하며, 수신 버퍼에 데이터가 수신되었음을 나타냅니다. return UDR0; } void UART1_send_String(char *data) { int i = 0; do { UART1_transmit(data[i]); i++; } while (data[i] != 0); UART1_transmit(data[i]); } int main(void) { UART_0_init(); while (1) { UART1_send_String("HelloWorld\n"); _delay_ms(1000); } return 0; }
int ledState10 = LOW; unsigned long previousMillis10 = 0; const long interval10 = 1000; int ledState9 = LOW; unsigned long previousMillis9 = 0; const long interval9 = 500; void setup() { pinMode(10, OUTPUT); pinMode(9, OUTPUT); } void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis10 >= interval10) { previousMillis10 = currentMillis; if (ledState10 == LOW) { ledState10 = HIGH; } else { ledState10 = LOW; } digitalWrite(10, ledState10); } if (currentMillis - previousMillis9 >= interval9) { previousMillis9 = currentMillis; if (ledState9 == LOW) { ledState9 = HIGH; } else { ledState9 = LOW; } digitalWrite(9, ledState9); } }
 

📌 EEPROM

EECR 레지스터 구조 예시

Bit 7 6 5 4 3 2 1 0 - - EEPM1 EEPM0 EERIE EEPE EEMPE EERE
  • 비트 7-6: 사용되지 않음
  • 비트 5-4 (EEPM1, EEPM0): EEPROM 프로그래밍 모드 비트
  • 비트 3 (EERIE): EEPROM 준비 인터럽트 활성화 비트
  • 비트 2 (EEPE): EEPROM 프로그래밍 활성화 비트
  • 비트 1 (EEMPE): EEPROM 마스터 쓰기 활성화 비트
  • 비트 0 (EERE): EEPROM 읽기 활성화 비트

EEPROM 쓰기

  1. 준비 상태 대기:
      • EECR 레지스터의 EEPE 비트가 0이 될 때까지 대기.
  1. 주소 설정:
      • EEAR 레지스터에 주소 설정.
  1. 데이터 설정:
      • EEDR 레지스터에 데이터 기록.
  1. 쓰기 명령:
      • EECR 레지스터의 EEMPE 비트를 1로 설정.
      • EECR 레지스터의 EEPE 비트를 1로 설정하여 쓰기 시작.

EEPROM 읽기

  1. 준비 상태 대기:
      • EECR 레지스터의 EEPE 비트가 0이 될 때까지 대기.
  1. 주소 설정:
      • EEAR 레지스터에 주소 설정.
  1. 읽기 명령:
      • EECR 레지스터의 EERE 비트를 1로 설정하여 읽기 시작.
  1. 데이터 읽기:
      • EEDR 레지스터에서 데이터 읽기.
void EEPROM_write(unsigned int uiAddress, unsigned char ucData) { // 이전 쓰기 완료까지 대기 while (EECR & (1 << EEPE)); // EEPE 비트 0 인지 확인 // 주소와 데이터 레지스터 설정 EEAR = uiAddress; EEDR = ucData; // EEMPE 비트 1로 설정 EECR |= (1 << EEMPE); // EEPE 비트 1로 설정해 쓰기 시작 EECR |= (1 << EEPE); } unsigned char EEPROM_read(unsigned int uiAddress) { // 이전 쓰기 완료까지 대기 while (EECR & (1 << EEPE)); // 주소 레지스터 설정 EEAR = uiAddress; // EERE 비트 1로 설정해 읽기 시작 EECR |= (1 << EERE); // 데이터 레지스터 데이터 반환 return EEDR; } void setup() { Serial.begin(9600); } void loop() { char sSREG; sSREG = SREG; SREG = 0; // Interrupt disable 설정 EECR = 0; // EEPM0,1 초기화, 읽기 지우기 가능 EEPROM_write(0x01, '2'); Serial.println(EEPROM_read(0x01)); SREG = sSREG; }
 

SELFPRGEN (Self Programming Enable)

  • 비트 위치: SELFPRGEN은 SPMCSR 레지스터의 비트 0에 위치합니다.
  • 기능: 이 비트는 셀프 프로그래밍을 활성화하는 역할을 합니다. 이 비트를 1로 설정하면 플래시 메모리 프로그래밍 명령이 실행됩니다.
 

SREG (Status Register)

  • SREG는 AVR 마이크로컨트롤러의 상태 레지스터로, 현재 CPU의 상태를 나타내는 8비트 레지스터입니다.
  • 이 레지스터는 인터럽트 활성화/비활성화, 연산 결과 플래그(Carry, Zero, Negative, Overflow 등)를 포함한 다양한 상태를 관리합니다.
 

 
 

시험 공부를 위한 요약 정리

1. MQTT 개요

  • MQTT (Message Queuing Telemetry Transport): M2M (Machine-to-Machine) 및 IoT (Internet of Things)를 위한 경량 메시지 프로토콜.
  • 특징:
    • 낮은 대역폭과 제한된 리소스 환경에서 안정적인 통신 가능.
    • 작은 데이터 패킷을 전송하여 효율적이고 전력 소비가 적음.
    • 클라이언트-서버 모델이 아닌 Broker, Publisher, Subscriber 모델로 구성.

2. MQTT 작동 원리

  • 공간 분리: 출판자와 구독자는 서로의 네트워크 위치를 모르며 IP 주소나 Port 번호를 교환하지 않음.
  • 시간 분리: 출판자와 구독자는 동시에 실행되거나 네트워크를 통해 연결되지 않아도 됨.
  • 동기화 분리: 출판자와 구독자는 서로를 중단시키지 않고 메시지를 전송하거나 수신 가능.

3. MQTT의 구성 요소

  • Broker: 메시지를 수신하고 구독자에게 배포하는 중재 역할.
  • Client: 메시지를 출판하거나 특정 토픽을 구독하는 사용자 장치.
  • Topic: 메시지를 필터링하는 라벨, 계층적인 구조를 가짐 (예: /home/temperature).
  • QoS (Quality of Service) 레벨:
    • QoS 0: Fire and forget, 확인 응답 없이 메시지를 전송.
    • QoS 1: At least once, 수신자가 메시지 확인 응답을 할 때까지 여러 번 전송.
    • QoS 2: Exactly once, 메시지가 정확히 한 번 전달됨을 보장.

4. MQTT 브로커 종류

  • Mosquitto: 오픈 소스, 경량, 임베디드 시스템에 적합.
  • HiveMQ: 엔터프라이즈 배포에 적합한 고성능 브로커.
  • RabbitMQ: 다양한 메시징 프로토콜을 지원, 분산 및 연합 구성에 적합.
  • VerneMQ: 대량의 동시 클라이언트를 처리할 수 있는 오픈 소스 고성능 브로커.
  • Mosca: Node.js 애플리케이션에 내장 가능.

5. MQTT 서버 구축 및 사용

  • 발행: mosquitto_pub -h localhost -t /testTopic -m "Hello World"
  • 구독: mosquitto_sub -h localhost -t /testTopic

6. Node.js와 MQTT 연동 예제

const express = require("express"); const app = express(); const http = require("http"); const server = http.createServer(app); const mqtt = require("mqtt"); const { SerialPort, ReadlineParser } = require("serialport"); var port = new SerialPort({ path: "/dev/cu.usbmodem11101", baudRate: 9600, autoOpen: false, }); const parser = port.pipe(new ReadlineParser()); port.open(function (error) { if (error) { console.log("연결된 포트 없음"); } else { console.log("시리얼 포트 연결됨"); const client = mqtt.connect("mqtt://mqtt-dashboard.com"); client.on("connect", function () { console.log("MQTT 클라이언트 연결됨"); parser.on("data", function (data) { var datetime = new Date(); var year = datetime.getFullYear(); var month = datetime.getMonth() + 1; var day = datetime.getDate(); var hours = datetime.getHours(); var minutes = datetime.getMinutes(); var seconds = datetime.getSeconds(); var timestamp = year + "-" + num_2(month) + "-" + num_2(day) + " " + num_2(hours) + ":" + num_2(minutes) + ":" + num_2(seconds); function num_2(num) { return (num < 10 ? "0" : "") + num; } var values = data.trim().split(","); var senddata = { timestamp: timestamp, value1: values[0], value2: values[1], }; client.publish("pys/1111", JSON.stringify(senddata)); console.log("전송된 데이터:", senddata); }); }); } });

웹 응용 프로그램 작성 정리

Part 1: 웹 응용 프로그램

웹 프로그래밍

  • 웹 프로그래밍: 웹사이트 혹은 웹 페이지를 만드는 과정
  • 프론트 엔드 프로그래밍: HTML, CSS, JavaScript 등을 사용하여 웹 브라우저에서 동작하는 코드 작성
  • 백 엔드 프로그래밍: Python, Ruby, PHP, Java 등 서버에서 동작하는 코드 작성

웹 서비스를 만드는 언어 및 프레임워크

  • 최근 사용되는 언어: Python, Java, C#, Ruby, PHP 등
  • 프레임워크:
    • Java: Spring
    • Ruby: Ruby on Rails
    • PHP: Laravel
    • Node.js: Express

Part 2: 웹 응용 프로그램 실습

Node.js

  • Node.js: 구글의 크롬 V8 자바스크립트 엔진을 기반으로 한 비동기 I/O를 지원하는 고성능 네트워크 서버
  • NPM: Node Package Manager로 Node.js 패키지 관리

Express

  • Express: Node.js의 웹 프레임워크로, 웹 애플리케이션 API를 구축 가능
  • 설치 및 서버 구축:
    • 설치: npm install express --save
    • 기본 서버 코드:
      • const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello World!')); app.listen(3000, () => console.log('Server is running on port 3000'));

Node.js 모듈

  • 기본 모듈:
    • assert, buffer, child_process, crypto, fs, http 등
  • 사용자 모듈 작성:
    • 모듈 내보내기: exports.myDateTime = function () { return Date(); };
    • 모듈 사용하기:
      • const dt = require('./myDateTimeModule'); console.log(dt.myDateTime());

파일 시스템

  • 파일 읽기:
    • const fs = require('fs'); fs.readFile('demofile1.html', (err, data) => { if (err) throw err; console.log(data.toString()); });
  • 파일 생성:
    • fs.appendFile('mynewfile.txt', 'Hello content!', (err) => { if (err) throw err; console.log('Saved!'); });
    • fs.open('mynewfile2.txt', 'w', (err, file) => { if (err) throw err; console.log('Saved!'); });
    • fs.writeFile('mynewfile3.txt', 'Hello content!', (err) => { if (err) throw err; console.log('Saved!'); });

이벤트 모듈

  • 이벤트 처리:
    • const events = require('events'); const eventEmitter = new events.EventEmitter(); const myEventHandler = () => { console.log('I hear a scream!'); } eventEmitter.on('scream', myEventHandler); eventEmitter.emit('scream');

Socket.io

  • 실시간 웹 애플리케이션 지원 라이브러리:
    • 설치: npm install socket.io
    • 서버 -> 클라이언트 이벤트 전송: socket.emit('event_name', msg);
    • 클라이언트 -> 서버 이벤트 수신:
      • socket.on('event_name', (data) => { console.log('Message from Client: ' + data); });

Arduino & Node.js 통신

ArduinoJson

  • 역직렬화:
    • StaticJsonDocument<200> doc; String json = "{'name':'홍길동'}"; deserializeJson(doc, json); String temp_name = doc["name"]; Serial.println(temp_name);
  • 직렬화:
    • DynamicJsonDocument doc(200); doc["name"] = "홍길동"; String jsonData = ""; serializeJson(doc, jsonData); Serial.println(jsonData);

회로 구성 및 코드

  • Node.js와 Arduino 통신:
    • const { SerialPort } = require('serialport'); const { ReadlineParser } = require('@serialport/parser-readline'); const port = new SerialPort({ path: 'COM9', baudRate: 9600 }); const parser = new ReadlineParser(); port.pipe(parser); const express = require('express'); const app = express(); const http = require('http'); const server = http.createServer(app); const { Server } = require("socket.io"); const io = new Server(server); app.get('/', (req, res) => res.sendFile(__dirname + "/index.html")); io.on('connection', (socket) => { console.log('a user connected'); socket.on('disconnect', () => console.log('user disconnected')); parser.on('data', (data) => { console.log(data); socket.emit('data', data); }); socket.on('message', (msg) => { console.log("Message from client: " + msg); if(msg === 'ledon') port.write("o"); if(msg === 'ledoff') port.write("f"); socket.emit('result', `Received message: "${ msg }"`); }); }); server.listen(3000, () => console.log("Server is running on port 3000"));
      Arduino 코드:
      #include <ArduinoJson.h> StaticJsonDocument<48> doc; void setup() { Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); } void loop() { int value = analogRead(0); doc["light"] = value; serializeJson(doc, Serial); Serial.println(); if (Serial.available() > 0) { char command = (char)Serial.read(); if (command == 'o') digitalWrite(LED_BUILTIN, HIGH); if (command == 'f') digitalWrite(LED_BUILTIN, LOW); } delay(2000); }

댓글

guest