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, ®, 1, 10);
// 2. 슬레이브가 해당 데이터를 준비했으므로, 클럭을 더 보내면서 데이터 수신
HAL_SPI_Receive(&hspi2, &temperature_data, 1, 10);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); // CS HIGH (슬레이브 비활성화)
이렇게 사용하거나, 더미데이터가 필요한 경우에는
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로 부품을 확인해야한다.