이번에는 앞서 만들어본 LED ON/OFF를 Low Level로 ST의 hal_drive 라이브러리를 사용하지 않고 직접 레지스터를 설정해 만들어 보겠다.
STM32F411RE의 Block Diagram이다. 제일 상단에 Core(ARM Cortex-M4 CPU)가 있고, 다양한 bus들과 peripheral들이 얽혀있다.
512KB Flash(Instruction Memory) : 기계어가 저장
128KB SRAM(Data Memory) : data가 저장
AHB, APB1, APB2
Bridge : AHB to APB
CPU입장에서 메모리와 peripheral 등 나머지 모두에 메모리 주소를 할당해 접근하는 방식이다.
CPU는 broadcasting방식으로 data와 address, signal을 bus로 뿌린다. 이때 어떤 하드웨어에 주는것인지 알기 위해 디코더를 통해 선택된 하드웨어만 CPU가 보낸 데이터를 받는다.
프로젝트에서는 8MHz HSI를 사용한다.
datasheet를 보면 AHB1의 memory address에서 RCC의 주소를 확인할 수 있다.
HSI 사용
Clock Control 레지스터와 Clock Enable 레지스터를 통해 GPIOA에 clk공급해주는 RCC설정을 low-level로 코딩했다.
#define AHB1_BASE 0x40020000 //AHB1의 base address
#define RCC_BASE (AHB1_BASE + 0x3800) // RCC의 base address
#define RCC_CR ((volatile unsigned int *)(RCC_BASE + 0x00)) // 최적화할 수 없는 4byte의 크기를 가지는 RCC_CR주소
#define RCC_AHB1ENR ((volatile unsigned int *)(RCC_BASE + 0x30)) // offset값인 0x30을 더해준다.
void RCC_Init()
{
// RCC Clock Control Register설정, 어차피 디폴트값이므로 설정 안해도됨
*RCC_CR |= (1<<0); // 0x40023800주소 값에 0번 bit를 1 셋팅
*RCC_CR &= ~(1<<16); // 0x40023800주소 값에 16번 bit를 0 셋팅
// RCC AHB1 Clock Enable, GPIOA GPIOC clock enable
*RCC_AHB1ENR |= (1<<0)|(1<<2); // GPIOA GPIOC clock enable, 0번 2번 bit를 1 셋팅
}
(volatile unsigned int *)를 사용하는 이유는 0x40020000와 같이 쓰면 숫자와 주소를 구분할 수 없기 때문에 32bit size를 가지는 주소를 표현하기 위해 포인터로 casting한다.
GPIO와 관련된 레지스터를 A,B,C... 각각 설정해주기 번거롭기 때문에 구조체를 사용해 정의하면 편리하게 사용할 수 있다.
// GPIO의 레지스터들의 주소 범위가 4byte씩 offset되있다.
// 4byte간격으로 연속적으로 memory에 할당된 사용자 정의 자료형
typedef struct{
volatile unsigned int MODER;
volatile unsigned int OTYPER;
volatile unsigned int OSPEEDR;
volatile unsigned int PUPDR;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
volatile unsigned int LCKR;
volatile unsigned int AFRL;
volatile unsigned int AFRH;
}GPIO_TypeDef;
#define GPIOA_BASE (AHB1_BASE + 0x0000)
#define GPIOB_BASE (AHB1_BASE + 0x0400)
#define GPIOC_BASE (AHB1_BASE + 0x0800)
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) // GPIOA는 GPIOA_BASE부터 GPIO_TypeDef만큼 크기의 주소
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
void GPIO_Init()
{
// GPIO A5, output, push-pull, no pullup/pulldown, High speed
//*GPIOA_MODER |= (1<<10); // GPIO MODE Register, 5pin output
//*GPIOA_OSPEEDR |= (1<<11)|(1<<10); // output high speed
GPIOA->MODER |= (1<<10); // 5번핀을 output으로
GPIOA->OSPEEDR |= (1<<11)|(1<<10); // 5핀 output high speed
GPIOC->MODER |= (1<<20)|(1<<24); // 10번, 12번핀을 output으로
GPIOC->OSPEEDR |= (1<<20)|(1<<21)|(1<<24)|(1<<25); // 10,12핀 output high speed
// OTYPER:push-pull, PUPDR:No pullup/pulldown 모두 디폴트로 설정되있음
// Button을 사용하기 위해 GPIOC 2번 3번 버튼도 input mode로 해주려고 했지만
// 이미 디폴트값(reset state)으로 설정되있음
}
MCU의 전원과 다른 크기의 전원을 사용해야되는 경우, 혹은 MCU의 전원을 너무 많이 사용하는 경우는 Open-Drain형태를 사용하는 것이 좋다.
GPIOC의 2핀에 pull-down버튼을 연결하고, GPIOC의 10, 12번 핀에 LED를 연결한다.
typedef struct{
volatile unsigned int MODER;
volatile unsigned int OTYPER;
volatile unsigned int OSPEEDR;
volatile unsigned int PUPDR;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
volatile unsigned int LCKR;
volatile unsigned int AFRL;
volatile unsigned int AFRH;
}GPIO_TypeDef;
#define AHB1_BASE 0x40020000
#define RCC_BASE (AHB1_BASE + 0x3800)
#define RCC_CR ((volatile unsigned int *)(RCC_BASE + 0x00))
#define RCC_AHB1ENR ((volatile unsigned int *)(RCC_BASE + 0x30))
#define GPIOA_BASE (AHB1_BASE + 0x0000)
#define GPIOC_BASE (AHB1_BASE + 0x0800)
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) // GPIOA 구조체의 base주소
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIO_SET 1
#define GPIO_RESET 0
void delay(unsigned int times) // 강제로 clock을 소모
{
unsigned int temp = times * 1000;
while (temp) temp--;
}
void RCC_Init()
{ // RCC Clock Control Register
*RCC_CR |= (1<<0);
*RCC_CR &= ~(1<<16);
// RCC AHB1 Clock Enable, GPIOA GPIOC clock enable
*RCC_AHB1ENR |= (1<<0)|(1<<2);
}
void GPIO_Init()
{
GPIOA->MODER |= (1<<10);
GPIOA->OSPEEDR |= (1<<11)|(1<<10);
GPIOC->MODER |= (1<<20)|(1<<24);
GPIOC->OSPEEDR |= (1<<20)|(1<<21)|(1<<24)|(1<<25);
}
void sysInit() // 전체 system init
{
RCC_Init();
GPIO_Init();
}
void GPIO_write(GPIO_TypeDef *GPIOx, int gpio_pin, int state)
{
if (state) { // SET이면 LED_ON
GPIOx->ODR |= (1<<gpio_pin);
}
else{ // RESET이면 LED_OFF
GPIOx->ODR &= ~(1<<gpio_pin);
}
}
int GPIO_read(GPIO_TypeDef *GPIOx, int gpio_pin)
{
int ret = 0;
if (GPIOx->IDR & (1<<gpio_pin)) { // pull-down 버튼
ret = 1; // 눌렀을 때
}
else {
ret = 0; // 평시
}
return ret;
}
int main()
{
sysInit();
int state;
while(1)
{
state = GPIO_read(GPIOC, 2);
if (state) { // SET이면 LED_ON
GPIO_write(GPIOA, 5, GPIO_SET);
GPIO_write(GPIOC, 10, GPIO_SET);
GPIO_write(GPIOC, 12, GPIO_SET);
}
else { // RESET이면 LED_OFF
GPIO_write(GPIOA, 5, GPIO_RESET);
GPIO_write(GPIOC, 10, GPIO_RESET);
GPIO_write(GPIOC, 12, GPIO_RESET);
}
delay(300);
}
return 0;
}
I/O핀을 read할때와 같이 masking을 이용해 확인할 때는 masking된 결과가 1이 아닐 수도 있기 때문에 0을 체크하거나 주의가 필요하다.
서로 다른 컴퓨터에서도 프로젝트파일을 가지고 있으면 import를 이용해 cube-ide에서 계속해서 작업할 수 있다.
File->import->Existing Projecets into Workspace-> Next
프로젝트 경로를 지정해주고 Finish