Wiznet makers

scott

Published June 19, 2026 ©

131 UCC

20 WCC

47 VAR

0 Contests

0 Followers

0 Following

Original Link

W5500-STM32-cJSON

STM32F103 + W5500 MQTT IoT Sensor Node

COMPONENTS
PROJECT DESCRIPTION

요약

STM32F103WIZnet W5500을 조합해 MQTT 기반 IoT 센서 노드를 구현한 프로젝트입니다. W5500의 하드웨어 TCP/IP 오프로딩으로 MCU에 소프트웨어 네트워크 스택 없이 이더넷 연결을 확보하고, cJSON으로 센서 데이터(온습도, CO2)를 JSON 직렬화하여 MQTT 브로커에 발행·구독합니다. 이식 과정에서 발생한 수신 블로킹, 힙 부족, 타이머 포팅 등 실전 이슈를 상세히 문서화한 점이 유사 사례 대비 두드러진 차별점입니다.


개요

임베디드 개발자가 MCU에 이더넷 연결을 추가하려 할 때 가장 큰 장벽 중 하나는 소프트웨어 TCP/IP 스택입니다. lwIP 같은 스택은 RTOS와 상당한 RAM을 요구하며, 코드 복잡도도 높습니다. W5500은 이 문제를 하드웨어 수준에서 해결합니다. TCP/IP 처리를 칩 내부에서 수행하므로, MCU는 SPI를 통한 소켓 API 호출만으로 네트워크를 사용할 수 있습니다.

이 프로젝트는 STM32F103 + W5500 + Paho MQTT + cJSON 조합으로 완전한 IoT 센서 노드를 구현합니다. 온습도·CO2 데이터를 1초 주기로 수집하고, cJSON으로 직렬화한 뒤 MQTT 브로커(Mosquitto 등)에 발행합니다. 동시에 device/control 토픽을 구독하여 JSON 제어 명령을 수신하고 GPIO·액추에이터를 동작시킵니다. MQTTX로 즉시 검증 가능한 구조여서, 임베디드 입문자부터 IoT 시스템 프로토타이핑을 빠르게 진행하려는 엔지니어까지 폭넓게 활용할 수 있습니다.


아키텍처


기술 배경

W5500 하드웨어 TCP/IP 오프로딩

W5500은 TCP/IP 프로토콜 스택을 실리콘 내부에 완전히 구현한 하드와이어드(hardwired) 이더넷 컨트롤러입니다. MCU는 SPI를 통해 레지스터와 소켓 버퍼에만 접근하고, 실제 TCP 연결 수립·유지·패킷 재전송은 W5500이 자율적으로 처리합니다. 8개의 독립 소켓을 동시 운용할 수 있으며, 소켓당 TX/RX 버퍼는 총 32KB 범위 내에서 유연하게 배분됩니다.

이 프로젝트에서 W5500은 소켓 0을 TCP 모드로 열어 MQTT 브로커(포트 1883)에 상시 연결을 유지합니다. MCU는 send() / recv() 같은 BSD 소켓 API만 호출하면 되고, TCP keepalive·ARP·체크섬 처리는 전부 W5500이 담당합니다. 결과적으로 STM32F103의 20KB RAM 중 네트워크 스택에 할당되는 공간이 거의 없으며, MCU 사이클은 센서 처리와 JSON 직렬화에 집중됩니다.

→ W5500 데이터시트: https://docs.wiznet.io/Product/iEthernet/W5500/datasheet

→ WIZnet ioLibrary: https://github.com/Wiznet/ioLibrary_Driver

MQTT와 Paho Embedded C

**MQTT(Message Queuing Telemetry Transport)**는 발행(Publish)/구독(Subscribe) 모델 기반의 경량 메시징 프로토콜입니다. 클라이언트는 브로커(Broker)를 중심으로 연결되며, 토픽(Topic) 문자열을 기준으로 메시지를 교환합니다. 연결 유지에 드는 오버헤드가 매우 낮고, 페이로드 형식 제한이 없어 임베디드 장치에 적합합니다.

