Bare metal C Ch12 SPI - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki

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 검증은 남아 μžˆλ‹€.

⚠️ **GitHub.com Fallback** ⚠️