카테고리 없음

ESP32 와 ESP32 의 통신 테스트

trustworthyhand 2025. 8. 17. 23:33

ESP32-WROOM-32U 모델을  2개 사용하고있습니다.

ESP32 NOW 로 리모콘과 로봇을 움직이는 코드를 만들어보자 
ESP-NOW로 A(송신기/컨트롤러) → B(수신기/로봇) 에게 FORWARD / STOP / REVERSE 명령을 주고,

                     B는 모터드라이버(EN/DIR/PWM)를 안전하게 구동하는 양방향 코드가 오늘의 목표이다.

 

ESP32 와 ESP32 연결을 하기위해서는 서로의 mac 주소를 알아야합니다 밑에 코드를 업로드를 한후 
MAC주소를 저장해주세요 (송신기, 수신기)

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.  
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include <esp_wifi.h>

void readMacAddress(){
  uint8_t baseMac[6];
  esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
  if (ret == ESP_OK) {
    Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
                  baseMac[0], baseMac[1], baseMac[2],
                  baseMac[3], baseMac[4], baseMac[5]);
  } else {
    Serial.println("Failed to read MAC address");
  }
}

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

  WiFi.mode(WIFI_STA);
  WiFi.STA.begin();

  Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
  readMacAddress();
}
 
void loop(){

}

 

포트번호 COM5, 흰색 

송신기(컨트롤러)-MAC주소를 저장해주세요

송신기의  MAC주소 : 44:1d:64:f4:dd:c4

포트번호 COM6, 검정색 

수신기(로봇)-MAC주소를 저장해주세요

수신기의  MAC주소 : ec:e3:34:21:a1:e8

이제 송신기 코드를 작성해보자 

(송신기는 로봇의 컨트롤러여서 수신기의 mac주소를 알아야합니다)

// ===== A(송신기) — ESP-NOW Controller =====
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>  // ★ Wi-Fi 저수준 API(채널 설정 등) 사용 시 필요

// ★ 수신기(B)의 MAC 주소로 바꾸세요 (사용자 제공 주소 유지)
uint8_t PEER_MAC[] = { 0xEC, 0xE3, 0x34, 0x21, 0xA1, 0xE8 };

#define ESPNOW_CHANNEL 1   // ★ 송신/수신 모두 동일 채널이어야 통신됨

// 송수신에 사용할 페이로드(메시지) 구조체
struct ControlMsg {
  char     command;  // 'F'/'S'/'R'
  uint32_t seq;      // 송신 시퀀스
  uint32_t ms;       // millis() 기준 타임스탬프
};

volatile uint32_t g_seq = 0;

// ===== 전송 완료 콜백 (보드 패키지 버전별 호환) =====
#if ESP_ARDUINO_VERSION_MAJOR >= 3
// ESP32 core v3.x (ESP-IDF 5): info + status
void onSendDone(const esp_now_send_info_t *info, esp_now_send_status_t status) {
  Serial.print("[A] Send status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "SUCCESS" : "FAIL");

  // ★ v3.x에서는 목적지 주소 필드명이 des_addr 입니다. (dest_addr 아님)
  if (info) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr),
             "%02X:%02X:%02X:%02X:%02X:%02X",
             info->des_addr[0], info->des_addr[1], info->des_addr[2],
             info->des_addr[3], info->des_addr[4], info->des_addr[5]);
    Serial.print("[A] dest="); Serial.println(macStr);
  }
}
#else
// ESP32 core v2.x (ESP-IDF 4): mac + status
void onSendDone(const uint8_t *mac, esp_now_send_status_t status) {
  Serial.print("[A] Send status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "SUCCESS" : "FAIL");

  if (mac) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr),
             "%02X:%02X:%02X:%02X:%02X:%02X",
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    Serial.print("[A] dest="); Serial.println(macStr);
  }
}
#endif
// ============================================