이 프로젝트는 Paho Embedded C 라이브러리를 사용합니다. Paho는 MQTTClient.cMQTTPacket/ 디렉터리로 구성되며, 플랫폼별 네트워크 송수신 함수만 구현해 등록하면 동작합니다. 이 프로젝트에서는 mqtt_interface.c가 W5500 소켓 API를 래핑하여 Paho의 네트워크 인터페이스 역할을 합니다. MQTTYield()를 메인 루프에서 주기적으로 호출하는 것이 프로토콜 구동의 핵심입니다.

→ Paho MQTT Embedded C: https://github.com/eclipse/paho.mqtt.embedded-c

→ MQTT 사양 (OASIS): https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html

cJSON

cJSON은 ANSI C로 작성된 경량 JSON 파서/생성기입니다. 단일 .c/.h 파일로 구성되어 임베디드 환경에 이식이 쉽습니다. 내부적으로 동적 메모리(malloc/free)를 사용하므로, 힙 크기 설정이 안정적 동작의 전제 조건입니다.

이 프로젝트에서 cJSON은 두 방향으로 활용됩니다. 발행 방향에서는 cJSON_CreateObject()로 센서 데이터 구조를 구성하고 cJSON_PrintUnformatted()로 문자열화하여 MQTT 페이로드로 전달합니다. 수신 방향에서는 device/control 토픽의 페이로드를 cJSON_Parse()로 파싱하고 cJSON_GetObjectItem()으로 lamp, fun 같은 제어 키를 추출합니다. 사용 후 cJSON_Delete()로 메모리를 반환하는 것이 필수입니다.

→ cJSON GitHub: https://github.com/DaveGamble/cJSON


기술 특징

이 프로젝트가 단순한 "동작하는 예제"를 넘어 레퍼런스로서 가치를 갖는 이유는, STM32 HAL + ioLibrary + Paho 조합에서 실제로 발생하는 이식 장벽을 체계적으로 해결했기 때문입니다.

① Sn_RX_RSR 사전 확인 — 수신 블로킹 문제의 근본 해결

Paho의 readPacket()mqttread()를 반복 호출하며 패킷을 조립합니다. W5500 소켓이 블로킹 모드일 때 수신 데이터가 없으면 recv()MQTTYield() 전체를 멈춥니다. 이 프로젝트는 recv() 호출 전에 Sn_RX_RSR 레지스터를 먼저 읽어 수신 바이트 수를 확인하고, 0이면 즉시 반환하는 방식으로 이 문제를 해결했습니다. 소프트웨어 레이어 수정 없이 W5500의 하드웨어 레지스터를 직접 활용한 깔끔한 패턴입니다.

② HAL_GetTick() 기반 Paho Timer 포팅

일부 WIZnet 예제는 별도의 MilliTimer_Handler() 인터럽트를 요구하지만, 이 프로젝트는 STM32 HAL이 이미 제공하는 HAL_GetTick()으로 Paho의 TimerIsExpired, TimerCountdownMS 등 타이머 인터페이스를 완전 대체했습니다. 추가 인터럽트 핸들러 없이 기존 HAL 인프라를 재사용한 실용적인 접근입니다.

③ PRIMASK 기반 중첩 가능 임계 구역

ioLibrary는 SPI 트랜잭션 전후로 임계 구역 진입/해제 콜백을 호출합니다. 이 프로젝트의 구현은 PRIMASK 레지스터를 저장하고 중첩 호출 횟수를 카운팅하여, 중첩 진입 시에도 원래 인터럽트 상태를 정확히 복원합니다. 단순히 __disable_irq()만 쓰는 구현과 달리 인터럽트 중첩 시나리오에서도 안전합니다.

