프로그래밍 초보 탈출

EFM32 예제로 배워보기

[기본.02] 새로운 프로젝트를 시작하자.

째즈토끼 2022. 4. 23. 09:14

Silicon Labs사에서 제공하는 개발환경의 이름은 Simplicity Studio다. EFM32 뿐만 아니라 EFR32, EFM8 등의 컴파일 환경을 제공한다. Windows용 외에도 Mac과 Linux용 버전도 무료도 받을 수 있다. 

https://www.silabs.com/developers/simplicity-studio

 

Simplicity Studio - Silicon Labs

Simplicity Studio simplifies IoT development by giving developers everything needed to complete projects using an integrated development environment (IDE).

www.silabs.com

설치는 알아서 잘하실 테니, 구구절절 설명하지 않겠다. 사실 화면 캡처하고 저장하고 여기 끼워 넣는 게 여간 번거로운 일이 아니다. 설치 과정은 뭐 특별한 점 없는 일반적인 설치 과정이다. Silicon Labs 멤버십에 가입하고 로그인을 하는 과정이 있는데, 이 부분은 스킵이 가능한지 어떤지 모르겠다. 설치 과정에서 주의할 점이 있다면 설치 경로 상에 "한글"로 된 폴더가 없을 것, 가능하면 경로가 짧을 것을 추천한다. TI의 Code Composer Studio를 쓰면서 한심한 일을 당한 적이 있는데, 파일 경로가 256 char를 넘어가서 프로젝트 백업 등에 문제가 생긴 일이다.(7zip으로 압축 백업한다) 어느 부분이었는지는 잘 기억이 나지 않지만, 컴파일러가 알아서 설치한 라이브러리였던가, 이클립스의 플러그인이었던가 그렇다. Simplicity Studio도 이클립스 기반의 개발환경이다 보니 같은 문제가 발생할 수도 있으니 주의해서 나쁠 것 없다.

자, 이제 무사히 설치를 마쳤다면 새로운 프로젝트를 만들어 보자.

첫 화면은 사용하는 MCU, 또는 개발 키트를 설정하는 부분이다. Start Kit를 사용한다면 캡처된 화면처럼 지정하거나, 혹은 MCU의 모델명을 찾아 지정해도 된다.

보드 또는 MCU를 지정하면 지정된 하드웨어에 대한 예제, 데이터시트 등의 지원 사항이 표시된다. "Create New Project"로 시작해보자.

기반으로 사용할 템플릿을 지정하는 부분이다. 깔끔하게 Empty C Project로 시작하자.

프로젝트의 이름과 폴더를 입력하는데, "With project files:"라는 항목이 있다. 기본적으로 제공되는 SDK 라이브러리들의 거취를 결정하는 부분인데, 원본의 위치 그대로 두고 참조할 것인지, 아니면 현재 프로젝트로 복사해서 참조할 것인지를 지정한다. 원본을 사용하는 경우, 업데이트 시 뭔가가 변경되는 수가 있기 때문에 필자의 경우는 무조건 복사해서 사용하는 편이다. 버그가 수정되는 등의 좋은 변경일 수도 있지만 내가 모르는 부분에서 뭔가 일이 발생하고 있다는 것은 반갑지 않은 일이다. 

프로젝트가 생성되었다. "autogen", "config", "gecko_sdx_xxx" 폴더와 구성 파일들이 같이 생성되는데, 내용은 폴더 이름으로 짐작하는 바와 같다.

자, 이제 UART 통신을 구현하기 위해서는 무얼 해야 할지 살펴보자. 기본 제공되는 라이브러리는 "em_uart.h", "em_uart.c"처럼 모두 "em_xxx.h", "em_xxx.c"라는 이름을 달고 있는데, 처음부터 프로젝트에 포함되지 않는다. 보통은 드라이버라고 말하는데 Sumplicity Studio에서는 Component라고 부르는데, 원하는 컴포넌트를 프로젝트에 Install 해야 그 기능을 사용할 수 있다. 설명은 Install이지만 Import라고 이해하시면 된다. 이 과정에서 해당 라이브러리가 내 프로젝트로 복사된다.

"Project Explorer"에서 "<프로젝트 명>.slcp" 항목을 선택하면 위의 그림과 같이 자주 보게 될 화면이 나타난다.

"SOFTWARE COMPONENTS" 메뉴에서 필요한 컴포넌트를 Install 할 수 있다. 필요 없어진 컴포넌트는 Uninstall도 가능하다. 필수 컴포넌트는 기본적으로 설치가 되어 있으니 추가로 필요한 것만 그때그때 설치하면 된다.

필자의 경우는 "Platform" > "Peripheral" 하위의 기본적인 컴포넌트 외에는 사용하지 않았다. 이 컴포넌트 들은 "em_xxx" 라이브러리들인데, 거의 매크로 함수 수준에 가까운 기능들을 제공한다. 원래는 데이터시트를 보며 레지스터를 직접 다뤄서 조작해야 할 것을 사용하기 쉽게 포장해 놓은 라이브러리라 할 수 있다.

그 외에 좀 더 상위 개념의 라이브러리, 이를테면 "Platform" > "Driver" 내의 UART 드라이버 같은 것들도 여러 가지 구현해 놓았는데, 필자는 사용하지 않았다. 내가 모르는 곳에서 일어나는 일을 믿지 않기 때문이다. 믿기 위해서는 소스를 뜯어보고 확인해야 하는데, 차라리 그냥 만드는 것보다 시간도 더 걸리고, 내가 쓰던 방식이 아닌 다른 방식에 익숙해져야 하는 것도 맘에 들지 않는다.

