: Motorola에 의해 개발된 full-duplex 통신 이 가능한 동기 통신 규격
full-duplex 통신 : 전이중 통신. 데이터의 송신과 수신이 동시에 가능한 통신

1) SCLK
Serial Clock(직렬 클럭)으로 마스터에서 출력하여 슬레이브로 입력해준다. SPI 통신의 주기를 SCLK로 설정할 수 있다. 동기화 신호이며 통신 Clock이라 생각하면 된다.
2) MOSI
Master Output Slave Input, 말 그대로 마스터 출력, 슬레이브 입력이다.
실제 마스터에서 슬레이브로 데이터를 먹일 때 MOSI를 통해 데이터를 준다.
3) MISO
Master Input Slave Output, 슬레이브 출력, 마스터 입력이다.
보통 MOSI를 통해 슬레이브에 명령 DATA가 들어오면 MISO를 통해 슬레이브에서 마스터로 응답 DATA가 출력된다.
4) CS
Chip Select, 보통 SS 핀 즉 Slave Select로 슬레이브를 선택할 때 사용된다.
<인터페이스란?>
전기 신호의 변환으로 중앙 처리 장치와 그 주변 장치를 서로 잇는 부분. 또는, 그 접속 장치.
키보드나 디스플레이 등처럼 사람과 컴퓨터를 연결하는 장치.

즉 Slave 칩의 갯수 만큼 CS선이 필요함. 평소엔 HIGH, 통신하는 슬레이브 CS에만 LOW 를 줘서 통신.
타이밍도를 보면 CS가 HIGH일 때는 동작을 하지 않도록 동기 클락도 생성이 되지 않고 CS가 LOW가 되면 동기 클락이 생성되며 데이터를 주고받는다.
마스터에서 CS에 LOW를 주어 제어 Enable을 시킨 뒤, clock을 생성하여 동기를 맞춰주고 DATA를 주고받는 통신방식이다.
SPI 통신방식을 사용하는 CHIP은 모두 이러한 방식으로 사용한다. 반드시 위의 나와있는 4핀이 필요하며 마스터에서는 출력 핀으로 CS, SCLK, MOSI 핀 입력으로는 MISO핀이 할당되어있다.
설정에 따라 4가지 모드를 가질 수 있다. 칩의 데이터시트를 보고 확인해야 한다.

1. 클럭의 극성 (Clock Polarity : CPOL)
IDLE 상태일 때(통신하지 않고 있는 평소 상태) SCLK라인이 HIGH 인지 LOW인지를 선택
⦁ CPOL(Clock Polarity)
∘ CPOL - 0 : Idle 상태에서 clock의 극성이 LOW
∘ CPOL - 1 : Idle 상태에서 clock의 극성이 HIGH
2. 클럭의 위상 ( Clock Phase : CPHA)
첫번째 엣지 1Edge 일 때 데이터를 가져갈 지 두번째 엣지 2Edge일 때 데이터를 가져갈지 선택
⦁ CPHA(Clock Phase)
∘ CPHA - 0 : clock의 첫번째 edge에서 전송된 첫번째 data를 capture
∘ CPHA - 1 : clock의 두번째 edge에서 전송된 첫번째 data를 capture

위의 타이밍 다이어그램은 CPOL이 LOW이고 CPHA가 1Edge인 설정이다.