④ 단일 바이트 SPI 콜백의 실용적 트레이드오프

이 프로젝트는 wiz_spi_readbyte()/wiz_spi_writebyte() 방식의 단일 바이트 SPI 콜백을 사용합니다. burst 전송보다 오버헤드가 크지만, 구조가 단순하고 HAL_SPI_TransmitReceive() 한 줄로 구현되어 이식성이 높습니다. 센서 데이터처럼 소량 데이터를 주기적으로 전송하는 용도에서는 충분한 성능을 발휘합니다.


W5500 적용 구조

이 프로젝트에서 W5500은 STM32F103과 이더넷 네트워크 사이의 전담 네트워크 프로세서로 동작합니다.

STM32F103 (애플리케이션 처리)
    │ SPI1 (PA5/PA6/PA7, CS=PA3)
    ▼
W5500 (하드웨어 TCP/IP)
    ├─ 소켓 0: TCP → MQTT Broker :1883
    ├─ TX 버퍼: 2KB × 8소켓
    └─ RX 버퍼: 2KB × 8소켓
    │ RJ45
    ▼
이더넷 네트워크

bsp_w5500.c가 초기화의 모든 것을 담당합니다. CS/SPI/임계구역 콜백을 ioLibrary에 등록하고, wizchip_init()으로 소켓 버퍼를 배분한 뒤, wizchip_setnetinfo()로 정적 IP(192.168.x.x)를 W5500 내부 레지스터에 기록합니다. 이후 애플리케이션은 W5500의 존재를 의식할 필요 없이 BSD 소켓 API만 호출합니다.

W5500의 VERSIONR 레지스터(기대값: 0x04) 읽기로 SPI 연결을 자가 진단할 수 있어, 하드웨어 트러블슈팅의 첫 번째 검증 단계로 활용됩니다.


WIZnet Makers 유사 사례

동일한 W5500 + STM32 + Paho MQTT 아키텍처가 WIZnet Makers 생태계에서 이미 여러 차례 검증되었습니다.

  • stm32f051-w5500-mqtt-sub-ex — STM32F051(Cortex-M0) + W5500 + Paho MQTT + ioLibrary 조합으로 구독(SUB) 기능을 구현. 본 프로젝트와 동일한 ioLibrary + STM32CubeIDE 기반이며, 본 프로젝트는 여기에 발행(PUB) + cJSON 직렬화 + 센서 처리를 더해 양방향 IoT 노드로 확장했습니다.

https://maker.wiznet.io/Benjamin/projects/stm32f051-w5500-mqtt-sub-ex/

  • STM32F401RE_MQTT with W5100 ethernet shield — STM32F401RE(Cortex-M4) + W5500 + SPI + MQTT 발행/구독 양방향 구현. 주기적 발행 루틴과 UART 디버깅 구조가 본 프로젝트와 유사하며, 본 프로젝트는 여기에 cJSON 기반 페이로드 구조화와 센서 연동을 추가했습니다.

https://maker.wiznet.io/Hannah/projects/stm32f401re-mqtt-with-w5100-ethernet-shield/

Porting MQTT to STM32 + W5500 (Ali Cloud) — STM32F103RCT6 + W5500 + Keil5 + Paho MQTT 이식으로 Alibaba Cloud에 연결. 본 프로젝트와 MCU 계열·툴체인이 동일하며, 이 사례에서는 cJSON 파싱을 부분적으로 활용했으나 센서 주기 발행 및 HAL 기반 Timer 포팅은 본 프로젝트에서 완성된 형태로 구현됩니다.

http://maker.wiznet.io/WIZnet/projects/porting-the-mqtt-protocol-to-stm32-and-using-the-w5500-ethernet-chip-to-connect-to-ali-cloud/