참고로 pintool도 제공한다. TI의 CCS 등에서는 핀의 기능을 지정하면 해당 핀의 초기화 코드까지 자동으로 생성되는데, Simplicity Studio는 그런 거 없다. 그냥 이름만 바꿔 쓸 수 있도록, 매크로 정의만 해준다. 코드 자동 생성이 편한 건 사실이지만, 툴이 업데이트될 때 어떤 일이 발생할지 몰라 대비를 해야 한다.

실제로 CCS의 PinMux를 사용하면서 겪은 일로 PinMux에서 특정 핀을 GPIO로 설정하고 신호를 반전시키도록 한 일이 있는데, 처음에는 잘 동작했지만 다음에 다시 코드를 생성하면 반전이 해제되는 문제가 있었다. 신호를 반전시키는 설정이 뭐냐 하면, 회로도를 받아 봤을 때 LED 연결 핀에 High 신호를 줘야 켜지는 경우가 있고, Low 신호를 줄 때 켜지는 경우가 있다. 물론 LED_ON()이라는 함수를 만들어 High 등 Low든 처리하겠지만, Low 신호를 줄 때 켜지는 회로에서도 반전 설정을 하면 High 신호를 줄 때 켜지는 식이다. 버튼 입력도 마찬가지, 회로 구성에 상관없이 버튼이 눌리면 High 신호가 들어오는 것처럼 만들 수 있다는 것이다.

이야기가 옆으로 샜는데, 필자가 하고 싶은 말은 코드 자동 생성을 맹신하지 말라는 말이다. 위의 사건 이후로 TI의 PinMux에 불신이 생겼는데, 아예 사용하지 않는 것은 아니고 일단 코드를 생성한 후 생성된 코드를 해당 기능을 초기화하는 부분으로 복사해 넣는 식으로 사용하고 있다. 아무튼 Simplicity Studio는 다행히(?) 자동으로 초기화 코드를 생성해 주지 않으므로 고민할 필요가 없다. 없는지 알았으나 소프트웨어 컴포넌트를 지정하면 세부 설정 포함한 코드 생성기가 있더라...

기껏 자동 생성하는 게 위처럼 define 해주는 게 끝이다. 그래서 pintool이라는 것은 없는 셈 치기로 했다.

int main(void)
{
  // Initialize Silicon Labs device, system, service(s) and protocol stack(s).
  // Note that if the kernel is present, processing task(s) will be created by
  // this call.
  sl_system_init();

....

자동 생성된 "main.c" 파일의 main() 함수를 보자. sl_system_init() 함수로 초기화를 하는데, 그 이후의 일은 신경 쓰지 않았다. 왜냐하면 sl_system_init() 호출 직후에 내가 만든 cust_main()이라는 함수를 호출할 예정이고 다시 이쪽으로 return 되는 일은 없을 것이기 때문이다. 즉, 친절히 이렇게 구성하라며 만들어 놓은 app.c와 일련의 루틴들은 무시한다. 낯선 루틴에 적응하고 싶지 않은 탓이다.

그래도 sl_system_init()는 그대로 두고 쓰기로 했다. 어느 순간 맘이 바뀌어 나만의 스타일로 바꿔버릴지 모르겠지만, 일단은 그냥 쓴다. 쓸 땐 쓰더라도 내용을 모른 채 쓸 수는 없다. 뭘 하는 놈인지 들여다봐야 한다.

void sl_system_init(void)
{
  sl_platform_init();
  sl_driver_init();
  sl_service_init();
  sl_stack_init();
  sl_internal_app_init();
}

아하, 별거 없네..

sl_platform_init() 외에는 전부 비어있는 함수들이다.

void sl_platform_init(void)
{
  CHIP_Init();
  sl_device_init_nvic();
  sl_board_preinit();
  sl_device_init_dcdc();
  sl_device_init_hfxo();
  sl_device_init_lfxo();
  sl_device_init_clocks();
  sl_device_init_emu();
  sl_board_init();
}

화면 캡처를 위해 새로 생성한 프로젝트의 초기화 코드다. 타깃을 Start Kit으로 지정해서 저런 코드가 생겼는데, Start Kit에 맞도록 잘 초기화를 할 거라고 믿는다.

참고로 Start Kit을 타깃으로 지정하지 않고, 칩 모델로 타깃을 지정하면...

void sl_platform_init(void)
{
  CHIP_Init();
  sl_device_init_nvic();
  sl_device_init_dcdc();
  sl_device_init_clocks();
  sl_device_init_emu();
}

이런 식으로 초기화 코드가 달라진다. MCU 외의 주변 회로 정보가 없으니 그럴 수밖에... 실제로 프로젝트를 진행할 때 Start Kit과 같은 MCU를 쓰더라도 타깃을 Start Kit으로 하면 안 되고 MCU 모델로 지정해야 하는 이유다.

각각 보드, dcdc, 클럭 등의 초기화 세분화되어 있는데 소스를 직접 수정하는 것은 권장하지 않는다. 설정을 바꾸고 싶다면 "config" 폴더의 "sl_xxxx_config.h" 파일을 수정하도록 설계되어 있으니 참고하자. 아니면 생성된 초기화 코드를 무시하고 나만의 초기화 루틴을 만들던지...