이번에는 무선 시리얼통신 쪽에서 STA 에 해당하는 부분을 만들면 된다.  이 녀석은 전원이 켜지면 지정된 SoftAP에 연결된 이후 STA의 UART로 데이터를 입력받아 SoftAP로 전달하는 역할을 한다. 그래서 이전 SoftAP의 내용을 어느정도 감 잡은 독자의 경우, 큰 무리없이 내용을 확인 가능할 것으로 판단된다.

     

    --- 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) : https://makeutil.tistory.com/315

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

     

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

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

     

    1. 동작 설정

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

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

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

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

     

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

     

     

    2. WirelessSerialExClient (SoftAP STA)

       이제 SoftAP Station소스를 기반으로하여 기능을 만들어 보자.

    2.1. Station 소스 

      소스 내용은 SoftAP와 유사하지만 차이점은 UDP 서버거 아니라 소켓만 만들어서 서버에 접속한다는 점이다. 따라서, SoftAP에 접속 후 SoftAP에 생성된 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);
    }
    
    // UART에서 데이터를 읽어 UDP로 전송하는 태스크
    void uart_send_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);
    }

     

      나머지 내용은 기존 예제와 동일하다.  테스트는 간단하게 하나의 PC에 두개의 ESP를 연결하고, 각각의 UART도 연결해 준 다. 그리고 터미널 소프트웨어 2개를 열어서 각각 ESP32의 UART 포트를 열고 터미널에 데이터를 입력하면 반대편 터미널에서 입력된 문자가 출력되어야 한다. 

     

      그림으로 설명하면 다음과 같다. 필자는 랩톱(노트북)을 사용하고 있어서 여러개의 장치를 연결하기 위해서 USB허브를 사용한다. 아래의 두 ESP32는 각각 HostAP와 Station으로 동작한다. COM5는 HostAP를 다운로드하기 위해 사용되고, ESP32의 UART와 연결된다. 반대로 COM8은 두번째 ESP32로 Station으로 동작하기한 펌웨어를 다운로드에 사용되므로 ESP32의 UART에 연결된다.

     

     두 ESP32와 실제 PC의 시리얼 통신은 각 ESP32의 UART2에 해당하는 19, 20, GND핀을 사용하여  PC와 연결한다.

     

     

     

      따라서, 실행결과를 확인하기 위해서는 테라텀이나 PUTTY 같은 프로그램을 이용하여 COM3과 COM8을 각각 열어두고 통신속도를 115200, 데이터 8비트, 스탑비트 1비트, 패리티 없음으로 실행한다. 마지막으로 한쪽 터미널을 선택하고 키입력을하면 입력된 키가 반대편 터미널에서 출력된다. 

     

     

    아주 쉽게 이런 어려운 기능을 편리하게 사용할 수 있다니... 세상 참 많이 편해지고 있다. 

     

    다음은 이제 편의성을 조금 높혀보도록 하겠다. 전원켜면 PWM으로 LED 상태를 표시하고, 설정이 완료되면 LED를 특정 상태로 점멸시키고, 완전히 연결되면 완전 점등상태 유지 한다던지 이런 부분들을 먼저 진행하고 시간이 되면 TCP로 동작하도록 변경해보도록 하자.

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