세 사례 모두 W5500 하드웨어 TCP/IP + Paho MQTT + STM32 조합을 채택했습니다. 이는 소프트웨어 TCP/IP 스택 없이 신뢰성 있는 MQTT 연결을 구현하는 데 이 아키텍처가 WIZnet 생태계에서 사실상의 표준 패턴임을 보여줍니다.


비즈니스 가치

외부 고객 관점에서 이 아키텍처는 WiFi 연결의 불안정성을 피해야 하는 환경에 강점을 발휘합니다. 공장 내부, 지하 설비, 의료 시설처럼 무선 간섭이 우려되는 곳에서 유선 이더넷 기반 MQTT 센서 노드는 즉각적인 대안입니다. cJSON으로 구조화된 페이로드는 Home Assistant, Node-RED, AWS IoT Core, Alibaba Cloud IoT 등 주요 IoT 플랫폼과 별도 변환 없이 연동됩니다.

WIZnet 내부 관점에서는 W5500의 Sn_RX_RSR 레지스터를 활용한 수신 사전 확인 패턴이 주목할 만합니다. 이는 W5500 소켓 API의 저수준 특성을 적극 활용한 사례로, ioLibrary 사용 가이드나 애플리케이션 노트에 수록할 만한 구현 패턴입니다.

실제 적용 시나리오를 구체적으로 살펴보면 다음과 같습니다.

  • 스마트 빌딩 환경 모니터링: CO2·온습도 센서 데이터를 1초 주기로 MQTT 발행 → BMS(Building Management System)에서 실시간 수집
  • 산업 자동화 원격 제어: device/control 토픽으로 JSON 명령 수신 → 릴레이·모터·밸브 제어
  • 농업 IoT: 토양 수분·온도 데이터 발행 + 관개 시스템 원격 제어
  • 교육·프로토타이핑: ioLibrary + Paho + cJSON 이식 과정을 완전히 문서화한 레퍼런스로서, STM32 기반 IoT 교육 과정의 실습 자료로 활용 가능

한계 및 개선 방향

이 프로젝트는 학습과 프로토타이핑 목적으로 탄탄하게 완성되어 있습니다. 실제 제품화나 대규모 배포로 발전시킬 때 고려할 개선 포인트를 살펴봅니다.

현재 한계

  • 힙 부족(Heap 512B 기본값): Keil 기본 Heap_Size=0x200은 cJSON 동적 할당 반복 시 JSON string creation failed 오류를 유발합니다. 복수 JSON 객체 동시 생성 시 특히 취약합니다.
  • 단일 바이트 SPI 콜백: 바이트 단위 전송은 burst 모드 대비 SPI 오버헤드가 큽니다. 소량 데이터에는 충분하지만, 전송량이 늘어나면 병목이 됩니다.
  • RTOS 부재: while(1) 단일 루프 구조에서 센서 주기·MQTT keepalive·재연결 로직이 타이밍을 공유합니다. 재연결이 잦은 불안정 네트워크 환경에서는 타이밍 경합이 발생할 수 있습니다.
  • 정적 IP 고정: 배포 환경 변경 시 bsp_w5500.c를 수동으로 수정해야 합니다.
  • RST 핀 미제어: W5500 RST를 MCU가 제어하지 않아 불안정한 전원 복구 시 W5500이 초기화되지 않을 수 있습니다.

개선 방향

  • 힙 확대 및 정적 버퍼 전환: Heap_Size0x2000(8KB) 이상으로 늘리거나, cJSON_PrintPreallocated()를 도입해 정적 버퍼로 JSON 문자열을 생성하면 동적 할당 실패 리스크를 근본적으로 제거할 수 있습니다.
  • SPI burst 콜백 적용: reg_wizchip_spiburst_cbfunc()에 burst 송수신 함수를 등록하면 동일한 데이터 전송량에서 SPI 트랜잭션 횟수를 크게 줄일 수 있습니다.
  • FreeRTOS 도입: 센서 태스크, MQTT 루프 태스크, 재연결 태스크를 분리하면 각 로직의 타이밍 독립성이 확보됩니다. STM32F103의 20KB RAM 내에서 최소한의 태스크 구성은 충분히 가능합니다.
  • DHCP 활성화: ioLibrary의 DHCP 클라이언트(dhcp.c)를 추가하면 배포 환경 변경에 유연하게 대응할 수 있습니다.
  • RST 핀 연결: MCU GPIO로 W5500 RST를 제어하고 초기화 시퀀스에서 하드웨어 리셋을 수행하면 전원 불안정 상황에서의 신뢰성이 높아집니다.

