Repository navigation
Chapter 12
⚠️ Hardware Not Verified이 Chapter는
make를 이용한 Compile / Link까지 확인했다.
ADXL345 sensor module이 없어서 실제 SPI waveform,DEVID = 0xE5, X/Y/Z acceleration 값은 아직 검증하지 않았다.
STM32F439ZI의 SPI1을 Master mode로 설정하고 ADXL345 accelerometer와 SPI 통신하기 위한 low-level driver를 구현한다.
STM32F439ZI
│
│ SPI1
│
├── PA5 → SCK
├── PA6 → MISO
├── PA7 → MOSI
└── PA9 → CS
│
▼
ADXL345SPI 설정:
SPI : SPI1
Mode : Master
SCK : PA5
MISO : PA6
MOSI : PA7
CS : PA9 GPIO
AF : AF5
Frame : 8-bit
Bit Order: MSB First
Mode : SPI Mode 3
CPOL = 1
CPHA = 1SPI는 Synchronous Serial Communication 방식이다.
UART와 달리 master가 clock을 직접 생성한다.
Master Slave
SCK ───────────────────────→ Clock
MOSI ───────────────────────→ Data
MISO ←─────────────────────── Data
CS ───────────────────────→ Select
각 signal:
SCK
→ Serial Clock
MOSI
→ Master Out Slave In
MISO
→ Master In Slave Out
CS
→ Chip Select
이번 실습:
| Pin | Function | Mode |
|---|---|---|
| PA5 | SPI1_SCK | AF5 |
| PA6 | SPI1_MISO | AF5 |
| PA7 | SPI1_MOSI | AF5 |
| PA9 | ADXL345 CS | GPIO Output |
PA5~PA7은:
GPIO Alternate Function
↓
AF5
↓
SPI1으로 연결한다.
CS는 SPI hardware NSS를 사용하지 않고 일반 GPIO output으로 직접 제어한다.
이번 코드에서 사용하는 register:
RCC->AHB1ENR
RCC->APB2ENR
GPIOA->MODER
GPIOA->AFR[0]
GPIOA->ODR
SPI1->CR1
SPI1->SR
SPI1->DR
주요 SPI status:
SR.RXNE
→ Receive buffer Not Empty
SR.TXE
→ Transmit buffer Empty
SR.BSY
→ SPI communication Busy
ADXL345 register access에서는 address의 상위 bit를 command 정보로 사용한다.
bit7
R/W
0 → Write
1 → Read
bit6
MB
0 → Single Byte
1 → Multi Byte
예를 들어 register read command는 개념적으로:
Register Address
OR
0x80
↓
Read CommandMulti-byte read라면:
Register Address
|
0x80
|
0x40형태가 된다.
현재 보존된 실습 원본에서 확인되는 파일:
Inc/
└── spi.h
Src/
└── spi.c
ADXL345 application layer는 현재 Notion 및 repository search에서 전체 source를 확인하지 못했다.
따라서 이 Wiki에서는:
spi.h
spi.c를 confirmed source로 기록한다.
향후 sensor module 확보 후:
adxl345.h
adxl345.c
main.c를 실제 테스트 코드 기준으로 추가한다.
#ifndef SPI_H_
#define SPI_H_
#include <stdint.h>
#include "stm32f4xx.h"
/*
- Configure GPIO pins used by SPI1.
-
- PA5 → SCK
- PA6 → MISO
- PA7 → MOSI
- PA9 → Software-controlled CS
*/
void spi_gpio_init(void);
/*
- Configure SPI1 as Master.
-
- ADXL345 communication configuration:
-
- CPOL = 1
- CPHA = 1
-
- → SPI Mode 3
*/
void spi1_config(void);
/*
- Transmit one or more bytes through SPI1.
-
- data:
-
pointer to TX buffer
-
- size:
-
number of bytes to send
*/
void spi1_transmit(uint8_t *data, uint32_t size);
/*
- Receive one or more bytes through SPI1.
-
- SPI master must generate SCK even when only
- receiving data, therefore the implementation
- transmits dummy bytes internally.
*/
void spi1_receive(uint8_t *data, uint32_t size);
/*
- Assert chip select.
-
- ADXL345 CS is active LOW.
*/
void cs_enable(void);
/*
- Deassert chip select.
*/
void cs_disable(void);
#endif
#include "spi.h"
/*
- ============================================================
- RCC Clock Enable Bits
- ============================================================
*/
/*
- RCC AHB1ENR bit0
-
- GPIOA clock enable.
-
- SPI1 pins PA5 / PA6 / PA7 and
- software CS PA9 all belong to GPIOA.
*/
#define GPIOAEN (1U << 0)
/*
- RCC APB2ENR bit12
-
- SPI1 peripheral clock enable.
*/
#define SPI1EN (1U << 12)
/*
- ============================================================
- SPI Status Register Bits
- ============================================================
*/
/*
- SR bit0
-
- RXNE = Receive buffer Not Empty
-
- 0 → no new received data
- 1 → received data available in DR
*/
#define SR_RXNE (1U << 0)
/*
- SR bit1
-
- TXE = Transmit buffer Empty
-
- 0 → transmit buffer not ready
- 1 → new data may be written to DR
*/
#define SR_TXE (1U << 1)
/*
- SR bit7
-
- BSY = Busy flag
-
- 1 → SPI communication still in progress
- 0 → SPI bus no longer busy
*/
#define SR_BSY (1U << 7)
/*
- ============================================================
- SPI GPIO Initialization
- ============================================================
*/
void spi_gpio_init(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable GPIOA Clock
* --------------------------------------------------------
*/
RCC->AHB1ENR |= GPIOAEN;
/*
* --------------------------------------------------------
* Step 2. PA5 → Alternate Function
* --------------------------------------------------------
*
* PA5:
*
* SPI1_SCK
*
* MODER<span>[11:10]</span> = 10
*/
GPIOA->MODER &= ~(0x3UL << (5 * 2));
GPIOA->MODER |= (0x2UL << (5 * 2));
/*
* --------------------------------------------------------
* Step 3. PA6 → Alternate Function
* --------------------------------------------------------
*
* PA6:
*
* SPI1_MISO
*
* MODER<span>[13:12]</span> = 10
*/
GPIOA->MODER &= ~(0x3UL << (6 * 2));
GPIOA->MODER |= (0x2UL << (6 * 2));
/*
* --------------------------------------------------------
* Step 4. PA7 → Alternate Function
* --------------------------------------------------------
*
* PA7:
*
* SPI1_MOSI
*
* MODER<span>[15:14]</span> = 10
*/
GPIOA->MODER &= ~(0x3UL << (7 * 2));
GPIOA->MODER |= (0x2UL << (7 * 2));
/*
* --------------------------------------------------------
* Step 5. PA9 → General Purpose Output
* --------------------------------------------------------
*
* PA9 is NOT using hardware NSS.
*
* It is manually controlled as the ADXL345
* Chip Select signal.
*
* MODER<span>[19:18]</span> = 01
*/
GPIOA->MODER &= ~(0x3UL << (9 * 2));
GPIOA->MODER |= (0x1UL << (9 * 2));
/*
* --------------------------------------------------------
* Step 6. PA5 → AF5
* --------------------------------------------------------
*
* PA5 is in pin range 0~7,
* therefore AFR<span>[0]</span> is used.
*
* Each pin uses 4 bits.
*/
GPIOA->AFR<span>[0]</span> &= ~(0xFUL << (5 * 4));
GPIOA->AFR<span>[0]</span> |= (0x5UL << (5 * 4));
/*
* --------------------------------------------------------
* Step 7. PA6 → AF5
* --------------------------------------------------------
*/
GPIOA->AFR<span>[0]</span> &= ~(0xFUL << (6 * 4));
GPIOA->AFR<span>[0]</span> |= (0x5UL << (6 * 4));
/*
* --------------------------------------------------------
* Step 8. PA7 → AF5
* --------------------------------------------------------
*/
GPIOA->AFR<span>[0]</span> &= ~(0xFUL << (7 * 4));
GPIOA->AFR<span>[0]</span> |= (0x5UL << (7 * 4));
}
/*
- ============================================================
- SPI1 Configuration
- ============================================================
*/
void spi1_config(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable SPI1 Clock
* --------------------------------------------------------
*
* SPI1 belongs to APB2.
*/
RCC->APB2ENR |= SPI1EN;
/*
* --------------------------------------------------------
* Step 2. Configure SPI Clock Divider
* --------------------------------------------------------
*
* CR1 BR<span>[2:0]</span>
*
* Current setting:
*
* 001
*
* → fPCLK / 4
*/
/*
* BR0 = 1
*/
SPI1->CR1 |= (1U << 3);
/*
* BR1 = 0
*/
SPI1->CR1 &= ~(1U << 4);
/*
* BR2 = 0
*/
SPI1->CR1 &= ~(1U << 5);
/*
* --------------------------------------------------------
* Step 3. Configure SPI Mode 3
* --------------------------------------------------------
*
* ADXL345 configuration used in this practice:
*
* CPOL = 1
* CPHA = 1
*
* SPI Mode 3
*/
/*
* CPHA = 1
*/
SPI1->CR1 |= (1U << 0);
/*
* CPOL = 1
*/
SPI1->CR1 |= (1U << 1);
/*
* --------------------------------------------------------
* Step 4. Full-Duplex Mode
* --------------------------------------------------------
*
* BIDIMODE = 0
*
* Separate MOSI and MISO lines are used.
*/
SPI1->CR1 &= ~(1U << 10);
/*
* --------------------------------------------------------
* Step 5. MSB First
* --------------------------------------------------------
*
* LSBFIRST = 0
*
* Most Significant Bit is transmitted first.
*/
SPI1->CR1 &= ~(1U << 7);
/*
* --------------------------------------------------------
* Step 6. Master Mode
* --------------------------------------------------------
*
* MSTR = 1
*
* STM32 generates SCK.
*/
SPI1->CR1 |= (1U << 2);
/*
* --------------------------------------------------------
* Step 7. 8-bit Data Frame
* --------------------------------------------------------
*
* DFF = 0
*
* Each SPI transfer uses 8-bit data.
*/
SPI1->CR1 &= ~(1U << 11);
/*
* --------------------------------------------------------
* Step 8. Software Slave Management
* --------------------------------------------------------
*
* SSM = 1
*
* Hardware NSS management is disabled.
*
* PA9 GPIO is used as CS instead.
*/
SPI1->CR1 |= (1U << 8);
/*
* --------------------------------------------------------
* Step 9. Internal Slave Select
* --------------------------------------------------------
*
* SSI = 1
*
* Required with software slave management
* so the master does not enter mode-fault state.
*/
SPI1->CR1 |= (1U << 9);
/*
* --------------------------------------------------------
* Step 10. Enable SPI1
* --------------------------------------------------------
*
* SPE = 1
*/
SPI1->CR1 |= (1U << 6);
}
/*
- ============================================================
- SPI Transmit
- ============================================================
*/
void spi1_transmit(uint8_t *data, uint32_t size)
{
uint32_t i = 0;
uint8_t temp;
/*
* Send every byte in the TX buffer.
*/
while (i < size)
{
/*
* Wait until transmit buffer becomes empty.
*
* TXE = 1
*
* means SPI1->DR can accept new data.
*/
while (!(SPI1->SR & SR_TXE))
{
}
/*
* Write one byte to Data Register.
*
* SPI hardware now shifts this byte
* out through MOSI.
*/
SPI1->DR = data<span>[i]</span>;
/*
* Move to next byte.
*/
i++;
}
/*
* Wait until the final value has moved
* out of the transmit buffer.
*/
while (!(SPI1->SR & SR_TXE))
{
}
/*
* TXE only tells us the transmit buffer is empty.
*
* The SPI shift register may still be transferring
* the final bits on the physical bus.
*
* Therefore also wait until:
*
* BSY = 0
*/
while (SPI1->SR & SR_BSY)
{
}
/*
* --------------------------------------------------------
* Clear possible OVR condition
* --------------------------------------------------------
*
* SPI is full-duplex.
*
* Even while transmitting, received data may
* accumulate in the receive side.
*
* The required clear sequence is:
*
* Read DR
* ↓
* Read SR
*
* The actual value is not needed.
*/
temp = SPI1->DR;
temp = SPI1->SR;
/*
* Prevent compiler warning:
*
* variable intentionally read only for
* hardware side effect.
*/
(void)temp;
}
/*
- ============================================================
- SPI Receive
- ============================================================
*/
void spi1_receive(uint8_t data, uint32_t size)
{
while (size)
{
/
* ----------------------------------------------------
* Generate SPI Clock
* ----------------------------------------------------
*
* SPI slave cannot send data by itself.
*
* SCK is generated only by the master.
*
* Therefore even when we only want to receive,
* the master must transmit a dummy byte.
*
* The actual dummy data value is irrelevant here.
*/
SPI1->DR = 0;
/*
* Wait until a byte has been received.
*
* RXNE:
*
* 0 → no received byte
* 1 → received byte available
*/
while (!(SPI1->SR & SR_RXNE))
{
}
/*
* Read received byte from Data Register
* and store it in the caller's buffer.
*/
*data++ = (uint8_t)SPI1->DR;
/*
* One byte completed.
*/
size--;
}
}
/*
- ============================================================
- Chip Select Control
- ============================================================
*/
void cs_enable(void)
{
/*
* ADXL345 chip select is active LOW.
*
* PA9 = 0
*
* → Sensor selected
* → SPI transaction begins
*/
GPIOA->ODR &= ~(1U << 9);
}
void cs_disable(void)
{
/*
* PA9 = 1
*
* → Sensor deselected
* → SPI transaction ends
*/
GPIOA->ODR |= (1U << 9);
}
spi_gpio_init()
│
├── GPIOA Clock ON
├── PA5 → AF Mode
├── PA6 → AF Mode
├── PA7 → AF Mode
├── PA9 → Output
└── PA5/6/7 → AF5
spi1_config()
│
├── SPI1 Clock ON
├── BR = /4
├── CPOL = 1
├── CPHA = 1
├── Full Duplex
├── MSB First
├── Master
├── 8-bit
├── SSM = 1
├── SSI = 1
└── SPE = 1
TX Buffer
↓
Wait TXE
↓
Write SPI1->DR
↓
Shift Register
↓
MOSI
↓
ADXL345마지막 byte까지 넣었다고 바로 transaction이 끝난 것은 아니다.
TXE = 1은 transmit buffer가 비었다는 뜻이고:
BSY = 0까지 확인해야 실제 bus transfer 완료를 확인할 수 있다.
그래서:
while (!(SPI1->SR & SR_TXE))
{
}
while (SPI1->SR & SR_BSY)
{
}
두 상태를 모두 확인한다.
SPI에서는 master가 SCK를 만든다.
따라서 slave data만 받고 싶어도:
Master
↓
Generate Clock
↓
Slave shifts MISO data과정이 필요하다.
코드:
SPI1->DR = 0;의 0은 의미 있는 command data가 아니라 clock을 발생시키기 위한 dummy transmit이다.
Dummy TX
↓
SCK generated
↓
Slave shifts data
↓
RXNE = 1
↓
Read DRSPI는 기본적으로 transmit과 receive가 동시에 진행된다.
SCK edge
│
├── MOSI bit → Slave
│
└── MISO bit → Master즉:
Transmit only처럼 코드를 작성해도 hardware에서는 receive path가 같이 동작할 수 있다.
그래서 사용하지 않은 receive data를 처리하지 않으면:
OVR
Overrun상태가 발생할 수 있다.
원본 코드:
temp = SPI1->DR;
temp = SPI1->SR;
(void)temp;이 부분은 값을 계산하기 위해 존재하는 것이 아니다.
Read DR
↓
Read SR
↓
OVR clear라는 hardware-defined side effect를 사용한다.
즉 bare-metal에서는:
register를 읽는 행위 자체가 hardware state를 변경할 수 있다.
는 점이 중요하다.
SPI transaction의 기본 경계:
CS HIGH
↓
CS LOW
↓
Command
↓
Data
↓
CS HIGH코드에서는:
cs_enable();
/* SPI communication */
cs_disable();
형태로 사용한다.
이번 코드는 hardware NSS가 아니라:
PA9 GPIO를 CS로 사용한다.
그리고 SPI 내부에서는:
SSM = 1
SSI = 1로 software slave management를 사용한다.
이렇게 하면 application에서:
Sensor A CS
Sensor B CS
Sensor C CS처럼 여러 slave를 각 GPIO로 제어하기 쉽다.
첫 번째 검증은 acceleration data보다:
DEVID를 먼저 읽는 것이 좋다.
Expected:
ADXL345 DEVID
0xE5
즉:
STM32 SPI 설정
+
배선
+
CS
+
SPI Mode
+
Sensor 응답이 모두 정상인지 가장 간단하게 확인할 수 있다.
원래 Chapter 목표에는:
ADXL345 DEVID Read
X/Y/Z Acceleration Read
UART Output까지 포함되어 있다.
하지만 현재 보존된 source에서 전체 내용을 확인할 수 있는 것은:
spi.h
spi.c까지다.
따라서 현재 Wiki에서는 application code를 추측해서 원본처럼 만들지 않는다.
재실습 후:
adxl345.h
adxl345.c
main.c를 실제 실행했던 source 그대로 추가한다.
현재 확인된 결과:
Compile : PASS
Link : PASS
Hardware: NOT VERIFIEDarm-none-eabi-size:
text = 2328 bytes
data = 0 bytes
bss = 2560 bytes경고 없이 build가 완료되었다.
ADXL345 module 확보 후 다음 순서로 검증한다.
STM32F439ZI ADXL345
PA5 SCK → SCLK
PA6 MISO ← SDO
PA7 MOSI → SDA/SDI
PA9 CS → CS
3.3V → VCC
GND → GND
실제 module pin label과 전원 사양은 사용 모듈의 datasheet/board schematic을 다시 확인한다.
확인:
PA5 MODER → AF
PA6 MODER → AF
PA7 MODER → AF
PA5 AF → 5
PA6 AF → 5
PA7 AF → 5
PA9 MODER → Output
확인:
SPI1->CR1Expected:
MSTR = 1
CPOL = 1
CPHA = 1
DFF = 0
SSM = 1
SSI = 1
SPE = 1Idle:
PA9 = HIGHTransaction:
PA9 = LOW완료:
PA9 = HIGHFirst hardware target:
DEVID == 0xE5결과가 다르면 먼저:
Wiring
SPI Mode
CS polarity
MOSI/MISO swap
AF setting
Clock rate을 확인한다.
DEVID가 정상일 때 그다음:
DATAX
DATAY
DATAZmulti-byte read를 검증한다.
현재 넣을 수 있는 것:
images/ch12_build.png
images/ch12_size.png실기 후 추가:
images/ch12_wiring.jpg
images/ch12_spi_registers.png
images/ch12_devid.png
images/ch12_logic_analyzer.png
images/ch12_xyz_uart.png특히 가장 중요한 실기 증거:
DEVID = 0xE5와 가능하면:
CS
SCK
MOSI
MISOlogic analyzer waveform이다.
SPI1 Master
│
├── PA5 → SCK
├── PA6 ← MISO
├── PA7 → MOSI
└── PA9 → CS
↓
ADXL345SPI는 master가 SCK를 생성하는 synchronous serial protocol이다.
SPI1의 PA5/PA6/PA7은 AF5를 사용한다.
PA9는 software-controlled CS로 사용한다.
MSTR=1로 Master mode를 설정한다.이번 실습은
CPOL=1,CPHA=1인 Mode 3를 사용한다.SSM=1,SSI=1로 software slave management를 사용한다.TXE는 transmit buffer empty를 의미한다.RXNE는 received data가 있다는 뜻이다.BSY는 실제 SPI transfer가 진행 중임을 나타낸다.SPI receive에서도 master가 clock을 만들기 위해 dummy byte를 transmit해야 한다.
OVR clear에는
DR read → SR readsequence가 사용된다.현재 상태는 build/link PASS, hardware verification pending이다.
첫 실기 확인 목표는 **ADXL345 DEVID =
0xE5**다.