Skip to content

Bare metal C Ch12 SPI

Hongjayeong-02 edited this page Sep 21, 2026 · 2 revisions

Ch12. SPI + ADXL345

한 줄 요약: SPI1을 Master로 설정하고 ADXL345와 통신하는 driver를 구현한다. 현재 기록상 센서 모듈이 없어 실제 센서 응답은 미검증 상태다.

1. 핵심 이론

SPI는 Master가 SCK clock을 직접 생성하는 동기식 serial protocol이다.

SCK  : clock
MOSI : Master → Slave
MISO : Slave → Master
CS   : Slave select

사용 핀

Notion 기록 기준 원서와 F439ZI에서 동일한 매핑을 사용한다.

기능 | Pin -- | -- SPI1 SCK | PA5 / AF5 SPI1 MISO | PA6 / AF5 SPI1 MOSI | PA7 / AF5 ADXL345 CS | PA9 GPIO

ADXL345용으로 코드에서는 CPOL=1, CPHA=1을 설정한다.

2. 동작 흐름

SPI GPIO init
     │
     ▼
SPI1 Master config
     │
     ▼
ADXL345 DATA_FORMAT / POWER_CTL write
     │
     ▼
CS Low
     │
     ▼
Register address transmit
     │
     ▼
Dummy TX로 clock 생성 + RX
     │
     ▼
CS High
     │
     ▼
X/Y/Z 조합 → UART 출력

3. 실제 프로젝트 구조


Ch12_spi/

├── Inc/spi.h

├── Src/spi.c

├── Inc/adxl345.h

├── Src/adxl345.c

├── Inc/uart.h

├── Src/uart.c

├── Inc/systick.h

├── Src/systick.c

├── Src/main.c

syscalls.c, sysmem.c, startup file, linker script, CubeIDE metadata/build output은 Ch04에서 다룬 공통 기반이므로 이 페이지에서는 반복하지 않는다. 아래에는 이 chapter 기능 구현에 직접 사용한 source/header를 전부 기록한다.

4. 전체 코드

Inc/spi.h

#ifndef SPI_H_
#define SPI_H_

#include <stdint.h> #include "stm32f4xx.h"

void spi_gpio_init(void); void spi1_config(void); void spi1_transmit(uint8_t *data, uint32_t size); void spi1_receive(uint8_t *data, uint32_t size); void cs_enable(void); void cs_disable(void);

#endif

Src/spi.c

#include "spi.h"

#define GPIOAEN (1U << 0) #define SPI1EN (1U << 12)

#define SR_RXNE (1U << 0) #define SR_TXE (1U << 1) #define SR_BSY (1U << 7)

void spi_gpio_init(void) { RCC->AHB1ENR |= GPIOAEN;

GPIOA-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (5 * 2));
GPIOA-&gt;MODER |=  (0x2UL &lt;&lt; (5 * 2));
GPIOA-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (6 * 2));
GPIOA-&gt;MODER |=  (0x2UL &lt;&lt; (6 * 2));
GPIOA-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (7 * 2));
GPIOA-&gt;MODER |=  (0x2UL &lt;&lt; (7 * 2));

GPIOA-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOA-&gt;MODER |=  (0x1UL &lt;&lt; (9 * 2));   /* PA9 = CS, 범용출력 */

GPIOA-&gt;AFR[0] &amp;= ~(0xFUL &lt;&lt; (5 * 4));
GPIOA-&gt;AFR[0] |=  (0x5UL &lt;&lt; (5 * 4));
GPIOA-&gt;AFR[0] &amp;= ~(0xFUL &lt;&lt; (6 * 4));
GPIOA-&gt;AFR[0] |=  (0x5UL &lt;&lt; (6 * 4));
GPIOA-&gt;AFR[0] &amp;= ~(0xFUL &lt;&lt; (7 * 4));
GPIOA-&gt;AFR[0] |=  (0x5UL &lt;&lt; (7 * 4));

}

