이번 시간에는 이제까지 살펴 보지 않았던 Cortex-M4 환경에서의 ST firmware 개발과 관련한 내용을 간략히 소개해 보고자 한다.
목차
7. Cortex-M4 환경에서의 firmware 개발하기
8. References
(*) 목차는 처음 예정했던 내용과 차이가 날 수도 있다.
DK2 보드에는 Cortex-A7(CPU1)과 Cortex-M4(CPU2) 두개의 CPU(정확히는 SoC)가 장착되어 있다. 따라서 부팅 시점이나 부팅 후 적당한 시점에 둘간의 상호 통신이 필요한 상황이 발생할 수 있는데, 이번 blog post에서는 Cortex-M4에 ST firmware를 올리는 방법과 두 CPU(CPU1 <-> CPU2) 간의 IPCC(Inter Processor Communication Channel) 상황에 대해서 간략하게 짚어 보고자 한다.
<확장 편>
7. Cortex-M4 환경에서의 firmware 개발
앞선 장(1-6장)에서는 Cortex-A7 MPU 환경(main processor, OS: Linux)에 대해서만 이야기하였다. 하지만 이번 장에서는 DK2 보드에 존재하는 또 다른 CPU인 Cortex-M4(coprocessor, OS: ST firmware/bare-metal application)에 관하여 이야기하고자 한다. 사실 이 부분이 DK2를 개발한 ST Microelectronics 사의 주력 분야라고 말할 수 있다. 즉, 소규모 embedded system 개발에 관한한 ST Microelectronics가 업계 1위(정확한 근거가 있는 얘기는 아님)라고 말할 수 있을 정도로 널리 사용되는게 사실이다.
[그림 7.1] STM32MP1 Architecture
[그림 7.2] STM32MP1 Coprocessor Management
이장에서 소개하는 내용 대부분은 STM32 MPU wiki(참고 문헌 [1])를 참조하여 작성하였다.
a) STM32CubeIDE 설치
먼저 아래 내용을 참조하여 STM32Cube IDE를 설치해 보도록 하자
[Tip] Cortex-M 계열의 firmware를 개발하기 위해서는 (유료 버젼인) IAR Embedded Workbench(EWARM)나 KEIL MDK(MDK-ARM) 등이 많이 쓰인다. STM32CubeIDE는 무료 버젼이므로 나름의 가치가 있다고 생각된다.
지금부터 소개하는 내용은 모두 Ubuntu 18.04를 기준으로 하였다(참고: Windows, MacOS 사용자의 경우는 해당 버젼을 설치하시기 바람).
$ unzip en.st-stm32cubeide_1.3.0_5720_20200220_1053_amd64.deb_bundle.sh.zip
$ sudo sh ./st-stm32cubeide_1.3.0_5720_20200220_1053_amd64.deb_bundle.sh
=> super user 권한으로 설치하고, 몇가지 license에 관한 물음에 yes로 응답해 주면 된다.
[그림 7.3] STM32CubeIDE 설치 - Ubuntu용 shell script 실행
[그림 7.4] STM32CubeIDE 실행 화면
[Tip] STM32CubeIDE는 eclipse를 기반으로 하고 있다.
다음으로 할 일은, 아래 위치에서 STM32Cube MP1 package를 download 받아 설치하는 것이다.
$ unzip en.stm32cubemp1_v1-2-0.zip
$ cd STM32Cube_FW_MP1_V1.2.0
[Tip] 버젼이 여러 개가 있는데 가장 최신 버젼을 내려 받으면 된다.
[그림 7.5] STM32Cube MP1 package 내용
아래 내용은 STM32Cube MP1 package를 구성하는 디렉토리 구조를 간략히 보여준다(ST code는 실로 오래 간만에 보는 것 같다 😁).
STM32Cube_FW_MP1_V1.2.0 ├── Drivers │ ├── BSP BSP drivers for the supported STM32MP1 boards │ │ └── [...] │ ├── CMSIS │ │ └── [...] │ └── STM32MP1xx_HAL_Driver HAL drivers for the supported STM32MP1 devices │ └── [...] ├── _htmresc │ └── [...] ├── License.md ├── Middlewares │ └── [...] ├── package.xml ├── Projects │ ├── STM32CubeProjectsList.html List of examples and applications for STM32CubeMP1 Package │ ├── STM32MP157C-DK2 Set of examples and applications → STM32MP15 Discovery kits │ │ └── [...] │ └── STM32MP157C-EV1 Set of examples and applications → STM32MP15 Evaluation boards │ └── [...] ├── Readme.md ├── Release_Notes.html Release note for STM32CubeMP1 Package └── Utilities └── [...]
b) 예제 Application(OpenAMP_TTY_echo) 돌려 보기
STM32Cube MP1 package에는 몇가지 example이 포함되어 있다. 따라서 지금 부터는 이 중 하나를 선택하여 돌려 보도록 하겠다.
[Tip] 시작에 앞서 1-6장에서 사용했던 Buildroot를 버리고(?) ST에서 배포한 image(yocto image)로 테스트를 진행하기로 한다. (그 이유는) Buildroot 환경에서 테스트를 해 보니, 무언가 빠져 있는지 Cortext-M4 상에서 program 구동이 안된다.
먼저, File -> Import => General => Existing Projects into Workspace => Browse 버튼 선택하여 OpenAMP_TTY_echo Application을 open하도록 한다.
[그림 7.6] File -> Import => General => Existing Projects into Workspace => Browse 버튼 선택
참고: OpenAMP_TTY_echo application은 아래 위치에 있다.
STM32Cube_FW_MP1_V1.2.0/Projects/STM32MP157C-DK2/Applications/OpenAMP/OpenAMP_TTY_echo
정상적으로 open할 경우, 아래와 같은 파일 들이 보이게 될 것이다.
[그림 7.7] OpenAMP_TTY_echo Application
OpenAMP_TTY_echo가 무엇을 하는 program인지를 따져 보는 것은 뒤에서 하기로 하고, 일단은 무조건 build 부터 해 보도록 하겠다.
좌측 상단의 Project Explorer 창에서 OpenAMP_TTY_echo_CMD4를 선택한 상태에서 toolbar에 있는 작은 망치(해머) 버튼을 누르면 build가 진행된다.
Eclipse IDE(STM32Cube IDE) 화면 하단 중앙에 build 과정이 출력되게 되는데, 정상적으로 build가 될 경우, OpenAMP_TTY_echo_CM4.bin 파일이 생성됨을 알 수 있다.
실제 source code가 위치한 디렉토리로 이동해 build 결과를 확인해 보니, bin 파일 외에도 elf 파일 등이 보인다.
[Tip] bin 파일은 flash memory에 writing할 때 사용하고, elf는 memory에 loading하여 곧 바로 실행할 때 사용된다.
자, 그러면 지금부터는 방금 전에 build 한 파일(OpenAMP_TTY_echo_CM4.elf)을 Cortex-M4에서 돌려보도록 하겠다.
Run -> Debug Configurations -> STM32 Cortex-M C/C++ Applications를 선택하고, 아래와 같이 Debugger tab을 선택한 후, 필요한 정보(실제로는 target board의 IP 주소만 입력하면 됨)를 입력하도록 한다.
[그림 7.11] Debug Session 설정하기
이 상태에서 Debug 버튼을 누르면, OpenAMP_TTY_echo_CM4.elf 파일이 linux로 copy되면서, 대략적으로 아래의 (예상) 절차를 따라 Cortex-M4 CPU에서 실행되게 된다.
[Tip] Cortex-M4 SoC 내에는 별도의 flash memory가 존재할 것이고, 여기에 bin 파일을 writing하는 방법이 분명 있을 텐데, 이와 관련해서는 추후 확인해 보도록 하겠다(보통은 ST에서 설치 전용 program을 제공).
<OpenAMP_TTY_echo_CM4.elf writing 예상 절차>
STM32CubeIDE => network(SSH) => Linux => remoteproc driver => SRAM/IPCC => Run it on Cortex-M4
[그림 7.12] OpenAMP_TTY_echo_CM4.elf 파일이 복사된 위치 - linux file system
[그림 7.13] OpenAMP_TTY_echo_CM4.elf 파일이 Cortex-M4에서 실행되는 과정을 보여주는 linux kernel message(dmesg 출력 결과)
참고: linux driver인 remoteproc에 관해서는 별도로 분석이 필요해 보인다.
[그림 7.14] remoteproc linux device driver & device tree 예
c) IPCC(Inter Processor Communication Channel)의 이해
지금부터는 OpenAMP_TTY_echo program이 무엇을 하는 녀석(?)인지 살펴볼 생각인데, 이를 이해하기 위해서는 사전에 몇가지 개념을 이해하고 넘어갈 필요가 있어 보인다.
먼저 아래 그림 7.15는 STM32MPU1을 구성하는 Cortex-A 쪽의 Linux와 Cortex-M 쪽의 ST firmware 간의 상호 통신에 관한 전반적인 개요도가 되겠다. 그림을 보면 알 수 있는 바와 같이 mailbox, rpmsg, remoteproc 등등의 아주 신기한(?) 녀석들이 눈에 들어온다.
[그림 7.15] STM32MPU1 remoteproc and rpmsg framework
다음 그림은 Linux 쪽의 rpmsg와 ST firmware 쪽의 OpenAMP program 간의 inter processor 통신에 관한 부분이 되겠다.
[그림 7.16] STM32MPU1 Inter Processor Communication Channel
마지막으로 아래 그림은 remote processor(여기서는 Cortex-M4)를 제어(power on/off, load firmware)하기 위해 linux에서 제공(물론 stm32에 맞게 개선)하는 remote processor framework을 소개한다.
<remoteproc의 역할>
- ELF firmware를 remote processor 메모리에 적재하고,
- firmware resource table을 parsing 하며,
- remote processor의 start/stop을 제어하고,
- remote firmware를 모니터링하거나 debugging하기 위해 서비스를 제공한다.
<stm32_rproc의 역할>
- RPROC framework에 callback function(vendor specific codes)을 등록하고,
- remote processor와 관련된 platform resource 즉, register, watchdog, reset, clock 및 메모리 등을 handling 한다.
- 또한, mailbox를 통해 remote processor에게 noti를 날려준다.
[그림 7.17] STM32MPU1 RPROC(Remote Processor) framework
사실 위에 소개한 내용을 모두 상세히 이해하기는 쉽지 않아 보이는데, 여기에서는 일단 이 정도까지만 살펴 보기로 하고, OpenAMP_TTY_echo program 분석으로 넘어가기로 하자.
d) 예제 Application(OpenAMP_TTY_echo) 코드 분석
OpenAMP의 동작 시험을 해 보면, OpenAMP가 무엇을 하려는 것인지 대략 이해할 수가 있다. Target board에서 아래 명령을 하나씩 수행해 보도록 하자.
<Target board>
root@stm32mp1:~# stty -onlcr -echo -F /dev/ttyRPMSG0
=> /dev/ttyRPMSG0를 초기화(open) 한다.
root@stm32mp1:~# cat /dev/ttyRPMSG0 &
=> /dev/ttyRPMS0 channel로 값이 들어오기를 기다린다.
=> &(background 실행) 대신, 별도의 terminal을 띄우고 실행해도 된다.
root@stm32mp1:~# echo "Hello Virtual UART0" > /dev/ttyRPMSG0
=> /dev/ttyRPMSG0 channel로 string을 내보낸다.
Hello Virtual UART0
=> 앞서 cat /dev/ttyRPMSG0 &를 통해 수신한 내용이 출력된다.
OpenAMP는 linux와 두개의 채널(/dev/ttyRPMSG0, ttyRPMSG1)을 열어 두고 있다. 따라서 /dev/ttyRPMSG1에 대해서도 동일한 실험이 가능하다.
[그림 7.18] RPMSG를 통해 OpenAMP와 통신(<=> linux)하는 모습
다음 코드는 OpenAMP main 함수의 흐름을 대략적으로 정리해 본 것이다. 앞서 테스트한 내용과 비교해 보면, 그 의미를 파악하는 것이 크게 어렵지 않다는 것을 알 수 있을 것이다.
<main.c 흐름도>
main() // loop을 돌면서 uart0, uart1으로 message가 들어오는지를 기다렸다가 메신지가 있으면 응답 메시지를 내보내는 routine
{
HAL_Init( ); //HAL code 초기화
if (boot mode)
SystemClock_Config();
MX_IPCC_Init( ); // IPCC code 초기화
MX_OPENAMP_Init( ); // OPENAMP code(mailbox, shmem 등) 초기화
VIRT_UART_Init(&huart0); // uart0 초기화
VIRT_UART_Init(&huart1); // uart1 초기화
VIRT_UART_RegisterCallback(&huart0, ..., VIRT_UART0_RxCpltCallback); // uart0에 대한 callback 함수 등록
VIRT_UART_RegisterCallback(&huart1, ..., VIRT_UART1_RxCpltCallback); // uart1에 대한 callback 함수 등록
while (1) // 무한 loop
{
OPENAMP_check_for_message(); // OPENAMP message가 도착했는지 체크
if (VirtUart0RxMsg) //uart0를 통해 message send(transmit)
VIRT_UART_Transmit(&huart0, VirtUart0ChannelBuffRx, VirtUart0ChannelRxSize);
if (VirtUart1RxMsg) //uart1를 통해 message send(transmit)
VIRT_UART_Transmit(&huart1, VirtUart1ChannelBuffRx, VirtUart1ChannelRxSize);
}
}
아래 내용은 OpenAMP_TTY_echo readme.txt 파일에서 발췌한 내용으로, 위의 main() 함수에서 하는 일을 요약해 주고 있다.
[그림 7.19] STM32Cube_FW_MP1_V1.2.0/Projects/STM32MP157C-DK2/Applications/OpenAMP/OpenAMP_TTY_echo/readme.txt에서 발췌한 내용
사실 main 함수에서 호출하는 함수 하나 하나를 다 따져 가면서, ST firmware code를 깊게 설명하자면 할 말이 더 많이 있겠지만, 내용이 지나치에 길어질 수 있으므로 이쯤에서 이번 blog post를 마무리 지을까 한다.
여기에 미쳐 소개하지 못한 내용은 STM32 MPU wiki를 통해 독자 여러분이 직접 파악해 보시기를 권해 드린다. 😅
8. References
[4] STM32CubeIDE installation guide - User manual
Slowboot
댓글 없음:
댓글 쓰기