ESP32 IDF

ESP32 IDF SPI

임베디드 친구 2024. 11. 12. 11:57
반응형

안녕하세요! '소프트웨어 공장' 블로그에 오신 것을 환영합니다. 오늘은 ESP32 개발에 있어서 중요한 통신 프로토콜 중 하나인 SPI (Serial Peripheral Interface)에 대해 이야기해 보겠습니다. 이 포스팅에서는 SPI의 개념부터 ESP32에서 이를 사용하여 장치를 제어하는 방법까지 단계별로 자세히 설명합니다. 또한, 실제로 SPI를 사용해 장치를 연결하고 데이터를 전송하는 예제를 VS Code 환경에서 구현하는 방식으로 소개할 예정이니, 끝까지 집중해주세요!

SPI란 무엇인가?

SPI (Serial Peripheral Interface)는 마스터-슬레이브 구조의 동기식 직렬 통신 프로토콜입니다. 이 프로토콜은 마이크로컨트롤러와 다양한 외부 장치 간의 고속 통신을 가능하게 합니다. 일반적으로 4개의 핀을 사용하며 다음과 같은 신호 라인이 포함됩니다:

  1. MOSI (Master Out Slave In): 마스터에서 슬레이브로 데이터 전송.
  2. MISO (Master In Slave Out): 슬레이브에서 마스터로 데이터 전송.
  3. SCLK (Serial Clock): 마스터가 생성하는 클럭 신호.
  4. SS/CS (Slave Select/Chip Select): 마스터가 특정 슬레이브를 선택할 때 사용.

ESP32는 다수의 SPI 버스를 지원하며, 기본적으로 HSPIVSPI 두 개의 하드웨어 SPI 버스를 제공합니다. 이를 통해 센서, 디스플레이, 메모리 등 다양한 주변 장치와 통신할 수 있습니다.

ESP32에서 SPI 설정하기

ESP-IDF에서 SPI 설정

ESP-IDF는 ESP32 개발을 위한 공식 개발 프레임워크로, SPI 통신을 매우 쉽게 설정할 수 있는 라이브러리를 제공합니다. 아래의 단계에서는 VS Code 개발 환경을 사용하여 SPI 통신을 설정하는 방법을 설명합니다.

VS Code 개발 환경 설정

먼저, VS Code에서 ESP32 프로젝트를 설정하고, 필요한 SPI 관련 헤더 파일을 포함시켜야 합니다. 이 예제에서는 ESP32에 연결된 SPI 장치와 데이터를 주고받는 간단한 예제를 다뤄 보겠습니다.

1. 프로젝트 생성

VS Code에서 ESP-IDF 플러그인을 사용하여 새 프로젝트를 생성합니다. 기본 템플릿을 선택하고 main.c 파일을 열어줍니다.

2. SPI 관련 헤더 파일 포함

먼저, 필요한 헤더 파일을 추가합니다.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "esp_system.h"
#include "esp_log.h"

3. 핀 설정 및 SPI 초기화

ESP32에서 SPI 장치를 초기화하려면 SPI 핀 구성과 장치 핸들러를 설정해야 합니다. 아래 코드는 SPI 버스를 초기화하고 장치를 연결하는 방법을 보여줍니다.

#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK  18
#define PIN_NUM_CS   5

static const char *TAG = "SPI_EXAMPLE";

void app_main(void)
{
    esp_err_t ret;
    spi_device_handle_t spi;
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1
    };
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1 * 1000 * 1000, // 1 MHz
        .mode = 0,                          // SPI 모드 0
        .spics_io_num = PIN_NUM_CS,         // CS 핀 번호
        .queue_size = 7,                    // 트랜잭션 큐 크기
    };

    // SPI 버스 초기화
    ret = spi_bus_initialize(HSPI_HOST, &buscfg, 1);
    ESP_ERROR_CHECK(ret);

    // 장치 추가
    ret = spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "SPI 장치 초기화 완료");
}

위 코드는 다음과 같은 작업을 수행합니다:

  1. 핀 번호 정의: MISO, MOSI, SCLK, CS 핀을 정의합니다.
  2. SPI 버스 설정: spi_bus_config_t 구조체를 사용하여 SPI 버스를 설정합니다.
  3. 장치 인터페이스 설정: spi_device_interface_config_t 구조체를 사용하여 SPI 장치의 속성 (클럭 속도, 모드, CS 핀 등)을 설정합니다.
  4. 버스 초기화 및 장치 추가: spi_bus_initialize()spi_bus_add_device() 함수를 사용해 SPI 버스와 장치를 초기화합니다.

SPI를 통해 데이터 전송하기

SPI 버스가 초기화된 후, 이제 데이터를 주고받는 방법을 살펴보겠습니다. 다음 예제는 마스터가 슬레이브에게 데이터를 전송하고 응답을 받는 과정입니다.