FAQ

Q. STM32F103 외 다른 STM32 시리즈에도 적용할 수 있나요?

STM32F4, F7 계열에서도 SPI 핀과 bsp_w5500.c의 HAL 콜백만 수정하면 동일하게 동작합니다. ioLibrary는 MCU 독립적으로 설계되어 있으며, Paho 역시 mqtt_interface.c의 네트워크 함수만 교체하면 어떤 플랫폼에서도 이식 가능합니다.

Q. Keil 힙 크기를 얼마로 설정해야 안전한가요?

cJSON 동적 할당 + MQTT 버퍼 + 스택 여유분을 고려하면 최소 0x1000(4KB), 안정적 운용을 위해서는 0x2000(8KB)를 권장합니다. startup_stm32f103xe.sHeap_Size EQU 값을 수정하고 링커 메모리 맵에서 RAM 여유를 확인한 뒤 적용하십시오.

Q. 정적 IP 대신 DHCP를 사용하려면 어떻게 해야 하나요?

ioLibrary에 포함된 DHCP/dhcp.c를 프로젝트에 추가하고, bsp_w5500.cnetwork_init()에서 NETINFO_STATIC 대신 NETINFO_DHCP로 설정한 뒤 DHCP_run()을 메인 루프에서 주기적으로 호출하면 됩니다.

Q. MQTTX 외에 어떤 브로커/클라이언트와 연동 가능한가요?

MQTT 3.1.1 표준을 준수하므로 Mosquitto, HiveMQ, AWS IoT Core, Alibaba Cloud IoT, Home Assistant Mosquitto Add-on 등 MQTT 3.1.1을 지원하는 모든 브로커와 연동됩니다. 클라우드 브로커 연결 시에는 TLS 지원이 필요한 경우가 많으며, 현재 구현에는 TLS가 포함되어 있지 않습니다.

Q. FreeRTOS 없이 안정적으로 장시간 운용할 수 있나요?

네트워크가 안정적인 환경에서는 현재의 단일 루프 구조도 충분합니다. 다만 브로커 재접속이 반복되는 환경에서는 재연결 루틴이 MQTT 루프 타이밍에 영향을 줄 수 있습니다. MQTTYield() 타임아웃을 적절히 튜닝하고, 재연결 횟수를 모니터링하는 것을 권장합니다.



Summary

This project builds a complete MQTT-based IoT sensor node using an STM32F103 and a WIZnet W5500 Ethernet controller. The W5500's hardwired TCP/IP offloading eliminates the need for a software network stack on the MCU, while cJSON serializes sensor data (temperature, humidity, CO2) into JSON payloads for MQTT publish and subscribe. What sets this project apart from similar examples is its thorough documentation of real-world porting challenges — receive blocking, heap exhaustion, and timer porting — making it a practical reference beyond a simple "hello world" demo.


Overview

One of the most common barriers when adding Ethernet to an MCU is the software TCP/IP stack. Libraries like lwIP demand an RTOS and significant RAM, and they add considerable code complexity. The W5500 solves this at the hardware level: all TCP/IP processing happens inside the chip, and the MCU communicates via SPI using a simple BSD socket API.

