Category (Click)
개발보드 덕질하기

[STM32C0316-DK] 펌웨어 계층화, LED 점등 모듈 구현

 차완기 - @6/3/2023, 12:56:00 AM
지난 포스팅에서 STM32C0316-DK 개발보드를 훑어보았습니다. 한번 전원만 넣어보고 그대로 어딘가에 박아두기에는 아까우니 STM32 개발 연습도 할 겸 디스커버리 키트의 모든 모듈을 테스트해 볼 예정입니다.
여기에서는 STM32CubeMX와 Visual Studio Code + STM32 VS Code Extension 조합으로 개발환경을 설정하였습니다. 이와 관련해서는 아래 내용을 참고해 보세요.

펌웨어 계층화

CubeMX를 이용해 펌웨어를 생성하면 peripheral, clock 설정 등 init과 관련된 모든 소스코드가 main.c 파일에 들어가게 됩니다.
/* Includes ------------------------------------------------------------------*/ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */
C
복사
그 와중에도 코드 제너레이터를 위해 코드를 작성할 수 있는 부위가 나뉘어있는데요, 이 때문에 코드를 작성할 때 main.c에 모두 작성하게 되면 가독성이 매우 떨어진다는 문제가 있습니다.
이것 이외에도 추상화 수준이나 목적별로 코드를 모듈화하여 코드를 쉽게 재사용하기 위해 CubeMX에서 자동 생성되는 코드와 기능 구현을 위한 코드를 분리하려 합니다.
여기에서는 BSP와 APP 두 가지로 나누어 코드를 작성하였습니다.
BSP(Board Support Package): 개발보드에 붙어있는 LED를 켜거나 스위치의 데이터를 읽어오는 등 모듈을 제어하기 위한 드라이버 모듈 패키지입니다.
APP(Application): BSP를 이용해 의도한 기능을 구현하는 모듈입니다.

기본 구조 생성

새 프로젝트를 생성하였기 때문에 APP과 BSP를 위한 디렉토리를 생성하고 CMakeLists에 이를 추가하는 과정이 필요했습니다.

파일 생성

App
app.c
app.h
Bsp
bsp.h
프로젝트 폴더에 위와 같이 두 디렉토리와 파일을 생성하였습니다. App 폴더의 경우 대부분의 기능 구현이, Bsp 폴더에는 보드의 모듈 별 드라이버 파일이 저장됩니다.

st-project.cmake 수정

CubeIDE를 사용하지 않기 때문에 추가한 파일들의 구조를 직접 CMakeLists.txt에 반영하여야 합니다. 프로젝트 디렉토리의 CMakeLists.txt 파일을 확인해 보면 cmake/st-project.cmake 파일에 대부분의 설정이 저장되어 있다는 것을 알 수 있습니다.
cmake/custom-path.cmake
cmake 디렉토리 아래에 custom-path.cmake 파일을 추가하고 App, Bsp 디렉토리를 include path에 추가하는 함수인 add_custom_path를 만들었습니다.
CMakeLists.txt
그 후 프로젝트 최상위 디렉토리의 CMakeLists.txt에는 방금 추가한 custom-path.cmake를 include하고, 생성한 add_custom_path 함수를 인자값과 함께 호출하였습니다.
Core/Src/main.c
여기까지 진행하면 include error가 발생하지 않죠.
CubeMX에서 코드를 생성할 때 target_sources가 자동으로 업데이트되기 때문에 target_sources는 추가할 필요가 없습니다.

main.c 수정

main.c에서 APP계층의 함수를 호출하고, app.c에서는 BSP 계층의 함수를 호출하도록 합니다.
Core/Src/main.c
main.c 파일의 main() 함수에는 APP 계층의 init함수인 app_init(), 메인 루프가 있는 app_main() 두 함수를 HAL 라이브러리와 peripheral 등의 init이 끝난 후 호출하였습니다. 따라서 main.c의 while문은 사용하지 않게 되겠죠.

APP계층 구현

