STM32 discovery 프로그래밍 - FabLabSeoul/WingProject GitHub Wiki
STM32-discovery 프로그래밍
STM32 Value line discovery 보드용 설명입니다.
이 글을 보기 전에 http://www.signaltech.co.kr/base/img/arm/stm32f103.pdf 문서를 보자.
STM32 Value line discovery 보드는 STM32F100xx 계열의 프로세서를 사용한다. 프로그래밍하기 전에 먼저 기본 레퍼런스 매뉴얼을 확인하자.
레퍼런스 매뉴얼 링크: http://www.st.com/web/catalog/mmc/FM141/SC1169/SS1031/LN775/PF216844#
- Design Resources 탭에서 RM0041 문서를 다운로드 받자.
문서를 찾는 경로는 STM32F100RB search -> Design Resources -> Reference Manual -> RM0041
- STM32-discovery 보드는 STM32F100RB 프로세서를 사용하고 있다.
입출력 처리 (GPIO, General Purpose Input Output)
(다음 내용은 RM0041문서의 99 페이지부터 나온다.)
여기서 설명하는 예제는 STM32\stm32vldiscovery_package\Project\Examples\ 폴더의 GPIOInput, GPIOOutput, GPIOToggle 에 있다.
STM32F100xx 계열의 프로세서로 input/output 을 처리하기 위해서는 각각의 포트에 옵션을 설정해줘야 한다. 포트는 STM32-discovery 에서 GPIOA, GPIOB, GPIOC, GPIOD 로 이름지어졌다. GPIO -> General purpose input output 의 약자이며, 접미사 A,B,C,D 를 합쳐놓았다.
- GPIOA는 PA0 ~ PA15 까지 총 16개 포트
- GPIOB는 PB0 ~ PB15 까지 총 16개 포트
- GPIOC는 PC0 ~ PC15 까지 총 16개 포트
- GPIOD는 PD0 ~ PD2 까지 총 3개 포트 (눈으로 확인한 결과 3개 밖에 없음)
- PC9, PC8 은 STM32 Value line discovery 보드에 led 와 연결되어 있다. 즉 PC8,9번에 출력으로 신호를 보내면 불이 켜진다.
각각의 포트를 제어하기 위해 레지스터에 옵션을 설정한다. 포트 레지스터 종류는 다음과 같다. (GPIOx는 GPIOA ~ GPIOD까지를 나타낸다.)
- GPIOx_CRL : 32bit configuration register low
- GPIOx_CRH : 32bit configuration register high
- GPIOx_IDR : 32bit input data register
- GPIOx_ODR : 32bit output data register
- GPIOx_BSRR : 32bit set/reset register
- GPIOx_BRR : 16bit reset register (소스코드 상에서는 32bit로 처리된다.)
- GPIOx_LCKR : 32bit locking register
st.com에서 제공하는 라이브러리 소스에서 포트 레지스터를 표현하는 구조체는 다음과 같다.
/**
* @brief General Purpose I/O
*/
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
입출력 환경 설정
(다음 내용은 RM0041문서의 110 페이지부터 나온다.)
GPIOx 핀에 입력을 받거나 출력을 주기 위해서는 환경설정을 해야한다. 환경설정에서는 크게 두 가지를 설정한다.
- mode 설정
- input/output (핀 입출력 설정)
- config 설정
- input (핀이 입력일 때 설정) (analog mode, float input, input with pull-up / pull-down)
- output (핀이 출력일 때 설정) (general purpose output push-pull / push-down, alternate function output push-pull / push-down)
간단하게 시작해보자. GPIOA 의 두 번째 핀. 즉, PA1 을 출력 핀으로 설정하고 싶다면, GPIOA 를 가르키는 메모리의 CRL 레지스터의 값을 바꾸면 된다. GPIOA를 가르키는 메모리 주소는 0x40010800 이다. GPIOA->CRL 메모리를 가르키기 위해서는 오프셋주소 0x00 만큼 더해주면 된다. 마찬가지로 GPIOA->CRH 메모리를 접근하기 위해서는 0x04 만큼 더해주면 된다. 그래서 GPIOA->CRH 주소는 0x40010804 이다. 왜냐하면 CRL 레지스터가 4바이트를 차지하기 때문이다. 이렇게 레지스터마다 주소를 설정하는 것이 번거롭기 때문에 구조체 GPIO_TypeDef 를 사용하고 있다.
그렇다고해서 매번 이렇게 메모리 주소로 프로그래밍하게 되면 힘드니까 ST.com에서 제공하는 Stm32-discovery 라이브러리를 사용하자. (http://www.st.com/web/en/catalog/tools/PF257914 에서 STSW-STM32078 을 다운받자.)
가장 간단한 예제인 GPIOToggle 샘플을 실행 시켜보면, (stm32vldiscovery_package\Project\Examples\GPIOToggle\MDK-ARM\GPIOToggle.uvproj 파일을 Keil MDK-ARM 프로그램으로 열어보자.)
#define
으로 선언된 이름으로 GPIOA, GPIOB, GPIOC, GPIOD 등이 눈에 띌 것이다. 메모리주소로 접근하는 것을 편하게 해주는 변수들이다.(사실 변수라고 하기에도 애매하고, 상수라고 하기도 애매하다) GPIOA 포트의 CRL 레지스터를 접근하기 위해서는 GPIOA->CRL 이라고 코딩하면 된다.
(Tip1: MDK-ARM의 소스 편집창에서 GPIOA 문자에 커서를 대고 F12키를 누르면 GPIOA를 선언한 코드로 바로 점프한다. F12키는 변수, 함수등 선언이 필요한 모든 소스에서 동작한다.)
(Tip2: MDK-ARM의 소스 편집창에서 F12키를 누르고 점프한 후, 다시 돌아올 때는 Ctrl + '-' 키를 누르면 된다. 여러번 점프해도 순서대로 돌아온다.)
그래서, PA1 포트를 출력 포트로 쓰기 위해서는 GPIOA->CRL = 0x20
라고 코딩하면 된다. 0x20
의 의미는 다음과 같다.
(CRL 레지스터 설정은 레퍼런스 문서은 RM0041 에 잘 나와 있다. (page 110))
- 1번 핀이므로 CRH가 아니라 CRL에 설정해야 한다.
- 1번 핀이므로 4,5,6,7 번 비트에 값을 설정해야 한다.
- Mode1 설정은 4,5 번 비트 이다.
- max speed 10MHz 로 설정할 것이므로 4,5번 비트는 10(b) 이 되어야 한다.
- CNF1 설정은 6,7 번 비트에 값을 설정해야 한다.
- output mode, general purpose output push-pull 로 설정할 것이므로 6,7번 비트는 00(b)이 되어야 한다.
- 최종 Mode1, CNF1 비트를 합치면 0010(b) 가 된다. 16진수로 0x02 가 된다.
- 1번 포트이므로 옆으로 4비트 시프트해서 0x20이 된다.
- 그래서 최종적으로
GPIOA->CRL = 0x20
으로 프로그래밍 된다.
포트 출력 프로그래밍
(다음 내용은 RM0041문서의 110 페이지부터 나온다.)
포트가 출력으로 지정되어 있고, 해당 포트에 값을 보내고 싶다면, (현재는 On/Off만 할 수 있다.) ODR 레지스터를 설정해야 한다. (이에 대한 내용은 매뉴얼의 112 페이지에 있다.) PA1 번 포트를 On 하고 싶다면 GPIOA->ODR = 0x02
, Off 하고 싶다면 GPIOA->ODR = 0x00
으로 코딩하자.
GPIOA->ODR = 0x02
의미는 다음과 같다.
- PA1번 포트 이고, On 인 상태이므로 00000000 00000010(b) 이다. 16진수로 0x02 가 된다.
다음과 같이 프로그래밍하면 PA1 포트에 3v 가 흐른다.
RCC->APB2ENR = RCC_APB2Periph_GPIOA;
GPIOA->CRL = 0x20;
GPIOA->ODR = 0x02;
#define
된 변수와 stm32-discovery 라이브러리 함수를 이용하면 좀더 명확한 코드가 된다.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIOA->ODR = GPIO_Pin_1;
포트 입력 프로그래밍
(다음 내용은 RM0041문서의 110 페이지부터 나온다.)
포트 입력처리는 출력처리와 비슷한 방식으로 한다. stm32-discovery 패키지 라이브러리에 있는 함수를 사용하면 간단하게 포트 입력 환경설정을 할 수 있다.
다음 코드는 GPIOA의 3번째 핀, 즉, PA2 포트를 입력 포트로 설정하는 코드다.
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
이 코드를 라이브러리를 쓰지않고 매뉴얼대로 프로그래밍한다면 다음과 같다.
GPIOA->CRL = 0x800;
GPIOA->CRL = 0x800;
코드의 의미는 다음과 같다.
- Input with pull-down/pull-up CNFy 설정이기 때문에 10(b)가 된다.
- Input Mode 이기 때문에 00(b)가 된다.
- CNFy + MODEy = 1000(b) = 0x8 이 된다.
- 세 번째 포트이므로 시프트 12 = 4 * 3 비트만큼 하므로 0x800 이 된다.
포트 입출력 프로그래밍
여기까지 되었다면, 입력을 받은 후에 출력 포트로 결과를 보내는 프로그램을 만들어 보자. PA2는 입력포트, PA3는 출력포트로 설정해서 PA2에 3v가 들어오면, 즉, On 상태가 되면, PA3에 3v를 흘려보내게 만들어 보자. 위에 나온 포트 출력, 포트 입력 프로그램을 합쳐 놓았다고 볼 수 있다.
코드는 다음과 같다.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIOA->CRL = 0x2800;
while (1)
{
int n = GPIOA->IDR & GPIO_Pin_2; // Read PA3 On/Off
if (n)
{
GPIOA->ODR = GPIO_Pin_3; // PA4 On
}
else
{
GPIOA->ODR = 0x00; // PA4 Off
}
}
주목해야 할 코드는 GPIOA->CRL = 0x2800;
이다. 0x2800
의 의미는 다음과 같다.
- PA2 가 입력 포트이므로 0x800 이 되어야 한다.
- PA3 가 출력 포트이므로 0x2000 이 되어야 한다.
- PA2 + PA3 환경설정을 합치면 0x2800 이 된다.
- 그러므로 GPIOA->CRL 에는 0x2800 이 저장되어야 한다.
위의 코드를 stm32-discovery 라이브러리를 사용해서 좀더 직관적으로 바꾸면 다음과 같다.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// Setting Pin2 Input Mode
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Setting Pin3 Output Mode
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while (1)
{
int n = GPIOA->IDR & GPIO_Pin_2; // Read PA3 On/Off
if (n)
{
GPIOA->ODR = GPIO_Pin_3; // PA4 On
}
else
{
GPIOA->ODR = 0x00; // PA4 Off
}
}
이 예제는 STM32\stm32vldiscovery_package\Project\Examples\GPIOInput 폴더에 있다.