본문 바로가기
MCU/STM32

[STM32G474] I2C, SPI 사용법: HAL API로 설정 및 코드 예제

by linuxgo 2025. 8. 19.
반응형

1. STM32G474 I2C 및 SPI 개요

STM32G474는 STMicroelectronics의 STM32G4 시리즈에 속하는 고성능 32비트 ARM Cortex-M4 마이크로컨트롤러로, 최대 170MHz로 동작하며 I2C와 SPI 통신 인터페이스를 지원합니다. I2C(Inter-Integrated Circuit)와 SPI(Serial Peripheral Interface)는 외부 장치(센서, 디스플레이 등)와 통신하기 위한 직렬 통신 프로토콜입니다. 이 문서에서는 STM32G474의 I2C와 SPI를 HAL API를 사용하여 설정하고 사용하는 방법을 설명합니다. 모든 예제 코드는 STM32CubeMX로 생성되며, STM32CubeIDE에서 실행 가능합니다. 코드에는 상세한 주석이 포함되어 있습니다.

I2C와 SPI의 주요 특징

I2C

  • 양방향 2선 통신 (SDA: 데이터, SCL: 클럭).
  • 다중 마스터 및 슬레이브 지원.
  • 속도: 표준 모드(100 kHz), 고속 모드(400 kHz), 초고속 모드(1 MHz).
  • STM32G474는 최대 4개의 I2C 인터페이스(I2C1~I2C4) 제공.
  • 주소: 7비트 또는 10비트 주소 지원.

SPI

  • 전이중 동기 통신 (MOSI, MISO, SCK, NSS).
  • 마스터/슬레이브 모드 지원.
  • 속도: 최대 85 MHz (STM32G474의 클럭 설정에 따라 달라짐).
  • STM32G474는 최대 4개의 SPI 인터페이스(SPI1~SPI4) 제공.
  • 데이터 프레임: 4~16비트.

HAL API는 복잡한 레지스터 설정을 추상화하여 I2C와 SPI 설정을 간소화합니다.

2. 주요 I2C 및 SPI HAL API 함수

