[HARMAN] 세미콘 아카데미/Embedded

[Harman 세미콘 아카데미] Day_91(Embedded)

uiop1716 2025. 6. 23. 09:08

저번주에 이어 FND를 Control 해보겠습니다.

Watch와 Stopwatch의 핵심은 정확한 시간이기 때문에, Timer Counter Peripheral으로 1ms마다 Interrupt 발생을 통해 시간을 잽니다. 

 

인터럽트의 동작 방식을 확인하면 Instruction Memory에 인터럽트용 전용 주소가 있고, 그 외에는 User Code가 들어가게 됩니다. 그래서, User Code를 수행하다가 Interrupt가 발생하면, PC에 현재 위치를 임시로 저장하고 해당 Interrupt의 위치에 접근하여 저장되어 있는 "함수 주소"를 받아 다시 이동하여 수행합니다.

User Code -> 인터럽트 발생 -> 해당 인터럽트 주소 접근 -> 내부에 저장된 함수 주소 이동 -> 함수 실행 -> 복귀

Instruction Memory

 

 

 

아래의 사이트에서 Cube IDE에서 활용할 개발 도구를 다운받아 줍니다.

https://www.st.com/en/development-tools/stm32cubeclt.html

 

STM32CubeCLT - STMicroelectronics

STM32CubeCLT - Toolset for third-party integrated development environment (IDE) providers, allowing the use of STMicroelectronics proprietary tools within their own IDE frameworks, STM32CubeCLT-DEB, STM32CubeCLT-RPM, STM32CubeCLT-Win, STM32CubeCLT-Lnx, STM

www.st.com

 

 

 

이제, 이를 활용해서 저번에 과제로 한 Stopwatch, Watch를 구현하고 Compile되는 과정에 대해 알아보겠습니다. 기본적인 동작은 아래의 그림과 같습니다. 두개의 모드를 버튼을 통해 스위칭하고, Stopwatch에서는 버튼을 통해 RUN / Stop/ Clear를 진행합니다.

시스템 동작

 

 

이전까지는 BareMetal로 프로젝트를 구성하였지만, 이번에는 STM32의 툴을 활용하여 먼저 clock을 설정해 줍니다. 외부의 8Mhz를 입력받고 내부의 100Mhz를 만들어 줍니다.

RCC Setting

 

 

아래는 버튼 입력을 위해 PB5, PB3, PA10은 Input으로 나머지는 FND 출력을 위해 Output으로 설정해 줍니다.

GPIO Pinout

 

 

 

Timer의 TIM2 Interrupt를 활용해 prescaler = 16 - 1, Counter Period = 1000 -1을 설정하여 1ms를 만들어 줍니다. 

TIM2 Setting

 

 

Project Manager -> Code Generator -> 첫번째꺼 설정 : 파일을 한번에 말고 따로 생성

 

 

 

Layer를 구성하면 아래의 그림과 같습니다. HAL Driver를 통해 여러 Driver를 관리하고 함수들을 쉽게 사용할 수 있습니다

Layer 구성도

 

 

HAL_TIM_PeriodElapsedCallback : 타이머 인터럽트를 발생 시 사용되는 사용자 콜백 (인터럽트 함수를 불러와 사용) 

Callback 활용 FND 출력

 

 

프로젝트 폴더 -> Debug -> CMD 

make clean : 이전 빌드 결과물 삭제

make all : .c파일을 자동으로 컴파일

 

 

아래의 CMD 명령어를 통해 ap_main.o라는 메모리가 차지 되는 것을 보면, .text, .data, .bss라는 심볼(섹션)을 지니는 것을 볼 수 있습니다. 즉, obj 파일은 섹션의 모둠이고 linker가 주소를 할당해 줍니다.

Memory map

 

 

 

1. objdump : 컴파일된 실행 파일을 Disassembly어로 변환

 

 

2.  -nm -nS : 함수명, 변수명을 심볼(T = .text, B = .bss)로  출력

 

 

 

컴파일 : .c파일  -> .obj 파일로 변환 (Machine Code 만들기 but 주소 할당 X)

Linker : .obj파일들을 묶어 주소 할당  ->  .elf 실행파일 생성 

 

 

Compiler가 색션 정보를 알려줍니다 (주소 할당 X)  -> 아래의 그림에서 .text 라는 색션으로 ap_Init을 선언해 준 것 

Compiler의 Section 설정 코드

 

 

Linker가 주소를 할당하기 위해 Memory Section 정보가 있는 Script 파일을 Linker에게 알려줘야 한다. STM32F411RETX_FLASH.ld 파일이 Linker Script 파일로 Linker에게 주소를 알려줍니다.

 

 

 

STM32F411RETX_FLASH.ld 내부를 보면 RAM과 Flash Memory의 저장 공간에 대한 시작 주소, 크기 등의 정보가 존재합니다. 처음 시작하면 Reset_Handler라는 Program의 시작점을 통해 Linker Script를 수행하게 됩니다.

 

 

그림으로 나타낸 모습

 

 

Sections : obj파일 내부 Section 정보를 Memory에 순서대로 할당 

해석 - isr_vector;  색션은

        -> FLASH;  Flash Memory에 저장하라

        ->  .text * ; temxt

 

 

