Repository navigation
Chapter 13
⚠️ Sensor Data Not VerifiedSTM32CubeIDE에서 Build → Download → Run까지 실제 NUCLEO-F439ZI에서 수행했다.
다만 ADXL345 I2C module이 없어서DEVID, X/Y/Z acceleration data는 아직 검증하지 않았다.
Ch12에서 SPI로 연결하려 했던 ADXL345를 이번에는 I2C1으로 통신한다.
STM32F439ZI
│
│ I2C1
│
├── PB8 → SCL
└── PB9 → SDA
│
▼
ADXL345설정:
I2C : I2C1
SCL : PB8
SDA : PB9
AF : AF4
Clock : 16 MHz APB1
Bus Speed: 100 kHz
Mode : Standard ModeCh12 SPI:
SCK
MOSI
MISO
CSCh13 I2C:
SCL
SDAI2C에서는 여러 slave가 같은 두 선을 공유하고 주소로 구분된다.
Master
│
├──────── SCL ──────┬──── Slave A
│ ├──── Slave B
│ └──── Slave C
│
└──────── SDA ──────┴────ADXL345의 register map 자체는 SPI와 I2C에서 동일하다.
달라지는 것은:
Sensor Register
↓
어떻게 접근하느냐즉 transport protocol이다.
이번 실습:
Pin | Function | AF -- | -- | -- PB8 | I2C1_SCL | AF4 PB9 | I2C1_SDA | AF4현재 Bare-Metal project:
APB1 Clock = 16 MHzStandard Mode:
Target SCL = 100 kHz기존 실습 계산:
CCR = 80왜냐하면 Standard Mode에서는:
fSCL = fPCLK1 / (2 × CCR)따라서:
100,000
=
16,000,000 / (2 × CCR)정리:
CCR = 80Standard Mode maximum rise time:
1000 ns16 MHz clock period:
62.5 ns따라서:
TRISE
=
1000 / 62.5 + 1
=
17결과:
I2C1->CCR = 80;
I2C1->TRISE = 17;I2C에서 가장 중요한 부분이다.
START
↓
Slave Address + W
↓
ADDR
↓
Register Address
↓
Data
↓
STOP특정 register를 읽을 때:
START
↓
Slave Address + W
↓
Register Address
↓
Repeated START
↓
Slave Address + R
↓
Read Data
↓
NACK
↓
STOP즉 register address를 먼저 Write 방향으로 알려준 뒤 Read 방향으로 다시 전환한다.
현재 보존된 원본에서 확실히 확인되는 핵심 함수:
i2c1_init()
i2c1_start()
i2c1_byte_read()현재 Notion에 함수 호출만 남아 있고 본문이 없는 함수:
i2c1_gpio_init()
i2c1_write_byte()
i2c1_stop()따라서 아래에서는 확인된 코드만 원본 코드로 기록한다.
Wiki에서는 확인된 함수 interface를 기준으로 정리한다.
#ifndef I2C_H__
#define I2C_H__
#include <stdint.h>
#include "stm32f4xx.h"
/*
- Initialize I2C1.
-
- PB8 → SCL
- PB9 → SDA
- AF4
- 100 kHz Standard Mode
*/
void i2c1_init(void);
/*
- Read one register byte from an I2C slave.
-
- saddr:
-
7-bit slave address
-
- maddr:
-
internal register/sub-address
-
- data:
-
destination for received byte
*/
void i2c1_byte_read(uint8_t saddr,
uint8_t maddr,
uint8_t *data);
#endif
#include "i2c.h"
/*
- ============================================================
- Timing Configuration
- ============================================================
*/
/*
- APB1 clock:
-
-
16 MHz
-
- Standard Mode target:
-
-
100 kHz
-
- fSCL = fPCLK1 / (2 × CCR)
-
- 100 kHz
- =
- 16 MHz / (2 × 80)
*/
#define I2C_100KHZ 80U
/*
- Standard Mode maximum rise time:
-
-
1000 ns
-
- APB1 clock:
-
-
16 MHz
-
= 62.5 ns / cycle
-
- TRISE:
-
-
1000 / 62.5 + 1
-
= 17
*/
#define SD_MODE_MAX_RISE_TIME 17U
/*
- ============================================================
- Register Bit Definitions
- ============================================================
*/
/*
- RCC APB1ENR bit21:
-
- I2C1 clock enable.
*/
#define I2C1EN (1U << 21)
/*
- CR1 bit0:
-
- PE = Peripheral Enable
*/
#define CR1_PE (1U << 0)
/*
- CR1 bit8:
-
- START generation.
*/
#define CR1_START (1U << 8)
/*
- CR1 bit9:
-
- STOP generation.
*/
#define CR1_STOP (1U << 9)
/*
- CR1 bit10:
-
- ACK control.
*/
#define CR1_ACK (1U << 10)
/*
- CR1 bit15:
-
- Software Reset.
*/
#define CR1_SWRST (1U << 15)
/*
- CR2 FREQ field:
-
- APB1 clock = 16 MHz
*/
#define CR2_FREQ_16MHZ 16U
/*
- SR1 bit0:
-
- SB = Start Bit generated.
*/
#define SR1_SB (1U << 0)
/*
- SR1 bit1:
-
- ADDR = Address sent/matched.
*/
#define SR1_ADDR (1U << 1)
/*
- SR1 bit6:
-
- RXNE = Receive Data Register Not Empty.
*/
#define SR1_RXNE (1U << 6)
/*
- SR2 bit1:
-
- BUSY = Communication ongoing on the bus.
*/
#define SR2_BUSY (1U << 1)
/*
-
============================================================
-
Private Functions
-
============================================================
-
-
Their complete archived source body is not present
-
in the current Notion record.
/
static void i2c1_gpio_init(void);
static void i2c1_start(uint8_t saddr, uint8_t rw);
static void i2c1_write_byte(uint8_t data);
static void i2c1_stop(void);
void i2c1_init(void)
{
/
-
- Step 1. Configure PB8 / PB9
-
-
- Expected configuration:
-
- PB8 → I2C1_SCL
- PB9 → I2C1_SDA
- AF4
-
- The complete body of i2c1_gpio_init()
- was not preserved in the current Notion source.
*/
i2c1_gpio_init();
/*
-
- Step 2. Enable I2C1 Clock
-
-
- I2C1 is connected to APB1.
*/
RCC->APB1ENR |= I2C1EN;
/*
-
- Step 3. Reset I2C Peripheral
-
-
- SWRST = 1
-
- Software-reset the internal I2C logic.
*/
I2C1->CR1 |= CR1_SWRST;
/*
- Release software reset.
*/
I2C1->CR1 &= ~CR1_SWRST;
/*
-
- Step 4. Tell I2C Hardware the APB1 Frequency
-
-
- CR2 FREQ field:
-
-
16
-
- means:
-
-
APB1 = 16 MHz
-
- This value is used internally by I2C timing logic.
*/
I2C1->CR2 = CR2_FREQ_16MHZ;
/*
-
- Step 5. Configure 100 kHz SCL
-
-
- Standard Mode:
-
-
CCR = 80
*/
I2C1->CCR = I2C_100KHZ;
/*
-
- Step 6. Configure Maximum Rise Time
-
-
- Standard Mode:
-
-
TRISE = 17
*/
I2C1->TRISE = SD_MODE_MAX_RISE_TIME;
/*
-
- Step 7. Enable I2C1
-
-
- PE = 1
/
I2C1->CR1 |= CR1_PE;
}
static void i2c1_start(uint8_t saddr, uint8_t rw)
{
/
Step 1. Wait Until Bus Is Free
SR2.BUSY:
1 → bus currently in use
0 → bus free
*/
while (I2C1->SR2 & SR2_BUSY)
{
}
/*
-
- Step 2. Enable ACK
-
-
- ACK is enabled by default before
- beginning the transaction.
*/
I2C1->CR1 |= CR1_ACK;
/*
-
- Step 3. Generate START
-
-
- Hardware generates the I2C START condition.
*/
I2C1->CR1 |= CR1_START;
/*
-
- Step 4. Wait for Start Bit
-
-
- SR1.SB = 1
-
- confirms START has been generated.
*/
while (!(I2C1->SR1 & SR1_SB))
{
}
/*
-
- Step 5. Send Slave Address + R/W Bit
-
-
- I2C uses a 7-bit slave address.
-
- Transmission byte:
-
- [A6 A5 A4 A3 A2 A1 A0 R/W]
-
- Therefore:
-
-
saddr << 1
-
- creates space for R/W in bit0.
*/
I2C1->DR =
rw
? (uint8_t)((saddr << 1) | 1U)
: (uint8_t)(saddr << 1);
/*
-
- Step 6. Wait for Address Phase Complete
-
*/
while (!(I2C1->SR1 & SR1_ADDR))
{
}
/*
-
- Step 7. Clear ADDR Flag
-
-
- STM32 I2C has a special hardware-defined sequence:
-
-
Read SR1
-
↓
-
Read SR2
-
↓
-
ADDR cleared
-
- The returned values themselves are not needed.
-
- The READ operation is required for its
- hardware side effect.
*/
(void)I2C1->SR1;
(void)I2C1->SR2;
}
예를 들어 ADXL345의 7-bit slave address가:
0x53이라면 binary:
1010011I2C address phase에서는 마지막 bit가 R/W다.
그래서:
7-bit address
1010011
↓ << 1
10100110Write:
10100110Read:
10100111즉:
(saddr << 1)과:
(saddr << 1) | 1U로 구분한다.
특정 sensor register를 한 byte 읽는 핵심 코드다.
void i2c1_byte_read(uint8_t saddr,
uint8_t maddr,
uint8_t data)
{
/
========================================================
Phase 1
Tell the slave WHICH register we want to read
========================================================
*/
/*
-
- Step 1. START + Slave Address in WRITE Mode
-
-
- rw = 0
-
- because the first phase sends the sensor's
- internal register address.
*/
i2c1_start(saddr, 0U);
/*
-
- Step 2. Send Sensor Register Address
-
-
- Example:
-
-
maddr = DEVID register address
-
- This tells ADXL345 which internal register
- will be accessed next.
-
- The complete archived implementation of
- i2c1_write_byte() is currently unavailable.
*/
i2c1_write_byte(maddr);
/*
- ========================================================
- Phase 2
- Change Direction: WRITE → READ
- ========================================================
*/
/*
-
- Step 3. Generate Repeated START
-
-
- Important:
-
- No STOP is generated between register-address
- write and data read.
-
- This keeps ownership of the bus.
*/
I2C1->CR1 |= CR1_START;
/*
- Wait until Repeated START is generated.
*/
while (!(I2C1->SR1 & SR1_SB))
{
}
/*
-
- Step 4. Send Slave Address in READ Mode
-
-
- Address format:
-
-
(saddr << 1) | 1
-
- LSB = 1
-
- means READ.
*/
I2C1->DR =
(uint8_t)((saddr << 1) | 1U);
/*
- Wait until address phase completes.
*/
while (!(I2C1->SR1 & SR1_ADDR))
{
}
/*
- ========================================================
- Phase 3
- Receive One Byte
- ========================================================
*/
/*
-
- Step 5. Disable ACK Before Receiving Last Byte
-
-
- This read operation receives exactly ONE byte.
-
- Therefore the first byte is also the last byte.
-
- ACK = 0 means:
-
-
Do not request another byte.
-
- The NACK behavior must be prepared BEFORE
- the final byte is completely received.
*/
I2C1->CR1 &= ~CR1_ACK;
/*
-
- Step 6. Clear ADDR
-
-
- Again:
-
- SR1 read
- ↓
- SR2 read
- ↓
- ADDR clear
*/
(void)I2C1->SR1;
(void)I2C1->SR2;
/*
-
- Step 7. Generate STOP
-
-
- Because this is the last receive byte,
- STOP is prepared before reading DR.
-
- The complete archived body of i2c1_stop()
- is not present in the current source record.
*/
i2c1_stop();
/*
-
- Step 8. Wait for Received Data
-
-
- RXNE = 1
-
- means one byte is available in DR.
*/
while (!(I2C1->SR1 & SR1_RXNE))
{
}
/*
-
- Step 9. Read Data
-
-
- Store the received sensor register value
- into the caller's variable.
*/
*data = (uint8_t)I2C1->DR;
}
왜 START를 두 번 쓰는가?
특정 register를 읽으려면 먼저:
어느 register?를 slave에게 알려줘야 한다.
그래서 첫 transaction은 Write다.
START
↓
Slave + W
↓
Register Address그다음 실제 data를 읽기 위해 direction을 바꾼다.
Repeated START
↓
Slave + R
↓
Data전체:
START
↓
Address + W
↓
Register Address
↓
Repeated START
↓
Address + R
↓
Data
↓
STOPSTOP을 중간에 보내지 않기 때문에 하나의 연결된 register-read transaction으로 유지할 수 있다.
이 부분:
(void)I2C1->SR1;
(void)I2C1->SR2;은 값을 얻기 위해 읽는 것이 아니다.
STM32 I2C hardware에서 ADDR flag clear 조건이:
Read SR1
↓
Read SR2이기 때문이다.
즉:
register read
=
hardware command 역할도 할 수 있다.
SR2를 읽지 않으면 ADDR가 계속 set되어 이후 state가 진행되지 않는 문제가 생길 수 있다.
Slave가 byte 하나를 보내고 나면 master는:
ACK
→ 다음 byte 더 보내
NACK
→ 이제 그만
을 알려준다.
한 byte만 읽는 이번 함수에서는:
I2C1->CR1 &= ~CR1_ACK;를 data를 읽기 전에 수행한다.
왜냐하면 마지막 byte에 대한 NACK timing을 hardware에 미리 알려줘야 하기 때문이다.
단일 byte receive 흐름:
ADDR complete
↓
ACK = 0
↓
ADDR clear
↓
STOP request
↓
RXNE
↓
Read DR처음 보면:
아직 data도 안 읽었는데 STOP을 왜 벌써 보내지?
라고 생각하기 쉽다.
하지만 I2C receive에서는 ACK/NACK 및 STOP의 timing이 실제 bus protocol과 맞아야 해서 마지막 byte 전에 종료 조건을 준비한다.
센서 register:
DEVID
POWER_CTL
DATA_FORMAT
DATAX
DATAY
DATAZ등은 그대로다.
CS LOW
↓
Command Address
↓
MOSI / MISO
↓
CS HIGHSTART
↓
Slave Address
↓
Register Address
↓
Repeated START
↓
Read
↓
STOP즉:
ADXL345 Driver Logic과:
Communication Transport를 분리해서 생각하는 것이 중요하다.
이번 chapter 목표:
ADXL345
↓
I2C Register Access
↓
DEVID
↓
X/Y/Z
↓
UART하지만 현재 보존된 기록에는 I2C low-level 핵심 함수만 있고 application 전체 source는 남아 있지 않다.
따라서:
DEVID Read
X/Y/Z Read
UART Print코드는 재실습 시 실제 source 기준으로 추가한다.
기존 실습 결과:
Build : PASS
Download : PASS
Run : PASS
Sensor Data Verification:
NOT COMPLETE
arm-none-eabi-size:
text = 2632 bytes
data = 0 bytes
bss = 2560 bytes경고 없이 build되었다. 센서가 연결되지 않았기 때문에 DEVID와 acceleration 값 자체는 확인하지 못했다.
NUCLEO-F439ZI ADXL345
PB8 SCL → SCL
PB9 SDA ↔ SDA
3.3V → VCC
GND → GND
실제 module의 address pin configuration도 확인한다.
Debugger:
PB8 MODER → AF
PB9 MODER → AF
PB8 AF → 4
PB9 AF → 4
확인:
I2C1->CR2 = 16
I2C1->CCR = 80
I2C1->TRISE = 17확인:
CR1.PE = 1가장 먼저:
ADXL345 DEVID를 읽는다.
Expected:
0xE5DEVID가 맞으면:
GPIO configuration
I2C timing
Slave address
START
Address phase
Repeated START
ACK/NACK
Receive가 대부분 정상이라고 판단할 수 있다.
그다음:
DATAX
DATAY
DATAZ를 읽고 UART로 출력한다.
while()에서 멈춘다면 어느 flag에서 멈췄는지 먼저 확인한다.
BUSY stuck?
→ Bus / wiring 문제
SB not set?
→ START generation 문제
ADDR not set?
→ Slave address / ACK / wiring 문제
ADDR never clears?
→ SR1 → SR2 read sequence 확인
RXNE not set?
→ Slave response / receive sequence 확인
I2C는 polling loop가 많기 때문에:
어느 while에서 멈췄는가?가 매우 중요한 debugging information이다.
현재:
images/ch13_build.png
images/ch13_download.png실기 검증 후:
images/ch13_wiring.jpg
images/ch13_gpio_af.png
images/ch13_i2c_timing.png
images/ch13_devid.png
images/ch13_xyz_uart.png
images/ch13_logic_analyzer.png가장 중요한 실기 증거:
DEVID = 0xE5그리고 가능하면 logic analyzer의:
START
Address
ACK
Register Address
Repeated START
Data
NACK
STOPwaveform이다.
16 MHz APB1
↓
CR2 = 16
↓
CCR = 80
↓
TRISE = 17
↓
100 kHz I2C그리고 register read:
START
↓
Slave + W
↓
Register Address
↓
Repeated START
↓
Slave + R
↓
NACK
↓
Data
↓
STOPI2C1은 PB8=SCL, PB9=SDA, AF4를 사용한다.
이번 project는 APB1 16MHz 기준이다.
Standard Mode 100kHz에서
CCR=80,TRISE=17을 사용한다.I2C slave address는 7-bit이고 전송 시
<<1후 LSB에 R/W를 붙인다.SB는 START condition 생성 완료를 나타낸다.ADDR는 address phase 완료를 나타낸다.ADDRclear에는 SR1 read → SR2 read 순서가 필요하다.register read는 Write sub-address → Repeated START → Read 구조다.
single-byte receive에서는 마지막 byte 전에 ACK를 끄고 STOP을 준비한다.
현재 상태는 build/download/run PASS, sensor data 미검증이다.