Blocking Mode
Polling Mode
1. 데이터 송신
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef*hspi, uint8_t*pData, uint16_t Size, uint32_t Timeout)
2. 데이터 수신
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef*hspi, uint8_t*pData, uint16_t Size, uint32_t Timeout)
3. 데이터 송수신
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef*hspi, uint8_t pTxData, uint8_t ```pRxData```, uint16_t Size, uint32_t Timeout)
※ Non- Blocking Mode
Interrupt Mode
위 Blocking 함수 이름 + _IT : Timeout 파라미터 빼고는 나머지 파라미터는 동일
DMA Mode
위 Blocking 함수 이름 + _DMA : Timeout 파라미터 빼고는 나머지 파라미터는 동일
Non-Block Mode 일 때, 전송 또는 수신 프로세스가 끝날 때 각각 실행됩니다. 그 후 아래 함수가 호출
송신 콜백 함수
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
수신 콜백 함수
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
송수신 콜백 함수
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
SPI 전송 상태 반환
HAL_SPI_StateTypeDef HAL_SPI_GetState(SPI_HandleTypeDef *hspi)
hspi 포인터 : SPI_HandleTypeDef를 가리키는 hspi 포인터
리턴값 : spi state
HAL_StatusTypeDef
*hspi)hspi 포인터 : SPI_HandleTypeDef를 가리키는 hspi 포인터
리턴값 : spi error Code
에러 코드(Error Code)
#define HAL_SPI_ERROR_NONE (0x00000000U) /*!< No error */
#define HAL_SPI_ERROR_MODF (0x00000001U) /*!< MODF error */
#define HAL_SPI_ERROR_CRC (0x00000002U) /*!< CRC error */
#define HAL_SPI_ERROR_OVR (0x00000004U) /*!< OVR error */
#define HAL_SPI_ERROR_FRE (0x00000008U) /*!< FRE error */
#define HAL_SPI_ERROR_DMA (0x00000010U) /*!< DMA transfer error */
#define HAL_SPI_ERROR_FLAG (0x00000020U) /*!< Error on RXNE/TXE/BSY Flag */
#define HAL_SPI_ERROR_ABORT (0x00000040U) /*!< Error during SPI Abort procedure */
#include "stm32f4xx_hal.h"
SPI_HandleTypeDef hspi1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SPI1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SPI1_Init();
uint8_t dataToSend = 0x55; // 전송할 데이터
while (1)
{
HAL_SPI_Transmit(&hspi1, &dataToSend, 1, HAL_MAX_DELAY); // 데이터 전송
HAL_Delay(1000); // 1초 대기
}
}
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_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
// 초기화 오류 처리
}
}
// 시스템 클록 구성, GPIO 초기화 함수 등 추가...
/* USER CODE BEGIN 4 */
// FND에 숫자를 표시하는 함수입니다.
// 'fnd'는 표시할 숫자를 나타내며, 'location'은 FND에서 숫자가 표시될 위치를 나타냅니다.
void DisplayFND(uint8_t fnd, uint8_t location){
// GPIO 핀을 통해 래치 클럭 핀을 LOW로 설정합니다.
// 데이터를 전송하기 전, 래치 클럭을 낮춰서 준비 상태로 만듭니다.
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 0); // latch clock pin off
// 전송할 데이터를 저장할 16비트 변수를 초기화합니다.
uint16_t data = 0;
// 표시할 숫자를 상위 바이트로 이동시켜 `data` 변수에 저장합니다.
data = fnd << 8;
// `location` 값을 바탕으로 해당 위치에 숫자를 표시하기 위한 비트를 설정합니다.
data |= 1 << (location - 1);
// 표시할 위치를 나타내는 하위 바이트를 생성합니다.
uint8_t lo = 1 << (location - 1);
// 아래 주석 처리된 코드는 소프트웨어 방식으로 데이터를 시리얼로 전송하는 루프입니다.
// 각 비트를 확인하며, 해당 비트가 1이면 데이터 라인을 HIGH로, 0이면 LOW로 설정합니다.
// 데이터를 시프트 클럭을 사용해 FND에 전송합니다.
/*
for(int i=0 ; i<16; i++){
if(data >> (15-i) & 1){ // MSB first in
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_12, 1); // Data line
}else {
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_12, 0); // Data line
}
// 시프트 클럭 핀을 HIGH로 설정하여 데이터를 전송합니다.
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, 1); // shift clock pin on
// 시프트 클럭 핀을 다시 LOW로 설정하여 다음 비트를 준비합니다.
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_13, 0); // shift clock pin off
}
*/
// SPI를 통해 상위 바이트(FND 숫자)를 전송합니다.
HAL_SPI_Transmit(&hspi1, &fnd, 1, 10);
// SPI를 통해 하위 바이트(위치 정보를 가진) 데이터를 전송합니다.
HAL_SPI_Transmit(&hspi1, &lo, 1, 10);
// GPIO 핀을 통해 래치 클럭 핀을 HIGH로 설정합니다.
// 이로 인해 전송된 데이터가 FND에 표시됩니다.
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 1); // latch clock pin on
}
출처 : https://eteo.tistory.com/160
설명:
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 0);:
래치 클럭 핀을 LOW로 설정합니다. 데이터를 전송하기 전에 준비 상태로 만듭니다.
데이터 준비 (data 변수 생성):
7-세그먼트 디스플레이에 표시할 숫자를 상위 바이트에 위치시키고, 위치 정보를 하위 바이트에 저장합니다.
data |= 1 << (location-1);는 숫자가 표시될 위치를 결정합니다.
소프트웨어 방식의 데이터 전송 (주석 처리됨):
각 비트를 시프트 클럭을 통해 전송하는 방식입니다. 이 부분은 주석 처리되어 있지만, FND 제어의 기본 원리를 설명하는 데 유용합니다.
HAL_SPI_Transmit:
SPI를 사용해 fnd와 lo 데이터를 각각 전송합니다. fnd는 숫자를, lo는 위치 정보를 전달합니다.
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_14, 1);:
래치 클럭 핀을 HIGH로 설정하여, 전송된 데이터가 실제 FND에 표시되도록 합니다.
⦁ Code
Master와 Slave에서 전송할 데이터인 Tx Buffer
/* Master, Slave Tx Buffer*/
uint8_t master_buffer_tx[] = "STM32F4 SPI Master Tx";
uint8_t slave_buffer_tx[] = "STM32F4 SPI Slave Tx";
Tx Buffer의 Size
#define COUNTOF(__BUFFER__) (sizeof(__BUFFER__) / sizeof(*(__BUFFER__)))
#define SLAVE_TX_SIZE (COUNTOF(slave_buffer_tx))
#define MASTER_TX_SIZE (COUNTOF(master_buffer_tx))
Master와 Slave에서 수신할 데이터인 Rx Buffer
uint8_t master_buffer_rx[SLAVE_TX_SIZE];
uint8_t slave_buffer_rx[MASTER_TX_SIZE];
시작을 위한 시작 버튼(PE4) 폴링 체크
while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) != GPIO_PIN_RESET);
Master에서 Transmit, Slave에서 Receive하기
if(HAL_SPI_Receive_DMA(&hspi3, (uint8_t*)slave_buffer_rx, MASTER_TX_SIZE) != HAL_OK)
{
/* Error Handler*/
}
/* Send the data to SPI3-Slave*/
if(HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)master_buffer_tx, MASTER_TX_SIZE) != HAL_OK)
{
/* Error Handler*/
}
데이터 송수신이 완료되었는지 확인
while(HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY);
while(HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
Slave에서 Transmit, Master에서 Receive하기
/* Send the data to SPI2-master*/
if(HAL_SPI_Transmit_DMA(&hspi3, (uint8_t*)slave_buffer_tx, SLAVE_TX_SIZE) != HAL_OK)
{
/* Error Handler*/
}
/* Receive the data to SPI3-slave*/
if(HAL_SPI_Receive_DMA(&hspi2, (uint8_t*)master_buffer_rx, SLAVE_TX_SIZE) != HAL_OK)
{
/* Error Handler*/
}```
데이터 송수신이 완료되었는지 확인
```/* Check the SPI tx, rx state*/
while(HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY);
while(HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY);
데이터 송수신 후 LED(PA6) Toggle
/* USER CODE BEGIN WHILE*/
while(1)
{
/* USER CODE END WHILE*/
/* USER CODE BEGIN 3*/
/* verify the transmission completion using LED*/
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);
HAL_Delay(500);
}
/* USER CODE END 3*/
[출처] STM32CubeMX SPI 실습|작성자 SongWARE
Ref
https://eteo.tistory.com/160
https://lovely-embedded.tistory.com/12
https://velog.io/@chaeyoonl/STM32-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0SPI%EB%9E%80
https://blog.naver.com/keum0821/221118293834
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=hanbulkr&logNo=221571648661
https://kooykoou.tistory.com/m/64