void spi_transaction_example(spi_device_handle_t spi)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));           // 트랜잭션 구조체 초기화
    uint8_t tx_data[4] = {0xAA, 0xBB, 0xCC, 0xDD};
    uint8_t rx_data[4] = {0};

    t.length = 8 * sizeof(tx_data);     // 전송할 데이터 길이 (비트 단위)
    t.tx_buffer = tx_data;              // 전송할 데이터
    t.rx_buffer = rx_data;              // 수신할 데이터

    // SPI 트랜잭션 실행
    ret = spi_device_transmit(spi, &t);
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "전송 데이터: %02X %02X %02X %02X", tx_data[0], tx_data[1], tx_data[2], tx_data[3]);
    ESP_LOGI(TAG, "수신 데이터: %02X %02X %02X %02X", rx_data[0], rx_data[1], rx_data[2], rx_data[3]);
}

위 코드는 다음과 같은 작업을 수행합니다:

  1. 트랜잭션 구조체 초기화: spi_transaction_t 구조체를 사용하여 전송할 데이터와 수신할 데이터를 설정합니다.
  2. 데이터 전송: spi_device_transmit() 함수를 호출하여 트랜잭션을 실행합니다. 이 함수는 블로킹 방식으로 동작하며, 트랜잭션이 완료될 때까지 대기합니다.
  3. 로그 출력: 전송한 데이터와 수신한 데이터를 로그로 출력하여 확인할 수 있습니다.

전체 코드

위에서 설명한 SPI 초기화 및 데이터 전송 코드를 종합하면, 전체 코드는 다음과 같습니다.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "esp_system.h"
#include "esp_log.h"

#define PIN_NUM_MISO 19
#define PIN_NUM_MOSI 23
#define PIN_NUM_CLK  18
#define PIN_NUM_CS   5

static const char *TAG = "SPI_EXAMPLE";

void spi_transaction_example(spi_device_handle_t spi);

void app_main(void)
{
    esp_err_t ret;
    spi_device_handle_t spi;
    spi_bus_config_t buscfg = {
        .miso_io_num = PIN_NUM_MISO,
        .mosi_io_num = PIN_NUM_MOSI,
        .sclk_io_num = PIN_NUM_CLK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1
    };
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 1 * 1000 * 1000, // 1 MHz
        .mode = 0,                          // SPI 모드 0
        .spics_io_num = PIN_NUM_CS,         // CS 핀 번호
        .queue_size = 7,                    // 트랜잭션 큐 크기
    };

    // SPI 버스 초기화
    ret = spi_bus_initialize(HSPI_HOST, &buscfg, 1);
    ESP_ERROR_CHECK(ret);

    // 장치 추가
    ret = spi_bus_add_device(HSPI_HOST, &devcfg, &spi);
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "SPI 장치 초기화 완료");

    // SPI 트랜잭션 예제 실행
    spi_transaction_example(spi);
}

void spi_transaction_example(spi_device_handle_t spi)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));           // 트랜잭션 구조체 초기화
    uint8_t tx_data[4] = {0xAA, 0xBB, 0xCC, 0xDD};
    uint8_t rx_data[4] = {0};

    t.length = 8 * sizeof(tx_data);     // 전송할 데이터 길이 (비트 단위)
    t.tx_buffer = tx_data;              // 전송할 데이터
    t.rx_buffer = rx_data;              // 수신할 데이터

    // SPI 트랜잭션 실행
    ret = spi_device_transmit(spi, &t);
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "전송 데이터: %02X %02X %02X %02X", tx_data[0], tx_data[1], tx_data[2], tx_data[3]);
    ESP_LOGI(TAG, "수신 데이터: %02X %02X %02X %02X", rx_data[0], rx_data[1], rx_data[2], rx_data[3]);
}

마치며

이번 포스팅에서는 SPI 프로토콜의 기본 개념부터 ESP32에서 이를 사용하는 방법까지 자세히 설명드렸습니다. 특히, VS Code 개발 환경에서 SPI 장치를 설정하고 데이터를 주고받는 과정을 단계별로 구현해 보았습니다. SPI는 다양한 외부 장치와의 고속 통신을 위해 널리 사용되며, 이를 통해 센서, 디스플레이, 메모리 등 여러 장치와 간편하게 연결할 수 있습니다.

반응형

'ESP32 IDF' 카테고리의 다른 글

ESP32 IDF Sleep  (0) 2024.11.14
ESP32 IDF UART  (0) 2024.11.13
ESP32 IDF I2C  (0) 2024.11.11
ESP32 IDF DAC  (0) 2024.11.10
ESP32 IDF ADC  (0) 2024.11.09