This project combines STM32F103 + W5500 + Paho MQTT + cJSON to build a fully functional IoT sensor node. Sensor readings are collected every second, serialized into JSON using cJSON, and published to an MQTT broker. Simultaneously, the node subscribes to a device/control topic to receive JSON commands and drive GPIO outputs. The entire setup can be verified instantly with MQTTX, making it accessible for embedded beginners and engineers who need a fast IoT prototype.


Architecture


Technology Background

W5500 Hardwired TCP/IP Offloading

The W5500 is a hardwired Ethernet controller with a fully implemented TCP/IP stack inside the silicon. The MCU accesses only the register map and socket buffers over SPI — actual TCP connection management, packet retransmission, and ARP handling are all autonomous. The chip supports 8 independent sockets simultaneously, with TX/RX buffers flexibly allocated within a 32 KB total.

In this project, the W5500 opens Socket 0 in TCP mode and maintains a persistent connection to the MQTT broker on port 1883. The MCU calls only send() and recv() — nothing more. As a result, almost none of STM32F103's 20 KB RAM is consumed by the network stack, leaving all MCU cycles free for sensor processing and JSON serialization.

→ W5500 Datasheet: https://docs.wiznet.io/Product/iEthernet/W5500/datasheet

→ WIZnet ioLibrary: https://github.com/Wiznet/ioLibrary_Driver

MQTT and Paho Embedded C

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe messaging protocol built around a central broker. Clients exchange messages by topic string, with minimal connection overhead and no payload format restrictions — making it a natural fit for constrained embedded devices.

This project uses the Paho Embedded C library. Paho consists of MQTTClient.c and the MQTTPacket/ directory. Porting it requires only implementing platform-specific send and receive functions and registering them with the library. Here, mqtt_interface.c wraps the W5500 socket API to serve as Paho's network interface. Calling MQTTYield() periodically in the main loop drives the entire protocol engine.

→ Paho MQTT Embedded C: https://github.com/eclipse/paho.mqtt.embedded-c

→ MQTT Specification (OASIS): https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html

cJSON

cJSON is a lightweight JSON parser and generator written in ANSI C. It ships as a single .c/.h file pair, making it trivial to port to embedded targets. It relies on dynamic memory (malloc/free) internally, so heap size configuration is a hard prerequisite for stable operation.

This project uses cJSON in both directions. On the publish side, cJSON_CreateObject() builds the sensor data structure and cJSON_PrintUnformatted() converts it to a string for the MQTT payload. On the subscribe side, incoming payloads on device/control are parsed with cJSON_Parse(), and keys like lamp and fun are extracted with cJSON_GetObjectItem(). Calling cJSON_Delete() after use is mandatory to avoid heap exhaustion.

→ cJSON GitHub: https://github.com/DaveGamble/cJSON


Technical Highlights

What makes this project a reference rather than just a working demo is how it systematically resolves the real porting barriers in the STM32 HAL + ioLibrary + Paho combination.

① Sn_RX_RSR Pre-check — Eliminating Receive Blocking at the Root

Paho's readPacket() calls mqttread() repeatedly to assemble packets. When the W5500 socket is in blocking mode and no data is available, recv() stalls the entire MQTTYield(). This project reads the Sn_RX_RSR register before calling recv() to check the number of available bytes — returning immediately if zero. It is a clean pattern that leverages W5500 hardware registers directly without modifying any software layer.

② HAL_GetTick()-Based Paho Timer Porting

Some WIZnet examples require a separate MilliTimer_Handler() interrupt. This project replaces it entirely using HAL_GetTick(), which STM32 HAL already provides, to implement Paho's TimerIsExpired, TimerCountdownMS, and related interfaces. No additional interrupt handler is needed — existing HAL infrastructure is fully reused.

③ PRIMASK-Based Nestable Critical Section

The ioLibrary calls critical section entry/exit callbacks around every SPI transaction. This project's implementation saves the PRIMASK register and counts nesting depth, restoring the original interrupt state precisely when the outermost exit is reached. Unlike a naive __disable_irq() approach, this is safe even when critical sections are nested.