// 피어(수신기) 등록 유틸리티
bool addPeer(const uint8_t *mac) {
  esp_now_peer_info_t peer{};          // 0으로 초기화
  memcpy(peer.peer_addr, mac, 6);
  peer.channel = ESPNOW_CHANNEL;       // ★ 채널 지정(수신기와 동일)
  peer.encrypt = false;                 // 암호화 사용 안 함(필요 시 true + 키 설정)
  return (esp_now_add_peer(&peer) == ESP_OK);
}

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

  // ★ ESP-NOW는 STA 모드 권장
  WiFi.mode(WIFI_STA);

  // ★ 예전의 promiscuous on/off 트릭은 필요 없음. 채널만 직접 설정
  esp_wifi_set_channel(ESPNOW_CHANNEL, WIFI_SECOND_CHAN_NONE);

  // ESP-NOW 초기화 및 콜백 등록
  if (esp_now_init() != ESP_OK) {
    Serial.println("[A] ESP-NOW init failed");
    while (1) delay(1000);
  }
  esp_now_register_send_cb(onSendDone);   // 버전에 맞는 콜백이 자동 바인딩됨

  // 피어(수신기) 추가
  if (!addPeer(PEER_MAC)) {
    Serial.println("[A] addPeer failed");
    while (1) delay(1000);
  }

  Serial.println("[A] Ready. Type F/S/R + Enter");
}

void loop() {
  // 시리얼에서 한 글자 읽어 명령 전송 ('F','S','R'만 사용)
  if (Serial.available()) {
    char c = toupper(Serial.read());  // 소문자 입력해도 대문자로 처리
    if (c=='F' || c=='S' || c=='R') {
      ControlMsg msg;
      msg.command = c;
      msg.seq     = ++g_seq;
      msg.ms      = millis();

      esp_err_t r = esp_now_send(PEER_MAC, (uint8_t*)&msg, sizeof(msg));
      Serial.print("[A] Send cmd="); Serial.write(c);
      Serial.print(", seq="); Serial.print(msg.seq);
      Serial.print(", ret="); Serial.println(r==ESP_OK ? "OK" : "ERR");
    }
  }

  // (선택) 버튼 입력 → 명령 전송 로직을 여기에 추가해도 됩니다.
  //  예: GPIO0=정방향, GPIO2=정지, GPIO4=역방향 등
}

 

이제 수신기 코드를 작성해보자 

(수신기는 로봇이여서 컨트로러의 송신기의 mac주소를 알아야합니다)

// ===== B(수신기) — ESP-NOW Robot =====
#include <Arduino.h>
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>

// ★ 송신기(A)의 MAC 주소 (제공: 44:1D:64:F4:DD:C4)
uint8_t PEER_MAC[] = { 0x44, 0x1D, 0x64, 0xF4, 0xDD, 0xC4 };

#define ESPNOW_CHANNEL 1  // ★ 송신/수신 동일 채널

// ---- 모터 핀 (ESP32-S 가정) ----
const int VSP_PIN = 21;  // PWM(LEDC)
const int DIR_PIN = 18;  // 방향
const int EN_PIN  = 16;  // Enable (Active-Low: LOW=Run)

// ---- PWM 설정 ----
int        LEDC_CH   = -1;   // ★ v3: ledcAttach()가 채널을 반환
const int  LEDC_FREQ = 1000; // 1 kHz
const int  LEDC_BITS = 8;    // 0~255 해상도
const int  PWM_RUN   = 200;  // 구동 듀티(실험값)
const int  PWM_START = 120;  // 시동 최소 듀티(실험값)

// ---- 안전/타이밍 ----
const uint32_t DEADTIME_MS   = 150;
const uint32_t FAILSAFE_MS   = 500;
const uint32_t RAMP_STEP_MS  = 10;
const int      RAMP_STEP     = 5;

// ---- 페이로드 ----
struct ControlMsg {
  char     command;   // 'F','S','R'
  uint32_t seq;
  uint32_t ms;
};

volatile ControlMsg g_lastMsg{};
volatile bool       g_hasNew   = false;
volatile uint32_t   g_lastRxMs = 0;

inline void pwmWrite(int duty) {
  if (LEDC_CH >= 0) ledcWrite(LEDC_CH, constrain(duty, 0, 255));
}

void motorStopHard() {
  pwmWrite(0);
  digitalWrite(EN_PIN, HIGH);  // Active-Low → HIGH=정지
}

void motorRunDir(bool forward) {
  motorStopHard();
  delay(DEADTIME_MS);
  digitalWrite(DIR_PIN, forward ? HIGH : LOW);
  delay(DEADTIME_MS);

  digitalWrite(EN_PIN, LOW);   // Run
  int duty = PWM_START;
  while (duty < PWM_RUN) {
    pwmWrite(duty);
    duty += RAMP_STEP;
    delay(RAMP_STEP_MS);
  }
  pwmWrite(PWM_RUN);
}