이제 APP 계층을 만들어줄 차례입니다.
App/app.h
App/app.c
헤더 파일에는 include guard와 앞서 main.c에서 사용하는 두 함수의 형태를 선언하고 소스 파일에서는 함수의 내용을 정의하였습니다.
app_init() 함수에서는 BSP계층의 init 함수를 호출하고 결과를 반환합니다. 여기서는 일단 true를 반환하도록 해두었습니다.
app_main() 함수는 메인 루프가 있는 함수이죠. for문을 이용해 무한 루프를 만들었습니다.

bsp led 모듈 구현

우선은 간단히 LED를 켜고 끄는 모듈을 만들어보기로 하였습니다.
Bsp 디렉토리 아래에 led 디렉토리를 생성하고 led.c와 led.h를 추가하였습니다.
Bsp/led/led.h
Bsp/bsp.h
그 후 bsp.h에는 led.h를 include시켜주었고 led.h에는 가드와 LED 제어를 위한 함수를 선언하였습니다.
코드 작성에 앞서 STM32C031-DK의 온보드 LED가 몇 번 핀에 달려있는지 확인하였습니다. CubeMX에서 개발보드 프리셋을 선택해 프로젝트를 생성하였기 때문에 PA5에 Led라는 라벨이 붙어있는 것을 확인할 수 있었습니다.
Core/Inc/main.h
따라서 코드를 생성한 후 main.h에 들어가 보면 위와 같이 GPIO가 정의되어 있는 것을 확인할 수 있습니다. 여기서 온보드 LED의 핀 번호포트 번호는 각각 Led_Pin, Led_GPIO_Port입니다.
Bsp/led/led.c
이렇게 코드를 작성해 주었습니다. init에서는 딱히 할 게 없어 핀을 low로 설정해두었습니다.
@6/3/2023 추가
코드 상의 on과 off 함수가 서로 반대로 되어있습니다.
글을 작성할 당시에는 아무 생각이 없었는데 알고 봤더니 LED는 부논리로 켜지게 되어있었네요..
따로 수정은 하지 않을 예정입니다.

bsp delay 모듈 구현

모듈화와 라이브러리의 의존성을 최소화하기 위해 하나의 함수에는 동일한 추상화 수준의 함수를 사용하는 것이 좋습니다. 이는 APP 계층에서는 반드시 BSP 계층의 함수만을 호출하여야 한다는 것을 의미하는데요, 이를 위해서 ms 단위의 blocking 함수인 HAL_Delay()를 BSP 계층에 wrapping할 필요가 있었습니다.
led와 마찬가지로 delay 디렉토리를 추가하고 동일한 방법으로 헤더파일과 소스파일을 추가하였습니다.
Bsp/delay/delay.h
Bsp/delay/delay.c
이렇게 하면 되겠죠.
Bsp/bsp.h
bsp.h에 delay 모듈도 추가하였습니다.

App 계층 기능 구현

이제 모든 준비가 완료되었으니 app.c의 메인 루프에 LED를 켜고 끄는 코드를 작성해 보죠.
App/app.c
app_init() 함수에서는 init 결과를 rtn 변수에 AND연산해 이를 반환하도록 하였습니다. main.c에서는 이 결과를 이용해 예외처리를 하면 되겠습니다.

테스트

전원과 디버거 USB를 연결한 후 펌웨어를 업로드하였습니다. 의도한 대로 잘 작동하네요.

전체 소스코드

v1.0
tag
이 시점의 소스코드는 위를 참고해 주세요.

마무리

ESP-IDF를 사용하는 프로젝트에서 계층구조를 처음 사용해 보았는데, STM32 프로젝트에서도 이를 적용해 보니 상당히 편리하다는 느낌이 듭니다. 이전에 STM32에서 RTOS를 사용할 때 대충 코드를 작성했더니 Core가 엄청나게 복잡해지던 경험이 있었는데, 다음부터는 이렇게 계속 코드를 짜보아야겠습니다.