Embedded System/STM32

[Cortex-M3] GPIO 사용하기

임베지수 2017. 4. 2. 14:51

내가 AVR(ATmega시리즈)을 배울때는 데이터시트 띄어놓고 레지스터보면서 코드를 짰지만 STM32 이놈은..... 너무 많다....

난 항상 새로운걸 배울땐 무지에서 오는 귀찮음 때문에 진도가 안나간다... 친구는 그렇게 하지말고 예제를 보며 역추적하라고 해서 그 방법을 적용해볼까 한다. 먼저 코드부터 살펴봅시다~~

 

#include "stm32f10x.h"
void init_port()
{
	GPIO_InitTypeDef PORTA;
	GPIO_InitTypeDef PORTB;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

	PORTA.GPIO_Pin = 0x0001;
	PORTA.GPIO_Mode = GPIO_Mode_Out_PP;
	PORTA.GPIO_Speed = GPIO_Speed_50MHz;

	GPIO_Init(GPIOA, &PORTA);

	PORTB.GPIO_Pin = 0x0001;
	PORTB.GPIO_Mode = GPIO_Mode_Out_PP;
	PORTB.GPIO_Speed = GPIO_Speed_50MHz;

	GPIO_Init(GPIOB, &PORTB);
}
int main()
{
	unsigned int i;
	init_port();

	while(1)
	{
		GPIO_WriteBit(GPIOA,0x0001,1);
		GPIO_WriteBit(GPIOB,0x0001,0);
		for(i=0;  i<1000000; i++);

		gpio_writebit(GPIOA,0x0001,0);
		gpio_writebit(GPIOB,0x0001,1);
		for(i=0;  i<1000000; i++);
	}
}

 

일단 init_port()라는 함수부터 분석해보자.

 GPIO_InitTypeDef PORTA;
 GPIO_InitTypeDef PORTB;

 

GPIO_initTypeDef란 뭘까? 헤더파일을 보면 구조체로 만들어져있었다. 구조체 안을 보면

 

GPIO의 몇 번 핀을 사용할지, GPIO속도, GPIO모드를 설정하기 위해 필요한 것들이 들어있다.

GPIO_Pin에는 16비트 숫자를 넣어주면 된다. 0번 비트를 사용하려면 0x0001, 1번 비트를 사용하려면 0x0002....이런식으로.. STM32는 AVR과 달리 한 포트가 8개의 핀이 아니라 16개의 핀으로 구성되어있다. 이진수로 표현하면 이해하기 쉽다.

 0번 핀 활성화: 0b0000000000000001;
 1번 핀 활성화: 0b0000000000000010;
 2번 핀 활성화: 0b0000000000000100;
         .
         .
         .
 15번 핀 활성화: 0b1000000000000000;

 

만약 0,1,2번 핀만 활성화하고 싶으면 0x0001, 0x0002, 0x0004 값을 더한 값을 대입하면된다. 즉 0x0007(0b0000000000000111)을 대입해주는 식이다.

 

GPIO_Speed는 GPIO의 반응속도를 뜻하는 듯하다. GPIO_Mode는 말 그대로 모드...입력으로 사용할지 출력으로 사용할지, Push-pull 혹은 Open-drain으로 사용할지 등을 결정해준다.

 

어쨌든 구조체 변수를 선언해주고 나서 RCC_APB2PeriphClockCmd() 라는 함수가 나온다. 이건 사용하고자 하는 포트에 클럭신호를 넣어준다고 한다. 이걸 안해주면 동작하지 않는다...또르르...

 

인자값을 살펴보면 사용할 포트를 입력해주고, ENABLE을 넣어주면 된다. 사용할 포트도 비트연산을 이용하여 한방에 여러 포트를 켤 수 있다. RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB 이런 식으로...

 

좀 더 자세히 살펴볼까...?

 

RCC_APB2PeriphClockCmd() 함수를 이용하면 RCC_APB2ENR 라는 레지스터에 값을 써주는데 위에서 빨간 네모 해놓은 부분을 SET 해주면 PORTA를 사용할 수 있다. 그래서 인자값으로 들어가는 RCC_APB2Periph_GPIOA 이놈이 0x00000004로 정의되어있음을 확인할 수 있다.

 

 