void applyCommand(char c) {
  switch (c) {
    case 'F': motorRunDir(true);  break;
    case 'R': motorRunDir(false); break;
    case 'S': motorStopHard();    break;
    default: break;
  }
}

#if ESP_ARDUINO_VERSION_MAJOR >= 3
void onRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
  if (len == sizeof(ControlMsg)) {
    ControlMsg msg;
    memcpy(&msg, data, sizeof(msg));
    memcpy((void*)&g_lastMsg, &msg, sizeof(msg)); // volatile 복사는 memcpy로
    g_hasNew   = true;
    g_lastRxMs = millis();

    if (info) {
      char macStr[18];
      snprintf(macStr, sizeof(macStr),
               "%02X:%02X:%02X:%02X:%02X:%02X",
               info->src_addr[0], info->src_addr[1], info->src_addr[2],
               info->src_addr[3], info->src_addr[4], info->src_addr[5]);
      Serial.print("[B] from "); Serial.println(macStr);
    }
  } else {
    Serial.print("[B] Unknown pkt len="); Serial.println(len);
  }
}

void onSendDone(const esp_now_send_info_t *info, esp_now_send_status_t status) {
  (void)info; (void)status;
}
#else
void onRecv(const uint8_t *mac, const uint8_t *data, int len) {
  if (len == sizeof(ControlMsg)) {
    ControlMsg msg;
    memcpy(&msg, data, sizeof(msg));
    memcpy((void*)&g_lastMsg, &msg, sizeof(msg));
    g_hasNew   = true;
    g_lastRxMs = millis();

    if (mac) {
      char macStr[18];
      snprintf(macStr, sizeof(macStr),
               "%02X:%02X:%02X:%02X:%02X:%02X",
               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
      Serial.print("[B] from "); Serial.println(macStr);
    }
  } else {
    Serial.print("[B] Unknown pkt len="); Serial.println(len);
  }
}

void onSendDone(const uint8_t *mac, esp_now_send_status_t status) {
  (void)mac; (void)status;
}
#endif

bool addPeer(const uint8_t *mac) {
  esp_now_peer_info_t peer{};
  memcpy(peer.peer_addr, mac, 6);
  peer.channel = ESPNOW_CHANNEL;
  peer.encrypt = false;
  return (esp_now_add_peer(&peer) == ESP_OK);
}

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

  // 모터 핀
  pinMode(DIR_PIN, OUTPUT);
  pinMode(EN_PIN,  OUTPUT);
  digitalWrite(DIR_PIN, LOW);
  digitalWrite(EN_PIN,  HIGH);  // 정지로 시작

  // ★ PWM(LEDC): v3 간단 API — 채널 자동 할당
  LEDC_CH = ledcAttach(VSP_PIN, LEDC_FREQ, LEDC_BITS); // 채널 번호 반환
  if (LEDC_CH < 0) {
    Serial.println("[B] ledcAttach failed");
    while (1) delay(1000);
  }
  pwmWrite(0);

  // ESP-NOW
  WiFi.mode(WIFI_STA);
  esp_wifi_set_channel(ESPNOW_CHANNEL, WIFI_SECOND_CHAN_NONE);

  if (esp_now_init() != ESP_OK) {
    Serial.println("[B] ESP-NOW init failed");
    while (1) delay(1000);
  }
  esp_now_register_recv_cb(onRecv);
  esp_now_register_send_cb(onSendDone);

  // (옵션) 회신용 Peer 등록
  if (!addPeer(PEER_MAC)) {
    Serial.println("[B] addPeer failed (reply not needed then)");
  }

  g_lastRxMs = millis();
  Serial.println("[B] Ready.");
}

void loop() {
  if (g_hasNew) {
    noInterrupts();
    ControlMsg m;
    memcpy(&m, (const void*)&g_lastMsg, sizeof(m)); // volatile → 일반복사
    g_hasNew = false;
    interrupts();

    Serial.print("[B] cmd=");   Serial.write(m.command);
    Serial.print(", seq=");     Serial.print(m.seq);
    Serial.print(", age(ms)="); Serial.println((int)(millis() - m.ms));

    applyCommand(m.command);
  }

  if (millis() - g_lastRxMs > FAILSAFE_MS) {
    motorStopHard();
  }

  delay(5);
}