-
Arduino FreeRTOS로 CZ-ME310G1모뎀 MQTT Example 실시간 처리하기MQTT 2026. 6. 15. 03:30
CZ-ME310G1 모뎀으로 MQTT를 사용하는 동작예제인 MQTT_example, AWS_IoT_Mqtt_Test은 각각 MQTT서버에 접속 후 Message Publish 후 대기하다가 외부에서 메시지가 구독한 토픽으로 들어오면 받아서 처리 후 종료하도록 구현되어 있습니다.
이전 AWS-IoT 블로그를 올린 후 아래와 같은 질문이 올라왔습니다. (관련 글: https://codezoo.tistory.com/131 )
CZ-ME310G1 모뎀으로 AWS-IoT 쉽게 사용하기
CZ-ME310G1 모뎀을 사용해서 AWS-IoT를 MCU에서 직접 다루는 것보다 훨씬 쉽고 간단하게 사용할 수 있습니다.AWS-IoT Core와 통신하기 위한 SSL, MQTT 소프트웨어 스택이 모두 CZ-ME310G1 모뎀 내부에 구축되어
codezoo.tistory.com

IoT 장비 입장에서는 장비 자체동작을 하면서 외부에서 들어오는 통신 메시지에 대응할 수 있는 형태로 펌웨어가 구성되어 있어야 합니다. 손쉽게 대응할 수 있는 방법은 RTOS를 사용하는 것인데, Arduino UNO R4 시리즈(minima, wifi, nano)의 경우 보드패키지에 Arduino FreeRTOS가 포함되어 있습니다.
보드 패키지에 포함된 Arduino FreeRTOS 버전은 보드패키지 1.6.0기준으로 FreeRTOS-Kernel-v10.5.1이 탑재되어 있습니다.
직접 확인해 보시려면 아래 사용자계정 아래로 따라가 보시면 됩니다.(윈도기준)
C:\Users\사용자계정\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.6.0\libraries\Arduino_FreeRTOS\src\lib\FreeRTOS-Kernel-v10.5.1
해당 FreeRTOS 커널에 대해서 조사한 내용은 아래와 같습니다.1. FreeRTOS Kernel v10.5.1은 언제 나왔을까?
- 출시일: 2022년 11월 16일
- 위상: FreeRTOS의 전성기를 이끈 v10 계열의 가장 성숙하고 안정적인 마이너 업데이트 버전 중 하나입니다. 아두이노 UNO R4가 출시될 당시 가장 검증된 커널이었기 때문에 공식 보드 패키지에 기본 탑재되었습니다.
2. v10.5.1 커널의 핵심 특징 및 변화
v10.5.1(및 v10.5.0)로 넘어오면서 현업 개발자들이 체감할 만한 중요한 커널 내부 메커니즘의 최적화가 있었습니다.
- 문맥 전환(Context Switch) 조건 최적화: 기존에는 태스크가 대기(Blocked) 상태에서 깨어날 때, 그 태스크의 우선순위가 현재 실행 중인 태스크와 같거나 높으면(>=) 무조건 문맥 전환을 시도했습니다. 하지만 v10.5.x 버전부터는 깨어난 태스크의 우선순위가 현재 실행 중인 태스크보다 확실히 높을 때(>)만 문맥 전환을 하도록 변경되었습니다. 이 덕분에 불필요한 스케줄러 오버헤드가 크게 줄었습니다.
- heap_4.c 메모리 감시 기능 추가: 메모리 관리 기법 중 가장 많이 쓰이는 heap_4에 xPortGetMinimumEverFreeHeapSize()라는 함수가 추가되었습니다. 이 함수는 시스템이 구동된 이후 "가장 힙 메모리가 아슬아슬하게 남았을 때의 최저치(Watermark)"를 알려줍니다. 우리가 앞에서 논의한 힙 사이즈가 적절한지 모니터링할 때 아주 유용한 무기입니다.
- 검증된 안정성: v10 시리즈의 핵심 기능인 스트림 버퍼(Stream Buffers)와 메시지 버퍼(Message Buffers)가 완벽히 안정화된 버전이라, MCU의 자원을 극도로 효율적으로 쥐어짜야 하는 양산형 임베디드 기기에서 일종의 '표준 뼈대'로 대접받는 버전입니다.
3. 이후 업데이트된 현재 최신 버전은?
v10.5.1 이후 FreeRTOS는 메이저 버전 업데이트를 거쳐 현재 메인 스트림이 v11 계열로 완전히 진화했습니다.
- 2026년 현재 최신 버전: FreeRTOS Kernel v11.3.0 (2026년 3월 출시 및 2026년 4월 LTS 패키지 포함)
🚀 v11 계열(최신 버전)로 오면서 달라진 점
- 보안성 및 MPU 기능 강화: 하드웨어 메모리 보호 장치(MPU, Memory Protection Unit) 지원이 획기적으로 향상되어, 커널이 차지하는 MPU 영역을 줄이고 사용자 앱이 쓸 수 있는 보안 영역을 늘려주었습니다.
- MISRA-C 컴플라이언스 정밀화: 자동차나 의료기기 같은 고신뢰성 장비의 필수 안전 규격인 MISRA-C 코딩 표준을 완벽히 준수하도록 커널 코드가 더욱 정교하게 다듬어졌습니다.
- 대칭형 멀티프로세싱(SMP) 최적화: 듀얼 코어나 쿼드 코어 MCU(예: ESP32나 최신 Cortex-M 계열)에서 여러 코어에 태스크를 지능적으로 분배하는 기능이 커널 자체에 깊숙이 내장되었습니다.
💡지금 v10.5.1을 계속 써도 괜찮을까?
"네, 100% 안심하고 쓰셔도 됩니다."
최신 v11.3.0 커널이 매력적이긴 하지만, 이는 주로 멀티코어 제어, 고급 보안(MPU) 환경, 또는 IoT 네트워크 보안망 구축이 필요할 때 빛을 발합니다.
우리가 지금 개발 중인 프로젝트처럼 아두이노 UNO R4(싱글코어) 환경에서 최신 버전으로 커널을 교체할 실익이 전혀 없습니다. 오히려 이미 보드 패키지에 완벽히 녹아들어 가 하드웨어 검증이 끝난 v10.5.1이 현재 하드웨어 사양에는 가장 완벽한 짝꿍입니다.
FreeRTOS 커널을 이용해서 MQTT_Example을 실시간 이벤트 처리하는 코드는 이미 업데이트되어 있습니다.
LIBRARY MANAGER에서 ME310G1로 검색해서 0.1.10 버전 이상을 설치하시면 해당 Example을 사용하실 수 있습니다.
라이브러리 설치 또는 업데이트 후 Examples --> ME310G1 --> UNO_R4 --> MQTT_example_freertos를 선택합니다.
소스코드는 두 개의 작업단위(태스크)로 구현되어 있습니다.
vMQTTTask는 모뎀을 초기화하고 실시간 MQTT통신하는 태스크이고, vCommandTask는 사용자 입력을 처리하는 태스크로 시리얼모니터에 q 또는 Q를 입력 후 엔터키를 누르면 vMQTTTask에 종료신호를 보내는 용도로 사용합니다.
<1> 바이너리 세마포어
모뎀을 초기화하고 통신준비가 될 때까지 다른 태스크들이 동작하지 않도록 하기 위해 바이너리 세마포어(이진 세마포어)를 사용했습니다.//1. 바이너리 세마포어 선언부
SemaphoreHandle_t xMQTTConnectSemaphore = NULL; // Binary semaphore for inter-task synchronization
//2. 바이너리 세마포어 생성
// Create binary semaphorexMQTTConnectSemaphore = xSemaphoreCreateBinary();
//3. 대기하는 태스크 실행을 위한 바이너리 세마포어 제공
// Give semaphore to wake up the waiting Command task (Loose Coupling)xSemaphoreGive(xMQTTConnectSemaphore);
void vCommandTask(void *pvParameters) {// Wait in kernel blocking state immediately upon start (0% CPU usage)// Wait infinitely until the MQTT task successfully connects and gives the semaphore.
//4. 세마포어를 획득(세마포어를 획득할 수 없으면 획득할 때까지 대기)xSemaphoreTake(xMQTTConnectSemaphore, portMAX_DELAY);1. xSemaphoreCreateBinary()의 초기 상태는 '0'
가장 오해하기 쉬운 부분이 "세마포어는 누군가 가져간(Take) 다음에 돌려줘야(Give) 한다"는 개념입니다. 이는 뮤텍스(Mutex, 동시접근 제어) 일 때의 이야기입니다.
반면, 본 코드에서 사용한 이진 세마포어(Binary Semaphore)는 주로 한 태스크가 다른 태스크에게 "나 준비 끝났어!"라고 신호를 보내는 알림(Signaling) 목적으로 쓰이며, 다음과 같은 메커니즘을 가집니다.- xSemaphoreCreateBinary() 함수로 세마포어를 막 만들었을 때, 세마포어의 방에는 데이터가 없는 상태(값 = 0)입니다. 즉, 태어날 때부터 기본적으로 닫혀(Locked) 있습니다.
- 열쇠가 없기 때문에 누구나 xSemaphoreTake()를 먼저 호출하면 무조건 블로킹(대기) 상태가 됩니다.
2. 부팅 플로우 및 동작 순서 (Time-line)
① Setup 단계 (세마포어 생성)
- xSemaphoreCreateBinary()가 실행됩니다.
- 세마포어 상태: 0 (Unavailable)
② 스케줄러 시작 직후 (vCommandTask 선언부)
- vCommandTask가 먼저 CPU를 잡고 실행되다가 xSemaphoreTake(..., portMAX_DELAY);를 만납니다.
- 하지만 세마포어 상태가 0이므로 가져갈 열쇠가 없습니다.
- 결과: vCommandTask는 열쇠가 생길 때까지 무한 대기(Blocked) 상태로 빠지며, CPU 제어권을 OS에 반납합니다.
③ MQTT 초기화 단계 (vMQTTTask 실행)
- vMQTTTask가 CPU를 넘겨받아 실행됩니다.
- LTE 모뎀을 켜고, 망에 접속하고, MQTT Broker에 연결하는 무거운 작업들을 수행합니다. (이때 vCommandTask는 계속 자고 있습니다.)
④ MQTT 접속 완료 시점 (Give 발동! )
- MQTT 접속이 마침내 성공하면, vMQTTTask 내부에서 xSemaphoreGive(xMQTTConnectSemaphore);를 호출합니다.
- 세마포어 상태: 0 ➡️ 1 (Available)로 변경됩니다.
⑤ Command 태스크 깨어남 (Take 완료)
- 세마포어 값이 1이 되는 순간, FreeRTOS 커널은 "어? 아까 이거 기다리면서 자고 있던 vCommandTask 있었는데!" 하고 기억해 냅니다.
- vCommandTask를 즉시 잠에서 깨웁니다.
- 잠에서 깬 vCommandTask는 세마포어를 성공적으로 Take 하여 값을 다시 1 ➡️ 0으로 만들고, 그 아래에 있는 본문 for(;;) 루프로 진입하여 키보드 입력을 감시하기 시작합니다.
<2> FreeRTOS Task 스택 공간할당
아래 1280, 192 값을 넣어서 테스트가 사용할 스택공간을 할당했습니다.// 1. Create MQTT master taskxTaskCreate(vMQTTTask, "MQTT_Task", 1280, NULL, 2, &xMQTTTaskHandle);
// 2. Create user control task (No longer force-suspended at boot!)xTaskCreate(vCommandTask, "Cmd_Task", 192, NULL, 1, &xCommandTaskHandle);
FreeRTOS의 xTaskCreate() 함수에서 세 번째 인자인 스택 깊이(Stack Depth)는 '바이트(Byte)' 단위가 아니라 '워드(Word)' 단위로 계산하도록 설계되어 있습니다.
1 워드가 몇 바이트인지는 MCU의 아키텍처(프로세서의 비트 수)에 따라 결정됩니다.- 구형 UNO R3 (8비트 AVR 칩셋): 1 워드 = 1 바이트 (따라서 입력한 숫자가 그대로 바이트가 됨)
- 신형 UNO R4 (32비트 ARM Cortex-M4 칩셋): 1 워드 = 4 바이트 (32비트 레지스터 크기)
우리가 사용하는 UNO R4는 32비트 하드웨어이기 때문에, FreeRTOS 커널이 내부적으로 1280이라는 숫자를 보면 1280 × 4 바이트로 계산하여 메모리를 가져옵니다. 1280 x 4 = 5120(5KB)
설명한 두 가지 사항 외 특징을 정리한 내용은 아래 스케치를 참고하시기 바랍니다.
MQTT 동작을 확인하기 위해서는 MQTT Explorer 설치를 권장합니다.
설치 후 아래와 같이 등록합니다.
1. Name, Host를 입력하고 ADVANCED 버튼을 누릅니다.
2. czme310/topic/get, czme310/topic/put을 각각 +ADD 합니다. BACK버튼을 누릅니다.
3. SAVE버튼을 누르고 CONNECT버튼을 누릅니다.
4. 모뎀으로부터 message 메시지를 받으면 Publish 항목아래 Topic에 czme310/topic/get 입력하고 원하는 메시지를 입력 후 PUBLISH 버튼을 누릅니다. 모뎀 시리얼 모니터에서 해당 메시지가 도착하는지 확인합니다. 메시지 도착까지 몇 초 정도 소요될 수 있습니다.
모뎀 시리얼모니터 로그입니다.
======================================
ME310 FreeRTOS Suspend/Resume Test
======================================
[SYSTEM] Allocating All Tasks Upfront...
[SYSTEM] Command Task Created and Cold-Suspended safely.
[SYSTEM] Starting FreeRTOS Scheduler Engine...
AT
AT
AT
AT
AT$GPSP=1
AT$GPSP=0
[INIT_CLEANUP] Disconnecting Leftover MQTT Sessions...
AT#MQDISC=1
AT#MQEN=1,0
AT#SGACT=1,0,"",""
Telit Test AT MQTT command
ME310 ON
AT Command
AT+CMEE=2
AT+CPIN?
Define PDP Context
AT+CGDCONT=1,"IP","simplio.apn"
AT+CGDCONT?
AT+CGREG?
Activate context
AT#SGACT=1,1
AT#MQEN?
AT#MQEN=1,1
AT#MQEN?
AT#MQCFG=1,"broker.emqx.io",1883,1,0
AT#MQCONN=1,"czme310g1001","",""
AT#MQSUB=1,"czme310/topic/get"
AT#MQPUBS=1,"czme310/topic/put",1,0,"message"
>>> MQTT Setup Complete. Streaming Listening Loop Running...
[SYSTEM] Command Task Awakened and Armed Successfully.
-------------------------------------
[URC DETECTED] #MQRING: 1,1,czme310/topic/get,36
[INFO] MQTT Instance : 1
[INFO] Message ID : 1
-------------------------------------
[MQTT] Fetching payload via library...
================ MQTT PAYLOAD ================
1234567890abcdefghijklmnopqrstuvwxyz
==============================================
-------------------------------------
[URC DETECTED] #MQRING: 1,1,czme310/topic/get,14
[INFO] MQTT Instance : 1
[INFO] Message ID : 1
-------------------------------------
[MQTT] Fetching payload via library...
================ MQTT PAYLOAD ================
hello world!!!
==============================================
-------------------------------------
[URC DETECTED] #MQRING: 1,1,czme310/topic/get,10
[INFO] MQTT Instance : 1
[INFO] Message ID : 1
-------------------------------------
[MQTT] Fetching payload via library...
================ MQTT PAYLOAD ================
bye bye!!!
==============================================
==============================================
[USER CMD] 'q' detected! Initiating Shutdown...
==============================================
[SYSTEM] MQTT Listening Task has been stopped.
[CLEANUP] Disconnecting MQTT Session...
AT#MQDISC=1
AT#MQEN=1,0
AT#SGACT=1,0,"",""
=== All MQTT Public Procedures Done ===
ME310G1 Modem Power Off
[SYSTEM] Shutdown sequence completed successfully.
동작영상 첨부 합니다.
LTE-CATM1 내장형 모뎀 대량 구매 상담, 외주 개발, 협업 문의, vodafone IoT유심 문의
(주)코드주
장병남 대표 010-8965-1323 rooney.jang@codezoo.co.kr
코드주 LTE-CatM1 내장형 모뎀 CZ-ME310G1 / GNSS(GPS) 지원 / 외장 LTE 안테나 포함 / Zephyr 및 Arduino 지원
메카솔루션 공식 쇼핑몰
www.mechasolution.com
iot유심 : codezoo
[codezoo] IoT Global SIM, IoT Connectivity, IoT Device
smartstore.naver.com
CodeZoo LTE-CatM1 전용 아두이노 브레이크아웃 쉴드
메카솔루션 공식 쇼핑몰
mechasolution.com
'MQTT' 카테고리의 다른 글
[Part2] Arduino FreeRTOS로 CZ-ME310G1모뎀 MQTT Example 실시간 처리하기 (0) 2026.06.20 HiveMQ TLS MQTT를 Type1SC AT커맨드로 연결하기 (2) 2025.06.10 hivemq.com broker에 접속이 안되요 (1) 2025.04.29 아두이노에서 실시간으로 MQTT 메시지를 받을수 있을까요? (0) 2025.02.02 MQTT 이제 모바일로 테스트 하세요 (0) 2023.10.12