Skip to content

Chapter 13

Hongjayeong-02 edited this page Oct 5, 2026 · 1 revision

13. I2C + ADXL345

⚠️ Sensor Data Not Verified

STM32CubeIDE에서 Build → Download → Run까지 실제 NUCLEO-F439ZI에서 수행했다.
다만 ADXL345 I2C module이 없어서 DEVID, X/Y/Z acceleration data는 아직 검증하지 않았다.


Goal

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 Mode

1. SPI와 I2C 차이

Ch12 SPI:

SCK
MOSI
MISO
CS

Ch13 I2C:

SCL
SDA

I2C에서는 여러 slave가 같은 두 선을 공유하고 주소로 구분된다.

Master
  │
  ├──────── SCL ──────┬──── Slave A
  │                   ├──── Slave B
  │                   └──── Slave C
  │
  └──────── SDA ──────┴────

ADXL345의 register map 자체는 SPI와 I2C에서 동일하다.

달라지는 것은:

Sensor Register
    ↓
어떻게 접근하느냐

즉 transport protocol이다.


2. I2C1 Pin Mapping

이번 실습:

Pin | Function | AF -- | -- | -- PB8 | I2C1_SCL | AF4 PB9 | I2C1_SDA | AF4

4. 100 kHz Timing

현재 Bare-Metal project:

APB1 Clock = 16 MHz

Standard Mode:

Target SCL = 100 kHz

기존 실습 계산:

CCR = 80

왜냐하면 Standard Mode에서는:

fSCL = fPCLK1 / (2 × CCR)

따라서:

100,000
=
16,000,000 / (2 × CCR)

정리:

CCR = 80

TRISE

Standard Mode maximum rise time:

1000 ns

16 MHz clock period:

62.5 ns

따라서:

TRISE
=
1000 / 62.5 + 1
=
17

결과:

I2C1->CCR   = 80;
I2C1->TRISE = 17;

5. I2C Transaction Flow

I2C에서 가장 중요한 부분이다.

Write

START
  ↓
Slave Address + W
  ↓
ADDR
  ↓
Register Address
  ↓
Data
  ↓
STOP

Register Read

특정 register를 읽을 때:

START
  ↓
Slave Address + W
  ↓
Register Address
  ↓
Repeated START
  ↓
Slave Address + R
  ↓
Read Data
  ↓
NACK
  ↓
STOP

즉 register address를 먼저 Write 방향으로 알려준 뒤 Read 방향으로 다시 전환한다.


6. Practice Code Status

현재 보존된 원본에서 확실히 확인되는 핵심 함수:

i2c1_init()
i2c1_start()
i2c1_byte_read()

현재 Notion에 함수 호출만 남아 있고 본문이 없는 함수:

i2c1_gpio_init()
i2c1_write_byte()
i2c1_stop()

따라서 아래에서는 확인된 코드만 원본 코드로 기록한다.


7. i2c.h

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


8. i2c.c — Definitions

#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);


9. i2c1_init()

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; }