④ Single-Byte SPI Callback — A Practical Trade-off

The project uses wiz_spi_readbyte()/wiz_spi_writebyte() single-byte SPI callbacks. The overhead is higher than burst transfer, but the implementation fits in a single HAL_SPI_TransmitReceive() call and is highly portable. For periodic transmission of small sensor payloads, the performance is more than sufficient.


Where WIZnet Fits

In this project, the W5500 acts as a dedicated network processor between the STM32F103 and the Ethernet network.

STM32F103 (Application Processing)
    │ SPI1 (PA5/PA6/PA7, CS=PA3)
    ▼
W5500 (Hardwired TCP/IP)
    ├─ Socket 0: TCP → MQTT Broker :1883
    ├─ TX Buffer: 2KB × 8 sockets
    └─ RX Buffer: 2KB × 8 sockets
    │ RJ45
    ▼
Ethernet Network

bsp_w5500.c handles all initialization: it registers CS, SPI, and critical section callbacks with the ioLibrary, allocates socket buffers via wizchip_init(), and writes the static IP configuration to W5500 internal registers via wizchip_setnetinfo(). After that, the application never needs to be aware of W5500's existence — it simply calls BSD socket APIs.

Reading the W5500 VERSIONR register (expected value: 0x04) serves as a self-diagnostic for SPI connectivity and is the recommended first step in hardware troubleshooting.


Similar Projects on WIZnet Makers

The W5500 + STM32 + Paho MQTT architecture has already been validated across multiple projects in the WIZnet Makers ecosystem.

  • stm32f051-w5500-mqtt-sub-ex — Implements MQTT subscribe using STM32F051 (Cortex-M0) + W5500 + Paho MQTT + ioLibrary on STM32CubeIDE. Shares the same ioLibrary foundation as this project. This project extends that baseline by adding publish, cJSON serialization, and sensor data handling to build a full bidirectional IoT node.

https://maker.wiznet.io/Benjamin/projects/stm32f051-w5500-mqtt-sub-ex/

  • STM32F401RE_MQTT with W5100 ethernet shield — Implements bidirectional MQTT publish/subscribe on STM32F401RE (Cortex-M4) + W5500 over SPI, with UART debugging. The periodic publish routine and debugging structure are similar to this project. This project adds cJSON-structured payloads and live sensor integration on top.

https://maker.wiznet.io/Hannah/projects/stm32f401re-mqtt-with-w5100-ethernet-shield/

  • Porting MQTT to STM32 + W5500 (Ali Cloud) — Ports Paho MQTT on STM32F103RCT6 + W5500 + Keil5 and connects to Alibaba Cloud IoT. Shares the same MCU family and toolchain as this project. While that project uses partial cJSON parsing, the complete sensor publish cycle and HAL-based timer porting are fully realized here.

http://maker.wiznet.io/WIZnet/projects/porting-the-mqtt-protocol-to-stm32-and-using-the-w5500-ethernet-chip-to-connect-to-ali-cloud/

All three projects adopt the same core pattern: W5500 hardwired TCP/IP + Paho MQTT + STM32. This consistency confirms that the architecture is the de facto standard for reliable MQTT connectivity in the WIZnet ecosystem — without a software TCP/IP stack.


Business Value

From a customer perspective, this architecture is well suited for environments where WiFi stability cannot be guaranteed. Factory floors, underground infrastructure, and medical facilities are all settings where wired Ethernet provides a more reliable foundation than wireless. cJSON-structured payloads integrate directly with Home Assistant, Node-RED, AWS IoT Core, and Alibaba Cloud IoT without format conversion.

From a WIZnet perspective, the Sn_RX_RSR pre-check pattern is worth highlighting. It is a practical example of leveraging W5500's low-level hardware registers to solve a real porting problem — a strong candidate for inclusion in application notes or ioLibrary usage guides.

