🔍 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 버스 내부 풀업이 있어 풀업 저항이 필요 없음
#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초 대기합니다. }
🔍 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️⃣ 주요 구성 요소
- SPI Master: 데이터를 전송하고 클럭 신호를 생성하는 장치.
- SPI Slave: 데이터를 수신하고 마스터의 클럭 신호에 맞춰 데이터를 전송하는 장치.
2️⃣ 데이터 전송 과정
- SCK (Serial Clock): SPI 마스터가 생성하는 클럭 신호. 마스터와 슬레이브 간의 데이터 동기화를 위해 사용됩니다.
- MOSI (Master Out Slave In): 마스터에서 슬레이브로 데이터가 전송되는 라인.
- MISO (Master In Slave Out): 슬레이브에서 마스터로 데이터가 전송되는 라인.
- 보통 MSB를 전송하고 LSB에서 수신
⚙️ Multi-drop Connection
- 하나의 마스터와 여러 개의 슬레이브 연결 가능:
- MISO, MOSI, SCK: 이 세 신호는 모든 장치에서 공유됩니다.
- 마스터는 슬레이브마다 하나의 /SS(슬레이브 선택) 선을 제공합니다:
- 마스터가 여러 슬레이브를 제어할 때 각 슬레이브를 선택하기 위해 /SS 선을 사용합니다.
- /SS 신호가 너무 가까이 배치되지 않도록 주의해야 합니다.
- /SS 선은 GPIO 출력선으로 사용 가능:
- 많은 슬레이브 장치를 연결할 수 있습니다.
- 마스터가 /SS 신호를 LOW로 설정하여 특정 슬레이브를 선택합니다.
- 동시에 슬레이브는 데이터 전송을 시작합니다.
- 마스터는 데이터를 전송하고 슬레이브로부터 데이터를 수신합니다.
- 마스터가 /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
📌 관련 함수
⚙️ 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 모드
모드 | 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 제어 }
- SPI.setClockDivider:
- 이 함수는 SPI 클럭 속도를 설정하는 데 사용됩니다.
- SPI 통신에서 마스터 장치는 클럭 신호를 생성하며, 이 클럭 신호는 슬레이브 장치와 동기화됩니다.
- SPI_CLOCK_DIV16:
SPI_CLOCK_DIV16
는 SPI 클럭 디바이더의 한 값입니다.- 시스템 클럭 주파수를 16으로 나눈 값을 SPI 클럭 속도로 설정합니다.
- 예를 들어, 시스템 클럭이 16 MHz라면,
SPI_CLOCK_DIV16
를 설정하면 SPI 클럭 속도는 16 MHz / 16 = 1 MHz가 됩니다.
🔍 직렬 통신
- 병렬 통신
- 한 번에 여러 비트(한 바이트, 8-bit)를 한 번에 전송
- 송수신 장치 간에 전송 비트만큼 선을 연결해야 함
- 직렬 통신
- 시프트 레지스터를 이용하여 한 번에 한 비트씩 전송
- 데이터를 전송하기 위하여 최소 한 개의 선 연결 필요
- 직렬 통신 방법
- 동기 직렬 통신 (Synchronous Serial Communication)
- 기준 클록을 공유하여 데이터를 동기화
- 예: SPI, I2C
- 비동기 직렬 통신 (Asynchronous Serial Communication)
- 기준 클록을 공유하지 않고, 미리 양측에 통신 속도를 설정하여 통신
- 예: UART
- Simplex: 한 방향으로만 데이터 전송 (예: 키보드, 마우스)
- Half-duplex: 양방향 통신이 가능하지만 동시에 이루어지지 않음 (예: 무전기)
- Full-duplex: 양방향 통신이 동시에 이루어짐 (예: 전화)
- MCU는 통신 제어기를 포함하여 직렬 통신을 처리합니다.
- 송신 과정: 프로세서가 통신 제어기에 데이터를 기록하고, 통신 제어기가 이를 채널로 전송합니다.
- 수신 과정: 통신 제어기가 채널에서 데이터를 수신하고, 프로세서가 이를 읽어 처리합니다.
📌 데이터 전송 타이밍
- 프로세서와 통신제어기, 통신제어기와 채널간 데이터 전송 속도가 다르기 때문에 버퍼를 이용
- 시프트 레지스터를 이용한 직렬/병렬 변환:
- 데이터 전송 시 시프트 레지스터를 사용하여 직렬 데이터를 병렬로 변환하거나, 병렬 데이터를 직렬로 변환합니다.
- 입출력 큐를 이용한 데이터 버퍼링:
- 데이터 전송과 수신을 위한 버퍼를 사용하여 데이터 흐름을 관리합니다.
- 버퍼링을 통해 데이터 전송 속도와 프로세서의 처리 속도 차이를 조정할 수 있습니다.
- 직렬 통신 시, 데이터를 직렬로 보내고 병렬로 읽어들임.
- 송신 과정:
- 프로세서:
- 프로세서는 데이터를 병렬 방식으로 통신 제어기에 보냅니다.
- 이 데이터는 주로 8비트 단위(한 바이트)로 전송됩니다.
- 통신 제어기 (Tx Register, PISO Shift Register):
- 통신 제어기는 프로세서로부터 받은 병렬 데이터를 Tx 레지스터에 저장합니다.
- Tx 레지스터의 병렬 데이터는 PISO(Parallel-In Serial-Out) 시프트 레지스터로 전달됩니다.
- PISO 시프트 레지스터는 병렬 데이터를 직렬 데이터로 변환합니다.
- 변환된 직렬 데이터는 통신 채널을 통해 전송됩니다.
- 수신 과정:
- 통신 제어기 (SIPO Shift Register, Rx Register):
- 통신 채널을 통해 수신된 직렬 데이터는 SIPO(Serial-In Parallel-Out) 시프트 레지스터에 입력됩니다.
- SIPO 시프트 레지스터는 직렬 데이터를 병렬 데이터로 변환합니다.
- 변환된 병렬 데이터는 Rx 레지스터에 저장됩니다.
- 프로세서:
- 프로세서는 Rx 레지스터에서 병렬 데이터를 읽어와 처리합니다.
- 단점
- 송신:
- 데이터를 완전히 전송하기 전에 다음 데이터가 도착하면 오류가 발생합니다.
- 수신:
- 수신된 데이터를 읽어 가기 전에 다음 데이터가 도착하면 overrun 오류가 발생합니다.
- 직렬통신 제어기: 프로세서로부터 병렬 데이터(8bit)를 받아 직렬 신호로 변환하는 장치
- 동기, 비동기 통신 설정 가능하지만 보통 비동기(UART)로 사용
- Full duplex: 송신선과 수신선 각각 연결해 동시에 송수신 가능
- RS232C:
- 비동기 직렬 통신 표준
- 중간에 모뎀 추가해서 장거리 통신 가능
- 직렬 통신 케이블로 연결
- 모뎀 사용하지 않을 때, 통신 거리는 약 15m
⚙️ 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 통신의 속도가 결정됩니다.
UBRRH
와UBRRL
로 나뉘어 상위 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 주파수 사용 시
⚙️ Atmega328 UART 프레임 설정
- 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 쓰기
- 준비 상태 대기:
- EECR 레지스터의 EEPE 비트가 0이 될 때까지 대기.
- 주소 설정:
- EEAR 레지스터에 주소 설정.
- 데이터 설정:
- EEDR 레지스터에 데이터 기록.
- 쓰기 명령:
- EECR 레지스터의 EEMPE 비트를 1로 설정.
- EECR 레지스터의 EEPE 비트를 1로 설정하여 쓰기 시작.
EEPROM 읽기
- 준비 상태 대기:
- EECR 레지스터의 EEPE 비트가 0이 될 때까지 대기.
- 주소 설정:
- EEAR 레지스터에 주소 설정.
- 읽기 명령:
- EECR 레지스터의 EERE 비트를 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 브로커 설치: Mosquitto 다운로드
- 발행:
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); }
댓글