void spi1_config(void) { RCC->APB2ENR |= SPI1EN;

SPI1-&gt;CR1 |=  (1U &lt;&lt; 3);
SPI1-&gt;CR1 &amp;= ~(1U &lt;&lt; 4);
SPI1-&gt;CR1 &amp;= ~(1U &lt;&lt; 5);

SPI1-&gt;CR1 |= (1U &lt;&lt; 0);   /* CPHA */
SPI1-&gt;CR1 |= (1U &lt;&lt; 1);   /* CPOL */

SPI1-&gt;CR1 &amp;= ~(1U &lt;&lt; 10);
SPI1-&gt;CR1 &amp;= ~(1U &lt;&lt; 7);
SPI1-&gt;CR1 |=  (1U &lt;&lt; 2);
SPI1-&gt;CR1 &amp;= ~(1U &lt;&lt; 11);
SPI1-&gt;CR1 |=  (1U &lt;&lt; 8);
SPI1-&gt;CR1 |=  (1U &lt;&lt; 9);

SPI1-&gt;CR1 |= (1U &lt;&lt; 6);

}

void spi1_transmit(uint8_t *data, uint32_t size) { uint32_t i = 0; uint8_t temp;

while (i &lt; size)
{
    while (!(SPI1-&gt;SR &amp; SR_TXE)) { }
    SPI1-&gt;DR = data[i];
    i++;
}
while (!(SPI1-&gt;SR &amp; SR_TXE)) { }
while ((SPI1-&gt;SR &amp; SR_BSY)) { }

temp = SPI1-&gt;DR;
temp = SPI1-&gt;SR;
(void)temp;

}

void spi1_receive(uint8_t *data, uint32_t size) { while (size) { SPI1->DR = 0; while (!(SPI1->SR & SR_RXNE)) { } *data++ = (uint8_t)SPI1->DR; size--; } }

void cs_enable(void) { GPIOA->ODR &= ~(1U << 9); } void cs_disable(void) { GPIOA->ODR |= (1U << 9); }

Inc/adxl345.h

#ifndef ADXL345_H_
#define ADXL345_H_

#include <stdint.h> #include "spi.h"

#define ADXL345_REG_DEVID 0x00U #define ADXL345_REG_DATA_FORMAT 0x31U #define ADXL345_REG_POWER_CTL 0x2DU #define ADXL345_REG_DATA_START 0x32U

#define ADXL345_RANGE_4G 0x01U #define ADXL345_RESET 0x00U #define ADXL345_MEASURE_BIT 0x08U

#define ADXL345_MULTI_BYTE_ENABLE 0x40U #define ADXL345_READ_OPERATION 0x80U

void adxl_init(void); void adxl_read(uint8_t address, uint8_t *rxdata); void adxl_write(uint8_t address, uint8_t value);

#endif

Src/adxl345.c

#include "adxl345.h"

void adxl_read(uint8_t address, uint8_t *rxdata) { address |= ADXL345_READ_OPERATION; address |= ADXL345_MULTI_BYTE_ENABLE;

cs_enable();
spi1_transmit(&amp;address, 1);
spi1_receive(rxdata, 6);
cs_disable();

}

void adxl_write(uint8_t address, uint8_t value) { uint8_t data[2]; data[0] = address | ADXL345_MULTI_BYTE_ENABLE; data[1] = value;

cs_enable();
spi1_transmit(data, 2);
cs_disable();

}

void adxl_init(void) { spi_gpio_init(); spi1_config();

adxl_write(ADXL345_REG_DATA_FORMAT, ADXL345_RANGE_4G);
adxl_write(ADXL345_REG_POWER_CTL, ADXL345_RESET);
adxl_write(ADXL345_REG_POWER_CTL, ADXL345_MEASURE_BIT);

}

Inc/uart.h

#ifndef UART_H
#define UART_H

#include "stm32f4xx.h"

void uart_init(void); void uart_write_string(const char *s); int __io_putchar(int ch);

#endif

Src/uart.c

#include <stdint.h>
#include "uart.h"

#define GPIODEN (1U << 3) #define USART3EN (1U << 18)

#define DBG_UART_BAUDRATE 115200U #define SYS_FREQ 16000000U #define APB1_CLK SYS_FREQ

#define CR1_TE (1U << 3) #define CR1_UE (1U << 13) #define SR_TXE (1U << 7)

static void uart_write(int ch); static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate); static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate);

