티스토리 뷰
아두이노(seeed Xiao) + 자석감지 센서 MLX90393 + 디스플레이 ST7789 TFT (2.0", 240×320) : 뮤토스코프 + 숨 프로젝트
잉_민 2026. 3. 1. 11:54뮤토스코프 인터랙티브 설치 — 프로젝트 정리
1. 주제
사람의 숨이 바람개비(이 안에 자석들어있음)를 돌리면, 자석 센서가 회전을 감지하고
그 주기에 따라 영상 재생 속도와 모터 속도가 실시간으로 반응하는 인터랙티브 설치 작품.
숨 → 바람개비(자석 회전) → MLX90393 감지 → 속도 계산 → 모터 PWM(뮤토스코프 회전) + TFT 스크린 영상 프레임 동기화

2. 방법론
전체 흐름
[숨 → 바람개비 회전]
→ 자석 센서: 회전 1회 감지
→ period(ms) 계산 (회전 주기)
→ 영상 속도: frameDelay = period / 21
→ 모터 속도: PWM = map(period, 500, 10000, 250, 190)
→ TFT: 21프레임 루프 재생
영상 재생 기준
- 원래 속도: 1초에 2장 (500ms/frame)
- period = 10500ms일 때 frameDelay = 500ms → 2fps
- 빠른 숨 → period 짧음 → 빠른 영상 + 높은 PWM
- 느린 숨 → period 김 → 느린 영상 + 낮은 PWM
정지 감지
- 자석이 2초 이상 센서에 고정 → 정지 판정
- 정지 후 자석이 실제로 벗어날 때까지 재감지 차단 (waitingForRelease)
- 정지 시 lastTrigger = 0 리셋 → 다음 회전 감지 가능
3. 하드웨어
메인 보드
- XIAO ESP32C6 (싱글코어, 160MHz)
센서: MLX90393 자석 센서
- 프로토콜: I2C
- SDA: GPIO16, SCL: GPIO17
- 설정: GAIN_1X, RES_19(XY), RES_16(Z)
- 감지값 범위: magnitude 110~210 (자석 없을 때), 임계값 threshold = 160.0
- 폴링: 16ms (약 60Hz)
디스플레이: ST7789 TFT (2.0", 240×320)
- 프로토콜: 하드웨어 SPI (SPIClass(SPI))
- CS: GPIO21, DC: GPIO22, RST: GPIO23, MOSI: GPIO18, SCK: GPIO19
- SPI 속도: 40MHz
- 설정: rotation(1), invertDisplay(true)
- 이미지 위치: setAddrWindow(60, 20, 200, 200) → 320×240 화면 중앙
- 파일읽기: 27ms, 화면출력: 24ms → 총 51ms
모터: N20 소형 DC 모터
- 핀: GPIO0 (D0)
- 트랜지스터: 2N2222A (TO-18 금속캔)
- PWM 범위: 190~250 (period 기반 자동 조정)
- 기동 최소 PWM: 200 이상
4. 회로도
[모터 회로]
ESP32C6 D0 ──[1kΩ]── 2N2222A 베이스
2N2222A 이미터 → GND
2N2222A 컬렉터 → 모터(-)
3.3V ──────────────── 모터(+)
모터 양단에 다이오드 (스트라이프→모터+)
[2N2222A TO-18 핀아웃]
탭 기준:
탭 옆 = 이미터 → GND
가운데 = 베이스 → 1kΩ → D0
반대 = 컬렉터 → 모터(-)
[자석 센서 회로]
MLX90393 SDA → GPIO16
MLX90393 SCL → GPIO17
MLX90393 VCC → 3.3V
MLX90393 GND → GND
[TFT 회로]
TFT CS → GPIO21
TFT DC → GPIO22
TFT RST → GPIO23
TFT MOSI → GPIO18
TFT SCK → GPIO19
TFT VCC → 3.3V
TFT GND → GND
TFT BLK → 3.3V (백라이트 ON)
5. 초기화 순서 (중요)
1. Wire.begin(16, 17) // I2C 먼저
2. sensor.begin_I2C()
3. pinMode(MOTOR_PIN) + analogWrite // 모터
4. spi->begin() // SPI
5. LittleFS.begin()
6. tft.init() // TFT 마지막
순서 틀리면 "센서 못찾음" 발생.
6. 영상 파일
- 경로: LittleFS /img_00.raw ~ /img_20.raw
- 포맷: RGB565 raw, 200×200px = 80,000 bytes
- 총 21프레임
- 바이트스왑 필수: frameBuf[i] = (val >> 8) | (val << 8)
최종 코드
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <LittleFS.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_MLX90393.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18
#define TFT_SCK 19
#define MOTOR_PIN 0
#define IMG_WIDTH 200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21
SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
Adafruit_MLX90393 sensor = Adafruit_MLX90393();
uint16_t frameBuf[200 * 200];
float threshold = 360.0;
bool magnetPresent = false;
bool isStopped = false;
unsigned long lastTrigger = 0;
unsigned long frameDelay = 500;
int currentFrame = 0;
unsigned long lastFrame = 0;
void setup() {
Serial.begin(115200);
delay(3000);
Serial.println("시작");
Wire.begin(16, 17);
if (!sensor.begin_I2C()) {
Serial.println("센서 못찾음");
while(1);
}
Serial.println("센서 OK");
sensor.setGain(MLX90393_GAIN_1X);
sensor.setResolution(MLX90393_X, MLX90393_RES_19);
sensor.setResolution(MLX90393_Y, MLX90393_RES_19);
sensor.setResolution(MLX90393_Z, MLX90393_RES_16);
ledcAttach(MOTOR_PIN, 5000, 8);
ledcWrite(MOTOR_PIN, 200);
spi->begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
LittleFS.begin(true);
tft.init(240, 320, SPI_MODE0);
tft.setSPISpeed(40000000);
tft.setRotation(1);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_BLACK);
Serial.println("TFT OK");
}
void loop() {
float x, y, z;
if (sensor.readData(&x, &y, &z)) {
float magnitude = sqrt(x*x + y*y + z*z);
Serial.print("M:"); Serial.print(magnitude);
Serial.print(" stopped:"); Serial.print(isStopped);
Serial.print(" magnet:"); Serial.println(magnetPresent);
// [1] 상승 엣지
if (magnitude > threshold && !magnetPresent) {
magnetPresent = true;
unsigned long now = millis();
Serial.println("---1---");
if (isStopped) {
isStopped = false;
ledcWrite(MOTOR_PIN, 200);
frameDelay = 500;
Serial.println(">>> Restarting 200");
} else if (lastTrigger > 0) {
unsigned long period = now - lastTrigger;
if (period > 500 && period < 10000) {
frameDelay = period / 21;
int pwm = map(period, 500, 10000, 250, 190);
pwm = constrain(pwm, 190, 250);
ledcWrite(MOTOR_PIN, pwm);
Serial.print(">>> period: "); Serial.print(period);
Serial.print(" PWM: "); Serial.println(pwm);
} else {
ledcWrite(MOTOR_PIN, 200);
Serial.println(">>> period 범위 밖 200");
}
}
lastTrigger = now;
}
// 자석 벗어남
if (magnitude < threshold * 0.9) {
magnetPresent = false;
}
}
// [2] 마지막 회전 후 2초 = 정지
if (!isStopped && lastTrigger > 0 && millis() - lastTrigger > 2000) {
isStopped = true;
lastTrigger = 0;
magnetPresent = false; // 여기
ledcWrite(MOTOR_PIN, 0);
Serial.println("---0--- PWM:0");
}
unsigned long now = millis();
if (now - lastFrame > frameDelay) {
char path[20];
sprintf(path, "/img_%02d.raw", currentFrame);
File f = LittleFS.open(path, "r");
if (f) {
f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
f.close();
for (int i = 0; i < IMG_WIDTH * IMG_HEIGHT; i++) {
uint16_t val = frameBuf[i];
frameBuf[i] = (val >> 8) | (val << 8);
}
tft.startWrite();
tft.setAddrWindow(60, 20, IMG_WIDTH, IMG_HEIGHT);
tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
tft.endWrite();
}
currentFrame = (currentFrame + 1) % TOTAL_IMGS;
lastFrame = now;
}
delay(16);
}
7. 시리얼 출력 형식
---1--- → 자석 감지 (한 바퀴)
---0--- → 정지 (2초 이상 고정)
period: 2000 frameDelay: 95 PWM: 235
8. 주요 해결 이슈 이력
문제 원인 해결
| 센서 못찾음 | 초기화 순서 잘못됨 | I2C를 SPI보다 먼저 |
| TFT 위에서 아래 렌더링 | drawRGBBitmap 줄마다 호출 | frameBuf + writePixels 한번에 |
| TFT 화면출력 2169ms | 소프트웨어 SPI | SPIClass(SPI) 하드웨어 SPI |
| 정지 후 재감지 안됨 | lastTrigger 리셋 안됨 + waitingForRelease 누락 | 정지 시 lastTrigger=0, waitingForRelease 플래그 |
| 모터 안돌음 | PWM 너무 낮음 | 최소 200 이상 |
9. 정지 후 모터 재시작 문제 — 시행착오 전체 기록
목표
숨을 멈추면 모터 정지 → 다시 숨을 불면 모터 재시작
시행착오 목록
시도 1: 2초 고정 감지 자석이 센서 위에 2초 이상 머물면 정지 판정. 문제: 숨을 천천히 불면 정상 회전인데도 정지 판정남. magnetSinceTime 방식 자체가 틀림.
시도 2: analogWrite(MOTOR_PIN, 0) 모터 끄기. 문제: ESP32C6에서 analogWrite(pin, 0)은 LEDC PWM 채널 자체를 해제(detach)해버림. 이후 analogWrite(pin, 200) 호출해도 채널이 없어서 무반응.
시도 3: digitalWrite(MOTOR_PIN, LOW) PWM 채널 유지하면서 끄기 시도. 문제: LEDC PWM이 켜진 상태에서 digitalWrite는 무시됨. PWM이 핀을 장악하고 있어서 효과 없음.
시도 4: analogWrite(MOTOR_PIN, 10) 완전히 끄지 않고 최소값 유지. 문제: PWM 10은 모터가 돌기엔 너무 낮고 꺼지지도 않아서 모터가 버둥대며 전류를 계속 끌어당김 → 삐 소리 + ESP32C6 전력 불안정 → 렉.
시도 5: pinMode 재초기화 + delay 킥스타트
pinMode(MOTOR_PIN, OUTPUT);
analogWrite(MOTOR_PIN, 255);
delay(50);
analogWrite(MOTOR_PIN, 200);
문제: 채널이 이미 해제된 상태라 효과 없음.
시도 6: 정지 감지 방식 변경 "자석 2초 고정" → "마지막 회전 후 2초 동안 새 회전 없음"으로 변경. 개선됨. 하지만 재시작 여전히 안됨.
시도 7: ledcAttach + ledcWrite (최종 해결) analogWrite 완전히 제거하고 LEDC API 직접 사용. ledcWrite(pin, 0)은 채널 유지하면서 0 출력 → 채널 해제 안됨.
ledcAttach(MOTOR_PIN, 5000, 8); // setup에서 한번만
ledcWrite(MOTOR_PIN, 200); // 켜기
ledcWrite(MOTOR_PIN, 0); // 끄기 (채널 유지)
문제: 재시작 여전히 안됨.
시도 8: magnetPresent 리셋 누락 발견 (최종 해결) ---0--- 후 magnetPresent = true 상태로 남아있어서 상승 엣지 조건 !magnetPresent가 영원히 안 걸림. 정지 시 magnetPresent = false 추가로 완전 해결.
if (!isStopped && lastTrigger > 0 && millis() - lastTrigger > 2000) {
isStopped = true;
lastTrigger = 0;
magnetPresent = false; // 핵심
ledcWrite(MOTOR_PIN, 0);
Serial.println("---0---");
}
결론
두 가지가 같이 해결되어야 했음:
- analogWrite → ledcWrite 교체 (PWM 채널 유지)
- 정지 시 magnetPresent = false 리셋 (상승 엣지 재감지 가능하게)
시행착오들..
디스플레이: ST7789 TFT (2.0", 240×320)
- GND → GND
- VCC → 3.3V
- SCL → D8
- SDA → D10
- RES → D5
- DC → D4
- CS → D3
- BLK → 3.3V
MOSI 핀 번호를 17이라고 계속 우겼는데 18이 맞았어
바꾼 tft 되는거 빨강.
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <SPI.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18 // D10 = GPIO18 (맞는 번호)
#define TFT_SCK 19 // D8 = GPIO19
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
void setup() {
tft.init(240, 280);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_RED);
}
void loop() {}
영상출력
참고 : https://ing-min.tistory.com/323
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <LittleFS.h>
#include <SPI.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18
#define TFT_SCK 19
#define IMG_WIDTH 200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
uint16_t lineBuf[200];
void drawRaw(int index) {
char path[20];
sprintf(path, "/img_%02d.raw", index);
File f = LittleFS.open(path, "r");
if (!f) return;
for (int y = 0; y < IMG_HEIGHT; y++) {
int bytesRead = 0;
uint8_t* buf = (uint8_t*)lineBuf;
while (bytesRead < IMG_WIDTH * 2) {
int r = f.read(buf + bytesRead, IMG_WIDTH * 2 - bytesRead);
if (r <= 0) break;
bytesRead += r;
}
for (int x = 0; x < IMG_WIDTH; x++) {
uint16_t val = lineBuf[x];
lineBuf[x] = (val >> 8) | (val << 8);
}
tft.drawRGBBitmap(20, 40 + y, lineBuf, IMG_WIDTH, 1);
}
f.close();
}
void setup() {
Serial.begin(115200);
LittleFS.begin(true);
tft.init(240, 280);
tft.setRotation(0);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_BLACK);
}
void loop() {
for (int i = 0; i < TOTAL_IMGS; i++) {
drawRaw(i);
delay(80);
}
}
영상이 위에서 아래로 선을 그리며 렌더링 되는문제
해결
ms (밀리초)
- 파일읽기: 27ms → LittleFS에서 80KB 읽는 시간
- 화면출력: 24ms → SPI로 TFT에 전송하는 시간
--디버깅 코드
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <LittleFS.h>
#include <SPI.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18
#define TFT_SCK 19
#define IMG_WIDTH 200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21
SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
uint16_t frameBuf[200 * 200];
int currentFrame = 0;
unsigned long lastFrame = 0;
void setup() {
Serial.begin(115200);
delay(1000);
spi->begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
LittleFS.begin(true);
tft.init(240, 320, SPI_MODE0);
tft.setSPISpeed(40000000);
tft.setRotation(0);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_BLACK);
Serial.println("TFT OK");
}
void loop() {
unsigned long now = millis();
if (now - lastFrame > 50) {
char path[20];
sprintf(path, "/img_%02d.raw", currentFrame);
File f = LittleFS.open(path, "r");
if (f) {
f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
f.close();
for (int i = 0; i < IMG_WIDTH * IMG_HEIGHT; i++) {
uint16_t val = frameBuf[i];
frameBuf[i] = (val >> 8) | (val << 8);
}
tft.startWrite();
tft.setAddrWindow(20, 60, IMG_WIDTH, IMG_HEIGHT);
tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
tft.endWrite();
}
currentFrame = (currentFrame + 1) % TOTAL_IMGS;
lastFrame = now;
}
}
되는 TFT 센서 코드
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <LittleFS.h>
#include <SPI.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18
#define TFT_SCK 19
#define IMG_WIDTH 200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21
SPIClass* spi = new SPIClass(SPI);
Adafruit_ST7789 tft = Adafruit_ST7789(spi, TFT_CS, TFT_DC, TFT_RST);
uint16_t frameBuf[200 * 200];
int currentFrame = 0;
unsigned long lastFrame = 0;
void setup() {
Serial.begin(115200);
delay(1000);
spi->begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
LittleFS.begin(true);
tft.init(240, 320, SPI_MODE0);
tft.setSPISpeed(40000000);
tft.setRotation(1);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_BLACK);
Serial.println("TFT OK");
}
void loop() {
unsigned long now = millis();
if (now - lastFrame > 50) {
char path[20];
sprintf(path, "/img_%02d.raw", currentFrame);
File f = LittleFS.open(path, "r");
if (f) {
f.read((uint8_t*)frameBuf, IMG_WIDTH * IMG_HEIGHT * 2);
f.close();
for (int i = 0; i < IMG_WIDTH * IMG_HEIGHT; i++) {
uint16_t val = frameBuf[i];
frameBuf[i] = (val >> 8) | (val << 8);
}
tft.startWrite();
tft.setAddrWindow(60, 20, IMG_WIDTH, IMG_HEIGHT);
tft.writePixels(frameBuf, IMG_WIDTH * IMG_HEIGHT);
tft.endWrite();
}
currentFrame = (currentFrame + 1) % TOTAL_IMGS;
lastFrame = now;
}
}
자석감지 센서
센서: MLX90393 자석 센서
- 프로토콜: I2C
- SDA: GPIO16, SCL: GPIO17
- 설정: GAIN_1X, RES_19(XY), RES_16(Z)
- 감지값 범위: magnitude 110~210 (자석 없을 때), 임계값 threshold = 160.0
- 폴링: 16ms (약 60Hz)
MLX90393 XIAO
3V3 → 3.3V
GND → GND
SDA → D6 (GPIO16)
SCL → D7 (GPIO17)
두개 통합코드 (자석감지 , tft)
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <LittleFS.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_MLX90393.h>
#define TFT_CS 21
#define TFT_DC 22
#define TFT_RST 23
#define TFT_MOSI 18
#define TFT_SCK 19
#define IMG_WIDTH 200
#define IMG_HEIGHT 200
#define TOTAL_IMGS 21
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
Adafruit_MLX90393 sensor = Adafruit_MLX90393();
uint16_t lineBuf[200];
float threshold = 200.0;
bool magnetPresent = false;
unsigned long lastTrigger = 0;
float currentSpeed = 0;
int currentFrame = 0;
unsigned long lastFrame = 0;
void drawRaw(int index) {
char path[20];
sprintf(path, "/img_%02d.raw", index);
File f = LittleFS.open(path, "r");
if (!f) return;
for (int y = 0; y < IMG_HEIGHT; y++) {
int bytesRead = 0;
uint8_t* buf = (uint8_t*)lineBuf;
while (bytesRead < IMG_WIDTH * 2) {
int r = f.read(buf + bytesRead, IMG_WIDTH * 2 - bytesRead);
if (r <= 0) break;
bytesRead += r;
}
for (int x = 0; x < IMG_WIDTH; x++) {
uint16_t val = lineBuf[x];
lineBuf[x] = (val >> 8) | (val << 8);
}
tft.drawRGBBitmap(20, 40 + y, lineBuf, IMG_WIDTH, 1);
}
f.close();
}
void setup() {
Serial.begin(115200);
Wire.begin(16, 17); // SDA=D6, SCL=D7
LittleFS.begin(true);
tft.init(240, 280);
tft.setRotation(0);
tft.invertDisplay(true);
tft.fillScreen(ST77XX_BLACK);
if (!sensor.begin_I2C()) {
Serial.println("MLX90393 못찾음");
} else {
Serial.println("MLX90393 OK");
sensor.setGain(MLX90393_GAIN_1X);
sensor.setResolution(MLX90393_X, MLX90393_RES_19);
sensor.setResolution(MLX90393_Y, MLX90393_RES_19);
sensor.setResolution(MLX90393_Z, MLX90393_RES_16);
}
}
void loop() {
float x, y, z;
if (sensor.readData(&x, &y, &z)) {
float magnitude = sqrt(x*x + y*y + z*z);
if (magnitude > threshold && !magnetPresent) {
magnetPresent = true;
unsigned long now = millis();
if (lastTrigger > 0) {
float period = (now - lastTrigger) / 1000.0;
currentSpeed = 1.0 / period;
Serial.print("속도: ");
Serial.println(currentSpeed);
}
lastTrigger = millis();
}
if (magnitude < threshold * 0.7) magnetPresent = false;
}
// 속도에 따라 재생 속도 조절
float frameDelay = 80;
if (currentSpeed > 0) {
frameDelay = max(20.0, 150.0 / (currentSpeed * TOTAL_IMGS));
}
unsigned long now = millis();
if (now - lastFrame > frameDelay) {
drawRaw(currentFrame);
currentFrame = (currentFrame + 1) % TOTAL_IMGS;
lastFrame = now;
}
}
자석 110- 310 값 범위.

'Iot > Arduino' 카테고리의 다른 글
| Arduino IDE : xiao seeed esp32-c6 + TFT ST7789 (0) | 2026.02.26 |
|---|---|
| OV7670_아두이노 _카메라 (0) | 2024.04.26 |
| arduino nano 33 IoT _ 무선 wifi 연결 + mqtt 통신 (0) | 2024.04.15 |
| 미니모터(ja12-n20) +아두이노 + 드라이버 (HG7881) (0) | 2024.03.30 |
| 아두이노 + LED strip (6812 RGBW) (0) | 2024.02.21 |
- Total
- Today
- Yesterday
- 라즈베리파이
- node.js
- MCP
- 4d guassian splatting
- VR
- 후디니
- Python
- Java
- ESP32
- zclaw
- VFXgraph
- 유니티
- docker
- sequelize
- AI
- opencv
- OpenClaw
- Arduino
- Express
- DeepLeaning
- colab
- Midjourney
- houdini
- three.js
- opticalflow
- Unity
- TouchDesigner
- CNC
- MQTT
- RNN
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |

