프로젝트 생성후 컴파일이 완료되었으나, 각 Device를 컨트롤하기 위해서는 Driver 코드들을 작성해야 한다.
그러나 다행(?)스럽게도 ST 에서는 Standard Peripheral Library라는 코드를 배포하고 있다.
이를 다운 받아서 emIDE 프로젝트에 추가하여 ARM GCC로 빌드를 하려고 한다.
Step0] Project 생성 및 Standard Peripheral Library 준비
1) Project 생성 : http://lifeseed.tistory.com/102
2) STM32F10x Standard Peripheral Library (STSW-STM32054)
아래 링크를 클릭하면 다운 받을 수 있다.
다운 받은 파일의 압축을 풀자. 다음과 같은 디렉토리 구조를 확인할 수 있다.
각 폴더에는 다음과 같은 파일들이 존재한다.
- Libraries :: CMSIS 및 ST에서 제공하는 각 IP의 Driver 파일
- Project :: 각종 예제 파일
- Utilities :: 각 Evaluation Board별 예제 및 환경 파일
Step1] Libraries 폴더 Workspace로 복사
우선 다운 받은 Standard Peripheral Libraries의 압축을 풀면 Libraries라는 폴더가 있다.
이 폴더를 프로젝트 workspace 폴더로 복사하자.
(Project 폴더로 복사할 수도 있지만 Library 파일들은 수정없이 공통으로 사용가능하므로, Workspace폴더로 복사하도록한다.)
Libraries폴더를 살펴 보면 CMSIS 와 STM32F10x_StdPeriph_Driver 두개의 폴더로 이루어져 있으며, CMSIS는 다시 CM3의 CoreSupport 와 DeviceSupport로 이루어 져 있다.
프로젝트에 추가될 파일은 크게 CMSIS의 CM3 CoreSupport 라는 폴더와 STM32F10x_StdPeriph_Driver 라는 폴더를 그대로 추가할 것이며, CMSIS의 CM3 DeviceSupport 에서 Start Up시 필요한 일부 파일을 Project 폴더로 복사해서 사용할 것이다.
Step2] Start.S 파일에 Interrupt Handler 추가
ST에서 제공하는 Library에는 ARM용 gcc 컴파일러를 지원하는 Start up assemble code가 없다.
그래서 Library에 포함된 다른 컴파일러들을 위한 Start up 코드를 참조하여, 프로젝트 생성시 제공되는 startup.S을 수정한다.
사실 추가될 내용은 interrupt handler들에 대한 정의뿐이며, 이와 관련된 코드 수정은 어렵지 않다.
.\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\ 에는 start up파일들이 여럿 존재하는데, 현재 사용하는 CPU인 STM32F103RB가 속하는 Medium-density device용 start up 코드인 startup_stm32f10x_md.s 를 참조한다.
왼쪽이 startup_stm32f10x_md.s 파일이며, 오른쪽이 emIDE에서 제공하는 startup.S 파일이다.
즉 왼쪽의 External Interrupt Handler 들을 오른쪽 코드의 문법과 동일하게 수정한 후 추가한다.
당연히 .long Default_Handler 는 제거하여 준다.
.long WWDG_IRQHandler /* 0: Watchdog Timer */
.long PVD_IRQHandler /* 1: PVD through EXTI Line detection */
.long TAMPER_IRQHandler /* 2: tamper */
.long RTC_IRQHandler /* 3: Real Time Clock */
.long FLASH_IRQHandler /* 4: flash global */
.long RCC_IRQHandler /* 5: RCC global */
.long EXTI0_IRQHandler /* 6: EXTI Line0 */
.long EXTI1_IRQHandler /* 7: EXTI Line1 */
.long EXTI2_IRQHandler /* 8: EXTI Line2 */
.long EXTI3_IRQHandler /* 9: EXTI Line3 */
.long EXTI4_IRQHandler /* 10: EXTI Line4 */
.long DMA1_Channel1_IRQHandler /* 11: DMA1 Channel1 global */
.long DMA1_Channel2_IRQHandler /* 12: DMA1 Channel2 global */
.long DMA1_Channel3_IRQHandler /* 13: DMA1 Channel3 global */
.long DMA1_Channel4_IRQHandler /* 14: DMA1 Channel4 global */
.long DMA1_Channel5_IRQHandler /* 15: DMA1 Channel5 global */
.long DMA1_Channel6_IRQHandler /* 16: DMA1 Channel6 global */
.long DMA1_Channel7_IRQHandler /* 17: DMA1 Channel7 global */
.long ADC1_2_IRQHandler /* 18: ADC1 and ADC2 global */
.long USB_HP_CAN_TX_IRQHandler /* 19: CAN1 TX */
.long USB_LP_CAN_RX0_IRQHandler /* 20: CAN1 RX0 */
.long CAN_RX1_IRQHandler /* 21: CAN1 RX1 */
.long CAN_SCE_IRQHandler /* 22: CAN1 SCE */
.long EXTI9_5_IRQHandler /* 23: EXTI Line5-9 */
.long TIM1_BRK_IRQHandler /* 24: TIM1 Break */
.long TIM1_UP_IRQHandler /* 25: TIM1 Update */
.long TIM1_TRG_COM_IRQHandler /* 26: TIM1 Trigger and Commutation */
.long TIM1_CC_IRQHandler /* 27: TIM1 Capture Compare */
.long TIM2_IRQHandler /* 28: Timer2 global */
.long TIM3_IRQHandler /* 29: Timer3 global */
.long TIM4_IRQHandler /* 30: Timer4 global */
.long I2C1_EV_IRQHandler /* 31: I2C1 event */
.long I2C1_ER_IRQHandler /* 32: I2C1 error */
.long I2C2_EV_IRQHandler /* 33: I2C2 event */
.long I2C2_ER_IRQHandler /* 34: I2C2 error */
.long SPI1_IRQHandler /* 35: SPI1 global */
.long SPI2_IRQHandler /* 36: SPI2 global */
.long USART1_IRQHandler /* 37: USART1 global */
.long USART2_IRQHandler /* 38: USART2 global */
.long USART3_IRQHandler /* 39: USART3 global */
.long EXTI15_10_IRQHandler /* 40: EXTI Line10-15 */
.long RTCAlarm_IRQHandler /* 41: RTC alarm through EXTI line */
.long USBWakeUp_IRQHandler /* 42: USB OTG FS Wakeup through EXTI line */
.size __isr_vector, . - __isr_vector
그리고 Compile Error를 제거하기위해, 각 Interrupt Handler들을 Weak속성으로 Define해둔다.
def_default_handler HardFault_Handler
def_default_handler MemManage_Handler
def_default_handler BusFault_Handler
def_default_handler UsageFault_Handler
def_default_handler SVC_Handler
def_default_handler DebugMon_Handler
def_default_handler PendSV_Handler
def_default_handler SysTick_Handler
def_default_handler Default_Handler
def_default_handler WWDG_IRQHandler /* 0: Watchdog Timer */
def_default_handler PVD_IRQHandler /* 1: PVD through EXTI Line detection */
def_default_handler TAMPER_IRQHandler /* 2: tamper */
def_default_handler RTC_IRQHandler /* 3: Real Time Clock */
def_default_handler FLASH_IRQHandler /* 4: flash global */
def_default_handler RCC_IRQHandler /* 5: RCC global */
def_default_handler EXTI0_IRQHandler /* 6: EXTI Line0 */
def_default_handler EXTI1_IRQHandler /* 7: EXTI Line1 */
def_default_handler EXTI2_IRQHandler /* 8: EXTI Line2 */
def_default_handler EXTI3_IRQHandler /* 9: EXTI Line3 */
def_default_handler EXTI4_IRQHandler /* 10: EXTI Line4 */
def_default_handler DMA1_Channel1_IRQHandler /* 11: DMA1 Channel1 global */
def_default_handler DMA1_Channel2_IRQHandler /* 12: DMA1 Channel2 global */
def_default_handler DMA1_Channel3_IRQHandler /* 13: DMA1 Channel3 global */
def_default_handler DMA1_Channel4_IRQHandler /* 14: DMA1 Channel4 global */
def_default_handler DMA1_Channel5_IRQHandler /* 15: DMA1 Channel5 global */
def_default_handler DMA1_Channel6_IRQHandler /* 16: DMA1 Channel6 global */
def_default_handler DMA1_Channel7_IRQHandler /* 17: DMA1 Channel7 global */
def_default_handler ADC1_2_IRQHandler /* 18: ADC1 and ADC2 global */
def_default_handler USB_HP_CAN_TX_IRQHandler /* 19: CAN1 TX */
def_default_handler USB_LP_CAN_RX0_IRQHandler /* 20: CAN1 RX0 */
def_default_handler CAN_RX1_IRQHandler /* 21: CAN1 RX1 */
def_default_handler CAN_SCE_IRQHandler /* 22: CAN1 SCE */
def_default_handler EXTI9_5_IRQHandler /* 23: EXTI Line5-9 */
def_default_handler TIM1_BRK_IRQHandler /* 24: TIM1 Break */
def_default_handler TIM1_UP_IRQHandler /* 25: TIM1 Update */
def_default_handler TIM1_TRG_COM_IRQHandler /* 26: TIM1 Trigger and Commutation */
def_default_handler TIM1_CC_IRQHandler /* 27: TIM1 Capture Compare */
def_default_handler TIM2_IRQHandler /* 28: Timer2 global */
def_default_handler TIM3_IRQHandler /* 29: Timer3 global */
def_default_handler TIM4_IRQHandler /* 30: Timer4 global */
def_default_handler I2C1_EV_IRQHandler /* 31: I2C1 event */
def_default_handler I2C1_ER_IRQHandler /* 32: I2C1 error */
def_default_handler I2C2_EV_IRQHandler /* 33: I2C2 event */
def_default_handler I2C2_ER_IRQHandler /* 34: I2C2 error */
def_default_handler SPI1_IRQHandler /* 35: SPI1 global */
def_default_handler SPI2_IRQHandler /* 36: SPI2 global */
def_default_handler USART1_IRQHandler /* 37: USART1 global */
def_default_handler USART2_IRQHandler /* 38: USART2 global */
def_default_handler USART3_IRQHandler /* 39: USART3 global */
def_default_handler EXTI15_10_IRQHandler /* 40: EXTI Line10-15 */
def_default_handler RTCAlarm_IRQHandler /* 41: RTC alarm through EXTI line */
def_default_handler USBWakeUp_IRQHandler /* 42: USB OTG FS Wakeup through EXTI line */
.end
아직은 Libraries 폴더를 프로젝트에 추가하지 않은 상태이므로, Build를 수행하면 프로젝트 생성시 추가되는 코드들만 compile된다. 에러가 없이 Compile되어야 한다.
Step3] 프로젝트에 Libraries 추가
우선 Libraries에 있는 두개의 폴더를 추가하도록 하자.
프로젝트창에서 프로젝트 명을 우클릭하여 Add files recursively... 를 선택하자.
Libraries 의 STM32F10x_StdPeriph_Driver를 선택하고 OK를 눌러 코드들을 추가한다.
그리고 다시 Add files recusively를 눌러 Libraries > CMSIS > CM3 의 CoreSupport 폴더를 선택하고 OK를 눌러 파일들을 추가한다.
Step 4] Include path 추가
메뉴의 Project > Build Option을 실행한다.
Search directories 의 Complier 탭에서 Add 버튼을 눌러 추가된 Libraries의 Header 파일이 있는 폴더의 PATH를 추가한다.
( emIDE는 Default로 프로젝트 폴더의 Inc파일을 Include하도록 되어 있다.)
..\Libraries\STM32F10x_StdPeriph_Driver\inc
..\Libraries\CMSIS\CM3\CoreSupport
..\Libraries\STM32F10x_StdPeriph_Driver\inc
Step5] Device Support 파일 추가
1) Device Support file 추가
.\workspace\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\ 폴더로 이동하면
stm32f10x.h
system_stm32f10x.c
system_stm32f10x.h
세개의 파일과 startup 폴더가 존재한다.
여기서 위 세개의 파일을 프로젝트 폴더로 복사한다.
.h파일은 Inc 폴더로 복사하고 .c 파일은 Src 폴더로 복사하자.
그리고 Add files 명령을 이용하여 프로젝트에 복사된 3개의 파일을 추가한다.
2) example file 추가
.\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\GPIO\IOToggle\ 폴더에서 Interrupt를 처리하기 위한 stm32f10x_it.c/h 파일과 Library들의 header를 include하는 stm32f10x_conf.h 파일을 프로젝트 폴더로 복사하자.
물론 .h파일은 Inc 폴더로 복사하고 .c 파일은 Src 폴더로 복사한다.
Step6] STM32 Build Define 추가
stm32f10x.h 파일을 include 하면 library에서 제공하는 api들을 사용할 수 있다.
단 이를 위해 CPU Type의 Define 및 library들의 header를 include하는 stm32f10x_conf.h를 stm32f10x.h에 포함시키기 위한 define이 추가되어야 한다.
메뉴의 Project > Build Option을 실행한다.
Compiler Settins 탭 내부의 #define 탭을 클릭한 후 Edit Box 창에 STM32F103RB 칩에 해당되는 STM32F10X_MD 및 stm32f10x_conf.h을 include USE_STDPERIPH_DRIVER를 define 탭에 추가한다.
이제 준비가 되었다 Build 버튼을 눌러 빌드를 수행하보자.
어라... 추가한 파일 system_stm32f10x.c 에서 에러가 발생한다.
기존에 프로젝트 생성시 만들어진 main.c 에 있는 SystemInit()라는 함수가 중복으로 사용되었기 때문에 발생하는 에러이다.
우리는 당연히 STM32F10x의 SystemInit() 함수를 사용할 것이므로 main.c에 있는 SystemInit 함수를 삭제한다.
다시 빌드하면... 두둥... 빌드가 완성되었다.
이 것으로 ST에서 제공하는 Standard Peripheral Library를 사용할 준비가 다 되었다.
주저리 주저리 길게 설명하였지만 요약하면 다음과 같다.
1. Startup코드에 External Interrupt Handler들을 추가한다. (library에 포한된 startup_md.s를 참조)
2. CMSIS CoreSupport 파일 2개와 STM32F10x_StdPeriph_Driver를 프로젝트에 등록시키고 빌드 속성에 Include path를 추가한다.
3. CMSIS 의 DeviceSupport에서 stm32f10x.h 를 포함한 3개의 파일과, Example파일에서 stm32f10x_conf.h를 포함한 3개의 파일을 프로젝트 폴더로 복사후 등록한다.
4. 빌드속성에서 STM32F10X_MD 및 USE_STDPERIPH_DRIVER 를 추가한다.
5. main.c에서 SystemInit() 함수를 제거한다.
지금의 과정을 반복하기가 상당히 까다롭다고 생각되면 생성된 코드를 Skeleton코드로 복사해두고 필요시마다 사용하면 된다.