int __io_putchar(int ch) { uart_write(ch); return ch; }

void uart_init(void) { RCC->AHB1ENR |= GPIODEN;

GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (9 * 2));

GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 0);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 0);   /* PD8 -&gt; AF7 (USART3_TX) */
GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 4);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 4);   /* PD9 -&gt; AF7 (USART3_RX) */

RCC-&gt;APB1ENR |= USART3EN;

uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);

USART3-&gt;CR1 = CR1_TE;
USART3-&gt;CR1 |= CR1_UE;

}

static void uart_write(int ch) { while (!(USART3->SR & SR_TXE)) { } USART3->DR = (ch & 0xFF); }

void uart_write_string(const char *s) { while (*s) uart_write((int)*s++); }

static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate) { return (uint16_t)((periph_clk + (baudrate / 2U)) / baudrate); }

static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate) { USART3->BRR = compute_uart_bd(periph_clk, baudrate); }

Inc/systick.h

#ifndef SYSTICK_H_
#define SYSTICK_H_

#include <stdint.h> #include "stm32f4xx.h"

void systick_msec_delay(uint32_t delay);

#endif /* SYSTICK_H_ */

Src/systick.c

#include "systick.h"

#define CTRL_ENABLE (1UL << 0) #define CTRL_CLKSRC (1UL << 2) #define CTRL_COUNTFLAG (1UL << 16)

/* 코어클럭 16MHz(기본 HSI) 기준 1ms당 클럭 사이클 수 */ #define ONE_MSEC_LOAD 16000UL

void systick_msec_delay(uint32_t delay) { SysTick->LOAD = ONE_MSEC_LOAD - 1UL; SysTick->VAL = 0UL; SysTick->CTRL = CTRL_CLKSRC; SysTick->CTRL |= CTRL_ENABLE;

for (uint32_t i = 0; i &lt; delay; i++)
{
    while ((SysTick-&gt;CTRL &amp; CTRL_COUNTFLAG) == 0UL) { }
}

SysTick-&gt;CTRL = 0UL;

}

Src/main.c

#include <stdio.h>
#include "adxl345.h"
#include "uart.h"
#include "systick.h"

int main(void) { uint8_t devid = 0; uint8_t rxdata[6]; int16_t x, y, z;

uart_init();
adxl_init();

adxl_read(ADXL345_REG_DEVID, &amp;devid);
printf("ADXL345 DEVID (expect 0xE5): 0x%02X\r\n", devid);

while (1)
{
    adxl_read(ADXL345_REG_DATA_START, rxdata);

    x = (int16_t)((rxdata[1] &lt;&lt; 8) | rxdata[0]);
    y = (int16_t)((rxdata[3] &lt;&lt; 8) | rxdata[2]);
    z = (int16_t)((rxdata[5] &lt;&lt; 8) | rxdata[4]);

    printf("X=%d Y=%d Z=%d\r\n", x, y, z);
    systick_msec_delay(200);
}

}

5. 검증 상태

Notion 기록:

  • make/build 및 link 확인

  • ADXL345 sensor module 부재

  • 실제 DEVID = 0xE5 및 X/Y/Z 값은 미검증

text = 2328 bytes
data = 0 bytes
bss  = 2560 bytes
warning 없음

현재 코드에서 확인이 필요한 부분

첨부 ZIP의 adxl_read()는 주소에 상관없이 항상 6 byte를 수신한다.

spi1_receive(rxdata, 6);

그런데 main.c에서는 DEVID 확인 시:

uint8_t devid;
adxl_read(ADXL345_REG_DEVID, &devid);

처럼 1 byte 변수의 주소를 넘긴다. 현재 센서 실기가 미검증 상태이므로, 실제 센서 연결 전에 단일-byte read와 6-byte burst read를 분리할지 확인할 필요가 있다.
여기서는 첨부된 실제 코드를 수정하지 않고 그대로 기록한다.

6. 핵심 정리

  • SPI는 Master가 clock을 생성한다.

  • 수신만 할 때도 clock 생성을 위해 dummy transmit이 필요하다.

  • 현재 ADXL345 sensor hardware 검증은 남아 있다.

Clone this wiki locally