우선 조금 부족하다고 생각되지만, 어쨋든 필요한 기능은 다 만들어뒀다. 그러면 이제 뭉쳐서 한번 만들어 보도록 하자. 그리고 시간이 조금더 된다면 LED로 상태를 표시할 수 있도록 해주면 더 좋겠다. 필자에게 충분한 시간이 주어지기를...

     

    이전 예제에서 SoftAP예제를 기반으로 필요한 내용만 추려 시리얼 통신에서 다루었던 내용을 포함시켜서 돌려보기만 하면된다. 다만, STA에서 Wifi를 통해 데이터를 받은후 출력해야되므로 WiFi쪽 처리 부분과 Uart 처리 부분에 대해서 주의하여 확인해보도록 하자. 

     

    --- contents ---

    01. [ESP32] VS Code 개발환경 구성 : https://makeutil.tistory.com/303

    02. [ESP32] 첫 프로젝트 생성하기 : https://makeutil.tistory.com/304

    03. [ESP32] 멀티 테스크 예제 (2 Task) : https://makeutil.tistory.com/305

    04. [ESP32] Task간 데이터 공유 (Queue, Mutex) : https://makeutil.tistory.com/306

    05. [ESP32] 개발보드 별 형상 및 I/O (ESP32/ESP32-S3) : https://makeutil.tistory.com/307

    06. [ESP32] UART 통신 예제 #1 : https://makeutil.tistory.com/309 

    07. [ESP32] UART 통신 예제 #2 : https://makeutil.tistory.com/313

    08. [ESP32] GPIO LED 켜기 : https://makeutil.tistory.com/311

    09. [ESP32] Timer와 PWM을 이용한 LED 점멸 : https://makeutil.tistory.com/312

    10. [ESP32] WiFi SoftAP와 WiFi Station (기본예제) : https://makeutil.tistory.com/314

    11. [ESP32] 무선 시리얼 통신장치 만들기 1 (SoftAP) : 현재글

     

    A1. [ESP32] 오류 - Fatal Error : No such file or directory : https://makeutil.tistory.com/308

    ------------------

     

    1. 동작설정 및 테스트 환경

    1.1. 동작설정

      1) 2대의 ESP32는 AP(Access Point)와 STA(Station)으로 동작된다.

      2) STA는 AP에 연결되어 ip를 할당받는다.

      3) AP쪽 UART로 데이터가 입력되면 STA의 UART로 데이터가 출력된다.

      4) STA의 UART에 데이터가 입력되면 AP의 UART에 데이터가 출력된다,

     

      결국 무선구간을 제외하면 장비와 장비간 무선으로 UART  통신을 할 수 있도록 되는 것이다.

     

    1.2. 테스트 환경

      시스템 테스트를 위해서 연결된 내용을 간략하게 그려보았다. 참고하기 바란다. COM3와 COM8은 USB2TTL 컨버터이다. USB를 연결하면 반대편에 헤더소켓으로 ESP32의 헤더핀에 연결할 수 있는 형태의 케이블이다. 보통 데이터선의 색상은 제조사마다 다를수 있지만, 전원은 빨간색, 그라운드는 검정색이다. 

     

      다운로드는 COM5와 COM7로 각각 기록하게되고, 실제 터미널에서는 COM3과 COM8을 이용하여 시리얼 통신을 진행하게 된다. 

     

     

    2. WirelessSerialExServer (SoftAP)

       이제 소프트 AP소스를 기반으로하여 기능을 만들어 보자.

    2.1. SoftAP 소스 

      소스 코드를 확인 해보도록 하자. 필자는 무선 통신구간은 UDP로 설정하여 샘플을 만들었다. 하지만 시리얼 포트의 특성을 보자면, TCP로 만드는게 기능에 걸 맞다. 이부분은 이후 업데이트 하도록 하고...

     

      이번에 추가된 부분은 UDP 통신 부분이다.  소스내용에서  udp_server_init을 통해서 udp 통신을 진행한다. 내용을 보면 우리가 일반적으로 C프로그래밍으로 네트워크 코드를 작성할 때와 거의 동일하다.

    #include <string.h>
    #include "esp_log.h"
    #include "esp_wifi.h"
    #include "esp_system.h"
    #include "esp_event.h"
    #include "esp_netif.h"
    #include "nvs_flash.h"
    #include "driver/uart.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "lwip/sockets.h"
    #include "lwip/inet.h"
    
    #define WIFI_SSID      "ESP32_AP"  // SoftAP SSID
    #define WIFI_PASS      "12345678"  // SoftAP Password
    #define MAX_STA_CONN   4  // 최대 동시 접속 클라이언트 수
    #define UART_BUF_SIZE  2048  // UART 버퍼 크기
    #define UART_PORT_NUM  UART_NUM_2  // 사용할 UART 포트 (UART2)
    #define UDP_PORT       5000 // UDP 포트 번호
    
    static const char *TAG = "softap_server";
    
    // UART 핀 정의
    #define UART_TX_PIN (19)
    #define UART_RX_PIN (20)
    
    static int udp_socket;
    struct sockaddr_in udp_client_addr;
    socklen_t client_addr_len = sizeof(udp_client_addr);
    
    // SoftAP 초기화
    void wifi_init_softap(void) {
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_ap();
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
        wifi_config_t ap_config = {
            .ap = {
                .ssid = WIFI_SSID,
                .ssid_len = strlen(WIFI_SSID),
                .password = WIFI_PASS,
                .max_connection = MAX_STA_CONN,
                .authmode = WIFI_AUTH_WPA_WPA2_PSK
            },
        };
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
        ESP_ERROR_CHECK(esp_wifi_start());
    
        ESP_LOGI(TAG, "SoftAP initialized. SSID: %s", WIFI_SSID);
    }
    
    // UDP 소켓 초기화
    void udp_server_init() {
        udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (udp_socket < 0) {
            ESP_LOGE(TAG, "Failed to create socket");
            return;
        }
    
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(UDP_PORT);
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
        if (bind(udp_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
            ESP_LOGE(TAG, "Socket bind failed");
            return;
        }
        ESP_LOGI(TAG, "UDP Server listening on port %d", UDP_PORT);
    }
    
    // UART2에서 데이터를 읽어 UDP로 전송하는 태스크
    void uart_send_task(void *pvParameter) {
        uint8_t data[128];
        int len;
        while (1) 
        {
            // UART에서 데이터를 받는다
            len = uart_read_bytes(UART_PORT_NUM, data, sizeof(data) - 1, pdMS_TO_TICKS(20));
            if (len > 0) {
                data[len] = '\0';
                ESP_LOGI(TAG, "Received from UART2: %s", data);
                // 입력 데이터 전송
                sendto(udp_socket, data, len, 0, (struct sockaddr *)&udp_client_addr, client_addr_len);
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
    }
    
    // UDP에서 데이터를 수신하여 UART2로 출력하는 태스크
    void udp_receive_task(void *pvParameter) {
        uint8_t rx_buffer[128];
        int len;
        while (1) {
            len = recvfrom(udp_socket, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&udp_client_addr, &client_addr_len);
            if (len > 0) {
                rx_buffer[len] = '\0';
                ESP_LOGI(TAG, "Received from UDP: %s", rx_buffer);
                uart_write_bytes(UART_PORT_NUM, (const char *)rx_buffer, len);
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
    }
    
    void app_main(void) 
    {
        ESP_ERROR_CHECK(nvs_flash_init());  // 플래시 초기화
        wifi_init_softap();                 // wifi 초기화
    
        udp_server_init();                  // udp 서버 초기화
    
        uart_config_t uart_config = {       // 시리얼 포트 설정
            .baud_rate = 115200,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
        };
    
        ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
        ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
        ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_BUF_SIZE, UART_BUF_SIZE, 0, NULL, 0));
        
        // 시리얼 포트 입력 후 STA로 전송
        xTaskCreate(uart_send_task, "uart_send_task", 4096, NULL, 10, NULL);
        // STA에서 UDP로 들어온 데이터를 시리얼 포트에 출력
        xTaskCreate(udp_receive_task, "udp_receive_task", 4096, NULL, 10, NULL);
    }

     

    다른 부분은 기존 예제에서 모두 확인 가능하다. task를 2개를 만들어서 각가 입력과 출력에 대해서 처리하도록 구성하였다. uart_send_task는 AP똑 UART의 입력을 STA의 UART 출력으로 보내고, udp_receive_task는 반대의 역할을 한다.

     

    지금까지 짧게짧게 만든 코드를 갖다 붙혀서 좀 지저분하긴 하지만 하나의 소스코드로 완성했다. 

     

     

    2.2. 실행결과

      이제 실행 결과를 확인해보도록 하자. 아... 시리얼 변환케이블이... 없다. 회사에서 쓰느라 가 갖다놔 버렸기에... 집에서 확인이 어렵게 되었다. 실행 결과는 이후에 업데이트 하도록 하겠다. 

     

     

    3. WirelessSerialExClient (SoftAP STA)

       다음은 STA 소스를 기반으로한 상대편쪽 소스를 확인해 보도록 하자. 

    3.1. 소스

      소스 내용은 SoftAP STA를 기반으로 하고 있다.  소스 내용은 단순하다. 앞의 소스1번에서 실행된 SoftAP의 정보를 통해서 전원인가시 연결된 이후, DHCP로 192.168.4.x를 할당받은 다음, UDP서버에 데이터를 쓰거나 가져오는 형식으로 만들어져 있다. 

    #include <string.h>
    #include "esp_log.h"
    #include "esp_wifi.h"
    #include "esp_system.h"
    #include "esp_event.h"
    #include "esp_netif.h"
    #include "nvs_flash.h"
    #include "driver/uart.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "lwip/sockets.h"
    #include "lwip/inet.h"
    
    #define WIFI_SSID      "ESP32_AP"  // AP의 SSID  
    #define WIFI_PASS      "12345678"  // AP의 비밀번호  
    #define UART_BUF_SIZE  2048  // UART 버퍼 크기
    #define UART_PORT_NUM  UART_NUM_2  // 사용할 UART 포트 (UART2)
    #define UDP_IP_ADDR    "192.168.4.1" // UDP 데이터 전송 대상 IP
    #define UDP_PORT       5000 // UDP 포트 번호
    
    static const char *TAG = "wifi_sta";
    
    // UART 핀 정의
    #define UART_TX_PIN (19)
    #define UART_RX_PIN (20)
    
    static int udp_socket;
    struct sockaddr_in udp_server_addr;
    
    // 이벤트 처리 함수
    static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
        if (event_base == WIFI_EVENT) {
            if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
                esp_wifi_connect();  // 연결 재시도
            }
        } else if (event_base == IP_EVENT) {
            if (event_id == IP_EVENT_STA_GOT_IP) {
                ip_event_got_ip_t *event = (ip_event_got_ip_t*) event_data;
                ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
            }
        }
    }
    
    // Wi-Fi STA 초기화
    void wifi_init_sta(void) {
        ESP_ERROR_CHECK(esp_netif_init());
        ESP_ERROR_CHECK(esp_event_loop_create_default());
        esp_netif_create_default_wifi_sta();
    
        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    
        wifi_config_t sta_config = {
            .sta = {
                .ssid = WIFI_SSID,
                .password = WIFI_PASS
            },
        };
    
        ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));
        ESP_ERROR_CHECK(esp_wifi_start());
        esp_wifi_connect();
    }
    
    // UDP 소켓 초기화
    void sta_udp_init() {
        udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
        if (udp_socket < 0) {
            ESP_LOGE(TAG, "Failed to create socket");
            return;
        }
    
        memset(&udp_server_addr, 0, sizeof(udp_server_addr));
        udp_server_addr.sin_family = AF_INET;
        udp_server_addr.sin_port = htons(UDP_PORT);
        udp_server_addr.sin_addr.s_addr = inet_addr(UDP_IP_ADDR);
    }
    
    // UART2에서 데이터를 읽어 UDP로 전송하는 태스크
    void uart2_task(void *pvParameter) 
    {
        uint8_t data[128];
        int len;
        while (1) {
            len = uart_read_bytes(UART_PORT_NUM, data, sizeof(data) - 1, pdMS_TO_TICKS(20));
            if (len > 0) {
                data[len] = '\0';
                ESP_LOGI(TAG, "Received from UART2: %s", data);
                sendto(udp_socket, data, len, 0, (struct sockaddr *)&udp_server_addr, sizeof(udp_server_addr));
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
    }
    
    // UDP에서 데이터를 수신하여 UART2로 출력하는 태스크
    void udp_receive_task(void *pvParameter) {
        uint8_t rx_buffer[128];
        struct sockaddr_in source_addr;
        socklen_t socklen = sizeof(source_addr);
        int len;
    
        while (1) {
            len = recvfrom(udp_socket, rx_buffer, sizeof(rx_buffer) - 1, 0, (struct sockaddr *)&source_addr, &socklen);
            if (len > 0) {
                rx_buffer[len] = '\0';
                ESP_LOGI(TAG, "Received from UDP: %s", rx_buffer);
                uart_write_bytes(UART_PORT_NUM, (const char *)rx_buffer, len);
            }
            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
    }
    
    void app_main(void) 
    {
        ESP_ERROR_CHECK(nvs_flash_init());
        wifi_init_sta();
        ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
        ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    
        sta_udp_init();
    
        uart_config_t uart_config = {
            .baud_rate = 115200,
            .data_bits = UART_DATA_8_BITS,
            .parity = UART_PARITY_DISABLE,
            .stop_bits = UART_STOP_BITS_1,
            .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
        };
    
        ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));
        ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
        ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_BUF_SIZE, UART_BUF_SIZE, 0, NULL, 0));
        
        xTaskCreate(uart_send_task, "uart_send_task", 4096, NULL, 10, NULL);
        xTaskCreate(udp_receive_task, "udp_receive_task", 4096, NULL, 10, NULL);
    }

     

     

    2. 실행결과

      마찬가지로 케이블이 없어서 바로 테스트는 어려울것 같고, 테스트 결과는 이후에 업데이트 하도록 하겠다.

     

     

    3. 통합테스트

      통합테스트를 위해서 다음의 절차대로 테스트를 진행한다. 위의 소스들에서 ESP32는 UART2로 데이터를 입력 받거나 출력 받도록 되어있으므로, UART to TTL 케이블을 이용하여 19번과 20번, 그리고 GND를 연결한다. USB2TTL 케이블의 전원은 호스트가 제공해주므로 전원핀은 열결하지 않아야 한다. 오히려 연결하면 양측전원입력차로 인해 ESP32또는 노트북의 USB포트등이  데미지 입을 수 있다. 그러니 절대로 연결하지 말아야 한다. 

     

     

     

     

     

     

    반응형
    • 네이버 블러그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기