Reset Handler는 Program의 시작점을 나타내므로 memory의 맨 위를 가리키는 _estack을 지니고 있습니다. 즉, RAM의 최상단 주소를 지니고 있고 이는 저희가 SP를 할당해주는 것과 같습니다.

SystemInit을 통해 Clock을 초기화 해주고, CopyDataInit, LoopCopyDataInit을 통해 Flash Memory의 초기값을 RAM으로 Copy 합니다. -> .data Section을 초기화

또한,  .bss Sction RAM은 '0'값으로 초기화 합니다.

 

 

Flash Memory 시작 시, Startup의 ISR Vector가 제일 먼저 들어가게 되고 그 위에 .text, .data, .bss 이런식으로 담기게 됨

이러한 방식의 이유는 MCU가 시작되고 ISR의 정보가 담긴 Vector Table을 확인합니다. Stack Pointer와 Reset Handler를 통해 어디에서 시작하고 무슨 일을 시작해야하는지 알기 위함입니다. 그래서 ISR에 관한 명령어를 먼저 확인하고 그 다음 User의 text, data, bss 등의 정보를 처리합니다.

ISR Interrupt Vector 정보가 맨처음 위치하는 이유

 

 

 

그래서, 이러한 구조에 대한 이해를 바탕으로 HAL 명령어를 활용하여 StopWatch와 Watch에 구현해봅니다. 저희가 저번주에 BareMetal Coding을 진행하였던 부분들이 거의 대부분(GPIO, Interrupt) HAL 명령어 체계로 사용할 수 있습니다. 그래서, 아래의 그림처럼 저희가 제어를 위해 생성해야하는 Button, FND 등의 Hardware Controller들은 직접 만들어서 사용합니다.

Layer 구성도

 

 

아래의 그림과 같이, 1번 버튼을 누르면 Watch와 Stopwatch의 모드 전환, 2번 버튼을 누르면 Run/ Stop, 3번 버튼을 누르면 Clear 되도록 설계합니다. 두가지의 기능(Watch, Stopwatch)에 대한 코드를 각각 설계하고, ap_main.c에 두 모드에 대한 FSM을 설계하였습니다.

 

 

먼저, Watch 입니다. 기본적으로 1200을 출력하고 1분 마다 값이 증가합니다. 또한, 1번 버튼을 통해 StopWatch의 모드로 변경할 수 있습니다. 아래의 코드는 Callback에 대한 함수로 타이머 인터럽트 발생 시, 수행되게 하기 위함입니다. FND에 출력할 4자리에 대해 prescaler = 100 -1, arr = 1000 - 1로 설정하여 1ms마다 밀리초, 초, 분, 시를  측정하도록 설계 합니다. 즉, 1ms마다 인터럽트의 발생을 clock Timer처럼 활용하는 셈입니다.

Watch.c Code

 

 

아래의 코드는, FND에 Data를 입력하여 display하기 위한 함수를 구성합니다. 12:00부터 시작을 해야하므로 hour *100 + min을 통해 FND 4자리에 출력합니다. 또한, TimeWatch_Blink() 함수는 0.05초 간격으로 DotProduct를 점멸하는 함수입니다. 이를 모아 Excute 함수로 묶어놓고 ap_main에서 호출하여 사용합니다.

Watch.c Code

 

 

 

StopWatch도 Interrupt Timer를 활용하여 시간을 Count 해야합니다. 그래서 동일한 IncTime 함수를 갖지만, Run / Stop / Clear의 3개의 상태를 갖고 Run일 때만 시간이 동작해야하므로, Run state에서만 인터럽트를 활용하도록 합니다.

StopWatch.c Code

 

3개의 Run / Stop / Clear state를 갖고 있으니 이를 execute하기 위해 함수를 생성해 줍니다. Stop모드에서 Button을 누르면 Run, Clear state로 천이하도록 하고, Counter가 동작하지 않도록 합니다. RUN 모드는 1ms의 인터럽트를 활용하여 FND에 분/초초/밀리초 를 출력하도록 합니다. Clear 모드에서는 출력 data를 0으로 하여 모두 초기화 시킵니다. StopWatch_Blink() 함수는 watch 때와 마찬가지로 0.05초 간격으로 특정 위치의 Dot Product를 깜빡이게 합니다.

StopWatch.c Code

 

 

 

이를 하나의 application으로 동작하기 위한 ap_main code입니다. 대부분 HAL 명령어를 통해 구현하였습니다. 아래의 코드는 Interrupt에 대한 Callback 함수로, 1ms의 Timer2 Interrupt를 활용하여 FND에 값을 출력합니다. 또한, Timer와 Stopwatch에 Clock Counter로 활용하기 위해 각각의 Callback 함수도 동작시킵니다. 

ap_main.c

 

그리하여, main 코드에서는 Watch state와 StopWatch state에 대한 FSM을 구현합니다. 각각의 State에서는 만들어 놓았던 각각의 Excute 함수를 실행하여 기능을 동작하게 됩니다. 이때, 1번 Button을 누르면 mode가 변경되게 됩니다.

ap_main.c

 

 

동작 영상 :