2.1. I2C HAL API 함수

  • *HAL_I2C_Init(I2C_HandleTypeDef hi2c)
    •   설명: I2C 인터페이스 초기화.
    •   매개변수: hi2c (I2C 핸들러 구조체).
  • **HAL_I2C_Master_Transmit(I2C_HandleTypeDef hi2c, uint16_t DevAddress, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 마스터 모드로 데이터 전송.
    •   매개변수: DevAddress (슬레이브 주소), pData (전송 데이터), Size (데이터 크기), Timeout (타임아웃).
  • **HAL_I2C_Master_Receive(I2C_HandleTypeDef hi2c, uint16_t DevAddress, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 마스터 모드로 데이터 수신.
  • **HAL_I2C_Mem_Write(I2C_HandleTypeDef hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 슬레이브 메모리에 데이터 쓰기.
  • **HAL_I2C_Mem_Read(I2C_HandleTypeDef hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 슬레이브 메모리에서 데이터 읽기.

2.2. SPI HAL API 함수

  • *HAL_SPI_Init(SPI_HandleTypeDef hspi)
    •   설명: SPI 인터페이스 초기화.
    •   매개변수: hspi (SPI 핸들러 구조체).
  • **HAL_SPI_Transmit(SPI_HandleTypeDef hspi, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 마스터 모드로 데이터 전송.
  • **HAL_SPI_Receive(SPI_HandleTypeDef hspi, uint8_t pData, uint16_t Size, uint32_t Timeout)
    •   설명: 마스터 모드로 데이터 수신.
  • **HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t pTxData, uint8_t pRxData, uint16_t Size, uint32_t Timeout)
    •   설명: 전이중 모드로 데이터 송수신.

3. I2C 및 SPI 설정 절차

3.1. STM32CubeMX 설정 절차

  1. 프로젝트 생성:
    •   STM32CubeMX에서 "New Project"를 선택하고 STM32G474 선택 (예: LQFP64 패키지).
    •   프로젝트 이름과 저장 경로 설정.
  2. 시스템 클럭 설정 (최대 170 MHz, HSE 사용):
    •   "System Core" > "RCC"에서 HSE를 "Crystal/Ceramic Resonator"로 설정 (8 MHz 가정).
    •   Clock Configuration 탭:
      •   HSE: 8 MHz.
      •   PLL 설정: PLLM = 1, PLLN = 85, PLLR = 2, PLLR Enable.
      •   SYSCLK = (8 MHz / 1) * 85 / 2 = 170 MHz.
      •   HCLK, PCLK1, PCLK2를 170 MHz로 설정 (Divider = 1).
      •   Flash Latency: 4 WS.
  3. I2C 설정 (예제 1):
    •   "Connectivity" > "I2C1" 활성화.
    •   Parameter Settings:
      •   I2C Speed Mode: Fast Mode (400 kHz).
      •   Duty Cycle: 2.
      •   Addressing Mode: 7-bit.
    • GPIO 설정:
      •   I2C1_SCL (PB6), I2C1_SDA (PB7)를 "Alternate Function Open Drain"으로 설정.
      •   Pull-up 활성화.
  4. SPI 설정 (예제 2):
    •   "Connectivity" > "SPI1" 활성화.
    •   Parameter Settings:
      •   Mode: Full-Duplex Master.
      •   Data Size: 8-bit.
      •   Prescaler: 8 (SPI 클럭 = 170 MHz / 8 = 21.25 MHz).
      •   First Bit: MSB First.
      •   CPOL: Low, CPHA: 2 Edge (LIS3DH 요구사항).
    •   GPIO 설정:
      •   SPI1_SCK (PA5), SPI1_MOSI (PA7), SPI1_MISO (PA6) 설정.
      •   NSS (PA4)를 "GPIO_Output"으로 설정 (소프트웨어 제어).
  5. 디버깅 인터페이스 설정:
    •   "System Core" > "SYS"에서 Debug를 "Serial Wire" (SWD)로 설정 (PA13: SWDIO, PA14: SWCLK).
  6. 프로젝트 생성:
    •   Project Manager에서 Toolchain/IDE를 STM32CubeIDE로 설정.
    •   "Generate Code" 클릭.

3.2. I2C 및 SPI 동작 원리

  1. 시스템 초기화:
    •   클럭 활성화 (__HAL_RCC_I2Cx_CLK_ENABLE(), __HAL_RCC_SPIx_CLK_ENABLE()).
    •   HAL_Init()으로 시스템 타이머 초기화.
  2. I2C 동작:
    •   I2C 핸들러 구조체 설정 (I2C_InitTypeDef).
    •   마스터 모드에서 슬레이브 주소로 데이터 송수신.
  3. SPI 동작:
    •   SPI 핸들러 구조체 설정 (SPI_InitTypeDef).
    •   마스터 모드에서 NSS 핀 제어 후 데이터 송수신.
  4. 인터럽트 (옵션):
    •   I2C 및 SPI 인터럽트 활성화 (HAL_I2C_EV_IRQHandler, HAL_SPI_IRQHandler).
    •   NVIC 설정으로 우선순위 지정.

4. I2C 및 SPI 예제 코드

4.1. 예제 1: I2C를 사용한 24C02 EEPROM 읽기/쓰기

I2C1 (PB6: SCL, PB7: SDA)을 사용하여 24C02 EEPROM에 데이터를 쓰고 읽습니다. 24C02는 2Kbit 메모리로, 8비트 주소와 7비트 슬레이브 주소(0x50)를 사용합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for I2C1 to read/write 24C02 EEPROM.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
I2C_HandleTypeDef hi2c1; // I2C1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define EEPROM_ADDR 0xA0 // 24C02의 7비트 슬레이브 주소 (0x50 << 1)
#define I2C_TIMEOUT 1000 // I2C 타임아웃 (ms)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t i2cStatus = 0; // I2C 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
uint8_t writeData[2] = {0x10, 0xAA}; // EEPROM 주소 0x10에 0xAA 쓰기
uint8_t readData[1]; // EEPROM에서 읽은 데이터
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_I2C1_Init(void); // I2C1 초기화 함수 선언

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/
  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_I2C1_Init(); // I2C1 초기화 (PB6: SCL, PB7: SDA)
  /* USER CODE BEGIN 2 */
  if (i2cStatus != 1) // I2C 초기화 성공 여부 확인
  {
    i2cStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
    while (1);
  }

  // 24C02 EEPROM에 데이터 쓰기 (주소 0x10에 0xAA 쓰기)
  if (HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, writeData[0], I2C_MEMADD_SIZE_8BIT, &writeData[1], 1, I2C_TIMEOUT) != HAL_OK)
  {
    i2cStatus = 0xFFFF;
    while (1);
  }
  HAL_Delay(5); // 24C02 쓰기 사이클 대기 (최대 5ms)

  // 24C02 EEPROM에서 데이터 읽기 (주소 0x10에서 1바이트 읽기)
  if (HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, writeData[0], I2C_MEMADD_SIZE_8BIT, readData, 1, I2C_TIMEOUT) != HAL_OK)
  {
    i2cStatus = 0xFFFF;
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (readData[0] == writeData[1]) // 읽은 데이터가 쓴 데이터(0xAA)와 일치하는지 확인
    {
      i2cStatus = 1; // 동작 성공
    }
    else
    {
      i2cStatus = 0xFFFF; // 오류 상태
    }
    HAL_Delay(100); // 100ms 대기
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief I2C1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C1_Init(void)
{
  hi2c1.Instance = I2C1;
  hi2c1.Init.Timing = 0x10909CEC; // 400 kHz 타이밍 (STM32CubeMX에서 생성)
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  i2cStatus = 1; // I2C 초기화 성공 표시
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  i2cStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: I2C1을 통해 24C02 EEPROM에 주소 0x10에 데이터 0xAA를 쓰고 읽기.
  • 설정: PB6 (SCL), PB7 (SDA), 400 kHz, 7비트 주소 (0x50).
  • 출력: 읽은 데이터가 0xAA와 일치하면 i2cStatus=1, 아니면 0xFFFF.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.
  • 특이사항: 24C02는 쓰기 후 5ms 대기 필요. 외부 풀업 저항(4.7kΩ) 필수.

4.2. 예제 2: SPI를 사용한 LIS3DH 가속도 센서 데이터 읽기

SPI1 (PA5: SCK, PA6: MISO, PA7: MOSI, PA4: NSS)을 사용하여 LIS3DH 가속도 센서에서 X축 데이터를 읽습니다. LIS3DH는 3축 가속도 센서로, SPI 모드에서 8비트 데이터, CPOL=Low, CPHA=2 Edge를 사용합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for SPI1 to read X-axis data from LIS3DH accelerometer.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
SPI_HandleTypeDef hspi1; // SPI1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define NSS_Pin GPIO_PIN_4 // PA4를 NSS로 사용
#define NSS_GPIO_Port GPIOA
#define SPI_TIMEOUT 1000 // SPI 타임아웃 (ms)
#define LIS3DH_WHO_AM_I 0x0F // WHO_AM_I 레지스터 주소
#define LIS3DH_CTRL_REG1 0x20 // 제어 레지스터 1
#define LIS3DH_OUT_X_L 0x28 // X축 저위 바이트
#define LIS3DH_OUT_X_H 0x29 // X축 고위 바이트
#define LIS3DH_READ 0x80 // 읽기 비트 (MSB=1)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t spiStatus = 0; // SPI 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
uint8_t whoAmI; // WHO_AM_I 레지스터 값
int16_t xAccel; // X축 가속도 값
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_SPI1_Init(void); // SPI1 초기화 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언

/* USER CODE BEGIN PFP */
void LIS3DH_Init(void); // LIS3DH 초기화 함수 선언
void LIS3DH_Read_X(void); // X축 데이터 읽기 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void LIS3DH_Init(void)
{
  uint8_t txData[2];
  uint8_t rxData[2];

  // LIS3DH CTRL_REG1 설정: 100 Hz, 모든 축 활성화
  txData[0] = LIS3DH_CTRL_REG1; // 레지스터 주소
  txData[1] = 0x57; // 100 Hz (0101), X/Y/Z 축 활성화 (0111)
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);
  HAL_SPI_Transmit(&hspi1, txData, 2, SPI_TIMEOUT);
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);

  // WHO_AM_I 레지스터 읽기 (0x33 예상)
  txData[0] = LIS3DH_WHO_AM_I | LIS3DH_READ; // 읽기 모드
  txData[1] = 0x00; // 더미 바이트
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, SPI_TIMEOUT);
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
  whoAmI = rxData[1]; // WHO_AM_I 값 저장 (0x33)
}

void LIS3DH_Read_X(void)
{
  uint8_t txData[2];
  uint8_t rxData[2];

  // X축 저위 바이트 읽기
  txData[0] = LIS3DH_OUT_X_L | LIS3DH_READ;
  txData[1] = 0x00;
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, SPI_TIMEOUT);
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
  xAccel = rxData[1];

  // X축 고위 바이트 읽기
  txData[0] = LIS3DH_OUT_X_H | LIS3DH_READ;
  txData[1] = 0x00;
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_RESET);
  HAL_SPI_TransmitReceive(&hspi1, txData, rxData, 2, SPI_TIMEOUT);
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);
  xAccel |= (rxData[1] << 8); // 16비트 X축 가속도 값 조합
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/
  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init(); // NSS 핀 초기화
  MX_SPI1_Init(); // SPI1 초기화
  /* USER CODE BEGIN 2 */
  if (spiStatus != 1) // SPI 초기화 성공 여부 확인
  {
    spiStatus = 0xFFFF;
    while (1);
  }

  LIS3DH_Init(); // LIS3DH 초기화
  if (whoAmI != 0x33) // WHO_AM_I 값 확인
  {
    spiStatus = 0xFFFF;
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    LIS3DH_Read_X(); // X축 가속도 읽기
    if (xAccel != 0) // 가속도 데이터 유효성 확인
    {
      spiStatus = 1; // 동작 성공
    }
    else
    {
      spiStatus = 0xFFFF; // 오류 상태
    }
    HAL_Delay(100); // 100ms 대기
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; // LIS3DH 요구사항
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 7;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  spiStatus = 1; // SPI 초기화 성공 표시
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(NSS_GPIO_Port, NSS_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : PA4 */
  GPIO_InitStruct.Pin = NSS_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(NSS_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  spiStatus = 0xFFFF;
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  while (1)
  {
  }
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: SPI1을 통해 LIS3DH 가속도 센서에서 X축 데이터를 읽음.
  • 설정: PA5 (SCK), PA6 (MISO), PA7 (MOSI), PA4 (NSS, 소프트웨어 제어), 21.25 MHz, CPOL=Low, CPHA=2 Edge.
  • 출력: WHO_AM_I (0x33) 확인 후 X축 가속도 값을 xAccel에 저장.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.
  • 특이사항: LIS3DH는 100 Hz, 8비트 데이터, 읽기 시 MSB=1 설정 필요.

5. 추가 고려 사항

  • 클럭 설정: HSE 주파수에 따라 PLL 설정 조정 필요.
  • I2C 풀업 저항: SDA, SCL 라인에 외부 풀업 저항(4.7kΩ 권장) 필요.
  • SPI NSS 관리: 소프트웨어 NSS 사용 시 GPIO 제어 주의.
  • 디버깅: SWD 인터페이스로 실시간 데이터 모니터링.
  • 저전력: I2C/SPI 비활성화 및 클럭 게이팅으로 전력 최적화.

키워드: STM32G474, I2C, SPI, HAL API, STM32CubeIDE, STM32CubeMX, 24C02 EEPROM, LIS3DH, 데이터 송수신, HSE 클럭, 디버깅

반응형