지금까지 포트에 클럭을 넣어주는것까지 했다. 이제 아까 선언한 구조체 변수로 자세한 핀 설정을 해주어야한다. 먼저

 

PORTA.GPIO_Pin = 0x0001;

이것은 위에서 설명했다. 포트의 몇 번 핀을 사용할것이냐 해주는 것이다. 0x0001을 넣었으므로 0번 핀을 사용한다는 의미가 되겠다.

 

PORTA.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Mode_Out_PP의 의미는 출력모드로 사용하는데 Push-Pull 형태를 가지겠다는 설정이다.

 

PORTA.GPIO_Speed = GPIO_Speed_50MHz;

GPIO의 속도를 50MHz로 설정해주는 것이다. 50MHz외에 2MHz, 10MHz도 설정해줄 수 있는데 빠른게 좋겠지?

 

 

이것도 좀 더 자세히 살펴보자. 일단 GPIO 설정에 관여하는 레지스터다.

 

 

 

레지스터 이름에서 알 수 있듯이 GPIO를 설정해주는 핀이다.  GPIOx_CRL, GPIOx_CRH 두 개가 있는데 STM32의 경우 한 포트가 16개의 핀을 가진다고 했으니까 하위 8개, 상위 8개 나눈것이다. 별다른 의미는 없다. 내부를 살펴보면 MODEx[1:0], CNFx[1:0] 이렇게 두 개가 한 쌍인데 각각 하나의 핀에 관련되어있다. MODE0, CNF0은 0번 핀의 설정을, MODE1, CNF1은 1번 핀의 설정을 해주는 식이다.

 

아래 표는 각 상태에 따른 설정을 나타내고 있다.

 

실제로 레지스터에 값을 써주는 함수는 GPIO_Init()이다. 첫 번째 인자값을 살펴보면 어떤 GPIO를 사용할 것이냐가 들어가고 두 번째 인자값에는 아까 선언한 구조체 변수의 주소가 들어간다. 그럼 구조체에 담긴 정보를 불러와서 Mode의 경우 출력에 Push-Pull형태라고 했으니 CNF[1:0]은 00으로, Speed는 50MHz로 했으니 Mode[1:0]는 11로 레지스터 값을 설정해준다.

 

GPIO_init() 함수까지 끝났다. 이제 사용만 하면 된다. 내가 원하는 비트에 HIGH, LOW를 출력하고 싶으면 아래 함수들을 잘 사용하면 된다.

  void GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) // 포트의 해당 핀을 Clear한다.
  void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) // 포트의 해당 핀을 Set한다.
  void GPIO_Write(GPIO_TypeDef *GPIOx, uint16_t PortVal) // 포트에 해당 값을 쓴다.
  void GPIO_WriteBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, BitAction BitVal) // 포트의 특정 비트에 0 또는 1을 쓴다.

 

위에서 나열한 4개 함수 중에 GPIO_WriteBit() 함수를 까서 보자.

 

if문에 BSRR, BRR이라는 레지스터가 있다. BSRR은 Bit Set/Reset Register의 약자이다. Set과 Reset 둘 다 관여한다.

BRR은 Bit Reset Register의 약자이다. Reset에만 관여한다.

 

 

 

왜 BSRR하나만 안쓰고 따로 만들어 놨을까... 내가 생각하기엔 연산량을 줄이기 위해서가 아닐까... 같은 인덱스를 사용한다고 가정하면 BSRR을 사용해서 0번 핀을 켰다가 끌 때 BSRR=0x00000001, BSRR=0x00000001+16 이렇게 16을 더해줘야 하니까... 아님 말고ㅋㅋㅋ 어쨌든 BSRR의 하위 15비트 중에 한 비트가 SET이 되면 해당 핀은 SET되고 BRR의 한 비트가 SET되면 해당핀은 CLEAR된다고 한다.

 

아래는 GPIO를 이용하여 간단하게 LED를 깜빡이는 소스를 STM32VLDISCOVERY보드에 플래시해본 영상이다.