10. i2c1_start()

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 &lt;&lt; 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; }

    11. Why saddr << 1?

    예를 들어 ADXL345의 7-bit slave address가:

    0x53

    이라면 binary:

    1010011

    I2C address phase에서는 마지막 bit가 R/W다.

    그래서:

    7-bit address
    1010011
    ↓ << 1
    10100110

    Write:

    10100110

    Read:

    10100111

    즉:

    (saddr << 1)

    과:

    (saddr << 1) | 1U

    로 구분한다.


    12. i2c1_byte_read()

    특정 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 &lt;&lt; 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; }

    13. Repeated START

    왜 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
    ↓
    STOP

    STOP을 중간에 보내지 않기 때문에 하나의 연결된 register-read transaction으로 유지할 수 있다.


    14. Why Read SR1 Then SR2?

    이 부분:

    (void)I2C1->SR1;
    (void)I2C1->SR2;

    은 값을 얻기 위해 읽는 것이 아니다.

    STM32 I2C hardware에서 ADDR flag clear 조건이:

    Read SR1
    ↓
    Read SR2

    이기 때문이다.

    즉:

    register read
    =
    hardware command 역할

    도 할 수 있다.

    SR2를 읽지 않으면 ADDR가 계속 set되어 이후 state가 진행되지 않는 문제가 생길 수 있다.


    15. ACK / NACK

    Slave가 byte 하나를 보내고 나면 master는:

    ACK
    → 다음 byte 더 보내
    
    
    
    

    NACK → 이제 그만

    을 알려준다.

    한 byte만 읽는 이번 함수에서는:

    I2C1->CR1 &= ~CR1_ACK;

    를 data를 읽기 전에 수행한다.

    왜냐하면 마지막 byte에 대한 NACK timing을 hardware에 미리 알려줘야 하기 때문이다.


    16. STOP Timing

    단일 byte receive 흐름:

    ADDR complete
    ↓
    ACK = 0
    ↓
    ADDR clear
    ↓
    STOP request
    ↓
    RXNE
    ↓
    Read DR

    처음 보면:

    아직 data도 안 읽었는데 STOP을 왜 벌써 보내지?

    라고 생각하기 쉽다.

    하지만 I2C receive에서는 ACK/NACK 및 STOP의 timing이 실제 bus protocol과 맞아야 해서 마지막 byte 전에 종료 조건을 준비한다.


    17. I2C vs SPI — ADXL345

    센서 register:

    DEVID
    POWER_CTL
    DATA_FORMAT
    DATAX
    DATAY
    DATAZ

    등은 그대로다.

    SPI

    CS LOW
    ↓
    Command Address
    ↓
    MOSI / MISO
    ↓
    CS HIGH

    I2C

    START
    ↓
    Slave Address
    ↓
    Register Address
    ↓
    Repeated START
    ↓
    Read
    ↓
    STOP

    즉:

    ADXL345 Driver Logic

    과:

    Communication Transport

    를 분리해서 생각하는 것이 중요하다.


    18. Application Layer Status

    이번 chapter 목표:

    ADXL345
    ↓
    I2C Register Access
    ↓
    DEVID
    ↓
    X/Y/Z
    ↓
    UART

    하지만 현재 보존된 기록에는 I2C low-level 핵심 함수만 있고 application 전체 source는 남아 있지 않다.

    따라서:

    DEVID Read
    X/Y/Z Read
    UART Print

    코드는 재실습 시 실제 source 기준으로 추가한다.


    19. Build / Run Result

    기존 실습 결과:

    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 값 자체는 확인하지 못했다.


    20. Hardware Re-Test Plan

    Step 1 — Wiring

    NUCLEO-F439ZI      ADXL345
    

    PB8 SCL → SCL PB9 SDA ↔ SDA

    3.3V → VCC GND → GND

    실제 module의 address pin configuration도 확인한다.


    Step 2 — GPIO

    Debugger:

    PB8 MODER → AF
    PB9 MODER → AF
    

    PB8 AF → 4 PB9 AF → 4


    Step 3 — I2C Timing

    확인:

    I2C1->CR2   = 16
    I2C1->CCR   = 80
    I2C1->TRISE = 17

    Step 4 — I2C Enable

    확인:

    CR1.PE = 1

    Step 5 — First Sensor Test

    가장 먼저:

    ADXL345 DEVID

    를 읽는다.

    Expected:

    0xE5

    DEVID가 맞으면:

    GPIO configuration
    I2C timing
    Slave address
    START
    Address phase
    Repeated START
    ACK/NACK
    Receive

    가 대부분 정상이라고 판단할 수 있다.


    Step 6 — X/Y/Z

    그다음:

    DATAX
    DATAY
    DATAZ

    를 읽고 UART로 출력한다.


    21. Debugging Checklist

    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이다.


    22. Screenshots to Add

    현재:

    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
    STOP

    waveform이다.


    23. Key Points

    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
    ↓
    STOP
    • I2C1은 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 완료를 나타낸다.

    • ADDR clear에는 SR1 read → SR2 read 순서가 필요하다.

    • register read는 Write sub-address → Repeated START → Read 구조다.

    • single-byte receive에서는 마지막 byte 전에 ACK를 끄고 STOP을 준비한다.

    • 현재 상태는 build/download/run PASS, sensor data 미검증이다.

    Clone this wiki locally