Repository navigation
Bare metal C Ch12 SPI
한 줄 요약: SPI1을 Master로 설정하고 ADXL345와 통신하는 driver를 구현한다. 현재 기록상 센서 모듈이 없어 실제 센서 응답은 미검증 상태다.
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 GPIOADXL345용으로 코드에서는 CPOL=1, CPHA=1을 설정한다.
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 출력
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를 전부 기록한다.
#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
#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->MODER &= ~(0x3UL << (5 * 2));
GPIOA->MODER |= (0x2UL << (5 * 2));
GPIOA->MODER &= ~(0x3UL << (6 * 2));
GPIOA->MODER |= (0x2UL << (6 * 2));
GPIOA->MODER &= ~(0x3UL << (7 * 2));
GPIOA->MODER |= (0x2UL << (7 * 2));
GPIOA->MODER &= ~(0x3UL << (9 * 2));
GPIOA->MODER |= (0x1UL << (9 * 2)); /* PA9 = CS, 범용출력 */
GPIOA->AFR[0] &= ~(0xFUL << (5 * 4));
GPIOA->AFR[0] |= (0x5UL << (5 * 4));
GPIOA->AFR[0] &= ~(0xFUL << (6 * 4));
GPIOA->AFR[0] |= (0x5UL << (6 * 4));
GPIOA->AFR[0] &= ~(0xFUL << (7 * 4));
GPIOA->AFR[0] |= (0x5UL << (7 * 4));
}
void spi1_config(void)
{
RCC->APB2ENR |= SPI1EN;
SPI1->CR1 |= (1U << 3);
SPI1->CR1 &= ~(1U << 4);
SPI1->CR1 &= ~(1U << 5);
SPI1->CR1 |= (1U << 0); /* CPHA */
SPI1->CR1 |= (1U << 1); /* CPOL */
SPI1->CR1 &= ~(1U << 10);
SPI1->CR1 &= ~(1U << 7);
SPI1->CR1 |= (1U << 2);
SPI1->CR1 &= ~(1U << 11);
SPI1->CR1 |= (1U << 8);
SPI1->CR1 |= (1U << 9);
SPI1->CR1 |= (1U << 6);
}
void spi1_transmit(uint8_t *data, uint32_t size)
{
uint32_t i = 0;
uint8_t temp;
while (i < size)
{
while (!(SPI1->SR & SR_TXE)) { }
SPI1->DR = data[i];
i++;
}
while (!(SPI1->SR & SR_TXE)) { }
while ((SPI1->SR & SR_BSY)) { }
temp = SPI1->DR;
temp = SPI1->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); }
#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
#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(&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);
}
#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
#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->MODER &= ~(0x3UL << (8 * 2));
GPIOD->MODER |= (0x2UL << (8 * 2));
GPIOD->MODER &= ~(0x3UL << (9 * 2));
GPIOD->MODER |= (0x2UL << (9 * 2));
GPIOD->AFR[1] &= ~(0xFUL << 0);
GPIOD->AFR[1] |= (0x7UL << 0); /* PD8 -> AF7 (USART3_TX) */
GPIOD->AFR[1] &= ~(0xFUL << 4);
GPIOD->AFR[1] |= (0x7UL << 4); /* PD9 -> AF7 (USART3_RX) */
RCC->APB1ENR |= USART3EN;
uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);
USART3->CR1 = CR1_TE;
USART3->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);
}
#ifndef SYSTICK_H_
#define SYSTICK_H_
#include <stdint.h>
#include "stm32f4xx.h"
void systick_msec_delay(uint32_t delay);
#endif /* SYSTICK_H_ */
#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 < delay; i++)
{
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL) { }
}
SysTick->CTRL = 0UL;
}
#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, &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] << 8) | rxdata[0]);
y = (int16_t)((rxdata[3] << 8) | rxdata[2]);
z = (int16_t)((rxdata[5] << 8) | rxdata[4]);
printf("X=%d Y=%d Z=%d\r\n", x, y, z);
systick_msec_delay(200);
}
}
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를 분리할지 확인할 필요가 있다.
여기서는 첨부된 실제 코드를 수정하지 않고 그대로 기록한다.
SPI는 Master가 clock을 생성한다.
수신만 할 때도 clock 생성을 위해 dummy transmit이 필요하다.
현재 ADXL345 sensor hardware 검증은 남아 있다.