[STM32] SPI

CS·2025년 3월 8일

STM32

목록 보기
8/8

SPI

Serial Peripheral Interface
송/수신 신호선을 모두 가지고 있는Full-Duplex 통신 방식으로,
Master가 제공하는 clock을 기준으로 Data를 구성하는 bit stream이 전송되는 동기식 통신 방식이다.

직렬 통신 방식중 하나로 광범위 하게 사용된다.
라즈베리파이에서도 CAN 통신을 하기위해 MCP2515같은 CAN Controller와 통신할 때 사용하는 방식이다.
UART, I2C에 비해 안정적이고 더 빠른 bit rate를 가질 수 있다.

4개의 신호선들로 구성된다.

1️⃣ MISO(Master In Slave Out) :

Master의 MISO pin은 Slave의 MISO pin과 연결되어야 함.
MSB먼저 8-bit Serial Data가 전송 됨.

2️⃣ MOSI(Master Out Slave In) :

Master의 MOSI pin은 Slave의 MOSI pin과 연결되어야 함.
MSB먼저 8-bit Serial Data가 전송 됨.

3️⃣ SCLK(Serial Clock) pin : Master 입장에서 출력

Master가 제공하는 SCLK는 관련 register에 의해 clock의 초기 [V] level을
High/Low중에서 선택할 수 있고, 위치도 조정 가능하다.
전송율(bit-rate)도 지정가능하다.

Slave와 정확히 통신하기 위해 clock 설정이 상호 동일해야 한다.

4️⃣ CS(Chip Select) pin : Master 입장에서 출력

SPI slave mode 입장에서, 전송은 "CS pin"이 "LOW level"이 될 때
"CS pin"이 "HIGH"가 될 때, 종료됨.
clock과 마찬가지로 Master가 CS를 발생시킴.

I2C의 Start, Stop bit를 생각하면 되겠다.

일반적으로, MCU(Master)와 SPI 통신을 하게 될 소자가 Slave가 된다.
MCU 내부 Pull-up 기능보다, 4개의 신호 선들을 I2C처럼 외부에서 Pull-up해주는 것이 좋다.

CubeMX에서 SPI3를 Master로 설정해주면, 이렇게 Parameter들을 입력할 수 있다.
이 Clock Parameter를 통신을 원하는 부품의 Datasheet를 보고, 맞춰야 한다.
pin들은 SPI Mode에 맞춰, 다르게 할당된다.

Basic Parameters의 경우, Data Size를 지정할 수 있다. 예를 들면

uint8_t TmpSpiTx = 0x20, *pTmpSpiTx;
pTmpSpiTx = &TmpSpiTx;

HAL_SPI_Transmit(&hspi3, (uint8_t*)pTmpSpiTx, 1, 100);


//

HAL_StatusTypeDef HAL_SPI_Transmit
(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

이렇게해서 Polling 방식으로 16bits Data 1개를 전송하려면
Basic Parameter의 "Data Size"를 16 Bits로 변경해주어야 한다.

위와 같이 작성하면, 8개의 SCLK pulse가 나가고, Data는 8 bits가 전송될 것 같지만,
16이라고 설정하였기에, 16개의 bits가 전송된다.

이를 잊고 Size parameter에 2를 설정하면 32bits가 전송되고,
지정하지 않은 부분은 0으로 zero padding되어 그 bits 수만큼 SCLK pulse를 생성한다는데 주의해야 한다.

당연히, Interrupt 방식은 뒤에 _IT()이 붙는 꼴이다.

Data를 받는 것은, Receive를 사용하면 된다.

HAL_SPI_TransmitReceive()

를 사용해주면, 한번에 SPI 송신을 하고, 자동으로 수신까지 진행한다.
Full-Duplex 방식이기에 가능한 것이다.

HAL_SPI_TransmitReceive(&hspi2, rx_reg, arr, 6, 10);

HAL_StatusTypeDef HAL_SPI_TransmitReceive
(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);

과 같이 작성하면 timeout이 10[ms]인데, 통신은 모두 이 timeout을 잘 설정해주어야
정상적으로 송수신이 가능하다. 이 timeout 이후 Data는 송수신이 되지않아 오동작이 원인이 된다.

크게 1000[ms]로 지정하더라도 지정한 Data크기만큼의 시간을 소모하므로
넉넉하게 크게 설정해주는 편이 좋겠다.

그리고, Slave로부터 읽어올 때, Slave의 주소를 Transmit()이나 TrasmitReceive()에 Slave에서 접근할 레지스터 주소를 입력해주어야한다.

예를 들어,

uint8_t reg = 0x05;  // 읽고 싶은 레지스터 주소
uint8_t temperature_data;  // 읽어온 온도 데이터를 저장할 변수

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // CS LOW (슬레이브 활성화)

// 1. 온도 센서의 0x05 레지스터를 읽겠다고 알림
HAL_SPI_Transmit(&hspi2, &reg, 1, 10);

// 2. 슬레이브가 해당 데이터를 준비했으므로, 클럭을 더 보내면서 데이터 수신
HAL_SPI_Receive(&hspi2, &temperature_data, 1, 10);

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // CS HIGH (슬레이브 비활성화)
  • HAL_SPI_Transmit() → 먼저 0x05를 보내서 "0x05 레지스터 값 달라고" 요청.
  • HAL_SPI_Receive() → 그 후 슬레이브가 준비한 값을 받아옴.

이렇게 사용하거나, 더미데이터가 필요한 경우에는

uint8_t tx_buffer[2] = {0x05, 0x00};  // 0x05: 레지스터 주소, 0x00: 더미 데이터
uint8_t rx_buffer[2];  // 응답을 저장할 버퍼

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); // CS LOW

HAL_SPI_TransmitReceive(&hspi2, tx_buffer, rx_buffer, 2, 10); 

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // CS HIGH

직접 더미데이터를 통해 클럭을 발생 시키기위해 0x00같은 값을 같이 보내야함.
물론, 더미데이터가 필요하지 않을 수도 있음.
Datasheet로 부품을 확인해야한다.

  • tx_buffer[0] = 0x05 → "0x05 레지스터 값" 요청
  • tx_buffer[1] = 0x00 → 클럭을 발생시키기 위해 더미 데이터 전송
  • rx_buffer[1] → 여기서 받은 값이 0x05 레지스터의 실제 데이터

profile
학습

0개의 댓글