ARM Low-Level GPIO

Park SeungChan·2024년 4월 4일
0

ARM

목록 보기
1/7

이번에는 앞서 만들어본 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가 저장

  • D(Data)-BUS : data가 있는 ram과 연결
  • I(Instruction)-BUS : 코딩한 기계어가 저장된 flash와 연결
  • S(System)-BUS : peripheral와 연결

AHB, APB1, APB2
Bridge : AHB to APB

Memory Mapped I/O

CPU입장에서 메모리와 peripheral 등 나머지 모두에 메모리 주소를 할당해 접근하는 방식이다.
CPU는 broadcasting방식으로 data와 address, signal을 bus로 뿌린다. 이때 어떤 하드웨어에 주는것인지 알기 위해 디코더를 통해 선택된 하드웨어만 CPU가 보낸 데이터를 받는다.

RCC(Reset & Clock Control)

  • 프로젝트를 만들면 우선 RCC설정을 해줘야한다.
  • Oscillator에서 clk이 만들어지고 RCC에 입력되어 제어된다.
  • RCC는 하드웨어마다 각각의 사용 유무에 따라 clock 공급을 선택하고 분배할 수 있다 -> 저전력 동작
  • PLL회로를 통해 clk주파수가 증폭될 수 있다.
  • HSI(High Speed Internal) : 내부 clock
  • HSE(High Speed External) : 외부 clock, 안정적으로 동작, X-tal
  • LSE(Low Speed External) : RTC에서 사용

프로젝트에서는 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

  • GPIOA의 5번pin(board 내부 LED), GPIOC의 10,12번pin을 output으로 설정
  • GPIOC의 2번pin을 input으로 설정

  • pushpull type을 사용하므로 default

  • output speed는 L->H, H->L로 변하는 속도
  • 모든 output speed를 High(11)로 설정

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)으로 설정되있음

}

Push-Pull / Open-Drain

  • Push-Pull output type은 MCU 내부의 전원을 사용한다.
  • Open-Drain output type은 MCU 외부의 전원을 사용한다.

MCU의 전원과 다른 크기의 전원을 사용해야되는 경우, 혹은 MCU의 전원을 너무 많이 사용하는 경우는 Open-Drain형태를 사용하는 것이 좋다.

Button으로 LED On/Off

GPIOC의 2핀에 pull-down버튼을 연결하고, GPIOC의 10, 12번 핀에 LED를 연결한다.

  • button은 push button(tact switch)를 사용

  • IDR로 버튼으로 입력된 데이터의 상태(1-on,0-off)를 확인 - pulldown
  • ODR로 핀에 출력을 결정
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 방법

서로 다른 컴퓨터에서도 프로젝트파일을 가지고 있으면 import를 이용해 cube-ide에서 계속해서 작업할 수 있다.

File->import->Existing Projecets into Workspace-> Next

프로젝트 경로를 지정해주고 Finish

profile
RTL Circuit Design & Verification

0개의 댓글