Concrete application scenarios include:

  • Smart building environmental monitoring: CO2, temperature, and humidity data published every second → real-time ingestion by a BMS (Building Management System)
  • Industrial automation remote control: JSON commands received on device/control → relay, motor, or valve actuation
  • Agricultural IoT: Soil moisture and temperature data published + remote irrigation control via subscribe
  • Education and prototyping: Fully documented ioLibrary + Paho + cJSON porting process, ready to use as lab material for STM32-based IoT courses

Limitations and Future Improvements

This project is solid for learning and prototyping. The following points are worth addressing when moving toward production or larger deployments.

Current Limitations

  • Heap too small (default 512 B): Keil's default Heap_Size=0x200 triggers JSON string creation failed errors when cJSON allocates repeatedly. The risk increases when multiple JSON objects are created concurrently.
  • Single-byte SPI callbacks: Byte-by-byte transfer carries higher SPI overhead than burst mode. Sufficient for small payloads, but becomes a bottleneck at higher data rates.
  • No RTOS: The while(1) single-loop structure shares timing between sensor collection, MQTT keepalive, and reconnection logic. Timing contention can occur on unstable networks where reconnects are frequent.
  • Static IP: Any change in deployment environment requires manually editing bsp_w5500.c.
  • RST pin uncontrolled: W5500 RST is not driven by the MCU. Unreliable power recovery may leave W5500 uninitialized.

Improvement Directions

  • Increase heap and switch to static buffers: Raise Heap_Size to at least 0x2000 (8 KB), or adopt cJSON_PrintPreallocated() with a static buffer to eliminate dynamic allocation failures at the root.
  • Enable SPI burst callbacks: Registering burst send/receive functions via reg_wizchip_spiburst_cbfunc() significantly reduces SPI transaction count for the same data volume.
  • Introduce FreeRTOS: Separating sensor, MQTT loop, and reconnect tasks gives each logic path independent timing. A minimal task configuration fits well within STM32F103's 20 KB RAM.
  • Enable DHCP: Adding ioLibrary's DHCP/dhcp.c, switching to NETINFO_DHCP, and calling DHCP_run() in the main loop makes the node flexible across deployment environments.
  • Control the RST pin: Driving W5500 RST from an MCU GPIO and issuing a hardware reset during initialization improves reliability under unstable power conditions.

FAQ

Q. Can this be ported to other STM32 series beyond F103?

Yes. For STM32F4 or F7, only the SPI pin assignments and HAL callbacks in bsp_w5500.c need updating. The ioLibrary is MCU-independent by design, and Paho requires only replacing the network functions in mqtt_interface.c to run on any platform.

Q. What heap size should I configure in Keil?

A minimum of 0x1000 (4 KB) is recommended, with 0x2000 (8 KB) preferred for stable operation. Factor in cJSON dynamic allocation, MQTT buffers, and stack headroom. Modify Heap_Size EQU in startup_stm32f103xe.s and verify remaining RAM in the linker memory map before applying.

Q. How do I switch from static IP to DHCP?

Add DHCP/dhcp.c from the ioLibrary to your project, change NETINFO_STATIC to NETINFO_DHCP in bsp_w5500.c's network_init(), and call DHCP_run() periodically in the main loop.

Q. Which brokers and clients does this work with besides MQTTX?

The implementation follows MQTT 3.1.1, so it works with any compliant broker: Mosquitto, HiveMQ, AWS IoT Core, Alibaba Cloud IoT, and the Home Assistant Mosquitto add-on. Note that cloud brokers often require TLS, which is not included in the current implementation.

Q. Can the node run stably for extended periods without FreeRTOS?

On a stable network, the single-loop structure is sufficient. On networks prone to broker disconnections, the reconnect routine can interfere with MQTTYield() timing. Tuning the MQTTYield() timeout and monitoring reconnect frequency are recommended mitigations.

Documents
Comments Write